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 migrate;
73mod scan;
74mod scripting;
75mod search;
76mod server;
77mod sets;
78mod streams;
79mod strings;
80mod suggest;
81pub mod table;
82mod tdigest;
83mod topk;
84mod ts;
85mod vectors;
86mod vfilter;
87mod zsets;
88
89pub use args::Args;
90pub use blocking::{Parked, Waiters};
91pub use server::parse_memory;
92pub use table::{COMMANDS, Spec, arity_ok, lookup};
93
94use crate::reply::Out;
95use std::cell::Cell;
96use std::path::{Path, PathBuf};
97use std::sync::atomic::Ordering::{Acquire, Relaxed, Release};
98use std::sync::atomic::{AtomicBool, AtomicU64, AtomicUsize};
99use yo_common::lock::{Held, Lock};
100use yo_common::{Code, Error};
101use yo_kv::cold::Store;
102use yo_kv::{Clock, Db, Keyspace};
103use yo_search::Registry;
104
105use search::cursor::Cursors;
106
107/// How many databases a server has.
108///
109/// Redis's default is sixteen and its `databases` setting can change it. Ours
110/// is sixteen and cannot, which is why `CONFIG GET databases` can answer with a
111/// constant. Nothing in the design needs the number to be fixed; nothing yet
112/// needs it not to be.
113pub const DATABASES: usize = 16;
114
115/// Every database's bit in [`Server::dirty`], which is what a fresh server
116/// starts on so that the first maintenance turn asks all of them.
117///
118/// A `u64` holds sixteen bits with room to spare, and the assertion below is
119/// what turns raising [`DATABASES`] past sixty four into a build failure rather
120/// than a shift that silently drops the databases past the end.
121const ALL_DATABASES: u64 = if DATABASES == 64 {
122    u64::MAX
123} else {
124    (1u64 << DATABASES) - 1
125};
126const _: () = assert!(DATABASES <= 64);
127
128/// How many keys one command throws away before it leaves the rest to the next.
129///
130/// A bound and not a loop to the end, because this runs in front of a client
131/// that is waiting for its reply, and a server a long way over its limit would
132/// otherwise hold that client for as long as it took to walk all the way back
133/// under. Sixty four is a batch's worth of commands, so a server that went over
134/// by what one batch allocated comes back under in one command, and a server
135/// whose limit was just cut in half works through it over the next few thousand
136/// rather than in one long stall. Redis bounds the same loop by a time slice
137/// instead of a count and hands the rest to a timer; there is no timer here, so
138/// the rest goes to the next command that runs.
139const EVICT_BUDGET: usize = 64;
140
141/// The `maxstore` a server with no storage limit carries.
142///
143/// Sixteen exabytes, which is every disk there is and then some, so a server
144/// that set a limit this high and a server that set none behave the same way and
145/// the only difference is what `CONFIG GET maxstore` says. Zero cannot be the
146/// sentinel because zero is a limit with a meaning: nothing may live on the
147/// file.
148const NO_MAXSTORE: u64 = u64::MAX;
149
150/// What a server says to a command that would allocate when it has no room.
151///
152/// Redis's `shared.oomerr`, word for word including the full stop, because
153/// clients match on the `OOM` prefix and people match on the sentence.
154const OOM: &[u8] = b"command not allowed when used memory > 'maxmemory'.";
155
156/// What the connection should do after a command.
157#[derive(Debug, Clone, Copy, PartialEq, Eq)]
158pub enum Flow {
159    /// Read the next command.
160    Continue,
161    /// Write what is buffered and then close, which is what `QUIT` asks for.
162    Close,
163    /// Nothing was written and nothing is owed yet.
164    ///
165    /// The client is on the waiter list and its reply comes when a key it named
166    /// has something in it or when its deadline passes, whichever happens first.
167    /// Until then the connection stops reading commands, because a client that
168    /// is waiting for an answer is not a client that has sent another question.
169    Block,
170}
171
172/// A number one thread adds to and any thread may read.
173///
174/// The add is a load, an add and a store rather than a fetch and add, which on
175/// x86 is three ordinary instructions instead of one locked one. That is sound
176/// because every counter here has exactly one writer, which is what the slots
177/// below are for: two threads never hold the same counter, so nothing can be
178/// lost between the load and the store. A reader can be a command or two behind,
179/// and `INFO` on a running server is behind by the time the reply reaches the
180/// client anyway.
181#[derive(Debug, Default)]
182pub struct Counter(AtomicU64);
183
184impl Counter {
185    /// One more.
186    fn bump(&self) {
187        self.0.store(self.get().wrapping_add(1), Relaxed);
188    }
189
190    /// One fewer, stopping at zero.
191    ///
192    /// The floor is for the gauge, which is the number of open connections: a
193    /// close that arrives without its open, which nothing can do now and a
194    /// misplaced call could, is a number that stays at zero rather than one
195    /// that wraps to eighteen quintillion clients.
196    fn drop_one(&self) {
197        self.0.store(self.get().saturating_sub(1), Relaxed);
198    }
199
200    /// What it says.
201    fn get(&self) -> u64 {
202        self.0.load(Relaxed)
203    }
204
205    /// Back to zero, which is `CONFIG RESETSTAT`.
206    fn zero(&self) {
207        self.0.store(0, Relaxed);
208    }
209}
210
211/// The numbers `INFO` reports that this layer cannot see for itself.
212///
213/// The reactor owns the sockets, so the reactor is what knows how many clients
214/// there are. It counts them here and nothing else does anything with them
215/// except report them.
216#[derive(Debug, Default)]
217pub struct Stats {
218    /// Connections open right now.
219    clients: Counter,
220    /// Connections accepted since the server started.
221    connections: Counter,
222    /// Commands run since the server started, which this layer counts itself.
223    commands: Counter,
224}
225
226impl Stats {
227    /// A connection arrived.
228    pub fn opened(&self) {
229        self.clients.bump();
230        self.connections.bump();
231    }
232
233    /// A connection went away.
234    pub fn closed(&self) {
235        self.clients.drop_one();
236    }
237}
238
239/// Every thread's [`Stats`] added together, which is what `INFO` answers.
240#[derive(Debug, Clone, Copy, Default)]
241pub struct Totals {
242    /// Connections open right now.
243    pub clients: u64,
244    /// Connections accepted since the server started.
245    pub connections: u64,
246    /// Commands run since the server started.
247    pub commands: u64,
248}
249
250thread_local! {
251    /// Which set of counters the running thread writes into.
252    ///
253    /// Claimed the first time a thread counts anything and kept for as long as
254    /// the thread runs. It is a number rather than a pointer, so a thread that
255    /// has counted on one server and then counts on another lands in the same
256    /// place in both, and a process with two servers in it shares the numbering
257    /// between them. That is the tests and it is not `yodb`, which has one.
258    static SLOT: Cell<usize> = const { Cell::new(usize::MAX) };
259}
260
261/// What one thread keeps to itself.
262///
263/// One of these per thread and not one per server, because a number every
264/// thread writes to is a cache line every thread has to own to write to it, and
265/// at a few million commands a second that one line is the server. So each
266/// thread writes into its own and whoever needs the whole picture, which is
267/// `INFO` and the maintenance turn, puts the pieces together when it asks.
268///
269/// A cache line apart for the same reason, so that two threads writing at once
270/// are not two threads passing one line back and forth.
271#[derive(Debug)]
272#[repr(align(64))]
273struct Local {
274    /// What the reactor counts.
275    stats: Stats,
276    /// A counter per command, for `INFO commandstats`.
277    cmdstats: CommandStats,
278    /// Which databases this thread has run a command against since the
279    /// maintenance turn last took the mask.
280    ///
281    /// One bit per database. The thread ors into it and the turn takes the whole
282    /// of it with a swap, which is what keeps a mark that lands during the swap
283    /// from being lost: the worst that can happen is a bit the turn has already
284    /// taken being set again, and that costs one more look at a database with
285    /// nothing to collect.
286    dirty: AtomicU64,
287    /// The mask this thread's maintenance turn is working from.
288    ///
289    /// Its own and not a shared one, because a turn reads it in place and then
290    /// clears bits of it, and a shared mask cleared that way would lose whatever
291    /// another thread marked in between. Every thread turns a loop and every
292    /// loop maintains, so what stops the same work being done twice is not the
293    /// mask but the stripe lock underneath it: two threads that both look at
294    /// database nine take turns, and the second one finds nothing left to move.
295    ///
296    /// Starts with every database set, so a server that has just been built
297    /// looks at all of them once rather than waiting to be told about the ones
298    /// something was loaded into before any command ran.
299    turn: AtomicU64,
300    /// How many of this thread's clients are on the waiter list.
301    ///
302    /// The waiter list is one list behind one lock, and a thread can only answer
303    /// the waiters it parked itself, so a thread with none of its own has no
304    /// reason to take that lock at all. Without this the check is the server
305    /// wide count, and one client blocked anywhere puts every thread through the
306    /// shared lock after every command it runs and again on every disconnect.
307    ///
308    /// Only the thread this belongs to writes it, because parking, answering and
309    /// forgetting a waiter all happen on the thread that read the command, so
310    /// the load and the store either side of a change cannot lose one.
311    parked: AtomicUsize,
312}
313
314impl Default for Local {
315    fn default() -> Local {
316        Local {
317            stats: Stats::default(),
318            cmdstats: CommandStats::default(),
319            dirty: AtomicU64::new(0),
320            turn: AtomicU64::new(ALL_DATABASES),
321            parked: AtomicUsize::new(0),
322        }
323    }
324}
325
326impl Local {
327    /// Note that a command has run against these databases.
328    fn mark(&self, dbs: u64) {
329        self.dirty.store(self.dirty.load(Relaxed) | dbs, Relaxed);
330    }
331
332    /// Add `dbs` to what this thread's turn is going to look at.
333    fn note(&self, dbs: u64) {
334        self.turn.store(self.turn.load(Relaxed) | dbs, Relaxed);
335    }
336
337    /// Take `at` off the list of databases this thread's turn will look at.
338    fn done(&self, at: usize) {
339        self.turn
340            .store(self.turn.load(Relaxed) & !(1u64 << at), Relaxed);
341    }
342
343    /// Whether this thread's turn still has database `at` to look at.
344    fn wanted(&self, at: usize) -> bool {
345        self.turn.load(Relaxed) & (1u64 << at) != 0
346    }
347
348    /// Note that `n` more of this thread's clients are parked.
349    fn blocked(&self, n: usize) {
350        self.parked
351            .store(self.parked.load(Relaxed).saturating_add(n), Relaxed);
352    }
353
354    /// Note that `n` of them are not parked any more.
355    fn woke(&self, n: usize) {
356        self.parked
357            .store(self.parked.load(Relaxed).saturating_sub(n), Relaxed);
358    }
359}
360
361/// Room for one thread, which is what a server starts with.
362fn one_thread() -> Box<[Local]> {
363    slots(1)
364}
365
366/// Room for `threads` of them.
367fn slots(threads: usize) -> Box<[Local]> {
368    (0..threads.max(1)).map(|_| Local::default()).collect()
369}
370
371/// Where the process was started, which is what `dir` defaults to.
372///
373/// A dot if the working directory cannot be read, which happens when it has
374/// been deleted out from under a running process. That is not a reason to
375/// refuse to start a server, and it leaves `BACKUP` to fail with the real error
376/// from the filesystem if anybody asks for one.
377fn working_dir() -> PathBuf {
378    std::env::current_dir().unwrap_or_else(|_| PathBuf::from("."))
379}
380
381/// One command's counters, for `INFO commandstats`.
382///
383/// Three of Redis's five. `usec` and `usec_per_call` are not here because
384/// nothing times a command, and timing one means two clock reads around a call
385/// that takes tens of nanoseconds to begin with. Redis pays that because Redis
386/// has room for it; this does not, and a zero under a name that says microseconds
387/// is worse than an absent field, which is the same rule the rest of `INFO`
388/// follows.
389#[derive(Debug, Clone, Copy, Default)]
390pub struct CommandStat {
391    /// Times the command ran, whatever it answered.
392    pub calls: u64,
393    /// Times it was turned away before it ran, which is the wrong number of
394    /// arguments or no room under `maxmemory`.
395    pub rejected: u64,
396    /// Times it ran and answered with an error.
397    pub failed: u64,
398}
399
400impl CommandStat {
401    /// Whether this command has ever been seen.
402    ///
403    /// A row that has not is left out of the reply, which is what Redis does and
404    /// is why the section is a handful of lines on a working server rather than
405    /// one line per command in the table.
406    const fn seen(&self) -> bool {
407        self.calls != 0 || self.rejected != 0 || self.failed != 0
408    }
409}
410
411/// One command's counters as one thread keeps them.
412///
413/// The same three numbers as [`CommandStat`], which is what they add up to when
414/// `INFO` asks. This is the written form and that is the read one.
415#[derive(Debug, Default)]
416struct Row {
417    /// Times the command ran.
418    calls: Counter,
419    /// Times it was turned away before it ran.
420    rejected: Counter,
421    /// Times it ran and answered with an error.
422    failed: Counter,
423}
424
425/// A counter per command, indexed the way [`table::index_of`] says.
426///
427/// A flat array and not a map, because the dispatcher is already holding the
428/// spec and the spec's position in the table is two addresses subtracted. That
429/// makes the counting a load, an add and a store on a row the previous command
430/// of the same name has already pulled into cache.
431#[derive(Debug)]
432struct CommandStats(Box<[Row]>);
433
434impl Default for CommandStats {
435    fn default() -> CommandStats {
436        CommandStats((0..table::count()).map(|_| Row::default()).collect())
437    }
438}
439
440impl CommandStats {
441    /// The row for one command.
442    fn at(&self, spec: &'static Spec) -> &Row {
443        &self.0[table::index_of(spec)]
444    }
445}
446
447/// Where a database gets its store from, asked by database number.
448///
449/// `None` means that database cannot have one. The caller owns whatever the
450/// stores are cut out of, which for `yodb` is one `.yo` file with a log per
451/// database, and this crate never learns what any of that is.
452pub type StoreSource = dyn FnMut(usize) -> Option<Store> + Send;
453
454/// Every thread that runs commands here shares this server, so it has to be
455/// `Send` and `Sync`, and the check is here so that a type added to it that is
456/// neither is a compile error where it was added rather than an error in the
457/// code that starts the threads.
458const _: () = {
459    const fn shareable<T: Send + Sync>() {}
460    shareable::<Server>();
461};
462
463/// Everything a server holds.
464///
465/// One per process, however many threads are serving out of it. What is inside
466/// is either shared outright, which is the counters and the settings, or behind
467/// a lock, which is the stripes and the few pieces of state a command can
468/// change. What makes this a server rather than a shard is that it is the whole
469/// of what a connection can address.
470pub struct Server {
471    dbs: Vec<Db>,
472    /// How many stripes each database is cut into, the same for all of them.
473    ///
474    /// Kept here as well as in each database so that the flat slot arithmetic
475    /// below is a multiply and a divide against a field on the server rather
476    /// than a walk asking each database how wide it is.
477    width: usize,
478    clock: Clock,
479    started_ms: u64,
480    /// Where the next maintenance turn starts looking, so that a database
481    /// under constant write load cannot hold the other fifteen's space.
482    ///
483    /// Shared, because compaction is asked for from two places: the maintenance
484    /// turn, which is one thread, and a command that went over the memory limit
485    /// and is trying to get back under it, which is any thread. Two threads that
486    /// read the same cursor start on the same database, and what that costs is
487    /// one of them finding the other has already moved what was there.
488    next_db: AtomicUsize,
489    /// One bit per database, set when a command ran against it.
490    ///
491    /// The maintenance turn after every batch used to ask all sixteen
492    /// databases whether they had anything to collect, and asking costs a load
493    /// and a store in each one. Fifteen of those are cold lines on a server
494    /// where every client is on database zero, which is every server, and the
495    /// answer is no every time. This is the cheap half of the question: a
496    /// database nobody has touched since it last said no cannot have started
497    /// saying yes.
498    ///
499    /// What the connections are holding, kept by the engine.
500    ///
501    /// Shared, because every thread has connections and the memory total is one
502    /// total. Each thread adds and subtracts its own change rather than storing
503    /// a figure it worked out, so two threads whose buffers grew in the same
504    /// moment both count.
505    conn_bytes: AtomicUsize,
506    /// The `maxmemory` limit in bytes, zero when there is not one.
507    ///
508    /// Zero is the default and it is the whole reason the check in front of
509    /// every write is one comparison against a field that is already warm. It
510    /// is read by every command on every thread and written by a client that
511    /// sends `CONFIG SET`, so it is a number the threads can share rather than
512    /// a field one of them owns.
513    maxmemory: AtomicU64,
514    /// Where a database gets a store from the first time it needs one.
515    ///
516    /// A closure and not a store, because there are sixteen databases and a
517    /// server that fills memory on database zero should not have opened
518    /// anything for the other fifteen. Nothing is asked of this until a memory
519    /// limit is actually reached, so a server that never fills memory never
520    /// opens a file, and a server that has no file never has one of these.
521    ///
522    /// `None` from the closure means that database cannot have one, which is
523    /// how the caller says the file it opened has no more room for logs.
524    ///
525    /// Behind a lock because it is a closure the caller gave us and there is no
526    /// saying it can be run by two threads at once. It is asked once per
527    /// database, the first time that database has to move something, so a
528    /// server that has reached its memory limit takes this lock sixteen times
529    /// in its life.
530    store: Lock<Option<Box<StoreSource>>>,
531    /// The `maxstore` limit in bytes, `None` when there is not one.
532    ///
533    /// The storage limit, and the other half of the inversion `14` section 4.1
534    /// describes. `maxmemory` is a limit on memory and the right answer to a
535    /// memory limit on a system with a file under it is to move data to the
536    /// file, not to delete it. Deleting is the right answer to a limit on the
537    /// file, and this is that limit.
538    ///
539    /// Zero is not "no limit" here, which is the one place this reads
540    /// differently from `maxmemory` and is the difference that makes a drop in
541    /// cache possible. A storage budget of zero bytes means nothing may live on
542    /// the file, so migration cannot make room and eviction is the only thing
543    /// left, which is Redis exactly. `None` is no limit and is the default,
544    /// which with `noeviction` means the database grows until the disk is full
545    /// and then writes fail, which is what a database does.
546    ///
547    /// Shared between the threads the same way `maxmemory` is, and no limit is
548    /// [`NO_MAXSTORE`] rather than a second field saying whether the first one
549    /// counts. Two fields cannot be read as one, and a limit that was on when
550    /// the bytes were read and off by the time the number was is a limit that
551    /// answers from a server that never existed.
552    maxstore: AtomicU64,
553    /// What [`Server::memory_bytes`] said at the last maintenance turn.
554    ///
555    /// The reading is a walk over every collection in every database and cannot
556    /// go on a command path, so the command path reads this instead and is at
557    /// most one batch behind. What that costs is overshoot: a server can end a
558    /// batch holding one batch's worth of allocation more than its limit before
559    /// anything notices. A batch is 64 commands, so that is bounded by what 64
560    /// commands can allocate and not by how long the server runs.
561    ///
562    /// Only kept up to date when there is a limit to judge it against. A server
563    /// with no `maxmemory` never reads it and never pays for it.
564    ///
565    /// Shared, because it is read in front of every write on every thread and
566    /// written by whichever thread last took a reading. A reader that catches it
567    /// mid write gets one of the two readings and both of them were true a
568    /// moment ago, which is all this number ever claims to be.
569    used: AtomicUsize,
570    /// Which database the next eviction draws from.
571    ///
572    /// Its own cursor and not [`Server::next_db`], because eviction and
573    /// compaction move at different rates and sharing one would make the
574    /// database that gets compacted depend on how many keys were evicted.
575    ///
576    /// Shared for the same reason [`Server::next_db`] is, and with the same
577    /// answer: two threads evicting at once may pick the same database, and one
578    /// of them finds the other got there first and moves on.
579    evict_db: AtomicUsize,
580    /// Which database the next active expiry sweep starts at.
581    ///
582    /// A third cursor for the same reason there is a second one. A sweep runs on
583    /// every turn of the loop and compaction runs when there is dead space, so
584    /// sharing a cursor would make which database gets swept depend on which one
585    /// was last collected.
586    expire_db: AtomicUsize,
587    /// The millisecond the last active expiry sweep ran on, so the next one on
588    /// the same millisecond does not bother.
589    ///
590    /// One for the server and not one per thread, so the sweeping a server does
591    /// is a function of how long it has been running and not of how many threads
592    /// it was started with. Two threads that read the same millisecond can both
593    /// decide to sweep, which costs one extra sweep of a budget that is already
594    /// small and cannot happen twice for the same millisecond more than once per
595    /// thread.
596    expire_ms: AtomicU64,
597    /// Clients parked on a blocking command.
598    ///
599    /// Behind a lock because a client parks on the thread that ran its command
600    /// and is woken by whichever thread later puts something under a key it
601    /// named, and those are not the same thread. The lock is only ever taken to
602    /// park somebody, to serve somebody or to forget a connection that has gone,
603    /// so a command that does not block never touches it.
604    waiters: Lock<Waiters>,
605    /// How many clients are parked.
606    ///
607    /// Beside the list rather than read out of it, because every command asks
608    /// whether anybody is waiting and nearly every answer is no. Taking a lock
609    /// to be told no would be a cache line every thread has to own to ask, which
610    /// is the cost the list was put behind a lock to avoid.
611    ///
612    /// Written under the lock, by whoever changed the list, so the number and
613    /// the list agree except while a change is in progress. A reader that asks
614    /// during one is told about the moment before it, and the worst that costs
615    /// is a walk of the list that serves nobody or one that has not started yet
616    /// and happens on the next command instead.
617    parked: AtomicUsize,
618    /// Sockets `MIGRATE` is holding open to the servers it has talked to.
619    ///
620    /// Empty on a server nobody has migrated a key out of, which is nearly all
621    /// of them, and it costs a vector's three words to be empty.
622    ///
623    /// Behind a lock because a socket cannot be written by two threads at once
624    /// and a cache of them cannot be searched by one while another is taking an
625    /// entry out. It is held for the whole of a migration, which is a round trip
626    /// to another server, so two threads migrating at the same time take turns.
627    /// That is the right way round: the alternative is a socket per thread per
628    /// peer, and a `MIGRATE` is not what a server spends its time on.
629    peers: Lock<migrate::Peers>,
630    /// What each thread that runs commands here keeps to itself.
631    ///
632    /// A fixed list, because a thread reading its own entry must not have the
633    /// list move under it, and how many threads there will be is known before
634    /// any of them starts. A server nobody told otherwise has one.
635    locals: Box<[Local]>,
636    /// How many entries have been handed out.
637    claimed: AtomicUsize,
638    /// The next client id, which is what `CLIENT ID` answers.
639    ///
640    /// On the server and not on a front, because CLIENT LIST and CLIENT KILL
641    /// name a client by this number across the whole server, and two threads
642    /// counting on their own would hand the same number to two clients. Starts
643    /// at one so that zero is never a client, which is what makes it usable as
644    /// the id of a command that came from nowhere.
645    next_client: AtomicU64,
646    /// Where `BACKUP` puts its files, and where `CONFIG GET dir` points.
647    ///
648    /// Absolute, and resolved once when the server is built rather than every
649    /// time somebody asks. `BACKUP LIST` answers absolute paths and a client is
650    /// entitled to hand one of them to a copy tool, so a relative path that
651    /// meant something different after a `chdir` would be a path that stops
652    /// working for reasons nobody could see.
653    dir: PathBuf,
654    /// What backup is running, if one is.
655    ///
656    /// On the server and not on a session, because a backup outlives the
657    /// connection that asked for it and any other connection can seal it.
658    ///
659    /// Behind a lock because there is one backup at a time and any thread can be
660    /// the one that starts, seals or abandons it. It is held while the base file
661    /// is written, which is what keeps two `BACKUP START` commands from writing
662    /// over each other's files.
663    backup: Lock<backup::State>,
664    /// Whether a sealed backup is sitting on disk.
665    ///
666    /// Beside the state rather than read out of it, because every batch of
667    /// commands asks whether there is a backup old enough to sweep away and on
668    /// nearly every server the answer is that there is no backup at all. A load
669    /// answers that. Written under the lock by whoever moved the phase, so a
670    /// reader that asks mid-change sees the moment before and sweeps one batch
671    /// later, which is a file staying on disk for a few microseconds longer than
672    /// it had to.
673    sealed: AtomicBool,
674    /// The search indexes and the names pointing at them.
675    ///
676    /// On the server and not on a database, which is the one collection in this
677    /// build that is. A real server keeps its indexes in the search module, the
678    /// module has one table, and `SELECT 1` followed by `FT._LIST` lists the
679    /// indexes made on database zero. `search.rs` has the rest of why.
680    ///
681    /// A server nobody has made an index on holds two empty vectors here, which
682    /// is six words and no allocation.
683    ///
684    /// Behind a lock because an index is made and dropped by whichever thread
685    /// ran the command, and the table it goes in is one table. Only the `FT`
686    /// commands take it, so nothing a working server spends its time on comes
687    /// through here.
688    search: Lock<Registry>,
689    /// The replies that came back in pieces and have pieces left.
690    ///
691    /// Beside the indexes rather than inside one, because a cursor is read
692    /// under its own number and a real server resolves the index name on a read
693    /// and then pays no attention to it, so a cursor made on one index reads
694    /// through the name of another. Behind a lock for the reason the registry is
695    /// behind one, and a server nobody has opened a cursor on holds an empty map
696    /// here.
697    cursors: Lock<Cursors>,
698    /// Set by `SHUTDOWN`, and read by whatever is turning the loop.
699    ///
700    /// A flag rather than an exit, because the command layer is not what owns
701    /// the process. It runs inside a batch that has other commands behind it
702    /// and inside a driver that has a socket file to take away and a file to
703    /// close, and a server that calls `exit` from a command handler skips all
704    /// of that. So the command says stop and the driver stops, on the same turn
705    /// and through the same door a signal uses.
706    stopping: AtomicBool,
707}
708
709impl Server {
710    /// A server with [`DATABASES`] empty databases on the system clock.
711    #[must_use]
712    pub fn new() -> Server {
713        let clock = Clock::system();
714        Server {
715            dbs: (0..DATABASES)
716                .map(|_| Db::with_clock(clock.clone(), 1))
717                .collect(),
718            width: 1,
719            started_ms: clock.now_ms(),
720            clock,
721            next_db: AtomicUsize::new(0),
722            conn_bytes: AtomicUsize::new(0),
723            maxmemory: AtomicU64::new(0),
724            store: Lock::new(None),
725            maxstore: AtomicU64::new(NO_MAXSTORE),
726            used: AtomicUsize::new(0),
727            evict_db: AtomicUsize::new(0),
728            expire_db: AtomicUsize::new(0),
729            expire_ms: AtomicU64::new(0),
730            waiters: Lock::default(),
731            parked: AtomicUsize::new(0),
732            peers: Lock::default(),
733            locals: one_thread(),
734            claimed: AtomicUsize::new(0),
735            next_client: AtomicU64::new(1),
736            dir: working_dir(),
737            backup: Lock::default(),
738            sealed: AtomicBool::new(false),
739            search: Lock::new(Registry::new()),
740            cursors: Lock::default(),
741            stopping: AtomicBool::new(false),
742        }
743    }
744
745    /// A server whose databases are cut into `width` stripes each.
746    ///
747    /// Not reachable from the command line yet. Every command group answers on
748    /// a server of any width now and so does everything that walks a whole
749    /// database, and the tests run each group at a width of one and a width of
750    /// eight and check the two agree.
751    ///
752    /// What is left before this is what `--threads` sets is the engine. A
753    /// database being several objects is what makes more than one thread
754    /// possible, and it is not what makes more than one thread happen.
755    #[must_use]
756    pub fn with_width(width: usize) -> Server {
757        let mut server = Server::new();
758        // The server's own clock and not a fresh one, because a database
759        // reading a different clock from the server it is on is a database
760        // whose keys expire against a time nobody set.
761        let clock = server.clock.clone();
762        server.dbs = (0..DATABASES)
763            .map(|_| Db::with_clock(clock.clone(), width))
764            .collect();
765        server.width = server.dbs[0].width();
766        server
767    }
768
769    /// A server on a clock the caller moves by hand, for tests.
770    #[must_use]
771    pub fn with_clock(clock: Clock) -> Server {
772        Server {
773            dbs: (0..DATABASES)
774                .map(|_| Db::with_clock(clock.clone(), 1))
775                .collect(),
776            width: 1,
777            started_ms: clock.now_ms(),
778            clock,
779            next_db: AtomicUsize::new(0),
780            conn_bytes: AtomicUsize::new(0),
781            maxmemory: AtomicU64::new(0),
782            store: Lock::new(None),
783            maxstore: AtomicU64::new(NO_MAXSTORE),
784            used: AtomicUsize::new(0),
785            evict_db: AtomicUsize::new(0),
786            expire_db: AtomicUsize::new(0),
787            expire_ms: AtomicU64::new(0),
788            waiters: Lock::default(),
789            parked: AtomicUsize::new(0),
790            peers: Lock::default(),
791            locals: one_thread(),
792            claimed: AtomicUsize::new(0),
793            next_client: AtomicU64::new(1),
794            dir: working_dir(),
795            backup: Lock::default(),
796            sealed: AtomicBool::new(false),
797            search: Lock::new(Registry::new()),
798            cursors: Lock::default(),
799            stopping: AtomicBool::new(false),
800        }
801    }
802
803    /// One database, by index.
804    ///
805    /// A caller that knows which key it wants names the one stripe the key is
806    /// on rather than working over the whole thing, which is what `at` and its
807    /// neighbours on [`Db`] are for. A caller that is about a database rather
808    /// than about a key, which is the snapshot walk and a setting, works over
809    /// all of them.
810    ///
811    /// The database is marked as having had something run against it, which is
812    /// what this does that [`Server::striped_ref`] does not. Anything that only
813    /// reads asks for that one and leaves the mark alone.
814    ///
815    /// The borrow is shared, and what makes that enough is that a database is
816    /// several stripes behind a lock each. A caller that wants to change
817    /// something holds the stripe it is changing, so two threads working on two
818    /// keys work at once and two working on one key take turns, which is the
819    /// whole point of cutting a database up.
820    ///
821    /// # Panics
822    ///
823    /// If `i` is not a database. `SELECT` is the only way a client changes the
824    /// index and it checks, so an index that is out of range here is a bug in
825    /// the caller and not something a client can ask for.
826    pub fn striped(&self, i: usize) -> &Db {
827        self.mine().mark(1u64 << i);
828        &self.dbs[i]
829    }
830
831    /// Every keyspace on the server, which is every stripe of every database.
832    ///
833    /// What the aggregates walk. A total over the whole server is a total over
834    /// all of these and the stripe boundaries do not appear in it, which is
835    /// what makes the numbers `INFO` reports the same numbers whatever the
836    /// server was cut into.
837    fn keyspaces(&self) -> impl Iterator<Item = Held<'_, Keyspace>> {
838        self.dbs
839            .iter()
840            .flat_map(|db| (0..db.width()).map(|i| db.hold_stripe(i)))
841    }
842
843    /// How many keyspaces there are, counting every stripe of every database.
844    ///
845    /// The maintenance turns walk these rather than the databases, because a
846    /// stripe is the thing that holds an arena and a deadline heap and so it is
847    /// the thing that has anything to collect.
848    const fn slots(&self) -> usize {
849        DATABASES * self.width
850    }
851
852    /// Which database slot `i` belongs to.
853    const fn slot_db(&self, i: usize) -> usize {
854        i / self.width
855    }
856
857    /// Keyspace `i` of [`Server::slots`].
858    fn slot(&self, i: usize) -> Held<'_, Keyspace> {
859        let (db, stripe) = (i / self.width, i % self.width);
860        self.dbs[db].hold_stripe(stripe)
861    }
862
863    /// Where `BACKUP` writes and what `CONFIG GET dir` answers.
864    #[must_use]
865    pub fn dir(&self) -> &Path {
866        &self.dir
867    }
868
869    /// Point the server at a different directory, which `yodb serve --dir` does.
870    ///
871    /// Only before it is serving. There is no `CONFIG SET dir` here and there
872    /// is none on a real server either without turning protected configs on,
873    /// for the good reason that moving it out from under a running backup would
874    /// leave files nothing can find again.
875    pub fn set_dir(&mut self, dir: PathBuf) {
876        self.dir = dir;
877    }
878
879    /// Drop a sealed backup that has outlived `backup-sealed-ttl`.
880    ///
881    /// Once per batch, from the same maintenance turn that collects the arena.
882    /// It reads two fields and returns on a server that has never taken a
883    /// backup, which is nearly all of them.
884    pub fn backup_expire(&self) {
885        backup::expire(self);
886    }
887
888    /// Ask for the server to stop, which is what `SHUTDOWN` does.
889    ///
890    /// It sets a flag and returns. Nothing here closes a socket, flushes a file
891    /// or ends the process, because none of those belong to this layer, and a
892    /// batch that is halfway through still has to finish and be written out.
893    pub fn stop(&self) {
894        self.stopping.store(true, Release);
895    }
896
897    /// Whether somebody has asked the server to stop.
898    ///
899    /// Read once per turn by the loop, next to the flag a signal sets. The two
900    /// mean the same thing and are separate only because one arrives from the
901    /// operating system and the other from a client.
902    #[must_use]
903    pub fn stopping(&self) -> bool {
904        self.stopping.load(Acquire)
905    }
906
907    /// One database, by index, without taking it mutably.
908    ///
909    /// What the prefetch stage needs. It runs for all 64 commands in a batch
910    /// before any of them executes, so it cannot hold the mutable borrow `run`
911    /// is about to want, and it does not need one: warming a cache line reads
912    /// nothing and changes nothing.
913    #[must_use]
914    pub fn striped_ref(&self, i: usize) -> &Db {
915        &self.dbs[i]
916    }
917
918    /// The stripe that answers for a database when a setting is read back.
919    ///
920    /// A ladder setting and an eviction policy are one number on a real server,
921    /// and the fact that every stripe of every database carries a copy of it is
922    /// ours rather than the client's problem. A write puts the same value on
923    /// every one of them, so any stripe answers for all of them and this is the
924    /// first one.
925    fn settings(&self) -> Held<'_, Keyspace> {
926        self.dbs[0].hold_stripe(0)
927    }
928
929    /// Take a new clock reading, which every database is looking at.
930    ///
931    /// Once per turn of the event loop, which is the only place time moves. A
932    /// command asking what the time is gets the answer the whole batch got, so
933    /// two keys written by the same batch expire together (`04` section 3).
934    ///
935    /// Every thread does this on every turn of its own loop and they do not
936    /// have to agree about when. The reading is only stored when the
937    /// millisecond has changed, so what the threads are sharing is a line that
938    /// is written about a thousand times a second and read millions.
939    pub fn refresh_clock(&self) {
940        self.clock.refresh();
941    }
942
943    /// Move every clock here on by `ms`, for tests about expiry.
944    ///
945    /// The same thing [`Server::set_clock_ms`] does and by the same argument,
946    /// except that it moves from wherever the clock is rather than to a stated
947    /// moment, which is what a test that wants a key to have expired asks for.
948    pub fn advance_clock_ms(&self, ms: u64) {
949        let now = self.clock.now_ms() + ms;
950        self.set_clock_ms(now);
951    }
952
953    /// Move every clock here to `ms` by hand, for tests about expiry.
954    ///
955    /// A test cannot wait a hundred seconds and a test that waits a hundred
956    /// milliseconds is a test that fails on a loaded machine, so time moves on
957    /// request. The system clock underneath will overwrite this on the next
958    /// [`Server::refresh_clock`], which is why this is only useful in a test
959    /// that drives commands directly rather than through the event loop.
960    pub fn set_clock_ms(&self, ms: u64) {
961        self.clock.set(ms);
962    }
963
964    /// Seconds since this server was built.
965    #[must_use]
966    pub fn uptime_secs(&self) -> u64 {
967        self.clock.now_ms().saturating_sub(self.started_ms) / 1000
968    }
969
970    /// Bytes held by every database's index and arena, plus the read and reply
971    /// buffers of every connection.
972    ///
973    /// The buffers are in here because they are real and because Redis counts
974    /// its own, so leaving them out would make the one number people compare
975    /// flattering rather than true. They are not a database, so nothing in the
976    /// keyspace can change them and the engine has to say when they move.
977    #[must_use]
978    pub fn memory_bytes(&self) -> usize {
979        self.keyspaces().map(|db| db.memory_bytes()).sum::<usize>() + self.conn_bytes()
980    }
981
982    /// What the keyspace itself is holding, live records only.
983    ///
984    /// `used_memory` minus this is what the store costs to run: the index, the
985    /// space dead records are sitting in until compaction gets to them, and the
986    /// connections' buffers.
987    #[must_use]
988    pub fn dataset_bytes(&self) -> usize {
989        self.keyspaces()
990            .map(|db| db.map().arena().live_bytes() as usize)
991            .sum()
992    }
993
994    /// Bytes the arenas are holding, live and dead together.
995    #[must_use]
996    pub fn arena_bytes(&self) -> usize {
997        self.keyspaces()
998            .map(|db| db.map().arena().reserved_bytes() as usize)
999            .sum()
1000    }
1001
1002    /// Bytes the indexes are holding.
1003    #[must_use]
1004    pub fn index_bytes(&self) -> usize {
1005        self.keyspaces()
1006            .map(|db| db.map().index().memory_bytes())
1007            .sum()
1008    }
1009
1010    /// What arena compaction has cost, across every database.
1011    ///
1012    /// The write amplification of value separation, which is invisible from the
1013    /// outside otherwise: a client that writes a megabyte can leave the store
1014    /// copying several more, and the only sign of it without these is that the
1015    /// writes got slower.
1016    #[must_use]
1017    pub fn compaction(&self) -> yo_kv::Compaction {
1018        self.keyspaces().map(|db| db.map().compaction()).fold(
1019            yo_kv::Compaction::default(),
1020            |a, b| yo_kv::Compaction {
1021                walked: a.walked + b.walked,
1022                moved: a.moved + b.moved,
1023                bytes: a.bytes + b.bytes,
1024            },
1025        )
1026    }
1027
1028    /// Arena segments whose pages are real, across every database.
1029    #[must_use]
1030    pub fn segment_count(&self) -> usize {
1031        self.keyspaces()
1032            .map(|db| db.map().arena().resident_segments())
1033            .sum()
1034    }
1035
1036    /// What the connections' read and reply buffers are holding.
1037    #[must_use]
1038    pub fn conn_bytes(&self) -> usize {
1039        self.conn_bytes.load(Relaxed)
1040    }
1041
1042    /// Note that the connections are holding `delta` bytes more than they were,
1043    /// or fewer when it is negative.
1044    ///
1045    /// A delta and not a total because the alternative is a walk over every
1046    /// connection, and the walk would have to happen on a turn of the loop
1047    /// rather than when `INFO` asks, which puts the cost of a report on the
1048    /// command path of a server nobody is asking.
1049    pub fn note_conn_bytes(&self, delta: isize) {
1050        // A read and a write and not a fetch and add, because the number is a
1051        // sum of signed changes and the saturating part has to happen in the
1052        // middle. Two threads that change their buffers in the same instant can
1053        // lose one of the two changes, which is a report that is a few kilobytes
1054        // out until the next connection on either thread moves it again.
1055        self.conn_bytes
1056            .store(self.conn_bytes().saturating_add_signed(delta), Relaxed);
1057    }
1058
1059    /// Keys reclaimed by running into them after their deadline.
1060    #[must_use]
1061    pub fn expired_keys(&self) -> u64 {
1062        self.keyspaces().map(|db| db.expired_keys()).sum()
1063    }
1064
1065    /// Keys thrown away to make room, which is the other number entirely.
1066    #[must_use]
1067    pub fn evicted_keys(&self) -> u64 {
1068        self.keyspaces().map(|db| db.evicted_keys()).sum()
1069    }
1070
1071    /// Every command that has been seen, with its counters.
1072    ///
1073    /// Only the ones that have. A server reports a handful of lines rather than
1074    /// one per command in the table, which is what Redis does and is the
1075    /// difference between a section a person can read and one they cannot.
1076    pub fn command_stats(&self) -> impl Iterator<Item = (&'static str, CommandStat)> {
1077        (0..table::count())
1078            .map(|at| (table::name_at(at), self.command_stat(at)))
1079            .filter(|(_, row)| row.seen())
1080    }
1081
1082    /// One command's counters, added up over every thread.
1083    fn command_stat(&self, at: usize) -> CommandStat {
1084        let mut sum = CommandStat::default();
1085        for thread in &self.locals {
1086            let row = &thread.cmdstats.0[at];
1087            sum.calls += row.calls.get();
1088            sum.rejected += row.rejected.get();
1089            sum.failed += row.failed.get();
1090        }
1091        sum
1092    }
1093
1094    /// The counters the calling thread writes into.
1095    ///
1096    /// The first call on a thread claims a set and every call after it is a
1097    /// thread local read and an index. A server asked to count from more threads
1098    /// than it was built for wraps round and shares a set, which loses the odd
1099    /// count between two threads and cannot happen to a server `yodb serve`
1100    /// built, because that one is told how many threads it will have before it
1101    /// starts any of them.
1102    pub fn counted(&self) -> &Stats {
1103        &self.mine().stats
1104    }
1105
1106    /// The next client id, taken.
1107    ///
1108    /// Every accept anywhere on this server comes through here, so no two
1109    /// clients share a number however many threads are accepting.
1110    pub fn next_client(&self) -> u64 {
1111        self.next_client.fetch_add(1, Relaxed)
1112    }
1113
1114    /// Which set of per thread state the calling thread is on.
1115    ///
1116    /// The number a blocked client is filed under, so that the thread holding
1117    /// that client's connection is the one that answers it. Claims a set on the
1118    /// first call the same way [`Server::counted`] does, and gives back the same
1119    /// number every time after.
1120    pub fn my_slot(&self) -> usize {
1121        self.mine_at()
1122    }
1123
1124    /// Everything the calling thread keeps to itself.
1125    fn mine(&self) -> &Local {
1126        &self.locals[self.mine_at()]
1127    }
1128
1129    /// The calling thread's place in `locals`, claiming one if it has none.
1130    ///
1131    /// Wraps round when more threads count here than the server was built for,
1132    /// which shares a set between two threads and loses the odd count. That
1133    /// cannot happen to the server `yodb serve` builds, because it is told how
1134    /// many threads it will have before it starts any of them.
1135    fn mine_at(&self) -> usize {
1136        let mut slot = SLOT.get();
1137        if slot == usize::MAX {
1138            slot = self.claimed.fetch_add(1, Relaxed);
1139            SLOT.set(slot);
1140        }
1141        slot % self.locals.len()
1142    }
1143
1144    /// Every thread's numbers added together, which is what `INFO` reports.
1145    #[must_use]
1146    pub fn totals(&self) -> Totals {
1147        let mut sum = Totals::default();
1148        for thread in &self.locals {
1149            sum.clients += thread.stats.clients.get();
1150            sum.connections += thread.stats.connections.get();
1151            sum.commands += thread.stats.commands.get();
1152        }
1153        sum
1154    }
1155
1156    /// Put the totals back to zero, which is `CONFIG RESETSTAT`.
1157    ///
1158    /// Every thread's set and not only the one asking, since the number the
1159    /// client is resetting is the sum it was just shown. The open connections
1160    /// are left alone because that is a gauge and not a total: the connections
1161    /// are still open.
1162    pub fn reset_stats(&self) {
1163        for thread in &self.locals {
1164            thread.stats.connections.zero();
1165            thread.stats.commands.zero();
1166        }
1167    }
1168
1169    /// Say how many threads will run commands here, before any of them does.
1170    ///
1171    /// What it changes is how many sets of counters there are. Called once at
1172    /// startup by whoever is about to start the threads, and calling it on a
1173    /// running server throws away what has been counted so far, which is why it
1174    /// wants the server to itself.
1175    pub fn set_threads(&mut self, threads: usize) {
1176        self.locals = slots(threads);
1177        self.claimed = AtomicUsize::new(0);
1178    }
1179
1180    /// The `maxmemory` limit in bytes, zero when there is not one.
1181    #[must_use]
1182    pub fn maxmemory(&self) -> u64 {
1183        self.maxmemory.load(Relaxed)
1184    }
1185
1186    /// Set the limit, and take a reading straight away.
1187    ///
1188    /// The reading is here rather than left to the next maintenance turn because
1189    /// a client that sets the limit and sends a write in the same batch expects
1190    /// the write to be judged against the limit it just set, and because the
1191    /// cached number is meaningless until the first time there is a limit to
1192    /// compare it with.
1193    ///
1194    /// Turning the limit on also turns on the running total every slab keeps of
1195    /// what its collections hold, and turning it off turns that back off, so a
1196    /// server with no limit is not paying to count something nobody reads. The
1197    /// first reading after switching it on is the walk that the total starts
1198    /// from, and it is the only walk.
1199    pub fn set_maxmemory(&self, bytes: u64) {
1200        self.maxmemory.store(bytes, Relaxed);
1201        for db in &self.dbs {
1202            db.track_memory(bytes != 0);
1203        }
1204        self.used.store(self.settled_memory(), Relaxed);
1205    }
1206
1207    /// Say where a database should get its store from when it needs one.
1208    ///
1209    /// This is what turns the eviction inversion on. Until it is called every
1210    /// database answers a memory limit by evicting, which is Redis, and after it
1211    /// is called a database under memory pressure moves values to whatever the
1212    /// closure hands back instead of throwing keys away.
1213    ///
1214    /// Called at most once per database and only under pressure, so a server
1215    /// that is given a file and never fills memory never touches it.
1216    pub fn set_store_source(
1217        &mut self,
1218        source: impl FnMut(usize) -> Option<Store> + Send + 'static,
1219    ) {
1220        *self.store.lock() = Some(Box::new(source));
1221    }
1222
1223    /// Whether this server has been given somewhere to put cold values.
1224    #[must_use]
1225    pub fn has_store_source(&self) -> bool {
1226        self.store.lock().is_some()
1227    }
1228
1229    /// Open database `at`'s store, if it has not got one and there is one to be
1230    /// had.
1231    ///
1232    /// A store that will not open leaves the database where it was, which is
1233    /// evicting, because a memory limit that cannot be answered by moving data
1234    /// still has to be answered.
1235    fn attach_store(&self, at: usize) {
1236        if self.slot(at).store_bytes().is_some() {
1237            return;
1238        }
1239        // The closure is run with its lock held and the keyspace is taken after
1240        // it has answered, so the file is opened once however many threads asked
1241        // for it and the stripe is not held while a file is being opened.
1242        let mut source = self.store.lock();
1243        let Some(source) = source.as_mut() else {
1244            return;
1245        };
1246        if let Some(blocks) = source(at) {
1247            self.slot(at).attach(blocks);
1248        }
1249    }
1250
1251    /// The `maxstore` limit in bytes, `None` when there is not one.
1252    #[must_use]
1253    pub fn maxstore(&self) -> Option<u64> {
1254        match self.maxstore.load(Relaxed) {
1255            NO_MAXSTORE => None,
1256            bytes => Some(bytes),
1257        }
1258    }
1259
1260    /// Set the storage limit, or clear it with `None`.
1261    ///
1262    /// Nothing is read here the way [`Server::set_maxmemory`] reads the memory
1263    /// total, because this limit is compared against a number the store keeps
1264    /// and answers on demand, not against a walk.
1265    pub fn set_maxstore(&self, bytes: Option<u64>) {
1266        self.maxstore.store(bytes.unwrap_or(NO_MAXSTORE), Relaxed);
1267    }
1268
1269    /// What every attached store is holding, for `INFO memory`.
1270    ///
1271    /// Zero on a server with nothing attached, which is not the same as a server
1272    /// whose file is empty, and [`Server::regime`] is the field that tells those
1273    /// two apart.
1274    #[must_use]
1275    pub fn store_bytes(&self) -> u64 {
1276        self.keyspaces().filter_map(|db| db.store_bytes()).sum()
1277    }
1278
1279    /// What the file has been asked to do, added up over every database.
1280    ///
1281    /// Counters and not levels, so they only ever go up and a run is the
1282    /// difference between two readings. G9 is a ratio over these: the faults a
1283    /// run took, divided by the point reads it issued, has to come out at 1.05
1284    /// or less with a working set ten times memory. There is no way to work that
1285    /// out from outside the server, so it is reported rather than inferred.
1286    ///
1287    /// A fault is a read that went to the store. Whether it also went to the
1288    /// device depends on the store: a log serves a read out of a resident page
1289    /// without touching anything. At ten times memory almost every fault is a
1290    /// real read, which is why the gate is written against this number, but the
1291    /// two are not the same thing and a run tight against the bar should be
1292    /// checked against what the operating system says.
1293    #[must_use]
1294    pub fn cold_stats(&self) -> yo_kv::tier::Stats {
1295        let mut total = yo_kv::tier::Stats::default();
1296        for db in self.keyspaces() {
1297            let Some(tier) = db.tier() else { continue };
1298            let s = tier.stats();
1299            total.demoted += s.demoted;
1300            total.promoted += s.promoted;
1301            total.faults += s.faults;
1302            total.served += s.served;
1303            total.bytes_out += s.bytes_out;
1304            total.bytes_in += s.bytes_in;
1305        }
1306        total
1307    }
1308
1309    /// Which way this server answers a memory limit, in one word for `INFO`.
1310    ///
1311    /// `evict` is Redis: a memory limit throws keys away. `migrate` is the
1312    /// inversion: a memory limit moves values to the file and nothing stored is
1313    /// lost. A server reports one word rather than leaving an operator to work
1314    /// it out from a limit, a setting and whether a file happens to be open.
1315    #[must_use]
1316    pub fn regime(&self) -> &'static str {
1317        if (0..self.slots()).any(|at| self.migrates(at)) {
1318            "migrate"
1319        } else {
1320            "evict"
1321        }
1322    }
1323
1324    /// Whether database `at` answers a memory limit by moving values to the
1325    /// file rather than by throwing keys away.
1326    ///
1327    /// Three things have to hold. There has to be somewhere to move them, which
1328    /// is a store attached to that database or a source that can open one, and
1329    /// on a server that was never given a file this is false everywhere and
1330    /// every database behaves exactly as it did.
1331    /// The storage budget has to be more than nothing, which is what
1332    /// `maxstore 0` says it is not. And the file has to be under that budget,
1333    /// because a full file is a storage limit reached and eviction is the right
1334    /// answer to a storage limit.
1335    fn migrates(&self, at: usize) -> bool {
1336        let cap = self.maxstore();
1337        if cap == Some(0) {
1338            return false;
1339        }
1340        // Out of the stripe first. A match keeps whatever it is looking at
1341        // alive for the whole of itself, and that would be this stripe held
1342        // across the arms for no reason.
1343        let bytes = self.slot(at).store_bytes();
1344        match bytes {
1345            Some(held) => cap.is_none_or(|cap| held < cap),
1346            // Nothing attached, but somewhere to get one from the moment this
1347            // database needs it, which is what makes the answer yes rather than
1348            // no. Opening it here would mean `INFO` opened files.
1349            None => self.store.lock().is_some(),
1350        }
1351    }
1352
1353    /// Take a fresh memory reading, which the maintenance turn does once a batch.
1354    ///
1355    /// Nothing at all when there is no limit, which is the default and is every
1356    /// server that has not asked for one.
1357    pub fn refresh_memory(&self) {
1358        if self.maxmemory() != 0 {
1359            self.used.store(self.settled_memory(), Relaxed);
1360        }
1361    }
1362
1363    /// [`Server::memory_bytes`], asked the cheap way.
1364    ///
1365    /// The same number. The difference is that this asks each database only
1366    /// about the collections that could have moved since the last time, which is
1367    /// what a batch touched rather than what the server holds, so it can be
1368    /// asked once a batch and again on every command that is over the limit.
1369    fn settled_memory(&self) -> usize {
1370        self.keyspaces()
1371            .map(|mut db| db.settled_memory_bytes())
1372            .sum::<usize>()
1373            + self.conn_bytes()
1374    }
1375
1376    /// Make room under the `maxmemory` limit, throwing keys away if that is what
1377    /// it takes. Answers whether there is anything left it could throw away.
1378    ///
1379    /// Redis runs the same thing from `processCommand` before every command and
1380    /// so does this: a client that writes has to be judged at the moment it
1381    /// writes, not a batch later, or the limit is a suggestion.
1382    ///
1383    /// Three things happen in the loop and all three are needed. Eviction picks
1384    /// a key and drops it. Compaction gives the pages back, because dropping a
1385    /// key marks its record dead and returns nothing on its own, so a loop that
1386    /// only evicted would throw the whole keyspace away and watch the number
1387    /// stay where it was. The reading is taken again each time round, because
1388    /// the two of them together are the only thing that moves it.
1389    ///
1390    /// # Why running out of budget is not a no
1391    ///
1392    /// `false` means there was nothing left to evict, which is `noeviction`, or
1393    /// a `volatile` policy on a database where nothing has a deadline, or a
1394    /// keyspace that is already empty. It does not mean the server is still over
1395    /// its limit, and that difference is Redis's: `performEvictions` answers
1396    /// `EVICT_FAIL` only when it has run out of things to delete, and
1397    /// `processCommand` refuses the client on that and on nothing else. Running
1398    /// out of time part way through a job it is doing well comes back as
1399    /// `EVICT_RUNNING` and the command goes through, because a server that is
1400    /// evicting steadily and refusing every write while it does it is worse for
1401    /// the client than a little overshoot.
1402    ///
1403    /// # What the limit is worth
1404    ///
1405    /// Space comes back a segment at a time and a segment is two megabytes, so
1406    /// this holds a server to its limit give or take a segment. A `maxmemory` of
1407    /// a few hundred megabytes gets what it asked for. A `maxmemory` of four
1408    /// megabytes is asking for a precision this store does not have.
1409    pub fn make_room(&self) -> bool {
1410        let limit = self.maxmemory();
1411        if limit == 0 || self.used.load(Relaxed) as u64 <= limit {
1412            return true;
1413        }
1414        // The cached reading is a batch old and the batch may have compacted
1415        // since, so take a fresh one before throwing anything away. It is the
1416        // settled reading and not the walk, so what this costs is the handful of
1417        // collections the last batch touched and not the whole database.
1418        let mut used = self.settled_memory();
1419        self.used.store(used, Relaxed);
1420        let mut budget = EVICT_BUDGET;
1421        while used as u64 > limit {
1422            let over = used - limit as usize;
1423            if !self.relieve_step(over) {
1424                return false;
1425            }
1426            self.compact_hard_step();
1427            used = self.settled_memory();
1428            self.used.store(used, Relaxed);
1429            budget -= 1;
1430            if budget == 0 {
1431                break;
1432            }
1433        }
1434        true
1435    }
1436
1437    /// Give back `over` bytes from whichever database can, by moving values to
1438    /// the file where there is one and by throwing keys away where there is not.
1439    ///
1440    /// The two answers are the eviction inversion and which one a database gets
1441    /// is [`Server::migrates`]. Answers whether anything was given back at all,
1442    /// and `false` is what refuses the client's write.
1443    ///
1444    /// A store that will not take the bytes counts as nothing given back, so the
1445    /// write is refused rather than turned into a deletion. A disk that is
1446    /// misbehaving is a reason to stop accepting writes and it is not a reason
1447    /// to start losing data that was accepted already.
1448    ///
1449    /// Round robin from a cursor rather than always starting at database zero,
1450    /// so a server using more than one of them does not empty the first before
1451    /// touching the second. Almost every server is on database zero only, where
1452    /// this is one call that answers and fifteen that say the map is empty.
1453    fn relieve_step(&self, over: usize) -> bool {
1454        let from = self.evict_db.load(Relaxed);
1455        for turn in 0..self.slots() {
1456            let i = (from + turn) % self.slots();
1457            // An empty keyspace has nothing to move and opening a log for one
1458            // would cost a resident page window to find that out.
1459            let used = !self.slot(i).is_empty();
1460            let gave = if used && self.migrates(i) {
1461                self.attach_store(i);
1462                // Whether it made room and not whether it moved a key. A round
1463                // that demoted nothing and handed back a segment is a round
1464                // that made room, and reading only the count refuses the write
1465                // that provoked it.
1466                self.slot(i)
1467                    .relieve(over)
1468                    .is_ok_and(yo_kv::tier::Relief::made_room)
1469            } else {
1470                self.slot(i).evict_one()
1471            };
1472            if gave {
1473                self.evict_db.store((i + 1) % self.slots(), Relaxed);
1474                self.mine().mark(1u64 << self.slot_db(i));
1475                return true;
1476            }
1477        }
1478        false
1479    }
1480
1481    /// The sweep the shard loop calls, at most once a millisecond.
1482    ///
1483    /// The gate is the whole difference between this and [`Server::expire_step`].
1484    /// A maintenance slice runs on every turn of the loop and a turn is a
1485    /// hundred nanoseconds, so an ungated sweep would draw a fresh sample ten
1486    /// thousand times per millisecond and spend a real share of the shard on
1487    /// looking for keys that cannot have died since the last look. Nothing in a
1488    /// database changes fast enough to be worth asking about more often than the
1489    /// clock can tell the difference, and the clock here is milliseconds.
1490    ///
1491    /// A millisecond is also far finer than Redis, whose slow cycle runs at ten
1492    /// hertz, so this is not the thing that decides how promptly memory comes
1493    /// back. What it decides is that an idle server sweeps a thousand times a
1494    /// second rather than a million.
1495    pub fn expire_slice(&self, budget: usize) -> usize {
1496        let now = self.clock.now_ms();
1497        if now == self.expire_ms.load(Relaxed) {
1498            return 0;
1499        }
1500        self.expire_ms.store(now, Relaxed);
1501        self.expire_step(budget)
1502    }
1503
1504    /// Sweep dead keys out of the databases, spending at most `budget` looks.
1505    ///
1506    /// Answers what it spent, so the caller can charge its maintenance slice for
1507    /// it. See [`yo_kv::expiry`] for why the budget is in keys looked at.
1508    ///
1509    /// Round robin from its own cursor, and every database gets offered whatever
1510    /// is left of the budget rather than a sixteenth of it each, so a server on
1511    /// database zero only, which is nearly every server, spends the whole slice
1512    /// where the keys are. The fifteen empty ones cost a comparison apiece
1513    /// because a database with no key carrying a deadline says so without
1514    /// drawing anything.
1515    ///
1516    /// The cursor moves to the database after whichever one did the work, so two
1517    /// busy databases take turns instead of the lower numbered one starving the
1518    /// other.
1519    pub fn expire_step(&self, budget: usize) -> usize {
1520        let mut spent = 0;
1521        let from = self.expire_db.load(Relaxed);
1522        for turn in 0..self.slots() {
1523            if spent >= budget {
1524                break;
1525            }
1526            let i = (from + turn) % self.slots();
1527            let c = self.slot(i).expire_cycle(budget - spent);
1528            spent += c.examined;
1529            if c.expired > 0 {
1530                self.expire_db.store((i + 1) % self.slots(), Relaxed);
1531                self.mine().note(1u64 << self.slot_db(i));
1532            }
1533        }
1534        spent
1535    }
1536
1537    /// One slice of compaction for a server that is over its limit.
1538    ///
1539    /// Takes the databases in the same order [`Server::compact_step`] does and
1540    /// stops at the first one that had something to move, and it asks with the
1541    /// ratios off. See [`Keyspace::compact_hard`] for what that changes.
1542    fn compact_hard_step(&self) -> Option<usize> {
1543        let from = self.next_db.load(Relaxed);
1544        for turn in 0..self.slots() {
1545            let i = (from + turn) % self.slots();
1546            if let Some(moved) = self.slot(i).compact_hard() {
1547                self.next_db.store((i + 1) % self.slots(), Relaxed);
1548                return Some(moved);
1549            }
1550        }
1551        None
1552    }
1553
1554    /// Take what every thread has marked and add it to the turn's own mask.
1555    ///
1556    /// The mask the turn works from is its own and not a shared one, because a
1557    /// mask it read in place and then cleared a bit of would be a mask that lost
1558    /// whatever another thread marked in between. A swap cannot lose a mark: a
1559    /// thread that ors while the swap happens either gets its bit in before the
1560    /// swap or leaves it there afterwards, and the second one costs one look at
1561    /// a database the turn has already been through.
1562    fn collect_marks(&self) {
1563        let mut marked = 0;
1564        for thread in &self.locals {
1565            marked |= thread.dirty.swap(0, Relaxed);
1566        }
1567        self.mine().note(marked);
1568    }
1569
1570    /// Give one database's dead space back, if any database has enough of it to
1571    /// be worth the move. `None` when no database had a candidate.
1572    ///
1573    /// Once per batch, next to the clock. Overwriting a key writes a new record
1574    /// and counts the old one dead, so without this a server holds everything
1575    /// it has ever written: 400000 sets over 100000 keys measured at 742 bytes
1576    /// a key against Redis at 144 for the same load, and the whole difference
1577    /// was dead records nothing ever came back for.
1578    ///
1579    /// At most one segment moves per call and the search starts one database
1580    /// further along each time, so the cost of asking is a comparison per
1581    /// database and the cost of acting is bounded by a segment.
1582    pub fn compact_step(&self) -> Option<usize> {
1583        self.collect_marks();
1584        let mine = self.mine();
1585        let from = self.next_db.load(Relaxed);
1586        for turn in 0..self.slots() {
1587            let i = (from + turn) % self.slots();
1588            // Nothing has run against this database since it last said it had
1589            // nothing to collect, so it still has nothing to collect and the
1590            // line it lives on stays where it is.
1591            let at = self.slot_db(i);
1592            if !mine.wanted(at) {
1593                continue;
1594            }
1595            if let Some(moved) = self.slot(i).compact_step() {
1596                self.next_db.store((i + 1) % self.slots(), Relaxed);
1597                return Some(moved);
1598            }
1599            // Only once every stripe of the database has said it has nothing,
1600            // since the bit is per database and one stripe answering for all of
1601            // them would stop the others being asked at all.
1602            if i % self.width == self.width - 1 {
1603                mine.done(at);
1604            }
1605        }
1606        None
1607    }
1608}
1609
1610impl Default for Server {
1611    fn default() -> Server {
1612        Server::new()
1613    }
1614}
1615
1616/// What one connection has chosen.
1617pub struct Session {
1618    db: usize,
1619    id: u64,
1620    name: Vec<u8>,
1621    /// The `HIMPORT` fieldsets this connection has prepared.
1622    ///
1623    /// Connection state and not keyspace state, which is the reference's design
1624    /// and not a shortcut: a fieldset is invisible to every other connection and
1625    /// the keys built from one outlive it.
1626    sets: himport::Fieldsets,
1627}
1628
1629impl Session {
1630    /// A new connection, on database zero with no name.
1631    #[must_use]
1632    pub fn new(id: u64) -> Session {
1633        Session {
1634            db: 0,
1635            id,
1636            name: Vec::new(),
1637            sets: himport::Fieldsets::default(),
1638        }
1639    }
1640
1641    /// The connection id, which `HELLO` reports and `CLIENT` will.
1642    #[must_use]
1643    pub const fn id(&self) -> u64 {
1644        self.id
1645    }
1646
1647    /// Which database this connection is working in.
1648    #[must_use]
1649    pub const fn db(&self) -> usize {
1650        self.db
1651    }
1652
1653    /// The name the client gave itself, empty if it gave none.
1654    #[must_use]
1655    pub fn name(&self) -> &[u8] {
1656        &self.name
1657    }
1658
1659    /// Put everything back the way it was when the connection was opened.
1660    ///
1661    /// The protocol is not here because it is not here: it lives in the reply
1662    /// buffer, and `RESET` sets it back there.
1663    pub fn reset(&mut self) {
1664        self.db = 0;
1665        self.name.clear();
1666        // `SELECT` leaves these alone and `RESET` does not, both checked
1667        // against 8.10.1, which is the one pair of answers you could not guess
1668        // from what the command is for.
1669        self.sets.clear();
1670    }
1671
1672    /// Record the name from `HELLO ... SETNAME`.
1673    fn set_name(&mut self, name: &[u8]) {
1674        yo_alloc::allow(|| {
1675            self.name.clear();
1676            self.name.extend_from_slice(name);
1677        });
1678    }
1679}
1680
1681/// Run one command and write its reply.
1682///
1683/// The name is looked up and the arity is checked here, once, so that no body
1684/// has to. Everything after that is the command's own.
1685pub fn execute(server: &Server, session: &mut Session, args: Args<'_>, out: &mut Out) -> Flow {
1686    // The decoder never produces a command with no name. If one ever arrives,
1687    // it is not something to answer.
1688    if args.is_empty() {
1689        return Flow::Continue;
1690    }
1691    resolved(server, session, lookup(args.name()), args, out)
1692}
1693
1694/// The same, for a caller that has already found the command.
1695///
1696/// The engine frames a command before it runs it, and between those two it also
1697/// asks which key the command touches so the record can be prefetched. That is
1698/// two more chances to look the name up, and looking it up three times to run it
1699/// once is three times the cost of the cheapest thing in the path. So the engine
1700/// resolves the name where it frames the command, carries the answer on the
1701/// framed command, and both the other two take it from there.
1702///
1703/// `spec` is `None` for a name that is not a command, which is the same thing
1704/// [`lookup`] says and lands in the same reply.
1705pub fn resolved(
1706    server: &Server,
1707    session: &mut Session,
1708    spec: Option<&'static Spec>,
1709    args: Args<'_>,
1710    out: &mut Out,
1711) -> Flow {
1712    if args.is_empty() {
1713        return Flow::Continue;
1714    }
1715    server.mine().stats.commands.bump();
1716
1717    let Some(spec) = spec else {
1718        write_error(out, &args::unknown_command(args));
1719        return Flow::Continue;
1720    };
1721    if !arity_ok(spec, args.len()) {
1722        server.mine().cmdstats.at(spec).rejected.bump();
1723        write_error(out, &args::wrong_arity(spec.name));
1724        return Flow::Continue;
1725    }
1726
1727    // The limit first, so a server with no `maxmemory`, which is the default and
1728    // is nearly all of them, pays one comparison against a field that is already
1729    // warm. Every command and not only the writes, because that is where Redis
1730    // puts it: making room is the server's job whatever the client asked for,
1731    // and the flag only decides who gets told no when there is no room to make.
1732    //
1733    // The flag is Redis's own `denyoom` and the list of commands carrying it is
1734    // Redis's list, so a command that only frees is let through with nothing
1735    // left, which is what lets a client dig itself out with `DEL`.
1736    if server.maxmemory() != 0 && !server.make_room() && spec.flags.contains(&"denyoom") {
1737        server.mine().cmdstats.at(spec).rejected.bump();
1738        out.error_line(b"OOM ", OOM);
1739        return Flow::Continue;
1740    }
1741
1742    // Which databases the maintenance turn after this batch has to ask. Marked
1743    // for every command and not only for the writes, because a read can make
1744    // garbage too: a `GET` on a key whose expiry has passed reaps it, and the
1745    // record it dropped is exactly the kind of thing the collector is for.
1746    // `COPY`, `SWAPDB` and `FLUSHALL` reach a database nobody selected, so the
1747    // two groups that hold them mark all of them rather than the session's.
1748    server.mine().mark(match spec.group {
1749        "string" | "bitmap" | "hyperloglog" | "geo" | "set" | "hash" | "list" | "zset"
1750        | "array" | "stream" | "bloom" | "cuckoo" | "cms" | "topk" | "tdigest" | "ts" => {
1751            1u64 << session.db
1752        }
1753        _ => ALL_DATABASES,
1754    });
1755
1756    let mark = out.len();
1757    // Before the group, because the five that block are list commands and would
1758    // otherwise land in `lists`, which is handed one database and nothing that
1759    // could park a client. The flag is the right thing to branch on rather than
1760    // a list of names: it is what `COMMAND INFO` reports about exactly these
1761    // commands, and the sorted set and stream ones that arrive later carry it
1762    // too.
1763    let done = if spec.flags.contains(&"blocking") {
1764        blocking::execute(server, session, spec, args, out)
1765    } else {
1766        match spec.group {
1767            "string" => {
1768                let db = session.db;
1769                strings::execute(&server.dbs[db], spec, args, out).map(|()| Flow::Continue)
1770            }
1771            // Its own group and its own file, and the same values underneath:
1772            // a bitmap is a string, so `STRLEN` on one answers and `SETBIT` on
1773            // something a `SET` left behind works.
1774            "bitmap" => {
1775                let db = session.db;
1776                bits::execute(&server.dbs[db], spec, args, out).map(|()| Flow::Continue)
1777            }
1778            // The same again: a sketch is a string with a documented layout, so
1779            // `GET` hands one to a client and `SET` takes it back.
1780            "hyperloglog" => {
1781                let db = session.db;
1782                hll::execute(&server.dbs[db], spec, args, out).map(|()| Flow::Continue)
1783            }
1784            "set" => {
1785                let db = session.db;
1786                sets::execute(&server.dbs[db], spec, args, out).map(|()| Flow::Continue)
1787            }
1788            // The one hash command whose state is not in the keyspace. A
1789            // fieldset belongs to the connection, so this is handed the session
1790            // as well as the database, the same exception `MIGRATE` gets in the
1791            // keyspace group for the socket it keeps.
1792            "hash" if spec.name == "himport" => {
1793                let db = session.db;
1794                himport::execute(&server.dbs[db], &mut session.sets, args, out)
1795                    .map(|()| Flow::Continue)
1796            }
1797            // The one group that reaches back into the server after it has
1798            // written its reply, because a hash is what a search index is
1799            // made of. What comes back is what the indexes have to be told,
1800            // which is not the same as whether the command was a write.
1801            "hash" => {
1802                let db = session.db;
1803                let changed = hashes::execute(&server.dbs[db], spec, args, out);
1804                changed.map(|changed| {
1805                    indexing::changed(server, db, args.get(1), changed);
1806                    Flow::Continue
1807                })
1808            }
1809            "list" => {
1810                let db = session.db;
1811                lists::execute(&server.dbs[db], spec, args, out).map(|()| Flow::Continue)
1812            }
1813            "zset" => {
1814                let db = session.db;
1815                zsets::execute(&server.dbs[db], spec, args, out).map(|()| Flow::Continue)
1816            }
1817            // A geo key is a sorted set and these are sorted set commands with
1818            // arithmetic on the way in and on the way out, so a client can ZREM
1819            // a place out of one and ZCARD it to count them.
1820            "geo" => {
1821                let db = session.db;
1822                geo::execute(&server.dbs[db], spec, args, out).map(|()| Flow::Continue)
1823            }
1824            "array" => {
1825                let db = session.db;
1826                arrays::execute(&server.dbs[db], spec, args, out).map(|()| Flow::Continue)
1827            }
1828            "graph" => {
1829                let db = session.db;
1830                graph::execute(&server.dbs[db], spec, args, out).map(|()| Flow::Continue)
1831            }
1832            // A document under a key, reached by a path. The group is Redis's
1833            // module surface and the storage is ours, the same trade the vector
1834            // set group makes.
1835            "json" => {
1836                let db = session.db;
1837                json::execute(&server.dbs[db], spec, args, out).map(|()| Flow::Continue)
1838            }
1839            "vector" => {
1840                let db = session.db;
1841                vectors::execute(&server.dbs[db], spec, args, out).map(|()| Flow::Continue)
1842            }
1843            "bloom" => {
1844                let db = session.db;
1845                bloom::execute(&server.dbs[db], spec, args, out).map(|()| Flow::Continue)
1846            }
1847            "cuckoo" => {
1848                let db = session.db;
1849                cuckoo::execute(&server.dbs[db], spec, args, out).map(|()| Flow::Continue)
1850            }
1851            "cms" => {
1852                let db = session.db;
1853                cms::execute(&server.dbs[db], spec, args, out).map(|()| Flow::Continue)
1854            }
1855            "topk" => {
1856                let db = session.db;
1857                topk::execute(&server.dbs[db], spec, args, out).map(|()| Flow::Continue)
1858            }
1859            "tdigest" => {
1860                let db = session.db;
1861                tdigest::execute(&server.dbs[db], spec, args, out).map(|()| Flow::Continue)
1862            }
1863            "ts" => {
1864                let db = session.db;
1865                ts::execute(&server.dbs[db], spec, args, out).map(|()| Flow::Continue)
1866            }
1867            // The clock is read before the database is borrowed, because every
1868            // stream command needs the time and it lives on the server. An
1869            // `XADD` with no ID, an `XCLAIM` working out what is idle and an
1870            // `XINFO` reporting it all have to agree about what moment this is.
1871            "stream" => {
1872                let db = session.db;
1873                let now = server.now_ms();
1874                streams::execute(&server.dbs[db], spec, args, now, out).map(|()| Flow::Continue)
1875            }
1876            // The one keyspace command that needs more than the databases,
1877            // because the socket it talks down is held on the server between
1878            // commands and not opened again for each one.
1879            "keyspace" if spec.name == "migrate" => {
1880                migrate::execute(server, session.db, args, out).map(|()| Flow::Continue)
1881            }
1882            // Every database and not the one the session is on, because `COPY` takes
1883            // a `DB n` and writes into a database nobody selected. The other group
1884            // that reaches back into the server afterwards, and it hands back a list
1885            // rather than one answer, because `DEL a b c` is three keys and a rename
1886            // is two.
1887            "keyspace" => {
1888                let mut touched = indexing::Touched::new(server);
1889                let done =
1890                    keyspace::execute(&server.dbs, session.db, spec, args, out, &mut touched);
1891                done.map(|()| {
1892                    indexing::touched(server, &touched);
1893                    Flow::Continue
1894                })
1895            }
1896            // No database at all, because an index is not a key. The registry
1897            // is the whole of what these sixteen commands touch, and then
1898            // `FT.CREATE` hands back the name it made so the keys that
1899            // already match its prefix can be read into it. The lock goes
1900            // before the scan runs, since the scan takes it again for every
1901            // key it reads.
1902            "search" if spec.name == "FT.SEARCH" => {
1903                // The two search commands that read documents, and so the two
1904                // that need the keyspace as well as the registry. They take and
1905                // let go of the registry themselves, because they cannot hold
1906                // that and a stripe at the same time.
1907                search::find(server, session.db, args, out).map(|()| Flow::Continue)
1908            }
1909            "search" if spec.name == "FT.AGGREGATE" => {
1910                search::roll(server, session.db, args, out).map(|()| Flow::Continue)
1911            }
1912            "search" if spec.name == "FT.PROFILE" => {
1913                // Which is one of those two with the working shown, so it needs
1914                // everything they need and takes the same route to it.
1915                search::profiled(server, session.db, args, out).map(|()| Flow::Continue)
1916            }
1917            // The four search commands that name a key rather than an index.
1918            // A suggestion dictionary is a real key with a type of its own, so
1919            // these are handed a database and never touch the registry.
1920            "search" if spec.name.starts_with("FT.SUG") => {
1921                let db = session.db;
1922                suggest::execute(&server.dbs[db], spec, args, out).map(|()| Flow::Continue)
1923            }
1924            "search" if spec.name == "FT.CURSOR" => {
1925                // Its own arm because the cursors are not in the registry, and
1926                // it takes and lets go of the registry itself to look up the
1927                // index name it is given.
1928                search::cursor::execute(server, args, out).map(|()| Flow::Continue)
1929            }
1930            "search" => {
1931                let db = session.db;
1932                let made = search::execute(server, &mut server.search.lock(), db, spec, args, out);
1933                made.map(|made| {
1934                    if let Some(fill) = made {
1935                        indexing::scan(server, db, &fill);
1936                    }
1937                    Flow::Continue
1938                })
1939            }
1940            "scripting" => scripting::execute(spec, args, out).map(|()| Flow::Continue),
1941            _ => server::execute(server, session, spec, args, out),
1942        }
1943    };
1944    let flow = match done {
1945        Ok(flow) => flow,
1946        Err(e) => {
1947            out.truncate(mark);
1948            write_error(out, &e);
1949            Flow::Continue
1950        }
1951    };
1952
1953    // Counted here and not before the call, which is where Redis counts it, so
1954    // that `INFO commandstats` leaves out the `INFO` that asked for it in the
1955    // same way theirs does.
1956    //
1957    // Failure is read off the reply rather than off the `Result`, because the
1958    // two are not the same set. A command that ran out of arguments comes back
1959    // as an `Err` and a command that was sent the wrong password writes its own
1960    // error line and comes back `Ok`, and both of those are a call that failed.
1961    // The first byte at the mark is what a client would branch on, and it is `-`
1962    // for an error on either protocol and `!` for RESP3's long form.
1963    let row = server.mine().cmdstats.at(spec);
1964    row.calls.bump();
1965    if matches!(out.as_slice().get(mark), Some(b'-' | b'!')) {
1966        row.failed.bump();
1967    }
1968    flow
1969}
1970
1971/// The error line for an error value.
1972///
1973/// The prefix is what a client branches on, and there are three of them:
1974/// `WRONGTYPE` for a command sent at the wrong kind of value, `INVALIDOBJ` for a
1975/// HyperLogLog whose opcodes do not add up, and `ERR` for everything else. The three errors that need a different one,
1976/// `NOPROTO`, `WRONGPASS` and `OOM`, are written where they are decided rather
1977/// than routed through here. `OOM` is not a [`Code`] of its own because
1978/// [`Code::Full`] already covers the string that is too long for
1979/// `proto-max-bulk-len`, and that one goes out as `ERR` on a real server.
1980fn write_error(out: &mut Out, e: &Error) {
1981    let prefix: &[u8] = match e.code() {
1982        Code::WrongType => b"WRONGTYPE ",
1983        // Only the HyperLogLog commands answer this one, and the prefix is the
1984        // sentence a client branches on to tell a sketch it cannot read from a
1985        // sketch it sent wrong.
1986        Code::Corrupt => b"INVALIDOBJ ",
1987        _ => b"ERR ",
1988    };
1989    out.error_line(prefix, e.message().as_bytes());
1990}
1991
1992#[cfg(test)]
1993mod tests {
1994    use super::*;
1995    use crate::proto::{Limits, Proto};
1996    use crate::request::Argv;
1997
1998    /// Build the wire bytes for a command.
1999    ///
2000    /// Tests go through the codec rather than around it, so an argument in a
2001    /// test is the same borrowed slice a connection produces.
2002    pub(crate) fn encode(parts: &[&[u8]]) -> Vec<u8> {
2003        let mut wire = format!("*{}\r\n", parts.len()).into_bytes();
2004        for p in parts {
2005            wire.extend_from_slice(format!("${}\r\n", p.len()).as_bytes());
2006            wire.extend_from_slice(p);
2007            wire.extend_from_slice(b"\r\n");
2008        }
2009        wire
2010    }
2011
2012    /// A server, a connection and a buffer, driven the way the reactor will.
2013    struct Fixture {
2014        server: Server,
2015        session: Session,
2016        argv: Argv,
2017        out: Out,
2018    }
2019
2020    impl Fixture {
2021        fn new() -> Fixture {
2022            Fixture::on(Server::new())
2023        }
2024
2025        /// The same, on a server whose databases are cut into `width` stripes.
2026        fn striped(width: usize) -> Fixture {
2027            Fixture::on(Server::with_width(width))
2028        }
2029
2030        fn on(server: Server) -> Fixture {
2031            Fixture {
2032                server,
2033                session: Session::new(7),
2034                argv: Argv::new(),
2035                out: Out::new(Proto::Resp2),
2036            }
2037        }
2038
2039        /// Run one command and answer with the bytes it wrote.
2040        fn run(&mut self, parts: &[&[u8]]) -> String {
2041            self.flow(parts).1
2042        }
2043
2044        /// Run one command and answer with the bytes exactly as written.
2045        ///
2046        /// [`Fixture::run`] goes through `from_utf8_lossy`, which is fine for
2047        /// every reply that is text and destroys a `DUMP` payload, since a
2048        /// payload is arbitrary bytes and a checksum on the end of them.
2049        fn raw(&mut self, parts: &[&[u8]]) -> Vec<u8> {
2050            let wire = encode(parts);
2051            self.argv.decode(&wire, &Limits::default()).unwrap();
2052            self.out.clear();
2053            execute(
2054                &self.server,
2055                &mut self.session,
2056                Args::new(&self.argv, &wire),
2057                &mut self.out,
2058            );
2059            self.out.as_slice().to_vec()
2060        }
2061
2062        /// Move every clock in the server on by `ms`.
2063        fn advance(&mut self, ms: u64) {
2064            self.server.advance_clock_ms(ms);
2065        }
2066
2067        /// The same, with what the connection should do next.
2068        fn flow(&mut self, parts: &[&[u8]]) -> (Flow, String) {
2069            let wire = encode(parts);
2070            self.argv.decode(&wire, &Limits::default()).unwrap();
2071            self.out.clear();
2072            let flow = execute(
2073                &self.server,
2074                &mut self.session,
2075                Args::new(&self.argv, &wire),
2076                &mut self.out,
2077            );
2078            (
2079                flow,
2080                String::from_utf8_lossy(self.out.as_slice()).into_owned(),
2081            )
2082        }
2083    }
2084
2085    /// What a client does all day: write the same keys again and again. Every
2086    /// one of those writes leaves the previous record behind, so a server that
2087    /// never compacts holds every version of every key it has ever been sent.
2088    ///
2089    /// Not under Miri, and not because of anything it would find. The bound
2090    /// only means something once several megabytes have gone through the
2091    /// arena, which reclaims a segment at a time and has segments of two
2092    /// megabytes, so a server that reclaimed nothing would still be under the
2093    /// bound in any smaller version of this. Thirty two megabytes is thirty
2094    /// two thousand commands and was over forty minutes interpreted. The paths
2095    /// it walks are walked by the hundreds of tests around it that write a key
2096    /// and read it back, which do run there.
2097    #[cfg_attr(miri, ignore = "megabytes through the arena")]
2098    #[test]
2099    fn rewriting_the_same_keys_does_not_grow_the_server() {
2100        let mut f = Fixture::new();
2101        let val = vec![b'v'; 1024];
2102        let keys: Vec<Vec<u8>> = (0..64).map(|i| format!("key:{i}").into_bytes()).collect();
2103
2104        for k in &keys {
2105            f.run(&[b"SET", k, &val]);
2106        }
2107        f.server.compact_step();
2108        let after_first = f.server.memory_bytes();
2109
2110        // 64 KiB a pass, five hundred passes, and the same 64 keys at the end
2111        // of it. Thirty two megabytes written to hold sixty four kilobytes,
2112        // which is the shape of a real workload and is enough churn to fill
2113        // sixteen segments if nothing ever comes back.
2114        for _ in 0..500 {
2115            for k in &keys {
2116                f.run(&[b"SET", k, &val]);
2117            }
2118            f.server.compact_step();
2119        }
2120
2121        assert!(
2122            f.server.memory_bytes() <= after_first * 2,
2123            "held {} after five hundred passes against {after_first} after one",
2124            f.server.memory_bytes()
2125        );
2126        assert_eq!(f.run(&[b"DBSIZE"]), format!(":{}\r\n", keys.len()));
2127        assert_eq!(f.run(&[b"STRLEN", b"key:7"]), ":1024\r\n");
2128    }
2129
2130    /// The same churn on a database nobody starts on, either side of a quiet
2131    /// spell long enough for the maintenance turn to stop asking about it.
2132    ///
2133    /// The turn after each batch skips a database that has already said it has
2134    /// nothing to collect and has not been touched since, which is what keeps a
2135    /// server whose clients are all on database zero from loading and storing
2136    /// in the other fifteen every batch to be told no. Two things could go
2137    /// wrong with that. A database might never be marked at all, so this uses
2138    /// database nine, which nothing marks by accident. And a database whose
2139    /// mark was cleared might never get it back, so this drains the collector
2140    /// until it says there is nothing left, checks the mark really is gone, and
2141    /// then writes another thirty two megabytes through the same sixty four
2142    /// keys. If either went wrong the server would hold all of it.
2143    ///
2144    /// Not under Miri, for the reason on the test above: the volume is the
2145    /// claim, and the volume is what the interpreter charges for.
2146    #[cfg_attr(miri, ignore = "megabytes through the arena")]
2147    #[test]
2148    fn a_database_nobody_started_on_is_still_collected() {
2149        let mut f = Fixture::new();
2150        assert_eq!(f.run(&[b"SELECT", b"9"]), "+OK\r\n");
2151        let val = vec![b'v'; 1024];
2152        let keys: Vec<Vec<u8>> = (0..64).map(|i| format!("key:{i}").into_bytes()).collect();
2153
2154        for k in &keys {
2155            f.run(&[b"SET", k, &val]);
2156        }
2157        while f.server.compact_step().is_some() {}
2158        assert!(
2159            !f.server.mine().wanted(9),
2160            "database nine was drained and should not be asked again until it is written to"
2161        );
2162        let after_first = f.server.memory_bytes();
2163
2164        for _ in 0..500 {
2165            for k in &keys {
2166                f.run(&[b"SET", k, &val]);
2167            }
2168            f.server.compact_step();
2169        }
2170
2171        assert!(
2172            f.server.memory_bytes() <= after_first * 2,
2173            "held {} after five hundred passes against {after_first} after one",
2174            f.server.memory_bytes()
2175        );
2176        assert_eq!(f.run(&[b"DBSIZE"]), format!(":{}\r\n", keys.len()));
2177        assert_eq!(f.run(&[b"STRLEN", b"key:7"]), ":1024\r\n");
2178        // And nothing landed anywhere else on the way.
2179        f.run(&[b"SELECT", b"0"]);
2180        assert_eq!(f.run(&[b"DBSIZE"]), ":0\r\n");
2181    }
2182
2183    #[test]
2184    fn a_command_goes_from_bytes_to_bytes() {
2185        let mut f = Fixture::new();
2186        assert_eq!(f.run(&[b"SET", b"k", b"v"]), "+OK\r\n");
2187        assert_eq!(f.run(&[b"GET", b"k"]), "$1\r\nv\r\n");
2188        assert_eq!(f.run(&[b"GET", b"nosuch"]), "$-1\r\n");
2189        assert_eq!(f.run(&[b"STRLEN", b"k"]), ":1\r\n");
2190        // The name is matched whatever case it came in, and so are the options.
2191        assert_eq!(f.run(&[b"set", b"k", b"v2", b"xx"]), "+OK\r\n");
2192        assert_eq!(f.run(&[b"GET", b"k"]), "$2\r\nv2\r\n");
2193    }
2194
2195    #[test]
2196    fn deleting_counts_keys_removed_and_existing_counts_arguments_matched() {
2197        let mut f = Fixture::new();
2198        f.run(&[b"MSET", b"a", b"1", b"b", b"2", b"c", b"3"]);
2199        // A key named twice exists twice and can only be deleted once, and both
2200        // of those are Redis's answers rather than tidier ones.
2201        assert_eq!(f.run(&[b"EXISTS", b"a", b"a", b"nosuch"]), ":2\r\n");
2202        assert_eq!(f.run(&[b"DEL", b"a", b"a", b"nosuch"]), ":1\r\n");
2203        assert_eq!(f.run(&[b"EXISTS", b"a"]), ":0\r\n");
2204        // UNLINK is the same body and reports the same way.
2205        assert_eq!(f.run(&[b"UNLINK", b"b", b"c"]), ":2\r\n");
2206        assert_eq!(f.run(&[b"DBSIZE"]), ":0\r\n");
2207    }
2208
2209    #[test]
2210    fn type_is_a_simple_string_and_says_none_for_a_key_that_is_not_there() {
2211        let mut f = Fixture::new();
2212        f.run(&[b"SET", b"k", b"v"]);
2213        // A simple string on both protocols, which is unusual: most replies
2214        // that carry a word are bulk strings.
2215        assert_eq!(f.run(&[b"TYPE", b"k"]), "+string\r\n");
2216        assert_eq!(f.run(&[b"TYPE", b"nosuch"]), "+none\r\n");
2217    }
2218
2219    #[test]
2220    fn touch_counts_the_way_exists_counts() {
2221        let mut f = Fixture::new();
2222        f.run(&[b"MSET", b"a", b"1", b"b", b"2"]);
2223        assert_eq!(f.run(&[b"TOUCH", b"a", b"b"]), ":2\r\n");
2224        assert_eq!(
2225            f.run(&[b"TOUCH", b"a", b"a"]),
2226            ":2\r\n",
2227            "twice counts twice"
2228        );
2229        assert_eq!(f.run(&[b"TOUCH", b"a", b"nosuch"]), ":1\r\n");
2230        assert_eq!(f.run(&[b"TOUCH", b"nosuch"]), ":0\r\n");
2231    }
2232
2233    #[test]
2234    fn a_rename_moves_the_deadline_with_the_value_and_drops_the_one_it_lands_on() {
2235        let mut f = Fixture::new();
2236        f.run(&[b"SET", b"a", b"v1", b"EX", b"100"]);
2237        f.run(&[b"SET", b"b", b"v2", b"EX", b"500"]);
2238
2239        assert_eq!(f.run(&[b"RENAME", b"a", b"b"]), "+OK\r\n");
2240        assert_eq!(f.run(&[b"GET", b"b"]), "$2\r\nv1\r\n");
2241        assert_eq!(
2242            f.run(&[b"TTL", b"b"]),
2243            ":100\r\n",
2244            "the source's and not b's"
2245        );
2246        assert_eq!(f.run(&[b"EXISTS", b"a"]), ":0\r\n");
2247    }
2248
2249    #[test]
2250    fn a_rename_with_no_source_is_an_error_and_not_a_zero() {
2251        let mut f = Fixture::new();
2252        assert_eq!(f.run(&[b"RENAME", b"a", b"b"]), "-ERR no such key\r\n");
2253        // The source is checked before the destination, so this is the error
2254        // and not the zero RENAMENX would otherwise answer for a taken name.
2255        assert_eq!(f.run(&[b"RENAMENX", b"a", b"a"]), "-ERR no such key\r\n");
2256    }
2257
2258    #[test]
2259    fn renamenx_refuses_a_taken_name_including_the_one_it_already_has() {
2260        let mut f = Fixture::new();
2261        f.run(&[b"MSET", b"a", b"v1", b"b", b"v2"]);
2262
2263        assert_eq!(f.run(&[b"RENAMENX", b"a", b"b"]), ":0\r\n");
2264        assert_eq!(f.run(&[b"GET", b"b"]), "$2\r\nv2\r\n");
2265        // Renaming onto itself is 0 here and OK for plain RENAME, which is the
2266        // one call the two disagree about and neither does any work for.
2267        assert_eq!(f.run(&[b"RENAMENX", b"a", b"a"]), ":0\r\n");
2268        assert_eq!(f.run(&[b"RENAME", b"a", b"a"]), "+OK\r\n");
2269        assert_eq!(f.run(&[b"RENAMENX", b"a", b"c"]), ":1\r\n");
2270        assert_eq!(f.run(&[b"GET", b"c"]), "$2\r\nv1\r\n");
2271    }
2272
2273    #[test]
2274    fn renaming_a_set_does_not_touch_a_member() {
2275        let mut f = Fixture::new();
2276        for i in 0..300 {
2277            f.run(&[b"SADD", b"s", format!("m{i}").as_bytes()]);
2278        }
2279        let before = f.server.memory_bytes();
2280
2281        assert_eq!(f.run(&[b"RENAME", b"s", b"t"]), "+OK\r\n");
2282        assert_eq!(f.run(&[b"SCARD", b"t"]), ":300\r\n");
2283        assert_eq!(f.run(&[b"TYPE", b"t"]), "+set\r\n");
2284        assert!(
2285            f.server.memory_bytes().abs_diff(before) < 256,
2286            "the members were copied: {} against {before}",
2287            f.server.memory_bytes()
2288        );
2289    }
2290
2291    #[test]
2292    fn a_copy_is_a_second_value_and_not_a_second_name() {
2293        let mut f = Fixture::new();
2294        f.run(&[b"SADD", b"s", b"m1", b"m2"]);
2295
2296        assert_eq!(f.run(&[b"COPY", b"s", b"t"]), ":1\r\n");
2297        f.run(&[b"SADD", b"t", b"m3"]);
2298        assert_eq!(f.run(&[b"SCARD", b"s"]), ":2\r\n", "the original is intact");
2299        assert_eq!(f.run(&[b"SCARD", b"t"]), ":3\r\n");
2300    }
2301
2302    /// Every type a key can hold, copied, because two of them used to panic.
2303    ///
2304    /// `COPY` reads the value out of the source through one match on the type
2305    /// tag, and that match had a catch all at the bottom from back when a set
2306    /// and a hash were the only bodies. The list and the sorted set landed after
2307    /// it and nobody came back, so `COPY mylist other` took the shard down. It
2308    /// is an ordinary command against a type the server supports everywhere
2309    /// else, so this walks all five rather than the two that were broken: the
2310    /// point is that the next type cannot land the same way.
2311    #[test]
2312    fn every_type_can_be_copied() {
2313        let mut f = Fixture::new();
2314        f.run(&[b"SET", b"str", b"v1"]);
2315        f.run(&[b"SADD", b"set", b"m1"]);
2316        f.run(&[b"HSET", b"hash", b"f", b"v"]);
2317        f.run(&[b"RPUSH", b"list", b"a", b"b"]);
2318        f.run(&[b"ZADD", b"zset", b"1", b"m1"]);
2319
2320        for name in [
2321            &b"str"[..],
2322            &b"set"[..],
2323            &b"hash"[..],
2324            &b"list"[..],
2325            &b"zset"[..],
2326        ] {
2327            let dst = [name, b":copy"].concat();
2328            assert_eq!(
2329                f.run(&[b"COPY", name, &dst]),
2330                ":1\r\n",
2331                "copying {}",
2332                String::from_utf8_lossy(name)
2333            );
2334            assert_eq!(f.run(&[b"TYPE", name]), f.run(&[b"TYPE", &dst]));
2335        }
2336
2337        assert_eq!(f.run(&[b"LRANGE", b"list:copy", b"0", b"-1"]), {
2338            let mut want = String::from("*2\r\n");
2339            want.push_str("$1\r\na\r\n$1\r\nb\r\n");
2340            want
2341        });
2342        assert_eq!(f.run(&[b"ZSCORE", b"zset:copy", b"m1"]), "$1\r\n1\r\n");
2343
2344        // And the copy is its own value, not a second name for the source.
2345        f.run(&[b"RPUSH", b"list:copy", b"c"]);
2346        assert_eq!(f.run(&[b"LLEN", b"list"]), ":2\r\n");
2347        assert_eq!(f.run(&[b"LLEN", b"list:copy"]), ":3\r\n");
2348    }
2349
2350    #[test]
2351    fn a_copy_refuses_a_taken_destination_until_it_is_told_it_can_have_it() {
2352        let mut f = Fixture::new();
2353        f.run(&[b"SET", b"a", b"v1", b"EX", b"100"]);
2354        f.run(&[b"SET", b"b", b"v2"]);
2355
2356        assert_eq!(f.run(&[b"COPY", b"a", b"b"]), ":0\r\n");
2357        assert_eq!(f.run(&[b"GET", b"b"]), "$2\r\nv2\r\n");
2358        assert_eq!(f.run(&[b"COPY", b"a", b"b", b"REPLACE"]), ":1\r\n");
2359        assert_eq!(f.run(&[b"GET", b"b"]), "$2\r\nv1\r\n");
2360        assert_eq!(f.run(&[b"TTL", b"b"]), ":100\r\n", "the deadline came too");
2361        assert_eq!(f.run(&[b"COPY", b"nosuch", b"z"]), ":0\r\n");
2362    }
2363
2364    #[test]
2365    fn a_copy_into_another_database_is_a_copy_and_onto_itself_there_is_too() {
2366        let mut f = Fixture::new();
2367        f.run(&[b"SET", b"a", b"v1"]);
2368
2369        // Same key, different database, so this is not the same object and is
2370        // an ordinary copy. Same key in the same database is the error below.
2371        assert_eq!(f.run(&[b"COPY", b"a", b"a", b"DB", b"1"]), ":1\r\n");
2372        f.run(&[b"SELECT", b"1"]);
2373        assert_eq!(f.run(&[b"GET", b"a"]), "$2\r\nv1\r\n");
2374        assert_eq!(
2375            f.run(&[b"COPY", b"a", b"a", b"DB", b"0"]),
2376            ":0\r\n",
2377            "taken"
2378        );
2379        assert_eq!(
2380            f.run(&[b"COPY", b"a", b"a", b"DB", b"0", b"REPLACE"]),
2381            ":1\r\n"
2382        );
2383    }
2384
2385    #[test]
2386    fn sort_takes_its_options_in_any_order_and_the_last_one_wins() {
2387        let mut f = Fixture::new();
2388        f.run(&[b"RPUSH", b"l", b"3", b"1", b"2"]);
2389        assert_eq!(
2390            f.run(&[b"SORT", b"l"]),
2391            "*3\r\n$1\r\n1\r\n$1\r\n2\r\n$1\r\n3\r\n"
2392        );
2393        // DESC then ASC is ASC, because the only thing ASC does is undo a DESC.
2394        assert_eq!(
2395            f.run(&[b"SORT", b"l", b"DESC", b"asc"]),
2396            "*3\r\n$1\r\n1\r\n$1\r\n2\r\n$1\r\n3\r\n"
2397        );
2398        assert_eq!(
2399            f.run(&[b"sort", b"l", b"LIMIT", b"1", b"1", b"DESC"]),
2400            "*1\r\n$1\r\n2\r\n"
2401        );
2402    }
2403
2404    #[test]
2405    fn sort_reads_a_key_per_element_for_by_and_for_get() {
2406        let mut f = Fixture::new();
2407        f.run(&[b"RPUSH", b"l", b"a", b"b"]);
2408        f.run(&[b"MSET", b"w_a", b"2", b"w_b", b"1", b"d_b", b"bee"]);
2409        // `b` weighs less so it comes first, and its `GET` hits where `a`'s
2410        // misses, which is a nil in the middle of the array and not a short one.
2411        assert_eq!(
2412            f.run(&[b"SORT", b"l", b"BY", b"w_*", b"GET", b"#", b"GET", b"d_*"]),
2413            "*4\r\n$1\r\nb\r\n$3\r\nbee\r\n$1\r\na\r\n$-1\r\n"
2414        );
2415    }
2416
2417    #[test]
2418    fn sort_store_writes_a_list_and_answers_its_length() {
2419        let mut f = Fixture::new();
2420        f.run(&[b"RPUSH", b"l", b"3", b"1", b"2"]);
2421        assert_eq!(f.run(&[b"SORT", b"l", b"STORE", b"out"]), ":3\r\n");
2422        assert_eq!(f.run(&[b"TYPE", b"out"]), "+list\r\n");
2423        assert_eq!(
2424            f.run(&[b"LRANGE", b"out", b"0", b"-1"]),
2425            "*3\r\n$1\r\n1\r\n$1\r\n2\r\n$1\r\n3\r\n"
2426        );
2427        // An empty result takes the destination with it rather than leaving a
2428        // list that holds nothing.
2429        assert_eq!(f.run(&[b"SORT", b"missing", b"STORE", b"out"]), ":0\r\n");
2430        assert_eq!(f.run(&[b"EXISTS", b"out"]), ":0\r\n");
2431    }
2432
2433    #[test]
2434    fn sort_ro_does_not_know_the_word_store() {
2435        let mut f = Fixture::new();
2436        f.run(&[b"RPUSH", b"l", b"2", b"1"]);
2437        assert_eq!(f.run(&[b"SORT_RO", b"l"]), "*2\r\n$1\r\n1\r\n$1\r\n2\r\n");
2438        assert_eq!(
2439            f.run(&[b"SORT_RO", b"l", b"STORE", b"d"]),
2440            "-ERR syntax error\r\n"
2441        );
2442        assert_eq!(f.run(&[b"EXISTS", b"d"]), ":0\r\n");
2443    }
2444
2445    #[test]
2446    fn sort_refuses_what_it_cannot_sort() {
2447        let mut f = Fixture::new();
2448        assert_eq!(f.run(&[b"SORT", b"nosuchkey"]), "*0\r\n");
2449        f.run(&[b"SET", b"s", b"x"]);
2450        assert_eq!(
2451            f.run(&[b"SORT", b"s"]),
2452            "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n"
2453        );
2454        f.run(&[b"RPUSH", b"words", b"one", b"two"]);
2455        assert_eq!(
2456            f.run(&[b"SORT", b"words"]),
2457            "-ERR One or more scores can't be converted into double\r\n"
2458        );
2459        assert_eq!(
2460            f.run(&[b"SORT", b"words", b"ALPHA"]),
2461            "*2\r\n$3\r\none\r\n$3\r\ntwo\r\n"
2462        );
2463        assert_eq!(f.run(&[b"SORT", b"words", b"BY"]), "-ERR syntax error\r\n");
2464    }
2465
2466    #[test]
2467    fn move_takes_the_key_out_of_one_database_and_puts_it_in_another() {
2468        let mut f = Fixture::new();
2469        assert_eq!(f.run(&[b"RPUSH", b"l", b"a", b"b"]), ":2\r\n");
2470        assert_eq!(f.run(&[b"MOVE", b"l", b"1"]), ":1\r\n");
2471        assert_eq!(f.run(&[b"EXISTS", b"l"]), ":0\r\n");
2472        assert_eq!(f.run(&[b"SELECT", b"1"]), "+OK\r\n");
2473        assert_eq!(
2474            f.run(&[b"LRANGE", b"l", b"0", b"-1"]),
2475            "*2\r\n$1\r\na\r\n$1\r\nb\r\n"
2476        );
2477        // And back, which proves the body survived the trip rather than being
2478        // rebuilt from a copy that happened to look the same.
2479        assert_eq!(f.run(&[b"MOVE", b"l", b"0"]), ":1\r\n");
2480        assert_eq!(f.run(&[b"EXISTS", b"l"]), ":0\r\n");
2481    }
2482
2483    #[test]
2484    fn move_answers_zero_when_either_end_says_no() {
2485        let mut f = Fixture::new();
2486        assert_eq!(f.run(&[b"MOVE", b"nope", b"1"]), ":0\r\n");
2487        assert_eq!(f.run(&[b"SET", b"a", b"here"]), "+OK\r\n");
2488        assert_eq!(f.run(&[b"SELECT", b"1"]), "+OK\r\n");
2489        assert_eq!(f.run(&[b"SET", b"a", b"there"]), "+OK\r\n");
2490        assert_eq!(f.run(&[b"SELECT", b"0"]), "+OK\r\n");
2491        // The destination is taken, so nothing moves and the source is still
2492        // there with what it had.
2493        assert_eq!(f.run(&[b"MOVE", b"a", b"1"]), ":0\r\n");
2494        assert_eq!(f.run(&[b"GET", b"a"]), "$4\r\nhere\r\n");
2495        assert_eq!(f.run(&[b"SELECT", b"1"]), "+OK\r\n");
2496        assert_eq!(f.run(&[b"GET", b"a"]), "$5\r\nthere\r\n");
2497    }
2498
2499    #[test]
2500    fn move_refuses_a_database_that_is_not_one_and_the_one_it_is_on() {
2501        let mut f = Fixture::new();
2502        assert_eq!(
2503            f.run(&[b"MOVE", b"a", b"0"]),
2504            "-ERR source and destination objects are the same\r\n"
2505        );
2506        assert_eq!(
2507            f.run(&[b"MOVE", b"a", b"99"]),
2508            "-ERR DB index is out of range\r\n"
2509        );
2510        assert_eq!(
2511            f.run(&[b"MOVE", b"a", b"-1"]),
2512            "-ERR DB index is out of range\r\n"
2513        );
2514        assert_eq!(
2515            f.run(&[b"MOVE", b"a", b"x"]),
2516            "-ERR value is not an integer or out of range\r\n"
2517        );
2518    }
2519
2520    #[test]
2521    fn swapdb_swaps_what_two_connections_would_see() {
2522        let mut f = Fixture::new();
2523        assert_eq!(f.run(&[b"SET", b"k", b"zero"]), "+OK\r\n");
2524        assert_eq!(f.run(&[b"SELECT", b"1"]), "+OK\r\n");
2525        assert_eq!(f.run(&[b"SET", b"k", b"one"]), "+OK\r\n");
2526        assert_eq!(f.run(&[b"SELECT", b"0"]), "+OK\r\n");
2527
2528        assert_eq!(f.run(&[b"SWAPDB", b"0", b"1"]), "+OK\r\n");
2529        // Still on database zero, and database zero is a different database.
2530        assert_eq!(f.run(&[b"GET", b"k"]), "$3\r\none\r\n");
2531        assert_eq!(f.run(&[b"SELECT", b"1"]), "+OK\r\n");
2532        assert_eq!(f.run(&[b"GET", b"k"]), "$4\r\nzero\r\n");
2533        // A database swapped with itself is fine and changes nothing.
2534        assert_eq!(f.run(&[b"SWAPDB", b"1", b"1"]), "+OK\r\n");
2535        assert_eq!(f.run(&[b"GET", b"k"]), "$4\r\nzero\r\n");
2536    }
2537
2538    /// Every database on a server reads the server's clock and not one of its
2539    /// own. They used to be told the time one at a time and now they share the
2540    /// reading, so a server that built its databases from a second clock would
2541    /// answer a deadline worked out against a time nobody had set.
2542    #[test]
2543    fn a_wide_server_puts_its_databases_on_its_own_clock() {
2544        let mut f = Fixture::striped(8);
2545        f.server.set_clock_ms(1_700_000_000_000);
2546        assert_eq!(f.run(&[b"SET", b"k", b"v", b"EX", b"100"]), "+OK\r\n");
2547        assert_eq!(f.run(&[b"EXPIRETIME", b"k"]), ":1700000100\r\n");
2548        assert_eq!(f.run(&[b"TTL", b"k"]), ":100\r\n");
2549        f.server.set_clock_ms(1_700_000_050_000);
2550        assert_eq!(f.run(&[b"TTL", b"k"]), ":50\r\n");
2551    }
2552
2553    /// The swap is stripe by stripe, so a database cut into more than one
2554    /// stripe is the case that would catch it exchanging some of the keys and
2555    /// leaving the rest. Sixteen keys over four stripes is enough that every
2556    /// stripe has something in it whatever the hashes come out as.
2557    #[test]
2558    fn swapdb_swaps_every_stripe_of_a_wide_database() {
2559        let mut f = Fixture::striped(4);
2560        for i in 0..16u32 {
2561            let key = format!("k{i}");
2562            assert_eq!(f.run(&[b"SET", key.as_bytes(), b"zero"]), "+OK\r\n");
2563        }
2564        assert_eq!(f.run(&[b"SELECT", b"1"]), "+OK\r\n");
2565        assert_eq!(f.run(&[b"SET", b"only", b"one"]), "+OK\r\n");
2566        assert_eq!(f.run(&[b"SELECT", b"0"]), "+OK\r\n");
2567
2568        assert_eq!(f.run(&[b"SWAPDB", b"0", b"1"]), "+OK\r\n");
2569        assert_eq!(f.run(&[b"DBSIZE"]), ":1\r\n");
2570        assert_eq!(f.run(&[b"GET", b"only"]), "$3\r\none\r\n");
2571        assert_eq!(f.run(&[b"SELECT", b"1"]), "+OK\r\n");
2572        assert_eq!(f.run(&[b"DBSIZE"]), ":16\r\n");
2573        for i in 0..16u32 {
2574            let key = format!("k{i}");
2575            assert_eq!(f.run(&[b"GET", key.as_bytes()]), "$4\r\nzero\r\n");
2576        }
2577    }
2578
2579    #[test]
2580    fn swapdb_says_which_index_it_could_not_read() {
2581        let mut f = Fixture::new();
2582        assert_eq!(
2583            f.run(&[b"SWAPDB", b"x", b"1"]),
2584            "-ERR invalid first DB index\r\n"
2585        );
2586        assert_eq!(
2587            f.run(&[b"SWAPDB", b"0", b"y"]),
2588            "-ERR invalid second DB index\r\n"
2589        );
2590        // A number too big to be an index on a server that keeps one in an int
2591        // is the same complaint, and a plausible one that is not ours is the
2592        // range complaint instead. The split is Redis's.
2593        assert_eq!(
2594            f.run(&[b"SWAPDB", b"99999999999999", b"1"]),
2595            "-ERR invalid first DB index\r\n"
2596        );
2597        assert_eq!(
2598            f.run(&[b"SWAPDB", b"0", b"99"]),
2599            "-ERR DB index is out of range\r\n"
2600        );
2601        assert_eq!(
2602            f.run(&[b"SWAPDB", b"-1", b"0"]),
2603            "-ERR DB index is out of range\r\n"
2604        );
2605    }
2606
2607    #[test]
2608    fn wait_answers_zero_replicas_without_waiting() {
2609        let mut f = Fixture::new();
2610        assert_eq!(f.run(&[b"SET", b"a", b"v"]), "+OK\r\n");
2611        assert_eq!(f.run(&[b"WAIT", b"0", b"0"]), ":0\r\n");
2612        // A replica that is never going to arrive, and a timeout that would be
2613        // a real wait on a server that had one.
2614        assert_eq!(f.run(&[b"WAIT", b"3", b"1000"]), ":0\r\n");
2615        // Negative replicas is not an error, because zero is already more than
2616        // it asked for.
2617        assert_eq!(f.run(&[b"WAIT", b"-1", b"0"]), ":0\r\n");
2618        assert_eq!(
2619            f.run(&[b"WAIT", b"x", b"0"]),
2620            "-ERR value is not an integer or out of range\r\n"
2621        );
2622        assert_eq!(
2623            f.run(&[b"WAIT", b"0", b"-1"]),
2624            "-ERR timeout is negative\r\n"
2625        );
2626        assert_eq!(
2627            f.run(&[b"WAIT", b"0", b"1.5"]),
2628            "-ERR timeout is not an integer or out of range\r\n"
2629        );
2630    }
2631
2632    #[test]
2633    fn waitaof_answers_two_zeroes_and_refuses_a_local_wait() {
2634        let mut f = Fixture::new();
2635        assert_eq!(f.run(&[b"WAITAOF", b"0", b"0", b"0"]), "*2\r\n:0\r\n:0\r\n");
2636        assert_eq!(
2637            f.run(&[b"WAITAOF", b"1", b"0", b"0"]),
2638            "-ERR WAITAOF cannot be used when numlocal is set but appendonly is disabled.\r\n"
2639        );
2640        assert_eq!(
2641            f.run(&[b"WAITAOF", b"2", b"0", b"0"]),
2642            "-ERR value is out of range, value must between 0 and 1\r\n"
2643        );
2644        assert_eq!(
2645            f.run(&[b"WAITAOF", b"0", b"-1", b"0"]),
2646            "-ERR value is out of range, must be positive\r\n"
2647        );
2648        // The arguments are all read before the server looks at itself, so a
2649        // bad timeout beats the append only complaint even with numlocal set.
2650        assert_eq!(
2651            f.run(&[b"WAITAOF", b"1", b"0", b"-5"]),
2652            "-ERR timeout is negative\r\n"
2653        );
2654    }
2655
2656    /// The bytes inside a bulk reply, with the header and the trailing break
2657    /// taken off. Every `DUMP` test needs this and none of them care how the
2658    /// length was written.
2659    fn payload(reply: &[u8]) -> Vec<u8> {
2660        let head = reply.windows(2).position(|w| w == b"\r\n").unwrap();
2661        reply[head + 2..reply.len() - 2].to_vec()
2662    }
2663
2664    #[test]
2665    fn a_value_survives_a_dump_and_a_restore() {
2666        let mut f = Fixture::new();
2667        f.run(&[b"SET", b"s", b"hello"]);
2668        f.run(&[b"RPUSH", b"l", b"a", b"b", b"c"]);
2669        f.run(&[b"SADD", b"t", b"1", b"2", b"3"]);
2670        f.run(&[b"SADD", b"u", b"x", b"y"]);
2671        f.run(&[b"HSET", b"h", b"f", b"1", b"g", b"2"]);
2672        f.run(&[b"ZADD", b"z", b"1.5", b"a", b"2.5", b"b"]);
2673
2674        for key in [&b"s"[..], b"l", b"t", b"u", b"h", b"z"] {
2675            let mut copy = key.to_vec();
2676            copy.push(b'2');
2677            let bytes = payload(&f.raw(&[b"DUMP", key]));
2678            assert_eq!(f.run(&[b"RESTORE", &copy, b"0", &bytes]), "+OK\r\n");
2679            assert_eq!(f.run(&[b"TYPE", &copy]), f.run(&[b"TYPE", key]));
2680        }
2681
2682        assert_eq!(f.run(&[b"GET", b"s2"]), "$5\r\nhello\r\n");
2683        assert_eq!(
2684            f.run(&[b"LRANGE", b"l2", b"0", b"-1"]),
2685            "*3\r\n$1\r\na\r\n$1\r\nb\r\n$1\r\nc\r\n"
2686        );
2687        assert_eq!(sorted(&f.run(&[b"SMEMBERS", b"t2"])), ["1", "2", "3"]);
2688        assert_eq!(sorted(&f.run(&[b"SMEMBERS", b"u2"])), ["x", "y"]);
2689        assert_eq!(f.run(&[b"HGET", b"h2", b"g"]), "$1\r\n2\r\n");
2690        assert_eq!(f.run(&[b"ZSCORE", b"z2", b"b"]), "$3\r\n2.5\r\n");
2691        // The encoding survives too, since the payload names the plainest legal
2692        // type and the loader puts the value back on the rung it belongs on.
2693        assert_eq!(
2694            f.run(&[b"OBJECT", b"ENCODING", b"t2"]),
2695            f.run(&[b"OBJECT", b"ENCODING", b"t"])
2696        );
2697    }
2698
2699    #[test]
2700    fn a_dumped_hash_keeps_its_field_deadlines() {
2701        let mut f = Fixture::new();
2702        f.run(&[b"HSET", b"h", b"keep", b"1", b"go", b"2"]);
2703        assert_eq!(
2704            f.run(&[b"HEXPIRE", b"h", b"100", b"FIELDS", b"1", b"go"]),
2705            "*1\r\n:1\r\n"
2706        );
2707        let bytes = payload(&f.raw(&[b"DUMP", b"h"]));
2708        assert_eq!(f.run(&[b"RESTORE", b"h2", b"0", &bytes]), "+OK\r\n");
2709        assert_eq!(
2710            f.run(&[b"HTTL", b"h2", b"FIELDS", b"2", b"keep", b"go"]),
2711            "*2\r\n:-1\r\n:100\r\n"
2712        );
2713    }
2714
2715    #[test]
2716    fn dump_leaves_the_deadline_behind_and_restore_is_given_a_new_one() {
2717        let mut f = Fixture::new();
2718        f.run(&[b"SET", b"a", b"v", b"EX", b"100"]);
2719        let bytes = payload(&f.raw(&[b"DUMP", b"a"]));
2720        assert_eq!(f.run(&[b"RESTORE", b"b", b"0", &bytes]), "+OK\r\n");
2721        assert_eq!(f.run(&[b"TTL", b"b"]), ":-1\r\n");
2722        assert_eq!(f.run(&[b"RESTORE", b"c", b"5000", &bytes]), "+OK\r\n");
2723        assert_eq!(f.run(&[b"TTL", b"c"]), ":5\r\n");
2724        // An absolute deadline that has already gone is not an error. The key is
2725        // not created and the reply is the same OK a live one gets.
2726        assert_eq!(
2727            f.run(&[b"RESTORE", b"d", b"1", &bytes, b"ABSTTL"]),
2728            "+OK\r\n"
2729        );
2730        assert_eq!(f.run(&[b"EXISTS", b"d"]), ":0\r\n");
2731    }
2732
2733    #[test]
2734    fn dump_answers_nothing_for_a_key_that_is_not_there() {
2735        let mut f = Fixture::new();
2736        assert_eq!(f.run(&[b"DUMP", b"nope"]), "$-1\r\n");
2737        f.run(&[b"SET", b"gone", b"v", b"PX", b"10"]);
2738        f.advance(50);
2739        assert_eq!(f.run(&[b"DUMP", b"gone"]), "$-1\r\n");
2740    }
2741
2742    #[test]
2743    fn restore_refuses_a_key_that_is_there_unless_it_is_told_to_replace() {
2744        let mut f = Fixture::new();
2745        f.run(&[b"SET", b"a", b"first"]);
2746        f.run(&[b"SET", b"b", b"second"]);
2747        let bytes = payload(&f.raw(&[b"DUMP", b"b"]));
2748        assert_eq!(
2749            f.run(&[b"RESTORE", b"a", b"0", &bytes]),
2750            "-BUSYKEY Target key name already exists.\r\n"
2751        );
2752        assert_eq!(f.run(&[b"GET", b"a"]), "$5\r\nfirst\r\n");
2753        assert_eq!(
2754            f.run(&[b"RESTORE", b"a", b"0", &bytes, b"REPLACE"]),
2755            "+OK\r\n"
2756        );
2757        assert_eq!(f.run(&[b"GET", b"a"]), "$6\r\nsecond\r\n");
2758    }
2759
2760    /// The busy key comes before the payload, which is not the order the
2761    /// arguments read in. Whether a key is taken should not depend on whether
2762    /// the bytes behind it happened to be good.
2763    #[test]
2764    fn restore_asks_about_the_key_before_it_looks_at_the_bytes() {
2765        let mut f = Fixture::new();
2766        f.run(&[b"SET", b"a", b"v"]);
2767        assert_eq!(
2768            f.run(&[b"RESTORE", b"a", b"0", b"rubbish"]),
2769            "-BUSYKEY Target key name already exists.\r\n"
2770        );
2771        // And the options come before even that, so a bad FREQ beats the busy
2772        // key the same way a bad DB beats a missing source in COPY.
2773        assert_eq!(
2774            f.run(&[b"RESTORE", b"a", b"0", b"rubbish", b"FREQ", b"300"]),
2775            "-ERR Invalid FREQ value, must be >= 0 and <= 255\r\n"
2776        );
2777    }
2778
2779    #[test]
2780    fn restore_can_tell_a_bad_footer_from_bad_bytes() {
2781        let mut f = Fixture::new();
2782        f.run(&[b"SET", b"a", b"hello"]);
2783        let good = payload(&f.raw(&[b"DUMP", b"a"]));
2784
2785        let mut flipped = good.clone();
2786        flipped[2] ^= 0x40;
2787        assert_eq!(
2788            f.run(&[b"RESTORE", b"b", b"0", &flipped]),
2789            "-ERR DUMP payload version or checksum are wrong\r\n"
2790        );
2791        assert_eq!(
2792            f.run(&[b"RESTORE", b"b", b"0", b"short"]),
2793            "-ERR DUMP payload version or checksum are wrong\r\n"
2794        );
2795        // A footer that is right over a body that is not. The type byte says
2796        // string and there is nothing behind it, so the checksum agrees and the
2797        // value does not exist.
2798        let mut truncated = good[..1].to_vec();
2799        truncated.extend_from_slice(&good[good.len() - 10..good.len() - 8]);
2800        let crc = yo_common::crc::crc64(0, &truncated);
2801        truncated.extend_from_slice(&crc.to_le_bytes());
2802        assert_eq!(
2803            f.run(&[b"RESTORE", b"b", b"0", &truncated]),
2804            "-ERR Bad data format\r\n"
2805        );
2806        assert_eq!(f.run(&[b"EXISTS", b"b"]), ":0\r\n");
2807    }
2808
2809    #[test]
2810    fn restore_checks_the_three_numbers_a_client_can_get_wrong() {
2811        let mut f = Fixture::new();
2812        f.run(&[b"SET", b"a", b"v"]);
2813        let bytes = payload(&f.raw(&[b"DUMP", b"a"]));
2814        assert_eq!(
2815            f.run(&[b"RESTORE", b"b", b"-1", &bytes]),
2816            "-ERR Invalid TTL value, must be >= 0\r\n"
2817        );
2818        assert_eq!(
2819            f.run(&[b"RESTORE", b"b", b"0", &bytes, b"IDLETIME", b"-1"]),
2820            "-ERR Invalid IDLETIME value, must be >= 0\r\n"
2821        );
2822        assert_eq!(
2823            f.run(&[b"RESTORE", b"b", b"0", &bytes, b"FREQ", b"256"]),
2824            "-ERR Invalid FREQ value, must be >= 0 and <= 255\r\n"
2825        );
2826        // Both are accepted and both are then dropped, which is D-26.
2827        assert_eq!(
2828            f.run(&[b"RESTORE", b"b", b"0", &bytes, b"IDLETIME", b"90"]),
2829            "+OK\r\n"
2830        );
2831        assert_eq!(
2832            f.run(&[b"RESTORE", b"c", b"0", &bytes, b"FREQ", b"200", b"REPLACE"]),
2833            "+OK\r\n"
2834        );
2835    }
2836
2837    /// Neither word is refused for being the wrong one. Each is only accepted
2838    /// while the other is unset, so the second of the two falls through to the
2839    /// plain syntax error rather than getting a message of its own.
2840    #[test]
2841    fn restore_takes_idletime_or_freq_and_not_both() {
2842        let mut f = Fixture::new();
2843        f.run(&[b"SET", b"a", b"v"]);
2844        let bytes = payload(&f.raw(&[b"DUMP", b"a"]));
2845        assert_eq!(
2846            f.run(&[
2847                b"RESTORE",
2848                b"b",
2849                b"0",
2850                &bytes,
2851                b"IDLETIME",
2852                b"1",
2853                b"FREQ",
2854                b"2"
2855            ]),
2856            "-ERR syntax error\r\n"
2857        );
2858        assert_eq!(
2859            f.run(&[
2860                b"RESTORE",
2861                b"b",
2862                b"0",
2863                &bytes,
2864                b"FREQ",
2865                b"2",
2866                b"IDLETIME",
2867                b"1"
2868            ]),
2869            "-ERR syntax error\r\n"
2870        );
2871        assert_eq!(
2872            f.run(&[b"RESTORE", b"b", b"0", &bytes, b"FREQ"]),
2873            "-ERR syntax error\r\n"
2874        );
2875        assert_eq!(
2876            f.run(&[b"RESTORE", b"b", b"0", &bytes, b"NOSUCH"]),
2877            "-ERR syntax error\r\n"
2878        );
2879    }
2880
2881    #[test]
2882    fn copy_checks_its_options_before_it_looks_for_anything() {
2883        let mut f = Fixture::new();
2884        // No key exists at all, and every one of these is still the option
2885        // complaint rather than a zero, which is the order a real server uses.
2886        assert_eq!(
2887            f.run(&[b"COPY", b"a", b"b", b"DB", b"99"]),
2888            "-ERR DB index is out of range\r\n"
2889        );
2890        assert_eq!(
2891            f.run(&[b"COPY", b"a", b"b", b"DB", b"-1"]),
2892            "-ERR DB index is out of range\r\n"
2893        );
2894        assert_eq!(
2895            f.run(&[b"COPY", b"a", b"b", b"DB", b"x"]),
2896            "-ERR value is not an integer or out of range\r\n"
2897        );
2898        assert_eq!(
2899            f.run(&[b"COPY", b"a", b"b", b"nonsense"]),
2900            "-ERR syntax error\r\n"
2901        );
2902        assert_eq!(
2903            f.run(&[b"COPY", b"a", b"a"]),
2904            "-ERR source and destination objects are the same\r\n"
2905        );
2906        // Repeated, reordered and lowercased, and the last DB wins.
2907        assert_eq!(
2908            f.run(&[b"COPY", b"a", b"b", b"dB", b"1", b"rEpLaCe", b"db", b"2"]),
2909            ":0\r\n"
2910        );
2911    }
2912
2913    #[test]
2914    fn time_is_two_bulk_strings_and_moves() {
2915        let mut f = Fixture::new();
2916        let first = f.run(&[b"TIME"]);
2917        assert!(first.starts_with("*2\r\n$"), "got {first}");
2918        let parts: Vec<&str> = first.split("\r\n").collect();
2919        let secs: i64 = parts[2].parse().expect("seconds as decimal text");
2920        let micros: i64 = parts[4].parse().expect("microseconds as decimal text");
2921        assert!(secs > 1_700_000_000, "a real wall clock, got {secs}");
2922        assert!((0..1_000_000).contains(&micros), "got {micros}");
2923        // The coarse clock the keyspace uses is a cached millisecond that a
2924        // background tick refreshes, so a TIME built on it would answer the
2925        // same microsecond twice in a row here.
2926        assert_ne!(first, f.run(&[b"TIME"]));
2927    }
2928
2929    #[test]
2930    fn a_keyspace_scan_walks_every_key_once() {
2931        // The count below is thirty two, so ninety six keys is three pages of
2932        // cursor and says the same thing as five hundred at a fifth of the
2933        // interpreted work.
2934        let n = if cfg!(miri) { 96 } else { 500 };
2935        let mut f = Fixture::new();
2936        for i in 0..n {
2937            f.run(&[b"SET", format!("k{i}").as_bytes(), b"v"]);
2938        }
2939
2940        let mut seen: Vec<String> = Vec::new();
2941        let mut cursor = "0".to_owned();
2942        let mut calls = 0;
2943        loop {
2944            let (next, keys) = scan_reply(&f.run(&[b"SCAN", cursor.as_bytes(), b"COUNT", b"32"]));
2945            seen.extend(keys);
2946            cursor = next;
2947            calls += 1;
2948            assert!(calls < 10_000, "the cursor is not advancing");
2949            if cursor == "0" {
2950                break;
2951            }
2952        }
2953
2954        seen.sort();
2955        seen.dedup();
2956        assert_eq!(seen.len(), n, "every key once and only once");
2957        // And more than one call to get them, or the COUNT is being ignored and
2958        // the loop above proved nothing about resuming.
2959        assert!(calls > 1, "{n} keys came back in one batch");
2960    }
2961
2962    #[test]
2963    fn a_scan_narrows_by_pattern_and_by_type() {
2964        let mut f = Fixture::new();
2965        f.run(&[b"SET", b"str", b"v"]);
2966        f.run(&[b"SADD", b"members", b"a"]);
2967        f.run(&[b"HSET", b"fields", b"f", b"v"]);
2968
2969        let all = |f: &mut Fixture, args: &[&[u8]]| {
2970            let mut out: Vec<String> = Vec::new();
2971            let mut cursor = "0".to_owned();
2972            loop {
2973                let mut line: Vec<&[u8]> = vec![b"SCAN", cursor.as_bytes()];
2974                line.extend_from_slice(args);
2975                let (next, keys) = scan_reply(&f.run(&line));
2976                out.extend(keys);
2977                cursor = next;
2978                if cursor == "0" {
2979                    break;
2980                }
2981            }
2982            out.sort();
2983            out
2984        };
2985
2986        assert_eq!(all(&mut f, &[]), ["fields", "members", "str"]);
2987        assert_eq!(all(&mut f, &[b"MATCH", b"*e*"]), ["fields", "members"]);
2988        assert_eq!(all(&mut f, &[b"TYPE", b"set"]), ["members"]);
2989        // Case insensitive, the same as Redis's own comparison.
2990        assert_eq!(all(&mut f, &[b"TYPE", b"HASH"]), ["fields"]);
2991        // A type nothing can hold is not an error, it just matches nothing.
2992        assert!(all(&mut f, &[b"TYPE", b"list"]).is_empty());
2993        assert!(all(&mut f, &[b"TYPE", b"banana"]).is_empty());
2994        // Both filters at once, and they are an and rather than an or.
2995        assert!(all(&mut f, &[b"MATCH", b"str*", b"TYPE", b"set"]).is_empty());
2996    }
2997
2998    #[test]
2999    fn a_scan_says_what_is_wrong_with_it() {
3000        let mut f = Fixture::new();
3001        assert_eq!(f.run(&[b"SCAN", b"nope"]), "-ERR invalid cursor\r\n");
3002        assert_eq!(f.run(&[b"SCAN", b"-1"]), "-ERR invalid cursor\r\n");
3003        assert_eq!(f.run(&[b"SCAN", b"0", b"MATCH"]), "-ERR syntax error\r\n");
3004        assert_eq!(
3005            f.run(&[b"SCAN", b"0", b"COUNT", b"0"]),
3006            "-ERR syntax error\r\n"
3007        );
3008        assert_eq!(
3009            f.run(&[b"SCAN", b"0", b"COUNT", b"x"]),
3010            "-ERR value is not an integer or out of range\r\n"
3011        );
3012        assert_eq!(
3013            f.run(&[b"SCAN", b"0", b"WAT", b"1"]),
3014            "-ERR syntax error\r\n"
3015        );
3016        // A cursor the client made up is a cursor. It resumes somewhere
3017        // arbitrary and answers whatever is there, which is what Redis does and
3018        // is the only behaviour that does not need the server to remember every
3019        // cursor it has handed out.
3020        assert!(f.run(&[b"SCAN", b"18446744073709551615"]).starts_with("*2"));
3021    }
3022
3023    #[test]
3024    fn keys_and_randomkey_look_at_the_whole_database() {
3025        let mut f = Fixture::new();
3026        assert_eq!(f.run(&[b"KEYS", b"*"]), "*0\r\n");
3027        assert_eq!(f.run(&[b"RANDOMKEY"]), "$-1\r\n");
3028
3029        for name in ["one", "two", "three"] {
3030            f.run(&[b"SET", name.as_bytes(), b"v"]);
3031        }
3032        assert_eq!(sorted(&f.run(&[b"KEYS", b"*"])), ["one", "three", "two"]);
3033        assert_eq!(sorted(&f.run(&[b"KEYS", b"t*"])), ["three", "two"]);
3034        assert_eq!(f.run(&[b"KEYS", b"nothing"]), "*0\r\n");
3035
3036        for _ in 0..50 {
3037            let got = f.run(&[b"RANDOMKEY"]);
3038            assert!(
3039                ["$3\r\none\r\n", "$3\r\ntwo\r\n", "$5\r\nthree\r\n"].contains(&got.as_str()),
3040                "got {got}"
3041            );
3042        }
3043    }
3044
3045    #[test]
3046    fn a_walk_does_not_answer_keys_that_have_expired() {
3047        let mut f = Fixture::new();
3048        f.run(&[b"SET", b"alive", b"v"]);
3049        f.run(&[b"SET", b"dead", b"v", b"PX", b"1"]);
3050        f.server.advance_clock_ms(2);
3051        assert_eq!(
3052            f.run(&[b"DBSIZE"]),
3053            ":2\r\n",
3054            "nothing has collected it yet"
3055        );
3056
3057        assert_eq!(f.run(&[b"KEYS", b"*"]), "*1\r\n$5\r\nalive\r\n");
3058        let (_, keys) = scan_reply(&f.run(&[b"SCAN", b"0", b"COUNT", b"1000"]));
3059        assert_eq!(keys, ["alive"]);
3060        for _ in 0..20 {
3061            assert_eq!(f.run(&[b"RANDOMKEY"]), "$5\r\nalive\r\n");
3062        }
3063        // The walk collected it on the way past, which is what makes DBSIZE
3064        // here answer what Redis answers once its own cycle has been round.
3065        assert_eq!(f.run(&[b"DBSIZE"]), ":1\r\n");
3066    }
3067
3068    #[test]
3069    fn a_key_deadline_goes_on_and_comes_back_in_all_four_units() {
3070        let mut f = Fixture::new();
3071        f.run(&[b"SET", b"k", b"v"]);
3072        assert_eq!(f.run(&[b"TTL", b"k"]), ":-1\r\n", "there and no deadline");
3073        assert_eq!(f.run(&[b"TTL", b"nosuch"]), ":-2\r\n", "not there at all");
3074
3075        assert_eq!(f.run(&[b"EXPIRE", b"k", b"100"]), ":1\r\n");
3076        assert_eq!(f.run(&[b"TTL", b"k"]), ":100\r\n");
3077        let ms = int(&f.run(&[b"PTTL", b"k"]));
3078        assert!((99_000..=100_000).contains(&ms), "got {ms}");
3079
3080        // The absolute pair, derived from the same one number the store kept.
3081        let at = int(&f.run(&[b"EXPIRETIME", b"k"]));
3082        let at_ms = int(&f.run(&[b"PEXPIRETIME", b"k"]));
3083        assert_eq!(at, (at_ms + 500) / 1000);
3084        assert!(at_ms > 1_700_000_000_000, "an absolute moment, got {at_ms}");
3085
3086        assert_eq!(f.run(&[b"PERSIST", b"k"]), ":1\r\n");
3087        assert_eq!(f.run(&[b"TTL", b"k"]), ":-1\r\n");
3088        assert_eq!(
3089            f.run(&[b"PERSIST", b"k"]),
3090            ":0\r\n",
3091            "nothing to take off the second time"
3092        );
3093        assert_eq!(f.run(&[b"PERSIST", b"nosuch"]), ":0\r\n");
3094        assert_eq!(
3095            f.run(&[b"GET", b"k"]),
3096            "$1\r\nv\r\n",
3097            "and the value went through all of that untouched"
3098        );
3099    }
3100
3101    #[test]
3102    fn every_type_can_be_given_a_deadline_and_it_is_the_same_deadline() {
3103        let mut f = Fixture::new();
3104        f.run(&[b"SET", b"str", b"v"]);
3105        f.run(&[b"SADD", b"set", b"a", b"b"]);
3106        f.run(&[b"HSET", b"hash", b"f", b"v"]);
3107
3108        for key in [b"str".as_slice(), b"set", b"hash"] {
3109            assert_eq!(f.run(&[b"EXPIRE", key, b"100"]), ":1\r\n");
3110            assert_eq!(f.run(&[b"TTL", key]), ":100\r\n");
3111        }
3112        // The body is not touched by any of that, which is the whole reason the
3113        // deadline lives in the record and the body lives somewhere else.
3114        assert_eq!(f.run(&[b"SCARD", b"set"]), ":2\r\n");
3115        assert_eq!(f.run(&[b"HGET", b"hash", b"f"]), "$1\r\nv\r\n");
3116        assert_eq!(f.run(&[b"GET", b"str"]), "$1\r\nv\r\n");
3117    }
3118
3119    #[test]
3120    fn a_deadline_that_has_already_gone_deletes_the_key_now() {
3121        let mut f = Fixture::new();
3122        for key in [b"a".as_slice(), b"b", b"c", b"d"] {
3123            f.run(&[b"SET", key, b"v"]);
3124        }
3125        // Four ways of naming a moment that has passed, and all four are a
3126        // delete answering 1 rather than an error. Zero is a moment, minus one
3127        // is a moment, and the hash field commands refuse the negative one.
3128        assert_eq!(f.run(&[b"EXPIRE", b"a", b"0"]), ":1\r\n");
3129        assert_eq!(f.run(&[b"EXPIRE", b"b", b"-1"]), ":1\r\n");
3130        assert_eq!(f.run(&[b"EXPIREAT", b"c", b"1"]), ":1\r\n");
3131        assert_eq!(f.run(&[b"PEXPIREAT", b"d", b"1"]), ":1\r\n");
3132        assert_eq!(f.run(&[b"DBSIZE"]), ":0\r\n");
3133        assert_eq!(
3134            f.run(&[b"EXPIRE", b"a", b"100"]),
3135            ":0\r\n",
3136            "and the key really went, so there is nothing to put a deadline on"
3137        );
3138    }
3139
3140    #[test]
3141    fn the_four_conditions_decide_whether_the_deadline_moves() {
3142        let mut f = Fixture::new();
3143        f.run(&[b"SET", b"k", b"v"]);
3144
3145        assert_eq!(f.run(&[b"EXPIRE", b"k", b"100", b"XX"]), ":0\r\n");
3146        assert_eq!(f.run(&[b"TTL", b"k"]), ":-1\r\n", "and XX left it alone");
3147        assert_eq!(f.run(&[b"EXPIRE", b"k", b"100", b"GT"]), ":0\r\n");
3148        assert_eq!(
3149            f.run(&[b"EXPIRE", b"k", b"100", b"LT"]),
3150            ":1\r\n",
3151            "no deadline reads as infinitely far away, so LT passes where GT fails"
3152        );
3153
3154        assert_eq!(f.run(&[b"EXPIRE", b"k", b"50", b"NX"]), ":0\r\n");
3155        assert_eq!(f.run(&[b"EXPIRE", b"k", b"50", b"GT"]), ":0\r\n");
3156        assert_eq!(f.run(&[b"TTL", b"k"]), ":100\r\n");
3157        assert_eq!(f.run(&[b"EXPIRE", b"k", b"50", b"LT"]), ":1\r\n");
3158        assert_eq!(f.run(&[b"EXPIRE", b"k", b"200", b"GT"]), ":1\r\n");
3159        assert_eq!(f.run(&[b"TTL", b"k"]), ":200\r\n");
3160
3161        // The condition is answered before the past check, so this is a 0 and
3162        // the key survives. The other order would delete it.
3163        assert_eq!(f.run(&[b"EXPIRE", b"k", b"0", b"NX"]), ":0\r\n");
3164        assert_eq!(f.run(&[b"EXISTS", b"k"]), ":1\r\n");
3165        assert_eq!(f.run(&[b"EXPIRE", b"k", b"0", b"XX"]), ":1\r\n");
3166        assert_eq!(f.run(&[b"EXISTS", b"k"]), ":0\r\n", "and XX let it through");
3167    }
3168
3169    #[test]
3170    fn the_conditions_are_a_set_and_not_a_keyword() {
3171        let mut f = Fixture::new();
3172        f.run(&[b"SET", b"k", b"v"]);
3173
3174        assert_eq!(f.run(&[b"EXPIRE", b"k", b"100", b"nx"]), ":1\r\n");
3175        assert_eq!(
3176            f.run(&[b"EXPIRE", b"k", b"100", b"nx", b"nx"]),
3177            ":0\r\n",
3178            "the same keyword twice means it once, and NX now has a deadline to fail on"
3179        );
3180
3181        // XX with LT is the one pair that is not either of them on its own: LT
3182        // alone would accept a key with no deadline and this does not.
3183        assert_eq!(f.run(&[b"EXPIRE", b"k", b"200", b"xx", b"gt"]), ":1\r\n");
3184        assert_eq!(f.run(&[b"TTL", b"k"]), ":200\r\n");
3185        assert_eq!(f.run(&[b"EXPIRE", b"k", b"100", b"gt", b"xx"]), ":0\r\n");
3186        assert_eq!(f.run(&[b"EXPIRE", b"k", b"100", b"XX", b"LT"]), ":1\r\n");
3187        assert_eq!(f.run(&[b"TTL", b"k"]), ":100\r\n");
3188        f.run(&[b"PERSIST", b"k"]);
3189        assert_eq!(
3190            f.run(&[b"EXPIRE", b"k", b"100", b"XX", b"LT"]),
3191            ":0\r\n",
3192            "where LT on its own would have taken it"
3193        );
3194        assert_eq!(f.run(&[b"EXPIRE", b"k", b"100", b"LT"]), ":1\r\n");
3195    }
3196
3197    #[test]
3198    fn a_key_is_gone_once_its_moment_passes() {
3199        let mut f = Fixture::new();
3200        f.run(&[b"SET", b"k", b"v"]);
3201        f.run(&[b"EXPIRE", b"k", b"100"]);
3202
3203        let at = int(&f.run(&[b"PEXPIRETIME", b"k"]));
3204        f.server.set_clock_ms(at as u64 + 1);
3205        assert_eq!(f.run(&[b"GET", b"k"]), "$-1\r\n");
3206        assert_eq!(f.run(&[b"TTL", b"k"]), ":-2\r\n");
3207        assert_eq!(f.run(&[b"EXISTS", b"k"]), ":0\r\n");
3208        assert_eq!(f.run(&[b"DBSIZE"]), ":0\r\n");
3209    }
3210
3211    #[test]
3212    fn the_expiry_commands_refuse_what_a_real_server_refuses() {
3213        let mut f = Fixture::new();
3214        f.run(&[b"SET", b"k", b"v"]);
3215        for (bad, want) in [
3216            (
3217                &[b"EXPIRE".as_slice(), b"k", b"soon"][..],
3218                "-ERR value is not an integer or out of range\r\n",
3219            ),
3220            (
3221                &[b"EXPIRE", b"k", b"100", b"MAYBE"],
3222                "-ERR Unsupported option MAYBE\r\n",
3223            ),
3224            (
3225                &[b"EXPIRE", b"k", b"100", b"NX", b"XX"],
3226                "-ERR NX and XX, GT or LT options at the same time are not compatible\r\n",
3227            ),
3228            (
3229                &[b"EXPIRE", b"k", b"100", b"NX", b"GT"],
3230                "-ERR NX and XX, GT or LT options at the same time are not compatible\r\n",
3231            ),
3232            (
3233                &[b"EXPIRE", b"k", b"100", b"GT", b"LT", b"GT"],
3234                "-ERR GT and LT options at the same time are not compatible\r\n",
3235            ),
3236            // Seconds that overflow when multiplied into milliseconds. Every
3237            // message names the command it came from.
3238            (
3239                &[b"EXPIRE", b"k", b"9223372036854775807"],
3240                "-ERR invalid expire time in 'expire' command\r\n",
3241            ),
3242            (
3243                &[b"EXPIREAT", b"k", b"9223372036854775807"],
3244                "-ERR invalid expire time in 'expireat' command\r\n",
3245            ),
3246            (
3247                &[b"PEXPIRE", b"k", b"9223372036854775807"],
3248                "-ERR invalid expire time in 'pexpire' command\r\n",
3249            ),
3250        ] {
3251            assert_eq!(f.run(bad), want, "for {bad:?}");
3252        }
3253        assert_eq!(
3254            f.run(&[b"TTL", b"k"]),
3255            ":-1\r\n",
3256            "and none of those put a deadline on anything"
3257        );
3258
3259        // The one of the four that has no arithmetic to overflow. Redis takes
3260        // it and holds the number as given, and a record here holds forty six
3261        // bits, so it lands in the year 4199 instead. D-17.
3262        assert_eq!(
3263            f.run(&[b"PEXPIREAT", b"k", b"9223372036854775807"]),
3264            ":1\r\n"
3265        );
3266        assert_eq!(f.run(&[b"PEXPIRETIME", b"k"]), ":70368744177663\r\n");
3267    }
3268
3269    #[test]
3270    fn flushing_empties_this_database_or_every_one_of_them() {
3271        let mut f = Fixture::new();
3272        f.run(&[b"SELECT", b"0"]);
3273        f.run(&[b"MSET", b"a", b"1", b"b", b"2"]);
3274        f.run(&[b"SELECT", b"1"]);
3275        f.run(&[b"SET", b"c", b"3"]);
3276        assert_eq!(f.run(&[b"DBSIZE"]), ":1\r\n");
3277        // ASYNC and SYNC are both taken and neither changes anything, since the
3278        // keyspace is empty before the OK goes out either way.
3279        assert_eq!(f.run(&[b"FLUSHDB", b"async"]), "+OK\r\n");
3280        assert_eq!(f.run(&[b"DBSIZE"]), ":0\r\n");
3281        // Only database one was emptied.
3282        f.run(&[b"SELECT", b"0"]);
3283        assert_eq!(f.run(&[b"DBSIZE"]), ":2\r\n");
3284        assert_eq!(f.run(&[b"FLUSHALL", b"SYNC"]), "+OK\r\n");
3285        assert_eq!(f.run(&[b"DBSIZE"]), ":0\r\n");
3286        f.run(&[b"SELECT", b"1"]);
3287        assert_eq!(f.run(&[b"DBSIZE"]), ":0\r\n");
3288        // Anything else after the name is a syntax error, and so is a third
3289        // argument even when the second one is a word we take.
3290        assert_eq!(f.run(&[b"FLUSHALL", b"nope"]), "-ERR syntax error\r\n");
3291        assert_eq!(
3292            f.run(&[b"FLUSHDB", b"sync", b"sync"]),
3293            "-ERR syntax error\r\n"
3294        );
3295    }
3296
3297    #[test]
3298    fn the_script_cache_and_the_library_set_answer_for_being_empty() {
3299        let mut f = Fixture::new();
3300        assert_eq!(f.run(&[b"SCRIPT", b"FLUSH"]), "+OK\r\n");
3301        assert_eq!(f.run(&[b"SCRIPT", b"FLUSH", b"async"]), "+OK\r\n");
3302        assert_eq!(f.run(&[b"FUNCTION", b"FLUSH", b"SYNC"]), "+OK\r\n");
3303        // Nothing is cached, so nothing is there, one answer per hash asked
3304        // about.
3305        assert_eq!(
3306            f.run(&[b"SCRIPT", b"EXISTS", b"aaaa", b"bbbb"]),
3307            "*2\r\n:0\r\n:0\r\n"
3308        );
3309        assert_eq!(f.run(&[b"FUNCTION", b"LIST"]), "*0\r\n");
3310        assert_eq!(
3311            f.run(&[b"FUNCTION", b"LIST", b"LIBRARYNAME", b"x", b"WITHCODE"]),
3312            "*0\r\n"
3313        );
3314        assert_eq!(
3315            f.run(&[b"FUNCTION", b"DELETE", b"nosuch"]),
3316            "-ERR Library not found\r\n"
3317        );
3318
3319        // Redis's two messages here are its own, one per container, and one of
3320        // them reads like a typo.
3321        assert_eq!(
3322            f.run(&[b"SCRIPT", b"FLUSH", b"nope"]),
3323            "-ERR SCRIPT FLUSH only support SYNC|ASYNC option\r\n"
3324        );
3325        assert_eq!(
3326            f.run(&[b"FUNCTION", b"FLUSH", b"nope"]),
3327            "-ERR FUNCTION FLUSH only supports SYNC|ASYNC option\r\n"
3328        );
3329        // A second argument after the mode is the generic one instead, because
3330        // the count is checked before the word is looked at.
3331        assert_eq!(
3332            f.run(&[b"FUNCTION", b"FLUSH", b"sync", b"sync"]),
3333            "-ERR unknown subcommand or wrong number of arguments for 'flush'. Try FUNCTION HELP.\r\n"
3334        );
3335        assert_eq!(
3336            f.run(&[b"FUNCTION", b"LIST", b"bogus"]),
3337            "-ERR Unknown argument bogus\r\n"
3338        );
3339        assert_eq!(
3340            f.run(&[b"SCRIPT", b"EXISTS"]),
3341            "-ERR wrong number of arguments for 'script|exists' command\r\n"
3342        );
3343
3344        // The ones that need an interpreter are not here, and say so rather
3345        // than answering OK to a load that loaded nothing.
3346        assert_eq!(
3347            f.run(&[b"SCRIPT", b"LOAD", b"return 1"]),
3348            "-ERR unknown subcommand 'LOAD'. Try SCRIPT HELP.\r\n"
3349        );
3350        assert_eq!(
3351            f.run(&[b"FUNCTION", b"STATS"]),
3352            "-ERR unknown subcommand 'STATS'. Try FUNCTION HELP.\r\n"
3353        );
3354    }
3355
3356    #[test]
3357    fn a_counter_is_an_integer_and_not_a_string_of_digits() {
3358        let mut f = Fixture::new();
3359        assert_eq!(f.run(&[b"INCR", b"c"]), ":1\r\n");
3360        assert_eq!(f.run(&[b"INCRBY", b"c", b"41"]), ":42\r\n");
3361        assert_eq!(f.run(&[b"DECRBY", b"c", b"2"]), ":40\r\n");
3362        // Read back as a string it is still an integer, written out as digits
3363        // only because somebody asked for them.
3364        assert_eq!(f.run(&[b"GET", b"c"]), "$2\r\n40\r\n");
3365        assert_eq!(f.run(&[b"INCRBYFLOAT", b"c", b"0.5"]), "$4\r\n40.5\r\n");
3366        // A counter that is not a number is the error the store raises and this
3367        // layer only spells, which is the whole point of the split.
3368        f.run(&[b"SET", b"k", b"hello"]);
3369        assert_eq!(
3370            f.run(&[b"INCR", b"k"]),
3371            "-ERR value is not an integer or out of range\r\n"
3372        );
3373        assert_eq!(
3374            f.run(&[b"INCRBYFLOAT", b"c", b"inf"]),
3375            "-ERR increment would produce NaN or Infinity\r\n"
3376        );
3377    }
3378
3379    /// Every one of these was read off a running 8.8. They are the answers a
3380    /// client library's own test suite checks, and the shapes are not
3381    /// guessable: `DIGEST` is hexadecimal in a bulk string, `MSETEX` is an
3382    /// integer, `INCREX` is a pair.
3383    #[test]
3384    fn the_newer_commands_reply_in_the_shapes_a_real_server_sends() {
3385        let mut f = Fixture::new();
3386        assert_eq!(f.run(&[b"SET", b"k", b"hello"]), "+OK\r\n");
3387        // The same digest a real 8.8 answers for the same five bytes, which is
3388        // what makes `IFDEQ` usable against a mixed deployment.
3389        assert_eq!(f.run(&[b"DIGEST", b"k"]), "$16\r\n9555e8555c62dcfd\r\n");
3390        assert_eq!(f.run(&[b"DIGEST", b"nosuch"]), "$-1\r\n");
3391        assert_eq!(f.run(&[b"MSETEX", b"1", b"a", b"1"]), ":1\r\n");
3392        assert_eq!(f.run(&[b"MSETEX", b"1", b"a", b"2", b"NX"]), ":0\r\n");
3393        assert_eq!(f.run(&[b"GET", b"a"]), "$1\r\n1\r\n");
3394        assert_eq!(f.run(&[b"INCREX", b"n"]), "*2\r\n:1\r\n:1\r\n");
3395        assert_eq!(
3396            f.run(&[b"INCREX", b"n", b"BYINT", b"5", b"UBOUND", b"3"]),
3397            "*2\r\n:1\r\n:0\r\n",
3398            "a refused increment reports the value it left alone and applied nothing"
3399        );
3400        assert_eq!(
3401            f.run(&[
3402                b"INCREX",
3403                b"n",
3404                b"BYINT",
3405                b"5",
3406                b"UBOUND",
3407                b"3",
3408                b"SATURATE"
3409            ]),
3410            "*2\r\n:3\r\n:2\r\n"
3411        );
3412        assert_eq!(f.run(&[b"DELEX", b"a", b"IFEQ", b"2"]), ":0\r\n");
3413        assert_eq!(f.run(&[b"DELEX", b"a", b"IFEQ", b"1"]), ":1\r\n");
3414    }
3415
3416    #[test]
3417    fn the_same_answers_come_out_in_resp3_spelling() {
3418        let mut f = Fixture::new();
3419        assert!(f.run(&[b"HELLO", b"3"]).starts_with("%7\r\n"));
3420        assert_eq!(f.run(&[b"GET", b"nosuch"]), "_\r\n");
3421        // A float counter is a double on RESP3 and the digits in a bulk string
3422        // on RESP2, and `INCRBYFLOAT` is a bulk string on both.
3423        assert_eq!(
3424            f.run(&[b"INCREX", b"c", b"BYFLOAT", b"1.5"]),
3425            "*2\r\n,1.5\r\n,1.5\r\n"
3426        );
3427        assert_eq!(f.run(&[b"INCRBYFLOAT", b"f", b"2.5"]), "$3\r\n2.5\r\n");
3428        // `RESET` puts the protocol back, which is the part that is easy to
3429        // miss and leaves a pooled connection speaking the wrong one.
3430        assert_eq!(f.run(&[b"RESET"]), "+RESET\r\n");
3431        assert_eq!(f.run(&[b"GET", b"nosuch"]), "$-1\r\n");
3432    }
3433
3434    #[test]
3435    fn a_command_nobody_has_heard_of_is_an_error_and_not_a_closed_socket() {
3436        let mut f = Fixture::new();
3437        let (flow, reply) = f.flow(&[b"NOPE", b"a", b"b"]);
3438        assert_eq!(flow, Flow::Continue);
3439        assert_eq!(
3440            reply,
3441            "-ERR unknown command 'NOPE', with args beginning with: 'a' 'b' \r\n"
3442        );
3443        // A name with a line ending in it cannot write its own frame into the
3444        // stream, which is the reason the error writer maps them to spaces.
3445        let reply = f.run(&[b"NO\r\n+PONG\r\nPE"]);
3446        assert_eq!(reply.matches("\r\n").count(), 1);
3447    }
3448
3449    #[test]
3450    fn arity_is_checked_before_the_command_is() {
3451        let mut f = Fixture::new();
3452        assert_eq!(
3453            f.run(&[b"GET"]),
3454            "-ERR wrong number of arguments for 'get' command\r\n"
3455        );
3456        assert_eq!(
3457            f.run(&[b"MSET", b"k"]),
3458            "-ERR wrong number of arguments for 'mset' command\r\n"
3459        );
3460        // The table says `PING` takes one or more and a real server then
3461        // refuses three, which is the sort of thing that only shows up against
3462        // the real thing.
3463        assert_eq!(
3464            f.run(&[b"PING", b"a", b"b"]),
3465            "-ERR wrong number of arguments for 'ping' command\r\n"
3466        );
3467        assert_eq!(f.run(&[b"PING"]), "+PONG\r\n");
3468        assert_eq!(f.run(&[b"PING", b"hi"]), "$2\r\nhi\r\n");
3469        // `DELEX` takes two or four and nothing between.
3470        assert_eq!(
3471            f.run(&[b"DELEX", b"k", b"IFEQ"]),
3472            "-ERR wrong number of arguments for 'delex' command\r\n"
3473        );
3474    }
3475
3476    /// The option rules, all of them measured against 8.8 rather than read off
3477    /// the documentation. The surprising one is that `SET` accepts the same
3478    /// keyword twice and `INCREX` does not.
3479    #[test]
3480    fn the_option_combinations_are_the_ones_a_real_server_accepts() {
3481        let mut f = Fixture::new();
3482        let syntax = "-ERR syntax error\r\n";
3483        assert_eq!(f.run(&[b"SET", b"k", b"v", b"NX", b"XX"]), syntax);
3484        assert_eq!(f.run(&[b"SET", b"k", b"v", b"NX", b"IFEQ", b"a"]), syntax);
3485        assert_eq!(
3486            f.run(&[b"SET", b"k", b"v", b"KEEPTTL", b"EX", b"5"]),
3487            syntax
3488        );
3489        assert_eq!(
3490            f.run(&[b"SET", b"k", b"v", b"EX", b"5", b"PX", b"5"]),
3491            syntax
3492        );
3493        assert_eq!(f.run(&[b"SET", b"k", b"v", b"PERSIST"]), syntax);
3494        // Twice is fine, and the last one wins.
3495        assert_eq!(
3496            f.run(&[b"SET", b"k", b"v", b"EX", b"5", b"EX", b"100"]),
3497            "+OK\r\n"
3498        );
3499        assert_eq!(f.run(&[b"SET", b"k", b"v", b"XX", b"XX"]), "+OK\r\n");
3500        assert_eq!(f.run(&[b"SET", b"k", b"v", b"GET", b"GET"]), "$1\r\nv\r\n");
3501        // `INCREX` refuses what `SET` allows.
3502        assert_eq!(
3503            f.run(&[b"INCREX", b"n", b"BYINT", b"1", b"BYINT", b"2"]),
3504            syntax
3505        );
3506        assert_eq!(
3507            f.run(&[b"INCREX", b"n", b"ENX"]),
3508            "-ERR ENX flag requires an expiration\r\n"
3509        );
3510        assert_eq!(
3511            f.run(&[b"INCREX", b"n", b"UBOUND", b"abc"]),
3512            "-ERR UBOUND is not an integer or out of range\r\n"
3513        );
3514        assert_eq!(
3515            f.run(&[b"INCREX", b"n", b"LBOUND", b"10", b"UBOUND", b"5"]),
3516            "-ERR LBOUND can't be greater than UBOUND\r\n"
3517        );
3518        assert_eq!(
3519            f.run(&[b"LCS", b"a", b"b", b"LEN", b"IDX"]),
3520            "-ERR If you want both the length and indexes, please just use IDX.\r\n"
3521        );
3522    }
3523
3524    /// Where the expiration rules bite. The one worth the test is `GETEX` on a
3525    /// key that is not there, which answers null without ever looking at the
3526    /// expiration it was given.
3527    #[test]
3528    fn the_expiry_rules_are_redis_own() {
3529        let mut f = Fixture::new();
3530        let bad = "-ERR invalid expire time in 'set' command\r\n";
3531        assert_eq!(f.run(&[b"SET", b"k", b"v", b"EX", b"0"]), bad);
3532        assert_eq!(f.run(&[b"SET", b"k", b"v", b"EX", b"-1"]), bad);
3533        assert_eq!(f.run(&[b"SET", b"k", b"v", b"EXAT", b"0"]), bad);
3534        assert_eq!(
3535            f.run(&[b"SET", b"k", b"v", b"EX", b"9999999999999999"]),
3536            bad
3537        );
3538        assert_eq!(
3539            f.run(&[b"SET", b"k", b"v", b"PX", b"99999999999999999999"]),
3540            "-ERR value is not an integer or out of range\r\n"
3541        );
3542        assert_eq!(
3543            f.run(&[b"SETEX", b"k", b"0", b"v"]),
3544            "-ERR invalid expire time in 'setex' command\r\n"
3545        );
3546        assert_eq!(f.run(&[b"GETEX", b"nosuch", b"EX", b"0"]), "$-1\r\n");
3547        assert_eq!(f.run(&[b"GETEX", b"nosuch", b"EX", b"abc"]), "$-1\r\n");
3548        assert_eq!(
3549            f.run(&[b"GETEX", b"nosuch", b"KEEPTTL"]),
3550            "-ERR syntax error\r\n",
3551            "the option list is still checked before the key is looked up"
3552        );
3553        // A deadline in the past is accepted and the key goes with it.
3554        assert_eq!(f.run(&[b"SET", b"k", b"v"]), "+OK\r\n");
3555        assert_eq!(f.run(&[b"SET", b"k", b"v", b"EXAT", b"1"]), "+OK\r\n");
3556        assert_eq!(f.run(&[b"GET", b"k"]), "$-1\r\n");
3557    }
3558
3559    #[test]
3560    fn mset_takes_its_pairs_from_the_read_buffer() {
3561        let mut f = Fixture::new();
3562        assert_eq!(f.run(&[b"MSET", b"a", b"1", b"b", b"2"]), "+OK\r\n");
3563        assert_eq!(
3564            f.run(&[b"MGET", b"a", b"b", b"nosuch"]),
3565            "*3\r\n$1\r\n1\r\n$1\r\n2\r\n$-1\r\n"
3566        );
3567        assert_eq!(f.run(&[b"MSETNX", b"b", b"9", b"c", b"3"]), ":0\r\n");
3568        assert_eq!(f.run(&[b"MSETNX", b"c", b"3", b"d", b"4"]), ":1\r\n");
3569        assert_eq!(
3570            f.run(&[b"MSETEX", b"2", b"e", b"5"]),
3571            "-ERR wrong number of key-value pairs\r\n"
3572        );
3573        assert_eq!(
3574            f.run(&[b"MSETEX", b"0", b"e", b"5"]),
3575            "-ERR invalid numkeys value\r\n"
3576        );
3577        assert_eq!(
3578            f.run(&[b"MSETEX", b"abc", b"e", b"5"]),
3579            "-ERR invalid numkeys value\r\n"
3580        );
3581    }
3582
3583    #[test]
3584    fn lcs_answers_the_length_the_string_and_the_runs() {
3585        let mut f = Fixture::new();
3586        f.run(&[b"MSET", b"a", b"ohmytext", b"b", b"mynewtext"]);
3587        assert_eq!(f.run(&[b"LCS", b"a", b"b"]), "$6\r\nmytext\r\n");
3588        assert_eq!(f.run(&[b"LCS", b"a", b"b", b"LEN"]), ":6\r\n");
3589        assert_eq!(
3590            f.run(&[b"LCS", b"a", b"b", b"IDX", b"MINMATCHLEN", b"4"]),
3591            "*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"
3592        );
3593        // Without `IDX` the two options that only mean something with it are
3594        // accepted and ignored, which is what a real server does.
3595        assert_eq!(
3596            f.run(&[b"LCS", b"a", b"b", b"MINMATCHLEN", b"4", b"WITHMATCHLEN"]),
3597            "$6\r\nmytext\r\n"
3598        );
3599    }
3600
3601    #[test]
3602    fn select_moves_the_connection_and_the_databases_stay_apart() {
3603        let mut f = Fixture::new();
3604        f.run(&[b"SET", b"k", b"zero"]);
3605        assert_eq!(f.run(&[b"SELECT", b"4"]), "+OK\r\n");
3606        assert_eq!(f.run(&[b"GET", b"k"]), "$-1\r\n");
3607        f.run(&[b"SET", b"k", b"four"]);
3608        assert_eq!(f.run(&[b"SELECT", b"0"]), "+OK\r\n");
3609        assert_eq!(f.run(&[b"GET", b"k"]), "$4\r\nzero\r\n");
3610        assert_eq!(
3611            f.run(&[b"SELECT", b"99"]),
3612            "-ERR DB index is out of range\r\n"
3613        );
3614        assert_eq!(
3615            f.run(&[b"SELECT", b"-1"]),
3616            "-ERR DB index is out of range\r\n"
3617        );
3618        assert_eq!(
3619            f.run(&[b"SELECT", b"abc"]),
3620            "-ERR value is not an integer or out of range\r\n"
3621        );
3622        // `RESET` brings it back to zero.
3623        f.run(&[b"SELECT", b"4"]);
3624        f.run(&[b"RESET"]);
3625        assert_eq!(f.run(&[b"GET", b"k"]), "$4\r\nzero\r\n");
3626    }
3627
3628    #[test]
3629    fn hello_agrees_on_a_protocol_and_refuses_the_ones_that_do_not_exist() {
3630        let mut f = Fixture::new();
3631        let reply = f.run(&[b"HELLO"]);
3632        assert!(reply.starts_with("*14\r\n"), "{reply}");
3633        assert!(reply.contains("$5\r\nredis\r\n"), "{reply}");
3634        assert!(reply.contains("$5\r\n8.8.0\r\n"), "{reply}");
3635        assert!(
3636            reply.contains(":7\r\n"),
3637            "the connection id is in there: {reply}"
3638        );
3639        assert_eq!(
3640            f.run(&[b"HELLO", b"4"]),
3641            "-NOPROTO unsupported protocol version\r\n"
3642        );
3643        assert_eq!(
3644            f.run(&[b"HELLO", b"abc"]),
3645            "-ERR Protocol version is not an integer or out of range\r\n"
3646        );
3647        assert_eq!(
3648            f.run(&[b"HELLO", b"3", b"SETNAME"]),
3649            "-ERR Syntax error in HELLO option 'SETNAME'\r\n"
3650        );
3651        assert!(
3652            f.run(&[b"HELLO", b"3", b"SETNAME", b"bob"])
3653                .starts_with("%7\r\n")
3654        );
3655        assert_eq!(f.session.name(), b"bob");
3656        f.run(&[b"RESET"]);
3657        assert_eq!(f.session.name(), b"");
3658    }
3659
3660    #[test]
3661    fn command_describes_this_server_in_the_shape_a_driver_reads() {
3662        let mut f = Fixture::new();
3663        let count = format!(":{}\r\n", COMMANDS.len());
3664        assert_eq!(f.run(&[b"COMMAND", b"COUNT"]), count);
3665        let info = f.run(&[b"COMMAND", b"INFO", b"get"]);
3666        assert_eq!(
3667            info,
3668            "*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\
3669             *3\r\n+@read\r\n+@string\r\n+@fast\r\n*0\r\n*0\r\n*0\r\n"
3670        );
3671        // A null in the list, and the plain one: `$-1` and not `*-1`.
3672        assert_eq!(f.run(&[b"COMMAND", b"INFO", b"nosuch"]), "*1\r\n$-1\r\n");
3673        assert_eq!(
3674            f.run(&[b"COMMAND", b"LIST", b"FILTERBY", b"PATTERN", b"getr*"]),
3675            "*1\r\n$8\r\ngetrange\r\n"
3676        );
3677        assert_eq!(
3678            f.run(&[b"COMMAND", b"NOPE"]),
3679            "-ERR unknown subcommand 'NOPE'. Try COMMAND HELP.\r\n"
3680        );
3681    }
3682
3683    /// A cluster aware client asks this question and then routes on the
3684    /// answer, so `MSETEX`, whose keys are not where the table says, is the one
3685    /// that matters.
3686    #[test]
3687    fn command_getkeys_finds_the_keys_including_the_hidden_ones() {
3688        let mut f = Fixture::new();
3689        assert_eq!(
3690            f.run(&[b"COMMAND", b"GETKEYS", b"get", b"k"]),
3691            "*1\r\n$1\r\nk\r\n"
3692        );
3693        assert_eq!(
3694            f.run(&[b"COMMAND", b"GETKEYS", b"mset", b"a", b"1", b"b", b"2"]),
3695            "*2\r\n$1\r\na\r\n$1\r\nb\r\n"
3696        );
3697        assert_eq!(
3698            f.run(&[
3699                b"COMMAND", b"GETKEYS", b"msetex", b"2", b"a", b"1", b"b", b"2"
3700            ]),
3701            "*2\r\n$1\r\na\r\n$1\r\nb\r\n"
3702        );
3703        assert_eq!(
3704            f.run(&[b"COMMAND", b"GETKEYS", b"ping"]),
3705            "-ERR The command has no key arguments\r\n"
3706        );
3707        assert_eq!(
3708            f.run(&[b"COMMAND", b"GETKEYS", b"set"]),
3709            "-ERR Invalid number of arguments specified for command\r\n"
3710        );
3711    }
3712
3713    #[test]
3714    fn config_answers_what_it_can_and_refuses_what_it_cannot() {
3715        let mut f = Fixture::new();
3716        assert_eq!(
3717            f.run(&[b"CONFIG", b"GET", b"maxmemory"]),
3718            "*2\r\n$9\r\nmaxmemory\r\n$1\r\n0\r\n"
3719        );
3720        // A pattern matches more than one, and a setting two patterns both ask
3721        // for is still sent once.
3722        let both = f.run(&[b"CONFIG", b"GET", b"maxmemory*", b"maxmemory"]);
3723        assert!(both.starts_with("*6\r\n"), "{both}");
3724        assert_eq!(f.run(&[b"CONFIG", b"GET", b"nosuch"]), "*0\r\n");
3725        assert_eq!(f.run(&[b"CONFIG", b"SET", b"appendonly", b"no"]), "+OK\r\n");
3726        assert_eq!(
3727            f.run(&[b"CONFIG", b"SET", b"appendonly", b"yes"]),
3728            "-ERR CONFIG SET failed (possibly related to argument 'appendonly') - can't set immutable config\r\n"
3729        );
3730        assert_eq!(
3731            f.run(&[b"CONFIG", b"SET", b"nosuch", b"1"]),
3732            "-ERR Unknown option or number of arguments for CONFIG SET - 'nosuch'\r\n"
3733        );
3734        assert_eq!(
3735            f.run(&[b"CONFIG", b"GET"]),
3736            "-ERR wrong number of arguments for 'config|get' command\r\n"
3737        );
3738        // Too few arguments and an odd number of them are different
3739        // complaints, which is the sort of thing only the real server tells
3740        // you.
3741        assert_eq!(
3742            f.run(&[b"CONFIG", b"SET", b"appendonly"]),
3743            "-ERR wrong number of arguments for 'config|set' command\r\n"
3744        );
3745        assert_eq!(
3746            f.run(&[b"CONFIG", b"SET", b"appendonly", b"no", b"maxmemory"]),
3747            "-ERR syntax error\r\n"
3748        );
3749        assert_eq!(f.run(&[b"CONFIG", b"RESETSTAT"]), "+OK\r\n");
3750        assert_eq!(
3751            f.run(&[b"CONFIG", b"REWRITE"]),
3752            "-ERR The server is running without a config file\r\n"
3753        );
3754    }
3755
3756    #[test]
3757    fn the_eviction_policy_reads_back_what_was_written_to_it() {
3758        let mut f = Fixture::new();
3759        assert_eq!(
3760            f.run(&[b"CONFIG", b"GET", b"maxmemory-policy"]),
3761            "*2\r\n$16\r\nmaxmemory-policy\r\n$10\r\nnoeviction\r\n"
3762        );
3763        assert_eq!(
3764            f.run(&[b"CONFIG", b"SET", b"maxmemory-policy", b"AllKeys-LFU"]),
3765            "+OK\r\n",
3766            "the name is matched without regard to case, like every other one"
3767        );
3768        assert_eq!(
3769            f.run(&[b"CONFIG", b"GET", b"maxmemory-policy"]),
3770            "*2\r\n$16\r\nmaxmemory-policy\r\n$11\r\nallkeys-lfu\r\n"
3771        );
3772        // And INFO agrees with CONFIG, which it did not when it was a literal.
3773        assert!(
3774            f.run(&[b"INFO", b"memory"])
3775                .contains("maxmemory_policy:allkeys-lfu"),
3776            "INFO and CONFIG disagree about the policy"
3777        );
3778        // The refusal names every legal value in the order the real server's
3779        // enum table lists them, because a client comparing the message compares
3780        // the whole string.
3781        assert_eq!(
3782            f.run(&[b"CONFIG", b"SET", b"maxmemory-policy", b"garbage"]),
3783            "-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"
3784        );
3785        // A bad pair leaves the good one in the same command alone, and the
3786        // policy is checked by the same pass that checks the numbers.
3787        assert_eq!(
3788            f.run(&[b"CONFIG", b"GET", b"maxmemory-policy"]),
3789            "*2\r\n$16\r\nmaxmemory-policy\r\n$11\r\nallkeys-lfu\r\n"
3790        );
3791        f.run(&[
3792            b"CONFIG",
3793            b"SET",
3794            b"hash-max-listpack-entries",
3795            b"7",
3796            b"maxmemory-policy",
3797            b"nonsense",
3798        ]);
3799        assert_eq!(
3800            f.run(&[b"CONFIG", b"GET", b"hash-max-listpack-entries"]),
3801            "*2\r\n$25\r\nhash-max-listpack-entries\r\n$3\r\n512\r\n"
3802        );
3803    }
3804
3805    #[test]
3806    fn the_three_eviction_numbers_read_back_too() {
3807        let mut f = Fixture::new();
3808        for (name, default, set) in [
3809            ("maxmemory-samples", "5", "12"),
3810            ("lfu-log-factor", "10", "3"),
3811            ("lfu-decay-time", "1", "60"),
3812        ] {
3813            let get = || {
3814                format!(
3815                    "*2\r\n${}\r\n{name}\r\n${}\r\n{default}\r\n",
3816                    name.len(),
3817                    default.len()
3818                )
3819            };
3820            assert_eq!(f.run(&[b"CONFIG", b"GET", name.as_bytes()]), get());
3821            assert_eq!(
3822                f.run(&[b"CONFIG", b"SET", name.as_bytes(), set.as_bytes()]),
3823                "+OK\r\n"
3824            );
3825            assert_eq!(
3826                f.run(&[b"CONFIG", b"GET", name.as_bytes()]),
3827                format!(
3828                    "*2\r\n${}\r\n{name}\r\n${}\r\n{set}\r\n",
3829                    name.len(),
3830                    set.len()
3831                )
3832            );
3833            // A number that is not a number is refused with the same sentence
3834            // every other number gets, which names the setting the client typed.
3835            assert_eq!(
3836                f.run(&[b"CONFIG", b"SET", name.as_bytes(), b"soon"]),
3837                format!(
3838                    "-ERR CONFIG SET failed (possibly related to argument '{name}') - argument couldn't be parsed into an integer\r\n"
3839                )
3840            );
3841        }
3842    }
3843
3844    #[test]
3845    fn the_memory_limit_reads_back_in_bytes_whatever_the_unit_was() {
3846        let mut f = Fixture::new();
3847        assert_eq!(
3848            f.run(&[b"CONFIG", b"GET", b"maxmemory"]),
3849            "*2\r\n$9\r\nmaxmemory\r\n$1\r\n0\r\n",
3850            "no limit is the default"
3851        );
3852        // The pairing is Redis's and it is a trap: the bare letter is a power of
3853        // ten and the one with the b is a power of two.
3854        for (typed, bytes) in [
3855            (&b"1024"[..], "1024"),
3856            (b"1k", "1000"),
3857            (b"1kb", "1024"),
3858            (b"1M", "1000000"),
3859            (b"1Mb", "1048576"),
3860            (b"1gb", "1073741824"),
3861            (b"100mb", "104857600"),
3862        ] {
3863            assert_eq!(f.run(&[b"CONFIG", b"SET", b"maxmemory", typed]), "+OK\r\n");
3864            assert_eq!(
3865                f.run(&[b"CONFIG", b"GET", b"maxmemory"]),
3866                format!("*2\r\n$9\r\nmaxmemory\r\n${}\r\n{bytes}\r\n", bytes.len()),
3867                "set {}",
3868                String::from_utf8_lossy(typed)
3869            );
3870        }
3871        assert!(
3872            f.run(&[b"INFO", b"memory"]).contains("maxmemory:104857600"),
3873            "the report agrees with the setting"
3874        );
3875
3876        // A unit nobody has heard of, and a negative number, which is not a very
3877        // large one however it is spelled.
3878        for bad in [&b"1tb"[..], b"-1", b"", b"lots"] {
3879            assert_eq!(
3880                f.run(&[b"CONFIG", b"SET", b"maxmemory", bad]),
3881                "-ERR CONFIG SET failed (possibly related to argument 'maxmemory') - argument must be a memory value\r\n",
3882                "refused {}",
3883                String::from_utf8_lossy(bad)
3884            );
3885        }
3886        assert!(
3887            f.run(&[b"INFO", b"memory"]).contains("maxmemory:104857600"),
3888            "and the refusal left the old one alone"
3889        );
3890    }
3891
3892    #[test]
3893    fn a_write_is_refused_when_there_is_no_room_and_nothing_to_evict() {
3894        let mut f = Fixture::new();
3895        f.run(&[b"SET", b"here", b"already"]);
3896        // A byte, which is under what an empty server holds, so nothing this
3897        // command could do would get it under. The default policy is
3898        // `noeviction`, so nothing is what it does.
3899        f.run(&[b"CONFIG", b"SET", b"maxmemory", b"1"]);
3900        assert_eq!(
3901            f.run(&[b"SET", b"k", b"v"]),
3902            "-OOM command not allowed when used memory > 'maxmemory'.\r\n"
3903        );
3904        assert_eq!(
3905            f.run(&[b"LPUSH", b"l", b"v"]),
3906            "-OOM command not allowed when used memory > 'maxmemory'.\r\n"
3907        );
3908        // Reading is allowed, and so is the one thing that would help.
3909        assert_eq!(f.run(&[b"GET", b"here"]), "$7\r\nalready\r\n");
3910        assert_eq!(f.run(&[b"DEL", b"here"]), ":1\r\n");
3911        assert!(f.run(&[b"INFO", b"stats"]).contains("evicted_keys:0"));
3912
3913        // Taking the limit away lets the write through again.
3914        f.run(&[b"CONFIG", b"SET", b"maxmemory", b"0"]);
3915        assert_eq!(f.run(&[b"SET", b"k", b"v"]), "+OK\r\n");
3916    }
3917
3918    /// Not under Miri, for the reason in `filled`: what it is watching is a
3919    /// whole two megabyte segment going back, so the megabytes are the claim
3920    /// and there is no smaller version of it that says the same thing.
3921    #[cfg_attr(miri, ignore = "several megabytes of arena, see `filled`")]
3922    #[test]
3923    fn an_allkeys_policy_makes_room_instead_of_refusing() {
3924        let mut f = Fixture::new();
3925        let val = vec![b'v'; 256];
3926        for i in 0..24000u32 {
3927            let k = format!("key:{i:08}");
3928            f.run(&[b"SET", k.as_bytes(), &val]);
3929        }
3930        let full = f.server.memory_bytes();
3931        assert!(
3932            full > 3 * 1024 * 1024,
3933            "the arena is several segments: {full}"
3934        );
3935
3936        // Two megabytes under what it is holding, which is one segment's worth,
3937        // so getting there means giving a whole segment back and not just
3938        // dropping a few records.
3939        let limit = full - 2 * 1024 * 1024;
3940        f.run(&[b"CONFIG", b"SET", b"maxmemory-policy", b"allkeys-lru"]);
3941        f.run(&[
3942            b"CONFIG",
3943            b"SET",
3944            b"maxmemory",
3945            limit.to_string().as_bytes(),
3946        ]);
3947
3948        // Writes keep working the whole way down. The budget means one command
3949        // does not do it all, so this runs until the server has settled and
3950        // checks that nothing was refused on the way.
3951        for i in 0..2000u32 {
3952            let k = format!("new:{i:08}");
3953            assert_eq!(
3954                f.run(&[b"SET", k.as_bytes(), &val]),
3955                "+OK\r\n",
3956                "write {i} was refused"
3957            );
3958            f.server.refresh_memory();
3959            if f.server.memory_bytes() <= limit {
3960                break;
3961            }
3962        }
3963        assert!(
3964            f.server.memory_bytes() <= limit,
3965            "it never got under: {} against {limit}",
3966            f.server.memory_bytes()
3967        );
3968        let info = f.run(&[b"INFO", b"stats"]);
3969        assert!(!info.contains("evicted_keys:0"), "{info}");
3970        assert!(
3971            f.run(&[b"DBSIZE"]) != ":0\r\n",
3972            "and it did not empty the database to get there"
3973        );
3974    }
3975
3976    /// Not under Miri. Every round is eleven commands over six collections
3977    /// holding two hundred byte values, which is a third of a second each
3978    /// interpreted, and the rounds cannot come down far: one in seven takes an
3979    /// entry back out, so under about a hundred and seventy of them the
3980    /// collections never reach the hundred and twenty eight entries where the
3981    /// small representations give up and become the big ones, and a
3982    /// representation changing under the running total is one of the five
3983    /// things this is here to watch. What is left is an hour, for an accounting
3984    /// claim rather than a safety one, and the commands it sends are sent a few
3985    /// at a time by the tests around it.
3986    #[cfg_attr(miri, ignore = "an hour of commands, and they cannot come down")]
3987    #[test]
3988    fn the_running_total_and_the_walk_agree_on_a_mixed_keyspace() {
3989        // The limit is judged against a number kept as the collections move,
3990        // rather than found by asking all of them, and the two have to be the
3991        // same number or the limit is enforced against a fiction. This does the
3992        // things that move it, which is growing a collection, shrinking one,
3993        // changing its representation, deleting it and reusing its slot, across
3994        // all five types, and checks the two against each other as it goes.
3995        let mut f = Fixture::new();
3996        f.run(&[b"CONFIG", b"SET", b"maxmemory", b"1gb"]);
3997        let big = vec![b'v'; 200];
3998
3999        for i in 0..400u32 {
4000            let n = i.to_string();
4001            let n = n.as_bytes();
4002            f.run(&[b"SADD", b"s", n]);
4003            f.run(&[b"SADD", b"s2", &big]);
4004            f.run(&[b"HSET", b"h", n, &big]);
4005            f.run(&[b"RPUSH", b"l", &big]);
4006            f.run(&[b"ZADD", b"z", n, n]);
4007            f.run(&[b"ARSET", b"a", n, &big]);
4008            if i % 7 == 0 {
4009                f.run(&[b"SREM", b"s", n]);
4010                f.run(&[b"HDEL", b"h", n]);
4011                f.run(&[b"LPOP", b"l"]);
4012                f.run(&[b"ZREM", b"z", n]);
4013                f.run(&[b"ARDEL", b"a", n]);
4014            }
4015            if i % 53 == 0 {
4016                // Every type deleted and made again, so a slot goes on the free
4017                // list and comes back holding something else.
4018                f.run(&[b"DEL", b"s2"]);
4019            }
4020            assert_eq!(
4021                f.server.settled_memory(),
4022                f.server.memory_bytes(),
4023                "after round {i}"
4024            );
4025        }
4026
4027        // The run has to have built something, or the two numbers agreeing is
4028        // two zeroes agreeing.
4029        assert_eq!(f.run(&[b"DBSIZE"]), ":6\r\n");
4030        assert!(
4031            f.server.memory_bytes() > 512 * 1024,
4032            "{}",
4033            f.server.memory_bytes()
4034        );
4035
4036        // And it survives the collections going away entirely.
4037        f.run(&[b"FLUSHALL"]);
4038        assert_eq!(f.server.settled_memory(), f.server.memory_bytes());
4039    }
4040
4041    #[test]
4042    fn taking_the_limit_away_stops_the_counting_and_putting_it_back_starts_again() {
4043        // A server with no limit does not keep the running total, so setting a
4044        // limit on a database that is already full has to start it from a walk.
4045        // If it did not, the first reading would be zero and the server would
4046        // think it had all the room in the world.
4047        let mut f = Fixture::new();
4048        for i in 0..200u32 {
4049            let n = i.to_string();
4050            f.run(&[b"SADD", b"s", n.as_bytes()]);
4051            f.run(&[b"HSET", b"h", n.as_bytes(), b"value"]);
4052        }
4053        f.run(&[b"CONFIG", b"SET", b"maxmemory", b"1gb"]);
4054        assert_eq!(f.server.settled_memory(), f.server.memory_bytes());
4055
4056        f.run(&[b"CONFIG", b"SET", b"maxmemory", b"0"]);
4057        for i in 200..400u32 {
4058            let n = i.to_string();
4059            f.run(&[b"SADD", b"s", n.as_bytes()]);
4060        }
4061        f.run(&[b"CONFIG", b"SET", b"maxmemory", b"1gb"]);
4062        assert_eq!(
4063            f.server.settled_memory(),
4064            f.server.memory_bytes(),
4065            "the writes it was not watching are in the number it started from"
4066        );
4067    }
4068
4069    #[test]
4070    fn evicted_keys_and_expired_keys_are_different_numbers() {
4071        let mut f = Fixture::new();
4072        // Nothing has been evicted and nothing can be under the default policy,
4073        // so this stays at zero while the other one moves.
4074        f.run(&[b"SET", b"gone", b"v", b"PX", b"1"]);
4075        f.server.advance_clock_ms(20);
4076        f.run(&[b"GET", b"gone"]);
4077        let info = f.run(&[b"INFO", b"stats"]);
4078        assert!(info.contains("expired_keys:1"), "{info}");
4079        assert!(info.contains("evicted_keys:0"), "{info}");
4080    }
4081
4082    #[test]
4083    fn the_object_subcommands_follow_the_policy() {
4084        let mut f = Fixture::new();
4085        f.run(&[b"SET", b"s", b"v"]);
4086        // Under the default the clock is kept and the counter is not, and under
4087        // an LFU policy it is the other way round. Each subcommand refuses on
4088        // the side where its reading of the three bytes means nothing.
4089        assert_eq!(f.run(&[b"OBJECT", b"IDLETIME", b"s"]), ":0\r\n");
4090        assert!(
4091            f.run(&[b"OBJECT", b"FREQ", b"s"])
4092                .starts_with("-ERR An LFU maxmemory policy is not selected"),
4093        );
4094
4095        f.run(&[b"CONFIG", b"SET", b"maxmemory-policy", b"allkeys-lfu"]);
4096        assert!(
4097            f.run(&[b"OBJECT", b"IDLETIME", b"s"])
4098                .starts_with("-ERR An LFU maxmemory policy is selected"),
4099        );
4100        // The key was written under a clock policy, so what comes back is that
4101        // clock read as a counter. It is a number and not an error, which is the
4102        // point: switching at runtime does not invalidate anything, it only makes
4103        // the old field mean something else until the key is used again.
4104        assert!(
4105            f.run(&[b"OBJECT", b"FREQ", b"s"]).starts_with(':'),
4106            "FREQ should answer under an LFU policy"
4107        );
4108    }
4109
4110    #[test]
4111    fn object_says_which_rung_of_the_ladder_a_key_is_on() {
4112        let mut f = Fixture::new();
4113        f.run(&[b"SET", b"s", b"hello"]);
4114        f.run(&[b"SET", b"n", b"123"]);
4115        f.run(&[b"SADD", b"si", b"1", b"2", b"3"]);
4116        f.run(&[b"SADD", b"ss", b"a", b"b"]);
4117        f.run(&[b"HSET", b"h", b"f", b"v"]);
4118        for (key, want) in [
4119            (b"s".as_slice(), "embstr"),
4120            (b"n", "int"),
4121            (b"si", "intset"),
4122            (b"ss", "listpack"),
4123            (b"h", "listpack"),
4124        ] {
4125            let reply = f.run(&[b"OBJECT", b"ENCODING", key]);
4126            assert_eq!(reply, format!("${}\r\n{want}\r\n", want.len()));
4127        }
4128
4129        // A field deadline widens the blob rather than promoting it, and this
4130        // is the only place a client can see that happen.
4131        f.run(&[b"HEXPIRE", b"h", b"100", b"FIELDS", b"1", b"f"]);
4132        assert_eq!(
4133            f.run(&[b"OBJECT", b"ENCODING", b"h"]),
4134            "$10\r\nlistpackex\r\n"
4135        );
4136
4137        assert_eq!(f.run(&[b"OBJECT", b"REFCOUNT", b"s"]), ":1\r\n");
4138        assert_eq!(f.run(&[b"OBJECT", b"IDLETIME", b"s"]), ":0\r\n");
4139        assert!(f.run(&[b"OBJECT", b"HELP"]).starts_with("*14\r\n+OBJECT "));
4140    }
4141
4142    #[test]
4143    fn object_answers_nil_for_a_key_that_is_not_there() {
4144        let mut f = Fixture::new();
4145        for sub in [b"ENCODING".as_slice(), b"REFCOUNT", b"IDLETIME", b"FREQ"] {
4146            assert_eq!(
4147                f.run(&[b"OBJECT", sub, b"nokey"]),
4148                "$-1\r\n",
4149                "a nil and not an error, which is what 8.10.1 does"
4150            );
4151        }
4152        // And the key is looked up before FREQ has its complaint, so the
4153        // complaint only reaches a key that exists.
4154        f.run(&[b"SET", b"s", b"v"]);
4155        assert!(
4156            f.run(&[b"OBJECT", b"FREQ", b"s"])
4157                .starts_with("-ERR An LFU maxmemory policy is not"),
4158        );
4159        assert_eq!(
4160            f.run(&[b"OBJECT", b"NOPE", b"s"]),
4161            "-ERR unknown subcommand 'NOPE'. Try OBJECT HELP.\r\n"
4162        );
4163        assert_eq!(
4164            f.run(&[b"OBJECT", b"ENCODING"]),
4165            "-ERR wrong number of arguments for 'object|encoding' command\r\n"
4166        );
4167        assert_eq!(
4168            f.run(&[b"OBJECT", b"ENCODING", b"s", b"extra"]),
4169            "-ERR wrong number of arguments for 'object|encoding' command\r\n"
4170        );
4171        assert_eq!(
4172            f.run(&[b"OBJECT"]),
4173            "-ERR wrong number of arguments for 'object' command\r\n"
4174        );
4175    }
4176
4177    #[test]
4178    fn config_moves_the_ladder_and_object_encoding_agrees() {
4179        let mut f = Fixture::new();
4180        assert_eq!(
4181            f.run(&[b"CONFIG", b"GET", b"hash-max-listpack-entries"]),
4182            "*2\r\n$25\r\nhash-max-listpack-entries\r\n$3\r\n512\r\n",
4183            "512 and not the 128 everyone remembers, which is what 8.10.1 says"
4184        );
4185        // The old spelling is the same number under a different name, and a
4186        // glob that catches both sends both.
4187        assert_eq!(
4188            f.run(&[b"CONFIG", b"GET", b"hash-max-ziplist-entries"]),
4189            "*2\r\n$24\r\nhash-max-ziplist-entries\r\n$3\r\n512\r\n"
4190        );
4191        assert!(
4192            f.run(&[b"CONFIG", b"GET", b"hash-max-*"])
4193                .starts_with("*8\r\n")
4194        );
4195        assert!(
4196            f.run(&[b"CONFIG", b"GET", b"set-max-*"])
4197                .starts_with("*6\r\n")
4198        );
4199
4200        f.run(&[b"HSET", b"h", b"a", b"1", b"b", b"2", b"c", b"3"]);
4201        assert_eq!(f.run(&[b"OBJECT", b"ENCODING", b"h"]), "$8\r\nlistpack\r\n");
4202
4203        assert_eq!(
4204            f.run(&[b"CONFIG", b"SET", b"hash-max-ziplist-entries", b"2"]),
4205            "+OK\r\n",
4206            "written under the old name and read back under the new one"
4207        );
4208        assert_eq!(
4209            f.run(&[b"CONFIG", b"GET", b"hash-max-listpack-entries"]),
4210            "*2\r\n$25\r\nhash-max-listpack-entries\r\n$1\r\n2\r\n"
4211        );
4212        assert_eq!(
4213            f.run(&[b"OBJECT", b"ENCODING", b"h"]),
4214            "$8\r\nlistpack\r\n",
4215            "the hash that already exists is left exactly where it was"
4216        );
4217        f.run(&[b"HSET", b"h2", b"a", b"1", b"b", b"2", b"c", b"3"]);
4218        assert_eq!(
4219            f.run(&[b"OBJECT", b"ENCODING", b"h2"]),
4220            "$9\r\nhashtable\r\n",
4221            "and the next one built goes straight to a table"
4222        );
4223
4224        // The set has three of these and all three move.
4225        f.run(&[b"CONFIG", b"SET", b"set-max-intset-entries", b"2"]);
4226        f.run(&[b"SADD", b"s", b"1", b"2", b"3"]);
4227        assert_eq!(f.run(&[b"OBJECT", b"ENCODING", b"s"]), "$8\r\nlistpack\r\n");
4228        f.run(&[b"CONFIG", b"SET", b"set-max-listpack-value", b"2"]);
4229        f.run(&[b"SADD", b"s2", b"abcdefgh"]);
4230        assert_eq!(
4231            f.run(&[b"OBJECT", b"ENCODING", b"s2"]),
4232            "$9\r\nhashtable\r\n"
4233        );
4234    }
4235
4236    #[test]
4237    fn config_set_takes_all_of_the_ladder_or_none_of_it() {
4238        let mut f = Fixture::new();
4239        assert_eq!(
4240            f.run(&[
4241                b"CONFIG",
4242                b"SET",
4243                b"hash-max-listpack-entries",
4244                b"7",
4245                b"set-max-listpack-entries",
4246                b"abc"
4247            ]),
4248            "-ERR CONFIG SET failed (possibly related to argument 'set-max-listpack-entries') - argument couldn't be parsed into an integer\r\n"
4249        );
4250        assert_eq!(
4251            f.run(&[b"CONFIG", b"GET", b"hash-max-listpack-entries"]),
4252            "*2\r\n$25\r\nhash-max-listpack-entries\r\n$3\r\n512\r\n",
4253            "the pair in front of the bad one did not go in"
4254        );
4255        // The name in the complaint is the one that was typed, so the old
4256        // spelling comes back as the old spelling.
4257        assert_eq!(
4258            f.run(&[b"CONFIG", b"SET", b"hash-max-ziplist-entries", b"abc"]),
4259            "-ERR CONFIG SET failed (possibly related to argument 'hash-max-ziplist-entries') - argument couldn't be parsed into an integer\r\n"
4260        );
4261        assert_eq!(
4262            f.run(&[b"CONFIG", b"SET", b"set-max-intset-entries", b"-1"]),
4263            "-ERR CONFIG SET failed (possibly related to argument 'set-max-intset-entries') - argument must be between 0 and 9223372036854775807 inclusive\r\n"
4264        );
4265        // A number past what an i64 holds is the parse complaint and not the
4266        // range one, which is upstream reading it before it checks it.
4267        assert_eq!(
4268            f.run(&[
4269                b"CONFIG",
4270                b"SET",
4271                b"set-max-intset-entries",
4272                b"99999999999999999999"
4273            ]),
4274            "-ERR CONFIG SET failed (possibly related to argument 'set-max-intset-entries') - argument couldn't be parsed into an integer\r\n"
4275        );
4276        assert_eq!(
4277            f.run(&[
4278                b"CONFIG",
4279                b"SET",
4280                b"set-max-intset-entries",
4281                b"9223372036854775807"
4282            ]),
4283            "+OK\r\n"
4284        );
4285    }
4286
4287    #[test]
4288    fn a_setting_moved_on_one_database_moved_on_all_of_them() {
4289        let mut f = Fixture::new();
4290        f.run(&[b"CONFIG", b"SET", b"hash-max-listpack-entries", b"1"]);
4291        f.run(&[b"SELECT", b"3"]);
4292        f.run(&[b"HSET", b"h", b"a", b"1", b"b", b"2"]);
4293        assert_eq!(
4294            f.run(&[b"OBJECT", b"ENCODING", b"h"]),
4295            "$9\r\nhashtable\r\n",
4296            "these are one server wide number in Redis, whatever a Keyspace carries"
4297        );
4298    }
4299
4300    #[test]
4301    fn info_reports_the_numbers_it_can_stand_behind() {
4302        let mut f = Fixture::new();
4303        f.run(&[b"MSET", b"a", b"1", b"b", b"2"]);
4304        let all = f.run(&[b"INFO"]);
4305        assert!(all.contains("redis_version:8.8.0"), "{all}");
4306        assert!(
4307            all.contains(concat!("yo_version:", env!("CARGO_PKG_VERSION"))),
4308            "{all}"
4309        );
4310        assert!(all.contains("db0:keys=2,expires=0,avg_ttl=0"), "{all}");
4311        assert!(all.contains("role:master"), "{all}");
4312        // One section is one section.
4313        let clients = f.run(&[b"INFO", b"clients"]);
4314        assert!(clients.contains("connected_clients:0"), "{clients}");
4315        assert!(!clients.contains("redis_version"), "{clients}");
4316        assert_eq!(f.run(&[b"INFO", b"nosuch"]), "$0\r\n\r\n");
4317    }
4318
4319    /// The sections a bare `INFO` gives back, and the ones you have to ask for.
4320    ///
4321    /// This is Redis's `unit/info-command` written against the fixture. Every
4322    /// assertion in it is one of theirs, in their order, and the two fields it
4323    /// turns on are the two that suite was failing on: `master_repl_offset`,
4324    /// which is in the default set, and `rejected_calls`, which is not.
4325    #[test]
4326    fn commandstats_is_asked_for_and_replication_is_not() {
4327        let mut f = Fixture::new();
4328        for arg in ["", "all", "default", "everything"] {
4329            let info = if arg.is_empty() {
4330                f.run(&[b"INFO"])
4331            } else {
4332                f.run(&[b"INFO", arg.as_bytes()])
4333            };
4334            assert!(info.contains("redis_version"), "{arg}: {info}");
4335            assert!(info.contains("used_cpu_user"), "{arg}: {info}");
4336            assert!(info.contains("used_memory"), "{arg}: {info}");
4337            assert!(!info.contains("sentinel_tilt"), "{arg}: {info}");
4338            let asked = arg == "all" || arg == "everything";
4339            assert_eq!(
4340                info.contains("rejected_calls"),
4341                asked,
4342                "{arg} should{} carry the command counters: {info}",
4343                if asked { "" } else { " not" }
4344            );
4345        }
4346
4347        let cpu = f.run(&[b"INFO", b"cpu"]);
4348        assert!(cpu.contains("used_cpu_user"), "{cpu}");
4349        assert!(!cpu.contains("used_memory"), "{cpu}");
4350
4351        // Their case, to make the point that a section name is not case
4352        // sensitive any more than a command name is.
4353        let stats = f.run(&[b"INFO", b"commandSTATS"]);
4354        assert!(!stats.contains("used_memory"), "{stats}");
4355        assert!(stats.contains("rejected_calls"), "{stats}");
4356
4357        // Two sections named, and neither of them pulls in a third.
4358        let pair = f.run(&[b"INFO", b"cpu", b"sentinel"]);
4359        assert!(pair.contains("used_cpu_user"), "{pair}");
4360        assert!(!pair.contains("master_repl_offset"), "{pair}");
4361
4362        let with_all = f.run(&[b"INFO", b"cpu", b"all"]);
4363        assert!(with_all.contains("used_memory"), "{with_all}");
4364        assert!(with_all.contains("master_repl_offset"), "{with_all}");
4365        assert!(with_all.contains("rejected_calls"), "{with_all}");
4366        // A section named twice is still written once.
4367        assert_eq!(
4368            with_all.matches("used_cpu_user_children").count(),
4369            1,
4370            "{with_all}"
4371        );
4372
4373        let with_default = f.run(&[b"INFO", b"cpu", b"default"]);
4374        assert!(with_default.contains("used_memory"), "{with_default}");
4375        assert!(
4376            with_default.contains("master_repl_offset"),
4377            "{with_default}"
4378        );
4379        assert!(!with_default.contains("rejected_calls"), "{with_default}");
4380        assert_eq!(
4381            with_default.matches("used_cpu_user_children").count(),
4382            1,
4383            "{with_default}"
4384        );
4385    }
4386
4387    /// The memory section says what this process may use, not what the machine
4388    /// has.
4389    ///
4390    /// The distinction is the whole point of it. A server inside a container
4391    /// that reports the host's memory is a server whose operator sizes it for
4392    /// memory it will be killed for touching, so all three numbers are there:
4393    /// what the machine has, what the cgroup allows, and the quarter of the
4394    /// tighter one that pools are sized from.
4395    #[test]
4396    fn info_memory_reports_the_cap_and_the_quarter_of_it_that_gets_used() {
4397        let mut f = Fixture::new();
4398        let info = f.run(&[b"INFO", b"memory"]);
4399        for field in [
4400            "total_system_memory:",
4401            "mem_cgroup_limit:",
4402            "mem_limit:",
4403            "mem_budget:",
4404        ] {
4405            assert!(info.contains(field), "no {field} in {info}");
4406        }
4407
4408        let field = |name: &str| -> u64 {
4409            info.lines()
4410                .find_map(|l| l.strip_prefix(name))
4411                .unwrap_or_else(|| panic!("no {name} in {info}"))
4412                .trim()
4413                .parse()
4414                .unwrap_or_else(|_| panic!("{name} is not a number in {info}"))
4415        };
4416        let limit = field("mem_limit:");
4417        assert_eq!(field("mem_budget:"), limit / 4, "{info}");
4418        // Zero means there is no limit to report, which is a real answer on a
4419        // machine with no cgroups and no way to ask how big it is.
4420        if limit != 0 {
4421            let host = field("total_system_memory:");
4422            let cgroup = field("mem_cgroup_limit:");
4423            assert!(
4424                limit == host || limit == cgroup,
4425                "the limit came from neither number: {info}"
4426            );
4427        }
4428    }
4429
4430    /// The three counters, each on the path that raises it.
4431    ///
4432    /// `calls` on a command that worked, `failed_calls` on one that ran and
4433    /// answered with an error, and `rejected_calls` on one that never ran at
4434    /// all. The last two are the pair that is easy to collapse into one number
4435    /// and that Redis keeps apart, because a client sending the wrong number of
4436    /// arguments and a client asking for a list element that is not there are
4437    /// not the same problem.
4438    #[test]
4439    fn a_command_counts_what_it_did_separately_from_what_it_refused() {
4440        let mut f = Fixture::new();
4441        f.run(&[b"SET", b"k", b"v"]);
4442        f.run(&[b"SET", b"k", b"w"]);
4443        // Ran, and answered with an error, because `k` is not a list.
4444        f.run(&[b"LPUSH", b"k", b"x"]);
4445        // Never ran: `LPUSH` takes at least three arguments.
4446        f.run(&[b"LPUSH", b"k"]);
4447
4448        let stats = f.run(&[b"INFO", b"commandstats"]);
4449        assert!(
4450            stats.contains("cmdstat_set:calls=2,rejected_calls=0,failed_calls=0"),
4451            "{stats}"
4452        );
4453        assert!(
4454            stats.contains("cmdstat_lpush:calls=1,rejected_calls=1,failed_calls=1"),
4455            "{stats}"
4456        );
4457        assert!(
4458            !stats.contains("cmdstat_zadd"),
4459            "a command nobody has sent has no row: {stats}"
4460        );
4461    }
4462
4463    /// A cache that writes with a deadline and never reads back used to hold
4464    /// every key it had ever written, because lazy expiry needs somebody to walk
4465    /// past a key before it can reclaim it and nobody ever did.
4466    #[test]
4467    fn the_active_sweep_reclaims_keys_no_client_comes_back_for() {
4468        // Four thousand keys is four thousand trips through dispatch, and what
4469        // Miri charges for is trips rather than keys, so this was over five
4470        // minutes there. An eighth of each keeps everything the test is about,
4471        // which is three keys with a deadline for every one without and a
4472        // sweep that has to reclaim all of the first kind and none of the
4473        // second.
4474        let (dead, live) = if cfg!(miri) {
4475            (375, 125)
4476        } else {
4477            (3_000, 1_000)
4478        };
4479        let mut f = Fixture::new();
4480        for i in 0..dead {
4481            f.run(&[b"SET", format!("d{i}").as_bytes(), b"v", b"PX", b"50"]);
4482        }
4483        for i in 0..live {
4484            f.run(&[b"SET", format!("k{i}").as_bytes(), b"v"]);
4485        }
4486        let all = format!(":{}\r\n", dead + live);
4487        assert_eq!(f.run(&[b"DBSIZE"]), all);
4488        f.advance(100);
4489        assert_eq!(
4490            f.run(&[b"DBSIZE"]),
4491            all,
4492            "DBSIZE counts records and nothing has read past the dead ones yet"
4493        );
4494
4495        // What the shard loop does, one slice at a time.
4496        let rest = format!(":{live}\r\n");
4497        let mut spent = 0;
4498        for _ in 0..2_000 {
4499            spent += f.server.expire_step(4096);
4500            if f.run(&[b"DBSIZE"]) == rest {
4501                break;
4502            }
4503        }
4504        assert_eq!(f.run(&[b"DBSIZE"]), rest, "spent {spent} looks");
4505        assert!(
4506            f.run(&[b"INFO", b"stats"])
4507                .contains(&format!("expired_keys:{dead}"))
4508        );
4509        for i in 0..live {
4510            assert_eq!(
4511                f.run(&[b"GET", format!("k{i}").as_bytes()]),
4512                "$1\r\nv\r\n",
4513                "it took a key that had no deadline"
4514            );
4515        }
4516    }
4517
4518    #[test]
4519    fn a_sweep_of_a_server_with_no_deadlines_anywhere_costs_nothing() {
4520        // The keys are only here so that the database the sweep walks is not an
4521        // empty one. Two hundred of them fills as many slots as a sweep looks
4522        // at and is a tenth of the interpreted work.
4523        let n = if cfg!(miri) { 200 } else { 2_000 };
4524        let mut f = Fixture::new();
4525        for i in 0..n {
4526            f.run(&[b"SET", format!("k{i}").as_bytes(), b"v"]);
4527        }
4528        assert_eq!(f.server.expire_step(4096), 0);
4529        // And one database having them does not make the other fifteen pay.
4530        f.run(&[b"SELECT", b"3"]);
4531        f.run(&[b"SET", b"x", b"v", b"PX", b"50"]);
4532        f.advance(100);
4533        for _ in 0..64 {
4534            f.server.expire_step(4096);
4535        }
4536        assert_eq!(f.run(&[b"DBSIZE"]), ":0\r\n");
4537        f.run(&[b"SELECT", b"0"]);
4538        assert_eq!(f.run(&[b"DBSIZE"]), format!(":{n}\r\n"));
4539        assert_eq!(f.server.expire_step(4096), 0, "and it is quiet again");
4540    }
4541
4542    /// The gate, which is what stops a maintenance slice that runs every hundred
4543    /// nanoseconds from drawing a sample every hundred nanoseconds.
4544    #[test]
4545    fn the_sweep_the_loop_calls_runs_at_most_once_a_millisecond() {
4546        let mut f = Fixture::new();
4547        for i in 0..500u32 {
4548            f.run(&[b"SET", format!("d{i}").as_bytes(), b"v", b"PX", b"50"]);
4549        }
4550        f.advance(100);
4551        let at = f.server.striped(0).now_ms();
4552        f.server.set_clock_ms(at);
4553        // A small budget, so that one slice cannot finish the job and a second
4554        // one having nothing to do would mean the gate and not an empty
4555        // database.
4556        assert!(f.server.expire_slice(8) > 0, "the first one works");
4557        for _ in 0..1_000 {
4558            assert_eq!(
4559                f.server.expire_slice(8),
4560                0,
4561                "the millisecond has not moved and neither should this"
4562            );
4563        }
4564        assert!(
4565            f.server.striped(0).expires() > 400,
4566            "there is plenty left to take"
4567        );
4568        f.server.set_clock_ms(at + 1);
4569        assert!(f.server.expire_slice(8) > 0, "and then it goes again");
4570    }
4571
4572    /// `expires=` used to be a hardcoded zero, which meant a dashboard watching
4573    /// how much of a cache is volatile was reading a constant.
4574    #[test]
4575    fn info_keyspace_counts_the_keys_that_have_a_deadline() {
4576        let mut f = Fixture::new();
4577        f.run(&[b"MSET", b"a", b"1", b"b", b"2", b"c", b"3"]);
4578        assert!(
4579            f.run(&[b"INFO", b"keyspace"])
4580                .contains("db0:keys=3,expires=0"),
4581            "none of them has one yet"
4582        );
4583        f.run(&[b"EXPIRE", b"a", b"1000"]);
4584        f.run(&[b"EXPIRE", b"b", b"1000"]);
4585        let two = f.run(&[b"INFO", b"keyspace"]);
4586        assert!(two.contains("db0:keys=3,expires=2"), "{two}");
4587        f.run(&[b"PERSIST", b"a"]);
4588        f.run(&[b"DEL", b"b"]);
4589        let none = f.run(&[b"INFO", b"keyspace"]);
4590        assert!(none.contains("db0:keys=2,expires=0"), "{none}");
4591
4592        // Each database answers for itself, the way Redis reports it.
4593        f.run(&[b"SELECT", b"1"]);
4594        f.run(&[b"SET", b"x", b"1", b"EX", b"1000"]);
4595        let both = f.run(&[b"INFO", b"keyspace"]);
4596        assert!(both.contains("db0:keys=2,expires=0"), "{both}");
4597        assert!(both.contains("db1:keys=1,expires=1"), "{both}");
4598    }
4599
4600    /// Not under Miri, which reads a zero on purpose because it has no
4601    /// `getrusage` to call, so the second half of this would burn a billion
4602    /// interpreted multiplications waiting for a number that is never going to
4603    /// move. The first half, that the section is there and has the fields Redis
4604    /// clients look for, is checked by the `INFO` tests above as well, and
4605    /// those do run there.
4606    #[cfg(unix)]
4607    #[cfg_attr(miri, ignore = "no getrusage under Miri, so the number is fixed")]
4608    #[test]
4609    fn info_cpu_reports_processor_time_that_was_really_measured() {
4610        let mut f = Fixture::new();
4611        let cpu = f.run(&[b"INFO", b"cpu"]);
4612        assert!(cpu.contains("# CPU"), "{cpu}");
4613        // Redis's unit/info-command asks for this one by name in three tests.
4614        assert!(cpu.contains("used_cpu_user:"), "{cpu}");
4615        assert!(cpu.contains("used_cpu_sys:"), "{cpu}");
4616        assert!(cpu.contains("used_cpu_user_children:0.000000"), "{cpu}");
4617        assert!(!cpu.contains("redis_version"), "{cpu}");
4618
4619        // It is a measurement and not a constant, so it goes up when work
4620        // happens. A tight loop rather than a sleep, because sleeping is the
4621        // one thing that does not move this number.
4622        let before = used_cpu_user(&cpu);
4623        let mut n = 0u64;
4624        let mut rounds = 0;
4625        while used_cpu_user(&f.run(&[b"INFO", b"cpu"])) <= before {
4626            for i in 0..1_000_000u64 {
4627                n = n.wrapping_add(i.wrapping_mul(i));
4628            }
4629            rounds += 1;
4630            // A bound rather than a spin, so a platform where this number does
4631            // not move fails here instead of hanging. Even a clock with whole
4632            // millisecond granularity gets there in the first round or two.
4633            assert!(rounds < 1_000, "cpu time never moved, n is {n}");
4634        }
4635    }
4636
4637    /// Pull `used_cpu_user` back out of an `INFO cpu` reply.
4638    #[cfg(unix)]
4639    fn used_cpu_user(info: &str) -> f64 {
4640        info.lines()
4641            .find_map(|l| l.strip_prefix("used_cpu_user:"))
4642            .expect("no used_cpu_user in the reply")
4643            .trim()
4644            .parse()
4645            .expect("used_cpu_user is not a number")
4646    }
4647
4648    /// The safety net under the rule that a body checks its arguments before
4649    /// it writes anything. `MGET` writes its array header first and then reads
4650    /// each key, so if a later argument could fail the header would already be
4651    /// out. Nothing in the string group does that today and this is what would
4652    /// catch the first one that did.
4653    #[test]
4654    fn a_command_that_fails_leaves_nothing_half_written() {
4655        let mut f = Fixture::new();
4656        let reply = f.run(&[b"SETRANGE", b"k", b"-1", b"x"]);
4657        assert_eq!(reply, "-ERR offset is out of range\r\n");
4658        assert!(!reply.contains(':'), "no integer went out in front of it");
4659    }
4660
4661    #[test]
4662    fn quit_answers_first_and_closes_after() {
4663        let mut f = Fixture::new();
4664        let (flow, reply) = f.flow(&[b"QUIT"]);
4665        assert_eq!(reply, "+OK\r\n");
4666        assert_eq!(flow, Flow::Close);
4667    }
4668
4669    /// A server that has not been asked to stop is not stopping, and one that
4670    /// has says so without writing anything back.
4671    ///
4672    /// The empty reply is the point. Redis answers nothing at all here and the
4673    /// client sees the socket close, and an `OK` would be a promise from a
4674    /// process that is about to not exist.
4675    #[test]
4676    fn shutdown_writes_nothing_and_sets_the_flag() {
4677        let mut f = Fixture::new();
4678        assert!(!f.server.stopping(), "nobody has asked yet");
4679
4680        let (flow, reply) = f.flow(&[b"SHUTDOWN"]);
4681        assert_eq!(reply, "");
4682        assert_eq!(flow, Flow::Close);
4683        assert!(f.server.stopping());
4684    }
4685
4686    /// Every flag combination 8.10.1 takes, and every one it refuses.
4687    ///
4688    /// The refusals are the half worth pinning down. `SAVE` and `NOSAVE`
4689    /// contradict each other, `ABORT` says to do nothing so it cannot be
4690    /// combined with a word about how to do it, and repeating any one of them
4691    /// is fine. All of it was read off a running 8.10.1 rather than worked out
4692    /// from the documentation, which does not say.
4693    #[test]
4694    fn shutdown_takes_the_flags_redis_takes() {
4695        for flags in [
4696            &[b"NOSAVE".as_slice()][..],
4697            &[b"SAVE"],
4698            &[b"NOW"],
4699            &[b"FORCE"],
4700            &[b"nosave"],
4701            &[b"NOW", b"NOW"],
4702            &[b"SAVE", b"SAVE"],
4703            &[b"NOSAVE", b"NOW", b"FORCE"],
4704        ] {
4705            let mut f = Fixture::new();
4706            let mut parts = vec![b"SHUTDOWN".as_slice()];
4707            parts.extend_from_slice(flags);
4708            let (flow, reply) = f.flow(&parts);
4709            assert_eq!(reply, "", "SHUTDOWN {flags:?} answered something");
4710            assert_eq!(flow, Flow::Close, "SHUTDOWN {flags:?} did not close");
4711            assert!(f.server.stopping(), "SHUTDOWN {flags:?} did not stop");
4712        }
4713
4714        for flags in [
4715            &[b"BOGUS".as_slice()][..],
4716            &[b"SAVE", b"NOSAVE"],
4717            &[b"NOSAVE", b"SAVE"],
4718            &[b"ABORT", b"NOW"],
4719            &[b"NOSAVE", b"ABORT"],
4720            &[b"NOW", b"FORCE", b"ABORT"],
4721        ] {
4722            let mut f = Fixture::new();
4723            let mut parts = vec![b"SHUTDOWN".as_slice()];
4724            parts.extend_from_slice(flags);
4725            assert_eq!(
4726                f.run(&parts),
4727                "-ERR syntax error\r\n",
4728                "SHUTDOWN {flags:?} was accepted"
4729            );
4730            assert!(!f.server.stopping(), "SHUTDOWN {flags:?} stopped anyway");
4731        }
4732    }
4733
4734    /// `ABORT` has nothing to call off, ever.
4735    ///
4736    /// A shutdown here is decided and done inside one turn of the loop, so
4737    /// there is no window in which one is in progress. That makes Redis's
4738    /// message for a cancel with nothing to cancel the right answer every time
4739    /// rather than only when nothing happens to be pending. Two `ABORT`s is
4740    /// still one `ABORT`, which is what 8.10.1 does.
4741    #[test]
4742    fn shutdown_abort_never_has_anything_to_abort() {
4743        let mut f = Fixture::new();
4744        for parts in [
4745            &[b"SHUTDOWN".as_slice(), b"ABORT"][..],
4746            &[b"SHUTDOWN", b"ABORT", b"ABORT"],
4747        ] {
4748            assert_eq!(f.run(parts), "-ERR No shutdown in progress.\r\n");
4749            assert!(!f.server.stopping(), "an abort stopped the server");
4750        }
4751    }
4752
4753    /// A fixture whose server writes into a directory of its own.
4754    ///
4755    /// Every test here really writes files, because the whole point of the
4756    /// command is the files and a backup that is only a state machine would
4757    /// pass a test suite and fail the first person who tried to restore one.
4758    /// The directory carries the test's name so that the suite can run its
4759    /// tests in parallel the way it always does.
4760    struct Backups {
4761        f: Fixture,
4762        dir: PathBuf,
4763    }
4764
4765    impl Backups {
4766        fn new(name: &str) -> Backups {
4767            let dir = std::env::temp_dir().join(format!("yo-backup-{name}-{}", std::process::id()));
4768            let _ = std::fs::remove_dir_all(&dir);
4769            std::fs::create_dir_all(&dir).expect("could not make a temporary directory");
4770            let mut f = Fixture::new();
4771            f.server.set_dir(dir.clone());
4772            Backups { f, dir }
4773        }
4774
4775        fn run(&mut self, parts: &[&[u8]]) -> String {
4776            self.f.run(parts)
4777        }
4778
4779        /// The names in `backupdir`, sorted, so a test can say what is on disk.
4780        fn files(&self) -> Vec<String> {
4781            let mut names: Vec<String> = match std::fs::read_dir(self.dir.join("backupdir")) {
4782                Ok(entries) => entries
4783                    .filter_map(|e| e.ok())
4784                    .map(|e| e.file_name().to_string_lossy().into_owned())
4785                    .collect(),
4786                Err(_) => Vec::new(),
4787            };
4788            names.sort();
4789            names
4790        }
4791
4792        fn read(&self, name: &str) -> Vec<u8> {
4793            std::fs::read(self.dir.join("backupdir").join(name)).expect("could not read")
4794        }
4795    }
4796
4797    impl Drop for Backups {
4798        fn drop(&mut self) {
4799            let _ = std::fs::remove_dir_all(&self.dir);
4800        }
4801    }
4802
4803    /// The four states and the moves between them, in the order a client walks
4804    /// them, with the files checked at every step.
4805    #[test]
4806    fn backup_walks_the_states_the_reference_walks() {
4807        let mut b = Backups::new("states");
4808        let status = |b: &mut Backups| b.run(&[b"BACKUP", b"STATUS"]);
4809
4810        assert!(status(&mut b).contains("idle"));
4811        assert!(b.files().is_empty(), "an idle server has written a backup");
4812
4813        assert_eq!(b.run(&[b"BACKUP", b"START"]), "+OK\r\n");
4814        assert!(status(&mut b).contains("incrementing"));
4815        assert_eq!(b.files(), ["appendonly.aof.1.base.rdb"]);
4816
4817        assert_eq!(b.run(&[b"BACKUP", b"SEAL"]), "+OK\r\n");
4818        assert!(status(&mut b).contains("sealed"));
4819        assert_eq!(
4820            b.files(),
4821            [
4822                "appendonly.aof.1.base.rdb",
4823                "appendonly.aof.1.incr.aof",
4824                "appendonly.aof.manifest",
4825            ]
4826        );
4827
4828        assert_eq!(b.run(&[b"BACKUP", b"CLEANUP"]), "+OK\r\n");
4829        assert!(status(&mut b).contains("idle"));
4830        assert!(b.files().is_empty(), "cleanup left something behind");
4831    }
4832
4833    /// Every move that is refused, in the reference's words.
4834    #[test]
4835    fn backup_refuses_the_moves_the_reference_refuses() {
4836        let mut b = Backups::new("refusals");
4837
4838        assert_eq!(
4839            b.run(&[b"BACKUP", b"SEAL"]),
4840            "-ERR No backup ready to seal (must be in the incrementing state)\r\n"
4841        );
4842        assert_eq!(
4843            b.run(&[b"BACKUP", b"ABORT"]),
4844            "-ERR No backup in progress\r\n"
4845        );
4846        // Cleanup from idle is not an error, it is a way of saying there was
4847        // nothing to clean up.
4848        assert_eq!(b.run(&[b"BACKUP", b"CLEANUP"]), "+OK\r\n");
4849
4850        b.run(&[b"BACKUP", b"START"]);
4851        assert_eq!(
4852            b.run(&[b"BACKUP", b"START"]),
4853            "-ERR A backup is already in progress, ABORT it first\r\n"
4854        );
4855        assert_eq!(
4856            b.run(&[b"BACKUP", b"CLEANUP"]),
4857            "-ERR Backup is in progress\r\n"
4858        );
4859
4860        b.run(&[b"BACKUP", b"SEAL"]);
4861        assert_eq!(
4862            b.run(&[b"BACKUP", b"START"]),
4863            "-ERR A sealed backup exists, CLEANUP it first\r\n"
4864        );
4865        assert_eq!(
4866            b.run(&[b"BACKUP", b"SEAL"]),
4867            "-ERR No backup ready to seal (must be in the incrementing state)\r\n"
4868        );
4869        assert_eq!(
4870            b.run(&[b"BACKUP", b"ABORT"]),
4871            "-ERR No backup in progress\r\n"
4872        );
4873    }
4874
4875    /// An abort takes the base file away and leaves a state saying who did it.
4876    ///
4877    /// The next backup takes the next sequence number rather than reusing the
4878    /// one whose files were just thrown away, so a directory somebody copied a
4879    /// half finished backup out of cannot end up with two different files under
4880    /// one name.
4881    #[test]
4882    fn backup_abort_removes_the_file_and_says_who_did_it() {
4883        let mut b = Backups::new("abort");
4884        b.run(&[b"BACKUP", b"START"]);
4885        assert_eq!(b.run(&[b"BACKUP", b"ABORT"]), "+OK\r\n");
4886
4887        let status = b.run(&[b"BACKUP", b"STATUS"]);
4888        assert!(status.contains("failed"), "{status}");
4889        assert!(status.contains("aborted by user"), "{status}");
4890        assert!(b.files().is_empty(), "abort left the base file behind");
4891        assert_eq!(b.run(&[b"BACKUP", b"LIST"]), "*0\r\n");
4892
4893        // A start from failed works, and is the second backup.
4894        assert_eq!(b.run(&[b"BACKUP", b"START"]), "+OK\r\n");
4895        assert_eq!(b.files(), ["appendonly.aof.2.base.rdb"]);
4896        let status = b.run(&[b"BACKUP", b"STATUS"]);
4897        assert!(status.contains("incrementing"), "{status}");
4898        assert!(!status.contains("aborted"), "the old error was kept");
4899    }
4900
4901    /// `LIST` names nothing, then one file, then three, and they are absolute.
4902    #[test]
4903    fn backup_list_names_the_files_that_are_pinned_so_far() {
4904        let mut b = Backups::new("list");
4905        assert_eq!(b.run(&[b"BACKUP", b"LIST"]), "*0\r\n");
4906
4907        b.run(&[b"BACKUP", b"START"]);
4908        let base = b.dir.join("backupdir").join("appendonly.aof.1.base.rdb");
4909        let base = base.to_string_lossy().into_owned();
4910        assert_eq!(
4911            b.run(&[b"BACKUP", b"LIST"]),
4912            format!("*1\r\n${}\r\n{base}\r\n", base.len())
4913        );
4914
4915        b.run(&[b"BACKUP", b"SEAL"]);
4916        let listed = b.run(&[b"BACKUP", b"LIST"]);
4917        assert!(listed.starts_with("*3\r\n"), "{listed}");
4918        // The order is the manifest's order, base then incremental then the
4919        // manifest itself, which is the order a restore needs them in.
4920        let names: Vec<&str> = listed
4921            .lines()
4922            .filter(|l| l.starts_with('/') || l.contains(":\\"))
4923            .collect();
4924        assert_eq!(names.len(), 3, "{listed}");
4925        assert!(names[0].ends_with("appendonly.aof.1.base.rdb"), "{listed}");
4926        assert!(names[1].ends_with("appendonly.aof.1.incr.aof"), "{listed}");
4927        assert!(names[2].ends_with("appendonly.aof.manifest"), "{listed}");
4928    }
4929
4930    /// The base file is the dataset as it was at `START` and not at `SEAL`.
4931    ///
4932    /// That is D-46 and it is the one thing about this a client can notice, so
4933    /// it is pinned here rather than left to be discovered by whoever restores
4934    /// one. The incremental file is empty for the same reason: there is no
4935    /// append only log underneath this server to copy the writes in between out
4936    /// of.
4937    #[test]
4938    fn a_backup_holds_the_dataset_as_it_was_at_start() {
4939        let mut b = Backups::new("contents");
4940        b.run(&[b"SET", b"bk", b"v1"]);
4941        b.run(&[b"BACKUP", b"START"]);
4942        b.run(&[b"SET", b"bk", b"v2"]);
4943        b.run(&[b"BACKUP", b"SEAL"]);
4944
4945        let base = b.read("appendonly.aof.1.base.rdb");
4946        assert!(base.starts_with(b"REDIS"), "not an RDB file");
4947        assert!(base.windows(2).any(|w| w == b"v1"), "the value is missing");
4948        assert!(
4949            !base.windows(2).any(|w| w == b"v2"),
4950            "the base file moved on after START"
4951        );
4952        // The aux field a loader acts on, and the one that says this file is
4953        // the base of an append only file rather than a standalone dump. Its
4954        // value is the one byte string 1, which the encoder writes as an
4955        // integer the way a real server writes it.
4956        let at = base
4957            .windows(8)
4958            .position(|w| w == b"aof-base")
4959            .expect("no aof-base aux field");
4960        assert_eq!(&base[at + 8..at + 10], b"\xc0\x01", "{:?}", &base[at..]);
4961
4962        assert!(b.read("appendonly.aof.1.incr.aof").is_empty());
4963        assert_eq!(
4964            String::from_utf8(b.read("appendonly.aof.manifest")).expect("the manifest is text"),
4965            "file appendonly.aof.1.base.rdb seq 1 type b\n\
4966             file appendonly.aof.1.incr.aof seq 1 type i startoffset 0 endoffset 0\n"
4967        );
4968    }
4969
4970    /// `STATUS` is a map of four pairs on RESP3 and the same pairs flat on
4971    /// RESP2, which is what every other map shaped reply in this server does.
4972    #[test]
4973    fn backup_status_is_a_map_on_resp3_and_a_flat_array_on_resp2() {
4974        let mut b = Backups::new("status");
4975        b.f.server.set_clock_ms(1_700_000_000_000);
4976
4977        assert_eq!(
4978            b.run(&[b"BACKUP", b"STATUS"]),
4979            "*8\r\n$5\r\nstate\r\n$4\r\nidle\r\n$5\r\nerror\r\n$0\r\n\r\n\
4980             $10\r\nstart_time\r\n:0\r\n$8\r\nend_time\r\n:0\r\n"
4981        );
4982
4983        b.f.out = Out::new(Proto::Resp3);
4984        b.run(&[b"BACKUP", b"START"]);
4985        assert_eq!(
4986            b.run(&[b"BACKUP", b"STATUS"]),
4987            "%4\r\n$5\r\nstate\r\n$12\r\nincrementing\r\n$5\r\nerror\r\n$0\r\n\r\n\
4988             $10\r\nstart_time\r\n:1700000000\r\n$8\r\nend_time\r\n:0\r\n"
4989        );
4990
4991        b.run(&[b"BACKUP", b"SEAL"]);
4992        let sealed = b.run(&[b"BACKUP", b"STATUS"]);
4993        assert!(sealed.contains("end_time\r\n:1700000000"), "{sealed}");
4994    }
4995
4996    /// A sealed backup that nobody cleans up goes away on its own once
4997    /// `backup-sealed-ttl` seconds have passed since the seal.
4998    #[test]
4999    fn a_sealed_backup_is_swept_away_after_the_timeout() {
5000        let mut b = Backups::new("ttl");
5001        b.f.server.set_clock_ms(1_000_000);
5002        assert_eq!(
5003            b.run(&[b"CONFIG", b"SET", b"backup-sealed-ttl", b"60"]),
5004            "+OK\r\n"
5005        );
5006        b.run(&[b"BACKUP", b"START"]);
5007        b.run(&[b"BACKUP", b"SEAL"]);
5008
5009        // A minute short of the deadline, nothing happens.
5010        b.f.server.set_clock_ms(1_000_000 + 59_000);
5011        b.f.server.backup_expire();
5012        assert!(b.run(&[b"BACKUP", b"STATUS"]).contains("sealed"));
5013        assert_eq!(b.files().len(), 3);
5014
5015        b.f.server.set_clock_ms(1_000_000 + 60_000);
5016        b.f.server.backup_expire();
5017        let status = b.run(&[b"BACKUP", b"STATUS"]);
5018        assert!(status.contains("idle"), "{status}");
5019        assert!(b.files().is_empty(), "the timeout left the files behind");
5020
5021        // Zero is the default and means a sealed backup is kept for ever.
5022        b.run(&[b"CONFIG", b"SET", b"backup-sealed-ttl", b"0"]);
5023        b.run(&[b"BACKUP", b"START"]);
5024        b.run(&[b"BACKUP", b"SEAL"]);
5025        b.f.server.set_clock_ms(9_000_000_000);
5026        b.f.server.backup_expire();
5027        assert!(b.run(&[b"BACKUP", b"STATUS"]).contains("sealed"));
5028    }
5029
5030    /// The three settings around the command, read and written the way 8.10.1
5031    /// reads and writes them.
5032    #[test]
5033    fn the_backup_settings_behave_the_way_the_reference_does() {
5034        let mut b = Backups::new("config");
5035        let dir = b.dir.to_string_lossy().into_owned();
5036
5037        assert_eq!(
5038            b.run(&[b"CONFIG", b"GET", b"dir"]),
5039            format!("*2\r\n$3\r\ndir\r\n${}\r\n{dir}\r\n", dir.len())
5040        );
5041        assert_eq!(
5042            b.run(&[b"CONFIG", b"GET", b"backupdirname"]),
5043            "*2\r\n$13\r\nbackupdirname\r\n$9\r\nbackupdir\r\n"
5044        );
5045        assert_eq!(
5046            b.run(&[b"CONFIG", b"GET", b"backup-sealed-ttl"]),
5047            "*2\r\n$17\r\nbackup-sealed-ttl\r\n$1\r\n0\r\n"
5048        );
5049
5050        // `dir` is a protected config, so it is refused even for the value it
5051        // already holds, and `backupdirname` is immutable.
5052        assert_eq!(
5053            b.run(&[b"CONFIG", b"SET", b"dir", dir.as_bytes()]),
5054            "-ERR CONFIG SET failed (possibly related to argument 'dir') - can't set protected config\r\n"
5055        );
5056        assert_eq!(
5057            b.run(&[b"CONFIG", b"SET", b"backupdirname", b"other"]),
5058            "-ERR CONFIG SET failed (possibly related to argument 'backupdirname') - can't set immutable config\r\n"
5059        );
5060        assert!(
5061            b.run(&[b"CONFIG", b"SET", b"backup-sealed-ttl", b"abc"])
5062                .contains("argument couldn't be parsed into an integer")
5063        );
5064        assert!(
5065            b.run(&[b"CONFIG", b"SET", b"backup-sealed-ttl", b"-1"])
5066                .contains("argument must be between 0 and 9223372036854775807 inclusive")
5067        );
5068    }
5069
5070    /// The help text, which has `HELP` in it twice because the reference's does.
5071    #[test]
5072    fn backup_help_is_the_text_the_reference_sends() {
5073        let mut f = Fixture::new();
5074        let help = f.run(&[b"BACKUP", b"HELP"]);
5075        assert!(help.starts_with("*17\r\n"), "{help}");
5076        assert!(
5077            help.contains("+BACKUP <subcommand> [<arg> [value] [opt] ...]. Subcommands are:\r\n")
5078        );
5079        assert!(help.contains("+    Start a new backup into the configured 'backupdirname'.\r\n"));
5080        assert!(help.contains("+    Freeze the current backup (BASE + INCR + manifest).\r\n"));
5081        assert!(help.contains("+    Return this help.\r\n+HELP\r\n+    Print this help.\r\n"));
5082    }
5083
5084    /// What a mistyped `BACKUP` gets told.
5085    ///
5086    /// The arity error names `backup` where the reference names `backup|start`,
5087    /// which is D-46: the table reports one arity for the container the way the
5088    /// reference does, and the per subcommand table that would carry the better
5089    /// name is not built yet. Every subcommand is exactly two words, so nothing
5090    /// legal is refused by it.
5091    #[test]
5092    fn backup_refuses_what_it_cannot_read() {
5093        let mut f = Fixture::new();
5094        assert_eq!(
5095            f.run(&[b"BACKUP"]),
5096            "-ERR wrong number of arguments for 'backup' command\r\n"
5097        );
5098        assert_eq!(
5099            f.run(&[b"BACKUP", b"START", b"x"]),
5100            "-ERR wrong number of arguments for 'backup' command\r\n"
5101        );
5102        assert_eq!(
5103            f.run(&[b"BACKUP", b"NOPE"]),
5104            "-ERR unknown subcommand 'NOPE'. Try BACKUP HELP.\r\n"
5105        );
5106    }
5107
5108    #[test]
5109    fn the_command_counter_counts_every_command_including_the_bad_ones() {
5110        let mut f = Fixture::new();
5111        f.run(&[b"PING"]);
5112        f.run(&[b"NOPE"]);
5113        f.run(&[b"GET"]);
5114        assert_eq!(f.server.totals().commands, 3);
5115    }
5116
5117    #[test]
5118    fn what_a_thread_marked_is_taken_by_the_maintenance_turn() {
5119        let mut server = Server::new();
5120        server.set_threads(2);
5121        // A fresh server has every database on the turn's list, so start from
5122        // nothing to see the one mark arrive.
5123        server.mine().turn.store(0, Relaxed);
5124        server.locals[1].mark(1 << 9);
5125        server.collect_marks();
5126        assert!(server.mine().wanted(9));
5127        // And taken once rather than left to be taken again next turn.
5128        assert_eq!(server.locals[1].dirty.load(Relaxed), 0);
5129    }
5130
5131    #[test]
5132    fn what_two_threads_counted_is_added_up_when_info_asks() {
5133        let mut server = Server::new();
5134        server.set_threads(2);
5135        // Written into the two sets by hand, because what is under test is the
5136        // adding up and not the claiming, and one test thread can only ever
5137        // claim one set.
5138        let ping = lookup(b"PING").expect("PING is a command");
5139        for (at, calls) in [(0, 2), (1, 3)] {
5140            let counters = &server.locals[at];
5141            for _ in 0..calls {
5142                counters.stats.commands.bump();
5143                counters.cmdstats.at(ping).calls.bump();
5144            }
5145            counters.stats.opened();
5146        }
5147        assert_eq!(server.totals().commands, 5);
5148        assert_eq!(server.totals().clients, 2);
5149        assert_eq!(server.totals().connections, 2);
5150        let rows: Vec<_> = server.command_stats().collect();
5151        assert_eq!(rows.len(), 1);
5152        assert_eq!(rows[0].0, "ping");
5153        assert_eq!(rows[0].1.calls, 5);
5154        // A reset takes the totals and leaves the open connections, which are
5155        // still open.
5156        server.reset_stats();
5157        assert_eq!(server.totals().commands, 0);
5158        assert_eq!(server.totals().connections, 0);
5159        assert_eq!(server.totals().clients, 2);
5160    }
5161
5162    #[test]
5163    fn the_parked_count_says_what_the_waiter_list_says() {
5164        let mut f = Fixture::new();
5165        assert_eq!(f.server.parked(), 0);
5166        for client in 1..=3u64 {
5167            f.session = Session::new(client);
5168            assert_eq!(f.flow(&[b"BLPOP", b"q", b"0"]).0, Flow::Block);
5169        }
5170        assert_eq!(f.server.parked(), 3);
5171        assert_eq!(f.server.waiters().len(), 3);
5172
5173        // The three ways the list gets shorter, each of which has to move the
5174        // number with it, because a number left behind is either a walk of the
5175        // list that never happens or one that runs off the end of it.
5176        f.server.forget_waiters(2);
5177        assert_eq!(f.server.parked(), f.server.waiters().len());
5178        f.server.forget_waiters(1);
5179        assert_eq!(f.server.parked(), f.server.waiters().len());
5180        f.run(&[b"RPUSH", b"q", b"v"]);
5181        let mut out = Out::new(Proto::Resp2);
5182        assert!(f.server.serve_waiter(3, 0, &mut out));
5183        f.server.forget_waiters(3);
5184        assert_eq!(f.server.parked(), 0);
5185        assert!(f.server.waiters().is_empty());
5186    }
5187
5188    #[test]
5189    fn a_set_goes_from_bytes_to_bytes() {
5190        let mut f = Fixture::new();
5191        assert_eq!(f.run(&[b"SADD", b"s", b"a", b"b", b"c"]), ":3\r\n");
5192        assert_eq!(f.run(&[b"SADD", b"s", b"b", b"d"]), ":1\r\n");
5193        assert_eq!(f.run(&[b"SCARD", b"s"]), ":4\r\n");
5194        assert_eq!(f.run(&[b"SISMEMBER", b"s", b"a"]), ":1\r\n");
5195        assert_eq!(f.run(&[b"SISMEMBER", b"s", b"z"]), ":0\r\n");
5196        assert_eq!(f.run(&[b"TYPE", b"s"]), "+set\r\n");
5197        assert_eq!(
5198            f.run(&[b"SMISMEMBER", b"s", b"a", b"z", b"d"]),
5199            "*3\r\n:1\r\n:0\r\n:1\r\n"
5200        );
5201        assert_eq!(f.run(&[b"SREM", b"s", b"a", b"z"]), ":1\r\n");
5202        assert_eq!(f.run(&[b"SCARD", b"s"]), ":3\r\n");
5203    }
5204
5205    #[test]
5206    fn a_set_command_at_a_key_that_is_not_there_answers_empty() {
5207        let mut f = Fixture::new();
5208        assert_eq!(f.run(&[b"SCARD", b"nope"]), ":0\r\n");
5209        assert_eq!(f.run(&[b"SISMEMBER", b"nope", b"a"]), ":0\r\n");
5210        assert_eq!(f.run(&[b"SREM", b"nope", b"a"]), ":0\r\n");
5211        assert_eq!(f.run(&[b"SMEMBERS", b"nope"]), "*0\r\n");
5212        assert_eq!(
5213            f.run(&[b"SMISMEMBER", b"nope", b"a", b"b"]),
5214            "*2\r\n:0\r\n:0\r\n"
5215        );
5216        assert_eq!(f.run(&[b"DBSIZE"]), ":0\r\n", "and made nothing");
5217    }
5218
5219    #[test]
5220    fn smembers_answers_a_set_on_resp3_and_an_array_on_resp2() {
5221        // Not cosmetic. A RESP3 client that gets a `~` hands the caller a set
5222        // and one that gets a `*` hands it a list, without either of them being
5223        // told which command was sent.
5224        let mut f = Fixture::new();
5225        f.run(&[b"SADD", b"s", b"one"]);
5226        assert_eq!(f.run(&[b"SMEMBERS", b"s"]), "*1\r\n$3\r\none\r\n");
5227
5228        f.run(&[b"HELLO", b"3"]);
5229        assert_eq!(f.run(&[b"SMEMBERS", b"s"]), "~1\r\n$3\r\none\r\n");
5230    }
5231
5232    #[test]
5233    fn an_integer_member_comes_back_as_the_digits_it_never_stored() {
5234        // An intset holds the number, so these digits exist for the first time
5235        // in the reply buffer.
5236        let mut f = Fixture::new();
5237        f.run(&[b"SADD", b"s", b"42"]);
5238        assert_eq!(f.run(&[b"SMEMBERS", b"s"]), "*1\r\n$2\r\n42\r\n");
5239        assert_eq!(f.run(&[b"SISMEMBER", b"s", b"42"]), ":1\r\n");
5240        assert_eq!(
5241            f.run(&[b"SISMEMBER", b"s", b"042"]),
5242            ":0\r\n",
5243            "the member is the bytes and not the number they parse to"
5244        );
5245    }
5246
5247    #[test]
5248    fn the_wrong_command_at_the_wrong_type_says_so_both_ways() {
5249        let mut f = Fixture::new();
5250        f.run(&[b"SET", b"str", b"v"]);
5251        f.run(&[b"SADD", b"set", b"a"]);
5252
5253        let wrong = "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n";
5254        assert_eq!(f.run(&[b"SADD", b"str", b"a"]), wrong);
5255        assert_eq!(f.run(&[b"SCARD", b"str"]), wrong);
5256        assert_eq!(f.run(&[b"SMEMBERS", b"str"]), wrong);
5257        assert_eq!(f.run(&[b"SMISMEMBER", b"str", b"a"]), wrong);
5258        assert_eq!(f.run(&[b"GET", b"set"]), wrong);
5259        assert_eq!(f.run(&[b"APPEND", b"set", b"x"]), wrong);
5260        assert_eq!(f.run(&[b"INCR", b"set"]), wrong);
5261        assert_eq!(f.run(&[b"STRLEN", b"set"]), wrong);
5262
5263        // MGET is the one that does not, because Redis gives nil for the odd
5264        // key out rather than failing the good keys next to it.
5265        assert_eq!(
5266            f.run(&[b"MGET", b"str", b"set", b"nope"]),
5267            "*3\r\n$1\r\nv\r\n$-1\r\n$-1\r\n"
5268        );
5269        // And plain SET overwrites any type, which takes the body with it.
5270        assert_eq!(f.run(&[b"SET", b"set", b"now a string"]), "+OK\r\n");
5271        assert_eq!(f.run(&[b"TYPE", b"set"]), "+string\r\n");
5272    }
5273
5274    #[test]
5275    fn a_wrongtype_leaves_nothing_half_written() {
5276        // SMISMEMBER writes an array header and then one reply per member, so
5277        // it is the first command in the server that could get a header out in
5278        // front of an error if it checked its key in the wrong order.
5279        let mut f = Fixture::new();
5280        f.run(&[b"SET", b"k", b"v"]);
5281        let reply = f.run(&[b"SMISMEMBER", b"k", b"a", b"b"]);
5282        assert!(reply.starts_with("-WRONGTYPE"), "got {reply}");
5283        assert!(!reply.contains('*'), "an array header went out in front");
5284    }
5285
5286    #[test]
5287    fn emptying_a_set_takes_the_key_with_it() {
5288        let mut f = Fixture::new();
5289        f.run(&[b"SADD", b"s", b"a", b"b"]);
5290        assert_eq!(f.run(&[b"DBSIZE"]), ":1\r\n");
5291        assert_eq!(f.run(&[b"SREM", b"s", b"a", b"b"]), ":2\r\n");
5292        assert_eq!(f.run(&[b"EXISTS", b"s"]), ":0\r\n");
5293        assert_eq!(f.run(&[b"TYPE", b"s"]), "+none\r\n");
5294        assert_eq!(f.run(&[b"DBSIZE"]), ":0\r\n");
5295    }
5296
5297    /// Pull the cursor and the members out of one `SSCAN` reply.
5298    ///
5299    /// Crude on purpose. A test that walked a set through a real client would
5300    /// be testing the client, and what these tests are about is the shape of
5301    /// the bytes and the fact that a walk sees every member once.
5302    fn split_scan(reply: &str) -> (String, Vec<String>) {
5303        let mut lines = reply.split("\r\n");
5304        assert_eq!(lines.next(), Some("*2"), "got {reply}");
5305        lines.next().expect("the cursor header");
5306        let cursor = lines.next().expect("the cursor").to_owned();
5307        let header = lines.next().expect("the member header");
5308        let n: usize = header[1..].parse().expect("a member count");
5309        let mut members = Vec::with_capacity(n);
5310        for _ in 0..n {
5311            lines.next().expect("a member header");
5312            members.push(lines.next().expect("a member").to_owned());
5313        }
5314        (cursor, members)
5315    }
5316
5317    #[test]
5318    fn popping_takes_a_member_off_the_set_and_hands_it_back() {
5319        let mut f = Fixture::new();
5320        f.run(&[b"SADD", b"s", b"a", b"b", b"c", b"d"]);
5321
5322        let one = f.run(&[b"SPOP", b"s"]);
5323        assert!(
5324            ["$1\r\na\r\n", "$1\r\nb\r\n", "$1\r\nc\r\n", "$1\r\nd\r\n"].contains(&one.as_str()),
5325            "got {one}"
5326        );
5327        assert_eq!(f.run(&[b"SCARD", b"s"]), ":3\r\n");
5328
5329        // A count takes that many, and the last one takes the key with it.
5330        let (_, rest) = ("", f.run(&[b"SPOP", b"s", b"3"]));
5331        assert!(rest.starts_with("*3\r\n"), "got {rest}");
5332        assert_eq!(f.run(&[b"EXISTS", b"s"]), ":0\r\n");
5333        // And a pop at a key that is not there is a nil, not an empty bulk.
5334        assert_eq!(f.run(&[b"SPOP", b"s"]), "$-1\r\n");
5335        assert_eq!(f.run(&[b"SPOP", b"s", b"2"]), "*0\r\n");
5336    }
5337
5338    #[test]
5339    fn the_two_draws_disagree_about_the_reply_type_and_they_are_right_to() {
5340        // The one place in the server where the reply type carries something
5341        // the command name does not. SPOP's members are distinct so a RESP3
5342        // client can build a set out of them. SRANDMEMBER with a negative count
5343        // can hand back the same member three times, and a set would lose two.
5344        let mut f = Fixture::new();
5345        f.run(&[b"HELLO", b"3"]);
5346        f.run(&[b"SADD", b"s", b"a", b"b", b"c"]);
5347
5348        assert!(f.run(&[b"SPOP", b"s", b"2"]).starts_with("~2\r\n"));
5349        // And a positive count is an array too, since Redis makes it one.
5350        assert!(f.run(&[b"SRANDMEMBER", b"s", b"1"]).starts_with("*1\r\n"));
5351
5352        // A negative count against a set of one is where the difference bites:
5353        // the same member three times, which is a three element reply and would
5354        // have been a one element reply if it had gone out as a set.
5355        f.run(&[b"SADD", b"one", b"z"]);
5356        assert_eq!(
5357            f.run(&[b"SRANDMEMBER", b"one", b"-3"]),
5358            "*3\r\n$1\r\nz\r\n$1\r\nz\r\n$1\r\nz\r\n"
5359        );
5360    }
5361
5362    #[test]
5363    fn drawing_a_member_removes_nothing_and_says_nil_at_a_missing_key() {
5364        let mut f = Fixture::new();
5365        f.run(&[b"SADD", b"s", b"only"]);
5366        assert_eq!(f.run(&[b"SRANDMEMBER", b"s"]), "$4\r\nonly\r\n");
5367        assert_eq!(f.run(&[b"SRANDMEMBER", b"s"]), "$4\r\nonly\r\n");
5368        assert_eq!(f.run(&[b"SCARD", b"s"]), ":1\r\n");
5369
5370        assert_eq!(f.run(&[b"SRANDMEMBER", b"nope"]), "$-1\r\n");
5371        // The count form answers an empty array rather than a nil, which is the
5372        // pair of answers Redis gives and is not the pair it looks like.
5373        assert_eq!(f.run(&[b"SRANDMEMBER", b"nope", b"3"]), "*0\r\n");
5374        assert_eq!(f.run(&[b"SRANDMEMBER", b"nope", b"-3"]), "*0\r\n");
5375        // Asking for more than is there answers all of it once and not padding.
5376        assert_eq!(f.run(&[b"SRANDMEMBER", b"s", b"9"]), "*1\r\n$4\r\nonly\r\n");
5377    }
5378
5379    #[test]
5380    fn a_pop_count_that_is_not_a_positive_number_says_so() {
5381        let mut f = Fixture::new();
5382        f.run(&[b"SADD", b"s", b"a"]);
5383        let bad = "-ERR value is out of range, must be positive\r\n";
5384        assert_eq!(f.run(&[b"SPOP", b"s", b"-1"]), bad);
5385        assert_eq!(f.run(&[b"SPOP", b"s", b"abc"]), bad);
5386        assert_eq!(f.run(&[b"SCARD", b"s"]), ":1\r\n", "and took nothing");
5387        // Zero is allowed and is a real answer rather than an error.
5388        assert_eq!(f.run(&[b"SPOP", b"s", b"0"]), "*0\r\n");
5389        assert_eq!(f.run(&[b"EXISTS", b"s"]), ":1\r\n");
5390    }
5391
5392    #[test]
5393    fn a_scan_walks_a_set_of_any_size_exactly_once() {
5394        let mut f = Fixture::new();
5395        let members: Vec<Vec<u8>> = (0..300).map(|i| format!("m{i}").into_bytes()).collect();
5396        let args: Vec<&[u8]> = [&b"SADD"[..], &b"s"[..]]
5397            .into_iter()
5398            .chain(members.iter().map(Vec::as_slice))
5399            .collect();
5400        f.run(&args);
5401
5402        let mut seen = Vec::new();
5403        let mut cursor = "0".to_owned();
5404        loop {
5405            let reply = f.run(&[b"SSCAN", b"s", cursor.as_bytes()]);
5406            let (next, got) = split_scan(&reply);
5407            seen.extend(got);
5408            cursor = next;
5409            if cursor == "0" {
5410                break;
5411            }
5412        }
5413        seen.sort();
5414        seen.dedup();
5415        assert_eq!(seen.len(), 300, "a walk saw a member twice or missed one");
5416
5417        // A set small enough to be a listpack answers in one call whatever
5418        // cursor it was handed, which is what Redis does for that encoding.
5419        f.run(&[b"SADD", b"small", b"a", b"b", b"c"]);
5420        let (cursor, got) = split_scan(&f.run(&[b"SSCAN", b"small", b"0", b"COUNT", b"1"]));
5421        assert_eq!(cursor, "0");
5422        assert_eq!(got.len(), 3);
5423        // And a key that is not there is a finished scan of nothing.
5424        assert_eq!(f.run(&[b"SSCAN", b"nope", b"0"]), "*2\r\n$1\r\n0\r\n*0\r\n");
5425    }
5426
5427    #[test]
5428    fn a_scan_takes_match_and_count_and_refuses_anything_else() {
5429        let mut f = Fixture::new();
5430        f.run(&[b"SADD", b"s", b"aa", b"ab", b"ba", b"12", b"13"]);
5431
5432        let (_, got) = split_scan(&f.run(&[b"SSCAN", b"s", b"0", b"MATCH", b"a*"]));
5433        let mut got = got;
5434        got.sort();
5435        assert_eq!(got, ["aa", "ab"]);
5436
5437        // An integer member has no digits stored anywhere, so MATCH is the one
5438        // place a scan pays to write some.
5439        let (_, got) = split_scan(&f.run(&[b"SSCAN", b"s", b"0", b"MATCH", b"1?"]));
5440        let mut got = got;
5441        got.sort();
5442        assert_eq!(got, ["12", "13"]);
5443
5444        assert_eq!(f.run(&[b"SSCAN", b"s", b"abc"]), "-ERR invalid cursor\r\n");
5445        assert_eq!(f.run(&[b"SSCAN", b"s", b"-1"]), "-ERR invalid cursor\r\n");
5446        assert_eq!(
5447            f.run(&[b"SSCAN", b"s", b"0", b"NOPE", b"1"]),
5448            "-ERR syntax error\r\n"
5449        );
5450        // A count under one is a syntax error and not a range error, which is
5451        // the odder of Redis's two answers and the reason it is copied exactly.
5452        assert_eq!(
5453            f.run(&[b"SSCAN", b"s", b"0", b"COUNT", b"0"]),
5454            "-ERR syntax error\r\n"
5455        );
5456    }
5457
5458    #[test]
5459    fn moving_a_member_takes_it_off_one_set_and_puts_it_on_another() {
5460        let mut f = Fixture::new();
5461        f.run(&[b"SADD", b"src", b"a", b"b"]);
5462        f.run(&[b"SADD", b"dst", b"c"]);
5463
5464        assert_eq!(f.run(&[b"SMOVE", b"src", b"dst", b"a"]), ":1\r\n");
5465        assert_eq!(f.run(&[b"SISMEMBER", b"src", b"a"]), ":0\r\n");
5466        assert_eq!(f.run(&[b"SISMEMBER", b"dst", b"a"]), ":1\r\n");
5467        // A member that is not in the source is a zero and moves nothing.
5468        assert_eq!(f.run(&[b"SMOVE", b"src", b"dst", b"zz"]), ":0\r\n");
5469        assert_eq!(f.run(&[b"SCARD", b"dst"]), ":2\r\n");
5470
5471        // A destination that does not exist gets made, and a source that runs
5472        // out goes away.
5473        assert_eq!(f.run(&[b"SMOVE", b"src", b"fresh", b"b"]), ":1\r\n");
5474        assert_eq!(f.run(&[b"EXISTS", b"src"]), ":0\r\n");
5475        assert_eq!(f.run(&[b"SMEMBERS", b"fresh"]), "*1\r\n$1\r\nb\r\n");
5476    }
5477
5478    #[test]
5479    fn moving_checks_the_types_in_the_order_redis_checks_them() {
5480        // Not the order it looks like it should be. A source that is not there
5481        // answers zero without ever looking at the destination, so this is a
5482        // zero and not a WRONGTYPE even though the destination is a string.
5483        let mut f = Fixture::new();
5484        f.run(&[b"SET", b"str", b"v"]);
5485        f.run(&[b"SADD", b"set", b"a"]);
5486
5487        let wrong = "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n";
5488        assert_eq!(f.run(&[b"SMOVE", b"nope", b"str", b"a"]), ":0\r\n");
5489        assert_eq!(f.run(&[b"SMOVE", b"str", b"set", b"a"]), wrong);
5490        assert_eq!(f.run(&[b"SMOVE", b"set", b"str", b"a"]), wrong);
5491        assert_eq!(f.run(&[b"SPOP", b"str"]), wrong);
5492        assert_eq!(f.run(&[b"SRANDMEMBER", b"str"]), wrong);
5493        assert_eq!(f.run(&[b"SSCAN", b"str", b"0"]), wrong);
5494        assert_eq!(
5495            f.run(&[b"SISMEMBER", b"set", b"a"]),
5496            ":1\r\n",
5497            "and none of that moved anything"
5498        );
5499    }
5500
5501    #[test]
5502    fn a_scan_leaves_nothing_half_written_when_its_arguments_are_wrong() {
5503        // SSCAN writes an outer array header before it walks, so it is the
5504        // command most likely to get bytes out in front of an error.
5505        let mut f = Fixture::new();
5506        f.run(&[b"SADD", b"s", b"a"]);
5507        for bad in [
5508            &[b"SSCAN".as_slice(), b"s", b"abc"][..],
5509            &[b"SSCAN".as_slice(), b"s", b"0", b"COUNT", b"nope"][..],
5510            &[b"SSCAN".as_slice(), b"s", b"0", b"MATCH"][..],
5511        ] {
5512            let reply = f.run(bad);
5513            assert!(reply.starts_with("-ERR"), "got {reply}");
5514            assert!(!reply.contains('*'), "an array header went out in front");
5515        }
5516    }
5517
5518    #[test]
5519    fn a_hash_writes_reads_and_deletes_its_fields() {
5520        let mut f = Fixture::new();
5521        assert_eq!(f.run(&[b"HSET", b"h", b"a", b"1", b"b", b"2"]), ":2\r\n");
5522        assert_eq!(f.run(&[b"HSET", b"h", b"a", b"9"]), ":0\r\n", "a was there");
5523        assert_eq!(f.run(&[b"HGET", b"h", b"a"]), "$1\r\n9\r\n");
5524        assert_eq!(f.run(&[b"HGET", b"h", b"nope"]), "$-1\r\n");
5525        assert_eq!(f.run(&[b"HGET", b"nokey", b"a"]), "$-1\r\n");
5526        assert_eq!(f.run(&[b"HLEN", b"h"]), ":2\r\n");
5527        assert_eq!(f.run(&[b"HEXISTS", b"h", b"a"]), ":1\r\n");
5528        assert_eq!(f.run(&[b"HEXISTS", b"h", b"nope"]), ":0\r\n");
5529        assert_eq!(f.run(&[b"HSTRLEN", b"h", b"a"]), ":1\r\n");
5530        assert_eq!(f.run(&[b"HSTRLEN", b"h", b"nope"]), ":0\r\n");
5531
5532        // The value the client sent is `9`, so HGET h b must not find the `2`
5533        // that is a value. A search with a step of one would have.
5534        assert_eq!(f.run(&[b"HGET", b"h", b"2"]), "$-1\r\n");
5535
5536        assert_eq!(f.run(&[b"HDEL", b"h", b"a", b"nope"]), ":1\r\n");
5537        assert_eq!(f.run(&[b"HDEL", b"h", b"b"]), ":1\r\n");
5538        assert_eq!(
5539            f.run(&[b"EXISTS", b"h"]),
5540            ":0\r\n",
5541            "and losing the last field lost the key"
5542        );
5543    }
5544
5545    #[test]
5546    fn hgetall_answers_a_map_on_resp3_and_the_same_pairs_flat_on_resp2() {
5547        let mut f = Fixture::new();
5548        f.run(&[b"HSET", b"h", b"a", b"1"]);
5549        assert_eq!(f.run(&[b"HGETALL", b"h"]), "*2\r\n$1\r\na\r\n$1\r\n1\r\n");
5550        assert_eq!(f.run(&[b"HGETALL", b"nokey"]), "*0\r\n");
5551        assert_eq!(f.run(&[b"HKEYS", b"h"]), "*1\r\n$1\r\na\r\n");
5552        assert_eq!(f.run(&[b"HVALS", b"h"]), "*1\r\n$1\r\n1\r\n");
5553        assert_eq!(f.run(&[b"HKEYS", b"nokey"]), "*0\r\n");
5554
5555        f.run(&[b"HELLO", b"3"]);
5556        assert_eq!(f.run(&[b"HGETALL", b"h"]), "%1\r\n$1\r\na\r\n$1\r\n1\r\n");
5557        assert_eq!(
5558            f.run(&[b"HGETALL", b"nokey"]),
5559            "%0\r\n",
5560            "a missing key is the empty hash and never a nil"
5561        );
5562        assert_eq!(
5563            f.run(&[b"HKEYS", b"h"]),
5564            "*1\r\n$1\r\na\r\n",
5565            "and the two that answer one side stay arrays"
5566        );
5567    }
5568
5569    #[test]
5570    fn hmget_answers_once_per_field_and_hmset_answers_ok() {
5571        let mut f = Fixture::new();
5572        assert_eq!(f.run(&[b"HMSET", b"h", b"a", b"1", b"c", b"3"]), "+OK\r\n");
5573        assert_eq!(
5574            f.run(&[b"HMGET", b"h", b"a", b"b", b"c"]),
5575            "*3\r\n$1\r\n1\r\n$-1\r\n$1\r\n3\r\n",
5576            "the reply is positional, so b is a nil and not a gap"
5577        );
5578        assert_eq!(
5579            f.run(&[b"HMGET", b"nokey", b"a", b"b"]),
5580            "*2\r\n$-1\r\n$-1\r\n",
5581            "and a missing key is all nils rather than an empty array"
5582        );
5583
5584        assert_eq!(f.run(&[b"HSETNX", b"h", b"a", b"9"]), ":0\r\n");
5585        assert_eq!(f.run(&[b"HSETNX", b"h", b"z", b"9"]), ":1\r\n");
5586        assert_eq!(f.run(&[b"HGET", b"h", b"a"]), "$1\r\n1\r\n");
5587    }
5588
5589    #[test]
5590    fn a_hash_counts_up_and_says_so_when_it_cannot() {
5591        let mut f = Fixture::new();
5592        assert_eq!(f.run(&[b"HINCRBY", b"h", b"n", b"5"]), ":5\r\n");
5593        assert_eq!(f.run(&[b"HINCRBY", b"h", b"n", b"-7"]), ":-2\r\n");
5594        assert_eq!(f.run(&[b"HGET", b"h", b"n"]), "$2\r\n-2\r\n");
5595        assert_eq!(
5596            f.run(&[b"HINCRBYFLOAT", b"h", b"f", b"10.5"]),
5597            "$4\r\n10.5\r\n",
5598            "a bulk string and not a double, on both protocols"
5599        );
5600
5601        f.run(&[b"HSET", b"h", b"s", b"words"]);
5602        let bad = f.run(&[b"HINCRBY", b"h", b"s", b"1"]);
5603        assert!(
5604            bad.starts_with("-ERR hash value is not an integer"),
5605            "{bad}"
5606        );
5607        let bad = f.run(&[b"HINCRBY", b"h", b"n", b"nope"]);
5608        assert!(
5609            bad.starts_with("-ERR value is not an integer"),
5610            "a bad argument is not yet a hash value, {bad}"
5611        );
5612        assert_eq!(
5613            f.run(&[b"HGET", b"h", b"s"]),
5614            "$5\r\nwords\r\n",
5615            "and neither of them wrote anything"
5616        );
5617    }
5618
5619    #[test]
5620    fn a_hash_scan_walks_every_pair_once_and_novalues_drops_half_of_it() {
5621        // Fourteen minutes under Miri at five hundred, which was the slowest
5622        // test in this crate that was not about megabytes. What the count has
5623        // to be is more than one page of the cursor, and the count below is
5624        // thirty two, so ninety six is three pages and asks the same question.
5625        let fields = if cfg!(miri) { 96 } else { 500 };
5626        let mut f = Fixture::new();
5627        for i in 0..fields {
5628            let field = format!("field-{i}");
5629            let value = format!("value-{i}");
5630            f.run(&[b"HSET", b"h", field.as_bytes(), value.as_bytes()]);
5631        }
5632
5633        let mut seen: Vec<String> = Vec::new();
5634        let mut cursor = "0".to_owned();
5635        loop {
5636            let reply = f.run(&[b"HSCAN", b"h", cursor.as_bytes(), b"COUNT", b"32"]);
5637            let (next, items) = scan_reply(&reply);
5638            assert_eq!(items.len() % 2, 0, "a pair went out half written");
5639            for pair in items.chunks(2) {
5640                assert_eq!(
5641                    pair[0].strip_prefix("field-"),
5642                    pair[1].strip_prefix("value-"),
5643                    "a field came back with someone else's value"
5644                );
5645                seen.push(pair[0].clone());
5646            }
5647            cursor = next;
5648            if cursor == "0" {
5649                break;
5650            }
5651        }
5652        seen.sort();
5653        seen.dedup();
5654        assert_eq!(seen.len(), fields, "every field once and only once");
5655
5656        let (_, items) = scan_reply(&f.run(&[b"HSCAN", b"h", b"0", b"NOVALUES", b"COUNT", b"32"]));
5657        assert!(
5658            items.iter().all(|s| s.starts_with("field-")),
5659            "NOVALUES still sent the values"
5660        );
5661
5662        let last = fields - 1;
5663        let (_, one) = scan_reply(&f.run(&[
5664            b"HSCAN",
5665            b"h",
5666            b"0",
5667            b"MATCH",
5668            format!("field-{last}").as_bytes(),
5669            b"COUNT",
5670            b"1000",
5671        ]));
5672        assert_eq!(
5673            one,
5674            [format!("field-{last}"), format!("value-{last}")],
5675            "MATCH is on the field"
5676        );
5677    }
5678
5679    #[test]
5680    fn hrandfield_draws_what_it_was_asked_for_and_nests_values_on_resp3() {
5681        let mut f = Fixture::new();
5682        f.run(&[b"HSET", b"h", b"a", b"1"]);
5683        assert_eq!(f.run(&[b"HRANDFIELD", b"h"]), "$1\r\na\r\n");
5684        assert_eq!(f.run(&[b"HRANDFIELD", b"nokey"]), "$-1\r\n");
5685        assert_eq!(f.run(&[b"HRANDFIELD", b"nokey", b"3"]), "*0\r\n");
5686        assert_eq!(
5687            f.run(&[b"HRANDFIELD", b"h", b"3"]),
5688            "*1\r\n$1\r\na\r\n",
5689            "a positive count is capped at the size of the hash"
5690        );
5691        assert_eq!(
5692            f.run(&[b"HRANDFIELD", b"h", b"-3"]),
5693            "*3\r\n$1\r\na\r\n$1\r\na\r\n$1\r\na\r\n",
5694            "and a negative one repeats itself"
5695        );
5696        assert_eq!(
5697            f.run(&[b"HRANDFIELD", b"h", b"1", b"WITHVALUES"]),
5698            "*2\r\n$1\r\na\r\n$1\r\n1\r\n",
5699            "flat on RESP2"
5700        );
5701
5702        f.run(&[b"HELLO", b"3"]);
5703        assert_eq!(
5704            f.run(&[b"HRANDFIELD", b"h", b"1", b"WITHVALUES"]),
5705            "*1\r\n*2\r\n$1\r\na\r\n$1\r\n1\r\n",
5706            "and nested on RESP3, but still an array and never a map"
5707        );
5708    }
5709
5710    #[test]
5711    fn every_hash_command_says_wrongtype_and_writes_nothing() {
5712        let mut f = Fixture::new();
5713        f.run(&[b"SET", b"str", b"v"]);
5714        let wrong = "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n";
5715
5716        for cmd in [
5717            &[b"HSET".as_slice(), b"str", b"f", b"v"][..],
5718            &[b"HMSET".as_slice(), b"str", b"f", b"v"][..],
5719            &[b"HSETNX".as_slice(), b"str", b"f", b"v"][..],
5720            &[b"HGET".as_slice(), b"str", b"f"][..],
5721            &[b"HMGET".as_slice(), b"str", b"f"][..],
5722            &[b"HDEL".as_slice(), b"str", b"f"][..],
5723            &[b"HLEN".as_slice(), b"str"][..],
5724            &[b"HEXISTS".as_slice(), b"str", b"f"][..],
5725            &[b"HSTRLEN".as_slice(), b"str", b"f"][..],
5726            &[b"HGETALL".as_slice(), b"str"][..],
5727            &[b"HKEYS".as_slice(), b"str"][..],
5728            &[b"HVALS".as_slice(), b"str"][..],
5729            &[b"HINCRBY".as_slice(), b"str", b"f", b"1"][..],
5730            &[b"HINCRBYFLOAT".as_slice(), b"str", b"f", b"1"][..],
5731            &[b"HRANDFIELD".as_slice(), b"str"][..],
5732            &[b"HRANDFIELD".as_slice(), b"str", b"2"][..],
5733            &[b"HSCAN".as_slice(), b"str", b"0"][..],
5734        ] {
5735            let reply = f.run(cmd);
5736            assert_eq!(reply, wrong, "{:?}", cmd[0]);
5737        }
5738        assert_eq!(
5739            f.run(&[b"GET", b"str"]),
5740            "$1\r\nv\r\n",
5741            "and none of them touched the value"
5742        );
5743    }
5744
5745    #[test]
5746    fn a_hash_scan_leaves_nothing_half_written_when_its_arguments_are_wrong() {
5747        let mut f = Fixture::new();
5748        f.run(&[b"HSET", b"h", b"f", b"v"]);
5749        for bad in [
5750            &[b"HSCAN".as_slice(), b"h", b"abc"][..],
5751            &[b"HSCAN".as_slice(), b"h", b"0", b"COUNT", b"nope"][..],
5752            &[b"HSCAN".as_slice(), b"h", b"0", b"COUNT", b"0"][..],
5753            &[b"HSCAN".as_slice(), b"h", b"0", b"MATCH"][..],
5754        ] {
5755            let reply = f.run(bad);
5756            assert!(reply.starts_with("-ERR"), "got {reply}");
5757            assert!(!reply.contains('*'), "an array header went out in front");
5758        }
5759    }
5760
5761    #[test]
5762    fn a_field_deadline_goes_on_and_comes_back_in_all_four_units() {
5763        let mut f = Fixture::new();
5764        f.run(&[b"HSET", b"h", b"a", b"1", b"b", b"2"]);
5765        assert_eq!(
5766            f.run(&[b"HEXPIRE", b"h", b"100", b"FIELDS", b"1", b"a"]),
5767            "*1\r\n:1\r\n"
5768        );
5769        assert_eq!(
5770            f.run(&[b"HTTL", b"h", b"FIELDS", b"3", b"a", b"b", b"nope"]),
5771            "*3\r\n:100\r\n:-1\r\n:-2\r\n",
5772            "one answer per field, and the two sentinels are TTL's own"
5773        );
5774
5775        // The same deadline in the other three units, all of them derived from
5776        // the one number the store kept.
5777        let ms = int_reply(&f.run(&[b"HPTTL", b"h", b"FIELDS", b"1", b"a"]));
5778        assert!((99_000..=100_000).contains(&ms), "got {ms}");
5779        let at = int_reply(&f.run(&[b"HEXPIRETIME", b"h", b"FIELDS", b"1", b"a"]));
5780        let at_ms = int_reply(&f.run(&[b"HPEXPIRETIME", b"h", b"FIELDS", b"1", b"a"]));
5781        assert_eq!(at, at_ms.div_euclid(1000) + i64::from(at_ms % 1000 != 0));
5782        assert!(at_ms > 1_700_000_000_000, "an absolute moment, got {at_ms}");
5783
5784        assert_eq!(
5785            f.run(&[b"HPERSIST", b"h", b"FIELDS", b"3", b"a", b"b", b"nope"]),
5786            "*3\r\n:1\r\n:-1\r\n:-2\r\n",
5787            "one for the deadline taken off, and it does not say what it was"
5788        );
5789        assert_eq!(
5790            f.run(&[b"HTTL", b"h", b"FIELDS", b"1", b"a"]),
5791            "*1\r\n:-1\r\n"
5792        );
5793        assert_eq!(
5794            f.run(&[b"HGET", b"h", b"a"]),
5795            "$1\r\n1\r\n",
5796            "and the field is still there with the value it had"
5797        );
5798    }
5799
5800    #[test]
5801    fn a_deadline_that_has_already_gone_deletes_the_field_now() {
5802        let mut f = Fixture::new();
5803        f.run(&[b"HSET", b"h", b"a", b"1", b"b", b"2"]);
5804        assert_eq!(
5805            f.run(&[b"HEXPIREAT", b"h", b"1", b"FIELDS", b"1", b"a"]),
5806            "*1\r\n:2\r\n",
5807            "two, and not one, because nothing was stored"
5808        );
5809        assert_eq!(f.run(&[b"HGET", b"h", b"a"]), "$-1\r\n");
5810        assert_eq!(f.run(&[b"HLEN", b"h"]), ":1\r\n");
5811
5812        assert_eq!(
5813            f.run(&[b"HPEXPIREAT", b"h", b"1", b"FIELDS", b"1", b"b"]),
5814            "*1\r\n:2\r\n"
5815        );
5816        assert_eq!(
5817            f.run(&[b"EXISTS", b"h"]),
5818            ":0\r\n",
5819            "and the last field going took the key with it"
5820        );
5821
5822        // Zero is a delete and not an error, where minus one is an error. That
5823        // is Redis's split and it is easy to get backwards.
5824        f.run(&[b"HSET", b"h", b"a", b"1"]);
5825        assert_eq!(
5826            f.run(&[b"HEXPIRE", b"h", b"0", b"FIELDS", b"1", b"a"]),
5827            "*1\r\n:2\r\n"
5828        );
5829    }
5830
5831    #[test]
5832    fn a_field_is_gone_once_its_moment_passes() {
5833        let mut f = Fixture::new();
5834        f.run(&[b"HSET", b"h", b"a", b"1", b"b", b"2"]);
5835        assert_eq!(
5836            f.run(&[b"HPEXPIRE", b"h", b"20", b"FIELDS", b"1", b"a"]),
5837            "*1\r\n:1\r\n"
5838        );
5839        assert_eq!(f.run(&[b"HGET", b"h", b"a"]), "$1\r\n1\r\n", "not yet");
5840
5841        // Time moves once per turn of the event loop and nowhere else, so a
5842        // test moves it by hand rather than by sleeping. There is nothing to
5843        // sleep for: the deadline is a number and so is the clock.
5844        f.server.advance_clock_ms(60);
5845        assert_eq!(f.run(&[b"HLEN", b"h"]), ":1\r\n");
5846        assert_eq!(f.run(&[b"HGET", b"h", b"a"]), "$-1\r\n");
5847        assert_eq!(
5848            f.run(&[b"HGETALL", b"h"]),
5849            "*2\r\n$1\r\nb\r\n$1\r\n2\r\n",
5850            "and the walks do not hand back a field that has expired"
5851        );
5852    }
5853
5854    #[test]
5855    fn a_missing_key_answers_the_no_field_sentinel_for_every_field() {
5856        let mut f = Fixture::new();
5857        for cmd in [
5858            &[
5859                b"HEXPIRE".as_slice(),
5860                b"nokey",
5861                b"100",
5862                b"FIELDS",
5863                b"2",
5864                b"a",
5865                b"b",
5866            ][..],
5867            &[b"HTTL".as_slice(), b"nokey", b"FIELDS", b"2", b"a", b"b"][..],
5868            &[b"HPTTL".as_slice(), b"nokey", b"FIELDS", b"2", b"a", b"b"][..],
5869            &[
5870                b"HEXPIRETIME".as_slice(),
5871                b"nokey",
5872                b"FIELDS",
5873                b"2",
5874                b"a",
5875                b"b",
5876            ][..],
5877            &[
5878                b"HPERSIST".as_slice(),
5879                b"nokey",
5880                b"FIELDS",
5881                b"2",
5882                b"a",
5883                b"b",
5884            ][..],
5885        ] {
5886            assert_eq!(f.run(cmd), "*2\r\n:-2\r\n:-2\r\n", "{:?}", cmd[0]);
5887        }
5888    }
5889
5890    #[test]
5891    fn writing_a_field_clears_the_deadline_that_was_on_it() {
5892        let mut f = Fixture::new();
5893        f.run(&[b"HSET", b"h", b"a", b"1"]);
5894        f.run(&[b"HEXPIRE", b"h", b"100", b"FIELDS", b"1", b"a"]);
5895        f.run(&[b"HSET", b"h", b"a", b"2"]);
5896        assert_eq!(
5897            f.run(&[b"HTTL", b"h", b"FIELDS", b"1", b"a"]),
5898            "*1\r\n:-1\r\n",
5899            "Redis has done this since 7.4, and it is why HGETEX exists"
5900        );
5901    }
5902
5903    #[test]
5904    fn the_four_conditions_reach_the_store_the_way_they_were_written() {
5905        let mut f = Fixture::new();
5906        f.run(&[b"HSET", b"h", b"a", b"1"]);
5907        assert_eq!(
5908            f.run(&[b"HEXPIRE", b"h", b"100", b"XX", b"FIELDS", b"1", b"a"]),
5909            "*1\r\n:0\r\n",
5910            "XX on a field with no deadline changes nothing"
5911        );
5912        assert_eq!(
5913            f.run(&[b"HEXPIRE", b"h", b"100", b"NX", b"FIELDS", b"1", b"a"]),
5914            "*1\r\n:1\r\n"
5915        );
5916        assert_eq!(
5917            f.run(&[b"HEXPIRE", b"h", b"200", b"NX", b"FIELDS", b"1", b"a"]),
5918            "*1\r\n:0\r\n",
5919            "and NX will not move one that is already there"
5920        );
5921        assert_eq!(
5922            f.run(&[b"HEXPIRE", b"h", b"50", b"GT", b"FIELDS", b"1", b"a"]),
5923            "*1\r\n:0\r\n"
5924        );
5925        assert_eq!(
5926            f.run(&[b"HEXPIRE", b"h", b"500", b"GT", b"FIELDS", b"1", b"a"]),
5927            "*1\r\n:1\r\n"
5928        );
5929        assert_eq!(
5930            f.run(&[b"HEXPIRE", b"h", b"50", b"LT", b"FIELDS", b"1", b"a"]),
5931            "*1\r\n:1\r\n"
5932        );
5933        assert_eq!(
5934            f.run(&[b"HTTL", b"h", b"FIELDS", b"1", b"a"]),
5935            "*1\r\n:50\r\n"
5936        );
5937    }
5938
5939    #[test]
5940    fn the_field_ttl_family_leaves_nothing_half_written_on_a_bad_argument() {
5941        let mut f = Fixture::new();
5942        f.run(&[b"HSET", b"h", b"a", b"1"]);
5943        for (bad, want) in [
5944            (
5945                &[b"HEXPIRE".as_slice(), b"h", b"-1", b"FIELDS", b"1", b"a"][..],
5946                "-ERR invalid expire time, must be >= 0",
5947            ),
5948            (
5949                &[
5950                    b"HEXPIRE".as_slice(),
5951                    b"h",
5952                    b"9999999999999999",
5953                    b"FIELDS",
5954                    b"1",
5955                    b"a",
5956                ][..],
5957                "-ERR invalid expire time in 'hexpire' command",
5958            ),
5959            (
5960                &[b"HEXPIRE".as_slice(), b"h", b"100", b"FIELD", b"1", b"a"][..],
5961                "-ERR wrong number of arguments for 'hexpire' command",
5962            ),
5963            (
5964                &[b"HEXPIRE".as_slice(), b"h", b"100", b"FIELDS", b"0", b"a"][..],
5965                "-ERR Parameter `numFields` should be greater than 0",
5966            ),
5967            (
5968                &[b"HEXPIRE".as_slice(), b"h", b"100", b"FIELDS", b"2", b"a"][..],
5969                "-ERR wrong number of arguments",
5970            ),
5971            (
5972                &[b"HTTL".as_slice(), b"h", b"FIELDS", b"3", b"a", b"b"][..],
5973                "-ERR wrong number of arguments",
5974            ),
5975        ] {
5976            let reply = f.run(bad);
5977            assert!(reply.starts_with(want), "wanted {want}, got {reply}");
5978            assert!(!reply.contains('*'), "an array header went out in front");
5979        }
5980        assert_eq!(
5981            f.run(&[b"HTTL", b"h", b"FIELDS", b"1", b"a"]),
5982            "*1\r\n:-1\r\n",
5983            "and not one of them put a deadline on anything"
5984        );
5985    }
5986
5987    #[test]
5988    fn every_field_ttl_command_says_wrongtype_and_writes_nothing() {
5989        let mut f = Fixture::new();
5990        f.run(&[b"SET", b"str", b"v"]);
5991        let wrong = "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n";
5992
5993        for cmd in [
5994            &[b"HEXPIRE".as_slice(), b"str", b"100", b"FIELDS", b"1", b"f"][..],
5995            &[
5996                b"HPEXPIRE".as_slice(),
5997                b"str",
5998                b"100",
5999                b"FIELDS",
6000                b"1",
6001                b"f",
6002            ][..],
6003            &[
6004                b"HEXPIREAT".as_slice(),
6005                b"str",
6006                b"9999999999",
6007                b"FIELDS",
6008                b"1",
6009                b"f",
6010            ][..],
6011            &[
6012                b"HPEXPIREAT".as_slice(),
6013                b"str",
6014                b"9999999999999",
6015                b"FIELDS",
6016                b"1",
6017                b"f",
6018            ][..],
6019            &[b"HTTL".as_slice(), b"str", b"FIELDS", b"1", b"f"][..],
6020            &[b"HPTTL".as_slice(), b"str", b"FIELDS", b"1", b"f"][..],
6021            &[b"HEXPIRETIME".as_slice(), b"str", b"FIELDS", b"1", b"f"][..],
6022            &[b"HPEXPIRETIME".as_slice(), b"str", b"FIELDS", b"1", b"f"][..],
6023            &[b"HPERSIST".as_slice(), b"str", b"FIELDS", b"1", b"f"][..],
6024        ] {
6025            assert_eq!(f.run(cmd), wrong, "{:?}", cmd[0]);
6026        }
6027        assert_eq!(
6028            f.run(&[b"GET", b"str"]),
6029            "$1\r\nv\r\n",
6030            "and none of them touched the value"
6031        );
6032    }
6033
6034    #[test]
6035    fn hgetdel_hands_the_value_out_and_then_takes_the_field() {
6036        let mut f = Fixture::new();
6037        f.run(&[b"HSET", b"h", b"a", b"1", b"b", b"2"]);
6038        assert_eq!(
6039            f.run(&[b"HGETDEL", b"h", b"FIELDS", b"2", b"a", b"nope"]),
6040            "*2\r\n$1\r\n1\r\n$-1\r\n",
6041            "positional, so the field that was not there is a nil in its place"
6042        );
6043        assert_eq!(f.run(&[b"HLEN", b"h"]), ":1\r\n");
6044        assert_eq!(
6045            f.run(&[b"HGETDEL", b"nokey", b"FIELDS", b"1", b"a"]),
6046            "*1\r\n$-1\r\n"
6047        );
6048        assert_eq!(
6049            f.run(&[b"HGETDEL", b"h", b"FIELDS", b"1", b"b"]),
6050            "*1\r\n$1\r\n2\r\n"
6051        );
6052        assert_eq!(
6053            f.run(&[b"EXISTS", b"h"]),
6054            ":0\r\n",
6055            "and the last field took the key"
6056        );
6057    }
6058
6059    #[test]
6060    fn hgetex_reads_and_moves_the_deadline_in_one_command() {
6061        let mut f = Fixture::new();
6062        f.run(&[b"HSET", b"h", b"a", b"1"]);
6063        assert_eq!(
6064            f.run(&[b"HGETEX", b"h", b"FIELDS", b"1", b"a"]),
6065            "*1\r\n$1\r\n1\r\n"
6066        );
6067        assert_eq!(
6068            f.run(&[b"HTTL", b"h", b"FIELDS", b"1", b"a"]),
6069            "*1\r\n:-1\r\n",
6070            "no option means leave it alone, which is the one place this is not GETEX"
6071        );
6072
6073        f.run(&[b"HGETEX", b"h", b"EX", b"100", b"FIELDS", b"1", b"a"]);
6074        assert_eq!(
6075            f.run(&[b"HTTL", b"h", b"FIELDS", b"1", b"a"]),
6076            "*1\r\n:100\r\n"
6077        );
6078        f.run(&[b"HGETEX", b"h", b"FIELDS", b"1", b"a"]);
6079        assert_eq!(
6080            f.run(&[b"HTTL", b"h", b"FIELDS", b"1", b"a"]),
6081            "*1\r\n:100\r\n",
6082            "and a plain read really does leave it alone"
6083        );
6084        assert_eq!(
6085            f.run(&[b"HGETEX", b"h", b"PERSIST", b"FIELDS", b"1", b"a"]),
6086            "*1\r\n$1\r\n1\r\n"
6087        );
6088        assert_eq!(
6089            f.run(&[b"HTTL", b"h", b"FIELDS", b"1", b"a"]),
6090            "*1\r\n:-1\r\n"
6091        );
6092
6093        assert_eq!(
6094            f.run(&[b"HGETEX", b"h", b"EXAT", b"1", b"FIELDS", b"1", b"a"]),
6095            "*1\r\n$1\r\n1\r\n",
6096            "the value goes out before the deadline that has already gone is applied"
6097        );
6098        assert_eq!(f.run(&[b"EXISTS", b"h"]), ":0\r\n");
6099        assert_eq!(
6100            f.run(&[b"HGETEX", b"nokey", b"EX", b"100", b"FIELDS", b"1", b"a"]),
6101            "*1\r\n$-1\r\n"
6102        );
6103    }
6104
6105    #[test]
6106    fn hsetex_writes_all_of_it_or_none_of_it() {
6107        let mut f = Fixture::new();
6108        assert_eq!(
6109            f.run(&[b"HSETEX", b"h", b"FIELDS", b"1", b"a", b"1"]),
6110            ":1\r\n"
6111        );
6112        assert_eq!(f.run(&[b"HGET", b"h", b"a"]), "$1\r\n1\r\n");
6113        assert_eq!(
6114            f.run(&[
6115                b"HSETEX", b"h", b"FNX", b"FIELDS", b"2", b"a", b"9", b"new", b"9"
6116            ]),
6117            ":0\r\n",
6118            "FNX wants every field named to be missing"
6119        );
6120        assert_eq!(f.run(&[b"HGET", b"h", b"a"]), "$1\r\n1\r\n");
6121        assert_eq!(
6122            f.run(&[b"HEXISTS", b"h", b"new"]),
6123            ":0\r\n",
6124            "and none of the list was written"
6125        );
6126        assert_eq!(
6127            f.run(&[
6128                b"HSETEX", b"h", b"FXX", b"FIELDS", b"2", b"a", b"9", b"nope", b"9"
6129            ]),
6130            ":0\r\n",
6131            "and FXX wants every one of them to be there"
6132        );
6133        assert_eq!(f.run(&[b"HGET", b"h", b"a"]), "$1\r\n1\r\n");
6134        assert_eq!(
6135            f.run(&[b"HSETEX", b"h", b"FXX", b"FIELDS", b"1", b"a", b"9"]),
6136            ":1\r\n"
6137        );
6138        assert_eq!(f.run(&[b"HGET", b"h", b"a"]), "$1\r\n9\r\n");
6139
6140        assert_eq!(
6141            f.run(&[b"HSETEX", b"gone", b"FXX", b"FIELDS", b"1", b"a", b"1"]),
6142            ":0\r\n"
6143        );
6144        assert_eq!(
6145            f.run(&[b"EXISTS", b"gone"]),
6146            ":0\r\n",
6147            "a key with no fields cannot meet FXX and is not created trying"
6148        );
6149    }
6150
6151    #[test]
6152    fn hsetex_clears_the_deadline_unless_it_is_told_to_keep_it() {
6153        let mut f = Fixture::new();
6154        f.run(&[b"HSETEX", b"h", b"EX", b"100", b"FIELDS", b"1", b"a", b"1"]);
6155        assert_eq!(
6156            f.run(&[b"HTTL", b"h", b"FIELDS", b"1", b"a"]),
6157            "*1\r\n:100\r\n"
6158        );
6159
6160        f.run(&[b"HSETEX", b"h", b"KEEPTTL", b"FIELDS", b"1", b"a", b"2"]);
6161        assert_eq!(f.run(&[b"HGET", b"h", b"a"]), "$1\r\n2\r\n");
6162        assert_eq!(
6163            f.run(&[b"HTTL", b"h", b"FIELDS", b"1", b"a"]),
6164            "*1\r\n:100\r\n",
6165            "KEEPTTL put back what the write cleared"
6166        );
6167
6168        f.run(&[b"HSETEX", b"h", b"FIELDS", b"1", b"a", b"3"]);
6169        assert_eq!(
6170            f.run(&[b"HTTL", b"h", b"FIELDS", b"1", b"a"]),
6171            "*1\r\n:-1\r\n",
6172            "and without it a write clears the deadline the way HSET does"
6173        );
6174
6175        // Any order, because Redis reads these in a loop and not in a fixed
6176        // sequence.
6177        assert_eq!(
6178            f.run(&[
6179                b"HSETEX", b"h", b"PX", b"100000", b"FXX", b"FIELDS", b"1", b"a", b"4"
6180            ]),
6181            ":1\r\n"
6182        );
6183        assert_eq!(
6184            f.run(&[b"HTTL", b"h", b"FIELDS", b"1", b"a"]),
6185            "*1\r\n:100\r\n"
6186        );
6187
6188        assert_eq!(
6189            f.run(&[b"HSETEX", b"h", b"EXAT", b"1", b"FIELDS", b"1", b"a", b"5"]),
6190            ":1\r\n",
6191            "written, and not the separate code the HEXPIRE family has for this"
6192        );
6193        assert_eq!(
6194            f.run(&[b"EXISTS", b"h"]),
6195            ":0\r\n",
6196            "and storing it and then removing it emptied the hash"
6197        );
6198    }
6199
6200    #[test]
6201    fn the_last_three_hash_commands_word_their_mistakes_their_own_way() {
6202        let mut f = Fixture::new();
6203        f.run(&[b"HSET", b"h", b"a", b"1"]);
6204        for (bad, want) in [
6205            // HGETDEL has three sentences of its own for these three mistakes.
6206            (
6207                &[b"HGETDEL".as_slice(), b"h", b"FIELDS", b"0", b"a"][..],
6208                "-ERR Number of fields must be a positive integer",
6209            ),
6210            (
6211                &[b"HGETDEL".as_slice(), b"h", b"FIELDS", b"2", b"a"][..],
6212                "-ERR The `numfields` parameter must match the number of arguments",
6213            ),
6214            (
6215                &[b"HGETDEL".as_slice(), b"h", b"FIELD", b"1", b"a"][..],
6216                "-ERR Mandatory argument FIELDS is missing or not at the right position",
6217            ),
6218            // And HGETEX and HSETEX have three different ones between them.
6219            (
6220                &[b"HGETEX".as_slice(), b"h", b"FIELDS", b"0", b"a"][..],
6221                "-ERR invalid number of fields",
6222            ),
6223            (
6224                &[b"HGETEX".as_slice(), b"h", b"FIELDS", b"2", b"a"][..],
6225                "-ERR wrong number of arguments",
6226            ),
6227            (
6228                &[b"HGETEX".as_slice(), b"h", b"FIELD", b"1", b"a"][..],
6229                "-ERR unknown argument: FIELD",
6230            ),
6231            (
6232                &[
6233                    b"HGETEX".as_slice(),
6234                    b"h",
6235                    b"KEEPTTL",
6236                    b"FIELDS",
6237                    b"1",
6238                    b"a",
6239                ][..],
6240                "-ERR unknown argument: KEEPTTL",
6241            ),
6242            (
6243                &[
6244                    b"HGETEX".as_slice(),
6245                    b"h",
6246                    b"EX",
6247                    b"100",
6248                    b"PERSIST",
6249                    b"FIELDS",
6250                    b"1",
6251                    b"a",
6252                ][..],
6253                "-ERR Only one of EX, PX, EXAT, PXAT or PERSIST arguments can be specified",
6254            ),
6255            (
6256                &[
6257                    b"HSETEX".as_slice(),
6258                    b"h",
6259                    b"EX",
6260                    b"1",
6261                    b"KEEPTTL",
6262                    b"FIELDS",
6263                    b"1",
6264                    b"a",
6265                    b"1",
6266                ][..],
6267                "-ERR Only one of EX, PX, EXAT, PXAT or KEEPTTL arguments can be specified",
6268            ),
6269            (
6270                &[
6271                    b"HSETEX".as_slice(),
6272                    b"h",
6273                    b"FNX",
6274                    b"FXX",
6275                    b"FIELDS",
6276                    b"1",
6277                    b"a",
6278                    b"1",
6279                ][..],
6280                "-ERR Only one of FXX or FNX arguments can be specified",
6281            ),
6282            (
6283                &[
6284                    b"HSETEX".as_slice(),
6285                    b"h",
6286                    b"FIELDS",
6287                    b"2",
6288                    b"a",
6289                    b"1",
6290                    b"b",
6291                ][..],
6292                "-ERR wrong number of arguments",
6293            ),
6294            (
6295                &[
6296                    b"HGETEX".as_slice(),
6297                    b"h",
6298                    b"EX",
6299                    b"-1",
6300                    b"FIELDS",
6301                    b"1",
6302                    b"a",
6303                ][..],
6304                "-ERR invalid expire time, must be >= 0",
6305            ),
6306            (
6307                &[
6308                    b"HGETEX".as_slice(),
6309                    b"h",
6310                    b"PXAT",
6311                    b"99999999999999",
6312                    b"FIELDS",
6313                    b"1",
6314                    b"a",
6315                ][..],
6316                "-ERR invalid expire time in 'hgetex' command",
6317            ),
6318            (
6319                &[
6320                    b"HSETEX".as_slice(),
6321                    b"h",
6322                    b"EX",
6323                    b"abc",
6324                    b"FIELDS",
6325                    b"1",
6326                    b"a",
6327                    b"1",
6328                ][..],
6329                "-ERR value is not an integer or out of range",
6330            ),
6331        ] {
6332            let reply = f.run(bad);
6333            assert!(reply.starts_with(want), "wanted {want}, got {reply}");
6334            assert!(!reply.contains('*'), "an array header went out in front");
6335        }
6336        assert_eq!(
6337            f.run(&[b"HGET", b"h", b"a"]),
6338            "$1\r\n1\r\n",
6339            "and not one of them wrote anything"
6340        );
6341        assert_eq!(
6342            f.run(&[b"HTTL", b"h", b"FIELDS", b"1", b"a"]),
6343            "*1\r\n:-1\r\n"
6344        );
6345    }
6346
6347    #[test]
6348    fn the_last_three_hash_commands_say_wrongtype_and_write_nothing() {
6349        let mut f = Fixture::new();
6350        f.run(&[b"SET", b"str", b"v"]);
6351        let wrong = "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n";
6352        for cmd in [
6353            &[b"HGETDEL".as_slice(), b"str", b"FIELDS", b"1", b"f"][..],
6354            &[b"HGETEX".as_slice(), b"str", b"FIELDS", b"1", b"f"][..],
6355            &[
6356                b"HGETEX".as_slice(),
6357                b"str",
6358                b"EX",
6359                b"100",
6360                b"FIELDS",
6361                b"1",
6362                b"f",
6363            ][..],
6364            &[b"HSETEX".as_slice(), b"str", b"FIELDS", b"1", b"f", b"v"][..],
6365        ] {
6366            assert_eq!(f.run(cmd), wrong, "{:?}", cmd[0]);
6367        }
6368        assert_eq!(f.run(&[b"GET", b"str"]), "$1\r\nv\r\n");
6369    }
6370
6371    /// The two orders `HIMPORT` juggles, which are not the same order.
6372    ///
6373    /// Values arrive in the order the fields were declared in and the hash is
6374    /// built in sorted order, so the first value is not generally the first
6375    /// field. And the sort is by length before bytes, which nothing else here
6376    /// sorts names with: `b` comes before `aa` where a plain byte comparison
6377    /// would put `aa` first. Both read off 8.10.1.
6378    #[test]
6379    fn himport_writes_declared_values_into_sorted_fields() {
6380        let mut f = Fixture::new();
6381        assert_eq!(
6382            f.run(&[b"HIMPORT", b"PREPARE", b"shape", b"b", b"aa", b"a"]),
6383            "+OK\r\n"
6384        );
6385        assert_eq!(
6386            f.run(&[b"HIMPORT", b"SET", b"k", b"shape", b"1", b"2", b"3"]),
6387            "+OK\r\n"
6388        );
6389        assert_eq!(f.run(&[b"HKEYS", b"k"]), bulks(&["a", "b", "aa"]));
6390        assert_eq!(
6391            f.run(&[b"HGETALL", b"k"]),
6392            bulks(&["a", "3", "b", "1", "aa", "2"])
6393        );
6394    }
6395
6396    /// It replaces the key rather than writing over it, so a field the fieldset
6397    /// does not name is gone afterwards and so is the deadline.
6398    #[test]
6399    fn himport_set_replaces_the_whole_key() {
6400        let mut f = Fixture::new();
6401        f.run(&[b"HSET", b"k", b"gone", b"old", b"a", b"old"]);
6402        f.run(&[b"EXPIRE", b"k", b"100"]);
6403        f.run(&[b"HIMPORT", b"PREPARE", b"shape", b"a", b"b"]);
6404        assert_eq!(
6405            f.run(&[b"HIMPORT", b"SET", b"k", b"shape", b"1", b"2"]),
6406            "+OK\r\n"
6407        );
6408        assert_eq!(f.run(&[b"HGETALL", b"k"]), bulks(&["a", "1", "b", "2"]));
6409        assert_eq!(f.run(&[b"TTL", b"k"]), ":-1\r\n");
6410    }
6411
6412    /// A fieldset is connection state. `SELECT` leaves them alone and `RESET`
6413    /// throws them away, and a key built from one outlives it.
6414    #[test]
6415    fn himport_fieldsets_belong_to_the_connection_and_not_to_the_keyspace() {
6416        let mut f = Fixture::new();
6417        f.run(&[b"HIMPORT", b"PREPARE", b"shape", b"a"]);
6418        f.run(&[b"SELECT", b"1"]);
6419        assert_eq!(
6420            f.run(&[b"HIMPORT", b"SET", b"k", b"shape", b"1"]),
6421            "+OK\r\n"
6422        );
6423        f.run(&[b"SELECT", b"0"]);
6424        assert_eq!(f.run(&[b"RESET"]), "+RESET\r\n");
6425        assert_eq!(
6426            f.run(&[b"HIMPORT", b"SET", b"k2", b"shape", b"1"]),
6427            "-ERR no such fieldset\r\n"
6428        );
6429    }
6430
6431    /// Which complaint wins when a line is wrong in more than one place.
6432    ///
6433    /// The type of the key beats both of the others, so a `HIMPORT SET` against
6434    /// a string is a WRONGTYPE even when the fieldset is missing too, which is
6435    /// the ordering a real server has and not the one the argument order
6436    /// suggests.
6437    #[test]
6438    fn himport_complains_in_the_order_a_real_server_does() {
6439        let mut f = Fixture::new();
6440        f.run(&[b"SET", b"str", b"v"]);
6441        f.run(&[b"HIMPORT", b"PREPARE", b"shape", b"a", b"b"]);
6442        let wrong = "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n";
6443        assert_eq!(
6444            f.run(&[b"HIMPORT", b"SET", b"str", b"nope", b"1"]),
6445            wrong,
6446            "the type beats a missing fieldset"
6447        );
6448        assert_eq!(
6449            f.run(&[b"HIMPORT", b"SET", b"str", b"shape", b"1"]),
6450            wrong,
6451            "and it beats a value count that does not fit"
6452        );
6453        assert_eq!(
6454            f.run(&[b"HIMPORT", b"SET", b"k", b"nope", b"1"]),
6455            "-ERR no such fieldset\r\n"
6456        );
6457        // One sentence for too few and for too many alike.
6458        for values in [&[b"1".as_slice()][..], &[b"1".as_slice(), b"2", b"3"][..]] {
6459            let mut line: Vec<&[u8]> = vec![b"HIMPORT", b"SET", b"k", b"shape"];
6460            line.extend_from_slice(values);
6461            assert_eq!(
6462                f.run(&line),
6463                "-ERR value count does not match fieldset field count\r\n",
6464                "{} values into two fields",
6465                values.len()
6466            );
6467        }
6468        assert_eq!(f.run(&[b"EXISTS", b"k"]), ":0\r\n");
6469    }
6470
6471    /// The arity of each subcommand, and the unknown one.
6472    #[test]
6473    fn himport_checks_each_subcommand_count_under_its_own_name() {
6474        let mut f = Fixture::new();
6475        assert_eq!(
6476            f.run(&[b"HIMPORT"]),
6477            "-ERR wrong number of arguments for 'himport' command\r\n"
6478        );
6479        for (rest, name) in [
6480            (&["PREPARE"][..], "prepare"),
6481            (&["PREPARE", "fs"][..], "prepare"),
6482            (&["SET"][..], "set"),
6483            (&["SET", "k"][..], "set"),
6484            (&["SET", "k", "fs"][..], "set"),
6485            (&["DISCARD"][..], "discard"),
6486            (&["DISCARD", "a", "b"][..], "discard"),
6487            (&["DISCARDALL", "x"][..], "discardall"),
6488        ] {
6489            let mut line: Vec<&[u8]> = vec![b"HIMPORT"];
6490            line.extend(rest.iter().map(|a| a.as_bytes()));
6491            assert_eq!(
6492                f.run(&line),
6493                format!("-ERR wrong number of arguments for 'himport|{name}' command\r\n"),
6494                "HIMPORT {}",
6495                rest.join(" ")
6496            );
6497        }
6498        assert_eq!(
6499            f.run(&[b"HIMPORT", b"NOPE", b"x"]),
6500            "-ERR unknown subcommand 'NOPE'. Try HIMPORT HELP.\r\n"
6501        );
6502    }
6503
6504    /// A `PREPARE` that fails leaves the name pointing where it pointed, which
6505    /// is the answer of the two that could not be guessed from outside.
6506    #[test]
6507    fn a_failed_himport_prepare_leaves_the_old_fieldset_alone() {
6508        let mut f = Fixture::new();
6509        f.run(&[b"HIMPORT", b"PREPARE", b"shape", b"a", b"b"]);
6510        assert_eq!(
6511            f.run(&[b"HIMPORT", b"PREPARE", b"shape", b"c", b"c"]),
6512            "-ERR duplicate field name in fieldset\r\n"
6513        );
6514        assert_eq!(
6515            f.run(&[b"HIMPORT", b"SET", b"k", b"shape", b"1", b"2"]),
6516            "+OK\r\n"
6517        );
6518        assert_eq!(f.run(&[b"HGETALL", b"k"]), bulks(&["a", "1", "b", "2"]));
6519    }
6520
6521    /// Preparing the same name twice replaces it, and the two discards count
6522    /// what they took rather than answering OK.
6523    #[test]
6524    fn himport_prepare_replaces_and_the_discards_count() {
6525        let mut f = Fixture::new();
6526        f.run(&[b"HIMPORT", b"PREPARE", b"shape", b"a", b"b"]);
6527        f.run(&[b"HIMPORT", b"PREPARE", b"shape", b"z"]);
6528        assert_eq!(
6529            f.run(&[b"HIMPORT", b"SET", b"k", b"shape", b"1"]),
6530            "+OK\r\n"
6531        );
6532        assert_eq!(f.run(&[b"HGETALL", b"k"]), bulks(&["z", "1"]));
6533
6534        assert_eq!(f.run(&[b"HIMPORT", b"DISCARD", b"shape"]), ":1\r\n");
6535        assert_eq!(f.run(&[b"HIMPORT", b"DISCARD", b"shape"]), ":0\r\n");
6536        f.run(&[b"HIMPORT", b"PREPARE", b"one", b"a"]);
6537        f.run(&[b"HIMPORT", b"PREPARE", b"two", b"a"]);
6538        assert_eq!(f.run(&[b"HIMPORT", b"DISCARDALL"]), ":2\r\n");
6539        assert_eq!(f.run(&[b"HIMPORT", b"DISCARDALL"]), ":0\r\n");
6540    }
6541
6542    /// The one integer of a single element array reply.
6543    /// The number out of a plain integer reply.
6544    ///
6545    /// [`int_reply`] is the same thing wrapped in a one element array, which is
6546    /// the shape every hash field command answers in.
6547    fn int(reply: &str) -> i64 {
6548        let body = reply
6549            .strip_prefix(':')
6550            .and_then(|s| s.strip_suffix("\r\n"))
6551            .unwrap_or_else(|| panic!("wanted an integer, got {reply}"));
6552        body.parse().expect("an integer")
6553    }
6554
6555    fn int_reply(reply: &str) -> i64 {
6556        let body = reply
6557            .strip_prefix("*1\r\n:")
6558            .and_then(|s| s.strip_suffix("\r\n"))
6559            .unwrap_or_else(|| panic!("wanted one integer, got {reply}"));
6560        body.parse().expect("an integer")
6561    }
6562
6563    /// The cursor and the flat items of a scan reply.
6564    fn scan_reply(reply: &str) -> (String, Vec<String>) {
6565        let mut lines = reply.split("\r\n");
6566        assert_eq!(lines.next(), Some("*2"), "got {reply}");
6567        lines.next().expect("the cursor header");
6568        let cursor = lines.next().expect("a cursor").to_owned();
6569        let header = lines.next().expect("an item count");
6570        let n: usize = header[1..].parse().expect("a count");
6571        let mut items = Vec::with_capacity(n);
6572        for _ in 0..n {
6573            lines.next().expect("an item header");
6574            items.push(lines.next().expect("an item").to_owned());
6575        }
6576        (cursor, items)
6577    }
6578
6579    /// The members of a set reply, sorted, since none of these promise an
6580    /// order and a test that asserted one would be asserting an accident.
6581    fn sorted(reply: &str) -> Vec<String> {
6582        let mut lines = reply.split("\r\n");
6583        let header = lines.next().expect("a header");
6584        assert!(
6585            header.starts_with('*') || header.starts_with('~'),
6586            "got {reply}"
6587        );
6588        let n: usize = header[1..].parse().expect("a member count");
6589        let mut got = Vec::with_capacity(n);
6590        for _ in 0..n {
6591            lines.next().expect("a member header");
6592            got.push(lines.next().expect("a member").to_owned());
6593        }
6594        got.sort();
6595        got
6596    }
6597
6598    #[test]
6599    fn the_algebra_answers_what_the_sets_share_and_do_not() {
6600        let mut f = Fixture::new();
6601        f.run(&[b"SADD", b"a", b"1", b"2", b"3"]);
6602        f.run(&[b"SADD", b"b", b"2", b"3", b"4"]);
6603        f.run(&[b"SADD", b"c", b"3", b"4", b"5"]);
6604
6605        assert_eq!(sorted(&f.run(&[b"SINTER", b"a", b"b", b"c"])), ["3"]);
6606        assert_eq!(
6607            sorted(&f.run(&[b"SUNION", b"a", b"b", b"c"])),
6608            ["1", "2", "3", "4", "5"]
6609        );
6610        assert_eq!(sorted(&f.run(&[b"SDIFF", b"a", b"b"])), ["1"]);
6611        assert_eq!(sorted(&f.run(&[b"SINTER", b"a"])), ["1", "2", "3"]);
6612
6613        // A key that is not there is an empty set, which empties an
6614        // intersection and does nothing at all to a union.
6615        assert_eq!(f.run(&[b"SINTER", b"a", b"nope"]), "*0\r\n");
6616        assert_eq!(sorted(&f.run(&[b"SUNION", b"a", b"nope"])), ["1", "2", "3"]);
6617        assert_eq!(f.run(&[b"SDIFF", b"nope", b"a"]), "*0\r\n");
6618        assert_eq!(f.run(&[b"DBSIZE"]), ":3\r\n", "and none of it made a key");
6619    }
6620
6621    #[test]
6622    fn the_algebra_answers_a_set_on_resp3_and_an_array_on_resp2() {
6623        let mut f = Fixture::new();
6624        f.run(&[b"SADD", b"a", b"x"]);
6625        assert_eq!(f.run(&[b"SINTER", b"a"]), "*1\r\n$1\r\nx\r\n");
6626        assert_eq!(f.run(&[b"SUNION", b"a"]), "*1\r\n$1\r\nx\r\n");
6627        assert_eq!(f.run(&[b"SDIFF", b"a"]), "*1\r\n$1\r\nx\r\n");
6628
6629        f.run(&[b"HELLO", b"3"]);
6630        assert_eq!(f.run(&[b"SINTER", b"a"]), "~1\r\n$1\r\nx\r\n");
6631        assert_eq!(f.run(&[b"SUNION", b"a"]), "~1\r\n$1\r\nx\r\n");
6632        assert_eq!(f.run(&[b"SDIFF", b"a"]), "~1\r\n$1\r\nx\r\n");
6633        assert_eq!(f.run(&[b"SINTER", b"nope"]), "~0\r\n");
6634    }
6635
6636    #[test]
6637    fn a_store_form_writes_a_key_and_answers_how_big_it_is() {
6638        let mut f = Fixture::new();
6639        f.run(&[b"SADD", b"a", b"1", b"2", b"3"]);
6640        f.run(&[b"SADD", b"b", b"2", b"3", b"4"]);
6641
6642        assert_eq!(f.run(&[b"SINTERSTORE", b"d", b"a", b"b"]), ":2\r\n");
6643        assert_eq!(sorted(&f.run(&[b"SMEMBERS", b"d"])), ["2", "3"]);
6644        assert_eq!(f.run(&[b"SUNIONSTORE", b"d", b"a", b"b"]), ":4\r\n");
6645        assert_eq!(sorted(&f.run(&[b"SMEMBERS", b"d"])), ["1", "2", "3", "4"]);
6646        assert_eq!(f.run(&[b"SDIFFSTORE", b"d", b"a", b"b"]), ":1\r\n");
6647        assert_eq!(f.run(&[b"SMEMBERS", b"d"]), "*1\r\n$1\r\n1\r\n");
6648
6649        // An empty answer deletes the destination rather than leaving an empty
6650        // set behind, and the destination may be one of the sources.
6651        assert_eq!(f.run(&[b"SDIFFSTORE", b"d", b"a", b"a"]), ":0\r\n");
6652        assert_eq!(f.run(&[b"EXISTS", b"d"]), ":0\r\n");
6653        assert_eq!(f.run(&[b"SINTERSTORE", b"a", b"a", b"b"]), ":2\r\n");
6654        assert_eq!(sorted(&f.run(&[b"SMEMBERS", b"a"])), ["2", "3"]);
6655
6656        // And a destination holding something else is overwritten, the same way
6657        // SET overwrites, rather than refused.
6658        f.run(&[b"SET", b"str", b"v"]);
6659        assert_eq!(f.run(&[b"SUNIONSTORE", b"str", b"b"]), ":3\r\n");
6660        assert_eq!(f.run(&[b"TYPE", b"str"]), "+set\r\n");
6661    }
6662
6663    #[test]
6664    fn sintercard_counts_without_building_and_stops_at_a_limit() {
6665        let mut f = Fixture::new();
6666        f.run(&[b"SADD", b"a", b"1", b"2", b"3", b"4"]);
6667        f.run(&[b"SADD", b"b", b"2", b"3", b"4", b"5"]);
6668
6669        assert_eq!(f.run(&[b"SINTERCARD", b"2", b"a", b"b"]), ":3\r\n");
6670        assert_eq!(
6671            f.run(&[b"SINTERCARD", b"2", b"a", b"b", b"LIMIT", b"2"]),
6672            ":2\r\n"
6673        );
6674        assert_eq!(
6675            f.run(&[b"SINTERCARD", b"2", b"a", b"b", b"LIMIT", b"0"]),
6676            ":3\r\n",
6677            "a limit of zero is no limit"
6678        );
6679        assert_eq!(f.run(&[b"SINTERCARD", b"1", b"a"]), ":4\r\n");
6680        assert_eq!(f.run(&[b"SINTERCARD", b"2", b"a", b"nope"]), ":0\r\n");
6681
6682        // The counted keys are what make its three error messages its own.
6683        assert_eq!(
6684            f.run(&[b"SINTERCARD", b"0", b"a"]),
6685            "-ERR numkeys should be greater than 0\r\n"
6686        );
6687        assert_eq!(
6688            f.run(&[b"SINTERCARD", b"abc", b"a"]),
6689            "-ERR numkeys should be greater than 0\r\n"
6690        );
6691        assert_eq!(
6692            f.run(&[b"SINTERCARD", b"3", b"a", b"b"]),
6693            "-ERR Number of keys can't be greater than number of args\r\n"
6694        );
6695        assert_eq!(
6696            f.run(&[b"SINTERCARD", b"2", b"a", b"b", b"LIMIT", b"-1"]),
6697            "-ERR LIMIT can't be negative\r\n"
6698        );
6699        assert_eq!(
6700            f.run(&[b"SINTERCARD", b"2", b"a", b"b", b"NOPE", b"1"]),
6701            "-ERR syntax error\r\n"
6702        );
6703        // A key really can be called LIMIT, which is why the count exists.
6704        f.run(&[b"SADD", b"LIMIT", b"2"]);
6705        assert_eq!(f.run(&[b"SINTERCARD", b"2", b"a", b"LIMIT"]), ":1\r\n");
6706    }
6707
6708    /// The two Redis 8.10 added, which are SINTERCARD's shape over a union and
6709    /// over a difference. Every number here was read off 8.10.1 first.
6710    #[test]
6711    fn sunioncard_and_sdiffcard_count_without_building() {
6712        let mut f = Fixture::new();
6713        f.run(&[b"SADD", b"a", b"1", b"2", b"3", b"4"]);
6714        f.run(&[b"SADD", b"b", b"3", b"4", b"5", b"6"]);
6715
6716        assert_eq!(f.run(&[b"SUNIONCARD", b"2", b"a", b"b"]), ":6\r\n");
6717        assert_eq!(
6718            f.run(&[b"SUNIONCARD", b"2", b"a", b"b", b"LIMIT", b"2"]),
6719            ":2\r\n"
6720        );
6721        assert_eq!(
6722            f.run(&[b"SUNIONCARD", b"2", b"a", b"b", b"LIMIT", b"0"]),
6723            ":6\r\n",
6724            "a limit of zero is no limit"
6725        );
6726        assert_eq!(f.run(&[b"SUNIONCARD", b"1", b"a"]), ":4\r\n");
6727        assert_eq!(
6728            f.run(&[b"SUNIONCARD", b"2", b"a", b"nope"]),
6729            ":4\r\n",
6730            "a missing key adds nothing to a union"
6731        );
6732
6733        assert_eq!(f.run(&[b"SDIFFCARD", b"2", b"a", b"b"]), ":2\r\n");
6734        assert_eq!(
6735            f.run(&[b"SDIFFCARD", b"2", b"a", b"b", b"LIMIT", b"1"]),
6736            ":1\r\n"
6737        );
6738        assert_eq!(
6739            f.run(&[b"SDIFFCARD", b"2", b"b", b"a"]),
6740            ":2\r\n",
6741            "a difference is not symmetric"
6742        );
6743        assert_eq!(f.run(&[b"SDIFFCARD", b"1", b"a"]), ":4\r\n");
6744        assert_eq!(f.run(&[b"SDIFFCARD", b"2", b"a", b"nope"]), ":4\r\n");
6745        assert_eq!(
6746            f.run(&[b"SDIFFCARD", b"2", b"nope", b"a"]),
6747            ":0\r\n",
6748            "nothing taken away from nothing"
6749        );
6750
6751        // The same three messages SINTERCARD has, because the line is the same
6752        // line and is parsed once for all three.
6753        for name in [b"SUNIONCARD".as_slice(), b"SDIFFCARD".as_slice()] {
6754            assert_eq!(
6755                f.run(&[name, b"0", b"a"]),
6756                "-ERR numkeys should be greater than 0\r\n"
6757            );
6758            assert_eq!(
6759                f.run(&[name, b"abc", b"a"]),
6760                "-ERR numkeys should be greater than 0\r\n"
6761            );
6762            assert_eq!(
6763                f.run(&[name, b"-1", b"a"]),
6764                "-ERR numkeys should be greater than 0\r\n"
6765            );
6766            assert_eq!(
6767                f.run(&[name, b"3", b"a", b"b"]),
6768                "-ERR Number of keys can't be greater than number of args\r\n"
6769            );
6770            assert_eq!(
6771                f.run(&[name, b"2", b"a", b"b", b"LIMIT", b"-1"]),
6772                "-ERR LIMIT can't be negative\r\n"
6773            );
6774            assert_eq!(
6775                f.run(&[name, b"2", b"a", b"b", b"LIMIT", b"abc"]),
6776                "-ERR LIMIT can't be negative\r\n",
6777                "a LIMIT that is not a number gets the negative message too"
6778            );
6779            assert_eq!(
6780                f.run(&[name, b"2", b"a", b"b", b"NOPE", b"1"]),
6781                "-ERR syntax error\r\n"
6782            );
6783            assert_eq!(
6784                f.run(&[name, b"2", b"a", b"b", b"LIMIT"]),
6785                "-ERR syntax error\r\n"
6786            );
6787            assert_eq!(
6788                f.run(&[name, b"2", b"a", b"b", b"LIMIT", b"1", b"X"]),
6789                "-ERR syntax error\r\n"
6790            );
6791        }
6792
6793        // And a key called LIMIT is a key, here as much as on SINTERCARD.
6794        f.run(&[b"SADD", b"LIMIT", b"2"]);
6795        assert_eq!(f.run(&[b"SUNIONCARD", b"2", b"a", b"LIMIT"]), ":4\r\n");
6796        assert_eq!(f.run(&[b"SDIFFCARD", b"2", b"a", b"LIMIT"]), ":3\r\n");
6797    }
6798
6799    #[test]
6800    fn the_algebra_answers_wrongtype_before_it_writes_anything() {
6801        let mut f = Fixture::new();
6802        f.run(&[b"SADD", b"a", b"1"]);
6803        f.run(&[b"SADD", b"d", b"old"]);
6804        f.run(&[b"SET", b"str", b"v"]);
6805
6806        let wrong = "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n";
6807        for bad in [
6808            &[b"SINTER".as_slice(), b"a", b"str"][..],
6809            &[b"SUNION".as_slice(), b"str"][..],
6810            &[b"SDIFF".as_slice(), b"a", b"str"][..],
6811            &[b"SINTERCARD".as_slice(), b"2", b"a", b"str"][..],
6812            &[b"SINTERSTORE".as_slice(), b"d", b"a", b"str"][..],
6813            &[b"SUNIONSTORE".as_slice(), b"d", b"str"][..],
6814            &[b"SDIFFSTORE".as_slice(), b"d", b"a", b"str"][..],
6815        ] {
6816            let reply = f.run(bad);
6817            assert_eq!(reply, wrong, "for {:?}", bad[0]);
6818        }
6819        assert_eq!(
6820            f.run(&[b"SMEMBERS", b"d"]),
6821            "*1\r\n$3\r\nold\r\n",
6822            "and the destination was left alone every time"
6823        );
6824    }
6825
6826    /// The leak a set can spring that nothing on the wire would ever show: the
6827    /// key goes, the body does not, and `DBSIZE` looks right the whole time.
6828    /// Not under Miri. What this claims is that memory does not grow over two
6829    /// hundred passes, so the passes are the claim rather than the way it
6830    /// happens to be written, and two hundred passes of a two hundred member
6831    /// collection is forty thousand trips through dispatch, which is what an
6832    /// interpreter charges for. A count small enough to run there would leave a
6833    /// server that reclaims nothing inside the bound and the test would pass on
6834    /// a leak. Nothing about memory safety goes uninterpreted either way: this
6835    /// is an accounting claim, and the same commands are run a few at a time by
6836    /// the tests around it.
6837    #[cfg_attr(miri, ignore = "the volume is the claim")]
6838    #[test]
6839    fn churning_sets_does_not_grow_the_server() {
6840        let mut f = Fixture::new();
6841        let members: Vec<Vec<u8>> = (0..200).map(|i| format!("m{i}").into_bytes()).collect();
6842        let args: Vec<&[u8]> = std::iter::once(&b"SADD"[..])
6843            .chain(std::iter::once(&b"s"[..]))
6844            .chain(members.iter().map(Vec::as_slice))
6845            .collect();
6846
6847        f.run(&args);
6848        f.run(&[b"DEL", b"s"]);
6849        f.server.compact_step();
6850        let after_first = f.server.memory_bytes();
6851
6852        for _ in 0..200 {
6853            f.run(&args);
6854            f.run(&[b"DEL", b"s"]);
6855            f.server.compact_step();
6856        }
6857        assert_eq!(f.run(&[b"DBSIZE"]), ":0\r\n");
6858        assert!(
6859            f.server.memory_bytes() <= after_first * 2,
6860            "held {} after two hundred passes against {after_first} after one",
6861            f.server.memory_bytes()
6862        );
6863    }
6864
6865    // --------------------------------------------------------------- bitmaps
6866
6867    /// The two single bit commands, and the encoding rule underneath them.
6868    ///
6869    /// A write always leaves the value `raw` and a read never re-encodes, which
6870    /// is why the `int` key here is still `int` after a `GETBIT` and is `raw`
6871    /// with its first digit changed after a `SETBIT`.
6872    #[test]
6873    fn a_bit_is_written_and_read_back_and_a_write_unpacks_an_int() {
6874        let mut f = Fixture::new();
6875        assert_eq!(f.run(&[b"SETBIT", b"k", b"7", b"1"]), ":0\r\n");
6876        assert_eq!(f.run(&[b"GET", b"k"]), "$1\r\n\u{1}\r\n");
6877        assert_eq!(f.run(&[b"GETBIT", b"k", b"7"]), ":1\r\n");
6878        assert_eq!(f.run(&[b"GETBIT", b"k", b"6"]), ":0\r\n");
6879        assert_eq!(f.run(&[b"GETBIT", b"k", b"100"]), ":0\r\n");
6880        assert_eq!(f.run(&[b"SETBIT", b"k", b"7", b"0"]), ":1\r\n");
6881
6882        // Writing a nought past the end still creates the key and still pads.
6883        assert_eq!(f.run(&[b"SETBIT", b"nk", b"0", b"0"]), ":0\r\n");
6884        assert_eq!(f.run(&[b"STRLEN", b"nk"]), ":1\r\n");
6885        assert_eq!(f.run(&[b"OBJECT", b"ENCODING", b"nk"]), "$3\r\nraw\r\n");
6886
6887        f.run(&[b"SET", b"num", b"12345"]);
6888        assert_eq!(f.run(&[b"GETBIT", b"num", b"1"]), ":0\r\n");
6889        assert_eq!(f.run(&[b"OBJECT", b"ENCODING", b"num"]), "$3\r\nint\r\n");
6890        assert_eq!(f.run(&[b"SETBIT", b"num", b"1", b"1"]), ":0\r\n");
6891        assert_eq!(f.run(&[b"OBJECT", b"ENCODING", b"num"]), "$3\r\nraw\r\n");
6892        assert_eq!(f.run(&[b"GET", b"num"]), "$5\r\nq2345\r\n");
6893    }
6894
6895    /// Counting, in bytes and in bits.
6896    ///
6897    /// The `0 -5 BIT` row is 25 on a real 8.10.1 and Redis's own documentation
6898    /// says 22 for it. The server is the thing being copied here.
6899    #[test]
6900    fn bits_are_counted_over_a_range_of_bytes_or_of_bits() {
6901        let mut f = Fixture::new();
6902        f.run(&[b"SET", b"mykey", b"foobar"]);
6903        assert_eq!(f.run(&[b"BITCOUNT", b"mykey"]), ":26\r\n");
6904        assert_eq!(f.run(&[b"BITCOUNT", b"mykey", b"0", b"0"]), ":4\r\n");
6905        assert_eq!(f.run(&[b"BITCOUNT", b"mykey", b"1", b"1"]), ":6\r\n");
6906        assert_eq!(
6907            f.run(&[b"BITCOUNT", b"mykey", b"1", b"1", b"BYTE"]),
6908            ":6\r\n"
6909        );
6910        assert_eq!(
6911            f.run(&[b"BITCOUNT", b"mykey", b"0", b"-5", b"BIT"]),
6912            ":25\r\n"
6913        );
6914        assert_eq!(
6915            f.run(&[b"BITCOUNT", b"mykey", b"5", b"30", b"BIT"]),
6916            ":17\r\n"
6917        );
6918        assert_eq!(f.run(&[b"BITCOUNT", b"nokey"]), ":0\r\n");
6919
6920        // A start past the end is left where it is and the end is pulled back,
6921        // so the range comes out backwards and counts nothing.
6922        assert_eq!(f.run(&[b"BITCOUNT", b"mykey", b"10", b"20"]), ":0\r\n");
6923
6924        // A lone start is a syntax error here, where BITPOS allows it.
6925        assert_eq!(
6926            f.run(&[b"BITCOUNT", b"mykey", b"0"]),
6927            "-ERR syntax error\r\n"
6928        );
6929        assert_eq!(
6930            f.run(&[b"BITCOUNT", b"mykey", b"0", b"1", b"NIB"]),
6931            "-ERR syntax error\r\n"
6932        );
6933    }
6934
6935    /// Searching, and the one place a miss is not minus one.
6936    ///
6937    /// A search for a nought that runs to the end of the string answers the
6938    /// length in bits, because the string is treated as if it had noughts after
6939    /// it forever. Give it an explicit end and it answers minus one instead.
6940    #[test]
6941    fn a_search_for_a_nought_past_the_end_answers_the_length_in_bits() {
6942        let mut f = Fixture::new();
6943        f.run(&[b"SET", b"ones", b"\xff\xff\xff"]);
6944        assert_eq!(f.run(&[b"BITPOS", b"ones", b"0"]), ":24\r\n");
6945        assert_eq!(f.run(&[b"BITPOS", b"ones", b"0", b"0"]), ":24\r\n");
6946        assert_eq!(f.run(&[b"BITPOS", b"ones", b"0", b"0", b"-1"]), ":-1\r\n");
6947        assert_eq!(f.run(&[b"BITPOS", b"ones", b"0", b"0", b"3"]), ":-1\r\n");
6948        assert_eq!(f.run(&[b"BITPOS", b"ones", b"1"]), ":0\r\n");
6949
6950        f.run(&[b"SET", b"mid", b"\x00\xff\xf0"]);
6951        assert_eq!(f.run(&[b"BITPOS", b"mid", b"1", b"0"]), ":8\r\n");
6952        assert_eq!(f.run(&[b"BITPOS", b"mid", b"1", b"2"]), ":16\r\n");
6953        assert_eq!(
6954            f.run(&[b"BITPOS", b"mid", b"1", b"0", b"-1", b"BIT"]),
6955            ":8\r\n"
6956        );
6957
6958        // A missing key is all noughts, so a one is never found and a nought is
6959        // at position zero.
6960        assert_eq!(f.run(&[b"BITPOS", b"gone", b"1"]), ":-1\r\n");
6961        assert_eq!(f.run(&[b"BITPOS", b"gone", b"0"]), ":0\r\n");
6962    }
6963
6964    /// The eight operations, with the answers a real server gives for them.
6965    #[test]
6966    fn the_eight_combinations_write_what_a_real_server_writes() {
6967        let mut f = Fixture::new();
6968        f.run(&[b"SET", b"a", b"abc"]);
6969        f.run(&[b"SET", b"b", b"abd"]);
6970        let cases: &[(&[u8], &str)] = &[
6971            (b"AND", "ab`"),
6972            (b"OR", "abg"),
6973            (b"XOR", "\u{0}\u{0}\u{7}"),
6974            (b"DIFF", "\u{0}\u{0}\u{3}"),
6975            (b"DIFF1", "\u{0}\u{0}\u{4}"),
6976            (b"ANDOR", "ab`"),
6977            (b"ONE", "\u{0}\u{0}\u{7}"),
6978        ];
6979        for (op, want) in cases {
6980            assert_eq!(f.run(&[b"BITOP", op, b"d", b"a", b"b"]), ":3\r\n", "{op:?}");
6981            assert_eq!(
6982                f.run(&[b"GET", b"d"]),
6983                format!("$3\r\n{want}\r\n"),
6984                "{op:?}"
6985            );
6986        }
6987        // The one whose answer is not text, so it is compared as bytes.
6988        assert_eq!(f.run(&[b"BITOP", b"NOT", b"d", b"a"]), ":3\r\n");
6989        assert_eq!(f.raw(&[b"GET", b"d"]), b"$3\r\n\x9e\x9d\x9c\r\n".to_vec());
6990
6991        // A missing source is a string of noughts as long as it needs to be, so
6992        // an AND against one writes three zero bytes rather than nothing.
6993        assert_eq!(f.run(&[b"BITOP", b"AND", b"d", b"a", b"gone"]), ":3\r\n");
6994        assert_eq!(f.run(&[b"GET", b"d"]), "$3\r\n\u{0}\u{0}\u{0}\r\n");
6995
6996        // Every source missing is an empty result, and an empty result takes
6997        // the destination with it.
6998        f.run(&[b"SET", b"dest", b"x"]);
6999        assert_eq!(f.run(&[b"BITOP", b"AND", b"dest", b"g1", b"g2"]), ":0\r\n");
7000        assert_eq!(f.run(&[b"EXISTS", b"dest"]), ":0\r\n");
7001    }
7002
7003    /// What `BITOP` says when it is asked for something it cannot do.
7004    #[test]
7005    fn bitop_names_the_operation_in_its_own_complaints() {
7006        let mut f = Fixture::new();
7007        f.run(&[b"SET", b"a", b"abc"]);
7008        assert_eq!(
7009            f.run(&[b"BITOP", b"nope", b"d", b"a"]),
7010            "-ERR syntax error\r\n"
7011        );
7012        assert_eq!(
7013            f.run(&[b"BITOP", b"NOT", b"d", b"a", b"a"]),
7014            "-ERR BITOP NOT must be called with a single source key.\r\n"
7015        );
7016        for op in [&b"DIFF"[..], b"DIFF1", b"ANDOR"] {
7017            assert_eq!(
7018                f.run(&[b"BITOP", op, b"d", b"a"]),
7019                format!(
7020                    "-ERR BITOP {} must be called with at least two source keys.\r\n",
7021                    String::from_utf8_lossy(op)
7022                )
7023            );
7024        }
7025        f.run(&[b"LPUSH", b"l", b"x"]);
7026        assert_eq!(
7027            f.run(&[b"BITOP", b"AND", b"d", b"a", b"l"]),
7028            "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n"
7029        );
7030    }
7031
7032    /// Packed fields, the three overflow policies and the `#` offset.
7033    #[test]
7034    fn bitfield_reads_and_writes_packed_fields() {
7035        let mut f = Fixture::new();
7036        assert_eq!(f.run(&[b"BITFIELD", b"bf"]), "*0\r\n");
7037        assert_eq!(f.run(&[b"EXISTS", b"bf"]), ":0\r\n");
7038
7039        assert_eq!(
7040            f.run(&[
7041                b"BITFIELD",
7042                b"bf",
7043                b"INCRBY",
7044                b"u2",
7045                b"100",
7046                b"1",
7047                b"GET",
7048                b"u4",
7049                b"0"
7050            ]),
7051            "*2\r\n:1\r\n:0\r\n"
7052        );
7053        // The field at bit 100 is two bits wide, so it ends in the thirteenth
7054        // byte and the value grew to thirteen bytes to hold it.
7055        assert_eq!(f.run(&[b"STRLEN", b"bf"]), ":13\r\n");
7056
7057        // A `#` offset counts in fields rather than in bits.
7058        assert_eq!(
7059            f.run(&[
7060                b"BITFIELD",
7061                b"bf",
7062                b"SET",
7063                b"u8",
7064                b"#0",
7065                b"255",
7066                b"GET",
7067                b"u8",
7068                b"#0"
7069            ]),
7070            "*2\r\n:0\r\n:255\r\n"
7071        );
7072
7073        assert_eq!(
7074            f.run(&[
7075                b"BITFIELD",
7076                b"bf",
7077                b"OVERFLOW",
7078                b"SAT",
7079                b"INCRBY",
7080                b"i8",
7081                b"0",
7082                b"120",
7083                b"INCRBY",
7084                b"i8",
7085                b"0",
7086                b"120"
7087            ]),
7088            "*2\r\n:119\r\n:127\r\n"
7089        );
7090        assert_eq!(
7091            f.run(&[
7092                b"BITFIELD",
7093                b"bf2",
7094                b"OVERFLOW",
7095                b"FAIL",
7096                b"INCRBY",
7097                b"u2",
7098                b"0",
7099                b"5"
7100            ]),
7101            "*1\r\n$-1\r\n"
7102        );
7103        assert_eq!(
7104            f.run(&[
7105                b"BITFIELD",
7106                b"bf3",
7107                b"OVERFLOW",
7108                b"WRAP",
7109                b"INCRBY",
7110                b"u2",
7111                b"0",
7112                b"5"
7113            ]),
7114            "*1\r\n:1\r\n"
7115        );
7116        assert_eq!(
7117            f.run(&[b"BITFIELD", b"bf3", b"GET", b"i64", b"0"]),
7118            "*1\r\n:4611686018427387904\r\n"
7119        );
7120    }
7121
7122    /// A bad subcommand anywhere in the line stops all of it.
7123    ///
7124    /// Redis checks the whole argument list before it runs any of it, so the
7125    /// `SET` in front of the bad type here never happens and the key it would
7126    /// have created is not there afterwards.
7127    #[test]
7128    fn a_bad_bitfield_subcommand_leaves_the_key_alone() {
7129        let mut f = Fixture::new();
7130        let bad_type = "-ERR Invalid bitfield type. Use something like i16 u8. Note that u64 is not supported but i64 is.\r\n";
7131        assert_eq!(
7132            f.run(&[
7133                b"BITFIELD",
7134                b"bad",
7135                b"SET",
7136                b"u8",
7137                b"0",
7138                b"1",
7139                b"GET",
7140                b"u99",
7141                b"0"
7142            ]),
7143            bad_type
7144        );
7145        assert_eq!(f.run(&[b"EXISTS", b"bad"]), ":0\r\n");
7146        assert_eq!(
7147            f.run(&[b"BITFIELD", b"bad", b"GET", b"u64", b"0"]),
7148            bad_type
7149        );
7150        assert_eq!(
7151            f.run(&[b"BITFIELD", b"bad", b"GET"]),
7152            "-ERR syntax error\r\n"
7153        );
7154        assert_eq!(
7155            f.run(&[b"BITFIELD", b"bad", b"NOPE", b"u8", b"0"]),
7156            "-ERR syntax error\r\n"
7157        );
7158        assert_eq!(
7159            f.run(&[b"BITFIELD", b"bad", b"OVERFLOW"]),
7160            "-ERR syntax error\r\n"
7161        );
7162        assert_eq!(
7163            f.run(&[
7164                b"BITFIELD",
7165                b"bad",
7166                b"OVERFLOW",
7167                b"NOPE",
7168                b"GET",
7169                b"u8",
7170                b"0"
7171            ]),
7172            "-ERR Invalid OVERFLOW type specified\r\n"
7173        );
7174        assert_eq!(
7175            f.run(&[b"BITFIELD", b"bad", b"SET", b"u8", b"0", b"notanum"]),
7176            "-ERR value is not an integer or out of range\r\n"
7177        );
7178        for at in [&b"#-1"[..], b"abc"] {
7179            assert_eq!(
7180                f.run(&[b"BITFIELD", b"bad", b"GET", b"u8", at]),
7181                "-ERR bit offset is not an integer or out of range\r\n"
7182            );
7183        }
7184    }
7185
7186    /// The read only twin reads, refuses to write, and creates nothing.
7187    #[test]
7188    fn bitfield_ro_answers_gets_and_refuses_the_rest() {
7189        let mut f = Fixture::new();
7190        f.run(&[b"SET", b"n", b"123"]);
7191        assert_eq!(
7192            f.run(&[b"BITFIELD_RO", b"n", b"GET", b"u8", b"0"]),
7193            "*1\r\n:49\r\n"
7194        );
7195        // A read does not unpack an int the way a write does.
7196        assert_eq!(f.run(&[b"OBJECT", b"ENCODING", b"n"]), "$3\r\nint\r\n");
7197
7198        // An OVERFLOW word is allowed even though nothing here can overflow.
7199        assert_eq!(
7200            f.run(&[
7201                b"BITFIELD_RO",
7202                b"n",
7203                b"OVERFLOW",
7204                b"SAT",
7205                b"GET",
7206                b"u8",
7207                b"0"
7208            ]),
7209            "*1\r\n:49\r\n"
7210        );
7211        for sub in [&b"SET"[..], b"INCRBY"] {
7212            assert_eq!(
7213                f.run(&[b"BITFIELD_RO", b"n", sub, b"u8", b"0", b"1"]),
7214                "-ERR BITFIELD_RO only supports the GET subcommand\r\n"
7215            );
7216        }
7217
7218        assert_eq!(
7219            f.run(&[b"BITFIELD_RO", b"gone", b"GET", b"u8", b"100"]),
7220            "*1\r\n:0\r\n"
7221        );
7222        assert_eq!(f.run(&[b"EXISTS", b"gone"]), ":0\r\n");
7223    }
7224
7225    /// The offsets a bitmap command will not take.
7226    #[test]
7227    fn an_offset_off_the_end_of_the_world_is_refused() {
7228        let mut f = Fixture::new();
7229        let bad = "-ERR bit offset is not an integer or out of range\r\n";
7230        for arg in [&b"abc"[..], b"-1", b"4294967296"] {
7231            assert_eq!(f.run(&[b"SETBIT", b"k", arg, b"1"]), bad);
7232            assert_eq!(f.run(&[b"GETBIT", b"k", arg]), bad);
7233        }
7234        for arg in [&b"2"[..], b"-1"] {
7235            assert_eq!(
7236                f.run(&[b"BITPOS", b"k", arg]),
7237                "-ERR The bit argument must be 1 or 0.\r\n"
7238            );
7239        }
7240        assert_eq!(
7241            f.run(&[b"BITPOS", b"k", b"abc"]),
7242            "-ERR value is not an integer or out of range\r\n"
7243        );
7244        assert_eq!(
7245            f.run(&[b"BITPOS", b"k", b"0", b"5", b"BIT"]),
7246            "-ERR value is not an integer or out of range\r\n"
7247        );
7248        let bad_bit = "-ERR bit is not an integer or out of range\r\n";
7249        assert_eq!(f.run(&[b"SETBIT", b"k", b"0", b"2"]), bad_bit);
7250        assert_eq!(f.run(&[b"SETBIT", b"k", b"0", b"abc"]), bad_bit);
7251    }
7252
7253    /// Every one of the seven refuses a key that is not a string.
7254    #[test]
7255    fn every_bitmap_command_says_wrongtype() {
7256        let mut f = Fixture::new();
7257        f.run(&[b"LPUSH", b"l", b"x"]);
7258        let wrong = "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n";
7259        let cases: &[&[&[u8]]] = &[
7260            &[b"SETBIT", b"l", b"0", b"1"],
7261            &[b"GETBIT", b"l", b"0"],
7262            &[b"BITCOUNT", b"l"],
7263            &[b"BITPOS", b"l", b"1"],
7264            &[b"BITOP", b"AND", b"d", b"l"],
7265            &[b"BITFIELD", b"l", b"GET", b"u8", b"0"],
7266            &[b"BITFIELD_RO", b"l", b"GET", b"u8", b"0"],
7267        ];
7268        for case in cases {
7269            assert_eq!(f.run(case), wrong, "{:?}", case[0]);
7270        }
7271    }
7272
7273    // --------------------------------------------------------- hyperloglogs
7274
7275    #[test]
7276    fn a_sketch_is_added_to_and_counted() {
7277        let mut f = Fixture::new();
7278        // Creating the key counts as a change, even with nothing to add.
7279        assert_eq!(f.run(&[b"PFADD", b"h"]), ":1\r\n");
7280        assert_eq!(f.run(&[b"PFADD", b"h"]), ":0\r\n");
7281        assert_eq!(f.run(&[b"PFCOUNT", b"h"]), ":0\r\n");
7282        assert_eq!(f.run(&[b"STRLEN", b"h"]), ":18\r\n");
7283        // And it is a string, which is not an implementation detail: a client
7284        // can `GET` a sketch out of one server and `SET` it into another.
7285        assert_eq!(f.run(&[b"TYPE", b"h"]), "+string\r\n");
7286        assert_eq!(f.run(&[b"OBJECT", b"ENCODING", b"h"]), "$3\r\nraw\r\n");
7287
7288        assert_eq!(f.run(&[b"PFADD", b"h", b"a", b"b", b"c"]), ":1\r\n");
7289        assert_eq!(f.run(&[b"PFADD", b"h", b"a"]), ":0\r\n");
7290        assert_eq!(f.run(&[b"PFCOUNT", b"h"]), ":3\r\n");
7291    }
7292
7293    #[test]
7294    fn the_bytes_of_a_sketch_are_the_ones_a_real_server_writes() {
7295        let mut f = Fixture::new();
7296        f.run(&[b"PFADD", b"h", b"a", b"b", b"c"]);
7297        // Not text, so it is compared as bytes.
7298        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";
7299        let mut reply = b"$27\r\n".to_vec();
7300        reply.extend_from_slice(want);
7301        reply.extend_from_slice(b"\r\n");
7302        assert_eq!(f.raw(&[b"GET", b"h"]), reply);
7303    }
7304
7305    #[test]
7306    fn counting_several_keys_counts_their_union() {
7307        let mut f = Fixture::new();
7308        f.run(&[b"PFADD", b"a", b"x", b"y"]);
7309        f.run(&[b"PFADD", b"b", b"y", b"z"]);
7310        assert_eq!(f.run(&[b"PFCOUNT", b"a"]), ":2\r\n");
7311        assert_eq!(f.run(&[b"PFCOUNT", b"a", b"b"]), ":3\r\n");
7312        // A key that is not there is an empty sketch, not an error and not
7313        // something that gets created by being counted.
7314        assert_eq!(f.run(&[b"PFCOUNT", b"gone"]), ":0\r\n");
7315        assert_eq!(f.run(&[b"PFCOUNT", b"a", b"gone"]), ":2\r\n");
7316        assert_eq!(f.run(&[b"EXISTS", b"gone"]), ":0\r\n");
7317    }
7318
7319    #[test]
7320    fn a_merge_keeps_what_the_destination_had() {
7321        let mut f = Fixture::new();
7322        f.run(&[b"PFADD", b"a", b"x", b"y"]);
7323        f.run(&[b"PFADD", b"b", b"z"]);
7324        assert_eq!(f.run(&[b"PFMERGE", b"d", b"a", b"b"]), "+OK\r\n");
7325        assert_eq!(f.run(&[b"PFCOUNT", b"d"]), ":3\r\n");
7326        // The destination is one of the sources, so a second merge adds to it.
7327        f.run(&[b"PFADD", b"c", b"w"]);
7328        assert_eq!(f.run(&[b"PFMERGE", b"d", b"c"]), "+OK\r\n");
7329        assert_eq!(f.run(&[b"PFCOUNT", b"d"]), ":4\r\n");
7330        // And with no sources it is a no-op that still answers OK and still
7331        // creates a destination that was not there.
7332        assert_eq!(f.run(&[b"PFMERGE", b"fresh"]), "+OK\r\n");
7333        assert_eq!(f.run(&[b"PFCOUNT", b"fresh"]), ":0\r\n");
7334    }
7335
7336    /// Not under Miri, and not for the number of commands: a dense sketch is
7337    /// sixteen thousand three hundred and eighty four registers and every
7338    /// command here walks all of them, so one `PFCOUNT` is more interpreted
7339    /// work than a hundred ordinary tests. The registers and the walking are in
7340    /// `yo-kv`, where fifteen tests of their own cover both encodings and where
7341    /// the interpreter does run over them. What is left here is the dispatch
7342    /// around it, which is the same dispatch every other command in this file
7343    /// goes through.
7344    #[cfg_attr(miri, ignore = "sixteen thousand registers a command")]
7345    #[test]
7346    fn the_debug_forms_answer_four_different_shapes() {
7347        let mut f = Fixture::new();
7348        f.run(&[b"PFADD", b"h", b"a", b"b", b"c"]);
7349        assert_eq!(f.run(&[b"PFDEBUG", b"ENCODING", b"h"]), "+sparse\r\n");
7350        assert_eq!(
7351            f.run(&[b"PFDEBUG", b"DECODE", b"h"]),
7352            "$44\r\nZ:8436 v:1,1 Z:4274 v:2,1 Z:3068 v:1,1 Z:603\r\n"
7353        );
7354        assert_eq!(f.run(&[b"PFDEBUG", b"TODENSE", b"h"]), ":1\r\n");
7355        assert_eq!(f.run(&[b"PFDEBUG", b"TODENSE", b"h"]), ":0\r\n");
7356        assert_eq!(f.run(&[b"PFDEBUG", b"ENCODING", b"h"]), "+dense\r\n");
7357        assert_eq!(f.run(&[b"STRLEN", b"h"]), ":12304\r\n");
7358        assert_eq!(f.run(&[b"PFCOUNT", b"h"]), ":3\r\n");
7359        // A dense sketch has no opcodes left to print.
7360        assert_eq!(
7361            f.run(&[b"PFDEBUG", b"DECODE", b"h"]),
7362            "-ERR HLL encoding is not sparse\r\n"
7363        );
7364
7365        // All 16384 registers, of which three are not nought.
7366        let reply = f.run(&[b"PFDEBUG", b"GETREG", b"h"]);
7367        assert!(reply.starts_with("*16384\r\n"), "{}", &reply[..16]);
7368        assert_eq!(reply.matches(":0\r\n").count(), 16381);
7369        assert_eq!(reply.matches(":1\r\n").count(), 2);
7370        assert_eq!(reply.matches(":2\r\n").count(), 1);
7371
7372        assert_eq!(f.run(&[b"PFSELFTEST"]), "+OK\r\n");
7373    }
7374
7375    #[test]
7376    fn a_string_that_is_not_a_sketch_is_refused_with_its_own_sentence() {
7377        let mut f = Fixture::new();
7378        f.run(&[b"SET", b"plain", b"not a sketch"]);
7379        let not_hll = "-WRONGTYPE Key is not a valid HyperLogLog string value.\r\n";
7380        assert_eq!(f.run(&[b"PFADD", b"plain", b"a"]), not_hll);
7381        assert_eq!(f.run(&[b"PFCOUNT", b"plain"]), not_hll);
7382        assert_eq!(f.run(&[b"PFMERGE", b"plain"]), not_hll);
7383        assert_eq!(f.run(&[b"PFDEBUG", b"ENCODING", b"plain"]), not_hll);
7384
7385        // A key that is not a string at all gets the ordinary sentence, and a
7386        // destination that would have been written is not created.
7387        f.run(&[b"RPUSH", b"l", b"x"]);
7388        let wrong = "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n";
7389        assert_eq!(f.run(&[b"PFADD", b"l", b"a"]), wrong);
7390        assert_eq!(f.run(&[b"PFCOUNT", b"l"]), wrong);
7391        assert_eq!(f.run(&[b"PFMERGE", b"dest", b"l"]), wrong);
7392        assert_eq!(f.run(&[b"EXISTS", b"dest"]), ":0\r\n");
7393        assert_eq!(f.run(&[b"PFDEBUG", b"GETREG", b"l"]), wrong);
7394    }
7395
7396    #[test]
7397    fn pfdebug_has_its_own_complaints() {
7398        let mut f = Fixture::new();
7399        f.run(&[b"PFADD", b"h", b"a"]);
7400        // The word is quoted exactly as the client spelled it, and this is not
7401        // the "Try X HELP." sentence every other container command uses.
7402        assert_eq!(
7403            f.run(&[b"PFDEBUG", b"NOPE", b"h"]),
7404            "-ERR Unknown PFDEBUG subcommand 'NOPE'\r\n"
7405        );
7406        // Where all three of the real commands take a missing key as empty.
7407        let gone = "-ERR The specified key does not exist\r\n";
7408        assert_eq!(f.run(&[b"PFDEBUG", b"GETREG", b"missing"]), gone);
7409        assert_eq!(f.run(&[b"PFDEBUG", b"DECODE", b"missing"]), gone);
7410        assert_eq!(f.run(&[b"PFDEBUG", b"ENCODING", b"missing"]), gone);
7411        assert_eq!(f.run(&[b"PFDEBUG", b"TODENSE", b"missing"]), gone);
7412        assert_eq!(
7413            f.run(&[b"PFDEBUG"]),
7414            "-ERR wrong number of arguments for 'pfdebug' command\r\n"
7415        );
7416        assert_eq!(
7417            f.run(&[b"PFSELFTEST", b"x"]),
7418            "-ERR wrong number of arguments for 'pfselftest' command\r\n"
7419        );
7420    }
7421
7422    #[test]
7423    fn a_sketch_whose_opcodes_do_not_add_up_says_so() {
7424        let mut f = Fixture::new();
7425        f.run(&[b"PFADD", b"h", b"a", b"b", b"c"]);
7426        // The sketch with its last byte cut off, which is still a header and a
7427        // magic and is a run length encoding that stops short of register 16384.
7428        let reply = f.raw(&[b"GET", b"h"]);
7429        let short = reply[5..reply.len() - 3].to_vec();
7430        f.run(&[b"SET", b"h", &short]);
7431        assert_eq!(
7432            f.run(&[b"PFCOUNT", b"h"]),
7433            "-INVALIDOBJ Corrupted HLL object detected\r\n"
7434        );
7435    }
7436
7437    #[test]
7438    fn a_sketch_survives_a_dump_and_a_restore_in_both_encodings() {
7439        let mut f = Fixture::new();
7440        // One that stays sparse and one that has gone dense, since the payload
7441        // carries the bytes and the two encodings are different lengths.
7442        f.run(&[b"PFADD", b"small", b"a", b"b", b"c"]);
7443        // Ten thousand elements is what takes a sketch dense on its own, and it
7444        // is ten thousand trips through dispatch, which is what Miri charges
7445        // for. There the same sketch is taken across by hand. What this test is
7446        // about is a dense payload surviving a round trip and the encoding is
7447        // dense either way: that a sketch converts when it fills up is what
7448        // `the_debug_forms_answer_four_different_shapes` is for.
7449        if cfg!(miri) {
7450            f.run(&[b"PFADD", b"big", b"a", b"b", b"c"]);
7451            f.run(&[b"PFDEBUG", b"TODENSE", b"big"]);
7452        } else {
7453            for i in 0..10_000u32 {
7454                let ele = format!("e{i}");
7455                f.run(&[b"PFADD", b"big", ele.as_bytes()]);
7456            }
7457        }
7458        assert_eq!(f.run(&[b"PFDEBUG", b"ENCODING", b"small"]), "+sparse\r\n");
7459        assert_eq!(f.run(&[b"PFDEBUG", b"ENCODING", b"big"]), "+dense\r\n");
7460
7461        for key in [&b"small"[..], b"big"] {
7462            let mut copy = key.to_vec();
7463            copy.push(b'2');
7464            let bytes = payload(&f.raw(&[b"DUMP", key]));
7465            assert_eq!(f.run(&[b"RESTORE", &copy, b"0", &bytes]), "+OK\r\n");
7466            // The bytes, the encoding and the estimate all come back, which is
7467            // the whole of what byte compatibility across a round trip means.
7468            assert_eq!(f.raw(&[b"GET", &copy]), f.raw(&[b"GET", key]));
7469            assert_eq!(
7470                f.run(&[b"PFDEBUG", b"ENCODING", &copy]),
7471                f.run(&[b"PFDEBUG", b"ENCODING", key])
7472            );
7473            assert_eq!(f.run(&[b"PFCOUNT", &copy]), f.run(&[b"PFCOUNT", key]));
7474        }
7475        assert_eq!(f.run(&[b"PFCOUNT", b"small2"]), ":3\r\n");
7476        assert_eq!(f.run(&[b"STRLEN", b"big2"]), ":12304\r\n");
7477    }
7478
7479    /// One RESP2 bulk string. The JSON replies are almost all one of these and
7480    /// the text inside them has quotes in it, so writing the frame out by hand
7481    /// buries the part of the assertion that matters.
7482    fn bulk(s: &str) -> String {
7483        format!("${}\r\n{s}\r\n", s.len())
7484    }
7485
7486    /// A RESP2 array of bulk strings, which is what most of the list replies
7487    /// are and what writing them out by hand in every assertion looks like.
7488    fn bulks(parts: &[&str]) -> String {
7489        let mut s = format!("*{}\r\n", parts.len());
7490        for p in parts {
7491            s.push_str(&format!("${}\r\n{p}\r\n", p.len()));
7492        }
7493        s
7494    }
7495
7496    #[test]
7497    fn a_list_is_pushed_from_both_ends_and_the_left_one_reverses() {
7498        let mut f = Fixture::new();
7499        // Each element in turn goes at the head, so the last one sent is at the
7500        // front when it is over. That reads like a bug in the client and it is
7501        // what every Redis has always done.
7502        assert_eq!(f.run(&[b"LPUSH", b"k", b"a", b"b", b"c"]), ":3\r\n");
7503        assert_eq!(
7504            f.run(&[b"LRANGE", b"k", b"0", b"-1"]),
7505            bulks(&["c", "b", "a"])
7506        );
7507        assert_eq!(f.run(&[b"RPUSH", b"k", b"d"]), ":4\r\n");
7508        assert_eq!(f.run(&[b"LLEN", b"k"]), ":4\r\n");
7509        assert_eq!(f.run(&[b"LPOP", b"k"]), "$1\r\nc\r\n");
7510        assert_eq!(f.run(&[b"RPOP", b"k"]), "$1\r\nd\r\n");
7511        assert_eq!(f.run(&[b"LRANGE", b"k", b"0", b"-1"]), bulks(&["b", "a"]));
7512        assert_eq!(f.run(&[b"TYPE", b"k"]), "+list\r\n");
7513    }
7514
7515    #[test]
7516    fn the_x_pushes_refuse_to_bring_a_list_back_to_life() {
7517        let mut f = Fixture::new();
7518        assert_eq!(f.run(&[b"LPUSHX", b"k", b"a"]), ":0\r\n");
7519        assert_eq!(f.run(&[b"RPUSHX", b"k", b"a"]), ":0\r\n");
7520        assert_eq!(f.run(&[b"EXISTS", b"k"]), ":0\r\n");
7521        f.run(&[b"RPUSH", b"k", b"a"]);
7522        assert_eq!(f.run(&[b"LPUSHX", b"k", b"z"]), ":2\r\n");
7523        assert_eq!(f.run(&[b"RPUSHX", b"k", b"y"]), ":3\r\n");
7524        assert_eq!(
7525            f.run(&[b"LRANGE", b"k", b"0", b"-1"]),
7526            bulks(&["z", "a", "y"])
7527        );
7528    }
7529
7530    /// The four ways a pop can come back with nothing, which are three
7531    /// different replies and a RESP2 client can tell all of them apart.
7532    #[test]
7533    fn an_empty_pop_is_a_different_nothing_with_a_count_and_without() {
7534        let mut f = Fixture::new();
7535        assert_eq!(f.run(&[b"LPOP", b"nope"]), "$-1\r\n");
7536        assert_eq!(f.run(&[b"LPOP", b"nope", b"2"]), "*-1\r\n");
7537        assert_eq!(f.run(&[b"RPOP", b"nope"]), "$-1\r\n");
7538        assert_eq!(f.run(&[b"RPOP", b"nope", b"2"]), "*-1\r\n");
7539        f.run(&[b"RPUSH", b"k", b"a", b"b", b"c"]);
7540        // A count of zero against a list that is there is an empty array and
7541        // not a null array, which is the fourth answer.
7542        assert_eq!(f.run(&[b"LPOP", b"k", b"0"]), "*0\r\n");
7543        assert_eq!(f.run(&[b"LPOP", b"k", b"1"]), bulks(&["a"]));
7544        // More than there is takes what there is and the key goes with it.
7545        assert_eq!(f.run(&[b"RPOP", b"k", b"9"]), bulks(&["c", "b"]));
7546        assert_eq!(f.run(&[b"EXISTS", b"k"]), ":0\r\n");
7547    }
7548
7549    #[test]
7550    fn a_pop_count_has_its_own_sentence_and_a_third_argument_is_an_arity_error() {
7551        let mut f = Fixture::new();
7552        f.run(&[b"RPUSH", b"k", b"a"]);
7553        let range = "-ERR value is out of range, must be positive\r\n";
7554        assert_eq!(f.run(&[b"LPOP", b"k", b"-1"]), range);
7555        assert_eq!(f.run(&[b"LPOP", b"k", b"abc"]), range);
7556        assert_eq!(f.run(&[b"RPOP", b"k", b"-1"]), range);
7557        // Redis calls this an arity error and not a syntax error, which is a
7558        // distinction it does not always make.
7559        assert_eq!(
7560            f.run(&[b"LPOP", b"k", b"1", b"2"]),
7561            "-ERR wrong number of arguments for 'lpop' command\r\n"
7562        );
7563        assert_eq!(f.run(&[b"LLEN", b"k"]), ":1\r\n");
7564    }
7565
7566    #[test]
7567    fn a_range_takes_negative_ends_and_clamps_the_ones_that_run_off() {
7568        let mut f = Fixture::new();
7569        f.run(&[b"RPUSH", b"k", b"a", b"b", b"c"]);
7570        assert_eq!(
7571            f.run(&[b"LRANGE", b"k", b"0", b"-1"]),
7572            bulks(&["a", "b", "c"])
7573        );
7574        assert_eq!(f.run(&[b"LRANGE", b"k", b"-2", b"-1"]), bulks(&["b", "c"]));
7575        assert_eq!(f.run(&[b"LRANGE", b"k", b"1", b"1"]), bulks(&["b"]));
7576        assert_eq!(f.run(&[b"LRANGE", b"k", b"5", b"10"]), "*0\r\n");
7577        assert_eq!(f.run(&[b"LRANGE", b"k", b"2", b"1"]), "*0\r\n");
7578        assert_eq!(
7579            f.run(&[b"LRANGE", b"k", b"-100", b"100"]),
7580            bulks(&["a", "b", "c"])
7581        );
7582        // A key that is not there is an empty range and not a nil, which is the
7583        // one place a list disagrees with a set.
7584        assert_eq!(f.run(&[b"LRANGE", b"nope", b"0", b"-1"]), "*0\r\n");
7585        assert_eq!(
7586            f.run(&[b"LRANGE", b"k", b"a", b"b"]),
7587            "-ERR value is not an integer or out of range\r\n"
7588        );
7589    }
7590
7591    #[test]
7592    fn an_index_reads_and_writes_from_whichever_end_is_nearer() {
7593        let mut f = Fixture::new();
7594        f.run(&[b"RPUSH", b"k", b"a", b"b", b"c"]);
7595        assert_eq!(f.run(&[b"LINDEX", b"k", b"0"]), "$1\r\na\r\n");
7596        assert_eq!(f.run(&[b"LINDEX", b"k", b"-1"]), "$1\r\nc\r\n");
7597        assert_eq!(f.run(&[b"LINDEX", b"k", b"99"]), "$-1\r\n");
7598        assert_eq!(f.run(&[b"LINDEX", b"nope", b"0"]), "$-1\r\n");
7599        assert_eq!(f.run(&[b"LSET", b"k", b"-1", b"z"]), "+OK\r\n");
7600        assert_eq!(
7601            f.run(&[b"LRANGE", b"k", b"0", b"-1"]),
7602            bulks(&["a", "b", "z"])
7603        );
7604        // Both ways of missing are errors here rather than a nil, because a
7605        // list is never empty and there is nothing else the reply could be.
7606        assert_eq!(
7607            f.run(&[b"LSET", b"k", b"99", b"z"]),
7608            "-ERR index out of range\r\n"
7609        );
7610        assert_eq!(
7611            f.run(&[b"LSET", b"nope", b"0", b"z"]),
7612            "-ERR no such key\r\n"
7613        );
7614    }
7615
7616    #[test]
7617    fn linsert_says_three_things_with_one_signed_number() {
7618        let mut f = Fixture::new();
7619        // Zero for a key that is not there, which is not the same as minus one
7620        // for a pivot that is not in a list that is.
7621        assert_eq!(
7622            f.run(&[b"LINSERT", b"nope", b"BEFORE", b"a", b"x"]),
7623            ":0\r\n"
7624        );
7625        f.run(&[b"RPUSH", b"k", b"a", b"b"]);
7626        assert_eq!(f.run(&[b"LINSERT", b"k", b"before", b"a", b"X"]), ":3\r\n");
7627        assert_eq!(f.run(&[b"LINSERT", b"k", b"AFTER", b"b", b"Y"]), ":4\r\n");
7628        assert_eq!(
7629            f.run(&[b"LRANGE", b"k", b"0", b"-1"]),
7630            bulks(&["X", "a", "b", "Y"])
7631        );
7632        assert_eq!(
7633            f.run(&[b"LINSERT", b"k", b"BEFORE", b"zz", b"x"]),
7634            ":-1\r\n"
7635        );
7636        assert_eq!(
7637            f.run(&[b"LINSERT", b"k", b"SIDEWAYS", b"a", b"x"]),
7638            "-ERR syntax error\r\n"
7639        );
7640    }
7641
7642    #[test]
7643    fn lrem_counts_in_three_directions_and_takes_the_key_when_it_empties() {
7644        let mut f = Fixture::new();
7645        f.run(&[b"RPUSH", b"k", b"a", b"b", b"a", b"c", b"a"]);
7646        assert_eq!(f.run(&[b"LREM", b"k", b"2", b"a"]), ":2\r\n");
7647        assert_eq!(
7648            f.run(&[b"LRANGE", b"k", b"0", b"-1"]),
7649            bulks(&["b", "c", "a"])
7650        );
7651        assert_eq!(f.run(&[b"LREM", b"k", b"-1", b"a"]), ":1\r\n");
7652        assert_eq!(f.run(&[b"LRANGE", b"k", b"0", b"-1"]), bulks(&["b", "c"]));
7653        assert_eq!(f.run(&[b"LREM", b"k", b"0", b"b"]), ":1\r\n");
7654        assert_eq!(f.run(&[b"LREM", b"k", b"0", b"c"]), ":1\r\n");
7655        assert_eq!(f.run(&[b"EXISTS", b"k"]), ":0\r\n");
7656        assert_eq!(f.run(&[b"LREM", b"nope", b"0", b"a"]), ":0\r\n");
7657    }
7658
7659    #[test]
7660    fn ltrim_keeps_a_window_and_an_empty_one_deletes_the_key() {
7661        let mut f = Fixture::new();
7662        f.run(&[b"RPUSH", b"k", b"a", b"b", b"c", b"d"]);
7663        assert_eq!(f.run(&[b"LTRIM", b"k", b"1", b"-2"]), "+OK\r\n");
7664        assert_eq!(f.run(&[b"LRANGE", b"k", b"0", b"-1"]), bulks(&["b", "c"]));
7665        // `LTRIM k 1 0` is the documented way to empty a list, so it has to
7666        // leave `EXISTS` answering zero rather than leaving an empty one.
7667        assert_eq!(f.run(&[b"LTRIM", b"k", b"1", b"0"]), "+OK\r\n");
7668        assert_eq!(f.run(&[b"EXISTS", b"k"]), ":0\r\n");
7669        assert_eq!(f.run(&[b"LTRIM", b"nope", b"0", b"-1"]), "+OK\r\n");
7670    }
7671
7672    #[test]
7673    fn lpos_walks_from_either_end_and_stops_where_it_is_told() {
7674        let mut f = Fixture::new();
7675        f.run(&[b"RPUSH", b"p", b"a", b"b", b"c", b"a", b"b", b"c", b"a"]);
7676        assert_eq!(f.run(&[b"LPOS", b"p", b"a"]), ":0\r\n");
7677        assert_eq!(f.run(&[b"LPOS", b"p", b"a", b"RANK", b"-1"]), ":6\r\n");
7678        assert_eq!(f.run(&[b"LPOS", b"p", b"a", b"RANK", b"2"]), ":3\r\n");
7679        assert_eq!(
7680            f.run(&[b"LPOS", b"p", b"a", b"COUNT", b"2"]),
7681            "*2\r\n:0\r\n:3\r\n"
7682        );
7683        assert_eq!(
7684            f.run(&[b"LPOS", b"p", b"a", b"RANK", b"-1", b"COUNT", b"0"]),
7685            "*3\r\n:6\r\n:3\r\n:0\r\n"
7686        );
7687        // MAXLEN counts elements looked at and not matches found, so three
7688        // stops after `a b c` and finds the one match in it.
7689        assert_eq!(
7690            f.run(&[b"LPOS", b"p", b"a", b"COUNT", b"0", b"MAXLEN", b"3"]),
7691            "*1\r\n:0\r\n"
7692        );
7693        // Nothing found is three different replies depending on how it was
7694        // asked and whether the key is there at all.
7695        assert_eq!(f.run(&[b"LPOS", b"p", b"zz"]), "$-1\r\n");
7696        assert_eq!(f.run(&[b"LPOS", b"p", b"zz", b"COUNT", b"0"]), "*0\r\n");
7697        assert_eq!(f.run(&[b"LPOS", b"nope", b"a"]), "$-1\r\n");
7698        assert_eq!(f.run(&[b"LPOS", b"nope", b"a", b"COUNT", b"2"]), "*0\r\n");
7699    }
7700
7701    #[test]
7702    fn lpos_words_its_three_mistakes_the_way_redis_does() {
7703        let mut f = Fixture::new();
7704        f.run(&[b"RPUSH", b"p", b"a"]);
7705        // The whole sentence and not a prefix, because the older wording of it
7706        // is still all over the internet and clients match on the text.
7707        assert_eq!(
7708            f.run(&[b"LPOS", b"p", b"a", b"RANK", b"0"]),
7709            "-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"
7710        );
7711        assert_eq!(
7712            f.run(&[b"LPOS", b"p", b"a", b"COUNT", b"-1"]),
7713            "-ERR COUNT can't be negative\r\n"
7714        );
7715        assert_eq!(
7716            f.run(&[b"LPOS", b"p", b"a", b"MAXLEN", b"-1"]),
7717            "-ERR MAXLEN can't be negative\r\n"
7718        );
7719        assert_eq!(
7720            f.run(&[b"LPOS", b"p", b"a", b"RANK"]),
7721            "-ERR syntax error\r\n"
7722        );
7723        assert_eq!(
7724            f.run(&[b"LPOS", b"p", b"a", b"FOO", b"1"]),
7725            "-ERR syntax error\r\n"
7726        );
7727    }
7728
7729    #[test]
7730    fn a_move_takes_from_one_end_and_gives_to_another_even_on_one_key() {
7731        let mut f = Fixture::new();
7732        f.run(&[b"RPUSH", b"k", b"a", b"b", b"c"]);
7733        assert_eq!(f.run(&[b"RPOPLPUSH", b"k", b"d"]), "$1\r\nc\r\n");
7734        assert_eq!(f.run(&[b"LRANGE", b"k", b"0", b"-1"]), bulks(&["a", "b"]));
7735        assert_eq!(f.run(&[b"LRANGE", b"d", b"0", b"-1"]), bulks(&["c"]));
7736        assert_eq!(
7737            f.run(&[b"LMOVE", b"k", b"d", b"LEFT", b"RIGHT"]),
7738            "$1\r\na\r\n"
7739        );
7740        assert_eq!(f.run(&[b"LRANGE", b"d", b"0", b"-1"]), bulks(&["c", "a"]));
7741        // The same key twice is the documented way to rotate a list and falls
7742        // out of taking the element before deciding where to put it.
7743        f.run(&[b"DEL", b"r"]);
7744        f.run(&[b"RPUSH", b"r", b"1", b"2", b"3"]);
7745        assert_eq!(f.run(&[b"RPOPLPUSH", b"r", b"r"]), "$1\r\n3\r\n");
7746        assert_eq!(
7747            f.run(&[b"LRANGE", b"r", b"0", b"-1"]),
7748            bulks(&["3", "1", "2"])
7749        );
7750        assert_eq!(
7751            f.run(&[b"LMOVE", b"nope", b"d", b"LEFT", b"LEFT"]),
7752            "$-1\r\n"
7753        );
7754        assert_eq!(
7755            f.run(&[b"LMOVE", b"r", b"d", b"LEFT", b"SIDEWAYS"]),
7756            "-ERR syntax error\r\n"
7757        );
7758    }
7759
7760    #[test]
7761    fn a_move_checks_the_destination_before_it_takes_anything() {
7762        let mut f = Fixture::new();
7763        f.run(&[b"RPUSH", b"k", b"a", b"b"]);
7764        f.run(&[b"SET", b"str", b"v"]);
7765        assert_eq!(
7766            f.run(&[b"LMOVE", b"k", b"str", b"LEFT", b"LEFT"]),
7767            "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n"
7768        );
7769        // The element is still where it was, rather than having gone nowhere.
7770        assert_eq!(f.run(&[b"LRANGE", b"k", b"0", b"-1"]), bulks(&["a", "b"]));
7771    }
7772
7773    #[test]
7774    fn a_block_move_orders_the_block_by_the_ends_and_the_ordering_word() {
7775        // OBO is what you get from sending LMOVE that many times, BULK keeps
7776        // the source order. The two only differ when both ends are the same,
7777        // which is the whole reason the word exists.
7778        for (from, to, order, want) in [
7779            ("LEFT", "RIGHT", "OBO", ["a", "b"]),
7780            ("LEFT", "RIGHT", "BULK", ["a", "b"]),
7781            ("LEFT", "LEFT", "OBO", ["b", "a"]),
7782            ("LEFT", "LEFT", "BULK", ["a", "b"]),
7783            ("RIGHT", "LEFT", "OBO", ["d", "e"]),
7784            ("RIGHT", "LEFT", "BULK", ["d", "e"]),
7785            ("RIGHT", "RIGHT", "OBO", ["e", "d"]),
7786            ("RIGHT", "RIGHT", "BULK", ["d", "e"]),
7787        ] {
7788            let mut f = Fixture::new();
7789            f.run(&[b"RPUSH", b"s", b"a", b"b", b"c", b"d", b"e"]);
7790            let how = format!("{from} {to} {order}");
7791            let reply = f.run(&[
7792                b"LMOVEM",
7793                b"s",
7794                b"d",
7795                from.as_bytes(),
7796                to.as_bytes(),
7797                b"COUNT",
7798                b"2",
7799                order.as_bytes(),
7800            ]);
7801            assert_eq!(reply, bulks(&want), "the reply for {how}");
7802            assert_eq!(
7803                f.run(&[b"LRANGE", b"d", b"0", b"-1"]),
7804                bulks(&want),
7805                "the destination for {how}"
7806            );
7807        }
7808    }
7809
7810    #[test]
7811    fn a_block_move_of_one_needs_no_count_at_all() {
7812        let mut f = Fixture::new();
7813        f.run(&[b"RPUSH", b"s", b"a", b"b", b"c"]);
7814        assert_eq!(
7815            f.run(&[b"LMOVEM", b"s", b"d", b"LEFT", b"RIGHT"]),
7816            bulks(&["a"])
7817        );
7818        assert_eq!(f.run(&[b"LRANGE", b"s", b"0", b"-1"]), bulks(&["b", "c"]));
7819        // Six and seven arguments are neither of the two forms, so the
7820        // reference calls both of them a syntax error rather than guessing.
7821        assert_eq!(
7822            f.run(&[b"LMOVEM", b"s", b"d", b"LEFT", b"RIGHT", b"COUNT"]),
7823            "-ERR syntax error\r\n"
7824        );
7825        assert_eq!(
7826            f.run(&[b"LMOVEM", b"s", b"d", b"LEFT", b"RIGHT", b"COUNT", b"2"]),
7827            "-ERR syntax error\r\n"
7828        );
7829    }
7830
7831    #[test]
7832    fn a_block_move_with_exactly_takes_all_of_them_or_none() {
7833        let mut f = Fixture::new();
7834        f.run(&[b"RPUSH", b"s", b"a", b"b", b"c"]);
7835        // A null array and not a null bulk string, which `redis-cli` prints as
7836        // `(nil)` either way and only the raw wire tells apart. What it would
7837        // have sent is an array, so its nothing is an array's nothing.
7838        assert_eq!(
7839            f.run(&[
7840                b"LMOVEM", b"s", b"d", b"LEFT", b"RIGHT", b"EXACTLY", b"99", b"BULK"
7841            ]),
7842            "*-1\r\n"
7843        );
7844        assert_eq!(
7845            f.run(&[b"LRANGE", b"s", b"0", b"-1"]),
7846            bulks(&["a", "b", "c"])
7847        );
7848        // COUNT takes what there is, and an emptied source goes away.
7849        assert_eq!(
7850            f.run(&[
7851                b"LMOVEM", b"s", b"d", b"LEFT", b"RIGHT", b"COUNT", b"99", b"BULK"
7852            ]),
7853            bulks(&["a", "b", "c"])
7854        );
7855        assert_eq!(f.run(&[b"EXISTS", b"s"]), ":0\r\n");
7856        assert_eq!(
7857            f.run(&[
7858                b"LMOVEM", b"s", b"d", b"LEFT", b"RIGHT", b"COUNT", b"1", b"BULK"
7859            ]),
7860            "*-1\r\n"
7861        );
7862    }
7863
7864    #[test]
7865    fn a_block_move_onto_itself_rotates_by_the_count() {
7866        let mut f = Fixture::new();
7867        f.run(&[b"RPUSH", b"s", b"a", b"b", b"c"]);
7868        assert_eq!(
7869            f.run(&[
7870                b"LMOVEM", b"s", b"s", b"LEFT", b"RIGHT", b"COUNT", b"2", b"BULK"
7871            ]),
7872            bulks(&["a", "b"])
7873        );
7874        assert_eq!(
7875            f.run(&[b"LRANGE", b"s", b"0", b"-1"]),
7876            bulks(&["c", "a", "b"])
7877        );
7878    }
7879
7880    #[test]
7881    fn a_block_move_reads_the_count_before_the_ordering_word() {
7882        let mut f = Fixture::new();
7883        f.run(&[b"RPUSH", b"s", b"a", b"b"]);
7884        f.run(&[b"SET", b"str", b"v"]);
7885        let count = "-ERR count should be greater than 0\r\n";
7886        assert_eq!(
7887            f.run(&[
7888                b"LMOVEM", b"s", b"d", b"LEFT", b"RIGHT", b"COUNT", b"abc", b"NOPE"
7889            ]),
7890            count
7891        );
7892        assert_eq!(
7893            f.run(&[
7894                b"LMOVEM", b"s", b"d", b"LEFT", b"RIGHT", b"COUNT", b"0", b"BULK"
7895            ]),
7896            count
7897        );
7898        assert_eq!(
7899            f.run(&[
7900                b"LMOVEM", b"s", b"d", b"LEFT", b"RIGHT", b"COUNT", b"1", b"NOPE"
7901            ]),
7902            "-ERR syntax error\r\n"
7903        );
7904        assert_eq!(
7905            f.run(&[
7906                b"LMOVEM", b"s", b"d", b"LEFT", b"RIGHT", b"NOPE", b"abc", b"BULK"
7907            ]),
7908            "-ERR syntax error\r\n"
7909        );
7910        // Every argument is read before the keys are looked at, so a bad count
7911        // beats a wrong type even when the type is wrong on the source.
7912        assert_eq!(
7913            f.run(&[
7914                b"LMOVEM", b"str", b"d", b"LEFT", b"RIGHT", b"COUNT", b"abc", b"BULK"
7915            ]),
7916            count
7917        );
7918        assert_eq!(
7919            f.run(&[
7920                b"LMOVEM", b"s", b"str", b"LEFT", b"RIGHT", b"COUNT", b"1", b"BULK"
7921            ]),
7922            "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n"
7923        );
7924        assert_eq!(f.run(&[b"LRANGE", b"s", b"0", b"-1"]), bulks(&["a", "b"]));
7925    }
7926
7927    #[test]
7928    fn lmpop_answers_from_the_first_key_that_has_anything() {
7929        let mut f = Fixture::new();
7930        f.run(&[b"RPUSH", b"b", b"1", b"2", b"3"]);
7931        // The name of the key that answered comes back with the elements,
7932        // because the client cannot work out which one it was.
7933        assert_eq!(
7934            f.run(&[b"LMPOP", b"2", b"a", b"b", b"LEFT", b"COUNT", b"2"]),
7935            "*2\r\n$1\r\nb\r\n*2\r\n$1\r\n1\r\n$1\r\n2\r\n"
7936        );
7937        assert_eq!(
7938            f.run(&[b"LMPOP", b"2", b"a", b"b", b"RIGHT"]),
7939            "*2\r\n$1\r\nb\r\n*1\r\n$1\r\n3\r\n"
7940        );
7941        assert_eq!(f.run(&[b"EXISTS", b"b"]), ":0\r\n");
7942        // A null array and not a null, even though what it stands in for is an
7943        // array holding a key name and then another array.
7944        assert_eq!(f.run(&[b"LMPOP", b"2", b"a", b"b", b"LEFT"]), "*-1\r\n");
7945    }
7946
7947    #[test]
7948    fn lmpop_has_its_own_words_for_a_count_and_for_a_key_count() {
7949        let mut f = Fixture::new();
7950        f.run(&[b"RPUSH", b"k", b"a"]);
7951        assert_eq!(
7952            f.run(&[b"LMPOP", b"0", b"k", b"LEFT"]),
7953            "-ERR numkeys should be greater than 0\r\n"
7954        );
7955        assert_eq!(
7956            f.run(&[b"LMPOP", b"-1", b"k", b"LEFT"]),
7957            "-ERR numkeys should be greater than 0\r\n"
7958        );
7959        assert_eq!(
7960            f.run(&[b"LMPOP", b"1", b"k", b"LEFT", b"COUNT", b"0"]),
7961            "-ERR count should be greater than 0\r\n"
7962        );
7963        // A key count that eats the direction is a syntax error and not a
7964        // sentence about key counts, because the direction is simply not there.
7965        assert_eq!(
7966            f.run(&[b"LMPOP", b"3", b"k", b"LEFT"]),
7967            "-ERR syntax error\r\n"
7968        );
7969        assert_eq!(
7970            f.run(&[b"LMPOP", b"1", b"k", b"LEFT", b"COUNT", b"1", b"x"]),
7971            "-ERR syntax error\r\n"
7972        );
7973        assert_eq!(
7974            f.run(&[b"LMPOP", b"1", b"k", b"LEFT", b"FOO", b"1"]),
7975            "-ERR syntax error\r\n"
7976        );
7977        assert_eq!(
7978            f.run(&[b"LMPOP", b"1", b"k", b"SIDEWAYS"]),
7979            "-ERR syntax error\r\n"
7980        );
7981        assert_eq!(f.run(&[b"LLEN", b"k"]), ":1\r\n");
7982    }
7983
7984    #[test]
7985    fn every_list_command_says_wrongtype_and_writes_nothing() {
7986        let mut f = Fixture::new();
7987        f.run(&[b"SET", b"str", b"v"]);
7988        let wrong = "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n";
7989        for cmd in [
7990            &[b"LPUSH".as_slice(), b"str", b"a"][..],
7991            &[b"RPUSH", b"str", b"a"],
7992            &[b"LPUSHX", b"str", b"a"],
7993            &[b"RPUSHX", b"str", b"a"],
7994            &[b"LPOP", b"str"],
7995            &[b"LPOP", b"str", b"2"],
7996            &[b"RPOP", b"str"],
7997            &[b"LLEN", b"str"],
7998            &[b"LRANGE", b"str", b"0", b"-1"],
7999            &[b"LINDEX", b"str", b"0"],
8000            &[b"LSET", b"str", b"0", b"a"],
8001            &[b"LINSERT", b"str", b"BEFORE", b"a", b"b"],
8002            &[b"LREM", b"str", b"0", b"a"],
8003            &[b"LTRIM", b"str", b"0", b"-1"],
8004            &[b"LPOS", b"str", b"a"],
8005            &[b"LPOS", b"str", b"a", b"COUNT", b"0"],
8006            &[b"RPOPLPUSH", b"str", b"d"],
8007            &[b"LMOVE", b"str", b"d", b"LEFT", b"LEFT"],
8008            &[b"LMPOP", b"1", b"str", b"LEFT"],
8009        ] {
8010            assert_eq!(f.run(cmd), wrong, "{:?}", String::from_utf8_lossy(cmd[0]));
8011        }
8012        assert_eq!(f.run(&[b"GET", b"str"]), "$1\r\nv\r\n");
8013        assert_eq!(f.run(&[b"EXISTS", b"d"]), ":0\r\n");
8014    }
8015
8016    /// A timeout is not an integer and it is not an ordinary float either: the
8017    /// three sentences it can answer with are its own, and which one a given
8018    /// argument gets is not what reading the code would suggest.
8019    #[test]
8020    fn a_timeout_has_three_ways_of_being_wrong() {
8021        let mut f = Fixture::new();
8022        let not_float = "-ERR timeout is not a float or out of range\r\n";
8023        let range = "-ERR timeout is out of range\r\n";
8024        for (bad, want) in [
8025            (&[b"BLPOP".as_slice(), b"k", b"abc"][..], not_float),
8026            (&[b"BLPOP", b"k", b"nan"], not_float),
8027            (&[b"BLPOP", b"k", b""], not_float),
8028            // Whitespace on either side, which `strtold` would take and Redis
8029            // does not.
8030            (&[b"BLPOP", b"k", b" 1"], not_float),
8031            (&[b"BLPOP", b"k", b"1 "], not_float),
8032            (&[b"BLPOP", b"k", b"-1"], "-ERR timeout is negative\r\n"),
8033            (&[b"BLPOP", b"k", b"-0.1"], "-ERR timeout is negative\r\n"),
8034            // These three parse, so they are not the not-a-float error, and all
8035            // three are further off than an i64 of milliseconds reaches.
8036            (&[b"BLPOP", b"k", b"1e400"], range),
8037            (&[b"BLPOP", b"k", b"inf"], range),
8038            (&[b"BLPOP", b"k", b"9999999999999999"], range),
8039            (&[b"BRPOP", b"k", b"abc"], not_float),
8040            (
8041                &[b"BLMOVE", b"a", b"b", b"LEFT", b"RIGHT", b"abc"],
8042                not_float,
8043            ),
8044            (
8045                &[b"BRPOPLPUSH", b"a", b"b", b"-1"],
8046                "-ERR timeout is negative\r\n",
8047            ),
8048            (&[b"BLMPOP", b"abc", b"1", b"k", b"LEFT"], not_float),
8049        ] {
8050            assert_eq!(f.run(bad), want, "for {bad:?}");
8051        }
8052    }
8053
8054    /// A timeout of exactly zero means no timeout, and there are two ways of
8055    /// writing exactly zero.
8056    #[test]
8057    fn a_zero_timeout_waits_and_the_smallest_positive_one_does_not() {
8058        let mut f = Fixture::new();
8059        for timeout in [b"0".as_slice(), b"0.0", b"-0.0"] {
8060            let (flow, out) = f.flow(&[b"BLPOP", b"k", timeout]);
8061            assert_eq!(flow, Flow::Block, "for {timeout:?}");
8062            assert!(out.is_empty(), "for {timeout:?}");
8063        }
8064        // Positive, so it is a real deadline, and the deadline is this
8065        // millisecond. Nothing is written here either: the reply comes from the
8066        // sweep, which is the engine's and not this layer's.
8067        let (flow, out) = f.flow(&[b"BLPOP", b"k", b"0.0000001"]);
8068        assert_eq!(flow, Flow::Block);
8069        assert!(out.is_empty());
8070    }
8071
8072    #[test]
8073    fn a_blocking_command_that_can_be_answered_answers_like_the_one_it_wraps() {
8074        let mut f = Fixture::new();
8075        f.run(&[b"RPUSH", b"L", b"a", b"b", b"c", b"d", b"e"]);
8076
8077        // The one difference from LPOP: the reply names the key that answered,
8078        // which is what makes BLPOP over several keys usable.
8079        assert_eq!(
8080            f.flow(&[b"BLPOP", b"nope", b"L", b"0"]),
8081            (Flow::Continue, "*2\r\n$1\r\nL\r\n$1\r\na\r\n".to_owned())
8082        );
8083        assert_eq!(
8084            f.run(&[b"BRPOP", b"L", b"0"]),
8085            "*2\r\n$1\r\nL\r\n$1\r\ne\r\n"
8086        );
8087        assert_eq!(
8088            f.run(&[
8089                b"BLMPOP", b"0", b"2", b"nope", b"L", b"LEFT", b"COUNT", b"2"
8090            ]),
8091            "*2\r\n$1\r\nL\r\n*2\r\n$1\r\nb\r\n$1\r\nc\r\n"
8092        );
8093        assert_eq!(
8094            f.run(&[b"BLMOVE", b"L", b"D", b"LEFT", b"RIGHT", b"0"]),
8095            "$1\r\nd\r\n"
8096        );
8097        assert_eq!(
8098            f.run(&[b"EXISTS", b"L"]),
8099            ":0\r\n",
8100            "and the key went with it"
8101        );
8102        assert_eq!(f.run(&[b"LRANGE", b"D", b"0", b"-1"]), "*1\r\n$1\r\nd\r\n");
8103        // Onto itself, which is how a list is rotated and is a real thing to ask
8104        // a blocking move for.
8105        f.run(&[b"RPUSH", b"D", b"x"]);
8106        assert_eq!(f.run(&[b"BRPOPLPUSH", b"D", b"D", b"0"]), "$1\r\nx\r\n");
8107        assert_eq!(
8108            f.run(&[b"LRANGE", b"D", b"0", b"-1"]),
8109            "*2\r\n$1\r\nx\r\n$1\r\nd\r\n"
8110        );
8111    }
8112
8113    #[test]
8114    fn blmpop_reads_its_count_and_its_key_count_the_way_lmpop_does() {
8115        let mut f = Fixture::new();
8116        f.run(&[b"RPUSH", b"k", b"a"]);
8117        for (bad, want) in [
8118            (
8119                &[b"BLMPOP".as_slice(), b"0", b"0", b"k", b"LEFT"][..],
8120                "-ERR numkeys should be greater than 0\r\n",
8121            ),
8122            (
8123                &[b"BLMPOP", b"0", b"-1", b"k", b"LEFT"],
8124                "-ERR numkeys should be greater than 0\r\n",
8125            ),
8126            // Two keys named and one given, so the word that should have been
8127            // the direction is a key and there is no direction left.
8128            (
8129                &[b"BLMPOP", b"0", b"2", b"k", b"LEFT"],
8130                "-ERR syntax error\r\n",
8131            ),
8132            (
8133                &[b"BLMPOP", b"0", b"1", b"k", b"SIDEWAYS"],
8134                "-ERR syntax error\r\n",
8135            ),
8136            (
8137                &[b"BLMPOP", b"0", b"1", b"k", b"LEFT", b"COUNT"],
8138                "-ERR syntax error\r\n",
8139            ),
8140            (
8141                &[b"BLMPOP", b"0", b"1", b"k", b"LEFT", b"COUNT", b"2", b"x"],
8142                "-ERR syntax error\r\n",
8143            ),
8144            // A count that is not a number at all gets the same sentence a zero
8145            // or a negative one gets, rather than the usual one about integers.
8146            (
8147                &[b"BLMPOP", b"0", b"1", b"k", b"LEFT", b"COUNT", b"0"],
8148                "-ERR count should be greater than 0\r\n",
8149            ),
8150            (
8151                &[b"BLMPOP", b"0", b"1", b"k", b"LEFT", b"COUNT", b"abc"],
8152                "-ERR count should be greater than 0\r\n",
8153            ),
8154        ] {
8155            assert_eq!(f.run(bad), want, "for {bad:?}");
8156        }
8157        assert_eq!(f.run(&[b"LLEN", b"k"]), ":1\r\n", "and none of them popped");
8158    }
8159
8160    #[test]
8161    fn a_blocking_move_reads_its_directions_before_its_timeout() {
8162        let mut f = Fixture::new();
8163        // Both are wrong. Redis checks the directions first, so this is the
8164        // syntax error and not a complaint about the timeout.
8165        assert_eq!(
8166            f.run(&[b"BLMOVE", b"a", b"b", b"UP", b"DOWN", b"abc"]),
8167            "-ERR syntax error\r\n"
8168        );
8169        assert_eq!(
8170            f.run(&[b"BLMOVE", b"a", b"b", b"LEFT", b"DOWN", b"0.05"]),
8171            "-ERR syntax error\r\n"
8172        );
8173    }
8174
8175    /// `BLMOVEM` answers exactly what `LMOVEM` answers when it does not have to
8176    /// wait, which is the same relationship every other command in this file has
8177    /// with the one it wraps.
8178    #[test]
8179    fn a_blocking_block_move_that_can_be_answered_answers_like_lmovem() {
8180        let mut f = Fixture::new();
8181        f.run(&[b"RPUSH", b"L", b"a", b"b", b"c", b"d", b"e"]);
8182        assert_eq!(
8183            f.flow(&[b"BLMOVEM", b"L", b"D", b"LEFT", b"RIGHT", b"0"]),
8184            (Flow::Continue, "*1\r\n$1\r\na\r\n".to_owned())
8185        );
8186        assert_eq!(
8187            f.run(&[
8188                b"BLMOVEM", b"L", b"D", b"RIGHT", b"RIGHT", b"0", b"COUNT", b"2", b"OBO"
8189            ]),
8190            bulks(&["e", "d"])
8191        );
8192        assert_eq!(
8193            f.run(&[b"LRANGE", b"D", b"0", b"-1"]),
8194            bulks(&["a", "e", "d"])
8195        );
8196        // `EXACTLY` with enough there does not wait either.
8197        assert_eq!(
8198            f.run(&[
8199                b"BLMOVEM", b"L", b"D", b"LEFT", b"RIGHT", b"0", b"EXACTLY", b"2", b"BULK"
8200            ]),
8201            bulks(&["b", "c"])
8202        );
8203        assert_eq!(f.run(&[b"EXISTS", b"L"]), ":0\r\n", "and the key went");
8204    }
8205
8206    /// The one thing `BLMOVEM` decides differently from the other five: `COUNT`
8207    /// is ready as soon as there is anything and `EXACTLY` is not ready until the
8208    /// whole block has arrived.
8209    #[test]
8210    fn a_blocking_block_move_waits_for_the_whole_block_only_under_exactly() {
8211        let mut f = Fixture::new();
8212        f.run(&[b"RPUSH", b"L", b"a", b"b"]);
8213        // Two there and three asked for. `COUNT` takes the two.
8214        assert_eq!(
8215            f.flow(&[
8216                b"BLMOVEM", b"L", b"D", b"LEFT", b"RIGHT", b"0", b"COUNT", b"3", b"BULK"
8217            ]),
8218            (Flow::Continue, bulks(&["a", "b"]))
8219        );
8220
8221        f.run(&[b"RPUSH", b"L", b"a", b"b"]);
8222        // The same line with `EXACTLY` parks instead, and takes nothing on the
8223        // way past.
8224        assert_eq!(
8225            f.flow(&[
8226                b"BLMOVEM", b"L", b"D", b"LEFT", b"RIGHT", b"0", b"EXACTLY", b"3", b"BULK"
8227            ])
8228            .0,
8229            Flow::Block
8230        );
8231        assert_eq!(f.run(&[b"LRANGE", b"L", b"0", b"-1"]), bulks(&["a", "b"]));
8232    }
8233
8234    #[test]
8235    fn a_blocking_block_move_reads_its_directions_then_its_timeout_then_its_count() {
8236        let mut f = Fixture::new();
8237        let syntax = "-ERR syntax error\r\n";
8238        // All three are wrong and the directions are read first.
8239        assert_eq!(
8240            f.run(&[
8241                b"BLMOVEM", b"a", b"b", b"UP", b"DOWN", b"abc", b"NOPE", b"x", b"y"
8242            ]),
8243            syntax
8244        );
8245        // Directions fine, timeout and count both wrong, so the timeout wins.
8246        assert_eq!(
8247            f.run(&[
8248                b"BLMOVEM", b"a", b"b", b"LEFT", b"RIGHT", b"abc", b"COUNT", b"abc", b"BULK"
8249            ]),
8250            "-ERR timeout is not a float or out of range\r\n"
8251        );
8252        assert_eq!(
8253            f.run(&[
8254                b"BLMOVEM", b"a", b"b", b"LEFT", b"RIGHT", b"-1", b"COUNT", b"1", b"BULK"
8255            ]),
8256            "-ERR timeout is negative\r\n"
8257        );
8258        // And with the timeout fine, the count before the ordering word.
8259        assert_eq!(
8260            f.run(&[
8261                b"BLMOVEM", b"a", b"b", b"LEFT", b"RIGHT", b"0", b"COUNT", b"abc", b"NOPE"
8262            ]),
8263            "-ERR count should be greater than 0\r\n"
8264        );
8265        assert_eq!(
8266            f.run(&[
8267                b"BLMOVEM", b"a", b"b", b"LEFT", b"RIGHT", b"0", b"COUNT", b"1", b"NOPE"
8268            ]),
8269            syntax
8270        );
8271        // Seven and eight arguments are neither of the two forms, the same way
8272        // six and seven are for `LMOVEM`.
8273        assert_eq!(
8274            f.run(&[b"BLMOVEM", b"a", b"b", b"LEFT", b"RIGHT", b"0", b"COUNT"]),
8275            syntax
8276        );
8277        assert_eq!(
8278            f.run(&[
8279                b"BLMOVEM", b"a", b"b", b"LEFT", b"RIGHT", b"0", b"COUNT", b"2"
8280            ]),
8281            syntax
8282        );
8283    }
8284
8285    /// The four ways a blocking command sees a key of another type, and the one
8286    /// way it does not.
8287    #[test]
8288    fn a_blocking_command_errors_on_a_wrong_type_rather_than_waiting_on_it() {
8289        let mut f = Fixture::new();
8290        f.run(&[b"SET", b"S", b"v"]);
8291        f.run(&[b"RPUSH", b"D", b"x"]);
8292        let wrong = "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n";
8293
8294        assert_eq!(f.run(&[b"BLPOP", b"S", b"0"]), wrong);
8295        // Every key is checked even when an earlier one would have blocked, so
8296        // an empty key in front of a string does not hide it.
8297        assert_eq!(f.run(&[b"BLPOP", b"E", b"S", b"0"]), wrong);
8298        assert_eq!(f.run(&[b"BRPOP", b"S", b"0"]), wrong);
8299        assert_eq!(f.run(&[b"BLMPOP", b"0", b"1", b"S", b"LEFT"]), wrong);
8300        assert_eq!(f.run(&[b"BRPOPLPUSH", b"S", b"D", b"0"]), wrong);
8301        // The destination, which is only reached because the source has
8302        // something in it.
8303        assert_eq!(f.run(&[b"BRPOPLPUSH", b"D", b"S", b"0"]), wrong);
8304        assert_eq!(f.run(&[b"LRANGE", b"D", b"0", b"-1"]), "*1\r\n$1\r\nx\r\n");
8305        assert_eq!(
8306            f.run(&[b"BLMOVEM", b"S", b"D", b"LEFT", b"RIGHT", b"0"]),
8307            wrong
8308        );
8309        assert_eq!(
8310            f.run(&[b"BLMOVEM", b"D", b"S", b"LEFT", b"RIGHT", b"0"]),
8311            wrong
8312        );
8313
8314        // And the one that does not: an empty source means the destination is
8315        // never looked at, so this waits rather than erroring, and on a real
8316        // server it times out.
8317        assert_eq!(
8318            f.flow(&[b"BLMOVE", b"E", b"S", b"LEFT", b"RIGHT", b"0.1"])
8319                .0,
8320            Flow::Block
8321        );
8322        // `BLMOVEM` has a second way of not being ready, and it hides the
8323        // destination just as well: the source is a list with two elements in it
8324        // and `EXACTLY` wants three, so the string never gets looked at.
8325        assert_eq!(
8326            f.flow(&[b"BLMOVEM", b"E", b"S", b"LEFT", b"RIGHT", b"0.1"])
8327                .0,
8328            Flow::Block
8329        );
8330        f.run(&[b"RPUSH", b"E", b"1", b"2"]);
8331        assert_eq!(
8332            f.flow(&[
8333                b"BLMOVEM", b"E", b"S", b"LEFT", b"RIGHT", b"0.1", b"EXACTLY", b"3", b"BULK"
8334            ])
8335            .0,
8336            Flow::Block
8337        );
8338    }
8339
8340    /// The same churn the set and the string get, because a list that leaks a
8341    /// chunk per push looks exactly like one that does not until it has run for
8342    /// an afternoon.
8343    /// Not under Miri, for the reason on `churning_sets_does_not_grow_the_server`.
8344    #[cfg_attr(miri, ignore = "the volume is the claim")]
8345    #[test]
8346    fn churning_lists_does_not_grow_the_server() {
8347        let mut f = Fixture::new();
8348        let vals: Vec<Vec<u8>> = (0..200).map(|i| format!("v{i}").into_bytes()).collect();
8349        let args: Vec<&[u8]> = [&b"RPUSH"[..], &b"k"[..]]
8350            .into_iter()
8351            .chain(vals.iter().map(Vec::as_slice))
8352            .collect();
8353
8354        f.run(&args);
8355        f.run(&[b"DEL", b"k"]);
8356        f.server.compact_step();
8357        let after_first = f.server.memory_bytes();
8358
8359        for _ in 0..200 {
8360            f.run(&args);
8361            f.run(&[b"LTRIM", b"k", b"1", b"0"]);
8362            f.server.compact_step();
8363        }
8364        assert_eq!(f.run(&[b"DBSIZE"]), ":0\r\n");
8365        assert!(
8366            f.server.memory_bytes() <= after_first * 2,
8367            "held {} after two hundred passes against {after_first} after one",
8368            f.server.memory_bytes()
8369        );
8370    }
8371
8372    // ------------------------------------------------------------ sorted set
8373
8374    #[test]
8375    fn a_sorted_set_takes_scores_and_gives_them_back() {
8376        let mut f = Fixture::new();
8377        assert_eq!(f.run(&[b"ZADD", b"z", b"1", b"a", b"2", b"b"]), ":2\r\n");
8378        assert_eq!(f.run(&[b"ZADD", b"z", b"1", b"a", b"3", b"c"]), ":1\r\n");
8379        assert_eq!(f.run(&[b"ZCARD", b"z"]), ":3\r\n");
8380        assert_eq!(f.run(&[b"ZSCORE", b"z", b"b"]), "$1\r\n2\r\n");
8381        assert_eq!(f.run(&[b"ZSCORE", b"z", b"nope"]), "$-1\r\n");
8382        assert_eq!(f.run(&[b"ZSCORE", b"nokey", b"b"]), "$-1\r\n");
8383        assert_eq!(
8384            f.run(&[b"ZMSCORE", b"z", b"a", b"nope", b"c"]),
8385            "*3\r\n$1\r\n1\r\n$-1\r\n$1\r\n3\r\n"
8386        );
8387        assert_eq!(f.run(&[b"ZREM", b"z", b"a", b"nope"]), ":1\r\n");
8388        assert_eq!(f.run(&[b"ZCARD", b"z"]), ":2\r\n");
8389        // The key goes when the last member does.
8390        assert_eq!(f.run(&[b"ZREM", b"z", b"b", b"c"]), ":2\r\n");
8391        assert_eq!(f.run(&[b"EXISTS", b"z"]), ":0\r\n");
8392    }
8393
8394    #[test]
8395    fn a_score_is_a_double_on_resp3_and_digits_on_resp2() {
8396        let mut f = Fixture::new();
8397        f.run(&[b"ZADD", b"z", b"1.5", b"a", b"inf", b"b", b"-inf", b"c"]);
8398        assert_eq!(f.run(&[b"ZSCORE", b"z", b"a"]), "$3\r\n1.5\r\n");
8399        assert_eq!(f.run(&[b"ZSCORE", b"z", b"b"]), "$3\r\ninf\r\n");
8400        assert_eq!(f.run(&[b"ZSCORE", b"z", b"c"]), "$4\r\n-inf\r\n");
8401
8402        f.out = Out::new(Proto::Resp3);
8403        assert_eq!(f.run(&[b"ZSCORE", b"z", b"a"]), ",1.5\r\n");
8404        assert_eq!(f.run(&[b"ZSCORE", b"z", b"b"]), ",inf\r\n");
8405        assert_eq!(f.run(&[b"ZSCORE", b"z", b"c"]), ",-inf\r\n");
8406        assert_eq!(f.run(&[b"ZSCORE", b"z", b"nope"]), "_\r\n");
8407    }
8408
8409    #[test]
8410    fn the_zadd_options_gate_what_gets_written() {
8411        let mut f = Fixture::new();
8412        f.run(&[b"ZADD", b"z", b"5", b"a"]);
8413        // NX leaves a member that is there alone, XX will not create one.
8414        assert_eq!(f.run(&[b"ZADD", b"z", b"NX", b"9", b"a"]), ":0\r\n");
8415        assert_eq!(f.run(&[b"ZSCORE", b"z", b"a"]), "$1\r\n5\r\n");
8416        assert_eq!(f.run(&[b"ZADD", b"z", b"XX", b"9", b"new"]), ":0\r\n");
8417        assert_eq!(f.run(&[b"EXISTS", b"z"]), ":1\r\n");
8418        // GT and LT only move a score one way.
8419        assert_eq!(f.run(&[b"ZADD", b"z", b"GT", b"CH", b"3", b"a"]), ":0\r\n");
8420        assert_eq!(f.run(&[b"ZADD", b"z", b"GT", b"CH", b"7", b"a"]), ":1\r\n");
8421        assert_eq!(f.run(&[b"ZADD", b"z", b"LT", b"CH", b"9", b"a"]), ":0\r\n");
8422        // CH counts a moved score and plain ZADD does not.
8423        assert_eq!(f.run(&[b"ZADD", b"z", b"1", b"a", b"1", b"b"]), ":1\r\n");
8424        assert_eq!(
8425            f.run(&[b"ZADD", b"z", b"CH", b"2", b"a", b"2", b"c"]),
8426            ":2\r\n"
8427        );
8428    }
8429
8430    #[test]
8431    fn zadd_incr_answers_a_score_or_nothing_at_all() {
8432        let mut f = Fixture::new();
8433        assert_eq!(f.run(&[b"ZADD", b"z", b"INCR", b"5", b"m"]), "$1\r\n5\r\n");
8434        assert_eq!(f.run(&[b"ZADD", b"z", b"INCR", b"2", b"m"]), "$1\r\n7\r\n");
8435        // A gate that refuses is the string nil, because the reply it stands in
8436        // for is a score.
8437        assert_eq!(
8438            f.run(&[b"ZADD", b"z", b"NX", b"INCR", b"2", b"m"]),
8439            "$-1\r\n"
8440        );
8441        assert_eq!(
8442            f.run(&[b"ZADD", b"z", b"XX", b"INCR", b"2", b"gone"]),
8443            "$-1\r\n"
8444        );
8445        assert_eq!(
8446            f.run(&[b"ZADD", b"z", b"GT", b"INCR", b"-1", b"m"]),
8447            "$-1\r\n"
8448        );
8449        assert_eq!(
8450            f.run(&[b"ZADD", b"z", b"GT", b"INCR", b"1", b"m"]),
8451            "$1\r\n8\r\n"
8452        );
8453        assert_eq!(f.run(&[b"ZINCRBY", b"z", b"2", b"m"]), "$2\r\n10\r\n");
8454        assert_eq!(f.run(&[b"ZINCRBY", b"z", b"1", b"fresh"]), "$1\r\n1\r\n");
8455    }
8456
8457    #[test]
8458    fn the_two_infinities_will_not_be_added_together() {
8459        let mut f = Fixture::new();
8460        f.run(&[b"ZADD", b"z", b"inf", b"m"]);
8461        let nan = "-ERR resulting score is not a number (NaN)\r\n";
8462        assert_eq!(f.run(&[b"ZINCRBY", b"z", b"-inf", b"m"]), nan);
8463        assert_eq!(f.run(&[b"ZADD", b"z", b"INCR", b"-inf", b"m"]), nan);
8464        assert_eq!(f.run(&[b"ZSCORE", b"z", b"m"]), "$3\r\ninf\r\n");
8465        // And a key made for an increment that then fails does not stay behind.
8466        assert_eq!(f.run(&[b"ZINCRBY", b"gone", b"1", b"m"]), "$1\r\n1\r\n");
8467    }
8468
8469    #[test]
8470    fn zadd_says_its_mistakes_the_way_redis_says_them() {
8471        let mut f = Fixture::new();
8472        // The pairs are counted before the options are looked at, so this is a
8473        // syntax error about having none and not a complaint about NX and XX.
8474        assert_eq!(
8475            f.run(&[b"ZADD", b"z", b"NX", b"XX"]),
8476            "-ERR syntax error\r\n"
8477        );
8478        assert_eq!(
8479            f.run(&[b"ZADD", b"z", b"NX", b"XX", b"1", b"a"]),
8480            "-ERR XX and NX options at the same time are not compatible\r\n"
8481        );
8482        let gtlt = "-ERR GT, LT, and/or NX options at the same time are not compatible\r\n";
8483        assert_eq!(f.run(&[b"ZADD", b"z", b"NX", b"GT", b"1", b"a"]), gtlt);
8484        assert_eq!(f.run(&[b"ZADD", b"z", b"GT", b"LT", b"1", b"a"]), gtlt);
8485        assert_eq!(
8486            f.run(&[b"ZADD", b"z", b"INCR", b"1", b"a", b"2", b"b"]),
8487            "-ERR INCR option supports a single increment-element pair\r\n"
8488        );
8489        // An odd number of arguments after the options.
8490        assert_eq!(
8491            f.run(&[b"ZADD", b"z", b"1", b"a", b"2"]),
8492            "-ERR syntax error\r\n"
8493        );
8494        // Every score is read before the first is stored.
8495        assert_eq!(
8496            f.run(&[b"ZADD", b"z", b"1", b"a", b"nonsense", b"b"]),
8497            "-ERR value is not a valid float\r\n"
8498        );
8499        assert_eq!(f.run(&[b"EXISTS", b"z"]), ":0\r\n");
8500    }
8501
8502    #[test]
8503    fn a_rank_says_where_a_member_sits_from_either_end() {
8504        let mut f = Fixture::new();
8505        f.run(&[b"ZADD", b"z", b"1", b"a", b"2", b"b", b"3", b"c"]);
8506        assert_eq!(f.run(&[b"ZRANK", b"z", b"a"]), ":0\r\n");
8507        assert_eq!(f.run(&[b"ZRANK", b"z", b"c"]), ":2\r\n");
8508        assert_eq!(f.run(&[b"ZREVRANK", b"z", b"c"]), ":0\r\n");
8509        assert_eq!(f.run(&[b"ZREVRANK", b"z", b"a"]), ":2\r\n");
8510        // WITHSCORE changes both shapes: the answer and the nothing.
8511        assert_eq!(
8512            f.run(&[b"ZRANK", b"z", b"b", b"WITHSCORE"]),
8513            "*2\r\n:1\r\n$1\r\n2\r\n"
8514        );
8515        assert_eq!(f.run(&[b"ZRANK", b"z", b"nope"]), "$-1\r\n");
8516        assert_eq!(f.run(&[b"ZRANK", b"z", b"nope", b"WITHSCORE"]), "*-1\r\n");
8517        assert_eq!(f.run(&[b"ZRANK", b"nokey", b"a", b"WITHSCORE"]), "*-1\r\n");
8518        // A bad option is a syntax error and one argument too many is an arity
8519        // error, which is Redis's split.
8520        assert_eq!(
8521            f.run(&[b"ZRANK", b"z", b"b", b"bogus"]),
8522            "-ERR syntax error\r\n"
8523        );
8524        assert_eq!(
8525            f.run(&[b"ZREVRANK", b"z", b"b", b"WITHSCORE", b"more"]),
8526            "-ERR wrong number of arguments for 'zrevrank' command\r\n"
8527        );
8528    }
8529
8530    #[test]
8531    fn the_two_counts_read_their_two_kinds_of_bound() {
8532        let mut f = Fixture::new();
8533        f.run(&[b"ZADD", b"z", b"1", b"a", b"2", b"b", b"3", b"c"]);
8534        assert_eq!(f.run(&[b"ZCOUNT", b"z", b"-inf", b"+inf"]), ":3\r\n");
8535        assert_eq!(f.run(&[b"ZCOUNT", b"z", b"2", b"3"]), ":2\r\n");
8536        assert_eq!(f.run(&[b"ZCOUNT", b"z", b"(1", b"3"]), ":2\r\n");
8537        assert_eq!(f.run(&[b"ZCOUNT", b"z", b"(1", b"(3"]), ":1\r\n");
8538        assert_eq!(f.run(&[b"ZCOUNT", b"nokey", b"-inf", b"+inf"]), ":0\r\n");
8539        assert_eq!(
8540            f.run(&[b"ZCOUNT", b"z", b"bogus", b"3"]),
8541            "-ERR min or max is not a float\r\n"
8542        );
8543
8544        f.run(&[b"ZADD", b"l", b"0", b"a", b"0", b"b", b"0", b"c"]);
8545        assert_eq!(f.run(&[b"ZLEXCOUNT", b"l", b"-", b"+"]), ":3\r\n");
8546        assert_eq!(f.run(&[b"ZLEXCOUNT", b"l", b"[a", b"(c"]), ":2\r\n");
8547        assert_eq!(f.run(&[b"ZLEXCOUNT", b"l", b"(a", b"+"]), ":2\r\n");
8548        // A bare member is not a bound, because a member can start with any
8549        // byte and there would be no way to say the bracket if it were optional.
8550        assert_eq!(
8551            f.run(&[b"ZLEXCOUNT", b"l", b"a", b"c"]),
8552            "-ERR min or max not valid string range item\r\n"
8553        );
8554    }
8555
8556    /// The three ways `ZRANGE` can be asked for a window, forwards and back.
8557    ///
8558    /// Every byte in here was read off a real 8.10.1 rather than worked out,
8559    /// because the interesting part of this command is not what it selects, it
8560    /// is which of the two ends the client is expected to name first.
8561    #[test]
8562    fn one_range_command_selects_by_rank_or_score_or_name() {
8563        let mut f = Fixture::new();
8564        f.run(&[b"ZADD", b"z", b"1", b"a", b"2", b"b", b"3", b"c"]);
8565        assert_eq!(
8566            f.run(&[b"ZRANGE", b"z", b"0", b"-1"]),
8567            "*3\r\n$1\r\na\r\n$1\r\nb\r\n$1\r\nc\r\n"
8568        );
8569        assert_eq!(
8570            f.run(&[b"ZRANGE", b"z", b"-2", b"-1"]),
8571            "*2\r\n$1\r\nb\r\n$1\r\nc\r\n"
8572        );
8573        assert_eq!(f.run(&[b"ZRANGE", b"z", b"5", b"9"]), "*0\r\n");
8574        assert_eq!(f.run(&[b"ZRANGE", b"nokey", b"0", b"-1"]), "*0\r\n");
8575        // REV over ranks reverses the walk and leaves the two arguments alone,
8576        // because a rank counts from the end the walk starts at.
8577        assert_eq!(
8578            f.run(&[b"ZRANGE", b"z", b"0", b"-1", b"REV"]),
8579            "*3\r\n$1\r\nc\r\n$1\r\nb\r\n$1\r\na\r\n"
8580        );
8581        assert_eq!(
8582            f.run(&[b"ZRANGE", b"z", b"(1", b"+inf", b"BYSCORE"]),
8583            "*2\r\n$1\r\nb\r\n$1\r\nc\r\n"
8584        );
8585        // And REV over scores does swap them, since a bound does not count from
8586        // anywhere. This is the one line of the parse that tells the two apart.
8587        assert_eq!(
8588            f.run(&[b"ZRANGE", b"z", b"+inf", b"(1", b"BYSCORE", b"REV"]),
8589            "*2\r\n$1\r\nc\r\n$1\r\nb\r\n"
8590        );
8591        assert_eq!(
8592            f.run(&[b"ZRANGE", b"z", b"-", b"+", b"BYLEX"]),
8593            "*3\r\n$1\r\na\r\n$1\r\nb\r\n$1\r\nc\r\n"
8594        );
8595        assert_eq!(
8596            f.run(&[b"ZRANGE", b"z", b"+", b"-", b"BYLEX", b"REV"]),
8597            "*3\r\n$1\r\nc\r\n$1\r\nb\r\n$1\r\na\r\n"
8598        );
8599    }
8600
8601    /// The older spellings, which are the same six windows with the mode in the
8602    /// name and the high end named first on the three that go backwards.
8603    #[test]
8604    fn the_older_range_spellings_name_their_high_end_first() {
8605        let mut f = Fixture::new();
8606        f.run(&[b"ZADD", b"z", b"1", b"a", b"2", b"b", b"3", b"c"]);
8607        assert_eq!(
8608            f.run(&[b"ZREVRANGE", b"z", b"0", b"-1"]),
8609            "*3\r\n$1\r\nc\r\n$1\r\nb\r\n$1\r\na\r\n"
8610        );
8611        assert_eq!(
8612            f.run(&[b"ZREVRANGE", b"z", b"0", b"0", b"WITHSCORES"]),
8613            "*2\r\n$1\r\nc\r\n$1\r\n3\r\n"
8614        );
8615        assert_eq!(
8616            f.run(&[b"ZRANGEBYSCORE", b"z", b"(1", b"3"]),
8617            "*2\r\n$1\r\nb\r\n$1\r\nc\r\n"
8618        );
8619        assert_eq!(
8620            f.run(&[b"ZREVRANGEBYSCORE", b"z", b"3", b"(1"]),
8621            "*2\r\n$1\r\nc\r\n$1\r\nb\r\n"
8622        );
8623        // The two arguments the wrong way round is an empty answer and not an
8624        // error, which is what the swap being in the parse rather than in the
8625        // window buys.
8626        assert_eq!(f.run(&[b"ZREVRANGEBYSCORE", b"z", b"(1", b"3"]), "*0\r\n");
8627        assert_eq!(
8628            f.run(&[b"ZRANGEBYLEX", b"z", b"[a", b"(c"]),
8629            "*2\r\n$1\r\na\r\n$1\r\nb\r\n"
8630        );
8631        assert_eq!(
8632            f.run(&[b"ZREVRANGEBYLEX", b"z", b"(c", b"[a"]),
8633            "*2\r\n$1\r\nb\r\n$1\r\na\r\n"
8634        );
8635        // BYSCORE, BYLEX and REV mean nothing to these, so they are not another
8636        // way of spelling the mode, they are a syntax error.
8637        for cmd in [
8638            &[b"ZREVRANGE".as_slice(), b"z", b"0", b"-1", b"BYSCORE"][..],
8639            &[b"ZRANGEBYSCORE", b"z", b"1", b"3", b"REV"],
8640            &[b"ZRANGEBYLEX", b"z", b"[a", b"[c", b"BYLEX"],
8641        ] {
8642            assert_eq!(f.run(cmd), "-ERR syntax error\r\n", "{:?}", cmd[0]);
8643        }
8644    }
8645
8646    /// `LIMIT` and `WITHSCORES`, which every one of these commands reads and
8647    /// only some of them accept.
8648    #[test]
8649    fn limit_and_withscores_are_read_by_all_of_them_and_refused_afterwards() {
8650        let mut f = Fixture::new();
8651        f.run(&[b"ZADD", b"z", b"1", b"a", b"2", b"b", b"3", b"c"]);
8652        assert_eq!(
8653            f.run(&[
8654                b"ZRANGE", b"z", b"-inf", b"+inf", b"BYSCORE", b"LIMIT", b"1", b"1"
8655            ]),
8656            "*1\r\n$1\r\nb\r\n"
8657        );
8658        // A negative offset skips past everything, a negative count is no bound.
8659        assert_eq!(
8660            f.run(&[
8661                b"ZRANGE", b"z", b"-inf", b"+inf", b"BYSCORE", b"LIMIT", b"-1", b"2"
8662            ]),
8663            "*0\r\n"
8664        );
8665        assert_eq!(
8666            f.run(&[
8667                b"ZRANGE", b"z", b"-inf", b"+inf", b"BYSCORE", b"LIMIT", b"0", b"-1"
8668            ]),
8669            "*3\r\n$1\r\na\r\n$1\r\nb\r\n$1\r\nc\r\n"
8670        );
8671        // The two options in either order, which falls out of the parse loop.
8672        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";
8673        assert_eq!(
8674            f.run(&[
8675                b"ZRANGEBYSCORE",
8676                b"z",
8677                b"1",
8678                b"3",
8679                b"WITHSCORES",
8680                b"LIMIT",
8681                b"0",
8682                b"2"
8683            ]),
8684            both
8685        );
8686        assert_eq!(
8687            f.run(&[
8688                b"ZRANGEBYSCORE",
8689                b"z",
8690                b"1",
8691                b"3",
8692                b"LIMIT",
8693                b"0",
8694                b"2",
8695                b"WITHSCORES"
8696            ]),
8697            both
8698        );
8699        // LIMIT on a range by rank is refused after the whole option list has
8700        // been read, so this complains about LIMIT and not about WITHSCORES.
8701        let needs_by = "-ERR syntax error, LIMIT is only supported in combination with either BYSCORE or BYLEX\r\n";
8702        assert_eq!(
8703            f.run(&[
8704                b"ZREVRANGE",
8705                b"z",
8706                b"0",
8707                b"-1",
8708                b"WITHSCORES",
8709                b"LIMIT",
8710                b"0",
8711                b"1"
8712            ]),
8713            needs_by
8714        );
8715        assert_eq!(
8716            f.run(&[b"ZRANGE", b"z", b"0", b"-1", b"LIMIT", b"0", b"1"]),
8717            needs_by
8718        );
8719        let not_bylex = "-ERR syntax error, WITHSCORES not supported in combination with BYLEX\r\n";
8720        assert_eq!(
8721            f.run(&[b"ZRANGE", b"z", b"-", b"+", b"BYLEX", b"WITHSCORES"]),
8722            not_bylex
8723        );
8724        assert_eq!(
8725            f.run(&[b"ZRANGEBYLEX", b"z", b"[a", b"[c", b"WITHSCORES"]),
8726            not_bylex
8727        );
8728        // Two modes at once, an option nobody knows, a LIMIT missing its count,
8729        // and the three number errors, which are three different sentences.
8730        for cmd in [
8731            &[
8732                b"ZRANGE".as_slice(),
8733                b"z",
8734                b"0",
8735                b"-1",
8736                b"BYSCORE",
8737                b"BYLEX",
8738            ][..],
8739            &[b"ZRANGE", b"z", b"0", b"-1", b"junk"],
8740            &[b"ZRANGEBYSCORE", b"z", b"1", b"3", b"LIMIT", b"0"],
8741        ] {
8742            assert_eq!(f.run(cmd), "-ERR syntax error\r\n", "{cmd:?}");
8743        }
8744        assert_eq!(
8745            f.run(&[b"ZRANGEBYSCORE", b"z", b"bad", b"3"]),
8746            "-ERR min or max is not a float\r\n"
8747        );
8748        assert_eq!(
8749            f.run(&[b"ZRANGEBYLEX", b"z", b"a", b"[c"]),
8750            "-ERR min or max not valid string range item\r\n"
8751        );
8752        assert_eq!(
8753            f.run(&[b"ZRANGEBYSCORE", b"z", b"1", b"3", b"LIMIT", b"a", b"2"]),
8754            "-ERR value is not an integer or out of range\r\n"
8755        );
8756    }
8757
8758    /// `WITHSCORES` is the one place in this group where the two protocols
8759    /// disagree about the shape of the reply and not just the type of a value.
8760    #[test]
8761    fn withscores_nests_on_resp3_and_flattens_on_resp2() {
8762        let mut f = Fixture::new();
8763        f.run(&[b"ZADD", b"z", b"1", b"a", b"2", b"b", b"3", b"c"]);
8764        assert_eq!(
8765            f.run(&[b"ZRANGE", b"z", b"0", b"-1", b"WITHSCORES"]),
8766            "*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"
8767        );
8768        f.out = Out::new(Proto::Resp3);
8769        assert_eq!(
8770            f.run(&[b"ZRANGE", b"z", b"0", b"-1", b"WITHSCORES"]),
8771            "*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"
8772        );
8773        assert_eq!(
8774            f.run(&[b"ZRANGE", b"z", b"0", b"-1"]),
8775            "*3\r\n$1\r\na\r\n$1\r\nb\r\n$1\r\nc\r\n"
8776        );
8777    }
8778
8779    /// The store form, which is the same parse with the destination in front.
8780    #[test]
8781    fn a_range_store_writes_the_window_into_another_key() {
8782        let mut f = Fixture::new();
8783        f.run(&[b"ZADD", b"z", b"1", b"a", b"2", b"b", b"3", b"c"]);
8784        assert_eq!(f.run(&[b"ZRANGESTORE", b"d", b"z", b"0", b"-1"]), ":3\r\n");
8785        // A window that selects nothing deletes the destination rather than
8786        // leaving an empty sorted set, because an empty one does not exist.
8787        assert_eq!(f.run(&[b"ZRANGESTORE", b"d", b"z", b"5", b"9"]), ":0\r\n");
8788        assert_eq!(f.run(&[b"EXISTS", b"d"]), ":0\r\n");
8789        assert_eq!(
8790            f.run(&[b"ZRANGESTORE", b"d", b"z", b"(1", b"+inf", b"BYSCORE"]),
8791            ":2\r\n"
8792        );
8793        assert_eq!(
8794            f.run(&[b"ZRANGE", b"d", b"0", b"-1", b"WITHSCORES"]),
8795            "*4\r\n$1\r\nb\r\n$1\r\n2\r\n$1\r\nc\r\n$1\r\n3\r\n"
8796        );
8797        // The destination is allowed to be the source, because the result is
8798        // built whole before anything is written over.
8799        assert_eq!(f.run(&[b"ZRANGESTORE", b"z", b"z", b"1", b"2"]), ":2\r\n");
8800        assert_eq!(
8801            f.run(&[b"ZRANGE", b"z", b"0", b"-1", b"WITHSCORES"]),
8802            "*4\r\n$1\r\nb\r\n$1\r\n2\r\n$1\r\nc\r\n$1\r\n3\r\n"
8803        );
8804        // It takes every option ZRANGE takes except WITHSCORES, which is a
8805        // plain syntax error here and not the sentence about BYLEX.
8806        assert_eq!(
8807            f.run(&[b"ZRANGESTORE", b"d", b"z", b"0", b"-1", b"WITHSCORES"]),
8808            "-ERR syntax error\r\n"
8809        );
8810    }
8811
8812    /// The three removals, which are the read side's window with the walk
8813    /// turned into a removal and no options at all.
8814    #[test]
8815    fn the_three_removals_share_their_window_with_the_reads() {
8816        let mut f = Fixture::new();
8817        f.run(&[b"ZADD", b"z", b"1", b"a", b"2", b"b", b"3", b"c"]);
8818        assert_eq!(f.run(&[b"ZREMRANGEBYRANK", b"z", b"0", b"0"]), ":1\r\n");
8819        assert_eq!(
8820            f.run(&[b"ZRANGE", b"z", b"0", b"-1"]),
8821            "*2\r\n$1\r\nb\r\n$1\r\nc\r\n"
8822        );
8823        assert_eq!(
8824            f.run(&[b"ZREMRANGEBYSCORE", b"z", b"(2", b"+inf"]),
8825            ":1\r\n"
8826        );
8827        assert_eq!(f.run(&[b"ZRANGE", b"z", b"0", b"-1"]), "*1\r\n$1\r\nb\r\n");
8828        // The last member going takes the key with it.
8829        assert_eq!(f.run(&[b"ZREMRANGEBYLEX", b"z", b"-", b"+"]), ":1\r\n");
8830        assert_eq!(f.run(&[b"EXISTS", b"z"]), ":0\r\n");
8831        assert_eq!(
8832            f.run(&[b"ZREMRANGEBYRANK", b"nokey", b"0", b"-1"]),
8833            ":0\r\n"
8834        );
8835        assert_eq!(
8836            f.run(&[b"ZREMRANGEBYRANK", b"z", b"0", b"x"]),
8837            "-ERR value is not an integer or out of range\r\n"
8838        );
8839    }
8840
8841    /// The algebra, which is one gather and three names for it.
8842    #[test]
8843    fn the_three_algebra_commands_combine_scores_and_order_the_answer_once() {
8844        let mut f = Fixture::new();
8845        f.run(&[b"ZADD", b"z", b"1", b"a", b"2", b"b", b"3", b"c"]);
8846        f.run(&[b"ZADD", b"y", b"10", b"b", b"20", b"d"]);
8847        assert_eq!(
8848            f.run(&[b"ZUNION", b"2", b"z", b"y"]),
8849            "*4\r\n$1\r\na\r\n$1\r\nc\r\n$1\r\nb\r\n$1\r\nd\r\n"
8850        );
8851        // The scores are added where a member is in both, and the answer comes
8852        // out in the order those combined scores put it in.
8853        assert_eq!(
8854            f.run(&[b"ZUNION", b"2", b"z", b"y", b"WITHSCORES"]),
8855            "*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"
8856        );
8857        assert_eq!(
8858            f.run(&[
8859                b"ZUNION",
8860                b"2",
8861                b"z",
8862                b"y",
8863                b"WEIGHTS",
8864                b"2",
8865                b"3",
8866                b"WITHSCORES"
8867            ]),
8868            "*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"
8869        );
8870        assert_eq!(
8871            f.run(&[
8872                b"ZUNION",
8873                b"2",
8874                b"z",
8875                b"y",
8876                b"AGGREGATE",
8877                b"MIN",
8878                b"WITHSCORES"
8879            ]),
8880            "*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"
8881        );
8882        assert_eq!(
8883            f.run(&[
8884                b"ZUNION",
8885                b"2",
8886                b"z",
8887                b"y",
8888                b"AGGREGATE",
8889                b"MAX",
8890                b"WITHSCORES"
8891            ]),
8892            "*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"
8893        );
8894        assert_eq!(
8895            f.run(&[b"ZINTER", b"2", b"z", b"y", b"WITHSCORES"]),
8896            "*2\r\n$1\r\nb\r\n$2\r\n12\r\n"
8897        );
8898        assert_eq!(
8899            f.run(&[b"ZDIFF", b"2", b"z", b"y", b"WITHSCORES"]),
8900            "*4\r\n$1\r\na\r\n$1\r\n1\r\n$1\r\nc\r\n$1\r\n3\r\n"
8901        );
8902        assert_eq!(f.run(&[b"ZUNION", b"1", b"nokey"]), "*0\r\n");
8903        // A plain set is an input, and it behaves as a sorted set in which
8904        // every member scores one.
8905        f.run(&[b"SADD", b"p", b"a", b"d"]);
8906        assert_eq!(
8907            f.run(&[b"ZUNION", b"2", b"z", b"p", b"WITHSCORES"]),
8908            "*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"
8909        );
8910        // A difference never combines two scores, so it has nothing for either
8911        // of the two options to do and refuses both.
8912        for cmd in [
8913            &[
8914                b"ZDIFF".as_slice(),
8915                b"2",
8916                b"z",
8917                b"y",
8918                b"WEIGHTS",
8919                b"1",
8920                b"1",
8921            ][..],
8922            &[b"ZDIFF", b"2", b"z", b"y", b"AGGREGATE", b"MIN"],
8923        ] {
8924            assert_eq!(f.run(cmd), "-ERR syntax error\r\n", "{cmd:?}");
8925        }
8926    }
8927
8928    /// The count of keys, which is what lets a key be named `WEIGHTS`.
8929    #[test]
8930    fn the_algebra_counts_its_keys_and_says_so_when_the_count_is_wrong() {
8931        let mut f = Fixture::new();
8932        f.run(&[b"ZADD", b"z", b"1", b"a"]);
8933        f.run(&[b"ZADD", b"y", b"2", b"b"]);
8934        // Redis names the command in this one, so each spelling says its own.
8935        assert_eq!(
8936            f.run(&[b"ZUNION", b"0", b"z"]),
8937            "-ERR at least 1 input key is needed for 'zunion' command\r\n"
8938        );
8939        assert_eq!(
8940            f.run(&[b"ZUNION", b"-1", b"z"]),
8941            "-ERR at least 1 input key is needed for 'zunion' command\r\n"
8942        );
8943        assert_eq!(
8944            f.run(&[b"ZINTERCARD", b"0", b"z"]),
8945            "-ERR at least 1 input key is needed for 'zintercard' command\r\n"
8946        );
8947        // A count bigger than the line is a plain syntax error, which reads
8948        // oddly and is what Redis says.
8949        assert_eq!(
8950            f.run(&[b"ZUNION", b"3", b"z", b"y"]),
8951            "-ERR syntax error\r\n"
8952        );
8953        assert_eq!(
8954            f.run(&[b"ZUNION", b"x", b"z"]),
8955            "-ERR value is not an integer or out of range\r\n"
8956        );
8957        // A WEIGHTS list that is not one per key is a syntax error, and a
8958        // weight that is not a number gets a sentence of its own.
8959        assert_eq!(
8960            f.run(&[b"ZUNION", b"2", b"z", b"y", b"WEIGHTS", b"1"]),
8961            "-ERR syntax error\r\n"
8962        );
8963        assert_eq!(
8964            f.run(&[b"ZUNION", b"2", b"z", b"y", b"WEIGHTS", b"a", b"b"]),
8965            "-ERR weight value is not a float\r\n"
8966        );
8967        assert_eq!(
8968            f.run(&[b"ZUNION", b"2", b"z", b"y", b"AGGREGATE", b"NOPE"]),
8969            "-ERR syntax error\r\n"
8970        );
8971    }
8972
8973    /// The three store forms, which answer a count and take no WITHSCORES.
8974    #[test]
8975    fn the_algebra_stores_answer_a_count_and_delete_an_empty_destination() {
8976        let mut f = Fixture::new();
8977        f.run(&[b"ZADD", b"z", b"1", b"a", b"2", b"b", b"3", b"c"]);
8978        f.run(&[b"ZADD", b"y", b"10", b"b", b"20", b"d"]);
8979        assert_eq!(f.run(&[b"ZUNIONSTORE", b"d", b"2", b"z", b"y"]), ":4\r\n");
8980        assert_eq!(
8981            f.run(&[b"ZRANGE", b"d", b"0", b"-1", b"WITHSCORES"]),
8982            "*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"
8983        );
8984        assert_eq!(f.run(&[b"ZINTERSTORE", b"d", b"2", b"z", b"y"]), ":1\r\n");
8985        assert_eq!(f.run(&[b"ZDIFFSTORE", b"d", b"2", b"z", b"y"]), ":2\r\n");
8986        // An empty result deletes the destination rather than leaving an empty
8987        // sorted set, because an empty one does not exist.
8988        assert_eq!(
8989            f.run(&[b"ZINTERSTORE", b"d", b"2", b"z", b"nokey"]),
8990            ":0\r\n"
8991        );
8992        assert_eq!(f.run(&[b"EXISTS", b"d"]), ":0\r\n");
8993        // The destination is allowed to name its own source.
8994        assert_eq!(f.run(&[b"ZUNIONSTORE", b"z", b"2", b"z", b"y"]), ":4\r\n");
8995        assert_eq!(f.run(&[b"ZCARD", b"z"]), ":4\r\n");
8996        for cmd in [
8997            &[
8998                b"ZUNIONSTORE".as_slice(),
8999                b"d",
9000                b"2",
9001                b"z",
9002                b"y",
9003                b"WITHSCORES",
9004            ][..],
9005            &[
9006                b"ZDIFFSTORE",
9007                b"d",
9008                b"2",
9009                b"z",
9010                b"y",
9011                b"WEIGHTS",
9012                b"1",
9013                b"1",
9014            ],
9015        ] {
9016            assert_eq!(f.run(cmd), "-ERR syntax error\r\n", "{cmd:?}");
9017        }
9018    }
9019
9020    /// `ZINTERCARD`, which counts without building anything.
9021    #[test]
9022    fn intercard_counts_and_stops_at_its_limit() {
9023        let mut f = Fixture::new();
9024        f.run(&[b"ZADD", b"z", b"1", b"a", b"2", b"b", b"3", b"c"]);
9025        f.run(&[b"ZADD", b"y", b"10", b"b", b"20", b"c", b"30", b"d"]);
9026        assert_eq!(f.run(&[b"ZINTERCARD", b"2", b"z", b"y"]), ":2\r\n");
9027        // A limit of zero is no limit, which is Redis's reading of it.
9028        assert_eq!(
9029            f.run(&[b"ZINTERCARD", b"2", b"z", b"y", b"LIMIT", b"0"]),
9030            ":2\r\n"
9031        );
9032        assert_eq!(
9033            f.run(&[b"ZINTERCARD", b"2", b"z", b"y", b"LIMIT", b"1"]),
9034            ":1\r\n"
9035        );
9036        // A negative limit and a limit that is not a number at all get the same
9037        // sentence, which looks like a mistake in Redis and is copied as one.
9038        let bad = "-ERR LIMIT can't be negative\r\n";
9039        assert_eq!(
9040            f.run(&[b"ZINTERCARD", b"2", b"z", b"y", b"LIMIT", b"-1"]),
9041            bad
9042        );
9043        assert_eq!(
9044            f.run(&[b"ZINTERCARD", b"2", b"z", b"y", b"LIMIT", b"x"]),
9045            bad
9046        );
9047        for cmd in [
9048            &[b"ZINTERCARD".as_slice(), b"3", b"z", b"y"][..],
9049            &[b"ZINTERCARD", b"2", b"z", b"y", b"LIMIT"],
9050            &[b"ZINTERCARD", b"2", b"z", b"y", b"junk", b"1"],
9051        ] {
9052            assert_eq!(f.run(cmd), "-ERR syntax error\r\n", "{cmd:?}");
9053        }
9054    }
9055
9056    /// `ZRANDMEMBER`, which answers two different shapes out of one name.
9057    #[test]
9058    fn a_draw_answers_one_member_or_an_array_of_them() {
9059        let mut f = Fixture::new();
9060        f.run(&[b"ZADD", b"z", b"1", b"a", b"2", b"b", b"3", b"c"]);
9061        // No count is one member or a nil, a count is an array that may be
9062        // empty, and those are two reply types the client has to tell apart.
9063        assert_eq!(f.run(&[b"ZRANDMEMBER", b"nokey"]), "$-1\r\n");
9064        assert_eq!(f.run(&[b"ZRANDMEMBER", b"nokey", b"3"]), "*0\r\n");
9065        assert_eq!(f.run(&[b"ZRANDMEMBER", b"z", b"0"]), "*0\r\n");
9066        assert!(f.run(&[b"ZRANDMEMBER", b"z"]).starts_with("$1\r\n"));
9067        // A positive count draws without replacement, so a count over the size
9068        // answers the whole set and never a member twice.
9069        let all = f.run(&[b"ZRANDMEMBER", b"z", b"10"]);
9070        assert!(all.starts_with("*3\r\n"), "{all}");
9071        for m in ["a", "b", "c"] {
9072            assert!(all.contains(m), "{all}");
9073        }
9074        // A negative one draws with replacement and answers exactly as many as
9075        // it was asked for, whatever the size of the set.
9076        assert!(
9077            f.run(&[b"ZRANDMEMBER", b"z", b"-5"]).starts_with("*5\r\n"),
9078            "five draws with replacement"
9079        );
9080        assert!(
9081            f.run(&[b"ZRANDMEMBER", b"z", b"2", b"WITHSCORES"])
9082                .starts_with("*4\r\n"),
9083            "two pairs, flat on RESP2"
9084        );
9085        f.out = Out::new(Proto::Resp3);
9086        let got = f.run(&[b"ZRANDMEMBER", b"z", b"2", b"WITHSCORES"]);
9087        assert!(got.starts_with("*2\r\n*2\r\n"), "{got}");
9088        assert_eq!(f.run(&[b"ZRANDMEMBER", b"nokey"]), "_\r\n");
9089        f.out = Out::new(Proto::Resp2);
9090        assert_eq!(
9091            f.run(&[b"ZRANDMEMBER", b"z", b"2", b"junk"]),
9092            "-ERR syntax error\r\n"
9093        );
9094        assert_eq!(
9095            f.run(&[b"ZRANDMEMBER", b"z", b"x"]),
9096            "-ERR value is not an integer or out of range\r\n"
9097        );
9098    }
9099
9100    /// `ZSCAN`, and the one sorted set reply where a score is not a double.
9101    #[test]
9102    fn a_sorted_set_scan_answers_pairs_of_strings_on_both_protocols() {
9103        let mut f = Fixture::new();
9104        f.run(&[b"ZADD", b"z", b"1", b"a", b"2", b"b", b"3", b"c"]);
9105        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";
9106        assert_eq!(f.run(&[b"ZSCAN", b"z", b"0"]), all);
9107        assert_eq!(f.run(&[b"ZSCAN", b"z", b"0", b"COUNT", b"10"]), all);
9108        assert_eq!(
9109            f.run(&[b"ZSCAN", b"z", b"0", b"MATCH", b"a*"]),
9110            "*2\r\n$1\r\n0\r\n*2\r\n$1\r\na\r\n$1\r\n1\r\n"
9111        );
9112        assert_eq!(
9113            f.run(&[b"ZSCAN", b"nokey", b"0"]),
9114            "*2\r\n$1\r\n0\r\n*0\r\n"
9115        );
9116        // A score stays a bulk string on RESP3, which is the one place the two
9117        // protocols agree about a score and everywhere else they do not.
9118        f.out = Out::new(Proto::Resp3);
9119        assert_eq!(f.run(&[b"ZSCAN", b"z", b"0"]), all);
9120        f.out = Out::new(Proto::Resp2);
9121        assert_eq!(
9122            f.run(&[b"ZSCAN", b"z", b"0", b"NOVALUES"]),
9123            "-ERR NOVALUES option can only be used in HSCAN\r\n"
9124        );
9125        assert_eq!(f.run(&[b"ZSCAN", b"z", b"-1"]), "-ERR invalid cursor\r\n");
9126        assert_eq!(
9127            f.run(&[b"ZSCAN", b"z", b"0", b"COUNT", b"0"]),
9128            "-ERR syntax error\r\n"
9129        );
9130    }
9131
9132    /// The count is what decides the shape, and its value is not.
9133    #[test]
9134    fn a_sorted_set_pop_changes_shape_when_it_is_given_a_count() {
9135        let mut f = Fixture::new();
9136        f.run(&[b"ZADD", b"z", b"1", b"a", b"2", b"b", b"3", b"c"]);
9137        // No count, so one flat pair, and the score is a bulk string on RESP2.
9138        assert_eq!(f.run(&[b"ZPOPMIN", b"z"]), "*2\r\n$1\r\na\r\n$1\r\n1\r\n");
9139        assert_eq!(f.run(&[b"ZPOPMAX", b"z"]), "*2\r\n$1\r\nc\r\n$1\r\n3\r\n");
9140        f.run(&[b"ZADD", b"z", b"1", b"a", b"3", b"c"]);
9141        // A count, so pairs, and on RESP2 they are flattened into one run.
9142        assert_eq!(
9143            f.run(&[b"ZPOPMIN", b"z", b"2"]),
9144            "*4\r\n$1\r\na\r\n$1\r\n1\r\n$1\r\nb\r\n$1\r\n2\r\n"
9145        );
9146        // An empty array rather than a null, which is where a sorted set pop and
9147        // a list pop part company, and the same answer a count of zero gives.
9148        assert_eq!(f.run(&[b"ZPOPMIN", b"nokey"]), "*0\r\n");
9149        assert_eq!(f.run(&[b"ZPOPMIN", b"nokey", b"2"]), "*0\r\n");
9150        assert_eq!(f.run(&[b"ZPOPMIN", b"z", b"0"]), "*0\r\n");
9151        // The last member takes the key with it.
9152        assert_eq!(
9153            f.run(&[b"ZPOPMIN", b"z", b"9"]),
9154            "*2\r\n$1\r\nc\r\n$1\r\n3\r\n"
9155        );
9156        assert_eq!(f.run(&[b"EXISTS", b"z"]), ":0\r\n");
9157
9158        f.run(&[b"ZADD", b"z", b"1", b"a", b"2", b"b"]);
9159        f.out = Out::new(Proto::Resp3);
9160        assert_eq!(f.run(&[b"ZPOPMIN", b"z"]), "*2\r\n$1\r\na\r\n,1\r\n");
9161        assert_eq!(
9162            f.run(&[b"ZPOPMIN", b"z", b"1"]),
9163            "*1\r\n*2\r\n$1\r\nb\r\n,2\r\n"
9164        );
9165        f.out = Out::new(Proto::Resp2);
9166        // Both of these are the range error rather than the usual sentence about
9167        // integers, which is the odd answer and so the one worth copying.
9168        let bad = "-ERR value is out of range, must be positive\r\n";
9169        assert_eq!(f.run(&[b"ZPOPMIN", b"z", b"x"]), bad);
9170        assert_eq!(f.run(&[b"ZPOPMIN", b"z", b"-1"]), bad);
9171        assert_eq!(
9172            f.run(&[b"ZPOPMIN", b"z", b"1", b"2"]),
9173            "-ERR syntax error\r\n"
9174        );
9175    }
9176
9177    /// `ZMPOP`, which is `LMPOP` with scores and the same parse.
9178    #[test]
9179    fn a_multi_key_pop_names_the_key_that_answered_and_nests_its_pairs() {
9180        let mut f = Fixture::new();
9181        f.run(&[b"ZADD", b"z", b"1", b"a", b"2", b"b", b"3", b"c"]);
9182        assert_eq!(
9183            f.run(&[b"ZMPOP", b"2", b"nokey", b"z", b"MIN"]),
9184            "*2\r\n$1\r\nz\r\n*1\r\n*2\r\n$1\r\na\r\n$1\r\n1\r\n"
9185        );
9186        // Nested on RESP2 as well, because the key name is already in front of
9187        // the pairs and there is nothing left to flatten into.
9188        assert_eq!(
9189            f.run(&[b"ZMPOP", b"1", b"z", b"MAX", b"COUNT", b"2"]),
9190            "*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"
9191        );
9192        // A null array and not a null, the same as LMPOP.
9193        assert_eq!(f.run(&[b"ZMPOP", b"1", b"nokey", b"MIN"]), "*-1\r\n");
9194        f.out = Out::new(Proto::Resp3);
9195        assert_eq!(f.run(&[b"ZMPOP", b"1", b"nokey", b"MIN"]), "_\r\n");
9196        f.out = Out::new(Proto::Resp2);
9197        let numkeys = "-ERR numkeys should be greater than 0\r\n";
9198        for bad in [
9199            &[b"ZMPOP".as_slice(), b"0", b"z", b"MIN"][..],
9200            &[b"ZMPOP", b"-1", b"z", b"MIN"],
9201            &[b"ZMPOP", b"x", b"z", b"MIN"],
9202        ] {
9203            assert_eq!(f.run(bad), numkeys, "{:?}", bad[1]);
9204        }
9205        let count = "-ERR count should be greater than 0\r\n";
9206        for bad in [
9207            &[b"ZMPOP".as_slice(), b"1", b"z", b"MIN", b"COUNT", b"0"][..],
9208            &[b"ZMPOP", b"1", b"z", b"MIN", b"COUNT", b"-1"],
9209            &[b"ZMPOP", b"1", b"z", b"MIN", b"COUNT", b"x"],
9210        ] {
9211            assert_eq!(f.run(bad), count, "{:?}", bad[5]);
9212        }
9213        let syntax = "-ERR syntax error\r\n";
9214        for bad in [
9215            // Two keys named and one given, so the word that should have been
9216            // the direction is a key and there is no direction left.
9217            &[b"ZMPOP".as_slice(), b"2", b"z", b"MIN"][..],
9218            &[b"ZMPOP", b"1", b"z", b"SIDEWAYS"],
9219            &[b"ZMPOP", b"1", b"z", b"MIN", b"junk"],
9220            &[b"ZMPOP", b"1", b"z", b"MIN", b"COUNT", b"1", b"junk"],
9221        ] {
9222            assert_eq!(f.run(bad), syntax, "{bad:?}");
9223        }
9224    }
9225
9226    /// The three that wait, when there is something there and they do not have
9227    /// to. `BZPOPMIN` is the one reply in the group that is three flat elements.
9228    #[test]
9229    fn the_sorted_set_pops_that_wait_answer_like_the_ones_they_wrap() {
9230        let mut f = Fixture::new();
9231        f.run(&[b"ZADD", b"z", b"1", b"a", b"2", b"b", b"3", b"c"]);
9232        assert_eq!(
9233            f.flow(&[b"BZPOPMIN", b"nokey", b"z", b"0"]),
9234            (
9235                Flow::Continue,
9236                "*3\r\n$1\r\nz\r\n$1\r\na\r\n$1\r\n1\r\n".to_owned()
9237            )
9238        );
9239        assert_eq!(
9240            f.run(&[b"BZPOPMAX", b"z", b"0"]),
9241            "*3\r\n$1\r\nz\r\n$1\r\nc\r\n$1\r\n3\r\n"
9242        );
9243        f.run(&[b"ZADD", b"z", b"1", b"a", b"3", b"c"]);
9244        assert_eq!(
9245            f.run(&[
9246                b"BZMPOP", b"0", b"2", b"nokey", b"z", b"MIN", b"COUNT", b"2"
9247            ]),
9248            "*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"
9249        );
9250        f.out = Out::new(Proto::Resp3);
9251        assert_eq!(
9252            f.run(&[b"BZPOPMIN", b"z", b"0"]),
9253            "*3\r\n$1\r\nz\r\n$1\r\nc\r\n,3\r\n"
9254        );
9255        f.out = Out::new(Proto::Resp2);
9256        // Nothing to take, so the client is parked and nothing was written.
9257        assert_eq!(
9258            f.flow(&[b"BZPOPMIN", b"z", b"0"]),
9259            (Flow::Block, String::new())
9260        );
9261        assert_eq!(
9262            f.flow(&[b"BZMPOP", b"0", b"1", b"z", b"MIN"]),
9263            (Flow::Block, String::new())
9264        );
9265        // The timeout is read before the key count, so this complains about the
9266        // timeout and not about the count.
9267        assert_eq!(
9268            f.run(&[b"BZMPOP", b"abc", b"0", b"z", b"MIN"]),
9269            "-ERR timeout is not a float or out of range\r\n"
9270        );
9271        assert_eq!(
9272            f.run(&[b"BZMPOP", b"0", b"0", b"z", b"MIN"]),
9273            "-ERR numkeys should be greater than 0\r\n"
9274        );
9275        assert_eq!(
9276            f.run(&[b"BZPOPMIN", b"z", b"-1"]),
9277            "-ERR timeout is negative\r\n"
9278        );
9279    }
9280
9281    /// A parked sorted set client is served by whatever puts a member under one
9282    /// of its keys, and is not served by something of another type landing
9283    /// there.
9284    #[test]
9285    fn a_parked_sorted_set_client_waits_for_a_member_and_not_for_a_key() {
9286        let mut f = Fixture::new();
9287        assert_eq!(f.flow(&[b"BZPOPMIN", b"z", b"0"]).0, Flow::Block);
9288        assert_eq!(f.server.parked(), 1);
9289        // A string under the key is not what it asked for, so it stays parked
9290        // rather than being handed a WRONGTYPE on a command that was accepted.
9291        f.run(&[b"SET", b"z", b"v"]);
9292        let mut out = Out::new(Proto::Resp2);
9293        assert!(!f.server.serve_waiter(7, 0, &mut out));
9294        assert!(out.as_slice().is_empty());
9295        f.run(&[b"DEL", b"z"]);
9296        f.run(&[b"ZADD", b"z", b"5", b"m"]);
9297        assert!(f.server.serve_waiter(7, 0, &mut out));
9298        assert_eq!(
9299            core::str::from_utf8(out.as_slice()).expect("ascii"),
9300            "*3\r\n$1\r\nz\r\n$1\r\nm\r\n$1\r\n5\r\n"
9301        );
9302        // And the member is gone, which is what makes a queue of workers on a
9303        // sorted set work at all.
9304        assert_eq!(f.run(&[b"EXISTS", b"z"]), ":0\r\n");
9305    }
9306
9307    #[test]
9308    fn every_sorted_set_command_says_wrongtype_and_writes_nothing() {
9309        let mut f = Fixture::new();
9310        f.run(&[b"SET", b"s", b"v"]);
9311        let wrong = "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n";
9312        for cmd in [
9313            &[b"ZADD".as_slice(), b"s", b"1", b"a"][..],
9314            &[b"ZINCRBY", b"s", b"1", b"a"],
9315            &[b"ZCARD", b"s"],
9316            &[b"ZSCORE", b"s", b"a"],
9317            &[b"ZMSCORE", b"s", b"a"],
9318            &[b"ZREM", b"s", b"a"],
9319            &[b"ZRANK", b"s", b"a"],
9320            &[b"ZREVRANK", b"s", b"a"],
9321            &[b"ZCOUNT", b"s", b"1", b"2"],
9322            &[b"ZLEXCOUNT", b"s", b"-", b"+"],
9323            &[b"ZRANGE", b"s", b"0", b"-1"],
9324            &[b"ZREVRANGE", b"s", b"0", b"-1"],
9325            &[b"ZRANGEBYSCORE", b"s", b"1", b"2"],
9326            &[b"ZREVRANGEBYSCORE", b"s", b"2", b"1"],
9327            &[b"ZRANGEBYLEX", b"s", b"-", b"+"],
9328            &[b"ZREVRANGEBYLEX", b"s", b"+", b"-"],
9329            &[b"ZRANGESTORE", b"d", b"s", b"0", b"-1"],
9330            &[b"ZREMRANGEBYRANK", b"s", b"0", b"-1"],
9331            &[b"ZREMRANGEBYSCORE", b"s", b"1", b"2"],
9332            &[b"ZREMRANGEBYLEX", b"s", b"-", b"+"],
9333            &[b"ZUNION", b"1", b"s"],
9334            &[b"ZINTER", b"1", b"s"],
9335            &[b"ZDIFF", b"1", b"s"],
9336            &[b"ZUNIONSTORE", b"d", b"1", b"s"],
9337            &[b"ZINTERSTORE", b"d", b"1", b"s"],
9338            &[b"ZDIFFSTORE", b"d", b"1", b"s"],
9339            &[b"ZINTERCARD", b"1", b"s"],
9340            &[b"ZRANDMEMBER", b"s"],
9341            &[b"ZSCAN", b"s", b"0"],
9342            &[b"ZPOPMIN", b"s"],
9343            &[b"ZPOPMAX", b"s", b"2"],
9344            &[b"ZMPOP", b"1", b"s", b"MIN"],
9345            &[b"BZPOPMIN", b"s", b"0"],
9346            &[b"BZPOPMAX", b"s", b"0"],
9347            &[b"BZMPOP", b"0", b"1", b"s", b"MIN"],
9348        ] {
9349            assert_eq!(f.run(cmd), wrong, "{:?}", cmd[0]);
9350        }
9351        assert_eq!(f.run(&[b"GET", b"s"]), "$1\r\nv\r\n");
9352    }
9353
9354    /// The same churn the set, the string and the list get, because a sorted
9355    /// set that leaks a tree node per add looks exactly like one that does not
9356    /// until it has run for an afternoon.
9357    /// Not under Miri, for the reason on `churning_sets_does_not_grow_the_server`.
9358    #[cfg_attr(miri, ignore = "the volume is the claim")]
9359    #[test]
9360    fn churning_sorted_sets_does_not_grow_the_server() {
9361        let mut f = Fixture::new();
9362        let members: Vec<Vec<u8>> = (0..200).map(|i| format!("m{i}").into_bytes()).collect();
9363        let scores: Vec<Vec<u8>> = (0..200).map(|i| format!("{i}").into_bytes()).collect();
9364        let mut args: Vec<&[u8]> = vec![b"ZADD", b"z"];
9365        for i in 0..200 {
9366            args.push(&scores[i]);
9367            args.push(&members[i]);
9368        }
9369
9370        f.run(&args);
9371        f.run(&[b"DEL", b"z"]);
9372        f.server.compact_step();
9373        let after_first = f.server.memory_bytes();
9374
9375        for _ in 0..200 {
9376            f.run(&args);
9377            f.run(&[b"DEL", b"z"]);
9378            f.server.compact_step();
9379        }
9380        assert_eq!(f.run(&[b"DBSIZE"]), ":0\r\n");
9381        assert!(
9382            f.server.memory_bytes() <= after_first * 2,
9383            "held {} after two hundred passes against {after_first} after one",
9384            f.server.memory_bytes()
9385        );
9386    }
9387
9388    // ------------------------------------------------------------------- geo
9389
9390    /// The three places every Redis geo example uses, and one more.
9391    ///
9392    /// Every reply this section asserts on came off a running 8.10.1 with these
9393    /// three loaded, byte for byte, including the number of digits in a
9394    /// coordinate and the four places on a distance.
9395    fn sicily(f: &mut Fixture) {
9396        f.run(&[
9397            b"GEOADD",
9398            b"Sicily",
9399            b"13.361389",
9400            b"38.115556",
9401            b"Palermo",
9402            b"15.087269",
9403            b"37.502669",
9404            b"Catania",
9405        ]);
9406        f.run(&[
9407            b"GEOADD",
9408            b"Sicily",
9409            b"13.583333",
9410            b"37.316667",
9411            b"Agrigento",
9412        ]);
9413    }
9414
9415    #[test]
9416    fn places_go_in_as_scores_and_come_back_as_positions() {
9417        let mut f = Fixture::new();
9418        assert_eq!(
9419            f.run(&[
9420                b"GEOADD",
9421                b"Sicily",
9422                b"13.361389",
9423                b"38.115556",
9424                b"Palermo",
9425                b"15.087269",
9426                b"37.502669",
9427                b"Catania"
9428            ]),
9429            ":2\r\n"
9430        );
9431        // A geo key is a sorted set and says so, which is not an implementation
9432        // detail either: a client removes a place with ZREM and counts them
9433        // with ZCARD, and the score is the number a real server stores.
9434        assert_eq!(f.run(&[b"TYPE", b"Sicily"]), "+zset\r\n");
9435        assert_eq!(
9436            f.run(&[b"ZSCORE", b"Sicily", b"Palermo"]),
9437            "$16\r\n3479099956230698\r\n"
9438        );
9439        assert_eq!(
9440            f.run(&[b"GEOPOS", b"Sicily", b"Palermo", b"NonExisting"]),
9441            "*2\r\n*2\r\n$18\r\n13.361389338970184\r\n$16\r\n38.1155563954963\r\n*-1\r\n"
9442        );
9443        assert_eq!(
9444            f.run(&[
9445                b"GEOHASH",
9446                b"Sicily",
9447                b"Palermo",
9448                b"Catania",
9449                b"NonExisting"
9450            ]),
9451            "*3\r\n$11\r\nsqc8b49rny0\r\n$11\r\nsqdtr74hyu0\r\n$-1\r\n"
9452        );
9453        // A key that is not there is an empty one, and the two nulls are not
9454        // the same null: GEOPOS answers the array one and GEOHASH the string
9455        // one, which a RESP2 client can tell apart.
9456        assert_eq!(f.run(&[b"GEOPOS", b"nokey", b"a"]), "*1\r\n*-1\r\n");
9457        assert_eq!(f.run(&[b"GEOHASH", b"nokey", b"a"]), "*1\r\n$-1\r\n");
9458    }
9459
9460    #[test]
9461    fn a_distance_comes_back_with_four_places_in_whatever_unit_was_asked_for() {
9462        let mut f = Fixture::new();
9463        sicily(&mut f);
9464        assert_eq!(
9465            f.run(&[b"GEODIST", b"Sicily", b"Palermo", b"Catania"]),
9466            "$11\r\n166274.1516\r\n"
9467        );
9468        assert_eq!(
9469            f.run(&[b"GEODIST", b"Sicily", b"Palermo", b"Catania", b"km"]),
9470            "$8\r\n166.2742\r\n"
9471        );
9472        assert_eq!(
9473            f.run(&[b"GEODIST", b"Sicily", b"Palermo", b"Catania", b"mi"]),
9474            "$8\r\n103.3182\r\n"
9475        );
9476        // A member that is not there and a key that is not there are the same
9477        // nil, and the unit is read before the key is looked up, so a bad unit
9478        // on a missing key is still an error.
9479        assert_eq!(
9480            f.run(&[b"GEODIST", b"Sicily", b"Palermo", b"Foo"]),
9481            "$-1\r\n"
9482        );
9483        assert_eq!(f.run(&[b"GEODIST", b"nokey", b"a", b"b"]), "$-1\r\n");
9484        assert_eq!(
9485            f.run(&[b"GEODIST", b"nokey", b"a", b"b", b"parsecs"]),
9486            "-ERR unsupported unit provided. please use M, KM, FT, MI\r\n"
9487        );
9488        assert_eq!(
9489            f.run(&[b"GEODIST", b"Sicily", b"a", b"b", b"km", b"extra"]),
9490            "-ERR syntax error\r\n"
9491        );
9492    }
9493
9494    #[test]
9495    fn a_search_finds_what_is_inside_it_nearest_first() {
9496        let mut f = Fixture::new();
9497        sicily(&mut f);
9498        let all = "*3\r\n$7\r\nCatania\r\n$9\r\nAgrigento\r\n$7\r\nPalermo\r\n";
9499        assert_eq!(
9500            f.run(&[
9501                b"GEOSEARCH",
9502                b"Sicily",
9503                b"FROMLONLAT",
9504                b"15",
9505                b"37",
9506                b"BYRADIUS",
9507                b"200",
9508                b"km",
9509                b"ASC"
9510            ]),
9511            all
9512        );
9513        // The older spelling of the same search, which is the same nine boxes
9514        // and the same order.
9515        assert_eq!(
9516            f.run(&[b"GEORADIUS", b"Sicily", b"15", b"37", b"200", b"km", b"ASC"]),
9517            all
9518        );
9519        assert_eq!(
9520            f.run(&[
9521                b"GEORADIUS_RO",
9522                b"Sicily",
9523                b"15",
9524                b"37",
9525                b"200",
9526                b"km",
9527                b"ASC"
9528            ]),
9529            all
9530        );
9531        // A count with no ordering means the nearest ones, so DESC has to be
9532        // asked for to get the far end.
9533        assert_eq!(
9534            f.run(&[
9535                b"GEORADIUS",
9536                b"Sicily",
9537                b"15",
9538                b"37",
9539                b"200",
9540                b"km",
9541                b"DESC",
9542                b"COUNT",
9543                b"1"
9544            ]),
9545            "*1\r\n$7\r\nPalermo\r\n"
9546        );
9547        assert_eq!(
9548            f.run(&[
9549                b"GEORADIUS",
9550                b"Sicily",
9551                b"15",
9552                b"37",
9553                b"200",
9554                b"km",
9555                b"COUNT",
9556                b"1"
9557            ]),
9558            "*1\r\n$7\r\nCatania\r\n"
9559        );
9560        // Nothing inside a kilometre of that point, and nothing in a key that
9561        // is not there, and both are the empty array rather than an error.
9562        let empty = "*0\r\n";
9563        assert_eq!(
9564            f.run(&[
9565                b"GEOSEARCH",
9566                b"Sicily",
9567                b"FROMLONLAT",
9568                b"15",
9569                b"37",
9570                b"BYRADIUS",
9571                b"1",
9572                b"km"
9573            ]),
9574            empty
9575        );
9576        assert_eq!(
9577            f.run(&[
9578                b"GEOSEARCH",
9579                b"nokey",
9580                b"FROMLONLAT",
9581                b"15",
9582                b"37",
9583                b"BYRADIUS",
9584                b"1",
9585                b"km"
9586            ]),
9587            empty
9588        );
9589        assert_eq!(
9590            f.run(&[b"GEORADIUSBYMEMBER", b"nokey", b"m", b"1", b"km"]),
9591            empty
9592        );
9593    }
9594
9595    #[test]
9596    fn a_search_centred_on_a_member_starts_from_where_that_member_is() {
9597        let mut f = Fixture::new();
9598        sicily(&mut f);
9599        assert_eq!(
9600            f.run(&[b"GEORADIUSBYMEMBER", b"Sicily", b"Agrigento", b"100", b"km"]),
9601            "*2\r\n$9\r\nAgrigento\r\n$7\r\nPalermo\r\n"
9602        );
9603        // The member itself is nothing away from itself, which is where the
9604        // fixed point writer's zero shows up on the wire.
9605        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";
9606        assert_eq!(
9607            f.run(&[
9608                b"GEORADIUSBYMEMBER_RO",
9609                b"Sicily",
9610                b"Agrigento",
9611                b"100",
9612                b"km",
9613                b"WITHDIST"
9614            ]),
9615            with_dist
9616        );
9617        assert_eq!(
9618            f.run(&[
9619                b"GEOSEARCH",
9620                b"Sicily",
9621                b"FROMMEMBER",
9622                b"Agrigento",
9623                b"BYRADIUS",
9624                b"100",
9625                b"km",
9626                b"ASC",
9627                b"WITHDIST"
9628            ]),
9629            with_dist
9630        );
9631        assert_eq!(
9632            f.run(&[b"GEORADIUSBYMEMBER", b"Sicily", b"Nowhere", b"100", b"km"]),
9633            "-ERR could not decode requested zset member\r\n"
9634        );
9635    }
9636
9637    #[test]
9638    fn a_box_search_reports_the_distance_the_hash_and_the_coordinates() {
9639        let mut f = Fixture::new();
9640        sicily(&mut f);
9641        // Three options asked for, so each result is a four element array of
9642        // the member, the distance, the hash and a pair. The order of the three
9643        // is Redis's and not the order they were written in the command.
9644        assert_eq!(
9645            f.run(&[
9646                b"GEOSEARCH",
9647                b"Sicily",
9648                b"FROMLONLAT",
9649                b"15",
9650                b"37",
9651                b"BYBOX",
9652                b"400",
9653                b"400",
9654                b"km",
9655                b"ASC",
9656                b"WITHCOORD",
9657                b"WITHDIST",
9658                b"WITHHASH"
9659            ]),
9660            "*3\r\n*4\r\n$7\r\nCatania\r\n$7\r\n56.4413\r\n:3479447370796909\r\n*2\r\n\
9661             $18\r\n15.087267458438873\r\n$17\r\n37.50266842333162\r\n\
9662             *4\r\n$9\r\nAgrigento\r\n$8\r\n130.4235\r\n:3479030013248308\r\n*2\r\n\
9663             $18\r\n13.583331406116486\r\n$18\r\n37.316668049938166\r\n\
9664             *4\r\n$7\r\nPalermo\r\n$8\r\n190.4424\r\n:3479099956230698\r\n*2\r\n\
9665             $18\r\n13.361389338970184\r\n$16\r\n38.1155563954963\r\n"
9666        );
9667    }
9668
9669    #[test]
9670    fn a_store_writes_the_hashes_and_a_storedist_writes_the_distances() {
9671        let mut f = Fixture::new();
9672        sicily(&mut f);
9673        let hashes = "*6\r\n$9\r\nAgrigento\r\n$16\r\n3479030013248308\r\n\
9674                      $7\r\nPalermo\r\n$16\r\n3479099956230698\r\n\
9675                      $7\r\nCatania\r\n$16\r\n3479447370796909\r\n";
9676        assert_eq!(
9677            f.run(&[
9678                b"GEOSEARCHSTORE",
9679                b"dst",
9680                b"Sicily",
9681                b"FROMLONLAT",
9682                b"15",
9683                b"37",
9684                b"BYRADIUS",
9685                b"200",
9686                b"km",
9687                b"ASC"
9688            ]),
9689            ":3\r\n"
9690        );
9691        assert_eq!(
9692            f.run(&[b"ZRANGE", b"dst", b"0", b"-1", b"WITHSCORES"]),
9693            hashes
9694        );
9695        // The same again through the older spelling, which stores the same
9696        // scores, so a key written by either is a geo key.
9697        assert_eq!(
9698            f.run(&[
9699                b"GEORADIUS",
9700                b"Sicily",
9701                b"15",
9702                b"37",
9703                b"200",
9704                b"km",
9705                b"STORE",
9706                b"dst3"
9707            ]),
9708            ":3\r\n"
9709        );
9710        assert_eq!(
9711            f.run(&[b"ZRANGE", b"dst3", b"0", b"-1", b"WITHSCORES"]),
9712            hashes
9713        );
9714        // STOREDIST stores the distance in the search unit instead, and those
9715        // are full doubles rather than the four places WITHDIST writes. The
9716        // numbers on the right are what 8.10.1 stored for this search, and they
9717        // are compared with a tolerance rather than byte for byte because the
9718        // last bit of a haversine is the platform's sin, cos and asin: this
9719        // machine and that one disagree in the sixteenth digit, and so do two
9720        // Redis builds. Everything a client actually reads back is four places
9721        // and is asserted exactly above.
9722        assert_eq!(
9723            f.run(&[
9724                b"GEOSEARCHSTORE",
9725                b"dst2",
9726                b"Sicily",
9727                b"FROMLONLAT",
9728                b"15",
9729                b"37",
9730                b"BYRADIUS",
9731                b"200",
9732                b"km",
9733                b"ASC",
9734                b"STOREDIST"
9735            ]),
9736            ":3\r\n"
9737        );
9738        for (member, want) in [
9739            ("Catania", 56.441_257_870_158_19),
9740            ("Agrigento", 130.423_487_067_147_14),
9741            ("Palermo", 190.442_429_847_757_92),
9742        ] {
9743            let reply = f.run(&[b"ZSCORE", b"dst2", member.as_bytes()]);
9744            let got: f64 = reply
9745                .trim_start_matches(|c: char| c != '\n')
9746                .trim()
9747                .parse()
9748                .unwrap_or_else(|_| panic!("{member} scored {reply:?}"));
9749            assert!(
9750                (got - want).abs() < 1e-9,
9751                "{member} scored {got} not {want}"
9752            );
9753        }
9754        // The order they went in is the order the scores put them in, which is
9755        // the point of storing the distance rather than the hash.
9756        assert_eq!(
9757            f.run(&[b"ZRANGE", b"dst2", b"0", b"-1"]),
9758            "*3\r\n$7\r\nCatania\r\n$9\r\nAgrigento\r\n$7\r\nPalermo\r\n"
9759        );
9760        // A search that finds nothing takes the destination with it rather than
9761        // leaving what was there, and a source key that is not there is a
9762        // search that finds nothing.
9763        assert_eq!(
9764            f.run(&[
9765                b"GEOSEARCHSTORE",
9766                b"dst",
9767                b"nokey",
9768                b"FROMLONLAT",
9769                b"15",
9770                b"37",
9771                b"BYRADIUS",
9772                b"200",
9773                b"km"
9774            ]),
9775            ":0\r\n"
9776        );
9777        assert_eq!(f.run(&[b"EXISTS", b"dst"]), ":0\r\n");
9778    }
9779
9780    #[test]
9781    fn the_gates_on_geoadd_are_the_ones_zadd_has() {
9782        let mut f = Fixture::new();
9783        sicily(&mut f);
9784        // XX on a member that is already where it is changes nothing, and NX on
9785        // one that is there refuses to move it.
9786        assert_eq!(
9787            f.run(&[
9788                b"GEOADD",
9789                b"Sicily",
9790                b"XX",
9791                b"CH",
9792                b"13.361389",
9793                b"38.115556",
9794                b"Palermo"
9795            ]),
9796            ":0\r\n"
9797        );
9798        assert_eq!(
9799            f.run(&[
9800                b"GEOADD",
9801                b"Sicily",
9802                b"NX",
9803                b"13.361389",
9804                b"38.9",
9805                b"Palermo"
9806            ]),
9807            ":0\r\n"
9808        );
9809        assert_eq!(
9810            f.run(&[
9811                b"GEOADD",
9812                b"Sicily",
9813                b"CH",
9814                b"13.361389",
9815                b"38.9",
9816                b"Palermo"
9817            ]),
9818            ":1\r\n"
9819        );
9820        // Out of range, and nothing is stored: the whole call is refused rather
9821        // than the good pairs going in and the bad one stopping it.
9822        assert_eq!(
9823            f.run(&[
9824                b"GEOADD",
9825                b"new",
9826                b"13.361389",
9827                b"38.115556",
9828                b"here",
9829                b"181",
9830                b"38",
9831                b"there"
9832            ]),
9833            "-ERR invalid longitude,latitude pair 181.000000,38.000000\r\n"
9834        );
9835        assert_eq!(f.run(&[b"EXISTS", b"new"]), ":0\r\n");
9836        assert_eq!(
9837            f.run(&[b"GEOADD", b"new", b"x", b"38", b"here"]),
9838            "-ERR value is not a valid float\r\n"
9839        );
9840        // The count of triples is checked before the two gates are, and a call
9841        // with no triples at all reaches the same sentence.
9842        assert_eq!(
9843            f.run(&[b"GEOADD", b"new", b"13", b"38", b"here", b"and"]),
9844            "-ERR syntax error\r\n"
9845        );
9846        assert_eq!(
9847            f.run(&[b"GEOADD", b"new", b"NX", b"XX", b"CH"]),
9848            "-ERR syntax error\r\n"
9849        );
9850        assert_eq!(
9851            f.run(&[b"GEOADD", b"new", b"CH", b"CH", b"CH", b"CH"]),
9852            "-ERR syntax error\r\n"
9853        );
9854        assert_eq!(
9855            f.run(&[b"GEOADD", b"new", b"NX", b"CH"]),
9856            "-ERR wrong number of arguments for 'geoadd' command\r\n"
9857        );
9858    }
9859
9860    /// The sentences a search answers, which are its contract as much as the
9861    /// results are.
9862    #[test]
9863    fn every_way_a_search_can_be_written_wrong_has_its_own_sentence() {
9864        let mut f = Fixture::new();
9865        sicily(&mut f);
9866        let cases: &[(&[&[u8]], &str)] = &[
9867            (
9868                &[b"GEORADIUS", b"Sicily", b"15", b"37", b"x", b"km"],
9869                "-ERR need numeric radius\r\n",
9870            ),
9871            (
9872                &[b"GEORADIUS", b"Sicily", b"15", b"37", b"-1", b"km"],
9873                "-ERR radius cannot be negative\r\n",
9874            ),
9875            (
9876                &[b"GEORADIUS", b"Sicily", b"15", b"37", b"1", b"parsecs"],
9877                "-ERR unsupported unit provided. please use M, KM, FT, MI\r\n",
9878            ),
9879            (
9880                &[b"GEORADIUS", b"Sicily", b"181", b"37", b"1", b"km"],
9881                "-ERR invalid longitude,latitude pair 181.000000,37.000000\r\n",
9882            ),
9883            (
9884                &[
9885                    b"GEOSEARCH",
9886                    b"Sicily",
9887                    b"FROMLONLAT",
9888                    b"15",
9889                    b"37",
9890                    b"BYBOX",
9891                    b"x",
9892                    b"1",
9893                    b"km",
9894                ],
9895                "-ERR need numeric width\r\n",
9896            ),
9897            (
9898                &[
9899                    b"GEOSEARCH",
9900                    b"Sicily",
9901                    b"FROMLONLAT",
9902                    b"15",
9903                    b"37",
9904                    b"BYBOX",
9905                    b"1",
9906                    b"y",
9907                    b"km",
9908                ],
9909                "-ERR need numeric height\r\n",
9910            ),
9911            (
9912                &[
9913                    b"GEOSEARCH",
9914                    b"Sicily",
9915                    b"FROMLONLAT",
9916                    b"15",
9917                    b"37",
9918                    b"BYBOX",
9919                    b"-1",
9920                    b"1",
9921                    b"km",
9922                ],
9923                "-ERR height or width cannot be negative\r\n",
9924            ),
9925            (
9926                &[
9927                    b"GEOSEARCH",
9928                    b"Sicily",
9929                    b"FROMLONLAT",
9930                    b"15",
9931                    b"37",
9932                    b"BYRADIUS",
9933                    b"1",
9934                    b"km",
9935                    b"ANY",
9936                ],
9937                "-ERR the ANY argument requires COUNT argument\r\n",
9938            ),
9939            (
9940                &[
9941                    b"GEOSEARCH",
9942                    b"Sicily",
9943                    b"FROMLONLAT",
9944                    b"15",
9945                    b"37",
9946                    b"BYRADIUS",
9947                    b"1",
9948                    b"km",
9949                    b"COUNT",
9950                    b"0",
9951                ],
9952                "-ERR COUNT must be > 0\r\n",
9953            ),
9954            (
9955                &[
9956                    b"GEOSEARCH",
9957                    b"Sicily",
9958                    b"BYRADIUS",
9959                    b"1",
9960                    b"km",
9961                    b"BYBOX",
9962                    b"1",
9963                    b"1",
9964                    b"km",
9965                ],
9966                "-ERR syntax error\r\n",
9967            ),
9968            (
9969                &[
9970                    b"GEOSEARCH",
9971                    b"Sicily",
9972                    b"FROMMEMBER",
9973                    b"Palermo",
9974                    b"FROMLONLAT",
9975                    b"1",
9976                    b"2",
9977                    b"BYRADIUS",
9978                    b"1",
9979                    b"km",
9980                ],
9981                "-ERR syntax error\r\n",
9982            ),
9983            // The two options a GEOSEARCH cannot leave out, each with its own
9984            // sentence, and the command quoted the way the client spelled it.
9985            (
9986                &[
9987                    b"geosearch",
9988                    b"Sicily",
9989                    b"BYRADIUS",
9990                    b"1",
9991                    b"km",
9992                    b"ASC",
9993                    b"WITHDIST",
9994                ],
9995                "-ERR exactly one of FROMMEMBER or FROMLONLAT can be specified for geosearch\r\n",
9996            ),
9997            (
9998                &[
9999                    b"GEOSEARCH",
10000                    b"Sicily",
10001                    b"FROMLONLAT",
10002                    b"15",
10003                    b"37",
10004                    b"ASC",
10005                    b"WITHDIST",
10006                ],
10007                "-ERR exactly one of BYRADIUS and BYBOX can be specified for GEOSEARCH\r\n",
10008            ),
10009            // A store cannot also be asked for the distance, and the two
10010            // families name themselves differently in the same sentence.
10011            (
10012                &[
10013                    b"GEOSEARCHSTORE",
10014                    b"d",
10015                    b"Sicily",
10016                    b"FROMLONLAT",
10017                    b"15",
10018                    b"37",
10019                    b"BYRADIUS",
10020                    b"1",
10021                    b"km",
10022                    b"WITHCOORD",
10023                ],
10024                "-ERR GEOSEARCHSTORE is not compatible with WITHDIST, WITHHASH and WITHCOORD options\r\n",
10025            ),
10026            (
10027                &[
10028                    b"GEORADIUS",
10029                    b"Sicily",
10030                    b"15",
10031                    b"37",
10032                    b"1",
10033                    b"km",
10034                    b"WITHDIST",
10035                    b"STORE",
10036                    b"d",
10037                ],
10038                "-ERR STORE option in GEORADIUS is not compatible with WITHDIST, WITHHASH and WITHCOORD options\r\n",
10039            ),
10040            // The read only forms have no store at all, so the word is a stray
10041            // one, and GEOSEARCH's STOREDIST is only a GEOSEARCHSTORE option.
10042            (
10043                &[
10044                    b"GEORADIUS_RO",
10045                    b"Sicily",
10046                    b"15",
10047                    b"37",
10048                    b"1",
10049                    b"km",
10050                    b"STORE",
10051                    b"d",
10052                ],
10053                "-ERR syntax error\r\n",
10054            ),
10055            (
10056                &[
10057                    b"GEOSEARCH",
10058                    b"Sicily",
10059                    b"FROMLONLAT",
10060                    b"15",
10061                    b"37",
10062                    b"BYRADIUS",
10063                    b"1",
10064                    b"km",
10065                    b"STOREDIST",
10066                ],
10067                "-ERR syntax error\r\n",
10068            ),
10069        ];
10070        for (parts, want) in cases {
10071            assert_eq!(&f.run(parts), want, "{:?}", parts[0]);
10072        }
10073    }
10074
10075    /// A wrong type wins over a bad argument, because the key is looked up
10076    /// first, and every one of the ten says the same thing about it.
10077    #[test]
10078    fn every_geo_command_says_wrongtype() {
10079        let mut f = Fixture::new();
10080        f.run(&[b"SET", b"s", b"v"]);
10081        let wrong = "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n";
10082        let cases: &[&[&[u8]]] = &[
10083            &[b"GEOADD", b"s", b"13", b"38", b"m"],
10084            &[b"GEOPOS", b"s", b"m"],
10085            &[b"GEOHASH", b"s", b"m"],
10086            &[b"GEODIST", b"s", b"a", b"b"],
10087            &[
10088                b"GEOSEARCH",
10089                b"s",
10090                b"FROMLONLAT",
10091                b"15",
10092                b"37",
10093                b"BYRADIUS",
10094                b"1",
10095                b"km",
10096            ],
10097            &[
10098                b"GEOSEARCHSTORE",
10099                b"d",
10100                b"s",
10101                b"FROMLONLAT",
10102                b"15",
10103                b"37",
10104                b"BYRADIUS",
10105                b"1",
10106                b"km",
10107            ],
10108            &[b"GEORADIUS", b"s", b"15", b"37", b"1", b"km"],
10109            &[b"GEORADIUS_RO", b"s", b"15", b"37", b"1", b"km"],
10110            &[b"GEORADIUSBYMEMBER", b"s", b"m", b"1", b"km"],
10111            &[b"GEORADIUSBYMEMBER_RO", b"s", b"m", b"1", b"km"],
10112        ];
10113        for case in cases {
10114            assert_eq!(f.run(case), wrong, "{:?}", case[0]);
10115        }
10116        // And it wins over an argument that will not parse, which is the whole
10117        // reason the lookup comes first.
10118        assert_eq!(
10119            f.run(&[b"GEORADIUS", b"s", b"15", b"37", b"x", b"km"]),
10120            wrong
10121        );
10122    }
10123
10124    // ----------------------------------------------------------------- array
10125
10126    #[test]
10127    fn an_array_writes_at_any_index_and_reads_back_what_it_sent() {
10128        let mut f = Fixture::new();
10129        // Three consecutive positions from a high index, and the reply is how
10130        // many of them were empty before rather than how many were written.
10131        assert_eq!(
10132            f.run(&[b"ARSET", b"a", b"1000", b"x", b"y", b"z"]),
10133            ":3\r\n"
10134        );
10135        assert_eq!(f.run(&[b"ARSET", b"a", b"1000", b"X", b"Y"]), ":0\r\n");
10136        assert_eq!(f.run(&[b"ARGET", b"a", b"1000"]), "$1\r\nX\r\n");
10137        assert_eq!(f.run(&[b"ARGET", b"a", b"1002"]), "$1\r\nz\r\n");
10138        // A hole and a key that is not there are the same answer.
10139        assert_eq!(f.run(&[b"ARGET", b"a", b"999"]), "$-1\r\n");
10140        assert_eq!(f.run(&[b"ARGET", b"nope", b"0"]), "$-1\r\n");
10141        assert_eq!(
10142            f.run(&[b"ARMGET", b"a", b"1002", b"999", b"1000"]),
10143            "*3\r\n$1\r\nz\r\n$-1\r\n$1\r\nX\r\n"
10144        );
10145        // Scattered pairs in one command, last write wins within it.
10146        assert_eq!(f.run(&[b"ARMSET", b"a", b"5", b"p", b"5", b"q"]), ":1\r\n");
10147        assert_eq!(f.run(&[b"ARGET", b"a", b"5"]), "$1\r\nq\r\n");
10148    }
10149
10150    /// The two numbers an array reports are not the same number, and one of
10151    /// them does not fit a signed integer.
10152    #[test]
10153    fn the_length_is_the_high_water_mark_and_the_count_is_the_population() {
10154        let mut f = Fixture::new();
10155        assert_eq!(f.run(&[b"ARLEN", b"nope"]), ":0\r\n");
10156        assert_eq!(f.run(&[b"ARCOUNT", b"nope"]), ":0\r\n");
10157        f.run(&[b"ARMSET", b"a", b"0", b"x", b"9", b"y"]);
10158        assert_eq!(f.run(&[b"ARLEN", b"a"]), ":10\r\n");
10159        assert_eq!(f.run(&[b"ARCOUNT", b"a"]), ":2\r\n");
10160        // Deleting in the middle leaves the high water mark where it was.
10161        assert_eq!(f.run(&[b"ARDEL", b"a", b"0"]), ":1\r\n");
10162        assert_eq!(f.run(&[b"ARLEN", b"a"]), ":10\r\n");
10163        assert_eq!(f.run(&[b"ARCOUNT", b"a"]), ":1\r\n");
10164
10165        // The top of the space is addressable, and its length is a number with
10166        // bit sixty three set, so the reply has to be unsigned or it comes back
10167        // negative.
10168        f.run(&[b"ARSET", b"top", b"18446744073709551614", b"z"]);
10169        assert_eq!(f.run(&[b"ARLEN", b"top"]), ":18446744073709551615\r\n");
10170        assert_eq!(f.run(&[b"ARCOUNT", b"top"]), ":1\r\n");
10171        // And one past it does not exist, so a write that would reach it fails
10172        // before any of it lands.
10173        assert_eq!(
10174            f.run(&[b"ARSET", b"over", b"18446744073709551614", b"a", b"b"]),
10175            "-ERR array index overflow\r\n"
10176        );
10177        assert_eq!(f.run(&[b"EXISTS", b"over"]), ":0\r\n");
10178    }
10179
10180    /// One reply per position and not one per element, which is the whole
10181    /// reason the range is capped.
10182    #[test]
10183    fn a_range_read_answers_for_the_holes_too_and_is_capped_at_a_million() {
10184        let mut f = Fixture::new();
10185        f.run(&[b"ARSET", b"a", b"1", b"x"]);
10186        assert_eq!(
10187            f.run(&[b"ARGETRANGE", b"a", b"0", b"3"]),
10188            "*4\r\n$-1\r\n$1\r\nx\r\n$-1\r\n$-1\r\n"
10189        );
10190        // The two ends may come in either order, and the answer is reversed
10191        // rather than empty.
10192        assert_eq!(
10193            f.run(&[b"ARGETRANGE", b"a", b"3", b"0"]),
10194            "*4\r\n$-1\r\n$-1\r\n$1\r\nx\r\n$-1\r\n"
10195        );
10196        // A key that is not there reads like an array of nothing but holes.
10197        assert_eq!(
10198            f.run(&[b"ARGETRANGE", b"nope", b"0", b"1"]),
10199            "*2\r\n$-1\r\n$-1\r\n"
10200        );
10201        // A range wider than a million positions is refused and not trimmed,
10202        // because against a missing key it is a request for as many nulls as
10203        // the range is wide.
10204        assert_eq!(
10205            f.run(&[b"ARGETRANGE", b"nope", b"0", b"18446744073709551614"]),
10206            "-ERR range exceeds maximum of 1000000 items\r\n"
10207        );
10208    }
10209
10210    /// Every index in the argument list is read before the key is touched, so
10211    /// a bad one at the end leaves nothing half written.
10212    #[test]
10213    fn a_bad_index_late_in_the_line_writes_none_of_the_earlier_ones() {
10214        let mut f = Fixture::new();
10215        assert_eq!(
10216            f.run(&[b"ARMSET", b"a", b"0", b"x", b"-1", b"y"]),
10217            "-ERR invalid array index\r\n"
10218        );
10219        assert_eq!(f.run(&[b"EXISTS", b"a"]), ":0\r\n");
10220        f.run(&[b"ARSET", b"a", b"0", b"x", b"y", b"z"]);
10221        assert_eq!(
10222            f.run(&[b"ARDEL", b"a", b"0", b"01"]),
10223            "-ERR invalid array index\r\n"
10224        );
10225        assert_eq!(f.run(&[b"ARCOUNT", b"a"]), ":3\r\n");
10226        // An index is unsigned here, so the numbers a list would take are not
10227        // the last element, they are errors.
10228        assert_eq!(
10229            f.run(&[b"ARGET", b"a", b"-1"]),
10230            "-ERR invalid array index\r\n"
10231        );
10232        // And a pair list with an odd tail is an arity error rather than a
10233        // syntax one.
10234        assert_eq!(
10235            f.run(&[b"ARMSET", b"a", b"0", b"x", b"1"]),
10236            "-ERR wrong number of arguments for 'armset' command\r\n"
10237        );
10238        assert_eq!(
10239            f.run(&[b"ARDELRANGE", b"a", b"0", b"1", b"2"]),
10240            "-ERR wrong number of arguments for 'ardelrange' command\r\n"
10241        );
10242    }
10243
10244    #[test]
10245    fn a_range_delete_costs_the_elements_and_takes_the_key_when_it_empties() {
10246        let mut f = Fixture::new();
10247        f.run(&[b"ARSET", b"a", b"0", b"0", b"1", b"2", b"3", b"4"]);
10248        assert_eq!(f.run(&[b"ARDELRANGE", b"a", b"3", b"1"]), ":3\r\n");
10249        assert_eq!(f.run(&[b"ARCOUNT", b"a"]), ":2\r\n");
10250        // Two ranges in one command, and the second one covers the whole space
10251        // without walking it.
10252        assert_eq!(
10253            f.run(&[
10254                b"ARDELRANGE",
10255                b"a",
10256                b"100",
10257                b"200",
10258                b"0",
10259                b"18446744073709551614"
10260            ]),
10261            ":2\r\n"
10262        );
10263        assert_eq!(f.run(&[b"EXISTS", b"a"]), ":0\r\n");
10264        assert_eq!(f.run(&[b"ARDELRANGE", b"nope", b"0", b"1"]), ":0\r\n");
10265        assert_eq!(f.run(&[b"ARDEL", b"nope", b"0"]), ":0\r\n");
10266    }
10267
10268    /// A value goes out as the bytes it came in as, whichever of the three ways
10269    /// the array found to store it.
10270    #[test]
10271    fn a_value_comes_back_byte_for_byte_however_it_was_packed() {
10272        let mut f = Fixture::new();
10273        let long = vec![b'v'; 200];
10274        f.run(&[
10275            b"ARMSET", b"a", b"0", b"42", b"1", b"007", b"2", b"3.5", b"3", b"3.14", b"4",
10276            b"short", b"5", &long, b"6", b"-0",
10277        ]);
10278        // 42 is an integer, 007 is not one because it does not print back the
10279        // same, 3.5 survives a double and 3.14 does not, and the last two are a
10280        // word packed string and a blob.
10281        assert_eq!(
10282            f.run(&[b"ARGETRANGE", b"a", b"0", b"6"]),
10283            format!(
10284                "*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",
10285                String::from_utf8_lossy(&long)
10286            )
10287        );
10288    }
10289
10290    #[test]
10291    fn an_array_is_a_type_and_an_encoding_a_client_can_see() {
10292        let mut f = Fixture::new();
10293        f.run(&[b"ARSET", b"a", b"0", b"x"]);
10294        assert_eq!(f.run(&[b"TYPE", b"a"]), "+array\r\n");
10295        assert_eq!(
10296            f.run(&[b"OBJECT", b"ENCODING", b"a"]),
10297            "$12\r\nsliced-array\r\n"
10298        );
10299        // And it is a body like any other, so the key commands work on it.
10300        assert_eq!(f.run(&[b"EXPIRE", b"a", b"100"]), ":1\r\n");
10301        assert_eq!(f.run(&[b"PERSIST", b"a"]), ":1\r\n");
10302        assert_eq!(f.run(&[b"COPY", b"a", b"b"]), ":1\r\n");
10303        assert_eq!(f.run(&[b"ARGET", b"b", b"0"]), "$1\r\nx\r\n");
10304        assert_eq!(f.run(&[b"RENAME", b"a", b"c"]), "+OK\r\n");
10305        assert_eq!(f.run(&[b"ARCOUNT", b"c"]), ":1\r\n");
10306    }
10307
10308    #[test]
10309    fn every_array_command_refuses_a_key_holding_something_else() {
10310        let mut f = Fixture::new();
10311        f.run(&[b"SET", b"s", b"v"]);
10312        let wrong = "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n";
10313        for cmd in [
10314            &[b"ARSET".as_ref(), b"s", b"0", b"x"][..],
10315            &[b"ARMSET".as_ref(), b"s", b"0", b"x"][..],
10316            &[b"ARGET".as_ref(), b"s", b"0"][..],
10317            &[b"ARMGET".as_ref(), b"s", b"0"][..],
10318            &[b"ARGETRANGE".as_ref(), b"s", b"0", b"1"][..],
10319            &[b"ARLEN".as_ref(), b"s"][..],
10320            &[b"ARCOUNT".as_ref(), b"s"][..],
10321            &[b"ARDEL".as_ref(), b"s", b"0"][..],
10322            &[b"ARDELRANGE".as_ref(), b"s", b"0", b"1"][..],
10323            &[b"ARINSERT".as_ref(), b"s", b"x"][..],
10324            &[b"ARRING".as_ref(), b"s", b"4", b"x"][..],
10325            &[b"ARNEXT".as_ref(), b"s"][..],
10326            &[b"ARSEEK".as_ref(), b"s", b"1"][..],
10327            &[b"ARLASTITEMS".as_ref(), b"s", b"1"][..],
10328            &[b"ARSCAN".as_ref(), b"s", b"0", b"1"][..],
10329            &[b"ARGREP".as_ref(), b"s", b"0", b"1", b"EXACT", b"v"][..],
10330            &[b"AROP".as_ref(), b"s", b"0", b"1", b"SUM"][..],
10331            &[b"ARINFO".as_ref(), b"s"][..],
10332        ] {
10333            assert_eq!(f.run(cmd), wrong, "{}", String::from_utf8_lossy(cmd[0]));
10334        }
10335    }
10336
10337    /// Two of the array commands look the key up before they read the index and
10338    /// the rest read the index first, so the same broken argument gets two
10339    /// different errors depending on which command it went to.
10340    #[test]
10341    fn a_bad_index_reports_the_type_only_where_redis_reports_it() {
10342        let mut f = Fixture::new();
10343        f.run(&[b"SET", b"s", b"v"]);
10344        let wrong = "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n";
10345        let bad = "-ERR invalid array index\r\n";
10346        assert_eq!(f.run(&[b"ARGET", b"s", b"-1"]), wrong);
10347        assert_eq!(f.run(&[b"ARMGET", b"s", b"0", b"-1"]), wrong);
10348        assert_eq!(f.run(&[b"ARSET", b"s", b"-1", b"x"]), bad);
10349        assert_eq!(f.run(&[b"ARDEL", b"s", b"-1"]), bad);
10350        assert_eq!(f.run(&[b"ARSCAN", b"s", b"-1", b"0"]), bad);
10351        assert_eq!(f.run(&[b"ARGREP", b"s", b"-1", b"0", b"EXACT", b"v"]), bad);
10352        // And on a key that is an array the index is just an index.
10353        f.run(&[b"ARSET", b"a", b"0", b"x"]);
10354        assert_eq!(f.run(&[b"ARGET", b"a", b"-1"]), bad);
10355        assert_eq!(f.run(&[b"ARGET", b"nope", b"-1"]), bad);
10356    }
10357
10358    #[test]
10359    fn an_append_follows_a_cursor_the_client_can_move() {
10360        let mut f = Fixture::new();
10361        assert_eq!(f.run(&[b"ARNEXT", b"nope"]), ":0\r\n");
10362        assert_eq!(f.run(&[b"ARINSERT", b"a", b"x", b"y"]), ":1\r\n");
10363        assert_eq!(f.run(&[b"ARNEXT", b"a"]), ":2\r\n");
10364        assert_eq!(f.run(&[b"ARINSERT", b"a", b"z"]), ":2\r\n");
10365        assert_eq!(f.run(&[b"ARGET", b"a", b"2"]), "$1\r\nz\r\n");
10366
10367        // A seek says where the next one goes, and a missing key has no cursor
10368        // to move and is not created by the asking.
10369        assert_eq!(f.run(&[b"ARSEEK", b"nope", b"5"]), ":0\r\n");
10370        assert_eq!(f.run(&[b"EXISTS", b"nope"]), ":0\r\n");
10371        assert_eq!(f.run(&[b"ARSEEK", b"a", b"100"]), ":1\r\n");
10372        assert_eq!(f.run(&[b"ARNEXT", b"a"]), ":100\r\n");
10373        assert_eq!(f.run(&[b"ARINSERT", b"a", b"far"]), ":100\r\n");
10374        assert_eq!(f.run(&[b"ARSEEK", b"a", b"0"]), ":1\r\n");
10375        assert_eq!(f.run(&[b"ARNEXT", b"a"]), ":0\r\n");
10376
10377        // The top of the space is the one index only ARSEEK will take, and it
10378        // leaves the cursor with nowhere to go.
10379        assert_eq!(f.run(&[b"ARSEEK", b"a", b"18446744073709551615"]), ":1\r\n");
10380        assert_eq!(f.run(&[b"ARNEXT", b"a"]), "$-1\r\n");
10381        assert_eq!(
10382            f.run(&[b"ARINSERT", b"a", b"x"]),
10383            "-ERR insert index overflow\r\n"
10384        );
10385        assert_eq!(
10386            f.run(&[b"ARSET", b"a", b"18446744073709551615", b"x"]),
10387            "-ERR invalid array index\r\n"
10388        );
10389    }
10390
10391    #[test]
10392    fn a_ring_keeps_the_newest_and_renumbers_them_when_it_is_resized() {
10393        let mut f = Fixture::new();
10394        assert_eq!(f.run(&[b"ARRING", b"r", b"3", b"a", b"b", b"c"]), ":2\r\n");
10395        assert_eq!(f.run(&[b"ARRING", b"r", b"3", b"d", b"e"]), ":1\r\n");
10396        assert_eq!(f.run(&[b"ARLEN", b"r"]), ":3\r\n");
10397        assert_eq!(
10398            f.run(&[b"ARGETRANGE", b"r", b"0", b"2"]),
10399            "*3\r\n$1\r\nd\r\n$1\r\ne\r\n$1\r\nc\r\n"
10400        );
10401        // Growing it after it has wrapped puts the survivors back in the order
10402        // they arrived, which is the whole point of paying for the rebuild.
10403        assert_eq!(f.run(&[b"ARRING", b"r", b"5", b"f"]), ":3\r\n");
10404        assert_eq!(
10405            f.run(&[b"ARGETRANGE", b"r", b"0", b"3"]),
10406            "*4\r\n$1\r\nc\r\n$1\r\nd\r\n$1\r\ne\r\n$1\r\nf\r\n"
10407        );
10408        // The size is read before the key, so a bad one is a bad size wherever
10409        // it is sent.
10410        assert_eq!(
10411            f.run(&[b"ARRING", b"r", b"0", b"x"]),
10412            "-ERR size must be positive\r\n"
10413        );
10414        assert_eq!(
10415            f.run(&[b"ARRING", b"r", b"big", b"x"]),
10416            "-ERR invalid size\r\n"
10417        );
10418    }
10419
10420    #[test]
10421    fn the_last_items_walk_back_from_the_cursor_and_report_the_holes() {
10422        let mut f = Fixture::new();
10423        assert_eq!(f.run(&[b"ARLASTITEMS", b"nope", b"5"]), "*0\r\n");
10424        f.run(&[b"ARRING", b"r", b"4", b"a", b"b", b"c", b"d", b"e"]);
10425        assert_eq!(
10426            f.run(&[b"ARLASTITEMS", b"r", b"3"]),
10427            "*3\r\n$1\r\nc\r\n$1\r\nd\r\n$1\r\ne\r\n"
10428        );
10429        assert_eq!(
10430            f.run(&[b"ARLASTITEMS", b"r", b"3", b"rev"]),
10431            "*3\r\n$1\r\ne\r\n$1\r\nd\r\n$1\r\nc\r\n"
10432        );
10433        assert_eq!(
10434            f.run(&[b"ARLASTITEMS", b"r", b"99"]),
10435            "*4\r\n$1\r\nb\r\n$1\r\nc\r\n$1\r\nd\r\n$1\r\ne\r\n",
10436            "more than there is gets what there is"
10437        );
10438        // Nothing asked for is an empty reply, and Redis answers that before it
10439        // has read the option or looked at the key.
10440        assert_eq!(f.run(&[b"ARLASTITEMS", b"r", b"0", b"junk"]), "*0\r\n");
10441        assert_eq!(
10442            f.run(&[b"ARLASTITEMS", b"r", b"1", b"junk"]),
10443            "-ERR syntax error\r\n"
10444        );
10445        assert_eq!(
10446            f.run(&[b"ARLASTITEMS", b"r", b"nine"]),
10447            "-ERR invalid COUNT\r\n"
10448        );
10449
10450        // With no cursor the tail of the array is the anchor, and a hole inside
10451        // the window is reported as one.
10452        f.run(&[b"ARMSET", b"h", b"0", b"x", b"2", b"z"]);
10453        assert_eq!(
10454            f.run(&[b"ARLASTITEMS", b"h", b"5"]),
10455            "*2\r\n$-1\r\n$1\r\nz\r\n"
10456        );
10457    }
10458
10459    #[test]
10460    fn a_scan_answers_pairs_for_what_is_there_and_skips_what_is_not() {
10461        let mut f = Fixture::new();
10462        assert_eq!(f.run(&[b"ARSCAN", b"nope", b"0", b"10"]), "*0\r\n");
10463        f.run(&[b"ARMSET", b"a", b"0", b"x", b"7", b"y", b"1000000", b"z"]);
10464        // The whole index space, which ARGETRANGE refuses and this one answers
10465        // in three visits because holes cost nothing.
10466        assert_eq!(
10467            f.run(&[b"ARSCAN", b"a", b"0", b"18446744073709551614"]),
10468            "*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"
10469        );
10470        assert_eq!(
10471            f.run(&[
10472                b"ARSCAN",
10473                b"a",
10474                b"18446744073709551614",
10475                b"0",
10476                b"LIMIT",
10477                b"1"
10478            ]),
10479            "*1\r\n*2\r\n:1000000\r\n$1\r\nz\r\n"
10480        );
10481        assert_eq!(f.run(&[b"ARSCAN", b"a", b"1", b"6"]), "*0\r\n");
10482        assert_eq!(
10483            f.run(&[b"ARSCAN", b"a", b"0", b"10", b"LIMIT", b"0"]),
10484            "-ERR LIMIT must be positive\r\n"
10485        );
10486        assert_eq!(
10487            f.run(&[b"ARSCAN", b"a", b"0", b"10", b"NOPE", b"1"]),
10488            "-ERR syntax error\r\n"
10489        );
10490        assert_eq!(
10491            f.run(&[b"ARSCAN", b"a", b"0", b"10", b"LIMIT"]),
10492            "-ERR wrong number of arguments for 'arscan' command\r\n"
10493        );
10494    }
10495
10496    #[test]
10497    fn a_grep_answers_the_indexes_whose_elements_match() {
10498        let mut f = Fixture::new();
10499        assert_eq!(
10500            f.run(&[b"ARGREP", b"nope", b"0", b"10", b"EXACT", b"x"]),
10501            "*0\r\n"
10502        );
10503        f.run(&[b"ARSET", b"a", b"0", b"alpha", b"beta", b"gamma", b"ALPHA"]);
10504
10505        // The two bounds take the ends of the array as well as an index, and a
10506        // reversed range is walked backwards the way ARSCAN walks one.
10507        assert_eq!(
10508            f.run(&[b"ARGREP", b"a", b"-", b"+", b"GLOB", b"*a"]),
10509            "*3\r\n:0\r\n:1\r\n:2\r\n"
10510        );
10511        assert_eq!(
10512            f.run(&[b"ARGREP", b"a", b"+", b"-", b"GLOB", b"*a"]),
10513            "*3\r\n:2\r\n:1\r\n:0\r\n"
10514        );
10515        assert_eq!(
10516            f.run(&[b"ARGREP", b"a", b"1", b"2", b"GLOB", b"*a"]),
10517            "*2\r\n:1\r\n:2\r\n"
10518        );
10519
10520        // One test each. NOCASE reaches all four of them and it may be written
10521        // after the pattern it applies to.
10522        assert_eq!(
10523            f.run(&[b"ARGREP", b"a", b"-", b"+", b"EXACT", b"alpha"]),
10524            "*1\r\n:0\r\n"
10525        );
10526        assert_eq!(
10527            f.run(&[b"ARGREP", b"a", b"-", b"+", b"EXACT", b"alpha", b"NOCASE"]),
10528            "*2\r\n:0\r\n:3\r\n"
10529        );
10530        assert_eq!(
10531            f.run(&[b"ARGREP", b"a", b"-", b"+", b"MATCH", b"mm"]),
10532            "*1\r\n:2\r\n"
10533        );
10534        assert_eq!(
10535            f.run(&[b"ARGREP", b"a", b"-", b"+", b"RE", b"^[bg]"]),
10536            "*2\r\n:1\r\n:2\r\n"
10537        );
10538
10539        // OR is the default and AND has to be asked for, and either way the
10540        // last of a repeated option wins.
10541        let both: &[&[u8]] = &[
10542            b"ARGREP", b"a", b"-", b"+", b"EXACT", b"beta", b"MATCH", b"al",
10543        ];
10544        assert_eq!(f.run(both), "*2\r\n:0\r\n:1\r\n");
10545        assert_eq!(
10546            f.run(&[
10547                b"ARGREP", b"a", b"-", b"+", b"EXACT", b"beta", b"MATCH", b"al", b"AND"
10548            ]),
10549            "*0\r\n"
10550        );
10551        assert_eq!(
10552            f.run(&[
10553                b"ARGREP", b"a", b"-", b"+", b"EXACT", b"beta", b"MATCH", b"al", b"AND", b"OR"
10554            ]),
10555            "*2\r\n:0\r\n:1\r\n"
10556        );
10557
10558        // WITHVALUES turns each hit into a pair, and LIMIT counts the hits and
10559        // not the positions it had to look at.
10560        assert_eq!(
10561            f.run(&[
10562                b"ARGREP",
10563                b"a",
10564                b"-",
10565                b"+",
10566                b"MATCH",
10567                b"a",
10568                b"WITHVALUES",
10569                b"LIMIT",
10570                b"2"
10571            ]),
10572            "*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"
10573        );
10574        assert_eq!(
10575            f.run(&[
10576                b"ARGREP", b"a", b"-", b"+", b"EXACT", b"ALPHA", b"LIMIT", b"1"
10577            ]),
10578            "*1\r\n:3\r\n"
10579        );
10580    }
10581
10582    /// Everything ARGREP refuses, in the order it refuses it.
10583    #[test]
10584    fn a_grep_reports_a_broken_command_the_way_redis_does() {
10585        let mut f = Fixture::new();
10586        f.run(&[b"ARSET", b"a", b"0", b"alpha"]);
10587        let syntax = "-ERR syntax error\r\n";
10588
10589        // The bounds are read before the plan, so a bad index beats a bad
10590        // predicate whichever way round the two are written.
10591        assert_eq!(
10592            f.run(&[b"ARGREP", b"a", b"-1", b"0", b"NOPE", b"x"]),
10593            "-ERR invalid array index\r\n"
10594        );
10595        assert_eq!(f.run(&[b"ARGREP", b"a", b"0", b"1", b"NOPE", b"x"]), syntax);
10596        // A keyword with nothing after it, and a command that asks for nothing.
10597        assert_eq!(
10598            f.run(&[b"ARGREP", b"a", b"0", b"1", b"NOCASE", b"EXACT"]),
10599            syntax
10600        );
10601        assert_eq!(
10602            f.run(&[b"ARGREP", b"a", b"0", b"1", b"EXACT", b"x", b"LIMIT"]),
10603            syntax
10604        );
10605        assert_eq!(
10606            f.run(&[b"ARGREP", b"a", b"0", b"1", b"NOCASE", b"WITHVALUES"]),
10607            syntax,
10608            "a command with no predicate in it at all"
10609        );
10610        assert_eq!(
10611            f.run(&[b"ARGREP", b"a", b"0", b"1", b"EXACT", b"x", b"LIMIT", b"0"]),
10612            "-ERR LIMIT must be positive\r\n"
10613        );
10614        assert_eq!(
10615            f.run(&[
10616                b"ARGREP", b"a", b"0", b"1", b"EXACT", b"x", b"LIMIT", b"nine"
10617            ]),
10618            "-ERR value is not an integer or out of range\r\n"
10619        );
10620        assert_eq!(
10621            f.run(&[b"ARGREP", b"a", b"0", b"1", b"RE", b""]),
10622            "-ERR regular expression is empty\r\n"
10623        );
10624        assert_eq!(
10625            f.run(&[b"ARGREP", b"a", b"0", b"1", b"RE", b"(a"]),
10626            "-ERR invalid regular expression: Missing ')'\r\n"
10627        );
10628        assert_eq!(
10629            f.run(&[b"ARGREP", b"a", b"0", b"1", b"RE", br"(a)\1"]),
10630            "-ERR regular expression backreferences are not supported\r\n"
10631        );
10632        // The arity is minus six, so a predicate keyword with no pattern after
10633        // it is short by one and never reaches the parser.
10634        let arity = "-ERR wrong number of arguments for 'argrep' command\r\n";
10635        assert_eq!(f.run(&[b"ARGREP", b"a", b"0", b"1", b"EXACT"]), arity);
10636        assert_eq!(f.run(&[b"ARGREP", b"a", b"0", b"1"]), arity);
10637    }
10638
10639    #[test]
10640    fn an_op_reduces_a_range_to_one_number() {
10641        let mut f = Fixture::new();
10642        f.run(&[b"ARSET", b"a", b"0", b"1", b"2.5", b"word", b"-4"]);
10643        assert_eq!(
10644            f.run(&[b"AROP", b"a", b"0", b"10", b"SUM"]),
10645            "$4\r\n-0.5\r\n"
10646        );
10647        assert_eq!(f.run(&[b"AROP", b"a", b"0", b"10", b"min"]), "$2\r\n-4\r\n");
10648        assert_eq!(
10649            f.run(&[b"AROP", b"a", b"0", b"10", b"MAX"]),
10650            "$3\r\n2.5\r\n"
10651        );
10652        assert_eq!(f.run(&[b"AROP", b"a", b"0", b"10", b"USED"]), ":4\r\n");
10653        assert_eq!(
10654            f.run(&[b"AROP", b"a", b"0", b"10", b"MATCH", b"word"]),
10655            ":1\r\n"
10656        );
10657        // An aggregate is written with seventeen significant digits, which is
10658        // Redis's own choice and not what a score comes back as.
10659        f.run(&[b"ARSET", b"t", b"0", b"0.1", b"0.2"]);
10660        assert_eq!(
10661            f.run(&[b"AROP", b"t", b"0", b"10", b"SUM"]),
10662            "$19\r\n0.30000000000000004\r\n"
10663        );
10664        assert_eq!(f.run(&[b"ZADD", b"z", b"0.3", b"m"]), ":1\r\n");
10665        assert_eq!(f.run(&[b"ZSCORE", b"z", b"m"]), "$3\r\n0.3\r\n");
10666
10667        // Nothing to work with is a null, and a missing key is a null for the
10668        // aggregates and a zero for the two that count.
10669        f.run(&[b"ARSET", b"w", b"0", b"word"]);
10670        assert_eq!(f.run(&[b"AROP", b"w", b"0", b"10", b"SUM"]), "$-1\r\n");
10671        assert_eq!(f.run(&[b"AROP", b"nope", b"0", b"10", b"SUM"]), "$-1\r\n");
10672        assert_eq!(f.run(&[b"AROP", b"nope", b"0", b"10", b"USED"]), ":0\r\n");
10673
10674        assert_eq!(
10675            f.run(&[b"AROP", b"a", b"0", b"10", b"NOPE"]),
10676            "-ERR unknown operation\r\n"
10677        );
10678        assert_eq!(
10679            f.run(&[b"AROP", b"a", b"0", b"10", b"MATCH"]),
10680            "-ERR MATCH requires a value argument\r\n"
10681        );
10682        assert_eq!(
10683            f.run(&[b"AROP", b"a", b"0", b"10", b"SUM", b"extra"]),
10684            "-ERR wrong number of arguments for 'arop' command\r\n"
10685        );
10686    }
10687
10688    #[test]
10689    fn the_info_is_a_map_and_a_missing_key_is_an_error() {
10690        let mut f = Fixture::new();
10691        assert_eq!(f.run(&[b"ARINFO", b"nope"]), "-ERR no such key\r\n");
10692        f.run(&[b"ARINSERT", b"a", b"x", b"y"]);
10693        let short = f.run(&[b"ARINFO", b"a"]);
10694        assert!(
10695            short.starts_with("*14\r\n"),
10696            "seven pairs on RESP2: {short}"
10697        );
10698        assert!(short.contains("$5\r\ncount\r\n:2\r\n"), "{short}");
10699        assert!(
10700            short.contains("$17\r\nnext-insert-index\r\n:2\r\n"),
10701            "{short}"
10702        );
10703        assert!(short.contains("$10\r\nslice-size\r\n:4096\r\n"), "{short}");
10704        let full = f.run(&[b"ARINFO", b"a", b"full"]);
10705        assert!(full.starts_with("*24\r\n"), "twelve pairs: {full}");
10706        // Two values one apart are held sparsely, so the dense count is zero and
10707        // the two dense averages have nothing to average.
10708        assert!(full.contains("$12\r\ndense-slices\r\n:0\r\n"), "{full}");
10709        assert!(full.contains("$13\r\nsparse-slices\r\n:1\r\n"), "{full}");
10710        assert!(
10711            full.contains("$14\r\navg-dense-size\r\n$1\r\n0\r\n"),
10712            "{full}"
10713        );
10714        assert_eq!(f.run(&[b"ARINFO", b"a", b"nope"]), "-ERR syntax error\r\n");
10715
10716        // On RESP3 the same reply is a map and the averages are doubles.
10717        let mut g = Fixture::new();
10718        g.run(&[b"HELLO", b"3"]);
10719        g.run(&[b"ARINSERT", b"a", b"x"]);
10720        let map = g.run(&[b"ARINFO", b"a", b"FULL"]);
10721        assert!(map.starts_with("%12\r\n"), "{map}");
10722        assert!(map.contains("$5\r\ncount\r\n:1\r\n"), "{map}");
10723        assert!(map.contains("$14\r\navg-dense-size\r\n,0\r\n"), "{map}");
10724    }
10725
10726    #[test]
10727    fn a_double_on_the_wire_is_written_the_way_redis_writes_one() {
10728        let mut f = Fixture::new();
10729        // Whole numbers up to two to the sixty second come back as integers,
10730        // and past that the digit generator takes over and uses an exponent.
10731        for (score, want) in [
10732            ("3", "3"),
10733            ("3.5", "3.5"),
10734            ("0.3", "0.3"),
10735            ("1e30", "1e+30"),
10736            ("1e19", "1e+19"),
10737            ("1e-7", "1e-7"),
10738            ("0.000001", "0.000001"),
10739            ("4611686018427387904", "4611686018427387904"),
10740            ("-0", "-0"),
10741        ] {
10742            f.run(&[b"ZADD", b"z", score.as_bytes(), b"m"]);
10743            assert_eq!(
10744                f.run(&[b"ZSCORE", b"z", b"m"]),
10745                format!("${}\r\n{want}\r\n", want.len()),
10746                "score {score}"
10747            );
10748        }
10749
10750        // The same bytes on RESP3, where the reply is a double rather than a
10751        // bulk string.
10752        let mut g = Fixture::new();
10753        g.run(&[b"HELLO", b"3"]);
10754        g.run(&[b"ZADD", b"z", b"1e30", b"m"]);
10755        assert_eq!(g.run(&[b"ZSCORE", b"z", b"m"]), ",1e+30\r\n");
10756        // The two float increments are not this printer. They go through
10757        // ld2string in its human mode, which is a fixed point conversion with
10758        // the trailing zeros taken off, so they never write an exponent, and
10759        // they reply with a bulk string on both protocols.
10760        assert_eq!(
10761            g.run(&[b"INCRBYFLOAT", b"s", b"1e30"]),
10762            "$31\r\n1000000000000000000000000000000\r\n"
10763        );
10764        assert_eq!(g.run(&[b"INCRBYFLOAT", b"t", b"0.1"]), "$3\r\n0.1\r\n");
10765        assert_eq!(
10766            g.run(&[b"HINCRBYFLOAT", b"h", b"f", b"1e19"]),
10767            "$20\r\n10000000000000000000\r\n"
10768        );
10769    }
10770
10771    // ----------------------------------------------------------------- graph
10772
10773    #[test]
10774    fn a_node_comes_back_with_the_fields_it_went_in_with() {
10775        let mut f = Fixture::new();
10776        assert_eq!(
10777            f.run(&[
10778                b"G.NADD", b"social", b"ada", b"name", b"Ada", b"born", b"1815"
10779            ]),
10780            ":1\r\n"
10781        );
10782        // The year comes back as the four bytes that were sent and not as a
10783        // number, because every property is text and there is nothing on the
10784        // wire that says which of `1815` and `"1815"` the client meant. The
10785        // fields are in the document's order, which is sorted by name, because
10786        // that is what makes a field lookup a binary search.
10787        assert_eq!(
10788            f.run(&[b"G.NGET", b"social", b"ada"]),
10789            "*4\r\n$4\r\nborn\r\n$4\r\n1815\r\n$4\r\nname\r\n$3\r\nAda\r\n"
10790        );
10791        // A second write to the same id replaces the document and says so with
10792        // a zero, so an ingest can count what it created.
10793        assert_eq!(
10794            f.run(&[b"G.NADD", b"social", b"ada", b"name", b"Ada Lovelace"]),
10795            ":0\r\n"
10796        );
10797        assert_eq!(
10798            f.run(&[b"G.NGET", b"social", b"ada"]),
10799            "*2\r\n$4\r\nname\r\n$12\r\nAda Lovelace\r\n"
10800        );
10801        // A node with no properties is an empty map and not a null, which is
10802        // how a client tells an isolated node from one that is not there.
10803        assert_eq!(f.run(&[b"G.NADD", b"social", b"grace"]), ":1\r\n");
10804        assert_eq!(f.run(&[b"G.NGET", b"social", b"grace"]), "*0\r\n");
10805        assert_eq!(f.run(&[b"G.NGET", b"social", b"nobody"]), "$-1\r\n");
10806        assert_eq!(f.run(&[b"G.NGET", b"nokey", b"ada"]), "$-1\r\n");
10807
10808        // A field with no value creates nothing, because the pairs are checked
10809        // before the key is touched.
10810        assert_eq!(
10811            f.run(&[b"G.NADD", b"fresh", b"n", b"lonely"]),
10812            "-ERR syntax error\r\n"
10813        );
10814        assert_eq!(f.run(&[b"EXISTS", b"fresh"]), ":0\r\n");
10815
10816        // On RESP3 the same reply is a map.
10817        let mut g = Fixture::new();
10818        g.run(&[b"HELLO", b"3"]);
10819        g.run(&[b"G.NADD", b"social", b"ada", b"name", b"Ada"]);
10820        assert_eq!(
10821            g.run(&[b"G.NGET", b"social", b"ada"]),
10822            "%1\r\n$4\r\nname\r\n$3\r\nAda\r\n"
10823        );
10824    }
10825
10826    #[test]
10827    fn an_edge_creates_the_ends_it_needs() {
10828        let mut f = Fixture::new();
10829        assert_eq!(
10830            f.run(&[
10831                b"G.EADD", b"social", b"ada", b"grace", b"FOLLOWS", b"since", b"1843"
10832            ]),
10833            ":1\r\n"
10834        );
10835        // Neither end was written first and both are there, as empty nodes.
10836        assert_eq!(f.run(&[b"G.NGET", b"social", b"ada"]), "*0\r\n");
10837        assert_eq!(f.run(&[b"G.NGET", b"social", b"grace"]), "*0\r\n");
10838        assert_eq!(
10839            f.run(&[b"G.OUT", b"social", b"ada", b"FOLLOWS"]),
10840            "*2\r\n$1\r\n0\r\n*1\r\n$5\r\ngrace\r\n"
10841        );
10842        assert_eq!(
10843            f.run(&[b"G.IN", b"social", b"grace", b"FOLLOWS"]),
10844            "*2\r\n$1\r\n0\r\n*1\r\n$3\r\nada\r\n"
10845        );
10846        // The same pair under the same label again updates the edge rather than
10847        // making a second one.
10848        assert_eq!(
10849            f.run(&[
10850                b"G.EADD", b"social", b"ada", b"grace", b"FOLLOWS", b"since", b"1844"
10851            ]),
10852            ":0\r\n"
10853        );
10854        assert_eq!(f.run(&[b"G.DEG", b"social", b"ada", b"FOLLOWS"]), ":1\r\n");
10855        // A different label between the same pair is a different edge.
10856        assert_eq!(
10857            f.run(&[b"G.EADD", b"social", b"ada", b"grace", b"WORKS_WITH"]),
10858            ":1\r\n"
10859        );
10860        assert_eq!(
10861            f.run(&[b"G.DEG", b"social", b"ada", b"WORKS_WITH"]),
10862            ":1\r\n"
10863        );
10864
10865        assert_eq!(
10866            f.run(&[b"G.EDEL", b"social", b"ada", b"grace", b"FOLLOWS"]),
10867            ":1\r\n"
10868        );
10869        assert_eq!(
10870            f.run(&[b"G.EDEL", b"social", b"ada", b"grace", b"FOLLOWS"]),
10871            ":0\r\n"
10872        );
10873        // A label nothing has used, an end that is not there, and a key that is
10874        // not there are all a zero rather than an error.
10875        assert_eq!(
10876            f.run(&[b"G.EDEL", b"social", b"ada", b"grace", b"NEVER"]),
10877            ":0\r\n"
10878        );
10879        assert_eq!(
10880            f.run(&[b"G.EDEL", b"social", b"ada", b"nobody", b"FOLLOWS"]),
10881            ":0\r\n"
10882        );
10883        assert_eq!(
10884            f.run(&[b"G.EDEL", b"nokey", b"ada", b"grace", b"FOLLOWS"]),
10885            ":0\r\n"
10886        );
10887    }
10888
10889    /// A run is paged the way `SCAN` is paged, so a client that can walk one
10890    /// can walk the other.
10891    #[test]
10892    fn a_hop_answers_a_cursor_and_a_page() {
10893        let mut f = Fixture::new();
10894        for i in 0..25u32 {
10895            let dst = format!("n{i}");
10896            f.run(&[b"G.EADD", b"social", b"hub", dst.as_bytes(), b"FOLLOWS"]);
10897        }
10898        // Ten without being asked, and the cursor is where to carry on from.
10899        let first = f.run(&[b"G.OUT", b"social", b"hub", b"FOLLOWS"]);
10900        assert!(first.starts_with("*2\r\n$2\r\n10\r\n*10\r\n"), "{first}");
10901
10902        let mut seen = 0;
10903        let mut cursor = String::from("0");
10904        loop {
10905            let page = f.run(&[
10906                b"G.OUT",
10907                b"social",
10908                b"hub",
10909                b"FOLLOWS",
10910                b"COUNT",
10911                b"7",
10912                b"CURSOR",
10913                cursor.as_bytes(),
10914            ]);
10915            let (head, rest) = page.split_once("\r\n*").expect("a cursor and a page");
10916            cursor = head
10917                .rsplit("\r\n")
10918                .next()
10919                .expect("the cursor line")
10920                .to_string();
10921            seen += rest
10922                .split_once("\r\n")
10923                .expect("the page length")
10924                .0
10925                .parse::<usize>()
10926                .expect("a length");
10927            if cursor == "0" {
10928                break;
10929            }
10930        }
10931        assert_eq!(seen, 25, "every neighbour once across the pages");
10932
10933        // A cursor past the end is an empty page and not an error, and so is a
10934        // key or a label that is not there.
10935        assert_eq!(
10936            f.run(&[b"G.OUT", b"social", b"hub", b"FOLLOWS", b"CURSOR", b"900"]),
10937            "*2\r\n$1\r\n0\r\n*0\r\n"
10938        );
10939        assert_eq!(
10940            f.run(&[b"G.OUT", b"social", b"hub", b"NEVER"]),
10941            "*2\r\n$1\r\n0\r\n*0\r\n"
10942        );
10943        assert_eq!(
10944            f.run(&[b"G.OUT", b"nokey", b"hub", b"FOLLOWS"]),
10945            "*2\r\n$1\r\n0\r\n*0\r\n"
10946        );
10947        assert_eq!(
10948            f.run(&[b"G.OUT", b"social", b"hub", b"FOLLOWS", b"COUNT", b"0"]),
10949            "-ERR COUNT must be a positive integer\r\n"
10950        );
10951        assert_eq!(
10952            f.run(&[b"G.OUT", b"social", b"hub", b"FOLLOWS", b"NOPE", b"1"]),
10953            "-ERR syntax error\r\n"
10954        );
10955    }
10956
10957    #[test]
10958    fn a_degree_counts_one_way_or_both() {
10959        let mut f = Fixture::new();
10960        f.run(&[b"G.EADD", b"social", b"a", b"b", b"F"]);
10961        f.run(&[b"G.EADD", b"social", b"a", b"c", b"F"]);
10962        f.run(&[b"G.EADD", b"social", b"d", b"a", b"F"]);
10963        assert_eq!(f.run(&[b"G.DEG", b"social", b"a", b"F"]), ":2\r\n");
10964        assert_eq!(f.run(&[b"G.DEG", b"social", b"a", b"F", b"OUT"]), ":2\r\n");
10965        assert_eq!(f.run(&[b"G.DEG", b"social", b"a", b"F", b"IN"]), ":1\r\n");
10966        assert_eq!(f.run(&[b"G.DEG", b"social", b"a", b"F", b"BOTH"]), ":3\r\n");
10967        assert_eq!(f.run(&[b"G.DEG", b"social", b"nobody", b"F"]), ":0\r\n");
10968        assert_eq!(f.run(&[b"G.DEG", b"social", b"a", b"NEVER"]), ":0\r\n");
10969        assert_eq!(f.run(&[b"G.DEG", b"nokey", b"a", b"F"]), ":0\r\n");
10970        assert_eq!(
10971            f.run(&[b"G.DEG", b"social", b"a", b"F", b"SIDEWAYS"]),
10972            "-ERR syntax error\r\n"
10973        );
10974    }
10975
10976    /// A walk answers which nodes it can reach and not by how many routes, so a
10977    /// node two ways out is in the frontier once.
10978    #[test]
10979    fn a_walk_reaches_each_node_once_however_many_ways_there_are() {
10980        let mut f = Fixture::new();
10981        for (src, dst) in [
10982            ("ada", "grace"),
10983            ("ada", "alan"),
10984            ("grace", "edsger"),
10985            ("alan", "edsger"),
10986            ("edsger", "barbara"),
10987        ] {
10988            f.run(&[b"G.EADD", b"social", src.as_bytes(), dst.as_bytes(), b"F"]);
10989        }
10990        // Two hops without being asked, the start left out, and edsger once
10991        // even though both of the first hop's nodes point at it.
10992        assert_eq!(
10993            f.run(&[b"G.NEIGH", b"social", b"ada", b"F"]),
10994            "*3\r\n$5\r\ngrace\r\n$4\r\nalan\r\n$6\r\nedsger\r\n"
10995        );
10996        assert_eq!(
10997            f.run(&[b"G.NEIGH", b"social", b"ada", b"F", b"DEPTH", b"1"]),
10998            "*2\r\n$5\r\ngrace\r\n$4\r\nalan\r\n"
10999        );
11000        let deep = f.run(&[b"G.NEIGH", b"social", b"ada", b"F", b"DEPTH", b"9"]);
11001        assert!(deep.starts_with("*4\r\n"), "the whole component: {deep}");
11002        assert!(deep.contains("$7\r\nbarbara\r\n"), "{deep}");
11003        // COUNT stops the walk rather than trimming what it found.
11004        assert_eq!(
11005            f.run(&[b"G.NEIGH", b"social", b"ada", b"F", b"COUNT", b"1"]),
11006            "*1\r\n$5\r\ngrace\r\n"
11007        );
11008        // A node nothing leaves is an empty array and not an error.
11009        assert_eq!(f.run(&[b"G.NEIGH", b"social", b"barbara", b"F"]), "*0\r\n");
11010        assert_eq!(f.run(&[b"G.NEIGH", b"social", b"ada", b"NEVER"]), "*0\r\n");
11011        assert_eq!(f.run(&[b"G.NEIGH", b"nokey", b"ada", b"F"]), "*0\r\n");
11012        assert_eq!(
11013            f.run(&[b"G.NEIGH", b"social", b"ada", b"F", b"DEPTH", b"0"]),
11014            "-ERR DEPTH must be a positive integer\r\n"
11015        );
11016        assert_eq!(
11017            f.run(&[b"G.NEIGH", b"social", b"ada", b"F", b"NOPE", b"1"]),
11018            "-ERR syntax error\r\n"
11019        );
11020    }
11021
11022    /// The two sided search, which is the whole reason `G.PATH` is a command
11023    /// and not something a client builds out of `G.OUT`.
11024    #[test]
11025    fn a_path_is_the_shortest_one_and_goes_over_any_label() {
11026        let mut f = Fixture::new();
11027        // A chain of six, and a shortcut that makes a shorter way round under a
11028        // second label so the search has to take either kind of hop.
11029        for i in 0..6u32 {
11030            let src = format!("n{i}");
11031            let dst = format!("n{}", i + 1);
11032            f.run(&[b"G.EADD", b"road", src.as_bytes(), dst.as_bytes(), b"STEP"]);
11033        }
11034        assert_eq!(
11035            f.run(&[b"G.PATH", b"road", b"n0", b"n6"]),
11036            "*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"
11037        );
11038        f.run(&[b"G.EADD", b"road", b"n0", b"n5", b"JUMP"]);
11039        assert_eq!(
11040            f.run(&[b"G.PATH", b"road", b"n0", b"n6"]),
11041            "*3\r\n$2\r\nn0\r\n$2\r\nn5\r\n$2\r\nn6\r\n"
11042        );
11043        // A node to itself is a path of one, and a depth too short to reach is
11044        // no path at all.
11045        assert_eq!(
11046            f.run(&[b"G.PATH", b"road", b"n2", b"n2"]),
11047            "*1\r\n$2\r\nn2\r\n"
11048        );
11049        assert_eq!(
11050            f.run(&[b"G.PATH", b"road", b"n0", b"n6", b"MAXDEPTH", b"1"]),
11051            "*0\r\n"
11052        );
11053        // Direction counts: the chain only goes one way.
11054        assert_eq!(f.run(&[b"G.PATH", b"road", b"n6", b"n0"]), "*0\r\n");
11055        // An unreachable node, a node that is not there, and a key that is not
11056        // there are the same empty answer.
11057        f.run(&[b"G.NADD", b"road", b"island"]);
11058        assert_eq!(f.run(&[b"G.PATH", b"road", b"n0", b"island"]), "*0\r\n");
11059        assert_eq!(f.run(&[b"G.PATH", b"road", b"n0", b"nobody"]), "*0\r\n");
11060        assert_eq!(f.run(&[b"G.PATH", b"nokey", b"n0", b"n6"]), "*0\r\n");
11061        assert_eq!(
11062            f.run(&[b"G.PATH", b"road", b"n0", b"n6", b"NOPE", b"3"]),
11063            "-ERR syntax error\r\n"
11064        );
11065    }
11066
11067    /// The point of the escape in the record tag: the keyspace owns a graph key
11068    /// the way it owns every other key, and none of these commands know a graph
11069    /// exists.
11070    #[test]
11071    fn the_keyspace_sees_a_graph_key_like_any_other() {
11072        let mut f = Fixture::new();
11073        f.run(&[b"G.EADD", b"social", b"ada", b"grace", b"F"]);
11074        assert_eq!(f.run(&[b"TYPE", b"social"]), "+graph\r\n");
11075        assert_eq!(
11076            f.run(&[b"OBJECT", b"ENCODING", b"social"]),
11077            "$9\r\nadjacency\r\n"
11078        );
11079        assert_eq!(f.run(&[b"EXISTS", b"social"]), ":1\r\n");
11080        assert_eq!(f.run(&[b"DBSIZE"]), ":1\r\n");
11081        assert_eq!(f.run(&[b"KEYS", b"*"]), "*1\r\n$6\r\nsocial\r\n");
11082        // A graph is counted against the server the way every other body is,
11083        // which is what `maxmemory` will read when this key is a million nodes.
11084        // There is no `MEMORY USAGE` command yet, so this asks the server.
11085        let held = f.server.memory_bytes();
11086        for i in 0..200u32 {
11087            let dst = format!("n{i}");
11088            f.run(&[b"G.EADD", b"big", b"hub", dst.as_bytes(), b"F"]);
11089        }
11090        assert!(
11091            f.server.memory_bytes() > held,
11092            "two hundred edges cost something: {held} then {}",
11093            f.server.memory_bytes()
11094        );
11095        f.run(&[b"DEL", b"big"]);
11096
11097        // An expiry, then a rename, then a move to another database, all of
11098        // which are the keyspace moving a record it cannot look inside.
11099        assert_eq!(f.run(&[b"EXPIRE", b"social", b"100"]), ":1\r\n");
11100        assert_eq!(f.run(&[b"PERSIST", b"social"]), ":1\r\n");
11101        assert_eq!(f.run(&[b"RENAME", b"social", b"net"]), "+OK\r\n");
11102        assert_eq!(f.run(&[b"MOVE", b"net", b"1"]), ":1\r\n");
11103        assert_eq!(f.run(&[b"EXISTS", b"net"]), ":0\r\n");
11104        f.run(&[b"SELECT", b"1"]);
11105        assert_eq!(f.run(&[b"G.DEG", b"net", b"ada", b"F"]), ":1\r\n");
11106
11107        assert_eq!(f.run(&[b"DEL", b"net"]), ":1\r\n");
11108        assert_eq!(f.run(&[b"DBSIZE"]), ":0\r\n");
11109        f.run(&[b"G.NADD", b"g", b"n"]);
11110        assert_eq!(f.run(&[b"FLUSHDB"]), "+OK\r\n");
11111        assert_eq!(f.run(&[b"DBSIZE"]), ":0\r\n");
11112    }
11113
11114    /// Neither `COPY` nor `DUMP` has a byte shape for a graph, so both say so
11115    /// rather than answering the way they answer for a key that is not there.
11116    #[test]
11117    fn a_graph_cannot_be_copied_or_dumped() {
11118        let mut f = Fixture::new();
11119        f.run(&[b"G.NADD", b"social", b"ada"]);
11120        assert_eq!(
11121            f.run(&[b"COPY", b"social", b"other"]),
11122            "-ERR COPY is not supported for a graph\r\n"
11123        );
11124        assert_eq!(
11125            f.run(&[b"COPY", b"social", b"other", b"DB", b"1"]),
11126            "-ERR COPY is not supported for a graph\r\n"
11127        );
11128        assert_eq!(
11129            f.run(&[b"DUMP", b"social"]),
11130            "-ERR DUMP is not supported for a graph\r\n"
11131        );
11132        // A refused copy leaves both keys exactly as they were.
11133        assert_eq!(f.run(&[b"EXISTS", b"social", b"other"]), ":1\r\n");
11134    }
11135
11136    /// A graph key is a key, so the commands for the other types refuse it and
11137    /// the graph commands refuse theirs.
11138    #[test]
11139    fn a_graph_and_a_string_are_the_wrong_type_for_each_other() {
11140        let wrong = "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n";
11141        let mut f = Fixture::new();
11142        f.run(&[b"G.NADD", b"social", b"ada"]);
11143        assert_eq!(f.run(&[b"GET", b"social"]), wrong);
11144        assert_eq!(f.run(&[b"LPUSH", b"social", b"x"]), wrong);
11145        assert_eq!(f.run(&[b"SADD", b"social", b"x"]), wrong);
11146
11147        f.run(&[b"SET", b"str", b"v"]);
11148        for cmd in [
11149            vec![b"G.NADD".as_ref(), b"str", b"n"],
11150            vec![b"G.NGET".as_ref(), b"str", b"n"],
11151            vec![b"G.NDEL".as_ref(), b"str", b"n"],
11152            vec![b"G.EADD".as_ref(), b"str", b"a", b"b", b"F"],
11153            vec![b"G.EDEL".as_ref(), b"str", b"a", b"b", b"F"],
11154            vec![b"G.OUT".as_ref(), b"str", b"a", b"F"],
11155            vec![b"G.IN".as_ref(), b"str", b"a", b"F"],
11156            vec![b"G.DEG".as_ref(), b"str", b"a", b"F"],
11157            vec![b"G.NEIGH".as_ref(), b"str", b"a", b"F"],
11158            vec![b"G.PATH".as_ref(), b"str", b"a", b"b"],
11159        ] {
11160            assert_eq!(f.run(&cmd), wrong, "{:?}", cmd[0]);
11161        }
11162    }
11163
11164    /// Every other collection here takes its key with it when its last member
11165    /// goes, and a graph is no different.
11166    #[test]
11167    fn a_graph_goes_when_its_last_node_does() {
11168        let mut f = Fixture::new();
11169        f.run(&[
11170            b"G.EADD", b"social", b"ada", b"grace", b"F", b"since", b"1843",
11171        ]);
11172        assert_eq!(f.run(&[b"G.NDEL", b"social", b"ada"]), ":1\r\n");
11173        // The node and the edges that hung off it are both gone.
11174        assert_eq!(f.run(&[b"G.NGET", b"social", b"ada"]), "$-1\r\n");
11175        assert_eq!(
11176            f.run(&[b"G.DEG", b"social", b"grace", b"F", b"IN"]),
11177            ":0\r\n"
11178        );
11179        assert_eq!(f.run(&[b"G.NDEL", b"social", b"ada"]), ":0\r\n");
11180        assert_eq!(f.run(&[b"EXISTS", b"social"]), ":1\r\n");
11181
11182        assert_eq!(f.run(&[b"G.NDEL", b"social", b"grace"]), ":1\r\n");
11183        assert_eq!(f.run(&[b"EXISTS", b"social"]), ":0\r\n");
11184        assert_eq!(f.run(&[b"DBSIZE"]), ":0\r\n");
11185        assert_eq!(f.run(&[b"G.NDEL", b"nokey", b"ada"]), ":0\r\n");
11186
11187        // The id the removed node had is not handed out again, so a client
11188        // holding an id from an earlier reply cannot have it mean another node.
11189        f.run(&[b"G.NADD", b"social", b"first"]);
11190        f.run(&[b"G.NADD", b"social", b"second"]);
11191        f.run(&[b"G.NDEL", b"social", b"first"]);
11192        f.run(&[b"G.EADD", b"social", b"third", b"second", b"F"]);
11193        assert_eq!(
11194            f.run(&[b"G.OUT", b"social", b"third", b"F"]),
11195            "*2\r\n$1\r\n0\r\n*1\r\n$6\r\nsecond\r\n"
11196        );
11197    }
11198
11199    // ------------------------------------------------------------------ json
11200
11201    /// The two path syntaxes answer different shapes, which is the thing a
11202    /// client is most likely to be broken by and so the thing to pin first.
11203    #[test]
11204    fn a_json_path_answers_a_set_and_a_legacy_path_answers_a_value() {
11205        let mut f = Fixture::new();
11206        let doc = br#"{"a":1,"b":{"c":true}}"#;
11207        assert_eq!(f.run(&[b"JSON.SET", b"doc", b"$", doc]), "+OK\r\n");
11208        // No path at all is the legacy root and not `$`, so the document comes
11209        // back as itself rather than wrapped.
11210        assert_eq!(
11211            f.run(&[b"JSON.GET", b"doc"]),
11212            bulk(r#"{"a":1,"b":{"c":true}}"#)
11213        );
11214        assert_eq!(f.run(&[b"JSON.GET", b"doc", b"$.a"]), bulk("[1]"));
11215        assert_eq!(f.run(&[b"JSON.GET", b"doc", b".a"]), bulk("1"));
11216        assert_eq!(f.run(&[b"JSON.GET", b"doc", b"$..c"]), bulk("[true]"));
11217        // A path that matched nothing is an empty set on one syntax and an
11218        // error on the other, and the error does not quote the path.
11219        assert_eq!(f.run(&[b"JSON.GET", b"doc", b"$.nope"]), bulk("[]"));
11220        assert_eq!(
11221            f.run(&[b"JSON.GET", b"doc", b".nope"]),
11222            "-ERR Path does not exist\r\n"
11223        );
11224        assert_eq!(f.run(&[b"JSON.GET", b"nokey"]), "$-1\r\n");
11225        // The key is a document to the rest of the keyspace, under the name
11226        // RedisJSON registers, and every generic command works on it.
11227        assert_eq!(f.run(&[b"TYPE", b"doc"]), "+ReJSON-RL\r\n");
11228        assert_eq!(f.run(&[b"EXISTS", b"doc"]), ":1\r\n");
11229        assert_eq!(f.run(&[b"OBJECT", b"ENCODING", b"doc"]), bulk("raw"));
11230        assert_eq!(f.run(&[b"DEL", b"doc"]), ":1\r\n");
11231        assert_eq!(f.run(&[b"JSON.GET", b"doc"]), "$-1\r\n");
11232    }
11233
11234    /// The two error lines RedisJSON sends without a prefix in front of them.
11235    ///
11236    /// Every other error this server writes starts `ERR` or `WRONGTYPE`. These
11237    /// two do not, on a real server, and a differential harness compares the
11238    /// whole line.
11239    #[test]
11240    fn the_two_json_errors_that_carry_no_prefix() {
11241        let mut f = Fixture::new();
11242        f.run(&[b"SET", b"plain", b"x"]);
11243        let wrong = "-Existing key has wrong Redis type\r\n";
11244        assert_eq!(f.run(&[b"JSON.GET", b"plain"]), wrong);
11245        assert_eq!(f.run(&[b"JSON.SET", b"plain", b"$", b"1"]), wrong);
11246        assert_eq!(f.run(&[b"JSON.DEL", b"plain"]), wrong);
11247        assert_eq!(f.run(&[b"JSON.TYPE", b"plain"]), wrong);
11248        assert_eq!(f.run(&[b"JSON.CLEAR", b"plain"]), wrong);
11249
11250        f.run(&[b"JSON.SET", b"doc", b"$", br#"{"a":{"z":1},"b":{"z":2}}"#]);
11251        // A wildcard that matched something writes to all of it. A wildcard
11252        // that matched nothing would have to invent a place, and that is the
11253        // other unprefixed line.
11254        assert_eq!(f.run(&[b"JSON.SET", b"doc", b"$.*.z", b"9"]), "+OK\r\n");
11255        assert_eq!(
11256            f.run(&[b"JSON.GET", b"doc"]),
11257            bulk(r#"{"a":{"z":9},"b":{"z":9}}"#)
11258        );
11259        assert_eq!(
11260            f.run(&[b"JSON.SET", b"doc", b"$.*.y", b"9"]),
11261            "-Err wrong static path\r\n"
11262        );
11263    }
11264
11265    /// What `JSON.SET` does with a path that named nowhere.
11266    #[test]
11267    fn json_set_creates_one_field_and_refuses_to_invent_the_rest() {
11268        let mut f = Fixture::new();
11269        // A key that is not there can only be written whole.
11270        assert_eq!(
11271            f.run(&[b"JSON.SET", b"new", b".a", b"1"]),
11272            "-ERR new objects must be created at the root\r\n"
11273        );
11274        assert_eq!(f.run(&[b"EXISTS", b"new"]), ":0\r\n");
11275        // The root check comes before NX and XX, which is the order a real
11276        // server checks them in.
11277        assert_eq!(
11278            f.run(&[b"JSON.SET", b"new", b".a", b"1", b"NX"]),
11279            "-ERR new objects must be created at the root\r\n"
11280        );
11281        assert_eq!(f.run(&[b"JSON.SET", b"new", b"$", b"1", b"XX"]), "$-1\r\n");
11282        assert_eq!(f.run(&[b"JSON.SET", b"new", b"$", b"1", b"NX"]), "+OK\r\n");
11283
11284        f.run(&[
11285            b"JSON.SET",
11286            b"doc",
11287            b"$",
11288            br#"{"o":{},"arr":[1,2],"s":"x"}"#,
11289        ]);
11290        // One step past a container that is there is a place to write.
11291        assert_eq!(f.run(&[b"JSON.SET", b"doc", b"$.o.made", b"1"]), "+OK\r\n");
11292        // One step past something that is not, or past something that is not an
11293        // object, is not an error and is not a write either.
11294        assert_eq!(
11295            f.run(&[b"JSON.SET", b"doc", b"$.nope.made", b"1"]),
11296            "$-1\r\n"
11297        );
11298        assert_eq!(f.run(&[b"JSON.SET", b"doc", b"$.s.made", b"1"]), "$-1\r\n");
11299        // An index past the end does not append. JSON.ARRAPPEND appends.
11300        assert_eq!(
11301            f.run(&[b"JSON.SET", b"doc", b"$.arr[5]", b"9"]),
11302            "-ERR array index out of range\r\n"
11303        );
11304        assert_eq!(
11305            f.run(&[b"JSON.SET", b"doc", b"$.arr[2]", b"9"]),
11306            "-ERR array index out of range\r\n"
11307        );
11308        assert_eq!(f.run(&[b"JSON.SET", b"doc", b"$.arr[1]", b"9"]), "+OK\r\n");
11309        // NX on a path that is there and XX on a path that is not are both a
11310        // nil and neither changes anything.
11311        assert_eq!(
11312            f.run(&[b"JSON.SET", b"doc", b"$.o.made", b"2", b"NX"]),
11313            "$-1\r\n"
11314        );
11315        assert_eq!(
11316            f.run(&[b"JSON.SET", b"doc", b"$.gone", b"2", b"XX"]),
11317            "$-1\r\n"
11318        );
11319        assert_eq!(
11320            f.run(&[b"JSON.GET", b"doc"]),
11321            bulk(r#"{"o":{"made":1},"s":"x","arr":[1,9]}"#)
11322        );
11323        // Text that is not JSON is refused before the key is touched. The
11324        // line has no `ERR` in front of it, which is this command's and not
11325        // every command's, and is in D-37.
11326        assert!(
11327            f.run(&[b"JSON.SET", b"doc", b"$.s", b"nope"])
11328                .starts_with("-this is not the start of a value")
11329        );
11330        assert_eq!(f.run(&[b"JSON.GET", b"doc", b".s"]), bulk("\"x\""));
11331    }
11332
11333    /// `JSON.DEL`, `JSON.TYPE`, `JSON.TOGGLE` and `JSON.CLEAR`, each of which
11334    /// answers a count or a word rather than text.
11335    #[test]
11336    fn the_json_commands_that_do_not_answer_text() {
11337        let mut f = Fixture::new();
11338        let doc = br#"{"a":1,"t":true,"o":{"x":1},"arr":[1,2],"f":1.5,"s":"x","n":null}"#;
11339        f.run(&[b"JSON.SET", b"doc", b"$", doc]);
11340
11341        assert_eq!(f.run(&[b"JSON.TYPE", b"doc"]), bulk("object"));
11342        assert_eq!(f.run(&[b"JSON.TYPE", b"doc", b".a"]), bulk("integer"));
11343        assert_eq!(f.run(&[b"JSON.TYPE", b"doc", b".f"]), bulk("number"));
11344        assert_eq!(
11345            f.run(&[b"JSON.TYPE", b"doc", b"$.a"]),
11346            format!("*1\r\n{}", bulk("integer"))
11347        );
11348        // The one place a legacy path that matched nothing is a nil rather than
11349        // an error, which lines up with a key that is not there.
11350        assert_eq!(f.run(&[b"JSON.TYPE", b"doc", b".nope"]), "$-1\r\n");
11351        assert_eq!(f.run(&[b"JSON.TYPE", b"nokey"]), "$-1\r\n");
11352
11353        // A boolean flips and answers the value it now has, as an integer on
11354        // one syntax and as the word on the other.
11355        assert_eq!(f.run(&[b"JSON.TOGGLE", b"doc", b"$.t"]), "*1\r\n:0\r\n");
11356        assert_eq!(f.run(&[b"JSON.TOGGLE", b"doc", b".t"]), bulk("true"));
11357        // Something that is not a boolean is a hole on one syntax and one
11358        // sentence covering both cases on the other.
11359        assert_eq!(f.run(&[b"JSON.TOGGLE", b"doc", b"$.a"]), "*1\r\n$-1\r\n");
11360        assert_eq!(
11361            f.run(&[b"JSON.TOGGLE", b"doc", b".a"]),
11362            "-ERR Path does not exist or not a bool\r\n"
11363        );
11364        assert_eq!(
11365            f.run(&[b"JSON.TOGGLE", b"doc", b".nope"]),
11366            "-ERR Path does not exist or not a bool\r\n"
11367        );
11368        assert_eq!(
11369            f.run(&[b"JSON.TOGGLE", b"nokey", b"$.a"]),
11370            "-ERR could not perform this operation on a key that doesn't exist\r\n"
11371        );
11372
11373        // Clearing empties containers and zeroes numbers and leaves everything
11374        // else alone, and counts only what it changed.
11375        assert_eq!(f.run(&[b"JSON.CLEAR", b"doc", b"$.s"]), ":0\r\n");
11376        assert_eq!(f.run(&[b"JSON.CLEAR", b"doc", b"$.*"]), ":4\r\n");
11377        assert_eq!(f.run(&[b"JSON.CLEAR", b"doc", b"$.*"]), ":0\r\n");
11378        assert_eq!(
11379            f.run(&[b"JSON.GET", b"doc"]),
11380            bulk(r#"{"a":0,"f":0,"n":null,"o":{},"s":"x","t":true,"arr":[]}"#)
11381        );
11382
11383        // Deleting counts what it removed, and deleting the root is deleting
11384        // the key.
11385        assert_eq!(f.run(&[b"JSON.DEL", b"doc", b"$.nope"]), ":0\r\n");
11386        assert_eq!(f.run(&[b"JSON.DEL", b"doc", b"$.a"]), ":1\r\n");
11387        // Deleting the last member of the root container deletes the key, the
11388        // same way popping the last element off a list does. It is a rule about
11389        // deleting and not about shape: a document written as an empty object
11390        // by JSON.SET stays, because nothing was removed from it.
11391        assert_eq!(f.run(&[b"JSON.FORGET", b"doc", b"$.*"]), ":6\r\n");
11392        assert_eq!(f.run(&[b"EXISTS", b"doc"]), ":0\r\n");
11393        assert_eq!(f.run(&[b"JSON.GET", b"doc"]), "$-1\r\n");
11394        assert_eq!(f.run(&[b"JSON.DEL", b"doc"]), ":0\r\n");
11395        assert_eq!(f.run(&[b"JSON.SET", b"empty", b"$", b"{}"]), "+OK\r\n");
11396        assert_eq!(f.run(&[b"EXISTS", b"empty"]), ":1\r\n");
11397        assert_eq!(f.run(&[b"JSON.GET", b"empty"]), bulk("{}"));
11398        assert_eq!(f.run(&[b"JSON.DEL", b"nokey"]), ":0\r\n");
11399    }
11400
11401    /// `JSON.GET` with more than one path, and with a layout.
11402    ///
11403    /// The wrapper the reply is built in is laid out too, so what a path
11404    /// matched starts one level in for a single JSONPath and two for one of
11405    /// several, and getting that wrong is the kind of thing only a byte for
11406    /// byte comparison catches.
11407    #[test]
11408    fn json_get_lays_out_the_wrapper_it_builds() {
11409        let mut f = Fixture::new();
11410        f.run(&[b"JSON.SET", b"doc", b"$", br#"{"a":1,"b":[1,{"c":2}]}"#]);
11411
11412        assert_eq!(
11413            f.run(&[b"JSON.GET", b"doc", b"$.a", b"$.b"]),
11414            bulk(r#"{"$.a":[1],"$.b":[[1,{"c":2}]]}"#)
11415        );
11416        // Legacy paths are not wrapped, even when there are several of them.
11417        assert_eq!(
11418            f.run(&[b"JSON.GET", b"doc", b".a", b".b"]),
11419            bulk(r#"{".a":1,".b":[1,{"c":2}]}"#)
11420        );
11421        let fmt: &[&[u8]] = &[b"INDENT", b"  ", b"NEWLINE", b"\n", b"SPACE", b" "];
11422        let mut one = vec![b"JSON.GET".as_slice(), b"doc"];
11423        one.extend_from_slice(fmt);
11424        one.push(b"$.b");
11425        assert_eq!(
11426            f.run(&one),
11427            bulk("[\n  [\n    1,\n    {\n      \"c\": 2\n    }\n  ]\n]")
11428        );
11429        let mut two = vec![b"JSON.GET".as_slice(), b"doc"];
11430        two.extend_from_slice(fmt);
11431        two.push(b"$.a");
11432        two.push(b"$.nope");
11433        assert_eq!(
11434            f.run(&two),
11435            bulk("{\n  \"$.a\": [\n    1\n  ],\n  \"$.nope\": []\n}")
11436        );
11437        // The options are read before the paths and in any order, and a
11438        // document with nothing to lay out is the same either way.
11439        let mut root = vec![b"JSON.GET".as_slice(), b"doc", b"SPACE", b" "];
11440        root.push(b".a");
11441        assert_eq!(f.run(&root), bulk("1"));
11442    }
11443
11444    /// `JSON.MGET`, which is the only command here that reads more than one key
11445    /// and so the only one whose answer has holes in it.
11446    #[test]
11447    fn json_mget_answers_once_per_key_whatever_is_under_them() {
11448        let mut f = Fixture::new();
11449        f.run(&[b"JSON.SET", b"one", b"$", br#"{"a":1}"#]);
11450        f.run(&[b"JSON.SET", b"two", b"$", br#"{"a":2}"#]);
11451        f.run(&[b"SET", b"plain", b"x"]);
11452        assert_eq!(
11453            f.run(&[b"JSON.MGET", b"one", b"two", b"$.a"]),
11454            format!("*2\r\n{}{}", bulk("[1]"), bulk("[2]"))
11455        );
11456        // A key that is not there and a key holding something else are both a
11457        // hole rather than an error, the way MGET treats a hash.
11458        assert_eq!(
11459            f.run(&[b"JSON.MGET", b"one", b"nokey", b"plain", b".a"]),
11460            format!("*3\r\n{}$-1\r\n$-1\r\n", bulk("1"))
11461        );
11462        // A legacy path that matched nothing is a hole too, because one bad
11463        // answer should not lose the others.
11464        assert_eq!(f.run(&[b"JSON.MGET", b"one", b".nope"]), "*1\r\n$-1\r\n");
11465    }
11466
11467    /// The four commands that ask how big something is, and the four different
11468    /// sets of answers they give for the same three failures.
11469    ///
11470    /// There is no pattern in this and there is no reading it off the
11471    /// documentation either. It was read off a running RedisJSON one line at a
11472    /// time, and it is written down here because the error text is what a client
11473    /// library branches on.
11474    #[test]
11475    fn the_json_commands_that_answer_a_size_disagree_about_every_failure() {
11476        let mut f = Fixture::new();
11477        let doc = br#"{"a":[1,2,3],"o":{"x":1,"y":2},"s":"hello","n":7}"#;
11478        f.run(&[b"JSON.SET", b"doc", b"$", doc]);
11479
11480        assert_eq!(f.run(&[b"JSON.ARRLEN", b"doc", b".a"]), ":3\r\n");
11481        assert_eq!(f.run(&[b"JSON.ARRLEN", b"doc", b"$.a"]), "*1\r\n:3\r\n");
11482        assert_eq!(f.run(&[b"JSON.OBJLEN", b"doc", b".o"]), ":2\r\n");
11483        assert_eq!(f.run(&[b"JSON.STRLEN", b"doc", b".s"]), ":5\r\n");
11484        assert_eq!(
11485            f.run(&[b"JSON.OBJKEYS", b"doc", b".o"]),
11486            format!("*2\r\n{}{}", bulk("x"), bulk("y"))
11487        );
11488        // A JSONPath answers one entry per match and a hole for a match of the
11489        // wrong kind, which is the one shape all four agree on.
11490        assert_eq!(
11491            f.run(&[b"JSON.ARRLEN", b"doc", b"$.*"]),
11492            "*4\r\n:3\r\n$-1\r\n$-1\r\n$-1\r\n"
11493        );
11494
11495        // A legacy path that matched nothing. Two of them are an error and two
11496        // of them are a nil, and the two errors do not use the same sentence.
11497        assert_eq!(
11498            f.run(&[b"JSON.ARRLEN", b"doc", b".nope"]),
11499            "-ERR Path does not exist\r\n"
11500        );
11501        assert_eq!(
11502            f.run(&[b"JSON.STRLEN", b"doc", b".nope"]),
11503            "-ERR Path does not exist\r\n"
11504        );
11505        assert_eq!(f.run(&[b"JSON.OBJLEN", b"doc", b".nope"]), "$-1\r\n");
11506        // A nil bulk and not an empty array, even though the answer would have
11507        // been an array, which is what RedisJSON sends here too.
11508        assert_eq!(f.run(&[b"JSON.OBJKEYS", b"doc", b".nope"]), "$-1\r\n");
11509        // The JSONPath spelling of the same question is an empty array, since
11510        // no match is not a failure on that syntax.
11511        assert_eq!(f.run(&[b"JSON.OBJKEYS", b"doc", b"$.nope"]), "*0\r\n");
11512
11513        // A legacy path that matched the wrong kind of value. Now two of them
11514        // are an ERR and two of them are a WRONGTYPE, and it is not the same
11515        // two.
11516        assert_eq!(
11517            f.run(&[b"JSON.ARRLEN", b"doc", b".n"]),
11518            "-ERR Path does not exist or not an array\r\n"
11519        );
11520        assert_eq!(
11521            f.run(&[b"JSON.OBJKEYS", b"doc", b".n"]),
11522            "-ERR Path does not exist or not an object\r\n"
11523        );
11524        assert_eq!(
11525            f.run(&[b"JSON.OBJLEN", b"doc", b".n"]),
11526            "-WRONGTYPE wrong type of path value - expected object\r\n"
11527        );
11528        assert_eq!(
11529            f.run(&[b"JSON.STRLEN", b"doc", b".n"]),
11530            "-WRONGTYPE wrong type of path value - expected string\r\n"
11531        );
11532
11533        // A key that is not there, where the two syntaxes swap over: the legacy
11534        // path is the quiet answer and the JSONPath is the error.
11535        assert_eq!(f.run(&[b"JSON.ARRLEN", b"nokey", b".a"]), "$-1\r\n");
11536        assert_eq!(f.run(&[b"JSON.OBJLEN", b"nokey", b".a"]), "$-1\r\n");
11537        assert_eq!(f.run(&[b"JSON.STRLEN", b"nokey", b".a"]), "$-1\r\n");
11538        assert_eq!(f.run(&[b"JSON.OBJKEYS", b"nokey", b".a"]), "$-1\r\n");
11539        assert_eq!(
11540            f.run(&[b"JSON.ARRLEN", b"nokey", b"$.a"]),
11541            "-ERR could not perform this operation on a key that doesn't exist\r\n"
11542        );
11543        // Except this one, which answers about the path instead.
11544        assert_eq!(
11545            f.run(&[b"JSON.OBJLEN", b"nokey", b"$.a"]),
11546            "-ERR Path does not exist or not an object\r\n"
11547        );
11548    }
11549
11550    /// `JSON.ARRAPPEND`, `JSON.ARRINSERT`, `JSON.ARRTRIM` and `JSON.ARRPOP`.
11551    ///
11552    /// The four of them share one error line for a path that named something
11553    /// that is not an array, and they disagree about what an index outside the
11554    /// array means: insert refuses it and the other two clamp.
11555    #[test]
11556    fn the_json_array_writes_agree_on_the_errors_and_not_on_the_indexes() {
11557        let mut f = Fixture::new();
11558        f.run(&[b"JSON.SET", b"doc", b"$", br#"{"a":[1,2,3],"n":7}"#]);
11559
11560        assert_eq!(f.run(&[b"JSON.ARRAPPEND", b"doc", b".a", b"4"]), ":4\r\n");
11561        assert_eq!(
11562            f.run(&[b"JSON.ARRAPPEND", b"doc", b"$.a", b"5", b"6"]),
11563            "*1\r\n:6\r\n"
11564        );
11565        assert_eq!(f.run(&[b"JSON.GET", b"doc", b".a"]), bulk("[1,2,3,4,5,6]"));
11566
11567        // A negative index counts back from the end, and the end itself is a
11568        // place to insert at, so an insert at the length is an append.
11569        assert_eq!(
11570            f.run(&[b"JSON.ARRINSERT", b"doc", b".a", b"-1", b"0"]),
11571            ":7\r\n"
11572        );
11573        assert_eq!(
11574            f.run(&[b"JSON.GET", b"doc", b".a"]),
11575            bulk("[1,2,3,4,5,0,6]")
11576        );
11577        assert_eq!(
11578            f.run(&[b"JSON.ARRINSERT", b"doc", b".a", b"7", b"9"]),
11579            ":8\r\n"
11580        );
11581        // One past the end is not, and neither is one before the front.
11582        assert_eq!(
11583            f.run(&[b"JSON.ARRINSERT", b"doc", b".a", b"9", b"9"]),
11584            "-ERR index out of bounds\r\n"
11585        );
11586        assert_eq!(
11587            f.run(&[b"JSON.ARRINSERT", b"doc", b".a", b"-9", b"9"]),
11588            "-ERR index out of bounds\r\n"
11589        );
11590
11591        // Trim takes both ends inclusive and clamps both of them, so a start
11592        // past the end leaves an empty array rather than an error.
11593        f.run(&[b"JSON.SET", b"doc", b"$.a", b"[1,2,3,4,5]"]);
11594        assert_eq!(
11595            f.run(&[b"JSON.ARRTRIM", b"doc", b".a", b"1", b"3"]),
11596            ":3\r\n"
11597        );
11598        assert_eq!(f.run(&[b"JSON.GET", b"doc", b".a"]), bulk("[2,3,4]"));
11599        assert_eq!(
11600            f.run(&[b"JSON.ARRTRIM", b"doc", b".a", b"-2", b"99"]),
11601            ":2\r\n"
11602        );
11603        assert_eq!(f.run(&[b"JSON.GET", b"doc", b".a"]), bulk("[3,4]"));
11604        assert_eq!(
11605            f.run(&[b"JSON.ARRTRIM", b"doc", b".a", b"9", b"9"]),
11606            ":0\r\n"
11607        );
11608        assert_eq!(f.run(&[b"JSON.GET", b"doc", b".a"]), bulk("[]"));
11609
11610        // Pop clamps as well, its default is the last element, and an empty
11611        // array pops a nil rather than failing.
11612        f.run(&[b"JSON.SET", b"doc", b"$.a", b"[1,2,3]"]);
11613        assert_eq!(f.run(&[b"JSON.ARRPOP", b"doc", b".a"]), bulk("3"));
11614        assert_eq!(f.run(&[b"JSON.ARRPOP", b"doc", b".a", b"0"]), bulk("1"));
11615        assert_eq!(f.run(&[b"JSON.ARRPOP", b"doc", b".a", b"99"]), bulk("2"));
11616        assert_eq!(f.run(&[b"JSON.ARRPOP", b"doc", b".a"]), "$-1\r\n");
11617
11618        // One sentence covers a path that matched nothing and a path that
11619        // matched the wrong kind of value, for all four of them.
11620        for call in [
11621            &[&b"JSON.ARRAPPEND"[..], b"doc", b"PATH", b"1"][..],
11622            &[&b"JSON.ARRTRIM"[..], b"doc", b"PATH", b"1", b"1"][..],
11623            &[&b"JSON.ARRPOP"[..], b"doc", b"PATH", b"1"][..],
11624            &[&b"JSON.ARRINSERT"[..], b"doc", b"PATH", b"0", b"1"][..],
11625        ] {
11626            for path in [&b".n"[..], &b".nope"[..]] {
11627                let args: Vec<&[u8]> = call
11628                    .iter()
11629                    .map(|a| if *a == b"PATH" { path } else { *a })
11630                    .collect();
11631                assert_eq!(
11632                    f.run(&args),
11633                    "-ERR Path does not exist or not an array\r\n",
11634                    "{} {}",
11635                    String::from_utf8_lossy(call[0]),
11636                    String::from_utf8_lossy(path)
11637                );
11638            }
11639        }
11640
11641        // A key that is not there is the same sentence for all four, on either
11642        // syntax, and it is about the key and not about the path.
11643        assert_eq!(
11644            f.run(&[b"JSON.ARRAPPEND", b"nokey", b".a", b"1"]),
11645            "-ERR could not perform this operation on a key that doesn't exist\r\n"
11646        );
11647        assert_eq!(
11648            f.run(&[b"JSON.ARRPOP", b"nokey", b"$.a"]),
11649            "-ERR could not perform this operation on a key that doesn't exist\r\n"
11650        );
11651
11652        // The values are parsed before the key is touched, so text that is not
11653        // JSON leaves the document alone.
11654        // Text that is not JSON is refused before the key is touched, and
11655        // the line has no `ERR` in front of it, which is D-37.
11656        assert!(
11657            f.run(&[b"JSON.ARRAPPEND", b"doc", b".a", b"nope"])
11658                .starts_with("-this is not the start of a value")
11659        );
11660        assert_eq!(f.run(&[b"JSON.GET", b"doc", b".a"]), bulk("[]"));
11661    }
11662
11663    /// `JSON.ARRINSERT` refuses the whole command when any one of the arrays a
11664    /// path matched cannot take the index, which is D-36.
11665    ///
11666    /// RedisJSON walks the matches, inserts into each one it can, and returns
11667    /// the error on the first one it cannot, leaving the earlier inserts in the
11668    /// document. A write here is one list of edits applied together, so either
11669    /// all of them happen or none of them do.
11670    #[test]
11671    fn json_arrinsert_is_all_or_nothing_across_the_matches() {
11672        let mut f = Fixture::new();
11673        let doc = br#"{"a":[1,2,3],"n":{"a":[9,8],"in":{"a":[1]}}}"#;
11674        f.run(&[b"JSON.SET", b"doc", b"$", doc]);
11675        assert_eq!(
11676            f.run(&[b"JSON.ARRINSERT", b"doc", b"$..a", b"-2", b"0"]),
11677            "-ERR index out of bounds\r\n"
11678        );
11679        assert_eq!(
11680            f.run(&[b"JSON.GET", b"doc"]),
11681            bulk(r#"{"a":[1,2,3],"n":{"a":[9,8],"in":{"a":[1]}}}"#)
11682        );
11683        // Every match can take the index, so every match gets it.
11684        assert_eq!(
11685            f.run(&[b"JSON.ARRINSERT", b"doc", b"$..a", b"0", b"0"]),
11686            "*3\r\n:4\r\n:3\r\n:2\r\n"
11687        );
11688        assert_eq!(
11689            f.run(&[b"JSON.GET", b"doc"]),
11690            bulk(r#"{"a":[0,1,2,3],"n":{"a":[0,9,8],"in":{"a":[0,1]}}}"#)
11691        );
11692    }
11693
11694    /// `JSON.ARRINDEX`, whose stop is exclusive and whose start clamps to the
11695    /// last element rather than to one past it.
11696    ///
11697    /// Both of those read like mistakes and both are what RedisJSON does. The
11698    /// start is the one that bites: a start of five into an array of four still
11699    /// looks at the fourth, so a search that should have run out of array comes
11700    /// back with an answer.
11701    #[test]
11702    fn json_arrindex_has_an_exclusive_stop_and_a_start_that_cannot_run_off_the_end() {
11703        let mut f = Fixture::new();
11704        f.run(&[b"JSON.SET", b"doc", b"$", br#"{"a":[1,2,3,1],"n":7}"#]);
11705
11706        assert_eq!(f.run(&[b"JSON.ARRINDEX", b"doc", b".a", b"2"]), ":1\r\n");
11707        assert_eq!(f.run(&[b"JSON.ARRINDEX", b"doc", b".a", b"9"]), ":-1\r\n");
11708        assert_eq!(
11709            f.run(&[b"JSON.ARRINDEX", b"doc", b"$.a", b"2"]),
11710            "*1\r\n:1\r\n"
11711        );
11712
11713        // Zero as the stop means the end rather than the front, so leaving it
11714        // off and passing it are the same thing.
11715        assert_eq!(
11716            f.run(&[b"JSON.ARRINDEX", b"doc", b".a", b"1", b"1", b"0"]),
11717            ":3\r\n"
11718        );
11719        // The stop is exclusive, so a stop of three does not look at index
11720        // three.
11721        assert_eq!(
11722            f.run(&[b"JSON.ARRINDEX", b"doc", b".a", b"1", b"1", b"3"]),
11723            ":-1\r\n"
11724        );
11725
11726        // The start clamps to the last element in both directions, which is why
11727        // a start of four, five or minus one all find the 1 at index three.
11728        for start in [&b"4"[..], &b"5"[..], &b"-1"[..]] {
11729            assert_eq!(
11730                f.run(&[b"JSON.ARRINDEX", b"doc", b".a", b"1", start]),
11731                ":3\r\n",
11732                "{}",
11733                String::from_utf8_lossy(start)
11734            );
11735        }
11736        assert_eq!(
11737            f.run(&[b"JSON.ARRINDEX", b"doc", b".a", b"1", b"-100"]),
11738            ":0\r\n"
11739        );
11740        // An empty array is the one case that comes back with nothing, since
11741        // the stop is zero and the loop never starts.
11742        f.run(&[b"JSON.SET", b"doc", b"$.a", b"[]"]);
11743        assert_eq!(
11744            f.run(&[b"JSON.ARRINDEX", b"doc", b".a", b"1", b"1"]),
11745            ":-1\r\n"
11746        );
11747
11748        // The comparison is structural rather than one of the encoded bytes,
11749        // because an object in a stored document holds its keys as intern table
11750        // ids where one parsed off the wire holds them as bytes.
11751        f.run(&[b"JSON.SET", b"doc", b"$.a", br#"[{"k":1},[1,2],"s"]"#]);
11752        assert_eq!(
11753            f.run(&[b"JSON.ARRINDEX", b"doc", b".a", br#"{"k":1}"#]),
11754            ":0\r\n"
11755        );
11756        assert_eq!(
11757            f.run(&[b"JSON.ARRINDEX", b"doc", b".a", b"[1,2]"]),
11758            ":1\r\n"
11759        );
11760        assert_eq!(
11761            f.run(&[b"JSON.ARRINDEX", b"doc", b".a", b"[2,1]"]),
11762            ":-1\r\n"
11763        );
11764
11765        // Its errors are a third set again: a missing legacy path is the short
11766        // sentence, the wrong kind of value is a WRONGTYPE, and a key that is
11767        // not there is about the path on either syntax.
11768        assert_eq!(
11769            f.run(&[b"JSON.ARRINDEX", b"doc", b".nope", b"1"]),
11770            "-ERR Path does not exist\r\n"
11771        );
11772        assert_eq!(
11773            f.run(&[b"JSON.ARRINDEX", b"doc", b".n", b"1"]),
11774            "-WRONGTYPE wrong type of path value - expected array\r\n"
11775        );
11776        assert_eq!(
11777            f.run(&[b"JSON.ARRINDEX", b"nokey", b".a", b"1"]),
11778            "-ERR Path does not exist\r\n"
11779        );
11780        assert_eq!(
11781            f.run(&[b"JSON.ARRINDEX", b"nokey", b"$.a", b"1"]),
11782            "-ERR Path does not exist\r\n"
11783        );
11784    }
11785
11786    /// The number family answers text and keeps an integer an integer until
11787    /// something in the sum is not one.
11788    #[test]
11789    fn the_json_number_family_answers_json_text_and_keeps_its_integers() {
11790        let mut f = Fixture::new();
11791        let doc = br#"{"i":7,"f":1.5,"neg":-2,"s":"ab"}"#;
11792        f.run(&[b"JSON.SET", b"doc", b"$", doc]);
11793
11794        // A legacy path answers the new value as JSON text in a bulk string,
11795        // not as a number, which is the shape all three of them use.
11796        assert_eq!(
11797            f.run(&[b"JSON.NUMINCRBY", b"doc", b".i", b"2"]),
11798            bulk("9").as_str()
11799        );
11800        // A JSONPath answers a bulk string holding a JSON array.
11801        assert_eq!(
11802            f.run(&[b"JSON.NUMINCRBY", b"doc", b"$.i", b"2"]),
11803            bulk("[11]").as_str()
11804        );
11805        // Two integers stay an integer and a double anywhere in it makes the
11806        // answer a double, which the document then holds.
11807        assert_eq!(
11808            f.run(&[b"JSON.NUMINCRBY", b"doc", b".i", b"2.0"]),
11809            bulk("13.0").as_str()
11810        );
11811        assert_eq!(
11812            f.run(&[b"JSON.TYPE", b"doc", b".i"]),
11813            bulk("number").as_str()
11814        );
11815        assert_eq!(
11816            f.run(&[b"JSON.NUMMULTBY", b"doc", b".f", b"2"]),
11817            bulk("3.0").as_str()
11818        );
11819        assert_eq!(
11820            f.run(&[b"JSON.NUMPOWBY", b"doc", b".neg", b"3"]),
11821            bulk("-8").as_str()
11822        );
11823        // A power of a half is a square root, and the square root of a negative
11824        // number is the error that says the answer is not a number.
11825        f.run(&[b"JSON.SET", b"doc", b"$.f", b"1.5"]);
11826        assert_eq!(
11827            f.run(&[b"JSON.NUMPOWBY", b"doc", b".f", b"0.5"]),
11828            bulk("1.224744871391589").as_str()
11829        );
11830        assert_eq!(
11831            f.run(&[b"JSON.NUMPOWBY", b"doc", b".neg", b"0.5"]),
11832            "-ERR result is not a number\r\n"
11833        );
11834        // An integer answer that does not fit is refused rather than promoted,
11835        // and a negative exponent lands in the same error because there is no
11836        // integer answer to two to the minus one.
11837        f.run(&[b"JSON.SET", b"doc", b"$.big", b"9223372036854775807"]);
11838        assert_eq!(
11839            f.run(&[b"JSON.NUMINCRBY", b"doc", b".big", b"1"]),
11840            "-ERR numeric overflow\r\n"
11841        );
11842        f.run(&[b"JSON.SET", b"doc", b"$.p", b"2"]);
11843        assert_eq!(
11844            f.run(&[b"JSON.NUMPOWBY", b"doc", b".p", b"-1"]),
11845            "-ERR numeric overflow\r\n"
11846        );
11847        // A double that leaves the finite numbers is the other error.
11848        f.run(&[b"JSON.SET", b"doc", b"$.huge", b"1e308"]);
11849        assert_eq!(
11850            f.run(&[b"JSON.NUMMULTBY", b"doc", b".huge", b"1e10"]),
11851            "-ERR result is not a number\r\n"
11852        );
11853
11854        // A match that is not a number is a null inside the array on a
11855        // JSONPath, and a legacy path that found no number at all is the error
11856        // with the module's own typo in it.
11857        assert_eq!(
11858            f.run(&[b"JSON.NUMINCRBY", b"doc", b"$.s", b"1"]),
11859            bulk("[null]").as_str()
11860        );
11861        assert_eq!(
11862            f.run(&[b"JSON.NUMINCRBY", b"doc", b"$.nope", b"1"]),
11863            bulk("[]").as_str()
11864        );
11865        assert_eq!(
11866            f.run(&[b"JSON.NUMINCRBY", b"doc", b".s", b"1"]),
11867            "-ERR Path does not exist or does not contains a number\r\n"
11868        );
11869        assert_eq!(
11870            f.run(&[b"JSON.NUMINCRBY", b"doc", b".nope", b"1"]),
11871            "-ERR Path does not exist or does not contains a number\r\n"
11872        );
11873        // The operand is JSON and has to be a number. Valid JSON that is not
11874        // one is a line of its own, and it goes out without a prefix.
11875        assert_eq!(
11876            f.run(&[b"JSON.NUMINCRBY", b"doc", b".i", b"true"]),
11877            "-bad input number\r\n"
11878        );
11879        assert_eq!(
11880            f.run(&[b"JSON.NUMINCRBY", b"nokey", b".i", b"1"]),
11881            "-ERR could not perform this operation on a key that doesn't exist\r\n"
11882        );
11883        assert_eq!(
11884            f.run(&[b"JSON.NUMINCRBY", b"nokey", b"$.i", b"1"]),
11885            "-ERR could not perform this operation on a key that doesn't exist\r\n"
11886        );
11887    }
11888
11889    /// `JSON.STRAPPEND` puts its path in the middle and makes it optional,
11890    /// which nothing else in the group does.
11891    #[test]
11892    fn json_strappend_reads_its_shape_off_the_argument_count() {
11893        let mut f = Fixture::new();
11894        f.run(&[b"JSON.SET", b"doc", b"$", br#"{"s":"ab","n":1}"#]);
11895
11896        assert_eq!(
11897            f.run(&[b"JSON.STRAPPEND", b"doc", b".s", br#""c""#]),
11898            ":3\r\n"
11899        );
11900        assert_eq!(
11901            f.run(&[b"JSON.STRAPPEND", b"doc", b"$.s", br#""d""#]),
11902            "*1\r\n:4\r\n"
11903        );
11904        // The length is in bytes and not in characters, so one two byte letter
11905        // takes it up by two.
11906        assert_eq!(
11907            f.run(&[b"JSON.STRAPPEND", b"doc", b".s", br#""\u00e9""#]),
11908            ":6\r\n"
11909        );
11910        // Three arguments means the value is the last one and the path is the
11911        // root, so this appends to a document that is a string on its own.
11912        f.run(&[b"JSON.SET", b"str", b"$", br#""ab""#]);
11913        assert_eq!(f.run(&[b"JSON.STRAPPEND", b"str", br#""c""#]), ":3\r\n");
11914        assert_eq!(f.run(&[b"JSON.GET", b"str"]), bulk("\"abc\"").as_str());
11915
11916        // The value is JSON and has to be a JSON string. A number is a
11917        // WRONGTYPE about a path value even though it was the value that was
11918        // wrong, which is the module's wording and not a slip here.
11919        assert_eq!(
11920            f.run(&[b"JSON.STRAPPEND", b"doc", b".s", b"5"]),
11921            "-WRONGTYPE wrong type of path value - expected string\r\n"
11922        );
11923        assert_eq!(
11924            f.run(&[b"JSON.STRAPPEND", b"doc", b"$.n", br#""c""#]),
11925            "*1\r\n$-1\r\n"
11926        );
11927        assert_eq!(
11928            f.run(&[b"JSON.STRAPPEND", b"doc", b".n", br#""c""#]),
11929            "-ERR Path does not exist or not a string\r\n"
11930        );
11931        assert_eq!(
11932            f.run(&[b"JSON.STRAPPEND", b"doc", b"$.nope", br#""c""#]),
11933            "*0\r\n"
11934        );
11935        assert_eq!(
11936            f.run(&[b"JSON.STRAPPEND", b"nokey", br#""c""#]),
11937            "-ERR could not perform this operation on a key that doesn't exist\r\n"
11938        );
11939    }
11940
11941    /// A legacy path can match more than one value, and which of them the one
11942    /// answer comes from is not the same choice twice.
11943    #[test]
11944    fn a_legacy_wildcard_write_touches_every_match_and_answers_only_one() {
11945        let mut f = Fixture::new();
11946        // Three arrays of one, two and three elements, which tells the first
11947        // match and the last match apart in a single command.
11948        let three = br#"{"a":[[7],[7,7],[7,7,7]]}"#;
11949
11950        f.run(&[b"JSON.SET", b"doc", b"$", three]);
11951        assert_eq!(
11952            f.run(&[b"JSON.ARRAPPEND", b"doc", b".a[*]", b"9"]),
11953            ":4\r\n"
11954        );
11955        f.run(&[b"JSON.SET", b"doc", b"$", three]);
11956        assert_eq!(
11957            f.run(&[b"JSON.ARRINSERT", b"doc", b".a[*]", b"0", b"9"]),
11958            ":2\r\n"
11959        );
11960        f.run(&[b"JSON.SET", b"doc", b"$", three]);
11961        assert_eq!(
11962            f.run(&[b"JSON.ARRTRIM", b"doc", b".a[*]", b"0", b"1"]),
11963            ":1\r\n"
11964        );
11965        f.run(&[b"JSON.SET", b"doc", b"$", br#"{"a":[[1,2,3],[4,5,6]]}"#]);
11966        assert_eq!(
11967            f.run(&[b"JSON.ARRPOP", b"doc", b".a[*]", b"0"]),
11968            bulk("1").as_str()
11969        );
11970        f.run(&[b"JSON.SET", b"doc", b"$", br#"{"a":[1,2,3]}"#]);
11971        assert_eq!(
11972            f.run(&[b"JSON.NUMINCRBY", b"doc", b".a[*]", b"10"]),
11973            bulk("13").as_str()
11974        );
11975        f.run(&[b"JSON.SET", b"doc", b"$", br#"{"a":["p","qq","rrr"]}"#]);
11976        assert_eq!(
11977            f.run(&[b"JSON.STRAPPEND", b"doc", b".a[*]", br#""z""#]),
11978            ":4\r\n"
11979        );
11980        f.run(&[b"JSON.SET", b"doc", b"$", br#"{"a":[true,false,true]}"#]);
11981        assert_eq!(
11982            f.run(&[b"JSON.TOGGLE", b"doc", b".a[*]"]),
11983            bulk("false").as_str()
11984        );
11985        // Every one of them wrote to all three matches, whichever one it chose
11986        // to answer about.
11987        assert_eq!(
11988            f.run(&[b"JSON.GET", b"doc", b".a"]),
11989            bulk("[false,true,false]").as_str()
11990        );
11991
11992        // A match of the wrong kind is skipped rather than being the answer, so
11993        // a path that found a string and then two arrays still answers.
11994        f.run(&[b"JSON.SET", b"doc", b"$", br#"{"a":["x",[1],[1,2]]}"#]);
11995        assert_eq!(
11996            f.run(&[b"JSON.ARRAPPEND", b"doc", b".a[*]", b"9"]),
11997            ":3\r\n"
11998        );
11999        assert_eq!(
12000            f.run(&[b"JSON.GET", b"doc", b".a"]),
12001            bulk(r#"["x",[1,9],[1,2,9]]"#).as_str()
12002        );
12003        // Nothing of the right kind anywhere is the error, and that is the only
12004        // case that is.
12005        f.run(&[b"JSON.SET", b"doc", b"$", br#"{"a":["x","y"]}"#]);
12006        assert_eq!(
12007            f.run(&[b"JSON.ARRAPPEND", b"doc", b".a[*]", b"9"]),
12008            "-ERR Path does not exist or not an array\r\n"
12009        );
12010        assert_eq!(
12011            f.run(&[b"JSON.TOGGLE", b"doc", b".a[*]"]),
12012            "-ERR Path does not exist or not a bool\r\n"
12013        );
12014        // The one array that was there and had nothing in it is an answer and
12015        // not a skip, so the pop answers about it rather than about the array
12016        // after it.
12017        f.run(&[b"JSON.SET", b"doc", b"$", br#"{"a":[[],[2,3]]}"#]);
12018        assert_eq!(f.run(&[b"JSON.ARRPOP", b"doc", b".a[*]"]), "$-1\r\n");
12019        assert_eq!(
12020            f.run(&[b"JSON.GET", b"doc", b".a"]),
12021            bulk("[[],[2]]").as_str()
12022        );
12023    }
12024
12025    /// A path that matched a value and something inside that value writes to
12026    /// both, which is what `$..` and a nested wildcard are for.
12027    #[test]
12028    fn a_write_reaches_a_match_that_sits_inside_another_match() {
12029        let mut f = Fixture::new();
12030        let nested = br#"{"a":[{"a":[7]},{"a":[7,7]}]}"#;
12031
12032        f.run(&[b"JSON.SET", b"doc", b"$", nested]);
12033        assert_eq!(
12034            f.run(&[b"JSON.ARRAPPEND", b"doc", b"$..a", b"9"]),
12035            "*3\r\n:3\r\n:2\r\n:3\r\n"
12036        );
12037        assert_eq!(
12038            f.run(&[b"JSON.GET", b"doc", b"$"]),
12039            bulk(r#"[{"a":[{"a":[7,9]},{"a":[7,7,9]},9]}]"#).as_str()
12040        );
12041
12042        // The same for a trim, where the outer array keeps the two elements the
12043        // inner writes landed in.
12044        f.run(&[b"JSON.SET", b"doc", b"$", nested]);
12045        assert_eq!(
12046            f.run(&[b"JSON.ARRTRIM", b"doc", b"$..a", b"0", b"0"]),
12047            "*3\r\n:1\r\n:1\r\n:1\r\n"
12048        );
12049        assert_eq!(
12050            f.run(&[b"JSON.GET", b"doc", b"$"]),
12051            bulk(r#"[{"a":[{"a":[7]}]}]"#).as_str()
12052        );
12053
12054        // And for a number, where the first match is the object the outer array
12055        // holds and only the two inside it are numbers.
12056        f.run(&[b"JSON.SET", b"doc", b"$", nested]);
12057        assert_eq!(
12058            f.run(&[b"JSON.NUMINCRBY", b"doc", b"$..a[0]", b"1"]),
12059            bulk("[null,8,8]").as_str()
12060        );
12061    }
12062
12063    /// The value a write is given is looked at only once the path has found
12064    /// something of the right kind to use it on.
12065    #[test]
12066    fn a_bad_operand_is_not_the_answer_when_the_path_found_nothing_to_use_it_on() {
12067        let mut f = Fixture::new();
12068        f.run(&[b"JSON.SET", b"doc", b"$", br#"{"n":7,"s":"t"}"#]);
12069
12070        // A string is not a number, so the path answers first and the `"x"` is
12071        // never looked at. Same for the value that is not JSON at all.
12072        assert_eq!(
12073            f.run(&[b"JSON.NUMINCRBY", b"doc", b"$.s", br#""x""#]),
12074            bulk("[null]").as_str()
12075        );
12076        assert_eq!(
12077            f.run(&[b"JSON.NUMINCRBY", b"doc", b"$.s", b"notjson"]),
12078            bulk("[null]").as_str()
12079        );
12080        assert_eq!(
12081            f.run(&[b"JSON.NUMINCRBY", b"doc", b"$.missing", b"notjson"]),
12082            bulk("[]").as_str()
12083        );
12084        assert_eq!(
12085            f.run(&[b"JSON.NUMINCRBY", b"doc", b".s", br#""x""#]),
12086            "-ERR Path does not exist or does not contains a number\r\n"
12087        );
12088        // A number match anywhere and the value is looked at after all.
12089        assert_eq!(
12090            f.run(&[b"JSON.NUMINCRBY", b"doc", b"$.n", br#""x""#]),
12091            "-bad input number\r\n"
12092        );
12093
12094        // JSON.STRAPPEND follows the same order with its own two answers.
12095        assert_eq!(
12096            f.run(&[b"JSON.STRAPPEND", b"doc", b"$.n", b"1"]),
12097            "*1\r\n$-1\r\n"
12098        );
12099        assert_eq!(
12100            f.run(&[b"JSON.STRAPPEND", b"doc", b".n", b"1"]),
12101            "-ERR Path does not exist or not a string\r\n"
12102        );
12103        assert_eq!(
12104            f.run(&[b"JSON.STRAPPEND", b"doc", b"$.s", b"1"]),
12105            "-WRONGTYPE wrong type of path value - expected string\r\n"
12106        );
12107
12108        // A key that is not there still comes before either of them.
12109        assert_eq!(
12110            f.run(&[b"JSON.NUMINCRBY", b"nope", b"$.a", br#""x""#]),
12111            "-ERR could not perform this operation on a key that doesn't exist\r\n"
12112        );
12113        assert_eq!(
12114            f.run(&[b"JSON.STRAPPEND", b"nope", b"$.a", b"1"]),
12115            "-ERR could not perform this operation on a key that doesn't exist\r\n"
12116        );
12117    }
12118
12119    /// RFC 7386 in one test: a null deletes, everything else merges, and a
12120    /// patch that is not an object replaces what it lands on.
12121    #[test]
12122    fn a_merge_patch_adds_replaces_and_deletes_in_one_write() {
12123        let mut f = Fixture::new();
12124
12125        // A key that is not there is created at the root, nulls and all,
12126        // because a deletion with nothing to delete is still what the client
12127        // sent.
12128        assert_eq!(
12129            f.run(&[b"JSON.MERGE", b"doc", b"$", br#"{"x":null,"y":1}"#]),
12130            "+OK\r\n"
12131        );
12132        assert_eq!(
12133            f.run(&[b"JSON.GET", b"doc", b"$"]),
12134            bulk(r#"[{"x":null,"y":1}]"#).as_str()
12135        );
12136
12137        // Onto something that is there, a null deletes the member of that name
12138        // and the rest is merged one level at a time.
12139        f.run(&[b"JSON.SET", b"doc", b"$", br#"{"a":{"b":1,"c":2},"d":3}"#]);
12140        assert_eq!(
12141            f.run(&[b"JSON.MERGE", b"doc", b"$", br#"{"a":{"b":null,"e":4}}"#]),
12142            "+OK\r\n"
12143        );
12144        assert_eq!(
12145            f.run(&[b"JSON.GET", b"doc", b"$"]),
12146            bulk(r#"[{"a":{"c":2,"e":4},"d":3}]"#).as_str()
12147        );
12148
12149        // A patch that is not an object replaces what it is merged onto.
12150        assert_eq!(f.run(&[b"JSON.MERGE", b"doc", b"$.a", b"[1,2]"]), "+OK\r\n");
12151        assert_eq!(
12152            f.run(&[b"JSON.GET", b"doc", b"$"]),
12153            bulk(r#"[{"a":[1,2],"d":3}]"#).as_str()
12154        );
12155
12156        // A patch object onto a value that is not an object starts from an
12157        // empty object, so this time the null has nothing to delete and is
12158        // dropped rather than stored.
12159        assert_eq!(
12160            f.run(&[b"JSON.MERGE", b"doc", b"$.d", br#"{"p":null,"q":9}"#]),
12161            "+OK\r\n"
12162        );
12163        assert_eq!(
12164            f.run(&[b"JSON.GET", b"doc", b"$"]),
12165            bulk(r#"[{"a":[1,2],"d":{"q":9}}]"#).as_str()
12166        );
12167
12168        // A member one level past the end of the document is created and keeps
12169        // its nulls, two levels past it is a write that did not happen, and a
12170        // path that would have to invent where it goes is the unprefixed line.
12171        assert_eq!(
12172            f.run(&[b"JSON.MERGE", b"doc", b"$.new", br#"{"z":null}"#]),
12173            "+OK\r\n"
12174        );
12175        assert_eq!(
12176            f.run(&[b"JSON.GET", b"doc", b"$.new"]),
12177            bulk(r#"[{"z":null}]"#).as_str()
12178        );
12179        assert_eq!(
12180            f.run(&[b"JSON.MERGE", b"doc", b"$.no.deep", b"1"]),
12181            "$-1\r\n"
12182        );
12183        assert_eq!(
12184            f.run(&[b"JSON.MERGE", b"doc", b"$.no.*", b"1"]),
12185            "-Err wrong static path\r\n"
12186        );
12187
12188        // A wildcard merges every match.
12189        f.run(&[b"JSON.SET", b"doc", b"$", br#"{"a":{"n":1},"b":{"n":2}}"#]);
12190        assert_eq!(
12191            f.run(&[b"JSON.MERGE", b"doc", b"$.*", br#"{"m":0}"#]),
12192            "+OK\r\n"
12193        );
12194        assert_eq!(
12195            f.run(&[b"JSON.GET", b"doc", b"$"]),
12196            bulk(r#"[{"a":{"m":0,"n":1},"b":{"m":0,"n":2}}]"#).as_str()
12197        );
12198
12199        // The three ways to get it wrong.
12200        assert_eq!(
12201            f.run(&[b"JSON.MERGE", b"doc", b"$", b"{}", b"more"]),
12202            "-ERR syntax error\r\n"
12203        );
12204        assert_eq!(
12205            f.run(&[b"JSON.MERGE", b"gone", b"$.a", b"1"]),
12206            "-ERR new objects must be created at the root\r\n"
12207        );
12208        f.run(&[b"SET", b"str", b"x"]);
12209        assert_eq!(
12210            f.run(&[b"JSON.MERGE", b"str", b"$", b"1"]),
12211            "-Existing key has wrong Redis type\r\n"
12212        );
12213    }
12214
12215    /// A descent is the one path that matches a value and something inside that
12216    /// same value, and the inner merge has to survive the outer one.
12217    #[test]
12218    fn a_merge_down_a_descent_keeps_what_the_inner_match_did() {
12219        let mut f = Fixture::new();
12220        f.run(&[b"JSON.SET", b"doc", b"$", br#"{"a":{"b":1},"c":[2]}"#]);
12221        assert_eq!(
12222            f.run(&[b"JSON.MERGE", b"doc", b"$..*", br#"{"m":1}"#]),
12223            "+OK\r\n"
12224        );
12225        // `a`, `a.b`, `c` and `c[0]` all match. `a.b` is merged first and `a` is
12226        // merged onto the result, so the `{"m":1}` written into `a.b` is still
12227        // there. Doing it the other way round would leave `{"a":{"b":1,"m":1}}`.
12228        assert_eq!(
12229            f.run(&[b"JSON.GET", b"doc", b"$"]),
12230            bulk(r#"[{"a":{"b":{"m":1},"m":1},"c":{"m":1}}]"#).as_str()
12231        );
12232
12233        // A deletion down the same path, which is the case where the inner
12234        // merge empties the object the outer one then copies.
12235        f.run(&[b"JSON.SET", b"doc", b"$", br#"{"a":{"b":1},"c":[2]}"#]);
12236        assert_eq!(
12237            f.run(&[b"JSON.MERGE", b"doc", b"$..*", br#"{"a":null}"#]),
12238            "+OK\r\n"
12239        );
12240        assert_eq!(
12241            f.run(&[b"JSON.GET", b"doc", b"$"]),
12242            bulk(r#"[{"a":{"b":{}},"c":{}}]"#).as_str()
12243        );
12244    }
12245
12246    /// A filter is a selector like any other, so every command that takes a path
12247    /// takes one, reads and writes alike.
12248    #[test]
12249    fn a_filter_path_reads_and_writes_the_members_it_keeps() {
12250        let mut f = Fixture::new();
12251        let doc = br#"{"book":[{"t":"a","p":8},{"t":"b","p":13},{"t":"c","p":9}],"cap":10}"#;
12252        f.run(&[b"JSON.SET", b"doc", b"$", doc]);
12253
12254        assert_eq!(
12255            f.run(&[b"JSON.GET", b"doc", b"$.book[?(@.p < 10)].t"]),
12256            bulk(r#"["a","c"]"#).as_str()
12257        );
12258        // `$` inside the expression is the document, so a member can be measured
12259        // against something that is not inside it.
12260        assert_eq!(
12261            f.run(&[b"JSON.GET", b"doc", b"$.book[?(@.p < $.cap)].t"]),
12262            bulk(r#"["a","c"]"#).as_str()
12263        );
12264        // The legacy syntax takes one too, and answers the first match.
12265        assert_eq!(
12266            f.run(&[b"JSON.GET", b"doc", b"book[?(@.p < 10)].t"]),
12267            bulk(r#""a""#).as_str()
12268        );
12269        assert_eq!(
12270            f.run(&[b"JSON.TYPE", b"doc", b"$.book[?(@.p > 10)]"]),
12271            "*1\r\n$6\r\nobject\r\n"
12272        );
12273
12274        // A write goes through it as far as a value that is already there. A
12275        // field that is not there yet has nowhere definite to go, which is the
12276        // same refusal a wildcard gets.
12277        assert_eq!(
12278            f.run(&[b"JSON.NUMINCRBY", b"doc", b"$.book[?(@.p < 10)].p", b"1"]),
12279            bulk("[9,10]").as_str()
12280        );
12281        assert_eq!(
12282            f.run(&[b"JSON.SET", b"doc", b"$.book[?(@.p == 13)].t", br#""B""#]),
12283            "+OK\r\n"
12284        );
12285        assert_eq!(
12286            f.run(&[b"JSON.SET", b"doc", b"$.book[?(@.p == 13)].n", b"1"]),
12287            "-Err wrong static path\r\n"
12288        );
12289        assert_eq!(
12290            f.run(&[b"JSON.DEL", b"doc", b"$.book[?(@.p > 9)]"]),
12291            ":2\r\n"
12292        );
12293        assert_eq!(
12294            f.run(&[b"JSON.GET", b"doc", b"$"]),
12295            bulk(r#"[{"cap":10,"book":[{"p":9,"t":"a"}]}]"#).as_str()
12296        );
12297
12298        // A path that does not parse is refused before the document is read, so
12299        // a key that is not there answers the same way.
12300        assert!(
12301            f.run(&[b"JSON.GET", b"doc", b"$.book[?(@.p <)]"])
12302                .starts_with("-ERR")
12303        );
12304        assert!(
12305            f.run(&[b"JSON.GET", b"nokey", b"$.book[?(@.p <)]"])
12306                .starts_with("-ERR")
12307        );
12308    }
12309
12310    /// The operators past the comparisons, over the wire rather than in the
12311    /// parser's own tests, so that a client can reach all of them.
12312    #[test]
12313    fn a_filter_takes_the_membership_operators_and_the_methods_too() {
12314        let mut f = Fixture::new();
12315        let doc = br#"{"box":[{"t":"a","n":[1,2],"g":"x"},{"t":"b","n":[9],"g":"y"}]}"#;
12316        f.run(&[b"JSON.SET", b"doc", b"$", doc]);
12317
12318        for (path, want) in [
12319            (&b"$.box[?(@.g in [\"x\"])].t"[..], r#"["a"]"#),
12320            (b"$.box[?(@.g nin [\"x\"])].t", r#"["b"]"#),
12321            (b"$.box[?(@.n anyof [2,3])].t", r#"["a"]"#),
12322            (b"$.box[?(@.n subsetof [1,2,3])].t", r#"["a"]"#),
12323            (b"$.box[?(@.n size 2)].t", r#"["a"]"#),
12324            (b"$.box[?(@.n empty false)].t", r#"["a","b"]"#),
12325            (b"$.box[?(@.n.length() == 1)].t", r#"["b"]"#),
12326            (b"$.box[?(@.n.sum() > 5)].t", r#"["b"]"#),
12327            (b"$.box[?(@.n[0] + 1 == 2)].t", r#"["a"]"#),
12328            (b"$.box[?(@~ size 3)].t", r#"["a","b"]"#),
12329            (b"$.box[?(@.n~)].t", "[]"),
12330            (b"$.box[?(@.n sizeof 2)].t", r#"["a"]"#),
12331            (b"$.box[?(-@.n[0] == -9)].t", r#"["b"]"#),
12332            (b"$.box[?(1 in @.n)].t", r#"["a"]"#),
12333            (b"$.box[?(\"g\" in @~)].t", r#"["a","b"]"#),
12334        ] {
12335            assert_eq!(f.run(&[b"JSON.GET", b"doc", path]), bulk(want).as_str());
12336        }
12337
12338        // A write goes through one of these the same way it goes through a
12339        // comparison.
12340        assert_eq!(
12341            f.run(&[b"JSON.SET", b"doc", b"$.box[?(@.n size 1)].g", br#""z""#]),
12342            "+OK\r\n"
12343        );
12344        assert_eq!(
12345            f.run(&[b"JSON.GET", b"doc", b"$.box[?(@.g == \"z\")].t"]),
12346            bulk(r#"["b"]"#).as_str()
12347        );
12348    }
12349
12350    /// D-41. RedisJSON refuses this one, and which document it refuses is
12351    /// decided by how it happens to hold an array of numbers.
12352    #[test]
12353    fn a_merge_onto_a_number_inside_an_array_is_a_merge_and_not_an_error() {
12354        let mut f = Fixture::new();
12355        f.run(&[b"JSON.SET", b"doc", b"$", br#"{"a":[1,2]}"#]);
12356        assert_eq!(
12357            f.run(&[b"JSON.MERGE", b"doc", b"$.a[0]", br#"{"x":1}"#]),
12358            "+OK\r\n"
12359        );
12360        assert_eq!(
12361            f.run(&[b"JSON.GET", b"doc", b"$"]),
12362            bulk(r#"[{"a":[{"x":1},2]}]"#).as_str()
12363        );
12364        // The same document with one element that is not an integer is the one
12365        // RedisJSON is happy with, and it goes the same way here.
12366        f.run(&[b"JSON.SET", b"doc", b"$", br#"{"a":[1,"s"]}"#]);
12367        assert_eq!(
12368            f.run(&[b"JSON.MERGE", b"doc", b"$.a[0]", br#"{"x":1}"#]),
12369            "+OK\r\n"
12370        );
12371        assert_eq!(
12372            f.run(&[b"JSON.GET", b"doc", b"$"]),
12373            bulk(r#"[{"a":[{"x":1},"s"]}]"#).as_str()
12374        );
12375    }
12376
12377    /// `JSON.MSET` checks what it can before it writes anything and skips the
12378    /// one thing it cannot, which is a path with nowhere to put its value.
12379    #[test]
12380    fn an_mset_writes_every_triple_it_can_and_checks_the_rest_up_front() {
12381        let mut f = Fixture::new();
12382        assert_eq!(
12383            f.run(&[b"JSON.MSET", b"a", b"$", b"1", b"b", b"$", b"2"]),
12384            "+OK\r\n"
12385        );
12386        assert_eq!(f.run(&[b"JSON.GET", b"a", b"$"]), bulk("[1]").as_str());
12387        assert_eq!(f.run(&[b"JSON.GET", b"b", b"$"]), bulk("[2]").as_str());
12388
12389        // A repeated key takes the last write.
12390        assert_eq!(
12391            f.run(&[b"JSON.MSET", b"a", b"$", b"3", b"a", b"$", b"4"]),
12392            "+OK\r\n"
12393        );
12394        assert_eq!(f.run(&[b"JSON.GET", b"a", b"$"]), bulk("[4]").as_str());
12395
12396        // A triple whose path names nowhere is skipped, the others are still
12397        // written and the reply turns into a nil. Both ways round, because a
12398        // loop that gave up at the first skip would agree with this on one
12399        // order and not on the other.
12400        f.run(&[b"JSON.SET", b"a", b"$", br#"{"n":1}"#]);
12401        assert_eq!(
12402            f.run(&[b"JSON.MSET", b"a", b"$.no.deep", b"9", b"b", b"$", b"5"]),
12403            "$-1\r\n"
12404        );
12405        assert_eq!(f.run(&[b"JSON.GET", b"b", b"$"]), bulk("[5]").as_str());
12406        assert_eq!(
12407            f.run(&[b"JSON.MSET", b"b", b"$", b"6", b"a", b"$.no.deep", b"9"]),
12408            "$-1\r\n"
12409        );
12410        assert_eq!(f.run(&[b"JSON.GET", b"b", b"$"]), bulk("[6]").as_str());
12411
12412        // A value that is not JSON, a key holding something else and a path
12413        // that would have to create a document below its own root are all
12414        // checked before anything is written, so the good triple next to them
12415        // does not happen either.
12416        f.run(&[b"SET", b"str", b"x"]);
12417        assert_eq!(
12418            f.run(&[b"JSON.MSET", b"a", b"$.n", b"7", b"b", b"$", b"notjson"]),
12419            "-this is not the start of a value, at byte 0 of the JSON text\r\n"
12420        );
12421        assert_eq!(
12422            f.run(&[b"JSON.MSET", b"a", b"$.n", b"7", b"str", b"$", b"1"]),
12423            "-Existing key has wrong Redis type\r\n"
12424        );
12425        assert_eq!(
12426            f.run(&[b"JSON.MSET", b"a", b"$.n", b"7", b"gone", b"$.x", b"1"]),
12427            "-ERR new objects must be created at the root\r\n"
12428        );
12429        assert_eq!(f.run(&[b"JSON.GET", b"a", b"$.n"]), bulk("[1]").as_str());
12430
12431        // The two errors a path can be are checked up front as well, so the
12432        // triple before them is not written either. A wildcard that matched
12433        // nothing has nowhere to invent, and an index that is not in the array
12434        // is out of range, and both of them stop the whole command.
12435        assert_eq!(
12436            f.run(&[b"JSON.MSET", b"b", b"$", b"8", b"a", b"$.no.*", b"9"]),
12437            "-Err wrong static path\r\n"
12438        );
12439        assert_eq!(
12440            f.run(&[b"JSON.MSET", b"b", b"$", b"8", b"a", b"$[0]", b"9"]),
12441            "-ERR array index out of range\r\n"
12442        );
12443        assert_eq!(f.run(&[b"JSON.GET", b"b", b"$"]), bulk("[6]").as_str());
12444
12445        // Every triple is worked out against the keyspace as the command found
12446        // it, so a second triple on the same key does not see the first one and
12447        // the last write is the one that stays.
12448        f.run(&[b"JSON.SET", b"c", b"$", br#"{"n":1}"#]);
12449        assert_eq!(
12450            f.run(&[b"JSON.MSET", b"c", b"$", br#"{"n":2}"#, b"c", b"$.n", b"3"]),
12451            "+OK\r\n"
12452        );
12453        assert_eq!(
12454            f.run(&[b"JSON.GET", b"c", b"$"]),
12455            bulk(r#"[{"n":3}]"#).as_str()
12456        );
12457
12458        // An argument count that is not a run of key, path and value is the
12459        // arity error rather than a syntax one.
12460        assert_eq!(
12461            f.run(&[b"JSON.MSET", b"a", b"$", b"1", b"b"]),
12462            "-ERR wrong number of arguments for 'json.mset' command\r\n"
12463        );
12464    }
12465
12466    /// `JSON.RESP` hands back RESP types, and the marker element is what tells
12467    /// an empty array and an empty object apart.
12468    #[test]
12469    fn json_resp_answers_the_document_as_resp_types() {
12470        let mut f = Fixture::new();
12471        f.run(&[b"JSON.SET", b"doc", b"$", br#"{"a":1,"b":[2,"c"]}"#]);
12472        assert_eq!(
12473            f.run(&[b"JSON.RESP", b"doc"]),
12474            "*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"
12475        );
12476        // A JSONPath wraps the same answer in one more array.
12477        assert_eq!(
12478            f.run(&[b"JSON.RESP", b"doc", b"$.b"]),
12479            "*1\r\n*3\r\n+[\r\n:2\r\n$1\r\nc\r\n"
12480        );
12481
12482        f.run(&[
12483            b"JSON.SET",
12484            b"doc",
12485            b"$",
12486            br#"{"f":2.5,"t":true,"z":null,"e":[],"o":{}}"#,
12487        ]);
12488        assert_eq!(f.run(&[b"JSON.RESP", b"doc", b".e"]), "*1\r\n+[\r\n");
12489        assert_eq!(f.run(&[b"JSON.RESP", b"doc", b".o"]), "*1\r\n+{\r\n");
12490        // A double goes out as its text, so a client reads the same digits
12491        // `JSON.GET` would have given it.
12492        assert_eq!(f.run(&[b"JSON.RESP", b"doc", b".f"]), bulk("2.5").as_str());
12493        assert_eq!(f.run(&[b"JSON.RESP", b"doc", b".t"]), "+true\r\n");
12494        assert_eq!(f.run(&[b"JSON.RESP", b"doc", b".z"]), "$-1\r\n");
12495
12496        // A missing legacy path is an error, a missing JSONPath is an empty
12497        // array, and a key that is not there is a nil on either.
12498        assert_eq!(
12499            f.run(&[b"JSON.RESP", b"doc", b".nope"]),
12500            "-ERR Path does not exist\r\n"
12501        );
12502        assert_eq!(f.run(&[b"JSON.RESP", b"doc", b"$.nope"]), "*0\r\n");
12503        assert_eq!(f.run(&[b"JSON.RESP", b"gone"]), "$-1\r\n");
12504        assert_eq!(f.run(&[b"JSON.RESP", b"gone", b"$"]), "$-1\r\n");
12505    }
12506
12507    /// `JSON.DEBUG` answers a byte count that is this encoding's, so the test
12508    /// pins the shapes and that the two syntaxes agree rather than a number
12509    /// read off another server. That is D-42.
12510    #[test]
12511    fn json_debug_answers_a_byte_count_and_its_own_help() {
12512        let mut f = Fixture::new();
12513        f.run(&[b"JSON.SET", b"doc", b"$", br#"{"a":[1,2],"s":"hello"}"#]);
12514        let one = f.run(&[b"JSON.DEBUG", b"MEMORY", b"doc", b".s"]);
12515        assert!(one.starts_with(':'), "{one}");
12516        assert_eq!(
12517            f.run(&[b"JSON.DEBUG", b"memory", b"doc", b"$.s"]),
12518            format!("*1\r\n{one}")
12519        );
12520        let whole = f.run(&[b"JSON.DEBUG", b"MEMORY", b"doc"]);
12521        assert!(whole.starts_with(':') && whole.len() > one.len(), "{whole}");
12522
12523        // A key that is not there is a zero on a legacy path and an empty set
12524        // on a JSONPath, which is the one reader here that does not answer nil
12525        // for it.
12526        assert_eq!(f.run(&[b"JSON.DEBUG", b"MEMORY", b"gone"]), ":0\r\n");
12527        assert_eq!(f.run(&[b"JSON.DEBUG", b"MEMORY", b"gone", b"$"]), "*0\r\n");
12528        assert_eq!(
12529            f.run(&[b"JSON.DEBUG", b"MEMORY", b"doc", b".nope"]),
12530            "-ERR Path does not exist\r\n"
12531        );
12532        assert_eq!(
12533            f.run(&[b"JSON.DEBUG", b"MEMORY", b"doc", b"$.nope"]),
12534            "*0\r\n"
12535        );
12536
12537        assert_eq!(
12538            f.run(&[b"JSON.DEBUG", b"HELP"]),
12539            "*2\r\n$42\r\nMEMORY <key> [path] - reports memory usage\r\n\
12540             $34\r\nHELP                - this message\r\n"
12541        );
12542        assert_eq!(
12543            f.run(&[b"JSON.DEBUG", b"NOPE"]),
12544            "-ERR unknown subcommand - try `JSON.DEBUG HELP`\r\n"
12545        );
12546        assert_eq!(
12547            f.run(&[b"JSON.DEBUG", b"MEMORY"]),
12548            "-ERR wrong number of arguments for 'json.debug' command\r\n"
12549        );
12550    }
12551
12552    // ---------------------------------------------------------------- vector
12553
12554    /// The first `VADD` fixes the dimension and every one after it has to
12555    /// agree, because there is no create command to say it earlier.
12556    #[test]
12557    fn the_first_vadd_decides_how_wide_the_set_is() {
12558        let mut f = Fixture::new();
12559        assert_eq!(
12560            f.run(&[b"VADD", b"v", b"VALUES", b"2", b"1", b"0", b"east"]),
12561            ":1\r\n"
12562        );
12563        assert_eq!(f.run(&[b"VDIM", b"v"]), ":2\r\n");
12564        assert_eq!(f.run(&[b"VCARD", b"v"]), ":1\r\n");
12565        // A second vector under the same name replaces it and says so with a
12566        // zero, so an ingest can count what it created.
12567        assert_eq!(
12568            f.run(&[b"VADD", b"v", b"VALUES", b"2", b"0", b"1", b"east"]),
12569            ":0\r\n"
12570        );
12571        assert_eq!(f.run(&[b"VCARD", b"v"]), ":1\r\n");
12572        // Three dimensions into a two dimensional set names both numbers, since
12573        // a client that gets this wrong needs to know which end is which.
12574        assert_eq!(
12575            f.run(&[b"VADD", b"v", b"VALUES", b"3", b"1", b"0", b"0", b"up"]),
12576            "-ERR Vector dimension mismatch - got 3 but set has 2\r\n"
12577        );
12578        // A vector of zeros has no direction, and it is taken anyway and comes
12579        // back as the origin, because that is what a real server does with it.
12580        assert_eq!(
12581            f.run(&[b"VADD", b"v", b"VALUES", b"2", b"0", b"0", b"nowhere"]),
12582            ":1\r\n"
12583        );
12584        assert_eq!(
12585            f.run(&[b"VEMB", b"v", b"nowhere"]),
12586            "*2\r\n$1\r\n0\r\n$1\r\n0\r\n"
12587        );
12588        // A set is made with one quantisation and keeps it, and a `VADD` that
12589        // names another is refused. Naming none names `Q8`, which is why this
12590        // set is a `Q8` one.
12591        assert_eq!(
12592            f.run(&[b"VADD", b"v", b"VALUES", b"2", b"1", b"1", b"other", b"BIN"]),
12593            "-ERR asked quantization mismatch with existing vector set\r\n"
12594        );
12595        // Nothing above created a key, and a set that never took a vector has
12596        // no dimension to report.
12597        assert_eq!(f.run(&[b"EXISTS", b"fresh"]), ":0\r\n");
12598        assert_eq!(f.run(&[b"VDIM", b"fresh"]), "-ERR key does not exist\r\n");
12599        assert_eq!(f.run(&[b"VCARD", b"fresh"]), ":0\r\n");
12600    }
12601
12602    /// What a client sent comes back out, and what a client asked for is a
12603    /// similarity and not the distance underneath it.
12604    #[test]
12605    fn vemb_gives_back_the_vector_and_vsim_gives_back_a_similarity() {
12606        let mut f = Fixture::new();
12607        f.run(&[b"VADD", b"v", b"VALUES", b"2", b"3", b"4", b"a"]);
12608        // The set stored the direction and the length is multiplied back on the
12609        // way out, so this is `3 4` and not `0.6 0.8`. It is not quite `3 4`
12610        // either, because nobody named a quantisation and that means `Q8`: the
12611        // wider coordinate lands on a code exactly and the other one does not.
12612        // Both numbers are a real server's answers for the same input.
12613        assert_eq!(
12614            f.run(&[b"VEMB", b"v", b"a"]),
12615            "*2\r\n$17\r\n2.992125988006592\r\n$1\r\n4\r\n"
12616        );
12617        // NOQUANT is the way to ask for what went in to come back out.
12618        f.run(&[b"VADD", b"n", b"VALUES", b"2", b"3", b"4", b"a", b"NOQUANT"]);
12619        assert_eq!(
12620            f.run(&[b"VEMB", b"n", b"a"]),
12621            "*2\r\n$1\r\n3\r\n$1\r\n4\r\n"
12622        );
12623        // BIN keeps the signs and nothing else, and does not multiply the
12624        // length back on, since a sign has no length in it to scale.
12625        f.run(&[b"VADD", b"b", b"VALUES", b"2", b"3", b"-4", b"a", b"BIN"]);
12626        assert_eq!(
12627            f.run(&[b"VEMB", b"b", b"a"]),
12628            "*2\r\n$1\r\n1\r\n$2\r\n-1\r\n"
12629        );
12630        assert_eq!(f.run(&[b"VEMB", b"v", b"nobody"]), "*-1\r\n");
12631        assert_eq!(f.run(&[b"VEMB", b"nokey", b"a"]), "*-1\r\n");
12632
12633        // On the axes, where the unit vector is exact and so is the dot
12634        // product, both ends of the scale come out exact: the same direction is
12635        // 1 and the opposite one is 0, with a right angle at a half.
12636        let mut f = Fixture::new();
12637        f.run(&[b"VADD", b"v", b"VALUES", b"2", b"3", b"0", b"a"]);
12638        f.run(&[b"VADD", b"v", b"VALUES", b"2", b"-1", b"0", b"opposite"]);
12639        f.run(&[b"VADD", b"v", b"VALUES", b"2", b"0", b"7", b"across"]);
12640        assert_eq!(
12641            f.run(&[b"VSIM", b"v", b"VALUES", b"2", b"2", b"0", b"WITHSCORES"]),
12642            "*6\r\n$1\r\na\r\n$1\r\n1\r\n$6\r\nacross\r\n$3\r\n0.5\r\n\
12643             $8\r\nopposite\r\n$1\r\n0\r\n"
12644        );
12645        // A search from an element leaves that element out, since it is always
12646        // its own nearest neighbour.
12647        assert_eq!(
12648            f.run(&[b"VSIM", b"v", b"ELE", b"a"]),
12649            "*2\r\n$6\r\nacross\r\n$8\r\nopposite\r\n"
12650        );
12651        // An element that is not there is an empty answer and not an error,
12652        // which is what a missing key gives too.
12653        assert_eq!(f.run(&[b"VSIM", b"v", b"ELE", b"nobody"]), "*0\r\n");
12654        assert_eq!(f.run(&[b"VSIM", b"nokey", b"ELE", b"a"]), "*0\r\n");
12655        // COUNT bounds it and TRUTH reads every vector rather than the codes,
12656        // which has to agree with the index on a set this small.
12657        assert_eq!(
12658            f.run(&[b"VSIM", b"v", b"ELE", b"a", b"COUNT", b"1"]),
12659            "*1\r\n$6\r\nacross\r\n"
12660        );
12661        assert_eq!(
12662            f.run(&[b"VSIM", b"v", b"ELE", b"a", b"TRUTH"]),
12663            "*2\r\n$6\r\nacross\r\n$8\r\nopposite\r\n"
12664        );
12665        // EF widens how much of the index is read and does not change how many
12666        // answers come back, so a wide search still returns what COUNT asked
12667        // for.
12668        assert_eq!(
12669            f.run(&[b"VSIM", b"v", b"ELE", b"a", b"COUNT", b"1", b"EF", b"500"]),
12670            "*1\r\n$6\r\nacross\r\n"
12671        );
12672
12673        // On RESP3 a scored search is a map, which is what the vector set
12674        // module replies and is not what ZRANGE does here.
12675        let mut g = Fixture::new();
12676        g.run(&[b"HELLO", b"3"]);
12677        g.run(&[b"VADD", b"v", b"VALUES", b"2", b"1", b"0", b"east"]);
12678        assert_eq!(
12679            g.run(&[b"VSIM", b"v", b"VALUES", b"2", b"1", b"0", b"WITHSCORES"]),
12680            "%1\r\n$4\r\neast\r\n,1\r\n"
12681        );
12682    }
12683
12684    /// The attribute pair, and the one reply that means two things.
12685    #[test]
12686    fn an_attribute_is_bytes_and_an_empty_one_takes_it_off() {
12687        let mut f = Fixture::new();
12688        f.run(&[b"VADD", b"v", b"VALUES", b"2", b"1", b"0", b"east"]);
12689        assert_eq!(f.run(&[b"VGETATTR", b"v", b"east"]), "$-1\r\n");
12690        assert_eq!(f.run(&[b"VSETATTR", b"v", b"east", b"{\"k\":1}"]), ":1\r\n");
12691        assert_eq!(f.run(&[b"VGETATTR", b"v", b"east"]), "$7\r\n{\"k\":1}\r\n");
12692        // Not parsed as JSON, because nothing reads into it yet and refusing a
12693        // write for a rule nothing enforces would be the wrong trade.
12694        assert_eq!(f.run(&[b"VSETATTR", b"v", b"east", b"not json"]), ":1\r\n");
12695        assert_eq!(f.run(&[b"VGETATTR", b"v", b"east"]), "$8\r\nnot json\r\n");
12696        // An empty string clears it, which is Redis's spelling of the removal.
12697        assert_eq!(f.run(&[b"VSETATTR", b"v", b"east", b""]), ":1\r\n");
12698        assert_eq!(f.run(&[b"VGETATTR", b"v", b"east"]), "$-1\r\n");
12699        // An element that is not there answers zero rather than being created,
12700        // since an attribute with no vector under it is not a thing this holds.
12701        assert_eq!(f.run(&[b"VSETATTR", b"v", b"nobody", b"{}"]), ":0\r\n");
12702        assert_eq!(f.run(&[b"VSETATTR", b"nokey", b"east", b"{}"]), ":0\r\n");
12703        assert_eq!(f.run(&[b"EXISTS", b"nokey"]), ":0\r\n");
12704        // A null for an element with no attribute and a null for one that is
12705        // not there. VISMEMBER is how a client tells the two apart.
12706        assert_eq!(f.run(&[b"VGETATTR", b"v", b"nobody"]), "$-1\r\n");
12707        assert_eq!(f.run(&[b"VISMEMBER", b"v", b"east"]), ":1\r\n");
12708        assert_eq!(f.run(&[b"VISMEMBER", b"v", b"nobody"]), ":0\r\n");
12709        assert_eq!(f.run(&[b"VISMEMBER", b"nokey", b"east"]), ":0\r\n");
12710
12711        // WITHATTRIBS carries it alongside the answers.
12712        f.run(&[b"VSETATTR", b"v", b"east", b"{\"k\":1}"]);
12713        f.run(&[b"VADD", b"v", b"VALUES", b"2", b"0", b"1", b"north"]);
12714        assert_eq!(
12715            f.run(&[b"VSIM", b"v", b"VALUES", b"2", b"1", b"0", b"WITHATTRIBS"]),
12716            "*4\r\n$4\r\neast\r\n$7\r\n{\"k\":1}\r\n$5\r\nnorth\r\n$-1\r\n"
12717        );
12718    }
12719
12720    /// The slot a removed element had is reused, and nothing that was beside it
12721    /// comes back with the next element to get it.
12722    #[test]
12723    fn vrem_takes_the_attribute_with_it() {
12724        let mut f = Fixture::new();
12725        f.run(&[b"VADD", b"v", b"VALUES", b"2", b"1", b"0", b"east"]);
12726        f.run(&[b"VSETATTR", b"v", b"east", b"{\"k\":1}"]);
12727        assert_eq!(f.run(&[b"VREM", b"v", b"east"]), ":1\r\n");
12728        assert_eq!(f.run(&[b"VREM", b"v", b"east"]), ":0\r\n");
12729        assert_eq!(f.run(&[b"VREM", b"nokey", b"east"]), ":0\r\n");
12730        // The key went with the last element, the way every other collection
12731        // here works.
12732        assert_eq!(f.run(&[b"EXISTS", b"v"]), ":0\r\n");
12733
12734        // The next element is given the slot the removed one had, and it comes
12735        // with no attribute on it.
12736        f.run(&[b"VADD", b"v", b"VALUES", b"2", b"1", b"0", b"east"]);
12737        f.run(&[b"VSETATTR", b"v", b"east", b"{\"k\":1}"]);
12738        f.run(&[b"VADD", b"v", b"VALUES", b"2", b"0", b"1", b"north"]);
12739        f.run(&[b"VREM", b"v", b"east"]);
12740        f.run(&[b"VADD", b"v", b"VALUES", b"2", b"1", b"1", b"between"]);
12741        assert_eq!(f.run(&[b"VGETATTR", b"v", b"between"]), "$-1\r\n");
12742    }
12743
12744    /// `VINFO` says what the index is before it says anything a client could
12745    /// mistake for a graph.
12746    #[test]
12747    fn vinfo_says_partition_first() {
12748        let mut f = Fixture::new();
12749        f.run(&[
12750            b"VADD", b"v", b"VALUES", b"2", b"1", b"0", b"east", b"M", b"32",
12751        ]);
12752        f.run(&[b"VSETATTR", b"v", b"east", b"{}"]);
12753        let info = f.run(&[b"VINFO", b"v"]);
12754        assert!(info.starts_with("*24\r\n$10\r\nindex-type\r\n$9\r\npartition\r\n"));
12755        // What the client asked for and not what happened to the tuning, which
12756        // is `10` section 7: M is recorded and changes nothing.
12757        assert!(info.contains("$6\r\nhnsw-m\r\n:32\r\n"), "{info}");
12758        assert!(info.contains("$10\r\nvector-dim\r\n:2\r\n"), "{info}");
12759        assert!(info.contains("$16\r\nattributes-count\r\n:1\r\n"), "{info}");
12760        // Nobody named a quantisation, so this set is a `Q8` one and every
12761        // element in it is stored that way.
12762        assert!(
12763            info.contains("$10\r\nquant-type\r\n$4\r\nint8\r\n"),
12764            "{info}"
12765        );
12766        let mut f = Fixture::new();
12767        f.run(&[b"VADD", b"v", b"VALUES", b"2", b"0", b"1", b"north", b"BIN"]);
12768        assert!(
12769            f.run(&[b"VINFO", b"v"])
12770                .contains("$10\r\nquant-type\r\n$3\r\nbin\r\n")
12771        );
12772        assert_eq!(f.run(&[b"VINFO", b"nokey"]), "$-1\r\n");
12773    }
12774
12775    /// A set to read ranges of names out of.
12776    fn named() -> Fixture {
12777        let mut f = Fixture::new();
12778        for (i, name) in ["alpha", "beta", "gamma", "delta", "epsilon"]
12779            .iter()
12780            .enumerate()
12781        {
12782            let x = (i + 1).to_string();
12783            f.run(&[
12784                b"VADD",
12785                b"r",
12786                b"VALUES",
12787                b"2",
12788                x.as_bytes(),
12789                b"1",
12790                name.as_bytes(),
12791            ]);
12792        }
12793        f
12794    }
12795
12796    /// `VRANGE` reads the names in the order bytes come in and pays no
12797    /// attention to where the vectors point.
12798    #[test]
12799    fn vrange_walks_the_names_and_not_the_vectors() {
12800        let mut f = named();
12801        assert_eq!(
12802            f.run(&[b"VRANGE", b"r", b"-", b"+"]),
12803            "*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"
12804        );
12805        assert_eq!(
12806            f.run(&[b"VRANGE", b"r", b"[a", b"[d"]),
12807            "*2\r\n$5\r\nalpha\r\n$4\r\nbeta\r\n",
12808            "the high end is a name and not a prefix, so delta is past it"
12809        );
12810        assert_eq!(
12811            f.run(&[b"VRANGE", b"r", b"(alpha", b"(gamma"]),
12812            "*3\r\n$4\r\nbeta\r\n$5\r\ndelta\r\n$7\r\nepsilon\r\n"
12813        );
12814        assert_eq!(
12815            f.run(&[b"VRANGE", b"r", b"[beta", b"[beta"]),
12816            "*1\r\n$4\r\nbeta\r\n"
12817        );
12818        assert_eq!(f.run(&[b"VRANGE", b"r", b"[z", b"+"]), "*0\r\n");
12819        // Bytes and not letters, so an upper case name sorts before every lower
12820        // case one rather than beside its own spelling.
12821        f.run(&[b"VADD", b"r", b"VALUES", b"2", b"1", b"1", b"Beta"]);
12822        assert_eq!(
12823            f.run(&[b"VRANGE", b"r", b"-", b"[beta"]),
12824            "*3\r\n$4\r\nBeta\r\n$5\r\nalpha\r\n$4\r\nbeta\r\n"
12825        );
12826        assert_eq!(f.run(&[b"VRANGE", b"nokey", b"-", b"+"]), "*0\r\n");
12827    }
12828
12829    /// The count cuts the answer after the range is decided, and zero is not
12830    /// the same as leaving it out.
12831    #[test]
12832    fn a_vrange_count_of_zero_asks_for_nothing() {
12833        let mut f = named();
12834        assert_eq!(
12835            f.run(&[b"VRANGE", b"r", b"-", b"+", b"2"]),
12836            "*2\r\n$5\r\nalpha\r\n$4\r\nbeta\r\n"
12837        );
12838        assert_eq!(f.run(&[b"VRANGE", b"r", b"-", b"+", b"0"]), "*0\r\n");
12839        assert!(
12840            f.run(&[b"VRANGE", b"r", b"-", b"+", b"-1"])
12841                .starts_with("*5\r\n"),
12842            "a negative count is no limit at all"
12843        );
12844    }
12845
12846    /// Both ends are read before either is placed, and the count is read before
12847    /// either end.
12848    #[test]
12849    fn vrange_says_which_end_it_could_not_read() {
12850        let mut f = named();
12851        assert_eq!(
12852            f.run(&[b"VRANGE", b"r", b"x", b"y"]),
12853            "-ERR invalid start range format\r\n"
12854        );
12855        assert_eq!(
12856            f.run(&[b"VRANGE", b"r", b"+", b"x"]),
12857            "-ERR invalid end range format\r\n",
12858            "the high end is spelled wrong, which is worth saying before the \
12859             low end being on the wrong side"
12860        );
12861        assert_eq!(
12862            f.run(&[b"VRANGE", b"r", b"+", b"-"]),
12863            "-ERR '-' can only be used as first argument, '+' only as second\r\n"
12864        );
12865        // A bracket with nothing after it is not the empty name here, though an
12866        // element really can be called that.
12867        assert_eq!(
12868            f.run(&[b"VRANGE", b"r", b"[", b"+"]),
12869            "-ERR invalid start range format\r\n"
12870        );
12871        assert_eq!(
12872            f.run(&[b"VRANGE", b"r", b"x", b"+", b"z"]),
12873            "-ERR invalid COUNT value\r\n"
12874        );
12875        assert_eq!(
12876            f.run(&[b"VRANGE", b"r", b"-", b"+", b"2", b"extra"]),
12877            "-ERR wrong number of arguments for 'VRANGE' command\r\n"
12878        );
12879        f.run(&[b"SET", b"s", b"x"]);
12880        assert!(
12881            f.run(&[b"VRANGE", b"s", b"-", b"+"])
12882                .starts_with("-WRONGTYPE")
12883        );
12884    }
12885
12886    /// The option that asks for something this index does not have says so
12887    /// rather than doing something else quietly.
12888    #[test]
12889    fn reduce_is_refused_and_not_ignored() {
12890        let mut f = Fixture::new();
12891        let reduce = f.run(&[
12892            b"VADD", b"v", b"REDUCE", b"1", b"VALUES", b"2", b"1", b"0", b"east",
12893        ]);
12894        assert!(
12895            reduce.starts_with("-ERR REDUCE is not supported."),
12896            "{reduce}"
12897        );
12898        assert_eq!(f.run(&[b"EXISTS", b"v"]), ":0\r\n");
12899    }
12900
12901    /// A filtered search answers with the nearest elements that match, and an
12902    /// expression that is not one is an error before the key is looked at.
12903    #[test]
12904    fn vsim_filter_reads_the_attributes() {
12905        let mut f = Fixture::new();
12906        for (name, x, y, attr) in [
12907            ("a", "1", "0", r#"{"lang":"en","year":1999}"#),
12908            ("b", "9", "1", r#"{"lang":"fr","year":2005}"#),
12909            ("c", "8", "2", r#"{"lang":"en","year":1970}"#),
12910            ("d", "7", "3", r#"{"lang":"en","year":2020}"#),
12911        ] {
12912            f.run(&[
12913                b"VADD",
12914                b"v",
12915                b"VALUES",
12916                b"2",
12917                x.as_bytes(),
12918                y.as_bytes(),
12919                name.as_bytes(),
12920                b"SETATTR",
12921                attr.as_bytes(),
12922            ]);
12923        }
12924        // `b` is the nearest to the query and is the one the filter drops, so
12925        // this is the answer a filter applied afterwards would have got wrong.
12926        assert_eq!(
12927            f.run(&[
12928                b"VSIM",
12929                b"v",
12930                b"VALUES",
12931                b"2",
12932                b"9",
12933                b"1",
12934                b"COUNT",
12935                b"2",
12936                b"FILTER",
12937                b".lang == \"en\"",
12938            ]),
12939            "*2\r\n$1\r\na\r\n$1\r\nc\r\n"
12940        );
12941        // A number is compared as a number, and the two halves of an `and` both
12942        // have to hold.
12943        assert_eq!(
12944            f.run(&[
12945                b"VSIM",
12946                b"v",
12947                b"VALUES",
12948                b"2",
12949                b"9",
12950                b"1",
12951                b"FILTER",
12952                b".lang == 'en' and .year > 1980",
12953            ]),
12954            "*2\r\n$1\r\na\r\n$1\r\nd\r\n"
12955        );
12956        // A list, and a field an element does not have.
12957        assert_eq!(
12958            f.run(&[
12959                b"VSIM",
12960                b"v",
12961                b"VALUES",
12962                b"2",
12963                b"9",
12964                b"1",
12965                b"FILTER",
12966                b".lang in ['fr', 'de']",
12967            ]),
12968            "*1\r\n$1\r\nb\r\n"
12969        );
12970        assert_eq!(
12971            f.run(&[
12972                b"VSIM",
12973                b"v",
12974                b"VALUES",
12975                b"2",
12976                b"9",
12977                b"1",
12978                b"FILTER",
12979                b".rating > 3"
12980            ]),
12981            "*0\r\n"
12982        );
12983        // TRUTH measures every vector, and the filter still decides which ones
12984        // are measured.
12985        assert_eq!(
12986            f.run(&[
12987                b"VSIM",
12988                b"v",
12989                b"VALUES",
12990                b"2",
12991                b"9",
12992                b"1",
12993                b"TRUTH",
12994                b"FILTER",
12995                b".year < 1980",
12996            ]),
12997            "*1\r\n$1\r\nc\r\n"
12998        );
12999        // VSETATTR moves an element in and out of a filter, which means the tag
13000        // beside its code was rewritten and not just the string.
13001        f.run(&[b"VSETATTR", b"v", b"b", r#"{"lang":"en"}"#.as_bytes()]);
13002        assert_eq!(
13003            f.run(&[
13004                b"VSIM",
13005                b"v",
13006                b"VALUES",
13007                b"2",
13008                b"9",
13009                b"1",
13010                b"COUNT",
13011                b"1",
13012                b"FILTER",
13013                b".lang == \"en\"",
13014            ]),
13015            "*1\r\n$1\r\nb\r\n"
13016        );
13017        // And a VADD that replaces the vector keeps the attribute and the tag,
13018        // which is the same rewrite from the other end.
13019        f.run(&[b"VADD", b"v", b"VALUES", b"2", b"9", b"2", b"b"]);
13020        assert_eq!(
13021            f.run(&[
13022                b"VSIM",
13023                b"v",
13024                b"VALUES",
13025                b"2",
13026                b"9",
13027                b"1",
13028                b"COUNT",
13029                b"1",
13030                b"FILTER",
13031                b".lang == \"en\"",
13032            ]),
13033            "*1\r\n$1\r\nb\r\n"
13034        );
13035
13036        // The expression is parsed before the key is read, so a bad one is an
13037        // error whether or not the key is there.
13038        let bad = f.run(&[b"VSIM", b"nokey", b"ELE", b"e", b"FILTER", b".k =="]);
13039        assert_eq!(bad, "-ERR invalid FILTER expression\r\n");
13040        assert_eq!(
13041            f.run(&[b"VSIM", b"v", b"ELE", b"a", b"FILTER", b"junk"]),
13042            "-ERR invalid FILTER expression\r\n"
13043        );
13044        // FILTER-EF raises the effort rather than capping it, and zero is
13045        // Redis's word for no limit, so neither is an error.
13046        assert_eq!(
13047            f.run(&[
13048                b"VSIM",
13049                b"v",
13050                b"VALUES",
13051                b"2",
13052                b"9",
13053                b"1",
13054                b"COUNT",
13055                b"1",
13056                b"FILTER-EF",
13057                b"500",
13058                b"FILTER",
13059                b".lang == 'en'",
13060            ]),
13061            "*1\r\n$1\r\nb\r\n"
13062        );
13063        assert_eq!(
13064            f.run(&[
13065                b"VSIM",
13066                b"v",
13067                b"VALUES",
13068                b"2",
13069                b"9",
13070                b"1",
13071                b"COUNT",
13072                b"1",
13073                b"FILTER-EF",
13074                b"0"
13075            ]),
13076            "*1\r\n$1\r\nb\r\n"
13077        );
13078        assert_eq!(
13079            f.run(&[
13080                b"VSIM",
13081                b"v",
13082                b"VALUES",
13083                b"2",
13084                b"9",
13085                b"1",
13086                b"FILTER-EF",
13087                b"lots"
13088            ]),
13089            "-ERR EF must be a positive integer\r\n"
13090        );
13091    }
13092
13093    /// A vector set key is a key, so the keyspace owns it the way it owns every
13094    /// other one and none of those commands know what is inside it.
13095    #[test]
13096    fn the_keyspace_sees_a_vector_set_key_like_any_other() {
13097        let mut f = Fixture::new();
13098        f.run(&[b"VADD", b"v", b"VALUES", b"2", b"1", b"0", b"east"]);
13099        assert_eq!(f.run(&[b"TYPE", b"v"]), "+vectorset\r\n");
13100        assert_eq!(f.run(&[b"EXISTS", b"v"]), ":1\r\n");
13101        assert_eq!(f.run(&[b"OBJECT", b"ENCODING", b"v"]), "$6\r\nrabitq\r\n");
13102        assert_eq!(f.run(&[b"KEYS", b"*"]), "*1\r\n$1\r\nv\r\n");
13103        assert_eq!(f.run(&[b"DBSIZE"]), ":1\r\n");
13104        assert_eq!(f.run(&[b"EXPIRE", b"v", b"100"]), ":1\r\n");
13105        assert_eq!(f.run(&[b"TTL", b"v"]), ":100\r\n");
13106        assert_eq!(f.run(&[b"PERSIST", b"v"]), ":1\r\n");
13107        assert_eq!(f.run(&[b"DEL", b"v"]), ":1\r\n");
13108        assert_eq!(f.run(&[b"EXISTS", b"v"]), ":0\r\n");
13109
13110        // And the wrong type is the wrong type in both directions.
13111        f.run(&[b"SET", b"s", b"1"]);
13112        assert_eq!(
13113            f.run(&[b"VADD", b"s", b"VALUES", b"2", b"1", b"0", b"east"]),
13114            "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n"
13115        );
13116        assert_eq!(
13117            f.run(&[b"VCARD", b"s"]),
13118            "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n"
13119        );
13120        f.run(&[b"VADD", b"v", b"VALUES", b"2", b"1", b"0", b"east"]);
13121        assert_eq!(
13122            f.run(&[b"GET", b"v"]),
13123            "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n"
13124        );
13125        // A graph and a vector set share the escape in the record tag and are
13126        // still two different types, which is the case the tag alone cannot
13127        // decide.
13128        f.run(&[b"G.NADD", b"social", b"ada"]);
13129        assert_eq!(
13130            f.run(&[b"VCARD", b"social"]),
13131            "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n"
13132        );
13133        assert_eq!(
13134            f.run(&[b"G.NGET", b"v", b"ada"]),
13135            "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n"
13136        );
13137    }
13138
13139    /// `VRANDMEMBER` is `SRANDMEMBER` over the element names, in both of its
13140    /// shapes, off the database's own generator.
13141    #[test]
13142    fn vrandmember_has_the_two_shapes_srandmember_has() {
13143        let mut f = Fixture::new();
13144        for (i, name) in [&b"a"[..], b"b", b"c"].iter().enumerate() {
13145            let x = (i + 1).to_string();
13146            f.run(&[b"VADD", b"v", b"VALUES", b"2", x.as_bytes(), b"1", name]);
13147        }
13148        // One element is a bulk string and not an array of one.
13149        let one = f.run(&[b"VRANDMEMBER", b"v"]);
13150        assert!(one.starts_with("$1\r\n"), "{one}");
13151        // A positive count is distinct and stops at the size of the set.
13152        let mut all = f.run(&[b"VRANDMEMBER", b"v", b"9"]);
13153        assert!(all.starts_with("*3\r\n"), "{all}");
13154        for name in ["a", "b", "c"] {
13155            assert!(all.contains(name), "{all} is missing {name}");
13156        }
13157        all = f.run(&[b"VRANDMEMBER", b"v", b"2"]);
13158        assert!(all.starts_with("*2\r\n"), "{all}");
13159        // A negative one draws that many and allows repeats.
13160        let many = f.run(&[b"VRANDMEMBER", b"v", b"-5"]);
13161        assert!(many.starts_with("*5\r\n"), "{many}");
13162        // A key that is not there answers the shape that was asked for.
13163        assert_eq!(f.run(&[b"VRANDMEMBER", b"nokey"]), "$-1\r\n");
13164        assert_eq!(f.run(&[b"VRANDMEMBER", b"nokey", b"3"]), "*0\r\n");
13165    }
13166
13167    /// `VLINKS` answers about the index that is here rather than the graph that
13168    /// is not, which is D-2.
13169    #[test]
13170    fn vlinks_reports_one_layer_of_partition_neighbours() {
13171        let mut f = Fixture::new();
13172        f.run(&[b"VADD", b"v", b"VALUES", b"2", b"1", b"0", b"east"]);
13173        f.run(&[b"VADD", b"v", b"VALUES", b"2", b"0", b"1", b"north"]);
13174        // One layer deep, because the index is one layer deep, so a client
13175        // walking layers gets a short list and not a shape it cannot parse.
13176        assert_eq!(
13177            f.run(&[b"VLINKS", b"v", b"east"]),
13178            "*1\r\n*1\r\n$5\r\nnorth\r\n"
13179        );
13180        assert_eq!(
13181            f.run(&[b"VLINKS", b"v", b"east", b"WITHSCORES"]),
13182            "*1\r\n*2\r\n$5\r\nnorth\r\n$3\r\n0.5\r\n"
13183        );
13184        assert_eq!(f.run(&[b"VLINKS", b"v", b"nobody"]), "*-1\r\n");
13185        assert_eq!(f.run(&[b"VLINKS", b"nokey", b"east"]), "*-1\r\n");
13186    }
13187
13188    /// A vector arrives either as digits or as bytes, and the two have to mean
13189    /// the same thing.
13190    #[test]
13191    fn fp32_and_values_are_the_same_vector() {
13192        let mut f = Fixture::new();
13193        let mut blob = Vec::new();
13194        for x in [3.0f32, 4.0] {
13195            blob.extend_from_slice(&x.to_le_bytes());
13196        }
13197        assert_eq!(f.run(&[b"VADD", b"v", b"FP32", &blob, b"a"]), ":1\r\n");
13198        assert_eq!(f.run(&[b"VDIM", b"v"]), ":2\r\n");
13199        assert_eq!(
13200            f.run(&[b"VEMB", b"v", b"a"]),
13201            "*2\r\n$17\r\n2.992125988006592\r\n$1\r\n4\r\n"
13202        );
13203        // RAW is the stored bytes and the numbers that turn them back into the
13204        // client's vector, which for `Q8` is a code a coordinate, the length the
13205        // vector arrived with and the scale the codes are measured against. The
13206        // name of the form is a simple string, which is a real server's shape,
13207        // and all four of these are a real server's answers.
13208        assert_eq!(
13209            f.run(&[b"VEMB", b"v", b"a", b"RAW"]),
13210            "*4\r\n+int8\r\n$2\r\n_\x7f\r\n$1\r\n5\r\n$17\r\n0.800000011920929\r\n"
13211        );
13212        // A blob that is not a whole number of floats is not a vector.
13213        assert_eq!(
13214            f.run(&[b"VADD", b"w", b"FP32", b"abc", b"a"]),
13215            "-ERR invalid vector specification\r\n"
13216        );
13217        // Neither is a count that promises more than arrived.
13218        assert_eq!(
13219            f.run(&[b"VADD", b"w", b"VALUES", b"4", b"1", b"0", b"a"]),
13220            "-ERR syntax error\r\n"
13221        );
13222        assert_eq!(f.run(&[b"EXISTS", b"w"]), ":0\r\n");
13223    }
13224
13225    // ----------------------------------------------------------------- bloom
13226
13227    /// The filter a client gets when it does not describe one, and the two
13228    /// answers an add can give.
13229    #[test]
13230    fn bf_add_makes_the_filter_and_says_whether_it_was_new() {
13231        let mut f = Fixture::new();
13232        assert_eq!(f.run(&[b"BF.ADD", b"b", b"hello"]), ":1\r\n");
13233        assert_eq!(f.run(&[b"BF.ADD", b"b", b"hello"]), ":0\r\n");
13234        assert_eq!(f.run(&[b"BF.EXISTS", b"b", b"hello"]), ":1\r\n");
13235        assert_eq!(f.run(&[b"BF.EXISTS", b"b", b"never"]), ":0\r\n");
13236        assert_eq!(f.run(&[b"BF.CARD", b"b"]), ":1\r\n");
13237        // The defaults are the module's configs and not anything the command
13238        // said, which is 100 entries at a hundredth and a growth of 2.
13239        assert_eq!(
13240            f.run(&[b"BF.INFO", b"b"]),
13241            "*10\r\n+Capacity\r\n:100\r\n+Size\r\n:240\r\n\
13242             +Number of filters\r\n:1\r\n+Number of items inserted\r\n:1\r\n\
13243             +Expansion rate\r\n:2\r\n"
13244        );
13245        assert_eq!(f.run(&[b"TYPE", b"b"]), "+MBbloom--\r\n");
13246        assert_eq!(f.run(&[b"OBJECT", b"ENCODING", b"b"]), "$3\r\nraw\r\n");
13247        // A key that is not there has no filter to report on, and answers two
13248        // different ways about it depending on which command asked.
13249        assert_eq!(f.run(&[b"BF.CARD", b"gone"]), ":0\r\n");
13250        assert_eq!(f.run(&[b"BF.INFO", b"gone"]), "-ERR not found\r\n");
13251    }
13252
13253    /// `BF.EXISTS` on a key holding something else answers a miss, and
13254    /// everything else in the family answers `WRONGTYPE`.
13255    ///
13256    /// The two halves of a check and set disagree about what that key is, which
13257    /// is the module's behaviour and not a decision taken here.
13258    #[test]
13259    fn a_wrong_type_is_a_miss_to_the_two_that_only_read_bits() {
13260        let mut f = Fixture::new();
13261        f.run(&[b"SET", b"s", b"text"]);
13262        assert_eq!(f.run(&[b"BF.EXISTS", b"s", b"x"]), ":0\r\n");
13263        assert_eq!(f.run(&[b"BF.MEXISTS", b"s", b"x"]), "*1\r\n:0\r\n");
13264        for cmd in [
13265            vec![&b"BF.ADD"[..], b"s", b"x"],
13266            vec![&b"BF.MADD"[..], b"s", b"x"],
13267            vec![&b"BF.CARD"[..], b"s"],
13268            vec![&b"BF.INFO"[..], b"s"],
13269            vec![&b"BF.DEBUG"[..], b"s"],
13270            vec![&b"BF.SCANDUMP"[..], b"s", b"0"],
13271        ] {
13272            let name = String::from_utf8_lossy(cmd[0]).into_owned();
13273            assert!(f.run(&cmd).starts_with("-WRONGTYPE"), "{name}");
13274        }
13275        // The arguments are read before the key is, so a reserve with a bad
13276        // error rate complains about the rate and never learns about the string.
13277        assert_eq!(
13278            f.run(&[b"BF.RESERVE", b"s", b"abc", b"10"]),
13279            "-ERR bad error rate\r\n"
13280        );
13281        assert!(
13282            f.run(&[b"BF.RESERVE", b"s", b"0.01", b"10"])
13283                .starts_with("-WRONGTYPE")
13284        );
13285    }
13286
13287    /// A chain grows by its expansion factor and each link is half as wrong as
13288    /// the one before, which is what makes the whole filter hold its rate.
13289    #[test]
13290    fn a_full_filter_grows_a_link_and_a_fixed_one_says_no() {
13291        let mut f = Fixture::new();
13292        assert_eq!(f.run(&[b"BF.RESERVE", b"g", b"0.01", b"10"]), "+OK\r\n");
13293        for i in 0..10u32 {
13294            assert_eq!(
13295                f.run(&[b"BF.ADD", b"g", i.to_string().as_bytes()]),
13296                ":1\r\n"
13297            );
13298        }
13299        assert_eq!(f.run(&[b"BF.INFO", b"g", b"FILTERS"]), "*1\r\n:1\r\n");
13300        assert_eq!(f.run(&[b"BF.ADD", b"g", b"11"]), ":1\r\n");
13301        assert_eq!(f.run(&[b"BF.INFO", b"g", b"filters"]), "*1\r\n:2\r\n");
13302        // Capacity is the sum of every link and not the number that was asked
13303        // for, so it is 10 and then 10 plus 20.
13304        assert_eq!(f.run(&[b"BF.INFO", b"g", b"CAPACITY"]), "*1\r\n:30\r\n");
13305        assert_eq!(
13306            f.run(&[b"BF.DEBUG", b"g"]),
13307            "*3\r\n$7\r\nsize:11\r\n\
13308             $71\r\nbytes:16 bits:128 hashes:8 hashwidth:64 capacity:10 size:10 ratio:0.005\r\n\
13309             $71\r\nbytes:32 bits:256 hashes:9 hashwidth:64 capacity:20 size:1 ratio:0.0025\r\n"
13310        );
13311
13312        // The same filter told not to grow fills instead.
13313        assert_eq!(
13314            f.run(&[b"BF.RESERVE", b"n", b"0.01", b"2", b"NONSCALING"]),
13315            "+OK\r\n"
13316        );
13317        assert_eq!(f.run(&[b"BF.ADD", b"n", b"a"]), ":1\r\n");
13318        assert_eq!(f.run(&[b"BF.ADD", b"n", b"b"]), ":1\r\n");
13319        assert_eq!(
13320            f.run(&[b"BF.ADD", b"n", b"c"]),
13321            "-ERR non scaling filter is full\r\n"
13322        );
13323        // And an item that is already in it still answers, because membership
13324        // is checked before fullness.
13325        assert_eq!(f.run(&[b"BF.ADD", b"n", b"a"]), ":0\r\n");
13326        // A filter that will not grow has no expansion rate to report, in
13327        // either of the two spellings that make one.
13328        assert_eq!(f.run(&[b"BF.INFO", b"n", b"EXPANSION"]), "*1\r\n$-1\r\n");
13329        f.run(&[b"BF.RESERVE", b"z", b"0.01", b"2", b"EXPANSION", b"0"]);
13330        assert_eq!(f.run(&[b"BF.INFO", b"z", b"EXPANSION"]), "*1\r\n$-1\r\n");
13331        // Asking for both at once is refused, which is one of the module's
13332        // errors that carries no prefix at all.
13333        assert_eq!(
13334            f.run(&[
13335                b"BF.RESERVE",
13336                b"q",
13337                b"0.01",
13338                b"2",
13339                b"NONSCALING",
13340                b"EXPANSION",
13341                b"2"
13342            ]),
13343            "-Nonscaling filters cannot expand\r\n"
13344        );
13345    }
13346
13347    /// A multi add stops where the filter did, so the reply can be shorter than
13348    /// the argument list.
13349    #[test]
13350    fn madd_truncates_its_reply_at_the_item_that_did_not_fit() {
13351        let mut f = Fixture::new();
13352        f.run(&[b"BF.RESERVE", b"n", b"0.01", b"2", b"NONSCALING"]);
13353        assert_eq!(
13354            f.run(&[b"BF.MADD", b"n", b"a", b"b", b"c", b"d"]),
13355            "*3\r\n:1\r\n:1\r\n-ERR non scaling filter is full\r\n"
13356        );
13357        assert_eq!(
13358            f.run(&[b"BF.MEXISTS", b"n", b"a", b"c"]),
13359            "*2\r\n:1\r\n:0\r\n"
13360        );
13361    }
13362
13363    /// `BF.INSERT` describes a filter and fills it in one command, with its own
13364    /// spelling of every complaint.
13365    #[test]
13366    fn insert_is_a_reserve_and_a_madd_with_different_errors() {
13367        let mut f = Fixture::new();
13368        assert_eq!(
13369            f.run(&[
13370                b"BF.INSERT",
13371                b"i",
13372                b"CAPACITY",
13373                b"50",
13374                b"ERROR",
13375                b"0.001",
13376                b"ITEMS",
13377                b"a",
13378                b"b"
13379            ]),
13380            "*2\r\n:1\r\n:1\r\n"
13381        );
13382        assert_eq!(f.run(&[b"BF.INFO", b"i", b"CAPACITY"]), "*1\r\n:50\r\n");
13383        // NOCREATE is the only way to add without making the key.
13384        assert_eq!(
13385            f.run(&[b"BF.INSERT", b"gone", b"NOCREATE", b"ITEMS", b"a"]),
13386            "-ERR not found\r\n"
13387        );
13388        assert_eq!(f.run(&[b"EXISTS", b"gone"]), ":0\r\n");
13389        // The same mistakes as BF.RESERVE, in the sentences this command uses
13390        // for them, and one sentence where BF.RESERVE has two.
13391        assert_eq!(
13392            f.run(&[b"BF.INSERT", b"i", b"CAPACITY", b"abc", b"ITEMS", b"a"]),
13393            "-Bad capacity\r\n"
13394        );
13395        assert_eq!(
13396            f.run(&[b"BF.INSERT", b"i", b"ERROR", b"2", b"ITEMS", b"a"]),
13397            "-Bad error rate\r\n"
13398        );
13399        assert_eq!(
13400            f.run(&[b"BF.INSERT", b"i", b"EXPANSION", b"99999", b"ITEMS", b"a"]),
13401            "-Bad expansion\r\n"
13402        );
13403        // An option is matched on its first letter and not on the word, so a
13404        // token nobody meant as an option is one anyway if it starts with the
13405        // right letter. NOSUCHTHING is NONSCALING here, and the filter it
13406        // builds says so.
13407        assert_eq!(
13408            f.run(&[b"BF.INSERT", b"ns", b"NOSUCHTHING", b"ITEMS", b"a"]),
13409            "*1\r\n:1\r\n"
13410        );
13411        assert_eq!(f.run(&[b"BF.INFO", b"ns", b"EXPANSION"]), "*1\r\n$-1\r\n");
13412        // Only E and N need a second look, one for ERROR against EXPANSION and
13413        // the other for NOCREATE against NONSCALING, and both stop as soon as
13414        // they can tell the two apart.
13415        assert_eq!(
13416            f.run(&[b"BF.INSERT", b"e1", b"E", b"4", b"ITEMS", b"a"]),
13417            "*1\r\n:1\r\n"
13418        );
13419        assert_eq!(f.run(&[b"BF.INFO", b"e1", b"EXPANSION"]), "*1\r\n:4\r\n");
13420        assert_eq!(
13421            f.run(&[b"BF.INSERT", b"e2", b"ER", b"0.5", b"ITEMS", b"a"]),
13422            "*1\r\n:1\r\n"
13423        );
13424        assert_eq!(
13425            f.run(&[b"BF.INSERT", b"gone", b"NOC", b"ITEMS", b"a"]),
13426            "-ERR not found\r\n"
13427        );
13428        // A letter that starts nothing is the one case that is refused.
13429        assert_eq!(
13430            f.run(&[b"BF.INSERT", b"i", b"ZZZ", b"ITEMS", b"a"]),
13431            "-Unknown argument received\r\n"
13432        );
13433        // Everything after ITEMS is an item, even when it spells an option.
13434        assert_eq!(
13435            f.run(&[b"BF.INSERT", b"i", b"ITEMS", b"NOCREATE"]),
13436            "*1\r\n:1\r\n"
13437        );
13438        // And ITEMS with nothing after it is the same as leaving it out.
13439        assert!(
13440            f.run(&[b"BF.INSERT", b"i", b"ITEMS"])
13441                .contains("wrong number of arguments")
13442        );
13443    }
13444
13445    /// A filter dumped a chunk at a time and put back into another key is the
13446    /// same filter.
13447    #[test]
13448    fn a_dump_replays_into_a_filter_that_answers_the_same() {
13449        let mut f = Fixture::new();
13450        f.run(&[b"BF.RESERVE", b"src", b"0.01", b"10"]);
13451        for i in 0..25u32 {
13452            f.run(&[b"BF.ADD", b"src", i.to_string().as_bytes()]);
13453        }
13454        assert_eq!(f.run(&[b"BF.INFO", b"src", b"FILTERS"]), "*1\r\n:2\r\n");
13455
13456        // Iterator zero asks for the header and every one after it is a running
13457        // byte offset, and a chunk never spans two links.
13458        let mut iter = b"0".to_vec();
13459        let mut chunks = 0;
13460        loop {
13461            let raw = f.raw(&[b"BF.SCANDUMP", b"src", &iter]);
13462            let text = String::from_utf8_lossy(&raw).into_owned();
13463            let next = text
13464                .split("\r\n")
13465                .nth(1)
13466                .and_then(|n| n.strip_prefix(':'))
13467                .expect("a two element reply of an iterator and a chunk")
13468                .to_owned();
13469            let body = &raw[raw.iter().position(|&b| b == b'$').expect("a bulk chunk")..];
13470            let data = &body[body
13471                .windows(2)
13472                .position(|w| w == b"\r\n")
13473                .expect("a length line")
13474                + 2..body.len() - 2];
13475            if next == "0" {
13476                assert!(data.is_empty(), "the last chunk is empty");
13477                break;
13478            }
13479            let put = f.run(&[b"BF.LOADCHUNK", b"dst", next.as_bytes(), data]);
13480            assert_eq!(put, "+OK\r\n", "loading chunk {chunks}");
13481            iter = next.into_bytes();
13482            chunks += 1;
13483        }
13484        assert_eq!(chunks, 3, "a header and one chunk per link");
13485
13486        assert_eq!(f.run(&[b"BF.INFO", b"dst"]), f.run(&[b"BF.INFO", b"src"]));
13487        assert_eq!(f.run(&[b"BF.DEBUG", b"dst"]), f.run(&[b"BF.DEBUG", b"src"]));
13488        for i in 0..25u32 {
13489            assert_eq!(
13490                f.run(&[b"BF.EXISTS", b"dst", i.to_string().as_bytes()]),
13491                ":1\r\n"
13492            );
13493        }
13494
13495        // A header on top of a filter is refused rather than merged, and so is
13496        // one that no filter wrote.
13497        assert_eq!(
13498            f.run(&[b"BF.LOADCHUNK", b"dst", b"1", b"anything"]),
13499            "-ERR received bad data\r\n"
13500        );
13501        assert_eq!(
13502            f.run(&[b"BF.LOADCHUNK", b"fresh", b"1", b"anything"]),
13503            "-ERR received bad data\r\n"
13504        );
13505        // An offset past the end of the filter names itself.
13506        assert_eq!(
13507            f.run(&[b"BF.LOADCHUNK", b"dst", b"99999", b"x"]),
13508            "-ERR invalid offset - no link found\r\n"
13509        );
13510        assert_eq!(
13511            f.run(&[b"BF.LOADCHUNK", b"dst", b"nope", b"x"]),
13512            "-ERR Second argument must be numeric\r\n"
13513        );
13514        // The same complaint without the prefix on the way out, which is the
13515        // module's inconsistency and not a slip here.
13516        assert_eq!(
13517            f.run(&[b"BF.SCANDUMP", b"src", b"nope"]),
13518            "-Second argument must be numeric\r\n"
13519        );
13520    }
13521
13522    /// The argument checks, which have a sentence each and read numbers the way
13523    /// Redis reads them everywhere else.
13524    #[test]
13525    fn reserve_reads_its_numbers_the_way_string2ll_does() {
13526        let mut f = Fixture::new();
13527        for (args, want) in [
13528            (vec![&b"abc"[..], b"10"], "-ERR bad error rate\r\n"),
13529            (vec![&b"nan"[..], b"10"], "-ERR bad error rate\r\n"),
13530            (
13531                vec![&b"0"[..], b"10"],
13532                "-ERR error rate must be in the range (0.000000, 1.000000)\r\n",
13533            ),
13534            (
13535                vec![&b"1"[..], b"10"],
13536                "-ERR error rate must be in the range (0.000000, 1.000000)\r\n",
13537            ),
13538            (
13539                vec![&b"inf"[..], b"10"],
13540                "-ERR error rate must be in the range (0.000000, 1.000000)\r\n",
13541            ),
13542            (vec![&b"0.01"[..], b"+10"], "-ERR bad capacity\r\n"),
13543            (vec![&b"0.01"[..], b"1e2"], "-ERR bad capacity\r\n"),
13544            (vec![&b"0.01"[..], b"007"], "-ERR bad capacity\r\n"),
13545            (
13546                vec![&b"0.01"[..], b"0"],
13547                "-ERR capacity must be in the range [1, 1073741824]\r\n",
13548            ),
13549            (
13550                vec![&b"0.01"[..], b"1073741825"],
13551                "-ERR capacity must be in the range [1, 1073741824]\r\n",
13552            ),
13553        ] {
13554            let mut cmd = vec![&b"BF.RESERVE"[..], b"k"];
13555            cmd.extend(args.iter().copied());
13556            assert_eq!(f.run(&cmd), want, "{}", String::from_utf8_lossy(args[0]));
13557        }
13558        assert_eq!(
13559            f.run(&[b"BF.RESERVE", b"k", b"0.01", b"10", b"EXPANSION"]),
13560            "-ERR no expansion\r\n"
13561        );
13562        assert_eq!(
13563            f.run(&[b"BF.RESERVE", b"k", b"0.01", b"10", b"EXPANSION", b"abc"]),
13564            "-ERR bad expansion\r\n"
13565        );
13566        assert_eq!(
13567            f.run(&[b"BF.RESERVE", b"k", b"0.01", b"10", b"EXPANSION", b"32769"]),
13568            "-ERR expansion must be in the range [0, 32768]\r\n"
13569        );
13570        // Trailing rubbish after the capacity is ignored rather than refused.
13571        assert_eq!(
13572            f.run(&[b"BF.RESERVE", b"k", b"0.01", b"10", b"junk"]),
13573            "+OK\r\n"
13574        );
13575        assert_eq!(
13576            f.run(&[b"BF.RESERVE", b"k", b"0.01", b"10"]),
13577            "-ERR item exists\r\n"
13578        );
13579        assert_eq!(
13580            f.run(&[b"BF.INFO", b"k", b"nosuchfield"]),
13581            "-Invalid information value\r\n"
13582        );
13583        assert!(
13584            f.run(&[b"BF.INFO", b"k", b"CAPACITY", b"SIZE"])
13585                .contains("wrong number of arguments")
13586        );
13587    }
13588
13589    /// The RESP3 shapes, which are where this family differs most from RESP2.
13590    #[test]
13591    fn the_bloom_family_answers_in_resp3_spelling_too() {
13592        let mut f = Fixture::new();
13593        f.out.set_proto(Proto::Resp3);
13594        assert_eq!(f.run(&[b"BF.ADD", b"b", b"a"]), "#t\r\n");
13595        assert_eq!(f.run(&[b"BF.ADD", b"b", b"a"]), "#f\r\n");
13596        assert_eq!(f.run(&[b"BF.MADD", b"b", b"a", b"c"]), "*2\r\n#f\r\n#t\r\n");
13597        assert_eq!(f.run(&[b"BF.EXISTS", b"b", b"a"]), "#t\r\n");
13598        assert_eq!(
13599            f.run(&[b"BF.MEXISTS", b"b", b"a", b"z"]),
13600            "*2\r\n#t\r\n#f\r\n"
13601        );
13602        // The count stays an integer, because it counts rather than answers.
13603        assert_eq!(f.run(&[b"BF.CARD", b"b"]), ":2\r\n");
13604        assert_eq!(
13605            f.run(&[b"BF.INFO", b"b"]),
13606            "%5\r\n+Capacity\r\n:100\r\n+Size\r\n:240\r\n\
13607             +Number of filters\r\n:1\r\n+Number of items inserted\r\n:2\r\n\
13608             +Expansion rate\r\n:2\r\n"
13609        );
13610        // One field is a map of one here and a bare array of one on RESP2, so
13611        // this is the reply where the two protocols carry different facts.
13612        assert_eq!(
13613            f.run(&[b"BF.INFO", b"b", b"CAPACITY"]),
13614            "%1\r\n+Capacity\r\n:100\r\n"
13615        );
13616    }
13617
13618    // ---------------------------------------------------------------- cuckoo
13619
13620    /// A dump header, which is the four counts and the three widths a filter
13621    /// writes in front of its fingerprints.
13622    ///
13623    /// Written by hand rather than taken from a `CF.SCANDUMP`, because what the
13624    /// tests below want out of it is the states a filter cannot be put into
13625    /// from the wire.
13626    fn cf_header(
13627        items: u64,
13628        buckets: u64,
13629        deletes: u64,
13630        filters: u64,
13631        geometry: [u16; 3],
13632    ) -> Vec<u8> {
13633        let mut out = Vec::with_capacity(38);
13634        for n in [items, buckets, deletes, filters] {
13635            out.extend_from_slice(&n.to_le_bytes());
13636        }
13637        for n in geometry {
13638            out.extend_from_slice(&n.to_le_bytes());
13639        }
13640        out
13641    }
13642
13643    /// The filter a client gets when it does not describe one, and the thing a
13644    /// cuckoo filter does that a Bloom filter cannot, which is count copies and
13645    /// take them out again.
13646    #[test]
13647    fn cf_add_makes_the_filter_and_counts_the_copies() {
13648        let mut f = Fixture::new();
13649        assert_eq!(f.run(&[b"CF.ADD", b"d", b"hello"]), ":1\r\n");
13650        assert_eq!(f.run(&[b"CF.ADD", b"d", b"hello"]), ":1\r\n");
13651        assert_eq!(f.run(&[b"CF.COUNT", b"d", b"hello"]), ":2\r\n");
13652        // The NX form is the one that looks first, which is why it is a command
13653        // of its own rather than an option.
13654        assert_eq!(f.run(&[b"CF.ADDNX", b"d", b"hello"]), ":0\r\n");
13655        assert_eq!(f.run(&[b"CF.ADDNX", b"d", b"other"]), ":1\r\n");
13656        assert_eq!(f.run(&[b"CF.EXISTS", b"d", b"hello"]), ":1\r\n");
13657        assert_eq!(f.run(&[b"CF.EXISTS", b"d", b"no"]), ":0\r\n");
13658        assert_eq!(
13659            f.run(&[b"CF.MEXISTS", b"d", b"hello", b"no"]),
13660            "*2\r\n:1\r\n:0\r\n"
13661        );
13662        // The defaults are the module's configs: 1024 entries over buckets of
13663        // two, twenty kicks and a chain that grows by one.
13664        assert_eq!(
13665            f.run(&[b"CF.INFO", b"d"]),
13666            "*16\r\n+Size\r\n:1080\r\n+Number of buckets\r\n:512\r\n\
13667             +Number of filters\r\n:1\r\n+Number of items inserted\r\n:3\r\n\
13668             +Number of items deleted\r\n:0\r\n+Bucket size\r\n:2\r\n\
13669             +Expansion rate\r\n:1\r\n+Max iterations\r\n:20\r\n"
13670        );
13671        assert_eq!(
13672            f.run(&[b"CF.DEBUG", b"d"]),
13673            "$79\r\nbktsize:2 buckets:512 items:3 deletes:0 filters:1 \
13674             max_iterations:20 expansion:1\r\n"
13675        );
13676        assert_eq!(f.run(&[b"TYPE", b"d"]), "+MBbloomCF\r\n");
13677        assert_eq!(f.run(&[b"OBJECT", b"ENCODING", b"d"]), "$3\r\nraw\r\n");
13678
13679        // A delete takes one copy, so the same item goes twice and then stops.
13680        assert_eq!(f.run(&[b"CF.DEL", b"d", b"hello"]), ":1\r\n");
13681        assert_eq!(f.run(&[b"CF.COUNT", b"d", b"hello"]), ":1\r\n");
13682        assert_eq!(f.run(&[b"CF.DEL", b"d", b"hello"]), ":1\r\n");
13683        assert_eq!(f.run(&[b"CF.DEL", b"d", b"hello"]), ":0\r\n");
13684        assert_eq!(f.run(&[b"CF.COMPACT", b"d"]), "+OK\r\n");
13685
13686        // A key with no filter under it gets three different sentences and one
13687        // plain miss, depending on which command asked.
13688        assert_eq!(f.run(&[b"CF.INFO", b"gone"]), "-ERR not found\r\n");
13689        assert_eq!(f.run(&[b"CF.DEL", b"gone", b"x"]), "-Not found\r\n");
13690        assert_eq!(
13691            f.run(&[b"CF.COMPACT", b"gone"]),
13692            "-Cuckoo filter was not found\r\n"
13693        );
13694        assert_eq!(f.run(&[b"CF.EXISTS", b"gone", b"x"]), ":0\r\n");
13695        // And `CF.COMPACT` is declared as taking any number of keys and takes
13696        // exactly one, which is the module's own arity being wrong rather than
13697        // this table's.
13698        assert!(
13699            f.run(&[b"CF.COMPACT", b"a", b"b"])
13700                .contains("wrong number of arguments")
13701        );
13702    }
13703
13704    /// The four that only read fingerprints treat a key holding something else
13705    /// as a key with no filter, and everything else answers `WRONGTYPE`.
13706    #[test]
13707    fn a_wrong_type_is_a_miss_to_the_four_that_only_read_fingerprints() {
13708        let mut f = Fixture::new();
13709        f.run(&[b"SET", b"s", b"text"]);
13710        assert_eq!(f.run(&[b"CF.EXISTS", b"s", b"x"]), ":0\r\n");
13711        assert_eq!(f.run(&[b"CF.MEXISTS", b"s", b"x"]), "*1\r\n:0\r\n");
13712        assert_eq!(f.run(&[b"CF.COUNT", b"s", b"x"]), ":0\r\n");
13713        // `CF.DEL` writes and is still in that group, and `CF.COMPACT` writes
13714        // and is declared read only, so neither of the two halves of the family
13715        // is the same set as the flags say.
13716        assert_eq!(f.run(&[b"CF.DEL", b"s", b"x"]), "-Not found\r\n");
13717        assert_eq!(
13718            f.run(&[b"CF.COMPACT", b"s"]),
13719            "-Cuckoo filter was not found\r\n"
13720        );
13721        for cmd in [
13722            vec![&b"CF.ADD"[..], b"s", b"x"],
13723            vec![&b"CF.ADDNX"[..], b"s", b"x"],
13724            vec![&b"CF.INSERT"[..], b"s", b"ITEMS", b"x"],
13725            vec![&b"CF.INSERTNX"[..], b"s", b"ITEMS", b"x"],
13726            vec![&b"CF.INFO"[..], b"s"],
13727            vec![&b"CF.DEBUG"[..], b"s"],
13728            vec![&b"CF.SCANDUMP"[..], b"s", b"0"],
13729            vec![&b"CF.LOADCHUNK"[..], b"s", b"2", b"x"],
13730            vec![&b"CF.RESERVE"[..], b"s", b"64"],
13731        ] {
13732            let name = String::from_utf8_lossy(cmd[0]).into_owned();
13733            assert!(f.run(&cmd).starts_with("-WRONGTYPE"), "{name}");
13734        }
13735    }
13736
13737    /// `CF.RESERVE` reads its options by name in an order of its own, and the
13738    /// first pair with a given name is the only one it looks at.
13739    #[test]
13740    fn reserve_complains_about_its_options_in_the_order_it_looks_for_them() {
13741        let mut f = Fixture::new();
13742        assert_eq!(
13743            f.run(&[
13744                b"CF.RESERVE",
13745                b"r",
13746                b"64",
13747                b"BUCKETSIZE",
13748                b"1",
13749                b"MAXITERATIONS",
13750                b"7",
13751                b"EXPANSION",
13752                b"4"
13753            ]),
13754            "+OK\r\n"
13755        );
13756        assert_eq!(
13757            f.run(&[b"CF.DEBUG", b"r"]),
13758            "$77\r\nbktsize:1 buckets:64 items:0 deletes:0 filters:1 \
13759             max_iterations:7 expansion:4\r\n"
13760        );
13761        assert_eq!(f.run(&[b"CF.RESERVE", b"r", b"64"]), "-ERR item exists\r\n");
13762
13763        assert_eq!(f.run(&[b"CF.RESERVE", b"q", b"abc"]), "-Bad capacity\r\n");
13764        assert_eq!(
13765            f.run(&[b"CF.RESERVE", b"q", b"1"]),
13766            "-Capacity must be in the range [2 * BUCKETSIZE, 1073741824]\r\n"
13767        );
13768        // The range is the bucket size's and not a constant, so a capacity that
13769        // was fine at two slots a bucket is not at four.
13770        assert_eq!(
13771            f.run(&[b"CF.RESERVE", b"q", b"7", b"BUCKETSIZE", b"4"]),
13772            "-Capacity must be in the range [2 * BUCKETSIZE, 1073741824]\r\n"
13773        );
13774        assert_eq!(
13775            f.run(&[b"CF.RESERVE", b"q", b"8", b"BUCKETSIZE", b"4"]),
13776            "+OK\r\n"
13777        );
13778
13779        // The capacity is checked last, so a command that is wrong twice
13780        // answers about the option. Which option it answers about is the order
13781        // the module looks for them in and not the order they were written, so
13782        // a bad kick budget wins over a bad bucket size wherever the two sit.
13783        assert_eq!(
13784            f.run(&[b"CF.RESERVE", b"q2", b"64", b"BUCKETSIZE", b"0"]),
13785            "-BUCKETSIZE: value must be in the range [1, 255]\r\n"
13786        );
13787        assert_eq!(
13788            f.run(&[
13789                b"CF.RESERVE",
13790                b"q2",
13791                b"64",
13792                b"EXPANSION",
13793                b"xx",
13794                b"BUCKETSIZE",
13795                b"0"
13796            ]),
13797            "-BUCKETSIZE: value must be in the range [1, 255]\r\n"
13798        );
13799        assert_eq!(
13800            f.run(&[
13801                b"CF.RESERVE",
13802                b"q2",
13803                b"64",
13804                b"MAXITERATIONS",
13805                b"0",
13806                b"BUCKETSIZE",
13807                b"0"
13808            ]),
13809            "-MAXITERATIONS: value must be in the range [1, 65535]\r\n"
13810        );
13811        // A second pair with a name that has already been read is not looked at
13812        // at all, so this one is a filter with buckets of one rather than an
13813        // error about a bucket size of zero.
13814        assert_eq!(
13815            f.run(&[
13816                b"CF.RESERVE",
13817                b"q3",
13818                b"64",
13819                b"BUCKETSIZE",
13820                b"1",
13821                b"BUCKETSIZE",
13822                b"0"
13823            ]),
13824            "+OK\r\n"
13825        );
13826        // A pair nobody knows is dropped, which is the opposite of what
13827        // `CF.INSERT` does with the same mistake.
13828        assert_eq!(
13829            f.run(&[b"CF.RESERVE", b"q4", b"64", b"NOSUCH", b"9"]),
13830            "+OK\r\n"
13831        );
13832        assert_eq!(
13833            f.run(&[b"CF.DEBUG", b"q4"]),
13834            "$78\r\nbktsize:2 buckets:32 items:0 deletes:0 filters:1 \
13835             max_iterations:20 expansion:1\r\n"
13836        );
13837        // And an option with nothing after it leaves an odd number of them,
13838        // which is an arity error rather than a complaint about the option.
13839        assert!(
13840            f.run(&[b"CF.RESERVE", b"q5", b"64", b"BUCKETSIZE"])
13841                .contains("wrong number of arguments")
13842        );
13843    }
13844
13845    /// `CF.INSERT` is a reserve and a multi add, with a grammar that agrees
13846    /// with `CF.RESERVE` about nothing.
13847    #[test]
13848    fn insert_checks_every_occurrence_and_matches_on_the_first_letter() {
13849        let mut f = Fixture::new();
13850        assert_eq!(
13851            f.run(&[b"CF.INSERT", b"i", b"CAPACITY", b"64", b"ITEMS", b"a", b"b"]),
13852            "*2\r\n:1\r\n:1\r\n"
13853        );
13854        assert_eq!(
13855            f.run(&[b"CF.DEBUG", b"i"]),
13856            "$78\r\nbktsize:2 buckets:32 items:2 deletes:0 filters:1 \
13857             max_iterations:20 expansion:1\r\n"
13858        );
13859        // The NX form has three answers rather than two, which is why it stays
13860        // integers on both protocols.
13861        assert_eq!(
13862            f.run(&[b"CF.INSERTNX", b"i", b"ITEMS", b"a", b"c"]),
13863            "*2\r\n:0\r\n:1\r\n"
13864        );
13865        assert_eq!(
13866            f.run(&[b"CF.INSERT", b"gone", b"NOCREATE", b"ITEMS", b"a"]),
13867            "-ERR not found\r\n"
13868        );
13869        assert_eq!(f.run(&[b"EXISTS", b"gone"]), ":0\r\n");
13870
13871        assert_eq!(
13872            f.run(&[b"CF.INSERT", b"i", b"CAPACITY", b"abc", b"ITEMS", b"a"]),
13873            "-Bad capacity\r\n"
13874        );
13875        // The bucket size cannot be given here, so the range names the config
13876        // that holds it instead of the option `CF.RESERVE` names.
13877        assert_eq!(
13878            f.run(&[b"CF.INSERT", b"i", b"CAPACITY", b"2", b"ITEMS", b"a"]),
13879            "-Capacity must be in the range [cf-bucket-size * 2, 1073741824]\r\n"
13880        );
13881        // Every occurrence is checked, which is where this differs from
13882        // `CF.RESERVE`: the second `CAPACITY` is an error even though the first
13883        // one is the one that would have been used.
13884        assert_eq!(
13885            f.run(&[
13886                b"CF.INSERT",
13887                b"i",
13888                b"CAPACITY",
13889                b"8",
13890                b"CAPACITY",
13891                b"2",
13892                b"ITEMS",
13893                b"a"
13894            ]),
13895            "-Capacity must be in the range [cf-bucket-size * 2, 1073741824]\r\n"
13896        );
13897        // An option is one letter and not a word, so `NOSUCH` is `NOCREATE` and
13898        // `ITEMSXYZ` is `ITEMS`, and only a letter that starts nothing is
13899        // refused.
13900        assert_eq!(
13901            f.run(&[b"CF.INSERT", b"i", b"NOSUCH", b"ITEMS", b"a"]),
13902            "*1\r\n:1\r\n"
13903        );
13904        assert_eq!(
13905            f.run(&[b"CF.INSERT", b"i", b"ITEMSXYZ", b"a"]),
13906            "*1\r\n:1\r\n"
13907        );
13908        assert_eq!(
13909            f.run(&[b"CF.INSERT", b"i", b"ZZZ", b"ITEMS", b"a"]),
13910            "-Unknown argument received\r\n"
13911        );
13912        // Everything after ITEMS is an item, even when it spells an option.
13913        assert_eq!(
13914            f.run(&[b"CF.INSERT", b"i", b"ITEMS", b"NOCREATE"]),
13915            "*1\r\n:1\r\n"
13916        );
13917        // And the two ways of sending no items at all are the same complaint.
13918        assert!(
13919            f.run(&[b"CF.INSERT", b"i", b"ITEMS"])
13920                .contains("wrong number of arguments")
13921        );
13922        assert!(
13923            f.run(&[b"CF.INSERT", b"i", b"CAPACITY"])
13924                .contains("wrong number of arguments")
13925        );
13926    }
13927
13928    /// The two walls a filter can hit, which say different things and are not
13929    /// the same wall.
13930    #[test]
13931    fn a_full_filter_and_one_that_ran_out_of_filters_answer_differently() {
13932        let mut f = Fixture::new();
13933        f.run(&[
13934            b"CF.RESERVE",
13935            b"s",
13936            b"4",
13937            b"BUCKETSIZE",
13938            b"1",
13939            b"EXPANSION",
13940            b"0",
13941        ]);
13942        for i in 0..4u32 {
13943            assert_eq!(
13944                f.run(&[b"CF.ADD", b"s", i.to_string().as_bytes()]),
13945                ":1\r\n"
13946            );
13947        }
13948        assert_eq!(f.run(&[b"CF.ADD", b"s", b"4"]), "-Filter is full\r\n");
13949        assert_eq!(f.run(&[b"CF.ADDNX", b"s", b"zz"]), "-Filter is full\r\n");
13950        // The add commands say it in a sentence and the insert commands say it
13951        // in the array, one value per item, and the array is never short.
13952        assert_eq!(
13953            f.run(&[b"CF.INSERT", b"s", b"ITEMS", b"p", b"q"]),
13954            "*2\r\n:-1\r\n:-1\r\n"
13955        );
13956        assert_eq!(
13957            f.run(&[b"CF.INSERTNX", b"s", b"ITEMS", b"0", b"q"]),
13958            "*2\r\n:0\r\n:-1\r\n"
13959        );
13960
13961        // A chain that is allowed to grow stops for a different reason, and the
13962        // count it stops at is the filter limit rather than the room: this one
13963        // gives up with three slots free. Loading a chain that already has
13964        // every filter it is allowed shows why, since it refuses an item
13965        // straight into an empty one.
13966        let full = cf_header(0, 4, 0, 32, [1, 20, 1]);
13967        assert_eq!(f.run(&[b"CF.LOADCHUNK", b"g", b"1", &full]), "+OK\r\n");
13968        assert_eq!(
13969            f.run(&[b"CF.ADD", b"g", b"q"]),
13970            "-Maximum expansions reached\r\n"
13971        );
13972        assert_eq!(
13973            f.run(&[b"CF.INFO", b"g"]),
13974            "*16\r\n+Size\r\n:680\r\n+Number of buckets\r\n:4\r\n\
13975             +Number of filters\r\n:32\r\n+Number of items inserted\r\n:0\r\n\
13976             +Number of items deleted\r\n:0\r\n+Bucket size\r\n:1\r\n\
13977             +Expansion rate\r\n:1\r\n+Max iterations\r\n:20\r\n"
13978        );
13979    }
13980
13981    /// A filter dumped a chunk at a time and put back under another key is the
13982    /// same filter, and the headers that describe one nobody could build are
13983    /// refused on the way in.
13984    #[test]
13985    fn a_cuckoo_dump_replays_into_a_filter_that_answers_the_same() {
13986        let mut f = Fixture::new();
13987        f.run(&[
13988            b"CF.RESERVE",
13989            b"src",
13990            b"8",
13991            b"BUCKETSIZE",
13992            b"2",
13993            b"EXPANSION",
13994            b"2",
13995        ]);
13996        for i in 0..40u32 {
13997            f.run(&[b"CF.ADD", b"src", i.to_string().as_bytes()]);
13998        }
13999        // Position zero asks for the header and every one after it is a byte
14000        // offset across every filter laid end to end, and the walk ends on a
14001        // zero and a nil rather than an empty chunk.
14002        let mut pos = b"0".to_vec();
14003        let mut chunks = 0;
14004        loop {
14005            let raw = f.raw(&[b"CF.SCANDUMP", b"src", &pos]);
14006            let head = String::from_utf8_lossy(&raw[..raw.len().min(24)]).into_owned();
14007            let next = head
14008                .split("\r\n")
14009                .nth(1)
14010                .and_then(|n| n.strip_prefix(':'))
14011                .expect("a two element reply of a position and a chunk")
14012                .to_owned();
14013            if next == "0" {
14014                assert!(raw.ends_with(b"$-1\r\n"), "the walk ends on a nil");
14015                break;
14016            }
14017            let body = &raw[raw.iter().position(|&b| b == b'$').expect("a bulk chunk")..];
14018            let at = body
14019                .windows(2)
14020                .position(|w| w == b"\r\n")
14021                .expect("a length line")
14022                + 2;
14023            let data = &body[at..body.len() - 2];
14024            assert_eq!(
14025                f.run(&[b"CF.LOADCHUNK", b"dst", next.as_bytes(), data]),
14026                "+OK\r\n",
14027                "loading chunk {chunks}"
14028            );
14029            pos = next.into_bytes();
14030            chunks += 1;
14031        }
14032        assert!(chunks >= 2, "a header and at least one chunk");
14033
14034        assert_eq!(f.run(&[b"CF.INFO", b"dst"]), f.run(&[b"CF.INFO", b"src"]));
14035        assert_eq!(f.run(&[b"CF.DEBUG", b"dst"]), f.run(&[b"CF.DEBUG", b"src"]));
14036        for i in 0..40u32 {
14037            assert_eq!(
14038                f.run(&[b"CF.EXISTS", b"dst", i.to_string().as_bytes()]),
14039                ":1\r\n"
14040            );
14041        }
14042
14043        // A filter with nothing in it hands out no header at all, so a client
14044        // that dumps one has nothing to load back.
14045        f.run(&[b"CF.RESERVE", b"empty", b"4", b"BUCKETSIZE", b"1"]);
14046        assert_eq!(
14047            f.run(&[b"CF.SCANDUMP", b"empty", b"0"]),
14048            "*2\r\n:0\r\n$-1\r\n"
14049        );
14050
14051        // The positions this end will not take, which are not the same set at
14052        // both ends: a dump refuses a negative one and a load takes it as an
14053        // offset and fails to find anything there.
14054        assert_eq!(
14055            f.run(&[b"CF.SCANDUMP", b"src", b"nope"]),
14056            "-Invalid position\r\n"
14057        );
14058        assert_eq!(
14059            f.run(&[b"CF.SCANDUMP", b"src", b"-1"]),
14060            "-Invalid position\r\n"
14061        );
14062        assert_eq!(
14063            f.run(&[b"CF.LOADCHUNK", b"dst", b"0", b"x"]),
14064            "-Invalid position\r\n"
14065        );
14066        assert_eq!(
14067            f.run(&[b"CF.LOADCHUNK", b"dst", b"99999", b"x"]),
14068            "-Couldn't load chunk!\r\n"
14069        );
14070        // A header on top of a filter is refused rather than merged.
14071        let good = cf_header(0, 8, 0, 1, [2, 20, 1]);
14072        assert_eq!(
14073            f.run(&[b"CF.LOADCHUNK", b"dst", b"1", &good]),
14074            "-ERR item exists\r\n"
14075        );
14076        // A chunk that is not the size of a header where a header should have
14077        // been is one sentence, and one that is the size of a header and
14078        // describes a filter nobody could build is another.
14079        assert_eq!(
14080            f.run(&[b"CF.LOADCHUNK", b"n1", b"1", b"short"]),
14081            "-Invalid header\r\n"
14082        );
14083        for (why, bad) in [
14084            ("no filters at all", cf_header(0, 8, 0, 0, [2, 20, 1])),
14085            ("no buckets", cf_header(0, 0, 0, 1, [2, 20, 1])),
14086            (
14087                "a bucket count that is not a power of two",
14088                cf_header(0, 3, 0, 1, [2, 20, 1]),
14089            ),
14090            ("an empty bucket", cf_header(0, 8, 0, 1, [0, 20, 1])),
14091            ("no kicks", cf_header(0, 8, 0, 1, [2, 0, 1])),
14092            (
14093                "a growth nobody could reach",
14094                cf_header(0, 8, 0, 1, [2, 20, 32769]),
14095            ),
14096            (
14097                "a chain that cannot grow and did",
14098                cf_header(0, 8, 0, 2, [2, 20, 0]),
14099            ),
14100            // The count is written in eight bytes and read into two, so a
14101            // number that is a multiple of the second arrives as none.
14102            (
14103                "a filter count that wraps",
14104                cf_header(0, 8, 0, 65_536, [2, 20, 1]),
14105            ),
14106        ] {
14107            assert_eq!(
14108                f.run(&[b"CF.LOADCHUNK", b"bad", b"1", &bad]),
14109                "-Couldn't create filter!\r\n",
14110                "{why}"
14111            );
14112        }
14113    }
14114
14115    /// The RESP3 shapes, which are where this family differs most from RESP2
14116    /// and where one of its answers stops being readable.
14117    #[test]
14118    fn the_cuckoo_family_answers_in_resp3_spelling_too() {
14119        let mut f = Fixture::new();
14120        f.out.set_proto(Proto::Resp3);
14121        assert_eq!(f.run(&[b"CF.ADD", b"c", b"a"]), "#t\r\n");
14122        assert_eq!(f.run(&[b"CF.ADD", b"c", b"a"]), "#t\r\n");
14123        assert_eq!(f.run(&[b"CF.ADDNX", b"c", b"a"]), "#f\r\n");
14124        assert_eq!(f.run(&[b"CF.EXISTS", b"c", b"a"]), "#t\r\n");
14125        assert_eq!(
14126            f.run(&[b"CF.MEXISTS", b"c", b"a", b"z"]),
14127            "*2\r\n#t\r\n#f\r\n"
14128        );
14129        assert_eq!(f.run(&[b"CF.DEL", b"c", b"a"]), "#t\r\n");
14130        assert_eq!(f.run(&[b"CF.DEL", b"c", b"z"]), "#f\r\n");
14131        // The count stays an integer, because it counts rather than answers.
14132        assert_eq!(f.run(&[b"CF.COUNT", b"c", b"a"]), ":1\r\n");
14133        assert_eq!(
14134            f.run(&[b"CF.INFO", b"c"]),
14135            "%8\r\n+Size\r\n:1080\r\n+Number of buckets\r\n:512\r\n\
14136             +Number of filters\r\n:1\r\n+Number of items inserted\r\n:1\r\n\
14137             +Number of items deleted\r\n:1\r\n+Bucket size\r\n:2\r\n\
14138             +Expansion rate\r\n:1\r\n+Max iterations\r\n:20\r\n"
14139        );
14140
14141        // `CF.INSERT` writes a boolean per item here and an integer per item on
14142        // RESP2, and minus one has nowhere to go in a boolean, so a RESP3
14143        // client cannot tell an item that did not fit from one that is already
14144        // there. `CF.INSERTNX` keeps its integers for exactly that reason.
14145        f.run(&[
14146            b"CF.RESERVE",
14147            b"s",
14148            b"4",
14149            b"BUCKETSIZE",
14150            b"1",
14151            b"EXPANSION",
14152            b"0",
14153        ]);
14154        assert_eq!(
14155            f.run(&[
14156                b"CF.INSERT",
14157                b"s",
14158                b"ITEMS",
14159                b"a",
14160                b"b",
14161                b"c",
14162                b"d",
14163                b"e",
14164                b"f"
14165            ]),
14166            "*6\r\n#t\r\n#t\r\n#t\r\n#f\r\n#f\r\n#f\r\n"
14167        );
14168        assert_eq!(
14169            f.run(&[b"CF.INSERTNX", b"s", b"ITEMS", b"a", b"zz"]),
14170            "*2\r\n:0\r\n:-1\r\n"
14171        );
14172        assert_eq!(f.run(&[b"CF.ADD", b"s", b"zzz"]), "-Filter is full\r\n");
14173        // The end of a dump is a nil and not an empty chunk, which is one
14174        // underscore here and a negative length on RESP2.
14175        assert_eq!(f.run(&[b"CF.SCANDUMP", b"c", b"9999"]), "*2\r\n:0\r\n_\r\n");
14176    }
14177
14178    // ------------------------------------------------------------------- cms
14179
14180    /// A sketch is made from either end, and both constructors look at the key
14181    /// before they look at their arguments.
14182    #[test]
14183    fn a_sketch_is_made_from_a_size_or_from_an_error_rate() {
14184        let mut f = Fixture::new();
14185        assert_eq!(f.run(&[b"CMS.INITBYDIM", b"d", b"100", b"5"]), "+OK\r\n");
14186        assert_eq!(
14187            f.run(&[b"CMS.INFO", b"d"]),
14188            "*6\r\n+width\r\n:100\r\n+depth\r\n:5\r\n+count\r\n:0\r\n"
14189        );
14190        assert_eq!(f.run(&[b"TYPE", b"d"]), "+CMSk-TYPE\r\n");
14191        assert_eq!(f.run(&[b"OBJECT", b"ENCODING", b"d"]), "$3\r\nraw\r\n");
14192        // Two over the error rounded up, and the log of the probability over the
14193        // log of a half rounded up, which for these two is 200 by 6.
14194        assert_eq!(
14195            f.run(&[b"CMS.INITBYPROB", b"p", b"0.01", b"0.03"]),
14196            "+OK\r\n"
14197        );
14198        assert_eq!(
14199            f.run(&[b"CMS.INFO", b"p"]),
14200            "*6\r\n+width\r\n:200\r\n+depth\r\n:6\r\n+count\r\n:0\r\n"
14201        );
14202        // The key is checked first, so a width of zero at a key that is already
14203        // there is about the key and not about the width.
14204        assert_eq!(
14205            f.run(&[b"CMS.INITBYDIM", b"d", b"0", b"2"]),
14206            "-CMS: key already exists\r\n"
14207        );
14208        assert_eq!(
14209            f.run(&[b"CMS.INITBYDIM", b"new", b"0", b"2"]),
14210            "-CMS: invalid width\r\n"
14211        );
14212        assert_eq!(
14213            f.run(&[b"CMS.INITBYDIM", b"new", b"2", b"0"]),
14214            "-CMS: invalid depth\r\n"
14215        );
14216        assert_eq!(
14217            f.run(&[b"CMS.INITBYPROB", b"new", b"0", b"0.5"]),
14218            "-CMS: invalid overestimation value\r\n"
14219        );
14220        assert_eq!(
14221            f.run(&[b"CMS.INITBYPROB", b"new", b"0.1", b"1"]),
14222            "-CMS: invalid prob value\r\n"
14223        );
14224        // A probability whose float conversion is zero has no depth, and a width
14225        // past a signed sixty four bit integer has no width, and both are the
14226        // same sentence.
14227        assert_eq!(
14228            f.run(&[b"CMS.INITBYPROB", b"new", b"0.5", b"1e-46"]),
14229            "-CMS: invalid init arguments\r\n"
14230        );
14231        // And a sketch bigger than a gibibyte of counters is refused here where
14232        // the reference reserves address space nobody has touched, which is
14233        // D-47.
14234        assert_eq!(
14235            f.run(&[b"CMS.INITBYDIM", b"new", b"268435457", b"1"]),
14236            "-CMS: Insufficient memory to create the key\r\n"
14237        );
14238        assert_eq!(f.run(&[b"EXISTS", b"new"]), ":0\r\n");
14239    }
14240
14241    /// Every pair is parsed before any of them lands, the counters saturate,
14242    /// and the count is a signed total of what was asked for.
14243    #[test]
14244    fn increments_are_parsed_whole_and_the_counters_saturate() {
14245        let mut f = Fixture::new();
14246        f.run(&[b"CMS.INITBYDIM", b"c", b"100", b"4"]);
14247        assert_eq!(
14248            f.run(&[b"CMS.INCRBY", b"c", b"a", b"3", b"b", b"4"]),
14249            "*2\r\n:3\r\n:4\r\n"
14250        );
14251        // An item that is incremented twice in one call sees its own first
14252        // increment in the reply to the second.
14253        assert_eq!(
14254            f.run(&[b"CMS.INCRBY", b"c", b"a", b"1", b"a", b"1"]),
14255            "*2\r\n:4\r\n:5\r\n"
14256        );
14257        // A bad number anywhere means nothing at all is applied.
14258        assert_eq!(
14259            f.run(&[b"CMS.INCRBY", b"c", b"a", b"9", b"b", b"x"]),
14260            "-CMS: Cannot parse number\r\n"
14261        );
14262        assert_eq!(
14263            f.run(&[b"CMS.INCRBY", b"c", b"a", b"9", b"b", b"-1"]),
14264            "-CMS: Number cannot be negative\r\n"
14265        );
14266        assert_eq!(
14267            f.run(&[b"CMS.QUERY", b"c", b"a", b"b"]),
14268            "*2\r\n:5\r\n:4\r\n"
14269        );
14270        // The counters stop at four billion and the item that stopped says so in
14271        // its own slot while the one beside it answers a number.
14272        f.run(&[b"CMS.INCRBY", b"c", b"a", b"4294967295"]);
14273        assert_eq!(
14274            f.run(&[b"CMS.INCRBY", b"c", b"a", b"1", b"b", b"1"]),
14275            "*2\r\n-CMS: INCRBY overflow\r\n:5\r\n"
14276        );
14277        assert_eq!(f.run(&[b"CMS.QUERY", b"c", b"a"]), "*1\r\n:4294967295\r\n");
14278        // The count is what was asked for rather than what landed, and it is
14279        // signed, so a big enough total comes back negative.
14280        f.run(&[b"CMS.INITBYDIM", b"w", b"4", b"1"]);
14281        f.run(&[b"CMS.INCRBY", b"w", b"x", b"9223372036854775807"]);
14282        f.run(&[b"CMS.INCRBY", b"w", b"x", b"1"]);
14283        assert_eq!(
14284            f.run(&[b"CMS.INFO", b"w"]),
14285            "*6\r\n+width\r\n:4\r\n+depth\r\n:1\r\n+count\r\n:-9223372036854775808\r\n"
14286        );
14287        // An odd number of arguments after the key is an arity error and not a
14288        // syntax one.
14289        assert!(
14290            f.run(&[b"CMS.INCRBY", b"c", b"a", b"1", b"b"])
14291                .contains("wrong number of arguments")
14292        );
14293        assert_eq!(
14294            f.run(&[b"CMS.INCRBY", b"nope", b"a", b"1"]),
14295            "-CMS: key does not exist\r\n"
14296        );
14297        assert_eq!(
14298            f.run(&[b"CMS.QUERY", b"nope", b"a"]),
14299            "-CMS: key does not exist\r\n"
14300        );
14301    }
14302
14303    /// A merge overwrites its destination, and it is worked out in full before
14304    /// any of it is written.
14305    #[test]
14306    fn a_merge_lands_whole_or_not_at_all() {
14307        let mut f = Fixture::new();
14308        for name in [&b"m1"[..], b"m2", b"dst"] {
14309            f.run(&[b"CMS.INITBYDIM", name, b"64", b"3"]);
14310        }
14311        f.run(&[b"CMS.INCRBY", b"m1", b"a", b"5"]);
14312        f.run(&[b"CMS.INCRBY", b"m2", b"a", b"7"]);
14313        assert_eq!(
14314            f.run(&[b"CMS.MERGE", b"dst", b"2", b"m1", b"m2"]),
14315            "+OK\r\n"
14316        );
14317        assert_eq!(f.run(&[b"CMS.QUERY", b"dst", b"a"]), "*1\r\n:12\r\n");
14318        // Overwritten and not added to, so the same merge twice is the same
14319        // answer twice.
14320        assert_eq!(
14321            f.run(&[b"CMS.MERGE", b"dst", b"2", b"m1", b"m2"]),
14322            "+OK\r\n"
14323        );
14324        assert_eq!(f.run(&[b"CMS.QUERY", b"dst", b"a"]), "*1\r\n:12\r\n");
14325        assert_eq!(
14326            f.run(&[
14327                b"CMS.MERGE",
14328                b"dst",
14329                b"2",
14330                b"m1",
14331                b"m2",
14332                b"WEIGHTS",
14333                b"2",
14334                b"3"
14335            ]),
14336            "+OK\r\n"
14337        );
14338        assert_eq!(f.run(&[b"CMS.QUERY", b"dst", b"a"]), "*1\r\n:31\r\n");
14339        // A cell times a weight is checked wide rather than wrapped, so this is
14340        // a refusal and the destination is left exactly as it was.
14341        assert_eq!(
14342            f.run(&[
14343                b"CMS.MERGE",
14344                b"dst",
14345                b"1",
14346                b"m1",
14347                b"WEIGHTS",
14348                b"4611686018427387904"
14349            ]),
14350            "-CMS: MERGE overflow\r\n"
14351        );
14352        assert_eq!(f.run(&[b"CMS.QUERY", b"dst", b"a"]), "*1\r\n:31\r\n");
14353        // The destination comes first, then the count, then the layout, then the
14354        // weights, then the sources one at a time.
14355        f.run(&[b"CMS.INITBYDIM", b"wide", b"128", b"3"]);
14356        assert_eq!(
14357            f.run(&[b"CMS.MERGE", b"gone", b"1", b"m1"]),
14358            "-CMS: key does not exist\r\n"
14359        );
14360        assert_eq!(
14361            f.run(&[b"CMS.MERGE", b"dst", b"0", b"m1"]),
14362            "-CMS: Number of keys must be positive\r\n"
14363        );
14364        assert_eq!(
14365            f.run(&[b"CMS.MERGE", b"dst", b"3", b"m1"]),
14366            "-CMS: wrong number of keys\r\n"
14367        );
14368        assert_eq!(
14369            f.run(&[b"CMS.MERGE", b"dst", b"1", b"m1", b"WEIGHTS", b"1", b"2"]),
14370            "-CMS: wrong number of keys/weights\r\n"
14371        );
14372        assert_eq!(
14373            f.run(&[b"CMS.MERGE", b"dst", b"1", b"wide"]),
14374            "-CMS: width/depth is not equal\r\n"
14375        );
14376        assert_eq!(
14377            f.run(&[b"CMS.MERGE", b"dst", b"1", b"gone"]),
14378            "-CMS: key does not exist\r\n"
14379        );
14380    }
14381
14382    /// A key holding anything else is `WRONGTYPE` to all six, and a key holding
14383    /// a sketch is refused by the two commands that would have to serialise it.
14384    #[test]
14385    fn a_sketch_is_a_module_key_to_the_rest_of_the_keyspace() {
14386        let mut f = Fixture::new();
14387        f.run(&[b"SET", b"s", b"text"]);
14388        for cmd in [
14389            vec![&b"CMS.INITBYDIM"[..], b"s", b"8", b"2"],
14390            vec![&b"CMS.INCRBY"[..], b"s", b"a", b"1"],
14391            vec![&b"CMS.QUERY"[..], b"s", b"a"],
14392            vec![&b"CMS.INFO"[..], b"s"],
14393            vec![&b"CMS.MERGE"[..], b"s", b"1", b"s"],
14394        ] {
14395            let name = String::from_utf8_lossy(cmd[0]).into_owned();
14396            let reply = f.run(&cmd);
14397            // The two constructors see the key before anything else and say so
14398            // in the module's own words, and the rest are `WRONGTYPE`.
14399            assert!(
14400                reply.starts_with("-WRONGTYPE") || reply == "-CMS: key already exists\r\n",
14401                "{name}: {reply}"
14402            );
14403        }
14404        f.run(&[b"CMS.INITBYDIM", b"c", b"64", b"2"]);
14405        // Redis refuses to copy a module key that has no copy callback, and
14406        // these are its words rather than ours. `DUMP` is the other half of
14407        // D-48: the reference has a payload for one of these and we do not.
14408        assert_eq!(
14409            f.run(&[b"COPY", b"c", b"c2"]),
14410            "-ERR not supported for this module key\r\n"
14411        );
14412        assert_eq!(
14413            f.run(&[b"DUMP", b"c"]),
14414            "-ERR DUMP is not supported for this module key\r\n"
14415        );
14416        // A graph is nobody's module and keeps its own sentence.
14417        f.run(&[b"G.NADD", b"g", b"a"]);
14418        assert_eq!(
14419            f.run(&[b"COPY", b"g", b"g2"]),
14420            "-ERR COPY is not supported for a graph\r\n"
14421        );
14422        assert_eq!(
14423            f.run(&[b"DUMP", b"g"]),
14424            "-ERR DUMP is not supported for a graph\r\n"
14425        );
14426        // Everything that does not need a byte shape works on a sketch key the
14427        // way it works on any other.
14428        assert_eq!(f.run(&[b"EXPIRE", b"c", b"100"]), ":1\r\n");
14429        assert_eq!(f.run(&[b"PERSIST", b"c"]), ":1\r\n");
14430        assert_eq!(f.run(&[b"RENAME", b"c", b"c3"]), "+OK\r\n");
14431        assert_eq!(f.run(&[b"TYPE", b"c3"]), "+CMSk-TYPE\r\n");
14432        assert_eq!(f.run(&[b"DEL", b"c3"]), ":1\r\n");
14433    }
14434
14435    // ------------------------------------------------------------------ topk
14436
14437    /// `TOPK.RESERVE` takes three arguments or six, and looks at the key before
14438    /// it looks at any of them.
14439    #[test]
14440    fn a_reserve_takes_three_arguments_or_six() {
14441        let mut f = Fixture::new();
14442        assert_eq!(f.run(&[b"TOPK.RESERVE", b"t", b"5"]), "+OK\r\n");
14443        assert_eq!(
14444            f.run(&[b"TOPK.INFO", b"t"]),
14445            "*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"
14446        );
14447        // Four arguments and five are an arity error rather than a defaulted
14448        // depth or decay.
14449        for cmd in [
14450            vec![&b"TOPK.RESERVE"[..], b"u", b"5", b"8"],
14451            vec![&b"TOPK.RESERVE"[..], b"u", b"5", b"8", b"7"],
14452        ] {
14453            assert!(f.run(&cmd).contains("wrong number of arguments"));
14454        }
14455        assert_eq!(
14456            f.run(&[b"TOPK.RESERVE", b"u", b"5", b"8", b"7", b"0.5"]),
14457            "+OK\r\n"
14458        );
14459        // The key is checked first, so a reserve with nothing else right at a
14460        // key that is taken still says the key is taken.
14461        assert_eq!(
14462            f.run(&[b"TOPK.RESERVE", b"u", b"0", b"0", b"0", b"9"]),
14463            "-TopK: key already exists\r\n"
14464        );
14465        assert_eq!(
14466            f.run(&[b"TOPK.RESERVE", b"v", b"0"]),
14467            "-TopK: invalid k\r\n"
14468        );
14469        assert_eq!(
14470            f.run(&[b"TOPK.RESERVE", b"v", b"1", b"0", b"7", b"0.9"]),
14471            "-TopK: invalid width\r\n"
14472        );
14473        assert_eq!(
14474            f.run(&[b"TOPK.RESERVE", b"v", b"1", b"8", b"x", b"0.9"]),
14475            "-TopK: invalid depth\r\n"
14476        );
14477        // Zero is out and one is in, which is the module's `> 0` and `<= 1`.
14478        assert_eq!(
14479            f.run(&[b"TOPK.RESERVE", b"v", b"1", b"8", b"7", b"0"]),
14480            "-TopK: invalid decay value. must be '<= 1' & '> 0'\r\n"
14481        );
14482        assert_eq!(
14483            f.run(&[b"TOPK.RESERVE", b"v", b"1", b"8", b"7", b"1"]),
14484            "+OK\r\n"
14485        );
14486        // Past the cap, with the one sentence in the family that has a prefix.
14487        assert_eq!(
14488            f.run(&[
14489                b"TOPK.RESERVE",
14490                b"w",
14491                b"1",
14492                b"4294967295",
14493                b"4294967295",
14494                b"0.9"
14495            ]),
14496            "-ERR Insufficient memory to create topk data structure\r\n"
14497        );
14498    }
14499
14500    /// What the sketch keeps, and the three ways of asking about it.
14501    #[test]
14502    fn the_kept_set_is_what_query_and_list_answer_from() {
14503        let mut f = Fixture::new();
14504        f.run(&[b"TOPK.RESERVE", b"t", b"2", b"1000", b"5", b"0.9"]);
14505        // A null an item while there is room, then the name of whatever was
14506        // pushed out.
14507        assert_eq!(
14508            f.run(&[b"TOPK.ADD", b"t", b"a", b"b"]),
14509            "*2\r\n$-1\r\n$-1\r\n"
14510        );
14511        assert_eq!(f.run(&[b"TOPK.INCRBY", b"t", b"a", b"10"]), "*1\r\n$-1\r\n");
14512        // Two slots are full and `c` arrives with a count of one, which is not
14513        // under the smallest kept count, so it takes that slot straight away.
14514        assert_eq!(f.run(&[b"TOPK.ADD", b"t", b"c"]), "*1\r\n$1\r\nb\r\n");
14515        assert_eq!(f.run(&[b"TOPK.INCRBY", b"t", b"c", b"5"]), "*1\r\n$-1\r\n");
14516        assert_eq!(
14517            f.run(&[b"TOPK.QUERY", b"t", b"a", b"b", b"c"]),
14518            "*3\r\n:1\r\n:0\r\n:1\r\n"
14519        );
14520        // The table still counts what the kept set let go of.
14521        assert_eq!(
14522            f.run(&[b"TOPK.COUNT", b"t", b"a", b"b", b"c"]),
14523            "*3\r\n:11\r\n:1\r\n:6\r\n"
14524        );
14525        assert_eq!(f.run(&[b"TOPK.LIST", b"t"]), "*2\r\n$1\r\na\r\n$1\r\nc\r\n");
14526        assert_eq!(
14527            f.run(&[b"TOPK.LIST", b"t", b"WITHCOUNT"]),
14528            "*4\r\n$1\r\na\r\n:11\r\n$1\r\nc\r\n:6\r\n"
14529        );
14530        // Any prefix of the keyword turns the counts on, the empty string
14531        // included, and only a longer word or a different one is refused.
14532        assert_eq!(
14533            f.run(&[b"TOPK.LIST", b"t", b"w"]),
14534            f.run(&[b"TOPK.LIST", b"t", b"WITHCOUNT"])
14535        );
14536        assert_eq!(
14537            f.run(&[b"TOPK.LIST", b"t", b""]),
14538            f.run(&[b"TOPK.LIST", b"t", b"WITHCOUNT"])
14539        );
14540        assert_eq!(
14541            f.run(&[b"TOPK.LIST", b"t", b"WITHCOUNTS"]),
14542            "-WITHCOUNT keyword expected\r\n"
14543        );
14544        // And the keyword is looked at before the key, so a missing key with a
14545        // bad keyword complains about the keyword.
14546        assert_eq!(
14547            f.run(&[b"TOPK.LIST", b"missing", b"nope"]),
14548            "-WITHCOUNT keyword expected\r\n"
14549        );
14550        assert_eq!(
14551            f.run(&[b"TOPK.LIST", b"missing"]),
14552            "-TopK: key does not exist\r\n"
14553        );
14554        // An item counted zero times is kept and not listed.
14555        f.run(&[b"TOPK.RESERVE", b"z", b"3"]);
14556        assert_eq!(
14557            f.run(&[b"TOPK.INCRBY", b"z", b"nothing", b"0"]),
14558            "*1\r\n$-1\r\n"
14559        );
14560        assert_eq!(f.run(&[b"TOPK.QUERY", b"z", b"nothing"]), "*1\r\n:1\r\n");
14561        assert_eq!(f.run(&[b"TOPK.LIST", b"z"]), "*0\r\n");
14562    }
14563
14564    /// `TOPK.INCRBY` applies as it goes, so a bad increment leaves everything
14565    /// before it counted, and the reply counts what it wrote.
14566    #[test]
14567    fn an_increment_is_applied_as_it_goes_and_stops_at_a_bad_one() {
14568        let mut f = Fixture::new();
14569        f.run(&[b"TOPK.RESERVE", b"t", b"5", b"1000", b"5", b"0.9"]);
14570        // Three pairs, the middle one bad: two elements come back, one of them
14571        // the error, and the array header says two rather than three. That last
14572        // part is D-51 and it is why a client here stays in step.
14573        assert_eq!(
14574            f.run(&[b"TOPK.INCRBY", b"t", b"a", b"3", b"b", b"-1", b"c", b"4"]),
14575            format!(
14576                "*2\r\n$-1\r\n-{}\r\n",
14577                "TopK: increment must be an integer greater or equal to 0                            and smaller or equal to 100,000"
14578            )
14579        );
14580        assert_eq!(
14581            f.run(&[b"TOPK.COUNT", b"t", b"a", b"b", b"c"]),
14582            "*3\r\n:3\r\n:0\r\n:0\r\n"
14583        );
14584        // A hundred thousand is in and one more is out.
14585        assert_eq!(
14586            f.run(&[b"TOPK.INCRBY", b"t", b"a", b"100000"]),
14587            "*1\r\n$-1\r\n"
14588        );
14589        assert!(
14590            f.run(&[b"TOPK.INCRBY", b"t", b"a", b"100001"])
14591                .contains("smaller or equal to 100,000")
14592        );
14593        // Pairs have to be pairs.
14594        assert!(
14595            f.run(&[b"TOPK.INCRBY", b"t", b"a", b"1", b"b"])
14596                .contains("wrong number of arguments")
14597        );
14598        assert_eq!(f.run(&[b"TOPK.COUNT", b"t", b"a"]), "*1\r\n:100003\r\n");
14599    }
14600
14601    /// The RESP3 shapes, which are the two the protocols disagree about.
14602    #[test]
14603    fn a_query_is_a_bool_and_info_is_a_map_on_resp3() {
14604        let mut f = Fixture::new();
14605        f.run(&[b"HELLO", b"3"]);
14606        f.run(&[b"TOPK.RESERVE", b"t", b"2", b"8", b"7", b"0.5"]);
14607        f.run(&[b"TOPK.ADD", b"t", b"a"]);
14608        assert_eq!(
14609            f.run(&[b"TOPK.QUERY", b"t", b"a", b"b"]),
14610            "*2\r\n#t\r\n#f\r\n"
14611        );
14612        // The count stays an integer on both protocols.
14613        assert_eq!(f.run(&[b"TOPK.COUNT", b"t", b"a"]), "*1\r\n:1\r\n");
14614        assert_eq!(
14615            f.run(&[b"TOPK.INFO", b"t"]),
14616            "%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"
14617        );
14618        assert_eq!(f.run(&[b"TOPK.ADD", b"t", b"a"]), "*1\r\n_\r\n");
14619    }
14620
14621    /// A top k key answers the module sentences the other sketch families
14622    /// answer, and its own word for its type.
14623    #[test]
14624    fn a_top_k_sketch_is_a_module_key_to_the_rest_of_the_keyspace() {
14625        let mut f = Fixture::new();
14626        f.run(&[b"SET", b"s", b"text"]);
14627        for cmd in [
14628            vec![&b"TOPK.RESERVE"[..], b"s", b"5"],
14629            vec![&b"TOPK.ADD"[..], b"s", b"a"],
14630            vec![&b"TOPK.INCRBY"[..], b"s", b"a", b"1"],
14631            vec![&b"TOPK.QUERY"[..], b"s", b"a"],
14632            vec![&b"TOPK.COUNT"[..], b"s", b"a"],
14633            vec![&b"TOPK.LIST"[..], b"s"],
14634            vec![&b"TOPK.INFO"[..], b"s"],
14635        ] {
14636            let name = String::from_utf8_lossy(cmd[0]).into_owned();
14637            let reply = f.run(&cmd);
14638            assert!(
14639                reply.starts_with("-WRONGTYPE") || reply == "-TopK: key already exists\r\n",
14640                "{name}: {reply}"
14641            );
14642        }
14643        f.run(&[b"TOPK.RESERVE", b"t", b"5"]);
14644        assert_eq!(
14645            f.run(&[b"COPY", b"t", b"t2"]),
14646            "-ERR not supported for this module key\r\n"
14647        );
14648        assert_eq!(
14649            f.run(&[b"DUMP", b"t"]),
14650            "-ERR DUMP is not supported for this module key\r\n"
14651        );
14652        assert_eq!(f.run(&[b"EXPIRE", b"t", b"100"]), ":1\r\n");
14653        assert_eq!(f.run(&[b"PERSIST", b"t"]), ":1\r\n");
14654        assert_eq!(f.run(&[b"RENAME", b"t", b"t3"]), "+OK\r\n");
14655        assert_eq!(f.run(&[b"TYPE", b"t3"]), "+TopK-TYPE\r\n");
14656        assert_eq!(f.run(&[b"OBJECT", b"ENCODING", b"t3"]), "$3\r\nraw\r\n");
14657        assert_eq!(f.run(&[b"DEL", b"t3"]), ":1\r\n");
14658        // Every one of the six that is not the constructor says the same thing
14659        // about a key that is not there.
14660        assert_eq!(
14661            f.run(&[b"TOPK.INFO", b"t3"]),
14662            "-TopK: key does not exist\r\n"
14663        );
14664    }
14665
14666    // --------------------------------------------------------------- tdigest
14667
14668    /// `TDIGEST.CREATE` takes two arguments or four, and the keyword search is a
14669    /// search rather than a lookup.
14670    #[test]
14671    fn a_create_takes_two_arguments_or_four_and_reads_the_last_one() {
14672        let mut f = Fixture::new();
14673        assert_eq!(f.run(&[b"TDIGEST.CREATE", b"t"]), "+OK\r\n");
14674        // A hundred is the default and the capacity is six times it plus ten.
14675        assert_eq!(
14676            f.run(&[b"TDIGEST.INFO", b"t"]),
14677            "*18\r\n+Compression\r\n:100\r\n+Capacity\r\n:610\r\n+Merged nodes\r\n:0\r\n\
14678             +Unmerged nodes\r\n:0\r\n+Merged weight\r\n:0\r\n+Unmerged weight\r\n:0\r\n\
14679             +Observations\r\n:0\r\n+Total compressions\r\n:0\r\n+Memory usage\r\n:9840\r\n"
14680        );
14681        assert_eq!(
14682            f.run(&[b"TDIGEST.CREATE", b"t"]),
14683            "-ERR T-Digest: key already exists\r\n"
14684        );
14685        // Three arguments is an arity error and not a missing keyword.
14686        assert!(
14687            f.run(&[b"TDIGEST.CREATE", b"u", b"COMPRESSION"])
14688                .contains("wrong number of arguments")
14689        );
14690        assert_eq!(
14691            f.run(&[b"TDIGEST.CREATE", b"u", b"COMPRESSION", b"1000"]),
14692            "+OK\r\n"
14693        );
14694        assert_eq!(
14695            f.run(&[b"TDIGEST.CREATE", b"v", b"compression", b"1"]),
14696            "+OK\r\n"
14697        );
14698        // The word is looked for across both trailing arguments and the number
14699        // is then read out of the last one whatever was found, so this looks for
14700        // a number inside the word `COMPRESSION` and does not find one.
14701        assert_eq!(
14702            f.run(&[b"TDIGEST.CREATE", b"w", b"100", b"COMPRESSION"]),
14703            "-ERR T-Digest: error parsing compression parameter\r\n"
14704        );
14705        assert_eq!(
14706            f.run(&[b"TDIGEST.CREATE", b"w", b"NOPE", b"100"]),
14707            "-ERR T-Digest: wrong keyword\r\n"
14708        );
14709        assert_eq!(
14710            f.run(&[b"TDIGEST.CREATE", b"w", b"COMPRESSION", b"1.5"]),
14711            "-ERR T-Digest: error parsing compression parameter\r\n"
14712        );
14713        assert_eq!(
14714            f.run(&[b"TDIGEST.CREATE", b"w", b"COMPRESSION", b"0"]),
14715            "-ERR T-Digest: compression parameter needs to be a positive integer\r\n"
14716        );
14717        // The reference's own ceiling, which is where the capacity stops fitting
14718        // in an int, and one past it.
14719        assert_eq!(
14720            f.run(&[b"TDIGEST.CREATE", b"w", b"COMPRESSION", b"357913942"]),
14721            "-ERR T-Digest: allocation failed\r\n"
14722        );
14723        // And ours, which is a gibibyte of centroids and is D-52.
14724        assert_eq!(
14725            f.run(&[b"TDIGEST.CREATE", b"w", b"COMPRESSION", b"100000000"]),
14726            "-ERR T-Digest: allocation failed\r\n"
14727        );
14728        // The key is checked before the arguments, so a bad compression at a key
14729        // that is already a digest still says the key is taken.
14730        assert_eq!(
14731            f.run(&[b"TDIGEST.CREATE", b"t", b"COMPRESSION", b"0"]),
14732            "-ERR T-Digest: key already exists\r\n"
14733        );
14734    }
14735
14736    /// The four samples every note about this family is written against, and the
14737    /// answers a real 8.10.1 gives for them.
14738    #[test]
14739    fn the_quantile_family_answers_what_the_module_answers() {
14740        let mut f = Fixture::new();
14741        f.run(&[b"TDIGEST.CREATE", b"s"]);
14742        assert_eq!(
14743            f.run(&[b"TDIGEST.ADD", b"s", b"1", b"2", b"3", b"4"]),
14744            "+OK\r\n"
14745        );
14746        assert_eq!(f.run(&[b"TDIGEST.MIN", b"s"]), "$1\r\n1\r\n");
14747        assert_eq!(f.run(&[b"TDIGEST.MAX", b"s"]), "$1\r\n4\r\n");
14748        // The cdf of a sample is the weight below it plus half its own.
14749        assert_eq!(
14750            f.run(&[b"TDIGEST.CDF", b"s", b"1", b"2", b"3", b"4"]),
14751            "*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"
14752        );
14753        assert_eq!(
14754            f.run(&[b"TDIGEST.QUANTILE", b"s", b"0", b"0.5", b"1"]),
14755            "*3\r\n$1\r\n1\r\n$1\r\n3\r\n$1\r\n4\r\n"
14756        );
14757        // Out of order, the walk restarts, and 0.5 answers 3 either way while
14758        // the two after it are read from the front again.
14759        assert_eq!(
14760            f.run(&[b"TDIGEST.QUANTILE", b"s", b"0.5", b"0.1", b"0.9"]),
14761            "*3\r\n$1\r\n3\r\n$1\r\n1\r\n$1\r\n4\r\n"
14762        );
14763        assert_eq!(
14764            f.run(&[b"TDIGEST.RANK", b"s", b"0", b"1", b"3", b"4", b"5"]),
14765            "*5\r\n:-1\r\n:0\r\n:2\r\n:3\r\n:4\r\n"
14766        );
14767        assert_eq!(
14768            f.run(&[b"TDIGEST.REVRANK", b"s", b"0", b"1", b"3", b"4", b"5"]),
14769            "*5\r\n:4\r\n:3\r\n:1\r\n:0\r\n:-1\r\n"
14770        );
14771        assert_eq!(
14772            f.run(&[b"TDIGEST.BYRANK", b"s", b"0", b"1", b"3", b"4"]),
14773            "*4\r\n$1\r\n1\r\n$1\r\n2\r\n$1\r\n4\r\n$3\r\ninf\r\n"
14774        );
14775        assert_eq!(
14776            f.run(&[b"TDIGEST.BYREVRANK", b"s", b"0", b"1", b"3", b"4"]),
14777            "*4\r\n$1\r\n4\r\n$1\r\n3\r\n$1\r\n1\r\n$4\r\n-inf\r\n"
14778        );
14779        assert_eq!(
14780            f.run(&[b"TDIGEST.TRIMMED_MEAN", b"s", b"0", b"1"]),
14781            "$3\r\n2.5\r\n"
14782        );
14783        assert_eq!(
14784            f.run(&[b"TDIGEST.TRIMMED_MEAN", b"s", b"0.25", b"0.75"]),
14785            "$3\r\n2.5\r\n"
14786        );
14787        // The ranges, which are separate sentences from the parse failures.
14788        assert_eq!(
14789            f.run(&[b"TDIGEST.QUANTILE", b"s", b"1.1"]),
14790            "-ERR T-Digest: quantile should be in [0,1]\r\n"
14791        );
14792        assert_eq!(
14793            f.run(&[b"TDIGEST.QUANTILE", b"s", b"zzz"]),
14794            "-ERR T-Digest: error parsing quantile\r\n"
14795        );
14796        assert_eq!(
14797            f.run(&[b"TDIGEST.CDF", b"s", b"zzz"]),
14798            "-ERR T-Digest: error parsing cdf\r\n"
14799        );
14800        assert_eq!(
14801            f.run(&[b"TDIGEST.RANK", b"s", b"zzz"]),
14802            "-ERR T-Digest: error parsing value\r\n"
14803        );
14804        assert_eq!(
14805            f.run(&[b"TDIGEST.BYRANK", b"s", b"-1"]),
14806            "-ERR T-Digest: rank needs to be non negative\r\n"
14807        );
14808        assert_eq!(
14809            f.run(&[b"TDIGEST.BYRANK", b"s", b"1.5"]),
14810            "-ERR T-Digest: error parsing rank\r\n"
14811        );
14812        // Both cuts have their own parse sentence and share the range one, and
14813        // equal cuts are refused rather than answering nothing.
14814        assert_eq!(
14815            f.run(&[b"TDIGEST.TRIMMED_MEAN", b"s", b"zzz", b"0.9"]),
14816            "-ERR T-Digest: error parsing low_cut_percentile\r\n"
14817        );
14818        assert_eq!(
14819            f.run(&[b"TDIGEST.TRIMMED_MEAN", b"s", b"0.1", b"zzz"]),
14820            "-ERR T-Digest: error parsing high_cut_percentile\r\n"
14821        );
14822        assert_eq!(
14823            f.run(&[b"TDIGEST.TRIMMED_MEAN", b"s", b"0.1", b"1.1"]),
14824            "-ERR T-Digest: low_cut_percentile and high_cut_percentile should be in [0,1]\r\n"
14825        );
14826        assert_eq!(
14827            f.run(&[b"TDIGEST.TRIMMED_MEAN", b"s", b"0.5", b"0.5"]),
14828            "-ERR T-Digest: low_cut_percentile should be lower than high_cut_percentile\r\n"
14829        );
14830    }
14831
14832    /// An empty digest answers every question, and answers most of them with
14833    /// something that is not a number.
14834    #[test]
14835    fn an_empty_digest_has_an_answer_for_everything() {
14836        let mut f = Fixture::new();
14837        f.run(&[b"TDIGEST.CREATE", b"e"]);
14838        assert_eq!(f.run(&[b"TDIGEST.MIN", b"e"]), "$3\r\nnan\r\n");
14839        assert_eq!(f.run(&[b"TDIGEST.MAX", b"e"]), "$3\r\nnan\r\n");
14840        assert_eq!(
14841            f.run(&[b"TDIGEST.QUANTILE", b"e", b"0", b"1"]),
14842            "*2\r\n$3\r\nnan\r\n$3\r\nnan\r\n"
14843        );
14844        assert_eq!(f.run(&[b"TDIGEST.CDF", b"e", b"0"]), "*1\r\n$3\r\nnan\r\n");
14845        assert_eq!(
14846            f.run(&[b"TDIGEST.TRIMMED_MEAN", b"e", b"0.1", b"0.9"]),
14847            "$3\r\nnan\r\n"
14848        );
14849        // Minus two, which is a number no rank on a digest with samples in it
14850        // can ever be.
14851        assert_eq!(
14852            f.run(&[b"TDIGEST.RANK", b"e", b"0", b"1"]),
14853            "*2\r\n:-2\r\n:-2\r\n"
14854        );
14855        assert_eq!(
14856            f.run(&[b"TDIGEST.REVRANK", b"e", b"0", b"1"]),
14857            "*2\r\n:-2\r\n:-2\r\n"
14858        );
14859        assert_eq!(
14860            f.run(&[b"TDIGEST.BYRANK", b"e", b"0", b"5"]),
14861            "*2\r\n$3\r\nnan\r\n$3\r\nnan\r\n"
14862        );
14863        // A reset puts a digest with samples back into exactly this state.
14864        f.run(&[b"TDIGEST.ADD", b"e", b"1", b"2", b"3"]);
14865        assert_eq!(f.run(&[b"TDIGEST.RESET", b"e"]), "+OK\r\n");
14866        assert_eq!(f.run(&[b"TDIGEST.MIN", b"e"]), "$3\r\nnan\r\n");
14867        // Down to the compression count, so a reset digest and a fresh one of
14868        // the same compression report the same nine numbers.
14869        f.run(&[b"TDIGEST.CREATE", b"e2"]);
14870        assert_eq!(
14871            f.run(&[b"TDIGEST.INFO", b"e"]),
14872            f.run(&[b"TDIGEST.INFO", b"e2"])
14873        );
14874    }
14875
14876    /// The double parser is Redis's and not this engine's, and the two disagree
14877    /// at both ends of the range.
14878    #[test]
14879    fn a_sample_is_read_the_way_redis_reads_a_double() {
14880        let mut f = Fixture::new();
14881        f.run(&[b"TDIGEST.CREATE", b"a"]);
14882        // Overflow and underflow are parse failures rather than an infinity and
14883        // a zero, which is where this parts company with the rest of the engine.
14884        for bad in [
14885            &b"nan"[..],
14886            b"1e400",
14887            b"-1e400",
14888            b"1e309",
14889            b"1e-400",
14890            b"",
14891            b" 1",
14892            b"1 ",
14893            b"1e",
14894            b"--1",
14895        ] {
14896            assert_eq!(
14897                f.run(&[b"TDIGEST.ADD", b"a", bad]),
14898                "-ERR T-Digest: error parsing val parameter\r\n",
14899                "{}",
14900                String::from_utf8_lossy(bad)
14901            );
14902        }
14903        // An infinity spelled out parses and is then refused for being one, with
14904        // a different sentence.
14905        for word in [&b"inf"[..], b"-inf", b"+INF", b"Infinity"] {
14906            assert_eq!(
14907                f.run(&[b"TDIGEST.ADD", b"a", word]),
14908                "-ERR T-Digest: val parameter needs to be a finite number\r\n",
14909                "{}",
14910                String::from_utf8_lossy(word)
14911            );
14912        }
14913        // These all parse: hex, a bare point either side, and the smallest
14914        // subnormal the reference will take.
14915        for good in [&b"0x10"[..], b".5", b"1.", b"1e-320", b"-0", b"0"] {
14916            assert_eq!(
14917                f.run(&[b"TDIGEST.ADD", b"a", good]),
14918                "+OK\r\n",
14919                "{}",
14920                String::from_utf8_lossy(good)
14921            );
14922        }
14923        // Nothing landed from the failures, so six samples is what there is.
14924        assert!(
14925            f.run(&[b"TDIGEST.INFO", b"a"])
14926                .contains("Observations\r\n:6\r\n")
14927        );
14928        // Every value is parsed before any is added, so this whole command is a
14929        // no op.
14930        assert_eq!(
14931            f.run(&[b"TDIGEST.ADD", b"a", b"1", b"zzz"]),
14932            "-ERR T-Digest: error parsing val parameter\r\n"
14933        );
14934        assert!(
14935            f.run(&[b"TDIGEST.INFO", b"a"])
14936                .contains("Observations\r\n:6\r\n")
14937        );
14938    }
14939
14940    /// What a merge does to its destination, to its inputs and to the buffer
14941    /// split `TDIGEST.INFO` reports.
14942    #[test]
14943    fn a_merge_sweeps_the_destination_between_its_inputs() {
14944        let mut f = Fixture::new();
14945        f.run(&[b"TDIGEST.CREATE", b"m1", b"COMPRESSION", b"100"]);
14946        f.run(&[b"TDIGEST.ADD", b"m1", b"1", b"2", b"3"]);
14947        f.run(&[b"TDIGEST.CREATE", b"m2", b"COMPRESSION", b"200"]);
14948        f.run(&[b"TDIGEST.ADD", b"m2", b"4", b"5", b"6"]);
14949        assert_eq!(
14950            f.run(&[b"TDIGEST.MERGE", b"d", b"2", b"m1", b"m2"]),
14951            "+OK\r\n"
14952        );
14953        // The destination did not exist, so the compression is the largest of
14954        // the inputs. The three from the first input were swept in before the
14955        // three from the second arrived, which is the one visible effect of the
14956        // reference folding one input at a time.
14957        let info = f.run(&[b"TDIGEST.INFO", b"d"]);
14958        assert!(info.contains("Compression\r\n:200\r\n"), "{info}");
14959        assert!(info.contains("Merged nodes\r\n:3\r\n"), "{info}");
14960        assert!(info.contains("Unmerged nodes\r\n:3\r\n"), "{info}");
14961        assert!(info.contains("Total compressions\r\n:1\r\n"), "{info}");
14962        assert_eq!(f.run(&[b"TDIGEST.MIN", b"d"]), "$1\r\n1\r\n");
14963        assert_eq!(f.run(&[b"TDIGEST.MAX", b"d"]), "$1\r\n6\r\n");
14964        // Reading a source sweeps it too, so a merge writes to keys it only
14965        // reads from.
14966        assert!(
14967            f.run(&[b"TDIGEST.INFO", b"m1"])
14968                .contains("Merged nodes\r\n:3\r\n")
14969        );
14970        // Without OVERRIDE the destination joins its own inputs, so this takes
14971        // it to nine observations and keeps its own compression.
14972        f.run(&[b"TDIGEST.MERGE", b"d", b"1", b"m1"]);
14973        let info = f.run(&[b"TDIGEST.INFO", b"d"]);
14974        assert!(info.contains("Observations\r\n:9\r\n"), "{info}");
14975        assert!(info.contains("Compression\r\n:200\r\n"), "{info}");
14976        // With OVERRIDE the old destination is dropped and the compression goes
14977        // back to the largest of the inputs.
14978        f.run(&[b"TDIGEST.MERGE", b"d", b"1", b"m1", b"OVERRIDE"]);
14979        let info = f.run(&[b"TDIGEST.INFO", b"d"]);
14980        assert!(info.contains("Observations\r\n:3\r\n"), "{info}");
14981        assert!(info.contains("Compression\r\n:100\r\n"), "{info}");
14982        // And COMPRESSION beats both.
14983        f.run(&[b"TDIGEST.MERGE", b"d", b"1", b"m1", b"COMPRESSION", b"500"]);
14984        assert!(
14985            f.run(&[b"TDIGEST.INFO", b"d"])
14986                .contains("Compression\r\n:500\r\n")
14987        );
14988        // Naming the destination as a source folds it in twice.
14989        f.run(&[b"TDIGEST.MERGE", b"d", b"1", b"d"]);
14990        assert!(
14991            f.run(&[b"TDIGEST.INFO", b"d"])
14992                .contains("Observations\r\n:12\r\n")
14993        );
14994        // The arguments, in the order the reference checks them.
14995        assert_eq!(
14996            f.run(&[b"TDIGEST.MERGE", b"d", b"zzz", b"m1"]),
14997            "-ERR T-Digest: error parsing numkeys\r\n"
14998        );
14999        assert_eq!(
15000            f.run(&[b"TDIGEST.MERGE", b"d", b"0", b"m1"]),
15001            "-ERR T-Digest: numkeys needs to be a positive integer\r\n"
15002        );
15003        assert!(
15004            f.run(&[b"TDIGEST.MERGE", b"d", b"3", b"m1", b"m2"])
15005                .contains("wrong number of arguments")
15006        );
15007        assert!(
15008            f.run(&[b"TDIGEST.MERGE", b"d", b"1", b"m1", b"COMPRESSION"])
15009                .contains("wrong number of arguments")
15010        );
15011        assert_eq!(
15012            f.run(&[b"TDIGEST.MERGE", b"d", b"1", b"m1", b"NOPE"]),
15013            "-ERR T-Digest: wrong keyword\r\n"
15014        );
15015        // A source that is not there stops the whole thing, and the destination
15016        // is left as it was.
15017        assert_eq!(
15018            f.run(&[b"TDIGEST.MERGE", b"d", b"2", b"m1", b"gone"]),
15019            "-ERR T-Digest: key does not exist\r\n"
15020        );
15021        assert!(
15022            f.run(&[b"TDIGEST.INFO", b"d"])
15023                .contains("Observations\r\n:12\r\n")
15024        );
15025        // A destination that is not there and is also named as a source is the
15026        // same sentence rather than an empty merge.
15027        assert_eq!(
15028            f.run(&[b"TDIGEST.MERGE", b"gone", b"1", b"gone"]),
15029            "-ERR T-Digest: key does not exist\r\n"
15030        );
15031    }
15032
15033    /// The RESP3 shapes, which are the two the protocols disagree about.
15034    #[test]
15035    fn a_digest_answers_doubles_and_a_map_on_resp3() {
15036        let mut f = Fixture::new();
15037        f.run(&[b"HELLO", b"3"]);
15038        f.run(&[b"TDIGEST.CREATE", b"s"]);
15039        f.run(&[b"TDIGEST.ADD", b"s", b"1", b"2", b"3", b"4"]);
15040        assert_eq!(f.run(&[b"TDIGEST.MIN", b"s"]), ",1\r\n");
15041        assert_eq!(
15042            f.run(&[b"TDIGEST.QUANTILE", b"s", b"0", b"1"]),
15043            "*2\r\n,1\r\n,4\r\n"
15044        );
15045        assert_eq!(f.run(&[b"TDIGEST.CDF", b"s", b"1"]), "*1\r\n,0.125\r\n");
15046        // The two infinities and the NaN go out as the bare words.
15047        assert_eq!(f.run(&[b"TDIGEST.BYRANK", b"s", b"4"]), "*1\r\n,inf\r\n");
15048        assert_eq!(
15049            f.run(&[b"TDIGEST.BYREVRANK", b"s", b"4"]),
15050            "*1\r\n,-inf\r\n"
15051        );
15052        f.run(&[b"TDIGEST.CREATE", b"e"]);
15053        assert_eq!(f.run(&[b"TDIGEST.MIN", b"e"]), ",nan\r\n");
15054        // The ranks stay integers on both protocols.
15055        assert_eq!(f.run(&[b"TDIGEST.RANK", b"s", b"1"]), "*1\r\n:0\r\n");
15056        // Every question above swept the buffer in, so the four samples are all
15057        // merged by now and the compression count says it happened once.
15058        assert_eq!(
15059            f.run(&[b"TDIGEST.INFO", b"s"]),
15060            "%9\r\n+Compression\r\n:100\r\n+Capacity\r\n:610\r\n+Merged nodes\r\n:4\r\n\
15061             +Unmerged nodes\r\n:0\r\n+Merged weight\r\n:4\r\n+Unmerged weight\r\n:0\r\n\
15062             +Observations\r\n:4\r\n+Total compressions\r\n:1\r\n+Memory usage\r\n:9840\r\n"
15063        );
15064    }
15065
15066    /// A t digest key answers the module sentences the other sketch families
15067    /// answer, and its own word for its type.
15068    #[test]
15069    fn a_t_digest_is_a_module_key_to_the_rest_of_the_keyspace() {
15070        let mut f = Fixture::new();
15071        f.run(&[b"SET", b"s", b"text"]);
15072        for cmd in [
15073            vec![&b"TDIGEST.CREATE"[..], b"s"],
15074            vec![&b"TDIGEST.RESET"[..], b"s"],
15075            vec![&b"TDIGEST.ADD"[..], b"s", b"1"],
15076            vec![&b"TDIGEST.MIN"[..], b"s"],
15077            vec![&b"TDIGEST.MAX"[..], b"s"],
15078            vec![&b"TDIGEST.QUANTILE"[..], b"s", b"0.5"],
15079            vec![&b"TDIGEST.CDF"[..], b"s", b"1"],
15080            vec![&b"TDIGEST.TRIMMED_MEAN"[..], b"s", b"0.1", b"0.9"],
15081            vec![&b"TDIGEST.RANK"[..], b"s", b"1"],
15082            vec![&b"TDIGEST.REVRANK"[..], b"s", b"1"],
15083            vec![&b"TDIGEST.BYRANK"[..], b"s", b"0"],
15084            vec![&b"TDIGEST.BYREVRANK"[..], b"s", b"0"],
15085            vec![&b"TDIGEST.INFO"[..], b"s"],
15086        ] {
15087            let name = String::from_utf8_lossy(cmd[0]).into_owned();
15088            let reply = f.run(&cmd);
15089            assert!(reply.starts_with("-WRONGTYPE"), "{name}: {reply}");
15090        }
15091        // The merge checks its destination the same way, and its sources too.
15092        f.run(&[b"TDIGEST.CREATE", b"t"]);
15093        assert!(
15094            f.run(&[b"TDIGEST.MERGE", b"s", b"1", b"t"])
15095                .starts_with("-WRONGTYPE")
15096        );
15097        assert!(
15098            f.run(&[b"TDIGEST.MERGE", b"d", b"1", b"s"])
15099                .starts_with("-WRONGTYPE")
15100        );
15101        assert_eq!(
15102            f.run(&[b"COPY", b"t", b"t2"]),
15103            "-ERR not supported for this module key\r\n"
15104        );
15105        assert_eq!(
15106            f.run(&[b"DUMP", b"t"]),
15107            "-ERR DUMP is not supported for this module key\r\n"
15108        );
15109        assert_eq!(f.run(&[b"EXPIRE", b"t", b"100"]), ":1\r\n");
15110        assert_eq!(f.run(&[b"PERSIST", b"t"]), ":1\r\n");
15111        assert_eq!(f.run(&[b"RENAME", b"t", b"t3"]), "+OK\r\n");
15112        assert_eq!(f.run(&[b"TYPE", b"t3"]), "+TDIS-TYPE\r\n");
15113        assert_eq!(f.run(&[b"OBJECT", b"ENCODING", b"t3"]), "$3\r\nraw\r\n");
15114        assert_eq!(f.run(&[b"DEL", b"t3"]), ":1\r\n");
15115        // An empty digest is still a key, so the twelve that are not the
15116        // constructor all say the same thing once it is gone.
15117        assert_eq!(
15118            f.run(&[b"TDIGEST.INFO", b"t3"]),
15119            "-ERR T-Digest: key does not exist\r\n"
15120        );
15121        // The key is looked at before the arguments, so a bad argument at a key
15122        // that is not there still says the key is not there.
15123        assert_eq!(
15124            f.run(&[b"TDIGEST.QUANTILE", b"t3", b"zzz"]),
15125            "-ERR T-Digest: key does not exist\r\n"
15126        );
15127    }
15128
15129    // -------------------------------------------------------------------- ts
15130
15131    /// A `TS.INFO` reply with the memory usage taken out of it.
15132    ///
15133    /// That number is what a series costs here rather than what one costs in the
15134    /// module, which is D-53, and it moves whenever the layout of a chunk does.
15135    /// Everything either side of it is the wire contract and is worth pinning
15136    /// down exactly, so the tests below check the whole reply with the one
15137    /// number lifted out.
15138    fn without_memory(reply: &str) -> String {
15139        let head = "+memoryUsage\r\n:";
15140        let at = reply.find(head).expect("every TS.INFO reports memory");
15141        let rest = &reply[at + head.len()..];
15142        let end = rest.find("\r\n").expect("and it is a whole number");
15143        format!("{}{}", &reply[..at + head.len()], &rest[end..])
15144    }
15145
15146    /// A series is made empty and still says it has a chunk, and the options are
15147    /// read before the key is looked at.
15148    #[test]
15149    fn a_series_is_made_empty_and_reports_on_itself() {
15150        let mut f = Fixture::new();
15151        assert_eq!(f.run(&[b"TS.CREATE", b"t"]), "+OK\r\n");
15152        assert_eq!(f.run(&[b"TYPE", b"t"]), "+TSDB-TYPE\r\n");
15153        assert_eq!(f.run(&[b"OBJECT", b"ENCODING", b"t"]), "$3\r\nraw\r\n");
15154        // Fourteen fields, so twenty eight elements. An empty series reports one
15155        // chunk and zero at both ends, and neither the chunk type nor the
15156        // duplicate policy is ever a nil.
15157        assert_eq!(
15158            without_memory(&f.run(&[b"TS.INFO", b"t"])),
15159            "*28\r\n\
15160             +totalSamples\r\n:0\r\n\
15161             +memoryUsage\r\n:\r\n\
15162             +firstTimestamp\r\n:0\r\n\
15163             +lastTimestamp\r\n:0\r\n\
15164             +retentionTime\r\n:0\r\n\
15165             +chunkCount\r\n:1\r\n\
15166             +chunkSize\r\n:4096\r\n\
15167             +chunkType\r\n+compressed\r\n\
15168             +duplicatePolicy\r\n+block\r\n\
15169             +labels\r\n*0\r\n\
15170             +sourceKey\r\n$-1\r\n\
15171             +rules\r\n*0\r\n\
15172             +ignoreMaxTimeDiff\r\n:0\r\n\
15173             +ignoreMaxValDiff\r\n$1\r\n0\r\n"
15174        );
15175        // A key that is already there is about the key whatever it holds, and
15176        // the existence is what is checked rather than the type.
15177        assert_eq!(
15178            f.run(&[b"TS.CREATE", b"t"]),
15179            "-ERR TSDB: key already exists\r\n"
15180        );
15181        assert_eq!(f.run(&[b"SET", b"str", b"x"]), "+OK\r\n");
15182        assert_eq!(
15183            f.run(&[b"TS.CREATE", b"str"]),
15184            "-ERR TSDB: key already exists\r\n"
15185        );
15186        // But the arguments are read first, so a bad one at a key that is there
15187        // answers about the argument.
15188        assert_eq!(
15189            f.run(&[b"TS.CREATE", b"t", b"RETENTION", b"abc"]),
15190            "-ERR TSDB: Couldn't parse RETENTION\r\n"
15191        );
15192        // The seven that will not make a series say WRONGTYPE about a key
15193        // holding something else, where the two that would say a sentence.
15194        // The word is inside the sentence and not in front of it, because the
15195        // module writes its own error text and Redis puts ERR on the front of
15196        // anything a module writes.
15197        let wrong = "-ERR WRONGTYPE Operation against a key holding the wrong kind of value\r\n";
15198        assert_eq!(f.run(&[b"TS.INFO", b"str"]), wrong);
15199        assert_eq!(f.run(&[b"TS.GET", b"str"]), wrong);
15200        assert_eq!(f.run(&[b"TS.ALTER", b"str"]), wrong);
15201        assert_eq!(f.run(&[b"TS.DEL", b"str", b"0", b"1"]), wrong);
15202        assert_eq!(f.run(&[b"TS.INCRBY", b"str", b"1"]), wrong);
15203        assert_eq!(
15204            f.run(&[b"TS.ADD", b"str", b"1", b"1"]),
15205            "-ERR TSDB: the key is not a TSDB key\r\n"
15206        );
15207        // And the ones that will not make one say so about a key that is gone.
15208        assert_eq!(
15209            f.run(&[b"TS.INFO", b"nope"]),
15210            "-ERR TSDB: the key does not exist\r\n"
15211        );
15212        assert_eq!(
15213            f.run(&[b"TS.GET", b"nope"]),
15214            "-ERR TSDB: the key does not exist\r\n"
15215        );
15216        assert_eq!(
15217            f.run(&[b"TS.ALTER", b"nope"]),
15218            "-ERR TSDB: the key does not exist\r\n"
15219        );
15220        assert_eq!(
15221            f.run(&[b"TS.DEL", b"nope", b"1", b"2"]),
15222            "-ERR TSDB: the key does not exist\r\n"
15223        );
15224    }
15225
15226    /// Every option word, including the ones that are wrong, and the scan that
15227    /// finds them.
15228    #[test]
15229    fn the_options_are_a_keyword_scan_and_not_a_grammar() {
15230        let mut f = Fixture::new();
15231        assert_eq!(
15232            f.run(&[
15233                b"TS.CREATE",
15234                b"t",
15235                b"RETENTION",
15236                b"5000",
15237                b"ENCODING",
15238                b"UNCOMPRESSED",
15239                b"CHUNK_SIZE",
15240                b"128",
15241                b"DUPLICATE_POLICY",
15242                b"LAST",
15243                b"IGNORE",
15244                b"10",
15245                b"0.5",
15246                b"LABELS",
15247                b"room",
15248                b"kitchen"
15249            ]),
15250            "+OK\r\n"
15251        );
15252        let info = f.run(&[b"TS.INFO", b"t"]);
15253        assert!(info.contains("+retentionTime\r\n:5000\r\n"), "{info}");
15254        assert!(info.contains("+chunkSize\r\n:128\r\n"), "{info}");
15255        assert!(info.contains("+chunkType\r\n+uncompressed\r\n"), "{info}");
15256        assert!(info.contains("+duplicatePolicy\r\n+last\r\n"), "{info}");
15257        assert!(info.contains("+ignoreMaxTimeDiff\r\n:10\r\n"), "{info}");
15258        // A plain double here, where a sample value out of TS.GET is the
15259        // shortest digits that read back as the same number.
15260        assert!(
15261            info.contains("+ignoreMaxValDiff\r\n$3\r\n0.5\r\n"),
15262            "{info}"
15263        );
15264        assert!(
15265            info.contains("+labels\r\n*1\r\n*2\r\n$4\r\nroom\r\n$7\r\nkitchen\r\n"),
15266            "{info}"
15267        );
15268
15269        // A word that is not an option is read past rather than refused.
15270        assert_eq!(f.run(&[b"TS.CREATE", b"junk", b"FOO"]), "+OK\r\n");
15271        // LABELS eats everything after it in pairs, and the later scans still
15272        // look inside what it ate, so this sets a retention and stores a label
15273        // called RETENTION at the same time.
15274        assert_eq!(
15275            f.run(&[
15276                b"TS.CREATE",
15277                b"g",
15278                b"LABELS",
15279                b"a",
15280                b"b",
15281                b"RETENTION",
15282                b"5"
15283            ]),
15284            "+OK\r\n"
15285        );
15286        let greedy = f.run(&[b"TS.INFO", b"g"]);
15287        assert!(greedy.contains("+retentionTime\r\n:5\r\n"), "{greedy}");
15288        assert!(
15289            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"),
15290            "{greedy}"
15291        );
15292
15293        // Every way an option can be wrong, in the order the module reads them.
15294        assert_eq!(
15295            f.run(&[b"TS.CREATE", b"e", b"LABELS", b"a", b"b(c"]),
15296            "-ERR TSDB: Couldn't parse LABELS\r\n"
15297        );
15298        assert_eq!(
15299            f.run(&[b"TS.CREATE", b"e", b"LABELS", b"", b"b"]),
15300            "-ERR TSDB: Couldn't parse LABELS\r\n"
15301        );
15302        assert_eq!(
15303            f.run(&[b"TS.CREATE", b"e", b"RETENTION"]),
15304            "-ERR TSDB: Couldn't parse RETENTION\r\n"
15305        );
15306        // A retention below zero is one of the two the module writes with no
15307        // ERR in front of it, where one that is not a number gets one.
15308        assert_eq!(
15309            f.run(&[b"TS.CREATE", b"e", b"RETENTION", b"-1"]),
15310            "-TSDB: Couldn't parse RETENTION\r\n"
15311        );
15312        assert_eq!(
15313            f.run(&[b"TS.CREATE", b"e", b"CHUNK_SIZE", b"abc"]),
15314            "-ERR TSDB: Couldn't parse CHUNK_SIZE\r\n"
15315        );
15316        assert_eq!(
15317            f.run(&[b"TS.CREATE", b"e", b"CHUNK_SIZE", b"100"]),
15318            "-ERR TSDB: CHUNK_SIZE value must be a multiple of 8 in the range [48 .. 1048576]\r\n"
15319        );
15320        assert_eq!(
15321            f.run(&[b"TS.CREATE", b"e", b"ENCODING", b"nope"]),
15322            "-ERR TSDB: unknown ENCODING parameter\r\n"
15323        );
15324        // And an ENCODING with nothing behind it is an arity error where every
15325        // other keyword in the same spot is a sentence.
15326        assert!(
15327            f.run(&[b"TS.CREATE", b"e", b"ENCODING"])
15328                .contains("wrong number of arguments for 'ts.create' command")
15329        );
15330        assert_eq!(
15331            f.run(&[b"TS.CREATE", b"e", b"DUPLICATE_POLICY"]),
15332            "-ERR TSDB: Couldn't parse DUPLICATE_POLICY\r\n"
15333        );
15334        assert_eq!(
15335            f.run(&[b"TS.CREATE", b"e", b"DUPLICATE_POLICY", b"nope"]),
15336            "-ERR TSDB: Unknown DUPLICATE_POLICY\r\n"
15337        );
15338        assert_eq!(
15339            f.run(&[b"TS.CREATE", b"e", b"IGNORE", b"10"]),
15340            "-ERR TSDB: Couldn't parse IGNORE\r\n"
15341        );
15342        assert_eq!(
15343            f.run(&[b"TS.CREATE", b"e", b"IGNORE", b"-1", b"1"]),
15344            "-ERR TSDB: IGNORE arguments cannot be negative\r\n"
15345        );
15346        assert_eq!(f.run(&[b"EXISTS", b"e"]), ":0\r\n");
15347
15348        // An alter changes what was named and leaves the rest alone, and reads
15349        // an encoding only far enough to refuse a bad one.
15350        assert_eq!(f.run(&[b"TS.ALTER", b"t", b"RETENTION", b"9"]), "+OK\r\n");
15351        let after = f.run(&[b"TS.INFO", b"t"]);
15352        assert!(after.contains("+retentionTime\r\n:9\r\n"), "{after}");
15353        assert!(after.contains("+chunkSize\r\n:128\r\n"), "{after}");
15354        assert!(after.contains("+duplicatePolicy\r\n+last\r\n"), "{after}");
15355        assert_eq!(
15356            f.run(&[b"TS.ALTER", b"t", b"ENCODING", b"nope"]),
15357            "-ERR TSDB: unknown ENCODING parameter\r\n"
15358        );
15359        // An encoding it does take is still not applied.
15360        assert_eq!(
15361            f.run(&[b"TS.ALTER", b"t", b"ENCODING", b"COMPRESSED"]),
15362            "+OK\r\n"
15363        );
15364        assert!(
15365            f.run(&[b"TS.INFO", b"t"])
15366                .contains("+chunkType\r\n+uncompressed\r\n")
15367        );
15368    }
15369
15370    /// Samples go in, come back out and are refused for the reasons the module
15371    /// refuses them.
15372    #[test]
15373    fn samples_land_where_they_are_put_and_the_newest_comes_back() {
15374        let mut f = Fixture::new();
15375        assert_eq!(f.run(&[b"TS.ADD", b"t", b"100", b"1.5"]), ":100\r\n");
15376        // The series was made on the way in.
15377        assert_eq!(f.run(&[b"TYPE", b"t"]), "+TSDB-TYPE\r\n");
15378        assert_eq!(f.run(&[b"TS.ADD", b"t", b"200", b"2"]), ":200\r\n");
15379        // A sample value goes out as a simple string of the shortest digits
15380        // that read back as the same number.
15381        assert_eq!(f.run(&[b"TS.GET", b"t"]), "*2\r\n:200\r\n+2\r\n");
15382        assert_eq!(f.run(&[b"TS.ADD", b"t", b"300", b"1e300"]), ":300\r\n");
15383        assert_eq!(f.run(&[b"TS.GET", b"t"]), "*2\r\n:300\r\n+1E300\r\n");
15384        // An empty series has no newest sample and answers an empty array
15385        // rather than a nil.
15386        assert_eq!(f.run(&[b"TS.CREATE", b"empty"]), "+OK\r\n");
15387        assert_eq!(f.run(&[b"TS.GET", b"empty"]), "*0\r\n");
15388
15389        // The value is read before the key, so a bad one against a key holding
15390        // a string is about the value.
15391        assert_eq!(f.run(&[b"SET", b"str", b"x"]), "+OK\r\n");
15392        assert_eq!(
15393            f.run(&[b"TS.ADD", b"str", b"1", b".5"]),
15394            "-ERR TSDB: invalid value\r\n"
15395        );
15396        // The grammar is tighter than the one a number argument usually gets:
15397        // no leading plus, no bare fraction, no infinity and nothing that does
15398        // not fit.
15399        for bad in [
15400            &b".5"[..],
15401            b"1.",
15402            b"+1",
15403            b" 1",
15404            b"0x10",
15405            b"inf",
15406            b"1e400",
15407            b"--1",
15408            b"1e",
15409        ] {
15410            assert_eq!(
15411                f.run(&[b"TS.ADD", b"v", b"1", bad]),
15412                "-ERR TSDB: invalid value\r\n",
15413                "{}",
15414                String::from_utf8_lossy(bad)
15415            );
15416        }
15417        // And a reading that is not a number is one of three words.
15418        assert_eq!(f.run(&[b"TS.ADD", b"v", b"1", b"NaN"]), ":1\r\n");
15419
15420        // A timestamp that is not a number, and one that is and is below zero,
15421        // are two different sentences.
15422        assert_eq!(
15423            f.run(&[b"TS.ADD", b"t", b"abc", b"1"]),
15424            "-ERR TSDB: invalid timestamp\r\n"
15425        );
15426        assert_eq!(
15427            f.run(&[b"TS.ADD", b"t", b"-1", b"1"]),
15428            "-ERR TSDB: invalid timestamp, must be a nonnegative integer\r\n"
15429        );
15430
15431        // A repeated timestamp is blocked by default, and ON_DUPLICATE on the
15432        // command beats what the series was told.
15433        assert_eq!(
15434            f.run(&[b"TS.ADD", b"t", b"300", b"7"]),
15435            "-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"
15436        );
15437        assert_eq!(
15438            f.run(&[b"TS.ADD", b"t", b"300", b"7", b"ON_DUPLICATE", b"LAST"]),
15439            ":300\r\n"
15440        );
15441        assert_eq!(f.run(&[b"TS.GET", b"t"]), "*2\r\n:300\r\n+7\r\n");
15442        // ON_DUPLICATE is only read when the key was already there, which is
15443        // why a policy word that is not a policy passes on a fresh key.
15444        assert_eq!(
15445            f.run(&[b"TS.ADD", b"fresh", b"1", b"1", b"ON_DUPLICATE", b"nope"]),
15446            ":1\r\n"
15447        );
15448        assert_eq!(
15449            f.run(&[b"TS.ADD", b"fresh", b"2", b"1", b"ON_DUPLICATE", b"nope"]),
15450            "-ERR TSDB: Unknown DUPLICATE_POLICY\r\n"
15451        );
15452
15453        // Retention is exact and it is checked before anything else happens, so
15454        // a sample landing behind the window is refused rather than trimmed.
15455        assert_eq!(f.run(&[b"TS.CREATE", b"r", b"RETENTION", b"50"]), "+OK\r\n");
15456        assert_eq!(f.run(&[b"TS.ADD", b"r", b"1000", b"1"]), ":1000\r\n");
15457        assert_eq!(f.run(&[b"TS.ADD", b"r", b"960", b"1"]), ":960\r\n");
15458        assert_eq!(
15459            f.run(&[b"TS.ADD", b"r", b"940", b"1"]),
15460            "-ERR TSDB: Timestamp is older than retention\r\n"
15461        );
15462        // And the window trims as it moves.
15463        assert_eq!(f.run(&[b"TS.ADD", b"r", b"1100", b"1"]), ":1100\r\n");
15464        assert!(
15465            f.run(&[b"TS.INFO", b"r"])
15466                .contains("+totalSamples\r\n:1\r\n")
15467        );
15468
15469        // An ignore window drops a sample close enough to the newest one to be
15470        // uninteresting, and answers the newest timestamp so a client can tell.
15471        assert_eq!(
15472            f.run(&[
15473                b"TS.CREATE",
15474                b"i",
15475                b"DUPLICATE_POLICY",
15476                b"LAST",
15477                b"IGNORE",
15478                b"10",
15479                b"0.5"
15480            ]),
15481            "+OK\r\n"
15482        );
15483        assert_eq!(f.run(&[b"TS.ADD", b"i", b"1000", b"1"]), ":1000\r\n");
15484        assert_eq!(f.run(&[b"TS.ADD", b"i", b"1005", b"1.2"]), ":1000\r\n");
15485        assert_eq!(f.run(&[b"TS.ADD", b"i", b"1005", b"9"]), ":1005\r\n");
15486    }
15487
15488    /// Every triple in a `TS.MADD` is answered on its own, and none of them
15489    /// makes a series.
15490    #[test]
15491    fn a_madd_answers_each_triple_and_creates_nothing() {
15492        let mut f = Fixture::new();
15493        assert_eq!(f.run(&[b"TS.CREATE", b"a"]), "+OK\r\n");
15494        assert_eq!(f.run(&[b"TS.CREATE", b"b"]), "+OK\r\n");
15495        assert_eq!(
15496            f.run(&[
15497                b"TS.MADD", b"a", b"100", b"1", b"b", b"100", b"2", b"a", b"200", b"3"
15498            ]),
15499            "*3\r\n:100\r\n:100\r\n:200\r\n"
15500        );
15501        // A key that is not a series is an error in its own slot and the ones
15502        // after it still land.
15503        assert_eq!(f.run(&[b"SET", b"str", b"x"]), "+OK\r\n");
15504        assert_eq!(
15505            f.run(&[
15506                b"TS.MADD", b"gone", b"1", b"1", b"str", b"1", b"1", b"a", b"300", b"4"
15507            ]),
15508            "*3\r\n\
15509             -ERR TSDB: the key is not a TSDB key\r\n\
15510             -ERR TSDB: the key is not a TSDB key\r\n\
15511             :300\r\n"
15512        );
15513        assert_eq!(f.run(&[b"EXISTS", b"gone"]), ":0\r\n");
15514        // A bad value and a bad timestamp are answered in their slots too.
15515        assert_eq!(
15516            f.run(&[b"TS.MADD", b"a", b"400", b"zzz", b"a", b"abc", b"1"]),
15517            "*2\r\n-ERR TSDB: invalid value\r\n-ERR TSDB: invalid timestamp\r\n"
15518        );
15519        // And a list that is not made of triples is an arity error.
15520        assert!(
15521            f.run(&[b"TS.MADD", b"a", b"1", b"1", b"a"])
15522                .contains("wrong number of arguments for 'ts.madd' command")
15523        );
15524    }
15525
15526    /// The two increments, which only ever write forwards.
15527    #[test]
15528    fn an_increment_walks_the_newest_value_up_and_down() {
15529        let mut f = Fixture::new();
15530        assert_eq!(
15531            f.run(&[b"TS.INCRBY", b"t", b"5", b"TIMESTAMP", b"100"]),
15532            ":100\r\n"
15533        );
15534        assert_eq!(
15535            f.run(&[b"TS.INCRBY", b"t", b"5", b"TIMESTAMP", b"100"]),
15536            ":100\r\n"
15537        );
15538        // Two on one timestamp add up rather than collide, because the sample
15539        // goes in under the last policy whatever the series says.
15540        assert_eq!(f.run(&[b"TS.GET", b"t"]), "*2\r\n:100\r\n+10\r\n");
15541        assert_eq!(
15542            f.run(&[b"TS.DECRBY", b"t", b"3", b"TIMESTAMP", b"200"]),
15543            ":200\r\n"
15544        );
15545        assert_eq!(f.run(&[b"TS.GET", b"t"]), "*2\r\n:200\r\n+7\r\n");
15546        // A timestamp behind the newest sample is the other of the two errors
15547        // the module writes with no ERR in front of it.
15548        assert_eq!(
15549            f.run(&[b"TS.INCRBY", b"t", b"1", b"TIMESTAMP", b"150"]),
15550            "-TSDB: timestamp must be equal to or higher than the maximum existing timestamp\r\n"
15551        );
15552        // The increment goes through the ordinary number reader, so it takes
15553        // what a sample value will not and refuses a NaN that a sample value
15554        // takes.
15555        assert_eq!(
15556            f.run(&[b"TS.INCRBY", b"p", b"+5", b"TIMESTAMP", b"1"]),
15557            ":1\r\n"
15558        );
15559        assert_eq!(
15560            f.run(&[b"TS.INCRBY", b"q", b".5", b"TIMESTAMP", b"1"]),
15561            ":1\r\n"
15562        );
15563        assert_eq!(
15564            f.run(&[b"TS.INCRBY", b"t", b"nan"]),
15565            "-ERR TSDB: invalid increase/decrease value\r\n"
15566        );
15567        assert_eq!(
15568            f.run(&[b"TS.INCRBY", b"t", b"zzz"]),
15569            "-ERR TSDB: invalid increase/decrease value\r\n"
15570        );
15571        // A key holding something else is WRONGTYPE and is answered before the
15572        // number is looked at.
15573        assert_eq!(f.run(&[b"SET", b"str", b"x"]), "+OK\r\n");
15574        assert_eq!(
15575            f.run(&[b"TS.INCRBY", b"str", b"zzz"]),
15576            "-ERR WRONGTYPE Operation against a key holding the wrong kind of value\r\n"
15577        );
15578        // A TIMESTAMP keyword with nothing behind it is about the timestamp.
15579        // The reference reads one past the end of its own arguments here and
15580        // answers whatever was in that memory, so there is nothing to copy and
15581        // this answers the same thing every time.
15582        assert_eq!(
15583            f.run(&[b"TS.INCRBY", b"t", b"1", b"TIMESTAMP"]),
15584            "-ERR TSDB: invalid timestamp\r\n"
15585        );
15586        // And one behind a LABELS is a label name rather than the keyword, so
15587        // this lands at the clock rather than at 5.
15588        assert_eq!(
15589            f.run(&[b"TS.INCRBY", b"lab", b"1", b"LABELS", b"TIMESTAMP", b"5"]),
15590            format!(":{}\r\n", f.server.now_ms())
15591        );
15592        // Adding to a series whose newest value is not a number has no answer.
15593        assert_eq!(f.run(&[b"TS.ADD", b"n", b"1", b"nan"]), ":1\r\n");
15594        assert_eq!(
15595            f.run(&[b"TS.INCRBY", b"n", b"1", b"TIMESTAMP", b"2"]),
15596            "-ERR TSDB: cannot increment/decrement NaN value\r\n"
15597        );
15598    }
15599
15600    /// Deleting a span, both ends included.
15601    #[test]
15602    fn deleting_takes_out_a_span_and_answers_how_many_went() {
15603        let mut f = Fixture::new();
15604        for at in [b"100".as_slice(), b"200", b"300", b"400"] {
15605            f.run(&[b"TS.ADD", b"t", at, b"1"]);
15606        }
15607        assert_eq!(f.run(&[b"TS.DEL", b"t", b"200", b"300"]), ":2\r\n");
15608        assert!(
15609            f.run(&[b"TS.INFO", b"t"])
15610                .contains("+totalSamples\r\n:2\r\n")
15611        );
15612        // Ends the wrong way round take nothing out rather than being an error.
15613        assert_eq!(f.run(&[b"TS.DEL", b"t", b"400", b"100"]), ":0\r\n");
15614        // The two open ends.
15615        assert_eq!(f.run(&[b"TS.DEL", b"t", b"-", b"+"]), ":2\r\n");
15616        // A series everything has been deleted from keeps its chunk and reports
15617        // zero at both ends again.
15618        let empty = f.run(&[b"TS.INFO", b"t"]);
15619        assert!(empty.contains("+totalSamples\r\n:0\r\n"), "{empty}");
15620        assert!(empty.contains("+chunkCount\r\n:1\r\n"), "{empty}");
15621        assert!(empty.contains("+firstTimestamp\r\n:0\r\n"), "{empty}");
15622        assert!(empty.contains("+lastTimestamp\r\n:0\r\n"), "{empty}");
15623        assert_eq!(f.run(&[b"TS.DEL", b"t", b"0", b"1000"]), ":0\r\n");
15624        // The two ends have their own sentences.
15625        assert_eq!(
15626            f.run(&[b"TS.DEL", b"t", b"abc", b"5"]),
15627            "-ERR TSDB: wrong fromTimestamp\r\n"
15628        );
15629        assert_eq!(
15630            f.run(&[b"TS.DEL", b"t", b"5", b"abc"]),
15631            "-ERR TSDB: wrong toTimestamp\r\n"
15632        );
15633        assert_eq!(
15634            f.run(&[b"TS.DEL", b"t", b"-5", b"5"]),
15635            "-ERR TSDB: wrong fromTimestamp\r\n"
15636        );
15637    }
15638
15639    /// What RESP3 changes, which is the two places a number is written and the
15640    /// shape of `TS.INFO`.
15641    #[test]
15642    fn resp3_writes_a_sample_as_a_double_and_the_info_as_a_map() {
15643        let mut f = Fixture::new();
15644        f.out = Out::new(Proto::Resp3);
15645        assert_eq!(
15646            f.run(&[b"TS.CREATE", b"t", b"LABELS", b"room", b"kitchen"]),
15647            "+OK\r\n"
15648        );
15649        assert_eq!(f.run(&[b"TS.ADD", b"t", b"100", b"1e300"]), ":100\r\n");
15650        // A double rather than the simple string RESP2 gets.
15651        assert_eq!(f.run(&[b"TS.GET", b"t"]), "*2\r\n:100\r\n,1e+300\r\n");
15652        assert_eq!(
15653            without_memory(&f.run(&[b"TS.INFO", b"t"])),
15654            "%14\r\n\
15655             +totalSamples\r\n:1\r\n\
15656             +memoryUsage\r\n:\r\n\
15657             +firstTimestamp\r\n:100\r\n\
15658             +lastTimestamp\r\n:100\r\n\
15659             +retentionTime\r\n:0\r\n\
15660             +chunkCount\r\n:1\r\n\
15661             +chunkSize\r\n:4096\r\n\
15662             +chunkType\r\n+compressed\r\n\
15663             +duplicatePolicy\r\n+block\r\n\
15664             +labels\r\n%1\r\n$4\r\nroom\r\n$7\r\nkitchen\r\n\
15665             +sourceKey\r\n_\r\n\
15666             +rules\r\n%0\r\n\
15667             +ignoreMaxTimeDiff\r\n:0\r\n\
15668             +ignoreMaxValDiff\r\n,0\r\n"
15669        );
15670    }
15671
15672    /// Reading a span back, both ways round, with the two ends and the three
15673    /// things that trim what comes out.
15674    #[test]
15675    fn a_range_walks_a_span_and_a_revrange_walks_it_backwards() {
15676        let mut f = Fixture::new();
15677        for (at, v) in [
15678            (b"100".as_slice(), b"1".as_slice()),
15679            (b"200", b"2"),
15680            (b"300", b"3"),
15681            (b"400", b"4"),
15682        ] {
15683            f.run(&[b"TS.ADD", b"t", at, v]);
15684        }
15685        assert_eq!(
15686            f.run(&[b"TS.RANGE", b"t", b"-", b"+"]),
15687            "*4\r\n*2\r\n:100\r\n+1\r\n*2\r\n:200\r\n+2\r\n\
15688             *2\r\n:300\r\n+3\r\n*2\r\n:400\r\n+4\r\n"
15689        );
15690        // Both ends are included.
15691        assert_eq!(
15692            f.run(&[b"TS.RANGE", b"t", b"150", b"350"]),
15693            "*2\r\n*2\r\n:200\r\n+2\r\n*2\r\n:300\r\n+3\r\n"
15694        );
15695        // Backwards, and the count takes from the front of what comes out, so
15696        // backwards it takes the newest.
15697        assert_eq!(
15698            f.run(&[b"TS.REVRANGE", b"t", b"-", b"+", b"COUNT", b"2"]),
15699            "*2\r\n*2\r\n:400\r\n+4\r\n*2\r\n:300\r\n+3\r\n"
15700        );
15701        // Ends the wrong way round are empty rather than an error.
15702        assert_eq!(f.run(&[b"TS.RANGE", b"t", b"400", b"100"]), "*0\r\n");
15703        // The two filters.
15704        assert_eq!(
15705            f.run(&[
15706                b"TS.RANGE",
15707                b"t",
15708                b"-",
15709                b"+",
15710                b"FILTER_BY_VALUE",
15711                b"2",
15712                b"3"
15713            ]),
15714            "*2\r\n*2\r\n:200\r\n+2\r\n*2\r\n:300\r\n+3\r\n"
15715        );
15716        assert_eq!(
15717            f.run(&[
15718                b"TS.RANGE",
15719                b"t",
15720                b"-",
15721                b"+",
15722                b"FILTER_BY_TS",
15723                b"100",
15724                b"400"
15725            ]),
15726            "*2\r\n*2\r\n:100\r\n+1\r\n*2\r\n:400\r\n+4\r\n"
15727        );
15728        // A word that is not an option is ignored wherever it sits.
15729        assert_eq!(
15730            f.run(&[
15731                b"TS.RANGE",
15732                b"t",
15733                b"-",
15734                b"+",
15735                b"ZZZ",
15736                b"FILTER_BY_TS",
15737                b"400"
15738            ]),
15739            "*1\r\n*2\r\n:400\r\n+4\r\n"
15740        );
15741        // `LATEST` means nothing until there is a compaction rule to follow.
15742        assert_eq!(
15743            f.run(&[b"TS.RANGE", b"t", b"-", b"+", b"LATEST", b"COUNT", b"1"]),
15744            "*1\r\n*2\r\n:100\r\n+1\r\n"
15745        );
15746    }
15747
15748    /// The bucketing, which is one column a reduction and a flat row.
15749    #[test]
15750    fn aggregation_puts_one_column_a_reduction_in_a_flat_row() {
15751        let mut f = Fixture::new();
15752        for (at, v) in [
15753            (b"100".as_slice(), b"1".as_slice()),
15754            (b"200", b"2"),
15755            (b"300", b"3"),
15756            (b"400", b"4"),
15757        ] {
15758            f.run(&[b"TS.ADD", b"t", at, v]);
15759        }
15760        assert_eq!(
15761            f.run(&[
15762                b"TS.RANGE",
15763                b"t",
15764                b"-",
15765                b"+",
15766                b"AGGREGATION",
15767                b"avg",
15768                b"200"
15769            ]),
15770            "*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"
15771        );
15772        // Three reductions is a row of four and not a row of two with a nested
15773        // three in it.
15774        assert_eq!(
15775            f.run(&[
15776                b"TS.RANGE",
15777                b"t",
15778                b"-",
15779                b"+",
15780                b"AGGREGATION",
15781                b"min,max,count",
15782                b"200"
15783            ]),
15784            "*3\r\n\
15785             *4\r\n:0\r\n+1\r\n+1\r\n+1\r\n\
15786             *4\r\n:200\r\n+2\r\n+3\r\n+2\r\n\
15787             *4\r\n:400\r\n+4\r\n+4\r\n+1\r\n"
15788        );
15789        // The timestamp a bucket is reported under.
15790        assert_eq!(
15791            f.run(&[
15792                b"TS.RANGE",
15793                b"t",
15794                b"-",
15795                b"+",
15796                b"AGGREGATION",
15797                b"avg",
15798                b"200",
15799                b"BUCKETTIMESTAMP",
15800                b"+"
15801            ]),
15802            "*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"
15803        );
15804        // An alignment moves where the bucket edges land.
15805        assert_eq!(
15806            f.run(&[
15807                b"TS.RANGE",
15808                b"t",
15809                b"100",
15810                b"400",
15811                b"ALIGN",
15812                b"100",
15813                b"AGGREGATION",
15814                b"sum",
15815                b"200"
15816            ]),
15817            "*2\r\n*2\r\n:100\r\n+3\r\n*2\r\n:300\r\n+7\r\n"
15818        );
15819        // A `COUNT` sitting where the reduction name belongs is that name, and
15820        // the scan for a real one starts again two words later.
15821        assert_eq!(
15822            f.run(&[
15823                b"TS.RANGE",
15824                b"t",
15825                b"-",
15826                b"+",
15827                b"AGGREGATION",
15828                b"count",
15829                b"200"
15830            ]),
15831            "*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"
15832        );
15833        assert_eq!(
15834            f.run(&[
15835                b"TS.RANGE",
15836                b"t",
15837                b"-",
15838                b"+",
15839                b"AGGREGATION",
15840                b"count",
15841                b"200",
15842                b"COUNT",
15843                b"1"
15844            ]),
15845            "*1\r\n*2\r\n:0\r\n+1\r\n"
15846        );
15847    }
15848
15849    /// `EMPTY` fills the gaps between readings and nothing else, and `last`
15850    /// carries two different things depending on which kind of empty it is.
15851    #[test]
15852    fn empty_fills_a_gap_and_last_carries_the_reading_before_it() {
15853        let mut f = Fixture::new();
15854        for (at, v) in [
15855            (b"0".as_slice(), b"1".as_slice()),
15856            (b"100", b"2"),
15857            (b"500", b"nan"),
15858            (b"600", b"3"),
15859        ] {
15860            f.run(&[b"TS.ADD", b"g", at, v]);
15861        }
15862        // Without `EMPTY` the buckets with nothing in them are not there at all,
15863        // and neither is the one holding only a reading that is not a number.
15864        assert_eq!(
15865            f.run(&[
15866                b"TS.RANGE",
15867                b"g",
15868                b"-",
15869                b"+",
15870                b"AGGREGATION",
15871                b"avg",
15872                b"100"
15873            ]),
15874            "*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"
15875        );
15876        // The sum of nothing is zero rather than not a number.
15877        assert_eq!(
15878            f.run(&[
15879                b"TS.RANGE",
15880                b"g",
15881                b"-",
15882                b"+",
15883                b"AGGREGATION",
15884                b"sum",
15885                b"100",
15886                b"EMPTY"
15887            ]),
15888            "*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\
15889             *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\
15890             *2\r\n:600\r\n+3\r\n"
15891        );
15892        // Buckets 200 through 400 have no readings at all and carry the reading
15893        // before the gap either way round. Bucket 500 has a reading that is not
15894        // a number, so it carries whatever the bucket before it in the reading
15895        // direction answered, which is 2 forwards and 3 backwards.
15896        assert_eq!(
15897            f.run(&[
15898                b"TS.RANGE",
15899                b"g",
15900                b"-",
15901                b"+",
15902                b"AGGREGATION",
15903                b"last",
15904                b"100",
15905                b"EMPTY"
15906            ]),
15907            "*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\
15908             *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\
15909             *2\r\n:600\r\n+3\r\n"
15910        );
15911        assert_eq!(
15912            f.run(&[
15913                b"TS.REVRANGE",
15914                b"g",
15915                b"-",
15916                b"+",
15917                b"AGGREGATION",
15918                b"last",
15919                b"100",
15920                b"EMPTY"
15921            ]),
15922            "*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\
15923             *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\
15924             *2\r\n:0\r\n+1\r\n"
15925        );
15926        // And a window that opens on that bucket has nothing in range before it
15927        // to carry, so it answers not a number.
15928        assert_eq!(
15929            f.run(&[
15930                b"TS.RANGE",
15931                b"g",
15932                b"500",
15933                b"600",
15934                b"AGGREGATION",
15935                b"last",
15936                b"100",
15937                b"EMPTY"
15938            ]),
15939            "*2\r\n*2\r\n:500\r\n+NaN\r\n*2\r\n:600\r\n+3\r\n"
15940        );
15941    }
15942
15943    /// The sentences a read answers when its options do not add up, which are
15944    /// the module's own word for word.
15945    #[test]
15946    fn a_range_says_what_the_module_says_when_the_options_do_not_add_up() {
15947        let mut f = Fixture::new();
15948        f.run(&[b"TS.ADD", b"t", b"100", b"1"]);
15949        f.run(&[b"SET", b"str", b"x"]);
15950        let cases: &[(&[&[u8]], &str)] = &[
15951            (
15952                &[b"TS.RANGE", b"t"],
15953                "-ERR wrong number of arguments for 'ts.range' command\r\n",
15954            ),
15955            // The key is resolved before a single option is read.
15956            (
15957                &[b"TS.RANGE", b"gone", b"-", b"+", b"COUNT", b"x"],
15958                "-ERR TSDB: the key does not exist\r\n",
15959            ),
15960            (
15961                &[b"TS.RANGE", b"str", b"-", b"+"],
15962                "-ERR WRONGTYPE Operation against a key holding the wrong kind of value\r\n",
15963            ),
15964            (
15965                &[b"TS.RANGE", b"t", b"abc", b"+"],
15966                "-ERR TSDB: wrong fromTimestamp\r\n",
15967            ),
15968            (
15969                &[b"TS.RANGE", b"t", b"-", b"abc"],
15970                "-ERR TSDB: wrong toTimestamp\r\n",
15971            ),
15972            (
15973                &[b"TS.RANGE", b"t", b"-", b"+", b"COUNT"],
15974                "-ERR TSDB: COUNT argument is missing\r\n",
15975            ),
15976            (
15977                &[b"TS.RANGE", b"t", b"-", b"+", b"COUNT", b"x"],
15978                "-ERR TSDB: Couldn't parse COUNT\r\n",
15979            ),
15980            (
15981                &[b"TS.RANGE", b"t", b"-", b"+", b"COUNT", b"0"],
15982                "-ERR TSDB: Invalid COUNT value\r\n",
15983            ),
15984            (
15985                &[b"TS.RANGE", b"t", b"-", b"+", b"AGGREGATION", b"avg"],
15986                "-ERR TSDB: Couldn't parse AGGREGATION\r\n",
15987            ),
15988            (
15989                &[b"TS.RANGE", b"t", b"-", b"+", b"AGGREGATION", b"avg", b"x"],
15990                "-ERR TSDB: Couldn't parse AGGREGATION\r\n",
15991            ),
15992            (
15993                &[
15994                    b"TS.RANGE",
15995                    b"t",
15996                    b"-",
15997                    b"+",
15998                    b"AGGREGATION",
15999                    b"nope",
16000                    b"100",
16001                ],
16002                "-ERR TSDB: Unknown aggregation type\r\n",
16003            ),
16004            (
16005                &[
16006                    b"TS.RANGE",
16007                    b"t",
16008                    b"-",
16009                    b"+",
16010                    b"AGGREGATION",
16011                    b"avg,,min",
16012                    b"100",
16013                ],
16014                "-ERR TSDB: Empty aggregation type in list\r\n",
16015            ),
16016            // The list of names is read before the width is looked at.
16017            (
16018                &[b"TS.RANGE", b"t", b"-", b"+", b"AGGREGATION", b"nope", b"0"],
16019                "-ERR TSDB: Unknown aggregation type\r\n",
16020            ),
16021            (
16022                &[b"TS.RANGE", b"t", b"-", b"+", b"AGGREGATION", b"avg", b"0"],
16023                "-ERR TSDB: bucketDuration must be greater than zero\r\n",
16024            ),
16025            (
16026                &[
16027                    b"TS.RANGE",
16028                    b"t",
16029                    b"-",
16030                    b"+",
16031                    b"AGGREGATION",
16032                    b"avg",
16033                    b"100",
16034                    b"X",
16035                    b"EMPTY",
16036                ],
16037                "-ERR TSDB: EMPTY flag should be the 3rd or 5th flag after AGGREGATION flag\r\n",
16038            ),
16039            (
16040                &[
16041                    b"TS.RANGE",
16042                    b"t",
16043                    b"-",
16044                    b"+",
16045                    b"AGGREGATION",
16046                    b"avg",
16047                    b"100",
16048                    b"BUCKETTIMESTAMP",
16049                    b"z",
16050                ],
16051                "-ERR TSDB: unknown BUCKETTIMESTAMP parameter\r\n",
16052            ),
16053            (
16054                &[
16055                    b"TS.RANGE",
16056                    b"t",
16057                    b"-",
16058                    b"+",
16059                    b"AGGREGATION",
16060                    b"avg",
16061                    b"100",
16062                    b"X",
16063                    b"Y",
16064                    b"BUCKETTIMESTAMP",
16065                    b"-",
16066                ],
16067                "-ERR TSDB: BUCKETTIMESTAMP flag should be the 3rd or 4th flag after \
16068                 AGGREGATION flag\r\n",
16069            ),
16070            (
16071                &[
16072                    b"TS.RANGE",
16073                    b"t",
16074                    b"-",
16075                    b"+",
16076                    b"ALIGN",
16077                    b"z",
16078                    b"AGGREGATION",
16079                    b"avg",
16080                    b"100",
16081                ],
16082                "-ERR TSDB: unknown ALIGN parameter\r\n",
16083            ),
16084            (
16085                &[b"TS.RANGE", b"t", b"-", b"+", b"ALIGN", b"5"],
16086                "-ERR TSDB: ALIGN parameter can only be used with AGGREGATION\r\n",
16087            ),
16088            (
16089                &[
16090                    b"TS.RANGE",
16091                    b"t",
16092                    b"-",
16093                    b"+",
16094                    b"ALIGN",
16095                    b"-",
16096                    b"AGGREGATION",
16097                    b"avg",
16098                    b"100",
16099                ],
16100                "-ERR TSDB: start alignment can only be used with explicit start timestamp\r\n",
16101            ),
16102            (
16103                &[b"TS.RANGE", b"t", b"-", b"+", b"FILTER_BY_VALUE", b"1"],
16104                "-ERR TSDB: FILTER_BY_VALUE one or more arguments are missing\r\n",
16105            ),
16106            (
16107                &[
16108                    b"TS.RANGE",
16109                    b"t",
16110                    b"-",
16111                    b"+",
16112                    b"FILTER_BY_VALUE",
16113                    b"x",
16114                    b"2",
16115                ],
16116                "-ERR TSDB: Couldn't parse MIN\r\n",
16117            ),
16118            (
16119                &[
16120                    b"TS.RANGE",
16121                    b"t",
16122                    b"-",
16123                    b"+",
16124                    b"FILTER_BY_VALUE",
16125                    b"1",
16126                    b"y",
16127                ],
16128                "-ERR TSDB: Couldn't parse MAX\r\n",
16129            ),
16130            (
16131                &[b"TS.RANGE", b"t", b"-", b"+", b"FILTER_BY_TS"],
16132                "-ERR TSDB: FILTER_BY_TS one or more arguments are missing\r\n",
16133            ),
16134        ];
16135        for (argv, want) in cases {
16136            let got = f.run(argv);
16137            assert_eq!(&got, want, "{:?}", argv.last());
16138        }
16139        // The one sentence here that is yo's own rather than the module's, which
16140        // is D-54. A read that would build more rows than yo will build is
16141        // refused instead of attempted.
16142        f.run(&[b"TS.ADD", b"wide", b"0", b"1"]);
16143        f.run(&[b"TS.ADD", b"wide", b"1000000000000", b"2"]);
16144        assert_eq!(
16145            f.run(&[
16146                b"TS.RANGE",
16147                b"wide",
16148                b"-",
16149                b"+",
16150                b"AGGREGATION",
16151                b"avg",
16152                b"1",
16153                b"EMPTY"
16154            ]),
16155            "-ERR TSDB: the requested range holds too many empty buckets\r\n"
16156        );
16157    }
16158
16159    /// What RESP3 changes on a read, which is only how a number is written.
16160    #[test]
16161    fn resp3_writes_a_read_value_as_a_double() {
16162        let mut f = Fixture::new();
16163        f.out = Out::new(Proto::Resp3);
16164        for (at, v) in [
16165            (b"0".as_slice(), b"1".as_slice()),
16166            (b"100", b"2"),
16167            (b"500", b"nan"),
16168            (b"600", b"3"),
16169        ] {
16170            f.run(&[b"TS.ADD", b"g", at, v]);
16171        }
16172        assert_eq!(
16173            f.run(&[
16174                b"TS.RANGE",
16175                b"g",
16176                b"0",
16177                b"100",
16178                b"AGGREGATION",
16179                b"avg,min",
16180                b"200"
16181            ]),
16182            "*1\r\n*3\r\n:0\r\n,1.5\r\n,1\r\n"
16183        );
16184        assert_eq!(
16185            f.run(&[
16186                b"TS.RANGE",
16187                b"g",
16188                b"500",
16189                b"600",
16190                b"AGGREGATION",
16191                b"last",
16192                b"100",
16193                b"EMPTY"
16194            ]),
16195            "*2\r\n*2\r\n:500\r\n,nan\r\n*2\r\n:600\r\n,3\r\n"
16196        );
16197    }
16198
16199    /// Two series with an overlap and a gap each, plus a third holding nothing,
16200    /// which is what the joined reads are measured against.
16201    fn joined() -> Fixture {
16202        let mut f = Fixture::new();
16203        f.run(&[b"TS.CREATE", b"z"]);
16204        for (at, v) in [
16205            (b"10".as_slice(), b"1".as_slice()),
16206            (b"20", b"2"),
16207            (b"40", b"4"),
16208            (b"50", b"5"),
16209        ] {
16210            f.run(&[b"TS.ADD", b"x", at, v]);
16211        }
16212        for (at, v) in [
16213            (b"20".as_slice(), b"20".as_slice()),
16214            (b"30", b"30"),
16215            (b"50", b"50"),
16216            (b"60", b"60"),
16217        ] {
16218            f.run(&[b"TS.ADD", b"y", at, v]);
16219        }
16220        f
16221    }
16222
16223    /// The joined read lines its keys up on the timestamp and writes a row as
16224    /// the timestamp and then a nested array of the columns, which is the one
16225    /// shape in the family that is not the flat pair.
16226    #[test]
16227    fn an_nrange_joins_its_keys_on_the_timestamp() {
16228        let mut f = joined();
16229        // One key still nests, so the shape does not depend on the count.
16230        assert_eq!(
16231            f.run(&[b"TS.NRANGE", b"1", b"x", b"-", b"+"]),
16232            "*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\
16233             *2\r\n:40\r\n*1\r\n+4\r\n*2\r\n:50\r\n*1\r\n+5\r\n"
16234        );
16235        // A key with no reading where another key has one writes NaN there.
16236        assert_eq!(
16237            f.run(&[b"TS.NRANGE", b"2", b"x", b"y", b"-", b"+"]),
16238            "*6\r\n*2\r\n:10\r\n*2\r\n+1\r\n+NaN\r\n\
16239             *2\r\n:20\r\n*2\r\n+2\r\n+20\r\n\
16240             *2\r\n:30\r\n*2\r\n+NaN\r\n+30\r\n\
16241             *2\r\n:40\r\n*2\r\n+4\r\n+NaN\r\n\
16242             *2\r\n:50\r\n*2\r\n+5\r\n+50\r\n\
16243             *2\r\n:60\r\n*2\r\n+NaN\r\n+60\r\n"
16244        );
16245        // A series holding nothing is a column of NaN and never a row of its
16246        // own, and the same key twice answers twice.
16247        assert_eq!(
16248            f.run(&[b"TS.NRANGE", b"2", b"x", b"z", b"20", b"40"]),
16249            "*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"
16250        );
16251        assert_eq!(
16252            f.run(&[b"TS.NRANGE", b"2", b"x", b"x", b"40", b"50"]),
16253            "*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"
16254        );
16255        // COUNT is applied to the joined rows and not to each key, so backwards
16256        // it gives the newest joined row rather than the newest of each.
16257        assert_eq!(
16258            f.run(&[
16259                b"TS.NREVRANGE",
16260                b"2",
16261                b"x",
16262                b"y",
16263                b"-",
16264                b"+",
16265                b"COUNT",
16266                b"1"
16267            ]),
16268            "*1\r\n*2\r\n:60\r\n*2\r\n+NaN\r\n+60\r\n"
16269        );
16270        assert_eq!(
16271            f.run(&[b"TS.NRANGE", b"2", b"x", b"y", b"-", b"+", b"COUNT", b"1"]),
16272            "*1\r\n*2\r\n:10\r\n*2\r\n+1\r\n+NaN\r\n"
16273        );
16274        // The two sample filters are settled a key at a time, before the join.
16275        assert_eq!(
16276            f.run(&[
16277                b"TS.NRANGE",
16278                b"2",
16279                b"x",
16280                b"y",
16281                b"-",
16282                b"+",
16283                b"FILTER_BY_VALUE",
16284                b"2",
16285                b"30"
16286            ]),
16287            "*4\r\n*2\r\n:20\r\n*2\r\n+2\r\n+20\r\n\
16288             *2\r\n:30\r\n*2\r\n+NaN\r\n+30\r\n\
16289             *2\r\n:40\r\n*2\r\n+4\r\n+NaN\r\n\
16290             *2\r\n:50\r\n*2\r\n+5\r\n+NaN\r\n"
16291        );
16292    }
16293
16294    /// The aggregation on a joined read names one reduction a key and then the
16295    /// one bucket width, and each name may be a comma list, so a row can be
16296    /// wider than the key count.
16297    #[test]
16298    fn an_nrange_aggregation_names_one_reduction_a_key() {
16299        let mut f = joined();
16300        assert_eq!(
16301            f.run(&[
16302                b"TS.NRANGE",
16303                b"2",
16304                b"x",
16305                b"y",
16306                b"-",
16307                b"+",
16308                b"AGGREGATION",
16309                b"sum",
16310                b"sum",
16311                b"20"
16312            ]),
16313            "*4\r\n*2\r\n:0\r\n*2\r\n+1\r\n+NaN\r\n\
16314             *2\r\n:20\r\n*2\r\n+2\r\n+50\r\n\
16315             *2\r\n:40\r\n*2\r\n+9\r\n+50\r\n\
16316             *2\r\n:60\r\n*2\r\n+NaN\r\n+60\r\n"
16317        );
16318        // A comma list on the first key widens the row to three columns.
16319        assert_eq!(
16320            f.run(&[
16321                b"TS.NRANGE",
16322                b"2",
16323                b"x",
16324                b"y",
16325                b"-",
16326                b"+",
16327                b"AGGREGATION",
16328                b"sum,count",
16329                b"avg",
16330                b"20"
16331            ]),
16332            "*4\r\n*2\r\n:0\r\n*3\r\n+1\r\n+1\r\n+NaN\r\n\
16333             *2\r\n:20\r\n*3\r\n+2\r\n+1\r\n+25\r\n\
16334             *2\r\n:40\r\n*3\r\n+9\r\n+2\r\n+50\r\n\
16335             *2\r\n:60\r\n*3\r\n+NaN\r\n+NaN\r\n+60\r\n"
16336        );
16337        // Everything behind the width moves along with it, so BUCKETTIMESTAMP
16338        // sits one or two past the width whatever the key count is.
16339        assert_eq!(
16340            f.run(&[
16341                b"TS.NRANGE",
16342                b"2",
16343                b"x",
16344                b"y",
16345                b"-",
16346                b"+",
16347                b"AGGREGATION",
16348                b"avg",
16349                b"sum",
16350                b"100",
16351                b"EMPTY",
16352                b"BUCKETTIMESTAMP",
16353                b"end"
16354            ]),
16355            "*1\r\n*2\r\n:100\r\n*2\r\n+3\r\n+160\r\n"
16356        );
16357        // A COUNT landing in one of the name slots is a reduction name and not
16358        // the keyword, and the read then has no count at all.
16359        assert_eq!(
16360            f.run(&[
16361                b"TS.NRANGE",
16362                b"2",
16363                b"x",
16364                b"y",
16365                b"-",
16366                b"+",
16367                b"AGGREGATION",
16368                b"avg",
16369                b"COUNT",
16370                b"100"
16371            ]),
16372            "*1\r\n*2\r\n:0\r\n*2\r\n+3\r\n+4\r\n"
16373        );
16374    }
16375
16376    /// The sentences a joined read answers when it does not add up, which are
16377    /// the module's own and come out in the module's own order.
16378    #[test]
16379    fn an_nrange_says_what_the_module_says_when_it_does_not_add_up() {
16380        let mut f = joined();
16381        f.run(&[b"SET", b"str", b"hi"]);
16382        let bad_keys = "-ERR TSDB: numkeys must be a positive integer\r\n";
16383        let numkeys = "-ERR TSDB: the number of AGGREGATION arguments \
16384                       must be equal to numkeys\r\n";
16385        let cases: &[(&[&[u8]], &str)] = &[
16386            (&[b"TS.NRANGE", b"0", b"x", b"-", b"+"], bad_keys),
16387            (&[b"TS.NRANGE", b"-1", b"x", b"-", b"+"], bad_keys),
16388            (&[b"TS.NRANGE", b"abc", b"x", b"-", b"+"], bad_keys),
16389            // Not enough words behind the count for the keys and both ends of
16390            // the span, which is an arity error however many keys were named.
16391            (
16392                &[b"TS.NRANGE", b"2", b"x", b"-", b"+"],
16393                "-ERR wrong number of arguments for 'ts.nrange' command\r\n",
16394            ),
16395            (
16396                &[b"TS.NRANGE", b"99", b"x", b"-", b"+"],
16397                "-ERR wrong number of arguments for 'ts.nrange' command\r\n",
16398            ),
16399            // The reduction names are read before the two ends of the span,
16400            // which no other option is.
16401            (
16402                &[
16403                    b"TS.NRANGE",
16404                    b"2",
16405                    b"x",
16406                    b"y",
16407                    b"abc",
16408                    b"+",
16409                    b"AGGREGATION",
16410                    b"nope",
16411                    b"sum",
16412                    b"100",
16413                ],
16414                "-ERR TSDB: Unknown aggregation type\r\n",
16415            ),
16416            (
16417                &[b"TS.NRANGE", b"2", b"x", b"y", b"abc", b"+"],
16418                "-ERR TSDB: wrong fromTimestamp\r\n",
16419            ),
16420            (
16421                &[b"TS.NRANGE", b"2", b"x", b"y", b"-", b"abc"],
16422                "-ERR TSDB: wrong toTimestamp\r\n",
16423            ),
16424            // A name slot that is missing or holds a number is the count
16425            // sentence, and a width slot that is itself a reduction name is
16426            // that sentence as well.
16427            (
16428                &[
16429                    b"TS.NRANGE",
16430                    b"2",
16431                    b"x",
16432                    b"y",
16433                    b"-",
16434                    b"+",
16435                    b"AGGREGATION",
16436                    b"avg",
16437                ],
16438                numkeys,
16439            ),
16440            (
16441                &[
16442                    b"TS.NRANGE",
16443                    b"2",
16444                    b"x",
16445                    b"y",
16446                    b"-",
16447                    b"+",
16448                    b"AGGREGATION",
16449                    b"100",
16450                    b"sum",
16451                    b"100",
16452                ],
16453                numkeys,
16454            ),
16455            (
16456                &[
16457                    b"TS.NRANGE",
16458                    b"2",
16459                    b"x",
16460                    b"y",
16461                    b"-",
16462                    b"+",
16463                    b"AGGREGATION",
16464                    b"avg",
16465                    b"sum",
16466                    b"sum",
16467                    b"100",
16468                ],
16469                numkeys,
16470            ),
16471            (
16472                &[
16473                    b"TS.NRANGE",
16474                    b"2",
16475                    b"x",
16476                    b"y",
16477                    b"-",
16478                    b"+",
16479                    b"AGGREGATION",
16480                    b"avg",
16481                    b"sum",
16482                    b"abc",
16483                ],
16484                "-ERR TSDB: Couldn't parse AGGREGATION\r\n",
16485            ),
16486            (
16487                &[
16488                    b"TS.NRANGE",
16489                    b"2",
16490                    b"x",
16491                    b"y",
16492                    b"-",
16493                    b"+",
16494                    b"AGGREGATION",
16495                    b"avg",
16496                    b"sum",
16497                    b"0",
16498                ],
16499                "-ERR TSDB: bucketDuration must be greater than zero\r\n",
16500            ),
16501            // With one key none of that applies and the plain parser runs, so a
16502            // lone width is a missing width rather than a count mismatch.
16503            (
16504                &[b"TS.NRANGE", b"1", b"x", b"-", b"+", b"AGGREGATION", b"100"],
16505                "-ERR TSDB: Couldn't parse AGGREGATION\r\n",
16506            ),
16507            (
16508                &[
16509                    b"TS.NRANGE",
16510                    b"1",
16511                    b"x",
16512                    b"-",
16513                    b"+",
16514                    b"AGGREGATION",
16515                    b"100",
16516                    b"200",
16517                ],
16518                "-ERR TSDB: Unknown aggregation type\r\n",
16519            ),
16520            // The keys come last and in the order they were named.
16521            (
16522                &[b"TS.NRANGE", b"2", b"x", b"nope", b"-", b"+"],
16523                "-ERR TSDB: the key does not exist\r\n",
16524            ),
16525            (
16526                &[b"TS.NRANGE", b"2", b"str", b"nope", b"-", b"+"],
16527                "-ERR WRONGTYPE Operation against a key \
16528                 holding the wrong kind of value\r\n",
16529            ),
16530        ];
16531        for (argv, want) in cases {
16532            let got = f.run(argv);
16533            assert_eq!(&got, want, "{argv:?}");
16534        }
16535    }
16536
16537    /// `TS.READ`, which is a key, one timestamp and everything from there on.
16538    #[test]
16539    fn a_read_walks_from_a_timestamp_to_the_end_of_the_series() {
16540        let mut f = joined();
16541        assert_eq!(
16542            f.run(&[b"TS.READ", b"x", b"-"]),
16543            "*4\r\n*2\r\n:10\r\n+1\r\n*2\r\n:20\r\n+2\r\n\
16544             *2\r\n:40\r\n+4\r\n*2\r\n:50\r\n+5\r\n"
16545        );
16546        // A plus is the last sample on its own, and a timestamp between two
16547        // samples starts at the one behind it.
16548        assert_eq!(
16549            f.run(&[b"TS.READ", b"x", b"+"]),
16550            "*1\r\n*2\r\n:50\r\n+5\r\n"
16551        );
16552        assert_eq!(
16553            f.run(&[b"TS.READ", b"x", b"25"]),
16554            "*2\r\n*2\r\n:40\r\n+4\r\n*2\r\n:50\r\n+5\r\n"
16555        );
16556        // Past the end, a series holding nothing and a key that is not there
16557        // are all the empty array rather than an error.
16558        assert_eq!(f.run(&[b"TS.READ", b"x", b"99"]), "*0\r\n");
16559        assert_eq!(f.run(&[b"TS.READ", b"z", b"-"]), "*0\r\n");
16560        assert_eq!(f.run(&[b"TS.READ", b"z", b"+"]), "*0\r\n");
16561        assert_eq!(f.run(&[b"TS.READ", b"nope", b"-"]), "*0\r\n");
16562        // The timestamp refusal goes out with nothing in front of it, and a key
16563        // holding something else answers the bare WRONGTYPE rather than the
16564        // module's prefixed one, both unlike the rest of the family.
16565        assert_eq!(
16566            f.run(&[b"TS.READ", b"x", b"abc"]),
16567            "-TSDB: invalid timestamp\r\n"
16568        );
16569        assert_eq!(
16570            f.run(&[b"TS.READ", b"x", b"-1"]),
16571            "-TSDB: invalid timestamp\r\n"
16572        );
16573        f.run(&[b"SET", b"str", b"hi"]);
16574        assert_eq!(
16575            f.run(&[b"TS.READ", b"str", b"-"]),
16576            "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n"
16577        );
16578        // Anything other than exactly three words is an arity error, so there
16579        // is nowhere to put an option even though the table says minus three.
16580        assert_eq!(
16581            f.run(&[b"TS.READ", b"x"]),
16582            "-ERR wrong number of arguments for 'ts.read' command\r\n"
16583        );
16584        assert_eq!(
16585            f.run(&[b"TS.READ", b"x", b"-", b"COUNT", b"1"]),
16586            "-ERR wrong number of arguments for 'ts.read' command\r\n"
16587        );
16588    }
16589
16590    /// The keys of a joined read sit behind a count, so `COMMAND GETKEYS` has
16591    /// to read the count to find them.
16592    #[test]
16593    fn getkeys_reads_the_count_of_a_joined_read() {
16594        let mut f = Fixture::new();
16595        assert_eq!(
16596            f.run(&[
16597                b"COMMAND",
16598                b"GETKEYS",
16599                b"TS.NRANGE",
16600                b"2",
16601                b"a",
16602                b"b",
16603                b"-",
16604                b"+"
16605            ]),
16606            "*2\r\n$1\r\na\r\n$1\r\nb\r\n"
16607        );
16608        assert_eq!(
16609            f.run(&[
16610                b"COMMAND",
16611                b"GETKEYS",
16612                b"TS.NREVRANGE",
16613                b"1",
16614                b"a",
16615                b"-",
16616                b"+"
16617            ]),
16618            "*1\r\n$1\r\na\r\n"
16619        );
16620        // A count of zero, or one too large for the words that follow it, is
16621        // the server's own refusal and not the module's.
16622        for n in [b"0".as_slice(), b"9", b"abc"] {
16623            assert_eq!(
16624                f.run(&[b"COMMAND", b"GETKEYS", b"TS.NRANGE", n, b"a", b"-", b"+"]),
16625                "-ERR Invalid arguments specified for command\r\n"
16626            );
16627        }
16628    }
16629
16630    /// The five series every test of the label surface works against.
16631    fn labelled() -> Fixture {
16632        let mut f = Fixture::new();
16633        f.run(&[
16634            b"TS.CREATE",
16635            b"a",
16636            b"LABELS",
16637            b"room",
16638            b"kitchen",
16639            b"x",
16640            b"1",
16641        ]);
16642        f.run(&[
16643            b"TS.CREATE",
16644            b"b",
16645            b"LABELS",
16646            b"room",
16647            b"bedroom",
16648            b"x",
16649            b"2",
16650        ]);
16651        f.run(&[b"TS.CREATE", b"c", b"LABELS", b"room", b"kitchen"]);
16652        f.run(&[b"TS.CREATE", b"d"]);
16653        f.run(&[b"TS.CREATE", b"e", b"LABELS", b"r", b"bb", b"r", b"b"]);
16654        f.run(&[b"TS.ADD", b"a", b"100", b"1.5"]);
16655        f.run(&[b"TS.ADD", b"b", b"200", b"2"]);
16656        f
16657    }
16658
16659    /// The filter grammar, which is four steps and a `strtok` rather than a
16660    /// grammar, and which every command that searches on labels shares.
16661    #[test]
16662    fn a_filter_is_taken_apart_the_way_the_module_takes_one_apart() {
16663        let mut f = labelled();
16664        let cases: &[(&[&[u8]], &str)] = &[
16665            // The plain forms, and the order the answer comes back in, which is
16666            // by key name and not by anything the series remembers.
16667            (
16668                &[b"TS.QUERYINDEX", b"room=kitchen"],
16669                "*2\r\n$1\r\na\r\n$1\r\nc\r\n",
16670            ),
16671            (
16672                &[b"TS.QUERYINDEX", b"room=kitchen", b"x=1"],
16673                "*1\r\n$1\r\na\r\n",
16674            ),
16675            (
16676                &[b"TS.QUERYINDEX", b"room=(kitchen,bedroom)"],
16677                "*3\r\n$1\r\na\r\n$1\r\nb\r\n$1\r\nc\r\n",
16678            ),
16679            // An empty list still counts as something that says which series to
16680            // take, it just never takes any.
16681            (&[b"TS.QUERYINDEX", b"room=()"], "*0\r\n"),
16682            // Absent and present, neither of which stands on its own.
16683            (
16684                &[b"TS.QUERYINDEX", b"x=", b"room=kitchen"],
16685                "*1\r\n$1\r\nc\r\n",
16686            ),
16687            (
16688                &[b"TS.QUERYINDEX", b"room=kitchen", b"x!="],
16689                "*1\r\n$1\r\na\r\n",
16690            ),
16691            (
16692                &[b"TS.QUERYINDEX", b"room!=kitchen", b"x!="],
16693                "-ERR TSDB: please provide at least one matcher\r\n",
16694            ),
16695            // A run of separators is one separator and everything past the
16696            // second field is dropped, so all three of these ask one question.
16697            (
16698                &[b"TS.QUERYINDEX", b"room==kitchen"],
16699                "*2\r\n$1\r\na\r\n$1\r\nc\r\n",
16700            ),
16701            (
16702                &[b"TS.QUERYINDEX", b"room=kitchen=zz"],
16703                "*2\r\n$1\r\na\r\n$1\r\nc\r\n",
16704            ),
16705            (&[b"TS.QUERYINDEX", b"room!!=kitchen", b"x=1"], "*0\r\n"),
16706            // A bracket is only a list when it sits straight behind the
16707            // separator, and then the label in front of it has to be there.
16708            (&[b"TS.QUERYINDEX", b"()=1"], "*0\r\n"),
16709            (
16710                &[b"TS.QUERYINDEX", b"=(1)"],
16711                "-ERR TSDB: failed parsing labels\r\n",
16712            ),
16713            (
16714                &[b"TS.QUERYINDEX", b"room=(kitchen,)"],
16715                "-ERR TSDB: failed parsing labels\r\n",
16716            ),
16717            (
16718                &[b"TS.QUERYINDEX", b"room=(kitchen"],
16719                "-ERR TSDB: failed parsing labels\r\n",
16720            ),
16721            (&[b"TS.QUERYINDEX", b"room=x()"], "*0\r\n"),
16722            (
16723                &[b"TS.QUERYINDEX", b"nonsense"],
16724                "-ERR TSDB: failed parsing labels\r\n",
16725            ),
16726            // Nothing here says which series to take.
16727            (
16728                &[b"TS.QUERYINDEX", b"room!=kitchen"],
16729                "-ERR TSDB: please provide at least one matcher\r\n",
16730            ),
16731            // Names and values are both compared byte for byte.
16732            (&[b"TS.QUERYINDEX", b"ROOM=kitchen"], "*0\r\n"),
16733            (&[b"TS.QUERYINDEX", b"room=KITCHEN"], "*0\r\n"),
16734            (
16735                &[b"TS.QUERYINDEX"],
16736                "-ERR wrong number of arguments for 'ts.queryindex' command\r\n",
16737            ),
16738        ];
16739        for (argv, want) in cases {
16740            let got = f.run(argv);
16741            assert_eq!(&got, want, "{:?}", argv.last());
16742        }
16743    }
16744
16745    /// `TS.QUERYLABELS`, whose filter is the one that is allowed to be missing.
16746    #[test]
16747    fn querylabels_says_which_names_are_worn_and_what_they_are_set_to() {
16748        let mut f = labelled();
16749        let cases: &[(&[&[u8]], &str)] = &[
16750            (
16751                &[b"TS.QUERYLABELS", b"LABELS"],
16752                "*3\r\n$1\r\nr\r\n$4\r\nroom\r\n$1\r\nx\r\n",
16753            ),
16754            (
16755                &[b"TS.QUERYLABELS", b"LABELS", b"FILTER", b"room=kitchen"],
16756                "*2\r\n$4\r\nroom\r\n$1\r\nx\r\n",
16757            ),
16758            (
16759                &[b"TS.QUERYLABELS", b"VALUES", b"room"],
16760                "*2\r\n$7\r\nbedroom\r\n$7\r\nkitchen\r\n",
16761            ),
16762            // The series wearing `r` twice contributes the smaller of the two
16763            // here, which is not the one it was written down as first.
16764            (&[b"TS.QUERYLABELS", b"VALUES", b"r"], "*1\r\n$1\r\nb\r\n"),
16765            (&[b"TS.QUERYLABELS", b"VALUES", b"nolabel"], "*0\r\n"),
16766            (
16767                &[b"TS.QUERYLABELS", b"VALUES"],
16768                "-ERR wrong number of arguments for 'ts.querylabels' command\r\n",
16769            ),
16770            (
16771                &[b"TS.QUERYLABELS", b"ZZZ"],
16772                "-ERR TSDB: unknown subtype, must be one of LABELS|VALUES\r\n",
16773            ),
16774            (
16775                &[b"TS.QUERYLABELS", b"LABELS", b"ZZZ"],
16776                "-ERR TSDB: unknown argument, expected FILTER\r\n",
16777            ),
16778            (
16779                &[b"TS.QUERYLABELS", b"LABELS", b"FILTER"],
16780                "-ERR TSDB: FILTER given with no filter expressions\r\n",
16781            ),
16782            // With no filter at all every series is taken, which is why the
16783            // first case here answers about `r` as well. A filter that is there
16784            // still has to say which series to take.
16785            (
16786                &[b"TS.QUERYLABELS", b"LABELS", b"FILTER", b"room!=kitchen"],
16787                "-ERR TSDB: please provide at least one matcher\r\n",
16788            ),
16789            (
16790                &[
16791                    b"TS.QUERYLABELS",
16792                    b"LABELS",
16793                    b"FILTER",
16794                    b"room=kitchen",
16795                    b"x=",
16796                ],
16797                "*1\r\n$4\r\nroom\r\n",
16798            ),
16799        ];
16800        for (argv, want) in cases {
16801            let got = f.run(argv);
16802            assert_eq!(&got, want, "{:?}", argv.last());
16803        }
16804    }
16805
16806    /// `TS.MGET`, the newest sample of every series a filter takes, and the two
16807    /// ways of asking for the labels back alongside it.
16808    #[test]
16809    fn mget_writes_the_newest_sample_and_the_labels_that_were_asked_for() {
16810        let mut f = labelled();
16811        let cases: &[(&[&[u8]], &str)] = &[
16812            // A series with no samples writes an empty array where the sample
16813            // goes rather than dropping out of the reply.
16814            (
16815                &[b"TS.MGET", b"FILTER", b"room=kitchen"],
16816                "*2\r\n*3\r\n$1\r\na\r\n*0\r\n*2\r\n:100\r\n+1.5\r\n\
16817                 *3\r\n$1\r\nc\r\n*0\r\n*0\r\n",
16818            ),
16819            (
16820                &[b"TS.MGET", b"WITHLABELS", b"FILTER", b"room=kitchen"],
16821                "*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\
16822                 *2\r\n$1\r\nx\r\n$1\r\n1\r\n*2\r\n:100\r\n+1.5\r\n\
16823                 *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",
16824            ),
16825            // A selected label the series does not wear is a nil, not a gap.
16826            (
16827                &[
16828                    b"TS.MGET",
16829                    b"SELECTED_LABELS",
16830                    b"x",
16831                    b"FILTER",
16832                    b"room=kitchen",
16833                ],
16834                "*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\
16835                 *2\r\n:100\r\n+1.5\r\n\
16836                 *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",
16837            ),
16838            // The other half of the duplicated name rule. This one takes the
16839            // first written down where `TS.QUERYLABELS` takes the smallest.
16840            (
16841                &[b"TS.MGET", b"SELECTED_LABELS", b"r", b"FILTER", b"r=b"],
16842                "*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",
16843            ),
16844            (
16845                &[b"TS.MGET", b"WITHLABELS", b"FILTER", b"r=b"],
16846                "*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\
16847                 *2\r\n$1\r\nr\r\n$1\r\nb\r\n*0\r\n",
16848            ),
16849            // A word that is not an option is ignored, but a missing `FILTER`
16850            // is an arity error whatever else was written.
16851            (
16852                &[b"TS.MGET", b"ZZZ", b"FILTER", b"room=bedroom"],
16853                "*1\r\n*3\r\n$1\r\nb\r\n*0\r\n*2\r\n:200\r\n+2\r\n",
16854            ),
16855            (
16856                &[b"TS.MGET", b"a", b"b", b"c"],
16857                "-ERR wrong number of arguments for 'ts.mget' command\r\n",
16858            ),
16859            (
16860                &[b"TS.MGET", b"FILTER"],
16861                "-ERR wrong number of arguments for 'ts.mget' command\r\n",
16862            ),
16863            // Both keyword checks happen before the filter is read, and the two
16864            // sentences spell the second keyword without its `ED`.
16865            (
16866                &[
16867                    b"TS.MGET",
16868                    b"WITHLABELS",
16869                    b"SELECTED_LABELS",
16870                    b"x",
16871                    b"FILTER",
16872                    b"bad",
16873                ],
16874                "-ERR TSDB: cannot accept WITHLABELS and SELECT_LABELS together\r\n",
16875            ),
16876            (
16877                &[b"TS.MGET", b"SELECTED_LABELS", b"FILTER", b"bad"],
16878                "-ERR TSDB: SELECT_LABELS should have at least 1 parameter\r\n",
16879            ),
16880        ];
16881        for (argv, want) in cases {
16882            let got = f.run(argv);
16883            assert_eq!(&got, want, "{:?}", argv.last());
16884        }
16885    }
16886
16887    /// What RESP3 changes across the label surface, which is a set where there
16888    /// was an array and a map where there was a pair of them.
16889    #[test]
16890    fn resp3_writes_the_label_surface_as_sets_and_maps() {
16891        let mut f = labelled();
16892        f.out = Out::new(Proto::Resp3);
16893        let cases: &[(&[&[u8]], &str)] = &[
16894            (
16895                &[b"TS.QUERYINDEX", b"room=kitchen"],
16896                "~2\r\n$1\r\na\r\n$1\r\nc\r\n",
16897            ),
16898            (
16899                &[b"TS.QUERYLABELS", b"LABELS"],
16900                "~3\r\n$1\r\nr\r\n$4\r\nroom\r\n$1\r\nx\r\n",
16901            ),
16902            (
16903                &[b"TS.QUERYLABELS", b"VALUES", b"room"],
16904                "~2\r\n$7\r\nbedroom\r\n$7\r\nkitchen\r\n",
16905            ),
16906            // The key stops being the first of three and becomes the map key,
16907            // and the labels stop being pairs and become a map of their own.
16908            (
16909                &[b"TS.MGET", b"FILTER", b"room=kitchen"],
16910                "%2\r\n$1\r\na\r\n*2\r\n%0\r\n*2\r\n:100\r\n,1.5\r\n\
16911                 $1\r\nc\r\n*2\r\n%0\r\n*0\r\n",
16912            ),
16913            (
16914                &[b"TS.MGET", b"WITHLABELS", b"FILTER", b"room=kitchen"],
16915                "%2\r\n$1\r\na\r\n*2\r\n%2\r\n$4\r\nroom\r\n$7\r\nkitchen\r\n\
16916                 $1\r\nx\r\n$1\r\n1\r\n*2\r\n:100\r\n,1.5\r\n\
16917                 $1\r\nc\r\n*2\r\n%1\r\n$4\r\nroom\r\n$7\r\nkitchen\r\n*0\r\n",
16918            ),
16919            (
16920                &[
16921                    b"TS.MGET",
16922                    b"SELECTED_LABELS",
16923                    b"x",
16924                    b"FILTER",
16925                    b"room=kitchen",
16926                ],
16927                "%2\r\n$1\r\na\r\n*2\r\n%1\r\n$1\r\nx\r\n$1\r\n1\r\n\
16928                 *2\r\n:100\r\n,1.5\r\n\
16929                 $1\r\nc\r\n*2\r\n%1\r\n$1\r\nx\r\n_\r\n*0\r\n",
16930            ),
16931            // A map with a name in it twice, which is what a series wearing one
16932            // label name twice turns into.
16933            (
16934                &[b"TS.MGET", b"WITHLABELS", b"FILTER", b"r=b"],
16935                "%1\r\n$1\r\ne\r\n*2\r\n%2\r\n$1\r\nr\r\n$2\r\nbb\r\n\
16936                 $1\r\nr\r\n$1\r\nb\r\n*0\r\n",
16937            ),
16938        ];
16939        for (argv, want) in cases {
16940            let got = f.run(argv);
16941            assert_eq!(&got, want, "{:?}", argv.last());
16942        }
16943    }
16944
16945    /// The same five series with enough samples in them for a group to have
16946    /// something to fold.
16947    fn spanned() -> Fixture {
16948        let mut f = labelled();
16949        f.run(&[b"TS.ADD", b"a", b"200", b"2.5"]);
16950        f.run(&[b"TS.ADD", b"c", b"100", b"10"]);
16951        f.run(&[b"TS.ADD", b"c", b"300", b"30"]);
16952        f
16953    }
16954
16955    /// A span read out of every series a filter takes, with and without a group
16956    /// over the top of it.
16957    #[test]
16958    fn mrange_reads_every_series_and_folds_the_groups_it_is_asked_for() {
16959        let mut f = spanned();
16960        let cases: &[(&[&[u8]], &str)] = &[
16961            (
16962                &[b"TS.MRANGE", b"-", b"+", b"FILTER", b"room=kitchen"],
16963                "*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\
16964                 *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",
16965            ),
16966            // Newest first is applied to each series before anything else sees
16967            // the rows.
16968            (
16969                &[
16970                    b"TS.MREVRANGE",
16971                    b"-",
16972                    b"+",
16973                    b"WITHLABELS",
16974                    b"FILTER",
16975                    b"room=kitchen",
16976                ],
16977                "*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\
16978                 *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\
16979                 *3\r\n$1\r\nc\r\n*1\r\n*2\r\n$4\r\nroom\r\n$7\r\nkitchen\r\n\
16980                 *2\r\n*2\r\n:300\r\n+30\r\n*2\r\n:100\r\n+10\r\n",
16981            ),
16982            // A label a series does not wear comes back against a nil rather
16983            // than being left out.
16984            (
16985                &[
16986                    b"TS.MRANGE",
16987                    b"-",
16988                    b"+",
16989                    b"SELECTED_LABELS",
16990                    b"x",
16991                    b"FILTER",
16992                    b"room=kitchen",
16993                ],
16994                "*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\
16995                 *2\r\n*2\r\n:100\r\n+1.5\r\n*2\r\n:200\r\n+2.5\r\n\
16996                 *3\r\n$1\r\nc\r\n*1\r\n*2\r\n$1\r\nx\r\n$-1\r\n\
16997                 *2\r\n*2\r\n:100\r\n+10\r\n*2\r\n:300\r\n+30\r\n",
16998            ),
16999            // The fold: 100 is in both series and adds up, the other two are in
17000            // one each and are still rows.
17001            (
17002                &[
17003                    b"TS.MRANGE",
17004                    b"-",
17005                    b"+",
17006                    b"FILTER",
17007                    b"room=kitchen",
17008                    b"GROUPBY",
17009                    b"room",
17010                    b"REDUCE",
17011                    b"sum",
17012                ],
17013                "*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\
17014                 *2\r\n:200\r\n+2.5\r\n*2\r\n:300\r\n+30\r\n",
17015            ),
17016            // RESP2 has nowhere to put the reducer and the member keys, so a
17017            // group wearing labels writes them as two more labels.
17018            (
17019                &[
17020                    b"TS.MRANGE",
17021                    b"-",
17022                    b"+",
17023                    b"WITHLABELS",
17024                    b"FILTER",
17025                    b"room=kitchen",
17026                    b"GROUPBY",
17027                    b"room",
17028                    b"REDUCE",
17029                    b"max",
17030                ],
17031                "*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\
17032                 *2\r\n$11\r\n__reducer__\r\n$3\r\nmax\r\n\
17033                 *2\r\n$10\r\n__source__\r\n$3\r\na,c\r\n\
17034                 *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",
17035            ),
17036            // A count is applied to each member and then again to the fold.
17037            (
17038                &[
17039                    b"TS.MREVRANGE",
17040                    b"-",
17041                    b"+",
17042                    b"COUNT",
17043                    b"1",
17044                    b"FILTER",
17045                    b"room=kitchen",
17046                    b"GROUPBY",
17047                    b"room",
17048                    b"REDUCE",
17049                    b"count",
17050                ],
17051                "*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",
17052            ),
17053            // Nothing wears the label, so nothing is in any group.
17054            (
17055                &[
17056                    b"TS.MRANGE",
17057                    b"-",
17058                    b"+",
17059                    b"FILTER",
17060                    b"room=kitchen",
17061                    b"GROUPBY",
17062                    b"nope",
17063                    b"REDUCE",
17064                    b"sum",
17065                ],
17066                "*0\r\n",
17067            ),
17068            (
17069                &[
17070                    b"TS.MRANGE",
17071                    b"-",
17072                    b"+",
17073                    b"AGGREGATION",
17074                    b"sum,avg",
17075                    b"100",
17076                    b"FILTER",
17077                    b"room=bedroom",
17078                ],
17079                "*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",
17080            ),
17081            // The errors, in the order they are looked for.
17082            (
17083                &[b"TS.MRANGE", b"-", b"+", b"room=kitchen"],
17084                "-ERR TSDB: missing FILTER argument\r\n",
17085            ),
17086            (
17087                &[b"TS.MRANGE", b"-", b"+", b"FILTER"],
17088                "-ERR TSDB: missing labels for filter argument\r\n",
17089            ),
17090            (
17091                &[
17092                    b"TS.MRANGE",
17093                    b"-",
17094                    b"+",
17095                    b"GROUPBY",
17096                    b"room",
17097                    b"REDUCE",
17098                    b"sum",
17099                    b"FILTER",
17100                    b"room=kitchen",
17101                ],
17102                "-ERR TSDB: GROUPBY should always come after filter\r\n",
17103            ),
17104            // The group is four words from the end here, so the length is what
17105            // is wrong with it.
17106            (
17107                &[
17108                    b"TS.MRANGE",
17109                    b"-",
17110                    b"+",
17111                    b"FILTER",
17112                    b"room=kitchen",
17113                    b"GROUPBY",
17114                    b"room",
17115                    b"REDUCE",
17116                    b"sum",
17117                    b"x",
17118                ],
17119                "-ERR wrong number of arguments for 'ts.mrange' command\r\n",
17120            ),
17121            // And here it is not, so its words are filters and answer first.
17122            (
17123                &[
17124                    b"TS.MRANGE",
17125                    b"-",
17126                    b"+",
17127                    b"FILTER",
17128                    b"nope",
17129                    b"GROUPBY",
17130                    b"room",
17131                    b"REDUCE",
17132                    b"sum",
17133                    b"x",
17134                ],
17135                "-ERR TSDB: failed parsing labels\r\n",
17136            ),
17137            (
17138                &[
17139                    b"TS.MRANGE",
17140                    b"-",
17141                    b"+",
17142                    b"FILTER",
17143                    b"room=kitchen",
17144                    b"GROUPBY",
17145                    b"room",
17146                    b"REDUCE",
17147                    b"twa",
17148                ],
17149                "-ERR TSDB: Invalid reducer type\r\n",
17150            ),
17151            (
17152                &[
17153                    b"TS.MRANGE",
17154                    b"-",
17155                    b"+",
17156                    b"AGGREGATION",
17157                    b"sum,avg",
17158                    b"100",
17159                    b"FILTER",
17160                    b"room=kitchen",
17161                    b"GROUPBY",
17162                    b"room",
17163                    b"REDUCE",
17164                    b"sum",
17165                ],
17166                "-ERR TSDB: GROUPBY is not allowed when multiple aggregators are specified\r\n",
17167            ),
17168            // The label list ends at a keyword, so this is a `COUNT` with a
17169            // `FILTER` where its number should be.
17170            (
17171                &[
17172                    b"TS.MRANGE",
17173                    b"-",
17174                    b"+",
17175                    b"SELECTED_LABELS",
17176                    b"COUNT",
17177                    b"FILTER",
17178                    b"room=kitchen",
17179                ],
17180                "-ERR TSDB: Couldn't parse COUNT\r\n",
17181            ),
17182        ];
17183        for (argv, want) in cases {
17184            let got = f.run(argv);
17185            assert_eq!(&got, want, "{argv:?}");
17186        }
17187    }
17188
17189    /// The multi key reads on RESP3, where the key becomes a map key and the
17190    /// reducer and the member keys become fields of their own.
17191    #[test]
17192    fn resp3_writes_a_multi_key_read_as_a_map_of_four() {
17193        let mut f = spanned();
17194        f.out = Out::new(Proto::Resp3);
17195        let cases: &[(&[&[u8]], &str)] = &[
17196            (
17197                &[b"TS.MRANGE", b"-", b"+", b"FILTER", b"room=bedroom"],
17198                "%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\
17199                 *1\r\n*2\r\n:200\r\n,2\r\n",
17200            ),
17201            // The reductions a read asked for, which RESP2 has no room for at
17202            // all and which is empty on a read that asked for none.
17203            (
17204                &[
17205                    b"TS.MRANGE",
17206                    b"-",
17207                    b"+",
17208                    b"AGGREGATION",
17209                    b"sum,avg",
17210                    b"100",
17211                    b"FILTER",
17212                    b"room=bedroom",
17213                ],
17214                "%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\
17215                 $3\r\navg\r\n*1\r\n*3\r\n:200\r\n,2\r\n,2\r\n",
17216            ),
17217            (
17218                &[
17219                    b"TS.MRANGE",
17220                    b"-",
17221                    b"+",
17222                    b"FILTER",
17223                    b"room=kitchen",
17224                    b"GROUPBY",
17225                    b"room",
17226                    b"REDUCE",
17227                    b"sum",
17228                ],
17229                "%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\
17230                 $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\
17231                 *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",
17232            ),
17233            // The labels hold only the pair the group was made on, because the
17234            // reducer and the sources have somewhere else to go.
17235            (
17236                &[
17237                    b"TS.MRANGE",
17238                    b"-",
17239                    b"+",
17240                    b"WITHLABELS",
17241                    b"FILTER",
17242                    b"room=kitchen",
17243                    b"GROUPBY",
17244                    b"room",
17245                    b"REDUCE",
17246                    b"max",
17247                ],
17248                "%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\
17249                 %1\r\n$8\r\nreducers\r\n*1\r\n$3\r\nmax\r\n\
17250                 %1\r\n$7\r\nsources\r\n*2\r\n$1\r\na\r\n$1\r\nc\r\n\
17251                 *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",
17252            ),
17253            (
17254                &[
17255                    b"TS.MRANGE",
17256                    b"-",
17257                    b"+",
17258                    b"FILTER",
17259                    b"room=kitchen",
17260                    b"GROUPBY",
17261                    b"nope",
17262                    b"REDUCE",
17263                    b"sum",
17264                ],
17265                "%0\r\n",
17266            ),
17267        ];
17268        for (argv, want) in cases {
17269            let got = f.run(argv);
17270            assert_eq!(&got, want, "{argv:?}");
17271        }
17272    }
17273
17274    /// `TS.CREATERULE`, whose refusals come in an order of their own.
17275    #[test]
17276    fn createrule_checks_the_two_keys_last_and_the_two_links_after_that() {
17277        let mut f = Fixture::new();
17278        f.run(&[b"TS.CREATE", b"src"]);
17279        f.run(&[b"TS.CREATE", b"dst"]);
17280        f.run(&[b"SET", b"plain", b"v"]);
17281        let cases: &[(&[&[u8]], &str)] = &[
17282            // The width is read before the reduction, the reduction before the
17283            // width being above zero, and all three before either key is looked
17284            // at, so a command that is wrong twice complains about the first.
17285            (
17286                &[
17287                    b"TS.CREATERULE",
17288                    b"src",
17289                    b"dst",
17290                    b"AGGREGATION",
17291                    b"nope",
17292                    b"x",
17293                ],
17294                "-ERR TSDB: Couldn't parse AGGREGATION\r\n",
17295            ),
17296            (
17297                &[
17298                    b"TS.CREATERULE",
17299                    b"src",
17300                    b"dst",
17301                    b"AGGREGATION",
17302                    b"nope",
17303                    b"10",
17304                ],
17305                "-ERR TSDB: Unknown aggregation type\r\n",
17306            ),
17307            (
17308                &[
17309                    b"TS.CREATERULE",
17310                    b"src",
17311                    b"dst",
17312                    b"AGGREGATION",
17313                    b"avg",
17314                    b"0",
17315                ],
17316                "-ERR TSDB: bucketDuration must be greater than zero\r\n",
17317            ),
17318            (
17319                &[
17320                    b"TS.CREATERULE",
17321                    b"src",
17322                    b"dst",
17323                    b"AGGREGATION",
17324                    b"avg",
17325                    b"10",
17326                    b"x",
17327                ],
17328                "-ERR TSDB: Couldn't parse alignTimestamp\r\n",
17329            ),
17330            (
17331                &[
17332                    b"TS.CREATERULE",
17333                    b"src",
17334                    b"src",
17335                    b"AGGREGATION",
17336                    b"avg",
17337                    b"10",
17338                ],
17339                "-ERR TSDB: the source key and destination key should be different\r\n",
17340            ),
17341            // A key holding something else answers the same as a key that is not
17342            // there at all, because the source is looked up first and neither of
17343            // them is a series.
17344            (
17345                &[
17346                    b"TS.CREATERULE",
17347                    b"nope",
17348                    b"plain",
17349                    b"AGGREGATION",
17350                    b"avg",
17351                    b"10",
17352                ],
17353                "-ERR TSDB: the key does not exist\r\n",
17354            ),
17355            (
17356                &[
17357                    b"TS.CREATERULE",
17358                    b"src",
17359                    b"nope",
17360                    b"AGGREGATION",
17361                    b"avg",
17362                    b"10",
17363                ],
17364                "-ERR TSDB: the key does not exist\r\n",
17365            ),
17366            // A keyword other than AGGREGATION is an arity error rather than a
17367            // syntax one, because the arity is all that is checked.
17368            (
17369                &[b"TS.CREATERULE", b"src", b"dst", b"NOPE", b"avg", b"10"],
17370                "-ERR wrong number of arguments for 'ts.createrule' command\r\n",
17371            ),
17372            (
17373                &[
17374                    b"TS.CREATERULE",
17375                    b"src",
17376                    b"dst",
17377                    b"AGGREGATION",
17378                    b"avg",
17379                    b"10",
17380                ],
17381                "+OK\r\n",
17382            ),
17383            // The link is now in place, so the same rule again is refused from
17384            // the destination's end.
17385            (
17386                &[
17387                    b"TS.CREATERULE",
17388                    b"src",
17389                    b"dst",
17390                    b"AGGREGATION",
17391                    b"avg",
17392                    b"10",
17393                ],
17394                "-ERR TSDB: the destination key already has a src rule\r\n",
17395            ),
17396            // A source that is already someone's destination, and a destination
17397            // that is already someone's source, are two different sentences.
17398            (
17399                &[
17400                    b"TS.CREATERULE",
17401                    b"dst",
17402                    b"src",
17403                    b"AGGREGATION",
17404                    b"avg",
17405                    b"10",
17406                ],
17407                "-ERR TSDB: the source key already has a source rule\r\n",
17408            ),
17409            (&[b"TS.DELETERULE", b"src", b"dst"], "+OK\r\n"),
17410            (
17411                &[b"TS.DELETERULE", b"src", b"dst"],
17412                "-ERR TSDB: compaction rule does not exist\r\n",
17413            ),
17414            // The source is looked up and the destination is not, so a missing
17415            // destination is a missing rule and a missing source is a missing
17416            // key, which is the other way round from `TS.CREATERULE`.
17417            (
17418                &[b"TS.DELETERULE", b"src", b"nope"],
17419                "-ERR TSDB: compaction rule does not exist\r\n",
17420            ),
17421            (
17422                &[b"TS.DELETERULE", b"nope", b"dst"],
17423                "-ERR TSDB: the key does not exist\r\n",
17424            ),
17425        ];
17426        for (argv, want) in cases {
17427            let got = f.run(argv);
17428            assert_eq!(&got, want, "{argv:?}");
17429        }
17430    }
17431
17432    /// What a rule writes, which is every bucket but the one it is filling.
17433    #[test]
17434    fn a_rule_writes_a_bucket_when_a_later_reading_closes_it() {
17435        let mut f = Fixture::new();
17436        f.run(&[b"TS.CREATE", b"src"]);
17437        f.run(&[b"TS.CREATE", b"dst"]);
17438        // The readings written before the rule was made are not folded, so the
17439        // destination is still empty after the first two.
17440        f.run(&[b"TS.ADD", b"src", b"10", b"1"]);
17441        f.run(&[
17442            b"TS.CREATERULE",
17443            b"src",
17444            b"dst",
17445            b"AGGREGATION",
17446            b"sum",
17447            b"100",
17448        ]);
17449        f.run(&[b"TS.ADD", b"src", b"20", b"2"]);
17450        assert_eq!(f.run(&[b"TS.RANGE", b"dst", b"-", b"+"]), "*0\r\n");
17451        // The bucket the rule is filling holds only what it was given, so it is
17452        // 2 rather than 3, and it is written when a reading lands past it.
17453        assert_eq!(f.run(&[b"TS.GET", b"dst", b"LATEST"]), "*2\r\n:0\r\n+2\r\n");
17454        f.run(&[b"TS.ADD", b"src", b"110", b"4"]);
17455        assert_eq!(
17456            f.run(&[b"TS.RANGE", b"dst", b"-", b"+"]),
17457            "*1\r\n*2\r\n:0\r\n+2\r\n"
17458        );
17459        // A reading into a bucket that has already been written works that
17460        // bucket out again over everything the source now holds.
17461        f.run(&[b"TS.ADD", b"src", b"30", b"8"]);
17462        assert_eq!(
17463            f.run(&[b"TS.RANGE", b"dst", b"-", b"+"]),
17464            "*1\r\n*2\r\n:0\r\n+11\r\n"
17465        );
17466        // Deleting from the source works the buckets it touched out again and
17467        // reopens the newest one, so `LATEST` starts from the whole bucket.
17468        assert_eq!(f.run(&[b"TS.DEL", b"src", b"0", b"25"]), ":2\r\n");
17469        assert_eq!(
17470            f.run(&[b"TS.RANGE", b"dst", b"-", b"+"]),
17471            "*1\r\n*2\r\n:0\r\n+8\r\n"
17472        );
17473        assert_eq!(
17474            f.run(&[b"TS.GET", b"dst", b"LATEST"]),
17475            "*2\r\n:100\r\n+4\r\n"
17476        );
17477        // The link shows on both ends, and dropping either key takes it down.
17478        assert!(f.run(&[b"TS.INFO", b"dst"]).contains("sourceKey"));
17479        f.run(&[b"DEL", b"dst"]);
17480        assert_eq!(
17481            f.run(&[b"TS.DELETERULE", b"src", b"dst"]),
17482            "-ERR TSDB: compaction rule does not exist\r\n"
17483        );
17484    }
17485
17486    /// The three shapes an `XADD` id can take, and the one rule behind all of
17487    /// them.
17488    #[test]
17489    fn xadd_ids_only_ever_go_up() {
17490        let mut f = Fixture::new();
17491        // A bare millisecond is that millisecond and sequence zero.
17492        assert_eq!(f.run(&[b"XADD", b"s", b"5", b"a", b"1"]), "$3\r\n5-0\r\n");
17493        // And `5-*` is the next free sequence inside it.
17494        assert_eq!(f.run(&[b"XADD", b"s", b"5-*", b"a", b"2"]), "$3\r\n5-1\r\n");
17495        assert_eq!(f.run(&[b"XADD", b"s", b"5-*", b"a", b"3"]), "$3\r\n5-2\r\n");
17496        assert_eq!(f.run(&[b"XADD", b"s", b"6-9", b"a", b"4"]), "$3\r\n6-9\r\n");
17497        assert_eq!(f.run(&[b"XLEN", b"s"]), ":4\r\n");
17498
17499        assert!(
17500            f.run(&[b"XADD", b"s", b"6-9", b"a", b"5"])
17501                .contains("equal or smaller")
17502        );
17503        assert!(
17504            f.run(&[b"XADD", b"s", b"0-0", b"a", b"5"])
17505                .contains("must be greater than 0-0")
17506        );
17507        assert!(
17508            f.run(&[b"XADD", b"s", b"nonsense", b"a", b"5"])
17509                .contains("Invalid stream ID")
17510        );
17511        // The pairs have to be pairs, and Redis calls an odd one an arity error
17512        // rather than a syntax error even though the table has already passed.
17513        assert!(
17514            f.run(&[b"XADD", b"s", b"*", b"a"])
17515                .contains("wrong number of arguments")
17516        );
17517
17518        // `NOMKSTREAM` on a key that is not there is a null and not a zero, so a
17519        // producer can tell nobody is consuming this yet from the write landed.
17520        assert_eq!(
17521            f.run(&[b"XADD", b"gone", b"NOMKSTREAM", b"*", b"a", b"1"]),
17522            "$-1\r\n"
17523        );
17524        assert_eq!(f.run(&[b"EXISTS", b"gone"]), ":0\r\n");
17525        assert_eq!(f.run(&[b"TYPE", b"s"]), "+stream\r\n");
17526        assert_eq!(f.run(&[b"OBJECT", b"ENCODING", b"s"]), "$6\r\nstream\r\n");
17527    }
17528
17529    /// The trim options, which are three keywords that disagree about how many
17530    /// arguments they take.
17531    #[test]
17532    fn trimming_reads_its_options_the_way_redis_does() {
17533        let mut f = Fixture::new();
17534        for i in 1..=10u32 {
17535            f.run(&[b"XADD", b"s", format!("{i}-1").as_bytes(), b"a", b"1"]);
17536        }
17537        assert_eq!(f.run(&[b"XTRIM", b"s", b"MAXLEN", b"4"]), ":6\r\n");
17538        assert_eq!(f.run(&[b"XLEN", b"s"]), ":4\r\n");
17539        assert_eq!(f.run(&[b"XTRIM", b"s", b"MINID", b"9"]), ":2\r\n");
17540        assert_eq!(f.run(&[b"XLEN", b"s"]), ":2\r\n");
17541
17542        // One argument after the keyword and the `~` is read as the threshold,
17543        // which is what a real server does and is the reason this is a number
17544        // complaint and not a syntax one.
17545        assert!(
17546            f.run(&[b"XTRIM", b"s", b"MAXLEN", b"~"])
17547                .contains("not an integer")
17548        );
17549        assert!(
17550            f.run(&[b"XTRIM", b"s", b"MAXLEN", b"-1"])
17551                .contains("MAXLEN argument must be >= 0")
17552        );
17553        // The strategy check runs before the approximation check, so a LIMIT
17554        // with neither is told about the missing strategy.
17555        assert!(
17556            f.run(&[b"XTRIM", b"s", b"LIMIT", b"5"])
17557                .contains("without specifying a trimming strategy")
17558        );
17559        assert!(
17560            f.run(&[b"XTRIM", b"s", b"MAXLEN", b"5", b"LIMIT", b"5"])
17561                .contains("without the special ~ option")
17562        );
17563        assert!(
17564            f.run(&[b"XTRIM", b"s", b"MAXLEN", b"5", b"MINID", b"5"])
17565                .contains("at the same time are not compatible")
17566        );
17567        // NOMKSTREAM is XADD's and XTRIM does not take it.
17568        assert!(
17569            f.run(&[b"XTRIM", b"s", b"NOMKSTREAM", b"MAXLEN", b"5"])
17570                .contains("syntax error")
17571        );
17572        assert_eq!(f.run(&[b"XTRIM", b"missing", b"MAXLEN", b"5"]), ":0\r\n");
17573    }
17574
17575    /// `XRANGE`, whose two kinds of nothing are the thing worth pinning.
17576    #[test]
17577    fn xrange_looks_the_key_up_before_it_reads_the_count() {
17578        let mut f = Fixture::new();
17579        f.run(&[b"XADD", b"s", b"5-1", b"a", b"1"]);
17580        f.run(&[b"XADD", b"s", b"6-1", b"b", b"2"]);
17581
17582        assert_eq!(
17583            f.run(&[b"XRANGE", b"s", b"-", b"+"]),
17584            "*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\
17585             *2\r\n$3\r\n6-1\r\n*2\r\n$1\r\nb\r\n$1\r\n2\r\n"
17586        );
17587        assert_eq!(
17588            f.run(&[b"XREVRANGE", b"s", b"+", b"-", b"COUNT", b"1"]),
17589            "*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"
17590        );
17591        // The exclusive bound is stepped after the missing sequence is filled
17592        // in, so `(6` is `6-` and the largest sequence there is, minus one, and
17593        // `6-1` is still in the range.
17594        assert_eq!(
17595            f.run(&[b"XRANGE", b"s", b"-", b"(6"]),
17596            "*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\
17597             *2\r\n$3\r\n6-1\r\n*2\r\n$1\r\nb\r\n$1\r\n2\r\n"
17598        );
17599        assert_eq!(
17600            f.run(&[b"XRANGE", b"s", b"(5-1", b"+"]),
17601            "*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"
17602        );
17603        assert!(
17604            f.run(&[b"XRANGE", b"s", b"(-", b"+"])
17605                .contains("Invalid stream ID")
17606        );
17607
17608        // The two kinds of nothing. A key that is not there is an empty array
17609        // and a key that is there with a count of zero is a null array, because
17610        // the lookup happens first.
17611        assert_eq!(
17612            f.run(&[b"XRANGE", b"missing", b"-", b"+", b"COUNT", b"0"]),
17613            "*0\r\n"
17614        );
17615        assert_eq!(
17616            f.run(&[b"XRANGE", b"s", b"-", b"+", b"COUNT", b"0"]),
17617            "*-1\r\n"
17618        );
17619        f.run(&[b"SET", b"str", b"v"]);
17620        assert!(
17621            f.run(&[b"XRANGE", b"str", b"-", b"+", b"COUNT", b"0"])
17622                .starts_with("-WRONGTYPE")
17623        );
17624        // The count is read in a loop, so the last one wins.
17625        assert_eq!(
17626            f.run(&[b"XRANGE", b"s", b"-", b"+", b"COUNT", b"2", b"COUNT", b"1"]),
17627            "*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"
17628        );
17629    }
17630
17631    /// `XDEL` and `XACK` check every id before they touch any of them.
17632    #[test]
17633    fn a_bad_id_late_in_the_list_stops_the_whole_command() {
17634        let mut f = Fixture::new();
17635        f.run(&[b"XADD", b"s", b"1-1", b"a", b"1"]);
17636        f.run(&[b"XADD", b"s", b"2-1", b"a", b"2"]);
17637        assert!(
17638            f.run(&[b"XDEL", b"s", b"1-1", b"nonsense"])
17639                .contains("Invalid stream ID")
17640        );
17641        assert_eq!(f.run(&[b"XLEN", b"s"]), ":2\r\n");
17642        assert_eq!(f.run(&[b"XDEL", b"s", b"1-1", b"9-9"]), ":1\r\n");
17643        assert_eq!(f.run(&[b"XLEN", b"s"]), ":1\r\n");
17644        assert_eq!(f.run(&[b"XDEL", b"missing", b"1-1"]), ":0\r\n");
17645        assert_eq!(f.run(&[b"XACK", b"missing", b"g", b"1-1"]), ":0\r\n");
17646    }
17647
17648    /// `XGROUP`, and the two different complaints it makes about arguments.
17649    #[test]
17650    fn xgroup_has_an_arity_per_subcommand() {
17651        let mut f = Fixture::new();
17652        assert!(
17653            f.run(&[b"XGROUP", b"CREATE", b"s", b"g", b"$"])
17654                .contains("requires the key")
17655        );
17656        assert_eq!(
17657            f.run(&[b"XGROUP", b"CREATE", b"s", b"g", b"$", b"MKSTREAM"]),
17658            "+OK\r\n"
17659        );
17660        // A second CREATE is BUSYGROUP and not an ordinary error, because a
17661        // client racing another one to make a group branches on the prefix.
17662        assert!(
17663            f.run(&[b"XGROUP", b"CREATE", b"s", b"g", b"$"])
17664                .starts_with("-BUSYGROUP")
17665        );
17666        assert_eq!(
17667            f.run(&[b"XGROUP", b"CREATECONSUMER", b"s", b"g", b"c"]),
17668            ":1\r\n"
17669        );
17670        assert_eq!(
17671            f.run(&[b"XGROUP", b"CREATECONSUMER", b"s", b"g", b"c"]),
17672            ":0\r\n"
17673        );
17674        assert_eq!(
17675            f.run(&[b"XGROUP", b"DELCONSUMER", b"s", b"g", b"c"]),
17676            ":0\r\n"
17677        );
17678
17679        // Below the subcommand's own arity is an arity error naming the pair.
17680        let short = f.run(&[b"XGROUP", b"DESTROY", b"s"]);
17681        assert!(
17682            short.contains("wrong number of arguments for 'xgroup|destroy' command"),
17683            "{short}"
17684        );
17685        // At or above it in a shape the handler will not take is the other one.
17686        let odd = f.run(&[b"XGROUP", b"SETID", b"s", b"g", b"0", b"ENTRIESREAD"]);
17687        assert!(
17688            odd.contains("unknown subcommand or wrong number of arguments for 'SETID'"),
17689            "{odd}"
17690        );
17691        assert!(
17692            f.run(&[b"XGROUP", b"NOSUCH", b"s"])
17693                .contains("Try XGROUP HELP")
17694        );
17695
17696        assert_eq!(f.run(&[b"XGROUP", b"SETID", b"s", b"g", b"0"]), "+OK\r\n");
17697        assert!(
17698            f.run(&[b"XGROUP", b"SETID", b"s", b"nogroup", b"0"])
17699                .starts_with("-NOGROUP")
17700        );
17701        assert_eq!(f.run(&[b"XGROUP", b"DESTROY", b"s", b"g"]), ":1\r\n");
17702        assert_eq!(f.run(&[b"XGROUP", b"DESTROY", b"s", b"g"]), ":0\r\n");
17703        assert!(
17704            f.run(&[b"XGROUP", b"DESTROY", b"missing", b"g"])
17705                .contains("requires the key")
17706        );
17707    }
17708
17709    /// A group read, an acknowledgement, and what is left in between.
17710    #[test]
17711    fn xreadgroup_hands_out_and_xack_takes_back() {
17712        let mut f = Fixture::new();
17713        f.run(&[b"XADD", b"s", b"1-1", b"a", b"1"]);
17714        f.run(&[b"XADD", b"s", b"2-1", b"a", b"2"]);
17715        f.run(&[b"XGROUP", b"CREATE", b"s", b"g", b"0"]);
17716
17717        let first = f.run(&[
17718            b"XREADGROUP",
17719            b"GROUP",
17720            b"g",
17721            b"c1",
17722            b"COUNT",
17723            b"1",
17724            b"STREAMS",
17725            b"s",
17726            b">",
17727        ]);
17728        assert_eq!(
17729            first,
17730            "*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"
17731        );
17732        // A history read names its stream even with nothing to show, which is
17733        // the difference between it and a `>` read that found nothing.
17734        assert_eq!(
17735            f.run(&[b"XREADGROUP", b"GROUP", b"g", b"c2", b"STREAMS", b"s", b"0"]),
17736            "*1\r\n*2\r\n$1\r\ns\r\n*0\r\n"
17737        );
17738        assert_eq!(
17739            f.run(&[b"XREADGROUP", b"GROUP", b"g", b"c1", b"STREAMS", b"s", b"0"]),
17740            "*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"
17741        );
17742
17743        assert_eq!(
17744            f.run(&[b"XPENDING", b"s", b"g"]),
17745            "*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"
17746        );
17747        assert_eq!(f.run(&[b"XACK", b"s", b"g", b"1-1"]), ":1\r\n");
17748        assert_eq!(f.run(&[b"XACK", b"s", b"g", b"1-1"]), ":0\r\n");
17749        // Empty is four nulls and not a zero with three empty things.
17750        assert_eq!(
17751            f.run(&[b"XPENDING", b"s", b"g"]),
17752            "*4\r\n:0\r\n$-1\r\n$-1\r\n*-1\r\n"
17753        );
17754
17755        // A history read of an entry that has since been deleted is the id with
17756        // a null beside it, so the consumer can still acknowledge it.
17757        f.run(&[b"XREADGROUP", b"GROUP", b"g", b"c1", b"STREAMS", b"s", b">"]);
17758        f.run(&[b"XDEL", b"s", b"2-1"]);
17759        assert_eq!(
17760            f.run(&[b"XREADGROUP", b"GROUP", b"g", b"c1", b"STREAMS", b"s", b"0"]),
17761            "*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"
17762        );
17763
17764        // The group lookup runs before the id parse, so a `+` at a stream with
17765        // no such group is told about the group and not about the id.
17766        assert!(
17767            f.run(&[
17768                b"XREADGROUP",
17769                b"GROUP",
17770                b"nope",
17771                b"c",
17772                b"STREAMS",
17773                b"s",
17774                b"+"
17775            ])
17776            .starts_with("-NOGROUP")
17777        );
17778        assert!(
17779            f.run(&[b"XREADGROUP", b"GROUP", b"g", b"c", b"STREAMS", b"s", b"$"])
17780                .contains("meaningless in the context of XREADGROUP")
17781        );
17782        assert!(
17783            f.run(&[b"XREAD", b"GROUP", b"g", b"c", b"STREAMS", b"s", b"0"])
17784                .contains("only supported by XREADGROUP")
17785        );
17786        assert!(
17787            f.run(&[
17788                b"XREADGROUP",
17789                b"GROUP",
17790                b"g",
17791                b"c",
17792                b"STREAMS",
17793                b"s",
17794                b"a",
17795                b"b"
17796            ])
17797            .contains("Unbalanced 'xreadgroup' list of streams")
17798        );
17799    }
17800
17801    /// `XREAD` without `BLOCK`, which answers now and takes nothing for an
17802    /// answer.
17803    #[test]
17804    fn xread_with_no_block_writes_the_null_itself() {
17805        let mut f = Fixture::new();
17806        f.run(&[b"XADD", b"s", b"1-1", b"a", b"1"]);
17807        assert_eq!(
17808            f.run(&[b"XREAD", b"STREAMS", b"s", b"0"]),
17809            "*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"
17810        );
17811        // Nothing new is a null array and not an empty one, and a stream with
17812        // nothing new is left out rather than sent with an empty list.
17813        assert_eq!(f.run(&[b"XREAD", b"STREAMS", b"s", b"1-1"]), "*-1\r\n");
17814        assert_eq!(f.run(&[b"XREAD", b"STREAMS", b"missing", b"0"]), "*-1\r\n");
17815        f.run(&[b"XADD", b"other", b"1-1", b"b", b"2"]);
17816        assert_eq!(
17817            f.run(&[b"XREAD", b"STREAMS", b"s", b"other", b"1-1", b"0"]),
17818            "*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"
17819        );
17820        // `$` is the last id, so nothing that is already there comes back.
17821        assert_eq!(f.run(&[b"XREAD", b"STREAMS", b"s", b"$"]), "*-1\r\n");
17822        // And `+` is the last entry, whatever COUNT says.
17823        assert_eq!(
17824            f.run(&[b"XREAD", b"COUNT", b"5", b"STREAMS", b"s", b"+"]),
17825            "*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"
17826        );
17827        // A count of zero means unlimited here, which is the opposite of what it
17828        // means to XRANGE.
17829        assert_eq!(
17830            f.run(&[b"XREAD", b"COUNT", b"0", b"STREAMS", b"s", b"0"]),
17831            "*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"
17832        );
17833        // Milliseconds as a whole number, where BLPOP takes seconds as a float.
17834        assert!(
17835            f.run(&[b"XREAD", b"BLOCK", b"0.5", b"STREAMS", b"s", b"$"])
17836                .contains("not an integer")
17837        );
17838        assert!(
17839            f.run(&[b"XREAD", b"BLOCK", b"-1", b"STREAMS", b"s", b"$"])
17840                .contains("timeout is negative")
17841        );
17842        assert!(
17843            f.run(&[b"XREAD", b"STREAMS", b"s", b"other", b"0"])
17844                .contains("Unbalanced 'xread' list of streams")
17845        );
17846    }
17847
17848    /// A blocked reader, and the two ways it stops being blocked.
17849    #[test]
17850    fn a_blocked_xread_wakes_on_the_next_entry() {
17851        let mut f = Fixture::new();
17852        f.run(&[b"XADD", b"s", b"1-1", b"a", b"1"]);
17853        let (flow, reply) = f.flow(&[b"XREAD", b"BLOCK", b"0", b"STREAMS", b"s", b"$"]);
17854        assert_eq!(flow, Flow::Block);
17855        assert!(reply.is_empty());
17856
17857        // Everybody parked on the stream gets the entry, because a read takes
17858        // nothing away. That is the difference between this and BLPOP. Two
17859        // clients rather than one twice, since a client that is waiting is not
17860        // reading and cannot block again.
17861        f.session = Session::new(8);
17862        let (flow, _) = f.flow(&[b"XREAD", b"BLOCK", b"0", b"STREAMS", b"s", b"$"]);
17863        assert_eq!(flow, Flow::Block);
17864        assert_eq!(f.server.parked(), 2);
17865
17866        f.run(&[b"XADD", b"s", b"2-1", b"a", b"2"]);
17867        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";
17868        for client in [7, 8] {
17869            let mut out = Out::new(Proto::Resp2);
17870            assert!(f.server.serve_waiter(client, 0, &mut out));
17871            assert_eq!(core::str::from_utf8(out.as_slice()).expect("ascii"), want);
17872        }
17873
17874        // And a deadline that runs out is a null array, the same as a plain
17875        // XREAD that found nothing.
17876        f.server.forget_waiters(7);
17877        f.server.forget_waiters(8);
17878        let (flow, _) = f.flow(&[b"XREAD", b"BLOCK", b"50", b"STREAMS", b"s", b"$"]);
17879        assert_eq!(flow, Flow::Block);
17880        let mut out = Out::new(Proto::Resp2);
17881        assert!(!f.server.serve_waiter(8, 0, &mut out));
17882        assert!(out.as_slice().is_empty());
17883        assert!(f.server.serve_waiter(8, u64::MAX, &mut out));
17884        assert_eq!(
17885            core::str::from_utf8(out.as_slice()).expect("ascii"),
17886            "*-1\r\n"
17887        );
17888    }
17889
17890    /// A blocked group reader whose group is destroyed under it.
17891    #[test]
17892    fn losing_a_group_while_blocked_is_the_ordinary_sentence() {
17893        let mut f = Fixture::new();
17894        f.run(&[b"XADD", b"s", b"1-1", b"a", b"1"]);
17895        f.run(&[b"XGROUP", b"CREATE", b"s", b"g", b"$"]);
17896        let (flow, _) = f.flow(&[
17897            b"XREADGROUP",
17898            b"GROUP",
17899            b"g",
17900            b"c",
17901            b"BLOCK",
17902            b"0",
17903            b"STREAMS",
17904            b"s",
17905            b">",
17906        ]);
17907        assert_eq!(flow, Flow::Block);
17908
17909        f.run(&[b"XGROUP", b"DESTROY", b"s", b"g"]);
17910        let mut out = Out::new(Proto::Resp2);
17911        assert!(f.server.serve_waiter(7, 0, &mut out));
17912        // The ordinary sentence and not a special one about having been parked,
17913        // which is what a running 8.10 sends.
17914        assert_eq!(
17915            core::str::from_utf8(out.as_slice()).expect("ascii"),
17916            "-NOGROUP No such key 's' or consumer group 'g' in XREADGROUP with GROUP option\r\n"
17917        );
17918    }
17919
17920    /// `XCLAIM`, whose argument shape is the odd one in the group.
17921    #[test]
17922    fn xclaim_reads_ids_until_one_will_not_parse() {
17923        let mut f = Fixture::new();
17924        f.run(&[b"XADD", b"s", b"1-1", b"a", b"1"]);
17925        f.run(&[b"XADD", b"s", b"2-1", b"a", b"2"]);
17926        f.run(&[b"XGROUP", b"CREATE", b"s", b"g", b"0"]);
17927        f.run(&[b"XREADGROUP", b"GROUP", b"g", b"c1", b"STREAMS", b"s", b">"]);
17928
17929        // Everything after the first argument that is not an id is an option, so
17930        // a `-` is an unrecognised option and not a bad id.
17931        assert!(
17932            f.run(&[b"XCLAIM", b"s", b"g", b"c2", b"0", b"-"])
17933                .contains("Unrecognized XCLAIM option '-'")
17934        );
17935        assert_eq!(
17936            f.run(&[b"XCLAIM", b"s", b"g", b"c2", b"0", b"1-1", b"JUSTID"]),
17937            "*1\r\n$3\r\n1-1\r\n"
17938        );
17939        // An id that is pending but whose entry has gone is an empty answer, and
17940        // it leaves the pending list on the way past.
17941        f.run(&[b"XDEL", b"s", b"2-1"]);
17942        assert_eq!(
17943            f.run(&[b"XCLAIM", b"s", b"g", b"c2", b"0", b"2-1"]),
17944            "*0\r\n"
17945        );
17946        assert!(
17947            f.run(&[b"XPENDING", b"s", b"g"])
17948                .starts_with("*4\r\n:1\r\n")
17949        );
17950        assert!(
17951            f.run(&[b"XCLAIM", b"s", b"nope", b"c", b"0", b"1-1"])
17952                .starts_with("-NOGROUP")
17953        );
17954        assert!(
17955            f.run(&[b"XCLAIM", b"s", b"g", b"c", b"nan", b"1-1"])
17956                .contains("Invalid min-idle-time argument for XCLAIM")
17957        );
17958    }
17959
17960    /// `XAUTOCLAIM`, and the third value nobody expects.
17961    #[test]
17962    fn xautoclaim_reports_what_it_dropped() {
17963        let mut f = Fixture::new();
17964        f.run(&[b"XADD", b"s", b"1-1", b"a", b"1"]);
17965        f.run(&[b"XADD", b"s", b"2-1", b"a", b"2"]);
17966        f.run(&[b"XGROUP", b"CREATE", b"s", b"g", b"0"]);
17967        f.run(&[b"XREADGROUP", b"GROUP", b"g", b"c1", b"STREAMS", b"s", b">"]);
17968        f.run(&[b"XDEL", b"s", b"1-1"]);
17969
17970        // The cursor, what was claimed, and what was dropped for no longer being
17971        // in the stream. The third one is what makes a sweep converge.
17972        assert_eq!(
17973            f.run(&[b"XAUTOCLAIM", b"s", b"g", b"c2", b"0", b"-", b"JUSTID"]),
17974            "*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"
17975        );
17976        assert!(
17977            f.run(&[b"XAUTOCLAIM", b"s", b"g", b"c2", b"0", b"-", b"COUNT", b"0"])
17978                .contains("COUNT must be > 0")
17979        );
17980        assert!(
17981            f.run(&[b"XAUTOCLAIM", b"s", b"nope", b"c", b"0", b"-"])
17982                .starts_with("-NOGROUP")
17983        );
17984    }
17985
17986    /// `XDELEX`, which is `XDEL` with a say in what the groups keep.
17987    #[test]
17988    fn xdelex_answers_one_integer_an_id() {
17989        let mut f = Fixture::new();
17990        for i in 1..=4 {
17991            f.run(&[b"XADD", b"s", format!("{i}-1").as_bytes(), b"a", b"1"]);
17992        }
17993        f.run(&[b"XGROUP", b"CREATE", b"s", b"g", b"0"]);
17994        f.run(&[
17995            b"XREADGROUP",
17996            b"GROUP",
17997            b"g",
17998            b"c",
17999            b"COUNT",
18000            b"2",
18001            b"STREAMS",
18002            b"s",
18003            b">",
18004        ]);
18005
18006        // One means gone and minus one means it was not there to start with.
18007        assert_eq!(
18008            f.run(&[b"XDELEX", b"s", b"IDS", b"2", b"1-1", b"9-9"]),
18009            "*2\r\n:1\r\n:-1\r\n"
18010        );
18011        // `KEEPREF` leaves the pending entry behind, so the group still counts
18012        // the one it was handed even though the entry has gone.
18013        assert!(
18014            f.run(&[b"XPENDING", b"s", b"g"])
18015                .starts_with("*4\r\n:2\r\n")
18016        );
18017        // `DELREF` takes it out of every pending list on the way past.
18018        assert_eq!(
18019            f.run(&[b"XDELEX", b"s", b"DELREF", b"IDS", b"1", b"2-1"]),
18020            "*1\r\n:1\r\n"
18021        );
18022        // `1-1` is still in the list, because the delete before it said KEEPREF.
18023        assert_eq!(
18024            f.run(&[b"XPENDING", b"s", b"g"]),
18025            "*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"
18026        );
18027
18028        // Two means somebody still wants it, and the question is wider than the
18029        // name: the group's bookmark is at `2-1`, so `4-1` is above it and is
18030        // refused even though no consumer has ever been handed it.
18031        assert_eq!(
18032            f.run(&[b"XDELEX", b"s", b"ACKED", b"IDS", b"2", b"3-1", b"4-1"]),
18033            "*2\r\n:2\r\n:2\r\n"
18034        );
18035
18036        // A key that is not there answers minus ones without reading the IDs.
18037        assert_eq!(
18038            f.run(&[b"XDELEX", b"nope", b"IDS", b"2", b"bad", b"worse"]),
18039            "*2\r\n:-1\r\n:-1\r\n"
18040        );
18041        // A key that is there validates every ID before deleting any of them.
18042        assert!(
18043            f.run(&[b"XDELEX", b"s", b"IDS", b"2", b"3-1", b"bad"])
18044                .starts_with("-ERR Invalid stream ID")
18045        );
18046        assert_eq!(f.run(&[b"XLEN", b"s"]), ":2\r\n");
18047
18048        assert!(
18049            f.run(&[b"XDELEX", b"s", b"IDS", b"0", b"1-1"])
18050                .contains("Number of IDs must be a positive integer")
18051        );
18052        assert!(
18053            f.run(&[b"XDELEX", b"s", b"IDS", b"2", b"1-1"])
18054                .contains("The `numids` parameter must match the number of arguments")
18055        );
18056        // The condition is one word, so a second one is a syntax error, and so
18057        // is one ID more than the count promised.
18058        assert!(
18059            f.run(&[b"XDELEX", b"s", b"KEEPREF", b"DELREF", b"IDS", b"1", b"1-1"])
18060                .starts_with("-ERR syntax error")
18061        );
18062        assert!(
18063            f.run(&[b"XDELEX", b"s", b"IDS", b"1", b"1-1", b"2-1"])
18064                .starts_with("-ERR syntax error")
18065        );
18066        // The key is looked up first, so the wrong type beats the syntax.
18067        f.run(&[b"SET", b"str", b"v"]);
18068        assert!(
18069            f.run(&[b"XDELEX", b"str", b"BOGUS", b"IDS", b"0", b"1-1"])
18070                .starts_with("-WRONGTYPE")
18071        );
18072    }
18073
18074    /// `XACKDEL`, whose reply is about the pending list and not about the log.
18075    #[test]
18076    fn xackdel_reports_what_the_group_was_holding() {
18077        let mut f = Fixture::new();
18078        for i in 1..=3 {
18079            f.run(&[b"XADD", b"s", format!("{i}-1").as_bytes(), b"a", b"1"]);
18080        }
18081        f.run(&[b"XGROUP", b"CREATE", b"s", b"g", b"0"]);
18082        f.run(&[
18083            b"XREADGROUP",
18084            b"GROUP",
18085            b"g",
18086            b"c",
18087            b"COUNT",
18088            b"1",
18089            b"STREAMS",
18090            b"s",
18091            b">",
18092        ]);
18093
18094        // Minus one is not about the stream: `2-1` is sitting there unread and
18095        // still answers minus one, because the group was not holding it. It also
18096        // stays, since only an ID that was acknowledged is deleted.
18097        assert_eq!(
18098            f.run(&[b"XACKDEL", b"s", b"g", b"IDS", b"2", b"1-1", b"2-1"]),
18099            "*2\r\n:1\r\n:-1\r\n"
18100        );
18101        assert_eq!(f.run(&[b"XLEN", b"s"]), ":2\r\n");
18102
18103        // A missing group is minus one an ID and not a NOGROUP.
18104        assert_eq!(
18105            f.run(&[b"XACKDEL", b"s", b"nope", b"IDS", b"1", b"2-1"]),
18106            "*1\r\n:-1\r\n"
18107        );
18108        assert_eq!(
18109            f.run(&[b"XACKDEL", b"nope", b"g", b"IDS", b"1", b"2-1"]),
18110            "*1\r\n:-1\r\n"
18111        );
18112
18113        // The acknowledgement happens whatever the condition says, so an ACKED
18114        // that answers two has still emptied the pending list.
18115        f.run(&[b"XREADGROUP", b"GROUP", b"g", b"c", b"STREAMS", b"s", b">"]);
18116        f.run(&[b"XGROUP", b"CREATE", b"s", b"g2", b"0"]);
18117        assert_eq!(
18118            f.run(&[b"XACKDEL", b"s", b"g", b"ACKED", b"IDS", b"1", b"2-1"]),
18119            "*1\r\n:2\r\n"
18120        );
18121        assert_eq!(
18122            f.run(&[b"XPENDING", b"s", b"g"]),
18123            "*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"
18124        );
18125        assert_eq!(f.run(&[b"XLEN", b"s"]), ":2\r\n");
18126    }
18127
18128    /// `XNACK`, which hands an entry back to nobody.
18129    #[test]
18130    fn xnack_releases_an_entry_for_the_next_claim() {
18131        let mut f = Fixture::new();
18132        f.run(&[b"XADD", b"s", b"1-1", b"a", b"1"]);
18133        f.run(&[b"XADD", b"s", b"2-1", b"a", b"2"]);
18134        f.run(&[b"XGROUP", b"CREATE", b"s", b"g", b"0"]);
18135        f.run(&[b"XREADGROUP", b"GROUP", b"g", b"c1", b"STREAMS", b"s", b">"]);
18136        // Twice, so the delivery count is two and the words have something to
18137        // do with it.
18138        f.run(&[b"XCLAIM", b"s", b"g", b"c1", b"0", b"1-1", b"2-1"]);
18139
18140        assert_eq!(
18141            f.run(&[b"XNACK", b"s", b"g", b"FAIL", b"IDS", b"1", b"1-1"]),
18142            ":1\r\n"
18143        );
18144        // No owner, no idle time, and the count left where it was. A released
18145        // entry reads as idle for longer than any min-idle-time, which is what
18146        // puts it at the front of the next claim.
18147        assert_eq!(
18148            f.run(&[b"XPENDING", b"s", b"g", b"-", b"+", b"10"]),
18149            "*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"
18150        );
18151        // The consumer no longer holds it, so a filtered XPENDING skips it.
18152        assert_eq!(
18153            f.run(&[b"XPENDING", b"s", b"g", b"-", b"+", b"10", b"c1"]),
18154            "*1\r\n*4\r\n$3\r\n2-1\r\n$2\r\nc1\r\n:0\r\n:2\r\n"
18155        );
18156        // The bookmark did not move, so a `>` read will not hand it out again.
18157        assert_eq!(
18158            f.run(&[b"XREADGROUP", b"GROUP", b"g", b"c2", b"STREAMS", b"s", b">"]),
18159            "*-1\r\n"
18160        );
18161        // A claim at any min-idle-time takes it.
18162        assert_eq!(
18163            f.run(&[
18164                b"XAUTOCLAIM",
18165                b"s",
18166                b"g",
18167                b"c2",
18168                b"99999999",
18169                b"-",
18170                b"JUSTID"
18171            ]),
18172            "*3\r\n$3\r\n0-0\r\n*1\r\n$3\r\n1-1\r\n*0\r\n"
18173        );
18174
18175        // `SILENT` takes one off the count rather than putting it back to zero,
18176        // which only shows on an entry that has been handed out more than once.
18177        // It was delivered and then claimed, so it is on two and goes to one.
18178        f.run(&[b"XNACK", b"s", b"g", b"SILENT", b"IDS", b"1", b"1-1"]);
18179        assert!(
18180            f.run(&[b"XPENDING", b"s", b"g", b"-", b"+", b"10"])
18181                .contains(":-1\r\n:1\r\n")
18182        );
18183        // And it stops at zero rather than wrapping.
18184        f.run(&[b"XNACK", b"s", b"g", b"SILENT", b"IDS", b"1", b"1-1"]);
18185        f.run(&[b"XNACK", b"s", b"g", b"SILENT", b"IDS", b"1", b"1-1"]);
18186        assert!(
18187            f.run(&[b"XPENDING", b"s", b"g", b"-", b"+", b"10"])
18188                .contains(":-1\r\n:0\r\n")
18189        );
18190        // `FATAL` puts it at the ceiling, and `RETRYCOUNT` wins over the word.
18191        f.run(&[b"XNACK", b"s", b"g", b"FATAL", b"IDS", b"1", b"1-1"]);
18192        assert!(
18193            f.run(&[b"XPENDING", b"s", b"g", b"-", b"+", b"10"])
18194                .contains(":9223372036854775807\r\n")
18195        );
18196        f.run(&[
18197            b"XNACK",
18198            b"s",
18199            b"g",
18200            b"FATAL",
18201            b"IDS",
18202            b"1",
18203            b"1-1",
18204            b"RETRYCOUNT",
18205            b"3",
18206        ]);
18207        assert!(
18208            f.run(&[b"XPENDING", b"s", b"g", b"-", b"+", b"10"])
18209                .contains(":-1\r\n:3\r\n")
18210        );
18211
18212        // Releasing something the group is not holding is zero, and `FORCE`
18213        // makes the pending entry rather than answering zero. A forced entry
18214        // starts at zero, since there was no earlier count to keep.
18215        f.run(&[b"XACK", b"s", b"g", b"2-1"]);
18216        assert_eq!(
18217            f.run(&[b"XNACK", b"s", b"g", b"FAIL", b"IDS", b"1", b"2-1"]),
18218            ":0\r\n"
18219        );
18220        assert_eq!(
18221            f.run(&[
18222                b"XNACK", b"s", b"g", b"FAIL", b"IDS", b"1", b"2-1", b"FORCE"
18223            ]),
18224            ":1\r\n"
18225        );
18226        assert!(
18227            f.run(&[b"XPENDING", b"s", b"g", b"-", b"+", b"10"])
18228                .contains(":-1\r\n:0\r\n")
18229        );
18230        // `FORCE` on an ID the stream does not have is still zero.
18231        assert_eq!(
18232            f.run(&[
18233                b"XNACK", b"s", b"g", b"FAIL", b"IDS", b"1", b"9-9", b"FORCE"
18234            ]),
18235            ":0\r\n"
18236        );
18237
18238        // The group is looked up before the mode word, and it raises rather
18239        // than answering per ID the way the two delete commands do.
18240        assert_eq!(
18241            f.run(&[b"XNACK", b"s", b"nope", b"BOGUS", b"IDS", b"1", b"1-1"]),
18242            "-NOGROUP No such key 's' or consumer group 'nope'\r\n"
18243        );
18244        assert!(
18245            f.run(&[b"XNACK", b"s", b"g", b"BOGUS", b"IDS", b"1", b"1-1"])
18246                .starts_with("-ERR")
18247        );
18248        // Its own sentences, which are not the ones XDELEX uses.
18249        assert!(
18250            f.run(&[b"XNACK", b"s", b"g", b"FAIL", b"IDS", b"0", b"1-1"])
18251                .contains("numids must be a positive integer")
18252        );
18253        assert!(
18254            f.run(&[b"XNACK", b"s", b"g", b"FAIL", b"IDS", b"2", b"1-1"])
18255                .contains("number of IDs doesn't match numids")
18256        );
18257        // Everything past the counted IDs is an option, so one too many is an
18258        // option nobody recognises and not a count that does not add up.
18259        assert!(
18260            f.run(&[b"XNACK", b"s", b"g", b"FAIL", b"IDS", b"1", b"1-1", b"2-1"])
18261                .contains("Unrecognized XNACK option '2-1'")
18262        );
18263    }
18264
18265    /// `XINFO`, which is where the shape of the storage shows through.
18266    #[test]
18267    fn xinfo_reports_the_stream_the_groups_and_the_consumers() {
18268        let mut f = Fixture::new();
18269        f.run(&[b"XADD", b"s", b"1-1", b"a", b"1"]);
18270        f.run(&[b"XADD", b"s", b"2-1", b"a", b"2"]);
18271        f.run(&[b"XGROUP", b"CREATE", b"s", b"g", b"0"]);
18272        f.run(&[
18273            b"XREADGROUP",
18274            b"GROUP",
18275            b"g",
18276            b"c1",
18277            b"COUNT",
18278            b"1",
18279            b"STREAMS",
18280            b"s",
18281            b">",
18282        ]);
18283
18284        let info = f.run(&[b"XINFO", b"STREAM", b"s"]);
18285        // Ten pairs, since the six idempotency fields have nothing behind them
18286        // here and a zero would claim they had. That is D-27.
18287        assert!(info.starts_with("*20\r\n"), "{info}");
18288        assert!(info.contains("$6\r\nlength\r\n:2\r\n"), "{info}");
18289        assert!(
18290            info.contains("$17\r\nlast-generated-id\r\n$3\r\n2-1\r\n"),
18291            "{info}"
18292        );
18293        assert!(info.contains("$13\r\nentries-added\r\n:2\r\n"), "{info}");
18294        assert!(info.contains("$6\r\ngroups\r\n:1\r\n"), "{info}");
18295
18296        let groups = f.run(&[b"XINFO", b"GROUPS", b"s"]);
18297        assert!(groups.starts_with("*1\r\n*12\r\n"), "{groups}");
18298        assert!(groups.contains("$9\r\nconsumers\r\n:1\r\n"), "{groups}");
18299        assert!(groups.contains("$7\r\npending\r\n:1\r\n"), "{groups}");
18300        assert!(groups.contains("$3\r\nlag\r\n:1\r\n"), "{groups}");
18301
18302        // A consumer that has never been given anything reports minus one for
18303        // inactive rather than the moment it turned up, which is what tells a
18304        // worker that is stuck from one that has nothing to do.
18305        f.run(&[b"XGROUP", b"CREATECONSUMER", b"s", b"g", b"c2"]);
18306        let consumers = f.run(&[b"XINFO", b"CONSUMERS", b"s", b"g"]);
18307        assert!(consumers.starts_with("*2\r\n"), "{consumers}");
18308        assert!(
18309            consumers.contains("$8\r\ninactive\r\n:-1\r\n"),
18310            "{consumers}"
18311        );
18312        // And in name order, which the storage does not hold them in.
18313        let c1 = consumers.find("c1").unwrap();
18314        let c2 = consumers.find("c2").unwrap();
18315        assert!(c1 < c2, "{consumers}");
18316
18317        let full = f.run(&[b"XINFO", b"STREAM", b"s", b"FULL"]);
18318        assert!(full.starts_with("*18\r\n"), "{full}");
18319        assert!(full.contains("$12\r\nnacked-count\r\n:0\r\n"), "{full}");
18320        assert!(full.contains("$11\r\nactive-time\r\n"), "{full}");
18321
18322        assert!(
18323            f.run(&[b"XINFO", b"STREAM", b"missing"])
18324                .contains("no such key")
18325        );
18326        assert!(
18327            f.run(&[b"XINFO", b"GROUPS", b"missing"])
18328                .contains("no such key")
18329        );
18330        assert!(
18331            f.run(&[b"XINFO", b"CONSUMERS", b"s", b"nope"])
18332                .starts_with("-NOGROUP")
18333        );
18334        assert!(
18335            f.run(&[b"XINFO", b"NOSUCH", b"s"])
18336                .contains("Try XINFO HELP")
18337        );
18338        assert!(f.run(&[b"XINFO", b"HELP"]).contains("XINFO <subcommand>"));
18339        assert!(f.run(&[b"XGROUP", b"HELP"]).contains("XGROUP <subcommand>"));
18340    }
18341
18342    /// `XPENDING`'s long form, which reads its arguments by counting them.
18343    #[test]
18344    fn xpending_takes_the_consumer_only_when_the_count_comes_out_right() {
18345        let mut f = Fixture::new();
18346        f.run(&[b"XADD", b"s", b"1-1", b"a", b"1"]);
18347        f.run(&[b"XGROUP", b"CREATE", b"s", b"g", b"0"]);
18348        f.run(&[b"XREADGROUP", b"GROUP", b"g", b"c1", b"STREAMS", b"s", b">"]);
18349
18350        let list = f.run(&[b"XPENDING", b"s", b"g", b"-", b"+", b"10"]);
18351        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");
18352        assert_eq!(
18353            f.run(&[b"XPENDING", b"s", b"g", b"-", b"+", b"10", b"c1"]),
18354            "*1\r\n*4\r\n$3\r\n1-1\r\n$2\r\nc1\r\n:0\r\n:1\r\n"
18355        );
18356        // A consumer nobody has heard of holds nothing rather than erroring.
18357        assert_eq!(
18358            f.run(&[b"XPENDING", b"s", b"g", b"-", b"+", b"10", b"nope"]),
18359            "*0\r\n"
18360        );
18361        assert_eq!(
18362            f.run(&[b"XPENDING", b"s", b"g", b"IDLE", b"0", b"-", b"+", b"10"]),
18363            list
18364        );
18365        // IDLE is only read at position three.
18366        assert!(
18367            f.run(&[b"XPENDING", b"s", b"g", b"IDLE", b"0"])
18368                .contains("syntax error")
18369        );
18370        assert!(
18371            f.run(&[b"XPENDING", b"s", b"g", b"-", b"+"])
18372                .contains("syntax error")
18373        );
18374        assert_eq!(
18375            f.run(&[b"XPENDING", b"s", b"g", b"-", b"+", b"-1"]),
18376            "*0\r\n"
18377        );
18378        assert!(
18379            f.run(&[b"XPENDING", b"missing", b"g"])
18380                .starts_with("-NOGROUP")
18381        );
18382    }
18383
18384    /// `XSETID`, which is three counters and two refusals.
18385    #[test]
18386    fn xsetid_will_not_go_below_what_is_there() {
18387        let mut f = Fixture::new();
18388        f.run(&[b"XADD", b"s", b"5-5", b"a", b"1"]);
18389        assert_eq!(f.run(&[b"XSETID", b"s", b"9-9"]), "+OK\r\n");
18390        assert_eq!(
18391            f.run(&[
18392                b"XSETID",
18393                b"s",
18394                b"10-1",
18395                b"ENTRIESADDED",
18396                b"7",
18397                b"MAXDELETEDID",
18398                b"9-1"
18399            ]),
18400            "+OK\r\n"
18401        );
18402        let info = f.run(&[b"XINFO", b"STREAM", b"s"]);
18403        assert!(info.contains("$13\r\nentries-added\r\n:7\r\n"), "{info}");
18404        assert!(
18405            info.contains("$20\r\nmax-deleted-entry-id\r\n$3\r\n9-1\r\n"),
18406            "{info}"
18407        );
18408
18409        assert!(
18410            f.run(&[b"XSETID", b"s", b"1-1"])
18411                .contains("smaller than the target stream top item")
18412        );
18413        assert!(
18414            f.run(&[b"XSETID", b"s", b"10-1", b"ENTRIESADDED", b"-1"])
18415                .contains("entries_added must be positive")
18416        );
18417        assert!(
18418            f.run(&[b"XSETID", b"missing", b"1-1"])
18419                .contains("no such key")
18420        );
18421    }
18422
18423    /// RESP3, where the two reads answer a map and the entries stay an array.
18424    #[test]
18425    fn xread_answers_a_map_on_resp3_and_the_fields_stay_flat() {
18426        let mut f = Fixture::new();
18427        f.run(&[b"HELLO", b"3"]);
18428        f.run(&[b"XADD", b"s", b"1-1", b"a", b"1"]);
18429        // A map header and then the key and the entries side by side, with no
18430        // two element array wrapping the pair.
18431        assert_eq!(
18432            f.run(&[b"XREAD", b"STREAMS", b"s", b"0"]),
18433            "%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"
18434        );
18435        // The fields are still one flat array and not a map, which is Redis's
18436        // shape and is what every consumer written before RESP3 expects.
18437        assert_eq!(
18438            f.run(&[b"XRANGE", b"s", b"-", b"+"]),
18439            "*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"
18440        );
18441        assert_eq!(f.run(&[b"XREAD", b"STREAMS", b"s", b"1-1"]), "_\r\n");
18442    }
18443
18444    /// A store to migrate values into, so a test can watch the inversion.
18445    ///
18446    /// A vector rather than a file for the same reason the tier's own tests use
18447    /// one: the file work has not attached a real store yet, and what this is
18448    /// checking is the policy above the store rather than the store.
18449    struct Mem {
18450        blobs: Vec<Vec<u8>>,
18451    }
18452
18453    impl yo_kv::cold::Blocks for Mem {
18454        fn put(&mut self, bytes: &[u8]) -> yo_common::Result<yo_common::Addr> {
18455            self.blobs.push(bytes.to_vec());
18456            Ok(yo_common::Addr::new(
18457                yo_common::Space::Log,
18458                (self.blobs.len() - 1) as u64,
18459            ))
18460        }
18461
18462        fn get(&self, at: yo_common::Addr) -> yo_common::Result<&[u8]> {
18463            self.blobs
18464                .get(at.offset() as usize)
18465                .map(Vec::as_slice)
18466                .ok_or_else(|| {
18467                    yo_common::Error::new(yo_common::Code::Corrupt, "no chunk at that address")
18468                })
18469        }
18470
18471        fn bytes(&self) -> u64 {
18472            self.blobs.iter().map(|b| b.len() as u64).sum()
18473        }
18474    }
18475
18476    /// A server holding several segments of strings, with somewhere to put them.
18477    ///
18478    /// Answers the fixture and what it was holding when it stopped filling.
18479    /// The three tests that call this are the ones Miri is not run over.
18480    ///
18481    /// What they are about is the regime a database is in once the arena has
18482    /// several segments, and a segment is two megabytes, so there is no smaller
18483    /// version of the question: twenty four thousand keys is already the least
18484    /// that gets there. Interpreted, each of them sat for over forty minutes
18485    /// and was still going. The arena's own segment handling is interpreted in
18486    /// full in its own crate, and the policy these three check is ordinary
18487    /// bookkeeping with no unsafe block anywhere in it.
18488    fn filled(attach: bool) -> (Fixture, usize) {
18489        let mut f = Fixture::new();
18490        if attach {
18491            f.server
18492                .striped(0)
18493                .hold_stripe(0)
18494                .attach(Box::new(Mem { blobs: Vec::new() }));
18495        }
18496        let val = vec![b'v'; 256];
18497        for i in 0..24000u32 {
18498            let k = format!("key:{i:08}");
18499            f.run(&[b"SET", k.as_bytes(), &val]);
18500        }
18501        let full = f.server.memory_bytes();
18502        assert!(full > 3 * 1024 * 1024, "the arena is several segments");
18503        (f, full)
18504    }
18505
18506    /// Write until the server is under `limit` or the writes run out.
18507    ///
18508    /// The same shape the eviction test uses. A memory limit is enforced in
18509    /// front of a command, so nothing happens until something is written, and
18510    /// the budget means one command does not do the whole job.
18511    fn press(f: &mut Fixture, limit: usize) {
18512        let val = vec![b'v'; 256];
18513        for i in 0..3000u32 {
18514            let k = format!("new:{i:08}");
18515            assert_eq!(
18516                f.run(&[b"SET", k.as_bytes(), &val]),
18517                "+OK\r\n",
18518                "write {i} was refused"
18519            );
18520            f.server.refresh_memory();
18521            if f.server.memory_bytes() <= limit {
18522                return;
18523            }
18524        }
18525        panic!(
18526            "it never got under: {} against {limit}",
18527            f.server.memory_bytes()
18528        );
18529    }
18530
18531    #[test]
18532    fn the_storage_limit_reads_back_and_minus_one_is_no_limit() {
18533        let mut f = Fixture::new();
18534        assert_eq!(
18535            f.run(&[b"CONFIG", b"GET", b"maxstore"]),
18536            "*2\r\n$8\r\nmaxstore\r\n$2\r\n-1\r\n",
18537            "no limit is the default"
18538        );
18539        // The same memory value parser `maxmemory` uses, and the same trap in
18540        // it, plus the one spelling that means no limit at all.
18541        for (typed, bytes) in [
18542            (&b"0"[..], "0"),
18543            (b"1024", "1024"),
18544            (b"1k", "1000"),
18545            (b"1gb", "1073741824"),
18546            (b"-1", "-1"),
18547        ] {
18548            assert_eq!(f.run(&[b"CONFIG", b"SET", b"maxstore", typed]), "+OK\r\n");
18549            assert_eq!(
18550                f.run(&[b"CONFIG", b"GET", b"maxstore"]),
18551                format!("*2\r\n$8\r\nmaxstore\r\n${}\r\n{bytes}\r\n", bytes.len()),
18552                "set {}",
18553                String::from_utf8_lossy(typed)
18554            );
18555        }
18556        for bad in [&b"1tb"[..], b"-2", b"", b"lots"] {
18557            assert_eq!(
18558                f.run(&[b"CONFIG", b"SET", b"maxstore", bad]),
18559                "-ERR CONFIG SET failed (possibly related to argument 'maxstore') - argument must be a memory value or -1\r\n",
18560                "refused {}",
18561                String::from_utf8_lossy(bad)
18562            );
18563        }
18564        // Nothing is attached, so the answer to a memory limit is still Redis's.
18565        let info = f.run(&[b"INFO", b"memory"]);
18566        assert!(info.contains("maxstore:-1"), "{info}");
18567        assert!(info.contains("yo_memory_regime:evict"), "{info}");
18568        assert!(info.contains("yo_store_bytes:0"), "{info}");
18569    }
18570
18571    #[cfg_attr(miri, ignore = "several megabytes of arena, see `filled`")]
18572    #[test]
18573    fn a_memory_limit_moves_values_to_the_file_instead_of_dropping_keys() {
18574        // The inversion. The same pressure that makes a Redis server throw keys
18575        // away makes this one move values to the file, and afterwards every key
18576        // is still there and still answers with what was stored in it.
18577        let (mut f, full) = filled(true);
18578        let keys = f.run(&[b"DBSIZE"]);
18579        assert!(
18580            f.run(&[b"INFO", b"memory"])
18581                .contains("yo_memory_regime:migrate"),
18582            "a database with somewhere to put values migrates"
18583        );
18584
18585        let limit = full - 2 * 1024 * 1024;
18586        f.run(&[b"CONFIG", b"SET", b"maxmemory-policy", b"allkeys-lru"]);
18587        f.run(&[
18588            b"CONFIG",
18589            b"SET",
18590            b"maxmemory",
18591            limit.to_string().as_bytes(),
18592        ]);
18593        press(&mut f, limit);
18594
18595        assert!(
18596            f.run(&[b"INFO", b"stats"]).contains("evicted_keys:0"),
18597            "nothing was thrown away"
18598        );
18599        let after: usize = f.run(&[b"DBSIZE"])[1..]
18600            .trim_end()
18601            .parse()
18602            .expect("a count");
18603        let before: usize = keys[1..].trim_end().parse().expect("a count");
18604        assert!(after > before, "the keys that came in are all still here");
18605        assert!(
18606            f.server.store_bytes() > 0,
18607            "and what came out of memory went to the file"
18608        );
18609        // And the values read back, which is the part that makes it a migration
18610        // rather than a loss.
18611        let val = format!("$256\r\n{}\r\n", "v".repeat(256));
18612        assert_eq!(f.run(&[b"GET", b"key:00000000"]), val);
18613        assert_eq!(f.run(&[b"GET", b"key:00023999"]), val);
18614    }
18615
18616    #[cfg_attr(miri, ignore = "several megabytes of arena, see `filled`")]
18617    #[test]
18618    fn a_storage_limit_of_zero_restores_redis_behaviour_exactly() {
18619        // The documented setting for a drop in cache. A file that may hold
18620        // nothing cannot be migrated to, so eviction is all that is left, and
18621        // the server behaves exactly as it did before any of this existed.
18622        let (mut f, full) = filled(true);
18623        f.run(&[b"CONFIG", b"SET", b"maxstore", b"0"]);
18624        assert!(
18625            f.run(&[b"INFO", b"memory"])
18626                .contains("yo_memory_regime:evict"),
18627            "nothing may go to the file"
18628        );
18629
18630        let limit = full - 2 * 1024 * 1024;
18631        f.run(&[b"CONFIG", b"SET", b"maxmemory-policy", b"allkeys-lru"]);
18632        f.run(&[
18633            b"CONFIG",
18634            b"SET",
18635            b"maxmemory",
18636            limit.to_string().as_bytes(),
18637        ]);
18638        press(&mut f, limit);
18639
18640        assert!(
18641            !f.run(&[b"INFO", b"stats"]).contains("evicted_keys:0"),
18642            "keys were thrown away, which is what was asked for"
18643        );
18644        assert_eq!(f.server.store_bytes(), 0, "and the file was never written");
18645    }
18646
18647    #[cfg_attr(miri, ignore = "several megabytes of arena, see `filled`")]
18648    #[test]
18649    fn a_full_file_goes_back_to_evicting() {
18650        // A storage limit reached is a storage limit, and eviction is the right
18651        // answer to one. The budget here is a few kilobytes, so the first round
18652        // of migration fills it and everything after that is evicted.
18653        let (mut f, full) = filled(true);
18654        f.run(&[b"CONFIG", b"SET", b"maxstore", b"64kb"]);
18655        let limit = full - 2 * 1024 * 1024;
18656        f.run(&[b"CONFIG", b"SET", b"maxmemory-policy", b"allkeys-lru"]);
18657        f.run(&[
18658            b"CONFIG",
18659            b"SET",
18660            b"maxmemory",
18661            limit.to_string().as_bytes(),
18662        ]);
18663        press(&mut f, limit);
18664
18665        assert!(f.server.store_bytes() >= 64 * 1024, "the file filled up");
18666        assert!(
18667            !f.run(&[b"INFO", b"stats"]).contains("evicted_keys:0"),
18668            "and then it started evicting"
18669        );
18670        assert!(
18671            f.run(&[b"INFO", b"memory"])
18672                .contains("yo_memory_regime:evict"),
18673            "and it says so"
18674        );
18675    }
18676    // ------------------------------------------------------------- stripes
18677
18678    /// Every string command, run twice: once on a database that is one keyspace
18679    /// and once on a database that is eight, with the same commands in the same
18680    /// order and the replies compared byte for byte.
18681    ///
18682    /// This is the whole claim the striping rests on. A key belongs to one
18683    /// stripe and to no other, so the answer to a command cannot depend on how
18684    /// many stripes there are, and the way to check that is to ask the same
18685    /// question of two servers that differ in nothing else.
18686    ///
18687    /// The keys are chosen to land on different stripes rather than to look
18688    /// tidy. `MSET a 1 b 2 c 3` over eight stripes is only a test of anything if
18689    /// those three keys are not all on the same one, and at eight stripes three
18690    /// keys land together about one time in fifty.
18691    #[test]
18692    fn the_string_group_answers_the_same_however_many_stripes_there_are() {
18693        let script: &[&[&[u8]]] = &[
18694            // The single key commands, which are the ones that get handed one
18695            // stripe at the dispatch site.
18696            &[b"SET", b"k1", b"v1"],
18697            &[b"SET", b"k2", b"v2"],
18698            &[b"GET", b"k1"],
18699            &[b"GET", b"nothing"],
18700            &[b"GETSET", b"k1", b"v1b"],
18701            &[b"SETNX", b"k1", b"no"],
18702            &[b"SETNX", b"k3", b"yes"],
18703            &[b"APPEND", b"k3", b"!"],
18704            &[b"STRLEN", b"k3"],
18705            &[b"SETRANGE", b"k3", b"1", b"XY"],
18706            &[b"GETRANGE", b"k3", b"0", b"-1"],
18707            &[b"INCR", b"n1"],
18708            &[b"INCRBY", b"n1", b"41"],
18709            &[b"DECRBY", b"n1", b"2"],
18710            &[b"INCRBYFLOAT", b"f1", b"1.5"],
18711            &[b"SETEX", b"e1", b"100", b"v"],
18712            &[b"PSETEX", b"e2", b"100000", b"v"],
18713            &[b"GETEX", b"e1", b"PERSIST"],
18714            &[b"GETDEL", b"k2"],
18715            &[b"GET", b"k2"],
18716            &[b"DIGEST", b"k1"],
18717            &[b"DELEX", b"k3"],
18718            // The five that name more than one key, which are the ones that
18719            // cannot be handed one stripe at all.
18720            &[b"MSET", b"a", b"1", b"b", b"2", b"c", b"3"],
18721            &[b"MGET", b"a", b"b", b"c", b"missing"],
18722            &[b"MSETNX", b"d", b"4", b"e", b"5"],
18723            &[b"MSETNX", b"e", b"6", b"f", b"7"],
18724            &[b"MGET", b"d", b"e", b"f"],
18725            &[b"MSETEX", b"2", b"g", b"7", b"h", b"8", b"NX"],
18726            &[b"MSETEX", b"2", b"g", b"9", b"h", b"9", b"NX"],
18727            &[b"MSETEX", b"2", b"g", b"9", b"h", b"9", b"XX"],
18728            &[b"MGET", b"g", b"h"],
18729            &[b"SET", b"s1", b"ohmytext"],
18730            &[b"SET", b"s2", b"mynewtext"],
18731            &[b"LCS", b"s1", b"s2"],
18732            &[b"LCS", b"s1", b"s2", b"LEN"],
18733            &[b"LCS", b"s1", b"s2", b"IDX", b"MINMATCHLEN", b"4"],
18734            &[b"LCS", b"s1", b"s2", b"IDX", b"WITHMATCHLEN"],
18735            &[b"LCS", b"s1", b"gone"],
18736            // And the errors, which have to be the same errors.
18737            &[b"MSET", b"odd"],
18738            &[b"LCS", b"s1", b"s2", b"LEN", b"IDX"],
18739            &[b"MGET"],
18740        ];
18741
18742        let mut one = Fixture::new();
18743        let mut many = Fixture::striped(8);
18744        for parts in script {
18745            let a = one.run(parts);
18746            let b = many.run(parts);
18747            assert_eq!(a, b, "{}", String::from_utf8_lossy(parts[0]));
18748        }
18749    }
18750
18751    /// The keys of an `MSET` really do end up on different stripes.
18752    ///
18753    /// Without this the test above could pass on a server whose stripe number
18754    /// happened to be a constant, which is a striped database in name only.
18755    #[test]
18756    fn a_striped_database_spreads_the_keys_it_is_given() {
18757        let mut f = Fixture::striped(8);
18758        for i in 0..256 {
18759            let key = format!("key:{i}");
18760            f.run(&[b"SET", key.as_bytes(), b"v"]);
18761        }
18762        assert_eq!(f.run(&[b"DBSIZE"]), ":256\r\n");
18763    }
18764
18765    /// A wrong type stops an `MGET` no more than it does on one stripe: the key
18766    /// that is not a string comes back nil and the rest of the reply is intact.
18767    #[test]
18768    fn a_wrong_type_in_the_middle_of_an_mget_is_still_one_nil() {
18769        let mut one = Fixture::new();
18770        let mut many = Fixture::striped(8);
18771        for f in [&mut one, &mut many] {
18772            f.run(&[b"SET", b"str", b"v"]);
18773            // Planted rather than pushed. `RPUSH` belongs to the list group,
18774            // which has not been taught about stripes yet and would refuse the
18775            // wide server. What is under test is what `MGET` does when it walks
18776            // onto a key that is not a string, and that does not care how the
18777            // key got there.
18778            f.server
18779                .striped(0)
18780                .hold(b"list")
18781                .push(b"list", yo_kv::End::Right, core::iter::once(&b"v"[..]))
18782                .expect("a new list");
18783        }
18784        assert_eq!(
18785            one.run(&[b"MGET", b"str", b"list", b"gone"]),
18786            many.run(&[b"MGET", b"str", b"list", b"gone"])
18787        );
18788    }
18789
18790    /// The same claim for the keyspace group, and the same way of checking it.
18791    ///
18792    /// `SORT` is not in the script because it is the one command in that file
18793    /// that has not been taught about stripes, and `SCAN`, `KEYS` and
18794    /// `RANDOMKEY` are not in it either, because those three do not promise an
18795    /// order and comparing two replies byte for byte would be asserting one.
18796    /// They get tests of their own below.
18797    #[test]
18798    fn the_keyspace_group_answers_the_same_however_many_stripes_there_are() {
18799        let script: &[&[&[u8]]] = &[
18800            &[b"SET", b"k1", b"v1"],
18801            &[b"SET", b"k2", b"v2"],
18802            &[b"EXISTS", b"k1", b"k2", b"k1", b"gone"],
18803            &[b"TYPE", b"k1"],
18804            &[b"TYPE", b"gone"],
18805            &[b"TOUCH", b"k1", b"k2", b"k1", b"gone"],
18806            &[b"EXPIRE", b"k1", b"100"],
18807            &[b"TTL", b"k1"],
18808            &[b"EXPIRE", b"k1", b"200", b"NX"],
18809            &[b"PERSIST", b"k1"],
18810            &[b"TTL", b"k1"],
18811            &[b"PEXPIREAT", b"k2", b"1900000000000"],
18812            &[b"EXPIRETIME", b"k2"],
18813            &[b"PEXPIRETIME", b"k2"],
18814            &[b"PERSIST", b"k2"],
18815            &[b"OBJECT", b"ENCODING", b"k1"],
18816            &[b"OBJECT", b"REFCOUNT", b"k1"],
18817            &[b"OBJECT", b"IDLETIME", b"k1"],
18818            &[b"OBJECT", b"FREQ", b"k1"],
18819            &[b"OBJECT", b"ENCODING", b"gone"],
18820            &[b"OBJECT", b"HELP"],
18821            &[b"RENAME", b"k1", b"k9"],
18822            &[b"GET", b"k9"],
18823            &[b"RENAME", b"gone", b"x"],
18824            &[b"RENAMENX", b"k9", b"k2"],
18825            &[b"RENAMENX", b"k9", b"k8"],
18826            &[b"GET", b"k8"],
18827            &[b"COPY", b"k8", b"c1"],
18828            &[b"COPY", b"k8", b"c1"],
18829            &[b"COPY", b"k8", b"c1", b"REPLACE"],
18830            &[b"COPY", b"k8", b"k8"],
18831            &[b"COPY", b"gone", b"c2"],
18832            &[b"COPY", b"k8", b"k8", b"DB", b"1"],
18833            &[b"COPY", b"k8", b"c9", b"DB", b"9"],
18834            &[b"MOVE", b"c1", b"1"],
18835            &[b"MOVE", b"c1", b"1"],
18836            &[b"MOVE", b"k8", b"0"],
18837            &[b"DEL", b"k2", b"gone"],
18838            &[b"UNLINK", b"k8", b"k8"],
18839            &[b"DBSIZE"],
18840        ];
18841
18842        let mut one = Fixture::new();
18843        let mut many = Fixture::striped(8);
18844        for parts in script {
18845            let a = one.run(parts);
18846            let b = many.run(parts);
18847            assert_eq!(a, b, "{}", String::from_utf8_lossy(parts[0]));
18848        }
18849
18850        // `RESTORE` needs bytes a client would have got from a `DUMP`, so the
18851        // payload is taken from the store rather than parsed back out of a
18852        // reply that is not text. Both servers dump the same key and the bytes
18853        // are the same bytes, which is the first half of what is being checked
18854        // here.
18855        for f in [&mut one, &mut many] {
18856            f.run(&[b"SET", b"d1", b"payload"]);
18857            let payload = f
18858                .server
18859                .striped(0)
18860                .hold(b"d1")
18861                .dump(b"d1")
18862                .expect("a key that is there");
18863            assert!(
18864                f.run(&[b"DUMP", b"d1"])
18865                    .starts_with(&format!("${}", payload.len())),
18866                "a payload of the length the store gave"
18867            );
18868            assert_eq!(f.run(&[b"DUMP", b"gone"]), "$-1\r\n");
18869            assert_eq!(f.run(&[b"RESTORE", b"d2", b"0", &payload]), "+OK\r\n");
18870            assert_eq!(f.run(&[b"GET", b"d2"]), "$7\r\npayload\r\n");
18871            assert_eq!(
18872                f.run(&[b"RESTORE", b"d2", b"0", &payload]),
18873                "-BUSYKEY Target key name already exists.\r\n"
18874            );
18875            assert_eq!(
18876                f.run(&[b"RESTORE", b"d3", b"0", b"rubbish"]),
18877                "-ERR DUMP payload version or checksum are wrong\r\n"
18878            );
18879        }
18880    }
18881
18882    /// A `SCAN` of a database of eight stripes comes back with all of it.
18883    ///
18884    /// The cursor is the thing under test. It has to carry the stripe as well
18885    /// as the place in it, so a client that stops at one stripe and comes back
18886    /// carries on in that stripe and not at the top of the database, and the
18887    /// walk has to end once rather than eight times.
18888    #[test]
18889    fn a_scan_of_a_striped_database_walks_all_of_it() {
18890        // Eight stripes and a COUNT of ten, so eighty keys is already more than
18891        // one page on every stripe and the cursor has to carry which stripe it
18892        // was on, which is the thing being checked.
18893        let n = if cfg!(miri) { 80 } else { 500 };
18894        let mut f = Fixture::striped(8);
18895        for i in 0..n {
18896            let key = format!("key:{i}");
18897            f.run(&[b"SET", key.as_bytes(), b"v"]);
18898        }
18899
18900        let mut seen = Vec::new();
18901        let mut cursor = "0".to_owned();
18902        let mut calls = 0;
18903        loop {
18904            let reply = f.run(&[b"SCAN", cursor.as_bytes(), b"COUNT", b"10"]);
18905            let (next, keys) = scan_reply(&reply);
18906            seen.extend(keys);
18907            cursor = next;
18908            calls += 1;
18909            assert!(calls < 5_000, "a scan that will not finish");
18910            if cursor == "0" {
18911                break;
18912            }
18913        }
18914        seen.sort();
18915        assert_eq!(seen.len(), n, "a quiet scan answered a key twice");
18916        assert_eq!(seen, sorted(&f.run(&[b"KEYS", b"*"])));
18917
18918        // And the options still work when the walk is over several stripes,
18919        // since a `MATCH` is applied to keys a stripe handed up and a `TYPE` is
18920        // applied by each stripe on the way.
18921        let reply = f.run(&[b"SCAN", b"0", b"COUNT", b"1000", b"MATCH", b"key:4?"]);
18922        let (_, keys) = scan_reply(&reply);
18923        assert_eq!(keys.len(), 10, "key:40 through key:49");
18924        let reply = f.run(&[b"SCAN", b"0", b"COUNT", b"1000", b"TYPE", b"list"]);
18925        let (_, keys) = scan_reply(&reply);
18926        assert!(keys.is_empty(), "nothing here is a list");
18927    }
18928
18929    /// `RANDOMKEY` on a striped database answers a key from any of the stripes.
18930    ///
18931    /// The draw picks the stripe first, so the thing that can go wrong is that
18932    /// it always picks the same one, and two hundred draws over eight stripes
18933    /// would make that obvious.
18934    #[test]
18935    fn a_random_key_can_come_from_any_stripe() {
18936        let mut f = Fixture::striped(8);
18937        assert_eq!(f.run(&[b"RANDOMKEY"]), "$-1\r\n");
18938        for i in 0..200 {
18939            let key = format!("key:{i}");
18940            f.run(&[b"SET", key.as_bytes(), b"v"]);
18941        }
18942        let mut homes = std::collections::HashSet::new();
18943        for _ in 0..200 {
18944            let got = f.run(&[b"RANDOMKEY"]);
18945            let key = got.split("\r\n").nth(1).expect("a key").to_owned();
18946            assert_eq!(f.run(&[b"EXISTS", key.as_bytes()]), ":1\r\n");
18947            homes.insert(f.server.striped(0).stripe_of(key.as_bytes()));
18948        }
18949        assert_eq!(homes.len(), 8, "some stripe was never drawn from");
18950    }
18951
18952    /// Two keys that are not on the same stripe, which is what `RENAME` and
18953    /// `COPY` have to cope with and what a test has to arrange rather than
18954    /// hope for.
18955    fn apart(f: &mut Fixture, src: &str) -> String {
18956        let home = f.server.striped(0).stripe_of(src.as_bytes());
18957        for i in 0..1_000 {
18958            let dst = format!("dst:{i}");
18959            if f.server.striped(0).stripe_of(dst.as_bytes()) != home {
18960                return dst;
18961            }
18962        }
18963        panic!("eight stripes and a thousand keys all landed in one place");
18964    }
18965
18966    /// A rename whose two keys are on two stripes moves the value, the deadline
18967    /// and, for a collection, the body itself.
18968    #[test]
18969    fn a_rename_across_stripes_takes_everything_with_it() {
18970        let mut f = Fixture::striped(8);
18971        let dst = apart(&mut f, "src");
18972        let (src, dst) = (b"src".as_slice(), dst.as_bytes());
18973
18974        f.run(&[b"SET", src, b"v"]);
18975        f.run(&[b"EXPIRE", src, b"100"]);
18976        assert_eq!(f.run(&[b"RENAME", src, dst]), "+OK\r\n");
18977        assert_eq!(f.run(&[b"EXISTS", src, dst]), ":1\r\n");
18978        assert_eq!(f.run(&[b"GET", dst]), "$1\r\nv\r\n");
18979        assert_eq!(f.run(&[b"TTL", dst]), ":100\r\n", "the deadline came too");
18980
18981        // A list, because a string lives in its record and a collection lives
18982        // in a slab, and the second of those is the one that can be left
18983        // behind. Planted through the store, since the list group has not been
18984        // taught about stripes yet.
18985        f.server
18986            .striped(0)
18987            .hold(src)
18988            .push(src, yo_kv::End::Right, [&b"a"[..], &b"b"[..]].into_iter())
18989            .expect("a new list");
18990        assert_eq!(f.run(&[b"RENAME", src, dst]), "+OK\r\n");
18991        assert_eq!(f.run(&[b"TYPE", dst]), "+list\r\n");
18992        assert_eq!(
18993            f.server.striped(0).hold(dst).llen(dst).expect("a list"),
18994            2,
18995            "the members are on the stripe the key moved to"
18996        );
18997
18998        // And `RENAMENX` still refuses a destination that is taken, which is
18999        // the one answer the cross stripe path has to work out for itself.
19000        f.run(&[b"SET", src, b"v"]);
19001        assert_eq!(f.run(&[b"RENAMENX", src, dst]), ":0\r\n");
19002        assert_eq!(f.run(&[b"TYPE", dst]), "+list\r\n", "and left it alone");
19003        assert_eq!(f.run(&[b"GET", src]), "$1\r\nv\r\n", "and left the source");
19004    }
19005
19006    /// And a copy across two stripes leaves both keys behind it.
19007    #[test]
19008    fn a_copy_across_stripes_leaves_the_source_where_it_was() {
19009        let mut f = Fixture::striped(8);
19010        let dst = apart(&mut f, "src");
19011        let (src, dst) = (b"src".as_slice(), dst.as_bytes());
19012
19013        f.run(&[b"SET", src, b"v"]);
19014        assert_eq!(f.run(&[b"COPY", src, dst]), ":1\r\n");
19015        assert_eq!(f.run(&[b"EXISTS", src, dst]), ":2\r\n");
19016        assert_eq!(
19017            f.run(&[b"COPY", src, dst]),
19018            ":0\r\n",
19019            "the destination is taken"
19020        );
19021        f.run(&[b"SET", src, b"w"]);
19022        assert_eq!(f.run(&[b"COPY", src, dst, b"REPLACE"]), ":1\r\n");
19023        assert_eq!(f.run(&[b"GET", dst]), "$1\r\nw\r\n");
19024
19025        // A collection is cloned rather than moved, so both keys have a body of
19026        // their own afterwards and writing to one does not show up in the
19027        // other.
19028        f.run(&[b"DEL", src, dst]);
19029        f.server
19030            .striped(0)
19031            .hold(src)
19032            .push(src, yo_kv::End::Right, [&b"a"[..], &b"b"[..]].into_iter())
19033            .expect("a new list");
19034        assert_eq!(f.run(&[b"COPY", src, dst]), ":1\r\n");
19035        f.server
19036            .striped(0)
19037            .hold(src)
19038            .push(src, yo_kv::End::Right, core::iter::once(&b"c"[..]))
19039            .expect("a list that is there");
19040        assert_eq!(f.server.striped(0).hold(src).llen(src).expect("a list"), 3);
19041        assert_eq!(f.server.striped(0).hold(dst).llen(dst).expect("a list"), 2);
19042    }
19043
19044    /// Every bitmap command, on one stripe and on eight, replies compared byte
19045    /// for byte.
19046    ///
19047    /// `BITOP` is the one that names more than one key and it is where the work
19048    /// went. The rest are single key commands that now find their own stripe,
19049    /// and they are here because the cheapest way to be sure the routing is
19050    /// right is to ask.
19051    #[test]
19052    fn the_bitmap_group_answers_the_same_however_many_stripes_there_are() {
19053        let script: &[&[&[u8]]] = &[
19054            &[b"SET", b"k1", b"foobar"],
19055            &[b"SETBIT", b"b1", b"7", b"1"],
19056            &[b"SETBIT", b"b1", b"7", b"0"],
19057            &[b"GETBIT", b"k1", b"6"],
19058            &[b"GETBIT", b"k1", b"100"],
19059            &[b"BITCOUNT", b"k1"],
19060            &[b"BITCOUNT", b"k1", b"0", b"0"],
19061            &[b"BITCOUNT", b"k1", b"5", b"30", b"BIT"],
19062            &[b"BITPOS", b"k1", b"1"],
19063            &[b"BITPOS", b"k1", b"0", b"2"],
19064            &[b"BITPOS", b"k1", b"1", b"2", b"-1", b"BIT"],
19065            &[
19066                b"BITFIELD",
19067                b"bf",
19068                b"SET",
19069                b"u8",
19070                b"0",
19071                b"255",
19072                b"GET",
19073                b"u8",
19074                b"0",
19075            ],
19076            &[
19077                b"BITFIELD",
19078                b"bf",
19079                b"OVERFLOW",
19080                b"SAT",
19081                b"INCRBY",
19082                b"u8",
19083                b"0",
19084                b"10",
19085            ],
19086            &[b"BITFIELD_RO", b"bf", b"GET", b"u8", b"0"],
19087            // The multi key one, over sources that are not on one stripe unless
19088            // eight stripes have folded into one.
19089            &[b"SET", b"s1", b"abc"],
19090            &[b"SET", b"s2", b"abd"],
19091            &[b"SET", b"s3", b"a"],
19092            &[b"BITOP", b"AND", b"d1", b"s1", b"s2"],
19093            &[b"GET", b"d1"],
19094            &[b"BITOP", b"OR", b"d2", b"s1", b"s2", b"s3"],
19095            &[b"GET", b"d2"],
19096            &[b"BITOP", b"XOR", b"d3", b"s1", b"s2"],
19097            &[b"STRLEN", b"d3"],
19098            &[b"BITOP", b"NOT", b"d4", b"s1"],
19099            &[b"STRLEN", b"d4"],
19100            &[b"BITOP", b"DIFF", b"d5", b"s1", b"s2"],
19101            &[b"BITOP", b"DIFF1", b"d6", b"s1", b"s2"],
19102            &[b"BITOP", b"ANDOR", b"d7", b"s1", b"s2"],
19103            &[b"BITOP", b"ONE", b"d8", b"s1", b"s2"],
19104            // A source that is not there reads as empty, and a result with
19105            // nothing in it deletes the destination rather than writing one.
19106            &[b"BITOP", b"AND", b"d1", b"gone", b"also-gone"],
19107            &[b"EXISTS", b"d1"],
19108            &[b"BITOP", b"OR", b"d9", b"s1", b"gone"],
19109            &[b"GET", b"d9"],
19110            // And the errors, which have to be the same errors. The key that
19111            // is not a string is planted below rather than pushed here, since
19112            // the list group has not been taught about stripes yet.
19113            &[b"BITOP", b"AND", b"d1", b"s1", b"list"],
19114            &[b"BITOP", b"AND", b"list", b"s1", b"s2"],
19115            &[b"BITOP", b"NOT", b"d1", b"s1", b"s2"],
19116            &[b"BITOP", b"DIFF", b"d1", b"s1"],
19117            &[b"BITOP", b"NOPE", b"d1", b"s1"],
19118            &[b"BITCOUNT", b"list"],
19119            &[b"BITFIELD_RO", b"bf", b"SET", b"u8", b"0", b"1"],
19120        ];
19121
19122        let mut one = Fixture::new();
19123        let mut many = Fixture::striped(8);
19124        for f in [&mut one, &mut many] {
19125            plant_list(f, b"list");
19126        }
19127        for parts in script {
19128            let a = one.run(parts);
19129            let b = many.run(parts);
19130            assert_eq!(a, b, "{}", String::from_utf8_lossy(parts[0]));
19131        }
19132    }
19133
19134    /// A list under `key`, put there through the store.
19135    ///
19136    /// What a test does when it wants a key of the wrong type on a striped
19137    /// server, because the command that would make one is in a group that has
19138    /// not been taught about stripes yet.
19139    fn plant_list(f: &mut Fixture, key: &[u8]) {
19140        f.server
19141            .striped(0)
19142            .hold(key)
19143            .push(key, yo_kv::End::Right, core::iter::once(&b"x"[..]))
19144            .expect("a new list");
19145    }
19146
19147    /// A `BITOP` whose keys are on two stripes reads both of them.
19148    ///
19149    /// The test above spreads its keys by hashing and would still pass if one
19150    /// stripe were doing all the work, since the answers would be the same. This
19151    /// one puts the destination and the two sources where they are known not to
19152    /// share a stripe.
19153    #[test]
19154    fn a_bitop_across_stripes_reads_every_source() {
19155        let mut f = Fixture::striped(8);
19156        let other = apart(&mut f, "src");
19157        let (src, far) = (b"src".as_slice(), other.as_bytes());
19158        assert_ne!(
19159            f.server.striped(0).stripe_of(src),
19160            f.server.striped(0).stripe_of(far),
19161            "the two keys are the point of the test"
19162        );
19163
19164        f.run(&[b"SET", src, b"abc"]);
19165        f.run(&[b"SET", far, b"abd"]);
19166        assert_eq!(f.run(&[b"BITOP", b"AND", far, src, far]), ":3\r\n");
19167        assert_eq!(
19168            f.run(&[b"GET", far]),
19169            "$3\r\nab`\r\n",
19170            "a destination that is also a source"
19171        );
19172        f.run(&[b"SET", far, b"abd"]);
19173        assert_eq!(f.run(&[b"BITOP", b"XOR", src, src, far]), ":3\r\n");
19174        assert_eq!(
19175            f.run(&[b"GET", src]),
19176            "$3\r\n\0\0\x07\r\n",
19177            "and the other way round"
19178        );
19179
19180        // A result of nothing deletes a destination on whatever stripe it is
19181        // on, and a source of the wrong type is refused before anything is
19182        // written.
19183        f.run(&[b"SET", src, b"abc"]);
19184        f.run(&[b"DEL", far]);
19185        assert_eq!(f.run(&[b"BITOP", b"AND", src, far, b"gone"]), ":0\r\n");
19186        assert_eq!(f.run(&[b"EXISTS", src]), ":0\r\n");
19187        f.run(&[b"SET", src, b"abc"]);
19188        f.run(&[b"DEL", far]);
19189        plant_list(&mut f, far);
19190        assert_eq!(
19191            f.run(&[b"BITOP", b"OR", b"out", src, far]),
19192            "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n"
19193        );
19194        assert_eq!(f.run(&[b"EXISTS", b"out"]), ":0\r\n");
19195    }
19196
19197    /// Every HyperLogLog command, on one stripe and on eight.
19198    ///
19199    /// Not under Miri, for the reason on
19200    /// `the_debug_forms_answer_four_different_shapes`, and twice over here
19201    /// because the script is run against both shapes of server.
19202    #[cfg_attr(miri, ignore = "sixteen thousand registers a command")]
19203    #[test]
19204    fn the_hyperloglog_group_answers_the_same_however_many_stripes_there_are() {
19205        let script: &[&[&[u8]]] = &[
19206            &[b"PFADD", b"h1", b"a", b"b", b"c"],
19207            &[b"PFADD", b"h1", b"a"],
19208            &[b"PFADD", b"h2"],
19209            &[b"PFADD", b"h2", b"c", b"d", b"e"],
19210            &[b"PFCOUNT", b"h1"],
19211            &[b"PFCOUNT", b"h2"],
19212            &[b"PFCOUNT", b"missing"],
19213            // The two that name more than one key.
19214            &[b"PFCOUNT", b"h1", b"h2"],
19215            &[b"PFCOUNT", b"h1", b"missing"],
19216            &[b"PFMERGE", b"m", b"h1", b"h2"],
19217            &[b"PFCOUNT", b"m"],
19218            &[b"STRLEN", b"m"],
19219            &[b"PFMERGE", b"m"],
19220            &[b"PFCOUNT", b"m"],
19221            &[b"PFMERGE", b"m2", b"missing"],
19222            &[b"PFCOUNT", b"m2"],
19223            // The debugging ones, which are single key and change what they
19224            // look at.
19225            &[b"PFDEBUG", b"ENCODING", b"h1"],
19226            &[b"PFDEBUG", b"DECODE", b"h1"],
19227            &[b"PFDEBUG", b"TODENSE", b"h1"],
19228            &[b"PFDEBUG", b"ENCODING", b"h1"],
19229            &[b"PFDEBUG", b"TODENSE", b"h1"],
19230            &[b"PFCOUNT", b"h1", b"h2"],
19231            &[b"PFSELFTEST"],
19232            // And the errors.
19233            &[b"SET", b"plain", b"not a sketch at all"],
19234            &[b"PFADD", b"plain", b"a"],
19235            &[b"PFCOUNT", b"plain"],
19236            &[b"PFCOUNT", b"h1", b"plain"],
19237            &[b"PFMERGE", b"plain", b"h1"],
19238            &[b"PFMERGE", b"m", b"plain"],
19239            &[b"PFDEBUG", b"ENCODING", b"gone"],
19240            &[b"PFDEBUG", b"NOPE", b"h1"],
19241        ];
19242
19243        let mut one = Fixture::new();
19244        let mut many = Fixture::striped(8);
19245        for parts in script {
19246            let a = one.run(parts);
19247            let b = many.run(parts);
19248            assert_eq!(a, b, "{}", String::from_utf8_lossy(parts[0]));
19249        }
19250    }
19251
19252    /// Every set command, on one stripe and on eight.
19253    ///
19254    /// The commands that answer members answer them in whatever order the set
19255    /// or the table they were built in holds them, so those replies are
19256    /// compared as sets. Everything else is compared byte for byte. Two servers
19257    /// agreeing on the order would be a fact about the tables and not about the
19258    /// answer, and asserting it would make this test fail for a reason nobody
19259    /// cares about.
19260    #[test]
19261    fn the_set_group_answers_the_same_however_many_stripes_there_are() {
19262        const UNORDERED: [&str; 4] = ["SMEMBERS", "SINTER", "SUNION", "SDIFF"];
19263        let script: &[&[&[u8]]] = &[
19264            &[b"SADD", b"s1", b"a", b"b", b"c"],
19265            &[b"SADD", b"s1", b"a"],
19266            &[b"SADD", b"s2", b"b", b"c", b"d"],
19267            &[b"SADD", b"ints", b"1", b"2", b"3"],
19268            &[b"SCARD", b"s1"],
19269            &[b"SISMEMBER", b"s1", b"a"],
19270            &[b"SISMEMBER", b"s1", b"z"],
19271            &[b"SMISMEMBER", b"s1", b"a", b"z", b"c"],
19272            &[b"SMEMBERS", b"s1"],
19273            &[b"SREM", b"s1", b"c"],
19274            &[b"SADD", b"s1", b"c"],
19275            &[b"SSCAN", b"s1", b"0"],
19276            &[b"SSCAN", b"s1", b"0", b"COUNT", b"100", b"MATCH", b"a*"],
19277            // The two draws, on a set of one member, which is the only shape
19278            // whose answer two servers have to agree on.
19279            &[b"SADD", b"one", b"m"],
19280            &[b"SRANDMEMBER", b"one"],
19281            &[b"SRANDMEMBER", b"one", b"-3"],
19282            &[b"SRANDMEMBER", b"gone"],
19283            &[b"SPOP", b"one"],
19284            &[b"SPOP", b"one"],
19285            &[b"SPOP", b"gone", b"2"],
19286            // The one that names two keys.
19287            &[b"SMOVE", b"s1", b"s2", b"a"],
19288            &[b"SMOVE", b"s1", b"s2", b"zzz"],
19289            &[b"SMOVE", b"gone", b"s2", b"a"],
19290            &[b"SMEMBERS", b"s1"],
19291            &[b"SMEMBERS", b"s2"],
19292            // The algebra.
19293            &[b"SINTER", b"s1", b"s2"],
19294            &[b"SUNION", b"s1", b"s2"],
19295            &[b"SDIFF", b"s2", b"s1"],
19296            &[b"SINTER", b"s1", b"gone"],
19297            &[b"SUNION", b"s1", b"gone"],
19298            &[b"SDIFF", b"gone", b"s1"],
19299            &[b"SINTER", b"ints", b"s1"],
19300            &[b"SINTERCARD", b"2", b"s1", b"s2"],
19301            &[b"SINTERCARD", b"2", b"s1", b"s2", b"LIMIT", b"1"],
19302            &[b"SUNIONCARD", b"2", b"s1", b"s2"],
19303            &[b"SDIFFCARD", b"2", b"s2", b"s1"],
19304            &[b"SINTERSTORE", b"d1", b"s1", b"s2"],
19305            &[b"SMEMBERS", b"d1"],
19306            &[b"SUNIONSTORE", b"d2", b"s1", b"s2"],
19307            &[b"SCARD", b"d2"],
19308            &[b"SDIFFSTORE", b"d3", b"s2", b"s1"],
19309            &[b"SCARD", b"d3"],
19310            // An empty result deletes the destination rather than storing a
19311            // set with nothing in it.
19312            &[b"SINTERSTORE", b"d4", b"s1", b"gone"],
19313            &[b"EXISTS", b"d4"],
19314            // And a destination that is also a source.
19315            &[b"SUNIONSTORE", b"s2", b"s1", b"s2"],
19316            &[b"SCARD", b"s2"],
19317            // The errors, which have to be the same errors.
19318            &[b"SET", b"str", b"v"],
19319            &[b"SADD", b"str", b"a"],
19320            &[b"SINTER", b"s1", b"str"],
19321            &[b"SINTERSTORE", b"d5", b"s1", b"str"],
19322            &[b"EXISTS", b"d5"],
19323            &[b"SMOVE", b"str", b"s2", b"a"],
19324            &[b"SMOVE", b"s1", b"str", b"b"],
19325            &[b"SMOVE", b"gone", b"str", b"b"],
19326            &[b"SINTERCARD", b"0", b"s1"],
19327            &[b"SINTERCARD", b"3", b"s1", b"s2"],
19328            &[b"SINTERCARD", b"2", b"s1", b"s2", b"LIMIT", b"-1"],
19329            &[b"SPOP", b"s1", b"-1"],
19330        ];
19331
19332        let mut one = Fixture::new();
19333        let mut many = Fixture::striped(8);
19334        for parts in script {
19335            let a = one.run(parts);
19336            let b = many.run(parts);
19337            let name = String::from_utf8_lossy(parts[0]).to_uppercase();
19338            if UNORDERED.contains(&name.as_str()) && a.starts_with(['*', '~']) {
19339                assert_eq!(sorted(&a), sorted(&b), "{name}");
19340            } else {
19341                assert_eq!(a, b, "{name}");
19342            }
19343        }
19344    }
19345
19346    /// The algebra over sets that are known to be on different stripes.
19347    #[test]
19348    fn a_set_operation_across_stripes_reads_every_set() {
19349        let mut f = Fixture::striped(8);
19350        let second = apart(&mut f, "s1");
19351        let third = apart(&mut f, &second);
19352        let (s1, s2, s3) = (b"s1".as_slice(), second.as_bytes(), third.as_bytes());
19353
19354        f.run(&[b"SADD", s1, b"a", b"b", b"c"]);
19355        f.run(&[b"SADD", s2, b"b", b"c", b"d"]);
19356        assert_eq!(sorted(&f.run(&[b"SINTER", s1, s2])), ["b", "c"]);
19357        assert_eq!(
19358            sorted(&f.run(&[b"SUNION", s1, s2])),
19359            ["a", "b", "c", "d"],
19360            "a union of two stripes is both of them"
19361        );
19362        assert_eq!(sorted(&f.run(&[b"SDIFF", s1, s2])), ["a"]);
19363        assert_eq!(f.run(&[b"SINTERCARD", b"2", s1, s2]), ":2\r\n");
19364        assert_eq!(f.run(&[b"SUNIONCARD", b"2", s1, s2]), ":4\r\n");
19365        assert_eq!(f.run(&[b"SDIFFCARD", b"2", s1, s2]), ":1\r\n");
19366
19367        // A destination on a third stripe, and then one that is also a source.
19368        assert_eq!(f.run(&[b"SINTERSTORE", s3, s1, s2]), ":2\r\n");
19369        assert_eq!(sorted(&f.run(&[b"SMEMBERS", s3])), ["b", "c"]);
19370        assert_eq!(f.run(&[b"SUNIONSTORE", s2, s1, s2]), ":4\r\n");
19371        assert_eq!(sorted(&f.run(&[b"SMEMBERS", s2])), ["a", "b", "c", "d"]);
19372        assert_eq!(f.run(&[b"SDIFFSTORE", s3, s2, s1]), ":1\r\n");
19373        assert_eq!(sorted(&f.run(&[b"SMEMBERS", s3])), ["d"]);
19374
19375        // An empty result deletes a destination wherever it is, and a key of
19376        // the wrong type stops the command before the destination is touched.
19377        assert_eq!(f.run(&[b"SINTERSTORE", s3, s1, b"gone"]), ":0\r\n");
19378        assert_eq!(f.run(&[b"EXISTS", s3]), ":0\r\n");
19379        f.run(&[b"SET", s3, b"v"]);
19380        assert_eq!(
19381            f.run(&[b"SINTER", s1, s3]),
19382            "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n"
19383        );
19384        assert_eq!(f.run(&[b"GET", s3]), "$1\r\nv\r\n", "and left it alone");
19385    }
19386
19387    /// An `SMOVE` whose two keys are on two stripes.
19388    #[test]
19389    fn a_move_across_stripes_takes_the_member_with_it() {
19390        let mut f = Fixture::striped(8);
19391        let other = apart(&mut f, "src");
19392        let (src, dst) = (b"src".as_slice(), other.as_bytes());
19393
19394        f.run(&[b"SADD", src, b"a", b"b"]);
19395        f.run(&[b"SADD", dst, b"c"]);
19396        assert_eq!(f.run(&[b"SMOVE", src, dst, b"a"]), ":1\r\n");
19397        assert_eq!(sorted(&f.run(&[b"SMEMBERS", src])), ["b"]);
19398        assert_eq!(sorted(&f.run(&[b"SMEMBERS", dst])), ["a", "c"]);
19399        assert_eq!(f.run(&[b"SMOVE", src, dst, b"a"]), ":0\r\n", "it has gone");
19400
19401        // A destination that is not there is created on its own stripe, and a
19402        // source that loses its last member is deleted from its own.
19403        f.run(&[b"DEL", dst]);
19404        assert_eq!(f.run(&[b"SMOVE", src, dst, b"b"]), ":1\r\n");
19405        assert_eq!(f.run(&[b"EXISTS", src]), ":0\r\n", "the source is empty");
19406        assert_eq!(sorted(&f.run(&[b"SMEMBERS", dst])), ["b"]);
19407
19408        // And a source that is not there answers zero without ever asking what
19409        // the destination holds, which is Redis's order and not the obvious
19410        // one.
19411        f.run(&[b"SET", dst, b"v"]);
19412        assert_eq!(f.run(&[b"SMOVE", src, dst, b"b"]), ":0\r\n");
19413        f.run(&[b"SADD", src, b"b"]);
19414        assert_eq!(
19415            f.run(&[b"SMOVE", src, dst, b"b"]),
19416            "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n"
19417        );
19418    }
19419
19420    /// A count and a merge over sketches that are known to be on two stripes.
19421    #[test]
19422    fn a_pfcount_and_a_pfmerge_reach_across_stripes() {
19423        let mut f = Fixture::striped(8);
19424        let other = apart(&mut f, "src");
19425        let (src, far) = (b"src".as_slice(), other.as_bytes());
19426
19427        for i in 0..150 {
19428            let ele = format!("e:{i}");
19429            f.run(&[b"PFADD", src, ele.as_bytes()]);
19430        }
19431        for i in 150..200 {
19432            let ele = format!("e:{i}");
19433            f.run(&[b"PFADD", far, ele.as_bytes()]);
19434        }
19435        // The three numbers a real server gives for these elements, which are
19436        // the numbers the single stripe tests in the keyspace crate check too.
19437        assert_eq!(f.run(&[b"PFCOUNT", src]), ":151\r\n");
19438        assert_eq!(f.run(&[b"PFCOUNT", far]), ":49\r\n");
19439        assert_eq!(f.run(&[b"PFCOUNT", src, far]), ":199\r\n");
19440
19441        // A merge whose destination is on a third stripe, and then one that
19442        // writes into a source.
19443        let dest = apart(&mut f, &other);
19444        assert_eq!(f.run(&[b"PFMERGE", dest.as_bytes(), src, far]), "+OK\r\n");
19445        assert_eq!(f.run(&[b"PFCOUNT", dest.as_bytes()]), ":199\r\n");
19446        assert_eq!(f.run(&[b"PFMERGE", far, src]), "+OK\r\n");
19447        assert_eq!(f.run(&[b"PFCOUNT", far]), ":199\r\n", "and kept its own");
19448        assert_eq!(f.run(&[b"PFCOUNT", src]), ":151\r\n", "and left the source");
19449    }
19450
19451    /// Every sorted set command, on one stripe and on eight.
19452    ///
19453    /// Every reply here is compared byte for byte, unlike the set group, because
19454    /// a sorted set answers in rank order and members sharing a score come out
19455    /// in the order of their bytes. There is nothing left for the table the
19456    /// answer was built in to decide.
19457    #[test]
19458    fn the_sorted_set_group_answers_the_same_however_many_stripes_there_are() {
19459        let script: &[&[&[u8]]] = &[
19460            &[b"ZADD", b"z1", b"1", b"a", b"2", b"b", b"3", b"c"],
19461            &[b"ZADD", b"z1", b"NX", b"9", b"a"],
19462            &[b"ZADD", b"z1", b"XX", b"CH", b"5", b"a"],
19463            &[b"ZADD", b"z1", b"GT", b"CH", b"1", b"a"],
19464            &[b"ZADD", b"z1", b"INCR", b"2", b"a"],
19465            &[b"ZINCRBY", b"z1", b"1.5", b"b"],
19466            &[b"ZADD", b"z2", b"1", b"b", b"2", b"c", b"3", b"d"],
19467            &[b"ZADD", b"lex", b"0", b"a", b"0", b"b", b"0", b"c"],
19468            &[b"ZADD", b"one", b"1", b"m"],
19469            &[b"ZCARD", b"z1"],
19470            &[b"ZCARD", b"gone"],
19471            &[b"ZSCORE", b"z1", b"a"],
19472            &[b"ZSCORE", b"z1", b"zz"],
19473            &[b"ZMSCORE", b"z1", b"a", b"zz", b"c"],
19474            &[b"ZRANK", b"z1", b"c"],
19475            &[b"ZRANK", b"z1", b"c", b"WITHSCORE"],
19476            &[b"ZREVRANK", b"z1", b"c"],
19477            &[b"ZRANK", b"z1", b"gone"],
19478            &[b"ZCOUNT", b"z1", b"-inf", b"+inf"],
19479            &[b"ZCOUNT", b"z1", b"(1", b"3"],
19480            &[b"ZLEXCOUNT", b"lex", b"-", b"+"],
19481            // The range commands, which are one parse and one walk.
19482            &[b"ZRANGE", b"z1", b"0", b"-1"],
19483            &[b"ZRANGE", b"z1", b"0", b"-1", b"WITHSCORES"],
19484            &[b"ZRANGE", b"z1", b"1", b"9", b"BYSCORE"],
19485            &[b"ZRANGE", b"z1", b"9", b"1", b"BYSCORE", b"REV"],
19486            &[b"ZRANGE", b"lex", b"[a", b"(c", b"BYLEX"],
19487            &[b"ZREVRANGE", b"z1", b"0", b"-1"],
19488            &[
19489                b"ZRANGEBYSCORE",
19490                b"z1",
19491                b"-inf",
19492                b"+inf",
19493                b"LIMIT",
19494                b"1",
19495                b"1",
19496            ],
19497            &[b"ZREVRANGEBYLEX", b"lex", b"+", b"-"],
19498            &[b"ZSCAN", b"z1", b"0"],
19499            &[b"ZSCAN", b"z1", b"0", b"MATCH", b"a*", b"COUNT", b"100"],
19500            // The draw, on a sorted set of one member, which is the only shape
19501            // whose answer two servers have to agree on.
19502            &[b"ZRANDMEMBER", b"one"],
19503            &[b"ZRANDMEMBER", b"one", b"-3", b"WITHSCORES"],
19504            &[b"ZRANDMEMBER", b"gone"],
19505            // The one that copies a window into another key.
19506            &[b"ZRANGESTORE", b"d0", b"z1", b"0", b"1"],
19507            &[b"ZRANGE", b"d0", b"0", b"-1", b"WITHSCORES"],
19508            &[b"ZRANGESTORE", b"d0", b"z1", b"5", b"1"],
19509            &[b"EXISTS", b"d0"],
19510            // The algebra, in both its shapes.
19511            &[b"ZUNION", b"2", b"z1", b"z2"],
19512            &[b"ZUNION", b"2", b"z1", b"z2", b"WITHSCORES"],
19513            &[
19514                b"ZUNION",
19515                b"2",
19516                b"z1",
19517                b"z2",
19518                b"WEIGHTS",
19519                b"2",
19520                b"3",
19521                b"AGGREGATE",
19522                b"MAX",
19523                b"WITHSCORES",
19524            ],
19525            &[b"ZINTER", b"2", b"z1", b"z2", b"WITHSCORES"],
19526            &[b"ZDIFF", b"2", b"z1", b"z2", b"WITHSCORES"],
19527            &[b"ZDIFF", b"2", b"gone", b"z1"],
19528            &[b"ZINTERCARD", b"2", b"z1", b"z2"],
19529            &[b"ZINTERCARD", b"2", b"z1", b"z2", b"LIMIT", b"1"],
19530            &[b"ZUNIONSTORE", b"d1", b"2", b"z1", b"z2"],
19531            &[b"ZRANGE", b"d1", b"0", b"-1", b"WITHSCORES"],
19532            &[
19533                b"ZINTERSTORE",
19534                b"d2",
19535                b"2",
19536                b"z1",
19537                b"z2",
19538                b"AGGREGATE",
19539                b"MIN",
19540            ],
19541            &[b"ZRANGE", b"d2", b"0", b"-1", b"WITHSCORES"],
19542            &[b"ZDIFFSTORE", b"d3", b"2", b"z1", b"z2"],
19543            &[b"ZCARD", b"d3"],
19544            // An empty result deletes the destination rather than storing a
19545            // sorted set with nothing in it.
19546            &[b"ZINTERSTORE", b"d4", b"2", b"z1", b"gone"],
19547            &[b"EXISTS", b"d4"],
19548            // A plain set is a sorted set where every score is one, so it is a
19549            // legal input to all of these.
19550            &[b"SADD", b"plain", b"a", b"x"],
19551            &[b"ZUNIONSTORE", b"d5", b"2", b"z1", b"plain"],
19552            &[b"ZRANGE", b"d5", b"0", b"-1", b"WITHSCORES"],
19553            // And a destination that is also a source.
19554            &[b"ZUNIONSTORE", b"z2", b"2", b"z1", b"z2"],
19555            &[b"ZRANGE", b"z2", b"0", b"-1", b"WITHSCORES"],
19556            // The three removals and the two pops.
19557            &[b"ZREM", b"d5", b"x", b"nothere"],
19558            &[b"ZREMRANGEBYRANK", b"d5", b"0", b"0"],
19559            &[b"ZREMRANGEBYSCORE", b"d1", b"-inf", b"1"],
19560            &[b"ZREMRANGEBYLEX", b"lex", b"[a", b"[a"],
19561            &[b"ZPOPMIN", b"z1"],
19562            &[b"ZPOPMAX", b"z1", b"2"],
19563            &[b"ZPOPMIN", b"gone"],
19564            &[b"ZMPOP", b"2", b"gone", b"z2", b"MIN"],
19565            &[b"ZMPOP", b"2", b"gone", b"nothere", b"MAX", b"COUNT", b"2"],
19566            // The errors, which have to be the same errors.
19567            &[b"SET", b"str", b"v"],
19568            &[b"ZADD", b"str", b"1", b"a"],
19569            &[b"ZSCORE", b"str", b"a"],
19570            &[b"ZADD", b"z1", b"nan", b"a"],
19571            &[b"ZUNION", b"2", b"z1", b"str"],
19572            &[b"ZUNIONSTORE", b"d6", b"2", b"z1", b"str"],
19573            &[b"EXISTS", b"d6"],
19574            &[b"ZINTERCARD", b"0", b"z1"],
19575            &[b"ZINTERCARD", b"2", b"z1", b"z2", b"LIMIT", b"-1"],
19576            &[b"ZRANGESTORE", b"d7", b"str", b"0", b"-1"],
19577            &[b"ZMPOP", b"1", b"str", b"MIN"],
19578            &[b"ZPOPMIN", b"z1", b"-1"],
19579        ];
19580
19581        let mut one = Fixture::new();
19582        let mut many = Fixture::striped(8);
19583        for parts in script {
19584            let a = one.run(parts);
19585            let b = many.run(parts);
19586            assert_eq!(a, b, "{}", String::from_utf8_lossy(parts[0]));
19587        }
19588    }
19589
19590    /// The algebra over sorted sets that are known to be on different stripes.
19591    #[test]
19592    fn a_sorted_set_operation_across_stripes_reads_every_input() {
19593        let mut f = Fixture::striped(8);
19594        let second = apart(&mut f, "z1");
19595        let third = apart(&mut f, &second);
19596        let (z1, z2, z3) = (b"z1".as_slice(), second.as_bytes(), third.as_bytes());
19597
19598        f.run(&[b"ZADD", z1, b"1", b"a", b"2", b"b"]);
19599        f.run(&[b"ZADD", z2, b"3", b"b", b"4", b"c"]);
19600        // a is 1, c is 4, b is 2 and 3 added together, which is the order they
19601        // come out in and the answer that says both stripes were read.
19602        assert_eq!(
19603            f.run(&[b"ZUNION", b"2", z1, z2]),
19604            "*3\r\n$1\r\na\r\n$1\r\nc\r\n$1\r\nb\r\n"
19605        );
19606        assert_eq!(f.run(&[b"ZINTER", b"2", z1, z2]), "*1\r\n$1\r\nb\r\n");
19607        assert_eq!(f.run(&[b"ZDIFF", b"2", z1, z2]), "*1\r\n$1\r\na\r\n");
19608        assert_eq!(f.run(&[b"ZINTERCARD", b"2", z1, z2]), ":1\r\n");
19609        assert_eq!(
19610            f.run(&[b"ZINTERCARD", b"2", z1, z2, b"LIMIT", b"1"]),
19611            ":1\r\n"
19612        );
19613
19614        // A destination on a third stripe, and the weights and the aggregate
19615        // reaching every input.
19616        assert_eq!(f.run(&[b"ZUNIONSTORE", z3, b"2", z1, z2]), ":3\r\n");
19617        assert_eq!(f.run(&[b"ZSCORE", z3, b"b"]), "$1\r\n5\r\n");
19618        assert_eq!(
19619            f.run(&[
19620                b"ZUNIONSTORE",
19621                z3,
19622                b"2",
19623                z1,
19624                z2,
19625                b"WEIGHTS",
19626                b"2",
19627                b"3",
19628                b"AGGREGATE",
19629                b"MAX"
19630            ]),
19631            ":3\r\n"
19632        );
19633        assert_eq!(f.run(&[b"ZSCORE", z3, b"b"]), "$1\r\n9\r\n");
19634        assert_eq!(f.run(&[b"ZINTERSTORE", z3, b"2", z1, z2]), ":1\r\n");
19635        assert_eq!(f.run(&[b"ZCARD", z3]), ":1\r\n");
19636        assert_eq!(f.run(&[b"ZDIFFSTORE", z3, b"2", z2, z1]), ":1\r\n");
19637        assert_eq!(f.run(&[b"ZSCORE", z3, b"c"]), "$1\r\n4\r\n");
19638
19639        // A pop over keys on several stripes takes from the first one that has
19640        // anything, which is what makes the order of the keys matter.
19641        let popped = format!(
19642            "*2\r\n${}\r\n{second}\r\n*1\r\n*2\r\n$1\r\nb\r\n$1\r\n3\r\n",
19643            second.len()
19644        );
19645        assert_eq!(f.run(&[b"ZMPOP", b"3", b"gone", z2, z1, b"MIN"]), popped);
19646        f.run(&[b"ZADD", z2, b"3", b"b"]);
19647
19648        // An empty result deletes a destination wherever it is, and an input of
19649        // the wrong type stops the command before the destination is touched.
19650        assert_eq!(f.run(&[b"ZINTERSTORE", z3, b"2", z1, b"gone"]), ":0\r\n");
19651        assert_eq!(f.run(&[b"EXISTS", z3]), ":0\r\n");
19652        f.run(&[b"SET", z3, b"v"]);
19653        assert_eq!(
19654            f.run(&[b"ZUNION", b"2", z1, z3]),
19655            "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n"
19656        );
19657        assert_eq!(f.run(&[b"GET", z3]), "$1\r\nv\r\n", "and left it alone");
19658
19659        // And a destination that is also a source works across stripes for the
19660        // reason it works on one: the whole result is built before anything is
19661        // written.
19662        assert_eq!(f.run(&[b"ZUNIONSTORE", z2, b"2", z1, z2]), ":3\r\n");
19663        assert_eq!(f.run(&[b"ZSCORE", z2, b"b"]), "$1\r\n5\r\n");
19664        assert_eq!(f.run(&[b"ZCARD", z2]), ":3\r\n");
19665    }
19666
19667    /// A `ZRANGESTORE` whose two keys are on two stripes.
19668    #[test]
19669    fn a_range_store_across_stripes_copies_the_window() {
19670        let mut f = Fixture::striped(8);
19671        let other = apart(&mut f, "src");
19672        let third = apart(&mut f, &other);
19673        let (src, dst, plain) = (b"src".as_slice(), other.as_bytes(), third.as_bytes());
19674
19675        f.run(&[b"ZADD", src, b"1", b"a", b"2", b"b", b"3", b"c"]);
19676        assert_eq!(f.run(&[b"ZRANGESTORE", dst, src, b"0", b"1"]), ":2\r\n");
19677        assert_eq!(
19678            f.run(&[b"ZRANGE", dst, b"0", b"-1", b"WITHSCORES"]),
19679            "*4\r\n$1\r\na\r\n$1\r\n1\r\n$1\r\nb\r\n$1\r\n2\r\n"
19680        );
19681        assert_eq!(f.run(&[b"ZCARD", src]), ":3\r\n", "the source kept its own");
19682
19683        // A window walked backwards takes the other end of the sorted set and
19684        // still stores what it took in score order.
19685        assert_eq!(
19686            f.run(&[
19687                b"ZRANGESTORE",
19688                dst,
19689                src,
19690                b"+inf",
19691                b"-inf",
19692                b"BYSCORE",
19693                b"REV",
19694                b"LIMIT",
19695                b"0",
19696                b"2"
19697            ]),
19698            ":2\r\n"
19699        );
19700        assert_eq!(
19701            f.run(&[b"ZRANGE", dst, b"0", b"-1"]),
19702            "*2\r\n$1\r\nb\r\n$1\r\nc\r\n"
19703        );
19704
19705        // An empty window deletes the destination on its own stripe, and a
19706        // source of the wrong type is refused before the destination is touched.
19707        assert_eq!(f.run(&[b"ZRANGESTORE", dst, src, b"5", b"1"]), ":0\r\n");
19708        assert_eq!(f.run(&[b"EXISTS", dst]), ":0\r\n");
19709        f.run(&[b"ZRANGESTORE", dst, src, b"0", b"-1"]);
19710        f.run(&[b"SET", plain, b"v"]);
19711        assert_eq!(
19712            f.run(&[b"ZRANGESTORE", dst, plain, b"0", b"-1"]),
19713            "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n"
19714        );
19715        assert_eq!(
19716            f.run(&[b"ZCARD", dst]),
19717            ":3\r\n",
19718            "and left the destination"
19719        );
19720    }
19721
19722    /// Every list command, on one stripe and on eight.
19723    ///
19724    /// The blocking six are in here too, both when they can be answered on the
19725    /// spot and when they cannot, since a command that parks its client writes
19726    /// nothing at all and two servers have to agree about that as much as they
19727    /// agree about a reply.
19728    #[test]
19729    fn the_list_group_answers_the_same_however_many_stripes_there_are() {
19730        let script: &[&[&[u8]]] = &[
19731            &[b"RPUSH", b"l1", b"a", b"b", b"c"],
19732            &[b"LPUSH", b"l1", b"z"],
19733            &[b"RPUSHX", b"l1", b"d"],
19734            &[b"LPUSHX", b"gone", b"x"],
19735            &[b"RPUSHX", b"gone", b"x"],
19736            &[b"LLEN", b"l1"],
19737            &[b"LLEN", b"gone"],
19738            &[b"LRANGE", b"l1", b"0", b"-1"],
19739            &[b"LRANGE", b"l1", b"1", b"2"],
19740            &[b"LRANGE", b"l1", b"5", b"9"],
19741            &[b"LINDEX", b"l1", b"0"],
19742            &[b"LINDEX", b"l1", b"-1"],
19743            &[b"LINDEX", b"l1", b"99"],
19744            &[b"LSET", b"l1", b"0", b"y"],
19745            &[b"LINSERT", b"l1", b"BEFORE", b"b", b"aa"],
19746            &[b"LINSERT", b"l1", b"AFTER", b"nothere", b"x"],
19747            &[b"LPOS", b"l1", b"b"],
19748            &[b"LPOS", b"l1", b"b", b"COUNT", b"0"],
19749            &[b"LPOS", b"l1", b"nothere"],
19750            &[b"LPOS", b"l1", b"b", b"RANK", b"-1", b"MAXLEN", b"2"],
19751            &[b"LREM", b"l1", b"1", b"aa"],
19752            &[b"LTRIM", b"l1", b"0", b"3"],
19753            &[b"LRANGE", b"l1", b"0", b"-1"],
19754            &[b"LPOP", b"l1"],
19755            &[b"RPOP", b"l1"],
19756            &[b"LPOP", b"l1", b"2"],
19757            &[b"LPOP", b"gone"],
19758            &[b"LPOP", b"gone", b"2"],
19759            &[b"EXISTS", b"l1"],
19760            // The ones that name two keys, and the one that takes a block of
19761            // elements rather than the one on the end.
19762            &[b"RPUSH", b"src", b"a", b"b", b"c", b"d"],
19763            &[b"LMOVE", b"src", b"dst", b"LEFT", b"RIGHT"],
19764            &[b"RPOPLPUSH", b"src", b"dst"],
19765            &[b"LRANGE", b"dst", b"0", b"-1"],
19766            &[b"LMOVE", b"gone", b"dst", b"LEFT", b"RIGHT"],
19767            &[b"LMOVEM", b"src", b"dst", b"LEFT", b"RIGHT"],
19768            &[
19769                b"LMOVEM", b"src", b"dst", b"LEFT", b"RIGHT", b"COUNT", b"2", b"BULK",
19770            ],
19771            &[
19772                b"LMOVEM", b"dst", b"dst", b"LEFT", b"RIGHT", b"COUNT", b"2", b"OBO",
19773            ],
19774            &[b"LRANGE", b"dst", b"0", b"-1"],
19775            &[
19776                b"LMOVEM", b"src", b"dst", b"LEFT", b"RIGHT", b"EXACTLY", b"9", b"BULK",
19777            ],
19778            &[b"LMPOP", b"2", b"gone", b"dst", b"LEFT"],
19779            &[b"LMPOP", b"2", b"gone", b"dst", b"RIGHT", b"COUNT", b"2"],
19780            &[b"LMPOP", b"1", b"gone", b"LEFT"],
19781            // The blocking ones, first with something there to answer them and
19782            // then with nothing, which parks the client and writes nothing.
19783            &[b"RPUSH", b"q", b"a", b"b", b"c"],
19784            &[b"BLPOP", b"gone", b"q", b"0"],
19785            &[b"BRPOP", b"q", b"0"],
19786            &[b"BLMPOP", b"0", b"2", b"gone", b"q", b"LEFT"],
19787            &[b"RPUSH", b"q", b"x", b"y", b"z"],
19788            &[b"BLMOVE", b"q", b"dst", b"LEFT", b"RIGHT", b"0"],
19789            &[b"BRPOPLPUSH", b"q", b"dst", b"0"],
19790            &[b"BLMOVEM", b"q", b"dst", b"LEFT", b"RIGHT", b"0"],
19791            &[b"BLPOP", b"q", b"0"],
19792            &[b"BLMOVE", b"q", b"dst", b"LEFT", b"RIGHT", b"0"],
19793            // The errors, which have to be the same errors.
19794            &[b"SET", b"plain", b"v"],
19795            &[b"LPUSH", b"plain", b"a"],
19796            &[b"LLEN", b"plain"],
19797            &[b"LMOVE", b"dst", b"plain", b"LEFT", b"RIGHT"],
19798            &[b"LRANGE", b"dst", b"0", b"-1"],
19799            &[b"LMOVEM", b"dst", b"plain", b"LEFT", b"RIGHT"],
19800            &[b"LSET", b"gone", b"0", b"v"],
19801            &[b"LSET", b"dst", b"99", b"v"],
19802            &[b"LPOP", b"dst", b"-1"],
19803            &[b"LMPOP", b"0", b"dst", b"LEFT"],
19804            &[b"LPOS", b"dst", b"a", b"RANK", b"0"],
19805        ];
19806
19807        let mut one = Fixture::new();
19808        let mut many = Fixture::striped(8);
19809        for parts in script {
19810            let a = one.run(parts);
19811            let b = many.run(parts);
19812            assert_eq!(a, b, "{}", String::from_utf8_lossy(parts[0]));
19813        }
19814    }
19815
19816    /// An `LMOVE` and an `LMOVEM` whose two keys are on two stripes.
19817    #[test]
19818    fn a_list_move_across_stripes_takes_the_elements_with_it() {
19819        let mut f = Fixture::striped(8);
19820        let other = apart(&mut f, "src");
19821        let third = apart(&mut f, &other);
19822        let (src, dst, plain) = (b"src".as_slice(), other.as_bytes(), third.as_bytes());
19823
19824        f.run(&[b"RPUSH", src, b"a", b"b", b"c", b"d"]);
19825        assert_eq!(
19826            f.run(&[b"LMOVE", src, dst, b"LEFT", b"RIGHT"]),
19827            "$1\r\na\r\n"
19828        );
19829        assert_eq!(f.run(&[b"RPOPLPUSH", src, dst]), "$1\r\nd\r\n");
19830        assert_eq!(
19831            f.run(&[b"LRANGE", dst, b"0", b"-1"]),
19832            "*2\r\n$1\r\nd\r\n$1\r\na\r\n",
19833            "one went on each end of the destination"
19834        );
19835        assert_eq!(
19836            f.run(&[b"LRANGE", src, b"0", b"-1"]),
19837            "*2\r\n$1\r\nb\r\n$1\r\nc\r\n"
19838        );
19839
19840        // A block of them, which under BULK arrives in the order it left.
19841        assert_eq!(
19842            f.run(&[
19843                b"LMOVEM", src, dst, b"LEFT", b"RIGHT", b"COUNT", b"2", b"BULK"
19844            ]),
19845            "*2\r\n$1\r\nb\r\n$1\r\nc\r\n"
19846        );
19847        assert_eq!(
19848            f.run(&[b"LRANGE", dst, b"0", b"-1"]),
19849            "*4\r\n$1\r\nd\r\n$1\r\na\r\n$1\r\nb\r\n$1\r\nc\r\n"
19850        );
19851        assert_eq!(
19852            f.run(&[b"EXISTS", src]),
19853            ":0\r\n",
19854            "and the source is gone with its last element"
19855        );
19856
19857        // An `EXACTLY` the source cannot fill moves nothing, and a source that
19858        // is not there at all is the two kinds of nothing the two commands have.
19859        f.run(&[b"RPUSH", src, b"e", b"f"]);
19860        assert_eq!(
19861            f.run(&[
19862                b"LMOVEM", src, dst, b"LEFT", b"RIGHT", b"EXACTLY", b"3", b"BULK"
19863            ]),
19864            "*-1\r\n"
19865        );
19866        assert_eq!(f.run(&[b"LLEN", src]), ":2\r\n", "and took none of them");
19867        assert_eq!(
19868            f.run(&[b"LMOVE", b"gone", dst, b"LEFT", b"RIGHT"]),
19869            "$-1\r\n"
19870        );
19871        assert_eq!(
19872            f.run(&[b"LMOVEM", b"gone", dst, b"LEFT", b"RIGHT"]),
19873            "*-1\r\n"
19874        );
19875
19876        // A destination of the wrong type is refused before anything is taken,
19877        // which is the order that matters most here, since an element already
19878        // out of the source would have nowhere to go back to.
19879        f.run(&[b"SET", plain, b"v"]);
19880        assert_eq!(
19881            f.run(&[b"LMOVE", src, plain, b"LEFT", b"RIGHT"]),
19882            "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n"
19883        );
19884        assert_eq!(
19885            f.run(&[b"LLEN", src]),
19886            ":2\r\n",
19887            "and left the source alone"
19888        );
19889        assert_eq!(
19890            f.run(&[b"LMOVEM", src, plain, b"LEFT", b"RIGHT"]),
19891            "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n"
19892        );
19893        assert_eq!(f.run(&[b"LLEN", src]), ":2\r\n");
19894    }
19895
19896    /// A parked client served by a push that landed on another stripe.
19897    ///
19898    /// A waiter remembers the database and not the stripe, which is the point:
19899    /// serving it runs the same attempt the command ran, and the attempt finds
19900    /// the stripe each of its keys is on for itself.
19901    #[test]
19902    fn a_parked_client_is_served_from_the_stripe_its_key_is_on() {
19903        let mut f = Fixture::striped(8);
19904        let other = apart(&mut f, "q");
19905        let (q, far) = (b"q".as_slice(), other.as_bytes());
19906
19907        assert_eq!(f.flow(&[b"BLPOP", q, far, b"0"]).0, Flow::Block);
19908        assert_eq!(f.server.parked(), 1);
19909        f.run(&[b"RPUSH", far, b"v"]);
19910        let mut out = Out::new(Proto::Resp2);
19911        assert!(f.server.serve_waiter(7, 0, &mut out));
19912        let want = format!("*2\r\n${}\r\n{other}\r\n$1\r\nv\r\n", other.len());
19913        assert_eq!(core::str::from_utf8(out.as_slice()).expect("ascii"), want);
19914        assert_eq!(
19915            f.run(&[b"EXISTS", far]),
19916            ":0\r\n",
19917            "and it took the element with it"
19918        );
19919
19920        // And a move across two stripes is served the same way, by the push
19921        // that fills its source.
19922        f.server.forget_waiters(7);
19923        assert_eq!(
19924            f.flow(&[b"BLMOVE", q, far, b"LEFT", b"RIGHT", b"0"]).0,
19925            Flow::Block
19926        );
19927        f.run(&[b"RPUSH", q, b"w"]);
19928        let mut out = Out::new(Proto::Resp2);
19929        assert!(f.server.serve_waiter(7, 0, &mut out));
19930        assert_eq!(
19931            core::str::from_utf8(out.as_slice()).expect("ascii"),
19932            "$1\r\nw\r\n"
19933        );
19934        assert_eq!(f.run(&[b"LRANGE", far, b"0", b"-1"]), "*1\r\n$1\r\nw\r\n");
19935    }
19936
19937    /// Every stream command, on one stripe and on eight.
19938    ///
19939    /// Every ID is written out rather than left to the clock, so the two servers
19940    /// are being compared on what they store and not on how long the test took
19941    /// to get from one of them to the other.
19942    #[test]
19943    fn the_stream_group_answers_the_same_however_many_stripes_there_are() {
19944        let script: &[&[&[u8]]] = &[
19945            &[b"XADD", b"s", b"1-1", b"a", b"1"],
19946            &[b"XADD", b"s", b"2-1", b"b", b"2", b"c", b"3"],
19947            &[b"XADD", b"s", b"3-1", b"d", b"4"],
19948            &[b"XADD", b"s", b"1-1", b"e", b"5"],
19949            &[b"XADD", b"nomk", b"NOMKSTREAM", b"1-1", b"a", b"1"],
19950            &[b"XLEN", b"s"],
19951            &[b"XLEN", b"gone"],
19952            &[b"XRANGE", b"s", b"-", b"+"],
19953            &[b"XRANGE", b"s", b"2", b"+", b"COUNT", b"1"],
19954            &[b"XRANGE", b"gone", b"-", b"+", b"COUNT", b"0"],
19955            &[b"XRANGE", b"s", b"-", b"+", b"COUNT", b"0"],
19956            &[b"XREVRANGE", b"s", b"+", b"-"],
19957            &[b"XREAD", b"COUNT", b"2", b"STREAMS", b"s", b"0"],
19958            &[b"XREAD", b"STREAMS", b"s", b"gone", b"0", b"0"],
19959            &[b"XREAD", b"STREAMS", b"s", b"$"],
19960            // The groups, which is where most of the state is.
19961            &[b"XGROUP", b"CREATE", b"s", b"g", b"0"],
19962            &[b"XGROUP", b"CREATE", b"s", b"g", b"0"],
19963            &[b"XGROUP", b"CREATE", b"gone", b"g", b"0"],
19964            &[b"XGROUP", b"CREATE", b"made", b"g", b"$", b"MKSTREAM"],
19965            &[b"XGROUP", b"CREATECONSUMER", b"s", b"g", b"idle"],
19966            &[b"XREADGROUP", b"GROUP", b"g", b"c1", b"STREAMS", b"s", b">"],
19967            &[
19968                b"XREADGROUP",
19969                b"GROUP",
19970                b"g",
19971                b"c1",
19972                b"COUNT",
19973                b"1",
19974                b"STREAMS",
19975                b"s",
19976                b"0",
19977            ],
19978            &[
19979                b"XREADGROUP",
19980                b"GROUP",
19981                b"nope",
19982                b"c1",
19983                b"STREAMS",
19984                b"s",
19985                b">",
19986            ],
19987            &[b"XPENDING", b"s", b"g"],
19988            &[b"XPENDING", b"s", b"g", b"-", b"+", b"10"],
19989            &[b"XPENDING", b"s", b"g", b"-", b"+", b"10", b"c1"],
19990            &[b"XPENDING", b"s", b"nope"],
19991            &[b"XCLAIM", b"s", b"g", b"c2", b"0", b"1-1"],
19992            &[b"XCLAIM", b"s", b"g", b"c2", b"0", b"2-1", b"JUSTID"],
19993            &[b"XAUTOCLAIM", b"s", b"g", b"c3", b"0", b"0"],
19994            &[b"XACK", b"s", b"g", b"1-1"],
19995            &[b"XACK", b"s", b"g", b"1-1"],
19996            &[b"XNACK", b"s", b"g", b"FAIL", b"IDS", b"1", b"2-1"],
19997            &[b"XPENDING", b"s", b"g"],
19998            &[b"XINFO", b"STREAM", b"s"],
19999            &[b"XINFO", b"GROUPS", b"s"],
20000            &[b"XINFO", b"CONSUMERS", b"s", b"g"],
20001            &[b"XINFO", b"STREAM", b"gone"],
20002            // Deleting, trimming and moving the ID on.
20003            &[b"XDEL", b"s", b"3-1"],
20004            &[b"XDELEX", b"s", b"DELREF", b"IDS", b"1", b"2-1"],
20005            &[b"XACKDEL", b"s", b"g", b"KEEPREF", b"IDS", b"1", b"1-1"],
20006            &[b"XADD", b"s", b"9-1", b"z", b"9"],
20007            &[b"XTRIM", b"s", b"MAXLEN", b"1"],
20008            &[b"XTRIM", b"s", b"MINID", b"9"],
20009            &[b"XSETID", b"s", b"99-1"],
20010            &[b"XSETID", b"s", b"1-1"],
20011            &[b"XLEN", b"s"],
20012            &[b"XGROUP", b"SETID", b"s", b"g", b"0"],
20013            &[b"XGROUP", b"DELCONSUMER", b"s", b"g", b"c1"],
20014            &[b"XGROUP", b"DESTROY", b"s", b"g"],
20015            &[b"XGROUP", b"DESTROY", b"s", b"g"],
20016            // And the errors.
20017            &[b"SET", b"plain", b"v"],
20018            &[b"XADD", b"plain", b"1-1", b"a", b"1"],
20019            &[b"XLEN", b"plain"],
20020            &[b"XREAD", b"STREAMS", b"plain", b"0"],
20021            &[b"XRANGE", b"s", b"bogus", b"+"],
20022            &[b"XADD", b"s", b"1-1", b"a"],
20023            &[b"XREAD", b"STREAMS", b"s", b"gone", b"0"],
20024            &[b"XREADGROUP", b"GROUP", b"g", b"c", b"STREAMS", b"s", b"$"],
20025        ];
20026
20027        let mut one = Fixture::new();
20028        let mut many = Fixture::striped(8);
20029        for parts in script {
20030            let a = one.run(parts);
20031            let b = many.run(parts);
20032            assert_eq!(a, b, "{}", String::from_utf8_lossy(parts[0]));
20033        }
20034    }
20035
20036    /// An `XREAD` and an `XREADGROUP` naming two keys on two stripes.
20037    ///
20038    /// Nothing is shared between the two streams, so the only thing this can go
20039    /// wrong at is looking both of them up, which is exactly what a read that
20040    /// held one database and walked it would get wrong.
20041    #[test]
20042    fn a_stream_read_across_stripes_reads_every_key() {
20043        let mut f = Fixture::striped(8);
20044        let other = apart(&mut f, "s1");
20045        let (s1, s2) = (b"s1".as_slice(), other.as_bytes());
20046
20047        f.run(&[b"XADD", s1, b"1-1", b"a", b"1"]);
20048        f.run(&[b"XADD", s2, b"2-1", b"b", b"2"]);
20049        let got = f.run(&[b"XREAD", b"STREAMS", s1, s2, b"0", b"0"]);
20050        assert!(got.starts_with("*2\r\n"), "both streams answered: {got}");
20051        assert!(got.contains("1-1"), "the first one is in there: {got}");
20052        assert!(got.contains("2-1"), "and so is the second: {got}");
20053
20054        // A group read looks its group up on every key before it reads any of
20055        // them, so a group that is missing on the far key stops the near one.
20056        f.run(&[b"XGROUP", b"CREATE", s1, b"g", b"0"]);
20057        let got = f.run(&[
20058            b"XREADGROUP",
20059            b"GROUP",
20060            b"g",
20061            b"c",
20062            b"STREAMS",
20063            s1,
20064            s2,
20065            b">",
20066            b">",
20067        ]);
20068        assert!(got.starts_with("-NOGROUP"), "{got}");
20069        assert_eq!(
20070            f.run(&[b"XPENDING", s1, b"g"]),
20071            "*4\r\n:0\r\n$-1\r\n$-1\r\n*-1\r\n",
20072            "and read nothing from the key that did have the group"
20073        );
20074
20075        f.run(&[b"XGROUP", b"CREATE", s2, b"g", b"0"]);
20076        let got = f.run(&[
20077            b"XREADGROUP",
20078            b"GROUP",
20079            b"g",
20080            b"c",
20081            b"STREAMS",
20082            s1,
20083            s2,
20084            b">",
20085            b">",
20086        ]);
20087        assert!(got.starts_with("*2\r\n"), "now both are read: {got}");
20088    }
20089
20090    /// A client parked on an `XREAD` woken by an entry on another stripe.
20091    #[test]
20092    fn a_parked_stream_reader_is_served_from_the_stripe_its_key_is_on() {
20093        let mut f = Fixture::striped(8);
20094        let other = apart(&mut f, "s1");
20095        let (s1, far) = (b"s1".as_slice(), other.as_bytes());
20096        f.run(&[b"XADD", s1, b"1-1", b"a", b"1"]);
20097        f.run(&[b"XADD", far, b"1-1", b"a", b"1"]);
20098
20099        assert_eq!(
20100            f.flow(&[b"XREAD", b"BLOCK", b"0", b"STREAMS", s1, far, b"$", b"$"])
20101                .0,
20102            Flow::Block
20103        );
20104        f.run(&[b"XADD", far, b"2-1", b"b", b"2"]);
20105        let mut out = Out::new(Proto::Resp2);
20106        assert!(f.server.serve_waiter(7, 0, &mut out));
20107        let want = format!(
20108            "*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",
20109            other.len()
20110        );
20111        assert_eq!(core::str::from_utf8(out.as_slice()).expect("ascii"), want);
20112    }
20113
20114    /// Every JSON command, on one stripe and on eight.
20115    #[test]
20116    fn the_json_group_answers_the_same_however_many_stripes_there_are() {
20117        let script: &[&[&[u8]]] = &[
20118            &[
20119                b"JSON.SET",
20120                b"d",
20121                b"$",
20122                br#"{"a":1,"b":[1,2,3],"s":"hi","t":true}"#,
20123            ],
20124            &[b"JSON.SET", b"d", b"$.a", b"2"],
20125            &[b"JSON.SET", b"d", b"$.new", b"9", b"NX"],
20126            &[b"JSON.SET", b"d", b"$.new", b"8", b"NX"],
20127            &[b"JSON.SET", b"d", b"$.nope", b"7", b"XX"],
20128            &[b"JSON.GET", b"d"],
20129            &[b"JSON.GET", b"d", b"$.b"],
20130            &[b"JSON.GET", b"gone", b"$"],
20131            &[b"JSON.TYPE", b"d", b"$.b"],
20132            &[b"JSON.TYPE", b"d", b"$.s"],
20133            &[b"JSON.TOGGLE", b"d", b"$.t"],
20134            &[b"JSON.ARRLEN", b"d", b"$.b"],
20135            &[b"JSON.OBJLEN", b"d", b"$"],
20136            &[b"JSON.OBJKEYS", b"d", b"$"],
20137            &[b"JSON.STRLEN", b"d", b"$.s"],
20138            &[b"JSON.STRAPPEND", b"d", b"$.s", br#""there""#],
20139            &[b"JSON.ARRAPPEND", b"d", b"$.b", b"4"],
20140            &[b"JSON.ARRINSERT", b"d", b"$.b", b"0", b"0"],
20141            &[b"JSON.ARRINDEX", b"d", b"$.b", b"3"],
20142            &[b"JSON.ARRTRIM", b"d", b"$.b", b"1", b"3"],
20143            &[b"JSON.ARRPOP", b"d", b"$.b"],
20144            &[b"JSON.NUMINCRBY", b"d", b"$.a", b"5"],
20145            &[b"JSON.NUMMULTBY", b"d", b"$.a", b"2"],
20146            &[b"JSON.NUMPOWBY", b"d", b"$.a", b"2"],
20147            &[b"JSON.MERGE", b"d", b"$", br#"{"a":null,"m":1}"#],
20148            &[b"JSON.RESP", b"d", b"$.b"],
20149            &[b"JSON.DEBUG", b"MEMORY", b"d"],
20150            &[b"JSON.CLEAR", b"d", b"$.b"],
20151            &[b"JSON.DEL", b"d", b"$.m"],
20152            &[b"JSON.FORGET", b"d", b"$.nothere"],
20153            // The two that name more than one key.
20154            &[
20155                b"JSON.MSET",
20156                b"m1",
20157                b"$",
20158                b"1",
20159                b"m2",
20160                b"$",
20161                b"2",
20162                b"m3",
20163                b"$",
20164                b"3",
20165            ],
20166            &[b"JSON.MGET", b"m1", b"m2", b"m3", b"gone", b"$"],
20167            &[b"JSON.MSET", b"m1", b"$", b"9", b"m2", b"$.deep", b"9"],
20168            &[b"JSON.GET", b"m1", b"$"],
20169            &[b"JSON.MSET", b"m1", b"$", b"nonsense", b"m2", b"$", b"5"],
20170            &[b"JSON.GET", b"m2", b"$"],
20171            // And the errors.
20172            &[b"SET", b"plain", b"v"],
20173            &[b"JSON.GET", b"plain", b"$"],
20174            &[b"JSON.SET", b"plain", b"$", b"1"],
20175            &[b"JSON.MGET", b"m1", b"plain", b"$"],
20176            &[b"JSON.SET", b"d", b"$.b", b"["],
20177            &[b"JSON.DEL", b"plain"],
20178        ];
20179
20180        let mut one = Fixture::new();
20181        let mut many = Fixture::striped(8);
20182        for parts in script {
20183            let a = one.run(parts);
20184            let b = many.run(parts);
20185            assert_eq!(a, b, "{}", String::from_utf8_lossy(parts[0]));
20186        }
20187    }
20188
20189    /// A `JSON.MSET` and a `JSON.MGET` whose keys are on several stripes.
20190    ///
20191    /// `JSON.MSET` works every triple out against the keyspace as it was before
20192    /// the command and writes nothing until all of them are known to work, so
20193    /// the thing to check is that a triple that cannot be written stops the
20194    /// ones on other stripes as well as the ones on its own.
20195    #[test]
20196    fn a_json_multi_write_across_stripes_reaches_every_key() {
20197        let mut f = Fixture::striped(8);
20198        let second = apart(&mut f, "m1");
20199        let third = apart(&mut f, &second);
20200        let (m1, m2, m3) = (b"m1".as_slice(), second.as_bytes(), third.as_bytes());
20201
20202        assert_eq!(
20203            f.run(&[b"JSON.MSET", m1, b"$", b"1", m2, b"$", b"2", m3, b"$", b"3"]),
20204            "+OK\r\n"
20205        );
20206        assert_eq!(
20207            f.run(&[b"JSON.MGET", m1, m2, m3, b"gone", b"$"]),
20208            "*4\r\n$3\r\n[1]\r\n$3\r\n[2]\r\n$3\r\n[3]\r\n$-1\r\n"
20209        );
20210
20211        // A value that is not JSON is refused before anything is written, and
20212        // the key on the far stripe keeps what it had.
20213        assert_eq!(
20214            f.run(&[b"JSON.MSET", m1, b"$", b"9", m2, b"$", b"nonsense"]),
20215            "-this is not the start of a value, at byte 0 of the JSON text\r\n"
20216        );
20217        assert_eq!(f.run(&[b"JSON.GET", m1, b"$"]), "$3\r\n[1]\r\n");
20218
20219        // A path that names nowhere is not an error. That triple is skipped,
20220        // the ones on the other stripes are still written, and the reply is a
20221        // nil rather than OK.
20222        assert_eq!(
20223            f.run(&[
20224                b"JSON.MSET",
20225                m1,
20226                b"$",
20227                b"9",
20228                m2,
20229                b"$.deep",
20230                b"9",
20231                m3,
20232                b"$",
20233                b"7"
20234            ]),
20235            "$-1\r\n"
20236        );
20237        assert_eq!(f.run(&[b"JSON.GET", m1, b"$"]), "$3\r\n[9]\r\n");
20238        assert_eq!(f.run(&[b"JSON.GET", m2, b"$"]), "$3\r\n[2]\r\n");
20239        assert_eq!(f.run(&[b"JSON.GET", m3, b"$"]), "$3\r\n[7]\r\n");
20240    }
20241
20242    /// Every geospatial command, on one stripe and on eight.
20243    #[test]
20244    fn the_geo_group_answers_the_same_however_many_stripes_there_are() {
20245        let script: &[&[&[u8]]] = &[
20246            &[
20247                b"GEOADD",
20248                b"g",
20249                b"13.361389",
20250                b"38.115556",
20251                b"palermo",
20252                b"15.087269",
20253                b"37.502669",
20254                b"catania",
20255            ],
20256            &[
20257                b"GEOADD",
20258                b"g",
20259                b"NX",
20260                b"13.361389",
20261                b"38.115556",
20262                b"palermo",
20263            ],
20264            &[b"GEOADD", b"g", b"XX", b"CH", b"13.4", b"38.1", b"palermo"],
20265            &[b"GEOPOS", b"g", b"palermo", b"nothere"],
20266            &[b"GEOHASH", b"g", b"palermo", b"catania"],
20267            &[b"GEODIST", b"g", b"palermo", b"catania"],
20268            &[b"GEODIST", b"g", b"palermo", b"catania", b"KM"],
20269            &[b"GEODIST", b"g", b"palermo", b"nothere"],
20270            &[
20271                b"GEOSEARCH",
20272                b"g",
20273                b"FROMLONLAT",
20274                b"15",
20275                b"37",
20276                b"BYRADIUS",
20277                b"200",
20278                b"KM",
20279                b"ASC",
20280                b"WITHCOORD",
20281                b"WITHDIST",
20282                b"WITHHASH",
20283            ],
20284            &[
20285                b"GEOSEARCH",
20286                b"g",
20287                b"FROMMEMBER",
20288                b"palermo",
20289                b"BYBOX",
20290                b"400",
20291                b"400",
20292                b"KM",
20293                b"DESC",
20294            ],
20295            &[
20296                b"GEORADIUS",
20297                b"g",
20298                b"15",
20299                b"37",
20300                b"200",
20301                b"KM",
20302                b"COUNT",
20303                b"1",
20304            ],
20305            &[b"GEORADIUSBYMEMBER", b"g", b"palermo", b"200", b"KM"],
20306            &[b"GEORADIUSBYMEMBER_RO", b"g", b"nothere", b"200", b"KM"],
20307            &[
20308                b"GEOSEARCHSTORE",
20309                b"dst",
20310                b"g",
20311                b"FROMLONLAT",
20312                b"15",
20313                b"37",
20314                b"BYRADIUS",
20315                b"200",
20316                b"KM",
20317            ],
20318            &[b"ZRANGE", b"dst", b"0", b"-1"],
20319            &[
20320                b"GEOSEARCHSTORE",
20321                b"dst",
20322                b"g",
20323                b"FROMLONLAT",
20324                b"15",
20325                b"37",
20326                b"BYRADIUS",
20327                b"1",
20328                b"M",
20329                b"STOREDIST",
20330            ],
20331            &[b"EXISTS", b"dst"],
20332            &[
20333                b"GEORADIUS",
20334                b"g",
20335                b"15",
20336                b"37",
20337                b"200",
20338                b"KM",
20339                b"STORE",
20340                b"dst",
20341            ],
20342            &[b"ZCARD", b"dst"],
20343            // And the errors.
20344            &[b"GEOADD", b"g", b"181", b"38", b"nowhere"],
20345            &[b"SET", b"plain", b"v"],
20346            &[b"GEOPOS", b"plain", b"a"],
20347            &[b"GEOSEARCH", b"g", b"FROMLONLAT", b"15", b"37"],
20348            &[
20349                b"GEOSEARCHSTORE",
20350                b"dst",
20351                b"g",
20352                b"FROMLONLAT",
20353                b"15",
20354                b"37",
20355                b"BYRADIUS",
20356                b"200",
20357                b"KM",
20358                b"WITHCOORD",
20359            ],
20360        ];
20361
20362        let mut one = Fixture::new();
20363        let mut many = Fixture::striped(8);
20364        for parts in script {
20365            let a = one.run(parts);
20366            let b = many.run(parts);
20367            assert_eq!(a, b, "{}", String::from_utf8_lossy(parts[0]));
20368        }
20369    }
20370
20371    /// A `GEOSEARCHSTORE` whose two keys are on two stripes.
20372    #[test]
20373    fn a_geo_search_store_across_stripes_writes_what_it_found() {
20374        let mut f = Fixture::striped(8);
20375        let other = apart(&mut f, "g");
20376        let third = apart(&mut f, &other);
20377        let (g, dst, plain) = (b"g".as_slice(), other.as_bytes(), third.as_bytes());
20378
20379        f.run(&[
20380            b"GEOADD",
20381            g,
20382            b"13.361389",
20383            b"38.115556",
20384            b"palermo",
20385            b"15.087269",
20386            b"37.502669",
20387            b"catania",
20388        ]);
20389        assert_eq!(
20390            f.run(&[
20391                b"GEOSEARCHSTORE",
20392                dst,
20393                g,
20394                b"FROMLONLAT",
20395                b"15",
20396                b"37",
20397                b"BYRADIUS",
20398                b"200",
20399                b"KM",
20400                b"ASC",
20401            ]),
20402            ":2\r\n"
20403        );
20404        assert_eq!(
20405            f.run(&[b"ZRANGE", dst, b"0", b"-1"]),
20406            "*2\r\n$7\r\npalermo\r\n$7\r\ncatania\r\n",
20407            "the geohash is the score, so the order is not the search order"
20408        );
20409        assert_eq!(f.run(&[b"ZCARD", g]), ":2\r\n", "the source is untouched");
20410
20411        // `STOREDIST` stores the distance in the unit the search was asked in,
20412        // which is the destination stripe's sorted set and not the source's.
20413        assert_eq!(
20414            f.run(&[
20415                b"GEOSEARCHSTORE",
20416                dst,
20417                g,
20418                b"FROMMEMBER",
20419                b"palermo",
20420                b"BYRADIUS",
20421                b"200",
20422                b"KM",
20423                b"STOREDIST",
20424            ]),
20425            ":2\r\n"
20426        );
20427        assert_eq!(
20428            f.run(&[b"ZSCORE", dst, b"palermo"]),
20429            "$1\r\n0\r\n",
20430            "the centre is nought away from itself"
20431        );
20432
20433        // A search that found nothing deletes the destination on its own
20434        // stripe, and a source of the wrong type is refused with the
20435        // destination left alone.
20436        assert_eq!(
20437            f.run(&[
20438                b"GEOSEARCHSTORE",
20439                dst,
20440                g,
20441                b"FROMLONLAT",
20442                b"0",
20443                b"0",
20444                b"BYRADIUS",
20445                b"1",
20446                b"M",
20447            ]),
20448            ":0\r\n"
20449        );
20450        assert_eq!(f.run(&[b"EXISTS", dst]), ":0\r\n");
20451        f.run(&[
20452            b"GEOSEARCHSTORE",
20453            dst,
20454            g,
20455            b"FROMLONLAT",
20456            b"15",
20457            b"37",
20458            b"BYRADIUS",
20459            b"200",
20460            b"KM",
20461        ]);
20462        f.run(&[b"SET", plain, b"v"]);
20463        assert_eq!(
20464            f.run(&[
20465                b"GEOSEARCHSTORE",
20466                dst,
20467                plain,
20468                b"FROMLONLAT",
20469                b"15",
20470                b"37",
20471                b"BYRADIUS",
20472                b"200",
20473                b"KM",
20474            ]),
20475            "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n"
20476        );
20477        assert_eq!(
20478            f.run(&[b"ZCARD", dst]),
20479            ":2\r\n",
20480            "and left the destination"
20481        );
20482    }
20483
20484    /// Every time series command, on one stripe and on eight.
20485    ///
20486    /// Every timestamp is written out rather than left to the clock, so the two
20487    /// servers are compared on the samples they hold and not on how long the
20488    /// test took to get from one of them to the other.
20489    #[test]
20490    fn the_time_series_group_answers_the_same_however_many_stripes_there_are() {
20491        let script: &[&[&[u8]]] = &[
20492            &[
20493                b"TS.CREATE",
20494                b"ts:a",
20495                b"LABELS",
20496                b"sensor",
20497                b"a",
20498                b"room",
20499                b"1",
20500            ],
20501            &[b"TS.CREATE", b"ts:a"],
20502            &[b"TS.ALTER", b"ts:a", b"RETENTION", b"0"],
20503            &[b"TS.ADD", b"ts:a", b"1000", b"1.5"],
20504            &[
20505                b"TS.ADD", b"ts:b", b"1000", b"2", b"LABELS", b"sensor", b"b", b"room", b"1",
20506            ],
20507            &[
20508                b"TS.MADD", b"ts:a", b"2000", b"2.5", b"ts:b", b"2000", b"3", b"gone", b"1", b"1",
20509            ],
20510            &[b"TS.INCRBY", b"ts:a", b"1", b"TIMESTAMP", b"3000"],
20511            &[b"TS.DECRBY", b"ts:a", b"0.5", b"TIMESTAMP", b"4000"],
20512            &[b"TS.GET", b"ts:a"],
20513            &[b"TS.GET", b"gone"],
20514            &[b"TS.RANGE", b"ts:a", b"-", b"+"],
20515            &[b"TS.RANGE", b"ts:a", b"1000", b"3000", b"COUNT", b"2"],
20516            &[
20517                b"TS.RANGE",
20518                b"ts:a",
20519                b"-",
20520                b"+",
20521                b"AGGREGATION",
20522                b"avg",
20523                b"2000",
20524            ],
20525            &[b"TS.REVRANGE", b"ts:a", b"-", b"+"],
20526            &[b"TS.NRANGE", b"2", b"ts:a", b"ts:b", b"-", b"+"],
20527            &[b"TS.NREVRANGE", b"2", b"ts:a", b"ts:b", b"-", b"+"],
20528            &[b"TS.NRANGE", b"2", b"ts:a", b"gone", b"-", b"+"],
20529            &[b"TS.READ", b"ts:a", b"0"],
20530            &[b"TS.READ", b"ts:a", b"+"],
20531            // The filters, which are the ones that have to walk every stripe.
20532            &[b"TS.QUERYINDEX", b"sensor=a"],
20533            &[b"TS.QUERYINDEX", b"room=1"],
20534            &[b"TS.QUERYINDEX", b"room=9"],
20535            &[b"TS.QUERYLABELS", b"LABELS", b"FILTER", b"room=1"],
20536            &[
20537                b"TS.QUERYLABELS",
20538                b"VALUES",
20539                b"sensor",
20540                b"FILTER",
20541                b"room=1",
20542            ],
20543            &[b"TS.MGET", b"WITHLABELS", b"FILTER", b"room=1"],
20544            &[
20545                b"TS.MGET",
20546                b"SELECTED_LABELS",
20547                b"sensor",
20548                b"FILTER",
20549                b"sensor=a",
20550            ],
20551            &[b"TS.MRANGE", b"-", b"+", b"FILTER", b"room=1"],
20552            &[
20553                b"TS.MREVRANGE",
20554                b"-",
20555                b"+",
20556                b"WITHLABELS",
20557                b"FILTER",
20558                b"sensor=a",
20559            ],
20560            &[
20561                b"TS.MRANGE",
20562                b"-",
20563                b"+",
20564                b"FILTER",
20565                b"room=1",
20566                b"GROUPBY",
20567                b"room",
20568                b"REDUCE",
20569                b"max",
20570            ],
20571            &[b"TS.INFO", b"ts:a"],
20572            // And a rule, which is the one thing here that names two keys.
20573            &[
20574                b"TS.CREATERULE",
20575                b"ts:a",
20576                b"ts:down",
20577                b"AGGREGATION",
20578                b"avg",
20579                b"1000",
20580            ],
20581            &[b"TS.CREATE", b"ts:down"],
20582            &[
20583                b"TS.CREATERULE",
20584                b"ts:a",
20585                b"ts:down",
20586                b"AGGREGATION",
20587                b"avg",
20588                b"1000",
20589            ],
20590            &[b"TS.ADD", b"ts:a", b"5000", b"4"],
20591            &[b"TS.ADD", b"ts:a", b"6000", b"5"],
20592            &[b"TS.RANGE", b"ts:down", b"-", b"+"],
20593            &[b"TS.GET", b"ts:down", b"LATEST"],
20594            &[b"TS.INFO", b"ts:down"],
20595            &[b"TS.DEL", b"ts:a", b"5000", b"6000"],
20596            &[b"TS.RANGE", b"ts:down", b"-", b"+"],
20597            &[b"TS.DELETERULE", b"ts:a", b"ts:down"],
20598            &[b"TS.DELETERULE", b"ts:a", b"ts:down"],
20599            &[b"TS.DEL", b"ts:a", b"0", b"1000"],
20600            // And the errors.
20601            &[b"SET", b"plain", b"v"],
20602            &[b"TS.ADD", b"plain", b"1", b"1"],
20603            &[b"TS.GET", b"plain"],
20604            &[b"TS.READ", b"plain", b"0"],
20605            &[b"TS.ALTER", b"gone", b"RETENTION", b"0"],
20606            &[b"TS.RANGE", b"gone", b"-", b"+"],
20607            &[b"TS.INFO", b"gone"],
20608        ];
20609
20610        let mut one = Fixture::new();
20611        let mut many = Fixture::striped(8);
20612        for parts in script {
20613            let a = one.run(parts);
20614            let b = many.run(parts);
20615            assert_eq!(a, b, "{}", String::from_utf8_lossy(parts[0]));
20616        }
20617    }
20618
20619    /// A compaction rule whose two ends are on two stripes.
20620    ///
20621    /// This is the one thing in the family that walks from a key to another key,
20622    /// and it walks it in both directions: a sample on the source closes a
20623    /// bucket on the destination, a `LATEST` read on the destination folds the
20624    /// bucket the source is still filling, and a delete on the source rewrites
20625    /// what the destination already held. The same script is run against a
20626    /// server one stripe wide, where the two keys share a store, and against one
20627    /// eight stripes wide, where they do not.
20628    #[test]
20629    fn a_compaction_rule_across_stripes_reaches_both_ends() {
20630        let mut many = Fixture::striped(8);
20631        let other = apart(&mut many, "src");
20632        let (src, dst) = (b"src".as_slice(), other.as_bytes());
20633        let mut one = Fixture::new();
20634        let mut both = |parts: &[&[u8]]| {
20635            let a = one.run(parts);
20636            let b = many.run(parts);
20637            assert_eq!(a, b, "{}", String::from_utf8_lossy(parts[0]));
20638            a
20639        };
20640
20641        both(&[b"TS.CREATE", src]);
20642        both(&[b"TS.CREATE", dst]);
20643        assert_eq!(
20644            both(&[b"TS.CREATERULE", src, dst, b"AGGREGATION", b"avg", b"1000"]),
20645            "+OK\r\n"
20646        );
20647        both(&[b"TS.ADD", src, b"1000", b"1"]);
20648        both(&[b"TS.ADD", src, b"1500", b"3"]);
20649        // The bucket the source is filling is not written down yet, and asking
20650        // for it works it out off the source.
20651        assert_eq!(both(&[b"TS.RANGE", dst, b"-", b"+"]), "*0\r\n");
20652        let open = both(&[b"TS.GET", dst, b"LATEST"]);
20653        assert!(open.contains(":1000"), "the open bucket is folded: {open}");
20654
20655        // A sample past the bucket closes it, which is the write that has to
20656        // land on the other stripe.
20657        both(&[b"TS.ADD", src, b"2000", b"5"]);
20658        let got = both(&[b"TS.RANGE", dst, b"-", b"+"]);
20659        assert!(got.starts_with("*1\r\n"), "the bucket was written: {got}");
20660        assert!(got.contains(":1000"), "{got}");
20661
20662        // And a delete on the source takes it away again.
20663        both(&[b"TS.DEL", src, b"1000", b"1999"]);
20664        assert_eq!(both(&[b"TS.RANGE", dst, b"-", b"+"]), "*0\r\n");
20665
20666        // Both ends still know about each other, and the link comes apart from
20667        // the source.
20668        assert!(
20669            both(&[b"TS.INFO", dst]).contains("src"),
20670            "the source is named"
20671        );
20672        assert_eq!(both(&[b"TS.DELETERULE", src, dst]), "+OK\r\n");
20673        assert_eq!(
20674            both(&[b"TS.DELETERULE", src, dst]),
20675            "-ERR TSDB: compaction rule does not exist\r\n"
20676        );
20677    }
20678
20679    /// A label filter takes the series it names wherever they landed.
20680    #[test]
20681    fn a_label_query_across_stripes_finds_every_series() {
20682        let names: [&[u8]; 6] = [b"q:1", b"q:2", b"q:3", b"q:4", b"q:5", b"q:6"];
20683        let mut many = Fixture::striped(8);
20684        let mut homes: Vec<usize> = names
20685            .iter()
20686            .map(|name| many.server.striped(0).stripe_of(name))
20687            .collect();
20688        homes.sort_unstable();
20689        homes.dedup();
20690        assert!(homes.len() > 1, "the six keys are not all on one stripe");
20691
20692        let mut one = Fixture::new();
20693        let mut both = |parts: &[&[u8]]| {
20694            let a = one.run(parts);
20695            let b = many.run(parts);
20696            assert_eq!(a, b, "{}", String::from_utf8_lossy(parts[0]));
20697            a
20698        };
20699        for name in &names {
20700            both(&[b"TS.CREATE", name, b"LABELS", b"room", b"1"]);
20701            both(&[b"TS.ADD", name, b"1000", b"1"]);
20702        }
20703
20704        let got = both(&[b"TS.QUERYINDEX", b"room=1"]);
20705        assert!(got.starts_with("*6\r\n"), "every series answered: {got}");
20706        assert!(both(&[b"TS.MGET", b"FILTER", b"room=1"]).starts_with("*6\r\n"));
20707        assert!(both(&[b"TS.MRANGE", b"-", b"+", b"FILTER", b"room=1"]).starts_with("*6\r\n"));
20708        assert_eq!(
20709            both(&[b"TS.QUERYLABELS", b"LABELS", b"FILTER", b"room=1"]),
20710            "*1\r\n$4\r\nroom\r\n"
20711        );
20712    }
20713
20714    /// Every hash command, and the field import beside it, on one stripe and on
20715    /// eight.
20716    ///
20717    /// `HRANDFIELD` with a count draws from the stripe's own generator and two
20718    /// stripes do not draw the same numbers, so the only draw here is off a hash
20719    /// holding one field, where every generator gives the same answer.
20720    #[test]
20721    fn the_hash_group_answers_the_same_however_many_stripes_there_are() {
20722        let script: &[&[&[u8]]] = &[
20723            &[b"HSET", b"h", b"a", b"1", b"b", b"2"],
20724            &[b"HMSET", b"h", b"c", b"3"],
20725            &[b"HSETNX", b"h", b"a", b"9"],
20726            &[b"HSETNX", b"h", b"d", b"4"],
20727            &[b"HGET", b"h", b"a"],
20728            &[b"HGET", b"h", b"nope"],
20729            &[b"HMGET", b"h", b"a", b"nope"],
20730            &[b"HLEN", b"h"],
20731            &[b"HEXISTS", b"h", b"a"],
20732            &[b"HSTRLEN", b"h", b"a"],
20733            &[b"HGETALL", b"h"],
20734            &[b"HKEYS", b"h"],
20735            &[b"HVALS", b"h"],
20736            &[b"HINCRBY", b"h", b"a", b"5"],
20737            &[b"HINCRBYFLOAT", b"h", b"a", b"1.5"],
20738            &[b"HSCAN", b"h", b"0"],
20739            &[b"HSCAN", b"h", b"0", b"MATCH", b"a", b"COUNT", b"10"],
20740            &[b"HSCAN", b"h", b"0", b"NOVALUES"],
20741            &[b"HDEL", b"h", b"d"],
20742            &[b"HSET", b"one", b"f", b"v"],
20743            &[b"HRANDFIELD", b"one"],
20744            &[b"HRANDFIELD", b"one", b"1", b"WITHVALUES"],
20745            // The field deadlines.
20746            &[b"HEXPIRE", b"h", b"100", b"FIELDS", b"1", b"a"],
20747            &[b"HTTL", b"h", b"FIELDS", b"1", b"a"],
20748            &[b"HPTTL", b"h", b"FIELDS", b"1", b"a"],
20749            &[b"HEXPIRETIME", b"h", b"FIELDS", b"1", b"a"],
20750            &[b"HPEXPIRETIME", b"h", b"FIELDS", b"1", b"a"],
20751            &[b"HPERSIST", b"h", b"FIELDS", b"1", b"a"],
20752            &[b"HPEXPIREAT", b"h", b"1", b"FIELDS", b"1", b"b"],
20753            &[b"HGET", b"h", b"b"],
20754            // The three that came later and word everything their own way.
20755            &[b"HSETEX", b"h", b"EX", b"100", b"FIELDS", b"1", b"e", b"5"],
20756            &[b"HGETEX", b"h", b"PERSIST", b"FIELDS", b"1", b"e"],
20757            &[b"HGETDEL", b"h", b"FIELDS", b"1", b"e"],
20758            &[b"HGET", b"h", b"e"],
20759            // And the import, whose key is the third word.
20760            &[b"HIMPORT", b"PREPARE", b"fs", b"x", b"y"],
20761            &[b"HIMPORT", b"SET", b"imp", b"fs", b"1", b"2"],
20762            &[b"HGETALL", b"imp"],
20763            &[b"HIMPORT", b"SET", b"imp", b"nofs", b"1", b"2"],
20764            &[b"HIMPORT", b"DISCARD", b"fs"],
20765            // And the errors.
20766            &[b"SET", b"plain", b"v"],
20767            &[b"HSET", b"plain", b"a", b"1"],
20768            &[b"HGETALL", b"plain"],
20769            &[b"HGET", b"gone", b"a"],
20770            &[b"HINCRBY", b"h", b"a", b"nan"],
20771        ];
20772
20773        let mut one = Fixture::new();
20774        let mut many = Fixture::striped(8);
20775        // The field deadlines are absolute milliseconds worked out from the
20776        // clock, so both servers are put on the same one rather than left to
20777        // read the wall a moment apart.
20778        one.server.set_clock_ms(1_700_000_000_000);
20779        many.server.set_clock_ms(1_700_000_000_000);
20780        for parts in script {
20781            let a = one.run(parts);
20782            let b = many.run(parts);
20783            assert_eq!(a, b, "{}", String::from_utf8_lossy(parts[0]));
20784        }
20785    }
20786
20787    /// Every array command, on one stripe and on eight.
20788    #[test]
20789    fn the_array_group_answers_the_same_however_many_stripes_there_are() {
20790        let script: &[&[&[u8]]] = &[
20791            &[b"ARSET", b"a", b"0", b"x", b"y", b"z"],
20792            &[b"ARMSET", b"a", b"5", b"p", b"7", b"q"],
20793            &[b"ARGET", b"a", b"1"],
20794            &[b"ARGET", b"a", b"99"],
20795            &[b"ARMGET", b"a", b"0", b"5", b"99"],
20796            &[b"ARGETRANGE", b"a", b"0", b"7"],
20797            &[b"ARLEN", b"a"],
20798            &[b"ARCOUNT", b"a"],
20799            &[b"ARINSERT", b"a", b"m", b"n"],
20800            &[b"ARSCAN", b"a", b"0", b"20"],
20801            &[b"ARSCAN", b"a", b"0", b"20", b"LIMIT", b"2"],
20802            &[b"ARGREP", b"a", b"0", b"20", b"EXACT", b"x"],
20803            &[b"ARGREP", b"a", b"0", b"20", b"GLOB", b"*", b"WITHVALUES"],
20804            &[b"ARLASTITEMS", b"a", b"2"],
20805            &[b"ARLASTITEMS", b"a", b"2", b"REV"],
20806            &[b"ARNEXT", b"a"],
20807            &[b"ARSEEK", b"a", b"3"],
20808            &[b"AROP", b"a", b"0", b"20", b"USED"],
20809            &[b"AROP", b"a", b"0", b"20", b"MATCH", b"x"],
20810            &[b"ARINFO", b"a"],
20811            &[b"ARINFO", b"a", b"FULL"],
20812            &[b"ARDEL", b"a", b"0"],
20813            &[b"ARDELRANGE", b"a", b"1", b"2"],
20814            &[b"ARCOUNT", b"a"],
20815            &[b"ARRING", b"r", b"3", b"1", b"2", b"3", b"4"],
20816            &[b"ARGETRANGE", b"r", b"0", b"9"],
20817            // And the errors.
20818            &[b"SET", b"plain", b"v"],
20819            &[b"ARGET", b"plain", b"0"],
20820            &[b"ARSET", b"plain", b"0", b"v"],
20821            &[b"ARGET", b"gone", b"0"],
20822            &[b"ARSET", b"a", b"bad", b"v"],
20823        ];
20824
20825        let mut one = Fixture::new();
20826        let mut many = Fixture::striped(8);
20827        for parts in script {
20828            let a = one.run(parts);
20829            let b = many.run(parts);
20830            assert_eq!(a, b, "{}", String::from_utf8_lossy(parts[0]));
20831        }
20832    }
20833
20834    /// Every graph and vector set command, on one stripe and on eight.
20835    ///
20836    /// `VRANDMEMBER` is not in here for the reason `HRANDFIELD` with a count is
20837    /// not: it draws from the stripe's generator, and the stripes do not share
20838    /// one.
20839    #[test]
20840    fn the_graph_and_vector_groups_answer_the_same_however_many_stripes_there_are() {
20841        let script: &[&[&[u8]]] = &[
20842            &[b"G.NADD", b"g", b"n1", b"name", b"one"],
20843            &[b"G.NADD", b"g", b"n2", b"name", b"two"],
20844            &[b"G.NADD", b"g", b"n3"],
20845            &[b"G.NGET", b"g", b"n1"],
20846            &[b"G.NGET", b"g", b"gone"],
20847            &[b"G.EADD", b"g", b"n1", b"n2", b"knows"],
20848            &[b"G.EADD", b"g", b"n2", b"n3", b"knows"],
20849            &[b"G.OUT", b"g", b"n1", b"knows"],
20850            &[b"G.IN", b"g", b"n2", b"knows"],
20851            &[b"G.DEG", b"g", b"n1", b"knows"],
20852            &[b"G.DEG", b"g", b"n2", b"knows", b"BOTH"],
20853            &[b"G.NEIGH", b"g", b"n1", b"knows", b"DEPTH", b"2"],
20854            &[b"G.PATH", b"g", b"n1", b"n3"],
20855            &[b"G.EDEL", b"g", b"n1", b"n2", b"knows"],
20856            &[b"G.NDEL", b"g", b"n3"],
20857            &[b"G.NGET", b"g", b"n3"],
20858            // The vector set, which is one index under one key.
20859            &[b"VADD", b"v", b"VALUES", b"2", b"1", b"0", b"e1"],
20860            &[b"VADD", b"v", b"VALUES", b"2", b"0", b"1", b"e2"],
20861            &[b"VCARD", b"v"],
20862            &[b"VDIM", b"v"],
20863            &[b"VEMB", b"v", b"e1"],
20864            &[b"VSIM", b"v", b"VALUES", b"2", b"1", b"0"],
20865            &[b"VSIM", b"v", b"ELE", b"e1"],
20866            &[b"VISMEMBER", b"v", b"e1"],
20867            &[b"VISMEMBER", b"v", b"gone"],
20868            &[b"VSETATTR", b"v", b"e1", b"{\"k\":1}"],
20869            &[b"VGETATTR", b"v", b"e1"],
20870            &[b"VRANGE", b"v", b"-", b"+"],
20871            &[b"VLINKS", b"v", b"e1"],
20872            &[b"VINFO", b"v"],
20873            &[b"VREM", b"v", b"e2"],
20874            &[b"VCARD", b"v"],
20875            // And the errors.
20876            &[b"SET", b"plain", b"v"],
20877            &[b"G.NGET", b"plain", b"n1"],
20878            &[b"VCARD", b"plain"],
20879            &[b"G.NADD", b"gone2", b"n"],
20880            &[b"VEMB", b"gone3", b"e"],
20881        ];
20882
20883        let mut one = Fixture::new();
20884        let mut many = Fixture::striped(8);
20885        for parts in script {
20886            let a = one.run(parts);
20887            let b = many.run(parts);
20888            assert_eq!(a, b, "{}", String::from_utf8_lossy(parts[0]));
20889        }
20890    }
20891
20892    /// Every bloom filter, cuckoo filter, count min sketch, top k and t digest
20893    /// command, on one stripe and on eight.
20894    #[test]
20895    fn the_probabilistic_groups_answer_the_same_however_many_stripes_there_are() {
20896        let script: &[&[&[u8]]] = &[
20897            // The bloom filter.
20898            &[b"BF.RESERVE", b"bf", b"0.01", b"100"],
20899            &[b"BF.ADD", b"bf", b"a"],
20900            &[b"BF.ADD", b"bf", b"a"],
20901            &[b"BF.MADD", b"bf", b"b", b"c"],
20902            &[b"BF.EXISTS", b"bf", b"a"],
20903            &[b"BF.MEXISTS", b"bf", b"a", b"zz"],
20904            &[b"BF.CARD", b"bf"],
20905            &[b"BF.INFO", b"bf"],
20906            &[b"BF.INFO", b"bf", b"CAPACITY"],
20907            &[b"BF.DEBUG", b"bf"],
20908            &[b"BF.INSERT", b"made", b"CAPACITY", b"50", b"ITEMS", b"x"],
20909            &[b"BF.EXISTS", b"made", b"x"],
20910            &[b"BF.SCANDUMP", b"bf", b"0"],
20911            // The cuckoo filter.
20912            &[b"CF.RESERVE", b"cf", b"100"],
20913            &[b"CF.ADD", b"cf", b"a"],
20914            &[b"CF.ADDNX", b"cf", b"a"],
20915            &[b"CF.COUNT", b"cf", b"a"],
20916            &[b"CF.EXISTS", b"cf", b"a"],
20917            &[b"CF.MEXISTS", b"cf", b"a", b"zz"],
20918            &[b"CF.INSERT", b"cf", b"ITEMS", b"b", b"c"],
20919            &[b"CF.DEL", b"cf", b"a"],
20920            &[b"CF.COMPACT", b"cf"],
20921            &[b"CF.INFO", b"cf"],
20922            &[b"CF.DEBUG", b"cf"],
20923            &[b"CF.SCANDUMP", b"cf", b"0"],
20924            // The count min sketch.
20925            &[b"CMS.INITBYDIM", b"cms", b"100", b"5"],
20926            &[b"CMS.INITBYPROB", b"cms2", b"0.01", b"0.01"],
20927            &[b"CMS.INCRBY", b"cms", b"a", b"5", b"b", b"3"],
20928            &[b"CMS.QUERY", b"cms", b"a", b"b", b"gone"],
20929            &[b"CMS.INFO", b"cms"],
20930            // The top k sketch.
20931            &[b"TOPK.RESERVE", b"tk", b"3"],
20932            &[b"TOPK.ADD", b"tk", b"a", b"b", b"a"],
20933            &[b"TOPK.INCRBY", b"tk", b"c", b"4"],
20934            &[b"TOPK.QUERY", b"tk", b"a", b"zz"],
20935            &[b"TOPK.COUNT", b"tk", b"a", b"c"],
20936            &[b"TOPK.LIST", b"tk"],
20937            &[b"TOPK.LIST", b"tk", b"WITHCOUNT"],
20938            &[b"TOPK.INFO", b"tk"],
20939            // The t digest.
20940            &[b"TDIGEST.CREATE", b"td"],
20941            &[b"TDIGEST.ADD", b"td", b"1", b"2", b"3", b"4", b"5"],
20942            &[b"TDIGEST.MIN", b"td"],
20943            &[b"TDIGEST.MAX", b"td"],
20944            &[b"TDIGEST.QUANTILE", b"td", b"0.5"],
20945            &[b"TDIGEST.CDF", b"td", b"3"],
20946            &[b"TDIGEST.RANK", b"td", b"3"],
20947            &[b"TDIGEST.REVRANK", b"td", b"3"],
20948            &[b"TDIGEST.BYRANK", b"td", b"0"],
20949            &[b"TDIGEST.BYREVRANK", b"td", b"0"],
20950            &[b"TDIGEST.TRIMMED_MEAN", b"td", b"0.1", b"0.9"],
20951            &[b"TDIGEST.INFO", b"td"],
20952            &[b"TDIGEST.RESET", b"td"],
20953            &[b"TDIGEST.MIN", b"td"],
20954            // And the errors.
20955            &[b"SET", b"plain", b"v"],
20956            &[b"BF.ADD", b"plain", b"a"],
20957            &[b"CF.ADD", b"plain", b"a"],
20958            &[b"CMS.QUERY", b"plain", b"a"],
20959            &[b"TOPK.ADD", b"plain", b"a"],
20960            &[b"TDIGEST.ADD", b"plain", b"1"],
20961            &[b"CMS.INFO", b"gone"],
20962            &[b"TOPK.INFO", b"gone"],
20963            &[b"TDIGEST.INFO", b"gone"],
20964        ];
20965
20966        let mut one = Fixture::new();
20967        let mut many = Fixture::striped(8);
20968        for parts in script {
20969            let a = one.run(parts);
20970            let b = many.run(parts);
20971            assert_eq!(a, b, "{}", String::from_utf8_lossy(parts[0]));
20972        }
20973    }
20974
20975    /// The two sketch merges, with their sources on stripes of their own.
20976    ///
20977    /// These are the only two commands in the ten groups that name more than one
20978    /// key, and both read a run of sources and write a destination, so both go
20979    /// wrong in the same way if a merge holds one store and looks every source up
20980    /// in it.
20981    #[test]
20982    fn a_sketch_merge_across_stripes_reads_every_source() {
20983        let mut many = Fixture::striped(8);
20984        let other = apart(&mut many, "s1");
20985        let (s1, s2) = (b"s1".as_slice(), other.as_bytes());
20986        let mut one = Fixture::new();
20987        let mut both = |parts: &[&[u8]]| {
20988            let a = one.run(parts);
20989            let b = many.run(parts);
20990            assert_eq!(a, b, "{}", String::from_utf8_lossy(parts[0]));
20991            a
20992        };
20993
20994        // The count min sketch. The destination has to be the sources' shape,
20995        // and it is named first, so all three keys are read before anything is
20996        // written.
20997        for key in [b"cd".as_slice(), s1, s2] {
20998            both(&[b"CMS.INITBYDIM", key, b"100", b"5"]);
20999        }
21000        both(&[b"CMS.INCRBY", s1, b"x", b"5"]);
21001        both(&[b"CMS.INCRBY", s2, b"x", b"3"]);
21002        assert_eq!(
21003            both(&[b"CMS.MERGE", b"cd", b"2", s1, s2]),
21004            "+OK\r\n",
21005            "the merge took both sources"
21006        );
21007        assert_eq!(both(&[b"CMS.QUERY", b"cd", b"x"]), "*1\r\n:8\r\n");
21008        // And with weights, which are read against the sources in order.
21009        both(&[b"CMS.MERGE", b"cd", b"2", s1, s2, b"WEIGHTS", b"2", b"1"]);
21010        assert_eq!(both(&[b"CMS.QUERY", b"cd", b"x"]), "*1\r\n:13\r\n");
21011        // A source that is not a sketch is answered before anything is written.
21012        both(&[b"SET", b"plain", b"v"]);
21013        assert!(both(&[b"CMS.MERGE", b"cd", b"2", s1, b"plain"]).starts_with('-'));
21014        assert_eq!(both(&[b"CMS.QUERY", b"cd", b"x"]), "*1\r\n:13\r\n");
21015
21016        // The t digest, which builds its destination and then puts it in place.
21017        // The two source keys are used again here, so what they held goes first.
21018        both(&[b"FLUSHALL"]);
21019        both(&[b"TDIGEST.CREATE", b"td"]);
21020        both(&[b"TDIGEST.CREATE", s1]);
21021        both(&[b"TDIGEST.CREATE", s2]);
21022        both(&[b"TDIGEST.ADD", s1, b"1", b"2"]);
21023        both(&[b"TDIGEST.ADD", s2, b"9", b"10"]);
21024        assert_eq!(both(&[b"TDIGEST.MERGE", b"td", b"2", s1, s2]), "+OK\r\n");
21025        assert_eq!(both(&[b"TDIGEST.MIN", b"td"]), "$1\r\n1\r\n");
21026        assert_eq!(both(&[b"TDIGEST.MAX", b"td"]), "$2\r\n10\r\n");
21027    }
21028
21029    /// Every shape of `SORT`, on one stripe and on eight.
21030    ///
21031    /// The key it sorts, the keys a `BY` names, the keys a `GET` names and the
21032    /// destination are four different names and nothing lines them up, so on
21033    /// eight stripes this script is reading and writing all over the database
21034    /// while on one it is doing what it always did.
21035    #[test]
21036    fn the_sort_command_answers_the_same_however_many_stripes_there_are() {
21037        let script: &[&[&[u8]]] = &[
21038            &[b"RPUSH", b"l", b"3", b"1", b"2", b"10"],
21039            &[b"SORT", b"l"],
21040            &[b"SORT", b"l", b"DESC"],
21041            &[b"SORT", b"l", b"ALPHA"],
21042            &[b"SORT", b"l", b"LIMIT", b"1", b"2"],
21043            &[b"SORT_RO", b"l"],
21044            // A weight per element, so the order comes off keys the command
21045            // never named.
21046            &[
21047                b"MSET", b"w_1", b"4", b"w_2", b"3", b"w_3", b"2", b"w_10", b"1",
21048            ],
21049            &[b"SORT", b"l", b"BY", b"w_*"],
21050            &[b"SORT", b"l", b"BY", b"w_*", b"DESC"],
21051            &[b"DEL", b"w_2"],
21052            &[b"SORT", b"l", b"BY", b"w_*"],
21053            // And the answer off another set of keys again, with `#` mixed in
21054            // so the rows are not all lookups.
21055            &[b"MSET", b"d_1", b"one", b"d_3", b"three"],
21056            &[b"SORT", b"l", b"BY", b"w_*", b"GET", b"#", b"GET", b"d_*"],
21057            // A pattern that reaches into a hash, which is another key again.
21058            &[b"HSET", b"h_1", b"f", b"9"],
21059            &[b"HSET", b"h_2", b"f", b"8"],
21060            &[b"HSET", b"h_3", b"f", b"7"],
21061            &[b"HSET", b"h_10", b"f", b"6"],
21062            &[b"SORT", b"l", b"BY", b"h_*->f"],
21063            &[b"SORT", b"l", b"BY", b"nosort", b"GET", b"h_*->f"],
21064            // The destination, which is a fourth place to land.
21065            &[b"SORT", b"l", b"BY", b"w_*", b"STORE", b"out"],
21066            &[b"LRANGE", b"out", b"0", b"-1"],
21067            &[b"SORT", b"l", b"STORE", b"l"],
21068            &[b"LRANGE", b"l", b"0", b"-1"],
21069            // An empty result takes the destination away rather than leaving a
21070            // list of nothing behind.
21071            &[b"SORT", b"missing", b"STORE", b"out"],
21072            &[b"EXISTS", b"out"],
21073            // A set and a sorted set sort the same way a list does, and a set
21074            // written to a destination is sorted even when nothing asked.
21075            &[b"SADD", b"s", b"c", b"a", b"b"],
21076            &[b"SORT", b"s", b"ALPHA"],
21077            &[b"SORT", b"s", b"BY", b"nosort", b"STORE", b"out"],
21078            &[b"LRANGE", b"out", b"0", b"-1"],
21079            &[b"ZADD", b"z", b"3", b"c", b"1", b"a", b"2", b"b"],
21080            &[b"SORT", b"z", b"BY", b"nosort"],
21081            &[b"SORT", b"z", b"ALPHA", b"DESC"],
21082            // And the two ways it refuses: a key of the wrong type, and an
21083            // element that is not a number under a numeric sort.
21084            &[b"SET", b"str", b"v"],
21085            &[b"SORT", b"str"],
21086            &[b"RPUSH", b"words", b"one", b"two"],
21087            &[b"SORT", b"words"],
21088            &[b"SORT_RO", b"l", b"STORE", b"out"],
21089        ];
21090
21091        let mut one = Fixture::new();
21092        let mut many = Fixture::striped(8);
21093        for parts in script {
21094            let a = one.run(parts);
21095            let b = many.run(parts);
21096            assert_eq!(a, b, "{}", String::from_utf8_lossy(parts[0]));
21097        }
21098    }
21099
21100    /// One `SORT` whose four kinds of key are on stripes of their own.
21101    ///
21102    /// The script above spreads keys around by writing enough of them, and this
21103    /// one checks the spread rather than trusting it: the list, the weight key
21104    /// for one of its elements and the destination are asserted to be in three
21105    /// places before the command runs.
21106    #[test]
21107    fn a_sort_across_stripes_reads_every_pattern_key() {
21108        let mut f = Fixture::striped(8);
21109        let out = apart(&mut f, "l");
21110        let (list, dest) = (b"l".as_slice(), out.as_bytes());
21111
21112        f.run(&[b"RPUSH", list, b"a", b"b", b"c", b"d"]);
21113        f.run(&[
21114            b"MSET", b"w_a", b"4", b"w_b", b"3", b"w_c", b"2", b"w_d", b"1",
21115        ]);
21116        f.run(&[
21117            b"MSET", b"d_a", b"A", b"d_b", b"B", b"d_c", b"C", b"d_d", b"D",
21118        ]);
21119
21120        // The weights are four keys and they are not all in one place, which is
21121        // the thing that would go unnoticed if the command held a stripe.
21122        let db = f.server.striped(0);
21123        let weights: Vec<usize> = [b"w_a", b"w_b", b"w_c", b"w_d"]
21124            .iter()
21125            .map(|k| db.stripe_of(k.as_slice()))
21126            .collect();
21127        assert!(
21128            weights.iter().any(|s| *s != weights[0]),
21129            "the four weight keys all landed on one stripe, so this proves nothing"
21130        );
21131
21132        assert_eq!(
21133            f.run(&[b"SORT", list, b"BY", b"w_*", b"GET", b"d_*"]),
21134            "*4\r\n$1\r\nD\r\n$1\r\nC\r\n$1\r\nB\r\n$1\r\nA\r\n",
21135            "the order came off the weights and the answer off the data keys"
21136        );
21137        assert_eq!(
21138            f.run(&[b"SORT", list, b"BY", b"w_*", b"STORE", dest]),
21139            ":4\r\n"
21140        );
21141        assert_eq!(
21142            f.run(&[b"LRANGE", dest, b"0", b"-1"]),
21143            "*4\r\n$1\r\nd\r\n$1\r\nc\r\n$1\r\nb\r\n$1\r\na\r\n",
21144            "the destination is on a stripe of its own and got the whole answer"
21145        );
21146    }
21147
21148    /// A `CONFIG SET` reaches every stripe, so where a key landed does not
21149    /// decide what shape it is stored in.
21150    ///
21151    /// This is the setting that would go wrong quietly. A stripe that kept the
21152    /// old ladder would hold the same hash in a different encoding from the
21153    /// stripe next to it, and the only thing that would ever say so is
21154    /// `OBJECT ENCODING`, which is why the check is on that.
21155    #[test]
21156    fn a_setting_reaches_every_stripe_and_reads_back_from_any_of_them() {
21157        let mut f = Fixture::striped(8);
21158        let other = apart(&mut f, "h");
21159        let (first, second) = (b"h".as_slice(), other.as_bytes());
21160
21161        assert_eq!(
21162            f.run(&[b"CONFIG", b"SET", b"hash-max-listpack-entries", b"2"]),
21163            "+OK\r\n"
21164        );
21165        assert_eq!(
21166            f.run(&[b"CONFIG", b"GET", b"hash-max-listpack-entries"]),
21167            "*2\r\n$25\r\nhash-max-listpack-entries\r\n$1\r\n2\r\n",
21168            "the read comes off one stripe and has to answer for all of them"
21169        );
21170        for key in [first, second] {
21171            f.run(&[b"HSET", key, b"a", b"1", b"b", b"2"]);
21172            assert_eq!(
21173                f.run(&[b"OBJECT", b"ENCODING", key]),
21174                "$8\r\nlistpack\r\n",
21175                "two fields is still under the ladder"
21176            );
21177            f.run(&[b"HSET", key, b"c", b"3"]);
21178            assert_eq!(
21179                f.run(&[b"OBJECT", b"ENCODING", key]),
21180                "$9\r\nhashtable\r\n",
21181                "three fields is over it, on whichever stripe the key is on"
21182            );
21183        }
21184
21185        // And the policy, which every stripe has to agree about for the same
21186        // reason: an eviction draws from one stripe at a time.
21187        assert_eq!(
21188            f.run(&[b"CONFIG", b"SET", b"maxmemory-policy", b"allkeys-lru"]),
21189            "+OK\r\n"
21190        );
21191        let db = f.server.striped(0);
21192        assert!(
21193            (0..db.width()).all(|i| db.hold_stripe(i).policy().name() == "allkeys-lru"),
21194            "a stripe kept the old policy"
21195        );
21196    }
21197
21198    /// What an index holds, as the two numbers `FT.INFO` reports about it.
21199    ///
21200    /// Read off the registry rather than parsed back out of an `FT.INFO` reply,
21201    /// because the reply is thirty odd fields and these two are the ones the
21202    /// keyspace hook moves.
21203    fn held(f: &Fixture, name: &[u8]) -> (usize, u32) {
21204        let search = f.server.search.lock();
21205        let index = search.named(name).expect("the index is there");
21206        (index.held.docs.len(), index.held.docs.last())
21207    }
21208
21209    /// A hash written under an index's prefix reaches it, and one written
21210    /// outside the prefix does not.
21211    #[test]
21212    fn a_hash_that_is_written_reaches_the_index_that_follows_it() {
21213        let mut f = Fixture::new();
21214        f.run(&[
21215            b"FT.CREATE",
21216            b"ix",
21217            b"PREFIX",
21218            b"1",
21219            b"p:",
21220            b"SCHEMA",
21221            b"t",
21222            b"TEXT",
21223        ]);
21224        f.run(&[b"HSET", b"p:1", b"t", b"running dogs"]);
21225        assert_eq!(held(&f, b"ix"), (1, 1));
21226        f.run(&[b"HSET", b"other:1", b"t", b"running dogs"]);
21227        assert_eq!(held(&f, b"ix"), (1, 1));
21228
21229        // Every field of the key and not the one the command named, since a
21230        // document is read from nothing every time.
21231        f.run(&[b"HSET", b"p:1", b"u", b"beta"]);
21232        f.run(&[b"HDEL", b"p:1", b"u"]);
21233        assert_eq!(held(&f, b"ix"), (1, 3));
21234        let search = f.server.search.lock();
21235        let index = search.named(b"ix").expect("there");
21236        assert_eq!(index.held.docs.id(b"p:1"), Some(3));
21237    }
21238
21239    /// A fresh index reads the keys that were already there, and walks past a
21240    /// key of the wrong type without counting a failure.
21241    #[test]
21242    fn a_fresh_index_reads_the_keys_that_were_already_there() {
21243        let mut f = Fixture::new();
21244        f.run(&[b"HSET", b"p:1", b"t", b"alpha"]);
21245        f.run(&[b"SET", b"p:str", b"not a hash"]);
21246        f.run(&[b"HSET", b"q:1", b"t", b"beta"]);
21247        f.run(&[
21248            b"FT.CREATE",
21249            b"ix",
21250            b"PREFIX",
21251            b"1",
21252            b"p:",
21253            b"SCHEMA",
21254            b"t",
21255            b"TEXT",
21256        ]);
21257
21258        assert_eq!(held(&f, b"ix"), (1, 1));
21259        let search = f.server.search.lock();
21260        let index = search.named(b"ix").expect("there");
21261        assert_eq!(index.trouble.whole().failures(), 0);
21262    }
21263
21264    /// `SKIPINITIALSCAN` leaves what was there alone, and a later write to one
21265    /// of those keys still lands.
21266    #[test]
21267    fn an_index_that_skipped_the_scan_fills_up_on_the_next_write() {
21268        let mut f = Fixture::new();
21269        f.run(&[b"HSET", b"p:1", b"t", b"alpha"]);
21270        f.run(&[
21271            b"FT.CREATE",
21272            b"ix",
21273            b"PREFIX",
21274            b"1",
21275            b"p:",
21276            b"SKIPINITIALSCAN",
21277            b"SCHEMA",
21278            b"t",
21279            b"TEXT",
21280        ]);
21281        assert_eq!(held(&f, b"ix"), (0, 0));
21282        f.run(&[b"HSET", b"p:1", b"t", b"alpha"]);
21283        assert_eq!(held(&f, b"ix"), (1, 1));
21284    }
21285
21286    /// A command that changed nothing leaves the document where it was, which
21287    /// is not the same as a command that was not a write.
21288    ///
21289    /// All five of these were measured against 8.10.1. Writing the same value
21290    /// again moves the number and a deadline set for later does not, which is
21291    /// the pair that makes the rule "the fields are not what they were" rather
21292    /// than "this was a write".
21293    #[test]
21294    fn only_a_real_change_gives_the_document_a_new_number() {
21295        let mut f = Fixture::new();
21296        f.run(&[
21297            b"FT.CREATE",
21298            b"ix",
21299            b"PREFIX",
21300            b"1",
21301            b"p:",
21302            b"SCHEMA",
21303            b"t",
21304            b"TEXT",
21305        ]);
21306        f.run(&[b"HSET", b"p:1", b"t", b"alpha"]);
21307        assert_eq!(held(&f, b"ix"), (1, 1));
21308
21309        f.run(&[b"HSET", b"p:1", b"t", b"alpha"]);
21310        assert_eq!(held(&f, b"ix"), (1, 2), "the same value still rewrites");
21311
21312        for quiet in [
21313            vec![b"HSETNX".as_slice(), b"p:1", b"t", b"other"],
21314            vec![b"HDEL".as_slice(), b"p:1", b"nosuch"],
21315            vec![b"HGET".as_slice(), b"p:1", b"t"],
21316            vec![b"HGETALL".as_slice(), b"p:1"],
21317            vec![b"HEXPIRE".as_slice(), b"p:1", b"100", b"FIELDS", b"1", b"t"],
21318            vec![b"HPERSIST".as_slice(), b"p:1", b"FIELDS", b"1", b"t"],
21319            vec![
21320                b"HGETEX".as_slice(),
21321                b"p:1",
21322                b"EX",
21323                b"100",
21324                b"FIELDS",
21325                b"1",
21326                b"t",
21327            ],
21328            vec![b"HGETDEL".as_slice(), b"p:1", b"FIELDS", b"1", b"nosuch"],
21329        ] {
21330            f.run(&quiet);
21331            assert_eq!(held(&f, b"ix"), (1, 2), "{:?} moved the document", quiet[0]);
21332        }
21333
21334        // And the ones that do change something.
21335        f.run(&[b"HSET", b"p:2", b"n", b"1"]);
21336        f.run(&[b"HINCRBY", b"p:2", b"n", b"1"]);
21337        assert_eq!(held(&f, b"ix"), (2, 4));
21338        // A deadline that has already passed takes the field away, and taking
21339        // the last field away takes the key and the document with it. The
21340        // number still moves on the way past, because the field going and the
21341        // key going are two separate pieces of news and the first of them
21342        // writes the document one last time.
21343        f.run(&[b"HEXPIRE", b"p:2", b"0", b"FIELDS", b"1", b"n"]);
21344        assert_eq!(held(&f, b"ix"), (1, 5));
21345    }
21346
21347    /// The two ways of emptying a hash, which do not leave the same thing
21348    /// behind. `HDEL` of the last field spends no number and is counted as a
21349    /// refusal, and a deadline that has already passed spends one on a document
21350    /// nobody sees and is counted as nothing. Measured against 8.10.1 and not
21351    /// something anyone would guess.
21352    #[test]
21353    fn a_key_emptied_by_a_deadline_spends_a_number_and_one_emptied_by_hdel_does_not() {
21354        /// The index's own failure count.
21355        fn refused(f: &Fixture, name: &[u8]) -> u64 {
21356            let search = f.server.search.lock();
21357            let index = search.named(name).expect("the index is there");
21358            index.trouble.whole().failures()
21359        }
21360
21361        let mut f = Fixture::new();
21362        f.run(&[
21363            b"FT.CREATE",
21364            b"ix",
21365            b"PREFIX",
21366            b"1",
21367            b"p:",
21368            b"SCHEMA",
21369            b"t",
21370            b"TEXT",
21371        ]);
21372        f.run(&[b"HSET", b"p:1", b"t", b"alpha"]);
21373        assert_eq!(held(&f, b"ix"), (1, 1));
21374        f.run(&[b"HDEL", b"p:1", b"t"]);
21375        assert_eq!(
21376            held(&f, b"ix"),
21377            (0, 1),
21378            "HDEL of the last field spends none"
21379        );
21380        assert_eq!(refused(&f, b"ix"), 1, "and is counted as a refusal");
21381
21382        f.run(&[b"HSET", b"p:2", b"t", b"alpha"]);
21383        assert_eq!(held(&f, b"ix"), (1, 2));
21384        f.run(&[b"HEXPIRE", b"p:2", b"0", b"FIELDS", b"1", b"t"]);
21385        assert_eq!(held(&f, b"ix"), (0, 3), "a deadline spends one");
21386        assert_eq!(refused(&f, b"ix"), 1, "and is counted as nothing");
21387
21388        f.run(&[b"HSET", b"p:3", b"t", b"alpha"]);
21389        assert_eq!(held(&f, b"ix"), (1, 4));
21390        f.run(&[b"HGETDEL", b"p:3", b"FIELDS", b"1", b"t"]);
21391        assert_eq!(held(&f, b"ix"), (0, 5), "and so does HGETDEL");
21392
21393        // Two fields and one command is one rewrite and not two, whichever way
21394        // the fields go.
21395        f.run(&[b"HSET", b"p:4", b"t", b"alpha", b"u", b"beta"]);
21396        assert_eq!(held(&f, b"ix"), (1, 6));
21397        f.run(&[b"HEXPIRE", b"p:4", b"0", b"FIELDS", b"2", b"t", b"u"]);
21398        assert_eq!(held(&f, b"ix"), (0, 7));
21399        assert_eq!(refused(&f, b"ix"), 1);
21400    }
21401
21402    /// `HSETEX` with a deadline that has already passed is two pieces of news
21403    /// from one command, so the number moves twice and the value never reaches
21404    /// the index.
21405    #[test]
21406    fn a_field_written_already_past_its_deadline_moves_the_number_twice() {
21407        let mut f = Fixture::new();
21408        f.run(&[
21409            b"FT.CREATE",
21410            b"ix",
21411            b"PREFIX",
21412            b"1",
21413            b"p:",
21414            b"SCHEMA",
21415            b"t",
21416            b"TEXT",
21417            b"u",
21418            b"TEXT",
21419        ]);
21420        f.run(&[b"HSET", b"p:1", b"u", b"keepme"]);
21421        assert_eq!(held(&f, b"ix"), (1, 1));
21422        f.run(&[
21423            b"HSETEX", b"p:1", b"EXAT", b"1", b"FIELDS", b"1", b"t", b"zqx",
21424        ]);
21425        assert_eq!(
21426            held(&f, b"ix"),
21427            (1, 3),
21428            "the key lived and the field did not"
21429        );
21430
21431        // And the same when the key does not survive it.
21432        f.run(&[b"HSET", b"p:2", b"t", b"alpha"]);
21433        assert_eq!(held(&f, b"ix"), (2, 4));
21434        f.run(&[
21435            b"HSETEX", b"p:2", b"EXAT", b"1", b"FIELDS", b"1", b"t", b"zqx",
21436        ]);
21437        assert_eq!(held(&f, b"ix"), (1, 6));
21438    }
21439
21440    /// The number one key is indexed under, or `None` when it holds no
21441    /// document.
21442    fn number(f: &Fixture, name: &[u8], key: &[u8]) -> Option<u32> {
21443        let search = f.server.search.lock();
21444        let index = search.named(name).expect("the index is there");
21445        index.held.docs.id(key)
21446    }
21447
21448    /// An index over `p:` with one document under `p:1`, which is where four of
21449    /// the tests below start.
21450    fn indexed() -> Fixture {
21451        let mut f = Fixture::new();
21452        f.run(&[
21453            b"FT.CREATE",
21454            b"ix",
21455            b"PREFIX",
21456            b"1",
21457            b"p:",
21458            b"SCHEMA",
21459            b"t",
21460            b"TEXT",
21461        ]);
21462        f.run(&[b"HSET", b"p:1", b"t", b"alpha"]);
21463        f
21464    }
21465
21466    /// Every way a keyspace command takes a key away leaves no document behind,
21467    /// and none of them spends a number or is counted as a refusal.
21468    #[test]
21469    fn a_key_a_keyspace_command_takes_away_loses_its_document() {
21470        for take in [
21471            vec![b"DEL".as_slice(), b"p:1"],
21472            vec![b"UNLINK".as_slice(), b"p:1"],
21473            vec![b"PEXPIREAT".as_slice(), b"p:1", b"1"],
21474            vec![b"EXPIRE".as_slice(), b"p:1", b"-1"],
21475        ] {
21476            let mut f = indexed();
21477            assert_eq!(held(&f, b"ix"), (1, 1));
21478            f.run(&take);
21479            assert_eq!(held(&f, b"ix"), (0, 1), "{:?} left something", take[0]);
21480            let search = f.server.search.lock();
21481            let index = search.named(b"ix").expect("the index is there");
21482            assert_eq!(index.trouble.whole().failures(), 0, "{:?}", take[0]);
21483        }
21484
21485        // A deadline that has not passed yet is not one of them.
21486        let mut f = indexed();
21487        f.run(&[b"EXPIRE", b"p:1", b"1000"]);
21488        assert_eq!(held(&f, b"ix"), (1, 1));
21489        f.run(&[b"PERSIST", b"p:1"]);
21490        assert_eq!(held(&f, b"ix"), (1, 1));
21491    }
21492
21493    /// A rename inside the prefix keeps the number the document had, which is
21494    /// the one write on a followed key that does not spend one. Out of the
21495    /// prefix is an erase and into it is a fresh reading, both measured.
21496    #[test]
21497    fn a_rename_inside_the_prefix_keeps_the_number_the_document_had() {
21498        let mut f = indexed();
21499        f.run(&[b"RENAME", b"p:1", b"p:2"]);
21500        assert_eq!(held(&f, b"ix"), (1, 1), "nothing was read again");
21501        assert_eq!(number(&f, b"ix", b"p:2"), Some(1));
21502        assert_eq!(number(&f, b"ix", b"p:1"), None);
21503
21504        f.run(&[b"RENAME", b"p:2", b"q:1"]);
21505        assert_eq!(held(&f, b"ix"), (0, 1), "out of the prefix is an erase");
21506
21507        f.run(&[b"RENAME", b"q:1", b"p:3"]);
21508        assert_eq!(held(&f, b"ix"), (1, 2), "and into it is a reading");
21509        assert_eq!(number(&f, b"ix", b"p:3"), Some(2));
21510
21511        // `RENAMENX` goes the same way, and the one that answers zero changes
21512        // nothing.
21513        f.run(&[b"HSET", b"p:4", b"t", b"beta"]);
21514        assert_eq!(f.run(&[b"RENAMENX", b"p:3", b"p:4"]), ":0\r\n");
21515        assert_eq!(held(&f, b"ix"), (2, 3));
21516        f.run(&[b"RENAMENX", b"p:3", b"p:5"]);
21517        assert_eq!(number(&f, b"ix", b"p:5"), Some(2));
21518    }
21519
21520    /// A rename over a key that already had a document leaves one document and
21521    /// not two. A real server leaves both, and D-64 is that difference.
21522    #[test]
21523    fn a_rename_over_a_document_leaves_one_of_them() {
21524        let mut f = indexed();
21525        f.run(&[b"HSET", b"p:2", b"t", b"beta"]);
21526        assert_eq!(held(&f, b"ix"), (2, 2));
21527        f.run(&[b"RENAME", b"p:1", b"p:2"]);
21528        assert_eq!(held(&f, b"ix"), (1, 2));
21529        assert_eq!(number(&f, b"ix", b"p:2"), Some(1));
21530    }
21531
21532    /// A key that arrives under the prefix by being copied or restored is read
21533    /// as a new document, and one that is written over by something that is not
21534    /// a hash is erased without a word.
21535    #[test]
21536    fn a_key_that_arrives_under_the_prefix_is_read_and_one_overwritten_is_erased() {
21537        let mut f = indexed();
21538        f.run(&[b"HSET", b"q:1", b"t", b"beta"]);
21539        f.run(&[b"COPY", b"q:1", b"p:2"]);
21540        assert_eq!(held(&f, b"ix"), (2, 2));
21541        assert_eq!(number(&f, b"ix", b"p:2"), Some(2));
21542
21543        // Out of the prefix, where the source keeps the document it had.
21544        f.run(&[b"COPY", b"p:1", b"q:2"]);
21545        assert_eq!(held(&f, b"ix"), (2, 2));
21546
21547        // Over a key that has one, which is a new reading and not a rename.
21548        f.run(&[b"COPY", b"q:1", b"p:1", b"REPLACE"]);
21549        assert_eq!(held(&f, b"ix"), (2, 3));
21550        assert_eq!(number(&f, b"ix", b"p:1"), Some(3));
21551
21552        // And a string landing on top of a document takes it away, spending no
21553        // number and counting no failure.
21554        f.run(&[b"SET", b"s:1", b"plain"]);
21555        f.run(&[b"COPY", b"s:1", b"p:1", b"REPLACE"]);
21556        assert_eq!(held(&f, b"ix"), (1, 3));
21557        let dump = f.run(&[b"DUMP", b"q:1"]);
21558        assert!(dump.starts_with('$'), "{dump}");
21559    }
21560
21561    /// The keyspace group reads a key back on database zero whatever database
21562    /// the command ran on, which is measured and is not what the hash commands
21563    /// do. A `COPY` into another database indexes nothing and takes away
21564    /// whatever the destination had, and a `RESTORE` anywhere else is invisible.
21565    #[test]
21566    fn the_keyspace_group_reads_database_zero_whatever_database_it_ran_on() {
21567        let mut f = indexed();
21568        f.run(&[b"HSET", b"p:2", b"t", b"beta"]);
21569        assert_eq!(held(&f, b"ix"), (2, 2));
21570        // Into database one, so the indexes look for `p:2` on database zero,
21571        // find the one that is still there and read it again.
21572        f.run(&[b"COPY", b"p:1", b"p:2", b"DB", b"1", b"REPLACE"]);
21573        assert_eq!(held(&f, b"ix"), (2, 3));
21574        // And with nothing under that name on database zero, the copy leaves
21575        // the index one document lighter than it found it.
21576        f.run(&[b"DEL", b"p:2"]);
21577        assert_eq!(held(&f, b"ix"), (1, 3));
21578        f.run(&[b"COPY", b"p:1", b"p:2", b"DB", b"1", b"REPLACE"]);
21579        assert_eq!(held(&f, b"ix"), (1, 3), "the copy landed out of sight");
21580
21581        // A restore on another database is the same story.
21582        let dump = f.run(&[b"DUMP", b"p:1"]);
21583        assert!(dump.starts_with('$'), "{dump}");
21584        f.run(&[b"SELECT", b"1"]);
21585        f.run(&[b"HSET", b"q:1", b"t", b"gamma"]);
21586        f.run(&[b"RENAME", b"q:1", b"p:3"]);
21587        assert_eq!(held(&f, b"ix"), (1, 3), "and so is a rename");
21588    }
21589
21590    /// `MOVE` is not a change at all, because an index follows a key by name
21591    /// and a write on any database still reaches it.
21592    #[test]
21593    fn a_move_leaves_the_document_where_it_is() {
21594        let mut f = indexed();
21595        f.run(&[b"MOVE", b"p:1", b"1"]);
21596        assert_eq!(held(&f, b"ix"), (1, 1), "the key moved and nothing else");
21597        assert_eq!(number(&f, b"ix", b"p:1"), Some(1));
21598
21599        f.run(&[b"SELECT", b"1"]);
21600        f.run(&[b"HSET", b"p:1", b"t", b"beta"]);
21601        assert_eq!(held(&f, b"ix"), (1, 2), "and a write there still lands");
21602        f.run(&[b"DEL", b"p:1"]);
21603        assert_eq!(held(&f, b"ix"), (0, 2));
21604    }
21605
21606    /// A flush takes every index with it, whichever database it flushed.
21607    #[test]
21608    fn a_flush_drops_the_indexes() {
21609        for flush in [b"FLUSHALL".as_slice(), b"FLUSHDB"] {
21610            let mut f = indexed();
21611            f.run(&[flush]);
21612            assert!(f.server.search.lock().is_empty(), "{flush:?} kept an index");
21613            assert_eq!(f.run(&[b"FT._LIST"]), "*0\r\n");
21614        }
21615
21616        // Even on a database no index ever read, which is what a real server
21617        // does and is not what anyone would guess.
21618        let mut f = indexed();
21619        f.run(&[b"SELECT", b"9"]);
21620        f.run(&[b"FLUSHDB"]);
21621        assert!(f.server.search.lock().is_empty());
21622    }
21623
21624    /// An index whose schema has one tag field of each kind, plus a number so
21625    /// there is something for `FT.TAGVALS` to refuse.
21626    fn tagged() -> Fixture {
21627        let mut f = Fixture::new();
21628        f.run(&[
21629            b"FT.CREATE",
21630            b"tv",
21631            b"PREFIX",
21632            b"1",
21633            b"tv:",
21634            b"SCHEMA",
21635            b"g",
21636            b"AS",
21637            b"gg",
21638            b"TAG",
21639            b"h",
21640            b"TAG",
21641            b"SEPARATOR",
21642            b"|",
21643            b"CASESENSITIVE",
21644            b"n",
21645            b"NUMERIC",
21646        ]);
21647        f.run(&[
21648            b"HSET",
21649            b"tv:1",
21650            b"g",
21651            b"Red, BLUE ",
21652            b"h",
21653            b"Aa|bB",
21654            b"n",
21655            b"1",
21656        ]);
21657        f.run(&[b"HSET", b"tv:2", b"g", b"red", b"h", b"aa", b"n", b"2"]);
21658        f
21659    }
21660
21661    /// The values come back as they are stored, so an ordinary tag field
21662    /// answers them folded and trimmed and a `CASESENSITIVE` one answers what
21663    /// it was given. Byte order either way, which puts the capital first.
21664    #[test]
21665    fn tag_values_come_back_as_they_are_stored_and_sorted_by_their_bytes() {
21666        let mut f = tagged();
21667        assert_eq!(
21668            f.run(&[b"FT.TAGVALS", b"tv", b"gg"]),
21669            "*2\r\n$4\r\nblue\r\n$3\r\nred\r\n"
21670        );
21671        assert_eq!(
21672            f.run(&[b"FT.TAGVALS", b"tv", b"h"]),
21673            "*3\r\n$2\r\nAa\r\n$2\r\naa\r\n$2\r\nbB\r\n"
21674        );
21675    }
21676
21677    /// The name asked about is the attribute, so the identifier of a field
21678    /// declared `AS` is not a name this knows.
21679    #[test]
21680    fn tag_values_are_asked_for_by_the_attribute_and_not_the_identifier() {
21681        let mut f = tagged();
21682        for (name, want) in [
21683            (b"g".as_slice(), "-SEARCH_ATTR_BAD No such field\r\n"),
21684            (b"zz", "-SEARCH_ATTR_BAD No such field\r\n"),
21685            (b"n", "-SEARCH_ATTR_BAD Not a tag field\r\n"),
21686        ] {
21687            assert_eq!(f.run(&[b"FT.TAGVALS", b"tv", name]), want);
21688        }
21689        assert_eq!(
21690            f.run(&[b"FT.TAGVALS", b"nope", b"g"]),
21691            "-SEARCH_INDEX_NOT_FOUND Index not found: nope\r\n"
21692        );
21693    }
21694
21695    /// Looking up the index counts as a use of it on the roads that refuse the
21696    /// field as well as on the one that answers, which is measured.
21697    #[test]
21698    fn asking_for_tag_values_counts_a_use_of_the_index() {
21699        let mut f = tagged();
21700        let uses = |f: &mut Fixture| {
21701            let reply = f.run(&[b"FT.INFO", b"tv"]);
21702            let at = reply.find("number_of_uses").expect("the field is reported");
21703            let value = reply[at..].split("\r\n").nth(1).unwrap();
21704            value.trim_start_matches(':').parse::<i64>().unwrap()
21705        };
21706        let before = uses(&mut f);
21707        f.run(&[b"FT.TAGVALS", b"tv", b"gg"]);
21708        f.run(&[b"FT.TAGVALS", b"tv", b"zz"]);
21709        // Three more than before: two tag lookups and the second `FT.INFO`.
21710        assert_eq!(uses(&mut f), before + 3);
21711    }
21712
21713    /// A tag field nothing was ever written to has no list at all, which
21714    /// answers the same empty set a list that has been emptied does.
21715    #[test]
21716    fn a_tag_field_with_nothing_in_it_answers_empty() {
21717        let mut f = Fixture::new();
21718        f.run(&[b"FT.CREATE", b"e", b"SCHEMA", b"g", b"TAG"]);
21719        assert_eq!(f.run(&[b"FT.TAGVALS", b"e", b"g"]), "*0\r\n");
21720    }
21721
21722    /// A dictionary is module state and not a key, so nothing in the keyspace
21723    /// can see one.
21724    #[test]
21725    fn a_dictionary_is_not_a_key() {
21726        let mut f = Fixture::new();
21727        assert_eq!(f.run(&[b"FT.DICTADD", b"d", b"a", b"b"]), ":2\r\n");
21728        assert_eq!(f.run(&[b"TYPE", b"d"]), "+none\r\n");
21729        assert_eq!(f.run(&[b"EXISTS", b"d"]), ":0\r\n");
21730        assert_eq!(f.run(&[b"KEYS", b"d"]), "*0\r\n");
21731    }
21732
21733    /// The count is how many terms were new, an empty term is not a term, and
21734    /// the dump is sorted by bytes rather than folded.
21735    #[test]
21736    fn a_dictionary_counts_the_terms_it_had_not_seen() {
21737        let mut f = Fixture::new();
21738        assert_eq!(
21739            f.run(&[b"FT.DICTADD", b"d", b"zeta", b"alpha", b"Beta", b"alpha"]),
21740            ":3\r\n"
21741        );
21742        assert_eq!(f.run(&[b"FT.DICTADD", b"d", b"alpha"]), ":0\r\n");
21743        assert_eq!(f.run(&[b"FT.DICTADD", b"d", b""]), ":0\r\n");
21744        assert_eq!(
21745            f.run(&[b"FT.DICTDUMP", b"d"]),
21746            "*3\r\n$4\r\nBeta\r\n$5\r\nalpha\r\n$4\r\nzeta\r\n"
21747        );
21748        assert_eq!(f.run(&[b"FT.DICTDEL", b"d", b"alpha", b"nope"]), ":1\r\n");
21749    }
21750
21751    /// A name nobody ever added to is not an error on either of the two
21752    /// commands that will take one, which is the only place in the group where
21753    /// a missing name is forgiven.
21754    #[test]
21755    fn a_dictionary_nobody_made_dumps_empty_rather_than_failing() {
21756        let mut f = Fixture::new();
21757        assert_eq!(f.run(&[b"FT.DICTDUMP", b"nope"]), "*0\r\n");
21758        assert_eq!(f.run(&[b"FT.DICTDEL", b"nope", b"a"]), ":0\r\n");
21759    }
21760
21761    /// The dictionaries go when the keyspace does, the same way the indexes do.
21762    #[test]
21763    fn a_flush_drops_the_dictionaries() {
21764        for flush in [b"FLUSHALL".as_slice(), b"FLUSHDB"] {
21765            let mut f = Fixture::new();
21766            f.run(&[b"FT.DICTADD", b"d", b"a"]);
21767            f.run(&[flush]);
21768            assert_eq!(f.run(&[b"FT.DICTDUMP", b"d"]), "*0\r\n", "{flush:?}");
21769        }
21770    }
21771
21772    // -------------------------------------------------------------- profile
21773
21774    /// A fixture holding one index over three documents, two of which hold the
21775    /// first word and two the second.
21776    fn profiling() -> Fixture {
21777        let mut f = Fixture::new();
21778        f.run(&[
21779            b"FT.CREATE",
21780            b"ix",
21781            b"PREFIX",
21782            b"1",
21783            b"p:",
21784            b"SCHEMA",
21785            b"t",
21786            b"TEXT",
21787            b"n",
21788            b"NUMERIC",
21789        ]);
21790        f.run(&[b"HSET", b"p:1", b"t", b"alpha", b"n", b"1"]);
21791        f.run(&[b"HSET", b"p:2", b"t", b"alpha beta", b"n", b"2"]);
21792        f.run(&[b"HSET", b"p:3", b"t", b"beta", b"n", b"3"]);
21793        f
21794    }
21795
21796    /// The reply with every time taken out of it, since no two runs agree on
21797    /// those and everything else about a profile is exact.
21798    fn timeless(reply: &str) -> String {
21799        const KEYS: &[&str] = &[
21800            "+Total profile time",
21801            "+Parsing time",
21802            "+Workers queue time",
21803            "+Pipeline creation time",
21804            "+Time",
21805        ];
21806        let mut out = String::new();
21807        let mut parts = reply.split("\r\n").peekable();
21808        while let Some(part) = parts.next() {
21809            out.push_str(part);
21810            out.push_str("\r\n");
21811            if !KEYS.contains(&part) {
21812                continue;
21813            }
21814            // A double is one line on RESP3 and a bulk header and its digits on
21815            // RESP2, and both of them stand for the same one value.
21816            match parts.next() {
21817                Some(head) if head.starts_with('$') => {
21818                    parts.next();
21819                }
21820                _ => {}
21821            }
21822            out.push_str("<t>\r\n");
21823        }
21824        // The split leaves an empty piece past the last line ending.
21825        out.truncate(out.len() - 2);
21826        out
21827    }
21828
21829    /// The whole envelope on both protocols, which is a two element array on
21830    /// one and a two key map on the other.
21831    #[test]
21832    fn a_profile_wraps_the_reply_it_would_have_answered_anyway() {
21833        let mut f = profiling();
21834        assert_eq!(
21835            timeless(&f.run(&[b"FT.PROFILE", b"ix", b"SEARCH", b"QUERY", b"alpha"])),
21836            "*2\r\n\
21837             *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\
21838             $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\
21839             *4\r\n+Shards\r\n*1\r\n*14\r\n\
21840             +Total profile time\r\n<t>\r\n+Parsing time\r\n<t>\r\n\
21841             +Workers queue time\r\n<t>\r\n+Pipeline creation time\r\n<t>\r\n\
21842             +Warning\r\n*1\r\n+None\r\n\
21843             +Iterators profile\r\n*10\r\n+Type\r\n+TEXT\r\n+Term\r\n$5\r\nalpha\r\n\
21844             +Time\r\n<t>\r\n+Number of reading operations\r\n:2\r\n\
21845             +Estimated number of matches\r\n:2\r\n\
21846             +Result processors profile\r\n*4\r\n\
21847             *6\r\n+Type\r\n+Index\r\n+Time\r\n<t>\r\n+Results processed\r\n:2\r\n\
21848             *6\r\n+Type\r\n+Scorer\r\n+Time\r\n<t>\r\n+Results processed\r\n:2\r\n\
21849             *6\r\n+Type\r\n+Sorter\r\n+Time\r\n<t>\r\n+Results processed\r\n:2\r\n\
21850             *6\r\n+Type\r\n+Loader\r\n+Time\r\n<t>\r\n+Results processed\r\n:2\r\n\
21851             +Coordinator\r\n*0\r\n"
21852        );
21853        let mut g = profiling();
21854        g.run(&[b"HELLO", b"3"]);
21855        let three = timeless(&g.run(&[b"FT.PROFILE", b"ix", b"SEARCH", b"QUERY", b"alpha"]));
21856        assert!(three.starts_with("%2\r\n+Results\r\n"), "{three}");
21857        assert!(
21858            three.contains("+Profile\r\n%2\r\n+Shards\r\n*1\r\n%7\r\n"),
21859            "{three}"
21860        );
21861        assert!(three.ends_with("+Coordinator\r\n%0\r\n"), "{three}");
21862        assert!(
21863            three.contains(
21864                "+Iterators profile\r\n%5\r\n+Type\r\n+TEXT\r\n+Term\r\n$5\r\nalpha\r\n\
21865                 +Time\r\n<t>\r\n+Number of reading operations\r\n:2\r\n\
21866                 +Estimated number of matches\r\n:2\r\n"
21867            ),
21868            "{three}"
21869        );
21870    }
21871
21872    /// Every kind of step names itself, and the three that hold other steps say
21873    /// so in the singular or the plural depending on how many they hold.
21874    #[test]
21875    fn each_kind_of_step_writes_the_keys_that_belong_to_it() {
21876        let mut f = profiling();
21877        let tree = |f: &mut Fixture, query: &[u8]| {
21878            let reply = timeless(&f.run(&[b"FT.PROFILE", b"ix", b"SEARCH", b"QUERY", query]));
21879            let at = reply.find("+Iterators profile").expect("a tree");
21880            let end = reply.find("+Result processors").expect("a list of steps");
21881            reply[at..end].to_string()
21882        };
21883        assert_eq!(
21884            tree(&mut f, b"alpha beta"),
21885            "+Iterators profile\r\n*8\r\n+Type\r\n+INTERSECT\r\n+Time\r\n<t>\r\n\
21886             +Number of reading operations\r\n:1\r\n+Child iterators\r\n*2\r\n\
21887             *10\r\n+Type\r\n+TEXT\r\n+Term\r\n$5\r\nalpha\r\n+Time\r\n<t>\r\n\
21888             +Number of reading operations\r\n:2\r\n+Estimated number of matches\r\n:2\r\n\
21889             *10\r\n+Type\r\n+TEXT\r\n+Term\r\n$4\r\nbeta\r\n+Time\r\n<t>\r\n\
21890             +Number of reading operations\r\n:1\r\n+Estimated number of matches\r\n:2\r\n"
21891        );
21892        assert!(tree(&mut f, b"alpha|beta").starts_with(
21893            "+Iterators profile\r\n*10\r\n+Type\r\n+UNION\r\n+Query type\r\n+UNION\r\n\
21894             +Time\r\n<t>\r\n+Number of reading operations\r\n:3\r\n+Child iterators\r\n*2\r\n"
21895        ));
21896        // One thing under it, named in the singular, which is a different key
21897        // and not a list holding one.
21898        assert!(tree(&mut f, b"-alpha").starts_with(
21899            "+Iterators profile\r\n*8\r\n+Type\r\n+NOT\r\n+Time\r\n<t>\r\n\
21900             +Number of reading operations\r\n:1\r\n+Child iterator\r\n*10\r\n"
21901        ));
21902        assert!(tree(&mut f, b"~alpha").starts_with(
21903            "+Iterators profile\r\n*8\r\n+Type\r\n+OPTIONAL\r\n+Time\r\n<t>\r\n\
21904             +Number of reading operations\r\n:3\r\n+Child iterator\r\n*10\r\n"
21905        ));
21906        // No guess at how many, which is the one leaf that leaves it off.
21907        assert_eq!(
21908            tree(&mut f, b"*"),
21909            "+Iterators profile\r\n*6\r\n+Type\r\n+WILDCARD\r\n+Time\r\n<t>\r\n\
21910             +Number of reading operations\r\n:3\r\n"
21911        );
21912        assert!(tree(&mut f, b"@n:[1 2]").starts_with(
21913            "+Iterators profile\r\n*10\r\n+Type\r\n+NUMERIC\r\n+Term\r\n\
21914             $19\r\n1.000000 - 2.000000\r\n"
21915        ));
21916    }
21917
21918    /// A union an expansion made folds into a count of its branches and a union
21919    /// a client wrote with a bar does not.
21920    #[test]
21921    fn limited_folds_the_branches_an_expansion_made_and_leaves_a_bar_alone() {
21922        let mut f = profiling();
21923        f.run(&[b"HSET", b"p:4", b"t", b"alps"]);
21924        let tree = |f: &mut Fixture, words: &[&[u8]]| {
21925            let mut argv: Vec<&[u8]> = vec![b"FT.PROFILE", b"ix", b"SEARCH"];
21926            argv.extend_from_slice(words);
21927            let reply = timeless(&f.run(&argv));
21928            let at = reply.find("+Iterators profile").expect("a tree");
21929            let end = reply.find("+Result processors").expect("a list of steps");
21930            reply[at..end].to_string()
21931        };
21932        assert_eq!(
21933            tree(&mut f, &[b"LIMITED", b"QUERY", b"al*"]),
21934            "+Iterators profile\r\n*10\r\n+Type\r\n+UNION\r\n\
21935             +Query type\r\n$11\r\nPREFIX - al\r\n+Time\r\n<t>\r\n\
21936             +Number of reading operations\r\n:3\r\n+Child iterators\r\n\
21937             +The number of iterators in the union is 2\r\n"
21938        );
21939        assert!(tree(&mut f, &[b"QUERY", b"al*"]).contains("+Child iterators\r\n*2\r\n"));
21940        assert!(
21941            tree(&mut f, &[b"LIMITED", b"QUERY", b"alpha|beta"])
21942                .contains("+Child iterators\r\n*2\r\n")
21943        );
21944        // A union that says nothing but its own name says it as a status, and
21945        // one that says what it stood for says that as a string. Measured, and
21946        // it is the one place in this reply where the two are told apart.
21947        assert!(tree(&mut f, &[b"QUERY", b"alpha|beta"]).contains("+Query type\r\n+UNION\r\n"));
21948        assert!(
21949            tree(&mut f, &[b"QUERY", b"al*"]).contains("+Query type\r\n$11\r\nPREFIX - al\r\n")
21950        );
21951    }
21952
21953    /// Which steps a search runs the rows through, which turns on the window,
21954    /// on whether anything asked for the fields and on what the order is.
21955    #[test]
21956    fn the_steps_a_search_runs_depend_on_what_was_asked_for() {
21957        let mut f = profiling();
21958        let steps = |f: &mut Fixture, words: &[&[u8]]| {
21959            let mut argv: Vec<&[u8]> = vec![b"FT.PROFILE", b"ix", b"SEARCH", b"QUERY", b"alpha"];
21960            argv.extend_from_slice(words);
21961            let reply = timeless(&f.run(&argv));
21962            let at = reply.find("+Result processors").expect("a list of steps");
21963            let end = reply.find("+Coordinator").expect("an end");
21964            let mut out = Vec::new();
21965            let mut parts = reply[at..end].split("\r\n").peekable();
21966            while let Some(part) = parts.next() {
21967                if part == "+Type" {
21968                    out.push(parts.next().unwrap_or_default().to_string());
21969                }
21970            }
21971            out
21972        };
21973        assert_eq!(
21974            steps(&mut f, &[]),
21975            ["+Index", "+Scorer", "+Sorter", "+Loader"]
21976        );
21977        assert_eq!(
21978            steps(&mut f, &[b"NOCONTENT"]),
21979            ["+Index", "+Scorer", "+Sorter"]
21980        );
21981        // A window of nothing is a client asking for the total and nothing
21982        // else, so nothing is scored and nothing is sorted.
21983        assert_eq!(
21984            steps(&mut f, &[b"LIMIT", b"0", b"0"]),
21985            ["+Index", "+Counter"]
21986        );
21987        // A sort by a field does not need a score, and asking for the scores
21988        // puts the step back.
21989        assert_eq!(
21990            steps(&mut f, &[b"SORTBY", b"n"]),
21991            ["+Index", "+Sorter", "+Loader"]
21992        );
21993        assert_eq!(
21994            steps(&mut f, &[b"SORTBY", b"n", b"WITHSCORES"]),
21995            ["+Index", "+Scorer", "+Sorter", "+Loader"]
21996        );
21997        assert_eq!(
21998            steps(&mut f, &[b"HIGHLIGHT"]),
21999            ["+Index", "+Scorer", "+Sorter", "+Loader", "+Highlighter"]
22000        );
22001        assert_eq!(
22002            steps(&mut f, &[b"SUMMARIZE", b"NOCONTENT"]),
22003            ["+Index", "+Scorer", "+Sorter"]
22004        );
22005    }
22006
22007    /// A pipeline names each of its steps after the expression it runs, which
22008    /// is what a real server prints beside them.
22009    #[test]
22010    fn a_pipeline_names_every_step_after_what_it_runs() {
22011        let mut f = profiling();
22012        let steps = |f: &mut Fixture, words: &[&[u8]]| {
22013            let mut argv: Vec<&[u8]> = vec![b"FT.PROFILE", b"ix", b"AGGREGATE", b"QUERY", b"*"];
22014            argv.extend_from_slice(words);
22015            let reply = timeless(&f.run(&argv));
22016            let at = reply.find("+Result processors").expect("a list of steps");
22017            let end = reply.find("+Coordinator").expect("an end");
22018            let mut out = Vec::new();
22019            let mut parts = reply[at..end].split("\r\n").peekable();
22020            while let Some(part) = parts.next() {
22021                if part == "+Type" {
22022                    out.push(parts.next().unwrap_or_default().to_string());
22023                }
22024            }
22025            out
22026        };
22027        assert_eq!(steps(&mut f, &[]), ["+Index"]);
22028        assert_eq!(
22029            steps(&mut f, &[b"APPLY", b"1", b"AS", b"one"]),
22030            ["+Index", "+Projector - Literal 1"]
22031        );
22032        assert_eq!(
22033            steps(
22034                &mut f,
22035                &[b"LOAD", b"1", b"@n", b"APPLY", b"@n * 2", b"AS", b"d"]
22036            ),
22037            ["+Index", "+Loader", "+Projector - Operator *"]
22038        );
22039        assert_eq!(
22040            steps(&mut f, &[b"LOAD", b"1", b"@n", b"FILTER", b"@n > 1"]),
22041            ["+Index", "+Loader", "+Filter - Predicate >"]
22042        );
22043        assert_eq!(
22044            steps(
22045                &mut f,
22046                &[b"GROUPBY", b"1", b"@n", b"REDUCE", b"COUNT", b"0"]
22047            ),
22048            ["+Index", "+Loader", "+Grouper"]
22049        );
22050        assert_eq!(
22051            steps(&mut f, &[b"SORTBY", b"1", b"@n"]),
22052            ["+Index", "+Loader", "+Sorter"]
22053        );
22054        assert_eq!(
22055            steps(&mut f, &[b"LIMIT", b"0", b"2"]),
22056            ["+Index", "+Pager/Limiter"]
22057        );
22058        // Asking for the score by name is a step of its own, and it goes in
22059        // front of the read rather than after it.
22060        assert_eq!(
22061            steps(
22062                &mut f,
22063                &[
22064                    b"ADDSCORES",
22065                    b"LOAD",
22066                    b"1",
22067                    b"@n",
22068                    b"APPLY",
22069                    b"@__score",
22070                    b"AS",
22071                    b"s"
22072                ]
22073            ),
22074            [
22075                "+Index",
22076                "+Scorer",
22077                "+Loader",
22078                "+Projector - Property __score"
22079            ]
22080        );
22081    }
22082
22083    /// A field the schema marked sortable is held beside the document number,
22084    /// so a pipeline that only names those never opens a key and never reports
22085    /// a read.
22086    ///
22087    /// Measured: on a schema of `n NUMERIC SORTABLE g TAG`, `LOAD 1 @n` has no
22088    /// `Loader` step and `LOAD 1 @g` has one. So does `LOAD *`, because what a
22089    /// key turns out to hold is not knowable without opening it.
22090    #[test]
22091    fn a_sortable_field_is_read_without_the_key_being_opened() {
22092        let mut f = Fixture::new();
22093        f.run(&[
22094            b"FT.CREATE",
22095            b"sx",
22096            b"PREFIX",
22097            b"1",
22098            b"s:",
22099            b"SCHEMA",
22100            b"n",
22101            b"NUMERIC",
22102            b"SORTABLE",
22103            b"g",
22104            b"TAG",
22105        ]);
22106        f.run(&[b"HSET", b"s:1", b"n", b"1", b"g", b"one"]);
22107        f.run(&[b"HSET", b"s:2", b"n", b"2", b"g", b"two"]);
22108        let loads = |f: &mut Fixture, words: &[&[u8]]| {
22109            let mut argv: Vec<&[u8]> = vec![b"FT.PROFILE", b"sx", b"AGGREGATE", b"QUERY", b"*"];
22110            argv.extend_from_slice(words);
22111            f.run(&argv).contains("+Loader")
22112        };
22113        assert!(!loads(&mut f, &[b"LOAD", b"1", b"@n"]));
22114        assert!(!loads(&mut f, &[b"SORTBY", b"1", b"@n"]));
22115        assert!(!loads(&mut f, &[b"APPLY", b"@n * 2", b"AS", b"d"]));
22116        assert!(loads(&mut f, &[b"LOAD", b"1", b"@g"]));
22117        assert!(loads(&mut f, &[b"LOAD", b"2", b"@n", b"@g"]));
22118        assert!(loads(
22119            &mut f,
22120            &[b"GROUPBY", b"1", b"@g", b"REDUCE", b"COUNT", b"0"]
22121        ));
22122        assert!(loads(&mut f, &[b"LOAD", b"*"]));
22123    }
22124
22125    /// The four ways the words can be wrong, none of which reaches the search
22126    /// underneath.
22127    #[test]
22128    fn a_profile_checks_its_own_words_before_it_runs_anything() {
22129        let mut f = profiling();
22130        assert_eq!(
22131            f.run(&[b"FT.PROFILE", b"ix", b"SEARCH", b"QUERY"]),
22132            "-ERR wrong number of arguments for 'FT.PROFILE' command\r\n"
22133        );
22134        assert_eq!(
22135            f.run(&[b"FT.PROFILE", b"ix", b"BOGUS", b"QUERY", b"alpha"]),
22136            "-No `SEARCH`, `AGGREGATE`, or `HYBRID` provided\r\n"
22137        );
22138        // The word goes between the two and nowhere else, so one written in
22139        // front of them is not the word at all.
22140        assert_eq!(
22141            f.run(&[
22142                b"FT.PROFILE",
22143                b"ix",
22144                b"LIMITED",
22145                b"SEARCH",
22146                b"QUERY",
22147                b"alpha"
22148            ]),
22149            "-No `SEARCH`, `AGGREGATE`, or `HYBRID` provided\r\n"
22150        );
22151        assert_eq!(
22152            f.run(&[b"FT.PROFILE", b"ix", b"SEARCH", b"BOGUS", b"alpha"]),
22153            "-The QUERY keyword is expected\r\n"
22154        );
22155        assert_eq!(
22156            f.run(&[
22157                b"FT.PROFILE",
22158                b"ix",
22159                b"AGGREGATE",
22160                b"QUERY",
22161                b"alpha",
22162                b"WITHCURSOR"
22163            ]),
22164            "-FT.PROFILE does not support cursor\r\n"
22165        );
22166        // And what the search itself complains about comes back on its own,
22167        // without an envelope around it saying the command worked.
22168        assert_eq!(
22169            f.run(&[b"FT.PROFILE", b"nope", b"SEARCH", b"QUERY", b"alpha"]),
22170            "-SEARCH_INDEX_NOT_FOUND Index not found: nope\r\n"
22171        );
22172        assert_eq!(
22173            f.run(&[
22174                b"FT.PROFILE",
22175                b"ix",
22176                b"SEARCH",
22177                b"QUERY",
22178                b"alpha",
22179                b"extra"
22180            ]),
22181            "-SEARCH_ARG_UNRECOGNIZED Unknown argument `extra` at position 1 for <main>\r\n"
22182        );
22183    }
22184
22185    /// Every word of the command's own is read without regard to case.
22186    #[test]
22187    fn the_words_of_a_profile_are_read_the_way_every_other_word_is() {
22188        let mut f = profiling();
22189        let one = f.run(&[
22190            b"FT.PROFILE",
22191            b"ix",
22192            b"search",
22193            b"limited",
22194            b"query",
22195            b"alpha",
22196        ]);
22197        let two = f.run(&[
22198            b"FT.PROFILE",
22199            b"ix",
22200            b"SEARCH",
22201            b"LIMITED",
22202            b"QUERY",
22203            b"alpha",
22204        ]);
22205        assert_eq!(timeless(&one), timeless(&two));
22206    }
22207
22208    // --------------------------------------------------------------- config
22209
22210    /// The two shapes a dump comes back in, which are the one mix of simple
22211    /// strings and bulk strings the group sends.
22212    #[test]
22213    fn a_setting_reads_back_as_a_pair_on_one_protocol_and_a_map_on_the_other() {
22214        let mut f = Fixture::new();
22215        assert_eq!(
22216            f.run(&[b"FT.CONFIG", b"GET", b"TIMEOUT"]),
22217            "*1\r\n*2\r\n+TIMEOUT\r\n$3\r\n500\r\n"
22218        );
22219        assert_eq!(
22220            f.run(&[b"FT.CONFIG", b"GET", b"EXTLOAD"]),
22221            "*1\r\n*2\r\n+EXTLOAD\r\n$-1\r\n"
22222        );
22223        let mut g = Fixture::new();
22224        g.run(&[b"HELLO", b"3"]);
22225        assert_eq!(
22226            g.run(&[b"FT.CONFIG", b"GET", b"TIMEOUT"]),
22227            "%1\r\n+TIMEOUT\r\n$3\r\n500\r\n"
22228        );
22229        assert_eq!(
22230            g.run(&[b"FT.CONFIG", b"GET", b"EXTLOAD"]),
22231            "%1\r\n+EXTLOAD\r\n_\r\n"
22232        );
22233    }
22234
22235    /// The help text rides along in the middle of the same row, flat on RESP2
22236    /// and as a map of its own on RESP3.
22237    #[test]
22238    fn a_help_row_carries_the_description_and_the_value_together() {
22239        let mut f = Fixture::new();
22240        assert_eq!(
22241            f.run(&[b"FT.CONFIG", b"HELP", b"TIMEOUT"]),
22242            "*1\r\n*5\r\n+TIMEOUT\r\n+Description\r\n+Query (search) timeout\r\n\
22243             +Value\r\n$3\r\n500\r\n"
22244        );
22245        let mut g = Fixture::new();
22246        g.run(&[b"HELLO", b"3"]);
22247        assert_eq!(
22248            g.run(&[b"FT.CONFIG", b"HELP", b"TIMEOUT"]),
22249            "%1\r\n+TIMEOUT\r\n%2\r\n+Description\r\n+Query (search) timeout\r\n\
22250             +Value\r\n$3\r\n500\r\n"
22251        );
22252    }
22253
22254    /// A name is matched whole, ignoring case, and the single word star is the
22255    /// only thing that means all of them.
22256    #[test]
22257    fn only_a_bare_star_asks_for_every_setting_and_nothing_else_globs() {
22258        let mut f = Fixture::new();
22259        assert_eq!(
22260            f.run(&[b"FT.CONFIG", b"GET", b"timeout"]),
22261            "*1\r\n*2\r\n+TIMEOUT\r\n$3\r\n500\r\n"
22262        );
22263        for name in [
22264            b"TIMEOUT*".as_slice(),
22265            b"?IMEOUT",
22266            b"*TIMEOUT*",
22267            b"TIME",
22268            b"NOSUCH",
22269            b"",
22270        ] {
22271            assert_eq!(f.run(&[b"FT.CONFIG", b"GET", name]), "*0\r\n", "{name:?}");
22272        }
22273        assert!(f.run(&[b"FT.CONFIG", b"GET", b"*"]).starts_with("*69\r\n"));
22274        assert!(f.run(&[b"FT.CONFIG", b"HELP", b"*"]).starts_with("*69\r\n"));
22275    }
22276
22277    /// Words after the name are stepped over rather than refused, on both of
22278    /// the two reads.
22279    #[test]
22280    fn a_read_ignores_whatever_follows_the_name() {
22281        let mut f = Fixture::new();
22282        assert_eq!(
22283            f.run(&[b"FT.CONFIG", b"GET", b"timeout", b"extra", b"more"]),
22284            "*1\r\n*2\r\n+TIMEOUT\r\n$3\r\n500\r\n"
22285        );
22286        assert_eq!(
22287            f.run(&[b"FT.CONFIG", b"HELP", b"timeout", b"extra"]),
22288            "*1\r\n*5\r\n+TIMEOUT\r\n+Description\r\n+Query (search) timeout\r\n\
22289             +Value\r\n$3\r\n500\r\n"
22290        );
22291    }
22292
22293    /// The container reports its own name and the subcommand it was given in
22294    /// the two lines the dispatcher writes.
22295    #[test]
22296    fn a_missing_subcommand_and_a_missing_name_are_told_apart() {
22297        let mut f = Fixture::new();
22298        assert_eq!(
22299            f.run(&[b"FT.CONFIG"]),
22300            "-ERR wrong number of arguments for 'FT.CONFIG' command\r\n"
22301        );
22302        for sub in [b"GET".as_slice(), b"SET", b"HELP"] {
22303            let want = format!(
22304                "-ERR wrong number of arguments for 'FT.CONFIG|{}' command\r\n",
22305                String::from_utf8_lossy(sub)
22306            );
22307            assert_eq!(f.run(&[b"FT.CONFIG", sub]), want);
22308        }
22309        assert_eq!(
22310            f.run(&[b"ft.config", b"get"]),
22311            "-ERR wrong number of arguments for 'FT.CONFIG|GET' command\r\n"
22312        );
22313        assert_eq!(
22314            f.run(&[b"FT.CONFIG", b"bogus"]),
22315            "-ERR unknown subcommand 'bogus'. Try FT.CONFIG HELP.\r\n"
22316        );
22317    }
22318
22319    /// The name, then whether it can move, then the value, then the count of
22320    /// words, and each of the first three answers before the next is looked at.
22321    #[test]
22322    fn a_write_checks_the_name_then_the_setting_then_the_value() {
22323        let mut f = Fixture::new();
22324        for tail in [vec![b"1".as_slice()], vec![], vec![b"1", b"2", b"3"]] {
22325            let mut cmd: Vec<&[u8]> = vec![b"FT.CONFIG", b"SET", b"NOSUCH"];
22326            cmd.extend(tail);
22327            assert_eq!(f.run(&cmd), "-SEARCH_OPTION_INVALID Invalid option\r\n");
22328        }
22329        for tail in [vec![b"1000".as_slice()], vec![], vec![b"x", b"y"]] {
22330            let mut cmd: Vec<&[u8]> = vec![b"FT.CONFIG", b"SET", b"MAXDOCTABLESIZE"];
22331            cmd.extend(tail);
22332            assert_eq!(
22333                f.run(&cmd),
22334                "-SEARCH_OPTION_BAD Not modifiable at runtime\r\n"
22335            );
22336        }
22337        assert_eq!(
22338            f.run(&[b"FT.CONFIG", b"SET", b"TIMEOUT", b"x", b"y", b"z"]),
22339            "-SEARCH_PARSE_ARGS Could not convert argument to expected type\r\n"
22340        );
22341    }
22342
22343    /// Too many words is a status and not an error, and the value has already
22344    /// been written by the time it goes out.
22345    #[test]
22346    fn an_excess_of_words_is_noticed_after_the_value_is_kept() {
22347        let mut f = Fixture::new();
22348        assert_eq!(
22349            f.run(&[b"FT.CONFIG", b"SET", b"TIMEOUT", b"500"]),
22350            "+OK\r\n"
22351        );
22352        assert_eq!(
22353            f.run(&[b"FT.CONFIG", b"SET", b"TIMEOUT", b"600", b"junk"]),
22354            "+EXCESSARGS\r\n"
22355        );
22356        assert_eq!(
22357            f.run(&[b"FT.CONFIG", b"GET", b"TIMEOUT"]),
22358            "*1\r\n*2\r\n+TIMEOUT\r\n$3\r\n600\r\n"
22359        );
22360    }
22361
22362    /// Strictly first and loosely second, so a hexadecimal and a leading zero
22363    /// and an exponent all land and a fraction does not.
22364    #[test]
22365    fn a_number_is_read_the_strict_way_and_then_the_loose_one() {
22366        let mut f = Fixture::new();
22367        for (given, want) in [
22368            (b"0x10".as_slice(), "16"),
22369            (b"0X1f", "31"),
22370            (b"+0x10", "16"),
22371            (b"+5", "5"),
22372            (b"010", "10"),
22373            (b"08", "8"),
22374            (b"0777", "777"),
22375            (b"1e3", "1000"),
22376            (b"0.0", "0"),
22377            (b"-0.0", "0"),
22378        ] {
22379            assert_eq!(
22380                f.run(&[b"FT.CONFIG", b"SET", b"TIMEOUT", given]),
22381                "+OK\r\n",
22382                "{given:?}"
22383            );
22384            let want = format!("*1\r\n*2\r\n+TIMEOUT\r\n${}\r\n{want}\r\n", want.len());
22385            assert_eq!(
22386                f.run(&[b"FT.CONFIG", b"GET", b"TIMEOUT"]),
22387                want,
22388                "{given:?}"
22389            );
22390        }
22391        for given in [
22392            b" 5".as_slice(),
22393            b"5 ",
22394            b"1.5",
22395            b"1e-3",
22396            b"x",
22397            b"",
22398            b"0b11",
22399            b"0xg",
22400            b"nan",
22401            b"inf",
22402            b"1e100",
22403            b"99999999999999999999",
22404        ] {
22405            assert_eq!(
22406                f.run(&[b"FT.CONFIG", b"SET", b"TIMEOUT", given]),
22407                "-SEARCH_PARSE_ARGS Could not convert argument to expected type\r\n",
22408                "{given:?}"
22409            );
22410        }
22411    }
22412
22413    /// Which of the two readers found a negative decides what it is told, and
22414    /// on a setting with no range at all neither of them is refused.
22415    #[test]
22416    fn a_negative_is_answered_by_whichever_reader_found_it() {
22417        let mut f = Fixture::new();
22418        for given in [b"-1".as_slice(), b"-16"] {
22419            assert_eq!(
22420                f.run(&[b"FT.CONFIG", b"SET", b"TIMEOUT", given]),
22421                "-SEARCH_PARSE_ARGS Value is outside acceptable bounds\r\n",
22422                "{given:?}"
22423            );
22424        }
22425        for given in [b"-0x10".as_slice(), b"-1e3", b"-010", b"-2.0"] {
22426            assert_eq!(
22427                f.run(&[b"FT.CONFIG", b"SET", b"TIMEOUT", given]),
22428                "-SEARCH_PARSE_ARGS Could not convert argument to expected type\r\n",
22429                "{given:?}"
22430            );
22431        }
22432        let unlimited = "*1\r\n*2\r\n+MAXSEARCHRESULTS\r\n$9\r\nunlimited\r\n";
22433        for given in [b"-1".as_slice(), b"-0x10", b"-1e3", b"-010"] {
22434            assert_eq!(
22435                f.run(&[b"FT.CONFIG", b"SET", b"MAXSEARCHRESULTS", given]),
22436                "+OK\r\n",
22437                "{given:?}"
22438            );
22439            assert_eq!(
22440                f.run(&[b"FT.CONFIG", b"GET", b"MAXSEARCHRESULTS"]),
22441                unlimited,
22442                "{given:?}"
22443            );
22444        }
22445    }
22446
22447    /// The two settings with no range truncate into a signed thirty two bit
22448    /// slot and say so once the number has gone under.
22449    #[test]
22450    fn a_wide_setting_wraps_into_its_slot_before_it_is_read_back() {
22451        let mut f = Fixture::new();
22452        for (given, want) in [
22453            (b"2147483647".as_slice(), "2147483647"),
22454            (b"2147483648", "unlimited"),
22455            (b"4294967295", "unlimited"),
22456            (b"9223372036854775806", "unlimited"),
22457            (b"0", "0"),
22458        ] {
22459            assert_eq!(
22460                f.run(&[b"FT.CONFIG", b"SET", b"MAXSEARCHRESULTS", given]),
22461                "+OK\r\n",
22462                "{given:?}"
22463            );
22464            let want = format!(
22465                "*1\r\n*2\r\n+MAXSEARCHRESULTS\r\n${}\r\n{want}\r\n",
22466                want.len()
22467            );
22468            assert_eq!(
22469                f.run(&[b"FT.CONFIG", b"GET", b"MAXSEARCHRESULTS"]),
22470                want,
22471                "{given:?}"
22472            );
22473        }
22474    }
22475
22476    /// A number past what a setting will take says which way it went, and the
22477    /// ones with a softer roof of their own say what that roof is about.
22478    #[test]
22479    fn a_number_out_of_range_names_the_limit_it_crossed() {
22480        let mut f = Fixture::new();
22481        let bounds = "-SEARCH_PARSE_ARGS Value is outside acceptable bounds\r\n";
22482        for (name, given) in [
22483            (b"MINPREFIX".as_slice(), b"0".as_slice()),
22484            (b"MAX_AGGREGATE_GROUPS", b"0"),
22485            (b"BM25STD_TANH_FACTOR", b"0"),
22486            (b"DEFAULT_DIALECT", b"0"),
22487            (b"MINSTEMLEN", b"4294967296"),
22488            (b"_BG_INDEX_OOM_PAUSE_TIME", b"4294967296"),
22489            (b"INDEXER_YIELD_EVERY_OPS", b"4294967296"),
22490            (b"CONNECT_TIMEOUT", b"2147483648"),
22491        ] {
22492            assert_eq!(
22493                f.run(&[b"FT.CONFIG", b"SET", name, given]),
22494                bounds,
22495                "{name:?}"
22496            );
22497        }
22498        for (name, given, want) in [
22499            (
22500                b"MINSTEMLEN".as_slice(),
22501                b"1".as_slice(),
22502                "-SEARCH_SYNTAX Minimum stem length cannot be lower than 2\r\n",
22503            ),
22504            (
22505                b"MAX_AGGREGATE_GROUPS",
22506                b"67108865",
22507                "-SEARCH_LIMIT_OVER Value exceeds maximum possible aggregate groups\r\n",
22508            ),
22509            (
22510                b"WORKERS",
22511                b"17",
22512                "-SEARCH_LIMIT_OVER Number of worker threads cannot exceed 16\r\n",
22513            ),
22514            (
22515                b"_NUMERIC_RANGES_PARENTS",
22516                b"3",
22517                "-SEARCH_PARSE_ARGS Max depth for range cannot be higher than max \
22518                 depth for balance\r\n",
22519            ),
22520            (
22521                b"DEFAULT_DIALECT",
22522                b"5",
22523                "-SEARCH_VALUE_BAD Default dialect version cannot be higher than 4\r\n",
22524            ),
22525            (
22526                b"_BG_INDEX_MEM_PCT_THR",
22527                b"101",
22528                "-SEARCH_LIMIT_OVER Memory limit for indexing cannot be greater then \
22529                 100%\r\n",
22530            ),
22531            (
22532                b"BM25STD_TANH_FACTOR",
22533                b"10001",
22534                "-SEARCH_LIMIT_OVER BM25STD_TANH_FACTOR must be between 1 and 10000 \
22535                 inclusive\r\n",
22536            ),
22537            (
22538                b"BG_INDEX_SLEEP_DURATION_US",
22539                b"1000000",
22540                "-SEARCH_LIMIT_OVER BG_INDEX_SLEEP_DURATION_US must be between 1 and \
22541                 999999 (usleep POSIX limit)\r\n",
22542            ),
22543        ] {
22544            assert_eq!(
22545                f.run(&[b"FT.CONFIG", b"SET", name, given]),
22546                want,
22547                "{name:?}"
22548            );
22549        }
22550    }
22551
22552    /// The two trimming delays are measured against each other, and the answer
22553    /// names both settings and both numbers.
22554    #[test]
22555    fn the_trimming_delays_are_checked_against_one_another() {
22556        let mut f = Fixture::new();
22557        assert_eq!(
22558            f.run(&[b"FT.CONFIG", b"SET", b"_MIN_TRIM_DELAY_MS", b"5000"]),
22559            "-SEARCH_PARSE_ARGS _MIN_TRIM_DELAY_MS (5000) must be less than \
22560             _MAX_TRIM_DELAY_MS (5000)\r\n"
22561        );
22562        assert_eq!(
22563            f.run(&[b"FT.CONFIG", b"SET", b"_MAX_TRIM_DELAY_MS", b"1999"]),
22564            "-SEARCH_PARSE_ARGS _MAX_TRIM_DELAY_MS (1999) must be greater than \
22565             _MIN_TRIM_DELAY_MS (2000)\r\n"
22566        );
22567        assert_eq!(
22568            f.run(&[b"FT.CONFIG", b"SET", b"_MIN_TRIM_DELAY_MS", b"4999"]),
22569            "+OK\r\n"
22570        );
22571    }
22572
22573    /// Two of the word settings fold the spelling on the way in and the scorer
22574    /// does not, which is the one place in the table case counts.
22575    #[test]
22576    fn a_word_setting_folds_where_a_real_server_folds_and_not_otherwise() {
22577        let mut f = Fixture::new();
22578        assert_eq!(
22579            f.run(&[b"FT.CONFIG", b"SET", b"ON_TIMEOUT", b"RETURN"]),
22580            "+OK\r\n"
22581        );
22582        assert_eq!(
22583            f.run(&[b"FT.CONFIG", b"GET", b"ON_TIMEOUT"]),
22584            "*1\r\n*2\r\n+ON_TIMEOUT\r\n$6\r\nreturn\r\n"
22585        );
22586        assert_eq!(
22587            f.run(&[b"FT.CONFIG", b"SET", b"ON_TIMEOUT", b"nope"]),
22588            "-SEARCH_VALUE_BAD Invalid ON_TIMEOUT value\r\n"
22589        );
22590        assert_eq!(
22591            f.run(&[b"FT.CONFIG", b"SET", b"ON_OOM", b"IGNORE"]),
22592            "+OK\r\n"
22593        );
22594        assert_eq!(
22595            f.run(&[b"FT.CONFIG", b"GET", b"ON_OOM"]),
22596            "*1\r\n*2\r\n+ON_OOM\r\n$6\r\nignore\r\n"
22597        );
22598        assert_eq!(
22599            f.run(&[b"FT.CONFIG", b"SET", b"ON_OOM", b"nope"]),
22600            "-SEARCH_VALUE_BAD Invalid ON_OOM value\r\n"
22601        );
22602        let bad = "-SEARCH_VALUE_BAD Invalid default scorer value\r\n";
22603        for given in [b"bm25std".as_slice(), b"Bm25", b"TFIDF.docnorm", b""] {
22604            assert_eq!(
22605                f.run(&[b"FT.CONFIG", b"SET", b"DEFAULT_SCORER", given]),
22606                bad,
22607                "{given:?}"
22608            );
22609        }
22610        assert_eq!(
22611            f.run(&[b"FT.CONFIG", b"SET", b"DEFAULT_SCORER", b"TFIDF.DOCNORM"]),
22612            "+OK\r\n"
22613        );
22614    }
22615
22616    /// True and false, either case, and none of the other words a client might
22617    /// reach for.
22618    #[test]
22619    fn a_yes_or_no_setting_takes_those_two_words_only() {
22620        let mut f = Fixture::new();
22621        assert_eq!(
22622            f.run(&[b"FT.CONFIG", b"SET", b"_NUMERIC_COMPRESS", b"TRUE"]),
22623            "+OK\r\n"
22624        );
22625        assert_eq!(
22626            f.run(&[b"FT.CONFIG", b"GET", b"_NUMERIC_COMPRESS"]),
22627            "*1\r\n*2\r\n+_NUMERIC_COMPRESS\r\n$4\r\ntrue\r\n"
22628        );
22629        for given in [b"yes".as_slice(), b"no", b"1", b"0", b"enabled", b""] {
22630            assert_eq!(
22631                f.run(&[b"FT.CONFIG", b"SET", b"_NUMERIC_COMPRESS", given]),
22632                "-SEARCH_PARSE_ARGS Could not convert argument to expected type\r\n",
22633                "{given:?}"
22634            );
22635        }
22636    }
22637
22638    /// Two pairs of names sit over one number each, and one of that second pair
22639    /// takes no value at all.
22640    #[test]
22641    fn two_names_for_one_setting_move_together() {
22642        let mut f = Fixture::new();
22643        f.run(&[b"FT.CONFIG", b"SET", b"MAXEXPANSIONS", b"300"]);
22644        assert_eq!(
22645            f.run(&[b"FT.CONFIG", b"GET", b"MAXPREFIXEXPANSIONS"]),
22646            "*1\r\n*2\r\n+MAXPREFIXEXPANSIONS\r\n$3\r\n300\r\n"
22647        );
22648        f.run(&[b"FT.CONFIG", b"SET", b"MAXPREFIXEXPANSIONS", b"200"]);
22649        assert_eq!(
22650            f.run(&[b"FT.CONFIG", b"GET", b"MAXEXPANSIONS"]),
22651            "*1\r\n*2\r\n+MAXEXPANSIONS\r\n$3\r\n200\r\n"
22652        );
22653        let long = b"_FORK_GC_CLEAN_NUMERIC_EMPTY_NODES".as_slice();
22654        let short = b"FORK_GC_CLEAN_NUMERIC_EMPTY_NODES".as_slice();
22655        f.run(&[b"FT.CONFIG", b"SET", long, b"false"]);
22656        assert_eq!(
22657            f.run(&[b"FT.CONFIG", b"GET", short]),
22658            "*1\r\n*2\r\n+FORK_GC_CLEAN_NUMERIC_EMPTY_NODES\r\n$5\r\nfalse\r\n"
22659        );
22660        assert_eq!(f.run(&[b"FT.CONFIG", b"SET", short]), "+OK\r\n");
22661        assert_eq!(
22662            f.run(&[b"FT.CONFIG", b"GET", long]),
22663            "*1\r\n*2\r\n+_FORK_GC_CLEAN_NUMERIC_EMPTY_NODES\r\n$4\r\ntrue\r\n"
22664        );
22665    }
22666
22667    /// The one setting that takes a write and never gives it back.
22668    #[test]
22669    fn a_password_reads_back_as_stars_whatever_was_written() {
22670        let mut f = Fixture::new();
22671        assert_eq!(
22672            f.run(&[b"FT.CONFIG", b"SET", b"OSS_GLOBAL_PASSWORD", b"hunter2"]),
22673            "+OK\r\n"
22674        );
22675        assert_eq!(
22676            f.run(&[b"FT.CONFIG", b"GET", b"OSS_GLOBAL_PASSWORD"]),
22677            "*1\r\n*2\r\n+OSS_GLOBAL_PASSWORD\r\n$17\r\nPassword: *******\r\n"
22678        );
22679    }
22680
22681    /// The settings are not in the keyspace, so unlike the dictionaries and the
22682    /// synonym groups beside them they live through an emptied one.
22683    #[test]
22684    fn a_flush_leaves_the_settings_alone() {
22685        for flush in [b"FLUSHALL".as_slice(), b"FLUSHDB"] {
22686            let mut f = Fixture::new();
22687            f.run(&[b"FT.CONFIG", b"SET", b"TIMEOUT", b"777"]);
22688            f.run(&[flush]);
22689            assert_eq!(
22690                f.run(&[b"FT.CONFIG", b"GET", b"TIMEOUT"]),
22691                "*1\r\n*2\r\n+TIMEOUT\r\n$3\r\n777\r\n",
22692                "{flush:?}"
22693            );
22694        }
22695    }
22696
22697    // ------------------------------------------------------------- synonyms
22698
22699    /// The terms are folded on the way in and the group ids are not, and one
22700    /// term can be in more than one group.
22701    #[test]
22702    fn a_synonym_dump_folds_the_terms_and_keeps_the_ids_as_given() {
22703        let mut f = Fixture::new();
22704        f.run(&[b"FT.CREATE", b"e", b"SCHEMA", b"t", b"TEXT"]);
22705        assert_eq!(
22706            f.run(&[b"FT.SYNUPDATE", b"e", b"G1", b"BOY", b"kid"]),
22707            "+OK\r\n"
22708        );
22709        assert_eq!(f.run(&[b"FT.SYNUPDATE", b"e", b"g2", b"boy"]), "+OK\r\n");
22710        assert_eq!(
22711            f.run(&[b"FT.SYNDUMP", b"e"]),
22712            "*4\r\n$3\r\nboy\r\n*2\r\n$2\r\nG1\r\n$2\r\ng2\r\n\
22713             $3\r\nkid\r\n*1\r\n$2\r\nG1\r\n"
22714        );
22715    }
22716
22717    /// A group is not a comparison made at query time. It is a term of its
22718    /// own, so a word in a group reads as a union of the word, the groups it
22719    /// is in and its stem.
22720    #[test]
22721    fn a_word_in_a_group_reads_as_a_union_with_the_group_term() {
22722        let mut f = Fixture::new();
22723        f.run(&[b"FT.CREATE", b"e", b"SCHEMA", b"t", b"TEXT"]);
22724        f.run(&[b"FT.SYNUPDATE", b"e", b"gr", b"jogging"]);
22725        assert_eq!(
22726            f.run(&[b"FT.EXPLAIN", b"e", b"jogging"]),
22727            "$69\r\nUNION {\n  jogging\n  ~gr(expanded)\n  +jog(expanded)\n  jog(expanded)\n}\n\r\n"
22728        );
22729    }
22730
22731    /// The lookup on the document side is on the word and never on the stem,
22732    /// and a group written after the documents were still finds them because
22733    /// the index is read again.
22734    ///
22735    /// The group holds `running` and `d2` says `runs`, so a query for another
22736    /// word of the group finds `d1` and leaves `d2` where it is. A query for
22737    /// `running` itself does find `d2`, through the stem branch of the union
22738    /// rather than through the group, which is why the two asserts differ.
22739    #[test]
22740    fn a_group_matches_the_word_it_holds_and_not_a_stem_of_it() {
22741        let mut f = Fixture::new();
22742        f.run(&[b"FT.CREATE", b"e", b"SCHEMA", b"t", b"TEXT"]);
22743        f.run(&[b"HSET", b"d1", b"t", b"boy"]);
22744        f.run(&[b"HSET", b"d2", b"t", b"runs"]);
22745        f.run(&[b"FT.SYNUPDATE", b"e", b"gr", b"boy", b"child", b"running"]);
22746        assert_eq!(
22747            f.run(&[b"FT.SEARCH", b"e", b"child", b"NOCONTENT"]),
22748            "*2\r\n:1\r\n$2\r\nd1\r\n"
22749        );
22750        assert_eq!(
22751            f.run(&[b"FT.SEARCH", b"e", b"running", b"NOCONTENT"]),
22752            "*3\r\n:2\r\n$2\r\nd1\r\n$2\r\nd2\r\n"
22753        );
22754    }
22755
22756    /// Neither command makes an index and neither forgives a name that is not
22757    /// there, in the same words the rest of the group uses.
22758    #[test]
22759    fn a_synonym_command_on_a_name_that_is_not_there_fails() {
22760        let mut f = Fixture::new();
22761        let missing = "-SEARCH_INDEX_NOT_FOUND Index not found: nope\r\n";
22762        assert_eq!(f.run(&[b"FT.SYNDUMP", b"nope"]), missing);
22763        assert_eq!(f.run(&[b"FT.SYNUPDATE", b"nope", b"g", b"a"]), missing);
22764    }
22765
22766    /// The words after `PARAMS n` are counted before their shape is looked at,
22767    /// so a count that reaches past the end of the command and a count that is
22768    /// merely odd are two different errors.
22769    #[test]
22770    fn params_counts_the_words_before_it_pairs_them_up() {
22771        let mut f = Fixture::new();
22772        f.run(&[b"FT.CREATE", b"e", b"SCHEMA", b"t", b"TEXT"]);
22773        let none = "-SEARCH_PARSE_ARGS Bad arguments for PARAMS: \
22774                    Expected an argument, but none provided\r\n";
22775        let odd = "-SEARCH_ADD_ARGS Parameters must be specified in PARAM VALUE pairs\r\n";
22776        assert_eq!(f.run(&[b"FT.SEARCH", b"e", b"x", b"PARAMS", b"1"]), none);
22777        assert_eq!(
22778            f.run(&[b"FT.SEARCH", b"e", b"x", b"PARAMS", b"3", b"a", b"b"]),
22779            none
22780        );
22781        assert_eq!(
22782            f.run(&[b"FT.SEARCH", b"e", b"x", b"PARAMS", b"1", b"a"]),
22783            odd
22784        );
22785        assert_eq!(f.run(&[b"FT.SEARCH", b"e", b"x", b"PARAMS", b"0"]), odd);
22786        assert_eq!(
22787            f.run(&[b"FT.SEARCH", b"e", b"x", b"PARAMS", b"-1"]),
22788            "-SEARCH_PARSE_ARGS Bad arguments for PARAMS: Value is outside acceptable bounds\r\n"
22789        );
22790    }
22791
22792    // --------------------------------------------------------------- vectors
22793
22794    /// Five documents a unit apart along one axis, written in the opposite
22795    /// order to the one they sit in, so a reply in document order and a reply
22796    /// in distance order are two different replies.
22797    ///
22798    /// `d1` is furthest from the origin and `d5` is on it. The text field
22799    /// splits them so a query can narrow before it measures: `d1`, `d2` and
22800    /// `d4` say `alpha` and the other two say `beta`.
22801    fn vectored(f: &mut Fixture) {
22802        f.run(&[
22803            b"FT.CREATE",
22804            b"h",
22805            b"SCHEMA",
22806            b"t",
22807            b"TEXT",
22808            b"v",
22809            b"VECTOR",
22810            b"FLAT",
22811            b"6",
22812            b"TYPE",
22813            b"FLOAT32",
22814            b"DIM",
22815            b"2",
22816            b"DISTANCE_METRIC",
22817            b"L2",
22818        ]);
22819        let at: [&[u8]; 5] = [
22820            b"\x00\x00\x80\x40\x00\x00\x00\x00",
22821            b"\x00\x00\x40\x40\x00\x00\x00\x00",
22822            b"\x00\x00\x00\x40\x00\x00\x00\x00",
22823            b"\x00\x00\x80\x3f\x00\x00\x00\x00",
22824            b"\x00\x00\x00\x00\x00\x00\x00\x00",
22825        ];
22826        for (n, point) in at.iter().enumerate() {
22827            let key = format!("d{}", n + 1);
22828            let word: &[u8] = match n {
22829                0 | 1 | 3 => b"alpha",
22830                _ => b"beta",
22831            };
22832            f.run(&[b"HSET", key.as_bytes(), b"t", word, b"v", point]);
22833        }
22834    }
22835
22836    /// The origin, which every query below asks about.
22837    const ORIGIN: &[u8] = b"\x00\x00\x00\x00\x00\x00\x00\x00";
22838
22839    /// A `KNN` picks the k nearest and then answers them in document order,
22840    /// which is measured: asking for three of five that were written furthest
22841    /// first answers the last three written and not the first three.
22842    #[test]
22843    fn a_knn_picks_the_nearest_and_answers_them_in_document_order() {
22844        let mut f = Fixture::new();
22845        vectored(&mut f);
22846        assert_eq!(
22847            f.run(&[
22848                b"FT.SEARCH",
22849                b"h",
22850                b"*=>[KNN 5 @v $vec]",
22851                b"PARAMS",
22852                b"2",
22853                b"vec",
22854                ORIGIN,
22855                b"DIALECT",
22856                b"2",
22857                b"NOCONTENT",
22858            ]),
22859            "*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"
22860        );
22861        assert_eq!(
22862            f.run(&[
22863                b"FT.SEARCH",
22864                b"h",
22865                b"*=>[KNN 3 @v $vec]",
22866                b"PARAMS",
22867                b"2",
22868                b"vec",
22869                ORIGIN,
22870                b"DIALECT",
22871                b"2",
22872                b"NOCONTENT",
22873            ]),
22874            "*4\r\n:3\r\n$2\r\nd3\r\n$2\r\nd4\r\n$2\r\nd5\r\n"
22875        );
22876    }
22877
22878    /// A range takes what is really inside it, where the distances are squared
22879    /// so the five documents sit at 16, 9, 4, 1 and 0.
22880    #[test]
22881    fn a_range_takes_what_is_inside_it_and_the_distance_is_squared() {
22882        let mut f = Fixture::new();
22883        vectored(&mut f);
22884        for (radius, want) in [
22885            ("0", "*2\r\n:1\r\n$2\r\nd5\r\n"),
22886            ("2", "*3\r\n:2\r\n$2\r\nd4\r\n$2\r\nd5\r\n"),
22887            (
22888                "9",
22889                "*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",
22890            ),
22891        ] {
22892            let query = format!("@v:[VECTOR_RANGE {radius} $vec]");
22893            assert_eq!(
22894                f.run(&[
22895                    b"FT.SEARCH",
22896                    b"h",
22897                    query.as_bytes(),
22898                    b"PARAMS",
22899                    b"2",
22900                    b"vec",
22901                    ORIGIN,
22902                    b"DIALECT",
22903                    b"2",
22904                    b"NOCONTENT",
22905                ]),
22906                want,
22907                "radius {radius}"
22908            );
22909        }
22910    }
22911
22912    /// A `KNN` behind a query is the nearest of what the query matched, so
22913    /// asking for two of the three documents that say `alpha` answers the two
22914    /// of those three that are nearest and not the two nearest overall.
22915    #[test]
22916    fn a_knn_measures_what_the_query_in_front_of_it_matched() {
22917        let mut f = Fixture::new();
22918        vectored(&mut f);
22919        assert_eq!(
22920            f.run(&[
22921                b"FT.SEARCH",
22922                b"h",
22923                b"alpha=>[KNN 2 @v $vec]",
22924                b"PARAMS",
22925                b"2",
22926                b"vec",
22927                ORIGIN,
22928                b"DIALECT",
22929                b"2",
22930                b"NOCONTENT",
22931            ]),
22932            "*3\r\n:2\r\n$2\r\nd2\r\n$2\r\nd4\r\n"
22933        );
22934    }
22935
22936    /// A `KNN` counts in whole numbers and a range measures from zero, and the
22937    /// two are refused in their own words.
22938    ///
22939    /// The count is a token of its own and is checked where it stands, ahead of
22940    /// the field and ahead of the vector. A count that arrives through `PARAMS`
22941    /// is read by looser rules than one written into the query, which is
22942    /// measured: a leading plus is fine in a parameter and a syntax error in
22943    /// the query text.
22944    #[test]
22945    fn a_count_and_a_radius_are_refused_in_their_own_words() {
22946        let mut f = Fixture::new();
22947        vectored(&mut f);
22948        let ask = |f: &mut Fixture, query: &str| {
22949            f.run(&[
22950                b"FT.SEARCH",
22951                b"h",
22952                query.as_bytes(),
22953                b"PARAMS",
22954                b"2",
22955                b"vec",
22956                ORIGIN,
22957                b"DIALECT",
22958                b"2",
22959                b"NOCONTENT",
22960            ])
22961        };
22962        for (query, at, near) in [
22963            ("*=>[KNN -1 @v $vec]", 8, "-1"),
22964            ("*=>[KNN 1.5 @v $vec]", 8, "1.5"),
22965            ("*=>[KNN +3 @v $vec]", 8, "+3"),
22966            ("*=>[KNN 0x10 @v $vec]", 8, "0x10"),
22967            ("*=>[KNN abc @v $vec]", 8, "abc"),
22968            ("*=>[KNN 3 $vec]", 10, "vec"),
22969            ("*=>[KNN 3 @v vec]", 13, "vec"),
22970            ("@v:[VECTOR_RANGE 2 -1]", 19, "-1"),
22971        ] {
22972            assert_eq!(
22973                ask(&mut f, query),
22974                format!("-SEARCH_SYNTAX Syntax error at offset {at} near {near}\r\n"),
22975                "{query}"
22976            );
22977        }
22978
22979        // Read as a double the way a real server reads it, so the bound plus
22980        // thirty two rounds back onto the bound and gets in.
22981        let large = "-SEARCH_QUERY_BAD Error parsing vector similarity query: \
22982                     query KNN K parameter is too large, must not exceed 288230376151711744\r\n";
22983        assert_eq!(
22984            ask(&mut f, "*=>[KNN 288230376151711776 @v $vec]"),
22985            "*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"
22986        );
22987        assert_eq!(ask(&mut f, "*=>[KNN 288230376151711777 @v $vec]"), large);
22988        assert_eq!(ask(&mut f, "*=>[KNN 99999999999999999999 @v $vec]"), large);
22989
22990        for (radius, printed) in [("-1", "-1"), ("-0.5", "-0.5"), ("-1e2", "-100")] {
22991            let query = format!("@v:[VECTOR_RANGE {radius} $vec]");
22992            assert_eq!(
22993                ask(&mut f, &query),
22994                format!(
22995                    "-SEARCH_QUERY_BAD Error parsing vector similarity query: \
22996                     negative radius ({printed}) given in a range query\r\n"
22997                ),
22998                "{query}"
22999            );
23000        }
23001        // A radius of minus zero is not below zero and is a radius of zero.
23002        assert_eq!(
23003            ask(&mut f, "@v:[VECTOR_RANGE -0 $vec]"),
23004            "*2\r\n:1\r\n$2\r\nd5\r\n"
23005        );
23006    }
23007
23008    /// A count passed with `PARAMS` is read the way a real server reads one,
23009    /// which is not the way the same digits are read in the query text.
23010    #[test]
23011    fn a_count_that_came_from_params_is_read_by_its_own_rules() {
23012        let mut f = Fixture::new();
23013        vectored(&mut f);
23014        let ask = |f: &mut Fixture, count: &[u8]| {
23015            f.run(&[
23016                b"FT.SEARCH",
23017                b"h",
23018                b"*=>[KNN $k @v $vec]",
23019                b"PARAMS",
23020                b"4",
23021                b"vec",
23022                ORIGIN,
23023                b"k",
23024                count,
23025                b"DIALECT",
23026                b"2",
23027                b"NOCONTENT",
23028            ])
23029        };
23030        let three = "*4\r\n:3\r\n$2\r\nd3\r\n$2\r\nd4\r\n$2\r\nd5\r\n";
23031        assert_eq!(ask(&mut f, b"3"), three);
23032        assert_eq!(ask(&mut f, b"  3"), three);
23033        assert_eq!(ask(&mut f, b"+3"), three);
23034        for bad in [
23035            &b"3.0"[..],
23036            b"0x3",
23037            b"-1",
23038            b"abc",
23039            b"",
23040            b"99999999999999999999",
23041        ] {
23042            let value = String::from_utf8_lossy(bad).into_owned();
23043            assert_eq!(
23044                ask(&mut f, bad),
23045                format!(
23046                    "-SEARCH_NUMERIC_VALUE_INVALID Invalid numeric value ({value}) \
23047                     for parameter `k`\r\n"
23048                ),
23049                "{value}"
23050            );
23051        }
23052        assert_eq!(
23053            ask(&mut f, b"288230376151711777"),
23054            "-SEARCH_QUERY_BAD Error parsing vector similarity query: \
23055             query KNN K parameter is too large, must not exceed 288230376151711744\r\n"
23056        );
23057    }
23058
23059    /// A vector the wrong size is refused against the field it was passed to,
23060    /// naming both sizes in bytes.
23061    #[test]
23062    fn a_vector_the_wrong_size_is_refused_by_the_field_it_reached() {
23063        let mut f = Fixture::new();
23064        vectored(&mut f);
23065        assert_eq!(
23066            f.run(&[
23067                b"FT.SEARCH",
23068                b"h",
23069                b"*=>[KNN 5 @v $vec]",
23070                b"PARAMS",
23071                b"2",
23072                b"vec",
23073                b"abc",
23074                b"DIALECT",
23075                b"2",
23076                b"NOCONTENT",
23077            ]),
23078            "-SEARCH_QUERY_BAD Error parsing vector similarity query: \
23079             query vector blob size (3) does not match index's expected size (8).\r\n"
23080        );
23081    }
23082
23083    // ----------------------------------------------------------- spellcheck
23084
23085    /// The score is how many documents hold the suggestion over how many
23086    /// documents there are, and how close the suggestion is to the word does
23087    /// not come into it at all, so the nearer of the two words here is second.
23088    #[test]
23089    fn a_spellcheck_scores_a_suggestion_by_how_common_it_is() {
23090        let mut f = Fixture::new();
23091        f.run(&[b"FT.CREATE", b"e", b"SCHEMA", b"t", b"TEXT", b"NOSTEM"]);
23092        f.run(&[b"HSET", b"d1", b"t", b"hello"]);
23093        f.run(&[b"HSET", b"d2", b"t", b"hallo hello"]);
23094        assert_eq!(
23095            f.run(&[b"FT.SPELLCHECK", b"e", b"hellp", b"DISTANCE", b"2"]),
23096            "*1\r\n*3\r\n$4\r\nTERM\r\n$5\r\nhellp\r\n\
23097             *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"
23098        );
23099    }
23100
23101    /// On RESP3 the whole thing is wrapped in a map under one name, a word
23102    /// carries a list of one pair maps, and the score is a double rather than
23103    /// a string.
23104    #[test]
23105    fn a_spellcheck_answers_a_map_of_maps_on_resp3() {
23106        let mut f = Fixture::new();
23107        f.run(&[b"HELLO", b"3"]);
23108        f.run(&[b"FT.CREATE", b"e", b"SCHEMA", b"t", b"TEXT", b"NOSTEM"]);
23109        f.run(&[b"HSET", b"d1", b"t", b"hello"]);
23110        assert_eq!(
23111            f.run(&[b"FT.SPELLCHECK", b"e", b"hellp"]),
23112            "%1\r\n$7\r\nresults\r\n%1\r\n$5\r\nhellp\r\n\
23113             *1\r\n%1\r\n$5\r\nhello\r\n,1\r\n"
23114        );
23115    }
23116
23117    /// A word the index already holds is not a mistake and is left out of the
23118    /// answer, and that check never looks at the field the query named, while
23119    /// the search for candidates does.
23120    #[test]
23121    fn a_word_the_index_holds_is_never_asked_about_whatever_field_it_names() {
23122        let mut f = Fixture::new();
23123        f.run(&[
23124            b"FT.CREATE",
23125            b"e",
23126            b"SCHEMA",
23127            b"a",
23128            b"TEXT",
23129            b"NOSTEM",
23130            b"b",
23131            b"TEXT",
23132            b"NOSTEM",
23133        ]);
23134        f.run(&[b"HSET", b"d1", b"b", b"world"]);
23135        assert_eq!(f.run(&[b"FT.SPELLCHECK", b"e", b"@a:world"]), "*0\r\n");
23136        assert_eq!(
23137            f.run(&[b"FT.SPELLCHECK", b"e", b"@a:worlt"]),
23138            "*1\r\n*3\r\n$4\r\nTERM\r\n$5\r\nworlt\r\n*0\r\n"
23139        );
23140    }
23141
23142    /// A dictionary named by `INCLUDE` adds words the index never read, scored
23143    /// zero and reported in the spelling the dictionary was given, and one
23144    /// named by `EXCLUDE` says a word is spelled right after all.
23145    #[test]
23146    fn a_spellcheck_reads_the_dictionaries_it_is_pointed_at() {
23147        let mut f = Fixture::new();
23148        f.run(&[b"FT.CREATE", b"e", b"SCHEMA", b"t", b"TEXT", b"NOSTEM"]);
23149        f.run(&[b"FT.DICTADD", b"d", b"Hellp", b"hellq"]);
23150        assert_eq!(
23151            f.run(&[b"FT.SPELLCHECK", b"e", b"hellz", b"TERMS", b"INCLUDE", b"d"]),
23152            "*1\r\n*3\r\n$4\r\nTERM\r\n$5\r\nhellz\r\n\
23153             *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"
23154        );
23155        assert_eq!(
23156            f.run(&[b"FT.SPELLCHECK", b"e", b"hellq", b"TERMS", b"EXCLUDE", b"d"]),
23157            "*0\r\n"
23158        );
23159        assert_eq!(
23160            f.run(&[b"FT.SPELLCHECK", b"e", b"x", b"TERMS", b"INCLUDE", b"nope"]),
23161            "-Dict does not exist: nope\r\n"
23162        );
23163    }
23164
23165    /// The first `DISTANCE` counts and the rest are dropped, an argument
23166    /// nobody recognises is stepped over rather than refused, and a distance
23167    /// outside one to four is the one thing here that does fail.
23168    #[test]
23169    fn a_spellcheck_reads_its_arguments_leniently() {
23170        let mut f = Fixture::new();
23171        f.run(&[b"FT.CREATE", b"e", b"SCHEMA", b"t", b"TEXT", b"NOSTEM"]);
23172        f.run(&[b"HSET", b"d1", b"t", b"hello"]);
23173        let one = "*1\r\n*3\r\n$4\r\nTERM\r\n$5\r\nhellp\r\n\
23174                   *1\r\n*2\r\n$1\r\n1\r\n$5\r\nhello\r\n";
23175        assert_eq!(f.run(&[b"FT.SPELLCHECK", b"e", b"hellp", b"BOGUS"]), one);
23176        let none = "*1\r\n*3\r\n$4\r\nTERM\r\n$5\r\nhelqp\r\n*0\r\n";
23177        let args: &[&[u8]] = &[
23178            b"FT.SPELLCHECK",
23179            b"e",
23180            b"helqp",
23181            b"DISTANCE",
23182            b"1",
23183            b"DISTANCE",
23184            b"4",
23185        ];
23186        assert_eq!(f.run(args), none);
23187        assert_eq!(
23188            f.run(&[b"FT.SPELLCHECK", b"e", b"hellp", b"DISTANCE", b"5"]),
23189            "-bad distance given, distance must be a natural number between 1 to 4\r\n"
23190        );
23191        assert_eq!(
23192            f.run(&[b"FT.SPELLCHECK", b"nope", b"hellp"]),
23193            "-SEARCH_INDEX_NOT_FOUND Index not found: nope\r\n"
23194        );
23195    }
23196
23197    // -------------------------------------------------------------- suggest
23198
23199    /// The reply is the size of the dictionary afterwards, which is neither
23200    /// what was added nor whether anything changed.
23201    #[test]
23202    fn an_add_answers_how_many_suggestions_are_in_there_now() {
23203        let mut f = Fixture::new();
23204        assert_eq!(f.run(&[b"FT.SUGADD", b"s", b"one", b"1"]), ":1\r\n");
23205        assert_eq!(f.run(&[b"FT.SUGADD", b"s", b"one", b"9"]), ":1\r\n");
23206        assert_eq!(f.run(&[b"FT.SUGADD", b"s", b"only", b"2"]), ":2\r\n");
23207        assert_eq!(f.run(&[b"FT.SUGLEN", b"s"]), ":2\r\n");
23208        assert_eq!(f.run(&[b"FT.SUGLEN", b"nokey"]), ":0\r\n");
23209    }
23210
23211    /// A suggestion dictionary is the one thing the search module puts in the
23212    /// keyspace, so every keyspace command reaches it.
23213    #[test]
23214    fn a_suggestion_dictionary_is_a_key_with_a_type_of_its_own() {
23215        let mut f = Fixture::new();
23216        f.run(&[b"FT.SUGADD", b"s", b"one", b"1"]);
23217        assert_eq!(f.run(&[b"TYPE", b"s"]), "+trietype0\r\n");
23218        assert_eq!(f.run(&[b"OBJECT", b"ENCODING", b"s"]), "$3\r\nraw\r\n");
23219        assert_eq!(f.run(&[b"EXISTS", b"s"]), ":1\r\n");
23220        assert_eq!(f.run(&[b"KEYS", b"*"]), "*1\r\n$1\r\ns\r\n");
23221        assert_eq!(f.run(&[b"EXPIRE", b"s", b"100"]), ":1\r\n");
23222        assert_eq!(f.run(&[b"TTL", b"s"]), ":100\r\n");
23223        assert_eq!(f.run(&[b"DEL", b"s"]), ":1\r\n");
23224        assert_eq!(f.run(&[b"FT.SUGLEN", b"s"]), ":0\r\n");
23225    }
23226
23227    /// The last suggestion out takes the key with it, which most module types
23228    /// do not do.
23229    #[test]
23230    fn deleting_the_last_suggestion_deletes_the_key() {
23231        let mut f = Fixture::new();
23232        f.run(&[b"FT.SUGADD", b"s", b"one", b"1"]);
23233        assert_eq!(f.run(&[b"FT.SUGDEL", b"s", b"nope"]), ":0\r\n");
23234        assert_eq!(f.run(&[b"FT.SUGDEL", b"s", b"one"]), ":1\r\n");
23235        assert_eq!(f.run(&[b"EXISTS", b"s"]), ":0\r\n");
23236        assert_eq!(f.run(&[b"FT.SUGDEL", b"nokey", b"a"]), ":0\r\n");
23237    }
23238
23239    /// A key holding anything else is refused rather than overwritten, on all
23240    /// four of them.
23241    #[test]
23242    fn a_suggestion_command_on_another_kind_of_key_is_wrongtype() {
23243        let mut f = Fixture::new();
23244        f.run(&[b"SET", b"s", b"x"]);
23245        for cmd in [
23246            vec![&b"FT.SUGADD"[..], b"s", b"t", b"1"],
23247            vec![&b"FT.SUGGET"[..], b"s", b"t"],
23248            vec![&b"FT.SUGDEL"[..], b"s", b"t"],
23249            vec![&b"FT.SUGLEN"[..], b"s"],
23250        ] {
23251            assert!(f.run(&cmd).starts_with("-WRONGTYPE"), "{cmd:?}");
23252        }
23253        assert_eq!(f.run(&[b"GET", b"s"]), "$1\r\nx\r\n");
23254    }
23255
23256    /// The scores in here were read off a real server, single precision and
23257    /// all. An exact match answers a sentinel so it sorts in front.
23258    #[test]
23259    fn a_lookup_answers_a_score_it_works_out_rather_than_the_one_stored() {
23260        let mut f = Fixture::new();
23261        f.run(&[b"FT.SUGADD", b"s", b"one", b"1"]);
23262        f.run(&[b"FT.SUGADD", b"s", b"only", b"2"]);
23263        f.run(&[b"FT.SUGADD", b"s", b"ontario", b"3"]);
23264        assert_eq!(
23265            f.run(&[b"FT.SUGGET", b"s", b"on", b"WITHSCORES"]),
23266            "*6\r\n$7\r\nontario\r\n$18\r\n1.2247449159622192\r\n\
23267             $4\r\nonly\r\n$17\r\n1.154700517654419\r\n\
23268             $3\r\none\r\n$18\r\n0.7071067690849304\r\n"
23269        );
23270        assert_eq!(
23271            f.run(&[b"FT.SUGGET", b"s", b"one", b"WITHSCORES"]),
23272            "*2\r\n$3\r\none\r\n$10\r\n2147483648\r\n"
23273        );
23274        assert_eq!(f.run(&[b"FT.SUGGET", b"nokey", b"a"]), "*0\r\n");
23275    }
23276
23277    /// `FUZZY` is one edit, and the edit is a rune rather than a byte.
23278    #[test]
23279    fn fuzzy_allows_one_edit_and_nothing_allows_two() {
23280        let mut f = Fixture::new();
23281        f.run(&[b"FT.SUGADD", b"s", b"only", b"2"]);
23282        assert_eq!(f.run(&[b"FT.SUGGET", b"s", b"one"]), "*0\r\n");
23283        assert_eq!(
23284            f.run(&[b"FT.SUGGET", b"s", b"one", b"FUZZY", b"WITHSCORES"]),
23285            "*2\r\n$4\r\nonly\r\n$19\r\n0.19139298796653748\r\n"
23286        );
23287        assert_eq!(f.run(&[b"FT.SUGGET", b"s", b"xyz", b"FUZZY"]), "*0\r\n");
23288    }
23289
23290    /// Five without a `MAX`, and the terms come back in score order.
23291    #[test]
23292    fn a_lookup_answers_five_unless_it_is_told_otherwise() {
23293        let mut f = Fixture::new();
23294        for (term, score) in [
23295            (&b"a1"[..], &b"1"[..]),
23296            (b"a2", b"2"),
23297            (b"a3", b"3"),
23298            (b"a4", b"4"),
23299            (b"a5", b"5"),
23300            (b"a6", b"6"),
23301        ] {
23302            f.run(&[b"FT.SUGADD", b"s", term, score]);
23303        }
23304        assert_eq!(
23305            f.run(&[b"FT.SUGGET", b"s", b"a"]),
23306            "*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"
23307        );
23308        assert_eq!(
23309            f.run(&[b"FT.SUGGET", b"s", b"a", b"MAX", b"2"]),
23310            "*2\r\n$2\r\na6\r\n$2\r\na5\r\n"
23311        );
23312        // A `MAX` larger than the dictionary answers what there is.
23313        assert!(
23314            f.run(&[b"FT.SUGGET", b"s", b"a", b"MAX", b"100"])
23315                .starts_with("*6\r\n")
23316        );
23317    }
23318
23319    /// A payload is replaced only when one is given, and an empty one is no
23320    /// payload at all.
23321    #[test]
23322    fn a_payload_comes_back_beside_the_term_or_a_null_does() {
23323        let mut f = Fixture::new();
23324        f.run(&[b"FT.SUGADD", b"s", b"one", b"1", b"PAYLOAD", b"p"]);
23325        assert_eq!(
23326            f.run(&[b"FT.SUGGET", b"s", b"o", b"WITHPAYLOADS"]),
23327            "*2\r\n$3\r\none\r\n$1\r\np\r\n"
23328        );
23329        f.run(&[b"FT.SUGADD", b"s", b"one", b"2"]);
23330        assert_eq!(
23331            f.run(&[b"FT.SUGGET", b"s", b"o", b"WITHPAYLOADS"]),
23332            "*2\r\n$3\r\none\r\n$1\r\np\r\n"
23333        );
23334        // An empty payload is the same as not having given one at all, so it
23335        // leaves the payload where it is rather than clearing it.
23336        f.run(&[b"FT.SUGADD", b"s", b"one", b"2", b"PAYLOAD", b""]);
23337        assert_eq!(
23338            f.run(&[b"FT.SUGGET", b"s", b"o", b"WITHPAYLOADS"]),
23339            "*2\r\n$3\r\none\r\n$1\r\np\r\n"
23340        );
23341        // A term that never had one answers a null.
23342        f.run(&[b"FT.SUGADD", b"s", b"other", b"1", b"PAYLOAD", b""]);
23343        assert_eq!(
23344            f.run(&[b"FT.SUGGET", b"s", b"ot", b"WITHPAYLOADS"]),
23345            "*2\r\n$5\r\nother\r\n$-1\r\n"
23346        );
23347    }
23348
23349    /// `INCR` adds to the score that is there rather than replacing it, and
23350    /// three tenths a tenth at a time is the reading that shows the score is
23351    /// held in single precision.
23352    #[test]
23353    fn incr_adds_to_the_score_that_is_already_there() {
23354        let mut f = Fixture::new();
23355        for _ in 0..3 {
23356            f.run(&[b"FT.SUGADD", b"s", b"xxx", b"0.1", b"INCR"]);
23357        }
23358        assert_eq!(
23359            f.run(&[b"FT.SUGGET", b"s", b"xx", b"WITHSCORES"]),
23360            "*2\r\n$3\r\nxxx\r\n$18\r\n0.2121320366859436\r\n"
23361        );
23362    }
23363
23364    /// The five error sentences, none of which are written the same way.
23365    #[test]
23366    fn the_suggestion_errors_are_the_lines_the_module_sends() {
23367        let mut f = Fixture::new();
23368        f.run(&[b"FT.SUGADD", b"s", b"one", b"1"]);
23369        assert_eq!(
23370            f.run(&[b"FT.SUGADD", b"s", b"t", b"abc"]),
23371            "-ERR invalid score\r\n"
23372        );
23373        // The unknown word is complained about before the score is converted.
23374        assert_eq!(
23375            f.run(&[b"FT.SUGADD", b"s", b"t", b"abc", b"NOPE"]),
23376            "-Unknown argument `NOPE`\r\n"
23377        );
23378        assert_eq!(
23379            f.run(&[b"FT.SUGADD", b"s", b"t", b"1", b"PAYLOAD"]),
23380            "-Invalid payload: Expected an argument, but none provided\r\n"
23381        );
23382        // Too many words is an arity error and not an unknown argument.
23383        assert!(
23384            f.run(&[
23385                b"FT.SUGADD",
23386                b"s",
23387                b"t",
23388                b"1",
23389                b"PAYLOAD",
23390                b"a",
23391                b"PAYLOAD",
23392                b"b"
23393            ])
23394            .contains("wrong number of arguments")
23395        );
23396        assert_eq!(
23397            f.run(&[b"FT.SUGGET", b"s", b"o", b"NOPE"]),
23398            "-SEARCH_PARSE_ARGS Unrecognized argument: NOPE\r\n"
23399        );
23400        // A count read as a whole number and then found to be out of range,
23401        // against one that had to be read as a double first, where anything
23402        // under one is a conversion that failed rather than a range that did.
23403        for max in [&b"0"[..], b"-1", b"4294967296", b"1e10", b"inf"] {
23404            assert_eq!(
23405                f.run(&[b"FT.SUGGET", b"s", b"o", b"MAX", max]),
23406                "-SEARCH_PARSE_ARGS MAX: Value is outside acceptable bounds\r\n",
23407                "{}",
23408                String::from_utf8_lossy(max)
23409            );
23410        }
23411        for max in [
23412            &b"abc"[..],
23413            b"0.0",
23414            b"00",
23415            b"-0",
23416            b"+0",
23417            b"0.5",
23418            b"-1.5",
23419            b"1e400",
23420        ] {
23421            assert_eq!(
23422                f.run(&[b"FT.SUGGET", b"s", b"o", b"MAX", max]),
23423                "-SEARCH_PARSE_ARGS MAX: Could not convert argument to expected type\r\n",
23424                "{}",
23425                String::from_utf8_lossy(max)
23426            );
23427        }
23428        for max in [&b"01"[..], b"+1", b"1.5", b"0x10", b"1e2"] {
23429            assert_eq!(
23430                f.run(&[b"FT.SUGGET", b"s", b"o", b"MAX", max]),
23431                "*1\r\n$3\r\none\r\n",
23432                "{}",
23433                String::from_utf8_lossy(max)
23434            );
23435        }
23436        assert_eq!(
23437            f.run(&[b"FT.SUGGET", b"s", b"o", b"MAX"]),
23438            "-SEARCH_PARSE_ARGS MAX: Expected an argument, but none provided\r\n"
23439        );
23440        // A score too large for a double is refused where one spelled out is
23441        // taken, which is the module reading errno after the conversion.
23442        assert_eq!(
23443            f.run(&[b"FT.SUGADD", b"s", b"t", b"1e400"]),
23444            "-ERR invalid score\r\n"
23445        );
23446        assert_eq!(f.run(&[b"FT.SUGADD", b"s", b"t", b"inf"]), ":2\r\n");
23447    }
23448
23449    /// An empty term is taken and not stored, so the reply is the length that
23450    /// was already there and nothing new comes back. The key is still made,
23451    /// and a delete that finds nothing is what clears it away again.
23452    #[test]
23453    fn an_empty_suggestion_is_taken_and_dropped_but_still_makes_the_key() {
23454        let mut f = Fixture::new();
23455        f.run(&[b"FT.SUGADD", b"s", b"one", b"1"]);
23456        assert_eq!(f.run(&[b"FT.SUGADD", b"s", b"", b"1"]), ":1\r\n");
23457        assert_eq!(f.run(&[b"FT.SUGGET", b"s", b""]), "*1\r\n$3\r\none\r\n");
23458        assert_eq!(f.run(&[b"FT.SUGADD", b"e", b"", b"1"]), ":0\r\n");
23459        assert_eq!(f.run(&[b"EXISTS", b"e"]), ":1\r\n");
23460        assert_eq!(f.run(&[b"TYPE", b"e"]), "+trietype0\r\n");
23461        assert_eq!(f.run(&[b"FT.SUGDEL", b"e", b"nothing"]), ":0\r\n");
23462        assert_eq!(f.run(&[b"EXISTS", b"e"]), ":0\r\n");
23463    }
23464
23465    /// A key that will not read is counted against the index and against the
23466    /// field, and `FT.INFO` says so.
23467    #[test]
23468    fn a_hash_that_will_not_read_is_counted_where_ft_info_reports_it() {
23469        let mut f = Fixture::new();
23470        f.run(&[
23471            b"FT.CREATE",
23472            b"ix",
23473            b"PREFIX",
23474            b"1",
23475            b"p:",
23476            b"SCHEMA",
23477            b"n",
23478            b"NUMERIC",
23479        ]);
23480        f.run(&[b"HSET", b"p:1", b"n", b"notanumber"]);
23481        assert_eq!(held(&f, b"ix"), (0, 0));
23482
23483        let reply = f.run(&[b"FT.INFO", b"ix"]);
23484        assert!(
23485            reply.contains("SEARCH_NUMERIC_VALUE_INVALID Invalid numeric value: 'notanumber'"),
23486            "{reply}"
23487        );
23488        assert!(reply.contains("hash_indexing_failures"), "{reply}");
23489    }
23490
23491    /// An index can only be made on database zero, and the check comes after
23492    /// the `IFNX` shortcut and before everything else.
23493    #[test]
23494    fn an_index_can_only_be_made_on_database_zero() {
23495        let mut f = Fixture::new();
23496        f.run(&[b"FT.CREATE", b"ix", b"SCHEMA", b"t", b"TEXT"]);
23497        f.run(&[b"SELECT", b"1"]);
23498        let refused = "-Cannot create index on db != 0\r\n";
23499        assert_eq!(
23500            f.run(&[b"FT.CREATE", b"jx", b"SCHEMA", b"t", b"TEXT"]),
23501            refused
23502        );
23503        // The name is taken, and it still answers about the database.
23504        assert_eq!(
23505            f.run(&[b"FT.CREATE", b"ix", b"SCHEMA", b"t", b"TEXT"]),
23506            refused
23507        );
23508        // And so does one whose arguments are nonsense.
23509        assert_eq!(
23510            f.run(&[b"FT.CREATE", b"zz", b"BOGUS", b"SCHEMA", b"t", b"TEXT"]),
23511            refused
23512        );
23513        // `IFNX` over a name that is taken is the one that gets through.
23514        assert_eq!(
23515            f.run(&[b"FT._CREATEIFNX", b"ix", b"SCHEMA", b"t", b"TEXT"]),
23516            "+OK\r\n"
23517        );
23518        assert_eq!(f.server.search.lock().len(), 1);
23519    }
23520
23521    /// The scan reads the database the create was run on, and after that the
23522    /// index follows its keys in every database.
23523    ///
23524    /// The asymmetry is a real server's, measured, and it is the sort of thing
23525    /// nobody would arrive at by choosing.
23526    #[test]
23527    fn the_scan_is_one_database_and_the_following_is_all_of_them() {
23528        let mut f = Fixture::new();
23529        f.run(&[b"SELECT", b"1"]);
23530        f.run(&[b"HSET", b"p:9", b"t", b"on one"]);
23531        f.run(&[b"SELECT", b"0"]);
23532        f.run(&[b"HSET", b"p:0", b"t", b"on zero"]);
23533        f.run(&[
23534            b"FT.CREATE",
23535            b"ix",
23536            b"PREFIX",
23537            b"1",
23538            b"p:",
23539            b"SCHEMA",
23540            b"t",
23541            b"TEXT",
23542        ]);
23543        assert_eq!(held(&f, b"ix"), (1, 1), "the scan read database zero only");
23544
23545        f.run(&[b"SELECT", b"1"]);
23546        f.run(&[b"HSET", b"p:8", b"t", b"later"]);
23547        assert_eq!(
23548            held(&f, b"ix"),
23549            (2, 2),
23550            "and then it follows every database"
23551        );
23552    }
23553
23554    /// Four documents over the two kinds of field a query can ask about, which
23555    /// is the corpus the searches below read.
23556    fn corpus(f: &mut Fixture) {
23557        f.run(&[
23558            b"FT.CREATE",
23559            b"sx",
23560            b"PREFIX",
23561            b"1",
23562            b"d:",
23563            b"SCHEMA",
23564            b"t",
23565            b"TEXT",
23566            b"g",
23567            b"TAG",
23568            b"n",
23569            b"NUMERIC",
23570        ]);
23571        for (key, text, tag, number) in [
23572            (b"d:1".as_slice(), "alpha beta", "aa,bb", "1"),
23573            (b"d:2", "alpha gamma", "bb", "2"),
23574            (b"d:3", "delta", "cc", "3"),
23575            (b"d:4", "alpha beta gamma", "aa,cc", "4"),
23576        ] {
23577            f.run(&[
23578                b"HSET",
23579                key,
23580                b"t",
23581                text.as_bytes(),
23582                b"g",
23583                tag.as_bytes(),
23584                b"n",
23585                number.as_bytes(),
23586            ]);
23587        }
23588    }
23589
23590    /// A corpus with something to sort by: a text field the index keeps a copy
23591    /// of, a number, the same text field under another name, and a text field
23592    /// the index keeps nothing of.
23593    fn sortable(f: &mut Fixture) {
23594        f.run(&[
23595            b"FT.CREATE",
23596            b"sy",
23597            b"PREFIX",
23598            b"1",
23599            b"s:",
23600            b"SCHEMA",
23601            b"t",
23602            b"TEXT",
23603            b"SORTABLE",
23604            b"n",
23605            b"NUMERIC",
23606            b"SORTABLE",
23607            b"body",
23608            b"AS",
23609            b"b",
23610            b"TEXT",
23611            b"SORTABLE",
23612            b"p",
23613            b"TEXT",
23614        ]);
23615        for (key, text, number) in [
23616            (b"s:1".as_slice(), "Banana Split", "2"),
23617            (b"s:2", "apple", "10"),
23618        ] {
23619            f.run(&[
23620                b"HSET",
23621                key,
23622                b"t",
23623                text.as_bytes(),
23624                b"n",
23625                number.as_bytes(),
23626                b"body",
23627                text.as_bytes(),
23628                b"p",
23629                b"alpha",
23630            ]);
23631        }
23632        // A key with nothing under either sortable field, which is what sorts
23633        // last whichever way round the sort runs.
23634        f.run(&[b"HSET", b"s:3", b"p", b"alpha"]);
23635    }
23636
23637    /// A sort runs off the copy of the value the index keeps, and a row with no
23638    /// value at all is last both ways round.
23639    #[test]
23640    fn a_search_sorts_by_a_field_the_index_keeps_a_copy_of() {
23641        let mut f = Fixture::new();
23642        sortable(&mut f);
23643        assert_eq!(
23644            f.run(&[b"FT.SEARCH", b"sy", b"alpha", b"SORTBY", b"n", b"NOCONTENT"]),
23645            "*4\r\n:3\r\n$3\r\ns:1\r\n$3\r\ns:2\r\n$3\r\ns:3\r\n"
23646        );
23647        assert_eq!(
23648            f.run(&[
23649                b"FT.SEARCH",
23650                b"sy",
23651                b"alpha",
23652                b"SORTBY",
23653                b"n",
23654                b"DESC",
23655                b"NOCONTENT"
23656            ]),
23657            "*4\r\n:3\r\n$3\r\ns:2\r\n$3\r\ns:1\r\n$3\r\ns:3\r\n"
23658        );
23659        // The copy of a text field is folded, so `apple` sorts before
23660        // `Banana Split` where a comparison of the bytes would not.
23661        assert_eq!(
23662            f.run(&[b"FT.SEARCH", b"sy", b"alpha", b"SORTBY", b"t", b"NOCONTENT"]),
23663            "*4\r\n:3\r\n$3\r\ns:2\r\n$3\r\ns:1\r\n$3\r\ns:3\r\n"
23664        );
23665    }
23666
23667    /// A field the index keeps no copy of is sorted by the value read off the
23668    /// key, which happens after the walk rather than during it.
23669    #[test]
23670    fn a_search_sorts_by_a_field_it_has_to_read_the_key_for() {
23671        let mut f = Fixture::new();
23672        sortable(&mut f);
23673        f.run(&[b"HSET", b"s:1", b"p", b"alpha zulu"]);
23674        assert_eq!(
23675            f.run(&[
23676                b"FT.SEARCH",
23677                b"sy",
23678                b"alpha",
23679                b"SORTBY",
23680                b"p",
23681                b"NOCONTENT",
23682                b"LIMIT",
23683                b"0",
23684                b"2"
23685            ]),
23686            "*3\r\n:3\r\n$3\r\ns:2\r\n$3\r\ns:3\r\n"
23687        );
23688        // Nothing is folded on this side, because the schema never asked for a
23689        // copy to fold, so the value goes into the sort as it was written.
23690        assert_eq!(
23691            f.run(&[
23692                b"FT.SEARCH",
23693                b"sy",
23694                b"alpha",
23695                b"SORTBY",
23696                b"p",
23697                b"WITHSORTKEYS",
23698                b"NOCONTENT",
23699                b"LIMIT",
23700                b"2",
23701                b"1"
23702            ]),
23703            "*3\r\n:3\r\n$3\r\ns:1\r\n$11\r\n$alpha zulu\r\n"
23704        );
23705    }
23706
23707    /// The value the sort compared goes beside every row, as a number after a
23708    /// hash, as text after a dollar, and as a null on a row that had none.
23709    #[test]
23710    fn a_search_can_send_the_value_it_sorted_by_back() {
23711        let mut f = Fixture::new();
23712        sortable(&mut f);
23713        assert_eq!(
23714            f.run(&[
23715                b"FT.SEARCH",
23716                b"sy",
23717                b"alpha",
23718                b"SORTBY",
23719                b"n",
23720                b"WITHSORTKEYS",
23721                b"NOCONTENT"
23722            ]),
23723            concat!(
23724                "*7\r\n:3\r\n",
23725                "$3\r\ns:1\r\n$2\r\n#2\r\n",
23726                "$3\r\ns:2\r\n$3\r\n#10\r\n",
23727                "$3\r\ns:3\r\n$-1\r\n"
23728            )
23729        );
23730        assert_eq!(
23731            f.run(&[
23732                b"FT.SEARCH",
23733                b"sy",
23734                b"alpha",
23735                b"SORTBY",
23736                b"t",
23737                b"WITHSORTKEYS",
23738                b"NOCONTENT"
23739            ]),
23740            concat!(
23741                "*7\r\n:3\r\n",
23742                "$3\r\ns:2\r\n$6\r\n$apple\r\n",
23743                "$3\r\ns:1\r\n$13\r\n$banana split\r\n",
23744                "$3\r\ns:3\r\n$-1\r\n"
23745            )
23746        );
23747        // Asking for a sort key without sorting is taken and answers a null on
23748        // every row, which is what a real server does.
23749        assert_eq!(
23750            f.run(&[
23751                b"FT.SEARCH",
23752                b"sy",
23753                b"banana",
23754                b"WITHSORTKEYS",
23755                b"NOCONTENT"
23756            ]),
23757            "*3\r\n:1\r\n$3\r\ns:1\r\n$-1\r\n"
23758        );
23759    }
23760
23761    /// The field a search sorted by is written in front of the fields of the
23762    /// key, and the key's own value for it wins when the two share a name.
23763    #[test]
23764    fn a_sort_puts_the_field_it_sorted_by_in_front_of_the_row() {
23765        let mut f = Fixture::new();
23766        sortable(&mut f);
23767        // `b` is what the schema calls the field the key calls `body`, so the
23768        // folded copy comes back under one name and the value as it was written
23769        // comes back under the other.
23770        assert_eq!(
23771            f.run(&[
23772                b"FT.SEARCH",
23773                b"sy",
23774                b"alpha",
23775                b"SORTBY",
23776                b"b",
23777                b"LIMIT",
23778                b"0",
23779                b"1"
23780            ]),
23781            concat!(
23782                "*3\r\n:3\r\n$3\r\ns:2\r\n*10\r\n",
23783                "$1\r\nb\r\n$5\r\napple\r\n",
23784                "$1\r\nt\r\n$5\r\napple\r\n",
23785                "$1\r\nn\r\n$2\r\n10\r\n",
23786                "$4\r\nbody\r\n$5\r\napple\r\n",
23787                "$1\r\np\r\n$5\r\nalpha\r\n"
23788            )
23789        );
23790        // With a `RETURN` list there is nothing to put in, so the field is moved
23791        // to the front of the names that were asked for instead.
23792        assert_eq!(
23793            f.run(&[
23794                b"FT.SEARCH",
23795                b"sy",
23796                b"alpha",
23797                b"SORTBY",
23798                b"b",
23799                b"RETURN",
23800                b"2",
23801                b"p",
23802                b"b",
23803                b"LIMIT",
23804                b"0",
23805                b"1"
23806            ]),
23807            concat!(
23808                "*3\r\n:3\r\n$3\r\ns:2\r\n*4\r\n",
23809                "$1\r\nb\r\n$5\r\napple\r\n",
23810                "$1\r\np\r\n$5\r\nalpha\r\n"
23811            )
23812        );
23813    }
23814
23815    /// The four ways a `SORTBY` on a search is refused.
23816    #[test]
23817    fn a_search_refuses_the_sorts_it_cannot_run() {
23818        let mut f = Fixture::new();
23819        sortable(&mut f);
23820        assert_eq!(
23821            f.run(&[b"FT.SEARCH", b"sy", b"alpha", b"SORTBY"]),
23822            "-SEARCH_PARSE_ARGS Bad SORTBY arguments\r\n"
23823        );
23824        assert_eq!(
23825            f.run(&[b"FT.SEARCH", b"sy", b"alpha", b"SORTBY", b"n", b"SORTBY"]),
23826            "-SEARCH_PARSE_ARGS Multiple SORTBY steps are not allowed\r\n"
23827        );
23828        assert_eq!(
23829            f.run(&[b"FT.SEARCH", b"sy", b"alpha", b"SORTBY", b"n", b"MAX", b"2"]),
23830            "-SEARCH_PARSE_ARGS SORTBY MAX is not supported by FT.SEARCH\r\n"
23831        );
23832        assert_eq!(
23833            f.run(&[b"FT.SEARCH", b"sy", b"alpha", b"SORTBY", b"zz"]),
23834            "-SEARCH_PROP_NOT_FOUND Property `zz` not loaded nor in schema\r\n"
23835        );
23836        // The property is looked up once the whole list has read cleanly, so a
23837        // word after it that nobody knows is the error that comes back.
23838        assert_eq!(
23839            f.run(&[b"FT.SEARCH", b"sy", b"alpha", b"SORTBY", b"zz", b"NOPE"]),
23840            "-SEARCH_ARG_UNRECOGNIZED Unknown argument `NOPE` at position 3 for <main>\r\n"
23841        );
23842    }
23843
23844    /// An index over two text fields, a number and a tag, holding one key whose
23845    /// `a` runs long enough to be worth cutting down and whose `b` and `g` hold
23846    /// nothing the query matches.
23847    fn marking(f: &mut Fixture) {
23848        f.run(&[
23849            b"FT.CREATE",
23850            b"mk",
23851            b"ON",
23852            b"HASH",
23853            b"PREFIX",
23854            b"1",
23855            b"m:",
23856            b"SCHEMA",
23857            b"a",
23858            b"TEXT",
23859            b"b",
23860            b"TEXT",
23861            b"n",
23862            b"NUMERIC",
23863            b"g",
23864            b"TAG",
23865        ]);
23866        f.run(&[
23867            b"HSET",
23868            b"m:1",
23869            b"a",
23870            b"c1 c2 c3 fox d1 d2 d3 d4 d5 d6 d7 d8 d9 fox e1 e2 e3",
23871            b"b",
23872            b"t1 t2 t3 t4 t5 t6 t7 t8",
23873            b"n",
23874            b"1",
23875            b"g",
23876            b"red",
23877        ]);
23878    }
23879
23880    /// A field the query matched comes back as fragments and a field it did not
23881    /// comes back as its own front.
23882    #[test]
23883    fn a_summarize_cuts_a_field_down_to_what_matched() {
23884        let mut f = Fixture::new();
23885        marking(&mut f);
23886        let got = f.run(&[b"FT.SEARCH", b"mk", b"fox", b"SUMMARIZE", b"LEN", b"2"]);
23887        assert!(got.contains("c3 fox d1 d2... d9 fox e1 e2... "), "{got}");
23888        // `b` holds no match, so it keeps its front and loses its last word.
23889        assert!(got.contains("t1 t2 t3 t4 t5 t6 t7\r\n"), "{got}");
23890        // And so does the tag, which is a value like any other to this clause.
23891        assert!(got.contains("$1\r\nr\r\n"), "{got}");
23892    }
23893
23894    /// `FRAGS` is applied before the context either side of a fragment is worked
23895    /// out, so the fragment that is left runs over the match of the one that was
23896    /// dropped rather than stopping on it.
23897    #[test]
23898    fn a_dropped_fragment_stops_bounding_the_one_that_was_kept() {
23899        let mut f = Fixture::new();
23900        marking(&mut f);
23901        let got = f.run(&[
23902            b"FT.SEARCH",
23903            b"mk",
23904            b"fox",
23905            b"SUMMARIZE",
23906            b"FRAGS",
23907            b"1",
23908            b"LEN",
23909            b"20",
23910        ]);
23911        assert!(
23912            got.contains("c2 c3 fox d1 d2 d3 d4 d5 d6 d7 d8 d9 fox e1 e2... "),
23913            "{got}"
23914        );
23915        // Keep both and the first stops on the second rather than running over
23916        // it, on the same query and the same budget.
23917        let two = f.run(&[
23918            b"FT.SEARCH",
23919            b"mk",
23920            b"fox",
23921            b"SUMMARIZE",
23922            b"FRAGS",
23923            b"2",
23924            b"LEN",
23925            b"20",
23926        ]);
23927        assert!(
23928            two.contains("c2 c3 fox d1 d2 d3 d4 d5 d6 d7 d8 d9... d1"),
23929            "{two}"
23930        );
23931    }
23932
23933    /// A `HIGHLIGHT` wraps every match, and on a field with no match in it the
23934    /// clause also calls off the cutting down a `SUMMARIZE` would have done.
23935    #[test]
23936    fn a_highlight_marks_the_matches_and_leaves_the_rest_of_the_field_alone() {
23937        let mut f = Fixture::new();
23938        marking(&mut f);
23939        let got = f.run(&[b"FT.SEARCH", b"mk", b"fox", b"HIGHLIGHT"]);
23940        assert!(got.contains("<b>fox</b> d1 d2"), "{got}");
23941        let both = f.run(&[
23942            b"FT.SEARCH",
23943            b"mk",
23944            b"fox",
23945            b"SUMMARIZE",
23946            b"LEN",
23947            b"2",
23948            b"HIGHLIGHT",
23949        ]);
23950        assert!(both.contains("c3 <b>fox</b> d1 d2... "), "{both}");
23951        // `b` still holds no match, and this time it comes back whole.
23952        assert!(both.contains("t1 t2 t3 t4 t5 t6 t7 t8\r\n"), "{both}");
23953        assert!(both.contains("$3\r\nred\r\n"), "{both}");
23954        // Naming a field one clause does not cover leaves it cut down again.
23955        let split = f.run(&[
23956            b"FT.SEARCH",
23957            b"mk",
23958            b"fox",
23959            b"SUMMARIZE",
23960            b"FIELDS",
23961            b"1",
23962            b"b",
23963            b"LEN",
23964            b"2",
23965            b"HIGHLIGHT",
23966            b"FIELDS",
23967            b"1",
23968            b"a",
23969        ]);
23970        assert!(split.contains("t1 t2 t3 t4 t5 t6 t7\r\n"), "{split}");
23971    }
23972
23973    /// A tag is never marked, in its own field or in a text field beside it.
23974    #[test]
23975    fn a_highlight_does_not_mark_a_tag() {
23976        let mut f = Fixture::new();
23977        marking(&mut f);
23978        f.run(&[b"HSET", b"m:1", b"b", b"red and blue"]);
23979        let got = f.run(&[b"FT.SEARCH", b"mk", b"@g:{red}", b"HIGHLIGHT"]);
23980        assert!(!got.contains("<b>"), "{got}");
23981        assert!(got.contains("red and blue"), "{got}");
23982    }
23983
23984    /// A search answers a total and then a row for every key in the window,
23985    /// with the fields of that key after it.
23986    #[test]
23987    fn a_search_answers_a_total_and_then_the_rows() {
23988        let mut f = Fixture::new();
23989        corpus(&mut f);
23990        assert_eq!(
23991            f.run(&[b"FT.SEARCH", b"sx", b"delta"]),
23992            "*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"
23993        );
23994        // The fields are what the key holds and not what the schema names, so
23995        // a field nobody indexed comes back too.
23996        f.run(&[b"HSET", b"d:3", b"extra", b"more"]);
23997        assert!(f.run(&[b"FT.SEARCH", b"sx", b"delta"]).contains("extra"));
23998        // `NOCONTENT` leaves the keys on their own, and `LIMIT 0 0` leaves
23999        // the total on its own.
24000        assert_eq!(
24001            f.run(&[b"FT.SEARCH", b"sx", b"delta", b"NOCONTENT"]),
24002            "*2\r\n:1\r\n$3\r\nd:3\r\n"
24003        );
24004        assert_eq!(
24005            f.run(&[b"FT.SEARCH", b"sx", b"alpha", b"LIMIT", b"0", b"0"]),
24006            "*1\r\n:3\r\n"
24007        );
24008    }
24009
24010    /// The window is ten rows when nobody said, and the cap is on how wide it
24011    /// is rather than on where it starts.
24012    #[test]
24013    fn the_window_is_ten_rows_and_a_million_wide_at_most() {
24014        let mut f = Fixture::new();
24015        corpus(&mut f);
24016        assert_eq!(
24017            f.run(&[
24018                b"FT.SEARCH",
24019                b"sx",
24020                b"alpha",
24021                b"NOCONTENT",
24022                b"LIMIT",
24023                b"1",
24024                b"1"
24025            ]),
24026            "*2\r\n:3\r\n$3\r\nd:2\r\n"
24027        );
24028        assert_eq!(
24029            f.run(&[b"FT.SEARCH", b"sx", b"alpha", b"LIMIT", b"0"]),
24030            "-SEARCH_PARSE_ARGS LIMIT requires two arguments\r\n"
24031        );
24032        assert_eq!(
24033            f.run(&[b"FT.SEARCH", b"sx", b"alpha", b"LIMIT", b"0", b"-1"]),
24034            "-SEARCH_PARSE_ARGS LIMIT needs two numeric arguments\r\n"
24035        );
24036        assert_eq!(
24037            f.run(&[b"FT.SEARCH", b"sx", b"alpha", b"LIMIT", b"0", b"1000001"]),
24038            "-SEARCH_LIMIT_OVER LIMIT exceeds maximum of 1000000\r\n"
24039        );
24040        assert_eq!(
24041            f.run(&[
24042                b"FT.SEARCH",
24043                b"sx",
24044                b"alpha",
24045                b"NOCONTENT",
24046                b"LIMIT",
24047                b"999999",
24048                b"1000000"
24049            ]),
24050            "*1\r\n:3\r\n"
24051        );
24052    }
24053
24054    /// `RETURN 0` reads on the wire like `NOCONTENT` and is not the same
24055    /// thing, because a later `RETURN` puts the fields back and a later
24056    /// `RETURN` after a `NOCONTENT` does not.
24057    #[test]
24058    fn a_return_of_nothing_is_not_the_same_as_nocontent() {
24059        let mut f = Fixture::new();
24060        corpus(&mut f);
24061        let bare = "*2\r\n:1\r\n$3\r\nd:3\r\n";
24062        assert_eq!(
24063            f.run(&[b"FT.SEARCH", b"sx", b"delta", b"RETURN", b"0"]),
24064            bare
24065        );
24066        assert_eq!(
24067            f.run(&[
24068                b"FT.SEARCH",
24069                b"sx",
24070                b"delta",
24071                b"NOCONTENT",
24072                b"RETURN",
24073                b"1",
24074                b"t"
24075            ]),
24076            bare
24077        );
24078        assert_eq!(
24079            f.run(&[
24080                b"FT.SEARCH",
24081                b"sx",
24082                b"delta",
24083                b"RETURN",
24084                b"0",
24085                b"RETURN",
24086                b"1",
24087                b"t"
24088            ]),
24089            "*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"
24090        );
24091    }
24092
24093    /// The count after `RETURN` counts words and not fields, so the `AS` and
24094    /// the name after it are two of them.
24095    #[test]
24096    fn the_count_after_return_counts_words() {
24097        let mut f = Fixture::new();
24098        corpus(&mut f);
24099        // Two words is one renamed field, and the name is the one it comes
24100        // back under.
24101        assert_eq!(
24102            f.run(&[
24103                b"FT.SEARCH",
24104                b"sx",
24105                b"delta",
24106                b"RETURN",
24107                b"3",
24108                b"t",
24109                b"AS",
24110                b"x"
24111            ]),
24112            "*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"
24113        );
24114        // A count that stops on the `AS` has nothing to rename to, and one
24115        // that reaches past the last word is short an argument.
24116        assert_eq!(
24117            f.run(&[b"FT.SEARCH", b"sx", b"delta", b"RETURN", b"2", b"t", b"AS"]),
24118            "-SEARCH_PARSE_ARGS RETURN path AS name - must be accompanied with NAME\r\n"
24119        );
24120        assert_eq!(
24121            f.run(&[b"FT.SEARCH", b"sx", b"delta", b"RETURN", b"3", b"t", b"AS"]),
24122            "-SEARCH_PARSE_ARGS Bad arguments for RETURN: Expected an argument, but none provided\r\n"
24123        );
24124        // A count that stops before the `AS` asks for a field called `AS`,
24125        // which no key holds, and a field the key does not hold is left out
24126        // rather than sent empty.
24127        assert_eq!(
24128            f.run(&[b"FT.SEARCH", b"sx", b"delta", b"RETURN", b"1", b"AS"]),
24129            "*3\r\n:1\r\n$3\r\nd:3\r\n*0\r\n"
24130        );
24131    }
24132
24133    /// A `FILTER` is a numeric range written outside the query, and it is only
24134    /// the wrong way round on a field the schema holds as a number.
24135    #[test]
24136    fn a_filter_is_a_range_written_outside_the_query() {
24137        let mut f = Fixture::new();
24138        corpus(&mut f);
24139        assert_eq!(
24140            f.run(&[
24141                b"FT.SEARCH",
24142                b"sx",
24143                b"alpha",
24144                b"NOCONTENT",
24145                b"FILTER",
24146                b"n",
24147                b"2",
24148                b"4"
24149            ]),
24150            "*3\r\n:2\r\n$3\r\nd:2\r\n$3\r\nd:4\r\n"
24151        );
24152        assert_eq!(
24153            f.run(&[b"FT.SEARCH", b"sx", b"alpha", b"FILTER", b"n", b"2"]),
24154            "-SEARCH_PARSE_ARGS FILTER requires 3 arguments\r\n"
24155        );
24156        assert_eq!(
24157            f.run(&[b"FT.SEARCH", b"sx", b"alpha", b"FILTER", b"n", b"x", b"1"]),
24158            "-SEARCH_PARSE_ARGS Bad lower range: x\r\n"
24159        );
24160        assert_eq!(
24161            f.run(&[b"FT.SEARCH", b"sx", b"alpha", b"FILTER", b"n", b"2", b"1"]),
24162            "-SEARCH_SYNTAX Invalid numeric range (min > max): @n:[2.000000 1.000000]\r\n"
24163        );
24164        // The same range on a field that is not a number at all, and on a
24165        // field that is not there, answers nothing rather than refusing.
24166        for field in [b"g".as_slice(), b"nope"] {
24167            assert_eq!(
24168                f.run(&[b"FT.SEARCH", b"sx", b"alpha", b"FILTER", field, b"2", b"1"]),
24169                "*1\r\n:0\r\n"
24170            );
24171        }
24172    }
24173
24174    /// The index is resolved before the arguments after it are read, so a name
24175    /// that is not there answers about the name whatever else is wrong.
24176    #[test]
24177    fn the_index_is_found_before_the_arguments_are_read() {
24178        let mut f = Fixture::new();
24179        corpus(&mut f);
24180        let missing = "-SEARCH_INDEX_NOT_FOUND Index not found: nope\r\n";
24181        assert_eq!(f.run(&[b"FT.SEARCH", b"nope", b"alpha", b"BOGUS"]), missing);
24182        assert_eq!(
24183            f.run(&[b"FT.EXPLAIN", b"nope", b"alpha", b"BOGUS"]),
24184            missing
24185        );
24186        // And the arguments are read before the query is, so a query that
24187        // will not parse still answers about the argument.
24188        assert_eq!(
24189            f.run(&[b"FT.SEARCH", b"sx", b"@@@", b"BOGUS"]),
24190            "-SEARCH_ARG_UNRECOGNIZED Unknown argument `BOGUS` at position 1 for <main>\r\n"
24191        );
24192    }
24193
24194    /// `INKEYS` filters the answer before the total is taken, which is not
24195    /// where a client would guess it happens.
24196    #[test]
24197    fn inkeys_comes_off_the_total() {
24198        let mut f = Fixture::new();
24199        corpus(&mut f);
24200        assert_eq!(
24201            f.run(&[
24202                b"FT.SEARCH",
24203                b"sx",
24204                b"alpha",
24205                b"NOCONTENT",
24206                b"INKEYS",
24207                b"1",
24208                b"d:1"
24209            ]),
24210            "*2\r\n:1\r\n$3\r\nd:1\r\n"
24211        );
24212        assert_eq!(
24213            f.run(&[b"FT.SEARCH", b"sx", b"alpha", b"NOCONTENT", b"INKEYS", b"0"]),
24214            "*1\r\n:0\r\n"
24215        );
24216    }
24217
24218    /// The fields come from the database the session is on, and a row whose
24219    /// key will not load there is dropped from the reply and taken off the
24220    /// total.
24221    ///
24222    /// Measured against a real server, which follows a key on every database
24223    /// and then loads it from one.
24224    #[test]
24225    fn the_fields_are_read_from_the_session_database() {
24226        let mut f = Fixture::new();
24227        corpus(&mut f);
24228        f.run(&[b"SELECT", b"1"]);
24229        f.run(&[b"HSET", b"d:9", b"t", b"delta", b"n", b"9"]);
24230        // Both documents are in the index, and only one of them is in this
24231        // database.
24232        assert_eq!(
24233            f.run(&[b"FT.SEARCH", b"sx", b"delta", b"NOCONTENT"]),
24234            "*3\r\n:2\r\n$3\r\nd:3\r\n$3\r\nd:9\r\n"
24235        );
24236        assert_eq!(
24237            f.run(&[b"FT.SEARCH", b"sx", b"delta", b"RETURN", b"1", b"n"]),
24238            "*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"
24239        );
24240    }
24241
24242    /// The deeper protocol answers a map of five rather than an array, with
24243    /// every row a map of its own.
24244    #[test]
24245    fn the_third_protocol_answers_a_map_of_five() {
24246        let mut f = Fixture::new();
24247        corpus(&mut f);
24248        f.out = Out::new(Proto::Resp3);
24249        assert_eq!(
24250            f.run(&[b"FT.SEARCH", b"sx", b"delta", b"RETURN", b"1", b"n"]),
24251            concat!(
24252                "%5\r\n+attributes\r\n*0\r\n+format\r\n+STRING\r\n+results\r\n*1\r\n",
24253                "%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",
24254                "+values\r\n*0\r\n+total_results\r\n:1\r\n+warning\r\n*0\r\n"
24255            )
24256        );
24257    }
24258
24259    /// A window of nothing is a client asking for the count on its own, and a
24260    /// window of nothing that starts somewhere else is a contradiction all
24261    /// three commands refuse in the same words.
24262    #[test]
24263    fn a_window_of_nothing_has_to_start_at_the_top() {
24264        let mut f = Fixture::new();
24265        corpus(&mut f);
24266        let refused = "-SEARCH_LIMIT_OVER The `offset` of the LIMIT must be 0 when `num` is 0\r\n";
24267        assert_eq!(
24268            f.run(&[b"FT.SEARCH", b"sx", b"alpha", b"LIMIT", b"1", b"0"]),
24269            refused
24270        );
24271        assert_eq!(
24272            f.run(&[b"FT.EXPLAIN", b"sx", b"alpha", b"LIMIT", b"1", b"0"]),
24273            refused
24274        );
24275        assert_eq!(
24276            f.run(&[b"FT.AGGREGATE", b"sx", b"alpha", b"LIMIT", b"1", b"0"]),
24277            refused
24278        );
24279        assert_eq!(
24280            f.run(&[b"FT.AGGREGATE", b"sx", b"alpha", b"LIMIT", b"0", b"0"]),
24281            "*1\r\n:3\r\n"
24282        );
24283    }
24284
24285    /// An aggregation answers a count and then a list of properties for every
24286    /// row, which is empty until something asks for a field.
24287    #[test]
24288    fn an_aggregation_answers_a_count_and_then_the_properties() {
24289        let mut f = Fixture::new();
24290        corpus(&mut f);
24291        assert_eq!(
24292            f.run(&[b"FT.AGGREGATE", b"sx", b"alpha"]),
24293            "*4\r\n:1\r\n*0\r\n*0\r\n*0\r\n"
24294        );
24295        // Every row, and not the ten a search would have cut it down to. The
24296        // count in front of them is one because that is how far the reply had
24297        // got when it was written, which is measured against a real server.
24298        assert_eq!(
24299            f.run(&[b"FT.AGGREGATE", b"sx", b"alpha", b"LOAD", b"1", b"@t"]),
24300            concat!(
24301                "*4\r\n:1\r\n*2\r\n$1\r\nt\r\n$10\r\nalpha beta\r\n",
24302                "*2\r\n$1\r\nt\r\n$11\r\nalpha gamma\r\n",
24303                "*2\r\n$1\r\nt\r\n$16\r\nalpha beta gamma\r\n"
24304            )
24305        );
24306        // Ascending document number, because nothing sorts the answer. The
24307        // second and fourth documents are the ones the window lands on and the
24308        // best scoring one is not among them.
24309        assert_eq!(
24310            f.run(&[
24311                b"FT.AGGREGATE",
24312                b"sx",
24313                b"alpha",
24314                b"LOAD",
24315                b"1",
24316                b"@n",
24317                b"LIMIT",
24318                b"1",
24319                b"2"
24320            ]),
24321            "*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"
24322        );
24323        // A query nothing answers is a count of nothing and no rows at all.
24324        assert_eq!(
24325            f.run(&[b"FT.AGGREGATE", b"sx", b"nope", b"LOAD", b"1", b"@t"]),
24326            "*1\r\n:0\r\n"
24327        );
24328    }
24329
24330    /// `LOAD` counts words rather than fields, names the property after the
24331    /// path unless an `AS` renames it, and reads everything the key holds when
24332    /// it is given a star.
24333    #[test]
24334    fn a_load_counts_words_and_can_rename_what_it_reads() {
24335        let mut f = Fixture::new();
24336        corpus(&mut f);
24337        // Three words, which are the path, the `AS` and the name.
24338        assert_eq!(
24339            f.run(&[
24340                b"FT.AGGREGATE",
24341                b"sx",
24342                b"alpha",
24343                b"LOAD",
24344                b"3",
24345                b"@t",
24346                b"AS",
24347                b"text"
24348            ]),
24349            concat!(
24350                "*4\r\n:1\r\n*2\r\n$4\r\ntext\r\n$10\r\nalpha beta\r\n",
24351                "*2\r\n$4\r\ntext\r\n$11\r\nalpha gamma\r\n",
24352                "*2\r\n$4\r\ntext\r\n$16\r\nalpha beta gamma\r\n"
24353            )
24354        );
24355        assert_eq!(
24356            f.run(&[
24357                b"FT.AGGREGATE",
24358                b"sx",
24359                b"alpha",
24360                b"LOAD",
24361                b"*",
24362                b"LIMIT",
24363                b"0",
24364                b"1"
24365            ]),
24366            concat!(
24367                "*2\r\n:1\r\n*6\r\n$1\r\nt\r\n$10\r\nalpha beta\r\n",
24368                "$1\r\ng\r\n$5\r\naa,bb\r\n$1\r\nn\r\n$1\r\n1\r\n"
24369            )
24370        );
24371        // A field the key does not hold is left out rather than sent empty.
24372        assert_eq!(
24373            f.run(&[
24374                b"FT.AGGREGATE",
24375                b"sx",
24376                b"alpha",
24377                b"LOAD",
24378                b"2",
24379                b"@n",
24380                b"@nope",
24381                b"LIMIT",
24382                b"0",
24383                b"2"
24384            ]),
24385            "*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"
24386        );
24387    }
24388
24389    /// The `LOAD` grammar, which has four ways to go wrong and one of them is
24390    /// only reported once the rest of the argument list has read cleanly.
24391    #[test]
24392    fn a_load_refuses_a_count_it_cannot_use() {
24393        let mut f = Fixture::new();
24394        corpus(&mut f);
24395        let head = "-SEARCH_PARSE_ARGS Bad arguments for LOAD: ";
24396        assert_eq!(
24397            f.run(&[b"FT.AGGREGATE", b"sx", b"alpha", b"LOAD", b"x"]),
24398            format!("{head}Expected number of fields or `*`\r\n")
24399        );
24400        assert_eq!(
24401            f.run(&[b"FT.AGGREGATE", b"sx", b"alpha", b"LOAD", b"-1", b"@t"]),
24402            format!("{head}Value is outside acceptable bounds\r\n")
24403        );
24404        assert_eq!(
24405            f.run(&[b"FT.AGGREGATE", b"sx", b"alpha", b"LOAD", b"5", b"@t"]),
24406            format!("{head}Expected an argument, but none provided\r\n")
24407        );
24408        assert_eq!(
24409            f.run(&[b"FT.AGGREGATE", b"sx", b"alpha", b"LOAD"]),
24410            format!("{head}Expected an argument, but none provided\r\n")
24411        );
24412        // A count that runs out on the `AS` is held back, because the word
24413        // after it is read as an argument of its own and may be worth an error
24414        // of its own. Nothing follows here, so the held back line is the one.
24415        assert_eq!(
24416            f.run(&[
24417                b"FT.AGGREGATE",
24418                b"sx",
24419                b"alpha",
24420                b"LOAD",
24421                b"2",
24422                b"@t",
24423                b"AS"
24424            ]),
24425            "-SEARCH_PARSE_ARGS LOAD path AS name - must be accompanied with NAME\r\n"
24426        );
24427        // And here the word after it is one an aggregation stops taking once a
24428        // step has been read, so that is what the client hears about.
24429        assert_eq!(
24430            f.run(&[
24431                b"FT.AGGREGATE",
24432                b"sx",
24433                b"alpha",
24434                b"LOAD",
24435                b"2",
24436                b"@t",
24437                b"AS",
24438                b"VERBATIM"
24439            ]),
24440            "-SEARCH_ARG_UNRECOGNIZED Unknown argument `VERBATIM` at position 5 for <main>\r\n"
24441        );
24442        // A `LOAD 0` is a step that names nothing. It shuts the same door
24443        // without becoming a loader, so the count stays the one a query with no
24444        // `LOAD` gets.
24445        assert_eq!(
24446            f.run(&[
24447                b"FT.AGGREGATE",
24448                b"sx",
24449                b"alpha",
24450                b"LOAD",
24451                b"0",
24452                b"LIMIT",
24453                b"0",
24454                b"1"
24455            ]),
24456            "*2\r\n:1\r\n*0\r\n"
24457        );
24458    }
24459
24460    /// Reading a step of the pipeline stops the words about the search itself
24461    /// being taken, and `LIMIT` and `TIMEOUT` are not steps.
24462    #[test]
24463    fn a_pipeline_step_closes_the_door_on_the_search_words() {
24464        let mut f = Fixture::new();
24465        corpus(&mut f);
24466        assert_eq!(
24467            f.run(&[
24468                b"FT.AGGREGATE",
24469                b"sx",
24470                b"alpha",
24471                b"LOAD",
24472                b"1",
24473                b"@t",
24474                b"VERBATIM"
24475            ]),
24476            "-SEARCH_ARG_UNRECOGNIZED Unknown argument `VERBATIM` at position 4 for <main>\r\n"
24477        );
24478        assert_eq!(
24479            f.run(&[
24480                b"FT.AGGREGATE",
24481                b"sx",
24482                b"alpha",
24483                b"LIMIT",
24484                b"0",
24485                b"1",
24486                b"VERBATIM"
24487            ]),
24488            "*2\r\n:1\r\n*0\r\n"
24489        );
24490        // Three words a search takes that this command names in its refusal
24491        // rather than calling them unknown.
24492        for word in [b"RETURN".as_slice(), b"SUMMARIZE", b"HIGHLIGHT"] {
24493            let name = core::str::from_utf8(word).expect("the three words are text");
24494            assert_eq!(
24495                f.run(&[b"FT.AGGREGATE", b"sx", b"alpha", word]),
24496                format!("-SEARCH_PARSE_ARGS {name} is not supported on FT.AGGREGATE\r\n")
24497            );
24498        }
24499    }
24500
24501    /// `ADDSCORES` writes the score as a property to twelve significant digits
24502    /// where `WITHSCORES` writes it beside the row in full.
24503    #[test]
24504    fn addscores_writes_a_shorter_score_than_withscores() {
24505        let mut f = Fixture::new();
24506        corpus(&mut f);
24507        assert_eq!(
24508            f.run(&[
24509                b"FT.AGGREGATE",
24510                b"sx",
24511                b"alpha",
24512                b"ADDSCORES",
24513                b"LOAD",
24514                b"1",
24515                b"@n",
24516                b"LIMIT",
24517                b"0",
24518                b"2"
24519            ]),
24520            concat!(
24521                "*3\r\n:1\r\n",
24522                "*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",
24523                "*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"
24524            )
24525        );
24526        // `NOCONTENT` takes the properties away and leaves whatever was asked
24527        // for beside them, and a sort key is always null because nothing sorts
24528        // by one yet.
24529        assert_eq!(
24530            f.run(&[
24531                b"FT.AGGREGATE",
24532                b"sx",
24533                b"alpha",
24534                b"NOCONTENT",
24535                b"WITHSCORES",
24536                b"LIMIT",
24537                b"0",
24538                b"2"
24539            ]),
24540            "*3\r\n:1\r\n$18\r\n0.3566749439387324\r\n$18\r\n0.3566749439387324\r\n"
24541        );
24542        assert_eq!(
24543            f.run(&[
24544                b"FT.AGGREGATE",
24545                b"sx",
24546                b"alpha",
24547                b"WITHSORTKEYS",
24548                b"LOAD",
24549                b"1",
24550                b"@n",
24551                b"LIMIT",
24552                b"0",
24553                b"1"
24554            ]),
24555            "*3\r\n:1\r\n$-1\r\n*2\r\n$1\r\nn\r\n$1\r\n1\r\n"
24556        );
24557    }
24558
24559    /// The one scorer that has to see the whole answer first turns the count
24560    /// into the real total and hands the rows back backwards.
24561    #[test]
24562    fn a_normalising_scorer_answers_the_rows_backwards() {
24563        let mut f = Fixture::new();
24564        corpus(&mut f);
24565        assert_eq!(
24566            f.run(&[
24567                b"FT.AGGREGATE",
24568                b"sx",
24569                b"alpha",
24570                b"SCORER",
24571                b"BM25STD.NORM",
24572                b"ADDSCORES",
24573                b"LOAD",
24574                b"1",
24575                b"@n",
24576                b"LIMIT",
24577                b"1",
24578                b"2"
24579            ]),
24580            concat!(
24581                "*3\r\n:3\r\n",
24582                "*4\r\n$7\r\n__score\r\n$1\r\n1\r\n$1\r\nn\r\n$1\r\n2\r\n",
24583                "*4\r\n$7\r\n__score\r\n$1\r\n1\r\n$1\r\nn\r\n$1\r\n1\r\n"
24584            )
24585        );
24586        // Without `ADDSCORES` nothing on the row needs the score, so the rows
24587        // come back the way every other query answers them.
24588        assert_eq!(
24589            f.run(&[
24590                b"FT.AGGREGATE",
24591                b"sx",
24592                b"alpha",
24593                b"SCORER",
24594                b"BM25STD.NORM",
24595                b"LOAD",
24596                b"1",
24597                b"@n",
24598                b"LIMIT",
24599                b"1",
24600                b"2"
24601            ]),
24602            "*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"
24603        );
24604    }
24605
24606    /// The deeper protocol answers the same map of five a search answers, with
24607    /// the `id` gone because an aggregation is about the properties.
24608    #[test]
24609    fn an_aggregation_answers_a_map_of_five_as_well() {
24610        let mut f = Fixture::new();
24611        corpus(&mut f);
24612        f.out = Out::new(Proto::Resp3);
24613        assert_eq!(
24614            f.run(&[
24615                b"FT.AGGREGATE",
24616                b"sx",
24617                b"alpha",
24618                b"ADDSCORES",
24619                b"WITHSCORES",
24620                b"WITHSORTKEYS",
24621                b"LOAD",
24622                b"1",
24623                b"@n",
24624                b"LIMIT",
24625                b"0",
24626                b"1"
24627            ]),
24628            concat!(
24629                "%5\r\n+attributes\r\n*0\r\n+format\r\n+STRING\r\n+results\r\n*1\r\n",
24630                "%4\r\n+score\r\n,0.3566749439387324\r\n+sortkey\r\n_\r\n",
24631                "+extra_attributes\r\n%2\r\n$7\r\n__score\r\n$14\r\n0.356674943939\r\n",
24632                "$1\r\nn\r\n$1\r\n1\r\n+values\r\n*0\r\n",
24633                "+total_results\r\n:1\r\n+warning\r\n*0\r\n"
24634            )
24635        );
24636        // The count is worked out from the rows the reply reached under this
24637        // protocol, where under RESP2 it is worked out from the first of them.
24638        assert_eq!(
24639            f.run(&[
24640                b"FT.AGGREGATE",
24641                b"sx",
24642                b"alpha",
24643                b"NOCONTENT",
24644                b"LIMIT",
24645                b"0",
24646                b"1"
24647            ]),
24648            concat!(
24649                "%5\r\n+attributes\r\n*0\r\n+format\r\n+STRING\r\n+results\r\n*1\r\n",
24650                "%1\r\n+values\r\n*0\r\n+total_results\r\n:1\r\n+warning\r\n*0\r\n"
24651            )
24652        );
24653    }
24654}