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 scan;
75mod scripting;
76mod search;
77mod server;
78mod sets;
79mod streams;
80mod strings;
81mod suggest;
82pub mod table;
83mod tdigest;
84mod topk;
85mod ts;
86mod vectors;
87mod vfilter;
88mod zsets;
89
90pub use args::Args;
91pub use blocking::{Parked, Waiters};
92pub use server::parse_memory;
93pub use table::{COMMANDS, Spec, arity_ok, lookup};
94
95use crate::reply::Out;
96use std::cell::Cell;
97use std::path::{Path, PathBuf};
98use std::sync::atomic::Ordering::{Acquire, Relaxed, Release};
99use std::sync::atomic::{AtomicBool, AtomicU64, AtomicUsize};
100use yo_common::lock::{Held, Lock};
101use yo_common::{Code, Error};
102use yo_kv::cold::Store;
103use yo_kv::{Clock, Db, Keyspace};
104use yo_search::Registry;
105
106use search::cursor::Cursors;
107
108/// How many databases a server has.
109///
110/// Redis's default is sixteen and its `databases` setting can change it. Ours
111/// is sixteen and cannot, which is why `CONFIG GET databases` can answer with a
112/// constant. Nothing in the design needs the number to be fixed; nothing yet
113/// needs it not to be.
114pub const DATABASES: usize = 16;
115
116/// Every database's bit in [`Server::dirty`], which is what a fresh server
117/// starts on so that the first maintenance turn asks all of them.
118///
119/// A `u64` holds sixteen bits with room to spare, and the assertion below is
120/// what turns raising [`DATABASES`] past sixty four into a build failure rather
121/// than a shift that silently drops the databases past the end.
122const ALL_DATABASES: u64 = if DATABASES == 64 {
123    u64::MAX
124} else {
125    (1u64 << DATABASES) - 1
126};
127const _: () = assert!(DATABASES <= 64);
128
129/// How many keys one command throws away before it leaves the rest to the next.
130///
131/// A bound and not a loop to the end, because this runs in front of a client
132/// that is waiting for its reply, and a server a long way over its limit would
133/// otherwise hold that client for as long as it took to walk all the way back
134/// under. Sixty four is a batch's worth of commands, so a server that went over
135/// by what one batch allocated comes back under in one command, and a server
136/// whose limit was just cut in half works through it over the next few thousand
137/// rather than in one long stall. Redis bounds the same loop by a time slice
138/// instead of a count and hands the rest to a timer; there is no timer here, so
139/// the rest goes to the next command that runs.
140const EVICT_BUDGET: usize = 64;
141
142/// The `maxstore` a server with no storage limit carries.
143///
144/// Sixteen exabytes, which is every disk there is and then some, so a server
145/// that set a limit this high and a server that set none behave the same way and
146/// the only difference is what `CONFIG GET maxstore` says. Zero cannot be the
147/// sentinel because zero is a limit with a meaning: nothing may live on the
148/// file.
149const NO_MAXSTORE: u64 = u64::MAX;
150
151/// What a server says to a command that would allocate when it has no room.
152///
153/// Redis's `shared.oomerr`, word for word including the full stop, because
154/// clients match on the `OOM` prefix and people match on the sentence.
155const OOM: &[u8] = b"command not allowed when used memory > 'maxmemory'.";
156
157/// What the connection should do after a command.
158#[derive(Debug, Clone, Copy, PartialEq, Eq)]
159pub enum Flow {
160    /// Read the next command.
161    Continue,
162    /// Write what is buffered and then close, which is what `QUIT` asks for.
163    Close,
164    /// Nothing was written and nothing is owed yet.
165    ///
166    /// The client is on the waiter list and its reply comes when a key it named
167    /// has something in it or when its deadline passes, whichever happens first.
168    /// Until then the connection stops reading commands, because a client that
169    /// is waiting for an answer is not a client that has sent another question.
170    Block,
171}
172
173/// A number one thread adds to and any thread may read.
174///
175/// The add is a load, an add and a store rather than a fetch and add, which on
176/// x86 is three ordinary instructions instead of one locked one. That is sound
177/// because every counter here has exactly one writer, which is what the slots
178/// below are for: two threads never hold the same counter, so nothing can be
179/// lost between the load and the store. A reader can be a command or two behind,
180/// and `INFO` on a running server is behind by the time the reply reaches the
181/// client anyway.
182#[derive(Debug, Default)]
183pub struct Counter(AtomicU64);
184
185impl Counter {
186    /// One more.
187    fn bump(&self) {
188        self.0.store(self.get().wrapping_add(1), Relaxed);
189    }
190
191    /// One fewer, stopping at zero.
192    ///
193    /// The floor is for the gauge, which is the number of open connections: a
194    /// close that arrives without its open, which nothing can do now and a
195    /// misplaced call could, is a number that stays at zero rather than one
196    /// that wraps to eighteen quintillion clients.
197    fn drop_one(&self) {
198        self.0.store(self.get().saturating_sub(1), Relaxed);
199    }
200
201    /// What it says.
202    fn get(&self) -> u64 {
203        self.0.load(Relaxed)
204    }
205
206    /// Back to zero, which is `CONFIG RESETSTAT`.
207    fn zero(&self) {
208        self.0.store(0, Relaxed);
209    }
210}
211
212/// The numbers `INFO` reports that this layer cannot see for itself.
213///
214/// The reactor owns the sockets, so the reactor is what knows how many clients
215/// there are. It counts them here and nothing else does anything with them
216/// except report them.
217#[derive(Debug, Default)]
218pub struct Stats {
219    /// Connections open right now.
220    clients: Counter,
221    /// Connections accepted since the server started.
222    connections: Counter,
223    /// Commands run since the server started, which this layer counts itself.
224    commands: Counter,
225}
226
227impl Stats {
228    /// A connection arrived.
229    pub fn opened(&self) {
230        self.clients.bump();
231        self.connections.bump();
232    }
233
234    /// A connection went away.
235    pub fn closed(&self) {
236        self.clients.drop_one();
237    }
238}
239
240/// Every thread's [`Stats`] added together, which is what `INFO` answers.
241#[derive(Debug, Clone, Copy, Default)]
242pub struct Totals {
243    /// Connections open right now.
244    pub clients: u64,
245    /// Connections accepted since the server started.
246    pub connections: u64,
247    /// Commands run since the server started.
248    pub commands: u64,
249}
250
251thread_local! {
252    /// Which set of counters the running thread writes into.
253    ///
254    /// Claimed the first time a thread counts anything and kept for as long as
255    /// the thread runs. It is a number rather than a pointer, so a thread that
256    /// has counted on one server and then counts on another lands in the same
257    /// place in both, and a process with two servers in it shares the numbering
258    /// between them. That is the tests and it is not `yodb`, which has one.
259    static SLOT: Cell<usize> = const { Cell::new(usize::MAX) };
260}
261
262/// What one thread keeps to itself.
263///
264/// One of these per thread and not one per server, because a number every
265/// thread writes to is a cache line every thread has to own to write to it, and
266/// at a few million commands a second that one line is the server. So each
267/// thread writes into its own and whoever needs the whole picture, which is
268/// `INFO` and the maintenance turn, puts the pieces together when it asks.
269///
270/// A cache line apart for the same reason, so that two threads writing at once
271/// are not two threads passing one line back and forth.
272#[derive(Debug)]
273#[repr(align(64))]
274struct Local {
275    /// What the reactor counts.
276    stats: Stats,
277    /// A counter per command, for `INFO commandstats`.
278    cmdstats: CommandStats,
279    /// Which databases this thread has run a command against since the
280    /// maintenance turn last took the mask.
281    ///
282    /// One bit per database. The thread ors into it and the turn takes the whole
283    /// of it with a swap, which is what keeps a mark that lands during the swap
284    /// from being lost: the worst that can happen is a bit the turn has already
285    /// taken being set again, and that costs one more look at a database with
286    /// nothing to collect.
287    dirty: AtomicU64,
288    /// The mask this thread's maintenance turn is working from.
289    ///
290    /// Its own and not a shared one, because a turn reads it in place and then
291    /// clears bits of it, and a shared mask cleared that way would lose whatever
292    /// another thread marked in between. Every thread turns a loop and every
293    /// loop maintains, so what stops the same work being done twice is not the
294    /// mask but the stripe lock underneath it: two threads that both look at
295    /// database nine take turns, and the second one finds nothing left to move.
296    ///
297    /// Starts with every database set, so a server that has just been built
298    /// looks at all of them once rather than waiting to be told about the ones
299    /// something was loaded into before any command ran.
300    turn: AtomicU64,
301    /// How many of this thread's clients are on the waiter list.
302    ///
303    /// The waiter list is one list behind one lock, and a thread can only answer
304    /// the waiters it parked itself, so a thread with none of its own has no
305    /// reason to take that lock at all. Without this the check is the server
306    /// wide count, and one client blocked anywhere puts every thread through the
307    /// shared lock after every command it runs and again on every disconnect.
308    ///
309    /// Only the thread this belongs to writes it, because parking, answering and
310    /// forgetting a waiter all happen on the thread that read the command, so
311    /// the load and the store either side of a change cannot lose one.
312    parked: AtomicUsize,
313}
314
315impl Default for Local {
316    fn default() -> Local {
317        Local {
318            stats: Stats::default(),
319            cmdstats: CommandStats::default(),
320            dirty: AtomicU64::new(0),
321            turn: AtomicU64::new(ALL_DATABASES),
322            parked: AtomicUsize::new(0),
323        }
324    }
325}
326
327impl Local {
328    /// Note that a command has run against these databases.
329    fn mark(&self, dbs: u64) {
330        self.dirty.store(self.dirty.load(Relaxed) | dbs, Relaxed);
331    }
332
333    /// Add `dbs` to what this thread's turn is going to look at.
334    fn note(&self, dbs: u64) {
335        self.turn.store(self.turn.load(Relaxed) | dbs, Relaxed);
336    }
337
338    /// Take `at` off the list of databases this thread's turn will look at.
339    fn done(&self, at: usize) {
340        self.turn
341            .store(self.turn.load(Relaxed) & !(1u64 << at), Relaxed);
342    }
343
344    /// Whether this thread's turn still has database `at` to look at.
345    fn wanted(&self, at: usize) -> bool {
346        self.turn.load(Relaxed) & (1u64 << at) != 0
347    }
348
349    /// Note that `n` more of this thread's clients are parked.
350    fn blocked(&self, n: usize) {
351        self.parked
352            .store(self.parked.load(Relaxed).saturating_add(n), Relaxed);
353    }
354
355    /// Note that `n` of them are not parked any more.
356    fn woke(&self, n: usize) {
357        self.parked
358            .store(self.parked.load(Relaxed).saturating_sub(n), Relaxed);
359    }
360}
361
362/// Room for one thread, which is what a server starts with.
363fn one_thread() -> Box<[Local]> {
364    slots(1)
365}
366
367/// Room for `threads` of them.
368fn slots(threads: usize) -> Box<[Local]> {
369    (0..threads.max(1)).map(|_| Local::default()).collect()
370}
371
372/// Where the process was started, which is what `dir` defaults to.
373///
374/// A dot if the working directory cannot be read, which happens when it has
375/// been deleted out from under a running process. That is not a reason to
376/// refuse to start a server, and it leaves `BACKUP` to fail with the real error
377/// from the filesystem if anybody asks for one.
378fn working_dir() -> PathBuf {
379    std::env::current_dir().unwrap_or_else(|_| PathBuf::from("."))
380}
381
382/// One command's counters, for `INFO commandstats`.
383///
384/// Three of Redis's five. `usec` and `usec_per_call` are not here because
385/// nothing times a command, and timing one means two clock reads around a call
386/// that takes tens of nanoseconds to begin with. Redis pays that because Redis
387/// has room for it; this does not, and a zero under a name that says microseconds
388/// is worse than an absent field, which is the same rule the rest of `INFO`
389/// follows.
390#[derive(Debug, Clone, Copy, Default)]
391pub struct CommandStat {
392    /// Times the command ran, whatever it answered.
393    pub calls: u64,
394    /// Times it was turned away before it ran, which is the wrong number of
395    /// arguments or no room under `maxmemory`.
396    pub rejected: u64,
397    /// Times it ran and answered with an error.
398    pub failed: u64,
399}
400
401impl CommandStat {
402    /// Whether this command has ever been seen.
403    ///
404    /// A row that has not is left out of the reply, which is what Redis does and
405    /// is why the section is a handful of lines on a working server rather than
406    /// one line per command in the table.
407    const fn seen(&self) -> bool {
408        self.calls != 0 || self.rejected != 0 || self.failed != 0
409    }
410}
411
412/// One command's counters as one thread keeps them.
413///
414/// The same three numbers as [`CommandStat`], which is what they add up to when
415/// `INFO` asks. This is the written form and that is the read one.
416#[derive(Debug, Default)]
417struct Row {
418    /// Times the command ran.
419    calls: Counter,
420    /// Times it was turned away before it ran.
421    rejected: Counter,
422    /// Times it ran and answered with an error.
423    failed: Counter,
424}
425
426/// A counter per command, indexed the way [`table::index_of`] says.
427///
428/// A flat array and not a map, because the dispatcher is already holding the
429/// spec and the spec's position in the table is two addresses subtracted. That
430/// makes the counting a load, an add and a store on a row the previous command
431/// of the same name has already pulled into cache.
432#[derive(Debug)]
433struct CommandStats(Box<[Row]>);
434
435impl Default for CommandStats {
436    fn default() -> CommandStats {
437        CommandStats((0..table::count()).map(|_| Row::default()).collect())
438    }
439}
440
441impl CommandStats {
442    /// The row for one command.
443    fn at(&self, spec: &'static Spec) -> &Row {
444        &self.0[table::index_of(spec)]
445    }
446}
447
448/// Where a database gets its store from, asked by database number.
449///
450/// `None` means that database cannot have one. The caller owns whatever the
451/// stores are cut out of, which for `yodb` is one `.yo` file with a log per
452/// database, and this crate never learns what any of that is.
453pub type StoreSource = dyn FnMut(usize) -> Option<Store> + Send;
454
455/// Every thread that runs commands here shares this server, so it has to be
456/// `Send` and `Sync`, and the check is here so that a type added to it that is
457/// neither is a compile error where it was added rather than an error in the
458/// code that starts the threads.
459const _: () = {
460    const fn shareable<T: Send + Sync>() {}
461    shareable::<Server>();
462};
463
464/// Everything a server holds.
465///
466/// One per process, however many threads are serving out of it. What is inside
467/// is either shared outright, which is the counters and the settings, or behind
468/// a lock, which is the stripes and the few pieces of state a command can
469/// change. What makes this a server rather than a shard is that it is the whole
470/// of what a connection can address.
471pub struct Server {
472    dbs: Vec<Db>,
473    /// How many stripes each database is cut into, the same for all of them.
474    ///
475    /// Kept here as well as in each database so that the flat slot arithmetic
476    /// below is a multiply and a divide against a field on the server rather
477    /// than a walk asking each database how wide it is.
478    width: usize,
479    clock: Clock,
480    started_ms: u64,
481    /// Where the next maintenance turn starts looking, so that a database
482    /// under constant write load cannot hold the other fifteen's space.
483    ///
484    /// Shared, because compaction is asked for from two places: the maintenance
485    /// turn, which is one thread, and a command that went over the memory limit
486    /// and is trying to get back under it, which is any thread. Two threads that
487    /// read the same cursor start on the same database, and what that costs is
488    /// one of them finding the other has already moved what was there.
489    next_db: AtomicUsize,
490    /// One bit per database, set when a command ran against it.
491    ///
492    /// The maintenance turn after every batch used to ask all sixteen
493    /// databases whether they had anything to collect, and asking costs a load
494    /// and a store in each one. Fifteen of those are cold lines on a server
495    /// where every client is on database zero, which is every server, and the
496    /// answer is no every time. This is the cheap half of the question: a
497    /// database nobody has touched since it last said no cannot have started
498    /// saying yes.
499    ///
500    /// What the connections are holding, kept by the engine.
501    ///
502    /// Shared, because every thread has connections and the memory total is one
503    /// total. Each thread adds and subtracts its own change rather than storing
504    /// a figure it worked out, so two threads whose buffers grew in the same
505    /// moment both count.
506    conn_bytes: AtomicUsize,
507    /// The `maxmemory` limit in bytes, zero when there is not one.
508    ///
509    /// Zero is the default and it is the whole reason the check in front of
510    /// every write is one comparison against a field that is already warm. It
511    /// is read by every command on every thread and written by a client that
512    /// sends `CONFIG SET`, so it is a number the threads can share rather than
513    /// a field one of them owns.
514    maxmemory: AtomicU64,
515    /// Where a database gets a store from the first time it needs one.
516    ///
517    /// A closure and not a store, because there are sixteen databases and a
518    /// server that fills memory on database zero should not have opened
519    /// anything for the other fifteen. Nothing is asked of this until a memory
520    /// limit is actually reached, so a server that never fills memory never
521    /// opens a file, and a server that has no file never has one of these.
522    ///
523    /// `None` from the closure means that database cannot have one, which is
524    /// how the caller says the file it opened has no more room for logs.
525    ///
526    /// Behind a lock because it is a closure the caller gave us and there is no
527    /// saying it can be run by two threads at once. It is asked once per
528    /// database, the first time that database has to move something, so a
529    /// server that has reached its memory limit takes this lock sixteen times
530    /// in its life.
531    store: Lock<Option<Box<StoreSource>>>,
532    /// The `maxstore` limit in bytes, `None` when there is not one.
533    ///
534    /// The storage limit, and the other half of the inversion `14` section 4.1
535    /// describes. `maxmemory` is a limit on memory and the right answer to a
536    /// memory limit on a system with a file under it is to move data to the
537    /// file, not to delete it. Deleting is the right answer to a limit on the
538    /// file, and this is that limit.
539    ///
540    /// Zero is not "no limit" here, which is the one place this reads
541    /// differently from `maxmemory` and is the difference that makes a drop in
542    /// cache possible. A storage budget of zero bytes means nothing may live on
543    /// the file, so migration cannot make room and eviction is the only thing
544    /// left, which is Redis exactly. `None` is no limit and is the default,
545    /// which with `noeviction` means the database grows until the disk is full
546    /// and then writes fail, which is what a database does.
547    ///
548    /// Shared between the threads the same way `maxmemory` is, and no limit is
549    /// [`NO_MAXSTORE`] rather than a second field saying whether the first one
550    /// counts. Two fields cannot be read as one, and a limit that was on when
551    /// the bytes were read and off by the time the number was is a limit that
552    /// answers from a server that never existed.
553    maxstore: AtomicU64,
554    /// What [`Server::memory_bytes`] said at the last maintenance turn.
555    ///
556    /// The reading is a walk over every collection in every database and cannot
557    /// go on a command path, so the command path reads this instead and is at
558    /// most one batch behind. What that costs is overshoot: a server can end a
559    /// batch holding one batch's worth of allocation more than its limit before
560    /// anything notices. A batch is 64 commands, so that is bounded by what 64
561    /// commands can allocate and not by how long the server runs.
562    ///
563    /// Only kept up to date when there is a limit to judge it against. A server
564    /// with no `maxmemory` never reads it and never pays for it.
565    ///
566    /// Shared, because it is read in front of every write on every thread and
567    /// written by whichever thread last took a reading. A reader that catches it
568    /// mid write gets one of the two readings and both of them were true a
569    /// moment ago, which is all this number ever claims to be.
570    used: AtomicUsize,
571    /// Which database the next eviction draws from.
572    ///
573    /// Its own cursor and not [`Server::next_db`], because eviction and
574    /// compaction move at different rates and sharing one would make the
575    /// database that gets compacted depend on how many keys were evicted.
576    ///
577    /// Shared for the same reason [`Server::next_db`] is, and with the same
578    /// answer: two threads evicting at once may pick the same database, and one
579    /// of them finds the other got there first and moves on.
580    evict_db: AtomicUsize,
581    /// Which database the next active expiry sweep starts at.
582    ///
583    /// A third cursor for the same reason there is a second one. A sweep runs on
584    /// every turn of the loop and compaction runs when there is dead space, so
585    /// sharing a cursor would make which database gets swept depend on which one
586    /// was last collected.
587    expire_db: AtomicUsize,
588    /// The millisecond the last active expiry sweep ran on, so the next one on
589    /// the same millisecond does not bother.
590    ///
591    /// One for the server and not one per thread, so the sweeping a server does
592    /// is a function of how long it has been running and not of how many threads
593    /// it was started with. Two threads that read the same millisecond can both
594    /// decide to sweep, which costs one extra sweep of a budget that is already
595    /// small and cannot happen twice for the same millisecond more than once per
596    /// thread.
597    expire_ms: AtomicU64,
598    /// Clients parked on a blocking command.
599    ///
600    /// Behind a lock because a client parks on the thread that ran its command
601    /// and is woken by whichever thread later puts something under a key it
602    /// named, and those are not the same thread. The lock is only ever taken to
603    /// park somebody, to serve somebody or to forget a connection that has gone,
604    /// so a command that does not block never touches it.
605    waiters: Lock<Waiters>,
606    /// How many clients are parked.
607    ///
608    /// Beside the list rather than read out of it, because every command asks
609    /// whether anybody is waiting and nearly every answer is no. Taking a lock
610    /// to be told no would be a cache line every thread has to own to ask, which
611    /// is the cost the list was put behind a lock to avoid.
612    ///
613    /// Written under the lock, by whoever changed the list, so the number and
614    /// the list agree except while a change is in progress. A reader that asks
615    /// during one is told about the moment before it, and the worst that costs
616    /// is a walk of the list that serves nobody or one that has not started yet
617    /// and happens on the next command instead.
618    parked: AtomicUsize,
619    /// Sockets `MIGRATE` is holding open to the servers it has talked to.
620    ///
621    /// Empty on a server nobody has migrated a key out of, which is nearly all
622    /// of them, and it costs a vector's three words to be empty.
623    ///
624    /// Behind a lock because a socket cannot be written by two threads at once
625    /// and a cache of them cannot be searched by one while another is taking an
626    /// entry out. It is held for the whole of a migration, which is a round trip
627    /// to another server, so two threads migrating at the same time take turns.
628    /// That is the right way round: the alternative is a socket per thread per
629    /// peer, and a `MIGRATE` is not what a server spends its time on.
630    peers: Lock<migrate::Peers>,
631    /// What each thread that runs commands here keeps to itself.
632    ///
633    /// A fixed list, because a thread reading its own entry must not have the
634    /// list move under it, and how many threads there will be is known before
635    /// any of them starts. A server nobody told otherwise has one.
636    locals: Box<[Local]>,
637    /// How many entries have been handed out.
638    claimed: AtomicUsize,
639    /// The next client id, which is what `CLIENT ID` answers.
640    ///
641    /// On the server and not on a front, because CLIENT LIST and CLIENT KILL
642    /// name a client by this number across the whole server, and two threads
643    /// counting on their own would hand the same number to two clients. Starts
644    /// at one so that zero is never a client, which is what makes it usable as
645    /// the id of a command that came from nowhere.
646    next_client: AtomicU64,
647    /// Where `BACKUP` puts its files, and where `CONFIG GET dir` points.
648    ///
649    /// Absolute, and resolved once when the server is built rather than every
650    /// time somebody asks. `BACKUP LIST` answers absolute paths and a client is
651    /// entitled to hand one of them to a copy tool, so a relative path that
652    /// meant something different after a `chdir` would be a path that stops
653    /// working for reasons nobody could see.
654    dir: PathBuf,
655    /// What backup is running, if one is.
656    ///
657    /// On the server and not on a session, because a backup outlives the
658    /// connection that asked for it and any other connection can seal it.
659    ///
660    /// Behind a lock because there is one backup at a time and any thread can be
661    /// the one that starts, seals or abandons it. It is held while the base file
662    /// is written, which is what keeps two `BACKUP START` commands from writing
663    /// over each other's files.
664    backup: Lock<backup::State>,
665    /// Whether a sealed backup is sitting on disk.
666    ///
667    /// Beside the state rather than read out of it, because every batch of
668    /// commands asks whether there is a backup old enough to sweep away and on
669    /// nearly every server the answer is that there is no backup at all. A load
670    /// answers that. Written under the lock by whoever moved the phase, so a
671    /// reader that asks mid-change sees the moment before and sweeps one batch
672    /// later, which is a file staying on disk for a few microseconds longer than
673    /// it had to.
674    sealed: AtomicBool,
675    /// The search indexes and the names pointing at them.
676    ///
677    /// On the server and not on a database, which is the one collection in this
678    /// build that is. A real server keeps its indexes in the search module, the
679    /// module has one table, and `SELECT 1` followed by `FT._LIST` lists the
680    /// indexes made on database zero. `search.rs` has the rest of why.
681    ///
682    /// A server nobody has made an index on holds two empty vectors here, which
683    /// is six words and no allocation.
684    ///
685    /// Behind a lock because an index is made and dropped by whichever thread
686    /// ran the command, and the table it goes in is one table. Only the `FT`
687    /// commands take it, so nothing a working server spends its time on comes
688    /// through here.
689    search: Lock<Registry>,
690    /// The replies that came back in pieces and have pieces left.
691    ///
692    /// Beside the indexes rather than inside one, because a cursor is read
693    /// under its own number and a real server resolves the index name on a read
694    /// and then pays no attention to it, so a cursor made on one index reads
695    /// through the name of another. Behind a lock for the reason the registry is
696    /// behind one, and a server nobody has opened a cursor on holds an empty map
697    /// here.
698    cursors: Lock<Cursors>,
699    /// The script bodies `EVALSHA` runs, by their digests.
700    ///
701    /// On the server rather than on a connection, because that is the whole
702    /// point of the cache. A client loads its scripts once when it starts up,
703    /// on whichever connection it happened to open first, and then sends nothing
704    /// but digests forever after, from every connection in its pool.
705    ///
706    /// Behind a lock because loading is a write and every thread can be the one
707    /// doing it. Held only long enough to add a body or copy one out, never
708    /// across a run: a running script calls commands, and those take locks of
709    /// their own.
710    scripts: Lock<lua::Scripts>,
711    /// Every library `FUNCTION LOAD` has taken, and what each one registered.
712    ///
713    /// Data only. A callback is a Lua value and there is an interpreter per
714    /// thread, so what is here is the name, the code, the digest of the code and
715    /// one row per function, and every thread compiles the code for itself the
716    /// first time one of its clients calls into the library.
717    libraries: Lock<lua::library::Libraries>,
718    /// Set by `SHUTDOWN`, and read by whatever is turning the loop.
719    ///
720    /// A flag rather than an exit, because the command layer is not what owns
721    /// the process. It runs inside a batch that has other commands behind it
722    /// and inside a driver that has a socket file to take away and a file to
723    /// close, and a server that calls `exit` from a command handler skips all
724    /// of that. So the command says stop and the driver stops, on the same turn
725    /// and through the same door a signal uses.
726    stopping: AtomicBool,
727}
728
729impl Server {
730    /// A server with [`DATABASES`] empty databases on the system clock.
731    #[must_use]
732    pub fn new() -> Server {
733        let clock = Clock::system();
734        Server {
735            dbs: (0..DATABASES)
736                .map(|_| Db::with_clock(clock.clone(), 1))
737                .collect(),
738            width: 1,
739            started_ms: clock.now_ms(),
740            clock,
741            next_db: AtomicUsize::new(0),
742            conn_bytes: AtomicUsize::new(0),
743            maxmemory: AtomicU64::new(0),
744            store: Lock::new(None),
745            maxstore: AtomicU64::new(NO_MAXSTORE),
746            used: AtomicUsize::new(0),
747            evict_db: AtomicUsize::new(0),
748            expire_db: AtomicUsize::new(0),
749            expire_ms: AtomicU64::new(0),
750            waiters: Lock::default(),
751            parked: AtomicUsize::new(0),
752            peers: Lock::default(),
753            locals: one_thread(),
754            claimed: AtomicUsize::new(0),
755            next_client: AtomicU64::new(1),
756            dir: working_dir(),
757            backup: Lock::default(),
758            sealed: AtomicBool::new(false),
759            search: Lock::new(Registry::new()),
760            cursors: Lock::default(),
761            scripts: Lock::default(),
762            libraries: Lock::default(),
763            stopping: AtomicBool::new(false),
764        }
765    }
766
767    /// A server whose databases are cut into `width` stripes each.
768    ///
769    /// Not reachable from the command line yet. Every command group answers on
770    /// a server of any width now and so does everything that walks a whole
771    /// database, and the tests run each group at a width of one and a width of
772    /// eight and check the two agree.
773    ///
774    /// What is left before this is what `--threads` sets is the engine. A
775    /// database being several objects is what makes more than one thread
776    /// possible, and it is not what makes more than one thread happen.
777    #[must_use]
778    pub fn with_width(width: usize) -> Server {
779        let mut server = Server::new();
780        // The server's own clock and not a fresh one, because a database
781        // reading a different clock from the server it is on is a database
782        // whose keys expire against a time nobody set.
783        let clock = server.clock.clone();
784        server.dbs = (0..DATABASES)
785            .map(|_| Db::with_clock(clock.clone(), width))
786            .collect();
787        server.width = server.dbs[0].width();
788        server
789    }
790
791    /// A server on a clock the caller moves by hand, for tests.
792    #[must_use]
793    pub fn with_clock(clock: Clock) -> Server {
794        Server {
795            dbs: (0..DATABASES)
796                .map(|_| Db::with_clock(clock.clone(), 1))
797                .collect(),
798            width: 1,
799            started_ms: clock.now_ms(),
800            clock,
801            next_db: AtomicUsize::new(0),
802            conn_bytes: AtomicUsize::new(0),
803            maxmemory: AtomicU64::new(0),
804            store: Lock::new(None),
805            maxstore: AtomicU64::new(NO_MAXSTORE),
806            used: AtomicUsize::new(0),
807            evict_db: AtomicUsize::new(0),
808            expire_db: AtomicUsize::new(0),
809            expire_ms: AtomicU64::new(0),
810            waiters: Lock::default(),
811            parked: AtomicUsize::new(0),
812            peers: Lock::default(),
813            locals: one_thread(),
814            claimed: AtomicUsize::new(0),
815            next_client: AtomicU64::new(1),
816            dir: working_dir(),
817            backup: Lock::default(),
818            sealed: AtomicBool::new(false),
819            search: Lock::new(Registry::new()),
820            cursors: Lock::default(),
821            scripts: Lock::default(),
822            libraries: Lock::default(),
823            stopping: AtomicBool::new(false),
824        }
825    }
826
827    /// One database, by index.
828    ///
829    /// A caller that knows which key it wants names the one stripe the key is
830    /// on rather than working over the whole thing, which is what `at` and its
831    /// neighbours on [`Db`] are for. A caller that is about a database rather
832    /// than about a key, which is the snapshot walk and a setting, works over
833    /// all of them.
834    ///
835    /// The database is marked as having had something run against it, which is
836    /// what this does that [`Server::striped_ref`] does not. Anything that only
837    /// reads asks for that one and leaves the mark alone.
838    ///
839    /// The borrow is shared, and what makes that enough is that a database is
840    /// several stripes behind a lock each. A caller that wants to change
841    /// something holds the stripe it is changing, so two threads working on two
842    /// keys work at once and two working on one key take turns, which is the
843    /// whole point of cutting a database up.
844    ///
845    /// # Panics
846    ///
847    /// If `i` is not a database. `SELECT` is the only way a client changes the
848    /// index and it checks, so an index that is out of range here is a bug in
849    /// the caller and not something a client can ask for.
850    pub fn striped(&self, i: usize) -> &Db {
851        self.mine().mark(1u64 << i);
852        &self.dbs[i]
853    }
854
855    /// Every keyspace on the server, which is every stripe of every database.
856    ///
857    /// What the aggregates walk. A total over the whole server is a total over
858    /// all of these and the stripe boundaries do not appear in it, which is
859    /// what makes the numbers `INFO` reports the same numbers whatever the
860    /// server was cut into.
861    fn keyspaces(&self) -> impl Iterator<Item = Held<'_, Keyspace>> {
862        self.dbs
863            .iter()
864            .flat_map(|db| (0..db.width()).map(|i| db.hold_stripe(i)))
865    }
866
867    /// How many keyspaces there are, counting every stripe of every database.
868    ///
869    /// The maintenance turns walk these rather than the databases, because a
870    /// stripe is the thing that holds an arena and a deadline heap and so it is
871    /// the thing that has anything to collect.
872    const fn slots(&self) -> usize {
873        DATABASES * self.width
874    }
875
876    /// Which database slot `i` belongs to.
877    const fn slot_db(&self, i: usize) -> usize {
878        i / self.width
879    }
880
881    /// Keyspace `i` of [`Server::slots`].
882    fn slot(&self, i: usize) -> Held<'_, Keyspace> {
883        let (db, stripe) = (i / self.width, i % self.width);
884        self.dbs[db].hold_stripe(stripe)
885    }
886
887    /// Where `BACKUP` writes and what `CONFIG GET dir` answers.
888    #[must_use]
889    pub fn dir(&self) -> &Path {
890        &self.dir
891    }
892
893    /// Point the server at a different directory, which `yodb serve --dir` does.
894    ///
895    /// Only before it is serving. There is no `CONFIG SET dir` here and there
896    /// is none on a real server either without turning protected configs on,
897    /// for the good reason that moving it out from under a running backup would
898    /// leave files nothing can find again.
899    pub fn set_dir(&mut self, dir: PathBuf) {
900        self.dir = dir;
901    }
902
903    /// Drop a sealed backup that has outlived `backup-sealed-ttl`.
904    ///
905    /// Once per batch, from the same maintenance turn that collects the arena.
906    /// It reads two fields and returns on a server that has never taken a
907    /// backup, which is nearly all of them.
908    pub fn backup_expire(&self) {
909        backup::expire(self);
910    }
911
912    /// Ask for the server to stop, which is what `SHUTDOWN` does.
913    ///
914    /// It sets a flag and returns. Nothing here closes a socket, flushes a file
915    /// or ends the process, because none of those belong to this layer, and a
916    /// batch that is halfway through still has to finish and be written out.
917    pub fn stop(&self) {
918        self.stopping.store(true, Release);
919    }
920
921    /// Whether somebody has asked the server to stop.
922    ///
923    /// Read once per turn by the loop, next to the flag a signal sets. The two
924    /// mean the same thing and are separate only because one arrives from the
925    /// operating system and the other from a client.
926    #[must_use]
927    pub fn stopping(&self) -> bool {
928        self.stopping.load(Acquire)
929    }
930
931    /// One database, by index, without taking it mutably.
932    ///
933    /// What the prefetch stage needs. It runs for all 64 commands in a batch
934    /// before any of them executes, so it cannot hold the mutable borrow `run`
935    /// is about to want, and it does not need one: warming a cache line reads
936    /// nothing and changes nothing.
937    #[must_use]
938    pub fn striped_ref(&self, i: usize) -> &Db {
939        &self.dbs[i]
940    }
941
942    /// The stripe that answers for a database when a setting is read back.
943    ///
944    /// A ladder setting and an eviction policy are one number on a real server,
945    /// and the fact that every stripe of every database carries a copy of it is
946    /// ours rather than the client's problem. A write puts the same value on
947    /// every one of them, so any stripe answers for all of them and this is the
948    /// first one.
949    fn settings(&self) -> Held<'_, Keyspace> {
950        self.dbs[0].hold_stripe(0)
951    }
952
953    /// Take a new clock reading, which every database is looking at.
954    ///
955    /// Once per turn of the event loop, which is the only place time moves. A
956    /// command asking what the time is gets the answer the whole batch got, so
957    /// two keys written by the same batch expire together (`04` section 3).
958    ///
959    /// Every thread does this on every turn of its own loop and they do not
960    /// have to agree about when. The reading is only stored when the
961    /// millisecond has changed, so what the threads are sharing is a line that
962    /// is written about a thousand times a second and read millions.
963    pub fn refresh_clock(&self) {
964        self.clock.refresh();
965    }
966
967    /// Move every clock here on by `ms`, for tests about expiry.
968    ///
969    /// The same thing [`Server::set_clock_ms`] does and by the same argument,
970    /// except that it moves from wherever the clock is rather than to a stated
971    /// moment, which is what a test that wants a key to have expired asks for.
972    pub fn advance_clock_ms(&self, ms: u64) {
973        let now = self.clock.now_ms() + ms;
974        self.set_clock_ms(now);
975    }
976
977    /// Move every clock here to `ms` by hand, for tests about expiry.
978    ///
979    /// A test cannot wait a hundred seconds and a test that waits a hundred
980    /// milliseconds is a test that fails on a loaded machine, so time moves on
981    /// request. The system clock underneath will overwrite this on the next
982    /// [`Server::refresh_clock`], which is why this is only useful in a test
983    /// that drives commands directly rather than through the event loop.
984    pub fn set_clock_ms(&self, ms: u64) {
985        self.clock.set(ms);
986    }
987
988    /// Seconds since this server was built.
989    #[must_use]
990    pub fn uptime_secs(&self) -> u64 {
991        self.clock.now_ms().saturating_sub(self.started_ms) / 1000
992    }
993
994    /// Bytes held by every database's index and arena, plus the read and reply
995    /// buffers of every connection.
996    ///
997    /// The buffers are in here because they are real and because Redis counts
998    /// its own, so leaving them out would make the one number people compare
999    /// flattering rather than true. They are not a database, so nothing in the
1000    /// keyspace can change them and the engine has to say when they move.
1001    #[must_use]
1002    pub fn memory_bytes(&self) -> usize {
1003        self.keyspaces().map(|db| db.memory_bytes()).sum::<usize>() + self.conn_bytes()
1004    }
1005
1006    /// What the keyspace itself is holding, live records only.
1007    ///
1008    /// `used_memory` minus this is what the store costs to run: the index, the
1009    /// space dead records are sitting in until compaction gets to them, and the
1010    /// connections' buffers.
1011    #[must_use]
1012    pub fn dataset_bytes(&self) -> usize {
1013        self.keyspaces()
1014            .map(|db| db.map().arena().live_bytes() as usize)
1015            .sum()
1016    }
1017
1018    /// Bytes the arenas are holding, live and dead together.
1019    #[must_use]
1020    pub fn arena_bytes(&self) -> usize {
1021        self.keyspaces()
1022            .map(|db| db.map().arena().reserved_bytes() as usize)
1023            .sum()
1024    }
1025
1026    /// Bytes the indexes are holding.
1027    #[must_use]
1028    pub fn index_bytes(&self) -> usize {
1029        self.keyspaces()
1030            .map(|db| db.map().index().memory_bytes())
1031            .sum()
1032    }
1033
1034    /// What arena compaction has cost, across every database.
1035    ///
1036    /// The write amplification of value separation, which is invisible from the
1037    /// outside otherwise: a client that writes a megabyte can leave the store
1038    /// copying several more, and the only sign of it without these is that the
1039    /// writes got slower.
1040    #[must_use]
1041    pub fn compaction(&self) -> yo_kv::Compaction {
1042        self.keyspaces().map(|db| db.map().compaction()).fold(
1043            yo_kv::Compaction::default(),
1044            |a, b| yo_kv::Compaction {
1045                walked: a.walked + b.walked,
1046                moved: a.moved + b.moved,
1047                bytes: a.bytes + b.bytes,
1048            },
1049        )
1050    }
1051
1052    /// Arena segments whose pages are real, across every database.
1053    #[must_use]
1054    pub fn segment_count(&self) -> usize {
1055        self.keyspaces()
1056            .map(|db| db.map().arena().resident_segments())
1057            .sum()
1058    }
1059
1060    /// What the connections' read and reply buffers are holding.
1061    #[must_use]
1062    pub fn conn_bytes(&self) -> usize {
1063        self.conn_bytes.load(Relaxed)
1064    }
1065
1066    /// Note that the connections are holding `delta` bytes more than they were,
1067    /// or fewer when it is negative.
1068    ///
1069    /// A delta and not a total because the alternative is a walk over every
1070    /// connection, and the walk would have to happen on a turn of the loop
1071    /// rather than when `INFO` asks, which puts the cost of a report on the
1072    /// command path of a server nobody is asking.
1073    pub fn note_conn_bytes(&self, delta: isize) {
1074        // A read and a write and not a fetch and add, because the number is a
1075        // sum of signed changes and the saturating part has to happen in the
1076        // middle. Two threads that change their buffers in the same instant can
1077        // lose one of the two changes, which is a report that is a few kilobytes
1078        // out until the next connection on either thread moves it again.
1079        self.conn_bytes
1080            .store(self.conn_bytes().saturating_add_signed(delta), Relaxed);
1081    }
1082
1083    /// Keys reclaimed by running into them after their deadline.
1084    #[must_use]
1085    pub fn expired_keys(&self) -> u64 {
1086        self.keyspaces().map(|db| db.expired_keys()).sum()
1087    }
1088
1089    /// Keys thrown away to make room, which is the other number entirely.
1090    #[must_use]
1091    pub fn evicted_keys(&self) -> u64 {
1092        self.keyspaces().map(|db| db.evicted_keys()).sum()
1093    }
1094
1095    /// Every command that has been seen, with its counters.
1096    ///
1097    /// Only the ones that have. A server reports a handful of lines rather than
1098    /// one per command in the table, which is what Redis does and is the
1099    /// difference between a section a person can read and one they cannot.
1100    pub fn command_stats(&self) -> impl Iterator<Item = (&'static str, CommandStat)> {
1101        (0..table::count())
1102            .map(|at| (table::name_at(at), self.command_stat(at)))
1103            .filter(|(_, row)| row.seen())
1104    }
1105
1106    /// One command's counters, added up over every thread.
1107    fn command_stat(&self, at: usize) -> CommandStat {
1108        let mut sum = CommandStat::default();
1109        for thread in &self.locals {
1110            let row = &thread.cmdstats.0[at];
1111            sum.calls += row.calls.get();
1112            sum.rejected += row.rejected.get();
1113            sum.failed += row.failed.get();
1114        }
1115        sum
1116    }
1117
1118    /// The counters the calling thread writes into.
1119    ///
1120    /// The first call on a thread claims a set and every call after it is a
1121    /// thread local read and an index. A server asked to count from more threads
1122    /// than it was built for wraps round and shares a set, which loses the odd
1123    /// count between two threads and cannot happen to a server `yodb serve`
1124    /// built, because that one is told how many threads it will have before it
1125    /// starts any of them.
1126    pub fn counted(&self) -> &Stats {
1127        &self.mine().stats
1128    }
1129
1130    /// The next client id, taken.
1131    ///
1132    /// Every accept anywhere on this server comes through here, so no two
1133    /// clients share a number however many threads are accepting.
1134    pub fn next_client(&self) -> u64 {
1135        self.next_client.fetch_add(1, Relaxed)
1136    }
1137
1138    /// Which set of per thread state the calling thread is on.
1139    ///
1140    /// The number a blocked client is filed under, so that the thread holding
1141    /// that client's connection is the one that answers it. Claims a set on the
1142    /// first call the same way [`Server::counted`] does, and gives back the same
1143    /// number every time after.
1144    pub fn my_slot(&self) -> usize {
1145        self.mine_at()
1146    }
1147
1148    /// Everything the calling thread keeps to itself.
1149    fn mine(&self) -> &Local {
1150        &self.locals[self.mine_at()]
1151    }
1152
1153    /// The calling thread's place in `locals`, claiming one if it has none.
1154    ///
1155    /// Wraps round when more threads count here than the server was built for,
1156    /// which shares a set between two threads and loses the odd count. That
1157    /// cannot happen to the server `yodb serve` builds, because it is told how
1158    /// many threads it will have before it starts any of them.
1159    fn mine_at(&self) -> usize {
1160        let mut slot = SLOT.get();
1161        if slot == usize::MAX {
1162            slot = self.claimed.fetch_add(1, Relaxed);
1163            SLOT.set(slot);
1164        }
1165        slot % self.locals.len()
1166    }
1167
1168    /// Every thread's numbers added together, which is what `INFO` reports.
1169    #[must_use]
1170    pub fn totals(&self) -> Totals {
1171        let mut sum = Totals::default();
1172        for thread in &self.locals {
1173            sum.clients += thread.stats.clients.get();
1174            sum.connections += thread.stats.connections.get();
1175            sum.commands += thread.stats.commands.get();
1176        }
1177        sum
1178    }
1179
1180    /// Put the totals back to zero, which is `CONFIG RESETSTAT`.
1181    ///
1182    /// Every thread's set and not only the one asking, since the number the
1183    /// client is resetting is the sum it was just shown. The open connections
1184    /// are left alone because that is a gauge and not a total: the connections
1185    /// are still open.
1186    pub fn reset_stats(&self) {
1187        for thread in &self.locals {
1188            thread.stats.connections.zero();
1189            thread.stats.commands.zero();
1190        }
1191    }
1192
1193    /// Say how many threads will run commands here, before any of them does.
1194    ///
1195    /// What it changes is how many sets of counters there are. Called once at
1196    /// startup by whoever is about to start the threads, and calling it on a
1197    /// running server throws away what has been counted so far, which is why it
1198    /// wants the server to itself.
1199    pub fn set_threads(&mut self, threads: usize) {
1200        self.locals = slots(threads);
1201        self.claimed = AtomicUsize::new(0);
1202    }
1203
1204    /// The `maxmemory` limit in bytes, zero when there is not one.
1205    #[must_use]
1206    pub fn maxmemory(&self) -> u64 {
1207        self.maxmemory.load(Relaxed)
1208    }
1209
1210    /// Set the limit, and take a reading straight away.
1211    ///
1212    /// The reading is here rather than left to the next maintenance turn because
1213    /// a client that sets the limit and sends a write in the same batch expects
1214    /// the write to be judged against the limit it just set, and because the
1215    /// cached number is meaningless until the first time there is a limit to
1216    /// compare it with.
1217    ///
1218    /// Turning the limit on also turns on the running total every slab keeps of
1219    /// what its collections hold, and turning it off turns that back off, so a
1220    /// server with no limit is not paying to count something nobody reads. The
1221    /// first reading after switching it on is the walk that the total starts
1222    /// from, and it is the only walk.
1223    pub fn set_maxmemory(&self, bytes: u64) {
1224        self.maxmemory.store(bytes, Relaxed);
1225        for db in &self.dbs {
1226            db.track_memory(bytes != 0);
1227        }
1228        self.used.store(self.settled_memory(), Relaxed);
1229    }
1230
1231    /// Say where a database should get its store from when it needs one.
1232    ///
1233    /// This is what turns the eviction inversion on. Until it is called every
1234    /// database answers a memory limit by evicting, which is Redis, and after it
1235    /// is called a database under memory pressure moves values to whatever the
1236    /// closure hands back instead of throwing keys away.
1237    ///
1238    /// Called at most once per database and only under pressure, so a server
1239    /// that is given a file and never fills memory never touches it.
1240    pub fn set_store_source(
1241        &mut self,
1242        source: impl FnMut(usize) -> Option<Store> + Send + 'static,
1243    ) {
1244        *self.store.lock() = Some(Box::new(source));
1245    }
1246
1247    /// Whether this server has been given somewhere to put cold values.
1248    #[must_use]
1249    pub fn has_store_source(&self) -> bool {
1250        self.store.lock().is_some()
1251    }
1252
1253    /// Open database `at`'s store, if it has not got one and there is one to be
1254    /// had.
1255    ///
1256    /// A store that will not open leaves the database where it was, which is
1257    /// evicting, because a memory limit that cannot be answered by moving data
1258    /// still has to be answered.
1259    fn attach_store(&self, at: usize) {
1260        if self.slot(at).store_bytes().is_some() {
1261            return;
1262        }
1263        // The closure is run with its lock held and the keyspace is taken after
1264        // it has answered, so the file is opened once however many threads asked
1265        // for it and the stripe is not held while a file is being opened.
1266        let mut source = self.store.lock();
1267        let Some(source) = source.as_mut() else {
1268            return;
1269        };
1270        if let Some(blocks) = source(at) {
1271            self.slot(at).attach(blocks);
1272        }
1273    }
1274
1275    /// The `maxstore` limit in bytes, `None` when there is not one.
1276    #[must_use]
1277    pub fn maxstore(&self) -> Option<u64> {
1278        match self.maxstore.load(Relaxed) {
1279            NO_MAXSTORE => None,
1280            bytes => Some(bytes),
1281        }
1282    }
1283
1284    /// Set the storage limit, or clear it with `None`.
1285    ///
1286    /// Nothing is read here the way [`Server::set_maxmemory`] reads the memory
1287    /// total, because this limit is compared against a number the store keeps
1288    /// and answers on demand, not against a walk.
1289    pub fn set_maxstore(&self, bytes: Option<u64>) {
1290        self.maxstore.store(bytes.unwrap_or(NO_MAXSTORE), Relaxed);
1291    }
1292
1293    /// What every attached store is holding, for `INFO memory`.
1294    ///
1295    /// Zero on a server with nothing attached, which is not the same as a server
1296    /// whose file is empty, and [`Server::regime`] is the field that tells those
1297    /// two apart.
1298    #[must_use]
1299    pub fn store_bytes(&self) -> u64 {
1300        self.keyspaces().filter_map(|db| db.store_bytes()).sum()
1301    }
1302
1303    /// What the file has been asked to do, added up over every database.
1304    ///
1305    /// Counters and not levels, so they only ever go up and a run is the
1306    /// difference between two readings. G9 is a ratio over these: the faults a
1307    /// run took, divided by the point reads it issued, has to come out at 1.05
1308    /// or less with a working set ten times memory. There is no way to work that
1309    /// out from outside the server, so it is reported rather than inferred.
1310    ///
1311    /// A fault is a read that went to the store. Whether it also went to the
1312    /// device depends on the store: a log serves a read out of a resident page
1313    /// without touching anything. At ten times memory almost every fault is a
1314    /// real read, which is why the gate is written against this number, but the
1315    /// two are not the same thing and a run tight against the bar should be
1316    /// checked against what the operating system says.
1317    #[must_use]
1318    pub fn cold_stats(&self) -> yo_kv::tier::Stats {
1319        let mut total = yo_kv::tier::Stats::default();
1320        for db in self.keyspaces() {
1321            let Some(tier) = db.tier() else { continue };
1322            let s = tier.stats();
1323            total.demoted += s.demoted;
1324            total.promoted += s.promoted;
1325            total.faults += s.faults;
1326            total.served += s.served;
1327            total.bytes_out += s.bytes_out;
1328            total.bytes_in += s.bytes_in;
1329        }
1330        total
1331    }
1332
1333    /// Which way this server answers a memory limit, in one word for `INFO`.
1334    ///
1335    /// `evict` is Redis: a memory limit throws keys away. `migrate` is the
1336    /// inversion: a memory limit moves values to the file and nothing stored is
1337    /// lost. A server reports one word rather than leaving an operator to work
1338    /// it out from a limit, a setting and whether a file happens to be open.
1339    #[must_use]
1340    pub fn regime(&self) -> &'static str {
1341        if (0..self.slots()).any(|at| self.migrates(at)) {
1342            "migrate"
1343        } else {
1344            "evict"
1345        }
1346    }
1347
1348    /// Whether database `at` answers a memory limit by moving values to the
1349    /// file rather than by throwing keys away.
1350    ///
1351    /// Three things have to hold. There has to be somewhere to move them, which
1352    /// is a store attached to that database or a source that can open one, and
1353    /// on a server that was never given a file this is false everywhere and
1354    /// every database behaves exactly as it did.
1355    /// The storage budget has to be more than nothing, which is what
1356    /// `maxstore 0` says it is not. And the file has to be under that budget,
1357    /// because a full file is a storage limit reached and eviction is the right
1358    /// answer to a storage limit.
1359    fn migrates(&self, at: usize) -> bool {
1360        let cap = self.maxstore();
1361        if cap == Some(0) {
1362            return false;
1363        }
1364        // Out of the stripe first. A match keeps whatever it is looking at
1365        // alive for the whole of itself, and that would be this stripe held
1366        // across the arms for no reason.
1367        let bytes = self.slot(at).store_bytes();
1368        match bytes {
1369            Some(held) => cap.is_none_or(|cap| held < cap),
1370            // Nothing attached, but somewhere to get one from the moment this
1371            // database needs it, which is what makes the answer yes rather than
1372            // no. Opening it here would mean `INFO` opened files.
1373            None => self.store.lock().is_some(),
1374        }
1375    }
1376
1377    /// Take a fresh memory reading, which the maintenance turn does once a batch.
1378    ///
1379    /// Nothing at all when there is no limit, which is the default and is every
1380    /// server that has not asked for one.
1381    pub fn refresh_memory(&self) {
1382        if self.maxmemory() != 0 {
1383            self.used.store(self.settled_memory(), Relaxed);
1384        }
1385    }
1386
1387    /// [`Server::memory_bytes`], asked the cheap way.
1388    ///
1389    /// The same number. The difference is that this asks each database only
1390    /// about the collections that could have moved since the last time, which is
1391    /// what a batch touched rather than what the server holds, so it can be
1392    /// asked once a batch and again on every command that is over the limit.
1393    fn settled_memory(&self) -> usize {
1394        self.keyspaces()
1395            .map(|mut db| db.settled_memory_bytes())
1396            .sum::<usize>()
1397            + self.conn_bytes()
1398    }
1399
1400    /// Make room under the `maxmemory` limit, throwing keys away if that is what
1401    /// it takes. Answers whether there is anything left it could throw away.
1402    ///
1403    /// Redis runs the same thing from `processCommand` before every command and
1404    /// so does this: a client that writes has to be judged at the moment it
1405    /// writes, not a batch later, or the limit is a suggestion.
1406    ///
1407    /// Three things happen in the loop and all three are needed. Eviction picks
1408    /// a key and drops it. Compaction gives the pages back, because dropping a
1409    /// key marks its record dead and returns nothing on its own, so a loop that
1410    /// only evicted would throw the whole keyspace away and watch the number
1411    /// stay where it was. The reading is taken again each time round, because
1412    /// the two of them together are the only thing that moves it.
1413    ///
1414    /// # Why running out of budget is not a no
1415    ///
1416    /// `false` means there was nothing left to evict, which is `noeviction`, or
1417    /// a `volatile` policy on a database where nothing has a deadline, or a
1418    /// keyspace that is already empty. It does not mean the server is still over
1419    /// its limit, and that difference is Redis's: `performEvictions` answers
1420    /// `EVICT_FAIL` only when it has run out of things to delete, and
1421    /// `processCommand` refuses the client on that and on nothing else. Running
1422    /// out of time part way through a job it is doing well comes back as
1423    /// `EVICT_RUNNING` and the command goes through, because a server that is
1424    /// evicting steadily and refusing every write while it does it is worse for
1425    /// the client than a little overshoot.
1426    ///
1427    /// # What the limit is worth
1428    ///
1429    /// Space comes back a segment at a time and a segment is two megabytes, so
1430    /// this holds a server to its limit give or take a segment. A `maxmemory` of
1431    /// a few hundred megabytes gets what it asked for. A `maxmemory` of four
1432    /// megabytes is asking for a precision this store does not have.
1433    pub fn make_room(&self) -> bool {
1434        let limit = self.maxmemory();
1435        if limit == 0 || self.used.load(Relaxed) as u64 <= limit {
1436            return true;
1437        }
1438        // The cached reading is a batch old and the batch may have compacted
1439        // since, so take a fresh one before throwing anything away. It is the
1440        // settled reading and not the walk, so what this costs is the handful of
1441        // collections the last batch touched and not the whole database.
1442        let mut used = self.settled_memory();
1443        self.used.store(used, Relaxed);
1444        let mut budget = EVICT_BUDGET;
1445        while used as u64 > limit {
1446            let over = used - limit as usize;
1447            if !self.relieve_step(over) {
1448                return false;
1449            }
1450            self.compact_hard_step();
1451            used = self.settled_memory();
1452            self.used.store(used, Relaxed);
1453            budget -= 1;
1454            if budget == 0 {
1455                break;
1456            }
1457        }
1458        true
1459    }
1460
1461    /// Give back `over` bytes from whichever database can, by moving values to
1462    /// the file where there is one and by throwing keys away where there is not.
1463    ///
1464    /// The two answers are the eviction inversion and which one a database gets
1465    /// is [`Server::migrates`]. Answers whether anything was given back at all,
1466    /// and `false` is what refuses the client's write.
1467    ///
1468    /// A store that will not take the bytes counts as nothing given back, so the
1469    /// write is refused rather than turned into a deletion. A disk that is
1470    /// misbehaving is a reason to stop accepting writes and it is not a reason
1471    /// to start losing data that was accepted already.
1472    ///
1473    /// Round robin from a cursor rather than always starting at database zero,
1474    /// so a server using more than one of them does not empty the first before
1475    /// touching the second. Almost every server is on database zero only, where
1476    /// this is one call that answers and fifteen that say the map is empty.
1477    fn relieve_step(&self, over: usize) -> bool {
1478        let from = self.evict_db.load(Relaxed);
1479        for turn in 0..self.slots() {
1480            let i = (from + turn) % self.slots();
1481            // An empty keyspace has nothing to move and opening a log for one
1482            // would cost a resident page window to find that out.
1483            let used = !self.slot(i).is_empty();
1484            let gave = if used && self.migrates(i) {
1485                self.attach_store(i);
1486                // Whether it made room and not whether it moved a key. A round
1487                // that demoted nothing and handed back a segment is a round
1488                // that made room, and reading only the count refuses the write
1489                // that provoked it.
1490                self.slot(i)
1491                    .relieve(over)
1492                    .is_ok_and(yo_kv::tier::Relief::made_room)
1493            } else {
1494                self.slot(i).evict_one()
1495            };
1496            if gave {
1497                self.evict_db.store((i + 1) % self.slots(), Relaxed);
1498                self.mine().mark(1u64 << self.slot_db(i));
1499                return true;
1500            }
1501        }
1502        false
1503    }
1504
1505    /// The sweep the shard loop calls, at most once a millisecond.
1506    ///
1507    /// The gate is the whole difference between this and [`Server::expire_step`].
1508    /// A maintenance slice runs on every turn of the loop and a turn is a
1509    /// hundred nanoseconds, so an ungated sweep would draw a fresh sample ten
1510    /// thousand times per millisecond and spend a real share of the shard on
1511    /// looking for keys that cannot have died since the last look. Nothing in a
1512    /// database changes fast enough to be worth asking about more often than the
1513    /// clock can tell the difference, and the clock here is milliseconds.
1514    ///
1515    /// A millisecond is also far finer than Redis, whose slow cycle runs at ten
1516    /// hertz, so this is not the thing that decides how promptly memory comes
1517    /// back. What it decides is that an idle server sweeps a thousand times a
1518    /// second rather than a million.
1519    pub fn expire_slice(&self, budget: usize) -> usize {
1520        let now = self.clock.now_ms();
1521        if now == self.expire_ms.load(Relaxed) {
1522            return 0;
1523        }
1524        self.expire_ms.store(now, Relaxed);
1525        self.expire_step(budget)
1526    }
1527
1528    /// Sweep dead keys out of the databases, spending at most `budget` looks.
1529    ///
1530    /// Answers what it spent, so the caller can charge its maintenance slice for
1531    /// it. See [`yo_kv::expiry`] for why the budget is in keys looked at.
1532    ///
1533    /// Round robin from its own cursor, and every database gets offered whatever
1534    /// is left of the budget rather than a sixteenth of it each, so a server on
1535    /// database zero only, which is nearly every server, spends the whole slice
1536    /// where the keys are. The fifteen empty ones cost a comparison apiece
1537    /// because a database with no key carrying a deadline says so without
1538    /// drawing anything.
1539    ///
1540    /// The cursor moves to the database after whichever one did the work, so two
1541    /// busy databases take turns instead of the lower numbered one starving the
1542    /// other.
1543    pub fn expire_step(&self, budget: usize) -> usize {
1544        let mut spent = 0;
1545        let from = self.expire_db.load(Relaxed);
1546        for turn in 0..self.slots() {
1547            if spent >= budget {
1548                break;
1549            }
1550            let i = (from + turn) % self.slots();
1551            let c = self.slot(i).expire_cycle(budget - spent);
1552            spent += c.examined;
1553            if c.expired > 0 {
1554                self.expire_db.store((i + 1) % self.slots(), Relaxed);
1555                self.mine().note(1u64 << self.slot_db(i));
1556            }
1557        }
1558        spent
1559    }
1560
1561    /// One slice of compaction for a server that is over its limit.
1562    ///
1563    /// Takes the databases in the same order [`Server::compact_step`] does and
1564    /// stops at the first one that had something to move, and it asks with the
1565    /// ratios off. See [`Keyspace::compact_hard`] for what that changes.
1566    fn compact_hard_step(&self) -> Option<usize> {
1567        let from = self.next_db.load(Relaxed);
1568        for turn in 0..self.slots() {
1569            let i = (from + turn) % self.slots();
1570            if let Some(moved) = self.slot(i).compact_hard() {
1571                self.next_db.store((i + 1) % self.slots(), Relaxed);
1572                return Some(moved);
1573            }
1574        }
1575        None
1576    }
1577
1578    /// Take what every thread has marked and add it to the turn's own mask.
1579    ///
1580    /// The mask the turn works from is its own and not a shared one, because a
1581    /// mask it read in place and then cleared a bit of would be a mask that lost
1582    /// whatever another thread marked in between. A swap cannot lose a mark: a
1583    /// thread that ors while the swap happens either gets its bit in before the
1584    /// swap or leaves it there afterwards, and the second one costs one look at
1585    /// a database the turn has already been through.
1586    fn collect_marks(&self) {
1587        let mut marked = 0;
1588        for thread in &self.locals {
1589            marked |= thread.dirty.swap(0, Relaxed);
1590        }
1591        self.mine().note(marked);
1592    }
1593
1594    /// Give one database's dead space back, if any database has enough of it to
1595    /// be worth the move. `None` when no database had a candidate.
1596    ///
1597    /// Once per batch, next to the clock. Overwriting a key writes a new record
1598    /// and counts the old one dead, so without this a server holds everything
1599    /// it has ever written: 400000 sets over 100000 keys measured at 742 bytes
1600    /// a key against Redis at 144 for the same load, and the whole difference
1601    /// was dead records nothing ever came back for.
1602    ///
1603    /// At most one segment moves per call and the search starts one database
1604    /// further along each time, so the cost of asking is a comparison per
1605    /// database and the cost of acting is bounded by a segment.
1606    pub fn compact_step(&self) -> Option<usize> {
1607        self.collect_marks();
1608        let mine = self.mine();
1609        let from = self.next_db.load(Relaxed);
1610        for turn in 0..self.slots() {
1611            let i = (from + turn) % self.slots();
1612            // Nothing has run against this database since it last said it had
1613            // nothing to collect, so it still has nothing to collect and the
1614            // line it lives on stays where it is.
1615            let at = self.slot_db(i);
1616            if !mine.wanted(at) {
1617                continue;
1618            }
1619            if let Some(moved) = self.slot(i).compact_step() {
1620                self.next_db.store((i + 1) % self.slots(), Relaxed);
1621                return Some(moved);
1622            }
1623            // Only once every stripe of the database has said it has nothing,
1624            // since the bit is per database and one stripe answering for all of
1625            // them would stop the others being asked at all.
1626            if i % self.width == self.width - 1 {
1627                mine.done(at);
1628            }
1629        }
1630        None
1631    }
1632}
1633
1634impl Default for Server {
1635    fn default() -> Server {
1636        Server::new()
1637    }
1638}
1639
1640/// What one connection has chosen.
1641pub struct Session {
1642    db: usize,
1643    id: u64,
1644    name: Vec<u8>,
1645    /// The `HIMPORT` fieldsets this connection has prepared.
1646    ///
1647    /// Connection state and not keyspace state, which is the reference's design
1648    /// and not a shortcut: a fieldset is invisible to every other connection and
1649    /// the keys built from one outlive it.
1650    sets: himport::Fieldsets,
1651    /// Whether the command running right now was called by a script.
1652    ///
1653    /// The one thing it changes is what a blocking command does when it finds
1654    /// nothing to take. A client that sent `BLPOP` waits; a script that called
1655    /// `BLPOP` cannot, because the whole server is waiting on the script, and a
1656    /// script that parked would park everything behind it. So inside a script a
1657    /// blocking command times out at once and answers the null a client that
1658    /// waited its full timeout would have got. That is a real server's rule and
1659    /// it is why `BLPOP` is not on the list a script may not call.
1660    scripted: bool,
1661}
1662
1663impl Session {
1664    /// A new connection, on database zero with no name.
1665    #[must_use]
1666    pub fn new(id: u64) -> Session {
1667        Session {
1668            db: 0,
1669            id,
1670            name: Vec::new(),
1671            sets: himport::Fieldsets::default(),
1672            scripted: false,
1673        }
1674    }
1675
1676    /// Whether a script is what is asking, which only a blocking command reads.
1677    pub(crate) const fn scripted(&self) -> bool {
1678        self.scripted
1679    }
1680
1681    /// The connection id, which `HELLO` reports and `CLIENT` will.
1682    #[must_use]
1683    pub const fn id(&self) -> u64 {
1684        self.id
1685    }
1686
1687    /// Which database this connection is working in.
1688    #[must_use]
1689    pub const fn db(&self) -> usize {
1690        self.db
1691    }
1692
1693    /// The name the client gave itself, empty if it gave none.
1694    #[must_use]
1695    pub fn name(&self) -> &[u8] {
1696        &self.name
1697    }
1698
1699    /// Put everything back the way it was when the connection was opened.
1700    ///
1701    /// The protocol is not here because it is not here: it lives in the reply
1702    /// buffer, and `RESET` sets it back there.
1703    pub fn reset(&mut self) {
1704        self.db = 0;
1705        self.name.clear();
1706        // `SELECT` leaves these alone and `RESET` does not, both checked
1707        // against 8.10.1, which is the one pair of answers you could not guess
1708        // from what the command is for.
1709        self.sets.clear();
1710    }
1711
1712    /// Record the name from `HELLO ... SETNAME`.
1713    fn set_name(&mut self, name: &[u8]) {
1714        yo_alloc::allow(|| {
1715            self.name.clear();
1716            self.name.extend_from_slice(name);
1717        });
1718    }
1719}
1720
1721/// Run one command and write its reply.
1722///
1723/// The name is looked up and the arity is checked here, once, so that no body
1724/// has to. Everything after that is the command's own.
1725pub fn execute(server: &Server, session: &mut Session, args: Args<'_>, out: &mut Out) -> Flow {
1726    // The decoder never produces a command with no name. If one ever arrives,
1727    // it is not something to answer.
1728    if args.is_empty() {
1729        return Flow::Continue;
1730    }
1731    resolved(server, session, lookup(args.name()), args, out)
1732}
1733
1734/// The same, for a caller that has already found the command.
1735///
1736/// The engine frames a command before it runs it, and between those two it also
1737/// asks which key the command touches so the record can be prefetched. That is
1738/// two more chances to look the name up, and looking it up three times to run it
1739/// once is three times the cost of the cheapest thing in the path. So the engine
1740/// resolves the name where it frames the command, carries the answer on the
1741/// framed command, and both the other two take it from there.
1742///
1743/// `spec` is `None` for a name that is not a command, which is the same thing
1744/// [`lookup`] says and lands in the same reply.
1745pub fn resolved(
1746    server: &Server,
1747    session: &mut Session,
1748    spec: Option<&'static Spec>,
1749    args: Args<'_>,
1750    out: &mut Out,
1751) -> Flow {
1752    if args.is_empty() {
1753        return Flow::Continue;
1754    }
1755    server.mine().stats.commands.bump();
1756
1757    let Some(spec) = spec else {
1758        write_error(out, &args::unknown_command(args));
1759        return Flow::Continue;
1760    };
1761    if !arity_ok(spec, args.len()) {
1762        server.mine().cmdstats.at(spec).rejected.bump();
1763        write_error(out, &args::wrong_arity(spec.name));
1764        return Flow::Continue;
1765    }
1766
1767    // The limit first, so a server with no `maxmemory`, which is the default and
1768    // is nearly all of them, pays one comparison against a field that is already
1769    // warm. Every command and not only the writes, because that is where Redis
1770    // puts it: making room is the server's job whatever the client asked for,
1771    // and the flag only decides who gets told no when there is no room to make.
1772    //
1773    // The flag is Redis's own `denyoom` and the list of commands carrying it is
1774    // Redis's list, so a command that only frees is let through with nothing
1775    // left, which is what lets a client dig itself out with `DEL`.
1776    if server.maxmemory() != 0 && !server.make_room() && spec.flags.contains(&"denyoom") {
1777        server.mine().cmdstats.at(spec).rejected.bump();
1778        out.error_line(b"OOM ", OOM);
1779        return Flow::Continue;
1780    }
1781
1782    // Which databases the maintenance turn after this batch has to ask. Marked
1783    // for every command and not only for the writes, because a read can make
1784    // garbage too: a `GET` on a key whose expiry has passed reaps it, and the
1785    // record it dropped is exactly the kind of thing the collector is for.
1786    // `COPY`, `SWAPDB` and `FLUSHALL` reach a database nobody selected, so the
1787    // two groups that hold them mark all of them rather than the session's.
1788    server.mine().mark(match spec.group {
1789        "string" | "bitmap" | "hyperloglog" | "geo" | "set" | "hash" | "list" | "zset"
1790        | "array" | "stream" | "bloom" | "cuckoo" | "cms" | "topk" | "tdigest" | "ts" => {
1791            1u64 << session.db
1792        }
1793        _ => ALL_DATABASES,
1794    });
1795
1796    let mark = out.len();
1797    // Before the group, because the five that block are list commands and would
1798    // otherwise land in `lists`, which is handed one database and nothing that
1799    // could park a client. The flag is the right thing to branch on rather than
1800    // a list of names: it is what `COMMAND INFO` reports about exactly these
1801    // commands, and the sorted set and stream ones that arrive later carry it
1802    // too.
1803    let done = if spec.flags.contains(&"blocking") {
1804        blocking::execute(server, session, spec, args, out)
1805    } else {
1806        match spec.group {
1807            "string" => {
1808                let db = session.db;
1809                strings::execute(&server.dbs[db], spec, args, out).map(|()| Flow::Continue)
1810            }
1811            // Its own group and its own file, and the same values underneath:
1812            // a bitmap is a string, so `STRLEN` on one answers and `SETBIT` on
1813            // something a `SET` left behind works.
1814            "bitmap" => {
1815                let db = session.db;
1816                bits::execute(&server.dbs[db], spec, args, out).map(|()| Flow::Continue)
1817            }
1818            // The same again: a sketch is a string with a documented layout, so
1819            // `GET` hands one to a client and `SET` takes it back.
1820            "hyperloglog" => {
1821                let db = session.db;
1822                hll::execute(&server.dbs[db], spec, args, out).map(|()| Flow::Continue)
1823            }
1824            "set" => {
1825                let db = session.db;
1826                sets::execute(&server.dbs[db], spec, args, out).map(|()| Flow::Continue)
1827            }
1828            // The one hash command whose state is not in the keyspace. A
1829            // fieldset belongs to the connection, so this is handed the session
1830            // as well as the database, the same exception `MIGRATE` gets in the
1831            // keyspace group for the socket it keeps.
1832            "hash" if spec.name == "himport" => {
1833                let db = session.db;
1834                himport::execute(&server.dbs[db], &mut session.sets, args, out)
1835                    .map(|()| Flow::Continue)
1836            }
1837            // The one group that reaches back into the server after it has
1838            // written its reply, because a hash is what a search index is
1839            // made of. What comes back is what the indexes have to be told,
1840            // which is not the same as whether the command was a write.
1841            "hash" => {
1842                let db = session.db;
1843                let changed = hashes::execute(&server.dbs[db], spec, args, out);
1844                changed.map(|changed| {
1845                    indexing::changed(server, db, args.get(1), changed);
1846                    Flow::Continue
1847                })
1848            }
1849            "list" => {
1850                let db = session.db;
1851                lists::execute(&server.dbs[db], spec, args, out).map(|()| Flow::Continue)
1852            }
1853            "zset" => {
1854                let db = session.db;
1855                zsets::execute(&server.dbs[db], spec, args, out).map(|()| Flow::Continue)
1856            }
1857            // A geo key is a sorted set and these are sorted set commands with
1858            // arithmetic on the way in and on the way out, so a client can ZREM
1859            // a place out of one and ZCARD it to count them.
1860            "geo" => {
1861                let db = session.db;
1862                geo::execute(&server.dbs[db], spec, args, out).map(|()| Flow::Continue)
1863            }
1864            "array" => {
1865                let db = session.db;
1866                arrays::execute(&server.dbs[db], spec, args, out).map(|()| Flow::Continue)
1867            }
1868            "graph" => {
1869                let db = session.db;
1870                graph::execute(&server.dbs[db], spec, args, out).map(|()| Flow::Continue)
1871            }
1872            // A document under a key, reached by a path. The group is Redis's
1873            // module surface and the storage is ours, the same trade the vector
1874            // set group makes.
1875            "json" => {
1876                let db = session.db;
1877                json::execute(&server.dbs[db], spec, args, out).map(|()| Flow::Continue)
1878            }
1879            "vector" => {
1880                let db = session.db;
1881                vectors::execute(&server.dbs[db], spec, args, out).map(|()| Flow::Continue)
1882            }
1883            "bloom" => {
1884                let db = session.db;
1885                bloom::execute(&server.dbs[db], spec, args, out).map(|()| Flow::Continue)
1886            }
1887            "cuckoo" => {
1888                let db = session.db;
1889                cuckoo::execute(&server.dbs[db], spec, args, out).map(|()| Flow::Continue)
1890            }
1891            "cms" => {
1892                let db = session.db;
1893                cms::execute(&server.dbs[db], spec, args, out).map(|()| Flow::Continue)
1894            }
1895            "topk" => {
1896                let db = session.db;
1897                topk::execute(&server.dbs[db], spec, args, out).map(|()| Flow::Continue)
1898            }
1899            "tdigest" => {
1900                let db = session.db;
1901                tdigest::execute(&server.dbs[db], spec, args, out).map(|()| Flow::Continue)
1902            }
1903            "ts" => {
1904                let db = session.db;
1905                ts::execute(&server.dbs[db], spec, args, out).map(|()| Flow::Continue)
1906            }
1907            // The clock is read before the database is borrowed, because every
1908            // stream command needs the time and it lives on the server. An
1909            // `XADD` with no ID, an `XCLAIM` working out what is idle and an
1910            // `XINFO` reporting it all have to agree about what moment this is.
1911            "stream" => {
1912                let db = session.db;
1913                let now = server.now_ms();
1914                streams::execute(&server.dbs[db], spec, args, now, out).map(|()| Flow::Continue)
1915            }
1916            // The one keyspace command that needs more than the databases,
1917            // because the socket it talks down is held on the server between
1918            // commands and not opened again for each one.
1919            "keyspace" if spec.name == "migrate" => {
1920                migrate::execute(server, session.db, args, out).map(|()| Flow::Continue)
1921            }
1922            // Every database and not the one the session is on, because `COPY` takes
1923            // a `DB n` and writes into a database nobody selected. The other group
1924            // that reaches back into the server afterwards, and it hands back a list
1925            // rather than one answer, because `DEL a b c` is three keys and a rename
1926            // is two.
1927            "keyspace" => {
1928                let mut touched = indexing::Touched::new(server);
1929                let done =
1930                    keyspace::execute(&server.dbs, session.db, spec, args, out, &mut touched);
1931                done.map(|()| {
1932                    indexing::touched(server, &touched);
1933                    Flow::Continue
1934                })
1935            }
1936            // No database at all, because an index is not a key. The registry
1937            // is the whole of what these sixteen commands touch, and then
1938            // `FT.CREATE` hands back the name it made so the keys that
1939            // already match its prefix can be read into it. The lock goes
1940            // before the scan runs, since the scan takes it again for every
1941            // key it reads.
1942            "search" if spec.name == "FT.SEARCH" => {
1943                // The two search commands that read documents, and so the two
1944                // that need the keyspace as well as the registry. They take and
1945                // let go of the registry themselves, because they cannot hold
1946                // that and a stripe at the same time.
1947                search::find(server, session.db, args, out).map(|()| Flow::Continue)
1948            }
1949            "search" if spec.name == "FT.AGGREGATE" => {
1950                search::roll(server, session.db, args, out).map(|()| Flow::Continue)
1951            }
1952            "search" if spec.name == "FT.HYBRID" => {
1953                search::hybrid(server, session.db, args, out).map(|()| Flow::Continue)
1954            }
1955            "search" if spec.name == "FT.PROFILE" => {
1956                // Which is one of those two with the working shown, so it needs
1957                // everything they need and takes the same route to it.
1958                search::profiled(server, session.db, args, out).map(|()| Flow::Continue)
1959            }
1960            // The four search commands that name a key rather than an index.
1961            // A suggestion dictionary is a real key with a type of its own, so
1962            // these are handed a database and never touch the registry.
1963            "search" if spec.name.starts_with("FT.SUG") => {
1964                let db = session.db;
1965                suggest::execute(&server.dbs[db], spec, args, out).map(|()| Flow::Continue)
1966            }
1967            // The five deprecated document commands, which are the other search
1968            // commands that need the keyspace as well as the registry: what they
1969            // write and read is an ordinary hash.
1970            "search"
1971                if matches!(
1972                    spec.name,
1973                    "FT.ADD" | "FT.SAFEADD" | "FT.GET" | "FT.MGET" | "FT.DEL"
1974                ) =>
1975            {
1976                let db = session.db;
1977                search::docs::execute(server, db, spec, args, out).map(|()| Flow::Continue)
1978            }
1979            "search" if spec.name == "FT.CURSOR" => {
1980                // Its own arm because the cursors are not in the registry, and
1981                // it takes and lets go of the registry itself to look up the
1982                // index name it is given.
1983                search::cursor::execute(server, args, out).map(|()| Flow::Continue)
1984            }
1985            "search" => {
1986                let db = session.db;
1987                let made = search::execute(server, &mut server.search.lock(), db, spec, args, out);
1988                made.map(|made| {
1989                    match made {
1990                        Some(search::After::Scan(fill)) => indexing::scan(server, db, &fill),
1991                        Some(search::After::Sweep(keys)) => indexing::sweep(server, db, &keys),
1992                        None => {}
1993                    }
1994                    Flow::Continue
1995                })
1996            }
1997            "scripting" => {
1998                scripting::execute(server, session, spec, args, out).map(|()| Flow::Continue)
1999            }
2000            _ => server::execute(server, session, spec, args, out),
2001        }
2002    };
2003    let flow = match done {
2004        Ok(flow) => flow,
2005        Err(e) => {
2006            out.truncate(mark);
2007            write_error(out, &e);
2008            Flow::Continue
2009        }
2010    };
2011
2012    // Counted here and not before the call, which is where Redis counts it, so
2013    // that `INFO commandstats` leaves out the `INFO` that asked for it in the
2014    // same way theirs does.
2015    //
2016    // Failure is read off the reply rather than off the `Result`, because the
2017    // two are not the same set. A command that ran out of arguments comes back
2018    // as an `Err` and a command that was sent the wrong password writes its own
2019    // error line and comes back `Ok`, and both of those are a call that failed.
2020    // The first byte at the mark is what a client would branch on, and it is `-`
2021    // for an error on either protocol and `!` for RESP3's long form.
2022    let row = server.mine().cmdstats.at(spec);
2023    row.calls.bump();
2024    if matches!(out.as_slice().get(mark), Some(b'-' | b'!')) {
2025        row.failed.bump();
2026    }
2027    flow
2028}
2029
2030/// The error line for an error value.
2031///
2032/// The prefix is what a client branches on, and there are three of them:
2033/// `WRONGTYPE` for a command sent at the wrong kind of value, `INVALIDOBJ` for a
2034/// HyperLogLog whose opcodes do not add up, and `ERR` for everything else. The three errors that need a different one,
2035/// `NOPROTO`, `WRONGPASS` and `OOM`, are written where they are decided rather
2036/// than routed through here. `OOM` is not a [`Code`] of its own because
2037/// [`Code::Full`] already covers the string that is too long for
2038/// `proto-max-bulk-len`, and that one goes out as `ERR` on a real server.
2039fn write_error(out: &mut Out, e: &Error) {
2040    let prefix: &[u8] = match e.code() {
2041        Code::WrongType => b"WRONGTYPE ",
2042        // Only the HyperLogLog commands answer this one, and the prefix is the
2043        // sentence a client branches on to tell a sketch it cannot read from a
2044        // sketch it sent wrong.
2045        Code::Corrupt => b"INVALIDOBJ ",
2046        _ => b"ERR ",
2047    };
2048    out.error_line(prefix, e.message().as_bytes());
2049}
2050
2051#[cfg(test)]
2052mod tests {
2053    use super::*;
2054    use crate::proto::{Limits, Proto};
2055    use crate::request::Argv;
2056
2057    /// Build the wire bytes for a command.
2058    ///
2059    /// Tests go through the codec rather than around it, so an argument in a
2060    /// test is the same borrowed slice a connection produces.
2061    pub(crate) fn encode(parts: &[&[u8]]) -> Vec<u8> {
2062        let mut wire = format!("*{}\r\n", parts.len()).into_bytes();
2063        for p in parts {
2064            wire.extend_from_slice(format!("${}\r\n", p.len()).as_bytes());
2065            wire.extend_from_slice(p);
2066            wire.extend_from_slice(b"\r\n");
2067        }
2068        wire
2069    }
2070
2071    /// A server, a connection and a buffer, driven the way the reactor will.
2072    struct Fixture {
2073        server: Server,
2074        session: Session,
2075        argv: Argv,
2076        out: Out,
2077    }
2078
2079    impl Fixture {
2080        fn new() -> Fixture {
2081            Fixture::on(Server::new())
2082        }
2083
2084        /// The same, on a server whose databases are cut into `width` stripes.
2085        fn striped(width: usize) -> Fixture {
2086            Fixture::on(Server::with_width(width))
2087        }
2088
2089        fn on(server: Server) -> Fixture {
2090            Fixture {
2091                server,
2092                session: Session::new(7),
2093                argv: Argv::new(),
2094                out: Out::new(Proto::Resp2),
2095            }
2096        }
2097
2098        /// Run one command and answer with the bytes it wrote.
2099        fn run(&mut self, parts: &[&[u8]]) -> String {
2100            self.flow(parts).1
2101        }
2102
2103        /// Run one command and answer with the bytes exactly as written.
2104        ///
2105        /// [`Fixture::run`] goes through `from_utf8_lossy`, which is fine for
2106        /// every reply that is text and destroys a `DUMP` payload, since a
2107        /// payload is arbitrary bytes and a checksum on the end of them.
2108        fn raw(&mut self, parts: &[&[u8]]) -> Vec<u8> {
2109            let wire = encode(parts);
2110            self.argv.decode(&wire, &Limits::default()).unwrap();
2111            self.out.clear();
2112            execute(
2113                &self.server,
2114                &mut self.session,
2115                Args::new(&self.argv, &wire),
2116                &mut self.out,
2117            );
2118            self.out.as_slice().to_vec()
2119        }
2120
2121        /// Move every clock in the server on by `ms`.
2122        fn advance(&mut self, ms: u64) {
2123            self.server.advance_clock_ms(ms);
2124        }
2125
2126        /// The same, with what the connection should do next.
2127        fn flow(&mut self, parts: &[&[u8]]) -> (Flow, String) {
2128            let wire = encode(parts);
2129            self.argv.decode(&wire, &Limits::default()).unwrap();
2130            self.out.clear();
2131            let flow = execute(
2132                &self.server,
2133                &mut self.session,
2134                Args::new(&self.argv, &wire),
2135                &mut self.out,
2136            );
2137            (
2138                flow,
2139                String::from_utf8_lossy(self.out.as_slice()).into_owned(),
2140            )
2141        }
2142    }
2143
2144    /// What a client does all day: write the same keys again and again. Every
2145    /// one of those writes leaves the previous record behind, so a server that
2146    /// never compacts holds every version of every key it has ever been sent.
2147    ///
2148    /// Not under Miri, and not because of anything it would find. The bound
2149    /// only means something once several megabytes have gone through the
2150    /// arena, which reclaims a segment at a time and has segments of two
2151    /// megabytes, so a server that reclaimed nothing would still be under the
2152    /// bound in any smaller version of this. Thirty two megabytes is thirty
2153    /// two thousand commands and was over forty minutes interpreted. The paths
2154    /// it walks are walked by the hundreds of tests around it that write a key
2155    /// and read it back, which do run there.
2156    #[cfg_attr(miri, ignore = "megabytes through the arena")]
2157    #[test]
2158    fn rewriting_the_same_keys_does_not_grow_the_server() {
2159        let mut f = Fixture::new();
2160        let val = vec![b'v'; 1024];
2161        let keys: Vec<Vec<u8>> = (0..64).map(|i| format!("key:{i}").into_bytes()).collect();
2162
2163        for k in &keys {
2164            f.run(&[b"SET", k, &val]);
2165        }
2166        f.server.compact_step();
2167        let after_first = f.server.memory_bytes();
2168
2169        // 64 KiB a pass, five hundred passes, and the same 64 keys at the end
2170        // of it. Thirty two megabytes written to hold sixty four kilobytes,
2171        // which is the shape of a real workload and is enough churn to fill
2172        // sixteen segments if nothing ever comes back.
2173        for _ in 0..500 {
2174            for k in &keys {
2175                f.run(&[b"SET", k, &val]);
2176            }
2177            f.server.compact_step();
2178        }
2179
2180        assert!(
2181            f.server.memory_bytes() <= after_first * 2,
2182            "held {} after five hundred passes against {after_first} after one",
2183            f.server.memory_bytes()
2184        );
2185        assert_eq!(f.run(&[b"DBSIZE"]), format!(":{}\r\n", keys.len()));
2186        assert_eq!(f.run(&[b"STRLEN", b"key:7"]), ":1024\r\n");
2187    }
2188
2189    /// The same churn on a database nobody starts on, either side of a quiet
2190    /// spell long enough for the maintenance turn to stop asking about it.
2191    ///
2192    /// The turn after each batch skips a database that has already said it has
2193    /// nothing to collect and has not been touched since, which is what keeps a
2194    /// server whose clients are all on database zero from loading and storing
2195    /// in the other fifteen every batch to be told no. Two things could go
2196    /// wrong with that. A database might never be marked at all, so this uses
2197    /// database nine, which nothing marks by accident. And a database whose
2198    /// mark was cleared might never get it back, so this drains the collector
2199    /// until it says there is nothing left, checks the mark really is gone, and
2200    /// then writes another thirty two megabytes through the same sixty four
2201    /// keys. If either went wrong the server would hold all of it.
2202    ///
2203    /// Not under Miri, for the reason on the test above: the volume is the
2204    /// claim, and the volume is what the interpreter charges for.
2205    #[cfg_attr(miri, ignore = "megabytes through the arena")]
2206    #[test]
2207    fn a_database_nobody_started_on_is_still_collected() {
2208        let mut f = Fixture::new();
2209        assert_eq!(f.run(&[b"SELECT", b"9"]), "+OK\r\n");
2210        let val = vec![b'v'; 1024];
2211        let keys: Vec<Vec<u8>> = (0..64).map(|i| format!("key:{i}").into_bytes()).collect();
2212
2213        for k in &keys {
2214            f.run(&[b"SET", k, &val]);
2215        }
2216        while f.server.compact_step().is_some() {}
2217        assert!(
2218            !f.server.mine().wanted(9),
2219            "database nine was drained and should not be asked again until it is written to"
2220        );
2221        let after_first = f.server.memory_bytes();
2222
2223        for _ in 0..500 {
2224            for k in &keys {
2225                f.run(&[b"SET", k, &val]);
2226            }
2227            f.server.compact_step();
2228        }
2229
2230        assert!(
2231            f.server.memory_bytes() <= after_first * 2,
2232            "held {} after five hundred passes against {after_first} after one",
2233            f.server.memory_bytes()
2234        );
2235        assert_eq!(f.run(&[b"DBSIZE"]), format!(":{}\r\n", keys.len()));
2236        assert_eq!(f.run(&[b"STRLEN", b"key:7"]), ":1024\r\n");
2237        // And nothing landed anywhere else on the way.
2238        f.run(&[b"SELECT", b"0"]);
2239        assert_eq!(f.run(&[b"DBSIZE"]), ":0\r\n");
2240    }
2241
2242    #[test]
2243    fn a_command_goes_from_bytes_to_bytes() {
2244        let mut f = Fixture::new();
2245        assert_eq!(f.run(&[b"SET", b"k", b"v"]), "+OK\r\n");
2246        assert_eq!(f.run(&[b"GET", b"k"]), "$1\r\nv\r\n");
2247        assert_eq!(f.run(&[b"GET", b"nosuch"]), "$-1\r\n");
2248        assert_eq!(f.run(&[b"STRLEN", b"k"]), ":1\r\n");
2249        // The name is matched whatever case it came in, and so are the options.
2250        assert_eq!(f.run(&[b"set", b"k", b"v2", b"xx"]), "+OK\r\n");
2251        assert_eq!(f.run(&[b"GET", b"k"]), "$2\r\nv2\r\n");
2252    }
2253
2254    #[test]
2255    fn deleting_counts_keys_removed_and_existing_counts_arguments_matched() {
2256        let mut f = Fixture::new();
2257        f.run(&[b"MSET", b"a", b"1", b"b", b"2", b"c", b"3"]);
2258        // A key named twice exists twice and can only be deleted once, and both
2259        // of those are Redis's answers rather than tidier ones.
2260        assert_eq!(f.run(&[b"EXISTS", b"a", b"a", b"nosuch"]), ":2\r\n");
2261        assert_eq!(f.run(&[b"DEL", b"a", b"a", b"nosuch"]), ":1\r\n");
2262        assert_eq!(f.run(&[b"EXISTS", b"a"]), ":0\r\n");
2263        // UNLINK is the same body and reports the same way.
2264        assert_eq!(f.run(&[b"UNLINK", b"b", b"c"]), ":2\r\n");
2265        assert_eq!(f.run(&[b"DBSIZE"]), ":0\r\n");
2266    }
2267
2268    #[test]
2269    fn type_is_a_simple_string_and_says_none_for_a_key_that_is_not_there() {
2270        let mut f = Fixture::new();
2271        f.run(&[b"SET", b"k", b"v"]);
2272        // A simple string on both protocols, which is unusual: most replies
2273        // that carry a word are bulk strings.
2274        assert_eq!(f.run(&[b"TYPE", b"k"]), "+string\r\n");
2275        assert_eq!(f.run(&[b"TYPE", b"nosuch"]), "+none\r\n");
2276    }
2277
2278    #[test]
2279    fn touch_counts_the_way_exists_counts() {
2280        let mut f = Fixture::new();
2281        f.run(&[b"MSET", b"a", b"1", b"b", b"2"]);
2282        assert_eq!(f.run(&[b"TOUCH", b"a", b"b"]), ":2\r\n");
2283        assert_eq!(
2284            f.run(&[b"TOUCH", b"a", b"a"]),
2285            ":2\r\n",
2286            "twice counts twice"
2287        );
2288        assert_eq!(f.run(&[b"TOUCH", b"a", b"nosuch"]), ":1\r\n");
2289        assert_eq!(f.run(&[b"TOUCH", b"nosuch"]), ":0\r\n");
2290    }
2291
2292    #[test]
2293    fn a_rename_moves_the_deadline_with_the_value_and_drops_the_one_it_lands_on() {
2294        let mut f = Fixture::new();
2295        f.run(&[b"SET", b"a", b"v1", b"EX", b"100"]);
2296        f.run(&[b"SET", b"b", b"v2", b"EX", b"500"]);
2297
2298        assert_eq!(f.run(&[b"RENAME", b"a", b"b"]), "+OK\r\n");
2299        assert_eq!(f.run(&[b"GET", b"b"]), "$2\r\nv1\r\n");
2300        assert_eq!(
2301            f.run(&[b"TTL", b"b"]),
2302            ":100\r\n",
2303            "the source's and not b's"
2304        );
2305        assert_eq!(f.run(&[b"EXISTS", b"a"]), ":0\r\n");
2306    }
2307
2308    #[test]
2309    fn a_rename_with_no_source_is_an_error_and_not_a_zero() {
2310        let mut f = Fixture::new();
2311        assert_eq!(f.run(&[b"RENAME", b"a", b"b"]), "-ERR no such key\r\n");
2312        // The source is checked before the destination, so this is the error
2313        // and not the zero RENAMENX would otherwise answer for a taken name.
2314        assert_eq!(f.run(&[b"RENAMENX", b"a", b"a"]), "-ERR no such key\r\n");
2315    }
2316
2317    #[test]
2318    fn renamenx_refuses_a_taken_name_including_the_one_it_already_has() {
2319        let mut f = Fixture::new();
2320        f.run(&[b"MSET", b"a", b"v1", b"b", b"v2"]);
2321
2322        assert_eq!(f.run(&[b"RENAMENX", b"a", b"b"]), ":0\r\n");
2323        assert_eq!(f.run(&[b"GET", b"b"]), "$2\r\nv2\r\n");
2324        // Renaming onto itself is 0 here and OK for plain RENAME, which is the
2325        // one call the two disagree about and neither does any work for.
2326        assert_eq!(f.run(&[b"RENAMENX", b"a", b"a"]), ":0\r\n");
2327        assert_eq!(f.run(&[b"RENAME", b"a", b"a"]), "+OK\r\n");
2328        assert_eq!(f.run(&[b"RENAMENX", b"a", b"c"]), ":1\r\n");
2329        assert_eq!(f.run(&[b"GET", b"c"]), "$2\r\nv1\r\n");
2330    }
2331
2332    #[test]
2333    fn renaming_a_set_does_not_touch_a_member() {
2334        let mut f = Fixture::new();
2335        for i in 0..300 {
2336            f.run(&[b"SADD", b"s", format!("m{i}").as_bytes()]);
2337        }
2338        let before = f.server.memory_bytes();
2339
2340        assert_eq!(f.run(&[b"RENAME", b"s", b"t"]), "+OK\r\n");
2341        assert_eq!(f.run(&[b"SCARD", b"t"]), ":300\r\n");
2342        assert_eq!(f.run(&[b"TYPE", b"t"]), "+set\r\n");
2343        assert!(
2344            f.server.memory_bytes().abs_diff(before) < 256,
2345            "the members were copied: {} against {before}",
2346            f.server.memory_bytes()
2347        );
2348    }
2349
2350    #[test]
2351    fn a_copy_is_a_second_value_and_not_a_second_name() {
2352        let mut f = Fixture::new();
2353        f.run(&[b"SADD", b"s", b"m1", b"m2"]);
2354
2355        assert_eq!(f.run(&[b"COPY", b"s", b"t"]), ":1\r\n");
2356        f.run(&[b"SADD", b"t", b"m3"]);
2357        assert_eq!(f.run(&[b"SCARD", b"s"]), ":2\r\n", "the original is intact");
2358        assert_eq!(f.run(&[b"SCARD", b"t"]), ":3\r\n");
2359    }
2360
2361    /// Every type a key can hold, copied, because two of them used to panic.
2362    ///
2363    /// `COPY` reads the value out of the source through one match on the type
2364    /// tag, and that match had a catch all at the bottom from back when a set
2365    /// and a hash were the only bodies. The list and the sorted set landed after
2366    /// it and nobody came back, so `COPY mylist other` took the shard down. It
2367    /// is an ordinary command against a type the server supports everywhere
2368    /// else, so this walks all five rather than the two that were broken: the
2369    /// point is that the next type cannot land the same way.
2370    #[test]
2371    fn every_type_can_be_copied() {
2372        let mut f = Fixture::new();
2373        f.run(&[b"SET", b"str", b"v1"]);
2374        f.run(&[b"SADD", b"set", b"m1"]);
2375        f.run(&[b"HSET", b"hash", b"f", b"v"]);
2376        f.run(&[b"RPUSH", b"list", b"a", b"b"]);
2377        f.run(&[b"ZADD", b"zset", b"1", b"m1"]);
2378
2379        for name in [
2380            &b"str"[..],
2381            &b"set"[..],
2382            &b"hash"[..],
2383            &b"list"[..],
2384            &b"zset"[..],
2385        ] {
2386            let dst = [name, b":copy"].concat();
2387            assert_eq!(
2388                f.run(&[b"COPY", name, &dst]),
2389                ":1\r\n",
2390                "copying {}",
2391                String::from_utf8_lossy(name)
2392            );
2393            assert_eq!(f.run(&[b"TYPE", name]), f.run(&[b"TYPE", &dst]));
2394        }
2395
2396        assert_eq!(f.run(&[b"LRANGE", b"list:copy", b"0", b"-1"]), {
2397            let mut want = String::from("*2\r\n");
2398            want.push_str("$1\r\na\r\n$1\r\nb\r\n");
2399            want
2400        });
2401        assert_eq!(f.run(&[b"ZSCORE", b"zset:copy", b"m1"]), "$1\r\n1\r\n");
2402
2403        // And the copy is its own value, not a second name for the source.
2404        f.run(&[b"RPUSH", b"list:copy", b"c"]);
2405        assert_eq!(f.run(&[b"LLEN", b"list"]), ":2\r\n");
2406        assert_eq!(f.run(&[b"LLEN", b"list:copy"]), ":3\r\n");
2407    }
2408
2409    #[test]
2410    fn a_copy_refuses_a_taken_destination_until_it_is_told_it_can_have_it() {
2411        let mut f = Fixture::new();
2412        f.run(&[b"SET", b"a", b"v1", b"EX", b"100"]);
2413        f.run(&[b"SET", b"b", b"v2"]);
2414
2415        assert_eq!(f.run(&[b"COPY", b"a", b"b"]), ":0\r\n");
2416        assert_eq!(f.run(&[b"GET", b"b"]), "$2\r\nv2\r\n");
2417        assert_eq!(f.run(&[b"COPY", b"a", b"b", b"REPLACE"]), ":1\r\n");
2418        assert_eq!(f.run(&[b"GET", b"b"]), "$2\r\nv1\r\n");
2419        assert_eq!(f.run(&[b"TTL", b"b"]), ":100\r\n", "the deadline came too");
2420        assert_eq!(f.run(&[b"COPY", b"nosuch", b"z"]), ":0\r\n");
2421    }
2422
2423    #[test]
2424    fn a_copy_into_another_database_is_a_copy_and_onto_itself_there_is_too() {
2425        let mut f = Fixture::new();
2426        f.run(&[b"SET", b"a", b"v1"]);
2427
2428        // Same key, different database, so this is not the same object and is
2429        // an ordinary copy. Same key in the same database is the error below.
2430        assert_eq!(f.run(&[b"COPY", b"a", b"a", b"DB", b"1"]), ":1\r\n");
2431        f.run(&[b"SELECT", b"1"]);
2432        assert_eq!(f.run(&[b"GET", b"a"]), "$2\r\nv1\r\n");
2433        assert_eq!(
2434            f.run(&[b"COPY", b"a", b"a", b"DB", b"0"]),
2435            ":0\r\n",
2436            "taken"
2437        );
2438        assert_eq!(
2439            f.run(&[b"COPY", b"a", b"a", b"DB", b"0", b"REPLACE"]),
2440            ":1\r\n"
2441        );
2442    }
2443
2444    #[test]
2445    fn sort_takes_its_options_in_any_order_and_the_last_one_wins() {
2446        let mut f = Fixture::new();
2447        f.run(&[b"RPUSH", b"l", b"3", b"1", b"2"]);
2448        assert_eq!(
2449            f.run(&[b"SORT", b"l"]),
2450            "*3\r\n$1\r\n1\r\n$1\r\n2\r\n$1\r\n3\r\n"
2451        );
2452        // DESC then ASC is ASC, because the only thing ASC does is undo a DESC.
2453        assert_eq!(
2454            f.run(&[b"SORT", b"l", b"DESC", b"asc"]),
2455            "*3\r\n$1\r\n1\r\n$1\r\n2\r\n$1\r\n3\r\n"
2456        );
2457        assert_eq!(
2458            f.run(&[b"sort", b"l", b"LIMIT", b"1", b"1", b"DESC"]),
2459            "*1\r\n$1\r\n2\r\n"
2460        );
2461    }
2462
2463    #[test]
2464    fn sort_reads_a_key_per_element_for_by_and_for_get() {
2465        let mut f = Fixture::new();
2466        f.run(&[b"RPUSH", b"l", b"a", b"b"]);
2467        f.run(&[b"MSET", b"w_a", b"2", b"w_b", b"1", b"d_b", b"bee"]);
2468        // `b` weighs less so it comes first, and its `GET` hits where `a`'s
2469        // misses, which is a nil in the middle of the array and not a short one.
2470        assert_eq!(
2471            f.run(&[b"SORT", b"l", b"BY", b"w_*", b"GET", b"#", b"GET", b"d_*"]),
2472            "*4\r\n$1\r\nb\r\n$3\r\nbee\r\n$1\r\na\r\n$-1\r\n"
2473        );
2474    }
2475
2476    #[test]
2477    fn sort_store_writes_a_list_and_answers_its_length() {
2478        let mut f = Fixture::new();
2479        f.run(&[b"RPUSH", b"l", b"3", b"1", b"2"]);
2480        assert_eq!(f.run(&[b"SORT", b"l", b"STORE", b"out"]), ":3\r\n");
2481        assert_eq!(f.run(&[b"TYPE", b"out"]), "+list\r\n");
2482        assert_eq!(
2483            f.run(&[b"LRANGE", b"out", b"0", b"-1"]),
2484            "*3\r\n$1\r\n1\r\n$1\r\n2\r\n$1\r\n3\r\n"
2485        );
2486        // An empty result takes the destination with it rather than leaving a
2487        // list that holds nothing.
2488        assert_eq!(f.run(&[b"SORT", b"missing", b"STORE", b"out"]), ":0\r\n");
2489        assert_eq!(f.run(&[b"EXISTS", b"out"]), ":0\r\n");
2490    }
2491
2492    #[test]
2493    fn sort_ro_does_not_know_the_word_store() {
2494        let mut f = Fixture::new();
2495        f.run(&[b"RPUSH", b"l", b"2", b"1"]);
2496        assert_eq!(f.run(&[b"SORT_RO", b"l"]), "*2\r\n$1\r\n1\r\n$1\r\n2\r\n");
2497        assert_eq!(
2498            f.run(&[b"SORT_RO", b"l", b"STORE", b"d"]),
2499            "-ERR syntax error\r\n"
2500        );
2501        assert_eq!(f.run(&[b"EXISTS", b"d"]), ":0\r\n");
2502    }
2503
2504    #[test]
2505    fn sort_refuses_what_it_cannot_sort() {
2506        let mut f = Fixture::new();
2507        assert_eq!(f.run(&[b"SORT", b"nosuchkey"]), "*0\r\n");
2508        f.run(&[b"SET", b"s", b"x"]);
2509        assert_eq!(
2510            f.run(&[b"SORT", b"s"]),
2511            "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n"
2512        );
2513        f.run(&[b"RPUSH", b"words", b"one", b"two"]);
2514        assert_eq!(
2515            f.run(&[b"SORT", b"words"]),
2516            "-ERR One or more scores can't be converted into double\r\n"
2517        );
2518        assert_eq!(
2519            f.run(&[b"SORT", b"words", b"ALPHA"]),
2520            "*2\r\n$3\r\none\r\n$3\r\ntwo\r\n"
2521        );
2522        assert_eq!(f.run(&[b"SORT", b"words", b"BY"]), "-ERR syntax error\r\n");
2523    }
2524
2525    #[test]
2526    fn move_takes_the_key_out_of_one_database_and_puts_it_in_another() {
2527        let mut f = Fixture::new();
2528        assert_eq!(f.run(&[b"RPUSH", b"l", b"a", b"b"]), ":2\r\n");
2529        assert_eq!(f.run(&[b"MOVE", b"l", b"1"]), ":1\r\n");
2530        assert_eq!(f.run(&[b"EXISTS", b"l"]), ":0\r\n");
2531        assert_eq!(f.run(&[b"SELECT", b"1"]), "+OK\r\n");
2532        assert_eq!(
2533            f.run(&[b"LRANGE", b"l", b"0", b"-1"]),
2534            "*2\r\n$1\r\na\r\n$1\r\nb\r\n"
2535        );
2536        // And back, which proves the body survived the trip rather than being
2537        // rebuilt from a copy that happened to look the same.
2538        assert_eq!(f.run(&[b"MOVE", b"l", b"0"]), ":1\r\n");
2539        assert_eq!(f.run(&[b"EXISTS", b"l"]), ":0\r\n");
2540    }
2541
2542    #[test]
2543    fn move_answers_zero_when_either_end_says_no() {
2544        let mut f = Fixture::new();
2545        assert_eq!(f.run(&[b"MOVE", b"nope", b"1"]), ":0\r\n");
2546        assert_eq!(f.run(&[b"SET", b"a", b"here"]), "+OK\r\n");
2547        assert_eq!(f.run(&[b"SELECT", b"1"]), "+OK\r\n");
2548        assert_eq!(f.run(&[b"SET", b"a", b"there"]), "+OK\r\n");
2549        assert_eq!(f.run(&[b"SELECT", b"0"]), "+OK\r\n");
2550        // The destination is taken, so nothing moves and the source is still
2551        // there with what it had.
2552        assert_eq!(f.run(&[b"MOVE", b"a", b"1"]), ":0\r\n");
2553        assert_eq!(f.run(&[b"GET", b"a"]), "$4\r\nhere\r\n");
2554        assert_eq!(f.run(&[b"SELECT", b"1"]), "+OK\r\n");
2555        assert_eq!(f.run(&[b"GET", b"a"]), "$5\r\nthere\r\n");
2556    }
2557
2558    #[test]
2559    fn move_refuses_a_database_that_is_not_one_and_the_one_it_is_on() {
2560        let mut f = Fixture::new();
2561        assert_eq!(
2562            f.run(&[b"MOVE", b"a", b"0"]),
2563            "-ERR source and destination objects are the same\r\n"
2564        );
2565        assert_eq!(
2566            f.run(&[b"MOVE", b"a", b"99"]),
2567            "-ERR DB index is out of range\r\n"
2568        );
2569        assert_eq!(
2570            f.run(&[b"MOVE", b"a", b"-1"]),
2571            "-ERR DB index is out of range\r\n"
2572        );
2573        assert_eq!(
2574            f.run(&[b"MOVE", b"a", b"x"]),
2575            "-ERR value is not an integer or out of range\r\n"
2576        );
2577    }
2578
2579    #[test]
2580    fn swapdb_swaps_what_two_connections_would_see() {
2581        let mut f = Fixture::new();
2582        assert_eq!(f.run(&[b"SET", b"k", b"zero"]), "+OK\r\n");
2583        assert_eq!(f.run(&[b"SELECT", b"1"]), "+OK\r\n");
2584        assert_eq!(f.run(&[b"SET", b"k", b"one"]), "+OK\r\n");
2585        assert_eq!(f.run(&[b"SELECT", b"0"]), "+OK\r\n");
2586
2587        assert_eq!(f.run(&[b"SWAPDB", b"0", b"1"]), "+OK\r\n");
2588        // Still on database zero, and database zero is a different database.
2589        assert_eq!(f.run(&[b"GET", b"k"]), "$3\r\none\r\n");
2590        assert_eq!(f.run(&[b"SELECT", b"1"]), "+OK\r\n");
2591        assert_eq!(f.run(&[b"GET", b"k"]), "$4\r\nzero\r\n");
2592        // A database swapped with itself is fine and changes nothing.
2593        assert_eq!(f.run(&[b"SWAPDB", b"1", b"1"]), "+OK\r\n");
2594        assert_eq!(f.run(&[b"GET", b"k"]), "$4\r\nzero\r\n");
2595    }
2596
2597    /// Every database on a server reads the server's clock and not one of its
2598    /// own. They used to be told the time one at a time and now they share the
2599    /// reading, so a server that built its databases from a second clock would
2600    /// answer a deadline worked out against a time nobody had set.
2601    #[test]
2602    fn a_wide_server_puts_its_databases_on_its_own_clock() {
2603        let mut f = Fixture::striped(8);
2604        f.server.set_clock_ms(1_700_000_000_000);
2605        assert_eq!(f.run(&[b"SET", b"k", b"v", b"EX", b"100"]), "+OK\r\n");
2606        assert_eq!(f.run(&[b"EXPIRETIME", b"k"]), ":1700000100\r\n");
2607        assert_eq!(f.run(&[b"TTL", b"k"]), ":100\r\n");
2608        f.server.set_clock_ms(1_700_000_050_000);
2609        assert_eq!(f.run(&[b"TTL", b"k"]), ":50\r\n");
2610    }
2611
2612    /// The swap is stripe by stripe, so a database cut into more than one
2613    /// stripe is the case that would catch it exchanging some of the keys and
2614    /// leaving the rest. Sixteen keys over four stripes is enough that every
2615    /// stripe has something in it whatever the hashes come out as.
2616    #[test]
2617    fn swapdb_swaps_every_stripe_of_a_wide_database() {
2618        let mut f = Fixture::striped(4);
2619        for i in 0..16u32 {
2620            let key = format!("k{i}");
2621            assert_eq!(f.run(&[b"SET", key.as_bytes(), b"zero"]), "+OK\r\n");
2622        }
2623        assert_eq!(f.run(&[b"SELECT", b"1"]), "+OK\r\n");
2624        assert_eq!(f.run(&[b"SET", b"only", b"one"]), "+OK\r\n");
2625        assert_eq!(f.run(&[b"SELECT", b"0"]), "+OK\r\n");
2626
2627        assert_eq!(f.run(&[b"SWAPDB", b"0", b"1"]), "+OK\r\n");
2628        assert_eq!(f.run(&[b"DBSIZE"]), ":1\r\n");
2629        assert_eq!(f.run(&[b"GET", b"only"]), "$3\r\none\r\n");
2630        assert_eq!(f.run(&[b"SELECT", b"1"]), "+OK\r\n");
2631        assert_eq!(f.run(&[b"DBSIZE"]), ":16\r\n");
2632        for i in 0..16u32 {
2633            let key = format!("k{i}");
2634            assert_eq!(f.run(&[b"GET", key.as_bytes()]), "$4\r\nzero\r\n");
2635        }
2636    }
2637
2638    #[test]
2639    fn swapdb_says_which_index_it_could_not_read() {
2640        let mut f = Fixture::new();
2641        assert_eq!(
2642            f.run(&[b"SWAPDB", b"x", b"1"]),
2643            "-ERR invalid first DB index\r\n"
2644        );
2645        assert_eq!(
2646            f.run(&[b"SWAPDB", b"0", b"y"]),
2647            "-ERR invalid second DB index\r\n"
2648        );
2649        // A number too big to be an index on a server that keeps one in an int
2650        // is the same complaint, and a plausible one that is not ours is the
2651        // range complaint instead. The split is Redis's.
2652        assert_eq!(
2653            f.run(&[b"SWAPDB", b"99999999999999", b"1"]),
2654            "-ERR invalid first DB index\r\n"
2655        );
2656        assert_eq!(
2657            f.run(&[b"SWAPDB", b"0", b"99"]),
2658            "-ERR DB index is out of range\r\n"
2659        );
2660        assert_eq!(
2661            f.run(&[b"SWAPDB", b"-1", b"0"]),
2662            "-ERR DB index is out of range\r\n"
2663        );
2664    }
2665
2666    #[test]
2667    fn wait_answers_zero_replicas_without_waiting() {
2668        let mut f = Fixture::new();
2669        assert_eq!(f.run(&[b"SET", b"a", b"v"]), "+OK\r\n");
2670        assert_eq!(f.run(&[b"WAIT", b"0", b"0"]), ":0\r\n");
2671        // A replica that is never going to arrive, and a timeout that would be
2672        // a real wait on a server that had one.
2673        assert_eq!(f.run(&[b"WAIT", b"3", b"1000"]), ":0\r\n");
2674        // Negative replicas is not an error, because zero is already more than
2675        // it asked for.
2676        assert_eq!(f.run(&[b"WAIT", b"-1", b"0"]), ":0\r\n");
2677        assert_eq!(
2678            f.run(&[b"WAIT", b"x", b"0"]),
2679            "-ERR value is not an integer or out of range\r\n"
2680        );
2681        assert_eq!(
2682            f.run(&[b"WAIT", b"0", b"-1"]),
2683            "-ERR timeout is negative\r\n"
2684        );
2685        assert_eq!(
2686            f.run(&[b"WAIT", b"0", b"1.5"]),
2687            "-ERR timeout is not an integer or out of range\r\n"
2688        );
2689    }
2690
2691    #[test]
2692    fn waitaof_answers_two_zeroes_and_refuses_a_local_wait() {
2693        let mut f = Fixture::new();
2694        assert_eq!(f.run(&[b"WAITAOF", b"0", b"0", b"0"]), "*2\r\n:0\r\n:0\r\n");
2695        assert_eq!(
2696            f.run(&[b"WAITAOF", b"1", b"0", b"0"]),
2697            "-ERR WAITAOF cannot be used when numlocal is set but appendonly is disabled.\r\n"
2698        );
2699        assert_eq!(
2700            f.run(&[b"WAITAOF", b"2", b"0", b"0"]),
2701            "-ERR value is out of range, value must between 0 and 1\r\n"
2702        );
2703        assert_eq!(
2704            f.run(&[b"WAITAOF", b"0", b"-1", b"0"]),
2705            "-ERR value is out of range, must be positive\r\n"
2706        );
2707        // The arguments are all read before the server looks at itself, so a
2708        // bad timeout beats the append only complaint even with numlocal set.
2709        assert_eq!(
2710            f.run(&[b"WAITAOF", b"1", b"0", b"-5"]),
2711            "-ERR timeout is negative\r\n"
2712        );
2713    }
2714
2715    /// The bytes inside a bulk reply, with the header and the trailing break
2716    /// taken off. Every `DUMP` test needs this and none of them care how the
2717    /// length was written.
2718    fn payload(reply: &[u8]) -> Vec<u8> {
2719        let head = reply.windows(2).position(|w| w == b"\r\n").unwrap();
2720        reply[head + 2..reply.len() - 2].to_vec()
2721    }
2722
2723    #[test]
2724    fn a_value_survives_a_dump_and_a_restore() {
2725        let mut f = Fixture::new();
2726        f.run(&[b"SET", b"s", b"hello"]);
2727        f.run(&[b"RPUSH", b"l", b"a", b"b", b"c"]);
2728        f.run(&[b"SADD", b"t", b"1", b"2", b"3"]);
2729        f.run(&[b"SADD", b"u", b"x", b"y"]);
2730        f.run(&[b"HSET", b"h", b"f", b"1", b"g", b"2"]);
2731        f.run(&[b"ZADD", b"z", b"1.5", b"a", b"2.5", b"b"]);
2732
2733        for key in [&b"s"[..], b"l", b"t", b"u", b"h", b"z"] {
2734            let mut copy = key.to_vec();
2735            copy.push(b'2');
2736            let bytes = payload(&f.raw(&[b"DUMP", key]));
2737            assert_eq!(f.run(&[b"RESTORE", &copy, b"0", &bytes]), "+OK\r\n");
2738            assert_eq!(f.run(&[b"TYPE", &copy]), f.run(&[b"TYPE", key]));
2739        }
2740
2741        assert_eq!(f.run(&[b"GET", b"s2"]), "$5\r\nhello\r\n");
2742        assert_eq!(
2743            f.run(&[b"LRANGE", b"l2", b"0", b"-1"]),
2744            "*3\r\n$1\r\na\r\n$1\r\nb\r\n$1\r\nc\r\n"
2745        );
2746        assert_eq!(sorted(&f.run(&[b"SMEMBERS", b"t2"])), ["1", "2", "3"]);
2747        assert_eq!(sorted(&f.run(&[b"SMEMBERS", b"u2"])), ["x", "y"]);
2748        assert_eq!(f.run(&[b"HGET", b"h2", b"g"]), "$1\r\n2\r\n");
2749        assert_eq!(f.run(&[b"ZSCORE", b"z2", b"b"]), "$3\r\n2.5\r\n");
2750        // The encoding survives too, since the payload names the plainest legal
2751        // type and the loader puts the value back on the rung it belongs on.
2752        assert_eq!(
2753            f.run(&[b"OBJECT", b"ENCODING", b"t2"]),
2754            f.run(&[b"OBJECT", b"ENCODING", b"t"])
2755        );
2756    }
2757
2758    #[test]
2759    fn a_dumped_hash_keeps_its_field_deadlines() {
2760        let mut f = Fixture::new();
2761        f.run(&[b"HSET", b"h", b"keep", b"1", b"go", b"2"]);
2762        assert_eq!(
2763            f.run(&[b"HEXPIRE", b"h", b"100", b"FIELDS", b"1", b"go"]),
2764            "*1\r\n:1\r\n"
2765        );
2766        let bytes = payload(&f.raw(&[b"DUMP", b"h"]));
2767        assert_eq!(f.run(&[b"RESTORE", b"h2", b"0", &bytes]), "+OK\r\n");
2768        assert_eq!(
2769            f.run(&[b"HTTL", b"h2", b"FIELDS", b"2", b"keep", b"go"]),
2770            "*2\r\n:-1\r\n:100\r\n"
2771        );
2772    }
2773
2774    #[test]
2775    fn dump_leaves_the_deadline_behind_and_restore_is_given_a_new_one() {
2776        let mut f = Fixture::new();
2777        f.run(&[b"SET", b"a", b"v", b"EX", b"100"]);
2778        let bytes = payload(&f.raw(&[b"DUMP", b"a"]));
2779        assert_eq!(f.run(&[b"RESTORE", b"b", b"0", &bytes]), "+OK\r\n");
2780        assert_eq!(f.run(&[b"TTL", b"b"]), ":-1\r\n");
2781        assert_eq!(f.run(&[b"RESTORE", b"c", b"5000", &bytes]), "+OK\r\n");
2782        assert_eq!(f.run(&[b"TTL", b"c"]), ":5\r\n");
2783        // An absolute deadline that has already gone is not an error. The key is
2784        // not created and the reply is the same OK a live one gets.
2785        assert_eq!(
2786            f.run(&[b"RESTORE", b"d", b"1", &bytes, b"ABSTTL"]),
2787            "+OK\r\n"
2788        );
2789        assert_eq!(f.run(&[b"EXISTS", b"d"]), ":0\r\n");
2790    }
2791
2792    #[test]
2793    fn dump_answers_nothing_for_a_key_that_is_not_there() {
2794        let mut f = Fixture::new();
2795        assert_eq!(f.run(&[b"DUMP", b"nope"]), "$-1\r\n");
2796        f.run(&[b"SET", b"gone", b"v", b"PX", b"10"]);
2797        f.advance(50);
2798        assert_eq!(f.run(&[b"DUMP", b"gone"]), "$-1\r\n");
2799    }
2800
2801    #[test]
2802    fn restore_refuses_a_key_that_is_there_unless_it_is_told_to_replace() {
2803        let mut f = Fixture::new();
2804        f.run(&[b"SET", b"a", b"first"]);
2805        f.run(&[b"SET", b"b", b"second"]);
2806        let bytes = payload(&f.raw(&[b"DUMP", b"b"]));
2807        assert_eq!(
2808            f.run(&[b"RESTORE", b"a", b"0", &bytes]),
2809            "-BUSYKEY Target key name already exists.\r\n"
2810        );
2811        assert_eq!(f.run(&[b"GET", b"a"]), "$5\r\nfirst\r\n");
2812        assert_eq!(
2813            f.run(&[b"RESTORE", b"a", b"0", &bytes, b"REPLACE"]),
2814            "+OK\r\n"
2815        );
2816        assert_eq!(f.run(&[b"GET", b"a"]), "$6\r\nsecond\r\n");
2817    }
2818
2819    /// The busy key comes before the payload, which is not the order the
2820    /// arguments read in. Whether a key is taken should not depend on whether
2821    /// the bytes behind it happened to be good.
2822    #[test]
2823    fn restore_asks_about_the_key_before_it_looks_at_the_bytes() {
2824        let mut f = Fixture::new();
2825        f.run(&[b"SET", b"a", b"v"]);
2826        assert_eq!(
2827            f.run(&[b"RESTORE", b"a", b"0", b"rubbish"]),
2828            "-BUSYKEY Target key name already exists.\r\n"
2829        );
2830        // And the options come before even that, so a bad FREQ beats the busy
2831        // key the same way a bad DB beats a missing source in COPY.
2832        assert_eq!(
2833            f.run(&[b"RESTORE", b"a", b"0", b"rubbish", b"FREQ", b"300"]),
2834            "-ERR Invalid FREQ value, must be >= 0 and <= 255\r\n"
2835        );
2836    }
2837
2838    #[test]
2839    fn restore_can_tell_a_bad_footer_from_bad_bytes() {
2840        let mut f = Fixture::new();
2841        f.run(&[b"SET", b"a", b"hello"]);
2842        let good = payload(&f.raw(&[b"DUMP", b"a"]));
2843
2844        let mut flipped = good.clone();
2845        flipped[2] ^= 0x40;
2846        assert_eq!(
2847            f.run(&[b"RESTORE", b"b", b"0", &flipped]),
2848            "-ERR DUMP payload version or checksum are wrong\r\n"
2849        );
2850        assert_eq!(
2851            f.run(&[b"RESTORE", b"b", b"0", b"short"]),
2852            "-ERR DUMP payload version or checksum are wrong\r\n"
2853        );
2854        // A footer that is right over a body that is not. The type byte says
2855        // string and there is nothing behind it, so the checksum agrees and the
2856        // value does not exist.
2857        let mut truncated = good[..1].to_vec();
2858        truncated.extend_from_slice(&good[good.len() - 10..good.len() - 8]);
2859        let crc = yo_common::crc::crc64(0, &truncated);
2860        truncated.extend_from_slice(&crc.to_le_bytes());
2861        assert_eq!(
2862            f.run(&[b"RESTORE", b"b", b"0", &truncated]),
2863            "-ERR Bad data format\r\n"
2864        );
2865        assert_eq!(f.run(&[b"EXISTS", b"b"]), ":0\r\n");
2866    }
2867
2868    #[test]
2869    fn restore_checks_the_three_numbers_a_client_can_get_wrong() {
2870        let mut f = Fixture::new();
2871        f.run(&[b"SET", b"a", b"v"]);
2872        let bytes = payload(&f.raw(&[b"DUMP", b"a"]));
2873        assert_eq!(
2874            f.run(&[b"RESTORE", b"b", b"-1", &bytes]),
2875            "-ERR Invalid TTL value, must be >= 0\r\n"
2876        );
2877        assert_eq!(
2878            f.run(&[b"RESTORE", b"b", b"0", &bytes, b"IDLETIME", b"-1"]),
2879            "-ERR Invalid IDLETIME value, must be >= 0\r\n"
2880        );
2881        assert_eq!(
2882            f.run(&[b"RESTORE", b"b", b"0", &bytes, b"FREQ", b"256"]),
2883            "-ERR Invalid FREQ value, must be >= 0 and <= 255\r\n"
2884        );
2885        // Both are accepted and both are then dropped, which is D-26.
2886        assert_eq!(
2887            f.run(&[b"RESTORE", b"b", b"0", &bytes, b"IDLETIME", b"90"]),
2888            "+OK\r\n"
2889        );
2890        assert_eq!(
2891            f.run(&[b"RESTORE", b"c", b"0", &bytes, b"FREQ", b"200", b"REPLACE"]),
2892            "+OK\r\n"
2893        );
2894    }
2895
2896    /// Neither word is refused for being the wrong one. Each is only accepted
2897    /// while the other is unset, so the second of the two falls through to the
2898    /// plain syntax error rather than getting a message of its own.
2899    #[test]
2900    fn restore_takes_idletime_or_freq_and_not_both() {
2901        let mut f = Fixture::new();
2902        f.run(&[b"SET", b"a", b"v"]);
2903        let bytes = payload(&f.raw(&[b"DUMP", b"a"]));
2904        assert_eq!(
2905            f.run(&[
2906                b"RESTORE",
2907                b"b",
2908                b"0",
2909                &bytes,
2910                b"IDLETIME",
2911                b"1",
2912                b"FREQ",
2913                b"2"
2914            ]),
2915            "-ERR syntax error\r\n"
2916        );
2917        assert_eq!(
2918            f.run(&[
2919                b"RESTORE",
2920                b"b",
2921                b"0",
2922                &bytes,
2923                b"FREQ",
2924                b"2",
2925                b"IDLETIME",
2926                b"1"
2927            ]),
2928            "-ERR syntax error\r\n"
2929        );
2930        assert_eq!(
2931            f.run(&[b"RESTORE", b"b", b"0", &bytes, b"FREQ"]),
2932            "-ERR syntax error\r\n"
2933        );
2934        assert_eq!(
2935            f.run(&[b"RESTORE", b"b", b"0", &bytes, b"NOSUCH"]),
2936            "-ERR syntax error\r\n"
2937        );
2938    }
2939
2940    #[test]
2941    fn copy_checks_its_options_before_it_looks_for_anything() {
2942        let mut f = Fixture::new();
2943        // No key exists at all, and every one of these is still the option
2944        // complaint rather than a zero, which is the order a real server uses.
2945        assert_eq!(
2946            f.run(&[b"COPY", b"a", b"b", b"DB", b"99"]),
2947            "-ERR DB index is out of range\r\n"
2948        );
2949        assert_eq!(
2950            f.run(&[b"COPY", b"a", b"b", b"DB", b"-1"]),
2951            "-ERR DB index is out of range\r\n"
2952        );
2953        assert_eq!(
2954            f.run(&[b"COPY", b"a", b"b", b"DB", b"x"]),
2955            "-ERR value is not an integer or out of range\r\n"
2956        );
2957        assert_eq!(
2958            f.run(&[b"COPY", b"a", b"b", b"nonsense"]),
2959            "-ERR syntax error\r\n"
2960        );
2961        assert_eq!(
2962            f.run(&[b"COPY", b"a", b"a"]),
2963            "-ERR source and destination objects are the same\r\n"
2964        );
2965        // Repeated, reordered and lowercased, and the last DB wins.
2966        assert_eq!(
2967            f.run(&[b"COPY", b"a", b"b", b"dB", b"1", b"rEpLaCe", b"db", b"2"]),
2968            ":0\r\n"
2969        );
2970    }
2971
2972    #[test]
2973    fn time_is_two_bulk_strings_and_moves() {
2974        let mut f = Fixture::new();
2975        let first = f.run(&[b"TIME"]);
2976        assert!(first.starts_with("*2\r\n$"), "got {first}");
2977        let parts: Vec<&str> = first.split("\r\n").collect();
2978        let secs: i64 = parts[2].parse().expect("seconds as decimal text");
2979        let micros: i64 = parts[4].parse().expect("microseconds as decimal text");
2980        assert!(secs > 1_700_000_000, "a real wall clock, got {secs}");
2981        assert!((0..1_000_000).contains(&micros), "got {micros}");
2982        // The coarse clock the keyspace uses is a cached millisecond that a
2983        // background tick refreshes, so a TIME built on it would answer the
2984        // same microsecond twice in a row here.
2985        assert_ne!(first, f.run(&[b"TIME"]));
2986    }
2987
2988    #[test]
2989    fn a_keyspace_scan_walks_every_key_once() {
2990        // The count below is thirty two, so ninety six keys is three pages of
2991        // cursor and says the same thing as five hundred at a fifth of the
2992        // interpreted work.
2993        let n = if cfg!(miri) { 96 } else { 500 };
2994        let mut f = Fixture::new();
2995        for i in 0..n {
2996            f.run(&[b"SET", format!("k{i}").as_bytes(), b"v"]);
2997        }
2998
2999        let mut seen: Vec<String> = Vec::new();
3000        let mut cursor = "0".to_owned();
3001        let mut calls = 0;
3002        loop {
3003            let (next, keys) = scan_reply(&f.run(&[b"SCAN", cursor.as_bytes(), b"COUNT", b"32"]));
3004            seen.extend(keys);
3005            cursor = next;
3006            calls += 1;
3007            assert!(calls < 10_000, "the cursor is not advancing");
3008            if cursor == "0" {
3009                break;
3010            }
3011        }
3012
3013        seen.sort();
3014        seen.dedup();
3015        assert_eq!(seen.len(), n, "every key once and only once");
3016        // And more than one call to get them, or the COUNT is being ignored and
3017        // the loop above proved nothing about resuming.
3018        assert!(calls > 1, "{n} keys came back in one batch");
3019    }
3020
3021    #[test]
3022    fn a_scan_narrows_by_pattern_and_by_type() {
3023        let mut f = Fixture::new();
3024        f.run(&[b"SET", b"str", b"v"]);
3025        f.run(&[b"SADD", b"members", b"a"]);
3026        f.run(&[b"HSET", b"fields", b"f", b"v"]);
3027
3028        let all = |f: &mut Fixture, args: &[&[u8]]| {
3029            let mut out: Vec<String> = Vec::new();
3030            let mut cursor = "0".to_owned();
3031            loop {
3032                let mut line: Vec<&[u8]> = vec![b"SCAN", cursor.as_bytes()];
3033                line.extend_from_slice(args);
3034                let (next, keys) = scan_reply(&f.run(&line));
3035                out.extend(keys);
3036                cursor = next;
3037                if cursor == "0" {
3038                    break;
3039                }
3040            }
3041            out.sort();
3042            out
3043        };
3044
3045        assert_eq!(all(&mut f, &[]), ["fields", "members", "str"]);
3046        assert_eq!(all(&mut f, &[b"MATCH", b"*e*"]), ["fields", "members"]);
3047        assert_eq!(all(&mut f, &[b"TYPE", b"set"]), ["members"]);
3048        // Case insensitive, the same as Redis's own comparison.
3049        assert_eq!(all(&mut f, &[b"TYPE", b"HASH"]), ["fields"]);
3050        // A type nothing can hold is not an error, it just matches nothing.
3051        assert!(all(&mut f, &[b"TYPE", b"list"]).is_empty());
3052        assert!(all(&mut f, &[b"TYPE", b"banana"]).is_empty());
3053        // Both filters at once, and they are an and rather than an or.
3054        assert!(all(&mut f, &[b"MATCH", b"str*", b"TYPE", b"set"]).is_empty());
3055    }
3056
3057    #[test]
3058    fn a_scan_says_what_is_wrong_with_it() {
3059        let mut f = Fixture::new();
3060        assert_eq!(f.run(&[b"SCAN", b"nope"]), "-ERR invalid cursor\r\n");
3061        assert_eq!(f.run(&[b"SCAN", b"-1"]), "-ERR invalid cursor\r\n");
3062        assert_eq!(f.run(&[b"SCAN", b"0", b"MATCH"]), "-ERR syntax error\r\n");
3063        assert_eq!(
3064            f.run(&[b"SCAN", b"0", b"COUNT", b"0"]),
3065            "-ERR syntax error\r\n"
3066        );
3067        assert_eq!(
3068            f.run(&[b"SCAN", b"0", b"COUNT", b"x"]),
3069            "-ERR value is not an integer or out of range\r\n"
3070        );
3071        assert_eq!(
3072            f.run(&[b"SCAN", b"0", b"WAT", b"1"]),
3073            "-ERR syntax error\r\n"
3074        );
3075        // A cursor the client made up is a cursor. It resumes somewhere
3076        // arbitrary and answers whatever is there, which is what Redis does and
3077        // is the only behaviour that does not need the server to remember every
3078        // cursor it has handed out.
3079        assert!(f.run(&[b"SCAN", b"18446744073709551615"]).starts_with("*2"));
3080    }
3081
3082    #[test]
3083    fn keys_and_randomkey_look_at_the_whole_database() {
3084        let mut f = Fixture::new();
3085        assert_eq!(f.run(&[b"KEYS", b"*"]), "*0\r\n");
3086        assert_eq!(f.run(&[b"RANDOMKEY"]), "$-1\r\n");
3087
3088        for name in ["one", "two", "three"] {
3089            f.run(&[b"SET", name.as_bytes(), b"v"]);
3090        }
3091        assert_eq!(sorted(&f.run(&[b"KEYS", b"*"])), ["one", "three", "two"]);
3092        assert_eq!(sorted(&f.run(&[b"KEYS", b"t*"])), ["three", "two"]);
3093        assert_eq!(f.run(&[b"KEYS", b"nothing"]), "*0\r\n");
3094
3095        for _ in 0..50 {
3096            let got = f.run(&[b"RANDOMKEY"]);
3097            assert!(
3098                ["$3\r\none\r\n", "$3\r\ntwo\r\n", "$5\r\nthree\r\n"].contains(&got.as_str()),
3099                "got {got}"
3100            );
3101        }
3102    }
3103
3104    #[test]
3105    fn a_walk_does_not_answer_keys_that_have_expired() {
3106        let mut f = Fixture::new();
3107        f.run(&[b"SET", b"alive", b"v"]);
3108        f.run(&[b"SET", b"dead", b"v", b"PX", b"1"]);
3109        f.server.advance_clock_ms(2);
3110        assert_eq!(
3111            f.run(&[b"DBSIZE"]),
3112            ":2\r\n",
3113            "nothing has collected it yet"
3114        );
3115
3116        assert_eq!(f.run(&[b"KEYS", b"*"]), "*1\r\n$5\r\nalive\r\n");
3117        let (_, keys) = scan_reply(&f.run(&[b"SCAN", b"0", b"COUNT", b"1000"]));
3118        assert_eq!(keys, ["alive"]);
3119        for _ in 0..20 {
3120            assert_eq!(f.run(&[b"RANDOMKEY"]), "$5\r\nalive\r\n");
3121        }
3122        // The walk collected it on the way past, which is what makes DBSIZE
3123        // here answer what Redis answers once its own cycle has been round.
3124        assert_eq!(f.run(&[b"DBSIZE"]), ":1\r\n");
3125    }
3126
3127    #[test]
3128    fn a_key_deadline_goes_on_and_comes_back_in_all_four_units() {
3129        let mut f = Fixture::new();
3130        f.run(&[b"SET", b"k", b"v"]);
3131        assert_eq!(f.run(&[b"TTL", b"k"]), ":-1\r\n", "there and no deadline");
3132        assert_eq!(f.run(&[b"TTL", b"nosuch"]), ":-2\r\n", "not there at all");
3133
3134        assert_eq!(f.run(&[b"EXPIRE", b"k", b"100"]), ":1\r\n");
3135        assert_eq!(f.run(&[b"TTL", b"k"]), ":100\r\n");
3136        let ms = int(&f.run(&[b"PTTL", b"k"]));
3137        assert!((99_000..=100_000).contains(&ms), "got {ms}");
3138
3139        // The absolute pair, derived from the same one number the store kept.
3140        let at = int(&f.run(&[b"EXPIRETIME", b"k"]));
3141        let at_ms = int(&f.run(&[b"PEXPIRETIME", b"k"]));
3142        assert_eq!(at, (at_ms + 500) / 1000);
3143        assert!(at_ms > 1_700_000_000_000, "an absolute moment, got {at_ms}");
3144
3145        assert_eq!(f.run(&[b"PERSIST", b"k"]), ":1\r\n");
3146        assert_eq!(f.run(&[b"TTL", b"k"]), ":-1\r\n");
3147        assert_eq!(
3148            f.run(&[b"PERSIST", b"k"]),
3149            ":0\r\n",
3150            "nothing to take off the second time"
3151        );
3152        assert_eq!(f.run(&[b"PERSIST", b"nosuch"]), ":0\r\n");
3153        assert_eq!(
3154            f.run(&[b"GET", b"k"]),
3155            "$1\r\nv\r\n",
3156            "and the value went through all of that untouched"
3157        );
3158    }
3159
3160    #[test]
3161    fn every_type_can_be_given_a_deadline_and_it_is_the_same_deadline() {
3162        let mut f = Fixture::new();
3163        f.run(&[b"SET", b"str", b"v"]);
3164        f.run(&[b"SADD", b"set", b"a", b"b"]);
3165        f.run(&[b"HSET", b"hash", b"f", b"v"]);
3166
3167        for key in [b"str".as_slice(), b"set", b"hash"] {
3168            assert_eq!(f.run(&[b"EXPIRE", key, b"100"]), ":1\r\n");
3169            assert_eq!(f.run(&[b"TTL", key]), ":100\r\n");
3170        }
3171        // The body is not touched by any of that, which is the whole reason the
3172        // deadline lives in the record and the body lives somewhere else.
3173        assert_eq!(f.run(&[b"SCARD", b"set"]), ":2\r\n");
3174        assert_eq!(f.run(&[b"HGET", b"hash", b"f"]), "$1\r\nv\r\n");
3175        assert_eq!(f.run(&[b"GET", b"str"]), "$1\r\nv\r\n");
3176    }
3177
3178    #[test]
3179    fn a_deadline_that_has_already_gone_deletes_the_key_now() {
3180        let mut f = Fixture::new();
3181        for key in [b"a".as_slice(), b"b", b"c", b"d"] {
3182            f.run(&[b"SET", key, b"v"]);
3183        }
3184        // Four ways of naming a moment that has passed, and all four are a
3185        // delete answering 1 rather than an error. Zero is a moment, minus one
3186        // is a moment, and the hash field commands refuse the negative one.
3187        assert_eq!(f.run(&[b"EXPIRE", b"a", b"0"]), ":1\r\n");
3188        assert_eq!(f.run(&[b"EXPIRE", b"b", b"-1"]), ":1\r\n");
3189        assert_eq!(f.run(&[b"EXPIREAT", b"c", b"1"]), ":1\r\n");
3190        assert_eq!(f.run(&[b"PEXPIREAT", b"d", b"1"]), ":1\r\n");
3191        assert_eq!(f.run(&[b"DBSIZE"]), ":0\r\n");
3192        assert_eq!(
3193            f.run(&[b"EXPIRE", b"a", b"100"]),
3194            ":0\r\n",
3195            "and the key really went, so there is nothing to put a deadline on"
3196        );
3197    }
3198
3199    #[test]
3200    fn the_four_conditions_decide_whether_the_deadline_moves() {
3201        let mut f = Fixture::new();
3202        f.run(&[b"SET", b"k", b"v"]);
3203
3204        assert_eq!(f.run(&[b"EXPIRE", b"k", b"100", b"XX"]), ":0\r\n");
3205        assert_eq!(f.run(&[b"TTL", b"k"]), ":-1\r\n", "and XX left it alone");
3206        assert_eq!(f.run(&[b"EXPIRE", b"k", b"100", b"GT"]), ":0\r\n");
3207        assert_eq!(
3208            f.run(&[b"EXPIRE", b"k", b"100", b"LT"]),
3209            ":1\r\n",
3210            "no deadline reads as infinitely far away, so LT passes where GT fails"
3211        );
3212
3213        assert_eq!(f.run(&[b"EXPIRE", b"k", b"50", b"NX"]), ":0\r\n");
3214        assert_eq!(f.run(&[b"EXPIRE", b"k", b"50", b"GT"]), ":0\r\n");
3215        assert_eq!(f.run(&[b"TTL", b"k"]), ":100\r\n");
3216        assert_eq!(f.run(&[b"EXPIRE", b"k", b"50", b"LT"]), ":1\r\n");
3217        assert_eq!(f.run(&[b"EXPIRE", b"k", b"200", b"GT"]), ":1\r\n");
3218        assert_eq!(f.run(&[b"TTL", b"k"]), ":200\r\n");
3219
3220        // The condition is answered before the past check, so this is a 0 and
3221        // the key survives. The other order would delete it.
3222        assert_eq!(f.run(&[b"EXPIRE", b"k", b"0", b"NX"]), ":0\r\n");
3223        assert_eq!(f.run(&[b"EXISTS", b"k"]), ":1\r\n");
3224        assert_eq!(f.run(&[b"EXPIRE", b"k", b"0", b"XX"]), ":1\r\n");
3225        assert_eq!(f.run(&[b"EXISTS", b"k"]), ":0\r\n", "and XX let it through");
3226    }
3227
3228    #[test]
3229    fn the_conditions_are_a_set_and_not_a_keyword() {
3230        let mut f = Fixture::new();
3231        f.run(&[b"SET", b"k", b"v"]);
3232
3233        assert_eq!(f.run(&[b"EXPIRE", b"k", b"100", b"nx"]), ":1\r\n");
3234        assert_eq!(
3235            f.run(&[b"EXPIRE", b"k", b"100", b"nx", b"nx"]),
3236            ":0\r\n",
3237            "the same keyword twice means it once, and NX now has a deadline to fail on"
3238        );
3239
3240        // XX with LT is the one pair that is not either of them on its own: LT
3241        // alone would accept a key with no deadline and this does not.
3242        assert_eq!(f.run(&[b"EXPIRE", b"k", b"200", b"xx", b"gt"]), ":1\r\n");
3243        assert_eq!(f.run(&[b"TTL", b"k"]), ":200\r\n");
3244        assert_eq!(f.run(&[b"EXPIRE", b"k", b"100", b"gt", b"xx"]), ":0\r\n");
3245        assert_eq!(f.run(&[b"EXPIRE", b"k", b"100", b"XX", b"LT"]), ":1\r\n");
3246        assert_eq!(f.run(&[b"TTL", b"k"]), ":100\r\n");
3247        f.run(&[b"PERSIST", b"k"]);
3248        assert_eq!(
3249            f.run(&[b"EXPIRE", b"k", b"100", b"XX", b"LT"]),
3250            ":0\r\n",
3251            "where LT on its own would have taken it"
3252        );
3253        assert_eq!(f.run(&[b"EXPIRE", b"k", b"100", b"LT"]), ":1\r\n");
3254    }
3255
3256    #[test]
3257    fn a_key_is_gone_once_its_moment_passes() {
3258        let mut f = Fixture::new();
3259        f.run(&[b"SET", b"k", b"v"]);
3260        f.run(&[b"EXPIRE", b"k", b"100"]);
3261
3262        let at = int(&f.run(&[b"PEXPIRETIME", b"k"]));
3263        f.server.set_clock_ms(at as u64 + 1);
3264        assert_eq!(f.run(&[b"GET", b"k"]), "$-1\r\n");
3265        assert_eq!(f.run(&[b"TTL", b"k"]), ":-2\r\n");
3266        assert_eq!(f.run(&[b"EXISTS", b"k"]), ":0\r\n");
3267        assert_eq!(f.run(&[b"DBSIZE"]), ":0\r\n");
3268    }
3269
3270    #[test]
3271    fn the_expiry_commands_refuse_what_a_real_server_refuses() {
3272        let mut f = Fixture::new();
3273        f.run(&[b"SET", b"k", b"v"]);
3274        for (bad, want) in [
3275            (
3276                &[b"EXPIRE".as_slice(), b"k", b"soon"][..],
3277                "-ERR value is not an integer or out of range\r\n",
3278            ),
3279            (
3280                &[b"EXPIRE", b"k", b"100", b"MAYBE"],
3281                "-ERR Unsupported option MAYBE\r\n",
3282            ),
3283            (
3284                &[b"EXPIRE", b"k", b"100", b"NX", b"XX"],
3285                "-ERR NX and XX, GT or LT options at the same time are not compatible\r\n",
3286            ),
3287            (
3288                &[b"EXPIRE", b"k", b"100", b"NX", b"GT"],
3289                "-ERR NX and XX, GT or LT options at the same time are not compatible\r\n",
3290            ),
3291            (
3292                &[b"EXPIRE", b"k", b"100", b"GT", b"LT", b"GT"],
3293                "-ERR GT and LT options at the same time are not compatible\r\n",
3294            ),
3295            // Seconds that overflow when multiplied into milliseconds. Every
3296            // message names the command it came from.
3297            (
3298                &[b"EXPIRE", b"k", b"9223372036854775807"],
3299                "-ERR invalid expire time in 'expire' command\r\n",
3300            ),
3301            (
3302                &[b"EXPIREAT", b"k", b"9223372036854775807"],
3303                "-ERR invalid expire time in 'expireat' command\r\n",
3304            ),
3305            (
3306                &[b"PEXPIRE", b"k", b"9223372036854775807"],
3307                "-ERR invalid expire time in 'pexpire' command\r\n",
3308            ),
3309        ] {
3310            assert_eq!(f.run(bad), want, "for {bad:?}");
3311        }
3312        assert_eq!(
3313            f.run(&[b"TTL", b"k"]),
3314            ":-1\r\n",
3315            "and none of those put a deadline on anything"
3316        );
3317
3318        // The one of the four that has no arithmetic to overflow. Redis takes
3319        // it and holds the number as given, and a record here holds forty six
3320        // bits, so it lands in the year 4199 instead. D-17.
3321        assert_eq!(
3322            f.run(&[b"PEXPIREAT", b"k", b"9223372036854775807"]),
3323            ":1\r\n"
3324        );
3325        assert_eq!(f.run(&[b"PEXPIRETIME", b"k"]), ":70368744177663\r\n");
3326    }
3327
3328    #[test]
3329    fn flushing_empties_this_database_or_every_one_of_them() {
3330        let mut f = Fixture::new();
3331        f.run(&[b"SELECT", b"0"]);
3332        f.run(&[b"MSET", b"a", b"1", b"b", b"2"]);
3333        f.run(&[b"SELECT", b"1"]);
3334        f.run(&[b"SET", b"c", b"3"]);
3335        assert_eq!(f.run(&[b"DBSIZE"]), ":1\r\n");
3336        // ASYNC and SYNC are both taken and neither changes anything, since the
3337        // keyspace is empty before the OK goes out either way.
3338        assert_eq!(f.run(&[b"FLUSHDB", b"async"]), "+OK\r\n");
3339        assert_eq!(f.run(&[b"DBSIZE"]), ":0\r\n");
3340        // Only database one was emptied.
3341        f.run(&[b"SELECT", b"0"]);
3342        assert_eq!(f.run(&[b"DBSIZE"]), ":2\r\n");
3343        assert_eq!(f.run(&[b"FLUSHALL", b"SYNC"]), "+OK\r\n");
3344        assert_eq!(f.run(&[b"DBSIZE"]), ":0\r\n");
3345        f.run(&[b"SELECT", b"1"]);
3346        assert_eq!(f.run(&[b"DBSIZE"]), ":0\r\n");
3347        // Anything else after the name is a syntax error, and so is a third
3348        // argument even when the second one is a word we take.
3349        assert_eq!(f.run(&[b"FLUSHALL", b"nope"]), "-ERR syntax error\r\n");
3350        assert_eq!(
3351            f.run(&[b"FLUSHDB", b"sync", b"sync"]),
3352            "-ERR syntax error\r\n"
3353        );
3354    }
3355
3356    #[test]
3357    fn the_script_cache_and_the_library_set_answer_for_being_empty() {
3358        let mut f = Fixture::new();
3359        assert_eq!(f.run(&[b"SCRIPT", b"FLUSH"]), "+OK\r\n");
3360        assert_eq!(f.run(&[b"SCRIPT", b"FLUSH", b"async"]), "+OK\r\n");
3361        assert_eq!(f.run(&[b"FUNCTION", b"FLUSH", b"SYNC"]), "+OK\r\n");
3362        // Nothing is cached, so nothing is there, one answer per hash asked
3363        // about.
3364        assert_eq!(
3365            f.run(&[b"SCRIPT", b"EXISTS", b"aaaa", b"bbbb"]),
3366            "*2\r\n:0\r\n:0\r\n"
3367        );
3368        assert_eq!(f.run(&[b"FUNCTION", b"LIST"]), "*0\r\n");
3369        assert_eq!(
3370            f.run(&[b"FUNCTION", b"LIST", b"LIBRARYNAME", b"x", b"WITHCODE"]),
3371            "*0\r\n"
3372        );
3373        assert_eq!(
3374            f.run(&[b"FUNCTION", b"DELETE", b"nosuch"]),
3375            "-ERR Library not found\r\n"
3376        );
3377
3378        // Redis's two messages here are its own, one per container, and one of
3379        // them reads like a typo.
3380        assert_eq!(
3381            f.run(&[b"SCRIPT", b"FLUSH", b"nope"]),
3382            "-ERR SCRIPT FLUSH only support SYNC|ASYNC option\r\n"
3383        );
3384        assert_eq!(
3385            f.run(&[b"FUNCTION", b"FLUSH", b"nope"]),
3386            "-ERR FUNCTION FLUSH only supports SYNC|ASYNC option\r\n"
3387        );
3388        // A second argument after the mode is the generic one instead, because
3389        // the count is checked before the word is looked at. The subcommand in
3390        // the sentence is the client's own spelling and not the canonical one,
3391        // which is the same thing `unknown subcommand` does.
3392        assert_eq!(
3393            f.run(&[b"FUNCTION", b"FLUSH", b"sync", b"sync"]),
3394            "-ERR unknown subcommand or wrong number of arguments for 'FLUSH'. Try FUNCTION HELP.\r\n"
3395        );
3396        assert_eq!(
3397            f.run(&[b"FUNCTION", b"LIST", b"bogus"]),
3398            "-ERR Unknown argument bogus\r\n"
3399        );
3400        assert_eq!(
3401            f.run(&[b"SCRIPT", b"EXISTS"]),
3402            "-ERR wrong number of arguments for 'script|exists' command\r\n"
3403        );
3404
3405        assert_eq!(
3406            f.run(&[b"FUNCTION", b"NOPE"]),
3407            "-ERR unknown subcommand 'NOPE'. Try FUNCTION HELP.\r\n"
3408        );
3409    }
3410
3411    #[test]
3412    fn the_script_cache_holds_what_was_loaded_into_it() {
3413        let mut f = Fixture::new();
3414        // The hash is the sha1 of the body and nothing else, so it is the same
3415        // number a real server answers and a client can compute it itself.
3416        let sha = b"e0e1f9fabfc9d4800c877a703b823ac0578ff8db";
3417        assert_eq!(
3418            f.run(&[b"SCRIPT", b"LOAD", b"return 1"]),
3419            "$40\r\ne0e1f9fabfc9d4800c877a703b823ac0578ff8db\r\n"
3420        );
3421        assert_eq!(f.run(&[b"SCRIPT", b"EXISTS", sha]), "*1\r\n:1\r\n");
3422        assert_eq!(f.run(&[b"EVALSHA", sha, b"0"]), ":1\r\n");
3423        // Loading is idempotent and a body that will not parse is refused
3424        // where it was written rather than where it is called.
3425        assert_eq!(
3426            f.run(&[b"SCRIPT", b"LOAD", b"return 1"]),
3427            "$40\r\ne0e1f9fabfc9d4800c877a703b823ac0578ff8db\r\n"
3428        );
3429        assert!(
3430            f.run(&[b"SCRIPT", b"LOAD", b"this is not lua"])
3431                .starts_with("-ERR Error compiling script"),
3432        );
3433
3434        assert_eq!(f.run(&[b"SCRIPT", b"FLUSH"]), "+OK\r\n");
3435        assert_eq!(f.run(&[b"SCRIPT", b"EXISTS", sha]), "*1\r\n:0\r\n");
3436        assert_eq!(
3437            f.run(&[b"EVALSHA", sha, b"0"]),
3438            "-NOSCRIPT No matching script. Please use EVAL.\r\n"
3439        );
3440
3441        // Running the body puts it in the cache too, which is what makes the
3442        // load then call then fall back to load pattern a client uses work.
3443        assert_eq!(f.run(&[b"EVAL", b"return 1", b"0"]), ":1\r\n");
3444        assert_eq!(f.run(&[b"SCRIPT", b"EXISTS", sha]), "*1\r\n:1\r\n");
3445
3446        // Nothing here can run long enough to be killed, which is D-101, so
3447        // the answer is the one a real server gives when nothing is stuck.
3448        assert_eq!(
3449            f.run(&[b"SCRIPT", b"KILL"]),
3450            "-NOTBUSY No scripts in execution right now.\r\n"
3451        );
3452        assert_eq!(f.run(&[b"SCRIPT", b"DEBUG", b"NO"]), "+OK\r\n");
3453        assert_eq!(f.run(&[b"SCRIPT", b"DEBUG", b"yes"]), "+OK\r\n");
3454        assert_eq!(
3455            f.run(&[b"SCRIPT", b"DEBUG", b"maybe"]),
3456            "-ERR Use SCRIPT DEBUG YES/SYNC/NO\r\n"
3457        );
3458    }
3459
3460    #[test]
3461    fn eval_counts_its_keys_before_it_compiles_anything() {
3462        let mut f = Fixture::new();
3463        assert_eq!(
3464            f.run(&[b"EVAL", b"return 1"]),
3465            "-ERR wrong number of arguments for 'eval' command\r\n"
3466        );
3467        assert_eq!(
3468            f.run(&[b"EVAL", b"return 1", b"abc"]),
3469            "-ERR value is not an integer or out of range\r\n"
3470        );
3471        assert_eq!(
3472            f.run(&[b"EVAL", b"return 1", b"-1"]),
3473            "-ERR Number of keys can't be negative\r\n"
3474        );
3475        assert_eq!(
3476            f.run(&[b"EVAL", b"return 1", b"1"]),
3477            "-ERR Number of keys can't be greater than number of args\r\n"
3478        );
3479        // The count splits the tail, and everything past the keys is ARGV.
3480        assert_eq!(
3481            f.run(&[
3482                b"EVAL",
3483                b"return {KEYS[1],KEYS[2],ARGV[1]}",
3484                b"2",
3485                b"a",
3486                b"b",
3487                b"c"
3488            ]),
3489            "*3\r\n$1\r\na\r\n$1\r\nb\r\n$1\r\nc\r\n"
3490        );
3491        assert_eq!(
3492            f.run(&[b"EVAL", b"return #KEYS", b"0", b"a", b"b"]),
3493            ":0\r\n"
3494        );
3495        assert_eq!(
3496            f.run(&[b"EVAL", b"return #ARGV", b"0", b"a", b"b"]),
3497            ":2\r\n"
3498        );
3499    }
3500
3501    #[test]
3502    fn a_lua_value_comes_back_as_the_reply_it_maps_to() {
3503        let mut f = Fixture::new();
3504        let eval = |f: &mut Fixture, body: &[u8]| f.run(&[b"EVAL", body, b"0"]);
3505
3506        // A number is truncated toward zero rather than rounded, and the two
3507        // ends of the range saturate the way the cast does.
3508        assert_eq!(eval(&mut f, b"return 3.99"), ":3\r\n");
3509        assert_eq!(eval(&mut f, b"return -3.99"), ":-3\r\n");
3510        assert_eq!(eval(&mut f, b"return 0.5"), ":0\r\n");
3511        assert_eq!(eval(&mut f, b"return 2^63"), ":9223372036854775807\r\n");
3512        assert_eq!(eval(&mut f, b"return -2^63"), ":-9223372036854775808\r\n");
3513        assert_eq!(eval(&mut f, b"return 1/0"), ":9223372036854775807\r\n");
3514        assert_eq!(eval(&mut f, b"return 0/0"), ":0\r\n");
3515
3516        assert_eq!(eval(&mut f, b"return 'hello'"), "$5\r\nhello\r\n");
3517        assert_eq!(eval(&mut f, b"return true"), ":1\r\n");
3518        // Everything that is not there is the same nothing.
3519        assert_eq!(eval(&mut f, b"return false"), "$-1\r\n");
3520        assert_eq!(eval(&mut f, b"return nil"), "$-1\r\n");
3521        assert_eq!(eval(&mut f, b"return"), "$-1\r\n");
3522        assert_eq!(eval(&mut f, b""), "$-1\r\n");
3523
3524        // A table is an array that stops at the first hole, which is what makes
3525        // a script build a reply by appending rather than by indexing.
3526        assert_eq!(eval(&mut f, b"return {}"), "*0\r\n");
3527        assert_eq!(eval(&mut f, b"return {1,2,nil,4}"), "*2\r\n:1\r\n:2\r\n");
3528        assert_eq!(
3529            eval(&mut f, b"return {1,'a',{2}}"),
3530            "*3\r\n:1\r\n$1\r\na\r\n*1\r\n:2\r\n"
3531        );
3532
3533        // The named fields, in the order a real server looks for them.
3534        assert_eq!(eval(&mut f, b"return {ok='fine'}"), "+fine\r\n");
3535        assert_eq!(eval(&mut f, b"return {err='mine'}"), "-mine\r\n");
3536        assert_eq!(eval(&mut f, b"return {err='a', ok='b'}"), "-a\r\n");
3537        assert_eq!(eval(&mut f, b"return {ok='b', double=1.5}"), "+b\r\n");
3538        // A line break inside one of them becomes a space, because the reply is
3539        // a single line and a client that saw the break would lose the frame.
3540        assert_eq!(eval(&mut f, b"return {ok='a\\r\\nb'}"), "+a  b\r\n");
3541        // A field of the wrong type is not that kind of reply at all, and falls
3542        // through to the array walk, which finds nothing.
3543        assert_eq!(eval(&mut f, b"return {ok=1}"), "*0\r\n");
3544        assert_eq!(eval(&mut f, b"return {err={}}"), "*0\r\n");
3545    }
3546
3547    #[test]
3548    fn the_protocol_the_client_asked_for_is_the_one_a_table_answers_in() {
3549        let mut f = Fixture::new();
3550        // Under RESP2 the four typed tables have to come back as something a
3551        // client that only knows RESP2 can read.
3552        assert_eq!(
3553            f.run(&[b"EVAL", b"return {double=3.5}", b"0"]),
3554            "$3\r\n3.5\r\n"
3555        );
3556        assert_eq!(
3557            f.run(&[b"EVAL", b"return {big_number='123'}", b"0"]),
3558            "$3\r\n123\r\n"
3559        );
3560        assert_eq!(
3561            f.run(&[b"EVAL", b"return {map={a='b'}}", b"0"]),
3562            "*2\r\n$1\r\na\r\n$1\r\nb\r\n"
3563        );
3564        assert_eq!(
3565            f.run(&[b"EVAL", b"return {set={a=true}}", b"0"]),
3566            "*1\r\n$1\r\na\r\n"
3567        );
3568        assert_eq!(f.run(&[b"EVAL", b"return false", b"0"]), "$-1\r\n");
3569
3570        f.out = Out::new(Proto::Resp3);
3571        assert_eq!(f.run(&[b"EVAL", b"return {double=3.5}", b"0"]), ",3.5\r\n");
3572        assert_eq!(
3573            f.run(&[b"EVAL", b"return {big_number='123'}", b"0"]),
3574            "(123\r\n"
3575        );
3576        assert_eq!(
3577            f.run(&[b"EVAL", b"return {map={a='b'}}", b"0"]),
3578            "%1\r\n$1\r\na\r\n$1\r\nb\r\n"
3579        );
3580        assert_eq!(
3581            f.run(&[b"EVAL", b"return {set={a=true}}", b"0"]),
3582            "~1\r\n$1\r\na\r\n"
3583        );
3584        assert_eq!(f.run(&[b"EVAL", b"return false", b"0"]), "_\r\n");
3585    }
3586
3587    #[test]
3588    fn a_reply_comes_back_into_lua_as_the_value_it_maps_to() {
3589        let mut f = Fixture::new();
3590        f.run(&[b"SET", b"s", b"hello"]);
3591        f.run(&[b"RPUSH", b"l", b"a", b"b"]);
3592        let eval = |f: &mut Fixture, body: &[u8]| f.run(&[b"EVAL", body, b"0"]);
3593
3594        assert_eq!(
3595            eval(&mut f, b"return type(redis.call('get','s'))"),
3596            "$6\r\nstring\r\n"
3597        );
3598        assert_eq!(
3599            eval(&mut f, b"return type(redis.call('llen','l'))"),
3600            "$6\r\nnumber\r\n"
3601        );
3602        assert_eq!(
3603            eval(&mut f, b"return type(redis.call('lrange','l',0,-1))"),
3604            "$5\r\ntable\r\n"
3605        );
3606        // A status is a table with one field, which is what lets a script pass
3607        // one straight back out again.
3608        assert_eq!(
3609            eval(&mut f, b"return redis.call('set','s','v')['ok']"),
3610            "$2\r\nOK\r\n"
3611        );
3612        // A missing key is false under RESP2 and nil once the script asks for
3613        // RESP3, which is the one conversion the script gets to choose.
3614        assert_eq!(
3615            eval(&mut f, b"return tostring(redis.call('get','nosuch'))"),
3616            "$5\r\nfalse\r\n"
3617        );
3618        assert_eq!(
3619            eval(
3620                &mut f,
3621                b"redis.setresp(3) return tostring(redis.call('get','nosuch'))"
3622            ),
3623            "$3\r\nnil\r\n"
3624        );
3625        // The choice does not outlive the script that made it.
3626        assert_eq!(
3627            eval(&mut f, b"return tostring(redis.call('get','nosuch'))"),
3628            "$5\r\nfalse\r\n"
3629        );
3630    }
3631
3632    #[test]
3633    fn an_error_from_a_script_names_the_line_it_came_from() {
3634        let mut f = Fixture::new();
3635        // The position is the script's own, not the prelude's, and the suffix
3636        // names the script so a client can find it in the cache.
3637        assert_eq!(
3638            f.run(&[b"EVAL", b"error('boom')", b"0"]),
3639            "-ERR user_script:1: boom script: \
3640             82903a0434f1503e152f89c03c9acd881a0e8150, on @user_script:1.\r\n"
3641        );
3642        // Level zero says the message already knows where it came from.
3643        assert_eq!(
3644            f.run(&[b"EVAL", b"error('boom', 0)", b"0"]),
3645            "-ERR boom script: 90724e16396e5864c1184910ba6d7440461cee4f, on @user_script:1.\r\n"
3646        );
3647        // A table with an err field keeps its own text and gets the suffix.
3648        assert!(
3649            f.run(&[b"EVAL", b"error({err='structured'})", b"0"])
3650                .starts_with("-structured script: "),
3651        );
3652        // A script that will not parse is refused before it runs, so there is
3653        // no script and nothing to name.
3654        assert_eq!(
3655            f.run(&[b"EVAL", b"return this is not lua", b"0"]),
3656            "-ERR Error compiling script (new function): user_script:1: '<eof>' expected near 'is'\r\n"
3657        );
3658
3659        // A table that came out of pcall is a string by the time the script
3660        // sees it, which is a real server's own wrapping and not Lua's.
3661        assert_eq!(
3662            f.run(&[
3663                b"EVAL",
3664                b"local a, b = pcall(function() error({err='z'}) end) return type(b) .. ':' .. tostring(b)",
3665                b"0"
3666            ]),
3667            "$8\r\nstring:z\r\n"
3668        );
3669        assert_eq!(
3670            f.run(&[
3671                b"EVAL",
3672                b"local a, b = pcall(function() error({a=1}) end) return type(b)",
3673                b"0"
3674            ]),
3675            "$5\r\ntable\r\n"
3676        );
3677    }
3678
3679    #[test]
3680    fn redis_call_refuses_what_it_cannot_run_and_pcall_hands_it_back() {
3681        let mut f = Fixture::new();
3682        let sentence = |f: &mut Fixture, body: &[u8]| {
3683            let reply = f.run(&[b"EVAL", body, b"0"]);
3684            reply.split(" script: ").next().unwrap().to_owned()
3685        };
3686
3687        assert_eq!(
3688            sentence(&mut f, b"return redis.call()"),
3689            "-ERR Please specify at least one argument for this redis lib call"
3690        );
3691        assert_eq!(
3692            sentence(&mut f, b"return redis.call('get', {})"),
3693            "-ERR Lua redis lib command arguments must be strings or integers"
3694        );
3695        assert_eq!(
3696            sentence(&mut f, b"return redis.call('nosuchcmd')"),
3697            "-ERR Unknown Redis command called from script"
3698        );
3699        assert_eq!(
3700            sentence(&mut f, b"return redis.call('get')"),
3701            "-ERR Wrong number of args calling Redis command from script"
3702        );
3703        // The commands that make no sense inside a script are refused by name
3704        // rather than by not being implemented, so the sentence is the same one
3705        // a real server writes for each of them.
3706        for name in [
3707            &b"return redis.call('multi')"[..],
3708            b"return redis.call('exec')",
3709            b"return redis.call('watch','k')",
3710            b"return redis.call('subscribe','c')",
3711            b"return redis.call('debug','jmap')",
3712            b"return redis.call('eval','return 1',0)",
3713            b"return redis.call('config','get','maxmemory')",
3714        ] {
3715            assert_eq!(
3716                sentence(&mut f, name),
3717                "-ERR This Redis command is not allowed from script",
3718                "for {}",
3719                String::from_utf8_lossy(name)
3720            );
3721        }
3722        // HELP is the one subcommand of a refused container that is allowed,
3723        // because it reads nothing and changes nothing.
3724        assert!(
3725            f.run(&[b"EVAL", b"return redis.call('config','help')", b"0"])
3726                .starts_with('*'),
3727        );
3728
3729        // pcall answers the same sentence as a value instead of raising it, and
3730        // the value has an err field a script can read.
3731        assert_eq!(
3732            f.run(&[
3733                b"EVAL",
3734                b"local x = redis.pcall('nosuchcmd') return x.err",
3735                b"0"
3736            ]),
3737            "$44\r\nERR Unknown Redis command called from script\r\n"
3738        );
3739        // Returning it unread raises it, because the table has an err field.
3740        assert_eq!(
3741            f.run(&[b"EVAL", b"return redis.pcall('nosuchcmd')", b"0"]),
3742            "-ERR Unknown Redis command called from script\r\n"
3743        );
3744    }
3745
3746    #[test]
3747    fn a_read_only_script_is_stopped_at_the_write_and_not_at_the_door() {
3748        let mut f = Fixture::new();
3749        f.run(&[b"SET", b"k", b"v"]);
3750        assert_eq!(
3751            f.run(&[b"EVAL_RO", b"return redis.call('get', KEYS[1])", b"1", b"k"]),
3752            "$1\r\nv\r\n"
3753        );
3754        assert!(
3755            f.run(&[
3756                b"EVAL_RO",
3757                b"return redis.call('set', KEYS[1], 'x')",
3758                b"1",
3759                b"k"
3760            ])
3761            .starts_with("-ERR Write commands are not allowed from read-only scripts."),
3762        );
3763        // The write did not happen, and the same body under EVAL does.
3764        assert_eq!(f.run(&[b"GET", b"k"]), "$1\r\nv\r\n");
3765        assert_eq!(
3766            f.run(&[
3767                b"EVAL",
3768                b"return redis.call('set', KEYS[1], 'x')",
3769                b"1",
3770                b"k"
3771            ]),
3772            "+OK\r\n"
3773        );
3774        assert_eq!(f.run(&[b"GET", b"k"]), "$1\r\nx\r\n");
3775
3776        // EVALSHA_RO runs a cached body under the same rule.
3777        let sha = b"e0e1f9fabfc9d4800c877a703b823ac0578ff8db";
3778        f.run(&[b"SCRIPT", b"LOAD", b"return 1"]);
3779        assert_eq!(f.run(&[b"EVALSHA_RO", sha, b"0"]), ":1\r\n");
3780    }
3781
3782    #[test]
3783    fn a_script_cannot_leave_anything_behind_for_the_next_one() {
3784        let mut f = Fixture::new();
3785        // A plain global write and a write through a name on the redis table
3786        // both raise, with the position the script wrote them at.
3787        for body in [&b"x = 1"[..], b"pcall = 1", b"redis = 1", b"redis.call = 1"] {
3788            let reply = f.run(&[b"EVAL", body, b"0"]);
3789            assert!(
3790                reply
3791                    .starts_with("-ERR user_script:1: Attempt to modify a readonly table script: "),
3792                "{body:?} gave {reply}",
3793            );
3794        }
3795        // Walking round the guard with rawset or setmetatable raises too, and
3796        // without the position, which is where a real server raises it from.
3797        for body in [
3798            &b"rawset(redis, 'call', 1)"[..],
3799            b"rawset(_G, 'zz', 1)",
3800            b"setmetatable(_G, {})",
3801            b"setmetatable(redis, {})",
3802        ] {
3803            let reply = f.run(&[b"EVAL", body, b"0"]);
3804            assert!(
3805                reply.starts_with("-ERR Attempt to modify a readonly table script: "),
3806                "{body:?} gave {reply}",
3807            );
3808        }
3809        // Reading a name that is not there is a mistake rather than a nil, so a
3810        // misspelled global stops the script instead of doing nothing quietly.
3811        assert!(
3812            f.run(&[b"EVAL", b"return nosuchglobal", b"0"])
3813                .contains("Script attempted to access nonexistent global variable 'nosuchglobal'"),
3814        );
3815        // Reading a name that is not on the redis table is a nil, which is how
3816        // a script tests for a helper that an older server does not have.
3817        assert_eq!(
3818            f.run(&[b"EVAL", b"return tostring(redis.nosuchfield)", b"0"]),
3819            "$3\r\nnil\r\n"
3820        );
3821
3822        // The one write that lands, D-103, is taken back out before the next
3823        // script starts, so nothing a script does reaches the one after it.
3824        assert_eq!(f.run(&[b"EVAL", b"_G.pcall = 1 return 1", b"0"]), ":1\r\n");
3825        assert_eq!(
3826            f.run(&[b"EVAL", b"return type(pcall)", b"0"]),
3827            "$8\r\nfunction\r\n"
3828        );
3829        assert_eq!(
3830            f.run(&[b"EVAL", b"return type(redis.call)", b"0"]),
3831            "$8\r\nfunction\r\n"
3832        );
3833    }
3834
3835    #[test]
3836    fn a_script_can_walk_the_redis_table_it_is_not_allowed_to_write_to() {
3837        let mut f = Fixture::new();
3838        // The guard in front of the table is empty, so the three base library
3839        // readers that skip a metatable are pointed at the real table behind
3840        // it. A script counts what a real server counts.
3841        assert_eq!(
3842            f.run(&[
3843                b"EVAL",
3844                b"local n = 0 for k in pairs(redis) do n = n + 1 end return n",
3845                b"0",
3846            ]),
3847            ":23\r\n"
3848        );
3849        assert_eq!(
3850            f.run(&[
3851                b"EVAL",
3852                b"local t = {} for k in pairs(redis) do t[#t+1] = k end \
3853                  table.sort(t) return table.concat(t, ' ')",
3854                b"0",
3855            ]),
3856            "$243\r\nLOG_DEBUG LOG_NOTICE LOG_VERBOSE LOG_WARNING REDIS_VERSION \
3857             REDIS_VERSION_NUM REPL_ALL REPL_AOF REPL_NONE REPL_REPLICA REPL_SLAVE \
3858             acl_check_cmd breakpoint call debug error_reply log pcall replicate_commands \
3859             set_repl setresp sha1hex status_reply\r\n"
3860        );
3861        // The loop hands over the values as well as the names, so the twelve
3862        // helpers are callable from inside a traversal and not just findable.
3863        assert_eq!(
3864            f.run(&[
3865                b"EVAL",
3866                b"local n = 0 for k, v in pairs(redis) do \
3867                  if type(v) == 'function' then n = n + 1 end end return n",
3868                b"0",
3869            ]),
3870            ":12\r\n"
3871        );
3872        // The other two readers agree with it.
3873        assert_eq!(
3874            f.run(&[b"EVAL", b"return type(next(redis))", b"0"]),
3875            "$6\r\nstring\r\n"
3876        );
3877        assert_eq!(
3878            f.run(&[b"EVAL", b"return type(rawget(redis, 'call'))", b"0"]),
3879            "$8\r\nfunction\r\n"
3880        );
3881        assert_eq!(
3882            f.run(&[
3883                b"EVAL",
3884                b"return tostring(rawget(redis, 'nosuchfield'))",
3885                b"0",
3886            ]),
3887            "$3\r\nnil\r\n"
3888        );
3889        // Reading round the guard is the only thing that was given back. A
3890        // write still lands on the guard and still raises.
3891        for body in [&b"redis.call = 1"[..], b"rawset(redis, 'call', 1)"] {
3892            assert!(
3893                f.run(&[b"EVAL", body, b"0"])
3894                    .contains("Attempt to modify a readonly table script: "),
3895                "{body:?}",
3896            );
3897        }
3898        // A table nobody guards walks the way it always did, whether a script
3899        // made it or the standard library did.
3900        assert_eq!(
3901            f.run(&[
3902                b"EVAL",
3903                b"local t = {a=1,b=2} local n = 0 for k in pairs(t) do n = n + 1 end return n",
3904                b"0",
3905            ]),
3906            ":2\r\n"
3907        );
3908        assert_eq!(
3909            f.run(&[b"EVAL", b"return tostring(next({}))", b"0"]),
3910            "$3\r\nnil\r\n"
3911        );
3912        assert_eq!(
3913            f.run(&[
3914                b"EVAL",
3915                b"local f for k, v in pairs(string) do if k == 'sub' then f = v end end \
3916                  return type(f)",
3917                b"0",
3918            ]),
3919            "$8\r\nfunction\r\n"
3920        );
3921    }
3922
3923    #[test]
3924    fn a_script_gets_the_bit_library_a_real_server_carries() {
3925        let mut f = Fixture::new();
3926        // Every answer is a signed word, which is why the ones past two to the
3927        // thirty one come back negative.
3928        for (body, want) in [
3929            ("bit.tobit(1)", ":1\r\n"),
3930            ("bit.tobit(2^32 + 1)", ":1\r\n"),
3931            ("bit.tobit(2^31)", ":-2147483648\r\n"),
3932            ("bit.tobit(0xffffffff)", ":-1\r\n"),
3933            // The rounding is to the nearest and not toward zero.
3934            ("bit.tobit(1.5)", ":2\r\n"),
3935            ("bit.tobit(2.5)", ":2\r\n"),
3936            ("bit.bnot(0)", ":-1\r\n"),
3937            ("bit.band(0xff, 0x0f)", ":15\r\n"),
3938            ("bit.band(1, 2, 3)", ":0\r\n"),
3939            ("bit.bor(1, 2, 4)", ":7\r\n"),
3940            ("bit.bxor(0xff, 0x0f)", ":240\r\n"),
3941            // Only the low five bits of a count are read.
3942            ("bit.lshift(1, 31)", ":-2147483648\r\n"),
3943            ("bit.lshift(1, 32)", ":1\r\n"),
3944            ("bit.lshift(1, 33)", ":2\r\n"),
3945            ("bit.rshift(-1, 1)", ":2147483647\r\n"),
3946            ("bit.arshift(-1, 1)", ":-1\r\n"),
3947            ("bit.rol(0x12345678, 8)", ":878082066\r\n"),
3948            ("bit.ror(0x12345678, 8)", ":2014458966\r\n"),
3949            ("bit.bswap(0x12345678)", ":2018915346\r\n"),
3950            // A string that reads as a number is a number, which is Lua's rule
3951            // and not a courtesy of this library.
3952            ("bit.tobit('0x10')", ":16\r\n"),
3953        ] {
3954            let script = format!("return {body}");
3955            assert_eq!(f.run(&[b"EVAL", script.as_bytes(), b"0"]), want, "{body}");
3956        }
3957        // The digits are the low ones, a negative count asks for upper case,
3958        // and a count outside eight is brought back to it.
3959        for (body, want) in [
3960            ("bit.tohex(1)", "00000001"),
3961            ("bit.tohex(-1)", "ffffffff"),
3962            ("bit.tohex(255, 2)", "ff"),
3963            ("bit.tohex(255, -8)", "000000FF"),
3964            ("bit.tohex(0x87654321, 4)", "4321"),
3965            ("bit.tohex(1, 0)", ""),
3966            ("bit.tohex(1, 9)", "00000001"),
3967        ] {
3968            let script = format!("return {body}");
3969            assert_eq!(
3970                f.run(&[b"EVAL", script.as_bytes(), b"0"]),
3971                format!("${}\r\n{want}\r\n", want.len()),
3972                "{body}",
3973            );
3974        }
3975        // A bad argument names the position, the function and what was passed,
3976        // and the line in front of it is the script's own.
3977        for (body, want) in [
3978            (
3979                "return bit.band()",
3980                "bad argument #1 to 'band' (number expected, got no value)",
3981            ),
3982            (
3983                "return bit.band('x')",
3984                "bad argument #1 to 'band' (number expected, got string)",
3985            ),
3986            (
3987                "return bit.tobit(true)",
3988                "bad argument #1 to 'tobit' (number expected, got boolean)",
3989            ),
3990            (
3991                "return bit.lshift(1)",
3992                "bad argument #2 to 'lshift' (number expected, got no value)",
3993            ),
3994        ] {
3995            let reply = f.run(&[b"EVAL", body.as_bytes(), b"0"]);
3996            assert!(
3997                reply.starts_with(&format!("-ERR user_script:1: {want} script: ")),
3998                "{body} gave {reply}",
3999            );
4000        }
4001        // The name in the message is the one the call site used, so a call that
4002        // went through `pcall` has no name to report.
4003        assert_eq!(
4004            f.run(&[
4005                b"EVAL",
4006                b"local ok, e = pcall(bit.band, 'x') return tostring(e)",
4007                b"0",
4008            ]),
4009            "$52\r\nbad argument #1 to '?' (number expected, got string)\r\n"
4010        );
4011        // The table is readable and not writable, the same as `redis`.
4012        assert_eq!(
4013            f.run(&[
4014                b"EVAL",
4015                b"local t = {} for k in pairs(bit) do t[#t+1] = k end \
4016                  table.sort(t) return table.concat(t, ' ')",
4017                b"0",
4018            ]),
4019            "$66\r\narshift band bnot bor bswap bxor lshift rol ror rshift tobit tohex\r\n"
4020        );
4021        for body in [&b"bit.band = 1"[..], b"rawset(bit, 'zz', 1)"] {
4022            assert!(
4023                f.run(&[b"EVAL", body, b"0"])
4024                    .contains("Attempt to modify a readonly table script: "),
4025                "{body:?}",
4026            );
4027        }
4028    }
4029
4030    #[test]
4031    fn a_script_gets_the_cjson_library_a_real_server_carries() {
4032        let mut f = Fixture::new();
4033        // Encoding, including the three shapes nobody guesses right: an empty
4034        // table is an object, a number is fourteen significant digits, and a
4035        // hole in an array is a null rather than a shorter array.
4036        for (body, want) in [
4037            ("cjson.encode(nil)", "null"),
4038            ("cjson.encode(true)", "true"),
4039            ("cjson.encode(cjson.null)", "null"),
4040            ("cjson.encode(100)", "100"),
4041            ("cjson.encode(1/3)", "0.33333333333333"),
4042            ("cjson.encode(1e300)", "1e+300"),
4043            ("cjson.encode(2^53)", "9.007199254741e+15"),
4044            ("cjson.encode({})", "{}"),
4045            ("cjson.encode({1,2,3})", "[1,2,3]"),
4046            ("cjson.encode({a=1})", "{\"a\":1}"),
4047            ("cjson.encode({[1]=1,[3]=3})", "[1,null,3]"),
4048            ("cjson.encode({[0]=1})", "{\"0\":1}"),
4049            ("cjson.encode('a\\nb')", "\"a\\nb\""),
4050            // A tab and a backslash have short escapes, a vertical tab does not.
4051            ("cjson.encode('\\t\\\\')", "\"\\t\\\\\""),
4052            ("cjson.encode('\\11')", "\"\\u000b\""),
4053            // Reading and writing again is the shortest way to say the decoder
4054            // built what the encoder expected.
4055            (
4056                "cjson.encode(cjson.decode('[1,[2,{\"a\":null}]]'))",
4057                "[1,[2,{\"a\":null}]]",
4058            ),
4059            // An empty array comes back as an object, because a table with
4060            // nothing in it has nothing to say about which it was.
4061            ("cjson.encode(cjson.decode('[]'))", "{}"),
4062        ] {
4063            let script = format!("return {body}");
4064            assert_eq!(
4065                f.run(&[b"EVAL", script.as_bytes(), b"0"]),
4066                format!("${}\r\n{want}\r\n", want.len()),
4067                "{body}",
4068            );
4069        }
4070        // Decoding, where the leniency about numbers is on by default and a
4071        // null is a value of its own rather than a missing key.
4072        for (body, want) in [
4073            ("cjson.decode('[1,2,3]')[2]", ":2\r\n"),
4074            ("cjson.decode('{\"a\":41}').a + 1", ":42\r\n"),
4075            ("cjson.decode('0x10')", ":16\r\n"),
4076            ("cjson.decode('+1')", ":1\r\n"),
4077            ("cjson.decode('01')", ":1\r\n"),
4078            ("cjson.decode(1) + 1", ":2\r\n"),
4079            // A long bracket, because Lua 5.1 would eat the backslash first.
4080            ("cjson.decode([[\"\\u0041\"]]) == 'A' and 1 or 0", ":1\r\n"),
4081            ("cjson.decode('null') == cjson.null and 1 or 0", ":1\r\n"),
4082            ("cjson.decode('null') == nil and 1 or 0", ":0\r\n"),
4083        ] {
4084            let script = format!("return {body}");
4085            assert_eq!(f.run(&[b"EVAL", script.as_bytes(), b"0"]), want, "{body}");
4086        }
4087        // The settings, each of which answers with what it now holds.
4088        for (body, want) in [
4089            (
4090                "cjson.encode_number_precision(3) return cjson.encode(1/3)",
4091                "0.333",
4092            ),
4093            (
4094                "cjson.encode_invalid_numbers('null') return cjson.encode(1/0)",
4095                "null",
4096            ),
4097            (
4098                "cjson.encode_invalid_numbers(true) return cjson.encode(1/0)",
4099                "inf",
4100            ),
4101            (
4102                "cjson.encode_sparse_array(true) return cjson.encode({[1]=1,[100]=1})",
4103                "{\"1\":1,\"100\":1}",
4104            ),
4105            (
4106                "cjson.decode_array_with_array_mt(true) return cjson.encode(cjson.decode('[]'))",
4107                "[]",
4108            ),
4109            ("return tostring(cjson.encode_max_depth())", "1000"),
4110            ("return tostring(cjson.encode_keep_buffer(false))", "false"),
4111            ("return tostring(cjson.encode_sparse_array())", "false"),
4112            // A setting one script changed is not a setting the next one sees,
4113            // which is D-105.
4114            ("return tostring(cjson.encode_number_precision())", "14"),
4115        ] {
4116            assert_eq!(
4117                f.run(&[b"EVAL", body.as_bytes(), b"0"]),
4118                format!("${}\r\n{want}\r\n", want.len()),
4119                "{body}",
4120            );
4121        }
4122        // A failure names what stopped it and, when it was the text, where.
4123        for (body, want) in [
4124            (
4125                "return cjson.encode(1/0)",
4126                "Cannot serialise number: must not be NaN or Inf",
4127            ),
4128            (
4129                "return cjson.encode({[1]=1,[100]=1})",
4130                "Cannot serialise table: excessively sparse array",
4131            ),
4132            (
4133                "return cjson.encode({[true]=1})",
4134                "Cannot serialise boolean: table key must be a number or string",
4135            ),
4136            (
4137                "return cjson.encode(tostring)",
4138                "Cannot serialise function: type not supported",
4139            ),
4140            (
4141                "return cjson.encode()",
4142                "bad argument #1 to 'encode' (expected 1 argument)",
4143            ),
4144            (
4145                "return cjson.decode('[1,2')",
4146                "Expected comma or array end but found T_END at character 5",
4147            ),
4148            (
4149                "return cjson.decode('{\"a\" 1}')",
4150                "Expected colon but found T_NUMBER at character 6",
4151            ),
4152            (
4153                "return cjson.decode('tru')",
4154                "Expected value but found invalid token at character 1",
4155            ),
4156            (
4157                "return cjson.decode('[1] 2')",
4158                "Expected the end but found T_NUMBER at character 5",
4159            ),
4160            (
4161                "return cjson.encode_max_depth(0)",
4162                "bad argument #1 to 'encode_max_depth' (expected integer between 1 and 2147483647)",
4163            ),
4164            (
4165                "return cjson.encode_invalid_numbers('yes')",
4166                "bad argument #1 to 'encode_invalid_numbers' (invalid option 'yes')",
4167            ),
4168            (
4169                "return cjson.encode_max_depth(1, 2)",
4170                "bad argument #2 to 'encode_max_depth' (found too many arguments)",
4171            ),
4172        ] {
4173            let reply = f.run(&[b"EVAL", body.as_bytes(), b"0"]);
4174            assert!(
4175                reply.starts_with(&format!("-ERR user_script:1: {want} script: ")),
4176                "{body} gave {reply}",
4177            );
4178        }
4179        // A module of its own, with settings of its own and no guard on it,
4180        // which is what a real server hands back.
4181        assert_eq!(
4182            f.run(&[
4183                b"EVAL",
4184                b"local n = cjson.new() n.encode_number_precision(3) \
4185                  return cjson.encode(1/3) .. ' ' .. n.encode(1/3)",
4186                b"0",
4187            ]),
4188            "$22\r\n0.33333333333333 0.333\r\n"
4189        );
4190        // The table is readable and not writable, the same as `redis`.
4191        let names = "_NAME _VERSION decode decode_array_with_array_mt decode_invalid_numbers \
4192                     decode_max_depth encode encode_invalid_numbers encode_keep_buffer \
4193                     encode_max_depth encode_number_precision encode_sparse_array new null";
4194        assert_eq!(
4195            f.run(&[
4196                b"EVAL",
4197                b"local t = {} for k in pairs(cjson) do t[#t+1] = k end \
4198                  table.sort(t) return table.concat(t, ' ')",
4199                b"0",
4200            ]),
4201            format!("${}\r\n{names}\r\n", names.len())
4202        );
4203        for body in [&b"cjson.encode = 1"[..], b"rawset(cjson, 'zz', 1)"] {
4204            assert!(
4205                f.run(&[b"EVAL", body, b"0"])
4206                    .contains("Attempt to modify a readonly table script: "),
4207                "{body:?}",
4208            );
4209        }
4210    }
4211
4212    #[test]
4213    fn a_script_gets_the_struct_library_a_real_server_carries() {
4214        let mut f = Fixture::new();
4215        // Packing, where the sizes are the ones a sixty four bit build gives
4216        // and the order is the machine's own unless the format says otherwise.
4217        for (body, want) in [
4218            ("#struct.pack('i4', 1)", ":4\r\n"),
4219            ("#struct.pack('l', 1)", ":8\r\n"),
4220            ("#struct.pack('d', 1)", ":8\r\n"),
4221            ("#struct.pack('f', 1)", ":4\r\n"),
4222            ("#struct.pack('s', 'abc')", ":4\r\n"),
4223            ("#struct.pack('c3', 'abcdef')", ":3\r\n"),
4224            ("#struct.pack('x')", ":1\r\n"),
4225            ("string.byte(struct.pack('i4', 1), 1)", ":1\r\n"),
4226            ("string.byte(struct.pack('>i4', 1), 4)", ":1\r\n"),
4227            ("string.byte(struct.pack('<i4', 1), 1)", ":1\r\n"),
4228            // Past eight bytes the C shifts an unsigned long off the end, so
4229            // the rest of the bytes are zero and a negative is not carried.
4230            ("string.byte(struct.pack('i16', -1), 9)", ":0\r\n"),
4231            ("string.byte(struct.pack('i8', -1), 8)", ":255\r\n"),
4232            // A count of zero on `c` writes the whole string, `s` adds the
4233            // terminator, and `x` writes a zero byte nobody reads back.
4234            ("#struct.pack('c0', 'abcd')", ":4\r\n"),
4235            ("string.byte(struct.pack('s', 'a'), 2)", ":0\r\n"),
4236            ("string.byte(struct.pack('bxb', 1, 2), 2)", ":0\r\n"),
4237        ] {
4238            let script = format!("return {body}");
4239            assert_eq!(f.run(&[b"EVAL", script.as_bytes(), b"0"]), want, "{body}");
4240        }
4241        // Sizes, including the two the C is lenient about: an unknown letter
4242        // and a bare digit are both nothing at all rather than a complaint.
4243        for (body, want) in [
4244            ("struct.size('i')", ":4\r\n"),
4245            ("struct.size('l')", ":8\r\n"),
4246            ("struct.size('T')", ":8\r\n"),
4247            ("struct.size('h')", ":2\r\n"),
4248            ("struct.size('c10')", ":10\r\n"),
4249            ("struct.size('ic')", ":5\r\n"),
4250            ("struct.size('!8ic')", ":5\r\n"),
4251            ("struct.size('!4i')", ":4\r\n"),
4252            // Nothing is padded until `!` turns alignment on, and then a
4253            // double is pushed out to the next eight byte boundary.
4254            ("struct.size('bd')", ":9\r\n"),
4255            ("struct.size('!bd')", ":16\r\n"),
4256            ("struct.size('A')", ":0\r\n"),
4257            ("struct.size('7')", ":0\r\n"),
4258        ] {
4259            let script = format!("return {body}");
4260            assert_eq!(f.run(&[b"EVAL", script.as_bytes(), b"0"]), want, "{body}");
4261        }
4262        // Unpacking, which hands back the values and then where it stopped, so
4263        // the last number can be passed straight back in as the next offset.
4264        for (body, want) in [
4265            ("select('#', struct.unpack('i4', '\\1\\0\\0\\0'))", ":2\r\n"),
4266            ("select(1, struct.unpack('i4', '\\1\\0\\0\\0'))", ":1\r\n"),
4267            ("select(2, struct.unpack('i4', '\\1\\0\\0\\0'))", ":5\r\n"),
4268            ("select(1, struct.unpack('i1', '\\255'))", ":-1\r\n"),
4269            ("select(1, struct.unpack('I1', '\\255'))", ":255\r\n"),
4270            (
4271                "select(1, struct.unpack('i4', struct.pack('i4', -70000)))",
4272                ":-70000\r\n",
4273            ),
4274            ("select(2, struct.unpack('i1', 'abc', 2))", ":3\r\n"),
4275            // A `c0` takes its length from the value read just before it and
4276            // swallows it, so one byte says how long the next three are and
4277            // only the string and the position come back.
4278            ("select('#', struct.unpack('bc0', '\\3abcd'))", ":2\r\n"),
4279            ("select(2, struct.unpack('bc0', '\\3abcd'))", ":5\r\n"),
4280        ] {
4281            let script = format!("return {body}");
4282            assert_eq!(f.run(&[b"EVAL", script.as_bytes(), b"0"]), want, "{body}");
4283        }
4284        for (body, want) in [
4285            ("select(1, struct.unpack('bc0', '\\3abcd'))", "abc"),
4286            ("select(1, struct.unpack('s', 'ab\\0cd'))", "ab"),
4287            ("select(1, struct.unpack('c3', 'abcdef'))", "abc"),
4288        ] {
4289            let script = format!("return {body}");
4290            assert_eq!(
4291                f.run(&[b"EVAL", script.as_bytes(), b"0"]),
4292                format!("${}\r\n{want}\r\n", want.len()),
4293                "{body}",
4294            );
4295        }
4296        // A failure names the argument the C names, which is not always the
4297        // argument a reader would pick.
4298        for (body, want) in [
4299            (
4300                "return struct.pack()",
4301                "bad argument #1 to 'pack' (string expected, got no value)",
4302            ),
4303            // The C pushes a nil before it reads anything, so a missing value
4304            // is a nil rather than nothing at all.
4305            (
4306                "return struct.pack('i4')",
4307                "bad argument #2 to 'pack' (number expected, got nil)",
4308            ),
4309            // And it reads the string with a post increment before it checks
4310            // the length, so the number here is one past the real argument.
4311            (
4312                "return struct.pack('c6', 'abc')",
4313                "bad argument #3 to 'pack' (string too short)",
4314            ),
4315            (
4316                "return struct.pack('A', 'x')",
4317                "bad argument #1 to 'pack' (invalid format option 'A')",
4318            ),
4319            (
4320                "return struct.pack('i33', 1)",
4321                "integral size 33 is larger than limit of 32",
4322            ),
4323            (
4324                "return struct.pack('!3i', 1)",
4325                "alignment 3 is not a power of 2",
4326            ),
4327            (
4328                "return struct.unpack()",
4329                "bad argument #1 to 'unpack' (string expected, got no value)",
4330            ),
4331            (
4332                "return struct.unpack('i4')",
4333                "bad argument #2 to 'unpack' (string expected, got no value)",
4334            ),
4335            (
4336                "return struct.unpack('i4', 'ab')",
4337                "bad argument #2 to 'unpack' (data string too short)",
4338            ),
4339            (
4340                "return struct.unpack('i1', 'abc', 0)",
4341                "bad argument #3 to 'unpack' (offset must be 1 or greater)",
4342            ),
4343            (
4344                "return struct.unpack('c0', 'abc')",
4345                "format 'c0' needs a previous size",
4346            ),
4347            (
4348                "return struct.unpack('s', 'abc')",
4349                "unfinished string in data",
4350            ),
4351            (
4352                "return struct.size()",
4353                "bad argument #1 to 'size' (string expected, got no value)",
4354            ),
4355            (
4356                "return struct.size('s')",
4357                "bad argument #1 to 'size' (option 's' has no fixed size)",
4358            ),
4359            (
4360                "return struct.size('c0')",
4361                "bad argument #1 to 'size' (option 'c0' has no fixed size)",
4362            ),
4363        ] {
4364            let reply = f.run(&[b"EVAL", body.as_bytes(), b"0"]);
4365            assert!(
4366                reply.starts_with(&format!("-ERR user_script:1: {want} script: ")),
4367                "{body} gave {reply}",
4368            );
4369        }
4370        // Three members and no version, which is all the C registers.
4371        let names = "pack size unpack";
4372        assert_eq!(
4373            f.run(&[
4374                b"EVAL",
4375                b"local t = {} for k in pairs(struct) do t[#t+1] = k end \
4376                  table.sort(t) return table.concat(t, ' ')",
4377                b"0",
4378            ]),
4379            format!("${}\r\n{names}\r\n", names.len())
4380        );
4381        for body in [&b"struct.pack = 1"[..], b"rawset(struct, 'zz', 1)"] {
4382            assert!(
4383                f.run(&[b"EVAL", body, b"0"])
4384                    .contains("Attempt to modify a readonly table script: "),
4385                "{body:?}",
4386            );
4387        }
4388    }
4389
4390    #[test]
4391    fn a_script_gets_the_cmsgpack_library_a_real_server_carries() {
4392        let mut f = Fixture::new();
4393        // Every value goes out in the shortest form that holds it, and several
4394        // arguments are packed one after another into one string.
4395        let hex = "local function hx(s) return (string.gsub(s, '.', \
4396                   function(c) return string.format('%02x', string.byte(c)) end)) end ";
4397        for (body, want) in [
4398            ("cmsgpack.pack(nil)", "c0"),
4399            ("cmsgpack.pack(true)", "c3"),
4400            ("cmsgpack.pack(false)", "c2"),
4401            ("cmsgpack.pack(0)", "00"),
4402            ("cmsgpack.pack(127)", "7f"),
4403            ("cmsgpack.pack(128)", "cc80"),
4404            ("cmsgpack.pack(-1)", "ff"),
4405            ("cmsgpack.pack(-33)", "d0df"),
4406            ("cmsgpack.pack(65535)", "cdffff"),
4407            ("cmsgpack.pack(4294967296)", "cf0000000100000000"),
4408            ("cmsgpack.pack(2^53)", "cf0020000000000000"),
4409            ("cmsgpack.pack(-2^63)", "d38000000000000000"),
4410            // Past what an integer holds it is a number again, and a number
4411            // goes out narrow whenever four bytes give it back unchanged.
4412            ("cmsgpack.pack(2^64)", "ca5f800000"),
4413            ("cmsgpack.pack(1.5)", "ca3fc00000"),
4414            ("cmsgpack.pack(0.1)", "cb3fb999999999999a"),
4415            ("cmsgpack.pack('abc')", "a3616263"),
4416            ("cmsgpack.pack('')", "a0"),
4417            ("cmsgpack.pack({})", "90"),
4418            ("cmsgpack.pack({1, 2})", "920102"),
4419            ("cmsgpack.pack({a = 1})", "81a16101"),
4420            ("cmsgpack.pack(1, 'a', true)", "01a161c3"),
4421            // Sixteen levels of table are packed and the seventeenth is a nil,
4422            // which is what the C does rather than refusing the whole thing.
4423            (
4424                "(function() local t = {} local c = t \
4425                 for i = 1, 20 do c.n = {} c = c.n end return cmsgpack.pack(t) end)()",
4426                "81a16e81a16e81a16e81a16e81a16e81a16e81a16e81a16e\
4427                 81a16e81a16e81a16e81a16e81a16e81a16e81a16e81a16ec0",
4428            ),
4429        ] {
4430            let script = format!("{hex} return hx({body})");
4431            assert_eq!(
4432                f.run(&[b"EVAL", script.as_bytes(), b"0"]),
4433                format!("${}\r\n{want}\r\n", want.len()),
4434                "{body}",
4435            );
4436        }
4437        // Unpacking reads the whole stream, so a string holding three values
4438        // hands back three. The two that take an offset put where they got to
4439        // in front of the values, and answer minus one when nothing is left.
4440        for (body, want) in [
4441            ("cmsgpack.unpack(cmsgpack.pack(42))", 42),
4442            ("select('#', cmsgpack.unpack('\\1\\2\\3'))", 3),
4443            ("select(3, cmsgpack.unpack('\\1\\2\\3'))", 3),
4444            ("select('#', cmsgpack.unpack(''))", 0),
4445            ("select('#', cmsgpack.unpack_one('\\1\\2\\3'))", 2),
4446            ("select(1, cmsgpack.unpack_one('\\1\\2\\3'))", 1),
4447            ("select(2, cmsgpack.unpack_one('\\1\\2\\3'))", 1),
4448            ("select(1, cmsgpack.unpack_one('\\1\\2\\3', 2))", -1),
4449            ("select(1, cmsgpack.unpack_one('\\1'))", -1),
4450            ("select(1, cmsgpack.unpack_one('', 0))", -1),
4451            ("select('#', cmsgpack.unpack_limit('\\1\\2\\3', 2))", 3),
4452            ("select(1, cmsgpack.unpack_limit('\\1\\2\\3', 2))", 2),
4453            // A limit of nothing at all takes the read everything path, which
4454            // has no offset in front of it.
4455            ("select('#', cmsgpack.unpack_limit('\\1\\2\\3', 0, 0))", 3),
4456            ("cmsgpack.unpack(cmsgpack.pack({1, 2, 3}))[2]", 2),
4457        ] {
4458            let script = format!("return {body}");
4459            assert_eq!(
4460                f.run(&[b"EVAL", script.as_bytes(), b"0"]),
4461                format!(":{want}\r\n"),
4462                "{body}",
4463            );
4464        }
4465        for (body, want) in [
4466            ("cmsgpack.unpack(cmsgpack.pack({a = 'b'})).a", "b"),
4467            ("tostring(cmsgpack.unpack(cmsgpack.pack(1.5)))", "1.5"),
4468            ("tostring(cmsgpack.unpack(cmsgpack.pack(nil)))", "nil"),
4469            (
4470                "tostring(cmsgpack.unpack(string.char(0xcb, 0x7f, 0xf0, 0, 0, 0, 0, 0, 0)))",
4471                "inf",
4472            ),
4473            ("cmsgpack._NAME", "cmsgpack"),
4474            ("cmsgpack._VERSION", "lua-cmsgpack 0.4.0"),
4475            (
4476                "cmsgpack._COPYRIGHT",
4477                "Copyright (C) 2012, Salvatore Sanfilippo",
4478            ),
4479            (
4480                "cmsgpack._DESCRIPTION",
4481                "MessagePack C implementation for Lua",
4482            ),
4483        ] {
4484            let script = format!("return {body}");
4485            assert_eq!(
4486                f.run(&[b"EVAL", script.as_bytes(), b"0"]),
4487                format!("${}\r\n{want}\r\n", want.len()),
4488                "{body}",
4489            );
4490        }
4491        for (body, want) in [
4492            // The C counts the arguments before it reads any of them, so the
4493            // one it names when there are none is the one before the first.
4494            (
4495                "return cmsgpack.pack()",
4496                "bad argument #0 to 'pack' (MessagePack pack needs input.)",
4497            ),
4498            (
4499                "return cmsgpack.unpack()",
4500                "bad argument #1 to 'unpack' (string expected, got no value)",
4501            ),
4502            (
4503                "return cmsgpack.unpack(string.char(193))",
4504                "Bad data format in input.",
4505            ),
4506            (
4507                "return cmsgpack.unpack(string.char(204))",
4508                "Missing bytes in input.",
4509            ),
4510            (
4511                "return cmsgpack.unpack(string.char(146, 1))",
4512                "Missing bytes in input.",
4513            ),
4514            (
4515                "return cmsgpack.unpack_one('\\1', 5)",
4516                "Start offset 5 greater than input length 1.",
4517            ),
4518            (
4519                "return cmsgpack.unpack_limit('\\1\\2', 1, 5)",
4520                "Start offset 5 greater than input length 2.",
4521            ),
4522            // The second number here is the length of the input rather than
4523            // the limit, which is a mixed up argument in the C kept on purpose.
4524            (
4525                "return cmsgpack.unpack_one('\\1', -1)",
4526                "Invalid request to unpack with offset of -1 and limit of 1.",
4527            ),
4528            (
4529                "return cmsgpack.unpack_limit('\\1', -1, 0)",
4530                "Invalid request to unpack with offset of 0 and limit of 1.",
4531            ),
4532        ] {
4533            let reply = f.run(&[b"EVAL", body.as_bytes(), b"0"]);
4534            assert!(
4535                reply.starts_with(&format!("-ERR user_script:1: {want} script: ")),
4536                "{body} gave {reply}",
4537            );
4538        }
4539        // Four calls and the four names the C sets on the table beside them.
4540        let names = "_COPYRIGHT _DESCRIPTION _NAME _VERSION pack unpack unpack_limit unpack_one";
4541        assert_eq!(
4542            f.run(&[
4543                b"EVAL",
4544                b"local t = {} for k in pairs(cmsgpack) do t[#t+1] = k end \
4545                  table.sort(t) return table.concat(t, ' ')",
4546                b"0",
4547            ]),
4548            format!("${}\r\n{names}\r\n", names.len())
4549        );
4550        for body in [&b"cmsgpack.pack = 1"[..], b"rawset(cmsgpack, 'zz', 1)"] {
4551            assert!(
4552                f.run(&[b"EVAL", body, b"0"])
4553                    .contains("Attempt to modify a readonly table script: "),
4554                "{body:?}",
4555            );
4556        }
4557        // A library is a table like any other from a script's side, so packing
4558        // one walks its members rather than finding the guard in front empty.
4559        assert_eq!(
4560            f.run(&[
4561                b"EVAL",
4562                b"return cmsgpack.unpack(cmsgpack.pack(cmsgpack))._NAME",
4563                b"0",
4564            ]),
4565            "$8\r\ncmsgpack\r\n"
4566        );
4567    }
4568
4569    /// The library used by most of the function tests below.
4570    ///
4571    /// Written out once because every one of them wants a library that has
4572    /// something to call, and because the line numbers in the failures a couple
4573    /// of them check are line numbers in this.
4574    const LIB: &[u8] = b"#!lua name=mylib\n\
4575        local counter = 0\n\
4576        redis.register_function{function_name = 'ping', description = 'says pong',\n\
4577        callback = function(keys, args) return 'pong' end, flags = {'no-writes'}}\n\
4578        redis.register_function('count', function() counter = counter + 1 return counter end)\n\
4579        redis.register_function('echo', function(keys, args) return {keys, args} end)\n\
4580        redis.register_function('setit', function(keys, args) \
4581        return redis.call('SET', keys[1], args[1]) end)\n\
4582        redis.register_function('raise', function() error('boom') end)\n";
4583
4584    /// A second library, for the tests that need two of them.
4585    const OTHER: &[u8] = b"#!lua name=other\n\
4586        redis.register_function('twice', function(keys, args) return 2 end)\n";
4587
4588    #[test]
4589    fn a_library_is_loaded_once_and_called_by_name_forever_after() {
4590        let mut f = Fixture::new();
4591        assert_eq!(f.run(&[b"FUNCTION", b"LOAD", LIB]), "$5\r\nmylib\r\n");
4592        assert_eq!(f.run(&[b"FCALL", b"ping", b"0"]), "$4\r\npong\r\n");
4593        // The dictionary FCALL looks in is one for the whole server and it does
4594        // not care about case, which is why this finds the same function.
4595        assert_eq!(f.run(&[b"FCALL", b"PiNg", b"0"]), "$4\r\npong\r\n");
4596        // Keys and arguments arrive as the two arguments of the callback rather
4597        // than as globals, and a function that reads KEYS is reading a name
4598        // that is not there.
4599        assert_eq!(
4600            f.run(&[b"FCALL", b"echo", b"1", b"k", b"a", b"b"]),
4601            "*2\r\n*1\r\n$1\r\nk\r\n*2\r\n$1\r\na\r\n$1\r\nb\r\n"
4602        );
4603        assert_eq!(f.run(&[b"FCALL", b"setit", b"1", b"s", b"v"]), "+OK\r\n");
4604        assert_eq!(f.run(&[b"GET", b"s"]), "$1\r\nv\r\n");
4605        // A library's own local outlives the call that made it, which is the
4606        // whole reason a library is not a script.
4607        assert_eq!(f.run(&[b"FCALL", b"count", b"0"]), ":1\r\n");
4608        assert_eq!(f.run(&[b"FCALL", b"count", b"0"]), ":2\r\n");
4609        // The name a failure ends with is the function's, where a script's is
4610        // its digest, and the line is a line in the library.
4611        assert_eq!(
4612            f.run(&[b"FCALL", b"raise", b"0"]),
4613            "-ERR user_function:8: boom script: raise, on @user_function:8.\r\n"
4614        );
4615        // Deleting is by the exact name, so the upper case spelling that found
4616        // the function a moment ago does not find the library.
4617        assert_eq!(
4618            f.run(&[b"FUNCTION", b"DELETE", b"MYLIB"]),
4619            "-ERR Library not found\r\n"
4620        );
4621        assert_eq!(f.run(&[b"FUNCTION", b"DELETE", b"mylib"]), "+OK\r\n");
4622        assert_eq!(
4623            f.run(&[b"FCALL", b"ping", b"0"]),
4624            "-ERR Function not found\r\n"
4625        );
4626    }
4627
4628    #[test]
4629    fn a_library_that_is_wrong_says_which_way_it_is_wrong() {
4630        let mut f = Fixture::new();
4631        for (code, want) in [
4632            (&b"return 1"[..], "ERR Missing library metadata"),
4633            (b"#!lua name=x", "ERR Invalid library metadata"),
4634            (b"#!\n", "ERR Library name was not given"),
4635            (b"#!lua\nx", "ERR Library name was not given"),
4636            (
4637                b"#!lua name=a name=b\nx",
4638                "ERR Invalid metadata value, name argument was given multiple times",
4639            ),
4640            (
4641                b"#!lua nome=a\nx",
4642                "ERR Invalid metadata value given: nome=a",
4643            ),
4644            (b"#!lua name=\"q\nx", "ERR Invalid library metadata"),
4645            (
4646                b"#!lua name=a-b\nx",
4647                "ERR Library names can only contain letters, numbers, or underscores(_) \
4648                 and must be at least one character long",
4649            ),
4650            (b"#!zz name=x\nx", "ERR Engine 'zz' not found"),
4651            (
4652                b"#!lua name=c\nthis is not lua",
4653                "ERR Error compiling function: user_function:2: '=' expected near 'is'",
4654            ),
4655            // Nothing at all is on the global table during a load except one
4656            // table with eight names on it, so `error` is as absent as anything
4657            // a library misspelled would be.
4658            (
4659                b"#!lua name=r\nerror('boom')",
4660                "ERR Error registering functions: ERR user_function:2: \
4661                 Script attempted to access nonexistent global variable 'error'",
4662            ),
4663            // And `redis` is there but `redis.call` is not, so the name the
4664            // complaint gives is `call` and not `redis`.
4665            (
4666                b"#!lua name=r\nredis.call('PING')",
4667                "ERR Error registering functions: ERR user_function:2: \
4668                 Script attempted to access nonexistent global variable 'call'",
4669            ),
4670            (
4671                b"#!lua name=r\nx = 1",
4672                "ERR Error registering functions: ERR user_function:2: \
4673                 Attempt to modify a readonly table",
4674            ),
4675            (b"#!lua name=n\nlocal x = 1", "ERR No functions registered"),
4676        ] {
4677            assert_eq!(
4678                f.run(&[b"FUNCTION", b"LOAD", code]),
4679                format!("-{want}\r\n"),
4680                "{}",
4681                String::from_utf8_lossy(code),
4682            );
4683        }
4684    }
4685
4686    #[test]
4687    fn register_function_turns_away_every_call_it_cannot_make_sense_of() {
4688        let mut f = Fixture::new();
4689        for (call, want) in [
4690            (
4691                &b"redis.register_function()"[..],
4692                "wrong number of arguments to redis.register_function",
4693            ),
4694            (
4695                b"redis.register_function('a', function() end, 1)",
4696                "wrong number of arguments to redis.register_function",
4697            ),
4698            (
4699                b"redis.register_function('a')",
4700                "calling redis.register_function with a single argument is only \
4701                 applicable to Lua table (representing named arguments).",
4702            ),
4703            (
4704                b"redis.register_function({foo = 'a'})",
4705                "unknown argument given to redis.register_function",
4706            ),
4707            (
4708                b"redis.register_function({callback = function() end})",
4709                "redis.register_function must get a function name argument",
4710            ),
4711            (
4712                b"redis.register_function({function_name = 'a'})",
4713                "redis.register_function must get a callback argument",
4714            ),
4715            (
4716                b"redis.register_function({function_name = {}, callback = function() end})",
4717                "function_name argument given to redis.register_function must be a string",
4718            ),
4719            (
4720                b"redis.register_function({function_name = 'a', description = {}, \
4721                  callback = function() end})",
4722                "description argument given to redis.register_function must be a string",
4723            ),
4724            (
4725                b"redis.register_function({function_name = 'a', callback = 1})",
4726                "callback argument given to redis.register_function must be a function",
4727            ),
4728            (
4729                b"redis.register_function({function_name = 'a', callback = function() end, \
4730                  flags = 1})",
4731                "flags argument to redis.register_function must be a table \
4732                 representing function flags",
4733            ),
4734            (
4735                b"redis.register_function({function_name = 'a', callback = function() end, \
4736                  flags = {'zz'}})",
4737                "unknown flag given",
4738            ),
4739            (
4740                b"redis.register_function({}, function() end)",
4741                "first argument to redis.register_function must be a string",
4742            ),
4743            (
4744                b"redis.register_function('a', 1)",
4745                "second argument to redis.register_function must be a function",
4746            ),
4747            (
4748                b"redis.register_function('a-b', function() end)",
4749                "Library names can only contain letters, numbers, or underscores(_) \
4750                 and must be at least one character long",
4751            ),
4752            (
4753                b"redis.register_function('d', function() end) \
4754                  redis.register_function('d', function() end)",
4755                "Function already exists in the library",
4756            ),
4757        ] {
4758            let mut code = b"#!lua name=e\n".to_vec();
4759            code.extend_from_slice(call);
4760            // Two `ERR` in a row on purpose. The sentence comes back as a table
4761            // with the code already on it, which is what keeps the position off
4762            // the front of it, and then the code goes on the line as well.
4763            assert_eq!(
4764                f.run(&[b"FUNCTION", b"LOAD", &code]),
4765                format!("-ERR Error registering functions: ERR {want}\r\n"),
4766                "{}",
4767                String::from_utf8_lossy(call),
4768            );
4769        }
4770        // A number is a name, because the C reads an argument that should be a
4771        // string through a helper that takes a number and prints it.
4772        assert_eq!(
4773            f.run(&[
4774                b"FUNCTION",
4775                b"LOAD",
4776                b"#!lua name=n\nredis.register_function(12, function() return 1 end)",
4777            ]),
4778            "$1\r\nn\r\n"
4779        );
4780        assert_eq!(f.run(&[b"FCALL", b"12", b"0"]), ":1\r\n");
4781        // The dictionary inside one library is case sensitive where the one
4782        // across libraries is not, so these are two functions.
4783        assert_eq!(
4784            f.run(&[
4785                b"FUNCTION",
4786                b"LOAD",
4787                b"#!lua name=c\nredis.register_function('d', function() return 1 end) \
4788                  redis.register_function('D', function() return 2 end)",
4789            ]),
4790            "$1\r\nc\r\n"
4791        );
4792    }
4793
4794    #[test]
4795    fn a_library_cannot_take_a_name_another_library_already_has() {
4796        let mut f = Fixture::new();
4797        assert_eq!(f.run(&[b"FUNCTION", b"LOAD", LIB]), "$5\r\nmylib\r\n");
4798        assert_eq!(
4799            f.run(&[b"FUNCTION", b"LOAD", LIB]),
4800            "-ERR Library 'mylib' already exists\r\n"
4801        );
4802        // A different library that registers a name the first one already has,
4803        // which is checked without regard to case because the dictionary it is
4804        // checked against is.
4805        assert_eq!(
4806            f.run(&[
4807                b"FUNCTION",
4808                b"LOAD",
4809                b"#!lua name=other\nredis.register_function('PING', function() return 1 end)",
4810            ]),
4811            "-ERR Function PING already exists\r\n"
4812        );
4813        // REPLACE reloads a library over itself, and the collision check leaves
4814        // the library being replaced out or nothing could ever be reloaded.
4815        assert_eq!(
4816            f.run(&[b"FUNCTION", b"LOAD", b"REPLACE", LIB]),
4817            "$5\r\nmylib\r\n"
4818        );
4819        // The counter went back to zero with the reload, since the library is a
4820        // new one and its locals are new with it.
4821        assert_eq!(f.run(&[b"FCALL", b"count", b"0"]), ":1\r\n");
4822        assert_eq!(
4823            f.run(&[b"FUNCTION", b"LOAD", b"NOPE", LIB]),
4824            "-ERR Unknown option given: NOPE\r\n"
4825        );
4826        // The loop that reads the options stops one short of the end, so the
4827        // last argument is the code whatever it looks like.
4828        assert_eq!(
4829            f.run(&[b"FUNCTION", b"LOAD", b"REPLACE"]),
4830            "-ERR Missing library metadata\r\n"
4831        );
4832        assert_eq!(
4833            f.run(&[b"FUNCTION", b"LOAD"]),
4834            "-ERR wrong number of arguments for 'function|load' command\r\n"
4835        );
4836    }
4837
4838    #[test]
4839    fn fcall_checks_the_name_before_it_looks_at_anything_else() {
4840        let mut f = Fixture::new();
4841        assert_eq!(f.run(&[b"FUNCTION", b"LOAD", LIB]), "$5\r\nmylib\r\n");
4842        for (args, want) in [
4843            (&[&b"nosuch"[..], b"x"][..], "ERR Function not found"),
4844            (&[b"ping", b"x"], "ERR Bad number of keys provided"),
4845            (&[b"ping", b"1.5"], "ERR Bad number of keys provided"),
4846            (&[b"ping", b"+1"], "ERR Bad number of keys provided"),
4847            (
4848                &[b"ping", b"99999999999999999999"],
4849                "ERR Bad number of keys provided",
4850            ),
4851            (
4852                &[b"ping", b"3", b"a"],
4853                "ERR Number of keys can't be greater than number of args",
4854            ),
4855            (&[b"ping", b"-1"], "ERR Number of keys can't be negative"),
4856        ] {
4857            let mut wire: Vec<&[u8]> = vec![b"FCALL"];
4858            wire.extend_from_slice(args);
4859            assert_eq!(f.run(&wire), format!("-{want}\r\n"), "{args:?}");
4860        }
4861        // The read-only spelling refuses a function the library did not mark
4862        // no-writes, and it refuses it before anything runs.
4863        assert_eq!(
4864            f.run(&[b"FCALL_RO", b"setit", b"1", b"s", b"v"]),
4865            "-ERR Can not execute a script with write flag using *_ro command.\r\n"
4866        );
4867        assert_eq!(f.run(&[b"FCALL_RO", b"ping", b"0"]), "$4\r\npong\r\n");
4868        assert_eq!(
4869            f.run(&[b"FCALL_RO", b"nosuch", b"0"]),
4870            "-ERR Function not found\r\n"
4871        );
4872        // And a function that was marked no-writes is held to it whichever
4873        // spelling called it.
4874        assert_eq!(
4875            f.run(&[
4876                b"FUNCTION",
4877                b"LOAD",
4878                b"#!lua name=w\nredis.register_function{function_name = 'w', \
4879                  flags = {'no-writes'}, callback = function(keys) \
4880                  return redis.call('SET', keys[1], 'x') end}",
4881            ]),
4882            "$1\r\nw\r\n"
4883        );
4884        assert!(
4885            f.run(&[b"FCALL", b"w", b"1", b"k"])
4886                .starts_with("-ERR Write commands are not allowed from read-only scripts."),
4887        );
4888    }
4889
4890    #[test]
4891    fn a_function_gets_the_globals_a_script_gets_minus_the_ones_only_eval_has() {
4892        let mut f = Fixture::new();
4893        // The three names on the `redis` table that only mean something inside
4894        // EVAL are not there, and neither is the error handler EVAL installs.
4895        let names = "LOG_DEBUG LOG_NOTICE LOG_VERBOSE LOG_WARNING REDIS_VERSION \
4896                     REDIS_VERSION_NUM REPL_ALL REPL_AOF REPL_NONE REPL_REPLICA REPL_SLAVE \
4897                     acl_check_cmd call error_reply log pcall set_repl setresp sha1hex \
4898                     status_reply";
4899        let globals = "_G _VERSION assert bit cjson cmsgpack collectgarbage coroutine error \
4900                       gcinfo getmetatable ipairs load loadstring math next os pairs pcall \
4901                       rawequal rawget rawset redis select setmetatable string struct table \
4902                       tonumber tostring type unpack xpcall";
4903        assert_eq!(
4904            f.run(&[
4905                b"FUNCTION",
4906                b"LOAD",
4907                b"#!lua name=g\n\
4908                  local function sorted(t) local o = {} for k in pairs(t) do o[#o+1] = k end \
4909                  table.sort(o) return table.concat(o, ' ') end\n\
4910                  redis.register_function('names', function() return sorted(redis) end)\n\
4911                  redis.register_function('globals', function() return sorted(_G) end)\n\
4912                  redis.register_function('keysg', function() return KEYS[1] end)\n\
4913                  redis.register_function('zzz', function() return tostring(redis.zzz) end)\n\
4914                  redis.register_function('wr', function() rawset(_G, 'x', 1) end)\n\
4915                  redis.register_function('gwr', function() _G.pcall = 1 end)\n",
4916            ]),
4917            "$1\r\ng\r\n"
4918        );
4919        assert_eq!(
4920            f.run(&[b"FCALL", b"names", b"0"]),
4921            format!("${}\r\n{names}\r\n", names.len())
4922        );
4923        assert_eq!(
4924            f.run(&[b"FCALL", b"globals", b"0"]),
4925            format!("${}\r\n{globals}\r\n", globals.len())
4926        );
4927        // No `KEYS`, and reading a global that is not there is a mistake rather
4928        // than a nil, so this is the sandbox's own complaint.
4929        assert!(
4930            f.run(&[b"FCALL", b"keysg", b"1", b"k"])
4931                .contains("nonexistent global variable 'KEYS'"),
4932        );
4933        // The `redis` table has no error metatable on it, unlike the global
4934        // table, so a name that is not on it is a nil and not a complaint.
4935        assert_eq!(f.run(&[b"FCALL", b"zzz", b"0"]), "$3\r\nnil\r\n");
4936        // The global table cannot be written to either way round, which is a
4937        // stricter rule than the one a script runs under.
4938        for name in [&b"wr"[..], b"gwr"] {
4939            assert!(
4940                f.run(&[b"FCALL", name, b"0"])
4941                    .contains("Attempt to modify a readonly table"),
4942                "{}",
4943                String::from_utf8_lossy(name),
4944            );
4945        }
4946    }
4947
4948    #[test]
4949    fn function_list_says_what_every_library_registered() {
4950        let mut f = Fixture::new();
4951        assert_eq!(f.run(&[b"FUNCTION", b"LOAD", LIB]), "$5\r\nmylib\r\n");
4952        // One map per library on RESP3, and the functions inside it in the
4953        // order the library registered them, which is D-109.
4954        f.out = Out::new(Proto::Resp3);
4955        let listed = f.run(&[b"FUNCTION", b"LIST"]);
4956        assert!(listed.starts_with("*1\r\n%3\r\n$12\r\nlibrary_name\r\n$5\r\nmylib\r\n"));
4957        assert!(listed.contains("$6\r\nengine\r\n$3\r\nLUA\r\n"));
4958        assert!(listed.contains(
4959            "%3\r\n$4\r\nname\r\n$4\r\nping\r\n\
4960             $11\r\ndescription\r\n$9\r\nsays pong\r\n$5\r\nflags\r\n~1\r\n+no-writes\r\n"
4961        ));
4962        // A function with no description gets a null rather than an empty
4963        // string, and no flags is an empty set rather than a missing field.
4964        assert!(listed.contains(
4965            "$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"
4966        ));
4967        assert!(!listed.contains("library_code"));
4968        assert!(
4969            f.run(&[b"FUNCTION", b"LIST", b"WITHCODE"])
4970                .contains("library_code")
4971        );
4972        // The pattern is matched without regard to case, which is a third rule
4973        // again next to the two the two dictionaries use.
4974        assert!(
4975            f.run(&[b"FUNCTION", b"LIST", b"LIBRARYNAME", b"MY*"])
4976                .starts_with("*1\r\n")
4977        );
4978        assert_eq!(
4979            f.run(&[b"FUNCTION", b"LIST", b"LIBRARYNAME", b"zz*"]),
4980            "*0\r\n"
4981        );
4982        // On RESP2 the same reply is a flat array of six, which is what `map`
4983        // means on a protocol that has no map.
4984        f.out = Out::new(Proto::Resp2);
4985        assert!(f.run(&[b"FUNCTION", b"LIST"]).starts_with("*1\r\n*6\r\n"));
4986        for (args, want) in [
4987            (&[&b"ZZ"[..]][..], "ERR Unknown argument ZZ"),
4988            (&[b"WITHCODE", b"WITHCODE"], "ERR Unknown argument WITHCODE"),
4989            (
4990                &[b"LIBRARYNAME", b"a", b"LIBRARYNAME", b"b"],
4991                "ERR Unknown argument LIBRARYNAME",
4992            ),
4993            (&[b"LIBRARYNAME"], "ERR library name argument was not given"),
4994        ] {
4995            let mut wire: Vec<&[u8]> = vec![b"FUNCTION", b"LIST"];
4996            wire.extend_from_slice(args);
4997            assert_eq!(f.run(&wire), format!("-{want}\r\n"), "{args:?}");
4998        }
4999    }
5000
5001    #[test]
5002    fn function_stats_counts_what_is_loaded_and_says_nothing_is_running() {
5003        let mut f = Fixture::new();
5004        f.out = Out::new(Proto::Resp3);
5005        assert_eq!(
5006            f.run(&[b"FUNCTION", b"STATS"]),
5007            "%2\r\n$14\r\nrunning_script\r\n_\r\n$7\r\nengines\r\n%1\r\n$3\r\nLUA\r\n\
5008             %2\r\n$15\r\nlibraries_count\r\n:0\r\n$15\r\nfunctions_count\r\n:0\r\n"
5009        );
5010        assert_eq!(f.run(&[b"FUNCTION", b"LOAD", LIB]), "$5\r\nmylib\r\n");
5011        assert!(
5012            f.run(&[b"FUNCTION", b"STATS"])
5013                .ends_with("libraries_count\r\n:1\r\n$15\r\nfunctions_count\r\n:5\r\n"),
5014        );
5015        assert_eq!(f.run(&[b"FUNCTION", b"FLUSH"]), "+OK\r\n");
5016        assert!(
5017            f.run(&[b"FUNCTION", b"STATS"])
5018                .ends_with(":0\r\n$15\r\nfunctions_count\r\n:0\r\n")
5019        );
5020    }
5021
5022    #[test]
5023    fn every_function_subcommand_complains_about_its_own_arity() {
5024        let mut f = Fixture::new();
5025        for (args, want) in [
5026            (
5027                &[&b"STATS"[..], b"X"][..],
5028                "ERR wrong number of arguments for 'function|stats' command",
5029            ),
5030            (
5031                &[b"KILL", b"X"],
5032                "ERR wrong number of arguments for 'function|kill' command",
5033            ),
5034            (
5035                &[b"HELP", b"X"],
5036                "ERR wrong number of arguments for 'function|help' command",
5037            ),
5038            (
5039                &[b"DELETE"],
5040                "ERR wrong number of arguments for 'function|delete' command",
5041            ),
5042            (
5043                &[b"DELETE", b"a", b"b"],
5044                "ERR wrong number of arguments for 'function|delete' command",
5045            ),
5046            (
5047                &[b"DUMP", b"X"],
5048                "ERR wrong number of arguments for 'function|dump' command",
5049            ),
5050            (
5051                &[b"RESTORE"],
5052                "ERR wrong number of arguments for 'function|restore' command",
5053            ),
5054            // RESTORE is the other one that falls through to the generic
5055            // sentence, and for the same reason FLUSH does.
5056            (
5057                &[b"RESTORE", b"a", b"FLUSH", b"X"],
5058                "ERR unknown subcommand or wrong number of arguments for 'RESTORE'. \
5059                 Try FUNCTION HELP.",
5060            ),
5061            (
5062                &[b"RESTORE", b"a", b"ZZ"],
5063                "ERR Wrong restore policy given, value should be either FLUSH, APPEND \
5064                 or REPLACE.",
5065            ),
5066            // FLUSH is the one that does not, because it checks the count
5067            // itself before it looks at the argument.
5068            (
5069                &[b"FLUSH", b"SYNC", b"X"],
5070                "ERR unknown subcommand or wrong number of arguments for 'FLUSH'. \
5071                 Try FUNCTION HELP.",
5072            ),
5073            (
5074                &[b"FLUSH", b"ZZ"],
5075                "ERR FUNCTION FLUSH only supports SYNC|ASYNC option",
5076            ),
5077            (&[b"ZZ"], "ERR unknown subcommand 'ZZ'. Try FUNCTION HELP."),
5078        ] {
5079            let mut wire: Vec<&[u8]> = vec![b"FUNCTION"];
5080            wire.extend_from_slice(args);
5081            assert_eq!(f.run(&wire), format!("-{want}\r\n"), "{args:?}");
5082        }
5083        assert_eq!(
5084            f.run(&[b"FUNCTION"]),
5085            "-ERR wrong number of arguments for 'function' command\r\n"
5086        );
5087        assert_eq!(
5088            f.run(&[b"FUNCTION", b"KILL"]),
5089            "-NOTBUSY No scripts in execution right now.\r\n"
5090        );
5091    }
5092
5093    /// The two ends of the same pipe, so they are tested as one.
5094    ///
5095    /// An empty server dumps ten bytes rather than nothing, because the footer
5096    /// is there whether or not a library is in front of it, and restoring those
5097    /// ten bytes is a working no op.
5098    #[test]
5099    fn a_library_survives_a_dump_and_a_restore() {
5100        let mut f = Fixture::new();
5101        let empty = payload(&f.raw(&[b"FUNCTION", b"DUMP"]));
5102        assert_eq!(empty.len(), 10);
5103        assert_eq!(f.run(&[b"FUNCTION", b"RESTORE", &empty]), "+OK\r\n");
5104
5105        assert_eq!(f.run(&[b"FUNCTION", b"LOAD", LIB]), "$5\r\nmylib\r\n");
5106        let full = payload(&f.raw(&[b"FUNCTION", b"DUMP"]));
5107        assert!(full.len() > empty.len());
5108
5109        // The default policy is APPEND, so restoring onto the library the
5110        // payload came from is a name collision and not a quiet replacement.
5111        assert_eq!(
5112            f.run(&[b"FUNCTION", b"RESTORE", &full]),
5113            "-ERR Library mylib already exists\r\n"
5114        );
5115        assert_eq!(
5116            f.run(&[b"FUNCTION", b"RESTORE", &full, b"REPLACE"]),
5117            "+OK\r\n"
5118        );
5119        assert_eq!(
5120            f.run(&[b"FUNCTION", b"RESTORE", &full, b"FLUSH"]),
5121            "+OK\r\n"
5122        );
5123        // Whichever way it went back, the functions in it still run.
5124        assert_eq!(f.run(&[b"FCALL", b"ping", b"0"]), "$4\r\npong\r\n");
5125
5126        // FLUSH keeps only what the payload held, so a library that was there
5127        // and is not in the payload is gone.
5128        assert_eq!(f.run(&[b"FUNCTION", b"LOAD", OTHER]), "$5\r\nother\r\n");
5129        assert_eq!(
5130            f.run(&[b"FUNCTION", b"RESTORE", &full, b"FLUSH"]),
5131            "+OK\r\n"
5132        );
5133        assert_eq!(
5134            f.run(&[b"FUNCTION", b"DELETE", b"other"]),
5135            "-ERR Library not found\r\n"
5136        );
5137    }
5138
5139    /// A payload that is going to be refused has to leave the server alone.
5140    ///
5141    /// Every one of these is refused for a different reason and at a different
5142    /// depth, from bytes that are not a payload at all down to a library that
5143    /// compiles and then collides, and the library that was already there has to
5144    /// still be there afterwards in every case.
5145    #[test]
5146    fn a_restore_that_fails_changes_nothing() {
5147        let mut f = Fixture::new();
5148        assert_eq!(f.run(&[b"FUNCTION", b"LOAD", LIB]), "$5\r\nmylib\r\n");
5149        let good = payload(&f.raw(&[b"FUNCTION", b"DUMP"]));
5150
5151        // Put the footer back on, so that each of these is refused for the
5152        // reason it is meant to be testing rather than for a checksum the edit
5153        // broke on the way.
5154        let reseal = |body: &[u8], version: u16| {
5155            let mut out = body.to_vec();
5156            out.extend_from_slice(&version.to_le_bytes());
5157            let crc = yo_common::crc::crc64(0, &out);
5158            out.extend_from_slice(&crc.to_le_bytes());
5159            out
5160        };
5161        let body = &good[..good.len() - 10];
5162
5163        let mut torn = good.clone();
5164        let n = torn.len();
5165        torn[n - 1] ^= 0xff;
5166        let future = reseal(body, 999);
5167        // The opcode in front of the one library, changed to the one the 7.0
5168        // release candidates wrote and then to one that is not a library at all.
5169        let mut pre_ga = body.to_vec();
5170        pre_ga[0] = 246;
5171        let pre_ga = reseal(&pre_ga, yo_kv::rdb::VERSION);
5172        let mut other = body.to_vec();
5173        other[0] = 0;
5174        let other = reseal(&other, yo_kv::rdb::VERSION);
5175        // A library whose length says there is more of it than there is.
5176        let mut cut = body.to_vec();
5177        cut.truncate(body.len() - 1);
5178        let cut = reseal(&cut, yo_kv::rdb::VERSION);
5179
5180        for (bytes, want) in [
5181            (vec![], "ERR DUMP payload version or checksum are wrong"),
5182            (
5183                b"0123456789".to_vec(),
5184                "ERR DUMP payload version or checksum are wrong",
5185            ),
5186            (torn, "ERR DUMP payload version or checksum are wrong"),
5187            (future, "ERR DUMP payload version or checksum are wrong"),
5188            (pre_ga, "ERR Pre-GA function format not supported"),
5189            (other, "ERR given type is not a function"),
5190            (cut, "ERR Failed loading library payload"),
5191        ] {
5192            assert_eq!(
5193                f.run(&[b"FUNCTION", b"RESTORE", &bytes]),
5194                format!("-{want}\r\n")
5195            );
5196        }
5197
5198        // Still exactly the one library, and it still runs.
5199        assert_eq!(f.run(&[b"FCALL", b"ping", b"0"]), "$4\r\npong\r\n");
5200        let again = payload(&f.raw(&[b"FUNCTION", b"DUMP"]));
5201        assert_eq!(again, good);
5202    }
5203
5204    /// A REPLACE takes a library's name off another library and still refuses to
5205    /// take a function name off one it is leaving alone.
5206    #[test]
5207    fn a_restore_will_not_take_a_function_name_off_a_library_it_keeps() {
5208        let mut f = Fixture::new();
5209        assert_eq!(f.run(&[b"FUNCTION", b"LOAD", LIB]), "$5\r\nmylib\r\n");
5210        let full = payload(&f.raw(&[b"FUNCTION", b"DUMP"]));
5211        // A second library registering the name the payload's library uses.
5212        let clash =
5213            b"#!lua name=cl\nredis.register_function('ping', function() return 'other' end)"
5214                .as_slice();
5215        assert_eq!(f.run(&[b"FUNCTION", b"FLUSH"]), "+OK\r\n");
5216        assert_eq!(f.run(&[b"FUNCTION", b"LOAD", clash]), "$2\r\ncl\r\n");
5217        assert_eq!(
5218            f.run(&[b"FUNCTION", b"RESTORE", &full, b"REPLACE"]),
5219            "-ERR Function ping already exists\r\n"
5220        );
5221        // Untouched, so the name still belongs to the library that had it.
5222        assert_eq!(f.run(&[b"FCALL", b"ping", b"0"]), "$5\r\nother\r\n");
5223    }
5224
5225    #[test]
5226    fn command_getkeys_reads_the_key_count_out_of_a_script_call() {
5227        let mut f = Fixture::new();
5228        assert_eq!(
5229            f.run(&[b"COMMAND", b"GETKEYS", b"EVAL", b"return 1", b"1", b"k"]),
5230            "*1\r\n$1\r\nk\r\n"
5231        );
5232        assert_eq!(
5233            f.run(&[
5234                b"COMMAND", b"GETKEYS", b"EVALSHA", b"abc", b"2", b"k1", b"k2"
5235            ]),
5236            "*2\r\n$2\r\nk1\r\n$2\r\nk2\r\n"
5237        );
5238        // None is a real answer for a script and the arguments past the count
5239        // are not keys, so they are not listed.
5240        assert_eq!(
5241            f.run(&[b"COMMAND", b"GETKEYS", b"EVAL_RO", b"return 1", b"0", b"a"]),
5242            "*0\r\n"
5243        );
5244        // A count that makes no sense finds no keys rather than being an error,
5245        // which is what a real server's key spec does with it.
5246        assert_eq!(
5247            f.run(&[b"COMMAND", b"GETKEYS", b"EVAL", b"return 1", b"3", b"k"]),
5248            "*0\r\n"
5249        );
5250        assert_eq!(
5251            f.run(&[b"COMMAND", b"GETKEYS", b"EVAL", b"return 1", b"-1"]),
5252            "*0\r\n"
5253        );
5254        assert_eq!(
5255            f.run(&[b"COMMAND", b"GETKEYS", b"EVAL", b"return 1", b"abc"]),
5256            "*0\r\n"
5257        );
5258        // The count itself has to be there, and that is an arity question.
5259        assert_eq!(
5260            f.run(&[b"COMMAND", b"GETKEYS", b"EVAL", b"return 1"]),
5261            "-ERR Invalid number of arguments specified for command\r\n"
5262        );
5263    }
5264
5265    #[test]
5266    fn the_helpers_on_the_redis_table_answer_the_way_they_are_documented() {
5267        let mut f = Fixture::new();
5268        let eval = |f: &mut Fixture, body: &[u8]| f.run(&[b"EVAL", body, b"0"]);
5269
5270        assert_eq!(
5271            eval(&mut f, b"return redis.sha1hex('')"),
5272            "$40\r\nda39a3ee5e6b4b0d3255bfef95601890afd80709\r\n"
5273        );
5274        assert_eq!(
5275            eval(&mut f, b"return redis.sha1hex('return 1')"),
5276            "$40\r\ne0e1f9fabfc9d4800c877a703b823ac0578ff8db\r\n"
5277        );
5278        // A message with no space in it gets the generic code in front, and one
5279        // that already looks like a coded error is left alone.
5280        assert_eq!(
5281            eval(&mut f, b"return redis.error_reply('boom')"),
5282            "-ERR boom\r\n"
5283        );
5284        assert_eq!(
5285            eval(&mut f, b"return redis.error_reply('WRONGTYPE nope')"),
5286            "-WRONGTYPE nope\r\n"
5287        );
5288        assert_eq!(
5289            eval(&mut f, b"return redis.status_reply('fine')"),
5290            "+fine\r\n"
5291        );
5292        // Neither of them raises when it is called wrongly, they answer a value
5293        // that is an error, which is a difference a script can see.
5294        assert_eq!(
5295            eval(&mut f, b"return redis.error_reply(1)"),
5296            "-ERR wrong number or type of arguments\r\n"
5297        );
5298        assert_eq!(
5299            eval(&mut f, b"local x = redis.status_reply() return x.err"),
5300            "$37\r\nERR wrong number or type of arguments\r\n"
5301        );
5302
5303        // The constants a script branches on.
5304        assert_eq!(
5305            eval(
5306                &mut f,
5307                b"return redis.LOG_DEBUG .. redis.LOG_VERBOSE .. redis.LOG_NOTICE .. redis.LOG_WARNING"
5308            ),
5309            "$4\r\n0123\r\n"
5310        );
5311        assert_eq!(
5312            eval(
5313                &mut f,
5314                b"return redis.REPL_NONE .. redis.REPL_AOF .. redis.REPL_SLAVE .. redis.REPL_REPLICA .. redis.REPL_ALL"
5315            ),
5316            "$5\r\n01223\r\n"
5317        );
5318        // The calls that exist so an old script keeps working.
5319        assert_eq!(eval(&mut f, b"return redis.replicate_commands()"), ":1\r\n");
5320        assert_eq!(
5321            eval(&mut f, b"redis.set_repl(redis.REPL_ALL) return 1"),
5322            ":1\r\n"
5323        );
5324        assert_eq!(
5325            eval(&mut f, b"redis.log(redis.LOG_WARNING, 'x') return 1"),
5326            ":1\r\n"
5327        );
5328        assert_eq!(
5329            eval(&mut f, b"return redis.acl_check_cmd('get', 'k')"),
5330            ":1\r\n"
5331        );
5332        // Each of those checks its arguments the way a real server does.
5333        assert!(eval(&mut f, b"redis.setresp(4)").contains("RESP version must be 2 or 3."),);
5334        assert!(eval(&mut f, b"redis.set_repl(9)").contains("Invalid replication flags."));
5335        assert!(
5336            eval(&mut f, b"redis.log('x', 'y')")
5337                .contains("First argument must be a number (log level)."),
5338        );
5339        assert!(
5340            eval(&mut f, b"return redis.acl_check_cmd('nosuchcmd')")
5341                .contains("Invalid command passed to redis.acl_check_cmd()"),
5342        );
5343        assert!(
5344            eval(&mut f, b"return redis.acl_check_cmd('get')")
5345                .contains("Wrong number of args for redis.acl_check_cmd()"),
5346        );
5347    }
5348
5349    #[test]
5350    fn a_counter_is_an_integer_and_not_a_string_of_digits() {
5351        let mut f = Fixture::new();
5352        assert_eq!(f.run(&[b"INCR", b"c"]), ":1\r\n");
5353        assert_eq!(f.run(&[b"INCRBY", b"c", b"41"]), ":42\r\n");
5354        assert_eq!(f.run(&[b"DECRBY", b"c", b"2"]), ":40\r\n");
5355        // Read back as a string it is still an integer, written out as digits
5356        // only because somebody asked for them.
5357        assert_eq!(f.run(&[b"GET", b"c"]), "$2\r\n40\r\n");
5358        assert_eq!(f.run(&[b"INCRBYFLOAT", b"c", b"0.5"]), "$4\r\n40.5\r\n");
5359        // A counter that is not a number is the error the store raises and this
5360        // layer only spells, which is the whole point of the split.
5361        f.run(&[b"SET", b"k", b"hello"]);
5362        assert_eq!(
5363            f.run(&[b"INCR", b"k"]),
5364            "-ERR value is not an integer or out of range\r\n"
5365        );
5366        assert_eq!(
5367            f.run(&[b"INCRBYFLOAT", b"c", b"inf"]),
5368            "-ERR increment would produce NaN or Infinity\r\n"
5369        );
5370    }
5371
5372    /// Every one of these was read off a running 8.8. They are the answers a
5373    /// client library's own test suite checks, and the shapes are not
5374    /// guessable: `DIGEST` is hexadecimal in a bulk string, `MSETEX` is an
5375    /// integer, `INCREX` is a pair.
5376    #[test]
5377    fn the_newer_commands_reply_in_the_shapes_a_real_server_sends() {
5378        let mut f = Fixture::new();
5379        assert_eq!(f.run(&[b"SET", b"k", b"hello"]), "+OK\r\n");
5380        // The same digest a real 8.8 answers for the same five bytes, which is
5381        // what makes `IFDEQ` usable against a mixed deployment.
5382        assert_eq!(f.run(&[b"DIGEST", b"k"]), "$16\r\n9555e8555c62dcfd\r\n");
5383        assert_eq!(f.run(&[b"DIGEST", b"nosuch"]), "$-1\r\n");
5384        assert_eq!(f.run(&[b"MSETEX", b"1", b"a", b"1"]), ":1\r\n");
5385        assert_eq!(f.run(&[b"MSETEX", b"1", b"a", b"2", b"NX"]), ":0\r\n");
5386        assert_eq!(f.run(&[b"GET", b"a"]), "$1\r\n1\r\n");
5387        assert_eq!(f.run(&[b"INCREX", b"n"]), "*2\r\n:1\r\n:1\r\n");
5388        assert_eq!(
5389            f.run(&[b"INCREX", b"n", b"BYINT", b"5", b"UBOUND", b"3"]),
5390            "*2\r\n:1\r\n:0\r\n",
5391            "a refused increment reports the value it left alone and applied nothing"
5392        );
5393        assert_eq!(
5394            f.run(&[
5395                b"INCREX",
5396                b"n",
5397                b"BYINT",
5398                b"5",
5399                b"UBOUND",
5400                b"3",
5401                b"SATURATE"
5402            ]),
5403            "*2\r\n:3\r\n:2\r\n"
5404        );
5405        assert_eq!(f.run(&[b"DELEX", b"a", b"IFEQ", b"2"]), ":0\r\n");
5406        assert_eq!(f.run(&[b"DELEX", b"a", b"IFEQ", b"1"]), ":1\r\n");
5407    }
5408
5409    #[test]
5410    fn the_same_answers_come_out_in_resp3_spelling() {
5411        let mut f = Fixture::new();
5412        assert!(f.run(&[b"HELLO", b"3"]).starts_with("%7\r\n"));
5413        assert_eq!(f.run(&[b"GET", b"nosuch"]), "_\r\n");
5414        // A float counter is a double on RESP3 and the digits in a bulk string
5415        // on RESP2, and `INCRBYFLOAT` is a bulk string on both.
5416        assert_eq!(
5417            f.run(&[b"INCREX", b"c", b"BYFLOAT", b"1.5"]),
5418            "*2\r\n,1.5\r\n,1.5\r\n"
5419        );
5420        assert_eq!(f.run(&[b"INCRBYFLOAT", b"f", b"2.5"]), "$3\r\n2.5\r\n");
5421        // `RESET` puts the protocol back, which is the part that is easy to
5422        // miss and leaves a pooled connection speaking the wrong one.
5423        assert_eq!(f.run(&[b"RESET"]), "+RESET\r\n");
5424        assert_eq!(f.run(&[b"GET", b"nosuch"]), "$-1\r\n");
5425    }
5426
5427    #[test]
5428    fn a_command_nobody_has_heard_of_is_an_error_and_not_a_closed_socket() {
5429        let mut f = Fixture::new();
5430        let (flow, reply) = f.flow(&[b"NOPE", b"a", b"b"]);
5431        assert_eq!(flow, Flow::Continue);
5432        assert_eq!(
5433            reply,
5434            "-ERR unknown command 'NOPE', with args beginning with: 'a' 'b' \r\n"
5435        );
5436        // A name with a line ending in it cannot write its own frame into the
5437        // stream, which is the reason the error writer maps them to spaces.
5438        let reply = f.run(&[b"NO\r\n+PONG\r\nPE"]);
5439        assert_eq!(reply.matches("\r\n").count(), 1);
5440    }
5441
5442    #[test]
5443    fn arity_is_checked_before_the_command_is() {
5444        let mut f = Fixture::new();
5445        assert_eq!(
5446            f.run(&[b"GET"]),
5447            "-ERR wrong number of arguments for 'get' command\r\n"
5448        );
5449        assert_eq!(
5450            f.run(&[b"MSET", b"k"]),
5451            "-ERR wrong number of arguments for 'mset' command\r\n"
5452        );
5453        // The table says `PING` takes one or more and a real server then
5454        // refuses three, which is the sort of thing that only shows up against
5455        // the real thing.
5456        assert_eq!(
5457            f.run(&[b"PING", b"a", b"b"]),
5458            "-ERR wrong number of arguments for 'ping' command\r\n"
5459        );
5460        assert_eq!(f.run(&[b"PING"]), "+PONG\r\n");
5461        assert_eq!(f.run(&[b"PING", b"hi"]), "$2\r\nhi\r\n");
5462        // `DELEX` takes two or four and nothing between.
5463        assert_eq!(
5464            f.run(&[b"DELEX", b"k", b"IFEQ"]),
5465            "-ERR wrong number of arguments for 'delex' command\r\n"
5466        );
5467    }
5468
5469    /// The option rules, all of them measured against 8.8 rather than read off
5470    /// the documentation. The surprising one is that `SET` accepts the same
5471    /// keyword twice and `INCREX` does not.
5472    #[test]
5473    fn the_option_combinations_are_the_ones_a_real_server_accepts() {
5474        let mut f = Fixture::new();
5475        let syntax = "-ERR syntax error\r\n";
5476        assert_eq!(f.run(&[b"SET", b"k", b"v", b"NX", b"XX"]), syntax);
5477        assert_eq!(f.run(&[b"SET", b"k", b"v", b"NX", b"IFEQ", b"a"]), syntax);
5478        assert_eq!(
5479            f.run(&[b"SET", b"k", b"v", b"KEEPTTL", b"EX", b"5"]),
5480            syntax
5481        );
5482        assert_eq!(
5483            f.run(&[b"SET", b"k", b"v", b"EX", b"5", b"PX", b"5"]),
5484            syntax
5485        );
5486        assert_eq!(f.run(&[b"SET", b"k", b"v", b"PERSIST"]), syntax);
5487        // Twice is fine, and the last one wins.
5488        assert_eq!(
5489            f.run(&[b"SET", b"k", b"v", b"EX", b"5", b"EX", b"100"]),
5490            "+OK\r\n"
5491        );
5492        assert_eq!(f.run(&[b"SET", b"k", b"v", b"XX", b"XX"]), "+OK\r\n");
5493        assert_eq!(f.run(&[b"SET", b"k", b"v", b"GET", b"GET"]), "$1\r\nv\r\n");
5494        // `INCREX` refuses what `SET` allows.
5495        assert_eq!(
5496            f.run(&[b"INCREX", b"n", b"BYINT", b"1", b"BYINT", b"2"]),
5497            syntax
5498        );
5499        assert_eq!(
5500            f.run(&[b"INCREX", b"n", b"ENX"]),
5501            "-ERR ENX flag requires an expiration\r\n"
5502        );
5503        assert_eq!(
5504            f.run(&[b"INCREX", b"n", b"UBOUND", b"abc"]),
5505            "-ERR UBOUND is not an integer or out of range\r\n"
5506        );
5507        assert_eq!(
5508            f.run(&[b"INCREX", b"n", b"LBOUND", b"10", b"UBOUND", b"5"]),
5509            "-ERR LBOUND can't be greater than UBOUND\r\n"
5510        );
5511        assert_eq!(
5512            f.run(&[b"LCS", b"a", b"b", b"LEN", b"IDX"]),
5513            "-ERR If you want both the length and indexes, please just use IDX.\r\n"
5514        );
5515    }
5516
5517    /// Where the expiration rules bite. The one worth the test is `GETEX` on a
5518    /// key that is not there, which answers null without ever looking at the
5519    /// expiration it was given.
5520    #[test]
5521    fn the_expiry_rules_are_redis_own() {
5522        let mut f = Fixture::new();
5523        let bad = "-ERR invalid expire time in 'set' command\r\n";
5524        assert_eq!(f.run(&[b"SET", b"k", b"v", b"EX", b"0"]), bad);
5525        assert_eq!(f.run(&[b"SET", b"k", b"v", b"EX", b"-1"]), bad);
5526        assert_eq!(f.run(&[b"SET", b"k", b"v", b"EXAT", b"0"]), bad);
5527        assert_eq!(
5528            f.run(&[b"SET", b"k", b"v", b"EX", b"9999999999999999"]),
5529            bad
5530        );
5531        assert_eq!(
5532            f.run(&[b"SET", b"k", b"v", b"PX", b"99999999999999999999"]),
5533            "-ERR value is not an integer or out of range\r\n"
5534        );
5535        assert_eq!(
5536            f.run(&[b"SETEX", b"k", b"0", b"v"]),
5537            "-ERR invalid expire time in 'setex' command\r\n"
5538        );
5539        assert_eq!(f.run(&[b"GETEX", b"nosuch", b"EX", b"0"]), "$-1\r\n");
5540        assert_eq!(f.run(&[b"GETEX", b"nosuch", b"EX", b"abc"]), "$-1\r\n");
5541        assert_eq!(
5542            f.run(&[b"GETEX", b"nosuch", b"KEEPTTL"]),
5543            "-ERR syntax error\r\n",
5544            "the option list is still checked before the key is looked up"
5545        );
5546        // A deadline in the past is accepted and the key goes with it.
5547        assert_eq!(f.run(&[b"SET", b"k", b"v"]), "+OK\r\n");
5548        assert_eq!(f.run(&[b"SET", b"k", b"v", b"EXAT", b"1"]), "+OK\r\n");
5549        assert_eq!(f.run(&[b"GET", b"k"]), "$-1\r\n");
5550    }
5551
5552    #[test]
5553    fn mset_takes_its_pairs_from_the_read_buffer() {
5554        let mut f = Fixture::new();
5555        assert_eq!(f.run(&[b"MSET", b"a", b"1", b"b", b"2"]), "+OK\r\n");
5556        assert_eq!(
5557            f.run(&[b"MGET", b"a", b"b", b"nosuch"]),
5558            "*3\r\n$1\r\n1\r\n$1\r\n2\r\n$-1\r\n"
5559        );
5560        assert_eq!(f.run(&[b"MSETNX", b"b", b"9", b"c", b"3"]), ":0\r\n");
5561        assert_eq!(f.run(&[b"MSETNX", b"c", b"3", b"d", b"4"]), ":1\r\n");
5562        assert_eq!(
5563            f.run(&[b"MSETEX", b"2", b"e", b"5"]),
5564            "-ERR wrong number of key-value pairs\r\n"
5565        );
5566        assert_eq!(
5567            f.run(&[b"MSETEX", b"0", b"e", b"5"]),
5568            "-ERR invalid numkeys value\r\n"
5569        );
5570        assert_eq!(
5571            f.run(&[b"MSETEX", b"abc", b"e", b"5"]),
5572            "-ERR invalid numkeys value\r\n"
5573        );
5574    }
5575
5576    #[test]
5577    fn lcs_answers_the_length_the_string_and_the_runs() {
5578        let mut f = Fixture::new();
5579        f.run(&[b"MSET", b"a", b"ohmytext", b"b", b"mynewtext"]);
5580        assert_eq!(f.run(&[b"LCS", b"a", b"b"]), "$6\r\nmytext\r\n");
5581        assert_eq!(f.run(&[b"LCS", b"a", b"b", b"LEN"]), ":6\r\n");
5582        assert_eq!(
5583            f.run(&[b"LCS", b"a", b"b", b"IDX", b"MINMATCHLEN", b"4"]),
5584            "*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"
5585        );
5586        // Without `IDX` the two options that only mean something with it are
5587        // accepted and ignored, which is what a real server does.
5588        assert_eq!(
5589            f.run(&[b"LCS", b"a", b"b", b"MINMATCHLEN", b"4", b"WITHMATCHLEN"]),
5590            "$6\r\nmytext\r\n"
5591        );
5592    }
5593
5594    #[test]
5595    fn select_moves_the_connection_and_the_databases_stay_apart() {
5596        let mut f = Fixture::new();
5597        f.run(&[b"SET", b"k", b"zero"]);
5598        assert_eq!(f.run(&[b"SELECT", b"4"]), "+OK\r\n");
5599        assert_eq!(f.run(&[b"GET", b"k"]), "$-1\r\n");
5600        f.run(&[b"SET", b"k", b"four"]);
5601        assert_eq!(f.run(&[b"SELECT", b"0"]), "+OK\r\n");
5602        assert_eq!(f.run(&[b"GET", b"k"]), "$4\r\nzero\r\n");
5603        assert_eq!(
5604            f.run(&[b"SELECT", b"99"]),
5605            "-ERR DB index is out of range\r\n"
5606        );
5607        assert_eq!(
5608            f.run(&[b"SELECT", b"-1"]),
5609            "-ERR DB index is out of range\r\n"
5610        );
5611        assert_eq!(
5612            f.run(&[b"SELECT", b"abc"]),
5613            "-ERR value is not an integer or out of range\r\n"
5614        );
5615        // `RESET` brings it back to zero.
5616        f.run(&[b"SELECT", b"4"]);
5617        f.run(&[b"RESET"]);
5618        assert_eq!(f.run(&[b"GET", b"k"]), "$4\r\nzero\r\n");
5619    }
5620
5621    #[test]
5622    fn hello_agrees_on_a_protocol_and_refuses_the_ones_that_do_not_exist() {
5623        let mut f = Fixture::new();
5624        let reply = f.run(&[b"HELLO"]);
5625        assert!(reply.starts_with("*14\r\n"), "{reply}");
5626        assert!(reply.contains("$5\r\nredis\r\n"), "{reply}");
5627        assert!(reply.contains("$5\r\n8.8.0\r\n"), "{reply}");
5628        assert!(
5629            reply.contains(":7\r\n"),
5630            "the connection id is in there: {reply}"
5631        );
5632        assert_eq!(
5633            f.run(&[b"HELLO", b"4"]),
5634            "-NOPROTO unsupported protocol version\r\n"
5635        );
5636        assert_eq!(
5637            f.run(&[b"HELLO", b"abc"]),
5638            "-ERR Protocol version is not an integer or out of range\r\n"
5639        );
5640        assert_eq!(
5641            f.run(&[b"HELLO", b"3", b"SETNAME"]),
5642            "-ERR Syntax error in HELLO option 'SETNAME'\r\n"
5643        );
5644        assert!(
5645            f.run(&[b"HELLO", b"3", b"SETNAME", b"bob"])
5646                .starts_with("%7\r\n")
5647        );
5648        assert_eq!(f.session.name(), b"bob");
5649        f.run(&[b"RESET"]);
5650        assert_eq!(f.session.name(), b"");
5651    }
5652
5653    #[test]
5654    fn command_describes_this_server_in_the_shape_a_driver_reads() {
5655        let mut f = Fixture::new();
5656        let count = format!(":{}\r\n", COMMANDS.len());
5657        assert_eq!(f.run(&[b"COMMAND", b"COUNT"]), count);
5658        let info = f.run(&[b"COMMAND", b"INFO", b"get"]);
5659        assert_eq!(
5660            info,
5661            "*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\
5662             *3\r\n+@read\r\n+@string\r\n+@fast\r\n*0\r\n*0\r\n*0\r\n"
5663        );
5664        // A null in the list, and the plain one: `$-1` and not `*-1`.
5665        assert_eq!(f.run(&[b"COMMAND", b"INFO", b"nosuch"]), "*1\r\n$-1\r\n");
5666        assert_eq!(
5667            f.run(&[b"COMMAND", b"LIST", b"FILTERBY", b"PATTERN", b"getr*"]),
5668            "*1\r\n$8\r\ngetrange\r\n"
5669        );
5670        assert_eq!(
5671            f.run(&[b"COMMAND", b"NOPE"]),
5672            "-ERR unknown subcommand 'NOPE'. Try COMMAND HELP.\r\n"
5673        );
5674    }
5675
5676    /// A cluster aware client asks this question and then routes on the
5677    /// answer, so `MSETEX`, whose keys are not where the table says, is the one
5678    /// that matters.
5679    #[test]
5680    fn command_getkeys_finds_the_keys_including_the_hidden_ones() {
5681        let mut f = Fixture::new();
5682        assert_eq!(
5683            f.run(&[b"COMMAND", b"GETKEYS", b"get", b"k"]),
5684            "*1\r\n$1\r\nk\r\n"
5685        );
5686        assert_eq!(
5687            f.run(&[b"COMMAND", b"GETKEYS", b"mset", b"a", b"1", b"b", b"2"]),
5688            "*2\r\n$1\r\na\r\n$1\r\nb\r\n"
5689        );
5690        assert_eq!(
5691            f.run(&[
5692                b"COMMAND", b"GETKEYS", b"msetex", b"2", b"a", b"1", b"b", b"2"
5693            ]),
5694            "*2\r\n$1\r\na\r\n$1\r\nb\r\n"
5695        );
5696        assert_eq!(
5697            f.run(&[b"COMMAND", b"GETKEYS", b"ping"]),
5698            "-ERR The command has no key arguments\r\n"
5699        );
5700        assert_eq!(
5701            f.run(&[b"COMMAND", b"GETKEYS", b"set"]),
5702            "-ERR Invalid number of arguments specified for command\r\n"
5703        );
5704    }
5705
5706    #[test]
5707    fn config_answers_what_it_can_and_refuses_what_it_cannot() {
5708        let mut f = Fixture::new();
5709        assert_eq!(
5710            f.run(&[b"CONFIG", b"GET", b"maxmemory"]),
5711            "*2\r\n$9\r\nmaxmemory\r\n$1\r\n0\r\n"
5712        );
5713        // A pattern matches more than one, and a setting two patterns both ask
5714        // for is still sent once.
5715        let both = f.run(&[b"CONFIG", b"GET", b"maxmemory*", b"maxmemory"]);
5716        assert!(both.starts_with("*6\r\n"), "{both}");
5717        assert_eq!(f.run(&[b"CONFIG", b"GET", b"nosuch"]), "*0\r\n");
5718        assert_eq!(f.run(&[b"CONFIG", b"SET", b"appendonly", b"no"]), "+OK\r\n");
5719        assert_eq!(
5720            f.run(&[b"CONFIG", b"SET", b"appendonly", b"yes"]),
5721            "-ERR CONFIG SET failed (possibly related to argument 'appendonly') - can't set immutable config\r\n"
5722        );
5723        assert_eq!(
5724            f.run(&[b"CONFIG", b"SET", b"nosuch", b"1"]),
5725            "-ERR Unknown option or number of arguments for CONFIG SET - 'nosuch'\r\n"
5726        );
5727        assert_eq!(
5728            f.run(&[b"CONFIG", b"GET"]),
5729            "-ERR wrong number of arguments for 'config|get' command\r\n"
5730        );
5731        // Too few arguments and an odd number of them are different
5732        // complaints, which is the sort of thing only the real server tells
5733        // you.
5734        assert_eq!(
5735            f.run(&[b"CONFIG", b"SET", b"appendonly"]),
5736            "-ERR wrong number of arguments for 'config|set' command\r\n"
5737        );
5738        assert_eq!(
5739            f.run(&[b"CONFIG", b"SET", b"appendonly", b"no", b"maxmemory"]),
5740            "-ERR syntax error\r\n"
5741        );
5742        assert_eq!(f.run(&[b"CONFIG", b"RESETSTAT"]), "+OK\r\n");
5743        assert_eq!(
5744            f.run(&[b"CONFIG", b"REWRITE"]),
5745            "-ERR The server is running without a config file\r\n"
5746        );
5747    }
5748
5749    #[test]
5750    fn the_eviction_policy_reads_back_what_was_written_to_it() {
5751        let mut f = Fixture::new();
5752        assert_eq!(
5753            f.run(&[b"CONFIG", b"GET", b"maxmemory-policy"]),
5754            "*2\r\n$16\r\nmaxmemory-policy\r\n$10\r\nnoeviction\r\n"
5755        );
5756        assert_eq!(
5757            f.run(&[b"CONFIG", b"SET", b"maxmemory-policy", b"AllKeys-LFU"]),
5758            "+OK\r\n",
5759            "the name is matched without regard to case, like every other one"
5760        );
5761        assert_eq!(
5762            f.run(&[b"CONFIG", b"GET", b"maxmemory-policy"]),
5763            "*2\r\n$16\r\nmaxmemory-policy\r\n$11\r\nallkeys-lfu\r\n"
5764        );
5765        // And INFO agrees with CONFIG, which it did not when it was a literal.
5766        assert!(
5767            f.run(&[b"INFO", b"memory"])
5768                .contains("maxmemory_policy:allkeys-lfu"),
5769            "INFO and CONFIG disagree about the policy"
5770        );
5771        // The refusal names every legal value in the order the real server's
5772        // enum table lists them, because a client comparing the message compares
5773        // the whole string.
5774        assert_eq!(
5775            f.run(&[b"CONFIG", b"SET", b"maxmemory-policy", b"garbage"]),
5776            "-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"
5777        );
5778        // A bad pair leaves the good one in the same command alone, and the
5779        // policy is checked by the same pass that checks the numbers.
5780        assert_eq!(
5781            f.run(&[b"CONFIG", b"GET", b"maxmemory-policy"]),
5782            "*2\r\n$16\r\nmaxmemory-policy\r\n$11\r\nallkeys-lfu\r\n"
5783        );
5784        f.run(&[
5785            b"CONFIG",
5786            b"SET",
5787            b"hash-max-listpack-entries",
5788            b"7",
5789            b"maxmemory-policy",
5790            b"nonsense",
5791        ]);
5792        assert_eq!(
5793            f.run(&[b"CONFIG", b"GET", b"hash-max-listpack-entries"]),
5794            "*2\r\n$25\r\nhash-max-listpack-entries\r\n$3\r\n512\r\n"
5795        );
5796    }
5797
5798    #[test]
5799    fn the_three_eviction_numbers_read_back_too() {
5800        let mut f = Fixture::new();
5801        for (name, default, set) in [
5802            ("maxmemory-samples", "5", "12"),
5803            ("lfu-log-factor", "10", "3"),
5804            ("lfu-decay-time", "1", "60"),
5805        ] {
5806            let get = || {
5807                format!(
5808                    "*2\r\n${}\r\n{name}\r\n${}\r\n{default}\r\n",
5809                    name.len(),
5810                    default.len()
5811                )
5812            };
5813            assert_eq!(f.run(&[b"CONFIG", b"GET", name.as_bytes()]), get());
5814            assert_eq!(
5815                f.run(&[b"CONFIG", b"SET", name.as_bytes(), set.as_bytes()]),
5816                "+OK\r\n"
5817            );
5818            assert_eq!(
5819                f.run(&[b"CONFIG", b"GET", name.as_bytes()]),
5820                format!(
5821                    "*2\r\n${}\r\n{name}\r\n${}\r\n{set}\r\n",
5822                    name.len(),
5823                    set.len()
5824                )
5825            );
5826            // A number that is not a number is refused with the same sentence
5827            // every other number gets, which names the setting the client typed.
5828            assert_eq!(
5829                f.run(&[b"CONFIG", b"SET", name.as_bytes(), b"soon"]),
5830                format!(
5831                    "-ERR CONFIG SET failed (possibly related to argument '{name}') - argument couldn't be parsed into an integer\r\n"
5832                )
5833            );
5834        }
5835    }
5836
5837    #[test]
5838    fn the_memory_limit_reads_back_in_bytes_whatever_the_unit_was() {
5839        let mut f = Fixture::new();
5840        assert_eq!(
5841            f.run(&[b"CONFIG", b"GET", b"maxmemory"]),
5842            "*2\r\n$9\r\nmaxmemory\r\n$1\r\n0\r\n",
5843            "no limit is the default"
5844        );
5845        // The pairing is Redis's and it is a trap: the bare letter is a power of
5846        // ten and the one with the b is a power of two.
5847        for (typed, bytes) in [
5848            (&b"1024"[..], "1024"),
5849            (b"1k", "1000"),
5850            (b"1kb", "1024"),
5851            (b"1M", "1000000"),
5852            (b"1Mb", "1048576"),
5853            (b"1gb", "1073741824"),
5854            (b"100mb", "104857600"),
5855        ] {
5856            assert_eq!(f.run(&[b"CONFIG", b"SET", b"maxmemory", typed]), "+OK\r\n");
5857            assert_eq!(
5858                f.run(&[b"CONFIG", b"GET", b"maxmemory"]),
5859                format!("*2\r\n$9\r\nmaxmemory\r\n${}\r\n{bytes}\r\n", bytes.len()),
5860                "set {}",
5861                String::from_utf8_lossy(typed)
5862            );
5863        }
5864        assert!(
5865            f.run(&[b"INFO", b"memory"]).contains("maxmemory:104857600"),
5866            "the report agrees with the setting"
5867        );
5868
5869        // A unit nobody has heard of, and a negative number, which is not a very
5870        // large one however it is spelled.
5871        for bad in [&b"1tb"[..], b"-1", b"", b"lots"] {
5872            assert_eq!(
5873                f.run(&[b"CONFIG", b"SET", b"maxmemory", bad]),
5874                "-ERR CONFIG SET failed (possibly related to argument 'maxmemory') - argument must be a memory value\r\n",
5875                "refused {}",
5876                String::from_utf8_lossy(bad)
5877            );
5878        }
5879        assert!(
5880            f.run(&[b"INFO", b"memory"]).contains("maxmemory:104857600"),
5881            "and the refusal left the old one alone"
5882        );
5883    }
5884
5885    #[test]
5886    fn a_write_is_refused_when_there_is_no_room_and_nothing_to_evict() {
5887        let mut f = Fixture::new();
5888        f.run(&[b"SET", b"here", b"already"]);
5889        // A byte, which is under what an empty server holds, so nothing this
5890        // command could do would get it under. The default policy is
5891        // `noeviction`, so nothing is what it does.
5892        f.run(&[b"CONFIG", b"SET", b"maxmemory", b"1"]);
5893        assert_eq!(
5894            f.run(&[b"SET", b"k", b"v"]),
5895            "-OOM command not allowed when used memory > 'maxmemory'.\r\n"
5896        );
5897        assert_eq!(
5898            f.run(&[b"LPUSH", b"l", b"v"]),
5899            "-OOM command not allowed when used memory > 'maxmemory'.\r\n"
5900        );
5901        // Reading is allowed, and so is the one thing that would help.
5902        assert_eq!(f.run(&[b"GET", b"here"]), "$7\r\nalready\r\n");
5903        assert_eq!(f.run(&[b"DEL", b"here"]), ":1\r\n");
5904        assert!(f.run(&[b"INFO", b"stats"]).contains("evicted_keys:0"));
5905
5906        // Taking the limit away lets the write through again.
5907        f.run(&[b"CONFIG", b"SET", b"maxmemory", b"0"]);
5908        assert_eq!(f.run(&[b"SET", b"k", b"v"]), "+OK\r\n");
5909    }
5910
5911    /// Not under Miri, for the reason in `filled`: what it is watching is a
5912    /// whole two megabyte segment going back, so the megabytes are the claim
5913    /// and there is no smaller version of it that says the same thing.
5914    #[cfg_attr(miri, ignore = "several megabytes of arena, see `filled`")]
5915    #[test]
5916    fn an_allkeys_policy_makes_room_instead_of_refusing() {
5917        let mut f = Fixture::new();
5918        let val = vec![b'v'; 256];
5919        for i in 0..24000u32 {
5920            let k = format!("key:{i:08}");
5921            f.run(&[b"SET", k.as_bytes(), &val]);
5922        }
5923        let full = f.server.memory_bytes();
5924        assert!(
5925            full > 3 * 1024 * 1024,
5926            "the arena is several segments: {full}"
5927        );
5928
5929        // Two megabytes under what it is holding, which is one segment's worth,
5930        // so getting there means giving a whole segment back and not just
5931        // dropping a few records.
5932        let limit = full - 2 * 1024 * 1024;
5933        f.run(&[b"CONFIG", b"SET", b"maxmemory-policy", b"allkeys-lru"]);
5934        f.run(&[
5935            b"CONFIG",
5936            b"SET",
5937            b"maxmemory",
5938            limit.to_string().as_bytes(),
5939        ]);
5940
5941        // Writes keep working the whole way down. The budget means one command
5942        // does not do it all, so this runs until the server has settled and
5943        // checks that nothing was refused on the way.
5944        for i in 0..2000u32 {
5945            let k = format!("new:{i:08}");
5946            assert_eq!(
5947                f.run(&[b"SET", k.as_bytes(), &val]),
5948                "+OK\r\n",
5949                "write {i} was refused"
5950            );
5951            f.server.refresh_memory();
5952            if f.server.memory_bytes() <= limit {
5953                break;
5954            }
5955        }
5956        assert!(
5957            f.server.memory_bytes() <= limit,
5958            "it never got under: {} against {limit}",
5959            f.server.memory_bytes()
5960        );
5961        let info = f.run(&[b"INFO", b"stats"]);
5962        assert!(!info.contains("evicted_keys:0"), "{info}");
5963        assert!(
5964            f.run(&[b"DBSIZE"]) != ":0\r\n",
5965            "and it did not empty the database to get there"
5966        );
5967    }
5968
5969    /// Not under Miri. Every round is eleven commands over six collections
5970    /// holding two hundred byte values, which is a third of a second each
5971    /// interpreted, and the rounds cannot come down far: one in seven takes an
5972    /// entry back out, so under about a hundred and seventy of them the
5973    /// collections never reach the hundred and twenty eight entries where the
5974    /// small representations give up and become the big ones, and a
5975    /// representation changing under the running total is one of the five
5976    /// things this is here to watch. What is left is an hour, for an accounting
5977    /// claim rather than a safety one, and the commands it sends are sent a few
5978    /// at a time by the tests around it.
5979    #[cfg_attr(miri, ignore = "an hour of commands, and they cannot come down")]
5980    #[test]
5981    fn the_running_total_and_the_walk_agree_on_a_mixed_keyspace() {
5982        // The limit is judged against a number kept as the collections move,
5983        // rather than found by asking all of them, and the two have to be the
5984        // same number or the limit is enforced against a fiction. This does the
5985        // things that move it, which is growing a collection, shrinking one,
5986        // changing its representation, deleting it and reusing its slot, across
5987        // all five types, and checks the two against each other as it goes.
5988        let mut f = Fixture::new();
5989        f.run(&[b"CONFIG", b"SET", b"maxmemory", b"1gb"]);
5990        let big = vec![b'v'; 200];
5991
5992        for i in 0..400u32 {
5993            let n = i.to_string();
5994            let n = n.as_bytes();
5995            f.run(&[b"SADD", b"s", n]);
5996            f.run(&[b"SADD", b"s2", &big]);
5997            f.run(&[b"HSET", b"h", n, &big]);
5998            f.run(&[b"RPUSH", b"l", &big]);
5999            f.run(&[b"ZADD", b"z", n, n]);
6000            f.run(&[b"ARSET", b"a", n, &big]);
6001            if i % 7 == 0 {
6002                f.run(&[b"SREM", b"s", n]);
6003                f.run(&[b"HDEL", b"h", n]);
6004                f.run(&[b"LPOP", b"l"]);
6005                f.run(&[b"ZREM", b"z", n]);
6006                f.run(&[b"ARDEL", b"a", n]);
6007            }
6008            if i % 53 == 0 {
6009                // Every type deleted and made again, so a slot goes on the free
6010                // list and comes back holding something else.
6011                f.run(&[b"DEL", b"s2"]);
6012            }
6013            assert_eq!(
6014                f.server.settled_memory(),
6015                f.server.memory_bytes(),
6016                "after round {i}"
6017            );
6018        }
6019
6020        // The run has to have built something, or the two numbers agreeing is
6021        // two zeroes agreeing.
6022        assert_eq!(f.run(&[b"DBSIZE"]), ":6\r\n");
6023        assert!(
6024            f.server.memory_bytes() > 512 * 1024,
6025            "{}",
6026            f.server.memory_bytes()
6027        );
6028
6029        // And it survives the collections going away entirely.
6030        f.run(&[b"FLUSHALL"]);
6031        assert_eq!(f.server.settled_memory(), f.server.memory_bytes());
6032    }
6033
6034    #[test]
6035    fn taking_the_limit_away_stops_the_counting_and_putting_it_back_starts_again() {
6036        // A server with no limit does not keep the running total, so setting a
6037        // limit on a database that is already full has to start it from a walk.
6038        // If it did not, the first reading would be zero and the server would
6039        // think it had all the room in the world.
6040        let mut f = Fixture::new();
6041        for i in 0..200u32 {
6042            let n = i.to_string();
6043            f.run(&[b"SADD", b"s", n.as_bytes()]);
6044            f.run(&[b"HSET", b"h", n.as_bytes(), b"value"]);
6045        }
6046        f.run(&[b"CONFIG", b"SET", b"maxmemory", b"1gb"]);
6047        assert_eq!(f.server.settled_memory(), f.server.memory_bytes());
6048
6049        f.run(&[b"CONFIG", b"SET", b"maxmemory", b"0"]);
6050        for i in 200..400u32 {
6051            let n = i.to_string();
6052            f.run(&[b"SADD", b"s", n.as_bytes()]);
6053        }
6054        f.run(&[b"CONFIG", b"SET", b"maxmemory", b"1gb"]);
6055        assert_eq!(
6056            f.server.settled_memory(),
6057            f.server.memory_bytes(),
6058            "the writes it was not watching are in the number it started from"
6059        );
6060    }
6061
6062    #[test]
6063    fn evicted_keys_and_expired_keys_are_different_numbers() {
6064        let mut f = Fixture::new();
6065        // Nothing has been evicted and nothing can be under the default policy,
6066        // so this stays at zero while the other one moves.
6067        f.run(&[b"SET", b"gone", b"v", b"PX", b"1"]);
6068        f.server.advance_clock_ms(20);
6069        f.run(&[b"GET", b"gone"]);
6070        let info = f.run(&[b"INFO", b"stats"]);
6071        assert!(info.contains("expired_keys:1"), "{info}");
6072        assert!(info.contains("evicted_keys:0"), "{info}");
6073    }
6074
6075    #[test]
6076    fn the_object_subcommands_follow_the_policy() {
6077        let mut f = Fixture::new();
6078        f.run(&[b"SET", b"s", b"v"]);
6079        // Under the default the clock is kept and the counter is not, and under
6080        // an LFU policy it is the other way round. Each subcommand refuses on
6081        // the side where its reading of the three bytes means nothing.
6082        assert_eq!(f.run(&[b"OBJECT", b"IDLETIME", b"s"]), ":0\r\n");
6083        assert!(
6084            f.run(&[b"OBJECT", b"FREQ", b"s"])
6085                .starts_with("-ERR An LFU maxmemory policy is not selected"),
6086        );
6087
6088        f.run(&[b"CONFIG", b"SET", b"maxmemory-policy", b"allkeys-lfu"]);
6089        assert!(
6090            f.run(&[b"OBJECT", b"IDLETIME", b"s"])
6091                .starts_with("-ERR An LFU maxmemory policy is selected"),
6092        );
6093        // The key was written under a clock policy, so what comes back is that
6094        // clock read as a counter. It is a number and not an error, which is the
6095        // point: switching at runtime does not invalidate anything, it only makes
6096        // the old field mean something else until the key is used again.
6097        assert!(
6098            f.run(&[b"OBJECT", b"FREQ", b"s"]).starts_with(':'),
6099            "FREQ should answer under an LFU policy"
6100        );
6101    }
6102
6103    #[test]
6104    fn object_says_which_rung_of_the_ladder_a_key_is_on() {
6105        let mut f = Fixture::new();
6106        f.run(&[b"SET", b"s", b"hello"]);
6107        f.run(&[b"SET", b"n", b"123"]);
6108        f.run(&[b"SADD", b"si", b"1", b"2", b"3"]);
6109        f.run(&[b"SADD", b"ss", b"a", b"b"]);
6110        f.run(&[b"HSET", b"h", b"f", b"v"]);
6111        for (key, want) in [
6112            (b"s".as_slice(), "embstr"),
6113            (b"n", "int"),
6114            (b"si", "intset"),
6115            (b"ss", "listpack"),
6116            (b"h", "listpack"),
6117        ] {
6118            let reply = f.run(&[b"OBJECT", b"ENCODING", key]);
6119            assert_eq!(reply, format!("${}\r\n{want}\r\n", want.len()));
6120        }
6121
6122        // A field deadline widens the blob rather than promoting it, and this
6123        // is the only place a client can see that happen.
6124        f.run(&[b"HEXPIRE", b"h", b"100", b"FIELDS", b"1", b"f"]);
6125        assert_eq!(
6126            f.run(&[b"OBJECT", b"ENCODING", b"h"]),
6127            "$10\r\nlistpackex\r\n"
6128        );
6129
6130        assert_eq!(f.run(&[b"OBJECT", b"REFCOUNT", b"s"]), ":1\r\n");
6131        assert_eq!(f.run(&[b"OBJECT", b"IDLETIME", b"s"]), ":0\r\n");
6132        assert!(f.run(&[b"OBJECT", b"HELP"]).starts_with("*14\r\n+OBJECT "));
6133    }
6134
6135    #[test]
6136    fn object_answers_nil_for_a_key_that_is_not_there() {
6137        let mut f = Fixture::new();
6138        for sub in [b"ENCODING".as_slice(), b"REFCOUNT", b"IDLETIME", b"FREQ"] {
6139            assert_eq!(
6140                f.run(&[b"OBJECT", sub, b"nokey"]),
6141                "$-1\r\n",
6142                "a nil and not an error, which is what 8.10.1 does"
6143            );
6144        }
6145        // And the key is looked up before FREQ has its complaint, so the
6146        // complaint only reaches a key that exists.
6147        f.run(&[b"SET", b"s", b"v"]);
6148        assert!(
6149            f.run(&[b"OBJECT", b"FREQ", b"s"])
6150                .starts_with("-ERR An LFU maxmemory policy is not"),
6151        );
6152        assert_eq!(
6153            f.run(&[b"OBJECT", b"NOPE", b"s"]),
6154            "-ERR unknown subcommand 'NOPE'. Try OBJECT HELP.\r\n"
6155        );
6156        assert_eq!(
6157            f.run(&[b"OBJECT", b"ENCODING"]),
6158            "-ERR wrong number of arguments for 'object|encoding' command\r\n"
6159        );
6160        assert_eq!(
6161            f.run(&[b"OBJECT", b"ENCODING", b"s", b"extra"]),
6162            "-ERR wrong number of arguments for 'object|encoding' command\r\n"
6163        );
6164        assert_eq!(
6165            f.run(&[b"OBJECT"]),
6166            "-ERR wrong number of arguments for 'object' command\r\n"
6167        );
6168    }
6169
6170    #[test]
6171    fn config_moves_the_ladder_and_object_encoding_agrees() {
6172        let mut f = Fixture::new();
6173        assert_eq!(
6174            f.run(&[b"CONFIG", b"GET", b"hash-max-listpack-entries"]),
6175            "*2\r\n$25\r\nhash-max-listpack-entries\r\n$3\r\n512\r\n",
6176            "512 and not the 128 everyone remembers, which is what 8.10.1 says"
6177        );
6178        // The old spelling is the same number under a different name, and a
6179        // glob that catches both sends both.
6180        assert_eq!(
6181            f.run(&[b"CONFIG", b"GET", b"hash-max-ziplist-entries"]),
6182            "*2\r\n$24\r\nhash-max-ziplist-entries\r\n$3\r\n512\r\n"
6183        );
6184        assert!(
6185            f.run(&[b"CONFIG", b"GET", b"hash-max-*"])
6186                .starts_with("*8\r\n")
6187        );
6188        assert!(
6189            f.run(&[b"CONFIG", b"GET", b"set-max-*"])
6190                .starts_with("*6\r\n")
6191        );
6192
6193        f.run(&[b"HSET", b"h", b"a", b"1", b"b", b"2", b"c", b"3"]);
6194        assert_eq!(f.run(&[b"OBJECT", b"ENCODING", b"h"]), "$8\r\nlistpack\r\n");
6195
6196        assert_eq!(
6197            f.run(&[b"CONFIG", b"SET", b"hash-max-ziplist-entries", b"2"]),
6198            "+OK\r\n",
6199            "written under the old name and read back under the new one"
6200        );
6201        assert_eq!(
6202            f.run(&[b"CONFIG", b"GET", b"hash-max-listpack-entries"]),
6203            "*2\r\n$25\r\nhash-max-listpack-entries\r\n$1\r\n2\r\n"
6204        );
6205        assert_eq!(
6206            f.run(&[b"OBJECT", b"ENCODING", b"h"]),
6207            "$8\r\nlistpack\r\n",
6208            "the hash that already exists is left exactly where it was"
6209        );
6210        f.run(&[b"HSET", b"h2", b"a", b"1", b"b", b"2", b"c", b"3"]);
6211        assert_eq!(
6212            f.run(&[b"OBJECT", b"ENCODING", b"h2"]),
6213            "$9\r\nhashtable\r\n",
6214            "and the next one built goes straight to a table"
6215        );
6216
6217        // The set has three of these and all three move.
6218        f.run(&[b"CONFIG", b"SET", b"set-max-intset-entries", b"2"]);
6219        f.run(&[b"SADD", b"s", b"1", b"2", b"3"]);
6220        assert_eq!(f.run(&[b"OBJECT", b"ENCODING", b"s"]), "$8\r\nlistpack\r\n");
6221        f.run(&[b"CONFIG", b"SET", b"set-max-listpack-value", b"2"]);
6222        f.run(&[b"SADD", b"s2", b"abcdefgh"]);
6223        assert_eq!(
6224            f.run(&[b"OBJECT", b"ENCODING", b"s2"]),
6225            "$9\r\nhashtable\r\n"
6226        );
6227    }
6228
6229    #[test]
6230    fn config_set_takes_all_of_the_ladder_or_none_of_it() {
6231        let mut f = Fixture::new();
6232        assert_eq!(
6233            f.run(&[
6234                b"CONFIG",
6235                b"SET",
6236                b"hash-max-listpack-entries",
6237                b"7",
6238                b"set-max-listpack-entries",
6239                b"abc"
6240            ]),
6241            "-ERR CONFIG SET failed (possibly related to argument 'set-max-listpack-entries') - argument couldn't be parsed into an integer\r\n"
6242        );
6243        assert_eq!(
6244            f.run(&[b"CONFIG", b"GET", b"hash-max-listpack-entries"]),
6245            "*2\r\n$25\r\nhash-max-listpack-entries\r\n$3\r\n512\r\n",
6246            "the pair in front of the bad one did not go in"
6247        );
6248        // The name in the complaint is the one that was typed, so the old
6249        // spelling comes back as the old spelling.
6250        assert_eq!(
6251            f.run(&[b"CONFIG", b"SET", b"hash-max-ziplist-entries", b"abc"]),
6252            "-ERR CONFIG SET failed (possibly related to argument 'hash-max-ziplist-entries') - argument couldn't be parsed into an integer\r\n"
6253        );
6254        assert_eq!(
6255            f.run(&[b"CONFIG", b"SET", b"set-max-intset-entries", b"-1"]),
6256            "-ERR CONFIG SET failed (possibly related to argument 'set-max-intset-entries') - argument must be between 0 and 9223372036854775807 inclusive\r\n"
6257        );
6258        // A number past what an i64 holds is the parse complaint and not the
6259        // range one, which is upstream reading it before it checks it.
6260        assert_eq!(
6261            f.run(&[
6262                b"CONFIG",
6263                b"SET",
6264                b"set-max-intset-entries",
6265                b"99999999999999999999"
6266            ]),
6267            "-ERR CONFIG SET failed (possibly related to argument 'set-max-intset-entries') - argument couldn't be parsed into an integer\r\n"
6268        );
6269        assert_eq!(
6270            f.run(&[
6271                b"CONFIG",
6272                b"SET",
6273                b"set-max-intset-entries",
6274                b"9223372036854775807"
6275            ]),
6276            "+OK\r\n"
6277        );
6278    }
6279
6280    #[test]
6281    fn a_setting_moved_on_one_database_moved_on_all_of_them() {
6282        let mut f = Fixture::new();
6283        f.run(&[b"CONFIG", b"SET", b"hash-max-listpack-entries", b"1"]);
6284        f.run(&[b"SELECT", b"3"]);
6285        f.run(&[b"HSET", b"h", b"a", b"1", b"b", b"2"]);
6286        assert_eq!(
6287            f.run(&[b"OBJECT", b"ENCODING", b"h"]),
6288            "$9\r\nhashtable\r\n",
6289            "these are one server wide number in Redis, whatever a Keyspace carries"
6290        );
6291    }
6292
6293    #[test]
6294    fn info_reports_the_numbers_it_can_stand_behind() {
6295        let mut f = Fixture::new();
6296        f.run(&[b"MSET", b"a", b"1", b"b", b"2"]);
6297        let all = f.run(&[b"INFO"]);
6298        assert!(all.contains("redis_version:8.8.0"), "{all}");
6299        assert!(
6300            all.contains(concat!("yo_version:", env!("CARGO_PKG_VERSION"))),
6301            "{all}"
6302        );
6303        assert!(all.contains("db0:keys=2,expires=0,avg_ttl=0"), "{all}");
6304        assert!(all.contains("role:master"), "{all}");
6305        // One section is one section.
6306        let clients = f.run(&[b"INFO", b"clients"]);
6307        assert!(clients.contains("connected_clients:0"), "{clients}");
6308        assert!(!clients.contains("redis_version"), "{clients}");
6309        assert_eq!(f.run(&[b"INFO", b"nosuch"]), "$0\r\n\r\n");
6310    }
6311
6312    /// The sections a bare `INFO` gives back, and the ones you have to ask for.
6313    ///
6314    /// This is Redis's `unit/info-command` written against the fixture. Every
6315    /// assertion in it is one of theirs, in their order, and the two fields it
6316    /// turns on are the two that suite was failing on: `master_repl_offset`,
6317    /// which is in the default set, and `rejected_calls`, which is not.
6318    #[test]
6319    fn commandstats_is_asked_for_and_replication_is_not() {
6320        let mut f = Fixture::new();
6321        for arg in ["", "all", "default", "everything"] {
6322            let info = if arg.is_empty() {
6323                f.run(&[b"INFO"])
6324            } else {
6325                f.run(&[b"INFO", arg.as_bytes()])
6326            };
6327            assert!(info.contains("redis_version"), "{arg}: {info}");
6328            assert!(info.contains("used_cpu_user"), "{arg}: {info}");
6329            assert!(info.contains("used_memory"), "{arg}: {info}");
6330            assert!(!info.contains("sentinel_tilt"), "{arg}: {info}");
6331            let asked = arg == "all" || arg == "everything";
6332            assert_eq!(
6333                info.contains("rejected_calls"),
6334                asked,
6335                "{arg} should{} carry the command counters: {info}",
6336                if asked { "" } else { " not" }
6337            );
6338        }
6339
6340        let cpu = f.run(&[b"INFO", b"cpu"]);
6341        assert!(cpu.contains("used_cpu_user"), "{cpu}");
6342        assert!(!cpu.contains("used_memory"), "{cpu}");
6343
6344        // Their case, to make the point that a section name is not case
6345        // sensitive any more than a command name is.
6346        let stats = f.run(&[b"INFO", b"commandSTATS"]);
6347        assert!(!stats.contains("used_memory"), "{stats}");
6348        assert!(stats.contains("rejected_calls"), "{stats}");
6349
6350        // Two sections named, and neither of them pulls in a third.
6351        let pair = f.run(&[b"INFO", b"cpu", b"sentinel"]);
6352        assert!(pair.contains("used_cpu_user"), "{pair}");
6353        assert!(!pair.contains("master_repl_offset"), "{pair}");
6354
6355        let with_all = f.run(&[b"INFO", b"cpu", b"all"]);
6356        assert!(with_all.contains("used_memory"), "{with_all}");
6357        assert!(with_all.contains("master_repl_offset"), "{with_all}");
6358        assert!(with_all.contains("rejected_calls"), "{with_all}");
6359        // A section named twice is still written once.
6360        assert_eq!(
6361            with_all.matches("used_cpu_user_children").count(),
6362            1,
6363            "{with_all}"
6364        );
6365
6366        let with_default = f.run(&[b"INFO", b"cpu", b"default"]);
6367        assert!(with_default.contains("used_memory"), "{with_default}");
6368        assert!(
6369            with_default.contains("master_repl_offset"),
6370            "{with_default}"
6371        );
6372        assert!(!with_default.contains("rejected_calls"), "{with_default}");
6373        assert_eq!(
6374            with_default.matches("used_cpu_user_children").count(),
6375            1,
6376            "{with_default}"
6377        );
6378    }
6379
6380    /// The memory section says what this process may use, not what the machine
6381    /// has.
6382    ///
6383    /// The distinction is the whole point of it. A server inside a container
6384    /// that reports the host's memory is a server whose operator sizes it for
6385    /// memory it will be killed for touching, so all three numbers are there:
6386    /// what the machine has, what the cgroup allows, and the quarter of the
6387    /// tighter one that pools are sized from.
6388    #[test]
6389    fn info_memory_reports_the_cap_and_the_quarter_of_it_that_gets_used() {
6390        let mut f = Fixture::new();
6391        let info = f.run(&[b"INFO", b"memory"]);
6392        for field in [
6393            "total_system_memory:",
6394            "mem_cgroup_limit:",
6395            "mem_limit:",
6396            "mem_budget:",
6397        ] {
6398            assert!(info.contains(field), "no {field} in {info}");
6399        }
6400
6401        let field = |name: &str| -> u64 {
6402            info.lines()
6403                .find_map(|l| l.strip_prefix(name))
6404                .unwrap_or_else(|| panic!("no {name} in {info}"))
6405                .trim()
6406                .parse()
6407                .unwrap_or_else(|_| panic!("{name} is not a number in {info}"))
6408        };
6409        let limit = field("mem_limit:");
6410        assert_eq!(field("mem_budget:"), limit / 4, "{info}");
6411        // Zero means there is no limit to report, which is a real answer on a
6412        // machine with no cgroups and no way to ask how big it is.
6413        if limit != 0 {
6414            let host = field("total_system_memory:");
6415            let cgroup = field("mem_cgroup_limit:");
6416            assert!(
6417                limit == host || limit == cgroup,
6418                "the limit came from neither number: {info}"
6419            );
6420        }
6421    }
6422
6423    /// The three counters, each on the path that raises it.
6424    ///
6425    /// `calls` on a command that worked, `failed_calls` on one that ran and
6426    /// answered with an error, and `rejected_calls` on one that never ran at
6427    /// all. The last two are the pair that is easy to collapse into one number
6428    /// and that Redis keeps apart, because a client sending the wrong number of
6429    /// arguments and a client asking for a list element that is not there are
6430    /// not the same problem.
6431    #[test]
6432    fn a_command_counts_what_it_did_separately_from_what_it_refused() {
6433        let mut f = Fixture::new();
6434        f.run(&[b"SET", b"k", b"v"]);
6435        f.run(&[b"SET", b"k", b"w"]);
6436        // Ran, and answered with an error, because `k` is not a list.
6437        f.run(&[b"LPUSH", b"k", b"x"]);
6438        // Never ran: `LPUSH` takes at least three arguments.
6439        f.run(&[b"LPUSH", b"k"]);
6440
6441        let stats = f.run(&[b"INFO", b"commandstats"]);
6442        assert!(
6443            stats.contains("cmdstat_set:calls=2,rejected_calls=0,failed_calls=0"),
6444            "{stats}"
6445        );
6446        assert!(
6447            stats.contains("cmdstat_lpush:calls=1,rejected_calls=1,failed_calls=1"),
6448            "{stats}"
6449        );
6450        assert!(
6451            !stats.contains("cmdstat_zadd"),
6452            "a command nobody has sent has no row: {stats}"
6453        );
6454    }
6455
6456    /// A cache that writes with a deadline and never reads back used to hold
6457    /// every key it had ever written, because lazy expiry needs somebody to walk
6458    /// past a key before it can reclaim it and nobody ever did.
6459    #[test]
6460    fn the_active_sweep_reclaims_keys_no_client_comes_back_for() {
6461        // Four thousand keys is four thousand trips through dispatch, and what
6462        // Miri charges for is trips rather than keys, so this was over five
6463        // minutes there. An eighth of each keeps everything the test is about,
6464        // which is three keys with a deadline for every one without and a
6465        // sweep that has to reclaim all of the first kind and none of the
6466        // second.
6467        let (dead, live) = if cfg!(miri) {
6468            (375, 125)
6469        } else {
6470            (3_000, 1_000)
6471        };
6472        let mut f = Fixture::new();
6473        for i in 0..dead {
6474            f.run(&[b"SET", format!("d{i}").as_bytes(), b"v", b"PX", b"50"]);
6475        }
6476        for i in 0..live {
6477            f.run(&[b"SET", format!("k{i}").as_bytes(), b"v"]);
6478        }
6479        let all = format!(":{}\r\n", dead + live);
6480        assert_eq!(f.run(&[b"DBSIZE"]), all);
6481        f.advance(100);
6482        assert_eq!(
6483            f.run(&[b"DBSIZE"]),
6484            all,
6485            "DBSIZE counts records and nothing has read past the dead ones yet"
6486        );
6487
6488        // What the shard loop does, one slice at a time.
6489        let rest = format!(":{live}\r\n");
6490        let mut spent = 0;
6491        for _ in 0..2_000 {
6492            spent += f.server.expire_step(4096);
6493            if f.run(&[b"DBSIZE"]) == rest {
6494                break;
6495            }
6496        }
6497        assert_eq!(f.run(&[b"DBSIZE"]), rest, "spent {spent} looks");
6498        assert!(
6499            f.run(&[b"INFO", b"stats"])
6500                .contains(&format!("expired_keys:{dead}"))
6501        );
6502        for i in 0..live {
6503            assert_eq!(
6504                f.run(&[b"GET", format!("k{i}").as_bytes()]),
6505                "$1\r\nv\r\n",
6506                "it took a key that had no deadline"
6507            );
6508        }
6509    }
6510
6511    #[test]
6512    fn a_sweep_of_a_server_with_no_deadlines_anywhere_costs_nothing() {
6513        // The keys are only here so that the database the sweep walks is not an
6514        // empty one. Two hundred of them fills as many slots as a sweep looks
6515        // at and is a tenth of the interpreted work.
6516        let n = if cfg!(miri) { 200 } else { 2_000 };
6517        let mut f = Fixture::new();
6518        for i in 0..n {
6519            f.run(&[b"SET", format!("k{i}").as_bytes(), b"v"]);
6520        }
6521        assert_eq!(f.server.expire_step(4096), 0);
6522        // And one database having them does not make the other fifteen pay.
6523        f.run(&[b"SELECT", b"3"]);
6524        f.run(&[b"SET", b"x", b"v", b"PX", b"50"]);
6525        f.advance(100);
6526        for _ in 0..64 {
6527            f.server.expire_step(4096);
6528        }
6529        assert_eq!(f.run(&[b"DBSIZE"]), ":0\r\n");
6530        f.run(&[b"SELECT", b"0"]);
6531        assert_eq!(f.run(&[b"DBSIZE"]), format!(":{n}\r\n"));
6532        assert_eq!(f.server.expire_step(4096), 0, "and it is quiet again");
6533    }
6534
6535    /// The gate, which is what stops a maintenance slice that runs every hundred
6536    /// nanoseconds from drawing a sample every hundred nanoseconds.
6537    #[test]
6538    fn the_sweep_the_loop_calls_runs_at_most_once_a_millisecond() {
6539        let mut f = Fixture::new();
6540        for i in 0..500u32 {
6541            f.run(&[b"SET", format!("d{i}").as_bytes(), b"v", b"PX", b"50"]);
6542        }
6543        f.advance(100);
6544        let at = f.server.striped(0).now_ms();
6545        f.server.set_clock_ms(at);
6546        // A small budget, so that one slice cannot finish the job and a second
6547        // one having nothing to do would mean the gate and not an empty
6548        // database.
6549        assert!(f.server.expire_slice(8) > 0, "the first one works");
6550        for _ in 0..1_000 {
6551            assert_eq!(
6552                f.server.expire_slice(8),
6553                0,
6554                "the millisecond has not moved and neither should this"
6555            );
6556        }
6557        assert!(
6558            f.server.striped(0).expires() > 400,
6559            "there is plenty left to take"
6560        );
6561        f.server.set_clock_ms(at + 1);
6562        assert!(f.server.expire_slice(8) > 0, "and then it goes again");
6563    }
6564
6565    /// `expires=` used to be a hardcoded zero, which meant a dashboard watching
6566    /// how much of a cache is volatile was reading a constant.
6567    #[test]
6568    fn info_keyspace_counts_the_keys_that_have_a_deadline() {
6569        let mut f = Fixture::new();
6570        f.run(&[b"MSET", b"a", b"1", b"b", b"2", b"c", b"3"]);
6571        assert!(
6572            f.run(&[b"INFO", b"keyspace"])
6573                .contains("db0:keys=3,expires=0"),
6574            "none of them has one yet"
6575        );
6576        f.run(&[b"EXPIRE", b"a", b"1000"]);
6577        f.run(&[b"EXPIRE", b"b", b"1000"]);
6578        let two = f.run(&[b"INFO", b"keyspace"]);
6579        assert!(two.contains("db0:keys=3,expires=2"), "{two}");
6580        f.run(&[b"PERSIST", b"a"]);
6581        f.run(&[b"DEL", b"b"]);
6582        let none = f.run(&[b"INFO", b"keyspace"]);
6583        assert!(none.contains("db0:keys=2,expires=0"), "{none}");
6584
6585        // Each database answers for itself, the way Redis reports it.
6586        f.run(&[b"SELECT", b"1"]);
6587        f.run(&[b"SET", b"x", b"1", b"EX", b"1000"]);
6588        let both = f.run(&[b"INFO", b"keyspace"]);
6589        assert!(both.contains("db0:keys=2,expires=0"), "{both}");
6590        assert!(both.contains("db1:keys=1,expires=1"), "{both}");
6591    }
6592
6593    /// Not under Miri, which reads a zero on purpose because it has no
6594    /// `getrusage` to call, so the second half of this would burn a billion
6595    /// interpreted multiplications waiting for a number that is never going to
6596    /// move. The first half, that the section is there and has the fields Redis
6597    /// clients look for, is checked by the `INFO` tests above as well, and
6598    /// those do run there.
6599    #[cfg(unix)]
6600    #[cfg_attr(miri, ignore = "no getrusage under Miri, so the number is fixed")]
6601    #[test]
6602    fn info_cpu_reports_processor_time_that_was_really_measured() {
6603        let mut f = Fixture::new();
6604        let cpu = f.run(&[b"INFO", b"cpu"]);
6605        assert!(cpu.contains("# CPU"), "{cpu}");
6606        // Redis's unit/info-command asks for this one by name in three tests.
6607        assert!(cpu.contains("used_cpu_user:"), "{cpu}");
6608        assert!(cpu.contains("used_cpu_sys:"), "{cpu}");
6609        assert!(cpu.contains("used_cpu_user_children:0.000000"), "{cpu}");
6610        assert!(!cpu.contains("redis_version"), "{cpu}");
6611
6612        // It is a measurement and not a constant, so it goes up when work
6613        // happens. A tight loop rather than a sleep, because sleeping is the
6614        // one thing that does not move this number.
6615        let before = used_cpu_user(&cpu);
6616        let mut n = 0u64;
6617        let mut rounds = 0;
6618        while used_cpu_user(&f.run(&[b"INFO", b"cpu"])) <= before {
6619            for i in 0..1_000_000u64 {
6620                n = n.wrapping_add(i.wrapping_mul(i));
6621            }
6622            rounds += 1;
6623            // A bound rather than a spin, so a platform where this number does
6624            // not move fails here instead of hanging. Even a clock with whole
6625            // millisecond granularity gets there in the first round or two.
6626            assert!(rounds < 1_000, "cpu time never moved, n is {n}");
6627        }
6628    }
6629
6630    /// Pull `used_cpu_user` back out of an `INFO cpu` reply.
6631    #[cfg(unix)]
6632    fn used_cpu_user(info: &str) -> f64 {
6633        info.lines()
6634            .find_map(|l| l.strip_prefix("used_cpu_user:"))
6635            .expect("no used_cpu_user in the reply")
6636            .trim()
6637            .parse()
6638            .expect("used_cpu_user is not a number")
6639    }
6640
6641    /// The safety net under the rule that a body checks its arguments before
6642    /// it writes anything. `MGET` writes its array header first and then reads
6643    /// each key, so if a later argument could fail the header would already be
6644    /// out. Nothing in the string group does that today and this is what would
6645    /// catch the first one that did.
6646    #[test]
6647    fn a_command_that_fails_leaves_nothing_half_written() {
6648        let mut f = Fixture::new();
6649        let reply = f.run(&[b"SETRANGE", b"k", b"-1", b"x"]);
6650        assert_eq!(reply, "-ERR offset is out of range\r\n");
6651        assert!(!reply.contains(':'), "no integer went out in front of it");
6652    }
6653
6654    #[test]
6655    fn quit_answers_first_and_closes_after() {
6656        let mut f = Fixture::new();
6657        let (flow, reply) = f.flow(&[b"QUIT"]);
6658        assert_eq!(reply, "+OK\r\n");
6659        assert_eq!(flow, Flow::Close);
6660    }
6661
6662    /// A server that has not been asked to stop is not stopping, and one that
6663    /// has says so without writing anything back.
6664    ///
6665    /// The empty reply is the point. Redis answers nothing at all here and the
6666    /// client sees the socket close, and an `OK` would be a promise from a
6667    /// process that is about to not exist.
6668    #[test]
6669    fn shutdown_writes_nothing_and_sets_the_flag() {
6670        let mut f = Fixture::new();
6671        assert!(!f.server.stopping(), "nobody has asked yet");
6672
6673        let (flow, reply) = f.flow(&[b"SHUTDOWN"]);
6674        assert_eq!(reply, "");
6675        assert_eq!(flow, Flow::Close);
6676        assert!(f.server.stopping());
6677    }
6678
6679    /// Every flag combination 8.10.1 takes, and every one it refuses.
6680    ///
6681    /// The refusals are the half worth pinning down. `SAVE` and `NOSAVE`
6682    /// contradict each other, `ABORT` says to do nothing so it cannot be
6683    /// combined with a word about how to do it, and repeating any one of them
6684    /// is fine. All of it was read off a running 8.10.1 rather than worked out
6685    /// from the documentation, which does not say.
6686    #[test]
6687    fn shutdown_takes_the_flags_redis_takes() {
6688        for flags in [
6689            &[b"NOSAVE".as_slice()][..],
6690            &[b"SAVE"],
6691            &[b"NOW"],
6692            &[b"FORCE"],
6693            &[b"nosave"],
6694            &[b"NOW", b"NOW"],
6695            &[b"SAVE", b"SAVE"],
6696            &[b"NOSAVE", b"NOW", b"FORCE"],
6697        ] {
6698            let mut f = Fixture::new();
6699            let mut parts = vec![b"SHUTDOWN".as_slice()];
6700            parts.extend_from_slice(flags);
6701            let (flow, reply) = f.flow(&parts);
6702            assert_eq!(reply, "", "SHUTDOWN {flags:?} answered something");
6703            assert_eq!(flow, Flow::Close, "SHUTDOWN {flags:?} did not close");
6704            assert!(f.server.stopping(), "SHUTDOWN {flags:?} did not stop");
6705        }
6706
6707        for flags in [
6708            &[b"BOGUS".as_slice()][..],
6709            &[b"SAVE", b"NOSAVE"],
6710            &[b"NOSAVE", b"SAVE"],
6711            &[b"ABORT", b"NOW"],
6712            &[b"NOSAVE", b"ABORT"],
6713            &[b"NOW", b"FORCE", b"ABORT"],
6714        ] {
6715            let mut f = Fixture::new();
6716            let mut parts = vec![b"SHUTDOWN".as_slice()];
6717            parts.extend_from_slice(flags);
6718            assert_eq!(
6719                f.run(&parts),
6720                "-ERR syntax error\r\n",
6721                "SHUTDOWN {flags:?} was accepted"
6722            );
6723            assert!(!f.server.stopping(), "SHUTDOWN {flags:?} stopped anyway");
6724        }
6725    }
6726
6727    /// `ABORT` has nothing to call off, ever.
6728    ///
6729    /// A shutdown here is decided and done inside one turn of the loop, so
6730    /// there is no window in which one is in progress. That makes Redis's
6731    /// message for a cancel with nothing to cancel the right answer every time
6732    /// rather than only when nothing happens to be pending. Two `ABORT`s is
6733    /// still one `ABORT`, which is what 8.10.1 does.
6734    #[test]
6735    fn shutdown_abort_never_has_anything_to_abort() {
6736        let mut f = Fixture::new();
6737        for parts in [
6738            &[b"SHUTDOWN".as_slice(), b"ABORT"][..],
6739            &[b"SHUTDOWN", b"ABORT", b"ABORT"],
6740        ] {
6741            assert_eq!(f.run(parts), "-ERR No shutdown in progress.\r\n");
6742            assert!(!f.server.stopping(), "an abort stopped the server");
6743        }
6744    }
6745
6746    /// A fixture whose server writes into a directory of its own.
6747    ///
6748    /// Every test here really writes files, because the whole point of the
6749    /// command is the files and a backup that is only a state machine would
6750    /// pass a test suite and fail the first person who tried to restore one.
6751    /// The directory carries the test's name so that the suite can run its
6752    /// tests in parallel the way it always does.
6753    struct Backups {
6754        f: Fixture,
6755        dir: PathBuf,
6756    }
6757
6758    impl Backups {
6759        fn new(name: &str) -> Backups {
6760            let dir = std::env::temp_dir().join(format!("yo-backup-{name}-{}", std::process::id()));
6761            let _ = std::fs::remove_dir_all(&dir);
6762            std::fs::create_dir_all(&dir).expect("could not make a temporary directory");
6763            let mut f = Fixture::new();
6764            f.server.set_dir(dir.clone());
6765            Backups { f, dir }
6766        }
6767
6768        fn run(&mut self, parts: &[&[u8]]) -> String {
6769            self.f.run(parts)
6770        }
6771
6772        /// The names in `backupdir`, sorted, so a test can say what is on disk.
6773        fn files(&self) -> Vec<String> {
6774            let mut names: Vec<String> = match std::fs::read_dir(self.dir.join("backupdir")) {
6775                Ok(entries) => entries
6776                    .filter_map(|e| e.ok())
6777                    .map(|e| e.file_name().to_string_lossy().into_owned())
6778                    .collect(),
6779                Err(_) => Vec::new(),
6780            };
6781            names.sort();
6782            names
6783        }
6784
6785        fn read(&self, name: &str) -> Vec<u8> {
6786            std::fs::read(self.dir.join("backupdir").join(name)).expect("could not read")
6787        }
6788    }
6789
6790    impl Drop for Backups {
6791        fn drop(&mut self) {
6792            let _ = std::fs::remove_dir_all(&self.dir);
6793        }
6794    }
6795
6796    /// The four states and the moves between them, in the order a client walks
6797    /// them, with the files checked at every step.
6798    #[test]
6799    fn backup_walks_the_states_the_reference_walks() {
6800        let mut b = Backups::new("states");
6801        let status = |b: &mut Backups| b.run(&[b"BACKUP", b"STATUS"]);
6802
6803        assert!(status(&mut b).contains("idle"));
6804        assert!(b.files().is_empty(), "an idle server has written a backup");
6805
6806        assert_eq!(b.run(&[b"BACKUP", b"START"]), "+OK\r\n");
6807        assert!(status(&mut b).contains("incrementing"));
6808        assert_eq!(b.files(), ["appendonly.aof.1.base.rdb"]);
6809
6810        assert_eq!(b.run(&[b"BACKUP", b"SEAL"]), "+OK\r\n");
6811        assert!(status(&mut b).contains("sealed"));
6812        assert_eq!(
6813            b.files(),
6814            [
6815                "appendonly.aof.1.base.rdb",
6816                "appendonly.aof.1.incr.aof",
6817                "appendonly.aof.manifest",
6818            ]
6819        );
6820
6821        assert_eq!(b.run(&[b"BACKUP", b"CLEANUP"]), "+OK\r\n");
6822        assert!(status(&mut b).contains("idle"));
6823        assert!(b.files().is_empty(), "cleanup left something behind");
6824    }
6825
6826    /// Every move that is refused, in the reference's words.
6827    #[test]
6828    fn backup_refuses_the_moves_the_reference_refuses() {
6829        let mut b = Backups::new("refusals");
6830
6831        assert_eq!(
6832            b.run(&[b"BACKUP", b"SEAL"]),
6833            "-ERR No backup ready to seal (must be in the incrementing state)\r\n"
6834        );
6835        assert_eq!(
6836            b.run(&[b"BACKUP", b"ABORT"]),
6837            "-ERR No backup in progress\r\n"
6838        );
6839        // Cleanup from idle is not an error, it is a way of saying there was
6840        // nothing to clean up.
6841        assert_eq!(b.run(&[b"BACKUP", b"CLEANUP"]), "+OK\r\n");
6842
6843        b.run(&[b"BACKUP", b"START"]);
6844        assert_eq!(
6845            b.run(&[b"BACKUP", b"START"]),
6846            "-ERR A backup is already in progress, ABORT it first\r\n"
6847        );
6848        assert_eq!(
6849            b.run(&[b"BACKUP", b"CLEANUP"]),
6850            "-ERR Backup is in progress\r\n"
6851        );
6852
6853        b.run(&[b"BACKUP", b"SEAL"]);
6854        assert_eq!(
6855            b.run(&[b"BACKUP", b"START"]),
6856            "-ERR A sealed backup exists, CLEANUP it first\r\n"
6857        );
6858        assert_eq!(
6859            b.run(&[b"BACKUP", b"SEAL"]),
6860            "-ERR No backup ready to seal (must be in the incrementing state)\r\n"
6861        );
6862        assert_eq!(
6863            b.run(&[b"BACKUP", b"ABORT"]),
6864            "-ERR No backup in progress\r\n"
6865        );
6866    }
6867
6868    /// An abort takes the base file away and leaves a state saying who did it.
6869    ///
6870    /// The next backup takes the next sequence number rather than reusing the
6871    /// one whose files were just thrown away, so a directory somebody copied a
6872    /// half finished backup out of cannot end up with two different files under
6873    /// one name.
6874    #[test]
6875    fn backup_abort_removes_the_file_and_says_who_did_it() {
6876        let mut b = Backups::new("abort");
6877        b.run(&[b"BACKUP", b"START"]);
6878        assert_eq!(b.run(&[b"BACKUP", b"ABORT"]), "+OK\r\n");
6879
6880        let status = b.run(&[b"BACKUP", b"STATUS"]);
6881        assert!(status.contains("failed"), "{status}");
6882        assert!(status.contains("aborted by user"), "{status}");
6883        assert!(b.files().is_empty(), "abort left the base file behind");
6884        assert_eq!(b.run(&[b"BACKUP", b"LIST"]), "*0\r\n");
6885
6886        // A start from failed works, and is the second backup.
6887        assert_eq!(b.run(&[b"BACKUP", b"START"]), "+OK\r\n");
6888        assert_eq!(b.files(), ["appendonly.aof.2.base.rdb"]);
6889        let status = b.run(&[b"BACKUP", b"STATUS"]);
6890        assert!(status.contains("incrementing"), "{status}");
6891        assert!(!status.contains("aborted"), "the old error was kept");
6892    }
6893
6894    /// `LIST` names nothing, then one file, then three, and they are absolute.
6895    #[test]
6896    fn backup_list_names_the_files_that_are_pinned_so_far() {
6897        let mut b = Backups::new("list");
6898        assert_eq!(b.run(&[b"BACKUP", b"LIST"]), "*0\r\n");
6899
6900        b.run(&[b"BACKUP", b"START"]);
6901        let base = b.dir.join("backupdir").join("appendonly.aof.1.base.rdb");
6902        let base = base.to_string_lossy().into_owned();
6903        assert_eq!(
6904            b.run(&[b"BACKUP", b"LIST"]),
6905            format!("*1\r\n${}\r\n{base}\r\n", base.len())
6906        );
6907
6908        b.run(&[b"BACKUP", b"SEAL"]);
6909        let listed = b.run(&[b"BACKUP", b"LIST"]);
6910        assert!(listed.starts_with("*3\r\n"), "{listed}");
6911        // The order is the manifest's order, base then incremental then the
6912        // manifest itself, which is the order a restore needs them in.
6913        let names: Vec<&str> = listed
6914            .lines()
6915            .filter(|l| l.starts_with('/') || l.contains(":\\"))
6916            .collect();
6917        assert_eq!(names.len(), 3, "{listed}");
6918        assert!(names[0].ends_with("appendonly.aof.1.base.rdb"), "{listed}");
6919        assert!(names[1].ends_with("appendonly.aof.1.incr.aof"), "{listed}");
6920        assert!(names[2].ends_with("appendonly.aof.manifest"), "{listed}");
6921    }
6922
6923    /// The base file is the dataset as it was at `START` and not at `SEAL`.
6924    ///
6925    /// That is D-46 and it is the one thing about this a client can notice, so
6926    /// it is pinned here rather than left to be discovered by whoever restores
6927    /// one. The incremental file is empty for the same reason: there is no
6928    /// append only log underneath this server to copy the writes in between out
6929    /// of.
6930    #[test]
6931    fn a_backup_holds_the_dataset_as_it_was_at_start() {
6932        let mut b = Backups::new("contents");
6933        b.run(&[b"SET", b"bk", b"v1"]);
6934        b.run(&[b"BACKUP", b"START"]);
6935        b.run(&[b"SET", b"bk", b"v2"]);
6936        b.run(&[b"BACKUP", b"SEAL"]);
6937
6938        let base = b.read("appendonly.aof.1.base.rdb");
6939        assert!(base.starts_with(b"REDIS"), "not an RDB file");
6940        assert!(base.windows(2).any(|w| w == b"v1"), "the value is missing");
6941        assert!(
6942            !base.windows(2).any(|w| w == b"v2"),
6943            "the base file moved on after START"
6944        );
6945        // The aux field a loader acts on, and the one that says this file is
6946        // the base of an append only file rather than a standalone dump. Its
6947        // value is the one byte string 1, which the encoder writes as an
6948        // integer the way a real server writes it.
6949        let at = base
6950            .windows(8)
6951            .position(|w| w == b"aof-base")
6952            .expect("no aof-base aux field");
6953        assert_eq!(&base[at + 8..at + 10], b"\xc0\x01", "{:?}", &base[at..]);
6954
6955        assert!(b.read("appendonly.aof.1.incr.aof").is_empty());
6956        assert_eq!(
6957            String::from_utf8(b.read("appendonly.aof.manifest")).expect("the manifest is text"),
6958            "file appendonly.aof.1.base.rdb seq 1 type b\n\
6959             file appendonly.aof.1.incr.aof seq 1 type i startoffset 0 endoffset 0\n"
6960        );
6961    }
6962
6963    /// `STATUS` is a map of four pairs on RESP3 and the same pairs flat on
6964    /// RESP2, which is what every other map shaped reply in this server does.
6965    #[test]
6966    fn backup_status_is_a_map_on_resp3_and_a_flat_array_on_resp2() {
6967        let mut b = Backups::new("status");
6968        b.f.server.set_clock_ms(1_700_000_000_000);
6969
6970        assert_eq!(
6971            b.run(&[b"BACKUP", b"STATUS"]),
6972            "*8\r\n$5\r\nstate\r\n$4\r\nidle\r\n$5\r\nerror\r\n$0\r\n\r\n\
6973             $10\r\nstart_time\r\n:0\r\n$8\r\nend_time\r\n:0\r\n"
6974        );
6975
6976        b.f.out = Out::new(Proto::Resp3);
6977        b.run(&[b"BACKUP", b"START"]);
6978        assert_eq!(
6979            b.run(&[b"BACKUP", b"STATUS"]),
6980            "%4\r\n$5\r\nstate\r\n$12\r\nincrementing\r\n$5\r\nerror\r\n$0\r\n\r\n\
6981             $10\r\nstart_time\r\n:1700000000\r\n$8\r\nend_time\r\n:0\r\n"
6982        );
6983
6984        b.run(&[b"BACKUP", b"SEAL"]);
6985        let sealed = b.run(&[b"BACKUP", b"STATUS"]);
6986        assert!(sealed.contains("end_time\r\n:1700000000"), "{sealed}");
6987    }
6988
6989    /// A sealed backup that nobody cleans up goes away on its own once
6990    /// `backup-sealed-ttl` seconds have passed since the seal.
6991    #[test]
6992    fn a_sealed_backup_is_swept_away_after_the_timeout() {
6993        let mut b = Backups::new("ttl");
6994        b.f.server.set_clock_ms(1_000_000);
6995        assert_eq!(
6996            b.run(&[b"CONFIG", b"SET", b"backup-sealed-ttl", b"60"]),
6997            "+OK\r\n"
6998        );
6999        b.run(&[b"BACKUP", b"START"]);
7000        b.run(&[b"BACKUP", b"SEAL"]);
7001
7002        // A minute short of the deadline, nothing happens.
7003        b.f.server.set_clock_ms(1_000_000 + 59_000);
7004        b.f.server.backup_expire();
7005        assert!(b.run(&[b"BACKUP", b"STATUS"]).contains("sealed"));
7006        assert_eq!(b.files().len(), 3);
7007
7008        b.f.server.set_clock_ms(1_000_000 + 60_000);
7009        b.f.server.backup_expire();
7010        let status = b.run(&[b"BACKUP", b"STATUS"]);
7011        assert!(status.contains("idle"), "{status}");
7012        assert!(b.files().is_empty(), "the timeout left the files behind");
7013
7014        // Zero is the default and means a sealed backup is kept for ever.
7015        b.run(&[b"CONFIG", b"SET", b"backup-sealed-ttl", b"0"]);
7016        b.run(&[b"BACKUP", b"START"]);
7017        b.run(&[b"BACKUP", b"SEAL"]);
7018        b.f.server.set_clock_ms(9_000_000_000);
7019        b.f.server.backup_expire();
7020        assert!(b.run(&[b"BACKUP", b"STATUS"]).contains("sealed"));
7021    }
7022
7023    /// The three settings around the command, read and written the way 8.10.1
7024    /// reads and writes them.
7025    #[test]
7026    fn the_backup_settings_behave_the_way_the_reference_does() {
7027        let mut b = Backups::new("config");
7028        let dir = b.dir.to_string_lossy().into_owned();
7029
7030        assert_eq!(
7031            b.run(&[b"CONFIG", b"GET", b"dir"]),
7032            format!("*2\r\n$3\r\ndir\r\n${}\r\n{dir}\r\n", dir.len())
7033        );
7034        assert_eq!(
7035            b.run(&[b"CONFIG", b"GET", b"backupdirname"]),
7036            "*2\r\n$13\r\nbackupdirname\r\n$9\r\nbackupdir\r\n"
7037        );
7038        assert_eq!(
7039            b.run(&[b"CONFIG", b"GET", b"backup-sealed-ttl"]),
7040            "*2\r\n$17\r\nbackup-sealed-ttl\r\n$1\r\n0\r\n"
7041        );
7042
7043        // `dir` is a protected config, so it is refused even for the value it
7044        // already holds, and `backupdirname` is immutable.
7045        assert_eq!(
7046            b.run(&[b"CONFIG", b"SET", b"dir", dir.as_bytes()]),
7047            "-ERR CONFIG SET failed (possibly related to argument 'dir') - can't set protected config\r\n"
7048        );
7049        assert_eq!(
7050            b.run(&[b"CONFIG", b"SET", b"backupdirname", b"other"]),
7051            "-ERR CONFIG SET failed (possibly related to argument 'backupdirname') - can't set immutable config\r\n"
7052        );
7053        assert!(
7054            b.run(&[b"CONFIG", b"SET", b"backup-sealed-ttl", b"abc"])
7055                .contains("argument couldn't be parsed into an integer")
7056        );
7057        assert!(
7058            b.run(&[b"CONFIG", b"SET", b"backup-sealed-ttl", b"-1"])
7059                .contains("argument must be between 0 and 9223372036854775807 inclusive")
7060        );
7061    }
7062
7063    /// The help text, which has `HELP` in it twice because the reference's does.
7064    #[test]
7065    fn backup_help_is_the_text_the_reference_sends() {
7066        let mut f = Fixture::new();
7067        let help = f.run(&[b"BACKUP", b"HELP"]);
7068        assert!(help.starts_with("*17\r\n"), "{help}");
7069        assert!(
7070            help.contains("+BACKUP <subcommand> [<arg> [value] [opt] ...]. Subcommands are:\r\n")
7071        );
7072        assert!(help.contains("+    Start a new backup into the configured 'backupdirname'.\r\n"));
7073        assert!(help.contains("+    Freeze the current backup (BASE + INCR + manifest).\r\n"));
7074        assert!(help.contains("+    Return this help.\r\n+HELP\r\n+    Print this help.\r\n"));
7075    }
7076
7077    /// What a mistyped `BACKUP` gets told.
7078    ///
7079    /// The arity error names `backup` where the reference names `backup|start`,
7080    /// which is D-46: the table reports one arity for the container the way the
7081    /// reference does, and the per subcommand table that would carry the better
7082    /// name is not built yet. Every subcommand is exactly two words, so nothing
7083    /// legal is refused by it.
7084    #[test]
7085    fn backup_refuses_what_it_cannot_read() {
7086        let mut f = Fixture::new();
7087        assert_eq!(
7088            f.run(&[b"BACKUP"]),
7089            "-ERR wrong number of arguments for 'backup' command\r\n"
7090        );
7091        assert_eq!(
7092            f.run(&[b"BACKUP", b"START", b"x"]),
7093            "-ERR wrong number of arguments for 'backup' command\r\n"
7094        );
7095        assert_eq!(
7096            f.run(&[b"BACKUP", b"NOPE"]),
7097            "-ERR unknown subcommand 'NOPE'. Try BACKUP HELP.\r\n"
7098        );
7099    }
7100
7101    #[test]
7102    fn the_command_counter_counts_every_command_including_the_bad_ones() {
7103        let mut f = Fixture::new();
7104        f.run(&[b"PING"]);
7105        f.run(&[b"NOPE"]);
7106        f.run(&[b"GET"]);
7107        assert_eq!(f.server.totals().commands, 3);
7108    }
7109
7110    #[test]
7111    fn what_a_thread_marked_is_taken_by_the_maintenance_turn() {
7112        let mut server = Server::new();
7113        server.set_threads(2);
7114        // A fresh server has every database on the turn's list, so start from
7115        // nothing to see the one mark arrive.
7116        server.mine().turn.store(0, Relaxed);
7117        server.locals[1].mark(1 << 9);
7118        server.collect_marks();
7119        assert!(server.mine().wanted(9));
7120        // And taken once rather than left to be taken again next turn.
7121        assert_eq!(server.locals[1].dirty.load(Relaxed), 0);
7122    }
7123
7124    #[test]
7125    fn what_two_threads_counted_is_added_up_when_info_asks() {
7126        let mut server = Server::new();
7127        server.set_threads(2);
7128        // Written into the two sets by hand, because what is under test is the
7129        // adding up and not the claiming, and one test thread can only ever
7130        // claim one set.
7131        let ping = lookup(b"PING").expect("PING is a command");
7132        for (at, calls) in [(0, 2), (1, 3)] {
7133            let counters = &server.locals[at];
7134            for _ in 0..calls {
7135                counters.stats.commands.bump();
7136                counters.cmdstats.at(ping).calls.bump();
7137            }
7138            counters.stats.opened();
7139        }
7140        assert_eq!(server.totals().commands, 5);
7141        assert_eq!(server.totals().clients, 2);
7142        assert_eq!(server.totals().connections, 2);
7143        let rows: Vec<_> = server.command_stats().collect();
7144        assert_eq!(rows.len(), 1);
7145        assert_eq!(rows[0].0, "ping");
7146        assert_eq!(rows[0].1.calls, 5);
7147        // A reset takes the totals and leaves the open connections, which are
7148        // still open.
7149        server.reset_stats();
7150        assert_eq!(server.totals().commands, 0);
7151        assert_eq!(server.totals().connections, 0);
7152        assert_eq!(server.totals().clients, 2);
7153    }
7154
7155    #[test]
7156    fn the_parked_count_says_what_the_waiter_list_says() {
7157        let mut f = Fixture::new();
7158        assert_eq!(f.server.parked(), 0);
7159        for client in 1..=3u64 {
7160            f.session = Session::new(client);
7161            assert_eq!(f.flow(&[b"BLPOP", b"q", b"0"]).0, Flow::Block);
7162        }
7163        assert_eq!(f.server.parked(), 3);
7164        assert_eq!(f.server.waiters().len(), 3);
7165
7166        // The three ways the list gets shorter, each of which has to move the
7167        // number with it, because a number left behind is either a walk of the
7168        // list that never happens or one that runs off the end of it.
7169        f.server.forget_waiters(2);
7170        assert_eq!(f.server.parked(), f.server.waiters().len());
7171        f.server.forget_waiters(1);
7172        assert_eq!(f.server.parked(), f.server.waiters().len());
7173        f.run(&[b"RPUSH", b"q", b"v"]);
7174        let mut out = Out::new(Proto::Resp2);
7175        assert!(f.server.serve_waiter(3, 0, &mut out));
7176        f.server.forget_waiters(3);
7177        assert_eq!(f.server.parked(), 0);
7178        assert!(f.server.waiters().is_empty());
7179    }
7180
7181    #[test]
7182    fn a_set_goes_from_bytes_to_bytes() {
7183        let mut f = Fixture::new();
7184        assert_eq!(f.run(&[b"SADD", b"s", b"a", b"b", b"c"]), ":3\r\n");
7185        assert_eq!(f.run(&[b"SADD", b"s", b"b", b"d"]), ":1\r\n");
7186        assert_eq!(f.run(&[b"SCARD", b"s"]), ":4\r\n");
7187        assert_eq!(f.run(&[b"SISMEMBER", b"s", b"a"]), ":1\r\n");
7188        assert_eq!(f.run(&[b"SISMEMBER", b"s", b"z"]), ":0\r\n");
7189        assert_eq!(f.run(&[b"TYPE", b"s"]), "+set\r\n");
7190        assert_eq!(
7191            f.run(&[b"SMISMEMBER", b"s", b"a", b"z", b"d"]),
7192            "*3\r\n:1\r\n:0\r\n:1\r\n"
7193        );
7194        assert_eq!(f.run(&[b"SREM", b"s", b"a", b"z"]), ":1\r\n");
7195        assert_eq!(f.run(&[b"SCARD", b"s"]), ":3\r\n");
7196    }
7197
7198    #[test]
7199    fn a_set_command_at_a_key_that_is_not_there_answers_empty() {
7200        let mut f = Fixture::new();
7201        assert_eq!(f.run(&[b"SCARD", b"nope"]), ":0\r\n");
7202        assert_eq!(f.run(&[b"SISMEMBER", b"nope", b"a"]), ":0\r\n");
7203        assert_eq!(f.run(&[b"SREM", b"nope", b"a"]), ":0\r\n");
7204        assert_eq!(f.run(&[b"SMEMBERS", b"nope"]), "*0\r\n");
7205        assert_eq!(
7206            f.run(&[b"SMISMEMBER", b"nope", b"a", b"b"]),
7207            "*2\r\n:0\r\n:0\r\n"
7208        );
7209        assert_eq!(f.run(&[b"DBSIZE"]), ":0\r\n", "and made nothing");
7210    }
7211
7212    #[test]
7213    fn smembers_answers_a_set_on_resp3_and_an_array_on_resp2() {
7214        // Not cosmetic. A RESP3 client that gets a `~` hands the caller a set
7215        // and one that gets a `*` hands it a list, without either of them being
7216        // told which command was sent.
7217        let mut f = Fixture::new();
7218        f.run(&[b"SADD", b"s", b"one"]);
7219        assert_eq!(f.run(&[b"SMEMBERS", b"s"]), "*1\r\n$3\r\none\r\n");
7220
7221        f.run(&[b"HELLO", b"3"]);
7222        assert_eq!(f.run(&[b"SMEMBERS", b"s"]), "~1\r\n$3\r\none\r\n");
7223    }
7224
7225    #[test]
7226    fn an_integer_member_comes_back_as_the_digits_it_never_stored() {
7227        // An intset holds the number, so these digits exist for the first time
7228        // in the reply buffer.
7229        let mut f = Fixture::new();
7230        f.run(&[b"SADD", b"s", b"42"]);
7231        assert_eq!(f.run(&[b"SMEMBERS", b"s"]), "*1\r\n$2\r\n42\r\n");
7232        assert_eq!(f.run(&[b"SISMEMBER", b"s", b"42"]), ":1\r\n");
7233        assert_eq!(
7234            f.run(&[b"SISMEMBER", b"s", b"042"]),
7235            ":0\r\n",
7236            "the member is the bytes and not the number they parse to"
7237        );
7238    }
7239
7240    #[test]
7241    fn the_wrong_command_at_the_wrong_type_says_so_both_ways() {
7242        let mut f = Fixture::new();
7243        f.run(&[b"SET", b"str", b"v"]);
7244        f.run(&[b"SADD", b"set", b"a"]);
7245
7246        let wrong = "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n";
7247        assert_eq!(f.run(&[b"SADD", b"str", b"a"]), wrong);
7248        assert_eq!(f.run(&[b"SCARD", b"str"]), wrong);
7249        assert_eq!(f.run(&[b"SMEMBERS", b"str"]), wrong);
7250        assert_eq!(f.run(&[b"SMISMEMBER", b"str", b"a"]), wrong);
7251        assert_eq!(f.run(&[b"GET", b"set"]), wrong);
7252        assert_eq!(f.run(&[b"APPEND", b"set", b"x"]), wrong);
7253        assert_eq!(f.run(&[b"INCR", b"set"]), wrong);
7254        assert_eq!(f.run(&[b"STRLEN", b"set"]), wrong);
7255
7256        // MGET is the one that does not, because Redis gives nil for the odd
7257        // key out rather than failing the good keys next to it.
7258        assert_eq!(
7259            f.run(&[b"MGET", b"str", b"set", b"nope"]),
7260            "*3\r\n$1\r\nv\r\n$-1\r\n$-1\r\n"
7261        );
7262        // And plain SET overwrites any type, which takes the body with it.
7263        assert_eq!(f.run(&[b"SET", b"set", b"now a string"]), "+OK\r\n");
7264        assert_eq!(f.run(&[b"TYPE", b"set"]), "+string\r\n");
7265    }
7266
7267    #[test]
7268    fn a_wrongtype_leaves_nothing_half_written() {
7269        // SMISMEMBER writes an array header and then one reply per member, so
7270        // it is the first command in the server that could get a header out in
7271        // front of an error if it checked its key in the wrong order.
7272        let mut f = Fixture::new();
7273        f.run(&[b"SET", b"k", b"v"]);
7274        let reply = f.run(&[b"SMISMEMBER", b"k", b"a", b"b"]);
7275        assert!(reply.starts_with("-WRONGTYPE"), "got {reply}");
7276        assert!(!reply.contains('*'), "an array header went out in front");
7277    }
7278
7279    #[test]
7280    fn emptying_a_set_takes_the_key_with_it() {
7281        let mut f = Fixture::new();
7282        f.run(&[b"SADD", b"s", b"a", b"b"]);
7283        assert_eq!(f.run(&[b"DBSIZE"]), ":1\r\n");
7284        assert_eq!(f.run(&[b"SREM", b"s", b"a", b"b"]), ":2\r\n");
7285        assert_eq!(f.run(&[b"EXISTS", b"s"]), ":0\r\n");
7286        assert_eq!(f.run(&[b"TYPE", b"s"]), "+none\r\n");
7287        assert_eq!(f.run(&[b"DBSIZE"]), ":0\r\n");
7288    }
7289
7290    /// Pull the cursor and the members out of one `SSCAN` reply.
7291    ///
7292    /// Crude on purpose. A test that walked a set through a real client would
7293    /// be testing the client, and what these tests are about is the shape of
7294    /// the bytes and the fact that a walk sees every member once.
7295    fn split_scan(reply: &str) -> (String, Vec<String>) {
7296        let mut lines = reply.split("\r\n");
7297        assert_eq!(lines.next(), Some("*2"), "got {reply}");
7298        lines.next().expect("the cursor header");
7299        let cursor = lines.next().expect("the cursor").to_owned();
7300        let header = lines.next().expect("the member header");
7301        let n: usize = header[1..].parse().expect("a member count");
7302        let mut members = Vec::with_capacity(n);
7303        for _ in 0..n {
7304            lines.next().expect("a member header");
7305            members.push(lines.next().expect("a member").to_owned());
7306        }
7307        (cursor, members)
7308    }
7309
7310    #[test]
7311    fn popping_takes_a_member_off_the_set_and_hands_it_back() {
7312        let mut f = Fixture::new();
7313        f.run(&[b"SADD", b"s", b"a", b"b", b"c", b"d"]);
7314
7315        let one = f.run(&[b"SPOP", b"s"]);
7316        assert!(
7317            ["$1\r\na\r\n", "$1\r\nb\r\n", "$1\r\nc\r\n", "$1\r\nd\r\n"].contains(&one.as_str()),
7318            "got {one}"
7319        );
7320        assert_eq!(f.run(&[b"SCARD", b"s"]), ":3\r\n");
7321
7322        // A count takes that many, and the last one takes the key with it.
7323        let (_, rest) = ("", f.run(&[b"SPOP", b"s", b"3"]));
7324        assert!(rest.starts_with("*3\r\n"), "got {rest}");
7325        assert_eq!(f.run(&[b"EXISTS", b"s"]), ":0\r\n");
7326        // And a pop at a key that is not there is a nil, not an empty bulk.
7327        assert_eq!(f.run(&[b"SPOP", b"s"]), "$-1\r\n");
7328        assert_eq!(f.run(&[b"SPOP", b"s", b"2"]), "*0\r\n");
7329    }
7330
7331    #[test]
7332    fn the_two_draws_disagree_about_the_reply_type_and_they_are_right_to() {
7333        // The one place in the server where the reply type carries something
7334        // the command name does not. SPOP's members are distinct so a RESP3
7335        // client can build a set out of them. SRANDMEMBER with a negative count
7336        // can hand back the same member three times, and a set would lose two.
7337        let mut f = Fixture::new();
7338        f.run(&[b"HELLO", b"3"]);
7339        f.run(&[b"SADD", b"s", b"a", b"b", b"c"]);
7340
7341        assert!(f.run(&[b"SPOP", b"s", b"2"]).starts_with("~2\r\n"));
7342        // And a positive count is an array too, since Redis makes it one.
7343        assert!(f.run(&[b"SRANDMEMBER", b"s", b"1"]).starts_with("*1\r\n"));
7344
7345        // A negative count against a set of one is where the difference bites:
7346        // the same member three times, which is a three element reply and would
7347        // have been a one element reply if it had gone out as a set.
7348        f.run(&[b"SADD", b"one", b"z"]);
7349        assert_eq!(
7350            f.run(&[b"SRANDMEMBER", b"one", b"-3"]),
7351            "*3\r\n$1\r\nz\r\n$1\r\nz\r\n$1\r\nz\r\n"
7352        );
7353    }
7354
7355    #[test]
7356    fn drawing_a_member_removes_nothing_and_says_nil_at_a_missing_key() {
7357        let mut f = Fixture::new();
7358        f.run(&[b"SADD", b"s", b"only"]);
7359        assert_eq!(f.run(&[b"SRANDMEMBER", b"s"]), "$4\r\nonly\r\n");
7360        assert_eq!(f.run(&[b"SRANDMEMBER", b"s"]), "$4\r\nonly\r\n");
7361        assert_eq!(f.run(&[b"SCARD", b"s"]), ":1\r\n");
7362
7363        assert_eq!(f.run(&[b"SRANDMEMBER", b"nope"]), "$-1\r\n");
7364        // The count form answers an empty array rather than a nil, which is the
7365        // pair of answers Redis gives and is not the pair it looks like.
7366        assert_eq!(f.run(&[b"SRANDMEMBER", b"nope", b"3"]), "*0\r\n");
7367        assert_eq!(f.run(&[b"SRANDMEMBER", b"nope", b"-3"]), "*0\r\n");
7368        // Asking for more than is there answers all of it once and not padding.
7369        assert_eq!(f.run(&[b"SRANDMEMBER", b"s", b"9"]), "*1\r\n$4\r\nonly\r\n");
7370    }
7371
7372    #[test]
7373    fn a_pop_count_that_is_not_a_positive_number_says_so() {
7374        let mut f = Fixture::new();
7375        f.run(&[b"SADD", b"s", b"a"]);
7376        let bad = "-ERR value is out of range, must be positive\r\n";
7377        assert_eq!(f.run(&[b"SPOP", b"s", b"-1"]), bad);
7378        assert_eq!(f.run(&[b"SPOP", b"s", b"abc"]), bad);
7379        assert_eq!(f.run(&[b"SCARD", b"s"]), ":1\r\n", "and took nothing");
7380        // Zero is allowed and is a real answer rather than an error.
7381        assert_eq!(f.run(&[b"SPOP", b"s", b"0"]), "*0\r\n");
7382        assert_eq!(f.run(&[b"EXISTS", b"s"]), ":1\r\n");
7383    }
7384
7385    #[test]
7386    fn a_scan_walks_a_set_of_any_size_exactly_once() {
7387        let mut f = Fixture::new();
7388        let members: Vec<Vec<u8>> = (0..300).map(|i| format!("m{i}").into_bytes()).collect();
7389        let args: Vec<&[u8]> = [&b"SADD"[..], &b"s"[..]]
7390            .into_iter()
7391            .chain(members.iter().map(Vec::as_slice))
7392            .collect();
7393        f.run(&args);
7394
7395        let mut seen = Vec::new();
7396        let mut cursor = "0".to_owned();
7397        loop {
7398            let reply = f.run(&[b"SSCAN", b"s", cursor.as_bytes()]);
7399            let (next, got) = split_scan(&reply);
7400            seen.extend(got);
7401            cursor = next;
7402            if cursor == "0" {
7403                break;
7404            }
7405        }
7406        seen.sort();
7407        seen.dedup();
7408        assert_eq!(seen.len(), 300, "a walk saw a member twice or missed one");
7409
7410        // A set small enough to be a listpack answers in one call whatever
7411        // cursor it was handed, which is what Redis does for that encoding.
7412        f.run(&[b"SADD", b"small", b"a", b"b", b"c"]);
7413        let (cursor, got) = split_scan(&f.run(&[b"SSCAN", b"small", b"0", b"COUNT", b"1"]));
7414        assert_eq!(cursor, "0");
7415        assert_eq!(got.len(), 3);
7416        // And a key that is not there is a finished scan of nothing.
7417        assert_eq!(f.run(&[b"SSCAN", b"nope", b"0"]), "*2\r\n$1\r\n0\r\n*0\r\n");
7418    }
7419
7420    #[test]
7421    fn a_scan_takes_match_and_count_and_refuses_anything_else() {
7422        let mut f = Fixture::new();
7423        f.run(&[b"SADD", b"s", b"aa", b"ab", b"ba", b"12", b"13"]);
7424
7425        let (_, got) = split_scan(&f.run(&[b"SSCAN", b"s", b"0", b"MATCH", b"a*"]));
7426        let mut got = got;
7427        got.sort();
7428        assert_eq!(got, ["aa", "ab"]);
7429
7430        // An integer member has no digits stored anywhere, so MATCH is the one
7431        // place a scan pays to write some.
7432        let (_, got) = split_scan(&f.run(&[b"SSCAN", b"s", b"0", b"MATCH", b"1?"]));
7433        let mut got = got;
7434        got.sort();
7435        assert_eq!(got, ["12", "13"]);
7436
7437        assert_eq!(f.run(&[b"SSCAN", b"s", b"abc"]), "-ERR invalid cursor\r\n");
7438        assert_eq!(f.run(&[b"SSCAN", b"s", b"-1"]), "-ERR invalid cursor\r\n");
7439        assert_eq!(
7440            f.run(&[b"SSCAN", b"s", b"0", b"NOPE", b"1"]),
7441            "-ERR syntax error\r\n"
7442        );
7443        // A count under one is a syntax error and not a range error, which is
7444        // the odder of Redis's two answers and the reason it is copied exactly.
7445        assert_eq!(
7446            f.run(&[b"SSCAN", b"s", b"0", b"COUNT", b"0"]),
7447            "-ERR syntax error\r\n"
7448        );
7449    }
7450
7451    #[test]
7452    fn moving_a_member_takes_it_off_one_set_and_puts_it_on_another() {
7453        let mut f = Fixture::new();
7454        f.run(&[b"SADD", b"src", b"a", b"b"]);
7455        f.run(&[b"SADD", b"dst", b"c"]);
7456
7457        assert_eq!(f.run(&[b"SMOVE", b"src", b"dst", b"a"]), ":1\r\n");
7458        assert_eq!(f.run(&[b"SISMEMBER", b"src", b"a"]), ":0\r\n");
7459        assert_eq!(f.run(&[b"SISMEMBER", b"dst", b"a"]), ":1\r\n");
7460        // A member that is not in the source is a zero and moves nothing.
7461        assert_eq!(f.run(&[b"SMOVE", b"src", b"dst", b"zz"]), ":0\r\n");
7462        assert_eq!(f.run(&[b"SCARD", b"dst"]), ":2\r\n");
7463
7464        // A destination that does not exist gets made, and a source that runs
7465        // out goes away.
7466        assert_eq!(f.run(&[b"SMOVE", b"src", b"fresh", b"b"]), ":1\r\n");
7467        assert_eq!(f.run(&[b"EXISTS", b"src"]), ":0\r\n");
7468        assert_eq!(f.run(&[b"SMEMBERS", b"fresh"]), "*1\r\n$1\r\nb\r\n");
7469    }
7470
7471    #[test]
7472    fn moving_checks_the_types_in_the_order_redis_checks_them() {
7473        // Not the order it looks like it should be. A source that is not there
7474        // answers zero without ever looking at the destination, so this is a
7475        // zero and not a WRONGTYPE even though the destination is a string.
7476        let mut f = Fixture::new();
7477        f.run(&[b"SET", b"str", b"v"]);
7478        f.run(&[b"SADD", b"set", b"a"]);
7479
7480        let wrong = "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n";
7481        assert_eq!(f.run(&[b"SMOVE", b"nope", b"str", b"a"]), ":0\r\n");
7482        assert_eq!(f.run(&[b"SMOVE", b"str", b"set", b"a"]), wrong);
7483        assert_eq!(f.run(&[b"SMOVE", b"set", b"str", b"a"]), wrong);
7484        assert_eq!(f.run(&[b"SPOP", b"str"]), wrong);
7485        assert_eq!(f.run(&[b"SRANDMEMBER", b"str"]), wrong);
7486        assert_eq!(f.run(&[b"SSCAN", b"str", b"0"]), wrong);
7487        assert_eq!(
7488            f.run(&[b"SISMEMBER", b"set", b"a"]),
7489            ":1\r\n",
7490            "and none of that moved anything"
7491        );
7492    }
7493
7494    #[test]
7495    fn a_scan_leaves_nothing_half_written_when_its_arguments_are_wrong() {
7496        // SSCAN writes an outer array header before it walks, so it is the
7497        // command most likely to get bytes out in front of an error.
7498        let mut f = Fixture::new();
7499        f.run(&[b"SADD", b"s", b"a"]);
7500        for bad in [
7501            &[b"SSCAN".as_slice(), b"s", b"abc"][..],
7502            &[b"SSCAN".as_slice(), b"s", b"0", b"COUNT", b"nope"][..],
7503            &[b"SSCAN".as_slice(), b"s", b"0", b"MATCH"][..],
7504        ] {
7505            let reply = f.run(bad);
7506            assert!(reply.starts_with("-ERR"), "got {reply}");
7507            assert!(!reply.contains('*'), "an array header went out in front");
7508        }
7509    }
7510
7511    #[test]
7512    fn a_hash_writes_reads_and_deletes_its_fields() {
7513        let mut f = Fixture::new();
7514        assert_eq!(f.run(&[b"HSET", b"h", b"a", b"1", b"b", b"2"]), ":2\r\n");
7515        assert_eq!(f.run(&[b"HSET", b"h", b"a", b"9"]), ":0\r\n", "a was there");
7516        assert_eq!(f.run(&[b"HGET", b"h", b"a"]), "$1\r\n9\r\n");
7517        assert_eq!(f.run(&[b"HGET", b"h", b"nope"]), "$-1\r\n");
7518        assert_eq!(f.run(&[b"HGET", b"nokey", b"a"]), "$-1\r\n");
7519        assert_eq!(f.run(&[b"HLEN", b"h"]), ":2\r\n");
7520        assert_eq!(f.run(&[b"HEXISTS", b"h", b"a"]), ":1\r\n");
7521        assert_eq!(f.run(&[b"HEXISTS", b"h", b"nope"]), ":0\r\n");
7522        assert_eq!(f.run(&[b"HSTRLEN", b"h", b"a"]), ":1\r\n");
7523        assert_eq!(f.run(&[b"HSTRLEN", b"h", b"nope"]), ":0\r\n");
7524
7525        // The value the client sent is `9`, so HGET h b must not find the `2`
7526        // that is a value. A search with a step of one would have.
7527        assert_eq!(f.run(&[b"HGET", b"h", b"2"]), "$-1\r\n");
7528
7529        assert_eq!(f.run(&[b"HDEL", b"h", b"a", b"nope"]), ":1\r\n");
7530        assert_eq!(f.run(&[b"HDEL", b"h", b"b"]), ":1\r\n");
7531        assert_eq!(
7532            f.run(&[b"EXISTS", b"h"]),
7533            ":0\r\n",
7534            "and losing the last field lost the key"
7535        );
7536    }
7537
7538    #[test]
7539    fn hgetall_answers_a_map_on_resp3_and_the_same_pairs_flat_on_resp2() {
7540        let mut f = Fixture::new();
7541        f.run(&[b"HSET", b"h", b"a", b"1"]);
7542        assert_eq!(f.run(&[b"HGETALL", b"h"]), "*2\r\n$1\r\na\r\n$1\r\n1\r\n");
7543        assert_eq!(f.run(&[b"HGETALL", b"nokey"]), "*0\r\n");
7544        assert_eq!(f.run(&[b"HKEYS", b"h"]), "*1\r\n$1\r\na\r\n");
7545        assert_eq!(f.run(&[b"HVALS", b"h"]), "*1\r\n$1\r\n1\r\n");
7546        assert_eq!(f.run(&[b"HKEYS", b"nokey"]), "*0\r\n");
7547
7548        f.run(&[b"HELLO", b"3"]);
7549        assert_eq!(f.run(&[b"HGETALL", b"h"]), "%1\r\n$1\r\na\r\n$1\r\n1\r\n");
7550        assert_eq!(
7551            f.run(&[b"HGETALL", b"nokey"]),
7552            "%0\r\n",
7553            "a missing key is the empty hash and never a nil"
7554        );
7555        assert_eq!(
7556            f.run(&[b"HKEYS", b"h"]),
7557            "*1\r\n$1\r\na\r\n",
7558            "and the two that answer one side stay arrays"
7559        );
7560    }
7561
7562    #[test]
7563    fn hmget_answers_once_per_field_and_hmset_answers_ok() {
7564        let mut f = Fixture::new();
7565        assert_eq!(f.run(&[b"HMSET", b"h", b"a", b"1", b"c", b"3"]), "+OK\r\n");
7566        assert_eq!(
7567            f.run(&[b"HMGET", b"h", b"a", b"b", b"c"]),
7568            "*3\r\n$1\r\n1\r\n$-1\r\n$1\r\n3\r\n",
7569            "the reply is positional, so b is a nil and not a gap"
7570        );
7571        assert_eq!(
7572            f.run(&[b"HMGET", b"nokey", b"a", b"b"]),
7573            "*2\r\n$-1\r\n$-1\r\n",
7574            "and a missing key is all nils rather than an empty array"
7575        );
7576
7577        assert_eq!(f.run(&[b"HSETNX", b"h", b"a", b"9"]), ":0\r\n");
7578        assert_eq!(f.run(&[b"HSETNX", b"h", b"z", b"9"]), ":1\r\n");
7579        assert_eq!(f.run(&[b"HGET", b"h", b"a"]), "$1\r\n1\r\n");
7580    }
7581
7582    #[test]
7583    fn a_hash_counts_up_and_says_so_when_it_cannot() {
7584        let mut f = Fixture::new();
7585        assert_eq!(f.run(&[b"HINCRBY", b"h", b"n", b"5"]), ":5\r\n");
7586        assert_eq!(f.run(&[b"HINCRBY", b"h", b"n", b"-7"]), ":-2\r\n");
7587        assert_eq!(f.run(&[b"HGET", b"h", b"n"]), "$2\r\n-2\r\n");
7588        assert_eq!(
7589            f.run(&[b"HINCRBYFLOAT", b"h", b"f", b"10.5"]),
7590            "$4\r\n10.5\r\n",
7591            "a bulk string and not a double, on both protocols"
7592        );
7593
7594        f.run(&[b"HSET", b"h", b"s", b"words"]);
7595        let bad = f.run(&[b"HINCRBY", b"h", b"s", b"1"]);
7596        assert!(
7597            bad.starts_with("-ERR hash value is not an integer"),
7598            "{bad}"
7599        );
7600        let bad = f.run(&[b"HINCRBY", b"h", b"n", b"nope"]);
7601        assert!(
7602            bad.starts_with("-ERR value is not an integer"),
7603            "a bad argument is not yet a hash value, {bad}"
7604        );
7605        assert_eq!(
7606            f.run(&[b"HGET", b"h", b"s"]),
7607            "$5\r\nwords\r\n",
7608            "and neither of them wrote anything"
7609        );
7610    }
7611
7612    #[test]
7613    fn a_hash_scan_walks_every_pair_once_and_novalues_drops_half_of_it() {
7614        // Fourteen minutes under Miri at five hundred, which was the slowest
7615        // test in this crate that was not about megabytes. What the count has
7616        // to be is more than one page of the cursor, and the count below is
7617        // thirty two, so ninety six is three pages and asks the same question.
7618        let fields = if cfg!(miri) { 96 } else { 500 };
7619        let mut f = Fixture::new();
7620        for i in 0..fields {
7621            let field = format!("field-{i}");
7622            let value = format!("value-{i}");
7623            f.run(&[b"HSET", b"h", field.as_bytes(), value.as_bytes()]);
7624        }
7625
7626        let mut seen: Vec<String> = Vec::new();
7627        let mut cursor = "0".to_owned();
7628        loop {
7629            let reply = f.run(&[b"HSCAN", b"h", cursor.as_bytes(), b"COUNT", b"32"]);
7630            let (next, items) = scan_reply(&reply);
7631            assert_eq!(items.len() % 2, 0, "a pair went out half written");
7632            for pair in items.chunks(2) {
7633                assert_eq!(
7634                    pair[0].strip_prefix("field-"),
7635                    pair[1].strip_prefix("value-"),
7636                    "a field came back with someone else's value"
7637                );
7638                seen.push(pair[0].clone());
7639            }
7640            cursor = next;
7641            if cursor == "0" {
7642                break;
7643            }
7644        }
7645        seen.sort();
7646        seen.dedup();
7647        assert_eq!(seen.len(), fields, "every field once and only once");
7648
7649        let (_, items) = scan_reply(&f.run(&[b"HSCAN", b"h", b"0", b"NOVALUES", b"COUNT", b"32"]));
7650        assert!(
7651            items.iter().all(|s| s.starts_with("field-")),
7652            "NOVALUES still sent the values"
7653        );
7654
7655        let last = fields - 1;
7656        let (_, one) = scan_reply(&f.run(&[
7657            b"HSCAN",
7658            b"h",
7659            b"0",
7660            b"MATCH",
7661            format!("field-{last}").as_bytes(),
7662            b"COUNT",
7663            b"1000",
7664        ]));
7665        assert_eq!(
7666            one,
7667            [format!("field-{last}"), format!("value-{last}")],
7668            "MATCH is on the field"
7669        );
7670    }
7671
7672    #[test]
7673    fn hrandfield_draws_what_it_was_asked_for_and_nests_values_on_resp3() {
7674        let mut f = Fixture::new();
7675        f.run(&[b"HSET", b"h", b"a", b"1"]);
7676        assert_eq!(f.run(&[b"HRANDFIELD", b"h"]), "$1\r\na\r\n");
7677        assert_eq!(f.run(&[b"HRANDFIELD", b"nokey"]), "$-1\r\n");
7678        assert_eq!(f.run(&[b"HRANDFIELD", b"nokey", b"3"]), "*0\r\n");
7679        assert_eq!(
7680            f.run(&[b"HRANDFIELD", b"h", b"3"]),
7681            "*1\r\n$1\r\na\r\n",
7682            "a positive count is capped at the size of the hash"
7683        );
7684        assert_eq!(
7685            f.run(&[b"HRANDFIELD", b"h", b"-3"]),
7686            "*3\r\n$1\r\na\r\n$1\r\na\r\n$1\r\na\r\n",
7687            "and a negative one repeats itself"
7688        );
7689        assert_eq!(
7690            f.run(&[b"HRANDFIELD", b"h", b"1", b"WITHVALUES"]),
7691            "*2\r\n$1\r\na\r\n$1\r\n1\r\n",
7692            "flat on RESP2"
7693        );
7694
7695        f.run(&[b"HELLO", b"3"]);
7696        assert_eq!(
7697            f.run(&[b"HRANDFIELD", b"h", b"1", b"WITHVALUES"]),
7698            "*1\r\n*2\r\n$1\r\na\r\n$1\r\n1\r\n",
7699            "and nested on RESP3, but still an array and never a map"
7700        );
7701    }
7702
7703    #[test]
7704    fn every_hash_command_says_wrongtype_and_writes_nothing() {
7705        let mut f = Fixture::new();
7706        f.run(&[b"SET", b"str", b"v"]);
7707        let wrong = "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n";
7708
7709        for cmd in [
7710            &[b"HSET".as_slice(), b"str", b"f", b"v"][..],
7711            &[b"HMSET".as_slice(), b"str", b"f", b"v"][..],
7712            &[b"HSETNX".as_slice(), b"str", b"f", b"v"][..],
7713            &[b"HGET".as_slice(), b"str", b"f"][..],
7714            &[b"HMGET".as_slice(), b"str", b"f"][..],
7715            &[b"HDEL".as_slice(), b"str", b"f"][..],
7716            &[b"HLEN".as_slice(), b"str"][..],
7717            &[b"HEXISTS".as_slice(), b"str", b"f"][..],
7718            &[b"HSTRLEN".as_slice(), b"str", b"f"][..],
7719            &[b"HGETALL".as_slice(), b"str"][..],
7720            &[b"HKEYS".as_slice(), b"str"][..],
7721            &[b"HVALS".as_slice(), b"str"][..],
7722            &[b"HINCRBY".as_slice(), b"str", b"f", b"1"][..],
7723            &[b"HINCRBYFLOAT".as_slice(), b"str", b"f", b"1"][..],
7724            &[b"HRANDFIELD".as_slice(), b"str"][..],
7725            &[b"HRANDFIELD".as_slice(), b"str", b"2"][..],
7726            &[b"HSCAN".as_slice(), b"str", b"0"][..],
7727        ] {
7728            let reply = f.run(cmd);
7729            assert_eq!(reply, wrong, "{:?}", cmd[0]);
7730        }
7731        assert_eq!(
7732            f.run(&[b"GET", b"str"]),
7733            "$1\r\nv\r\n",
7734            "and none of them touched the value"
7735        );
7736    }
7737
7738    #[test]
7739    fn a_hash_scan_leaves_nothing_half_written_when_its_arguments_are_wrong() {
7740        let mut f = Fixture::new();
7741        f.run(&[b"HSET", b"h", b"f", b"v"]);
7742        for bad in [
7743            &[b"HSCAN".as_slice(), b"h", b"abc"][..],
7744            &[b"HSCAN".as_slice(), b"h", b"0", b"COUNT", b"nope"][..],
7745            &[b"HSCAN".as_slice(), b"h", b"0", b"COUNT", b"0"][..],
7746            &[b"HSCAN".as_slice(), b"h", b"0", b"MATCH"][..],
7747        ] {
7748            let reply = f.run(bad);
7749            assert!(reply.starts_with("-ERR"), "got {reply}");
7750            assert!(!reply.contains('*'), "an array header went out in front");
7751        }
7752    }
7753
7754    #[test]
7755    fn a_field_deadline_goes_on_and_comes_back_in_all_four_units() {
7756        let mut f = Fixture::new();
7757        f.run(&[b"HSET", b"h", b"a", b"1", b"b", b"2"]);
7758        assert_eq!(
7759            f.run(&[b"HEXPIRE", b"h", b"100", b"FIELDS", b"1", b"a"]),
7760            "*1\r\n:1\r\n"
7761        );
7762        assert_eq!(
7763            f.run(&[b"HTTL", b"h", b"FIELDS", b"3", b"a", b"b", b"nope"]),
7764            "*3\r\n:100\r\n:-1\r\n:-2\r\n",
7765            "one answer per field, and the two sentinels are TTL's own"
7766        );
7767
7768        // The same deadline in the other three units, all of them derived from
7769        // the one number the store kept.
7770        let ms = int_reply(&f.run(&[b"HPTTL", b"h", b"FIELDS", b"1", b"a"]));
7771        assert!((99_000..=100_000).contains(&ms), "got {ms}");
7772        let at = int_reply(&f.run(&[b"HEXPIRETIME", b"h", b"FIELDS", b"1", b"a"]));
7773        let at_ms = int_reply(&f.run(&[b"HPEXPIRETIME", b"h", b"FIELDS", b"1", b"a"]));
7774        assert_eq!(at, at_ms.div_euclid(1000) + i64::from(at_ms % 1000 != 0));
7775        assert!(at_ms > 1_700_000_000_000, "an absolute moment, got {at_ms}");
7776
7777        assert_eq!(
7778            f.run(&[b"HPERSIST", b"h", b"FIELDS", b"3", b"a", b"b", b"nope"]),
7779            "*3\r\n:1\r\n:-1\r\n:-2\r\n",
7780            "one for the deadline taken off, and it does not say what it was"
7781        );
7782        assert_eq!(
7783            f.run(&[b"HTTL", b"h", b"FIELDS", b"1", b"a"]),
7784            "*1\r\n:-1\r\n"
7785        );
7786        assert_eq!(
7787            f.run(&[b"HGET", b"h", b"a"]),
7788            "$1\r\n1\r\n",
7789            "and the field is still there with the value it had"
7790        );
7791    }
7792
7793    #[test]
7794    fn a_deadline_that_has_already_gone_deletes_the_field_now() {
7795        let mut f = Fixture::new();
7796        f.run(&[b"HSET", b"h", b"a", b"1", b"b", b"2"]);
7797        assert_eq!(
7798            f.run(&[b"HEXPIREAT", b"h", b"1", b"FIELDS", b"1", b"a"]),
7799            "*1\r\n:2\r\n",
7800            "two, and not one, because nothing was stored"
7801        );
7802        assert_eq!(f.run(&[b"HGET", b"h", b"a"]), "$-1\r\n");
7803        assert_eq!(f.run(&[b"HLEN", b"h"]), ":1\r\n");
7804
7805        assert_eq!(
7806            f.run(&[b"HPEXPIREAT", b"h", b"1", b"FIELDS", b"1", b"b"]),
7807            "*1\r\n:2\r\n"
7808        );
7809        assert_eq!(
7810            f.run(&[b"EXISTS", b"h"]),
7811            ":0\r\n",
7812            "and the last field going took the key with it"
7813        );
7814
7815        // Zero is a delete and not an error, where minus one is an error. That
7816        // is Redis's split and it is easy to get backwards.
7817        f.run(&[b"HSET", b"h", b"a", b"1"]);
7818        assert_eq!(
7819            f.run(&[b"HEXPIRE", b"h", b"0", b"FIELDS", b"1", b"a"]),
7820            "*1\r\n:2\r\n"
7821        );
7822    }
7823
7824    #[test]
7825    fn a_field_is_gone_once_its_moment_passes() {
7826        let mut f = Fixture::new();
7827        f.run(&[b"HSET", b"h", b"a", b"1", b"b", b"2"]);
7828        assert_eq!(
7829            f.run(&[b"HPEXPIRE", b"h", b"20", b"FIELDS", b"1", b"a"]),
7830            "*1\r\n:1\r\n"
7831        );
7832        assert_eq!(f.run(&[b"HGET", b"h", b"a"]), "$1\r\n1\r\n", "not yet");
7833
7834        // Time moves once per turn of the event loop and nowhere else, so a
7835        // test moves it by hand rather than by sleeping. There is nothing to
7836        // sleep for: the deadline is a number and so is the clock.
7837        f.server.advance_clock_ms(60);
7838        assert_eq!(f.run(&[b"HLEN", b"h"]), ":1\r\n");
7839        assert_eq!(f.run(&[b"HGET", b"h", b"a"]), "$-1\r\n");
7840        assert_eq!(
7841            f.run(&[b"HGETALL", b"h"]),
7842            "*2\r\n$1\r\nb\r\n$1\r\n2\r\n",
7843            "and the walks do not hand back a field that has expired"
7844        );
7845    }
7846
7847    #[test]
7848    fn a_missing_key_answers_the_no_field_sentinel_for_every_field() {
7849        let mut f = Fixture::new();
7850        for cmd in [
7851            &[
7852                b"HEXPIRE".as_slice(),
7853                b"nokey",
7854                b"100",
7855                b"FIELDS",
7856                b"2",
7857                b"a",
7858                b"b",
7859            ][..],
7860            &[b"HTTL".as_slice(), b"nokey", b"FIELDS", b"2", b"a", b"b"][..],
7861            &[b"HPTTL".as_slice(), b"nokey", b"FIELDS", b"2", b"a", b"b"][..],
7862            &[
7863                b"HEXPIRETIME".as_slice(),
7864                b"nokey",
7865                b"FIELDS",
7866                b"2",
7867                b"a",
7868                b"b",
7869            ][..],
7870            &[
7871                b"HPERSIST".as_slice(),
7872                b"nokey",
7873                b"FIELDS",
7874                b"2",
7875                b"a",
7876                b"b",
7877            ][..],
7878        ] {
7879            assert_eq!(f.run(cmd), "*2\r\n:-2\r\n:-2\r\n", "{:?}", cmd[0]);
7880        }
7881    }
7882
7883    #[test]
7884    fn writing_a_field_clears_the_deadline_that_was_on_it() {
7885        let mut f = Fixture::new();
7886        f.run(&[b"HSET", b"h", b"a", b"1"]);
7887        f.run(&[b"HEXPIRE", b"h", b"100", b"FIELDS", b"1", b"a"]);
7888        f.run(&[b"HSET", b"h", b"a", b"2"]);
7889        assert_eq!(
7890            f.run(&[b"HTTL", b"h", b"FIELDS", b"1", b"a"]),
7891            "*1\r\n:-1\r\n",
7892            "Redis has done this since 7.4, and it is why HGETEX exists"
7893        );
7894    }
7895
7896    #[test]
7897    fn the_four_conditions_reach_the_store_the_way_they_were_written() {
7898        let mut f = Fixture::new();
7899        f.run(&[b"HSET", b"h", b"a", b"1"]);
7900        assert_eq!(
7901            f.run(&[b"HEXPIRE", b"h", b"100", b"XX", b"FIELDS", b"1", b"a"]),
7902            "*1\r\n:0\r\n",
7903            "XX on a field with no deadline changes nothing"
7904        );
7905        assert_eq!(
7906            f.run(&[b"HEXPIRE", b"h", b"100", b"NX", b"FIELDS", b"1", b"a"]),
7907            "*1\r\n:1\r\n"
7908        );
7909        assert_eq!(
7910            f.run(&[b"HEXPIRE", b"h", b"200", b"NX", b"FIELDS", b"1", b"a"]),
7911            "*1\r\n:0\r\n",
7912            "and NX will not move one that is already there"
7913        );
7914        assert_eq!(
7915            f.run(&[b"HEXPIRE", b"h", b"50", b"GT", b"FIELDS", b"1", b"a"]),
7916            "*1\r\n:0\r\n"
7917        );
7918        assert_eq!(
7919            f.run(&[b"HEXPIRE", b"h", b"500", b"GT", b"FIELDS", b"1", b"a"]),
7920            "*1\r\n:1\r\n"
7921        );
7922        assert_eq!(
7923            f.run(&[b"HEXPIRE", b"h", b"50", b"LT", b"FIELDS", b"1", b"a"]),
7924            "*1\r\n:1\r\n"
7925        );
7926        assert_eq!(
7927            f.run(&[b"HTTL", b"h", b"FIELDS", b"1", b"a"]),
7928            "*1\r\n:50\r\n"
7929        );
7930    }
7931
7932    #[test]
7933    fn the_field_ttl_family_leaves_nothing_half_written_on_a_bad_argument() {
7934        let mut f = Fixture::new();
7935        f.run(&[b"HSET", b"h", b"a", b"1"]);
7936        for (bad, want) in [
7937            (
7938                &[b"HEXPIRE".as_slice(), b"h", b"-1", b"FIELDS", b"1", b"a"][..],
7939                "-ERR invalid expire time, must be >= 0",
7940            ),
7941            (
7942                &[
7943                    b"HEXPIRE".as_slice(),
7944                    b"h",
7945                    b"9999999999999999",
7946                    b"FIELDS",
7947                    b"1",
7948                    b"a",
7949                ][..],
7950                "-ERR invalid expire time in 'hexpire' command",
7951            ),
7952            (
7953                &[b"HEXPIRE".as_slice(), b"h", b"100", b"FIELD", b"1", b"a"][..],
7954                "-ERR wrong number of arguments for 'hexpire' command",
7955            ),
7956            (
7957                &[b"HEXPIRE".as_slice(), b"h", b"100", b"FIELDS", b"0", b"a"][..],
7958                "-ERR Parameter `numFields` should be greater than 0",
7959            ),
7960            (
7961                &[b"HEXPIRE".as_slice(), b"h", b"100", b"FIELDS", b"2", b"a"][..],
7962                "-ERR wrong number of arguments",
7963            ),
7964            (
7965                &[b"HTTL".as_slice(), b"h", b"FIELDS", b"3", b"a", b"b"][..],
7966                "-ERR wrong number of arguments",
7967            ),
7968        ] {
7969            let reply = f.run(bad);
7970            assert!(reply.starts_with(want), "wanted {want}, got {reply}");
7971            assert!(!reply.contains('*'), "an array header went out in front");
7972        }
7973        assert_eq!(
7974            f.run(&[b"HTTL", b"h", b"FIELDS", b"1", b"a"]),
7975            "*1\r\n:-1\r\n",
7976            "and not one of them put a deadline on anything"
7977        );
7978    }
7979
7980    #[test]
7981    fn every_field_ttl_command_says_wrongtype_and_writes_nothing() {
7982        let mut f = Fixture::new();
7983        f.run(&[b"SET", b"str", b"v"]);
7984        let wrong = "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n";
7985
7986        for cmd in [
7987            &[b"HEXPIRE".as_slice(), b"str", b"100", b"FIELDS", b"1", b"f"][..],
7988            &[
7989                b"HPEXPIRE".as_slice(),
7990                b"str",
7991                b"100",
7992                b"FIELDS",
7993                b"1",
7994                b"f",
7995            ][..],
7996            &[
7997                b"HEXPIREAT".as_slice(),
7998                b"str",
7999                b"9999999999",
8000                b"FIELDS",
8001                b"1",
8002                b"f",
8003            ][..],
8004            &[
8005                b"HPEXPIREAT".as_slice(),
8006                b"str",
8007                b"9999999999999",
8008                b"FIELDS",
8009                b"1",
8010                b"f",
8011            ][..],
8012            &[b"HTTL".as_slice(), b"str", b"FIELDS", b"1", b"f"][..],
8013            &[b"HPTTL".as_slice(), b"str", b"FIELDS", b"1", b"f"][..],
8014            &[b"HEXPIRETIME".as_slice(), b"str", b"FIELDS", b"1", b"f"][..],
8015            &[b"HPEXPIRETIME".as_slice(), b"str", b"FIELDS", b"1", b"f"][..],
8016            &[b"HPERSIST".as_slice(), b"str", b"FIELDS", b"1", b"f"][..],
8017        ] {
8018            assert_eq!(f.run(cmd), wrong, "{:?}", cmd[0]);
8019        }
8020        assert_eq!(
8021            f.run(&[b"GET", b"str"]),
8022            "$1\r\nv\r\n",
8023            "and none of them touched the value"
8024        );
8025    }
8026
8027    #[test]
8028    fn hgetdel_hands_the_value_out_and_then_takes_the_field() {
8029        let mut f = Fixture::new();
8030        f.run(&[b"HSET", b"h", b"a", b"1", b"b", b"2"]);
8031        assert_eq!(
8032            f.run(&[b"HGETDEL", b"h", b"FIELDS", b"2", b"a", b"nope"]),
8033            "*2\r\n$1\r\n1\r\n$-1\r\n",
8034            "positional, so the field that was not there is a nil in its place"
8035        );
8036        assert_eq!(f.run(&[b"HLEN", b"h"]), ":1\r\n");
8037        assert_eq!(
8038            f.run(&[b"HGETDEL", b"nokey", b"FIELDS", b"1", b"a"]),
8039            "*1\r\n$-1\r\n"
8040        );
8041        assert_eq!(
8042            f.run(&[b"HGETDEL", b"h", b"FIELDS", b"1", b"b"]),
8043            "*1\r\n$1\r\n2\r\n"
8044        );
8045        assert_eq!(
8046            f.run(&[b"EXISTS", b"h"]),
8047            ":0\r\n",
8048            "and the last field took the key"
8049        );
8050    }
8051
8052    #[test]
8053    fn hgetex_reads_and_moves_the_deadline_in_one_command() {
8054        let mut f = Fixture::new();
8055        f.run(&[b"HSET", b"h", b"a", b"1"]);
8056        assert_eq!(
8057            f.run(&[b"HGETEX", b"h", b"FIELDS", b"1", b"a"]),
8058            "*1\r\n$1\r\n1\r\n"
8059        );
8060        assert_eq!(
8061            f.run(&[b"HTTL", b"h", b"FIELDS", b"1", b"a"]),
8062            "*1\r\n:-1\r\n",
8063            "no option means leave it alone, which is the one place this is not GETEX"
8064        );
8065
8066        f.run(&[b"HGETEX", b"h", b"EX", b"100", b"FIELDS", b"1", b"a"]);
8067        assert_eq!(
8068            f.run(&[b"HTTL", b"h", b"FIELDS", b"1", b"a"]),
8069            "*1\r\n:100\r\n"
8070        );
8071        f.run(&[b"HGETEX", b"h", b"FIELDS", b"1", b"a"]);
8072        assert_eq!(
8073            f.run(&[b"HTTL", b"h", b"FIELDS", b"1", b"a"]),
8074            "*1\r\n:100\r\n",
8075            "and a plain read really does leave it alone"
8076        );
8077        assert_eq!(
8078            f.run(&[b"HGETEX", b"h", b"PERSIST", b"FIELDS", b"1", b"a"]),
8079            "*1\r\n$1\r\n1\r\n"
8080        );
8081        assert_eq!(
8082            f.run(&[b"HTTL", b"h", b"FIELDS", b"1", b"a"]),
8083            "*1\r\n:-1\r\n"
8084        );
8085
8086        assert_eq!(
8087            f.run(&[b"HGETEX", b"h", b"EXAT", b"1", b"FIELDS", b"1", b"a"]),
8088            "*1\r\n$1\r\n1\r\n",
8089            "the value goes out before the deadline that has already gone is applied"
8090        );
8091        assert_eq!(f.run(&[b"EXISTS", b"h"]), ":0\r\n");
8092        assert_eq!(
8093            f.run(&[b"HGETEX", b"nokey", b"EX", b"100", b"FIELDS", b"1", b"a"]),
8094            "*1\r\n$-1\r\n"
8095        );
8096    }
8097
8098    #[test]
8099    fn hsetex_writes_all_of_it_or_none_of_it() {
8100        let mut f = Fixture::new();
8101        assert_eq!(
8102            f.run(&[b"HSETEX", b"h", b"FIELDS", b"1", b"a", b"1"]),
8103            ":1\r\n"
8104        );
8105        assert_eq!(f.run(&[b"HGET", b"h", b"a"]), "$1\r\n1\r\n");
8106        assert_eq!(
8107            f.run(&[
8108                b"HSETEX", b"h", b"FNX", b"FIELDS", b"2", b"a", b"9", b"new", b"9"
8109            ]),
8110            ":0\r\n",
8111            "FNX wants every field named to be missing"
8112        );
8113        assert_eq!(f.run(&[b"HGET", b"h", b"a"]), "$1\r\n1\r\n");
8114        assert_eq!(
8115            f.run(&[b"HEXISTS", b"h", b"new"]),
8116            ":0\r\n",
8117            "and none of the list was written"
8118        );
8119        assert_eq!(
8120            f.run(&[
8121                b"HSETEX", b"h", b"FXX", b"FIELDS", b"2", b"a", b"9", b"nope", b"9"
8122            ]),
8123            ":0\r\n",
8124            "and FXX wants every one of them to be there"
8125        );
8126        assert_eq!(f.run(&[b"HGET", b"h", b"a"]), "$1\r\n1\r\n");
8127        assert_eq!(
8128            f.run(&[b"HSETEX", b"h", b"FXX", b"FIELDS", b"1", b"a", b"9"]),
8129            ":1\r\n"
8130        );
8131        assert_eq!(f.run(&[b"HGET", b"h", b"a"]), "$1\r\n9\r\n");
8132
8133        assert_eq!(
8134            f.run(&[b"HSETEX", b"gone", b"FXX", b"FIELDS", b"1", b"a", b"1"]),
8135            ":0\r\n"
8136        );
8137        assert_eq!(
8138            f.run(&[b"EXISTS", b"gone"]),
8139            ":0\r\n",
8140            "a key with no fields cannot meet FXX and is not created trying"
8141        );
8142    }
8143
8144    #[test]
8145    fn hsetex_clears_the_deadline_unless_it_is_told_to_keep_it() {
8146        let mut f = Fixture::new();
8147        f.run(&[b"HSETEX", b"h", b"EX", b"100", b"FIELDS", b"1", b"a", b"1"]);
8148        assert_eq!(
8149            f.run(&[b"HTTL", b"h", b"FIELDS", b"1", b"a"]),
8150            "*1\r\n:100\r\n"
8151        );
8152
8153        f.run(&[b"HSETEX", b"h", b"KEEPTTL", b"FIELDS", b"1", b"a", b"2"]);
8154        assert_eq!(f.run(&[b"HGET", b"h", b"a"]), "$1\r\n2\r\n");
8155        assert_eq!(
8156            f.run(&[b"HTTL", b"h", b"FIELDS", b"1", b"a"]),
8157            "*1\r\n:100\r\n",
8158            "KEEPTTL put back what the write cleared"
8159        );
8160
8161        f.run(&[b"HSETEX", b"h", b"FIELDS", b"1", b"a", b"3"]);
8162        assert_eq!(
8163            f.run(&[b"HTTL", b"h", b"FIELDS", b"1", b"a"]),
8164            "*1\r\n:-1\r\n",
8165            "and without it a write clears the deadline the way HSET does"
8166        );
8167
8168        // Any order, because Redis reads these in a loop and not in a fixed
8169        // sequence.
8170        assert_eq!(
8171            f.run(&[
8172                b"HSETEX", b"h", b"PX", b"100000", b"FXX", b"FIELDS", b"1", b"a", b"4"
8173            ]),
8174            ":1\r\n"
8175        );
8176        assert_eq!(
8177            f.run(&[b"HTTL", b"h", b"FIELDS", b"1", b"a"]),
8178            "*1\r\n:100\r\n"
8179        );
8180
8181        assert_eq!(
8182            f.run(&[b"HSETEX", b"h", b"EXAT", b"1", b"FIELDS", b"1", b"a", b"5"]),
8183            ":1\r\n",
8184            "written, and not the separate code the HEXPIRE family has for this"
8185        );
8186        assert_eq!(
8187            f.run(&[b"EXISTS", b"h"]),
8188            ":0\r\n",
8189            "and storing it and then removing it emptied the hash"
8190        );
8191    }
8192
8193    #[test]
8194    fn the_last_three_hash_commands_word_their_mistakes_their_own_way() {
8195        let mut f = Fixture::new();
8196        f.run(&[b"HSET", b"h", b"a", b"1"]);
8197        for (bad, want) in [
8198            // HGETDEL has three sentences of its own for these three mistakes.
8199            (
8200                &[b"HGETDEL".as_slice(), b"h", b"FIELDS", b"0", b"a"][..],
8201                "-ERR Number of fields must be a positive integer",
8202            ),
8203            (
8204                &[b"HGETDEL".as_slice(), b"h", b"FIELDS", b"2", b"a"][..],
8205                "-ERR The `numfields` parameter must match the number of arguments",
8206            ),
8207            (
8208                &[b"HGETDEL".as_slice(), b"h", b"FIELD", b"1", b"a"][..],
8209                "-ERR Mandatory argument FIELDS is missing or not at the right position",
8210            ),
8211            // And HGETEX and HSETEX have three different ones between them.
8212            (
8213                &[b"HGETEX".as_slice(), b"h", b"FIELDS", b"0", b"a"][..],
8214                "-ERR invalid number of fields",
8215            ),
8216            (
8217                &[b"HGETEX".as_slice(), b"h", b"FIELDS", b"2", b"a"][..],
8218                "-ERR wrong number of arguments",
8219            ),
8220            (
8221                &[b"HGETEX".as_slice(), b"h", b"FIELD", b"1", b"a"][..],
8222                "-ERR unknown argument: FIELD",
8223            ),
8224            (
8225                &[
8226                    b"HGETEX".as_slice(),
8227                    b"h",
8228                    b"KEEPTTL",
8229                    b"FIELDS",
8230                    b"1",
8231                    b"a",
8232                ][..],
8233                "-ERR unknown argument: KEEPTTL",
8234            ),
8235            (
8236                &[
8237                    b"HGETEX".as_slice(),
8238                    b"h",
8239                    b"EX",
8240                    b"100",
8241                    b"PERSIST",
8242                    b"FIELDS",
8243                    b"1",
8244                    b"a",
8245                ][..],
8246                "-ERR Only one of EX, PX, EXAT, PXAT or PERSIST arguments can be specified",
8247            ),
8248            (
8249                &[
8250                    b"HSETEX".as_slice(),
8251                    b"h",
8252                    b"EX",
8253                    b"1",
8254                    b"KEEPTTL",
8255                    b"FIELDS",
8256                    b"1",
8257                    b"a",
8258                    b"1",
8259                ][..],
8260                "-ERR Only one of EX, PX, EXAT, PXAT or KEEPTTL arguments can be specified",
8261            ),
8262            (
8263                &[
8264                    b"HSETEX".as_slice(),
8265                    b"h",
8266                    b"FNX",
8267                    b"FXX",
8268                    b"FIELDS",
8269                    b"1",
8270                    b"a",
8271                    b"1",
8272                ][..],
8273                "-ERR Only one of FXX or FNX arguments can be specified",
8274            ),
8275            (
8276                &[
8277                    b"HSETEX".as_slice(),
8278                    b"h",
8279                    b"FIELDS",
8280                    b"2",
8281                    b"a",
8282                    b"1",
8283                    b"b",
8284                ][..],
8285                "-ERR wrong number of arguments",
8286            ),
8287            (
8288                &[
8289                    b"HGETEX".as_slice(),
8290                    b"h",
8291                    b"EX",
8292                    b"-1",
8293                    b"FIELDS",
8294                    b"1",
8295                    b"a",
8296                ][..],
8297                "-ERR invalid expire time, must be >= 0",
8298            ),
8299            (
8300                &[
8301                    b"HGETEX".as_slice(),
8302                    b"h",
8303                    b"PXAT",
8304                    b"99999999999999",
8305                    b"FIELDS",
8306                    b"1",
8307                    b"a",
8308                ][..],
8309                "-ERR invalid expire time in 'hgetex' command",
8310            ),
8311            (
8312                &[
8313                    b"HSETEX".as_slice(),
8314                    b"h",
8315                    b"EX",
8316                    b"abc",
8317                    b"FIELDS",
8318                    b"1",
8319                    b"a",
8320                    b"1",
8321                ][..],
8322                "-ERR value is not an integer or out of range",
8323            ),
8324        ] {
8325            let reply = f.run(bad);
8326            assert!(reply.starts_with(want), "wanted {want}, got {reply}");
8327            assert!(!reply.contains('*'), "an array header went out in front");
8328        }
8329        assert_eq!(
8330            f.run(&[b"HGET", b"h", b"a"]),
8331            "$1\r\n1\r\n",
8332            "and not one of them wrote anything"
8333        );
8334        assert_eq!(
8335            f.run(&[b"HTTL", b"h", b"FIELDS", b"1", b"a"]),
8336            "*1\r\n:-1\r\n"
8337        );
8338    }
8339
8340    #[test]
8341    fn the_last_three_hash_commands_say_wrongtype_and_write_nothing() {
8342        let mut f = Fixture::new();
8343        f.run(&[b"SET", b"str", b"v"]);
8344        let wrong = "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n";
8345        for cmd in [
8346            &[b"HGETDEL".as_slice(), b"str", b"FIELDS", b"1", b"f"][..],
8347            &[b"HGETEX".as_slice(), b"str", b"FIELDS", b"1", b"f"][..],
8348            &[
8349                b"HGETEX".as_slice(),
8350                b"str",
8351                b"EX",
8352                b"100",
8353                b"FIELDS",
8354                b"1",
8355                b"f",
8356            ][..],
8357            &[b"HSETEX".as_slice(), b"str", b"FIELDS", b"1", b"f", b"v"][..],
8358        ] {
8359            assert_eq!(f.run(cmd), wrong, "{:?}", cmd[0]);
8360        }
8361        assert_eq!(f.run(&[b"GET", b"str"]), "$1\r\nv\r\n");
8362    }
8363
8364    /// The two orders `HIMPORT` juggles, which are not the same order.
8365    ///
8366    /// Values arrive in the order the fields were declared in and the hash is
8367    /// built in sorted order, so the first value is not generally the first
8368    /// field. And the sort is by length before bytes, which nothing else here
8369    /// sorts names with: `b` comes before `aa` where a plain byte comparison
8370    /// would put `aa` first. Both read off 8.10.1.
8371    #[test]
8372    fn himport_writes_declared_values_into_sorted_fields() {
8373        let mut f = Fixture::new();
8374        assert_eq!(
8375            f.run(&[b"HIMPORT", b"PREPARE", b"shape", b"b", b"aa", b"a"]),
8376            "+OK\r\n"
8377        );
8378        assert_eq!(
8379            f.run(&[b"HIMPORT", b"SET", b"k", b"shape", b"1", b"2", b"3"]),
8380            "+OK\r\n"
8381        );
8382        assert_eq!(f.run(&[b"HKEYS", b"k"]), bulks(&["a", "b", "aa"]));
8383        assert_eq!(
8384            f.run(&[b"HGETALL", b"k"]),
8385            bulks(&["a", "3", "b", "1", "aa", "2"])
8386        );
8387    }
8388
8389    /// It replaces the key rather than writing over it, so a field the fieldset
8390    /// does not name is gone afterwards and so is the deadline.
8391    #[test]
8392    fn himport_set_replaces_the_whole_key() {
8393        let mut f = Fixture::new();
8394        f.run(&[b"HSET", b"k", b"gone", b"old", b"a", b"old"]);
8395        f.run(&[b"EXPIRE", b"k", b"100"]);
8396        f.run(&[b"HIMPORT", b"PREPARE", b"shape", b"a", b"b"]);
8397        assert_eq!(
8398            f.run(&[b"HIMPORT", b"SET", b"k", b"shape", b"1", b"2"]),
8399            "+OK\r\n"
8400        );
8401        assert_eq!(f.run(&[b"HGETALL", b"k"]), bulks(&["a", "1", "b", "2"]));
8402        assert_eq!(f.run(&[b"TTL", b"k"]), ":-1\r\n");
8403    }
8404
8405    /// A fieldset is connection state. `SELECT` leaves them alone and `RESET`
8406    /// throws them away, and a key built from one outlives it.
8407    #[test]
8408    fn himport_fieldsets_belong_to_the_connection_and_not_to_the_keyspace() {
8409        let mut f = Fixture::new();
8410        f.run(&[b"HIMPORT", b"PREPARE", b"shape", b"a"]);
8411        f.run(&[b"SELECT", b"1"]);
8412        assert_eq!(
8413            f.run(&[b"HIMPORT", b"SET", b"k", b"shape", b"1"]),
8414            "+OK\r\n"
8415        );
8416        f.run(&[b"SELECT", b"0"]);
8417        assert_eq!(f.run(&[b"RESET"]), "+RESET\r\n");
8418        assert_eq!(
8419            f.run(&[b"HIMPORT", b"SET", b"k2", b"shape", b"1"]),
8420            "-ERR no such fieldset\r\n"
8421        );
8422    }
8423
8424    /// Which complaint wins when a line is wrong in more than one place.
8425    ///
8426    /// The type of the key beats both of the others, so a `HIMPORT SET` against
8427    /// a string is a WRONGTYPE even when the fieldset is missing too, which is
8428    /// the ordering a real server has and not the one the argument order
8429    /// suggests.
8430    #[test]
8431    fn himport_complains_in_the_order_a_real_server_does() {
8432        let mut f = Fixture::new();
8433        f.run(&[b"SET", b"str", b"v"]);
8434        f.run(&[b"HIMPORT", b"PREPARE", b"shape", b"a", b"b"]);
8435        let wrong = "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n";
8436        assert_eq!(
8437            f.run(&[b"HIMPORT", b"SET", b"str", b"nope", b"1"]),
8438            wrong,
8439            "the type beats a missing fieldset"
8440        );
8441        assert_eq!(
8442            f.run(&[b"HIMPORT", b"SET", b"str", b"shape", b"1"]),
8443            wrong,
8444            "and it beats a value count that does not fit"
8445        );
8446        assert_eq!(
8447            f.run(&[b"HIMPORT", b"SET", b"k", b"nope", b"1"]),
8448            "-ERR no such fieldset\r\n"
8449        );
8450        // One sentence for too few and for too many alike.
8451        for values in [&[b"1".as_slice()][..], &[b"1".as_slice(), b"2", b"3"][..]] {
8452            let mut line: Vec<&[u8]> = vec![b"HIMPORT", b"SET", b"k", b"shape"];
8453            line.extend_from_slice(values);
8454            assert_eq!(
8455                f.run(&line),
8456                "-ERR value count does not match fieldset field count\r\n",
8457                "{} values into two fields",
8458                values.len()
8459            );
8460        }
8461        assert_eq!(f.run(&[b"EXISTS", b"k"]), ":0\r\n");
8462    }
8463
8464    /// The arity of each subcommand, and the unknown one.
8465    #[test]
8466    fn himport_checks_each_subcommand_count_under_its_own_name() {
8467        let mut f = Fixture::new();
8468        assert_eq!(
8469            f.run(&[b"HIMPORT"]),
8470            "-ERR wrong number of arguments for 'himport' command\r\n"
8471        );
8472        for (rest, name) in [
8473            (&["PREPARE"][..], "prepare"),
8474            (&["PREPARE", "fs"][..], "prepare"),
8475            (&["SET"][..], "set"),
8476            (&["SET", "k"][..], "set"),
8477            (&["SET", "k", "fs"][..], "set"),
8478            (&["DISCARD"][..], "discard"),
8479            (&["DISCARD", "a", "b"][..], "discard"),
8480            (&["DISCARDALL", "x"][..], "discardall"),
8481        ] {
8482            let mut line: Vec<&[u8]> = vec![b"HIMPORT"];
8483            line.extend(rest.iter().map(|a| a.as_bytes()));
8484            assert_eq!(
8485                f.run(&line),
8486                format!("-ERR wrong number of arguments for 'himport|{name}' command\r\n"),
8487                "HIMPORT {}",
8488                rest.join(" ")
8489            );
8490        }
8491        assert_eq!(
8492            f.run(&[b"HIMPORT", b"NOPE", b"x"]),
8493            "-ERR unknown subcommand 'NOPE'. Try HIMPORT HELP.\r\n"
8494        );
8495    }
8496
8497    /// A `PREPARE` that fails leaves the name pointing where it pointed, which
8498    /// is the answer of the two that could not be guessed from outside.
8499    #[test]
8500    fn a_failed_himport_prepare_leaves_the_old_fieldset_alone() {
8501        let mut f = Fixture::new();
8502        f.run(&[b"HIMPORT", b"PREPARE", b"shape", b"a", b"b"]);
8503        assert_eq!(
8504            f.run(&[b"HIMPORT", b"PREPARE", b"shape", b"c", b"c"]),
8505            "-ERR duplicate field name in fieldset\r\n"
8506        );
8507        assert_eq!(
8508            f.run(&[b"HIMPORT", b"SET", b"k", b"shape", b"1", b"2"]),
8509            "+OK\r\n"
8510        );
8511        assert_eq!(f.run(&[b"HGETALL", b"k"]), bulks(&["a", "1", "b", "2"]));
8512    }
8513
8514    /// Preparing the same name twice replaces it, and the two discards count
8515    /// what they took rather than answering OK.
8516    #[test]
8517    fn himport_prepare_replaces_and_the_discards_count() {
8518        let mut f = Fixture::new();
8519        f.run(&[b"HIMPORT", b"PREPARE", b"shape", b"a", b"b"]);
8520        f.run(&[b"HIMPORT", b"PREPARE", b"shape", b"z"]);
8521        assert_eq!(
8522            f.run(&[b"HIMPORT", b"SET", b"k", b"shape", b"1"]),
8523            "+OK\r\n"
8524        );
8525        assert_eq!(f.run(&[b"HGETALL", b"k"]), bulks(&["z", "1"]));
8526
8527        assert_eq!(f.run(&[b"HIMPORT", b"DISCARD", b"shape"]), ":1\r\n");
8528        assert_eq!(f.run(&[b"HIMPORT", b"DISCARD", b"shape"]), ":0\r\n");
8529        f.run(&[b"HIMPORT", b"PREPARE", b"one", b"a"]);
8530        f.run(&[b"HIMPORT", b"PREPARE", b"two", b"a"]);
8531        assert_eq!(f.run(&[b"HIMPORT", b"DISCARDALL"]), ":2\r\n");
8532        assert_eq!(f.run(&[b"HIMPORT", b"DISCARDALL"]), ":0\r\n");
8533    }
8534
8535    /// The one integer of a single element array reply.
8536    /// The number out of a plain integer reply.
8537    ///
8538    /// [`int_reply`] is the same thing wrapped in a one element array, which is
8539    /// the shape every hash field command answers in.
8540    fn int(reply: &str) -> i64 {
8541        let body = reply
8542            .strip_prefix(':')
8543            .and_then(|s| s.strip_suffix("\r\n"))
8544            .unwrap_or_else(|| panic!("wanted an integer, got {reply}"));
8545        body.parse().expect("an integer")
8546    }
8547
8548    fn int_reply(reply: &str) -> i64 {
8549        let body = reply
8550            .strip_prefix("*1\r\n:")
8551            .and_then(|s| s.strip_suffix("\r\n"))
8552            .unwrap_or_else(|| panic!("wanted one integer, got {reply}"));
8553        body.parse().expect("an integer")
8554    }
8555
8556    /// The cursor and the flat items of a scan reply.
8557    fn scan_reply(reply: &str) -> (String, Vec<String>) {
8558        let mut lines = reply.split("\r\n");
8559        assert_eq!(lines.next(), Some("*2"), "got {reply}");
8560        lines.next().expect("the cursor header");
8561        let cursor = lines.next().expect("a cursor").to_owned();
8562        let header = lines.next().expect("an item count");
8563        let n: usize = header[1..].parse().expect("a count");
8564        let mut items = Vec::with_capacity(n);
8565        for _ in 0..n {
8566            lines.next().expect("an item header");
8567            items.push(lines.next().expect("an item").to_owned());
8568        }
8569        (cursor, items)
8570    }
8571
8572    /// The members of a set reply, sorted, since none of these promise an
8573    /// order and a test that asserted one would be asserting an accident.
8574    fn sorted(reply: &str) -> Vec<String> {
8575        let mut lines = reply.split("\r\n");
8576        let header = lines.next().expect("a header");
8577        assert!(
8578            header.starts_with('*') || header.starts_with('~'),
8579            "got {reply}"
8580        );
8581        let n: usize = header[1..].parse().expect("a member count");
8582        let mut got = Vec::with_capacity(n);
8583        for _ in 0..n {
8584            lines.next().expect("a member header");
8585            got.push(lines.next().expect("a member").to_owned());
8586        }
8587        got.sort();
8588        got
8589    }
8590
8591    #[test]
8592    fn the_algebra_answers_what_the_sets_share_and_do_not() {
8593        let mut f = Fixture::new();
8594        f.run(&[b"SADD", b"a", b"1", b"2", b"3"]);
8595        f.run(&[b"SADD", b"b", b"2", b"3", b"4"]);
8596        f.run(&[b"SADD", b"c", b"3", b"4", b"5"]);
8597
8598        assert_eq!(sorted(&f.run(&[b"SINTER", b"a", b"b", b"c"])), ["3"]);
8599        assert_eq!(
8600            sorted(&f.run(&[b"SUNION", b"a", b"b", b"c"])),
8601            ["1", "2", "3", "4", "5"]
8602        );
8603        assert_eq!(sorted(&f.run(&[b"SDIFF", b"a", b"b"])), ["1"]);
8604        assert_eq!(sorted(&f.run(&[b"SINTER", b"a"])), ["1", "2", "3"]);
8605
8606        // A key that is not there is an empty set, which empties an
8607        // intersection and does nothing at all to a union.
8608        assert_eq!(f.run(&[b"SINTER", b"a", b"nope"]), "*0\r\n");
8609        assert_eq!(sorted(&f.run(&[b"SUNION", b"a", b"nope"])), ["1", "2", "3"]);
8610        assert_eq!(f.run(&[b"SDIFF", b"nope", b"a"]), "*0\r\n");
8611        assert_eq!(f.run(&[b"DBSIZE"]), ":3\r\n", "and none of it made a key");
8612    }
8613
8614    #[test]
8615    fn the_algebra_answers_a_set_on_resp3_and_an_array_on_resp2() {
8616        let mut f = Fixture::new();
8617        f.run(&[b"SADD", b"a", b"x"]);
8618        assert_eq!(f.run(&[b"SINTER", b"a"]), "*1\r\n$1\r\nx\r\n");
8619        assert_eq!(f.run(&[b"SUNION", b"a"]), "*1\r\n$1\r\nx\r\n");
8620        assert_eq!(f.run(&[b"SDIFF", b"a"]), "*1\r\n$1\r\nx\r\n");
8621
8622        f.run(&[b"HELLO", b"3"]);
8623        assert_eq!(f.run(&[b"SINTER", b"a"]), "~1\r\n$1\r\nx\r\n");
8624        assert_eq!(f.run(&[b"SUNION", b"a"]), "~1\r\n$1\r\nx\r\n");
8625        assert_eq!(f.run(&[b"SDIFF", b"a"]), "~1\r\n$1\r\nx\r\n");
8626        assert_eq!(f.run(&[b"SINTER", b"nope"]), "~0\r\n");
8627    }
8628
8629    #[test]
8630    fn a_store_form_writes_a_key_and_answers_how_big_it_is() {
8631        let mut f = Fixture::new();
8632        f.run(&[b"SADD", b"a", b"1", b"2", b"3"]);
8633        f.run(&[b"SADD", b"b", b"2", b"3", b"4"]);
8634
8635        assert_eq!(f.run(&[b"SINTERSTORE", b"d", b"a", b"b"]), ":2\r\n");
8636        assert_eq!(sorted(&f.run(&[b"SMEMBERS", b"d"])), ["2", "3"]);
8637        assert_eq!(f.run(&[b"SUNIONSTORE", b"d", b"a", b"b"]), ":4\r\n");
8638        assert_eq!(sorted(&f.run(&[b"SMEMBERS", b"d"])), ["1", "2", "3", "4"]);
8639        assert_eq!(f.run(&[b"SDIFFSTORE", b"d", b"a", b"b"]), ":1\r\n");
8640        assert_eq!(f.run(&[b"SMEMBERS", b"d"]), "*1\r\n$1\r\n1\r\n");
8641
8642        // An empty answer deletes the destination rather than leaving an empty
8643        // set behind, and the destination may be one of the sources.
8644        assert_eq!(f.run(&[b"SDIFFSTORE", b"d", b"a", b"a"]), ":0\r\n");
8645        assert_eq!(f.run(&[b"EXISTS", b"d"]), ":0\r\n");
8646        assert_eq!(f.run(&[b"SINTERSTORE", b"a", b"a", b"b"]), ":2\r\n");
8647        assert_eq!(sorted(&f.run(&[b"SMEMBERS", b"a"])), ["2", "3"]);
8648
8649        // And a destination holding something else is overwritten, the same way
8650        // SET overwrites, rather than refused.
8651        f.run(&[b"SET", b"str", b"v"]);
8652        assert_eq!(f.run(&[b"SUNIONSTORE", b"str", b"b"]), ":3\r\n");
8653        assert_eq!(f.run(&[b"TYPE", b"str"]), "+set\r\n");
8654    }
8655
8656    #[test]
8657    fn sintercard_counts_without_building_and_stops_at_a_limit() {
8658        let mut f = Fixture::new();
8659        f.run(&[b"SADD", b"a", b"1", b"2", b"3", b"4"]);
8660        f.run(&[b"SADD", b"b", b"2", b"3", b"4", b"5"]);
8661
8662        assert_eq!(f.run(&[b"SINTERCARD", b"2", b"a", b"b"]), ":3\r\n");
8663        assert_eq!(
8664            f.run(&[b"SINTERCARD", b"2", b"a", b"b", b"LIMIT", b"2"]),
8665            ":2\r\n"
8666        );
8667        assert_eq!(
8668            f.run(&[b"SINTERCARD", b"2", b"a", b"b", b"LIMIT", b"0"]),
8669            ":3\r\n",
8670            "a limit of zero is no limit"
8671        );
8672        assert_eq!(f.run(&[b"SINTERCARD", b"1", b"a"]), ":4\r\n");
8673        assert_eq!(f.run(&[b"SINTERCARD", b"2", b"a", b"nope"]), ":0\r\n");
8674
8675        // The counted keys are what make its three error messages its own.
8676        assert_eq!(
8677            f.run(&[b"SINTERCARD", b"0", b"a"]),
8678            "-ERR numkeys should be greater than 0\r\n"
8679        );
8680        assert_eq!(
8681            f.run(&[b"SINTERCARD", b"abc", b"a"]),
8682            "-ERR numkeys should be greater than 0\r\n"
8683        );
8684        assert_eq!(
8685            f.run(&[b"SINTERCARD", b"3", b"a", b"b"]),
8686            "-ERR Number of keys can't be greater than number of args\r\n"
8687        );
8688        assert_eq!(
8689            f.run(&[b"SINTERCARD", b"2", b"a", b"b", b"LIMIT", b"-1"]),
8690            "-ERR LIMIT can't be negative\r\n"
8691        );
8692        assert_eq!(
8693            f.run(&[b"SINTERCARD", b"2", b"a", b"b", b"NOPE", b"1"]),
8694            "-ERR syntax error\r\n"
8695        );
8696        // A key really can be called LIMIT, which is why the count exists.
8697        f.run(&[b"SADD", b"LIMIT", b"2"]);
8698        assert_eq!(f.run(&[b"SINTERCARD", b"2", b"a", b"LIMIT"]), ":1\r\n");
8699    }
8700
8701    /// The two Redis 8.10 added, which are SINTERCARD's shape over a union and
8702    /// over a difference. Every number here was read off 8.10.1 first.
8703    #[test]
8704    fn sunioncard_and_sdiffcard_count_without_building() {
8705        let mut f = Fixture::new();
8706        f.run(&[b"SADD", b"a", b"1", b"2", b"3", b"4"]);
8707        f.run(&[b"SADD", b"b", b"3", b"4", b"5", b"6"]);
8708
8709        assert_eq!(f.run(&[b"SUNIONCARD", b"2", b"a", b"b"]), ":6\r\n");
8710        assert_eq!(
8711            f.run(&[b"SUNIONCARD", b"2", b"a", b"b", b"LIMIT", b"2"]),
8712            ":2\r\n"
8713        );
8714        assert_eq!(
8715            f.run(&[b"SUNIONCARD", b"2", b"a", b"b", b"LIMIT", b"0"]),
8716            ":6\r\n",
8717            "a limit of zero is no limit"
8718        );
8719        assert_eq!(f.run(&[b"SUNIONCARD", b"1", b"a"]), ":4\r\n");
8720        assert_eq!(
8721            f.run(&[b"SUNIONCARD", b"2", b"a", b"nope"]),
8722            ":4\r\n",
8723            "a missing key adds nothing to a union"
8724        );
8725
8726        assert_eq!(f.run(&[b"SDIFFCARD", b"2", b"a", b"b"]), ":2\r\n");
8727        assert_eq!(
8728            f.run(&[b"SDIFFCARD", b"2", b"a", b"b", b"LIMIT", b"1"]),
8729            ":1\r\n"
8730        );
8731        assert_eq!(
8732            f.run(&[b"SDIFFCARD", b"2", b"b", b"a"]),
8733            ":2\r\n",
8734            "a difference is not symmetric"
8735        );
8736        assert_eq!(f.run(&[b"SDIFFCARD", b"1", b"a"]), ":4\r\n");
8737        assert_eq!(f.run(&[b"SDIFFCARD", b"2", b"a", b"nope"]), ":4\r\n");
8738        assert_eq!(
8739            f.run(&[b"SDIFFCARD", b"2", b"nope", b"a"]),
8740            ":0\r\n",
8741            "nothing taken away from nothing"
8742        );
8743
8744        // The same three messages SINTERCARD has, because the line is the same
8745        // line and is parsed once for all three.
8746        for name in [b"SUNIONCARD".as_slice(), b"SDIFFCARD".as_slice()] {
8747            assert_eq!(
8748                f.run(&[name, b"0", b"a"]),
8749                "-ERR numkeys should be greater than 0\r\n"
8750            );
8751            assert_eq!(
8752                f.run(&[name, b"abc", b"a"]),
8753                "-ERR numkeys should be greater than 0\r\n"
8754            );
8755            assert_eq!(
8756                f.run(&[name, b"-1", b"a"]),
8757                "-ERR numkeys should be greater than 0\r\n"
8758            );
8759            assert_eq!(
8760                f.run(&[name, b"3", b"a", b"b"]),
8761                "-ERR Number of keys can't be greater than number of args\r\n"
8762            );
8763            assert_eq!(
8764                f.run(&[name, b"2", b"a", b"b", b"LIMIT", b"-1"]),
8765                "-ERR LIMIT can't be negative\r\n"
8766            );
8767            assert_eq!(
8768                f.run(&[name, b"2", b"a", b"b", b"LIMIT", b"abc"]),
8769                "-ERR LIMIT can't be negative\r\n",
8770                "a LIMIT that is not a number gets the negative message too"
8771            );
8772            assert_eq!(
8773                f.run(&[name, b"2", b"a", b"b", b"NOPE", b"1"]),
8774                "-ERR syntax error\r\n"
8775            );
8776            assert_eq!(
8777                f.run(&[name, b"2", b"a", b"b", b"LIMIT"]),
8778                "-ERR syntax error\r\n"
8779            );
8780            assert_eq!(
8781                f.run(&[name, b"2", b"a", b"b", b"LIMIT", b"1", b"X"]),
8782                "-ERR syntax error\r\n"
8783            );
8784        }
8785
8786        // And a key called LIMIT is a key, here as much as on SINTERCARD.
8787        f.run(&[b"SADD", b"LIMIT", b"2"]);
8788        assert_eq!(f.run(&[b"SUNIONCARD", b"2", b"a", b"LIMIT"]), ":4\r\n");
8789        assert_eq!(f.run(&[b"SDIFFCARD", b"2", b"a", b"LIMIT"]), ":3\r\n");
8790    }
8791
8792    #[test]
8793    fn the_algebra_answers_wrongtype_before_it_writes_anything() {
8794        let mut f = Fixture::new();
8795        f.run(&[b"SADD", b"a", b"1"]);
8796        f.run(&[b"SADD", b"d", b"old"]);
8797        f.run(&[b"SET", b"str", b"v"]);
8798
8799        let wrong = "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n";
8800        for bad in [
8801            &[b"SINTER".as_slice(), b"a", b"str"][..],
8802            &[b"SUNION".as_slice(), b"str"][..],
8803            &[b"SDIFF".as_slice(), b"a", b"str"][..],
8804            &[b"SINTERCARD".as_slice(), b"2", b"a", b"str"][..],
8805            &[b"SINTERSTORE".as_slice(), b"d", b"a", b"str"][..],
8806            &[b"SUNIONSTORE".as_slice(), b"d", b"str"][..],
8807            &[b"SDIFFSTORE".as_slice(), b"d", b"a", b"str"][..],
8808        ] {
8809            let reply = f.run(bad);
8810            assert_eq!(reply, wrong, "for {:?}", bad[0]);
8811        }
8812        assert_eq!(
8813            f.run(&[b"SMEMBERS", b"d"]),
8814            "*1\r\n$3\r\nold\r\n",
8815            "and the destination was left alone every time"
8816        );
8817    }
8818
8819    /// The leak a set can spring that nothing on the wire would ever show: the
8820    /// key goes, the body does not, and `DBSIZE` looks right the whole time.
8821    /// Not under Miri. What this claims is that memory does not grow over two
8822    /// hundred passes, so the passes are the claim rather than the way it
8823    /// happens to be written, and two hundred passes of a two hundred member
8824    /// collection is forty thousand trips through dispatch, which is what an
8825    /// interpreter charges for. A count small enough to run there would leave a
8826    /// server that reclaims nothing inside the bound and the test would pass on
8827    /// a leak. Nothing about memory safety goes uninterpreted either way: this
8828    /// is an accounting claim, and the same commands are run a few at a time by
8829    /// the tests around it.
8830    #[cfg_attr(miri, ignore = "the volume is the claim")]
8831    #[test]
8832    fn churning_sets_does_not_grow_the_server() {
8833        let mut f = Fixture::new();
8834        let members: Vec<Vec<u8>> = (0..200).map(|i| format!("m{i}").into_bytes()).collect();
8835        let args: Vec<&[u8]> = std::iter::once(&b"SADD"[..])
8836            .chain(std::iter::once(&b"s"[..]))
8837            .chain(members.iter().map(Vec::as_slice))
8838            .collect();
8839
8840        f.run(&args);
8841        f.run(&[b"DEL", b"s"]);
8842        f.server.compact_step();
8843        let after_first = f.server.memory_bytes();
8844
8845        for _ in 0..200 {
8846            f.run(&args);
8847            f.run(&[b"DEL", b"s"]);
8848            f.server.compact_step();
8849        }
8850        assert_eq!(f.run(&[b"DBSIZE"]), ":0\r\n");
8851        assert!(
8852            f.server.memory_bytes() <= after_first * 2,
8853            "held {} after two hundred passes against {after_first} after one",
8854            f.server.memory_bytes()
8855        );
8856    }
8857
8858    // --------------------------------------------------------------- bitmaps
8859
8860    /// The two single bit commands, and the encoding rule underneath them.
8861    ///
8862    /// A write always leaves the value `raw` and a read never re-encodes, which
8863    /// is why the `int` key here is still `int` after a `GETBIT` and is `raw`
8864    /// with its first digit changed after a `SETBIT`.
8865    #[test]
8866    fn a_bit_is_written_and_read_back_and_a_write_unpacks_an_int() {
8867        let mut f = Fixture::new();
8868        assert_eq!(f.run(&[b"SETBIT", b"k", b"7", b"1"]), ":0\r\n");
8869        assert_eq!(f.run(&[b"GET", b"k"]), "$1\r\n\u{1}\r\n");
8870        assert_eq!(f.run(&[b"GETBIT", b"k", b"7"]), ":1\r\n");
8871        assert_eq!(f.run(&[b"GETBIT", b"k", b"6"]), ":0\r\n");
8872        assert_eq!(f.run(&[b"GETBIT", b"k", b"100"]), ":0\r\n");
8873        assert_eq!(f.run(&[b"SETBIT", b"k", b"7", b"0"]), ":1\r\n");
8874
8875        // Writing a nought past the end still creates the key and still pads.
8876        assert_eq!(f.run(&[b"SETBIT", b"nk", b"0", b"0"]), ":0\r\n");
8877        assert_eq!(f.run(&[b"STRLEN", b"nk"]), ":1\r\n");
8878        assert_eq!(f.run(&[b"OBJECT", b"ENCODING", b"nk"]), "$3\r\nraw\r\n");
8879
8880        f.run(&[b"SET", b"num", b"12345"]);
8881        assert_eq!(f.run(&[b"GETBIT", b"num", b"1"]), ":0\r\n");
8882        assert_eq!(f.run(&[b"OBJECT", b"ENCODING", b"num"]), "$3\r\nint\r\n");
8883        assert_eq!(f.run(&[b"SETBIT", b"num", b"1", b"1"]), ":0\r\n");
8884        assert_eq!(f.run(&[b"OBJECT", b"ENCODING", b"num"]), "$3\r\nraw\r\n");
8885        assert_eq!(f.run(&[b"GET", b"num"]), "$5\r\nq2345\r\n");
8886    }
8887
8888    /// Counting, in bytes and in bits.
8889    ///
8890    /// The `0 -5 BIT` row is 25 on a real 8.10.1 and Redis's own documentation
8891    /// says 22 for it. The server is the thing being copied here.
8892    #[test]
8893    fn bits_are_counted_over_a_range_of_bytes_or_of_bits() {
8894        let mut f = Fixture::new();
8895        f.run(&[b"SET", b"mykey", b"foobar"]);
8896        assert_eq!(f.run(&[b"BITCOUNT", b"mykey"]), ":26\r\n");
8897        assert_eq!(f.run(&[b"BITCOUNT", b"mykey", b"0", b"0"]), ":4\r\n");
8898        assert_eq!(f.run(&[b"BITCOUNT", b"mykey", b"1", b"1"]), ":6\r\n");
8899        assert_eq!(
8900            f.run(&[b"BITCOUNT", b"mykey", b"1", b"1", b"BYTE"]),
8901            ":6\r\n"
8902        );
8903        assert_eq!(
8904            f.run(&[b"BITCOUNT", b"mykey", b"0", b"-5", b"BIT"]),
8905            ":25\r\n"
8906        );
8907        assert_eq!(
8908            f.run(&[b"BITCOUNT", b"mykey", b"5", b"30", b"BIT"]),
8909            ":17\r\n"
8910        );
8911        assert_eq!(f.run(&[b"BITCOUNT", b"nokey"]), ":0\r\n");
8912
8913        // A start past the end is left where it is and the end is pulled back,
8914        // so the range comes out backwards and counts nothing.
8915        assert_eq!(f.run(&[b"BITCOUNT", b"mykey", b"10", b"20"]), ":0\r\n");
8916
8917        // A lone start is a syntax error here, where BITPOS allows it.
8918        assert_eq!(
8919            f.run(&[b"BITCOUNT", b"mykey", b"0"]),
8920            "-ERR syntax error\r\n"
8921        );
8922        assert_eq!(
8923            f.run(&[b"BITCOUNT", b"mykey", b"0", b"1", b"NIB"]),
8924            "-ERR syntax error\r\n"
8925        );
8926    }
8927
8928    /// Searching, and the one place a miss is not minus one.
8929    ///
8930    /// A search for a nought that runs to the end of the string answers the
8931    /// length in bits, because the string is treated as if it had noughts after
8932    /// it forever. Give it an explicit end and it answers minus one instead.
8933    #[test]
8934    fn a_search_for_a_nought_past_the_end_answers_the_length_in_bits() {
8935        let mut f = Fixture::new();
8936        f.run(&[b"SET", b"ones", b"\xff\xff\xff"]);
8937        assert_eq!(f.run(&[b"BITPOS", b"ones", b"0"]), ":24\r\n");
8938        assert_eq!(f.run(&[b"BITPOS", b"ones", b"0", b"0"]), ":24\r\n");
8939        assert_eq!(f.run(&[b"BITPOS", b"ones", b"0", b"0", b"-1"]), ":-1\r\n");
8940        assert_eq!(f.run(&[b"BITPOS", b"ones", b"0", b"0", b"3"]), ":-1\r\n");
8941        assert_eq!(f.run(&[b"BITPOS", b"ones", b"1"]), ":0\r\n");
8942
8943        f.run(&[b"SET", b"mid", b"\x00\xff\xf0"]);
8944        assert_eq!(f.run(&[b"BITPOS", b"mid", b"1", b"0"]), ":8\r\n");
8945        assert_eq!(f.run(&[b"BITPOS", b"mid", b"1", b"2"]), ":16\r\n");
8946        assert_eq!(
8947            f.run(&[b"BITPOS", b"mid", b"1", b"0", b"-1", b"BIT"]),
8948            ":8\r\n"
8949        );
8950
8951        // A missing key is all noughts, so a one is never found and a nought is
8952        // at position zero.
8953        assert_eq!(f.run(&[b"BITPOS", b"gone", b"1"]), ":-1\r\n");
8954        assert_eq!(f.run(&[b"BITPOS", b"gone", b"0"]), ":0\r\n");
8955    }
8956
8957    /// The eight operations, with the answers a real server gives for them.
8958    #[test]
8959    fn the_eight_combinations_write_what_a_real_server_writes() {
8960        let mut f = Fixture::new();
8961        f.run(&[b"SET", b"a", b"abc"]);
8962        f.run(&[b"SET", b"b", b"abd"]);
8963        let cases: &[(&[u8], &str)] = &[
8964            (b"AND", "ab`"),
8965            (b"OR", "abg"),
8966            (b"XOR", "\u{0}\u{0}\u{7}"),
8967            (b"DIFF", "\u{0}\u{0}\u{3}"),
8968            (b"DIFF1", "\u{0}\u{0}\u{4}"),
8969            (b"ANDOR", "ab`"),
8970            (b"ONE", "\u{0}\u{0}\u{7}"),
8971        ];
8972        for (op, want) in cases {
8973            assert_eq!(f.run(&[b"BITOP", op, b"d", b"a", b"b"]), ":3\r\n", "{op:?}");
8974            assert_eq!(
8975                f.run(&[b"GET", b"d"]),
8976                format!("$3\r\n{want}\r\n"),
8977                "{op:?}"
8978            );
8979        }
8980        // The one whose answer is not text, so it is compared as bytes.
8981        assert_eq!(f.run(&[b"BITOP", b"NOT", b"d", b"a"]), ":3\r\n");
8982        assert_eq!(f.raw(&[b"GET", b"d"]), b"$3\r\n\x9e\x9d\x9c\r\n".to_vec());
8983
8984        // A missing source is a string of noughts as long as it needs to be, so
8985        // an AND against one writes three zero bytes rather than nothing.
8986        assert_eq!(f.run(&[b"BITOP", b"AND", b"d", b"a", b"gone"]), ":3\r\n");
8987        assert_eq!(f.run(&[b"GET", b"d"]), "$3\r\n\u{0}\u{0}\u{0}\r\n");
8988
8989        // Every source missing is an empty result, and an empty result takes
8990        // the destination with it.
8991        f.run(&[b"SET", b"dest", b"x"]);
8992        assert_eq!(f.run(&[b"BITOP", b"AND", b"dest", b"g1", b"g2"]), ":0\r\n");
8993        assert_eq!(f.run(&[b"EXISTS", b"dest"]), ":0\r\n");
8994    }
8995
8996    /// What `BITOP` says when it is asked for something it cannot do.
8997    #[test]
8998    fn bitop_names_the_operation_in_its_own_complaints() {
8999        let mut f = Fixture::new();
9000        f.run(&[b"SET", b"a", b"abc"]);
9001        assert_eq!(
9002            f.run(&[b"BITOP", b"nope", b"d", b"a"]),
9003            "-ERR syntax error\r\n"
9004        );
9005        assert_eq!(
9006            f.run(&[b"BITOP", b"NOT", b"d", b"a", b"a"]),
9007            "-ERR BITOP NOT must be called with a single source key.\r\n"
9008        );
9009        for op in [&b"DIFF"[..], b"DIFF1", b"ANDOR"] {
9010            assert_eq!(
9011                f.run(&[b"BITOP", op, b"d", b"a"]),
9012                format!(
9013                    "-ERR BITOP {} must be called with at least two source keys.\r\n",
9014                    String::from_utf8_lossy(op)
9015                )
9016            );
9017        }
9018        f.run(&[b"LPUSH", b"l", b"x"]);
9019        assert_eq!(
9020            f.run(&[b"BITOP", b"AND", b"d", b"a", b"l"]),
9021            "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n"
9022        );
9023    }
9024
9025    /// Packed fields, the three overflow policies and the `#` offset.
9026    #[test]
9027    fn bitfield_reads_and_writes_packed_fields() {
9028        let mut f = Fixture::new();
9029        assert_eq!(f.run(&[b"BITFIELD", b"bf"]), "*0\r\n");
9030        assert_eq!(f.run(&[b"EXISTS", b"bf"]), ":0\r\n");
9031
9032        assert_eq!(
9033            f.run(&[
9034                b"BITFIELD",
9035                b"bf",
9036                b"INCRBY",
9037                b"u2",
9038                b"100",
9039                b"1",
9040                b"GET",
9041                b"u4",
9042                b"0"
9043            ]),
9044            "*2\r\n:1\r\n:0\r\n"
9045        );
9046        // The field at bit 100 is two bits wide, so it ends in the thirteenth
9047        // byte and the value grew to thirteen bytes to hold it.
9048        assert_eq!(f.run(&[b"STRLEN", b"bf"]), ":13\r\n");
9049
9050        // A `#` offset counts in fields rather than in bits.
9051        assert_eq!(
9052            f.run(&[
9053                b"BITFIELD",
9054                b"bf",
9055                b"SET",
9056                b"u8",
9057                b"#0",
9058                b"255",
9059                b"GET",
9060                b"u8",
9061                b"#0"
9062            ]),
9063            "*2\r\n:0\r\n:255\r\n"
9064        );
9065
9066        assert_eq!(
9067            f.run(&[
9068                b"BITFIELD",
9069                b"bf",
9070                b"OVERFLOW",
9071                b"SAT",
9072                b"INCRBY",
9073                b"i8",
9074                b"0",
9075                b"120",
9076                b"INCRBY",
9077                b"i8",
9078                b"0",
9079                b"120"
9080            ]),
9081            "*2\r\n:119\r\n:127\r\n"
9082        );
9083        assert_eq!(
9084            f.run(&[
9085                b"BITFIELD",
9086                b"bf2",
9087                b"OVERFLOW",
9088                b"FAIL",
9089                b"INCRBY",
9090                b"u2",
9091                b"0",
9092                b"5"
9093            ]),
9094            "*1\r\n$-1\r\n"
9095        );
9096        assert_eq!(
9097            f.run(&[
9098                b"BITFIELD",
9099                b"bf3",
9100                b"OVERFLOW",
9101                b"WRAP",
9102                b"INCRBY",
9103                b"u2",
9104                b"0",
9105                b"5"
9106            ]),
9107            "*1\r\n:1\r\n"
9108        );
9109        assert_eq!(
9110            f.run(&[b"BITFIELD", b"bf3", b"GET", b"i64", b"0"]),
9111            "*1\r\n:4611686018427387904\r\n"
9112        );
9113    }
9114
9115    /// A bad subcommand anywhere in the line stops all of it.
9116    ///
9117    /// Redis checks the whole argument list before it runs any of it, so the
9118    /// `SET` in front of the bad type here never happens and the key it would
9119    /// have created is not there afterwards.
9120    #[test]
9121    fn a_bad_bitfield_subcommand_leaves_the_key_alone() {
9122        let mut f = Fixture::new();
9123        let bad_type = "-ERR Invalid bitfield type. Use something like i16 u8. Note that u64 is not supported but i64 is.\r\n";
9124        assert_eq!(
9125            f.run(&[
9126                b"BITFIELD",
9127                b"bad",
9128                b"SET",
9129                b"u8",
9130                b"0",
9131                b"1",
9132                b"GET",
9133                b"u99",
9134                b"0"
9135            ]),
9136            bad_type
9137        );
9138        assert_eq!(f.run(&[b"EXISTS", b"bad"]), ":0\r\n");
9139        assert_eq!(
9140            f.run(&[b"BITFIELD", b"bad", b"GET", b"u64", b"0"]),
9141            bad_type
9142        );
9143        assert_eq!(
9144            f.run(&[b"BITFIELD", b"bad", b"GET"]),
9145            "-ERR syntax error\r\n"
9146        );
9147        assert_eq!(
9148            f.run(&[b"BITFIELD", b"bad", b"NOPE", b"u8", b"0"]),
9149            "-ERR syntax error\r\n"
9150        );
9151        assert_eq!(
9152            f.run(&[b"BITFIELD", b"bad", b"OVERFLOW"]),
9153            "-ERR syntax error\r\n"
9154        );
9155        assert_eq!(
9156            f.run(&[
9157                b"BITFIELD",
9158                b"bad",
9159                b"OVERFLOW",
9160                b"NOPE",
9161                b"GET",
9162                b"u8",
9163                b"0"
9164            ]),
9165            "-ERR Invalid OVERFLOW type specified\r\n"
9166        );
9167        assert_eq!(
9168            f.run(&[b"BITFIELD", b"bad", b"SET", b"u8", b"0", b"notanum"]),
9169            "-ERR value is not an integer or out of range\r\n"
9170        );
9171        for at in [&b"#-1"[..], b"abc"] {
9172            assert_eq!(
9173                f.run(&[b"BITFIELD", b"bad", b"GET", b"u8", at]),
9174                "-ERR bit offset is not an integer or out of range\r\n"
9175            );
9176        }
9177    }
9178
9179    /// The read only twin reads, refuses to write, and creates nothing.
9180    #[test]
9181    fn bitfield_ro_answers_gets_and_refuses_the_rest() {
9182        let mut f = Fixture::new();
9183        f.run(&[b"SET", b"n", b"123"]);
9184        assert_eq!(
9185            f.run(&[b"BITFIELD_RO", b"n", b"GET", b"u8", b"0"]),
9186            "*1\r\n:49\r\n"
9187        );
9188        // A read does not unpack an int the way a write does.
9189        assert_eq!(f.run(&[b"OBJECT", b"ENCODING", b"n"]), "$3\r\nint\r\n");
9190
9191        // An OVERFLOW word is allowed even though nothing here can overflow.
9192        assert_eq!(
9193            f.run(&[
9194                b"BITFIELD_RO",
9195                b"n",
9196                b"OVERFLOW",
9197                b"SAT",
9198                b"GET",
9199                b"u8",
9200                b"0"
9201            ]),
9202            "*1\r\n:49\r\n"
9203        );
9204        for sub in [&b"SET"[..], b"INCRBY"] {
9205            assert_eq!(
9206                f.run(&[b"BITFIELD_RO", b"n", sub, b"u8", b"0", b"1"]),
9207                "-ERR BITFIELD_RO only supports the GET subcommand\r\n"
9208            );
9209        }
9210
9211        assert_eq!(
9212            f.run(&[b"BITFIELD_RO", b"gone", b"GET", b"u8", b"100"]),
9213            "*1\r\n:0\r\n"
9214        );
9215        assert_eq!(f.run(&[b"EXISTS", b"gone"]), ":0\r\n");
9216    }
9217
9218    /// The offsets a bitmap command will not take.
9219    #[test]
9220    fn an_offset_off_the_end_of_the_world_is_refused() {
9221        let mut f = Fixture::new();
9222        let bad = "-ERR bit offset is not an integer or out of range\r\n";
9223        for arg in [&b"abc"[..], b"-1", b"4294967296"] {
9224            assert_eq!(f.run(&[b"SETBIT", b"k", arg, b"1"]), bad);
9225            assert_eq!(f.run(&[b"GETBIT", b"k", arg]), bad);
9226        }
9227        for arg in [&b"2"[..], b"-1"] {
9228            assert_eq!(
9229                f.run(&[b"BITPOS", b"k", arg]),
9230                "-ERR The bit argument must be 1 or 0.\r\n"
9231            );
9232        }
9233        assert_eq!(
9234            f.run(&[b"BITPOS", b"k", b"abc"]),
9235            "-ERR value is not an integer or out of range\r\n"
9236        );
9237        assert_eq!(
9238            f.run(&[b"BITPOS", b"k", b"0", b"5", b"BIT"]),
9239            "-ERR value is not an integer or out of range\r\n"
9240        );
9241        let bad_bit = "-ERR bit is not an integer or out of range\r\n";
9242        assert_eq!(f.run(&[b"SETBIT", b"k", b"0", b"2"]), bad_bit);
9243        assert_eq!(f.run(&[b"SETBIT", b"k", b"0", b"abc"]), bad_bit);
9244    }
9245
9246    /// Every one of the seven refuses a key that is not a string.
9247    #[test]
9248    fn every_bitmap_command_says_wrongtype() {
9249        let mut f = Fixture::new();
9250        f.run(&[b"LPUSH", b"l", b"x"]);
9251        let wrong = "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n";
9252        let cases: &[&[&[u8]]] = &[
9253            &[b"SETBIT", b"l", b"0", b"1"],
9254            &[b"GETBIT", b"l", b"0"],
9255            &[b"BITCOUNT", b"l"],
9256            &[b"BITPOS", b"l", b"1"],
9257            &[b"BITOP", b"AND", b"d", b"l"],
9258            &[b"BITFIELD", b"l", b"GET", b"u8", b"0"],
9259            &[b"BITFIELD_RO", b"l", b"GET", b"u8", b"0"],
9260        ];
9261        for case in cases {
9262            assert_eq!(f.run(case), wrong, "{:?}", case[0]);
9263        }
9264    }
9265
9266    // --------------------------------------------------------- hyperloglogs
9267
9268    #[test]
9269    fn a_sketch_is_added_to_and_counted() {
9270        let mut f = Fixture::new();
9271        // Creating the key counts as a change, even with nothing to add.
9272        assert_eq!(f.run(&[b"PFADD", b"h"]), ":1\r\n");
9273        assert_eq!(f.run(&[b"PFADD", b"h"]), ":0\r\n");
9274        assert_eq!(f.run(&[b"PFCOUNT", b"h"]), ":0\r\n");
9275        assert_eq!(f.run(&[b"STRLEN", b"h"]), ":18\r\n");
9276        // And it is a string, which is not an implementation detail: a client
9277        // can `GET` a sketch out of one server and `SET` it into another.
9278        assert_eq!(f.run(&[b"TYPE", b"h"]), "+string\r\n");
9279        assert_eq!(f.run(&[b"OBJECT", b"ENCODING", b"h"]), "$3\r\nraw\r\n");
9280
9281        assert_eq!(f.run(&[b"PFADD", b"h", b"a", b"b", b"c"]), ":1\r\n");
9282        assert_eq!(f.run(&[b"PFADD", b"h", b"a"]), ":0\r\n");
9283        assert_eq!(f.run(&[b"PFCOUNT", b"h"]), ":3\r\n");
9284    }
9285
9286    #[test]
9287    fn the_bytes_of_a_sketch_are_the_ones_a_real_server_writes() {
9288        let mut f = Fixture::new();
9289        f.run(&[b"PFADD", b"h", b"a", b"b", b"c"]);
9290        // Not text, so it is compared as bytes.
9291        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";
9292        let mut reply = b"$27\r\n".to_vec();
9293        reply.extend_from_slice(want);
9294        reply.extend_from_slice(b"\r\n");
9295        assert_eq!(f.raw(&[b"GET", b"h"]), reply);
9296    }
9297
9298    #[test]
9299    fn counting_several_keys_counts_their_union() {
9300        let mut f = Fixture::new();
9301        f.run(&[b"PFADD", b"a", b"x", b"y"]);
9302        f.run(&[b"PFADD", b"b", b"y", b"z"]);
9303        assert_eq!(f.run(&[b"PFCOUNT", b"a"]), ":2\r\n");
9304        assert_eq!(f.run(&[b"PFCOUNT", b"a", b"b"]), ":3\r\n");
9305        // A key that is not there is an empty sketch, not an error and not
9306        // something that gets created by being counted.
9307        assert_eq!(f.run(&[b"PFCOUNT", b"gone"]), ":0\r\n");
9308        assert_eq!(f.run(&[b"PFCOUNT", b"a", b"gone"]), ":2\r\n");
9309        assert_eq!(f.run(&[b"EXISTS", b"gone"]), ":0\r\n");
9310    }
9311
9312    #[test]
9313    fn a_merge_keeps_what_the_destination_had() {
9314        let mut f = Fixture::new();
9315        f.run(&[b"PFADD", b"a", b"x", b"y"]);
9316        f.run(&[b"PFADD", b"b", b"z"]);
9317        assert_eq!(f.run(&[b"PFMERGE", b"d", b"a", b"b"]), "+OK\r\n");
9318        assert_eq!(f.run(&[b"PFCOUNT", b"d"]), ":3\r\n");
9319        // The destination is one of the sources, so a second merge adds to it.
9320        f.run(&[b"PFADD", b"c", b"w"]);
9321        assert_eq!(f.run(&[b"PFMERGE", b"d", b"c"]), "+OK\r\n");
9322        assert_eq!(f.run(&[b"PFCOUNT", b"d"]), ":4\r\n");
9323        // And with no sources it is a no-op that still answers OK and still
9324        // creates a destination that was not there.
9325        assert_eq!(f.run(&[b"PFMERGE", b"fresh"]), "+OK\r\n");
9326        assert_eq!(f.run(&[b"PFCOUNT", b"fresh"]), ":0\r\n");
9327    }
9328
9329    /// Not under Miri, and not for the number of commands: a dense sketch is
9330    /// sixteen thousand three hundred and eighty four registers and every
9331    /// command here walks all of them, so one `PFCOUNT` is more interpreted
9332    /// work than a hundred ordinary tests. The registers and the walking are in
9333    /// `yo-kv`, where fifteen tests of their own cover both encodings and where
9334    /// the interpreter does run over them. What is left here is the dispatch
9335    /// around it, which is the same dispatch every other command in this file
9336    /// goes through.
9337    #[cfg_attr(miri, ignore = "sixteen thousand registers a command")]
9338    #[test]
9339    fn the_debug_forms_answer_four_different_shapes() {
9340        let mut f = Fixture::new();
9341        f.run(&[b"PFADD", b"h", b"a", b"b", b"c"]);
9342        assert_eq!(f.run(&[b"PFDEBUG", b"ENCODING", b"h"]), "+sparse\r\n");
9343        assert_eq!(
9344            f.run(&[b"PFDEBUG", b"DECODE", b"h"]),
9345            "$44\r\nZ:8436 v:1,1 Z:4274 v:2,1 Z:3068 v:1,1 Z:603\r\n"
9346        );
9347        assert_eq!(f.run(&[b"PFDEBUG", b"TODENSE", b"h"]), ":1\r\n");
9348        assert_eq!(f.run(&[b"PFDEBUG", b"TODENSE", b"h"]), ":0\r\n");
9349        assert_eq!(f.run(&[b"PFDEBUG", b"ENCODING", b"h"]), "+dense\r\n");
9350        assert_eq!(f.run(&[b"STRLEN", b"h"]), ":12304\r\n");
9351        assert_eq!(f.run(&[b"PFCOUNT", b"h"]), ":3\r\n");
9352        // A dense sketch has no opcodes left to print.
9353        assert_eq!(
9354            f.run(&[b"PFDEBUG", b"DECODE", b"h"]),
9355            "-ERR HLL encoding is not sparse\r\n"
9356        );
9357
9358        // All 16384 registers, of which three are not nought.
9359        let reply = f.run(&[b"PFDEBUG", b"GETREG", b"h"]);
9360        assert!(reply.starts_with("*16384\r\n"), "{}", &reply[..16]);
9361        assert_eq!(reply.matches(":0\r\n").count(), 16381);
9362        assert_eq!(reply.matches(":1\r\n").count(), 2);
9363        assert_eq!(reply.matches(":2\r\n").count(), 1);
9364
9365        assert_eq!(f.run(&[b"PFSELFTEST"]), "+OK\r\n");
9366    }
9367
9368    #[test]
9369    fn a_string_that_is_not_a_sketch_is_refused_with_its_own_sentence() {
9370        let mut f = Fixture::new();
9371        f.run(&[b"SET", b"plain", b"not a sketch"]);
9372        let not_hll = "-WRONGTYPE Key is not a valid HyperLogLog string value.\r\n";
9373        assert_eq!(f.run(&[b"PFADD", b"plain", b"a"]), not_hll);
9374        assert_eq!(f.run(&[b"PFCOUNT", b"plain"]), not_hll);
9375        assert_eq!(f.run(&[b"PFMERGE", b"plain"]), not_hll);
9376        assert_eq!(f.run(&[b"PFDEBUG", b"ENCODING", b"plain"]), not_hll);
9377
9378        // A key that is not a string at all gets the ordinary sentence, and a
9379        // destination that would have been written is not created.
9380        f.run(&[b"RPUSH", b"l", b"x"]);
9381        let wrong = "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n";
9382        assert_eq!(f.run(&[b"PFADD", b"l", b"a"]), wrong);
9383        assert_eq!(f.run(&[b"PFCOUNT", b"l"]), wrong);
9384        assert_eq!(f.run(&[b"PFMERGE", b"dest", b"l"]), wrong);
9385        assert_eq!(f.run(&[b"EXISTS", b"dest"]), ":0\r\n");
9386        assert_eq!(f.run(&[b"PFDEBUG", b"GETREG", b"l"]), wrong);
9387    }
9388
9389    #[test]
9390    fn pfdebug_has_its_own_complaints() {
9391        let mut f = Fixture::new();
9392        f.run(&[b"PFADD", b"h", b"a"]);
9393        // The word is quoted exactly as the client spelled it, and this is not
9394        // the "Try X HELP." sentence every other container command uses.
9395        assert_eq!(
9396            f.run(&[b"PFDEBUG", b"NOPE", b"h"]),
9397            "-ERR Unknown PFDEBUG subcommand 'NOPE'\r\n"
9398        );
9399        // Where all three of the real commands take a missing key as empty.
9400        let gone = "-ERR The specified key does not exist\r\n";
9401        assert_eq!(f.run(&[b"PFDEBUG", b"GETREG", b"missing"]), gone);
9402        assert_eq!(f.run(&[b"PFDEBUG", b"DECODE", b"missing"]), gone);
9403        assert_eq!(f.run(&[b"PFDEBUG", b"ENCODING", b"missing"]), gone);
9404        assert_eq!(f.run(&[b"PFDEBUG", b"TODENSE", b"missing"]), gone);
9405        assert_eq!(
9406            f.run(&[b"PFDEBUG"]),
9407            "-ERR wrong number of arguments for 'pfdebug' command\r\n"
9408        );
9409        assert_eq!(
9410            f.run(&[b"PFSELFTEST", b"x"]),
9411            "-ERR wrong number of arguments for 'pfselftest' command\r\n"
9412        );
9413    }
9414
9415    #[test]
9416    fn a_sketch_whose_opcodes_do_not_add_up_says_so() {
9417        let mut f = Fixture::new();
9418        f.run(&[b"PFADD", b"h", b"a", b"b", b"c"]);
9419        // The sketch with its last byte cut off, which is still a header and a
9420        // magic and is a run length encoding that stops short of register 16384.
9421        let reply = f.raw(&[b"GET", b"h"]);
9422        let short = reply[5..reply.len() - 3].to_vec();
9423        f.run(&[b"SET", b"h", &short]);
9424        assert_eq!(
9425            f.run(&[b"PFCOUNT", b"h"]),
9426            "-INVALIDOBJ Corrupted HLL object detected\r\n"
9427        );
9428    }
9429
9430    #[test]
9431    fn a_sketch_survives_a_dump_and_a_restore_in_both_encodings() {
9432        let mut f = Fixture::new();
9433        // One that stays sparse and one that has gone dense, since the payload
9434        // carries the bytes and the two encodings are different lengths.
9435        f.run(&[b"PFADD", b"small", b"a", b"b", b"c"]);
9436        // Ten thousand elements is what takes a sketch dense on its own, and it
9437        // is ten thousand trips through dispatch, which is what Miri charges
9438        // for. There the same sketch is taken across by hand. What this test is
9439        // about is a dense payload surviving a round trip and the encoding is
9440        // dense either way: that a sketch converts when it fills up is what
9441        // `the_debug_forms_answer_four_different_shapes` is for.
9442        if cfg!(miri) {
9443            f.run(&[b"PFADD", b"big", b"a", b"b", b"c"]);
9444            f.run(&[b"PFDEBUG", b"TODENSE", b"big"]);
9445        } else {
9446            for i in 0..10_000u32 {
9447                let ele = format!("e{i}");
9448                f.run(&[b"PFADD", b"big", ele.as_bytes()]);
9449            }
9450        }
9451        assert_eq!(f.run(&[b"PFDEBUG", b"ENCODING", b"small"]), "+sparse\r\n");
9452        assert_eq!(f.run(&[b"PFDEBUG", b"ENCODING", b"big"]), "+dense\r\n");
9453
9454        for key in [&b"small"[..], b"big"] {
9455            let mut copy = key.to_vec();
9456            copy.push(b'2');
9457            let bytes = payload(&f.raw(&[b"DUMP", key]));
9458            assert_eq!(f.run(&[b"RESTORE", &copy, b"0", &bytes]), "+OK\r\n");
9459            // The bytes, the encoding and the estimate all come back, which is
9460            // the whole of what byte compatibility across a round trip means.
9461            assert_eq!(f.raw(&[b"GET", &copy]), f.raw(&[b"GET", key]));
9462            assert_eq!(
9463                f.run(&[b"PFDEBUG", b"ENCODING", &copy]),
9464                f.run(&[b"PFDEBUG", b"ENCODING", key])
9465            );
9466            assert_eq!(f.run(&[b"PFCOUNT", &copy]), f.run(&[b"PFCOUNT", key]));
9467        }
9468        assert_eq!(f.run(&[b"PFCOUNT", b"small2"]), ":3\r\n");
9469        assert_eq!(f.run(&[b"STRLEN", b"big2"]), ":12304\r\n");
9470    }
9471
9472    /// One RESP2 bulk string. The JSON replies are almost all one of these and
9473    /// the text inside them has quotes in it, so writing the frame out by hand
9474    /// buries the part of the assertion that matters.
9475    fn bulk(s: &str) -> String {
9476        format!("${}\r\n{s}\r\n", s.len())
9477    }
9478
9479    /// A RESP2 array of bulk strings, which is what most of the list replies
9480    /// are and what writing them out by hand in every assertion looks like.
9481    fn bulks(parts: &[&str]) -> String {
9482        let mut s = format!("*{}\r\n", parts.len());
9483        for p in parts {
9484            s.push_str(&format!("${}\r\n{p}\r\n", p.len()));
9485        }
9486        s
9487    }
9488
9489    #[test]
9490    fn a_list_is_pushed_from_both_ends_and_the_left_one_reverses() {
9491        let mut f = Fixture::new();
9492        // Each element in turn goes at the head, so the last one sent is at the
9493        // front when it is over. That reads like a bug in the client and it is
9494        // what every Redis has always done.
9495        assert_eq!(f.run(&[b"LPUSH", b"k", b"a", b"b", b"c"]), ":3\r\n");
9496        assert_eq!(
9497            f.run(&[b"LRANGE", b"k", b"0", b"-1"]),
9498            bulks(&["c", "b", "a"])
9499        );
9500        assert_eq!(f.run(&[b"RPUSH", b"k", b"d"]), ":4\r\n");
9501        assert_eq!(f.run(&[b"LLEN", b"k"]), ":4\r\n");
9502        assert_eq!(f.run(&[b"LPOP", b"k"]), "$1\r\nc\r\n");
9503        assert_eq!(f.run(&[b"RPOP", b"k"]), "$1\r\nd\r\n");
9504        assert_eq!(f.run(&[b"LRANGE", b"k", b"0", b"-1"]), bulks(&["b", "a"]));
9505        assert_eq!(f.run(&[b"TYPE", b"k"]), "+list\r\n");
9506    }
9507
9508    #[test]
9509    fn the_x_pushes_refuse_to_bring_a_list_back_to_life() {
9510        let mut f = Fixture::new();
9511        assert_eq!(f.run(&[b"LPUSHX", b"k", b"a"]), ":0\r\n");
9512        assert_eq!(f.run(&[b"RPUSHX", b"k", b"a"]), ":0\r\n");
9513        assert_eq!(f.run(&[b"EXISTS", b"k"]), ":0\r\n");
9514        f.run(&[b"RPUSH", b"k", b"a"]);
9515        assert_eq!(f.run(&[b"LPUSHX", b"k", b"z"]), ":2\r\n");
9516        assert_eq!(f.run(&[b"RPUSHX", b"k", b"y"]), ":3\r\n");
9517        assert_eq!(
9518            f.run(&[b"LRANGE", b"k", b"0", b"-1"]),
9519            bulks(&["z", "a", "y"])
9520        );
9521    }
9522
9523    /// The four ways a pop can come back with nothing, which are three
9524    /// different replies and a RESP2 client can tell all of them apart.
9525    #[test]
9526    fn an_empty_pop_is_a_different_nothing_with_a_count_and_without() {
9527        let mut f = Fixture::new();
9528        assert_eq!(f.run(&[b"LPOP", b"nope"]), "$-1\r\n");
9529        assert_eq!(f.run(&[b"LPOP", b"nope", b"2"]), "*-1\r\n");
9530        assert_eq!(f.run(&[b"RPOP", b"nope"]), "$-1\r\n");
9531        assert_eq!(f.run(&[b"RPOP", b"nope", b"2"]), "*-1\r\n");
9532        f.run(&[b"RPUSH", b"k", b"a", b"b", b"c"]);
9533        // A count of zero against a list that is there is an empty array and
9534        // not a null array, which is the fourth answer.
9535        assert_eq!(f.run(&[b"LPOP", b"k", b"0"]), "*0\r\n");
9536        assert_eq!(f.run(&[b"LPOP", b"k", b"1"]), bulks(&["a"]));
9537        // More than there is takes what there is and the key goes with it.
9538        assert_eq!(f.run(&[b"RPOP", b"k", b"9"]), bulks(&["c", "b"]));
9539        assert_eq!(f.run(&[b"EXISTS", b"k"]), ":0\r\n");
9540    }
9541
9542    #[test]
9543    fn a_pop_count_has_its_own_sentence_and_a_third_argument_is_an_arity_error() {
9544        let mut f = Fixture::new();
9545        f.run(&[b"RPUSH", b"k", b"a"]);
9546        let range = "-ERR value is out of range, must be positive\r\n";
9547        assert_eq!(f.run(&[b"LPOP", b"k", b"-1"]), range);
9548        assert_eq!(f.run(&[b"LPOP", b"k", b"abc"]), range);
9549        assert_eq!(f.run(&[b"RPOP", b"k", b"-1"]), range);
9550        // Redis calls this an arity error and not a syntax error, which is a
9551        // distinction it does not always make.
9552        assert_eq!(
9553            f.run(&[b"LPOP", b"k", b"1", b"2"]),
9554            "-ERR wrong number of arguments for 'lpop' command\r\n"
9555        );
9556        assert_eq!(f.run(&[b"LLEN", b"k"]), ":1\r\n");
9557    }
9558
9559    #[test]
9560    fn a_range_takes_negative_ends_and_clamps_the_ones_that_run_off() {
9561        let mut f = Fixture::new();
9562        f.run(&[b"RPUSH", b"k", b"a", b"b", b"c"]);
9563        assert_eq!(
9564            f.run(&[b"LRANGE", b"k", b"0", b"-1"]),
9565            bulks(&["a", "b", "c"])
9566        );
9567        assert_eq!(f.run(&[b"LRANGE", b"k", b"-2", b"-1"]), bulks(&["b", "c"]));
9568        assert_eq!(f.run(&[b"LRANGE", b"k", b"1", b"1"]), bulks(&["b"]));
9569        assert_eq!(f.run(&[b"LRANGE", b"k", b"5", b"10"]), "*0\r\n");
9570        assert_eq!(f.run(&[b"LRANGE", b"k", b"2", b"1"]), "*0\r\n");
9571        assert_eq!(
9572            f.run(&[b"LRANGE", b"k", b"-100", b"100"]),
9573            bulks(&["a", "b", "c"])
9574        );
9575        // A key that is not there is an empty range and not a nil, which is the
9576        // one place a list disagrees with a set.
9577        assert_eq!(f.run(&[b"LRANGE", b"nope", b"0", b"-1"]), "*0\r\n");
9578        assert_eq!(
9579            f.run(&[b"LRANGE", b"k", b"a", b"b"]),
9580            "-ERR value is not an integer or out of range\r\n"
9581        );
9582    }
9583
9584    #[test]
9585    fn an_index_reads_and_writes_from_whichever_end_is_nearer() {
9586        let mut f = Fixture::new();
9587        f.run(&[b"RPUSH", b"k", b"a", b"b", b"c"]);
9588        assert_eq!(f.run(&[b"LINDEX", b"k", b"0"]), "$1\r\na\r\n");
9589        assert_eq!(f.run(&[b"LINDEX", b"k", b"-1"]), "$1\r\nc\r\n");
9590        assert_eq!(f.run(&[b"LINDEX", b"k", b"99"]), "$-1\r\n");
9591        assert_eq!(f.run(&[b"LINDEX", b"nope", b"0"]), "$-1\r\n");
9592        assert_eq!(f.run(&[b"LSET", b"k", b"-1", b"z"]), "+OK\r\n");
9593        assert_eq!(
9594            f.run(&[b"LRANGE", b"k", b"0", b"-1"]),
9595            bulks(&["a", "b", "z"])
9596        );
9597        // Both ways of missing are errors here rather than a nil, because a
9598        // list is never empty and there is nothing else the reply could be.
9599        assert_eq!(
9600            f.run(&[b"LSET", b"k", b"99", b"z"]),
9601            "-ERR index out of range\r\n"
9602        );
9603        assert_eq!(
9604            f.run(&[b"LSET", b"nope", b"0", b"z"]),
9605            "-ERR no such key\r\n"
9606        );
9607    }
9608
9609    #[test]
9610    fn linsert_says_three_things_with_one_signed_number() {
9611        let mut f = Fixture::new();
9612        // Zero for a key that is not there, which is not the same as minus one
9613        // for a pivot that is not in a list that is.
9614        assert_eq!(
9615            f.run(&[b"LINSERT", b"nope", b"BEFORE", b"a", b"x"]),
9616            ":0\r\n"
9617        );
9618        f.run(&[b"RPUSH", b"k", b"a", b"b"]);
9619        assert_eq!(f.run(&[b"LINSERT", b"k", b"before", b"a", b"X"]), ":3\r\n");
9620        assert_eq!(f.run(&[b"LINSERT", b"k", b"AFTER", b"b", b"Y"]), ":4\r\n");
9621        assert_eq!(
9622            f.run(&[b"LRANGE", b"k", b"0", b"-1"]),
9623            bulks(&["X", "a", "b", "Y"])
9624        );
9625        assert_eq!(
9626            f.run(&[b"LINSERT", b"k", b"BEFORE", b"zz", b"x"]),
9627            ":-1\r\n"
9628        );
9629        assert_eq!(
9630            f.run(&[b"LINSERT", b"k", b"SIDEWAYS", b"a", b"x"]),
9631            "-ERR syntax error\r\n"
9632        );
9633    }
9634
9635    #[test]
9636    fn lrem_counts_in_three_directions_and_takes_the_key_when_it_empties() {
9637        let mut f = Fixture::new();
9638        f.run(&[b"RPUSH", b"k", b"a", b"b", b"a", b"c", b"a"]);
9639        assert_eq!(f.run(&[b"LREM", b"k", b"2", b"a"]), ":2\r\n");
9640        assert_eq!(
9641            f.run(&[b"LRANGE", b"k", b"0", b"-1"]),
9642            bulks(&["b", "c", "a"])
9643        );
9644        assert_eq!(f.run(&[b"LREM", b"k", b"-1", b"a"]), ":1\r\n");
9645        assert_eq!(f.run(&[b"LRANGE", b"k", b"0", b"-1"]), bulks(&["b", "c"]));
9646        assert_eq!(f.run(&[b"LREM", b"k", b"0", b"b"]), ":1\r\n");
9647        assert_eq!(f.run(&[b"LREM", b"k", b"0", b"c"]), ":1\r\n");
9648        assert_eq!(f.run(&[b"EXISTS", b"k"]), ":0\r\n");
9649        assert_eq!(f.run(&[b"LREM", b"nope", b"0", b"a"]), ":0\r\n");
9650    }
9651
9652    #[test]
9653    fn ltrim_keeps_a_window_and_an_empty_one_deletes_the_key() {
9654        let mut f = Fixture::new();
9655        f.run(&[b"RPUSH", b"k", b"a", b"b", b"c", b"d"]);
9656        assert_eq!(f.run(&[b"LTRIM", b"k", b"1", b"-2"]), "+OK\r\n");
9657        assert_eq!(f.run(&[b"LRANGE", b"k", b"0", b"-1"]), bulks(&["b", "c"]));
9658        // `LTRIM k 1 0` is the documented way to empty a list, so it has to
9659        // leave `EXISTS` answering zero rather than leaving an empty one.
9660        assert_eq!(f.run(&[b"LTRIM", b"k", b"1", b"0"]), "+OK\r\n");
9661        assert_eq!(f.run(&[b"EXISTS", b"k"]), ":0\r\n");
9662        assert_eq!(f.run(&[b"LTRIM", b"nope", b"0", b"-1"]), "+OK\r\n");
9663    }
9664
9665    #[test]
9666    fn lpos_walks_from_either_end_and_stops_where_it_is_told() {
9667        let mut f = Fixture::new();
9668        f.run(&[b"RPUSH", b"p", b"a", b"b", b"c", b"a", b"b", b"c", b"a"]);
9669        assert_eq!(f.run(&[b"LPOS", b"p", b"a"]), ":0\r\n");
9670        assert_eq!(f.run(&[b"LPOS", b"p", b"a", b"RANK", b"-1"]), ":6\r\n");
9671        assert_eq!(f.run(&[b"LPOS", b"p", b"a", b"RANK", b"2"]), ":3\r\n");
9672        assert_eq!(
9673            f.run(&[b"LPOS", b"p", b"a", b"COUNT", b"2"]),
9674            "*2\r\n:0\r\n:3\r\n"
9675        );
9676        assert_eq!(
9677            f.run(&[b"LPOS", b"p", b"a", b"RANK", b"-1", b"COUNT", b"0"]),
9678            "*3\r\n:6\r\n:3\r\n:0\r\n"
9679        );
9680        // MAXLEN counts elements looked at and not matches found, so three
9681        // stops after `a b c` and finds the one match in it.
9682        assert_eq!(
9683            f.run(&[b"LPOS", b"p", b"a", b"COUNT", b"0", b"MAXLEN", b"3"]),
9684            "*1\r\n:0\r\n"
9685        );
9686        // Nothing found is three different replies depending on how it was
9687        // asked and whether the key is there at all.
9688        assert_eq!(f.run(&[b"LPOS", b"p", b"zz"]), "$-1\r\n");
9689        assert_eq!(f.run(&[b"LPOS", b"p", b"zz", b"COUNT", b"0"]), "*0\r\n");
9690        assert_eq!(f.run(&[b"LPOS", b"nope", b"a"]), "$-1\r\n");
9691        assert_eq!(f.run(&[b"LPOS", b"nope", b"a", b"COUNT", b"2"]), "*0\r\n");
9692    }
9693
9694    #[test]
9695    fn lpos_words_its_three_mistakes_the_way_redis_does() {
9696        let mut f = Fixture::new();
9697        f.run(&[b"RPUSH", b"p", b"a"]);
9698        // The whole sentence and not a prefix, because the older wording of it
9699        // is still all over the internet and clients match on the text.
9700        assert_eq!(
9701            f.run(&[b"LPOS", b"p", b"a", b"RANK", b"0"]),
9702            "-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"
9703        );
9704        assert_eq!(
9705            f.run(&[b"LPOS", b"p", b"a", b"COUNT", b"-1"]),
9706            "-ERR COUNT can't be negative\r\n"
9707        );
9708        assert_eq!(
9709            f.run(&[b"LPOS", b"p", b"a", b"MAXLEN", b"-1"]),
9710            "-ERR MAXLEN can't be negative\r\n"
9711        );
9712        assert_eq!(
9713            f.run(&[b"LPOS", b"p", b"a", b"RANK"]),
9714            "-ERR syntax error\r\n"
9715        );
9716        assert_eq!(
9717            f.run(&[b"LPOS", b"p", b"a", b"FOO", b"1"]),
9718            "-ERR syntax error\r\n"
9719        );
9720    }
9721
9722    #[test]
9723    fn a_move_takes_from_one_end_and_gives_to_another_even_on_one_key() {
9724        let mut f = Fixture::new();
9725        f.run(&[b"RPUSH", b"k", b"a", b"b", b"c"]);
9726        assert_eq!(f.run(&[b"RPOPLPUSH", b"k", b"d"]), "$1\r\nc\r\n");
9727        assert_eq!(f.run(&[b"LRANGE", b"k", b"0", b"-1"]), bulks(&["a", "b"]));
9728        assert_eq!(f.run(&[b"LRANGE", b"d", b"0", b"-1"]), bulks(&["c"]));
9729        assert_eq!(
9730            f.run(&[b"LMOVE", b"k", b"d", b"LEFT", b"RIGHT"]),
9731            "$1\r\na\r\n"
9732        );
9733        assert_eq!(f.run(&[b"LRANGE", b"d", b"0", b"-1"]), bulks(&["c", "a"]));
9734        // The same key twice is the documented way to rotate a list and falls
9735        // out of taking the element before deciding where to put it.
9736        f.run(&[b"DEL", b"r"]);
9737        f.run(&[b"RPUSH", b"r", b"1", b"2", b"3"]);
9738        assert_eq!(f.run(&[b"RPOPLPUSH", b"r", b"r"]), "$1\r\n3\r\n");
9739        assert_eq!(
9740            f.run(&[b"LRANGE", b"r", b"0", b"-1"]),
9741            bulks(&["3", "1", "2"])
9742        );
9743        assert_eq!(
9744            f.run(&[b"LMOVE", b"nope", b"d", b"LEFT", b"LEFT"]),
9745            "$-1\r\n"
9746        );
9747        assert_eq!(
9748            f.run(&[b"LMOVE", b"r", b"d", b"LEFT", b"SIDEWAYS"]),
9749            "-ERR syntax error\r\n"
9750        );
9751    }
9752
9753    #[test]
9754    fn a_move_checks_the_destination_before_it_takes_anything() {
9755        let mut f = Fixture::new();
9756        f.run(&[b"RPUSH", b"k", b"a", b"b"]);
9757        f.run(&[b"SET", b"str", b"v"]);
9758        assert_eq!(
9759            f.run(&[b"LMOVE", b"k", b"str", b"LEFT", b"LEFT"]),
9760            "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n"
9761        );
9762        // The element is still where it was, rather than having gone nowhere.
9763        assert_eq!(f.run(&[b"LRANGE", b"k", b"0", b"-1"]), bulks(&["a", "b"]));
9764    }
9765
9766    #[test]
9767    fn a_block_move_orders_the_block_by_the_ends_and_the_ordering_word() {
9768        // OBO is what you get from sending LMOVE that many times, BULK keeps
9769        // the source order. The two only differ when both ends are the same,
9770        // which is the whole reason the word exists.
9771        for (from, to, order, want) in [
9772            ("LEFT", "RIGHT", "OBO", ["a", "b"]),
9773            ("LEFT", "RIGHT", "BULK", ["a", "b"]),
9774            ("LEFT", "LEFT", "OBO", ["b", "a"]),
9775            ("LEFT", "LEFT", "BULK", ["a", "b"]),
9776            ("RIGHT", "LEFT", "OBO", ["d", "e"]),
9777            ("RIGHT", "LEFT", "BULK", ["d", "e"]),
9778            ("RIGHT", "RIGHT", "OBO", ["e", "d"]),
9779            ("RIGHT", "RIGHT", "BULK", ["d", "e"]),
9780        ] {
9781            let mut f = Fixture::new();
9782            f.run(&[b"RPUSH", b"s", b"a", b"b", b"c", b"d", b"e"]);
9783            let how = format!("{from} {to} {order}");
9784            let reply = f.run(&[
9785                b"LMOVEM",
9786                b"s",
9787                b"d",
9788                from.as_bytes(),
9789                to.as_bytes(),
9790                b"COUNT",
9791                b"2",
9792                order.as_bytes(),
9793            ]);
9794            assert_eq!(reply, bulks(&want), "the reply for {how}");
9795            assert_eq!(
9796                f.run(&[b"LRANGE", b"d", b"0", b"-1"]),
9797                bulks(&want),
9798                "the destination for {how}"
9799            );
9800        }
9801    }
9802
9803    #[test]
9804    fn a_block_move_of_one_needs_no_count_at_all() {
9805        let mut f = Fixture::new();
9806        f.run(&[b"RPUSH", b"s", b"a", b"b", b"c"]);
9807        assert_eq!(
9808            f.run(&[b"LMOVEM", b"s", b"d", b"LEFT", b"RIGHT"]),
9809            bulks(&["a"])
9810        );
9811        assert_eq!(f.run(&[b"LRANGE", b"s", b"0", b"-1"]), bulks(&["b", "c"]));
9812        // Six and seven arguments are neither of the two forms, so the
9813        // reference calls both of them a syntax error rather than guessing.
9814        assert_eq!(
9815            f.run(&[b"LMOVEM", b"s", b"d", b"LEFT", b"RIGHT", b"COUNT"]),
9816            "-ERR syntax error\r\n"
9817        );
9818        assert_eq!(
9819            f.run(&[b"LMOVEM", b"s", b"d", b"LEFT", b"RIGHT", b"COUNT", b"2"]),
9820            "-ERR syntax error\r\n"
9821        );
9822    }
9823
9824    #[test]
9825    fn a_block_move_with_exactly_takes_all_of_them_or_none() {
9826        let mut f = Fixture::new();
9827        f.run(&[b"RPUSH", b"s", b"a", b"b", b"c"]);
9828        // A null array and not a null bulk string, which `redis-cli` prints as
9829        // `(nil)` either way and only the raw wire tells apart. What it would
9830        // have sent is an array, so its nothing is an array's nothing.
9831        assert_eq!(
9832            f.run(&[
9833                b"LMOVEM", b"s", b"d", b"LEFT", b"RIGHT", b"EXACTLY", b"99", b"BULK"
9834            ]),
9835            "*-1\r\n"
9836        );
9837        assert_eq!(
9838            f.run(&[b"LRANGE", b"s", b"0", b"-1"]),
9839            bulks(&["a", "b", "c"])
9840        );
9841        // COUNT takes what there is, and an emptied source goes away.
9842        assert_eq!(
9843            f.run(&[
9844                b"LMOVEM", b"s", b"d", b"LEFT", b"RIGHT", b"COUNT", b"99", b"BULK"
9845            ]),
9846            bulks(&["a", "b", "c"])
9847        );
9848        assert_eq!(f.run(&[b"EXISTS", b"s"]), ":0\r\n");
9849        assert_eq!(
9850            f.run(&[
9851                b"LMOVEM", b"s", b"d", b"LEFT", b"RIGHT", b"COUNT", b"1", b"BULK"
9852            ]),
9853            "*-1\r\n"
9854        );
9855    }
9856
9857    #[test]
9858    fn a_block_move_onto_itself_rotates_by_the_count() {
9859        let mut f = Fixture::new();
9860        f.run(&[b"RPUSH", b"s", b"a", b"b", b"c"]);
9861        assert_eq!(
9862            f.run(&[
9863                b"LMOVEM", b"s", b"s", b"LEFT", b"RIGHT", b"COUNT", b"2", b"BULK"
9864            ]),
9865            bulks(&["a", "b"])
9866        );
9867        assert_eq!(
9868            f.run(&[b"LRANGE", b"s", b"0", b"-1"]),
9869            bulks(&["c", "a", "b"])
9870        );
9871    }
9872
9873    #[test]
9874    fn a_block_move_reads_the_count_before_the_ordering_word() {
9875        let mut f = Fixture::new();
9876        f.run(&[b"RPUSH", b"s", b"a", b"b"]);
9877        f.run(&[b"SET", b"str", b"v"]);
9878        let count = "-ERR count should be greater than 0\r\n";
9879        assert_eq!(
9880            f.run(&[
9881                b"LMOVEM", b"s", b"d", b"LEFT", b"RIGHT", b"COUNT", b"abc", b"NOPE"
9882            ]),
9883            count
9884        );
9885        assert_eq!(
9886            f.run(&[
9887                b"LMOVEM", b"s", b"d", b"LEFT", b"RIGHT", b"COUNT", b"0", b"BULK"
9888            ]),
9889            count
9890        );
9891        assert_eq!(
9892            f.run(&[
9893                b"LMOVEM", b"s", b"d", b"LEFT", b"RIGHT", b"COUNT", b"1", b"NOPE"
9894            ]),
9895            "-ERR syntax error\r\n"
9896        );
9897        assert_eq!(
9898            f.run(&[
9899                b"LMOVEM", b"s", b"d", b"LEFT", b"RIGHT", b"NOPE", b"abc", b"BULK"
9900            ]),
9901            "-ERR syntax error\r\n"
9902        );
9903        // Every argument is read before the keys are looked at, so a bad count
9904        // beats a wrong type even when the type is wrong on the source.
9905        assert_eq!(
9906            f.run(&[
9907                b"LMOVEM", b"str", b"d", b"LEFT", b"RIGHT", b"COUNT", b"abc", b"BULK"
9908            ]),
9909            count
9910        );
9911        assert_eq!(
9912            f.run(&[
9913                b"LMOVEM", b"s", b"str", b"LEFT", b"RIGHT", b"COUNT", b"1", b"BULK"
9914            ]),
9915            "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n"
9916        );
9917        assert_eq!(f.run(&[b"LRANGE", b"s", b"0", b"-1"]), bulks(&["a", "b"]));
9918    }
9919
9920    #[test]
9921    fn lmpop_answers_from_the_first_key_that_has_anything() {
9922        let mut f = Fixture::new();
9923        f.run(&[b"RPUSH", b"b", b"1", b"2", b"3"]);
9924        // The name of the key that answered comes back with the elements,
9925        // because the client cannot work out which one it was.
9926        assert_eq!(
9927            f.run(&[b"LMPOP", b"2", b"a", b"b", b"LEFT", b"COUNT", b"2"]),
9928            "*2\r\n$1\r\nb\r\n*2\r\n$1\r\n1\r\n$1\r\n2\r\n"
9929        );
9930        assert_eq!(
9931            f.run(&[b"LMPOP", b"2", b"a", b"b", b"RIGHT"]),
9932            "*2\r\n$1\r\nb\r\n*1\r\n$1\r\n3\r\n"
9933        );
9934        assert_eq!(f.run(&[b"EXISTS", b"b"]), ":0\r\n");
9935        // A null array and not a null, even though what it stands in for is an
9936        // array holding a key name and then another array.
9937        assert_eq!(f.run(&[b"LMPOP", b"2", b"a", b"b", b"LEFT"]), "*-1\r\n");
9938    }
9939
9940    #[test]
9941    fn lmpop_has_its_own_words_for_a_count_and_for_a_key_count() {
9942        let mut f = Fixture::new();
9943        f.run(&[b"RPUSH", b"k", b"a"]);
9944        assert_eq!(
9945            f.run(&[b"LMPOP", b"0", b"k", b"LEFT"]),
9946            "-ERR numkeys should be greater than 0\r\n"
9947        );
9948        assert_eq!(
9949            f.run(&[b"LMPOP", b"-1", b"k", b"LEFT"]),
9950            "-ERR numkeys should be greater than 0\r\n"
9951        );
9952        assert_eq!(
9953            f.run(&[b"LMPOP", b"1", b"k", b"LEFT", b"COUNT", b"0"]),
9954            "-ERR count should be greater than 0\r\n"
9955        );
9956        // A key count that eats the direction is a syntax error and not a
9957        // sentence about key counts, because the direction is simply not there.
9958        assert_eq!(
9959            f.run(&[b"LMPOP", b"3", b"k", b"LEFT"]),
9960            "-ERR syntax error\r\n"
9961        );
9962        assert_eq!(
9963            f.run(&[b"LMPOP", b"1", b"k", b"LEFT", b"COUNT", b"1", b"x"]),
9964            "-ERR syntax error\r\n"
9965        );
9966        assert_eq!(
9967            f.run(&[b"LMPOP", b"1", b"k", b"LEFT", b"FOO", b"1"]),
9968            "-ERR syntax error\r\n"
9969        );
9970        assert_eq!(
9971            f.run(&[b"LMPOP", b"1", b"k", b"SIDEWAYS"]),
9972            "-ERR syntax error\r\n"
9973        );
9974        assert_eq!(f.run(&[b"LLEN", b"k"]), ":1\r\n");
9975    }
9976
9977    #[test]
9978    fn every_list_command_says_wrongtype_and_writes_nothing() {
9979        let mut f = Fixture::new();
9980        f.run(&[b"SET", b"str", b"v"]);
9981        let wrong = "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n";
9982        for cmd in [
9983            &[b"LPUSH".as_slice(), b"str", b"a"][..],
9984            &[b"RPUSH", b"str", b"a"],
9985            &[b"LPUSHX", b"str", b"a"],
9986            &[b"RPUSHX", b"str", b"a"],
9987            &[b"LPOP", b"str"],
9988            &[b"LPOP", b"str", b"2"],
9989            &[b"RPOP", b"str"],
9990            &[b"LLEN", b"str"],
9991            &[b"LRANGE", b"str", b"0", b"-1"],
9992            &[b"LINDEX", b"str", b"0"],
9993            &[b"LSET", b"str", b"0", b"a"],
9994            &[b"LINSERT", b"str", b"BEFORE", b"a", b"b"],
9995            &[b"LREM", b"str", b"0", b"a"],
9996            &[b"LTRIM", b"str", b"0", b"-1"],
9997            &[b"LPOS", b"str", b"a"],
9998            &[b"LPOS", b"str", b"a", b"COUNT", b"0"],
9999            &[b"RPOPLPUSH", b"str", b"d"],
10000            &[b"LMOVE", b"str", b"d", b"LEFT", b"LEFT"],
10001            &[b"LMPOP", b"1", b"str", b"LEFT"],
10002        ] {
10003            assert_eq!(f.run(cmd), wrong, "{:?}", String::from_utf8_lossy(cmd[0]));
10004        }
10005        assert_eq!(f.run(&[b"GET", b"str"]), "$1\r\nv\r\n");
10006        assert_eq!(f.run(&[b"EXISTS", b"d"]), ":0\r\n");
10007    }
10008
10009    /// A timeout is not an integer and it is not an ordinary float either: the
10010    /// three sentences it can answer with are its own, and which one a given
10011    /// argument gets is not what reading the code would suggest.
10012    #[test]
10013    fn a_timeout_has_three_ways_of_being_wrong() {
10014        let mut f = Fixture::new();
10015        let not_float = "-ERR timeout is not a float or out of range\r\n";
10016        let range = "-ERR timeout is out of range\r\n";
10017        for (bad, want) in [
10018            (&[b"BLPOP".as_slice(), b"k", b"abc"][..], not_float),
10019            (&[b"BLPOP", b"k", b"nan"], not_float),
10020            (&[b"BLPOP", b"k", b""], not_float),
10021            // Whitespace on either side, which `strtold` would take and Redis
10022            // does not.
10023            (&[b"BLPOP", b"k", b" 1"], not_float),
10024            (&[b"BLPOP", b"k", b"1 "], not_float),
10025            (&[b"BLPOP", b"k", b"-1"], "-ERR timeout is negative\r\n"),
10026            (&[b"BLPOP", b"k", b"-0.1"], "-ERR timeout is negative\r\n"),
10027            // These three parse, so they are not the not-a-float error, and all
10028            // three are further off than an i64 of milliseconds reaches.
10029            (&[b"BLPOP", b"k", b"1e400"], range),
10030            (&[b"BLPOP", b"k", b"inf"], range),
10031            (&[b"BLPOP", b"k", b"9999999999999999"], range),
10032            (&[b"BRPOP", b"k", b"abc"], not_float),
10033            (
10034                &[b"BLMOVE", b"a", b"b", b"LEFT", b"RIGHT", b"abc"],
10035                not_float,
10036            ),
10037            (
10038                &[b"BRPOPLPUSH", b"a", b"b", b"-1"],
10039                "-ERR timeout is negative\r\n",
10040            ),
10041            (&[b"BLMPOP", b"abc", b"1", b"k", b"LEFT"], not_float),
10042        ] {
10043            assert_eq!(f.run(bad), want, "for {bad:?}");
10044        }
10045    }
10046
10047    /// A timeout of exactly zero means no timeout, and there are two ways of
10048    /// writing exactly zero.
10049    #[test]
10050    fn a_zero_timeout_waits_and_the_smallest_positive_one_does_not() {
10051        let mut f = Fixture::new();
10052        for timeout in [b"0".as_slice(), b"0.0", b"-0.0"] {
10053            let (flow, out) = f.flow(&[b"BLPOP", b"k", timeout]);
10054            assert_eq!(flow, Flow::Block, "for {timeout:?}");
10055            assert!(out.is_empty(), "for {timeout:?}");
10056        }
10057        // Positive, so it is a real deadline, and the deadline is this
10058        // millisecond. Nothing is written here either: the reply comes from the
10059        // sweep, which is the engine's and not this layer's.
10060        let (flow, out) = f.flow(&[b"BLPOP", b"k", b"0.0000001"]);
10061        assert_eq!(flow, Flow::Block);
10062        assert!(out.is_empty());
10063    }
10064
10065    #[test]
10066    fn a_blocking_command_that_can_be_answered_answers_like_the_one_it_wraps() {
10067        let mut f = Fixture::new();
10068        f.run(&[b"RPUSH", b"L", b"a", b"b", b"c", b"d", b"e"]);
10069
10070        // The one difference from LPOP: the reply names the key that answered,
10071        // which is what makes BLPOP over several keys usable.
10072        assert_eq!(
10073            f.flow(&[b"BLPOP", b"nope", b"L", b"0"]),
10074            (Flow::Continue, "*2\r\n$1\r\nL\r\n$1\r\na\r\n".to_owned())
10075        );
10076        assert_eq!(
10077            f.run(&[b"BRPOP", b"L", b"0"]),
10078            "*2\r\n$1\r\nL\r\n$1\r\ne\r\n"
10079        );
10080        assert_eq!(
10081            f.run(&[
10082                b"BLMPOP", b"0", b"2", b"nope", b"L", b"LEFT", b"COUNT", b"2"
10083            ]),
10084            "*2\r\n$1\r\nL\r\n*2\r\n$1\r\nb\r\n$1\r\nc\r\n"
10085        );
10086        assert_eq!(
10087            f.run(&[b"BLMOVE", b"L", b"D", b"LEFT", b"RIGHT", b"0"]),
10088            "$1\r\nd\r\n"
10089        );
10090        assert_eq!(
10091            f.run(&[b"EXISTS", b"L"]),
10092            ":0\r\n",
10093            "and the key went with it"
10094        );
10095        assert_eq!(f.run(&[b"LRANGE", b"D", b"0", b"-1"]), "*1\r\n$1\r\nd\r\n");
10096        // Onto itself, which is how a list is rotated and is a real thing to ask
10097        // a blocking move for.
10098        f.run(&[b"RPUSH", b"D", b"x"]);
10099        assert_eq!(f.run(&[b"BRPOPLPUSH", b"D", b"D", b"0"]), "$1\r\nx\r\n");
10100        assert_eq!(
10101            f.run(&[b"LRANGE", b"D", b"0", b"-1"]),
10102            "*2\r\n$1\r\nx\r\n$1\r\nd\r\n"
10103        );
10104    }
10105
10106    #[test]
10107    fn blmpop_reads_its_count_and_its_key_count_the_way_lmpop_does() {
10108        let mut f = Fixture::new();
10109        f.run(&[b"RPUSH", b"k", b"a"]);
10110        for (bad, want) in [
10111            (
10112                &[b"BLMPOP".as_slice(), b"0", b"0", b"k", b"LEFT"][..],
10113                "-ERR numkeys should be greater than 0\r\n",
10114            ),
10115            (
10116                &[b"BLMPOP", b"0", b"-1", b"k", b"LEFT"],
10117                "-ERR numkeys should be greater than 0\r\n",
10118            ),
10119            // Two keys named and one given, so the word that should have been
10120            // the direction is a key and there is no direction left.
10121            (
10122                &[b"BLMPOP", b"0", b"2", b"k", b"LEFT"],
10123                "-ERR syntax error\r\n",
10124            ),
10125            (
10126                &[b"BLMPOP", b"0", b"1", b"k", b"SIDEWAYS"],
10127                "-ERR syntax error\r\n",
10128            ),
10129            (
10130                &[b"BLMPOP", b"0", b"1", b"k", b"LEFT", b"COUNT"],
10131                "-ERR syntax error\r\n",
10132            ),
10133            (
10134                &[b"BLMPOP", b"0", b"1", b"k", b"LEFT", b"COUNT", b"2", b"x"],
10135                "-ERR syntax error\r\n",
10136            ),
10137            // A count that is not a number at all gets the same sentence a zero
10138            // or a negative one gets, rather than the usual one about integers.
10139            (
10140                &[b"BLMPOP", b"0", b"1", b"k", b"LEFT", b"COUNT", b"0"],
10141                "-ERR count should be greater than 0\r\n",
10142            ),
10143            (
10144                &[b"BLMPOP", b"0", b"1", b"k", b"LEFT", b"COUNT", b"abc"],
10145                "-ERR count should be greater than 0\r\n",
10146            ),
10147        ] {
10148            assert_eq!(f.run(bad), want, "for {bad:?}");
10149        }
10150        assert_eq!(f.run(&[b"LLEN", b"k"]), ":1\r\n", "and none of them popped");
10151    }
10152
10153    #[test]
10154    fn a_blocking_move_reads_its_directions_before_its_timeout() {
10155        let mut f = Fixture::new();
10156        // Both are wrong. Redis checks the directions first, so this is the
10157        // syntax error and not a complaint about the timeout.
10158        assert_eq!(
10159            f.run(&[b"BLMOVE", b"a", b"b", b"UP", b"DOWN", b"abc"]),
10160            "-ERR syntax error\r\n"
10161        );
10162        assert_eq!(
10163            f.run(&[b"BLMOVE", b"a", b"b", b"LEFT", b"DOWN", b"0.05"]),
10164            "-ERR syntax error\r\n"
10165        );
10166    }
10167
10168    /// `BLMOVEM` answers exactly what `LMOVEM` answers when it does not have to
10169    /// wait, which is the same relationship every other command in this file has
10170    /// with the one it wraps.
10171    #[test]
10172    fn a_blocking_block_move_that_can_be_answered_answers_like_lmovem() {
10173        let mut f = Fixture::new();
10174        f.run(&[b"RPUSH", b"L", b"a", b"b", b"c", b"d", b"e"]);
10175        assert_eq!(
10176            f.flow(&[b"BLMOVEM", b"L", b"D", b"LEFT", b"RIGHT", b"0"]),
10177            (Flow::Continue, "*1\r\n$1\r\na\r\n".to_owned())
10178        );
10179        assert_eq!(
10180            f.run(&[
10181                b"BLMOVEM", b"L", b"D", b"RIGHT", b"RIGHT", b"0", b"COUNT", b"2", b"OBO"
10182            ]),
10183            bulks(&["e", "d"])
10184        );
10185        assert_eq!(
10186            f.run(&[b"LRANGE", b"D", b"0", b"-1"]),
10187            bulks(&["a", "e", "d"])
10188        );
10189        // `EXACTLY` with enough there does not wait either.
10190        assert_eq!(
10191            f.run(&[
10192                b"BLMOVEM", b"L", b"D", b"LEFT", b"RIGHT", b"0", b"EXACTLY", b"2", b"BULK"
10193            ]),
10194            bulks(&["b", "c"])
10195        );
10196        assert_eq!(f.run(&[b"EXISTS", b"L"]), ":0\r\n", "and the key went");
10197    }
10198
10199    /// The one thing `BLMOVEM` decides differently from the other five: `COUNT`
10200    /// is ready as soon as there is anything and `EXACTLY` is not ready until the
10201    /// whole block has arrived.
10202    #[test]
10203    fn a_blocking_block_move_waits_for_the_whole_block_only_under_exactly() {
10204        let mut f = Fixture::new();
10205        f.run(&[b"RPUSH", b"L", b"a", b"b"]);
10206        // Two there and three asked for. `COUNT` takes the two.
10207        assert_eq!(
10208            f.flow(&[
10209                b"BLMOVEM", b"L", b"D", b"LEFT", b"RIGHT", b"0", b"COUNT", b"3", b"BULK"
10210            ]),
10211            (Flow::Continue, bulks(&["a", "b"]))
10212        );
10213
10214        f.run(&[b"RPUSH", b"L", b"a", b"b"]);
10215        // The same line with `EXACTLY` parks instead, and takes nothing on the
10216        // way past.
10217        assert_eq!(
10218            f.flow(&[
10219                b"BLMOVEM", b"L", b"D", b"LEFT", b"RIGHT", b"0", b"EXACTLY", b"3", b"BULK"
10220            ])
10221            .0,
10222            Flow::Block
10223        );
10224        assert_eq!(f.run(&[b"LRANGE", b"L", b"0", b"-1"]), bulks(&["a", "b"]));
10225    }
10226
10227    #[test]
10228    fn a_blocking_block_move_reads_its_directions_then_its_timeout_then_its_count() {
10229        let mut f = Fixture::new();
10230        let syntax = "-ERR syntax error\r\n";
10231        // All three are wrong and the directions are read first.
10232        assert_eq!(
10233            f.run(&[
10234                b"BLMOVEM", b"a", b"b", b"UP", b"DOWN", b"abc", b"NOPE", b"x", b"y"
10235            ]),
10236            syntax
10237        );
10238        // Directions fine, timeout and count both wrong, so the timeout wins.
10239        assert_eq!(
10240            f.run(&[
10241                b"BLMOVEM", b"a", b"b", b"LEFT", b"RIGHT", b"abc", b"COUNT", b"abc", b"BULK"
10242            ]),
10243            "-ERR timeout is not a float or out of range\r\n"
10244        );
10245        assert_eq!(
10246            f.run(&[
10247                b"BLMOVEM", b"a", b"b", b"LEFT", b"RIGHT", b"-1", b"COUNT", b"1", b"BULK"
10248            ]),
10249            "-ERR timeout is negative\r\n"
10250        );
10251        // And with the timeout fine, the count before the ordering word.
10252        assert_eq!(
10253            f.run(&[
10254                b"BLMOVEM", b"a", b"b", b"LEFT", b"RIGHT", b"0", b"COUNT", b"abc", b"NOPE"
10255            ]),
10256            "-ERR count should be greater than 0\r\n"
10257        );
10258        assert_eq!(
10259            f.run(&[
10260                b"BLMOVEM", b"a", b"b", b"LEFT", b"RIGHT", b"0", b"COUNT", b"1", b"NOPE"
10261            ]),
10262            syntax
10263        );
10264        // Seven and eight arguments are neither of the two forms, the same way
10265        // six and seven are for `LMOVEM`.
10266        assert_eq!(
10267            f.run(&[b"BLMOVEM", b"a", b"b", b"LEFT", b"RIGHT", b"0", b"COUNT"]),
10268            syntax
10269        );
10270        assert_eq!(
10271            f.run(&[
10272                b"BLMOVEM", b"a", b"b", b"LEFT", b"RIGHT", b"0", b"COUNT", b"2"
10273            ]),
10274            syntax
10275        );
10276    }
10277
10278    /// The four ways a blocking command sees a key of another type, and the one
10279    /// way it does not.
10280    #[test]
10281    fn a_blocking_command_errors_on_a_wrong_type_rather_than_waiting_on_it() {
10282        let mut f = Fixture::new();
10283        f.run(&[b"SET", b"S", b"v"]);
10284        f.run(&[b"RPUSH", b"D", b"x"]);
10285        let wrong = "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n";
10286
10287        assert_eq!(f.run(&[b"BLPOP", b"S", b"0"]), wrong);
10288        // Every key is checked even when an earlier one would have blocked, so
10289        // an empty key in front of a string does not hide it.
10290        assert_eq!(f.run(&[b"BLPOP", b"E", b"S", b"0"]), wrong);
10291        assert_eq!(f.run(&[b"BRPOP", b"S", b"0"]), wrong);
10292        assert_eq!(f.run(&[b"BLMPOP", b"0", b"1", b"S", b"LEFT"]), wrong);
10293        assert_eq!(f.run(&[b"BRPOPLPUSH", b"S", b"D", b"0"]), wrong);
10294        // The destination, which is only reached because the source has
10295        // something in it.
10296        assert_eq!(f.run(&[b"BRPOPLPUSH", b"D", b"S", b"0"]), wrong);
10297        assert_eq!(f.run(&[b"LRANGE", b"D", b"0", b"-1"]), "*1\r\n$1\r\nx\r\n");
10298        assert_eq!(
10299            f.run(&[b"BLMOVEM", b"S", b"D", b"LEFT", b"RIGHT", b"0"]),
10300            wrong
10301        );
10302        assert_eq!(
10303            f.run(&[b"BLMOVEM", b"D", b"S", b"LEFT", b"RIGHT", b"0"]),
10304            wrong
10305        );
10306
10307        // And the one that does not: an empty source means the destination is
10308        // never looked at, so this waits rather than erroring, and on a real
10309        // server it times out.
10310        assert_eq!(
10311            f.flow(&[b"BLMOVE", b"E", b"S", b"LEFT", b"RIGHT", b"0.1"])
10312                .0,
10313            Flow::Block
10314        );
10315        // `BLMOVEM` has a second way of not being ready, and it hides the
10316        // destination just as well: the source is a list with two elements in it
10317        // and `EXACTLY` wants three, so the string never gets looked at.
10318        assert_eq!(
10319            f.flow(&[b"BLMOVEM", b"E", b"S", b"LEFT", b"RIGHT", b"0.1"])
10320                .0,
10321            Flow::Block
10322        );
10323        f.run(&[b"RPUSH", b"E", b"1", b"2"]);
10324        assert_eq!(
10325            f.flow(&[
10326                b"BLMOVEM", b"E", b"S", b"LEFT", b"RIGHT", b"0.1", b"EXACTLY", b"3", b"BULK"
10327            ])
10328            .0,
10329            Flow::Block
10330        );
10331    }
10332
10333    /// The same churn the set and the string get, because a list that leaks a
10334    /// chunk per push looks exactly like one that does not until it has run for
10335    /// an afternoon.
10336    /// Not under Miri, for the reason on `churning_sets_does_not_grow_the_server`.
10337    #[cfg_attr(miri, ignore = "the volume is the claim")]
10338    #[test]
10339    fn churning_lists_does_not_grow_the_server() {
10340        let mut f = Fixture::new();
10341        let vals: Vec<Vec<u8>> = (0..200).map(|i| format!("v{i}").into_bytes()).collect();
10342        let args: Vec<&[u8]> = [&b"RPUSH"[..], &b"k"[..]]
10343            .into_iter()
10344            .chain(vals.iter().map(Vec::as_slice))
10345            .collect();
10346
10347        f.run(&args);
10348        f.run(&[b"DEL", b"k"]);
10349        f.server.compact_step();
10350        let after_first = f.server.memory_bytes();
10351
10352        for _ in 0..200 {
10353            f.run(&args);
10354            f.run(&[b"LTRIM", b"k", b"1", b"0"]);
10355            f.server.compact_step();
10356        }
10357        assert_eq!(f.run(&[b"DBSIZE"]), ":0\r\n");
10358        assert!(
10359            f.server.memory_bytes() <= after_first * 2,
10360            "held {} after two hundred passes against {after_first} after one",
10361            f.server.memory_bytes()
10362        );
10363    }
10364
10365    // ------------------------------------------------------------ sorted set
10366
10367    #[test]
10368    fn a_sorted_set_takes_scores_and_gives_them_back() {
10369        let mut f = Fixture::new();
10370        assert_eq!(f.run(&[b"ZADD", b"z", b"1", b"a", b"2", b"b"]), ":2\r\n");
10371        assert_eq!(f.run(&[b"ZADD", b"z", b"1", b"a", b"3", b"c"]), ":1\r\n");
10372        assert_eq!(f.run(&[b"ZCARD", b"z"]), ":3\r\n");
10373        assert_eq!(f.run(&[b"ZSCORE", b"z", b"b"]), "$1\r\n2\r\n");
10374        assert_eq!(f.run(&[b"ZSCORE", b"z", b"nope"]), "$-1\r\n");
10375        assert_eq!(f.run(&[b"ZSCORE", b"nokey", b"b"]), "$-1\r\n");
10376        assert_eq!(
10377            f.run(&[b"ZMSCORE", b"z", b"a", b"nope", b"c"]),
10378            "*3\r\n$1\r\n1\r\n$-1\r\n$1\r\n3\r\n"
10379        );
10380        assert_eq!(f.run(&[b"ZREM", b"z", b"a", b"nope"]), ":1\r\n");
10381        assert_eq!(f.run(&[b"ZCARD", b"z"]), ":2\r\n");
10382        // The key goes when the last member does.
10383        assert_eq!(f.run(&[b"ZREM", b"z", b"b", b"c"]), ":2\r\n");
10384        assert_eq!(f.run(&[b"EXISTS", b"z"]), ":0\r\n");
10385    }
10386
10387    #[test]
10388    fn a_score_is_a_double_on_resp3_and_digits_on_resp2() {
10389        let mut f = Fixture::new();
10390        f.run(&[b"ZADD", b"z", b"1.5", b"a", b"inf", b"b", b"-inf", b"c"]);
10391        assert_eq!(f.run(&[b"ZSCORE", b"z", b"a"]), "$3\r\n1.5\r\n");
10392        assert_eq!(f.run(&[b"ZSCORE", b"z", b"b"]), "$3\r\ninf\r\n");
10393        assert_eq!(f.run(&[b"ZSCORE", b"z", b"c"]), "$4\r\n-inf\r\n");
10394
10395        f.out = Out::new(Proto::Resp3);
10396        assert_eq!(f.run(&[b"ZSCORE", b"z", b"a"]), ",1.5\r\n");
10397        assert_eq!(f.run(&[b"ZSCORE", b"z", b"b"]), ",inf\r\n");
10398        assert_eq!(f.run(&[b"ZSCORE", b"z", b"c"]), ",-inf\r\n");
10399        assert_eq!(f.run(&[b"ZSCORE", b"z", b"nope"]), "_\r\n");
10400    }
10401
10402    #[test]
10403    fn the_zadd_options_gate_what_gets_written() {
10404        let mut f = Fixture::new();
10405        f.run(&[b"ZADD", b"z", b"5", b"a"]);
10406        // NX leaves a member that is there alone, XX will not create one.
10407        assert_eq!(f.run(&[b"ZADD", b"z", b"NX", b"9", b"a"]), ":0\r\n");
10408        assert_eq!(f.run(&[b"ZSCORE", b"z", b"a"]), "$1\r\n5\r\n");
10409        assert_eq!(f.run(&[b"ZADD", b"z", b"XX", b"9", b"new"]), ":0\r\n");
10410        assert_eq!(f.run(&[b"EXISTS", b"z"]), ":1\r\n");
10411        // GT and LT only move a score one way.
10412        assert_eq!(f.run(&[b"ZADD", b"z", b"GT", b"CH", b"3", b"a"]), ":0\r\n");
10413        assert_eq!(f.run(&[b"ZADD", b"z", b"GT", b"CH", b"7", b"a"]), ":1\r\n");
10414        assert_eq!(f.run(&[b"ZADD", b"z", b"LT", b"CH", b"9", b"a"]), ":0\r\n");
10415        // CH counts a moved score and plain ZADD does not.
10416        assert_eq!(f.run(&[b"ZADD", b"z", b"1", b"a", b"1", b"b"]), ":1\r\n");
10417        assert_eq!(
10418            f.run(&[b"ZADD", b"z", b"CH", b"2", b"a", b"2", b"c"]),
10419            ":2\r\n"
10420        );
10421    }
10422
10423    #[test]
10424    fn zadd_incr_answers_a_score_or_nothing_at_all() {
10425        let mut f = Fixture::new();
10426        assert_eq!(f.run(&[b"ZADD", b"z", b"INCR", b"5", b"m"]), "$1\r\n5\r\n");
10427        assert_eq!(f.run(&[b"ZADD", b"z", b"INCR", b"2", b"m"]), "$1\r\n7\r\n");
10428        // A gate that refuses is the string nil, because the reply it stands in
10429        // for is a score.
10430        assert_eq!(
10431            f.run(&[b"ZADD", b"z", b"NX", b"INCR", b"2", b"m"]),
10432            "$-1\r\n"
10433        );
10434        assert_eq!(
10435            f.run(&[b"ZADD", b"z", b"XX", b"INCR", b"2", b"gone"]),
10436            "$-1\r\n"
10437        );
10438        assert_eq!(
10439            f.run(&[b"ZADD", b"z", b"GT", b"INCR", b"-1", b"m"]),
10440            "$-1\r\n"
10441        );
10442        assert_eq!(
10443            f.run(&[b"ZADD", b"z", b"GT", b"INCR", b"1", b"m"]),
10444            "$1\r\n8\r\n"
10445        );
10446        assert_eq!(f.run(&[b"ZINCRBY", b"z", b"2", b"m"]), "$2\r\n10\r\n");
10447        assert_eq!(f.run(&[b"ZINCRBY", b"z", b"1", b"fresh"]), "$1\r\n1\r\n");
10448    }
10449
10450    #[test]
10451    fn the_two_infinities_will_not_be_added_together() {
10452        let mut f = Fixture::new();
10453        f.run(&[b"ZADD", b"z", b"inf", b"m"]);
10454        let nan = "-ERR resulting score is not a number (NaN)\r\n";
10455        assert_eq!(f.run(&[b"ZINCRBY", b"z", b"-inf", b"m"]), nan);
10456        assert_eq!(f.run(&[b"ZADD", b"z", b"INCR", b"-inf", b"m"]), nan);
10457        assert_eq!(f.run(&[b"ZSCORE", b"z", b"m"]), "$3\r\ninf\r\n");
10458        // And a key made for an increment that then fails does not stay behind.
10459        assert_eq!(f.run(&[b"ZINCRBY", b"gone", b"1", b"m"]), "$1\r\n1\r\n");
10460    }
10461
10462    #[test]
10463    fn zadd_says_its_mistakes_the_way_redis_says_them() {
10464        let mut f = Fixture::new();
10465        // The pairs are counted before the options are looked at, so this is a
10466        // syntax error about having none and not a complaint about NX and XX.
10467        assert_eq!(
10468            f.run(&[b"ZADD", b"z", b"NX", b"XX"]),
10469            "-ERR syntax error\r\n"
10470        );
10471        assert_eq!(
10472            f.run(&[b"ZADD", b"z", b"NX", b"XX", b"1", b"a"]),
10473            "-ERR XX and NX options at the same time are not compatible\r\n"
10474        );
10475        let gtlt = "-ERR GT, LT, and/or NX options at the same time are not compatible\r\n";
10476        assert_eq!(f.run(&[b"ZADD", b"z", b"NX", b"GT", b"1", b"a"]), gtlt);
10477        assert_eq!(f.run(&[b"ZADD", b"z", b"GT", b"LT", b"1", b"a"]), gtlt);
10478        assert_eq!(
10479            f.run(&[b"ZADD", b"z", b"INCR", b"1", b"a", b"2", b"b"]),
10480            "-ERR INCR option supports a single increment-element pair\r\n"
10481        );
10482        // An odd number of arguments after the options.
10483        assert_eq!(
10484            f.run(&[b"ZADD", b"z", b"1", b"a", b"2"]),
10485            "-ERR syntax error\r\n"
10486        );
10487        // Every score is read before the first is stored.
10488        assert_eq!(
10489            f.run(&[b"ZADD", b"z", b"1", b"a", b"nonsense", b"b"]),
10490            "-ERR value is not a valid float\r\n"
10491        );
10492        assert_eq!(f.run(&[b"EXISTS", b"z"]), ":0\r\n");
10493    }
10494
10495    #[test]
10496    fn a_rank_says_where_a_member_sits_from_either_end() {
10497        let mut f = Fixture::new();
10498        f.run(&[b"ZADD", b"z", b"1", b"a", b"2", b"b", b"3", b"c"]);
10499        assert_eq!(f.run(&[b"ZRANK", b"z", b"a"]), ":0\r\n");
10500        assert_eq!(f.run(&[b"ZRANK", b"z", b"c"]), ":2\r\n");
10501        assert_eq!(f.run(&[b"ZREVRANK", b"z", b"c"]), ":0\r\n");
10502        assert_eq!(f.run(&[b"ZREVRANK", b"z", b"a"]), ":2\r\n");
10503        // WITHSCORE changes both shapes: the answer and the nothing.
10504        assert_eq!(
10505            f.run(&[b"ZRANK", b"z", b"b", b"WITHSCORE"]),
10506            "*2\r\n:1\r\n$1\r\n2\r\n"
10507        );
10508        assert_eq!(f.run(&[b"ZRANK", b"z", b"nope"]), "$-1\r\n");
10509        assert_eq!(f.run(&[b"ZRANK", b"z", b"nope", b"WITHSCORE"]), "*-1\r\n");
10510        assert_eq!(f.run(&[b"ZRANK", b"nokey", b"a", b"WITHSCORE"]), "*-1\r\n");
10511        // A bad option is a syntax error and one argument too many is an arity
10512        // error, which is Redis's split.
10513        assert_eq!(
10514            f.run(&[b"ZRANK", b"z", b"b", b"bogus"]),
10515            "-ERR syntax error\r\n"
10516        );
10517        assert_eq!(
10518            f.run(&[b"ZREVRANK", b"z", b"b", b"WITHSCORE", b"more"]),
10519            "-ERR wrong number of arguments for 'zrevrank' command\r\n"
10520        );
10521    }
10522
10523    #[test]
10524    fn the_two_counts_read_their_two_kinds_of_bound() {
10525        let mut f = Fixture::new();
10526        f.run(&[b"ZADD", b"z", b"1", b"a", b"2", b"b", b"3", b"c"]);
10527        assert_eq!(f.run(&[b"ZCOUNT", b"z", b"-inf", b"+inf"]), ":3\r\n");
10528        assert_eq!(f.run(&[b"ZCOUNT", b"z", b"2", b"3"]), ":2\r\n");
10529        assert_eq!(f.run(&[b"ZCOUNT", b"z", b"(1", b"3"]), ":2\r\n");
10530        assert_eq!(f.run(&[b"ZCOUNT", b"z", b"(1", b"(3"]), ":1\r\n");
10531        assert_eq!(f.run(&[b"ZCOUNT", b"nokey", b"-inf", b"+inf"]), ":0\r\n");
10532        assert_eq!(
10533            f.run(&[b"ZCOUNT", b"z", b"bogus", b"3"]),
10534            "-ERR min or max is not a float\r\n"
10535        );
10536
10537        f.run(&[b"ZADD", b"l", b"0", b"a", b"0", b"b", b"0", b"c"]);
10538        assert_eq!(f.run(&[b"ZLEXCOUNT", b"l", b"-", b"+"]), ":3\r\n");
10539        assert_eq!(f.run(&[b"ZLEXCOUNT", b"l", b"[a", b"(c"]), ":2\r\n");
10540        assert_eq!(f.run(&[b"ZLEXCOUNT", b"l", b"(a", b"+"]), ":2\r\n");
10541        // A bare member is not a bound, because a member can start with any
10542        // byte and there would be no way to say the bracket if it were optional.
10543        assert_eq!(
10544            f.run(&[b"ZLEXCOUNT", b"l", b"a", b"c"]),
10545            "-ERR min or max not valid string range item\r\n"
10546        );
10547    }
10548
10549    /// The three ways `ZRANGE` can be asked for a window, forwards and back.
10550    ///
10551    /// Every byte in here was read off a real 8.10.1 rather than worked out,
10552    /// because the interesting part of this command is not what it selects, it
10553    /// is which of the two ends the client is expected to name first.
10554    #[test]
10555    fn one_range_command_selects_by_rank_or_score_or_name() {
10556        let mut f = Fixture::new();
10557        f.run(&[b"ZADD", b"z", b"1", b"a", b"2", b"b", b"3", b"c"]);
10558        assert_eq!(
10559            f.run(&[b"ZRANGE", b"z", b"0", b"-1"]),
10560            "*3\r\n$1\r\na\r\n$1\r\nb\r\n$1\r\nc\r\n"
10561        );
10562        assert_eq!(
10563            f.run(&[b"ZRANGE", b"z", b"-2", b"-1"]),
10564            "*2\r\n$1\r\nb\r\n$1\r\nc\r\n"
10565        );
10566        assert_eq!(f.run(&[b"ZRANGE", b"z", b"5", b"9"]), "*0\r\n");
10567        assert_eq!(f.run(&[b"ZRANGE", b"nokey", b"0", b"-1"]), "*0\r\n");
10568        // REV over ranks reverses the walk and leaves the two arguments alone,
10569        // because a rank counts from the end the walk starts at.
10570        assert_eq!(
10571            f.run(&[b"ZRANGE", b"z", b"0", b"-1", b"REV"]),
10572            "*3\r\n$1\r\nc\r\n$1\r\nb\r\n$1\r\na\r\n"
10573        );
10574        assert_eq!(
10575            f.run(&[b"ZRANGE", b"z", b"(1", b"+inf", b"BYSCORE"]),
10576            "*2\r\n$1\r\nb\r\n$1\r\nc\r\n"
10577        );
10578        // And REV over scores does swap them, since a bound does not count from
10579        // anywhere. This is the one line of the parse that tells the two apart.
10580        assert_eq!(
10581            f.run(&[b"ZRANGE", b"z", b"+inf", b"(1", b"BYSCORE", b"REV"]),
10582            "*2\r\n$1\r\nc\r\n$1\r\nb\r\n"
10583        );
10584        assert_eq!(
10585            f.run(&[b"ZRANGE", b"z", b"-", b"+", b"BYLEX"]),
10586            "*3\r\n$1\r\na\r\n$1\r\nb\r\n$1\r\nc\r\n"
10587        );
10588        assert_eq!(
10589            f.run(&[b"ZRANGE", b"z", b"+", b"-", b"BYLEX", b"REV"]),
10590            "*3\r\n$1\r\nc\r\n$1\r\nb\r\n$1\r\na\r\n"
10591        );
10592    }
10593
10594    /// The older spellings, which are the same six windows with the mode in the
10595    /// name and the high end named first on the three that go backwards.
10596    #[test]
10597    fn the_older_range_spellings_name_their_high_end_first() {
10598        let mut f = Fixture::new();
10599        f.run(&[b"ZADD", b"z", b"1", b"a", b"2", b"b", b"3", b"c"]);
10600        assert_eq!(
10601            f.run(&[b"ZREVRANGE", b"z", b"0", b"-1"]),
10602            "*3\r\n$1\r\nc\r\n$1\r\nb\r\n$1\r\na\r\n"
10603        );
10604        assert_eq!(
10605            f.run(&[b"ZREVRANGE", b"z", b"0", b"0", b"WITHSCORES"]),
10606            "*2\r\n$1\r\nc\r\n$1\r\n3\r\n"
10607        );
10608        assert_eq!(
10609            f.run(&[b"ZRANGEBYSCORE", b"z", b"(1", b"3"]),
10610            "*2\r\n$1\r\nb\r\n$1\r\nc\r\n"
10611        );
10612        assert_eq!(
10613            f.run(&[b"ZREVRANGEBYSCORE", b"z", b"3", b"(1"]),
10614            "*2\r\n$1\r\nc\r\n$1\r\nb\r\n"
10615        );
10616        // The two arguments the wrong way round is an empty answer and not an
10617        // error, which is what the swap being in the parse rather than in the
10618        // window buys.
10619        assert_eq!(f.run(&[b"ZREVRANGEBYSCORE", b"z", b"(1", b"3"]), "*0\r\n");
10620        assert_eq!(
10621            f.run(&[b"ZRANGEBYLEX", b"z", b"[a", b"(c"]),
10622            "*2\r\n$1\r\na\r\n$1\r\nb\r\n"
10623        );
10624        assert_eq!(
10625            f.run(&[b"ZREVRANGEBYLEX", b"z", b"(c", b"[a"]),
10626            "*2\r\n$1\r\nb\r\n$1\r\na\r\n"
10627        );
10628        // BYSCORE, BYLEX and REV mean nothing to these, so they are not another
10629        // way of spelling the mode, they are a syntax error.
10630        for cmd in [
10631            &[b"ZREVRANGE".as_slice(), b"z", b"0", b"-1", b"BYSCORE"][..],
10632            &[b"ZRANGEBYSCORE", b"z", b"1", b"3", b"REV"],
10633            &[b"ZRANGEBYLEX", b"z", b"[a", b"[c", b"BYLEX"],
10634        ] {
10635            assert_eq!(f.run(cmd), "-ERR syntax error\r\n", "{:?}", cmd[0]);
10636        }
10637    }
10638
10639    /// `LIMIT` and `WITHSCORES`, which every one of these commands reads and
10640    /// only some of them accept.
10641    #[test]
10642    fn limit_and_withscores_are_read_by_all_of_them_and_refused_afterwards() {
10643        let mut f = Fixture::new();
10644        f.run(&[b"ZADD", b"z", b"1", b"a", b"2", b"b", b"3", b"c"]);
10645        assert_eq!(
10646            f.run(&[
10647                b"ZRANGE", b"z", b"-inf", b"+inf", b"BYSCORE", b"LIMIT", b"1", b"1"
10648            ]),
10649            "*1\r\n$1\r\nb\r\n"
10650        );
10651        // A negative offset skips past everything, a negative count is no bound.
10652        assert_eq!(
10653            f.run(&[
10654                b"ZRANGE", b"z", b"-inf", b"+inf", b"BYSCORE", b"LIMIT", b"-1", b"2"
10655            ]),
10656            "*0\r\n"
10657        );
10658        assert_eq!(
10659            f.run(&[
10660                b"ZRANGE", b"z", b"-inf", b"+inf", b"BYSCORE", b"LIMIT", b"0", b"-1"
10661            ]),
10662            "*3\r\n$1\r\na\r\n$1\r\nb\r\n$1\r\nc\r\n"
10663        );
10664        // The two options in either order, which falls out of the parse loop.
10665        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";
10666        assert_eq!(
10667            f.run(&[
10668                b"ZRANGEBYSCORE",
10669                b"z",
10670                b"1",
10671                b"3",
10672                b"WITHSCORES",
10673                b"LIMIT",
10674                b"0",
10675                b"2"
10676            ]),
10677            both
10678        );
10679        assert_eq!(
10680            f.run(&[
10681                b"ZRANGEBYSCORE",
10682                b"z",
10683                b"1",
10684                b"3",
10685                b"LIMIT",
10686                b"0",
10687                b"2",
10688                b"WITHSCORES"
10689            ]),
10690            both
10691        );
10692        // LIMIT on a range by rank is refused after the whole option list has
10693        // been read, so this complains about LIMIT and not about WITHSCORES.
10694        let needs_by = "-ERR syntax error, LIMIT is only supported in combination with either BYSCORE or BYLEX\r\n";
10695        assert_eq!(
10696            f.run(&[
10697                b"ZREVRANGE",
10698                b"z",
10699                b"0",
10700                b"-1",
10701                b"WITHSCORES",
10702                b"LIMIT",
10703                b"0",
10704                b"1"
10705            ]),
10706            needs_by
10707        );
10708        assert_eq!(
10709            f.run(&[b"ZRANGE", b"z", b"0", b"-1", b"LIMIT", b"0", b"1"]),
10710            needs_by
10711        );
10712        let not_bylex = "-ERR syntax error, WITHSCORES not supported in combination with BYLEX\r\n";
10713        assert_eq!(
10714            f.run(&[b"ZRANGE", b"z", b"-", b"+", b"BYLEX", b"WITHSCORES"]),
10715            not_bylex
10716        );
10717        assert_eq!(
10718            f.run(&[b"ZRANGEBYLEX", b"z", b"[a", b"[c", b"WITHSCORES"]),
10719            not_bylex
10720        );
10721        // Two modes at once, an option nobody knows, a LIMIT missing its count,
10722        // and the three number errors, which are three different sentences.
10723        for cmd in [
10724            &[
10725                b"ZRANGE".as_slice(),
10726                b"z",
10727                b"0",
10728                b"-1",
10729                b"BYSCORE",
10730                b"BYLEX",
10731            ][..],
10732            &[b"ZRANGE", b"z", b"0", b"-1", b"junk"],
10733            &[b"ZRANGEBYSCORE", b"z", b"1", b"3", b"LIMIT", b"0"],
10734        ] {
10735            assert_eq!(f.run(cmd), "-ERR syntax error\r\n", "{cmd:?}");
10736        }
10737        assert_eq!(
10738            f.run(&[b"ZRANGEBYSCORE", b"z", b"bad", b"3"]),
10739            "-ERR min or max is not a float\r\n"
10740        );
10741        assert_eq!(
10742            f.run(&[b"ZRANGEBYLEX", b"z", b"a", b"[c"]),
10743            "-ERR min or max not valid string range item\r\n"
10744        );
10745        assert_eq!(
10746            f.run(&[b"ZRANGEBYSCORE", b"z", b"1", b"3", b"LIMIT", b"a", b"2"]),
10747            "-ERR value is not an integer or out of range\r\n"
10748        );
10749    }
10750
10751    /// `WITHSCORES` is the one place in this group where the two protocols
10752    /// disagree about the shape of the reply and not just the type of a value.
10753    #[test]
10754    fn withscores_nests_on_resp3_and_flattens_on_resp2() {
10755        let mut f = Fixture::new();
10756        f.run(&[b"ZADD", b"z", b"1", b"a", b"2", b"b", b"3", b"c"]);
10757        assert_eq!(
10758            f.run(&[b"ZRANGE", b"z", b"0", b"-1", b"WITHSCORES"]),
10759            "*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"
10760        );
10761        f.out = Out::new(Proto::Resp3);
10762        assert_eq!(
10763            f.run(&[b"ZRANGE", b"z", b"0", b"-1", b"WITHSCORES"]),
10764            "*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"
10765        );
10766        assert_eq!(
10767            f.run(&[b"ZRANGE", b"z", b"0", b"-1"]),
10768            "*3\r\n$1\r\na\r\n$1\r\nb\r\n$1\r\nc\r\n"
10769        );
10770    }
10771
10772    /// The store form, which is the same parse with the destination in front.
10773    #[test]
10774    fn a_range_store_writes_the_window_into_another_key() {
10775        let mut f = Fixture::new();
10776        f.run(&[b"ZADD", b"z", b"1", b"a", b"2", b"b", b"3", b"c"]);
10777        assert_eq!(f.run(&[b"ZRANGESTORE", b"d", b"z", b"0", b"-1"]), ":3\r\n");
10778        // A window that selects nothing deletes the destination rather than
10779        // leaving an empty sorted set, because an empty one does not exist.
10780        assert_eq!(f.run(&[b"ZRANGESTORE", b"d", b"z", b"5", b"9"]), ":0\r\n");
10781        assert_eq!(f.run(&[b"EXISTS", b"d"]), ":0\r\n");
10782        assert_eq!(
10783            f.run(&[b"ZRANGESTORE", b"d", b"z", b"(1", b"+inf", b"BYSCORE"]),
10784            ":2\r\n"
10785        );
10786        assert_eq!(
10787            f.run(&[b"ZRANGE", b"d", b"0", b"-1", b"WITHSCORES"]),
10788            "*4\r\n$1\r\nb\r\n$1\r\n2\r\n$1\r\nc\r\n$1\r\n3\r\n"
10789        );
10790        // The destination is allowed to be the source, because the result is
10791        // built whole before anything is written over.
10792        assert_eq!(f.run(&[b"ZRANGESTORE", b"z", b"z", b"1", b"2"]), ":2\r\n");
10793        assert_eq!(
10794            f.run(&[b"ZRANGE", b"z", b"0", b"-1", b"WITHSCORES"]),
10795            "*4\r\n$1\r\nb\r\n$1\r\n2\r\n$1\r\nc\r\n$1\r\n3\r\n"
10796        );
10797        // It takes every option ZRANGE takes except WITHSCORES, which is a
10798        // plain syntax error here and not the sentence about BYLEX.
10799        assert_eq!(
10800            f.run(&[b"ZRANGESTORE", b"d", b"z", b"0", b"-1", b"WITHSCORES"]),
10801            "-ERR syntax error\r\n"
10802        );
10803    }
10804
10805    /// The three removals, which are the read side's window with the walk
10806    /// turned into a removal and no options at all.
10807    #[test]
10808    fn the_three_removals_share_their_window_with_the_reads() {
10809        let mut f = Fixture::new();
10810        f.run(&[b"ZADD", b"z", b"1", b"a", b"2", b"b", b"3", b"c"]);
10811        assert_eq!(f.run(&[b"ZREMRANGEBYRANK", b"z", b"0", b"0"]), ":1\r\n");
10812        assert_eq!(
10813            f.run(&[b"ZRANGE", b"z", b"0", b"-1"]),
10814            "*2\r\n$1\r\nb\r\n$1\r\nc\r\n"
10815        );
10816        assert_eq!(
10817            f.run(&[b"ZREMRANGEBYSCORE", b"z", b"(2", b"+inf"]),
10818            ":1\r\n"
10819        );
10820        assert_eq!(f.run(&[b"ZRANGE", b"z", b"0", b"-1"]), "*1\r\n$1\r\nb\r\n");
10821        // The last member going takes the key with it.
10822        assert_eq!(f.run(&[b"ZREMRANGEBYLEX", b"z", b"-", b"+"]), ":1\r\n");
10823        assert_eq!(f.run(&[b"EXISTS", b"z"]), ":0\r\n");
10824        assert_eq!(
10825            f.run(&[b"ZREMRANGEBYRANK", b"nokey", b"0", b"-1"]),
10826            ":0\r\n"
10827        );
10828        assert_eq!(
10829            f.run(&[b"ZREMRANGEBYRANK", b"z", b"0", b"x"]),
10830            "-ERR value is not an integer or out of range\r\n"
10831        );
10832    }
10833
10834    /// The algebra, which is one gather and three names for it.
10835    #[test]
10836    fn the_three_algebra_commands_combine_scores_and_order_the_answer_once() {
10837        let mut f = Fixture::new();
10838        f.run(&[b"ZADD", b"z", b"1", b"a", b"2", b"b", b"3", b"c"]);
10839        f.run(&[b"ZADD", b"y", b"10", b"b", b"20", b"d"]);
10840        assert_eq!(
10841            f.run(&[b"ZUNION", b"2", b"z", b"y"]),
10842            "*4\r\n$1\r\na\r\n$1\r\nc\r\n$1\r\nb\r\n$1\r\nd\r\n"
10843        );
10844        // The scores are added where a member is in both, and the answer comes
10845        // out in the order those combined scores put it in.
10846        assert_eq!(
10847            f.run(&[b"ZUNION", b"2", b"z", b"y", b"WITHSCORES"]),
10848            "*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"
10849        );
10850        assert_eq!(
10851            f.run(&[
10852                b"ZUNION",
10853                b"2",
10854                b"z",
10855                b"y",
10856                b"WEIGHTS",
10857                b"2",
10858                b"3",
10859                b"WITHSCORES"
10860            ]),
10861            "*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"
10862        );
10863        assert_eq!(
10864            f.run(&[
10865                b"ZUNION",
10866                b"2",
10867                b"z",
10868                b"y",
10869                b"AGGREGATE",
10870                b"MIN",
10871                b"WITHSCORES"
10872            ]),
10873            "*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"
10874        );
10875        assert_eq!(
10876            f.run(&[
10877                b"ZUNION",
10878                b"2",
10879                b"z",
10880                b"y",
10881                b"AGGREGATE",
10882                b"MAX",
10883                b"WITHSCORES"
10884            ]),
10885            "*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"
10886        );
10887        assert_eq!(
10888            f.run(&[b"ZINTER", b"2", b"z", b"y", b"WITHSCORES"]),
10889            "*2\r\n$1\r\nb\r\n$2\r\n12\r\n"
10890        );
10891        assert_eq!(
10892            f.run(&[b"ZDIFF", b"2", b"z", b"y", b"WITHSCORES"]),
10893            "*4\r\n$1\r\na\r\n$1\r\n1\r\n$1\r\nc\r\n$1\r\n3\r\n"
10894        );
10895        assert_eq!(f.run(&[b"ZUNION", b"1", b"nokey"]), "*0\r\n");
10896        // A plain set is an input, and it behaves as a sorted set in which
10897        // every member scores one.
10898        f.run(&[b"SADD", b"p", b"a", b"d"]);
10899        assert_eq!(
10900            f.run(&[b"ZUNION", b"2", b"z", b"p", b"WITHSCORES"]),
10901            "*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"
10902        );
10903        // A difference never combines two scores, so it has nothing for either
10904        // of the two options to do and refuses both.
10905        for cmd in [
10906            &[
10907                b"ZDIFF".as_slice(),
10908                b"2",
10909                b"z",
10910                b"y",
10911                b"WEIGHTS",
10912                b"1",
10913                b"1",
10914            ][..],
10915            &[b"ZDIFF", b"2", b"z", b"y", b"AGGREGATE", b"MIN"],
10916        ] {
10917            assert_eq!(f.run(cmd), "-ERR syntax error\r\n", "{cmd:?}");
10918        }
10919    }
10920
10921    /// The count of keys, which is what lets a key be named `WEIGHTS`.
10922    #[test]
10923    fn the_algebra_counts_its_keys_and_says_so_when_the_count_is_wrong() {
10924        let mut f = Fixture::new();
10925        f.run(&[b"ZADD", b"z", b"1", b"a"]);
10926        f.run(&[b"ZADD", b"y", b"2", b"b"]);
10927        // Redis names the command in this one, so each spelling says its own.
10928        assert_eq!(
10929            f.run(&[b"ZUNION", b"0", b"z"]),
10930            "-ERR at least 1 input key is needed for 'zunion' command\r\n"
10931        );
10932        assert_eq!(
10933            f.run(&[b"ZUNION", b"-1", b"z"]),
10934            "-ERR at least 1 input key is needed for 'zunion' command\r\n"
10935        );
10936        assert_eq!(
10937            f.run(&[b"ZINTERCARD", b"0", b"z"]),
10938            "-ERR at least 1 input key is needed for 'zintercard' command\r\n"
10939        );
10940        // A count bigger than the line is a plain syntax error, which reads
10941        // oddly and is what Redis says.
10942        assert_eq!(
10943            f.run(&[b"ZUNION", b"3", b"z", b"y"]),
10944            "-ERR syntax error\r\n"
10945        );
10946        assert_eq!(
10947            f.run(&[b"ZUNION", b"x", b"z"]),
10948            "-ERR value is not an integer or out of range\r\n"
10949        );
10950        // A WEIGHTS list that is not one per key is a syntax error, and a
10951        // weight that is not a number gets a sentence of its own.
10952        assert_eq!(
10953            f.run(&[b"ZUNION", b"2", b"z", b"y", b"WEIGHTS", b"1"]),
10954            "-ERR syntax error\r\n"
10955        );
10956        assert_eq!(
10957            f.run(&[b"ZUNION", b"2", b"z", b"y", b"WEIGHTS", b"a", b"b"]),
10958            "-ERR weight value is not a float\r\n"
10959        );
10960        assert_eq!(
10961            f.run(&[b"ZUNION", b"2", b"z", b"y", b"AGGREGATE", b"NOPE"]),
10962            "-ERR syntax error\r\n"
10963        );
10964    }
10965
10966    /// The three store forms, which answer a count and take no WITHSCORES.
10967    #[test]
10968    fn the_algebra_stores_answer_a_count_and_delete_an_empty_destination() {
10969        let mut f = Fixture::new();
10970        f.run(&[b"ZADD", b"z", b"1", b"a", b"2", b"b", b"3", b"c"]);
10971        f.run(&[b"ZADD", b"y", b"10", b"b", b"20", b"d"]);
10972        assert_eq!(f.run(&[b"ZUNIONSTORE", b"d", b"2", b"z", b"y"]), ":4\r\n");
10973        assert_eq!(
10974            f.run(&[b"ZRANGE", b"d", b"0", b"-1", b"WITHSCORES"]),
10975            "*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"
10976        );
10977        assert_eq!(f.run(&[b"ZINTERSTORE", b"d", b"2", b"z", b"y"]), ":1\r\n");
10978        assert_eq!(f.run(&[b"ZDIFFSTORE", b"d", b"2", b"z", b"y"]), ":2\r\n");
10979        // An empty result deletes the destination rather than leaving an empty
10980        // sorted set, because an empty one does not exist.
10981        assert_eq!(
10982            f.run(&[b"ZINTERSTORE", b"d", b"2", b"z", b"nokey"]),
10983            ":0\r\n"
10984        );
10985        assert_eq!(f.run(&[b"EXISTS", b"d"]), ":0\r\n");
10986        // The destination is allowed to name its own source.
10987        assert_eq!(f.run(&[b"ZUNIONSTORE", b"z", b"2", b"z", b"y"]), ":4\r\n");
10988        assert_eq!(f.run(&[b"ZCARD", b"z"]), ":4\r\n");
10989        for cmd in [
10990            &[
10991                b"ZUNIONSTORE".as_slice(),
10992                b"d",
10993                b"2",
10994                b"z",
10995                b"y",
10996                b"WITHSCORES",
10997            ][..],
10998            &[
10999                b"ZDIFFSTORE",
11000                b"d",
11001                b"2",
11002                b"z",
11003                b"y",
11004                b"WEIGHTS",
11005                b"1",
11006                b"1",
11007            ],
11008        ] {
11009            assert_eq!(f.run(cmd), "-ERR syntax error\r\n", "{cmd:?}");
11010        }
11011    }
11012
11013    /// `ZINTERCARD`, which counts without building anything.
11014    #[test]
11015    fn intercard_counts_and_stops_at_its_limit() {
11016        let mut f = Fixture::new();
11017        f.run(&[b"ZADD", b"z", b"1", b"a", b"2", b"b", b"3", b"c"]);
11018        f.run(&[b"ZADD", b"y", b"10", b"b", b"20", b"c", b"30", b"d"]);
11019        assert_eq!(f.run(&[b"ZINTERCARD", b"2", b"z", b"y"]), ":2\r\n");
11020        // A limit of zero is no limit, which is Redis's reading of it.
11021        assert_eq!(
11022            f.run(&[b"ZINTERCARD", b"2", b"z", b"y", b"LIMIT", b"0"]),
11023            ":2\r\n"
11024        );
11025        assert_eq!(
11026            f.run(&[b"ZINTERCARD", b"2", b"z", b"y", b"LIMIT", b"1"]),
11027            ":1\r\n"
11028        );
11029        // A negative limit and a limit that is not a number at all get the same
11030        // sentence, which looks like a mistake in Redis and is copied as one.
11031        let bad = "-ERR LIMIT can't be negative\r\n";
11032        assert_eq!(
11033            f.run(&[b"ZINTERCARD", b"2", b"z", b"y", b"LIMIT", b"-1"]),
11034            bad
11035        );
11036        assert_eq!(
11037            f.run(&[b"ZINTERCARD", b"2", b"z", b"y", b"LIMIT", b"x"]),
11038            bad
11039        );
11040        for cmd in [
11041            &[b"ZINTERCARD".as_slice(), b"3", b"z", b"y"][..],
11042            &[b"ZINTERCARD", b"2", b"z", b"y", b"LIMIT"],
11043            &[b"ZINTERCARD", b"2", b"z", b"y", b"junk", b"1"],
11044        ] {
11045            assert_eq!(f.run(cmd), "-ERR syntax error\r\n", "{cmd:?}");
11046        }
11047    }
11048
11049    /// `ZRANDMEMBER`, which answers two different shapes out of one name.
11050    #[test]
11051    fn a_draw_answers_one_member_or_an_array_of_them() {
11052        let mut f = Fixture::new();
11053        f.run(&[b"ZADD", b"z", b"1", b"a", b"2", b"b", b"3", b"c"]);
11054        // No count is one member or a nil, a count is an array that may be
11055        // empty, and those are two reply types the client has to tell apart.
11056        assert_eq!(f.run(&[b"ZRANDMEMBER", b"nokey"]), "$-1\r\n");
11057        assert_eq!(f.run(&[b"ZRANDMEMBER", b"nokey", b"3"]), "*0\r\n");
11058        assert_eq!(f.run(&[b"ZRANDMEMBER", b"z", b"0"]), "*0\r\n");
11059        assert!(f.run(&[b"ZRANDMEMBER", b"z"]).starts_with("$1\r\n"));
11060        // A positive count draws without replacement, so a count over the size
11061        // answers the whole set and never a member twice.
11062        let all = f.run(&[b"ZRANDMEMBER", b"z", b"10"]);
11063        assert!(all.starts_with("*3\r\n"), "{all}");
11064        for m in ["a", "b", "c"] {
11065            assert!(all.contains(m), "{all}");
11066        }
11067        // A negative one draws with replacement and answers exactly as many as
11068        // it was asked for, whatever the size of the set.
11069        assert!(
11070            f.run(&[b"ZRANDMEMBER", b"z", b"-5"]).starts_with("*5\r\n"),
11071            "five draws with replacement"
11072        );
11073        assert!(
11074            f.run(&[b"ZRANDMEMBER", b"z", b"2", b"WITHSCORES"])
11075                .starts_with("*4\r\n"),
11076            "two pairs, flat on RESP2"
11077        );
11078        f.out = Out::new(Proto::Resp3);
11079        let got = f.run(&[b"ZRANDMEMBER", b"z", b"2", b"WITHSCORES"]);
11080        assert!(got.starts_with("*2\r\n*2\r\n"), "{got}");
11081        assert_eq!(f.run(&[b"ZRANDMEMBER", b"nokey"]), "_\r\n");
11082        f.out = Out::new(Proto::Resp2);
11083        assert_eq!(
11084            f.run(&[b"ZRANDMEMBER", b"z", b"2", b"junk"]),
11085            "-ERR syntax error\r\n"
11086        );
11087        assert_eq!(
11088            f.run(&[b"ZRANDMEMBER", b"z", b"x"]),
11089            "-ERR value is not an integer or out of range\r\n"
11090        );
11091    }
11092
11093    /// `ZSCAN`, and the one sorted set reply where a score is not a double.
11094    #[test]
11095    fn a_sorted_set_scan_answers_pairs_of_strings_on_both_protocols() {
11096        let mut f = Fixture::new();
11097        f.run(&[b"ZADD", b"z", b"1", b"a", b"2", b"b", b"3", b"c"]);
11098        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";
11099        assert_eq!(f.run(&[b"ZSCAN", b"z", b"0"]), all);
11100        assert_eq!(f.run(&[b"ZSCAN", b"z", b"0", b"COUNT", b"10"]), all);
11101        assert_eq!(
11102            f.run(&[b"ZSCAN", b"z", b"0", b"MATCH", b"a*"]),
11103            "*2\r\n$1\r\n0\r\n*2\r\n$1\r\na\r\n$1\r\n1\r\n"
11104        );
11105        assert_eq!(
11106            f.run(&[b"ZSCAN", b"nokey", b"0"]),
11107            "*2\r\n$1\r\n0\r\n*0\r\n"
11108        );
11109        // A score stays a bulk string on RESP3, which is the one place the two
11110        // protocols agree about a score and everywhere else they do not.
11111        f.out = Out::new(Proto::Resp3);
11112        assert_eq!(f.run(&[b"ZSCAN", b"z", b"0"]), all);
11113        f.out = Out::new(Proto::Resp2);
11114        assert_eq!(
11115            f.run(&[b"ZSCAN", b"z", b"0", b"NOVALUES"]),
11116            "-ERR NOVALUES option can only be used in HSCAN\r\n"
11117        );
11118        assert_eq!(f.run(&[b"ZSCAN", b"z", b"-1"]), "-ERR invalid cursor\r\n");
11119        assert_eq!(
11120            f.run(&[b"ZSCAN", b"z", b"0", b"COUNT", b"0"]),
11121            "-ERR syntax error\r\n"
11122        );
11123    }
11124
11125    /// The count is what decides the shape, and its value is not.
11126    #[test]
11127    fn a_sorted_set_pop_changes_shape_when_it_is_given_a_count() {
11128        let mut f = Fixture::new();
11129        f.run(&[b"ZADD", b"z", b"1", b"a", b"2", b"b", b"3", b"c"]);
11130        // No count, so one flat pair, and the score is a bulk string on RESP2.
11131        assert_eq!(f.run(&[b"ZPOPMIN", b"z"]), "*2\r\n$1\r\na\r\n$1\r\n1\r\n");
11132        assert_eq!(f.run(&[b"ZPOPMAX", b"z"]), "*2\r\n$1\r\nc\r\n$1\r\n3\r\n");
11133        f.run(&[b"ZADD", b"z", b"1", b"a", b"3", b"c"]);
11134        // A count, so pairs, and on RESP2 they are flattened into one run.
11135        assert_eq!(
11136            f.run(&[b"ZPOPMIN", b"z", b"2"]),
11137            "*4\r\n$1\r\na\r\n$1\r\n1\r\n$1\r\nb\r\n$1\r\n2\r\n"
11138        );
11139        // An empty array rather than a null, which is where a sorted set pop and
11140        // a list pop part company, and the same answer a count of zero gives.
11141        assert_eq!(f.run(&[b"ZPOPMIN", b"nokey"]), "*0\r\n");
11142        assert_eq!(f.run(&[b"ZPOPMIN", b"nokey", b"2"]), "*0\r\n");
11143        assert_eq!(f.run(&[b"ZPOPMIN", b"z", b"0"]), "*0\r\n");
11144        // The last member takes the key with it.
11145        assert_eq!(
11146            f.run(&[b"ZPOPMIN", b"z", b"9"]),
11147            "*2\r\n$1\r\nc\r\n$1\r\n3\r\n"
11148        );
11149        assert_eq!(f.run(&[b"EXISTS", b"z"]), ":0\r\n");
11150
11151        f.run(&[b"ZADD", b"z", b"1", b"a", b"2", b"b"]);
11152        f.out = Out::new(Proto::Resp3);
11153        assert_eq!(f.run(&[b"ZPOPMIN", b"z"]), "*2\r\n$1\r\na\r\n,1\r\n");
11154        assert_eq!(
11155            f.run(&[b"ZPOPMIN", b"z", b"1"]),
11156            "*1\r\n*2\r\n$1\r\nb\r\n,2\r\n"
11157        );
11158        f.out = Out::new(Proto::Resp2);
11159        // Both of these are the range error rather than the usual sentence about
11160        // integers, which is the odd answer and so the one worth copying.
11161        let bad = "-ERR value is out of range, must be positive\r\n";
11162        assert_eq!(f.run(&[b"ZPOPMIN", b"z", b"x"]), bad);
11163        assert_eq!(f.run(&[b"ZPOPMIN", b"z", b"-1"]), bad);
11164        assert_eq!(
11165            f.run(&[b"ZPOPMIN", b"z", b"1", b"2"]),
11166            "-ERR syntax error\r\n"
11167        );
11168    }
11169
11170    /// `ZMPOP`, which is `LMPOP` with scores and the same parse.
11171    #[test]
11172    fn a_multi_key_pop_names_the_key_that_answered_and_nests_its_pairs() {
11173        let mut f = Fixture::new();
11174        f.run(&[b"ZADD", b"z", b"1", b"a", b"2", b"b", b"3", b"c"]);
11175        assert_eq!(
11176            f.run(&[b"ZMPOP", b"2", b"nokey", b"z", b"MIN"]),
11177            "*2\r\n$1\r\nz\r\n*1\r\n*2\r\n$1\r\na\r\n$1\r\n1\r\n"
11178        );
11179        // Nested on RESP2 as well, because the key name is already in front of
11180        // the pairs and there is nothing left to flatten into.
11181        assert_eq!(
11182            f.run(&[b"ZMPOP", b"1", b"z", b"MAX", b"COUNT", b"2"]),
11183            "*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"
11184        );
11185        // A null array and not a null, the same as LMPOP.
11186        assert_eq!(f.run(&[b"ZMPOP", b"1", b"nokey", b"MIN"]), "*-1\r\n");
11187        f.out = Out::new(Proto::Resp3);
11188        assert_eq!(f.run(&[b"ZMPOP", b"1", b"nokey", b"MIN"]), "_\r\n");
11189        f.out = Out::new(Proto::Resp2);
11190        let numkeys = "-ERR numkeys should be greater than 0\r\n";
11191        for bad in [
11192            &[b"ZMPOP".as_slice(), b"0", b"z", b"MIN"][..],
11193            &[b"ZMPOP", b"-1", b"z", b"MIN"],
11194            &[b"ZMPOP", b"x", b"z", b"MIN"],
11195        ] {
11196            assert_eq!(f.run(bad), numkeys, "{:?}", bad[1]);
11197        }
11198        let count = "-ERR count should be greater than 0\r\n";
11199        for bad in [
11200            &[b"ZMPOP".as_slice(), b"1", b"z", b"MIN", b"COUNT", b"0"][..],
11201            &[b"ZMPOP", b"1", b"z", b"MIN", b"COUNT", b"-1"],
11202            &[b"ZMPOP", b"1", b"z", b"MIN", b"COUNT", b"x"],
11203        ] {
11204            assert_eq!(f.run(bad), count, "{:?}", bad[5]);
11205        }
11206        let syntax = "-ERR syntax error\r\n";
11207        for bad in [
11208            // Two keys named and one given, so the word that should have been
11209            // the direction is a key and there is no direction left.
11210            &[b"ZMPOP".as_slice(), b"2", b"z", b"MIN"][..],
11211            &[b"ZMPOP", b"1", b"z", b"SIDEWAYS"],
11212            &[b"ZMPOP", b"1", b"z", b"MIN", b"junk"],
11213            &[b"ZMPOP", b"1", b"z", b"MIN", b"COUNT", b"1", b"junk"],
11214        ] {
11215            assert_eq!(f.run(bad), syntax, "{bad:?}");
11216        }
11217    }
11218
11219    /// The three that wait, when there is something there and they do not have
11220    /// to. `BZPOPMIN` is the one reply in the group that is three flat elements.
11221    #[test]
11222    fn the_sorted_set_pops_that_wait_answer_like_the_ones_they_wrap() {
11223        let mut f = Fixture::new();
11224        f.run(&[b"ZADD", b"z", b"1", b"a", b"2", b"b", b"3", b"c"]);
11225        assert_eq!(
11226            f.flow(&[b"BZPOPMIN", b"nokey", b"z", b"0"]),
11227            (
11228                Flow::Continue,
11229                "*3\r\n$1\r\nz\r\n$1\r\na\r\n$1\r\n1\r\n".to_owned()
11230            )
11231        );
11232        assert_eq!(
11233            f.run(&[b"BZPOPMAX", b"z", b"0"]),
11234            "*3\r\n$1\r\nz\r\n$1\r\nc\r\n$1\r\n3\r\n"
11235        );
11236        f.run(&[b"ZADD", b"z", b"1", b"a", b"3", b"c"]);
11237        assert_eq!(
11238            f.run(&[
11239                b"BZMPOP", b"0", b"2", b"nokey", b"z", b"MIN", b"COUNT", b"2"
11240            ]),
11241            "*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"
11242        );
11243        f.out = Out::new(Proto::Resp3);
11244        assert_eq!(
11245            f.run(&[b"BZPOPMIN", b"z", b"0"]),
11246            "*3\r\n$1\r\nz\r\n$1\r\nc\r\n,3\r\n"
11247        );
11248        f.out = Out::new(Proto::Resp2);
11249        // Nothing to take, so the client is parked and nothing was written.
11250        assert_eq!(
11251            f.flow(&[b"BZPOPMIN", b"z", b"0"]),
11252            (Flow::Block, String::new())
11253        );
11254        assert_eq!(
11255            f.flow(&[b"BZMPOP", b"0", b"1", b"z", b"MIN"]),
11256            (Flow::Block, String::new())
11257        );
11258        // The timeout is read before the key count, so this complains about the
11259        // timeout and not about the count.
11260        assert_eq!(
11261            f.run(&[b"BZMPOP", b"abc", b"0", b"z", b"MIN"]),
11262            "-ERR timeout is not a float or out of range\r\n"
11263        );
11264        assert_eq!(
11265            f.run(&[b"BZMPOP", b"0", b"0", b"z", b"MIN"]),
11266            "-ERR numkeys should be greater than 0\r\n"
11267        );
11268        assert_eq!(
11269            f.run(&[b"BZPOPMIN", b"z", b"-1"]),
11270            "-ERR timeout is negative\r\n"
11271        );
11272    }
11273
11274    /// A parked sorted set client is served by whatever puts a member under one
11275    /// of its keys, and is not served by something of another type landing
11276    /// there.
11277    #[test]
11278    fn a_parked_sorted_set_client_waits_for_a_member_and_not_for_a_key() {
11279        let mut f = Fixture::new();
11280        assert_eq!(f.flow(&[b"BZPOPMIN", b"z", b"0"]).0, Flow::Block);
11281        assert_eq!(f.server.parked(), 1);
11282        // A string under the key is not what it asked for, so it stays parked
11283        // rather than being handed a WRONGTYPE on a command that was accepted.
11284        f.run(&[b"SET", b"z", b"v"]);
11285        let mut out = Out::new(Proto::Resp2);
11286        assert!(!f.server.serve_waiter(7, 0, &mut out));
11287        assert!(out.as_slice().is_empty());
11288        f.run(&[b"DEL", b"z"]);
11289        f.run(&[b"ZADD", b"z", b"5", b"m"]);
11290        assert!(f.server.serve_waiter(7, 0, &mut out));
11291        assert_eq!(
11292            core::str::from_utf8(out.as_slice()).expect("ascii"),
11293            "*3\r\n$1\r\nz\r\n$1\r\nm\r\n$1\r\n5\r\n"
11294        );
11295        // And the member is gone, which is what makes a queue of workers on a
11296        // sorted set work at all.
11297        assert_eq!(f.run(&[b"EXISTS", b"z"]), ":0\r\n");
11298    }
11299
11300    #[test]
11301    fn every_sorted_set_command_says_wrongtype_and_writes_nothing() {
11302        let mut f = Fixture::new();
11303        f.run(&[b"SET", b"s", b"v"]);
11304        let wrong = "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n";
11305        for cmd in [
11306            &[b"ZADD".as_slice(), b"s", b"1", b"a"][..],
11307            &[b"ZINCRBY", b"s", b"1", b"a"],
11308            &[b"ZCARD", b"s"],
11309            &[b"ZSCORE", b"s", b"a"],
11310            &[b"ZMSCORE", b"s", b"a"],
11311            &[b"ZREM", b"s", b"a"],
11312            &[b"ZRANK", b"s", b"a"],
11313            &[b"ZREVRANK", b"s", b"a"],
11314            &[b"ZCOUNT", b"s", b"1", b"2"],
11315            &[b"ZLEXCOUNT", b"s", b"-", b"+"],
11316            &[b"ZRANGE", b"s", b"0", b"-1"],
11317            &[b"ZREVRANGE", b"s", b"0", b"-1"],
11318            &[b"ZRANGEBYSCORE", b"s", b"1", b"2"],
11319            &[b"ZREVRANGEBYSCORE", b"s", b"2", b"1"],
11320            &[b"ZRANGEBYLEX", b"s", b"-", b"+"],
11321            &[b"ZREVRANGEBYLEX", b"s", b"+", b"-"],
11322            &[b"ZRANGESTORE", b"d", b"s", b"0", b"-1"],
11323            &[b"ZREMRANGEBYRANK", b"s", b"0", b"-1"],
11324            &[b"ZREMRANGEBYSCORE", b"s", b"1", b"2"],
11325            &[b"ZREMRANGEBYLEX", b"s", b"-", b"+"],
11326            &[b"ZUNION", b"1", b"s"],
11327            &[b"ZINTER", b"1", b"s"],
11328            &[b"ZDIFF", b"1", b"s"],
11329            &[b"ZUNIONSTORE", b"d", b"1", b"s"],
11330            &[b"ZINTERSTORE", b"d", b"1", b"s"],
11331            &[b"ZDIFFSTORE", b"d", b"1", b"s"],
11332            &[b"ZINTERCARD", b"1", b"s"],
11333            &[b"ZRANDMEMBER", b"s"],
11334            &[b"ZSCAN", b"s", b"0"],
11335            &[b"ZPOPMIN", b"s"],
11336            &[b"ZPOPMAX", b"s", b"2"],
11337            &[b"ZMPOP", b"1", b"s", b"MIN"],
11338            &[b"BZPOPMIN", b"s", b"0"],
11339            &[b"BZPOPMAX", b"s", b"0"],
11340            &[b"BZMPOP", b"0", b"1", b"s", b"MIN"],
11341        ] {
11342            assert_eq!(f.run(cmd), wrong, "{:?}", cmd[0]);
11343        }
11344        assert_eq!(f.run(&[b"GET", b"s"]), "$1\r\nv\r\n");
11345    }
11346
11347    /// The same churn the set, the string and the list get, because a sorted
11348    /// set that leaks a tree node per add looks exactly like one that does not
11349    /// until it has run for an afternoon.
11350    /// Not under Miri, for the reason on `churning_sets_does_not_grow_the_server`.
11351    #[cfg_attr(miri, ignore = "the volume is the claim")]
11352    #[test]
11353    fn churning_sorted_sets_does_not_grow_the_server() {
11354        let mut f = Fixture::new();
11355        let members: Vec<Vec<u8>> = (0..200).map(|i| format!("m{i}").into_bytes()).collect();
11356        let scores: Vec<Vec<u8>> = (0..200).map(|i| format!("{i}").into_bytes()).collect();
11357        let mut args: Vec<&[u8]> = vec![b"ZADD", b"z"];
11358        for i in 0..200 {
11359            args.push(&scores[i]);
11360            args.push(&members[i]);
11361        }
11362
11363        f.run(&args);
11364        f.run(&[b"DEL", b"z"]);
11365        f.server.compact_step();
11366        let after_first = f.server.memory_bytes();
11367
11368        for _ in 0..200 {
11369            f.run(&args);
11370            f.run(&[b"DEL", b"z"]);
11371            f.server.compact_step();
11372        }
11373        assert_eq!(f.run(&[b"DBSIZE"]), ":0\r\n");
11374        assert!(
11375            f.server.memory_bytes() <= after_first * 2,
11376            "held {} after two hundred passes against {after_first} after one",
11377            f.server.memory_bytes()
11378        );
11379    }
11380
11381    // ------------------------------------------------------------------- geo
11382
11383    /// The three places every Redis geo example uses, and one more.
11384    ///
11385    /// Every reply this section asserts on came off a running 8.10.1 with these
11386    /// three loaded, byte for byte, including the number of digits in a
11387    /// coordinate and the four places on a distance.
11388    fn sicily(f: &mut Fixture) {
11389        f.run(&[
11390            b"GEOADD",
11391            b"Sicily",
11392            b"13.361389",
11393            b"38.115556",
11394            b"Palermo",
11395            b"15.087269",
11396            b"37.502669",
11397            b"Catania",
11398        ]);
11399        f.run(&[
11400            b"GEOADD",
11401            b"Sicily",
11402            b"13.583333",
11403            b"37.316667",
11404            b"Agrigento",
11405        ]);
11406    }
11407
11408    #[test]
11409    fn places_go_in_as_scores_and_come_back_as_positions() {
11410        let mut f = Fixture::new();
11411        assert_eq!(
11412            f.run(&[
11413                b"GEOADD",
11414                b"Sicily",
11415                b"13.361389",
11416                b"38.115556",
11417                b"Palermo",
11418                b"15.087269",
11419                b"37.502669",
11420                b"Catania"
11421            ]),
11422            ":2\r\n"
11423        );
11424        // A geo key is a sorted set and says so, which is not an implementation
11425        // detail either: a client removes a place with ZREM and counts them
11426        // with ZCARD, and the score is the number a real server stores.
11427        assert_eq!(f.run(&[b"TYPE", b"Sicily"]), "+zset\r\n");
11428        assert_eq!(
11429            f.run(&[b"ZSCORE", b"Sicily", b"Palermo"]),
11430            "$16\r\n3479099956230698\r\n"
11431        );
11432        assert_eq!(
11433            f.run(&[b"GEOPOS", b"Sicily", b"Palermo", b"NonExisting"]),
11434            "*2\r\n*2\r\n$18\r\n13.361389338970184\r\n$16\r\n38.1155563954963\r\n*-1\r\n"
11435        );
11436        assert_eq!(
11437            f.run(&[
11438                b"GEOHASH",
11439                b"Sicily",
11440                b"Palermo",
11441                b"Catania",
11442                b"NonExisting"
11443            ]),
11444            "*3\r\n$11\r\nsqc8b49rny0\r\n$11\r\nsqdtr74hyu0\r\n$-1\r\n"
11445        );
11446        // A key that is not there is an empty one, and the two nulls are not
11447        // the same null: GEOPOS answers the array one and GEOHASH the string
11448        // one, which a RESP2 client can tell apart.
11449        assert_eq!(f.run(&[b"GEOPOS", b"nokey", b"a"]), "*1\r\n*-1\r\n");
11450        assert_eq!(f.run(&[b"GEOHASH", b"nokey", b"a"]), "*1\r\n$-1\r\n");
11451    }
11452
11453    #[test]
11454    fn a_distance_comes_back_with_four_places_in_whatever_unit_was_asked_for() {
11455        let mut f = Fixture::new();
11456        sicily(&mut f);
11457        assert_eq!(
11458            f.run(&[b"GEODIST", b"Sicily", b"Palermo", b"Catania"]),
11459            "$11\r\n166274.1516\r\n"
11460        );
11461        assert_eq!(
11462            f.run(&[b"GEODIST", b"Sicily", b"Palermo", b"Catania", b"km"]),
11463            "$8\r\n166.2742\r\n"
11464        );
11465        assert_eq!(
11466            f.run(&[b"GEODIST", b"Sicily", b"Palermo", b"Catania", b"mi"]),
11467            "$8\r\n103.3182\r\n"
11468        );
11469        // A member that is not there and a key that is not there are the same
11470        // nil, and the unit is read before the key is looked up, so a bad unit
11471        // on a missing key is still an error.
11472        assert_eq!(
11473            f.run(&[b"GEODIST", b"Sicily", b"Palermo", b"Foo"]),
11474            "$-1\r\n"
11475        );
11476        assert_eq!(f.run(&[b"GEODIST", b"nokey", b"a", b"b"]), "$-1\r\n");
11477        assert_eq!(
11478            f.run(&[b"GEODIST", b"nokey", b"a", b"b", b"parsecs"]),
11479            "-ERR unsupported unit provided. please use M, KM, FT, MI\r\n"
11480        );
11481        assert_eq!(
11482            f.run(&[b"GEODIST", b"Sicily", b"a", b"b", b"km", b"extra"]),
11483            "-ERR syntax error\r\n"
11484        );
11485    }
11486
11487    #[test]
11488    fn a_search_finds_what_is_inside_it_nearest_first() {
11489        let mut f = Fixture::new();
11490        sicily(&mut f);
11491        let all = "*3\r\n$7\r\nCatania\r\n$9\r\nAgrigento\r\n$7\r\nPalermo\r\n";
11492        assert_eq!(
11493            f.run(&[
11494                b"GEOSEARCH",
11495                b"Sicily",
11496                b"FROMLONLAT",
11497                b"15",
11498                b"37",
11499                b"BYRADIUS",
11500                b"200",
11501                b"km",
11502                b"ASC"
11503            ]),
11504            all
11505        );
11506        // The older spelling of the same search, which is the same nine boxes
11507        // and the same order.
11508        assert_eq!(
11509            f.run(&[b"GEORADIUS", b"Sicily", b"15", b"37", b"200", b"km", b"ASC"]),
11510            all
11511        );
11512        assert_eq!(
11513            f.run(&[
11514                b"GEORADIUS_RO",
11515                b"Sicily",
11516                b"15",
11517                b"37",
11518                b"200",
11519                b"km",
11520                b"ASC"
11521            ]),
11522            all
11523        );
11524        // A count with no ordering means the nearest ones, so DESC has to be
11525        // asked for to get the far end.
11526        assert_eq!(
11527            f.run(&[
11528                b"GEORADIUS",
11529                b"Sicily",
11530                b"15",
11531                b"37",
11532                b"200",
11533                b"km",
11534                b"DESC",
11535                b"COUNT",
11536                b"1"
11537            ]),
11538            "*1\r\n$7\r\nPalermo\r\n"
11539        );
11540        assert_eq!(
11541            f.run(&[
11542                b"GEORADIUS",
11543                b"Sicily",
11544                b"15",
11545                b"37",
11546                b"200",
11547                b"km",
11548                b"COUNT",
11549                b"1"
11550            ]),
11551            "*1\r\n$7\r\nCatania\r\n"
11552        );
11553        // Nothing inside a kilometre of that point, and nothing in a key that
11554        // is not there, and both are the empty array rather than an error.
11555        let empty = "*0\r\n";
11556        assert_eq!(
11557            f.run(&[
11558                b"GEOSEARCH",
11559                b"Sicily",
11560                b"FROMLONLAT",
11561                b"15",
11562                b"37",
11563                b"BYRADIUS",
11564                b"1",
11565                b"km"
11566            ]),
11567            empty
11568        );
11569        assert_eq!(
11570            f.run(&[
11571                b"GEOSEARCH",
11572                b"nokey",
11573                b"FROMLONLAT",
11574                b"15",
11575                b"37",
11576                b"BYRADIUS",
11577                b"1",
11578                b"km"
11579            ]),
11580            empty
11581        );
11582        assert_eq!(
11583            f.run(&[b"GEORADIUSBYMEMBER", b"nokey", b"m", b"1", b"km"]),
11584            empty
11585        );
11586    }
11587
11588    #[test]
11589    fn a_search_centred_on_a_member_starts_from_where_that_member_is() {
11590        let mut f = Fixture::new();
11591        sicily(&mut f);
11592        assert_eq!(
11593            f.run(&[b"GEORADIUSBYMEMBER", b"Sicily", b"Agrigento", b"100", b"km"]),
11594            "*2\r\n$9\r\nAgrigento\r\n$7\r\nPalermo\r\n"
11595        );
11596        // The member itself is nothing away from itself, which is where the
11597        // fixed point writer's zero shows up on the wire.
11598        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";
11599        assert_eq!(
11600            f.run(&[
11601                b"GEORADIUSBYMEMBER_RO",
11602                b"Sicily",
11603                b"Agrigento",
11604                b"100",
11605                b"km",
11606                b"WITHDIST"
11607            ]),
11608            with_dist
11609        );
11610        assert_eq!(
11611            f.run(&[
11612                b"GEOSEARCH",
11613                b"Sicily",
11614                b"FROMMEMBER",
11615                b"Agrigento",
11616                b"BYRADIUS",
11617                b"100",
11618                b"km",
11619                b"ASC",
11620                b"WITHDIST"
11621            ]),
11622            with_dist
11623        );
11624        assert_eq!(
11625            f.run(&[b"GEORADIUSBYMEMBER", b"Sicily", b"Nowhere", b"100", b"km"]),
11626            "-ERR could not decode requested zset member\r\n"
11627        );
11628    }
11629
11630    #[test]
11631    fn a_box_search_reports_the_distance_the_hash_and_the_coordinates() {
11632        let mut f = Fixture::new();
11633        sicily(&mut f);
11634        // Three options asked for, so each result is a four element array of
11635        // the member, the distance, the hash and a pair. The order of the three
11636        // is Redis's and not the order they were written in the command.
11637        assert_eq!(
11638            f.run(&[
11639                b"GEOSEARCH",
11640                b"Sicily",
11641                b"FROMLONLAT",
11642                b"15",
11643                b"37",
11644                b"BYBOX",
11645                b"400",
11646                b"400",
11647                b"km",
11648                b"ASC",
11649                b"WITHCOORD",
11650                b"WITHDIST",
11651                b"WITHHASH"
11652            ]),
11653            "*3\r\n*4\r\n$7\r\nCatania\r\n$7\r\n56.4413\r\n:3479447370796909\r\n*2\r\n\
11654             $18\r\n15.087267458438873\r\n$17\r\n37.50266842333162\r\n\
11655             *4\r\n$9\r\nAgrigento\r\n$8\r\n130.4235\r\n:3479030013248308\r\n*2\r\n\
11656             $18\r\n13.583331406116486\r\n$18\r\n37.316668049938166\r\n\
11657             *4\r\n$7\r\nPalermo\r\n$8\r\n190.4424\r\n:3479099956230698\r\n*2\r\n\
11658             $18\r\n13.361389338970184\r\n$16\r\n38.1155563954963\r\n"
11659        );
11660    }
11661
11662    #[test]
11663    fn a_store_writes_the_hashes_and_a_storedist_writes_the_distances() {
11664        let mut f = Fixture::new();
11665        sicily(&mut f);
11666        let hashes = "*6\r\n$9\r\nAgrigento\r\n$16\r\n3479030013248308\r\n\
11667                      $7\r\nPalermo\r\n$16\r\n3479099956230698\r\n\
11668                      $7\r\nCatania\r\n$16\r\n3479447370796909\r\n";
11669        assert_eq!(
11670            f.run(&[
11671                b"GEOSEARCHSTORE",
11672                b"dst",
11673                b"Sicily",
11674                b"FROMLONLAT",
11675                b"15",
11676                b"37",
11677                b"BYRADIUS",
11678                b"200",
11679                b"km",
11680                b"ASC"
11681            ]),
11682            ":3\r\n"
11683        );
11684        assert_eq!(
11685            f.run(&[b"ZRANGE", b"dst", b"0", b"-1", b"WITHSCORES"]),
11686            hashes
11687        );
11688        // The same again through the older spelling, which stores the same
11689        // scores, so a key written by either is a geo key.
11690        assert_eq!(
11691            f.run(&[
11692                b"GEORADIUS",
11693                b"Sicily",
11694                b"15",
11695                b"37",
11696                b"200",
11697                b"km",
11698                b"STORE",
11699                b"dst3"
11700            ]),
11701            ":3\r\n"
11702        );
11703        assert_eq!(
11704            f.run(&[b"ZRANGE", b"dst3", b"0", b"-1", b"WITHSCORES"]),
11705            hashes
11706        );
11707        // STOREDIST stores the distance in the search unit instead, and those
11708        // are full doubles rather than the four places WITHDIST writes. The
11709        // numbers on the right are what 8.10.1 stored for this search, and they
11710        // are compared with a tolerance rather than byte for byte because the
11711        // last bit of a haversine is the platform's sin, cos and asin: this
11712        // machine and that one disagree in the sixteenth digit, and so do two
11713        // Redis builds. Everything a client actually reads back is four places
11714        // and is asserted exactly above.
11715        assert_eq!(
11716            f.run(&[
11717                b"GEOSEARCHSTORE",
11718                b"dst2",
11719                b"Sicily",
11720                b"FROMLONLAT",
11721                b"15",
11722                b"37",
11723                b"BYRADIUS",
11724                b"200",
11725                b"km",
11726                b"ASC",
11727                b"STOREDIST"
11728            ]),
11729            ":3\r\n"
11730        );
11731        for (member, want) in [
11732            ("Catania", 56.441_257_870_158_19),
11733            ("Agrigento", 130.423_487_067_147_14),
11734            ("Palermo", 190.442_429_847_757_92),
11735        ] {
11736            let reply = f.run(&[b"ZSCORE", b"dst2", member.as_bytes()]);
11737            let got: f64 = reply
11738                .trim_start_matches(|c: char| c != '\n')
11739                .trim()
11740                .parse()
11741                .unwrap_or_else(|_| panic!("{member} scored {reply:?}"));
11742            assert!(
11743                (got - want).abs() < 1e-9,
11744                "{member} scored {got} not {want}"
11745            );
11746        }
11747        // The order they went in is the order the scores put them in, which is
11748        // the point of storing the distance rather than the hash.
11749        assert_eq!(
11750            f.run(&[b"ZRANGE", b"dst2", b"0", b"-1"]),
11751            "*3\r\n$7\r\nCatania\r\n$9\r\nAgrigento\r\n$7\r\nPalermo\r\n"
11752        );
11753        // A search that finds nothing takes the destination with it rather than
11754        // leaving what was there, and a source key that is not there is a
11755        // search that finds nothing.
11756        assert_eq!(
11757            f.run(&[
11758                b"GEOSEARCHSTORE",
11759                b"dst",
11760                b"nokey",
11761                b"FROMLONLAT",
11762                b"15",
11763                b"37",
11764                b"BYRADIUS",
11765                b"200",
11766                b"km"
11767            ]),
11768            ":0\r\n"
11769        );
11770        assert_eq!(f.run(&[b"EXISTS", b"dst"]), ":0\r\n");
11771    }
11772
11773    #[test]
11774    fn the_gates_on_geoadd_are_the_ones_zadd_has() {
11775        let mut f = Fixture::new();
11776        sicily(&mut f);
11777        // XX on a member that is already where it is changes nothing, and NX on
11778        // one that is there refuses to move it.
11779        assert_eq!(
11780            f.run(&[
11781                b"GEOADD",
11782                b"Sicily",
11783                b"XX",
11784                b"CH",
11785                b"13.361389",
11786                b"38.115556",
11787                b"Palermo"
11788            ]),
11789            ":0\r\n"
11790        );
11791        assert_eq!(
11792            f.run(&[
11793                b"GEOADD",
11794                b"Sicily",
11795                b"NX",
11796                b"13.361389",
11797                b"38.9",
11798                b"Palermo"
11799            ]),
11800            ":0\r\n"
11801        );
11802        assert_eq!(
11803            f.run(&[
11804                b"GEOADD",
11805                b"Sicily",
11806                b"CH",
11807                b"13.361389",
11808                b"38.9",
11809                b"Palermo"
11810            ]),
11811            ":1\r\n"
11812        );
11813        // Out of range, and nothing is stored: the whole call is refused rather
11814        // than the good pairs going in and the bad one stopping it.
11815        assert_eq!(
11816            f.run(&[
11817                b"GEOADD",
11818                b"new",
11819                b"13.361389",
11820                b"38.115556",
11821                b"here",
11822                b"181",
11823                b"38",
11824                b"there"
11825            ]),
11826            "-ERR invalid longitude,latitude pair 181.000000,38.000000\r\n"
11827        );
11828        assert_eq!(f.run(&[b"EXISTS", b"new"]), ":0\r\n");
11829        assert_eq!(
11830            f.run(&[b"GEOADD", b"new", b"x", b"38", b"here"]),
11831            "-ERR value is not a valid float\r\n"
11832        );
11833        // The count of triples is checked before the two gates are, and a call
11834        // with no triples at all reaches the same sentence.
11835        assert_eq!(
11836            f.run(&[b"GEOADD", b"new", b"13", b"38", b"here", b"and"]),
11837            "-ERR syntax error\r\n"
11838        );
11839        assert_eq!(
11840            f.run(&[b"GEOADD", b"new", b"NX", b"XX", b"CH"]),
11841            "-ERR syntax error\r\n"
11842        );
11843        assert_eq!(
11844            f.run(&[b"GEOADD", b"new", b"CH", b"CH", b"CH", b"CH"]),
11845            "-ERR syntax error\r\n"
11846        );
11847        assert_eq!(
11848            f.run(&[b"GEOADD", b"new", b"NX", b"CH"]),
11849            "-ERR wrong number of arguments for 'geoadd' command\r\n"
11850        );
11851    }
11852
11853    /// The sentences a search answers, which are its contract as much as the
11854    /// results are.
11855    #[test]
11856    fn every_way_a_search_can_be_written_wrong_has_its_own_sentence() {
11857        let mut f = Fixture::new();
11858        sicily(&mut f);
11859        let cases: &[(&[&[u8]], &str)] = &[
11860            (
11861                &[b"GEORADIUS", b"Sicily", b"15", b"37", b"x", b"km"],
11862                "-ERR need numeric radius\r\n",
11863            ),
11864            (
11865                &[b"GEORADIUS", b"Sicily", b"15", b"37", b"-1", b"km"],
11866                "-ERR radius cannot be negative\r\n",
11867            ),
11868            (
11869                &[b"GEORADIUS", b"Sicily", b"15", b"37", b"1", b"parsecs"],
11870                "-ERR unsupported unit provided. please use M, KM, FT, MI\r\n",
11871            ),
11872            (
11873                &[b"GEORADIUS", b"Sicily", b"181", b"37", b"1", b"km"],
11874                "-ERR invalid longitude,latitude pair 181.000000,37.000000\r\n",
11875            ),
11876            (
11877                &[
11878                    b"GEOSEARCH",
11879                    b"Sicily",
11880                    b"FROMLONLAT",
11881                    b"15",
11882                    b"37",
11883                    b"BYBOX",
11884                    b"x",
11885                    b"1",
11886                    b"km",
11887                ],
11888                "-ERR need numeric width\r\n",
11889            ),
11890            (
11891                &[
11892                    b"GEOSEARCH",
11893                    b"Sicily",
11894                    b"FROMLONLAT",
11895                    b"15",
11896                    b"37",
11897                    b"BYBOX",
11898                    b"1",
11899                    b"y",
11900                    b"km",
11901                ],
11902                "-ERR need numeric height\r\n",
11903            ),
11904            (
11905                &[
11906                    b"GEOSEARCH",
11907                    b"Sicily",
11908                    b"FROMLONLAT",
11909                    b"15",
11910                    b"37",
11911                    b"BYBOX",
11912                    b"-1",
11913                    b"1",
11914                    b"km",
11915                ],
11916                "-ERR height or width cannot be negative\r\n",
11917            ),
11918            (
11919                &[
11920                    b"GEOSEARCH",
11921                    b"Sicily",
11922                    b"FROMLONLAT",
11923                    b"15",
11924                    b"37",
11925                    b"BYRADIUS",
11926                    b"1",
11927                    b"km",
11928                    b"ANY",
11929                ],
11930                "-ERR the ANY argument requires COUNT argument\r\n",
11931            ),
11932            (
11933                &[
11934                    b"GEOSEARCH",
11935                    b"Sicily",
11936                    b"FROMLONLAT",
11937                    b"15",
11938                    b"37",
11939                    b"BYRADIUS",
11940                    b"1",
11941                    b"km",
11942                    b"COUNT",
11943                    b"0",
11944                ],
11945                "-ERR COUNT must be > 0\r\n",
11946            ),
11947            (
11948                &[
11949                    b"GEOSEARCH",
11950                    b"Sicily",
11951                    b"BYRADIUS",
11952                    b"1",
11953                    b"km",
11954                    b"BYBOX",
11955                    b"1",
11956                    b"1",
11957                    b"km",
11958                ],
11959                "-ERR syntax error\r\n",
11960            ),
11961            (
11962                &[
11963                    b"GEOSEARCH",
11964                    b"Sicily",
11965                    b"FROMMEMBER",
11966                    b"Palermo",
11967                    b"FROMLONLAT",
11968                    b"1",
11969                    b"2",
11970                    b"BYRADIUS",
11971                    b"1",
11972                    b"km",
11973                ],
11974                "-ERR syntax error\r\n",
11975            ),
11976            // The two options a GEOSEARCH cannot leave out, each with its own
11977            // sentence, and the command quoted the way the client spelled it.
11978            (
11979                &[
11980                    b"geosearch",
11981                    b"Sicily",
11982                    b"BYRADIUS",
11983                    b"1",
11984                    b"km",
11985                    b"ASC",
11986                    b"WITHDIST",
11987                ],
11988                "-ERR exactly one of FROMMEMBER or FROMLONLAT can be specified for geosearch\r\n",
11989            ),
11990            (
11991                &[
11992                    b"GEOSEARCH",
11993                    b"Sicily",
11994                    b"FROMLONLAT",
11995                    b"15",
11996                    b"37",
11997                    b"ASC",
11998                    b"WITHDIST",
11999                ],
12000                "-ERR exactly one of BYRADIUS and BYBOX can be specified for GEOSEARCH\r\n",
12001            ),
12002            // A store cannot also be asked for the distance, and the two
12003            // families name themselves differently in the same sentence.
12004            (
12005                &[
12006                    b"GEOSEARCHSTORE",
12007                    b"d",
12008                    b"Sicily",
12009                    b"FROMLONLAT",
12010                    b"15",
12011                    b"37",
12012                    b"BYRADIUS",
12013                    b"1",
12014                    b"km",
12015                    b"WITHCOORD",
12016                ],
12017                "-ERR GEOSEARCHSTORE is not compatible with WITHDIST, WITHHASH and WITHCOORD options\r\n",
12018            ),
12019            (
12020                &[
12021                    b"GEORADIUS",
12022                    b"Sicily",
12023                    b"15",
12024                    b"37",
12025                    b"1",
12026                    b"km",
12027                    b"WITHDIST",
12028                    b"STORE",
12029                    b"d",
12030                ],
12031                "-ERR STORE option in GEORADIUS is not compatible with WITHDIST, WITHHASH and WITHCOORD options\r\n",
12032            ),
12033            // The read only forms have no store at all, so the word is a stray
12034            // one, and GEOSEARCH's STOREDIST is only a GEOSEARCHSTORE option.
12035            (
12036                &[
12037                    b"GEORADIUS_RO",
12038                    b"Sicily",
12039                    b"15",
12040                    b"37",
12041                    b"1",
12042                    b"km",
12043                    b"STORE",
12044                    b"d",
12045                ],
12046                "-ERR syntax error\r\n",
12047            ),
12048            (
12049                &[
12050                    b"GEOSEARCH",
12051                    b"Sicily",
12052                    b"FROMLONLAT",
12053                    b"15",
12054                    b"37",
12055                    b"BYRADIUS",
12056                    b"1",
12057                    b"km",
12058                    b"STOREDIST",
12059                ],
12060                "-ERR syntax error\r\n",
12061            ),
12062        ];
12063        for (parts, want) in cases {
12064            assert_eq!(&f.run(parts), want, "{:?}", parts[0]);
12065        }
12066    }
12067
12068    /// A wrong type wins over a bad argument, because the key is looked up
12069    /// first, and every one of the ten says the same thing about it.
12070    #[test]
12071    fn every_geo_command_says_wrongtype() {
12072        let mut f = Fixture::new();
12073        f.run(&[b"SET", b"s", b"v"]);
12074        let wrong = "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n";
12075        let cases: &[&[&[u8]]] = &[
12076            &[b"GEOADD", b"s", b"13", b"38", b"m"],
12077            &[b"GEOPOS", b"s", b"m"],
12078            &[b"GEOHASH", b"s", b"m"],
12079            &[b"GEODIST", b"s", b"a", b"b"],
12080            &[
12081                b"GEOSEARCH",
12082                b"s",
12083                b"FROMLONLAT",
12084                b"15",
12085                b"37",
12086                b"BYRADIUS",
12087                b"1",
12088                b"km",
12089            ],
12090            &[
12091                b"GEOSEARCHSTORE",
12092                b"d",
12093                b"s",
12094                b"FROMLONLAT",
12095                b"15",
12096                b"37",
12097                b"BYRADIUS",
12098                b"1",
12099                b"km",
12100            ],
12101            &[b"GEORADIUS", b"s", b"15", b"37", b"1", b"km"],
12102            &[b"GEORADIUS_RO", b"s", b"15", b"37", b"1", b"km"],
12103            &[b"GEORADIUSBYMEMBER", b"s", b"m", b"1", b"km"],
12104            &[b"GEORADIUSBYMEMBER_RO", b"s", b"m", b"1", b"km"],
12105        ];
12106        for case in cases {
12107            assert_eq!(f.run(case), wrong, "{:?}", case[0]);
12108        }
12109        // And it wins over an argument that will not parse, which is the whole
12110        // reason the lookup comes first.
12111        assert_eq!(
12112            f.run(&[b"GEORADIUS", b"s", b"15", b"37", b"x", b"km"]),
12113            wrong
12114        );
12115    }
12116
12117    // ----------------------------------------------------------------- array
12118
12119    #[test]
12120    fn an_array_writes_at_any_index_and_reads_back_what_it_sent() {
12121        let mut f = Fixture::new();
12122        // Three consecutive positions from a high index, and the reply is how
12123        // many of them were empty before rather than how many were written.
12124        assert_eq!(
12125            f.run(&[b"ARSET", b"a", b"1000", b"x", b"y", b"z"]),
12126            ":3\r\n"
12127        );
12128        assert_eq!(f.run(&[b"ARSET", b"a", b"1000", b"X", b"Y"]), ":0\r\n");
12129        assert_eq!(f.run(&[b"ARGET", b"a", b"1000"]), "$1\r\nX\r\n");
12130        assert_eq!(f.run(&[b"ARGET", b"a", b"1002"]), "$1\r\nz\r\n");
12131        // A hole and a key that is not there are the same answer.
12132        assert_eq!(f.run(&[b"ARGET", b"a", b"999"]), "$-1\r\n");
12133        assert_eq!(f.run(&[b"ARGET", b"nope", b"0"]), "$-1\r\n");
12134        assert_eq!(
12135            f.run(&[b"ARMGET", b"a", b"1002", b"999", b"1000"]),
12136            "*3\r\n$1\r\nz\r\n$-1\r\n$1\r\nX\r\n"
12137        );
12138        // Scattered pairs in one command, last write wins within it.
12139        assert_eq!(f.run(&[b"ARMSET", b"a", b"5", b"p", b"5", b"q"]), ":1\r\n");
12140        assert_eq!(f.run(&[b"ARGET", b"a", b"5"]), "$1\r\nq\r\n");
12141    }
12142
12143    /// The two numbers an array reports are not the same number, and one of
12144    /// them does not fit a signed integer.
12145    #[test]
12146    fn the_length_is_the_high_water_mark_and_the_count_is_the_population() {
12147        let mut f = Fixture::new();
12148        assert_eq!(f.run(&[b"ARLEN", b"nope"]), ":0\r\n");
12149        assert_eq!(f.run(&[b"ARCOUNT", b"nope"]), ":0\r\n");
12150        f.run(&[b"ARMSET", b"a", b"0", b"x", b"9", b"y"]);
12151        assert_eq!(f.run(&[b"ARLEN", b"a"]), ":10\r\n");
12152        assert_eq!(f.run(&[b"ARCOUNT", b"a"]), ":2\r\n");
12153        // Deleting in the middle leaves the high water mark where it was.
12154        assert_eq!(f.run(&[b"ARDEL", b"a", b"0"]), ":1\r\n");
12155        assert_eq!(f.run(&[b"ARLEN", b"a"]), ":10\r\n");
12156        assert_eq!(f.run(&[b"ARCOUNT", b"a"]), ":1\r\n");
12157
12158        // The top of the space is addressable, and its length is a number with
12159        // bit sixty three set, so the reply has to be unsigned or it comes back
12160        // negative.
12161        f.run(&[b"ARSET", b"top", b"18446744073709551614", b"z"]);
12162        assert_eq!(f.run(&[b"ARLEN", b"top"]), ":18446744073709551615\r\n");
12163        assert_eq!(f.run(&[b"ARCOUNT", b"top"]), ":1\r\n");
12164        // And one past it does not exist, so a write that would reach it fails
12165        // before any of it lands.
12166        assert_eq!(
12167            f.run(&[b"ARSET", b"over", b"18446744073709551614", b"a", b"b"]),
12168            "-ERR array index overflow\r\n"
12169        );
12170        assert_eq!(f.run(&[b"EXISTS", b"over"]), ":0\r\n");
12171    }
12172
12173    /// One reply per position and not one per element, which is the whole
12174    /// reason the range is capped.
12175    #[test]
12176    fn a_range_read_answers_for_the_holes_too_and_is_capped_at_a_million() {
12177        let mut f = Fixture::new();
12178        f.run(&[b"ARSET", b"a", b"1", b"x"]);
12179        assert_eq!(
12180            f.run(&[b"ARGETRANGE", b"a", b"0", b"3"]),
12181            "*4\r\n$-1\r\n$1\r\nx\r\n$-1\r\n$-1\r\n"
12182        );
12183        // The two ends may come in either order, and the answer is reversed
12184        // rather than empty.
12185        assert_eq!(
12186            f.run(&[b"ARGETRANGE", b"a", b"3", b"0"]),
12187            "*4\r\n$-1\r\n$-1\r\n$1\r\nx\r\n$-1\r\n"
12188        );
12189        // A key that is not there reads like an array of nothing but holes.
12190        assert_eq!(
12191            f.run(&[b"ARGETRANGE", b"nope", b"0", b"1"]),
12192            "*2\r\n$-1\r\n$-1\r\n"
12193        );
12194        // A range wider than a million positions is refused and not trimmed,
12195        // because against a missing key it is a request for as many nulls as
12196        // the range is wide.
12197        assert_eq!(
12198            f.run(&[b"ARGETRANGE", b"nope", b"0", b"18446744073709551614"]),
12199            "-ERR range exceeds maximum of 1000000 items\r\n"
12200        );
12201    }
12202
12203    /// Every index in the argument list is read before the key is touched, so
12204    /// a bad one at the end leaves nothing half written.
12205    #[test]
12206    fn a_bad_index_late_in_the_line_writes_none_of_the_earlier_ones() {
12207        let mut f = Fixture::new();
12208        assert_eq!(
12209            f.run(&[b"ARMSET", b"a", b"0", b"x", b"-1", b"y"]),
12210            "-ERR invalid array index\r\n"
12211        );
12212        assert_eq!(f.run(&[b"EXISTS", b"a"]), ":0\r\n");
12213        f.run(&[b"ARSET", b"a", b"0", b"x", b"y", b"z"]);
12214        assert_eq!(
12215            f.run(&[b"ARDEL", b"a", b"0", b"01"]),
12216            "-ERR invalid array index\r\n"
12217        );
12218        assert_eq!(f.run(&[b"ARCOUNT", b"a"]), ":3\r\n");
12219        // An index is unsigned here, so the numbers a list would take are not
12220        // the last element, they are errors.
12221        assert_eq!(
12222            f.run(&[b"ARGET", b"a", b"-1"]),
12223            "-ERR invalid array index\r\n"
12224        );
12225        // And a pair list with an odd tail is an arity error rather than a
12226        // syntax one.
12227        assert_eq!(
12228            f.run(&[b"ARMSET", b"a", b"0", b"x", b"1"]),
12229            "-ERR wrong number of arguments for 'armset' command\r\n"
12230        );
12231        assert_eq!(
12232            f.run(&[b"ARDELRANGE", b"a", b"0", b"1", b"2"]),
12233            "-ERR wrong number of arguments for 'ardelrange' command\r\n"
12234        );
12235    }
12236
12237    #[test]
12238    fn a_range_delete_costs_the_elements_and_takes_the_key_when_it_empties() {
12239        let mut f = Fixture::new();
12240        f.run(&[b"ARSET", b"a", b"0", b"0", b"1", b"2", b"3", b"4"]);
12241        assert_eq!(f.run(&[b"ARDELRANGE", b"a", b"3", b"1"]), ":3\r\n");
12242        assert_eq!(f.run(&[b"ARCOUNT", b"a"]), ":2\r\n");
12243        // Two ranges in one command, and the second one covers the whole space
12244        // without walking it.
12245        assert_eq!(
12246            f.run(&[
12247                b"ARDELRANGE",
12248                b"a",
12249                b"100",
12250                b"200",
12251                b"0",
12252                b"18446744073709551614"
12253            ]),
12254            ":2\r\n"
12255        );
12256        assert_eq!(f.run(&[b"EXISTS", b"a"]), ":0\r\n");
12257        assert_eq!(f.run(&[b"ARDELRANGE", b"nope", b"0", b"1"]), ":0\r\n");
12258        assert_eq!(f.run(&[b"ARDEL", b"nope", b"0"]), ":0\r\n");
12259    }
12260
12261    /// A value goes out as the bytes it came in as, whichever of the three ways
12262    /// the array found to store it.
12263    #[test]
12264    fn a_value_comes_back_byte_for_byte_however_it_was_packed() {
12265        let mut f = Fixture::new();
12266        let long = vec![b'v'; 200];
12267        f.run(&[
12268            b"ARMSET", b"a", b"0", b"42", b"1", b"007", b"2", b"3.5", b"3", b"3.14", b"4",
12269            b"short", b"5", &long, b"6", b"-0",
12270        ]);
12271        // 42 is an integer, 007 is not one because it does not print back the
12272        // same, 3.5 survives a double and 3.14 does not, and the last two are a
12273        // word packed string and a blob.
12274        assert_eq!(
12275            f.run(&[b"ARGETRANGE", b"a", b"0", b"6"]),
12276            format!(
12277                "*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",
12278                String::from_utf8_lossy(&long)
12279            )
12280        );
12281    }
12282
12283    #[test]
12284    fn an_array_is_a_type_and_an_encoding_a_client_can_see() {
12285        let mut f = Fixture::new();
12286        f.run(&[b"ARSET", b"a", b"0", b"x"]);
12287        assert_eq!(f.run(&[b"TYPE", b"a"]), "+array\r\n");
12288        assert_eq!(
12289            f.run(&[b"OBJECT", b"ENCODING", b"a"]),
12290            "$12\r\nsliced-array\r\n"
12291        );
12292        // And it is a body like any other, so the key commands work on it.
12293        assert_eq!(f.run(&[b"EXPIRE", b"a", b"100"]), ":1\r\n");
12294        assert_eq!(f.run(&[b"PERSIST", b"a"]), ":1\r\n");
12295        assert_eq!(f.run(&[b"COPY", b"a", b"b"]), ":1\r\n");
12296        assert_eq!(f.run(&[b"ARGET", b"b", b"0"]), "$1\r\nx\r\n");
12297        assert_eq!(f.run(&[b"RENAME", b"a", b"c"]), "+OK\r\n");
12298        assert_eq!(f.run(&[b"ARCOUNT", b"c"]), ":1\r\n");
12299    }
12300
12301    #[test]
12302    fn every_array_command_refuses_a_key_holding_something_else() {
12303        let mut f = Fixture::new();
12304        f.run(&[b"SET", b"s", b"v"]);
12305        let wrong = "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n";
12306        for cmd in [
12307            &[b"ARSET".as_ref(), b"s", b"0", b"x"][..],
12308            &[b"ARMSET".as_ref(), b"s", b"0", b"x"][..],
12309            &[b"ARGET".as_ref(), b"s", b"0"][..],
12310            &[b"ARMGET".as_ref(), b"s", b"0"][..],
12311            &[b"ARGETRANGE".as_ref(), b"s", b"0", b"1"][..],
12312            &[b"ARLEN".as_ref(), b"s"][..],
12313            &[b"ARCOUNT".as_ref(), b"s"][..],
12314            &[b"ARDEL".as_ref(), b"s", b"0"][..],
12315            &[b"ARDELRANGE".as_ref(), b"s", b"0", b"1"][..],
12316            &[b"ARINSERT".as_ref(), b"s", b"x"][..],
12317            &[b"ARRING".as_ref(), b"s", b"4", b"x"][..],
12318            &[b"ARNEXT".as_ref(), b"s"][..],
12319            &[b"ARSEEK".as_ref(), b"s", b"1"][..],
12320            &[b"ARLASTITEMS".as_ref(), b"s", b"1"][..],
12321            &[b"ARSCAN".as_ref(), b"s", b"0", b"1"][..],
12322            &[b"ARGREP".as_ref(), b"s", b"0", b"1", b"EXACT", b"v"][..],
12323            &[b"AROP".as_ref(), b"s", b"0", b"1", b"SUM"][..],
12324            &[b"ARINFO".as_ref(), b"s"][..],
12325        ] {
12326            assert_eq!(f.run(cmd), wrong, "{}", String::from_utf8_lossy(cmd[0]));
12327        }
12328    }
12329
12330    /// Two of the array commands look the key up before they read the index and
12331    /// the rest read the index first, so the same broken argument gets two
12332    /// different errors depending on which command it went to.
12333    #[test]
12334    fn a_bad_index_reports_the_type_only_where_redis_reports_it() {
12335        let mut f = Fixture::new();
12336        f.run(&[b"SET", b"s", b"v"]);
12337        let wrong = "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n";
12338        let bad = "-ERR invalid array index\r\n";
12339        assert_eq!(f.run(&[b"ARGET", b"s", b"-1"]), wrong);
12340        assert_eq!(f.run(&[b"ARMGET", b"s", b"0", b"-1"]), wrong);
12341        assert_eq!(f.run(&[b"ARSET", b"s", b"-1", b"x"]), bad);
12342        assert_eq!(f.run(&[b"ARDEL", b"s", b"-1"]), bad);
12343        assert_eq!(f.run(&[b"ARSCAN", b"s", b"-1", b"0"]), bad);
12344        assert_eq!(f.run(&[b"ARGREP", b"s", b"-1", b"0", b"EXACT", b"v"]), bad);
12345        // And on a key that is an array the index is just an index.
12346        f.run(&[b"ARSET", b"a", b"0", b"x"]);
12347        assert_eq!(f.run(&[b"ARGET", b"a", b"-1"]), bad);
12348        assert_eq!(f.run(&[b"ARGET", b"nope", b"-1"]), bad);
12349    }
12350
12351    #[test]
12352    fn an_append_follows_a_cursor_the_client_can_move() {
12353        let mut f = Fixture::new();
12354        assert_eq!(f.run(&[b"ARNEXT", b"nope"]), ":0\r\n");
12355        assert_eq!(f.run(&[b"ARINSERT", b"a", b"x", b"y"]), ":1\r\n");
12356        assert_eq!(f.run(&[b"ARNEXT", b"a"]), ":2\r\n");
12357        assert_eq!(f.run(&[b"ARINSERT", b"a", b"z"]), ":2\r\n");
12358        assert_eq!(f.run(&[b"ARGET", b"a", b"2"]), "$1\r\nz\r\n");
12359
12360        // A seek says where the next one goes, and a missing key has no cursor
12361        // to move and is not created by the asking.
12362        assert_eq!(f.run(&[b"ARSEEK", b"nope", b"5"]), ":0\r\n");
12363        assert_eq!(f.run(&[b"EXISTS", b"nope"]), ":0\r\n");
12364        assert_eq!(f.run(&[b"ARSEEK", b"a", b"100"]), ":1\r\n");
12365        assert_eq!(f.run(&[b"ARNEXT", b"a"]), ":100\r\n");
12366        assert_eq!(f.run(&[b"ARINSERT", b"a", b"far"]), ":100\r\n");
12367        assert_eq!(f.run(&[b"ARSEEK", b"a", b"0"]), ":1\r\n");
12368        assert_eq!(f.run(&[b"ARNEXT", b"a"]), ":0\r\n");
12369
12370        // The top of the space is the one index only ARSEEK will take, and it
12371        // leaves the cursor with nowhere to go.
12372        assert_eq!(f.run(&[b"ARSEEK", b"a", b"18446744073709551615"]), ":1\r\n");
12373        assert_eq!(f.run(&[b"ARNEXT", b"a"]), "$-1\r\n");
12374        assert_eq!(
12375            f.run(&[b"ARINSERT", b"a", b"x"]),
12376            "-ERR insert index overflow\r\n"
12377        );
12378        assert_eq!(
12379            f.run(&[b"ARSET", b"a", b"18446744073709551615", b"x"]),
12380            "-ERR invalid array index\r\n"
12381        );
12382    }
12383
12384    #[test]
12385    fn a_ring_keeps_the_newest_and_renumbers_them_when_it_is_resized() {
12386        let mut f = Fixture::new();
12387        assert_eq!(f.run(&[b"ARRING", b"r", b"3", b"a", b"b", b"c"]), ":2\r\n");
12388        assert_eq!(f.run(&[b"ARRING", b"r", b"3", b"d", b"e"]), ":1\r\n");
12389        assert_eq!(f.run(&[b"ARLEN", b"r"]), ":3\r\n");
12390        assert_eq!(
12391            f.run(&[b"ARGETRANGE", b"r", b"0", b"2"]),
12392            "*3\r\n$1\r\nd\r\n$1\r\ne\r\n$1\r\nc\r\n"
12393        );
12394        // Growing it after it has wrapped puts the survivors back in the order
12395        // they arrived, which is the whole point of paying for the rebuild.
12396        assert_eq!(f.run(&[b"ARRING", b"r", b"5", b"f"]), ":3\r\n");
12397        assert_eq!(
12398            f.run(&[b"ARGETRANGE", b"r", b"0", b"3"]),
12399            "*4\r\n$1\r\nc\r\n$1\r\nd\r\n$1\r\ne\r\n$1\r\nf\r\n"
12400        );
12401        // The size is read before the key, so a bad one is a bad size wherever
12402        // it is sent.
12403        assert_eq!(
12404            f.run(&[b"ARRING", b"r", b"0", b"x"]),
12405            "-ERR size must be positive\r\n"
12406        );
12407        assert_eq!(
12408            f.run(&[b"ARRING", b"r", b"big", b"x"]),
12409            "-ERR invalid size\r\n"
12410        );
12411    }
12412
12413    #[test]
12414    fn the_last_items_walk_back_from_the_cursor_and_report_the_holes() {
12415        let mut f = Fixture::new();
12416        assert_eq!(f.run(&[b"ARLASTITEMS", b"nope", b"5"]), "*0\r\n");
12417        f.run(&[b"ARRING", b"r", b"4", b"a", b"b", b"c", b"d", b"e"]);
12418        assert_eq!(
12419            f.run(&[b"ARLASTITEMS", b"r", b"3"]),
12420            "*3\r\n$1\r\nc\r\n$1\r\nd\r\n$1\r\ne\r\n"
12421        );
12422        assert_eq!(
12423            f.run(&[b"ARLASTITEMS", b"r", b"3", b"rev"]),
12424            "*3\r\n$1\r\ne\r\n$1\r\nd\r\n$1\r\nc\r\n"
12425        );
12426        assert_eq!(
12427            f.run(&[b"ARLASTITEMS", b"r", b"99"]),
12428            "*4\r\n$1\r\nb\r\n$1\r\nc\r\n$1\r\nd\r\n$1\r\ne\r\n",
12429            "more than there is gets what there is"
12430        );
12431        // Nothing asked for is an empty reply, and Redis answers that before it
12432        // has read the option or looked at the key.
12433        assert_eq!(f.run(&[b"ARLASTITEMS", b"r", b"0", b"junk"]), "*0\r\n");
12434        assert_eq!(
12435            f.run(&[b"ARLASTITEMS", b"r", b"1", b"junk"]),
12436            "-ERR syntax error\r\n"
12437        );
12438        assert_eq!(
12439            f.run(&[b"ARLASTITEMS", b"r", b"nine"]),
12440            "-ERR invalid COUNT\r\n"
12441        );
12442
12443        // With no cursor the tail of the array is the anchor, and a hole inside
12444        // the window is reported as one.
12445        f.run(&[b"ARMSET", b"h", b"0", b"x", b"2", b"z"]);
12446        assert_eq!(
12447            f.run(&[b"ARLASTITEMS", b"h", b"5"]),
12448            "*2\r\n$-1\r\n$1\r\nz\r\n"
12449        );
12450    }
12451
12452    #[test]
12453    fn a_scan_answers_pairs_for_what_is_there_and_skips_what_is_not() {
12454        let mut f = Fixture::new();
12455        assert_eq!(f.run(&[b"ARSCAN", b"nope", b"0", b"10"]), "*0\r\n");
12456        f.run(&[b"ARMSET", b"a", b"0", b"x", b"7", b"y", b"1000000", b"z"]);
12457        // The whole index space, which ARGETRANGE refuses and this one answers
12458        // in three visits because holes cost nothing.
12459        assert_eq!(
12460            f.run(&[b"ARSCAN", b"a", b"0", b"18446744073709551614"]),
12461            "*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"
12462        );
12463        assert_eq!(
12464            f.run(&[
12465                b"ARSCAN",
12466                b"a",
12467                b"18446744073709551614",
12468                b"0",
12469                b"LIMIT",
12470                b"1"
12471            ]),
12472            "*1\r\n*2\r\n:1000000\r\n$1\r\nz\r\n"
12473        );
12474        assert_eq!(f.run(&[b"ARSCAN", b"a", b"1", b"6"]), "*0\r\n");
12475        assert_eq!(
12476            f.run(&[b"ARSCAN", b"a", b"0", b"10", b"LIMIT", b"0"]),
12477            "-ERR LIMIT must be positive\r\n"
12478        );
12479        assert_eq!(
12480            f.run(&[b"ARSCAN", b"a", b"0", b"10", b"NOPE", b"1"]),
12481            "-ERR syntax error\r\n"
12482        );
12483        assert_eq!(
12484            f.run(&[b"ARSCAN", b"a", b"0", b"10", b"LIMIT"]),
12485            "-ERR wrong number of arguments for 'arscan' command\r\n"
12486        );
12487    }
12488
12489    #[test]
12490    fn a_grep_answers_the_indexes_whose_elements_match() {
12491        let mut f = Fixture::new();
12492        assert_eq!(
12493            f.run(&[b"ARGREP", b"nope", b"0", b"10", b"EXACT", b"x"]),
12494            "*0\r\n"
12495        );
12496        f.run(&[b"ARSET", b"a", b"0", b"alpha", b"beta", b"gamma", b"ALPHA"]);
12497
12498        // The two bounds take the ends of the array as well as an index, and a
12499        // reversed range is walked backwards the way ARSCAN walks one.
12500        assert_eq!(
12501            f.run(&[b"ARGREP", b"a", b"-", b"+", b"GLOB", b"*a"]),
12502            "*3\r\n:0\r\n:1\r\n:2\r\n"
12503        );
12504        assert_eq!(
12505            f.run(&[b"ARGREP", b"a", b"+", b"-", b"GLOB", b"*a"]),
12506            "*3\r\n:2\r\n:1\r\n:0\r\n"
12507        );
12508        assert_eq!(
12509            f.run(&[b"ARGREP", b"a", b"1", b"2", b"GLOB", b"*a"]),
12510            "*2\r\n:1\r\n:2\r\n"
12511        );
12512
12513        // One test each. NOCASE reaches all four of them and it may be written
12514        // after the pattern it applies to.
12515        assert_eq!(
12516            f.run(&[b"ARGREP", b"a", b"-", b"+", b"EXACT", b"alpha"]),
12517            "*1\r\n:0\r\n"
12518        );
12519        assert_eq!(
12520            f.run(&[b"ARGREP", b"a", b"-", b"+", b"EXACT", b"alpha", b"NOCASE"]),
12521            "*2\r\n:0\r\n:3\r\n"
12522        );
12523        assert_eq!(
12524            f.run(&[b"ARGREP", b"a", b"-", b"+", b"MATCH", b"mm"]),
12525            "*1\r\n:2\r\n"
12526        );
12527        assert_eq!(
12528            f.run(&[b"ARGREP", b"a", b"-", b"+", b"RE", b"^[bg]"]),
12529            "*2\r\n:1\r\n:2\r\n"
12530        );
12531
12532        // OR is the default and AND has to be asked for, and either way the
12533        // last of a repeated option wins.
12534        let both: &[&[u8]] = &[
12535            b"ARGREP", b"a", b"-", b"+", b"EXACT", b"beta", b"MATCH", b"al",
12536        ];
12537        assert_eq!(f.run(both), "*2\r\n:0\r\n:1\r\n");
12538        assert_eq!(
12539            f.run(&[
12540                b"ARGREP", b"a", b"-", b"+", b"EXACT", b"beta", b"MATCH", b"al", b"AND"
12541            ]),
12542            "*0\r\n"
12543        );
12544        assert_eq!(
12545            f.run(&[
12546                b"ARGREP", b"a", b"-", b"+", b"EXACT", b"beta", b"MATCH", b"al", b"AND", b"OR"
12547            ]),
12548            "*2\r\n:0\r\n:1\r\n"
12549        );
12550
12551        // WITHVALUES turns each hit into a pair, and LIMIT counts the hits and
12552        // not the positions it had to look at.
12553        assert_eq!(
12554            f.run(&[
12555                b"ARGREP",
12556                b"a",
12557                b"-",
12558                b"+",
12559                b"MATCH",
12560                b"a",
12561                b"WITHVALUES",
12562                b"LIMIT",
12563                b"2"
12564            ]),
12565            "*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"
12566        );
12567        assert_eq!(
12568            f.run(&[
12569                b"ARGREP", b"a", b"-", b"+", b"EXACT", b"ALPHA", b"LIMIT", b"1"
12570            ]),
12571            "*1\r\n:3\r\n"
12572        );
12573    }
12574
12575    /// Everything ARGREP refuses, in the order it refuses it.
12576    #[test]
12577    fn a_grep_reports_a_broken_command_the_way_redis_does() {
12578        let mut f = Fixture::new();
12579        f.run(&[b"ARSET", b"a", b"0", b"alpha"]);
12580        let syntax = "-ERR syntax error\r\n";
12581
12582        // The bounds are read before the plan, so a bad index beats a bad
12583        // predicate whichever way round the two are written.
12584        assert_eq!(
12585            f.run(&[b"ARGREP", b"a", b"-1", b"0", b"NOPE", b"x"]),
12586            "-ERR invalid array index\r\n"
12587        );
12588        assert_eq!(f.run(&[b"ARGREP", b"a", b"0", b"1", b"NOPE", b"x"]), syntax);
12589        // A keyword with nothing after it, and a command that asks for nothing.
12590        assert_eq!(
12591            f.run(&[b"ARGREP", b"a", b"0", b"1", b"NOCASE", b"EXACT"]),
12592            syntax
12593        );
12594        assert_eq!(
12595            f.run(&[b"ARGREP", b"a", b"0", b"1", b"EXACT", b"x", b"LIMIT"]),
12596            syntax
12597        );
12598        assert_eq!(
12599            f.run(&[b"ARGREP", b"a", b"0", b"1", b"NOCASE", b"WITHVALUES"]),
12600            syntax,
12601            "a command with no predicate in it at all"
12602        );
12603        assert_eq!(
12604            f.run(&[b"ARGREP", b"a", b"0", b"1", b"EXACT", b"x", b"LIMIT", b"0"]),
12605            "-ERR LIMIT must be positive\r\n"
12606        );
12607        assert_eq!(
12608            f.run(&[
12609                b"ARGREP", b"a", b"0", b"1", b"EXACT", b"x", b"LIMIT", b"nine"
12610            ]),
12611            "-ERR value is not an integer or out of range\r\n"
12612        );
12613        assert_eq!(
12614            f.run(&[b"ARGREP", b"a", b"0", b"1", b"RE", b""]),
12615            "-ERR regular expression is empty\r\n"
12616        );
12617        assert_eq!(
12618            f.run(&[b"ARGREP", b"a", b"0", b"1", b"RE", b"(a"]),
12619            "-ERR invalid regular expression: Missing ')'\r\n"
12620        );
12621        assert_eq!(
12622            f.run(&[b"ARGREP", b"a", b"0", b"1", b"RE", br"(a)\1"]),
12623            "-ERR regular expression backreferences are not supported\r\n"
12624        );
12625        // The arity is minus six, so a predicate keyword with no pattern after
12626        // it is short by one and never reaches the parser.
12627        let arity = "-ERR wrong number of arguments for 'argrep' command\r\n";
12628        assert_eq!(f.run(&[b"ARGREP", b"a", b"0", b"1", b"EXACT"]), arity);
12629        assert_eq!(f.run(&[b"ARGREP", b"a", b"0", b"1"]), arity);
12630    }
12631
12632    #[test]
12633    fn an_op_reduces_a_range_to_one_number() {
12634        let mut f = Fixture::new();
12635        f.run(&[b"ARSET", b"a", b"0", b"1", b"2.5", b"word", b"-4"]);
12636        assert_eq!(
12637            f.run(&[b"AROP", b"a", b"0", b"10", b"SUM"]),
12638            "$4\r\n-0.5\r\n"
12639        );
12640        assert_eq!(f.run(&[b"AROP", b"a", b"0", b"10", b"min"]), "$2\r\n-4\r\n");
12641        assert_eq!(
12642            f.run(&[b"AROP", b"a", b"0", b"10", b"MAX"]),
12643            "$3\r\n2.5\r\n"
12644        );
12645        assert_eq!(f.run(&[b"AROP", b"a", b"0", b"10", b"USED"]), ":4\r\n");
12646        assert_eq!(
12647            f.run(&[b"AROP", b"a", b"0", b"10", b"MATCH", b"word"]),
12648            ":1\r\n"
12649        );
12650        // An aggregate is written with seventeen significant digits, which is
12651        // Redis's own choice and not what a score comes back as.
12652        f.run(&[b"ARSET", b"t", b"0", b"0.1", b"0.2"]);
12653        assert_eq!(
12654            f.run(&[b"AROP", b"t", b"0", b"10", b"SUM"]),
12655            "$19\r\n0.30000000000000004\r\n"
12656        );
12657        assert_eq!(f.run(&[b"ZADD", b"z", b"0.3", b"m"]), ":1\r\n");
12658        assert_eq!(f.run(&[b"ZSCORE", b"z", b"m"]), "$3\r\n0.3\r\n");
12659
12660        // Nothing to work with is a null, and a missing key is a null for the
12661        // aggregates and a zero for the two that count.
12662        f.run(&[b"ARSET", b"w", b"0", b"word"]);
12663        assert_eq!(f.run(&[b"AROP", b"w", b"0", b"10", b"SUM"]), "$-1\r\n");
12664        assert_eq!(f.run(&[b"AROP", b"nope", b"0", b"10", b"SUM"]), "$-1\r\n");
12665        assert_eq!(f.run(&[b"AROP", b"nope", b"0", b"10", b"USED"]), ":0\r\n");
12666
12667        assert_eq!(
12668            f.run(&[b"AROP", b"a", b"0", b"10", b"NOPE"]),
12669            "-ERR unknown operation\r\n"
12670        );
12671        assert_eq!(
12672            f.run(&[b"AROP", b"a", b"0", b"10", b"MATCH"]),
12673            "-ERR MATCH requires a value argument\r\n"
12674        );
12675        assert_eq!(
12676            f.run(&[b"AROP", b"a", b"0", b"10", b"SUM", b"extra"]),
12677            "-ERR wrong number of arguments for 'arop' command\r\n"
12678        );
12679    }
12680
12681    #[test]
12682    fn the_info_is_a_map_and_a_missing_key_is_an_error() {
12683        let mut f = Fixture::new();
12684        assert_eq!(f.run(&[b"ARINFO", b"nope"]), "-ERR no such key\r\n");
12685        f.run(&[b"ARINSERT", b"a", b"x", b"y"]);
12686        let short = f.run(&[b"ARINFO", b"a"]);
12687        assert!(
12688            short.starts_with("*14\r\n"),
12689            "seven pairs on RESP2: {short}"
12690        );
12691        assert!(short.contains("$5\r\ncount\r\n:2\r\n"), "{short}");
12692        assert!(
12693            short.contains("$17\r\nnext-insert-index\r\n:2\r\n"),
12694            "{short}"
12695        );
12696        assert!(short.contains("$10\r\nslice-size\r\n:4096\r\n"), "{short}");
12697        let full = f.run(&[b"ARINFO", b"a", b"full"]);
12698        assert!(full.starts_with("*24\r\n"), "twelve pairs: {full}");
12699        // Two values one apart are held sparsely, so the dense count is zero and
12700        // the two dense averages have nothing to average.
12701        assert!(full.contains("$12\r\ndense-slices\r\n:0\r\n"), "{full}");
12702        assert!(full.contains("$13\r\nsparse-slices\r\n:1\r\n"), "{full}");
12703        assert!(
12704            full.contains("$14\r\navg-dense-size\r\n$1\r\n0\r\n"),
12705            "{full}"
12706        );
12707        assert_eq!(f.run(&[b"ARINFO", b"a", b"nope"]), "-ERR syntax error\r\n");
12708
12709        // On RESP3 the same reply is a map and the averages are doubles.
12710        let mut g = Fixture::new();
12711        g.run(&[b"HELLO", b"3"]);
12712        g.run(&[b"ARINSERT", b"a", b"x"]);
12713        let map = g.run(&[b"ARINFO", b"a", b"FULL"]);
12714        assert!(map.starts_with("%12\r\n"), "{map}");
12715        assert!(map.contains("$5\r\ncount\r\n:1\r\n"), "{map}");
12716        assert!(map.contains("$14\r\navg-dense-size\r\n,0\r\n"), "{map}");
12717    }
12718
12719    #[test]
12720    fn a_double_on_the_wire_is_written_the_way_redis_writes_one() {
12721        let mut f = Fixture::new();
12722        // Whole numbers up to two to the sixty second come back as integers,
12723        // and past that the digit generator takes over and uses an exponent.
12724        for (score, want) in [
12725            ("3", "3"),
12726            ("3.5", "3.5"),
12727            ("0.3", "0.3"),
12728            ("1e30", "1e+30"),
12729            ("1e19", "1e+19"),
12730            ("1e-7", "1e-7"),
12731            ("0.000001", "0.000001"),
12732            ("4611686018427387904", "4611686018427387904"),
12733            ("-0", "-0"),
12734        ] {
12735            f.run(&[b"ZADD", b"z", score.as_bytes(), b"m"]);
12736            assert_eq!(
12737                f.run(&[b"ZSCORE", b"z", b"m"]),
12738                format!("${}\r\n{want}\r\n", want.len()),
12739                "score {score}"
12740            );
12741        }
12742
12743        // The same bytes on RESP3, where the reply is a double rather than a
12744        // bulk string.
12745        let mut g = Fixture::new();
12746        g.run(&[b"HELLO", b"3"]);
12747        g.run(&[b"ZADD", b"z", b"1e30", b"m"]);
12748        assert_eq!(g.run(&[b"ZSCORE", b"z", b"m"]), ",1e+30\r\n");
12749        // The two float increments are not this printer. They go through
12750        // ld2string in its human mode, which is a fixed point conversion with
12751        // the trailing zeros taken off, so they never write an exponent, and
12752        // they reply with a bulk string on both protocols.
12753        assert_eq!(
12754            g.run(&[b"INCRBYFLOAT", b"s", b"1e30"]),
12755            "$31\r\n1000000000000000000000000000000\r\n"
12756        );
12757        assert_eq!(g.run(&[b"INCRBYFLOAT", b"t", b"0.1"]), "$3\r\n0.1\r\n");
12758        assert_eq!(
12759            g.run(&[b"HINCRBYFLOAT", b"h", b"f", b"1e19"]),
12760            "$20\r\n10000000000000000000\r\n"
12761        );
12762    }
12763
12764    // ----------------------------------------------------------------- graph
12765
12766    #[test]
12767    fn a_node_comes_back_with_the_fields_it_went_in_with() {
12768        let mut f = Fixture::new();
12769        assert_eq!(
12770            f.run(&[
12771                b"G.NADD", b"social", b"ada", b"name", b"Ada", b"born", b"1815"
12772            ]),
12773            ":1\r\n"
12774        );
12775        // The year comes back as the four bytes that were sent and not as a
12776        // number, because every property is text and there is nothing on the
12777        // wire that says which of `1815` and `"1815"` the client meant. The
12778        // fields are in the document's order, which is sorted by name, because
12779        // that is what makes a field lookup a binary search.
12780        assert_eq!(
12781            f.run(&[b"G.NGET", b"social", b"ada"]),
12782            "*4\r\n$4\r\nborn\r\n$4\r\n1815\r\n$4\r\nname\r\n$3\r\nAda\r\n"
12783        );
12784        // A second write to the same id replaces the document and says so with
12785        // a zero, so an ingest can count what it created.
12786        assert_eq!(
12787            f.run(&[b"G.NADD", b"social", b"ada", b"name", b"Ada Lovelace"]),
12788            ":0\r\n"
12789        );
12790        assert_eq!(
12791            f.run(&[b"G.NGET", b"social", b"ada"]),
12792            "*2\r\n$4\r\nname\r\n$12\r\nAda Lovelace\r\n"
12793        );
12794        // A node with no properties is an empty map and not a null, which is
12795        // how a client tells an isolated node from one that is not there.
12796        assert_eq!(f.run(&[b"G.NADD", b"social", b"grace"]), ":1\r\n");
12797        assert_eq!(f.run(&[b"G.NGET", b"social", b"grace"]), "*0\r\n");
12798        assert_eq!(f.run(&[b"G.NGET", b"social", b"nobody"]), "$-1\r\n");
12799        assert_eq!(f.run(&[b"G.NGET", b"nokey", b"ada"]), "$-1\r\n");
12800
12801        // A field with no value creates nothing, because the pairs are checked
12802        // before the key is touched.
12803        assert_eq!(
12804            f.run(&[b"G.NADD", b"fresh", b"n", b"lonely"]),
12805            "-ERR syntax error\r\n"
12806        );
12807        assert_eq!(f.run(&[b"EXISTS", b"fresh"]), ":0\r\n");
12808
12809        // On RESP3 the same reply is a map.
12810        let mut g = Fixture::new();
12811        g.run(&[b"HELLO", b"3"]);
12812        g.run(&[b"G.NADD", b"social", b"ada", b"name", b"Ada"]);
12813        assert_eq!(
12814            g.run(&[b"G.NGET", b"social", b"ada"]),
12815            "%1\r\n$4\r\nname\r\n$3\r\nAda\r\n"
12816        );
12817    }
12818
12819    #[test]
12820    fn an_edge_creates_the_ends_it_needs() {
12821        let mut f = Fixture::new();
12822        assert_eq!(
12823            f.run(&[
12824                b"G.EADD", b"social", b"ada", b"grace", b"FOLLOWS", b"since", b"1843"
12825            ]),
12826            ":1\r\n"
12827        );
12828        // Neither end was written first and both are there, as empty nodes.
12829        assert_eq!(f.run(&[b"G.NGET", b"social", b"ada"]), "*0\r\n");
12830        assert_eq!(f.run(&[b"G.NGET", b"social", b"grace"]), "*0\r\n");
12831        assert_eq!(
12832            f.run(&[b"G.OUT", b"social", b"ada", b"FOLLOWS"]),
12833            "*2\r\n$1\r\n0\r\n*1\r\n$5\r\ngrace\r\n"
12834        );
12835        assert_eq!(
12836            f.run(&[b"G.IN", b"social", b"grace", b"FOLLOWS"]),
12837            "*2\r\n$1\r\n0\r\n*1\r\n$3\r\nada\r\n"
12838        );
12839        // The same pair under the same label again updates the edge rather than
12840        // making a second one.
12841        assert_eq!(
12842            f.run(&[
12843                b"G.EADD", b"social", b"ada", b"grace", b"FOLLOWS", b"since", b"1844"
12844            ]),
12845            ":0\r\n"
12846        );
12847        assert_eq!(f.run(&[b"G.DEG", b"social", b"ada", b"FOLLOWS"]), ":1\r\n");
12848        // A different label between the same pair is a different edge.
12849        assert_eq!(
12850            f.run(&[b"G.EADD", b"social", b"ada", b"grace", b"WORKS_WITH"]),
12851            ":1\r\n"
12852        );
12853        assert_eq!(
12854            f.run(&[b"G.DEG", b"social", b"ada", b"WORKS_WITH"]),
12855            ":1\r\n"
12856        );
12857
12858        assert_eq!(
12859            f.run(&[b"G.EDEL", b"social", b"ada", b"grace", b"FOLLOWS"]),
12860            ":1\r\n"
12861        );
12862        assert_eq!(
12863            f.run(&[b"G.EDEL", b"social", b"ada", b"grace", b"FOLLOWS"]),
12864            ":0\r\n"
12865        );
12866        // A label nothing has used, an end that is not there, and a key that is
12867        // not there are all a zero rather than an error.
12868        assert_eq!(
12869            f.run(&[b"G.EDEL", b"social", b"ada", b"grace", b"NEVER"]),
12870            ":0\r\n"
12871        );
12872        assert_eq!(
12873            f.run(&[b"G.EDEL", b"social", b"ada", b"nobody", b"FOLLOWS"]),
12874            ":0\r\n"
12875        );
12876        assert_eq!(
12877            f.run(&[b"G.EDEL", b"nokey", b"ada", b"grace", b"FOLLOWS"]),
12878            ":0\r\n"
12879        );
12880    }
12881
12882    /// A run is paged the way `SCAN` is paged, so a client that can walk one
12883    /// can walk the other.
12884    #[test]
12885    fn a_hop_answers_a_cursor_and_a_page() {
12886        let mut f = Fixture::new();
12887        for i in 0..25u32 {
12888            let dst = format!("n{i}");
12889            f.run(&[b"G.EADD", b"social", b"hub", dst.as_bytes(), b"FOLLOWS"]);
12890        }
12891        // Ten without being asked, and the cursor is where to carry on from.
12892        let first = f.run(&[b"G.OUT", b"social", b"hub", b"FOLLOWS"]);
12893        assert!(first.starts_with("*2\r\n$2\r\n10\r\n*10\r\n"), "{first}");
12894
12895        let mut seen = 0;
12896        let mut cursor = String::from("0");
12897        loop {
12898            let page = f.run(&[
12899                b"G.OUT",
12900                b"social",
12901                b"hub",
12902                b"FOLLOWS",
12903                b"COUNT",
12904                b"7",
12905                b"CURSOR",
12906                cursor.as_bytes(),
12907            ]);
12908            let (head, rest) = page.split_once("\r\n*").expect("a cursor and a page");
12909            cursor = head
12910                .rsplit("\r\n")
12911                .next()
12912                .expect("the cursor line")
12913                .to_string();
12914            seen += rest
12915                .split_once("\r\n")
12916                .expect("the page length")
12917                .0
12918                .parse::<usize>()
12919                .expect("a length");
12920            if cursor == "0" {
12921                break;
12922            }
12923        }
12924        assert_eq!(seen, 25, "every neighbour once across the pages");
12925
12926        // A cursor past the end is an empty page and not an error, and so is a
12927        // key or a label that is not there.
12928        assert_eq!(
12929            f.run(&[b"G.OUT", b"social", b"hub", b"FOLLOWS", b"CURSOR", b"900"]),
12930            "*2\r\n$1\r\n0\r\n*0\r\n"
12931        );
12932        assert_eq!(
12933            f.run(&[b"G.OUT", b"social", b"hub", b"NEVER"]),
12934            "*2\r\n$1\r\n0\r\n*0\r\n"
12935        );
12936        assert_eq!(
12937            f.run(&[b"G.OUT", b"nokey", b"hub", b"FOLLOWS"]),
12938            "*2\r\n$1\r\n0\r\n*0\r\n"
12939        );
12940        assert_eq!(
12941            f.run(&[b"G.OUT", b"social", b"hub", b"FOLLOWS", b"COUNT", b"0"]),
12942            "-ERR COUNT must be a positive integer\r\n"
12943        );
12944        assert_eq!(
12945            f.run(&[b"G.OUT", b"social", b"hub", b"FOLLOWS", b"NOPE", b"1"]),
12946            "-ERR syntax error\r\n"
12947        );
12948    }
12949
12950    #[test]
12951    fn a_degree_counts_one_way_or_both() {
12952        let mut f = Fixture::new();
12953        f.run(&[b"G.EADD", b"social", b"a", b"b", b"F"]);
12954        f.run(&[b"G.EADD", b"social", b"a", b"c", b"F"]);
12955        f.run(&[b"G.EADD", b"social", b"d", b"a", b"F"]);
12956        assert_eq!(f.run(&[b"G.DEG", b"social", b"a", b"F"]), ":2\r\n");
12957        assert_eq!(f.run(&[b"G.DEG", b"social", b"a", b"F", b"OUT"]), ":2\r\n");
12958        assert_eq!(f.run(&[b"G.DEG", b"social", b"a", b"F", b"IN"]), ":1\r\n");
12959        assert_eq!(f.run(&[b"G.DEG", b"social", b"a", b"F", b"BOTH"]), ":3\r\n");
12960        assert_eq!(f.run(&[b"G.DEG", b"social", b"nobody", b"F"]), ":0\r\n");
12961        assert_eq!(f.run(&[b"G.DEG", b"social", b"a", b"NEVER"]), ":0\r\n");
12962        assert_eq!(f.run(&[b"G.DEG", b"nokey", b"a", b"F"]), ":0\r\n");
12963        assert_eq!(
12964            f.run(&[b"G.DEG", b"social", b"a", b"F", b"SIDEWAYS"]),
12965            "-ERR syntax error\r\n"
12966        );
12967    }
12968
12969    /// A walk answers which nodes it can reach and not by how many routes, so a
12970    /// node two ways out is in the frontier once.
12971    #[test]
12972    fn a_walk_reaches_each_node_once_however_many_ways_there_are() {
12973        let mut f = Fixture::new();
12974        for (src, dst) in [
12975            ("ada", "grace"),
12976            ("ada", "alan"),
12977            ("grace", "edsger"),
12978            ("alan", "edsger"),
12979            ("edsger", "barbara"),
12980        ] {
12981            f.run(&[b"G.EADD", b"social", src.as_bytes(), dst.as_bytes(), b"F"]);
12982        }
12983        // Two hops without being asked, the start left out, and edsger once
12984        // even though both of the first hop's nodes point at it.
12985        assert_eq!(
12986            f.run(&[b"G.NEIGH", b"social", b"ada", b"F"]),
12987            "*3\r\n$5\r\ngrace\r\n$4\r\nalan\r\n$6\r\nedsger\r\n"
12988        );
12989        assert_eq!(
12990            f.run(&[b"G.NEIGH", b"social", b"ada", b"F", b"DEPTH", b"1"]),
12991            "*2\r\n$5\r\ngrace\r\n$4\r\nalan\r\n"
12992        );
12993        let deep = f.run(&[b"G.NEIGH", b"social", b"ada", b"F", b"DEPTH", b"9"]);
12994        assert!(deep.starts_with("*4\r\n"), "the whole component: {deep}");
12995        assert!(deep.contains("$7\r\nbarbara\r\n"), "{deep}");
12996        // COUNT stops the walk rather than trimming what it found.
12997        assert_eq!(
12998            f.run(&[b"G.NEIGH", b"social", b"ada", b"F", b"COUNT", b"1"]),
12999            "*1\r\n$5\r\ngrace\r\n"
13000        );
13001        // A node nothing leaves is an empty array and not an error.
13002        assert_eq!(f.run(&[b"G.NEIGH", b"social", b"barbara", b"F"]), "*0\r\n");
13003        assert_eq!(f.run(&[b"G.NEIGH", b"social", b"ada", b"NEVER"]), "*0\r\n");
13004        assert_eq!(f.run(&[b"G.NEIGH", b"nokey", b"ada", b"F"]), "*0\r\n");
13005        assert_eq!(
13006            f.run(&[b"G.NEIGH", b"social", b"ada", b"F", b"DEPTH", b"0"]),
13007            "-ERR DEPTH must be a positive integer\r\n"
13008        );
13009        assert_eq!(
13010            f.run(&[b"G.NEIGH", b"social", b"ada", b"F", b"NOPE", b"1"]),
13011            "-ERR syntax error\r\n"
13012        );
13013    }
13014
13015    /// The two sided search, which is the whole reason `G.PATH` is a command
13016    /// and not something a client builds out of `G.OUT`.
13017    #[test]
13018    fn a_path_is_the_shortest_one_and_goes_over_any_label() {
13019        let mut f = Fixture::new();
13020        // A chain of six, and a shortcut that makes a shorter way round under a
13021        // second label so the search has to take either kind of hop.
13022        for i in 0..6u32 {
13023            let src = format!("n{i}");
13024            let dst = format!("n{}", i + 1);
13025            f.run(&[b"G.EADD", b"road", src.as_bytes(), dst.as_bytes(), b"STEP"]);
13026        }
13027        assert_eq!(
13028            f.run(&[b"G.PATH", b"road", b"n0", b"n6"]),
13029            "*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"
13030        );
13031        f.run(&[b"G.EADD", b"road", b"n0", b"n5", b"JUMP"]);
13032        assert_eq!(
13033            f.run(&[b"G.PATH", b"road", b"n0", b"n6"]),
13034            "*3\r\n$2\r\nn0\r\n$2\r\nn5\r\n$2\r\nn6\r\n"
13035        );
13036        // A node to itself is a path of one, and a depth too short to reach is
13037        // no path at all.
13038        assert_eq!(
13039            f.run(&[b"G.PATH", b"road", b"n2", b"n2"]),
13040            "*1\r\n$2\r\nn2\r\n"
13041        );
13042        assert_eq!(
13043            f.run(&[b"G.PATH", b"road", b"n0", b"n6", b"MAXDEPTH", b"1"]),
13044            "*0\r\n"
13045        );
13046        // Direction counts: the chain only goes one way.
13047        assert_eq!(f.run(&[b"G.PATH", b"road", b"n6", b"n0"]), "*0\r\n");
13048        // An unreachable node, a node that is not there, and a key that is not
13049        // there are the same empty answer.
13050        f.run(&[b"G.NADD", b"road", b"island"]);
13051        assert_eq!(f.run(&[b"G.PATH", b"road", b"n0", b"island"]), "*0\r\n");
13052        assert_eq!(f.run(&[b"G.PATH", b"road", b"n0", b"nobody"]), "*0\r\n");
13053        assert_eq!(f.run(&[b"G.PATH", b"nokey", b"n0", b"n6"]), "*0\r\n");
13054        assert_eq!(
13055            f.run(&[b"G.PATH", b"road", b"n0", b"n6", b"NOPE", b"3"]),
13056            "-ERR syntax error\r\n"
13057        );
13058    }
13059
13060    /// The point of the escape in the record tag: the keyspace owns a graph key
13061    /// the way it owns every other key, and none of these commands know a graph
13062    /// exists.
13063    #[test]
13064    fn the_keyspace_sees_a_graph_key_like_any_other() {
13065        let mut f = Fixture::new();
13066        f.run(&[b"G.EADD", b"social", b"ada", b"grace", b"F"]);
13067        assert_eq!(f.run(&[b"TYPE", b"social"]), "+graph\r\n");
13068        assert_eq!(
13069            f.run(&[b"OBJECT", b"ENCODING", b"social"]),
13070            "$9\r\nadjacency\r\n"
13071        );
13072        assert_eq!(f.run(&[b"EXISTS", b"social"]), ":1\r\n");
13073        assert_eq!(f.run(&[b"DBSIZE"]), ":1\r\n");
13074        assert_eq!(f.run(&[b"KEYS", b"*"]), "*1\r\n$6\r\nsocial\r\n");
13075        // A graph is counted against the server the way every other body is,
13076        // which is what `maxmemory` will read when this key is a million nodes.
13077        // There is no `MEMORY USAGE` command yet, so this asks the server.
13078        let held = f.server.memory_bytes();
13079        for i in 0..200u32 {
13080            let dst = format!("n{i}");
13081            f.run(&[b"G.EADD", b"big", b"hub", dst.as_bytes(), b"F"]);
13082        }
13083        assert!(
13084            f.server.memory_bytes() > held,
13085            "two hundred edges cost something: {held} then {}",
13086            f.server.memory_bytes()
13087        );
13088        f.run(&[b"DEL", b"big"]);
13089
13090        // An expiry, then a rename, then a move to another database, all of
13091        // which are the keyspace moving a record it cannot look inside.
13092        assert_eq!(f.run(&[b"EXPIRE", b"social", b"100"]), ":1\r\n");
13093        assert_eq!(f.run(&[b"PERSIST", b"social"]), ":1\r\n");
13094        assert_eq!(f.run(&[b"RENAME", b"social", b"net"]), "+OK\r\n");
13095        assert_eq!(f.run(&[b"MOVE", b"net", b"1"]), ":1\r\n");
13096        assert_eq!(f.run(&[b"EXISTS", b"net"]), ":0\r\n");
13097        f.run(&[b"SELECT", b"1"]);
13098        assert_eq!(f.run(&[b"G.DEG", b"net", b"ada", b"F"]), ":1\r\n");
13099
13100        assert_eq!(f.run(&[b"DEL", b"net"]), ":1\r\n");
13101        assert_eq!(f.run(&[b"DBSIZE"]), ":0\r\n");
13102        f.run(&[b"G.NADD", b"g", b"n"]);
13103        assert_eq!(f.run(&[b"FLUSHDB"]), "+OK\r\n");
13104        assert_eq!(f.run(&[b"DBSIZE"]), ":0\r\n");
13105    }
13106
13107    /// Neither `COPY` nor `DUMP` has a byte shape for a graph, so both say so
13108    /// rather than answering the way they answer for a key that is not there.
13109    #[test]
13110    fn a_graph_cannot_be_copied_or_dumped() {
13111        let mut f = Fixture::new();
13112        f.run(&[b"G.NADD", b"social", b"ada"]);
13113        assert_eq!(
13114            f.run(&[b"COPY", b"social", b"other"]),
13115            "-ERR COPY is not supported for a graph\r\n"
13116        );
13117        assert_eq!(
13118            f.run(&[b"COPY", b"social", b"other", b"DB", b"1"]),
13119            "-ERR COPY is not supported for a graph\r\n"
13120        );
13121        assert_eq!(
13122            f.run(&[b"DUMP", b"social"]),
13123            "-ERR DUMP is not supported for a graph\r\n"
13124        );
13125        // A refused copy leaves both keys exactly as they were.
13126        assert_eq!(f.run(&[b"EXISTS", b"social", b"other"]), ":1\r\n");
13127    }
13128
13129    /// A graph key is a key, so the commands for the other types refuse it and
13130    /// the graph commands refuse theirs.
13131    #[test]
13132    fn a_graph_and_a_string_are_the_wrong_type_for_each_other() {
13133        let wrong = "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n";
13134        let mut f = Fixture::new();
13135        f.run(&[b"G.NADD", b"social", b"ada"]);
13136        assert_eq!(f.run(&[b"GET", b"social"]), wrong);
13137        assert_eq!(f.run(&[b"LPUSH", b"social", b"x"]), wrong);
13138        assert_eq!(f.run(&[b"SADD", b"social", b"x"]), wrong);
13139
13140        f.run(&[b"SET", b"str", b"v"]);
13141        for cmd in [
13142            vec![b"G.NADD".as_ref(), b"str", b"n"],
13143            vec![b"G.NGET".as_ref(), b"str", b"n"],
13144            vec![b"G.NDEL".as_ref(), b"str", b"n"],
13145            vec![b"G.EADD".as_ref(), b"str", b"a", b"b", b"F"],
13146            vec![b"G.EDEL".as_ref(), b"str", b"a", b"b", b"F"],
13147            vec![b"G.OUT".as_ref(), b"str", b"a", b"F"],
13148            vec![b"G.IN".as_ref(), b"str", b"a", b"F"],
13149            vec![b"G.DEG".as_ref(), b"str", b"a", b"F"],
13150            vec![b"G.NEIGH".as_ref(), b"str", b"a", b"F"],
13151            vec![b"G.PATH".as_ref(), b"str", b"a", b"b"],
13152        ] {
13153            assert_eq!(f.run(&cmd), wrong, "{:?}", cmd[0]);
13154        }
13155    }
13156
13157    /// Every other collection here takes its key with it when its last member
13158    /// goes, and a graph is no different.
13159    #[test]
13160    fn a_graph_goes_when_its_last_node_does() {
13161        let mut f = Fixture::new();
13162        f.run(&[
13163            b"G.EADD", b"social", b"ada", b"grace", b"F", b"since", b"1843",
13164        ]);
13165        assert_eq!(f.run(&[b"G.NDEL", b"social", b"ada"]), ":1\r\n");
13166        // The node and the edges that hung off it are both gone.
13167        assert_eq!(f.run(&[b"G.NGET", b"social", b"ada"]), "$-1\r\n");
13168        assert_eq!(
13169            f.run(&[b"G.DEG", b"social", b"grace", b"F", b"IN"]),
13170            ":0\r\n"
13171        );
13172        assert_eq!(f.run(&[b"G.NDEL", b"social", b"ada"]), ":0\r\n");
13173        assert_eq!(f.run(&[b"EXISTS", b"social"]), ":1\r\n");
13174
13175        assert_eq!(f.run(&[b"G.NDEL", b"social", b"grace"]), ":1\r\n");
13176        assert_eq!(f.run(&[b"EXISTS", b"social"]), ":0\r\n");
13177        assert_eq!(f.run(&[b"DBSIZE"]), ":0\r\n");
13178        assert_eq!(f.run(&[b"G.NDEL", b"nokey", b"ada"]), ":0\r\n");
13179
13180        // The id the removed node had is not handed out again, so a client
13181        // holding an id from an earlier reply cannot have it mean another node.
13182        f.run(&[b"G.NADD", b"social", b"first"]);
13183        f.run(&[b"G.NADD", b"social", b"second"]);
13184        f.run(&[b"G.NDEL", b"social", b"first"]);
13185        f.run(&[b"G.EADD", b"social", b"third", b"second", b"F"]);
13186        assert_eq!(
13187            f.run(&[b"G.OUT", b"social", b"third", b"F"]),
13188            "*2\r\n$1\r\n0\r\n*1\r\n$6\r\nsecond\r\n"
13189        );
13190    }
13191
13192    // ------------------------------------------------------------------ json
13193
13194    /// The two path syntaxes answer different shapes, which is the thing a
13195    /// client is most likely to be broken by and so the thing to pin first.
13196    #[test]
13197    fn a_json_path_answers_a_set_and_a_legacy_path_answers_a_value() {
13198        let mut f = Fixture::new();
13199        let doc = br#"{"a":1,"b":{"c":true}}"#;
13200        assert_eq!(f.run(&[b"JSON.SET", b"doc", b"$", doc]), "+OK\r\n");
13201        // No path at all is the legacy root and not `$`, so the document comes
13202        // back as itself rather than wrapped.
13203        assert_eq!(
13204            f.run(&[b"JSON.GET", b"doc"]),
13205            bulk(r#"{"a":1,"b":{"c":true}}"#)
13206        );
13207        assert_eq!(f.run(&[b"JSON.GET", b"doc", b"$.a"]), bulk("[1]"));
13208        assert_eq!(f.run(&[b"JSON.GET", b"doc", b".a"]), bulk("1"));
13209        assert_eq!(f.run(&[b"JSON.GET", b"doc", b"$..c"]), bulk("[true]"));
13210        // A path that matched nothing is an empty set on one syntax and an
13211        // error on the other, and the error does not quote the path.
13212        assert_eq!(f.run(&[b"JSON.GET", b"doc", b"$.nope"]), bulk("[]"));
13213        assert_eq!(
13214            f.run(&[b"JSON.GET", b"doc", b".nope"]),
13215            "-ERR Path does not exist\r\n"
13216        );
13217        assert_eq!(f.run(&[b"JSON.GET", b"nokey"]), "$-1\r\n");
13218        // The key is a document to the rest of the keyspace, under the name
13219        // RedisJSON registers, and every generic command works on it.
13220        assert_eq!(f.run(&[b"TYPE", b"doc"]), "+ReJSON-RL\r\n");
13221        assert_eq!(f.run(&[b"EXISTS", b"doc"]), ":1\r\n");
13222        assert_eq!(f.run(&[b"OBJECT", b"ENCODING", b"doc"]), bulk("raw"));
13223        assert_eq!(f.run(&[b"DEL", b"doc"]), ":1\r\n");
13224        assert_eq!(f.run(&[b"JSON.GET", b"doc"]), "$-1\r\n");
13225    }
13226
13227    /// The two error lines RedisJSON sends without a prefix in front of them.
13228    ///
13229    /// Every other error this server writes starts `ERR` or `WRONGTYPE`. These
13230    /// two do not, on a real server, and a differential harness compares the
13231    /// whole line.
13232    #[test]
13233    fn the_two_json_errors_that_carry_no_prefix() {
13234        let mut f = Fixture::new();
13235        f.run(&[b"SET", b"plain", b"x"]);
13236        let wrong = "-Existing key has wrong Redis type\r\n";
13237        assert_eq!(f.run(&[b"JSON.GET", b"plain"]), wrong);
13238        assert_eq!(f.run(&[b"JSON.SET", b"plain", b"$", b"1"]), wrong);
13239        assert_eq!(f.run(&[b"JSON.DEL", b"plain"]), wrong);
13240        assert_eq!(f.run(&[b"JSON.TYPE", b"plain"]), wrong);
13241        assert_eq!(f.run(&[b"JSON.CLEAR", b"plain"]), wrong);
13242
13243        f.run(&[b"JSON.SET", b"doc", b"$", br#"{"a":{"z":1},"b":{"z":2}}"#]);
13244        // A wildcard that matched something writes to all of it. A wildcard
13245        // that matched nothing would have to invent a place, and that is the
13246        // other unprefixed line.
13247        assert_eq!(f.run(&[b"JSON.SET", b"doc", b"$.*.z", b"9"]), "+OK\r\n");
13248        assert_eq!(
13249            f.run(&[b"JSON.GET", b"doc"]),
13250            bulk(r#"{"a":{"z":9},"b":{"z":9}}"#)
13251        );
13252        assert_eq!(
13253            f.run(&[b"JSON.SET", b"doc", b"$.*.y", b"9"]),
13254            "-Err wrong static path\r\n"
13255        );
13256    }
13257
13258    /// What `JSON.SET` does with a path that named nowhere.
13259    #[test]
13260    fn json_set_creates_one_field_and_refuses_to_invent_the_rest() {
13261        let mut f = Fixture::new();
13262        // A key that is not there can only be written whole.
13263        assert_eq!(
13264            f.run(&[b"JSON.SET", b"new", b".a", b"1"]),
13265            "-ERR new objects must be created at the root\r\n"
13266        );
13267        assert_eq!(f.run(&[b"EXISTS", b"new"]), ":0\r\n");
13268        // The root check comes before NX and XX, which is the order a real
13269        // server checks them in.
13270        assert_eq!(
13271            f.run(&[b"JSON.SET", b"new", b".a", b"1", b"NX"]),
13272            "-ERR new objects must be created at the root\r\n"
13273        );
13274        assert_eq!(f.run(&[b"JSON.SET", b"new", b"$", b"1", b"XX"]), "$-1\r\n");
13275        assert_eq!(f.run(&[b"JSON.SET", b"new", b"$", b"1", b"NX"]), "+OK\r\n");
13276
13277        f.run(&[
13278            b"JSON.SET",
13279            b"doc",
13280            b"$",
13281            br#"{"o":{},"arr":[1,2],"s":"x"}"#,
13282        ]);
13283        // One step past a container that is there is a place to write.
13284        assert_eq!(f.run(&[b"JSON.SET", b"doc", b"$.o.made", b"1"]), "+OK\r\n");
13285        // One step past something that is not, or past something that is not an
13286        // object, is not an error and is not a write either.
13287        assert_eq!(
13288            f.run(&[b"JSON.SET", b"doc", b"$.nope.made", b"1"]),
13289            "$-1\r\n"
13290        );
13291        assert_eq!(f.run(&[b"JSON.SET", b"doc", b"$.s.made", b"1"]), "$-1\r\n");
13292        // An index past the end does not append. JSON.ARRAPPEND appends.
13293        assert_eq!(
13294            f.run(&[b"JSON.SET", b"doc", b"$.arr[5]", b"9"]),
13295            "-ERR array index out of range\r\n"
13296        );
13297        assert_eq!(
13298            f.run(&[b"JSON.SET", b"doc", b"$.arr[2]", b"9"]),
13299            "-ERR array index out of range\r\n"
13300        );
13301        assert_eq!(f.run(&[b"JSON.SET", b"doc", b"$.arr[1]", b"9"]), "+OK\r\n");
13302        // NX on a path that is there and XX on a path that is not are both a
13303        // nil and neither changes anything.
13304        assert_eq!(
13305            f.run(&[b"JSON.SET", b"doc", b"$.o.made", b"2", b"NX"]),
13306            "$-1\r\n"
13307        );
13308        assert_eq!(
13309            f.run(&[b"JSON.SET", b"doc", b"$.gone", b"2", b"XX"]),
13310            "$-1\r\n"
13311        );
13312        assert_eq!(
13313            f.run(&[b"JSON.GET", b"doc"]),
13314            bulk(r#"{"o":{"made":1},"s":"x","arr":[1,9]}"#)
13315        );
13316        // Text that is not JSON is refused before the key is touched. The
13317        // line has no `ERR` in front of it, which is this command's and not
13318        // every command's, and is in D-37.
13319        assert!(
13320            f.run(&[b"JSON.SET", b"doc", b"$.s", b"nope"])
13321                .starts_with("-this is not the start of a value")
13322        );
13323        assert_eq!(f.run(&[b"JSON.GET", b"doc", b".s"]), bulk("\"x\""));
13324    }
13325
13326    /// `JSON.DEL`, `JSON.TYPE`, `JSON.TOGGLE` and `JSON.CLEAR`, each of which
13327    /// answers a count or a word rather than text.
13328    #[test]
13329    fn the_json_commands_that_do_not_answer_text() {
13330        let mut f = Fixture::new();
13331        let doc = br#"{"a":1,"t":true,"o":{"x":1},"arr":[1,2],"f":1.5,"s":"x","n":null}"#;
13332        f.run(&[b"JSON.SET", b"doc", b"$", doc]);
13333
13334        assert_eq!(f.run(&[b"JSON.TYPE", b"doc"]), bulk("object"));
13335        assert_eq!(f.run(&[b"JSON.TYPE", b"doc", b".a"]), bulk("integer"));
13336        assert_eq!(f.run(&[b"JSON.TYPE", b"doc", b".f"]), bulk("number"));
13337        assert_eq!(
13338            f.run(&[b"JSON.TYPE", b"doc", b"$.a"]),
13339            format!("*1\r\n{}", bulk("integer"))
13340        );
13341        // The one place a legacy path that matched nothing is a nil rather than
13342        // an error, which lines up with a key that is not there.
13343        assert_eq!(f.run(&[b"JSON.TYPE", b"doc", b".nope"]), "$-1\r\n");
13344        assert_eq!(f.run(&[b"JSON.TYPE", b"nokey"]), "$-1\r\n");
13345
13346        // A boolean flips and answers the value it now has, as an integer on
13347        // one syntax and as the word on the other.
13348        assert_eq!(f.run(&[b"JSON.TOGGLE", b"doc", b"$.t"]), "*1\r\n:0\r\n");
13349        assert_eq!(f.run(&[b"JSON.TOGGLE", b"doc", b".t"]), bulk("true"));
13350        // Something that is not a boolean is a hole on one syntax and one
13351        // sentence covering both cases on the other.
13352        assert_eq!(f.run(&[b"JSON.TOGGLE", b"doc", b"$.a"]), "*1\r\n$-1\r\n");
13353        assert_eq!(
13354            f.run(&[b"JSON.TOGGLE", b"doc", b".a"]),
13355            "-ERR Path does not exist or not a bool\r\n"
13356        );
13357        assert_eq!(
13358            f.run(&[b"JSON.TOGGLE", b"doc", b".nope"]),
13359            "-ERR Path does not exist or not a bool\r\n"
13360        );
13361        assert_eq!(
13362            f.run(&[b"JSON.TOGGLE", b"nokey", b"$.a"]),
13363            "-ERR could not perform this operation on a key that doesn't exist\r\n"
13364        );
13365
13366        // Clearing empties containers and zeroes numbers and leaves everything
13367        // else alone, and counts only what it changed.
13368        assert_eq!(f.run(&[b"JSON.CLEAR", b"doc", b"$.s"]), ":0\r\n");
13369        assert_eq!(f.run(&[b"JSON.CLEAR", b"doc", b"$.*"]), ":4\r\n");
13370        assert_eq!(f.run(&[b"JSON.CLEAR", b"doc", b"$.*"]), ":0\r\n");
13371        assert_eq!(
13372            f.run(&[b"JSON.GET", b"doc"]),
13373            bulk(r#"{"a":0,"f":0,"n":null,"o":{},"s":"x","t":true,"arr":[]}"#)
13374        );
13375
13376        // Deleting counts what it removed, and deleting the root is deleting
13377        // the key.
13378        assert_eq!(f.run(&[b"JSON.DEL", b"doc", b"$.nope"]), ":0\r\n");
13379        assert_eq!(f.run(&[b"JSON.DEL", b"doc", b"$.a"]), ":1\r\n");
13380        // Deleting the last member of the root container deletes the key, the
13381        // same way popping the last element off a list does. It is a rule about
13382        // deleting and not about shape: a document written as an empty object
13383        // by JSON.SET stays, because nothing was removed from it.
13384        assert_eq!(f.run(&[b"JSON.FORGET", b"doc", b"$.*"]), ":6\r\n");
13385        assert_eq!(f.run(&[b"EXISTS", b"doc"]), ":0\r\n");
13386        assert_eq!(f.run(&[b"JSON.GET", b"doc"]), "$-1\r\n");
13387        assert_eq!(f.run(&[b"JSON.DEL", b"doc"]), ":0\r\n");
13388        assert_eq!(f.run(&[b"JSON.SET", b"empty", b"$", b"{}"]), "+OK\r\n");
13389        assert_eq!(f.run(&[b"EXISTS", b"empty"]), ":1\r\n");
13390        assert_eq!(f.run(&[b"JSON.GET", b"empty"]), bulk("{}"));
13391        assert_eq!(f.run(&[b"JSON.DEL", b"nokey"]), ":0\r\n");
13392    }
13393
13394    /// `JSON.GET` with more than one path, and with a layout.
13395    ///
13396    /// The wrapper the reply is built in is laid out too, so what a path
13397    /// matched starts one level in for a single JSONPath and two for one of
13398    /// several, and getting that wrong is the kind of thing only a byte for
13399    /// byte comparison catches.
13400    #[test]
13401    fn json_get_lays_out_the_wrapper_it_builds() {
13402        let mut f = Fixture::new();
13403        f.run(&[b"JSON.SET", b"doc", b"$", br#"{"a":1,"b":[1,{"c":2}]}"#]);
13404
13405        assert_eq!(
13406            f.run(&[b"JSON.GET", b"doc", b"$.a", b"$.b"]),
13407            bulk(r#"{"$.a":[1],"$.b":[[1,{"c":2}]]}"#)
13408        );
13409        // Legacy paths are not wrapped, even when there are several of them.
13410        assert_eq!(
13411            f.run(&[b"JSON.GET", b"doc", b".a", b".b"]),
13412            bulk(r#"{".a":1,".b":[1,{"c":2}]}"#)
13413        );
13414        let fmt: &[&[u8]] = &[b"INDENT", b"  ", b"NEWLINE", b"\n", b"SPACE", b" "];
13415        let mut one = vec![b"JSON.GET".as_slice(), b"doc"];
13416        one.extend_from_slice(fmt);
13417        one.push(b"$.b");
13418        assert_eq!(
13419            f.run(&one),
13420            bulk("[\n  [\n    1,\n    {\n      \"c\": 2\n    }\n  ]\n]")
13421        );
13422        let mut two = vec![b"JSON.GET".as_slice(), b"doc"];
13423        two.extend_from_slice(fmt);
13424        two.push(b"$.a");
13425        two.push(b"$.nope");
13426        assert_eq!(
13427            f.run(&two),
13428            bulk("{\n  \"$.a\": [\n    1\n  ],\n  \"$.nope\": []\n}")
13429        );
13430        // The options are read before the paths and in any order, and a
13431        // document with nothing to lay out is the same either way.
13432        let mut root = vec![b"JSON.GET".as_slice(), b"doc", b"SPACE", b" "];
13433        root.push(b".a");
13434        assert_eq!(f.run(&root), bulk("1"));
13435    }
13436
13437    /// `JSON.MGET`, which is the only command here that reads more than one key
13438    /// and so the only one whose answer has holes in it.
13439    #[test]
13440    fn json_mget_answers_once_per_key_whatever_is_under_them() {
13441        let mut f = Fixture::new();
13442        f.run(&[b"JSON.SET", b"one", b"$", br#"{"a":1}"#]);
13443        f.run(&[b"JSON.SET", b"two", b"$", br#"{"a":2}"#]);
13444        f.run(&[b"SET", b"plain", b"x"]);
13445        assert_eq!(
13446            f.run(&[b"JSON.MGET", b"one", b"two", b"$.a"]),
13447            format!("*2\r\n{}{}", bulk("[1]"), bulk("[2]"))
13448        );
13449        // A key that is not there and a key holding something else are both a
13450        // hole rather than an error, the way MGET treats a hash.
13451        assert_eq!(
13452            f.run(&[b"JSON.MGET", b"one", b"nokey", b"plain", b".a"]),
13453            format!("*3\r\n{}$-1\r\n$-1\r\n", bulk("1"))
13454        );
13455        // A legacy path that matched nothing is a hole too, because one bad
13456        // answer should not lose the others.
13457        assert_eq!(f.run(&[b"JSON.MGET", b"one", b".nope"]), "*1\r\n$-1\r\n");
13458    }
13459
13460    /// The four commands that ask how big something is, and the four different
13461    /// sets of answers they give for the same three failures.
13462    ///
13463    /// There is no pattern in this and there is no reading it off the
13464    /// documentation either. It was read off a running RedisJSON one line at a
13465    /// time, and it is written down here because the error text is what a client
13466    /// library branches on.
13467    #[test]
13468    fn the_json_commands_that_answer_a_size_disagree_about_every_failure() {
13469        let mut f = Fixture::new();
13470        let doc = br#"{"a":[1,2,3],"o":{"x":1,"y":2},"s":"hello","n":7}"#;
13471        f.run(&[b"JSON.SET", b"doc", b"$", doc]);
13472
13473        assert_eq!(f.run(&[b"JSON.ARRLEN", b"doc", b".a"]), ":3\r\n");
13474        assert_eq!(f.run(&[b"JSON.ARRLEN", b"doc", b"$.a"]), "*1\r\n:3\r\n");
13475        assert_eq!(f.run(&[b"JSON.OBJLEN", b"doc", b".o"]), ":2\r\n");
13476        assert_eq!(f.run(&[b"JSON.STRLEN", b"doc", b".s"]), ":5\r\n");
13477        assert_eq!(
13478            f.run(&[b"JSON.OBJKEYS", b"doc", b".o"]),
13479            format!("*2\r\n{}{}", bulk("x"), bulk("y"))
13480        );
13481        // A JSONPath answers one entry per match and a hole for a match of the
13482        // wrong kind, which is the one shape all four agree on.
13483        assert_eq!(
13484            f.run(&[b"JSON.ARRLEN", b"doc", b"$.*"]),
13485            "*4\r\n:3\r\n$-1\r\n$-1\r\n$-1\r\n"
13486        );
13487
13488        // A legacy path that matched nothing. Two of them are an error and two
13489        // of them are a nil, and the two errors do not use the same sentence.
13490        assert_eq!(
13491            f.run(&[b"JSON.ARRLEN", b"doc", b".nope"]),
13492            "-ERR Path does not exist\r\n"
13493        );
13494        assert_eq!(
13495            f.run(&[b"JSON.STRLEN", b"doc", b".nope"]),
13496            "-ERR Path does not exist\r\n"
13497        );
13498        assert_eq!(f.run(&[b"JSON.OBJLEN", b"doc", b".nope"]), "$-1\r\n");
13499        // A nil bulk and not an empty array, even though the answer would have
13500        // been an array, which is what RedisJSON sends here too.
13501        assert_eq!(f.run(&[b"JSON.OBJKEYS", b"doc", b".nope"]), "$-1\r\n");
13502        // The JSONPath spelling of the same question is an empty array, since
13503        // no match is not a failure on that syntax.
13504        assert_eq!(f.run(&[b"JSON.OBJKEYS", b"doc", b"$.nope"]), "*0\r\n");
13505
13506        // A legacy path that matched the wrong kind of value. Now two of them
13507        // are an ERR and two of them are a WRONGTYPE, and it is not the same
13508        // two.
13509        assert_eq!(
13510            f.run(&[b"JSON.ARRLEN", b"doc", b".n"]),
13511            "-ERR Path does not exist or not an array\r\n"
13512        );
13513        assert_eq!(
13514            f.run(&[b"JSON.OBJKEYS", b"doc", b".n"]),
13515            "-ERR Path does not exist or not an object\r\n"
13516        );
13517        assert_eq!(
13518            f.run(&[b"JSON.OBJLEN", b"doc", b".n"]),
13519            "-WRONGTYPE wrong type of path value - expected object\r\n"
13520        );
13521        assert_eq!(
13522            f.run(&[b"JSON.STRLEN", b"doc", b".n"]),
13523            "-WRONGTYPE wrong type of path value - expected string\r\n"
13524        );
13525
13526        // A key that is not there, where the two syntaxes swap over: the legacy
13527        // path is the quiet answer and the JSONPath is the error.
13528        assert_eq!(f.run(&[b"JSON.ARRLEN", b"nokey", b".a"]), "$-1\r\n");
13529        assert_eq!(f.run(&[b"JSON.OBJLEN", b"nokey", b".a"]), "$-1\r\n");
13530        assert_eq!(f.run(&[b"JSON.STRLEN", b"nokey", b".a"]), "$-1\r\n");
13531        assert_eq!(f.run(&[b"JSON.OBJKEYS", b"nokey", b".a"]), "$-1\r\n");
13532        assert_eq!(
13533            f.run(&[b"JSON.ARRLEN", b"nokey", b"$.a"]),
13534            "-ERR could not perform this operation on a key that doesn't exist\r\n"
13535        );
13536        // Except this one, which answers about the path instead.
13537        assert_eq!(
13538            f.run(&[b"JSON.OBJLEN", b"nokey", b"$.a"]),
13539            "-ERR Path does not exist or not an object\r\n"
13540        );
13541    }
13542
13543    /// `JSON.ARRAPPEND`, `JSON.ARRINSERT`, `JSON.ARRTRIM` and `JSON.ARRPOP`.
13544    ///
13545    /// The four of them share one error line for a path that named something
13546    /// that is not an array, and they disagree about what an index outside the
13547    /// array means: insert refuses it and the other two clamp.
13548    #[test]
13549    fn the_json_array_writes_agree_on_the_errors_and_not_on_the_indexes() {
13550        let mut f = Fixture::new();
13551        f.run(&[b"JSON.SET", b"doc", b"$", br#"{"a":[1,2,3],"n":7}"#]);
13552
13553        assert_eq!(f.run(&[b"JSON.ARRAPPEND", b"doc", b".a", b"4"]), ":4\r\n");
13554        assert_eq!(
13555            f.run(&[b"JSON.ARRAPPEND", b"doc", b"$.a", b"5", b"6"]),
13556            "*1\r\n:6\r\n"
13557        );
13558        assert_eq!(f.run(&[b"JSON.GET", b"doc", b".a"]), bulk("[1,2,3,4,5,6]"));
13559
13560        // A negative index counts back from the end, and the end itself is a
13561        // place to insert at, so an insert at the length is an append.
13562        assert_eq!(
13563            f.run(&[b"JSON.ARRINSERT", b"doc", b".a", b"-1", b"0"]),
13564            ":7\r\n"
13565        );
13566        assert_eq!(
13567            f.run(&[b"JSON.GET", b"doc", b".a"]),
13568            bulk("[1,2,3,4,5,0,6]")
13569        );
13570        assert_eq!(
13571            f.run(&[b"JSON.ARRINSERT", b"doc", b".a", b"7", b"9"]),
13572            ":8\r\n"
13573        );
13574        // One past the end is not, and neither is one before the front.
13575        assert_eq!(
13576            f.run(&[b"JSON.ARRINSERT", b"doc", b".a", b"9", b"9"]),
13577            "-ERR index out of bounds\r\n"
13578        );
13579        assert_eq!(
13580            f.run(&[b"JSON.ARRINSERT", b"doc", b".a", b"-9", b"9"]),
13581            "-ERR index out of bounds\r\n"
13582        );
13583
13584        // Trim takes both ends inclusive and clamps both of them, so a start
13585        // past the end leaves an empty array rather than an error.
13586        f.run(&[b"JSON.SET", b"doc", b"$.a", b"[1,2,3,4,5]"]);
13587        assert_eq!(
13588            f.run(&[b"JSON.ARRTRIM", b"doc", b".a", b"1", b"3"]),
13589            ":3\r\n"
13590        );
13591        assert_eq!(f.run(&[b"JSON.GET", b"doc", b".a"]), bulk("[2,3,4]"));
13592        assert_eq!(
13593            f.run(&[b"JSON.ARRTRIM", b"doc", b".a", b"-2", b"99"]),
13594            ":2\r\n"
13595        );
13596        assert_eq!(f.run(&[b"JSON.GET", b"doc", b".a"]), bulk("[3,4]"));
13597        assert_eq!(
13598            f.run(&[b"JSON.ARRTRIM", b"doc", b".a", b"9", b"9"]),
13599            ":0\r\n"
13600        );
13601        assert_eq!(f.run(&[b"JSON.GET", b"doc", b".a"]), bulk("[]"));
13602
13603        // Pop clamps as well, its default is the last element, and an empty
13604        // array pops a nil rather than failing.
13605        f.run(&[b"JSON.SET", b"doc", b"$.a", b"[1,2,3]"]);
13606        assert_eq!(f.run(&[b"JSON.ARRPOP", b"doc", b".a"]), bulk("3"));
13607        assert_eq!(f.run(&[b"JSON.ARRPOP", b"doc", b".a", b"0"]), bulk("1"));
13608        assert_eq!(f.run(&[b"JSON.ARRPOP", b"doc", b".a", b"99"]), bulk("2"));
13609        assert_eq!(f.run(&[b"JSON.ARRPOP", b"doc", b".a"]), "$-1\r\n");
13610
13611        // One sentence covers a path that matched nothing and a path that
13612        // matched the wrong kind of value, for all four of them.
13613        for call in [
13614            &[&b"JSON.ARRAPPEND"[..], b"doc", b"PATH", b"1"][..],
13615            &[&b"JSON.ARRTRIM"[..], b"doc", b"PATH", b"1", b"1"][..],
13616            &[&b"JSON.ARRPOP"[..], b"doc", b"PATH", b"1"][..],
13617            &[&b"JSON.ARRINSERT"[..], b"doc", b"PATH", b"0", b"1"][..],
13618        ] {
13619            for path in [&b".n"[..], &b".nope"[..]] {
13620                let args: Vec<&[u8]> = call
13621                    .iter()
13622                    .map(|a| if *a == b"PATH" { path } else { *a })
13623                    .collect();
13624                assert_eq!(
13625                    f.run(&args),
13626                    "-ERR Path does not exist or not an array\r\n",
13627                    "{} {}",
13628                    String::from_utf8_lossy(call[0]),
13629                    String::from_utf8_lossy(path)
13630                );
13631            }
13632        }
13633
13634        // A key that is not there is the same sentence for all four, on either
13635        // syntax, and it is about the key and not about the path.
13636        assert_eq!(
13637            f.run(&[b"JSON.ARRAPPEND", b"nokey", b".a", b"1"]),
13638            "-ERR could not perform this operation on a key that doesn't exist\r\n"
13639        );
13640        assert_eq!(
13641            f.run(&[b"JSON.ARRPOP", b"nokey", b"$.a"]),
13642            "-ERR could not perform this operation on a key that doesn't exist\r\n"
13643        );
13644
13645        // The values are parsed before the key is touched, so text that is not
13646        // JSON leaves the document alone.
13647        // Text that is not JSON is refused before the key is touched, and
13648        // the line has no `ERR` in front of it, which is D-37.
13649        assert!(
13650            f.run(&[b"JSON.ARRAPPEND", b"doc", b".a", b"nope"])
13651                .starts_with("-this is not the start of a value")
13652        );
13653        assert_eq!(f.run(&[b"JSON.GET", b"doc", b".a"]), bulk("[]"));
13654    }
13655
13656    /// `JSON.ARRINSERT` refuses the whole command when any one of the arrays a
13657    /// path matched cannot take the index, which is D-36.
13658    ///
13659    /// RedisJSON walks the matches, inserts into each one it can, and returns
13660    /// the error on the first one it cannot, leaving the earlier inserts in the
13661    /// document. A write here is one list of edits applied together, so either
13662    /// all of them happen or none of them do.
13663    #[test]
13664    fn json_arrinsert_is_all_or_nothing_across_the_matches() {
13665        let mut f = Fixture::new();
13666        let doc = br#"{"a":[1,2,3],"n":{"a":[9,8],"in":{"a":[1]}}}"#;
13667        f.run(&[b"JSON.SET", b"doc", b"$", doc]);
13668        assert_eq!(
13669            f.run(&[b"JSON.ARRINSERT", b"doc", b"$..a", b"-2", b"0"]),
13670            "-ERR index out of bounds\r\n"
13671        );
13672        assert_eq!(
13673            f.run(&[b"JSON.GET", b"doc"]),
13674            bulk(r#"{"a":[1,2,3],"n":{"a":[9,8],"in":{"a":[1]}}}"#)
13675        );
13676        // Every match can take the index, so every match gets it.
13677        assert_eq!(
13678            f.run(&[b"JSON.ARRINSERT", b"doc", b"$..a", b"0", b"0"]),
13679            "*3\r\n:4\r\n:3\r\n:2\r\n"
13680        );
13681        assert_eq!(
13682            f.run(&[b"JSON.GET", b"doc"]),
13683            bulk(r#"{"a":[0,1,2,3],"n":{"a":[0,9,8],"in":{"a":[0,1]}}}"#)
13684        );
13685    }
13686
13687    /// `JSON.ARRINDEX`, whose stop is exclusive and whose start clamps to the
13688    /// last element rather than to one past it.
13689    ///
13690    /// Both of those read like mistakes and both are what RedisJSON does. The
13691    /// start is the one that bites: a start of five into an array of four still
13692    /// looks at the fourth, so a search that should have run out of array comes
13693    /// back with an answer.
13694    #[test]
13695    fn json_arrindex_has_an_exclusive_stop_and_a_start_that_cannot_run_off_the_end() {
13696        let mut f = Fixture::new();
13697        f.run(&[b"JSON.SET", b"doc", b"$", br#"{"a":[1,2,3,1],"n":7}"#]);
13698
13699        assert_eq!(f.run(&[b"JSON.ARRINDEX", b"doc", b".a", b"2"]), ":1\r\n");
13700        assert_eq!(f.run(&[b"JSON.ARRINDEX", b"doc", b".a", b"9"]), ":-1\r\n");
13701        assert_eq!(
13702            f.run(&[b"JSON.ARRINDEX", b"doc", b"$.a", b"2"]),
13703            "*1\r\n:1\r\n"
13704        );
13705
13706        // Zero as the stop means the end rather than the front, so leaving it
13707        // off and passing it are the same thing.
13708        assert_eq!(
13709            f.run(&[b"JSON.ARRINDEX", b"doc", b".a", b"1", b"1", b"0"]),
13710            ":3\r\n"
13711        );
13712        // The stop is exclusive, so a stop of three does not look at index
13713        // three.
13714        assert_eq!(
13715            f.run(&[b"JSON.ARRINDEX", b"doc", b".a", b"1", b"1", b"3"]),
13716            ":-1\r\n"
13717        );
13718
13719        // The start clamps to the last element in both directions, which is why
13720        // a start of four, five or minus one all find the 1 at index three.
13721        for start in [&b"4"[..], &b"5"[..], &b"-1"[..]] {
13722            assert_eq!(
13723                f.run(&[b"JSON.ARRINDEX", b"doc", b".a", b"1", start]),
13724                ":3\r\n",
13725                "{}",
13726                String::from_utf8_lossy(start)
13727            );
13728        }
13729        assert_eq!(
13730            f.run(&[b"JSON.ARRINDEX", b"doc", b".a", b"1", b"-100"]),
13731            ":0\r\n"
13732        );
13733        // An empty array is the one case that comes back with nothing, since
13734        // the stop is zero and the loop never starts.
13735        f.run(&[b"JSON.SET", b"doc", b"$.a", b"[]"]);
13736        assert_eq!(
13737            f.run(&[b"JSON.ARRINDEX", b"doc", b".a", b"1", b"1"]),
13738            ":-1\r\n"
13739        );
13740
13741        // The comparison is structural rather than one of the encoded bytes,
13742        // because an object in a stored document holds its keys as intern table
13743        // ids where one parsed off the wire holds them as bytes.
13744        f.run(&[b"JSON.SET", b"doc", b"$.a", br#"[{"k":1},[1,2],"s"]"#]);
13745        assert_eq!(
13746            f.run(&[b"JSON.ARRINDEX", b"doc", b".a", br#"{"k":1}"#]),
13747            ":0\r\n"
13748        );
13749        assert_eq!(
13750            f.run(&[b"JSON.ARRINDEX", b"doc", b".a", b"[1,2]"]),
13751            ":1\r\n"
13752        );
13753        assert_eq!(
13754            f.run(&[b"JSON.ARRINDEX", b"doc", b".a", b"[2,1]"]),
13755            ":-1\r\n"
13756        );
13757
13758        // Its errors are a third set again: a missing legacy path is the short
13759        // sentence, the wrong kind of value is a WRONGTYPE, and a key that is
13760        // not there is about the path on either syntax.
13761        assert_eq!(
13762            f.run(&[b"JSON.ARRINDEX", b"doc", b".nope", b"1"]),
13763            "-ERR Path does not exist\r\n"
13764        );
13765        assert_eq!(
13766            f.run(&[b"JSON.ARRINDEX", b"doc", b".n", b"1"]),
13767            "-WRONGTYPE wrong type of path value - expected array\r\n"
13768        );
13769        assert_eq!(
13770            f.run(&[b"JSON.ARRINDEX", b"nokey", b".a", b"1"]),
13771            "-ERR Path does not exist\r\n"
13772        );
13773        assert_eq!(
13774            f.run(&[b"JSON.ARRINDEX", b"nokey", b"$.a", b"1"]),
13775            "-ERR Path does not exist\r\n"
13776        );
13777    }
13778
13779    /// The number family answers text and keeps an integer an integer until
13780    /// something in the sum is not one.
13781    #[test]
13782    fn the_json_number_family_answers_json_text_and_keeps_its_integers() {
13783        let mut f = Fixture::new();
13784        let doc = br#"{"i":7,"f":1.5,"neg":-2,"s":"ab"}"#;
13785        f.run(&[b"JSON.SET", b"doc", b"$", doc]);
13786
13787        // A legacy path answers the new value as JSON text in a bulk string,
13788        // not as a number, which is the shape all three of them use.
13789        assert_eq!(
13790            f.run(&[b"JSON.NUMINCRBY", b"doc", b".i", b"2"]),
13791            bulk("9").as_str()
13792        );
13793        // A JSONPath answers a bulk string holding a JSON array.
13794        assert_eq!(
13795            f.run(&[b"JSON.NUMINCRBY", b"doc", b"$.i", b"2"]),
13796            bulk("[11]").as_str()
13797        );
13798        // Two integers stay an integer and a double anywhere in it makes the
13799        // answer a double, which the document then holds.
13800        assert_eq!(
13801            f.run(&[b"JSON.NUMINCRBY", b"doc", b".i", b"2.0"]),
13802            bulk("13.0").as_str()
13803        );
13804        assert_eq!(
13805            f.run(&[b"JSON.TYPE", b"doc", b".i"]),
13806            bulk("number").as_str()
13807        );
13808        assert_eq!(
13809            f.run(&[b"JSON.NUMMULTBY", b"doc", b".f", b"2"]),
13810            bulk("3.0").as_str()
13811        );
13812        assert_eq!(
13813            f.run(&[b"JSON.NUMPOWBY", b"doc", b".neg", b"3"]),
13814            bulk("-8").as_str()
13815        );
13816        // A power of a half is a square root, and the square root of a negative
13817        // number is the error that says the answer is not a number.
13818        f.run(&[b"JSON.SET", b"doc", b"$.f", b"1.5"]);
13819        assert_eq!(
13820            f.run(&[b"JSON.NUMPOWBY", b"doc", b".f", b"0.5"]),
13821            bulk("1.224744871391589").as_str()
13822        );
13823        assert_eq!(
13824            f.run(&[b"JSON.NUMPOWBY", b"doc", b".neg", b"0.5"]),
13825            "-ERR result is not a number\r\n"
13826        );
13827        // An integer answer that does not fit is refused rather than promoted,
13828        // and a negative exponent lands in the same error because there is no
13829        // integer answer to two to the minus one.
13830        f.run(&[b"JSON.SET", b"doc", b"$.big", b"9223372036854775807"]);
13831        assert_eq!(
13832            f.run(&[b"JSON.NUMINCRBY", b"doc", b".big", b"1"]),
13833            "-ERR numeric overflow\r\n"
13834        );
13835        f.run(&[b"JSON.SET", b"doc", b"$.p", b"2"]);
13836        assert_eq!(
13837            f.run(&[b"JSON.NUMPOWBY", b"doc", b".p", b"-1"]),
13838            "-ERR numeric overflow\r\n"
13839        );
13840        // A double that leaves the finite numbers is the other error.
13841        f.run(&[b"JSON.SET", b"doc", b"$.huge", b"1e308"]);
13842        assert_eq!(
13843            f.run(&[b"JSON.NUMMULTBY", b"doc", b".huge", b"1e10"]),
13844            "-ERR result is not a number\r\n"
13845        );
13846
13847        // A match that is not a number is a null inside the array on a
13848        // JSONPath, and a legacy path that found no number at all is the error
13849        // with the module's own typo in it.
13850        assert_eq!(
13851            f.run(&[b"JSON.NUMINCRBY", b"doc", b"$.s", b"1"]),
13852            bulk("[null]").as_str()
13853        );
13854        assert_eq!(
13855            f.run(&[b"JSON.NUMINCRBY", b"doc", b"$.nope", b"1"]),
13856            bulk("[]").as_str()
13857        );
13858        assert_eq!(
13859            f.run(&[b"JSON.NUMINCRBY", b"doc", b".s", b"1"]),
13860            "-ERR Path does not exist or does not contains a number\r\n"
13861        );
13862        assert_eq!(
13863            f.run(&[b"JSON.NUMINCRBY", b"doc", b".nope", b"1"]),
13864            "-ERR Path does not exist or does not contains a number\r\n"
13865        );
13866        // The operand is JSON and has to be a number. Valid JSON that is not
13867        // one is a line of its own, and it goes out without a prefix.
13868        assert_eq!(
13869            f.run(&[b"JSON.NUMINCRBY", b"doc", b".i", b"true"]),
13870            "-bad input number\r\n"
13871        );
13872        assert_eq!(
13873            f.run(&[b"JSON.NUMINCRBY", b"nokey", b".i", b"1"]),
13874            "-ERR could not perform this operation on a key that doesn't exist\r\n"
13875        );
13876        assert_eq!(
13877            f.run(&[b"JSON.NUMINCRBY", b"nokey", b"$.i", b"1"]),
13878            "-ERR could not perform this operation on a key that doesn't exist\r\n"
13879        );
13880    }
13881
13882    /// `JSON.STRAPPEND` puts its path in the middle and makes it optional,
13883    /// which nothing else in the group does.
13884    #[test]
13885    fn json_strappend_reads_its_shape_off_the_argument_count() {
13886        let mut f = Fixture::new();
13887        f.run(&[b"JSON.SET", b"doc", b"$", br#"{"s":"ab","n":1}"#]);
13888
13889        assert_eq!(
13890            f.run(&[b"JSON.STRAPPEND", b"doc", b".s", br#""c""#]),
13891            ":3\r\n"
13892        );
13893        assert_eq!(
13894            f.run(&[b"JSON.STRAPPEND", b"doc", b"$.s", br#""d""#]),
13895            "*1\r\n:4\r\n"
13896        );
13897        // The length is in bytes and not in characters, so one two byte letter
13898        // takes it up by two.
13899        assert_eq!(
13900            f.run(&[b"JSON.STRAPPEND", b"doc", b".s", br#""\u00e9""#]),
13901            ":6\r\n"
13902        );
13903        // Three arguments means the value is the last one and the path is the
13904        // root, so this appends to a document that is a string on its own.
13905        f.run(&[b"JSON.SET", b"str", b"$", br#""ab""#]);
13906        assert_eq!(f.run(&[b"JSON.STRAPPEND", b"str", br#""c""#]), ":3\r\n");
13907        assert_eq!(f.run(&[b"JSON.GET", b"str"]), bulk("\"abc\"").as_str());
13908
13909        // The value is JSON and has to be a JSON string. A number is a
13910        // WRONGTYPE about a path value even though it was the value that was
13911        // wrong, which is the module's wording and not a slip here.
13912        assert_eq!(
13913            f.run(&[b"JSON.STRAPPEND", b"doc", b".s", b"5"]),
13914            "-WRONGTYPE wrong type of path value - expected string\r\n"
13915        );
13916        assert_eq!(
13917            f.run(&[b"JSON.STRAPPEND", b"doc", b"$.n", br#""c""#]),
13918            "*1\r\n$-1\r\n"
13919        );
13920        assert_eq!(
13921            f.run(&[b"JSON.STRAPPEND", b"doc", b".n", br#""c""#]),
13922            "-ERR Path does not exist or not a string\r\n"
13923        );
13924        assert_eq!(
13925            f.run(&[b"JSON.STRAPPEND", b"doc", b"$.nope", br#""c""#]),
13926            "*0\r\n"
13927        );
13928        assert_eq!(
13929            f.run(&[b"JSON.STRAPPEND", b"nokey", br#""c""#]),
13930            "-ERR could not perform this operation on a key that doesn't exist\r\n"
13931        );
13932    }
13933
13934    /// A legacy path can match more than one value, and which of them the one
13935    /// answer comes from is not the same choice twice.
13936    #[test]
13937    fn a_legacy_wildcard_write_touches_every_match_and_answers_only_one() {
13938        let mut f = Fixture::new();
13939        // Three arrays of one, two and three elements, which tells the first
13940        // match and the last match apart in a single command.
13941        let three = br#"{"a":[[7],[7,7],[7,7,7]]}"#;
13942
13943        f.run(&[b"JSON.SET", b"doc", b"$", three]);
13944        assert_eq!(
13945            f.run(&[b"JSON.ARRAPPEND", b"doc", b".a[*]", b"9"]),
13946            ":4\r\n"
13947        );
13948        f.run(&[b"JSON.SET", b"doc", b"$", three]);
13949        assert_eq!(
13950            f.run(&[b"JSON.ARRINSERT", b"doc", b".a[*]", b"0", b"9"]),
13951            ":2\r\n"
13952        );
13953        f.run(&[b"JSON.SET", b"doc", b"$", three]);
13954        assert_eq!(
13955            f.run(&[b"JSON.ARRTRIM", b"doc", b".a[*]", b"0", b"1"]),
13956            ":1\r\n"
13957        );
13958        f.run(&[b"JSON.SET", b"doc", b"$", br#"{"a":[[1,2,3],[4,5,6]]}"#]);
13959        assert_eq!(
13960            f.run(&[b"JSON.ARRPOP", b"doc", b".a[*]", b"0"]),
13961            bulk("1").as_str()
13962        );
13963        f.run(&[b"JSON.SET", b"doc", b"$", br#"{"a":[1,2,3]}"#]);
13964        assert_eq!(
13965            f.run(&[b"JSON.NUMINCRBY", b"doc", b".a[*]", b"10"]),
13966            bulk("13").as_str()
13967        );
13968        f.run(&[b"JSON.SET", b"doc", b"$", br#"{"a":["p","qq","rrr"]}"#]);
13969        assert_eq!(
13970            f.run(&[b"JSON.STRAPPEND", b"doc", b".a[*]", br#""z""#]),
13971            ":4\r\n"
13972        );
13973        f.run(&[b"JSON.SET", b"doc", b"$", br#"{"a":[true,false,true]}"#]);
13974        assert_eq!(
13975            f.run(&[b"JSON.TOGGLE", b"doc", b".a[*]"]),
13976            bulk("false").as_str()
13977        );
13978        // Every one of them wrote to all three matches, whichever one it chose
13979        // to answer about.
13980        assert_eq!(
13981            f.run(&[b"JSON.GET", b"doc", b".a"]),
13982            bulk("[false,true,false]").as_str()
13983        );
13984
13985        // A match of the wrong kind is skipped rather than being the answer, so
13986        // a path that found a string and then two arrays still answers.
13987        f.run(&[b"JSON.SET", b"doc", b"$", br#"{"a":["x",[1],[1,2]]}"#]);
13988        assert_eq!(
13989            f.run(&[b"JSON.ARRAPPEND", b"doc", b".a[*]", b"9"]),
13990            ":3\r\n"
13991        );
13992        assert_eq!(
13993            f.run(&[b"JSON.GET", b"doc", b".a"]),
13994            bulk(r#"["x",[1,9],[1,2,9]]"#).as_str()
13995        );
13996        // Nothing of the right kind anywhere is the error, and that is the only
13997        // case that is.
13998        f.run(&[b"JSON.SET", b"doc", b"$", br#"{"a":["x","y"]}"#]);
13999        assert_eq!(
14000            f.run(&[b"JSON.ARRAPPEND", b"doc", b".a[*]", b"9"]),
14001            "-ERR Path does not exist or not an array\r\n"
14002        );
14003        assert_eq!(
14004            f.run(&[b"JSON.TOGGLE", b"doc", b".a[*]"]),
14005            "-ERR Path does not exist or not a bool\r\n"
14006        );
14007        // The one array that was there and had nothing in it is an answer and
14008        // not a skip, so the pop answers about it rather than about the array
14009        // after it.
14010        f.run(&[b"JSON.SET", b"doc", b"$", br#"{"a":[[],[2,3]]}"#]);
14011        assert_eq!(f.run(&[b"JSON.ARRPOP", b"doc", b".a[*]"]), "$-1\r\n");
14012        assert_eq!(
14013            f.run(&[b"JSON.GET", b"doc", b".a"]),
14014            bulk("[[],[2]]").as_str()
14015        );
14016    }
14017
14018    /// A path that matched a value and something inside that value writes to
14019    /// both, which is what `$..` and a nested wildcard are for.
14020    #[test]
14021    fn a_write_reaches_a_match_that_sits_inside_another_match() {
14022        let mut f = Fixture::new();
14023        let nested = br#"{"a":[{"a":[7]},{"a":[7,7]}]}"#;
14024
14025        f.run(&[b"JSON.SET", b"doc", b"$", nested]);
14026        assert_eq!(
14027            f.run(&[b"JSON.ARRAPPEND", b"doc", b"$..a", b"9"]),
14028            "*3\r\n:3\r\n:2\r\n:3\r\n"
14029        );
14030        assert_eq!(
14031            f.run(&[b"JSON.GET", b"doc", b"$"]),
14032            bulk(r#"[{"a":[{"a":[7,9]},{"a":[7,7,9]},9]}]"#).as_str()
14033        );
14034
14035        // The same for a trim, where the outer array keeps the two elements the
14036        // inner writes landed in.
14037        f.run(&[b"JSON.SET", b"doc", b"$", nested]);
14038        assert_eq!(
14039            f.run(&[b"JSON.ARRTRIM", b"doc", b"$..a", b"0", b"0"]),
14040            "*3\r\n:1\r\n:1\r\n:1\r\n"
14041        );
14042        assert_eq!(
14043            f.run(&[b"JSON.GET", b"doc", b"$"]),
14044            bulk(r#"[{"a":[{"a":[7]}]}]"#).as_str()
14045        );
14046
14047        // And for a number, where the first match is the object the outer array
14048        // holds and only the two inside it are numbers.
14049        f.run(&[b"JSON.SET", b"doc", b"$", nested]);
14050        assert_eq!(
14051            f.run(&[b"JSON.NUMINCRBY", b"doc", b"$..a[0]", b"1"]),
14052            bulk("[null,8,8]").as_str()
14053        );
14054    }
14055
14056    /// The value a write is given is looked at only once the path has found
14057    /// something of the right kind to use it on.
14058    #[test]
14059    fn a_bad_operand_is_not_the_answer_when_the_path_found_nothing_to_use_it_on() {
14060        let mut f = Fixture::new();
14061        f.run(&[b"JSON.SET", b"doc", b"$", br#"{"n":7,"s":"t"}"#]);
14062
14063        // A string is not a number, so the path answers first and the `"x"` is
14064        // never looked at. Same for the value that is not JSON at all.
14065        assert_eq!(
14066            f.run(&[b"JSON.NUMINCRBY", b"doc", b"$.s", br#""x""#]),
14067            bulk("[null]").as_str()
14068        );
14069        assert_eq!(
14070            f.run(&[b"JSON.NUMINCRBY", b"doc", b"$.s", b"notjson"]),
14071            bulk("[null]").as_str()
14072        );
14073        assert_eq!(
14074            f.run(&[b"JSON.NUMINCRBY", b"doc", b"$.missing", b"notjson"]),
14075            bulk("[]").as_str()
14076        );
14077        assert_eq!(
14078            f.run(&[b"JSON.NUMINCRBY", b"doc", b".s", br#""x""#]),
14079            "-ERR Path does not exist or does not contains a number\r\n"
14080        );
14081        // A number match anywhere and the value is looked at after all.
14082        assert_eq!(
14083            f.run(&[b"JSON.NUMINCRBY", b"doc", b"$.n", br#""x""#]),
14084            "-bad input number\r\n"
14085        );
14086
14087        // JSON.STRAPPEND follows the same order with its own two answers.
14088        assert_eq!(
14089            f.run(&[b"JSON.STRAPPEND", b"doc", b"$.n", b"1"]),
14090            "*1\r\n$-1\r\n"
14091        );
14092        assert_eq!(
14093            f.run(&[b"JSON.STRAPPEND", b"doc", b".n", b"1"]),
14094            "-ERR Path does not exist or not a string\r\n"
14095        );
14096        assert_eq!(
14097            f.run(&[b"JSON.STRAPPEND", b"doc", b"$.s", b"1"]),
14098            "-WRONGTYPE wrong type of path value - expected string\r\n"
14099        );
14100
14101        // A key that is not there still comes before either of them.
14102        assert_eq!(
14103            f.run(&[b"JSON.NUMINCRBY", b"nope", b"$.a", br#""x""#]),
14104            "-ERR could not perform this operation on a key that doesn't exist\r\n"
14105        );
14106        assert_eq!(
14107            f.run(&[b"JSON.STRAPPEND", b"nope", b"$.a", b"1"]),
14108            "-ERR could not perform this operation on a key that doesn't exist\r\n"
14109        );
14110    }
14111
14112    /// RFC 7386 in one test: a null deletes, everything else merges, and a
14113    /// patch that is not an object replaces what it lands on.
14114    #[test]
14115    fn a_merge_patch_adds_replaces_and_deletes_in_one_write() {
14116        let mut f = Fixture::new();
14117
14118        // A key that is not there is created at the root, nulls and all,
14119        // because a deletion with nothing to delete is still what the client
14120        // sent.
14121        assert_eq!(
14122            f.run(&[b"JSON.MERGE", b"doc", b"$", br#"{"x":null,"y":1}"#]),
14123            "+OK\r\n"
14124        );
14125        assert_eq!(
14126            f.run(&[b"JSON.GET", b"doc", b"$"]),
14127            bulk(r#"[{"x":null,"y":1}]"#).as_str()
14128        );
14129
14130        // Onto something that is there, a null deletes the member of that name
14131        // and the rest is merged one level at a time.
14132        f.run(&[b"JSON.SET", b"doc", b"$", br#"{"a":{"b":1,"c":2},"d":3}"#]);
14133        assert_eq!(
14134            f.run(&[b"JSON.MERGE", b"doc", b"$", br#"{"a":{"b":null,"e":4}}"#]),
14135            "+OK\r\n"
14136        );
14137        assert_eq!(
14138            f.run(&[b"JSON.GET", b"doc", b"$"]),
14139            bulk(r#"[{"a":{"c":2,"e":4},"d":3}]"#).as_str()
14140        );
14141
14142        // A patch that is not an object replaces what it is merged onto.
14143        assert_eq!(f.run(&[b"JSON.MERGE", b"doc", b"$.a", b"[1,2]"]), "+OK\r\n");
14144        assert_eq!(
14145            f.run(&[b"JSON.GET", b"doc", b"$"]),
14146            bulk(r#"[{"a":[1,2],"d":3}]"#).as_str()
14147        );
14148
14149        // A patch object onto a value that is not an object starts from an
14150        // empty object, so this time the null has nothing to delete and is
14151        // dropped rather than stored.
14152        assert_eq!(
14153            f.run(&[b"JSON.MERGE", b"doc", b"$.d", br#"{"p":null,"q":9}"#]),
14154            "+OK\r\n"
14155        );
14156        assert_eq!(
14157            f.run(&[b"JSON.GET", b"doc", b"$"]),
14158            bulk(r#"[{"a":[1,2],"d":{"q":9}}]"#).as_str()
14159        );
14160
14161        // A member one level past the end of the document is created and keeps
14162        // its nulls, two levels past it is a write that did not happen, and a
14163        // path that would have to invent where it goes is the unprefixed line.
14164        assert_eq!(
14165            f.run(&[b"JSON.MERGE", b"doc", b"$.new", br#"{"z":null}"#]),
14166            "+OK\r\n"
14167        );
14168        assert_eq!(
14169            f.run(&[b"JSON.GET", b"doc", b"$.new"]),
14170            bulk(r#"[{"z":null}]"#).as_str()
14171        );
14172        assert_eq!(
14173            f.run(&[b"JSON.MERGE", b"doc", b"$.no.deep", b"1"]),
14174            "$-1\r\n"
14175        );
14176        assert_eq!(
14177            f.run(&[b"JSON.MERGE", b"doc", b"$.no.*", b"1"]),
14178            "-Err wrong static path\r\n"
14179        );
14180
14181        // A wildcard merges every match.
14182        f.run(&[b"JSON.SET", b"doc", b"$", br#"{"a":{"n":1},"b":{"n":2}}"#]);
14183        assert_eq!(
14184            f.run(&[b"JSON.MERGE", b"doc", b"$.*", br#"{"m":0}"#]),
14185            "+OK\r\n"
14186        );
14187        assert_eq!(
14188            f.run(&[b"JSON.GET", b"doc", b"$"]),
14189            bulk(r#"[{"a":{"m":0,"n":1},"b":{"m":0,"n":2}}]"#).as_str()
14190        );
14191
14192        // The three ways to get it wrong.
14193        assert_eq!(
14194            f.run(&[b"JSON.MERGE", b"doc", b"$", b"{}", b"more"]),
14195            "-ERR syntax error\r\n"
14196        );
14197        assert_eq!(
14198            f.run(&[b"JSON.MERGE", b"gone", b"$.a", b"1"]),
14199            "-ERR new objects must be created at the root\r\n"
14200        );
14201        f.run(&[b"SET", b"str", b"x"]);
14202        assert_eq!(
14203            f.run(&[b"JSON.MERGE", b"str", b"$", b"1"]),
14204            "-Existing key has wrong Redis type\r\n"
14205        );
14206    }
14207
14208    /// A descent is the one path that matches a value and something inside that
14209    /// same value, and the inner merge has to survive the outer one.
14210    #[test]
14211    fn a_merge_down_a_descent_keeps_what_the_inner_match_did() {
14212        let mut f = Fixture::new();
14213        f.run(&[b"JSON.SET", b"doc", b"$", br#"{"a":{"b":1},"c":[2]}"#]);
14214        assert_eq!(
14215            f.run(&[b"JSON.MERGE", b"doc", b"$..*", br#"{"m":1}"#]),
14216            "+OK\r\n"
14217        );
14218        // `a`, `a.b`, `c` and `c[0]` all match. `a.b` is merged first and `a` is
14219        // merged onto the result, so the `{"m":1}` written into `a.b` is still
14220        // there. Doing it the other way round would leave `{"a":{"b":1,"m":1}}`.
14221        assert_eq!(
14222            f.run(&[b"JSON.GET", b"doc", b"$"]),
14223            bulk(r#"[{"a":{"b":{"m":1},"m":1},"c":{"m":1}}]"#).as_str()
14224        );
14225
14226        // A deletion down the same path, which is the case where the inner
14227        // merge empties the object the outer one then copies.
14228        f.run(&[b"JSON.SET", b"doc", b"$", br#"{"a":{"b":1},"c":[2]}"#]);
14229        assert_eq!(
14230            f.run(&[b"JSON.MERGE", b"doc", b"$..*", br#"{"a":null}"#]),
14231            "+OK\r\n"
14232        );
14233        assert_eq!(
14234            f.run(&[b"JSON.GET", b"doc", b"$"]),
14235            bulk(r#"[{"a":{"b":{}},"c":{}}]"#).as_str()
14236        );
14237    }
14238
14239    /// A filter is a selector like any other, so every command that takes a path
14240    /// takes one, reads and writes alike.
14241    #[test]
14242    fn a_filter_path_reads_and_writes_the_members_it_keeps() {
14243        let mut f = Fixture::new();
14244        let doc = br#"{"book":[{"t":"a","p":8},{"t":"b","p":13},{"t":"c","p":9}],"cap":10}"#;
14245        f.run(&[b"JSON.SET", b"doc", b"$", doc]);
14246
14247        assert_eq!(
14248            f.run(&[b"JSON.GET", b"doc", b"$.book[?(@.p < 10)].t"]),
14249            bulk(r#"["a","c"]"#).as_str()
14250        );
14251        // `$` inside the expression is the document, so a member can be measured
14252        // against something that is not inside it.
14253        assert_eq!(
14254            f.run(&[b"JSON.GET", b"doc", b"$.book[?(@.p < $.cap)].t"]),
14255            bulk(r#"["a","c"]"#).as_str()
14256        );
14257        // The legacy syntax takes one too, and answers the first match.
14258        assert_eq!(
14259            f.run(&[b"JSON.GET", b"doc", b"book[?(@.p < 10)].t"]),
14260            bulk(r#""a""#).as_str()
14261        );
14262        assert_eq!(
14263            f.run(&[b"JSON.TYPE", b"doc", b"$.book[?(@.p > 10)]"]),
14264            "*1\r\n$6\r\nobject\r\n"
14265        );
14266
14267        // A write goes through it as far as a value that is already there. A
14268        // field that is not there yet has nowhere definite to go, which is the
14269        // same refusal a wildcard gets.
14270        assert_eq!(
14271            f.run(&[b"JSON.NUMINCRBY", b"doc", b"$.book[?(@.p < 10)].p", b"1"]),
14272            bulk("[9,10]").as_str()
14273        );
14274        assert_eq!(
14275            f.run(&[b"JSON.SET", b"doc", b"$.book[?(@.p == 13)].t", br#""B""#]),
14276            "+OK\r\n"
14277        );
14278        assert_eq!(
14279            f.run(&[b"JSON.SET", b"doc", b"$.book[?(@.p == 13)].n", b"1"]),
14280            "-Err wrong static path\r\n"
14281        );
14282        assert_eq!(
14283            f.run(&[b"JSON.DEL", b"doc", b"$.book[?(@.p > 9)]"]),
14284            ":2\r\n"
14285        );
14286        assert_eq!(
14287            f.run(&[b"JSON.GET", b"doc", b"$"]),
14288            bulk(r#"[{"cap":10,"book":[{"p":9,"t":"a"}]}]"#).as_str()
14289        );
14290
14291        // A path that does not parse is refused before the document is read, so
14292        // a key that is not there answers the same way.
14293        assert!(
14294            f.run(&[b"JSON.GET", b"doc", b"$.book[?(@.p <)]"])
14295                .starts_with("-ERR")
14296        );
14297        assert!(
14298            f.run(&[b"JSON.GET", b"nokey", b"$.book[?(@.p <)]"])
14299                .starts_with("-ERR")
14300        );
14301    }
14302
14303    /// The operators past the comparisons, over the wire rather than in the
14304    /// parser's own tests, so that a client can reach all of them.
14305    #[test]
14306    fn a_filter_takes_the_membership_operators_and_the_methods_too() {
14307        let mut f = Fixture::new();
14308        let doc = br#"{"box":[{"t":"a","n":[1,2],"g":"x"},{"t":"b","n":[9],"g":"y"}]}"#;
14309        f.run(&[b"JSON.SET", b"doc", b"$", doc]);
14310
14311        for (path, want) in [
14312            (&b"$.box[?(@.g in [\"x\"])].t"[..], r#"["a"]"#),
14313            (b"$.box[?(@.g nin [\"x\"])].t", r#"["b"]"#),
14314            (b"$.box[?(@.n anyof [2,3])].t", r#"["a"]"#),
14315            (b"$.box[?(@.n subsetof [1,2,3])].t", r#"["a"]"#),
14316            (b"$.box[?(@.n size 2)].t", r#"["a"]"#),
14317            (b"$.box[?(@.n empty false)].t", r#"["a","b"]"#),
14318            (b"$.box[?(@.n.length() == 1)].t", r#"["b"]"#),
14319            (b"$.box[?(@.n.sum() > 5)].t", r#"["b"]"#),
14320            (b"$.box[?(@.n[0] + 1 == 2)].t", r#"["a"]"#),
14321            (b"$.box[?(@~ size 3)].t", r#"["a","b"]"#),
14322            (b"$.box[?(@.n~)].t", "[]"),
14323            (b"$.box[?(@.n sizeof 2)].t", r#"["a"]"#),
14324            (b"$.box[?(-@.n[0] == -9)].t", r#"["b"]"#),
14325            (b"$.box[?(1 in @.n)].t", r#"["a"]"#),
14326            (b"$.box[?(\"g\" in @~)].t", r#"["a","b"]"#),
14327        ] {
14328            assert_eq!(f.run(&[b"JSON.GET", b"doc", path]), bulk(want).as_str());
14329        }
14330
14331        // A write goes through one of these the same way it goes through a
14332        // comparison.
14333        assert_eq!(
14334            f.run(&[b"JSON.SET", b"doc", b"$.box[?(@.n size 1)].g", br#""z""#]),
14335            "+OK\r\n"
14336        );
14337        assert_eq!(
14338            f.run(&[b"JSON.GET", b"doc", b"$.box[?(@.g == \"z\")].t"]),
14339            bulk(r#"["b"]"#).as_str()
14340        );
14341    }
14342
14343    /// D-41. RedisJSON refuses this one, and which document it refuses is
14344    /// decided by how it happens to hold an array of numbers.
14345    #[test]
14346    fn a_merge_onto_a_number_inside_an_array_is_a_merge_and_not_an_error() {
14347        let mut f = Fixture::new();
14348        f.run(&[b"JSON.SET", b"doc", b"$", br#"{"a":[1,2]}"#]);
14349        assert_eq!(
14350            f.run(&[b"JSON.MERGE", b"doc", b"$.a[0]", br#"{"x":1}"#]),
14351            "+OK\r\n"
14352        );
14353        assert_eq!(
14354            f.run(&[b"JSON.GET", b"doc", b"$"]),
14355            bulk(r#"[{"a":[{"x":1},2]}]"#).as_str()
14356        );
14357        // The same document with one element that is not an integer is the one
14358        // RedisJSON is happy with, and it goes the same way here.
14359        f.run(&[b"JSON.SET", b"doc", b"$", br#"{"a":[1,"s"]}"#]);
14360        assert_eq!(
14361            f.run(&[b"JSON.MERGE", b"doc", b"$.a[0]", br#"{"x":1}"#]),
14362            "+OK\r\n"
14363        );
14364        assert_eq!(
14365            f.run(&[b"JSON.GET", b"doc", b"$"]),
14366            bulk(r#"[{"a":[{"x":1},"s"]}]"#).as_str()
14367        );
14368    }
14369
14370    /// `JSON.MSET` checks what it can before it writes anything and skips the
14371    /// one thing it cannot, which is a path with nowhere to put its value.
14372    #[test]
14373    fn an_mset_writes_every_triple_it_can_and_checks_the_rest_up_front() {
14374        let mut f = Fixture::new();
14375        assert_eq!(
14376            f.run(&[b"JSON.MSET", b"a", b"$", b"1", b"b", b"$", b"2"]),
14377            "+OK\r\n"
14378        );
14379        assert_eq!(f.run(&[b"JSON.GET", b"a", b"$"]), bulk("[1]").as_str());
14380        assert_eq!(f.run(&[b"JSON.GET", b"b", b"$"]), bulk("[2]").as_str());
14381
14382        // A repeated key takes the last write.
14383        assert_eq!(
14384            f.run(&[b"JSON.MSET", b"a", b"$", b"3", b"a", b"$", b"4"]),
14385            "+OK\r\n"
14386        );
14387        assert_eq!(f.run(&[b"JSON.GET", b"a", b"$"]), bulk("[4]").as_str());
14388
14389        // A triple whose path names nowhere is skipped, the others are still
14390        // written and the reply turns into a nil. Both ways round, because a
14391        // loop that gave up at the first skip would agree with this on one
14392        // order and not on the other.
14393        f.run(&[b"JSON.SET", b"a", b"$", br#"{"n":1}"#]);
14394        assert_eq!(
14395            f.run(&[b"JSON.MSET", b"a", b"$.no.deep", b"9", b"b", b"$", b"5"]),
14396            "$-1\r\n"
14397        );
14398        assert_eq!(f.run(&[b"JSON.GET", b"b", b"$"]), bulk("[5]").as_str());
14399        assert_eq!(
14400            f.run(&[b"JSON.MSET", b"b", b"$", b"6", b"a", b"$.no.deep", b"9"]),
14401            "$-1\r\n"
14402        );
14403        assert_eq!(f.run(&[b"JSON.GET", b"b", b"$"]), bulk("[6]").as_str());
14404
14405        // A value that is not JSON, a key holding something else and a path
14406        // that would have to create a document below its own root are all
14407        // checked before anything is written, so the good triple next to them
14408        // does not happen either.
14409        f.run(&[b"SET", b"str", b"x"]);
14410        assert_eq!(
14411            f.run(&[b"JSON.MSET", b"a", b"$.n", b"7", b"b", b"$", b"notjson"]),
14412            "-this is not the start of a value, at byte 0 of the JSON text\r\n"
14413        );
14414        assert_eq!(
14415            f.run(&[b"JSON.MSET", b"a", b"$.n", b"7", b"str", b"$", b"1"]),
14416            "-Existing key has wrong Redis type\r\n"
14417        );
14418        assert_eq!(
14419            f.run(&[b"JSON.MSET", b"a", b"$.n", b"7", b"gone", b"$.x", b"1"]),
14420            "-ERR new objects must be created at the root\r\n"
14421        );
14422        assert_eq!(f.run(&[b"JSON.GET", b"a", b"$.n"]), bulk("[1]").as_str());
14423
14424        // The two errors a path can be are checked up front as well, so the
14425        // triple before them is not written either. A wildcard that matched
14426        // nothing has nowhere to invent, and an index that is not in the array
14427        // is out of range, and both of them stop the whole command.
14428        assert_eq!(
14429            f.run(&[b"JSON.MSET", b"b", b"$", b"8", b"a", b"$.no.*", b"9"]),
14430            "-Err wrong static path\r\n"
14431        );
14432        assert_eq!(
14433            f.run(&[b"JSON.MSET", b"b", b"$", b"8", b"a", b"$[0]", b"9"]),
14434            "-ERR array index out of range\r\n"
14435        );
14436        assert_eq!(f.run(&[b"JSON.GET", b"b", b"$"]), bulk("[6]").as_str());
14437
14438        // Every triple is worked out against the keyspace as the command found
14439        // it, so a second triple on the same key does not see the first one and
14440        // the last write is the one that stays.
14441        f.run(&[b"JSON.SET", b"c", b"$", br#"{"n":1}"#]);
14442        assert_eq!(
14443            f.run(&[b"JSON.MSET", b"c", b"$", br#"{"n":2}"#, b"c", b"$.n", b"3"]),
14444            "+OK\r\n"
14445        );
14446        assert_eq!(
14447            f.run(&[b"JSON.GET", b"c", b"$"]),
14448            bulk(r#"[{"n":3}]"#).as_str()
14449        );
14450
14451        // An argument count that is not a run of key, path and value is the
14452        // arity error rather than a syntax one.
14453        assert_eq!(
14454            f.run(&[b"JSON.MSET", b"a", b"$", b"1", b"b"]),
14455            "-ERR wrong number of arguments for 'json.mset' command\r\n"
14456        );
14457    }
14458
14459    /// `JSON.RESP` hands back RESP types, and the marker element is what tells
14460    /// an empty array and an empty object apart.
14461    #[test]
14462    fn json_resp_answers_the_document_as_resp_types() {
14463        let mut f = Fixture::new();
14464        f.run(&[b"JSON.SET", b"doc", b"$", br#"{"a":1,"b":[2,"c"]}"#]);
14465        assert_eq!(
14466            f.run(&[b"JSON.RESP", b"doc"]),
14467            "*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"
14468        );
14469        // A JSONPath wraps the same answer in one more array.
14470        assert_eq!(
14471            f.run(&[b"JSON.RESP", b"doc", b"$.b"]),
14472            "*1\r\n*3\r\n+[\r\n:2\r\n$1\r\nc\r\n"
14473        );
14474
14475        f.run(&[
14476            b"JSON.SET",
14477            b"doc",
14478            b"$",
14479            br#"{"f":2.5,"t":true,"z":null,"e":[],"o":{}}"#,
14480        ]);
14481        assert_eq!(f.run(&[b"JSON.RESP", b"doc", b".e"]), "*1\r\n+[\r\n");
14482        assert_eq!(f.run(&[b"JSON.RESP", b"doc", b".o"]), "*1\r\n+{\r\n");
14483        // A double goes out as its text, so a client reads the same digits
14484        // `JSON.GET` would have given it.
14485        assert_eq!(f.run(&[b"JSON.RESP", b"doc", b".f"]), bulk("2.5").as_str());
14486        assert_eq!(f.run(&[b"JSON.RESP", b"doc", b".t"]), "+true\r\n");
14487        assert_eq!(f.run(&[b"JSON.RESP", b"doc", b".z"]), "$-1\r\n");
14488
14489        // A missing legacy path is an error, a missing JSONPath is an empty
14490        // array, and a key that is not there is a nil on either.
14491        assert_eq!(
14492            f.run(&[b"JSON.RESP", b"doc", b".nope"]),
14493            "-ERR Path does not exist\r\n"
14494        );
14495        assert_eq!(f.run(&[b"JSON.RESP", b"doc", b"$.nope"]), "*0\r\n");
14496        assert_eq!(f.run(&[b"JSON.RESP", b"gone"]), "$-1\r\n");
14497        assert_eq!(f.run(&[b"JSON.RESP", b"gone", b"$"]), "$-1\r\n");
14498    }
14499
14500    /// `JSON.DEBUG` answers a byte count that is this encoding's, so the test
14501    /// pins the shapes and that the two syntaxes agree rather than a number
14502    /// read off another server. That is D-42.
14503    #[test]
14504    fn json_debug_answers_a_byte_count_and_its_own_help() {
14505        let mut f = Fixture::new();
14506        f.run(&[b"JSON.SET", b"doc", b"$", br#"{"a":[1,2],"s":"hello"}"#]);
14507        let one = f.run(&[b"JSON.DEBUG", b"MEMORY", b"doc", b".s"]);
14508        assert!(one.starts_with(':'), "{one}");
14509        assert_eq!(
14510            f.run(&[b"JSON.DEBUG", b"memory", b"doc", b"$.s"]),
14511            format!("*1\r\n{one}")
14512        );
14513        let whole = f.run(&[b"JSON.DEBUG", b"MEMORY", b"doc"]);
14514        assert!(whole.starts_with(':') && whole.len() > one.len(), "{whole}");
14515
14516        // A key that is not there is a zero on a legacy path and an empty set
14517        // on a JSONPath, which is the one reader here that does not answer nil
14518        // for it.
14519        assert_eq!(f.run(&[b"JSON.DEBUG", b"MEMORY", b"gone"]), ":0\r\n");
14520        assert_eq!(f.run(&[b"JSON.DEBUG", b"MEMORY", b"gone", b"$"]), "*0\r\n");
14521        assert_eq!(
14522            f.run(&[b"JSON.DEBUG", b"MEMORY", b"doc", b".nope"]),
14523            "-ERR Path does not exist\r\n"
14524        );
14525        assert_eq!(
14526            f.run(&[b"JSON.DEBUG", b"MEMORY", b"doc", b"$.nope"]),
14527            "*0\r\n"
14528        );
14529
14530        assert_eq!(
14531            f.run(&[b"JSON.DEBUG", b"HELP"]),
14532            "*2\r\n$42\r\nMEMORY <key> [path] - reports memory usage\r\n\
14533             $34\r\nHELP                - this message\r\n"
14534        );
14535        assert_eq!(
14536            f.run(&[b"JSON.DEBUG", b"NOPE"]),
14537            "-ERR unknown subcommand - try `JSON.DEBUG HELP`\r\n"
14538        );
14539        assert_eq!(
14540            f.run(&[b"JSON.DEBUG", b"MEMORY"]),
14541            "-ERR wrong number of arguments for 'json.debug' command\r\n"
14542        );
14543    }
14544
14545    // ---------------------------------------------------------------- vector
14546
14547    /// The first `VADD` fixes the dimension and every one after it has to
14548    /// agree, because there is no create command to say it earlier.
14549    #[test]
14550    fn the_first_vadd_decides_how_wide_the_set_is() {
14551        let mut f = Fixture::new();
14552        assert_eq!(
14553            f.run(&[b"VADD", b"v", b"VALUES", b"2", b"1", b"0", b"east"]),
14554            ":1\r\n"
14555        );
14556        assert_eq!(f.run(&[b"VDIM", b"v"]), ":2\r\n");
14557        assert_eq!(f.run(&[b"VCARD", b"v"]), ":1\r\n");
14558        // A second vector under the same name replaces it and says so with a
14559        // zero, so an ingest can count what it created.
14560        assert_eq!(
14561            f.run(&[b"VADD", b"v", b"VALUES", b"2", b"0", b"1", b"east"]),
14562            ":0\r\n"
14563        );
14564        assert_eq!(f.run(&[b"VCARD", b"v"]), ":1\r\n");
14565        // Three dimensions into a two dimensional set names both numbers, since
14566        // a client that gets this wrong needs to know which end is which.
14567        assert_eq!(
14568            f.run(&[b"VADD", b"v", b"VALUES", b"3", b"1", b"0", b"0", b"up"]),
14569            "-ERR Vector dimension mismatch - got 3 but set has 2\r\n"
14570        );
14571        // A vector of zeros has no direction, and it is taken anyway and comes
14572        // back as the origin, because that is what a real server does with it.
14573        assert_eq!(
14574            f.run(&[b"VADD", b"v", b"VALUES", b"2", b"0", b"0", b"nowhere"]),
14575            ":1\r\n"
14576        );
14577        assert_eq!(
14578            f.run(&[b"VEMB", b"v", b"nowhere"]),
14579            "*2\r\n$1\r\n0\r\n$1\r\n0\r\n"
14580        );
14581        // A set is made with one quantisation and keeps it, and a `VADD` that
14582        // names another is refused. Naming none names `Q8`, which is why this
14583        // set is a `Q8` one.
14584        assert_eq!(
14585            f.run(&[b"VADD", b"v", b"VALUES", b"2", b"1", b"1", b"other", b"BIN"]),
14586            "-ERR asked quantization mismatch with existing vector set\r\n"
14587        );
14588        // Nothing above created a key, and a set that never took a vector has
14589        // no dimension to report.
14590        assert_eq!(f.run(&[b"EXISTS", b"fresh"]), ":0\r\n");
14591        assert_eq!(f.run(&[b"VDIM", b"fresh"]), "-ERR key does not exist\r\n");
14592        assert_eq!(f.run(&[b"VCARD", b"fresh"]), ":0\r\n");
14593    }
14594
14595    /// What a client sent comes back out, and what a client asked for is a
14596    /// similarity and not the distance underneath it.
14597    #[test]
14598    fn vemb_gives_back_the_vector_and_vsim_gives_back_a_similarity() {
14599        let mut f = Fixture::new();
14600        f.run(&[b"VADD", b"v", b"VALUES", b"2", b"3", b"4", b"a"]);
14601        // The set stored the direction and the length is multiplied back on the
14602        // way out, so this is `3 4` and not `0.6 0.8`. It is not quite `3 4`
14603        // either, because nobody named a quantisation and that means `Q8`: the
14604        // wider coordinate lands on a code exactly and the other one does not.
14605        // Both numbers are a real server's answers for the same input.
14606        assert_eq!(
14607            f.run(&[b"VEMB", b"v", b"a"]),
14608            "*2\r\n$17\r\n2.992125988006592\r\n$1\r\n4\r\n"
14609        );
14610        // NOQUANT is the way to ask for what went in to come back out.
14611        f.run(&[b"VADD", b"n", b"VALUES", b"2", b"3", b"4", b"a", b"NOQUANT"]);
14612        assert_eq!(
14613            f.run(&[b"VEMB", b"n", b"a"]),
14614            "*2\r\n$1\r\n3\r\n$1\r\n4\r\n"
14615        );
14616        // BIN keeps the signs and nothing else, and does not multiply the
14617        // length back on, since a sign has no length in it to scale.
14618        f.run(&[b"VADD", b"b", b"VALUES", b"2", b"3", b"-4", b"a", b"BIN"]);
14619        assert_eq!(
14620            f.run(&[b"VEMB", b"b", b"a"]),
14621            "*2\r\n$1\r\n1\r\n$2\r\n-1\r\n"
14622        );
14623        assert_eq!(f.run(&[b"VEMB", b"v", b"nobody"]), "*-1\r\n");
14624        assert_eq!(f.run(&[b"VEMB", b"nokey", b"a"]), "*-1\r\n");
14625
14626        // On the axes, where the unit vector is exact and so is the dot
14627        // product, both ends of the scale come out exact: the same direction is
14628        // 1 and the opposite one is 0, with a right angle at a half.
14629        let mut f = Fixture::new();
14630        f.run(&[b"VADD", b"v", b"VALUES", b"2", b"3", b"0", b"a"]);
14631        f.run(&[b"VADD", b"v", b"VALUES", b"2", b"-1", b"0", b"opposite"]);
14632        f.run(&[b"VADD", b"v", b"VALUES", b"2", b"0", b"7", b"across"]);
14633        assert_eq!(
14634            f.run(&[b"VSIM", b"v", b"VALUES", b"2", b"2", b"0", b"WITHSCORES"]),
14635            "*6\r\n$1\r\na\r\n$1\r\n1\r\n$6\r\nacross\r\n$3\r\n0.5\r\n\
14636             $8\r\nopposite\r\n$1\r\n0\r\n"
14637        );
14638        // A search from an element leaves that element out, since it is always
14639        // its own nearest neighbour.
14640        assert_eq!(
14641            f.run(&[b"VSIM", b"v", b"ELE", b"a"]),
14642            "*2\r\n$6\r\nacross\r\n$8\r\nopposite\r\n"
14643        );
14644        // An element that is not there is an empty answer and not an error,
14645        // which is what a missing key gives too.
14646        assert_eq!(f.run(&[b"VSIM", b"v", b"ELE", b"nobody"]), "*0\r\n");
14647        assert_eq!(f.run(&[b"VSIM", b"nokey", b"ELE", b"a"]), "*0\r\n");
14648        // COUNT bounds it and TRUTH reads every vector rather than the codes,
14649        // which has to agree with the index on a set this small.
14650        assert_eq!(
14651            f.run(&[b"VSIM", b"v", b"ELE", b"a", b"COUNT", b"1"]),
14652            "*1\r\n$6\r\nacross\r\n"
14653        );
14654        assert_eq!(
14655            f.run(&[b"VSIM", b"v", b"ELE", b"a", b"TRUTH"]),
14656            "*2\r\n$6\r\nacross\r\n$8\r\nopposite\r\n"
14657        );
14658        // EF widens how much of the index is read and does not change how many
14659        // answers come back, so a wide search still returns what COUNT asked
14660        // for.
14661        assert_eq!(
14662            f.run(&[b"VSIM", b"v", b"ELE", b"a", b"COUNT", b"1", b"EF", b"500"]),
14663            "*1\r\n$6\r\nacross\r\n"
14664        );
14665
14666        // On RESP3 a scored search is a map, which is what the vector set
14667        // module replies and is not what ZRANGE does here.
14668        let mut g = Fixture::new();
14669        g.run(&[b"HELLO", b"3"]);
14670        g.run(&[b"VADD", b"v", b"VALUES", b"2", b"1", b"0", b"east"]);
14671        assert_eq!(
14672            g.run(&[b"VSIM", b"v", b"VALUES", b"2", b"1", b"0", b"WITHSCORES"]),
14673            "%1\r\n$4\r\neast\r\n,1\r\n"
14674        );
14675    }
14676
14677    /// The attribute pair, and the one reply that means two things.
14678    #[test]
14679    fn an_attribute_is_bytes_and_an_empty_one_takes_it_off() {
14680        let mut f = Fixture::new();
14681        f.run(&[b"VADD", b"v", b"VALUES", b"2", b"1", b"0", b"east"]);
14682        assert_eq!(f.run(&[b"VGETATTR", b"v", b"east"]), "$-1\r\n");
14683        assert_eq!(f.run(&[b"VSETATTR", b"v", b"east", b"{\"k\":1}"]), ":1\r\n");
14684        assert_eq!(f.run(&[b"VGETATTR", b"v", b"east"]), "$7\r\n{\"k\":1}\r\n");
14685        // Not parsed as JSON, because nothing reads into it yet and refusing a
14686        // write for a rule nothing enforces would be the wrong trade.
14687        assert_eq!(f.run(&[b"VSETATTR", b"v", b"east", b"not json"]), ":1\r\n");
14688        assert_eq!(f.run(&[b"VGETATTR", b"v", b"east"]), "$8\r\nnot json\r\n");
14689        // An empty string clears it, which is Redis's spelling of the removal.
14690        assert_eq!(f.run(&[b"VSETATTR", b"v", b"east", b""]), ":1\r\n");
14691        assert_eq!(f.run(&[b"VGETATTR", b"v", b"east"]), "$-1\r\n");
14692        // An element that is not there answers zero rather than being created,
14693        // since an attribute with no vector under it is not a thing this holds.
14694        assert_eq!(f.run(&[b"VSETATTR", b"v", b"nobody", b"{}"]), ":0\r\n");
14695        assert_eq!(f.run(&[b"VSETATTR", b"nokey", b"east", b"{}"]), ":0\r\n");
14696        assert_eq!(f.run(&[b"EXISTS", b"nokey"]), ":0\r\n");
14697        // A null for an element with no attribute and a null for one that is
14698        // not there. VISMEMBER is how a client tells the two apart.
14699        assert_eq!(f.run(&[b"VGETATTR", b"v", b"nobody"]), "$-1\r\n");
14700        assert_eq!(f.run(&[b"VISMEMBER", b"v", b"east"]), ":1\r\n");
14701        assert_eq!(f.run(&[b"VISMEMBER", b"v", b"nobody"]), ":0\r\n");
14702        assert_eq!(f.run(&[b"VISMEMBER", b"nokey", b"east"]), ":0\r\n");
14703
14704        // WITHATTRIBS carries it alongside the answers.
14705        f.run(&[b"VSETATTR", b"v", b"east", b"{\"k\":1}"]);
14706        f.run(&[b"VADD", b"v", b"VALUES", b"2", b"0", b"1", b"north"]);
14707        assert_eq!(
14708            f.run(&[b"VSIM", b"v", b"VALUES", b"2", b"1", b"0", b"WITHATTRIBS"]),
14709            "*4\r\n$4\r\neast\r\n$7\r\n{\"k\":1}\r\n$5\r\nnorth\r\n$-1\r\n"
14710        );
14711    }
14712
14713    /// The slot a removed element had is reused, and nothing that was beside it
14714    /// comes back with the next element to get it.
14715    #[test]
14716    fn vrem_takes_the_attribute_with_it() {
14717        let mut f = Fixture::new();
14718        f.run(&[b"VADD", b"v", b"VALUES", b"2", b"1", b"0", b"east"]);
14719        f.run(&[b"VSETATTR", b"v", b"east", b"{\"k\":1}"]);
14720        assert_eq!(f.run(&[b"VREM", b"v", b"east"]), ":1\r\n");
14721        assert_eq!(f.run(&[b"VREM", b"v", b"east"]), ":0\r\n");
14722        assert_eq!(f.run(&[b"VREM", b"nokey", b"east"]), ":0\r\n");
14723        // The key went with the last element, the way every other collection
14724        // here works.
14725        assert_eq!(f.run(&[b"EXISTS", b"v"]), ":0\r\n");
14726
14727        // The next element is given the slot the removed one had, and it comes
14728        // with no attribute on it.
14729        f.run(&[b"VADD", b"v", b"VALUES", b"2", b"1", b"0", b"east"]);
14730        f.run(&[b"VSETATTR", b"v", b"east", b"{\"k\":1}"]);
14731        f.run(&[b"VADD", b"v", b"VALUES", b"2", b"0", b"1", b"north"]);
14732        f.run(&[b"VREM", b"v", b"east"]);
14733        f.run(&[b"VADD", b"v", b"VALUES", b"2", b"1", b"1", b"between"]);
14734        assert_eq!(f.run(&[b"VGETATTR", b"v", b"between"]), "$-1\r\n");
14735    }
14736
14737    /// `VINFO` says what the index is before it says anything a client could
14738    /// mistake for a graph.
14739    #[test]
14740    fn vinfo_says_partition_first() {
14741        let mut f = Fixture::new();
14742        f.run(&[
14743            b"VADD", b"v", b"VALUES", b"2", b"1", b"0", b"east", b"M", b"32",
14744        ]);
14745        f.run(&[b"VSETATTR", b"v", b"east", b"{}"]);
14746        let info = f.run(&[b"VINFO", b"v"]);
14747        assert!(info.starts_with("*24\r\n$10\r\nindex-type\r\n$9\r\npartition\r\n"));
14748        // What the client asked for and not what happened to the tuning, which
14749        // is `10` section 7: M is recorded and changes nothing.
14750        assert!(info.contains("$6\r\nhnsw-m\r\n:32\r\n"), "{info}");
14751        assert!(info.contains("$10\r\nvector-dim\r\n:2\r\n"), "{info}");
14752        assert!(info.contains("$16\r\nattributes-count\r\n:1\r\n"), "{info}");
14753        // Nobody named a quantisation, so this set is a `Q8` one and every
14754        // element in it is stored that way.
14755        assert!(
14756            info.contains("$10\r\nquant-type\r\n$4\r\nint8\r\n"),
14757            "{info}"
14758        );
14759        let mut f = Fixture::new();
14760        f.run(&[b"VADD", b"v", b"VALUES", b"2", b"0", b"1", b"north", b"BIN"]);
14761        assert!(
14762            f.run(&[b"VINFO", b"v"])
14763                .contains("$10\r\nquant-type\r\n$3\r\nbin\r\n")
14764        );
14765        assert_eq!(f.run(&[b"VINFO", b"nokey"]), "$-1\r\n");
14766    }
14767
14768    /// A set to read ranges of names out of.
14769    fn named() -> Fixture {
14770        let mut f = Fixture::new();
14771        for (i, name) in ["alpha", "beta", "gamma", "delta", "epsilon"]
14772            .iter()
14773            .enumerate()
14774        {
14775            let x = (i + 1).to_string();
14776            f.run(&[
14777                b"VADD",
14778                b"r",
14779                b"VALUES",
14780                b"2",
14781                x.as_bytes(),
14782                b"1",
14783                name.as_bytes(),
14784            ]);
14785        }
14786        f
14787    }
14788
14789    /// `VRANGE` reads the names in the order bytes come in and pays no
14790    /// attention to where the vectors point.
14791    #[test]
14792    fn vrange_walks_the_names_and_not_the_vectors() {
14793        let mut f = named();
14794        assert_eq!(
14795            f.run(&[b"VRANGE", b"r", b"-", b"+"]),
14796            "*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"
14797        );
14798        assert_eq!(
14799            f.run(&[b"VRANGE", b"r", b"[a", b"[d"]),
14800            "*2\r\n$5\r\nalpha\r\n$4\r\nbeta\r\n",
14801            "the high end is a name and not a prefix, so delta is past it"
14802        );
14803        assert_eq!(
14804            f.run(&[b"VRANGE", b"r", b"(alpha", b"(gamma"]),
14805            "*3\r\n$4\r\nbeta\r\n$5\r\ndelta\r\n$7\r\nepsilon\r\n"
14806        );
14807        assert_eq!(
14808            f.run(&[b"VRANGE", b"r", b"[beta", b"[beta"]),
14809            "*1\r\n$4\r\nbeta\r\n"
14810        );
14811        assert_eq!(f.run(&[b"VRANGE", b"r", b"[z", b"+"]), "*0\r\n");
14812        // Bytes and not letters, so an upper case name sorts before every lower
14813        // case one rather than beside its own spelling.
14814        f.run(&[b"VADD", b"r", b"VALUES", b"2", b"1", b"1", b"Beta"]);
14815        assert_eq!(
14816            f.run(&[b"VRANGE", b"r", b"-", b"[beta"]),
14817            "*3\r\n$4\r\nBeta\r\n$5\r\nalpha\r\n$4\r\nbeta\r\n"
14818        );
14819        assert_eq!(f.run(&[b"VRANGE", b"nokey", b"-", b"+"]), "*0\r\n");
14820    }
14821
14822    /// The count cuts the answer after the range is decided, and zero is not
14823    /// the same as leaving it out.
14824    #[test]
14825    fn a_vrange_count_of_zero_asks_for_nothing() {
14826        let mut f = named();
14827        assert_eq!(
14828            f.run(&[b"VRANGE", b"r", b"-", b"+", b"2"]),
14829            "*2\r\n$5\r\nalpha\r\n$4\r\nbeta\r\n"
14830        );
14831        assert_eq!(f.run(&[b"VRANGE", b"r", b"-", b"+", b"0"]), "*0\r\n");
14832        assert!(
14833            f.run(&[b"VRANGE", b"r", b"-", b"+", b"-1"])
14834                .starts_with("*5\r\n"),
14835            "a negative count is no limit at all"
14836        );
14837    }
14838
14839    /// Both ends are read before either is placed, and the count is read before
14840    /// either end.
14841    #[test]
14842    fn vrange_says_which_end_it_could_not_read() {
14843        let mut f = named();
14844        assert_eq!(
14845            f.run(&[b"VRANGE", b"r", b"x", b"y"]),
14846            "-ERR invalid start range format\r\n"
14847        );
14848        assert_eq!(
14849            f.run(&[b"VRANGE", b"r", b"+", b"x"]),
14850            "-ERR invalid end range format\r\n",
14851            "the high end is spelled wrong, which is worth saying before the \
14852             low end being on the wrong side"
14853        );
14854        assert_eq!(
14855            f.run(&[b"VRANGE", b"r", b"+", b"-"]),
14856            "-ERR '-' can only be used as first argument, '+' only as second\r\n"
14857        );
14858        // A bracket with nothing after it is not the empty name here, though an
14859        // element really can be called that.
14860        assert_eq!(
14861            f.run(&[b"VRANGE", b"r", b"[", b"+"]),
14862            "-ERR invalid start range format\r\n"
14863        );
14864        assert_eq!(
14865            f.run(&[b"VRANGE", b"r", b"x", b"+", b"z"]),
14866            "-ERR invalid COUNT value\r\n"
14867        );
14868        assert_eq!(
14869            f.run(&[b"VRANGE", b"r", b"-", b"+", b"2", b"extra"]),
14870            "-ERR wrong number of arguments for 'VRANGE' command\r\n"
14871        );
14872        f.run(&[b"SET", b"s", b"x"]);
14873        assert!(
14874            f.run(&[b"VRANGE", b"s", b"-", b"+"])
14875                .starts_with("-WRONGTYPE")
14876        );
14877    }
14878
14879    /// The option that asks for something this index does not have says so
14880    /// rather than doing something else quietly.
14881    #[test]
14882    fn reduce_is_refused_and_not_ignored() {
14883        let mut f = Fixture::new();
14884        let reduce = f.run(&[
14885            b"VADD", b"v", b"REDUCE", b"1", b"VALUES", b"2", b"1", b"0", b"east",
14886        ]);
14887        assert!(
14888            reduce.starts_with("-ERR REDUCE is not supported."),
14889            "{reduce}"
14890        );
14891        assert_eq!(f.run(&[b"EXISTS", b"v"]), ":0\r\n");
14892    }
14893
14894    /// A filtered search answers with the nearest elements that match, and an
14895    /// expression that is not one is an error before the key is looked at.
14896    #[test]
14897    fn vsim_filter_reads_the_attributes() {
14898        let mut f = Fixture::new();
14899        for (name, x, y, attr) in [
14900            ("a", "1", "0", r#"{"lang":"en","year":1999}"#),
14901            ("b", "9", "1", r#"{"lang":"fr","year":2005}"#),
14902            ("c", "8", "2", r#"{"lang":"en","year":1970}"#),
14903            ("d", "7", "3", r#"{"lang":"en","year":2020}"#),
14904        ] {
14905            f.run(&[
14906                b"VADD",
14907                b"v",
14908                b"VALUES",
14909                b"2",
14910                x.as_bytes(),
14911                y.as_bytes(),
14912                name.as_bytes(),
14913                b"SETATTR",
14914                attr.as_bytes(),
14915            ]);
14916        }
14917        // `b` is the nearest to the query and is the one the filter drops, so
14918        // this is the answer a filter applied afterwards would have got wrong.
14919        assert_eq!(
14920            f.run(&[
14921                b"VSIM",
14922                b"v",
14923                b"VALUES",
14924                b"2",
14925                b"9",
14926                b"1",
14927                b"COUNT",
14928                b"2",
14929                b"FILTER",
14930                b".lang == \"en\"",
14931            ]),
14932            "*2\r\n$1\r\na\r\n$1\r\nc\r\n"
14933        );
14934        // A number is compared as a number, and the two halves of an `and` both
14935        // have to hold.
14936        assert_eq!(
14937            f.run(&[
14938                b"VSIM",
14939                b"v",
14940                b"VALUES",
14941                b"2",
14942                b"9",
14943                b"1",
14944                b"FILTER",
14945                b".lang == 'en' and .year > 1980",
14946            ]),
14947            "*2\r\n$1\r\na\r\n$1\r\nd\r\n"
14948        );
14949        // A list, and a field an element does not have.
14950        assert_eq!(
14951            f.run(&[
14952                b"VSIM",
14953                b"v",
14954                b"VALUES",
14955                b"2",
14956                b"9",
14957                b"1",
14958                b"FILTER",
14959                b".lang in ['fr', 'de']",
14960            ]),
14961            "*1\r\n$1\r\nb\r\n"
14962        );
14963        assert_eq!(
14964            f.run(&[
14965                b"VSIM",
14966                b"v",
14967                b"VALUES",
14968                b"2",
14969                b"9",
14970                b"1",
14971                b"FILTER",
14972                b".rating > 3"
14973            ]),
14974            "*0\r\n"
14975        );
14976        // TRUTH measures every vector, and the filter still decides which ones
14977        // are measured.
14978        assert_eq!(
14979            f.run(&[
14980                b"VSIM",
14981                b"v",
14982                b"VALUES",
14983                b"2",
14984                b"9",
14985                b"1",
14986                b"TRUTH",
14987                b"FILTER",
14988                b".year < 1980",
14989            ]),
14990            "*1\r\n$1\r\nc\r\n"
14991        );
14992        // VSETATTR moves an element in and out of a filter, which means the tag
14993        // beside its code was rewritten and not just the string.
14994        f.run(&[b"VSETATTR", b"v", b"b", r#"{"lang":"en"}"#.as_bytes()]);
14995        assert_eq!(
14996            f.run(&[
14997                b"VSIM",
14998                b"v",
14999                b"VALUES",
15000                b"2",
15001                b"9",
15002                b"1",
15003                b"COUNT",
15004                b"1",
15005                b"FILTER",
15006                b".lang == \"en\"",
15007            ]),
15008            "*1\r\n$1\r\nb\r\n"
15009        );
15010        // And a VADD that replaces the vector keeps the attribute and the tag,
15011        // which is the same rewrite from the other end.
15012        f.run(&[b"VADD", b"v", b"VALUES", b"2", b"9", b"2", b"b"]);
15013        assert_eq!(
15014            f.run(&[
15015                b"VSIM",
15016                b"v",
15017                b"VALUES",
15018                b"2",
15019                b"9",
15020                b"1",
15021                b"COUNT",
15022                b"1",
15023                b"FILTER",
15024                b".lang == \"en\"",
15025            ]),
15026            "*1\r\n$1\r\nb\r\n"
15027        );
15028
15029        // The expression is parsed before the key is read, so a bad one is an
15030        // error whether or not the key is there.
15031        let bad = f.run(&[b"VSIM", b"nokey", b"ELE", b"e", b"FILTER", b".k =="]);
15032        assert_eq!(bad, "-ERR invalid FILTER expression\r\n");
15033        assert_eq!(
15034            f.run(&[b"VSIM", b"v", b"ELE", b"a", b"FILTER", b"junk"]),
15035            "-ERR invalid FILTER expression\r\n"
15036        );
15037        // FILTER-EF raises the effort rather than capping it, and zero is
15038        // Redis's word for no limit, so neither is an error.
15039        assert_eq!(
15040            f.run(&[
15041                b"VSIM",
15042                b"v",
15043                b"VALUES",
15044                b"2",
15045                b"9",
15046                b"1",
15047                b"COUNT",
15048                b"1",
15049                b"FILTER-EF",
15050                b"500",
15051                b"FILTER",
15052                b".lang == 'en'",
15053            ]),
15054            "*1\r\n$1\r\nb\r\n"
15055        );
15056        assert_eq!(
15057            f.run(&[
15058                b"VSIM",
15059                b"v",
15060                b"VALUES",
15061                b"2",
15062                b"9",
15063                b"1",
15064                b"COUNT",
15065                b"1",
15066                b"FILTER-EF",
15067                b"0"
15068            ]),
15069            "*1\r\n$1\r\nb\r\n"
15070        );
15071        assert_eq!(
15072            f.run(&[
15073                b"VSIM",
15074                b"v",
15075                b"VALUES",
15076                b"2",
15077                b"9",
15078                b"1",
15079                b"FILTER-EF",
15080                b"lots"
15081            ]),
15082            "-ERR EF must be a positive integer\r\n"
15083        );
15084    }
15085
15086    /// A vector set key is a key, so the keyspace owns it the way it owns every
15087    /// other one and none of those commands know what is inside it.
15088    #[test]
15089    fn the_keyspace_sees_a_vector_set_key_like_any_other() {
15090        let mut f = Fixture::new();
15091        f.run(&[b"VADD", b"v", b"VALUES", b"2", b"1", b"0", b"east"]);
15092        assert_eq!(f.run(&[b"TYPE", b"v"]), "+vectorset\r\n");
15093        assert_eq!(f.run(&[b"EXISTS", b"v"]), ":1\r\n");
15094        assert_eq!(f.run(&[b"OBJECT", b"ENCODING", b"v"]), "$6\r\nrabitq\r\n");
15095        assert_eq!(f.run(&[b"KEYS", b"*"]), "*1\r\n$1\r\nv\r\n");
15096        assert_eq!(f.run(&[b"DBSIZE"]), ":1\r\n");
15097        assert_eq!(f.run(&[b"EXPIRE", b"v", b"100"]), ":1\r\n");
15098        assert_eq!(f.run(&[b"TTL", b"v"]), ":100\r\n");
15099        assert_eq!(f.run(&[b"PERSIST", b"v"]), ":1\r\n");
15100        assert_eq!(f.run(&[b"DEL", b"v"]), ":1\r\n");
15101        assert_eq!(f.run(&[b"EXISTS", b"v"]), ":0\r\n");
15102
15103        // And the wrong type is the wrong type in both directions.
15104        f.run(&[b"SET", b"s", b"1"]);
15105        assert_eq!(
15106            f.run(&[b"VADD", b"s", b"VALUES", b"2", b"1", b"0", b"east"]),
15107            "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n"
15108        );
15109        assert_eq!(
15110            f.run(&[b"VCARD", b"s"]),
15111            "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n"
15112        );
15113        f.run(&[b"VADD", b"v", b"VALUES", b"2", b"1", b"0", b"east"]);
15114        assert_eq!(
15115            f.run(&[b"GET", b"v"]),
15116            "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n"
15117        );
15118        // A graph and a vector set share the escape in the record tag and are
15119        // still two different types, which is the case the tag alone cannot
15120        // decide.
15121        f.run(&[b"G.NADD", b"social", b"ada"]);
15122        assert_eq!(
15123            f.run(&[b"VCARD", b"social"]),
15124            "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n"
15125        );
15126        assert_eq!(
15127            f.run(&[b"G.NGET", b"v", b"ada"]),
15128            "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n"
15129        );
15130    }
15131
15132    /// `VRANDMEMBER` is `SRANDMEMBER` over the element names, in both of its
15133    /// shapes, off the database's own generator.
15134    #[test]
15135    fn vrandmember_has_the_two_shapes_srandmember_has() {
15136        let mut f = Fixture::new();
15137        for (i, name) in [&b"a"[..], b"b", b"c"].iter().enumerate() {
15138            let x = (i + 1).to_string();
15139            f.run(&[b"VADD", b"v", b"VALUES", b"2", x.as_bytes(), b"1", name]);
15140        }
15141        // One element is a bulk string and not an array of one.
15142        let one = f.run(&[b"VRANDMEMBER", b"v"]);
15143        assert!(one.starts_with("$1\r\n"), "{one}");
15144        // A positive count is distinct and stops at the size of the set.
15145        let mut all = f.run(&[b"VRANDMEMBER", b"v", b"9"]);
15146        assert!(all.starts_with("*3\r\n"), "{all}");
15147        for name in ["a", "b", "c"] {
15148            assert!(all.contains(name), "{all} is missing {name}");
15149        }
15150        all = f.run(&[b"VRANDMEMBER", b"v", b"2"]);
15151        assert!(all.starts_with("*2\r\n"), "{all}");
15152        // A negative one draws that many and allows repeats.
15153        let many = f.run(&[b"VRANDMEMBER", b"v", b"-5"]);
15154        assert!(many.starts_with("*5\r\n"), "{many}");
15155        // A key that is not there answers the shape that was asked for.
15156        assert_eq!(f.run(&[b"VRANDMEMBER", b"nokey"]), "$-1\r\n");
15157        assert_eq!(f.run(&[b"VRANDMEMBER", b"nokey", b"3"]), "*0\r\n");
15158    }
15159
15160    /// `VLINKS` answers about the index that is here rather than the graph that
15161    /// is not, which is D-2.
15162    #[test]
15163    fn vlinks_reports_one_layer_of_partition_neighbours() {
15164        let mut f = Fixture::new();
15165        f.run(&[b"VADD", b"v", b"VALUES", b"2", b"1", b"0", b"east"]);
15166        f.run(&[b"VADD", b"v", b"VALUES", b"2", b"0", b"1", b"north"]);
15167        // One layer deep, because the index is one layer deep, so a client
15168        // walking layers gets a short list and not a shape it cannot parse.
15169        assert_eq!(
15170            f.run(&[b"VLINKS", b"v", b"east"]),
15171            "*1\r\n*1\r\n$5\r\nnorth\r\n"
15172        );
15173        assert_eq!(
15174            f.run(&[b"VLINKS", b"v", b"east", b"WITHSCORES"]),
15175            "*1\r\n*2\r\n$5\r\nnorth\r\n$3\r\n0.5\r\n"
15176        );
15177        assert_eq!(f.run(&[b"VLINKS", b"v", b"nobody"]), "*-1\r\n");
15178        assert_eq!(f.run(&[b"VLINKS", b"nokey", b"east"]), "*-1\r\n");
15179    }
15180
15181    /// A vector arrives either as digits or as bytes, and the two have to mean
15182    /// the same thing.
15183    #[test]
15184    fn fp32_and_values_are_the_same_vector() {
15185        let mut f = Fixture::new();
15186        let mut blob = Vec::new();
15187        for x in [3.0f32, 4.0] {
15188            blob.extend_from_slice(&x.to_le_bytes());
15189        }
15190        assert_eq!(f.run(&[b"VADD", b"v", b"FP32", &blob, b"a"]), ":1\r\n");
15191        assert_eq!(f.run(&[b"VDIM", b"v"]), ":2\r\n");
15192        assert_eq!(
15193            f.run(&[b"VEMB", b"v", b"a"]),
15194            "*2\r\n$17\r\n2.992125988006592\r\n$1\r\n4\r\n"
15195        );
15196        // RAW is the stored bytes and the numbers that turn them back into the
15197        // client's vector, which for `Q8` is a code a coordinate, the length the
15198        // vector arrived with and the scale the codes are measured against. The
15199        // name of the form is a simple string, which is a real server's shape,
15200        // and all four of these are a real server's answers.
15201        assert_eq!(
15202            f.run(&[b"VEMB", b"v", b"a", b"RAW"]),
15203            "*4\r\n+int8\r\n$2\r\n_\x7f\r\n$1\r\n5\r\n$17\r\n0.800000011920929\r\n"
15204        );
15205        // A blob that is not a whole number of floats is not a vector.
15206        assert_eq!(
15207            f.run(&[b"VADD", b"w", b"FP32", b"abc", b"a"]),
15208            "-ERR invalid vector specification\r\n"
15209        );
15210        // Neither is a count that promises more than arrived.
15211        assert_eq!(
15212            f.run(&[b"VADD", b"w", b"VALUES", b"4", b"1", b"0", b"a"]),
15213            "-ERR syntax error\r\n"
15214        );
15215        assert_eq!(f.run(&[b"EXISTS", b"w"]), ":0\r\n");
15216    }
15217
15218    // ----------------------------------------------------------------- bloom
15219
15220    /// The filter a client gets when it does not describe one, and the two
15221    /// answers an add can give.
15222    #[test]
15223    fn bf_add_makes_the_filter_and_says_whether_it_was_new() {
15224        let mut f = Fixture::new();
15225        assert_eq!(f.run(&[b"BF.ADD", b"b", b"hello"]), ":1\r\n");
15226        assert_eq!(f.run(&[b"BF.ADD", b"b", b"hello"]), ":0\r\n");
15227        assert_eq!(f.run(&[b"BF.EXISTS", b"b", b"hello"]), ":1\r\n");
15228        assert_eq!(f.run(&[b"BF.EXISTS", b"b", b"never"]), ":0\r\n");
15229        assert_eq!(f.run(&[b"BF.CARD", b"b"]), ":1\r\n");
15230        // The defaults are the module's configs and not anything the command
15231        // said, which is 100 entries at a hundredth and a growth of 2.
15232        assert_eq!(
15233            f.run(&[b"BF.INFO", b"b"]),
15234            "*10\r\n+Capacity\r\n:100\r\n+Size\r\n:240\r\n\
15235             +Number of filters\r\n:1\r\n+Number of items inserted\r\n:1\r\n\
15236             +Expansion rate\r\n:2\r\n"
15237        );
15238        assert_eq!(f.run(&[b"TYPE", b"b"]), "+MBbloom--\r\n");
15239        assert_eq!(f.run(&[b"OBJECT", b"ENCODING", b"b"]), "$3\r\nraw\r\n");
15240        // A key that is not there has no filter to report on, and answers two
15241        // different ways about it depending on which command asked.
15242        assert_eq!(f.run(&[b"BF.CARD", b"gone"]), ":0\r\n");
15243        assert_eq!(f.run(&[b"BF.INFO", b"gone"]), "-ERR not found\r\n");
15244    }
15245
15246    /// `BF.EXISTS` on a key holding something else answers a miss, and
15247    /// everything else in the family answers `WRONGTYPE`.
15248    ///
15249    /// The two halves of a check and set disagree about what that key is, which
15250    /// is the module's behaviour and not a decision taken here.
15251    #[test]
15252    fn a_wrong_type_is_a_miss_to_the_two_that_only_read_bits() {
15253        let mut f = Fixture::new();
15254        f.run(&[b"SET", b"s", b"text"]);
15255        assert_eq!(f.run(&[b"BF.EXISTS", b"s", b"x"]), ":0\r\n");
15256        assert_eq!(f.run(&[b"BF.MEXISTS", b"s", b"x"]), "*1\r\n:0\r\n");
15257        for cmd in [
15258            vec![&b"BF.ADD"[..], b"s", b"x"],
15259            vec![&b"BF.MADD"[..], b"s", b"x"],
15260            vec![&b"BF.CARD"[..], b"s"],
15261            vec![&b"BF.INFO"[..], b"s"],
15262            vec![&b"BF.DEBUG"[..], b"s"],
15263            vec![&b"BF.SCANDUMP"[..], b"s", b"0"],
15264        ] {
15265            let name = String::from_utf8_lossy(cmd[0]).into_owned();
15266            assert!(f.run(&cmd).starts_with("-WRONGTYPE"), "{name}");
15267        }
15268        // The arguments are read before the key is, so a reserve with a bad
15269        // error rate complains about the rate and never learns about the string.
15270        assert_eq!(
15271            f.run(&[b"BF.RESERVE", b"s", b"abc", b"10"]),
15272            "-ERR bad error rate\r\n"
15273        );
15274        assert!(
15275            f.run(&[b"BF.RESERVE", b"s", b"0.01", b"10"])
15276                .starts_with("-WRONGTYPE")
15277        );
15278    }
15279
15280    /// A chain grows by its expansion factor and each link is half as wrong as
15281    /// the one before, which is what makes the whole filter hold its rate.
15282    #[test]
15283    fn a_full_filter_grows_a_link_and_a_fixed_one_says_no() {
15284        let mut f = Fixture::new();
15285        assert_eq!(f.run(&[b"BF.RESERVE", b"g", b"0.01", b"10"]), "+OK\r\n");
15286        for i in 0..10u32 {
15287            assert_eq!(
15288                f.run(&[b"BF.ADD", b"g", i.to_string().as_bytes()]),
15289                ":1\r\n"
15290            );
15291        }
15292        assert_eq!(f.run(&[b"BF.INFO", b"g", b"FILTERS"]), "*1\r\n:1\r\n");
15293        assert_eq!(f.run(&[b"BF.ADD", b"g", b"11"]), ":1\r\n");
15294        assert_eq!(f.run(&[b"BF.INFO", b"g", b"filters"]), "*1\r\n:2\r\n");
15295        // Capacity is the sum of every link and not the number that was asked
15296        // for, so it is 10 and then 10 plus 20.
15297        assert_eq!(f.run(&[b"BF.INFO", b"g", b"CAPACITY"]), "*1\r\n:30\r\n");
15298        assert_eq!(
15299            f.run(&[b"BF.DEBUG", b"g"]),
15300            "*3\r\n$7\r\nsize:11\r\n\
15301             $71\r\nbytes:16 bits:128 hashes:8 hashwidth:64 capacity:10 size:10 ratio:0.005\r\n\
15302             $71\r\nbytes:32 bits:256 hashes:9 hashwidth:64 capacity:20 size:1 ratio:0.0025\r\n"
15303        );
15304
15305        // The same filter told not to grow fills instead.
15306        assert_eq!(
15307            f.run(&[b"BF.RESERVE", b"n", b"0.01", b"2", b"NONSCALING"]),
15308            "+OK\r\n"
15309        );
15310        assert_eq!(f.run(&[b"BF.ADD", b"n", b"a"]), ":1\r\n");
15311        assert_eq!(f.run(&[b"BF.ADD", b"n", b"b"]), ":1\r\n");
15312        assert_eq!(
15313            f.run(&[b"BF.ADD", b"n", b"c"]),
15314            "-ERR non scaling filter is full\r\n"
15315        );
15316        // And an item that is already in it still answers, because membership
15317        // is checked before fullness.
15318        assert_eq!(f.run(&[b"BF.ADD", b"n", b"a"]), ":0\r\n");
15319        // A filter that will not grow has no expansion rate to report, in
15320        // either of the two spellings that make one.
15321        assert_eq!(f.run(&[b"BF.INFO", b"n", b"EXPANSION"]), "*1\r\n$-1\r\n");
15322        f.run(&[b"BF.RESERVE", b"z", b"0.01", b"2", b"EXPANSION", b"0"]);
15323        assert_eq!(f.run(&[b"BF.INFO", b"z", b"EXPANSION"]), "*1\r\n$-1\r\n");
15324        // Asking for both at once is refused, which is one of the module's
15325        // errors that carries no prefix at all.
15326        assert_eq!(
15327            f.run(&[
15328                b"BF.RESERVE",
15329                b"q",
15330                b"0.01",
15331                b"2",
15332                b"NONSCALING",
15333                b"EXPANSION",
15334                b"2"
15335            ]),
15336            "-Nonscaling filters cannot expand\r\n"
15337        );
15338    }
15339
15340    /// A multi add stops where the filter did, so the reply can be shorter than
15341    /// the argument list.
15342    #[test]
15343    fn madd_truncates_its_reply_at_the_item_that_did_not_fit() {
15344        let mut f = Fixture::new();
15345        f.run(&[b"BF.RESERVE", b"n", b"0.01", b"2", b"NONSCALING"]);
15346        assert_eq!(
15347            f.run(&[b"BF.MADD", b"n", b"a", b"b", b"c", b"d"]),
15348            "*3\r\n:1\r\n:1\r\n-ERR non scaling filter is full\r\n"
15349        );
15350        assert_eq!(
15351            f.run(&[b"BF.MEXISTS", b"n", b"a", b"c"]),
15352            "*2\r\n:1\r\n:0\r\n"
15353        );
15354    }
15355
15356    /// `BF.INSERT` describes a filter and fills it in one command, with its own
15357    /// spelling of every complaint.
15358    #[test]
15359    fn insert_is_a_reserve_and_a_madd_with_different_errors() {
15360        let mut f = Fixture::new();
15361        assert_eq!(
15362            f.run(&[
15363                b"BF.INSERT",
15364                b"i",
15365                b"CAPACITY",
15366                b"50",
15367                b"ERROR",
15368                b"0.001",
15369                b"ITEMS",
15370                b"a",
15371                b"b"
15372            ]),
15373            "*2\r\n:1\r\n:1\r\n"
15374        );
15375        assert_eq!(f.run(&[b"BF.INFO", b"i", b"CAPACITY"]), "*1\r\n:50\r\n");
15376        // NOCREATE is the only way to add without making the key.
15377        assert_eq!(
15378            f.run(&[b"BF.INSERT", b"gone", b"NOCREATE", b"ITEMS", b"a"]),
15379            "-ERR not found\r\n"
15380        );
15381        assert_eq!(f.run(&[b"EXISTS", b"gone"]), ":0\r\n");
15382        // The same mistakes as BF.RESERVE, in the sentences this command uses
15383        // for them, and one sentence where BF.RESERVE has two.
15384        assert_eq!(
15385            f.run(&[b"BF.INSERT", b"i", b"CAPACITY", b"abc", b"ITEMS", b"a"]),
15386            "-Bad capacity\r\n"
15387        );
15388        assert_eq!(
15389            f.run(&[b"BF.INSERT", b"i", b"ERROR", b"2", b"ITEMS", b"a"]),
15390            "-Bad error rate\r\n"
15391        );
15392        assert_eq!(
15393            f.run(&[b"BF.INSERT", b"i", b"EXPANSION", b"99999", b"ITEMS", b"a"]),
15394            "-Bad expansion\r\n"
15395        );
15396        // An option is matched on its first letter and not on the word, so a
15397        // token nobody meant as an option is one anyway if it starts with the
15398        // right letter. NOSUCHTHING is NONSCALING here, and the filter it
15399        // builds says so.
15400        assert_eq!(
15401            f.run(&[b"BF.INSERT", b"ns", b"NOSUCHTHING", b"ITEMS", b"a"]),
15402            "*1\r\n:1\r\n"
15403        );
15404        assert_eq!(f.run(&[b"BF.INFO", b"ns", b"EXPANSION"]), "*1\r\n$-1\r\n");
15405        // Only E and N need a second look, one for ERROR against EXPANSION and
15406        // the other for NOCREATE against NONSCALING, and both stop as soon as
15407        // they can tell the two apart.
15408        assert_eq!(
15409            f.run(&[b"BF.INSERT", b"e1", b"E", b"4", b"ITEMS", b"a"]),
15410            "*1\r\n:1\r\n"
15411        );
15412        assert_eq!(f.run(&[b"BF.INFO", b"e1", b"EXPANSION"]), "*1\r\n:4\r\n");
15413        assert_eq!(
15414            f.run(&[b"BF.INSERT", b"e2", b"ER", b"0.5", b"ITEMS", b"a"]),
15415            "*1\r\n:1\r\n"
15416        );
15417        assert_eq!(
15418            f.run(&[b"BF.INSERT", b"gone", b"NOC", b"ITEMS", b"a"]),
15419            "-ERR not found\r\n"
15420        );
15421        // A letter that starts nothing is the one case that is refused.
15422        assert_eq!(
15423            f.run(&[b"BF.INSERT", b"i", b"ZZZ", b"ITEMS", b"a"]),
15424            "-Unknown argument received\r\n"
15425        );
15426        // Everything after ITEMS is an item, even when it spells an option.
15427        assert_eq!(
15428            f.run(&[b"BF.INSERT", b"i", b"ITEMS", b"NOCREATE"]),
15429            "*1\r\n:1\r\n"
15430        );
15431        // And ITEMS with nothing after it is the same as leaving it out.
15432        assert!(
15433            f.run(&[b"BF.INSERT", b"i", b"ITEMS"])
15434                .contains("wrong number of arguments")
15435        );
15436    }
15437
15438    /// A filter dumped a chunk at a time and put back into another key is the
15439    /// same filter.
15440    #[test]
15441    fn a_dump_replays_into_a_filter_that_answers_the_same() {
15442        let mut f = Fixture::new();
15443        f.run(&[b"BF.RESERVE", b"src", b"0.01", b"10"]);
15444        for i in 0..25u32 {
15445            f.run(&[b"BF.ADD", b"src", i.to_string().as_bytes()]);
15446        }
15447        assert_eq!(f.run(&[b"BF.INFO", b"src", b"FILTERS"]), "*1\r\n:2\r\n");
15448
15449        // Iterator zero asks for the header and every one after it is a running
15450        // byte offset, and a chunk never spans two links.
15451        let mut iter = b"0".to_vec();
15452        let mut chunks = 0;
15453        loop {
15454            let raw = f.raw(&[b"BF.SCANDUMP", b"src", &iter]);
15455            let text = String::from_utf8_lossy(&raw).into_owned();
15456            let next = text
15457                .split("\r\n")
15458                .nth(1)
15459                .and_then(|n| n.strip_prefix(':'))
15460                .expect("a two element reply of an iterator and a chunk")
15461                .to_owned();
15462            let body = &raw[raw.iter().position(|&b| b == b'$').expect("a bulk chunk")..];
15463            let data = &body[body
15464                .windows(2)
15465                .position(|w| w == b"\r\n")
15466                .expect("a length line")
15467                + 2..body.len() - 2];
15468            if next == "0" {
15469                assert!(data.is_empty(), "the last chunk is empty");
15470                break;
15471            }
15472            let put = f.run(&[b"BF.LOADCHUNK", b"dst", next.as_bytes(), data]);
15473            assert_eq!(put, "+OK\r\n", "loading chunk {chunks}");
15474            iter = next.into_bytes();
15475            chunks += 1;
15476        }
15477        assert_eq!(chunks, 3, "a header and one chunk per link");
15478
15479        assert_eq!(f.run(&[b"BF.INFO", b"dst"]), f.run(&[b"BF.INFO", b"src"]));
15480        assert_eq!(f.run(&[b"BF.DEBUG", b"dst"]), f.run(&[b"BF.DEBUG", b"src"]));
15481        for i in 0..25u32 {
15482            assert_eq!(
15483                f.run(&[b"BF.EXISTS", b"dst", i.to_string().as_bytes()]),
15484                ":1\r\n"
15485            );
15486        }
15487
15488        // A header on top of a filter is refused rather than merged, and so is
15489        // one that no filter wrote.
15490        assert_eq!(
15491            f.run(&[b"BF.LOADCHUNK", b"dst", b"1", b"anything"]),
15492            "-ERR received bad data\r\n"
15493        );
15494        assert_eq!(
15495            f.run(&[b"BF.LOADCHUNK", b"fresh", b"1", b"anything"]),
15496            "-ERR received bad data\r\n"
15497        );
15498        // An offset past the end of the filter names itself.
15499        assert_eq!(
15500            f.run(&[b"BF.LOADCHUNK", b"dst", b"99999", b"x"]),
15501            "-ERR invalid offset - no link found\r\n"
15502        );
15503        assert_eq!(
15504            f.run(&[b"BF.LOADCHUNK", b"dst", b"nope", b"x"]),
15505            "-ERR Second argument must be numeric\r\n"
15506        );
15507        // The same complaint without the prefix on the way out, which is the
15508        // module's inconsistency and not a slip here.
15509        assert_eq!(
15510            f.run(&[b"BF.SCANDUMP", b"src", b"nope"]),
15511            "-Second argument must be numeric\r\n"
15512        );
15513    }
15514
15515    /// The argument checks, which have a sentence each and read numbers the way
15516    /// Redis reads them everywhere else.
15517    #[test]
15518    fn reserve_reads_its_numbers_the_way_string2ll_does() {
15519        let mut f = Fixture::new();
15520        for (args, want) in [
15521            (vec![&b"abc"[..], b"10"], "-ERR bad error rate\r\n"),
15522            (vec![&b"nan"[..], b"10"], "-ERR bad error rate\r\n"),
15523            (
15524                vec![&b"0"[..], b"10"],
15525                "-ERR error rate must be in the range (0.000000, 1.000000)\r\n",
15526            ),
15527            (
15528                vec![&b"1"[..], b"10"],
15529                "-ERR error rate must be in the range (0.000000, 1.000000)\r\n",
15530            ),
15531            (
15532                vec![&b"inf"[..], b"10"],
15533                "-ERR error rate must be in the range (0.000000, 1.000000)\r\n",
15534            ),
15535            (vec![&b"0.01"[..], b"+10"], "-ERR bad capacity\r\n"),
15536            (vec![&b"0.01"[..], b"1e2"], "-ERR bad capacity\r\n"),
15537            (vec![&b"0.01"[..], b"007"], "-ERR bad capacity\r\n"),
15538            (
15539                vec![&b"0.01"[..], b"0"],
15540                "-ERR capacity must be in the range [1, 1073741824]\r\n",
15541            ),
15542            (
15543                vec![&b"0.01"[..], b"1073741825"],
15544                "-ERR capacity must be in the range [1, 1073741824]\r\n",
15545            ),
15546        ] {
15547            let mut cmd = vec![&b"BF.RESERVE"[..], b"k"];
15548            cmd.extend(args.iter().copied());
15549            assert_eq!(f.run(&cmd), want, "{}", String::from_utf8_lossy(args[0]));
15550        }
15551        assert_eq!(
15552            f.run(&[b"BF.RESERVE", b"k", b"0.01", b"10", b"EXPANSION"]),
15553            "-ERR no expansion\r\n"
15554        );
15555        assert_eq!(
15556            f.run(&[b"BF.RESERVE", b"k", b"0.01", b"10", b"EXPANSION", b"abc"]),
15557            "-ERR bad expansion\r\n"
15558        );
15559        assert_eq!(
15560            f.run(&[b"BF.RESERVE", b"k", b"0.01", b"10", b"EXPANSION", b"32769"]),
15561            "-ERR expansion must be in the range [0, 32768]\r\n"
15562        );
15563        // Trailing rubbish after the capacity is ignored rather than refused.
15564        assert_eq!(
15565            f.run(&[b"BF.RESERVE", b"k", b"0.01", b"10", b"junk"]),
15566            "+OK\r\n"
15567        );
15568        assert_eq!(
15569            f.run(&[b"BF.RESERVE", b"k", b"0.01", b"10"]),
15570            "-ERR item exists\r\n"
15571        );
15572        assert_eq!(
15573            f.run(&[b"BF.INFO", b"k", b"nosuchfield"]),
15574            "-Invalid information value\r\n"
15575        );
15576        assert!(
15577            f.run(&[b"BF.INFO", b"k", b"CAPACITY", b"SIZE"])
15578                .contains("wrong number of arguments")
15579        );
15580    }
15581
15582    /// The RESP3 shapes, which are where this family differs most from RESP2.
15583    #[test]
15584    fn the_bloom_family_answers_in_resp3_spelling_too() {
15585        let mut f = Fixture::new();
15586        f.out.set_proto(Proto::Resp3);
15587        assert_eq!(f.run(&[b"BF.ADD", b"b", b"a"]), "#t\r\n");
15588        assert_eq!(f.run(&[b"BF.ADD", b"b", b"a"]), "#f\r\n");
15589        assert_eq!(f.run(&[b"BF.MADD", b"b", b"a", b"c"]), "*2\r\n#f\r\n#t\r\n");
15590        assert_eq!(f.run(&[b"BF.EXISTS", b"b", b"a"]), "#t\r\n");
15591        assert_eq!(
15592            f.run(&[b"BF.MEXISTS", b"b", b"a", b"z"]),
15593            "*2\r\n#t\r\n#f\r\n"
15594        );
15595        // The count stays an integer, because it counts rather than answers.
15596        assert_eq!(f.run(&[b"BF.CARD", b"b"]), ":2\r\n");
15597        assert_eq!(
15598            f.run(&[b"BF.INFO", b"b"]),
15599            "%5\r\n+Capacity\r\n:100\r\n+Size\r\n:240\r\n\
15600             +Number of filters\r\n:1\r\n+Number of items inserted\r\n:2\r\n\
15601             +Expansion rate\r\n:2\r\n"
15602        );
15603        // One field is a map of one here and a bare array of one on RESP2, so
15604        // this is the reply where the two protocols carry different facts.
15605        assert_eq!(
15606            f.run(&[b"BF.INFO", b"b", b"CAPACITY"]),
15607            "%1\r\n+Capacity\r\n:100\r\n"
15608        );
15609    }
15610
15611    // ---------------------------------------------------------------- cuckoo
15612
15613    /// A dump header, which is the four counts and the three widths a filter
15614    /// writes in front of its fingerprints.
15615    ///
15616    /// Written by hand rather than taken from a `CF.SCANDUMP`, because what the
15617    /// tests below want out of it is the states a filter cannot be put into
15618    /// from the wire.
15619    fn cf_header(
15620        items: u64,
15621        buckets: u64,
15622        deletes: u64,
15623        filters: u64,
15624        geometry: [u16; 3],
15625    ) -> Vec<u8> {
15626        let mut out = Vec::with_capacity(38);
15627        for n in [items, buckets, deletes, filters] {
15628            out.extend_from_slice(&n.to_le_bytes());
15629        }
15630        for n in geometry {
15631            out.extend_from_slice(&n.to_le_bytes());
15632        }
15633        out
15634    }
15635
15636    /// The filter a client gets when it does not describe one, and the thing a
15637    /// cuckoo filter does that a Bloom filter cannot, which is count copies and
15638    /// take them out again.
15639    #[test]
15640    fn cf_add_makes_the_filter_and_counts_the_copies() {
15641        let mut f = Fixture::new();
15642        assert_eq!(f.run(&[b"CF.ADD", b"d", b"hello"]), ":1\r\n");
15643        assert_eq!(f.run(&[b"CF.ADD", b"d", b"hello"]), ":1\r\n");
15644        assert_eq!(f.run(&[b"CF.COUNT", b"d", b"hello"]), ":2\r\n");
15645        // The NX form is the one that looks first, which is why it is a command
15646        // of its own rather than an option.
15647        assert_eq!(f.run(&[b"CF.ADDNX", b"d", b"hello"]), ":0\r\n");
15648        assert_eq!(f.run(&[b"CF.ADDNX", b"d", b"other"]), ":1\r\n");
15649        assert_eq!(f.run(&[b"CF.EXISTS", b"d", b"hello"]), ":1\r\n");
15650        assert_eq!(f.run(&[b"CF.EXISTS", b"d", b"no"]), ":0\r\n");
15651        assert_eq!(
15652            f.run(&[b"CF.MEXISTS", b"d", b"hello", b"no"]),
15653            "*2\r\n:1\r\n:0\r\n"
15654        );
15655        // The defaults are the module's configs: 1024 entries over buckets of
15656        // two, twenty kicks and a chain that grows by one.
15657        assert_eq!(
15658            f.run(&[b"CF.INFO", b"d"]),
15659            "*16\r\n+Size\r\n:1080\r\n+Number of buckets\r\n:512\r\n\
15660             +Number of filters\r\n:1\r\n+Number of items inserted\r\n:3\r\n\
15661             +Number of items deleted\r\n:0\r\n+Bucket size\r\n:2\r\n\
15662             +Expansion rate\r\n:1\r\n+Max iterations\r\n:20\r\n"
15663        );
15664        assert_eq!(
15665            f.run(&[b"CF.DEBUG", b"d"]),
15666            "$79\r\nbktsize:2 buckets:512 items:3 deletes:0 filters:1 \
15667             max_iterations:20 expansion:1\r\n"
15668        );
15669        assert_eq!(f.run(&[b"TYPE", b"d"]), "+MBbloomCF\r\n");
15670        assert_eq!(f.run(&[b"OBJECT", b"ENCODING", b"d"]), "$3\r\nraw\r\n");
15671
15672        // A delete takes one copy, so the same item goes twice and then stops.
15673        assert_eq!(f.run(&[b"CF.DEL", b"d", b"hello"]), ":1\r\n");
15674        assert_eq!(f.run(&[b"CF.COUNT", b"d", b"hello"]), ":1\r\n");
15675        assert_eq!(f.run(&[b"CF.DEL", b"d", b"hello"]), ":1\r\n");
15676        assert_eq!(f.run(&[b"CF.DEL", b"d", b"hello"]), ":0\r\n");
15677        assert_eq!(f.run(&[b"CF.COMPACT", b"d"]), "+OK\r\n");
15678
15679        // A key with no filter under it gets three different sentences and one
15680        // plain miss, depending on which command asked.
15681        assert_eq!(f.run(&[b"CF.INFO", b"gone"]), "-ERR not found\r\n");
15682        assert_eq!(f.run(&[b"CF.DEL", b"gone", b"x"]), "-Not found\r\n");
15683        assert_eq!(
15684            f.run(&[b"CF.COMPACT", b"gone"]),
15685            "-Cuckoo filter was not found\r\n"
15686        );
15687        assert_eq!(f.run(&[b"CF.EXISTS", b"gone", b"x"]), ":0\r\n");
15688        // And `CF.COMPACT` is declared as taking any number of keys and takes
15689        // exactly one, which is the module's own arity being wrong rather than
15690        // this table's.
15691        assert!(
15692            f.run(&[b"CF.COMPACT", b"a", b"b"])
15693                .contains("wrong number of arguments")
15694        );
15695    }
15696
15697    /// The four that only read fingerprints treat a key holding something else
15698    /// as a key with no filter, and everything else answers `WRONGTYPE`.
15699    #[test]
15700    fn a_wrong_type_is_a_miss_to_the_four_that_only_read_fingerprints() {
15701        let mut f = Fixture::new();
15702        f.run(&[b"SET", b"s", b"text"]);
15703        assert_eq!(f.run(&[b"CF.EXISTS", b"s", b"x"]), ":0\r\n");
15704        assert_eq!(f.run(&[b"CF.MEXISTS", b"s", b"x"]), "*1\r\n:0\r\n");
15705        assert_eq!(f.run(&[b"CF.COUNT", b"s", b"x"]), ":0\r\n");
15706        // `CF.DEL` writes and is still in that group, and `CF.COMPACT` writes
15707        // and is declared read only, so neither of the two halves of the family
15708        // is the same set as the flags say.
15709        assert_eq!(f.run(&[b"CF.DEL", b"s", b"x"]), "-Not found\r\n");
15710        assert_eq!(
15711            f.run(&[b"CF.COMPACT", b"s"]),
15712            "-Cuckoo filter was not found\r\n"
15713        );
15714        for cmd in [
15715            vec![&b"CF.ADD"[..], b"s", b"x"],
15716            vec![&b"CF.ADDNX"[..], b"s", b"x"],
15717            vec![&b"CF.INSERT"[..], b"s", b"ITEMS", b"x"],
15718            vec![&b"CF.INSERTNX"[..], b"s", b"ITEMS", b"x"],
15719            vec![&b"CF.INFO"[..], b"s"],
15720            vec![&b"CF.DEBUG"[..], b"s"],
15721            vec![&b"CF.SCANDUMP"[..], b"s", b"0"],
15722            vec![&b"CF.LOADCHUNK"[..], b"s", b"2", b"x"],
15723            vec![&b"CF.RESERVE"[..], b"s", b"64"],
15724        ] {
15725            let name = String::from_utf8_lossy(cmd[0]).into_owned();
15726            assert!(f.run(&cmd).starts_with("-WRONGTYPE"), "{name}");
15727        }
15728    }
15729
15730    /// `CF.RESERVE` reads its options by name in an order of its own, and the
15731    /// first pair with a given name is the only one it looks at.
15732    #[test]
15733    fn reserve_complains_about_its_options_in_the_order_it_looks_for_them() {
15734        let mut f = Fixture::new();
15735        assert_eq!(
15736            f.run(&[
15737                b"CF.RESERVE",
15738                b"r",
15739                b"64",
15740                b"BUCKETSIZE",
15741                b"1",
15742                b"MAXITERATIONS",
15743                b"7",
15744                b"EXPANSION",
15745                b"4"
15746            ]),
15747            "+OK\r\n"
15748        );
15749        assert_eq!(
15750            f.run(&[b"CF.DEBUG", b"r"]),
15751            "$77\r\nbktsize:1 buckets:64 items:0 deletes:0 filters:1 \
15752             max_iterations:7 expansion:4\r\n"
15753        );
15754        assert_eq!(f.run(&[b"CF.RESERVE", b"r", b"64"]), "-ERR item exists\r\n");
15755
15756        assert_eq!(f.run(&[b"CF.RESERVE", b"q", b"abc"]), "-Bad capacity\r\n");
15757        assert_eq!(
15758            f.run(&[b"CF.RESERVE", b"q", b"1"]),
15759            "-Capacity must be in the range [2 * BUCKETSIZE, 1073741824]\r\n"
15760        );
15761        // The range is the bucket size's and not a constant, so a capacity that
15762        // was fine at two slots a bucket is not at four.
15763        assert_eq!(
15764            f.run(&[b"CF.RESERVE", b"q", b"7", b"BUCKETSIZE", b"4"]),
15765            "-Capacity must be in the range [2 * BUCKETSIZE, 1073741824]\r\n"
15766        );
15767        assert_eq!(
15768            f.run(&[b"CF.RESERVE", b"q", b"8", b"BUCKETSIZE", b"4"]),
15769            "+OK\r\n"
15770        );
15771
15772        // The capacity is checked last, so a command that is wrong twice
15773        // answers about the option. Which option it answers about is the order
15774        // the module looks for them in and not the order they were written, so
15775        // a bad kick budget wins over a bad bucket size wherever the two sit.
15776        assert_eq!(
15777            f.run(&[b"CF.RESERVE", b"q2", b"64", b"BUCKETSIZE", b"0"]),
15778            "-BUCKETSIZE: value must be in the range [1, 255]\r\n"
15779        );
15780        assert_eq!(
15781            f.run(&[
15782                b"CF.RESERVE",
15783                b"q2",
15784                b"64",
15785                b"EXPANSION",
15786                b"xx",
15787                b"BUCKETSIZE",
15788                b"0"
15789            ]),
15790            "-BUCKETSIZE: value must be in the range [1, 255]\r\n"
15791        );
15792        assert_eq!(
15793            f.run(&[
15794                b"CF.RESERVE",
15795                b"q2",
15796                b"64",
15797                b"MAXITERATIONS",
15798                b"0",
15799                b"BUCKETSIZE",
15800                b"0"
15801            ]),
15802            "-MAXITERATIONS: value must be in the range [1, 65535]\r\n"
15803        );
15804        // A second pair with a name that has already been read is not looked at
15805        // at all, so this one is a filter with buckets of one rather than an
15806        // error about a bucket size of zero.
15807        assert_eq!(
15808            f.run(&[
15809                b"CF.RESERVE",
15810                b"q3",
15811                b"64",
15812                b"BUCKETSIZE",
15813                b"1",
15814                b"BUCKETSIZE",
15815                b"0"
15816            ]),
15817            "+OK\r\n"
15818        );
15819        // A pair nobody knows is dropped, which is the opposite of what
15820        // `CF.INSERT` does with the same mistake.
15821        assert_eq!(
15822            f.run(&[b"CF.RESERVE", b"q4", b"64", b"NOSUCH", b"9"]),
15823            "+OK\r\n"
15824        );
15825        assert_eq!(
15826            f.run(&[b"CF.DEBUG", b"q4"]),
15827            "$78\r\nbktsize:2 buckets:32 items:0 deletes:0 filters:1 \
15828             max_iterations:20 expansion:1\r\n"
15829        );
15830        // And an option with nothing after it leaves an odd number of them,
15831        // which is an arity error rather than a complaint about the option.
15832        assert!(
15833            f.run(&[b"CF.RESERVE", b"q5", b"64", b"BUCKETSIZE"])
15834                .contains("wrong number of arguments")
15835        );
15836    }
15837
15838    /// `CF.INSERT` is a reserve and a multi add, with a grammar that agrees
15839    /// with `CF.RESERVE` about nothing.
15840    #[test]
15841    fn insert_checks_every_occurrence_and_matches_on_the_first_letter() {
15842        let mut f = Fixture::new();
15843        assert_eq!(
15844            f.run(&[b"CF.INSERT", b"i", b"CAPACITY", b"64", b"ITEMS", b"a", b"b"]),
15845            "*2\r\n:1\r\n:1\r\n"
15846        );
15847        assert_eq!(
15848            f.run(&[b"CF.DEBUG", b"i"]),
15849            "$78\r\nbktsize:2 buckets:32 items:2 deletes:0 filters:1 \
15850             max_iterations:20 expansion:1\r\n"
15851        );
15852        // The NX form has three answers rather than two, which is why it stays
15853        // integers on both protocols.
15854        assert_eq!(
15855            f.run(&[b"CF.INSERTNX", b"i", b"ITEMS", b"a", b"c"]),
15856            "*2\r\n:0\r\n:1\r\n"
15857        );
15858        assert_eq!(
15859            f.run(&[b"CF.INSERT", b"gone", b"NOCREATE", b"ITEMS", b"a"]),
15860            "-ERR not found\r\n"
15861        );
15862        assert_eq!(f.run(&[b"EXISTS", b"gone"]), ":0\r\n");
15863
15864        assert_eq!(
15865            f.run(&[b"CF.INSERT", b"i", b"CAPACITY", b"abc", b"ITEMS", b"a"]),
15866            "-Bad capacity\r\n"
15867        );
15868        // The bucket size cannot be given here, so the range names the config
15869        // that holds it instead of the option `CF.RESERVE` names.
15870        assert_eq!(
15871            f.run(&[b"CF.INSERT", b"i", b"CAPACITY", b"2", b"ITEMS", b"a"]),
15872            "-Capacity must be in the range [cf-bucket-size * 2, 1073741824]\r\n"
15873        );
15874        // Every occurrence is checked, which is where this differs from
15875        // `CF.RESERVE`: the second `CAPACITY` is an error even though the first
15876        // one is the one that would have been used.
15877        assert_eq!(
15878            f.run(&[
15879                b"CF.INSERT",
15880                b"i",
15881                b"CAPACITY",
15882                b"8",
15883                b"CAPACITY",
15884                b"2",
15885                b"ITEMS",
15886                b"a"
15887            ]),
15888            "-Capacity must be in the range [cf-bucket-size * 2, 1073741824]\r\n"
15889        );
15890        // An option is one letter and not a word, so `NOSUCH` is `NOCREATE` and
15891        // `ITEMSXYZ` is `ITEMS`, and only a letter that starts nothing is
15892        // refused.
15893        assert_eq!(
15894            f.run(&[b"CF.INSERT", b"i", b"NOSUCH", b"ITEMS", b"a"]),
15895            "*1\r\n:1\r\n"
15896        );
15897        assert_eq!(
15898            f.run(&[b"CF.INSERT", b"i", b"ITEMSXYZ", b"a"]),
15899            "*1\r\n:1\r\n"
15900        );
15901        assert_eq!(
15902            f.run(&[b"CF.INSERT", b"i", b"ZZZ", b"ITEMS", b"a"]),
15903            "-Unknown argument received\r\n"
15904        );
15905        // Everything after ITEMS is an item, even when it spells an option.
15906        assert_eq!(
15907            f.run(&[b"CF.INSERT", b"i", b"ITEMS", b"NOCREATE"]),
15908            "*1\r\n:1\r\n"
15909        );
15910        // And the two ways of sending no items at all are the same complaint.
15911        assert!(
15912            f.run(&[b"CF.INSERT", b"i", b"ITEMS"])
15913                .contains("wrong number of arguments")
15914        );
15915        assert!(
15916            f.run(&[b"CF.INSERT", b"i", b"CAPACITY"])
15917                .contains("wrong number of arguments")
15918        );
15919    }
15920
15921    /// The two walls a filter can hit, which say different things and are not
15922    /// the same wall.
15923    #[test]
15924    fn a_full_filter_and_one_that_ran_out_of_filters_answer_differently() {
15925        let mut f = Fixture::new();
15926        f.run(&[
15927            b"CF.RESERVE",
15928            b"s",
15929            b"4",
15930            b"BUCKETSIZE",
15931            b"1",
15932            b"EXPANSION",
15933            b"0",
15934        ]);
15935        for i in 0..4u32 {
15936            assert_eq!(
15937                f.run(&[b"CF.ADD", b"s", i.to_string().as_bytes()]),
15938                ":1\r\n"
15939            );
15940        }
15941        assert_eq!(f.run(&[b"CF.ADD", b"s", b"4"]), "-Filter is full\r\n");
15942        assert_eq!(f.run(&[b"CF.ADDNX", b"s", b"zz"]), "-Filter is full\r\n");
15943        // The add commands say it in a sentence and the insert commands say it
15944        // in the array, one value per item, and the array is never short.
15945        assert_eq!(
15946            f.run(&[b"CF.INSERT", b"s", b"ITEMS", b"p", b"q"]),
15947            "*2\r\n:-1\r\n:-1\r\n"
15948        );
15949        assert_eq!(
15950            f.run(&[b"CF.INSERTNX", b"s", b"ITEMS", b"0", b"q"]),
15951            "*2\r\n:0\r\n:-1\r\n"
15952        );
15953
15954        // A chain that is allowed to grow stops for a different reason, and the
15955        // count it stops at is the filter limit rather than the room: this one
15956        // gives up with three slots free. Loading a chain that already has
15957        // every filter it is allowed shows why, since it refuses an item
15958        // straight into an empty one.
15959        let full = cf_header(0, 4, 0, 32, [1, 20, 1]);
15960        assert_eq!(f.run(&[b"CF.LOADCHUNK", b"g", b"1", &full]), "+OK\r\n");
15961        assert_eq!(
15962            f.run(&[b"CF.ADD", b"g", b"q"]),
15963            "-Maximum expansions reached\r\n"
15964        );
15965        assert_eq!(
15966            f.run(&[b"CF.INFO", b"g"]),
15967            "*16\r\n+Size\r\n:680\r\n+Number of buckets\r\n:4\r\n\
15968             +Number of filters\r\n:32\r\n+Number of items inserted\r\n:0\r\n\
15969             +Number of items deleted\r\n:0\r\n+Bucket size\r\n:1\r\n\
15970             +Expansion rate\r\n:1\r\n+Max iterations\r\n:20\r\n"
15971        );
15972    }
15973
15974    /// A filter dumped a chunk at a time and put back under another key is the
15975    /// same filter, and the headers that describe one nobody could build are
15976    /// refused on the way in.
15977    #[test]
15978    fn a_cuckoo_dump_replays_into_a_filter_that_answers_the_same() {
15979        let mut f = Fixture::new();
15980        f.run(&[
15981            b"CF.RESERVE",
15982            b"src",
15983            b"8",
15984            b"BUCKETSIZE",
15985            b"2",
15986            b"EXPANSION",
15987            b"2",
15988        ]);
15989        for i in 0..40u32 {
15990            f.run(&[b"CF.ADD", b"src", i.to_string().as_bytes()]);
15991        }
15992        // Position zero asks for the header and every one after it is a byte
15993        // offset across every filter laid end to end, and the walk ends on a
15994        // zero and a nil rather than an empty chunk.
15995        let mut pos = b"0".to_vec();
15996        let mut chunks = 0;
15997        loop {
15998            let raw = f.raw(&[b"CF.SCANDUMP", b"src", &pos]);
15999            let head = String::from_utf8_lossy(&raw[..raw.len().min(24)]).into_owned();
16000            let next = head
16001                .split("\r\n")
16002                .nth(1)
16003                .and_then(|n| n.strip_prefix(':'))
16004                .expect("a two element reply of a position and a chunk")
16005                .to_owned();
16006            if next == "0" {
16007                assert!(raw.ends_with(b"$-1\r\n"), "the walk ends on a nil");
16008                break;
16009            }
16010            let body = &raw[raw.iter().position(|&b| b == b'$').expect("a bulk chunk")..];
16011            let at = body
16012                .windows(2)
16013                .position(|w| w == b"\r\n")
16014                .expect("a length line")
16015                + 2;
16016            let data = &body[at..body.len() - 2];
16017            assert_eq!(
16018                f.run(&[b"CF.LOADCHUNK", b"dst", next.as_bytes(), data]),
16019                "+OK\r\n",
16020                "loading chunk {chunks}"
16021            );
16022            pos = next.into_bytes();
16023            chunks += 1;
16024        }
16025        assert!(chunks >= 2, "a header and at least one chunk");
16026
16027        assert_eq!(f.run(&[b"CF.INFO", b"dst"]), f.run(&[b"CF.INFO", b"src"]));
16028        assert_eq!(f.run(&[b"CF.DEBUG", b"dst"]), f.run(&[b"CF.DEBUG", b"src"]));
16029        for i in 0..40u32 {
16030            assert_eq!(
16031                f.run(&[b"CF.EXISTS", b"dst", i.to_string().as_bytes()]),
16032                ":1\r\n"
16033            );
16034        }
16035
16036        // A filter with nothing in it hands out no header at all, so a client
16037        // that dumps one has nothing to load back.
16038        f.run(&[b"CF.RESERVE", b"empty", b"4", b"BUCKETSIZE", b"1"]);
16039        assert_eq!(
16040            f.run(&[b"CF.SCANDUMP", b"empty", b"0"]),
16041            "*2\r\n:0\r\n$-1\r\n"
16042        );
16043
16044        // The positions this end will not take, which are not the same set at
16045        // both ends: a dump refuses a negative one and a load takes it as an
16046        // offset and fails to find anything there.
16047        assert_eq!(
16048            f.run(&[b"CF.SCANDUMP", b"src", b"nope"]),
16049            "-Invalid position\r\n"
16050        );
16051        assert_eq!(
16052            f.run(&[b"CF.SCANDUMP", b"src", b"-1"]),
16053            "-Invalid position\r\n"
16054        );
16055        assert_eq!(
16056            f.run(&[b"CF.LOADCHUNK", b"dst", b"0", b"x"]),
16057            "-Invalid position\r\n"
16058        );
16059        assert_eq!(
16060            f.run(&[b"CF.LOADCHUNK", b"dst", b"99999", b"x"]),
16061            "-Couldn't load chunk!\r\n"
16062        );
16063        // A header on top of a filter is refused rather than merged.
16064        let good = cf_header(0, 8, 0, 1, [2, 20, 1]);
16065        assert_eq!(
16066            f.run(&[b"CF.LOADCHUNK", b"dst", b"1", &good]),
16067            "-ERR item exists\r\n"
16068        );
16069        // A chunk that is not the size of a header where a header should have
16070        // been is one sentence, and one that is the size of a header and
16071        // describes a filter nobody could build is another.
16072        assert_eq!(
16073            f.run(&[b"CF.LOADCHUNK", b"n1", b"1", b"short"]),
16074            "-Invalid header\r\n"
16075        );
16076        for (why, bad) in [
16077            ("no filters at all", cf_header(0, 8, 0, 0, [2, 20, 1])),
16078            ("no buckets", cf_header(0, 0, 0, 1, [2, 20, 1])),
16079            (
16080                "a bucket count that is not a power of two",
16081                cf_header(0, 3, 0, 1, [2, 20, 1]),
16082            ),
16083            ("an empty bucket", cf_header(0, 8, 0, 1, [0, 20, 1])),
16084            ("no kicks", cf_header(0, 8, 0, 1, [2, 0, 1])),
16085            (
16086                "a growth nobody could reach",
16087                cf_header(0, 8, 0, 1, [2, 20, 32769]),
16088            ),
16089            (
16090                "a chain that cannot grow and did",
16091                cf_header(0, 8, 0, 2, [2, 20, 0]),
16092            ),
16093            // The count is written in eight bytes and read into two, so a
16094            // number that is a multiple of the second arrives as none.
16095            (
16096                "a filter count that wraps",
16097                cf_header(0, 8, 0, 65_536, [2, 20, 1]),
16098            ),
16099        ] {
16100            assert_eq!(
16101                f.run(&[b"CF.LOADCHUNK", b"bad", b"1", &bad]),
16102                "-Couldn't create filter!\r\n",
16103                "{why}"
16104            );
16105        }
16106    }
16107
16108    /// The RESP3 shapes, which are where this family differs most from RESP2
16109    /// and where one of its answers stops being readable.
16110    #[test]
16111    fn the_cuckoo_family_answers_in_resp3_spelling_too() {
16112        let mut f = Fixture::new();
16113        f.out.set_proto(Proto::Resp3);
16114        assert_eq!(f.run(&[b"CF.ADD", b"c", b"a"]), "#t\r\n");
16115        assert_eq!(f.run(&[b"CF.ADD", b"c", b"a"]), "#t\r\n");
16116        assert_eq!(f.run(&[b"CF.ADDNX", b"c", b"a"]), "#f\r\n");
16117        assert_eq!(f.run(&[b"CF.EXISTS", b"c", b"a"]), "#t\r\n");
16118        assert_eq!(
16119            f.run(&[b"CF.MEXISTS", b"c", b"a", b"z"]),
16120            "*2\r\n#t\r\n#f\r\n"
16121        );
16122        assert_eq!(f.run(&[b"CF.DEL", b"c", b"a"]), "#t\r\n");
16123        assert_eq!(f.run(&[b"CF.DEL", b"c", b"z"]), "#f\r\n");
16124        // The count stays an integer, because it counts rather than answers.
16125        assert_eq!(f.run(&[b"CF.COUNT", b"c", b"a"]), ":1\r\n");
16126        assert_eq!(
16127            f.run(&[b"CF.INFO", b"c"]),
16128            "%8\r\n+Size\r\n:1080\r\n+Number of buckets\r\n:512\r\n\
16129             +Number of filters\r\n:1\r\n+Number of items inserted\r\n:1\r\n\
16130             +Number of items deleted\r\n:1\r\n+Bucket size\r\n:2\r\n\
16131             +Expansion rate\r\n:1\r\n+Max iterations\r\n:20\r\n"
16132        );
16133
16134        // `CF.INSERT` writes a boolean per item here and an integer per item on
16135        // RESP2, and minus one has nowhere to go in a boolean, so a RESP3
16136        // client cannot tell an item that did not fit from one that is already
16137        // there. `CF.INSERTNX` keeps its integers for exactly that reason.
16138        f.run(&[
16139            b"CF.RESERVE",
16140            b"s",
16141            b"4",
16142            b"BUCKETSIZE",
16143            b"1",
16144            b"EXPANSION",
16145            b"0",
16146        ]);
16147        assert_eq!(
16148            f.run(&[
16149                b"CF.INSERT",
16150                b"s",
16151                b"ITEMS",
16152                b"a",
16153                b"b",
16154                b"c",
16155                b"d",
16156                b"e",
16157                b"f"
16158            ]),
16159            "*6\r\n#t\r\n#t\r\n#t\r\n#f\r\n#f\r\n#f\r\n"
16160        );
16161        assert_eq!(
16162            f.run(&[b"CF.INSERTNX", b"s", b"ITEMS", b"a", b"zz"]),
16163            "*2\r\n:0\r\n:-1\r\n"
16164        );
16165        assert_eq!(f.run(&[b"CF.ADD", b"s", b"zzz"]), "-Filter is full\r\n");
16166        // The end of a dump is a nil and not an empty chunk, which is one
16167        // underscore here and a negative length on RESP2.
16168        assert_eq!(f.run(&[b"CF.SCANDUMP", b"c", b"9999"]), "*2\r\n:0\r\n_\r\n");
16169    }
16170
16171    // ------------------------------------------------------------------- cms
16172
16173    /// A sketch is made from either end, and both constructors look at the key
16174    /// before they look at their arguments.
16175    #[test]
16176    fn a_sketch_is_made_from_a_size_or_from_an_error_rate() {
16177        let mut f = Fixture::new();
16178        assert_eq!(f.run(&[b"CMS.INITBYDIM", b"d", b"100", b"5"]), "+OK\r\n");
16179        assert_eq!(
16180            f.run(&[b"CMS.INFO", b"d"]),
16181            "*6\r\n+width\r\n:100\r\n+depth\r\n:5\r\n+count\r\n:0\r\n"
16182        );
16183        assert_eq!(f.run(&[b"TYPE", b"d"]), "+CMSk-TYPE\r\n");
16184        assert_eq!(f.run(&[b"OBJECT", b"ENCODING", b"d"]), "$3\r\nraw\r\n");
16185        // Two over the error rounded up, and the log of the probability over the
16186        // log of a half rounded up, which for these two is 200 by 6.
16187        assert_eq!(
16188            f.run(&[b"CMS.INITBYPROB", b"p", b"0.01", b"0.03"]),
16189            "+OK\r\n"
16190        );
16191        assert_eq!(
16192            f.run(&[b"CMS.INFO", b"p"]),
16193            "*6\r\n+width\r\n:200\r\n+depth\r\n:6\r\n+count\r\n:0\r\n"
16194        );
16195        // The key is checked first, so a width of zero at a key that is already
16196        // there is about the key and not about the width.
16197        assert_eq!(
16198            f.run(&[b"CMS.INITBYDIM", b"d", b"0", b"2"]),
16199            "-CMS: key already exists\r\n"
16200        );
16201        assert_eq!(
16202            f.run(&[b"CMS.INITBYDIM", b"new", b"0", b"2"]),
16203            "-CMS: invalid width\r\n"
16204        );
16205        assert_eq!(
16206            f.run(&[b"CMS.INITBYDIM", b"new", b"2", b"0"]),
16207            "-CMS: invalid depth\r\n"
16208        );
16209        assert_eq!(
16210            f.run(&[b"CMS.INITBYPROB", b"new", b"0", b"0.5"]),
16211            "-CMS: invalid overestimation value\r\n"
16212        );
16213        assert_eq!(
16214            f.run(&[b"CMS.INITBYPROB", b"new", b"0.1", b"1"]),
16215            "-CMS: invalid prob value\r\n"
16216        );
16217        // A probability whose float conversion is zero has no depth, and a width
16218        // past a signed sixty four bit integer has no width, and both are the
16219        // same sentence.
16220        assert_eq!(
16221            f.run(&[b"CMS.INITBYPROB", b"new", b"0.5", b"1e-46"]),
16222            "-CMS: invalid init arguments\r\n"
16223        );
16224        // And a sketch bigger than a gibibyte of counters is refused here where
16225        // the reference reserves address space nobody has touched, which is
16226        // D-47.
16227        assert_eq!(
16228            f.run(&[b"CMS.INITBYDIM", b"new", b"268435457", b"1"]),
16229            "-CMS: Insufficient memory to create the key\r\n"
16230        );
16231        assert_eq!(f.run(&[b"EXISTS", b"new"]), ":0\r\n");
16232    }
16233
16234    /// Every pair is parsed before any of them lands, the counters saturate,
16235    /// and the count is a signed total of what was asked for.
16236    #[test]
16237    fn increments_are_parsed_whole_and_the_counters_saturate() {
16238        let mut f = Fixture::new();
16239        f.run(&[b"CMS.INITBYDIM", b"c", b"100", b"4"]);
16240        assert_eq!(
16241            f.run(&[b"CMS.INCRBY", b"c", b"a", b"3", b"b", b"4"]),
16242            "*2\r\n:3\r\n:4\r\n"
16243        );
16244        // An item that is incremented twice in one call sees its own first
16245        // increment in the reply to the second.
16246        assert_eq!(
16247            f.run(&[b"CMS.INCRBY", b"c", b"a", b"1", b"a", b"1"]),
16248            "*2\r\n:4\r\n:5\r\n"
16249        );
16250        // A bad number anywhere means nothing at all is applied.
16251        assert_eq!(
16252            f.run(&[b"CMS.INCRBY", b"c", b"a", b"9", b"b", b"x"]),
16253            "-CMS: Cannot parse number\r\n"
16254        );
16255        assert_eq!(
16256            f.run(&[b"CMS.INCRBY", b"c", b"a", b"9", b"b", b"-1"]),
16257            "-CMS: Number cannot be negative\r\n"
16258        );
16259        assert_eq!(
16260            f.run(&[b"CMS.QUERY", b"c", b"a", b"b"]),
16261            "*2\r\n:5\r\n:4\r\n"
16262        );
16263        // The counters stop at four billion and the item that stopped says so in
16264        // its own slot while the one beside it answers a number.
16265        f.run(&[b"CMS.INCRBY", b"c", b"a", b"4294967295"]);
16266        assert_eq!(
16267            f.run(&[b"CMS.INCRBY", b"c", b"a", b"1", b"b", b"1"]),
16268            "*2\r\n-CMS: INCRBY overflow\r\n:5\r\n"
16269        );
16270        assert_eq!(f.run(&[b"CMS.QUERY", b"c", b"a"]), "*1\r\n:4294967295\r\n");
16271        // The count is what was asked for rather than what landed, and it is
16272        // signed, so a big enough total comes back negative.
16273        f.run(&[b"CMS.INITBYDIM", b"w", b"4", b"1"]);
16274        f.run(&[b"CMS.INCRBY", b"w", b"x", b"9223372036854775807"]);
16275        f.run(&[b"CMS.INCRBY", b"w", b"x", b"1"]);
16276        assert_eq!(
16277            f.run(&[b"CMS.INFO", b"w"]),
16278            "*6\r\n+width\r\n:4\r\n+depth\r\n:1\r\n+count\r\n:-9223372036854775808\r\n"
16279        );
16280        // An odd number of arguments after the key is an arity error and not a
16281        // syntax one.
16282        assert!(
16283            f.run(&[b"CMS.INCRBY", b"c", b"a", b"1", b"b"])
16284                .contains("wrong number of arguments")
16285        );
16286        assert_eq!(
16287            f.run(&[b"CMS.INCRBY", b"nope", b"a", b"1"]),
16288            "-CMS: key does not exist\r\n"
16289        );
16290        assert_eq!(
16291            f.run(&[b"CMS.QUERY", b"nope", b"a"]),
16292            "-CMS: key does not exist\r\n"
16293        );
16294    }
16295
16296    /// A merge overwrites its destination, and it is worked out in full before
16297    /// any of it is written.
16298    #[test]
16299    fn a_merge_lands_whole_or_not_at_all() {
16300        let mut f = Fixture::new();
16301        for name in [&b"m1"[..], b"m2", b"dst"] {
16302            f.run(&[b"CMS.INITBYDIM", name, b"64", b"3"]);
16303        }
16304        f.run(&[b"CMS.INCRBY", b"m1", b"a", b"5"]);
16305        f.run(&[b"CMS.INCRBY", b"m2", b"a", b"7"]);
16306        assert_eq!(
16307            f.run(&[b"CMS.MERGE", b"dst", b"2", b"m1", b"m2"]),
16308            "+OK\r\n"
16309        );
16310        assert_eq!(f.run(&[b"CMS.QUERY", b"dst", b"a"]), "*1\r\n:12\r\n");
16311        // Overwritten and not added to, so the same merge twice is the same
16312        // answer twice.
16313        assert_eq!(
16314            f.run(&[b"CMS.MERGE", b"dst", b"2", b"m1", b"m2"]),
16315            "+OK\r\n"
16316        );
16317        assert_eq!(f.run(&[b"CMS.QUERY", b"dst", b"a"]), "*1\r\n:12\r\n");
16318        assert_eq!(
16319            f.run(&[
16320                b"CMS.MERGE",
16321                b"dst",
16322                b"2",
16323                b"m1",
16324                b"m2",
16325                b"WEIGHTS",
16326                b"2",
16327                b"3"
16328            ]),
16329            "+OK\r\n"
16330        );
16331        assert_eq!(f.run(&[b"CMS.QUERY", b"dst", b"a"]), "*1\r\n:31\r\n");
16332        // A cell times a weight is checked wide rather than wrapped, so this is
16333        // a refusal and the destination is left exactly as it was.
16334        assert_eq!(
16335            f.run(&[
16336                b"CMS.MERGE",
16337                b"dst",
16338                b"1",
16339                b"m1",
16340                b"WEIGHTS",
16341                b"4611686018427387904"
16342            ]),
16343            "-CMS: MERGE overflow\r\n"
16344        );
16345        assert_eq!(f.run(&[b"CMS.QUERY", b"dst", b"a"]), "*1\r\n:31\r\n");
16346        // The destination comes first, then the count, then the layout, then the
16347        // weights, then the sources one at a time.
16348        f.run(&[b"CMS.INITBYDIM", b"wide", b"128", b"3"]);
16349        assert_eq!(
16350            f.run(&[b"CMS.MERGE", b"gone", b"1", b"m1"]),
16351            "-CMS: key does not exist\r\n"
16352        );
16353        assert_eq!(
16354            f.run(&[b"CMS.MERGE", b"dst", b"0", b"m1"]),
16355            "-CMS: Number of keys must be positive\r\n"
16356        );
16357        assert_eq!(
16358            f.run(&[b"CMS.MERGE", b"dst", b"3", b"m1"]),
16359            "-CMS: wrong number of keys\r\n"
16360        );
16361        assert_eq!(
16362            f.run(&[b"CMS.MERGE", b"dst", b"1", b"m1", b"WEIGHTS", b"1", b"2"]),
16363            "-CMS: wrong number of keys/weights\r\n"
16364        );
16365        assert_eq!(
16366            f.run(&[b"CMS.MERGE", b"dst", b"1", b"wide"]),
16367            "-CMS: width/depth is not equal\r\n"
16368        );
16369        assert_eq!(
16370            f.run(&[b"CMS.MERGE", b"dst", b"1", b"gone"]),
16371            "-CMS: key does not exist\r\n"
16372        );
16373    }
16374
16375    /// A key holding anything else is `WRONGTYPE` to all six, and a key holding
16376    /// a sketch is refused by the two commands that would have to serialise it.
16377    #[test]
16378    fn a_sketch_is_a_module_key_to_the_rest_of_the_keyspace() {
16379        let mut f = Fixture::new();
16380        f.run(&[b"SET", b"s", b"text"]);
16381        for cmd in [
16382            vec![&b"CMS.INITBYDIM"[..], b"s", b"8", b"2"],
16383            vec![&b"CMS.INCRBY"[..], b"s", b"a", b"1"],
16384            vec![&b"CMS.QUERY"[..], b"s", b"a"],
16385            vec![&b"CMS.INFO"[..], b"s"],
16386            vec![&b"CMS.MERGE"[..], b"s", b"1", b"s"],
16387        ] {
16388            let name = String::from_utf8_lossy(cmd[0]).into_owned();
16389            let reply = f.run(&cmd);
16390            // The two constructors see the key before anything else and say so
16391            // in the module's own words, and the rest are `WRONGTYPE`.
16392            assert!(
16393                reply.starts_with("-WRONGTYPE") || reply == "-CMS: key already exists\r\n",
16394                "{name}: {reply}"
16395            );
16396        }
16397        f.run(&[b"CMS.INITBYDIM", b"c", b"64", b"2"]);
16398        // Redis refuses to copy a module key that has no copy callback, and
16399        // these are its words rather than ours. `DUMP` is the other half of
16400        // D-48: the reference has a payload for one of these and we do not.
16401        assert_eq!(
16402            f.run(&[b"COPY", b"c", b"c2"]),
16403            "-ERR not supported for this module key\r\n"
16404        );
16405        assert_eq!(
16406            f.run(&[b"DUMP", b"c"]),
16407            "-ERR DUMP is not supported for this module key\r\n"
16408        );
16409        // A graph is nobody's module and keeps its own sentence.
16410        f.run(&[b"G.NADD", b"g", b"a"]);
16411        assert_eq!(
16412            f.run(&[b"COPY", b"g", b"g2"]),
16413            "-ERR COPY is not supported for a graph\r\n"
16414        );
16415        assert_eq!(
16416            f.run(&[b"DUMP", b"g"]),
16417            "-ERR DUMP is not supported for a graph\r\n"
16418        );
16419        // Everything that does not need a byte shape works on a sketch key the
16420        // way it works on any other.
16421        assert_eq!(f.run(&[b"EXPIRE", b"c", b"100"]), ":1\r\n");
16422        assert_eq!(f.run(&[b"PERSIST", b"c"]), ":1\r\n");
16423        assert_eq!(f.run(&[b"RENAME", b"c", b"c3"]), "+OK\r\n");
16424        assert_eq!(f.run(&[b"TYPE", b"c3"]), "+CMSk-TYPE\r\n");
16425        assert_eq!(f.run(&[b"DEL", b"c3"]), ":1\r\n");
16426    }
16427
16428    // ------------------------------------------------------------------ topk
16429
16430    /// `TOPK.RESERVE` takes three arguments or six, and looks at the key before
16431    /// it looks at any of them.
16432    #[test]
16433    fn a_reserve_takes_three_arguments_or_six() {
16434        let mut f = Fixture::new();
16435        assert_eq!(f.run(&[b"TOPK.RESERVE", b"t", b"5"]), "+OK\r\n");
16436        assert_eq!(
16437            f.run(&[b"TOPK.INFO", b"t"]),
16438            "*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"
16439        );
16440        // Four arguments and five are an arity error rather than a defaulted
16441        // depth or decay.
16442        for cmd in [
16443            vec![&b"TOPK.RESERVE"[..], b"u", b"5", b"8"],
16444            vec![&b"TOPK.RESERVE"[..], b"u", b"5", b"8", b"7"],
16445        ] {
16446            assert!(f.run(&cmd).contains("wrong number of arguments"));
16447        }
16448        assert_eq!(
16449            f.run(&[b"TOPK.RESERVE", b"u", b"5", b"8", b"7", b"0.5"]),
16450            "+OK\r\n"
16451        );
16452        // The key is checked first, so a reserve with nothing else right at a
16453        // key that is taken still says the key is taken.
16454        assert_eq!(
16455            f.run(&[b"TOPK.RESERVE", b"u", b"0", b"0", b"0", b"9"]),
16456            "-TopK: key already exists\r\n"
16457        );
16458        assert_eq!(
16459            f.run(&[b"TOPK.RESERVE", b"v", b"0"]),
16460            "-TopK: invalid k\r\n"
16461        );
16462        assert_eq!(
16463            f.run(&[b"TOPK.RESERVE", b"v", b"1", b"0", b"7", b"0.9"]),
16464            "-TopK: invalid width\r\n"
16465        );
16466        assert_eq!(
16467            f.run(&[b"TOPK.RESERVE", b"v", b"1", b"8", b"x", b"0.9"]),
16468            "-TopK: invalid depth\r\n"
16469        );
16470        // Zero is out and one is in, which is the module's `> 0` and `<= 1`.
16471        assert_eq!(
16472            f.run(&[b"TOPK.RESERVE", b"v", b"1", b"8", b"7", b"0"]),
16473            "-TopK: invalid decay value. must be '<= 1' & '> 0'\r\n"
16474        );
16475        assert_eq!(
16476            f.run(&[b"TOPK.RESERVE", b"v", b"1", b"8", b"7", b"1"]),
16477            "+OK\r\n"
16478        );
16479        // Past the cap, with the one sentence in the family that has a prefix.
16480        assert_eq!(
16481            f.run(&[
16482                b"TOPK.RESERVE",
16483                b"w",
16484                b"1",
16485                b"4294967295",
16486                b"4294967295",
16487                b"0.9"
16488            ]),
16489            "-ERR Insufficient memory to create topk data structure\r\n"
16490        );
16491    }
16492
16493    /// What the sketch keeps, and the three ways of asking about it.
16494    #[test]
16495    fn the_kept_set_is_what_query_and_list_answer_from() {
16496        let mut f = Fixture::new();
16497        f.run(&[b"TOPK.RESERVE", b"t", b"2", b"1000", b"5", b"0.9"]);
16498        // A null an item while there is room, then the name of whatever was
16499        // pushed out.
16500        assert_eq!(
16501            f.run(&[b"TOPK.ADD", b"t", b"a", b"b"]),
16502            "*2\r\n$-1\r\n$-1\r\n"
16503        );
16504        assert_eq!(f.run(&[b"TOPK.INCRBY", b"t", b"a", b"10"]), "*1\r\n$-1\r\n");
16505        // Two slots are full and `c` arrives with a count of one, which is not
16506        // under the smallest kept count, so it takes that slot straight away.
16507        assert_eq!(f.run(&[b"TOPK.ADD", b"t", b"c"]), "*1\r\n$1\r\nb\r\n");
16508        assert_eq!(f.run(&[b"TOPK.INCRBY", b"t", b"c", b"5"]), "*1\r\n$-1\r\n");
16509        assert_eq!(
16510            f.run(&[b"TOPK.QUERY", b"t", b"a", b"b", b"c"]),
16511            "*3\r\n:1\r\n:0\r\n:1\r\n"
16512        );
16513        // The table still counts what the kept set let go of.
16514        assert_eq!(
16515            f.run(&[b"TOPK.COUNT", b"t", b"a", b"b", b"c"]),
16516            "*3\r\n:11\r\n:1\r\n:6\r\n"
16517        );
16518        assert_eq!(f.run(&[b"TOPK.LIST", b"t"]), "*2\r\n$1\r\na\r\n$1\r\nc\r\n");
16519        assert_eq!(
16520            f.run(&[b"TOPK.LIST", b"t", b"WITHCOUNT"]),
16521            "*4\r\n$1\r\na\r\n:11\r\n$1\r\nc\r\n:6\r\n"
16522        );
16523        // Any prefix of the keyword turns the counts on, the empty string
16524        // included, and only a longer word or a different one is refused.
16525        assert_eq!(
16526            f.run(&[b"TOPK.LIST", b"t", b"w"]),
16527            f.run(&[b"TOPK.LIST", b"t", b"WITHCOUNT"])
16528        );
16529        assert_eq!(
16530            f.run(&[b"TOPK.LIST", b"t", b""]),
16531            f.run(&[b"TOPK.LIST", b"t", b"WITHCOUNT"])
16532        );
16533        assert_eq!(
16534            f.run(&[b"TOPK.LIST", b"t", b"WITHCOUNTS"]),
16535            "-WITHCOUNT keyword expected\r\n"
16536        );
16537        // And the keyword is looked at before the key, so a missing key with a
16538        // bad keyword complains about the keyword.
16539        assert_eq!(
16540            f.run(&[b"TOPK.LIST", b"missing", b"nope"]),
16541            "-WITHCOUNT keyword expected\r\n"
16542        );
16543        assert_eq!(
16544            f.run(&[b"TOPK.LIST", b"missing"]),
16545            "-TopK: key does not exist\r\n"
16546        );
16547        // An item counted zero times is kept and not listed.
16548        f.run(&[b"TOPK.RESERVE", b"z", b"3"]);
16549        assert_eq!(
16550            f.run(&[b"TOPK.INCRBY", b"z", b"nothing", b"0"]),
16551            "*1\r\n$-1\r\n"
16552        );
16553        assert_eq!(f.run(&[b"TOPK.QUERY", b"z", b"nothing"]), "*1\r\n:1\r\n");
16554        assert_eq!(f.run(&[b"TOPK.LIST", b"z"]), "*0\r\n");
16555    }
16556
16557    /// `TOPK.INCRBY` applies as it goes, so a bad increment leaves everything
16558    /// before it counted, and the reply counts what it wrote.
16559    #[test]
16560    fn an_increment_is_applied_as_it_goes_and_stops_at_a_bad_one() {
16561        let mut f = Fixture::new();
16562        f.run(&[b"TOPK.RESERVE", b"t", b"5", b"1000", b"5", b"0.9"]);
16563        // Three pairs, the middle one bad: two elements come back, one of them
16564        // the error, and the array header says two rather than three. That last
16565        // part is D-51 and it is why a client here stays in step.
16566        assert_eq!(
16567            f.run(&[b"TOPK.INCRBY", b"t", b"a", b"3", b"b", b"-1", b"c", b"4"]),
16568            format!(
16569                "*2\r\n$-1\r\n-{}\r\n",
16570                "TopK: increment must be an integer greater or equal to 0                            and smaller or equal to 100,000"
16571            )
16572        );
16573        assert_eq!(
16574            f.run(&[b"TOPK.COUNT", b"t", b"a", b"b", b"c"]),
16575            "*3\r\n:3\r\n:0\r\n:0\r\n"
16576        );
16577        // A hundred thousand is in and one more is out.
16578        assert_eq!(
16579            f.run(&[b"TOPK.INCRBY", b"t", b"a", b"100000"]),
16580            "*1\r\n$-1\r\n"
16581        );
16582        assert!(
16583            f.run(&[b"TOPK.INCRBY", b"t", b"a", b"100001"])
16584                .contains("smaller or equal to 100,000")
16585        );
16586        // Pairs have to be pairs.
16587        assert!(
16588            f.run(&[b"TOPK.INCRBY", b"t", b"a", b"1", b"b"])
16589                .contains("wrong number of arguments")
16590        );
16591        assert_eq!(f.run(&[b"TOPK.COUNT", b"t", b"a"]), "*1\r\n:100003\r\n");
16592    }
16593
16594    /// The RESP3 shapes, which are the two the protocols disagree about.
16595    #[test]
16596    fn a_query_is_a_bool_and_info_is_a_map_on_resp3() {
16597        let mut f = Fixture::new();
16598        f.run(&[b"HELLO", b"3"]);
16599        f.run(&[b"TOPK.RESERVE", b"t", b"2", b"8", b"7", b"0.5"]);
16600        f.run(&[b"TOPK.ADD", b"t", b"a"]);
16601        assert_eq!(
16602            f.run(&[b"TOPK.QUERY", b"t", b"a", b"b"]),
16603            "*2\r\n#t\r\n#f\r\n"
16604        );
16605        // The count stays an integer on both protocols.
16606        assert_eq!(f.run(&[b"TOPK.COUNT", b"t", b"a"]), "*1\r\n:1\r\n");
16607        assert_eq!(
16608            f.run(&[b"TOPK.INFO", b"t"]),
16609            "%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"
16610        );
16611        assert_eq!(f.run(&[b"TOPK.ADD", b"t", b"a"]), "*1\r\n_\r\n");
16612    }
16613
16614    /// A top k key answers the module sentences the other sketch families
16615    /// answer, and its own word for its type.
16616    #[test]
16617    fn a_top_k_sketch_is_a_module_key_to_the_rest_of_the_keyspace() {
16618        let mut f = Fixture::new();
16619        f.run(&[b"SET", b"s", b"text"]);
16620        for cmd in [
16621            vec![&b"TOPK.RESERVE"[..], b"s", b"5"],
16622            vec![&b"TOPK.ADD"[..], b"s", b"a"],
16623            vec![&b"TOPK.INCRBY"[..], b"s", b"a", b"1"],
16624            vec![&b"TOPK.QUERY"[..], b"s", b"a"],
16625            vec![&b"TOPK.COUNT"[..], b"s", b"a"],
16626            vec![&b"TOPK.LIST"[..], b"s"],
16627            vec![&b"TOPK.INFO"[..], b"s"],
16628        ] {
16629            let name = String::from_utf8_lossy(cmd[0]).into_owned();
16630            let reply = f.run(&cmd);
16631            assert!(
16632                reply.starts_with("-WRONGTYPE") || reply == "-TopK: key already exists\r\n",
16633                "{name}: {reply}"
16634            );
16635        }
16636        f.run(&[b"TOPK.RESERVE", b"t", b"5"]);
16637        assert_eq!(
16638            f.run(&[b"COPY", b"t", b"t2"]),
16639            "-ERR not supported for this module key\r\n"
16640        );
16641        assert_eq!(
16642            f.run(&[b"DUMP", b"t"]),
16643            "-ERR DUMP is not supported for this module key\r\n"
16644        );
16645        assert_eq!(f.run(&[b"EXPIRE", b"t", b"100"]), ":1\r\n");
16646        assert_eq!(f.run(&[b"PERSIST", b"t"]), ":1\r\n");
16647        assert_eq!(f.run(&[b"RENAME", b"t", b"t3"]), "+OK\r\n");
16648        assert_eq!(f.run(&[b"TYPE", b"t3"]), "+TopK-TYPE\r\n");
16649        assert_eq!(f.run(&[b"OBJECT", b"ENCODING", b"t3"]), "$3\r\nraw\r\n");
16650        assert_eq!(f.run(&[b"DEL", b"t3"]), ":1\r\n");
16651        // Every one of the six that is not the constructor says the same thing
16652        // about a key that is not there.
16653        assert_eq!(
16654            f.run(&[b"TOPK.INFO", b"t3"]),
16655            "-TopK: key does not exist\r\n"
16656        );
16657    }
16658
16659    // --------------------------------------------------------------- tdigest
16660
16661    /// `TDIGEST.CREATE` takes two arguments or four, and the keyword search is a
16662    /// search rather than a lookup.
16663    #[test]
16664    fn a_create_takes_two_arguments_or_four_and_reads_the_last_one() {
16665        let mut f = Fixture::new();
16666        assert_eq!(f.run(&[b"TDIGEST.CREATE", b"t"]), "+OK\r\n");
16667        // A hundred is the default and the capacity is six times it plus ten.
16668        assert_eq!(
16669            f.run(&[b"TDIGEST.INFO", b"t"]),
16670            "*18\r\n+Compression\r\n:100\r\n+Capacity\r\n:610\r\n+Merged nodes\r\n:0\r\n\
16671             +Unmerged nodes\r\n:0\r\n+Merged weight\r\n:0\r\n+Unmerged weight\r\n:0\r\n\
16672             +Observations\r\n:0\r\n+Total compressions\r\n:0\r\n+Memory usage\r\n:9840\r\n"
16673        );
16674        assert_eq!(
16675            f.run(&[b"TDIGEST.CREATE", b"t"]),
16676            "-ERR T-Digest: key already exists\r\n"
16677        );
16678        // Three arguments is an arity error and not a missing keyword.
16679        assert!(
16680            f.run(&[b"TDIGEST.CREATE", b"u", b"COMPRESSION"])
16681                .contains("wrong number of arguments")
16682        );
16683        assert_eq!(
16684            f.run(&[b"TDIGEST.CREATE", b"u", b"COMPRESSION", b"1000"]),
16685            "+OK\r\n"
16686        );
16687        assert_eq!(
16688            f.run(&[b"TDIGEST.CREATE", b"v", b"compression", b"1"]),
16689            "+OK\r\n"
16690        );
16691        // The word is looked for across both trailing arguments and the number
16692        // is then read out of the last one whatever was found, so this looks for
16693        // a number inside the word `COMPRESSION` and does not find one.
16694        assert_eq!(
16695            f.run(&[b"TDIGEST.CREATE", b"w", b"100", b"COMPRESSION"]),
16696            "-ERR T-Digest: error parsing compression parameter\r\n"
16697        );
16698        assert_eq!(
16699            f.run(&[b"TDIGEST.CREATE", b"w", b"NOPE", b"100"]),
16700            "-ERR T-Digest: wrong keyword\r\n"
16701        );
16702        assert_eq!(
16703            f.run(&[b"TDIGEST.CREATE", b"w", b"COMPRESSION", b"1.5"]),
16704            "-ERR T-Digest: error parsing compression parameter\r\n"
16705        );
16706        assert_eq!(
16707            f.run(&[b"TDIGEST.CREATE", b"w", b"COMPRESSION", b"0"]),
16708            "-ERR T-Digest: compression parameter needs to be a positive integer\r\n"
16709        );
16710        // The reference's own ceiling, which is where the capacity stops fitting
16711        // in an int, and one past it.
16712        assert_eq!(
16713            f.run(&[b"TDIGEST.CREATE", b"w", b"COMPRESSION", b"357913942"]),
16714            "-ERR T-Digest: allocation failed\r\n"
16715        );
16716        // And ours, which is a gibibyte of centroids and is D-52.
16717        assert_eq!(
16718            f.run(&[b"TDIGEST.CREATE", b"w", b"COMPRESSION", b"100000000"]),
16719            "-ERR T-Digest: allocation failed\r\n"
16720        );
16721        // The key is checked before the arguments, so a bad compression at a key
16722        // that is already a digest still says the key is taken.
16723        assert_eq!(
16724            f.run(&[b"TDIGEST.CREATE", b"t", b"COMPRESSION", b"0"]),
16725            "-ERR T-Digest: key already exists\r\n"
16726        );
16727    }
16728
16729    /// The four samples every note about this family is written against, and the
16730    /// answers a real 8.10.1 gives for them.
16731    #[test]
16732    fn the_quantile_family_answers_what_the_module_answers() {
16733        let mut f = Fixture::new();
16734        f.run(&[b"TDIGEST.CREATE", b"s"]);
16735        assert_eq!(
16736            f.run(&[b"TDIGEST.ADD", b"s", b"1", b"2", b"3", b"4"]),
16737            "+OK\r\n"
16738        );
16739        assert_eq!(f.run(&[b"TDIGEST.MIN", b"s"]), "$1\r\n1\r\n");
16740        assert_eq!(f.run(&[b"TDIGEST.MAX", b"s"]), "$1\r\n4\r\n");
16741        // The cdf of a sample is the weight below it plus half its own.
16742        assert_eq!(
16743            f.run(&[b"TDIGEST.CDF", b"s", b"1", b"2", b"3", b"4"]),
16744            "*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"
16745        );
16746        assert_eq!(
16747            f.run(&[b"TDIGEST.QUANTILE", b"s", b"0", b"0.5", b"1"]),
16748            "*3\r\n$1\r\n1\r\n$1\r\n3\r\n$1\r\n4\r\n"
16749        );
16750        // Out of order, the walk restarts, and 0.5 answers 3 either way while
16751        // the two after it are read from the front again.
16752        assert_eq!(
16753            f.run(&[b"TDIGEST.QUANTILE", b"s", b"0.5", b"0.1", b"0.9"]),
16754            "*3\r\n$1\r\n3\r\n$1\r\n1\r\n$1\r\n4\r\n"
16755        );
16756        assert_eq!(
16757            f.run(&[b"TDIGEST.RANK", b"s", b"0", b"1", b"3", b"4", b"5"]),
16758            "*5\r\n:-1\r\n:0\r\n:2\r\n:3\r\n:4\r\n"
16759        );
16760        assert_eq!(
16761            f.run(&[b"TDIGEST.REVRANK", b"s", b"0", b"1", b"3", b"4", b"5"]),
16762            "*5\r\n:4\r\n:3\r\n:1\r\n:0\r\n:-1\r\n"
16763        );
16764        assert_eq!(
16765            f.run(&[b"TDIGEST.BYRANK", b"s", b"0", b"1", b"3", b"4"]),
16766            "*4\r\n$1\r\n1\r\n$1\r\n2\r\n$1\r\n4\r\n$3\r\ninf\r\n"
16767        );
16768        assert_eq!(
16769            f.run(&[b"TDIGEST.BYREVRANK", b"s", b"0", b"1", b"3", b"4"]),
16770            "*4\r\n$1\r\n4\r\n$1\r\n3\r\n$1\r\n1\r\n$4\r\n-inf\r\n"
16771        );
16772        assert_eq!(
16773            f.run(&[b"TDIGEST.TRIMMED_MEAN", b"s", b"0", b"1"]),
16774            "$3\r\n2.5\r\n"
16775        );
16776        assert_eq!(
16777            f.run(&[b"TDIGEST.TRIMMED_MEAN", b"s", b"0.25", b"0.75"]),
16778            "$3\r\n2.5\r\n"
16779        );
16780        // The ranges, which are separate sentences from the parse failures.
16781        assert_eq!(
16782            f.run(&[b"TDIGEST.QUANTILE", b"s", b"1.1"]),
16783            "-ERR T-Digest: quantile should be in [0,1]\r\n"
16784        );
16785        assert_eq!(
16786            f.run(&[b"TDIGEST.QUANTILE", b"s", b"zzz"]),
16787            "-ERR T-Digest: error parsing quantile\r\n"
16788        );
16789        assert_eq!(
16790            f.run(&[b"TDIGEST.CDF", b"s", b"zzz"]),
16791            "-ERR T-Digest: error parsing cdf\r\n"
16792        );
16793        assert_eq!(
16794            f.run(&[b"TDIGEST.RANK", b"s", b"zzz"]),
16795            "-ERR T-Digest: error parsing value\r\n"
16796        );
16797        assert_eq!(
16798            f.run(&[b"TDIGEST.BYRANK", b"s", b"-1"]),
16799            "-ERR T-Digest: rank needs to be non negative\r\n"
16800        );
16801        assert_eq!(
16802            f.run(&[b"TDIGEST.BYRANK", b"s", b"1.5"]),
16803            "-ERR T-Digest: error parsing rank\r\n"
16804        );
16805        // Both cuts have their own parse sentence and share the range one, and
16806        // equal cuts are refused rather than answering nothing.
16807        assert_eq!(
16808            f.run(&[b"TDIGEST.TRIMMED_MEAN", b"s", b"zzz", b"0.9"]),
16809            "-ERR T-Digest: error parsing low_cut_percentile\r\n"
16810        );
16811        assert_eq!(
16812            f.run(&[b"TDIGEST.TRIMMED_MEAN", b"s", b"0.1", b"zzz"]),
16813            "-ERR T-Digest: error parsing high_cut_percentile\r\n"
16814        );
16815        assert_eq!(
16816            f.run(&[b"TDIGEST.TRIMMED_MEAN", b"s", b"0.1", b"1.1"]),
16817            "-ERR T-Digest: low_cut_percentile and high_cut_percentile should be in [0,1]\r\n"
16818        );
16819        assert_eq!(
16820            f.run(&[b"TDIGEST.TRIMMED_MEAN", b"s", b"0.5", b"0.5"]),
16821            "-ERR T-Digest: low_cut_percentile should be lower than high_cut_percentile\r\n"
16822        );
16823    }
16824
16825    /// An empty digest answers every question, and answers most of them with
16826    /// something that is not a number.
16827    #[test]
16828    fn an_empty_digest_has_an_answer_for_everything() {
16829        let mut f = Fixture::new();
16830        f.run(&[b"TDIGEST.CREATE", b"e"]);
16831        assert_eq!(f.run(&[b"TDIGEST.MIN", b"e"]), "$3\r\nnan\r\n");
16832        assert_eq!(f.run(&[b"TDIGEST.MAX", b"e"]), "$3\r\nnan\r\n");
16833        assert_eq!(
16834            f.run(&[b"TDIGEST.QUANTILE", b"e", b"0", b"1"]),
16835            "*2\r\n$3\r\nnan\r\n$3\r\nnan\r\n"
16836        );
16837        assert_eq!(f.run(&[b"TDIGEST.CDF", b"e", b"0"]), "*1\r\n$3\r\nnan\r\n");
16838        assert_eq!(
16839            f.run(&[b"TDIGEST.TRIMMED_MEAN", b"e", b"0.1", b"0.9"]),
16840            "$3\r\nnan\r\n"
16841        );
16842        // Minus two, which is a number no rank on a digest with samples in it
16843        // can ever be.
16844        assert_eq!(
16845            f.run(&[b"TDIGEST.RANK", b"e", b"0", b"1"]),
16846            "*2\r\n:-2\r\n:-2\r\n"
16847        );
16848        assert_eq!(
16849            f.run(&[b"TDIGEST.REVRANK", b"e", b"0", b"1"]),
16850            "*2\r\n:-2\r\n:-2\r\n"
16851        );
16852        assert_eq!(
16853            f.run(&[b"TDIGEST.BYRANK", b"e", b"0", b"5"]),
16854            "*2\r\n$3\r\nnan\r\n$3\r\nnan\r\n"
16855        );
16856        // A reset puts a digest with samples back into exactly this state.
16857        f.run(&[b"TDIGEST.ADD", b"e", b"1", b"2", b"3"]);
16858        assert_eq!(f.run(&[b"TDIGEST.RESET", b"e"]), "+OK\r\n");
16859        assert_eq!(f.run(&[b"TDIGEST.MIN", b"e"]), "$3\r\nnan\r\n");
16860        // Down to the compression count, so a reset digest and a fresh one of
16861        // the same compression report the same nine numbers.
16862        f.run(&[b"TDIGEST.CREATE", b"e2"]);
16863        assert_eq!(
16864            f.run(&[b"TDIGEST.INFO", b"e"]),
16865            f.run(&[b"TDIGEST.INFO", b"e2"])
16866        );
16867    }
16868
16869    /// The double parser is Redis's and not this engine's, and the two disagree
16870    /// at both ends of the range.
16871    #[test]
16872    fn a_sample_is_read_the_way_redis_reads_a_double() {
16873        let mut f = Fixture::new();
16874        f.run(&[b"TDIGEST.CREATE", b"a"]);
16875        // Overflow and underflow are parse failures rather than an infinity and
16876        // a zero, which is where this parts company with the rest of the engine.
16877        for bad in [
16878            &b"nan"[..],
16879            b"1e400",
16880            b"-1e400",
16881            b"1e309",
16882            b"1e-400",
16883            b"",
16884            b" 1",
16885            b"1 ",
16886            b"1e",
16887            b"--1",
16888        ] {
16889            assert_eq!(
16890                f.run(&[b"TDIGEST.ADD", b"a", bad]),
16891                "-ERR T-Digest: error parsing val parameter\r\n",
16892                "{}",
16893                String::from_utf8_lossy(bad)
16894            );
16895        }
16896        // An infinity spelled out parses and is then refused for being one, with
16897        // a different sentence.
16898        for word in [&b"inf"[..], b"-inf", b"+INF", b"Infinity"] {
16899            assert_eq!(
16900                f.run(&[b"TDIGEST.ADD", b"a", word]),
16901                "-ERR T-Digest: val parameter needs to be a finite number\r\n",
16902                "{}",
16903                String::from_utf8_lossy(word)
16904            );
16905        }
16906        // These all parse: hex, a bare point either side, and the smallest
16907        // subnormal the reference will take.
16908        for good in [&b"0x10"[..], b".5", b"1.", b"1e-320", b"-0", b"0"] {
16909            assert_eq!(
16910                f.run(&[b"TDIGEST.ADD", b"a", good]),
16911                "+OK\r\n",
16912                "{}",
16913                String::from_utf8_lossy(good)
16914            );
16915        }
16916        // Nothing landed from the failures, so six samples is what there is.
16917        assert!(
16918            f.run(&[b"TDIGEST.INFO", b"a"])
16919                .contains("Observations\r\n:6\r\n")
16920        );
16921        // Every value is parsed before any is added, so this whole command is a
16922        // no op.
16923        assert_eq!(
16924            f.run(&[b"TDIGEST.ADD", b"a", b"1", b"zzz"]),
16925            "-ERR T-Digest: error parsing val parameter\r\n"
16926        );
16927        assert!(
16928            f.run(&[b"TDIGEST.INFO", b"a"])
16929                .contains("Observations\r\n:6\r\n")
16930        );
16931    }
16932
16933    /// What a merge does to its destination, to its inputs and to the buffer
16934    /// split `TDIGEST.INFO` reports.
16935    #[test]
16936    fn a_merge_sweeps_the_destination_between_its_inputs() {
16937        let mut f = Fixture::new();
16938        f.run(&[b"TDIGEST.CREATE", b"m1", b"COMPRESSION", b"100"]);
16939        f.run(&[b"TDIGEST.ADD", b"m1", b"1", b"2", b"3"]);
16940        f.run(&[b"TDIGEST.CREATE", b"m2", b"COMPRESSION", b"200"]);
16941        f.run(&[b"TDIGEST.ADD", b"m2", b"4", b"5", b"6"]);
16942        assert_eq!(
16943            f.run(&[b"TDIGEST.MERGE", b"d", b"2", b"m1", b"m2"]),
16944            "+OK\r\n"
16945        );
16946        // The destination did not exist, so the compression is the largest of
16947        // the inputs. The three from the first input were swept in before the
16948        // three from the second arrived, which is the one visible effect of the
16949        // reference folding one input at a time.
16950        let info = f.run(&[b"TDIGEST.INFO", b"d"]);
16951        assert!(info.contains("Compression\r\n:200\r\n"), "{info}");
16952        assert!(info.contains("Merged nodes\r\n:3\r\n"), "{info}");
16953        assert!(info.contains("Unmerged nodes\r\n:3\r\n"), "{info}");
16954        assert!(info.contains("Total compressions\r\n:1\r\n"), "{info}");
16955        assert_eq!(f.run(&[b"TDIGEST.MIN", b"d"]), "$1\r\n1\r\n");
16956        assert_eq!(f.run(&[b"TDIGEST.MAX", b"d"]), "$1\r\n6\r\n");
16957        // Reading a source sweeps it too, so a merge writes to keys it only
16958        // reads from.
16959        assert!(
16960            f.run(&[b"TDIGEST.INFO", b"m1"])
16961                .contains("Merged nodes\r\n:3\r\n")
16962        );
16963        // Without OVERRIDE the destination joins its own inputs, so this takes
16964        // it to nine observations and keeps its own compression.
16965        f.run(&[b"TDIGEST.MERGE", b"d", b"1", b"m1"]);
16966        let info = f.run(&[b"TDIGEST.INFO", b"d"]);
16967        assert!(info.contains("Observations\r\n:9\r\n"), "{info}");
16968        assert!(info.contains("Compression\r\n:200\r\n"), "{info}");
16969        // With OVERRIDE the old destination is dropped and the compression goes
16970        // back to the largest of the inputs.
16971        f.run(&[b"TDIGEST.MERGE", b"d", b"1", b"m1", b"OVERRIDE"]);
16972        let info = f.run(&[b"TDIGEST.INFO", b"d"]);
16973        assert!(info.contains("Observations\r\n:3\r\n"), "{info}");
16974        assert!(info.contains("Compression\r\n:100\r\n"), "{info}");
16975        // And COMPRESSION beats both.
16976        f.run(&[b"TDIGEST.MERGE", b"d", b"1", b"m1", b"COMPRESSION", b"500"]);
16977        assert!(
16978            f.run(&[b"TDIGEST.INFO", b"d"])
16979                .contains("Compression\r\n:500\r\n")
16980        );
16981        // Naming the destination as a source folds it in twice.
16982        f.run(&[b"TDIGEST.MERGE", b"d", b"1", b"d"]);
16983        assert!(
16984            f.run(&[b"TDIGEST.INFO", b"d"])
16985                .contains("Observations\r\n:12\r\n")
16986        );
16987        // The arguments, in the order the reference checks them.
16988        assert_eq!(
16989            f.run(&[b"TDIGEST.MERGE", b"d", b"zzz", b"m1"]),
16990            "-ERR T-Digest: error parsing numkeys\r\n"
16991        );
16992        assert_eq!(
16993            f.run(&[b"TDIGEST.MERGE", b"d", b"0", b"m1"]),
16994            "-ERR T-Digest: numkeys needs to be a positive integer\r\n"
16995        );
16996        assert!(
16997            f.run(&[b"TDIGEST.MERGE", b"d", b"3", b"m1", b"m2"])
16998                .contains("wrong number of arguments")
16999        );
17000        assert!(
17001            f.run(&[b"TDIGEST.MERGE", b"d", b"1", b"m1", b"COMPRESSION"])
17002                .contains("wrong number of arguments")
17003        );
17004        assert_eq!(
17005            f.run(&[b"TDIGEST.MERGE", b"d", b"1", b"m1", b"NOPE"]),
17006            "-ERR T-Digest: wrong keyword\r\n"
17007        );
17008        // A source that is not there stops the whole thing, and the destination
17009        // is left as it was.
17010        assert_eq!(
17011            f.run(&[b"TDIGEST.MERGE", b"d", b"2", b"m1", b"gone"]),
17012            "-ERR T-Digest: key does not exist\r\n"
17013        );
17014        assert!(
17015            f.run(&[b"TDIGEST.INFO", b"d"])
17016                .contains("Observations\r\n:12\r\n")
17017        );
17018        // A destination that is not there and is also named as a source is the
17019        // same sentence rather than an empty merge.
17020        assert_eq!(
17021            f.run(&[b"TDIGEST.MERGE", b"gone", b"1", b"gone"]),
17022            "-ERR T-Digest: key does not exist\r\n"
17023        );
17024    }
17025
17026    /// The RESP3 shapes, which are the two the protocols disagree about.
17027    #[test]
17028    fn a_digest_answers_doubles_and_a_map_on_resp3() {
17029        let mut f = Fixture::new();
17030        f.run(&[b"HELLO", b"3"]);
17031        f.run(&[b"TDIGEST.CREATE", b"s"]);
17032        f.run(&[b"TDIGEST.ADD", b"s", b"1", b"2", b"3", b"4"]);
17033        assert_eq!(f.run(&[b"TDIGEST.MIN", b"s"]), ",1\r\n");
17034        assert_eq!(
17035            f.run(&[b"TDIGEST.QUANTILE", b"s", b"0", b"1"]),
17036            "*2\r\n,1\r\n,4\r\n"
17037        );
17038        assert_eq!(f.run(&[b"TDIGEST.CDF", b"s", b"1"]), "*1\r\n,0.125\r\n");
17039        // The two infinities and the NaN go out as the bare words.
17040        assert_eq!(f.run(&[b"TDIGEST.BYRANK", b"s", b"4"]), "*1\r\n,inf\r\n");
17041        assert_eq!(
17042            f.run(&[b"TDIGEST.BYREVRANK", b"s", b"4"]),
17043            "*1\r\n,-inf\r\n"
17044        );
17045        f.run(&[b"TDIGEST.CREATE", b"e"]);
17046        assert_eq!(f.run(&[b"TDIGEST.MIN", b"e"]), ",nan\r\n");
17047        // The ranks stay integers on both protocols.
17048        assert_eq!(f.run(&[b"TDIGEST.RANK", b"s", b"1"]), "*1\r\n:0\r\n");
17049        // Every question above swept the buffer in, so the four samples are all
17050        // merged by now and the compression count says it happened once.
17051        assert_eq!(
17052            f.run(&[b"TDIGEST.INFO", b"s"]),
17053            "%9\r\n+Compression\r\n:100\r\n+Capacity\r\n:610\r\n+Merged nodes\r\n:4\r\n\
17054             +Unmerged nodes\r\n:0\r\n+Merged weight\r\n:4\r\n+Unmerged weight\r\n:0\r\n\
17055             +Observations\r\n:4\r\n+Total compressions\r\n:1\r\n+Memory usage\r\n:9840\r\n"
17056        );
17057    }
17058
17059    /// A t digest key answers the module sentences the other sketch families
17060    /// answer, and its own word for its type.
17061    #[test]
17062    fn a_t_digest_is_a_module_key_to_the_rest_of_the_keyspace() {
17063        let mut f = Fixture::new();
17064        f.run(&[b"SET", b"s", b"text"]);
17065        for cmd in [
17066            vec![&b"TDIGEST.CREATE"[..], b"s"],
17067            vec![&b"TDIGEST.RESET"[..], b"s"],
17068            vec![&b"TDIGEST.ADD"[..], b"s", b"1"],
17069            vec![&b"TDIGEST.MIN"[..], b"s"],
17070            vec![&b"TDIGEST.MAX"[..], b"s"],
17071            vec![&b"TDIGEST.QUANTILE"[..], b"s", b"0.5"],
17072            vec![&b"TDIGEST.CDF"[..], b"s", b"1"],
17073            vec![&b"TDIGEST.TRIMMED_MEAN"[..], b"s", b"0.1", b"0.9"],
17074            vec![&b"TDIGEST.RANK"[..], b"s", b"1"],
17075            vec![&b"TDIGEST.REVRANK"[..], b"s", b"1"],
17076            vec![&b"TDIGEST.BYRANK"[..], b"s", b"0"],
17077            vec![&b"TDIGEST.BYREVRANK"[..], b"s", b"0"],
17078            vec![&b"TDIGEST.INFO"[..], b"s"],
17079        ] {
17080            let name = String::from_utf8_lossy(cmd[0]).into_owned();
17081            let reply = f.run(&cmd);
17082            assert!(reply.starts_with("-WRONGTYPE"), "{name}: {reply}");
17083        }
17084        // The merge checks its destination the same way, and its sources too.
17085        f.run(&[b"TDIGEST.CREATE", b"t"]);
17086        assert!(
17087            f.run(&[b"TDIGEST.MERGE", b"s", b"1", b"t"])
17088                .starts_with("-WRONGTYPE")
17089        );
17090        assert!(
17091            f.run(&[b"TDIGEST.MERGE", b"d", b"1", b"s"])
17092                .starts_with("-WRONGTYPE")
17093        );
17094        assert_eq!(
17095            f.run(&[b"COPY", b"t", b"t2"]),
17096            "-ERR not supported for this module key\r\n"
17097        );
17098        assert_eq!(
17099            f.run(&[b"DUMP", b"t"]),
17100            "-ERR DUMP is not supported for this module key\r\n"
17101        );
17102        assert_eq!(f.run(&[b"EXPIRE", b"t", b"100"]), ":1\r\n");
17103        assert_eq!(f.run(&[b"PERSIST", b"t"]), ":1\r\n");
17104        assert_eq!(f.run(&[b"RENAME", b"t", b"t3"]), "+OK\r\n");
17105        assert_eq!(f.run(&[b"TYPE", b"t3"]), "+TDIS-TYPE\r\n");
17106        assert_eq!(f.run(&[b"OBJECT", b"ENCODING", b"t3"]), "$3\r\nraw\r\n");
17107        assert_eq!(f.run(&[b"DEL", b"t3"]), ":1\r\n");
17108        // An empty digest is still a key, so the twelve that are not the
17109        // constructor all say the same thing once it is gone.
17110        assert_eq!(
17111            f.run(&[b"TDIGEST.INFO", b"t3"]),
17112            "-ERR T-Digest: key does not exist\r\n"
17113        );
17114        // The key is looked at before the arguments, so a bad argument at a key
17115        // that is not there still says the key is not there.
17116        assert_eq!(
17117            f.run(&[b"TDIGEST.QUANTILE", b"t3", b"zzz"]),
17118            "-ERR T-Digest: key does not exist\r\n"
17119        );
17120    }
17121
17122    // -------------------------------------------------------------------- ts
17123
17124    /// A `TS.INFO` reply with the memory usage taken out of it.
17125    ///
17126    /// That number is what a series costs here rather than what one costs in the
17127    /// module, which is D-53, and it moves whenever the layout of a chunk does.
17128    /// Everything either side of it is the wire contract and is worth pinning
17129    /// down exactly, so the tests below check the whole reply with the one
17130    /// number lifted out.
17131    fn without_memory(reply: &str) -> String {
17132        let head = "+memoryUsage\r\n:";
17133        let at = reply.find(head).expect("every TS.INFO reports memory");
17134        let rest = &reply[at + head.len()..];
17135        let end = rest.find("\r\n").expect("and it is a whole number");
17136        format!("{}{}", &reply[..at + head.len()], &rest[end..])
17137    }
17138
17139    /// A series is made empty and still says it has a chunk, and the options are
17140    /// read before the key is looked at.
17141    #[test]
17142    fn a_series_is_made_empty_and_reports_on_itself() {
17143        let mut f = Fixture::new();
17144        assert_eq!(f.run(&[b"TS.CREATE", b"t"]), "+OK\r\n");
17145        assert_eq!(f.run(&[b"TYPE", b"t"]), "+TSDB-TYPE\r\n");
17146        assert_eq!(f.run(&[b"OBJECT", b"ENCODING", b"t"]), "$3\r\nraw\r\n");
17147        // Fourteen fields, so twenty eight elements. An empty series reports one
17148        // chunk and zero at both ends, and neither the chunk type nor the
17149        // duplicate policy is ever a nil.
17150        assert_eq!(
17151            without_memory(&f.run(&[b"TS.INFO", b"t"])),
17152            "*28\r\n\
17153             +totalSamples\r\n:0\r\n\
17154             +memoryUsage\r\n:\r\n\
17155             +firstTimestamp\r\n:0\r\n\
17156             +lastTimestamp\r\n:0\r\n\
17157             +retentionTime\r\n:0\r\n\
17158             +chunkCount\r\n:1\r\n\
17159             +chunkSize\r\n:4096\r\n\
17160             +chunkType\r\n+compressed\r\n\
17161             +duplicatePolicy\r\n+block\r\n\
17162             +labels\r\n*0\r\n\
17163             +sourceKey\r\n$-1\r\n\
17164             +rules\r\n*0\r\n\
17165             +ignoreMaxTimeDiff\r\n:0\r\n\
17166             +ignoreMaxValDiff\r\n$1\r\n0\r\n"
17167        );
17168        // A key that is already there is about the key whatever it holds, and
17169        // the existence is what is checked rather than the type.
17170        assert_eq!(
17171            f.run(&[b"TS.CREATE", b"t"]),
17172            "-ERR TSDB: key already exists\r\n"
17173        );
17174        assert_eq!(f.run(&[b"SET", b"str", b"x"]), "+OK\r\n");
17175        assert_eq!(
17176            f.run(&[b"TS.CREATE", b"str"]),
17177            "-ERR TSDB: key already exists\r\n"
17178        );
17179        // But the arguments are read first, so a bad one at a key that is there
17180        // answers about the argument.
17181        assert_eq!(
17182            f.run(&[b"TS.CREATE", b"t", b"RETENTION", b"abc"]),
17183            "-ERR TSDB: Couldn't parse RETENTION\r\n"
17184        );
17185        // The seven that will not make a series say WRONGTYPE about a key
17186        // holding something else, where the two that would say a sentence.
17187        // The word is inside the sentence and not in front of it, because the
17188        // module writes its own error text and Redis puts ERR on the front of
17189        // anything a module writes.
17190        let wrong = "-ERR WRONGTYPE Operation against a key holding the wrong kind of value\r\n";
17191        assert_eq!(f.run(&[b"TS.INFO", b"str"]), wrong);
17192        assert_eq!(f.run(&[b"TS.GET", b"str"]), wrong);
17193        assert_eq!(f.run(&[b"TS.ALTER", b"str"]), wrong);
17194        assert_eq!(f.run(&[b"TS.DEL", b"str", b"0", b"1"]), wrong);
17195        assert_eq!(f.run(&[b"TS.INCRBY", b"str", b"1"]), wrong);
17196        assert_eq!(
17197            f.run(&[b"TS.ADD", b"str", b"1", b"1"]),
17198            "-ERR TSDB: the key is not a TSDB key\r\n"
17199        );
17200        // And the ones that will not make one say so about a key that is gone.
17201        assert_eq!(
17202            f.run(&[b"TS.INFO", b"nope"]),
17203            "-ERR TSDB: the key does not exist\r\n"
17204        );
17205        assert_eq!(
17206            f.run(&[b"TS.GET", b"nope"]),
17207            "-ERR TSDB: the key does not exist\r\n"
17208        );
17209        assert_eq!(
17210            f.run(&[b"TS.ALTER", b"nope"]),
17211            "-ERR TSDB: the key does not exist\r\n"
17212        );
17213        assert_eq!(
17214            f.run(&[b"TS.DEL", b"nope", b"1", b"2"]),
17215            "-ERR TSDB: the key does not exist\r\n"
17216        );
17217    }
17218
17219    /// Every option word, including the ones that are wrong, and the scan that
17220    /// finds them.
17221    #[test]
17222    fn the_options_are_a_keyword_scan_and_not_a_grammar() {
17223        let mut f = Fixture::new();
17224        assert_eq!(
17225            f.run(&[
17226                b"TS.CREATE",
17227                b"t",
17228                b"RETENTION",
17229                b"5000",
17230                b"ENCODING",
17231                b"UNCOMPRESSED",
17232                b"CHUNK_SIZE",
17233                b"128",
17234                b"DUPLICATE_POLICY",
17235                b"LAST",
17236                b"IGNORE",
17237                b"10",
17238                b"0.5",
17239                b"LABELS",
17240                b"room",
17241                b"kitchen"
17242            ]),
17243            "+OK\r\n"
17244        );
17245        let info = f.run(&[b"TS.INFO", b"t"]);
17246        assert!(info.contains("+retentionTime\r\n:5000\r\n"), "{info}");
17247        assert!(info.contains("+chunkSize\r\n:128\r\n"), "{info}");
17248        assert!(info.contains("+chunkType\r\n+uncompressed\r\n"), "{info}");
17249        assert!(info.contains("+duplicatePolicy\r\n+last\r\n"), "{info}");
17250        assert!(info.contains("+ignoreMaxTimeDiff\r\n:10\r\n"), "{info}");
17251        // A plain double here, where a sample value out of TS.GET is the
17252        // shortest digits that read back as the same number.
17253        assert!(
17254            info.contains("+ignoreMaxValDiff\r\n$3\r\n0.5\r\n"),
17255            "{info}"
17256        );
17257        assert!(
17258            info.contains("+labels\r\n*1\r\n*2\r\n$4\r\nroom\r\n$7\r\nkitchen\r\n"),
17259            "{info}"
17260        );
17261
17262        // A word that is not an option is read past rather than refused.
17263        assert_eq!(f.run(&[b"TS.CREATE", b"junk", b"FOO"]), "+OK\r\n");
17264        // LABELS eats everything after it in pairs, and the later scans still
17265        // look inside what it ate, so this sets a retention and stores a label
17266        // called RETENTION at the same time.
17267        assert_eq!(
17268            f.run(&[
17269                b"TS.CREATE",
17270                b"g",
17271                b"LABELS",
17272                b"a",
17273                b"b",
17274                b"RETENTION",
17275                b"5"
17276            ]),
17277            "+OK\r\n"
17278        );
17279        let greedy = f.run(&[b"TS.INFO", b"g"]);
17280        assert!(greedy.contains("+retentionTime\r\n:5\r\n"), "{greedy}");
17281        assert!(
17282            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"),
17283            "{greedy}"
17284        );
17285
17286        // Every way an option can be wrong, in the order the module reads them.
17287        assert_eq!(
17288            f.run(&[b"TS.CREATE", b"e", b"LABELS", b"a", b"b(c"]),
17289            "-ERR TSDB: Couldn't parse LABELS\r\n"
17290        );
17291        assert_eq!(
17292            f.run(&[b"TS.CREATE", b"e", b"LABELS", b"", b"b"]),
17293            "-ERR TSDB: Couldn't parse LABELS\r\n"
17294        );
17295        assert_eq!(
17296            f.run(&[b"TS.CREATE", b"e", b"RETENTION"]),
17297            "-ERR TSDB: Couldn't parse RETENTION\r\n"
17298        );
17299        // A retention below zero is one of the two the module writes with no
17300        // ERR in front of it, where one that is not a number gets one.
17301        assert_eq!(
17302            f.run(&[b"TS.CREATE", b"e", b"RETENTION", b"-1"]),
17303            "-TSDB: Couldn't parse RETENTION\r\n"
17304        );
17305        assert_eq!(
17306            f.run(&[b"TS.CREATE", b"e", b"CHUNK_SIZE", b"abc"]),
17307            "-ERR TSDB: Couldn't parse CHUNK_SIZE\r\n"
17308        );
17309        assert_eq!(
17310            f.run(&[b"TS.CREATE", b"e", b"CHUNK_SIZE", b"100"]),
17311            "-ERR TSDB: CHUNK_SIZE value must be a multiple of 8 in the range [48 .. 1048576]\r\n"
17312        );
17313        assert_eq!(
17314            f.run(&[b"TS.CREATE", b"e", b"ENCODING", b"nope"]),
17315            "-ERR TSDB: unknown ENCODING parameter\r\n"
17316        );
17317        // And an ENCODING with nothing behind it is an arity error where every
17318        // other keyword in the same spot is a sentence.
17319        assert!(
17320            f.run(&[b"TS.CREATE", b"e", b"ENCODING"])
17321                .contains("wrong number of arguments for 'ts.create' command")
17322        );
17323        assert_eq!(
17324            f.run(&[b"TS.CREATE", b"e", b"DUPLICATE_POLICY"]),
17325            "-ERR TSDB: Couldn't parse DUPLICATE_POLICY\r\n"
17326        );
17327        assert_eq!(
17328            f.run(&[b"TS.CREATE", b"e", b"DUPLICATE_POLICY", b"nope"]),
17329            "-ERR TSDB: Unknown DUPLICATE_POLICY\r\n"
17330        );
17331        assert_eq!(
17332            f.run(&[b"TS.CREATE", b"e", b"IGNORE", b"10"]),
17333            "-ERR TSDB: Couldn't parse IGNORE\r\n"
17334        );
17335        assert_eq!(
17336            f.run(&[b"TS.CREATE", b"e", b"IGNORE", b"-1", b"1"]),
17337            "-ERR TSDB: IGNORE arguments cannot be negative\r\n"
17338        );
17339        assert_eq!(f.run(&[b"EXISTS", b"e"]), ":0\r\n");
17340
17341        // An alter changes what was named and leaves the rest alone, and reads
17342        // an encoding only far enough to refuse a bad one.
17343        assert_eq!(f.run(&[b"TS.ALTER", b"t", b"RETENTION", b"9"]), "+OK\r\n");
17344        let after = f.run(&[b"TS.INFO", b"t"]);
17345        assert!(after.contains("+retentionTime\r\n:9\r\n"), "{after}");
17346        assert!(after.contains("+chunkSize\r\n:128\r\n"), "{after}");
17347        assert!(after.contains("+duplicatePolicy\r\n+last\r\n"), "{after}");
17348        assert_eq!(
17349            f.run(&[b"TS.ALTER", b"t", b"ENCODING", b"nope"]),
17350            "-ERR TSDB: unknown ENCODING parameter\r\n"
17351        );
17352        // An encoding it does take is still not applied.
17353        assert_eq!(
17354            f.run(&[b"TS.ALTER", b"t", b"ENCODING", b"COMPRESSED"]),
17355            "+OK\r\n"
17356        );
17357        assert!(
17358            f.run(&[b"TS.INFO", b"t"])
17359                .contains("+chunkType\r\n+uncompressed\r\n")
17360        );
17361    }
17362
17363    /// Samples go in, come back out and are refused for the reasons the module
17364    /// refuses them.
17365    #[test]
17366    fn samples_land_where_they_are_put_and_the_newest_comes_back() {
17367        let mut f = Fixture::new();
17368        assert_eq!(f.run(&[b"TS.ADD", b"t", b"100", b"1.5"]), ":100\r\n");
17369        // The series was made on the way in.
17370        assert_eq!(f.run(&[b"TYPE", b"t"]), "+TSDB-TYPE\r\n");
17371        assert_eq!(f.run(&[b"TS.ADD", b"t", b"200", b"2"]), ":200\r\n");
17372        // A sample value goes out as a simple string of the shortest digits
17373        // that read back as the same number.
17374        assert_eq!(f.run(&[b"TS.GET", b"t"]), "*2\r\n:200\r\n+2\r\n");
17375        assert_eq!(f.run(&[b"TS.ADD", b"t", b"300", b"1e300"]), ":300\r\n");
17376        assert_eq!(f.run(&[b"TS.GET", b"t"]), "*2\r\n:300\r\n+1E300\r\n");
17377        // An empty series has no newest sample and answers an empty array
17378        // rather than a nil.
17379        assert_eq!(f.run(&[b"TS.CREATE", b"empty"]), "+OK\r\n");
17380        assert_eq!(f.run(&[b"TS.GET", b"empty"]), "*0\r\n");
17381
17382        // The value is read before the key, so a bad one against a key holding
17383        // a string is about the value.
17384        assert_eq!(f.run(&[b"SET", b"str", b"x"]), "+OK\r\n");
17385        assert_eq!(
17386            f.run(&[b"TS.ADD", b"str", b"1", b".5"]),
17387            "-ERR TSDB: invalid value\r\n"
17388        );
17389        // The grammar is tighter than the one a number argument usually gets:
17390        // no leading plus, no bare fraction, no infinity and nothing that does
17391        // not fit.
17392        for bad in [
17393            &b".5"[..],
17394            b"1.",
17395            b"+1",
17396            b" 1",
17397            b"0x10",
17398            b"inf",
17399            b"1e400",
17400            b"--1",
17401            b"1e",
17402        ] {
17403            assert_eq!(
17404                f.run(&[b"TS.ADD", b"v", b"1", bad]),
17405                "-ERR TSDB: invalid value\r\n",
17406                "{}",
17407                String::from_utf8_lossy(bad)
17408            );
17409        }
17410        // And a reading that is not a number is one of three words.
17411        assert_eq!(f.run(&[b"TS.ADD", b"v", b"1", b"NaN"]), ":1\r\n");
17412
17413        // A timestamp that is not a number, and one that is and is below zero,
17414        // are two different sentences.
17415        assert_eq!(
17416            f.run(&[b"TS.ADD", b"t", b"abc", b"1"]),
17417            "-ERR TSDB: invalid timestamp\r\n"
17418        );
17419        assert_eq!(
17420            f.run(&[b"TS.ADD", b"t", b"-1", b"1"]),
17421            "-ERR TSDB: invalid timestamp, must be a nonnegative integer\r\n"
17422        );
17423
17424        // A repeated timestamp is blocked by default, and ON_DUPLICATE on the
17425        // command beats what the series was told.
17426        assert_eq!(
17427            f.run(&[b"TS.ADD", b"t", b"300", b"7"]),
17428            "-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"
17429        );
17430        assert_eq!(
17431            f.run(&[b"TS.ADD", b"t", b"300", b"7", b"ON_DUPLICATE", b"LAST"]),
17432            ":300\r\n"
17433        );
17434        assert_eq!(f.run(&[b"TS.GET", b"t"]), "*2\r\n:300\r\n+7\r\n");
17435        // ON_DUPLICATE is only read when the key was already there, which is
17436        // why a policy word that is not a policy passes on a fresh key.
17437        assert_eq!(
17438            f.run(&[b"TS.ADD", b"fresh", b"1", b"1", b"ON_DUPLICATE", b"nope"]),
17439            ":1\r\n"
17440        );
17441        assert_eq!(
17442            f.run(&[b"TS.ADD", b"fresh", b"2", b"1", b"ON_DUPLICATE", b"nope"]),
17443            "-ERR TSDB: Unknown DUPLICATE_POLICY\r\n"
17444        );
17445
17446        // Retention is exact and it is checked before anything else happens, so
17447        // a sample landing behind the window is refused rather than trimmed.
17448        assert_eq!(f.run(&[b"TS.CREATE", b"r", b"RETENTION", b"50"]), "+OK\r\n");
17449        assert_eq!(f.run(&[b"TS.ADD", b"r", b"1000", b"1"]), ":1000\r\n");
17450        assert_eq!(f.run(&[b"TS.ADD", b"r", b"960", b"1"]), ":960\r\n");
17451        assert_eq!(
17452            f.run(&[b"TS.ADD", b"r", b"940", b"1"]),
17453            "-ERR TSDB: Timestamp is older than retention\r\n"
17454        );
17455        // And the window trims as it moves.
17456        assert_eq!(f.run(&[b"TS.ADD", b"r", b"1100", b"1"]), ":1100\r\n");
17457        assert!(
17458            f.run(&[b"TS.INFO", b"r"])
17459                .contains("+totalSamples\r\n:1\r\n")
17460        );
17461
17462        // An ignore window drops a sample close enough to the newest one to be
17463        // uninteresting, and answers the newest timestamp so a client can tell.
17464        assert_eq!(
17465            f.run(&[
17466                b"TS.CREATE",
17467                b"i",
17468                b"DUPLICATE_POLICY",
17469                b"LAST",
17470                b"IGNORE",
17471                b"10",
17472                b"0.5"
17473            ]),
17474            "+OK\r\n"
17475        );
17476        assert_eq!(f.run(&[b"TS.ADD", b"i", b"1000", b"1"]), ":1000\r\n");
17477        assert_eq!(f.run(&[b"TS.ADD", b"i", b"1005", b"1.2"]), ":1000\r\n");
17478        assert_eq!(f.run(&[b"TS.ADD", b"i", b"1005", b"9"]), ":1005\r\n");
17479    }
17480
17481    /// Every triple in a `TS.MADD` is answered on its own, and none of them
17482    /// makes a series.
17483    #[test]
17484    fn a_madd_answers_each_triple_and_creates_nothing() {
17485        let mut f = Fixture::new();
17486        assert_eq!(f.run(&[b"TS.CREATE", b"a"]), "+OK\r\n");
17487        assert_eq!(f.run(&[b"TS.CREATE", b"b"]), "+OK\r\n");
17488        assert_eq!(
17489            f.run(&[
17490                b"TS.MADD", b"a", b"100", b"1", b"b", b"100", b"2", b"a", b"200", b"3"
17491            ]),
17492            "*3\r\n:100\r\n:100\r\n:200\r\n"
17493        );
17494        // A key that is not a series is an error in its own slot and the ones
17495        // after it still land.
17496        assert_eq!(f.run(&[b"SET", b"str", b"x"]), "+OK\r\n");
17497        assert_eq!(
17498            f.run(&[
17499                b"TS.MADD", b"gone", b"1", b"1", b"str", b"1", b"1", b"a", b"300", b"4"
17500            ]),
17501            "*3\r\n\
17502             -ERR TSDB: the key is not a TSDB key\r\n\
17503             -ERR TSDB: the key is not a TSDB key\r\n\
17504             :300\r\n"
17505        );
17506        assert_eq!(f.run(&[b"EXISTS", b"gone"]), ":0\r\n");
17507        // A bad value and a bad timestamp are answered in their slots too.
17508        assert_eq!(
17509            f.run(&[b"TS.MADD", b"a", b"400", b"zzz", b"a", b"abc", b"1"]),
17510            "*2\r\n-ERR TSDB: invalid value\r\n-ERR TSDB: invalid timestamp\r\n"
17511        );
17512        // And a list that is not made of triples is an arity error.
17513        assert!(
17514            f.run(&[b"TS.MADD", b"a", b"1", b"1", b"a"])
17515                .contains("wrong number of arguments for 'ts.madd' command")
17516        );
17517    }
17518
17519    /// The two increments, which only ever write forwards.
17520    #[test]
17521    fn an_increment_walks_the_newest_value_up_and_down() {
17522        let mut f = Fixture::new();
17523        assert_eq!(
17524            f.run(&[b"TS.INCRBY", b"t", b"5", b"TIMESTAMP", b"100"]),
17525            ":100\r\n"
17526        );
17527        assert_eq!(
17528            f.run(&[b"TS.INCRBY", b"t", b"5", b"TIMESTAMP", b"100"]),
17529            ":100\r\n"
17530        );
17531        // Two on one timestamp add up rather than collide, because the sample
17532        // goes in under the last policy whatever the series says.
17533        assert_eq!(f.run(&[b"TS.GET", b"t"]), "*2\r\n:100\r\n+10\r\n");
17534        assert_eq!(
17535            f.run(&[b"TS.DECRBY", b"t", b"3", b"TIMESTAMP", b"200"]),
17536            ":200\r\n"
17537        );
17538        assert_eq!(f.run(&[b"TS.GET", b"t"]), "*2\r\n:200\r\n+7\r\n");
17539        // A timestamp behind the newest sample is the other of the two errors
17540        // the module writes with no ERR in front of it.
17541        assert_eq!(
17542            f.run(&[b"TS.INCRBY", b"t", b"1", b"TIMESTAMP", b"150"]),
17543            "-TSDB: timestamp must be equal to or higher than the maximum existing timestamp\r\n"
17544        );
17545        // The increment goes through the ordinary number reader, so it takes
17546        // what a sample value will not and refuses a NaN that a sample value
17547        // takes.
17548        assert_eq!(
17549            f.run(&[b"TS.INCRBY", b"p", b"+5", b"TIMESTAMP", b"1"]),
17550            ":1\r\n"
17551        );
17552        assert_eq!(
17553            f.run(&[b"TS.INCRBY", b"q", b".5", b"TIMESTAMP", b"1"]),
17554            ":1\r\n"
17555        );
17556        assert_eq!(
17557            f.run(&[b"TS.INCRBY", b"t", b"nan"]),
17558            "-ERR TSDB: invalid increase/decrease value\r\n"
17559        );
17560        assert_eq!(
17561            f.run(&[b"TS.INCRBY", b"t", b"zzz"]),
17562            "-ERR TSDB: invalid increase/decrease value\r\n"
17563        );
17564        // A key holding something else is WRONGTYPE and is answered before the
17565        // number is looked at.
17566        assert_eq!(f.run(&[b"SET", b"str", b"x"]), "+OK\r\n");
17567        assert_eq!(
17568            f.run(&[b"TS.INCRBY", b"str", b"zzz"]),
17569            "-ERR WRONGTYPE Operation against a key holding the wrong kind of value\r\n"
17570        );
17571        // A TIMESTAMP keyword with nothing behind it is about the timestamp.
17572        // The reference reads one past the end of its own arguments here and
17573        // answers whatever was in that memory, so there is nothing to copy and
17574        // this answers the same thing every time.
17575        assert_eq!(
17576            f.run(&[b"TS.INCRBY", b"t", b"1", b"TIMESTAMP"]),
17577            "-ERR TSDB: invalid timestamp\r\n"
17578        );
17579        // And one behind a LABELS is a label name rather than the keyword, so
17580        // this lands at the clock rather than at 5.
17581        assert_eq!(
17582            f.run(&[b"TS.INCRBY", b"lab", b"1", b"LABELS", b"TIMESTAMP", b"5"]),
17583            format!(":{}\r\n", f.server.now_ms())
17584        );
17585        // Adding to a series whose newest value is not a number has no answer.
17586        assert_eq!(f.run(&[b"TS.ADD", b"n", b"1", b"nan"]), ":1\r\n");
17587        assert_eq!(
17588            f.run(&[b"TS.INCRBY", b"n", b"1", b"TIMESTAMP", b"2"]),
17589            "-ERR TSDB: cannot increment/decrement NaN value\r\n"
17590        );
17591    }
17592
17593    /// Deleting a span, both ends included.
17594    #[test]
17595    fn deleting_takes_out_a_span_and_answers_how_many_went() {
17596        let mut f = Fixture::new();
17597        for at in [b"100".as_slice(), b"200", b"300", b"400"] {
17598            f.run(&[b"TS.ADD", b"t", at, b"1"]);
17599        }
17600        assert_eq!(f.run(&[b"TS.DEL", b"t", b"200", b"300"]), ":2\r\n");
17601        assert!(
17602            f.run(&[b"TS.INFO", b"t"])
17603                .contains("+totalSamples\r\n:2\r\n")
17604        );
17605        // Ends the wrong way round take nothing out rather than being an error.
17606        assert_eq!(f.run(&[b"TS.DEL", b"t", b"400", b"100"]), ":0\r\n");
17607        // The two open ends.
17608        assert_eq!(f.run(&[b"TS.DEL", b"t", b"-", b"+"]), ":2\r\n");
17609        // A series everything has been deleted from keeps its chunk and reports
17610        // zero at both ends again.
17611        let empty = f.run(&[b"TS.INFO", b"t"]);
17612        assert!(empty.contains("+totalSamples\r\n:0\r\n"), "{empty}");
17613        assert!(empty.contains("+chunkCount\r\n:1\r\n"), "{empty}");
17614        assert!(empty.contains("+firstTimestamp\r\n:0\r\n"), "{empty}");
17615        assert!(empty.contains("+lastTimestamp\r\n:0\r\n"), "{empty}");
17616        assert_eq!(f.run(&[b"TS.DEL", b"t", b"0", b"1000"]), ":0\r\n");
17617        // The two ends have their own sentences.
17618        assert_eq!(
17619            f.run(&[b"TS.DEL", b"t", b"abc", b"5"]),
17620            "-ERR TSDB: wrong fromTimestamp\r\n"
17621        );
17622        assert_eq!(
17623            f.run(&[b"TS.DEL", b"t", b"5", b"abc"]),
17624            "-ERR TSDB: wrong toTimestamp\r\n"
17625        );
17626        assert_eq!(
17627            f.run(&[b"TS.DEL", b"t", b"-5", b"5"]),
17628            "-ERR TSDB: wrong fromTimestamp\r\n"
17629        );
17630    }
17631
17632    /// What RESP3 changes, which is the two places a number is written and the
17633    /// shape of `TS.INFO`.
17634    #[test]
17635    fn resp3_writes_a_sample_as_a_double_and_the_info_as_a_map() {
17636        let mut f = Fixture::new();
17637        f.out = Out::new(Proto::Resp3);
17638        assert_eq!(
17639            f.run(&[b"TS.CREATE", b"t", b"LABELS", b"room", b"kitchen"]),
17640            "+OK\r\n"
17641        );
17642        assert_eq!(f.run(&[b"TS.ADD", b"t", b"100", b"1e300"]), ":100\r\n");
17643        // A double rather than the simple string RESP2 gets.
17644        assert_eq!(f.run(&[b"TS.GET", b"t"]), "*2\r\n:100\r\n,1e+300\r\n");
17645        assert_eq!(
17646            without_memory(&f.run(&[b"TS.INFO", b"t"])),
17647            "%14\r\n\
17648             +totalSamples\r\n:1\r\n\
17649             +memoryUsage\r\n:\r\n\
17650             +firstTimestamp\r\n:100\r\n\
17651             +lastTimestamp\r\n:100\r\n\
17652             +retentionTime\r\n:0\r\n\
17653             +chunkCount\r\n:1\r\n\
17654             +chunkSize\r\n:4096\r\n\
17655             +chunkType\r\n+compressed\r\n\
17656             +duplicatePolicy\r\n+block\r\n\
17657             +labels\r\n%1\r\n$4\r\nroom\r\n$7\r\nkitchen\r\n\
17658             +sourceKey\r\n_\r\n\
17659             +rules\r\n%0\r\n\
17660             +ignoreMaxTimeDiff\r\n:0\r\n\
17661             +ignoreMaxValDiff\r\n,0\r\n"
17662        );
17663    }
17664
17665    /// Reading a span back, both ways round, with the two ends and the three
17666    /// things that trim what comes out.
17667    #[test]
17668    fn a_range_walks_a_span_and_a_revrange_walks_it_backwards() {
17669        let mut f = Fixture::new();
17670        for (at, v) in [
17671            (b"100".as_slice(), b"1".as_slice()),
17672            (b"200", b"2"),
17673            (b"300", b"3"),
17674            (b"400", b"4"),
17675        ] {
17676            f.run(&[b"TS.ADD", b"t", at, v]);
17677        }
17678        assert_eq!(
17679            f.run(&[b"TS.RANGE", b"t", b"-", b"+"]),
17680            "*4\r\n*2\r\n:100\r\n+1\r\n*2\r\n:200\r\n+2\r\n\
17681             *2\r\n:300\r\n+3\r\n*2\r\n:400\r\n+4\r\n"
17682        );
17683        // Both ends are included.
17684        assert_eq!(
17685            f.run(&[b"TS.RANGE", b"t", b"150", b"350"]),
17686            "*2\r\n*2\r\n:200\r\n+2\r\n*2\r\n:300\r\n+3\r\n"
17687        );
17688        // Backwards, and the count takes from the front of what comes out, so
17689        // backwards it takes the newest.
17690        assert_eq!(
17691            f.run(&[b"TS.REVRANGE", b"t", b"-", b"+", b"COUNT", b"2"]),
17692            "*2\r\n*2\r\n:400\r\n+4\r\n*2\r\n:300\r\n+3\r\n"
17693        );
17694        // Ends the wrong way round are empty rather than an error.
17695        assert_eq!(f.run(&[b"TS.RANGE", b"t", b"400", b"100"]), "*0\r\n");
17696        // The two filters.
17697        assert_eq!(
17698            f.run(&[
17699                b"TS.RANGE",
17700                b"t",
17701                b"-",
17702                b"+",
17703                b"FILTER_BY_VALUE",
17704                b"2",
17705                b"3"
17706            ]),
17707            "*2\r\n*2\r\n:200\r\n+2\r\n*2\r\n:300\r\n+3\r\n"
17708        );
17709        assert_eq!(
17710            f.run(&[
17711                b"TS.RANGE",
17712                b"t",
17713                b"-",
17714                b"+",
17715                b"FILTER_BY_TS",
17716                b"100",
17717                b"400"
17718            ]),
17719            "*2\r\n*2\r\n:100\r\n+1\r\n*2\r\n:400\r\n+4\r\n"
17720        );
17721        // A word that is not an option is ignored wherever it sits.
17722        assert_eq!(
17723            f.run(&[
17724                b"TS.RANGE",
17725                b"t",
17726                b"-",
17727                b"+",
17728                b"ZZZ",
17729                b"FILTER_BY_TS",
17730                b"400"
17731            ]),
17732            "*1\r\n*2\r\n:400\r\n+4\r\n"
17733        );
17734        // `LATEST` means nothing until there is a compaction rule to follow.
17735        assert_eq!(
17736            f.run(&[b"TS.RANGE", b"t", b"-", b"+", b"LATEST", b"COUNT", b"1"]),
17737            "*1\r\n*2\r\n:100\r\n+1\r\n"
17738        );
17739    }
17740
17741    /// The bucketing, which is one column a reduction and a flat row.
17742    #[test]
17743    fn aggregation_puts_one_column_a_reduction_in_a_flat_row() {
17744        let mut f = Fixture::new();
17745        for (at, v) in [
17746            (b"100".as_slice(), b"1".as_slice()),
17747            (b"200", b"2"),
17748            (b"300", b"3"),
17749            (b"400", b"4"),
17750        ] {
17751            f.run(&[b"TS.ADD", b"t", at, v]);
17752        }
17753        assert_eq!(
17754            f.run(&[
17755                b"TS.RANGE",
17756                b"t",
17757                b"-",
17758                b"+",
17759                b"AGGREGATION",
17760                b"avg",
17761                b"200"
17762            ]),
17763            "*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"
17764        );
17765        // Three reductions is a row of four and not a row of two with a nested
17766        // three in it.
17767        assert_eq!(
17768            f.run(&[
17769                b"TS.RANGE",
17770                b"t",
17771                b"-",
17772                b"+",
17773                b"AGGREGATION",
17774                b"min,max,count",
17775                b"200"
17776            ]),
17777            "*3\r\n\
17778             *4\r\n:0\r\n+1\r\n+1\r\n+1\r\n\
17779             *4\r\n:200\r\n+2\r\n+3\r\n+2\r\n\
17780             *4\r\n:400\r\n+4\r\n+4\r\n+1\r\n"
17781        );
17782        // The timestamp a bucket is reported under.
17783        assert_eq!(
17784            f.run(&[
17785                b"TS.RANGE",
17786                b"t",
17787                b"-",
17788                b"+",
17789                b"AGGREGATION",
17790                b"avg",
17791                b"200",
17792                b"BUCKETTIMESTAMP",
17793                b"+"
17794            ]),
17795            "*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"
17796        );
17797        // An alignment moves where the bucket edges land.
17798        assert_eq!(
17799            f.run(&[
17800                b"TS.RANGE",
17801                b"t",
17802                b"100",
17803                b"400",
17804                b"ALIGN",
17805                b"100",
17806                b"AGGREGATION",
17807                b"sum",
17808                b"200"
17809            ]),
17810            "*2\r\n*2\r\n:100\r\n+3\r\n*2\r\n:300\r\n+7\r\n"
17811        );
17812        // A `COUNT` sitting where the reduction name belongs is that name, and
17813        // the scan for a real one starts again two words later.
17814        assert_eq!(
17815            f.run(&[
17816                b"TS.RANGE",
17817                b"t",
17818                b"-",
17819                b"+",
17820                b"AGGREGATION",
17821                b"count",
17822                b"200"
17823            ]),
17824            "*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"
17825        );
17826        assert_eq!(
17827            f.run(&[
17828                b"TS.RANGE",
17829                b"t",
17830                b"-",
17831                b"+",
17832                b"AGGREGATION",
17833                b"count",
17834                b"200",
17835                b"COUNT",
17836                b"1"
17837            ]),
17838            "*1\r\n*2\r\n:0\r\n+1\r\n"
17839        );
17840    }
17841
17842    /// `EMPTY` fills the gaps between readings and nothing else, and `last`
17843    /// carries two different things depending on which kind of empty it is.
17844    #[test]
17845    fn empty_fills_a_gap_and_last_carries_the_reading_before_it() {
17846        let mut f = Fixture::new();
17847        for (at, v) in [
17848            (b"0".as_slice(), b"1".as_slice()),
17849            (b"100", b"2"),
17850            (b"500", b"nan"),
17851            (b"600", b"3"),
17852        ] {
17853            f.run(&[b"TS.ADD", b"g", at, v]);
17854        }
17855        // Without `EMPTY` the buckets with nothing in them are not there at all,
17856        // and neither is the one holding only a reading that is not a number.
17857        assert_eq!(
17858            f.run(&[
17859                b"TS.RANGE",
17860                b"g",
17861                b"-",
17862                b"+",
17863                b"AGGREGATION",
17864                b"avg",
17865                b"100"
17866            ]),
17867            "*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"
17868        );
17869        // The sum of nothing is zero rather than not a number.
17870        assert_eq!(
17871            f.run(&[
17872                b"TS.RANGE",
17873                b"g",
17874                b"-",
17875                b"+",
17876                b"AGGREGATION",
17877                b"sum",
17878                b"100",
17879                b"EMPTY"
17880            ]),
17881            "*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\
17882             *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\
17883             *2\r\n:600\r\n+3\r\n"
17884        );
17885        // Buckets 200 through 400 have no readings at all and carry the reading
17886        // before the gap either way round. Bucket 500 has a reading that is not
17887        // a number, so it carries whatever the bucket before it in the reading
17888        // direction answered, which is 2 forwards and 3 backwards.
17889        assert_eq!(
17890            f.run(&[
17891                b"TS.RANGE",
17892                b"g",
17893                b"-",
17894                b"+",
17895                b"AGGREGATION",
17896                b"last",
17897                b"100",
17898                b"EMPTY"
17899            ]),
17900            "*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\
17901             *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\
17902             *2\r\n:600\r\n+3\r\n"
17903        );
17904        assert_eq!(
17905            f.run(&[
17906                b"TS.REVRANGE",
17907                b"g",
17908                b"-",
17909                b"+",
17910                b"AGGREGATION",
17911                b"last",
17912                b"100",
17913                b"EMPTY"
17914            ]),
17915            "*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\
17916             *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\
17917             *2\r\n:0\r\n+1\r\n"
17918        );
17919        // And a window that opens on that bucket has nothing in range before it
17920        // to carry, so it answers not a number.
17921        assert_eq!(
17922            f.run(&[
17923                b"TS.RANGE",
17924                b"g",
17925                b"500",
17926                b"600",
17927                b"AGGREGATION",
17928                b"last",
17929                b"100",
17930                b"EMPTY"
17931            ]),
17932            "*2\r\n*2\r\n:500\r\n+NaN\r\n*2\r\n:600\r\n+3\r\n"
17933        );
17934    }
17935
17936    /// The sentences a read answers when its options do not add up, which are
17937    /// the module's own word for word.
17938    #[test]
17939    fn a_range_says_what_the_module_says_when_the_options_do_not_add_up() {
17940        let mut f = Fixture::new();
17941        f.run(&[b"TS.ADD", b"t", b"100", b"1"]);
17942        f.run(&[b"SET", b"str", b"x"]);
17943        let cases: &[(&[&[u8]], &str)] = &[
17944            (
17945                &[b"TS.RANGE", b"t"],
17946                "-ERR wrong number of arguments for 'ts.range' command\r\n",
17947            ),
17948            // The key is resolved before a single option is read.
17949            (
17950                &[b"TS.RANGE", b"gone", b"-", b"+", b"COUNT", b"x"],
17951                "-ERR TSDB: the key does not exist\r\n",
17952            ),
17953            (
17954                &[b"TS.RANGE", b"str", b"-", b"+"],
17955                "-ERR WRONGTYPE Operation against a key holding the wrong kind of value\r\n",
17956            ),
17957            (
17958                &[b"TS.RANGE", b"t", b"abc", b"+"],
17959                "-ERR TSDB: wrong fromTimestamp\r\n",
17960            ),
17961            (
17962                &[b"TS.RANGE", b"t", b"-", b"abc"],
17963                "-ERR TSDB: wrong toTimestamp\r\n",
17964            ),
17965            (
17966                &[b"TS.RANGE", b"t", b"-", b"+", b"COUNT"],
17967                "-ERR TSDB: COUNT argument is missing\r\n",
17968            ),
17969            (
17970                &[b"TS.RANGE", b"t", b"-", b"+", b"COUNT", b"x"],
17971                "-ERR TSDB: Couldn't parse COUNT\r\n",
17972            ),
17973            (
17974                &[b"TS.RANGE", b"t", b"-", b"+", b"COUNT", b"0"],
17975                "-ERR TSDB: Invalid COUNT value\r\n",
17976            ),
17977            (
17978                &[b"TS.RANGE", b"t", b"-", b"+", b"AGGREGATION", b"avg"],
17979                "-ERR TSDB: Couldn't parse AGGREGATION\r\n",
17980            ),
17981            (
17982                &[b"TS.RANGE", b"t", b"-", b"+", b"AGGREGATION", b"avg", b"x"],
17983                "-ERR TSDB: Couldn't parse AGGREGATION\r\n",
17984            ),
17985            (
17986                &[
17987                    b"TS.RANGE",
17988                    b"t",
17989                    b"-",
17990                    b"+",
17991                    b"AGGREGATION",
17992                    b"nope",
17993                    b"100",
17994                ],
17995                "-ERR TSDB: Unknown aggregation type\r\n",
17996            ),
17997            (
17998                &[
17999                    b"TS.RANGE",
18000                    b"t",
18001                    b"-",
18002                    b"+",
18003                    b"AGGREGATION",
18004                    b"avg,,min",
18005                    b"100",
18006                ],
18007                "-ERR TSDB: Empty aggregation type in list\r\n",
18008            ),
18009            // The list of names is read before the width is looked at.
18010            (
18011                &[b"TS.RANGE", b"t", b"-", b"+", b"AGGREGATION", b"nope", b"0"],
18012                "-ERR TSDB: Unknown aggregation type\r\n",
18013            ),
18014            (
18015                &[b"TS.RANGE", b"t", b"-", b"+", b"AGGREGATION", b"avg", b"0"],
18016                "-ERR TSDB: bucketDuration must be greater than zero\r\n",
18017            ),
18018            (
18019                &[
18020                    b"TS.RANGE",
18021                    b"t",
18022                    b"-",
18023                    b"+",
18024                    b"AGGREGATION",
18025                    b"avg",
18026                    b"100",
18027                    b"X",
18028                    b"EMPTY",
18029                ],
18030                "-ERR TSDB: EMPTY flag should be the 3rd or 5th flag after AGGREGATION flag\r\n",
18031            ),
18032            (
18033                &[
18034                    b"TS.RANGE",
18035                    b"t",
18036                    b"-",
18037                    b"+",
18038                    b"AGGREGATION",
18039                    b"avg",
18040                    b"100",
18041                    b"BUCKETTIMESTAMP",
18042                    b"z",
18043                ],
18044                "-ERR TSDB: unknown BUCKETTIMESTAMP parameter\r\n",
18045            ),
18046            (
18047                &[
18048                    b"TS.RANGE",
18049                    b"t",
18050                    b"-",
18051                    b"+",
18052                    b"AGGREGATION",
18053                    b"avg",
18054                    b"100",
18055                    b"X",
18056                    b"Y",
18057                    b"BUCKETTIMESTAMP",
18058                    b"-",
18059                ],
18060                "-ERR TSDB: BUCKETTIMESTAMP flag should be the 3rd or 4th flag after \
18061                 AGGREGATION flag\r\n",
18062            ),
18063            (
18064                &[
18065                    b"TS.RANGE",
18066                    b"t",
18067                    b"-",
18068                    b"+",
18069                    b"ALIGN",
18070                    b"z",
18071                    b"AGGREGATION",
18072                    b"avg",
18073                    b"100",
18074                ],
18075                "-ERR TSDB: unknown ALIGN parameter\r\n",
18076            ),
18077            (
18078                &[b"TS.RANGE", b"t", b"-", b"+", b"ALIGN", b"5"],
18079                "-ERR TSDB: ALIGN parameter can only be used with AGGREGATION\r\n",
18080            ),
18081            (
18082                &[
18083                    b"TS.RANGE",
18084                    b"t",
18085                    b"-",
18086                    b"+",
18087                    b"ALIGN",
18088                    b"-",
18089                    b"AGGREGATION",
18090                    b"avg",
18091                    b"100",
18092                ],
18093                "-ERR TSDB: start alignment can only be used with explicit start timestamp\r\n",
18094            ),
18095            (
18096                &[b"TS.RANGE", b"t", b"-", b"+", b"FILTER_BY_VALUE", b"1"],
18097                "-ERR TSDB: FILTER_BY_VALUE one or more arguments are missing\r\n",
18098            ),
18099            (
18100                &[
18101                    b"TS.RANGE",
18102                    b"t",
18103                    b"-",
18104                    b"+",
18105                    b"FILTER_BY_VALUE",
18106                    b"x",
18107                    b"2",
18108                ],
18109                "-ERR TSDB: Couldn't parse MIN\r\n",
18110            ),
18111            (
18112                &[
18113                    b"TS.RANGE",
18114                    b"t",
18115                    b"-",
18116                    b"+",
18117                    b"FILTER_BY_VALUE",
18118                    b"1",
18119                    b"y",
18120                ],
18121                "-ERR TSDB: Couldn't parse MAX\r\n",
18122            ),
18123            (
18124                &[b"TS.RANGE", b"t", b"-", b"+", b"FILTER_BY_TS"],
18125                "-ERR TSDB: FILTER_BY_TS one or more arguments are missing\r\n",
18126            ),
18127        ];
18128        for (argv, want) in cases {
18129            let got = f.run(argv);
18130            assert_eq!(&got, want, "{:?}", argv.last());
18131        }
18132        // The one sentence here that is yo's own rather than the module's, which
18133        // is D-54. A read that would build more rows than yo will build is
18134        // refused instead of attempted.
18135        f.run(&[b"TS.ADD", b"wide", b"0", b"1"]);
18136        f.run(&[b"TS.ADD", b"wide", b"1000000000000", b"2"]);
18137        assert_eq!(
18138            f.run(&[
18139                b"TS.RANGE",
18140                b"wide",
18141                b"-",
18142                b"+",
18143                b"AGGREGATION",
18144                b"avg",
18145                b"1",
18146                b"EMPTY"
18147            ]),
18148            "-ERR TSDB: the requested range holds too many empty buckets\r\n"
18149        );
18150    }
18151
18152    /// What RESP3 changes on a read, which is only how a number is written.
18153    #[test]
18154    fn resp3_writes_a_read_value_as_a_double() {
18155        let mut f = Fixture::new();
18156        f.out = Out::new(Proto::Resp3);
18157        for (at, v) in [
18158            (b"0".as_slice(), b"1".as_slice()),
18159            (b"100", b"2"),
18160            (b"500", b"nan"),
18161            (b"600", b"3"),
18162        ] {
18163            f.run(&[b"TS.ADD", b"g", at, v]);
18164        }
18165        assert_eq!(
18166            f.run(&[
18167                b"TS.RANGE",
18168                b"g",
18169                b"0",
18170                b"100",
18171                b"AGGREGATION",
18172                b"avg,min",
18173                b"200"
18174            ]),
18175            "*1\r\n*3\r\n:0\r\n,1.5\r\n,1\r\n"
18176        );
18177        assert_eq!(
18178            f.run(&[
18179                b"TS.RANGE",
18180                b"g",
18181                b"500",
18182                b"600",
18183                b"AGGREGATION",
18184                b"last",
18185                b"100",
18186                b"EMPTY"
18187            ]),
18188            "*2\r\n*2\r\n:500\r\n,nan\r\n*2\r\n:600\r\n,3\r\n"
18189        );
18190    }
18191
18192    /// Two series with an overlap and a gap each, plus a third holding nothing,
18193    /// which is what the joined reads are measured against.
18194    fn joined() -> Fixture {
18195        let mut f = Fixture::new();
18196        f.run(&[b"TS.CREATE", b"z"]);
18197        for (at, v) in [
18198            (b"10".as_slice(), b"1".as_slice()),
18199            (b"20", b"2"),
18200            (b"40", b"4"),
18201            (b"50", b"5"),
18202        ] {
18203            f.run(&[b"TS.ADD", b"x", at, v]);
18204        }
18205        for (at, v) in [
18206            (b"20".as_slice(), b"20".as_slice()),
18207            (b"30", b"30"),
18208            (b"50", b"50"),
18209            (b"60", b"60"),
18210        ] {
18211            f.run(&[b"TS.ADD", b"y", at, v]);
18212        }
18213        f
18214    }
18215
18216    /// The joined read lines its keys up on the timestamp and writes a row as
18217    /// the timestamp and then a nested array of the columns, which is the one
18218    /// shape in the family that is not the flat pair.
18219    #[test]
18220    fn an_nrange_joins_its_keys_on_the_timestamp() {
18221        let mut f = joined();
18222        // One key still nests, so the shape does not depend on the count.
18223        assert_eq!(
18224            f.run(&[b"TS.NRANGE", b"1", b"x", b"-", b"+"]),
18225            "*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\
18226             *2\r\n:40\r\n*1\r\n+4\r\n*2\r\n:50\r\n*1\r\n+5\r\n"
18227        );
18228        // A key with no reading where another key has one writes NaN there.
18229        assert_eq!(
18230            f.run(&[b"TS.NRANGE", b"2", b"x", b"y", b"-", b"+"]),
18231            "*6\r\n*2\r\n:10\r\n*2\r\n+1\r\n+NaN\r\n\
18232             *2\r\n:20\r\n*2\r\n+2\r\n+20\r\n\
18233             *2\r\n:30\r\n*2\r\n+NaN\r\n+30\r\n\
18234             *2\r\n:40\r\n*2\r\n+4\r\n+NaN\r\n\
18235             *2\r\n:50\r\n*2\r\n+5\r\n+50\r\n\
18236             *2\r\n:60\r\n*2\r\n+NaN\r\n+60\r\n"
18237        );
18238        // A series holding nothing is a column of NaN and never a row of its
18239        // own, and the same key twice answers twice.
18240        assert_eq!(
18241            f.run(&[b"TS.NRANGE", b"2", b"x", b"z", b"20", b"40"]),
18242            "*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"
18243        );
18244        assert_eq!(
18245            f.run(&[b"TS.NRANGE", b"2", b"x", b"x", b"40", b"50"]),
18246            "*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"
18247        );
18248        // COUNT is applied to the joined rows and not to each key, so backwards
18249        // it gives the newest joined row rather than the newest of each.
18250        assert_eq!(
18251            f.run(&[
18252                b"TS.NREVRANGE",
18253                b"2",
18254                b"x",
18255                b"y",
18256                b"-",
18257                b"+",
18258                b"COUNT",
18259                b"1"
18260            ]),
18261            "*1\r\n*2\r\n:60\r\n*2\r\n+NaN\r\n+60\r\n"
18262        );
18263        assert_eq!(
18264            f.run(&[b"TS.NRANGE", b"2", b"x", b"y", b"-", b"+", b"COUNT", b"1"]),
18265            "*1\r\n*2\r\n:10\r\n*2\r\n+1\r\n+NaN\r\n"
18266        );
18267        // The two sample filters are settled a key at a time, before the join.
18268        assert_eq!(
18269            f.run(&[
18270                b"TS.NRANGE",
18271                b"2",
18272                b"x",
18273                b"y",
18274                b"-",
18275                b"+",
18276                b"FILTER_BY_VALUE",
18277                b"2",
18278                b"30"
18279            ]),
18280            "*4\r\n*2\r\n:20\r\n*2\r\n+2\r\n+20\r\n\
18281             *2\r\n:30\r\n*2\r\n+NaN\r\n+30\r\n\
18282             *2\r\n:40\r\n*2\r\n+4\r\n+NaN\r\n\
18283             *2\r\n:50\r\n*2\r\n+5\r\n+NaN\r\n"
18284        );
18285    }
18286
18287    /// The aggregation on a joined read names one reduction a key and then the
18288    /// one bucket width, and each name may be a comma list, so a row can be
18289    /// wider than the key count.
18290    #[test]
18291    fn an_nrange_aggregation_names_one_reduction_a_key() {
18292        let mut f = joined();
18293        assert_eq!(
18294            f.run(&[
18295                b"TS.NRANGE",
18296                b"2",
18297                b"x",
18298                b"y",
18299                b"-",
18300                b"+",
18301                b"AGGREGATION",
18302                b"sum",
18303                b"sum",
18304                b"20"
18305            ]),
18306            "*4\r\n*2\r\n:0\r\n*2\r\n+1\r\n+NaN\r\n\
18307             *2\r\n:20\r\n*2\r\n+2\r\n+50\r\n\
18308             *2\r\n:40\r\n*2\r\n+9\r\n+50\r\n\
18309             *2\r\n:60\r\n*2\r\n+NaN\r\n+60\r\n"
18310        );
18311        // A comma list on the first key widens the row to three columns.
18312        assert_eq!(
18313            f.run(&[
18314                b"TS.NRANGE",
18315                b"2",
18316                b"x",
18317                b"y",
18318                b"-",
18319                b"+",
18320                b"AGGREGATION",
18321                b"sum,count",
18322                b"avg",
18323                b"20"
18324            ]),
18325            "*4\r\n*2\r\n:0\r\n*3\r\n+1\r\n+1\r\n+NaN\r\n\
18326             *2\r\n:20\r\n*3\r\n+2\r\n+1\r\n+25\r\n\
18327             *2\r\n:40\r\n*3\r\n+9\r\n+2\r\n+50\r\n\
18328             *2\r\n:60\r\n*3\r\n+NaN\r\n+NaN\r\n+60\r\n"
18329        );
18330        // Everything behind the width moves along with it, so BUCKETTIMESTAMP
18331        // sits one or two past the width whatever the key count is.
18332        assert_eq!(
18333            f.run(&[
18334                b"TS.NRANGE",
18335                b"2",
18336                b"x",
18337                b"y",
18338                b"-",
18339                b"+",
18340                b"AGGREGATION",
18341                b"avg",
18342                b"sum",
18343                b"100",
18344                b"EMPTY",
18345                b"BUCKETTIMESTAMP",
18346                b"end"
18347            ]),
18348            "*1\r\n*2\r\n:100\r\n*2\r\n+3\r\n+160\r\n"
18349        );
18350        // A COUNT landing in one of the name slots is a reduction name and not
18351        // the keyword, and the read then has no count at all.
18352        assert_eq!(
18353            f.run(&[
18354                b"TS.NRANGE",
18355                b"2",
18356                b"x",
18357                b"y",
18358                b"-",
18359                b"+",
18360                b"AGGREGATION",
18361                b"avg",
18362                b"COUNT",
18363                b"100"
18364            ]),
18365            "*1\r\n*2\r\n:0\r\n*2\r\n+3\r\n+4\r\n"
18366        );
18367    }
18368
18369    /// The sentences a joined read answers when it does not add up, which are
18370    /// the module's own and come out in the module's own order.
18371    #[test]
18372    fn an_nrange_says_what_the_module_says_when_it_does_not_add_up() {
18373        let mut f = joined();
18374        f.run(&[b"SET", b"str", b"hi"]);
18375        let bad_keys = "-ERR TSDB: numkeys must be a positive integer\r\n";
18376        let numkeys = "-ERR TSDB: the number of AGGREGATION arguments \
18377                       must be equal to numkeys\r\n";
18378        let cases: &[(&[&[u8]], &str)] = &[
18379            (&[b"TS.NRANGE", b"0", b"x", b"-", b"+"], bad_keys),
18380            (&[b"TS.NRANGE", b"-1", b"x", b"-", b"+"], bad_keys),
18381            (&[b"TS.NRANGE", b"abc", b"x", b"-", b"+"], bad_keys),
18382            // Not enough words behind the count for the keys and both ends of
18383            // the span, which is an arity error however many keys were named.
18384            (
18385                &[b"TS.NRANGE", b"2", b"x", b"-", b"+"],
18386                "-ERR wrong number of arguments for 'ts.nrange' command\r\n",
18387            ),
18388            (
18389                &[b"TS.NRANGE", b"99", b"x", b"-", b"+"],
18390                "-ERR wrong number of arguments for 'ts.nrange' command\r\n",
18391            ),
18392            // The reduction names are read before the two ends of the span,
18393            // which no other option is.
18394            (
18395                &[
18396                    b"TS.NRANGE",
18397                    b"2",
18398                    b"x",
18399                    b"y",
18400                    b"abc",
18401                    b"+",
18402                    b"AGGREGATION",
18403                    b"nope",
18404                    b"sum",
18405                    b"100",
18406                ],
18407                "-ERR TSDB: Unknown aggregation type\r\n",
18408            ),
18409            (
18410                &[b"TS.NRANGE", b"2", b"x", b"y", b"abc", b"+"],
18411                "-ERR TSDB: wrong fromTimestamp\r\n",
18412            ),
18413            (
18414                &[b"TS.NRANGE", b"2", b"x", b"y", b"-", b"abc"],
18415                "-ERR TSDB: wrong toTimestamp\r\n",
18416            ),
18417            // A name slot that is missing or holds a number is the count
18418            // sentence, and a width slot that is itself a reduction name is
18419            // that sentence as well.
18420            (
18421                &[
18422                    b"TS.NRANGE",
18423                    b"2",
18424                    b"x",
18425                    b"y",
18426                    b"-",
18427                    b"+",
18428                    b"AGGREGATION",
18429                    b"avg",
18430                ],
18431                numkeys,
18432            ),
18433            (
18434                &[
18435                    b"TS.NRANGE",
18436                    b"2",
18437                    b"x",
18438                    b"y",
18439                    b"-",
18440                    b"+",
18441                    b"AGGREGATION",
18442                    b"100",
18443                    b"sum",
18444                    b"100",
18445                ],
18446                numkeys,
18447            ),
18448            (
18449                &[
18450                    b"TS.NRANGE",
18451                    b"2",
18452                    b"x",
18453                    b"y",
18454                    b"-",
18455                    b"+",
18456                    b"AGGREGATION",
18457                    b"avg",
18458                    b"sum",
18459                    b"sum",
18460                    b"100",
18461                ],
18462                numkeys,
18463            ),
18464            (
18465                &[
18466                    b"TS.NRANGE",
18467                    b"2",
18468                    b"x",
18469                    b"y",
18470                    b"-",
18471                    b"+",
18472                    b"AGGREGATION",
18473                    b"avg",
18474                    b"sum",
18475                    b"abc",
18476                ],
18477                "-ERR TSDB: Couldn't parse AGGREGATION\r\n",
18478            ),
18479            (
18480                &[
18481                    b"TS.NRANGE",
18482                    b"2",
18483                    b"x",
18484                    b"y",
18485                    b"-",
18486                    b"+",
18487                    b"AGGREGATION",
18488                    b"avg",
18489                    b"sum",
18490                    b"0",
18491                ],
18492                "-ERR TSDB: bucketDuration must be greater than zero\r\n",
18493            ),
18494            // With one key none of that applies and the plain parser runs, so a
18495            // lone width is a missing width rather than a count mismatch.
18496            (
18497                &[b"TS.NRANGE", b"1", b"x", b"-", b"+", b"AGGREGATION", b"100"],
18498                "-ERR TSDB: Couldn't parse AGGREGATION\r\n",
18499            ),
18500            (
18501                &[
18502                    b"TS.NRANGE",
18503                    b"1",
18504                    b"x",
18505                    b"-",
18506                    b"+",
18507                    b"AGGREGATION",
18508                    b"100",
18509                    b"200",
18510                ],
18511                "-ERR TSDB: Unknown aggregation type\r\n",
18512            ),
18513            // The keys come last and in the order they were named.
18514            (
18515                &[b"TS.NRANGE", b"2", b"x", b"nope", b"-", b"+"],
18516                "-ERR TSDB: the key does not exist\r\n",
18517            ),
18518            (
18519                &[b"TS.NRANGE", b"2", b"str", b"nope", b"-", b"+"],
18520                "-ERR WRONGTYPE Operation against a key \
18521                 holding the wrong kind of value\r\n",
18522            ),
18523        ];
18524        for (argv, want) in cases {
18525            let got = f.run(argv);
18526            assert_eq!(&got, want, "{argv:?}");
18527        }
18528    }
18529
18530    /// `TS.READ`, which is a key, one timestamp and everything from there on.
18531    #[test]
18532    fn a_read_walks_from_a_timestamp_to_the_end_of_the_series() {
18533        let mut f = joined();
18534        assert_eq!(
18535            f.run(&[b"TS.READ", b"x", b"-"]),
18536            "*4\r\n*2\r\n:10\r\n+1\r\n*2\r\n:20\r\n+2\r\n\
18537             *2\r\n:40\r\n+4\r\n*2\r\n:50\r\n+5\r\n"
18538        );
18539        // A plus is the last sample on its own, and a timestamp between two
18540        // samples starts at the one behind it.
18541        assert_eq!(
18542            f.run(&[b"TS.READ", b"x", b"+"]),
18543            "*1\r\n*2\r\n:50\r\n+5\r\n"
18544        );
18545        assert_eq!(
18546            f.run(&[b"TS.READ", b"x", b"25"]),
18547            "*2\r\n*2\r\n:40\r\n+4\r\n*2\r\n:50\r\n+5\r\n"
18548        );
18549        // Past the end, a series holding nothing and a key that is not there
18550        // are all the empty array rather than an error.
18551        assert_eq!(f.run(&[b"TS.READ", b"x", b"99"]), "*0\r\n");
18552        assert_eq!(f.run(&[b"TS.READ", b"z", b"-"]), "*0\r\n");
18553        assert_eq!(f.run(&[b"TS.READ", b"z", b"+"]), "*0\r\n");
18554        assert_eq!(f.run(&[b"TS.READ", b"nope", b"-"]), "*0\r\n");
18555        // The timestamp refusal goes out with nothing in front of it, and a key
18556        // holding something else answers the bare WRONGTYPE rather than the
18557        // module's prefixed one, both unlike the rest of the family.
18558        assert_eq!(
18559            f.run(&[b"TS.READ", b"x", b"abc"]),
18560            "-TSDB: invalid timestamp\r\n"
18561        );
18562        assert_eq!(
18563            f.run(&[b"TS.READ", b"x", b"-1"]),
18564            "-TSDB: invalid timestamp\r\n"
18565        );
18566        f.run(&[b"SET", b"str", b"hi"]);
18567        assert_eq!(
18568            f.run(&[b"TS.READ", b"str", b"-"]),
18569            "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n"
18570        );
18571        // Anything other than exactly three words is an arity error, so there
18572        // is nowhere to put an option even though the table says minus three.
18573        assert_eq!(
18574            f.run(&[b"TS.READ", b"x"]),
18575            "-ERR wrong number of arguments for 'ts.read' command\r\n"
18576        );
18577        assert_eq!(
18578            f.run(&[b"TS.READ", b"x", b"-", b"COUNT", b"1"]),
18579            "-ERR wrong number of arguments for 'ts.read' command\r\n"
18580        );
18581    }
18582
18583    /// The keys of a joined read sit behind a count, so `COMMAND GETKEYS` has
18584    /// to read the count to find them.
18585    #[test]
18586    fn getkeys_reads_the_count_of_a_joined_read() {
18587        let mut f = Fixture::new();
18588        assert_eq!(
18589            f.run(&[
18590                b"COMMAND",
18591                b"GETKEYS",
18592                b"TS.NRANGE",
18593                b"2",
18594                b"a",
18595                b"b",
18596                b"-",
18597                b"+"
18598            ]),
18599            "*2\r\n$1\r\na\r\n$1\r\nb\r\n"
18600        );
18601        assert_eq!(
18602            f.run(&[
18603                b"COMMAND",
18604                b"GETKEYS",
18605                b"TS.NREVRANGE",
18606                b"1",
18607                b"a",
18608                b"-",
18609                b"+"
18610            ]),
18611            "*1\r\n$1\r\na\r\n"
18612        );
18613        // A count of zero, or one too large for the words that follow it, is
18614        // the server's own refusal and not the module's.
18615        for n in [b"0".as_slice(), b"9", b"abc"] {
18616            assert_eq!(
18617                f.run(&[b"COMMAND", b"GETKEYS", b"TS.NRANGE", n, b"a", b"-", b"+"]),
18618                "-ERR Invalid arguments specified for command\r\n"
18619            );
18620        }
18621    }
18622
18623    /// The five series every test of the label surface works against.
18624    fn labelled() -> Fixture {
18625        let mut f = Fixture::new();
18626        f.run(&[
18627            b"TS.CREATE",
18628            b"a",
18629            b"LABELS",
18630            b"room",
18631            b"kitchen",
18632            b"x",
18633            b"1",
18634        ]);
18635        f.run(&[
18636            b"TS.CREATE",
18637            b"b",
18638            b"LABELS",
18639            b"room",
18640            b"bedroom",
18641            b"x",
18642            b"2",
18643        ]);
18644        f.run(&[b"TS.CREATE", b"c", b"LABELS", b"room", b"kitchen"]);
18645        f.run(&[b"TS.CREATE", b"d"]);
18646        f.run(&[b"TS.CREATE", b"e", b"LABELS", b"r", b"bb", b"r", b"b"]);
18647        f.run(&[b"TS.ADD", b"a", b"100", b"1.5"]);
18648        f.run(&[b"TS.ADD", b"b", b"200", b"2"]);
18649        f
18650    }
18651
18652    /// The filter grammar, which is four steps and a `strtok` rather than a
18653    /// grammar, and which every command that searches on labels shares.
18654    #[test]
18655    fn a_filter_is_taken_apart_the_way_the_module_takes_one_apart() {
18656        let mut f = labelled();
18657        let cases: &[(&[&[u8]], &str)] = &[
18658            // The plain forms, and the order the answer comes back in, which is
18659            // by key name and not by anything the series remembers.
18660            (
18661                &[b"TS.QUERYINDEX", b"room=kitchen"],
18662                "*2\r\n$1\r\na\r\n$1\r\nc\r\n",
18663            ),
18664            (
18665                &[b"TS.QUERYINDEX", b"room=kitchen", b"x=1"],
18666                "*1\r\n$1\r\na\r\n",
18667            ),
18668            (
18669                &[b"TS.QUERYINDEX", b"room=(kitchen,bedroom)"],
18670                "*3\r\n$1\r\na\r\n$1\r\nb\r\n$1\r\nc\r\n",
18671            ),
18672            // An empty list still counts as something that says which series to
18673            // take, it just never takes any.
18674            (&[b"TS.QUERYINDEX", b"room=()"], "*0\r\n"),
18675            // Absent and present, neither of which stands on its own.
18676            (
18677                &[b"TS.QUERYINDEX", b"x=", b"room=kitchen"],
18678                "*1\r\n$1\r\nc\r\n",
18679            ),
18680            (
18681                &[b"TS.QUERYINDEX", b"room=kitchen", b"x!="],
18682                "*1\r\n$1\r\na\r\n",
18683            ),
18684            (
18685                &[b"TS.QUERYINDEX", b"room!=kitchen", b"x!="],
18686                "-ERR TSDB: please provide at least one matcher\r\n",
18687            ),
18688            // A run of separators is one separator and everything past the
18689            // second field is dropped, so all three of these ask one question.
18690            (
18691                &[b"TS.QUERYINDEX", b"room==kitchen"],
18692                "*2\r\n$1\r\na\r\n$1\r\nc\r\n",
18693            ),
18694            (
18695                &[b"TS.QUERYINDEX", b"room=kitchen=zz"],
18696                "*2\r\n$1\r\na\r\n$1\r\nc\r\n",
18697            ),
18698            (&[b"TS.QUERYINDEX", b"room!!=kitchen", b"x=1"], "*0\r\n"),
18699            // A bracket is only a list when it sits straight behind the
18700            // separator, and then the label in front of it has to be there.
18701            (&[b"TS.QUERYINDEX", b"()=1"], "*0\r\n"),
18702            (
18703                &[b"TS.QUERYINDEX", b"=(1)"],
18704                "-ERR TSDB: failed parsing labels\r\n",
18705            ),
18706            (
18707                &[b"TS.QUERYINDEX", b"room=(kitchen,)"],
18708                "-ERR TSDB: failed parsing labels\r\n",
18709            ),
18710            (
18711                &[b"TS.QUERYINDEX", b"room=(kitchen"],
18712                "-ERR TSDB: failed parsing labels\r\n",
18713            ),
18714            (&[b"TS.QUERYINDEX", b"room=x()"], "*0\r\n"),
18715            (
18716                &[b"TS.QUERYINDEX", b"nonsense"],
18717                "-ERR TSDB: failed parsing labels\r\n",
18718            ),
18719            // Nothing here says which series to take.
18720            (
18721                &[b"TS.QUERYINDEX", b"room!=kitchen"],
18722                "-ERR TSDB: please provide at least one matcher\r\n",
18723            ),
18724            // Names and values are both compared byte for byte.
18725            (&[b"TS.QUERYINDEX", b"ROOM=kitchen"], "*0\r\n"),
18726            (&[b"TS.QUERYINDEX", b"room=KITCHEN"], "*0\r\n"),
18727            (
18728                &[b"TS.QUERYINDEX"],
18729                "-ERR wrong number of arguments for 'ts.queryindex' command\r\n",
18730            ),
18731        ];
18732        for (argv, want) in cases {
18733            let got = f.run(argv);
18734            assert_eq!(&got, want, "{:?}", argv.last());
18735        }
18736    }
18737
18738    /// `TS.QUERYLABELS`, whose filter is the one that is allowed to be missing.
18739    #[test]
18740    fn querylabels_says_which_names_are_worn_and_what_they_are_set_to() {
18741        let mut f = labelled();
18742        let cases: &[(&[&[u8]], &str)] = &[
18743            (
18744                &[b"TS.QUERYLABELS", b"LABELS"],
18745                "*3\r\n$1\r\nr\r\n$4\r\nroom\r\n$1\r\nx\r\n",
18746            ),
18747            (
18748                &[b"TS.QUERYLABELS", b"LABELS", b"FILTER", b"room=kitchen"],
18749                "*2\r\n$4\r\nroom\r\n$1\r\nx\r\n",
18750            ),
18751            (
18752                &[b"TS.QUERYLABELS", b"VALUES", b"room"],
18753                "*2\r\n$7\r\nbedroom\r\n$7\r\nkitchen\r\n",
18754            ),
18755            // The series wearing `r` twice contributes the smaller of the two
18756            // here, which is not the one it was written down as first.
18757            (&[b"TS.QUERYLABELS", b"VALUES", b"r"], "*1\r\n$1\r\nb\r\n"),
18758            (&[b"TS.QUERYLABELS", b"VALUES", b"nolabel"], "*0\r\n"),
18759            (
18760                &[b"TS.QUERYLABELS", b"VALUES"],
18761                "-ERR wrong number of arguments for 'ts.querylabels' command\r\n",
18762            ),
18763            (
18764                &[b"TS.QUERYLABELS", b"ZZZ"],
18765                "-ERR TSDB: unknown subtype, must be one of LABELS|VALUES\r\n",
18766            ),
18767            (
18768                &[b"TS.QUERYLABELS", b"LABELS", b"ZZZ"],
18769                "-ERR TSDB: unknown argument, expected FILTER\r\n",
18770            ),
18771            (
18772                &[b"TS.QUERYLABELS", b"LABELS", b"FILTER"],
18773                "-ERR TSDB: FILTER given with no filter expressions\r\n",
18774            ),
18775            // With no filter at all every series is taken, which is why the
18776            // first case here answers about `r` as well. A filter that is there
18777            // still has to say which series to take.
18778            (
18779                &[b"TS.QUERYLABELS", b"LABELS", b"FILTER", b"room!=kitchen"],
18780                "-ERR TSDB: please provide at least one matcher\r\n",
18781            ),
18782            (
18783                &[
18784                    b"TS.QUERYLABELS",
18785                    b"LABELS",
18786                    b"FILTER",
18787                    b"room=kitchen",
18788                    b"x=",
18789                ],
18790                "*1\r\n$4\r\nroom\r\n",
18791            ),
18792        ];
18793        for (argv, want) in cases {
18794            let got = f.run(argv);
18795            assert_eq!(&got, want, "{:?}", argv.last());
18796        }
18797    }
18798
18799    /// `TS.MGET`, the newest sample of every series a filter takes, and the two
18800    /// ways of asking for the labels back alongside it.
18801    #[test]
18802    fn mget_writes_the_newest_sample_and_the_labels_that_were_asked_for() {
18803        let mut f = labelled();
18804        let cases: &[(&[&[u8]], &str)] = &[
18805            // A series with no samples writes an empty array where the sample
18806            // goes rather than dropping out of the reply.
18807            (
18808                &[b"TS.MGET", b"FILTER", b"room=kitchen"],
18809                "*2\r\n*3\r\n$1\r\na\r\n*0\r\n*2\r\n:100\r\n+1.5\r\n\
18810                 *3\r\n$1\r\nc\r\n*0\r\n*0\r\n",
18811            ),
18812            (
18813                &[b"TS.MGET", b"WITHLABELS", b"FILTER", b"room=kitchen"],
18814                "*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\
18815                 *2\r\n$1\r\nx\r\n$1\r\n1\r\n*2\r\n:100\r\n+1.5\r\n\
18816                 *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",
18817            ),
18818            // A selected label the series does not wear is a nil, not a gap.
18819            (
18820                &[
18821                    b"TS.MGET",
18822                    b"SELECTED_LABELS",
18823                    b"x",
18824                    b"FILTER",
18825                    b"room=kitchen",
18826                ],
18827                "*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\
18828                 *2\r\n:100\r\n+1.5\r\n\
18829                 *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",
18830            ),
18831            // The other half of the duplicated name rule. This one takes the
18832            // first written down where `TS.QUERYLABELS` takes the smallest.
18833            (
18834                &[b"TS.MGET", b"SELECTED_LABELS", b"r", b"FILTER", b"r=b"],
18835                "*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",
18836            ),
18837            (
18838                &[b"TS.MGET", b"WITHLABELS", b"FILTER", b"r=b"],
18839                "*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\
18840                 *2\r\n$1\r\nr\r\n$1\r\nb\r\n*0\r\n",
18841            ),
18842            // A word that is not an option is ignored, but a missing `FILTER`
18843            // is an arity error whatever else was written.
18844            (
18845                &[b"TS.MGET", b"ZZZ", b"FILTER", b"room=bedroom"],
18846                "*1\r\n*3\r\n$1\r\nb\r\n*0\r\n*2\r\n:200\r\n+2\r\n",
18847            ),
18848            (
18849                &[b"TS.MGET", b"a", b"b", b"c"],
18850                "-ERR wrong number of arguments for 'ts.mget' command\r\n",
18851            ),
18852            (
18853                &[b"TS.MGET", b"FILTER"],
18854                "-ERR wrong number of arguments for 'ts.mget' command\r\n",
18855            ),
18856            // Both keyword checks happen before the filter is read, and the two
18857            // sentences spell the second keyword without its `ED`.
18858            (
18859                &[
18860                    b"TS.MGET",
18861                    b"WITHLABELS",
18862                    b"SELECTED_LABELS",
18863                    b"x",
18864                    b"FILTER",
18865                    b"bad",
18866                ],
18867                "-ERR TSDB: cannot accept WITHLABELS and SELECT_LABELS together\r\n",
18868            ),
18869            (
18870                &[b"TS.MGET", b"SELECTED_LABELS", b"FILTER", b"bad"],
18871                "-ERR TSDB: SELECT_LABELS should have at least 1 parameter\r\n",
18872            ),
18873        ];
18874        for (argv, want) in cases {
18875            let got = f.run(argv);
18876            assert_eq!(&got, want, "{:?}", argv.last());
18877        }
18878    }
18879
18880    /// What RESP3 changes across the label surface, which is a set where there
18881    /// was an array and a map where there was a pair of them.
18882    #[test]
18883    fn resp3_writes_the_label_surface_as_sets_and_maps() {
18884        let mut f = labelled();
18885        f.out = Out::new(Proto::Resp3);
18886        let cases: &[(&[&[u8]], &str)] = &[
18887            (
18888                &[b"TS.QUERYINDEX", b"room=kitchen"],
18889                "~2\r\n$1\r\na\r\n$1\r\nc\r\n",
18890            ),
18891            (
18892                &[b"TS.QUERYLABELS", b"LABELS"],
18893                "~3\r\n$1\r\nr\r\n$4\r\nroom\r\n$1\r\nx\r\n",
18894            ),
18895            (
18896                &[b"TS.QUERYLABELS", b"VALUES", b"room"],
18897                "~2\r\n$7\r\nbedroom\r\n$7\r\nkitchen\r\n",
18898            ),
18899            // The key stops being the first of three and becomes the map key,
18900            // and the labels stop being pairs and become a map of their own.
18901            (
18902                &[b"TS.MGET", b"FILTER", b"room=kitchen"],
18903                "%2\r\n$1\r\na\r\n*2\r\n%0\r\n*2\r\n:100\r\n,1.5\r\n\
18904                 $1\r\nc\r\n*2\r\n%0\r\n*0\r\n",
18905            ),
18906            (
18907                &[b"TS.MGET", b"WITHLABELS", b"FILTER", b"room=kitchen"],
18908                "%2\r\n$1\r\na\r\n*2\r\n%2\r\n$4\r\nroom\r\n$7\r\nkitchen\r\n\
18909                 $1\r\nx\r\n$1\r\n1\r\n*2\r\n:100\r\n,1.5\r\n\
18910                 $1\r\nc\r\n*2\r\n%1\r\n$4\r\nroom\r\n$7\r\nkitchen\r\n*0\r\n",
18911            ),
18912            (
18913                &[
18914                    b"TS.MGET",
18915                    b"SELECTED_LABELS",
18916                    b"x",
18917                    b"FILTER",
18918                    b"room=kitchen",
18919                ],
18920                "%2\r\n$1\r\na\r\n*2\r\n%1\r\n$1\r\nx\r\n$1\r\n1\r\n\
18921                 *2\r\n:100\r\n,1.5\r\n\
18922                 $1\r\nc\r\n*2\r\n%1\r\n$1\r\nx\r\n_\r\n*0\r\n",
18923            ),
18924            // A map with a name in it twice, which is what a series wearing one
18925            // label name twice turns into.
18926            (
18927                &[b"TS.MGET", b"WITHLABELS", b"FILTER", b"r=b"],
18928                "%1\r\n$1\r\ne\r\n*2\r\n%2\r\n$1\r\nr\r\n$2\r\nbb\r\n\
18929                 $1\r\nr\r\n$1\r\nb\r\n*0\r\n",
18930            ),
18931        ];
18932        for (argv, want) in cases {
18933            let got = f.run(argv);
18934            assert_eq!(&got, want, "{:?}", argv.last());
18935        }
18936    }
18937
18938    /// The same five series with enough samples in them for a group to have
18939    /// something to fold.
18940    fn spanned() -> Fixture {
18941        let mut f = labelled();
18942        f.run(&[b"TS.ADD", b"a", b"200", b"2.5"]);
18943        f.run(&[b"TS.ADD", b"c", b"100", b"10"]);
18944        f.run(&[b"TS.ADD", b"c", b"300", b"30"]);
18945        f
18946    }
18947
18948    /// A span read out of every series a filter takes, with and without a group
18949    /// over the top of it.
18950    #[test]
18951    fn mrange_reads_every_series_and_folds_the_groups_it_is_asked_for() {
18952        let mut f = spanned();
18953        let cases: &[(&[&[u8]], &str)] = &[
18954            (
18955                &[b"TS.MRANGE", b"-", b"+", b"FILTER", b"room=kitchen"],
18956                "*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\
18957                 *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",
18958            ),
18959            // Newest first is applied to each series before anything else sees
18960            // the rows.
18961            (
18962                &[
18963                    b"TS.MREVRANGE",
18964                    b"-",
18965                    b"+",
18966                    b"WITHLABELS",
18967                    b"FILTER",
18968                    b"room=kitchen",
18969                ],
18970                "*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\
18971                 *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\
18972                 *3\r\n$1\r\nc\r\n*1\r\n*2\r\n$4\r\nroom\r\n$7\r\nkitchen\r\n\
18973                 *2\r\n*2\r\n:300\r\n+30\r\n*2\r\n:100\r\n+10\r\n",
18974            ),
18975            // A label a series does not wear comes back against a nil rather
18976            // than being left out.
18977            (
18978                &[
18979                    b"TS.MRANGE",
18980                    b"-",
18981                    b"+",
18982                    b"SELECTED_LABELS",
18983                    b"x",
18984                    b"FILTER",
18985                    b"room=kitchen",
18986                ],
18987                "*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\
18988                 *2\r\n*2\r\n:100\r\n+1.5\r\n*2\r\n:200\r\n+2.5\r\n\
18989                 *3\r\n$1\r\nc\r\n*1\r\n*2\r\n$1\r\nx\r\n$-1\r\n\
18990                 *2\r\n*2\r\n:100\r\n+10\r\n*2\r\n:300\r\n+30\r\n",
18991            ),
18992            // The fold: 100 is in both series and adds up, the other two are in
18993            // one each and are still rows.
18994            (
18995                &[
18996                    b"TS.MRANGE",
18997                    b"-",
18998                    b"+",
18999                    b"FILTER",
19000                    b"room=kitchen",
19001                    b"GROUPBY",
19002                    b"room",
19003                    b"REDUCE",
19004                    b"sum",
19005                ],
19006                "*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\
19007                 *2\r\n:200\r\n+2.5\r\n*2\r\n:300\r\n+30\r\n",
19008            ),
19009            // RESP2 has nowhere to put the reducer and the member keys, so a
19010            // group wearing labels writes them as two more labels.
19011            (
19012                &[
19013                    b"TS.MRANGE",
19014                    b"-",
19015                    b"+",
19016                    b"WITHLABELS",
19017                    b"FILTER",
19018                    b"room=kitchen",
19019                    b"GROUPBY",
19020                    b"room",
19021                    b"REDUCE",
19022                    b"max",
19023                ],
19024                "*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\
19025                 *2\r\n$11\r\n__reducer__\r\n$3\r\nmax\r\n\
19026                 *2\r\n$10\r\n__source__\r\n$3\r\na,c\r\n\
19027                 *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",
19028            ),
19029            // A count is applied to each member and then again to the fold.
19030            (
19031                &[
19032                    b"TS.MREVRANGE",
19033                    b"-",
19034                    b"+",
19035                    b"COUNT",
19036                    b"1",
19037                    b"FILTER",
19038                    b"room=kitchen",
19039                    b"GROUPBY",
19040                    b"room",
19041                    b"REDUCE",
19042                    b"count",
19043                ],
19044                "*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",
19045            ),
19046            // Nothing wears the label, so nothing is in any group.
19047            (
19048                &[
19049                    b"TS.MRANGE",
19050                    b"-",
19051                    b"+",
19052                    b"FILTER",
19053                    b"room=kitchen",
19054                    b"GROUPBY",
19055                    b"nope",
19056                    b"REDUCE",
19057                    b"sum",
19058                ],
19059                "*0\r\n",
19060            ),
19061            (
19062                &[
19063                    b"TS.MRANGE",
19064                    b"-",
19065                    b"+",
19066                    b"AGGREGATION",
19067                    b"sum,avg",
19068                    b"100",
19069                    b"FILTER",
19070                    b"room=bedroom",
19071                ],
19072                "*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",
19073            ),
19074            // The errors, in the order they are looked for.
19075            (
19076                &[b"TS.MRANGE", b"-", b"+", b"room=kitchen"],
19077                "-ERR TSDB: missing FILTER argument\r\n",
19078            ),
19079            (
19080                &[b"TS.MRANGE", b"-", b"+", b"FILTER"],
19081                "-ERR TSDB: missing labels for filter argument\r\n",
19082            ),
19083            (
19084                &[
19085                    b"TS.MRANGE",
19086                    b"-",
19087                    b"+",
19088                    b"GROUPBY",
19089                    b"room",
19090                    b"REDUCE",
19091                    b"sum",
19092                    b"FILTER",
19093                    b"room=kitchen",
19094                ],
19095                "-ERR TSDB: GROUPBY should always come after filter\r\n",
19096            ),
19097            // The group is four words from the end here, so the length is what
19098            // is wrong with it.
19099            (
19100                &[
19101                    b"TS.MRANGE",
19102                    b"-",
19103                    b"+",
19104                    b"FILTER",
19105                    b"room=kitchen",
19106                    b"GROUPBY",
19107                    b"room",
19108                    b"REDUCE",
19109                    b"sum",
19110                    b"x",
19111                ],
19112                "-ERR wrong number of arguments for 'ts.mrange' command\r\n",
19113            ),
19114            // And here it is not, so its words are filters and answer first.
19115            (
19116                &[
19117                    b"TS.MRANGE",
19118                    b"-",
19119                    b"+",
19120                    b"FILTER",
19121                    b"nope",
19122                    b"GROUPBY",
19123                    b"room",
19124                    b"REDUCE",
19125                    b"sum",
19126                    b"x",
19127                ],
19128                "-ERR TSDB: failed parsing labels\r\n",
19129            ),
19130            (
19131                &[
19132                    b"TS.MRANGE",
19133                    b"-",
19134                    b"+",
19135                    b"FILTER",
19136                    b"room=kitchen",
19137                    b"GROUPBY",
19138                    b"room",
19139                    b"REDUCE",
19140                    b"twa",
19141                ],
19142                "-ERR TSDB: Invalid reducer type\r\n",
19143            ),
19144            (
19145                &[
19146                    b"TS.MRANGE",
19147                    b"-",
19148                    b"+",
19149                    b"AGGREGATION",
19150                    b"sum,avg",
19151                    b"100",
19152                    b"FILTER",
19153                    b"room=kitchen",
19154                    b"GROUPBY",
19155                    b"room",
19156                    b"REDUCE",
19157                    b"sum",
19158                ],
19159                "-ERR TSDB: GROUPBY is not allowed when multiple aggregators are specified\r\n",
19160            ),
19161            // The label list ends at a keyword, so this is a `COUNT` with a
19162            // `FILTER` where its number should be.
19163            (
19164                &[
19165                    b"TS.MRANGE",
19166                    b"-",
19167                    b"+",
19168                    b"SELECTED_LABELS",
19169                    b"COUNT",
19170                    b"FILTER",
19171                    b"room=kitchen",
19172                ],
19173                "-ERR TSDB: Couldn't parse COUNT\r\n",
19174            ),
19175        ];
19176        for (argv, want) in cases {
19177            let got = f.run(argv);
19178            assert_eq!(&got, want, "{argv:?}");
19179        }
19180    }
19181
19182    /// The multi key reads on RESP3, where the key becomes a map key and the
19183    /// reducer and the member keys become fields of their own.
19184    #[test]
19185    fn resp3_writes_a_multi_key_read_as_a_map_of_four() {
19186        let mut f = spanned();
19187        f.out = Out::new(Proto::Resp3);
19188        let cases: &[(&[&[u8]], &str)] = &[
19189            (
19190                &[b"TS.MRANGE", b"-", b"+", b"FILTER", b"room=bedroom"],
19191                "%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\
19192                 *1\r\n*2\r\n:200\r\n,2\r\n",
19193            ),
19194            // The reductions a read asked for, which RESP2 has no room for at
19195            // all and which is empty on a read that asked for none.
19196            (
19197                &[
19198                    b"TS.MRANGE",
19199                    b"-",
19200                    b"+",
19201                    b"AGGREGATION",
19202                    b"sum,avg",
19203                    b"100",
19204                    b"FILTER",
19205                    b"room=bedroom",
19206                ],
19207                "%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\
19208                 $3\r\navg\r\n*1\r\n*3\r\n:200\r\n,2\r\n,2\r\n",
19209            ),
19210            (
19211                &[
19212                    b"TS.MRANGE",
19213                    b"-",
19214                    b"+",
19215                    b"FILTER",
19216                    b"room=kitchen",
19217                    b"GROUPBY",
19218                    b"room",
19219                    b"REDUCE",
19220                    b"sum",
19221                ],
19222                "%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\
19223                 $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\
19224                 *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",
19225            ),
19226            // The labels hold only the pair the group was made on, because the
19227            // reducer and the sources have somewhere else to go.
19228            (
19229                &[
19230                    b"TS.MRANGE",
19231                    b"-",
19232                    b"+",
19233                    b"WITHLABELS",
19234                    b"FILTER",
19235                    b"room=kitchen",
19236                    b"GROUPBY",
19237                    b"room",
19238                    b"REDUCE",
19239                    b"max",
19240                ],
19241                "%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\
19242                 %1\r\n$8\r\nreducers\r\n*1\r\n$3\r\nmax\r\n\
19243                 %1\r\n$7\r\nsources\r\n*2\r\n$1\r\na\r\n$1\r\nc\r\n\
19244                 *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",
19245            ),
19246            (
19247                &[
19248                    b"TS.MRANGE",
19249                    b"-",
19250                    b"+",
19251                    b"FILTER",
19252                    b"room=kitchen",
19253                    b"GROUPBY",
19254                    b"nope",
19255                    b"REDUCE",
19256                    b"sum",
19257                ],
19258                "%0\r\n",
19259            ),
19260        ];
19261        for (argv, want) in cases {
19262            let got = f.run(argv);
19263            assert_eq!(&got, want, "{argv:?}");
19264        }
19265    }
19266
19267    /// `TS.CREATERULE`, whose refusals come in an order of their own.
19268    #[test]
19269    fn createrule_checks_the_two_keys_last_and_the_two_links_after_that() {
19270        let mut f = Fixture::new();
19271        f.run(&[b"TS.CREATE", b"src"]);
19272        f.run(&[b"TS.CREATE", b"dst"]);
19273        f.run(&[b"SET", b"plain", b"v"]);
19274        let cases: &[(&[&[u8]], &str)] = &[
19275            // The width is read before the reduction, the reduction before the
19276            // width being above zero, and all three before either key is looked
19277            // at, so a command that is wrong twice complains about the first.
19278            (
19279                &[
19280                    b"TS.CREATERULE",
19281                    b"src",
19282                    b"dst",
19283                    b"AGGREGATION",
19284                    b"nope",
19285                    b"x",
19286                ],
19287                "-ERR TSDB: Couldn't parse AGGREGATION\r\n",
19288            ),
19289            (
19290                &[
19291                    b"TS.CREATERULE",
19292                    b"src",
19293                    b"dst",
19294                    b"AGGREGATION",
19295                    b"nope",
19296                    b"10",
19297                ],
19298                "-ERR TSDB: Unknown aggregation type\r\n",
19299            ),
19300            (
19301                &[
19302                    b"TS.CREATERULE",
19303                    b"src",
19304                    b"dst",
19305                    b"AGGREGATION",
19306                    b"avg",
19307                    b"0",
19308                ],
19309                "-ERR TSDB: bucketDuration must be greater than zero\r\n",
19310            ),
19311            (
19312                &[
19313                    b"TS.CREATERULE",
19314                    b"src",
19315                    b"dst",
19316                    b"AGGREGATION",
19317                    b"avg",
19318                    b"10",
19319                    b"x",
19320                ],
19321                "-ERR TSDB: Couldn't parse alignTimestamp\r\n",
19322            ),
19323            (
19324                &[
19325                    b"TS.CREATERULE",
19326                    b"src",
19327                    b"src",
19328                    b"AGGREGATION",
19329                    b"avg",
19330                    b"10",
19331                ],
19332                "-ERR TSDB: the source key and destination key should be different\r\n",
19333            ),
19334            // A key holding something else answers the same as a key that is not
19335            // there at all, because the source is looked up first and neither of
19336            // them is a series.
19337            (
19338                &[
19339                    b"TS.CREATERULE",
19340                    b"nope",
19341                    b"plain",
19342                    b"AGGREGATION",
19343                    b"avg",
19344                    b"10",
19345                ],
19346                "-ERR TSDB: the key does not exist\r\n",
19347            ),
19348            (
19349                &[
19350                    b"TS.CREATERULE",
19351                    b"src",
19352                    b"nope",
19353                    b"AGGREGATION",
19354                    b"avg",
19355                    b"10",
19356                ],
19357                "-ERR TSDB: the key does not exist\r\n",
19358            ),
19359            // A keyword other than AGGREGATION is an arity error rather than a
19360            // syntax one, because the arity is all that is checked.
19361            (
19362                &[b"TS.CREATERULE", b"src", b"dst", b"NOPE", b"avg", b"10"],
19363                "-ERR wrong number of arguments for 'ts.createrule' command\r\n",
19364            ),
19365            (
19366                &[
19367                    b"TS.CREATERULE",
19368                    b"src",
19369                    b"dst",
19370                    b"AGGREGATION",
19371                    b"avg",
19372                    b"10",
19373                ],
19374                "+OK\r\n",
19375            ),
19376            // The link is now in place, so the same rule again is refused from
19377            // the destination's end.
19378            (
19379                &[
19380                    b"TS.CREATERULE",
19381                    b"src",
19382                    b"dst",
19383                    b"AGGREGATION",
19384                    b"avg",
19385                    b"10",
19386                ],
19387                "-ERR TSDB: the destination key already has a src rule\r\n",
19388            ),
19389            // A source that is already someone's destination, and a destination
19390            // that is already someone's source, are two different sentences.
19391            (
19392                &[
19393                    b"TS.CREATERULE",
19394                    b"dst",
19395                    b"src",
19396                    b"AGGREGATION",
19397                    b"avg",
19398                    b"10",
19399                ],
19400                "-ERR TSDB: the source key already has a source rule\r\n",
19401            ),
19402            (&[b"TS.DELETERULE", b"src", b"dst"], "+OK\r\n"),
19403            (
19404                &[b"TS.DELETERULE", b"src", b"dst"],
19405                "-ERR TSDB: compaction rule does not exist\r\n",
19406            ),
19407            // The source is looked up and the destination is not, so a missing
19408            // destination is a missing rule and a missing source is a missing
19409            // key, which is the other way round from `TS.CREATERULE`.
19410            (
19411                &[b"TS.DELETERULE", b"src", b"nope"],
19412                "-ERR TSDB: compaction rule does not exist\r\n",
19413            ),
19414            (
19415                &[b"TS.DELETERULE", b"nope", b"dst"],
19416                "-ERR TSDB: the key does not exist\r\n",
19417            ),
19418        ];
19419        for (argv, want) in cases {
19420            let got = f.run(argv);
19421            assert_eq!(&got, want, "{argv:?}");
19422        }
19423    }
19424
19425    /// What a rule writes, which is every bucket but the one it is filling.
19426    #[test]
19427    fn a_rule_writes_a_bucket_when_a_later_reading_closes_it() {
19428        let mut f = Fixture::new();
19429        f.run(&[b"TS.CREATE", b"src"]);
19430        f.run(&[b"TS.CREATE", b"dst"]);
19431        // The readings written before the rule was made are not folded, so the
19432        // destination is still empty after the first two.
19433        f.run(&[b"TS.ADD", b"src", b"10", b"1"]);
19434        f.run(&[
19435            b"TS.CREATERULE",
19436            b"src",
19437            b"dst",
19438            b"AGGREGATION",
19439            b"sum",
19440            b"100",
19441        ]);
19442        f.run(&[b"TS.ADD", b"src", b"20", b"2"]);
19443        assert_eq!(f.run(&[b"TS.RANGE", b"dst", b"-", b"+"]), "*0\r\n");
19444        // The bucket the rule is filling holds only what it was given, so it is
19445        // 2 rather than 3, and it is written when a reading lands past it.
19446        assert_eq!(f.run(&[b"TS.GET", b"dst", b"LATEST"]), "*2\r\n:0\r\n+2\r\n");
19447        f.run(&[b"TS.ADD", b"src", b"110", b"4"]);
19448        assert_eq!(
19449            f.run(&[b"TS.RANGE", b"dst", b"-", b"+"]),
19450            "*1\r\n*2\r\n:0\r\n+2\r\n"
19451        );
19452        // A reading into a bucket that has already been written works that
19453        // bucket out again over everything the source now holds.
19454        f.run(&[b"TS.ADD", b"src", b"30", b"8"]);
19455        assert_eq!(
19456            f.run(&[b"TS.RANGE", b"dst", b"-", b"+"]),
19457            "*1\r\n*2\r\n:0\r\n+11\r\n"
19458        );
19459        // Deleting from the source works the buckets it touched out again and
19460        // reopens the newest one, so `LATEST` starts from the whole bucket.
19461        assert_eq!(f.run(&[b"TS.DEL", b"src", b"0", b"25"]), ":2\r\n");
19462        assert_eq!(
19463            f.run(&[b"TS.RANGE", b"dst", b"-", b"+"]),
19464            "*1\r\n*2\r\n:0\r\n+8\r\n"
19465        );
19466        assert_eq!(
19467            f.run(&[b"TS.GET", b"dst", b"LATEST"]),
19468            "*2\r\n:100\r\n+4\r\n"
19469        );
19470        // The link shows on both ends, and dropping either key takes it down.
19471        assert!(f.run(&[b"TS.INFO", b"dst"]).contains("sourceKey"));
19472        f.run(&[b"DEL", b"dst"]);
19473        assert_eq!(
19474            f.run(&[b"TS.DELETERULE", b"src", b"dst"]),
19475            "-ERR TSDB: compaction rule does not exist\r\n"
19476        );
19477    }
19478
19479    /// The three shapes an `XADD` id can take, and the one rule behind all of
19480    /// them.
19481    #[test]
19482    fn xadd_ids_only_ever_go_up() {
19483        let mut f = Fixture::new();
19484        // A bare millisecond is that millisecond and sequence zero.
19485        assert_eq!(f.run(&[b"XADD", b"s", b"5", b"a", b"1"]), "$3\r\n5-0\r\n");
19486        // And `5-*` is the next free sequence inside it.
19487        assert_eq!(f.run(&[b"XADD", b"s", b"5-*", b"a", b"2"]), "$3\r\n5-1\r\n");
19488        assert_eq!(f.run(&[b"XADD", b"s", b"5-*", b"a", b"3"]), "$3\r\n5-2\r\n");
19489        assert_eq!(f.run(&[b"XADD", b"s", b"6-9", b"a", b"4"]), "$3\r\n6-9\r\n");
19490        assert_eq!(f.run(&[b"XLEN", b"s"]), ":4\r\n");
19491
19492        assert!(
19493            f.run(&[b"XADD", b"s", b"6-9", b"a", b"5"])
19494                .contains("equal or smaller")
19495        );
19496        assert!(
19497            f.run(&[b"XADD", b"s", b"0-0", b"a", b"5"])
19498                .contains("must be greater than 0-0")
19499        );
19500        assert!(
19501            f.run(&[b"XADD", b"s", b"nonsense", b"a", b"5"])
19502                .contains("Invalid stream ID")
19503        );
19504        // The pairs have to be pairs, and Redis calls an odd one an arity error
19505        // rather than a syntax error even though the table has already passed.
19506        assert!(
19507            f.run(&[b"XADD", b"s", b"*", b"a"])
19508                .contains("wrong number of arguments")
19509        );
19510
19511        // `NOMKSTREAM` on a key that is not there is a null and not a zero, so a
19512        // producer can tell nobody is consuming this yet from the write landed.
19513        assert_eq!(
19514            f.run(&[b"XADD", b"gone", b"NOMKSTREAM", b"*", b"a", b"1"]),
19515            "$-1\r\n"
19516        );
19517        assert_eq!(f.run(&[b"EXISTS", b"gone"]), ":0\r\n");
19518        assert_eq!(f.run(&[b"TYPE", b"s"]), "+stream\r\n");
19519        assert_eq!(f.run(&[b"OBJECT", b"ENCODING", b"s"]), "$6\r\nstream\r\n");
19520    }
19521
19522    /// The trim options, which are three keywords that disagree about how many
19523    /// arguments they take.
19524    #[test]
19525    fn trimming_reads_its_options_the_way_redis_does() {
19526        let mut f = Fixture::new();
19527        for i in 1..=10u32 {
19528            f.run(&[b"XADD", b"s", format!("{i}-1").as_bytes(), b"a", b"1"]);
19529        }
19530        assert_eq!(f.run(&[b"XTRIM", b"s", b"MAXLEN", b"4"]), ":6\r\n");
19531        assert_eq!(f.run(&[b"XLEN", b"s"]), ":4\r\n");
19532        assert_eq!(f.run(&[b"XTRIM", b"s", b"MINID", b"9"]), ":2\r\n");
19533        assert_eq!(f.run(&[b"XLEN", b"s"]), ":2\r\n");
19534
19535        // One argument after the keyword and the `~` is read as the threshold,
19536        // which is what a real server does and is the reason this is a number
19537        // complaint and not a syntax one.
19538        assert!(
19539            f.run(&[b"XTRIM", b"s", b"MAXLEN", b"~"])
19540                .contains("not an integer")
19541        );
19542        assert!(
19543            f.run(&[b"XTRIM", b"s", b"MAXLEN", b"-1"])
19544                .contains("MAXLEN argument must be >= 0")
19545        );
19546        // The strategy check runs before the approximation check, so a LIMIT
19547        // with neither is told about the missing strategy.
19548        assert!(
19549            f.run(&[b"XTRIM", b"s", b"LIMIT", b"5"])
19550                .contains("without specifying a trimming strategy")
19551        );
19552        assert!(
19553            f.run(&[b"XTRIM", b"s", b"MAXLEN", b"5", b"LIMIT", b"5"])
19554                .contains("without the special ~ option")
19555        );
19556        assert!(
19557            f.run(&[b"XTRIM", b"s", b"MAXLEN", b"5", b"MINID", b"5"])
19558                .contains("at the same time are not compatible")
19559        );
19560        // NOMKSTREAM is XADD's and XTRIM does not take it.
19561        assert!(
19562            f.run(&[b"XTRIM", b"s", b"NOMKSTREAM", b"MAXLEN", b"5"])
19563                .contains("syntax error")
19564        );
19565        assert_eq!(f.run(&[b"XTRIM", b"missing", b"MAXLEN", b"5"]), ":0\r\n");
19566    }
19567
19568    /// `XRANGE`, whose two kinds of nothing are the thing worth pinning.
19569    #[test]
19570    fn xrange_looks_the_key_up_before_it_reads_the_count() {
19571        let mut f = Fixture::new();
19572        f.run(&[b"XADD", b"s", b"5-1", b"a", b"1"]);
19573        f.run(&[b"XADD", b"s", b"6-1", b"b", b"2"]);
19574
19575        assert_eq!(
19576            f.run(&[b"XRANGE", b"s", b"-", b"+"]),
19577            "*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\
19578             *2\r\n$3\r\n6-1\r\n*2\r\n$1\r\nb\r\n$1\r\n2\r\n"
19579        );
19580        assert_eq!(
19581            f.run(&[b"XREVRANGE", b"s", b"+", b"-", b"COUNT", b"1"]),
19582            "*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"
19583        );
19584        // The exclusive bound is stepped after the missing sequence is filled
19585        // in, so `(6` is `6-` and the largest sequence there is, minus one, and
19586        // `6-1` is still in the range.
19587        assert_eq!(
19588            f.run(&[b"XRANGE", b"s", b"-", b"(6"]),
19589            "*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\
19590             *2\r\n$3\r\n6-1\r\n*2\r\n$1\r\nb\r\n$1\r\n2\r\n"
19591        );
19592        assert_eq!(
19593            f.run(&[b"XRANGE", b"s", b"(5-1", b"+"]),
19594            "*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"
19595        );
19596        assert!(
19597            f.run(&[b"XRANGE", b"s", b"(-", b"+"])
19598                .contains("Invalid stream ID")
19599        );
19600
19601        // The two kinds of nothing. A key that is not there is an empty array
19602        // and a key that is there with a count of zero is a null array, because
19603        // the lookup happens first.
19604        assert_eq!(
19605            f.run(&[b"XRANGE", b"missing", b"-", b"+", b"COUNT", b"0"]),
19606            "*0\r\n"
19607        );
19608        assert_eq!(
19609            f.run(&[b"XRANGE", b"s", b"-", b"+", b"COUNT", b"0"]),
19610            "*-1\r\n"
19611        );
19612        f.run(&[b"SET", b"str", b"v"]);
19613        assert!(
19614            f.run(&[b"XRANGE", b"str", b"-", b"+", b"COUNT", b"0"])
19615                .starts_with("-WRONGTYPE")
19616        );
19617        // The count is read in a loop, so the last one wins.
19618        assert_eq!(
19619            f.run(&[b"XRANGE", b"s", b"-", b"+", b"COUNT", b"2", b"COUNT", b"1"]),
19620            "*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"
19621        );
19622    }
19623
19624    /// `XDEL` and `XACK` check every id before they touch any of them.
19625    #[test]
19626    fn a_bad_id_late_in_the_list_stops_the_whole_command() {
19627        let mut f = Fixture::new();
19628        f.run(&[b"XADD", b"s", b"1-1", b"a", b"1"]);
19629        f.run(&[b"XADD", b"s", b"2-1", b"a", b"2"]);
19630        assert!(
19631            f.run(&[b"XDEL", b"s", b"1-1", b"nonsense"])
19632                .contains("Invalid stream ID")
19633        );
19634        assert_eq!(f.run(&[b"XLEN", b"s"]), ":2\r\n");
19635        assert_eq!(f.run(&[b"XDEL", b"s", b"1-1", b"9-9"]), ":1\r\n");
19636        assert_eq!(f.run(&[b"XLEN", b"s"]), ":1\r\n");
19637        assert_eq!(f.run(&[b"XDEL", b"missing", b"1-1"]), ":0\r\n");
19638        assert_eq!(f.run(&[b"XACK", b"missing", b"g", b"1-1"]), ":0\r\n");
19639    }
19640
19641    /// `XGROUP`, and the two different complaints it makes about arguments.
19642    #[test]
19643    fn xgroup_has_an_arity_per_subcommand() {
19644        let mut f = Fixture::new();
19645        assert!(
19646            f.run(&[b"XGROUP", b"CREATE", b"s", b"g", b"$"])
19647                .contains("requires the key")
19648        );
19649        assert_eq!(
19650            f.run(&[b"XGROUP", b"CREATE", b"s", b"g", b"$", b"MKSTREAM"]),
19651            "+OK\r\n"
19652        );
19653        // A second CREATE is BUSYGROUP and not an ordinary error, because a
19654        // client racing another one to make a group branches on the prefix.
19655        assert!(
19656            f.run(&[b"XGROUP", b"CREATE", b"s", b"g", b"$"])
19657                .starts_with("-BUSYGROUP")
19658        );
19659        assert_eq!(
19660            f.run(&[b"XGROUP", b"CREATECONSUMER", b"s", b"g", b"c"]),
19661            ":1\r\n"
19662        );
19663        assert_eq!(
19664            f.run(&[b"XGROUP", b"CREATECONSUMER", b"s", b"g", b"c"]),
19665            ":0\r\n"
19666        );
19667        assert_eq!(
19668            f.run(&[b"XGROUP", b"DELCONSUMER", b"s", b"g", b"c"]),
19669            ":0\r\n"
19670        );
19671
19672        // Below the subcommand's own arity is an arity error naming the pair.
19673        let short = f.run(&[b"XGROUP", b"DESTROY", b"s"]);
19674        assert!(
19675            short.contains("wrong number of arguments for 'xgroup|destroy' command"),
19676            "{short}"
19677        );
19678        // At or above it in a shape the handler will not take is the other one.
19679        let odd = f.run(&[b"XGROUP", b"SETID", b"s", b"g", b"0", b"ENTRIESREAD"]);
19680        assert!(
19681            odd.contains("unknown subcommand or wrong number of arguments for 'SETID'"),
19682            "{odd}"
19683        );
19684        assert!(
19685            f.run(&[b"XGROUP", b"NOSUCH", b"s"])
19686                .contains("Try XGROUP HELP")
19687        );
19688
19689        assert_eq!(f.run(&[b"XGROUP", b"SETID", b"s", b"g", b"0"]), "+OK\r\n");
19690        assert!(
19691            f.run(&[b"XGROUP", b"SETID", b"s", b"nogroup", b"0"])
19692                .starts_with("-NOGROUP")
19693        );
19694        assert_eq!(f.run(&[b"XGROUP", b"DESTROY", b"s", b"g"]), ":1\r\n");
19695        assert_eq!(f.run(&[b"XGROUP", b"DESTROY", b"s", b"g"]), ":0\r\n");
19696        assert!(
19697            f.run(&[b"XGROUP", b"DESTROY", b"missing", b"g"])
19698                .contains("requires the key")
19699        );
19700    }
19701
19702    /// A group read, an acknowledgement, and what is left in between.
19703    #[test]
19704    fn xreadgroup_hands_out_and_xack_takes_back() {
19705        let mut f = Fixture::new();
19706        f.run(&[b"XADD", b"s", b"1-1", b"a", b"1"]);
19707        f.run(&[b"XADD", b"s", b"2-1", b"a", b"2"]);
19708        f.run(&[b"XGROUP", b"CREATE", b"s", b"g", b"0"]);
19709
19710        let first = f.run(&[
19711            b"XREADGROUP",
19712            b"GROUP",
19713            b"g",
19714            b"c1",
19715            b"COUNT",
19716            b"1",
19717            b"STREAMS",
19718            b"s",
19719            b">",
19720        ]);
19721        assert_eq!(
19722            first,
19723            "*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"
19724        );
19725        // A history read names its stream even with nothing to show, which is
19726        // the difference between it and a `>` read that found nothing.
19727        assert_eq!(
19728            f.run(&[b"XREADGROUP", b"GROUP", b"g", b"c2", b"STREAMS", b"s", b"0"]),
19729            "*1\r\n*2\r\n$1\r\ns\r\n*0\r\n"
19730        );
19731        assert_eq!(
19732            f.run(&[b"XREADGROUP", b"GROUP", b"g", b"c1", b"STREAMS", b"s", b"0"]),
19733            "*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"
19734        );
19735
19736        assert_eq!(
19737            f.run(&[b"XPENDING", b"s", b"g"]),
19738            "*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"
19739        );
19740        assert_eq!(f.run(&[b"XACK", b"s", b"g", b"1-1"]), ":1\r\n");
19741        assert_eq!(f.run(&[b"XACK", b"s", b"g", b"1-1"]), ":0\r\n");
19742        // Empty is four nulls and not a zero with three empty things.
19743        assert_eq!(
19744            f.run(&[b"XPENDING", b"s", b"g"]),
19745            "*4\r\n:0\r\n$-1\r\n$-1\r\n*-1\r\n"
19746        );
19747
19748        // A history read of an entry that has since been deleted is the id with
19749        // a null beside it, so the consumer can still acknowledge it.
19750        f.run(&[b"XREADGROUP", b"GROUP", b"g", b"c1", b"STREAMS", b"s", b">"]);
19751        f.run(&[b"XDEL", b"s", b"2-1"]);
19752        assert_eq!(
19753            f.run(&[b"XREADGROUP", b"GROUP", b"g", b"c1", b"STREAMS", b"s", b"0"]),
19754            "*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"
19755        );
19756
19757        // The group lookup runs before the id parse, so a `+` at a stream with
19758        // no such group is told about the group and not about the id.
19759        assert!(
19760            f.run(&[
19761                b"XREADGROUP",
19762                b"GROUP",
19763                b"nope",
19764                b"c",
19765                b"STREAMS",
19766                b"s",
19767                b"+"
19768            ])
19769            .starts_with("-NOGROUP")
19770        );
19771        assert!(
19772            f.run(&[b"XREADGROUP", b"GROUP", b"g", b"c", b"STREAMS", b"s", b"$"])
19773                .contains("meaningless in the context of XREADGROUP")
19774        );
19775        assert!(
19776            f.run(&[b"XREAD", b"GROUP", b"g", b"c", b"STREAMS", b"s", b"0"])
19777                .contains("only supported by XREADGROUP")
19778        );
19779        assert!(
19780            f.run(&[
19781                b"XREADGROUP",
19782                b"GROUP",
19783                b"g",
19784                b"c",
19785                b"STREAMS",
19786                b"s",
19787                b"a",
19788                b"b"
19789            ])
19790            .contains("Unbalanced 'xreadgroup' list of streams")
19791        );
19792    }
19793
19794    /// `XREAD` without `BLOCK`, which answers now and takes nothing for an
19795    /// answer.
19796    #[test]
19797    fn xread_with_no_block_writes_the_null_itself() {
19798        let mut f = Fixture::new();
19799        f.run(&[b"XADD", b"s", b"1-1", b"a", b"1"]);
19800        assert_eq!(
19801            f.run(&[b"XREAD", b"STREAMS", b"s", b"0"]),
19802            "*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"
19803        );
19804        // Nothing new is a null array and not an empty one, and a stream with
19805        // nothing new is left out rather than sent with an empty list.
19806        assert_eq!(f.run(&[b"XREAD", b"STREAMS", b"s", b"1-1"]), "*-1\r\n");
19807        assert_eq!(f.run(&[b"XREAD", b"STREAMS", b"missing", b"0"]), "*-1\r\n");
19808        f.run(&[b"XADD", b"other", b"1-1", b"b", b"2"]);
19809        assert_eq!(
19810            f.run(&[b"XREAD", b"STREAMS", b"s", b"other", b"1-1", b"0"]),
19811            "*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"
19812        );
19813        // `$` is the last id, so nothing that is already there comes back.
19814        assert_eq!(f.run(&[b"XREAD", b"STREAMS", b"s", b"$"]), "*-1\r\n");
19815        // And `+` is the last entry, whatever COUNT says.
19816        assert_eq!(
19817            f.run(&[b"XREAD", b"COUNT", b"5", b"STREAMS", b"s", b"+"]),
19818            "*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"
19819        );
19820        // A count of zero means unlimited here, which is the opposite of what it
19821        // means to XRANGE.
19822        assert_eq!(
19823            f.run(&[b"XREAD", b"COUNT", b"0", b"STREAMS", b"s", b"0"]),
19824            "*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"
19825        );
19826        // Milliseconds as a whole number, where BLPOP takes seconds as a float.
19827        assert!(
19828            f.run(&[b"XREAD", b"BLOCK", b"0.5", b"STREAMS", b"s", b"$"])
19829                .contains("not an integer")
19830        );
19831        assert!(
19832            f.run(&[b"XREAD", b"BLOCK", b"-1", b"STREAMS", b"s", b"$"])
19833                .contains("timeout is negative")
19834        );
19835        assert!(
19836            f.run(&[b"XREAD", b"STREAMS", b"s", b"other", b"0"])
19837                .contains("Unbalanced 'xread' list of streams")
19838        );
19839    }
19840
19841    /// A blocked reader, and the two ways it stops being blocked.
19842    #[test]
19843    fn a_blocked_xread_wakes_on_the_next_entry() {
19844        let mut f = Fixture::new();
19845        f.run(&[b"XADD", b"s", b"1-1", b"a", b"1"]);
19846        let (flow, reply) = f.flow(&[b"XREAD", b"BLOCK", b"0", b"STREAMS", b"s", b"$"]);
19847        assert_eq!(flow, Flow::Block);
19848        assert!(reply.is_empty());
19849
19850        // Everybody parked on the stream gets the entry, because a read takes
19851        // nothing away. That is the difference between this and BLPOP. Two
19852        // clients rather than one twice, since a client that is waiting is not
19853        // reading and cannot block again.
19854        f.session = Session::new(8);
19855        let (flow, _) = f.flow(&[b"XREAD", b"BLOCK", b"0", b"STREAMS", b"s", b"$"]);
19856        assert_eq!(flow, Flow::Block);
19857        assert_eq!(f.server.parked(), 2);
19858
19859        f.run(&[b"XADD", b"s", b"2-1", b"a", b"2"]);
19860        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";
19861        for client in [7, 8] {
19862            let mut out = Out::new(Proto::Resp2);
19863            assert!(f.server.serve_waiter(client, 0, &mut out));
19864            assert_eq!(core::str::from_utf8(out.as_slice()).expect("ascii"), want);
19865        }
19866
19867        // And a deadline that runs out is a null array, the same as a plain
19868        // XREAD that found nothing.
19869        f.server.forget_waiters(7);
19870        f.server.forget_waiters(8);
19871        let (flow, _) = f.flow(&[b"XREAD", b"BLOCK", b"50", b"STREAMS", b"s", b"$"]);
19872        assert_eq!(flow, Flow::Block);
19873        let mut out = Out::new(Proto::Resp2);
19874        assert!(!f.server.serve_waiter(8, 0, &mut out));
19875        assert!(out.as_slice().is_empty());
19876        assert!(f.server.serve_waiter(8, u64::MAX, &mut out));
19877        assert_eq!(
19878            core::str::from_utf8(out.as_slice()).expect("ascii"),
19879            "*-1\r\n"
19880        );
19881    }
19882
19883    /// A blocked group reader whose group is destroyed under it.
19884    #[test]
19885    fn losing_a_group_while_blocked_is_the_ordinary_sentence() {
19886        let mut f = Fixture::new();
19887        f.run(&[b"XADD", b"s", b"1-1", b"a", b"1"]);
19888        f.run(&[b"XGROUP", b"CREATE", b"s", b"g", b"$"]);
19889        let (flow, _) = f.flow(&[
19890            b"XREADGROUP",
19891            b"GROUP",
19892            b"g",
19893            b"c",
19894            b"BLOCK",
19895            b"0",
19896            b"STREAMS",
19897            b"s",
19898            b">",
19899        ]);
19900        assert_eq!(flow, Flow::Block);
19901
19902        f.run(&[b"XGROUP", b"DESTROY", b"s", b"g"]);
19903        let mut out = Out::new(Proto::Resp2);
19904        assert!(f.server.serve_waiter(7, 0, &mut out));
19905        // The ordinary sentence and not a special one about having been parked,
19906        // which is what a running 8.10 sends.
19907        assert_eq!(
19908            core::str::from_utf8(out.as_slice()).expect("ascii"),
19909            "-NOGROUP No such key 's' or consumer group 'g' in XREADGROUP with GROUP option\r\n"
19910        );
19911    }
19912
19913    /// `XCLAIM`, whose argument shape is the odd one in the group.
19914    #[test]
19915    fn xclaim_reads_ids_until_one_will_not_parse() {
19916        let mut f = Fixture::new();
19917        f.run(&[b"XADD", b"s", b"1-1", b"a", b"1"]);
19918        f.run(&[b"XADD", b"s", b"2-1", b"a", b"2"]);
19919        f.run(&[b"XGROUP", b"CREATE", b"s", b"g", b"0"]);
19920        f.run(&[b"XREADGROUP", b"GROUP", b"g", b"c1", b"STREAMS", b"s", b">"]);
19921
19922        // Everything after the first argument that is not an id is an option, so
19923        // a `-` is an unrecognised option and not a bad id.
19924        assert!(
19925            f.run(&[b"XCLAIM", b"s", b"g", b"c2", b"0", b"-"])
19926                .contains("Unrecognized XCLAIM option '-'")
19927        );
19928        assert_eq!(
19929            f.run(&[b"XCLAIM", b"s", b"g", b"c2", b"0", b"1-1", b"JUSTID"]),
19930            "*1\r\n$3\r\n1-1\r\n"
19931        );
19932        // An id that is pending but whose entry has gone is an empty answer, and
19933        // it leaves the pending list on the way past.
19934        f.run(&[b"XDEL", b"s", b"2-1"]);
19935        assert_eq!(
19936            f.run(&[b"XCLAIM", b"s", b"g", b"c2", b"0", b"2-1"]),
19937            "*0\r\n"
19938        );
19939        assert!(
19940            f.run(&[b"XPENDING", b"s", b"g"])
19941                .starts_with("*4\r\n:1\r\n")
19942        );
19943        assert!(
19944            f.run(&[b"XCLAIM", b"s", b"nope", b"c", b"0", b"1-1"])
19945                .starts_with("-NOGROUP")
19946        );
19947        assert!(
19948            f.run(&[b"XCLAIM", b"s", b"g", b"c", b"nan", b"1-1"])
19949                .contains("Invalid min-idle-time argument for XCLAIM")
19950        );
19951    }
19952
19953    /// `XAUTOCLAIM`, and the third value nobody expects.
19954    #[test]
19955    fn xautoclaim_reports_what_it_dropped() {
19956        let mut f = Fixture::new();
19957        f.run(&[b"XADD", b"s", b"1-1", b"a", b"1"]);
19958        f.run(&[b"XADD", b"s", b"2-1", b"a", b"2"]);
19959        f.run(&[b"XGROUP", b"CREATE", b"s", b"g", b"0"]);
19960        f.run(&[b"XREADGROUP", b"GROUP", b"g", b"c1", b"STREAMS", b"s", b">"]);
19961        f.run(&[b"XDEL", b"s", b"1-1"]);
19962
19963        // The cursor, what was claimed, and what was dropped for no longer being
19964        // in the stream. The third one is what makes a sweep converge.
19965        assert_eq!(
19966            f.run(&[b"XAUTOCLAIM", b"s", b"g", b"c2", b"0", b"-", b"JUSTID"]),
19967            "*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"
19968        );
19969        assert!(
19970            f.run(&[b"XAUTOCLAIM", b"s", b"g", b"c2", b"0", b"-", b"COUNT", b"0"])
19971                .contains("COUNT must be > 0")
19972        );
19973        assert!(
19974            f.run(&[b"XAUTOCLAIM", b"s", b"nope", b"c", b"0", b"-"])
19975                .starts_with("-NOGROUP")
19976        );
19977    }
19978
19979    /// `XDELEX`, which is `XDEL` with a say in what the groups keep.
19980    #[test]
19981    fn xdelex_answers_one_integer_an_id() {
19982        let mut f = Fixture::new();
19983        for i in 1..=4 {
19984            f.run(&[b"XADD", b"s", format!("{i}-1").as_bytes(), b"a", b"1"]);
19985        }
19986        f.run(&[b"XGROUP", b"CREATE", b"s", b"g", b"0"]);
19987        f.run(&[
19988            b"XREADGROUP",
19989            b"GROUP",
19990            b"g",
19991            b"c",
19992            b"COUNT",
19993            b"2",
19994            b"STREAMS",
19995            b"s",
19996            b">",
19997        ]);
19998
19999        // One means gone and minus one means it was not there to start with.
20000        assert_eq!(
20001            f.run(&[b"XDELEX", b"s", b"IDS", b"2", b"1-1", b"9-9"]),
20002            "*2\r\n:1\r\n:-1\r\n"
20003        );
20004        // `KEEPREF` leaves the pending entry behind, so the group still counts
20005        // the one it was handed even though the entry has gone.
20006        assert!(
20007            f.run(&[b"XPENDING", b"s", b"g"])
20008                .starts_with("*4\r\n:2\r\n")
20009        );
20010        // `DELREF` takes it out of every pending list on the way past.
20011        assert_eq!(
20012            f.run(&[b"XDELEX", b"s", b"DELREF", b"IDS", b"1", b"2-1"]),
20013            "*1\r\n:1\r\n"
20014        );
20015        // `1-1` is still in the list, because the delete before it said KEEPREF.
20016        assert_eq!(
20017            f.run(&[b"XPENDING", b"s", b"g"]),
20018            "*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"
20019        );
20020
20021        // Two means somebody still wants it, and the question is wider than the
20022        // name: the group's bookmark is at `2-1`, so `4-1` is above it and is
20023        // refused even though no consumer has ever been handed it.
20024        assert_eq!(
20025            f.run(&[b"XDELEX", b"s", b"ACKED", b"IDS", b"2", b"3-1", b"4-1"]),
20026            "*2\r\n:2\r\n:2\r\n"
20027        );
20028
20029        // A key that is not there answers minus ones without reading the IDs.
20030        assert_eq!(
20031            f.run(&[b"XDELEX", b"nope", b"IDS", b"2", b"bad", b"worse"]),
20032            "*2\r\n:-1\r\n:-1\r\n"
20033        );
20034        // A key that is there validates every ID before deleting any of them.
20035        assert!(
20036            f.run(&[b"XDELEX", b"s", b"IDS", b"2", b"3-1", b"bad"])
20037                .starts_with("-ERR Invalid stream ID")
20038        );
20039        assert_eq!(f.run(&[b"XLEN", b"s"]), ":2\r\n");
20040
20041        assert!(
20042            f.run(&[b"XDELEX", b"s", b"IDS", b"0", b"1-1"])
20043                .contains("Number of IDs must be a positive integer")
20044        );
20045        assert!(
20046            f.run(&[b"XDELEX", b"s", b"IDS", b"2", b"1-1"])
20047                .contains("The `numids` parameter must match the number of arguments")
20048        );
20049        // The condition is one word, so a second one is a syntax error, and so
20050        // is one ID more than the count promised.
20051        assert!(
20052            f.run(&[b"XDELEX", b"s", b"KEEPREF", b"DELREF", b"IDS", b"1", b"1-1"])
20053                .starts_with("-ERR syntax error")
20054        );
20055        assert!(
20056            f.run(&[b"XDELEX", b"s", b"IDS", b"1", b"1-1", b"2-1"])
20057                .starts_with("-ERR syntax error")
20058        );
20059        // The key is looked up first, so the wrong type beats the syntax.
20060        f.run(&[b"SET", b"str", b"v"]);
20061        assert!(
20062            f.run(&[b"XDELEX", b"str", b"BOGUS", b"IDS", b"0", b"1-1"])
20063                .starts_with("-WRONGTYPE")
20064        );
20065    }
20066
20067    /// `XACKDEL`, whose reply is about the pending list and not about the log.
20068    #[test]
20069    fn xackdel_reports_what_the_group_was_holding() {
20070        let mut f = Fixture::new();
20071        for i in 1..=3 {
20072            f.run(&[b"XADD", b"s", format!("{i}-1").as_bytes(), b"a", b"1"]);
20073        }
20074        f.run(&[b"XGROUP", b"CREATE", b"s", b"g", b"0"]);
20075        f.run(&[
20076            b"XREADGROUP",
20077            b"GROUP",
20078            b"g",
20079            b"c",
20080            b"COUNT",
20081            b"1",
20082            b"STREAMS",
20083            b"s",
20084            b">",
20085        ]);
20086
20087        // Minus one is not about the stream: `2-1` is sitting there unread and
20088        // still answers minus one, because the group was not holding it. It also
20089        // stays, since only an ID that was acknowledged is deleted.
20090        assert_eq!(
20091            f.run(&[b"XACKDEL", b"s", b"g", b"IDS", b"2", b"1-1", b"2-1"]),
20092            "*2\r\n:1\r\n:-1\r\n"
20093        );
20094        assert_eq!(f.run(&[b"XLEN", b"s"]), ":2\r\n");
20095
20096        // A missing group is minus one an ID and not a NOGROUP.
20097        assert_eq!(
20098            f.run(&[b"XACKDEL", b"s", b"nope", b"IDS", b"1", b"2-1"]),
20099            "*1\r\n:-1\r\n"
20100        );
20101        assert_eq!(
20102            f.run(&[b"XACKDEL", b"nope", b"g", b"IDS", b"1", b"2-1"]),
20103            "*1\r\n:-1\r\n"
20104        );
20105
20106        // The acknowledgement happens whatever the condition says, so an ACKED
20107        // that answers two has still emptied the pending list.
20108        f.run(&[b"XREADGROUP", b"GROUP", b"g", b"c", b"STREAMS", b"s", b">"]);
20109        f.run(&[b"XGROUP", b"CREATE", b"s", b"g2", b"0"]);
20110        assert_eq!(
20111            f.run(&[b"XACKDEL", b"s", b"g", b"ACKED", b"IDS", b"1", b"2-1"]),
20112            "*1\r\n:2\r\n"
20113        );
20114        assert_eq!(
20115            f.run(&[b"XPENDING", b"s", b"g"]),
20116            "*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"
20117        );
20118        assert_eq!(f.run(&[b"XLEN", b"s"]), ":2\r\n");
20119    }
20120
20121    /// `XNACK`, which hands an entry back to nobody.
20122    #[test]
20123    fn xnack_releases_an_entry_for_the_next_claim() {
20124        let mut f = Fixture::new();
20125        f.run(&[b"XADD", b"s", b"1-1", b"a", b"1"]);
20126        f.run(&[b"XADD", b"s", b"2-1", b"a", b"2"]);
20127        f.run(&[b"XGROUP", b"CREATE", b"s", b"g", b"0"]);
20128        f.run(&[b"XREADGROUP", b"GROUP", b"g", b"c1", b"STREAMS", b"s", b">"]);
20129        // Twice, so the delivery count is two and the words have something to
20130        // do with it.
20131        f.run(&[b"XCLAIM", b"s", b"g", b"c1", b"0", b"1-1", b"2-1"]);
20132
20133        assert_eq!(
20134            f.run(&[b"XNACK", b"s", b"g", b"FAIL", b"IDS", b"1", b"1-1"]),
20135            ":1\r\n"
20136        );
20137        // No owner, no idle time, and the count left where it was. A released
20138        // entry reads as idle for longer than any min-idle-time, which is what
20139        // puts it at the front of the next claim.
20140        assert_eq!(
20141            f.run(&[b"XPENDING", b"s", b"g", b"-", b"+", b"10"]),
20142            "*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"
20143        );
20144        // The consumer no longer holds it, so a filtered XPENDING skips it.
20145        assert_eq!(
20146            f.run(&[b"XPENDING", b"s", b"g", b"-", b"+", b"10", b"c1"]),
20147            "*1\r\n*4\r\n$3\r\n2-1\r\n$2\r\nc1\r\n:0\r\n:2\r\n"
20148        );
20149        // The bookmark did not move, so a `>` read will not hand it out again.
20150        assert_eq!(
20151            f.run(&[b"XREADGROUP", b"GROUP", b"g", b"c2", b"STREAMS", b"s", b">"]),
20152            "*-1\r\n"
20153        );
20154        // A claim at any min-idle-time takes it.
20155        assert_eq!(
20156            f.run(&[
20157                b"XAUTOCLAIM",
20158                b"s",
20159                b"g",
20160                b"c2",
20161                b"99999999",
20162                b"-",
20163                b"JUSTID"
20164            ]),
20165            "*3\r\n$3\r\n0-0\r\n*1\r\n$3\r\n1-1\r\n*0\r\n"
20166        );
20167
20168        // `SILENT` takes one off the count rather than putting it back to zero,
20169        // which only shows on an entry that has been handed out more than once.
20170        // It was delivered and then claimed, so it is on two and goes to one.
20171        f.run(&[b"XNACK", b"s", b"g", b"SILENT", b"IDS", b"1", b"1-1"]);
20172        assert!(
20173            f.run(&[b"XPENDING", b"s", b"g", b"-", b"+", b"10"])
20174                .contains(":-1\r\n:1\r\n")
20175        );
20176        // And it stops at zero rather than wrapping.
20177        f.run(&[b"XNACK", b"s", b"g", b"SILENT", b"IDS", b"1", b"1-1"]);
20178        f.run(&[b"XNACK", b"s", b"g", b"SILENT", b"IDS", b"1", b"1-1"]);
20179        assert!(
20180            f.run(&[b"XPENDING", b"s", b"g", b"-", b"+", b"10"])
20181                .contains(":-1\r\n:0\r\n")
20182        );
20183        // `FATAL` puts it at the ceiling, and `RETRYCOUNT` wins over the word.
20184        f.run(&[b"XNACK", b"s", b"g", b"FATAL", b"IDS", b"1", b"1-1"]);
20185        assert!(
20186            f.run(&[b"XPENDING", b"s", b"g", b"-", b"+", b"10"])
20187                .contains(":9223372036854775807\r\n")
20188        );
20189        f.run(&[
20190            b"XNACK",
20191            b"s",
20192            b"g",
20193            b"FATAL",
20194            b"IDS",
20195            b"1",
20196            b"1-1",
20197            b"RETRYCOUNT",
20198            b"3",
20199        ]);
20200        assert!(
20201            f.run(&[b"XPENDING", b"s", b"g", b"-", b"+", b"10"])
20202                .contains(":-1\r\n:3\r\n")
20203        );
20204
20205        // Releasing something the group is not holding is zero, and `FORCE`
20206        // makes the pending entry rather than answering zero. A forced entry
20207        // starts at zero, since there was no earlier count to keep.
20208        f.run(&[b"XACK", b"s", b"g", b"2-1"]);
20209        assert_eq!(
20210            f.run(&[b"XNACK", b"s", b"g", b"FAIL", b"IDS", b"1", b"2-1"]),
20211            ":0\r\n"
20212        );
20213        assert_eq!(
20214            f.run(&[
20215                b"XNACK", b"s", b"g", b"FAIL", b"IDS", b"1", b"2-1", b"FORCE"
20216            ]),
20217            ":1\r\n"
20218        );
20219        assert!(
20220            f.run(&[b"XPENDING", b"s", b"g", b"-", b"+", b"10"])
20221                .contains(":-1\r\n:0\r\n")
20222        );
20223        // `FORCE` on an ID the stream does not have is still zero.
20224        assert_eq!(
20225            f.run(&[
20226                b"XNACK", b"s", b"g", b"FAIL", b"IDS", b"1", b"9-9", b"FORCE"
20227            ]),
20228            ":0\r\n"
20229        );
20230
20231        // The group is looked up before the mode word, and it raises rather
20232        // than answering per ID the way the two delete commands do.
20233        assert_eq!(
20234            f.run(&[b"XNACK", b"s", b"nope", b"BOGUS", b"IDS", b"1", b"1-1"]),
20235            "-NOGROUP No such key 's' or consumer group 'nope'\r\n"
20236        );
20237        assert!(
20238            f.run(&[b"XNACK", b"s", b"g", b"BOGUS", b"IDS", b"1", b"1-1"])
20239                .starts_with("-ERR")
20240        );
20241        // Its own sentences, which are not the ones XDELEX uses.
20242        assert!(
20243            f.run(&[b"XNACK", b"s", b"g", b"FAIL", b"IDS", b"0", b"1-1"])
20244                .contains("numids must be a positive integer")
20245        );
20246        assert!(
20247            f.run(&[b"XNACK", b"s", b"g", b"FAIL", b"IDS", b"2", b"1-1"])
20248                .contains("number of IDs doesn't match numids")
20249        );
20250        // Everything past the counted IDs is an option, so one too many is an
20251        // option nobody recognises and not a count that does not add up.
20252        assert!(
20253            f.run(&[b"XNACK", b"s", b"g", b"FAIL", b"IDS", b"1", b"1-1", b"2-1"])
20254                .contains("Unrecognized XNACK option '2-1'")
20255        );
20256    }
20257
20258    /// `XINFO`, which is where the shape of the storage shows through.
20259    #[test]
20260    fn xinfo_reports_the_stream_the_groups_and_the_consumers() {
20261        let mut f = Fixture::new();
20262        f.run(&[b"XADD", b"s", b"1-1", b"a", b"1"]);
20263        f.run(&[b"XADD", b"s", b"2-1", b"a", b"2"]);
20264        f.run(&[b"XGROUP", b"CREATE", b"s", b"g", b"0"]);
20265        f.run(&[
20266            b"XREADGROUP",
20267            b"GROUP",
20268            b"g",
20269            b"c1",
20270            b"COUNT",
20271            b"1",
20272            b"STREAMS",
20273            b"s",
20274            b">",
20275        ]);
20276
20277        let info = f.run(&[b"XINFO", b"STREAM", b"s"]);
20278        // Ten pairs, since the six idempotency fields have nothing behind them
20279        // here and a zero would claim they had. That is D-27.
20280        assert!(info.starts_with("*20\r\n"), "{info}");
20281        assert!(info.contains("$6\r\nlength\r\n:2\r\n"), "{info}");
20282        assert!(
20283            info.contains("$17\r\nlast-generated-id\r\n$3\r\n2-1\r\n"),
20284            "{info}"
20285        );
20286        assert!(info.contains("$13\r\nentries-added\r\n:2\r\n"), "{info}");
20287        assert!(info.contains("$6\r\ngroups\r\n:1\r\n"), "{info}");
20288
20289        let groups = f.run(&[b"XINFO", b"GROUPS", b"s"]);
20290        assert!(groups.starts_with("*1\r\n*12\r\n"), "{groups}");
20291        assert!(groups.contains("$9\r\nconsumers\r\n:1\r\n"), "{groups}");
20292        assert!(groups.contains("$7\r\npending\r\n:1\r\n"), "{groups}");
20293        assert!(groups.contains("$3\r\nlag\r\n:1\r\n"), "{groups}");
20294
20295        // A consumer that has never been given anything reports minus one for
20296        // inactive rather than the moment it turned up, which is what tells a
20297        // worker that is stuck from one that has nothing to do.
20298        f.run(&[b"XGROUP", b"CREATECONSUMER", b"s", b"g", b"c2"]);
20299        let consumers = f.run(&[b"XINFO", b"CONSUMERS", b"s", b"g"]);
20300        assert!(consumers.starts_with("*2\r\n"), "{consumers}");
20301        assert!(
20302            consumers.contains("$8\r\ninactive\r\n:-1\r\n"),
20303            "{consumers}"
20304        );
20305        // And in name order, which the storage does not hold them in.
20306        let c1 = consumers.find("c1").unwrap();
20307        let c2 = consumers.find("c2").unwrap();
20308        assert!(c1 < c2, "{consumers}");
20309
20310        let full = f.run(&[b"XINFO", b"STREAM", b"s", b"FULL"]);
20311        assert!(full.starts_with("*18\r\n"), "{full}");
20312        assert!(full.contains("$12\r\nnacked-count\r\n:0\r\n"), "{full}");
20313        assert!(full.contains("$11\r\nactive-time\r\n"), "{full}");
20314
20315        assert!(
20316            f.run(&[b"XINFO", b"STREAM", b"missing"])
20317                .contains("no such key")
20318        );
20319        assert!(
20320            f.run(&[b"XINFO", b"GROUPS", b"missing"])
20321                .contains("no such key")
20322        );
20323        assert!(
20324            f.run(&[b"XINFO", b"CONSUMERS", b"s", b"nope"])
20325                .starts_with("-NOGROUP")
20326        );
20327        assert!(
20328            f.run(&[b"XINFO", b"NOSUCH", b"s"])
20329                .contains("Try XINFO HELP")
20330        );
20331        assert!(f.run(&[b"XINFO", b"HELP"]).contains("XINFO <subcommand>"));
20332        assert!(f.run(&[b"XGROUP", b"HELP"]).contains("XGROUP <subcommand>"));
20333    }
20334
20335    /// `XPENDING`'s long form, which reads its arguments by counting them.
20336    #[test]
20337    fn xpending_takes_the_consumer_only_when_the_count_comes_out_right() {
20338        let mut f = Fixture::new();
20339        f.run(&[b"XADD", b"s", b"1-1", b"a", b"1"]);
20340        f.run(&[b"XGROUP", b"CREATE", b"s", b"g", b"0"]);
20341        f.run(&[b"XREADGROUP", b"GROUP", b"g", b"c1", b"STREAMS", b"s", b">"]);
20342
20343        let list = f.run(&[b"XPENDING", b"s", b"g", b"-", b"+", b"10"]);
20344        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");
20345        assert_eq!(
20346            f.run(&[b"XPENDING", b"s", b"g", b"-", b"+", b"10", b"c1"]),
20347            "*1\r\n*4\r\n$3\r\n1-1\r\n$2\r\nc1\r\n:0\r\n:1\r\n"
20348        );
20349        // A consumer nobody has heard of holds nothing rather than erroring.
20350        assert_eq!(
20351            f.run(&[b"XPENDING", b"s", b"g", b"-", b"+", b"10", b"nope"]),
20352            "*0\r\n"
20353        );
20354        assert_eq!(
20355            f.run(&[b"XPENDING", b"s", b"g", b"IDLE", b"0", b"-", b"+", b"10"]),
20356            list
20357        );
20358        // IDLE is only read at position three.
20359        assert!(
20360            f.run(&[b"XPENDING", b"s", b"g", b"IDLE", b"0"])
20361                .contains("syntax error")
20362        );
20363        assert!(
20364            f.run(&[b"XPENDING", b"s", b"g", b"-", b"+"])
20365                .contains("syntax error")
20366        );
20367        assert_eq!(
20368            f.run(&[b"XPENDING", b"s", b"g", b"-", b"+", b"-1"]),
20369            "*0\r\n"
20370        );
20371        assert!(
20372            f.run(&[b"XPENDING", b"missing", b"g"])
20373                .starts_with("-NOGROUP")
20374        );
20375    }
20376
20377    /// `XSETID`, which is three counters and two refusals.
20378    #[test]
20379    fn xsetid_will_not_go_below_what_is_there() {
20380        let mut f = Fixture::new();
20381        f.run(&[b"XADD", b"s", b"5-5", b"a", b"1"]);
20382        assert_eq!(f.run(&[b"XSETID", b"s", b"9-9"]), "+OK\r\n");
20383        assert_eq!(
20384            f.run(&[
20385                b"XSETID",
20386                b"s",
20387                b"10-1",
20388                b"ENTRIESADDED",
20389                b"7",
20390                b"MAXDELETEDID",
20391                b"9-1"
20392            ]),
20393            "+OK\r\n"
20394        );
20395        let info = f.run(&[b"XINFO", b"STREAM", b"s"]);
20396        assert!(info.contains("$13\r\nentries-added\r\n:7\r\n"), "{info}");
20397        assert!(
20398            info.contains("$20\r\nmax-deleted-entry-id\r\n$3\r\n9-1\r\n"),
20399            "{info}"
20400        );
20401
20402        assert!(
20403            f.run(&[b"XSETID", b"s", b"1-1"])
20404                .contains("smaller than the target stream top item")
20405        );
20406        assert!(
20407            f.run(&[b"XSETID", b"s", b"10-1", b"ENTRIESADDED", b"-1"])
20408                .contains("entries_added must be positive")
20409        );
20410        assert!(
20411            f.run(&[b"XSETID", b"missing", b"1-1"])
20412                .contains("no such key")
20413        );
20414    }
20415
20416    /// RESP3, where the two reads answer a map and the entries stay an array.
20417    #[test]
20418    fn xread_answers_a_map_on_resp3_and_the_fields_stay_flat() {
20419        let mut f = Fixture::new();
20420        f.run(&[b"HELLO", b"3"]);
20421        f.run(&[b"XADD", b"s", b"1-1", b"a", b"1"]);
20422        // A map header and then the key and the entries side by side, with no
20423        // two element array wrapping the pair.
20424        assert_eq!(
20425            f.run(&[b"XREAD", b"STREAMS", b"s", b"0"]),
20426            "%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"
20427        );
20428        // The fields are still one flat array and not a map, which is Redis's
20429        // shape and is what every consumer written before RESP3 expects.
20430        assert_eq!(
20431            f.run(&[b"XRANGE", b"s", b"-", b"+"]),
20432            "*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"
20433        );
20434        assert_eq!(f.run(&[b"XREAD", b"STREAMS", b"s", b"1-1"]), "_\r\n");
20435    }
20436
20437    /// A store to migrate values into, so a test can watch the inversion.
20438    ///
20439    /// A vector rather than a file for the same reason the tier's own tests use
20440    /// one: the file work has not attached a real store yet, and what this is
20441    /// checking is the policy above the store rather than the store.
20442    struct Mem {
20443        blobs: Vec<Vec<u8>>,
20444    }
20445
20446    impl yo_kv::cold::Blocks for Mem {
20447        fn put(&mut self, bytes: &[u8]) -> yo_common::Result<yo_common::Addr> {
20448            self.blobs.push(bytes.to_vec());
20449            Ok(yo_common::Addr::new(
20450                yo_common::Space::Log,
20451                (self.blobs.len() - 1) as u64,
20452            ))
20453        }
20454
20455        fn get(&self, at: yo_common::Addr) -> yo_common::Result<&[u8]> {
20456            self.blobs
20457                .get(at.offset() as usize)
20458                .map(Vec::as_slice)
20459                .ok_or_else(|| {
20460                    yo_common::Error::new(yo_common::Code::Corrupt, "no chunk at that address")
20461                })
20462        }
20463
20464        fn bytes(&self) -> u64 {
20465            self.blobs.iter().map(|b| b.len() as u64).sum()
20466        }
20467    }
20468
20469    /// A server holding several segments of strings, with somewhere to put them.
20470    ///
20471    /// Answers the fixture and what it was holding when it stopped filling.
20472    /// The three tests that call this are the ones Miri is not run over.
20473    ///
20474    /// What they are about is the regime a database is in once the arena has
20475    /// several segments, and a segment is two megabytes, so there is no smaller
20476    /// version of the question: twenty four thousand keys is already the least
20477    /// that gets there. Interpreted, each of them sat for over forty minutes
20478    /// and was still going. The arena's own segment handling is interpreted in
20479    /// full in its own crate, and the policy these three check is ordinary
20480    /// bookkeeping with no unsafe block anywhere in it.
20481    fn filled(attach: bool) -> (Fixture, usize) {
20482        let mut f = Fixture::new();
20483        if attach {
20484            f.server
20485                .striped(0)
20486                .hold_stripe(0)
20487                .attach(Box::new(Mem { blobs: Vec::new() }));
20488        }
20489        let val = vec![b'v'; 256];
20490        for i in 0..24000u32 {
20491            let k = format!("key:{i:08}");
20492            f.run(&[b"SET", k.as_bytes(), &val]);
20493        }
20494        let full = f.server.memory_bytes();
20495        assert!(full > 3 * 1024 * 1024, "the arena is several segments");
20496        (f, full)
20497    }
20498
20499    /// Write until the server is under `limit` or the writes run out.
20500    ///
20501    /// The same shape the eviction test uses. A memory limit is enforced in
20502    /// front of a command, so nothing happens until something is written, and
20503    /// the budget means one command does not do the whole job.
20504    fn press(f: &mut Fixture, limit: usize) {
20505        let val = vec![b'v'; 256];
20506        for i in 0..3000u32 {
20507            let k = format!("new:{i:08}");
20508            assert_eq!(
20509                f.run(&[b"SET", k.as_bytes(), &val]),
20510                "+OK\r\n",
20511                "write {i} was refused"
20512            );
20513            f.server.refresh_memory();
20514            if f.server.memory_bytes() <= limit {
20515                return;
20516            }
20517        }
20518        panic!(
20519            "it never got under: {} against {limit}",
20520            f.server.memory_bytes()
20521        );
20522    }
20523
20524    #[test]
20525    fn the_storage_limit_reads_back_and_minus_one_is_no_limit() {
20526        let mut f = Fixture::new();
20527        assert_eq!(
20528            f.run(&[b"CONFIG", b"GET", b"maxstore"]),
20529            "*2\r\n$8\r\nmaxstore\r\n$2\r\n-1\r\n",
20530            "no limit is the default"
20531        );
20532        // The same memory value parser `maxmemory` uses, and the same trap in
20533        // it, plus the one spelling that means no limit at all.
20534        for (typed, bytes) in [
20535            (&b"0"[..], "0"),
20536            (b"1024", "1024"),
20537            (b"1k", "1000"),
20538            (b"1gb", "1073741824"),
20539            (b"-1", "-1"),
20540        ] {
20541            assert_eq!(f.run(&[b"CONFIG", b"SET", b"maxstore", typed]), "+OK\r\n");
20542            assert_eq!(
20543                f.run(&[b"CONFIG", b"GET", b"maxstore"]),
20544                format!("*2\r\n$8\r\nmaxstore\r\n${}\r\n{bytes}\r\n", bytes.len()),
20545                "set {}",
20546                String::from_utf8_lossy(typed)
20547            );
20548        }
20549        for bad in [&b"1tb"[..], b"-2", b"", b"lots"] {
20550            assert_eq!(
20551                f.run(&[b"CONFIG", b"SET", b"maxstore", bad]),
20552                "-ERR CONFIG SET failed (possibly related to argument 'maxstore') - argument must be a memory value or -1\r\n",
20553                "refused {}",
20554                String::from_utf8_lossy(bad)
20555            );
20556        }
20557        // Nothing is attached, so the answer to a memory limit is still Redis's.
20558        let info = f.run(&[b"INFO", b"memory"]);
20559        assert!(info.contains("maxstore:-1"), "{info}");
20560        assert!(info.contains("yo_memory_regime:evict"), "{info}");
20561        assert!(info.contains("yo_store_bytes:0"), "{info}");
20562    }
20563
20564    #[cfg_attr(miri, ignore = "several megabytes of arena, see `filled`")]
20565    #[test]
20566    fn a_memory_limit_moves_values_to_the_file_instead_of_dropping_keys() {
20567        // The inversion. The same pressure that makes a Redis server throw keys
20568        // away makes this one move values to the file, and afterwards every key
20569        // is still there and still answers with what was stored in it.
20570        let (mut f, full) = filled(true);
20571        let keys = f.run(&[b"DBSIZE"]);
20572        assert!(
20573            f.run(&[b"INFO", b"memory"])
20574                .contains("yo_memory_regime:migrate"),
20575            "a database with somewhere to put values migrates"
20576        );
20577
20578        let limit = full - 2 * 1024 * 1024;
20579        f.run(&[b"CONFIG", b"SET", b"maxmemory-policy", b"allkeys-lru"]);
20580        f.run(&[
20581            b"CONFIG",
20582            b"SET",
20583            b"maxmemory",
20584            limit.to_string().as_bytes(),
20585        ]);
20586        press(&mut f, limit);
20587
20588        assert!(
20589            f.run(&[b"INFO", b"stats"]).contains("evicted_keys:0"),
20590            "nothing was thrown away"
20591        );
20592        let after: usize = f.run(&[b"DBSIZE"])[1..]
20593            .trim_end()
20594            .parse()
20595            .expect("a count");
20596        let before: usize = keys[1..].trim_end().parse().expect("a count");
20597        assert!(after > before, "the keys that came in are all still here");
20598        assert!(
20599            f.server.store_bytes() > 0,
20600            "and what came out of memory went to the file"
20601        );
20602        // And the values read back, which is the part that makes it a migration
20603        // rather than a loss.
20604        let val = format!("$256\r\n{}\r\n", "v".repeat(256));
20605        assert_eq!(f.run(&[b"GET", b"key:00000000"]), val);
20606        assert_eq!(f.run(&[b"GET", b"key:00023999"]), val);
20607    }
20608
20609    #[cfg_attr(miri, ignore = "several megabytes of arena, see `filled`")]
20610    #[test]
20611    fn a_storage_limit_of_zero_restores_redis_behaviour_exactly() {
20612        // The documented setting for a drop in cache. A file that may hold
20613        // nothing cannot be migrated to, so eviction is all that is left, and
20614        // the server behaves exactly as it did before any of this existed.
20615        let (mut f, full) = filled(true);
20616        f.run(&[b"CONFIG", b"SET", b"maxstore", b"0"]);
20617        assert!(
20618            f.run(&[b"INFO", b"memory"])
20619                .contains("yo_memory_regime:evict"),
20620            "nothing may go to the file"
20621        );
20622
20623        let limit = full - 2 * 1024 * 1024;
20624        f.run(&[b"CONFIG", b"SET", b"maxmemory-policy", b"allkeys-lru"]);
20625        f.run(&[
20626            b"CONFIG",
20627            b"SET",
20628            b"maxmemory",
20629            limit.to_string().as_bytes(),
20630        ]);
20631        press(&mut f, limit);
20632
20633        assert!(
20634            !f.run(&[b"INFO", b"stats"]).contains("evicted_keys:0"),
20635            "keys were thrown away, which is what was asked for"
20636        );
20637        assert_eq!(f.server.store_bytes(), 0, "and the file was never written");
20638    }
20639
20640    #[cfg_attr(miri, ignore = "several megabytes of arena, see `filled`")]
20641    #[test]
20642    fn a_full_file_goes_back_to_evicting() {
20643        // A storage limit reached is a storage limit, and eviction is the right
20644        // answer to one. The budget here is a few kilobytes, so the first round
20645        // of migration fills it and everything after that is evicted.
20646        let (mut f, full) = filled(true);
20647        f.run(&[b"CONFIG", b"SET", b"maxstore", b"64kb"]);
20648        let limit = full - 2 * 1024 * 1024;
20649        f.run(&[b"CONFIG", b"SET", b"maxmemory-policy", b"allkeys-lru"]);
20650        f.run(&[
20651            b"CONFIG",
20652            b"SET",
20653            b"maxmemory",
20654            limit.to_string().as_bytes(),
20655        ]);
20656        press(&mut f, limit);
20657
20658        assert!(f.server.store_bytes() >= 64 * 1024, "the file filled up");
20659        assert!(
20660            !f.run(&[b"INFO", b"stats"]).contains("evicted_keys:0"),
20661            "and then it started evicting"
20662        );
20663        assert!(
20664            f.run(&[b"INFO", b"memory"])
20665                .contains("yo_memory_regime:evict"),
20666            "and it says so"
20667        );
20668    }
20669    // ------------------------------------------------------------- stripes
20670
20671    /// Every string command, run twice: once on a database that is one keyspace
20672    /// and once on a database that is eight, with the same commands in the same
20673    /// order and the replies compared byte for byte.
20674    ///
20675    /// This is the whole claim the striping rests on. A key belongs to one
20676    /// stripe and to no other, so the answer to a command cannot depend on how
20677    /// many stripes there are, and the way to check that is to ask the same
20678    /// question of two servers that differ in nothing else.
20679    ///
20680    /// The keys are chosen to land on different stripes rather than to look
20681    /// tidy. `MSET a 1 b 2 c 3` over eight stripes is only a test of anything if
20682    /// those three keys are not all on the same one, and at eight stripes three
20683    /// keys land together about one time in fifty.
20684    #[test]
20685    fn the_string_group_answers_the_same_however_many_stripes_there_are() {
20686        let script: &[&[&[u8]]] = &[
20687            // The single key commands, which are the ones that get handed one
20688            // stripe at the dispatch site.
20689            &[b"SET", b"k1", b"v1"],
20690            &[b"SET", b"k2", b"v2"],
20691            &[b"GET", b"k1"],
20692            &[b"GET", b"nothing"],
20693            &[b"GETSET", b"k1", b"v1b"],
20694            &[b"SETNX", b"k1", b"no"],
20695            &[b"SETNX", b"k3", b"yes"],
20696            &[b"APPEND", b"k3", b"!"],
20697            &[b"STRLEN", b"k3"],
20698            &[b"SETRANGE", b"k3", b"1", b"XY"],
20699            &[b"GETRANGE", b"k3", b"0", b"-1"],
20700            &[b"INCR", b"n1"],
20701            &[b"INCRBY", b"n1", b"41"],
20702            &[b"DECRBY", b"n1", b"2"],
20703            &[b"INCRBYFLOAT", b"f1", b"1.5"],
20704            &[b"SETEX", b"e1", b"100", b"v"],
20705            &[b"PSETEX", b"e2", b"100000", b"v"],
20706            &[b"GETEX", b"e1", b"PERSIST"],
20707            &[b"GETDEL", b"k2"],
20708            &[b"GET", b"k2"],
20709            &[b"DIGEST", b"k1"],
20710            &[b"DELEX", b"k3"],
20711            // The five that name more than one key, which are the ones that
20712            // cannot be handed one stripe at all.
20713            &[b"MSET", b"a", b"1", b"b", b"2", b"c", b"3"],
20714            &[b"MGET", b"a", b"b", b"c", b"missing"],
20715            &[b"MSETNX", b"d", b"4", b"e", b"5"],
20716            &[b"MSETNX", b"e", b"6", b"f", b"7"],
20717            &[b"MGET", b"d", b"e", b"f"],
20718            &[b"MSETEX", b"2", b"g", b"7", b"h", b"8", b"NX"],
20719            &[b"MSETEX", b"2", b"g", b"9", b"h", b"9", b"NX"],
20720            &[b"MSETEX", b"2", b"g", b"9", b"h", b"9", b"XX"],
20721            &[b"MGET", b"g", b"h"],
20722            &[b"SET", b"s1", b"ohmytext"],
20723            &[b"SET", b"s2", b"mynewtext"],
20724            &[b"LCS", b"s1", b"s2"],
20725            &[b"LCS", b"s1", b"s2", b"LEN"],
20726            &[b"LCS", b"s1", b"s2", b"IDX", b"MINMATCHLEN", b"4"],
20727            &[b"LCS", b"s1", b"s2", b"IDX", b"WITHMATCHLEN"],
20728            &[b"LCS", b"s1", b"gone"],
20729            // And the errors, which have to be the same errors.
20730            &[b"MSET", b"odd"],
20731            &[b"LCS", b"s1", b"s2", b"LEN", b"IDX"],
20732            &[b"MGET"],
20733        ];
20734
20735        let mut one = Fixture::new();
20736        let mut many = Fixture::striped(8);
20737        for parts in script {
20738            let a = one.run(parts);
20739            let b = many.run(parts);
20740            assert_eq!(a, b, "{}", String::from_utf8_lossy(parts[0]));
20741        }
20742    }
20743
20744    /// The keys of an `MSET` really do end up on different stripes.
20745    ///
20746    /// Without this the test above could pass on a server whose stripe number
20747    /// happened to be a constant, which is a striped database in name only.
20748    #[test]
20749    fn a_striped_database_spreads_the_keys_it_is_given() {
20750        let mut f = Fixture::striped(8);
20751        for i in 0..256 {
20752            let key = format!("key:{i}");
20753            f.run(&[b"SET", key.as_bytes(), b"v"]);
20754        }
20755        assert_eq!(f.run(&[b"DBSIZE"]), ":256\r\n");
20756    }
20757
20758    /// A wrong type stops an `MGET` no more than it does on one stripe: the key
20759    /// that is not a string comes back nil and the rest of the reply is intact.
20760    #[test]
20761    fn a_wrong_type_in_the_middle_of_an_mget_is_still_one_nil() {
20762        let mut one = Fixture::new();
20763        let mut many = Fixture::striped(8);
20764        for f in [&mut one, &mut many] {
20765            f.run(&[b"SET", b"str", b"v"]);
20766            // Planted rather than pushed. `RPUSH` belongs to the list group,
20767            // which has not been taught about stripes yet and would refuse the
20768            // wide server. What is under test is what `MGET` does when it walks
20769            // onto a key that is not a string, and that does not care how the
20770            // key got there.
20771            f.server
20772                .striped(0)
20773                .hold(b"list")
20774                .push(b"list", yo_kv::End::Right, core::iter::once(&b"v"[..]))
20775                .expect("a new list");
20776        }
20777        assert_eq!(
20778            one.run(&[b"MGET", b"str", b"list", b"gone"]),
20779            many.run(&[b"MGET", b"str", b"list", b"gone"])
20780        );
20781    }
20782
20783    /// The same claim for the keyspace group, and the same way of checking it.
20784    ///
20785    /// `SORT` is not in the script because it is the one command in that file
20786    /// that has not been taught about stripes, and `SCAN`, `KEYS` and
20787    /// `RANDOMKEY` are not in it either, because those three do not promise an
20788    /// order and comparing two replies byte for byte would be asserting one.
20789    /// They get tests of their own below.
20790    #[test]
20791    fn the_keyspace_group_answers_the_same_however_many_stripes_there_are() {
20792        let script: &[&[&[u8]]] = &[
20793            &[b"SET", b"k1", b"v1"],
20794            &[b"SET", b"k2", b"v2"],
20795            &[b"EXISTS", b"k1", b"k2", b"k1", b"gone"],
20796            &[b"TYPE", b"k1"],
20797            &[b"TYPE", b"gone"],
20798            &[b"TOUCH", b"k1", b"k2", b"k1", b"gone"],
20799            &[b"EXPIRE", b"k1", b"100"],
20800            &[b"TTL", b"k1"],
20801            &[b"EXPIRE", b"k1", b"200", b"NX"],
20802            &[b"PERSIST", b"k1"],
20803            &[b"TTL", b"k1"],
20804            &[b"PEXPIREAT", b"k2", b"1900000000000"],
20805            &[b"EXPIRETIME", b"k2"],
20806            &[b"PEXPIRETIME", b"k2"],
20807            &[b"PERSIST", b"k2"],
20808            &[b"OBJECT", b"ENCODING", b"k1"],
20809            &[b"OBJECT", b"REFCOUNT", b"k1"],
20810            &[b"OBJECT", b"IDLETIME", b"k1"],
20811            &[b"OBJECT", b"FREQ", b"k1"],
20812            &[b"OBJECT", b"ENCODING", b"gone"],
20813            &[b"OBJECT", b"HELP"],
20814            &[b"RENAME", b"k1", b"k9"],
20815            &[b"GET", b"k9"],
20816            &[b"RENAME", b"gone", b"x"],
20817            &[b"RENAMENX", b"k9", b"k2"],
20818            &[b"RENAMENX", b"k9", b"k8"],
20819            &[b"GET", b"k8"],
20820            &[b"COPY", b"k8", b"c1"],
20821            &[b"COPY", b"k8", b"c1"],
20822            &[b"COPY", b"k8", b"c1", b"REPLACE"],
20823            &[b"COPY", b"k8", b"k8"],
20824            &[b"COPY", b"gone", b"c2"],
20825            &[b"COPY", b"k8", b"k8", b"DB", b"1"],
20826            &[b"COPY", b"k8", b"c9", b"DB", b"9"],
20827            &[b"MOVE", b"c1", b"1"],
20828            &[b"MOVE", b"c1", b"1"],
20829            &[b"MOVE", b"k8", b"0"],
20830            &[b"DEL", b"k2", b"gone"],
20831            &[b"UNLINK", b"k8", b"k8"],
20832            &[b"DBSIZE"],
20833        ];
20834
20835        let mut one = Fixture::new();
20836        let mut many = Fixture::striped(8);
20837        for parts in script {
20838            let a = one.run(parts);
20839            let b = many.run(parts);
20840            assert_eq!(a, b, "{}", String::from_utf8_lossy(parts[0]));
20841        }
20842
20843        // `RESTORE` needs bytes a client would have got from a `DUMP`, so the
20844        // payload is taken from the store rather than parsed back out of a
20845        // reply that is not text. Both servers dump the same key and the bytes
20846        // are the same bytes, which is the first half of what is being checked
20847        // here.
20848        for f in [&mut one, &mut many] {
20849            f.run(&[b"SET", b"d1", b"payload"]);
20850            let payload = f
20851                .server
20852                .striped(0)
20853                .hold(b"d1")
20854                .dump(b"d1")
20855                .expect("a key that is there");
20856            assert!(
20857                f.run(&[b"DUMP", b"d1"])
20858                    .starts_with(&format!("${}", payload.len())),
20859                "a payload of the length the store gave"
20860            );
20861            assert_eq!(f.run(&[b"DUMP", b"gone"]), "$-1\r\n");
20862            assert_eq!(f.run(&[b"RESTORE", b"d2", b"0", &payload]), "+OK\r\n");
20863            assert_eq!(f.run(&[b"GET", b"d2"]), "$7\r\npayload\r\n");
20864            assert_eq!(
20865                f.run(&[b"RESTORE", b"d2", b"0", &payload]),
20866                "-BUSYKEY Target key name already exists.\r\n"
20867            );
20868            assert_eq!(
20869                f.run(&[b"RESTORE", b"d3", b"0", b"rubbish"]),
20870                "-ERR DUMP payload version or checksum are wrong\r\n"
20871            );
20872        }
20873    }
20874
20875    /// A `SCAN` of a database of eight stripes comes back with all of it.
20876    ///
20877    /// The cursor is the thing under test. It has to carry the stripe as well
20878    /// as the place in it, so a client that stops at one stripe and comes back
20879    /// carries on in that stripe and not at the top of the database, and the
20880    /// walk has to end once rather than eight times.
20881    #[test]
20882    fn a_scan_of_a_striped_database_walks_all_of_it() {
20883        // Eight stripes and a COUNT of ten, so eighty keys is already more than
20884        // one page on every stripe and the cursor has to carry which stripe it
20885        // was on, which is the thing being checked.
20886        let n = if cfg!(miri) { 80 } else { 500 };
20887        let mut f = Fixture::striped(8);
20888        for i in 0..n {
20889            let key = format!("key:{i}");
20890            f.run(&[b"SET", key.as_bytes(), b"v"]);
20891        }
20892
20893        let mut seen = Vec::new();
20894        let mut cursor = "0".to_owned();
20895        let mut calls = 0;
20896        loop {
20897            let reply = f.run(&[b"SCAN", cursor.as_bytes(), b"COUNT", b"10"]);
20898            let (next, keys) = scan_reply(&reply);
20899            seen.extend(keys);
20900            cursor = next;
20901            calls += 1;
20902            assert!(calls < 5_000, "a scan that will not finish");
20903            if cursor == "0" {
20904                break;
20905            }
20906        }
20907        seen.sort();
20908        assert_eq!(seen.len(), n, "a quiet scan answered a key twice");
20909        assert_eq!(seen, sorted(&f.run(&[b"KEYS", b"*"])));
20910
20911        // And the options still work when the walk is over several stripes,
20912        // since a `MATCH` is applied to keys a stripe handed up and a `TYPE` is
20913        // applied by each stripe on the way.
20914        let reply = f.run(&[b"SCAN", b"0", b"COUNT", b"1000", b"MATCH", b"key:4?"]);
20915        let (_, keys) = scan_reply(&reply);
20916        assert_eq!(keys.len(), 10, "key:40 through key:49");
20917        let reply = f.run(&[b"SCAN", b"0", b"COUNT", b"1000", b"TYPE", b"list"]);
20918        let (_, keys) = scan_reply(&reply);
20919        assert!(keys.is_empty(), "nothing here is a list");
20920    }
20921
20922    /// `RANDOMKEY` on a striped database answers a key from any of the stripes.
20923    ///
20924    /// The draw picks the stripe first, so the thing that can go wrong is that
20925    /// it always picks the same one, and two hundred draws over eight stripes
20926    /// would make that obvious.
20927    #[test]
20928    fn a_random_key_can_come_from_any_stripe() {
20929        let mut f = Fixture::striped(8);
20930        assert_eq!(f.run(&[b"RANDOMKEY"]), "$-1\r\n");
20931        for i in 0..200 {
20932            let key = format!("key:{i}");
20933            f.run(&[b"SET", key.as_bytes(), b"v"]);
20934        }
20935        let mut homes = std::collections::HashSet::new();
20936        for _ in 0..200 {
20937            let got = f.run(&[b"RANDOMKEY"]);
20938            let key = got.split("\r\n").nth(1).expect("a key").to_owned();
20939            assert_eq!(f.run(&[b"EXISTS", key.as_bytes()]), ":1\r\n");
20940            homes.insert(f.server.striped(0).stripe_of(key.as_bytes()));
20941        }
20942        assert_eq!(homes.len(), 8, "some stripe was never drawn from");
20943    }
20944
20945    /// Two keys that are not on the same stripe, which is what `RENAME` and
20946    /// `COPY` have to cope with and what a test has to arrange rather than
20947    /// hope for.
20948    fn apart(f: &mut Fixture, src: &str) -> String {
20949        let home = f.server.striped(0).stripe_of(src.as_bytes());
20950        for i in 0..1_000 {
20951            let dst = format!("dst:{i}");
20952            if f.server.striped(0).stripe_of(dst.as_bytes()) != home {
20953                return dst;
20954            }
20955        }
20956        panic!("eight stripes and a thousand keys all landed in one place");
20957    }
20958
20959    /// A rename whose two keys are on two stripes moves the value, the deadline
20960    /// and, for a collection, the body itself.
20961    #[test]
20962    fn a_rename_across_stripes_takes_everything_with_it() {
20963        let mut f = Fixture::striped(8);
20964        let dst = apart(&mut f, "src");
20965        let (src, dst) = (b"src".as_slice(), dst.as_bytes());
20966
20967        f.run(&[b"SET", src, b"v"]);
20968        f.run(&[b"EXPIRE", src, b"100"]);
20969        assert_eq!(f.run(&[b"RENAME", src, dst]), "+OK\r\n");
20970        assert_eq!(f.run(&[b"EXISTS", src, dst]), ":1\r\n");
20971        assert_eq!(f.run(&[b"GET", dst]), "$1\r\nv\r\n");
20972        assert_eq!(f.run(&[b"TTL", dst]), ":100\r\n", "the deadline came too");
20973
20974        // A list, because a string lives in its record and a collection lives
20975        // in a slab, and the second of those is the one that can be left
20976        // behind. Planted through the store, since the list group has not been
20977        // taught about stripes yet.
20978        f.server
20979            .striped(0)
20980            .hold(src)
20981            .push(src, yo_kv::End::Right, [&b"a"[..], &b"b"[..]].into_iter())
20982            .expect("a new list");
20983        assert_eq!(f.run(&[b"RENAME", src, dst]), "+OK\r\n");
20984        assert_eq!(f.run(&[b"TYPE", dst]), "+list\r\n");
20985        assert_eq!(
20986            f.server.striped(0).hold(dst).llen(dst).expect("a list"),
20987            2,
20988            "the members are on the stripe the key moved to"
20989        );
20990
20991        // And `RENAMENX` still refuses a destination that is taken, which is
20992        // the one answer the cross stripe path has to work out for itself.
20993        f.run(&[b"SET", src, b"v"]);
20994        assert_eq!(f.run(&[b"RENAMENX", src, dst]), ":0\r\n");
20995        assert_eq!(f.run(&[b"TYPE", dst]), "+list\r\n", "and left it alone");
20996        assert_eq!(f.run(&[b"GET", src]), "$1\r\nv\r\n", "and left the source");
20997    }
20998
20999    /// And a copy across two stripes leaves both keys behind it.
21000    #[test]
21001    fn a_copy_across_stripes_leaves_the_source_where_it_was() {
21002        let mut f = Fixture::striped(8);
21003        let dst = apart(&mut f, "src");
21004        let (src, dst) = (b"src".as_slice(), dst.as_bytes());
21005
21006        f.run(&[b"SET", src, b"v"]);
21007        assert_eq!(f.run(&[b"COPY", src, dst]), ":1\r\n");
21008        assert_eq!(f.run(&[b"EXISTS", src, dst]), ":2\r\n");
21009        assert_eq!(
21010            f.run(&[b"COPY", src, dst]),
21011            ":0\r\n",
21012            "the destination is taken"
21013        );
21014        f.run(&[b"SET", src, b"w"]);
21015        assert_eq!(f.run(&[b"COPY", src, dst, b"REPLACE"]), ":1\r\n");
21016        assert_eq!(f.run(&[b"GET", dst]), "$1\r\nw\r\n");
21017
21018        // A collection is cloned rather than moved, so both keys have a body of
21019        // their own afterwards and writing to one does not show up in the
21020        // other.
21021        f.run(&[b"DEL", src, dst]);
21022        f.server
21023            .striped(0)
21024            .hold(src)
21025            .push(src, yo_kv::End::Right, [&b"a"[..], &b"b"[..]].into_iter())
21026            .expect("a new list");
21027        assert_eq!(f.run(&[b"COPY", src, dst]), ":1\r\n");
21028        f.server
21029            .striped(0)
21030            .hold(src)
21031            .push(src, yo_kv::End::Right, core::iter::once(&b"c"[..]))
21032            .expect("a list that is there");
21033        assert_eq!(f.server.striped(0).hold(src).llen(src).expect("a list"), 3);
21034        assert_eq!(f.server.striped(0).hold(dst).llen(dst).expect("a list"), 2);
21035    }
21036
21037    /// Every bitmap command, on one stripe and on eight, replies compared byte
21038    /// for byte.
21039    ///
21040    /// `BITOP` is the one that names more than one key and it is where the work
21041    /// went. The rest are single key commands that now find their own stripe,
21042    /// and they are here because the cheapest way to be sure the routing is
21043    /// right is to ask.
21044    #[test]
21045    fn the_bitmap_group_answers_the_same_however_many_stripes_there_are() {
21046        let script: &[&[&[u8]]] = &[
21047            &[b"SET", b"k1", b"foobar"],
21048            &[b"SETBIT", b"b1", b"7", b"1"],
21049            &[b"SETBIT", b"b1", b"7", b"0"],
21050            &[b"GETBIT", b"k1", b"6"],
21051            &[b"GETBIT", b"k1", b"100"],
21052            &[b"BITCOUNT", b"k1"],
21053            &[b"BITCOUNT", b"k1", b"0", b"0"],
21054            &[b"BITCOUNT", b"k1", b"5", b"30", b"BIT"],
21055            &[b"BITPOS", b"k1", b"1"],
21056            &[b"BITPOS", b"k1", b"0", b"2"],
21057            &[b"BITPOS", b"k1", b"1", b"2", b"-1", b"BIT"],
21058            &[
21059                b"BITFIELD",
21060                b"bf",
21061                b"SET",
21062                b"u8",
21063                b"0",
21064                b"255",
21065                b"GET",
21066                b"u8",
21067                b"0",
21068            ],
21069            &[
21070                b"BITFIELD",
21071                b"bf",
21072                b"OVERFLOW",
21073                b"SAT",
21074                b"INCRBY",
21075                b"u8",
21076                b"0",
21077                b"10",
21078            ],
21079            &[b"BITFIELD_RO", b"bf", b"GET", b"u8", b"0"],
21080            // The multi key one, over sources that are not on one stripe unless
21081            // eight stripes have folded into one.
21082            &[b"SET", b"s1", b"abc"],
21083            &[b"SET", b"s2", b"abd"],
21084            &[b"SET", b"s3", b"a"],
21085            &[b"BITOP", b"AND", b"d1", b"s1", b"s2"],
21086            &[b"GET", b"d1"],
21087            &[b"BITOP", b"OR", b"d2", b"s1", b"s2", b"s3"],
21088            &[b"GET", b"d2"],
21089            &[b"BITOP", b"XOR", b"d3", b"s1", b"s2"],
21090            &[b"STRLEN", b"d3"],
21091            &[b"BITOP", b"NOT", b"d4", b"s1"],
21092            &[b"STRLEN", b"d4"],
21093            &[b"BITOP", b"DIFF", b"d5", b"s1", b"s2"],
21094            &[b"BITOP", b"DIFF1", b"d6", b"s1", b"s2"],
21095            &[b"BITOP", b"ANDOR", b"d7", b"s1", b"s2"],
21096            &[b"BITOP", b"ONE", b"d8", b"s1", b"s2"],
21097            // A source that is not there reads as empty, and a result with
21098            // nothing in it deletes the destination rather than writing one.
21099            &[b"BITOP", b"AND", b"d1", b"gone", b"also-gone"],
21100            &[b"EXISTS", b"d1"],
21101            &[b"BITOP", b"OR", b"d9", b"s1", b"gone"],
21102            &[b"GET", b"d9"],
21103            // And the errors, which have to be the same errors. The key that
21104            // is not a string is planted below rather than pushed here, since
21105            // the list group has not been taught about stripes yet.
21106            &[b"BITOP", b"AND", b"d1", b"s1", b"list"],
21107            &[b"BITOP", b"AND", b"list", b"s1", b"s2"],
21108            &[b"BITOP", b"NOT", b"d1", b"s1", b"s2"],
21109            &[b"BITOP", b"DIFF", b"d1", b"s1"],
21110            &[b"BITOP", b"NOPE", b"d1", b"s1"],
21111            &[b"BITCOUNT", b"list"],
21112            &[b"BITFIELD_RO", b"bf", b"SET", b"u8", b"0", b"1"],
21113        ];
21114
21115        let mut one = Fixture::new();
21116        let mut many = Fixture::striped(8);
21117        for f in [&mut one, &mut many] {
21118            plant_list(f, b"list");
21119        }
21120        for parts in script {
21121            let a = one.run(parts);
21122            let b = many.run(parts);
21123            assert_eq!(a, b, "{}", String::from_utf8_lossy(parts[0]));
21124        }
21125    }
21126
21127    /// A list under `key`, put there through the store.
21128    ///
21129    /// What a test does when it wants a key of the wrong type on a striped
21130    /// server, because the command that would make one is in a group that has
21131    /// not been taught about stripes yet.
21132    fn plant_list(f: &mut Fixture, key: &[u8]) {
21133        f.server
21134            .striped(0)
21135            .hold(key)
21136            .push(key, yo_kv::End::Right, core::iter::once(&b"x"[..]))
21137            .expect("a new list");
21138    }
21139
21140    /// A `BITOP` whose keys are on two stripes reads both of them.
21141    ///
21142    /// The test above spreads its keys by hashing and would still pass if one
21143    /// stripe were doing all the work, since the answers would be the same. This
21144    /// one puts the destination and the two sources where they are known not to
21145    /// share a stripe.
21146    #[test]
21147    fn a_bitop_across_stripes_reads_every_source() {
21148        let mut f = Fixture::striped(8);
21149        let other = apart(&mut f, "src");
21150        let (src, far) = (b"src".as_slice(), other.as_bytes());
21151        assert_ne!(
21152            f.server.striped(0).stripe_of(src),
21153            f.server.striped(0).stripe_of(far),
21154            "the two keys are the point of the test"
21155        );
21156
21157        f.run(&[b"SET", src, b"abc"]);
21158        f.run(&[b"SET", far, b"abd"]);
21159        assert_eq!(f.run(&[b"BITOP", b"AND", far, src, far]), ":3\r\n");
21160        assert_eq!(
21161            f.run(&[b"GET", far]),
21162            "$3\r\nab`\r\n",
21163            "a destination that is also a source"
21164        );
21165        f.run(&[b"SET", far, b"abd"]);
21166        assert_eq!(f.run(&[b"BITOP", b"XOR", src, src, far]), ":3\r\n");
21167        assert_eq!(
21168            f.run(&[b"GET", src]),
21169            "$3\r\n\0\0\x07\r\n",
21170            "and the other way round"
21171        );
21172
21173        // A result of nothing deletes a destination on whatever stripe it is
21174        // on, and a source of the wrong type is refused before anything is
21175        // written.
21176        f.run(&[b"SET", src, b"abc"]);
21177        f.run(&[b"DEL", far]);
21178        assert_eq!(f.run(&[b"BITOP", b"AND", src, far, b"gone"]), ":0\r\n");
21179        assert_eq!(f.run(&[b"EXISTS", src]), ":0\r\n");
21180        f.run(&[b"SET", src, b"abc"]);
21181        f.run(&[b"DEL", far]);
21182        plant_list(&mut f, far);
21183        assert_eq!(
21184            f.run(&[b"BITOP", b"OR", b"out", src, far]),
21185            "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n"
21186        );
21187        assert_eq!(f.run(&[b"EXISTS", b"out"]), ":0\r\n");
21188    }
21189
21190    /// Every HyperLogLog command, on one stripe and on eight.
21191    ///
21192    /// Not under Miri, for the reason on
21193    /// `the_debug_forms_answer_four_different_shapes`, and twice over here
21194    /// because the script is run against both shapes of server.
21195    #[cfg_attr(miri, ignore = "sixteen thousand registers a command")]
21196    #[test]
21197    fn the_hyperloglog_group_answers_the_same_however_many_stripes_there_are() {
21198        let script: &[&[&[u8]]] = &[
21199            &[b"PFADD", b"h1", b"a", b"b", b"c"],
21200            &[b"PFADD", b"h1", b"a"],
21201            &[b"PFADD", b"h2"],
21202            &[b"PFADD", b"h2", b"c", b"d", b"e"],
21203            &[b"PFCOUNT", b"h1"],
21204            &[b"PFCOUNT", b"h2"],
21205            &[b"PFCOUNT", b"missing"],
21206            // The two that name more than one key.
21207            &[b"PFCOUNT", b"h1", b"h2"],
21208            &[b"PFCOUNT", b"h1", b"missing"],
21209            &[b"PFMERGE", b"m", b"h1", b"h2"],
21210            &[b"PFCOUNT", b"m"],
21211            &[b"STRLEN", b"m"],
21212            &[b"PFMERGE", b"m"],
21213            &[b"PFCOUNT", b"m"],
21214            &[b"PFMERGE", b"m2", b"missing"],
21215            &[b"PFCOUNT", b"m2"],
21216            // The debugging ones, which are single key and change what they
21217            // look at.
21218            &[b"PFDEBUG", b"ENCODING", b"h1"],
21219            &[b"PFDEBUG", b"DECODE", b"h1"],
21220            &[b"PFDEBUG", b"TODENSE", b"h1"],
21221            &[b"PFDEBUG", b"ENCODING", b"h1"],
21222            &[b"PFDEBUG", b"TODENSE", b"h1"],
21223            &[b"PFCOUNT", b"h1", b"h2"],
21224            &[b"PFSELFTEST"],
21225            // And the errors.
21226            &[b"SET", b"plain", b"not a sketch at all"],
21227            &[b"PFADD", b"plain", b"a"],
21228            &[b"PFCOUNT", b"plain"],
21229            &[b"PFCOUNT", b"h1", b"plain"],
21230            &[b"PFMERGE", b"plain", b"h1"],
21231            &[b"PFMERGE", b"m", b"plain"],
21232            &[b"PFDEBUG", b"ENCODING", b"gone"],
21233            &[b"PFDEBUG", b"NOPE", b"h1"],
21234        ];
21235
21236        let mut one = Fixture::new();
21237        let mut many = Fixture::striped(8);
21238        for parts in script {
21239            let a = one.run(parts);
21240            let b = many.run(parts);
21241            assert_eq!(a, b, "{}", String::from_utf8_lossy(parts[0]));
21242        }
21243    }
21244
21245    /// Every set command, on one stripe and on eight.
21246    ///
21247    /// The commands that answer members answer them in whatever order the set
21248    /// or the table they were built in holds them, so those replies are
21249    /// compared as sets. Everything else is compared byte for byte. Two servers
21250    /// agreeing on the order would be a fact about the tables and not about the
21251    /// answer, and asserting it would make this test fail for a reason nobody
21252    /// cares about.
21253    #[test]
21254    fn the_set_group_answers_the_same_however_many_stripes_there_are() {
21255        const UNORDERED: [&str; 4] = ["SMEMBERS", "SINTER", "SUNION", "SDIFF"];
21256        let script: &[&[&[u8]]] = &[
21257            &[b"SADD", b"s1", b"a", b"b", b"c"],
21258            &[b"SADD", b"s1", b"a"],
21259            &[b"SADD", b"s2", b"b", b"c", b"d"],
21260            &[b"SADD", b"ints", b"1", b"2", b"3"],
21261            &[b"SCARD", b"s1"],
21262            &[b"SISMEMBER", b"s1", b"a"],
21263            &[b"SISMEMBER", b"s1", b"z"],
21264            &[b"SMISMEMBER", b"s1", b"a", b"z", b"c"],
21265            &[b"SMEMBERS", b"s1"],
21266            &[b"SREM", b"s1", b"c"],
21267            &[b"SADD", b"s1", b"c"],
21268            &[b"SSCAN", b"s1", b"0"],
21269            &[b"SSCAN", b"s1", b"0", b"COUNT", b"100", b"MATCH", b"a*"],
21270            // The two draws, on a set of one member, which is the only shape
21271            // whose answer two servers have to agree on.
21272            &[b"SADD", b"one", b"m"],
21273            &[b"SRANDMEMBER", b"one"],
21274            &[b"SRANDMEMBER", b"one", b"-3"],
21275            &[b"SRANDMEMBER", b"gone"],
21276            &[b"SPOP", b"one"],
21277            &[b"SPOP", b"one"],
21278            &[b"SPOP", b"gone", b"2"],
21279            // The one that names two keys.
21280            &[b"SMOVE", b"s1", b"s2", b"a"],
21281            &[b"SMOVE", b"s1", b"s2", b"zzz"],
21282            &[b"SMOVE", b"gone", b"s2", b"a"],
21283            &[b"SMEMBERS", b"s1"],
21284            &[b"SMEMBERS", b"s2"],
21285            // The algebra.
21286            &[b"SINTER", b"s1", b"s2"],
21287            &[b"SUNION", b"s1", b"s2"],
21288            &[b"SDIFF", b"s2", b"s1"],
21289            &[b"SINTER", b"s1", b"gone"],
21290            &[b"SUNION", b"s1", b"gone"],
21291            &[b"SDIFF", b"gone", b"s1"],
21292            &[b"SINTER", b"ints", b"s1"],
21293            &[b"SINTERCARD", b"2", b"s1", b"s2"],
21294            &[b"SINTERCARD", b"2", b"s1", b"s2", b"LIMIT", b"1"],
21295            &[b"SUNIONCARD", b"2", b"s1", b"s2"],
21296            &[b"SDIFFCARD", b"2", b"s2", b"s1"],
21297            &[b"SINTERSTORE", b"d1", b"s1", b"s2"],
21298            &[b"SMEMBERS", b"d1"],
21299            &[b"SUNIONSTORE", b"d2", b"s1", b"s2"],
21300            &[b"SCARD", b"d2"],
21301            &[b"SDIFFSTORE", b"d3", b"s2", b"s1"],
21302            &[b"SCARD", b"d3"],
21303            // An empty result deletes the destination rather than storing a
21304            // set with nothing in it.
21305            &[b"SINTERSTORE", b"d4", b"s1", b"gone"],
21306            &[b"EXISTS", b"d4"],
21307            // And a destination that is also a source.
21308            &[b"SUNIONSTORE", b"s2", b"s1", b"s2"],
21309            &[b"SCARD", b"s2"],
21310            // The errors, which have to be the same errors.
21311            &[b"SET", b"str", b"v"],
21312            &[b"SADD", b"str", b"a"],
21313            &[b"SINTER", b"s1", b"str"],
21314            &[b"SINTERSTORE", b"d5", b"s1", b"str"],
21315            &[b"EXISTS", b"d5"],
21316            &[b"SMOVE", b"str", b"s2", b"a"],
21317            &[b"SMOVE", b"s1", b"str", b"b"],
21318            &[b"SMOVE", b"gone", b"str", b"b"],
21319            &[b"SINTERCARD", b"0", b"s1"],
21320            &[b"SINTERCARD", b"3", b"s1", b"s2"],
21321            &[b"SINTERCARD", b"2", b"s1", b"s2", b"LIMIT", b"-1"],
21322            &[b"SPOP", b"s1", b"-1"],
21323        ];
21324
21325        let mut one = Fixture::new();
21326        let mut many = Fixture::striped(8);
21327        for parts in script {
21328            let a = one.run(parts);
21329            let b = many.run(parts);
21330            let name = String::from_utf8_lossy(parts[0]).to_uppercase();
21331            if UNORDERED.contains(&name.as_str()) && a.starts_with(['*', '~']) {
21332                assert_eq!(sorted(&a), sorted(&b), "{name}");
21333            } else {
21334                assert_eq!(a, b, "{name}");
21335            }
21336        }
21337    }
21338
21339    /// The algebra over sets that are known to be on different stripes.
21340    #[test]
21341    fn a_set_operation_across_stripes_reads_every_set() {
21342        let mut f = Fixture::striped(8);
21343        let second = apart(&mut f, "s1");
21344        let third = apart(&mut f, &second);
21345        let (s1, s2, s3) = (b"s1".as_slice(), second.as_bytes(), third.as_bytes());
21346
21347        f.run(&[b"SADD", s1, b"a", b"b", b"c"]);
21348        f.run(&[b"SADD", s2, b"b", b"c", b"d"]);
21349        assert_eq!(sorted(&f.run(&[b"SINTER", s1, s2])), ["b", "c"]);
21350        assert_eq!(
21351            sorted(&f.run(&[b"SUNION", s1, s2])),
21352            ["a", "b", "c", "d"],
21353            "a union of two stripes is both of them"
21354        );
21355        assert_eq!(sorted(&f.run(&[b"SDIFF", s1, s2])), ["a"]);
21356        assert_eq!(f.run(&[b"SINTERCARD", b"2", s1, s2]), ":2\r\n");
21357        assert_eq!(f.run(&[b"SUNIONCARD", b"2", s1, s2]), ":4\r\n");
21358        assert_eq!(f.run(&[b"SDIFFCARD", b"2", s1, s2]), ":1\r\n");
21359
21360        // A destination on a third stripe, and then one that is also a source.
21361        assert_eq!(f.run(&[b"SINTERSTORE", s3, s1, s2]), ":2\r\n");
21362        assert_eq!(sorted(&f.run(&[b"SMEMBERS", s3])), ["b", "c"]);
21363        assert_eq!(f.run(&[b"SUNIONSTORE", s2, s1, s2]), ":4\r\n");
21364        assert_eq!(sorted(&f.run(&[b"SMEMBERS", s2])), ["a", "b", "c", "d"]);
21365        assert_eq!(f.run(&[b"SDIFFSTORE", s3, s2, s1]), ":1\r\n");
21366        assert_eq!(sorted(&f.run(&[b"SMEMBERS", s3])), ["d"]);
21367
21368        // An empty result deletes a destination wherever it is, and a key of
21369        // the wrong type stops the command before the destination is touched.
21370        assert_eq!(f.run(&[b"SINTERSTORE", s3, s1, b"gone"]), ":0\r\n");
21371        assert_eq!(f.run(&[b"EXISTS", s3]), ":0\r\n");
21372        f.run(&[b"SET", s3, b"v"]);
21373        assert_eq!(
21374            f.run(&[b"SINTER", s1, s3]),
21375            "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n"
21376        );
21377        assert_eq!(f.run(&[b"GET", s3]), "$1\r\nv\r\n", "and left it alone");
21378    }
21379
21380    /// An `SMOVE` whose two keys are on two stripes.
21381    #[test]
21382    fn a_move_across_stripes_takes_the_member_with_it() {
21383        let mut f = Fixture::striped(8);
21384        let other = apart(&mut f, "src");
21385        let (src, dst) = (b"src".as_slice(), other.as_bytes());
21386
21387        f.run(&[b"SADD", src, b"a", b"b"]);
21388        f.run(&[b"SADD", dst, b"c"]);
21389        assert_eq!(f.run(&[b"SMOVE", src, dst, b"a"]), ":1\r\n");
21390        assert_eq!(sorted(&f.run(&[b"SMEMBERS", src])), ["b"]);
21391        assert_eq!(sorted(&f.run(&[b"SMEMBERS", dst])), ["a", "c"]);
21392        assert_eq!(f.run(&[b"SMOVE", src, dst, b"a"]), ":0\r\n", "it has gone");
21393
21394        // A destination that is not there is created on its own stripe, and a
21395        // source that loses its last member is deleted from its own.
21396        f.run(&[b"DEL", dst]);
21397        assert_eq!(f.run(&[b"SMOVE", src, dst, b"b"]), ":1\r\n");
21398        assert_eq!(f.run(&[b"EXISTS", src]), ":0\r\n", "the source is empty");
21399        assert_eq!(sorted(&f.run(&[b"SMEMBERS", dst])), ["b"]);
21400
21401        // And a source that is not there answers zero without ever asking what
21402        // the destination holds, which is Redis's order and not the obvious
21403        // one.
21404        f.run(&[b"SET", dst, b"v"]);
21405        assert_eq!(f.run(&[b"SMOVE", src, dst, b"b"]), ":0\r\n");
21406        f.run(&[b"SADD", src, b"b"]);
21407        assert_eq!(
21408            f.run(&[b"SMOVE", src, dst, b"b"]),
21409            "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n"
21410        );
21411    }
21412
21413    /// A count and a merge over sketches that are known to be on two stripes.
21414    #[test]
21415    fn a_pfcount_and_a_pfmerge_reach_across_stripes() {
21416        let mut f = Fixture::striped(8);
21417        let other = apart(&mut f, "src");
21418        let (src, far) = (b"src".as_slice(), other.as_bytes());
21419
21420        for i in 0..150 {
21421            let ele = format!("e:{i}");
21422            f.run(&[b"PFADD", src, ele.as_bytes()]);
21423        }
21424        for i in 150..200 {
21425            let ele = format!("e:{i}");
21426            f.run(&[b"PFADD", far, ele.as_bytes()]);
21427        }
21428        // The three numbers a real server gives for these elements, which are
21429        // the numbers the single stripe tests in the keyspace crate check too.
21430        assert_eq!(f.run(&[b"PFCOUNT", src]), ":151\r\n");
21431        assert_eq!(f.run(&[b"PFCOUNT", far]), ":49\r\n");
21432        assert_eq!(f.run(&[b"PFCOUNT", src, far]), ":199\r\n");
21433
21434        // A merge whose destination is on a third stripe, and then one that
21435        // writes into a source.
21436        let dest = apart(&mut f, &other);
21437        assert_eq!(f.run(&[b"PFMERGE", dest.as_bytes(), src, far]), "+OK\r\n");
21438        assert_eq!(f.run(&[b"PFCOUNT", dest.as_bytes()]), ":199\r\n");
21439        assert_eq!(f.run(&[b"PFMERGE", far, src]), "+OK\r\n");
21440        assert_eq!(f.run(&[b"PFCOUNT", far]), ":199\r\n", "and kept its own");
21441        assert_eq!(f.run(&[b"PFCOUNT", src]), ":151\r\n", "and left the source");
21442    }
21443
21444    /// Every sorted set command, on one stripe and on eight.
21445    ///
21446    /// Every reply here is compared byte for byte, unlike the set group, because
21447    /// a sorted set answers in rank order and members sharing a score come out
21448    /// in the order of their bytes. There is nothing left for the table the
21449    /// answer was built in to decide.
21450    #[test]
21451    fn the_sorted_set_group_answers_the_same_however_many_stripes_there_are() {
21452        let script: &[&[&[u8]]] = &[
21453            &[b"ZADD", b"z1", b"1", b"a", b"2", b"b", b"3", b"c"],
21454            &[b"ZADD", b"z1", b"NX", b"9", b"a"],
21455            &[b"ZADD", b"z1", b"XX", b"CH", b"5", b"a"],
21456            &[b"ZADD", b"z1", b"GT", b"CH", b"1", b"a"],
21457            &[b"ZADD", b"z1", b"INCR", b"2", b"a"],
21458            &[b"ZINCRBY", b"z1", b"1.5", b"b"],
21459            &[b"ZADD", b"z2", b"1", b"b", b"2", b"c", b"3", b"d"],
21460            &[b"ZADD", b"lex", b"0", b"a", b"0", b"b", b"0", b"c"],
21461            &[b"ZADD", b"one", b"1", b"m"],
21462            &[b"ZCARD", b"z1"],
21463            &[b"ZCARD", b"gone"],
21464            &[b"ZSCORE", b"z1", b"a"],
21465            &[b"ZSCORE", b"z1", b"zz"],
21466            &[b"ZMSCORE", b"z1", b"a", b"zz", b"c"],
21467            &[b"ZRANK", b"z1", b"c"],
21468            &[b"ZRANK", b"z1", b"c", b"WITHSCORE"],
21469            &[b"ZREVRANK", b"z1", b"c"],
21470            &[b"ZRANK", b"z1", b"gone"],
21471            &[b"ZCOUNT", b"z1", b"-inf", b"+inf"],
21472            &[b"ZCOUNT", b"z1", b"(1", b"3"],
21473            &[b"ZLEXCOUNT", b"lex", b"-", b"+"],
21474            // The range commands, which are one parse and one walk.
21475            &[b"ZRANGE", b"z1", b"0", b"-1"],
21476            &[b"ZRANGE", b"z1", b"0", b"-1", b"WITHSCORES"],
21477            &[b"ZRANGE", b"z1", b"1", b"9", b"BYSCORE"],
21478            &[b"ZRANGE", b"z1", b"9", b"1", b"BYSCORE", b"REV"],
21479            &[b"ZRANGE", b"lex", b"[a", b"(c", b"BYLEX"],
21480            &[b"ZREVRANGE", b"z1", b"0", b"-1"],
21481            &[
21482                b"ZRANGEBYSCORE",
21483                b"z1",
21484                b"-inf",
21485                b"+inf",
21486                b"LIMIT",
21487                b"1",
21488                b"1",
21489            ],
21490            &[b"ZREVRANGEBYLEX", b"lex", b"+", b"-"],
21491            &[b"ZSCAN", b"z1", b"0"],
21492            &[b"ZSCAN", b"z1", b"0", b"MATCH", b"a*", b"COUNT", b"100"],
21493            // The draw, on a sorted set of one member, which is the only shape
21494            // whose answer two servers have to agree on.
21495            &[b"ZRANDMEMBER", b"one"],
21496            &[b"ZRANDMEMBER", b"one", b"-3", b"WITHSCORES"],
21497            &[b"ZRANDMEMBER", b"gone"],
21498            // The one that copies a window into another key.
21499            &[b"ZRANGESTORE", b"d0", b"z1", b"0", b"1"],
21500            &[b"ZRANGE", b"d0", b"0", b"-1", b"WITHSCORES"],
21501            &[b"ZRANGESTORE", b"d0", b"z1", b"5", b"1"],
21502            &[b"EXISTS", b"d0"],
21503            // The algebra, in both its shapes.
21504            &[b"ZUNION", b"2", b"z1", b"z2"],
21505            &[b"ZUNION", b"2", b"z1", b"z2", b"WITHSCORES"],
21506            &[
21507                b"ZUNION",
21508                b"2",
21509                b"z1",
21510                b"z2",
21511                b"WEIGHTS",
21512                b"2",
21513                b"3",
21514                b"AGGREGATE",
21515                b"MAX",
21516                b"WITHSCORES",
21517            ],
21518            &[b"ZINTER", b"2", b"z1", b"z2", b"WITHSCORES"],
21519            &[b"ZDIFF", b"2", b"z1", b"z2", b"WITHSCORES"],
21520            &[b"ZDIFF", b"2", b"gone", b"z1"],
21521            &[b"ZINTERCARD", b"2", b"z1", b"z2"],
21522            &[b"ZINTERCARD", b"2", b"z1", b"z2", b"LIMIT", b"1"],
21523            &[b"ZUNIONSTORE", b"d1", b"2", b"z1", b"z2"],
21524            &[b"ZRANGE", b"d1", b"0", b"-1", b"WITHSCORES"],
21525            &[
21526                b"ZINTERSTORE",
21527                b"d2",
21528                b"2",
21529                b"z1",
21530                b"z2",
21531                b"AGGREGATE",
21532                b"MIN",
21533            ],
21534            &[b"ZRANGE", b"d2", b"0", b"-1", b"WITHSCORES"],
21535            &[b"ZDIFFSTORE", b"d3", b"2", b"z1", b"z2"],
21536            &[b"ZCARD", b"d3"],
21537            // An empty result deletes the destination rather than storing a
21538            // sorted set with nothing in it.
21539            &[b"ZINTERSTORE", b"d4", b"2", b"z1", b"gone"],
21540            &[b"EXISTS", b"d4"],
21541            // A plain set is a sorted set where every score is one, so it is a
21542            // legal input to all of these.
21543            &[b"SADD", b"plain", b"a", b"x"],
21544            &[b"ZUNIONSTORE", b"d5", b"2", b"z1", b"plain"],
21545            &[b"ZRANGE", b"d5", b"0", b"-1", b"WITHSCORES"],
21546            // And a destination that is also a source.
21547            &[b"ZUNIONSTORE", b"z2", b"2", b"z1", b"z2"],
21548            &[b"ZRANGE", b"z2", b"0", b"-1", b"WITHSCORES"],
21549            // The three removals and the two pops.
21550            &[b"ZREM", b"d5", b"x", b"nothere"],
21551            &[b"ZREMRANGEBYRANK", b"d5", b"0", b"0"],
21552            &[b"ZREMRANGEBYSCORE", b"d1", b"-inf", b"1"],
21553            &[b"ZREMRANGEBYLEX", b"lex", b"[a", b"[a"],
21554            &[b"ZPOPMIN", b"z1"],
21555            &[b"ZPOPMAX", b"z1", b"2"],
21556            &[b"ZPOPMIN", b"gone"],
21557            &[b"ZMPOP", b"2", b"gone", b"z2", b"MIN"],
21558            &[b"ZMPOP", b"2", b"gone", b"nothere", b"MAX", b"COUNT", b"2"],
21559            // The errors, which have to be the same errors.
21560            &[b"SET", b"str", b"v"],
21561            &[b"ZADD", b"str", b"1", b"a"],
21562            &[b"ZSCORE", b"str", b"a"],
21563            &[b"ZADD", b"z1", b"nan", b"a"],
21564            &[b"ZUNION", b"2", b"z1", b"str"],
21565            &[b"ZUNIONSTORE", b"d6", b"2", b"z1", b"str"],
21566            &[b"EXISTS", b"d6"],
21567            &[b"ZINTERCARD", b"0", b"z1"],
21568            &[b"ZINTERCARD", b"2", b"z1", b"z2", b"LIMIT", b"-1"],
21569            &[b"ZRANGESTORE", b"d7", b"str", b"0", b"-1"],
21570            &[b"ZMPOP", b"1", b"str", b"MIN"],
21571            &[b"ZPOPMIN", b"z1", b"-1"],
21572        ];
21573
21574        let mut one = Fixture::new();
21575        let mut many = Fixture::striped(8);
21576        for parts in script {
21577            let a = one.run(parts);
21578            let b = many.run(parts);
21579            assert_eq!(a, b, "{}", String::from_utf8_lossy(parts[0]));
21580        }
21581    }
21582
21583    /// The algebra over sorted sets that are known to be on different stripes.
21584    #[test]
21585    fn a_sorted_set_operation_across_stripes_reads_every_input() {
21586        let mut f = Fixture::striped(8);
21587        let second = apart(&mut f, "z1");
21588        let third = apart(&mut f, &second);
21589        let (z1, z2, z3) = (b"z1".as_slice(), second.as_bytes(), third.as_bytes());
21590
21591        f.run(&[b"ZADD", z1, b"1", b"a", b"2", b"b"]);
21592        f.run(&[b"ZADD", z2, b"3", b"b", b"4", b"c"]);
21593        // a is 1, c is 4, b is 2 and 3 added together, which is the order they
21594        // come out in and the answer that says both stripes were read.
21595        assert_eq!(
21596            f.run(&[b"ZUNION", b"2", z1, z2]),
21597            "*3\r\n$1\r\na\r\n$1\r\nc\r\n$1\r\nb\r\n"
21598        );
21599        assert_eq!(f.run(&[b"ZINTER", b"2", z1, z2]), "*1\r\n$1\r\nb\r\n");
21600        assert_eq!(f.run(&[b"ZDIFF", b"2", z1, z2]), "*1\r\n$1\r\na\r\n");
21601        assert_eq!(f.run(&[b"ZINTERCARD", b"2", z1, z2]), ":1\r\n");
21602        assert_eq!(
21603            f.run(&[b"ZINTERCARD", b"2", z1, z2, b"LIMIT", b"1"]),
21604            ":1\r\n"
21605        );
21606
21607        // A destination on a third stripe, and the weights and the aggregate
21608        // reaching every input.
21609        assert_eq!(f.run(&[b"ZUNIONSTORE", z3, b"2", z1, z2]), ":3\r\n");
21610        assert_eq!(f.run(&[b"ZSCORE", z3, b"b"]), "$1\r\n5\r\n");
21611        assert_eq!(
21612            f.run(&[
21613                b"ZUNIONSTORE",
21614                z3,
21615                b"2",
21616                z1,
21617                z2,
21618                b"WEIGHTS",
21619                b"2",
21620                b"3",
21621                b"AGGREGATE",
21622                b"MAX"
21623            ]),
21624            ":3\r\n"
21625        );
21626        assert_eq!(f.run(&[b"ZSCORE", z3, b"b"]), "$1\r\n9\r\n");
21627        assert_eq!(f.run(&[b"ZINTERSTORE", z3, b"2", z1, z2]), ":1\r\n");
21628        assert_eq!(f.run(&[b"ZCARD", z3]), ":1\r\n");
21629        assert_eq!(f.run(&[b"ZDIFFSTORE", z3, b"2", z2, z1]), ":1\r\n");
21630        assert_eq!(f.run(&[b"ZSCORE", z3, b"c"]), "$1\r\n4\r\n");
21631
21632        // A pop over keys on several stripes takes from the first one that has
21633        // anything, which is what makes the order of the keys matter.
21634        let popped = format!(
21635            "*2\r\n${}\r\n{second}\r\n*1\r\n*2\r\n$1\r\nb\r\n$1\r\n3\r\n",
21636            second.len()
21637        );
21638        assert_eq!(f.run(&[b"ZMPOP", b"3", b"gone", z2, z1, b"MIN"]), popped);
21639        f.run(&[b"ZADD", z2, b"3", b"b"]);
21640
21641        // An empty result deletes a destination wherever it is, and an input of
21642        // the wrong type stops the command before the destination is touched.
21643        assert_eq!(f.run(&[b"ZINTERSTORE", z3, b"2", z1, b"gone"]), ":0\r\n");
21644        assert_eq!(f.run(&[b"EXISTS", z3]), ":0\r\n");
21645        f.run(&[b"SET", z3, b"v"]);
21646        assert_eq!(
21647            f.run(&[b"ZUNION", b"2", z1, z3]),
21648            "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n"
21649        );
21650        assert_eq!(f.run(&[b"GET", z3]), "$1\r\nv\r\n", "and left it alone");
21651
21652        // And a destination that is also a source works across stripes for the
21653        // reason it works on one: the whole result is built before anything is
21654        // written.
21655        assert_eq!(f.run(&[b"ZUNIONSTORE", z2, b"2", z1, z2]), ":3\r\n");
21656        assert_eq!(f.run(&[b"ZSCORE", z2, b"b"]), "$1\r\n5\r\n");
21657        assert_eq!(f.run(&[b"ZCARD", z2]), ":3\r\n");
21658    }
21659
21660    /// A `ZRANGESTORE` whose two keys are on two stripes.
21661    #[test]
21662    fn a_range_store_across_stripes_copies_the_window() {
21663        let mut f = Fixture::striped(8);
21664        let other = apart(&mut f, "src");
21665        let third = apart(&mut f, &other);
21666        let (src, dst, plain) = (b"src".as_slice(), other.as_bytes(), third.as_bytes());
21667
21668        f.run(&[b"ZADD", src, b"1", b"a", b"2", b"b", b"3", b"c"]);
21669        assert_eq!(f.run(&[b"ZRANGESTORE", dst, src, b"0", b"1"]), ":2\r\n");
21670        assert_eq!(
21671            f.run(&[b"ZRANGE", dst, b"0", b"-1", b"WITHSCORES"]),
21672            "*4\r\n$1\r\na\r\n$1\r\n1\r\n$1\r\nb\r\n$1\r\n2\r\n"
21673        );
21674        assert_eq!(f.run(&[b"ZCARD", src]), ":3\r\n", "the source kept its own");
21675
21676        // A window walked backwards takes the other end of the sorted set and
21677        // still stores what it took in score order.
21678        assert_eq!(
21679            f.run(&[
21680                b"ZRANGESTORE",
21681                dst,
21682                src,
21683                b"+inf",
21684                b"-inf",
21685                b"BYSCORE",
21686                b"REV",
21687                b"LIMIT",
21688                b"0",
21689                b"2"
21690            ]),
21691            ":2\r\n"
21692        );
21693        assert_eq!(
21694            f.run(&[b"ZRANGE", dst, b"0", b"-1"]),
21695            "*2\r\n$1\r\nb\r\n$1\r\nc\r\n"
21696        );
21697
21698        // An empty window deletes the destination on its own stripe, and a
21699        // source of the wrong type is refused before the destination is touched.
21700        assert_eq!(f.run(&[b"ZRANGESTORE", dst, src, b"5", b"1"]), ":0\r\n");
21701        assert_eq!(f.run(&[b"EXISTS", dst]), ":0\r\n");
21702        f.run(&[b"ZRANGESTORE", dst, src, b"0", b"-1"]);
21703        f.run(&[b"SET", plain, b"v"]);
21704        assert_eq!(
21705            f.run(&[b"ZRANGESTORE", dst, plain, b"0", b"-1"]),
21706            "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n"
21707        );
21708        assert_eq!(
21709            f.run(&[b"ZCARD", dst]),
21710            ":3\r\n",
21711            "and left the destination"
21712        );
21713    }
21714
21715    /// Every list command, on one stripe and on eight.
21716    ///
21717    /// The blocking six are in here too, both when they can be answered on the
21718    /// spot and when they cannot, since a command that parks its client writes
21719    /// nothing at all and two servers have to agree about that as much as they
21720    /// agree about a reply.
21721    #[test]
21722    fn the_list_group_answers_the_same_however_many_stripes_there_are() {
21723        let script: &[&[&[u8]]] = &[
21724            &[b"RPUSH", b"l1", b"a", b"b", b"c"],
21725            &[b"LPUSH", b"l1", b"z"],
21726            &[b"RPUSHX", b"l1", b"d"],
21727            &[b"LPUSHX", b"gone", b"x"],
21728            &[b"RPUSHX", b"gone", b"x"],
21729            &[b"LLEN", b"l1"],
21730            &[b"LLEN", b"gone"],
21731            &[b"LRANGE", b"l1", b"0", b"-1"],
21732            &[b"LRANGE", b"l1", b"1", b"2"],
21733            &[b"LRANGE", b"l1", b"5", b"9"],
21734            &[b"LINDEX", b"l1", b"0"],
21735            &[b"LINDEX", b"l1", b"-1"],
21736            &[b"LINDEX", b"l1", b"99"],
21737            &[b"LSET", b"l1", b"0", b"y"],
21738            &[b"LINSERT", b"l1", b"BEFORE", b"b", b"aa"],
21739            &[b"LINSERT", b"l1", b"AFTER", b"nothere", b"x"],
21740            &[b"LPOS", b"l1", b"b"],
21741            &[b"LPOS", b"l1", b"b", b"COUNT", b"0"],
21742            &[b"LPOS", b"l1", b"nothere"],
21743            &[b"LPOS", b"l1", b"b", b"RANK", b"-1", b"MAXLEN", b"2"],
21744            &[b"LREM", b"l1", b"1", b"aa"],
21745            &[b"LTRIM", b"l1", b"0", b"3"],
21746            &[b"LRANGE", b"l1", b"0", b"-1"],
21747            &[b"LPOP", b"l1"],
21748            &[b"RPOP", b"l1"],
21749            &[b"LPOP", b"l1", b"2"],
21750            &[b"LPOP", b"gone"],
21751            &[b"LPOP", b"gone", b"2"],
21752            &[b"EXISTS", b"l1"],
21753            // The ones that name two keys, and the one that takes a block of
21754            // elements rather than the one on the end.
21755            &[b"RPUSH", b"src", b"a", b"b", b"c", b"d"],
21756            &[b"LMOVE", b"src", b"dst", b"LEFT", b"RIGHT"],
21757            &[b"RPOPLPUSH", b"src", b"dst"],
21758            &[b"LRANGE", b"dst", b"0", b"-1"],
21759            &[b"LMOVE", b"gone", b"dst", b"LEFT", b"RIGHT"],
21760            &[b"LMOVEM", b"src", b"dst", b"LEFT", b"RIGHT"],
21761            &[
21762                b"LMOVEM", b"src", b"dst", b"LEFT", b"RIGHT", b"COUNT", b"2", b"BULK",
21763            ],
21764            &[
21765                b"LMOVEM", b"dst", b"dst", b"LEFT", b"RIGHT", b"COUNT", b"2", b"OBO",
21766            ],
21767            &[b"LRANGE", b"dst", b"0", b"-1"],
21768            &[
21769                b"LMOVEM", b"src", b"dst", b"LEFT", b"RIGHT", b"EXACTLY", b"9", b"BULK",
21770            ],
21771            &[b"LMPOP", b"2", b"gone", b"dst", b"LEFT"],
21772            &[b"LMPOP", b"2", b"gone", b"dst", b"RIGHT", b"COUNT", b"2"],
21773            &[b"LMPOP", b"1", b"gone", b"LEFT"],
21774            // The blocking ones, first with something there to answer them and
21775            // then with nothing, which parks the client and writes nothing.
21776            &[b"RPUSH", b"q", b"a", b"b", b"c"],
21777            &[b"BLPOP", b"gone", b"q", b"0"],
21778            &[b"BRPOP", b"q", b"0"],
21779            &[b"BLMPOP", b"0", b"2", b"gone", b"q", b"LEFT"],
21780            &[b"RPUSH", b"q", b"x", b"y", b"z"],
21781            &[b"BLMOVE", b"q", b"dst", b"LEFT", b"RIGHT", b"0"],
21782            &[b"BRPOPLPUSH", b"q", b"dst", b"0"],
21783            &[b"BLMOVEM", b"q", b"dst", b"LEFT", b"RIGHT", b"0"],
21784            &[b"BLPOP", b"q", b"0"],
21785            &[b"BLMOVE", b"q", b"dst", b"LEFT", b"RIGHT", b"0"],
21786            // The errors, which have to be the same errors.
21787            &[b"SET", b"plain", b"v"],
21788            &[b"LPUSH", b"plain", b"a"],
21789            &[b"LLEN", b"plain"],
21790            &[b"LMOVE", b"dst", b"plain", b"LEFT", b"RIGHT"],
21791            &[b"LRANGE", b"dst", b"0", b"-1"],
21792            &[b"LMOVEM", b"dst", b"plain", b"LEFT", b"RIGHT"],
21793            &[b"LSET", b"gone", b"0", b"v"],
21794            &[b"LSET", b"dst", b"99", b"v"],
21795            &[b"LPOP", b"dst", b"-1"],
21796            &[b"LMPOP", b"0", b"dst", b"LEFT"],
21797            &[b"LPOS", b"dst", b"a", b"RANK", b"0"],
21798        ];
21799
21800        let mut one = Fixture::new();
21801        let mut many = Fixture::striped(8);
21802        for parts in script {
21803            let a = one.run(parts);
21804            let b = many.run(parts);
21805            assert_eq!(a, b, "{}", String::from_utf8_lossy(parts[0]));
21806        }
21807    }
21808
21809    /// An `LMOVE` and an `LMOVEM` whose two keys are on two stripes.
21810    #[test]
21811    fn a_list_move_across_stripes_takes_the_elements_with_it() {
21812        let mut f = Fixture::striped(8);
21813        let other = apart(&mut f, "src");
21814        let third = apart(&mut f, &other);
21815        let (src, dst, plain) = (b"src".as_slice(), other.as_bytes(), third.as_bytes());
21816
21817        f.run(&[b"RPUSH", src, b"a", b"b", b"c", b"d"]);
21818        assert_eq!(
21819            f.run(&[b"LMOVE", src, dst, b"LEFT", b"RIGHT"]),
21820            "$1\r\na\r\n"
21821        );
21822        assert_eq!(f.run(&[b"RPOPLPUSH", src, dst]), "$1\r\nd\r\n");
21823        assert_eq!(
21824            f.run(&[b"LRANGE", dst, b"0", b"-1"]),
21825            "*2\r\n$1\r\nd\r\n$1\r\na\r\n",
21826            "one went on each end of the destination"
21827        );
21828        assert_eq!(
21829            f.run(&[b"LRANGE", src, b"0", b"-1"]),
21830            "*2\r\n$1\r\nb\r\n$1\r\nc\r\n"
21831        );
21832
21833        // A block of them, which under BULK arrives in the order it left.
21834        assert_eq!(
21835            f.run(&[
21836                b"LMOVEM", src, dst, b"LEFT", b"RIGHT", b"COUNT", b"2", b"BULK"
21837            ]),
21838            "*2\r\n$1\r\nb\r\n$1\r\nc\r\n"
21839        );
21840        assert_eq!(
21841            f.run(&[b"LRANGE", dst, b"0", b"-1"]),
21842            "*4\r\n$1\r\nd\r\n$1\r\na\r\n$1\r\nb\r\n$1\r\nc\r\n"
21843        );
21844        assert_eq!(
21845            f.run(&[b"EXISTS", src]),
21846            ":0\r\n",
21847            "and the source is gone with its last element"
21848        );
21849
21850        // An `EXACTLY` the source cannot fill moves nothing, and a source that
21851        // is not there at all is the two kinds of nothing the two commands have.
21852        f.run(&[b"RPUSH", src, b"e", b"f"]);
21853        assert_eq!(
21854            f.run(&[
21855                b"LMOVEM", src, dst, b"LEFT", b"RIGHT", b"EXACTLY", b"3", b"BULK"
21856            ]),
21857            "*-1\r\n"
21858        );
21859        assert_eq!(f.run(&[b"LLEN", src]), ":2\r\n", "and took none of them");
21860        assert_eq!(
21861            f.run(&[b"LMOVE", b"gone", dst, b"LEFT", b"RIGHT"]),
21862            "$-1\r\n"
21863        );
21864        assert_eq!(
21865            f.run(&[b"LMOVEM", b"gone", dst, b"LEFT", b"RIGHT"]),
21866            "*-1\r\n"
21867        );
21868
21869        // A destination of the wrong type is refused before anything is taken,
21870        // which is the order that matters most here, since an element already
21871        // out of the source would have nowhere to go back to.
21872        f.run(&[b"SET", plain, b"v"]);
21873        assert_eq!(
21874            f.run(&[b"LMOVE", src, plain, b"LEFT", b"RIGHT"]),
21875            "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n"
21876        );
21877        assert_eq!(
21878            f.run(&[b"LLEN", src]),
21879            ":2\r\n",
21880            "and left the source alone"
21881        );
21882        assert_eq!(
21883            f.run(&[b"LMOVEM", src, plain, b"LEFT", b"RIGHT"]),
21884            "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n"
21885        );
21886        assert_eq!(f.run(&[b"LLEN", src]), ":2\r\n");
21887    }
21888
21889    /// A parked client served by a push that landed on another stripe.
21890    ///
21891    /// A waiter remembers the database and not the stripe, which is the point:
21892    /// serving it runs the same attempt the command ran, and the attempt finds
21893    /// the stripe each of its keys is on for itself.
21894    #[test]
21895    fn a_parked_client_is_served_from_the_stripe_its_key_is_on() {
21896        let mut f = Fixture::striped(8);
21897        let other = apart(&mut f, "q");
21898        let (q, far) = (b"q".as_slice(), other.as_bytes());
21899
21900        assert_eq!(f.flow(&[b"BLPOP", q, far, b"0"]).0, Flow::Block);
21901        assert_eq!(f.server.parked(), 1);
21902        f.run(&[b"RPUSH", far, b"v"]);
21903        let mut out = Out::new(Proto::Resp2);
21904        assert!(f.server.serve_waiter(7, 0, &mut out));
21905        let want = format!("*2\r\n${}\r\n{other}\r\n$1\r\nv\r\n", other.len());
21906        assert_eq!(core::str::from_utf8(out.as_slice()).expect("ascii"), want);
21907        assert_eq!(
21908            f.run(&[b"EXISTS", far]),
21909            ":0\r\n",
21910            "and it took the element with it"
21911        );
21912
21913        // And a move across two stripes is served the same way, by the push
21914        // that fills its source.
21915        f.server.forget_waiters(7);
21916        assert_eq!(
21917            f.flow(&[b"BLMOVE", q, far, b"LEFT", b"RIGHT", b"0"]).0,
21918            Flow::Block
21919        );
21920        f.run(&[b"RPUSH", q, b"w"]);
21921        let mut out = Out::new(Proto::Resp2);
21922        assert!(f.server.serve_waiter(7, 0, &mut out));
21923        assert_eq!(
21924            core::str::from_utf8(out.as_slice()).expect("ascii"),
21925            "$1\r\nw\r\n"
21926        );
21927        assert_eq!(f.run(&[b"LRANGE", far, b"0", b"-1"]), "*1\r\n$1\r\nw\r\n");
21928    }
21929
21930    /// Every stream command, on one stripe and on eight.
21931    ///
21932    /// Every ID is written out rather than left to the clock, so the two servers
21933    /// are being compared on what they store and not on how long the test took
21934    /// to get from one of them to the other.
21935    #[test]
21936    fn the_stream_group_answers_the_same_however_many_stripes_there_are() {
21937        let script: &[&[&[u8]]] = &[
21938            &[b"XADD", b"s", b"1-1", b"a", b"1"],
21939            &[b"XADD", b"s", b"2-1", b"b", b"2", b"c", b"3"],
21940            &[b"XADD", b"s", b"3-1", b"d", b"4"],
21941            &[b"XADD", b"s", b"1-1", b"e", b"5"],
21942            &[b"XADD", b"nomk", b"NOMKSTREAM", b"1-1", b"a", b"1"],
21943            &[b"XLEN", b"s"],
21944            &[b"XLEN", b"gone"],
21945            &[b"XRANGE", b"s", b"-", b"+"],
21946            &[b"XRANGE", b"s", b"2", b"+", b"COUNT", b"1"],
21947            &[b"XRANGE", b"gone", b"-", b"+", b"COUNT", b"0"],
21948            &[b"XRANGE", b"s", b"-", b"+", b"COUNT", b"0"],
21949            &[b"XREVRANGE", b"s", b"+", b"-"],
21950            &[b"XREAD", b"COUNT", b"2", b"STREAMS", b"s", b"0"],
21951            &[b"XREAD", b"STREAMS", b"s", b"gone", b"0", b"0"],
21952            &[b"XREAD", b"STREAMS", b"s", b"$"],
21953            // The groups, which is where most of the state is.
21954            &[b"XGROUP", b"CREATE", b"s", b"g", b"0"],
21955            &[b"XGROUP", b"CREATE", b"s", b"g", b"0"],
21956            &[b"XGROUP", b"CREATE", b"gone", b"g", b"0"],
21957            &[b"XGROUP", b"CREATE", b"made", b"g", b"$", b"MKSTREAM"],
21958            &[b"XGROUP", b"CREATECONSUMER", b"s", b"g", b"idle"],
21959            &[b"XREADGROUP", b"GROUP", b"g", b"c1", b"STREAMS", b"s", b">"],
21960            &[
21961                b"XREADGROUP",
21962                b"GROUP",
21963                b"g",
21964                b"c1",
21965                b"COUNT",
21966                b"1",
21967                b"STREAMS",
21968                b"s",
21969                b"0",
21970            ],
21971            &[
21972                b"XREADGROUP",
21973                b"GROUP",
21974                b"nope",
21975                b"c1",
21976                b"STREAMS",
21977                b"s",
21978                b">",
21979            ],
21980            &[b"XPENDING", b"s", b"g"],
21981            &[b"XPENDING", b"s", b"g", b"-", b"+", b"10"],
21982            &[b"XPENDING", b"s", b"g", b"-", b"+", b"10", b"c1"],
21983            &[b"XPENDING", b"s", b"nope"],
21984            &[b"XCLAIM", b"s", b"g", b"c2", b"0", b"1-1"],
21985            &[b"XCLAIM", b"s", b"g", b"c2", b"0", b"2-1", b"JUSTID"],
21986            &[b"XAUTOCLAIM", b"s", b"g", b"c3", b"0", b"0"],
21987            &[b"XACK", b"s", b"g", b"1-1"],
21988            &[b"XACK", b"s", b"g", b"1-1"],
21989            &[b"XNACK", b"s", b"g", b"FAIL", b"IDS", b"1", b"2-1"],
21990            &[b"XPENDING", b"s", b"g"],
21991            &[b"XINFO", b"STREAM", b"s"],
21992            &[b"XINFO", b"GROUPS", b"s"],
21993            &[b"XINFO", b"CONSUMERS", b"s", b"g"],
21994            &[b"XINFO", b"STREAM", b"gone"],
21995            // Deleting, trimming and moving the ID on.
21996            &[b"XDEL", b"s", b"3-1"],
21997            &[b"XDELEX", b"s", b"DELREF", b"IDS", b"1", b"2-1"],
21998            &[b"XACKDEL", b"s", b"g", b"KEEPREF", b"IDS", b"1", b"1-1"],
21999            &[b"XADD", b"s", b"9-1", b"z", b"9"],
22000            &[b"XTRIM", b"s", b"MAXLEN", b"1"],
22001            &[b"XTRIM", b"s", b"MINID", b"9"],
22002            &[b"XSETID", b"s", b"99-1"],
22003            &[b"XSETID", b"s", b"1-1"],
22004            &[b"XLEN", b"s"],
22005            &[b"XGROUP", b"SETID", b"s", b"g", b"0"],
22006            &[b"XGROUP", b"DELCONSUMER", b"s", b"g", b"c1"],
22007            &[b"XGROUP", b"DESTROY", b"s", b"g"],
22008            &[b"XGROUP", b"DESTROY", b"s", b"g"],
22009            // And the errors.
22010            &[b"SET", b"plain", b"v"],
22011            &[b"XADD", b"plain", b"1-1", b"a", b"1"],
22012            &[b"XLEN", b"plain"],
22013            &[b"XREAD", b"STREAMS", b"plain", b"0"],
22014            &[b"XRANGE", b"s", b"bogus", b"+"],
22015            &[b"XADD", b"s", b"1-1", b"a"],
22016            &[b"XREAD", b"STREAMS", b"s", b"gone", b"0"],
22017            &[b"XREADGROUP", b"GROUP", b"g", b"c", b"STREAMS", b"s", b"$"],
22018        ];
22019
22020        let mut one = Fixture::new();
22021        let mut many = Fixture::striped(8);
22022        for parts in script {
22023            let a = one.run(parts);
22024            let b = many.run(parts);
22025            assert_eq!(a, b, "{}", String::from_utf8_lossy(parts[0]));
22026        }
22027    }
22028
22029    /// An `XREAD` and an `XREADGROUP` naming two keys on two stripes.
22030    ///
22031    /// Nothing is shared between the two streams, so the only thing this can go
22032    /// wrong at is looking both of them up, which is exactly what a read that
22033    /// held one database and walked it would get wrong.
22034    #[test]
22035    fn a_stream_read_across_stripes_reads_every_key() {
22036        let mut f = Fixture::striped(8);
22037        let other = apart(&mut f, "s1");
22038        let (s1, s2) = (b"s1".as_slice(), other.as_bytes());
22039
22040        f.run(&[b"XADD", s1, b"1-1", b"a", b"1"]);
22041        f.run(&[b"XADD", s2, b"2-1", b"b", b"2"]);
22042        let got = f.run(&[b"XREAD", b"STREAMS", s1, s2, b"0", b"0"]);
22043        assert!(got.starts_with("*2\r\n"), "both streams answered: {got}");
22044        assert!(got.contains("1-1"), "the first one is in there: {got}");
22045        assert!(got.contains("2-1"), "and so is the second: {got}");
22046
22047        // A group read looks its group up on every key before it reads any of
22048        // them, so a group that is missing on the far key stops the near one.
22049        f.run(&[b"XGROUP", b"CREATE", s1, b"g", b"0"]);
22050        let got = f.run(&[
22051            b"XREADGROUP",
22052            b"GROUP",
22053            b"g",
22054            b"c",
22055            b"STREAMS",
22056            s1,
22057            s2,
22058            b">",
22059            b">",
22060        ]);
22061        assert!(got.starts_with("-NOGROUP"), "{got}");
22062        assert_eq!(
22063            f.run(&[b"XPENDING", s1, b"g"]),
22064            "*4\r\n:0\r\n$-1\r\n$-1\r\n*-1\r\n",
22065            "and read nothing from the key that did have the group"
22066        );
22067
22068        f.run(&[b"XGROUP", b"CREATE", s2, b"g", b"0"]);
22069        let got = f.run(&[
22070            b"XREADGROUP",
22071            b"GROUP",
22072            b"g",
22073            b"c",
22074            b"STREAMS",
22075            s1,
22076            s2,
22077            b">",
22078            b">",
22079        ]);
22080        assert!(got.starts_with("*2\r\n"), "now both are read: {got}");
22081    }
22082
22083    /// A client parked on an `XREAD` woken by an entry on another stripe.
22084    #[test]
22085    fn a_parked_stream_reader_is_served_from_the_stripe_its_key_is_on() {
22086        let mut f = Fixture::striped(8);
22087        let other = apart(&mut f, "s1");
22088        let (s1, far) = (b"s1".as_slice(), other.as_bytes());
22089        f.run(&[b"XADD", s1, b"1-1", b"a", b"1"]);
22090        f.run(&[b"XADD", far, b"1-1", b"a", b"1"]);
22091
22092        assert_eq!(
22093            f.flow(&[b"XREAD", b"BLOCK", b"0", b"STREAMS", s1, far, b"$", b"$"])
22094                .0,
22095            Flow::Block
22096        );
22097        f.run(&[b"XADD", far, b"2-1", b"b", b"2"]);
22098        let mut out = Out::new(Proto::Resp2);
22099        assert!(f.server.serve_waiter(7, 0, &mut out));
22100        let want = format!(
22101            "*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",
22102            other.len()
22103        );
22104        assert_eq!(core::str::from_utf8(out.as_slice()).expect("ascii"), want);
22105    }
22106
22107    /// Every JSON command, on one stripe and on eight.
22108    #[test]
22109    fn the_json_group_answers_the_same_however_many_stripes_there_are() {
22110        let script: &[&[&[u8]]] = &[
22111            &[
22112                b"JSON.SET",
22113                b"d",
22114                b"$",
22115                br#"{"a":1,"b":[1,2,3],"s":"hi","t":true}"#,
22116            ],
22117            &[b"JSON.SET", b"d", b"$.a", b"2"],
22118            &[b"JSON.SET", b"d", b"$.new", b"9", b"NX"],
22119            &[b"JSON.SET", b"d", b"$.new", b"8", b"NX"],
22120            &[b"JSON.SET", b"d", b"$.nope", b"7", b"XX"],
22121            &[b"JSON.GET", b"d"],
22122            &[b"JSON.GET", b"d", b"$.b"],
22123            &[b"JSON.GET", b"gone", b"$"],
22124            &[b"JSON.TYPE", b"d", b"$.b"],
22125            &[b"JSON.TYPE", b"d", b"$.s"],
22126            &[b"JSON.TOGGLE", b"d", b"$.t"],
22127            &[b"JSON.ARRLEN", b"d", b"$.b"],
22128            &[b"JSON.OBJLEN", b"d", b"$"],
22129            &[b"JSON.OBJKEYS", b"d", b"$"],
22130            &[b"JSON.STRLEN", b"d", b"$.s"],
22131            &[b"JSON.STRAPPEND", b"d", b"$.s", br#""there""#],
22132            &[b"JSON.ARRAPPEND", b"d", b"$.b", b"4"],
22133            &[b"JSON.ARRINSERT", b"d", b"$.b", b"0", b"0"],
22134            &[b"JSON.ARRINDEX", b"d", b"$.b", b"3"],
22135            &[b"JSON.ARRTRIM", b"d", b"$.b", b"1", b"3"],
22136            &[b"JSON.ARRPOP", b"d", b"$.b"],
22137            &[b"JSON.NUMINCRBY", b"d", b"$.a", b"5"],
22138            &[b"JSON.NUMMULTBY", b"d", b"$.a", b"2"],
22139            &[b"JSON.NUMPOWBY", b"d", b"$.a", b"2"],
22140            &[b"JSON.MERGE", b"d", b"$", br#"{"a":null,"m":1}"#],
22141            &[b"JSON.RESP", b"d", b"$.b"],
22142            &[b"JSON.DEBUG", b"MEMORY", b"d"],
22143            &[b"JSON.CLEAR", b"d", b"$.b"],
22144            &[b"JSON.DEL", b"d", b"$.m"],
22145            &[b"JSON.FORGET", b"d", b"$.nothere"],
22146            // The two that name more than one key.
22147            &[
22148                b"JSON.MSET",
22149                b"m1",
22150                b"$",
22151                b"1",
22152                b"m2",
22153                b"$",
22154                b"2",
22155                b"m3",
22156                b"$",
22157                b"3",
22158            ],
22159            &[b"JSON.MGET", b"m1", b"m2", b"m3", b"gone", b"$"],
22160            &[b"JSON.MSET", b"m1", b"$", b"9", b"m2", b"$.deep", b"9"],
22161            &[b"JSON.GET", b"m1", b"$"],
22162            &[b"JSON.MSET", b"m1", b"$", b"nonsense", b"m2", b"$", b"5"],
22163            &[b"JSON.GET", b"m2", b"$"],
22164            // And the errors.
22165            &[b"SET", b"plain", b"v"],
22166            &[b"JSON.GET", b"plain", b"$"],
22167            &[b"JSON.SET", b"plain", b"$", b"1"],
22168            &[b"JSON.MGET", b"m1", b"plain", b"$"],
22169            &[b"JSON.SET", b"d", b"$.b", b"["],
22170            &[b"JSON.DEL", b"plain"],
22171        ];
22172
22173        let mut one = Fixture::new();
22174        let mut many = Fixture::striped(8);
22175        for parts in script {
22176            let a = one.run(parts);
22177            let b = many.run(parts);
22178            assert_eq!(a, b, "{}", String::from_utf8_lossy(parts[0]));
22179        }
22180    }
22181
22182    /// A `JSON.MSET` and a `JSON.MGET` whose keys are on several stripes.
22183    ///
22184    /// `JSON.MSET` works every triple out against the keyspace as it was before
22185    /// the command and writes nothing until all of them are known to work, so
22186    /// the thing to check is that a triple that cannot be written stops the
22187    /// ones on other stripes as well as the ones on its own.
22188    #[test]
22189    fn a_json_multi_write_across_stripes_reaches_every_key() {
22190        let mut f = Fixture::striped(8);
22191        let second = apart(&mut f, "m1");
22192        let third = apart(&mut f, &second);
22193        let (m1, m2, m3) = (b"m1".as_slice(), second.as_bytes(), third.as_bytes());
22194
22195        assert_eq!(
22196            f.run(&[b"JSON.MSET", m1, b"$", b"1", m2, b"$", b"2", m3, b"$", b"3"]),
22197            "+OK\r\n"
22198        );
22199        assert_eq!(
22200            f.run(&[b"JSON.MGET", m1, m2, m3, b"gone", b"$"]),
22201            "*4\r\n$3\r\n[1]\r\n$3\r\n[2]\r\n$3\r\n[3]\r\n$-1\r\n"
22202        );
22203
22204        // A value that is not JSON is refused before anything is written, and
22205        // the key on the far stripe keeps what it had.
22206        assert_eq!(
22207            f.run(&[b"JSON.MSET", m1, b"$", b"9", m2, b"$", b"nonsense"]),
22208            "-this is not the start of a value, at byte 0 of the JSON text\r\n"
22209        );
22210        assert_eq!(f.run(&[b"JSON.GET", m1, b"$"]), "$3\r\n[1]\r\n");
22211
22212        // A path that names nowhere is not an error. That triple is skipped,
22213        // the ones on the other stripes are still written, and the reply is a
22214        // nil rather than OK.
22215        assert_eq!(
22216            f.run(&[
22217                b"JSON.MSET",
22218                m1,
22219                b"$",
22220                b"9",
22221                m2,
22222                b"$.deep",
22223                b"9",
22224                m3,
22225                b"$",
22226                b"7"
22227            ]),
22228            "$-1\r\n"
22229        );
22230        assert_eq!(f.run(&[b"JSON.GET", m1, b"$"]), "$3\r\n[9]\r\n");
22231        assert_eq!(f.run(&[b"JSON.GET", m2, b"$"]), "$3\r\n[2]\r\n");
22232        assert_eq!(f.run(&[b"JSON.GET", m3, b"$"]), "$3\r\n[7]\r\n");
22233    }
22234
22235    /// Every geospatial command, on one stripe and on eight.
22236    #[test]
22237    fn the_geo_group_answers_the_same_however_many_stripes_there_are() {
22238        let script: &[&[&[u8]]] = &[
22239            &[
22240                b"GEOADD",
22241                b"g",
22242                b"13.361389",
22243                b"38.115556",
22244                b"palermo",
22245                b"15.087269",
22246                b"37.502669",
22247                b"catania",
22248            ],
22249            &[
22250                b"GEOADD",
22251                b"g",
22252                b"NX",
22253                b"13.361389",
22254                b"38.115556",
22255                b"palermo",
22256            ],
22257            &[b"GEOADD", b"g", b"XX", b"CH", b"13.4", b"38.1", b"palermo"],
22258            &[b"GEOPOS", b"g", b"palermo", b"nothere"],
22259            &[b"GEOHASH", b"g", b"palermo", b"catania"],
22260            &[b"GEODIST", b"g", b"palermo", b"catania"],
22261            &[b"GEODIST", b"g", b"palermo", b"catania", b"KM"],
22262            &[b"GEODIST", b"g", b"palermo", b"nothere"],
22263            &[
22264                b"GEOSEARCH",
22265                b"g",
22266                b"FROMLONLAT",
22267                b"15",
22268                b"37",
22269                b"BYRADIUS",
22270                b"200",
22271                b"KM",
22272                b"ASC",
22273                b"WITHCOORD",
22274                b"WITHDIST",
22275                b"WITHHASH",
22276            ],
22277            &[
22278                b"GEOSEARCH",
22279                b"g",
22280                b"FROMMEMBER",
22281                b"palermo",
22282                b"BYBOX",
22283                b"400",
22284                b"400",
22285                b"KM",
22286                b"DESC",
22287            ],
22288            &[
22289                b"GEORADIUS",
22290                b"g",
22291                b"15",
22292                b"37",
22293                b"200",
22294                b"KM",
22295                b"COUNT",
22296                b"1",
22297            ],
22298            &[b"GEORADIUSBYMEMBER", b"g", b"palermo", b"200", b"KM"],
22299            &[b"GEORADIUSBYMEMBER_RO", b"g", b"nothere", b"200", b"KM"],
22300            &[
22301                b"GEOSEARCHSTORE",
22302                b"dst",
22303                b"g",
22304                b"FROMLONLAT",
22305                b"15",
22306                b"37",
22307                b"BYRADIUS",
22308                b"200",
22309                b"KM",
22310            ],
22311            &[b"ZRANGE", b"dst", b"0", b"-1"],
22312            &[
22313                b"GEOSEARCHSTORE",
22314                b"dst",
22315                b"g",
22316                b"FROMLONLAT",
22317                b"15",
22318                b"37",
22319                b"BYRADIUS",
22320                b"1",
22321                b"M",
22322                b"STOREDIST",
22323            ],
22324            &[b"EXISTS", b"dst"],
22325            &[
22326                b"GEORADIUS",
22327                b"g",
22328                b"15",
22329                b"37",
22330                b"200",
22331                b"KM",
22332                b"STORE",
22333                b"dst",
22334            ],
22335            &[b"ZCARD", b"dst"],
22336            // And the errors.
22337            &[b"GEOADD", b"g", b"181", b"38", b"nowhere"],
22338            &[b"SET", b"plain", b"v"],
22339            &[b"GEOPOS", b"plain", b"a"],
22340            &[b"GEOSEARCH", b"g", b"FROMLONLAT", b"15", b"37"],
22341            &[
22342                b"GEOSEARCHSTORE",
22343                b"dst",
22344                b"g",
22345                b"FROMLONLAT",
22346                b"15",
22347                b"37",
22348                b"BYRADIUS",
22349                b"200",
22350                b"KM",
22351                b"WITHCOORD",
22352            ],
22353        ];
22354
22355        let mut one = Fixture::new();
22356        let mut many = Fixture::striped(8);
22357        for parts in script {
22358            let a = one.run(parts);
22359            let b = many.run(parts);
22360            assert_eq!(a, b, "{}", String::from_utf8_lossy(parts[0]));
22361        }
22362    }
22363
22364    /// A `GEOSEARCHSTORE` whose two keys are on two stripes.
22365    #[test]
22366    fn a_geo_search_store_across_stripes_writes_what_it_found() {
22367        let mut f = Fixture::striped(8);
22368        let other = apart(&mut f, "g");
22369        let third = apart(&mut f, &other);
22370        let (g, dst, plain) = (b"g".as_slice(), other.as_bytes(), third.as_bytes());
22371
22372        f.run(&[
22373            b"GEOADD",
22374            g,
22375            b"13.361389",
22376            b"38.115556",
22377            b"palermo",
22378            b"15.087269",
22379            b"37.502669",
22380            b"catania",
22381        ]);
22382        assert_eq!(
22383            f.run(&[
22384                b"GEOSEARCHSTORE",
22385                dst,
22386                g,
22387                b"FROMLONLAT",
22388                b"15",
22389                b"37",
22390                b"BYRADIUS",
22391                b"200",
22392                b"KM",
22393                b"ASC",
22394            ]),
22395            ":2\r\n"
22396        );
22397        assert_eq!(
22398            f.run(&[b"ZRANGE", dst, b"0", b"-1"]),
22399            "*2\r\n$7\r\npalermo\r\n$7\r\ncatania\r\n",
22400            "the geohash is the score, so the order is not the search order"
22401        );
22402        assert_eq!(f.run(&[b"ZCARD", g]), ":2\r\n", "the source is untouched");
22403
22404        // `STOREDIST` stores the distance in the unit the search was asked in,
22405        // which is the destination stripe's sorted set and not the source's.
22406        assert_eq!(
22407            f.run(&[
22408                b"GEOSEARCHSTORE",
22409                dst,
22410                g,
22411                b"FROMMEMBER",
22412                b"palermo",
22413                b"BYRADIUS",
22414                b"200",
22415                b"KM",
22416                b"STOREDIST",
22417            ]),
22418            ":2\r\n"
22419        );
22420        assert_eq!(
22421            f.run(&[b"ZSCORE", dst, b"palermo"]),
22422            "$1\r\n0\r\n",
22423            "the centre is nought away from itself"
22424        );
22425
22426        // A search that found nothing deletes the destination on its own
22427        // stripe, and a source of the wrong type is refused with the
22428        // destination left alone.
22429        assert_eq!(
22430            f.run(&[
22431                b"GEOSEARCHSTORE",
22432                dst,
22433                g,
22434                b"FROMLONLAT",
22435                b"0",
22436                b"0",
22437                b"BYRADIUS",
22438                b"1",
22439                b"M",
22440            ]),
22441            ":0\r\n"
22442        );
22443        assert_eq!(f.run(&[b"EXISTS", dst]), ":0\r\n");
22444        f.run(&[
22445            b"GEOSEARCHSTORE",
22446            dst,
22447            g,
22448            b"FROMLONLAT",
22449            b"15",
22450            b"37",
22451            b"BYRADIUS",
22452            b"200",
22453            b"KM",
22454        ]);
22455        f.run(&[b"SET", plain, b"v"]);
22456        assert_eq!(
22457            f.run(&[
22458                b"GEOSEARCHSTORE",
22459                dst,
22460                plain,
22461                b"FROMLONLAT",
22462                b"15",
22463                b"37",
22464                b"BYRADIUS",
22465                b"200",
22466                b"KM",
22467            ]),
22468            "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n"
22469        );
22470        assert_eq!(
22471            f.run(&[b"ZCARD", dst]),
22472            ":2\r\n",
22473            "and left the destination"
22474        );
22475    }
22476
22477    /// Every time series command, on one stripe and on eight.
22478    ///
22479    /// Every timestamp is written out rather than left to the clock, so the two
22480    /// servers are compared on the samples they hold and not on how long the
22481    /// test took to get from one of them to the other.
22482    #[test]
22483    fn the_time_series_group_answers_the_same_however_many_stripes_there_are() {
22484        let script: &[&[&[u8]]] = &[
22485            &[
22486                b"TS.CREATE",
22487                b"ts:a",
22488                b"LABELS",
22489                b"sensor",
22490                b"a",
22491                b"room",
22492                b"1",
22493            ],
22494            &[b"TS.CREATE", b"ts:a"],
22495            &[b"TS.ALTER", b"ts:a", b"RETENTION", b"0"],
22496            &[b"TS.ADD", b"ts:a", b"1000", b"1.5"],
22497            &[
22498                b"TS.ADD", b"ts:b", b"1000", b"2", b"LABELS", b"sensor", b"b", b"room", b"1",
22499            ],
22500            &[
22501                b"TS.MADD", b"ts:a", b"2000", b"2.5", b"ts:b", b"2000", b"3", b"gone", b"1", b"1",
22502            ],
22503            &[b"TS.INCRBY", b"ts:a", b"1", b"TIMESTAMP", b"3000"],
22504            &[b"TS.DECRBY", b"ts:a", b"0.5", b"TIMESTAMP", b"4000"],
22505            &[b"TS.GET", b"ts:a"],
22506            &[b"TS.GET", b"gone"],
22507            &[b"TS.RANGE", b"ts:a", b"-", b"+"],
22508            &[b"TS.RANGE", b"ts:a", b"1000", b"3000", b"COUNT", b"2"],
22509            &[
22510                b"TS.RANGE",
22511                b"ts:a",
22512                b"-",
22513                b"+",
22514                b"AGGREGATION",
22515                b"avg",
22516                b"2000",
22517            ],
22518            &[b"TS.REVRANGE", b"ts:a", b"-", b"+"],
22519            &[b"TS.NRANGE", b"2", b"ts:a", b"ts:b", b"-", b"+"],
22520            &[b"TS.NREVRANGE", b"2", b"ts:a", b"ts:b", b"-", b"+"],
22521            &[b"TS.NRANGE", b"2", b"ts:a", b"gone", b"-", b"+"],
22522            &[b"TS.READ", b"ts:a", b"0"],
22523            &[b"TS.READ", b"ts:a", b"+"],
22524            // The filters, which are the ones that have to walk every stripe.
22525            &[b"TS.QUERYINDEX", b"sensor=a"],
22526            &[b"TS.QUERYINDEX", b"room=1"],
22527            &[b"TS.QUERYINDEX", b"room=9"],
22528            &[b"TS.QUERYLABELS", b"LABELS", b"FILTER", b"room=1"],
22529            &[
22530                b"TS.QUERYLABELS",
22531                b"VALUES",
22532                b"sensor",
22533                b"FILTER",
22534                b"room=1",
22535            ],
22536            &[b"TS.MGET", b"WITHLABELS", b"FILTER", b"room=1"],
22537            &[
22538                b"TS.MGET",
22539                b"SELECTED_LABELS",
22540                b"sensor",
22541                b"FILTER",
22542                b"sensor=a",
22543            ],
22544            &[b"TS.MRANGE", b"-", b"+", b"FILTER", b"room=1"],
22545            &[
22546                b"TS.MREVRANGE",
22547                b"-",
22548                b"+",
22549                b"WITHLABELS",
22550                b"FILTER",
22551                b"sensor=a",
22552            ],
22553            &[
22554                b"TS.MRANGE",
22555                b"-",
22556                b"+",
22557                b"FILTER",
22558                b"room=1",
22559                b"GROUPBY",
22560                b"room",
22561                b"REDUCE",
22562                b"max",
22563            ],
22564            &[b"TS.INFO", b"ts:a"],
22565            // And a rule, which is the one thing here that names two keys.
22566            &[
22567                b"TS.CREATERULE",
22568                b"ts:a",
22569                b"ts:down",
22570                b"AGGREGATION",
22571                b"avg",
22572                b"1000",
22573            ],
22574            &[b"TS.CREATE", b"ts:down"],
22575            &[
22576                b"TS.CREATERULE",
22577                b"ts:a",
22578                b"ts:down",
22579                b"AGGREGATION",
22580                b"avg",
22581                b"1000",
22582            ],
22583            &[b"TS.ADD", b"ts:a", b"5000", b"4"],
22584            &[b"TS.ADD", b"ts:a", b"6000", b"5"],
22585            &[b"TS.RANGE", b"ts:down", b"-", b"+"],
22586            &[b"TS.GET", b"ts:down", b"LATEST"],
22587            &[b"TS.INFO", b"ts:down"],
22588            &[b"TS.DEL", b"ts:a", b"5000", b"6000"],
22589            &[b"TS.RANGE", b"ts:down", b"-", b"+"],
22590            &[b"TS.DELETERULE", b"ts:a", b"ts:down"],
22591            &[b"TS.DELETERULE", b"ts:a", b"ts:down"],
22592            &[b"TS.DEL", b"ts:a", b"0", b"1000"],
22593            // And the errors.
22594            &[b"SET", b"plain", b"v"],
22595            &[b"TS.ADD", b"plain", b"1", b"1"],
22596            &[b"TS.GET", b"plain"],
22597            &[b"TS.READ", b"plain", b"0"],
22598            &[b"TS.ALTER", b"gone", b"RETENTION", b"0"],
22599            &[b"TS.RANGE", b"gone", b"-", b"+"],
22600            &[b"TS.INFO", b"gone"],
22601        ];
22602
22603        let mut one = Fixture::new();
22604        let mut many = Fixture::striped(8);
22605        for parts in script {
22606            let a = one.run(parts);
22607            let b = many.run(parts);
22608            assert_eq!(a, b, "{}", String::from_utf8_lossy(parts[0]));
22609        }
22610    }
22611
22612    /// A compaction rule whose two ends are on two stripes.
22613    ///
22614    /// This is the one thing in the family that walks from a key to another key,
22615    /// and it walks it in both directions: a sample on the source closes a
22616    /// bucket on the destination, a `LATEST` read on the destination folds the
22617    /// bucket the source is still filling, and a delete on the source rewrites
22618    /// what the destination already held. The same script is run against a
22619    /// server one stripe wide, where the two keys share a store, and against one
22620    /// eight stripes wide, where they do not.
22621    #[test]
22622    fn a_compaction_rule_across_stripes_reaches_both_ends() {
22623        let mut many = Fixture::striped(8);
22624        let other = apart(&mut many, "src");
22625        let (src, dst) = (b"src".as_slice(), other.as_bytes());
22626        let mut one = Fixture::new();
22627        let mut both = |parts: &[&[u8]]| {
22628            let a = one.run(parts);
22629            let b = many.run(parts);
22630            assert_eq!(a, b, "{}", String::from_utf8_lossy(parts[0]));
22631            a
22632        };
22633
22634        both(&[b"TS.CREATE", src]);
22635        both(&[b"TS.CREATE", dst]);
22636        assert_eq!(
22637            both(&[b"TS.CREATERULE", src, dst, b"AGGREGATION", b"avg", b"1000"]),
22638            "+OK\r\n"
22639        );
22640        both(&[b"TS.ADD", src, b"1000", b"1"]);
22641        both(&[b"TS.ADD", src, b"1500", b"3"]);
22642        // The bucket the source is filling is not written down yet, and asking
22643        // for it works it out off the source.
22644        assert_eq!(both(&[b"TS.RANGE", dst, b"-", b"+"]), "*0\r\n");
22645        let open = both(&[b"TS.GET", dst, b"LATEST"]);
22646        assert!(open.contains(":1000"), "the open bucket is folded: {open}");
22647
22648        // A sample past the bucket closes it, which is the write that has to
22649        // land on the other stripe.
22650        both(&[b"TS.ADD", src, b"2000", b"5"]);
22651        let got = both(&[b"TS.RANGE", dst, b"-", b"+"]);
22652        assert!(got.starts_with("*1\r\n"), "the bucket was written: {got}");
22653        assert!(got.contains(":1000"), "{got}");
22654
22655        // And a delete on the source takes it away again.
22656        both(&[b"TS.DEL", src, b"1000", b"1999"]);
22657        assert_eq!(both(&[b"TS.RANGE", dst, b"-", b"+"]), "*0\r\n");
22658
22659        // Both ends still know about each other, and the link comes apart from
22660        // the source.
22661        assert!(
22662            both(&[b"TS.INFO", dst]).contains("src"),
22663            "the source is named"
22664        );
22665        assert_eq!(both(&[b"TS.DELETERULE", src, dst]), "+OK\r\n");
22666        assert_eq!(
22667            both(&[b"TS.DELETERULE", src, dst]),
22668            "-ERR TSDB: compaction rule does not exist\r\n"
22669        );
22670    }
22671
22672    /// A label filter takes the series it names wherever they landed.
22673    #[test]
22674    fn a_label_query_across_stripes_finds_every_series() {
22675        let names: [&[u8]; 6] = [b"q:1", b"q:2", b"q:3", b"q:4", b"q:5", b"q:6"];
22676        let mut many = Fixture::striped(8);
22677        let mut homes: Vec<usize> = names
22678            .iter()
22679            .map(|name| many.server.striped(0).stripe_of(name))
22680            .collect();
22681        homes.sort_unstable();
22682        homes.dedup();
22683        assert!(homes.len() > 1, "the six keys are not all on one stripe");
22684
22685        let mut one = Fixture::new();
22686        let mut both = |parts: &[&[u8]]| {
22687            let a = one.run(parts);
22688            let b = many.run(parts);
22689            assert_eq!(a, b, "{}", String::from_utf8_lossy(parts[0]));
22690            a
22691        };
22692        for name in &names {
22693            both(&[b"TS.CREATE", name, b"LABELS", b"room", b"1"]);
22694            both(&[b"TS.ADD", name, b"1000", b"1"]);
22695        }
22696
22697        let got = both(&[b"TS.QUERYINDEX", b"room=1"]);
22698        assert!(got.starts_with("*6\r\n"), "every series answered: {got}");
22699        assert!(both(&[b"TS.MGET", b"FILTER", b"room=1"]).starts_with("*6\r\n"));
22700        assert!(both(&[b"TS.MRANGE", b"-", b"+", b"FILTER", b"room=1"]).starts_with("*6\r\n"));
22701        assert_eq!(
22702            both(&[b"TS.QUERYLABELS", b"LABELS", b"FILTER", b"room=1"]),
22703            "*1\r\n$4\r\nroom\r\n"
22704        );
22705    }
22706
22707    /// Every hash command, and the field import beside it, on one stripe and on
22708    /// eight.
22709    ///
22710    /// `HRANDFIELD` with a count draws from the stripe's own generator and two
22711    /// stripes do not draw the same numbers, so the only draw here is off a hash
22712    /// holding one field, where every generator gives the same answer.
22713    #[test]
22714    fn the_hash_group_answers_the_same_however_many_stripes_there_are() {
22715        let script: &[&[&[u8]]] = &[
22716            &[b"HSET", b"h", b"a", b"1", b"b", b"2"],
22717            &[b"HMSET", b"h", b"c", b"3"],
22718            &[b"HSETNX", b"h", b"a", b"9"],
22719            &[b"HSETNX", b"h", b"d", b"4"],
22720            &[b"HGET", b"h", b"a"],
22721            &[b"HGET", b"h", b"nope"],
22722            &[b"HMGET", b"h", b"a", b"nope"],
22723            &[b"HLEN", b"h"],
22724            &[b"HEXISTS", b"h", b"a"],
22725            &[b"HSTRLEN", b"h", b"a"],
22726            &[b"HGETALL", b"h"],
22727            &[b"HKEYS", b"h"],
22728            &[b"HVALS", b"h"],
22729            &[b"HINCRBY", b"h", b"a", b"5"],
22730            &[b"HINCRBYFLOAT", b"h", b"a", b"1.5"],
22731            &[b"HSCAN", b"h", b"0"],
22732            &[b"HSCAN", b"h", b"0", b"MATCH", b"a", b"COUNT", b"10"],
22733            &[b"HSCAN", b"h", b"0", b"NOVALUES"],
22734            &[b"HDEL", b"h", b"d"],
22735            &[b"HSET", b"one", b"f", b"v"],
22736            &[b"HRANDFIELD", b"one"],
22737            &[b"HRANDFIELD", b"one", b"1", b"WITHVALUES"],
22738            // The field deadlines.
22739            &[b"HEXPIRE", b"h", b"100", b"FIELDS", b"1", b"a"],
22740            &[b"HTTL", b"h", b"FIELDS", b"1", b"a"],
22741            &[b"HPTTL", b"h", b"FIELDS", b"1", b"a"],
22742            &[b"HEXPIRETIME", b"h", b"FIELDS", b"1", b"a"],
22743            &[b"HPEXPIRETIME", b"h", b"FIELDS", b"1", b"a"],
22744            &[b"HPERSIST", b"h", b"FIELDS", b"1", b"a"],
22745            &[b"HPEXPIREAT", b"h", b"1", b"FIELDS", b"1", b"b"],
22746            &[b"HGET", b"h", b"b"],
22747            // The three that came later and word everything their own way.
22748            &[b"HSETEX", b"h", b"EX", b"100", b"FIELDS", b"1", b"e", b"5"],
22749            &[b"HGETEX", b"h", b"PERSIST", b"FIELDS", b"1", b"e"],
22750            &[b"HGETDEL", b"h", b"FIELDS", b"1", b"e"],
22751            &[b"HGET", b"h", b"e"],
22752            // And the import, whose key is the third word.
22753            &[b"HIMPORT", b"PREPARE", b"fs", b"x", b"y"],
22754            &[b"HIMPORT", b"SET", b"imp", b"fs", b"1", b"2"],
22755            &[b"HGETALL", b"imp"],
22756            &[b"HIMPORT", b"SET", b"imp", b"nofs", b"1", b"2"],
22757            &[b"HIMPORT", b"DISCARD", b"fs"],
22758            // And the errors.
22759            &[b"SET", b"plain", b"v"],
22760            &[b"HSET", b"plain", b"a", b"1"],
22761            &[b"HGETALL", b"plain"],
22762            &[b"HGET", b"gone", b"a"],
22763            &[b"HINCRBY", b"h", b"a", b"nan"],
22764        ];
22765
22766        let mut one = Fixture::new();
22767        let mut many = Fixture::striped(8);
22768        // The field deadlines are absolute milliseconds worked out from the
22769        // clock, so both servers are put on the same one rather than left to
22770        // read the wall a moment apart.
22771        one.server.set_clock_ms(1_700_000_000_000);
22772        many.server.set_clock_ms(1_700_000_000_000);
22773        for parts in script {
22774            let a = one.run(parts);
22775            let b = many.run(parts);
22776            assert_eq!(a, b, "{}", String::from_utf8_lossy(parts[0]));
22777        }
22778    }
22779
22780    /// Every array command, on one stripe and on eight.
22781    #[test]
22782    fn the_array_group_answers_the_same_however_many_stripes_there_are() {
22783        let script: &[&[&[u8]]] = &[
22784            &[b"ARSET", b"a", b"0", b"x", b"y", b"z"],
22785            &[b"ARMSET", b"a", b"5", b"p", b"7", b"q"],
22786            &[b"ARGET", b"a", b"1"],
22787            &[b"ARGET", b"a", b"99"],
22788            &[b"ARMGET", b"a", b"0", b"5", b"99"],
22789            &[b"ARGETRANGE", b"a", b"0", b"7"],
22790            &[b"ARLEN", b"a"],
22791            &[b"ARCOUNT", b"a"],
22792            &[b"ARINSERT", b"a", b"m", b"n"],
22793            &[b"ARSCAN", b"a", b"0", b"20"],
22794            &[b"ARSCAN", b"a", b"0", b"20", b"LIMIT", b"2"],
22795            &[b"ARGREP", b"a", b"0", b"20", b"EXACT", b"x"],
22796            &[b"ARGREP", b"a", b"0", b"20", b"GLOB", b"*", b"WITHVALUES"],
22797            &[b"ARLASTITEMS", b"a", b"2"],
22798            &[b"ARLASTITEMS", b"a", b"2", b"REV"],
22799            &[b"ARNEXT", b"a"],
22800            &[b"ARSEEK", b"a", b"3"],
22801            &[b"AROP", b"a", b"0", b"20", b"USED"],
22802            &[b"AROP", b"a", b"0", b"20", b"MATCH", b"x"],
22803            &[b"ARINFO", b"a"],
22804            &[b"ARINFO", b"a", b"FULL"],
22805            &[b"ARDEL", b"a", b"0"],
22806            &[b"ARDELRANGE", b"a", b"1", b"2"],
22807            &[b"ARCOUNT", b"a"],
22808            &[b"ARRING", b"r", b"3", b"1", b"2", b"3", b"4"],
22809            &[b"ARGETRANGE", b"r", b"0", b"9"],
22810            // And the errors.
22811            &[b"SET", b"plain", b"v"],
22812            &[b"ARGET", b"plain", b"0"],
22813            &[b"ARSET", b"plain", b"0", b"v"],
22814            &[b"ARGET", b"gone", b"0"],
22815            &[b"ARSET", b"a", b"bad", b"v"],
22816        ];
22817
22818        let mut one = Fixture::new();
22819        let mut many = Fixture::striped(8);
22820        for parts in script {
22821            let a = one.run(parts);
22822            let b = many.run(parts);
22823            assert_eq!(a, b, "{}", String::from_utf8_lossy(parts[0]));
22824        }
22825    }
22826
22827    /// Every graph and vector set command, on one stripe and on eight.
22828    ///
22829    /// `VRANDMEMBER` is not in here for the reason `HRANDFIELD` with a count is
22830    /// not: it draws from the stripe's generator, and the stripes do not share
22831    /// one.
22832    #[test]
22833    fn the_graph_and_vector_groups_answer_the_same_however_many_stripes_there_are() {
22834        let script: &[&[&[u8]]] = &[
22835            &[b"G.NADD", b"g", b"n1", b"name", b"one"],
22836            &[b"G.NADD", b"g", b"n2", b"name", b"two"],
22837            &[b"G.NADD", b"g", b"n3"],
22838            &[b"G.NGET", b"g", b"n1"],
22839            &[b"G.NGET", b"g", b"gone"],
22840            &[b"G.EADD", b"g", b"n1", b"n2", b"knows"],
22841            &[b"G.EADD", b"g", b"n2", b"n3", b"knows"],
22842            &[b"G.OUT", b"g", b"n1", b"knows"],
22843            &[b"G.IN", b"g", b"n2", b"knows"],
22844            &[b"G.DEG", b"g", b"n1", b"knows"],
22845            &[b"G.DEG", b"g", b"n2", b"knows", b"BOTH"],
22846            &[b"G.NEIGH", b"g", b"n1", b"knows", b"DEPTH", b"2"],
22847            &[b"G.PATH", b"g", b"n1", b"n3"],
22848            &[b"G.EDEL", b"g", b"n1", b"n2", b"knows"],
22849            &[b"G.NDEL", b"g", b"n3"],
22850            &[b"G.NGET", b"g", b"n3"],
22851            // The vector set, which is one index under one key.
22852            &[b"VADD", b"v", b"VALUES", b"2", b"1", b"0", b"e1"],
22853            &[b"VADD", b"v", b"VALUES", b"2", b"0", b"1", b"e2"],
22854            &[b"VCARD", b"v"],
22855            &[b"VDIM", b"v"],
22856            &[b"VEMB", b"v", b"e1"],
22857            &[b"VSIM", b"v", b"VALUES", b"2", b"1", b"0"],
22858            &[b"VSIM", b"v", b"ELE", b"e1"],
22859            &[b"VISMEMBER", b"v", b"e1"],
22860            &[b"VISMEMBER", b"v", b"gone"],
22861            &[b"VSETATTR", b"v", b"e1", b"{\"k\":1}"],
22862            &[b"VGETATTR", b"v", b"e1"],
22863            &[b"VRANGE", b"v", b"-", b"+"],
22864            &[b"VLINKS", b"v", b"e1"],
22865            &[b"VINFO", b"v"],
22866            &[b"VREM", b"v", b"e2"],
22867            &[b"VCARD", b"v"],
22868            // And the errors.
22869            &[b"SET", b"plain", b"v"],
22870            &[b"G.NGET", b"plain", b"n1"],
22871            &[b"VCARD", b"plain"],
22872            &[b"G.NADD", b"gone2", b"n"],
22873            &[b"VEMB", b"gone3", b"e"],
22874        ];
22875
22876        let mut one = Fixture::new();
22877        let mut many = Fixture::striped(8);
22878        for parts in script {
22879            let a = one.run(parts);
22880            let b = many.run(parts);
22881            assert_eq!(a, b, "{}", String::from_utf8_lossy(parts[0]));
22882        }
22883    }
22884
22885    /// Every bloom filter, cuckoo filter, count min sketch, top k and t digest
22886    /// command, on one stripe and on eight.
22887    #[test]
22888    fn the_probabilistic_groups_answer_the_same_however_many_stripes_there_are() {
22889        let script: &[&[&[u8]]] = &[
22890            // The bloom filter.
22891            &[b"BF.RESERVE", b"bf", b"0.01", b"100"],
22892            &[b"BF.ADD", b"bf", b"a"],
22893            &[b"BF.ADD", b"bf", b"a"],
22894            &[b"BF.MADD", b"bf", b"b", b"c"],
22895            &[b"BF.EXISTS", b"bf", b"a"],
22896            &[b"BF.MEXISTS", b"bf", b"a", b"zz"],
22897            &[b"BF.CARD", b"bf"],
22898            &[b"BF.INFO", b"bf"],
22899            &[b"BF.INFO", b"bf", b"CAPACITY"],
22900            &[b"BF.DEBUG", b"bf"],
22901            &[b"BF.INSERT", b"made", b"CAPACITY", b"50", b"ITEMS", b"x"],
22902            &[b"BF.EXISTS", b"made", b"x"],
22903            &[b"BF.SCANDUMP", b"bf", b"0"],
22904            // The cuckoo filter.
22905            &[b"CF.RESERVE", b"cf", b"100"],
22906            &[b"CF.ADD", b"cf", b"a"],
22907            &[b"CF.ADDNX", b"cf", b"a"],
22908            &[b"CF.COUNT", b"cf", b"a"],
22909            &[b"CF.EXISTS", b"cf", b"a"],
22910            &[b"CF.MEXISTS", b"cf", b"a", b"zz"],
22911            &[b"CF.INSERT", b"cf", b"ITEMS", b"b", b"c"],
22912            &[b"CF.DEL", b"cf", b"a"],
22913            &[b"CF.COMPACT", b"cf"],
22914            &[b"CF.INFO", b"cf"],
22915            &[b"CF.DEBUG", b"cf"],
22916            &[b"CF.SCANDUMP", b"cf", b"0"],
22917            // The count min sketch.
22918            &[b"CMS.INITBYDIM", b"cms", b"100", b"5"],
22919            &[b"CMS.INITBYPROB", b"cms2", b"0.01", b"0.01"],
22920            &[b"CMS.INCRBY", b"cms", b"a", b"5", b"b", b"3"],
22921            &[b"CMS.QUERY", b"cms", b"a", b"b", b"gone"],
22922            &[b"CMS.INFO", b"cms"],
22923            // The top k sketch.
22924            &[b"TOPK.RESERVE", b"tk", b"3"],
22925            &[b"TOPK.ADD", b"tk", b"a", b"b", b"a"],
22926            &[b"TOPK.INCRBY", b"tk", b"c", b"4"],
22927            &[b"TOPK.QUERY", b"tk", b"a", b"zz"],
22928            &[b"TOPK.COUNT", b"tk", b"a", b"c"],
22929            &[b"TOPK.LIST", b"tk"],
22930            &[b"TOPK.LIST", b"tk", b"WITHCOUNT"],
22931            &[b"TOPK.INFO", b"tk"],
22932            // The t digest.
22933            &[b"TDIGEST.CREATE", b"td"],
22934            &[b"TDIGEST.ADD", b"td", b"1", b"2", b"3", b"4", b"5"],
22935            &[b"TDIGEST.MIN", b"td"],
22936            &[b"TDIGEST.MAX", b"td"],
22937            &[b"TDIGEST.QUANTILE", b"td", b"0.5"],
22938            &[b"TDIGEST.CDF", b"td", b"3"],
22939            &[b"TDIGEST.RANK", b"td", b"3"],
22940            &[b"TDIGEST.REVRANK", b"td", b"3"],
22941            &[b"TDIGEST.BYRANK", b"td", b"0"],
22942            &[b"TDIGEST.BYREVRANK", b"td", b"0"],
22943            &[b"TDIGEST.TRIMMED_MEAN", b"td", b"0.1", b"0.9"],
22944            &[b"TDIGEST.INFO", b"td"],
22945            &[b"TDIGEST.RESET", b"td"],
22946            &[b"TDIGEST.MIN", b"td"],
22947            // And the errors.
22948            &[b"SET", b"plain", b"v"],
22949            &[b"BF.ADD", b"plain", b"a"],
22950            &[b"CF.ADD", b"plain", b"a"],
22951            &[b"CMS.QUERY", b"plain", b"a"],
22952            &[b"TOPK.ADD", b"plain", b"a"],
22953            &[b"TDIGEST.ADD", b"plain", b"1"],
22954            &[b"CMS.INFO", b"gone"],
22955            &[b"TOPK.INFO", b"gone"],
22956            &[b"TDIGEST.INFO", b"gone"],
22957        ];
22958
22959        let mut one = Fixture::new();
22960        let mut many = Fixture::striped(8);
22961        for parts in script {
22962            let a = one.run(parts);
22963            let b = many.run(parts);
22964            assert_eq!(a, b, "{}", String::from_utf8_lossy(parts[0]));
22965        }
22966    }
22967
22968    /// The two sketch merges, with their sources on stripes of their own.
22969    ///
22970    /// These are the only two commands in the ten groups that name more than one
22971    /// key, and both read a run of sources and write a destination, so both go
22972    /// wrong in the same way if a merge holds one store and looks every source up
22973    /// in it.
22974    #[test]
22975    fn a_sketch_merge_across_stripes_reads_every_source() {
22976        let mut many = Fixture::striped(8);
22977        let other = apart(&mut many, "s1");
22978        let (s1, s2) = (b"s1".as_slice(), other.as_bytes());
22979        let mut one = Fixture::new();
22980        let mut both = |parts: &[&[u8]]| {
22981            let a = one.run(parts);
22982            let b = many.run(parts);
22983            assert_eq!(a, b, "{}", String::from_utf8_lossy(parts[0]));
22984            a
22985        };
22986
22987        // The count min sketch. The destination has to be the sources' shape,
22988        // and it is named first, so all three keys are read before anything is
22989        // written.
22990        for key in [b"cd".as_slice(), s1, s2] {
22991            both(&[b"CMS.INITBYDIM", key, b"100", b"5"]);
22992        }
22993        both(&[b"CMS.INCRBY", s1, b"x", b"5"]);
22994        both(&[b"CMS.INCRBY", s2, b"x", b"3"]);
22995        assert_eq!(
22996            both(&[b"CMS.MERGE", b"cd", b"2", s1, s2]),
22997            "+OK\r\n",
22998            "the merge took both sources"
22999        );
23000        assert_eq!(both(&[b"CMS.QUERY", b"cd", b"x"]), "*1\r\n:8\r\n");
23001        // And with weights, which are read against the sources in order.
23002        both(&[b"CMS.MERGE", b"cd", b"2", s1, s2, b"WEIGHTS", b"2", b"1"]);
23003        assert_eq!(both(&[b"CMS.QUERY", b"cd", b"x"]), "*1\r\n:13\r\n");
23004        // A source that is not a sketch is answered before anything is written.
23005        both(&[b"SET", b"plain", b"v"]);
23006        assert!(both(&[b"CMS.MERGE", b"cd", b"2", s1, b"plain"]).starts_with('-'));
23007        assert_eq!(both(&[b"CMS.QUERY", b"cd", b"x"]), "*1\r\n:13\r\n");
23008
23009        // The t digest, which builds its destination and then puts it in place.
23010        // The two source keys are used again here, so what they held goes first.
23011        both(&[b"FLUSHALL"]);
23012        both(&[b"TDIGEST.CREATE", b"td"]);
23013        both(&[b"TDIGEST.CREATE", s1]);
23014        both(&[b"TDIGEST.CREATE", s2]);
23015        both(&[b"TDIGEST.ADD", s1, b"1", b"2"]);
23016        both(&[b"TDIGEST.ADD", s2, b"9", b"10"]);
23017        assert_eq!(both(&[b"TDIGEST.MERGE", b"td", b"2", s1, s2]), "+OK\r\n");
23018        assert_eq!(both(&[b"TDIGEST.MIN", b"td"]), "$1\r\n1\r\n");
23019        assert_eq!(both(&[b"TDIGEST.MAX", b"td"]), "$2\r\n10\r\n");
23020    }
23021
23022    /// Every shape of `SORT`, on one stripe and on eight.
23023    ///
23024    /// The key it sorts, the keys a `BY` names, the keys a `GET` names and the
23025    /// destination are four different names and nothing lines them up, so on
23026    /// eight stripes this script is reading and writing all over the database
23027    /// while on one it is doing what it always did.
23028    #[test]
23029    fn the_sort_command_answers_the_same_however_many_stripes_there_are() {
23030        let script: &[&[&[u8]]] = &[
23031            &[b"RPUSH", b"l", b"3", b"1", b"2", b"10"],
23032            &[b"SORT", b"l"],
23033            &[b"SORT", b"l", b"DESC"],
23034            &[b"SORT", b"l", b"ALPHA"],
23035            &[b"SORT", b"l", b"LIMIT", b"1", b"2"],
23036            &[b"SORT_RO", b"l"],
23037            // A weight per element, so the order comes off keys the command
23038            // never named.
23039            &[
23040                b"MSET", b"w_1", b"4", b"w_2", b"3", b"w_3", b"2", b"w_10", b"1",
23041            ],
23042            &[b"SORT", b"l", b"BY", b"w_*"],
23043            &[b"SORT", b"l", b"BY", b"w_*", b"DESC"],
23044            &[b"DEL", b"w_2"],
23045            &[b"SORT", b"l", b"BY", b"w_*"],
23046            // And the answer off another set of keys again, with `#` mixed in
23047            // so the rows are not all lookups.
23048            &[b"MSET", b"d_1", b"one", b"d_3", b"three"],
23049            &[b"SORT", b"l", b"BY", b"w_*", b"GET", b"#", b"GET", b"d_*"],
23050            // A pattern that reaches into a hash, which is another key again.
23051            &[b"HSET", b"h_1", b"f", b"9"],
23052            &[b"HSET", b"h_2", b"f", b"8"],
23053            &[b"HSET", b"h_3", b"f", b"7"],
23054            &[b"HSET", b"h_10", b"f", b"6"],
23055            &[b"SORT", b"l", b"BY", b"h_*->f"],
23056            &[b"SORT", b"l", b"BY", b"nosort", b"GET", b"h_*->f"],
23057            // The destination, which is a fourth place to land.
23058            &[b"SORT", b"l", b"BY", b"w_*", b"STORE", b"out"],
23059            &[b"LRANGE", b"out", b"0", b"-1"],
23060            &[b"SORT", b"l", b"STORE", b"l"],
23061            &[b"LRANGE", b"l", b"0", b"-1"],
23062            // An empty result takes the destination away rather than leaving a
23063            // list of nothing behind.
23064            &[b"SORT", b"missing", b"STORE", b"out"],
23065            &[b"EXISTS", b"out"],
23066            // A set and a sorted set sort the same way a list does, and a set
23067            // written to a destination is sorted even when nothing asked.
23068            &[b"SADD", b"s", b"c", b"a", b"b"],
23069            &[b"SORT", b"s", b"ALPHA"],
23070            &[b"SORT", b"s", b"BY", b"nosort", b"STORE", b"out"],
23071            &[b"LRANGE", b"out", b"0", b"-1"],
23072            &[b"ZADD", b"z", b"3", b"c", b"1", b"a", b"2", b"b"],
23073            &[b"SORT", b"z", b"BY", b"nosort"],
23074            &[b"SORT", b"z", b"ALPHA", b"DESC"],
23075            // And the two ways it refuses: a key of the wrong type, and an
23076            // element that is not a number under a numeric sort.
23077            &[b"SET", b"str", b"v"],
23078            &[b"SORT", b"str"],
23079            &[b"RPUSH", b"words", b"one", b"two"],
23080            &[b"SORT", b"words"],
23081            &[b"SORT_RO", b"l", b"STORE", b"out"],
23082        ];
23083
23084        let mut one = Fixture::new();
23085        let mut many = Fixture::striped(8);
23086        for parts in script {
23087            let a = one.run(parts);
23088            let b = many.run(parts);
23089            assert_eq!(a, b, "{}", String::from_utf8_lossy(parts[0]));
23090        }
23091    }
23092
23093    /// One `SORT` whose four kinds of key are on stripes of their own.
23094    ///
23095    /// The script above spreads keys around by writing enough of them, and this
23096    /// one checks the spread rather than trusting it: the list, the weight key
23097    /// for one of its elements and the destination are asserted to be in three
23098    /// places before the command runs.
23099    #[test]
23100    fn a_sort_across_stripes_reads_every_pattern_key() {
23101        let mut f = Fixture::striped(8);
23102        let out = apart(&mut f, "l");
23103        let (list, dest) = (b"l".as_slice(), out.as_bytes());
23104
23105        f.run(&[b"RPUSH", list, b"a", b"b", b"c", b"d"]);
23106        f.run(&[
23107            b"MSET", b"w_a", b"4", b"w_b", b"3", b"w_c", b"2", b"w_d", b"1",
23108        ]);
23109        f.run(&[
23110            b"MSET", b"d_a", b"A", b"d_b", b"B", b"d_c", b"C", b"d_d", b"D",
23111        ]);
23112
23113        // The weights are four keys and they are not all in one place, which is
23114        // the thing that would go unnoticed if the command held a stripe.
23115        let db = f.server.striped(0);
23116        let weights: Vec<usize> = [b"w_a", b"w_b", b"w_c", b"w_d"]
23117            .iter()
23118            .map(|k| db.stripe_of(k.as_slice()))
23119            .collect();
23120        assert!(
23121            weights.iter().any(|s| *s != weights[0]),
23122            "the four weight keys all landed on one stripe, so this proves nothing"
23123        );
23124
23125        assert_eq!(
23126            f.run(&[b"SORT", list, b"BY", b"w_*", b"GET", b"d_*"]),
23127            "*4\r\n$1\r\nD\r\n$1\r\nC\r\n$1\r\nB\r\n$1\r\nA\r\n",
23128            "the order came off the weights and the answer off the data keys"
23129        );
23130        assert_eq!(
23131            f.run(&[b"SORT", list, b"BY", b"w_*", b"STORE", dest]),
23132            ":4\r\n"
23133        );
23134        assert_eq!(
23135            f.run(&[b"LRANGE", dest, b"0", b"-1"]),
23136            "*4\r\n$1\r\nd\r\n$1\r\nc\r\n$1\r\nb\r\n$1\r\na\r\n",
23137            "the destination is on a stripe of its own and got the whole answer"
23138        );
23139    }
23140
23141    /// A `CONFIG SET` reaches every stripe, so where a key landed does not
23142    /// decide what shape it is stored in.
23143    ///
23144    /// This is the setting that would go wrong quietly. A stripe that kept the
23145    /// old ladder would hold the same hash in a different encoding from the
23146    /// stripe next to it, and the only thing that would ever say so is
23147    /// `OBJECT ENCODING`, which is why the check is on that.
23148    #[test]
23149    fn a_setting_reaches_every_stripe_and_reads_back_from_any_of_them() {
23150        let mut f = Fixture::striped(8);
23151        let other = apart(&mut f, "h");
23152        let (first, second) = (b"h".as_slice(), other.as_bytes());
23153
23154        assert_eq!(
23155            f.run(&[b"CONFIG", b"SET", b"hash-max-listpack-entries", b"2"]),
23156            "+OK\r\n"
23157        );
23158        assert_eq!(
23159            f.run(&[b"CONFIG", b"GET", b"hash-max-listpack-entries"]),
23160            "*2\r\n$25\r\nhash-max-listpack-entries\r\n$1\r\n2\r\n",
23161            "the read comes off one stripe and has to answer for all of them"
23162        );
23163        for key in [first, second] {
23164            f.run(&[b"HSET", key, b"a", b"1", b"b", b"2"]);
23165            assert_eq!(
23166                f.run(&[b"OBJECT", b"ENCODING", key]),
23167                "$8\r\nlistpack\r\n",
23168                "two fields is still under the ladder"
23169            );
23170            f.run(&[b"HSET", key, b"c", b"3"]);
23171            assert_eq!(
23172                f.run(&[b"OBJECT", b"ENCODING", key]),
23173                "$9\r\nhashtable\r\n",
23174                "three fields is over it, on whichever stripe the key is on"
23175            );
23176        }
23177
23178        // And the policy, which every stripe has to agree about for the same
23179        // reason: an eviction draws from one stripe at a time.
23180        assert_eq!(
23181            f.run(&[b"CONFIG", b"SET", b"maxmemory-policy", b"allkeys-lru"]),
23182            "+OK\r\n"
23183        );
23184        let db = f.server.striped(0);
23185        assert!(
23186            (0..db.width()).all(|i| db.hold_stripe(i).policy().name() == "allkeys-lru"),
23187            "a stripe kept the old policy"
23188        );
23189    }
23190
23191    /// What an index holds, as the two numbers `FT.INFO` reports about it.
23192    ///
23193    /// Read off the registry rather than parsed back out of an `FT.INFO` reply,
23194    /// because the reply is thirty odd fields and these two are the ones the
23195    /// keyspace hook moves.
23196    fn held(f: &Fixture, name: &[u8]) -> (usize, u32) {
23197        let search = f.server.search.lock();
23198        let index = search.named(name).expect("the index is there");
23199        (index.held.docs.len(), index.held.docs.last())
23200    }
23201
23202    /// A hash written under an index's prefix reaches it, and one written
23203    /// outside the prefix does not.
23204    #[test]
23205    fn a_hash_that_is_written_reaches_the_index_that_follows_it() {
23206        let mut f = Fixture::new();
23207        f.run(&[
23208            b"FT.CREATE",
23209            b"ix",
23210            b"PREFIX",
23211            b"1",
23212            b"p:",
23213            b"SCHEMA",
23214            b"t",
23215            b"TEXT",
23216        ]);
23217        f.run(&[b"HSET", b"p:1", b"t", b"running dogs"]);
23218        assert_eq!(held(&f, b"ix"), (1, 1));
23219        f.run(&[b"HSET", b"other:1", b"t", b"running dogs"]);
23220        assert_eq!(held(&f, b"ix"), (1, 1));
23221
23222        // Every field of the key and not the one the command named, since a
23223        // document is read from nothing every time.
23224        f.run(&[b"HSET", b"p:1", b"u", b"beta"]);
23225        f.run(&[b"HDEL", b"p:1", b"u"]);
23226        assert_eq!(held(&f, b"ix"), (1, 3));
23227        let search = f.server.search.lock();
23228        let index = search.named(b"ix").expect("there");
23229        assert_eq!(index.held.docs.id(b"p:1"), Some(3));
23230    }
23231
23232    /// A fresh index reads the keys that were already there, and walks past a
23233    /// key of the wrong type without counting a failure.
23234    #[test]
23235    fn a_fresh_index_reads_the_keys_that_were_already_there() {
23236        let mut f = Fixture::new();
23237        f.run(&[b"HSET", b"p:1", b"t", b"alpha"]);
23238        f.run(&[b"SET", b"p:str", b"not a hash"]);
23239        f.run(&[b"HSET", b"q:1", b"t", b"beta"]);
23240        f.run(&[
23241            b"FT.CREATE",
23242            b"ix",
23243            b"PREFIX",
23244            b"1",
23245            b"p:",
23246            b"SCHEMA",
23247            b"t",
23248            b"TEXT",
23249        ]);
23250
23251        assert_eq!(held(&f, b"ix"), (1, 1));
23252        let search = f.server.search.lock();
23253        let index = search.named(b"ix").expect("there");
23254        assert_eq!(index.trouble.whole().failures(), 0);
23255    }
23256
23257    /// `SKIPINITIALSCAN` leaves what was there alone, and a later write to one
23258    /// of those keys still lands.
23259    #[test]
23260    fn an_index_that_skipped_the_scan_fills_up_on_the_next_write() {
23261        let mut f = Fixture::new();
23262        f.run(&[b"HSET", b"p:1", b"t", b"alpha"]);
23263        f.run(&[
23264            b"FT.CREATE",
23265            b"ix",
23266            b"PREFIX",
23267            b"1",
23268            b"p:",
23269            b"SKIPINITIALSCAN",
23270            b"SCHEMA",
23271            b"t",
23272            b"TEXT",
23273        ]);
23274        assert_eq!(held(&f, b"ix"), (0, 0));
23275        f.run(&[b"HSET", b"p:1", b"t", b"alpha"]);
23276        assert_eq!(held(&f, b"ix"), (1, 1));
23277    }
23278
23279    /// A command that changed nothing leaves the document where it was, which
23280    /// is not the same as a command that was not a write.
23281    ///
23282    /// All five of these were measured against 8.10.1. Writing the same value
23283    /// again moves the number and a deadline set for later does not, which is
23284    /// the pair that makes the rule "the fields are not what they were" rather
23285    /// than "this was a write".
23286    #[test]
23287    fn only_a_real_change_gives_the_document_a_new_number() {
23288        let mut f = Fixture::new();
23289        f.run(&[
23290            b"FT.CREATE",
23291            b"ix",
23292            b"PREFIX",
23293            b"1",
23294            b"p:",
23295            b"SCHEMA",
23296            b"t",
23297            b"TEXT",
23298        ]);
23299        f.run(&[b"HSET", b"p:1", b"t", b"alpha"]);
23300        assert_eq!(held(&f, b"ix"), (1, 1));
23301
23302        f.run(&[b"HSET", b"p:1", b"t", b"alpha"]);
23303        assert_eq!(held(&f, b"ix"), (1, 2), "the same value still rewrites");
23304
23305        for quiet in [
23306            vec![b"HSETNX".as_slice(), b"p:1", b"t", b"other"],
23307            vec![b"HDEL".as_slice(), b"p:1", b"nosuch"],
23308            vec![b"HGET".as_slice(), b"p:1", b"t"],
23309            vec![b"HGETALL".as_slice(), b"p:1"],
23310            vec![b"HEXPIRE".as_slice(), b"p:1", b"100", b"FIELDS", b"1", b"t"],
23311            vec![b"HPERSIST".as_slice(), b"p:1", b"FIELDS", b"1", b"t"],
23312            vec![
23313                b"HGETEX".as_slice(),
23314                b"p:1",
23315                b"EX",
23316                b"100",
23317                b"FIELDS",
23318                b"1",
23319                b"t",
23320            ],
23321            vec![b"HGETDEL".as_slice(), b"p:1", b"FIELDS", b"1", b"nosuch"],
23322        ] {
23323            f.run(&quiet);
23324            assert_eq!(held(&f, b"ix"), (1, 2), "{:?} moved the document", quiet[0]);
23325        }
23326
23327        // And the ones that do change something.
23328        f.run(&[b"HSET", b"p:2", b"n", b"1"]);
23329        f.run(&[b"HINCRBY", b"p:2", b"n", b"1"]);
23330        assert_eq!(held(&f, b"ix"), (2, 4));
23331        // A deadline that has already passed takes the field away, and taking
23332        // the last field away takes the key and the document with it. The
23333        // number still moves on the way past, because the field going and the
23334        // key going are two separate pieces of news and the first of them
23335        // writes the document one last time.
23336        f.run(&[b"HEXPIRE", b"p:2", b"0", b"FIELDS", b"1", b"n"]);
23337        assert_eq!(held(&f, b"ix"), (1, 5));
23338    }
23339
23340    /// The two ways of emptying a hash, which do not leave the same thing
23341    /// behind. `HDEL` of the last field spends no number and is counted as a
23342    /// refusal, and a deadline that has already passed spends one on a document
23343    /// nobody sees and is counted as nothing. Measured against 8.10.1 and not
23344    /// something anyone would guess.
23345    #[test]
23346    fn a_key_emptied_by_a_deadline_spends_a_number_and_one_emptied_by_hdel_does_not() {
23347        /// The index's own failure count.
23348        fn refused(f: &Fixture, name: &[u8]) -> u64 {
23349            let search = f.server.search.lock();
23350            let index = search.named(name).expect("the index is there");
23351            index.trouble.whole().failures()
23352        }
23353
23354        let mut f = Fixture::new();
23355        f.run(&[
23356            b"FT.CREATE",
23357            b"ix",
23358            b"PREFIX",
23359            b"1",
23360            b"p:",
23361            b"SCHEMA",
23362            b"t",
23363            b"TEXT",
23364        ]);
23365        f.run(&[b"HSET", b"p:1", b"t", b"alpha"]);
23366        assert_eq!(held(&f, b"ix"), (1, 1));
23367        f.run(&[b"HDEL", b"p:1", b"t"]);
23368        assert_eq!(
23369            held(&f, b"ix"),
23370            (0, 1),
23371            "HDEL of the last field spends none"
23372        );
23373        assert_eq!(refused(&f, b"ix"), 1, "and is counted as a refusal");
23374
23375        f.run(&[b"HSET", b"p:2", b"t", b"alpha"]);
23376        assert_eq!(held(&f, b"ix"), (1, 2));
23377        f.run(&[b"HEXPIRE", b"p:2", b"0", b"FIELDS", b"1", b"t"]);
23378        assert_eq!(held(&f, b"ix"), (0, 3), "a deadline spends one");
23379        assert_eq!(refused(&f, b"ix"), 1, "and is counted as nothing");
23380
23381        f.run(&[b"HSET", b"p:3", b"t", b"alpha"]);
23382        assert_eq!(held(&f, b"ix"), (1, 4));
23383        f.run(&[b"HGETDEL", b"p:3", b"FIELDS", b"1", b"t"]);
23384        assert_eq!(held(&f, b"ix"), (0, 5), "and so does HGETDEL");
23385
23386        // Two fields and one command is one rewrite and not two, whichever way
23387        // the fields go.
23388        f.run(&[b"HSET", b"p:4", b"t", b"alpha", b"u", b"beta"]);
23389        assert_eq!(held(&f, b"ix"), (1, 6));
23390        f.run(&[b"HEXPIRE", b"p:4", b"0", b"FIELDS", b"2", b"t", b"u"]);
23391        assert_eq!(held(&f, b"ix"), (0, 7));
23392        assert_eq!(refused(&f, b"ix"), 1);
23393    }
23394
23395    /// `HSETEX` with a deadline that has already passed is two pieces of news
23396    /// from one command, so the number moves twice and the value never reaches
23397    /// the index.
23398    #[test]
23399    fn a_field_written_already_past_its_deadline_moves_the_number_twice() {
23400        let mut f = Fixture::new();
23401        f.run(&[
23402            b"FT.CREATE",
23403            b"ix",
23404            b"PREFIX",
23405            b"1",
23406            b"p:",
23407            b"SCHEMA",
23408            b"t",
23409            b"TEXT",
23410            b"u",
23411            b"TEXT",
23412        ]);
23413        f.run(&[b"HSET", b"p:1", b"u", b"keepme"]);
23414        assert_eq!(held(&f, b"ix"), (1, 1));
23415        f.run(&[
23416            b"HSETEX", b"p:1", b"EXAT", b"1", b"FIELDS", b"1", b"t", b"zqx",
23417        ]);
23418        assert_eq!(
23419            held(&f, b"ix"),
23420            (1, 3),
23421            "the key lived and the field did not"
23422        );
23423
23424        // And the same when the key does not survive it.
23425        f.run(&[b"HSET", b"p:2", b"t", b"alpha"]);
23426        assert_eq!(held(&f, b"ix"), (2, 4));
23427        f.run(&[
23428            b"HSETEX", b"p:2", b"EXAT", b"1", b"FIELDS", b"1", b"t", b"zqx",
23429        ]);
23430        assert_eq!(held(&f, b"ix"), (1, 6));
23431    }
23432
23433    /// The number one key is indexed under, or `None` when it holds no
23434    /// document.
23435    fn number(f: &Fixture, name: &[u8], key: &[u8]) -> Option<u32> {
23436        let search = f.server.search.lock();
23437        let index = search.named(name).expect("the index is there");
23438        index.held.docs.id(key)
23439    }
23440
23441    /// An index over `p:` with one document under `p:1`, which is where four of
23442    /// the tests below start.
23443    fn indexed() -> Fixture {
23444        let mut f = Fixture::new();
23445        f.run(&[
23446            b"FT.CREATE",
23447            b"ix",
23448            b"PREFIX",
23449            b"1",
23450            b"p:",
23451            b"SCHEMA",
23452            b"t",
23453            b"TEXT",
23454        ]);
23455        f.run(&[b"HSET", b"p:1", b"t", b"alpha"]);
23456        f
23457    }
23458
23459    /// Every way a keyspace command takes a key away leaves no document behind,
23460    /// and none of them spends a number or is counted as a refusal.
23461    #[test]
23462    fn a_key_a_keyspace_command_takes_away_loses_its_document() {
23463        for take in [
23464            vec![b"DEL".as_slice(), b"p:1"],
23465            vec![b"UNLINK".as_slice(), b"p:1"],
23466            vec![b"PEXPIREAT".as_slice(), b"p:1", b"1"],
23467            vec![b"EXPIRE".as_slice(), b"p:1", b"-1"],
23468        ] {
23469            let mut f = indexed();
23470            assert_eq!(held(&f, b"ix"), (1, 1));
23471            f.run(&take);
23472            assert_eq!(held(&f, b"ix"), (0, 1), "{:?} left something", take[0]);
23473            let search = f.server.search.lock();
23474            let index = search.named(b"ix").expect("the index is there");
23475            assert_eq!(index.trouble.whole().failures(), 0, "{:?}", take[0]);
23476        }
23477
23478        // A deadline that has not passed yet is not one of them.
23479        let mut f = indexed();
23480        f.run(&[b"EXPIRE", b"p:1", b"1000"]);
23481        assert_eq!(held(&f, b"ix"), (1, 1));
23482        f.run(&[b"PERSIST", b"p:1"]);
23483        assert_eq!(held(&f, b"ix"), (1, 1));
23484    }
23485
23486    /// A rename inside the prefix keeps the number the document had, which is
23487    /// the one write on a followed key that does not spend one. Out of the
23488    /// prefix is an erase and into it is a fresh reading, both measured.
23489    #[test]
23490    fn a_rename_inside_the_prefix_keeps_the_number_the_document_had() {
23491        let mut f = indexed();
23492        f.run(&[b"RENAME", b"p:1", b"p:2"]);
23493        assert_eq!(held(&f, b"ix"), (1, 1), "nothing was read again");
23494        assert_eq!(number(&f, b"ix", b"p:2"), Some(1));
23495        assert_eq!(number(&f, b"ix", b"p:1"), None);
23496
23497        f.run(&[b"RENAME", b"p:2", b"q:1"]);
23498        assert_eq!(held(&f, b"ix"), (0, 1), "out of the prefix is an erase");
23499
23500        f.run(&[b"RENAME", b"q:1", b"p:3"]);
23501        assert_eq!(held(&f, b"ix"), (1, 2), "and into it is a reading");
23502        assert_eq!(number(&f, b"ix", b"p:3"), Some(2));
23503
23504        // `RENAMENX` goes the same way, and the one that answers zero changes
23505        // nothing.
23506        f.run(&[b"HSET", b"p:4", b"t", b"beta"]);
23507        assert_eq!(f.run(&[b"RENAMENX", b"p:3", b"p:4"]), ":0\r\n");
23508        assert_eq!(held(&f, b"ix"), (2, 3));
23509        f.run(&[b"RENAMENX", b"p:3", b"p:5"]);
23510        assert_eq!(number(&f, b"ix", b"p:5"), Some(2));
23511    }
23512
23513    /// A rename over a key that already had a document leaves one document and
23514    /// not two. A real server leaves both, and D-64 is that difference.
23515    #[test]
23516    fn a_rename_over_a_document_leaves_one_of_them() {
23517        let mut f = indexed();
23518        f.run(&[b"HSET", b"p:2", b"t", b"beta"]);
23519        assert_eq!(held(&f, b"ix"), (2, 2));
23520        f.run(&[b"RENAME", b"p:1", b"p:2"]);
23521        assert_eq!(held(&f, b"ix"), (1, 2));
23522        assert_eq!(number(&f, b"ix", b"p:2"), Some(1));
23523    }
23524
23525    /// A key that arrives under the prefix by being copied or restored is read
23526    /// as a new document, and one that is written over by something that is not
23527    /// a hash is erased without a word.
23528    #[test]
23529    fn a_key_that_arrives_under_the_prefix_is_read_and_one_overwritten_is_erased() {
23530        let mut f = indexed();
23531        f.run(&[b"HSET", b"q:1", b"t", b"beta"]);
23532        f.run(&[b"COPY", b"q:1", b"p:2"]);
23533        assert_eq!(held(&f, b"ix"), (2, 2));
23534        assert_eq!(number(&f, b"ix", b"p:2"), Some(2));
23535
23536        // Out of the prefix, where the source keeps the document it had.
23537        f.run(&[b"COPY", b"p:1", b"q:2"]);
23538        assert_eq!(held(&f, b"ix"), (2, 2));
23539
23540        // Over a key that has one, which is a new reading and not a rename.
23541        f.run(&[b"COPY", b"q:1", b"p:1", b"REPLACE"]);
23542        assert_eq!(held(&f, b"ix"), (2, 3));
23543        assert_eq!(number(&f, b"ix", b"p:1"), Some(3));
23544
23545        // And a string landing on top of a document takes it away, spending no
23546        // number and counting no failure.
23547        f.run(&[b"SET", b"s:1", b"plain"]);
23548        f.run(&[b"COPY", b"s:1", b"p:1", b"REPLACE"]);
23549        assert_eq!(held(&f, b"ix"), (1, 3));
23550        let dump = f.run(&[b"DUMP", b"q:1"]);
23551        assert!(dump.starts_with('$'), "{dump}");
23552    }
23553
23554    /// The keyspace group reads a key back on database zero whatever database
23555    /// the command ran on, which is measured and is not what the hash commands
23556    /// do. A `COPY` into another database indexes nothing and takes away
23557    /// whatever the destination had, and a `RESTORE` anywhere else is invisible.
23558    #[test]
23559    fn the_keyspace_group_reads_database_zero_whatever_database_it_ran_on() {
23560        let mut f = indexed();
23561        f.run(&[b"HSET", b"p:2", b"t", b"beta"]);
23562        assert_eq!(held(&f, b"ix"), (2, 2));
23563        // Into database one, so the indexes look for `p:2` on database zero,
23564        // find the one that is still there and read it again.
23565        f.run(&[b"COPY", b"p:1", b"p:2", b"DB", b"1", b"REPLACE"]);
23566        assert_eq!(held(&f, b"ix"), (2, 3));
23567        // And with nothing under that name on database zero, the copy leaves
23568        // the index one document lighter than it found it.
23569        f.run(&[b"DEL", b"p:2"]);
23570        assert_eq!(held(&f, b"ix"), (1, 3));
23571        f.run(&[b"COPY", b"p:1", b"p:2", b"DB", b"1", b"REPLACE"]);
23572        assert_eq!(held(&f, b"ix"), (1, 3), "the copy landed out of sight");
23573
23574        // A restore on another database is the same story.
23575        let dump = f.run(&[b"DUMP", b"p:1"]);
23576        assert!(dump.starts_with('$'), "{dump}");
23577        f.run(&[b"SELECT", b"1"]);
23578        f.run(&[b"HSET", b"q:1", b"t", b"gamma"]);
23579        f.run(&[b"RENAME", b"q:1", b"p:3"]);
23580        assert_eq!(held(&f, b"ix"), (1, 3), "and so is a rename");
23581    }
23582
23583    /// `MOVE` is not a change at all, because an index follows a key by name
23584    /// and a write on any database still reaches it.
23585    #[test]
23586    fn a_move_leaves_the_document_where_it_is() {
23587        let mut f = indexed();
23588        f.run(&[b"MOVE", b"p:1", b"1"]);
23589        assert_eq!(held(&f, b"ix"), (1, 1), "the key moved and nothing else");
23590        assert_eq!(number(&f, b"ix", b"p:1"), Some(1));
23591
23592        f.run(&[b"SELECT", b"1"]);
23593        f.run(&[b"HSET", b"p:1", b"t", b"beta"]);
23594        assert_eq!(held(&f, b"ix"), (1, 2), "and a write there still lands");
23595        f.run(&[b"DEL", b"p:1"]);
23596        assert_eq!(held(&f, b"ix"), (0, 2));
23597    }
23598
23599    /// A flush takes every index with it, whichever database it flushed.
23600    #[test]
23601    fn a_flush_drops_the_indexes() {
23602        for flush in [b"FLUSHALL".as_slice(), b"FLUSHDB"] {
23603            let mut f = indexed();
23604            f.run(&[flush]);
23605            assert!(f.server.search.lock().is_empty(), "{flush:?} kept an index");
23606            assert_eq!(f.run(&[b"FT._LIST"]), "*0\r\n");
23607        }
23608
23609        // Even on a database no index ever read, which is what a real server
23610        // does and is not what anyone would guess.
23611        let mut f = indexed();
23612        f.run(&[b"SELECT", b"9"]);
23613        f.run(&[b"FLUSHDB"]);
23614        assert!(f.server.search.lock().is_empty());
23615    }
23616
23617    /// An index whose schema has one tag field of each kind, plus a number so
23618    /// there is something for `FT.TAGVALS` to refuse.
23619    fn tagged() -> Fixture {
23620        let mut f = Fixture::new();
23621        f.run(&[
23622            b"FT.CREATE",
23623            b"tv",
23624            b"PREFIX",
23625            b"1",
23626            b"tv:",
23627            b"SCHEMA",
23628            b"g",
23629            b"AS",
23630            b"gg",
23631            b"TAG",
23632            b"h",
23633            b"TAG",
23634            b"SEPARATOR",
23635            b"|",
23636            b"CASESENSITIVE",
23637            b"n",
23638            b"NUMERIC",
23639        ]);
23640        f.run(&[
23641            b"HSET",
23642            b"tv:1",
23643            b"g",
23644            b"Red, BLUE ",
23645            b"h",
23646            b"Aa|bB",
23647            b"n",
23648            b"1",
23649        ]);
23650        f.run(&[b"HSET", b"tv:2", b"g", b"red", b"h", b"aa", b"n", b"2"]);
23651        f
23652    }
23653
23654    /// The values come back as they are stored, so an ordinary tag field
23655    /// answers them folded and trimmed and a `CASESENSITIVE` one answers what
23656    /// it was given. Byte order either way, which puts the capital first.
23657    #[test]
23658    fn tag_values_come_back_as_they_are_stored_and_sorted_by_their_bytes() {
23659        let mut f = tagged();
23660        assert_eq!(
23661            f.run(&[b"FT.TAGVALS", b"tv", b"gg"]),
23662            "*2\r\n$4\r\nblue\r\n$3\r\nred\r\n"
23663        );
23664        assert_eq!(
23665            f.run(&[b"FT.TAGVALS", b"tv", b"h"]),
23666            "*3\r\n$2\r\nAa\r\n$2\r\naa\r\n$2\r\nbB\r\n"
23667        );
23668    }
23669
23670    /// The name asked about is the attribute, so the identifier of a field
23671    /// declared `AS` is not a name this knows.
23672    #[test]
23673    fn tag_values_are_asked_for_by_the_attribute_and_not_the_identifier() {
23674        let mut f = tagged();
23675        for (name, want) in [
23676            (b"g".as_slice(), "-SEARCH_ATTR_BAD No such field\r\n"),
23677            (b"zz", "-SEARCH_ATTR_BAD No such field\r\n"),
23678            (b"n", "-SEARCH_ATTR_BAD Not a tag field\r\n"),
23679        ] {
23680            assert_eq!(f.run(&[b"FT.TAGVALS", b"tv", name]), want);
23681        }
23682        assert_eq!(
23683            f.run(&[b"FT.TAGVALS", b"nope", b"g"]),
23684            "-SEARCH_INDEX_NOT_FOUND Index not found: nope\r\n"
23685        );
23686    }
23687
23688    /// Looking up the index counts as a use of it on the roads that refuse the
23689    /// field as well as on the one that answers, which is measured.
23690    #[test]
23691    fn asking_for_tag_values_counts_a_use_of_the_index() {
23692        let mut f = tagged();
23693        let uses = |f: &mut Fixture| {
23694            let reply = f.run(&[b"FT.INFO", b"tv"]);
23695            let at = reply.find("number_of_uses").expect("the field is reported");
23696            let value = reply[at..].split("\r\n").nth(1).unwrap();
23697            value.trim_start_matches(':').parse::<i64>().unwrap()
23698        };
23699        let before = uses(&mut f);
23700        f.run(&[b"FT.TAGVALS", b"tv", b"gg"]);
23701        f.run(&[b"FT.TAGVALS", b"tv", b"zz"]);
23702        // Three more than before: two tag lookups and the second `FT.INFO`.
23703        assert_eq!(uses(&mut f), before + 3);
23704    }
23705
23706    /// A tag field nothing was ever written to has no list at all, which
23707    /// answers the same empty set a list that has been emptied does.
23708    #[test]
23709    fn a_tag_field_with_nothing_in_it_answers_empty() {
23710        let mut f = Fixture::new();
23711        f.run(&[b"FT.CREATE", b"e", b"SCHEMA", b"g", b"TAG"]);
23712        assert_eq!(f.run(&[b"FT.TAGVALS", b"e", b"g"]), "*0\r\n");
23713    }
23714
23715    /// A dictionary is module state and not a key, so nothing in the keyspace
23716    /// can see one.
23717    #[test]
23718    fn a_dictionary_is_not_a_key() {
23719        let mut f = Fixture::new();
23720        assert_eq!(f.run(&[b"FT.DICTADD", b"d", b"a", b"b"]), ":2\r\n");
23721        assert_eq!(f.run(&[b"TYPE", b"d"]), "+none\r\n");
23722        assert_eq!(f.run(&[b"EXISTS", b"d"]), ":0\r\n");
23723        assert_eq!(f.run(&[b"KEYS", b"d"]), "*0\r\n");
23724    }
23725
23726    /// The count is how many terms were new, an empty term is not a term, and
23727    /// the dump is sorted by bytes rather than folded.
23728    #[test]
23729    fn a_dictionary_counts_the_terms_it_had_not_seen() {
23730        let mut f = Fixture::new();
23731        assert_eq!(
23732            f.run(&[b"FT.DICTADD", b"d", b"zeta", b"alpha", b"Beta", b"alpha"]),
23733            ":3\r\n"
23734        );
23735        assert_eq!(f.run(&[b"FT.DICTADD", b"d", b"alpha"]), ":0\r\n");
23736        assert_eq!(f.run(&[b"FT.DICTADD", b"d", b""]), ":0\r\n");
23737        assert_eq!(
23738            f.run(&[b"FT.DICTDUMP", b"d"]),
23739            "*3\r\n$4\r\nBeta\r\n$5\r\nalpha\r\n$4\r\nzeta\r\n"
23740        );
23741        assert_eq!(f.run(&[b"FT.DICTDEL", b"d", b"alpha", b"nope"]), ":1\r\n");
23742    }
23743
23744    /// A name nobody ever added to is not an error on either of the two
23745    /// commands that will take one, which is the only place in the group where
23746    /// a missing name is forgiven.
23747    #[test]
23748    fn a_dictionary_nobody_made_dumps_empty_rather_than_failing() {
23749        let mut f = Fixture::new();
23750        assert_eq!(f.run(&[b"FT.DICTDUMP", b"nope"]), "*0\r\n");
23751        assert_eq!(f.run(&[b"FT.DICTDEL", b"nope", b"a"]), ":0\r\n");
23752    }
23753
23754    /// The dictionaries go when the keyspace does, the same way the indexes do.
23755    #[test]
23756    fn a_flush_drops_the_dictionaries() {
23757        for flush in [b"FLUSHALL".as_slice(), b"FLUSHDB"] {
23758            let mut f = Fixture::new();
23759            f.run(&[b"FT.DICTADD", b"d", b"a"]);
23760            f.run(&[flush]);
23761            assert_eq!(f.run(&[b"FT.DICTDUMP", b"d"]), "*0\r\n", "{flush:?}");
23762        }
23763    }
23764
23765    // -------------------------------------------------------------- profile
23766
23767    /// A fixture holding one index over three documents, two of which hold the
23768    /// first word and two the second.
23769    fn profiling() -> Fixture {
23770        let mut f = Fixture::new();
23771        f.run(&[
23772            b"FT.CREATE",
23773            b"ix",
23774            b"PREFIX",
23775            b"1",
23776            b"p:",
23777            b"SCHEMA",
23778            b"t",
23779            b"TEXT",
23780            b"n",
23781            b"NUMERIC",
23782        ]);
23783        f.run(&[b"HSET", b"p:1", b"t", b"alpha", b"n", b"1"]);
23784        f.run(&[b"HSET", b"p:2", b"t", b"alpha beta", b"n", b"2"]);
23785        f.run(&[b"HSET", b"p:3", b"t", b"beta", b"n", b"3"]);
23786        f
23787    }
23788
23789    /// The reply with every time taken out of it, since no two runs agree on
23790    /// those and everything else about a profile is exact.
23791    fn timeless(reply: &str) -> String {
23792        const KEYS: &[&str] = &[
23793            "+Total profile time",
23794            "+Parsing time",
23795            "+Workers queue time",
23796            "+Pipeline creation time",
23797            "+Time",
23798        ];
23799        let mut out = String::new();
23800        let mut parts = reply.split("\r\n").peekable();
23801        while let Some(part) = parts.next() {
23802            out.push_str(part);
23803            out.push_str("\r\n");
23804            if !KEYS.contains(&part) {
23805                continue;
23806            }
23807            // A double is one line on RESP3 and a bulk header and its digits on
23808            // RESP2, and both of them stand for the same one value.
23809            match parts.next() {
23810                Some(head) if head.starts_with('$') => {
23811                    parts.next();
23812                }
23813                _ => {}
23814            }
23815            out.push_str("<t>\r\n");
23816        }
23817        // The split leaves an empty piece past the last line ending.
23818        out.truncate(out.len() - 2);
23819        out
23820    }
23821
23822    /// The whole envelope on both protocols, which is a two element array on
23823    /// one and a two key map on the other.
23824    #[test]
23825    fn a_profile_wraps_the_reply_it_would_have_answered_anyway() {
23826        let mut f = profiling();
23827        assert_eq!(
23828            timeless(&f.run(&[b"FT.PROFILE", b"ix", b"SEARCH", b"QUERY", b"alpha"])),
23829            "*2\r\n\
23830             *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\
23831             $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\
23832             *4\r\n+Shards\r\n*1\r\n*14\r\n\
23833             +Total profile time\r\n<t>\r\n+Parsing time\r\n<t>\r\n\
23834             +Workers queue time\r\n<t>\r\n+Pipeline creation time\r\n<t>\r\n\
23835             +Warning\r\n*1\r\n+None\r\n\
23836             +Iterators profile\r\n*10\r\n+Type\r\n+TEXT\r\n+Term\r\n$5\r\nalpha\r\n\
23837             +Time\r\n<t>\r\n+Number of reading operations\r\n:2\r\n\
23838             +Estimated number of matches\r\n:2\r\n\
23839             +Result processors profile\r\n*4\r\n\
23840             *6\r\n+Type\r\n+Index\r\n+Time\r\n<t>\r\n+Results processed\r\n:2\r\n\
23841             *6\r\n+Type\r\n+Scorer\r\n+Time\r\n<t>\r\n+Results processed\r\n:2\r\n\
23842             *6\r\n+Type\r\n+Sorter\r\n+Time\r\n<t>\r\n+Results processed\r\n:2\r\n\
23843             *6\r\n+Type\r\n+Loader\r\n+Time\r\n<t>\r\n+Results processed\r\n:2\r\n\
23844             +Coordinator\r\n*0\r\n"
23845        );
23846        let mut g = profiling();
23847        g.run(&[b"HELLO", b"3"]);
23848        let three = timeless(&g.run(&[b"FT.PROFILE", b"ix", b"SEARCH", b"QUERY", b"alpha"]));
23849        assert!(three.starts_with("%2\r\n+Results\r\n"), "{three}");
23850        assert!(
23851            three.contains("+Profile\r\n%2\r\n+Shards\r\n*1\r\n%7\r\n"),
23852            "{three}"
23853        );
23854        assert!(three.ends_with("+Coordinator\r\n%0\r\n"), "{three}");
23855        assert!(
23856            three.contains(
23857                "+Iterators profile\r\n%5\r\n+Type\r\n+TEXT\r\n+Term\r\n$5\r\nalpha\r\n\
23858                 +Time\r\n<t>\r\n+Number of reading operations\r\n:2\r\n\
23859                 +Estimated number of matches\r\n:2\r\n"
23860            ),
23861            "{three}"
23862        );
23863    }
23864
23865    /// Every kind of step names itself, and the three that hold other steps say
23866    /// so in the singular or the plural depending on how many they hold.
23867    #[test]
23868    fn each_kind_of_step_writes_the_keys_that_belong_to_it() {
23869        let mut f = profiling();
23870        let tree = |f: &mut Fixture, query: &[u8]| {
23871            let reply = timeless(&f.run(&[b"FT.PROFILE", b"ix", b"SEARCH", b"QUERY", query]));
23872            let at = reply.find("+Iterators profile").expect("a tree");
23873            let end = reply.find("+Result processors").expect("a list of steps");
23874            reply[at..end].to_string()
23875        };
23876        assert_eq!(
23877            tree(&mut f, b"alpha beta"),
23878            "+Iterators profile\r\n*8\r\n+Type\r\n+INTERSECT\r\n+Time\r\n<t>\r\n\
23879             +Number of reading operations\r\n:1\r\n+Child iterators\r\n*2\r\n\
23880             *10\r\n+Type\r\n+TEXT\r\n+Term\r\n$5\r\nalpha\r\n+Time\r\n<t>\r\n\
23881             +Number of reading operations\r\n:2\r\n+Estimated number of matches\r\n:2\r\n\
23882             *10\r\n+Type\r\n+TEXT\r\n+Term\r\n$4\r\nbeta\r\n+Time\r\n<t>\r\n\
23883             +Number of reading operations\r\n:1\r\n+Estimated number of matches\r\n:2\r\n"
23884        );
23885        assert!(tree(&mut f, b"alpha|beta").starts_with(
23886            "+Iterators profile\r\n*10\r\n+Type\r\n+UNION\r\n+Query type\r\n+UNION\r\n\
23887             +Time\r\n<t>\r\n+Number of reading operations\r\n:3\r\n+Child iterators\r\n*2\r\n"
23888        ));
23889        // One thing under it, named in the singular, which is a different key
23890        // and not a list holding one.
23891        assert!(tree(&mut f, b"-alpha").starts_with(
23892            "+Iterators profile\r\n*8\r\n+Type\r\n+NOT\r\n+Time\r\n<t>\r\n\
23893             +Number of reading operations\r\n:1\r\n+Child iterator\r\n*10\r\n"
23894        ));
23895        assert!(tree(&mut f, b"~alpha").starts_with(
23896            "+Iterators profile\r\n*8\r\n+Type\r\n+OPTIONAL\r\n+Time\r\n<t>\r\n\
23897             +Number of reading operations\r\n:3\r\n+Child iterator\r\n*10\r\n"
23898        ));
23899        // No guess at how many, which is the one leaf that leaves it off.
23900        assert_eq!(
23901            tree(&mut f, b"*"),
23902            "+Iterators profile\r\n*6\r\n+Type\r\n+WILDCARD\r\n+Time\r\n<t>\r\n\
23903             +Number of reading operations\r\n:3\r\n"
23904        );
23905        assert!(tree(&mut f, b"@n:[1 2]").starts_with(
23906            "+Iterators profile\r\n*10\r\n+Type\r\n+NUMERIC\r\n+Term\r\n\
23907             $19\r\n1.000000 - 2.000000\r\n"
23908        ));
23909    }
23910
23911    /// A union an expansion made folds into a count of its branches and a union
23912    /// a client wrote with a bar does not.
23913    #[test]
23914    fn limited_folds_the_branches_an_expansion_made_and_leaves_a_bar_alone() {
23915        let mut f = profiling();
23916        f.run(&[b"HSET", b"p:4", b"t", b"alps"]);
23917        let tree = |f: &mut Fixture, words: &[&[u8]]| {
23918            let mut argv: Vec<&[u8]> = vec![b"FT.PROFILE", b"ix", b"SEARCH"];
23919            argv.extend_from_slice(words);
23920            let reply = timeless(&f.run(&argv));
23921            let at = reply.find("+Iterators profile").expect("a tree");
23922            let end = reply.find("+Result processors").expect("a list of steps");
23923            reply[at..end].to_string()
23924        };
23925        assert_eq!(
23926            tree(&mut f, &[b"LIMITED", b"QUERY", b"al*"]),
23927            "+Iterators profile\r\n*10\r\n+Type\r\n+UNION\r\n\
23928             +Query type\r\n$11\r\nPREFIX - al\r\n+Time\r\n<t>\r\n\
23929             +Number of reading operations\r\n:3\r\n+Child iterators\r\n\
23930             +The number of iterators in the union is 2\r\n"
23931        );
23932        assert!(tree(&mut f, &[b"QUERY", b"al*"]).contains("+Child iterators\r\n*2\r\n"));
23933        assert!(
23934            tree(&mut f, &[b"LIMITED", b"QUERY", b"alpha|beta"])
23935                .contains("+Child iterators\r\n*2\r\n")
23936        );
23937        // A union that says nothing but its own name says it as a status, and
23938        // one that says what it stood for says that as a string. Measured, and
23939        // it is the one place in this reply where the two are told apart.
23940        assert!(tree(&mut f, &[b"QUERY", b"alpha|beta"]).contains("+Query type\r\n+UNION\r\n"));
23941        assert!(
23942            tree(&mut f, &[b"QUERY", b"al*"]).contains("+Query type\r\n$11\r\nPREFIX - al\r\n")
23943        );
23944    }
23945
23946    /// Which steps a search runs the rows through, which turns on the window,
23947    /// on whether anything asked for the fields and on what the order is.
23948    #[test]
23949    fn the_steps_a_search_runs_depend_on_what_was_asked_for() {
23950        let mut f = profiling();
23951        let steps = |f: &mut Fixture, words: &[&[u8]]| {
23952            let mut argv: Vec<&[u8]> = vec![b"FT.PROFILE", b"ix", b"SEARCH", b"QUERY", b"alpha"];
23953            argv.extend_from_slice(words);
23954            let reply = timeless(&f.run(&argv));
23955            let at = reply.find("+Result processors").expect("a list of steps");
23956            let end = reply.find("+Coordinator").expect("an end");
23957            let mut out = Vec::new();
23958            let mut parts = reply[at..end].split("\r\n").peekable();
23959            while let Some(part) = parts.next() {
23960                if part == "+Type" {
23961                    out.push(parts.next().unwrap_or_default().to_string());
23962                }
23963            }
23964            out
23965        };
23966        assert_eq!(
23967            steps(&mut f, &[]),
23968            ["+Index", "+Scorer", "+Sorter", "+Loader"]
23969        );
23970        assert_eq!(
23971            steps(&mut f, &[b"NOCONTENT"]),
23972            ["+Index", "+Scorer", "+Sorter"]
23973        );
23974        // A window of nothing is a client asking for the total and nothing
23975        // else, so nothing is scored and nothing is sorted.
23976        assert_eq!(
23977            steps(&mut f, &[b"LIMIT", b"0", b"0"]),
23978            ["+Index", "+Counter"]
23979        );
23980        // A sort by a field does not need a score, and asking for the scores
23981        // puts the step back.
23982        assert_eq!(
23983            steps(&mut f, &[b"SORTBY", b"n"]),
23984            ["+Index", "+Sorter", "+Loader"]
23985        );
23986        assert_eq!(
23987            steps(&mut f, &[b"SORTBY", b"n", b"WITHSCORES"]),
23988            ["+Index", "+Scorer", "+Sorter", "+Loader"]
23989        );
23990        assert_eq!(
23991            steps(&mut f, &[b"HIGHLIGHT"]),
23992            ["+Index", "+Scorer", "+Sorter", "+Loader", "+Highlighter"]
23993        );
23994        assert_eq!(
23995            steps(&mut f, &[b"SUMMARIZE", b"NOCONTENT"]),
23996            ["+Index", "+Scorer", "+Sorter"]
23997        );
23998    }
23999
24000    /// A pipeline names each of its steps after the expression it runs, which
24001    /// is what a real server prints beside them.
24002    #[test]
24003    fn a_pipeline_names_every_step_after_what_it_runs() {
24004        let mut f = profiling();
24005        let steps = |f: &mut Fixture, words: &[&[u8]]| {
24006            let mut argv: Vec<&[u8]> = vec![b"FT.PROFILE", b"ix", b"AGGREGATE", b"QUERY", b"*"];
24007            argv.extend_from_slice(words);
24008            let reply = timeless(&f.run(&argv));
24009            let at = reply.find("+Result processors").expect("a list of steps");
24010            let end = reply.find("+Coordinator").expect("an end");
24011            let mut out = Vec::new();
24012            let mut parts = reply[at..end].split("\r\n").peekable();
24013            while let Some(part) = parts.next() {
24014                if part == "+Type" {
24015                    out.push(parts.next().unwrap_or_default().to_string());
24016                }
24017            }
24018            out
24019        };
24020        assert_eq!(steps(&mut f, &[]), ["+Index"]);
24021        assert_eq!(
24022            steps(&mut f, &[b"APPLY", b"1", b"AS", b"one"]),
24023            ["+Index", "+Projector - Literal 1"]
24024        );
24025        assert_eq!(
24026            steps(
24027                &mut f,
24028                &[b"LOAD", b"1", b"@n", b"APPLY", b"@n * 2", b"AS", b"d"]
24029            ),
24030            ["+Index", "+Loader", "+Projector - Operator *"]
24031        );
24032        assert_eq!(
24033            steps(&mut f, &[b"LOAD", b"1", b"@n", b"FILTER", b"@n > 1"]),
24034            ["+Index", "+Loader", "+Filter - Predicate >"]
24035        );
24036        assert_eq!(
24037            steps(
24038                &mut f,
24039                &[b"GROUPBY", b"1", b"@n", b"REDUCE", b"COUNT", b"0"]
24040            ),
24041            ["+Index", "+Loader", "+Grouper"]
24042        );
24043        assert_eq!(
24044            steps(&mut f, &[b"SORTBY", b"1", b"@n"]),
24045            ["+Index", "+Loader", "+Sorter"]
24046        );
24047        assert_eq!(
24048            steps(&mut f, &[b"LIMIT", b"0", b"2"]),
24049            ["+Index", "+Pager/Limiter"]
24050        );
24051        // Asking for the score by name is a step of its own, and it goes in
24052        // front of the read rather than after it.
24053        assert_eq!(
24054            steps(
24055                &mut f,
24056                &[
24057                    b"ADDSCORES",
24058                    b"LOAD",
24059                    b"1",
24060                    b"@n",
24061                    b"APPLY",
24062                    b"@__score",
24063                    b"AS",
24064                    b"s"
24065                ]
24066            ),
24067            [
24068                "+Index",
24069                "+Scorer",
24070                "+Loader",
24071                "+Projector - Property __score"
24072            ]
24073        );
24074    }
24075
24076    /// A field the schema marked sortable is held beside the document number,
24077    /// so a pipeline that only names those never opens a key and never reports
24078    /// a read.
24079    ///
24080    /// Measured: on a schema of `n NUMERIC SORTABLE g TAG`, `LOAD 1 @n` has no
24081    /// `Loader` step and `LOAD 1 @g` has one. So does `LOAD *`, because what a
24082    /// key turns out to hold is not knowable without opening it.
24083    #[test]
24084    fn a_sortable_field_is_read_without_the_key_being_opened() {
24085        let mut f = Fixture::new();
24086        f.run(&[
24087            b"FT.CREATE",
24088            b"sx",
24089            b"PREFIX",
24090            b"1",
24091            b"s:",
24092            b"SCHEMA",
24093            b"n",
24094            b"NUMERIC",
24095            b"SORTABLE",
24096            b"g",
24097            b"TAG",
24098        ]);
24099        f.run(&[b"HSET", b"s:1", b"n", b"1", b"g", b"one"]);
24100        f.run(&[b"HSET", b"s:2", b"n", b"2", b"g", b"two"]);
24101        let loads = |f: &mut Fixture, words: &[&[u8]]| {
24102            let mut argv: Vec<&[u8]> = vec![b"FT.PROFILE", b"sx", b"AGGREGATE", b"QUERY", b"*"];
24103            argv.extend_from_slice(words);
24104            f.run(&argv).contains("+Loader")
24105        };
24106        assert!(!loads(&mut f, &[b"LOAD", b"1", b"@n"]));
24107        assert!(!loads(&mut f, &[b"SORTBY", b"1", b"@n"]));
24108        assert!(!loads(&mut f, &[b"APPLY", b"@n * 2", b"AS", b"d"]));
24109        assert!(loads(&mut f, &[b"LOAD", b"1", b"@g"]));
24110        assert!(loads(&mut f, &[b"LOAD", b"2", b"@n", b"@g"]));
24111        assert!(loads(
24112            &mut f,
24113            &[b"GROUPBY", b"1", b"@g", b"REDUCE", b"COUNT", b"0"]
24114        ));
24115        assert!(loads(&mut f, &[b"LOAD", b"*"]));
24116    }
24117
24118    /// The four ways the words can be wrong, none of which reaches the search
24119    /// underneath.
24120    #[test]
24121    fn a_profile_checks_its_own_words_before_it_runs_anything() {
24122        let mut f = profiling();
24123        assert_eq!(
24124            f.run(&[b"FT.PROFILE", b"ix", b"SEARCH", b"QUERY"]),
24125            "-ERR wrong number of arguments for 'FT.PROFILE' command\r\n"
24126        );
24127        assert_eq!(
24128            f.run(&[b"FT.PROFILE", b"ix", b"BOGUS", b"QUERY", b"alpha"]),
24129            "-No `SEARCH`, `AGGREGATE`, or `HYBRID` provided\r\n"
24130        );
24131        // The word goes between the two and nowhere else, so one written in
24132        // front of them is not the word at all.
24133        assert_eq!(
24134            f.run(&[
24135                b"FT.PROFILE",
24136                b"ix",
24137                b"LIMITED",
24138                b"SEARCH",
24139                b"QUERY",
24140                b"alpha"
24141            ]),
24142            "-No `SEARCH`, `AGGREGATE`, or `HYBRID` provided\r\n"
24143        );
24144        assert_eq!(
24145            f.run(&[b"FT.PROFILE", b"ix", b"SEARCH", b"BOGUS", b"alpha"]),
24146            "-The QUERY keyword is expected\r\n"
24147        );
24148        assert_eq!(
24149            f.run(&[
24150                b"FT.PROFILE",
24151                b"ix",
24152                b"AGGREGATE",
24153                b"QUERY",
24154                b"alpha",
24155                b"WITHCURSOR"
24156            ]),
24157            "-FT.PROFILE does not support cursor\r\n"
24158        );
24159        // And what the search itself complains about comes back on its own,
24160        // without an envelope around it saying the command worked.
24161        assert_eq!(
24162            f.run(&[b"FT.PROFILE", b"nope", b"SEARCH", b"QUERY", b"alpha"]),
24163            "-SEARCH_INDEX_NOT_FOUND Index not found: nope\r\n"
24164        );
24165        assert_eq!(
24166            f.run(&[
24167                b"FT.PROFILE",
24168                b"ix",
24169                b"SEARCH",
24170                b"QUERY",
24171                b"alpha",
24172                b"extra"
24173            ]),
24174            "-SEARCH_ARG_UNRECOGNIZED Unknown argument `extra` at position 1 for <main>\r\n"
24175        );
24176    }
24177
24178    /// Every word of the command's own is read without regard to case.
24179    #[test]
24180    fn the_words_of_a_profile_are_read_the_way_every_other_word_is() {
24181        let mut f = profiling();
24182        let one = f.run(&[
24183            b"FT.PROFILE",
24184            b"ix",
24185            b"search",
24186            b"limited",
24187            b"query",
24188            b"alpha",
24189        ]);
24190        let two = f.run(&[
24191            b"FT.PROFILE",
24192            b"ix",
24193            b"SEARCH",
24194            b"LIMITED",
24195            b"QUERY",
24196            b"alpha",
24197        ]);
24198        assert_eq!(timeless(&one), timeless(&two));
24199    }
24200
24201    // -------------------------------------------------------------- dropping
24202
24203    /// The two spellings take opposite defaults, which is measured and is the
24204    /// only difference between them that a client can see.
24205    #[test]
24206    fn the_two_ways_of_dropping_an_index_disagree_about_the_documents() {
24207        let mut f = profiling();
24208        assert_eq!(f.run(&[b"FT.DROPINDEX", b"ix"]), "+OK\r\n");
24209        assert_eq!(f.run(&[b"EXISTS", b"p:1", b"p:2", b"p:3"]), ":3\r\n");
24210
24211        let mut f = profiling();
24212        assert_eq!(f.run(&[b"FT.DROPINDEX", b"ix", b"DD"]), "+OK\r\n");
24213        assert_eq!(f.run(&[b"EXISTS", b"p:1", b"p:2", b"p:3"]), ":0\r\n");
24214
24215        let mut f = profiling();
24216        assert_eq!(f.run(&[b"FT.DROP", b"ix"]), "+OK\r\n");
24217        assert_eq!(f.run(&[b"EXISTS", b"p:1", b"p:2", b"p:3"]), ":0\r\n");
24218
24219        let mut f = profiling();
24220        assert_eq!(f.run(&[b"FT.DROP", b"ix", b"KEEPDOCS"]), "+OK\r\n");
24221        assert_eq!(f.run(&[b"EXISTS", b"p:1", b"p:2", b"p:3"]), ":3\r\n");
24222    }
24223
24224    /// Each spelling takes its own word and refuses the other one's, which
24225    /// reads as an oversight and is what a real server answers.
24226    #[test]
24227    fn neither_way_of_dropping_an_index_takes_the_other_ones_word() {
24228        let mut f = profiling();
24229        let line = "-SEARCH_ARG_UNRECOGNIZED Unknown argument\r\n";
24230        assert_eq!(f.run(&[b"FT.DROPINDEX", b"ix", b"KEEPDOCS"]), line);
24231        assert_eq!(f.run(&[b"FT.DROP", b"ix", b"DD"]), line);
24232        // Refused rather than half done, so the index is still there.
24233        assert_eq!(f.run(&[b"FT._LIST"]), "*1\r\n+ix\r\n");
24234    }
24235
24236    /// Only what the index read is deleted, which is not the same as
24237    /// everything under its prefix.
24238    #[test]
24239    fn dropping_the_documents_leaves_a_key_the_index_never_read() {
24240        let mut f = profiling();
24241        f.run(&[b"SET", b"p:4", b"alpha"]);
24242        f.run(&[b"HSET", b"q:1", b"t", b"alpha"]);
24243        assert_eq!(f.run(&[b"FT.DROPINDEX", b"ix", b"DD"]), "+OK\r\n");
24244        assert_eq!(f.run(&[b"EXISTS", b"p:1", b"p:2", b"p:3"]), ":0\r\n");
24245        assert_eq!(f.run(&[b"EXISTS", b"p:4", b"q:1"]), ":2\r\n");
24246    }
24247
24248    /// An index still standing over the same keys hears about them going,
24249    /// rather than answering later with keys that are not there.
24250    #[test]
24251    fn another_index_over_the_same_keys_loses_the_documents_too() {
24252        let mut f = profiling();
24253        f.run(&[
24254            b"FT.CREATE",
24255            b"other",
24256            b"PREFIX",
24257            b"1",
24258            b"p:",
24259            b"SCHEMA",
24260            b"t",
24261            b"TEXT",
24262        ]);
24263        assert_eq!(f.run(&[b"FT.DROPINDEX", b"ix", b"DD"]), "+OK\r\n");
24264        assert_eq!(
24265            f.run(&[b"FT.SEARCH", b"other", b"alpha", b"NOCONTENT"]),
24266            "*1\r\n:0\r\n"
24267        );
24268    }
24269
24270    /// A drop that found nothing to drop deletes nothing either, which is the
24271    /// one case where the shortcut spelling answers `OK` without a sweep.
24272    #[test]
24273    fn a_drop_of_an_index_that_is_not_there_touches_no_keys() {
24274        let mut f = profiling();
24275        assert_eq!(f.run(&[b"FT._DROPINDEXIFX", b"nope", b"DD"]), "+OK\r\n");
24276        assert_eq!(f.run(&[b"EXISTS", b"p:1", b"p:2", b"p:3"]), ":3\r\n");
24277        assert_eq!(f.run(&[b"FT._DROPIFX", b"nope"]), "+OK\r\n");
24278        assert_eq!(f.run(&[b"EXISTS", b"p:1", b"p:2", b"p:3"]), ":3\r\n");
24279    }
24280
24281    // --------------------------------------------------------------- config
24282
24283    /// The two shapes a dump comes back in, which are the one mix of simple
24284    /// strings and bulk strings the group sends.
24285    #[test]
24286    fn a_setting_reads_back_as_a_pair_on_one_protocol_and_a_map_on_the_other() {
24287        let mut f = Fixture::new();
24288        assert_eq!(
24289            f.run(&[b"FT.CONFIG", b"GET", b"TIMEOUT"]),
24290            "*1\r\n*2\r\n+TIMEOUT\r\n$3\r\n500\r\n"
24291        );
24292        assert_eq!(
24293            f.run(&[b"FT.CONFIG", b"GET", b"EXTLOAD"]),
24294            "*1\r\n*2\r\n+EXTLOAD\r\n$-1\r\n"
24295        );
24296        let mut g = Fixture::new();
24297        g.run(&[b"HELLO", b"3"]);
24298        assert_eq!(
24299            g.run(&[b"FT.CONFIG", b"GET", b"TIMEOUT"]),
24300            "%1\r\n+TIMEOUT\r\n$3\r\n500\r\n"
24301        );
24302        assert_eq!(
24303            g.run(&[b"FT.CONFIG", b"GET", b"EXTLOAD"]),
24304            "%1\r\n+EXTLOAD\r\n_\r\n"
24305        );
24306    }
24307
24308    /// The help text rides along in the middle of the same row, flat on RESP2
24309    /// and as a map of its own on RESP3.
24310    #[test]
24311    fn a_help_row_carries_the_description_and_the_value_together() {
24312        let mut f = Fixture::new();
24313        assert_eq!(
24314            f.run(&[b"FT.CONFIG", b"HELP", b"TIMEOUT"]),
24315            "*1\r\n*5\r\n+TIMEOUT\r\n+Description\r\n+Query (search) timeout\r\n\
24316             +Value\r\n$3\r\n500\r\n"
24317        );
24318        let mut g = Fixture::new();
24319        g.run(&[b"HELLO", b"3"]);
24320        assert_eq!(
24321            g.run(&[b"FT.CONFIG", b"HELP", b"TIMEOUT"]),
24322            "%1\r\n+TIMEOUT\r\n%2\r\n+Description\r\n+Query (search) timeout\r\n\
24323             +Value\r\n$3\r\n500\r\n"
24324        );
24325    }
24326
24327    /// A name is matched whole, ignoring case, and the single word star is the
24328    /// only thing that means all of them.
24329    #[test]
24330    fn only_a_bare_star_asks_for_every_setting_and_nothing_else_globs() {
24331        let mut f = Fixture::new();
24332        assert_eq!(
24333            f.run(&[b"FT.CONFIG", b"GET", b"timeout"]),
24334            "*1\r\n*2\r\n+TIMEOUT\r\n$3\r\n500\r\n"
24335        );
24336        for name in [
24337            b"TIMEOUT*".as_slice(),
24338            b"?IMEOUT",
24339            b"*TIMEOUT*",
24340            b"TIME",
24341            b"NOSUCH",
24342            b"",
24343        ] {
24344            assert_eq!(f.run(&[b"FT.CONFIG", b"GET", name]), "*0\r\n", "{name:?}");
24345        }
24346        assert!(f.run(&[b"FT.CONFIG", b"GET", b"*"]).starts_with("*69\r\n"));
24347        assert!(f.run(&[b"FT.CONFIG", b"HELP", b"*"]).starts_with("*69\r\n"));
24348    }
24349
24350    /// Words after the name are stepped over rather than refused, on both of
24351    /// the two reads.
24352    #[test]
24353    fn a_read_ignores_whatever_follows_the_name() {
24354        let mut f = Fixture::new();
24355        assert_eq!(
24356            f.run(&[b"FT.CONFIG", b"GET", b"timeout", b"extra", b"more"]),
24357            "*1\r\n*2\r\n+TIMEOUT\r\n$3\r\n500\r\n"
24358        );
24359        assert_eq!(
24360            f.run(&[b"FT.CONFIG", b"HELP", b"timeout", b"extra"]),
24361            "*1\r\n*5\r\n+TIMEOUT\r\n+Description\r\n+Query (search) timeout\r\n\
24362             +Value\r\n$3\r\n500\r\n"
24363        );
24364    }
24365
24366    /// The container reports its own name and the subcommand it was given in
24367    /// the two lines the dispatcher writes.
24368    #[test]
24369    fn a_missing_subcommand_and_a_missing_name_are_told_apart() {
24370        let mut f = Fixture::new();
24371        assert_eq!(
24372            f.run(&[b"FT.CONFIG"]),
24373            "-ERR wrong number of arguments for 'FT.CONFIG' command\r\n"
24374        );
24375        for sub in [b"GET".as_slice(), b"SET", b"HELP"] {
24376            let want = format!(
24377                "-ERR wrong number of arguments for 'FT.CONFIG|{}' command\r\n",
24378                String::from_utf8_lossy(sub)
24379            );
24380            assert_eq!(f.run(&[b"FT.CONFIG", sub]), want);
24381        }
24382        assert_eq!(
24383            f.run(&[b"ft.config", b"get"]),
24384            "-ERR wrong number of arguments for 'FT.CONFIG|GET' command\r\n"
24385        );
24386        assert_eq!(
24387            f.run(&[b"FT.CONFIG", b"bogus"]),
24388            "-ERR unknown subcommand 'bogus'. Try FT.CONFIG HELP.\r\n"
24389        );
24390    }
24391
24392    /// The name, then whether it can move, then the value, then the count of
24393    /// words, and each of the first three answers before the next is looked at.
24394    #[test]
24395    fn a_write_checks_the_name_then_the_setting_then_the_value() {
24396        let mut f = Fixture::new();
24397        for tail in [vec![b"1".as_slice()], vec![], vec![b"1", b"2", b"3"]] {
24398            let mut cmd: Vec<&[u8]> = vec![b"FT.CONFIG", b"SET", b"NOSUCH"];
24399            cmd.extend(tail);
24400            assert_eq!(f.run(&cmd), "-SEARCH_OPTION_INVALID Invalid option\r\n");
24401        }
24402        for tail in [vec![b"1000".as_slice()], vec![], vec![b"x", b"y"]] {
24403            let mut cmd: Vec<&[u8]> = vec![b"FT.CONFIG", b"SET", b"MAXDOCTABLESIZE"];
24404            cmd.extend(tail);
24405            assert_eq!(
24406                f.run(&cmd),
24407                "-SEARCH_OPTION_BAD Not modifiable at runtime\r\n"
24408            );
24409        }
24410        assert_eq!(
24411            f.run(&[b"FT.CONFIG", b"SET", b"TIMEOUT", b"x", b"y", b"z"]),
24412            "-SEARCH_PARSE_ARGS Could not convert argument to expected type\r\n"
24413        );
24414    }
24415
24416    /// Too many words is a status and not an error, and the value has already
24417    /// been written by the time it goes out.
24418    #[test]
24419    fn an_excess_of_words_is_noticed_after_the_value_is_kept() {
24420        let mut f = Fixture::new();
24421        assert_eq!(
24422            f.run(&[b"FT.CONFIG", b"SET", b"TIMEOUT", b"500"]),
24423            "+OK\r\n"
24424        );
24425        assert_eq!(
24426            f.run(&[b"FT.CONFIG", b"SET", b"TIMEOUT", b"600", b"junk"]),
24427            "+EXCESSARGS\r\n"
24428        );
24429        assert_eq!(
24430            f.run(&[b"FT.CONFIG", b"GET", b"TIMEOUT"]),
24431            "*1\r\n*2\r\n+TIMEOUT\r\n$3\r\n600\r\n"
24432        );
24433    }
24434
24435    /// Strictly first and loosely second, so a hexadecimal and a leading zero
24436    /// and an exponent all land and a fraction does not.
24437    #[test]
24438    fn a_number_is_read_the_strict_way_and_then_the_loose_one() {
24439        let mut f = Fixture::new();
24440        for (given, want) in [
24441            (b"0x10".as_slice(), "16"),
24442            (b"0X1f", "31"),
24443            (b"+0x10", "16"),
24444            (b"+5", "5"),
24445            (b"010", "10"),
24446            (b"08", "8"),
24447            (b"0777", "777"),
24448            (b"1e3", "1000"),
24449            (b"0.0", "0"),
24450            (b"-0.0", "0"),
24451        ] {
24452            assert_eq!(
24453                f.run(&[b"FT.CONFIG", b"SET", b"TIMEOUT", given]),
24454                "+OK\r\n",
24455                "{given:?}"
24456            );
24457            let want = format!("*1\r\n*2\r\n+TIMEOUT\r\n${}\r\n{want}\r\n", want.len());
24458            assert_eq!(
24459                f.run(&[b"FT.CONFIG", b"GET", b"TIMEOUT"]),
24460                want,
24461                "{given:?}"
24462            );
24463        }
24464        for given in [
24465            b" 5".as_slice(),
24466            b"5 ",
24467            b"1.5",
24468            b"1e-3",
24469            b"x",
24470            b"",
24471            b"0b11",
24472            b"0xg",
24473            b"nan",
24474            b"inf",
24475            b"1e100",
24476            b"99999999999999999999",
24477        ] {
24478            assert_eq!(
24479                f.run(&[b"FT.CONFIG", b"SET", b"TIMEOUT", given]),
24480                "-SEARCH_PARSE_ARGS Could not convert argument to expected type\r\n",
24481                "{given:?}"
24482            );
24483        }
24484    }
24485
24486    /// Which of the two readers found a negative decides what it is told, and
24487    /// on a setting with no range at all neither of them is refused.
24488    #[test]
24489    fn a_negative_is_answered_by_whichever_reader_found_it() {
24490        let mut f = Fixture::new();
24491        for given in [b"-1".as_slice(), b"-16"] {
24492            assert_eq!(
24493                f.run(&[b"FT.CONFIG", b"SET", b"TIMEOUT", given]),
24494                "-SEARCH_PARSE_ARGS Value is outside acceptable bounds\r\n",
24495                "{given:?}"
24496            );
24497        }
24498        for given in [b"-0x10".as_slice(), b"-1e3", b"-010", b"-2.0"] {
24499            assert_eq!(
24500                f.run(&[b"FT.CONFIG", b"SET", b"TIMEOUT", given]),
24501                "-SEARCH_PARSE_ARGS Could not convert argument to expected type\r\n",
24502                "{given:?}"
24503            );
24504        }
24505        let unlimited = "*1\r\n*2\r\n+MAXSEARCHRESULTS\r\n$9\r\nunlimited\r\n";
24506        for given in [b"-1".as_slice(), b"-0x10", b"-1e3", b"-010"] {
24507            assert_eq!(
24508                f.run(&[b"FT.CONFIG", b"SET", b"MAXSEARCHRESULTS", given]),
24509                "+OK\r\n",
24510                "{given:?}"
24511            );
24512            assert_eq!(
24513                f.run(&[b"FT.CONFIG", b"GET", b"MAXSEARCHRESULTS"]),
24514                unlimited,
24515                "{given:?}"
24516            );
24517        }
24518    }
24519
24520    /// The two settings with no range truncate into a signed thirty two bit
24521    /// slot and say so once the number has gone under.
24522    #[test]
24523    fn a_wide_setting_wraps_into_its_slot_before_it_is_read_back() {
24524        let mut f = Fixture::new();
24525        for (given, want) in [
24526            (b"2147483647".as_slice(), "2147483647"),
24527            (b"2147483648", "unlimited"),
24528            (b"4294967295", "unlimited"),
24529            (b"9223372036854775806", "unlimited"),
24530            (b"0", "0"),
24531        ] {
24532            assert_eq!(
24533                f.run(&[b"FT.CONFIG", b"SET", b"MAXSEARCHRESULTS", given]),
24534                "+OK\r\n",
24535                "{given:?}"
24536            );
24537            let want = format!(
24538                "*1\r\n*2\r\n+MAXSEARCHRESULTS\r\n${}\r\n{want}\r\n",
24539                want.len()
24540            );
24541            assert_eq!(
24542                f.run(&[b"FT.CONFIG", b"GET", b"MAXSEARCHRESULTS"]),
24543                want,
24544                "{given:?}"
24545            );
24546        }
24547    }
24548
24549    /// A number past what a setting will take says which way it went, and the
24550    /// ones with a softer roof of their own say what that roof is about.
24551    #[test]
24552    fn a_number_out_of_range_names_the_limit_it_crossed() {
24553        let mut f = Fixture::new();
24554        let bounds = "-SEARCH_PARSE_ARGS Value is outside acceptable bounds\r\n";
24555        for (name, given) in [
24556            (b"MINPREFIX".as_slice(), b"0".as_slice()),
24557            (b"MAX_AGGREGATE_GROUPS", b"0"),
24558            (b"BM25STD_TANH_FACTOR", b"0"),
24559            (b"DEFAULT_DIALECT", b"0"),
24560            (b"MINSTEMLEN", b"4294967296"),
24561            (b"_BG_INDEX_OOM_PAUSE_TIME", b"4294967296"),
24562            (b"INDEXER_YIELD_EVERY_OPS", b"4294967296"),
24563            (b"CONNECT_TIMEOUT", b"2147483648"),
24564        ] {
24565            assert_eq!(
24566                f.run(&[b"FT.CONFIG", b"SET", name, given]),
24567                bounds,
24568                "{name:?}"
24569            );
24570        }
24571        for (name, given, want) in [
24572            (
24573                b"MINSTEMLEN".as_slice(),
24574                b"1".as_slice(),
24575                "-SEARCH_SYNTAX Minimum stem length cannot be lower than 2\r\n",
24576            ),
24577            (
24578                b"MAX_AGGREGATE_GROUPS",
24579                b"67108865",
24580                "-SEARCH_LIMIT_OVER Value exceeds maximum possible aggregate groups\r\n",
24581            ),
24582            (
24583                b"WORKERS",
24584                b"17",
24585                "-SEARCH_LIMIT_OVER Number of worker threads cannot exceed 16\r\n",
24586            ),
24587            (
24588                b"_NUMERIC_RANGES_PARENTS",
24589                b"3",
24590                "-SEARCH_PARSE_ARGS Max depth for range cannot be higher than max \
24591                 depth for balance\r\n",
24592            ),
24593            (
24594                b"DEFAULT_DIALECT",
24595                b"5",
24596                "-SEARCH_VALUE_BAD Default dialect version cannot be higher than 4\r\n",
24597            ),
24598            (
24599                b"_BG_INDEX_MEM_PCT_THR",
24600                b"101",
24601                "-SEARCH_LIMIT_OVER Memory limit for indexing cannot be greater then \
24602                 100%\r\n",
24603            ),
24604            (
24605                b"BM25STD_TANH_FACTOR",
24606                b"10001",
24607                "-SEARCH_LIMIT_OVER BM25STD_TANH_FACTOR must be between 1 and 10000 \
24608                 inclusive\r\n",
24609            ),
24610            (
24611                b"BG_INDEX_SLEEP_DURATION_US",
24612                b"1000000",
24613                "-SEARCH_LIMIT_OVER BG_INDEX_SLEEP_DURATION_US must be between 1 and \
24614                 999999 (usleep POSIX limit)\r\n",
24615            ),
24616        ] {
24617            assert_eq!(
24618                f.run(&[b"FT.CONFIG", b"SET", name, given]),
24619                want,
24620                "{name:?}"
24621            );
24622        }
24623    }
24624
24625    /// The two trimming delays are measured against each other, and the answer
24626    /// names both settings and both numbers.
24627    #[test]
24628    fn the_trimming_delays_are_checked_against_one_another() {
24629        let mut f = Fixture::new();
24630        assert_eq!(
24631            f.run(&[b"FT.CONFIG", b"SET", b"_MIN_TRIM_DELAY_MS", b"5000"]),
24632            "-SEARCH_PARSE_ARGS _MIN_TRIM_DELAY_MS (5000) must be less than \
24633             _MAX_TRIM_DELAY_MS (5000)\r\n"
24634        );
24635        assert_eq!(
24636            f.run(&[b"FT.CONFIG", b"SET", b"_MAX_TRIM_DELAY_MS", b"1999"]),
24637            "-SEARCH_PARSE_ARGS _MAX_TRIM_DELAY_MS (1999) must be greater than \
24638             _MIN_TRIM_DELAY_MS (2000)\r\n"
24639        );
24640        assert_eq!(
24641            f.run(&[b"FT.CONFIG", b"SET", b"_MIN_TRIM_DELAY_MS", b"4999"]),
24642            "+OK\r\n"
24643        );
24644    }
24645
24646    /// Two of the word settings fold the spelling on the way in and the scorer
24647    /// does not, which is the one place in the table case counts.
24648    #[test]
24649    fn a_word_setting_folds_where_a_real_server_folds_and_not_otherwise() {
24650        let mut f = Fixture::new();
24651        assert_eq!(
24652            f.run(&[b"FT.CONFIG", b"SET", b"ON_TIMEOUT", b"RETURN"]),
24653            "+OK\r\n"
24654        );
24655        assert_eq!(
24656            f.run(&[b"FT.CONFIG", b"GET", b"ON_TIMEOUT"]),
24657            "*1\r\n*2\r\n+ON_TIMEOUT\r\n$6\r\nreturn\r\n"
24658        );
24659        assert_eq!(
24660            f.run(&[b"FT.CONFIG", b"SET", b"ON_TIMEOUT", b"nope"]),
24661            "-SEARCH_VALUE_BAD Invalid ON_TIMEOUT value\r\n"
24662        );
24663        assert_eq!(
24664            f.run(&[b"FT.CONFIG", b"SET", b"ON_OOM", b"IGNORE"]),
24665            "+OK\r\n"
24666        );
24667        assert_eq!(
24668            f.run(&[b"FT.CONFIG", b"GET", b"ON_OOM"]),
24669            "*1\r\n*2\r\n+ON_OOM\r\n$6\r\nignore\r\n"
24670        );
24671        assert_eq!(
24672            f.run(&[b"FT.CONFIG", b"SET", b"ON_OOM", b"nope"]),
24673            "-SEARCH_VALUE_BAD Invalid ON_OOM value\r\n"
24674        );
24675        let bad = "-SEARCH_VALUE_BAD Invalid default scorer value\r\n";
24676        for given in [b"bm25std".as_slice(), b"Bm25", b"TFIDF.docnorm", b""] {
24677            assert_eq!(
24678                f.run(&[b"FT.CONFIG", b"SET", b"DEFAULT_SCORER", given]),
24679                bad,
24680                "{given:?}"
24681            );
24682        }
24683        assert_eq!(
24684            f.run(&[b"FT.CONFIG", b"SET", b"DEFAULT_SCORER", b"TFIDF.DOCNORM"]),
24685            "+OK\r\n"
24686        );
24687    }
24688
24689    /// True and false, either case, and none of the other words a client might
24690    /// reach for.
24691    #[test]
24692    fn a_yes_or_no_setting_takes_those_two_words_only() {
24693        let mut f = Fixture::new();
24694        assert_eq!(
24695            f.run(&[b"FT.CONFIG", b"SET", b"_NUMERIC_COMPRESS", b"TRUE"]),
24696            "+OK\r\n"
24697        );
24698        assert_eq!(
24699            f.run(&[b"FT.CONFIG", b"GET", b"_NUMERIC_COMPRESS"]),
24700            "*1\r\n*2\r\n+_NUMERIC_COMPRESS\r\n$4\r\ntrue\r\n"
24701        );
24702        for given in [b"yes".as_slice(), b"no", b"1", b"0", b"enabled", b""] {
24703            assert_eq!(
24704                f.run(&[b"FT.CONFIG", b"SET", b"_NUMERIC_COMPRESS", given]),
24705                "-SEARCH_PARSE_ARGS Could not convert argument to expected type\r\n",
24706                "{given:?}"
24707            );
24708        }
24709    }
24710
24711    /// Two pairs of names sit over one number each, and one of that second pair
24712    /// takes no value at all.
24713    #[test]
24714    fn two_names_for_one_setting_move_together() {
24715        let mut f = Fixture::new();
24716        f.run(&[b"FT.CONFIG", b"SET", b"MAXEXPANSIONS", b"300"]);
24717        assert_eq!(
24718            f.run(&[b"FT.CONFIG", b"GET", b"MAXPREFIXEXPANSIONS"]),
24719            "*1\r\n*2\r\n+MAXPREFIXEXPANSIONS\r\n$3\r\n300\r\n"
24720        );
24721        f.run(&[b"FT.CONFIG", b"SET", b"MAXPREFIXEXPANSIONS", b"200"]);
24722        assert_eq!(
24723            f.run(&[b"FT.CONFIG", b"GET", b"MAXEXPANSIONS"]),
24724            "*1\r\n*2\r\n+MAXEXPANSIONS\r\n$3\r\n200\r\n"
24725        );
24726        let long = b"_FORK_GC_CLEAN_NUMERIC_EMPTY_NODES".as_slice();
24727        let short = b"FORK_GC_CLEAN_NUMERIC_EMPTY_NODES".as_slice();
24728        f.run(&[b"FT.CONFIG", b"SET", long, b"false"]);
24729        assert_eq!(
24730            f.run(&[b"FT.CONFIG", b"GET", short]),
24731            "*1\r\n*2\r\n+FORK_GC_CLEAN_NUMERIC_EMPTY_NODES\r\n$5\r\nfalse\r\n"
24732        );
24733        assert_eq!(f.run(&[b"FT.CONFIG", b"SET", short]), "+OK\r\n");
24734        assert_eq!(
24735            f.run(&[b"FT.CONFIG", b"GET", long]),
24736            "*1\r\n*2\r\n+_FORK_GC_CLEAN_NUMERIC_EMPTY_NODES\r\n$4\r\ntrue\r\n"
24737        );
24738    }
24739
24740    /// The one setting that takes a write and never gives it back.
24741    #[test]
24742    fn a_password_reads_back_as_stars_whatever_was_written() {
24743        let mut f = Fixture::new();
24744        assert_eq!(
24745            f.run(&[b"FT.CONFIG", b"SET", b"OSS_GLOBAL_PASSWORD", b"hunter2"]),
24746            "+OK\r\n"
24747        );
24748        assert_eq!(
24749            f.run(&[b"FT.CONFIG", b"GET", b"OSS_GLOBAL_PASSWORD"]),
24750            "*1\r\n*2\r\n+OSS_GLOBAL_PASSWORD\r\n$17\r\nPassword: *******\r\n"
24751        );
24752    }
24753
24754    /// The settings are not in the keyspace, so unlike the dictionaries and the
24755    /// synonym groups beside them they live through an emptied one.
24756    #[test]
24757    fn a_flush_leaves_the_settings_alone() {
24758        for flush in [b"FLUSHALL".as_slice(), b"FLUSHDB"] {
24759            let mut f = Fixture::new();
24760            f.run(&[b"FT.CONFIG", b"SET", b"TIMEOUT", b"777"]);
24761            f.run(&[flush]);
24762            assert_eq!(
24763                f.run(&[b"FT.CONFIG", b"GET", b"TIMEOUT"]),
24764                "*1\r\n*2\r\n+TIMEOUT\r\n$3\r\n777\r\n",
24765                "{flush:?}"
24766            );
24767        }
24768    }
24769
24770    // ---------------------------------------------------------------- debug
24771
24772    /// A small index with one of everything a dump can read, so the tests below
24773    /// all name the same three documents and the same four fields.
24774    fn debugging() -> Fixture {
24775        let mut f = Fixture::new();
24776        f.run(&[
24777            b"FT.CREATE",
24778            b"dx",
24779            b"PREFIX",
24780            b"1",
24781            b"d:",
24782            b"SCHEMA",
24783            b"t",
24784            b"TEXT",
24785            b"g",
24786            b"TAG",
24787            b"n",
24788            b"NUMERIC",
24789            b"s",
24790            b"TEXT",
24791            b"SORTABLE",
24792        ]);
24793        f.run(&[
24794            b"HSET",
24795            b"d:1",
24796            b"t",
24797            b"running dogs",
24798            b"g",
24799            b"red,blue",
24800            b"n",
24801            b"1",
24802            b"s",
24803            b"Alpha",
24804        ]);
24805        f.run(&[
24806            b"HSET", b"d:2", b"t", b"running", b"g", b"red", b"n", b"2", b"s", b"beta",
24807        ]);
24808        f.run(&[
24809            b"HSET",
24810            b"d:3",
24811            b"t",
24812            b"dogs alpha",
24813            b"g",
24814            b"green",
24815            b"n",
24816            b"3",
24817        ]);
24818        f
24819    }
24820
24821    /// The whole dictionary in byte order, with the stems in it as entries of
24822    /// their own rather than hidden behind the words they came from.
24823    #[test]
24824    fn a_term_dump_lists_the_stems_beside_the_words() {
24825        let mut f = debugging();
24826        assert_eq!(
24827            f.run(&[b"_FT.DEBUG", b"DUMP_TERMS", b"dx"]),
24828            "*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\
24829             $4\r\ndogs\r\n$7\r\nrunning\r\n"
24830        );
24831    }
24832
24833    /// A posting list is looked up on the bytes given and nothing folds them, so
24834    /// the term that a query would have found is not the term a dump wants.
24835    #[test]
24836    fn a_posting_list_is_read_by_the_bytes_and_not_by_the_word() {
24837        let mut f = debugging();
24838        assert_eq!(
24839            f.run(&[b"_FT.DEBUG", b"DUMP_INVIDX", b"dx", b"running"]),
24840            "*2\r\n:1\r\n:2\r\n"
24841        );
24842        assert_eq!(
24843            f.run(&[b"_FT.DEBUG", b"DUMP_INVIDX", b"dx", b"+run"]),
24844            "*2\r\n:1\r\n:2\r\n"
24845        );
24846        for term in [b"RUNNING".as_slice(), b"nosuchterm", b""] {
24847            assert_eq!(
24848                f.run(&[b"_FT.DEBUG", b"DUMP_INVIDX", b"dx", term]),
24849                "-Can not find the inverted index\r\n",
24850                "{term:?}"
24851            );
24852        }
24853    }
24854
24855    /// Tag values come back folded and in byte order, each with the documents
24856    /// that hold it, and a document with two values is under both of them.
24857    #[test]
24858    fn a_tag_dump_pairs_every_value_with_its_documents() {
24859        let mut f = debugging();
24860        assert_eq!(
24861            f.run(&[b"_FT.DEBUG", b"DUMP_TAGIDX", b"dx", b"g"]),
24862            "*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\
24863             *2\r\n$3\r\nred\r\n*2\r\n:1\r\n:2\r\n"
24864        );
24865    }
24866
24867    /// One list holding every document in the field, which is D-96: a range tree
24868    /// answers one list per range and this answers the one it keeps.
24869    #[test]
24870    fn a_number_dump_answers_a_single_range() {
24871        let mut f = debugging();
24872        assert_eq!(
24873            f.run(&[b"_FT.DEBUG", b"DUMP_NUMIDX", b"dx", b"n"]),
24874            "*1\r\n*3\r\n:1\r\n:2\r\n:3\r\n"
24875        );
24876    }
24877
24878    /// A point is a number underneath, so the field that holds points answers
24879    /// the subcommand that dumps numbers and not the one that dumps tags.
24880    #[test]
24881    fn a_geo_field_is_dumped_as_a_numeric_one() {
24882        let mut f = Fixture::new();
24883        f.run(&[
24884            b"FT.CREATE",
24885            b"gx",
24886            b"PREFIX",
24887            b"1",
24888            b"q:",
24889            b"SCHEMA",
24890            b"loc",
24891            b"GEO",
24892            b"gg",
24893            b"AS",
24894            b"tag",
24895            b"TAG",
24896        ]);
24897        f.run(&[b"HSET", b"q:1", b"loc", b"1,2", b"gg", b"red"]);
24898        f.run(&[b"HSET", b"q:2", b"loc", b"3,4", b"gg", b"BLUE"]);
24899        assert_eq!(
24900            f.run(&[b"_FT.DEBUG", b"DUMP_NUMIDX", b"gx", b"loc"]),
24901            "*1\r\n*2\r\n:1\r\n:2\r\n"
24902        );
24903        assert_eq!(
24904            f.run(&[b"_FT.DEBUG", b"DUMP_TAGIDX", b"gx", b"loc"]),
24905            "-Could not find given field in index spec\r\n"
24906        );
24907    }
24908
24909    /// A field is named the way a query names it, so the attribute is the name
24910    /// and the identifier the value was read from is not one.
24911    #[test]
24912    fn a_dump_takes_the_attribute_and_not_the_identifier() {
24913        let mut f = Fixture::new();
24914        f.run(&[
24915            b"FT.CREATE",
24916            b"zx",
24917            b"PREFIX",
24918            b"1",
24919            b"z:",
24920            b"SCHEMA",
24921            b"gg",
24922            b"AS",
24923            b"tag",
24924            b"TAG",
24925        ]);
24926        f.run(&[b"HSET", b"z:1", b"gg", b"red"]);
24927        assert_eq!(
24928            f.run(&[b"_FT.DEBUG", b"DUMP_TAGIDX", b"zx", b"tag"]),
24929            "*1\r\n*2\r\n$3\r\nred\r\n*1\r\n:1\r\n"
24930        );
24931        assert_eq!(
24932            f.run(&[b"_FT.DEBUG", b"DUMP_TAGIDX", b"zx", b"gg"]),
24933            "-Could not find given field in index spec\r\n"
24934        );
24935    }
24936
24937    /// The seven keys, with the score as a bulk string here and a double there,
24938    /// and the whole row flat on one protocol and a map on the other.
24939    #[test]
24940    fn a_document_row_is_flat_on_one_protocol_and_a_map_on_the_other() {
24941        let mut f = debugging();
24942        assert_eq!(
24943            f.run(&[b"_FT.DEBUG", b"DOCINFO", b"dx", b"d:1", b"REVEAL"]),
24944            "*14\r\n+internal_id\r\n:1\r\n$5\r\nflags\r\n\
24945             $36\r\n(0xc):HasSortVector,HasOffsetVector,\r\n+score\r\n$1\r\n1\r\n\
24946             +num_tokens\r\n:3\r\n+max_freq\r\n:1\r\n+refcount\r\n:1\r\n\
24947             +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\
24948             $5\r\nvalue\r\n$5\r\nalpha\r\n"
24949        );
24950        let mut g = debugging();
24951        g.run(&[b"HELLO", b"3"]);
24952        assert_eq!(
24953            g.run(&[b"_FT.DEBUG", b"DOCINFO", b"dx", b"d:1", b"REVEAL"]),
24954            "%7\r\n+internal_id\r\n:1\r\n$5\r\nflags\r\n\
24955             $36\r\n(0xc):HasSortVector,HasOffsetVector,\r\n+score\r\n,1\r\n\
24956             +num_tokens\r\n:3\r\n+max_freq\r\n:1\r\n+refcount\r\n:1\r\n\
24957             +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\
24958             $5\r\nvalue\r\n$5\r\nalpha\r\n"
24959        );
24960    }
24961
24962    /// A document that wrote nothing into a sortable slot has no sortables key
24963    /// at all, so the row is a key shorter rather than carrying an empty list.
24964    #[test]
24965    fn a_document_with_no_sortable_value_drops_the_key() {
24966        let mut f = debugging();
24967        assert_eq!(
24968            f.run(&[b"_FT.DEBUG", b"DOCINFO", b"dx", b"d:3", b"REVEAL"]),
24969            "*12\r\n+internal_id\r\n:3\r\n$5\r\nflags\r\n$22\r\n(0x8):HasOffsetVector,\r\n\
24970             +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"
24971        );
24972    }
24973
24974    /// The flag word is the number and then the names it stands for, and an
24975    /// index built without offsets has none of the three set.
24976    #[test]
24977    fn the_flag_word_spells_out_the_bits_it_carries() {
24978        let mut f = Fixture::new();
24979        f.run(&[
24980            b"FT.CREATE",
24981            b"nx",
24982            b"NOOFFSETS",
24983            b"PREFIX",
24984            b"1",
24985            b"o:",
24986            b"SCHEMA",
24987            b"t",
24988            b"TEXT",
24989        ]);
24990        f.run(&[b"HSET", b"o:1", b"t", b"alpha"]);
24991        assert!(
24992            f.run(&[b"_FT.DEBUG", b"DOCINFO", b"nx", b"o:1", b"REVEAL"])
24993                .contains("$6\r\n(0x0):\r\n")
24994        );
24995    }
24996
24997    /// Obfuscation replaces the field name with where the field sits in the
24998    /// whole schema, which is not where its value sits among the sortables.
24999    #[test]
25000    fn obfuscation_numbers_a_field_by_its_place_in_the_schema() {
25001        let mut f = debugging();
25002        assert!(
25003            f.run(&[b"_FT.DEBUG", b"DOCINFO", b"dx", b"d:1", b"OBFUSCATE"])
25004                .contains("$22\r\nFieldPath@3 AS Field@3\r\n")
25005        );
25006    }
25007
25008    /// The keyword is read where it belongs and anything after it is stepped
25009    /// over, whatever the line that complains about it says.
25010    #[test]
25011    fn a_document_row_reads_its_keyword_at_a_fixed_place() {
25012        let mut f = debugging();
25013        let want = f.run(&[b"_FT.DEBUG", b"DOCINFO", b"dx", b"d:1", b"REVEAL"]);
25014        assert_eq!(
25015            f.run(&[b"_FT.DEBUG", b"DOCINFO", b"dx", b"d:1", b"REVEAL", b"more"]),
25016            want
25017        );
25018        assert_eq!(
25019            f.run(&[b"_FT.DEBUG", b"DOCINFO", b"dx", b"d:1", b"more", b"REVEAL"]),
25020            "-Invalid argument. Expected REVEAL or OBFUSCATE as the last argument\r\n"
25021        );
25022        assert_eq!(
25023            f.run(&[b"_FT.DEBUG", b"DOCINFO", b"dx", b"d:1"]),
25024            "-ERR wrong number of arguments for '_FT.DEBUG|DOCINFO' command\r\n"
25025        );
25026    }
25027
25028    /// The key is looked up before the keyword is read, so a key nobody indexed
25029    /// beats a keyword nobody wrote.
25030    #[test]
25031    fn a_document_row_looks_the_key_up_before_it_reads_the_keyword() {
25032        let mut f = debugging();
25033        assert_eq!(
25034            f.run(&[b"_FT.DEBUG", b"DOCINFO", b"dx", b"nope", b"zz"]),
25035            "-Document not found in index\r\n"
25036        );
25037        assert_eq!(
25038            f.run(&[b"_FT.DEBUG", b"DOCINFO", b"dx", b"d:1", b"zz"]),
25039            "-Invalid argument. Expected REVEAL or OBFUSCATE as the last argument\r\n"
25040        );
25041    }
25042
25043    /// The two directions of the document table, and the number nobody handed
25044    /// out reads as one that was given up rather than as one that never was.
25045    #[test]
25046    fn a_document_number_goes_both_ways() {
25047        let mut f = debugging();
25048        assert_eq!(
25049            f.run(&[b"_FT.DEBUG", b"IDTODOCID", b"dx", b"2"]),
25050            "$3\r\nd:2\r\n"
25051        );
25052        assert_eq!(
25053            f.run(&[b"_FT.DEBUG", b"DOCIDTOID", b"dx", b"d:2"]),
25054            ":2\r\n"
25055        );
25056        assert_eq!(
25057            f.run(&[b"_FT.DEBUG", b"DOCIDTOID", b"dx", b"nope"]),
25058            ":0\r\n"
25059        );
25060        assert_eq!(f.run(&[b"_FT.DEBUG", b"GET_MAX_DOC_ID", b"dx"]), ":3\r\n");
25061        for id in [b"9".as_slice(), b"0", b"-1", b"9223372036854775807"] {
25062            assert_eq!(
25063                f.run(&[b"_FT.DEBUG", b"IDTODOCID", b"dx", id]),
25064                "-document was removed\r\n",
25065                "{id:?}"
25066            );
25067        }
25068    }
25069
25070    /// A document number is read the strict way Redis reads an integer, so a
25071    /// leading zero, a leading plus and a leading space are all refused.
25072    #[test]
25073    fn a_document_number_is_read_the_strict_way() {
25074        let mut f = debugging();
25075        for id in [
25076            b"x".as_slice(),
25077            b"1.5",
25078            b" 1",
25079            b"+1",
25080            b"01",
25081            b"0x1",
25082            b"",
25083            b"9223372036854775808",
25084            b"18446744073709551615",
25085        ] {
25086            assert_eq!(
25087                f.run(&[b"_FT.DEBUG", b"IDTODOCID", b"dx", id]),
25088                "-bad id given\r\n",
25089                "{id:?}"
25090            );
25091        }
25092    }
25093
25094    /// A number a document has given up is still in every list it was in, so a
25095    /// dump names documents that the table says are gone.
25096    #[test]
25097    fn a_dump_keeps_a_number_the_table_has_given_up() {
25098        let mut f = debugging();
25099        f.run(&[b"DEL", b"d:2"]);
25100        assert_eq!(
25101            f.run(&[b"_FT.DEBUG", b"DUMP_INVIDX", b"dx", b"running"]),
25102            "*2\r\n:1\r\n:2\r\n"
25103        );
25104        assert_eq!(
25105            f.run(&[b"_FT.DEBUG", b"IDTODOCID", b"dx", b"2"]),
25106            "-document was removed\r\n"
25107        );
25108        assert_eq!(
25109            f.run(&[b"_FT.DEBUG", b"DOCIDTOID", b"dx", b"d:2"]),
25110            ":0\r\n"
25111        );
25112    }
25113
25114    /// A rewrite hands out a new number and leaves the old one behind, so the
25115    /// counter climbs past the number of documents there are.
25116    #[test]
25117    fn a_rewrite_takes_a_number_of_its_own() {
25118        let mut f = debugging();
25119        f.run(&[b"HSET", b"d:1", b"t", b"cats"]);
25120        assert_eq!(
25121            f.run(&[b"_FT.DEBUG", b"DOCIDTOID", b"dx", b"d:1"]),
25122            ":4\r\n"
25123        );
25124        assert_eq!(f.run(&[b"_FT.DEBUG", b"GET_MAX_DOC_ID", b"dx"]), ":4\r\n");
25125        assert_eq!(
25126            f.run(&[b"_FT.DEBUG", b"IDTODOCID", b"dx", b"1"]),
25127            "-document was removed\r\n"
25128        );
25129        assert_eq!(
25130            f.run(&[b"_FT.DEBUG", b"DUMP_INVIDX", b"dx", b"running"]),
25131            "*2\r\n:1\r\n:2\r\n"
25132        );
25133    }
25134
25135    /// An alias reads the index it stands for, the same as a query does.
25136    #[test]
25137    fn a_dump_follows_an_alias() {
25138        let mut f = debugging();
25139        f.run(&[b"FT.ALIASADD", b"da", b"dx"]);
25140        assert_eq!(f.run(&[b"_FT.DEBUG", b"GET_MAX_DOC_ID", b"da"]), ":3\r\n");
25141        assert_eq!(
25142            f.run(&[b"_FT.DEBUG", b"IDTODOCID", b"da", b"1"]),
25143            "$3\r\nd:1\r\n"
25144        );
25145    }
25146
25147    /// The index name is matched as written and the subcommand name is not, and
25148    /// an index nobody made is reported as a context that could not be built.
25149    #[test]
25150    fn an_index_name_is_case_sensitive_and_a_subcommand_name_is_not() {
25151        let mut f = debugging();
25152        assert_eq!(f.run(&[b"_FT.DEBUG", b"get_max_doc_id", b"dx"]), ":3\r\n");
25153        assert_eq!(
25154            f.run(&[b"_FT.DEBUG", b"GET_MAX_DOC_ID", b"DX"]),
25155            "-Can not create a search ctx\r\n"
25156        );
25157        assert_eq!(
25158            f.run(&[b"_FT.DEBUG", b"DUMP_TERMS", b"nope"]),
25159            "-Can not create a search ctx\r\n"
25160        );
25161    }
25162
25163    /// A field with nothing written into it answers an empty dump rather than an
25164    /// error, since the field is in the schema and only the values are missing.
25165    #[test]
25166    fn an_empty_field_dumps_as_nothing_at_all() {
25167        let mut f = Fixture::new();
25168        f.run(&[
25169            b"FT.CREATE",
25170            b"ex",
25171            b"PREFIX",
25172            b"1",
25173            b"e:",
25174            b"SCHEMA",
25175            b"t",
25176            b"TEXT",
25177            b"g",
25178            b"TAG",
25179            b"n",
25180            b"NUMERIC",
25181        ]);
25182        assert_eq!(f.run(&[b"_FT.DEBUG", b"DUMP_TERMS", b"ex"]), "*0\r\n");
25183        assert_eq!(
25184            f.run(&[b"_FT.DEBUG", b"DUMP_TAGIDX", b"ex", b"g"]),
25185            "*0\r\n"
25186        );
25187        assert_eq!(
25188            f.run(&[b"_FT.DEBUG", b"DUMP_NUMIDX", b"ex", b"n"]),
25189            "*0\r\n"
25190        );
25191        assert_eq!(f.run(&[b"_FT.DEBUG", b"GET_MAX_DOC_ID", b"ex"]), ":0\r\n");
25192    }
25193
25194    /// The two lines the dispatcher owns are the two that carry a code word, and
25195    /// every subcommand but `DOCINFO` counts its arguments exactly.
25196    #[test]
25197    fn the_two_lines_with_a_code_word_are_the_arity_and_the_unknown_one() {
25198        let mut f = debugging();
25199        for (sub, extra) in [
25200            (b"DUMP_TERMS".as_slice(), 1),
25201            (b"GET_MAX_DOC_ID", 1),
25202            (b"DUMP_INVIDX", 2),
25203            (b"DUMP_TAGIDX", 2),
25204            (b"DUMP_NUMIDX", 2),
25205            (b"IDTODOCID", 2),
25206            (b"DOCIDTOID", 2),
25207        ] {
25208            let want = format!(
25209                "-ERR wrong number of arguments for '_FT.DEBUG|{}' command\r\n",
25210                str::from_utf8(sub).unwrap()
25211            );
25212            for given in [extra - 1, extra + 1] {
25213                let mut cmd: Vec<&[u8]> = vec![b"_FT.DEBUG", sub];
25214                cmd.extend(std::iter::repeat_n(b"dx".as_slice(), given));
25215                assert_eq!(f.run(&cmd), want, "{sub:?} {given}");
25216            }
25217            let mut right: Vec<&[u8]> = vec![b"_FT.DEBUG", sub, b"dx"];
25218            right.extend(std::iter::repeat_n(b"g".as_slice(), extra - 1));
25219            assert_ne!(f.run(&right), want, "{sub:?}");
25220        }
25221        assert_eq!(
25222            f.run(&[b"_FT.DEBUG", b"bogus", b"dx"]),
25223            "-ERR unknown subcommand 'bogus'. Try _FT.DEBUG HELP.\r\n"
25224        );
25225    }
25226
25227    /// The eight names that answer rather than the sixty two a real server
25228    /// registers, which is D-97, and anything after the name is stepped over.
25229    #[test]
25230    fn the_help_names_the_subcommands_that_answer() {
25231        let mut f = Fixture::new();
25232        let want = "*8\r\n$11\r\nDUMP_INVIDX\r\n$11\r\nDUMP_NUMIDX\r\n$11\r\nDUMP_TAGIDX\r\n\
25233             $9\r\nIDTODOCID\r\n$9\r\nDOCIDTOID\r\n$7\r\nDOCINFO\r\n$10\r\nDUMP_TERMS\r\n\
25234             $14\r\nGET_MAX_DOC_ID\r\n";
25235        assert_eq!(f.run(&[b"_FT.DEBUG", b"HELP"]), want);
25236        assert_eq!(f.run(&[b"_FT.DEBUG", b"HELP", b"extra"]), want);
25237    }
25238
25239    // ------------------------------------------------------------- synonyms
25240
25241    /// The terms are folded on the way in and the group ids are not, and one
25242    /// term can be in more than one group.
25243    #[test]
25244    fn a_synonym_dump_folds_the_terms_and_keeps_the_ids_as_given() {
25245        let mut f = Fixture::new();
25246        f.run(&[b"FT.CREATE", b"e", b"SCHEMA", b"t", b"TEXT"]);
25247        assert_eq!(
25248            f.run(&[b"FT.SYNUPDATE", b"e", b"G1", b"BOY", b"kid"]),
25249            "+OK\r\n"
25250        );
25251        assert_eq!(f.run(&[b"FT.SYNUPDATE", b"e", b"g2", b"boy"]), "+OK\r\n");
25252        assert_eq!(
25253            f.run(&[b"FT.SYNDUMP", b"e"]),
25254            "*4\r\n$3\r\nboy\r\n*2\r\n$2\r\nG1\r\n$2\r\ng2\r\n\
25255             $3\r\nkid\r\n*1\r\n$2\r\nG1\r\n"
25256        );
25257    }
25258
25259    /// A group is not a comparison made at query time. It is a term of its
25260    /// own, so a word in a group reads as a union of the word, the groups it
25261    /// is in and its stem.
25262    #[test]
25263    fn a_word_in_a_group_reads_as_a_union_with_the_group_term() {
25264        let mut f = Fixture::new();
25265        f.run(&[b"FT.CREATE", b"e", b"SCHEMA", b"t", b"TEXT"]);
25266        f.run(&[b"FT.SYNUPDATE", b"e", b"gr", b"jogging"]);
25267        assert_eq!(
25268            f.run(&[b"FT.EXPLAIN", b"e", b"jogging"]),
25269            "$69\r\nUNION {\n  jogging\n  ~gr(expanded)\n  +jog(expanded)\n  jog(expanded)\n}\n\r\n"
25270        );
25271    }
25272
25273    /// The lookup on the document side is on the word and never on the stem,
25274    /// and a group written after the documents were still finds them because
25275    /// the index is read again.
25276    ///
25277    /// The group holds `running` and `d2` says `runs`, so a query for another
25278    /// word of the group finds `d1` and leaves `d2` where it is. A query for
25279    /// `running` itself does find `d2`, through the stem branch of the union
25280    /// rather than through the group, which is why the two asserts differ.
25281    #[test]
25282    fn a_group_matches_the_word_it_holds_and_not_a_stem_of_it() {
25283        let mut f = Fixture::new();
25284        f.run(&[b"FT.CREATE", b"e", b"SCHEMA", b"t", b"TEXT"]);
25285        f.run(&[b"HSET", b"d1", b"t", b"boy"]);
25286        f.run(&[b"HSET", b"d2", b"t", b"runs"]);
25287        f.run(&[b"FT.SYNUPDATE", b"e", b"gr", b"boy", b"child", b"running"]);
25288        assert_eq!(
25289            f.run(&[b"FT.SEARCH", b"e", b"child", b"NOCONTENT"]),
25290            "*2\r\n:1\r\n$2\r\nd1\r\n"
25291        );
25292        assert_eq!(
25293            f.run(&[b"FT.SEARCH", b"e", b"running", b"NOCONTENT"]),
25294            "*3\r\n:2\r\n$2\r\nd1\r\n$2\r\nd2\r\n"
25295        );
25296    }
25297
25298    /// Neither command makes an index and neither forgives a name that is not
25299    /// there, in the same words the rest of the group uses.
25300    #[test]
25301    fn a_synonym_command_on_a_name_that_is_not_there_fails() {
25302        let mut f = Fixture::new();
25303        let missing = "-SEARCH_INDEX_NOT_FOUND Index not found: nope\r\n";
25304        assert_eq!(f.run(&[b"FT.SYNDUMP", b"nope"]), missing);
25305        assert_eq!(f.run(&[b"FT.SYNUPDATE", b"nope", b"g", b"a"]), missing);
25306    }
25307
25308    /// The words after `PARAMS n` are counted before their shape is looked at,
25309    /// so a count that reaches past the end of the command and a count that is
25310    /// merely odd are two different errors.
25311    #[test]
25312    fn params_counts_the_words_before_it_pairs_them_up() {
25313        let mut f = Fixture::new();
25314        f.run(&[b"FT.CREATE", b"e", b"SCHEMA", b"t", b"TEXT"]);
25315        let none = "-SEARCH_PARSE_ARGS Bad arguments for PARAMS: \
25316                    Expected an argument, but none provided\r\n";
25317        let odd = "-SEARCH_ADD_ARGS Parameters must be specified in PARAM VALUE pairs\r\n";
25318        assert_eq!(f.run(&[b"FT.SEARCH", b"e", b"x", b"PARAMS", b"1"]), none);
25319        assert_eq!(
25320            f.run(&[b"FT.SEARCH", b"e", b"x", b"PARAMS", b"3", b"a", b"b"]),
25321            none
25322        );
25323        assert_eq!(
25324            f.run(&[b"FT.SEARCH", b"e", b"x", b"PARAMS", b"1", b"a"]),
25325            odd
25326        );
25327        assert_eq!(f.run(&[b"FT.SEARCH", b"e", b"x", b"PARAMS", b"0"]), odd);
25328        assert_eq!(
25329            f.run(&[b"FT.SEARCH", b"e", b"x", b"PARAMS", b"-1"]),
25330            "-SEARCH_PARSE_ARGS Bad arguments for PARAMS: Value is outside acceptable bounds\r\n"
25331        );
25332    }
25333
25334    // --------------------------------------------------------------- vectors
25335
25336    /// Five documents a unit apart along one axis, written in the opposite
25337    /// order to the one they sit in, so a reply in document order and a reply
25338    /// in distance order are two different replies.
25339    ///
25340    /// `d1` is furthest from the origin and `d5` is on it. The text field
25341    /// splits them so a query can narrow before it measures: `d1`, `d2` and
25342    /// `d4` say `alpha` and the other two say `beta`.
25343    fn vectored(f: &mut Fixture) {
25344        f.run(&[
25345            b"FT.CREATE",
25346            b"h",
25347            b"SCHEMA",
25348            b"t",
25349            b"TEXT",
25350            b"v",
25351            b"VECTOR",
25352            b"FLAT",
25353            b"6",
25354            b"TYPE",
25355            b"FLOAT32",
25356            b"DIM",
25357            b"2",
25358            b"DISTANCE_METRIC",
25359            b"L2",
25360        ]);
25361        let at: [&[u8]; 5] = [
25362            b"\x00\x00\x80\x40\x00\x00\x00\x00",
25363            b"\x00\x00\x40\x40\x00\x00\x00\x00",
25364            b"\x00\x00\x00\x40\x00\x00\x00\x00",
25365            b"\x00\x00\x80\x3f\x00\x00\x00\x00",
25366            b"\x00\x00\x00\x00\x00\x00\x00\x00",
25367        ];
25368        for (n, point) in at.iter().enumerate() {
25369            let key = format!("d{}", n + 1);
25370            let word: &[u8] = match n {
25371                0 | 1 | 3 => b"alpha",
25372                _ => b"beta",
25373            };
25374            f.run(&[b"HSET", key.as_bytes(), b"t", word, b"v", point]);
25375        }
25376    }
25377
25378    /// The origin, which every query below asks about.
25379    const ORIGIN: &[u8] = b"\x00\x00\x00\x00\x00\x00\x00\x00";
25380
25381    /// A `KNN` picks the k nearest and then answers them in document order,
25382    /// which is measured: asking for three of five that were written furthest
25383    /// first answers the last three written and not the first three.
25384    #[test]
25385    fn a_knn_picks_the_nearest_and_answers_them_in_document_order() {
25386        let mut f = Fixture::new();
25387        vectored(&mut f);
25388        assert_eq!(
25389            f.run(&[
25390                b"FT.SEARCH",
25391                b"h",
25392                b"*=>[KNN 5 @v $vec]",
25393                b"PARAMS",
25394                b"2",
25395                b"vec",
25396                ORIGIN,
25397                b"DIALECT",
25398                b"2",
25399                b"NOCONTENT",
25400            ]),
25401            "*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"
25402        );
25403        assert_eq!(
25404            f.run(&[
25405                b"FT.SEARCH",
25406                b"h",
25407                b"*=>[KNN 3 @v $vec]",
25408                b"PARAMS",
25409                b"2",
25410                b"vec",
25411                ORIGIN,
25412                b"DIALECT",
25413                b"2",
25414                b"NOCONTENT",
25415            ]),
25416            "*4\r\n:3\r\n$2\r\nd3\r\n$2\r\nd4\r\n$2\r\nd5\r\n"
25417        );
25418    }
25419
25420    /// A range takes what is really inside it, where the distances are squared
25421    /// so the five documents sit at 16, 9, 4, 1 and 0.
25422    #[test]
25423    fn a_range_takes_what_is_inside_it_and_the_distance_is_squared() {
25424        let mut f = Fixture::new();
25425        vectored(&mut f);
25426        for (radius, want) in [
25427            ("0", "*2\r\n:1\r\n$2\r\nd5\r\n"),
25428            ("2", "*3\r\n:2\r\n$2\r\nd4\r\n$2\r\nd5\r\n"),
25429            (
25430                "9",
25431                "*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",
25432            ),
25433        ] {
25434            let query = format!("@v:[VECTOR_RANGE {radius} $vec]");
25435            assert_eq!(
25436                f.run(&[
25437                    b"FT.SEARCH",
25438                    b"h",
25439                    query.as_bytes(),
25440                    b"PARAMS",
25441                    b"2",
25442                    b"vec",
25443                    ORIGIN,
25444                    b"DIALECT",
25445                    b"2",
25446                    b"NOCONTENT",
25447                ]),
25448                want,
25449                "radius {radius}"
25450            );
25451        }
25452    }
25453
25454    /// A `KNN` behind a query is the nearest of what the query matched, so
25455    /// asking for two of the three documents that say `alpha` answers the two
25456    /// of those three that are nearest and not the two nearest overall.
25457    #[test]
25458    fn a_knn_measures_what_the_query_in_front_of_it_matched() {
25459        let mut f = Fixture::new();
25460        vectored(&mut f);
25461        assert_eq!(
25462            f.run(&[
25463                b"FT.SEARCH",
25464                b"h",
25465                b"alpha=>[KNN 2 @v $vec]",
25466                b"PARAMS",
25467                b"2",
25468                b"vec",
25469                ORIGIN,
25470                b"DIALECT",
25471                b"2",
25472                b"NOCONTENT",
25473            ]),
25474            "*3\r\n:2\r\n$2\r\nd2\r\n$2\r\nd4\r\n"
25475        );
25476    }
25477
25478    /// A `KNN` counts in whole numbers and a range measures from zero, and the
25479    /// two are refused in their own words.
25480    ///
25481    /// The count is a token of its own and is checked where it stands, ahead of
25482    /// the field and ahead of the vector. A count that arrives through `PARAMS`
25483    /// is read by looser rules than one written into the query, which is
25484    /// measured: a leading plus is fine in a parameter and a syntax error in
25485    /// the query text.
25486    #[test]
25487    fn a_count_and_a_radius_are_refused_in_their_own_words() {
25488        let mut f = Fixture::new();
25489        vectored(&mut f);
25490        let ask = |f: &mut Fixture, query: &str| {
25491            f.run(&[
25492                b"FT.SEARCH",
25493                b"h",
25494                query.as_bytes(),
25495                b"PARAMS",
25496                b"2",
25497                b"vec",
25498                ORIGIN,
25499                b"DIALECT",
25500                b"2",
25501                b"NOCONTENT",
25502            ])
25503        };
25504        for (query, at, near) in [
25505            ("*=>[KNN -1 @v $vec]", 8, "-1"),
25506            ("*=>[KNN 1.5 @v $vec]", 8, "1.5"),
25507            ("*=>[KNN +3 @v $vec]", 8, "+3"),
25508            ("*=>[KNN 0x10 @v $vec]", 8, "0x10"),
25509            ("*=>[KNN abc @v $vec]", 8, "abc"),
25510            ("*=>[KNN 3 $vec]", 10, "vec"),
25511            ("*=>[KNN 3 @v vec]", 13, "vec"),
25512            ("@v:[VECTOR_RANGE 2 -1]", 19, "-1"),
25513        ] {
25514            assert_eq!(
25515                ask(&mut f, query),
25516                format!("-SEARCH_SYNTAX Syntax error at offset {at} near {near}\r\n"),
25517                "{query}"
25518            );
25519        }
25520
25521        // Read as a double the way a real server reads it, so the bound plus
25522        // thirty two rounds back onto the bound and gets in.
25523        let large = "-SEARCH_QUERY_BAD Error parsing vector similarity query: \
25524                     query KNN K parameter is too large, must not exceed 288230376151711744\r\n";
25525        assert_eq!(
25526            ask(&mut f, "*=>[KNN 288230376151711776 @v $vec]"),
25527            "*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"
25528        );
25529        assert_eq!(ask(&mut f, "*=>[KNN 288230376151711777 @v $vec]"), large);
25530        assert_eq!(ask(&mut f, "*=>[KNN 99999999999999999999 @v $vec]"), large);
25531
25532        for (radius, printed) in [("-1", "-1"), ("-0.5", "-0.5"), ("-1e2", "-100")] {
25533            let query = format!("@v:[VECTOR_RANGE {radius} $vec]");
25534            assert_eq!(
25535                ask(&mut f, &query),
25536                format!(
25537                    "-SEARCH_QUERY_BAD Error parsing vector similarity query: \
25538                     negative radius ({printed}) given in a range query\r\n"
25539                ),
25540                "{query}"
25541            );
25542        }
25543        // A radius of minus zero is not below zero and is a radius of zero.
25544        assert_eq!(
25545            ask(&mut f, "@v:[VECTOR_RANGE -0 $vec]"),
25546            "*2\r\n:1\r\n$2\r\nd5\r\n"
25547        );
25548    }
25549
25550    /// A count passed with `PARAMS` is read the way a real server reads one,
25551    /// which is not the way the same digits are read in the query text.
25552    #[test]
25553    fn a_count_that_came_from_params_is_read_by_its_own_rules() {
25554        let mut f = Fixture::new();
25555        vectored(&mut f);
25556        let ask = |f: &mut Fixture, count: &[u8]| {
25557            f.run(&[
25558                b"FT.SEARCH",
25559                b"h",
25560                b"*=>[KNN $k @v $vec]",
25561                b"PARAMS",
25562                b"4",
25563                b"vec",
25564                ORIGIN,
25565                b"k",
25566                count,
25567                b"DIALECT",
25568                b"2",
25569                b"NOCONTENT",
25570            ])
25571        };
25572        let three = "*4\r\n:3\r\n$2\r\nd3\r\n$2\r\nd4\r\n$2\r\nd5\r\n";
25573        assert_eq!(ask(&mut f, b"3"), three);
25574        assert_eq!(ask(&mut f, b"  3"), three);
25575        assert_eq!(ask(&mut f, b"+3"), three);
25576        for bad in [
25577            &b"3.0"[..],
25578            b"0x3",
25579            b"-1",
25580            b"abc",
25581            b"",
25582            b"99999999999999999999",
25583        ] {
25584            let value = String::from_utf8_lossy(bad).into_owned();
25585            assert_eq!(
25586                ask(&mut f, bad),
25587                format!(
25588                    "-SEARCH_NUMERIC_VALUE_INVALID Invalid numeric value ({value}) \
25589                     for parameter `k`\r\n"
25590                ),
25591                "{value}"
25592            );
25593        }
25594        assert_eq!(
25595            ask(&mut f, b"288230376151711777"),
25596            "-SEARCH_QUERY_BAD Error parsing vector similarity query: \
25597             query KNN K parameter is too large, must not exceed 288230376151711744\r\n"
25598        );
25599    }
25600
25601    /// A vector the wrong size is refused against the field it was passed to,
25602    /// naming both sizes in bytes.
25603    #[test]
25604    fn a_vector_the_wrong_size_is_refused_by_the_field_it_reached() {
25605        let mut f = Fixture::new();
25606        vectored(&mut f);
25607        assert_eq!(
25608            f.run(&[
25609                b"FT.SEARCH",
25610                b"h",
25611                b"*=>[KNN 5 @v $vec]",
25612                b"PARAMS",
25613                b"2",
25614                b"vec",
25615                b"abc",
25616                b"DIALECT",
25617                b"2",
25618                b"NOCONTENT",
25619            ]),
25620            "-SEARCH_QUERY_BAD Error parsing vector similarity query: \
25621             query vector blob size (3) does not match index's expected size (8).\r\n"
25622        );
25623    }
25624
25625    /// A nearest neighbour clause puts its distance on every row it answers,
25626    /// under `__v_score` unless the query renamed it. A range clause puts
25627    /// nothing there at all unless the query named it, which is what
25628    /// `YIELD_DISTANCE_AS` is for.
25629    #[test]
25630    fn a_vector_clause_yields_its_distance_under_the_name_it_was_given() {
25631        let mut f = Fixture::new();
25632        vectored(&mut f);
25633        let ask = |f: &mut Fixture, query: &str| {
25634            f.run(&[
25635                b"FT.SEARCH",
25636                b"h",
25637                query.as_bytes(),
25638                b"PARAMS",
25639                b"2",
25640                b"vec",
25641                ORIGIN,
25642                b"DIALECT",
25643                b"2",
25644                b"LIMIT",
25645                b"0",
25646                b"1",
25647            ])
25648        };
25649        assert_eq!(
25650            ask(&mut f, "*=>[KNN 3 @v $vec]"),
25651            "*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"
25652        );
25653        assert_eq!(
25654            ask(&mut f, "*=>[KNN 3 @v $vec AS d]"),
25655            "*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"
25656        );
25657        assert_eq!(
25658            ask(&mut f, "@v:[VECTOR_RANGE 4 $vec]"),
25659            "*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"
25660        );
25661        assert_eq!(
25662            ask(&mut f, "@v:[VECTOR_RANGE 4 $vec]=>{$YIELD_DISTANCE_AS: d}"),
25663            "*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"
25664        );
25665    }
25666
25667    /// What decides whether a `RETURN` answers the distance is the name the row
25668    /// would carry it under and not the field it would have been read from,
25669    /// because it is on the row before any key is read.
25670    ///
25671    /// So naming it answers it, renaming it answers nothing at all, and giving
25672    /// its name to another field answers the distance under that name.
25673    #[test]
25674    fn a_return_answers_the_distance_by_the_name_the_row_carries_it_under() {
25675        let mut f = Fixture::new();
25676        vectored(&mut f);
25677        let ask = |f: &mut Fixture, ret: &[&[u8]]| {
25678            let mut args: Vec<&[u8]> = vec![b"FT.SEARCH", b"h", b"*=>[KNN 1 @v $vec]"];
25679            args.extend_from_slice(ret);
25680            args.extend_from_slice(&[b"PARAMS", b"2", b"vec", ORIGIN, b"DIALECT", b"2"]);
25681            f.run(&args)
25682        };
25683        assert_eq!(
25684            ask(&mut f, &[b"RETURN", b"1", b"__v_score"]),
25685            "*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"
25686        );
25687        assert_eq!(
25688            ask(&mut f, &[b"RETURN", b"3", b"__v_score", b"AS", b"x"]),
25689            "*3\r\n:1\r\n$2\r\nd5\r\n*0\r\n"
25690        );
25691        assert_eq!(
25692            ask(&mut f, &[b"RETURN", b"3", b"t", b"AS", b"__v_score"]),
25693            "*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"
25694        );
25695        assert_eq!(
25696            ask(&mut f, &[b"RETURN", b"1", b"t"]),
25697            "*3\r\n:1\r\n$2\r\nd5\r\n*2\r\n$1\r\nt\r\n$4\r\nbeta\r\n"
25698        );
25699        // The distance goes in front of the rest whatever order they were
25700        // named in, and `NOCONTENT` takes it away with everything else.
25701        assert_eq!(
25702            ask(&mut f, &[b"RETURN", b"2", b"t", b"__v_score"]),
25703            "*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"
25704        );
25705        assert_eq!(ask(&mut f, &[b"NOCONTENT"]), "*2\r\n:1\r\n$2\r\nd5\r\n");
25706    }
25707
25708    /// A `SORTBY` can name a distance the query yielded, which sorts by the
25709    /// number rather than by anything the key holds. A name the query did not
25710    /// yield is refused the way any other unknown property is.
25711    #[test]
25712    fn a_sortby_can_name_a_distance_the_query_yielded() {
25713        let mut f = Fixture::new();
25714        vectored(&mut f);
25715        let ask = |f: &mut Fixture, query: &str, by: &[u8], desc: bool| {
25716            let mut args: Vec<&[u8]> = vec![b"FT.SEARCH", b"h", query.as_bytes(), b"SORTBY", by];
25717            if desc {
25718                args.push(b"DESC");
25719            }
25720            args.extend_from_slice(&[
25721                b"PARAMS",
25722                b"2",
25723                b"vec",
25724                ORIGIN,
25725                b"DIALECT",
25726                b"2",
25727                b"NOCONTENT",
25728            ]);
25729            f.run(&args)
25730        };
25731        assert_eq!(
25732            ask(&mut f, "*=>[KNN 3 @v $vec]", b"__v_score", false),
25733            "*4\r\n:3\r\n$2\r\nd5\r\n$2\r\nd4\r\n$2\r\nd3\r\n"
25734        );
25735        assert_eq!(
25736            ask(&mut f, "*=>[KNN 3 @v $vec]", b"__v_score", true),
25737            "*4\r\n:3\r\n$2\r\nd3\r\n$2\r\nd4\r\n$2\r\nd5\r\n"
25738        );
25739        assert_eq!(
25740            ask(&mut f, "*=>[KNN 3 @v $vec AS d]", b"d", false),
25741            "*4\r\n:3\r\n$2\r\nd5\r\n$2\r\nd4\r\n$2\r\nd3\r\n"
25742        );
25743        // Renaming it takes the old name away, and a query with no vector
25744        // clause in it never had the property at all.
25745        let missing = "-SEARCH_PROP_NOT_FOUND Property `__v_score` \
25746                       not loaded nor in schema\r\n";
25747        assert_eq!(
25748            ask(&mut f, "*=>[KNN 3 @v $vec AS d]", b"__v_score", false),
25749            missing
25750        );
25751        assert_eq!(ask(&mut f, "alpha", b"__v_score", false), missing);
25752        // The query is read before the property is looked up, which is
25753        // measured: a query that will not parse is answered first.
25754        assert_eq!(
25755            ask(&mut f, "foo(", b"zz", false),
25756            "-SEARCH_SYNTAX Syntax error at offset 3 near foo\r\n"
25757        );
25758    }
25759
25760    /// Two vector clauses in one query answer two distances, outermost first.
25761    #[test]
25762    fn two_vector_clauses_answer_two_distances() {
25763        let mut f = Fixture::new();
25764        vectored(&mut f);
25765        assert_eq!(
25766            f.run(&[
25767                b"FT.SEARCH",
25768                b"h",
25769                b"@v:[VECTOR_RANGE 9 $vec]=>{$YIELD_DISTANCE_AS: rr}=>[KNN 2 @v $vec]",
25770                b"RETURN",
25771                b"2",
25772                b"rr",
25773                b"__v_score",
25774                b"PARAMS",
25775                b"2",
25776                b"vec",
25777                ORIGIN,
25778                b"DIALECT",
25779                b"2",
25780            ]),
25781            "*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"
25782        );
25783    }
25784
25785    /// An aggregation carries the distance on every row whether or not the
25786    /// pipeline ever mentions it, and carries it in front of everything a
25787    /// `LOAD` asked for.
25788    #[test]
25789    fn an_aggregation_answers_a_distance_nothing_asked_for() {
25790        let mut f = Fixture::new();
25791        vectored(&mut f);
25792        let ask = |f: &mut Fixture, query: &str, rest: &[&[u8]]| {
25793            let mut args: Vec<&[u8]> = vec![b"FT.AGGREGATE", b"h", query.as_bytes()];
25794            args.extend_from_slice(rest);
25795            args.extend_from_slice(&[b"PARAMS", b"2", b"vec", ORIGIN, b"DIALECT", b"2"]);
25796            f.run(&args)
25797        };
25798        assert_eq!(
25799            ask(&mut f, "*=>[KNN 2 @v $vec]", &[]),
25800            "*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"
25801        );
25802        assert_eq!(
25803            ask(&mut f, "*=>[KNN 2 @v $vec]", &[b"LOAD", b"1", b"@t"]),
25804            "*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"
25805        );
25806        assert_eq!(
25807            ask(&mut f, "*=>[KNN 2 @v $vec AS d]", &[]),
25808            "*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"
25809        );
25810        // A range shows nothing until the query names it.
25811        assert_eq!(
25812            ask(&mut f, "@v:[VECTOR_RANGE 1 $vec]", &[]),
25813            "*3\r\n:1\r\n*0\r\n*0\r\n"
25814        );
25815        assert_eq!(
25816            ask(
25817                &mut f,
25818                "@v:[VECTOR_RANGE 1 $vec]=>{$YIELD_DISTANCE_AS: rr}",
25819                &[]
25820            ),
25821            "*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"
25822        );
25823    }
25824
25825    /// A nearest neighbour clause hands its documents back nearest first and an
25826    /// aggregation keeps them that way, where a search sorts them into document
25827    /// order. A tie goes to the document written first.
25828    #[test]
25829    fn an_aggregation_keeps_the_order_a_nearest_neighbour_clause_made() {
25830        let mut f = Fixture::new();
25831        vectored(&mut f);
25832        // Sitting on `d3`, so `d2` and `d4` are the same distance away.
25833        const MIDDLE: &[u8] = b"\x00\x00\x00\x40\x00\x00\x00\x00";
25834        let ask = |f: &mut Fixture, query: &str, vec: &[u8]| {
25835            f.run(&[
25836                b"FT.AGGREGATE",
25837                b"h",
25838                query.as_bytes(),
25839                b"LOAD",
25840                b"1",
25841                b"@t",
25842                b"PARAMS",
25843                b"2",
25844                b"vec",
25845                vec,
25846                b"DIALECT",
25847                b"2",
25848            ])
25849        };
25850        assert_eq!(
25851            ask(&mut f, "*=>[KNN 3 @v $vec]", MIDDLE),
25852            "*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"
25853        );
25854        // A range does no ordering, so those rows stay in document order.
25855        assert_eq!(
25856            ask(
25857                &mut f,
25858                "@v:[VECTOR_RANGE 1 $vec]=>{$YIELD_DISTANCE_AS: rr}",
25859                MIDDLE
25860            ),
25861            "*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"
25862        );
25863    }
25864
25865    /// Every step of the pipeline can name a distance the query yielded, and a
25866    /// query with no vector clause in it is refused for the name three
25867    /// different ways depending on which step asked.
25868    #[test]
25869    fn a_pipeline_step_can_name_a_distance_the_query_yielded() {
25870        let mut f = Fixture::new();
25871        vectored(&mut f);
25872        let ask = |f: &mut Fixture, query: &str, rest: &[&[u8]]| {
25873            let mut args: Vec<&[u8]> = vec![b"FT.AGGREGATE", b"h", query.as_bytes()];
25874            args.extend_from_slice(rest);
25875            args.extend_from_slice(&[b"PARAMS", b"2", b"vec", ORIGIN, b"DIALECT", b"2"]);
25876            f.run(&args)
25877        };
25878        let knn = "*=>[KNN 2 @v $vec]";
25879        assert_eq!(
25880            ask(&mut f, knn, &[b"APPLY", b"@__v_score * 2", b"AS", b"x"]),
25881            "*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"
25882        );
25883        assert_eq!(
25884            ask(&mut f, knn, &[b"FILTER", b"@__v_score > 0"]),
25885            "*2\r\n:1\r\n*2\r\n$9\r\n__v_score\r\n$1\r\n1\r\n"
25886        );
25887        assert_eq!(
25888            ask(&mut f, knn, &[b"SORTBY", b"2", b"@__v_score", b"DESC"]),
25889            "*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"
25890        );
25891        assert_eq!(
25892            ask(
25893                &mut f,
25894                knn,
25895                &[
25896                    b"GROUPBY",
25897                    b"1",
25898                    b"@t",
25899                    b"REDUCE",
25900                    b"MAX",
25901                    b"1",
25902                    b"@__v_score",
25903                    b"AS",
25904                    b"m"
25905                ]
25906            ),
25907            "*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"
25908        );
25909        assert_eq!(
25910            ask(&mut f, "*", &[b"APPLY", b"@__v_score", b"AS", b"x"]),
25911            "-SEARCH_PROP_NOT_FOUND Property not loaded nor in pipeline: \
25912             `__v_score`\r\n"
25913        );
25914        assert_eq!(
25915            ask(&mut f, "*", &[b"GROUPBY", b"1", b"@__v_score"]),
25916            "-SEARCH_PROP_NOT_FOUND No such property `__v_score`\r\n"
25917        );
25918        assert_eq!(
25919            ask(&mut f, "*", &[b"SORTBY", b"2", b"@__v_score", b"ASC"]),
25920            "-SEARCH_PROP_NOT_FOUND Property `__v_score` not loaded nor in \
25921             schema\r\n"
25922        );
25923    }
25924
25925    /// An aggregation reads every word before it reads the query, and reads the
25926    /// query before it ties anything on the pipeline to a place on the row.
25927    ///
25928    /// So a command with a fault in all three answers the one about the words,
25929    /// a command with a fault in the last two answers the one about the query,
25930    /// and the pipeline speaks last. That is measured, and it is the whole
25931    /// reason the arguments are read twice.
25932    #[test]
25933    fn the_words_come_before_the_query_and_the_query_before_the_pipeline() {
25934        let mut f = Fixture::new();
25935        vectored(&mut f);
25936        let ask = |f: &mut Fixture, rest: &[&[u8]]| {
25937            let mut args: Vec<&[u8]> = vec![b"FT.AGGREGATE", b"h"];
25938            args.extend_from_slice(rest);
25939            f.run(&args)
25940        };
25941        assert_eq!(
25942            ask(
25943                &mut f,
25944                &[b"foo(", b"APPLY", b"@zz", b"AS", b"x", b"LIMIT", b"x", b"1"]
25945            ),
25946            "-SEARCH_PARSE_ARGS LIMIT needs two numeric arguments\r\n"
25947        );
25948        assert_eq!(
25949            ask(&mut f, &[b"foo(", b"APPLY", b"@zz", b"AS", b"x"]),
25950            "-SEARCH_SYNTAX Syntax error at offset 3 near foo\r\n"
25951        );
25952        assert_eq!(
25953            ask(&mut f, &[b"*", b"APPLY", b"@zz", b"AS", b"x"]),
25954            "-SEARCH_PROP_NOT_FOUND Property not loaded nor in pipeline: `zz`\r\n"
25955        );
25956        // An expression that will not read is the pipeline's fault too, so it
25957        // speaks after the query and after a property named before it.
25958        assert_eq!(
25959            ask(&mut f, &[b"foo(", b"APPLY", b"@@@", b"AS", b"x"]),
25960            "-SEARCH_SYNTAX Syntax error at offset 3 near foo\r\n"
25961        );
25962        assert_eq!(
25963            ask(
25964                &mut f,
25965                &[
25966                    b"*", b"APPLY", b"@zz", b"AS", b"x", b"APPLY", b"@@@", b"AS", b"y"
25967                ]
25968            ),
25969            "-SEARCH_PROP_NOT_FOUND Property not loaded nor in pipeline: `zz`\r\n"
25970        );
25971        assert_eq!(
25972            ask(&mut f, &[b"*", b"APPLY", b"@@@", b"AS", b"x"]),
25973            "-SEARCH_EXPR Syntax error at offset 0 near ''\r\n"
25974        );
25975    }
25976
25977    /// A vector clause says which of the ways of answering one it took, and a
25978    /// range says nothing at all when there is no distance to hand back.
25979    #[test]
25980    fn a_vector_step_says_which_way_it_was_answered() {
25981        let mut f = Fixture::new();
25982        vectored(&mut f);
25983        let tree = |f: &mut Fixture, query: &[u8]| {
25984            let reply = timeless(&f.run(&[
25985                b"FT.PROFILE",
25986                b"h",
25987                b"AGGREGATE",
25988                b"QUERY",
25989                query,
25990                b"PARAMS",
25991                b"2",
25992                b"vec",
25993                ORIGIN,
25994                b"DIALECT",
25995                b"2",
25996            ]));
25997            let at = reply.find("+Iterators profile").expect("a tree");
25998            let end = reply.find("+Result processors").expect("a list of steps");
25999            reply[at..end].to_string()
26000        };
26001        assert_eq!(
26002            tree(&mut f, b"*=>[KNN 3 @v $vec]"),
26003            "+Iterators profile\r\n*8\r\n+Type\r\n+VECTOR\r\n+Time\r\n<t>\r\n\
26004             +Number of reading operations\r\n:3\r\n\
26005             +Vector search mode\r\n+STANDARD_KNN\r\n"
26006        );
26007        // Renaming the distance changes nothing about how it was answered.
26008        assert_eq!(
26009            tree(&mut f, b"*=>[KNN 3 @v $vec AS d]"),
26010            tree(&mut f, b"*=>[KNN 3 @v $vec]")
26011        );
26012        // A range with nothing to yield is not a vector step at all, and one
26013        // that yields names the distance in its own type.
26014        assert_eq!(
26015            tree(&mut f, b"@v:[VECTOR_RANGE 9 $vec]"),
26016            "+Iterators profile\r\n*6\r\n+Type\r\n+ID-LIST-SORTED\r\n+Time\r\n<t>\r\n\
26017             +Number of reading operations\r\n:4\r\n"
26018        );
26019        assert_eq!(
26020            tree(
26021                &mut f,
26022                b"@v:[VECTOR_RANGE 9 $vec]=>{$YIELD_DISTANCE_AS: rr}"
26023            ),
26024            "+Iterators profile\r\n*8\r\n\
26025             +Type\r\n+METRIC SORTED BY ID - VECTOR DISTANCE\r\n+Time\r\n<t>\r\n\
26026             +Number of reading operations\r\n:4\r\n\
26027             +Vector search mode\r\n+RANGE_QUERY\r\n"
26028        );
26029    }
26030
26031    /// What a vector clause narrowed itself down with hangs under it as a
26032    /// single child, and the step that works the distances out is behind the
26033    /// index whenever the query yields one.
26034    #[test]
26035    fn a_clause_in_front_of_a_vector_hangs_under_it_as_one_child() {
26036        let mut f = Fixture::new();
26037        vectored(&mut f);
26038        let ask = |f: &mut Fixture, query: &[u8]| {
26039            timeless(&f.run(&[
26040                b"FT.PROFILE",
26041                b"h",
26042                b"AGGREGATE",
26043                b"QUERY",
26044                query,
26045                b"PARAMS",
26046                b"2",
26047                b"vec",
26048                ORIGIN,
26049                b"DIALECT",
26050                b"2",
26051            ]))
26052        };
26053        let cut = |reply: &str| {
26054            let at = reply.find("+Iterators profile").expect("a tree");
26055            reply[at..].to_string()
26056        };
26057        assert_eq!(
26058            cut(&ask(&mut f, b"@t:alpha=>[KNN 3 @v $vec]")),
26059            "+Iterators profile\r\n*10\r\n+Type\r\n+VECTOR\r\n+Time\r\n<t>\r\n\
26060             +Number of reading operations\r\n:3\r\n\
26061             +Vector search mode\r\n+HYBRID_ADHOC_BF\r\n+Child iterator\r\n\
26062             *10\r\n+Type\r\n+TEXT\r\n+Term\r\n$5\r\nalpha\r\n+Time\r\n<t>\r\n\
26063             +Number of reading operations\r\n:3\r\n\
26064             +Estimated number of matches\r\n:3\r\n\
26065             +Result processors profile\r\n*2\r\n\
26066             *6\r\n+Type\r\n+Index\r\n+Time\r\n<t>\r\n+Results processed\r\n:3\r\n\
26067             *6\r\n+Type\r\n+Metrics Applier\r\n+Time\r\n<t>\r\n\
26068             +Results processed\r\n:3\r\n+Coordinator\r\n*0\r\n"
26069        );
26070        // A range nobody named yields nothing, so nothing works a distance out
26071        // and the step is not there.
26072        assert!(ask(&mut f, b"@v:[VECTOR_RANGE 9 $vec]").ends_with(
26073            "+Result processors profile\r\n*1\r\n*6\r\n+Type\r\n+Index\r\n\
26074             +Time\r\n<t>\r\n+Results processed\r\n:4\r\n+Coordinator\r\n*0\r\n"
26075        ));
26076        // A nearest neighbour clause with nothing in front of it yields all
26077        // the same, so the step is there without a child above it.
26078        assert!(ask(&mut f, b"*=>[KNN 3 @v $vec]").contains("+Type\r\n+Metrics Applier\r\n"));
26079    }
26080
26081    /// A `LIMIT 0 0` on an aggregation is a client asking for the total and
26082    /// nothing else, so the step that would have paged the rows counts them
26083    /// instead, whether or not a `SORTBY` put an order in front of it.
26084    #[test]
26085    fn a_window_of_nothing_on_an_aggregation_counts_rather_than_pages() {
26086        let mut f = profiling();
26087        let steps = |f: &mut Fixture, words: &[&[u8]]| {
26088            let mut argv: Vec<&[u8]> = vec![b"FT.PROFILE", b"ix", b"AGGREGATE", b"QUERY", b"*"];
26089            argv.extend_from_slice(words);
26090            let reply = timeless(&f.run(&argv));
26091            let at = reply.find("+Result processors").expect("a list of steps");
26092            reply[at..].to_string()
26093        };
26094        assert_eq!(
26095            steps(&mut f, &[b"LIMIT", b"0", b"0"]),
26096            "+Result processors profile\r\n*2\r\n\
26097             *6\r\n+Type\r\n+Index\r\n+Time\r\n<t>\r\n+Results processed\r\n:3\r\n\
26098             *6\r\n+Type\r\n+Counter\r\n+Time\r\n<t>\r\n+Results processed\r\n:1\r\n\
26099             +Coordinator\r\n*0\r\n"
26100        );
26101        assert!(
26102            steps(
26103                &mut f,
26104                &[b"SORTBY", b"2", b"@n", b"ASC", b"LIMIT", b"0", b"0"]
26105            )
26106            .contains("+Type\r\n+Counter\r\n")
26107        );
26108        // A window that keeps something is still a window.
26109        assert!(steps(&mut f, &[b"LIMIT", b"0", b"2"]).contains(
26110            "+Type\r\n+Pager/Limiter\r\n+Time\r\n<t>\r\n\
26111             +Results processed\r\n:2\r\n"
26112        ));
26113    }
26114
26115    // ----------------------------------------------------------- spellcheck
26116
26117    /// The score is how many documents hold the suggestion over how many
26118    /// documents there are, and how close the suggestion is to the word does
26119    /// not come into it at all, so the nearer of the two words here is second.
26120    #[test]
26121    fn a_spellcheck_scores_a_suggestion_by_how_common_it_is() {
26122        let mut f = Fixture::new();
26123        f.run(&[b"FT.CREATE", b"e", b"SCHEMA", b"t", b"TEXT", b"NOSTEM"]);
26124        f.run(&[b"HSET", b"d1", b"t", b"hello"]);
26125        f.run(&[b"HSET", b"d2", b"t", b"hallo hello"]);
26126        assert_eq!(
26127            f.run(&[b"FT.SPELLCHECK", b"e", b"hellp", b"DISTANCE", b"2"]),
26128            "*1\r\n*3\r\n$4\r\nTERM\r\n$5\r\nhellp\r\n\
26129             *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"
26130        );
26131    }
26132
26133    /// On RESP3 the whole thing is wrapped in a map under one name, a word
26134    /// carries a list of one pair maps, and the score is a double rather than
26135    /// a string.
26136    #[test]
26137    fn a_spellcheck_answers_a_map_of_maps_on_resp3() {
26138        let mut f = Fixture::new();
26139        f.run(&[b"HELLO", b"3"]);
26140        f.run(&[b"FT.CREATE", b"e", b"SCHEMA", b"t", b"TEXT", b"NOSTEM"]);
26141        f.run(&[b"HSET", b"d1", b"t", b"hello"]);
26142        assert_eq!(
26143            f.run(&[b"FT.SPELLCHECK", b"e", b"hellp"]),
26144            "%1\r\n$7\r\nresults\r\n%1\r\n$5\r\nhellp\r\n\
26145             *1\r\n%1\r\n$5\r\nhello\r\n,1\r\n"
26146        );
26147    }
26148
26149    /// A word the index already holds is not a mistake and is left out of the
26150    /// answer, and that check never looks at the field the query named, while
26151    /// the search for candidates does.
26152    #[test]
26153    fn a_word_the_index_holds_is_never_asked_about_whatever_field_it_names() {
26154        let mut f = Fixture::new();
26155        f.run(&[
26156            b"FT.CREATE",
26157            b"e",
26158            b"SCHEMA",
26159            b"a",
26160            b"TEXT",
26161            b"NOSTEM",
26162            b"b",
26163            b"TEXT",
26164            b"NOSTEM",
26165        ]);
26166        f.run(&[b"HSET", b"d1", b"b", b"world"]);
26167        assert_eq!(f.run(&[b"FT.SPELLCHECK", b"e", b"@a:world"]), "*0\r\n");
26168        assert_eq!(
26169            f.run(&[b"FT.SPELLCHECK", b"e", b"@a:worlt"]),
26170            "*1\r\n*3\r\n$4\r\nTERM\r\n$5\r\nworlt\r\n*0\r\n"
26171        );
26172    }
26173
26174    /// A dictionary named by `INCLUDE` adds words the index never read, scored
26175    /// zero and reported in the spelling the dictionary was given, and one
26176    /// named by `EXCLUDE` says a word is spelled right after all.
26177    #[test]
26178    fn a_spellcheck_reads_the_dictionaries_it_is_pointed_at() {
26179        let mut f = Fixture::new();
26180        f.run(&[b"FT.CREATE", b"e", b"SCHEMA", b"t", b"TEXT", b"NOSTEM"]);
26181        f.run(&[b"FT.DICTADD", b"d", b"Hellp", b"hellq"]);
26182        assert_eq!(
26183            f.run(&[b"FT.SPELLCHECK", b"e", b"hellz", b"TERMS", b"INCLUDE", b"d"]),
26184            "*1\r\n*3\r\n$4\r\nTERM\r\n$5\r\nhellz\r\n\
26185             *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"
26186        );
26187        assert_eq!(
26188            f.run(&[b"FT.SPELLCHECK", b"e", b"hellq", b"TERMS", b"EXCLUDE", b"d"]),
26189            "*0\r\n"
26190        );
26191        assert_eq!(
26192            f.run(&[b"FT.SPELLCHECK", b"e", b"x", b"TERMS", b"INCLUDE", b"nope"]),
26193            "-Dict does not exist: nope\r\n"
26194        );
26195    }
26196
26197    /// The first `DISTANCE` counts and the rest are dropped, an argument
26198    /// nobody recognises is stepped over rather than refused, and a distance
26199    /// outside one to four is the one thing here that does fail.
26200    #[test]
26201    fn a_spellcheck_reads_its_arguments_leniently() {
26202        let mut f = Fixture::new();
26203        f.run(&[b"FT.CREATE", b"e", b"SCHEMA", b"t", b"TEXT", b"NOSTEM"]);
26204        f.run(&[b"HSET", b"d1", b"t", b"hello"]);
26205        let one = "*1\r\n*3\r\n$4\r\nTERM\r\n$5\r\nhellp\r\n\
26206                   *1\r\n*2\r\n$1\r\n1\r\n$5\r\nhello\r\n";
26207        assert_eq!(f.run(&[b"FT.SPELLCHECK", b"e", b"hellp", b"BOGUS"]), one);
26208        let none = "*1\r\n*3\r\n$4\r\nTERM\r\n$5\r\nhelqp\r\n*0\r\n";
26209        let args: &[&[u8]] = &[
26210            b"FT.SPELLCHECK",
26211            b"e",
26212            b"helqp",
26213            b"DISTANCE",
26214            b"1",
26215            b"DISTANCE",
26216            b"4",
26217        ];
26218        assert_eq!(f.run(args), none);
26219        assert_eq!(
26220            f.run(&[b"FT.SPELLCHECK", b"e", b"hellp", b"DISTANCE", b"5"]),
26221            "-bad distance given, distance must be a natural number between 1 to 4\r\n"
26222        );
26223        assert_eq!(
26224            f.run(&[b"FT.SPELLCHECK", b"nope", b"hellp"]),
26225            "-SEARCH_INDEX_NOT_FOUND Index not found: nope\r\n"
26226        );
26227    }
26228
26229    // -------------------------------------------------------------- suggest
26230
26231    /// The reply is the size of the dictionary afterwards, which is neither
26232    /// what was added nor whether anything changed.
26233    #[test]
26234    fn an_add_answers_how_many_suggestions_are_in_there_now() {
26235        let mut f = Fixture::new();
26236        assert_eq!(f.run(&[b"FT.SUGADD", b"s", b"one", b"1"]), ":1\r\n");
26237        assert_eq!(f.run(&[b"FT.SUGADD", b"s", b"one", b"9"]), ":1\r\n");
26238        assert_eq!(f.run(&[b"FT.SUGADD", b"s", b"only", b"2"]), ":2\r\n");
26239        assert_eq!(f.run(&[b"FT.SUGLEN", b"s"]), ":2\r\n");
26240        assert_eq!(f.run(&[b"FT.SUGLEN", b"nokey"]), ":0\r\n");
26241    }
26242
26243    /// A suggestion dictionary is the one thing the search module puts in the
26244    /// keyspace, so every keyspace command reaches it.
26245    #[test]
26246    fn a_suggestion_dictionary_is_a_key_with_a_type_of_its_own() {
26247        let mut f = Fixture::new();
26248        f.run(&[b"FT.SUGADD", b"s", b"one", b"1"]);
26249        assert_eq!(f.run(&[b"TYPE", b"s"]), "+trietype0\r\n");
26250        assert_eq!(f.run(&[b"OBJECT", b"ENCODING", b"s"]), "$3\r\nraw\r\n");
26251        assert_eq!(f.run(&[b"EXISTS", b"s"]), ":1\r\n");
26252        assert_eq!(f.run(&[b"KEYS", b"*"]), "*1\r\n$1\r\ns\r\n");
26253        assert_eq!(f.run(&[b"EXPIRE", b"s", b"100"]), ":1\r\n");
26254        assert_eq!(f.run(&[b"TTL", b"s"]), ":100\r\n");
26255        assert_eq!(f.run(&[b"DEL", b"s"]), ":1\r\n");
26256        assert_eq!(f.run(&[b"FT.SUGLEN", b"s"]), ":0\r\n");
26257    }
26258
26259    /// The last suggestion out takes the key with it, which most module types
26260    /// do not do.
26261    #[test]
26262    fn deleting_the_last_suggestion_deletes_the_key() {
26263        let mut f = Fixture::new();
26264        f.run(&[b"FT.SUGADD", b"s", b"one", b"1"]);
26265        assert_eq!(f.run(&[b"FT.SUGDEL", b"s", b"nope"]), ":0\r\n");
26266        assert_eq!(f.run(&[b"FT.SUGDEL", b"s", b"one"]), ":1\r\n");
26267        assert_eq!(f.run(&[b"EXISTS", b"s"]), ":0\r\n");
26268        assert_eq!(f.run(&[b"FT.SUGDEL", b"nokey", b"a"]), ":0\r\n");
26269    }
26270
26271    /// A key holding anything else is refused rather than overwritten, on all
26272    /// four of them.
26273    #[test]
26274    fn a_suggestion_command_on_another_kind_of_key_is_wrongtype() {
26275        let mut f = Fixture::new();
26276        f.run(&[b"SET", b"s", b"x"]);
26277        for cmd in [
26278            vec![&b"FT.SUGADD"[..], b"s", b"t", b"1"],
26279            vec![&b"FT.SUGGET"[..], b"s", b"t"],
26280            vec![&b"FT.SUGDEL"[..], b"s", b"t"],
26281            vec![&b"FT.SUGLEN"[..], b"s"],
26282        ] {
26283            assert!(f.run(&cmd).starts_with("-WRONGTYPE"), "{cmd:?}");
26284        }
26285        assert_eq!(f.run(&[b"GET", b"s"]), "$1\r\nx\r\n");
26286    }
26287
26288    /// The scores in here were read off a real server, single precision and
26289    /// all. An exact match answers a sentinel so it sorts in front.
26290    #[test]
26291    fn a_lookup_answers_a_score_it_works_out_rather_than_the_one_stored() {
26292        let mut f = Fixture::new();
26293        f.run(&[b"FT.SUGADD", b"s", b"one", b"1"]);
26294        f.run(&[b"FT.SUGADD", b"s", b"only", b"2"]);
26295        f.run(&[b"FT.SUGADD", b"s", b"ontario", b"3"]);
26296        assert_eq!(
26297            f.run(&[b"FT.SUGGET", b"s", b"on", b"WITHSCORES"]),
26298            "*6\r\n$7\r\nontario\r\n$18\r\n1.2247449159622192\r\n\
26299             $4\r\nonly\r\n$17\r\n1.154700517654419\r\n\
26300             $3\r\none\r\n$18\r\n0.7071067690849304\r\n"
26301        );
26302        assert_eq!(
26303            f.run(&[b"FT.SUGGET", b"s", b"one", b"WITHSCORES"]),
26304            "*2\r\n$3\r\none\r\n$10\r\n2147483648\r\n"
26305        );
26306        assert_eq!(f.run(&[b"FT.SUGGET", b"nokey", b"a"]), "*0\r\n");
26307    }
26308
26309    /// `FUZZY` is one edit, and the edit is a rune rather than a byte.
26310    #[test]
26311    fn fuzzy_allows_one_edit_and_nothing_allows_two() {
26312        let mut f = Fixture::new();
26313        f.run(&[b"FT.SUGADD", b"s", b"only", b"2"]);
26314        assert_eq!(f.run(&[b"FT.SUGGET", b"s", b"one"]), "*0\r\n");
26315        assert_eq!(
26316            f.run(&[b"FT.SUGGET", b"s", b"one", b"FUZZY", b"WITHSCORES"]),
26317            "*2\r\n$4\r\nonly\r\n$19\r\n0.19139298796653748\r\n"
26318        );
26319        assert_eq!(f.run(&[b"FT.SUGGET", b"s", b"xyz", b"FUZZY"]), "*0\r\n");
26320    }
26321
26322    /// Five without a `MAX`, and the terms come back in score order.
26323    #[test]
26324    fn a_lookup_answers_five_unless_it_is_told_otherwise() {
26325        let mut f = Fixture::new();
26326        for (term, score) in [
26327            (&b"a1"[..], &b"1"[..]),
26328            (b"a2", b"2"),
26329            (b"a3", b"3"),
26330            (b"a4", b"4"),
26331            (b"a5", b"5"),
26332            (b"a6", b"6"),
26333        ] {
26334            f.run(&[b"FT.SUGADD", b"s", term, score]);
26335        }
26336        assert_eq!(
26337            f.run(&[b"FT.SUGGET", b"s", b"a"]),
26338            "*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"
26339        );
26340        assert_eq!(
26341            f.run(&[b"FT.SUGGET", b"s", b"a", b"MAX", b"2"]),
26342            "*2\r\n$2\r\na6\r\n$2\r\na5\r\n"
26343        );
26344        // A `MAX` larger than the dictionary answers what there is.
26345        assert!(
26346            f.run(&[b"FT.SUGGET", b"s", b"a", b"MAX", b"100"])
26347                .starts_with("*6\r\n")
26348        );
26349    }
26350
26351    /// A payload is replaced only when one is given, and an empty one is no
26352    /// payload at all.
26353    #[test]
26354    fn a_payload_comes_back_beside_the_term_or_a_null_does() {
26355        let mut f = Fixture::new();
26356        f.run(&[b"FT.SUGADD", b"s", b"one", b"1", b"PAYLOAD", b"p"]);
26357        assert_eq!(
26358            f.run(&[b"FT.SUGGET", b"s", b"o", b"WITHPAYLOADS"]),
26359            "*2\r\n$3\r\none\r\n$1\r\np\r\n"
26360        );
26361        f.run(&[b"FT.SUGADD", b"s", b"one", b"2"]);
26362        assert_eq!(
26363            f.run(&[b"FT.SUGGET", b"s", b"o", b"WITHPAYLOADS"]),
26364            "*2\r\n$3\r\none\r\n$1\r\np\r\n"
26365        );
26366        // An empty payload is the same as not having given one at all, so it
26367        // leaves the payload where it is rather than clearing it.
26368        f.run(&[b"FT.SUGADD", b"s", b"one", b"2", b"PAYLOAD", b""]);
26369        assert_eq!(
26370            f.run(&[b"FT.SUGGET", b"s", b"o", b"WITHPAYLOADS"]),
26371            "*2\r\n$3\r\none\r\n$1\r\np\r\n"
26372        );
26373        // A term that never had one answers a null.
26374        f.run(&[b"FT.SUGADD", b"s", b"other", b"1", b"PAYLOAD", b""]);
26375        assert_eq!(
26376            f.run(&[b"FT.SUGGET", b"s", b"ot", b"WITHPAYLOADS"]),
26377            "*2\r\n$5\r\nother\r\n$-1\r\n"
26378        );
26379    }
26380
26381    /// `INCR` adds to the score that is there rather than replacing it, and
26382    /// three tenths a tenth at a time is the reading that shows the score is
26383    /// held in single precision.
26384    #[test]
26385    fn incr_adds_to_the_score_that_is_already_there() {
26386        let mut f = Fixture::new();
26387        for _ in 0..3 {
26388            f.run(&[b"FT.SUGADD", b"s", b"xxx", b"0.1", b"INCR"]);
26389        }
26390        assert_eq!(
26391            f.run(&[b"FT.SUGGET", b"s", b"xx", b"WITHSCORES"]),
26392            "*2\r\n$3\r\nxxx\r\n$18\r\n0.2121320366859436\r\n"
26393        );
26394    }
26395
26396    /// The five error sentences, none of which are written the same way.
26397    #[test]
26398    fn the_suggestion_errors_are_the_lines_the_module_sends() {
26399        let mut f = Fixture::new();
26400        f.run(&[b"FT.SUGADD", b"s", b"one", b"1"]);
26401        assert_eq!(
26402            f.run(&[b"FT.SUGADD", b"s", b"t", b"abc"]),
26403            "-ERR invalid score\r\n"
26404        );
26405        // The unknown word is complained about before the score is converted.
26406        assert_eq!(
26407            f.run(&[b"FT.SUGADD", b"s", b"t", b"abc", b"NOPE"]),
26408            "-Unknown argument `NOPE`\r\n"
26409        );
26410        assert_eq!(
26411            f.run(&[b"FT.SUGADD", b"s", b"t", b"1", b"PAYLOAD"]),
26412            "-Invalid payload: Expected an argument, but none provided\r\n"
26413        );
26414        // Too many words is an arity error and not an unknown argument.
26415        assert!(
26416            f.run(&[
26417                b"FT.SUGADD",
26418                b"s",
26419                b"t",
26420                b"1",
26421                b"PAYLOAD",
26422                b"a",
26423                b"PAYLOAD",
26424                b"b"
26425            ])
26426            .contains("wrong number of arguments")
26427        );
26428        assert_eq!(
26429            f.run(&[b"FT.SUGGET", b"s", b"o", b"NOPE"]),
26430            "-SEARCH_PARSE_ARGS Unrecognized argument: NOPE\r\n"
26431        );
26432        // A count read as a whole number and then found to be out of range,
26433        // against one that had to be read as a double first, where anything
26434        // under one is a conversion that failed rather than a range that did.
26435        for max in [&b"0"[..], b"-1", b"4294967296", b"1e10", b"inf"] {
26436            assert_eq!(
26437                f.run(&[b"FT.SUGGET", b"s", b"o", b"MAX", max]),
26438                "-SEARCH_PARSE_ARGS MAX: Value is outside acceptable bounds\r\n",
26439                "{}",
26440                String::from_utf8_lossy(max)
26441            );
26442        }
26443        for max in [
26444            &b"abc"[..],
26445            b"0.0",
26446            b"00",
26447            b"-0",
26448            b"+0",
26449            b"0.5",
26450            b"-1.5",
26451            b"1e400",
26452        ] {
26453            assert_eq!(
26454                f.run(&[b"FT.SUGGET", b"s", b"o", b"MAX", max]),
26455                "-SEARCH_PARSE_ARGS MAX: Could not convert argument to expected type\r\n",
26456                "{}",
26457                String::from_utf8_lossy(max)
26458            );
26459        }
26460        for max in [&b"01"[..], b"+1", b"1.5", b"0x10", b"1e2"] {
26461            assert_eq!(
26462                f.run(&[b"FT.SUGGET", b"s", b"o", b"MAX", max]),
26463                "*1\r\n$3\r\none\r\n",
26464                "{}",
26465                String::from_utf8_lossy(max)
26466            );
26467        }
26468        assert_eq!(
26469            f.run(&[b"FT.SUGGET", b"s", b"o", b"MAX"]),
26470            "-SEARCH_PARSE_ARGS MAX: Expected an argument, but none provided\r\n"
26471        );
26472        // A score too large for a double is refused where one spelled out is
26473        // taken, which is the module reading errno after the conversion.
26474        assert_eq!(
26475            f.run(&[b"FT.SUGADD", b"s", b"t", b"1e400"]),
26476            "-ERR invalid score\r\n"
26477        );
26478        assert_eq!(f.run(&[b"FT.SUGADD", b"s", b"t", b"inf"]), ":2\r\n");
26479    }
26480
26481    /// An empty term is taken and not stored, so the reply is the length that
26482    /// was already there and nothing new comes back. The key is still made,
26483    /// and a delete that finds nothing is what clears it away again.
26484    #[test]
26485    fn an_empty_suggestion_is_taken_and_dropped_but_still_makes_the_key() {
26486        let mut f = Fixture::new();
26487        f.run(&[b"FT.SUGADD", b"s", b"one", b"1"]);
26488        assert_eq!(f.run(&[b"FT.SUGADD", b"s", b"", b"1"]), ":1\r\n");
26489        assert_eq!(f.run(&[b"FT.SUGGET", b"s", b""]), "*1\r\n$3\r\none\r\n");
26490        assert_eq!(f.run(&[b"FT.SUGADD", b"e", b"", b"1"]), ":0\r\n");
26491        assert_eq!(f.run(&[b"EXISTS", b"e"]), ":1\r\n");
26492        assert_eq!(f.run(&[b"TYPE", b"e"]), "+trietype0\r\n");
26493        assert_eq!(f.run(&[b"FT.SUGDEL", b"e", b"nothing"]), ":0\r\n");
26494        assert_eq!(f.run(&[b"EXISTS", b"e"]), ":0\r\n");
26495    }
26496
26497    /// A key that will not read is counted against the index and against the
26498    /// field, and `FT.INFO` says so.
26499    #[test]
26500    fn a_hash_that_will_not_read_is_counted_where_ft_info_reports_it() {
26501        let mut f = Fixture::new();
26502        f.run(&[
26503            b"FT.CREATE",
26504            b"ix",
26505            b"PREFIX",
26506            b"1",
26507            b"p:",
26508            b"SCHEMA",
26509            b"n",
26510            b"NUMERIC",
26511        ]);
26512        f.run(&[b"HSET", b"p:1", b"n", b"notanumber"]);
26513        assert_eq!(held(&f, b"ix"), (0, 0));
26514
26515        let reply = f.run(&[b"FT.INFO", b"ix"]);
26516        assert!(
26517            reply.contains("SEARCH_NUMERIC_VALUE_INVALID Invalid numeric value: 'notanumber'"),
26518            "{reply}"
26519        );
26520        assert!(reply.contains("hash_indexing_failures"), "{reply}");
26521    }
26522
26523    /// An index can only be made on database zero, and the check comes after
26524    /// the `IFNX` shortcut and before everything else.
26525    #[test]
26526    fn an_index_can_only_be_made_on_database_zero() {
26527        let mut f = Fixture::new();
26528        f.run(&[b"FT.CREATE", b"ix", b"SCHEMA", b"t", b"TEXT"]);
26529        f.run(&[b"SELECT", b"1"]);
26530        let refused = "-Cannot create index on db != 0\r\n";
26531        assert_eq!(
26532            f.run(&[b"FT.CREATE", b"jx", b"SCHEMA", b"t", b"TEXT"]),
26533            refused
26534        );
26535        // The name is taken, and it still answers about the database.
26536        assert_eq!(
26537            f.run(&[b"FT.CREATE", b"ix", b"SCHEMA", b"t", b"TEXT"]),
26538            refused
26539        );
26540        // And so does one whose arguments are nonsense.
26541        assert_eq!(
26542            f.run(&[b"FT.CREATE", b"zz", b"BOGUS", b"SCHEMA", b"t", b"TEXT"]),
26543            refused
26544        );
26545        // `IFNX` over a name that is taken is the one that gets through.
26546        assert_eq!(
26547            f.run(&[b"FT._CREATEIFNX", b"ix", b"SCHEMA", b"t", b"TEXT"]),
26548            "+OK\r\n"
26549        );
26550        assert_eq!(f.server.search.lock().len(), 1);
26551    }
26552
26553    /// The scan reads the database the create was run on, and after that the
26554    /// index follows its keys in every database.
26555    ///
26556    /// The asymmetry is a real server's, measured, and it is the sort of thing
26557    /// nobody would arrive at by choosing.
26558    #[test]
26559    fn the_scan_is_one_database_and_the_following_is_all_of_them() {
26560        let mut f = Fixture::new();
26561        f.run(&[b"SELECT", b"1"]);
26562        f.run(&[b"HSET", b"p:9", b"t", b"on one"]);
26563        f.run(&[b"SELECT", b"0"]);
26564        f.run(&[b"HSET", b"p:0", b"t", b"on zero"]);
26565        f.run(&[
26566            b"FT.CREATE",
26567            b"ix",
26568            b"PREFIX",
26569            b"1",
26570            b"p:",
26571            b"SCHEMA",
26572            b"t",
26573            b"TEXT",
26574        ]);
26575        assert_eq!(held(&f, b"ix"), (1, 1), "the scan read database zero only");
26576
26577        f.run(&[b"SELECT", b"1"]);
26578        f.run(&[b"HSET", b"p:8", b"t", b"later"]);
26579        assert_eq!(
26580            held(&f, b"ix"),
26581            (2, 2),
26582            "and then it follows every database"
26583        );
26584    }
26585
26586    /// Four documents over the two kinds of field a query can ask about, which
26587    /// is the corpus the searches below read.
26588    fn corpus(f: &mut Fixture) {
26589        f.run(&[
26590            b"FT.CREATE",
26591            b"sx",
26592            b"PREFIX",
26593            b"1",
26594            b"d:",
26595            b"SCHEMA",
26596            b"t",
26597            b"TEXT",
26598            b"g",
26599            b"TAG",
26600            b"n",
26601            b"NUMERIC",
26602        ]);
26603        for (key, text, tag, number) in [
26604            (b"d:1".as_slice(), "alpha beta", "aa,bb", "1"),
26605            (b"d:2", "alpha gamma", "bb", "2"),
26606            (b"d:3", "delta", "cc", "3"),
26607            (b"d:4", "alpha beta gamma", "aa,cc", "4"),
26608        ] {
26609            f.run(&[
26610                b"HSET",
26611                key,
26612                b"t",
26613                text.as_bytes(),
26614                b"g",
26615                tag.as_bytes(),
26616                b"n",
26617                number.as_bytes(),
26618            ]);
26619        }
26620    }
26621
26622    /// A corpus with something to sort by: a text field the index keeps a copy
26623    /// of, a number, the same text field under another name, and a text field
26624    /// the index keeps nothing of.
26625    fn sortable(f: &mut Fixture) {
26626        f.run(&[
26627            b"FT.CREATE",
26628            b"sy",
26629            b"PREFIX",
26630            b"1",
26631            b"s:",
26632            b"SCHEMA",
26633            b"t",
26634            b"TEXT",
26635            b"SORTABLE",
26636            b"n",
26637            b"NUMERIC",
26638            b"SORTABLE",
26639            b"body",
26640            b"AS",
26641            b"b",
26642            b"TEXT",
26643            b"SORTABLE",
26644            b"p",
26645            b"TEXT",
26646        ]);
26647        for (key, text, number) in [
26648            (b"s:1".as_slice(), "Banana Split", "2"),
26649            (b"s:2", "apple", "10"),
26650        ] {
26651            f.run(&[
26652                b"HSET",
26653                key,
26654                b"t",
26655                text.as_bytes(),
26656                b"n",
26657                number.as_bytes(),
26658                b"body",
26659                text.as_bytes(),
26660                b"p",
26661                b"alpha",
26662            ]);
26663        }
26664        // A key with nothing under either sortable field, which is what sorts
26665        // last whichever way round the sort runs.
26666        f.run(&[b"HSET", b"s:3", b"p", b"alpha"]);
26667    }
26668
26669    /// A sort runs off the copy of the value the index keeps, and a row with no
26670    /// value at all is last both ways round.
26671    #[test]
26672    fn a_search_sorts_by_a_field_the_index_keeps_a_copy_of() {
26673        let mut f = Fixture::new();
26674        sortable(&mut f);
26675        assert_eq!(
26676            f.run(&[b"FT.SEARCH", b"sy", b"alpha", b"SORTBY", b"n", b"NOCONTENT"]),
26677            "*4\r\n:3\r\n$3\r\ns:1\r\n$3\r\ns:2\r\n$3\r\ns:3\r\n"
26678        );
26679        assert_eq!(
26680            f.run(&[
26681                b"FT.SEARCH",
26682                b"sy",
26683                b"alpha",
26684                b"SORTBY",
26685                b"n",
26686                b"DESC",
26687                b"NOCONTENT"
26688            ]),
26689            "*4\r\n:3\r\n$3\r\ns:2\r\n$3\r\ns:1\r\n$3\r\ns:3\r\n"
26690        );
26691        // The copy of a text field is folded, so `apple` sorts before
26692        // `Banana Split` where a comparison of the bytes would not.
26693        assert_eq!(
26694            f.run(&[b"FT.SEARCH", b"sy", b"alpha", b"SORTBY", b"t", b"NOCONTENT"]),
26695            "*4\r\n:3\r\n$3\r\ns:2\r\n$3\r\ns:1\r\n$3\r\ns:3\r\n"
26696        );
26697    }
26698
26699    /// A field the index keeps no copy of is sorted by the value read off the
26700    /// key, which happens after the walk rather than during it.
26701    #[test]
26702    fn a_search_sorts_by_a_field_it_has_to_read_the_key_for() {
26703        let mut f = Fixture::new();
26704        sortable(&mut f);
26705        f.run(&[b"HSET", b"s:1", b"p", b"alpha zulu"]);
26706        assert_eq!(
26707            f.run(&[
26708                b"FT.SEARCH",
26709                b"sy",
26710                b"alpha",
26711                b"SORTBY",
26712                b"p",
26713                b"NOCONTENT",
26714                b"LIMIT",
26715                b"0",
26716                b"2"
26717            ]),
26718            "*3\r\n:3\r\n$3\r\ns:2\r\n$3\r\ns:3\r\n"
26719        );
26720        // Nothing is folded on this side, because the schema never asked for a
26721        // copy to fold, so the value goes into the sort as it was written.
26722        assert_eq!(
26723            f.run(&[
26724                b"FT.SEARCH",
26725                b"sy",
26726                b"alpha",
26727                b"SORTBY",
26728                b"p",
26729                b"WITHSORTKEYS",
26730                b"NOCONTENT",
26731                b"LIMIT",
26732                b"2",
26733                b"1"
26734            ]),
26735            "*3\r\n:3\r\n$3\r\ns:1\r\n$11\r\n$alpha zulu\r\n"
26736        );
26737    }
26738
26739    /// The value the sort compared goes beside every row, as a number after a
26740    /// hash, as text after a dollar, and as a null on a row that had none.
26741    #[test]
26742    fn a_search_can_send_the_value_it_sorted_by_back() {
26743        let mut f = Fixture::new();
26744        sortable(&mut f);
26745        assert_eq!(
26746            f.run(&[
26747                b"FT.SEARCH",
26748                b"sy",
26749                b"alpha",
26750                b"SORTBY",
26751                b"n",
26752                b"WITHSORTKEYS",
26753                b"NOCONTENT"
26754            ]),
26755            concat!(
26756                "*7\r\n:3\r\n",
26757                "$3\r\ns:1\r\n$2\r\n#2\r\n",
26758                "$3\r\ns:2\r\n$3\r\n#10\r\n",
26759                "$3\r\ns:3\r\n$-1\r\n"
26760            )
26761        );
26762        assert_eq!(
26763            f.run(&[
26764                b"FT.SEARCH",
26765                b"sy",
26766                b"alpha",
26767                b"SORTBY",
26768                b"t",
26769                b"WITHSORTKEYS",
26770                b"NOCONTENT"
26771            ]),
26772            concat!(
26773                "*7\r\n:3\r\n",
26774                "$3\r\ns:2\r\n$6\r\n$apple\r\n",
26775                "$3\r\ns:1\r\n$13\r\n$banana split\r\n",
26776                "$3\r\ns:3\r\n$-1\r\n"
26777            )
26778        );
26779        // Asking for a sort key without sorting is taken and answers a null on
26780        // every row, which is what a real server does.
26781        assert_eq!(
26782            f.run(&[
26783                b"FT.SEARCH",
26784                b"sy",
26785                b"banana",
26786                b"WITHSORTKEYS",
26787                b"NOCONTENT"
26788            ]),
26789            "*3\r\n:1\r\n$3\r\ns:1\r\n$-1\r\n"
26790        );
26791    }
26792
26793    /// The field a search sorted by is written in front of the fields of the
26794    /// key, and the key's own value for it wins when the two share a name.
26795    #[test]
26796    fn a_sort_puts_the_field_it_sorted_by_in_front_of_the_row() {
26797        let mut f = Fixture::new();
26798        sortable(&mut f);
26799        // `b` is what the schema calls the field the key calls `body`, so the
26800        // folded copy comes back under one name and the value as it was written
26801        // comes back under the other.
26802        assert_eq!(
26803            f.run(&[
26804                b"FT.SEARCH",
26805                b"sy",
26806                b"alpha",
26807                b"SORTBY",
26808                b"b",
26809                b"LIMIT",
26810                b"0",
26811                b"1"
26812            ]),
26813            concat!(
26814                "*3\r\n:3\r\n$3\r\ns:2\r\n*10\r\n",
26815                "$1\r\nb\r\n$5\r\napple\r\n",
26816                "$1\r\nt\r\n$5\r\napple\r\n",
26817                "$1\r\nn\r\n$2\r\n10\r\n",
26818                "$4\r\nbody\r\n$5\r\napple\r\n",
26819                "$1\r\np\r\n$5\r\nalpha\r\n"
26820            )
26821        );
26822        // With a `RETURN` list there is nothing to put in, so the field is moved
26823        // to the front of the names that were asked for instead.
26824        assert_eq!(
26825            f.run(&[
26826                b"FT.SEARCH",
26827                b"sy",
26828                b"alpha",
26829                b"SORTBY",
26830                b"b",
26831                b"RETURN",
26832                b"2",
26833                b"p",
26834                b"b",
26835                b"LIMIT",
26836                b"0",
26837                b"1"
26838            ]),
26839            concat!(
26840                "*3\r\n:3\r\n$3\r\ns:2\r\n*4\r\n",
26841                "$1\r\nb\r\n$5\r\napple\r\n",
26842                "$1\r\np\r\n$5\r\nalpha\r\n"
26843            )
26844        );
26845    }
26846
26847    /// The four ways a `SORTBY` on a search is refused.
26848    #[test]
26849    fn a_search_refuses_the_sorts_it_cannot_run() {
26850        let mut f = Fixture::new();
26851        sortable(&mut f);
26852        assert_eq!(
26853            f.run(&[b"FT.SEARCH", b"sy", b"alpha", b"SORTBY"]),
26854            "-SEARCH_PARSE_ARGS Bad SORTBY arguments\r\n"
26855        );
26856        assert_eq!(
26857            f.run(&[b"FT.SEARCH", b"sy", b"alpha", b"SORTBY", b"n", b"SORTBY"]),
26858            "-SEARCH_PARSE_ARGS Multiple SORTBY steps are not allowed\r\n"
26859        );
26860        assert_eq!(
26861            f.run(&[b"FT.SEARCH", b"sy", b"alpha", b"SORTBY", b"n", b"MAX", b"2"]),
26862            "-SEARCH_PARSE_ARGS SORTBY MAX is not supported by FT.SEARCH\r\n"
26863        );
26864        assert_eq!(
26865            f.run(&[b"FT.SEARCH", b"sy", b"alpha", b"SORTBY", b"zz"]),
26866            "-SEARCH_PROP_NOT_FOUND Property `zz` not loaded nor in schema\r\n"
26867        );
26868        // The property is looked up once the whole list has read cleanly, so a
26869        // word after it that nobody knows is the error that comes back.
26870        assert_eq!(
26871            f.run(&[b"FT.SEARCH", b"sy", b"alpha", b"SORTBY", b"zz", b"NOPE"]),
26872            "-SEARCH_ARG_UNRECOGNIZED Unknown argument `NOPE` at position 3 for <main>\r\n"
26873        );
26874    }
26875
26876    /// An index over two text fields, a number and a tag, holding one key whose
26877    /// `a` runs long enough to be worth cutting down and whose `b` and `g` hold
26878    /// nothing the query matches.
26879    fn marking(f: &mut Fixture) {
26880        f.run(&[
26881            b"FT.CREATE",
26882            b"mk",
26883            b"ON",
26884            b"HASH",
26885            b"PREFIX",
26886            b"1",
26887            b"m:",
26888            b"SCHEMA",
26889            b"a",
26890            b"TEXT",
26891            b"b",
26892            b"TEXT",
26893            b"n",
26894            b"NUMERIC",
26895            b"g",
26896            b"TAG",
26897        ]);
26898        f.run(&[
26899            b"HSET",
26900            b"m:1",
26901            b"a",
26902            b"c1 c2 c3 fox d1 d2 d3 d4 d5 d6 d7 d8 d9 fox e1 e2 e3",
26903            b"b",
26904            b"t1 t2 t3 t4 t5 t6 t7 t8",
26905            b"n",
26906            b"1",
26907            b"g",
26908            b"red",
26909        ]);
26910    }
26911
26912    /// A field the query matched comes back as fragments and a field it did not
26913    /// comes back as its own front.
26914    #[test]
26915    fn a_summarize_cuts_a_field_down_to_what_matched() {
26916        let mut f = Fixture::new();
26917        marking(&mut f);
26918        let got = f.run(&[b"FT.SEARCH", b"mk", b"fox", b"SUMMARIZE", b"LEN", b"2"]);
26919        assert!(got.contains("c3 fox d1 d2... d9 fox e1 e2... "), "{got}");
26920        // `b` holds no match, so it keeps its front and loses its last word.
26921        assert!(got.contains("t1 t2 t3 t4 t5 t6 t7\r\n"), "{got}");
26922        // And so does the tag, which is a value like any other to this clause.
26923        assert!(got.contains("$1\r\nr\r\n"), "{got}");
26924    }
26925
26926    /// `FRAGS` is applied before the context either side of a fragment is worked
26927    /// out, so the fragment that is left runs over the match of the one that was
26928    /// dropped rather than stopping on it.
26929    #[test]
26930    fn a_dropped_fragment_stops_bounding_the_one_that_was_kept() {
26931        let mut f = Fixture::new();
26932        marking(&mut f);
26933        let got = f.run(&[
26934            b"FT.SEARCH",
26935            b"mk",
26936            b"fox",
26937            b"SUMMARIZE",
26938            b"FRAGS",
26939            b"1",
26940            b"LEN",
26941            b"20",
26942        ]);
26943        assert!(
26944            got.contains("c2 c3 fox d1 d2 d3 d4 d5 d6 d7 d8 d9 fox e1 e2... "),
26945            "{got}"
26946        );
26947        // Keep both and the first stops on the second rather than running over
26948        // it, on the same query and the same budget.
26949        let two = f.run(&[
26950            b"FT.SEARCH",
26951            b"mk",
26952            b"fox",
26953            b"SUMMARIZE",
26954            b"FRAGS",
26955            b"2",
26956            b"LEN",
26957            b"20",
26958        ]);
26959        assert!(
26960            two.contains("c2 c3 fox d1 d2 d3 d4 d5 d6 d7 d8 d9... d1"),
26961            "{two}"
26962        );
26963    }
26964
26965    /// A `HIGHLIGHT` wraps every match, and on a field with no match in it the
26966    /// clause also calls off the cutting down a `SUMMARIZE` would have done.
26967    #[test]
26968    fn a_highlight_marks_the_matches_and_leaves_the_rest_of_the_field_alone() {
26969        let mut f = Fixture::new();
26970        marking(&mut f);
26971        let got = f.run(&[b"FT.SEARCH", b"mk", b"fox", b"HIGHLIGHT"]);
26972        assert!(got.contains("<b>fox</b> d1 d2"), "{got}");
26973        let both = f.run(&[
26974            b"FT.SEARCH",
26975            b"mk",
26976            b"fox",
26977            b"SUMMARIZE",
26978            b"LEN",
26979            b"2",
26980            b"HIGHLIGHT",
26981        ]);
26982        assert!(both.contains("c3 <b>fox</b> d1 d2... "), "{both}");
26983        // `b` still holds no match, and this time it comes back whole.
26984        assert!(both.contains("t1 t2 t3 t4 t5 t6 t7 t8\r\n"), "{both}");
26985        assert!(both.contains("$3\r\nred\r\n"), "{both}");
26986        // Naming a field one clause does not cover leaves it cut down again.
26987        let split = f.run(&[
26988            b"FT.SEARCH",
26989            b"mk",
26990            b"fox",
26991            b"SUMMARIZE",
26992            b"FIELDS",
26993            b"1",
26994            b"b",
26995            b"LEN",
26996            b"2",
26997            b"HIGHLIGHT",
26998            b"FIELDS",
26999            b"1",
27000            b"a",
27001        ]);
27002        assert!(split.contains("t1 t2 t3 t4 t5 t6 t7\r\n"), "{split}");
27003    }
27004
27005    /// A tag is never marked, in its own field or in a text field beside it.
27006    #[test]
27007    fn a_highlight_does_not_mark_a_tag() {
27008        let mut f = Fixture::new();
27009        marking(&mut f);
27010        f.run(&[b"HSET", b"m:1", b"b", b"red and blue"]);
27011        let got = f.run(&[b"FT.SEARCH", b"mk", b"@g:{red}", b"HIGHLIGHT"]);
27012        assert!(!got.contains("<b>"), "{got}");
27013        assert!(got.contains("red and blue"), "{got}");
27014    }
27015
27016    /// A search answers a total and then a row for every key in the window,
27017    /// with the fields of that key after it.
27018    #[test]
27019    fn a_search_answers_a_total_and_then_the_rows() {
27020        let mut f = Fixture::new();
27021        corpus(&mut f);
27022        assert_eq!(
27023            f.run(&[b"FT.SEARCH", b"sx", b"delta"]),
27024            "*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"
27025        );
27026        // The fields are what the key holds and not what the schema names, so
27027        // a field nobody indexed comes back too.
27028        f.run(&[b"HSET", b"d:3", b"extra", b"more"]);
27029        assert!(f.run(&[b"FT.SEARCH", b"sx", b"delta"]).contains("extra"));
27030        // `NOCONTENT` leaves the keys on their own, and `LIMIT 0 0` leaves
27031        // the total on its own.
27032        assert_eq!(
27033            f.run(&[b"FT.SEARCH", b"sx", b"delta", b"NOCONTENT"]),
27034            "*2\r\n:1\r\n$3\r\nd:3\r\n"
27035        );
27036        assert_eq!(
27037            f.run(&[b"FT.SEARCH", b"sx", b"alpha", b"LIMIT", b"0", b"0"]),
27038            "*1\r\n:3\r\n"
27039        );
27040    }
27041
27042    /// The window is ten rows when nobody said, and the cap is on how wide it
27043    /// is rather than on where it starts.
27044    #[test]
27045    fn the_window_is_ten_rows_and_a_million_wide_at_most() {
27046        let mut f = Fixture::new();
27047        corpus(&mut f);
27048        assert_eq!(
27049            f.run(&[
27050                b"FT.SEARCH",
27051                b"sx",
27052                b"alpha",
27053                b"NOCONTENT",
27054                b"LIMIT",
27055                b"1",
27056                b"1"
27057            ]),
27058            "*2\r\n:3\r\n$3\r\nd:2\r\n"
27059        );
27060        assert_eq!(
27061            f.run(&[b"FT.SEARCH", b"sx", b"alpha", b"LIMIT", b"0"]),
27062            "-SEARCH_PARSE_ARGS LIMIT requires two arguments\r\n"
27063        );
27064        assert_eq!(
27065            f.run(&[b"FT.SEARCH", b"sx", b"alpha", b"LIMIT", b"0", b"-1"]),
27066            "-SEARCH_PARSE_ARGS LIMIT needs two numeric arguments\r\n"
27067        );
27068        assert_eq!(
27069            f.run(&[b"FT.SEARCH", b"sx", b"alpha", b"LIMIT", b"0", b"1000001"]),
27070            "-SEARCH_LIMIT_OVER LIMIT exceeds maximum of 1000000\r\n"
27071        );
27072        assert_eq!(
27073            f.run(&[
27074                b"FT.SEARCH",
27075                b"sx",
27076                b"alpha",
27077                b"NOCONTENT",
27078                b"LIMIT",
27079                b"999999",
27080                b"1000000"
27081            ]),
27082            "*1\r\n:3\r\n"
27083        );
27084    }
27085
27086    /// `RETURN 0` reads on the wire like `NOCONTENT` and is not the same
27087    /// thing, because a later `RETURN` puts the fields back and a later
27088    /// `RETURN` after a `NOCONTENT` does not.
27089    #[test]
27090    fn a_return_of_nothing_is_not_the_same_as_nocontent() {
27091        let mut f = Fixture::new();
27092        corpus(&mut f);
27093        let bare = "*2\r\n:1\r\n$3\r\nd:3\r\n";
27094        assert_eq!(
27095            f.run(&[b"FT.SEARCH", b"sx", b"delta", b"RETURN", b"0"]),
27096            bare
27097        );
27098        assert_eq!(
27099            f.run(&[
27100                b"FT.SEARCH",
27101                b"sx",
27102                b"delta",
27103                b"NOCONTENT",
27104                b"RETURN",
27105                b"1",
27106                b"t"
27107            ]),
27108            bare
27109        );
27110        assert_eq!(
27111            f.run(&[
27112                b"FT.SEARCH",
27113                b"sx",
27114                b"delta",
27115                b"RETURN",
27116                b"0",
27117                b"RETURN",
27118                b"1",
27119                b"t"
27120            ]),
27121            "*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"
27122        );
27123    }
27124
27125    /// The count after `RETURN` counts words and not fields, so the `AS` and
27126    /// the name after it are two of them.
27127    #[test]
27128    fn the_count_after_return_counts_words() {
27129        let mut f = Fixture::new();
27130        corpus(&mut f);
27131        // Two words is one renamed field, and the name is the one it comes
27132        // back under.
27133        assert_eq!(
27134            f.run(&[
27135                b"FT.SEARCH",
27136                b"sx",
27137                b"delta",
27138                b"RETURN",
27139                b"3",
27140                b"t",
27141                b"AS",
27142                b"x"
27143            ]),
27144            "*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"
27145        );
27146        // A count that stops on the `AS` has nothing to rename to, and one
27147        // that reaches past the last word is short an argument.
27148        assert_eq!(
27149            f.run(&[b"FT.SEARCH", b"sx", b"delta", b"RETURN", b"2", b"t", b"AS"]),
27150            "-SEARCH_PARSE_ARGS RETURN path AS name - must be accompanied with NAME\r\n"
27151        );
27152        assert_eq!(
27153            f.run(&[b"FT.SEARCH", b"sx", b"delta", b"RETURN", b"3", b"t", b"AS"]),
27154            "-SEARCH_PARSE_ARGS Bad arguments for RETURN: Expected an argument, but none provided\r\n"
27155        );
27156        // A count that stops before the `AS` asks for a field called `AS`,
27157        // which no key holds, and a field the key does not hold is left out
27158        // rather than sent empty.
27159        assert_eq!(
27160            f.run(&[b"FT.SEARCH", b"sx", b"delta", b"RETURN", b"1", b"AS"]),
27161            "*3\r\n:1\r\n$3\r\nd:3\r\n*0\r\n"
27162        );
27163    }
27164
27165    /// A `FILTER` is a numeric range written outside the query, and it is only
27166    /// the wrong way round on a field the schema holds as a number.
27167    #[test]
27168    fn a_filter_is_a_range_written_outside_the_query() {
27169        let mut f = Fixture::new();
27170        corpus(&mut f);
27171        assert_eq!(
27172            f.run(&[
27173                b"FT.SEARCH",
27174                b"sx",
27175                b"alpha",
27176                b"NOCONTENT",
27177                b"FILTER",
27178                b"n",
27179                b"2",
27180                b"4"
27181            ]),
27182            "*3\r\n:2\r\n$3\r\nd:2\r\n$3\r\nd:4\r\n"
27183        );
27184        assert_eq!(
27185            f.run(&[b"FT.SEARCH", b"sx", b"alpha", b"FILTER", b"n", b"2"]),
27186            "-SEARCH_PARSE_ARGS FILTER requires 3 arguments\r\n"
27187        );
27188        assert_eq!(
27189            f.run(&[b"FT.SEARCH", b"sx", b"alpha", b"FILTER", b"n", b"x", b"1"]),
27190            "-SEARCH_PARSE_ARGS Bad lower range: x\r\n"
27191        );
27192        assert_eq!(
27193            f.run(&[b"FT.SEARCH", b"sx", b"alpha", b"FILTER", b"n", b"2", b"1"]),
27194            "-SEARCH_SYNTAX Invalid numeric range (min > max): @n:[2.000000 1.000000]\r\n"
27195        );
27196        // The same range on a field that is not a number at all, and on a
27197        // field that is not there, answers nothing rather than refusing.
27198        for field in [b"g".as_slice(), b"nope"] {
27199            assert_eq!(
27200                f.run(&[b"FT.SEARCH", b"sx", b"alpha", b"FILTER", field, b"2", b"1"]),
27201                "*1\r\n:0\r\n"
27202            );
27203        }
27204    }
27205
27206    /// The index is resolved before the arguments after it are read, so a name
27207    /// that is not there answers about the name whatever else is wrong.
27208    #[test]
27209    fn the_index_is_found_before_the_arguments_are_read() {
27210        let mut f = Fixture::new();
27211        corpus(&mut f);
27212        let missing = "-SEARCH_INDEX_NOT_FOUND Index not found: nope\r\n";
27213        assert_eq!(f.run(&[b"FT.SEARCH", b"nope", b"alpha", b"BOGUS"]), missing);
27214        assert_eq!(
27215            f.run(&[b"FT.EXPLAIN", b"nope", b"alpha", b"BOGUS"]),
27216            missing
27217        );
27218        // And the arguments are read before the query is, so a query that
27219        // will not parse still answers about the argument.
27220        assert_eq!(
27221            f.run(&[b"FT.SEARCH", b"sx", b"@@@", b"BOGUS"]),
27222            "-SEARCH_ARG_UNRECOGNIZED Unknown argument `BOGUS` at position 1 for <main>\r\n"
27223        );
27224    }
27225
27226    /// `INKEYS` filters the answer before the total is taken, which is not
27227    /// where a client would guess it happens.
27228    #[test]
27229    fn inkeys_comes_off_the_total() {
27230        let mut f = Fixture::new();
27231        corpus(&mut f);
27232        assert_eq!(
27233            f.run(&[
27234                b"FT.SEARCH",
27235                b"sx",
27236                b"alpha",
27237                b"NOCONTENT",
27238                b"INKEYS",
27239                b"1",
27240                b"d:1"
27241            ]),
27242            "*2\r\n:1\r\n$3\r\nd:1\r\n"
27243        );
27244        assert_eq!(
27245            f.run(&[b"FT.SEARCH", b"sx", b"alpha", b"NOCONTENT", b"INKEYS", b"0"]),
27246            "*1\r\n:0\r\n"
27247        );
27248    }
27249
27250    /// The fields come from the database the session is on, and a row whose
27251    /// key will not load there is dropped from the reply and taken off the
27252    /// total.
27253    ///
27254    /// Measured against a real server, which follows a key on every database
27255    /// and then loads it from one.
27256    #[test]
27257    fn the_fields_are_read_from_the_session_database() {
27258        let mut f = Fixture::new();
27259        corpus(&mut f);
27260        f.run(&[b"SELECT", b"1"]);
27261        f.run(&[b"HSET", b"d:9", b"t", b"delta", b"n", b"9"]);
27262        // Both documents are in the index, and only one of them is in this
27263        // database.
27264        assert_eq!(
27265            f.run(&[b"FT.SEARCH", b"sx", b"delta", b"NOCONTENT"]),
27266            "*3\r\n:2\r\n$3\r\nd:3\r\n$3\r\nd:9\r\n"
27267        );
27268        assert_eq!(
27269            f.run(&[b"FT.SEARCH", b"sx", b"delta", b"RETURN", b"1", b"n"]),
27270            "*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"
27271        );
27272    }
27273
27274    /// The deeper protocol answers a map of five rather than an array, with
27275    /// every row a map of its own.
27276    #[test]
27277    fn the_third_protocol_answers_a_map_of_five() {
27278        let mut f = Fixture::new();
27279        corpus(&mut f);
27280        f.out = Out::new(Proto::Resp3);
27281        assert_eq!(
27282            f.run(&[b"FT.SEARCH", b"sx", b"delta", b"RETURN", b"1", b"n"]),
27283            concat!(
27284                "%5\r\n+attributes\r\n*0\r\n+format\r\n+STRING\r\n+results\r\n*1\r\n",
27285                "%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",
27286                "+values\r\n*0\r\n+total_results\r\n:1\r\n+warning\r\n*0\r\n"
27287            )
27288        );
27289    }
27290
27291    /// A window of nothing is a client asking for the count on its own, and a
27292    /// window of nothing that starts somewhere else is a contradiction all
27293    /// three commands refuse in the same words.
27294    #[test]
27295    fn a_window_of_nothing_has_to_start_at_the_top() {
27296        let mut f = Fixture::new();
27297        corpus(&mut f);
27298        let refused = "-SEARCH_LIMIT_OVER The `offset` of the LIMIT must be 0 when `num` is 0\r\n";
27299        assert_eq!(
27300            f.run(&[b"FT.SEARCH", b"sx", b"alpha", b"LIMIT", b"1", b"0"]),
27301            refused
27302        );
27303        assert_eq!(
27304            f.run(&[b"FT.EXPLAIN", b"sx", b"alpha", b"LIMIT", b"1", b"0"]),
27305            refused
27306        );
27307        assert_eq!(
27308            f.run(&[b"FT.AGGREGATE", b"sx", b"alpha", b"LIMIT", b"1", b"0"]),
27309            refused
27310        );
27311        assert_eq!(
27312            f.run(&[b"FT.AGGREGATE", b"sx", b"alpha", b"LIMIT", b"0", b"0"]),
27313            "*1\r\n:3\r\n"
27314        );
27315    }
27316
27317    /// An aggregation answers a count and then a list of properties for every
27318    /// row, which is empty until something asks for a field.
27319    #[test]
27320    fn an_aggregation_answers_a_count_and_then_the_properties() {
27321        let mut f = Fixture::new();
27322        corpus(&mut f);
27323        assert_eq!(
27324            f.run(&[b"FT.AGGREGATE", b"sx", b"alpha"]),
27325            "*4\r\n:1\r\n*0\r\n*0\r\n*0\r\n"
27326        );
27327        // Every row, and not the ten a search would have cut it down to. The
27328        // count in front of them is one because that is how far the reply had
27329        // got when it was written, which is measured against a real server.
27330        assert_eq!(
27331            f.run(&[b"FT.AGGREGATE", b"sx", b"alpha", b"LOAD", b"1", b"@t"]),
27332            concat!(
27333                "*4\r\n:1\r\n*2\r\n$1\r\nt\r\n$10\r\nalpha beta\r\n",
27334                "*2\r\n$1\r\nt\r\n$11\r\nalpha gamma\r\n",
27335                "*2\r\n$1\r\nt\r\n$16\r\nalpha beta gamma\r\n"
27336            )
27337        );
27338        // Ascending document number, because nothing sorts the answer. The
27339        // second and fourth documents are the ones the window lands on and the
27340        // best scoring one is not among them.
27341        assert_eq!(
27342            f.run(&[
27343                b"FT.AGGREGATE",
27344                b"sx",
27345                b"alpha",
27346                b"LOAD",
27347                b"1",
27348                b"@n",
27349                b"LIMIT",
27350                b"1",
27351                b"2"
27352            ]),
27353            "*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"
27354        );
27355        // A query nothing answers is a count of nothing and no rows at all.
27356        assert_eq!(
27357            f.run(&[b"FT.AGGREGATE", b"sx", b"nope", b"LOAD", b"1", b"@t"]),
27358            "*1\r\n:0\r\n"
27359        );
27360    }
27361
27362    /// `LOAD` counts words rather than fields, names the property after the
27363    /// path unless an `AS` renames it, and reads everything the key holds when
27364    /// it is given a star.
27365    #[test]
27366    fn a_load_counts_words_and_can_rename_what_it_reads() {
27367        let mut f = Fixture::new();
27368        corpus(&mut f);
27369        // Three words, which are the path, the `AS` and the name.
27370        assert_eq!(
27371            f.run(&[
27372                b"FT.AGGREGATE",
27373                b"sx",
27374                b"alpha",
27375                b"LOAD",
27376                b"3",
27377                b"@t",
27378                b"AS",
27379                b"text"
27380            ]),
27381            concat!(
27382                "*4\r\n:1\r\n*2\r\n$4\r\ntext\r\n$10\r\nalpha beta\r\n",
27383                "*2\r\n$4\r\ntext\r\n$11\r\nalpha gamma\r\n",
27384                "*2\r\n$4\r\ntext\r\n$16\r\nalpha beta gamma\r\n"
27385            )
27386        );
27387        assert_eq!(
27388            f.run(&[
27389                b"FT.AGGREGATE",
27390                b"sx",
27391                b"alpha",
27392                b"LOAD",
27393                b"*",
27394                b"LIMIT",
27395                b"0",
27396                b"1"
27397            ]),
27398            concat!(
27399                "*2\r\n:1\r\n*6\r\n$1\r\nt\r\n$10\r\nalpha beta\r\n",
27400                "$1\r\ng\r\n$5\r\naa,bb\r\n$1\r\nn\r\n$1\r\n1\r\n"
27401            )
27402        );
27403        // A field the key does not hold is left out rather than sent empty.
27404        assert_eq!(
27405            f.run(&[
27406                b"FT.AGGREGATE",
27407                b"sx",
27408                b"alpha",
27409                b"LOAD",
27410                b"2",
27411                b"@n",
27412                b"@nope",
27413                b"LIMIT",
27414                b"0",
27415                b"2"
27416            ]),
27417            "*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"
27418        );
27419    }
27420
27421    /// The `LOAD` grammar, which has four ways to go wrong and one of them is
27422    /// only reported once the rest of the argument list has read cleanly.
27423    #[test]
27424    fn a_load_refuses_a_count_it_cannot_use() {
27425        let mut f = Fixture::new();
27426        corpus(&mut f);
27427        let head = "-SEARCH_PARSE_ARGS Bad arguments for LOAD: ";
27428        assert_eq!(
27429            f.run(&[b"FT.AGGREGATE", b"sx", b"alpha", b"LOAD", b"x"]),
27430            format!("{head}Expected number of fields or `*`\r\n")
27431        );
27432        assert_eq!(
27433            f.run(&[b"FT.AGGREGATE", b"sx", b"alpha", b"LOAD", b"-1", b"@t"]),
27434            format!("{head}Value is outside acceptable bounds\r\n")
27435        );
27436        assert_eq!(
27437            f.run(&[b"FT.AGGREGATE", b"sx", b"alpha", b"LOAD", b"5", b"@t"]),
27438            format!("{head}Expected an argument, but none provided\r\n")
27439        );
27440        assert_eq!(
27441            f.run(&[b"FT.AGGREGATE", b"sx", b"alpha", b"LOAD"]),
27442            format!("{head}Expected an argument, but none provided\r\n")
27443        );
27444        // A count that runs out on the `AS` is held back, because the word
27445        // after it is read as an argument of its own and may be worth an error
27446        // of its own. Nothing follows here, so the held back line is the one.
27447        assert_eq!(
27448            f.run(&[
27449                b"FT.AGGREGATE",
27450                b"sx",
27451                b"alpha",
27452                b"LOAD",
27453                b"2",
27454                b"@t",
27455                b"AS"
27456            ]),
27457            "-SEARCH_PARSE_ARGS LOAD path AS name - must be accompanied with NAME\r\n"
27458        );
27459        // And here the word after it is one an aggregation stops taking once a
27460        // step has been read, so that is what the client hears about.
27461        assert_eq!(
27462            f.run(&[
27463                b"FT.AGGREGATE",
27464                b"sx",
27465                b"alpha",
27466                b"LOAD",
27467                b"2",
27468                b"@t",
27469                b"AS",
27470                b"VERBATIM"
27471            ]),
27472            "-SEARCH_ARG_UNRECOGNIZED Unknown argument `VERBATIM` at position 5 for <main>\r\n"
27473        );
27474        // A `LOAD 0` is a step that names nothing. It shuts the same door
27475        // without becoming a loader, so the count stays the one a query with no
27476        // `LOAD` gets.
27477        assert_eq!(
27478            f.run(&[
27479                b"FT.AGGREGATE",
27480                b"sx",
27481                b"alpha",
27482                b"LOAD",
27483                b"0",
27484                b"LIMIT",
27485                b"0",
27486                b"1"
27487            ]),
27488            "*2\r\n:1\r\n*0\r\n"
27489        );
27490    }
27491
27492    /// Reading a step of the pipeline stops the words about the search itself
27493    /// being taken, and `LIMIT` and `TIMEOUT` are not steps.
27494    #[test]
27495    fn a_pipeline_step_closes_the_door_on_the_search_words() {
27496        let mut f = Fixture::new();
27497        corpus(&mut f);
27498        assert_eq!(
27499            f.run(&[
27500                b"FT.AGGREGATE",
27501                b"sx",
27502                b"alpha",
27503                b"LOAD",
27504                b"1",
27505                b"@t",
27506                b"VERBATIM"
27507            ]),
27508            "-SEARCH_ARG_UNRECOGNIZED Unknown argument `VERBATIM` at position 4 for <main>\r\n"
27509        );
27510        assert_eq!(
27511            f.run(&[
27512                b"FT.AGGREGATE",
27513                b"sx",
27514                b"alpha",
27515                b"LIMIT",
27516                b"0",
27517                b"1",
27518                b"VERBATIM"
27519            ]),
27520            "*2\r\n:1\r\n*0\r\n"
27521        );
27522        // Three words a search takes that this command names in its refusal
27523        // rather than calling them unknown.
27524        for word in [b"RETURN".as_slice(), b"SUMMARIZE", b"HIGHLIGHT"] {
27525            let name = core::str::from_utf8(word).expect("the three words are text");
27526            assert_eq!(
27527                f.run(&[b"FT.AGGREGATE", b"sx", b"alpha", word]),
27528                format!("-SEARCH_PARSE_ARGS {name} is not supported on FT.AGGREGATE\r\n")
27529            );
27530        }
27531    }
27532
27533    /// `ADDSCORES` writes the score as a property to twelve significant digits
27534    /// where `WITHSCORES` writes it beside the row in full.
27535    #[test]
27536    fn addscores_writes_a_shorter_score_than_withscores() {
27537        let mut f = Fixture::new();
27538        corpus(&mut f);
27539        assert_eq!(
27540            f.run(&[
27541                b"FT.AGGREGATE",
27542                b"sx",
27543                b"alpha",
27544                b"ADDSCORES",
27545                b"LOAD",
27546                b"1",
27547                b"@n",
27548                b"LIMIT",
27549                b"0",
27550                b"2"
27551            ]),
27552            concat!(
27553                "*3\r\n:1\r\n",
27554                "*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",
27555                "*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"
27556            )
27557        );
27558        // `NOCONTENT` takes the properties away and leaves whatever was asked
27559        // for beside them, and a sort key is always null because nothing sorts
27560        // by one yet.
27561        assert_eq!(
27562            f.run(&[
27563                b"FT.AGGREGATE",
27564                b"sx",
27565                b"alpha",
27566                b"NOCONTENT",
27567                b"WITHSCORES",
27568                b"LIMIT",
27569                b"0",
27570                b"2"
27571            ]),
27572            "*3\r\n:1\r\n$18\r\n0.3566749439387324\r\n$18\r\n0.3566749439387324\r\n"
27573        );
27574        assert_eq!(
27575            f.run(&[
27576                b"FT.AGGREGATE",
27577                b"sx",
27578                b"alpha",
27579                b"WITHSORTKEYS",
27580                b"LOAD",
27581                b"1",
27582                b"@n",
27583                b"LIMIT",
27584                b"0",
27585                b"1"
27586            ]),
27587            "*3\r\n:1\r\n$-1\r\n*2\r\n$1\r\nn\r\n$1\r\n1\r\n"
27588        );
27589    }
27590
27591    /// The one scorer that has to see the whole answer first turns the count
27592    /// into the real total and hands the rows back backwards.
27593    #[test]
27594    fn a_normalising_scorer_answers_the_rows_backwards() {
27595        let mut f = Fixture::new();
27596        corpus(&mut f);
27597        assert_eq!(
27598            f.run(&[
27599                b"FT.AGGREGATE",
27600                b"sx",
27601                b"alpha",
27602                b"SCORER",
27603                b"BM25STD.NORM",
27604                b"ADDSCORES",
27605                b"LOAD",
27606                b"1",
27607                b"@n",
27608                b"LIMIT",
27609                b"1",
27610                b"2"
27611            ]),
27612            concat!(
27613                "*3\r\n:3\r\n",
27614                "*4\r\n$7\r\n__score\r\n$1\r\n1\r\n$1\r\nn\r\n$1\r\n2\r\n",
27615                "*4\r\n$7\r\n__score\r\n$1\r\n1\r\n$1\r\nn\r\n$1\r\n1\r\n"
27616            )
27617        );
27618        // Without `ADDSCORES` nothing on the row needs the score, so the rows
27619        // come back the way every other query answers them.
27620        assert_eq!(
27621            f.run(&[
27622                b"FT.AGGREGATE",
27623                b"sx",
27624                b"alpha",
27625                b"SCORER",
27626                b"BM25STD.NORM",
27627                b"LOAD",
27628                b"1",
27629                b"@n",
27630                b"LIMIT",
27631                b"1",
27632                b"2"
27633            ]),
27634            "*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"
27635        );
27636    }
27637
27638    /// The deeper protocol answers the same map of five a search answers, with
27639    /// the `id` gone because an aggregation is about the properties.
27640    #[test]
27641    fn an_aggregation_answers_a_map_of_five_as_well() {
27642        let mut f = Fixture::new();
27643        corpus(&mut f);
27644        f.out = Out::new(Proto::Resp3);
27645        assert_eq!(
27646            f.run(&[
27647                b"FT.AGGREGATE",
27648                b"sx",
27649                b"alpha",
27650                b"ADDSCORES",
27651                b"WITHSCORES",
27652                b"WITHSORTKEYS",
27653                b"LOAD",
27654                b"1",
27655                b"@n",
27656                b"LIMIT",
27657                b"0",
27658                b"1"
27659            ]),
27660            concat!(
27661                "%5\r\n+attributes\r\n*0\r\n+format\r\n+STRING\r\n+results\r\n*1\r\n",
27662                "%4\r\n+score\r\n,0.3566749439387324\r\n+sortkey\r\n_\r\n",
27663                "+extra_attributes\r\n%2\r\n$7\r\n__score\r\n$14\r\n0.356674943939\r\n",
27664                "$1\r\nn\r\n$1\r\n1\r\n+values\r\n*0\r\n",
27665                "+total_results\r\n:1\r\n+warning\r\n*0\r\n"
27666            )
27667        );
27668        // The count is worked out from the rows the reply reached under this
27669        // protocol, where under RESP2 it is worked out from the first of them.
27670        assert_eq!(
27671            f.run(&[
27672                b"FT.AGGREGATE",
27673                b"sx",
27674                b"alpha",
27675                b"NOCONTENT",
27676                b"LIMIT",
27677                b"0",
27678                b"1"
27679            ]),
27680            concat!(
27681                "%5\r\n+attributes\r\n*0\r\n+format\r\n+STRING\r\n+results\r\n*1\r\n",
27682                "%1\r\n+values\r\n*0\r\n+total_results\r\n:1\r\n+warning\r\n*0\r\n"
27683            )
27684        );
27685    }
27686}