Skip to main content

yo_resp/dispatch/
mod.rs

1//! From a decoded command to a written reply.
2//!
3//! This is the layer Y23 exists to keep thin. The wire and the embedded API
4//! both have to reach the same code, or there are two implementations of `INCR`
5//! and one of them is wrong. So `yo-kv` holds one method per command taking
6//! ordinary Rust values, and everything here is about the part that is only
7//! true on a socket: which keyword goes where, which combinations a real server
8//! refuses, and which of the two protocols the answer is spelled in.
9//!
10//! # What runs a command
11//!
12//! [`Server`] holds the databases. [`Session`] holds what one connection has
13//! chosen: which database, which name it gave itself, what its id is. The
14//! protocol version lives in the [`Out`] because that is what needs it, and
15//! `HELLO` changes it there.
16//!
17//! ```
18//! use yo_resp::{Argv, Limits, Out, Proto};
19//! use yo_resp::dispatch::{Args, Flow, Server, Session, execute};
20//!
21//! let mut server = Server::new();
22//! let mut session = Session::new(1);
23//! let mut out = Out::new(Proto::Resp2);
24//!
25//! let wire = b"*3\r\n$3\r\nSET\r\n$1\r\nk\r\n$1\r\nv\r\n";
26//! let mut argv = Argv::new();
27//! argv.decode(wire, &Limits::default())?;
28//! let flow = execute(&mut server, &mut session, Args::new(&argv, wire), &mut out);
29//!
30//! assert_eq!(flow, Flow::Continue);
31//! assert_eq!(out.as_slice(), b"+OK\r\n");
32//! # Ok::<(), yo_resp::ProtocolError>(())
33//! ```
34//!
35//! # Errors are values until the last moment
36//!
37//! A command body returns a [`Result`], and this module turns the error into
38//! the line that goes on the wire. That is what keeps the same body usable from
39//! the embedded API, where an error is a value with a [`Code`] on it and not a
40//! sentence to be parsed.
41//!
42//! The reply buffer is rolled back to where it was before a failing command
43//! wrote anything, so a body that checks its arguments halfway through cannot
44//! leave half a reply in front of the error.
45//!
46//! # Nothing here allocates
47//!
48//! Arguments are slices of the connection's read buffer, keywords are compared
49//! in place, numbers are written straight into the reply, and the pairs of
50//! `MSET` reach the store as an iterator rather than a `Vec`. The two places
51//! that do allocate, an error message and the text of `INFO`, say so and wrap
52//! it, because a shard thread that allocates aborts.
53
54mod args;
55mod arrays;
56mod backup;
57mod bits;
58mod blocking;
59mod bloom;
60mod cms;
61mod cpu;
62mod cuckoo;
63mod geo;
64mod graph;
65mod hashes;
66mod himport;
67mod hll;
68mod indexing;
69mod json;
70mod keyspace;
71mod lists;
72mod lua;
73mod migrate;
74mod misses;
75mod multi;
76mod notify;
77mod pubsub;
78mod scan;
79mod scripting;
80mod search;
81mod server;
82mod sets;
83mod streams;
84mod strings;
85mod suggest;
86pub mod table;
87mod tdigest;
88mod topk;
89mod ts;
90mod vectors;
91mod vfilter;
92mod zsets;
93
94pub use args::Args;
95pub use blocking::{Parked, Waiters};
96pub(crate) use pubsub::Envelope;
97pub use server::parse_memory;
98pub use table::{COMMANDS, Spec, arity_ok, lookup};
99
100use crate::reply::Out;
101use std::cell::Cell;
102use std::path::{Path, PathBuf};
103use std::sync::atomic::Ordering::{Acquire, Relaxed, Release};
104use std::sync::atomic::{AtomicBool, AtomicU32, AtomicU64, AtomicUsize};
105use yo_common::lock::{Held, Lock};
106use yo_common::{Code, Error};
107use yo_kv::cold::Store;
108use yo_kv::{Clock, Db, Keyspace};
109use yo_search::Registry;
110
111use multi::Watches;
112use search::cursor::Cursors;
113
114/// How many databases a server has.
115///
116/// Redis's default is sixteen and its `databases` setting can change it. Ours
117/// is sixteen and cannot, which is why `CONFIG GET databases` can answer with a
118/// constant. Nothing in the design needs the number to be fixed; nothing yet
119/// needs it not to be.
120pub const DATABASES: usize = 16;
121
122/// Every database's bit in [`Server::dirty`], which is what a fresh server
123/// starts on so that the first maintenance turn asks all of them.
124///
125/// A `u64` holds sixteen bits with room to spare, and the assertion below is
126/// what turns raising [`DATABASES`] past sixty four into a build failure rather
127/// than a shift that silently drops the databases past the end.
128const ALL_DATABASES: u64 = if DATABASES == 64 {
129    u64::MAX
130} else {
131    (1u64 << DATABASES) - 1
132};
133const _: () = assert!(DATABASES <= 64);
134
135/// How many keys one command throws away before it leaves the rest to the next.
136///
137/// A bound and not a loop to the end, because this runs in front of a client
138/// that is waiting for its reply, and a server a long way over its limit would
139/// otherwise hold that client for as long as it took to walk all the way back
140/// under. Sixty four is a batch's worth of commands, so a server that went over
141/// by what one batch allocated comes back under in one command, and a server
142/// whose limit was just cut in half works through it over the next few thousand
143/// rather than in one long stall. Redis bounds the same loop by a time slice
144/// instead of a count and hands the rest to a timer; there is no timer here, so
145/// the rest goes to the next command that runs.
146const EVICT_BUDGET: usize = 64;
147
148/// The `maxstore` a server with no storage limit carries.
149///
150/// Sixteen exabytes, which is every disk there is and then some, so a server
151/// that set a limit this high and a server that set none behave the same way and
152/// the only difference is what `CONFIG GET maxstore` says. Zero cannot be the
153/// sentinel because zero is a limit with a meaning: nothing may live on the
154/// file.
155const NO_MAXSTORE: u64 = u64::MAX;
156
157/// What a server says to a command that would allocate when it has no room.
158///
159/// Redis's `shared.oomerr`, word for word including the full stop, because
160/// clients match on the `OOM` prefix and people match on the sentence.
161const OOM: &[u8] = b"command not allowed when used memory > 'maxmemory'.";
162
163/// What the connection should do after a command.
164#[derive(Debug, Clone, Copy, PartialEq, Eq)]
165pub enum Flow {
166    /// Read the next command.
167    Continue,
168    /// Write what is buffered and then close, which is what `QUIT` asks for.
169    Close,
170    /// Nothing was written and nothing is owed yet.
171    ///
172    /// The client is on the waiter list and its reply comes when a key it named
173    /// has something in it or when its deadline passes, whichever happens first.
174    /// Until then the connection stops reading commands, because a client that
175    /// is waiting for an answer is not a client that has sent another question.
176    Block,
177}
178
179/// A number one thread adds to and any thread may read.
180///
181/// The add is a load, an add and a store rather than a fetch and add, which on
182/// x86 is three ordinary instructions instead of one locked one. That is sound
183/// because every counter here has exactly one writer, which is what the slots
184/// below are for: two threads never hold the same counter, so nothing can be
185/// lost between the load and the store. A reader can be a command or two behind,
186/// and `INFO` on a running server is behind by the time the reply reaches the
187/// client anyway.
188#[derive(Debug, Default)]
189pub struct Counter(AtomicU64);
190
191impl Counter {
192    /// One more.
193    fn bump(&self) {
194        self.0.store(self.get().wrapping_add(1), Relaxed);
195    }
196
197    /// One fewer, stopping at zero.
198    ///
199    /// The floor is for the gauge, which is the number of open connections: a
200    /// close that arrives without its open, which nothing can do now and a
201    /// misplaced call could, is a number that stays at zero rather than one
202    /// that wraps to eighteen quintillion clients.
203    fn drop_one(&self) {
204        self.0.store(self.get().saturating_sub(1), Relaxed);
205    }
206
207    /// What it says.
208    fn get(&self) -> u64 {
209        self.0.load(Relaxed)
210    }
211
212    /// Back to zero, which is `CONFIG RESETSTAT`.
213    fn zero(&self) {
214        self.0.store(0, Relaxed);
215    }
216}
217
218/// The numbers `INFO` reports that this layer cannot see for itself.
219///
220/// The reactor owns the sockets, so the reactor is what knows how many clients
221/// there are. It counts them here and nothing else does anything with them
222/// except report them.
223#[derive(Debug, Default)]
224pub struct Stats {
225    /// Connections open right now.
226    clients: Counter,
227    /// Connections accepted since the server started.
228    connections: Counter,
229    /// Commands run since the server started, which this layer counts itself.
230    commands: Counter,
231}
232
233impl Stats {
234    /// A connection arrived.
235    pub fn opened(&self) {
236        self.clients.bump();
237        self.connections.bump();
238    }
239
240    /// A connection went away.
241    pub fn closed(&self) {
242        self.clients.drop_one();
243    }
244}
245
246/// Every thread's [`Stats`] added together, which is what `INFO` answers.
247#[derive(Debug, Clone, Copy, Default)]
248pub struct Totals {
249    /// Connections open right now.
250    pub clients: u64,
251    /// Connections accepted since the server started.
252    pub connections: u64,
253    /// Commands run since the server started.
254    pub commands: u64,
255}
256
257thread_local! {
258    /// Which set of counters the running thread writes into.
259    ///
260    /// Claimed the first time a thread counts anything and kept for as long as
261    /// the thread runs. It is a number rather than a pointer, so a thread that
262    /// has counted on one server and then counts on another lands in the same
263    /// place in both, and a process with two servers in it shares the numbering
264    /// between them. That is the tests and it is not `yodb`, which has one.
265    static SLOT: Cell<usize> = const { Cell::new(usize::MAX) };
266}
267
268/// What one thread keeps to itself.
269///
270/// One of these per thread and not one per server, because a number every
271/// thread writes to is a cache line every thread has to own to write to it, and
272/// at a few million commands a second that one line is the server. So each
273/// thread writes into its own and whoever needs the whole picture, which is
274/// `INFO` and the maintenance turn, puts the pieces together when it asks.
275///
276/// A cache line apart for the same reason, so that two threads writing at once
277/// are not two threads passing one line back and forth.
278#[derive(Debug)]
279#[repr(align(64))]
280struct Local {
281    /// What the reactor counts.
282    stats: Stats,
283    /// A counter per command, for `INFO commandstats`.
284    cmdstats: CommandStats,
285    /// Which databases this thread has run a command against since the
286    /// maintenance turn last took the mask.
287    ///
288    /// One bit per database. The thread ors into it and the turn takes the whole
289    /// of it with a swap, which is what keeps a mark that lands during the swap
290    /// from being lost: the worst that can happen is a bit the turn has already
291    /// taken being set again, and that costs one more look at a database with
292    /// nothing to collect.
293    dirty: AtomicU64,
294    /// The mask this thread's maintenance turn is working from.
295    ///
296    /// Its own and not a shared one, because a turn reads it in place and then
297    /// clears bits of it, and a shared mask cleared that way would lose whatever
298    /// another thread marked in between. Every thread turns a loop and every
299    /// loop maintains, so what stops the same work being done twice is not the
300    /// mask but the stripe lock underneath it: two threads that both look at
301    /// database nine take turns, and the second one finds nothing left to move.
302    ///
303    /// Starts with every database set, so a server that has just been built
304    /// looks at all of them once rather than waiting to be told about the ones
305    /// something was loaded into before any command ran.
306    turn: AtomicU64,
307    /// How many of this thread's clients are on the waiter list.
308    ///
309    /// The waiter list is one list behind one lock, and a thread can only answer
310    /// the waiters it parked itself, so a thread with none of its own has no
311    /// reason to take that lock at all. Without this the check is the server
312    /// wide count, and one client blocked anywhere puts every thread through the
313    /// shared lock after every command it runs and again on every disconnect.
314    ///
315    /// Only the thread this belongs to writes it, because parking, answering and
316    /// forgetting a waiter all happen on the thread that read the command, so
317    /// the load and the store either side of a change cannot lose one.
318    parked: AtomicUsize,
319}
320
321impl Default for Local {
322    fn default() -> Local {
323        Local {
324            stats: Stats::default(),
325            cmdstats: CommandStats::default(),
326            dirty: AtomicU64::new(0),
327            turn: AtomicU64::new(ALL_DATABASES),
328            parked: AtomicUsize::new(0),
329        }
330    }
331}
332
333impl Local {
334    /// Note that a command has run against these databases.
335    fn mark(&self, dbs: u64) {
336        self.dirty.store(self.dirty.load(Relaxed) | dbs, Relaxed);
337    }
338
339    /// Add `dbs` to what this thread's turn is going to look at.
340    fn note(&self, dbs: u64) {
341        self.turn.store(self.turn.load(Relaxed) | dbs, Relaxed);
342    }
343
344    /// Take `at` off the list of databases this thread's turn will look at.
345    fn done(&self, at: usize) {
346        self.turn
347            .store(self.turn.load(Relaxed) & !(1u64 << at), Relaxed);
348    }
349
350    /// Whether this thread's turn still has database `at` to look at.
351    fn wanted(&self, at: usize) -> bool {
352        self.turn.load(Relaxed) & (1u64 << at) != 0
353    }
354
355    /// Note that `n` more of this thread's clients are parked.
356    fn blocked(&self, n: usize) {
357        self.parked
358            .store(self.parked.load(Relaxed).saturating_add(n), Relaxed);
359    }
360
361    /// Note that `n` of them are not parked any more.
362    fn woke(&self, n: usize) {
363        self.parked
364            .store(self.parked.load(Relaxed).saturating_sub(n), Relaxed);
365    }
366}
367
368/// Room for one thread, which is what a server starts with.
369fn one_thread() -> Box<[Local]> {
370    slots(1)
371}
372
373/// Room for `threads` of them.
374fn slots(threads: usize) -> Box<[Local]> {
375    (0..threads.max(1)).map(|_| Local::default()).collect()
376}
377
378/// Where the process was started, which is what `dir` defaults to.
379///
380/// A dot if the working directory cannot be read, which happens when it has
381/// been deleted out from under a running process. That is not a reason to
382/// refuse to start a server, and it leaves `BACKUP` to fail with the real error
383/// from the filesystem if anybody asks for one.
384fn working_dir() -> PathBuf {
385    std::env::current_dir().unwrap_or_else(|_| PathBuf::from("."))
386}
387
388/// One command's counters, for `INFO commandstats`.
389///
390/// Three of Redis's five. `usec` and `usec_per_call` are not here because
391/// nothing times a command, and timing one means two clock reads around a call
392/// that takes tens of nanoseconds to begin with. Redis pays that because Redis
393/// has room for it; this does not, and a zero under a name that says microseconds
394/// is worse than an absent field, which is the same rule the rest of `INFO`
395/// follows.
396#[derive(Debug, Clone, Copy, Default)]
397pub struct CommandStat {
398    /// Times the command ran, whatever it answered.
399    pub calls: u64,
400    /// Times it was turned away before it ran, which is the wrong number of
401    /// arguments or no room under `maxmemory`.
402    pub rejected: u64,
403    /// Times it ran and answered with an error.
404    pub failed: u64,
405}
406
407impl CommandStat {
408    /// Whether this command has ever been seen.
409    ///
410    /// A row that has not is left out of the reply, which is what Redis does and
411    /// is why the section is a handful of lines on a working server rather than
412    /// one line per command in the table.
413    const fn seen(&self) -> bool {
414        self.calls != 0 || self.rejected != 0 || self.failed != 0
415    }
416}
417
418/// One command's counters as one thread keeps them.
419///
420/// The same three numbers as [`CommandStat`], which is what they add up to when
421/// `INFO` asks. This is the written form and that is the read one.
422#[derive(Debug, Default)]
423struct Row {
424    /// Times the command ran.
425    calls: Counter,
426    /// Times it was turned away before it ran.
427    rejected: Counter,
428    /// Times it ran and answered with an error.
429    failed: Counter,
430}
431
432/// A counter per command, indexed the way [`table::index_of`] says.
433///
434/// A flat array and not a map, because the dispatcher is already holding the
435/// spec and the spec's position in the table is two addresses subtracted. That
436/// makes the counting a load, an add and a store on a row the previous command
437/// of the same name has already pulled into cache.
438#[derive(Debug)]
439struct CommandStats(Box<[Row]>);
440
441impl Default for CommandStats {
442    fn default() -> CommandStats {
443        CommandStats((0..table::count()).map(|_| Row::default()).collect())
444    }
445}
446
447impl CommandStats {
448    /// The row for one command.
449    fn at(&self, spec: &'static Spec) -> &Row {
450        &self.0[table::index_of(spec)]
451    }
452}
453
454/// Where a database gets its store from, asked by database number.
455///
456/// `None` means that database cannot have one. The caller owns whatever the
457/// stores are cut out of, which for `yodb` is one `.yo` file with a log per
458/// database, and this crate never learns what any of that is.
459pub type StoreSource = dyn FnMut(usize) -> Option<Store> + Send;
460
461/// Every thread that runs commands here shares this server, so it has to be
462/// `Send` and `Sync`, and the check is here so that a type added to it that is
463/// neither is a compile error where it was added rather than an error in the
464/// code that starts the threads.
465const _: () = {
466    const fn shareable<T: Send + Sync>() {}
467    shareable::<Server>();
468};
469
470/// Everything a server holds.
471///
472/// One per process, however many threads are serving out of it. What is inside
473/// is either shared outright, which is the counters and the settings, or behind
474/// a lock, which is the stripes and the few pieces of state a command can
475/// change. What makes this a server rather than a shard is that it is the whole
476/// of what a connection can address.
477pub struct Server {
478    dbs: Vec<Db>,
479    /// How many stripes each database is cut into, the same for all of them.
480    ///
481    /// Kept here as well as in each database so that the flat slot arithmetic
482    /// below is a multiply and a divide against a field on the server rather
483    /// than a walk asking each database how wide it is.
484    width: usize,
485    clock: Clock,
486    started_ms: u64,
487    /// Where the next maintenance turn starts looking, so that a database
488    /// under constant write load cannot hold the other fifteen's space.
489    ///
490    /// Shared, because compaction is asked for from two places: the maintenance
491    /// turn, which is one thread, and a command that went over the memory limit
492    /// and is trying to get back under it, which is any thread. Two threads that
493    /// read the same cursor start on the same database, and what that costs is
494    /// one of them finding the other has already moved what was there.
495    next_db: AtomicUsize,
496    /// One bit per database, set when a command ran against it.
497    ///
498    /// The maintenance turn after every batch used to ask all sixteen
499    /// databases whether they had anything to collect, and asking costs a load
500    /// and a store in each one. Fifteen of those are cold lines on a server
501    /// where every client is on database zero, which is every server, and the
502    /// answer is no every time. This is the cheap half of the question: a
503    /// database nobody has touched since it last said no cannot have started
504    /// saying yes.
505    ///
506    /// What the connections are holding, kept by the engine.
507    ///
508    /// Shared, because every thread has connections and the memory total is one
509    /// total. Each thread adds and subtracts its own change rather than storing
510    /// a figure it worked out, so two threads whose buffers grew in the same
511    /// moment both count.
512    conn_bytes: AtomicUsize,
513    /// The `maxmemory` limit in bytes, zero when there is not one.
514    ///
515    /// Zero is the default and it is the whole reason the check in front of
516    /// every write is one comparison against a field that is already warm. It
517    /// is read by every command on every thread and written by a client that
518    /// sends `CONFIG SET`, so it is a number the threads can share rather than
519    /// a field one of them owns.
520    maxmemory: AtomicU64,
521    /// Where a database gets a store from the first time it needs one.
522    ///
523    /// A closure and not a store, because there are sixteen databases and a
524    /// server that fills memory on database zero should not have opened
525    /// anything for the other fifteen. Nothing is asked of this until a memory
526    /// limit is actually reached, so a server that never fills memory never
527    /// opens a file, and a server that has no file never has one of these.
528    ///
529    /// `None` from the closure means that database cannot have one, which is
530    /// how the caller says the file it opened has no more room for logs.
531    ///
532    /// Behind a lock because it is a closure the caller gave us and there is no
533    /// saying it can be run by two threads at once. It is asked once per
534    /// database, the first time that database has to move something, so a
535    /// server that has reached its memory limit takes this lock sixteen times
536    /// in its life.
537    store: Lock<Option<Box<StoreSource>>>,
538    /// The `maxstore` limit in bytes, `None` when there is not one.
539    ///
540    /// The storage limit, and the other half of the inversion `14` section 4.1
541    /// describes. `maxmemory` is a limit on memory and the right answer to a
542    /// memory limit on a system with a file under it is to move data to the
543    /// file, not to delete it. Deleting is the right answer to a limit on the
544    /// file, and this is that limit.
545    ///
546    /// Zero is not "no limit" here, which is the one place this reads
547    /// differently from `maxmemory` and is the difference that makes a drop in
548    /// cache possible. A storage budget of zero bytes means nothing may live on
549    /// the file, so migration cannot make room and eviction is the only thing
550    /// left, which is Redis exactly. `None` is no limit and is the default,
551    /// which with `noeviction` means the database grows until the disk is full
552    /// and then writes fail, which is what a database does.
553    ///
554    /// Shared between the threads the same way `maxmemory` is, and no limit is
555    /// [`NO_MAXSTORE`] rather than a second field saying whether the first one
556    /// counts. Two fields cannot be read as one, and a limit that was on when
557    /// the bytes were read and off by the time the number was is a limit that
558    /// answers from a server that never existed.
559    maxstore: AtomicU64,
560    /// What [`Server::memory_bytes`] said at the last maintenance turn.
561    ///
562    /// The reading is a walk over every collection in every database and cannot
563    /// go on a command path, so the command path reads this instead and is at
564    /// most one batch behind. What that costs is overshoot: a server can end a
565    /// batch holding one batch's worth of allocation more than its limit before
566    /// anything notices. A batch is 64 commands, so that is bounded by what 64
567    /// commands can allocate and not by how long the server runs.
568    ///
569    /// Only kept up to date when there is a limit to judge it against. A server
570    /// with no `maxmemory` never reads it and never pays for it.
571    ///
572    /// Shared, because it is read in front of every write on every thread and
573    /// written by whichever thread last took a reading. A reader that catches it
574    /// mid write gets one of the two readings and both of them were true a
575    /// moment ago, which is all this number ever claims to be.
576    used: AtomicUsize,
577    /// Which database the next eviction draws from.
578    ///
579    /// Its own cursor and not [`Server::next_db`], because eviction and
580    /// compaction move at different rates and sharing one would make the
581    /// database that gets compacted depend on how many keys were evicted.
582    ///
583    /// Shared for the same reason [`Server::next_db`] is, and with the same
584    /// answer: two threads evicting at once may pick the same database, and one
585    /// of them finds the other got there first and moves on.
586    evict_db: AtomicUsize,
587    /// Which database the next active expiry sweep starts at.
588    ///
589    /// A third cursor for the same reason there is a second one. A sweep runs on
590    /// every turn of the loop and compaction runs when there is dead space, so
591    /// sharing a cursor would make which database gets swept depend on which one
592    /// was last collected.
593    expire_db: AtomicUsize,
594    /// The millisecond the last active expiry sweep ran on, so the next one on
595    /// the same millisecond does not bother.
596    ///
597    /// One for the server and not one per thread, so the sweeping a server does
598    /// is a function of how long it has been running and not of how many threads
599    /// it was started with. Two threads that read the same millisecond can both
600    /// decide to sweep, which costs one extra sweep of a budget that is already
601    /// small and cannot happen twice for the same millisecond more than once per
602    /// thread.
603    expire_ms: AtomicU64,
604    /// Clients parked on a blocking command.
605    ///
606    /// Behind a lock because a client parks on the thread that ran its command
607    /// and is woken by whichever thread later puts something under a key it
608    /// named, and those are not the same thread. The lock is only ever taken to
609    /// park somebody, to serve somebody or to forget a connection that has gone,
610    /// so a command that does not block never touches it.
611    waiters: Lock<Waiters>,
612    /// How many clients are parked.
613    ///
614    /// Beside the list rather than read out of it, because every command asks
615    /// whether anybody is waiting and nearly every answer is no. Taking a lock
616    /// to be told no would be a cache line every thread has to own to ask, which
617    /// is the cost the list was put behind a lock to avoid.
618    ///
619    /// Written under the lock, by whoever changed the list, so the number and
620    /// the list agree except while a change is in progress. A reader that asks
621    /// during one is told about the moment before it, and the worst that costs
622    /// is a walk of the list that serves nobody or one that has not started yet
623    /// and happens on the next command instead.
624    parked: AtomicUsize,
625    /// Sockets `MIGRATE` is holding open to the servers it has talked to.
626    ///
627    /// Empty on a server nobody has migrated a key out of, which is nearly all
628    /// of them, and it costs a vector's three words to be empty.
629    ///
630    /// Behind a lock because a socket cannot be written by two threads at once
631    /// and a cache of them cannot be searched by one while another is taking an
632    /// entry out. It is held for the whole of a migration, which is a round trip
633    /// to another server, so two threads migrating at the same time take turns.
634    /// That is the right way round: the alternative is a socket per thread per
635    /// peer, and a `MIGRATE` is not what a server spends its time on.
636    peers: Lock<migrate::Peers>,
637    /// What each thread that runs commands here keeps to itself.
638    ///
639    /// A fixed list, because a thread reading its own entry must not have the
640    /// list move under it, and how many threads there will be is known before
641    /// any of them starts. A server nobody told otherwise has one.
642    locals: Box<[Local]>,
643    /// How many entries have been handed out.
644    claimed: AtomicUsize,
645    /// The next client id, which is what `CLIENT ID` answers.
646    ///
647    /// On the server and not on a front, because CLIENT LIST and CLIENT KILL
648    /// name a client by this number across the whole server, and two threads
649    /// counting on their own would hand the same number to two clients. Starts
650    /// at one so that zero is never a client, which is what makes it usable as
651    /// the id of a command that came from nowhere.
652    next_client: AtomicU64,
653    /// Where `BACKUP` puts its files, and where `CONFIG GET dir` points.
654    ///
655    /// Absolute, and resolved once when the server is built rather than every
656    /// time somebody asks. `BACKUP LIST` answers absolute paths and a client is
657    /// entitled to hand one of them to a copy tool, so a relative path that
658    /// meant something different after a `chdir` would be a path that stops
659    /// working for reasons nobody could see.
660    dir: PathBuf,
661    /// What backup is running, if one is.
662    ///
663    /// On the server and not on a session, because a backup outlives the
664    /// connection that asked for it and any other connection can seal it.
665    ///
666    /// Behind a lock because there is one backup at a time and any thread can be
667    /// the one that starts, seals or abandons it. It is held while the base file
668    /// is written, which is what keeps two `BACKUP START` commands from writing
669    /// over each other's files.
670    backup: Lock<backup::State>,
671    /// Whether a sealed backup is sitting on disk.
672    ///
673    /// Beside the state rather than read out of it, because every batch of
674    /// commands asks whether there is a backup old enough to sweep away and on
675    /// nearly every server the answer is that there is no backup at all. A load
676    /// answers that. Written under the lock by whoever moved the phase, so a
677    /// reader that asks mid-change sees the moment before and sweeps one batch
678    /// later, which is a file staying on disk for a few microseconds longer than
679    /// it had to.
680    sealed: AtomicBool,
681    /// The search indexes and the names pointing at them.
682    ///
683    /// On the server and not on a database, which is the one collection in this
684    /// build that is. A real server keeps its indexes in the search module, the
685    /// module has one table, and `SELECT 1` followed by `FT._LIST` lists the
686    /// indexes made on database zero. `search.rs` has the rest of why.
687    ///
688    /// A server nobody has made an index on holds two empty vectors here, which
689    /// is six words and no allocation.
690    ///
691    /// Behind a lock because an index is made and dropped by whichever thread
692    /// ran the command, and the table it goes in is one table. Only the `FT`
693    /// commands take it, so nothing a working server spends its time on comes
694    /// through here.
695    search: Lock<Registry>,
696    /// The replies that came back in pieces and have pieces left.
697    ///
698    /// Beside the indexes rather than inside one, because a cursor is read
699    /// under its own number and a real server resolves the index name on a read
700    /// and then pays no attention to it, so a cursor made on one index reads
701    /// through the name of another. Behind a lock for the reason the registry is
702    /// behind one, and a server nobody has opened a cursor on holds an empty map
703    /// here.
704    cursors: Lock<Cursors>,
705    /// The script bodies `EVALSHA` runs, by their digests.
706    ///
707    /// On the server rather than on a connection, because that is the whole
708    /// point of the cache. A client loads its scripts once when it starts up,
709    /// on whichever connection it happened to open first, and then sends nothing
710    /// but digests forever after, from every connection in its pool.
711    ///
712    /// Behind a lock because loading is a write and every thread can be the one
713    /// doing it. Held only long enough to add a body or copy one out, never
714    /// across a run: a running script calls commands, and those take locks of
715    /// their own.
716    scripts: Lock<lua::Scripts>,
717    /// Every library `FUNCTION LOAD` has taken, and what each one registered.
718    ///
719    /// Data only. A callback is a Lua value and there is an interpreter per
720    /// thread, so what is here is the name, the code, the digest of the code and
721    /// one row per function, and every thread compiles the code for itself the
722    /// first time one of its clients calls into the library.
723    libraries: Lock<lua::library::Libraries>,
724    /// Set by `SHUTDOWN`, and read by whatever is turning the loop.
725    ///
726    /// A flag rather than an exit, because the command layer is not what owns
727    /// the process. It runs inside a batch that has other commands behind it
728    /// and inside a driver that has a socket file to take away and a file to
729    /// close, and a server that calls `exit` from a command handler skips all
730    /// of that. So the command says stop and the driver stops, on the same turn
731    /// and through the same door a signal uses.
732    stopping: AtomicBool,
733    /// Every key any connection is watching, with a stamp on each.
734    ///
735    /// Here and not on the connection, and that is the whole design of `WATCH`
736    /// rather than an implementation detail. A connection cannot see a write
737    /// another thread made, so what records the write has to sit beside the key.
738    /// See the `multi` module for the rest of it.
739    watches: Lock<Watches>,
740    /// How many watched keys there are, so the write path can ask without
741    /// taking the lock.
742    ///
743    /// Zero on every server nobody has sent `WATCH` to, which is very nearly all
744    /// of them, and that is what keeps the cost of watches on a server that has
745    /// none down to one relaxed load per write.
746    watched: AtomicUsize,
747    /// Who is listening on what, for pub/sub.
748    ///
749    /// Here and not on the connection for the reason the watches are: a publish
750    /// arrives on a connection that knows nothing about the subscribers, so what
751    /// finds them has to sit beside the name rather than beside the client. See
752    /// the `pubsub` module for the rest of it.
753    pubsub: Lock<pubsub::Registry>,
754    /// How many subscriptions there are, so a publish can ask without taking
755    /// the lock.
756    ///
757    /// Zero on every server nobody has subscribed on, which is what keeps
758    /// `PUBLISH` on a server with no listeners down to one relaxed load.
759    subs: AtomicUsize,
760    /// One inbox per thread, for messages published on another one.
761    ///
762    /// Its own array and not a field on [`Local`], which is a cache line per
763    /// thread precisely so that no other thread writes to it. A mailbox is a
764    /// line another thread is meant to write to, so it gets one of its own.
765    mail: Box<[pubsub::Mailbox]>,
766    /// Which classes of keyspace notification are turned on.
767    ///
768    /// Zero is off and is the default, so the read every write does costs one
769    /// relaxed load and a test. It is `notify-keyspace-events` and the bits are
770    /// Redis's own, kept in the `notify` module beside the two parsers that
771    /// turn them into the setting text and back.
772    notify: AtomicU32,
773}
774
775impl Server {
776    /// A server with [`DATABASES`] empty databases on the system clock.
777    #[must_use]
778    pub fn new() -> Server {
779        let clock = Clock::system();
780        Server {
781            dbs: (0..DATABASES)
782                .map(|_| Db::with_clock(clock.clone(), 1))
783                .collect(),
784            width: 1,
785            started_ms: clock.now_ms(),
786            clock,
787            next_db: AtomicUsize::new(0),
788            conn_bytes: AtomicUsize::new(0),
789            maxmemory: AtomicU64::new(0),
790            store: Lock::new(None),
791            maxstore: AtomicU64::new(NO_MAXSTORE),
792            used: AtomicUsize::new(0),
793            evict_db: AtomicUsize::new(0),
794            expire_db: AtomicUsize::new(0),
795            expire_ms: AtomicU64::new(0),
796            waiters: Lock::default(),
797            parked: AtomicUsize::new(0),
798            peers: Lock::default(),
799            locals: one_thread(),
800            claimed: AtomicUsize::new(0),
801            next_client: AtomicU64::new(1),
802            dir: working_dir(),
803            backup: Lock::default(),
804            sealed: AtomicBool::new(false),
805            search: Lock::new(Registry::new()),
806            cursors: Lock::default(),
807            scripts: Lock::default(),
808            libraries: Lock::default(),
809            stopping: AtomicBool::new(false),
810            watches: Lock::default(),
811            watched: AtomicUsize::new(0),
812            pubsub: Lock::default(),
813            subs: AtomicUsize::new(0),
814            notify: AtomicU32::new(0),
815            mail: pubsub::boxes(1),
816        }
817    }
818
819    /// A server whose databases are cut into `width` stripes each.
820    ///
821    /// Not reachable from the command line yet. Every command group answers on
822    /// a server of any width now and so does everything that walks a whole
823    /// database, and the tests run each group at a width of one and a width of
824    /// eight and check the two agree.
825    ///
826    /// What is left before this is what `--threads` sets is the engine. A
827    /// database being several objects is what makes more than one thread
828    /// possible, and it is not what makes more than one thread happen.
829    #[must_use]
830    pub fn with_width(width: usize) -> Server {
831        let mut server = Server::new();
832        // The server's own clock and not a fresh one, because a database
833        // reading a different clock from the server it is on is a database
834        // whose keys expire against a time nobody set.
835        let clock = server.clock.clone();
836        server.dbs = (0..DATABASES)
837            .map(|_| Db::with_clock(clock.clone(), width))
838            .collect();
839        server.width = server.dbs[0].width();
840        server
841    }
842
843    /// A server on a clock the caller moves by hand, for tests.
844    #[must_use]
845    pub fn with_clock(clock: Clock) -> Server {
846        Server {
847            dbs: (0..DATABASES)
848                .map(|_| Db::with_clock(clock.clone(), 1))
849                .collect(),
850            width: 1,
851            started_ms: clock.now_ms(),
852            clock,
853            next_db: AtomicUsize::new(0),
854            conn_bytes: AtomicUsize::new(0),
855            maxmemory: AtomicU64::new(0),
856            store: Lock::new(None),
857            maxstore: AtomicU64::new(NO_MAXSTORE),
858            used: AtomicUsize::new(0),
859            evict_db: AtomicUsize::new(0),
860            expire_db: AtomicUsize::new(0),
861            expire_ms: AtomicU64::new(0),
862            waiters: Lock::default(),
863            parked: AtomicUsize::new(0),
864            peers: Lock::default(),
865            locals: one_thread(),
866            claimed: AtomicUsize::new(0),
867            next_client: AtomicU64::new(1),
868            dir: working_dir(),
869            backup: Lock::default(),
870            sealed: AtomicBool::new(false),
871            search: Lock::new(Registry::new()),
872            cursors: Lock::default(),
873            scripts: Lock::default(),
874            libraries: Lock::default(),
875            stopping: AtomicBool::new(false),
876            watches: Lock::default(),
877            watched: AtomicUsize::new(0),
878            pubsub: Lock::default(),
879            subs: AtomicUsize::new(0),
880            notify: AtomicU32::new(0),
881            mail: pubsub::boxes(1),
882        }
883    }
884
885    /// One database, by index.
886    ///
887    /// A caller that knows which key it wants names the one stripe the key is
888    /// on rather than working over the whole thing, which is what `at` and its
889    /// neighbours on [`Db`] are for. A caller that is about a database rather
890    /// than about a key, which is the snapshot walk and a setting, works over
891    /// all of them.
892    ///
893    /// The database is marked as having had something run against it, which is
894    /// what this does that [`Server::striped_ref`] does not. Anything that only
895    /// reads asks for that one and leaves the mark alone.
896    ///
897    /// The borrow is shared, and what makes that enough is that a database is
898    /// several stripes behind a lock each. A caller that wants to change
899    /// something holds the stripe it is changing, so two threads working on two
900    /// keys work at once and two working on one key take turns, which is the
901    /// whole point of cutting a database up.
902    ///
903    /// # Panics
904    ///
905    /// If `i` is not a database. `SELECT` is the only way a client changes the
906    /// index and it checks, so an index that is out of range here is a bug in
907    /// the caller and not something a client can ask for.
908    pub fn striped(&self, i: usize) -> &Db {
909        self.mine().mark(1u64 << i);
910        &self.dbs[i]
911    }
912
913    /// Every keyspace on the server, which is every stripe of every database.
914    ///
915    /// What the aggregates walk. A total over the whole server is a total over
916    /// all of these and the stripe boundaries do not appear in it, which is
917    /// what makes the numbers `INFO` reports the same numbers whatever the
918    /// server was cut into.
919    fn keyspaces(&self) -> impl Iterator<Item = Held<'_, Keyspace>> {
920        self.dbs
921            .iter()
922            .flat_map(|db| (0..db.width()).map(|i| db.hold_stripe(i)))
923    }
924
925    /// How many keyspaces there are, counting every stripe of every database.
926    ///
927    /// The maintenance turns walk these rather than the databases, because a
928    /// stripe is the thing that holds an arena and a deadline heap and so it is
929    /// the thing that has anything to collect.
930    const fn slots(&self) -> usize {
931        DATABASES * self.width
932    }
933
934    /// Which database slot `i` belongs to.
935    const fn slot_db(&self, i: usize) -> usize {
936        i / self.width
937    }
938
939    /// Keyspace `i` of [`Server::slots`].
940    fn slot(&self, i: usize) -> Held<'_, Keyspace> {
941        let (db, stripe) = (i / self.width, i % self.width);
942        self.dbs[db].hold_stripe(stripe)
943    }
944
945    /// Where `BACKUP` writes and what `CONFIG GET dir` answers.
946    #[must_use]
947    pub fn dir(&self) -> &Path {
948        &self.dir
949    }
950
951    /// Point the server at a different directory, which `yodb serve --dir` does.
952    ///
953    /// Only before it is serving. There is no `CONFIG SET dir` here and there
954    /// is none on a real server either without turning protected configs on,
955    /// for the good reason that moving it out from under a running backup would
956    /// leave files nothing can find again.
957    pub fn set_dir(&mut self, dir: PathBuf) {
958        self.dir = dir;
959    }
960
961    /// Drop a sealed backup that has outlived `backup-sealed-ttl`.
962    ///
963    /// Once per batch, from the same maintenance turn that collects the arena.
964    /// It reads two fields and returns on a server that has never taken a
965    /// backup, which is nearly all of them.
966    pub fn backup_expire(&self) {
967        backup::expire(self);
968    }
969
970    /// Ask for the server to stop, which is what `SHUTDOWN` does.
971    ///
972    /// It sets a flag and returns. Nothing here closes a socket, flushes a file
973    /// or ends the process, because none of those belong to this layer, and a
974    /// batch that is halfway through still has to finish and be written out.
975    pub fn stop(&self) {
976        self.stopping.store(true, Release);
977    }
978
979    /// Whether somebody has asked the server to stop.
980    ///
981    /// Read once per turn by the loop, next to the flag a signal sets. The two
982    /// mean the same thing and are separate only because one arrives from the
983    /// operating system and the other from a client.
984    #[must_use]
985    pub fn stopping(&self) -> bool {
986        self.stopping.load(Acquire)
987    }
988
989    /// One database, by index, without taking it mutably.
990    ///
991    /// What the prefetch stage needs. It runs for all 64 commands in a batch
992    /// before any of them executes, so it cannot hold the mutable borrow `run`
993    /// is about to want, and it does not need one: warming a cache line reads
994    /// nothing and changes nothing.
995    #[must_use]
996    pub fn striped_ref(&self, i: usize) -> &Db {
997        &self.dbs[i]
998    }
999
1000    /// The stripe that answers for a database when a setting is read back.
1001    ///
1002    /// A ladder setting and an eviction policy are one number on a real server,
1003    /// and the fact that every stripe of every database carries a copy of it is
1004    /// ours rather than the client's problem. A write puts the same value on
1005    /// every one of them, so any stripe answers for all of them and this is the
1006    /// first one.
1007    fn settings(&self) -> Held<'_, Keyspace> {
1008        self.dbs[0].hold_stripe(0)
1009    }
1010
1011    /// Take a new clock reading, which every database is looking at.
1012    ///
1013    /// Once per turn of the event loop, which is the only place time moves. A
1014    /// command asking what the time is gets the answer the whole batch got, so
1015    /// two keys written by the same batch expire together (`04` section 3).
1016    ///
1017    /// Every thread does this on every turn of its own loop and they do not
1018    /// have to agree about when. The reading is only stored when the
1019    /// millisecond has changed, so what the threads are sharing is a line that
1020    /// is written about a thousand times a second and read millions.
1021    pub fn refresh_clock(&self) {
1022        self.clock.refresh();
1023    }
1024
1025    /// Move every clock here on by `ms`, for tests about expiry.
1026    ///
1027    /// The same thing [`Server::set_clock_ms`] does and by the same argument,
1028    /// except that it moves from wherever the clock is rather than to a stated
1029    /// moment, which is what a test that wants a key to have expired asks for.
1030    pub fn advance_clock_ms(&self, ms: u64) {
1031        let now = self.clock.now_ms() + ms;
1032        self.set_clock_ms(now);
1033    }
1034
1035    /// Move every clock here to `ms` by hand, for tests about expiry.
1036    ///
1037    /// A test cannot wait a hundred seconds and a test that waits a hundred
1038    /// milliseconds is a test that fails on a loaded machine, so time moves on
1039    /// request. The system clock underneath will overwrite this on the next
1040    /// [`Server::refresh_clock`], which is why this is only useful in a test
1041    /// that drives commands directly rather than through the event loop.
1042    pub fn set_clock_ms(&self, ms: u64) {
1043        self.clock.set(ms);
1044    }
1045
1046    /// Seconds since this server was built.
1047    #[must_use]
1048    pub fn uptime_secs(&self) -> u64 {
1049        self.clock.now_ms().saturating_sub(self.started_ms) / 1000
1050    }
1051
1052    /// Bytes held by every database's index and arena, plus the read and reply
1053    /// buffers of every connection.
1054    ///
1055    /// The buffers are in here because they are real and because Redis counts
1056    /// its own, so leaving them out would make the one number people compare
1057    /// flattering rather than true. They are not a database, so nothing in the
1058    /// keyspace can change them and the engine has to say when they move.
1059    #[must_use]
1060    pub fn memory_bytes(&self) -> usize {
1061        self.keyspaces().map(|db| db.memory_bytes()).sum::<usize>() + self.conn_bytes()
1062    }
1063
1064    /// What the keyspace itself is holding, live records only.
1065    ///
1066    /// `used_memory` minus this is what the store costs to run: the index, the
1067    /// space dead records are sitting in until compaction gets to them, and the
1068    /// connections' buffers.
1069    #[must_use]
1070    pub fn dataset_bytes(&self) -> usize {
1071        self.keyspaces()
1072            .map(|db| db.map().arena().live_bytes() as usize)
1073            .sum()
1074    }
1075
1076    /// Bytes the arenas are holding, live and dead together.
1077    #[must_use]
1078    pub fn arena_bytes(&self) -> usize {
1079        self.keyspaces()
1080            .map(|db| db.map().arena().reserved_bytes() as usize)
1081            .sum()
1082    }
1083
1084    /// Bytes the indexes are holding.
1085    #[must_use]
1086    pub fn index_bytes(&self) -> usize {
1087        self.keyspaces()
1088            .map(|db| db.map().index().memory_bytes())
1089            .sum()
1090    }
1091
1092    /// What arena compaction has cost, across every database.
1093    ///
1094    /// The write amplification of value separation, which is invisible from the
1095    /// outside otherwise: a client that writes a megabyte can leave the store
1096    /// copying several more, and the only sign of it without these is that the
1097    /// writes got slower.
1098    #[must_use]
1099    pub fn compaction(&self) -> yo_kv::Compaction {
1100        self.keyspaces().map(|db| db.map().compaction()).fold(
1101            yo_kv::Compaction::default(),
1102            |a, b| yo_kv::Compaction {
1103                walked: a.walked + b.walked,
1104                moved: a.moved + b.moved,
1105                bytes: a.bytes + b.bytes,
1106            },
1107        )
1108    }
1109
1110    /// Arena segments whose pages are real, across every database.
1111    #[must_use]
1112    pub fn segment_count(&self) -> usize {
1113        self.keyspaces()
1114            .map(|db| db.map().arena().resident_segments())
1115            .sum()
1116    }
1117
1118    /// What the connections' read and reply buffers are holding.
1119    #[must_use]
1120    pub fn conn_bytes(&self) -> usize {
1121        self.conn_bytes.load(Relaxed)
1122    }
1123
1124    /// Note that the connections are holding `delta` bytes more than they were,
1125    /// or fewer when it is negative.
1126    ///
1127    /// A delta and not a total because the alternative is a walk over every
1128    /// connection, and the walk would have to happen on a turn of the loop
1129    /// rather than when `INFO` asks, which puts the cost of a report on the
1130    /// command path of a server nobody is asking.
1131    pub fn note_conn_bytes(&self, delta: isize) {
1132        // A read and a write and not a fetch and add, because the number is a
1133        // sum of signed changes and the saturating part has to happen in the
1134        // middle. Two threads that change their buffers in the same instant can
1135        // lose one of the two changes, which is a report that is a few kilobytes
1136        // out until the next connection on either thread moves it again.
1137        self.conn_bytes
1138            .store(self.conn_bytes().saturating_add_signed(delta), Relaxed);
1139    }
1140
1141    /// Keys reclaimed by running into them after their deadline.
1142    #[must_use]
1143    pub fn expired_keys(&self) -> u64 {
1144        self.keyspaces().map(|db| db.expired_keys()).sum()
1145    }
1146
1147    /// Keys thrown away to make room, which is the other number entirely.
1148    #[must_use]
1149    pub fn evicted_keys(&self) -> u64 {
1150        self.keyspaces().map(|db| db.evicted_keys()).sum()
1151    }
1152
1153    /// Every command that has been seen, with its counters.
1154    ///
1155    /// Only the ones that have. A server reports a handful of lines rather than
1156    /// one per command in the table, which is what Redis does and is the
1157    /// difference between a section a person can read and one they cannot.
1158    pub fn command_stats(&self) -> impl Iterator<Item = (&'static str, CommandStat)> {
1159        (0..table::count())
1160            .map(|at| (table::name_at(at), self.command_stat(at)))
1161            .filter(|(_, row)| row.seen())
1162    }
1163
1164    /// One command's counters, added up over every thread.
1165    fn command_stat(&self, at: usize) -> CommandStat {
1166        let mut sum = CommandStat::default();
1167        for thread in &self.locals {
1168            let row = &thread.cmdstats.0[at];
1169            sum.calls += row.calls.get();
1170            sum.rejected += row.rejected.get();
1171            sum.failed += row.failed.get();
1172        }
1173        sum
1174    }
1175
1176    /// The counters the calling thread writes into.
1177    ///
1178    /// The first call on a thread claims a set and every call after it is a
1179    /// thread local read and an index. A server asked to count from more threads
1180    /// than it was built for wraps round and shares a set, which loses the odd
1181    /// count between two threads and cannot happen to a server `yodb serve`
1182    /// built, because that one is told how many threads it will have before it
1183    /// starts any of them.
1184    pub fn counted(&self) -> &Stats {
1185        &self.mine().stats
1186    }
1187
1188    /// The next client id, taken.
1189    ///
1190    /// Every accept anywhere on this server comes through here, so no two
1191    /// clients share a number however many threads are accepting.
1192    pub fn next_client(&self) -> u64 {
1193        self.next_client.fetch_add(1, Relaxed)
1194    }
1195
1196    /// Which set of per thread state the calling thread is on.
1197    ///
1198    /// The number a blocked client is filed under, so that the thread holding
1199    /// that client's connection is the one that answers it. Claims a set on the
1200    /// first call the same way [`Server::counted`] does, and gives back the same
1201    /// number every time after.
1202    pub fn my_slot(&self) -> usize {
1203        self.mine_at()
1204    }
1205
1206    /// Everything the calling thread keeps to itself.
1207    fn mine(&self) -> &Local {
1208        &self.locals[self.mine_at()]
1209    }
1210
1211    /// The calling thread's place in `locals`, claiming one if it has none.
1212    ///
1213    /// Wraps round when more threads count here than the server was built for,
1214    /// which shares a set between two threads and loses the odd count. That
1215    /// cannot happen to the server `yodb serve` builds, because it is told how
1216    /// many threads it will have before it starts any of them.
1217    fn mine_at(&self) -> usize {
1218        let mut slot = SLOT.get();
1219        if slot == usize::MAX {
1220            slot = self.claimed.fetch_add(1, Relaxed);
1221            SLOT.set(slot);
1222        }
1223        slot % self.locals.len()
1224    }
1225
1226    /// Every thread's numbers added together, which is what `INFO` reports.
1227    #[must_use]
1228    pub fn totals(&self) -> Totals {
1229        let mut sum = Totals::default();
1230        for thread in &self.locals {
1231            sum.clients += thread.stats.clients.get();
1232            sum.connections += thread.stats.connections.get();
1233            sum.commands += thread.stats.commands.get();
1234        }
1235        sum
1236    }
1237
1238    /// Put the totals back to zero, which is `CONFIG RESETSTAT`.
1239    ///
1240    /// Every thread's set and not only the one asking, since the number the
1241    /// client is resetting is the sum it was just shown. The open connections
1242    /// are left alone because that is a gauge and not a total: the connections
1243    /// are still open.
1244    pub fn reset_stats(&self) {
1245        for thread in &self.locals {
1246            thread.stats.connections.zero();
1247            thread.stats.commands.zero();
1248        }
1249    }
1250
1251    /// Say how many threads will run commands here, before any of them does.
1252    ///
1253    /// What it changes is how many sets of counters there are, and how many
1254    /// pub/sub mailboxes. Called once at startup by whoever is about to start
1255    /// the threads, and calling it on a running server throws away what has been
1256    /// counted so far, which is why it wants the server to itself.
1257    pub fn set_threads(&mut self, threads: usize) {
1258        self.locals = slots(threads);
1259        self.mail = pubsub::boxes(threads);
1260        self.claimed = AtomicUsize::new(0);
1261    }
1262
1263    /// The `maxmemory` limit in bytes, zero when there is not one.
1264    #[must_use]
1265    pub fn maxmemory(&self) -> u64 {
1266        self.maxmemory.load(Relaxed)
1267    }
1268
1269    /// Set the limit, and take a reading straight away.
1270    ///
1271    /// The reading is here rather than left to the next maintenance turn because
1272    /// a client that sets the limit and sends a write in the same batch expects
1273    /// the write to be judged against the limit it just set, and because the
1274    /// cached number is meaningless until the first time there is a limit to
1275    /// compare it with.
1276    ///
1277    /// Turning the limit on also turns on the running total every slab keeps of
1278    /// what its collections hold, and turning it off turns that back off, so a
1279    /// server with no limit is not paying to count something nobody reads. The
1280    /// first reading after switching it on is the walk that the total starts
1281    /// from, and it is the only walk.
1282    pub fn set_maxmemory(&self, bytes: u64) {
1283        self.maxmemory.store(bytes, Relaxed);
1284        for db in &self.dbs {
1285            db.track_memory(bytes != 0);
1286        }
1287        self.used.store(self.settled_memory(), Relaxed);
1288    }
1289
1290    /// Say where a database should get its store from when it needs one.
1291    ///
1292    /// This is what turns the eviction inversion on. Until it is called every
1293    /// database answers a memory limit by evicting, which is Redis, and after it
1294    /// is called a database under memory pressure moves values to whatever the
1295    /// closure hands back instead of throwing keys away.
1296    ///
1297    /// Called at most once per database and only under pressure, so a server
1298    /// that is given a file and never fills memory never touches it.
1299    pub fn set_store_source(
1300        &mut self,
1301        source: impl FnMut(usize) -> Option<Store> + Send + 'static,
1302    ) {
1303        *self.store.lock() = Some(Box::new(source));
1304    }
1305
1306    /// Whether this server has been given somewhere to put cold values.
1307    #[must_use]
1308    pub fn has_store_source(&self) -> bool {
1309        self.store.lock().is_some()
1310    }
1311
1312    /// Open database `at`'s store, if it has not got one and there is one to be
1313    /// had.
1314    ///
1315    /// A store that will not open leaves the database where it was, which is
1316    /// evicting, because a memory limit that cannot be answered by moving data
1317    /// still has to be answered.
1318    fn attach_store(&self, at: usize) {
1319        if self.slot(at).store_bytes().is_some() {
1320            return;
1321        }
1322        // The closure is run with its lock held and the keyspace is taken after
1323        // it has answered, so the file is opened once however many threads asked
1324        // for it and the stripe is not held while a file is being opened.
1325        let mut source = self.store.lock();
1326        let Some(source) = source.as_mut() else {
1327            return;
1328        };
1329        if let Some(blocks) = source(at) {
1330            self.slot(at).attach(blocks);
1331        }
1332    }
1333
1334    /// The `maxstore` limit in bytes, `None` when there is not one.
1335    #[must_use]
1336    pub fn maxstore(&self) -> Option<u64> {
1337        match self.maxstore.load(Relaxed) {
1338            NO_MAXSTORE => None,
1339            bytes => Some(bytes),
1340        }
1341    }
1342
1343    /// Set the storage limit, or clear it with `None`.
1344    ///
1345    /// Nothing is read here the way [`Server::set_maxmemory`] reads the memory
1346    /// total, because this limit is compared against a number the store keeps
1347    /// and answers on demand, not against a walk.
1348    pub fn set_maxstore(&self, bytes: Option<u64>) {
1349        self.maxstore.store(bytes.unwrap_or(NO_MAXSTORE), Relaxed);
1350    }
1351
1352    /// What every attached store is holding, for `INFO memory`.
1353    ///
1354    /// Zero on a server with nothing attached, which is not the same as a server
1355    /// whose file is empty, and [`Server::regime`] is the field that tells those
1356    /// two apart.
1357    #[must_use]
1358    pub fn store_bytes(&self) -> u64 {
1359        self.keyspaces().filter_map(|db| db.store_bytes()).sum()
1360    }
1361
1362    /// What the file has been asked to do, added up over every database.
1363    ///
1364    /// Counters and not levels, so they only ever go up and a run is the
1365    /// difference between two readings. G9 is a ratio over these: the faults a
1366    /// run took, divided by the point reads it issued, has to come out at 1.05
1367    /// or less with a working set ten times memory. There is no way to work that
1368    /// out from outside the server, so it is reported rather than inferred.
1369    ///
1370    /// A fault is a read that went to the store. Whether it also went to the
1371    /// device depends on the store: a log serves a read out of a resident page
1372    /// without touching anything. At ten times memory almost every fault is a
1373    /// real read, which is why the gate is written against this number, but the
1374    /// two are not the same thing and a run tight against the bar should be
1375    /// checked against what the operating system says.
1376    #[must_use]
1377    pub fn cold_stats(&self) -> yo_kv::tier::Stats {
1378        let mut total = yo_kv::tier::Stats::default();
1379        for db in self.keyspaces() {
1380            let Some(tier) = db.tier() else { continue };
1381            let s = tier.stats();
1382            total.demoted += s.demoted;
1383            total.promoted += s.promoted;
1384            total.faults += s.faults;
1385            total.served += s.served;
1386            total.bytes_out += s.bytes_out;
1387            total.bytes_in += s.bytes_in;
1388        }
1389        total
1390    }
1391
1392    /// Which way this server answers a memory limit, in one word for `INFO`.
1393    ///
1394    /// `evict` is Redis: a memory limit throws keys away. `migrate` is the
1395    /// inversion: a memory limit moves values to the file and nothing stored is
1396    /// lost. A server reports one word rather than leaving an operator to work
1397    /// it out from a limit, a setting and whether a file happens to be open.
1398    #[must_use]
1399    pub fn regime(&self) -> &'static str {
1400        if (0..self.slots()).any(|at| self.migrates(at)) {
1401            "migrate"
1402        } else {
1403            "evict"
1404        }
1405    }
1406
1407    /// Whether database `at` answers a memory limit by moving values to the
1408    /// file rather than by throwing keys away.
1409    ///
1410    /// Three things have to hold. There has to be somewhere to move them, which
1411    /// is a store attached to that database or a source that can open one, and
1412    /// on a server that was never given a file this is false everywhere and
1413    /// every database behaves exactly as it did.
1414    /// The storage budget has to be more than nothing, which is what
1415    /// `maxstore 0` says it is not. And the file has to be under that budget,
1416    /// because a full file is a storage limit reached and eviction is the right
1417    /// answer to a storage limit.
1418    fn migrates(&self, at: usize) -> bool {
1419        let cap = self.maxstore();
1420        if cap == Some(0) {
1421            return false;
1422        }
1423        // Out of the stripe first. A match keeps whatever it is looking at
1424        // alive for the whole of itself, and that would be this stripe held
1425        // across the arms for no reason.
1426        let bytes = self.slot(at).store_bytes();
1427        match bytes {
1428            Some(held) => cap.is_none_or(|cap| held < cap),
1429            // Nothing attached, but somewhere to get one from the moment this
1430            // database needs it, which is what makes the answer yes rather than
1431            // no. Opening it here would mean `INFO` opened files.
1432            None => self.store.lock().is_some(),
1433        }
1434    }
1435
1436    /// Take a fresh memory reading, which the maintenance turn does once a batch.
1437    ///
1438    /// Nothing at all when there is no limit, which is the default and is every
1439    /// server that has not asked for one.
1440    pub fn refresh_memory(&self) {
1441        if self.maxmemory() != 0 {
1442            self.used.store(self.settled_memory(), Relaxed);
1443        }
1444    }
1445
1446    /// [`Server::memory_bytes`], asked the cheap way.
1447    ///
1448    /// The same number. The difference is that this asks each database only
1449    /// about the collections that could have moved since the last time, which is
1450    /// what a batch touched rather than what the server holds, so it can be
1451    /// asked once a batch and again on every command that is over the limit.
1452    fn settled_memory(&self) -> usize {
1453        self.keyspaces()
1454            .map(|mut db| db.settled_memory_bytes())
1455            .sum::<usize>()
1456            + self.conn_bytes()
1457    }
1458
1459    /// Make room under the `maxmemory` limit, throwing keys away if that is what
1460    /// it takes. Answers whether there is anything left it could throw away.
1461    ///
1462    /// Redis runs the same thing from `processCommand` before every command and
1463    /// so does this: a client that writes has to be judged at the moment it
1464    /// writes, not a batch later, or the limit is a suggestion.
1465    ///
1466    /// Three things happen in the loop and all three are needed. Eviction picks
1467    /// a key and drops it. Compaction gives the pages back, because dropping a
1468    /// key marks its record dead and returns nothing on its own, so a loop that
1469    /// only evicted would throw the whole keyspace away and watch the number
1470    /// stay where it was. The reading is taken again each time round, because
1471    /// the two of them together are the only thing that moves it.
1472    ///
1473    /// # Why running out of budget is not a no
1474    ///
1475    /// `false` means there was nothing left to evict, which is `noeviction`, or
1476    /// a `volatile` policy on a database where nothing has a deadline, or a
1477    /// keyspace that is already empty. It does not mean the server is still over
1478    /// its limit, and that difference is Redis's: `performEvictions` answers
1479    /// `EVICT_FAIL` only when it has run out of things to delete, and
1480    /// `processCommand` refuses the client on that and on nothing else. Running
1481    /// out of time part way through a job it is doing well comes back as
1482    /// `EVICT_RUNNING` and the command goes through, because a server that is
1483    /// evicting steadily and refusing every write while it does it is worse for
1484    /// the client than a little overshoot.
1485    ///
1486    /// # What the limit is worth
1487    ///
1488    /// Space comes back a segment at a time and a segment is two megabytes, so
1489    /// this holds a server to its limit give or take a segment. A `maxmemory` of
1490    /// a few hundred megabytes gets what it asked for. A `maxmemory` of four
1491    /// megabytes is asking for a precision this store does not have.
1492    pub fn make_room(&self) -> bool {
1493        let limit = self.maxmemory();
1494        if limit == 0 || self.used.load(Relaxed) as u64 <= limit {
1495            return true;
1496        }
1497        // The cached reading is a batch old and the batch may have compacted
1498        // since, so take a fresh one before throwing anything away. It is the
1499        // settled reading and not the walk, so what this costs is the handful of
1500        // collections the last batch touched and not the whole database.
1501        let mut used = self.settled_memory();
1502        self.used.store(used, Relaxed);
1503        let mut budget = EVICT_BUDGET;
1504        while used as u64 > limit {
1505            let over = used - limit as usize;
1506            if !self.relieve_step(over) {
1507                return false;
1508            }
1509            self.compact_hard_step();
1510            used = self.settled_memory();
1511            self.used.store(used, Relaxed);
1512            budget -= 1;
1513            if budget == 0 {
1514                break;
1515            }
1516        }
1517        true
1518    }
1519
1520    /// Give back `over` bytes from whichever database can, by moving values to
1521    /// the file where there is one and by throwing keys away where there is not.
1522    ///
1523    /// The two answers are the eviction inversion and which one a database gets
1524    /// is [`Server::migrates`]. Answers whether anything was given back at all,
1525    /// and `false` is what refuses the client's write.
1526    ///
1527    /// A store that will not take the bytes counts as nothing given back, so the
1528    /// write is refused rather than turned into a deletion. A disk that is
1529    /// misbehaving is a reason to stop accepting writes and it is not a reason
1530    /// to start losing data that was accepted already.
1531    ///
1532    /// Round robin from a cursor rather than always starting at database zero,
1533    /// so a server using more than one of them does not empty the first before
1534    /// touching the second. Almost every server is on database zero only, where
1535    /// this is one call that answers and fifteen that say the map is empty.
1536    fn relieve_step(&self, over: usize) -> bool {
1537        let from = self.evict_db.load(Relaxed);
1538        for turn in 0..self.slots() {
1539            let i = (from + turn) % self.slots();
1540            // An empty keyspace has nothing to move and opening a log for one
1541            // would cost a resident page window to find that out.
1542            let used = !self.slot(i).is_empty();
1543            let gave = if used && self.migrates(i) {
1544                self.attach_store(i);
1545                // Whether it made room and not whether it moved a key. A round
1546                // that demoted nothing and handed back a segment is a round
1547                // that made room, and reading only the count refuses the write
1548                // that provoked it.
1549                self.slot(i)
1550                    .relieve(over)
1551                    .is_ok_and(yo_kv::tier::Relief::made_room)
1552            } else {
1553                // Against this database rather than whichever one the write
1554                // that provoked the eviction was aimed at, since the key that
1555                // goes is this one's. The funnel is already armed above and
1556                // this is a second one inside it, which is what the answer
1557                // going back into the drain is for.
1558                let armed = notify::arm(self, self.slot_db(i));
1559                let gone = self.slot(i).evict_one();
1560                notify::drain(self, armed);
1561                gone
1562            };
1563            if gave {
1564                self.evict_db.store((i + 1) % self.slots(), Relaxed);
1565                self.mine().mark(1u64 << self.slot_db(i));
1566                return true;
1567            }
1568        }
1569        false
1570    }
1571
1572    /// The sweep the shard loop calls, at most once a millisecond.
1573    ///
1574    /// The gate is the whole difference between this and [`Server::expire_step`].
1575    /// A maintenance slice runs on every turn of the loop and a turn is a
1576    /// hundred nanoseconds, so an ungated sweep would draw a fresh sample ten
1577    /// thousand times per millisecond and spend a real share of the shard on
1578    /// looking for keys that cannot have died since the last look. Nothing in a
1579    /// database changes fast enough to be worth asking about more often than the
1580    /// clock can tell the difference, and the clock here is milliseconds.
1581    ///
1582    /// A millisecond is also far finer than Redis, whose slow cycle runs at ten
1583    /// hertz, so this is not the thing that decides how promptly memory comes
1584    /// back. What it decides is that an idle server sweeps a thousand times a
1585    /// second rather than a million.
1586    pub fn expire_slice(&self, budget: usize) -> usize {
1587        let now = self.clock.now_ms();
1588        if now == self.expire_ms.load(Relaxed) {
1589            return 0;
1590        }
1591        self.expire_ms.store(now, Relaxed);
1592        self.expire_step(budget)
1593    }
1594
1595    /// Sweep dead keys out of the databases, spending at most `budget` looks.
1596    ///
1597    /// Answers what it spent, so the caller can charge its maintenance slice for
1598    /// it. See [`yo_kv::expiry`] for why the budget is in keys looked at.
1599    ///
1600    /// Round robin from its own cursor, and every database gets offered whatever
1601    /// is left of the budget rather than a sixteenth of it each, so a server on
1602    /// database zero only, which is nearly every server, spends the whole slice
1603    /// where the keys are. The fifteen empty ones cost a comparison apiece
1604    /// because a database with no key carrying a deadline says so without
1605    /// drawing anything.
1606    ///
1607    /// The cursor moves to the database after whichever one did the work, so two
1608    /// busy databases take turns instead of the lower numbered one starving the
1609    /// other.
1610    pub fn expire_step(&self, budget: usize) -> usize {
1611        let mut spent = 0;
1612        let from = self.expire_db.load(Relaxed);
1613        for turn in 0..self.slots() {
1614            if spent >= budget {
1615                break;
1616            }
1617            let i = (from + turn) % self.slots();
1618            // Nothing armed this thread, because nothing asked for any of this:
1619            // the shard loop is between commands. So the sweep arms and drains
1620            // around itself, and a key it takes is news to a subscriber in the
1621            // same way a key a lookup took on the way past is.
1622            let armed = notify::arm(self, self.slot_db(i));
1623            let c = self.slot(i).expire_cycle(budget - spent);
1624            notify::drain(self, armed);
1625            spent += c.examined;
1626            if c.expired > 0 {
1627                self.expire_db.store((i + 1) % self.slots(), Relaxed);
1628                self.mine().note(1u64 << self.slot_db(i));
1629            }
1630        }
1631        spent
1632    }
1633
1634    /// One slice of compaction for a server that is over its limit.
1635    ///
1636    /// Takes the databases in the same order [`Server::compact_step`] does and
1637    /// stops at the first one that had something to move, and it asks with the
1638    /// ratios off. See [`Keyspace::compact_hard`] for what that changes.
1639    fn compact_hard_step(&self) -> Option<usize> {
1640        let from = self.next_db.load(Relaxed);
1641        for turn in 0..self.slots() {
1642            let i = (from + turn) % self.slots();
1643            if let Some(moved) = self.slot(i).compact_hard() {
1644                self.next_db.store((i + 1) % self.slots(), Relaxed);
1645                return Some(moved);
1646            }
1647        }
1648        None
1649    }
1650
1651    /// Take what every thread has marked and add it to the turn's own mask.
1652    ///
1653    /// The mask the turn works from is its own and not a shared one, because a
1654    /// mask it read in place and then cleared a bit of would be a mask that lost
1655    /// whatever another thread marked in between. A swap cannot lose a mark: a
1656    /// thread that ors while the swap happens either gets its bit in before the
1657    /// swap or leaves it there afterwards, and the second one costs one look at
1658    /// a database the turn has already been through.
1659    fn collect_marks(&self) {
1660        let mut marked = 0;
1661        for thread in &self.locals {
1662            marked |= thread.dirty.swap(0, Relaxed);
1663        }
1664        self.mine().note(marked);
1665    }
1666
1667    /// Give one database's dead space back, if any database has enough of it to
1668    /// be worth the move. `None` when no database had a candidate.
1669    ///
1670    /// Once per batch, next to the clock. Overwriting a key writes a new record
1671    /// and counts the old one dead, so without this a server holds everything
1672    /// it has ever written: 400000 sets over 100000 keys measured at 742 bytes
1673    /// a key against Redis at 144 for the same load, and the whole difference
1674    /// was dead records nothing ever came back for.
1675    ///
1676    /// At most one segment moves per call and the search starts one database
1677    /// further along each time, so the cost of asking is a comparison per
1678    /// database and the cost of acting is bounded by a segment.
1679    pub fn compact_step(&self) -> Option<usize> {
1680        self.collect_marks();
1681        let mine = self.mine();
1682        let from = self.next_db.load(Relaxed);
1683        for turn in 0..self.slots() {
1684            let i = (from + turn) % self.slots();
1685            // Nothing has run against this database since it last said it had
1686            // nothing to collect, so it still has nothing to collect and the
1687            // line it lives on stays where it is.
1688            let at = self.slot_db(i);
1689            if !mine.wanted(at) {
1690                continue;
1691            }
1692            if let Some(moved) = self.slot(i).compact_step() {
1693                self.next_db.store((i + 1) % self.slots(), Relaxed);
1694                return Some(moved);
1695            }
1696            // Only once every stripe of the database has said it has nothing,
1697            // since the bit is per database and one stripe answering for all of
1698            // them would stop the others being asked at all.
1699            if i % self.width == self.width - 1 {
1700                mine.done(at);
1701            }
1702        }
1703        None
1704    }
1705}
1706
1707impl Server {
1708    /// Whether anybody is watching anything.
1709    ///
1710    /// The one thing every write asks about watches, and it is a relaxed load of
1711    /// a word that is zero and shared on a server where no client has ever sent
1712    /// `WATCH`. Relaxed is enough because the answer only has to be right by the
1713    /// time it matters: a `WATCH` that has not been published yet has not
1714    /// returned to its client either, so no client can have started a
1715    /// transaction that depends on it.
1716    fn watching(&self) -> bool {
1717        self.watched.load(Relaxed) != 0
1718    }
1719
1720    /// Which classes of keyspace notification are turned on.
1721    ///
1722    /// Zero is off, which is the default and is what nearly every server runs
1723    /// with. Relaxed for the same reason the watch count is: a `CONFIG SET` that
1724    /// has not been published to another thread yet has not answered its client
1725    /// either.
1726    pub(crate) fn notify_flags(&self) -> u32 {
1727        self.notify.load(Relaxed)
1728    }
1729
1730    /// Turn a set of notification classes on, or turn them all off with zero.
1731    pub(crate) fn set_notify_flags(&self, flags: u32) {
1732        self.notify.store(flags, Relaxed);
1733    }
1734
1735    /// Note how many watched keys there are, after the table changed.
1736    ///
1737    /// Taken from the table under the same lock the change was made under, so
1738    /// the count can never say nobody is watching while somebody is.
1739    fn recount(&self, watches: &Watches) {
1740        self.watched.store(watches.len(), Relaxed);
1741    }
1742}
1743
1744impl Default for Server {
1745    fn default() -> Server {
1746        Server::new()
1747    }
1748}
1749
1750/// What one connection has chosen.
1751pub struct Session {
1752    db: usize,
1753    id: u64,
1754    /// Which connection slot on the front this session belongs to.
1755    ///
1756    /// Carried here so that a command can say where a reply for this connection
1757    /// goes without the front having to be asked. Pub/sub is what needs it: a
1758    /// subscription is a row on the server naming a slot, and the subscribe
1759    /// command is the only moment the connection and the server are both in
1760    /// hand. [`u32::MAX`] for a session that is not on a front, which is a test.
1761    conn: u32,
1762    name: Vec<u8>,
1763    /// The `HIMPORT` fieldsets this connection has prepared.
1764    ///
1765    /// Connection state and not keyspace state, which is the reference's design
1766    /// and not a shortcut: a fieldset is invisible to every other connection and
1767    /// the keys built from one outlive it.
1768    sets: himport::Fieldsets,
1769    /// Whether the command running right now was called by a script.
1770    ///
1771    /// The one thing it changes is what a blocking command does when it finds
1772    /// nothing to take. A client that sent `BLPOP` waits; a script that called
1773    /// `BLPOP` cannot, because the whole server is waiting on the script, and a
1774    /// script that parked would park everything behind it. So inside a script a
1775    /// blocking command times out at once and answers the null a client that
1776    /// waited its full timeout would have got. That is a real server's rule and
1777    /// it is why `BLPOP` is not on the list a script may not call.
1778    scripted: bool,
1779    /// The commands held since `MULTI`, `None` when no transaction is open.
1780    ///
1781    /// Connection state and nothing else. A transaction is invisible to every
1782    /// other connection until `EXEC` runs it, and a connection that goes away
1783    /// with one open has simply not run it.
1784    multi: Option<multi::Queue>,
1785    /// What this connection asked `WATCH` about, and what those keys looked
1786    /// like at the time.
1787    ///
1788    /// The other half is on the server, beside the keys, because a write by
1789    /// another thread has to reach it. See `multi` for why keeping the value
1790    /// here and comparing it at `EXEC` is not the same thing.
1791    watching: Vec<multi::Watched>,
1792    /// Whether the command running right now was handed over by `EXEC`.
1793    ///
1794    /// The one thing it changes is the RESP2 subscribe mode refusal, which a
1795    /// real server makes in `processCommand` and so does not make for a command
1796    /// that was queued: `MULTI`, `SUBSCRIBE z`, `GET x`, `EXEC` runs the `GET`
1797    /// on 8.10.1 even though sending it on its own would have been refused.
1798    running: bool,
1799    /// The buffer `EXEC` decodes the queued commands through.
1800    ///
1801    /// It lives here rather than in `exec` so that its capacity survives the
1802    /// transaction. A fresh one has no room for spans, so the first command of
1803    /// every transaction would allocate, and a client that runs transactions in
1804    /// a loop would be allocating on a command path forever. Everywhere else
1805    /// the buffer belongs to the connection already and the same reserve is
1806    /// free after the first command.
1807    replay: crate::request::Argv,
1808    /// What this connection has subscribed to, `None` until it subscribes to
1809    /// anything.
1810    ///
1811    /// Boxed so that a connection that never subscribes carries a null pointer
1812    /// rather than three empty vectors. The other half is on the server, keyed
1813    /// by name, because a publish arrives on a connection that cannot see this
1814    /// one. See the `pubsub` module.
1815    subs: Option<Box<pubsub::Subs>>,
1816}
1817
1818impl Session {
1819    /// A new connection, on database zero with no name.
1820    #[must_use]
1821    pub fn new(id: u64) -> Session {
1822        Session {
1823            db: 0,
1824            id,
1825            conn: u32::MAX,
1826            name: Vec::new(),
1827            sets: himport::Fieldsets::default(),
1828            scripted: false,
1829            multi: None,
1830            watching: Vec::new(),
1831            running: false,
1832            replay: crate::request::Argv::new(),
1833            subs: None,
1834        }
1835    }
1836
1837    /// Whether a script is what is asking, which only a blocking command reads.
1838    pub(crate) const fn scripted(&self) -> bool {
1839        self.scripted
1840    }
1841
1842    /// Whether `EXEC` is what is asking.
1843    pub(crate) const fn running(&self) -> bool {
1844        self.running
1845    }
1846
1847    /// Say which connection slot this session is in.
1848    ///
1849    /// Called by the front when it opens the connection, which is the only place
1850    /// that knows. A session nobody tells is not on a front, and the one thing
1851    /// that reads this checks the client id before it acts on it.
1852    pub(crate) const fn set_conn(&mut self, conn: u32) {
1853        self.conn = conn;
1854    }
1855
1856    /// The connection id, which `HELLO` reports and `CLIENT` will.
1857    #[must_use]
1858    pub const fn id(&self) -> u64 {
1859        self.id
1860    }
1861
1862    /// Which database this connection is working in.
1863    #[must_use]
1864    pub const fn db(&self) -> usize {
1865        self.db
1866    }
1867
1868    /// The name the client gave itself, empty if it gave none.
1869    #[must_use]
1870    pub fn name(&self) -> &[u8] {
1871        &self.name
1872    }
1873
1874    /// Put everything back the way it was when the connection was opened.
1875    ///
1876    /// The protocol is not here because it is not here: it lives in the reply
1877    /// buffer, and `RESET` sets it back there.
1878    pub fn reset(&mut self) {
1879        self.db = 0;
1880        self.name.clear();
1881        // `SELECT` leaves these alone and `RESET` does not, both checked
1882        // against 8.10.1, which is the one pair of answers you could not guess
1883        // from what the command is for.
1884        self.sets.clear();
1885    }
1886
1887    /// Record the name from `HELLO ... SETNAME`.
1888    fn set_name(&mut self, name: &[u8]) {
1889        yo_alloc::allow(|| {
1890            self.name.clear();
1891            self.name.extend_from_slice(name);
1892        });
1893    }
1894}
1895
1896/// Give back everything a connection was holding on the server.
1897///
1898/// The transaction, the watches and the subscriptions, and it is here rather
1899/// than in [`Session::reset`] because letting go of any of the three is a change
1900/// to the server. A `Session` on its own cannot reach one, and a connection that
1901/// dropped its lists without saying so would leave rows nobody is watching and
1902/// subscriptions nobody is listening to, which would keep every write and every
1903/// publish on the server paying for clients that are not there.
1904pub fn forget_session(server: &Server, session: &mut Session) {
1905    multi::release(server, session);
1906    pubsub::release(server, session);
1907}
1908
1909/// Run one command and write its reply.
1910///
1911/// The name is looked up and the arity is checked here, once, so that no body
1912/// has to. Everything after that is the command's own.
1913pub fn execute(server: &Server, session: &mut Session, args: Args<'_>, out: &mut Out) -> Flow {
1914    // The decoder never produces a command with no name. If one ever arrives,
1915    // it is not something to answer.
1916    if args.is_empty() {
1917        return Flow::Continue;
1918    }
1919    resolved(server, session, lookup(args.name()), args, out)
1920}
1921
1922/// The same, for a caller that has already found the command.
1923///
1924/// The engine frames a command before it runs it, and between those two it also
1925/// asks which key the command touches so the record can be prefetched. That is
1926/// two more chances to look the name up, and looking it up three times to run it
1927/// once is three times the cost of the cheapest thing in the path. So the engine
1928/// resolves the name where it frames the command, carries the answer on the
1929/// framed command, and both the other two take it from there.
1930///
1931/// `spec` is `None` for a name that is not a command, which is the same thing
1932/// [`lookup`] says and lands in the same reply.
1933pub fn resolved(
1934    server: &Server,
1935    session: &mut Session,
1936    spec: Option<&'static Spec>,
1937    args: Args<'_>,
1938    out: &mut Out,
1939) -> Flow {
1940    if args.is_empty() {
1941        return Flow::Continue;
1942    }
1943    server.mine().stats.commands.bump();
1944
1945    // The four refusals below are the ones a real server makes in
1946    // `processCommand`, before the command's own body is reached, and they are
1947    // the ones that kill an open transaction. That is the whole of the rule: an
1948    // error raised here means `EXEC` will refuse to run anything, and an error
1949    // raised by a command body does not, which is why `MULTI` inside `MULTI`
1950    // complains and leaves the transaction alive.
1951    let Some(spec) = spec else {
1952        multi::refuse(server, session, None, &args::unknown_command(args), out);
1953        return Flow::Continue;
1954    };
1955    if !arity_ok(spec, args.len()) {
1956        server.mine().cmdstats.at(spec).rejected.bump();
1957        multi::refuse(
1958            server,
1959            session,
1960            Some(spec),
1961            &args::wrong_arity(spec.name),
1962            out,
1963        );
1964        return Flow::Continue;
1965    }
1966    if session.in_multi()
1967        && let Some(e) = multi::refused_in_multi(spec)
1968    {
1969        server.mine().cmdstats.at(spec).rejected.bump();
1970        multi::refuse(server, session, Some(spec), &e, out);
1971        return Flow::Continue;
1972    }
1973
1974    // The limit first, so a server with no `maxmemory`, which is the default and
1975    // is nearly all of them, pays one comparison against a field that is already
1976    // warm. Every command and not only the writes, because that is where Redis
1977    // puts it: making room is the server's job whatever the client asked for,
1978    // and the flag only decides who gets told no when there is no room to make.
1979    //
1980    // The flag is Redis's own `denyoom` and the list of commands carrying it is
1981    // Redis's list, so a command that only frees is let through with nothing
1982    // left, which is what lets a client dig itself out with `DEL`.
1983    if server.maxmemory() != 0 && !server.make_room() && spec.flags.contains(&"denyoom") {
1984        server.mine().cmdstats.at(spec).rejected.bump();
1985        session.dirty_multi();
1986        out.error_line(b"OOM ", OOM);
1987        return Flow::Continue;
1988    }
1989
1990    // A RESP2 connection that has subscribed to something may only send a
1991    // handful of commands, because RESP2 sends a published message as an
1992    // ordinary array and a client with a reply outstanding could not tell the
1993    // two apart. Here, after the refusals above and before the queue below,
1994    // which is where a real server puts it: `EXEC` sent while subscribed comes
1995    // back as an `EXECABORT` rather than as this error, and a command `EXEC`
1996    // hands over is not asked at all.
1997    if let Some(e) = pubsub::refused(session, spec, out) {
1998        server.mine().cmdstats.at(spec).rejected.bump();
1999        multi::refuse(server, session, Some(spec), &e, out);
2000        return Flow::Continue;
2001    }
2002
2003    // Held rather than run, and the reply is `QUEUED`. After the refusals above
2004    // and before everything below, which is where a real server puts it: a
2005    // command has to be a real command with the right number of arguments to be
2006    // queued at all, and nothing it would have done gets done now.
2007    if session.queues(spec.name) {
2008        return multi::queue(session, args, out);
2009    }
2010
2011    // Which databases the maintenance turn after this batch has to ask. Marked
2012    // for every command and not only for the writes, because a read can make
2013    // garbage too: a `GET` on a key whose expiry has passed reaps it, and the
2014    // record it dropped is exactly the kind of thing the collector is for.
2015    // `COPY`, `SWAPDB` and `FLUSHALL` reach a database nobody selected, so the
2016    // two groups that hold them mark all of them rather than the session's.
2017    server.mine().mark(match spec.group {
2018        "string" | "bitmap" | "hyperloglog" | "geo" | "set" | "hash" | "list" | "zset"
2019        | "array" | "stream" | "bloom" | "cuckoo" | "cms" | "topk" | "tdigest" | "ts" => {
2020            1u64 << session.db
2021        }
2022        _ => ALL_DATABASES,
2023    });
2024
2025    let mark = out.len();
2026    // Before the group, because the five that block are list commands and would
2027    // otherwise land in `lists`, which is handed one database and nothing that
2028    // could park a client. The flag is the right thing to branch on rather than
2029    // a list of names: it is what `COMMAND INFO` reports about exactly these
2030    // commands, and the sorted set and stream ones that arrive later carry it
2031    // too.
2032    // What the command is about to do to the keyspace, for anybody subscribed to
2033    // hear about it. Armed here and drained after the group, because the bodies
2034    // below are handed a database and their arguments and have no way to reach
2035    // the pub/sub registry from there. Off costs one thread local store.
2036    let armed = notify::arm(server, session.db);
2037    // Which of the keys this command reads are not there. A real server says
2038    // this from inside each lookup and this says all of them in front, which is
2039    // the same order for every command whose first act is to read what it was
2040    // given, and that is nearly all of them.
2041    misses::report(&server.dbs[session.db], session.db, spec, args);
2042    let done = if spec.flags.contains(&"blocking") {
2043        blocking::execute(server, session, spec, args, out)
2044    } else {
2045        match spec.group {
2046            "string" => {
2047                let db = session.db;
2048                strings::execute(&server.dbs[db], db, spec, args, out).map(|()| Flow::Continue)
2049            }
2050            // Its own group and its own file, and the same values underneath:
2051            // a bitmap is a string, so `STRLEN` on one answers and `SETBIT` on
2052            // something a `SET` left behind works.
2053            "bitmap" => {
2054                let db = session.db;
2055                bits::execute(&server.dbs[db], db, spec, args, out).map(|()| Flow::Continue)
2056            }
2057            // The same again: a sketch is a string with a documented layout, so
2058            // `GET` hands one to a client and `SET` takes it back.
2059            "hyperloglog" => {
2060                let db = session.db;
2061                hll::execute(&server.dbs[db], db, spec, args, out).map(|()| Flow::Continue)
2062            }
2063            "set" => {
2064                let db = session.db;
2065                sets::execute(&server.dbs[db], db, spec, args, out).map(|()| Flow::Continue)
2066            }
2067            // The one hash command whose state is not in the keyspace. A
2068            // fieldset belongs to the connection, so this is handed the session
2069            // as well as the database, the same exception `MIGRATE` gets in the
2070            // keyspace group for the socket it keeps.
2071            "hash" if spec.name == "himport" => {
2072                let db = session.db;
2073                himport::execute(&server.dbs[db], &mut session.sets, args, out)
2074                    .map(|()| Flow::Continue)
2075            }
2076            // The one group that reaches back into the server after it has
2077            // written its reply, because a hash is what a search index is
2078            // made of. What comes back is what the indexes have to be told,
2079            // which is not the same as whether the command was a write.
2080            "hash" => {
2081                let db = session.db;
2082                let changed = hashes::execute(&server.dbs[db], db, spec, args, out);
2083                changed.map(|changed| {
2084                    indexing::changed(server, db, args.get(1), changed);
2085                    Flow::Continue
2086                })
2087            }
2088            "list" => {
2089                let db = session.db;
2090                lists::execute(&server.dbs[db], db, spec, args, out).map(|()| Flow::Continue)
2091            }
2092            "zset" => {
2093                let db = session.db;
2094                zsets::execute(&server.dbs[db], db, spec, args, out).map(|()| Flow::Continue)
2095            }
2096            // A geo key is a sorted set and these are sorted set commands with
2097            // arithmetic on the way in and on the way out, so a client can ZREM
2098            // a place out of one and ZCARD it to count them.
2099            "geo" => {
2100                let db = session.db;
2101                geo::execute(&server.dbs[db], db, spec, args, out).map(|()| Flow::Continue)
2102            }
2103            "array" => {
2104                let db = session.db;
2105                arrays::execute(&server.dbs[db], spec, args, out).map(|()| Flow::Continue)
2106            }
2107            "graph" => {
2108                let db = session.db;
2109                graph::execute(&server.dbs[db], spec, args, out).map(|()| Flow::Continue)
2110            }
2111            // A document under a key, reached by a path. The group is Redis's
2112            // module surface and the storage is ours, the same trade the vector
2113            // set group makes.
2114            "json" => {
2115                let db = session.db;
2116                json::execute(&server.dbs[db], spec, args, out).map(|()| Flow::Continue)
2117            }
2118            "vector" => {
2119                let db = session.db;
2120                vectors::execute(&server.dbs[db], spec, args, out).map(|()| Flow::Continue)
2121            }
2122            "bloom" => {
2123                let db = session.db;
2124                bloom::execute(&server.dbs[db], spec, args, out).map(|()| Flow::Continue)
2125            }
2126            "cuckoo" => {
2127                let db = session.db;
2128                cuckoo::execute(&server.dbs[db], spec, args, out).map(|()| Flow::Continue)
2129            }
2130            "cms" => {
2131                let db = session.db;
2132                cms::execute(&server.dbs[db], spec, args, out).map(|()| Flow::Continue)
2133            }
2134            "topk" => {
2135                let db = session.db;
2136                topk::execute(&server.dbs[db], spec, args, out).map(|()| Flow::Continue)
2137            }
2138            "tdigest" => {
2139                let db = session.db;
2140                tdigest::execute(&server.dbs[db], spec, args, out).map(|()| Flow::Continue)
2141            }
2142            "ts" => {
2143                let db = session.db;
2144                ts::execute(&server.dbs[db], spec, args, out).map(|()| Flow::Continue)
2145            }
2146            // The clock is read before the database is borrowed, because every
2147            // stream command needs the time and it lives on the server. An
2148            // `XADD` with no ID, an `XCLAIM` working out what is idle and an
2149            // `XINFO` reporting it all have to agree about what moment this is.
2150            "stream" => {
2151                let db = session.db;
2152                let now = server.now_ms();
2153                streams::execute(&server.dbs[db], db, spec, args, now, out).map(|()| Flow::Continue)
2154            }
2155            // The one keyspace command that needs more than the databases,
2156            // because the socket it talks down is held on the server between
2157            // commands and not opened again for each one.
2158            "keyspace" if spec.name == "migrate" => {
2159                migrate::execute(server, session.db, args, out).map(|()| Flow::Continue)
2160            }
2161            // Every database and not the one the session is on, because `COPY` takes
2162            // a `DB n` and writes into a database nobody selected. The other group
2163            // that reaches back into the server afterwards, and it hands back a list
2164            // rather than one answer, because `DEL a b c` is three keys and a rename
2165            // is two.
2166            "keyspace" => {
2167                let mut touched = indexing::Touched::new(server);
2168                let done =
2169                    keyspace::execute(&server.dbs, session.db, spec, args, out, &mut touched);
2170                done.map(|()| {
2171                    indexing::touched(server, &touched);
2172                    Flow::Continue
2173                })
2174            }
2175            // No database at all, because an index is not a key. The registry
2176            // is the whole of what these sixteen commands touch, and then
2177            // `FT.CREATE` hands back the name it made so the keys that
2178            // already match its prefix can be read into it. The lock goes
2179            // before the scan runs, since the scan takes it again for every
2180            // key it reads.
2181            "search" if spec.name == "FT.SEARCH" => {
2182                // The two search commands that read documents, and so the two
2183                // that need the keyspace as well as the registry. They take and
2184                // let go of the registry themselves, because they cannot hold
2185                // that and a stripe at the same time.
2186                search::find(server, session.db, args, out).map(|()| Flow::Continue)
2187            }
2188            "search" if spec.name == "FT.AGGREGATE" => {
2189                search::roll(server, session.db, args, out).map(|()| Flow::Continue)
2190            }
2191            "search" if spec.name == "FT.HYBRID" => {
2192                search::hybrid(server, session.db, args, out).map(|()| Flow::Continue)
2193            }
2194            "search" if spec.name == "FT.PROFILE" => {
2195                // Which is one of those two with the working shown, so it needs
2196                // everything they need and takes the same route to it.
2197                search::profiled(server, session.db, args, out).map(|()| Flow::Continue)
2198            }
2199            // The four search commands that name a key rather than an index.
2200            // A suggestion dictionary is a real key with a type of its own, so
2201            // these are handed a database and never touch the registry.
2202            "search" if spec.name.starts_with("FT.SUG") => {
2203                let db = session.db;
2204                suggest::execute(&server.dbs[db], spec, args, out).map(|()| Flow::Continue)
2205            }
2206            // The five deprecated document commands, which are the other search
2207            // commands that need the keyspace as well as the registry: what they
2208            // write and read is an ordinary hash.
2209            "search"
2210                if matches!(
2211                    spec.name,
2212                    "FT.ADD" | "FT.SAFEADD" | "FT.GET" | "FT.MGET" | "FT.DEL"
2213                ) =>
2214            {
2215                let db = session.db;
2216                search::docs::execute(server, db, spec, args, out).map(|()| Flow::Continue)
2217            }
2218            "search" if spec.name == "FT.CURSOR" => {
2219                // Its own arm because the cursors are not in the registry, and
2220                // it takes and lets go of the registry itself to look up the
2221                // index name it is given.
2222                search::cursor::execute(server, args, out).map(|()| Flow::Continue)
2223            }
2224            "search" => {
2225                let db = session.db;
2226                let made = search::execute(server, &mut server.search.lock(), db, spec, args, out);
2227                made.map(|made| {
2228                    match made {
2229                        Some(search::After::Scan(fill)) => indexing::scan(server, db, &fill),
2230                        Some(search::After::Sweep(keys)) => indexing::sweep(server, db, &keys),
2231                        None => {}
2232                    }
2233                    Flow::Continue
2234                })
2235            }
2236            "scripting" => {
2237                scripting::execute(server, session, spec, args, out).map(|()| Flow::Continue)
2238            }
2239            "transactions" => multi::execute(server, session, spec, args, out),
2240            // No database either, and the one group whose replies do not all go
2241            // to the connection that asked. The session is in it because a
2242            // subscription is connection state as well as server state.
2243            "pubsub" => pubsub::execute(server, session, spec, args, out),
2244            _ => server::execute(server, session, spec, args, out),
2245        }
2246    };
2247    // Before the error is written and not after, because a command that failed
2248    // half way through still changed whatever it changed before it failed and a
2249    // real server has already published those. Draining here also keeps the
2250    // notifications of a command run by `EXEC` in front of the next one's.
2251    // And back out the misses reported in front of a command that turned out to
2252    // have failed on its own arguments, since a server that fires from inside
2253    // the lookup never reached one.
2254    if let Err(e) = &done {
2255        misses::undo(spec, e);
2256    }
2257    notify::drain(server, armed);
2258
2259    let flow = match done {
2260        Ok(flow) => flow,
2261        Err(e) => {
2262            out.truncate(mark);
2263            write_error(out, &e);
2264            Flow::Continue
2265        }
2266    };
2267
2268    // After the command rather than before, so that whether each key it named is
2269    // there is read at the moment a real server would have signalled the change.
2270    // The load is what this costs a server nobody has sent `WATCH` to, and the
2271    // flag is Redis's own, so a command that only reads is never asked.
2272    if server.watching() && spec.flags.contains(&"write") {
2273        multi::touched(server, session, spec, args);
2274    }
2275
2276    // Counted here and not before the call, which is where Redis counts it, so
2277    // that `INFO commandstats` leaves out the `INFO` that asked for it in the
2278    // same way theirs does.
2279    //
2280    // Failure is read off the reply rather than off the `Result`, because the
2281    // two are not the same set. A command that ran out of arguments comes back
2282    // as an `Err` and a command that was sent the wrong password writes its own
2283    // error line and comes back `Ok`, and both of those are a call that failed.
2284    // The first byte at the mark is what a client would branch on, and it is `-`
2285    // for an error on either protocol and `!` for RESP3's long form.
2286    let row = server.mine().cmdstats.at(spec);
2287    row.calls.bump();
2288    if matches!(out.as_slice().get(mark), Some(b'-' | b'!')) {
2289        row.failed.bump();
2290    }
2291    flow
2292}
2293
2294/// The error line for an error value.
2295///
2296/// The prefix is what a client branches on, and there are three of them:
2297/// `WRONGTYPE` for a command sent at the wrong kind of value, `INVALIDOBJ` for a
2298/// HyperLogLog whose opcodes do not add up, and `ERR` for everything else. The three errors that need a different one,
2299/// `NOPROTO`, `WRONGPASS` and `OOM`, are written where they are decided rather
2300/// than routed through here. `OOM` is not a [`Code`] of its own because
2301/// [`Code::Full`] already covers the string that is too long for
2302/// `proto-max-bulk-len`, and that one goes out as `ERR` on a real server.
2303fn write_error(out: &mut Out, e: &Error) {
2304    let prefix: &[u8] = match e.code() {
2305        Code::WrongType => b"WRONGTYPE ",
2306        // Only the HyperLogLog commands answer this one, and the prefix is the
2307        // sentence a client branches on to tell a sketch it cannot read from a
2308        // sketch it sent wrong.
2309        Code::Corrupt => b"INVALIDOBJ ",
2310        _ => b"ERR ",
2311    };
2312    out.error_line(prefix, e.message().as_bytes());
2313}
2314
2315#[cfg(test)]
2316mod tests {
2317    use super::*;
2318    use crate::proto::{Limits, Proto};
2319    use crate::request::Argv;
2320
2321    /// Build the wire bytes for a command.
2322    ///
2323    /// Tests go through the codec rather than around it, so an argument in a
2324    /// test is the same borrowed slice a connection produces.
2325    pub(crate) fn encode(parts: &[&[u8]]) -> Vec<u8> {
2326        let mut wire = format!("*{}\r\n", parts.len()).into_bytes();
2327        for p in parts {
2328            wire.extend_from_slice(format!("${}\r\n", p.len()).as_bytes());
2329            wire.extend_from_slice(p);
2330            wire.extend_from_slice(b"\r\n");
2331        }
2332        wire
2333    }
2334
2335    /// A server, a connection and a buffer, driven the way the reactor will.
2336    struct Fixture {
2337        server: Server,
2338        session: Session,
2339        argv: Argv,
2340        out: Out,
2341    }
2342
2343    impl Fixture {
2344        fn new() -> Fixture {
2345            Fixture::on(Server::new())
2346        }
2347
2348        /// The same, on a server whose databases are cut into `width` stripes.
2349        fn striped(width: usize) -> Fixture {
2350            Fixture::on(Server::with_width(width))
2351        }
2352
2353        fn on(server: Server) -> Fixture {
2354            Fixture {
2355                server,
2356                session: Session::new(7),
2357                argv: Argv::new(),
2358                out: Out::new(Proto::Resp2),
2359            }
2360        }
2361
2362        /// Run one command and answer with the bytes it wrote.
2363        fn run(&mut self, parts: &[&[u8]]) -> String {
2364            self.flow(parts).1
2365        }
2366
2367        /// Run one command and answer with the bytes exactly as written.
2368        ///
2369        /// [`Fixture::run`] goes through `from_utf8_lossy`, which is fine for
2370        /// every reply that is text and destroys a `DUMP` payload, since a
2371        /// payload is arbitrary bytes and a checksum on the end of them.
2372        fn raw(&mut self, parts: &[&[u8]]) -> Vec<u8> {
2373            let wire = encode(parts);
2374            self.argv.decode(&wire, &Limits::default()).unwrap();
2375            self.out.clear();
2376            execute(
2377                &self.server,
2378                &mut self.session,
2379                Args::new(&self.argv, &wire),
2380                &mut self.out,
2381            );
2382            self.out.as_slice().to_vec()
2383        }
2384
2385        /// Move every clock in the server on by `ms`.
2386        fn advance(&mut self, ms: u64) {
2387            self.server.advance_clock_ms(ms);
2388        }
2389
2390        /// Run one command as a second connection to the same server.
2391        ///
2392        /// What `WATCH` is for is a write another connection made, and a test
2393        /// that only has one connection cannot tell the two apart.
2394        fn other(&mut self, parts: &[&[u8]]) -> String {
2395            self.other_in(self.session.db(), parts)
2396        }
2397
2398        /// The same, on a database of its own.
2399        fn other_in(&mut self, db: usize, parts: &[&[u8]]) -> String {
2400            let mut session = Session::new(8);
2401            session.db = db;
2402            let reply = self.by(&mut session, parts);
2403            forget_session(&self.server, &mut session);
2404            reply
2405        }
2406
2407        /// Run one command on a session the caller holds.
2408        fn by(&mut self, session: &mut Session, parts: &[&[u8]]) -> String {
2409            let wire = encode(parts);
2410            let mut argv = Argv::new();
2411            argv.decode(&wire, &Limits::default()).unwrap();
2412            let mut out = Out::new(Proto::Resp2);
2413            execute(&self.server, session, Args::new(&argv, &wire), &mut out);
2414            String::from_utf8_lossy(out.as_slice()).into_owned()
2415        }
2416
2417        /// The same, with what the connection should do next.
2418        fn flow(&mut self, parts: &[&[u8]]) -> (Flow, String) {
2419            let wire = encode(parts);
2420            self.argv.decode(&wire, &Limits::default()).unwrap();
2421            self.out.clear();
2422            let flow = execute(
2423                &self.server,
2424                &mut self.session,
2425                Args::new(&self.argv, &wire),
2426                &mut self.out,
2427            );
2428            (
2429                flow,
2430                String::from_utf8_lossy(self.out.as_slice()).into_owned(),
2431            )
2432        }
2433    }
2434
2435    #[test]
2436    fn multi_holds_commands_and_exec_runs_them() {
2437        let mut f = Fixture::new();
2438        assert_eq!(f.run(&[b"MULTI"]), "+OK\r\n");
2439        assert_eq!(f.run(&[b"SET", b"k", b"1"]), "+QUEUED\r\n");
2440        assert_eq!(f.run(&[b"INCR", b"k"]), "+QUEUED\r\n");
2441        // Nothing ran while it was being queued.
2442        assert_eq!(f.other(&[b"GET", b"k"]), "$-1\r\n");
2443        assert_eq!(f.run(&[b"EXEC"]), "*2\r\n+OK\r\n:2\r\n");
2444        assert_eq!(f.run(&[b"GET", b"k"]), "$1\r\n2\r\n");
2445    }
2446
2447    /// The test the `high_water` claim in `multi::exec` asks for.
2448    ///
2449    /// A `Vec` reaches the allocator exactly when its capacity changes, so a
2450    /// replay buffer whose room is the same before and after is one that did
2451    /// not allocate. The first transaction is what sets the room, which is the
2452    /// high water mark, and the second is the one that has to be free. Before
2453    /// the buffer moved onto the session this failed on every transaction,
2454    /// because `exec` made a new one each time and the room went back to zero.
2455    #[test]
2456    fn the_second_exec_of_a_shape_does_not_grow_the_buffer() {
2457        let mut f = Fixture::new();
2458        for _ in 0..2 {
2459            f.run(&[b"MULTI"]);
2460            f.run(&[b"SET", b"k", b"1"]);
2461            f.run(&[b"INCR", b"k"]);
2462            f.run(&[b"EXEC"]);
2463        }
2464        let room = f.session.replay.room();
2465        assert!(room > 0, "the first transaction should have set the room");
2466        f.run(&[b"MULTI"]);
2467        f.run(&[b"SET", b"k", b"1"]);
2468        f.run(&[b"INCR", b"k"]);
2469        f.run(&[b"EXEC"]);
2470        assert_eq!(f.session.replay.room(), room);
2471    }
2472
2473    #[test]
2474    fn an_empty_transaction_answers_an_empty_array() {
2475        let mut f = Fixture::new();
2476        f.run(&[b"MULTI"]);
2477        assert_eq!(f.run(&[b"EXEC"]), "*0\r\n");
2478    }
2479
2480    #[test]
2481    fn exec_and_discard_want_a_transaction_to_be_open() {
2482        let mut f = Fixture::new();
2483        assert_eq!(f.run(&[b"EXEC"]), "-ERR EXEC without MULTI\r\n");
2484        assert_eq!(f.run(&[b"DISCARD"]), "-ERR DISCARD without MULTI\r\n");
2485        // And `UNWATCH` does not, which is the one of the three that is happy
2486        // being sent for no reason.
2487        assert_eq!(f.run(&[b"UNWATCH"]), "+OK\r\n");
2488    }
2489
2490    #[test]
2491    fn an_error_a_command_body_raises_leaves_the_transaction_alive() {
2492        let mut f = Fixture::new();
2493        f.run(&[b"MULTI"]);
2494        assert_eq!(
2495            f.run(&[b"MULTI"]),
2496            "-ERR MULTI calls can not be nested\r\n",
2497            "nested MULTI is raised by the command and not by the funnel"
2498        );
2499        assert_eq!(
2500            f.run(&[b"WATCH", b"k"]),
2501            "-ERR WATCH inside MULTI is not allowed\r\n"
2502        );
2503        f.run(&[b"SET", b"k", b"1"]);
2504        assert_eq!(f.run(&[b"EXEC"]), "*1\r\n+OK\r\n");
2505    }
2506
2507    #[test]
2508    fn an_error_the_funnel_raises_kills_the_transaction() {
2509        for bad in [
2510            &[b"NOSUCHCOMMAND".as_slice()] as &[&[u8]],
2511            &[b"GET".as_slice()],
2512        ] {
2513            let mut f = Fixture::new();
2514            f.run(&[b"MULTI"]);
2515            assert!(f.run(bad).starts_with("-ERR "));
2516            assert_eq!(
2517                f.run(&[b"SET", b"k", b"1"]),
2518                "+QUEUED\r\n",
2519                "a dead transaction still answers QUEUED, which is Redis"
2520            );
2521            assert_eq!(
2522                f.run(&[b"EXEC"]),
2523                "-EXECABORT Transaction discarded because of previous errors.\r\n"
2524            );
2525            assert_eq!(f.run(&[b"GET", b"k"]), "$-1\r\n");
2526        }
2527    }
2528
2529    #[test]
2530    fn exec_with_an_argument_is_an_abort_and_not_an_arity_error() {
2531        let mut f = Fixture::new();
2532        f.run(&[b"MULTI"]);
2533        f.run(&[b"SET", b"k", b"1"]);
2534        assert_eq!(
2535            f.run(&[b"EXEC", b"x"]),
2536            "-EXECABORT Transaction discarded because of: wrong number of arguments for 'exec' command\r\n"
2537        );
2538        assert_eq!(f.run(&[b"EXEC"]), "-ERR EXEC without MULTI\r\n");
2539        assert_eq!(f.run(&[b"GET", b"k"]), "$-1\r\n");
2540    }
2541
2542    #[test]
2543    fn a_command_a_transaction_may_not_hold_kills_it() {
2544        let mut f = Fixture::new();
2545        f.run(&[b"MULTI"]);
2546        assert_eq!(
2547            f.run(&[b"SHUTDOWN", b"NOSAVE"]),
2548            "-ERR Command not allowed inside a transaction\r\n"
2549        );
2550        assert_eq!(
2551            f.run(&[b"EXEC"]),
2552            "-EXECABORT Transaction discarded because of previous errors.\r\n"
2553        );
2554    }
2555
2556    #[test]
2557    fn a_failing_command_inside_exec_is_an_element_and_the_rest_still_runs() {
2558        let mut f = Fixture::new();
2559        f.run(&[b"RPUSH", b"l", b"v"]);
2560        f.run(&[b"MULTI"]);
2561        f.run(&[b"INCR", b"l"]);
2562        f.run(&[b"SET", b"y", b"2"]);
2563        assert_eq!(
2564            f.run(&[b"EXEC"]),
2565            "*2\r\n-WRONGTYPE Operation against a key holding the wrong kind of value\r\n+OK\r\n"
2566        );
2567        assert_eq!(f.run(&[b"GET", b"y"]), "$1\r\n2\r\n");
2568    }
2569
2570    #[test]
2571    fn discard_and_reset_both_throw_the_queue_away() {
2572        let mut f = Fixture::new();
2573        f.run(&[b"MULTI"]);
2574        f.run(&[b"SET", b"k", b"1"]);
2575        assert_eq!(f.run(&[b"DISCARD"]), "+OK\r\n");
2576        assert_eq!(f.run(&[b"EXEC"]), "-ERR EXEC without MULTI\r\n");
2577        assert_eq!(f.run(&[b"GET", b"k"]), "$-1\r\n");
2578
2579        f.run(&[b"MULTI"]);
2580        f.run(&[b"SET", b"k", b"1"]);
2581        assert_eq!(f.run(&[b"RESET"]), "+RESET\r\n");
2582        assert_eq!(f.run(&[b"EXEC"]), "-ERR EXEC without MULTI\r\n");
2583        assert_eq!(f.run(&[b"GET", b"k"]), "$-1\r\n");
2584    }
2585
2586    #[test]
2587    fn select_is_queued_and_applied_when_exec_runs_it() {
2588        let mut f = Fixture::new();
2589        f.run(&[b"MULTI"]);
2590        assert_eq!(f.run(&[b"SELECT", b"3"]), "+QUEUED\r\n");
2591        f.run(&[b"SET", b"k", b"1"]);
2592        assert_eq!(f.run(&[b"EXEC"]), "*2\r\n+OK\r\n+OK\r\n");
2593        assert_eq!(f.session.db(), 3, "the SELECT applied and stayed applied");
2594        assert_eq!(f.run(&[b"GET", b"k"]), "$1\r\n1\r\n");
2595    }
2596
2597    #[test]
2598    fn a_write_by_another_connection_fails_the_transaction() {
2599        let mut f = Fixture::new();
2600        f.run(&[b"SET", b"k", b"1"]);
2601        assert_eq!(f.run(&[b"WATCH", b"k"]), "+OK\r\n");
2602        f.other(&[b"SET", b"k", b"2"]);
2603        f.run(&[b"MULTI"]);
2604        f.run(&[b"GET", b"k"]);
2605        assert_eq!(f.run(&[b"EXEC"]), "*-1\r\n");
2606    }
2607
2608    #[test]
2609    fn a_write_that_puts_the_same_value_back_still_fails_it() {
2610        let mut f = Fixture::new();
2611        f.run(&[b"SET", b"k", b"1"]);
2612        f.run(&[b"WATCH", b"k"]);
2613        f.other(&[b"SET", b"k", b"1"]);
2614        f.run(&[b"MULTI"]);
2615        assert_eq!(f.run(&[b"EXEC"]), "*-1\r\n");
2616    }
2617
2618    #[test]
2619    fn a_read_by_another_connection_does_not() {
2620        let mut f = Fixture::new();
2621        f.run(&[b"SET", b"k", b"1"]);
2622        f.run(&[b"WATCH", b"k"]);
2623        f.other(&[b"GET", b"k"]);
2624        f.other(&[b"STRLEN", b"k"]);
2625        f.run(&[b"MULTI"]);
2626        f.run(&[b"GET", b"k"]);
2627        assert_eq!(f.run(&[b"EXEC"]), "*1\r\n$1\r\n1\r\n");
2628    }
2629
2630    #[test]
2631    fn deleting_a_key_that_was_never_there_does_not_fail_a_watch_on_it() {
2632        let mut f = Fixture::new();
2633        f.run(&[b"WATCH", b"k"]);
2634        f.other(&[b"DEL", b"k"]);
2635        f.run(&[b"MULTI"]);
2636        f.run(&[b"PING"]);
2637        assert_eq!(f.run(&[b"EXEC"]), "*1\r\n+PONG\r\n");
2638        // And creating it does, which is the other half of the same rule.
2639        f.run(&[b"WATCH", b"k"]);
2640        f.other(&[b"SET", b"k", b"1"]);
2641        f.run(&[b"MULTI"]);
2642        assert_eq!(f.run(&[b"EXEC"]), "*-1\r\n");
2643    }
2644
2645    #[test]
2646    fn a_watched_key_that_expires_fails_the_transaction() {
2647        let mut f = Fixture::new();
2648        f.run(&[b"SET", b"k", b"1", b"PX", b"50"]);
2649        f.run(&[b"WATCH", b"k"]);
2650        f.run(&[b"MULTI"]);
2651        f.advance(100);
2652        assert_eq!(
2653            f.run(&[b"EXEC"]),
2654            "*-1\r\n",
2655            "nothing wrote to the key, so only the liveness check can catch this"
2656        );
2657    }
2658
2659    #[test]
2660    fn every_way_a_transaction_ends_lets_go_of_the_watches() {
2661        for end in [
2662            &[b"EXEC".as_slice()] as &[&[u8]],
2663            &[b"DISCARD".as_slice()],
2664            &[b"UNWATCH".as_slice()],
2665            &[b"RESET".as_slice()],
2666        ] {
2667            let mut f = Fixture::new();
2668            f.run(&[b"SET", b"k", b"1"]);
2669            f.run(&[b"WATCH", b"k"]);
2670            if end[0] != b"UNWATCH" && end[0] != b"RESET" {
2671                f.run(&[b"MULTI"]);
2672            }
2673            f.run(end);
2674            assert!(!f.server.watching(), "{end:?} left a row behind");
2675            // And the connection can start again with nothing carried over.
2676            f.other(&[b"SET", b"k", b"2"]);
2677            f.run(&[b"MULTI"]);
2678            f.run(&[b"GET", b"k"]);
2679            assert_eq!(f.run(&[b"EXEC"]), "*1\r\n$1\r\n2\r\n");
2680        }
2681    }
2682
2683    #[test]
2684    fn a_connection_going_away_lets_go_of_its_watches() {
2685        let mut f = Fixture::new();
2686        f.run(&[b"SET", b"k", b"1"]);
2687        f.run(&[b"WATCH", b"k"]);
2688        assert!(f.server.watching());
2689        forget_session(&f.server, &mut f.session);
2690        assert!(!f.server.watching());
2691    }
2692
2693    #[test]
2694    fn watching_the_same_key_twice_is_one_watch() {
2695        let mut f = Fixture::new();
2696        f.run(&[b"SET", b"k", b"1"]);
2697        f.run(&[b"WATCH", b"k", b"k"]);
2698        f.run(&[b"UNWATCH"]);
2699        assert!(
2700            !f.server.watching(),
2701            "the row counts watchers, so a doubled watch would leave one behind"
2702        );
2703    }
2704
2705    #[test]
2706    fn two_connections_can_watch_the_same_key() {
2707        let mut f = Fixture::new();
2708        f.run(&[b"SET", b"k", b"1"]);
2709        f.run(&[b"WATCH", b"k"]);
2710        let mut second = Session::new(9);
2711        second.db = f.session.db();
2712        assert_eq!(f.by(&mut second, &[b"WATCH", b"k"]), "+OK\r\n");
2713        // One lets go and the other's watch still works.
2714        forget_session(&f.server, &mut second);
2715        assert!(f.server.watching());
2716        f.other(&[b"SET", b"k", b"2"]);
2717        f.run(&[b"MULTI"]);
2718        assert_eq!(f.run(&[b"EXEC"]), "*-1\r\n");
2719    }
2720
2721    #[test]
2722    fn flushdb_fails_a_watch_on_a_key_that_was_there() {
2723        let mut f = Fixture::new();
2724        f.run(&[b"SET", b"k", b"1"]);
2725        f.run(&[b"WATCH", b"k"]);
2726        f.other(&[b"FLUSHDB"]);
2727        f.run(&[b"MULTI"]);
2728        assert_eq!(f.run(&[b"EXEC"]), "*-1\r\n");
2729    }
2730
2731    #[test]
2732    fn flushdb_does_not_fail_a_watch_on_a_key_that_was_not() {
2733        let mut f = Fixture::new();
2734        f.run(&[b"WATCH", b"k"]);
2735        f.other(&[b"FLUSHDB"]);
2736        f.run(&[b"MULTI"]);
2737        f.run(&[b"PING"]);
2738        assert_eq!(f.run(&[b"EXEC"]), "*1\r\n+PONG\r\n");
2739    }
2740
2741    #[test]
2742    fn a_watch_is_on_a_database_and_a_key_and_not_on_a_key() {
2743        let mut f = Fixture::new();
2744        f.run(&[b"SET", b"k", b"1"]);
2745        f.run(&[b"WATCH", b"k"]);
2746        // The same name in another database is another key.
2747        let elsewhere = f.session.db() + 1;
2748        f.other_in(elsewhere, &[b"SET", b"k", b"9"]);
2749        f.run(&[b"MULTI"]);
2750        f.run(&[b"GET", b"k"]);
2751        assert_eq!(f.run(&[b"EXEC"]), "*1\r\n$1\r\n1\r\n");
2752    }
2753
2754    #[test]
2755    fn a_write_that_reaches_a_key_it_did_not_name_still_fails_a_watch() {
2756        let mut f = Fixture::new();
2757        f.run(&[b"RPUSH", b"src", b"1"]);
2758        f.run(&[b"WATCH", b"dst"]);
2759        f.other(&[b"SORT", b"src", b"STORE", b"dst"]);
2760        f.run(&[b"MULTI"]);
2761        assert_eq!(
2762            f.run(&[b"EXEC"]),
2763            "*-1\r\n",
2764            "SORT is movablekeys, so every watched key in the database is asked"
2765        );
2766    }
2767
2768    #[test]
2769    fn a_server_nobody_is_watching_says_so() {
2770        let mut f = Fixture::new();
2771        assert!(!f.server.watching());
2772        f.run(&[b"SET", b"k", b"1"]);
2773        assert!(!f.server.watching());
2774    }
2775
2776    /// The count on the end of a subscribe reply is channels and patterns
2777    /// together, which is a thing a client uses to know when it is out of
2778    /// subscribe mode and so has to be the number the mode is decided on.
2779    /// Shard channels are counted on their own because they are their own
2780    /// namespace.
2781    #[test]
2782    fn the_count_a_subscribe_answers_covers_channels_and_patterns() {
2783        let mut f = Fixture::new();
2784        assert_eq!(
2785            f.run(&[b"SUBSCRIBE", b"a", b"b"]),
2786            "*3\r\n$9\r\nsubscribe\r\n$1\r\na\r\n:1\r\n*3\r\n$9\r\nsubscribe\r\n$1\r\nb\r\n:2\r\n"
2787        );
2788        assert_eq!(
2789            f.run(&[b"PSUBSCRIBE", b"c*"]),
2790            "*3\r\n$10\r\npsubscribe\r\n$2\r\nc*\r\n:3\r\n"
2791        );
2792        assert_eq!(
2793            f.run(&[b"SSUBSCRIBE", b"s"]),
2794            "*3\r\n$10\r\nssubscribe\r\n$1\r\ns\r\n:1\r\n"
2795        );
2796        // Subscribing again to something already held answers again with the
2797        // count unchanged, rather than counting it twice or saying nothing.
2798        assert_eq!(
2799            f.run(&[b"SUBSCRIBE", b"a"]),
2800            "*3\r\n$9\r\nsubscribe\r\n$1\r\na\r\n:3\r\n"
2801        );
2802    }
2803
2804    /// Unsubscribe has three shapes and a client has to be able to tell them
2805    /// apart, because the last one is what tells it the mode is over.
2806    #[test]
2807    fn unsubscribe_answers_for_names_it_was_not_holding_too() {
2808        let mut f = Fixture::new();
2809        f.run(&[b"SUBSCRIBE", b"a"]);
2810
2811        // A name that was never subscribed still gets a reply, with the count
2812        // as it stands.
2813        assert_eq!(
2814            f.run(&[b"UNSUBSCRIBE", b"zz"]),
2815            "*3\r\n$11\r\nunsubscribe\r\n$2\r\nzz\r\n:1\r\n"
2816        );
2817        // With no names, one reply per channel held, counting down.
2818        f.run(&[b"SUBSCRIBE", b"b"]);
2819        f.run(&[b"PSUBSCRIBE", b"p*"]);
2820        assert_eq!(
2821            f.run(&[b"UNSUBSCRIBE"]),
2822            "*3\r\n$11\r\nunsubscribe\r\n$1\r\na\r\n:2\r\n*3\r\n$11\r\nunsubscribe\r\n$1\r\nb\r\n:1\r\n"
2823        );
2824        // With no names and none of that family held, one reply with a nil
2825        // where the name goes and the count that is left.
2826        assert_eq!(
2827            f.run(&[b"UNSUBSCRIBE"]),
2828            "*3\r\n$11\r\nunsubscribe\r\n$-1\r\n:1\r\n",
2829            "the pattern is still held, so the count is one"
2830        );
2831        assert_eq!(
2832            f.run(&[b"SUNSUBSCRIBE"]),
2833            "*3\r\n$12\r\nsunsubscribe\r\n$-1\r\n:0\r\n",
2834            "shard channels are counted on their own"
2835        );
2836    }
2837
2838    /// The gate is on the funnel and the funnel is what `EXEC` goes through
2839    /// for the commands it queued, so it has to know it is running one.
2840    /// Redis lets a queued command through, and a transaction that subscribes
2841    /// and then reads is the case that says which way round it is.
2842    #[test]
2843    fn the_subscribe_gate_does_not_reach_inside_exec() {
2844        let mut f = Fixture::new();
2845        f.run(&[b"SET", b"k", b"1"]);
2846        f.run(&[b"MULTI"]);
2847        assert_eq!(f.run(&[b"SUBSCRIBE", b"z"]), "+QUEUED\r\n");
2848        assert_eq!(f.run(&[b"GET", b"k"]), "+QUEUED\r\n");
2849        assert_eq!(
2850            f.run(&[b"EXEC"]),
2851            "*2\r\n*3\r\n$9\r\nsubscribe\r\n$1\r\nz\r\n:1\r\n$1\r\n1\r\n"
2852        );
2853        // And once EXEC is done the connection really is subscribed, so the
2854        // gate is back on.
2855        assert_eq!(
2856            f.run(&[b"GET", b"k"]),
2857            "-ERR Can't execute 'get': only (P|S)SUBSCRIBE / (P|S)UNSUBSCRIBE / PING / QUIT / RESET are allowed in this context\r\n"
2858        );
2859    }
2860
2861    /// `EXEC` sent by a subscribed RESP2 client is refused by the gate like
2862    /// anything else, and a refusal on the funnel kills the transaction.
2863    #[test]
2864    fn exec_sent_by_a_subscriber_aborts_the_transaction() {
2865        let mut f = Fixture::new();
2866        f.run(&[b"MULTI"]);
2867        f.run(&[b"SET", b"k", b"1"]);
2868        f.run(&[b"SUBSCRIBE", b"z"]);
2869        f.run(&[b"EXEC"]);
2870        f.run(&[b"MULTI"]);
2871        assert_eq!(
2872            f.run(&[b"EXEC"]),
2873            "-EXECABORT Transaction discarded because of: Can't execute 'exec': only (P|S)SUBSCRIBE / (P|S)UNSUBSCRIBE / PING / QUIT / RESET are allowed in this context\r\n"
2874        );
2875    }
2876
2877    /// `RESET` is one of the few things a subscriber may send, and what it
2878    /// resets includes every subscription it is holding.
2879    #[test]
2880    fn reset_lets_go_of_every_subscription() {
2881        let mut f = Fixture::new();
2882        f.run(&[b"SUBSCRIBE", b"a"]);
2883        f.run(&[b"PSUBSCRIBE", b"p*"]);
2884        f.run(&[b"SSUBSCRIBE", b"s"]);
2885        assert_eq!(f.run(&[b"RESET"]), "+RESET\r\n");
2886        assert_eq!(f.run(&[b"PUBSUB", b"NUMPAT"]), ":0\r\n");
2887        assert_eq!(f.run(&[b"PUBSUB", b"CHANNELS"]), "*0\r\n");
2888        assert_eq!(f.run(&[b"PUBSUB", b"SHARDCHANNELS"]), "*0\r\n");
2889        // And the connection takes ordinary commands again.
2890        assert_eq!(f.run(&[b"GET", b"k"]), "$-1\r\n");
2891    }
2892
2893    /// What `PUBSUB` can be asked, on a server with one subscriber holding one
2894    /// of each.
2895    #[test]
2896    fn pubsub_reports_channels_patterns_and_shard_channels_apart() {
2897        let mut f = Fixture::new();
2898        let mut sub = Session::new(9);
2899        f.by(&mut sub, &[b"SUBSCRIBE", b"a"]);
2900        f.by(&mut sub, &[b"PSUBSCRIBE", b"a*"]);
2901        f.by(&mut sub, &[b"SSUBSCRIBE", b"a"]);
2902
2903        assert_eq!(f.run(&[b"PUBSUB", b"CHANNELS"]), "*1\r\n$1\r\na\r\n");
2904        assert_eq!(f.run(&[b"PUBSUB", b"CHANNELS", b"b*"]), "*0\r\n");
2905        assert_eq!(f.run(&[b"PUBSUB", b"SHARDCHANNELS"]), "*1\r\n$1\r\na\r\n");
2906        assert_eq!(f.run(&[b"PUBSUB", b"NUMPAT"]), ":1\r\n");
2907        assert_eq!(
2908            f.run(&[b"PUBSUB", b"NUMSUB", b"a", b"zz"]),
2909            "*4\r\n$1\r\na\r\n:1\r\n$2\r\nzz\r\n:0\r\n"
2910        );
2911        assert_eq!(
2912            f.run(&[b"PUBSUB", b"SHARDNUMSUB", b"a"]),
2913            "*2\r\n$1\r\na\r\n:1\r\n",
2914            "the shard channel and the channel share a name and not a count"
2915        );
2916        assert_eq!(f.run(&[b"PUBSUB", b"NUMSUB"]), "*0\r\n");
2917
2918        forget_session(&f.server, &mut sub);
2919        assert_eq!(f.run(&[b"PUBSUB", b"NUMPAT"]), ":0\r\n");
2920        assert_eq!(f.run(&[b"PUBSUB", b"CHANNELS"]), "*0\r\n");
2921    }
2922
2923    /// The one setting whose value is neither a number nor a word, and whose
2924    /// spelling on the way out is not the spelling on the way in.
2925    #[test]
2926    fn the_notification_setting_reads_back_in_the_servers_own_spelling() {
2927        let mut f = Fixture::new();
2928        assert_eq!(
2929            f.run(&[b"CONFIG", b"GET", b"notify-keyspace-events"]),
2930            "*2\r\n$22\r\nnotify-keyspace-events\r\n$0\r\n\r\n"
2931        );
2932        assert_eq!(
2933            f.run(&[b"CONFIG", b"SET", b"notify-keyspace-events", b"KEA"]),
2934            "+OK\r\n"
2935        );
2936        // `A` is a class of its own on the way in and stays one on the way out,
2937        // and the two channel letters move to the end.
2938        assert_eq!(
2939            f.run(&[b"CONFIG", b"GET", b"notify-keyspace-events"]),
2940            "*2\r\n$22\r\nnotify-keyspace-events\r\n$3\r\nAKE\r\n"
2941        );
2942        assert_eq!(
2943            f.run(&[b"CONFIG", b"SET", b"notify-keyspace-events", b"Kg"]),
2944            "+OK\r\n"
2945        );
2946        assert_eq!(
2947            f.run(&[b"CONFIG", b"GET", b"notify-keyspace-events"]),
2948            "*2\r\n$22\r\nnotify-keyspace-events\r\n$2\r\ngK\r\n"
2949        );
2950    }
2951
2952    #[test]
2953    fn a_letter_the_notification_setting_does_not_know_is_refused() {
2954        let mut f = Fixture::new();
2955        assert_eq!(
2956            f.run(&[b"CONFIG", b"SET", b"notify-keyspace-events", b"KEQ"]),
2957            "-ERR CONFIG SET failed (possibly related to argument 'notify-keyspace-events') \
2958             - Invalid event class character. Use 'Ag$lshzxeKEtmdnocaSTIV'.\r\n"
2959        );
2960        // And nothing was applied, since the whole setting is parsed before any
2961        // of it is stored.
2962        assert_eq!(
2963            f.run(&[b"CONFIG", b"GET", b"notify-keyspace-events"]),
2964            "*2\r\n$22\r\nnotify-keyspace-events\r\n$0\r\n\r\n"
2965        );
2966    }
2967
2968    /// One mistake in a `PUBSUB` subcommand has two error shapes depending on
2969    /// which subcommand it is, because the ones with a fixed argument count are
2970    /// checked by the subcommand table and the ones without fall through to
2971    /// the generic syntax error. Both are copied here rather than tidied,
2972    /// since a client that matches on the text sees the difference.
2973    #[test]
2974    fn pubsub_says_no_two_different_ways() {
2975        let mut f = Fixture::new();
2976        assert_eq!(
2977            f.run(&[b"PUBSUB"]),
2978            "-ERR wrong number of arguments for 'pubsub' command\r\n"
2979        );
2980        assert_eq!(
2981            f.run(&[b"PUBSUB", b"NOPE"]),
2982            "-ERR unknown subcommand 'NOPE'. Try PUBSUB HELP.\r\n"
2983        );
2984        assert_eq!(
2985            f.run(&[b"PUBSUB", b"CHANNELS", b"a*", b"b"]),
2986            "-ERR unknown subcommand or wrong number of arguments for 'CHANNELS'. Try PUBSUB HELP.\r\n"
2987        );
2988        assert_eq!(
2989            f.run(&[b"PUBSUB", b"NUMPAT", b"x"]),
2990            "-ERR wrong number of arguments for 'pubsub|numpat' command\r\n"
2991        );
2992        assert_eq!(
2993            f.run(&[b"PUBSUB", b"HELP", b"x"]),
2994            "-ERR wrong number of arguments for 'pubsub|help' command\r\n"
2995        );
2996    }
2997
2998    /// Publishing to nobody costs a lookup and answers zero, which is the
2999    /// common case on a server that has pub/sub compiled in and not in use.
3000    #[test]
3001    fn publishing_to_nobody_answers_zero() {
3002        let mut f = Fixture::new();
3003        assert_eq!(f.run(&[b"PUBLISH", b"a", b"hi"]), ":0\r\n");
3004        assert_eq!(f.run(&[b"SPUBLISH", b"a", b"hi"]), ":0\r\n");
3005        // An empty channel name is a name like any other.
3006        assert_eq!(f.run(&[b"PUBLISH", b"", b"hi"]), ":0\r\n");
3007    }
3008
3009    /// A publish counts everybody it reached, which is not the same as the
3010    /// number of subscribers: one connection holding two patterns that both
3011    /// match is two.
3012    #[test]
3013    fn a_publish_counts_the_deliveries_and_not_the_clients() {
3014        let mut f = Fixture::new();
3015        let mut sub = Session::new(9);
3016        f.by(&mut sub, &[b"SUBSCRIBE", b"news"]);
3017        f.by(&mut sub, &[b"PSUBSCRIBE", b"ne*"]);
3018        f.by(&mut sub, &[b"PSUBSCRIBE", b"n*s"]);
3019        assert_eq!(f.run(&[b"PUBLISH", b"news", b"hi"]), ":3\r\n");
3020        forget_session(&f.server, &mut sub);
3021    }
3022
3023    /// What a client does all day: write the same keys again and again. Every
3024    /// one of those writes leaves the previous record behind, so a server that
3025    /// never compacts holds every version of every key it has ever been sent.
3026    ///
3027    /// Not under Miri, and not because of anything it would find. The bound
3028    /// only means something once several megabytes have gone through the
3029    /// arena, which reclaims a segment at a time and has segments of two
3030    /// megabytes, so a server that reclaimed nothing would still be under the
3031    /// bound in any smaller version of this. Thirty two megabytes is thirty
3032    /// two thousand commands and was over forty minutes interpreted. The paths
3033    /// it walks are walked by the hundreds of tests around it that write a key
3034    /// and read it back, which do run there.
3035    #[cfg_attr(miri, ignore = "megabytes through the arena")]
3036    #[test]
3037    fn rewriting_the_same_keys_does_not_grow_the_server() {
3038        let mut f = Fixture::new();
3039        let val = vec![b'v'; 1024];
3040        let keys: Vec<Vec<u8>> = (0..64).map(|i| format!("key:{i}").into_bytes()).collect();
3041
3042        for k in &keys {
3043            f.run(&[b"SET", k, &val]);
3044        }
3045        f.server.compact_step();
3046        let after_first = f.server.memory_bytes();
3047
3048        // 64 KiB a pass, five hundred passes, and the same 64 keys at the end
3049        // of it. Thirty two megabytes written to hold sixty four kilobytes,
3050        // which is the shape of a real workload and is enough churn to fill
3051        // sixteen segments if nothing ever comes back.
3052        for _ in 0..500 {
3053            for k in &keys {
3054                f.run(&[b"SET", k, &val]);
3055            }
3056            f.server.compact_step();
3057        }
3058
3059        assert!(
3060            f.server.memory_bytes() <= after_first * 2,
3061            "held {} after five hundred passes against {after_first} after one",
3062            f.server.memory_bytes()
3063        );
3064        assert_eq!(f.run(&[b"DBSIZE"]), format!(":{}\r\n", keys.len()));
3065        assert_eq!(f.run(&[b"STRLEN", b"key:7"]), ":1024\r\n");
3066    }
3067
3068    /// The same churn on a database nobody starts on, either side of a quiet
3069    /// spell long enough for the maintenance turn to stop asking about it.
3070    ///
3071    /// The turn after each batch skips a database that has already said it has
3072    /// nothing to collect and has not been touched since, which is what keeps a
3073    /// server whose clients are all on database zero from loading and storing
3074    /// in the other fifteen every batch to be told no. Two things could go
3075    /// wrong with that. A database might never be marked at all, so this uses
3076    /// database nine, which nothing marks by accident. And a database whose
3077    /// mark was cleared might never get it back, so this drains the collector
3078    /// until it says there is nothing left, checks the mark really is gone, and
3079    /// then writes another thirty two megabytes through the same sixty four
3080    /// keys. If either went wrong the server would hold all of it.
3081    ///
3082    /// Not under Miri, for the reason on the test above: the volume is the
3083    /// claim, and the volume is what the interpreter charges for.
3084    #[cfg_attr(miri, ignore = "megabytes through the arena")]
3085    #[test]
3086    fn a_database_nobody_started_on_is_still_collected() {
3087        let mut f = Fixture::new();
3088        assert_eq!(f.run(&[b"SELECT", b"9"]), "+OK\r\n");
3089        let val = vec![b'v'; 1024];
3090        let keys: Vec<Vec<u8>> = (0..64).map(|i| format!("key:{i}").into_bytes()).collect();
3091
3092        for k in &keys {
3093            f.run(&[b"SET", k, &val]);
3094        }
3095        while f.server.compact_step().is_some() {}
3096        assert!(
3097            !f.server.mine().wanted(9),
3098            "database nine was drained and should not be asked again until it is written to"
3099        );
3100        let after_first = f.server.memory_bytes();
3101
3102        for _ in 0..500 {
3103            for k in &keys {
3104                f.run(&[b"SET", k, &val]);
3105            }
3106            f.server.compact_step();
3107        }
3108
3109        assert!(
3110            f.server.memory_bytes() <= after_first * 2,
3111            "held {} after five hundred passes against {after_first} after one",
3112            f.server.memory_bytes()
3113        );
3114        assert_eq!(f.run(&[b"DBSIZE"]), format!(":{}\r\n", keys.len()));
3115        assert_eq!(f.run(&[b"STRLEN", b"key:7"]), ":1024\r\n");
3116        // And nothing landed anywhere else on the way.
3117        f.run(&[b"SELECT", b"0"]);
3118        assert_eq!(f.run(&[b"DBSIZE"]), ":0\r\n");
3119    }
3120
3121    #[test]
3122    fn a_command_goes_from_bytes_to_bytes() {
3123        let mut f = Fixture::new();
3124        assert_eq!(f.run(&[b"SET", b"k", b"v"]), "+OK\r\n");
3125        assert_eq!(f.run(&[b"GET", b"k"]), "$1\r\nv\r\n");
3126        assert_eq!(f.run(&[b"GET", b"nosuch"]), "$-1\r\n");
3127        assert_eq!(f.run(&[b"STRLEN", b"k"]), ":1\r\n");
3128        // The name is matched whatever case it came in, and so are the options.
3129        assert_eq!(f.run(&[b"set", b"k", b"v2", b"xx"]), "+OK\r\n");
3130        assert_eq!(f.run(&[b"GET", b"k"]), "$2\r\nv2\r\n");
3131    }
3132
3133    #[test]
3134    fn deleting_counts_keys_removed_and_existing_counts_arguments_matched() {
3135        let mut f = Fixture::new();
3136        f.run(&[b"MSET", b"a", b"1", b"b", b"2", b"c", b"3"]);
3137        // A key named twice exists twice and can only be deleted once, and both
3138        // of those are Redis's answers rather than tidier ones.
3139        assert_eq!(f.run(&[b"EXISTS", b"a", b"a", b"nosuch"]), ":2\r\n");
3140        assert_eq!(f.run(&[b"DEL", b"a", b"a", b"nosuch"]), ":1\r\n");
3141        assert_eq!(f.run(&[b"EXISTS", b"a"]), ":0\r\n");
3142        // UNLINK is the same body and reports the same way.
3143        assert_eq!(f.run(&[b"UNLINK", b"b", b"c"]), ":2\r\n");
3144        assert_eq!(f.run(&[b"DBSIZE"]), ":0\r\n");
3145    }
3146
3147    #[test]
3148    fn type_is_a_simple_string_and_says_none_for_a_key_that_is_not_there() {
3149        let mut f = Fixture::new();
3150        f.run(&[b"SET", b"k", b"v"]);
3151        // A simple string on both protocols, which is unusual: most replies
3152        // that carry a word are bulk strings.
3153        assert_eq!(f.run(&[b"TYPE", b"k"]), "+string\r\n");
3154        assert_eq!(f.run(&[b"TYPE", b"nosuch"]), "+none\r\n");
3155    }
3156
3157    #[test]
3158    fn touch_counts_the_way_exists_counts() {
3159        let mut f = Fixture::new();
3160        f.run(&[b"MSET", b"a", b"1", b"b", b"2"]);
3161        assert_eq!(f.run(&[b"TOUCH", b"a", b"b"]), ":2\r\n");
3162        assert_eq!(
3163            f.run(&[b"TOUCH", b"a", b"a"]),
3164            ":2\r\n",
3165            "twice counts twice"
3166        );
3167        assert_eq!(f.run(&[b"TOUCH", b"a", b"nosuch"]), ":1\r\n");
3168        assert_eq!(f.run(&[b"TOUCH", b"nosuch"]), ":0\r\n");
3169    }
3170
3171    #[test]
3172    fn a_rename_moves_the_deadline_with_the_value_and_drops_the_one_it_lands_on() {
3173        let mut f = Fixture::new();
3174        f.run(&[b"SET", b"a", b"v1", b"EX", b"100"]);
3175        f.run(&[b"SET", b"b", b"v2", b"EX", b"500"]);
3176
3177        assert_eq!(f.run(&[b"RENAME", b"a", b"b"]), "+OK\r\n");
3178        assert_eq!(f.run(&[b"GET", b"b"]), "$2\r\nv1\r\n");
3179        assert_eq!(
3180            f.run(&[b"TTL", b"b"]),
3181            ":100\r\n",
3182            "the source's and not b's"
3183        );
3184        assert_eq!(f.run(&[b"EXISTS", b"a"]), ":0\r\n");
3185    }
3186
3187    #[test]
3188    fn a_rename_with_no_source_is_an_error_and_not_a_zero() {
3189        let mut f = Fixture::new();
3190        assert_eq!(f.run(&[b"RENAME", b"a", b"b"]), "-ERR no such key\r\n");
3191        // The source is checked before the destination, so this is the error
3192        // and not the zero RENAMENX would otherwise answer for a taken name.
3193        assert_eq!(f.run(&[b"RENAMENX", b"a", b"a"]), "-ERR no such key\r\n");
3194    }
3195
3196    #[test]
3197    fn renamenx_refuses_a_taken_name_including_the_one_it_already_has() {
3198        let mut f = Fixture::new();
3199        f.run(&[b"MSET", b"a", b"v1", b"b", b"v2"]);
3200
3201        assert_eq!(f.run(&[b"RENAMENX", b"a", b"b"]), ":0\r\n");
3202        assert_eq!(f.run(&[b"GET", b"b"]), "$2\r\nv2\r\n");
3203        // Renaming onto itself is 0 here and OK for plain RENAME, which is the
3204        // one call the two disagree about and neither does any work for.
3205        assert_eq!(f.run(&[b"RENAMENX", b"a", b"a"]), ":0\r\n");
3206        assert_eq!(f.run(&[b"RENAME", b"a", b"a"]), "+OK\r\n");
3207        assert_eq!(f.run(&[b"RENAMENX", b"a", b"c"]), ":1\r\n");
3208        assert_eq!(f.run(&[b"GET", b"c"]), "$2\r\nv1\r\n");
3209    }
3210
3211    #[test]
3212    fn renaming_a_set_does_not_touch_a_member() {
3213        let mut f = Fixture::new();
3214        for i in 0..300 {
3215            f.run(&[b"SADD", b"s", format!("m{i}").as_bytes()]);
3216        }
3217        let before = f.server.memory_bytes();
3218
3219        assert_eq!(f.run(&[b"RENAME", b"s", b"t"]), "+OK\r\n");
3220        assert_eq!(f.run(&[b"SCARD", b"t"]), ":300\r\n");
3221        assert_eq!(f.run(&[b"TYPE", b"t"]), "+set\r\n");
3222        assert!(
3223            f.server.memory_bytes().abs_diff(before) < 256,
3224            "the members were copied: {} against {before}",
3225            f.server.memory_bytes()
3226        );
3227    }
3228
3229    #[test]
3230    fn a_copy_is_a_second_value_and_not_a_second_name() {
3231        let mut f = Fixture::new();
3232        f.run(&[b"SADD", b"s", b"m1", b"m2"]);
3233
3234        assert_eq!(f.run(&[b"COPY", b"s", b"t"]), ":1\r\n");
3235        f.run(&[b"SADD", b"t", b"m3"]);
3236        assert_eq!(f.run(&[b"SCARD", b"s"]), ":2\r\n", "the original is intact");
3237        assert_eq!(f.run(&[b"SCARD", b"t"]), ":3\r\n");
3238    }
3239
3240    /// Every type a key can hold, copied, because two of them used to panic.
3241    ///
3242    /// `COPY` reads the value out of the source through one match on the type
3243    /// tag, and that match had a catch all at the bottom from back when a set
3244    /// and a hash were the only bodies. The list and the sorted set landed after
3245    /// it and nobody came back, so `COPY mylist other` took the shard down. It
3246    /// is an ordinary command against a type the server supports everywhere
3247    /// else, so this walks all five rather than the two that were broken: the
3248    /// point is that the next type cannot land the same way.
3249    #[test]
3250    fn every_type_can_be_copied() {
3251        let mut f = Fixture::new();
3252        f.run(&[b"SET", b"str", b"v1"]);
3253        f.run(&[b"SADD", b"set", b"m1"]);
3254        f.run(&[b"HSET", b"hash", b"f", b"v"]);
3255        f.run(&[b"RPUSH", b"list", b"a", b"b"]);
3256        f.run(&[b"ZADD", b"zset", b"1", b"m1"]);
3257
3258        for name in [
3259            &b"str"[..],
3260            &b"set"[..],
3261            &b"hash"[..],
3262            &b"list"[..],
3263            &b"zset"[..],
3264        ] {
3265            let dst = [name, b":copy"].concat();
3266            assert_eq!(
3267                f.run(&[b"COPY", name, &dst]),
3268                ":1\r\n",
3269                "copying {}",
3270                String::from_utf8_lossy(name)
3271            );
3272            assert_eq!(f.run(&[b"TYPE", name]), f.run(&[b"TYPE", &dst]));
3273        }
3274
3275        assert_eq!(f.run(&[b"LRANGE", b"list:copy", b"0", b"-1"]), {
3276            let mut want = String::from("*2\r\n");
3277            want.push_str("$1\r\na\r\n$1\r\nb\r\n");
3278            want
3279        });
3280        assert_eq!(f.run(&[b"ZSCORE", b"zset:copy", b"m1"]), "$1\r\n1\r\n");
3281
3282        // And the copy is its own value, not a second name for the source.
3283        f.run(&[b"RPUSH", b"list:copy", b"c"]);
3284        assert_eq!(f.run(&[b"LLEN", b"list"]), ":2\r\n");
3285        assert_eq!(f.run(&[b"LLEN", b"list:copy"]), ":3\r\n");
3286    }
3287
3288    #[test]
3289    fn a_copy_refuses_a_taken_destination_until_it_is_told_it_can_have_it() {
3290        let mut f = Fixture::new();
3291        f.run(&[b"SET", b"a", b"v1", b"EX", b"100"]);
3292        f.run(&[b"SET", b"b", b"v2"]);
3293
3294        assert_eq!(f.run(&[b"COPY", b"a", b"b"]), ":0\r\n");
3295        assert_eq!(f.run(&[b"GET", b"b"]), "$2\r\nv2\r\n");
3296        assert_eq!(f.run(&[b"COPY", b"a", b"b", b"REPLACE"]), ":1\r\n");
3297        assert_eq!(f.run(&[b"GET", b"b"]), "$2\r\nv1\r\n");
3298        assert_eq!(f.run(&[b"TTL", b"b"]), ":100\r\n", "the deadline came too");
3299        assert_eq!(f.run(&[b"COPY", b"nosuch", b"z"]), ":0\r\n");
3300    }
3301
3302    #[test]
3303    fn a_copy_into_another_database_is_a_copy_and_onto_itself_there_is_too() {
3304        let mut f = Fixture::new();
3305        f.run(&[b"SET", b"a", b"v1"]);
3306
3307        // Same key, different database, so this is not the same object and is
3308        // an ordinary copy. Same key in the same database is the error below.
3309        assert_eq!(f.run(&[b"COPY", b"a", b"a", b"DB", b"1"]), ":1\r\n");
3310        f.run(&[b"SELECT", b"1"]);
3311        assert_eq!(f.run(&[b"GET", b"a"]), "$2\r\nv1\r\n");
3312        assert_eq!(
3313            f.run(&[b"COPY", b"a", b"a", b"DB", b"0"]),
3314            ":0\r\n",
3315            "taken"
3316        );
3317        assert_eq!(
3318            f.run(&[b"COPY", b"a", b"a", b"DB", b"0", b"REPLACE"]),
3319            ":1\r\n"
3320        );
3321    }
3322
3323    #[test]
3324    fn sort_takes_its_options_in_any_order_and_the_last_one_wins() {
3325        let mut f = Fixture::new();
3326        f.run(&[b"RPUSH", b"l", b"3", b"1", b"2"]);
3327        assert_eq!(
3328            f.run(&[b"SORT", b"l"]),
3329            "*3\r\n$1\r\n1\r\n$1\r\n2\r\n$1\r\n3\r\n"
3330        );
3331        // DESC then ASC is ASC, because the only thing ASC does is undo a DESC.
3332        assert_eq!(
3333            f.run(&[b"SORT", b"l", b"DESC", b"asc"]),
3334            "*3\r\n$1\r\n1\r\n$1\r\n2\r\n$1\r\n3\r\n"
3335        );
3336        assert_eq!(
3337            f.run(&[b"sort", b"l", b"LIMIT", b"1", b"1", b"DESC"]),
3338            "*1\r\n$1\r\n2\r\n"
3339        );
3340    }
3341
3342    #[test]
3343    fn sort_reads_a_key_per_element_for_by_and_for_get() {
3344        let mut f = Fixture::new();
3345        f.run(&[b"RPUSH", b"l", b"a", b"b"]);
3346        f.run(&[b"MSET", b"w_a", b"2", b"w_b", b"1", b"d_b", b"bee"]);
3347        // `b` weighs less so it comes first, and its `GET` hits where `a`'s
3348        // misses, which is a nil in the middle of the array and not a short one.
3349        assert_eq!(
3350            f.run(&[b"SORT", b"l", b"BY", b"w_*", b"GET", b"#", b"GET", b"d_*"]),
3351            "*4\r\n$1\r\nb\r\n$3\r\nbee\r\n$1\r\na\r\n$-1\r\n"
3352        );
3353    }
3354
3355    #[test]
3356    fn sort_store_writes_a_list_and_answers_its_length() {
3357        let mut f = Fixture::new();
3358        f.run(&[b"RPUSH", b"l", b"3", b"1", b"2"]);
3359        assert_eq!(f.run(&[b"SORT", b"l", b"STORE", b"out"]), ":3\r\n");
3360        assert_eq!(f.run(&[b"TYPE", b"out"]), "+list\r\n");
3361        assert_eq!(
3362            f.run(&[b"LRANGE", b"out", b"0", b"-1"]),
3363            "*3\r\n$1\r\n1\r\n$1\r\n2\r\n$1\r\n3\r\n"
3364        );
3365        // An empty result takes the destination with it rather than leaving a
3366        // list that holds nothing.
3367        assert_eq!(f.run(&[b"SORT", b"missing", b"STORE", b"out"]), ":0\r\n");
3368        assert_eq!(f.run(&[b"EXISTS", b"out"]), ":0\r\n");
3369    }
3370
3371    #[test]
3372    fn sort_ro_does_not_know_the_word_store() {
3373        let mut f = Fixture::new();
3374        f.run(&[b"RPUSH", b"l", b"2", b"1"]);
3375        assert_eq!(f.run(&[b"SORT_RO", b"l"]), "*2\r\n$1\r\n1\r\n$1\r\n2\r\n");
3376        assert_eq!(
3377            f.run(&[b"SORT_RO", b"l", b"STORE", b"d"]),
3378            "-ERR syntax error\r\n"
3379        );
3380        assert_eq!(f.run(&[b"EXISTS", b"d"]), ":0\r\n");
3381    }
3382
3383    #[test]
3384    fn sort_refuses_what_it_cannot_sort() {
3385        let mut f = Fixture::new();
3386        assert_eq!(f.run(&[b"SORT", b"nosuchkey"]), "*0\r\n");
3387        f.run(&[b"SET", b"s", b"x"]);
3388        assert_eq!(
3389            f.run(&[b"SORT", b"s"]),
3390            "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n"
3391        );
3392        f.run(&[b"RPUSH", b"words", b"one", b"two"]);
3393        assert_eq!(
3394            f.run(&[b"SORT", b"words"]),
3395            "-ERR One or more scores can't be converted into double\r\n"
3396        );
3397        assert_eq!(
3398            f.run(&[b"SORT", b"words", b"ALPHA"]),
3399            "*2\r\n$3\r\none\r\n$3\r\ntwo\r\n"
3400        );
3401        assert_eq!(f.run(&[b"SORT", b"words", b"BY"]), "-ERR syntax error\r\n");
3402    }
3403
3404    #[test]
3405    fn move_takes_the_key_out_of_one_database_and_puts_it_in_another() {
3406        let mut f = Fixture::new();
3407        assert_eq!(f.run(&[b"RPUSH", b"l", b"a", b"b"]), ":2\r\n");
3408        assert_eq!(f.run(&[b"MOVE", b"l", b"1"]), ":1\r\n");
3409        assert_eq!(f.run(&[b"EXISTS", b"l"]), ":0\r\n");
3410        assert_eq!(f.run(&[b"SELECT", b"1"]), "+OK\r\n");
3411        assert_eq!(
3412            f.run(&[b"LRANGE", b"l", b"0", b"-1"]),
3413            "*2\r\n$1\r\na\r\n$1\r\nb\r\n"
3414        );
3415        // And back, which proves the body survived the trip rather than being
3416        // rebuilt from a copy that happened to look the same.
3417        assert_eq!(f.run(&[b"MOVE", b"l", b"0"]), ":1\r\n");
3418        assert_eq!(f.run(&[b"EXISTS", b"l"]), ":0\r\n");
3419    }
3420
3421    #[test]
3422    fn move_answers_zero_when_either_end_says_no() {
3423        let mut f = Fixture::new();
3424        assert_eq!(f.run(&[b"MOVE", b"nope", b"1"]), ":0\r\n");
3425        assert_eq!(f.run(&[b"SET", b"a", b"here"]), "+OK\r\n");
3426        assert_eq!(f.run(&[b"SELECT", b"1"]), "+OK\r\n");
3427        assert_eq!(f.run(&[b"SET", b"a", b"there"]), "+OK\r\n");
3428        assert_eq!(f.run(&[b"SELECT", b"0"]), "+OK\r\n");
3429        // The destination is taken, so nothing moves and the source is still
3430        // there with what it had.
3431        assert_eq!(f.run(&[b"MOVE", b"a", b"1"]), ":0\r\n");
3432        assert_eq!(f.run(&[b"GET", b"a"]), "$4\r\nhere\r\n");
3433        assert_eq!(f.run(&[b"SELECT", b"1"]), "+OK\r\n");
3434        assert_eq!(f.run(&[b"GET", b"a"]), "$5\r\nthere\r\n");
3435    }
3436
3437    #[test]
3438    fn move_refuses_a_database_that_is_not_one_and_the_one_it_is_on() {
3439        let mut f = Fixture::new();
3440        assert_eq!(
3441            f.run(&[b"MOVE", b"a", b"0"]),
3442            "-ERR source and destination objects are the same\r\n"
3443        );
3444        assert_eq!(
3445            f.run(&[b"MOVE", b"a", b"99"]),
3446            "-ERR DB index is out of range\r\n"
3447        );
3448        assert_eq!(
3449            f.run(&[b"MOVE", b"a", b"-1"]),
3450            "-ERR DB index is out of range\r\n"
3451        );
3452        assert_eq!(
3453            f.run(&[b"MOVE", b"a", b"x"]),
3454            "-ERR value is not an integer or out of range\r\n"
3455        );
3456    }
3457
3458    #[test]
3459    fn swapdb_swaps_what_two_connections_would_see() {
3460        let mut f = Fixture::new();
3461        assert_eq!(f.run(&[b"SET", b"k", b"zero"]), "+OK\r\n");
3462        assert_eq!(f.run(&[b"SELECT", b"1"]), "+OK\r\n");
3463        assert_eq!(f.run(&[b"SET", b"k", b"one"]), "+OK\r\n");
3464        assert_eq!(f.run(&[b"SELECT", b"0"]), "+OK\r\n");
3465
3466        assert_eq!(f.run(&[b"SWAPDB", b"0", b"1"]), "+OK\r\n");
3467        // Still on database zero, and database zero is a different database.
3468        assert_eq!(f.run(&[b"GET", b"k"]), "$3\r\none\r\n");
3469        assert_eq!(f.run(&[b"SELECT", b"1"]), "+OK\r\n");
3470        assert_eq!(f.run(&[b"GET", b"k"]), "$4\r\nzero\r\n");
3471        // A database swapped with itself is fine and changes nothing.
3472        assert_eq!(f.run(&[b"SWAPDB", b"1", b"1"]), "+OK\r\n");
3473        assert_eq!(f.run(&[b"GET", b"k"]), "$4\r\nzero\r\n");
3474    }
3475
3476    /// Every database on a server reads the server's clock and not one of its
3477    /// own. They used to be told the time one at a time and now they share the
3478    /// reading, so a server that built its databases from a second clock would
3479    /// answer a deadline worked out against a time nobody had set.
3480    #[test]
3481    fn a_wide_server_puts_its_databases_on_its_own_clock() {
3482        let mut f = Fixture::striped(8);
3483        f.server.set_clock_ms(1_700_000_000_000);
3484        assert_eq!(f.run(&[b"SET", b"k", b"v", b"EX", b"100"]), "+OK\r\n");
3485        assert_eq!(f.run(&[b"EXPIRETIME", b"k"]), ":1700000100\r\n");
3486        assert_eq!(f.run(&[b"TTL", b"k"]), ":100\r\n");
3487        f.server.set_clock_ms(1_700_000_050_000);
3488        assert_eq!(f.run(&[b"TTL", b"k"]), ":50\r\n");
3489    }
3490
3491    /// The swap is stripe by stripe, so a database cut into more than one
3492    /// stripe is the case that would catch it exchanging some of the keys and
3493    /// leaving the rest. Sixteen keys over four stripes is enough that every
3494    /// stripe has something in it whatever the hashes come out as.
3495    #[test]
3496    fn swapdb_swaps_every_stripe_of_a_wide_database() {
3497        let mut f = Fixture::striped(4);
3498        for i in 0..16u32 {
3499            let key = format!("k{i}");
3500            assert_eq!(f.run(&[b"SET", key.as_bytes(), b"zero"]), "+OK\r\n");
3501        }
3502        assert_eq!(f.run(&[b"SELECT", b"1"]), "+OK\r\n");
3503        assert_eq!(f.run(&[b"SET", b"only", b"one"]), "+OK\r\n");
3504        assert_eq!(f.run(&[b"SELECT", b"0"]), "+OK\r\n");
3505
3506        assert_eq!(f.run(&[b"SWAPDB", b"0", b"1"]), "+OK\r\n");
3507        assert_eq!(f.run(&[b"DBSIZE"]), ":1\r\n");
3508        assert_eq!(f.run(&[b"GET", b"only"]), "$3\r\none\r\n");
3509        assert_eq!(f.run(&[b"SELECT", b"1"]), "+OK\r\n");
3510        assert_eq!(f.run(&[b"DBSIZE"]), ":16\r\n");
3511        for i in 0..16u32 {
3512            let key = format!("k{i}");
3513            assert_eq!(f.run(&[b"GET", key.as_bytes()]), "$4\r\nzero\r\n");
3514        }
3515    }
3516
3517    #[test]
3518    fn swapdb_says_which_index_it_could_not_read() {
3519        let mut f = Fixture::new();
3520        assert_eq!(
3521            f.run(&[b"SWAPDB", b"x", b"1"]),
3522            "-ERR invalid first DB index\r\n"
3523        );
3524        assert_eq!(
3525            f.run(&[b"SWAPDB", b"0", b"y"]),
3526            "-ERR invalid second DB index\r\n"
3527        );
3528        // A number too big to be an index on a server that keeps one in an int
3529        // is the same complaint, and a plausible one that is not ours is the
3530        // range complaint instead. The split is Redis's.
3531        assert_eq!(
3532            f.run(&[b"SWAPDB", b"99999999999999", b"1"]),
3533            "-ERR invalid first DB index\r\n"
3534        );
3535        assert_eq!(
3536            f.run(&[b"SWAPDB", b"0", b"99"]),
3537            "-ERR DB index is out of range\r\n"
3538        );
3539        assert_eq!(
3540            f.run(&[b"SWAPDB", b"-1", b"0"]),
3541            "-ERR DB index is out of range\r\n"
3542        );
3543    }
3544
3545    #[test]
3546    fn wait_answers_zero_replicas_without_waiting() {
3547        let mut f = Fixture::new();
3548        assert_eq!(f.run(&[b"SET", b"a", b"v"]), "+OK\r\n");
3549        assert_eq!(f.run(&[b"WAIT", b"0", b"0"]), ":0\r\n");
3550        // A replica that is never going to arrive, and a timeout that would be
3551        // a real wait on a server that had one.
3552        assert_eq!(f.run(&[b"WAIT", b"3", b"1000"]), ":0\r\n");
3553        // Negative replicas is not an error, because zero is already more than
3554        // it asked for.
3555        assert_eq!(f.run(&[b"WAIT", b"-1", b"0"]), ":0\r\n");
3556        assert_eq!(
3557            f.run(&[b"WAIT", b"x", b"0"]),
3558            "-ERR value is not an integer or out of range\r\n"
3559        );
3560        assert_eq!(
3561            f.run(&[b"WAIT", b"0", b"-1"]),
3562            "-ERR timeout is negative\r\n"
3563        );
3564        assert_eq!(
3565            f.run(&[b"WAIT", b"0", b"1.5"]),
3566            "-ERR timeout is not an integer or out of range\r\n"
3567        );
3568    }
3569
3570    #[test]
3571    fn waitaof_answers_two_zeroes_and_refuses_a_local_wait() {
3572        let mut f = Fixture::new();
3573        assert_eq!(f.run(&[b"WAITAOF", b"0", b"0", b"0"]), "*2\r\n:0\r\n:0\r\n");
3574        assert_eq!(
3575            f.run(&[b"WAITAOF", b"1", b"0", b"0"]),
3576            "-ERR WAITAOF cannot be used when numlocal is set but appendonly is disabled.\r\n"
3577        );
3578        assert_eq!(
3579            f.run(&[b"WAITAOF", b"2", b"0", b"0"]),
3580            "-ERR value is out of range, value must between 0 and 1\r\n"
3581        );
3582        assert_eq!(
3583            f.run(&[b"WAITAOF", b"0", b"-1", b"0"]),
3584            "-ERR value is out of range, must be positive\r\n"
3585        );
3586        // The arguments are all read before the server looks at itself, so a
3587        // bad timeout beats the append only complaint even with numlocal set.
3588        assert_eq!(
3589            f.run(&[b"WAITAOF", b"1", b"0", b"-5"]),
3590            "-ERR timeout is negative\r\n"
3591        );
3592    }
3593
3594    /// The bytes inside a bulk reply, with the header and the trailing break
3595    /// taken off. Every `DUMP` test needs this and none of them care how the
3596    /// length was written.
3597    fn payload(reply: &[u8]) -> Vec<u8> {
3598        let head = reply.windows(2).position(|w| w == b"\r\n").unwrap();
3599        reply[head + 2..reply.len() - 2].to_vec()
3600    }
3601
3602    #[test]
3603    fn a_value_survives_a_dump_and_a_restore() {
3604        let mut f = Fixture::new();
3605        f.run(&[b"SET", b"s", b"hello"]);
3606        f.run(&[b"RPUSH", b"l", b"a", b"b", b"c"]);
3607        f.run(&[b"SADD", b"t", b"1", b"2", b"3"]);
3608        f.run(&[b"SADD", b"u", b"x", b"y"]);
3609        f.run(&[b"HSET", b"h", b"f", b"1", b"g", b"2"]);
3610        f.run(&[b"ZADD", b"z", b"1.5", b"a", b"2.5", b"b"]);
3611
3612        for key in [&b"s"[..], b"l", b"t", b"u", b"h", b"z"] {
3613            let mut copy = key.to_vec();
3614            copy.push(b'2');
3615            let bytes = payload(&f.raw(&[b"DUMP", key]));
3616            assert_eq!(f.run(&[b"RESTORE", &copy, b"0", &bytes]), "+OK\r\n");
3617            assert_eq!(f.run(&[b"TYPE", &copy]), f.run(&[b"TYPE", key]));
3618        }
3619
3620        assert_eq!(f.run(&[b"GET", b"s2"]), "$5\r\nhello\r\n");
3621        assert_eq!(
3622            f.run(&[b"LRANGE", b"l2", b"0", b"-1"]),
3623            "*3\r\n$1\r\na\r\n$1\r\nb\r\n$1\r\nc\r\n"
3624        );
3625        assert_eq!(sorted(&f.run(&[b"SMEMBERS", b"t2"])), ["1", "2", "3"]);
3626        assert_eq!(sorted(&f.run(&[b"SMEMBERS", b"u2"])), ["x", "y"]);
3627        assert_eq!(f.run(&[b"HGET", b"h2", b"g"]), "$1\r\n2\r\n");
3628        assert_eq!(f.run(&[b"ZSCORE", b"z2", b"b"]), "$3\r\n2.5\r\n");
3629        // The encoding survives too, since the payload names the plainest legal
3630        // type and the loader puts the value back on the rung it belongs on.
3631        assert_eq!(
3632            f.run(&[b"OBJECT", b"ENCODING", b"t2"]),
3633            f.run(&[b"OBJECT", b"ENCODING", b"t"])
3634        );
3635    }
3636
3637    #[test]
3638    fn a_dumped_hash_keeps_its_field_deadlines() {
3639        let mut f = Fixture::new();
3640        f.run(&[b"HSET", b"h", b"keep", b"1", b"go", b"2"]);
3641        assert_eq!(
3642            f.run(&[b"HEXPIRE", b"h", b"100", b"FIELDS", b"1", b"go"]),
3643            "*1\r\n:1\r\n"
3644        );
3645        let bytes = payload(&f.raw(&[b"DUMP", b"h"]));
3646        assert_eq!(f.run(&[b"RESTORE", b"h2", b"0", &bytes]), "+OK\r\n");
3647        assert_eq!(
3648            f.run(&[b"HTTL", b"h2", b"FIELDS", b"2", b"keep", b"go"]),
3649            "*2\r\n:-1\r\n:100\r\n"
3650        );
3651    }
3652
3653    #[test]
3654    fn dump_leaves_the_deadline_behind_and_restore_is_given_a_new_one() {
3655        let mut f = Fixture::new();
3656        f.run(&[b"SET", b"a", b"v", b"EX", b"100"]);
3657        let bytes = payload(&f.raw(&[b"DUMP", b"a"]));
3658        assert_eq!(f.run(&[b"RESTORE", b"b", b"0", &bytes]), "+OK\r\n");
3659        assert_eq!(f.run(&[b"TTL", b"b"]), ":-1\r\n");
3660        assert_eq!(f.run(&[b"RESTORE", b"c", b"5000", &bytes]), "+OK\r\n");
3661        assert_eq!(f.run(&[b"TTL", b"c"]), ":5\r\n");
3662        // An absolute deadline that has already gone is not an error. The key is
3663        // not created and the reply is the same OK a live one gets.
3664        assert_eq!(
3665            f.run(&[b"RESTORE", b"d", b"1", &bytes, b"ABSTTL"]),
3666            "+OK\r\n"
3667        );
3668        assert_eq!(f.run(&[b"EXISTS", b"d"]), ":0\r\n");
3669    }
3670
3671    #[test]
3672    fn dump_answers_nothing_for_a_key_that_is_not_there() {
3673        let mut f = Fixture::new();
3674        assert_eq!(f.run(&[b"DUMP", b"nope"]), "$-1\r\n");
3675        f.run(&[b"SET", b"gone", b"v", b"PX", b"10"]);
3676        f.advance(50);
3677        assert_eq!(f.run(&[b"DUMP", b"gone"]), "$-1\r\n");
3678    }
3679
3680    #[test]
3681    fn restore_refuses_a_key_that_is_there_unless_it_is_told_to_replace() {
3682        let mut f = Fixture::new();
3683        f.run(&[b"SET", b"a", b"first"]);
3684        f.run(&[b"SET", b"b", b"second"]);
3685        let bytes = payload(&f.raw(&[b"DUMP", b"b"]));
3686        assert_eq!(
3687            f.run(&[b"RESTORE", b"a", b"0", &bytes]),
3688            "-BUSYKEY Target key name already exists.\r\n"
3689        );
3690        assert_eq!(f.run(&[b"GET", b"a"]), "$5\r\nfirst\r\n");
3691        assert_eq!(
3692            f.run(&[b"RESTORE", b"a", b"0", &bytes, b"REPLACE"]),
3693            "+OK\r\n"
3694        );
3695        assert_eq!(f.run(&[b"GET", b"a"]), "$6\r\nsecond\r\n");
3696    }
3697
3698    /// The busy key comes before the payload, which is not the order the
3699    /// arguments read in. Whether a key is taken should not depend on whether
3700    /// the bytes behind it happened to be good.
3701    #[test]
3702    fn restore_asks_about_the_key_before_it_looks_at_the_bytes() {
3703        let mut f = Fixture::new();
3704        f.run(&[b"SET", b"a", b"v"]);
3705        assert_eq!(
3706            f.run(&[b"RESTORE", b"a", b"0", b"rubbish"]),
3707            "-BUSYKEY Target key name already exists.\r\n"
3708        );
3709        // And the options come before even that, so a bad FREQ beats the busy
3710        // key the same way a bad DB beats a missing source in COPY.
3711        assert_eq!(
3712            f.run(&[b"RESTORE", b"a", b"0", b"rubbish", b"FREQ", b"300"]),
3713            "-ERR Invalid FREQ value, must be >= 0 and <= 255\r\n"
3714        );
3715    }
3716
3717    #[test]
3718    fn restore_can_tell_a_bad_footer_from_bad_bytes() {
3719        let mut f = Fixture::new();
3720        f.run(&[b"SET", b"a", b"hello"]);
3721        let good = payload(&f.raw(&[b"DUMP", b"a"]));
3722
3723        let mut flipped = good.clone();
3724        flipped[2] ^= 0x40;
3725        assert_eq!(
3726            f.run(&[b"RESTORE", b"b", b"0", &flipped]),
3727            "-ERR DUMP payload version or checksum are wrong\r\n"
3728        );
3729        assert_eq!(
3730            f.run(&[b"RESTORE", b"b", b"0", b"short"]),
3731            "-ERR DUMP payload version or checksum are wrong\r\n"
3732        );
3733        // A footer that is right over a body that is not. The type byte says
3734        // string and there is nothing behind it, so the checksum agrees and the
3735        // value does not exist.
3736        let mut truncated = good[..1].to_vec();
3737        truncated.extend_from_slice(&good[good.len() - 10..good.len() - 8]);
3738        let crc = yo_common::crc::crc64(0, &truncated);
3739        truncated.extend_from_slice(&crc.to_le_bytes());
3740        assert_eq!(
3741            f.run(&[b"RESTORE", b"b", b"0", &truncated]),
3742            "-ERR Bad data format\r\n"
3743        );
3744        assert_eq!(f.run(&[b"EXISTS", b"b"]), ":0\r\n");
3745    }
3746
3747    #[test]
3748    fn restore_checks_the_three_numbers_a_client_can_get_wrong() {
3749        let mut f = Fixture::new();
3750        f.run(&[b"SET", b"a", b"v"]);
3751        let bytes = payload(&f.raw(&[b"DUMP", b"a"]));
3752        assert_eq!(
3753            f.run(&[b"RESTORE", b"b", b"-1", &bytes]),
3754            "-ERR Invalid TTL value, must be >= 0\r\n"
3755        );
3756        assert_eq!(
3757            f.run(&[b"RESTORE", b"b", b"0", &bytes, b"IDLETIME", b"-1"]),
3758            "-ERR Invalid IDLETIME value, must be >= 0\r\n"
3759        );
3760        assert_eq!(
3761            f.run(&[b"RESTORE", b"b", b"0", &bytes, b"FREQ", b"256"]),
3762            "-ERR Invalid FREQ value, must be >= 0 and <= 255\r\n"
3763        );
3764        // Both are accepted and both are then dropped, which is D-26.
3765        assert_eq!(
3766            f.run(&[b"RESTORE", b"b", b"0", &bytes, b"IDLETIME", b"90"]),
3767            "+OK\r\n"
3768        );
3769        assert_eq!(
3770            f.run(&[b"RESTORE", b"c", b"0", &bytes, b"FREQ", b"200", b"REPLACE"]),
3771            "+OK\r\n"
3772        );
3773    }
3774
3775    /// Neither word is refused for being the wrong one. Each is only accepted
3776    /// while the other is unset, so the second of the two falls through to the
3777    /// plain syntax error rather than getting a message of its own.
3778    #[test]
3779    fn restore_takes_idletime_or_freq_and_not_both() {
3780        let mut f = Fixture::new();
3781        f.run(&[b"SET", b"a", b"v"]);
3782        let bytes = payload(&f.raw(&[b"DUMP", b"a"]));
3783        assert_eq!(
3784            f.run(&[
3785                b"RESTORE",
3786                b"b",
3787                b"0",
3788                &bytes,
3789                b"IDLETIME",
3790                b"1",
3791                b"FREQ",
3792                b"2"
3793            ]),
3794            "-ERR syntax error\r\n"
3795        );
3796        assert_eq!(
3797            f.run(&[
3798                b"RESTORE",
3799                b"b",
3800                b"0",
3801                &bytes,
3802                b"FREQ",
3803                b"2",
3804                b"IDLETIME",
3805                b"1"
3806            ]),
3807            "-ERR syntax error\r\n"
3808        );
3809        assert_eq!(
3810            f.run(&[b"RESTORE", b"b", b"0", &bytes, b"FREQ"]),
3811            "-ERR syntax error\r\n"
3812        );
3813        assert_eq!(
3814            f.run(&[b"RESTORE", b"b", b"0", &bytes, b"NOSUCH"]),
3815            "-ERR syntax error\r\n"
3816        );
3817    }
3818
3819    #[test]
3820    fn copy_checks_its_options_before_it_looks_for_anything() {
3821        let mut f = Fixture::new();
3822        // No key exists at all, and every one of these is still the option
3823        // complaint rather than a zero, which is the order a real server uses.
3824        assert_eq!(
3825            f.run(&[b"COPY", b"a", b"b", b"DB", b"99"]),
3826            "-ERR DB index is out of range\r\n"
3827        );
3828        assert_eq!(
3829            f.run(&[b"COPY", b"a", b"b", b"DB", b"-1"]),
3830            "-ERR DB index is out of range\r\n"
3831        );
3832        assert_eq!(
3833            f.run(&[b"COPY", b"a", b"b", b"DB", b"x"]),
3834            "-ERR value is not an integer or out of range\r\n"
3835        );
3836        assert_eq!(
3837            f.run(&[b"COPY", b"a", b"b", b"nonsense"]),
3838            "-ERR syntax error\r\n"
3839        );
3840        assert_eq!(
3841            f.run(&[b"COPY", b"a", b"a"]),
3842            "-ERR source and destination objects are the same\r\n"
3843        );
3844        // Repeated, reordered and lowercased, and the last DB wins.
3845        assert_eq!(
3846            f.run(&[b"COPY", b"a", b"b", b"dB", b"1", b"rEpLaCe", b"db", b"2"]),
3847            ":0\r\n"
3848        );
3849    }
3850
3851    #[test]
3852    fn time_is_two_bulk_strings_and_moves() {
3853        let mut f = Fixture::new();
3854        let first = f.run(&[b"TIME"]);
3855        assert!(first.starts_with("*2\r\n$"), "got {first}");
3856        let parts: Vec<&str> = first.split("\r\n").collect();
3857        let secs: i64 = parts[2].parse().expect("seconds as decimal text");
3858        let micros: i64 = parts[4].parse().expect("microseconds as decimal text");
3859        assert!(secs > 1_700_000_000, "a real wall clock, got {secs}");
3860        assert!((0..1_000_000).contains(&micros), "got {micros}");
3861        // The coarse clock the keyspace uses is a cached millisecond that a
3862        // background tick refreshes, so a TIME built on it would answer the
3863        // same microsecond twice in a row here.
3864        assert_ne!(first, f.run(&[b"TIME"]));
3865    }
3866
3867    #[test]
3868    fn a_keyspace_scan_walks_every_key_once() {
3869        // The count below is thirty two, so ninety six keys is three pages of
3870        // cursor and says the same thing as five hundred at a fifth of the
3871        // interpreted work.
3872        let n = if cfg!(miri) { 96 } else { 500 };
3873        let mut f = Fixture::new();
3874        for i in 0..n {
3875            f.run(&[b"SET", format!("k{i}").as_bytes(), b"v"]);
3876        }
3877
3878        let mut seen: Vec<String> = Vec::new();
3879        let mut cursor = "0".to_owned();
3880        let mut calls = 0;
3881        loop {
3882            let (next, keys) = scan_reply(&f.run(&[b"SCAN", cursor.as_bytes(), b"COUNT", b"32"]));
3883            seen.extend(keys);
3884            cursor = next;
3885            calls += 1;
3886            assert!(calls < 10_000, "the cursor is not advancing");
3887            if cursor == "0" {
3888                break;
3889            }
3890        }
3891
3892        seen.sort();
3893        seen.dedup();
3894        assert_eq!(seen.len(), n, "every key once and only once");
3895        // And more than one call to get them, or the COUNT is being ignored and
3896        // the loop above proved nothing about resuming.
3897        assert!(calls > 1, "{n} keys came back in one batch");
3898    }
3899
3900    #[test]
3901    fn a_scan_narrows_by_pattern_and_by_type() {
3902        let mut f = Fixture::new();
3903        f.run(&[b"SET", b"str", b"v"]);
3904        f.run(&[b"SADD", b"members", b"a"]);
3905        f.run(&[b"HSET", b"fields", b"f", b"v"]);
3906
3907        let all = |f: &mut Fixture, args: &[&[u8]]| {
3908            let mut out: Vec<String> = Vec::new();
3909            let mut cursor = "0".to_owned();
3910            loop {
3911                let mut line: Vec<&[u8]> = vec![b"SCAN", cursor.as_bytes()];
3912                line.extend_from_slice(args);
3913                let (next, keys) = scan_reply(&f.run(&line));
3914                out.extend(keys);
3915                cursor = next;
3916                if cursor == "0" {
3917                    break;
3918                }
3919            }
3920            out.sort();
3921            out
3922        };
3923
3924        assert_eq!(all(&mut f, &[]), ["fields", "members", "str"]);
3925        assert_eq!(all(&mut f, &[b"MATCH", b"*e*"]), ["fields", "members"]);
3926        assert_eq!(all(&mut f, &[b"TYPE", b"set"]), ["members"]);
3927        // Case insensitive, the same as Redis's own comparison.
3928        assert_eq!(all(&mut f, &[b"TYPE", b"HASH"]), ["fields"]);
3929        // A type nothing can hold is not an error, it just matches nothing.
3930        assert!(all(&mut f, &[b"TYPE", b"list"]).is_empty());
3931        assert!(all(&mut f, &[b"TYPE", b"banana"]).is_empty());
3932        // Both filters at once, and they are an and rather than an or.
3933        assert!(all(&mut f, &[b"MATCH", b"str*", b"TYPE", b"set"]).is_empty());
3934    }
3935
3936    #[test]
3937    fn a_scan_says_what_is_wrong_with_it() {
3938        let mut f = Fixture::new();
3939        assert_eq!(f.run(&[b"SCAN", b"nope"]), "-ERR invalid cursor\r\n");
3940        assert_eq!(f.run(&[b"SCAN", b"-1"]), "-ERR invalid cursor\r\n");
3941        assert_eq!(f.run(&[b"SCAN", b"0", b"MATCH"]), "-ERR syntax error\r\n");
3942        assert_eq!(
3943            f.run(&[b"SCAN", b"0", b"COUNT", b"0"]),
3944            "-ERR syntax error\r\n"
3945        );
3946        assert_eq!(
3947            f.run(&[b"SCAN", b"0", b"COUNT", b"x"]),
3948            "-ERR value is not an integer or out of range\r\n"
3949        );
3950        assert_eq!(
3951            f.run(&[b"SCAN", b"0", b"WAT", b"1"]),
3952            "-ERR syntax error\r\n"
3953        );
3954        // A cursor the client made up is a cursor. It resumes somewhere
3955        // arbitrary and answers whatever is there, which is what Redis does and
3956        // is the only behaviour that does not need the server to remember every
3957        // cursor it has handed out.
3958        assert!(f.run(&[b"SCAN", b"18446744073709551615"]).starts_with("*2"));
3959    }
3960
3961    #[test]
3962    fn keys_and_randomkey_look_at_the_whole_database() {
3963        let mut f = Fixture::new();
3964        assert_eq!(f.run(&[b"KEYS", b"*"]), "*0\r\n");
3965        assert_eq!(f.run(&[b"RANDOMKEY"]), "$-1\r\n");
3966
3967        for name in ["one", "two", "three"] {
3968            f.run(&[b"SET", name.as_bytes(), b"v"]);
3969        }
3970        assert_eq!(sorted(&f.run(&[b"KEYS", b"*"])), ["one", "three", "two"]);
3971        assert_eq!(sorted(&f.run(&[b"KEYS", b"t*"])), ["three", "two"]);
3972        assert_eq!(f.run(&[b"KEYS", b"nothing"]), "*0\r\n");
3973
3974        for _ in 0..50 {
3975            let got = f.run(&[b"RANDOMKEY"]);
3976            assert!(
3977                ["$3\r\none\r\n", "$3\r\ntwo\r\n", "$5\r\nthree\r\n"].contains(&got.as_str()),
3978                "got {got}"
3979            );
3980        }
3981    }
3982
3983    #[test]
3984    fn a_walk_does_not_answer_keys_that_have_expired() {
3985        let mut f = Fixture::new();
3986        f.run(&[b"SET", b"alive", b"v"]);
3987        f.run(&[b"SET", b"dead", b"v", b"PX", b"1"]);
3988        f.server.advance_clock_ms(2);
3989        assert_eq!(
3990            f.run(&[b"DBSIZE"]),
3991            ":2\r\n",
3992            "nothing has collected it yet"
3993        );
3994
3995        assert_eq!(f.run(&[b"KEYS", b"*"]), "*1\r\n$5\r\nalive\r\n");
3996        let (_, keys) = scan_reply(&f.run(&[b"SCAN", b"0", b"COUNT", b"1000"]));
3997        assert_eq!(keys, ["alive"]);
3998        for _ in 0..20 {
3999            assert_eq!(f.run(&[b"RANDOMKEY"]), "$5\r\nalive\r\n");
4000        }
4001        // The walk collected it on the way past, which is what makes DBSIZE
4002        // here answer what Redis answers once its own cycle has been round.
4003        assert_eq!(f.run(&[b"DBSIZE"]), ":1\r\n");
4004    }
4005
4006    #[test]
4007    fn a_key_deadline_goes_on_and_comes_back_in_all_four_units() {
4008        let mut f = Fixture::new();
4009        f.run(&[b"SET", b"k", b"v"]);
4010        assert_eq!(f.run(&[b"TTL", b"k"]), ":-1\r\n", "there and no deadline");
4011        assert_eq!(f.run(&[b"TTL", b"nosuch"]), ":-2\r\n", "not there at all");
4012
4013        assert_eq!(f.run(&[b"EXPIRE", b"k", b"100"]), ":1\r\n");
4014        assert_eq!(f.run(&[b"TTL", b"k"]), ":100\r\n");
4015        let ms = int(&f.run(&[b"PTTL", b"k"]));
4016        assert!((99_000..=100_000).contains(&ms), "got {ms}");
4017
4018        // The absolute pair, derived from the same one number the store kept.
4019        let at = int(&f.run(&[b"EXPIRETIME", b"k"]));
4020        let at_ms = int(&f.run(&[b"PEXPIRETIME", b"k"]));
4021        assert_eq!(at, (at_ms + 500) / 1000);
4022        assert!(at_ms > 1_700_000_000_000, "an absolute moment, got {at_ms}");
4023
4024        assert_eq!(f.run(&[b"PERSIST", b"k"]), ":1\r\n");
4025        assert_eq!(f.run(&[b"TTL", b"k"]), ":-1\r\n");
4026        assert_eq!(
4027            f.run(&[b"PERSIST", b"k"]),
4028            ":0\r\n",
4029            "nothing to take off the second time"
4030        );
4031        assert_eq!(f.run(&[b"PERSIST", b"nosuch"]), ":0\r\n");
4032        assert_eq!(
4033            f.run(&[b"GET", b"k"]),
4034            "$1\r\nv\r\n",
4035            "and the value went through all of that untouched"
4036        );
4037    }
4038
4039    #[test]
4040    fn every_type_can_be_given_a_deadline_and_it_is_the_same_deadline() {
4041        let mut f = Fixture::new();
4042        f.run(&[b"SET", b"str", b"v"]);
4043        f.run(&[b"SADD", b"set", b"a", b"b"]);
4044        f.run(&[b"HSET", b"hash", b"f", b"v"]);
4045
4046        for key in [b"str".as_slice(), b"set", b"hash"] {
4047            assert_eq!(f.run(&[b"EXPIRE", key, b"100"]), ":1\r\n");
4048            assert_eq!(f.run(&[b"TTL", key]), ":100\r\n");
4049        }
4050        // The body is not touched by any of that, which is the whole reason the
4051        // deadline lives in the record and the body lives somewhere else.
4052        assert_eq!(f.run(&[b"SCARD", b"set"]), ":2\r\n");
4053        assert_eq!(f.run(&[b"HGET", b"hash", b"f"]), "$1\r\nv\r\n");
4054        assert_eq!(f.run(&[b"GET", b"str"]), "$1\r\nv\r\n");
4055    }
4056
4057    #[test]
4058    fn a_deadline_that_has_already_gone_deletes_the_key_now() {
4059        let mut f = Fixture::new();
4060        for key in [b"a".as_slice(), b"b", b"c", b"d"] {
4061            f.run(&[b"SET", key, b"v"]);
4062        }
4063        // Four ways of naming a moment that has passed, and all four are a
4064        // delete answering 1 rather than an error. Zero is a moment, minus one
4065        // is a moment, and the hash field commands refuse the negative one.
4066        assert_eq!(f.run(&[b"EXPIRE", b"a", b"0"]), ":1\r\n");
4067        assert_eq!(f.run(&[b"EXPIRE", b"b", b"-1"]), ":1\r\n");
4068        assert_eq!(f.run(&[b"EXPIREAT", b"c", b"1"]), ":1\r\n");
4069        assert_eq!(f.run(&[b"PEXPIREAT", b"d", b"1"]), ":1\r\n");
4070        assert_eq!(f.run(&[b"DBSIZE"]), ":0\r\n");
4071        assert_eq!(
4072            f.run(&[b"EXPIRE", b"a", b"100"]),
4073            ":0\r\n",
4074            "and the key really went, so there is nothing to put a deadline on"
4075        );
4076    }
4077
4078    #[test]
4079    fn the_four_conditions_decide_whether_the_deadline_moves() {
4080        let mut f = Fixture::new();
4081        f.run(&[b"SET", b"k", b"v"]);
4082
4083        assert_eq!(f.run(&[b"EXPIRE", b"k", b"100", b"XX"]), ":0\r\n");
4084        assert_eq!(f.run(&[b"TTL", b"k"]), ":-1\r\n", "and XX left it alone");
4085        assert_eq!(f.run(&[b"EXPIRE", b"k", b"100", b"GT"]), ":0\r\n");
4086        assert_eq!(
4087            f.run(&[b"EXPIRE", b"k", b"100", b"LT"]),
4088            ":1\r\n",
4089            "no deadline reads as infinitely far away, so LT passes where GT fails"
4090        );
4091
4092        assert_eq!(f.run(&[b"EXPIRE", b"k", b"50", b"NX"]), ":0\r\n");
4093        assert_eq!(f.run(&[b"EXPIRE", b"k", b"50", b"GT"]), ":0\r\n");
4094        assert_eq!(f.run(&[b"TTL", b"k"]), ":100\r\n");
4095        assert_eq!(f.run(&[b"EXPIRE", b"k", b"50", b"LT"]), ":1\r\n");
4096        assert_eq!(f.run(&[b"EXPIRE", b"k", b"200", b"GT"]), ":1\r\n");
4097        assert_eq!(f.run(&[b"TTL", b"k"]), ":200\r\n");
4098
4099        // The condition is answered before the past check, so this is a 0 and
4100        // the key survives. The other order would delete it.
4101        assert_eq!(f.run(&[b"EXPIRE", b"k", b"0", b"NX"]), ":0\r\n");
4102        assert_eq!(f.run(&[b"EXISTS", b"k"]), ":1\r\n");
4103        assert_eq!(f.run(&[b"EXPIRE", b"k", b"0", b"XX"]), ":1\r\n");
4104        assert_eq!(f.run(&[b"EXISTS", b"k"]), ":0\r\n", "and XX let it through");
4105    }
4106
4107    #[test]
4108    fn the_conditions_are_a_set_and_not_a_keyword() {
4109        let mut f = Fixture::new();
4110        f.run(&[b"SET", b"k", b"v"]);
4111
4112        assert_eq!(f.run(&[b"EXPIRE", b"k", b"100", b"nx"]), ":1\r\n");
4113        assert_eq!(
4114            f.run(&[b"EXPIRE", b"k", b"100", b"nx", b"nx"]),
4115            ":0\r\n",
4116            "the same keyword twice means it once, and NX now has a deadline to fail on"
4117        );
4118
4119        // XX with LT is the one pair that is not either of them on its own: LT
4120        // alone would accept a key with no deadline and this does not.
4121        assert_eq!(f.run(&[b"EXPIRE", b"k", b"200", b"xx", b"gt"]), ":1\r\n");
4122        assert_eq!(f.run(&[b"TTL", b"k"]), ":200\r\n");
4123        assert_eq!(f.run(&[b"EXPIRE", b"k", b"100", b"gt", b"xx"]), ":0\r\n");
4124        assert_eq!(f.run(&[b"EXPIRE", b"k", b"100", b"XX", b"LT"]), ":1\r\n");
4125        assert_eq!(f.run(&[b"TTL", b"k"]), ":100\r\n");
4126        f.run(&[b"PERSIST", b"k"]);
4127        assert_eq!(
4128            f.run(&[b"EXPIRE", b"k", b"100", b"XX", b"LT"]),
4129            ":0\r\n",
4130            "where LT on its own would have taken it"
4131        );
4132        assert_eq!(f.run(&[b"EXPIRE", b"k", b"100", b"LT"]), ":1\r\n");
4133    }
4134
4135    #[test]
4136    fn a_key_is_gone_once_its_moment_passes() {
4137        let mut f = Fixture::new();
4138        f.run(&[b"SET", b"k", b"v"]);
4139        f.run(&[b"EXPIRE", b"k", b"100"]);
4140
4141        let at = int(&f.run(&[b"PEXPIRETIME", b"k"]));
4142        f.server.set_clock_ms(at as u64 + 1);
4143        assert_eq!(f.run(&[b"GET", b"k"]), "$-1\r\n");
4144        assert_eq!(f.run(&[b"TTL", b"k"]), ":-2\r\n");
4145        assert_eq!(f.run(&[b"EXISTS", b"k"]), ":0\r\n");
4146        assert_eq!(f.run(&[b"DBSIZE"]), ":0\r\n");
4147    }
4148
4149    #[test]
4150    fn the_expiry_commands_refuse_what_a_real_server_refuses() {
4151        let mut f = Fixture::new();
4152        f.run(&[b"SET", b"k", b"v"]);
4153        for (bad, want) in [
4154            (
4155                &[b"EXPIRE".as_slice(), b"k", b"soon"][..],
4156                "-ERR value is not an integer or out of range\r\n",
4157            ),
4158            (
4159                &[b"EXPIRE", b"k", b"100", b"MAYBE"],
4160                "-ERR Unsupported option MAYBE\r\n",
4161            ),
4162            (
4163                &[b"EXPIRE", b"k", b"100", b"NX", b"XX"],
4164                "-ERR NX and XX, GT or LT options at the same time are not compatible\r\n",
4165            ),
4166            (
4167                &[b"EXPIRE", b"k", b"100", b"NX", b"GT"],
4168                "-ERR NX and XX, GT or LT options at the same time are not compatible\r\n",
4169            ),
4170            (
4171                &[b"EXPIRE", b"k", b"100", b"GT", b"LT", b"GT"],
4172                "-ERR GT and LT options at the same time are not compatible\r\n",
4173            ),
4174            // Seconds that overflow when multiplied into milliseconds. Every
4175            // message names the command it came from.
4176            (
4177                &[b"EXPIRE", b"k", b"9223372036854775807"],
4178                "-ERR invalid expire time in 'expire' command\r\n",
4179            ),
4180            (
4181                &[b"EXPIREAT", b"k", b"9223372036854775807"],
4182                "-ERR invalid expire time in 'expireat' command\r\n",
4183            ),
4184            (
4185                &[b"PEXPIRE", b"k", b"9223372036854775807"],
4186                "-ERR invalid expire time in 'pexpire' command\r\n",
4187            ),
4188        ] {
4189            assert_eq!(f.run(bad), want, "for {bad:?}");
4190        }
4191        assert_eq!(
4192            f.run(&[b"TTL", b"k"]),
4193            ":-1\r\n",
4194            "and none of those put a deadline on anything"
4195        );
4196
4197        // The one of the four that has no arithmetic to overflow. Redis takes
4198        // it and holds the number as given, and a record here holds forty six
4199        // bits, so it lands in the year 4199 instead. D-17.
4200        assert_eq!(
4201            f.run(&[b"PEXPIREAT", b"k", b"9223372036854775807"]),
4202            ":1\r\n"
4203        );
4204        assert_eq!(f.run(&[b"PEXPIRETIME", b"k"]), ":70368744177663\r\n");
4205    }
4206
4207    #[test]
4208    fn flushing_empties_this_database_or_every_one_of_them() {
4209        let mut f = Fixture::new();
4210        f.run(&[b"SELECT", b"0"]);
4211        f.run(&[b"MSET", b"a", b"1", b"b", b"2"]);
4212        f.run(&[b"SELECT", b"1"]);
4213        f.run(&[b"SET", b"c", b"3"]);
4214        assert_eq!(f.run(&[b"DBSIZE"]), ":1\r\n");
4215        // ASYNC and SYNC are both taken and neither changes anything, since the
4216        // keyspace is empty before the OK goes out either way.
4217        assert_eq!(f.run(&[b"FLUSHDB", b"async"]), "+OK\r\n");
4218        assert_eq!(f.run(&[b"DBSIZE"]), ":0\r\n");
4219        // Only database one was emptied.
4220        f.run(&[b"SELECT", b"0"]);
4221        assert_eq!(f.run(&[b"DBSIZE"]), ":2\r\n");
4222        assert_eq!(f.run(&[b"FLUSHALL", b"SYNC"]), "+OK\r\n");
4223        assert_eq!(f.run(&[b"DBSIZE"]), ":0\r\n");
4224        f.run(&[b"SELECT", b"1"]);
4225        assert_eq!(f.run(&[b"DBSIZE"]), ":0\r\n");
4226        // Anything else after the name is a syntax error, and so is a third
4227        // argument even when the second one is a word we take.
4228        assert_eq!(f.run(&[b"FLUSHALL", b"nope"]), "-ERR syntax error\r\n");
4229        assert_eq!(
4230            f.run(&[b"FLUSHDB", b"sync", b"sync"]),
4231            "-ERR syntax error\r\n"
4232        );
4233    }
4234
4235    #[test]
4236    fn the_script_cache_and_the_library_set_answer_for_being_empty() {
4237        let mut f = Fixture::new();
4238        assert_eq!(f.run(&[b"SCRIPT", b"FLUSH"]), "+OK\r\n");
4239        assert_eq!(f.run(&[b"SCRIPT", b"FLUSH", b"async"]), "+OK\r\n");
4240        assert_eq!(f.run(&[b"FUNCTION", b"FLUSH", b"SYNC"]), "+OK\r\n");
4241        // Nothing is cached, so nothing is there, one answer per hash asked
4242        // about.
4243        assert_eq!(
4244            f.run(&[b"SCRIPT", b"EXISTS", b"aaaa", b"bbbb"]),
4245            "*2\r\n:0\r\n:0\r\n"
4246        );
4247        assert_eq!(f.run(&[b"FUNCTION", b"LIST"]), "*0\r\n");
4248        assert_eq!(
4249            f.run(&[b"FUNCTION", b"LIST", b"LIBRARYNAME", b"x", b"WITHCODE"]),
4250            "*0\r\n"
4251        );
4252        assert_eq!(
4253            f.run(&[b"FUNCTION", b"DELETE", b"nosuch"]),
4254            "-ERR Library not found\r\n"
4255        );
4256
4257        // Redis's two messages here are its own, one per container, and one of
4258        // them reads like a typo.
4259        assert_eq!(
4260            f.run(&[b"SCRIPT", b"FLUSH", b"nope"]),
4261            "-ERR SCRIPT FLUSH only support SYNC|ASYNC option\r\n"
4262        );
4263        assert_eq!(
4264            f.run(&[b"FUNCTION", b"FLUSH", b"nope"]),
4265            "-ERR FUNCTION FLUSH only supports SYNC|ASYNC option\r\n"
4266        );
4267        // A second argument after the mode is the generic one instead, because
4268        // the count is checked before the word is looked at. The subcommand in
4269        // the sentence is the client's own spelling and not the canonical one,
4270        // which is the same thing `unknown subcommand` does.
4271        assert_eq!(
4272            f.run(&[b"FUNCTION", b"FLUSH", b"sync", b"sync"]),
4273            "-ERR unknown subcommand or wrong number of arguments for 'FLUSH'. Try FUNCTION HELP.\r\n"
4274        );
4275        assert_eq!(
4276            f.run(&[b"FUNCTION", b"LIST", b"bogus"]),
4277            "-ERR Unknown argument bogus\r\n"
4278        );
4279        assert_eq!(
4280            f.run(&[b"SCRIPT", b"EXISTS"]),
4281            "-ERR wrong number of arguments for 'script|exists' command\r\n"
4282        );
4283
4284        assert_eq!(
4285            f.run(&[b"FUNCTION", b"NOPE"]),
4286            "-ERR unknown subcommand 'NOPE'. Try FUNCTION HELP.\r\n"
4287        );
4288    }
4289
4290    #[test]
4291    fn the_script_cache_holds_what_was_loaded_into_it() {
4292        let mut f = Fixture::new();
4293        // The hash is the sha1 of the body and nothing else, so it is the same
4294        // number a real server answers and a client can compute it itself.
4295        let sha = b"e0e1f9fabfc9d4800c877a703b823ac0578ff8db";
4296        assert_eq!(
4297            f.run(&[b"SCRIPT", b"LOAD", b"return 1"]),
4298            "$40\r\ne0e1f9fabfc9d4800c877a703b823ac0578ff8db\r\n"
4299        );
4300        assert_eq!(f.run(&[b"SCRIPT", b"EXISTS", sha]), "*1\r\n:1\r\n");
4301        assert_eq!(f.run(&[b"EVALSHA", sha, b"0"]), ":1\r\n");
4302        // Loading is idempotent and a body that will not parse is refused
4303        // where it was written rather than where it is called.
4304        assert_eq!(
4305            f.run(&[b"SCRIPT", b"LOAD", b"return 1"]),
4306            "$40\r\ne0e1f9fabfc9d4800c877a703b823ac0578ff8db\r\n"
4307        );
4308        assert!(
4309            f.run(&[b"SCRIPT", b"LOAD", b"this is not lua"])
4310                .starts_with("-ERR Error compiling script"),
4311        );
4312
4313        assert_eq!(f.run(&[b"SCRIPT", b"FLUSH"]), "+OK\r\n");
4314        assert_eq!(f.run(&[b"SCRIPT", b"EXISTS", sha]), "*1\r\n:0\r\n");
4315        assert_eq!(
4316            f.run(&[b"EVALSHA", sha, b"0"]),
4317            "-NOSCRIPT No matching script. Please use EVAL.\r\n"
4318        );
4319
4320        // Running the body puts it in the cache too, which is what makes the
4321        // load then call then fall back to load pattern a client uses work.
4322        assert_eq!(f.run(&[b"EVAL", b"return 1", b"0"]), ":1\r\n");
4323        assert_eq!(f.run(&[b"SCRIPT", b"EXISTS", sha]), "*1\r\n:1\r\n");
4324
4325        // Nothing here can run long enough to be killed, which is D-101, so
4326        // the answer is the one a real server gives when nothing is stuck.
4327        assert_eq!(
4328            f.run(&[b"SCRIPT", b"KILL"]),
4329            "-NOTBUSY No scripts in execution right now.\r\n"
4330        );
4331        assert_eq!(f.run(&[b"SCRIPT", b"DEBUG", b"NO"]), "+OK\r\n");
4332        assert_eq!(f.run(&[b"SCRIPT", b"DEBUG", b"yes"]), "+OK\r\n");
4333        assert_eq!(
4334            f.run(&[b"SCRIPT", b"DEBUG", b"maybe"]),
4335            "-ERR Use SCRIPT DEBUG YES/SYNC/NO\r\n"
4336        );
4337    }
4338
4339    #[test]
4340    fn eval_counts_its_keys_before_it_compiles_anything() {
4341        let mut f = Fixture::new();
4342        assert_eq!(
4343            f.run(&[b"EVAL", b"return 1"]),
4344            "-ERR wrong number of arguments for 'eval' command\r\n"
4345        );
4346        assert_eq!(
4347            f.run(&[b"EVAL", b"return 1", b"abc"]),
4348            "-ERR value is not an integer or out of range\r\n"
4349        );
4350        assert_eq!(
4351            f.run(&[b"EVAL", b"return 1", b"-1"]),
4352            "-ERR Number of keys can't be negative\r\n"
4353        );
4354        assert_eq!(
4355            f.run(&[b"EVAL", b"return 1", b"1"]),
4356            "-ERR Number of keys can't be greater than number of args\r\n"
4357        );
4358        // The count splits the tail, and everything past the keys is ARGV.
4359        assert_eq!(
4360            f.run(&[
4361                b"EVAL",
4362                b"return {KEYS[1],KEYS[2],ARGV[1]}",
4363                b"2",
4364                b"a",
4365                b"b",
4366                b"c"
4367            ]),
4368            "*3\r\n$1\r\na\r\n$1\r\nb\r\n$1\r\nc\r\n"
4369        );
4370        assert_eq!(
4371            f.run(&[b"EVAL", b"return #KEYS", b"0", b"a", b"b"]),
4372            ":0\r\n"
4373        );
4374        assert_eq!(
4375            f.run(&[b"EVAL", b"return #ARGV", b"0", b"a", b"b"]),
4376            ":2\r\n"
4377        );
4378    }
4379
4380    #[test]
4381    fn a_lua_value_comes_back_as_the_reply_it_maps_to() {
4382        let mut f = Fixture::new();
4383        let eval = |f: &mut Fixture, body: &[u8]| f.run(&[b"EVAL", body, b"0"]);
4384
4385        // A number is truncated toward zero rather than rounded, and the two
4386        // ends of the range saturate the way the cast does.
4387        assert_eq!(eval(&mut f, b"return 3.99"), ":3\r\n");
4388        assert_eq!(eval(&mut f, b"return -3.99"), ":-3\r\n");
4389        assert_eq!(eval(&mut f, b"return 0.5"), ":0\r\n");
4390        assert_eq!(eval(&mut f, b"return 2^63"), ":9223372036854775807\r\n");
4391        assert_eq!(eval(&mut f, b"return -2^63"), ":-9223372036854775808\r\n");
4392        assert_eq!(eval(&mut f, b"return 1/0"), ":9223372036854775807\r\n");
4393        assert_eq!(eval(&mut f, b"return 0/0"), ":0\r\n");
4394
4395        assert_eq!(eval(&mut f, b"return 'hello'"), "$5\r\nhello\r\n");
4396        assert_eq!(eval(&mut f, b"return true"), ":1\r\n");
4397        // Everything that is not there is the same nothing.
4398        assert_eq!(eval(&mut f, b"return false"), "$-1\r\n");
4399        assert_eq!(eval(&mut f, b"return nil"), "$-1\r\n");
4400        assert_eq!(eval(&mut f, b"return"), "$-1\r\n");
4401        assert_eq!(eval(&mut f, b""), "$-1\r\n");
4402
4403        // A table is an array that stops at the first hole, which is what makes
4404        // a script build a reply by appending rather than by indexing.
4405        assert_eq!(eval(&mut f, b"return {}"), "*0\r\n");
4406        assert_eq!(eval(&mut f, b"return {1,2,nil,4}"), "*2\r\n:1\r\n:2\r\n");
4407        assert_eq!(
4408            eval(&mut f, b"return {1,'a',{2}}"),
4409            "*3\r\n:1\r\n$1\r\na\r\n*1\r\n:2\r\n"
4410        );
4411
4412        // The named fields, in the order a real server looks for them.
4413        assert_eq!(eval(&mut f, b"return {ok='fine'}"), "+fine\r\n");
4414        assert_eq!(eval(&mut f, b"return {err='mine'}"), "-mine\r\n");
4415        assert_eq!(eval(&mut f, b"return {err='a', ok='b'}"), "-a\r\n");
4416        assert_eq!(eval(&mut f, b"return {ok='b', double=1.5}"), "+b\r\n");
4417        // A line break inside one of them becomes a space, because the reply is
4418        // a single line and a client that saw the break would lose the frame.
4419        assert_eq!(eval(&mut f, b"return {ok='a\\r\\nb'}"), "+a  b\r\n");
4420        // A field of the wrong type is not that kind of reply at all, and falls
4421        // through to the array walk, which finds nothing.
4422        assert_eq!(eval(&mut f, b"return {ok=1}"), "*0\r\n");
4423        assert_eq!(eval(&mut f, b"return {err={}}"), "*0\r\n");
4424    }
4425
4426    #[test]
4427    fn the_protocol_the_client_asked_for_is_the_one_a_table_answers_in() {
4428        let mut f = Fixture::new();
4429        // Under RESP2 the four typed tables have to come back as something a
4430        // client that only knows RESP2 can read.
4431        assert_eq!(
4432            f.run(&[b"EVAL", b"return {double=3.5}", b"0"]),
4433            "$3\r\n3.5\r\n"
4434        );
4435        assert_eq!(
4436            f.run(&[b"EVAL", b"return {big_number='123'}", b"0"]),
4437            "$3\r\n123\r\n"
4438        );
4439        assert_eq!(
4440            f.run(&[b"EVAL", b"return {map={a='b'}}", b"0"]),
4441            "*2\r\n$1\r\na\r\n$1\r\nb\r\n"
4442        );
4443        assert_eq!(
4444            f.run(&[b"EVAL", b"return {set={a=true}}", b"0"]),
4445            "*1\r\n$1\r\na\r\n"
4446        );
4447        assert_eq!(f.run(&[b"EVAL", b"return false", b"0"]), "$-1\r\n");
4448
4449        f.out = Out::new(Proto::Resp3);
4450        assert_eq!(f.run(&[b"EVAL", b"return {double=3.5}", b"0"]), ",3.5\r\n");
4451        assert_eq!(
4452            f.run(&[b"EVAL", b"return {big_number='123'}", b"0"]),
4453            "(123\r\n"
4454        );
4455        assert_eq!(
4456            f.run(&[b"EVAL", b"return {map={a='b'}}", b"0"]),
4457            "%1\r\n$1\r\na\r\n$1\r\nb\r\n"
4458        );
4459        assert_eq!(
4460            f.run(&[b"EVAL", b"return {set={a=true}}", b"0"]),
4461            "~1\r\n$1\r\na\r\n"
4462        );
4463        assert_eq!(f.run(&[b"EVAL", b"return false", b"0"]), "_\r\n");
4464    }
4465
4466    #[test]
4467    fn a_reply_comes_back_into_lua_as_the_value_it_maps_to() {
4468        let mut f = Fixture::new();
4469        f.run(&[b"SET", b"s", b"hello"]);
4470        f.run(&[b"RPUSH", b"l", b"a", b"b"]);
4471        let eval = |f: &mut Fixture, body: &[u8]| f.run(&[b"EVAL", body, b"0"]);
4472
4473        assert_eq!(
4474            eval(&mut f, b"return type(redis.call('get','s'))"),
4475            "$6\r\nstring\r\n"
4476        );
4477        assert_eq!(
4478            eval(&mut f, b"return type(redis.call('llen','l'))"),
4479            "$6\r\nnumber\r\n"
4480        );
4481        assert_eq!(
4482            eval(&mut f, b"return type(redis.call('lrange','l',0,-1))"),
4483            "$5\r\ntable\r\n"
4484        );
4485        // A status is a table with one field, which is what lets a script pass
4486        // one straight back out again.
4487        assert_eq!(
4488            eval(&mut f, b"return redis.call('set','s','v')['ok']"),
4489            "$2\r\nOK\r\n"
4490        );
4491        // A missing key is false under RESP2 and nil once the script asks for
4492        // RESP3, which is the one conversion the script gets to choose.
4493        assert_eq!(
4494            eval(&mut f, b"return tostring(redis.call('get','nosuch'))"),
4495            "$5\r\nfalse\r\n"
4496        );
4497        assert_eq!(
4498            eval(
4499                &mut f,
4500                b"redis.setresp(3) return tostring(redis.call('get','nosuch'))"
4501            ),
4502            "$3\r\nnil\r\n"
4503        );
4504        // The choice does not outlive the script that made it.
4505        assert_eq!(
4506            eval(&mut f, b"return tostring(redis.call('get','nosuch'))"),
4507            "$5\r\nfalse\r\n"
4508        );
4509    }
4510
4511    #[test]
4512    fn an_error_from_a_script_names_the_line_it_came_from() {
4513        let mut f = Fixture::new();
4514        // The position is the script's own, not the prelude's, and the suffix
4515        // names the script so a client can find it in the cache.
4516        assert_eq!(
4517            f.run(&[b"EVAL", b"error('boom')", b"0"]),
4518            "-ERR user_script:1: boom script: \
4519             82903a0434f1503e152f89c03c9acd881a0e8150, on @user_script:1.\r\n"
4520        );
4521        // Level zero says the message already knows where it came from.
4522        assert_eq!(
4523            f.run(&[b"EVAL", b"error('boom', 0)", b"0"]),
4524            "-ERR boom script: 90724e16396e5864c1184910ba6d7440461cee4f, on @user_script:1.\r\n"
4525        );
4526        // A table with an err field keeps its own text and gets the suffix.
4527        assert!(
4528            f.run(&[b"EVAL", b"error({err='structured'})", b"0"])
4529                .starts_with("-structured script: "),
4530        );
4531        // A script that will not parse is refused before it runs, so there is
4532        // no script and nothing to name.
4533        assert_eq!(
4534            f.run(&[b"EVAL", b"return this is not lua", b"0"]),
4535            "-ERR Error compiling script (new function): user_script:1: '<eof>' expected near 'is'\r\n"
4536        );
4537
4538        // A table that came out of pcall is a string by the time the script
4539        // sees it, which is a real server's own wrapping and not Lua's.
4540        assert_eq!(
4541            f.run(&[
4542                b"EVAL",
4543                b"local a, b = pcall(function() error({err='z'}) end) return type(b) .. ':' .. tostring(b)",
4544                b"0"
4545            ]),
4546            "$8\r\nstring:z\r\n"
4547        );
4548        assert_eq!(
4549            f.run(&[
4550                b"EVAL",
4551                b"local a, b = pcall(function() error({a=1}) end) return type(b)",
4552                b"0"
4553            ]),
4554            "$5\r\ntable\r\n"
4555        );
4556    }
4557
4558    #[test]
4559    fn redis_call_refuses_what_it_cannot_run_and_pcall_hands_it_back() {
4560        let mut f = Fixture::new();
4561        let sentence = |f: &mut Fixture, body: &[u8]| {
4562            let reply = f.run(&[b"EVAL", body, b"0"]);
4563            reply.split(" script: ").next().unwrap().to_owned()
4564        };
4565
4566        assert_eq!(
4567            sentence(&mut f, b"return redis.call()"),
4568            "-ERR Please specify at least one argument for this redis lib call"
4569        );
4570        assert_eq!(
4571            sentence(&mut f, b"return redis.call('get', {})"),
4572            "-ERR Lua redis lib command arguments must be strings or integers"
4573        );
4574        assert_eq!(
4575            sentence(&mut f, b"return redis.call('nosuchcmd')"),
4576            "-ERR Unknown Redis command called from script"
4577        );
4578        assert_eq!(
4579            sentence(&mut f, b"return redis.call('get')"),
4580            "-ERR Wrong number of args calling Redis command from script"
4581        );
4582        // The commands that make no sense inside a script are refused by name
4583        // rather than by not being implemented, so the sentence is the same one
4584        // a real server writes for each of them.
4585        for name in [
4586            &b"return redis.call('multi')"[..],
4587            b"return redis.call('exec')",
4588            b"return redis.call('watch','k')",
4589            b"return redis.call('subscribe','c')",
4590            b"return redis.call('debug','jmap')",
4591            b"return redis.call('eval','return 1',0)",
4592            b"return redis.call('config','get','maxmemory')",
4593        ] {
4594            assert_eq!(
4595                sentence(&mut f, name),
4596                "-ERR This Redis command is not allowed from script",
4597                "for {}",
4598                String::from_utf8_lossy(name)
4599            );
4600        }
4601        // HELP is the one subcommand of a refused container that is allowed,
4602        // because it reads nothing and changes nothing.
4603        assert!(
4604            f.run(&[b"EVAL", b"return redis.call('config','help')", b"0"])
4605                .starts_with('*'),
4606        );
4607
4608        // pcall answers the same sentence as a value instead of raising it, and
4609        // the value has an err field a script can read.
4610        assert_eq!(
4611            f.run(&[
4612                b"EVAL",
4613                b"local x = redis.pcall('nosuchcmd') return x.err",
4614                b"0"
4615            ]),
4616            "$44\r\nERR Unknown Redis command called from script\r\n"
4617        );
4618        // Returning it unread raises it, because the table has an err field.
4619        assert_eq!(
4620            f.run(&[b"EVAL", b"return redis.pcall('nosuchcmd')", b"0"]),
4621            "-ERR Unknown Redis command called from script\r\n"
4622        );
4623    }
4624
4625    #[test]
4626    fn a_read_only_script_is_stopped_at_the_write_and_not_at_the_door() {
4627        let mut f = Fixture::new();
4628        f.run(&[b"SET", b"k", b"v"]);
4629        assert_eq!(
4630            f.run(&[b"EVAL_RO", b"return redis.call('get', KEYS[1])", b"1", b"k"]),
4631            "$1\r\nv\r\n"
4632        );
4633        assert!(
4634            f.run(&[
4635                b"EVAL_RO",
4636                b"return redis.call('set', KEYS[1], 'x')",
4637                b"1",
4638                b"k"
4639            ])
4640            .starts_with("-ERR Write commands are not allowed from read-only scripts."),
4641        );
4642        // The write did not happen, and the same body under EVAL does.
4643        assert_eq!(f.run(&[b"GET", b"k"]), "$1\r\nv\r\n");
4644        assert_eq!(
4645            f.run(&[
4646                b"EVAL",
4647                b"return redis.call('set', KEYS[1], 'x')",
4648                b"1",
4649                b"k"
4650            ]),
4651            "+OK\r\n"
4652        );
4653        assert_eq!(f.run(&[b"GET", b"k"]), "$1\r\nx\r\n");
4654
4655        // EVALSHA_RO runs a cached body under the same rule.
4656        let sha = b"e0e1f9fabfc9d4800c877a703b823ac0578ff8db";
4657        f.run(&[b"SCRIPT", b"LOAD", b"return 1"]);
4658        assert_eq!(f.run(&[b"EVALSHA_RO", sha, b"0"]), ":1\r\n");
4659    }
4660
4661    #[test]
4662    fn a_script_cannot_leave_anything_behind_for_the_next_one() {
4663        let mut f = Fixture::new();
4664        // A plain global write and a write through a name on the redis table
4665        // both raise, with the position the script wrote them at.
4666        for body in [&b"x = 1"[..], b"pcall = 1", b"redis = 1", b"redis.call = 1"] {
4667            let reply = f.run(&[b"EVAL", body, b"0"]);
4668            assert!(
4669                reply
4670                    .starts_with("-ERR user_script:1: Attempt to modify a readonly table script: "),
4671                "{body:?} gave {reply}",
4672            );
4673        }
4674        // Walking round the guard with rawset or setmetatable raises too, and
4675        // without the position, which is where a real server raises it from.
4676        for body in [
4677            &b"rawset(redis, 'call', 1)"[..],
4678            b"rawset(_G, 'zz', 1)",
4679            b"setmetatable(_G, {})",
4680            b"setmetatable(redis, {})",
4681        ] {
4682            let reply = f.run(&[b"EVAL", body, b"0"]);
4683            assert!(
4684                reply.starts_with("-ERR Attempt to modify a readonly table script: "),
4685                "{body:?} gave {reply}",
4686            );
4687        }
4688        // Reading a name that is not there is a mistake rather than a nil, so a
4689        // misspelled global stops the script instead of doing nothing quietly.
4690        assert!(
4691            f.run(&[b"EVAL", b"return nosuchglobal", b"0"])
4692                .contains("Script attempted to access nonexistent global variable 'nosuchglobal'"),
4693        );
4694        // Reading a name that is not on the redis table is a nil, which is how
4695        // a script tests for a helper that an older server does not have.
4696        assert_eq!(
4697            f.run(&[b"EVAL", b"return tostring(redis.nosuchfield)", b"0"]),
4698            "$3\r\nnil\r\n"
4699        );
4700
4701        // The one write that lands, D-103, is taken back out before the next
4702        // script starts, so nothing a script does reaches the one after it.
4703        assert_eq!(f.run(&[b"EVAL", b"_G.pcall = 1 return 1", b"0"]), ":1\r\n");
4704        assert_eq!(
4705            f.run(&[b"EVAL", b"return type(pcall)", b"0"]),
4706            "$8\r\nfunction\r\n"
4707        );
4708        assert_eq!(
4709            f.run(&[b"EVAL", b"return type(redis.call)", b"0"]),
4710            "$8\r\nfunction\r\n"
4711        );
4712    }
4713
4714    #[test]
4715    fn a_script_can_walk_the_redis_table_it_is_not_allowed_to_write_to() {
4716        let mut f = Fixture::new();
4717        // The guard in front of the table is empty, so the three base library
4718        // readers that skip a metatable are pointed at the real table behind
4719        // it. A script counts what a real server counts.
4720        assert_eq!(
4721            f.run(&[
4722                b"EVAL",
4723                b"local n = 0 for k in pairs(redis) do n = n + 1 end return n",
4724                b"0",
4725            ]),
4726            ":23\r\n"
4727        );
4728        assert_eq!(
4729            f.run(&[
4730                b"EVAL",
4731                b"local t = {} for k in pairs(redis) do t[#t+1] = k end \
4732                  table.sort(t) return table.concat(t, ' ')",
4733                b"0",
4734            ]),
4735            "$243\r\nLOG_DEBUG LOG_NOTICE LOG_VERBOSE LOG_WARNING REDIS_VERSION \
4736             REDIS_VERSION_NUM REPL_ALL REPL_AOF REPL_NONE REPL_REPLICA REPL_SLAVE \
4737             acl_check_cmd breakpoint call debug error_reply log pcall replicate_commands \
4738             set_repl setresp sha1hex status_reply\r\n"
4739        );
4740        // The loop hands over the values as well as the names, so the twelve
4741        // helpers are callable from inside a traversal and not just findable.
4742        assert_eq!(
4743            f.run(&[
4744                b"EVAL",
4745                b"local n = 0 for k, v in pairs(redis) do \
4746                  if type(v) == 'function' then n = n + 1 end end return n",
4747                b"0",
4748            ]),
4749            ":12\r\n"
4750        );
4751        // The other two readers agree with it.
4752        assert_eq!(
4753            f.run(&[b"EVAL", b"return type(next(redis))", b"0"]),
4754            "$6\r\nstring\r\n"
4755        );
4756        assert_eq!(
4757            f.run(&[b"EVAL", b"return type(rawget(redis, 'call'))", b"0"]),
4758            "$8\r\nfunction\r\n"
4759        );
4760        assert_eq!(
4761            f.run(&[
4762                b"EVAL",
4763                b"return tostring(rawget(redis, 'nosuchfield'))",
4764                b"0",
4765            ]),
4766            "$3\r\nnil\r\n"
4767        );
4768        // Reading round the guard is the only thing that was given back. A
4769        // write still lands on the guard and still raises.
4770        for body in [&b"redis.call = 1"[..], b"rawset(redis, 'call', 1)"] {
4771            assert!(
4772                f.run(&[b"EVAL", body, b"0"])
4773                    .contains("Attempt to modify a readonly table script: "),
4774                "{body:?}",
4775            );
4776        }
4777        // A table nobody guards walks the way it always did, whether a script
4778        // made it or the standard library did.
4779        assert_eq!(
4780            f.run(&[
4781                b"EVAL",
4782                b"local t = {a=1,b=2} local n = 0 for k in pairs(t) do n = n + 1 end return n",
4783                b"0",
4784            ]),
4785            ":2\r\n"
4786        );
4787        assert_eq!(
4788            f.run(&[b"EVAL", b"return tostring(next({}))", b"0"]),
4789            "$3\r\nnil\r\n"
4790        );
4791        assert_eq!(
4792            f.run(&[
4793                b"EVAL",
4794                b"local f for k, v in pairs(string) do if k == 'sub' then f = v end end \
4795                  return type(f)",
4796                b"0",
4797            ]),
4798            "$8\r\nfunction\r\n"
4799        );
4800    }
4801
4802    #[test]
4803    fn a_script_gets_the_bit_library_a_real_server_carries() {
4804        let mut f = Fixture::new();
4805        // Every answer is a signed word, which is why the ones past two to the
4806        // thirty one come back negative.
4807        for (body, want) in [
4808            ("bit.tobit(1)", ":1\r\n"),
4809            ("bit.tobit(2^32 + 1)", ":1\r\n"),
4810            ("bit.tobit(2^31)", ":-2147483648\r\n"),
4811            ("bit.tobit(0xffffffff)", ":-1\r\n"),
4812            // The rounding is to the nearest and not toward zero.
4813            ("bit.tobit(1.5)", ":2\r\n"),
4814            ("bit.tobit(2.5)", ":2\r\n"),
4815            ("bit.bnot(0)", ":-1\r\n"),
4816            ("bit.band(0xff, 0x0f)", ":15\r\n"),
4817            ("bit.band(1, 2, 3)", ":0\r\n"),
4818            ("bit.bor(1, 2, 4)", ":7\r\n"),
4819            ("bit.bxor(0xff, 0x0f)", ":240\r\n"),
4820            // Only the low five bits of a count are read.
4821            ("bit.lshift(1, 31)", ":-2147483648\r\n"),
4822            ("bit.lshift(1, 32)", ":1\r\n"),
4823            ("bit.lshift(1, 33)", ":2\r\n"),
4824            ("bit.rshift(-1, 1)", ":2147483647\r\n"),
4825            ("bit.arshift(-1, 1)", ":-1\r\n"),
4826            ("bit.rol(0x12345678, 8)", ":878082066\r\n"),
4827            ("bit.ror(0x12345678, 8)", ":2014458966\r\n"),
4828            ("bit.bswap(0x12345678)", ":2018915346\r\n"),
4829            // A string that reads as a number is a number, which is Lua's rule
4830            // and not a courtesy of this library.
4831            ("bit.tobit('0x10')", ":16\r\n"),
4832        ] {
4833            let script = format!("return {body}");
4834            assert_eq!(f.run(&[b"EVAL", script.as_bytes(), b"0"]), want, "{body}");
4835        }
4836        // The digits are the low ones, a negative count asks for upper case,
4837        // and a count outside eight is brought back to it.
4838        for (body, want) in [
4839            ("bit.tohex(1)", "00000001"),
4840            ("bit.tohex(-1)", "ffffffff"),
4841            ("bit.tohex(255, 2)", "ff"),
4842            ("bit.tohex(255, -8)", "000000FF"),
4843            ("bit.tohex(0x87654321, 4)", "4321"),
4844            ("bit.tohex(1, 0)", ""),
4845            ("bit.tohex(1, 9)", "00000001"),
4846        ] {
4847            let script = format!("return {body}");
4848            assert_eq!(
4849                f.run(&[b"EVAL", script.as_bytes(), b"0"]),
4850                format!("${}\r\n{want}\r\n", want.len()),
4851                "{body}",
4852            );
4853        }
4854        // A bad argument names the position, the function and what was passed,
4855        // and the line in front of it is the script's own.
4856        for (body, want) in [
4857            (
4858                "return bit.band()",
4859                "bad argument #1 to 'band' (number expected, got no value)",
4860            ),
4861            (
4862                "return bit.band('x')",
4863                "bad argument #1 to 'band' (number expected, got string)",
4864            ),
4865            (
4866                "return bit.tobit(true)",
4867                "bad argument #1 to 'tobit' (number expected, got boolean)",
4868            ),
4869            (
4870                "return bit.lshift(1)",
4871                "bad argument #2 to 'lshift' (number expected, got no value)",
4872            ),
4873        ] {
4874            let reply = f.run(&[b"EVAL", body.as_bytes(), b"0"]);
4875            assert!(
4876                reply.starts_with(&format!("-ERR user_script:1: {want} script: ")),
4877                "{body} gave {reply}",
4878            );
4879        }
4880        // The name in the message is the one the call site used, so a call that
4881        // went through `pcall` has no name to report.
4882        assert_eq!(
4883            f.run(&[
4884                b"EVAL",
4885                b"local ok, e = pcall(bit.band, 'x') return tostring(e)",
4886                b"0",
4887            ]),
4888            "$52\r\nbad argument #1 to '?' (number expected, got string)\r\n"
4889        );
4890        // The table is readable and not writable, the same as `redis`.
4891        assert_eq!(
4892            f.run(&[
4893                b"EVAL",
4894                b"local t = {} for k in pairs(bit) do t[#t+1] = k end \
4895                  table.sort(t) return table.concat(t, ' ')",
4896                b"0",
4897            ]),
4898            "$66\r\narshift band bnot bor bswap bxor lshift rol ror rshift tobit tohex\r\n"
4899        );
4900        for body in [&b"bit.band = 1"[..], b"rawset(bit, 'zz', 1)"] {
4901            assert!(
4902                f.run(&[b"EVAL", body, b"0"])
4903                    .contains("Attempt to modify a readonly table script: "),
4904                "{body:?}",
4905            );
4906        }
4907    }
4908
4909    #[test]
4910    fn a_script_gets_the_cjson_library_a_real_server_carries() {
4911        let mut f = Fixture::new();
4912        // Encoding, including the three shapes nobody guesses right: an empty
4913        // table is an object, a number is fourteen significant digits, and a
4914        // hole in an array is a null rather than a shorter array.
4915        for (body, want) in [
4916            ("cjson.encode(nil)", "null"),
4917            ("cjson.encode(true)", "true"),
4918            ("cjson.encode(cjson.null)", "null"),
4919            ("cjson.encode(100)", "100"),
4920            ("cjson.encode(1/3)", "0.33333333333333"),
4921            ("cjson.encode(1e300)", "1e+300"),
4922            ("cjson.encode(2^53)", "9.007199254741e+15"),
4923            ("cjson.encode({})", "{}"),
4924            ("cjson.encode({1,2,3})", "[1,2,3]"),
4925            ("cjson.encode({a=1})", "{\"a\":1}"),
4926            ("cjson.encode({[1]=1,[3]=3})", "[1,null,3]"),
4927            ("cjson.encode({[0]=1})", "{\"0\":1}"),
4928            ("cjson.encode('a\\nb')", "\"a\\nb\""),
4929            // A tab and a backslash have short escapes, a vertical tab does not.
4930            ("cjson.encode('\\t\\\\')", "\"\\t\\\\\""),
4931            ("cjson.encode('\\11')", "\"\\u000b\""),
4932            // Reading and writing again is the shortest way to say the decoder
4933            // built what the encoder expected.
4934            (
4935                "cjson.encode(cjson.decode('[1,[2,{\"a\":null}]]'))",
4936                "[1,[2,{\"a\":null}]]",
4937            ),
4938            // An empty array comes back as an object, because a table with
4939            // nothing in it has nothing to say about which it was.
4940            ("cjson.encode(cjson.decode('[]'))", "{}"),
4941        ] {
4942            let script = format!("return {body}");
4943            assert_eq!(
4944                f.run(&[b"EVAL", script.as_bytes(), b"0"]),
4945                format!("${}\r\n{want}\r\n", want.len()),
4946                "{body}",
4947            );
4948        }
4949        // Decoding, where the leniency about numbers is on by default and a
4950        // null is a value of its own rather than a missing key.
4951        for (body, want) in [
4952            ("cjson.decode('[1,2,3]')[2]", ":2\r\n"),
4953            ("cjson.decode('{\"a\":41}').a + 1", ":42\r\n"),
4954            ("cjson.decode('0x10')", ":16\r\n"),
4955            ("cjson.decode('+1')", ":1\r\n"),
4956            ("cjson.decode('01')", ":1\r\n"),
4957            ("cjson.decode(1) + 1", ":2\r\n"),
4958            // A long bracket, because Lua 5.1 would eat the backslash first.
4959            ("cjson.decode([[\"\\u0041\"]]) == 'A' and 1 or 0", ":1\r\n"),
4960            ("cjson.decode('null') == cjson.null and 1 or 0", ":1\r\n"),
4961            ("cjson.decode('null') == nil and 1 or 0", ":0\r\n"),
4962        ] {
4963            let script = format!("return {body}");
4964            assert_eq!(f.run(&[b"EVAL", script.as_bytes(), b"0"]), want, "{body}");
4965        }
4966        // The settings, each of which answers with what it now holds.
4967        for (body, want) in [
4968            (
4969                "cjson.encode_number_precision(3) return cjson.encode(1/3)",
4970                "0.333",
4971            ),
4972            (
4973                "cjson.encode_invalid_numbers('null') return cjson.encode(1/0)",
4974                "null",
4975            ),
4976            (
4977                "cjson.encode_invalid_numbers(true) return cjson.encode(1/0)",
4978                "inf",
4979            ),
4980            (
4981                "cjson.encode_sparse_array(true) return cjson.encode({[1]=1,[100]=1})",
4982                "{\"1\":1,\"100\":1}",
4983            ),
4984            (
4985                "cjson.decode_array_with_array_mt(true) return cjson.encode(cjson.decode('[]'))",
4986                "[]",
4987            ),
4988            ("return tostring(cjson.encode_max_depth())", "1000"),
4989            ("return tostring(cjson.encode_keep_buffer(false))", "false"),
4990            ("return tostring(cjson.encode_sparse_array())", "false"),
4991            // A setting one script changed is not a setting the next one sees,
4992            // which is D-105.
4993            ("return tostring(cjson.encode_number_precision())", "14"),
4994        ] {
4995            assert_eq!(
4996                f.run(&[b"EVAL", body.as_bytes(), b"0"]),
4997                format!("${}\r\n{want}\r\n", want.len()),
4998                "{body}",
4999            );
5000        }
5001        // A failure names what stopped it and, when it was the text, where.
5002        for (body, want) in [
5003            (
5004                "return cjson.encode(1/0)",
5005                "Cannot serialise number: must not be NaN or Inf",
5006            ),
5007            (
5008                "return cjson.encode({[1]=1,[100]=1})",
5009                "Cannot serialise table: excessively sparse array",
5010            ),
5011            (
5012                "return cjson.encode({[true]=1})",
5013                "Cannot serialise boolean: table key must be a number or string",
5014            ),
5015            (
5016                "return cjson.encode(tostring)",
5017                "Cannot serialise function: type not supported",
5018            ),
5019            (
5020                "return cjson.encode()",
5021                "bad argument #1 to 'encode' (expected 1 argument)",
5022            ),
5023            (
5024                "return cjson.decode('[1,2')",
5025                "Expected comma or array end but found T_END at character 5",
5026            ),
5027            (
5028                "return cjson.decode('{\"a\" 1}')",
5029                "Expected colon but found T_NUMBER at character 6",
5030            ),
5031            (
5032                "return cjson.decode('tru')",
5033                "Expected value but found invalid token at character 1",
5034            ),
5035            (
5036                "return cjson.decode('[1] 2')",
5037                "Expected the end but found T_NUMBER at character 5",
5038            ),
5039            (
5040                "return cjson.encode_max_depth(0)",
5041                "bad argument #1 to 'encode_max_depth' (expected integer between 1 and 2147483647)",
5042            ),
5043            (
5044                "return cjson.encode_invalid_numbers('yes')",
5045                "bad argument #1 to 'encode_invalid_numbers' (invalid option 'yes')",
5046            ),
5047            (
5048                "return cjson.encode_max_depth(1, 2)",
5049                "bad argument #2 to 'encode_max_depth' (found too many arguments)",
5050            ),
5051        ] {
5052            let reply = f.run(&[b"EVAL", body.as_bytes(), b"0"]);
5053            assert!(
5054                reply.starts_with(&format!("-ERR user_script:1: {want} script: ")),
5055                "{body} gave {reply}",
5056            );
5057        }
5058        // A module of its own, with settings of its own and no guard on it,
5059        // which is what a real server hands back.
5060        assert_eq!(
5061            f.run(&[
5062                b"EVAL",
5063                b"local n = cjson.new() n.encode_number_precision(3) \
5064                  return cjson.encode(1/3) .. ' ' .. n.encode(1/3)",
5065                b"0",
5066            ]),
5067            "$22\r\n0.33333333333333 0.333\r\n"
5068        );
5069        // The table is readable and not writable, the same as `redis`.
5070        let names = "_NAME _VERSION decode decode_array_with_array_mt decode_invalid_numbers \
5071                     decode_max_depth encode encode_invalid_numbers encode_keep_buffer \
5072                     encode_max_depth encode_number_precision encode_sparse_array new null";
5073        assert_eq!(
5074            f.run(&[
5075                b"EVAL",
5076                b"local t = {} for k in pairs(cjson) do t[#t+1] = k end \
5077                  table.sort(t) return table.concat(t, ' ')",
5078                b"0",
5079            ]),
5080            format!("${}\r\n{names}\r\n", names.len())
5081        );
5082        for body in [&b"cjson.encode = 1"[..], b"rawset(cjson, 'zz', 1)"] {
5083            assert!(
5084                f.run(&[b"EVAL", body, b"0"])
5085                    .contains("Attempt to modify a readonly table script: "),
5086                "{body:?}",
5087            );
5088        }
5089    }
5090
5091    #[test]
5092    fn a_script_gets_the_struct_library_a_real_server_carries() {
5093        let mut f = Fixture::new();
5094        // Packing, where the sizes are the ones a sixty four bit build gives
5095        // and the order is the machine's own unless the format says otherwise.
5096        for (body, want) in [
5097            ("#struct.pack('i4', 1)", ":4\r\n"),
5098            ("#struct.pack('l', 1)", ":8\r\n"),
5099            ("#struct.pack('d', 1)", ":8\r\n"),
5100            ("#struct.pack('f', 1)", ":4\r\n"),
5101            ("#struct.pack('s', 'abc')", ":4\r\n"),
5102            ("#struct.pack('c3', 'abcdef')", ":3\r\n"),
5103            ("#struct.pack('x')", ":1\r\n"),
5104            ("string.byte(struct.pack('i4', 1), 1)", ":1\r\n"),
5105            ("string.byte(struct.pack('>i4', 1), 4)", ":1\r\n"),
5106            ("string.byte(struct.pack('<i4', 1), 1)", ":1\r\n"),
5107            // Past eight bytes the C shifts an unsigned long off the end, so
5108            // the rest of the bytes are zero and a negative is not carried.
5109            ("string.byte(struct.pack('i16', -1), 9)", ":0\r\n"),
5110            ("string.byte(struct.pack('i8', -1), 8)", ":255\r\n"),
5111            // A count of zero on `c` writes the whole string, `s` adds the
5112            // terminator, and `x` writes a zero byte nobody reads back.
5113            ("#struct.pack('c0', 'abcd')", ":4\r\n"),
5114            ("string.byte(struct.pack('s', 'a'), 2)", ":0\r\n"),
5115            ("string.byte(struct.pack('bxb', 1, 2), 2)", ":0\r\n"),
5116        ] {
5117            let script = format!("return {body}");
5118            assert_eq!(f.run(&[b"EVAL", script.as_bytes(), b"0"]), want, "{body}");
5119        }
5120        // Sizes, including the two the C is lenient about: an unknown letter
5121        // and a bare digit are both nothing at all rather than a complaint.
5122        for (body, want) in [
5123            ("struct.size('i')", ":4\r\n"),
5124            ("struct.size('l')", ":8\r\n"),
5125            ("struct.size('T')", ":8\r\n"),
5126            ("struct.size('h')", ":2\r\n"),
5127            ("struct.size('c10')", ":10\r\n"),
5128            ("struct.size('ic')", ":5\r\n"),
5129            ("struct.size('!8ic')", ":5\r\n"),
5130            ("struct.size('!4i')", ":4\r\n"),
5131            // Nothing is padded until `!` turns alignment on, and then a
5132            // double is pushed out to the next eight byte boundary.
5133            ("struct.size('bd')", ":9\r\n"),
5134            ("struct.size('!bd')", ":16\r\n"),
5135            ("struct.size('A')", ":0\r\n"),
5136            ("struct.size('7')", ":0\r\n"),
5137        ] {
5138            let script = format!("return {body}");
5139            assert_eq!(f.run(&[b"EVAL", script.as_bytes(), b"0"]), want, "{body}");
5140        }
5141        // Unpacking, which hands back the values and then where it stopped, so
5142        // the last number can be passed straight back in as the next offset.
5143        for (body, want) in [
5144            ("select('#', struct.unpack('i4', '\\1\\0\\0\\0'))", ":2\r\n"),
5145            ("select(1, struct.unpack('i4', '\\1\\0\\0\\0'))", ":1\r\n"),
5146            ("select(2, struct.unpack('i4', '\\1\\0\\0\\0'))", ":5\r\n"),
5147            ("select(1, struct.unpack('i1', '\\255'))", ":-1\r\n"),
5148            ("select(1, struct.unpack('I1', '\\255'))", ":255\r\n"),
5149            (
5150                "select(1, struct.unpack('i4', struct.pack('i4', -70000)))",
5151                ":-70000\r\n",
5152            ),
5153            ("select(2, struct.unpack('i1', 'abc', 2))", ":3\r\n"),
5154            // A `c0` takes its length from the value read just before it and
5155            // swallows it, so one byte says how long the next three are and
5156            // only the string and the position come back.
5157            ("select('#', struct.unpack('bc0', '\\3abcd'))", ":2\r\n"),
5158            ("select(2, struct.unpack('bc0', '\\3abcd'))", ":5\r\n"),
5159        ] {
5160            let script = format!("return {body}");
5161            assert_eq!(f.run(&[b"EVAL", script.as_bytes(), b"0"]), want, "{body}");
5162        }
5163        for (body, want) in [
5164            ("select(1, struct.unpack('bc0', '\\3abcd'))", "abc"),
5165            ("select(1, struct.unpack('s', 'ab\\0cd'))", "ab"),
5166            ("select(1, struct.unpack('c3', 'abcdef'))", "abc"),
5167        ] {
5168            let script = format!("return {body}");
5169            assert_eq!(
5170                f.run(&[b"EVAL", script.as_bytes(), b"0"]),
5171                format!("${}\r\n{want}\r\n", want.len()),
5172                "{body}",
5173            );
5174        }
5175        // A failure names the argument the C names, which is not always the
5176        // argument a reader would pick.
5177        for (body, want) in [
5178            (
5179                "return struct.pack()",
5180                "bad argument #1 to 'pack' (string expected, got no value)",
5181            ),
5182            // The C pushes a nil before it reads anything, so a missing value
5183            // is a nil rather than nothing at all.
5184            (
5185                "return struct.pack('i4')",
5186                "bad argument #2 to 'pack' (number expected, got nil)",
5187            ),
5188            // And it reads the string with a post increment before it checks
5189            // the length, so the number here is one past the real argument.
5190            (
5191                "return struct.pack('c6', 'abc')",
5192                "bad argument #3 to 'pack' (string too short)",
5193            ),
5194            (
5195                "return struct.pack('A', 'x')",
5196                "bad argument #1 to 'pack' (invalid format option 'A')",
5197            ),
5198            (
5199                "return struct.pack('i33', 1)",
5200                "integral size 33 is larger than limit of 32",
5201            ),
5202            (
5203                "return struct.pack('!3i', 1)",
5204                "alignment 3 is not a power of 2",
5205            ),
5206            (
5207                "return struct.unpack()",
5208                "bad argument #1 to 'unpack' (string expected, got no value)",
5209            ),
5210            (
5211                "return struct.unpack('i4')",
5212                "bad argument #2 to 'unpack' (string expected, got no value)",
5213            ),
5214            (
5215                "return struct.unpack('i4', 'ab')",
5216                "bad argument #2 to 'unpack' (data string too short)",
5217            ),
5218            (
5219                "return struct.unpack('i1', 'abc', 0)",
5220                "bad argument #3 to 'unpack' (offset must be 1 or greater)",
5221            ),
5222            (
5223                "return struct.unpack('c0', 'abc')",
5224                "format 'c0' needs a previous size",
5225            ),
5226            (
5227                "return struct.unpack('s', 'abc')",
5228                "unfinished string in data",
5229            ),
5230            (
5231                "return struct.size()",
5232                "bad argument #1 to 'size' (string expected, got no value)",
5233            ),
5234            (
5235                "return struct.size('s')",
5236                "bad argument #1 to 'size' (option 's' has no fixed size)",
5237            ),
5238            (
5239                "return struct.size('c0')",
5240                "bad argument #1 to 'size' (option 'c0' has no fixed size)",
5241            ),
5242        ] {
5243            let reply = f.run(&[b"EVAL", body.as_bytes(), b"0"]);
5244            assert!(
5245                reply.starts_with(&format!("-ERR user_script:1: {want} script: ")),
5246                "{body} gave {reply}",
5247            );
5248        }
5249        // Three members and no version, which is all the C registers.
5250        let names = "pack size unpack";
5251        assert_eq!(
5252            f.run(&[
5253                b"EVAL",
5254                b"local t = {} for k in pairs(struct) do t[#t+1] = k end \
5255                  table.sort(t) return table.concat(t, ' ')",
5256                b"0",
5257            ]),
5258            format!("${}\r\n{names}\r\n", names.len())
5259        );
5260        for body in [&b"struct.pack = 1"[..], b"rawset(struct, 'zz', 1)"] {
5261            assert!(
5262                f.run(&[b"EVAL", body, b"0"])
5263                    .contains("Attempt to modify a readonly table script: "),
5264                "{body:?}",
5265            );
5266        }
5267    }
5268
5269    #[test]
5270    fn a_script_gets_the_cmsgpack_library_a_real_server_carries() {
5271        let mut f = Fixture::new();
5272        // Every value goes out in the shortest form that holds it, and several
5273        // arguments are packed one after another into one string.
5274        let hex = "local function hx(s) return (string.gsub(s, '.', \
5275                   function(c) return string.format('%02x', string.byte(c)) end)) end ";
5276        for (body, want) in [
5277            ("cmsgpack.pack(nil)", "c0"),
5278            ("cmsgpack.pack(true)", "c3"),
5279            ("cmsgpack.pack(false)", "c2"),
5280            ("cmsgpack.pack(0)", "00"),
5281            ("cmsgpack.pack(127)", "7f"),
5282            ("cmsgpack.pack(128)", "cc80"),
5283            ("cmsgpack.pack(-1)", "ff"),
5284            ("cmsgpack.pack(-33)", "d0df"),
5285            ("cmsgpack.pack(65535)", "cdffff"),
5286            ("cmsgpack.pack(4294967296)", "cf0000000100000000"),
5287            ("cmsgpack.pack(2^53)", "cf0020000000000000"),
5288            ("cmsgpack.pack(-2^63)", "d38000000000000000"),
5289            // Past what an integer holds it is a number again, and a number
5290            // goes out narrow whenever four bytes give it back unchanged.
5291            ("cmsgpack.pack(2^64)", "ca5f800000"),
5292            ("cmsgpack.pack(1.5)", "ca3fc00000"),
5293            ("cmsgpack.pack(0.1)", "cb3fb999999999999a"),
5294            ("cmsgpack.pack('abc')", "a3616263"),
5295            ("cmsgpack.pack('')", "a0"),
5296            ("cmsgpack.pack({})", "90"),
5297            ("cmsgpack.pack({1, 2})", "920102"),
5298            ("cmsgpack.pack({a = 1})", "81a16101"),
5299            ("cmsgpack.pack(1, 'a', true)", "01a161c3"),
5300            // Sixteen levels of table are packed and the seventeenth is a nil,
5301            // which is what the C does rather than refusing the whole thing.
5302            (
5303                "(function() local t = {} local c = t \
5304                 for i = 1, 20 do c.n = {} c = c.n end return cmsgpack.pack(t) end)()",
5305                "81a16e81a16e81a16e81a16e81a16e81a16e81a16e81a16e\
5306                 81a16e81a16e81a16e81a16e81a16e81a16e81a16e81a16ec0",
5307            ),
5308        ] {
5309            let script = format!("{hex} return hx({body})");
5310            assert_eq!(
5311                f.run(&[b"EVAL", script.as_bytes(), b"0"]),
5312                format!("${}\r\n{want}\r\n", want.len()),
5313                "{body}",
5314            );
5315        }
5316        // Unpacking reads the whole stream, so a string holding three values
5317        // hands back three. The two that take an offset put where they got to
5318        // in front of the values, and answer minus one when nothing is left.
5319        for (body, want) in [
5320            ("cmsgpack.unpack(cmsgpack.pack(42))", 42),
5321            ("select('#', cmsgpack.unpack('\\1\\2\\3'))", 3),
5322            ("select(3, cmsgpack.unpack('\\1\\2\\3'))", 3),
5323            ("select('#', cmsgpack.unpack(''))", 0),
5324            ("select('#', cmsgpack.unpack_one('\\1\\2\\3'))", 2),
5325            ("select(1, cmsgpack.unpack_one('\\1\\2\\3'))", 1),
5326            ("select(2, cmsgpack.unpack_one('\\1\\2\\3'))", 1),
5327            ("select(1, cmsgpack.unpack_one('\\1\\2\\3', 2))", -1),
5328            ("select(1, cmsgpack.unpack_one('\\1'))", -1),
5329            ("select(1, cmsgpack.unpack_one('', 0))", -1),
5330            ("select('#', cmsgpack.unpack_limit('\\1\\2\\3', 2))", 3),
5331            ("select(1, cmsgpack.unpack_limit('\\1\\2\\3', 2))", 2),
5332            // A limit of nothing at all takes the read everything path, which
5333            // has no offset in front of it.
5334            ("select('#', cmsgpack.unpack_limit('\\1\\2\\3', 0, 0))", 3),
5335            ("cmsgpack.unpack(cmsgpack.pack({1, 2, 3}))[2]", 2),
5336        ] {
5337            let script = format!("return {body}");
5338            assert_eq!(
5339                f.run(&[b"EVAL", script.as_bytes(), b"0"]),
5340                format!(":{want}\r\n"),
5341                "{body}",
5342            );
5343        }
5344        for (body, want) in [
5345            ("cmsgpack.unpack(cmsgpack.pack({a = 'b'})).a", "b"),
5346            ("tostring(cmsgpack.unpack(cmsgpack.pack(1.5)))", "1.5"),
5347            ("tostring(cmsgpack.unpack(cmsgpack.pack(nil)))", "nil"),
5348            (
5349                "tostring(cmsgpack.unpack(string.char(0xcb, 0x7f, 0xf0, 0, 0, 0, 0, 0, 0)))",
5350                "inf",
5351            ),
5352            ("cmsgpack._NAME", "cmsgpack"),
5353            ("cmsgpack._VERSION", "lua-cmsgpack 0.4.0"),
5354            (
5355                "cmsgpack._COPYRIGHT",
5356                "Copyright (C) 2012, Salvatore Sanfilippo",
5357            ),
5358            (
5359                "cmsgpack._DESCRIPTION",
5360                "MessagePack C implementation for Lua",
5361            ),
5362        ] {
5363            let script = format!("return {body}");
5364            assert_eq!(
5365                f.run(&[b"EVAL", script.as_bytes(), b"0"]),
5366                format!("${}\r\n{want}\r\n", want.len()),
5367                "{body}",
5368            );
5369        }
5370        for (body, want) in [
5371            // The C counts the arguments before it reads any of them, so the
5372            // one it names when there are none is the one before the first.
5373            (
5374                "return cmsgpack.pack()",
5375                "bad argument #0 to 'pack' (MessagePack pack needs input.)",
5376            ),
5377            (
5378                "return cmsgpack.unpack()",
5379                "bad argument #1 to 'unpack' (string expected, got no value)",
5380            ),
5381            (
5382                "return cmsgpack.unpack(string.char(193))",
5383                "Bad data format in input.",
5384            ),
5385            (
5386                "return cmsgpack.unpack(string.char(204))",
5387                "Missing bytes in input.",
5388            ),
5389            (
5390                "return cmsgpack.unpack(string.char(146, 1))",
5391                "Missing bytes in input.",
5392            ),
5393            (
5394                "return cmsgpack.unpack_one('\\1', 5)",
5395                "Start offset 5 greater than input length 1.",
5396            ),
5397            (
5398                "return cmsgpack.unpack_limit('\\1\\2', 1, 5)",
5399                "Start offset 5 greater than input length 2.",
5400            ),
5401            // The second number here is the length of the input rather than
5402            // the limit, which is a mixed up argument in the C kept on purpose.
5403            (
5404                "return cmsgpack.unpack_one('\\1', -1)",
5405                "Invalid request to unpack with offset of -1 and limit of 1.",
5406            ),
5407            (
5408                "return cmsgpack.unpack_limit('\\1', -1, 0)",
5409                "Invalid request to unpack with offset of 0 and limit of 1.",
5410            ),
5411        ] {
5412            let reply = f.run(&[b"EVAL", body.as_bytes(), b"0"]);
5413            assert!(
5414                reply.starts_with(&format!("-ERR user_script:1: {want} script: ")),
5415                "{body} gave {reply}",
5416            );
5417        }
5418        // Four calls and the four names the C sets on the table beside them.
5419        let names = "_COPYRIGHT _DESCRIPTION _NAME _VERSION pack unpack unpack_limit unpack_one";
5420        assert_eq!(
5421            f.run(&[
5422                b"EVAL",
5423                b"local t = {} for k in pairs(cmsgpack) do t[#t+1] = k end \
5424                  table.sort(t) return table.concat(t, ' ')",
5425                b"0",
5426            ]),
5427            format!("${}\r\n{names}\r\n", names.len())
5428        );
5429        for body in [&b"cmsgpack.pack = 1"[..], b"rawset(cmsgpack, 'zz', 1)"] {
5430            assert!(
5431                f.run(&[b"EVAL", body, b"0"])
5432                    .contains("Attempt to modify a readonly table script: "),
5433                "{body:?}",
5434            );
5435        }
5436        // A library is a table like any other from a script's side, so packing
5437        // one walks its members rather than finding the guard in front empty.
5438        assert_eq!(
5439            f.run(&[
5440                b"EVAL",
5441                b"return cmsgpack.unpack(cmsgpack.pack(cmsgpack))._NAME",
5442                b"0",
5443            ]),
5444            "$8\r\ncmsgpack\r\n"
5445        );
5446    }
5447
5448    /// The library used by most of the function tests below.
5449    ///
5450    /// Written out once because every one of them wants a library that has
5451    /// something to call, and because the line numbers in the failures a couple
5452    /// of them check are line numbers in this.
5453    const LIB: &[u8] = b"#!lua name=mylib\n\
5454        local counter = 0\n\
5455        redis.register_function{function_name = 'ping', description = 'says pong',\n\
5456        callback = function(keys, args) return 'pong' end, flags = {'no-writes'}}\n\
5457        redis.register_function('count', function() counter = counter + 1 return counter end)\n\
5458        redis.register_function('echo', function(keys, args) return {keys, args} end)\n\
5459        redis.register_function('setit', function(keys, args) \
5460        return redis.call('SET', keys[1], args[1]) end)\n\
5461        redis.register_function('raise', function() error('boom') end)\n";
5462
5463    /// A second library, for the tests that need two of them.
5464    const OTHER: &[u8] = b"#!lua name=other\n\
5465        redis.register_function('twice', function(keys, args) return 2 end)\n";
5466
5467    #[test]
5468    fn a_library_is_loaded_once_and_called_by_name_forever_after() {
5469        let mut f = Fixture::new();
5470        assert_eq!(f.run(&[b"FUNCTION", b"LOAD", LIB]), "$5\r\nmylib\r\n");
5471        assert_eq!(f.run(&[b"FCALL", b"ping", b"0"]), "$4\r\npong\r\n");
5472        // The dictionary FCALL looks in is one for the whole server and it does
5473        // not care about case, which is why this finds the same function.
5474        assert_eq!(f.run(&[b"FCALL", b"PiNg", b"0"]), "$4\r\npong\r\n");
5475        // Keys and arguments arrive as the two arguments of the callback rather
5476        // than as globals, and a function that reads KEYS is reading a name
5477        // that is not there.
5478        assert_eq!(
5479            f.run(&[b"FCALL", b"echo", b"1", b"k", b"a", b"b"]),
5480            "*2\r\n*1\r\n$1\r\nk\r\n*2\r\n$1\r\na\r\n$1\r\nb\r\n"
5481        );
5482        assert_eq!(f.run(&[b"FCALL", b"setit", b"1", b"s", b"v"]), "+OK\r\n");
5483        assert_eq!(f.run(&[b"GET", b"s"]), "$1\r\nv\r\n");
5484        // A library's own local outlives the call that made it, which is the
5485        // whole reason a library is not a script.
5486        assert_eq!(f.run(&[b"FCALL", b"count", b"0"]), ":1\r\n");
5487        assert_eq!(f.run(&[b"FCALL", b"count", b"0"]), ":2\r\n");
5488        // The name a failure ends with is the function's, where a script's is
5489        // its digest, and the line is a line in the library.
5490        assert_eq!(
5491            f.run(&[b"FCALL", b"raise", b"0"]),
5492            "-ERR user_function:8: boom script: raise, on @user_function:8.\r\n"
5493        );
5494        // Deleting is by the exact name, so the upper case spelling that found
5495        // the function a moment ago does not find the library.
5496        assert_eq!(
5497            f.run(&[b"FUNCTION", b"DELETE", b"MYLIB"]),
5498            "-ERR Library not found\r\n"
5499        );
5500        assert_eq!(f.run(&[b"FUNCTION", b"DELETE", b"mylib"]), "+OK\r\n");
5501        assert_eq!(
5502            f.run(&[b"FCALL", b"ping", b"0"]),
5503            "-ERR Function not found\r\n"
5504        );
5505    }
5506
5507    #[test]
5508    fn a_library_that_is_wrong_says_which_way_it_is_wrong() {
5509        let mut f = Fixture::new();
5510        for (code, want) in [
5511            (&b"return 1"[..], "ERR Missing library metadata"),
5512            (b"#!lua name=x", "ERR Invalid library metadata"),
5513            (b"#!\n", "ERR Library name was not given"),
5514            (b"#!lua\nx", "ERR Library name was not given"),
5515            (
5516                b"#!lua name=a name=b\nx",
5517                "ERR Invalid metadata value, name argument was given multiple times",
5518            ),
5519            (
5520                b"#!lua nome=a\nx",
5521                "ERR Invalid metadata value given: nome=a",
5522            ),
5523            (b"#!lua name=\"q\nx", "ERR Invalid library metadata"),
5524            (
5525                b"#!lua name=a-b\nx",
5526                "ERR Library names can only contain letters, numbers, or underscores(_) \
5527                 and must be at least one character long",
5528            ),
5529            (b"#!zz name=x\nx", "ERR Engine 'zz' not found"),
5530            (
5531                b"#!lua name=c\nthis is not lua",
5532                "ERR Error compiling function: user_function:2: '=' expected near 'is'",
5533            ),
5534            // Nothing at all is on the global table during a load except one
5535            // table with eight names on it, so `error` is as absent as anything
5536            // a library misspelled would be.
5537            (
5538                b"#!lua name=r\nerror('boom')",
5539                "ERR Error registering functions: ERR user_function:2: \
5540                 Script attempted to access nonexistent global variable 'error'",
5541            ),
5542            // And `redis` is there but `redis.call` is not, so the name the
5543            // complaint gives is `call` and not `redis`.
5544            (
5545                b"#!lua name=r\nredis.call('PING')",
5546                "ERR Error registering functions: ERR user_function:2: \
5547                 Script attempted to access nonexistent global variable 'call'",
5548            ),
5549            (
5550                b"#!lua name=r\nx = 1",
5551                "ERR Error registering functions: ERR user_function:2: \
5552                 Attempt to modify a readonly table",
5553            ),
5554            (b"#!lua name=n\nlocal x = 1", "ERR No functions registered"),
5555        ] {
5556            assert_eq!(
5557                f.run(&[b"FUNCTION", b"LOAD", code]),
5558                format!("-{want}\r\n"),
5559                "{}",
5560                String::from_utf8_lossy(code),
5561            );
5562        }
5563    }
5564
5565    #[test]
5566    fn register_function_turns_away_every_call_it_cannot_make_sense_of() {
5567        let mut f = Fixture::new();
5568        for (call, want) in [
5569            (
5570                &b"redis.register_function()"[..],
5571                "wrong number of arguments to redis.register_function",
5572            ),
5573            (
5574                b"redis.register_function('a', function() end, 1)",
5575                "wrong number of arguments to redis.register_function",
5576            ),
5577            (
5578                b"redis.register_function('a')",
5579                "calling redis.register_function with a single argument is only \
5580                 applicable to Lua table (representing named arguments).",
5581            ),
5582            (
5583                b"redis.register_function({foo = 'a'})",
5584                "unknown argument given to redis.register_function",
5585            ),
5586            (
5587                b"redis.register_function({callback = function() end})",
5588                "redis.register_function must get a function name argument",
5589            ),
5590            (
5591                b"redis.register_function({function_name = 'a'})",
5592                "redis.register_function must get a callback argument",
5593            ),
5594            (
5595                b"redis.register_function({function_name = {}, callback = function() end})",
5596                "function_name argument given to redis.register_function must be a string",
5597            ),
5598            (
5599                b"redis.register_function({function_name = 'a', description = {}, \
5600                  callback = function() end})",
5601                "description argument given to redis.register_function must be a string",
5602            ),
5603            (
5604                b"redis.register_function({function_name = 'a', callback = 1})",
5605                "callback argument given to redis.register_function must be a function",
5606            ),
5607            (
5608                b"redis.register_function({function_name = 'a', callback = function() end, \
5609                  flags = 1})",
5610                "flags argument to redis.register_function must be a table \
5611                 representing function flags",
5612            ),
5613            (
5614                b"redis.register_function({function_name = 'a', callback = function() end, \
5615                  flags = {'zz'}})",
5616                "unknown flag given",
5617            ),
5618            (
5619                b"redis.register_function({}, function() end)",
5620                "first argument to redis.register_function must be a string",
5621            ),
5622            (
5623                b"redis.register_function('a', 1)",
5624                "second argument to redis.register_function must be a function",
5625            ),
5626            (
5627                b"redis.register_function('a-b', function() end)",
5628                "Library names can only contain letters, numbers, or underscores(_) \
5629                 and must be at least one character long",
5630            ),
5631            (
5632                b"redis.register_function('d', function() end) \
5633                  redis.register_function('d', function() end)",
5634                "Function already exists in the library",
5635            ),
5636        ] {
5637            let mut code = b"#!lua name=e\n".to_vec();
5638            code.extend_from_slice(call);
5639            // Two `ERR` in a row on purpose. The sentence comes back as a table
5640            // with the code already on it, which is what keeps the position off
5641            // the front of it, and then the code goes on the line as well.
5642            assert_eq!(
5643                f.run(&[b"FUNCTION", b"LOAD", &code]),
5644                format!("-ERR Error registering functions: ERR {want}\r\n"),
5645                "{}",
5646                String::from_utf8_lossy(call),
5647            );
5648        }
5649        // A number is a name, because the C reads an argument that should be a
5650        // string through a helper that takes a number and prints it.
5651        assert_eq!(
5652            f.run(&[
5653                b"FUNCTION",
5654                b"LOAD",
5655                b"#!lua name=n\nredis.register_function(12, function() return 1 end)",
5656            ]),
5657            "$1\r\nn\r\n"
5658        );
5659        assert_eq!(f.run(&[b"FCALL", b"12", b"0"]), ":1\r\n");
5660        // The dictionary inside one library is case sensitive where the one
5661        // across libraries is not, so these are two functions.
5662        assert_eq!(
5663            f.run(&[
5664                b"FUNCTION",
5665                b"LOAD",
5666                b"#!lua name=c\nredis.register_function('d', function() return 1 end) \
5667                  redis.register_function('D', function() return 2 end)",
5668            ]),
5669            "$1\r\nc\r\n"
5670        );
5671    }
5672
5673    #[test]
5674    fn a_library_cannot_take_a_name_another_library_already_has() {
5675        let mut f = Fixture::new();
5676        assert_eq!(f.run(&[b"FUNCTION", b"LOAD", LIB]), "$5\r\nmylib\r\n");
5677        assert_eq!(
5678            f.run(&[b"FUNCTION", b"LOAD", LIB]),
5679            "-ERR Library 'mylib' already exists\r\n"
5680        );
5681        // A different library that registers a name the first one already has,
5682        // which is checked without regard to case because the dictionary it is
5683        // checked against is.
5684        assert_eq!(
5685            f.run(&[
5686                b"FUNCTION",
5687                b"LOAD",
5688                b"#!lua name=other\nredis.register_function('PING', function() return 1 end)",
5689            ]),
5690            "-ERR Function PING already exists\r\n"
5691        );
5692        // REPLACE reloads a library over itself, and the collision check leaves
5693        // the library being replaced out or nothing could ever be reloaded.
5694        assert_eq!(
5695            f.run(&[b"FUNCTION", b"LOAD", b"REPLACE", LIB]),
5696            "$5\r\nmylib\r\n"
5697        );
5698        // The counter went back to zero with the reload, since the library is a
5699        // new one and its locals are new with it.
5700        assert_eq!(f.run(&[b"FCALL", b"count", b"0"]), ":1\r\n");
5701        assert_eq!(
5702            f.run(&[b"FUNCTION", b"LOAD", b"NOPE", LIB]),
5703            "-ERR Unknown option given: NOPE\r\n"
5704        );
5705        // The loop that reads the options stops one short of the end, so the
5706        // last argument is the code whatever it looks like.
5707        assert_eq!(
5708            f.run(&[b"FUNCTION", b"LOAD", b"REPLACE"]),
5709            "-ERR Missing library metadata\r\n"
5710        );
5711        assert_eq!(
5712            f.run(&[b"FUNCTION", b"LOAD"]),
5713            "-ERR wrong number of arguments for 'function|load' command\r\n"
5714        );
5715    }
5716
5717    #[test]
5718    fn fcall_checks_the_name_before_it_looks_at_anything_else() {
5719        let mut f = Fixture::new();
5720        assert_eq!(f.run(&[b"FUNCTION", b"LOAD", LIB]), "$5\r\nmylib\r\n");
5721        for (args, want) in [
5722            (&[&b"nosuch"[..], b"x"][..], "ERR Function not found"),
5723            (&[b"ping", b"x"], "ERR Bad number of keys provided"),
5724            (&[b"ping", b"1.5"], "ERR Bad number of keys provided"),
5725            (&[b"ping", b"+1"], "ERR Bad number of keys provided"),
5726            (
5727                &[b"ping", b"99999999999999999999"],
5728                "ERR Bad number of keys provided",
5729            ),
5730            (
5731                &[b"ping", b"3", b"a"],
5732                "ERR Number of keys can't be greater than number of args",
5733            ),
5734            (&[b"ping", b"-1"], "ERR Number of keys can't be negative"),
5735        ] {
5736            let mut wire: Vec<&[u8]> = vec![b"FCALL"];
5737            wire.extend_from_slice(args);
5738            assert_eq!(f.run(&wire), format!("-{want}\r\n"), "{args:?}");
5739        }
5740        // The read-only spelling refuses a function the library did not mark
5741        // no-writes, and it refuses it before anything runs.
5742        assert_eq!(
5743            f.run(&[b"FCALL_RO", b"setit", b"1", b"s", b"v"]),
5744            "-ERR Can not execute a script with write flag using *_ro command.\r\n"
5745        );
5746        assert_eq!(f.run(&[b"FCALL_RO", b"ping", b"0"]), "$4\r\npong\r\n");
5747        assert_eq!(
5748            f.run(&[b"FCALL_RO", b"nosuch", b"0"]),
5749            "-ERR Function not found\r\n"
5750        );
5751        // And a function that was marked no-writes is held to it whichever
5752        // spelling called it.
5753        assert_eq!(
5754            f.run(&[
5755                b"FUNCTION",
5756                b"LOAD",
5757                b"#!lua name=w\nredis.register_function{function_name = 'w', \
5758                  flags = {'no-writes'}, callback = function(keys) \
5759                  return redis.call('SET', keys[1], 'x') end}",
5760            ]),
5761            "$1\r\nw\r\n"
5762        );
5763        assert!(
5764            f.run(&[b"FCALL", b"w", b"1", b"k"])
5765                .starts_with("-ERR Write commands are not allowed from read-only scripts."),
5766        );
5767    }
5768
5769    #[test]
5770    fn a_function_gets_the_globals_a_script_gets_minus_the_ones_only_eval_has() {
5771        let mut f = Fixture::new();
5772        // The three names on the `redis` table that only mean something inside
5773        // EVAL are not there, and neither is the error handler EVAL installs.
5774        let names = "LOG_DEBUG LOG_NOTICE LOG_VERBOSE LOG_WARNING REDIS_VERSION \
5775                     REDIS_VERSION_NUM REPL_ALL REPL_AOF REPL_NONE REPL_REPLICA REPL_SLAVE \
5776                     acl_check_cmd call error_reply log pcall set_repl setresp sha1hex \
5777                     status_reply";
5778        let globals = "_G _VERSION assert bit cjson cmsgpack collectgarbage coroutine error \
5779                       gcinfo getmetatable ipairs load loadstring math next os pairs pcall \
5780                       rawequal rawget rawset redis select setmetatable string struct table \
5781                       tonumber tostring type unpack xpcall";
5782        assert_eq!(
5783            f.run(&[
5784                b"FUNCTION",
5785                b"LOAD",
5786                b"#!lua name=g\n\
5787                  local function sorted(t) local o = {} for k in pairs(t) do o[#o+1] = k end \
5788                  table.sort(o) return table.concat(o, ' ') end\n\
5789                  redis.register_function('names', function() return sorted(redis) end)\n\
5790                  redis.register_function('globals', function() return sorted(_G) end)\n\
5791                  redis.register_function('keysg', function() return KEYS[1] end)\n\
5792                  redis.register_function('zzz', function() return tostring(redis.zzz) end)\n\
5793                  redis.register_function('wr', function() rawset(_G, 'x', 1) end)\n\
5794                  redis.register_function('gwr', function() _G.pcall = 1 end)\n",
5795            ]),
5796            "$1\r\ng\r\n"
5797        );
5798        assert_eq!(
5799            f.run(&[b"FCALL", b"names", b"0"]),
5800            format!("${}\r\n{names}\r\n", names.len())
5801        );
5802        assert_eq!(
5803            f.run(&[b"FCALL", b"globals", b"0"]),
5804            format!("${}\r\n{globals}\r\n", globals.len())
5805        );
5806        // No `KEYS`, and reading a global that is not there is a mistake rather
5807        // than a nil, so this is the sandbox's own complaint.
5808        assert!(
5809            f.run(&[b"FCALL", b"keysg", b"1", b"k"])
5810                .contains("nonexistent global variable 'KEYS'"),
5811        );
5812        // The `redis` table has no error metatable on it, unlike the global
5813        // table, so a name that is not on it is a nil and not a complaint.
5814        assert_eq!(f.run(&[b"FCALL", b"zzz", b"0"]), "$3\r\nnil\r\n");
5815        // The global table cannot be written to either way round, which is a
5816        // stricter rule than the one a script runs under.
5817        for name in [&b"wr"[..], b"gwr"] {
5818            assert!(
5819                f.run(&[b"FCALL", name, b"0"])
5820                    .contains("Attempt to modify a readonly table"),
5821                "{}",
5822                String::from_utf8_lossy(name),
5823            );
5824        }
5825    }
5826
5827    #[test]
5828    fn function_list_says_what_every_library_registered() {
5829        let mut f = Fixture::new();
5830        assert_eq!(f.run(&[b"FUNCTION", b"LOAD", LIB]), "$5\r\nmylib\r\n");
5831        // One map per library on RESP3, and the functions inside it in the
5832        // order the library registered them, which is D-109.
5833        f.out = Out::new(Proto::Resp3);
5834        let listed = f.run(&[b"FUNCTION", b"LIST"]);
5835        assert!(listed.starts_with("*1\r\n%3\r\n$12\r\nlibrary_name\r\n$5\r\nmylib\r\n"));
5836        assert!(listed.contains("$6\r\nengine\r\n$3\r\nLUA\r\n"));
5837        assert!(listed.contains(
5838            "%3\r\n$4\r\nname\r\n$4\r\nping\r\n\
5839             $11\r\ndescription\r\n$9\r\nsays pong\r\n$5\r\nflags\r\n~1\r\n+no-writes\r\n"
5840        ));
5841        // A function with no description gets a null rather than an empty
5842        // string, and no flags is an empty set rather than a missing field.
5843        assert!(listed.contains(
5844            "$4\r\nname\r\n$5\r\ncount\r\n$11\r\ndescription\r\n_\r\n$5\r\nflags\r\n~0\r\n"
5845        ));
5846        assert!(!listed.contains("library_code"));
5847        assert!(
5848            f.run(&[b"FUNCTION", b"LIST", b"WITHCODE"])
5849                .contains("library_code")
5850        );
5851        // The pattern is matched without regard to case, which is a third rule
5852        // again next to the two the two dictionaries use.
5853        assert!(
5854            f.run(&[b"FUNCTION", b"LIST", b"LIBRARYNAME", b"MY*"])
5855                .starts_with("*1\r\n")
5856        );
5857        assert_eq!(
5858            f.run(&[b"FUNCTION", b"LIST", b"LIBRARYNAME", b"zz*"]),
5859            "*0\r\n"
5860        );
5861        // On RESP2 the same reply is a flat array of six, which is what `map`
5862        // means on a protocol that has no map.
5863        f.out = Out::new(Proto::Resp2);
5864        assert!(f.run(&[b"FUNCTION", b"LIST"]).starts_with("*1\r\n*6\r\n"));
5865        for (args, want) in [
5866            (&[&b"ZZ"[..]][..], "ERR Unknown argument ZZ"),
5867            (&[b"WITHCODE", b"WITHCODE"], "ERR Unknown argument WITHCODE"),
5868            (
5869                &[b"LIBRARYNAME", b"a", b"LIBRARYNAME", b"b"],
5870                "ERR Unknown argument LIBRARYNAME",
5871            ),
5872            (&[b"LIBRARYNAME"], "ERR library name argument was not given"),
5873        ] {
5874            let mut wire: Vec<&[u8]> = vec![b"FUNCTION", b"LIST"];
5875            wire.extend_from_slice(args);
5876            assert_eq!(f.run(&wire), format!("-{want}\r\n"), "{args:?}");
5877        }
5878    }
5879
5880    #[test]
5881    fn function_stats_counts_what_is_loaded_and_says_nothing_is_running() {
5882        let mut f = Fixture::new();
5883        f.out = Out::new(Proto::Resp3);
5884        assert_eq!(
5885            f.run(&[b"FUNCTION", b"STATS"]),
5886            "%2\r\n$14\r\nrunning_script\r\n_\r\n$7\r\nengines\r\n%1\r\n$3\r\nLUA\r\n\
5887             %2\r\n$15\r\nlibraries_count\r\n:0\r\n$15\r\nfunctions_count\r\n:0\r\n"
5888        );
5889        assert_eq!(f.run(&[b"FUNCTION", b"LOAD", LIB]), "$5\r\nmylib\r\n");
5890        assert!(
5891            f.run(&[b"FUNCTION", b"STATS"])
5892                .ends_with("libraries_count\r\n:1\r\n$15\r\nfunctions_count\r\n:5\r\n"),
5893        );
5894        assert_eq!(f.run(&[b"FUNCTION", b"FLUSH"]), "+OK\r\n");
5895        assert!(
5896            f.run(&[b"FUNCTION", b"STATS"])
5897                .ends_with(":0\r\n$15\r\nfunctions_count\r\n:0\r\n")
5898        );
5899    }
5900
5901    #[test]
5902    fn every_function_subcommand_complains_about_its_own_arity() {
5903        let mut f = Fixture::new();
5904        for (args, want) in [
5905            (
5906                &[&b"STATS"[..], b"X"][..],
5907                "ERR wrong number of arguments for 'function|stats' command",
5908            ),
5909            (
5910                &[b"KILL", b"X"],
5911                "ERR wrong number of arguments for 'function|kill' command",
5912            ),
5913            (
5914                &[b"HELP", b"X"],
5915                "ERR wrong number of arguments for 'function|help' command",
5916            ),
5917            (
5918                &[b"DELETE"],
5919                "ERR wrong number of arguments for 'function|delete' command",
5920            ),
5921            (
5922                &[b"DELETE", b"a", b"b"],
5923                "ERR wrong number of arguments for 'function|delete' command",
5924            ),
5925            (
5926                &[b"DUMP", b"X"],
5927                "ERR wrong number of arguments for 'function|dump' command",
5928            ),
5929            (
5930                &[b"RESTORE"],
5931                "ERR wrong number of arguments for 'function|restore' command",
5932            ),
5933            // RESTORE is the other one that falls through to the generic
5934            // sentence, and for the same reason FLUSH does.
5935            (
5936                &[b"RESTORE", b"a", b"FLUSH", b"X"],
5937                "ERR unknown subcommand or wrong number of arguments for 'RESTORE'. \
5938                 Try FUNCTION HELP.",
5939            ),
5940            (
5941                &[b"RESTORE", b"a", b"ZZ"],
5942                "ERR Wrong restore policy given, value should be either FLUSH, APPEND \
5943                 or REPLACE.",
5944            ),
5945            // FLUSH is the one that does not, because it checks the count
5946            // itself before it looks at the argument.
5947            (
5948                &[b"FLUSH", b"SYNC", b"X"],
5949                "ERR unknown subcommand or wrong number of arguments for 'FLUSH'. \
5950                 Try FUNCTION HELP.",
5951            ),
5952            (
5953                &[b"FLUSH", b"ZZ"],
5954                "ERR FUNCTION FLUSH only supports SYNC|ASYNC option",
5955            ),
5956            (&[b"ZZ"], "ERR unknown subcommand 'ZZ'. Try FUNCTION HELP."),
5957        ] {
5958            let mut wire: Vec<&[u8]> = vec![b"FUNCTION"];
5959            wire.extend_from_slice(args);
5960            assert_eq!(f.run(&wire), format!("-{want}\r\n"), "{args:?}");
5961        }
5962        assert_eq!(
5963            f.run(&[b"FUNCTION"]),
5964            "-ERR wrong number of arguments for 'function' command\r\n"
5965        );
5966        assert_eq!(
5967            f.run(&[b"FUNCTION", b"KILL"]),
5968            "-NOTBUSY No scripts in execution right now.\r\n"
5969        );
5970    }
5971
5972    /// The two ends of the same pipe, so they are tested as one.
5973    ///
5974    /// An empty server dumps ten bytes rather than nothing, because the footer
5975    /// is there whether or not a library is in front of it, and restoring those
5976    /// ten bytes is a working no op.
5977    #[test]
5978    fn a_library_survives_a_dump_and_a_restore() {
5979        let mut f = Fixture::new();
5980        let empty = payload(&f.raw(&[b"FUNCTION", b"DUMP"]));
5981        assert_eq!(empty.len(), 10);
5982        assert_eq!(f.run(&[b"FUNCTION", b"RESTORE", &empty]), "+OK\r\n");
5983
5984        assert_eq!(f.run(&[b"FUNCTION", b"LOAD", LIB]), "$5\r\nmylib\r\n");
5985        let full = payload(&f.raw(&[b"FUNCTION", b"DUMP"]));
5986        assert!(full.len() > empty.len());
5987
5988        // The default policy is APPEND, so restoring onto the library the
5989        // payload came from is a name collision and not a quiet replacement.
5990        assert_eq!(
5991            f.run(&[b"FUNCTION", b"RESTORE", &full]),
5992            "-ERR Library mylib already exists\r\n"
5993        );
5994        assert_eq!(
5995            f.run(&[b"FUNCTION", b"RESTORE", &full, b"REPLACE"]),
5996            "+OK\r\n"
5997        );
5998        assert_eq!(
5999            f.run(&[b"FUNCTION", b"RESTORE", &full, b"FLUSH"]),
6000            "+OK\r\n"
6001        );
6002        // Whichever way it went back, the functions in it still run.
6003        assert_eq!(f.run(&[b"FCALL", b"ping", b"0"]), "$4\r\npong\r\n");
6004
6005        // FLUSH keeps only what the payload held, so a library that was there
6006        // and is not in the payload is gone.
6007        assert_eq!(f.run(&[b"FUNCTION", b"LOAD", OTHER]), "$5\r\nother\r\n");
6008        assert_eq!(
6009            f.run(&[b"FUNCTION", b"RESTORE", &full, b"FLUSH"]),
6010            "+OK\r\n"
6011        );
6012        assert_eq!(
6013            f.run(&[b"FUNCTION", b"DELETE", b"other"]),
6014            "-ERR Library not found\r\n"
6015        );
6016    }
6017
6018    /// A payload that is going to be refused has to leave the server alone.
6019    ///
6020    /// Every one of these is refused for a different reason and at a different
6021    /// depth, from bytes that are not a payload at all down to a library that
6022    /// compiles and then collides, and the library that was already there has to
6023    /// still be there afterwards in every case.
6024    #[test]
6025    fn a_restore_that_fails_changes_nothing() {
6026        let mut f = Fixture::new();
6027        assert_eq!(f.run(&[b"FUNCTION", b"LOAD", LIB]), "$5\r\nmylib\r\n");
6028        let good = payload(&f.raw(&[b"FUNCTION", b"DUMP"]));
6029
6030        // Put the footer back on, so that each of these is refused for the
6031        // reason it is meant to be testing rather than for a checksum the edit
6032        // broke on the way.
6033        let reseal = |body: &[u8], version: u16| {
6034            let mut out = body.to_vec();
6035            out.extend_from_slice(&version.to_le_bytes());
6036            let crc = yo_common::crc::crc64(0, &out);
6037            out.extend_from_slice(&crc.to_le_bytes());
6038            out
6039        };
6040        let body = &good[..good.len() - 10];
6041
6042        let mut torn = good.clone();
6043        let n = torn.len();
6044        torn[n - 1] ^= 0xff;
6045        let future = reseal(body, 999);
6046        // The opcode in front of the one library, changed to the one the 7.0
6047        // release candidates wrote and then to one that is not a library at all.
6048        let mut pre_ga = body.to_vec();
6049        pre_ga[0] = 246;
6050        let pre_ga = reseal(&pre_ga, yo_kv::rdb::VERSION);
6051        let mut other = body.to_vec();
6052        other[0] = 0;
6053        let other = reseal(&other, yo_kv::rdb::VERSION);
6054        // A library whose length says there is more of it than there is.
6055        let mut cut = body.to_vec();
6056        cut.truncate(body.len() - 1);
6057        let cut = reseal(&cut, yo_kv::rdb::VERSION);
6058
6059        for (bytes, want) in [
6060            (vec![], "ERR DUMP payload version or checksum are wrong"),
6061            (
6062                b"0123456789".to_vec(),
6063                "ERR DUMP payload version or checksum are wrong",
6064            ),
6065            (torn, "ERR DUMP payload version or checksum are wrong"),
6066            (future, "ERR DUMP payload version or checksum are wrong"),
6067            (pre_ga, "ERR Pre-GA function format not supported"),
6068            (other, "ERR given type is not a function"),
6069            (cut, "ERR Failed loading library payload"),
6070        ] {
6071            assert_eq!(
6072                f.run(&[b"FUNCTION", b"RESTORE", &bytes]),
6073                format!("-{want}\r\n")
6074            );
6075        }
6076
6077        // Still exactly the one library, and it still runs.
6078        assert_eq!(f.run(&[b"FCALL", b"ping", b"0"]), "$4\r\npong\r\n");
6079        let again = payload(&f.raw(&[b"FUNCTION", b"DUMP"]));
6080        assert_eq!(again, good);
6081    }
6082
6083    /// A REPLACE takes a library's name off another library and still refuses to
6084    /// take a function name off one it is leaving alone.
6085    #[test]
6086    fn a_restore_will_not_take_a_function_name_off_a_library_it_keeps() {
6087        let mut f = Fixture::new();
6088        assert_eq!(f.run(&[b"FUNCTION", b"LOAD", LIB]), "$5\r\nmylib\r\n");
6089        let full = payload(&f.raw(&[b"FUNCTION", b"DUMP"]));
6090        // A second library registering the name the payload's library uses.
6091        let clash =
6092            b"#!lua name=cl\nredis.register_function('ping', function() return 'other' end)"
6093                .as_slice();
6094        assert_eq!(f.run(&[b"FUNCTION", b"FLUSH"]), "+OK\r\n");
6095        assert_eq!(f.run(&[b"FUNCTION", b"LOAD", clash]), "$2\r\ncl\r\n");
6096        assert_eq!(
6097            f.run(&[b"FUNCTION", b"RESTORE", &full, b"REPLACE"]),
6098            "-ERR Function ping already exists\r\n"
6099        );
6100        // Untouched, so the name still belongs to the library that had it.
6101        assert_eq!(f.run(&[b"FCALL", b"ping", b"0"]), "$5\r\nother\r\n");
6102    }
6103
6104    #[test]
6105    fn command_getkeys_reads_the_key_count_out_of_a_script_call() {
6106        let mut f = Fixture::new();
6107        assert_eq!(
6108            f.run(&[b"COMMAND", b"GETKEYS", b"EVAL", b"return 1", b"1", b"k"]),
6109            "*1\r\n$1\r\nk\r\n"
6110        );
6111        assert_eq!(
6112            f.run(&[
6113                b"COMMAND", b"GETKEYS", b"EVALSHA", b"abc", b"2", b"k1", b"k2"
6114            ]),
6115            "*2\r\n$2\r\nk1\r\n$2\r\nk2\r\n"
6116        );
6117        // None is a real answer for a script and the arguments past the count
6118        // are not keys, so they are not listed.
6119        assert_eq!(
6120            f.run(&[b"COMMAND", b"GETKEYS", b"EVAL_RO", b"return 1", b"0", b"a"]),
6121            "*0\r\n"
6122        );
6123        // A count that makes no sense finds no keys rather than being an error,
6124        // which is what a real server's key spec does with it.
6125        assert_eq!(
6126            f.run(&[b"COMMAND", b"GETKEYS", b"EVAL", b"return 1", b"3", b"k"]),
6127            "*0\r\n"
6128        );
6129        assert_eq!(
6130            f.run(&[b"COMMAND", b"GETKEYS", b"EVAL", b"return 1", b"-1"]),
6131            "*0\r\n"
6132        );
6133        assert_eq!(
6134            f.run(&[b"COMMAND", b"GETKEYS", b"EVAL", b"return 1", b"abc"]),
6135            "*0\r\n"
6136        );
6137        // The count itself has to be there, and that is an arity question.
6138        assert_eq!(
6139            f.run(&[b"COMMAND", b"GETKEYS", b"EVAL", b"return 1"]),
6140            "-ERR Invalid number of arguments specified for command\r\n"
6141        );
6142    }
6143
6144    #[test]
6145    fn the_helpers_on_the_redis_table_answer_the_way_they_are_documented() {
6146        let mut f = Fixture::new();
6147        let eval = |f: &mut Fixture, body: &[u8]| f.run(&[b"EVAL", body, b"0"]);
6148
6149        assert_eq!(
6150            eval(&mut f, b"return redis.sha1hex('')"),
6151            "$40\r\nda39a3ee5e6b4b0d3255bfef95601890afd80709\r\n"
6152        );
6153        assert_eq!(
6154            eval(&mut f, b"return redis.sha1hex('return 1')"),
6155            "$40\r\ne0e1f9fabfc9d4800c877a703b823ac0578ff8db\r\n"
6156        );
6157        // A message with no space in it gets the generic code in front, and one
6158        // that already looks like a coded error is left alone.
6159        assert_eq!(
6160            eval(&mut f, b"return redis.error_reply('boom')"),
6161            "-ERR boom\r\n"
6162        );
6163        assert_eq!(
6164            eval(&mut f, b"return redis.error_reply('WRONGTYPE nope')"),
6165            "-WRONGTYPE nope\r\n"
6166        );
6167        assert_eq!(
6168            eval(&mut f, b"return redis.status_reply('fine')"),
6169            "+fine\r\n"
6170        );
6171        // Neither of them raises when it is called wrongly, they answer a value
6172        // that is an error, which is a difference a script can see.
6173        assert_eq!(
6174            eval(&mut f, b"return redis.error_reply(1)"),
6175            "-ERR wrong number or type of arguments\r\n"
6176        );
6177        assert_eq!(
6178            eval(&mut f, b"local x = redis.status_reply() return x.err"),
6179            "$37\r\nERR wrong number or type of arguments\r\n"
6180        );
6181
6182        // The constants a script branches on.
6183        assert_eq!(
6184            eval(
6185                &mut f,
6186                b"return redis.LOG_DEBUG .. redis.LOG_VERBOSE .. redis.LOG_NOTICE .. redis.LOG_WARNING"
6187            ),
6188            "$4\r\n0123\r\n"
6189        );
6190        assert_eq!(
6191            eval(
6192                &mut f,
6193                b"return redis.REPL_NONE .. redis.REPL_AOF .. redis.REPL_SLAVE .. redis.REPL_REPLICA .. redis.REPL_ALL"
6194            ),
6195            "$5\r\n01223\r\n"
6196        );
6197        // The calls that exist so an old script keeps working.
6198        assert_eq!(eval(&mut f, b"return redis.replicate_commands()"), ":1\r\n");
6199        assert_eq!(
6200            eval(&mut f, b"redis.set_repl(redis.REPL_ALL) return 1"),
6201            ":1\r\n"
6202        );
6203        assert_eq!(
6204            eval(&mut f, b"redis.log(redis.LOG_WARNING, 'x') return 1"),
6205            ":1\r\n"
6206        );
6207        assert_eq!(
6208            eval(&mut f, b"return redis.acl_check_cmd('get', 'k')"),
6209            ":1\r\n"
6210        );
6211        // Each of those checks its arguments the way a real server does.
6212        assert!(eval(&mut f, b"redis.setresp(4)").contains("RESP version must be 2 or 3."),);
6213        assert!(eval(&mut f, b"redis.set_repl(9)").contains("Invalid replication flags."));
6214        assert!(
6215            eval(&mut f, b"redis.log('x', 'y')")
6216                .contains("First argument must be a number (log level)."),
6217        );
6218        assert!(
6219            eval(&mut f, b"return redis.acl_check_cmd('nosuchcmd')")
6220                .contains("Invalid command passed to redis.acl_check_cmd()"),
6221        );
6222        assert!(
6223            eval(&mut f, b"return redis.acl_check_cmd('get')")
6224                .contains("Wrong number of args for redis.acl_check_cmd()"),
6225        );
6226    }
6227
6228    #[test]
6229    fn a_counter_is_an_integer_and_not_a_string_of_digits() {
6230        let mut f = Fixture::new();
6231        assert_eq!(f.run(&[b"INCR", b"c"]), ":1\r\n");
6232        assert_eq!(f.run(&[b"INCRBY", b"c", b"41"]), ":42\r\n");
6233        assert_eq!(f.run(&[b"DECRBY", b"c", b"2"]), ":40\r\n");
6234        // Read back as a string it is still an integer, written out as digits
6235        // only because somebody asked for them.
6236        assert_eq!(f.run(&[b"GET", b"c"]), "$2\r\n40\r\n");
6237        assert_eq!(f.run(&[b"INCRBYFLOAT", b"c", b"0.5"]), "$4\r\n40.5\r\n");
6238        // A counter that is not a number is the error the store raises and this
6239        // layer only spells, which is the whole point of the split.
6240        f.run(&[b"SET", b"k", b"hello"]);
6241        assert_eq!(
6242            f.run(&[b"INCR", b"k"]),
6243            "-ERR value is not an integer or out of range\r\n"
6244        );
6245        assert_eq!(
6246            f.run(&[b"INCRBYFLOAT", b"c", b"inf"]),
6247            "-ERR increment would produce NaN or Infinity\r\n"
6248        );
6249    }
6250
6251    /// Every one of these was read off a running 8.8. They are the answers a
6252    /// client library's own test suite checks, and the shapes are not
6253    /// guessable: `DIGEST` is hexadecimal in a bulk string, `MSETEX` is an
6254    /// integer, `INCREX` is a pair.
6255    #[test]
6256    fn the_newer_commands_reply_in_the_shapes_a_real_server_sends() {
6257        let mut f = Fixture::new();
6258        assert_eq!(f.run(&[b"SET", b"k", b"hello"]), "+OK\r\n");
6259        // The same digest a real 8.8 answers for the same five bytes, which is
6260        // what makes `IFDEQ` usable against a mixed deployment.
6261        assert_eq!(f.run(&[b"DIGEST", b"k"]), "$16\r\n9555e8555c62dcfd\r\n");
6262        assert_eq!(f.run(&[b"DIGEST", b"nosuch"]), "$-1\r\n");
6263        assert_eq!(f.run(&[b"MSETEX", b"1", b"a", b"1"]), ":1\r\n");
6264        assert_eq!(f.run(&[b"MSETEX", b"1", b"a", b"2", b"NX"]), ":0\r\n");
6265        assert_eq!(f.run(&[b"GET", b"a"]), "$1\r\n1\r\n");
6266        assert_eq!(f.run(&[b"INCREX", b"n"]), "*2\r\n:1\r\n:1\r\n");
6267        assert_eq!(
6268            f.run(&[b"INCREX", b"n", b"BYINT", b"5", b"UBOUND", b"3"]),
6269            "*2\r\n:1\r\n:0\r\n",
6270            "a refused increment reports the value it left alone and applied nothing"
6271        );
6272        assert_eq!(
6273            f.run(&[
6274                b"INCREX",
6275                b"n",
6276                b"BYINT",
6277                b"5",
6278                b"UBOUND",
6279                b"3",
6280                b"SATURATE"
6281            ]),
6282            "*2\r\n:3\r\n:2\r\n"
6283        );
6284        assert_eq!(f.run(&[b"DELEX", b"a", b"IFEQ", b"2"]), ":0\r\n");
6285        assert_eq!(f.run(&[b"DELEX", b"a", b"IFEQ", b"1"]), ":1\r\n");
6286    }
6287
6288    #[test]
6289    fn the_same_answers_come_out_in_resp3_spelling() {
6290        let mut f = Fixture::new();
6291        assert!(f.run(&[b"HELLO", b"3"]).starts_with("%7\r\n"));
6292        assert_eq!(f.run(&[b"GET", b"nosuch"]), "_\r\n");
6293        // A float counter is a double on RESP3 and the digits in a bulk string
6294        // on RESP2, and `INCRBYFLOAT` is a bulk string on both.
6295        assert_eq!(
6296            f.run(&[b"INCREX", b"c", b"BYFLOAT", b"1.5"]),
6297            "*2\r\n,1.5\r\n,1.5\r\n"
6298        );
6299        assert_eq!(f.run(&[b"INCRBYFLOAT", b"f", b"2.5"]), "$3\r\n2.5\r\n");
6300        // `RESET` puts the protocol back, which is the part that is easy to
6301        // miss and leaves a pooled connection speaking the wrong one.
6302        assert_eq!(f.run(&[b"RESET"]), "+RESET\r\n");
6303        assert_eq!(f.run(&[b"GET", b"nosuch"]), "$-1\r\n");
6304    }
6305
6306    #[test]
6307    fn a_command_nobody_has_heard_of_is_an_error_and_not_a_closed_socket() {
6308        let mut f = Fixture::new();
6309        let (flow, reply) = f.flow(&[b"NOPE", b"a", b"b"]);
6310        assert_eq!(flow, Flow::Continue);
6311        assert_eq!(
6312            reply,
6313            "-ERR unknown command 'NOPE', with args beginning with: 'a' 'b' \r\n"
6314        );
6315        // A name with a line ending in it cannot write its own frame into the
6316        // stream, which is the reason the error writer maps them to spaces.
6317        let reply = f.run(&[b"NO\r\n+PONG\r\nPE"]);
6318        assert_eq!(reply.matches("\r\n").count(), 1);
6319    }
6320
6321    #[test]
6322    fn arity_is_checked_before_the_command_is() {
6323        let mut f = Fixture::new();
6324        assert_eq!(
6325            f.run(&[b"GET"]),
6326            "-ERR wrong number of arguments for 'get' command\r\n"
6327        );
6328        assert_eq!(
6329            f.run(&[b"MSET", b"k"]),
6330            "-ERR wrong number of arguments for 'mset' command\r\n"
6331        );
6332        // The table says `PING` takes one or more and a real server then
6333        // refuses three, which is the sort of thing that only shows up against
6334        // the real thing.
6335        assert_eq!(
6336            f.run(&[b"PING", b"a", b"b"]),
6337            "-ERR wrong number of arguments for 'ping' command\r\n"
6338        );
6339        assert_eq!(f.run(&[b"PING"]), "+PONG\r\n");
6340        assert_eq!(f.run(&[b"PING", b"hi"]), "$2\r\nhi\r\n");
6341        // `DELEX` takes two or four and nothing between.
6342        assert_eq!(
6343            f.run(&[b"DELEX", b"k", b"IFEQ"]),
6344            "-ERR wrong number of arguments for 'delex' command\r\n"
6345        );
6346    }
6347
6348    /// The option rules, all of them measured against 8.8 rather than read off
6349    /// the documentation. The surprising one is that `SET` accepts the same
6350    /// keyword twice and `INCREX` does not.
6351    #[test]
6352    fn the_option_combinations_are_the_ones_a_real_server_accepts() {
6353        let mut f = Fixture::new();
6354        let syntax = "-ERR syntax error\r\n";
6355        assert_eq!(f.run(&[b"SET", b"k", b"v", b"NX", b"XX"]), syntax);
6356        assert_eq!(f.run(&[b"SET", b"k", b"v", b"NX", b"IFEQ", b"a"]), syntax);
6357        assert_eq!(
6358            f.run(&[b"SET", b"k", b"v", b"KEEPTTL", b"EX", b"5"]),
6359            syntax
6360        );
6361        assert_eq!(
6362            f.run(&[b"SET", b"k", b"v", b"EX", b"5", b"PX", b"5"]),
6363            syntax
6364        );
6365        assert_eq!(f.run(&[b"SET", b"k", b"v", b"PERSIST"]), syntax);
6366        // Twice is fine, and the last one wins.
6367        assert_eq!(
6368            f.run(&[b"SET", b"k", b"v", b"EX", b"5", b"EX", b"100"]),
6369            "+OK\r\n"
6370        );
6371        assert_eq!(f.run(&[b"SET", b"k", b"v", b"XX", b"XX"]), "+OK\r\n");
6372        assert_eq!(f.run(&[b"SET", b"k", b"v", b"GET", b"GET"]), "$1\r\nv\r\n");
6373        // `INCREX` refuses what `SET` allows.
6374        assert_eq!(
6375            f.run(&[b"INCREX", b"n", b"BYINT", b"1", b"BYINT", b"2"]),
6376            syntax
6377        );
6378        assert_eq!(
6379            f.run(&[b"INCREX", b"n", b"ENX"]),
6380            "-ERR ENX flag requires an expiration\r\n"
6381        );
6382        assert_eq!(
6383            f.run(&[b"INCREX", b"n", b"UBOUND", b"abc"]),
6384            "-ERR UBOUND is not an integer or out of range\r\n"
6385        );
6386        assert_eq!(
6387            f.run(&[b"INCREX", b"n", b"LBOUND", b"10", b"UBOUND", b"5"]),
6388            "-ERR LBOUND can't be greater than UBOUND\r\n"
6389        );
6390        assert_eq!(
6391            f.run(&[b"LCS", b"a", b"b", b"LEN", b"IDX"]),
6392            "-ERR If you want both the length and indexes, please just use IDX.\r\n"
6393        );
6394    }
6395
6396    /// Where the expiration rules bite. The one worth the test is `GETEX` on a
6397    /// key that is not there, which answers null without ever looking at the
6398    /// expiration it was given.
6399    #[test]
6400    fn the_expiry_rules_are_redis_own() {
6401        let mut f = Fixture::new();
6402        let bad = "-ERR invalid expire time in 'set' command\r\n";
6403        assert_eq!(f.run(&[b"SET", b"k", b"v", b"EX", b"0"]), bad);
6404        assert_eq!(f.run(&[b"SET", b"k", b"v", b"EX", b"-1"]), bad);
6405        assert_eq!(f.run(&[b"SET", b"k", b"v", b"EXAT", b"0"]), bad);
6406        assert_eq!(
6407            f.run(&[b"SET", b"k", b"v", b"EX", b"9999999999999999"]),
6408            bad
6409        );
6410        assert_eq!(
6411            f.run(&[b"SET", b"k", b"v", b"PX", b"99999999999999999999"]),
6412            "-ERR value is not an integer or out of range\r\n"
6413        );
6414        assert_eq!(
6415            f.run(&[b"SETEX", b"k", b"0", b"v"]),
6416            "-ERR invalid expire time in 'setex' command\r\n"
6417        );
6418        assert_eq!(f.run(&[b"GETEX", b"nosuch", b"EX", b"0"]), "$-1\r\n");
6419        assert_eq!(f.run(&[b"GETEX", b"nosuch", b"EX", b"abc"]), "$-1\r\n");
6420        assert_eq!(
6421            f.run(&[b"GETEX", b"nosuch", b"KEEPTTL"]),
6422            "-ERR syntax error\r\n",
6423            "the option list is still checked before the key is looked up"
6424        );
6425        // A deadline in the past is accepted and the key goes with it.
6426        assert_eq!(f.run(&[b"SET", b"k", b"v"]), "+OK\r\n");
6427        assert_eq!(f.run(&[b"SET", b"k", b"v", b"EXAT", b"1"]), "+OK\r\n");
6428        assert_eq!(f.run(&[b"GET", b"k"]), "$-1\r\n");
6429    }
6430
6431    #[test]
6432    fn mset_takes_its_pairs_from_the_read_buffer() {
6433        let mut f = Fixture::new();
6434        assert_eq!(f.run(&[b"MSET", b"a", b"1", b"b", b"2"]), "+OK\r\n");
6435        assert_eq!(
6436            f.run(&[b"MGET", b"a", b"b", b"nosuch"]),
6437            "*3\r\n$1\r\n1\r\n$1\r\n2\r\n$-1\r\n"
6438        );
6439        assert_eq!(f.run(&[b"MSETNX", b"b", b"9", b"c", b"3"]), ":0\r\n");
6440        assert_eq!(f.run(&[b"MSETNX", b"c", b"3", b"d", b"4"]), ":1\r\n");
6441        assert_eq!(
6442            f.run(&[b"MSETEX", b"2", b"e", b"5"]),
6443            "-ERR wrong number of key-value pairs\r\n"
6444        );
6445        assert_eq!(
6446            f.run(&[b"MSETEX", b"0", b"e", b"5"]),
6447            "-ERR invalid numkeys value\r\n"
6448        );
6449        assert_eq!(
6450            f.run(&[b"MSETEX", b"abc", b"e", b"5"]),
6451            "-ERR invalid numkeys value\r\n"
6452        );
6453    }
6454
6455    #[test]
6456    fn lcs_answers_the_length_the_string_and_the_runs() {
6457        let mut f = Fixture::new();
6458        f.run(&[b"MSET", b"a", b"ohmytext", b"b", b"mynewtext"]);
6459        assert_eq!(f.run(&[b"LCS", b"a", b"b"]), "$6\r\nmytext\r\n");
6460        assert_eq!(f.run(&[b"LCS", b"a", b"b", b"LEN"]), ":6\r\n");
6461        assert_eq!(
6462            f.run(&[b"LCS", b"a", b"b", b"IDX", b"MINMATCHLEN", b"4"]),
6463            "*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"
6464        );
6465        // Without `IDX` the two options that only mean something with it are
6466        // accepted and ignored, which is what a real server does.
6467        assert_eq!(
6468            f.run(&[b"LCS", b"a", b"b", b"MINMATCHLEN", b"4", b"WITHMATCHLEN"]),
6469            "$6\r\nmytext\r\n"
6470        );
6471    }
6472
6473    #[test]
6474    fn select_moves_the_connection_and_the_databases_stay_apart() {
6475        let mut f = Fixture::new();
6476        f.run(&[b"SET", b"k", b"zero"]);
6477        assert_eq!(f.run(&[b"SELECT", b"4"]), "+OK\r\n");
6478        assert_eq!(f.run(&[b"GET", b"k"]), "$-1\r\n");
6479        f.run(&[b"SET", b"k", b"four"]);
6480        assert_eq!(f.run(&[b"SELECT", b"0"]), "+OK\r\n");
6481        assert_eq!(f.run(&[b"GET", b"k"]), "$4\r\nzero\r\n");
6482        assert_eq!(
6483            f.run(&[b"SELECT", b"99"]),
6484            "-ERR DB index is out of range\r\n"
6485        );
6486        assert_eq!(
6487            f.run(&[b"SELECT", b"-1"]),
6488            "-ERR DB index is out of range\r\n"
6489        );
6490        assert_eq!(
6491            f.run(&[b"SELECT", b"abc"]),
6492            "-ERR value is not an integer or out of range\r\n"
6493        );
6494        // `RESET` brings it back to zero.
6495        f.run(&[b"SELECT", b"4"]);
6496        f.run(&[b"RESET"]);
6497        assert_eq!(f.run(&[b"GET", b"k"]), "$4\r\nzero\r\n");
6498    }
6499
6500    #[test]
6501    fn hello_agrees_on_a_protocol_and_refuses_the_ones_that_do_not_exist() {
6502        let mut f = Fixture::new();
6503        let reply = f.run(&[b"HELLO"]);
6504        assert!(reply.starts_with("*14\r\n"), "{reply}");
6505        assert!(reply.contains("$5\r\nredis\r\n"), "{reply}");
6506        assert!(reply.contains("$5\r\n8.8.0\r\n"), "{reply}");
6507        assert!(
6508            reply.contains(":7\r\n"),
6509            "the connection id is in there: {reply}"
6510        );
6511        assert_eq!(
6512            f.run(&[b"HELLO", b"4"]),
6513            "-NOPROTO unsupported protocol version\r\n"
6514        );
6515        assert_eq!(
6516            f.run(&[b"HELLO", b"abc"]),
6517            "-ERR Protocol version is not an integer or out of range\r\n"
6518        );
6519        assert_eq!(
6520            f.run(&[b"HELLO", b"3", b"SETNAME"]),
6521            "-ERR Syntax error in HELLO option 'SETNAME'\r\n"
6522        );
6523        assert!(
6524            f.run(&[b"HELLO", b"3", b"SETNAME", b"bob"])
6525                .starts_with("%7\r\n")
6526        );
6527        assert_eq!(f.session.name(), b"bob");
6528        f.run(&[b"RESET"]);
6529        assert_eq!(f.session.name(), b"");
6530    }
6531
6532    #[test]
6533    fn command_describes_this_server_in_the_shape_a_driver_reads() {
6534        let mut f = Fixture::new();
6535        let count = format!(":{}\r\n", COMMANDS.len());
6536        assert_eq!(f.run(&[b"COMMAND", b"COUNT"]), count);
6537        let info = f.run(&[b"COMMAND", b"INFO", b"get"]);
6538        assert_eq!(
6539            info,
6540            "*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\
6541             *3\r\n+@read\r\n+@string\r\n+@fast\r\n*0\r\n*0\r\n*0\r\n"
6542        );
6543        // A null in the list, and the plain one: `$-1` and not `*-1`.
6544        assert_eq!(f.run(&[b"COMMAND", b"INFO", b"nosuch"]), "*1\r\n$-1\r\n");
6545        assert_eq!(
6546            f.run(&[b"COMMAND", b"LIST", b"FILTERBY", b"PATTERN", b"getr*"]),
6547            "*1\r\n$8\r\ngetrange\r\n"
6548        );
6549        assert_eq!(
6550            f.run(&[b"COMMAND", b"NOPE"]),
6551            "-ERR unknown subcommand 'NOPE'. Try COMMAND HELP.\r\n"
6552        );
6553    }
6554
6555    /// A cluster aware client asks this question and then routes on the
6556    /// answer, so `MSETEX`, whose keys are not where the table says, is the one
6557    /// that matters.
6558    #[test]
6559    fn command_getkeys_finds_the_keys_including_the_hidden_ones() {
6560        let mut f = Fixture::new();
6561        assert_eq!(
6562            f.run(&[b"COMMAND", b"GETKEYS", b"get", b"k"]),
6563            "*1\r\n$1\r\nk\r\n"
6564        );
6565        assert_eq!(
6566            f.run(&[b"COMMAND", b"GETKEYS", b"mset", b"a", b"1", b"b", b"2"]),
6567            "*2\r\n$1\r\na\r\n$1\r\nb\r\n"
6568        );
6569        assert_eq!(
6570            f.run(&[
6571                b"COMMAND", b"GETKEYS", b"msetex", b"2", b"a", b"1", b"b", b"2"
6572            ]),
6573            "*2\r\n$1\r\na\r\n$1\r\nb\r\n"
6574        );
6575        assert_eq!(
6576            f.run(&[b"COMMAND", b"GETKEYS", b"ping"]),
6577            "-ERR The command has no key arguments\r\n"
6578        );
6579        assert_eq!(
6580            f.run(&[b"COMMAND", b"GETKEYS", b"set"]),
6581            "-ERR Invalid number of arguments specified for command\r\n"
6582        );
6583    }
6584
6585    #[test]
6586    fn config_answers_what_it_can_and_refuses_what_it_cannot() {
6587        let mut f = Fixture::new();
6588        assert_eq!(
6589            f.run(&[b"CONFIG", b"GET", b"maxmemory"]),
6590            "*2\r\n$9\r\nmaxmemory\r\n$1\r\n0\r\n"
6591        );
6592        // A pattern matches more than one, and a setting two patterns both ask
6593        // for is still sent once.
6594        let both = f.run(&[b"CONFIG", b"GET", b"maxmemory*", b"maxmemory"]);
6595        assert!(both.starts_with("*6\r\n"), "{both}");
6596        assert_eq!(f.run(&[b"CONFIG", b"GET", b"nosuch"]), "*0\r\n");
6597        assert_eq!(f.run(&[b"CONFIG", b"SET", b"appendonly", b"no"]), "+OK\r\n");
6598        assert_eq!(
6599            f.run(&[b"CONFIG", b"SET", b"appendonly", b"yes"]),
6600            "-ERR CONFIG SET failed (possibly related to argument 'appendonly') - can't set immutable config\r\n"
6601        );
6602        assert_eq!(
6603            f.run(&[b"CONFIG", b"SET", b"nosuch", b"1"]),
6604            "-ERR Unknown option or number of arguments for CONFIG SET - 'nosuch'\r\n"
6605        );
6606        assert_eq!(
6607            f.run(&[b"CONFIG", b"GET"]),
6608            "-ERR wrong number of arguments for 'config|get' command\r\n"
6609        );
6610        // Too few arguments and an odd number of them are different
6611        // complaints, which is the sort of thing only the real server tells
6612        // you.
6613        assert_eq!(
6614            f.run(&[b"CONFIG", b"SET", b"appendonly"]),
6615            "-ERR wrong number of arguments for 'config|set' command\r\n"
6616        );
6617        assert_eq!(
6618            f.run(&[b"CONFIG", b"SET", b"appendonly", b"no", b"maxmemory"]),
6619            "-ERR syntax error\r\n"
6620        );
6621        assert_eq!(f.run(&[b"CONFIG", b"RESETSTAT"]), "+OK\r\n");
6622        assert_eq!(
6623            f.run(&[b"CONFIG", b"REWRITE"]),
6624            "-ERR The server is running without a config file\r\n"
6625        );
6626    }
6627
6628    #[test]
6629    fn the_eviction_policy_reads_back_what_was_written_to_it() {
6630        let mut f = Fixture::new();
6631        assert_eq!(
6632            f.run(&[b"CONFIG", b"GET", b"maxmemory-policy"]),
6633            "*2\r\n$16\r\nmaxmemory-policy\r\n$10\r\nnoeviction\r\n"
6634        );
6635        assert_eq!(
6636            f.run(&[b"CONFIG", b"SET", b"maxmemory-policy", b"AllKeys-LFU"]),
6637            "+OK\r\n",
6638            "the name is matched without regard to case, like every other one"
6639        );
6640        assert_eq!(
6641            f.run(&[b"CONFIG", b"GET", b"maxmemory-policy"]),
6642            "*2\r\n$16\r\nmaxmemory-policy\r\n$11\r\nallkeys-lfu\r\n"
6643        );
6644        // And INFO agrees with CONFIG, which it did not when it was a literal.
6645        assert!(
6646            f.run(&[b"INFO", b"memory"])
6647                .contains("maxmemory_policy:allkeys-lfu"),
6648            "INFO and CONFIG disagree about the policy"
6649        );
6650        // The refusal names every legal value in the order the real server's
6651        // enum table lists them, because a client comparing the message compares
6652        // the whole string.
6653        assert_eq!(
6654            f.run(&[b"CONFIG", b"SET", b"maxmemory-policy", b"garbage"]),
6655            "-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"
6656        );
6657        // A bad pair leaves the good one in the same command alone, and the
6658        // policy is checked by the same pass that checks the numbers.
6659        assert_eq!(
6660            f.run(&[b"CONFIG", b"GET", b"maxmemory-policy"]),
6661            "*2\r\n$16\r\nmaxmemory-policy\r\n$11\r\nallkeys-lfu\r\n"
6662        );
6663        f.run(&[
6664            b"CONFIG",
6665            b"SET",
6666            b"hash-max-listpack-entries",
6667            b"7",
6668            b"maxmemory-policy",
6669            b"nonsense",
6670        ]);
6671        assert_eq!(
6672            f.run(&[b"CONFIG", b"GET", b"hash-max-listpack-entries"]),
6673            "*2\r\n$25\r\nhash-max-listpack-entries\r\n$3\r\n512\r\n"
6674        );
6675    }
6676
6677    #[test]
6678    fn the_three_eviction_numbers_read_back_too() {
6679        let mut f = Fixture::new();
6680        for (name, default, set) in [
6681            ("maxmemory-samples", "5", "12"),
6682            ("lfu-log-factor", "10", "3"),
6683            ("lfu-decay-time", "1", "60"),
6684        ] {
6685            let get = || {
6686                format!(
6687                    "*2\r\n${}\r\n{name}\r\n${}\r\n{default}\r\n",
6688                    name.len(),
6689                    default.len()
6690                )
6691            };
6692            assert_eq!(f.run(&[b"CONFIG", b"GET", name.as_bytes()]), get());
6693            assert_eq!(
6694                f.run(&[b"CONFIG", b"SET", name.as_bytes(), set.as_bytes()]),
6695                "+OK\r\n"
6696            );
6697            assert_eq!(
6698                f.run(&[b"CONFIG", b"GET", name.as_bytes()]),
6699                format!(
6700                    "*2\r\n${}\r\n{name}\r\n${}\r\n{set}\r\n",
6701                    name.len(),
6702                    set.len()
6703                )
6704            );
6705            // A number that is not a number is refused with the same sentence
6706            // every other number gets, which names the setting the client typed.
6707            assert_eq!(
6708                f.run(&[b"CONFIG", b"SET", name.as_bytes(), b"soon"]),
6709                format!(
6710                    "-ERR CONFIG SET failed (possibly related to argument '{name}') - argument couldn't be parsed into an integer\r\n"
6711                )
6712            );
6713        }
6714    }
6715
6716    #[test]
6717    fn the_memory_limit_reads_back_in_bytes_whatever_the_unit_was() {
6718        let mut f = Fixture::new();
6719        assert_eq!(
6720            f.run(&[b"CONFIG", b"GET", b"maxmemory"]),
6721            "*2\r\n$9\r\nmaxmemory\r\n$1\r\n0\r\n",
6722            "no limit is the default"
6723        );
6724        // The pairing is Redis's and it is a trap: the bare letter is a power of
6725        // ten and the one with the b is a power of two.
6726        for (typed, bytes) in [
6727            (&b"1024"[..], "1024"),
6728            (b"1k", "1000"),
6729            (b"1kb", "1024"),
6730            (b"1M", "1000000"),
6731            (b"1Mb", "1048576"),
6732            (b"1gb", "1073741824"),
6733            (b"100mb", "104857600"),
6734        ] {
6735            assert_eq!(f.run(&[b"CONFIG", b"SET", b"maxmemory", typed]), "+OK\r\n");
6736            assert_eq!(
6737                f.run(&[b"CONFIG", b"GET", b"maxmemory"]),
6738                format!("*2\r\n$9\r\nmaxmemory\r\n${}\r\n{bytes}\r\n", bytes.len()),
6739                "set {}",
6740                String::from_utf8_lossy(typed)
6741            );
6742        }
6743        assert!(
6744            f.run(&[b"INFO", b"memory"]).contains("maxmemory:104857600"),
6745            "the report agrees with the setting"
6746        );
6747
6748        // A unit nobody has heard of, and a negative number, which is not a very
6749        // large one however it is spelled.
6750        for bad in [&b"1tb"[..], b"-1", b"", b"lots"] {
6751            assert_eq!(
6752                f.run(&[b"CONFIG", b"SET", b"maxmemory", bad]),
6753                "-ERR CONFIG SET failed (possibly related to argument 'maxmemory') - argument must be a memory value\r\n",
6754                "refused {}",
6755                String::from_utf8_lossy(bad)
6756            );
6757        }
6758        assert!(
6759            f.run(&[b"INFO", b"memory"]).contains("maxmemory:104857600"),
6760            "and the refusal left the old one alone"
6761        );
6762    }
6763
6764    #[test]
6765    fn a_write_is_refused_when_there_is_no_room_and_nothing_to_evict() {
6766        let mut f = Fixture::new();
6767        f.run(&[b"SET", b"here", b"already"]);
6768        // A byte, which is under what an empty server holds, so nothing this
6769        // command could do would get it under. The default policy is
6770        // `noeviction`, so nothing is what it does.
6771        f.run(&[b"CONFIG", b"SET", b"maxmemory", b"1"]);
6772        assert_eq!(
6773            f.run(&[b"SET", b"k", b"v"]),
6774            "-OOM command not allowed when used memory > 'maxmemory'.\r\n"
6775        );
6776        assert_eq!(
6777            f.run(&[b"LPUSH", b"l", b"v"]),
6778            "-OOM command not allowed when used memory > 'maxmemory'.\r\n"
6779        );
6780        // Reading is allowed, and so is the one thing that would help.
6781        assert_eq!(f.run(&[b"GET", b"here"]), "$7\r\nalready\r\n");
6782        assert_eq!(f.run(&[b"DEL", b"here"]), ":1\r\n");
6783        assert!(f.run(&[b"INFO", b"stats"]).contains("evicted_keys:0"));
6784
6785        // Taking the limit away lets the write through again.
6786        f.run(&[b"CONFIG", b"SET", b"maxmemory", b"0"]);
6787        assert_eq!(f.run(&[b"SET", b"k", b"v"]), "+OK\r\n");
6788    }
6789
6790    /// Not under Miri, for the reason in `filled`: what it is watching is a
6791    /// whole two megabyte segment going back, so the megabytes are the claim
6792    /// and there is no smaller version of it that says the same thing.
6793    #[cfg_attr(miri, ignore = "several megabytes of arena, see `filled`")]
6794    #[test]
6795    fn an_allkeys_policy_makes_room_instead_of_refusing() {
6796        let mut f = Fixture::new();
6797        let val = vec![b'v'; 256];
6798        for i in 0..24000u32 {
6799            let k = format!("key:{i:08}");
6800            f.run(&[b"SET", k.as_bytes(), &val]);
6801        }
6802        let full = f.server.memory_bytes();
6803        assert!(
6804            full > 3 * 1024 * 1024,
6805            "the arena is several segments: {full}"
6806        );
6807
6808        // Two megabytes under what it is holding, which is one segment's worth,
6809        // so getting there means giving a whole segment back and not just
6810        // dropping a few records.
6811        let limit = full - 2 * 1024 * 1024;
6812        f.run(&[b"CONFIG", b"SET", b"maxmemory-policy", b"allkeys-lru"]);
6813        f.run(&[
6814            b"CONFIG",
6815            b"SET",
6816            b"maxmemory",
6817            limit.to_string().as_bytes(),
6818        ]);
6819
6820        // Writes keep working the whole way down. The budget means one command
6821        // does not do it all, so this runs until the server has settled and
6822        // checks that nothing was refused on the way.
6823        for i in 0..2000u32 {
6824            let k = format!("new:{i:08}");
6825            assert_eq!(
6826                f.run(&[b"SET", k.as_bytes(), &val]),
6827                "+OK\r\n",
6828                "write {i} was refused"
6829            );
6830            f.server.refresh_memory();
6831            if f.server.memory_bytes() <= limit {
6832                break;
6833            }
6834        }
6835        assert!(
6836            f.server.memory_bytes() <= limit,
6837            "it never got under: {} against {limit}",
6838            f.server.memory_bytes()
6839        );
6840        let info = f.run(&[b"INFO", b"stats"]);
6841        assert!(!info.contains("evicted_keys:0"), "{info}");
6842        assert!(
6843            f.run(&[b"DBSIZE"]) != ":0\r\n",
6844            "and it did not empty the database to get there"
6845        );
6846    }
6847
6848    /// Not under Miri. Every round is eleven commands over six collections
6849    /// holding two hundred byte values, which is a third of a second each
6850    /// interpreted, and the rounds cannot come down far: one in seven takes an
6851    /// entry back out, so under about a hundred and seventy of them the
6852    /// collections never reach the hundred and twenty eight entries where the
6853    /// small representations give up and become the big ones, and a
6854    /// representation changing under the running total is one of the five
6855    /// things this is here to watch. What is left is an hour, for an accounting
6856    /// claim rather than a safety one, and the commands it sends are sent a few
6857    /// at a time by the tests around it.
6858    #[cfg_attr(miri, ignore = "an hour of commands, and they cannot come down")]
6859    #[test]
6860    fn the_running_total_and_the_walk_agree_on_a_mixed_keyspace() {
6861        // The limit is judged against a number kept as the collections move,
6862        // rather than found by asking all of them, and the two have to be the
6863        // same number or the limit is enforced against a fiction. This does the
6864        // things that move it, which is growing a collection, shrinking one,
6865        // changing its representation, deleting it and reusing its slot, across
6866        // all five types, and checks the two against each other as it goes.
6867        let mut f = Fixture::new();
6868        f.run(&[b"CONFIG", b"SET", b"maxmemory", b"1gb"]);
6869        let big = vec![b'v'; 200];
6870
6871        for i in 0..400u32 {
6872            let n = i.to_string();
6873            let n = n.as_bytes();
6874            f.run(&[b"SADD", b"s", n]);
6875            f.run(&[b"SADD", b"s2", &big]);
6876            f.run(&[b"HSET", b"h", n, &big]);
6877            f.run(&[b"RPUSH", b"l", &big]);
6878            f.run(&[b"ZADD", b"z", n, n]);
6879            f.run(&[b"ARSET", b"a", n, &big]);
6880            if i % 7 == 0 {
6881                f.run(&[b"SREM", b"s", n]);
6882                f.run(&[b"HDEL", b"h", n]);
6883                f.run(&[b"LPOP", b"l"]);
6884                f.run(&[b"ZREM", b"z", n]);
6885                f.run(&[b"ARDEL", b"a", n]);
6886            }
6887            if i % 53 == 0 {
6888                // Every type deleted and made again, so a slot goes on the free
6889                // list and comes back holding something else.
6890                f.run(&[b"DEL", b"s2"]);
6891            }
6892            assert_eq!(
6893                f.server.settled_memory(),
6894                f.server.memory_bytes(),
6895                "after round {i}"
6896            );
6897        }
6898
6899        // The run has to have built something, or the two numbers agreeing is
6900        // two zeroes agreeing.
6901        assert_eq!(f.run(&[b"DBSIZE"]), ":6\r\n");
6902        assert!(
6903            f.server.memory_bytes() > 512 * 1024,
6904            "{}",
6905            f.server.memory_bytes()
6906        );
6907
6908        // And it survives the collections going away entirely.
6909        f.run(&[b"FLUSHALL"]);
6910        assert_eq!(f.server.settled_memory(), f.server.memory_bytes());
6911    }
6912
6913    #[test]
6914    fn taking_the_limit_away_stops_the_counting_and_putting_it_back_starts_again() {
6915        // A server with no limit does not keep the running total, so setting a
6916        // limit on a database that is already full has to start it from a walk.
6917        // If it did not, the first reading would be zero and the server would
6918        // think it had all the room in the world.
6919        let mut f = Fixture::new();
6920        for i in 0..200u32 {
6921            let n = i.to_string();
6922            f.run(&[b"SADD", b"s", n.as_bytes()]);
6923            f.run(&[b"HSET", b"h", n.as_bytes(), b"value"]);
6924        }
6925        f.run(&[b"CONFIG", b"SET", b"maxmemory", b"1gb"]);
6926        assert_eq!(f.server.settled_memory(), f.server.memory_bytes());
6927
6928        f.run(&[b"CONFIG", b"SET", b"maxmemory", b"0"]);
6929        for i in 200..400u32 {
6930            let n = i.to_string();
6931            f.run(&[b"SADD", b"s", n.as_bytes()]);
6932        }
6933        f.run(&[b"CONFIG", b"SET", b"maxmemory", b"1gb"]);
6934        assert_eq!(
6935            f.server.settled_memory(),
6936            f.server.memory_bytes(),
6937            "the writes it was not watching are in the number it started from"
6938        );
6939    }
6940
6941    #[test]
6942    fn evicted_keys_and_expired_keys_are_different_numbers() {
6943        let mut f = Fixture::new();
6944        // Nothing has been evicted and nothing can be under the default policy,
6945        // so this stays at zero while the other one moves.
6946        f.run(&[b"SET", b"gone", b"v", b"PX", b"1"]);
6947        f.server.advance_clock_ms(20);
6948        f.run(&[b"GET", b"gone"]);
6949        let info = f.run(&[b"INFO", b"stats"]);
6950        assert!(info.contains("expired_keys:1"), "{info}");
6951        assert!(info.contains("evicted_keys:0"), "{info}");
6952    }
6953
6954    #[test]
6955    fn the_object_subcommands_follow_the_policy() {
6956        let mut f = Fixture::new();
6957        f.run(&[b"SET", b"s", b"v"]);
6958        // Under the default the clock is kept and the counter is not, and under
6959        // an LFU policy it is the other way round. Each subcommand refuses on
6960        // the side where its reading of the three bytes means nothing.
6961        assert_eq!(f.run(&[b"OBJECT", b"IDLETIME", b"s"]), ":0\r\n");
6962        assert!(
6963            f.run(&[b"OBJECT", b"FREQ", b"s"])
6964                .starts_with("-ERR An LFU maxmemory policy is not selected"),
6965        );
6966
6967        f.run(&[b"CONFIG", b"SET", b"maxmemory-policy", b"allkeys-lfu"]);
6968        assert!(
6969            f.run(&[b"OBJECT", b"IDLETIME", b"s"])
6970                .starts_with("-ERR An LFU maxmemory policy is selected"),
6971        );
6972        // The key was written under a clock policy, so what comes back is that
6973        // clock read as a counter. It is a number and not an error, which is the
6974        // point: switching at runtime does not invalidate anything, it only makes
6975        // the old field mean something else until the key is used again.
6976        assert!(
6977            f.run(&[b"OBJECT", b"FREQ", b"s"]).starts_with(':'),
6978            "FREQ should answer under an LFU policy"
6979        );
6980    }
6981
6982    #[test]
6983    fn object_says_which_rung_of_the_ladder_a_key_is_on() {
6984        let mut f = Fixture::new();
6985        f.run(&[b"SET", b"s", b"hello"]);
6986        f.run(&[b"SET", b"n", b"123"]);
6987        f.run(&[b"SADD", b"si", b"1", b"2", b"3"]);
6988        f.run(&[b"SADD", b"ss", b"a", b"b"]);
6989        f.run(&[b"HSET", b"h", b"f", b"v"]);
6990        for (key, want) in [
6991            (b"s".as_slice(), "embstr"),
6992            (b"n", "int"),
6993            (b"si", "intset"),
6994            (b"ss", "listpack"),
6995            (b"h", "listpack"),
6996        ] {
6997            let reply = f.run(&[b"OBJECT", b"ENCODING", key]);
6998            assert_eq!(reply, format!("${}\r\n{want}\r\n", want.len()));
6999        }
7000
7001        // A field deadline widens the blob rather than promoting it, and this
7002        // is the only place a client can see that happen.
7003        f.run(&[b"HEXPIRE", b"h", b"100", b"FIELDS", b"1", b"f"]);
7004        assert_eq!(
7005            f.run(&[b"OBJECT", b"ENCODING", b"h"]),
7006            "$10\r\nlistpackex\r\n"
7007        );
7008
7009        assert_eq!(f.run(&[b"OBJECT", b"REFCOUNT", b"s"]), ":1\r\n");
7010        assert_eq!(f.run(&[b"OBJECT", b"IDLETIME", b"s"]), ":0\r\n");
7011        assert!(f.run(&[b"OBJECT", b"HELP"]).starts_with("*14\r\n+OBJECT "));
7012    }
7013
7014    #[test]
7015    fn object_answers_nil_for_a_key_that_is_not_there() {
7016        let mut f = Fixture::new();
7017        for sub in [b"ENCODING".as_slice(), b"REFCOUNT", b"IDLETIME", b"FREQ"] {
7018            assert_eq!(
7019                f.run(&[b"OBJECT", sub, b"nokey"]),
7020                "$-1\r\n",
7021                "a nil and not an error, which is what 8.10.1 does"
7022            );
7023        }
7024        // And the key is looked up before FREQ has its complaint, so the
7025        // complaint only reaches a key that exists.
7026        f.run(&[b"SET", b"s", b"v"]);
7027        assert!(
7028            f.run(&[b"OBJECT", b"FREQ", b"s"])
7029                .starts_with("-ERR An LFU maxmemory policy is not"),
7030        );
7031        assert_eq!(
7032            f.run(&[b"OBJECT", b"NOPE", b"s"]),
7033            "-ERR unknown subcommand 'NOPE'. Try OBJECT HELP.\r\n"
7034        );
7035        assert_eq!(
7036            f.run(&[b"OBJECT", b"ENCODING"]),
7037            "-ERR wrong number of arguments for 'object|encoding' command\r\n"
7038        );
7039        assert_eq!(
7040            f.run(&[b"OBJECT", b"ENCODING", b"s", b"extra"]),
7041            "-ERR wrong number of arguments for 'object|encoding' command\r\n"
7042        );
7043        assert_eq!(
7044            f.run(&[b"OBJECT"]),
7045            "-ERR wrong number of arguments for 'object' command\r\n"
7046        );
7047    }
7048
7049    #[test]
7050    fn config_moves_the_ladder_and_object_encoding_agrees() {
7051        let mut f = Fixture::new();
7052        assert_eq!(
7053            f.run(&[b"CONFIG", b"GET", b"hash-max-listpack-entries"]),
7054            "*2\r\n$25\r\nhash-max-listpack-entries\r\n$3\r\n512\r\n",
7055            "512 and not the 128 everyone remembers, which is what 8.10.1 says"
7056        );
7057        // The old spelling is the same number under a different name, and a
7058        // glob that catches both sends both.
7059        assert_eq!(
7060            f.run(&[b"CONFIG", b"GET", b"hash-max-ziplist-entries"]),
7061            "*2\r\n$24\r\nhash-max-ziplist-entries\r\n$3\r\n512\r\n"
7062        );
7063        assert!(
7064            f.run(&[b"CONFIG", b"GET", b"hash-max-*"])
7065                .starts_with("*8\r\n")
7066        );
7067        assert!(
7068            f.run(&[b"CONFIG", b"GET", b"set-max-*"])
7069                .starts_with("*6\r\n")
7070        );
7071
7072        f.run(&[b"HSET", b"h", b"a", b"1", b"b", b"2", b"c", b"3"]);
7073        assert_eq!(f.run(&[b"OBJECT", b"ENCODING", b"h"]), "$8\r\nlistpack\r\n");
7074
7075        assert_eq!(
7076            f.run(&[b"CONFIG", b"SET", b"hash-max-ziplist-entries", b"2"]),
7077            "+OK\r\n",
7078            "written under the old name and read back under the new one"
7079        );
7080        assert_eq!(
7081            f.run(&[b"CONFIG", b"GET", b"hash-max-listpack-entries"]),
7082            "*2\r\n$25\r\nhash-max-listpack-entries\r\n$1\r\n2\r\n"
7083        );
7084        assert_eq!(
7085            f.run(&[b"OBJECT", b"ENCODING", b"h"]),
7086            "$8\r\nlistpack\r\n",
7087            "the hash that already exists is left exactly where it was"
7088        );
7089        f.run(&[b"HSET", b"h2", b"a", b"1", b"b", b"2", b"c", b"3"]);
7090        assert_eq!(
7091            f.run(&[b"OBJECT", b"ENCODING", b"h2"]),
7092            "$9\r\nhashtable\r\n",
7093            "and the next one built goes straight to a table"
7094        );
7095
7096        // The set has three of these and all three move.
7097        f.run(&[b"CONFIG", b"SET", b"set-max-intset-entries", b"2"]);
7098        f.run(&[b"SADD", b"s", b"1", b"2", b"3"]);
7099        assert_eq!(f.run(&[b"OBJECT", b"ENCODING", b"s"]), "$8\r\nlistpack\r\n");
7100        f.run(&[b"CONFIG", b"SET", b"set-max-listpack-value", b"2"]);
7101        f.run(&[b"SADD", b"s2", b"abcdefgh"]);
7102        assert_eq!(
7103            f.run(&[b"OBJECT", b"ENCODING", b"s2"]),
7104            "$9\r\nhashtable\r\n"
7105        );
7106    }
7107
7108    #[test]
7109    fn config_set_takes_all_of_the_ladder_or_none_of_it() {
7110        let mut f = Fixture::new();
7111        assert_eq!(
7112            f.run(&[
7113                b"CONFIG",
7114                b"SET",
7115                b"hash-max-listpack-entries",
7116                b"7",
7117                b"set-max-listpack-entries",
7118                b"abc"
7119            ]),
7120            "-ERR CONFIG SET failed (possibly related to argument 'set-max-listpack-entries') - argument couldn't be parsed into an integer\r\n"
7121        );
7122        assert_eq!(
7123            f.run(&[b"CONFIG", b"GET", b"hash-max-listpack-entries"]),
7124            "*2\r\n$25\r\nhash-max-listpack-entries\r\n$3\r\n512\r\n",
7125            "the pair in front of the bad one did not go in"
7126        );
7127        // The name in the complaint is the one that was typed, so the old
7128        // spelling comes back as the old spelling.
7129        assert_eq!(
7130            f.run(&[b"CONFIG", b"SET", b"hash-max-ziplist-entries", b"abc"]),
7131            "-ERR CONFIG SET failed (possibly related to argument 'hash-max-ziplist-entries') - argument couldn't be parsed into an integer\r\n"
7132        );
7133        assert_eq!(
7134            f.run(&[b"CONFIG", b"SET", b"set-max-intset-entries", b"-1"]),
7135            "-ERR CONFIG SET failed (possibly related to argument 'set-max-intset-entries') - argument must be between 0 and 9223372036854775807 inclusive\r\n"
7136        );
7137        // A number past what an i64 holds is the parse complaint and not the
7138        // range one, which is upstream reading it before it checks it.
7139        assert_eq!(
7140            f.run(&[
7141                b"CONFIG",
7142                b"SET",
7143                b"set-max-intset-entries",
7144                b"99999999999999999999"
7145            ]),
7146            "-ERR CONFIG SET failed (possibly related to argument 'set-max-intset-entries') - argument couldn't be parsed into an integer\r\n"
7147        );
7148        assert_eq!(
7149            f.run(&[
7150                b"CONFIG",
7151                b"SET",
7152                b"set-max-intset-entries",
7153                b"9223372036854775807"
7154            ]),
7155            "+OK\r\n"
7156        );
7157    }
7158
7159    #[test]
7160    fn a_setting_moved_on_one_database_moved_on_all_of_them() {
7161        let mut f = Fixture::new();
7162        f.run(&[b"CONFIG", b"SET", b"hash-max-listpack-entries", b"1"]);
7163        f.run(&[b"SELECT", b"3"]);
7164        f.run(&[b"HSET", b"h", b"a", b"1", b"b", b"2"]);
7165        assert_eq!(
7166            f.run(&[b"OBJECT", b"ENCODING", b"h"]),
7167            "$9\r\nhashtable\r\n",
7168            "these are one server wide number in Redis, whatever a Keyspace carries"
7169        );
7170    }
7171
7172    #[test]
7173    fn info_reports_the_numbers_it_can_stand_behind() {
7174        let mut f = Fixture::new();
7175        f.run(&[b"MSET", b"a", b"1", b"b", b"2"]);
7176        let all = f.run(&[b"INFO"]);
7177        assert!(all.contains("redis_version:8.8.0"), "{all}");
7178        assert!(
7179            all.contains(concat!("yo_version:", env!("CARGO_PKG_VERSION"))),
7180            "{all}"
7181        );
7182        assert!(all.contains("db0:keys=2,expires=0,avg_ttl=0"), "{all}");
7183        assert!(all.contains("role:master"), "{all}");
7184        // One section is one section.
7185        let clients = f.run(&[b"INFO", b"clients"]);
7186        assert!(clients.contains("connected_clients:0"), "{clients}");
7187        assert!(!clients.contains("redis_version"), "{clients}");
7188        assert_eq!(f.run(&[b"INFO", b"nosuch"]), "$0\r\n\r\n");
7189    }
7190
7191    /// The sections a bare `INFO` gives back, and the ones you have to ask for.
7192    ///
7193    /// This is Redis's `unit/info-command` written against the fixture. Every
7194    /// assertion in it is one of theirs, in their order, and the two fields it
7195    /// turns on are the two that suite was failing on: `master_repl_offset`,
7196    /// which is in the default set, and `rejected_calls`, which is not.
7197    #[test]
7198    fn commandstats_is_asked_for_and_replication_is_not() {
7199        let mut f = Fixture::new();
7200        for arg in ["", "all", "default", "everything"] {
7201            let info = if arg.is_empty() {
7202                f.run(&[b"INFO"])
7203            } else {
7204                f.run(&[b"INFO", arg.as_bytes()])
7205            };
7206            assert!(info.contains("redis_version"), "{arg}: {info}");
7207            assert!(info.contains("used_cpu_user"), "{arg}: {info}");
7208            assert!(info.contains("used_memory"), "{arg}: {info}");
7209            assert!(!info.contains("sentinel_tilt"), "{arg}: {info}");
7210            let asked = arg == "all" || arg == "everything";
7211            assert_eq!(
7212                info.contains("rejected_calls"),
7213                asked,
7214                "{arg} should{} carry the command counters: {info}",
7215                if asked { "" } else { " not" }
7216            );
7217        }
7218
7219        let cpu = f.run(&[b"INFO", b"cpu"]);
7220        assert!(cpu.contains("used_cpu_user"), "{cpu}");
7221        assert!(!cpu.contains("used_memory"), "{cpu}");
7222
7223        // Their case, to make the point that a section name is not case
7224        // sensitive any more than a command name is.
7225        let stats = f.run(&[b"INFO", b"commandSTATS"]);
7226        assert!(!stats.contains("used_memory"), "{stats}");
7227        assert!(stats.contains("rejected_calls"), "{stats}");
7228
7229        // Two sections named, and neither of them pulls in a third.
7230        let pair = f.run(&[b"INFO", b"cpu", b"sentinel"]);
7231        assert!(pair.contains("used_cpu_user"), "{pair}");
7232        assert!(!pair.contains("master_repl_offset"), "{pair}");
7233
7234        let with_all = f.run(&[b"INFO", b"cpu", b"all"]);
7235        assert!(with_all.contains("used_memory"), "{with_all}");
7236        assert!(with_all.contains("master_repl_offset"), "{with_all}");
7237        assert!(with_all.contains("rejected_calls"), "{with_all}");
7238        // A section named twice is still written once.
7239        assert_eq!(
7240            with_all.matches("used_cpu_user_children").count(),
7241            1,
7242            "{with_all}"
7243        );
7244
7245        let with_default = f.run(&[b"INFO", b"cpu", b"default"]);
7246        assert!(with_default.contains("used_memory"), "{with_default}");
7247        assert!(
7248            with_default.contains("master_repl_offset"),
7249            "{with_default}"
7250        );
7251        assert!(!with_default.contains("rejected_calls"), "{with_default}");
7252        assert_eq!(
7253            with_default.matches("used_cpu_user_children").count(),
7254            1,
7255            "{with_default}"
7256        );
7257    }
7258
7259    /// The memory section says what this process may use, not what the machine
7260    /// has.
7261    ///
7262    /// The distinction is the whole point of it. A server inside a container
7263    /// that reports the host's memory is a server whose operator sizes it for
7264    /// memory it will be killed for touching, so all three numbers are there:
7265    /// what the machine has, what the cgroup allows, and the quarter of the
7266    /// tighter one that pools are sized from.
7267    #[test]
7268    fn info_memory_reports_the_cap_and_the_quarter_of_it_that_gets_used() {
7269        let mut f = Fixture::new();
7270        let info = f.run(&[b"INFO", b"memory"]);
7271        for field in [
7272            "total_system_memory:",
7273            "mem_cgroup_limit:",
7274            "mem_limit:",
7275            "mem_budget:",
7276        ] {
7277            assert!(info.contains(field), "no {field} in {info}");
7278        }
7279
7280        let field = |name: &str| -> u64 {
7281            info.lines()
7282                .find_map(|l| l.strip_prefix(name))
7283                .unwrap_or_else(|| panic!("no {name} in {info}"))
7284                .trim()
7285                .parse()
7286                .unwrap_or_else(|_| panic!("{name} is not a number in {info}"))
7287        };
7288        let limit = field("mem_limit:");
7289        assert_eq!(field("mem_budget:"), limit / 4, "{info}");
7290        // Zero means there is no limit to report, which is a real answer on a
7291        // machine with no cgroups and no way to ask how big it is.
7292        if limit != 0 {
7293            let host = field("total_system_memory:");
7294            let cgroup = field("mem_cgroup_limit:");
7295            assert!(
7296                limit == host || limit == cgroup,
7297                "the limit came from neither number: {info}"
7298            );
7299        }
7300    }
7301
7302    /// The three counters, each on the path that raises it.
7303    ///
7304    /// `calls` on a command that worked, `failed_calls` on one that ran and
7305    /// answered with an error, and `rejected_calls` on one that never ran at
7306    /// all. The last two are the pair that is easy to collapse into one number
7307    /// and that Redis keeps apart, because a client sending the wrong number of
7308    /// arguments and a client asking for a list element that is not there are
7309    /// not the same problem.
7310    #[test]
7311    fn a_command_counts_what_it_did_separately_from_what_it_refused() {
7312        let mut f = Fixture::new();
7313        f.run(&[b"SET", b"k", b"v"]);
7314        f.run(&[b"SET", b"k", b"w"]);
7315        // Ran, and answered with an error, because `k` is not a list.
7316        f.run(&[b"LPUSH", b"k", b"x"]);
7317        // Never ran: `LPUSH` takes at least three arguments.
7318        f.run(&[b"LPUSH", b"k"]);
7319
7320        let stats = f.run(&[b"INFO", b"commandstats"]);
7321        assert!(
7322            stats.contains("cmdstat_set:calls=2,rejected_calls=0,failed_calls=0"),
7323            "{stats}"
7324        );
7325        assert!(
7326            stats.contains("cmdstat_lpush:calls=1,rejected_calls=1,failed_calls=1"),
7327            "{stats}"
7328        );
7329        assert!(
7330            !stats.contains("cmdstat_zadd"),
7331            "a command nobody has sent has no row: {stats}"
7332        );
7333    }
7334
7335    /// A cache that writes with a deadline and never reads back used to hold
7336    /// every key it had ever written, because lazy expiry needs somebody to walk
7337    /// past a key before it can reclaim it and nobody ever did.
7338    #[test]
7339    fn the_active_sweep_reclaims_keys_no_client_comes_back_for() {
7340        // Four thousand keys is four thousand trips through dispatch, and what
7341        // Miri charges for is trips rather than keys, so this was over five
7342        // minutes there. An eighth of each keeps everything the test is about,
7343        // which is three keys with a deadline for every one without and a
7344        // sweep that has to reclaim all of the first kind and none of the
7345        // second.
7346        let (dead, live) = if cfg!(miri) {
7347            (375, 125)
7348        } else {
7349            (3_000, 1_000)
7350        };
7351        let mut f = Fixture::new();
7352        for i in 0..dead {
7353            f.run(&[b"SET", format!("d{i}").as_bytes(), b"v", b"PX", b"50"]);
7354        }
7355        for i in 0..live {
7356            f.run(&[b"SET", format!("k{i}").as_bytes(), b"v"]);
7357        }
7358        let all = format!(":{}\r\n", dead + live);
7359        assert_eq!(f.run(&[b"DBSIZE"]), all);
7360        f.advance(100);
7361        assert_eq!(
7362            f.run(&[b"DBSIZE"]),
7363            all,
7364            "DBSIZE counts records and nothing has read past the dead ones yet"
7365        );
7366
7367        // What the shard loop does, one slice at a time.
7368        let rest = format!(":{live}\r\n");
7369        let mut spent = 0;
7370        for _ in 0..2_000 {
7371            spent += f.server.expire_step(4096);
7372            if f.run(&[b"DBSIZE"]) == rest {
7373                break;
7374            }
7375        }
7376        assert_eq!(f.run(&[b"DBSIZE"]), rest, "spent {spent} looks");
7377        assert!(
7378            f.run(&[b"INFO", b"stats"])
7379                .contains(&format!("expired_keys:{dead}"))
7380        );
7381        for i in 0..live {
7382            assert_eq!(
7383                f.run(&[b"GET", format!("k{i}").as_bytes()]),
7384                "$1\r\nv\r\n",
7385                "it took a key that had no deadline"
7386            );
7387        }
7388    }
7389
7390    #[test]
7391    fn a_sweep_of_a_server_with_no_deadlines_anywhere_costs_nothing() {
7392        // The keys are only here so that the database the sweep walks is not an
7393        // empty one. Two hundred of them fills as many slots as a sweep looks
7394        // at and is a tenth of the interpreted work.
7395        let n = if cfg!(miri) { 200 } else { 2_000 };
7396        let mut f = Fixture::new();
7397        for i in 0..n {
7398            f.run(&[b"SET", format!("k{i}").as_bytes(), b"v"]);
7399        }
7400        assert_eq!(f.server.expire_step(4096), 0);
7401        // And one database having them does not make the other fifteen pay.
7402        f.run(&[b"SELECT", b"3"]);
7403        f.run(&[b"SET", b"x", b"v", b"PX", b"50"]);
7404        f.advance(100);
7405        for _ in 0..64 {
7406            f.server.expire_step(4096);
7407        }
7408        assert_eq!(f.run(&[b"DBSIZE"]), ":0\r\n");
7409        f.run(&[b"SELECT", b"0"]);
7410        assert_eq!(f.run(&[b"DBSIZE"]), format!(":{n}\r\n"));
7411        assert_eq!(f.server.expire_step(4096), 0, "and it is quiet again");
7412    }
7413
7414    /// The gate, which is what stops a maintenance slice that runs every hundred
7415    /// nanoseconds from drawing a sample every hundred nanoseconds.
7416    #[test]
7417    fn the_sweep_the_loop_calls_runs_at_most_once_a_millisecond() {
7418        let mut f = Fixture::new();
7419        for i in 0..500u32 {
7420            f.run(&[b"SET", format!("d{i}").as_bytes(), b"v", b"PX", b"50"]);
7421        }
7422        f.advance(100);
7423        let at = f.server.striped(0).now_ms();
7424        f.server.set_clock_ms(at);
7425        // A small budget, so that one slice cannot finish the job and a second
7426        // one having nothing to do would mean the gate and not an empty
7427        // database.
7428        assert!(f.server.expire_slice(8) > 0, "the first one works");
7429        for _ in 0..1_000 {
7430            assert_eq!(
7431                f.server.expire_slice(8),
7432                0,
7433                "the millisecond has not moved and neither should this"
7434            );
7435        }
7436        assert!(
7437            f.server.striped(0).expires() > 400,
7438            "there is plenty left to take"
7439        );
7440        f.server.set_clock_ms(at + 1);
7441        assert!(f.server.expire_slice(8) > 0, "and then it goes again");
7442    }
7443
7444    /// `expires=` used to be a hardcoded zero, which meant a dashboard watching
7445    /// how much of a cache is volatile was reading a constant.
7446    #[test]
7447    fn info_keyspace_counts_the_keys_that_have_a_deadline() {
7448        let mut f = Fixture::new();
7449        f.run(&[b"MSET", b"a", b"1", b"b", b"2", b"c", b"3"]);
7450        assert!(
7451            f.run(&[b"INFO", b"keyspace"])
7452                .contains("db0:keys=3,expires=0"),
7453            "none of them has one yet"
7454        );
7455        f.run(&[b"EXPIRE", b"a", b"1000"]);
7456        f.run(&[b"EXPIRE", b"b", b"1000"]);
7457        let two = f.run(&[b"INFO", b"keyspace"]);
7458        assert!(two.contains("db0:keys=3,expires=2"), "{two}");
7459        f.run(&[b"PERSIST", b"a"]);
7460        f.run(&[b"DEL", b"b"]);
7461        let none = f.run(&[b"INFO", b"keyspace"]);
7462        assert!(none.contains("db0:keys=2,expires=0"), "{none}");
7463
7464        // Each database answers for itself, the way Redis reports it.
7465        f.run(&[b"SELECT", b"1"]);
7466        f.run(&[b"SET", b"x", b"1", b"EX", b"1000"]);
7467        let both = f.run(&[b"INFO", b"keyspace"]);
7468        assert!(both.contains("db0:keys=2,expires=0"), "{both}");
7469        assert!(both.contains("db1:keys=1,expires=1"), "{both}");
7470    }
7471
7472    /// Not under Miri, which reads a zero on purpose because it has no
7473    /// `getrusage` to call, so the second half of this would burn a billion
7474    /// interpreted multiplications waiting for a number that is never going to
7475    /// move. The first half, that the section is there and has the fields Redis
7476    /// clients look for, is checked by the `INFO` tests above as well, and
7477    /// those do run there.
7478    #[cfg(unix)]
7479    #[cfg_attr(miri, ignore = "no getrusage under Miri, so the number is fixed")]
7480    #[test]
7481    fn info_cpu_reports_processor_time_that_was_really_measured() {
7482        let mut f = Fixture::new();
7483        let cpu = f.run(&[b"INFO", b"cpu"]);
7484        assert!(cpu.contains("# CPU"), "{cpu}");
7485        // Redis's unit/info-command asks for this one by name in three tests.
7486        assert!(cpu.contains("used_cpu_user:"), "{cpu}");
7487        assert!(cpu.contains("used_cpu_sys:"), "{cpu}");
7488        assert!(cpu.contains("used_cpu_user_children:0.000000"), "{cpu}");
7489        assert!(!cpu.contains("redis_version"), "{cpu}");
7490
7491        // It is a measurement and not a constant, so it goes up when work
7492        // happens. A tight loop rather than a sleep, because sleeping is the
7493        // one thing that does not move this number.
7494        let before = used_cpu_user(&cpu);
7495        let mut n = 0u64;
7496        let mut rounds = 0;
7497        while used_cpu_user(&f.run(&[b"INFO", b"cpu"])) <= before {
7498            for i in 0..1_000_000u64 {
7499                n = n.wrapping_add(i.wrapping_mul(i));
7500            }
7501            rounds += 1;
7502            // A bound rather than a spin, so a platform where this number does
7503            // not move fails here instead of hanging. Even a clock with whole
7504            // millisecond granularity gets there in the first round or two.
7505            assert!(rounds < 1_000, "cpu time never moved, n is {n}");
7506        }
7507    }
7508
7509    /// Pull `used_cpu_user` back out of an `INFO cpu` reply.
7510    #[cfg(unix)]
7511    fn used_cpu_user(info: &str) -> f64 {
7512        info.lines()
7513            .find_map(|l| l.strip_prefix("used_cpu_user:"))
7514            .expect("no used_cpu_user in the reply")
7515            .trim()
7516            .parse()
7517            .expect("used_cpu_user is not a number")
7518    }
7519
7520    /// The safety net under the rule that a body checks its arguments before
7521    /// it writes anything. `MGET` writes its array header first and then reads
7522    /// each key, so if a later argument could fail the header would already be
7523    /// out. Nothing in the string group does that today and this is what would
7524    /// catch the first one that did.
7525    #[test]
7526    fn a_command_that_fails_leaves_nothing_half_written() {
7527        let mut f = Fixture::new();
7528        let reply = f.run(&[b"SETRANGE", b"k", b"-1", b"x"]);
7529        assert_eq!(reply, "-ERR offset is out of range\r\n");
7530        assert!(!reply.contains(':'), "no integer went out in front of it");
7531    }
7532
7533    #[test]
7534    fn quit_answers_first_and_closes_after() {
7535        let mut f = Fixture::new();
7536        let (flow, reply) = f.flow(&[b"QUIT"]);
7537        assert_eq!(reply, "+OK\r\n");
7538        assert_eq!(flow, Flow::Close);
7539    }
7540
7541    /// A server that has not been asked to stop is not stopping, and one that
7542    /// has says so without writing anything back.
7543    ///
7544    /// The empty reply is the point. Redis answers nothing at all here and the
7545    /// client sees the socket close, and an `OK` would be a promise from a
7546    /// process that is about to not exist.
7547    #[test]
7548    fn shutdown_writes_nothing_and_sets_the_flag() {
7549        let mut f = Fixture::new();
7550        assert!(!f.server.stopping(), "nobody has asked yet");
7551
7552        let (flow, reply) = f.flow(&[b"SHUTDOWN"]);
7553        assert_eq!(reply, "");
7554        assert_eq!(flow, Flow::Close);
7555        assert!(f.server.stopping());
7556    }
7557
7558    /// Every flag combination 8.10.1 takes, and every one it refuses.
7559    ///
7560    /// The refusals are the half worth pinning down. `SAVE` and `NOSAVE`
7561    /// contradict each other, `ABORT` says to do nothing so it cannot be
7562    /// combined with a word about how to do it, and repeating any one of them
7563    /// is fine. All of it was read off a running 8.10.1 rather than worked out
7564    /// from the documentation, which does not say.
7565    #[test]
7566    fn shutdown_takes_the_flags_redis_takes() {
7567        for flags in [
7568            &[b"NOSAVE".as_slice()][..],
7569            &[b"SAVE"],
7570            &[b"NOW"],
7571            &[b"FORCE"],
7572            &[b"nosave"],
7573            &[b"NOW", b"NOW"],
7574            &[b"SAVE", b"SAVE"],
7575            &[b"NOSAVE", b"NOW", b"FORCE"],
7576        ] {
7577            let mut f = Fixture::new();
7578            let mut parts = vec![b"SHUTDOWN".as_slice()];
7579            parts.extend_from_slice(flags);
7580            let (flow, reply) = f.flow(&parts);
7581            assert_eq!(reply, "", "SHUTDOWN {flags:?} answered something");
7582            assert_eq!(flow, Flow::Close, "SHUTDOWN {flags:?} did not close");
7583            assert!(f.server.stopping(), "SHUTDOWN {flags:?} did not stop");
7584        }
7585
7586        for flags in [
7587            &[b"BOGUS".as_slice()][..],
7588            &[b"SAVE", b"NOSAVE"],
7589            &[b"NOSAVE", b"SAVE"],
7590            &[b"ABORT", b"NOW"],
7591            &[b"NOSAVE", b"ABORT"],
7592            &[b"NOW", b"FORCE", b"ABORT"],
7593        ] {
7594            let mut f = Fixture::new();
7595            let mut parts = vec![b"SHUTDOWN".as_slice()];
7596            parts.extend_from_slice(flags);
7597            assert_eq!(
7598                f.run(&parts),
7599                "-ERR syntax error\r\n",
7600                "SHUTDOWN {flags:?} was accepted"
7601            );
7602            assert!(!f.server.stopping(), "SHUTDOWN {flags:?} stopped anyway");
7603        }
7604    }
7605
7606    /// `ABORT` has nothing to call off, ever.
7607    ///
7608    /// A shutdown here is decided and done inside one turn of the loop, so
7609    /// there is no window in which one is in progress. That makes Redis's
7610    /// message for a cancel with nothing to cancel the right answer every time
7611    /// rather than only when nothing happens to be pending. Two `ABORT`s is
7612    /// still one `ABORT`, which is what 8.10.1 does.
7613    #[test]
7614    fn shutdown_abort_never_has_anything_to_abort() {
7615        let mut f = Fixture::new();
7616        for parts in [
7617            &[b"SHUTDOWN".as_slice(), b"ABORT"][..],
7618            &[b"SHUTDOWN", b"ABORT", b"ABORT"],
7619        ] {
7620            assert_eq!(f.run(parts), "-ERR No shutdown in progress.\r\n");
7621            assert!(!f.server.stopping(), "an abort stopped the server");
7622        }
7623    }
7624
7625    /// A fixture whose server writes into a directory of its own.
7626    ///
7627    /// Every test here really writes files, because the whole point of the
7628    /// command is the files and a backup that is only a state machine would
7629    /// pass a test suite and fail the first person who tried to restore one.
7630    /// The directory carries the test's name so that the suite can run its
7631    /// tests in parallel the way it always does.
7632    struct Backups {
7633        f: Fixture,
7634        dir: PathBuf,
7635    }
7636
7637    impl Backups {
7638        fn new(name: &str) -> Backups {
7639            let dir = std::env::temp_dir().join(format!("yo-backup-{name}-{}", std::process::id()));
7640            let _ = std::fs::remove_dir_all(&dir);
7641            std::fs::create_dir_all(&dir).expect("could not make a temporary directory");
7642            let mut f = Fixture::new();
7643            f.server.set_dir(dir.clone());
7644            Backups { f, dir }
7645        }
7646
7647        fn run(&mut self, parts: &[&[u8]]) -> String {
7648            self.f.run(parts)
7649        }
7650
7651        /// The names in `backupdir`, sorted, so a test can say what is on disk.
7652        fn files(&self) -> Vec<String> {
7653            let mut names: Vec<String> = match std::fs::read_dir(self.dir.join("backupdir")) {
7654                Ok(entries) => entries
7655                    .filter_map(|e| e.ok())
7656                    .map(|e| e.file_name().to_string_lossy().into_owned())
7657                    .collect(),
7658                Err(_) => Vec::new(),
7659            };
7660            names.sort();
7661            names
7662        }
7663
7664        fn read(&self, name: &str) -> Vec<u8> {
7665            std::fs::read(self.dir.join("backupdir").join(name)).expect("could not read")
7666        }
7667    }
7668
7669    impl Drop for Backups {
7670        fn drop(&mut self) {
7671            let _ = std::fs::remove_dir_all(&self.dir);
7672        }
7673    }
7674
7675    /// The four states and the moves between them, in the order a client walks
7676    /// them, with the files checked at every step.
7677    #[test]
7678    fn backup_walks_the_states_the_reference_walks() {
7679        let mut b = Backups::new("states");
7680        let status = |b: &mut Backups| b.run(&[b"BACKUP", b"STATUS"]);
7681
7682        assert!(status(&mut b).contains("idle"));
7683        assert!(b.files().is_empty(), "an idle server has written a backup");
7684
7685        assert_eq!(b.run(&[b"BACKUP", b"START"]), "+OK\r\n");
7686        assert!(status(&mut b).contains("incrementing"));
7687        assert_eq!(b.files(), ["appendonly.aof.1.base.rdb"]);
7688
7689        assert_eq!(b.run(&[b"BACKUP", b"SEAL"]), "+OK\r\n");
7690        assert!(status(&mut b).contains("sealed"));
7691        assert_eq!(
7692            b.files(),
7693            [
7694                "appendonly.aof.1.base.rdb",
7695                "appendonly.aof.1.incr.aof",
7696                "appendonly.aof.manifest",
7697            ]
7698        );
7699
7700        assert_eq!(b.run(&[b"BACKUP", b"CLEANUP"]), "+OK\r\n");
7701        assert!(status(&mut b).contains("idle"));
7702        assert!(b.files().is_empty(), "cleanup left something behind");
7703    }
7704
7705    /// Every move that is refused, in the reference's words.
7706    #[test]
7707    fn backup_refuses_the_moves_the_reference_refuses() {
7708        let mut b = Backups::new("refusals");
7709
7710        assert_eq!(
7711            b.run(&[b"BACKUP", b"SEAL"]),
7712            "-ERR No backup ready to seal (must be in the incrementing state)\r\n"
7713        );
7714        assert_eq!(
7715            b.run(&[b"BACKUP", b"ABORT"]),
7716            "-ERR No backup in progress\r\n"
7717        );
7718        // Cleanup from idle is not an error, it is a way of saying there was
7719        // nothing to clean up.
7720        assert_eq!(b.run(&[b"BACKUP", b"CLEANUP"]), "+OK\r\n");
7721
7722        b.run(&[b"BACKUP", b"START"]);
7723        assert_eq!(
7724            b.run(&[b"BACKUP", b"START"]),
7725            "-ERR A backup is already in progress, ABORT it first\r\n"
7726        );
7727        assert_eq!(
7728            b.run(&[b"BACKUP", b"CLEANUP"]),
7729            "-ERR Backup is in progress\r\n"
7730        );
7731
7732        b.run(&[b"BACKUP", b"SEAL"]);
7733        assert_eq!(
7734            b.run(&[b"BACKUP", b"START"]),
7735            "-ERR A sealed backup exists, CLEANUP it first\r\n"
7736        );
7737        assert_eq!(
7738            b.run(&[b"BACKUP", b"SEAL"]),
7739            "-ERR No backup ready to seal (must be in the incrementing state)\r\n"
7740        );
7741        assert_eq!(
7742            b.run(&[b"BACKUP", b"ABORT"]),
7743            "-ERR No backup in progress\r\n"
7744        );
7745    }
7746
7747    /// An abort takes the base file away and leaves a state saying who did it.
7748    ///
7749    /// The next backup takes the next sequence number rather than reusing the
7750    /// one whose files were just thrown away, so a directory somebody copied a
7751    /// half finished backup out of cannot end up with two different files under
7752    /// one name.
7753    #[test]
7754    fn backup_abort_removes_the_file_and_says_who_did_it() {
7755        let mut b = Backups::new("abort");
7756        b.run(&[b"BACKUP", b"START"]);
7757        assert_eq!(b.run(&[b"BACKUP", b"ABORT"]), "+OK\r\n");
7758
7759        let status = b.run(&[b"BACKUP", b"STATUS"]);
7760        assert!(status.contains("failed"), "{status}");
7761        assert!(status.contains("aborted by user"), "{status}");
7762        assert!(b.files().is_empty(), "abort left the base file behind");
7763        assert_eq!(b.run(&[b"BACKUP", b"LIST"]), "*0\r\n");
7764
7765        // A start from failed works, and is the second backup.
7766        assert_eq!(b.run(&[b"BACKUP", b"START"]), "+OK\r\n");
7767        assert_eq!(b.files(), ["appendonly.aof.2.base.rdb"]);
7768        let status = b.run(&[b"BACKUP", b"STATUS"]);
7769        assert!(status.contains("incrementing"), "{status}");
7770        assert!(!status.contains("aborted"), "the old error was kept");
7771    }
7772
7773    /// `LIST` names nothing, then one file, then three, and they are absolute.
7774    #[test]
7775    fn backup_list_names_the_files_that_are_pinned_so_far() {
7776        let mut b = Backups::new("list");
7777        assert_eq!(b.run(&[b"BACKUP", b"LIST"]), "*0\r\n");
7778
7779        b.run(&[b"BACKUP", b"START"]);
7780        let base = b.dir.join("backupdir").join("appendonly.aof.1.base.rdb");
7781        let base = base.to_string_lossy().into_owned();
7782        assert_eq!(
7783            b.run(&[b"BACKUP", b"LIST"]),
7784            format!("*1\r\n${}\r\n{base}\r\n", base.len())
7785        );
7786
7787        b.run(&[b"BACKUP", b"SEAL"]);
7788        let listed = b.run(&[b"BACKUP", b"LIST"]);
7789        assert!(listed.starts_with("*3\r\n"), "{listed}");
7790        // The order is the manifest's order, base then incremental then the
7791        // manifest itself, which is the order a restore needs them in.
7792        let names: Vec<&str> = listed
7793            .lines()
7794            .filter(|l| l.starts_with('/') || l.contains(":\\"))
7795            .collect();
7796        assert_eq!(names.len(), 3, "{listed}");
7797        assert!(names[0].ends_with("appendonly.aof.1.base.rdb"), "{listed}");
7798        assert!(names[1].ends_with("appendonly.aof.1.incr.aof"), "{listed}");
7799        assert!(names[2].ends_with("appendonly.aof.manifest"), "{listed}");
7800    }
7801
7802    /// The base file is the dataset as it was at `START` and not at `SEAL`.
7803    ///
7804    /// That is D-46 and it is the one thing about this a client can notice, so
7805    /// it is pinned here rather than left to be discovered by whoever restores
7806    /// one. The incremental file is empty for the same reason: there is no
7807    /// append only log underneath this server to copy the writes in between out
7808    /// of.
7809    #[test]
7810    fn a_backup_holds_the_dataset_as_it_was_at_start() {
7811        let mut b = Backups::new("contents");
7812        b.run(&[b"SET", b"bk", b"v1"]);
7813        b.run(&[b"BACKUP", b"START"]);
7814        b.run(&[b"SET", b"bk", b"v2"]);
7815        b.run(&[b"BACKUP", b"SEAL"]);
7816
7817        let base = b.read("appendonly.aof.1.base.rdb");
7818        assert!(base.starts_with(b"REDIS"), "not an RDB file");
7819        assert!(base.windows(2).any(|w| w == b"v1"), "the value is missing");
7820        assert!(
7821            !base.windows(2).any(|w| w == b"v2"),
7822            "the base file moved on after START"
7823        );
7824        // The aux field a loader acts on, and the one that says this file is
7825        // the base of an append only file rather than a standalone dump. Its
7826        // value is the one byte string 1, which the encoder writes as an
7827        // integer the way a real server writes it.
7828        let at = base
7829            .windows(8)
7830            .position(|w| w == b"aof-base")
7831            .expect("no aof-base aux field");
7832        assert_eq!(&base[at + 8..at + 10], b"\xc0\x01", "{:?}", &base[at..]);
7833
7834        assert!(b.read("appendonly.aof.1.incr.aof").is_empty());
7835        assert_eq!(
7836            String::from_utf8(b.read("appendonly.aof.manifest")).expect("the manifest is text"),
7837            "file appendonly.aof.1.base.rdb seq 1 type b\n\
7838             file appendonly.aof.1.incr.aof seq 1 type i startoffset 0 endoffset 0\n"
7839        );
7840    }
7841
7842    /// `STATUS` is a map of four pairs on RESP3 and the same pairs flat on
7843    /// RESP2, which is what every other map shaped reply in this server does.
7844    #[test]
7845    fn backup_status_is_a_map_on_resp3_and_a_flat_array_on_resp2() {
7846        let mut b = Backups::new("status");
7847        b.f.server.set_clock_ms(1_700_000_000_000);
7848
7849        assert_eq!(
7850            b.run(&[b"BACKUP", b"STATUS"]),
7851            "*8\r\n$5\r\nstate\r\n$4\r\nidle\r\n$5\r\nerror\r\n$0\r\n\r\n\
7852             $10\r\nstart_time\r\n:0\r\n$8\r\nend_time\r\n:0\r\n"
7853        );
7854
7855        b.f.out = Out::new(Proto::Resp3);
7856        b.run(&[b"BACKUP", b"START"]);
7857        assert_eq!(
7858            b.run(&[b"BACKUP", b"STATUS"]),
7859            "%4\r\n$5\r\nstate\r\n$12\r\nincrementing\r\n$5\r\nerror\r\n$0\r\n\r\n\
7860             $10\r\nstart_time\r\n:1700000000\r\n$8\r\nend_time\r\n:0\r\n"
7861        );
7862
7863        b.run(&[b"BACKUP", b"SEAL"]);
7864        let sealed = b.run(&[b"BACKUP", b"STATUS"]);
7865        assert!(sealed.contains("end_time\r\n:1700000000"), "{sealed}");
7866    }
7867
7868    /// A sealed backup that nobody cleans up goes away on its own once
7869    /// `backup-sealed-ttl` seconds have passed since the seal.
7870    #[test]
7871    fn a_sealed_backup_is_swept_away_after_the_timeout() {
7872        let mut b = Backups::new("ttl");
7873        b.f.server.set_clock_ms(1_000_000);
7874        assert_eq!(
7875            b.run(&[b"CONFIG", b"SET", b"backup-sealed-ttl", b"60"]),
7876            "+OK\r\n"
7877        );
7878        b.run(&[b"BACKUP", b"START"]);
7879        b.run(&[b"BACKUP", b"SEAL"]);
7880
7881        // A minute short of the deadline, nothing happens.
7882        b.f.server.set_clock_ms(1_000_000 + 59_000);
7883        b.f.server.backup_expire();
7884        assert!(b.run(&[b"BACKUP", b"STATUS"]).contains("sealed"));
7885        assert_eq!(b.files().len(), 3);
7886
7887        b.f.server.set_clock_ms(1_000_000 + 60_000);
7888        b.f.server.backup_expire();
7889        let status = b.run(&[b"BACKUP", b"STATUS"]);
7890        assert!(status.contains("idle"), "{status}");
7891        assert!(b.files().is_empty(), "the timeout left the files behind");
7892
7893        // Zero is the default and means a sealed backup is kept for ever.
7894        b.run(&[b"CONFIG", b"SET", b"backup-sealed-ttl", b"0"]);
7895        b.run(&[b"BACKUP", b"START"]);
7896        b.run(&[b"BACKUP", b"SEAL"]);
7897        b.f.server.set_clock_ms(9_000_000_000);
7898        b.f.server.backup_expire();
7899        assert!(b.run(&[b"BACKUP", b"STATUS"]).contains("sealed"));
7900    }
7901
7902    /// The three settings around the command, read and written the way 8.10.1
7903    /// reads and writes them.
7904    #[test]
7905    fn the_backup_settings_behave_the_way_the_reference_does() {
7906        let mut b = Backups::new("config");
7907        let dir = b.dir.to_string_lossy().into_owned();
7908
7909        assert_eq!(
7910            b.run(&[b"CONFIG", b"GET", b"dir"]),
7911            format!("*2\r\n$3\r\ndir\r\n${}\r\n{dir}\r\n", dir.len())
7912        );
7913        assert_eq!(
7914            b.run(&[b"CONFIG", b"GET", b"backupdirname"]),
7915            "*2\r\n$13\r\nbackupdirname\r\n$9\r\nbackupdir\r\n"
7916        );
7917        assert_eq!(
7918            b.run(&[b"CONFIG", b"GET", b"backup-sealed-ttl"]),
7919            "*2\r\n$17\r\nbackup-sealed-ttl\r\n$1\r\n0\r\n"
7920        );
7921
7922        // `dir` is a protected config, so it is refused even for the value it
7923        // already holds, and `backupdirname` is immutable.
7924        assert_eq!(
7925            b.run(&[b"CONFIG", b"SET", b"dir", dir.as_bytes()]),
7926            "-ERR CONFIG SET failed (possibly related to argument 'dir') - can't set protected config\r\n"
7927        );
7928        assert_eq!(
7929            b.run(&[b"CONFIG", b"SET", b"backupdirname", b"other"]),
7930            "-ERR CONFIG SET failed (possibly related to argument 'backupdirname') - can't set immutable config\r\n"
7931        );
7932        assert!(
7933            b.run(&[b"CONFIG", b"SET", b"backup-sealed-ttl", b"abc"])
7934                .contains("argument couldn't be parsed into an integer")
7935        );
7936        assert!(
7937            b.run(&[b"CONFIG", b"SET", b"backup-sealed-ttl", b"-1"])
7938                .contains("argument must be between 0 and 9223372036854775807 inclusive")
7939        );
7940    }
7941
7942    /// The help text, which has `HELP` in it twice because the reference's does.
7943    #[test]
7944    fn backup_help_is_the_text_the_reference_sends() {
7945        let mut f = Fixture::new();
7946        let help = f.run(&[b"BACKUP", b"HELP"]);
7947        assert!(help.starts_with("*17\r\n"), "{help}");
7948        assert!(
7949            help.contains("+BACKUP <subcommand> [<arg> [value] [opt] ...]. Subcommands are:\r\n")
7950        );
7951        assert!(help.contains("+    Start a new backup into the configured 'backupdirname'.\r\n"));
7952        assert!(help.contains("+    Freeze the current backup (BASE + INCR + manifest).\r\n"));
7953        assert!(help.contains("+    Return this help.\r\n+HELP\r\n+    Print this help.\r\n"));
7954    }
7955
7956    /// What a mistyped `BACKUP` gets told.
7957    ///
7958    /// The arity error names `backup` where the reference names `backup|start`,
7959    /// which is D-46: the table reports one arity for the container the way the
7960    /// reference does, and the per subcommand table that would carry the better
7961    /// name is not built yet. Every subcommand is exactly two words, so nothing
7962    /// legal is refused by it.
7963    #[test]
7964    fn backup_refuses_what_it_cannot_read() {
7965        let mut f = Fixture::new();
7966        assert_eq!(
7967            f.run(&[b"BACKUP"]),
7968            "-ERR wrong number of arguments for 'backup' command\r\n"
7969        );
7970        assert_eq!(
7971            f.run(&[b"BACKUP", b"START", b"x"]),
7972            "-ERR wrong number of arguments for 'backup' command\r\n"
7973        );
7974        assert_eq!(
7975            f.run(&[b"BACKUP", b"NOPE"]),
7976            "-ERR unknown subcommand 'NOPE'. Try BACKUP HELP.\r\n"
7977        );
7978    }
7979
7980    #[test]
7981    fn the_command_counter_counts_every_command_including_the_bad_ones() {
7982        let mut f = Fixture::new();
7983        f.run(&[b"PING"]);
7984        f.run(&[b"NOPE"]);
7985        f.run(&[b"GET"]);
7986        assert_eq!(f.server.totals().commands, 3);
7987    }
7988
7989    #[test]
7990    fn what_a_thread_marked_is_taken_by_the_maintenance_turn() {
7991        let mut server = Server::new();
7992        server.set_threads(2);
7993        // A fresh server has every database on the turn's list, so start from
7994        // nothing to see the one mark arrive.
7995        server.mine().turn.store(0, Relaxed);
7996        server.locals[1].mark(1 << 9);
7997        server.collect_marks();
7998        assert!(server.mine().wanted(9));
7999        // And taken once rather than left to be taken again next turn.
8000        assert_eq!(server.locals[1].dirty.load(Relaxed), 0);
8001    }
8002
8003    #[test]
8004    fn what_two_threads_counted_is_added_up_when_info_asks() {
8005        let mut server = Server::new();
8006        server.set_threads(2);
8007        // Written into the two sets by hand, because what is under test is the
8008        // adding up and not the claiming, and one test thread can only ever
8009        // claim one set.
8010        let ping = lookup(b"PING").expect("PING is a command");
8011        for (at, calls) in [(0, 2), (1, 3)] {
8012            let counters = &server.locals[at];
8013            for _ in 0..calls {
8014                counters.stats.commands.bump();
8015                counters.cmdstats.at(ping).calls.bump();
8016            }
8017            counters.stats.opened();
8018        }
8019        assert_eq!(server.totals().commands, 5);
8020        assert_eq!(server.totals().clients, 2);
8021        assert_eq!(server.totals().connections, 2);
8022        let rows: Vec<_> = server.command_stats().collect();
8023        assert_eq!(rows.len(), 1);
8024        assert_eq!(rows[0].0, "ping");
8025        assert_eq!(rows[0].1.calls, 5);
8026        // A reset takes the totals and leaves the open connections, which are
8027        // still open.
8028        server.reset_stats();
8029        assert_eq!(server.totals().commands, 0);
8030        assert_eq!(server.totals().connections, 0);
8031        assert_eq!(server.totals().clients, 2);
8032    }
8033
8034    #[test]
8035    fn the_parked_count_says_what_the_waiter_list_says() {
8036        let mut f = Fixture::new();
8037        assert_eq!(f.server.parked(), 0);
8038        for client in 1..=3u64 {
8039            f.session = Session::new(client);
8040            assert_eq!(f.flow(&[b"BLPOP", b"q", b"0"]).0, Flow::Block);
8041        }
8042        assert_eq!(f.server.parked(), 3);
8043        assert_eq!(f.server.waiters().len(), 3);
8044
8045        // The three ways the list gets shorter, each of which has to move the
8046        // number with it, because a number left behind is either a walk of the
8047        // list that never happens or one that runs off the end of it.
8048        f.server.forget_waiters(2);
8049        assert_eq!(f.server.parked(), f.server.waiters().len());
8050        f.server.forget_waiters(1);
8051        assert_eq!(f.server.parked(), f.server.waiters().len());
8052        f.run(&[b"RPUSH", b"q", b"v"]);
8053        let mut out = Out::new(Proto::Resp2);
8054        assert!(f.server.serve_waiter(3, 0, &mut out));
8055        f.server.forget_waiters(3);
8056        assert_eq!(f.server.parked(), 0);
8057        assert!(f.server.waiters().is_empty());
8058    }
8059
8060    #[test]
8061    fn a_set_goes_from_bytes_to_bytes() {
8062        let mut f = Fixture::new();
8063        assert_eq!(f.run(&[b"SADD", b"s", b"a", b"b", b"c"]), ":3\r\n");
8064        assert_eq!(f.run(&[b"SADD", b"s", b"b", b"d"]), ":1\r\n");
8065        assert_eq!(f.run(&[b"SCARD", b"s"]), ":4\r\n");
8066        assert_eq!(f.run(&[b"SISMEMBER", b"s", b"a"]), ":1\r\n");
8067        assert_eq!(f.run(&[b"SISMEMBER", b"s", b"z"]), ":0\r\n");
8068        assert_eq!(f.run(&[b"TYPE", b"s"]), "+set\r\n");
8069        assert_eq!(
8070            f.run(&[b"SMISMEMBER", b"s", b"a", b"z", b"d"]),
8071            "*3\r\n:1\r\n:0\r\n:1\r\n"
8072        );
8073        assert_eq!(f.run(&[b"SREM", b"s", b"a", b"z"]), ":1\r\n");
8074        assert_eq!(f.run(&[b"SCARD", b"s"]), ":3\r\n");
8075    }
8076
8077    #[test]
8078    fn a_set_command_at_a_key_that_is_not_there_answers_empty() {
8079        let mut f = Fixture::new();
8080        assert_eq!(f.run(&[b"SCARD", b"nope"]), ":0\r\n");
8081        assert_eq!(f.run(&[b"SISMEMBER", b"nope", b"a"]), ":0\r\n");
8082        assert_eq!(f.run(&[b"SREM", b"nope", b"a"]), ":0\r\n");
8083        assert_eq!(f.run(&[b"SMEMBERS", b"nope"]), "*0\r\n");
8084        assert_eq!(
8085            f.run(&[b"SMISMEMBER", b"nope", b"a", b"b"]),
8086            "*2\r\n:0\r\n:0\r\n"
8087        );
8088        assert_eq!(f.run(&[b"DBSIZE"]), ":0\r\n", "and made nothing");
8089    }
8090
8091    #[test]
8092    fn smembers_answers_a_set_on_resp3_and_an_array_on_resp2() {
8093        // Not cosmetic. A RESP3 client that gets a `~` hands the caller a set
8094        // and one that gets a `*` hands it a list, without either of them being
8095        // told which command was sent.
8096        let mut f = Fixture::new();
8097        f.run(&[b"SADD", b"s", b"one"]);
8098        assert_eq!(f.run(&[b"SMEMBERS", b"s"]), "*1\r\n$3\r\none\r\n");
8099
8100        f.run(&[b"HELLO", b"3"]);
8101        assert_eq!(f.run(&[b"SMEMBERS", b"s"]), "~1\r\n$3\r\none\r\n");
8102    }
8103
8104    #[test]
8105    fn an_integer_member_comes_back_as_the_digits_it_never_stored() {
8106        // An intset holds the number, so these digits exist for the first time
8107        // in the reply buffer.
8108        let mut f = Fixture::new();
8109        f.run(&[b"SADD", b"s", b"42"]);
8110        assert_eq!(f.run(&[b"SMEMBERS", b"s"]), "*1\r\n$2\r\n42\r\n");
8111        assert_eq!(f.run(&[b"SISMEMBER", b"s", b"42"]), ":1\r\n");
8112        assert_eq!(
8113            f.run(&[b"SISMEMBER", b"s", b"042"]),
8114            ":0\r\n",
8115            "the member is the bytes and not the number they parse to"
8116        );
8117    }
8118
8119    #[test]
8120    fn the_wrong_command_at_the_wrong_type_says_so_both_ways() {
8121        let mut f = Fixture::new();
8122        f.run(&[b"SET", b"str", b"v"]);
8123        f.run(&[b"SADD", b"set", b"a"]);
8124
8125        let wrong = "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n";
8126        assert_eq!(f.run(&[b"SADD", b"str", b"a"]), wrong);
8127        assert_eq!(f.run(&[b"SCARD", b"str"]), wrong);
8128        assert_eq!(f.run(&[b"SMEMBERS", b"str"]), wrong);
8129        assert_eq!(f.run(&[b"SMISMEMBER", b"str", b"a"]), wrong);
8130        assert_eq!(f.run(&[b"GET", b"set"]), wrong);
8131        assert_eq!(f.run(&[b"APPEND", b"set", b"x"]), wrong);
8132        assert_eq!(f.run(&[b"INCR", b"set"]), wrong);
8133        assert_eq!(f.run(&[b"STRLEN", b"set"]), wrong);
8134
8135        // MGET is the one that does not, because Redis gives nil for the odd
8136        // key out rather than failing the good keys next to it.
8137        assert_eq!(
8138            f.run(&[b"MGET", b"str", b"set", b"nope"]),
8139            "*3\r\n$1\r\nv\r\n$-1\r\n$-1\r\n"
8140        );
8141        // And plain SET overwrites any type, which takes the body with it.
8142        assert_eq!(f.run(&[b"SET", b"set", b"now a string"]), "+OK\r\n");
8143        assert_eq!(f.run(&[b"TYPE", b"set"]), "+string\r\n");
8144    }
8145
8146    #[test]
8147    fn a_wrongtype_leaves_nothing_half_written() {
8148        // SMISMEMBER writes an array header and then one reply per member, so
8149        // it is the first command in the server that could get a header out in
8150        // front of an error if it checked its key in the wrong order.
8151        let mut f = Fixture::new();
8152        f.run(&[b"SET", b"k", b"v"]);
8153        let reply = f.run(&[b"SMISMEMBER", b"k", b"a", b"b"]);
8154        assert!(reply.starts_with("-WRONGTYPE"), "got {reply}");
8155        assert!(!reply.contains('*'), "an array header went out in front");
8156    }
8157
8158    #[test]
8159    fn emptying_a_set_takes_the_key_with_it() {
8160        let mut f = Fixture::new();
8161        f.run(&[b"SADD", b"s", b"a", b"b"]);
8162        assert_eq!(f.run(&[b"DBSIZE"]), ":1\r\n");
8163        assert_eq!(f.run(&[b"SREM", b"s", b"a", b"b"]), ":2\r\n");
8164        assert_eq!(f.run(&[b"EXISTS", b"s"]), ":0\r\n");
8165        assert_eq!(f.run(&[b"TYPE", b"s"]), "+none\r\n");
8166        assert_eq!(f.run(&[b"DBSIZE"]), ":0\r\n");
8167    }
8168
8169    /// Pull the cursor and the members out of one `SSCAN` reply.
8170    ///
8171    /// Crude on purpose. A test that walked a set through a real client would
8172    /// be testing the client, and what these tests are about is the shape of
8173    /// the bytes and the fact that a walk sees every member once.
8174    fn split_scan(reply: &str) -> (String, Vec<String>) {
8175        let mut lines = reply.split("\r\n");
8176        assert_eq!(lines.next(), Some("*2"), "got {reply}");
8177        lines.next().expect("the cursor header");
8178        let cursor = lines.next().expect("the cursor").to_owned();
8179        let header = lines.next().expect("the member header");
8180        let n: usize = header[1..].parse().expect("a member count");
8181        let mut members = Vec::with_capacity(n);
8182        for _ in 0..n {
8183            lines.next().expect("a member header");
8184            members.push(lines.next().expect("a member").to_owned());
8185        }
8186        (cursor, members)
8187    }
8188
8189    #[test]
8190    fn popping_takes_a_member_off_the_set_and_hands_it_back() {
8191        let mut f = Fixture::new();
8192        f.run(&[b"SADD", b"s", b"a", b"b", b"c", b"d"]);
8193
8194        let one = f.run(&[b"SPOP", b"s"]);
8195        assert!(
8196            ["$1\r\na\r\n", "$1\r\nb\r\n", "$1\r\nc\r\n", "$1\r\nd\r\n"].contains(&one.as_str()),
8197            "got {one}"
8198        );
8199        assert_eq!(f.run(&[b"SCARD", b"s"]), ":3\r\n");
8200
8201        // A count takes that many, and the last one takes the key with it.
8202        let (_, rest) = ("", f.run(&[b"SPOP", b"s", b"3"]));
8203        assert!(rest.starts_with("*3\r\n"), "got {rest}");
8204        assert_eq!(f.run(&[b"EXISTS", b"s"]), ":0\r\n");
8205        // And a pop at a key that is not there is a nil, not an empty bulk.
8206        assert_eq!(f.run(&[b"SPOP", b"s"]), "$-1\r\n");
8207        assert_eq!(f.run(&[b"SPOP", b"s", b"2"]), "*0\r\n");
8208    }
8209
8210    #[test]
8211    fn the_two_draws_disagree_about_the_reply_type_and_they_are_right_to() {
8212        // The one place in the server where the reply type carries something
8213        // the command name does not. SPOP's members are distinct so a RESP3
8214        // client can build a set out of them. SRANDMEMBER with a negative count
8215        // can hand back the same member three times, and a set would lose two.
8216        let mut f = Fixture::new();
8217        f.run(&[b"HELLO", b"3"]);
8218        f.run(&[b"SADD", b"s", b"a", b"b", b"c"]);
8219
8220        assert!(f.run(&[b"SPOP", b"s", b"2"]).starts_with("~2\r\n"));
8221        // And a positive count is an array too, since Redis makes it one.
8222        assert!(f.run(&[b"SRANDMEMBER", b"s", b"1"]).starts_with("*1\r\n"));
8223
8224        // A negative count against a set of one is where the difference bites:
8225        // the same member three times, which is a three element reply and would
8226        // have been a one element reply if it had gone out as a set.
8227        f.run(&[b"SADD", b"one", b"z"]);
8228        assert_eq!(
8229            f.run(&[b"SRANDMEMBER", b"one", b"-3"]),
8230            "*3\r\n$1\r\nz\r\n$1\r\nz\r\n$1\r\nz\r\n"
8231        );
8232    }
8233
8234    #[test]
8235    fn drawing_a_member_removes_nothing_and_says_nil_at_a_missing_key() {
8236        let mut f = Fixture::new();
8237        f.run(&[b"SADD", b"s", b"only"]);
8238        assert_eq!(f.run(&[b"SRANDMEMBER", b"s"]), "$4\r\nonly\r\n");
8239        assert_eq!(f.run(&[b"SRANDMEMBER", b"s"]), "$4\r\nonly\r\n");
8240        assert_eq!(f.run(&[b"SCARD", b"s"]), ":1\r\n");
8241
8242        assert_eq!(f.run(&[b"SRANDMEMBER", b"nope"]), "$-1\r\n");
8243        // The count form answers an empty array rather than a nil, which is the
8244        // pair of answers Redis gives and is not the pair it looks like.
8245        assert_eq!(f.run(&[b"SRANDMEMBER", b"nope", b"3"]), "*0\r\n");
8246        assert_eq!(f.run(&[b"SRANDMEMBER", b"nope", b"-3"]), "*0\r\n");
8247        // Asking for more than is there answers all of it once and not padding.
8248        assert_eq!(f.run(&[b"SRANDMEMBER", b"s", b"9"]), "*1\r\n$4\r\nonly\r\n");
8249    }
8250
8251    #[test]
8252    fn a_pop_count_that_is_not_a_positive_number_says_so() {
8253        let mut f = Fixture::new();
8254        f.run(&[b"SADD", b"s", b"a"]);
8255        let bad = "-ERR value is out of range, must be positive\r\n";
8256        assert_eq!(f.run(&[b"SPOP", b"s", b"-1"]), bad);
8257        assert_eq!(f.run(&[b"SPOP", b"s", b"abc"]), bad);
8258        assert_eq!(f.run(&[b"SCARD", b"s"]), ":1\r\n", "and took nothing");
8259        // Zero is allowed and is a real answer rather than an error.
8260        assert_eq!(f.run(&[b"SPOP", b"s", b"0"]), "*0\r\n");
8261        assert_eq!(f.run(&[b"EXISTS", b"s"]), ":1\r\n");
8262    }
8263
8264    #[test]
8265    fn a_scan_walks_a_set_of_any_size_exactly_once() {
8266        let mut f = Fixture::new();
8267        let members: Vec<Vec<u8>> = (0..300).map(|i| format!("m{i}").into_bytes()).collect();
8268        let args: Vec<&[u8]> = [&b"SADD"[..], &b"s"[..]]
8269            .into_iter()
8270            .chain(members.iter().map(Vec::as_slice))
8271            .collect();
8272        f.run(&args);
8273
8274        let mut seen = Vec::new();
8275        let mut cursor = "0".to_owned();
8276        loop {
8277            let reply = f.run(&[b"SSCAN", b"s", cursor.as_bytes()]);
8278            let (next, got) = split_scan(&reply);
8279            seen.extend(got);
8280            cursor = next;
8281            if cursor == "0" {
8282                break;
8283            }
8284        }
8285        seen.sort();
8286        seen.dedup();
8287        assert_eq!(seen.len(), 300, "a walk saw a member twice or missed one");
8288
8289        // A set small enough to be a listpack answers in one call whatever
8290        // cursor it was handed, which is what Redis does for that encoding.
8291        f.run(&[b"SADD", b"small", b"a", b"b", b"c"]);
8292        let (cursor, got) = split_scan(&f.run(&[b"SSCAN", b"small", b"0", b"COUNT", b"1"]));
8293        assert_eq!(cursor, "0");
8294        assert_eq!(got.len(), 3);
8295        // And a key that is not there is a finished scan of nothing.
8296        assert_eq!(f.run(&[b"SSCAN", b"nope", b"0"]), "*2\r\n$1\r\n0\r\n*0\r\n");
8297    }
8298
8299    #[test]
8300    fn a_scan_takes_match_and_count_and_refuses_anything_else() {
8301        let mut f = Fixture::new();
8302        f.run(&[b"SADD", b"s", b"aa", b"ab", b"ba", b"12", b"13"]);
8303
8304        let (_, got) = split_scan(&f.run(&[b"SSCAN", b"s", b"0", b"MATCH", b"a*"]));
8305        let mut got = got;
8306        got.sort();
8307        assert_eq!(got, ["aa", "ab"]);
8308
8309        // An integer member has no digits stored anywhere, so MATCH is the one
8310        // place a scan pays to write some.
8311        let (_, got) = split_scan(&f.run(&[b"SSCAN", b"s", b"0", b"MATCH", b"1?"]));
8312        let mut got = got;
8313        got.sort();
8314        assert_eq!(got, ["12", "13"]);
8315
8316        assert_eq!(f.run(&[b"SSCAN", b"s", b"abc"]), "-ERR invalid cursor\r\n");
8317        assert_eq!(f.run(&[b"SSCAN", b"s", b"-1"]), "-ERR invalid cursor\r\n");
8318        assert_eq!(
8319            f.run(&[b"SSCAN", b"s", b"0", b"NOPE", b"1"]),
8320            "-ERR syntax error\r\n"
8321        );
8322        // A count under one is a syntax error and not a range error, which is
8323        // the odder of Redis's two answers and the reason it is copied exactly.
8324        assert_eq!(
8325            f.run(&[b"SSCAN", b"s", b"0", b"COUNT", b"0"]),
8326            "-ERR syntax error\r\n"
8327        );
8328    }
8329
8330    #[test]
8331    fn moving_a_member_takes_it_off_one_set_and_puts_it_on_another() {
8332        let mut f = Fixture::new();
8333        f.run(&[b"SADD", b"src", b"a", b"b"]);
8334        f.run(&[b"SADD", b"dst", b"c"]);
8335
8336        assert_eq!(f.run(&[b"SMOVE", b"src", b"dst", b"a"]), ":1\r\n");
8337        assert_eq!(f.run(&[b"SISMEMBER", b"src", b"a"]), ":0\r\n");
8338        assert_eq!(f.run(&[b"SISMEMBER", b"dst", b"a"]), ":1\r\n");
8339        // A member that is not in the source is a zero and moves nothing.
8340        assert_eq!(f.run(&[b"SMOVE", b"src", b"dst", b"zz"]), ":0\r\n");
8341        assert_eq!(f.run(&[b"SCARD", b"dst"]), ":2\r\n");
8342
8343        // A destination that does not exist gets made, and a source that runs
8344        // out goes away.
8345        assert_eq!(f.run(&[b"SMOVE", b"src", b"fresh", b"b"]), ":1\r\n");
8346        assert_eq!(f.run(&[b"EXISTS", b"src"]), ":0\r\n");
8347        assert_eq!(f.run(&[b"SMEMBERS", b"fresh"]), "*1\r\n$1\r\nb\r\n");
8348    }
8349
8350    #[test]
8351    fn moving_checks_the_types_in_the_order_redis_checks_them() {
8352        // Not the order it looks like it should be. A source that is not there
8353        // answers zero without ever looking at the destination, so this is a
8354        // zero and not a WRONGTYPE even though the destination is a string.
8355        let mut f = Fixture::new();
8356        f.run(&[b"SET", b"str", b"v"]);
8357        f.run(&[b"SADD", b"set", b"a"]);
8358
8359        let wrong = "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n";
8360        assert_eq!(f.run(&[b"SMOVE", b"nope", b"str", b"a"]), ":0\r\n");
8361        assert_eq!(f.run(&[b"SMOVE", b"str", b"set", b"a"]), wrong);
8362        assert_eq!(f.run(&[b"SMOVE", b"set", b"str", b"a"]), wrong);
8363        assert_eq!(f.run(&[b"SPOP", b"str"]), wrong);
8364        assert_eq!(f.run(&[b"SRANDMEMBER", b"str"]), wrong);
8365        assert_eq!(f.run(&[b"SSCAN", b"str", b"0"]), wrong);
8366        assert_eq!(
8367            f.run(&[b"SISMEMBER", b"set", b"a"]),
8368            ":1\r\n",
8369            "and none of that moved anything"
8370        );
8371    }
8372
8373    #[test]
8374    fn a_scan_leaves_nothing_half_written_when_its_arguments_are_wrong() {
8375        // SSCAN writes an outer array header before it walks, so it is the
8376        // command most likely to get bytes out in front of an error.
8377        let mut f = Fixture::new();
8378        f.run(&[b"SADD", b"s", b"a"]);
8379        for bad in [
8380            &[b"SSCAN".as_slice(), b"s", b"abc"][..],
8381            &[b"SSCAN".as_slice(), b"s", b"0", b"COUNT", b"nope"][..],
8382            &[b"SSCAN".as_slice(), b"s", b"0", b"MATCH"][..],
8383        ] {
8384            let reply = f.run(bad);
8385            assert!(reply.starts_with("-ERR"), "got {reply}");
8386            assert!(!reply.contains('*'), "an array header went out in front");
8387        }
8388    }
8389
8390    #[test]
8391    fn a_hash_writes_reads_and_deletes_its_fields() {
8392        let mut f = Fixture::new();
8393        assert_eq!(f.run(&[b"HSET", b"h", b"a", b"1", b"b", b"2"]), ":2\r\n");
8394        assert_eq!(f.run(&[b"HSET", b"h", b"a", b"9"]), ":0\r\n", "a was there");
8395        assert_eq!(f.run(&[b"HGET", b"h", b"a"]), "$1\r\n9\r\n");
8396        assert_eq!(f.run(&[b"HGET", b"h", b"nope"]), "$-1\r\n");
8397        assert_eq!(f.run(&[b"HGET", b"nokey", b"a"]), "$-1\r\n");
8398        assert_eq!(f.run(&[b"HLEN", b"h"]), ":2\r\n");
8399        assert_eq!(f.run(&[b"HEXISTS", b"h", b"a"]), ":1\r\n");
8400        assert_eq!(f.run(&[b"HEXISTS", b"h", b"nope"]), ":0\r\n");
8401        assert_eq!(f.run(&[b"HSTRLEN", b"h", b"a"]), ":1\r\n");
8402        assert_eq!(f.run(&[b"HSTRLEN", b"h", b"nope"]), ":0\r\n");
8403
8404        // The value the client sent is `9`, so HGET h b must not find the `2`
8405        // that is a value. A search with a step of one would have.
8406        assert_eq!(f.run(&[b"HGET", b"h", b"2"]), "$-1\r\n");
8407
8408        assert_eq!(f.run(&[b"HDEL", b"h", b"a", b"nope"]), ":1\r\n");
8409        assert_eq!(f.run(&[b"HDEL", b"h", b"b"]), ":1\r\n");
8410        assert_eq!(
8411            f.run(&[b"EXISTS", b"h"]),
8412            ":0\r\n",
8413            "and losing the last field lost the key"
8414        );
8415    }
8416
8417    #[test]
8418    fn hgetall_answers_a_map_on_resp3_and_the_same_pairs_flat_on_resp2() {
8419        let mut f = Fixture::new();
8420        f.run(&[b"HSET", b"h", b"a", b"1"]);
8421        assert_eq!(f.run(&[b"HGETALL", b"h"]), "*2\r\n$1\r\na\r\n$1\r\n1\r\n");
8422        assert_eq!(f.run(&[b"HGETALL", b"nokey"]), "*0\r\n");
8423        assert_eq!(f.run(&[b"HKEYS", b"h"]), "*1\r\n$1\r\na\r\n");
8424        assert_eq!(f.run(&[b"HVALS", b"h"]), "*1\r\n$1\r\n1\r\n");
8425        assert_eq!(f.run(&[b"HKEYS", b"nokey"]), "*0\r\n");
8426
8427        f.run(&[b"HELLO", b"3"]);
8428        assert_eq!(f.run(&[b"HGETALL", b"h"]), "%1\r\n$1\r\na\r\n$1\r\n1\r\n");
8429        assert_eq!(
8430            f.run(&[b"HGETALL", b"nokey"]),
8431            "%0\r\n",
8432            "a missing key is the empty hash and never a nil"
8433        );
8434        assert_eq!(
8435            f.run(&[b"HKEYS", b"h"]),
8436            "*1\r\n$1\r\na\r\n",
8437            "and the two that answer one side stay arrays"
8438        );
8439    }
8440
8441    #[test]
8442    fn hmget_answers_once_per_field_and_hmset_answers_ok() {
8443        let mut f = Fixture::new();
8444        assert_eq!(f.run(&[b"HMSET", b"h", b"a", b"1", b"c", b"3"]), "+OK\r\n");
8445        assert_eq!(
8446            f.run(&[b"HMGET", b"h", b"a", b"b", b"c"]),
8447            "*3\r\n$1\r\n1\r\n$-1\r\n$1\r\n3\r\n",
8448            "the reply is positional, so b is a nil and not a gap"
8449        );
8450        assert_eq!(
8451            f.run(&[b"HMGET", b"nokey", b"a", b"b"]),
8452            "*2\r\n$-1\r\n$-1\r\n",
8453            "and a missing key is all nils rather than an empty array"
8454        );
8455
8456        assert_eq!(f.run(&[b"HSETNX", b"h", b"a", b"9"]), ":0\r\n");
8457        assert_eq!(f.run(&[b"HSETNX", b"h", b"z", b"9"]), ":1\r\n");
8458        assert_eq!(f.run(&[b"HGET", b"h", b"a"]), "$1\r\n1\r\n");
8459    }
8460
8461    #[test]
8462    fn a_hash_counts_up_and_says_so_when_it_cannot() {
8463        let mut f = Fixture::new();
8464        assert_eq!(f.run(&[b"HINCRBY", b"h", b"n", b"5"]), ":5\r\n");
8465        assert_eq!(f.run(&[b"HINCRBY", b"h", b"n", b"-7"]), ":-2\r\n");
8466        assert_eq!(f.run(&[b"HGET", b"h", b"n"]), "$2\r\n-2\r\n");
8467        assert_eq!(
8468            f.run(&[b"HINCRBYFLOAT", b"h", b"f", b"10.5"]),
8469            "$4\r\n10.5\r\n",
8470            "a bulk string and not a double, on both protocols"
8471        );
8472
8473        f.run(&[b"HSET", b"h", b"s", b"words"]);
8474        let bad = f.run(&[b"HINCRBY", b"h", b"s", b"1"]);
8475        assert!(
8476            bad.starts_with("-ERR hash value is not an integer"),
8477            "{bad}"
8478        );
8479        let bad = f.run(&[b"HINCRBY", b"h", b"n", b"nope"]);
8480        assert!(
8481            bad.starts_with("-ERR value is not an integer"),
8482            "a bad argument is not yet a hash value, {bad}"
8483        );
8484        assert_eq!(
8485            f.run(&[b"HGET", b"h", b"s"]),
8486            "$5\r\nwords\r\n",
8487            "and neither of them wrote anything"
8488        );
8489    }
8490
8491    #[test]
8492    fn a_hash_scan_walks_every_pair_once_and_novalues_drops_half_of_it() {
8493        // Fourteen minutes under Miri at five hundred, which was the slowest
8494        // test in this crate that was not about megabytes. What the count has
8495        // to be is more than one page of the cursor, and the count below is
8496        // thirty two, so ninety six is three pages and asks the same question.
8497        let fields = if cfg!(miri) { 96 } else { 500 };
8498        let mut f = Fixture::new();
8499        for i in 0..fields {
8500            let field = format!("field-{i}");
8501            let value = format!("value-{i}");
8502            f.run(&[b"HSET", b"h", field.as_bytes(), value.as_bytes()]);
8503        }
8504
8505        let mut seen: Vec<String> = Vec::new();
8506        let mut cursor = "0".to_owned();
8507        loop {
8508            let reply = f.run(&[b"HSCAN", b"h", cursor.as_bytes(), b"COUNT", b"32"]);
8509            let (next, items) = scan_reply(&reply);
8510            assert_eq!(items.len() % 2, 0, "a pair went out half written");
8511            for pair in items.chunks(2) {
8512                assert_eq!(
8513                    pair[0].strip_prefix("field-"),
8514                    pair[1].strip_prefix("value-"),
8515                    "a field came back with someone else's value"
8516                );
8517                seen.push(pair[0].clone());
8518            }
8519            cursor = next;
8520            if cursor == "0" {
8521                break;
8522            }
8523        }
8524        seen.sort();
8525        seen.dedup();
8526        assert_eq!(seen.len(), fields, "every field once and only once");
8527
8528        let (_, items) = scan_reply(&f.run(&[b"HSCAN", b"h", b"0", b"NOVALUES", b"COUNT", b"32"]));
8529        assert!(
8530            items.iter().all(|s| s.starts_with("field-")),
8531            "NOVALUES still sent the values"
8532        );
8533
8534        let last = fields - 1;
8535        let (_, one) = scan_reply(&f.run(&[
8536            b"HSCAN",
8537            b"h",
8538            b"0",
8539            b"MATCH",
8540            format!("field-{last}").as_bytes(),
8541            b"COUNT",
8542            b"1000",
8543        ]));
8544        assert_eq!(
8545            one,
8546            [format!("field-{last}"), format!("value-{last}")],
8547            "MATCH is on the field"
8548        );
8549    }
8550
8551    #[test]
8552    fn hrandfield_draws_what_it_was_asked_for_and_nests_values_on_resp3() {
8553        let mut f = Fixture::new();
8554        f.run(&[b"HSET", b"h", b"a", b"1"]);
8555        assert_eq!(f.run(&[b"HRANDFIELD", b"h"]), "$1\r\na\r\n");
8556        assert_eq!(f.run(&[b"HRANDFIELD", b"nokey"]), "$-1\r\n");
8557        assert_eq!(f.run(&[b"HRANDFIELD", b"nokey", b"3"]), "*0\r\n");
8558        assert_eq!(
8559            f.run(&[b"HRANDFIELD", b"h", b"3"]),
8560            "*1\r\n$1\r\na\r\n",
8561            "a positive count is capped at the size of the hash"
8562        );
8563        assert_eq!(
8564            f.run(&[b"HRANDFIELD", b"h", b"-3"]),
8565            "*3\r\n$1\r\na\r\n$1\r\na\r\n$1\r\na\r\n",
8566            "and a negative one repeats itself"
8567        );
8568        assert_eq!(
8569            f.run(&[b"HRANDFIELD", b"h", b"1", b"WITHVALUES"]),
8570            "*2\r\n$1\r\na\r\n$1\r\n1\r\n",
8571            "flat on RESP2"
8572        );
8573
8574        f.run(&[b"HELLO", b"3"]);
8575        assert_eq!(
8576            f.run(&[b"HRANDFIELD", b"h", b"1", b"WITHVALUES"]),
8577            "*1\r\n*2\r\n$1\r\na\r\n$1\r\n1\r\n",
8578            "and nested on RESP3, but still an array and never a map"
8579        );
8580    }
8581
8582    #[test]
8583    fn every_hash_command_says_wrongtype_and_writes_nothing() {
8584        let mut f = Fixture::new();
8585        f.run(&[b"SET", b"str", b"v"]);
8586        let wrong = "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n";
8587
8588        for cmd in [
8589            &[b"HSET".as_slice(), b"str", b"f", b"v"][..],
8590            &[b"HMSET".as_slice(), b"str", b"f", b"v"][..],
8591            &[b"HSETNX".as_slice(), b"str", b"f", b"v"][..],
8592            &[b"HGET".as_slice(), b"str", b"f"][..],
8593            &[b"HMGET".as_slice(), b"str", b"f"][..],
8594            &[b"HDEL".as_slice(), b"str", b"f"][..],
8595            &[b"HLEN".as_slice(), b"str"][..],
8596            &[b"HEXISTS".as_slice(), b"str", b"f"][..],
8597            &[b"HSTRLEN".as_slice(), b"str", b"f"][..],
8598            &[b"HGETALL".as_slice(), b"str"][..],
8599            &[b"HKEYS".as_slice(), b"str"][..],
8600            &[b"HVALS".as_slice(), b"str"][..],
8601            &[b"HINCRBY".as_slice(), b"str", b"f", b"1"][..],
8602            &[b"HINCRBYFLOAT".as_slice(), b"str", b"f", b"1"][..],
8603            &[b"HRANDFIELD".as_slice(), b"str"][..],
8604            &[b"HRANDFIELD".as_slice(), b"str", b"2"][..],
8605            &[b"HSCAN".as_slice(), b"str", b"0"][..],
8606        ] {
8607            let reply = f.run(cmd);
8608            assert_eq!(reply, wrong, "{:?}", cmd[0]);
8609        }
8610        assert_eq!(
8611            f.run(&[b"GET", b"str"]),
8612            "$1\r\nv\r\n",
8613            "and none of them touched the value"
8614        );
8615    }
8616
8617    #[test]
8618    fn a_hash_scan_leaves_nothing_half_written_when_its_arguments_are_wrong() {
8619        let mut f = Fixture::new();
8620        f.run(&[b"HSET", b"h", b"f", b"v"]);
8621        for bad in [
8622            &[b"HSCAN".as_slice(), b"h", b"abc"][..],
8623            &[b"HSCAN".as_slice(), b"h", b"0", b"COUNT", b"nope"][..],
8624            &[b"HSCAN".as_slice(), b"h", b"0", b"COUNT", b"0"][..],
8625            &[b"HSCAN".as_slice(), b"h", b"0", b"MATCH"][..],
8626        ] {
8627            let reply = f.run(bad);
8628            assert!(reply.starts_with("-ERR"), "got {reply}");
8629            assert!(!reply.contains('*'), "an array header went out in front");
8630        }
8631    }
8632
8633    #[test]
8634    fn a_field_deadline_goes_on_and_comes_back_in_all_four_units() {
8635        let mut f = Fixture::new();
8636        f.run(&[b"HSET", b"h", b"a", b"1", b"b", b"2"]);
8637        assert_eq!(
8638            f.run(&[b"HEXPIRE", b"h", b"100", b"FIELDS", b"1", b"a"]),
8639            "*1\r\n:1\r\n"
8640        );
8641        assert_eq!(
8642            f.run(&[b"HTTL", b"h", b"FIELDS", b"3", b"a", b"b", b"nope"]),
8643            "*3\r\n:100\r\n:-1\r\n:-2\r\n",
8644            "one answer per field, and the two sentinels are TTL's own"
8645        );
8646
8647        // The same deadline in the other three units, all of them derived from
8648        // the one number the store kept.
8649        let ms = int_reply(&f.run(&[b"HPTTL", b"h", b"FIELDS", b"1", b"a"]));
8650        assert!((99_000..=100_000).contains(&ms), "got {ms}");
8651        let at = int_reply(&f.run(&[b"HEXPIRETIME", b"h", b"FIELDS", b"1", b"a"]));
8652        let at_ms = int_reply(&f.run(&[b"HPEXPIRETIME", b"h", b"FIELDS", b"1", b"a"]));
8653        assert_eq!(at, at_ms.div_euclid(1000) + i64::from(at_ms % 1000 != 0));
8654        assert!(at_ms > 1_700_000_000_000, "an absolute moment, got {at_ms}");
8655
8656        assert_eq!(
8657            f.run(&[b"HPERSIST", b"h", b"FIELDS", b"3", b"a", b"b", b"nope"]),
8658            "*3\r\n:1\r\n:-1\r\n:-2\r\n",
8659            "one for the deadline taken off, and it does not say what it was"
8660        );
8661        assert_eq!(
8662            f.run(&[b"HTTL", b"h", b"FIELDS", b"1", b"a"]),
8663            "*1\r\n:-1\r\n"
8664        );
8665        assert_eq!(
8666            f.run(&[b"HGET", b"h", b"a"]),
8667            "$1\r\n1\r\n",
8668            "and the field is still there with the value it had"
8669        );
8670    }
8671
8672    #[test]
8673    fn a_deadline_that_has_already_gone_deletes_the_field_now() {
8674        let mut f = Fixture::new();
8675        f.run(&[b"HSET", b"h", b"a", b"1", b"b", b"2"]);
8676        assert_eq!(
8677            f.run(&[b"HEXPIREAT", b"h", b"1", b"FIELDS", b"1", b"a"]),
8678            "*1\r\n:2\r\n",
8679            "two, and not one, because nothing was stored"
8680        );
8681        assert_eq!(f.run(&[b"HGET", b"h", b"a"]), "$-1\r\n");
8682        assert_eq!(f.run(&[b"HLEN", b"h"]), ":1\r\n");
8683
8684        assert_eq!(
8685            f.run(&[b"HPEXPIREAT", b"h", b"1", b"FIELDS", b"1", b"b"]),
8686            "*1\r\n:2\r\n"
8687        );
8688        assert_eq!(
8689            f.run(&[b"EXISTS", b"h"]),
8690            ":0\r\n",
8691            "and the last field going took the key with it"
8692        );
8693
8694        // Zero is a delete and not an error, where minus one is an error. That
8695        // is Redis's split and it is easy to get backwards.
8696        f.run(&[b"HSET", b"h", b"a", b"1"]);
8697        assert_eq!(
8698            f.run(&[b"HEXPIRE", b"h", b"0", b"FIELDS", b"1", b"a"]),
8699            "*1\r\n:2\r\n"
8700        );
8701    }
8702
8703    #[test]
8704    fn a_field_is_gone_once_its_moment_passes() {
8705        let mut f = Fixture::new();
8706        f.run(&[b"HSET", b"h", b"a", b"1", b"b", b"2"]);
8707        assert_eq!(
8708            f.run(&[b"HPEXPIRE", b"h", b"20", b"FIELDS", b"1", b"a"]),
8709            "*1\r\n:1\r\n"
8710        );
8711        assert_eq!(f.run(&[b"HGET", b"h", b"a"]), "$1\r\n1\r\n", "not yet");
8712
8713        // Time moves once per turn of the event loop and nowhere else, so a
8714        // test moves it by hand rather than by sleeping. There is nothing to
8715        // sleep for: the deadline is a number and so is the clock.
8716        f.server.advance_clock_ms(60);
8717        assert_eq!(f.run(&[b"HLEN", b"h"]), ":1\r\n");
8718        assert_eq!(f.run(&[b"HGET", b"h", b"a"]), "$-1\r\n");
8719        assert_eq!(
8720            f.run(&[b"HGETALL", b"h"]),
8721            "*2\r\n$1\r\nb\r\n$1\r\n2\r\n",
8722            "and the walks do not hand back a field that has expired"
8723        );
8724    }
8725
8726    #[test]
8727    fn a_missing_key_answers_the_no_field_sentinel_for_every_field() {
8728        let mut f = Fixture::new();
8729        for cmd in [
8730            &[
8731                b"HEXPIRE".as_slice(),
8732                b"nokey",
8733                b"100",
8734                b"FIELDS",
8735                b"2",
8736                b"a",
8737                b"b",
8738            ][..],
8739            &[b"HTTL".as_slice(), b"nokey", b"FIELDS", b"2", b"a", b"b"][..],
8740            &[b"HPTTL".as_slice(), b"nokey", b"FIELDS", b"2", b"a", b"b"][..],
8741            &[
8742                b"HEXPIRETIME".as_slice(),
8743                b"nokey",
8744                b"FIELDS",
8745                b"2",
8746                b"a",
8747                b"b",
8748            ][..],
8749            &[
8750                b"HPERSIST".as_slice(),
8751                b"nokey",
8752                b"FIELDS",
8753                b"2",
8754                b"a",
8755                b"b",
8756            ][..],
8757        ] {
8758            assert_eq!(f.run(cmd), "*2\r\n:-2\r\n:-2\r\n", "{:?}", cmd[0]);
8759        }
8760    }
8761
8762    #[test]
8763    fn writing_a_field_clears_the_deadline_that_was_on_it() {
8764        let mut f = Fixture::new();
8765        f.run(&[b"HSET", b"h", b"a", b"1"]);
8766        f.run(&[b"HEXPIRE", b"h", b"100", b"FIELDS", b"1", b"a"]);
8767        f.run(&[b"HSET", b"h", b"a", b"2"]);
8768        assert_eq!(
8769            f.run(&[b"HTTL", b"h", b"FIELDS", b"1", b"a"]),
8770            "*1\r\n:-1\r\n",
8771            "Redis has done this since 7.4, and it is why HGETEX exists"
8772        );
8773    }
8774
8775    #[test]
8776    fn the_four_conditions_reach_the_store_the_way_they_were_written() {
8777        let mut f = Fixture::new();
8778        f.run(&[b"HSET", b"h", b"a", b"1"]);
8779        assert_eq!(
8780            f.run(&[b"HEXPIRE", b"h", b"100", b"XX", b"FIELDS", b"1", b"a"]),
8781            "*1\r\n:0\r\n",
8782            "XX on a field with no deadline changes nothing"
8783        );
8784        assert_eq!(
8785            f.run(&[b"HEXPIRE", b"h", b"100", b"NX", b"FIELDS", b"1", b"a"]),
8786            "*1\r\n:1\r\n"
8787        );
8788        assert_eq!(
8789            f.run(&[b"HEXPIRE", b"h", b"200", b"NX", b"FIELDS", b"1", b"a"]),
8790            "*1\r\n:0\r\n",
8791            "and NX will not move one that is already there"
8792        );
8793        assert_eq!(
8794            f.run(&[b"HEXPIRE", b"h", b"50", b"GT", b"FIELDS", b"1", b"a"]),
8795            "*1\r\n:0\r\n"
8796        );
8797        assert_eq!(
8798            f.run(&[b"HEXPIRE", b"h", b"500", b"GT", b"FIELDS", b"1", b"a"]),
8799            "*1\r\n:1\r\n"
8800        );
8801        assert_eq!(
8802            f.run(&[b"HEXPIRE", b"h", b"50", b"LT", b"FIELDS", b"1", b"a"]),
8803            "*1\r\n:1\r\n"
8804        );
8805        assert_eq!(
8806            f.run(&[b"HTTL", b"h", b"FIELDS", b"1", b"a"]),
8807            "*1\r\n:50\r\n"
8808        );
8809    }
8810
8811    #[test]
8812    fn the_field_ttl_family_leaves_nothing_half_written_on_a_bad_argument() {
8813        let mut f = Fixture::new();
8814        f.run(&[b"HSET", b"h", b"a", b"1"]);
8815        for (bad, want) in [
8816            (
8817                &[b"HEXPIRE".as_slice(), b"h", b"-1", b"FIELDS", b"1", b"a"][..],
8818                "-ERR invalid expire time, must be >= 0",
8819            ),
8820            (
8821                &[
8822                    b"HEXPIRE".as_slice(),
8823                    b"h",
8824                    b"9999999999999999",
8825                    b"FIELDS",
8826                    b"1",
8827                    b"a",
8828                ][..],
8829                "-ERR invalid expire time in 'hexpire' command",
8830            ),
8831            (
8832                &[b"HEXPIRE".as_slice(), b"h", b"100", b"FIELD", b"1", b"a"][..],
8833                "-ERR wrong number of arguments for 'hexpire' command",
8834            ),
8835            (
8836                &[b"HEXPIRE".as_slice(), b"h", b"100", b"FIELDS", b"0", b"a"][..],
8837                "-ERR Parameter `numFields` should be greater than 0",
8838            ),
8839            (
8840                &[b"HEXPIRE".as_slice(), b"h", b"100", b"FIELDS", b"2", b"a"][..],
8841                "-ERR wrong number of arguments",
8842            ),
8843            (
8844                &[b"HTTL".as_slice(), b"h", b"FIELDS", b"3", b"a", b"b"][..],
8845                "-ERR wrong number of arguments",
8846            ),
8847        ] {
8848            let reply = f.run(bad);
8849            assert!(reply.starts_with(want), "wanted {want}, got {reply}");
8850            assert!(!reply.contains('*'), "an array header went out in front");
8851        }
8852        assert_eq!(
8853            f.run(&[b"HTTL", b"h", b"FIELDS", b"1", b"a"]),
8854            "*1\r\n:-1\r\n",
8855            "and not one of them put a deadline on anything"
8856        );
8857    }
8858
8859    #[test]
8860    fn every_field_ttl_command_says_wrongtype_and_writes_nothing() {
8861        let mut f = Fixture::new();
8862        f.run(&[b"SET", b"str", b"v"]);
8863        let wrong = "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n";
8864
8865        for cmd in [
8866            &[b"HEXPIRE".as_slice(), b"str", b"100", b"FIELDS", b"1", b"f"][..],
8867            &[
8868                b"HPEXPIRE".as_slice(),
8869                b"str",
8870                b"100",
8871                b"FIELDS",
8872                b"1",
8873                b"f",
8874            ][..],
8875            &[
8876                b"HEXPIREAT".as_slice(),
8877                b"str",
8878                b"9999999999",
8879                b"FIELDS",
8880                b"1",
8881                b"f",
8882            ][..],
8883            &[
8884                b"HPEXPIREAT".as_slice(),
8885                b"str",
8886                b"9999999999999",
8887                b"FIELDS",
8888                b"1",
8889                b"f",
8890            ][..],
8891            &[b"HTTL".as_slice(), b"str", b"FIELDS", b"1", b"f"][..],
8892            &[b"HPTTL".as_slice(), b"str", b"FIELDS", b"1", b"f"][..],
8893            &[b"HEXPIRETIME".as_slice(), b"str", b"FIELDS", b"1", b"f"][..],
8894            &[b"HPEXPIRETIME".as_slice(), b"str", b"FIELDS", b"1", b"f"][..],
8895            &[b"HPERSIST".as_slice(), b"str", b"FIELDS", b"1", b"f"][..],
8896        ] {
8897            assert_eq!(f.run(cmd), wrong, "{:?}", cmd[0]);
8898        }
8899        assert_eq!(
8900            f.run(&[b"GET", b"str"]),
8901            "$1\r\nv\r\n",
8902            "and none of them touched the value"
8903        );
8904    }
8905
8906    #[test]
8907    fn hgetdel_hands_the_value_out_and_then_takes_the_field() {
8908        let mut f = Fixture::new();
8909        f.run(&[b"HSET", b"h", b"a", b"1", b"b", b"2"]);
8910        assert_eq!(
8911            f.run(&[b"HGETDEL", b"h", b"FIELDS", b"2", b"a", b"nope"]),
8912            "*2\r\n$1\r\n1\r\n$-1\r\n",
8913            "positional, so the field that was not there is a nil in its place"
8914        );
8915        assert_eq!(f.run(&[b"HLEN", b"h"]), ":1\r\n");
8916        assert_eq!(
8917            f.run(&[b"HGETDEL", b"nokey", b"FIELDS", b"1", b"a"]),
8918            "*1\r\n$-1\r\n"
8919        );
8920        assert_eq!(
8921            f.run(&[b"HGETDEL", b"h", b"FIELDS", b"1", b"b"]),
8922            "*1\r\n$1\r\n2\r\n"
8923        );
8924        assert_eq!(
8925            f.run(&[b"EXISTS", b"h"]),
8926            ":0\r\n",
8927            "and the last field took the key"
8928        );
8929    }
8930
8931    #[test]
8932    fn hgetex_reads_and_moves_the_deadline_in_one_command() {
8933        let mut f = Fixture::new();
8934        f.run(&[b"HSET", b"h", b"a", b"1"]);
8935        assert_eq!(
8936            f.run(&[b"HGETEX", b"h", b"FIELDS", b"1", b"a"]),
8937            "*1\r\n$1\r\n1\r\n"
8938        );
8939        assert_eq!(
8940            f.run(&[b"HTTL", b"h", b"FIELDS", b"1", b"a"]),
8941            "*1\r\n:-1\r\n",
8942            "no option means leave it alone, which is the one place this is not GETEX"
8943        );
8944
8945        f.run(&[b"HGETEX", b"h", b"EX", b"100", b"FIELDS", b"1", b"a"]);
8946        assert_eq!(
8947            f.run(&[b"HTTL", b"h", b"FIELDS", b"1", b"a"]),
8948            "*1\r\n:100\r\n"
8949        );
8950        f.run(&[b"HGETEX", b"h", b"FIELDS", b"1", b"a"]);
8951        assert_eq!(
8952            f.run(&[b"HTTL", b"h", b"FIELDS", b"1", b"a"]),
8953            "*1\r\n:100\r\n",
8954            "and a plain read really does leave it alone"
8955        );
8956        assert_eq!(
8957            f.run(&[b"HGETEX", b"h", b"PERSIST", b"FIELDS", b"1", b"a"]),
8958            "*1\r\n$1\r\n1\r\n"
8959        );
8960        assert_eq!(
8961            f.run(&[b"HTTL", b"h", b"FIELDS", b"1", b"a"]),
8962            "*1\r\n:-1\r\n"
8963        );
8964
8965        assert_eq!(
8966            f.run(&[b"HGETEX", b"h", b"EXAT", b"1", b"FIELDS", b"1", b"a"]),
8967            "*1\r\n$1\r\n1\r\n",
8968            "the value goes out before the deadline that has already gone is applied"
8969        );
8970        assert_eq!(f.run(&[b"EXISTS", b"h"]), ":0\r\n");
8971        assert_eq!(
8972            f.run(&[b"HGETEX", b"nokey", b"EX", b"100", b"FIELDS", b"1", b"a"]),
8973            "*1\r\n$-1\r\n"
8974        );
8975    }
8976
8977    #[test]
8978    fn hsetex_writes_all_of_it_or_none_of_it() {
8979        let mut f = Fixture::new();
8980        assert_eq!(
8981            f.run(&[b"HSETEX", b"h", b"FIELDS", b"1", b"a", b"1"]),
8982            ":1\r\n"
8983        );
8984        assert_eq!(f.run(&[b"HGET", b"h", b"a"]), "$1\r\n1\r\n");
8985        assert_eq!(
8986            f.run(&[
8987                b"HSETEX", b"h", b"FNX", b"FIELDS", b"2", b"a", b"9", b"new", b"9"
8988            ]),
8989            ":0\r\n",
8990            "FNX wants every field named to be missing"
8991        );
8992        assert_eq!(f.run(&[b"HGET", b"h", b"a"]), "$1\r\n1\r\n");
8993        assert_eq!(
8994            f.run(&[b"HEXISTS", b"h", b"new"]),
8995            ":0\r\n",
8996            "and none of the list was written"
8997        );
8998        assert_eq!(
8999            f.run(&[
9000                b"HSETEX", b"h", b"FXX", b"FIELDS", b"2", b"a", b"9", b"nope", b"9"
9001            ]),
9002            ":0\r\n",
9003            "and FXX wants every one of them to be there"
9004        );
9005        assert_eq!(f.run(&[b"HGET", b"h", b"a"]), "$1\r\n1\r\n");
9006        assert_eq!(
9007            f.run(&[b"HSETEX", b"h", b"FXX", b"FIELDS", b"1", b"a", b"9"]),
9008            ":1\r\n"
9009        );
9010        assert_eq!(f.run(&[b"HGET", b"h", b"a"]), "$1\r\n9\r\n");
9011
9012        assert_eq!(
9013            f.run(&[b"HSETEX", b"gone", b"FXX", b"FIELDS", b"1", b"a", b"1"]),
9014            ":0\r\n"
9015        );
9016        assert_eq!(
9017            f.run(&[b"EXISTS", b"gone"]),
9018            ":0\r\n",
9019            "a key with no fields cannot meet FXX and is not created trying"
9020        );
9021    }
9022
9023    #[test]
9024    fn hsetex_clears_the_deadline_unless_it_is_told_to_keep_it() {
9025        let mut f = Fixture::new();
9026        f.run(&[b"HSETEX", b"h", b"EX", b"100", b"FIELDS", b"1", b"a", b"1"]);
9027        assert_eq!(
9028            f.run(&[b"HTTL", b"h", b"FIELDS", b"1", b"a"]),
9029            "*1\r\n:100\r\n"
9030        );
9031
9032        f.run(&[b"HSETEX", b"h", b"KEEPTTL", b"FIELDS", b"1", b"a", b"2"]);
9033        assert_eq!(f.run(&[b"HGET", b"h", b"a"]), "$1\r\n2\r\n");
9034        assert_eq!(
9035            f.run(&[b"HTTL", b"h", b"FIELDS", b"1", b"a"]),
9036            "*1\r\n:100\r\n",
9037            "KEEPTTL put back what the write cleared"
9038        );
9039
9040        f.run(&[b"HSETEX", b"h", b"FIELDS", b"1", b"a", b"3"]);
9041        assert_eq!(
9042            f.run(&[b"HTTL", b"h", b"FIELDS", b"1", b"a"]),
9043            "*1\r\n:-1\r\n",
9044            "and without it a write clears the deadline the way HSET does"
9045        );
9046
9047        // Any order, because Redis reads these in a loop and not in a fixed
9048        // sequence.
9049        assert_eq!(
9050            f.run(&[
9051                b"HSETEX", b"h", b"PX", b"100000", b"FXX", b"FIELDS", b"1", b"a", b"4"
9052            ]),
9053            ":1\r\n"
9054        );
9055        assert_eq!(
9056            f.run(&[b"HTTL", b"h", b"FIELDS", b"1", b"a"]),
9057            "*1\r\n:100\r\n"
9058        );
9059
9060        assert_eq!(
9061            f.run(&[b"HSETEX", b"h", b"EXAT", b"1", b"FIELDS", b"1", b"a", b"5"]),
9062            ":1\r\n",
9063            "written, and not the separate code the HEXPIRE family has for this"
9064        );
9065        assert_eq!(
9066            f.run(&[b"EXISTS", b"h"]),
9067            ":0\r\n",
9068            "and storing it and then removing it emptied the hash"
9069        );
9070    }
9071
9072    #[test]
9073    fn the_last_three_hash_commands_word_their_mistakes_their_own_way() {
9074        let mut f = Fixture::new();
9075        f.run(&[b"HSET", b"h", b"a", b"1"]);
9076        for (bad, want) in [
9077            // HGETDEL has three sentences of its own for these three mistakes.
9078            (
9079                &[b"HGETDEL".as_slice(), b"h", b"FIELDS", b"0", b"a"][..],
9080                "-ERR Number of fields must be a positive integer",
9081            ),
9082            (
9083                &[b"HGETDEL".as_slice(), b"h", b"FIELDS", b"2", b"a"][..],
9084                "-ERR The `numfields` parameter must match the number of arguments",
9085            ),
9086            (
9087                &[b"HGETDEL".as_slice(), b"h", b"FIELD", b"1", b"a"][..],
9088                "-ERR Mandatory argument FIELDS is missing or not at the right position",
9089            ),
9090            // And HGETEX and HSETEX have three different ones between them.
9091            (
9092                &[b"HGETEX".as_slice(), b"h", b"FIELDS", b"0", b"a"][..],
9093                "-ERR invalid number of fields",
9094            ),
9095            (
9096                &[b"HGETEX".as_slice(), b"h", b"FIELDS", b"2", b"a"][..],
9097                "-ERR wrong number of arguments",
9098            ),
9099            (
9100                &[b"HGETEX".as_slice(), b"h", b"FIELD", b"1", b"a"][..],
9101                "-ERR unknown argument: FIELD",
9102            ),
9103            (
9104                &[
9105                    b"HGETEX".as_slice(),
9106                    b"h",
9107                    b"KEEPTTL",
9108                    b"FIELDS",
9109                    b"1",
9110                    b"a",
9111                ][..],
9112                "-ERR unknown argument: KEEPTTL",
9113            ),
9114            (
9115                &[
9116                    b"HGETEX".as_slice(),
9117                    b"h",
9118                    b"EX",
9119                    b"100",
9120                    b"PERSIST",
9121                    b"FIELDS",
9122                    b"1",
9123                    b"a",
9124                ][..],
9125                "-ERR Only one of EX, PX, EXAT, PXAT or PERSIST arguments can be specified",
9126            ),
9127            (
9128                &[
9129                    b"HSETEX".as_slice(),
9130                    b"h",
9131                    b"EX",
9132                    b"1",
9133                    b"KEEPTTL",
9134                    b"FIELDS",
9135                    b"1",
9136                    b"a",
9137                    b"1",
9138                ][..],
9139                "-ERR Only one of EX, PX, EXAT, PXAT or KEEPTTL arguments can be specified",
9140            ),
9141            (
9142                &[
9143                    b"HSETEX".as_slice(),
9144                    b"h",
9145                    b"FNX",
9146                    b"FXX",
9147                    b"FIELDS",
9148                    b"1",
9149                    b"a",
9150                    b"1",
9151                ][..],
9152                "-ERR Only one of FXX or FNX arguments can be specified",
9153            ),
9154            (
9155                &[
9156                    b"HSETEX".as_slice(),
9157                    b"h",
9158                    b"FIELDS",
9159                    b"2",
9160                    b"a",
9161                    b"1",
9162                    b"b",
9163                ][..],
9164                "-ERR wrong number of arguments",
9165            ),
9166            (
9167                &[
9168                    b"HGETEX".as_slice(),
9169                    b"h",
9170                    b"EX",
9171                    b"-1",
9172                    b"FIELDS",
9173                    b"1",
9174                    b"a",
9175                ][..],
9176                "-ERR invalid expire time, must be >= 0",
9177            ),
9178            (
9179                &[
9180                    b"HGETEX".as_slice(),
9181                    b"h",
9182                    b"PXAT",
9183                    b"99999999999999",
9184                    b"FIELDS",
9185                    b"1",
9186                    b"a",
9187                ][..],
9188                "-ERR invalid expire time in 'hgetex' command",
9189            ),
9190            (
9191                &[
9192                    b"HSETEX".as_slice(),
9193                    b"h",
9194                    b"EX",
9195                    b"abc",
9196                    b"FIELDS",
9197                    b"1",
9198                    b"a",
9199                    b"1",
9200                ][..],
9201                "-ERR value is not an integer or out of range",
9202            ),
9203        ] {
9204            let reply = f.run(bad);
9205            assert!(reply.starts_with(want), "wanted {want}, got {reply}");
9206            assert!(!reply.contains('*'), "an array header went out in front");
9207        }
9208        assert_eq!(
9209            f.run(&[b"HGET", b"h", b"a"]),
9210            "$1\r\n1\r\n",
9211            "and not one of them wrote anything"
9212        );
9213        assert_eq!(
9214            f.run(&[b"HTTL", b"h", b"FIELDS", b"1", b"a"]),
9215            "*1\r\n:-1\r\n"
9216        );
9217    }
9218
9219    #[test]
9220    fn the_last_three_hash_commands_say_wrongtype_and_write_nothing() {
9221        let mut f = Fixture::new();
9222        f.run(&[b"SET", b"str", b"v"]);
9223        let wrong = "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n";
9224        for cmd in [
9225            &[b"HGETDEL".as_slice(), b"str", b"FIELDS", b"1", b"f"][..],
9226            &[b"HGETEX".as_slice(), b"str", b"FIELDS", b"1", b"f"][..],
9227            &[
9228                b"HGETEX".as_slice(),
9229                b"str",
9230                b"EX",
9231                b"100",
9232                b"FIELDS",
9233                b"1",
9234                b"f",
9235            ][..],
9236            &[b"HSETEX".as_slice(), b"str", b"FIELDS", b"1", b"f", b"v"][..],
9237        ] {
9238            assert_eq!(f.run(cmd), wrong, "{:?}", cmd[0]);
9239        }
9240        assert_eq!(f.run(&[b"GET", b"str"]), "$1\r\nv\r\n");
9241    }
9242
9243    /// The two orders `HIMPORT` juggles, which are not the same order.
9244    ///
9245    /// Values arrive in the order the fields were declared in and the hash is
9246    /// built in sorted order, so the first value is not generally the first
9247    /// field. And the sort is by length before bytes, which nothing else here
9248    /// sorts names with: `b` comes before `aa` where a plain byte comparison
9249    /// would put `aa` first. Both read off 8.10.1.
9250    #[test]
9251    fn himport_writes_declared_values_into_sorted_fields() {
9252        let mut f = Fixture::new();
9253        assert_eq!(
9254            f.run(&[b"HIMPORT", b"PREPARE", b"shape", b"b", b"aa", b"a"]),
9255            "+OK\r\n"
9256        );
9257        assert_eq!(
9258            f.run(&[b"HIMPORT", b"SET", b"k", b"shape", b"1", b"2", b"3"]),
9259            "+OK\r\n"
9260        );
9261        assert_eq!(f.run(&[b"HKEYS", b"k"]), bulks(&["a", "b", "aa"]));
9262        assert_eq!(
9263            f.run(&[b"HGETALL", b"k"]),
9264            bulks(&["a", "3", "b", "1", "aa", "2"])
9265        );
9266    }
9267
9268    /// It replaces the key rather than writing over it, so a field the fieldset
9269    /// does not name is gone afterwards and so is the deadline.
9270    #[test]
9271    fn himport_set_replaces_the_whole_key() {
9272        let mut f = Fixture::new();
9273        f.run(&[b"HSET", b"k", b"gone", b"old", b"a", b"old"]);
9274        f.run(&[b"EXPIRE", b"k", b"100"]);
9275        f.run(&[b"HIMPORT", b"PREPARE", b"shape", b"a", b"b"]);
9276        assert_eq!(
9277            f.run(&[b"HIMPORT", b"SET", b"k", b"shape", b"1", b"2"]),
9278            "+OK\r\n"
9279        );
9280        assert_eq!(f.run(&[b"HGETALL", b"k"]), bulks(&["a", "1", "b", "2"]));
9281        assert_eq!(f.run(&[b"TTL", b"k"]), ":-1\r\n");
9282    }
9283
9284    /// A fieldset is connection state. `SELECT` leaves them alone and `RESET`
9285    /// throws them away, and a key built from one outlives it.
9286    #[test]
9287    fn himport_fieldsets_belong_to_the_connection_and_not_to_the_keyspace() {
9288        let mut f = Fixture::new();
9289        f.run(&[b"HIMPORT", b"PREPARE", b"shape", b"a"]);
9290        f.run(&[b"SELECT", b"1"]);
9291        assert_eq!(
9292            f.run(&[b"HIMPORT", b"SET", b"k", b"shape", b"1"]),
9293            "+OK\r\n"
9294        );
9295        f.run(&[b"SELECT", b"0"]);
9296        assert_eq!(f.run(&[b"RESET"]), "+RESET\r\n");
9297        assert_eq!(
9298            f.run(&[b"HIMPORT", b"SET", b"k2", b"shape", b"1"]),
9299            "-ERR no such fieldset\r\n"
9300        );
9301    }
9302
9303    /// Which complaint wins when a line is wrong in more than one place.
9304    ///
9305    /// The type of the key beats both of the others, so a `HIMPORT SET` against
9306    /// a string is a WRONGTYPE even when the fieldset is missing too, which is
9307    /// the ordering a real server has and not the one the argument order
9308    /// suggests.
9309    #[test]
9310    fn himport_complains_in_the_order_a_real_server_does() {
9311        let mut f = Fixture::new();
9312        f.run(&[b"SET", b"str", b"v"]);
9313        f.run(&[b"HIMPORT", b"PREPARE", b"shape", b"a", b"b"]);
9314        let wrong = "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n";
9315        assert_eq!(
9316            f.run(&[b"HIMPORT", b"SET", b"str", b"nope", b"1"]),
9317            wrong,
9318            "the type beats a missing fieldset"
9319        );
9320        assert_eq!(
9321            f.run(&[b"HIMPORT", b"SET", b"str", b"shape", b"1"]),
9322            wrong,
9323            "and it beats a value count that does not fit"
9324        );
9325        assert_eq!(
9326            f.run(&[b"HIMPORT", b"SET", b"k", b"nope", b"1"]),
9327            "-ERR no such fieldset\r\n"
9328        );
9329        // One sentence for too few and for too many alike.
9330        for values in [&[b"1".as_slice()][..], &[b"1".as_slice(), b"2", b"3"][..]] {
9331            let mut line: Vec<&[u8]> = vec![b"HIMPORT", b"SET", b"k", b"shape"];
9332            line.extend_from_slice(values);
9333            assert_eq!(
9334                f.run(&line),
9335                "-ERR value count does not match fieldset field count\r\n",
9336                "{} values into two fields",
9337                values.len()
9338            );
9339        }
9340        assert_eq!(f.run(&[b"EXISTS", b"k"]), ":0\r\n");
9341    }
9342
9343    /// The arity of each subcommand, and the unknown one.
9344    #[test]
9345    fn himport_checks_each_subcommand_count_under_its_own_name() {
9346        let mut f = Fixture::new();
9347        assert_eq!(
9348            f.run(&[b"HIMPORT"]),
9349            "-ERR wrong number of arguments for 'himport' command\r\n"
9350        );
9351        for (rest, name) in [
9352            (&["PREPARE"][..], "prepare"),
9353            (&["PREPARE", "fs"][..], "prepare"),
9354            (&["SET"][..], "set"),
9355            (&["SET", "k"][..], "set"),
9356            (&["SET", "k", "fs"][..], "set"),
9357            (&["DISCARD"][..], "discard"),
9358            (&["DISCARD", "a", "b"][..], "discard"),
9359            (&["DISCARDALL", "x"][..], "discardall"),
9360        ] {
9361            let mut line: Vec<&[u8]> = vec![b"HIMPORT"];
9362            line.extend(rest.iter().map(|a| a.as_bytes()));
9363            assert_eq!(
9364                f.run(&line),
9365                format!("-ERR wrong number of arguments for 'himport|{name}' command\r\n"),
9366                "HIMPORT {}",
9367                rest.join(" ")
9368            );
9369        }
9370        assert_eq!(
9371            f.run(&[b"HIMPORT", b"NOPE", b"x"]),
9372            "-ERR unknown subcommand 'NOPE'. Try HIMPORT HELP.\r\n"
9373        );
9374    }
9375
9376    /// A `PREPARE` that fails leaves the name pointing where it pointed, which
9377    /// is the answer of the two that could not be guessed from outside.
9378    #[test]
9379    fn a_failed_himport_prepare_leaves_the_old_fieldset_alone() {
9380        let mut f = Fixture::new();
9381        f.run(&[b"HIMPORT", b"PREPARE", b"shape", b"a", b"b"]);
9382        assert_eq!(
9383            f.run(&[b"HIMPORT", b"PREPARE", b"shape", b"c", b"c"]),
9384            "-ERR duplicate field name in fieldset\r\n"
9385        );
9386        assert_eq!(
9387            f.run(&[b"HIMPORT", b"SET", b"k", b"shape", b"1", b"2"]),
9388            "+OK\r\n"
9389        );
9390        assert_eq!(f.run(&[b"HGETALL", b"k"]), bulks(&["a", "1", "b", "2"]));
9391    }
9392
9393    /// Preparing the same name twice replaces it, and the two discards count
9394    /// what they took rather than answering OK.
9395    #[test]
9396    fn himport_prepare_replaces_and_the_discards_count() {
9397        let mut f = Fixture::new();
9398        f.run(&[b"HIMPORT", b"PREPARE", b"shape", b"a", b"b"]);
9399        f.run(&[b"HIMPORT", b"PREPARE", b"shape", b"z"]);
9400        assert_eq!(
9401            f.run(&[b"HIMPORT", b"SET", b"k", b"shape", b"1"]),
9402            "+OK\r\n"
9403        );
9404        assert_eq!(f.run(&[b"HGETALL", b"k"]), bulks(&["z", "1"]));
9405
9406        assert_eq!(f.run(&[b"HIMPORT", b"DISCARD", b"shape"]), ":1\r\n");
9407        assert_eq!(f.run(&[b"HIMPORT", b"DISCARD", b"shape"]), ":0\r\n");
9408        f.run(&[b"HIMPORT", b"PREPARE", b"one", b"a"]);
9409        f.run(&[b"HIMPORT", b"PREPARE", b"two", b"a"]);
9410        assert_eq!(f.run(&[b"HIMPORT", b"DISCARDALL"]), ":2\r\n");
9411        assert_eq!(f.run(&[b"HIMPORT", b"DISCARDALL"]), ":0\r\n");
9412    }
9413
9414    /// The one integer of a single element array reply.
9415    /// The number out of a plain integer reply.
9416    ///
9417    /// [`int_reply`] is the same thing wrapped in a one element array, which is
9418    /// the shape every hash field command answers in.
9419    fn int(reply: &str) -> i64 {
9420        let body = reply
9421            .strip_prefix(':')
9422            .and_then(|s| s.strip_suffix("\r\n"))
9423            .unwrap_or_else(|| panic!("wanted an integer, got {reply}"));
9424        body.parse().expect("an integer")
9425    }
9426
9427    fn int_reply(reply: &str) -> i64 {
9428        let body = reply
9429            .strip_prefix("*1\r\n:")
9430            .and_then(|s| s.strip_suffix("\r\n"))
9431            .unwrap_or_else(|| panic!("wanted one integer, got {reply}"));
9432        body.parse().expect("an integer")
9433    }
9434
9435    /// The cursor and the flat items of a scan reply.
9436    fn scan_reply(reply: &str) -> (String, Vec<String>) {
9437        let mut lines = reply.split("\r\n");
9438        assert_eq!(lines.next(), Some("*2"), "got {reply}");
9439        lines.next().expect("the cursor header");
9440        let cursor = lines.next().expect("a cursor").to_owned();
9441        let header = lines.next().expect("an item count");
9442        let n: usize = header[1..].parse().expect("a count");
9443        let mut items = Vec::with_capacity(n);
9444        for _ in 0..n {
9445            lines.next().expect("an item header");
9446            items.push(lines.next().expect("an item").to_owned());
9447        }
9448        (cursor, items)
9449    }
9450
9451    /// The members of a set reply, sorted, since none of these promise an
9452    /// order and a test that asserted one would be asserting an accident.
9453    fn sorted(reply: &str) -> Vec<String> {
9454        let mut lines = reply.split("\r\n");
9455        let header = lines.next().expect("a header");
9456        assert!(
9457            header.starts_with('*') || header.starts_with('~'),
9458            "got {reply}"
9459        );
9460        let n: usize = header[1..].parse().expect("a member count");
9461        let mut got = Vec::with_capacity(n);
9462        for _ in 0..n {
9463            lines.next().expect("a member header");
9464            got.push(lines.next().expect("a member").to_owned());
9465        }
9466        got.sort();
9467        got
9468    }
9469
9470    #[test]
9471    fn the_algebra_answers_what_the_sets_share_and_do_not() {
9472        let mut f = Fixture::new();
9473        f.run(&[b"SADD", b"a", b"1", b"2", b"3"]);
9474        f.run(&[b"SADD", b"b", b"2", b"3", b"4"]);
9475        f.run(&[b"SADD", b"c", b"3", b"4", b"5"]);
9476
9477        assert_eq!(sorted(&f.run(&[b"SINTER", b"a", b"b", b"c"])), ["3"]);
9478        assert_eq!(
9479            sorted(&f.run(&[b"SUNION", b"a", b"b", b"c"])),
9480            ["1", "2", "3", "4", "5"]
9481        );
9482        assert_eq!(sorted(&f.run(&[b"SDIFF", b"a", b"b"])), ["1"]);
9483        assert_eq!(sorted(&f.run(&[b"SINTER", b"a"])), ["1", "2", "3"]);
9484
9485        // A key that is not there is an empty set, which empties an
9486        // intersection and does nothing at all to a union.
9487        assert_eq!(f.run(&[b"SINTER", b"a", b"nope"]), "*0\r\n");
9488        assert_eq!(sorted(&f.run(&[b"SUNION", b"a", b"nope"])), ["1", "2", "3"]);
9489        assert_eq!(f.run(&[b"SDIFF", b"nope", b"a"]), "*0\r\n");
9490        assert_eq!(f.run(&[b"DBSIZE"]), ":3\r\n", "and none of it made a key");
9491    }
9492
9493    #[test]
9494    fn the_algebra_answers_a_set_on_resp3_and_an_array_on_resp2() {
9495        let mut f = Fixture::new();
9496        f.run(&[b"SADD", b"a", b"x"]);
9497        assert_eq!(f.run(&[b"SINTER", b"a"]), "*1\r\n$1\r\nx\r\n");
9498        assert_eq!(f.run(&[b"SUNION", b"a"]), "*1\r\n$1\r\nx\r\n");
9499        assert_eq!(f.run(&[b"SDIFF", b"a"]), "*1\r\n$1\r\nx\r\n");
9500
9501        f.run(&[b"HELLO", b"3"]);
9502        assert_eq!(f.run(&[b"SINTER", b"a"]), "~1\r\n$1\r\nx\r\n");
9503        assert_eq!(f.run(&[b"SUNION", b"a"]), "~1\r\n$1\r\nx\r\n");
9504        assert_eq!(f.run(&[b"SDIFF", b"a"]), "~1\r\n$1\r\nx\r\n");
9505        assert_eq!(f.run(&[b"SINTER", b"nope"]), "~0\r\n");
9506    }
9507
9508    #[test]
9509    fn a_store_form_writes_a_key_and_answers_how_big_it_is() {
9510        let mut f = Fixture::new();
9511        f.run(&[b"SADD", b"a", b"1", b"2", b"3"]);
9512        f.run(&[b"SADD", b"b", b"2", b"3", b"4"]);
9513
9514        assert_eq!(f.run(&[b"SINTERSTORE", b"d", b"a", b"b"]), ":2\r\n");
9515        assert_eq!(sorted(&f.run(&[b"SMEMBERS", b"d"])), ["2", "3"]);
9516        assert_eq!(f.run(&[b"SUNIONSTORE", b"d", b"a", b"b"]), ":4\r\n");
9517        assert_eq!(sorted(&f.run(&[b"SMEMBERS", b"d"])), ["1", "2", "3", "4"]);
9518        assert_eq!(f.run(&[b"SDIFFSTORE", b"d", b"a", b"b"]), ":1\r\n");
9519        assert_eq!(f.run(&[b"SMEMBERS", b"d"]), "*1\r\n$1\r\n1\r\n");
9520
9521        // An empty answer deletes the destination rather than leaving an empty
9522        // set behind, and the destination may be one of the sources.
9523        assert_eq!(f.run(&[b"SDIFFSTORE", b"d", b"a", b"a"]), ":0\r\n");
9524        assert_eq!(f.run(&[b"EXISTS", b"d"]), ":0\r\n");
9525        assert_eq!(f.run(&[b"SINTERSTORE", b"a", b"a", b"b"]), ":2\r\n");
9526        assert_eq!(sorted(&f.run(&[b"SMEMBERS", b"a"])), ["2", "3"]);
9527
9528        // And a destination holding something else is overwritten, the same way
9529        // SET overwrites, rather than refused.
9530        f.run(&[b"SET", b"str", b"v"]);
9531        assert_eq!(f.run(&[b"SUNIONSTORE", b"str", b"b"]), ":3\r\n");
9532        assert_eq!(f.run(&[b"TYPE", b"str"]), "+set\r\n");
9533    }
9534
9535    #[test]
9536    fn sintercard_counts_without_building_and_stops_at_a_limit() {
9537        let mut f = Fixture::new();
9538        f.run(&[b"SADD", b"a", b"1", b"2", b"3", b"4"]);
9539        f.run(&[b"SADD", b"b", b"2", b"3", b"4", b"5"]);
9540
9541        assert_eq!(f.run(&[b"SINTERCARD", b"2", b"a", b"b"]), ":3\r\n");
9542        assert_eq!(
9543            f.run(&[b"SINTERCARD", b"2", b"a", b"b", b"LIMIT", b"2"]),
9544            ":2\r\n"
9545        );
9546        assert_eq!(
9547            f.run(&[b"SINTERCARD", b"2", b"a", b"b", b"LIMIT", b"0"]),
9548            ":3\r\n",
9549            "a limit of zero is no limit"
9550        );
9551        assert_eq!(f.run(&[b"SINTERCARD", b"1", b"a"]), ":4\r\n");
9552        assert_eq!(f.run(&[b"SINTERCARD", b"2", b"a", b"nope"]), ":0\r\n");
9553
9554        // The counted keys are what make its three error messages its own.
9555        assert_eq!(
9556            f.run(&[b"SINTERCARD", b"0", b"a"]),
9557            "-ERR numkeys should be greater than 0\r\n"
9558        );
9559        assert_eq!(
9560            f.run(&[b"SINTERCARD", b"abc", b"a"]),
9561            "-ERR numkeys should be greater than 0\r\n"
9562        );
9563        assert_eq!(
9564            f.run(&[b"SINTERCARD", b"3", b"a", b"b"]),
9565            "-ERR Number of keys can't be greater than number of args\r\n"
9566        );
9567        assert_eq!(
9568            f.run(&[b"SINTERCARD", b"2", b"a", b"b", b"LIMIT", b"-1"]),
9569            "-ERR LIMIT can't be negative\r\n"
9570        );
9571        assert_eq!(
9572            f.run(&[b"SINTERCARD", b"2", b"a", b"b", b"NOPE", b"1"]),
9573            "-ERR syntax error\r\n"
9574        );
9575        // A key really can be called LIMIT, which is why the count exists.
9576        f.run(&[b"SADD", b"LIMIT", b"2"]);
9577        assert_eq!(f.run(&[b"SINTERCARD", b"2", b"a", b"LIMIT"]), ":1\r\n");
9578    }
9579
9580    /// The two Redis 8.10 added, which are SINTERCARD's shape over a union and
9581    /// over a difference. Every number here was read off 8.10.1 first.
9582    #[test]
9583    fn sunioncard_and_sdiffcard_count_without_building() {
9584        let mut f = Fixture::new();
9585        f.run(&[b"SADD", b"a", b"1", b"2", b"3", b"4"]);
9586        f.run(&[b"SADD", b"b", b"3", b"4", b"5", b"6"]);
9587
9588        assert_eq!(f.run(&[b"SUNIONCARD", b"2", b"a", b"b"]), ":6\r\n");
9589        assert_eq!(
9590            f.run(&[b"SUNIONCARD", b"2", b"a", b"b", b"LIMIT", b"2"]),
9591            ":2\r\n"
9592        );
9593        assert_eq!(
9594            f.run(&[b"SUNIONCARD", b"2", b"a", b"b", b"LIMIT", b"0"]),
9595            ":6\r\n",
9596            "a limit of zero is no limit"
9597        );
9598        assert_eq!(f.run(&[b"SUNIONCARD", b"1", b"a"]), ":4\r\n");
9599        assert_eq!(
9600            f.run(&[b"SUNIONCARD", b"2", b"a", b"nope"]),
9601            ":4\r\n",
9602            "a missing key adds nothing to a union"
9603        );
9604
9605        assert_eq!(f.run(&[b"SDIFFCARD", b"2", b"a", b"b"]), ":2\r\n");
9606        assert_eq!(
9607            f.run(&[b"SDIFFCARD", b"2", b"a", b"b", b"LIMIT", b"1"]),
9608            ":1\r\n"
9609        );
9610        assert_eq!(
9611            f.run(&[b"SDIFFCARD", b"2", b"b", b"a"]),
9612            ":2\r\n",
9613            "a difference is not symmetric"
9614        );
9615        assert_eq!(f.run(&[b"SDIFFCARD", b"1", b"a"]), ":4\r\n");
9616        assert_eq!(f.run(&[b"SDIFFCARD", b"2", b"a", b"nope"]), ":4\r\n");
9617        assert_eq!(
9618            f.run(&[b"SDIFFCARD", b"2", b"nope", b"a"]),
9619            ":0\r\n",
9620            "nothing taken away from nothing"
9621        );
9622
9623        // The same three messages SINTERCARD has, because the line is the same
9624        // line and is parsed once for all three.
9625        for name in [b"SUNIONCARD".as_slice(), b"SDIFFCARD".as_slice()] {
9626            assert_eq!(
9627                f.run(&[name, b"0", b"a"]),
9628                "-ERR numkeys should be greater than 0\r\n"
9629            );
9630            assert_eq!(
9631                f.run(&[name, b"abc", b"a"]),
9632                "-ERR numkeys should be greater than 0\r\n"
9633            );
9634            assert_eq!(
9635                f.run(&[name, b"-1", b"a"]),
9636                "-ERR numkeys should be greater than 0\r\n"
9637            );
9638            assert_eq!(
9639                f.run(&[name, b"3", b"a", b"b"]),
9640                "-ERR Number of keys can't be greater than number of args\r\n"
9641            );
9642            assert_eq!(
9643                f.run(&[name, b"2", b"a", b"b", b"LIMIT", b"-1"]),
9644                "-ERR LIMIT can't be negative\r\n"
9645            );
9646            assert_eq!(
9647                f.run(&[name, b"2", b"a", b"b", b"LIMIT", b"abc"]),
9648                "-ERR LIMIT can't be negative\r\n",
9649                "a LIMIT that is not a number gets the negative message too"
9650            );
9651            assert_eq!(
9652                f.run(&[name, b"2", b"a", b"b", b"NOPE", b"1"]),
9653                "-ERR syntax error\r\n"
9654            );
9655            assert_eq!(
9656                f.run(&[name, b"2", b"a", b"b", b"LIMIT"]),
9657                "-ERR syntax error\r\n"
9658            );
9659            assert_eq!(
9660                f.run(&[name, b"2", b"a", b"b", b"LIMIT", b"1", b"X"]),
9661                "-ERR syntax error\r\n"
9662            );
9663        }
9664
9665        // And a key called LIMIT is a key, here as much as on SINTERCARD.
9666        f.run(&[b"SADD", b"LIMIT", b"2"]);
9667        assert_eq!(f.run(&[b"SUNIONCARD", b"2", b"a", b"LIMIT"]), ":4\r\n");
9668        assert_eq!(f.run(&[b"SDIFFCARD", b"2", b"a", b"LIMIT"]), ":3\r\n");
9669    }
9670
9671    #[test]
9672    fn the_algebra_answers_wrongtype_before_it_writes_anything() {
9673        let mut f = Fixture::new();
9674        f.run(&[b"SADD", b"a", b"1"]);
9675        f.run(&[b"SADD", b"d", b"old"]);
9676        f.run(&[b"SET", b"str", b"v"]);
9677
9678        let wrong = "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n";
9679        for bad in [
9680            &[b"SINTER".as_slice(), b"a", b"str"][..],
9681            &[b"SUNION".as_slice(), b"str"][..],
9682            &[b"SDIFF".as_slice(), b"a", b"str"][..],
9683            &[b"SINTERCARD".as_slice(), b"2", b"a", b"str"][..],
9684            &[b"SINTERSTORE".as_slice(), b"d", b"a", b"str"][..],
9685            &[b"SUNIONSTORE".as_slice(), b"d", b"str"][..],
9686            &[b"SDIFFSTORE".as_slice(), b"d", b"a", b"str"][..],
9687        ] {
9688            let reply = f.run(bad);
9689            assert_eq!(reply, wrong, "for {:?}", bad[0]);
9690        }
9691        assert_eq!(
9692            f.run(&[b"SMEMBERS", b"d"]),
9693            "*1\r\n$3\r\nold\r\n",
9694            "and the destination was left alone every time"
9695        );
9696    }
9697
9698    /// The leak a set can spring that nothing on the wire would ever show: the
9699    /// key goes, the body does not, and `DBSIZE` looks right the whole time.
9700    /// Not under Miri. What this claims is that memory does not grow over two
9701    /// hundred passes, so the passes are the claim rather than the way it
9702    /// happens to be written, and two hundred passes of a two hundred member
9703    /// collection is forty thousand trips through dispatch, which is what an
9704    /// interpreter charges for. A count small enough to run there would leave a
9705    /// server that reclaims nothing inside the bound and the test would pass on
9706    /// a leak. Nothing about memory safety goes uninterpreted either way: this
9707    /// is an accounting claim, and the same commands are run a few at a time by
9708    /// the tests around it.
9709    #[cfg_attr(miri, ignore = "the volume is the claim")]
9710    #[test]
9711    fn churning_sets_does_not_grow_the_server() {
9712        let mut f = Fixture::new();
9713        let members: Vec<Vec<u8>> = (0..200).map(|i| format!("m{i}").into_bytes()).collect();
9714        let args: Vec<&[u8]> = std::iter::once(&b"SADD"[..])
9715            .chain(std::iter::once(&b"s"[..]))
9716            .chain(members.iter().map(Vec::as_slice))
9717            .collect();
9718
9719        f.run(&args);
9720        f.run(&[b"DEL", b"s"]);
9721        f.server.compact_step();
9722        let after_first = f.server.memory_bytes();
9723
9724        for _ in 0..200 {
9725            f.run(&args);
9726            f.run(&[b"DEL", b"s"]);
9727            f.server.compact_step();
9728        }
9729        assert_eq!(f.run(&[b"DBSIZE"]), ":0\r\n");
9730        assert!(
9731            f.server.memory_bytes() <= after_first * 2,
9732            "held {} after two hundred passes against {after_first} after one",
9733            f.server.memory_bytes()
9734        );
9735    }
9736
9737    // --------------------------------------------------------------- bitmaps
9738
9739    /// The two single bit commands, and the encoding rule underneath them.
9740    ///
9741    /// A write always leaves the value `raw` and a read never re-encodes, which
9742    /// is why the `int` key here is still `int` after a `GETBIT` and is `raw`
9743    /// with its first digit changed after a `SETBIT`.
9744    #[test]
9745    fn a_bit_is_written_and_read_back_and_a_write_unpacks_an_int() {
9746        let mut f = Fixture::new();
9747        assert_eq!(f.run(&[b"SETBIT", b"k", b"7", b"1"]), ":0\r\n");
9748        assert_eq!(f.run(&[b"GET", b"k"]), "$1\r\n\u{1}\r\n");
9749        assert_eq!(f.run(&[b"GETBIT", b"k", b"7"]), ":1\r\n");
9750        assert_eq!(f.run(&[b"GETBIT", b"k", b"6"]), ":0\r\n");
9751        assert_eq!(f.run(&[b"GETBIT", b"k", b"100"]), ":0\r\n");
9752        assert_eq!(f.run(&[b"SETBIT", b"k", b"7", b"0"]), ":1\r\n");
9753
9754        // Writing a nought past the end still creates the key and still pads.
9755        assert_eq!(f.run(&[b"SETBIT", b"nk", b"0", b"0"]), ":0\r\n");
9756        assert_eq!(f.run(&[b"STRLEN", b"nk"]), ":1\r\n");
9757        assert_eq!(f.run(&[b"OBJECT", b"ENCODING", b"nk"]), "$3\r\nraw\r\n");
9758
9759        f.run(&[b"SET", b"num", b"12345"]);
9760        assert_eq!(f.run(&[b"GETBIT", b"num", b"1"]), ":0\r\n");
9761        assert_eq!(f.run(&[b"OBJECT", b"ENCODING", b"num"]), "$3\r\nint\r\n");
9762        assert_eq!(f.run(&[b"SETBIT", b"num", b"1", b"1"]), ":0\r\n");
9763        assert_eq!(f.run(&[b"OBJECT", b"ENCODING", b"num"]), "$3\r\nraw\r\n");
9764        assert_eq!(f.run(&[b"GET", b"num"]), "$5\r\nq2345\r\n");
9765    }
9766
9767    /// Counting, in bytes and in bits.
9768    ///
9769    /// The `0 -5 BIT` row is 25 on a real 8.10.1 and Redis's own documentation
9770    /// says 22 for it. The server is the thing being copied here.
9771    #[test]
9772    fn bits_are_counted_over_a_range_of_bytes_or_of_bits() {
9773        let mut f = Fixture::new();
9774        f.run(&[b"SET", b"mykey", b"foobar"]);
9775        assert_eq!(f.run(&[b"BITCOUNT", b"mykey"]), ":26\r\n");
9776        assert_eq!(f.run(&[b"BITCOUNT", b"mykey", b"0", b"0"]), ":4\r\n");
9777        assert_eq!(f.run(&[b"BITCOUNT", b"mykey", b"1", b"1"]), ":6\r\n");
9778        assert_eq!(
9779            f.run(&[b"BITCOUNT", b"mykey", b"1", b"1", b"BYTE"]),
9780            ":6\r\n"
9781        );
9782        assert_eq!(
9783            f.run(&[b"BITCOUNT", b"mykey", b"0", b"-5", b"BIT"]),
9784            ":25\r\n"
9785        );
9786        assert_eq!(
9787            f.run(&[b"BITCOUNT", b"mykey", b"5", b"30", b"BIT"]),
9788            ":17\r\n"
9789        );
9790        assert_eq!(f.run(&[b"BITCOUNT", b"nokey"]), ":0\r\n");
9791
9792        // A start past the end is left where it is and the end is pulled back,
9793        // so the range comes out backwards and counts nothing.
9794        assert_eq!(f.run(&[b"BITCOUNT", b"mykey", b"10", b"20"]), ":0\r\n");
9795
9796        // A lone start is a syntax error here, where BITPOS allows it.
9797        assert_eq!(
9798            f.run(&[b"BITCOUNT", b"mykey", b"0"]),
9799            "-ERR syntax error\r\n"
9800        );
9801        assert_eq!(
9802            f.run(&[b"BITCOUNT", b"mykey", b"0", b"1", b"NIB"]),
9803            "-ERR syntax error\r\n"
9804        );
9805    }
9806
9807    /// Searching, and the one place a miss is not minus one.
9808    ///
9809    /// A search for a nought that runs to the end of the string answers the
9810    /// length in bits, because the string is treated as if it had noughts after
9811    /// it forever. Give it an explicit end and it answers minus one instead.
9812    #[test]
9813    fn a_search_for_a_nought_past_the_end_answers_the_length_in_bits() {
9814        let mut f = Fixture::new();
9815        f.run(&[b"SET", b"ones", b"\xff\xff\xff"]);
9816        assert_eq!(f.run(&[b"BITPOS", b"ones", b"0"]), ":24\r\n");
9817        assert_eq!(f.run(&[b"BITPOS", b"ones", b"0", b"0"]), ":24\r\n");
9818        assert_eq!(f.run(&[b"BITPOS", b"ones", b"0", b"0", b"-1"]), ":-1\r\n");
9819        assert_eq!(f.run(&[b"BITPOS", b"ones", b"0", b"0", b"3"]), ":-1\r\n");
9820        assert_eq!(f.run(&[b"BITPOS", b"ones", b"1"]), ":0\r\n");
9821
9822        f.run(&[b"SET", b"mid", b"\x00\xff\xf0"]);
9823        assert_eq!(f.run(&[b"BITPOS", b"mid", b"1", b"0"]), ":8\r\n");
9824        assert_eq!(f.run(&[b"BITPOS", b"mid", b"1", b"2"]), ":16\r\n");
9825        assert_eq!(
9826            f.run(&[b"BITPOS", b"mid", b"1", b"0", b"-1", b"BIT"]),
9827            ":8\r\n"
9828        );
9829
9830        // A missing key is all noughts, so a one is never found and a nought is
9831        // at position zero.
9832        assert_eq!(f.run(&[b"BITPOS", b"gone", b"1"]), ":-1\r\n");
9833        assert_eq!(f.run(&[b"BITPOS", b"gone", b"0"]), ":0\r\n");
9834    }
9835
9836    /// The eight operations, with the answers a real server gives for them.
9837    #[test]
9838    fn the_eight_combinations_write_what_a_real_server_writes() {
9839        let mut f = Fixture::new();
9840        f.run(&[b"SET", b"a", b"abc"]);
9841        f.run(&[b"SET", b"b", b"abd"]);
9842        let cases: &[(&[u8], &str)] = &[
9843            (b"AND", "ab`"),
9844            (b"OR", "abg"),
9845            (b"XOR", "\u{0}\u{0}\u{7}"),
9846            (b"DIFF", "\u{0}\u{0}\u{3}"),
9847            (b"DIFF1", "\u{0}\u{0}\u{4}"),
9848            (b"ANDOR", "ab`"),
9849            (b"ONE", "\u{0}\u{0}\u{7}"),
9850        ];
9851        for (op, want) in cases {
9852            assert_eq!(f.run(&[b"BITOP", op, b"d", b"a", b"b"]), ":3\r\n", "{op:?}");
9853            assert_eq!(
9854                f.run(&[b"GET", b"d"]),
9855                format!("$3\r\n{want}\r\n"),
9856                "{op:?}"
9857            );
9858        }
9859        // The one whose answer is not text, so it is compared as bytes.
9860        assert_eq!(f.run(&[b"BITOP", b"NOT", b"d", b"a"]), ":3\r\n");
9861        assert_eq!(f.raw(&[b"GET", b"d"]), b"$3\r\n\x9e\x9d\x9c\r\n".to_vec());
9862
9863        // A missing source is a string of noughts as long as it needs to be, so
9864        // an AND against one writes three zero bytes rather than nothing.
9865        assert_eq!(f.run(&[b"BITOP", b"AND", b"d", b"a", b"gone"]), ":3\r\n");
9866        assert_eq!(f.run(&[b"GET", b"d"]), "$3\r\n\u{0}\u{0}\u{0}\r\n");
9867
9868        // Every source missing is an empty result, and an empty result takes
9869        // the destination with it.
9870        f.run(&[b"SET", b"dest", b"x"]);
9871        assert_eq!(f.run(&[b"BITOP", b"AND", b"dest", b"g1", b"g2"]), ":0\r\n");
9872        assert_eq!(f.run(&[b"EXISTS", b"dest"]), ":0\r\n");
9873    }
9874
9875    /// What `BITOP` says when it is asked for something it cannot do.
9876    #[test]
9877    fn bitop_names_the_operation_in_its_own_complaints() {
9878        let mut f = Fixture::new();
9879        f.run(&[b"SET", b"a", b"abc"]);
9880        assert_eq!(
9881            f.run(&[b"BITOP", b"nope", b"d", b"a"]),
9882            "-ERR syntax error\r\n"
9883        );
9884        assert_eq!(
9885            f.run(&[b"BITOP", b"NOT", b"d", b"a", b"a"]),
9886            "-ERR BITOP NOT must be called with a single source key.\r\n"
9887        );
9888        for op in [&b"DIFF"[..], b"DIFF1", b"ANDOR"] {
9889            assert_eq!(
9890                f.run(&[b"BITOP", op, b"d", b"a"]),
9891                format!(
9892                    "-ERR BITOP {} must be called with at least two source keys.\r\n",
9893                    String::from_utf8_lossy(op)
9894                )
9895            );
9896        }
9897        f.run(&[b"LPUSH", b"l", b"x"]);
9898        assert_eq!(
9899            f.run(&[b"BITOP", b"AND", b"d", b"a", b"l"]),
9900            "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n"
9901        );
9902    }
9903
9904    /// Packed fields, the three overflow policies and the `#` offset.
9905    #[test]
9906    fn bitfield_reads_and_writes_packed_fields() {
9907        let mut f = Fixture::new();
9908        assert_eq!(f.run(&[b"BITFIELD", b"bf"]), "*0\r\n");
9909        assert_eq!(f.run(&[b"EXISTS", b"bf"]), ":0\r\n");
9910
9911        assert_eq!(
9912            f.run(&[
9913                b"BITFIELD",
9914                b"bf",
9915                b"INCRBY",
9916                b"u2",
9917                b"100",
9918                b"1",
9919                b"GET",
9920                b"u4",
9921                b"0"
9922            ]),
9923            "*2\r\n:1\r\n:0\r\n"
9924        );
9925        // The field at bit 100 is two bits wide, so it ends in the thirteenth
9926        // byte and the value grew to thirteen bytes to hold it.
9927        assert_eq!(f.run(&[b"STRLEN", b"bf"]), ":13\r\n");
9928
9929        // A `#` offset counts in fields rather than in bits.
9930        assert_eq!(
9931            f.run(&[
9932                b"BITFIELD",
9933                b"bf",
9934                b"SET",
9935                b"u8",
9936                b"#0",
9937                b"255",
9938                b"GET",
9939                b"u8",
9940                b"#0"
9941            ]),
9942            "*2\r\n:0\r\n:255\r\n"
9943        );
9944
9945        assert_eq!(
9946            f.run(&[
9947                b"BITFIELD",
9948                b"bf",
9949                b"OVERFLOW",
9950                b"SAT",
9951                b"INCRBY",
9952                b"i8",
9953                b"0",
9954                b"120",
9955                b"INCRBY",
9956                b"i8",
9957                b"0",
9958                b"120"
9959            ]),
9960            "*2\r\n:119\r\n:127\r\n"
9961        );
9962        assert_eq!(
9963            f.run(&[
9964                b"BITFIELD",
9965                b"bf2",
9966                b"OVERFLOW",
9967                b"FAIL",
9968                b"INCRBY",
9969                b"u2",
9970                b"0",
9971                b"5"
9972            ]),
9973            "*1\r\n$-1\r\n"
9974        );
9975        assert_eq!(
9976            f.run(&[
9977                b"BITFIELD",
9978                b"bf3",
9979                b"OVERFLOW",
9980                b"WRAP",
9981                b"INCRBY",
9982                b"u2",
9983                b"0",
9984                b"5"
9985            ]),
9986            "*1\r\n:1\r\n"
9987        );
9988        assert_eq!(
9989            f.run(&[b"BITFIELD", b"bf3", b"GET", b"i64", b"0"]),
9990            "*1\r\n:4611686018427387904\r\n"
9991        );
9992    }
9993
9994    /// A bad subcommand anywhere in the line stops all of it.
9995    ///
9996    /// Redis checks the whole argument list before it runs any of it, so the
9997    /// `SET` in front of the bad type here never happens and the key it would
9998    /// have created is not there afterwards.
9999    #[test]
10000    fn a_bad_bitfield_subcommand_leaves_the_key_alone() {
10001        let mut f = Fixture::new();
10002        let bad_type = "-ERR Invalid bitfield type. Use something like i16 u8. Note that u64 is not supported but i64 is.\r\n";
10003        assert_eq!(
10004            f.run(&[
10005                b"BITFIELD",
10006                b"bad",
10007                b"SET",
10008                b"u8",
10009                b"0",
10010                b"1",
10011                b"GET",
10012                b"u99",
10013                b"0"
10014            ]),
10015            bad_type
10016        );
10017        assert_eq!(f.run(&[b"EXISTS", b"bad"]), ":0\r\n");
10018        assert_eq!(
10019            f.run(&[b"BITFIELD", b"bad", b"GET", b"u64", b"0"]),
10020            bad_type
10021        );
10022        assert_eq!(
10023            f.run(&[b"BITFIELD", b"bad", b"GET"]),
10024            "-ERR syntax error\r\n"
10025        );
10026        assert_eq!(
10027            f.run(&[b"BITFIELD", b"bad", b"NOPE", b"u8", b"0"]),
10028            "-ERR syntax error\r\n"
10029        );
10030        assert_eq!(
10031            f.run(&[b"BITFIELD", b"bad", b"OVERFLOW"]),
10032            "-ERR syntax error\r\n"
10033        );
10034        assert_eq!(
10035            f.run(&[
10036                b"BITFIELD",
10037                b"bad",
10038                b"OVERFLOW",
10039                b"NOPE",
10040                b"GET",
10041                b"u8",
10042                b"0"
10043            ]),
10044            "-ERR Invalid OVERFLOW type specified\r\n"
10045        );
10046        assert_eq!(
10047            f.run(&[b"BITFIELD", b"bad", b"SET", b"u8", b"0", b"notanum"]),
10048            "-ERR value is not an integer or out of range\r\n"
10049        );
10050        for at in [&b"#-1"[..], b"abc"] {
10051            assert_eq!(
10052                f.run(&[b"BITFIELD", b"bad", b"GET", b"u8", at]),
10053                "-ERR bit offset is not an integer or out of range\r\n"
10054            );
10055        }
10056    }
10057
10058    /// The read only twin reads, refuses to write, and creates nothing.
10059    #[test]
10060    fn bitfield_ro_answers_gets_and_refuses_the_rest() {
10061        let mut f = Fixture::new();
10062        f.run(&[b"SET", b"n", b"123"]);
10063        assert_eq!(
10064            f.run(&[b"BITFIELD_RO", b"n", b"GET", b"u8", b"0"]),
10065            "*1\r\n:49\r\n"
10066        );
10067        // A read does not unpack an int the way a write does.
10068        assert_eq!(f.run(&[b"OBJECT", b"ENCODING", b"n"]), "$3\r\nint\r\n");
10069
10070        // An OVERFLOW word is allowed even though nothing here can overflow.
10071        assert_eq!(
10072            f.run(&[
10073                b"BITFIELD_RO",
10074                b"n",
10075                b"OVERFLOW",
10076                b"SAT",
10077                b"GET",
10078                b"u8",
10079                b"0"
10080            ]),
10081            "*1\r\n:49\r\n"
10082        );
10083        for sub in [&b"SET"[..], b"INCRBY"] {
10084            assert_eq!(
10085                f.run(&[b"BITFIELD_RO", b"n", sub, b"u8", b"0", b"1"]),
10086                "-ERR BITFIELD_RO only supports the GET subcommand\r\n"
10087            );
10088        }
10089
10090        assert_eq!(
10091            f.run(&[b"BITFIELD_RO", b"gone", b"GET", b"u8", b"100"]),
10092            "*1\r\n:0\r\n"
10093        );
10094        assert_eq!(f.run(&[b"EXISTS", b"gone"]), ":0\r\n");
10095    }
10096
10097    /// The offsets a bitmap command will not take.
10098    #[test]
10099    fn an_offset_off_the_end_of_the_world_is_refused() {
10100        let mut f = Fixture::new();
10101        let bad = "-ERR bit offset is not an integer or out of range\r\n";
10102        for arg in [&b"abc"[..], b"-1", b"4294967296"] {
10103            assert_eq!(f.run(&[b"SETBIT", b"k", arg, b"1"]), bad);
10104            assert_eq!(f.run(&[b"GETBIT", b"k", arg]), bad);
10105        }
10106        for arg in [&b"2"[..], b"-1"] {
10107            assert_eq!(
10108                f.run(&[b"BITPOS", b"k", arg]),
10109                "-ERR The bit argument must be 1 or 0.\r\n"
10110            );
10111        }
10112        assert_eq!(
10113            f.run(&[b"BITPOS", b"k", b"abc"]),
10114            "-ERR value is not an integer or out of range\r\n"
10115        );
10116        assert_eq!(
10117            f.run(&[b"BITPOS", b"k", b"0", b"5", b"BIT"]),
10118            "-ERR value is not an integer or out of range\r\n"
10119        );
10120        let bad_bit = "-ERR bit is not an integer or out of range\r\n";
10121        assert_eq!(f.run(&[b"SETBIT", b"k", b"0", b"2"]), bad_bit);
10122        assert_eq!(f.run(&[b"SETBIT", b"k", b"0", b"abc"]), bad_bit);
10123    }
10124
10125    /// Every one of the seven refuses a key that is not a string.
10126    #[test]
10127    fn every_bitmap_command_says_wrongtype() {
10128        let mut f = Fixture::new();
10129        f.run(&[b"LPUSH", b"l", b"x"]);
10130        let wrong = "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n";
10131        let cases: &[&[&[u8]]] = &[
10132            &[b"SETBIT", b"l", b"0", b"1"],
10133            &[b"GETBIT", b"l", b"0"],
10134            &[b"BITCOUNT", b"l"],
10135            &[b"BITPOS", b"l", b"1"],
10136            &[b"BITOP", b"AND", b"d", b"l"],
10137            &[b"BITFIELD", b"l", b"GET", b"u8", b"0"],
10138            &[b"BITFIELD_RO", b"l", b"GET", b"u8", b"0"],
10139        ];
10140        for case in cases {
10141            assert_eq!(f.run(case), wrong, "{:?}", case[0]);
10142        }
10143    }
10144
10145    // --------------------------------------------------------- hyperloglogs
10146
10147    #[test]
10148    fn a_sketch_is_added_to_and_counted() {
10149        let mut f = Fixture::new();
10150        // Creating the key counts as a change, even with nothing to add.
10151        assert_eq!(f.run(&[b"PFADD", b"h"]), ":1\r\n");
10152        assert_eq!(f.run(&[b"PFADD", b"h"]), ":0\r\n");
10153        assert_eq!(f.run(&[b"PFCOUNT", b"h"]), ":0\r\n");
10154        assert_eq!(f.run(&[b"STRLEN", b"h"]), ":18\r\n");
10155        // And it is a string, which is not an implementation detail: a client
10156        // can `GET` a sketch out of one server and `SET` it into another.
10157        assert_eq!(f.run(&[b"TYPE", b"h"]), "+string\r\n");
10158        assert_eq!(f.run(&[b"OBJECT", b"ENCODING", b"h"]), "$3\r\nraw\r\n");
10159
10160        assert_eq!(f.run(&[b"PFADD", b"h", b"a", b"b", b"c"]), ":1\r\n");
10161        assert_eq!(f.run(&[b"PFADD", b"h", b"a"]), ":0\r\n");
10162        assert_eq!(f.run(&[b"PFCOUNT", b"h"]), ":3\r\n");
10163    }
10164
10165    #[test]
10166    fn the_bytes_of_a_sketch_are_the_ones_a_real_server_writes() {
10167        let mut f = Fixture::new();
10168        f.run(&[b"PFADD", b"h", b"a", b"b", b"c"]);
10169        // Not text, so it is compared as bytes.
10170        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";
10171        let mut reply = b"$27\r\n".to_vec();
10172        reply.extend_from_slice(want);
10173        reply.extend_from_slice(b"\r\n");
10174        assert_eq!(f.raw(&[b"GET", b"h"]), reply);
10175    }
10176
10177    #[test]
10178    fn counting_several_keys_counts_their_union() {
10179        let mut f = Fixture::new();
10180        f.run(&[b"PFADD", b"a", b"x", b"y"]);
10181        f.run(&[b"PFADD", b"b", b"y", b"z"]);
10182        assert_eq!(f.run(&[b"PFCOUNT", b"a"]), ":2\r\n");
10183        assert_eq!(f.run(&[b"PFCOUNT", b"a", b"b"]), ":3\r\n");
10184        // A key that is not there is an empty sketch, not an error and not
10185        // something that gets created by being counted.
10186        assert_eq!(f.run(&[b"PFCOUNT", b"gone"]), ":0\r\n");
10187        assert_eq!(f.run(&[b"PFCOUNT", b"a", b"gone"]), ":2\r\n");
10188        assert_eq!(f.run(&[b"EXISTS", b"gone"]), ":0\r\n");
10189    }
10190
10191    #[test]
10192    fn a_merge_keeps_what_the_destination_had() {
10193        let mut f = Fixture::new();
10194        f.run(&[b"PFADD", b"a", b"x", b"y"]);
10195        f.run(&[b"PFADD", b"b", b"z"]);
10196        assert_eq!(f.run(&[b"PFMERGE", b"d", b"a", b"b"]), "+OK\r\n");
10197        assert_eq!(f.run(&[b"PFCOUNT", b"d"]), ":3\r\n");
10198        // The destination is one of the sources, so a second merge adds to it.
10199        f.run(&[b"PFADD", b"c", b"w"]);
10200        assert_eq!(f.run(&[b"PFMERGE", b"d", b"c"]), "+OK\r\n");
10201        assert_eq!(f.run(&[b"PFCOUNT", b"d"]), ":4\r\n");
10202        // And with no sources it is a no-op that still answers OK and still
10203        // creates a destination that was not there.
10204        assert_eq!(f.run(&[b"PFMERGE", b"fresh"]), "+OK\r\n");
10205        assert_eq!(f.run(&[b"PFCOUNT", b"fresh"]), ":0\r\n");
10206    }
10207
10208    /// Not under Miri, and not for the number of commands: a dense sketch is
10209    /// sixteen thousand three hundred and eighty four registers and every
10210    /// command here walks all of them, so one `PFCOUNT` is more interpreted
10211    /// work than a hundred ordinary tests. The registers and the walking are in
10212    /// `yo-kv`, where fifteen tests of their own cover both encodings and where
10213    /// the interpreter does run over them. What is left here is the dispatch
10214    /// around it, which is the same dispatch every other command in this file
10215    /// goes through.
10216    #[cfg_attr(miri, ignore = "sixteen thousand registers a command")]
10217    #[test]
10218    fn the_debug_forms_answer_four_different_shapes() {
10219        let mut f = Fixture::new();
10220        f.run(&[b"PFADD", b"h", b"a", b"b", b"c"]);
10221        assert_eq!(f.run(&[b"PFDEBUG", b"ENCODING", b"h"]), "+sparse\r\n");
10222        assert_eq!(
10223            f.run(&[b"PFDEBUG", b"DECODE", b"h"]),
10224            "$44\r\nZ:8436 v:1,1 Z:4274 v:2,1 Z:3068 v:1,1 Z:603\r\n"
10225        );
10226        assert_eq!(f.run(&[b"PFDEBUG", b"TODENSE", b"h"]), ":1\r\n");
10227        assert_eq!(f.run(&[b"PFDEBUG", b"TODENSE", b"h"]), ":0\r\n");
10228        assert_eq!(f.run(&[b"PFDEBUG", b"ENCODING", b"h"]), "+dense\r\n");
10229        assert_eq!(f.run(&[b"STRLEN", b"h"]), ":12304\r\n");
10230        assert_eq!(f.run(&[b"PFCOUNT", b"h"]), ":3\r\n");
10231        // A dense sketch has no opcodes left to print.
10232        assert_eq!(
10233            f.run(&[b"PFDEBUG", b"DECODE", b"h"]),
10234            "-ERR HLL encoding is not sparse\r\n"
10235        );
10236
10237        // All 16384 registers, of which three are not nought.
10238        let reply = f.run(&[b"PFDEBUG", b"GETREG", b"h"]);
10239        assert!(reply.starts_with("*16384\r\n"), "{}", &reply[..16]);
10240        assert_eq!(reply.matches(":0\r\n").count(), 16381);
10241        assert_eq!(reply.matches(":1\r\n").count(), 2);
10242        assert_eq!(reply.matches(":2\r\n").count(), 1);
10243
10244        assert_eq!(f.run(&[b"PFSELFTEST"]), "+OK\r\n");
10245    }
10246
10247    #[test]
10248    fn a_string_that_is_not_a_sketch_is_refused_with_its_own_sentence() {
10249        let mut f = Fixture::new();
10250        f.run(&[b"SET", b"plain", b"not a sketch"]);
10251        let not_hll = "-WRONGTYPE Key is not a valid HyperLogLog string value.\r\n";
10252        assert_eq!(f.run(&[b"PFADD", b"plain", b"a"]), not_hll);
10253        assert_eq!(f.run(&[b"PFCOUNT", b"plain"]), not_hll);
10254        assert_eq!(f.run(&[b"PFMERGE", b"plain"]), not_hll);
10255        assert_eq!(f.run(&[b"PFDEBUG", b"ENCODING", b"plain"]), not_hll);
10256
10257        // A key that is not a string at all gets the ordinary sentence, and a
10258        // destination that would have been written is not created.
10259        f.run(&[b"RPUSH", b"l", b"x"]);
10260        let wrong = "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n";
10261        assert_eq!(f.run(&[b"PFADD", b"l", b"a"]), wrong);
10262        assert_eq!(f.run(&[b"PFCOUNT", b"l"]), wrong);
10263        assert_eq!(f.run(&[b"PFMERGE", b"dest", b"l"]), wrong);
10264        assert_eq!(f.run(&[b"EXISTS", b"dest"]), ":0\r\n");
10265        assert_eq!(f.run(&[b"PFDEBUG", b"GETREG", b"l"]), wrong);
10266    }
10267
10268    #[test]
10269    fn pfdebug_has_its_own_complaints() {
10270        let mut f = Fixture::new();
10271        f.run(&[b"PFADD", b"h", b"a"]);
10272        // The word is quoted exactly as the client spelled it, and this is not
10273        // the "Try X HELP." sentence every other container command uses.
10274        assert_eq!(
10275            f.run(&[b"PFDEBUG", b"NOPE", b"h"]),
10276            "-ERR Unknown PFDEBUG subcommand 'NOPE'\r\n"
10277        );
10278        // Where all three of the real commands take a missing key as empty.
10279        let gone = "-ERR The specified key does not exist\r\n";
10280        assert_eq!(f.run(&[b"PFDEBUG", b"GETREG", b"missing"]), gone);
10281        assert_eq!(f.run(&[b"PFDEBUG", b"DECODE", b"missing"]), gone);
10282        assert_eq!(f.run(&[b"PFDEBUG", b"ENCODING", b"missing"]), gone);
10283        assert_eq!(f.run(&[b"PFDEBUG", b"TODENSE", b"missing"]), gone);
10284        assert_eq!(
10285            f.run(&[b"PFDEBUG"]),
10286            "-ERR wrong number of arguments for 'pfdebug' command\r\n"
10287        );
10288        assert_eq!(
10289            f.run(&[b"PFSELFTEST", b"x"]),
10290            "-ERR wrong number of arguments for 'pfselftest' command\r\n"
10291        );
10292    }
10293
10294    #[test]
10295    fn a_sketch_whose_opcodes_do_not_add_up_says_so() {
10296        let mut f = Fixture::new();
10297        f.run(&[b"PFADD", b"h", b"a", b"b", b"c"]);
10298        // The sketch with its last byte cut off, which is still a header and a
10299        // magic and is a run length encoding that stops short of register 16384.
10300        let reply = f.raw(&[b"GET", b"h"]);
10301        let short = reply[5..reply.len() - 3].to_vec();
10302        f.run(&[b"SET", b"h", &short]);
10303        assert_eq!(
10304            f.run(&[b"PFCOUNT", b"h"]),
10305            "-INVALIDOBJ Corrupted HLL object detected\r\n"
10306        );
10307    }
10308
10309    #[test]
10310    fn a_sketch_survives_a_dump_and_a_restore_in_both_encodings() {
10311        let mut f = Fixture::new();
10312        // One that stays sparse and one that has gone dense, since the payload
10313        // carries the bytes and the two encodings are different lengths.
10314        f.run(&[b"PFADD", b"small", b"a", b"b", b"c"]);
10315        // Ten thousand elements is what takes a sketch dense on its own, and it
10316        // is ten thousand trips through dispatch, which is what Miri charges
10317        // for. There the same sketch is taken across by hand. What this test is
10318        // about is a dense payload surviving a round trip and the encoding is
10319        // dense either way: that a sketch converts when it fills up is what
10320        // `the_debug_forms_answer_four_different_shapes` is for.
10321        if cfg!(miri) {
10322            f.run(&[b"PFADD", b"big", b"a", b"b", b"c"]);
10323            f.run(&[b"PFDEBUG", b"TODENSE", b"big"]);
10324        } else {
10325            for i in 0..10_000u32 {
10326                let ele = format!("e{i}");
10327                f.run(&[b"PFADD", b"big", ele.as_bytes()]);
10328            }
10329        }
10330        assert_eq!(f.run(&[b"PFDEBUG", b"ENCODING", b"small"]), "+sparse\r\n");
10331        assert_eq!(f.run(&[b"PFDEBUG", b"ENCODING", b"big"]), "+dense\r\n");
10332
10333        for key in [&b"small"[..], b"big"] {
10334            let mut copy = key.to_vec();
10335            copy.push(b'2');
10336            let bytes = payload(&f.raw(&[b"DUMP", key]));
10337            assert_eq!(f.run(&[b"RESTORE", &copy, b"0", &bytes]), "+OK\r\n");
10338            // The bytes, the encoding and the estimate all come back, which is
10339            // the whole of what byte compatibility across a round trip means.
10340            assert_eq!(f.raw(&[b"GET", &copy]), f.raw(&[b"GET", key]));
10341            assert_eq!(
10342                f.run(&[b"PFDEBUG", b"ENCODING", &copy]),
10343                f.run(&[b"PFDEBUG", b"ENCODING", key])
10344            );
10345            assert_eq!(f.run(&[b"PFCOUNT", &copy]), f.run(&[b"PFCOUNT", key]));
10346        }
10347        assert_eq!(f.run(&[b"PFCOUNT", b"small2"]), ":3\r\n");
10348        assert_eq!(f.run(&[b"STRLEN", b"big2"]), ":12304\r\n");
10349    }
10350
10351    /// One RESP2 bulk string. The JSON replies are almost all one of these and
10352    /// the text inside them has quotes in it, so writing the frame out by hand
10353    /// buries the part of the assertion that matters.
10354    fn bulk(s: &str) -> String {
10355        format!("${}\r\n{s}\r\n", s.len())
10356    }
10357
10358    /// A RESP2 array of bulk strings, which is what most of the list replies
10359    /// are and what writing them out by hand in every assertion looks like.
10360    fn bulks(parts: &[&str]) -> String {
10361        let mut s = format!("*{}\r\n", parts.len());
10362        for p in parts {
10363            s.push_str(&format!("${}\r\n{p}\r\n", p.len()));
10364        }
10365        s
10366    }
10367
10368    #[test]
10369    fn a_list_is_pushed_from_both_ends_and_the_left_one_reverses() {
10370        let mut f = Fixture::new();
10371        // Each element in turn goes at the head, so the last one sent is at the
10372        // front when it is over. That reads like a bug in the client and it is
10373        // what every Redis has always done.
10374        assert_eq!(f.run(&[b"LPUSH", b"k", b"a", b"b", b"c"]), ":3\r\n");
10375        assert_eq!(
10376            f.run(&[b"LRANGE", b"k", b"0", b"-1"]),
10377            bulks(&["c", "b", "a"])
10378        );
10379        assert_eq!(f.run(&[b"RPUSH", b"k", b"d"]), ":4\r\n");
10380        assert_eq!(f.run(&[b"LLEN", b"k"]), ":4\r\n");
10381        assert_eq!(f.run(&[b"LPOP", b"k"]), "$1\r\nc\r\n");
10382        assert_eq!(f.run(&[b"RPOP", b"k"]), "$1\r\nd\r\n");
10383        assert_eq!(f.run(&[b"LRANGE", b"k", b"0", b"-1"]), bulks(&["b", "a"]));
10384        assert_eq!(f.run(&[b"TYPE", b"k"]), "+list\r\n");
10385    }
10386
10387    #[test]
10388    fn the_x_pushes_refuse_to_bring_a_list_back_to_life() {
10389        let mut f = Fixture::new();
10390        assert_eq!(f.run(&[b"LPUSHX", b"k", b"a"]), ":0\r\n");
10391        assert_eq!(f.run(&[b"RPUSHX", b"k", b"a"]), ":0\r\n");
10392        assert_eq!(f.run(&[b"EXISTS", b"k"]), ":0\r\n");
10393        f.run(&[b"RPUSH", b"k", b"a"]);
10394        assert_eq!(f.run(&[b"LPUSHX", b"k", b"z"]), ":2\r\n");
10395        assert_eq!(f.run(&[b"RPUSHX", b"k", b"y"]), ":3\r\n");
10396        assert_eq!(
10397            f.run(&[b"LRANGE", b"k", b"0", b"-1"]),
10398            bulks(&["z", "a", "y"])
10399        );
10400    }
10401
10402    /// The four ways a pop can come back with nothing, which are three
10403    /// different replies and a RESP2 client can tell all of them apart.
10404    #[test]
10405    fn an_empty_pop_is_a_different_nothing_with_a_count_and_without() {
10406        let mut f = Fixture::new();
10407        assert_eq!(f.run(&[b"LPOP", b"nope"]), "$-1\r\n");
10408        assert_eq!(f.run(&[b"LPOP", b"nope", b"2"]), "*-1\r\n");
10409        assert_eq!(f.run(&[b"RPOP", b"nope"]), "$-1\r\n");
10410        assert_eq!(f.run(&[b"RPOP", b"nope", b"2"]), "*-1\r\n");
10411        f.run(&[b"RPUSH", b"k", b"a", b"b", b"c"]);
10412        // A count of zero against a list that is there is an empty array and
10413        // not a null array, which is the fourth answer.
10414        assert_eq!(f.run(&[b"LPOP", b"k", b"0"]), "*0\r\n");
10415        assert_eq!(f.run(&[b"LPOP", b"k", b"1"]), bulks(&["a"]));
10416        // More than there is takes what there is and the key goes with it.
10417        assert_eq!(f.run(&[b"RPOP", b"k", b"9"]), bulks(&["c", "b"]));
10418        assert_eq!(f.run(&[b"EXISTS", b"k"]), ":0\r\n");
10419    }
10420
10421    #[test]
10422    fn a_pop_count_has_its_own_sentence_and_a_third_argument_is_an_arity_error() {
10423        let mut f = Fixture::new();
10424        f.run(&[b"RPUSH", b"k", b"a"]);
10425        let range = "-ERR value is out of range, must be positive\r\n";
10426        assert_eq!(f.run(&[b"LPOP", b"k", b"-1"]), range);
10427        assert_eq!(f.run(&[b"LPOP", b"k", b"abc"]), range);
10428        assert_eq!(f.run(&[b"RPOP", b"k", b"-1"]), range);
10429        // Redis calls this an arity error and not a syntax error, which is a
10430        // distinction it does not always make.
10431        assert_eq!(
10432            f.run(&[b"LPOP", b"k", b"1", b"2"]),
10433            "-ERR wrong number of arguments for 'lpop' command\r\n"
10434        );
10435        assert_eq!(f.run(&[b"LLEN", b"k"]), ":1\r\n");
10436    }
10437
10438    #[test]
10439    fn a_range_takes_negative_ends_and_clamps_the_ones_that_run_off() {
10440        let mut f = Fixture::new();
10441        f.run(&[b"RPUSH", b"k", b"a", b"b", b"c"]);
10442        assert_eq!(
10443            f.run(&[b"LRANGE", b"k", b"0", b"-1"]),
10444            bulks(&["a", "b", "c"])
10445        );
10446        assert_eq!(f.run(&[b"LRANGE", b"k", b"-2", b"-1"]), bulks(&["b", "c"]));
10447        assert_eq!(f.run(&[b"LRANGE", b"k", b"1", b"1"]), bulks(&["b"]));
10448        assert_eq!(f.run(&[b"LRANGE", b"k", b"5", b"10"]), "*0\r\n");
10449        assert_eq!(f.run(&[b"LRANGE", b"k", b"2", b"1"]), "*0\r\n");
10450        assert_eq!(
10451            f.run(&[b"LRANGE", b"k", b"-100", b"100"]),
10452            bulks(&["a", "b", "c"])
10453        );
10454        // A key that is not there is an empty range and not a nil, which is the
10455        // one place a list disagrees with a set.
10456        assert_eq!(f.run(&[b"LRANGE", b"nope", b"0", b"-1"]), "*0\r\n");
10457        assert_eq!(
10458            f.run(&[b"LRANGE", b"k", b"a", b"b"]),
10459            "-ERR value is not an integer or out of range\r\n"
10460        );
10461    }
10462
10463    #[test]
10464    fn an_index_reads_and_writes_from_whichever_end_is_nearer() {
10465        let mut f = Fixture::new();
10466        f.run(&[b"RPUSH", b"k", b"a", b"b", b"c"]);
10467        assert_eq!(f.run(&[b"LINDEX", b"k", b"0"]), "$1\r\na\r\n");
10468        assert_eq!(f.run(&[b"LINDEX", b"k", b"-1"]), "$1\r\nc\r\n");
10469        assert_eq!(f.run(&[b"LINDEX", b"k", b"99"]), "$-1\r\n");
10470        assert_eq!(f.run(&[b"LINDEX", b"nope", b"0"]), "$-1\r\n");
10471        assert_eq!(f.run(&[b"LSET", b"k", b"-1", b"z"]), "+OK\r\n");
10472        assert_eq!(
10473            f.run(&[b"LRANGE", b"k", b"0", b"-1"]),
10474            bulks(&["a", "b", "z"])
10475        );
10476        // Both ways of missing are errors here rather than a nil, because a
10477        // list is never empty and there is nothing else the reply could be.
10478        assert_eq!(
10479            f.run(&[b"LSET", b"k", b"99", b"z"]),
10480            "-ERR index out of range\r\n"
10481        );
10482        assert_eq!(
10483            f.run(&[b"LSET", b"nope", b"0", b"z"]),
10484            "-ERR no such key\r\n"
10485        );
10486    }
10487
10488    #[test]
10489    fn linsert_says_three_things_with_one_signed_number() {
10490        let mut f = Fixture::new();
10491        // Zero for a key that is not there, which is not the same as minus one
10492        // for a pivot that is not in a list that is.
10493        assert_eq!(
10494            f.run(&[b"LINSERT", b"nope", b"BEFORE", b"a", b"x"]),
10495            ":0\r\n"
10496        );
10497        f.run(&[b"RPUSH", b"k", b"a", b"b"]);
10498        assert_eq!(f.run(&[b"LINSERT", b"k", b"before", b"a", b"X"]), ":3\r\n");
10499        assert_eq!(f.run(&[b"LINSERT", b"k", b"AFTER", b"b", b"Y"]), ":4\r\n");
10500        assert_eq!(
10501            f.run(&[b"LRANGE", b"k", b"0", b"-1"]),
10502            bulks(&["X", "a", "b", "Y"])
10503        );
10504        assert_eq!(
10505            f.run(&[b"LINSERT", b"k", b"BEFORE", b"zz", b"x"]),
10506            ":-1\r\n"
10507        );
10508        assert_eq!(
10509            f.run(&[b"LINSERT", b"k", b"SIDEWAYS", b"a", b"x"]),
10510            "-ERR syntax error\r\n"
10511        );
10512    }
10513
10514    #[test]
10515    fn lrem_counts_in_three_directions_and_takes_the_key_when_it_empties() {
10516        let mut f = Fixture::new();
10517        f.run(&[b"RPUSH", b"k", b"a", b"b", b"a", b"c", b"a"]);
10518        assert_eq!(f.run(&[b"LREM", b"k", b"2", b"a"]), ":2\r\n");
10519        assert_eq!(
10520            f.run(&[b"LRANGE", b"k", b"0", b"-1"]),
10521            bulks(&["b", "c", "a"])
10522        );
10523        assert_eq!(f.run(&[b"LREM", b"k", b"-1", b"a"]), ":1\r\n");
10524        assert_eq!(f.run(&[b"LRANGE", b"k", b"0", b"-1"]), bulks(&["b", "c"]));
10525        assert_eq!(f.run(&[b"LREM", b"k", b"0", b"b"]), ":1\r\n");
10526        assert_eq!(f.run(&[b"LREM", b"k", b"0", b"c"]), ":1\r\n");
10527        assert_eq!(f.run(&[b"EXISTS", b"k"]), ":0\r\n");
10528        assert_eq!(f.run(&[b"LREM", b"nope", b"0", b"a"]), ":0\r\n");
10529    }
10530
10531    #[test]
10532    fn ltrim_keeps_a_window_and_an_empty_one_deletes_the_key() {
10533        let mut f = Fixture::new();
10534        f.run(&[b"RPUSH", b"k", b"a", b"b", b"c", b"d"]);
10535        assert_eq!(f.run(&[b"LTRIM", b"k", b"1", b"-2"]), "+OK\r\n");
10536        assert_eq!(f.run(&[b"LRANGE", b"k", b"0", b"-1"]), bulks(&["b", "c"]));
10537        // `LTRIM k 1 0` is the documented way to empty a list, so it has to
10538        // leave `EXISTS` answering zero rather than leaving an empty one.
10539        assert_eq!(f.run(&[b"LTRIM", b"k", b"1", b"0"]), "+OK\r\n");
10540        assert_eq!(f.run(&[b"EXISTS", b"k"]), ":0\r\n");
10541        assert_eq!(f.run(&[b"LTRIM", b"nope", b"0", b"-1"]), "+OK\r\n");
10542    }
10543
10544    #[test]
10545    fn lpos_walks_from_either_end_and_stops_where_it_is_told() {
10546        let mut f = Fixture::new();
10547        f.run(&[b"RPUSH", b"p", b"a", b"b", b"c", b"a", b"b", b"c", b"a"]);
10548        assert_eq!(f.run(&[b"LPOS", b"p", b"a"]), ":0\r\n");
10549        assert_eq!(f.run(&[b"LPOS", b"p", b"a", b"RANK", b"-1"]), ":6\r\n");
10550        assert_eq!(f.run(&[b"LPOS", b"p", b"a", b"RANK", b"2"]), ":3\r\n");
10551        assert_eq!(
10552            f.run(&[b"LPOS", b"p", b"a", b"COUNT", b"2"]),
10553            "*2\r\n:0\r\n:3\r\n"
10554        );
10555        assert_eq!(
10556            f.run(&[b"LPOS", b"p", b"a", b"RANK", b"-1", b"COUNT", b"0"]),
10557            "*3\r\n:6\r\n:3\r\n:0\r\n"
10558        );
10559        // MAXLEN counts elements looked at and not matches found, so three
10560        // stops after `a b c` and finds the one match in it.
10561        assert_eq!(
10562            f.run(&[b"LPOS", b"p", b"a", b"COUNT", b"0", b"MAXLEN", b"3"]),
10563            "*1\r\n:0\r\n"
10564        );
10565        // Nothing found is three different replies depending on how it was
10566        // asked and whether the key is there at all.
10567        assert_eq!(f.run(&[b"LPOS", b"p", b"zz"]), "$-1\r\n");
10568        assert_eq!(f.run(&[b"LPOS", b"p", b"zz", b"COUNT", b"0"]), "*0\r\n");
10569        assert_eq!(f.run(&[b"LPOS", b"nope", b"a"]), "$-1\r\n");
10570        assert_eq!(f.run(&[b"LPOS", b"nope", b"a", b"COUNT", b"2"]), "*0\r\n");
10571    }
10572
10573    #[test]
10574    fn lpos_words_its_three_mistakes_the_way_redis_does() {
10575        let mut f = Fixture::new();
10576        f.run(&[b"RPUSH", b"p", b"a"]);
10577        // The whole sentence and not a prefix, because the older wording of it
10578        // is still all over the internet and clients match on the text.
10579        assert_eq!(
10580            f.run(&[b"LPOS", b"p", b"a", b"RANK", b"0"]),
10581            "-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"
10582        );
10583        assert_eq!(
10584            f.run(&[b"LPOS", b"p", b"a", b"COUNT", b"-1"]),
10585            "-ERR COUNT can't be negative\r\n"
10586        );
10587        assert_eq!(
10588            f.run(&[b"LPOS", b"p", b"a", b"MAXLEN", b"-1"]),
10589            "-ERR MAXLEN can't be negative\r\n"
10590        );
10591        assert_eq!(
10592            f.run(&[b"LPOS", b"p", b"a", b"RANK"]),
10593            "-ERR syntax error\r\n"
10594        );
10595        assert_eq!(
10596            f.run(&[b"LPOS", b"p", b"a", b"FOO", b"1"]),
10597            "-ERR syntax error\r\n"
10598        );
10599    }
10600
10601    #[test]
10602    fn a_move_takes_from_one_end_and_gives_to_another_even_on_one_key() {
10603        let mut f = Fixture::new();
10604        f.run(&[b"RPUSH", b"k", b"a", b"b", b"c"]);
10605        assert_eq!(f.run(&[b"RPOPLPUSH", b"k", b"d"]), "$1\r\nc\r\n");
10606        assert_eq!(f.run(&[b"LRANGE", b"k", b"0", b"-1"]), bulks(&["a", "b"]));
10607        assert_eq!(f.run(&[b"LRANGE", b"d", b"0", b"-1"]), bulks(&["c"]));
10608        assert_eq!(
10609            f.run(&[b"LMOVE", b"k", b"d", b"LEFT", b"RIGHT"]),
10610            "$1\r\na\r\n"
10611        );
10612        assert_eq!(f.run(&[b"LRANGE", b"d", b"0", b"-1"]), bulks(&["c", "a"]));
10613        // The same key twice is the documented way to rotate a list and falls
10614        // out of taking the element before deciding where to put it.
10615        f.run(&[b"DEL", b"r"]);
10616        f.run(&[b"RPUSH", b"r", b"1", b"2", b"3"]);
10617        assert_eq!(f.run(&[b"RPOPLPUSH", b"r", b"r"]), "$1\r\n3\r\n");
10618        assert_eq!(
10619            f.run(&[b"LRANGE", b"r", b"0", b"-1"]),
10620            bulks(&["3", "1", "2"])
10621        );
10622        assert_eq!(
10623            f.run(&[b"LMOVE", b"nope", b"d", b"LEFT", b"LEFT"]),
10624            "$-1\r\n"
10625        );
10626        assert_eq!(
10627            f.run(&[b"LMOVE", b"r", b"d", b"LEFT", b"SIDEWAYS"]),
10628            "-ERR syntax error\r\n"
10629        );
10630    }
10631
10632    #[test]
10633    fn a_move_checks_the_destination_before_it_takes_anything() {
10634        let mut f = Fixture::new();
10635        f.run(&[b"RPUSH", b"k", b"a", b"b"]);
10636        f.run(&[b"SET", b"str", b"v"]);
10637        assert_eq!(
10638            f.run(&[b"LMOVE", b"k", b"str", b"LEFT", b"LEFT"]),
10639            "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n"
10640        );
10641        // The element is still where it was, rather than having gone nowhere.
10642        assert_eq!(f.run(&[b"LRANGE", b"k", b"0", b"-1"]), bulks(&["a", "b"]));
10643    }
10644
10645    #[test]
10646    fn a_block_move_orders_the_block_by_the_ends_and_the_ordering_word() {
10647        // OBO is what you get from sending LMOVE that many times, BULK keeps
10648        // the source order. The two only differ when both ends are the same,
10649        // which is the whole reason the word exists.
10650        for (from, to, order, want) in [
10651            ("LEFT", "RIGHT", "OBO", ["a", "b"]),
10652            ("LEFT", "RIGHT", "BULK", ["a", "b"]),
10653            ("LEFT", "LEFT", "OBO", ["b", "a"]),
10654            ("LEFT", "LEFT", "BULK", ["a", "b"]),
10655            ("RIGHT", "LEFT", "OBO", ["d", "e"]),
10656            ("RIGHT", "LEFT", "BULK", ["d", "e"]),
10657            ("RIGHT", "RIGHT", "OBO", ["e", "d"]),
10658            ("RIGHT", "RIGHT", "BULK", ["d", "e"]),
10659        ] {
10660            let mut f = Fixture::new();
10661            f.run(&[b"RPUSH", b"s", b"a", b"b", b"c", b"d", b"e"]);
10662            let how = format!("{from} {to} {order}");
10663            let reply = f.run(&[
10664                b"LMOVEM",
10665                b"s",
10666                b"d",
10667                from.as_bytes(),
10668                to.as_bytes(),
10669                b"COUNT",
10670                b"2",
10671                order.as_bytes(),
10672            ]);
10673            assert_eq!(reply, bulks(&want), "the reply for {how}");
10674            assert_eq!(
10675                f.run(&[b"LRANGE", b"d", b"0", b"-1"]),
10676                bulks(&want),
10677                "the destination for {how}"
10678            );
10679        }
10680    }
10681
10682    #[test]
10683    fn a_block_move_of_one_needs_no_count_at_all() {
10684        let mut f = Fixture::new();
10685        f.run(&[b"RPUSH", b"s", b"a", b"b", b"c"]);
10686        assert_eq!(
10687            f.run(&[b"LMOVEM", b"s", b"d", b"LEFT", b"RIGHT"]),
10688            bulks(&["a"])
10689        );
10690        assert_eq!(f.run(&[b"LRANGE", b"s", b"0", b"-1"]), bulks(&["b", "c"]));
10691        // Six and seven arguments are neither of the two forms, so the
10692        // reference calls both of them a syntax error rather than guessing.
10693        assert_eq!(
10694            f.run(&[b"LMOVEM", b"s", b"d", b"LEFT", b"RIGHT", b"COUNT"]),
10695            "-ERR syntax error\r\n"
10696        );
10697        assert_eq!(
10698            f.run(&[b"LMOVEM", b"s", b"d", b"LEFT", b"RIGHT", b"COUNT", b"2"]),
10699            "-ERR syntax error\r\n"
10700        );
10701    }
10702
10703    #[test]
10704    fn a_block_move_with_exactly_takes_all_of_them_or_none() {
10705        let mut f = Fixture::new();
10706        f.run(&[b"RPUSH", b"s", b"a", b"b", b"c"]);
10707        // A null array and not a null bulk string, which `redis-cli` prints as
10708        // `(nil)` either way and only the raw wire tells apart. What it would
10709        // have sent is an array, so its nothing is an array's nothing.
10710        assert_eq!(
10711            f.run(&[
10712                b"LMOVEM", b"s", b"d", b"LEFT", b"RIGHT", b"EXACTLY", b"99", b"BULK"
10713            ]),
10714            "*-1\r\n"
10715        );
10716        assert_eq!(
10717            f.run(&[b"LRANGE", b"s", b"0", b"-1"]),
10718            bulks(&["a", "b", "c"])
10719        );
10720        // COUNT takes what there is, and an emptied source goes away.
10721        assert_eq!(
10722            f.run(&[
10723                b"LMOVEM", b"s", b"d", b"LEFT", b"RIGHT", b"COUNT", b"99", b"BULK"
10724            ]),
10725            bulks(&["a", "b", "c"])
10726        );
10727        assert_eq!(f.run(&[b"EXISTS", b"s"]), ":0\r\n");
10728        assert_eq!(
10729            f.run(&[
10730                b"LMOVEM", b"s", b"d", b"LEFT", b"RIGHT", b"COUNT", b"1", b"BULK"
10731            ]),
10732            "*-1\r\n"
10733        );
10734    }
10735
10736    #[test]
10737    fn a_block_move_onto_itself_rotates_by_the_count() {
10738        let mut f = Fixture::new();
10739        f.run(&[b"RPUSH", b"s", b"a", b"b", b"c"]);
10740        assert_eq!(
10741            f.run(&[
10742                b"LMOVEM", b"s", b"s", b"LEFT", b"RIGHT", b"COUNT", b"2", b"BULK"
10743            ]),
10744            bulks(&["a", "b"])
10745        );
10746        assert_eq!(
10747            f.run(&[b"LRANGE", b"s", b"0", b"-1"]),
10748            bulks(&["c", "a", "b"])
10749        );
10750    }
10751
10752    #[test]
10753    fn a_block_move_reads_the_count_before_the_ordering_word() {
10754        let mut f = Fixture::new();
10755        f.run(&[b"RPUSH", b"s", b"a", b"b"]);
10756        f.run(&[b"SET", b"str", b"v"]);
10757        let count = "-ERR count should be greater than 0\r\n";
10758        assert_eq!(
10759            f.run(&[
10760                b"LMOVEM", b"s", b"d", b"LEFT", b"RIGHT", b"COUNT", b"abc", b"NOPE"
10761            ]),
10762            count
10763        );
10764        assert_eq!(
10765            f.run(&[
10766                b"LMOVEM", b"s", b"d", b"LEFT", b"RIGHT", b"COUNT", b"0", b"BULK"
10767            ]),
10768            count
10769        );
10770        assert_eq!(
10771            f.run(&[
10772                b"LMOVEM", b"s", b"d", b"LEFT", b"RIGHT", b"COUNT", b"1", b"NOPE"
10773            ]),
10774            "-ERR syntax error\r\n"
10775        );
10776        assert_eq!(
10777            f.run(&[
10778                b"LMOVEM", b"s", b"d", b"LEFT", b"RIGHT", b"NOPE", b"abc", b"BULK"
10779            ]),
10780            "-ERR syntax error\r\n"
10781        );
10782        // Every argument is read before the keys are looked at, so a bad count
10783        // beats a wrong type even when the type is wrong on the source.
10784        assert_eq!(
10785            f.run(&[
10786                b"LMOVEM", b"str", b"d", b"LEFT", b"RIGHT", b"COUNT", b"abc", b"BULK"
10787            ]),
10788            count
10789        );
10790        assert_eq!(
10791            f.run(&[
10792                b"LMOVEM", b"s", b"str", b"LEFT", b"RIGHT", b"COUNT", b"1", b"BULK"
10793            ]),
10794            "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n"
10795        );
10796        assert_eq!(f.run(&[b"LRANGE", b"s", b"0", b"-1"]), bulks(&["a", "b"]));
10797    }
10798
10799    #[test]
10800    fn lmpop_answers_from_the_first_key_that_has_anything() {
10801        let mut f = Fixture::new();
10802        f.run(&[b"RPUSH", b"b", b"1", b"2", b"3"]);
10803        // The name of the key that answered comes back with the elements,
10804        // because the client cannot work out which one it was.
10805        assert_eq!(
10806            f.run(&[b"LMPOP", b"2", b"a", b"b", b"LEFT", b"COUNT", b"2"]),
10807            "*2\r\n$1\r\nb\r\n*2\r\n$1\r\n1\r\n$1\r\n2\r\n"
10808        );
10809        assert_eq!(
10810            f.run(&[b"LMPOP", b"2", b"a", b"b", b"RIGHT"]),
10811            "*2\r\n$1\r\nb\r\n*1\r\n$1\r\n3\r\n"
10812        );
10813        assert_eq!(f.run(&[b"EXISTS", b"b"]), ":0\r\n");
10814        // A null array and not a null, even though what it stands in for is an
10815        // array holding a key name and then another array.
10816        assert_eq!(f.run(&[b"LMPOP", b"2", b"a", b"b", b"LEFT"]), "*-1\r\n");
10817    }
10818
10819    #[test]
10820    fn lmpop_has_its_own_words_for_a_count_and_for_a_key_count() {
10821        let mut f = Fixture::new();
10822        f.run(&[b"RPUSH", b"k", b"a"]);
10823        assert_eq!(
10824            f.run(&[b"LMPOP", b"0", b"k", b"LEFT"]),
10825            "-ERR numkeys should be greater than 0\r\n"
10826        );
10827        assert_eq!(
10828            f.run(&[b"LMPOP", b"-1", b"k", b"LEFT"]),
10829            "-ERR numkeys should be greater than 0\r\n"
10830        );
10831        assert_eq!(
10832            f.run(&[b"LMPOP", b"1", b"k", b"LEFT", b"COUNT", b"0"]),
10833            "-ERR count should be greater than 0\r\n"
10834        );
10835        // A key count that eats the direction is a syntax error and not a
10836        // sentence about key counts, because the direction is simply not there.
10837        assert_eq!(
10838            f.run(&[b"LMPOP", b"3", b"k", b"LEFT"]),
10839            "-ERR syntax error\r\n"
10840        );
10841        assert_eq!(
10842            f.run(&[b"LMPOP", b"1", b"k", b"LEFT", b"COUNT", b"1", b"x"]),
10843            "-ERR syntax error\r\n"
10844        );
10845        assert_eq!(
10846            f.run(&[b"LMPOP", b"1", b"k", b"LEFT", b"FOO", b"1"]),
10847            "-ERR syntax error\r\n"
10848        );
10849        assert_eq!(
10850            f.run(&[b"LMPOP", b"1", b"k", b"SIDEWAYS"]),
10851            "-ERR syntax error\r\n"
10852        );
10853        assert_eq!(f.run(&[b"LLEN", b"k"]), ":1\r\n");
10854    }
10855
10856    #[test]
10857    fn every_list_command_says_wrongtype_and_writes_nothing() {
10858        let mut f = Fixture::new();
10859        f.run(&[b"SET", b"str", b"v"]);
10860        let wrong = "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n";
10861        for cmd in [
10862            &[b"LPUSH".as_slice(), b"str", b"a"][..],
10863            &[b"RPUSH", b"str", b"a"],
10864            &[b"LPUSHX", b"str", b"a"],
10865            &[b"RPUSHX", b"str", b"a"],
10866            &[b"LPOP", b"str"],
10867            &[b"LPOP", b"str", b"2"],
10868            &[b"RPOP", b"str"],
10869            &[b"LLEN", b"str"],
10870            &[b"LRANGE", b"str", b"0", b"-1"],
10871            &[b"LINDEX", b"str", b"0"],
10872            &[b"LSET", b"str", b"0", b"a"],
10873            &[b"LINSERT", b"str", b"BEFORE", b"a", b"b"],
10874            &[b"LREM", b"str", b"0", b"a"],
10875            &[b"LTRIM", b"str", b"0", b"-1"],
10876            &[b"LPOS", b"str", b"a"],
10877            &[b"LPOS", b"str", b"a", b"COUNT", b"0"],
10878            &[b"RPOPLPUSH", b"str", b"d"],
10879            &[b"LMOVE", b"str", b"d", b"LEFT", b"LEFT"],
10880            &[b"LMPOP", b"1", b"str", b"LEFT"],
10881        ] {
10882            assert_eq!(f.run(cmd), wrong, "{:?}", String::from_utf8_lossy(cmd[0]));
10883        }
10884        assert_eq!(f.run(&[b"GET", b"str"]), "$1\r\nv\r\n");
10885        assert_eq!(f.run(&[b"EXISTS", b"d"]), ":0\r\n");
10886    }
10887
10888    /// A timeout is not an integer and it is not an ordinary float either: the
10889    /// three sentences it can answer with are its own, and which one a given
10890    /// argument gets is not what reading the code would suggest.
10891    #[test]
10892    fn a_timeout_has_three_ways_of_being_wrong() {
10893        let mut f = Fixture::new();
10894        let not_float = "-ERR timeout is not a float or out of range\r\n";
10895        let range = "-ERR timeout is out of range\r\n";
10896        for (bad, want) in [
10897            (&[b"BLPOP".as_slice(), b"k", b"abc"][..], not_float),
10898            (&[b"BLPOP", b"k", b"nan"], not_float),
10899            (&[b"BLPOP", b"k", b""], not_float),
10900            // Whitespace on either side, which `strtold` would take and Redis
10901            // does not.
10902            (&[b"BLPOP", b"k", b" 1"], not_float),
10903            (&[b"BLPOP", b"k", b"1 "], not_float),
10904            (&[b"BLPOP", b"k", b"-1"], "-ERR timeout is negative\r\n"),
10905            (&[b"BLPOP", b"k", b"-0.1"], "-ERR timeout is negative\r\n"),
10906            // These three parse, so they are not the not-a-float error, and all
10907            // three are further off than an i64 of milliseconds reaches.
10908            (&[b"BLPOP", b"k", b"1e400"], range),
10909            (&[b"BLPOP", b"k", b"inf"], range),
10910            (&[b"BLPOP", b"k", b"9999999999999999"], range),
10911            (&[b"BRPOP", b"k", b"abc"], not_float),
10912            (
10913                &[b"BLMOVE", b"a", b"b", b"LEFT", b"RIGHT", b"abc"],
10914                not_float,
10915            ),
10916            (
10917                &[b"BRPOPLPUSH", b"a", b"b", b"-1"],
10918                "-ERR timeout is negative\r\n",
10919            ),
10920            (&[b"BLMPOP", b"abc", b"1", b"k", b"LEFT"], not_float),
10921        ] {
10922            assert_eq!(f.run(bad), want, "for {bad:?}");
10923        }
10924    }
10925
10926    /// A timeout of exactly zero means no timeout, and there are two ways of
10927    /// writing exactly zero.
10928    #[test]
10929    fn a_zero_timeout_waits_and_the_smallest_positive_one_does_not() {
10930        let mut f = Fixture::new();
10931        for timeout in [b"0".as_slice(), b"0.0", b"-0.0"] {
10932            let (flow, out) = f.flow(&[b"BLPOP", b"k", timeout]);
10933            assert_eq!(flow, Flow::Block, "for {timeout:?}");
10934            assert!(out.is_empty(), "for {timeout:?}");
10935        }
10936        // Positive, so it is a real deadline, and the deadline is this
10937        // millisecond. Nothing is written here either: the reply comes from the
10938        // sweep, which is the engine's and not this layer's.
10939        let (flow, out) = f.flow(&[b"BLPOP", b"k", b"0.0000001"]);
10940        assert_eq!(flow, Flow::Block);
10941        assert!(out.is_empty());
10942    }
10943
10944    #[test]
10945    fn a_blocking_command_that_can_be_answered_answers_like_the_one_it_wraps() {
10946        let mut f = Fixture::new();
10947        f.run(&[b"RPUSH", b"L", b"a", b"b", b"c", b"d", b"e"]);
10948
10949        // The one difference from LPOP: the reply names the key that answered,
10950        // which is what makes BLPOP over several keys usable.
10951        assert_eq!(
10952            f.flow(&[b"BLPOP", b"nope", b"L", b"0"]),
10953            (Flow::Continue, "*2\r\n$1\r\nL\r\n$1\r\na\r\n".to_owned())
10954        );
10955        assert_eq!(
10956            f.run(&[b"BRPOP", b"L", b"0"]),
10957            "*2\r\n$1\r\nL\r\n$1\r\ne\r\n"
10958        );
10959        assert_eq!(
10960            f.run(&[
10961                b"BLMPOP", b"0", b"2", b"nope", b"L", b"LEFT", b"COUNT", b"2"
10962            ]),
10963            "*2\r\n$1\r\nL\r\n*2\r\n$1\r\nb\r\n$1\r\nc\r\n"
10964        );
10965        assert_eq!(
10966            f.run(&[b"BLMOVE", b"L", b"D", b"LEFT", b"RIGHT", b"0"]),
10967            "$1\r\nd\r\n"
10968        );
10969        assert_eq!(
10970            f.run(&[b"EXISTS", b"L"]),
10971            ":0\r\n",
10972            "and the key went with it"
10973        );
10974        assert_eq!(f.run(&[b"LRANGE", b"D", b"0", b"-1"]), "*1\r\n$1\r\nd\r\n");
10975        // Onto itself, which is how a list is rotated and is a real thing to ask
10976        // a blocking move for.
10977        f.run(&[b"RPUSH", b"D", b"x"]);
10978        assert_eq!(f.run(&[b"BRPOPLPUSH", b"D", b"D", b"0"]), "$1\r\nx\r\n");
10979        assert_eq!(
10980            f.run(&[b"LRANGE", b"D", b"0", b"-1"]),
10981            "*2\r\n$1\r\nx\r\n$1\r\nd\r\n"
10982        );
10983    }
10984
10985    #[test]
10986    fn blmpop_reads_its_count_and_its_key_count_the_way_lmpop_does() {
10987        let mut f = Fixture::new();
10988        f.run(&[b"RPUSH", b"k", b"a"]);
10989        for (bad, want) in [
10990            (
10991                &[b"BLMPOP".as_slice(), b"0", b"0", b"k", b"LEFT"][..],
10992                "-ERR numkeys should be greater than 0\r\n",
10993            ),
10994            (
10995                &[b"BLMPOP", b"0", b"-1", b"k", b"LEFT"],
10996                "-ERR numkeys should be greater than 0\r\n",
10997            ),
10998            // Two keys named and one given, so the word that should have been
10999            // the direction is a key and there is no direction left.
11000            (
11001                &[b"BLMPOP", b"0", b"2", b"k", b"LEFT"],
11002                "-ERR syntax error\r\n",
11003            ),
11004            (
11005                &[b"BLMPOP", b"0", b"1", b"k", b"SIDEWAYS"],
11006                "-ERR syntax error\r\n",
11007            ),
11008            (
11009                &[b"BLMPOP", b"0", b"1", b"k", b"LEFT", b"COUNT"],
11010                "-ERR syntax error\r\n",
11011            ),
11012            (
11013                &[b"BLMPOP", b"0", b"1", b"k", b"LEFT", b"COUNT", b"2", b"x"],
11014                "-ERR syntax error\r\n",
11015            ),
11016            // A count that is not a number at all gets the same sentence a zero
11017            // or a negative one gets, rather than the usual one about integers.
11018            (
11019                &[b"BLMPOP", b"0", b"1", b"k", b"LEFT", b"COUNT", b"0"],
11020                "-ERR count should be greater than 0\r\n",
11021            ),
11022            (
11023                &[b"BLMPOP", b"0", b"1", b"k", b"LEFT", b"COUNT", b"abc"],
11024                "-ERR count should be greater than 0\r\n",
11025            ),
11026        ] {
11027            assert_eq!(f.run(bad), want, "for {bad:?}");
11028        }
11029        assert_eq!(f.run(&[b"LLEN", b"k"]), ":1\r\n", "and none of them popped");
11030    }
11031
11032    #[test]
11033    fn a_blocking_move_reads_its_directions_before_its_timeout() {
11034        let mut f = Fixture::new();
11035        // Both are wrong. Redis checks the directions first, so this is the
11036        // syntax error and not a complaint about the timeout.
11037        assert_eq!(
11038            f.run(&[b"BLMOVE", b"a", b"b", b"UP", b"DOWN", b"abc"]),
11039            "-ERR syntax error\r\n"
11040        );
11041        assert_eq!(
11042            f.run(&[b"BLMOVE", b"a", b"b", b"LEFT", b"DOWN", b"0.05"]),
11043            "-ERR syntax error\r\n"
11044        );
11045    }
11046
11047    /// `BLMOVEM` answers exactly what `LMOVEM` answers when it does not have to
11048    /// wait, which is the same relationship every other command in this file has
11049    /// with the one it wraps.
11050    #[test]
11051    fn a_blocking_block_move_that_can_be_answered_answers_like_lmovem() {
11052        let mut f = Fixture::new();
11053        f.run(&[b"RPUSH", b"L", b"a", b"b", b"c", b"d", b"e"]);
11054        assert_eq!(
11055            f.flow(&[b"BLMOVEM", b"L", b"D", b"LEFT", b"RIGHT", b"0"]),
11056            (Flow::Continue, "*1\r\n$1\r\na\r\n".to_owned())
11057        );
11058        assert_eq!(
11059            f.run(&[
11060                b"BLMOVEM", b"L", b"D", b"RIGHT", b"RIGHT", b"0", b"COUNT", b"2", b"OBO"
11061            ]),
11062            bulks(&["e", "d"])
11063        );
11064        assert_eq!(
11065            f.run(&[b"LRANGE", b"D", b"0", b"-1"]),
11066            bulks(&["a", "e", "d"])
11067        );
11068        // `EXACTLY` with enough there does not wait either.
11069        assert_eq!(
11070            f.run(&[
11071                b"BLMOVEM", b"L", b"D", b"LEFT", b"RIGHT", b"0", b"EXACTLY", b"2", b"BULK"
11072            ]),
11073            bulks(&["b", "c"])
11074        );
11075        assert_eq!(f.run(&[b"EXISTS", b"L"]), ":0\r\n", "and the key went");
11076    }
11077
11078    /// The one thing `BLMOVEM` decides differently from the other five: `COUNT`
11079    /// is ready as soon as there is anything and `EXACTLY` is not ready until the
11080    /// whole block has arrived.
11081    #[test]
11082    fn a_blocking_block_move_waits_for_the_whole_block_only_under_exactly() {
11083        let mut f = Fixture::new();
11084        f.run(&[b"RPUSH", b"L", b"a", b"b"]);
11085        // Two there and three asked for. `COUNT` takes the two.
11086        assert_eq!(
11087            f.flow(&[
11088                b"BLMOVEM", b"L", b"D", b"LEFT", b"RIGHT", b"0", b"COUNT", b"3", b"BULK"
11089            ]),
11090            (Flow::Continue, bulks(&["a", "b"]))
11091        );
11092
11093        f.run(&[b"RPUSH", b"L", b"a", b"b"]);
11094        // The same line with `EXACTLY` parks instead, and takes nothing on the
11095        // way past.
11096        assert_eq!(
11097            f.flow(&[
11098                b"BLMOVEM", b"L", b"D", b"LEFT", b"RIGHT", b"0", b"EXACTLY", b"3", b"BULK"
11099            ])
11100            .0,
11101            Flow::Block
11102        );
11103        assert_eq!(f.run(&[b"LRANGE", b"L", b"0", b"-1"]), bulks(&["a", "b"]));
11104    }
11105
11106    #[test]
11107    fn a_blocking_block_move_reads_its_directions_then_its_timeout_then_its_count() {
11108        let mut f = Fixture::new();
11109        let syntax = "-ERR syntax error\r\n";
11110        // All three are wrong and the directions are read first.
11111        assert_eq!(
11112            f.run(&[
11113                b"BLMOVEM", b"a", b"b", b"UP", b"DOWN", b"abc", b"NOPE", b"x", b"y"
11114            ]),
11115            syntax
11116        );
11117        // Directions fine, timeout and count both wrong, so the timeout wins.
11118        assert_eq!(
11119            f.run(&[
11120                b"BLMOVEM", b"a", b"b", b"LEFT", b"RIGHT", b"abc", b"COUNT", b"abc", b"BULK"
11121            ]),
11122            "-ERR timeout is not a float or out of range\r\n"
11123        );
11124        assert_eq!(
11125            f.run(&[
11126                b"BLMOVEM", b"a", b"b", b"LEFT", b"RIGHT", b"-1", b"COUNT", b"1", b"BULK"
11127            ]),
11128            "-ERR timeout is negative\r\n"
11129        );
11130        // And with the timeout fine, the count before the ordering word.
11131        assert_eq!(
11132            f.run(&[
11133                b"BLMOVEM", b"a", b"b", b"LEFT", b"RIGHT", b"0", b"COUNT", b"abc", b"NOPE"
11134            ]),
11135            "-ERR count should be greater than 0\r\n"
11136        );
11137        assert_eq!(
11138            f.run(&[
11139                b"BLMOVEM", b"a", b"b", b"LEFT", b"RIGHT", b"0", b"COUNT", b"1", b"NOPE"
11140            ]),
11141            syntax
11142        );
11143        // Seven and eight arguments are neither of the two forms, the same way
11144        // six and seven are for `LMOVEM`.
11145        assert_eq!(
11146            f.run(&[b"BLMOVEM", b"a", b"b", b"LEFT", b"RIGHT", b"0", b"COUNT"]),
11147            syntax
11148        );
11149        assert_eq!(
11150            f.run(&[
11151                b"BLMOVEM", b"a", b"b", b"LEFT", b"RIGHT", b"0", b"COUNT", b"2"
11152            ]),
11153            syntax
11154        );
11155    }
11156
11157    /// The four ways a blocking command sees a key of another type, and the one
11158    /// way it does not.
11159    #[test]
11160    fn a_blocking_command_errors_on_a_wrong_type_rather_than_waiting_on_it() {
11161        let mut f = Fixture::new();
11162        f.run(&[b"SET", b"S", b"v"]);
11163        f.run(&[b"RPUSH", b"D", b"x"]);
11164        let wrong = "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n";
11165
11166        assert_eq!(f.run(&[b"BLPOP", b"S", b"0"]), wrong);
11167        // Every key is checked even when an earlier one would have blocked, so
11168        // an empty key in front of a string does not hide it.
11169        assert_eq!(f.run(&[b"BLPOP", b"E", b"S", b"0"]), wrong);
11170        assert_eq!(f.run(&[b"BRPOP", b"S", b"0"]), wrong);
11171        assert_eq!(f.run(&[b"BLMPOP", b"0", b"1", b"S", b"LEFT"]), wrong);
11172        assert_eq!(f.run(&[b"BRPOPLPUSH", b"S", b"D", b"0"]), wrong);
11173        // The destination, which is only reached because the source has
11174        // something in it.
11175        assert_eq!(f.run(&[b"BRPOPLPUSH", b"D", b"S", b"0"]), wrong);
11176        assert_eq!(f.run(&[b"LRANGE", b"D", b"0", b"-1"]), "*1\r\n$1\r\nx\r\n");
11177        assert_eq!(
11178            f.run(&[b"BLMOVEM", b"S", b"D", b"LEFT", b"RIGHT", b"0"]),
11179            wrong
11180        );
11181        assert_eq!(
11182            f.run(&[b"BLMOVEM", b"D", b"S", b"LEFT", b"RIGHT", b"0"]),
11183            wrong
11184        );
11185
11186        // And the one that does not: an empty source means the destination is
11187        // never looked at, so this waits rather than erroring, and on a real
11188        // server it times out.
11189        assert_eq!(
11190            f.flow(&[b"BLMOVE", b"E", b"S", b"LEFT", b"RIGHT", b"0.1"])
11191                .0,
11192            Flow::Block
11193        );
11194        // `BLMOVEM` has a second way of not being ready, and it hides the
11195        // destination just as well: the source is a list with two elements in it
11196        // and `EXACTLY` wants three, so the string never gets looked at.
11197        assert_eq!(
11198            f.flow(&[b"BLMOVEM", b"E", b"S", b"LEFT", b"RIGHT", b"0.1"])
11199                .0,
11200            Flow::Block
11201        );
11202        f.run(&[b"RPUSH", b"E", b"1", b"2"]);
11203        assert_eq!(
11204            f.flow(&[
11205                b"BLMOVEM", b"E", b"S", b"LEFT", b"RIGHT", b"0.1", b"EXACTLY", b"3", b"BULK"
11206            ])
11207            .0,
11208            Flow::Block
11209        );
11210    }
11211
11212    /// The same churn the set and the string get, because a list that leaks a
11213    /// chunk per push looks exactly like one that does not until it has run for
11214    /// an afternoon.
11215    /// Not under Miri, for the reason on `churning_sets_does_not_grow_the_server`.
11216    #[cfg_attr(miri, ignore = "the volume is the claim")]
11217    #[test]
11218    fn churning_lists_does_not_grow_the_server() {
11219        let mut f = Fixture::new();
11220        let vals: Vec<Vec<u8>> = (0..200).map(|i| format!("v{i}").into_bytes()).collect();
11221        let args: Vec<&[u8]> = [&b"RPUSH"[..], &b"k"[..]]
11222            .into_iter()
11223            .chain(vals.iter().map(Vec::as_slice))
11224            .collect();
11225
11226        f.run(&args);
11227        f.run(&[b"DEL", b"k"]);
11228        f.server.compact_step();
11229        let after_first = f.server.memory_bytes();
11230
11231        for _ in 0..200 {
11232            f.run(&args);
11233            f.run(&[b"LTRIM", b"k", b"1", b"0"]);
11234            f.server.compact_step();
11235        }
11236        assert_eq!(f.run(&[b"DBSIZE"]), ":0\r\n");
11237        assert!(
11238            f.server.memory_bytes() <= after_first * 2,
11239            "held {} after two hundred passes against {after_first} after one",
11240            f.server.memory_bytes()
11241        );
11242    }
11243
11244    // ------------------------------------------------------------ sorted set
11245
11246    #[test]
11247    fn a_sorted_set_takes_scores_and_gives_them_back() {
11248        let mut f = Fixture::new();
11249        assert_eq!(f.run(&[b"ZADD", b"z", b"1", b"a", b"2", b"b"]), ":2\r\n");
11250        assert_eq!(f.run(&[b"ZADD", b"z", b"1", b"a", b"3", b"c"]), ":1\r\n");
11251        assert_eq!(f.run(&[b"ZCARD", b"z"]), ":3\r\n");
11252        assert_eq!(f.run(&[b"ZSCORE", b"z", b"b"]), "$1\r\n2\r\n");
11253        assert_eq!(f.run(&[b"ZSCORE", b"z", b"nope"]), "$-1\r\n");
11254        assert_eq!(f.run(&[b"ZSCORE", b"nokey", b"b"]), "$-1\r\n");
11255        assert_eq!(
11256            f.run(&[b"ZMSCORE", b"z", b"a", b"nope", b"c"]),
11257            "*3\r\n$1\r\n1\r\n$-1\r\n$1\r\n3\r\n"
11258        );
11259        assert_eq!(f.run(&[b"ZREM", b"z", b"a", b"nope"]), ":1\r\n");
11260        assert_eq!(f.run(&[b"ZCARD", b"z"]), ":2\r\n");
11261        // The key goes when the last member does.
11262        assert_eq!(f.run(&[b"ZREM", b"z", b"b", b"c"]), ":2\r\n");
11263        assert_eq!(f.run(&[b"EXISTS", b"z"]), ":0\r\n");
11264    }
11265
11266    #[test]
11267    fn a_score_is_a_double_on_resp3_and_digits_on_resp2() {
11268        let mut f = Fixture::new();
11269        f.run(&[b"ZADD", b"z", b"1.5", b"a", b"inf", b"b", b"-inf", b"c"]);
11270        assert_eq!(f.run(&[b"ZSCORE", b"z", b"a"]), "$3\r\n1.5\r\n");
11271        assert_eq!(f.run(&[b"ZSCORE", b"z", b"b"]), "$3\r\ninf\r\n");
11272        assert_eq!(f.run(&[b"ZSCORE", b"z", b"c"]), "$4\r\n-inf\r\n");
11273
11274        f.out = Out::new(Proto::Resp3);
11275        assert_eq!(f.run(&[b"ZSCORE", b"z", b"a"]), ",1.5\r\n");
11276        assert_eq!(f.run(&[b"ZSCORE", b"z", b"b"]), ",inf\r\n");
11277        assert_eq!(f.run(&[b"ZSCORE", b"z", b"c"]), ",-inf\r\n");
11278        assert_eq!(f.run(&[b"ZSCORE", b"z", b"nope"]), "_\r\n");
11279    }
11280
11281    #[test]
11282    fn the_zadd_options_gate_what_gets_written() {
11283        let mut f = Fixture::new();
11284        f.run(&[b"ZADD", b"z", b"5", b"a"]);
11285        // NX leaves a member that is there alone, XX will not create one.
11286        assert_eq!(f.run(&[b"ZADD", b"z", b"NX", b"9", b"a"]), ":0\r\n");
11287        assert_eq!(f.run(&[b"ZSCORE", b"z", b"a"]), "$1\r\n5\r\n");
11288        assert_eq!(f.run(&[b"ZADD", b"z", b"XX", b"9", b"new"]), ":0\r\n");
11289        assert_eq!(f.run(&[b"EXISTS", b"z"]), ":1\r\n");
11290        // GT and LT only move a score one way.
11291        assert_eq!(f.run(&[b"ZADD", b"z", b"GT", b"CH", b"3", b"a"]), ":0\r\n");
11292        assert_eq!(f.run(&[b"ZADD", b"z", b"GT", b"CH", b"7", b"a"]), ":1\r\n");
11293        assert_eq!(f.run(&[b"ZADD", b"z", b"LT", b"CH", b"9", b"a"]), ":0\r\n");
11294        // CH counts a moved score and plain ZADD does not.
11295        assert_eq!(f.run(&[b"ZADD", b"z", b"1", b"a", b"1", b"b"]), ":1\r\n");
11296        assert_eq!(
11297            f.run(&[b"ZADD", b"z", b"CH", b"2", b"a", b"2", b"c"]),
11298            ":2\r\n"
11299        );
11300    }
11301
11302    #[test]
11303    fn zadd_incr_answers_a_score_or_nothing_at_all() {
11304        let mut f = Fixture::new();
11305        assert_eq!(f.run(&[b"ZADD", b"z", b"INCR", b"5", b"m"]), "$1\r\n5\r\n");
11306        assert_eq!(f.run(&[b"ZADD", b"z", b"INCR", b"2", b"m"]), "$1\r\n7\r\n");
11307        // A gate that refuses is the string nil, because the reply it stands in
11308        // for is a score.
11309        assert_eq!(
11310            f.run(&[b"ZADD", b"z", b"NX", b"INCR", b"2", b"m"]),
11311            "$-1\r\n"
11312        );
11313        assert_eq!(
11314            f.run(&[b"ZADD", b"z", b"XX", b"INCR", b"2", b"gone"]),
11315            "$-1\r\n"
11316        );
11317        assert_eq!(
11318            f.run(&[b"ZADD", b"z", b"GT", b"INCR", b"-1", b"m"]),
11319            "$-1\r\n"
11320        );
11321        assert_eq!(
11322            f.run(&[b"ZADD", b"z", b"GT", b"INCR", b"1", b"m"]),
11323            "$1\r\n8\r\n"
11324        );
11325        assert_eq!(f.run(&[b"ZINCRBY", b"z", b"2", b"m"]), "$2\r\n10\r\n");
11326        assert_eq!(f.run(&[b"ZINCRBY", b"z", b"1", b"fresh"]), "$1\r\n1\r\n");
11327    }
11328
11329    #[test]
11330    fn the_two_infinities_will_not_be_added_together() {
11331        let mut f = Fixture::new();
11332        f.run(&[b"ZADD", b"z", b"inf", b"m"]);
11333        let nan = "-ERR resulting score is not a number (NaN)\r\n";
11334        assert_eq!(f.run(&[b"ZINCRBY", b"z", b"-inf", b"m"]), nan);
11335        assert_eq!(f.run(&[b"ZADD", b"z", b"INCR", b"-inf", b"m"]), nan);
11336        assert_eq!(f.run(&[b"ZSCORE", b"z", b"m"]), "$3\r\ninf\r\n");
11337        // And a key made for an increment that then fails does not stay behind.
11338        assert_eq!(f.run(&[b"ZINCRBY", b"gone", b"1", b"m"]), "$1\r\n1\r\n");
11339    }
11340
11341    #[test]
11342    fn zadd_says_its_mistakes_the_way_redis_says_them() {
11343        let mut f = Fixture::new();
11344        // The pairs are counted before the options are looked at, so this is a
11345        // syntax error about having none and not a complaint about NX and XX.
11346        assert_eq!(
11347            f.run(&[b"ZADD", b"z", b"NX", b"XX"]),
11348            "-ERR syntax error\r\n"
11349        );
11350        assert_eq!(
11351            f.run(&[b"ZADD", b"z", b"NX", b"XX", b"1", b"a"]),
11352            "-ERR XX and NX options at the same time are not compatible\r\n"
11353        );
11354        let gtlt = "-ERR GT, LT, and/or NX options at the same time are not compatible\r\n";
11355        assert_eq!(f.run(&[b"ZADD", b"z", b"NX", b"GT", b"1", b"a"]), gtlt);
11356        assert_eq!(f.run(&[b"ZADD", b"z", b"GT", b"LT", b"1", b"a"]), gtlt);
11357        assert_eq!(
11358            f.run(&[b"ZADD", b"z", b"INCR", b"1", b"a", b"2", b"b"]),
11359            "-ERR INCR option supports a single increment-element pair\r\n"
11360        );
11361        // An odd number of arguments after the options.
11362        assert_eq!(
11363            f.run(&[b"ZADD", b"z", b"1", b"a", b"2"]),
11364            "-ERR syntax error\r\n"
11365        );
11366        // Every score is read before the first is stored.
11367        assert_eq!(
11368            f.run(&[b"ZADD", b"z", b"1", b"a", b"nonsense", b"b"]),
11369            "-ERR value is not a valid float\r\n"
11370        );
11371        assert_eq!(f.run(&[b"EXISTS", b"z"]), ":0\r\n");
11372    }
11373
11374    #[test]
11375    fn a_rank_says_where_a_member_sits_from_either_end() {
11376        let mut f = Fixture::new();
11377        f.run(&[b"ZADD", b"z", b"1", b"a", b"2", b"b", b"3", b"c"]);
11378        assert_eq!(f.run(&[b"ZRANK", b"z", b"a"]), ":0\r\n");
11379        assert_eq!(f.run(&[b"ZRANK", b"z", b"c"]), ":2\r\n");
11380        assert_eq!(f.run(&[b"ZREVRANK", b"z", b"c"]), ":0\r\n");
11381        assert_eq!(f.run(&[b"ZREVRANK", b"z", b"a"]), ":2\r\n");
11382        // WITHSCORE changes both shapes: the answer and the nothing.
11383        assert_eq!(
11384            f.run(&[b"ZRANK", b"z", b"b", b"WITHSCORE"]),
11385            "*2\r\n:1\r\n$1\r\n2\r\n"
11386        );
11387        assert_eq!(f.run(&[b"ZRANK", b"z", b"nope"]), "$-1\r\n");
11388        assert_eq!(f.run(&[b"ZRANK", b"z", b"nope", b"WITHSCORE"]), "*-1\r\n");
11389        assert_eq!(f.run(&[b"ZRANK", b"nokey", b"a", b"WITHSCORE"]), "*-1\r\n");
11390        // A bad option is a syntax error and one argument too many is an arity
11391        // error, which is Redis's split.
11392        assert_eq!(
11393            f.run(&[b"ZRANK", b"z", b"b", b"bogus"]),
11394            "-ERR syntax error\r\n"
11395        );
11396        assert_eq!(
11397            f.run(&[b"ZREVRANK", b"z", b"b", b"WITHSCORE", b"more"]),
11398            "-ERR wrong number of arguments for 'zrevrank' command\r\n"
11399        );
11400    }
11401
11402    #[test]
11403    fn the_two_counts_read_their_two_kinds_of_bound() {
11404        let mut f = Fixture::new();
11405        f.run(&[b"ZADD", b"z", b"1", b"a", b"2", b"b", b"3", b"c"]);
11406        assert_eq!(f.run(&[b"ZCOUNT", b"z", b"-inf", b"+inf"]), ":3\r\n");
11407        assert_eq!(f.run(&[b"ZCOUNT", b"z", b"2", b"3"]), ":2\r\n");
11408        assert_eq!(f.run(&[b"ZCOUNT", b"z", b"(1", b"3"]), ":2\r\n");
11409        assert_eq!(f.run(&[b"ZCOUNT", b"z", b"(1", b"(3"]), ":1\r\n");
11410        assert_eq!(f.run(&[b"ZCOUNT", b"nokey", b"-inf", b"+inf"]), ":0\r\n");
11411        assert_eq!(
11412            f.run(&[b"ZCOUNT", b"z", b"bogus", b"3"]),
11413            "-ERR min or max is not a float\r\n"
11414        );
11415
11416        f.run(&[b"ZADD", b"l", b"0", b"a", b"0", b"b", b"0", b"c"]);
11417        assert_eq!(f.run(&[b"ZLEXCOUNT", b"l", b"-", b"+"]), ":3\r\n");
11418        assert_eq!(f.run(&[b"ZLEXCOUNT", b"l", b"[a", b"(c"]), ":2\r\n");
11419        assert_eq!(f.run(&[b"ZLEXCOUNT", b"l", b"(a", b"+"]), ":2\r\n");
11420        // A bare member is not a bound, because a member can start with any
11421        // byte and there would be no way to say the bracket if it were optional.
11422        assert_eq!(
11423            f.run(&[b"ZLEXCOUNT", b"l", b"a", b"c"]),
11424            "-ERR min or max not valid string range item\r\n"
11425        );
11426    }
11427
11428    /// The three ways `ZRANGE` can be asked for a window, forwards and back.
11429    ///
11430    /// Every byte in here was read off a real 8.10.1 rather than worked out,
11431    /// because the interesting part of this command is not what it selects, it
11432    /// is which of the two ends the client is expected to name first.
11433    #[test]
11434    fn one_range_command_selects_by_rank_or_score_or_name() {
11435        let mut f = Fixture::new();
11436        f.run(&[b"ZADD", b"z", b"1", b"a", b"2", b"b", b"3", b"c"]);
11437        assert_eq!(
11438            f.run(&[b"ZRANGE", b"z", b"0", b"-1"]),
11439            "*3\r\n$1\r\na\r\n$1\r\nb\r\n$1\r\nc\r\n"
11440        );
11441        assert_eq!(
11442            f.run(&[b"ZRANGE", b"z", b"-2", b"-1"]),
11443            "*2\r\n$1\r\nb\r\n$1\r\nc\r\n"
11444        );
11445        assert_eq!(f.run(&[b"ZRANGE", b"z", b"5", b"9"]), "*0\r\n");
11446        assert_eq!(f.run(&[b"ZRANGE", b"nokey", b"0", b"-1"]), "*0\r\n");
11447        // REV over ranks reverses the walk and leaves the two arguments alone,
11448        // because a rank counts from the end the walk starts at.
11449        assert_eq!(
11450            f.run(&[b"ZRANGE", b"z", b"0", b"-1", b"REV"]),
11451            "*3\r\n$1\r\nc\r\n$1\r\nb\r\n$1\r\na\r\n"
11452        );
11453        assert_eq!(
11454            f.run(&[b"ZRANGE", b"z", b"(1", b"+inf", b"BYSCORE"]),
11455            "*2\r\n$1\r\nb\r\n$1\r\nc\r\n"
11456        );
11457        // And REV over scores does swap them, since a bound does not count from
11458        // anywhere. This is the one line of the parse that tells the two apart.
11459        assert_eq!(
11460            f.run(&[b"ZRANGE", b"z", b"+inf", b"(1", b"BYSCORE", b"REV"]),
11461            "*2\r\n$1\r\nc\r\n$1\r\nb\r\n"
11462        );
11463        assert_eq!(
11464            f.run(&[b"ZRANGE", b"z", b"-", b"+", b"BYLEX"]),
11465            "*3\r\n$1\r\na\r\n$1\r\nb\r\n$1\r\nc\r\n"
11466        );
11467        assert_eq!(
11468            f.run(&[b"ZRANGE", b"z", b"+", b"-", b"BYLEX", b"REV"]),
11469            "*3\r\n$1\r\nc\r\n$1\r\nb\r\n$1\r\na\r\n"
11470        );
11471    }
11472
11473    /// The older spellings, which are the same six windows with the mode in the
11474    /// name and the high end named first on the three that go backwards.
11475    #[test]
11476    fn the_older_range_spellings_name_their_high_end_first() {
11477        let mut f = Fixture::new();
11478        f.run(&[b"ZADD", b"z", b"1", b"a", b"2", b"b", b"3", b"c"]);
11479        assert_eq!(
11480            f.run(&[b"ZREVRANGE", b"z", b"0", b"-1"]),
11481            "*3\r\n$1\r\nc\r\n$1\r\nb\r\n$1\r\na\r\n"
11482        );
11483        assert_eq!(
11484            f.run(&[b"ZREVRANGE", b"z", b"0", b"0", b"WITHSCORES"]),
11485            "*2\r\n$1\r\nc\r\n$1\r\n3\r\n"
11486        );
11487        assert_eq!(
11488            f.run(&[b"ZRANGEBYSCORE", b"z", b"(1", b"3"]),
11489            "*2\r\n$1\r\nb\r\n$1\r\nc\r\n"
11490        );
11491        assert_eq!(
11492            f.run(&[b"ZREVRANGEBYSCORE", b"z", b"3", b"(1"]),
11493            "*2\r\n$1\r\nc\r\n$1\r\nb\r\n"
11494        );
11495        // The two arguments the wrong way round is an empty answer and not an
11496        // error, which is what the swap being in the parse rather than in the
11497        // window buys.
11498        assert_eq!(f.run(&[b"ZREVRANGEBYSCORE", b"z", b"(1", b"3"]), "*0\r\n");
11499        assert_eq!(
11500            f.run(&[b"ZRANGEBYLEX", b"z", b"[a", b"(c"]),
11501            "*2\r\n$1\r\na\r\n$1\r\nb\r\n"
11502        );
11503        assert_eq!(
11504            f.run(&[b"ZREVRANGEBYLEX", b"z", b"(c", b"[a"]),
11505            "*2\r\n$1\r\nb\r\n$1\r\na\r\n"
11506        );
11507        // BYSCORE, BYLEX and REV mean nothing to these, so they are not another
11508        // way of spelling the mode, they are a syntax error.
11509        for cmd in [
11510            &[b"ZREVRANGE".as_slice(), b"z", b"0", b"-1", b"BYSCORE"][..],
11511            &[b"ZRANGEBYSCORE", b"z", b"1", b"3", b"REV"],
11512            &[b"ZRANGEBYLEX", b"z", b"[a", b"[c", b"BYLEX"],
11513        ] {
11514            assert_eq!(f.run(cmd), "-ERR syntax error\r\n", "{:?}", cmd[0]);
11515        }
11516    }
11517
11518    /// `LIMIT` and `WITHSCORES`, which every one of these commands reads and
11519    /// only some of them accept.
11520    #[test]
11521    fn limit_and_withscores_are_read_by_all_of_them_and_refused_afterwards() {
11522        let mut f = Fixture::new();
11523        f.run(&[b"ZADD", b"z", b"1", b"a", b"2", b"b", b"3", b"c"]);
11524        assert_eq!(
11525            f.run(&[
11526                b"ZRANGE", b"z", b"-inf", b"+inf", b"BYSCORE", b"LIMIT", b"1", b"1"
11527            ]),
11528            "*1\r\n$1\r\nb\r\n"
11529        );
11530        // A negative offset skips past everything, a negative count is no bound.
11531        assert_eq!(
11532            f.run(&[
11533                b"ZRANGE", b"z", b"-inf", b"+inf", b"BYSCORE", b"LIMIT", b"-1", b"2"
11534            ]),
11535            "*0\r\n"
11536        );
11537        assert_eq!(
11538            f.run(&[
11539                b"ZRANGE", b"z", b"-inf", b"+inf", b"BYSCORE", b"LIMIT", b"0", b"-1"
11540            ]),
11541            "*3\r\n$1\r\na\r\n$1\r\nb\r\n$1\r\nc\r\n"
11542        );
11543        // The two options in either order, which falls out of the parse loop.
11544        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";
11545        assert_eq!(
11546            f.run(&[
11547                b"ZRANGEBYSCORE",
11548                b"z",
11549                b"1",
11550                b"3",
11551                b"WITHSCORES",
11552                b"LIMIT",
11553                b"0",
11554                b"2"
11555            ]),
11556            both
11557        );
11558        assert_eq!(
11559            f.run(&[
11560                b"ZRANGEBYSCORE",
11561                b"z",
11562                b"1",
11563                b"3",
11564                b"LIMIT",
11565                b"0",
11566                b"2",
11567                b"WITHSCORES"
11568            ]),
11569            both
11570        );
11571        // LIMIT on a range by rank is refused after the whole option list has
11572        // been read, so this complains about LIMIT and not about WITHSCORES.
11573        let needs_by = "-ERR syntax error, LIMIT is only supported in combination with either BYSCORE or BYLEX\r\n";
11574        assert_eq!(
11575            f.run(&[
11576                b"ZREVRANGE",
11577                b"z",
11578                b"0",
11579                b"-1",
11580                b"WITHSCORES",
11581                b"LIMIT",
11582                b"0",
11583                b"1"
11584            ]),
11585            needs_by
11586        );
11587        assert_eq!(
11588            f.run(&[b"ZRANGE", b"z", b"0", b"-1", b"LIMIT", b"0", b"1"]),
11589            needs_by
11590        );
11591        let not_bylex = "-ERR syntax error, WITHSCORES not supported in combination with BYLEX\r\n";
11592        assert_eq!(
11593            f.run(&[b"ZRANGE", b"z", b"-", b"+", b"BYLEX", b"WITHSCORES"]),
11594            not_bylex
11595        );
11596        assert_eq!(
11597            f.run(&[b"ZRANGEBYLEX", b"z", b"[a", b"[c", b"WITHSCORES"]),
11598            not_bylex
11599        );
11600        // Two modes at once, an option nobody knows, a LIMIT missing its count,
11601        // and the three number errors, which are three different sentences.
11602        for cmd in [
11603            &[
11604                b"ZRANGE".as_slice(),
11605                b"z",
11606                b"0",
11607                b"-1",
11608                b"BYSCORE",
11609                b"BYLEX",
11610            ][..],
11611            &[b"ZRANGE", b"z", b"0", b"-1", b"junk"],
11612            &[b"ZRANGEBYSCORE", b"z", b"1", b"3", b"LIMIT", b"0"],
11613        ] {
11614            assert_eq!(f.run(cmd), "-ERR syntax error\r\n", "{cmd:?}");
11615        }
11616        assert_eq!(
11617            f.run(&[b"ZRANGEBYSCORE", b"z", b"bad", b"3"]),
11618            "-ERR min or max is not a float\r\n"
11619        );
11620        assert_eq!(
11621            f.run(&[b"ZRANGEBYLEX", b"z", b"a", b"[c"]),
11622            "-ERR min or max not valid string range item\r\n"
11623        );
11624        assert_eq!(
11625            f.run(&[b"ZRANGEBYSCORE", b"z", b"1", b"3", b"LIMIT", b"a", b"2"]),
11626            "-ERR value is not an integer or out of range\r\n"
11627        );
11628    }
11629
11630    /// `WITHSCORES` is the one place in this group where the two protocols
11631    /// disagree about the shape of the reply and not just the type of a value.
11632    #[test]
11633    fn withscores_nests_on_resp3_and_flattens_on_resp2() {
11634        let mut f = Fixture::new();
11635        f.run(&[b"ZADD", b"z", b"1", b"a", b"2", b"b", b"3", b"c"]);
11636        assert_eq!(
11637            f.run(&[b"ZRANGE", b"z", b"0", b"-1", b"WITHSCORES"]),
11638            "*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"
11639        );
11640        f.out = Out::new(Proto::Resp3);
11641        assert_eq!(
11642            f.run(&[b"ZRANGE", b"z", b"0", b"-1", b"WITHSCORES"]),
11643            "*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"
11644        );
11645        assert_eq!(
11646            f.run(&[b"ZRANGE", b"z", b"0", b"-1"]),
11647            "*3\r\n$1\r\na\r\n$1\r\nb\r\n$1\r\nc\r\n"
11648        );
11649    }
11650
11651    /// The store form, which is the same parse with the destination in front.
11652    #[test]
11653    fn a_range_store_writes_the_window_into_another_key() {
11654        let mut f = Fixture::new();
11655        f.run(&[b"ZADD", b"z", b"1", b"a", b"2", b"b", b"3", b"c"]);
11656        assert_eq!(f.run(&[b"ZRANGESTORE", b"d", b"z", b"0", b"-1"]), ":3\r\n");
11657        // A window that selects nothing deletes the destination rather than
11658        // leaving an empty sorted set, because an empty one does not exist.
11659        assert_eq!(f.run(&[b"ZRANGESTORE", b"d", b"z", b"5", b"9"]), ":0\r\n");
11660        assert_eq!(f.run(&[b"EXISTS", b"d"]), ":0\r\n");
11661        assert_eq!(
11662            f.run(&[b"ZRANGESTORE", b"d", b"z", b"(1", b"+inf", b"BYSCORE"]),
11663            ":2\r\n"
11664        );
11665        assert_eq!(
11666            f.run(&[b"ZRANGE", b"d", b"0", b"-1", b"WITHSCORES"]),
11667            "*4\r\n$1\r\nb\r\n$1\r\n2\r\n$1\r\nc\r\n$1\r\n3\r\n"
11668        );
11669        // The destination is allowed to be the source, because the result is
11670        // built whole before anything is written over.
11671        assert_eq!(f.run(&[b"ZRANGESTORE", b"z", b"z", b"1", b"2"]), ":2\r\n");
11672        assert_eq!(
11673            f.run(&[b"ZRANGE", b"z", b"0", b"-1", b"WITHSCORES"]),
11674            "*4\r\n$1\r\nb\r\n$1\r\n2\r\n$1\r\nc\r\n$1\r\n3\r\n"
11675        );
11676        // It takes every option ZRANGE takes except WITHSCORES, which is a
11677        // plain syntax error here and not the sentence about BYLEX.
11678        assert_eq!(
11679            f.run(&[b"ZRANGESTORE", b"d", b"z", b"0", b"-1", b"WITHSCORES"]),
11680            "-ERR syntax error\r\n"
11681        );
11682    }
11683
11684    /// The three removals, which are the read side's window with the walk
11685    /// turned into a removal and no options at all.
11686    #[test]
11687    fn the_three_removals_share_their_window_with_the_reads() {
11688        let mut f = Fixture::new();
11689        f.run(&[b"ZADD", b"z", b"1", b"a", b"2", b"b", b"3", b"c"]);
11690        assert_eq!(f.run(&[b"ZREMRANGEBYRANK", b"z", b"0", b"0"]), ":1\r\n");
11691        assert_eq!(
11692            f.run(&[b"ZRANGE", b"z", b"0", b"-1"]),
11693            "*2\r\n$1\r\nb\r\n$1\r\nc\r\n"
11694        );
11695        assert_eq!(
11696            f.run(&[b"ZREMRANGEBYSCORE", b"z", b"(2", b"+inf"]),
11697            ":1\r\n"
11698        );
11699        assert_eq!(f.run(&[b"ZRANGE", b"z", b"0", b"-1"]), "*1\r\n$1\r\nb\r\n");
11700        // The last member going takes the key with it.
11701        assert_eq!(f.run(&[b"ZREMRANGEBYLEX", b"z", b"-", b"+"]), ":1\r\n");
11702        assert_eq!(f.run(&[b"EXISTS", b"z"]), ":0\r\n");
11703        assert_eq!(
11704            f.run(&[b"ZREMRANGEBYRANK", b"nokey", b"0", b"-1"]),
11705            ":0\r\n"
11706        );
11707        assert_eq!(
11708            f.run(&[b"ZREMRANGEBYRANK", b"z", b"0", b"x"]),
11709            "-ERR value is not an integer or out of range\r\n"
11710        );
11711    }
11712
11713    /// The algebra, which is one gather and three names for it.
11714    #[test]
11715    fn the_three_algebra_commands_combine_scores_and_order_the_answer_once() {
11716        let mut f = Fixture::new();
11717        f.run(&[b"ZADD", b"z", b"1", b"a", b"2", b"b", b"3", b"c"]);
11718        f.run(&[b"ZADD", b"y", b"10", b"b", b"20", b"d"]);
11719        assert_eq!(
11720            f.run(&[b"ZUNION", b"2", b"z", b"y"]),
11721            "*4\r\n$1\r\na\r\n$1\r\nc\r\n$1\r\nb\r\n$1\r\nd\r\n"
11722        );
11723        // The scores are added where a member is in both, and the answer comes
11724        // out in the order those combined scores put it in.
11725        assert_eq!(
11726            f.run(&[b"ZUNION", b"2", b"z", b"y", b"WITHSCORES"]),
11727            "*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"
11728        );
11729        assert_eq!(
11730            f.run(&[
11731                b"ZUNION",
11732                b"2",
11733                b"z",
11734                b"y",
11735                b"WEIGHTS",
11736                b"2",
11737                b"3",
11738                b"WITHSCORES"
11739            ]),
11740            "*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"
11741        );
11742        assert_eq!(
11743            f.run(&[
11744                b"ZUNION",
11745                b"2",
11746                b"z",
11747                b"y",
11748                b"AGGREGATE",
11749                b"MIN",
11750                b"WITHSCORES"
11751            ]),
11752            "*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"
11753        );
11754        assert_eq!(
11755            f.run(&[
11756                b"ZUNION",
11757                b"2",
11758                b"z",
11759                b"y",
11760                b"AGGREGATE",
11761                b"MAX",
11762                b"WITHSCORES"
11763            ]),
11764            "*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"
11765        );
11766        assert_eq!(
11767            f.run(&[b"ZINTER", b"2", b"z", b"y", b"WITHSCORES"]),
11768            "*2\r\n$1\r\nb\r\n$2\r\n12\r\n"
11769        );
11770        assert_eq!(
11771            f.run(&[b"ZDIFF", b"2", b"z", b"y", b"WITHSCORES"]),
11772            "*4\r\n$1\r\na\r\n$1\r\n1\r\n$1\r\nc\r\n$1\r\n3\r\n"
11773        );
11774        assert_eq!(f.run(&[b"ZUNION", b"1", b"nokey"]), "*0\r\n");
11775        // A plain set is an input, and it behaves as a sorted set in which
11776        // every member scores one.
11777        f.run(&[b"SADD", b"p", b"a", b"d"]);
11778        assert_eq!(
11779            f.run(&[b"ZUNION", b"2", b"z", b"p", b"WITHSCORES"]),
11780            "*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"
11781        );
11782        // A difference never combines two scores, so it has nothing for either
11783        // of the two options to do and refuses both.
11784        for cmd in [
11785            &[
11786                b"ZDIFF".as_slice(),
11787                b"2",
11788                b"z",
11789                b"y",
11790                b"WEIGHTS",
11791                b"1",
11792                b"1",
11793            ][..],
11794            &[b"ZDIFF", b"2", b"z", b"y", b"AGGREGATE", b"MIN"],
11795        ] {
11796            assert_eq!(f.run(cmd), "-ERR syntax error\r\n", "{cmd:?}");
11797        }
11798    }
11799
11800    /// The count of keys, which is what lets a key be named `WEIGHTS`.
11801    #[test]
11802    fn the_algebra_counts_its_keys_and_says_so_when_the_count_is_wrong() {
11803        let mut f = Fixture::new();
11804        f.run(&[b"ZADD", b"z", b"1", b"a"]);
11805        f.run(&[b"ZADD", b"y", b"2", b"b"]);
11806        // Redis names the command in this one, so each spelling says its own.
11807        assert_eq!(
11808            f.run(&[b"ZUNION", b"0", b"z"]),
11809            "-ERR at least 1 input key is needed for 'zunion' command\r\n"
11810        );
11811        assert_eq!(
11812            f.run(&[b"ZUNION", b"-1", b"z"]),
11813            "-ERR at least 1 input key is needed for 'zunion' command\r\n"
11814        );
11815        assert_eq!(
11816            f.run(&[b"ZINTERCARD", b"0", b"z"]),
11817            "-ERR at least 1 input key is needed for 'zintercard' command\r\n"
11818        );
11819        // A count bigger than the line is a plain syntax error, which reads
11820        // oddly and is what Redis says.
11821        assert_eq!(
11822            f.run(&[b"ZUNION", b"3", b"z", b"y"]),
11823            "-ERR syntax error\r\n"
11824        );
11825        assert_eq!(
11826            f.run(&[b"ZUNION", b"x", b"z"]),
11827            "-ERR value is not an integer or out of range\r\n"
11828        );
11829        // A WEIGHTS list that is not one per key is a syntax error, and a
11830        // weight that is not a number gets a sentence of its own.
11831        assert_eq!(
11832            f.run(&[b"ZUNION", b"2", b"z", b"y", b"WEIGHTS", b"1"]),
11833            "-ERR syntax error\r\n"
11834        );
11835        assert_eq!(
11836            f.run(&[b"ZUNION", b"2", b"z", b"y", b"WEIGHTS", b"a", b"b"]),
11837            "-ERR weight value is not a float\r\n"
11838        );
11839        assert_eq!(
11840            f.run(&[b"ZUNION", b"2", b"z", b"y", b"AGGREGATE", b"NOPE"]),
11841            "-ERR syntax error\r\n"
11842        );
11843    }
11844
11845    /// The three store forms, which answer a count and take no WITHSCORES.
11846    #[test]
11847    fn the_algebra_stores_answer_a_count_and_delete_an_empty_destination() {
11848        let mut f = Fixture::new();
11849        f.run(&[b"ZADD", b"z", b"1", b"a", b"2", b"b", b"3", b"c"]);
11850        f.run(&[b"ZADD", b"y", b"10", b"b", b"20", b"d"]);
11851        assert_eq!(f.run(&[b"ZUNIONSTORE", b"d", b"2", b"z", b"y"]), ":4\r\n");
11852        assert_eq!(
11853            f.run(&[b"ZRANGE", b"d", b"0", b"-1", b"WITHSCORES"]),
11854            "*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"
11855        );
11856        assert_eq!(f.run(&[b"ZINTERSTORE", b"d", b"2", b"z", b"y"]), ":1\r\n");
11857        assert_eq!(f.run(&[b"ZDIFFSTORE", b"d", b"2", b"z", b"y"]), ":2\r\n");
11858        // An empty result deletes the destination rather than leaving an empty
11859        // sorted set, because an empty one does not exist.
11860        assert_eq!(
11861            f.run(&[b"ZINTERSTORE", b"d", b"2", b"z", b"nokey"]),
11862            ":0\r\n"
11863        );
11864        assert_eq!(f.run(&[b"EXISTS", b"d"]), ":0\r\n");
11865        // The destination is allowed to name its own source.
11866        assert_eq!(f.run(&[b"ZUNIONSTORE", b"z", b"2", b"z", b"y"]), ":4\r\n");
11867        assert_eq!(f.run(&[b"ZCARD", b"z"]), ":4\r\n");
11868        for cmd in [
11869            &[
11870                b"ZUNIONSTORE".as_slice(),
11871                b"d",
11872                b"2",
11873                b"z",
11874                b"y",
11875                b"WITHSCORES",
11876            ][..],
11877            &[
11878                b"ZDIFFSTORE",
11879                b"d",
11880                b"2",
11881                b"z",
11882                b"y",
11883                b"WEIGHTS",
11884                b"1",
11885                b"1",
11886            ],
11887        ] {
11888            assert_eq!(f.run(cmd), "-ERR syntax error\r\n", "{cmd:?}");
11889        }
11890    }
11891
11892    /// `ZINTERCARD`, which counts without building anything.
11893    #[test]
11894    fn intercard_counts_and_stops_at_its_limit() {
11895        let mut f = Fixture::new();
11896        f.run(&[b"ZADD", b"z", b"1", b"a", b"2", b"b", b"3", b"c"]);
11897        f.run(&[b"ZADD", b"y", b"10", b"b", b"20", b"c", b"30", b"d"]);
11898        assert_eq!(f.run(&[b"ZINTERCARD", b"2", b"z", b"y"]), ":2\r\n");
11899        // A limit of zero is no limit, which is Redis's reading of it.
11900        assert_eq!(
11901            f.run(&[b"ZINTERCARD", b"2", b"z", b"y", b"LIMIT", b"0"]),
11902            ":2\r\n"
11903        );
11904        assert_eq!(
11905            f.run(&[b"ZINTERCARD", b"2", b"z", b"y", b"LIMIT", b"1"]),
11906            ":1\r\n"
11907        );
11908        // A negative limit and a limit that is not a number at all get the same
11909        // sentence, which looks like a mistake in Redis and is copied as one.
11910        let bad = "-ERR LIMIT can't be negative\r\n";
11911        assert_eq!(
11912            f.run(&[b"ZINTERCARD", b"2", b"z", b"y", b"LIMIT", b"-1"]),
11913            bad
11914        );
11915        assert_eq!(
11916            f.run(&[b"ZINTERCARD", b"2", b"z", b"y", b"LIMIT", b"x"]),
11917            bad
11918        );
11919        for cmd in [
11920            &[b"ZINTERCARD".as_slice(), b"3", b"z", b"y"][..],
11921            &[b"ZINTERCARD", b"2", b"z", b"y", b"LIMIT"],
11922            &[b"ZINTERCARD", b"2", b"z", b"y", b"junk", b"1"],
11923        ] {
11924            assert_eq!(f.run(cmd), "-ERR syntax error\r\n", "{cmd:?}");
11925        }
11926    }
11927
11928    /// `ZRANDMEMBER`, which answers two different shapes out of one name.
11929    #[test]
11930    fn a_draw_answers_one_member_or_an_array_of_them() {
11931        let mut f = Fixture::new();
11932        f.run(&[b"ZADD", b"z", b"1", b"a", b"2", b"b", b"3", b"c"]);
11933        // No count is one member or a nil, a count is an array that may be
11934        // empty, and those are two reply types the client has to tell apart.
11935        assert_eq!(f.run(&[b"ZRANDMEMBER", b"nokey"]), "$-1\r\n");
11936        assert_eq!(f.run(&[b"ZRANDMEMBER", b"nokey", b"3"]), "*0\r\n");
11937        assert_eq!(f.run(&[b"ZRANDMEMBER", b"z", b"0"]), "*0\r\n");
11938        assert!(f.run(&[b"ZRANDMEMBER", b"z"]).starts_with("$1\r\n"));
11939        // A positive count draws without replacement, so a count over the size
11940        // answers the whole set and never a member twice.
11941        let all = f.run(&[b"ZRANDMEMBER", b"z", b"10"]);
11942        assert!(all.starts_with("*3\r\n"), "{all}");
11943        for m in ["a", "b", "c"] {
11944            assert!(all.contains(m), "{all}");
11945        }
11946        // A negative one draws with replacement and answers exactly as many as
11947        // it was asked for, whatever the size of the set.
11948        assert!(
11949            f.run(&[b"ZRANDMEMBER", b"z", b"-5"]).starts_with("*5\r\n"),
11950            "five draws with replacement"
11951        );
11952        assert!(
11953            f.run(&[b"ZRANDMEMBER", b"z", b"2", b"WITHSCORES"])
11954                .starts_with("*4\r\n"),
11955            "two pairs, flat on RESP2"
11956        );
11957        f.out = Out::new(Proto::Resp3);
11958        let got = f.run(&[b"ZRANDMEMBER", b"z", b"2", b"WITHSCORES"]);
11959        assert!(got.starts_with("*2\r\n*2\r\n"), "{got}");
11960        assert_eq!(f.run(&[b"ZRANDMEMBER", b"nokey"]), "_\r\n");
11961        f.out = Out::new(Proto::Resp2);
11962        assert_eq!(
11963            f.run(&[b"ZRANDMEMBER", b"z", b"2", b"junk"]),
11964            "-ERR syntax error\r\n"
11965        );
11966        assert_eq!(
11967            f.run(&[b"ZRANDMEMBER", b"z", b"x"]),
11968            "-ERR value is not an integer or out of range\r\n"
11969        );
11970    }
11971
11972    /// `ZSCAN`, and the one sorted set reply where a score is not a double.
11973    #[test]
11974    fn a_sorted_set_scan_answers_pairs_of_strings_on_both_protocols() {
11975        let mut f = Fixture::new();
11976        f.run(&[b"ZADD", b"z", b"1", b"a", b"2", b"b", b"3", b"c"]);
11977        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";
11978        assert_eq!(f.run(&[b"ZSCAN", b"z", b"0"]), all);
11979        assert_eq!(f.run(&[b"ZSCAN", b"z", b"0", b"COUNT", b"10"]), all);
11980        assert_eq!(
11981            f.run(&[b"ZSCAN", b"z", b"0", b"MATCH", b"a*"]),
11982            "*2\r\n$1\r\n0\r\n*2\r\n$1\r\na\r\n$1\r\n1\r\n"
11983        );
11984        assert_eq!(
11985            f.run(&[b"ZSCAN", b"nokey", b"0"]),
11986            "*2\r\n$1\r\n0\r\n*0\r\n"
11987        );
11988        // A score stays a bulk string on RESP3, which is the one place the two
11989        // protocols agree about a score and everywhere else they do not.
11990        f.out = Out::new(Proto::Resp3);
11991        assert_eq!(f.run(&[b"ZSCAN", b"z", b"0"]), all);
11992        f.out = Out::new(Proto::Resp2);
11993        assert_eq!(
11994            f.run(&[b"ZSCAN", b"z", b"0", b"NOVALUES"]),
11995            "-ERR NOVALUES option can only be used in HSCAN\r\n"
11996        );
11997        assert_eq!(f.run(&[b"ZSCAN", b"z", b"-1"]), "-ERR invalid cursor\r\n");
11998        assert_eq!(
11999            f.run(&[b"ZSCAN", b"z", b"0", b"COUNT", b"0"]),
12000            "-ERR syntax error\r\n"
12001        );
12002    }
12003
12004    /// The count is what decides the shape, and its value is not.
12005    #[test]
12006    fn a_sorted_set_pop_changes_shape_when_it_is_given_a_count() {
12007        let mut f = Fixture::new();
12008        f.run(&[b"ZADD", b"z", b"1", b"a", b"2", b"b", b"3", b"c"]);
12009        // No count, so one flat pair, and the score is a bulk string on RESP2.
12010        assert_eq!(f.run(&[b"ZPOPMIN", b"z"]), "*2\r\n$1\r\na\r\n$1\r\n1\r\n");
12011        assert_eq!(f.run(&[b"ZPOPMAX", b"z"]), "*2\r\n$1\r\nc\r\n$1\r\n3\r\n");
12012        f.run(&[b"ZADD", b"z", b"1", b"a", b"3", b"c"]);
12013        // A count, so pairs, and on RESP2 they are flattened into one run.
12014        assert_eq!(
12015            f.run(&[b"ZPOPMIN", b"z", b"2"]),
12016            "*4\r\n$1\r\na\r\n$1\r\n1\r\n$1\r\nb\r\n$1\r\n2\r\n"
12017        );
12018        // An empty array rather than a null, which is where a sorted set pop and
12019        // a list pop part company, and the same answer a count of zero gives.
12020        assert_eq!(f.run(&[b"ZPOPMIN", b"nokey"]), "*0\r\n");
12021        assert_eq!(f.run(&[b"ZPOPMIN", b"nokey", b"2"]), "*0\r\n");
12022        assert_eq!(f.run(&[b"ZPOPMIN", b"z", b"0"]), "*0\r\n");
12023        // The last member takes the key with it.
12024        assert_eq!(
12025            f.run(&[b"ZPOPMIN", b"z", b"9"]),
12026            "*2\r\n$1\r\nc\r\n$1\r\n3\r\n"
12027        );
12028        assert_eq!(f.run(&[b"EXISTS", b"z"]), ":0\r\n");
12029
12030        f.run(&[b"ZADD", b"z", b"1", b"a", b"2", b"b"]);
12031        f.out = Out::new(Proto::Resp3);
12032        assert_eq!(f.run(&[b"ZPOPMIN", b"z"]), "*2\r\n$1\r\na\r\n,1\r\n");
12033        assert_eq!(
12034            f.run(&[b"ZPOPMIN", b"z", b"1"]),
12035            "*1\r\n*2\r\n$1\r\nb\r\n,2\r\n"
12036        );
12037        f.out = Out::new(Proto::Resp2);
12038        // Both of these are the range error rather than the usual sentence about
12039        // integers, which is the odd answer and so the one worth copying.
12040        let bad = "-ERR value is out of range, must be positive\r\n";
12041        assert_eq!(f.run(&[b"ZPOPMIN", b"z", b"x"]), bad);
12042        assert_eq!(f.run(&[b"ZPOPMIN", b"z", b"-1"]), bad);
12043        assert_eq!(
12044            f.run(&[b"ZPOPMIN", b"z", b"1", b"2"]),
12045            "-ERR syntax error\r\n"
12046        );
12047    }
12048
12049    /// `ZMPOP`, which is `LMPOP` with scores and the same parse.
12050    #[test]
12051    fn a_multi_key_pop_names_the_key_that_answered_and_nests_its_pairs() {
12052        let mut f = Fixture::new();
12053        f.run(&[b"ZADD", b"z", b"1", b"a", b"2", b"b", b"3", b"c"]);
12054        assert_eq!(
12055            f.run(&[b"ZMPOP", b"2", b"nokey", b"z", b"MIN"]),
12056            "*2\r\n$1\r\nz\r\n*1\r\n*2\r\n$1\r\na\r\n$1\r\n1\r\n"
12057        );
12058        // Nested on RESP2 as well, because the key name is already in front of
12059        // the pairs and there is nothing left to flatten into.
12060        assert_eq!(
12061            f.run(&[b"ZMPOP", b"1", b"z", b"MAX", b"COUNT", b"2"]),
12062            "*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"
12063        );
12064        // A null array and not a null, the same as LMPOP.
12065        assert_eq!(f.run(&[b"ZMPOP", b"1", b"nokey", b"MIN"]), "*-1\r\n");
12066        f.out = Out::new(Proto::Resp3);
12067        assert_eq!(f.run(&[b"ZMPOP", b"1", b"nokey", b"MIN"]), "_\r\n");
12068        f.out = Out::new(Proto::Resp2);
12069        let numkeys = "-ERR numkeys should be greater than 0\r\n";
12070        for bad in [
12071            &[b"ZMPOP".as_slice(), b"0", b"z", b"MIN"][..],
12072            &[b"ZMPOP", b"-1", b"z", b"MIN"],
12073            &[b"ZMPOP", b"x", b"z", b"MIN"],
12074        ] {
12075            assert_eq!(f.run(bad), numkeys, "{:?}", bad[1]);
12076        }
12077        let count = "-ERR count should be greater than 0\r\n";
12078        for bad in [
12079            &[b"ZMPOP".as_slice(), b"1", b"z", b"MIN", b"COUNT", b"0"][..],
12080            &[b"ZMPOP", b"1", b"z", b"MIN", b"COUNT", b"-1"],
12081            &[b"ZMPOP", b"1", b"z", b"MIN", b"COUNT", b"x"],
12082        ] {
12083            assert_eq!(f.run(bad), count, "{:?}", bad[5]);
12084        }
12085        let syntax = "-ERR syntax error\r\n";
12086        for bad in [
12087            // Two keys named and one given, so the word that should have been
12088            // the direction is a key and there is no direction left.
12089            &[b"ZMPOP".as_slice(), b"2", b"z", b"MIN"][..],
12090            &[b"ZMPOP", b"1", b"z", b"SIDEWAYS"],
12091            &[b"ZMPOP", b"1", b"z", b"MIN", b"junk"],
12092            &[b"ZMPOP", b"1", b"z", b"MIN", b"COUNT", b"1", b"junk"],
12093        ] {
12094            assert_eq!(f.run(bad), syntax, "{bad:?}");
12095        }
12096    }
12097
12098    /// The three that wait, when there is something there and they do not have
12099    /// to. `BZPOPMIN` is the one reply in the group that is three flat elements.
12100    #[test]
12101    fn the_sorted_set_pops_that_wait_answer_like_the_ones_they_wrap() {
12102        let mut f = Fixture::new();
12103        f.run(&[b"ZADD", b"z", b"1", b"a", b"2", b"b", b"3", b"c"]);
12104        assert_eq!(
12105            f.flow(&[b"BZPOPMIN", b"nokey", b"z", b"0"]),
12106            (
12107                Flow::Continue,
12108                "*3\r\n$1\r\nz\r\n$1\r\na\r\n$1\r\n1\r\n".to_owned()
12109            )
12110        );
12111        assert_eq!(
12112            f.run(&[b"BZPOPMAX", b"z", b"0"]),
12113            "*3\r\n$1\r\nz\r\n$1\r\nc\r\n$1\r\n3\r\n"
12114        );
12115        f.run(&[b"ZADD", b"z", b"1", b"a", b"3", b"c"]);
12116        assert_eq!(
12117            f.run(&[
12118                b"BZMPOP", b"0", b"2", b"nokey", b"z", b"MIN", b"COUNT", b"2"
12119            ]),
12120            "*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"
12121        );
12122        f.out = Out::new(Proto::Resp3);
12123        assert_eq!(
12124            f.run(&[b"BZPOPMIN", b"z", b"0"]),
12125            "*3\r\n$1\r\nz\r\n$1\r\nc\r\n,3\r\n"
12126        );
12127        f.out = Out::new(Proto::Resp2);
12128        // Nothing to take, so the client is parked and nothing was written.
12129        assert_eq!(
12130            f.flow(&[b"BZPOPMIN", b"z", b"0"]),
12131            (Flow::Block, String::new())
12132        );
12133        assert_eq!(
12134            f.flow(&[b"BZMPOP", b"0", b"1", b"z", b"MIN"]),
12135            (Flow::Block, String::new())
12136        );
12137        // The timeout is read before the key count, so this complains about the
12138        // timeout and not about the count.
12139        assert_eq!(
12140            f.run(&[b"BZMPOP", b"abc", b"0", b"z", b"MIN"]),
12141            "-ERR timeout is not a float or out of range\r\n"
12142        );
12143        assert_eq!(
12144            f.run(&[b"BZMPOP", b"0", b"0", b"z", b"MIN"]),
12145            "-ERR numkeys should be greater than 0\r\n"
12146        );
12147        assert_eq!(
12148            f.run(&[b"BZPOPMIN", b"z", b"-1"]),
12149            "-ERR timeout is negative\r\n"
12150        );
12151    }
12152
12153    /// A parked sorted set client is served by whatever puts a member under one
12154    /// of its keys, and is not served by something of another type landing
12155    /// there.
12156    #[test]
12157    fn a_parked_sorted_set_client_waits_for_a_member_and_not_for_a_key() {
12158        let mut f = Fixture::new();
12159        assert_eq!(f.flow(&[b"BZPOPMIN", b"z", b"0"]).0, Flow::Block);
12160        assert_eq!(f.server.parked(), 1);
12161        // A string under the key is not what it asked for, so it stays parked
12162        // rather than being handed a WRONGTYPE on a command that was accepted.
12163        f.run(&[b"SET", b"z", b"v"]);
12164        let mut out = Out::new(Proto::Resp2);
12165        assert!(!f.server.serve_waiter(7, 0, &mut out));
12166        assert!(out.as_slice().is_empty());
12167        f.run(&[b"DEL", b"z"]);
12168        f.run(&[b"ZADD", b"z", b"5", b"m"]);
12169        assert!(f.server.serve_waiter(7, 0, &mut out));
12170        assert_eq!(
12171            core::str::from_utf8(out.as_slice()).expect("ascii"),
12172            "*3\r\n$1\r\nz\r\n$1\r\nm\r\n$1\r\n5\r\n"
12173        );
12174        // And the member is gone, which is what makes a queue of workers on a
12175        // sorted set work at all.
12176        assert_eq!(f.run(&[b"EXISTS", b"z"]), ":0\r\n");
12177    }
12178
12179    #[test]
12180    fn every_sorted_set_command_says_wrongtype_and_writes_nothing() {
12181        let mut f = Fixture::new();
12182        f.run(&[b"SET", b"s", b"v"]);
12183        let wrong = "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n";
12184        for cmd in [
12185            &[b"ZADD".as_slice(), b"s", b"1", b"a"][..],
12186            &[b"ZINCRBY", b"s", b"1", b"a"],
12187            &[b"ZCARD", b"s"],
12188            &[b"ZSCORE", b"s", b"a"],
12189            &[b"ZMSCORE", b"s", b"a"],
12190            &[b"ZREM", b"s", b"a"],
12191            &[b"ZRANK", b"s", b"a"],
12192            &[b"ZREVRANK", b"s", b"a"],
12193            &[b"ZCOUNT", b"s", b"1", b"2"],
12194            &[b"ZLEXCOUNT", b"s", b"-", b"+"],
12195            &[b"ZRANGE", b"s", b"0", b"-1"],
12196            &[b"ZREVRANGE", b"s", b"0", b"-1"],
12197            &[b"ZRANGEBYSCORE", b"s", b"1", b"2"],
12198            &[b"ZREVRANGEBYSCORE", b"s", b"2", b"1"],
12199            &[b"ZRANGEBYLEX", b"s", b"-", b"+"],
12200            &[b"ZREVRANGEBYLEX", b"s", b"+", b"-"],
12201            &[b"ZRANGESTORE", b"d", b"s", b"0", b"-1"],
12202            &[b"ZREMRANGEBYRANK", b"s", b"0", b"-1"],
12203            &[b"ZREMRANGEBYSCORE", b"s", b"1", b"2"],
12204            &[b"ZREMRANGEBYLEX", b"s", b"-", b"+"],
12205            &[b"ZUNION", b"1", b"s"],
12206            &[b"ZINTER", b"1", b"s"],
12207            &[b"ZDIFF", b"1", b"s"],
12208            &[b"ZUNIONSTORE", b"d", b"1", b"s"],
12209            &[b"ZINTERSTORE", b"d", b"1", b"s"],
12210            &[b"ZDIFFSTORE", b"d", b"1", b"s"],
12211            &[b"ZINTERCARD", b"1", b"s"],
12212            &[b"ZRANDMEMBER", b"s"],
12213            &[b"ZSCAN", b"s", b"0"],
12214            &[b"ZPOPMIN", b"s"],
12215            &[b"ZPOPMAX", b"s", b"2"],
12216            &[b"ZMPOP", b"1", b"s", b"MIN"],
12217            &[b"BZPOPMIN", b"s", b"0"],
12218            &[b"BZPOPMAX", b"s", b"0"],
12219            &[b"BZMPOP", b"0", b"1", b"s", b"MIN"],
12220        ] {
12221            assert_eq!(f.run(cmd), wrong, "{:?}", cmd[0]);
12222        }
12223        assert_eq!(f.run(&[b"GET", b"s"]), "$1\r\nv\r\n");
12224    }
12225
12226    /// The same churn the set, the string and the list get, because a sorted
12227    /// set that leaks a tree node per add looks exactly like one that does not
12228    /// until it has run for an afternoon.
12229    /// Not under Miri, for the reason on `churning_sets_does_not_grow_the_server`.
12230    #[cfg_attr(miri, ignore = "the volume is the claim")]
12231    #[test]
12232    fn churning_sorted_sets_does_not_grow_the_server() {
12233        let mut f = Fixture::new();
12234        let members: Vec<Vec<u8>> = (0..200).map(|i| format!("m{i}").into_bytes()).collect();
12235        let scores: Vec<Vec<u8>> = (0..200).map(|i| format!("{i}").into_bytes()).collect();
12236        let mut args: Vec<&[u8]> = vec![b"ZADD", b"z"];
12237        for i in 0..200 {
12238            args.push(&scores[i]);
12239            args.push(&members[i]);
12240        }
12241
12242        f.run(&args);
12243        f.run(&[b"DEL", b"z"]);
12244        f.server.compact_step();
12245        let after_first = f.server.memory_bytes();
12246
12247        for _ in 0..200 {
12248            f.run(&args);
12249            f.run(&[b"DEL", b"z"]);
12250            f.server.compact_step();
12251        }
12252        assert_eq!(f.run(&[b"DBSIZE"]), ":0\r\n");
12253        assert!(
12254            f.server.memory_bytes() <= after_first * 2,
12255            "held {} after two hundred passes against {after_first} after one",
12256            f.server.memory_bytes()
12257        );
12258    }
12259
12260    // ------------------------------------------------------------------- geo
12261
12262    /// The three places every Redis geo example uses, and one more.
12263    ///
12264    /// Every reply this section asserts on came off a running 8.10.1 with these
12265    /// three loaded, byte for byte, including the number of digits in a
12266    /// coordinate and the four places on a distance.
12267    fn sicily(f: &mut Fixture) {
12268        f.run(&[
12269            b"GEOADD",
12270            b"Sicily",
12271            b"13.361389",
12272            b"38.115556",
12273            b"Palermo",
12274            b"15.087269",
12275            b"37.502669",
12276            b"Catania",
12277        ]);
12278        f.run(&[
12279            b"GEOADD",
12280            b"Sicily",
12281            b"13.583333",
12282            b"37.316667",
12283            b"Agrigento",
12284        ]);
12285    }
12286
12287    #[test]
12288    fn places_go_in_as_scores_and_come_back_as_positions() {
12289        let mut f = Fixture::new();
12290        assert_eq!(
12291            f.run(&[
12292                b"GEOADD",
12293                b"Sicily",
12294                b"13.361389",
12295                b"38.115556",
12296                b"Palermo",
12297                b"15.087269",
12298                b"37.502669",
12299                b"Catania"
12300            ]),
12301            ":2\r\n"
12302        );
12303        // A geo key is a sorted set and says so, which is not an implementation
12304        // detail either: a client removes a place with ZREM and counts them
12305        // with ZCARD, and the score is the number a real server stores.
12306        assert_eq!(f.run(&[b"TYPE", b"Sicily"]), "+zset\r\n");
12307        assert_eq!(
12308            f.run(&[b"ZSCORE", b"Sicily", b"Palermo"]),
12309            "$16\r\n3479099956230698\r\n"
12310        );
12311        assert_eq!(
12312            f.run(&[b"GEOPOS", b"Sicily", b"Palermo", b"NonExisting"]),
12313            "*2\r\n*2\r\n$18\r\n13.361389338970184\r\n$16\r\n38.1155563954963\r\n*-1\r\n"
12314        );
12315        assert_eq!(
12316            f.run(&[
12317                b"GEOHASH",
12318                b"Sicily",
12319                b"Palermo",
12320                b"Catania",
12321                b"NonExisting"
12322            ]),
12323            "*3\r\n$11\r\nsqc8b49rny0\r\n$11\r\nsqdtr74hyu0\r\n$-1\r\n"
12324        );
12325        // A key that is not there is an empty one, and the two nulls are not
12326        // the same null: GEOPOS answers the array one and GEOHASH the string
12327        // one, which a RESP2 client can tell apart.
12328        assert_eq!(f.run(&[b"GEOPOS", b"nokey", b"a"]), "*1\r\n*-1\r\n");
12329        assert_eq!(f.run(&[b"GEOHASH", b"nokey", b"a"]), "*1\r\n$-1\r\n");
12330    }
12331
12332    #[test]
12333    fn a_distance_comes_back_with_four_places_in_whatever_unit_was_asked_for() {
12334        let mut f = Fixture::new();
12335        sicily(&mut f);
12336        assert_eq!(
12337            f.run(&[b"GEODIST", b"Sicily", b"Palermo", b"Catania"]),
12338            "$11\r\n166274.1516\r\n"
12339        );
12340        assert_eq!(
12341            f.run(&[b"GEODIST", b"Sicily", b"Palermo", b"Catania", b"km"]),
12342            "$8\r\n166.2742\r\n"
12343        );
12344        assert_eq!(
12345            f.run(&[b"GEODIST", b"Sicily", b"Palermo", b"Catania", b"mi"]),
12346            "$8\r\n103.3182\r\n"
12347        );
12348        // A member that is not there and a key that is not there are the same
12349        // nil, and the unit is read before the key is looked up, so a bad unit
12350        // on a missing key is still an error.
12351        assert_eq!(
12352            f.run(&[b"GEODIST", b"Sicily", b"Palermo", b"Foo"]),
12353            "$-1\r\n"
12354        );
12355        assert_eq!(f.run(&[b"GEODIST", b"nokey", b"a", b"b"]), "$-1\r\n");
12356        assert_eq!(
12357            f.run(&[b"GEODIST", b"nokey", b"a", b"b", b"parsecs"]),
12358            "-ERR unsupported unit provided. please use M, KM, FT, MI\r\n"
12359        );
12360        assert_eq!(
12361            f.run(&[b"GEODIST", b"Sicily", b"a", b"b", b"km", b"extra"]),
12362            "-ERR syntax error\r\n"
12363        );
12364    }
12365
12366    #[test]
12367    fn a_search_finds_what_is_inside_it_nearest_first() {
12368        let mut f = Fixture::new();
12369        sicily(&mut f);
12370        let all = "*3\r\n$7\r\nCatania\r\n$9\r\nAgrigento\r\n$7\r\nPalermo\r\n";
12371        assert_eq!(
12372            f.run(&[
12373                b"GEOSEARCH",
12374                b"Sicily",
12375                b"FROMLONLAT",
12376                b"15",
12377                b"37",
12378                b"BYRADIUS",
12379                b"200",
12380                b"km",
12381                b"ASC"
12382            ]),
12383            all
12384        );
12385        // The older spelling of the same search, which is the same nine boxes
12386        // and the same order.
12387        assert_eq!(
12388            f.run(&[b"GEORADIUS", b"Sicily", b"15", b"37", b"200", b"km", b"ASC"]),
12389            all
12390        );
12391        assert_eq!(
12392            f.run(&[
12393                b"GEORADIUS_RO",
12394                b"Sicily",
12395                b"15",
12396                b"37",
12397                b"200",
12398                b"km",
12399                b"ASC"
12400            ]),
12401            all
12402        );
12403        // A count with no ordering means the nearest ones, so DESC has to be
12404        // asked for to get the far end.
12405        assert_eq!(
12406            f.run(&[
12407                b"GEORADIUS",
12408                b"Sicily",
12409                b"15",
12410                b"37",
12411                b"200",
12412                b"km",
12413                b"DESC",
12414                b"COUNT",
12415                b"1"
12416            ]),
12417            "*1\r\n$7\r\nPalermo\r\n"
12418        );
12419        assert_eq!(
12420            f.run(&[
12421                b"GEORADIUS",
12422                b"Sicily",
12423                b"15",
12424                b"37",
12425                b"200",
12426                b"km",
12427                b"COUNT",
12428                b"1"
12429            ]),
12430            "*1\r\n$7\r\nCatania\r\n"
12431        );
12432        // Nothing inside a kilometre of that point, and nothing in a key that
12433        // is not there, and both are the empty array rather than an error.
12434        let empty = "*0\r\n";
12435        assert_eq!(
12436            f.run(&[
12437                b"GEOSEARCH",
12438                b"Sicily",
12439                b"FROMLONLAT",
12440                b"15",
12441                b"37",
12442                b"BYRADIUS",
12443                b"1",
12444                b"km"
12445            ]),
12446            empty
12447        );
12448        assert_eq!(
12449            f.run(&[
12450                b"GEOSEARCH",
12451                b"nokey",
12452                b"FROMLONLAT",
12453                b"15",
12454                b"37",
12455                b"BYRADIUS",
12456                b"1",
12457                b"km"
12458            ]),
12459            empty
12460        );
12461        assert_eq!(
12462            f.run(&[b"GEORADIUSBYMEMBER", b"nokey", b"m", b"1", b"km"]),
12463            empty
12464        );
12465    }
12466
12467    #[test]
12468    fn a_search_centred_on_a_member_starts_from_where_that_member_is() {
12469        let mut f = Fixture::new();
12470        sicily(&mut f);
12471        assert_eq!(
12472            f.run(&[b"GEORADIUSBYMEMBER", b"Sicily", b"Agrigento", b"100", b"km"]),
12473            "*2\r\n$9\r\nAgrigento\r\n$7\r\nPalermo\r\n"
12474        );
12475        // The member itself is nothing away from itself, which is where the
12476        // fixed point writer's zero shows up on the wire.
12477        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";
12478        assert_eq!(
12479            f.run(&[
12480                b"GEORADIUSBYMEMBER_RO",
12481                b"Sicily",
12482                b"Agrigento",
12483                b"100",
12484                b"km",
12485                b"WITHDIST"
12486            ]),
12487            with_dist
12488        );
12489        assert_eq!(
12490            f.run(&[
12491                b"GEOSEARCH",
12492                b"Sicily",
12493                b"FROMMEMBER",
12494                b"Agrigento",
12495                b"BYRADIUS",
12496                b"100",
12497                b"km",
12498                b"ASC",
12499                b"WITHDIST"
12500            ]),
12501            with_dist
12502        );
12503        assert_eq!(
12504            f.run(&[b"GEORADIUSBYMEMBER", b"Sicily", b"Nowhere", b"100", b"km"]),
12505            "-ERR could not decode requested zset member\r\n"
12506        );
12507    }
12508
12509    #[test]
12510    fn a_box_search_reports_the_distance_the_hash_and_the_coordinates() {
12511        let mut f = Fixture::new();
12512        sicily(&mut f);
12513        // Three options asked for, so each result is a four element array of
12514        // the member, the distance, the hash and a pair. The order of the three
12515        // is Redis's and not the order they were written in the command.
12516        assert_eq!(
12517            f.run(&[
12518                b"GEOSEARCH",
12519                b"Sicily",
12520                b"FROMLONLAT",
12521                b"15",
12522                b"37",
12523                b"BYBOX",
12524                b"400",
12525                b"400",
12526                b"km",
12527                b"ASC",
12528                b"WITHCOORD",
12529                b"WITHDIST",
12530                b"WITHHASH"
12531            ]),
12532            "*3\r\n*4\r\n$7\r\nCatania\r\n$7\r\n56.4413\r\n:3479447370796909\r\n*2\r\n\
12533             $18\r\n15.087267458438873\r\n$17\r\n37.50266842333162\r\n\
12534             *4\r\n$9\r\nAgrigento\r\n$8\r\n130.4235\r\n:3479030013248308\r\n*2\r\n\
12535             $18\r\n13.583331406116486\r\n$18\r\n37.316668049938166\r\n\
12536             *4\r\n$7\r\nPalermo\r\n$8\r\n190.4424\r\n:3479099956230698\r\n*2\r\n\
12537             $18\r\n13.361389338970184\r\n$16\r\n38.1155563954963\r\n"
12538        );
12539    }
12540
12541    #[test]
12542    fn a_store_writes_the_hashes_and_a_storedist_writes_the_distances() {
12543        let mut f = Fixture::new();
12544        sicily(&mut f);
12545        let hashes = "*6\r\n$9\r\nAgrigento\r\n$16\r\n3479030013248308\r\n\
12546                      $7\r\nPalermo\r\n$16\r\n3479099956230698\r\n\
12547                      $7\r\nCatania\r\n$16\r\n3479447370796909\r\n";
12548        assert_eq!(
12549            f.run(&[
12550                b"GEOSEARCHSTORE",
12551                b"dst",
12552                b"Sicily",
12553                b"FROMLONLAT",
12554                b"15",
12555                b"37",
12556                b"BYRADIUS",
12557                b"200",
12558                b"km",
12559                b"ASC"
12560            ]),
12561            ":3\r\n"
12562        );
12563        assert_eq!(
12564            f.run(&[b"ZRANGE", b"dst", b"0", b"-1", b"WITHSCORES"]),
12565            hashes
12566        );
12567        // The same again through the older spelling, which stores the same
12568        // scores, so a key written by either is a geo key.
12569        assert_eq!(
12570            f.run(&[
12571                b"GEORADIUS",
12572                b"Sicily",
12573                b"15",
12574                b"37",
12575                b"200",
12576                b"km",
12577                b"STORE",
12578                b"dst3"
12579            ]),
12580            ":3\r\n"
12581        );
12582        assert_eq!(
12583            f.run(&[b"ZRANGE", b"dst3", b"0", b"-1", b"WITHSCORES"]),
12584            hashes
12585        );
12586        // STOREDIST stores the distance in the search unit instead, and those
12587        // are full doubles rather than the four places WITHDIST writes. The
12588        // numbers on the right are what 8.10.1 stored for this search, and they
12589        // are compared with a tolerance rather than byte for byte because the
12590        // last bit of a haversine is the platform's sin, cos and asin: this
12591        // machine and that one disagree in the sixteenth digit, and so do two
12592        // Redis builds. Everything a client actually reads back is four places
12593        // and is asserted exactly above.
12594        assert_eq!(
12595            f.run(&[
12596                b"GEOSEARCHSTORE",
12597                b"dst2",
12598                b"Sicily",
12599                b"FROMLONLAT",
12600                b"15",
12601                b"37",
12602                b"BYRADIUS",
12603                b"200",
12604                b"km",
12605                b"ASC",
12606                b"STOREDIST"
12607            ]),
12608            ":3\r\n"
12609        );
12610        for (member, want) in [
12611            ("Catania", 56.441_257_870_158_19),
12612            ("Agrigento", 130.423_487_067_147_14),
12613            ("Palermo", 190.442_429_847_757_92),
12614        ] {
12615            let reply = f.run(&[b"ZSCORE", b"dst2", member.as_bytes()]);
12616            let got: f64 = reply
12617                .trim_start_matches(|c: char| c != '\n')
12618                .trim()
12619                .parse()
12620                .unwrap_or_else(|_| panic!("{member} scored {reply:?}"));
12621            assert!(
12622                (got - want).abs() < 1e-9,
12623                "{member} scored {got} not {want}"
12624            );
12625        }
12626        // The order they went in is the order the scores put them in, which is
12627        // the point of storing the distance rather than the hash.
12628        assert_eq!(
12629            f.run(&[b"ZRANGE", b"dst2", b"0", b"-1"]),
12630            "*3\r\n$7\r\nCatania\r\n$9\r\nAgrigento\r\n$7\r\nPalermo\r\n"
12631        );
12632        // A search that finds nothing takes the destination with it rather than
12633        // leaving what was there, and a source key that is not there is a
12634        // search that finds nothing.
12635        assert_eq!(
12636            f.run(&[
12637                b"GEOSEARCHSTORE",
12638                b"dst",
12639                b"nokey",
12640                b"FROMLONLAT",
12641                b"15",
12642                b"37",
12643                b"BYRADIUS",
12644                b"200",
12645                b"km"
12646            ]),
12647            ":0\r\n"
12648        );
12649        assert_eq!(f.run(&[b"EXISTS", b"dst"]), ":0\r\n");
12650    }
12651
12652    #[test]
12653    fn the_gates_on_geoadd_are_the_ones_zadd_has() {
12654        let mut f = Fixture::new();
12655        sicily(&mut f);
12656        // XX on a member that is already where it is changes nothing, and NX on
12657        // one that is there refuses to move it.
12658        assert_eq!(
12659            f.run(&[
12660                b"GEOADD",
12661                b"Sicily",
12662                b"XX",
12663                b"CH",
12664                b"13.361389",
12665                b"38.115556",
12666                b"Palermo"
12667            ]),
12668            ":0\r\n"
12669        );
12670        assert_eq!(
12671            f.run(&[
12672                b"GEOADD",
12673                b"Sicily",
12674                b"NX",
12675                b"13.361389",
12676                b"38.9",
12677                b"Palermo"
12678            ]),
12679            ":0\r\n"
12680        );
12681        assert_eq!(
12682            f.run(&[
12683                b"GEOADD",
12684                b"Sicily",
12685                b"CH",
12686                b"13.361389",
12687                b"38.9",
12688                b"Palermo"
12689            ]),
12690            ":1\r\n"
12691        );
12692        // Out of range, and nothing is stored: the whole call is refused rather
12693        // than the good pairs going in and the bad one stopping it.
12694        assert_eq!(
12695            f.run(&[
12696                b"GEOADD",
12697                b"new",
12698                b"13.361389",
12699                b"38.115556",
12700                b"here",
12701                b"181",
12702                b"38",
12703                b"there"
12704            ]),
12705            "-ERR invalid longitude,latitude pair 181.000000,38.000000\r\n"
12706        );
12707        assert_eq!(f.run(&[b"EXISTS", b"new"]), ":0\r\n");
12708        assert_eq!(
12709            f.run(&[b"GEOADD", b"new", b"x", b"38", b"here"]),
12710            "-ERR value is not a valid float\r\n"
12711        );
12712        // The count of triples is checked before the two gates are, and a call
12713        // with no triples at all reaches the same sentence.
12714        assert_eq!(
12715            f.run(&[b"GEOADD", b"new", b"13", b"38", b"here", b"and"]),
12716            "-ERR syntax error\r\n"
12717        );
12718        assert_eq!(
12719            f.run(&[b"GEOADD", b"new", b"NX", b"XX", b"CH"]),
12720            "-ERR syntax error\r\n"
12721        );
12722        assert_eq!(
12723            f.run(&[b"GEOADD", b"new", b"CH", b"CH", b"CH", b"CH"]),
12724            "-ERR syntax error\r\n"
12725        );
12726        assert_eq!(
12727            f.run(&[b"GEOADD", b"new", b"NX", b"CH"]),
12728            "-ERR wrong number of arguments for 'geoadd' command\r\n"
12729        );
12730    }
12731
12732    /// The sentences a search answers, which are its contract as much as the
12733    /// results are.
12734    #[test]
12735    fn every_way_a_search_can_be_written_wrong_has_its_own_sentence() {
12736        let mut f = Fixture::new();
12737        sicily(&mut f);
12738        let cases: &[(&[&[u8]], &str)] = &[
12739            (
12740                &[b"GEORADIUS", b"Sicily", b"15", b"37", b"x", b"km"],
12741                "-ERR need numeric radius\r\n",
12742            ),
12743            (
12744                &[b"GEORADIUS", b"Sicily", b"15", b"37", b"-1", b"km"],
12745                "-ERR radius cannot be negative\r\n",
12746            ),
12747            (
12748                &[b"GEORADIUS", b"Sicily", b"15", b"37", b"1", b"parsecs"],
12749                "-ERR unsupported unit provided. please use M, KM, FT, MI\r\n",
12750            ),
12751            (
12752                &[b"GEORADIUS", b"Sicily", b"181", b"37", b"1", b"km"],
12753                "-ERR invalid longitude,latitude pair 181.000000,37.000000\r\n",
12754            ),
12755            (
12756                &[
12757                    b"GEOSEARCH",
12758                    b"Sicily",
12759                    b"FROMLONLAT",
12760                    b"15",
12761                    b"37",
12762                    b"BYBOX",
12763                    b"x",
12764                    b"1",
12765                    b"km",
12766                ],
12767                "-ERR need numeric width\r\n",
12768            ),
12769            (
12770                &[
12771                    b"GEOSEARCH",
12772                    b"Sicily",
12773                    b"FROMLONLAT",
12774                    b"15",
12775                    b"37",
12776                    b"BYBOX",
12777                    b"1",
12778                    b"y",
12779                    b"km",
12780                ],
12781                "-ERR need numeric height\r\n",
12782            ),
12783            (
12784                &[
12785                    b"GEOSEARCH",
12786                    b"Sicily",
12787                    b"FROMLONLAT",
12788                    b"15",
12789                    b"37",
12790                    b"BYBOX",
12791                    b"-1",
12792                    b"1",
12793                    b"km",
12794                ],
12795                "-ERR height or width cannot be negative\r\n",
12796            ),
12797            (
12798                &[
12799                    b"GEOSEARCH",
12800                    b"Sicily",
12801                    b"FROMLONLAT",
12802                    b"15",
12803                    b"37",
12804                    b"BYRADIUS",
12805                    b"1",
12806                    b"km",
12807                    b"ANY",
12808                ],
12809                "-ERR the ANY argument requires COUNT argument\r\n",
12810            ),
12811            (
12812                &[
12813                    b"GEOSEARCH",
12814                    b"Sicily",
12815                    b"FROMLONLAT",
12816                    b"15",
12817                    b"37",
12818                    b"BYRADIUS",
12819                    b"1",
12820                    b"km",
12821                    b"COUNT",
12822                    b"0",
12823                ],
12824                "-ERR COUNT must be > 0\r\n",
12825            ),
12826            (
12827                &[
12828                    b"GEOSEARCH",
12829                    b"Sicily",
12830                    b"BYRADIUS",
12831                    b"1",
12832                    b"km",
12833                    b"BYBOX",
12834                    b"1",
12835                    b"1",
12836                    b"km",
12837                ],
12838                "-ERR syntax error\r\n",
12839            ),
12840            (
12841                &[
12842                    b"GEOSEARCH",
12843                    b"Sicily",
12844                    b"FROMMEMBER",
12845                    b"Palermo",
12846                    b"FROMLONLAT",
12847                    b"1",
12848                    b"2",
12849                    b"BYRADIUS",
12850                    b"1",
12851                    b"km",
12852                ],
12853                "-ERR syntax error\r\n",
12854            ),
12855            // The two options a GEOSEARCH cannot leave out, each with its own
12856            // sentence, and the command quoted the way the client spelled it.
12857            (
12858                &[
12859                    b"geosearch",
12860                    b"Sicily",
12861                    b"BYRADIUS",
12862                    b"1",
12863                    b"km",
12864                    b"ASC",
12865                    b"WITHDIST",
12866                ],
12867                "-ERR exactly one of FROMMEMBER or FROMLONLAT can be specified for geosearch\r\n",
12868            ),
12869            (
12870                &[
12871                    b"GEOSEARCH",
12872                    b"Sicily",
12873                    b"FROMLONLAT",
12874                    b"15",
12875                    b"37",
12876                    b"ASC",
12877                    b"WITHDIST",
12878                ],
12879                "-ERR exactly one of BYRADIUS and BYBOX can be specified for GEOSEARCH\r\n",
12880            ),
12881            // A store cannot also be asked for the distance, and the two
12882            // families name themselves differently in the same sentence.
12883            (
12884                &[
12885                    b"GEOSEARCHSTORE",
12886                    b"d",
12887                    b"Sicily",
12888                    b"FROMLONLAT",
12889                    b"15",
12890                    b"37",
12891                    b"BYRADIUS",
12892                    b"1",
12893                    b"km",
12894                    b"WITHCOORD",
12895                ],
12896                "-ERR GEOSEARCHSTORE is not compatible with WITHDIST, WITHHASH and WITHCOORD options\r\n",
12897            ),
12898            (
12899                &[
12900                    b"GEORADIUS",
12901                    b"Sicily",
12902                    b"15",
12903                    b"37",
12904                    b"1",
12905                    b"km",
12906                    b"WITHDIST",
12907                    b"STORE",
12908                    b"d",
12909                ],
12910                "-ERR STORE option in GEORADIUS is not compatible with WITHDIST, WITHHASH and WITHCOORD options\r\n",
12911            ),
12912            // The read only forms have no store at all, so the word is a stray
12913            // one, and GEOSEARCH's STOREDIST is only a GEOSEARCHSTORE option.
12914            (
12915                &[
12916                    b"GEORADIUS_RO",
12917                    b"Sicily",
12918                    b"15",
12919                    b"37",
12920                    b"1",
12921                    b"km",
12922                    b"STORE",
12923                    b"d",
12924                ],
12925                "-ERR syntax error\r\n",
12926            ),
12927            (
12928                &[
12929                    b"GEOSEARCH",
12930                    b"Sicily",
12931                    b"FROMLONLAT",
12932                    b"15",
12933                    b"37",
12934                    b"BYRADIUS",
12935                    b"1",
12936                    b"km",
12937                    b"STOREDIST",
12938                ],
12939                "-ERR syntax error\r\n",
12940            ),
12941        ];
12942        for (parts, want) in cases {
12943            assert_eq!(&f.run(parts), want, "{:?}", parts[0]);
12944        }
12945    }
12946
12947    /// A wrong type wins over a bad argument, because the key is looked up
12948    /// first, and every one of the ten says the same thing about it.
12949    #[test]
12950    fn every_geo_command_says_wrongtype() {
12951        let mut f = Fixture::new();
12952        f.run(&[b"SET", b"s", b"v"]);
12953        let wrong = "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n";
12954        let cases: &[&[&[u8]]] = &[
12955            &[b"GEOADD", b"s", b"13", b"38", b"m"],
12956            &[b"GEOPOS", b"s", b"m"],
12957            &[b"GEOHASH", b"s", b"m"],
12958            &[b"GEODIST", b"s", b"a", b"b"],
12959            &[
12960                b"GEOSEARCH",
12961                b"s",
12962                b"FROMLONLAT",
12963                b"15",
12964                b"37",
12965                b"BYRADIUS",
12966                b"1",
12967                b"km",
12968            ],
12969            &[
12970                b"GEOSEARCHSTORE",
12971                b"d",
12972                b"s",
12973                b"FROMLONLAT",
12974                b"15",
12975                b"37",
12976                b"BYRADIUS",
12977                b"1",
12978                b"km",
12979            ],
12980            &[b"GEORADIUS", b"s", b"15", b"37", b"1", b"km"],
12981            &[b"GEORADIUS_RO", b"s", b"15", b"37", b"1", b"km"],
12982            &[b"GEORADIUSBYMEMBER", b"s", b"m", b"1", b"km"],
12983            &[b"GEORADIUSBYMEMBER_RO", b"s", b"m", b"1", b"km"],
12984        ];
12985        for case in cases {
12986            assert_eq!(f.run(case), wrong, "{:?}", case[0]);
12987        }
12988        // And it wins over an argument that will not parse, which is the whole
12989        // reason the lookup comes first.
12990        assert_eq!(
12991            f.run(&[b"GEORADIUS", b"s", b"15", b"37", b"x", b"km"]),
12992            wrong
12993        );
12994    }
12995
12996    // ----------------------------------------------------------------- array
12997
12998    #[test]
12999    fn an_array_writes_at_any_index_and_reads_back_what_it_sent() {
13000        let mut f = Fixture::new();
13001        // Three consecutive positions from a high index, and the reply is how
13002        // many of them were empty before rather than how many were written.
13003        assert_eq!(
13004            f.run(&[b"ARSET", b"a", b"1000", b"x", b"y", b"z"]),
13005            ":3\r\n"
13006        );
13007        assert_eq!(f.run(&[b"ARSET", b"a", b"1000", b"X", b"Y"]), ":0\r\n");
13008        assert_eq!(f.run(&[b"ARGET", b"a", b"1000"]), "$1\r\nX\r\n");
13009        assert_eq!(f.run(&[b"ARGET", b"a", b"1002"]), "$1\r\nz\r\n");
13010        // A hole and a key that is not there are the same answer.
13011        assert_eq!(f.run(&[b"ARGET", b"a", b"999"]), "$-1\r\n");
13012        assert_eq!(f.run(&[b"ARGET", b"nope", b"0"]), "$-1\r\n");
13013        assert_eq!(
13014            f.run(&[b"ARMGET", b"a", b"1002", b"999", b"1000"]),
13015            "*3\r\n$1\r\nz\r\n$-1\r\n$1\r\nX\r\n"
13016        );
13017        // Scattered pairs in one command, last write wins within it.
13018        assert_eq!(f.run(&[b"ARMSET", b"a", b"5", b"p", b"5", b"q"]), ":1\r\n");
13019        assert_eq!(f.run(&[b"ARGET", b"a", b"5"]), "$1\r\nq\r\n");
13020    }
13021
13022    /// The two numbers an array reports are not the same number, and one of
13023    /// them does not fit a signed integer.
13024    #[test]
13025    fn the_length_is_the_high_water_mark_and_the_count_is_the_population() {
13026        let mut f = Fixture::new();
13027        assert_eq!(f.run(&[b"ARLEN", b"nope"]), ":0\r\n");
13028        assert_eq!(f.run(&[b"ARCOUNT", b"nope"]), ":0\r\n");
13029        f.run(&[b"ARMSET", b"a", b"0", b"x", b"9", b"y"]);
13030        assert_eq!(f.run(&[b"ARLEN", b"a"]), ":10\r\n");
13031        assert_eq!(f.run(&[b"ARCOUNT", b"a"]), ":2\r\n");
13032        // Deleting in the middle leaves the high water mark where it was.
13033        assert_eq!(f.run(&[b"ARDEL", b"a", b"0"]), ":1\r\n");
13034        assert_eq!(f.run(&[b"ARLEN", b"a"]), ":10\r\n");
13035        assert_eq!(f.run(&[b"ARCOUNT", b"a"]), ":1\r\n");
13036
13037        // The top of the space is addressable, and its length is a number with
13038        // bit sixty three set, so the reply has to be unsigned or it comes back
13039        // negative.
13040        f.run(&[b"ARSET", b"top", b"18446744073709551614", b"z"]);
13041        assert_eq!(f.run(&[b"ARLEN", b"top"]), ":18446744073709551615\r\n");
13042        assert_eq!(f.run(&[b"ARCOUNT", b"top"]), ":1\r\n");
13043        // And one past it does not exist, so a write that would reach it fails
13044        // before any of it lands.
13045        assert_eq!(
13046            f.run(&[b"ARSET", b"over", b"18446744073709551614", b"a", b"b"]),
13047            "-ERR array index overflow\r\n"
13048        );
13049        assert_eq!(f.run(&[b"EXISTS", b"over"]), ":0\r\n");
13050    }
13051
13052    /// One reply per position and not one per element, which is the whole
13053    /// reason the range is capped.
13054    #[test]
13055    fn a_range_read_answers_for_the_holes_too_and_is_capped_at_a_million() {
13056        let mut f = Fixture::new();
13057        f.run(&[b"ARSET", b"a", b"1", b"x"]);
13058        assert_eq!(
13059            f.run(&[b"ARGETRANGE", b"a", b"0", b"3"]),
13060            "*4\r\n$-1\r\n$1\r\nx\r\n$-1\r\n$-1\r\n"
13061        );
13062        // The two ends may come in either order, and the answer is reversed
13063        // rather than empty.
13064        assert_eq!(
13065            f.run(&[b"ARGETRANGE", b"a", b"3", b"0"]),
13066            "*4\r\n$-1\r\n$-1\r\n$1\r\nx\r\n$-1\r\n"
13067        );
13068        // A key that is not there reads like an array of nothing but holes.
13069        assert_eq!(
13070            f.run(&[b"ARGETRANGE", b"nope", b"0", b"1"]),
13071            "*2\r\n$-1\r\n$-1\r\n"
13072        );
13073        // A range wider than a million positions is refused and not trimmed,
13074        // because against a missing key it is a request for as many nulls as
13075        // the range is wide.
13076        assert_eq!(
13077            f.run(&[b"ARGETRANGE", b"nope", b"0", b"18446744073709551614"]),
13078            "-ERR range exceeds maximum of 1000000 items\r\n"
13079        );
13080    }
13081
13082    /// Every index in the argument list is read before the key is touched, so
13083    /// a bad one at the end leaves nothing half written.
13084    #[test]
13085    fn a_bad_index_late_in_the_line_writes_none_of_the_earlier_ones() {
13086        let mut f = Fixture::new();
13087        assert_eq!(
13088            f.run(&[b"ARMSET", b"a", b"0", b"x", b"-1", b"y"]),
13089            "-ERR invalid array index\r\n"
13090        );
13091        assert_eq!(f.run(&[b"EXISTS", b"a"]), ":0\r\n");
13092        f.run(&[b"ARSET", b"a", b"0", b"x", b"y", b"z"]);
13093        assert_eq!(
13094            f.run(&[b"ARDEL", b"a", b"0", b"01"]),
13095            "-ERR invalid array index\r\n"
13096        );
13097        assert_eq!(f.run(&[b"ARCOUNT", b"a"]), ":3\r\n");
13098        // An index is unsigned here, so the numbers a list would take are not
13099        // the last element, they are errors.
13100        assert_eq!(
13101            f.run(&[b"ARGET", b"a", b"-1"]),
13102            "-ERR invalid array index\r\n"
13103        );
13104        // And a pair list with an odd tail is an arity error rather than a
13105        // syntax one.
13106        assert_eq!(
13107            f.run(&[b"ARMSET", b"a", b"0", b"x", b"1"]),
13108            "-ERR wrong number of arguments for 'armset' command\r\n"
13109        );
13110        assert_eq!(
13111            f.run(&[b"ARDELRANGE", b"a", b"0", b"1", b"2"]),
13112            "-ERR wrong number of arguments for 'ardelrange' command\r\n"
13113        );
13114    }
13115
13116    #[test]
13117    fn a_range_delete_costs_the_elements_and_takes_the_key_when_it_empties() {
13118        let mut f = Fixture::new();
13119        f.run(&[b"ARSET", b"a", b"0", b"0", b"1", b"2", b"3", b"4"]);
13120        assert_eq!(f.run(&[b"ARDELRANGE", b"a", b"3", b"1"]), ":3\r\n");
13121        assert_eq!(f.run(&[b"ARCOUNT", b"a"]), ":2\r\n");
13122        // Two ranges in one command, and the second one covers the whole space
13123        // without walking it.
13124        assert_eq!(
13125            f.run(&[
13126                b"ARDELRANGE",
13127                b"a",
13128                b"100",
13129                b"200",
13130                b"0",
13131                b"18446744073709551614"
13132            ]),
13133            ":2\r\n"
13134        );
13135        assert_eq!(f.run(&[b"EXISTS", b"a"]), ":0\r\n");
13136        assert_eq!(f.run(&[b"ARDELRANGE", b"nope", b"0", b"1"]), ":0\r\n");
13137        assert_eq!(f.run(&[b"ARDEL", b"nope", b"0"]), ":0\r\n");
13138    }
13139
13140    /// A value goes out as the bytes it came in as, whichever of the three ways
13141    /// the array found to store it.
13142    #[test]
13143    fn a_value_comes_back_byte_for_byte_however_it_was_packed() {
13144        let mut f = Fixture::new();
13145        let long = vec![b'v'; 200];
13146        f.run(&[
13147            b"ARMSET", b"a", b"0", b"42", b"1", b"007", b"2", b"3.5", b"3", b"3.14", b"4",
13148            b"short", b"5", &long, b"6", b"-0",
13149        ]);
13150        // 42 is an integer, 007 is not one because it does not print back the
13151        // same, 3.5 survives a double and 3.14 does not, and the last two are a
13152        // word packed string and a blob.
13153        assert_eq!(
13154            f.run(&[b"ARGETRANGE", b"a", b"0", b"6"]),
13155            format!(
13156                "*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",
13157                String::from_utf8_lossy(&long)
13158            )
13159        );
13160    }
13161
13162    #[test]
13163    fn an_array_is_a_type_and_an_encoding_a_client_can_see() {
13164        let mut f = Fixture::new();
13165        f.run(&[b"ARSET", b"a", b"0", b"x"]);
13166        assert_eq!(f.run(&[b"TYPE", b"a"]), "+array\r\n");
13167        assert_eq!(
13168            f.run(&[b"OBJECT", b"ENCODING", b"a"]),
13169            "$12\r\nsliced-array\r\n"
13170        );
13171        // And it is a body like any other, so the key commands work on it.
13172        assert_eq!(f.run(&[b"EXPIRE", b"a", b"100"]), ":1\r\n");
13173        assert_eq!(f.run(&[b"PERSIST", b"a"]), ":1\r\n");
13174        assert_eq!(f.run(&[b"COPY", b"a", b"b"]), ":1\r\n");
13175        assert_eq!(f.run(&[b"ARGET", b"b", b"0"]), "$1\r\nx\r\n");
13176        assert_eq!(f.run(&[b"RENAME", b"a", b"c"]), "+OK\r\n");
13177        assert_eq!(f.run(&[b"ARCOUNT", b"c"]), ":1\r\n");
13178    }
13179
13180    #[test]
13181    fn every_array_command_refuses_a_key_holding_something_else() {
13182        let mut f = Fixture::new();
13183        f.run(&[b"SET", b"s", b"v"]);
13184        let wrong = "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n";
13185        for cmd in [
13186            &[b"ARSET".as_ref(), b"s", b"0", b"x"][..],
13187            &[b"ARMSET".as_ref(), b"s", b"0", b"x"][..],
13188            &[b"ARGET".as_ref(), b"s", b"0"][..],
13189            &[b"ARMGET".as_ref(), b"s", b"0"][..],
13190            &[b"ARGETRANGE".as_ref(), b"s", b"0", b"1"][..],
13191            &[b"ARLEN".as_ref(), b"s"][..],
13192            &[b"ARCOUNT".as_ref(), b"s"][..],
13193            &[b"ARDEL".as_ref(), b"s", b"0"][..],
13194            &[b"ARDELRANGE".as_ref(), b"s", b"0", b"1"][..],
13195            &[b"ARINSERT".as_ref(), b"s", b"x"][..],
13196            &[b"ARRING".as_ref(), b"s", b"4", b"x"][..],
13197            &[b"ARNEXT".as_ref(), b"s"][..],
13198            &[b"ARSEEK".as_ref(), b"s", b"1"][..],
13199            &[b"ARLASTITEMS".as_ref(), b"s", b"1"][..],
13200            &[b"ARSCAN".as_ref(), b"s", b"0", b"1"][..],
13201            &[b"ARGREP".as_ref(), b"s", b"0", b"1", b"EXACT", b"v"][..],
13202            &[b"AROP".as_ref(), b"s", b"0", b"1", b"SUM"][..],
13203            &[b"ARINFO".as_ref(), b"s"][..],
13204        ] {
13205            assert_eq!(f.run(cmd), wrong, "{}", String::from_utf8_lossy(cmd[0]));
13206        }
13207    }
13208
13209    /// Two of the array commands look the key up before they read the index and
13210    /// the rest read the index first, so the same broken argument gets two
13211    /// different errors depending on which command it went to.
13212    #[test]
13213    fn a_bad_index_reports_the_type_only_where_redis_reports_it() {
13214        let mut f = Fixture::new();
13215        f.run(&[b"SET", b"s", b"v"]);
13216        let wrong = "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n";
13217        let bad = "-ERR invalid array index\r\n";
13218        assert_eq!(f.run(&[b"ARGET", b"s", b"-1"]), wrong);
13219        assert_eq!(f.run(&[b"ARMGET", b"s", b"0", b"-1"]), wrong);
13220        assert_eq!(f.run(&[b"ARSET", b"s", b"-1", b"x"]), bad);
13221        assert_eq!(f.run(&[b"ARDEL", b"s", b"-1"]), bad);
13222        assert_eq!(f.run(&[b"ARSCAN", b"s", b"-1", b"0"]), bad);
13223        assert_eq!(f.run(&[b"ARGREP", b"s", b"-1", b"0", b"EXACT", b"v"]), bad);
13224        // And on a key that is an array the index is just an index.
13225        f.run(&[b"ARSET", b"a", b"0", b"x"]);
13226        assert_eq!(f.run(&[b"ARGET", b"a", b"-1"]), bad);
13227        assert_eq!(f.run(&[b"ARGET", b"nope", b"-1"]), bad);
13228    }
13229
13230    #[test]
13231    fn an_append_follows_a_cursor_the_client_can_move() {
13232        let mut f = Fixture::new();
13233        assert_eq!(f.run(&[b"ARNEXT", b"nope"]), ":0\r\n");
13234        assert_eq!(f.run(&[b"ARINSERT", b"a", b"x", b"y"]), ":1\r\n");
13235        assert_eq!(f.run(&[b"ARNEXT", b"a"]), ":2\r\n");
13236        assert_eq!(f.run(&[b"ARINSERT", b"a", b"z"]), ":2\r\n");
13237        assert_eq!(f.run(&[b"ARGET", b"a", b"2"]), "$1\r\nz\r\n");
13238
13239        // A seek says where the next one goes, and a missing key has no cursor
13240        // to move and is not created by the asking.
13241        assert_eq!(f.run(&[b"ARSEEK", b"nope", b"5"]), ":0\r\n");
13242        assert_eq!(f.run(&[b"EXISTS", b"nope"]), ":0\r\n");
13243        assert_eq!(f.run(&[b"ARSEEK", b"a", b"100"]), ":1\r\n");
13244        assert_eq!(f.run(&[b"ARNEXT", b"a"]), ":100\r\n");
13245        assert_eq!(f.run(&[b"ARINSERT", b"a", b"far"]), ":100\r\n");
13246        assert_eq!(f.run(&[b"ARSEEK", b"a", b"0"]), ":1\r\n");
13247        assert_eq!(f.run(&[b"ARNEXT", b"a"]), ":0\r\n");
13248
13249        // The top of the space is the one index only ARSEEK will take, and it
13250        // leaves the cursor with nowhere to go.
13251        assert_eq!(f.run(&[b"ARSEEK", b"a", b"18446744073709551615"]), ":1\r\n");
13252        assert_eq!(f.run(&[b"ARNEXT", b"a"]), "$-1\r\n");
13253        assert_eq!(
13254            f.run(&[b"ARINSERT", b"a", b"x"]),
13255            "-ERR insert index overflow\r\n"
13256        );
13257        assert_eq!(
13258            f.run(&[b"ARSET", b"a", b"18446744073709551615", b"x"]),
13259            "-ERR invalid array index\r\n"
13260        );
13261    }
13262
13263    #[test]
13264    fn a_ring_keeps_the_newest_and_renumbers_them_when_it_is_resized() {
13265        let mut f = Fixture::new();
13266        assert_eq!(f.run(&[b"ARRING", b"r", b"3", b"a", b"b", b"c"]), ":2\r\n");
13267        assert_eq!(f.run(&[b"ARRING", b"r", b"3", b"d", b"e"]), ":1\r\n");
13268        assert_eq!(f.run(&[b"ARLEN", b"r"]), ":3\r\n");
13269        assert_eq!(
13270            f.run(&[b"ARGETRANGE", b"r", b"0", b"2"]),
13271            "*3\r\n$1\r\nd\r\n$1\r\ne\r\n$1\r\nc\r\n"
13272        );
13273        // Growing it after it has wrapped puts the survivors back in the order
13274        // they arrived, which is the whole point of paying for the rebuild.
13275        assert_eq!(f.run(&[b"ARRING", b"r", b"5", b"f"]), ":3\r\n");
13276        assert_eq!(
13277            f.run(&[b"ARGETRANGE", b"r", b"0", b"3"]),
13278            "*4\r\n$1\r\nc\r\n$1\r\nd\r\n$1\r\ne\r\n$1\r\nf\r\n"
13279        );
13280        // The size is read before the key, so a bad one is a bad size wherever
13281        // it is sent.
13282        assert_eq!(
13283            f.run(&[b"ARRING", b"r", b"0", b"x"]),
13284            "-ERR size must be positive\r\n"
13285        );
13286        assert_eq!(
13287            f.run(&[b"ARRING", b"r", b"big", b"x"]),
13288            "-ERR invalid size\r\n"
13289        );
13290    }
13291
13292    #[test]
13293    fn the_last_items_walk_back_from_the_cursor_and_report_the_holes() {
13294        let mut f = Fixture::new();
13295        assert_eq!(f.run(&[b"ARLASTITEMS", b"nope", b"5"]), "*0\r\n");
13296        f.run(&[b"ARRING", b"r", b"4", b"a", b"b", b"c", b"d", b"e"]);
13297        assert_eq!(
13298            f.run(&[b"ARLASTITEMS", b"r", b"3"]),
13299            "*3\r\n$1\r\nc\r\n$1\r\nd\r\n$1\r\ne\r\n"
13300        );
13301        assert_eq!(
13302            f.run(&[b"ARLASTITEMS", b"r", b"3", b"rev"]),
13303            "*3\r\n$1\r\ne\r\n$1\r\nd\r\n$1\r\nc\r\n"
13304        );
13305        assert_eq!(
13306            f.run(&[b"ARLASTITEMS", b"r", b"99"]),
13307            "*4\r\n$1\r\nb\r\n$1\r\nc\r\n$1\r\nd\r\n$1\r\ne\r\n",
13308            "more than there is gets what there is"
13309        );
13310        // Nothing asked for is an empty reply, and Redis answers that before it
13311        // has read the option or looked at the key.
13312        assert_eq!(f.run(&[b"ARLASTITEMS", b"r", b"0", b"junk"]), "*0\r\n");
13313        assert_eq!(
13314            f.run(&[b"ARLASTITEMS", b"r", b"1", b"junk"]),
13315            "-ERR syntax error\r\n"
13316        );
13317        assert_eq!(
13318            f.run(&[b"ARLASTITEMS", b"r", b"nine"]),
13319            "-ERR invalid COUNT\r\n"
13320        );
13321
13322        // With no cursor the tail of the array is the anchor, and a hole inside
13323        // the window is reported as one.
13324        f.run(&[b"ARMSET", b"h", b"0", b"x", b"2", b"z"]);
13325        assert_eq!(
13326            f.run(&[b"ARLASTITEMS", b"h", b"5"]),
13327            "*2\r\n$-1\r\n$1\r\nz\r\n"
13328        );
13329    }
13330
13331    #[test]
13332    fn a_scan_answers_pairs_for_what_is_there_and_skips_what_is_not() {
13333        let mut f = Fixture::new();
13334        assert_eq!(f.run(&[b"ARSCAN", b"nope", b"0", b"10"]), "*0\r\n");
13335        f.run(&[b"ARMSET", b"a", b"0", b"x", b"7", b"y", b"1000000", b"z"]);
13336        // The whole index space, which ARGETRANGE refuses and this one answers
13337        // in three visits because holes cost nothing.
13338        assert_eq!(
13339            f.run(&[b"ARSCAN", b"a", b"0", b"18446744073709551614"]),
13340            "*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"
13341        );
13342        assert_eq!(
13343            f.run(&[
13344                b"ARSCAN",
13345                b"a",
13346                b"18446744073709551614",
13347                b"0",
13348                b"LIMIT",
13349                b"1"
13350            ]),
13351            "*1\r\n*2\r\n:1000000\r\n$1\r\nz\r\n"
13352        );
13353        assert_eq!(f.run(&[b"ARSCAN", b"a", b"1", b"6"]), "*0\r\n");
13354        assert_eq!(
13355            f.run(&[b"ARSCAN", b"a", b"0", b"10", b"LIMIT", b"0"]),
13356            "-ERR LIMIT must be positive\r\n"
13357        );
13358        assert_eq!(
13359            f.run(&[b"ARSCAN", b"a", b"0", b"10", b"NOPE", b"1"]),
13360            "-ERR syntax error\r\n"
13361        );
13362        assert_eq!(
13363            f.run(&[b"ARSCAN", b"a", b"0", b"10", b"LIMIT"]),
13364            "-ERR wrong number of arguments for 'arscan' command\r\n"
13365        );
13366    }
13367
13368    #[test]
13369    fn a_grep_answers_the_indexes_whose_elements_match() {
13370        let mut f = Fixture::new();
13371        assert_eq!(
13372            f.run(&[b"ARGREP", b"nope", b"0", b"10", b"EXACT", b"x"]),
13373            "*0\r\n"
13374        );
13375        f.run(&[b"ARSET", b"a", b"0", b"alpha", b"beta", b"gamma", b"ALPHA"]);
13376
13377        // The two bounds take the ends of the array as well as an index, and a
13378        // reversed range is walked backwards the way ARSCAN walks one.
13379        assert_eq!(
13380            f.run(&[b"ARGREP", b"a", b"-", b"+", b"GLOB", b"*a"]),
13381            "*3\r\n:0\r\n:1\r\n:2\r\n"
13382        );
13383        assert_eq!(
13384            f.run(&[b"ARGREP", b"a", b"+", b"-", b"GLOB", b"*a"]),
13385            "*3\r\n:2\r\n:1\r\n:0\r\n"
13386        );
13387        assert_eq!(
13388            f.run(&[b"ARGREP", b"a", b"1", b"2", b"GLOB", b"*a"]),
13389            "*2\r\n:1\r\n:2\r\n"
13390        );
13391
13392        // One test each. NOCASE reaches all four of them and it may be written
13393        // after the pattern it applies to.
13394        assert_eq!(
13395            f.run(&[b"ARGREP", b"a", b"-", b"+", b"EXACT", b"alpha"]),
13396            "*1\r\n:0\r\n"
13397        );
13398        assert_eq!(
13399            f.run(&[b"ARGREP", b"a", b"-", b"+", b"EXACT", b"alpha", b"NOCASE"]),
13400            "*2\r\n:0\r\n:3\r\n"
13401        );
13402        assert_eq!(
13403            f.run(&[b"ARGREP", b"a", b"-", b"+", b"MATCH", b"mm"]),
13404            "*1\r\n:2\r\n"
13405        );
13406        assert_eq!(
13407            f.run(&[b"ARGREP", b"a", b"-", b"+", b"RE", b"^[bg]"]),
13408            "*2\r\n:1\r\n:2\r\n"
13409        );
13410
13411        // OR is the default and AND has to be asked for, and either way the
13412        // last of a repeated option wins.
13413        let both: &[&[u8]] = &[
13414            b"ARGREP", b"a", b"-", b"+", b"EXACT", b"beta", b"MATCH", b"al",
13415        ];
13416        assert_eq!(f.run(both), "*2\r\n:0\r\n:1\r\n");
13417        assert_eq!(
13418            f.run(&[
13419                b"ARGREP", b"a", b"-", b"+", b"EXACT", b"beta", b"MATCH", b"al", b"AND"
13420            ]),
13421            "*0\r\n"
13422        );
13423        assert_eq!(
13424            f.run(&[
13425                b"ARGREP", b"a", b"-", b"+", b"EXACT", b"beta", b"MATCH", b"al", b"AND", b"OR"
13426            ]),
13427            "*2\r\n:0\r\n:1\r\n"
13428        );
13429
13430        // WITHVALUES turns each hit into a pair, and LIMIT counts the hits and
13431        // not the positions it had to look at.
13432        assert_eq!(
13433            f.run(&[
13434                b"ARGREP",
13435                b"a",
13436                b"-",
13437                b"+",
13438                b"MATCH",
13439                b"a",
13440                b"WITHVALUES",
13441                b"LIMIT",
13442                b"2"
13443            ]),
13444            "*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"
13445        );
13446        assert_eq!(
13447            f.run(&[
13448                b"ARGREP", b"a", b"-", b"+", b"EXACT", b"ALPHA", b"LIMIT", b"1"
13449            ]),
13450            "*1\r\n:3\r\n"
13451        );
13452    }
13453
13454    /// Everything ARGREP refuses, in the order it refuses it.
13455    #[test]
13456    fn a_grep_reports_a_broken_command_the_way_redis_does() {
13457        let mut f = Fixture::new();
13458        f.run(&[b"ARSET", b"a", b"0", b"alpha"]);
13459        let syntax = "-ERR syntax error\r\n";
13460
13461        // The bounds are read before the plan, so a bad index beats a bad
13462        // predicate whichever way round the two are written.
13463        assert_eq!(
13464            f.run(&[b"ARGREP", b"a", b"-1", b"0", b"NOPE", b"x"]),
13465            "-ERR invalid array index\r\n"
13466        );
13467        assert_eq!(f.run(&[b"ARGREP", b"a", b"0", b"1", b"NOPE", b"x"]), syntax);
13468        // A keyword with nothing after it, and a command that asks for nothing.
13469        assert_eq!(
13470            f.run(&[b"ARGREP", b"a", b"0", b"1", b"NOCASE", b"EXACT"]),
13471            syntax
13472        );
13473        assert_eq!(
13474            f.run(&[b"ARGREP", b"a", b"0", b"1", b"EXACT", b"x", b"LIMIT"]),
13475            syntax
13476        );
13477        assert_eq!(
13478            f.run(&[b"ARGREP", b"a", b"0", b"1", b"NOCASE", b"WITHVALUES"]),
13479            syntax,
13480            "a command with no predicate in it at all"
13481        );
13482        assert_eq!(
13483            f.run(&[b"ARGREP", b"a", b"0", b"1", b"EXACT", b"x", b"LIMIT", b"0"]),
13484            "-ERR LIMIT must be positive\r\n"
13485        );
13486        assert_eq!(
13487            f.run(&[
13488                b"ARGREP", b"a", b"0", b"1", b"EXACT", b"x", b"LIMIT", b"nine"
13489            ]),
13490            "-ERR value is not an integer or out of range\r\n"
13491        );
13492        assert_eq!(
13493            f.run(&[b"ARGREP", b"a", b"0", b"1", b"RE", b""]),
13494            "-ERR regular expression is empty\r\n"
13495        );
13496        assert_eq!(
13497            f.run(&[b"ARGREP", b"a", b"0", b"1", b"RE", b"(a"]),
13498            "-ERR invalid regular expression: Missing ')'\r\n"
13499        );
13500        assert_eq!(
13501            f.run(&[b"ARGREP", b"a", b"0", b"1", b"RE", br"(a)\1"]),
13502            "-ERR regular expression backreferences are not supported\r\n"
13503        );
13504        // The arity is minus six, so a predicate keyword with no pattern after
13505        // it is short by one and never reaches the parser.
13506        let arity = "-ERR wrong number of arguments for 'argrep' command\r\n";
13507        assert_eq!(f.run(&[b"ARGREP", b"a", b"0", b"1", b"EXACT"]), arity);
13508        assert_eq!(f.run(&[b"ARGREP", b"a", b"0", b"1"]), arity);
13509    }
13510
13511    #[test]
13512    fn an_op_reduces_a_range_to_one_number() {
13513        let mut f = Fixture::new();
13514        f.run(&[b"ARSET", b"a", b"0", b"1", b"2.5", b"word", b"-4"]);
13515        assert_eq!(
13516            f.run(&[b"AROP", b"a", b"0", b"10", b"SUM"]),
13517            "$4\r\n-0.5\r\n"
13518        );
13519        assert_eq!(f.run(&[b"AROP", b"a", b"0", b"10", b"min"]), "$2\r\n-4\r\n");
13520        assert_eq!(
13521            f.run(&[b"AROP", b"a", b"0", b"10", b"MAX"]),
13522            "$3\r\n2.5\r\n"
13523        );
13524        assert_eq!(f.run(&[b"AROP", b"a", b"0", b"10", b"USED"]), ":4\r\n");
13525        assert_eq!(
13526            f.run(&[b"AROP", b"a", b"0", b"10", b"MATCH", b"word"]),
13527            ":1\r\n"
13528        );
13529        // An aggregate is written with seventeen significant digits, which is
13530        // Redis's own choice and not what a score comes back as.
13531        f.run(&[b"ARSET", b"t", b"0", b"0.1", b"0.2"]);
13532        assert_eq!(
13533            f.run(&[b"AROP", b"t", b"0", b"10", b"SUM"]),
13534            "$19\r\n0.30000000000000004\r\n"
13535        );
13536        assert_eq!(f.run(&[b"ZADD", b"z", b"0.3", b"m"]), ":1\r\n");
13537        assert_eq!(f.run(&[b"ZSCORE", b"z", b"m"]), "$3\r\n0.3\r\n");
13538
13539        // Nothing to work with is a null, and a missing key is a null for the
13540        // aggregates and a zero for the two that count.
13541        f.run(&[b"ARSET", b"w", b"0", b"word"]);
13542        assert_eq!(f.run(&[b"AROP", b"w", b"0", b"10", b"SUM"]), "$-1\r\n");
13543        assert_eq!(f.run(&[b"AROP", b"nope", b"0", b"10", b"SUM"]), "$-1\r\n");
13544        assert_eq!(f.run(&[b"AROP", b"nope", b"0", b"10", b"USED"]), ":0\r\n");
13545
13546        assert_eq!(
13547            f.run(&[b"AROP", b"a", b"0", b"10", b"NOPE"]),
13548            "-ERR unknown operation\r\n"
13549        );
13550        assert_eq!(
13551            f.run(&[b"AROP", b"a", b"0", b"10", b"MATCH"]),
13552            "-ERR MATCH requires a value argument\r\n"
13553        );
13554        assert_eq!(
13555            f.run(&[b"AROP", b"a", b"0", b"10", b"SUM", b"extra"]),
13556            "-ERR wrong number of arguments for 'arop' command\r\n"
13557        );
13558    }
13559
13560    #[test]
13561    fn the_info_is_a_map_and_a_missing_key_is_an_error() {
13562        let mut f = Fixture::new();
13563        assert_eq!(f.run(&[b"ARINFO", b"nope"]), "-ERR no such key\r\n");
13564        f.run(&[b"ARINSERT", b"a", b"x", b"y"]);
13565        let short = f.run(&[b"ARINFO", b"a"]);
13566        assert!(
13567            short.starts_with("*14\r\n"),
13568            "seven pairs on RESP2: {short}"
13569        );
13570        assert!(short.contains("$5\r\ncount\r\n:2\r\n"), "{short}");
13571        assert!(
13572            short.contains("$17\r\nnext-insert-index\r\n:2\r\n"),
13573            "{short}"
13574        );
13575        assert!(short.contains("$10\r\nslice-size\r\n:4096\r\n"), "{short}");
13576        let full = f.run(&[b"ARINFO", b"a", b"full"]);
13577        assert!(full.starts_with("*24\r\n"), "twelve pairs: {full}");
13578        // Two values one apart are held sparsely, so the dense count is zero and
13579        // the two dense averages have nothing to average.
13580        assert!(full.contains("$12\r\ndense-slices\r\n:0\r\n"), "{full}");
13581        assert!(full.contains("$13\r\nsparse-slices\r\n:1\r\n"), "{full}");
13582        assert!(
13583            full.contains("$14\r\navg-dense-size\r\n$1\r\n0\r\n"),
13584            "{full}"
13585        );
13586        assert_eq!(f.run(&[b"ARINFO", b"a", b"nope"]), "-ERR syntax error\r\n");
13587
13588        // On RESP3 the same reply is a map and the averages are doubles.
13589        let mut g = Fixture::new();
13590        g.run(&[b"HELLO", b"3"]);
13591        g.run(&[b"ARINSERT", b"a", b"x"]);
13592        let map = g.run(&[b"ARINFO", b"a", b"FULL"]);
13593        assert!(map.starts_with("%12\r\n"), "{map}");
13594        assert!(map.contains("$5\r\ncount\r\n:1\r\n"), "{map}");
13595        assert!(map.contains("$14\r\navg-dense-size\r\n,0\r\n"), "{map}");
13596    }
13597
13598    #[test]
13599    fn a_double_on_the_wire_is_written_the_way_redis_writes_one() {
13600        let mut f = Fixture::new();
13601        // Whole numbers up to two to the sixty second come back as integers,
13602        // and past that the digit generator takes over and uses an exponent.
13603        for (score, want) in [
13604            ("3", "3"),
13605            ("3.5", "3.5"),
13606            ("0.3", "0.3"),
13607            ("1e30", "1e+30"),
13608            ("1e19", "1e+19"),
13609            ("1e-7", "1e-7"),
13610            ("0.000001", "0.000001"),
13611            ("4611686018427387904", "4611686018427387904"),
13612            ("-0", "-0"),
13613        ] {
13614            f.run(&[b"ZADD", b"z", score.as_bytes(), b"m"]);
13615            assert_eq!(
13616                f.run(&[b"ZSCORE", b"z", b"m"]),
13617                format!("${}\r\n{want}\r\n", want.len()),
13618                "score {score}"
13619            );
13620        }
13621
13622        // The same bytes on RESP3, where the reply is a double rather than a
13623        // bulk string.
13624        let mut g = Fixture::new();
13625        g.run(&[b"HELLO", b"3"]);
13626        g.run(&[b"ZADD", b"z", b"1e30", b"m"]);
13627        assert_eq!(g.run(&[b"ZSCORE", b"z", b"m"]), ",1e+30\r\n");
13628        // The two float increments are not this printer. They go through
13629        // ld2string in its human mode, which is a fixed point conversion with
13630        // the trailing zeros taken off, so they never write an exponent, and
13631        // they reply with a bulk string on both protocols.
13632        assert_eq!(
13633            g.run(&[b"INCRBYFLOAT", b"s", b"1e30"]),
13634            "$31\r\n1000000000000000000000000000000\r\n"
13635        );
13636        assert_eq!(g.run(&[b"INCRBYFLOAT", b"t", b"0.1"]), "$3\r\n0.1\r\n");
13637        assert_eq!(
13638            g.run(&[b"HINCRBYFLOAT", b"h", b"f", b"1e19"]),
13639            "$20\r\n10000000000000000000\r\n"
13640        );
13641    }
13642
13643    // ----------------------------------------------------------------- graph
13644
13645    #[test]
13646    fn a_node_comes_back_with_the_fields_it_went_in_with() {
13647        let mut f = Fixture::new();
13648        assert_eq!(
13649            f.run(&[
13650                b"G.NADD", b"social", b"ada", b"name", b"Ada", b"born", b"1815"
13651            ]),
13652            ":1\r\n"
13653        );
13654        // The year comes back as the four bytes that were sent and not as a
13655        // number, because every property is text and there is nothing on the
13656        // wire that says which of `1815` and `"1815"` the client meant. The
13657        // fields are in the document's order, which is sorted by name, because
13658        // that is what makes a field lookup a binary search.
13659        assert_eq!(
13660            f.run(&[b"G.NGET", b"social", b"ada"]),
13661            "*4\r\n$4\r\nborn\r\n$4\r\n1815\r\n$4\r\nname\r\n$3\r\nAda\r\n"
13662        );
13663        // A second write to the same id replaces the document and says so with
13664        // a zero, so an ingest can count what it created.
13665        assert_eq!(
13666            f.run(&[b"G.NADD", b"social", b"ada", b"name", b"Ada Lovelace"]),
13667            ":0\r\n"
13668        );
13669        assert_eq!(
13670            f.run(&[b"G.NGET", b"social", b"ada"]),
13671            "*2\r\n$4\r\nname\r\n$12\r\nAda Lovelace\r\n"
13672        );
13673        // A node with no properties is an empty map and not a null, which is
13674        // how a client tells an isolated node from one that is not there.
13675        assert_eq!(f.run(&[b"G.NADD", b"social", b"grace"]), ":1\r\n");
13676        assert_eq!(f.run(&[b"G.NGET", b"social", b"grace"]), "*0\r\n");
13677        assert_eq!(f.run(&[b"G.NGET", b"social", b"nobody"]), "$-1\r\n");
13678        assert_eq!(f.run(&[b"G.NGET", b"nokey", b"ada"]), "$-1\r\n");
13679
13680        // A field with no value creates nothing, because the pairs are checked
13681        // before the key is touched.
13682        assert_eq!(
13683            f.run(&[b"G.NADD", b"fresh", b"n", b"lonely"]),
13684            "-ERR syntax error\r\n"
13685        );
13686        assert_eq!(f.run(&[b"EXISTS", b"fresh"]), ":0\r\n");
13687
13688        // On RESP3 the same reply is a map.
13689        let mut g = Fixture::new();
13690        g.run(&[b"HELLO", b"3"]);
13691        g.run(&[b"G.NADD", b"social", b"ada", b"name", b"Ada"]);
13692        assert_eq!(
13693            g.run(&[b"G.NGET", b"social", b"ada"]),
13694            "%1\r\n$4\r\nname\r\n$3\r\nAda\r\n"
13695        );
13696    }
13697
13698    #[test]
13699    fn an_edge_creates_the_ends_it_needs() {
13700        let mut f = Fixture::new();
13701        assert_eq!(
13702            f.run(&[
13703                b"G.EADD", b"social", b"ada", b"grace", b"FOLLOWS", b"since", b"1843"
13704            ]),
13705            ":1\r\n"
13706        );
13707        // Neither end was written first and both are there, as empty nodes.
13708        assert_eq!(f.run(&[b"G.NGET", b"social", b"ada"]), "*0\r\n");
13709        assert_eq!(f.run(&[b"G.NGET", b"social", b"grace"]), "*0\r\n");
13710        assert_eq!(
13711            f.run(&[b"G.OUT", b"social", b"ada", b"FOLLOWS"]),
13712            "*2\r\n$1\r\n0\r\n*1\r\n$5\r\ngrace\r\n"
13713        );
13714        assert_eq!(
13715            f.run(&[b"G.IN", b"social", b"grace", b"FOLLOWS"]),
13716            "*2\r\n$1\r\n0\r\n*1\r\n$3\r\nada\r\n"
13717        );
13718        // The same pair under the same label again updates the edge rather than
13719        // making a second one.
13720        assert_eq!(
13721            f.run(&[
13722                b"G.EADD", b"social", b"ada", b"grace", b"FOLLOWS", b"since", b"1844"
13723            ]),
13724            ":0\r\n"
13725        );
13726        assert_eq!(f.run(&[b"G.DEG", b"social", b"ada", b"FOLLOWS"]), ":1\r\n");
13727        // A different label between the same pair is a different edge.
13728        assert_eq!(
13729            f.run(&[b"G.EADD", b"social", b"ada", b"grace", b"WORKS_WITH"]),
13730            ":1\r\n"
13731        );
13732        assert_eq!(
13733            f.run(&[b"G.DEG", b"social", b"ada", b"WORKS_WITH"]),
13734            ":1\r\n"
13735        );
13736
13737        assert_eq!(
13738            f.run(&[b"G.EDEL", b"social", b"ada", b"grace", b"FOLLOWS"]),
13739            ":1\r\n"
13740        );
13741        assert_eq!(
13742            f.run(&[b"G.EDEL", b"social", b"ada", b"grace", b"FOLLOWS"]),
13743            ":0\r\n"
13744        );
13745        // A label nothing has used, an end that is not there, and a key that is
13746        // not there are all a zero rather than an error.
13747        assert_eq!(
13748            f.run(&[b"G.EDEL", b"social", b"ada", b"grace", b"NEVER"]),
13749            ":0\r\n"
13750        );
13751        assert_eq!(
13752            f.run(&[b"G.EDEL", b"social", b"ada", b"nobody", b"FOLLOWS"]),
13753            ":0\r\n"
13754        );
13755        assert_eq!(
13756            f.run(&[b"G.EDEL", b"nokey", b"ada", b"grace", b"FOLLOWS"]),
13757            ":0\r\n"
13758        );
13759    }
13760
13761    /// A run is paged the way `SCAN` is paged, so a client that can walk one
13762    /// can walk the other.
13763    #[test]
13764    fn a_hop_answers_a_cursor_and_a_page() {
13765        let mut f = Fixture::new();
13766        for i in 0..25u32 {
13767            let dst = format!("n{i}");
13768            f.run(&[b"G.EADD", b"social", b"hub", dst.as_bytes(), b"FOLLOWS"]);
13769        }
13770        // Ten without being asked, and the cursor is where to carry on from.
13771        let first = f.run(&[b"G.OUT", b"social", b"hub", b"FOLLOWS"]);
13772        assert!(first.starts_with("*2\r\n$2\r\n10\r\n*10\r\n"), "{first}");
13773
13774        let mut seen = 0;
13775        let mut cursor = String::from("0");
13776        loop {
13777            let page = f.run(&[
13778                b"G.OUT",
13779                b"social",
13780                b"hub",
13781                b"FOLLOWS",
13782                b"COUNT",
13783                b"7",
13784                b"CURSOR",
13785                cursor.as_bytes(),
13786            ]);
13787            let (head, rest) = page.split_once("\r\n*").expect("a cursor and a page");
13788            cursor = head
13789                .rsplit("\r\n")
13790                .next()
13791                .expect("the cursor line")
13792                .to_string();
13793            seen += rest
13794                .split_once("\r\n")
13795                .expect("the page length")
13796                .0
13797                .parse::<usize>()
13798                .expect("a length");
13799            if cursor == "0" {
13800                break;
13801            }
13802        }
13803        assert_eq!(seen, 25, "every neighbour once across the pages");
13804
13805        // A cursor past the end is an empty page and not an error, and so is a
13806        // key or a label that is not there.
13807        assert_eq!(
13808            f.run(&[b"G.OUT", b"social", b"hub", b"FOLLOWS", b"CURSOR", b"900"]),
13809            "*2\r\n$1\r\n0\r\n*0\r\n"
13810        );
13811        assert_eq!(
13812            f.run(&[b"G.OUT", b"social", b"hub", b"NEVER"]),
13813            "*2\r\n$1\r\n0\r\n*0\r\n"
13814        );
13815        assert_eq!(
13816            f.run(&[b"G.OUT", b"nokey", b"hub", b"FOLLOWS"]),
13817            "*2\r\n$1\r\n0\r\n*0\r\n"
13818        );
13819        assert_eq!(
13820            f.run(&[b"G.OUT", b"social", b"hub", b"FOLLOWS", b"COUNT", b"0"]),
13821            "-ERR COUNT must be a positive integer\r\n"
13822        );
13823        assert_eq!(
13824            f.run(&[b"G.OUT", b"social", b"hub", b"FOLLOWS", b"NOPE", b"1"]),
13825            "-ERR syntax error\r\n"
13826        );
13827    }
13828
13829    #[test]
13830    fn a_degree_counts_one_way_or_both() {
13831        let mut f = Fixture::new();
13832        f.run(&[b"G.EADD", b"social", b"a", b"b", b"F"]);
13833        f.run(&[b"G.EADD", b"social", b"a", b"c", b"F"]);
13834        f.run(&[b"G.EADD", b"social", b"d", b"a", b"F"]);
13835        assert_eq!(f.run(&[b"G.DEG", b"social", b"a", b"F"]), ":2\r\n");
13836        assert_eq!(f.run(&[b"G.DEG", b"social", b"a", b"F", b"OUT"]), ":2\r\n");
13837        assert_eq!(f.run(&[b"G.DEG", b"social", b"a", b"F", b"IN"]), ":1\r\n");
13838        assert_eq!(f.run(&[b"G.DEG", b"social", b"a", b"F", b"BOTH"]), ":3\r\n");
13839        assert_eq!(f.run(&[b"G.DEG", b"social", b"nobody", b"F"]), ":0\r\n");
13840        assert_eq!(f.run(&[b"G.DEG", b"social", b"a", b"NEVER"]), ":0\r\n");
13841        assert_eq!(f.run(&[b"G.DEG", b"nokey", b"a", b"F"]), ":0\r\n");
13842        assert_eq!(
13843            f.run(&[b"G.DEG", b"social", b"a", b"F", b"SIDEWAYS"]),
13844            "-ERR syntax error\r\n"
13845        );
13846    }
13847
13848    /// A walk answers which nodes it can reach and not by how many routes, so a
13849    /// node two ways out is in the frontier once.
13850    #[test]
13851    fn a_walk_reaches_each_node_once_however_many_ways_there_are() {
13852        let mut f = Fixture::new();
13853        for (src, dst) in [
13854            ("ada", "grace"),
13855            ("ada", "alan"),
13856            ("grace", "edsger"),
13857            ("alan", "edsger"),
13858            ("edsger", "barbara"),
13859        ] {
13860            f.run(&[b"G.EADD", b"social", src.as_bytes(), dst.as_bytes(), b"F"]);
13861        }
13862        // Two hops without being asked, the start left out, and edsger once
13863        // even though both of the first hop's nodes point at it.
13864        assert_eq!(
13865            f.run(&[b"G.NEIGH", b"social", b"ada", b"F"]),
13866            "*3\r\n$5\r\ngrace\r\n$4\r\nalan\r\n$6\r\nedsger\r\n"
13867        );
13868        assert_eq!(
13869            f.run(&[b"G.NEIGH", b"social", b"ada", b"F", b"DEPTH", b"1"]),
13870            "*2\r\n$5\r\ngrace\r\n$4\r\nalan\r\n"
13871        );
13872        let deep = f.run(&[b"G.NEIGH", b"social", b"ada", b"F", b"DEPTH", b"9"]);
13873        assert!(deep.starts_with("*4\r\n"), "the whole component: {deep}");
13874        assert!(deep.contains("$7\r\nbarbara\r\n"), "{deep}");
13875        // COUNT stops the walk rather than trimming what it found.
13876        assert_eq!(
13877            f.run(&[b"G.NEIGH", b"social", b"ada", b"F", b"COUNT", b"1"]),
13878            "*1\r\n$5\r\ngrace\r\n"
13879        );
13880        // A node nothing leaves is an empty array and not an error.
13881        assert_eq!(f.run(&[b"G.NEIGH", b"social", b"barbara", b"F"]), "*0\r\n");
13882        assert_eq!(f.run(&[b"G.NEIGH", b"social", b"ada", b"NEVER"]), "*0\r\n");
13883        assert_eq!(f.run(&[b"G.NEIGH", b"nokey", b"ada", b"F"]), "*0\r\n");
13884        assert_eq!(
13885            f.run(&[b"G.NEIGH", b"social", b"ada", b"F", b"DEPTH", b"0"]),
13886            "-ERR DEPTH must be a positive integer\r\n"
13887        );
13888        assert_eq!(
13889            f.run(&[b"G.NEIGH", b"social", b"ada", b"F", b"NOPE", b"1"]),
13890            "-ERR syntax error\r\n"
13891        );
13892    }
13893
13894    /// The two sided search, which is the whole reason `G.PATH` is a command
13895    /// and not something a client builds out of `G.OUT`.
13896    #[test]
13897    fn a_path_is_the_shortest_one_and_goes_over_any_label() {
13898        let mut f = Fixture::new();
13899        // A chain of six, and a shortcut that makes a shorter way round under a
13900        // second label so the search has to take either kind of hop.
13901        for i in 0..6u32 {
13902            let src = format!("n{i}");
13903            let dst = format!("n{}", i + 1);
13904            f.run(&[b"G.EADD", b"road", src.as_bytes(), dst.as_bytes(), b"STEP"]);
13905        }
13906        assert_eq!(
13907            f.run(&[b"G.PATH", b"road", b"n0", b"n6"]),
13908            "*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"
13909        );
13910        f.run(&[b"G.EADD", b"road", b"n0", b"n5", b"JUMP"]);
13911        assert_eq!(
13912            f.run(&[b"G.PATH", b"road", b"n0", b"n6"]),
13913            "*3\r\n$2\r\nn0\r\n$2\r\nn5\r\n$2\r\nn6\r\n"
13914        );
13915        // A node to itself is a path of one, and a depth too short to reach is
13916        // no path at all.
13917        assert_eq!(
13918            f.run(&[b"G.PATH", b"road", b"n2", b"n2"]),
13919            "*1\r\n$2\r\nn2\r\n"
13920        );
13921        assert_eq!(
13922            f.run(&[b"G.PATH", b"road", b"n0", b"n6", b"MAXDEPTH", b"1"]),
13923            "*0\r\n"
13924        );
13925        // Direction counts: the chain only goes one way.
13926        assert_eq!(f.run(&[b"G.PATH", b"road", b"n6", b"n0"]), "*0\r\n");
13927        // An unreachable node, a node that is not there, and a key that is not
13928        // there are the same empty answer.
13929        f.run(&[b"G.NADD", b"road", b"island"]);
13930        assert_eq!(f.run(&[b"G.PATH", b"road", b"n0", b"island"]), "*0\r\n");
13931        assert_eq!(f.run(&[b"G.PATH", b"road", b"n0", b"nobody"]), "*0\r\n");
13932        assert_eq!(f.run(&[b"G.PATH", b"nokey", b"n0", b"n6"]), "*0\r\n");
13933        assert_eq!(
13934            f.run(&[b"G.PATH", b"road", b"n0", b"n6", b"NOPE", b"3"]),
13935            "-ERR syntax error\r\n"
13936        );
13937    }
13938
13939    /// The point of the escape in the record tag: the keyspace owns a graph key
13940    /// the way it owns every other key, and none of these commands know a graph
13941    /// exists.
13942    #[test]
13943    fn the_keyspace_sees_a_graph_key_like_any_other() {
13944        let mut f = Fixture::new();
13945        f.run(&[b"G.EADD", b"social", b"ada", b"grace", b"F"]);
13946        assert_eq!(f.run(&[b"TYPE", b"social"]), "+graph\r\n");
13947        assert_eq!(
13948            f.run(&[b"OBJECT", b"ENCODING", b"social"]),
13949            "$9\r\nadjacency\r\n"
13950        );
13951        assert_eq!(f.run(&[b"EXISTS", b"social"]), ":1\r\n");
13952        assert_eq!(f.run(&[b"DBSIZE"]), ":1\r\n");
13953        assert_eq!(f.run(&[b"KEYS", b"*"]), "*1\r\n$6\r\nsocial\r\n");
13954        // A graph is counted against the server the way every other body is,
13955        // which is what `maxmemory` will read when this key is a million nodes.
13956        // There is no `MEMORY USAGE` command yet, so this asks the server.
13957        let held = f.server.memory_bytes();
13958        for i in 0..200u32 {
13959            let dst = format!("n{i}");
13960            f.run(&[b"G.EADD", b"big", b"hub", dst.as_bytes(), b"F"]);
13961        }
13962        assert!(
13963            f.server.memory_bytes() > held,
13964            "two hundred edges cost something: {held} then {}",
13965            f.server.memory_bytes()
13966        );
13967        f.run(&[b"DEL", b"big"]);
13968
13969        // An expiry, then a rename, then a move to another database, all of
13970        // which are the keyspace moving a record it cannot look inside.
13971        assert_eq!(f.run(&[b"EXPIRE", b"social", b"100"]), ":1\r\n");
13972        assert_eq!(f.run(&[b"PERSIST", b"social"]), ":1\r\n");
13973        assert_eq!(f.run(&[b"RENAME", b"social", b"net"]), "+OK\r\n");
13974        assert_eq!(f.run(&[b"MOVE", b"net", b"1"]), ":1\r\n");
13975        assert_eq!(f.run(&[b"EXISTS", b"net"]), ":0\r\n");
13976        f.run(&[b"SELECT", b"1"]);
13977        assert_eq!(f.run(&[b"G.DEG", b"net", b"ada", b"F"]), ":1\r\n");
13978
13979        assert_eq!(f.run(&[b"DEL", b"net"]), ":1\r\n");
13980        assert_eq!(f.run(&[b"DBSIZE"]), ":0\r\n");
13981        f.run(&[b"G.NADD", b"g", b"n"]);
13982        assert_eq!(f.run(&[b"FLUSHDB"]), "+OK\r\n");
13983        assert_eq!(f.run(&[b"DBSIZE"]), ":0\r\n");
13984    }
13985
13986    /// Neither `COPY` nor `DUMP` has a byte shape for a graph, so both say so
13987    /// rather than answering the way they answer for a key that is not there.
13988    #[test]
13989    fn a_graph_cannot_be_copied_or_dumped() {
13990        let mut f = Fixture::new();
13991        f.run(&[b"G.NADD", b"social", b"ada"]);
13992        assert_eq!(
13993            f.run(&[b"COPY", b"social", b"other"]),
13994            "-ERR COPY is not supported for a graph\r\n"
13995        );
13996        assert_eq!(
13997            f.run(&[b"COPY", b"social", b"other", b"DB", b"1"]),
13998            "-ERR COPY is not supported for a graph\r\n"
13999        );
14000        assert_eq!(
14001            f.run(&[b"DUMP", b"social"]),
14002            "-ERR DUMP is not supported for a graph\r\n"
14003        );
14004        // A refused copy leaves both keys exactly as they were.
14005        assert_eq!(f.run(&[b"EXISTS", b"social", b"other"]), ":1\r\n");
14006    }
14007
14008    /// A graph key is a key, so the commands for the other types refuse it and
14009    /// the graph commands refuse theirs.
14010    #[test]
14011    fn a_graph_and_a_string_are_the_wrong_type_for_each_other() {
14012        let wrong = "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n";
14013        let mut f = Fixture::new();
14014        f.run(&[b"G.NADD", b"social", b"ada"]);
14015        assert_eq!(f.run(&[b"GET", b"social"]), wrong);
14016        assert_eq!(f.run(&[b"LPUSH", b"social", b"x"]), wrong);
14017        assert_eq!(f.run(&[b"SADD", b"social", b"x"]), wrong);
14018
14019        f.run(&[b"SET", b"str", b"v"]);
14020        for cmd in [
14021            vec![b"G.NADD".as_ref(), b"str", b"n"],
14022            vec![b"G.NGET".as_ref(), b"str", b"n"],
14023            vec![b"G.NDEL".as_ref(), b"str", b"n"],
14024            vec![b"G.EADD".as_ref(), b"str", b"a", b"b", b"F"],
14025            vec![b"G.EDEL".as_ref(), b"str", b"a", b"b", b"F"],
14026            vec![b"G.OUT".as_ref(), b"str", b"a", b"F"],
14027            vec![b"G.IN".as_ref(), b"str", b"a", b"F"],
14028            vec![b"G.DEG".as_ref(), b"str", b"a", b"F"],
14029            vec![b"G.NEIGH".as_ref(), b"str", b"a", b"F"],
14030            vec![b"G.PATH".as_ref(), b"str", b"a", b"b"],
14031        ] {
14032            assert_eq!(f.run(&cmd), wrong, "{:?}", cmd[0]);
14033        }
14034    }
14035
14036    /// Every other collection here takes its key with it when its last member
14037    /// goes, and a graph is no different.
14038    #[test]
14039    fn a_graph_goes_when_its_last_node_does() {
14040        let mut f = Fixture::new();
14041        f.run(&[
14042            b"G.EADD", b"social", b"ada", b"grace", b"F", b"since", b"1843",
14043        ]);
14044        assert_eq!(f.run(&[b"G.NDEL", b"social", b"ada"]), ":1\r\n");
14045        // The node and the edges that hung off it are both gone.
14046        assert_eq!(f.run(&[b"G.NGET", b"social", b"ada"]), "$-1\r\n");
14047        assert_eq!(
14048            f.run(&[b"G.DEG", b"social", b"grace", b"F", b"IN"]),
14049            ":0\r\n"
14050        );
14051        assert_eq!(f.run(&[b"G.NDEL", b"social", b"ada"]), ":0\r\n");
14052        assert_eq!(f.run(&[b"EXISTS", b"social"]), ":1\r\n");
14053
14054        assert_eq!(f.run(&[b"G.NDEL", b"social", b"grace"]), ":1\r\n");
14055        assert_eq!(f.run(&[b"EXISTS", b"social"]), ":0\r\n");
14056        assert_eq!(f.run(&[b"DBSIZE"]), ":0\r\n");
14057        assert_eq!(f.run(&[b"G.NDEL", b"nokey", b"ada"]), ":0\r\n");
14058
14059        // The id the removed node had is not handed out again, so a client
14060        // holding an id from an earlier reply cannot have it mean another node.
14061        f.run(&[b"G.NADD", b"social", b"first"]);
14062        f.run(&[b"G.NADD", b"social", b"second"]);
14063        f.run(&[b"G.NDEL", b"social", b"first"]);
14064        f.run(&[b"G.EADD", b"social", b"third", b"second", b"F"]);
14065        assert_eq!(
14066            f.run(&[b"G.OUT", b"social", b"third", b"F"]),
14067            "*2\r\n$1\r\n0\r\n*1\r\n$6\r\nsecond\r\n"
14068        );
14069    }
14070
14071    // ------------------------------------------------------------------ json
14072
14073    /// The two path syntaxes answer different shapes, which is the thing a
14074    /// client is most likely to be broken by and so the thing to pin first.
14075    #[test]
14076    fn a_json_path_answers_a_set_and_a_legacy_path_answers_a_value() {
14077        let mut f = Fixture::new();
14078        let doc = br#"{"a":1,"b":{"c":true}}"#;
14079        assert_eq!(f.run(&[b"JSON.SET", b"doc", b"$", doc]), "+OK\r\n");
14080        // No path at all is the legacy root and not `$`, so the document comes
14081        // back as itself rather than wrapped.
14082        assert_eq!(
14083            f.run(&[b"JSON.GET", b"doc"]),
14084            bulk(r#"{"a":1,"b":{"c":true}}"#)
14085        );
14086        assert_eq!(f.run(&[b"JSON.GET", b"doc", b"$.a"]), bulk("[1]"));
14087        assert_eq!(f.run(&[b"JSON.GET", b"doc", b".a"]), bulk("1"));
14088        assert_eq!(f.run(&[b"JSON.GET", b"doc", b"$..c"]), bulk("[true]"));
14089        // A path that matched nothing is an empty set on one syntax and an
14090        // error on the other, and the error does not quote the path.
14091        assert_eq!(f.run(&[b"JSON.GET", b"doc", b"$.nope"]), bulk("[]"));
14092        assert_eq!(
14093            f.run(&[b"JSON.GET", b"doc", b".nope"]),
14094            "-ERR Path does not exist\r\n"
14095        );
14096        assert_eq!(f.run(&[b"JSON.GET", b"nokey"]), "$-1\r\n");
14097        // The key is a document to the rest of the keyspace, under the name
14098        // RedisJSON registers, and every generic command works on it.
14099        assert_eq!(f.run(&[b"TYPE", b"doc"]), "+ReJSON-RL\r\n");
14100        assert_eq!(f.run(&[b"EXISTS", b"doc"]), ":1\r\n");
14101        assert_eq!(f.run(&[b"OBJECT", b"ENCODING", b"doc"]), bulk("raw"));
14102        assert_eq!(f.run(&[b"DEL", b"doc"]), ":1\r\n");
14103        assert_eq!(f.run(&[b"JSON.GET", b"doc"]), "$-1\r\n");
14104    }
14105
14106    /// The two error lines RedisJSON sends without a prefix in front of them.
14107    ///
14108    /// Every other error this server writes starts `ERR` or `WRONGTYPE`. These
14109    /// two do not, on a real server, and a differential harness compares the
14110    /// whole line.
14111    #[test]
14112    fn the_two_json_errors_that_carry_no_prefix() {
14113        let mut f = Fixture::new();
14114        f.run(&[b"SET", b"plain", b"x"]);
14115        let wrong = "-Existing key has wrong Redis type\r\n";
14116        assert_eq!(f.run(&[b"JSON.GET", b"plain"]), wrong);
14117        assert_eq!(f.run(&[b"JSON.SET", b"plain", b"$", b"1"]), wrong);
14118        assert_eq!(f.run(&[b"JSON.DEL", b"plain"]), wrong);
14119        assert_eq!(f.run(&[b"JSON.TYPE", b"plain"]), wrong);
14120        assert_eq!(f.run(&[b"JSON.CLEAR", b"plain"]), wrong);
14121
14122        f.run(&[b"JSON.SET", b"doc", b"$", br#"{"a":{"z":1},"b":{"z":2}}"#]);
14123        // A wildcard that matched something writes to all of it. A wildcard
14124        // that matched nothing would have to invent a place, and that is the
14125        // other unprefixed line.
14126        assert_eq!(f.run(&[b"JSON.SET", b"doc", b"$.*.z", b"9"]), "+OK\r\n");
14127        assert_eq!(
14128            f.run(&[b"JSON.GET", b"doc"]),
14129            bulk(r#"{"a":{"z":9},"b":{"z":9}}"#)
14130        );
14131        assert_eq!(
14132            f.run(&[b"JSON.SET", b"doc", b"$.*.y", b"9"]),
14133            "-Err wrong static path\r\n"
14134        );
14135    }
14136
14137    /// What `JSON.SET` does with a path that named nowhere.
14138    #[test]
14139    fn json_set_creates_one_field_and_refuses_to_invent_the_rest() {
14140        let mut f = Fixture::new();
14141        // A key that is not there can only be written whole.
14142        assert_eq!(
14143            f.run(&[b"JSON.SET", b"new", b".a", b"1"]),
14144            "-ERR new objects must be created at the root\r\n"
14145        );
14146        assert_eq!(f.run(&[b"EXISTS", b"new"]), ":0\r\n");
14147        // The root check comes before NX and XX, which is the order a real
14148        // server checks them in.
14149        assert_eq!(
14150            f.run(&[b"JSON.SET", b"new", b".a", b"1", b"NX"]),
14151            "-ERR new objects must be created at the root\r\n"
14152        );
14153        assert_eq!(f.run(&[b"JSON.SET", b"new", b"$", b"1", b"XX"]), "$-1\r\n");
14154        assert_eq!(f.run(&[b"JSON.SET", b"new", b"$", b"1", b"NX"]), "+OK\r\n");
14155
14156        f.run(&[
14157            b"JSON.SET",
14158            b"doc",
14159            b"$",
14160            br#"{"o":{},"arr":[1,2],"s":"x"}"#,
14161        ]);
14162        // One step past a container that is there is a place to write.
14163        assert_eq!(f.run(&[b"JSON.SET", b"doc", b"$.o.made", b"1"]), "+OK\r\n");
14164        // One step past something that is not, or past something that is not an
14165        // object, is not an error and is not a write either.
14166        assert_eq!(
14167            f.run(&[b"JSON.SET", b"doc", b"$.nope.made", b"1"]),
14168            "$-1\r\n"
14169        );
14170        assert_eq!(f.run(&[b"JSON.SET", b"doc", b"$.s.made", b"1"]), "$-1\r\n");
14171        // An index past the end does not append. JSON.ARRAPPEND appends.
14172        assert_eq!(
14173            f.run(&[b"JSON.SET", b"doc", b"$.arr[5]", b"9"]),
14174            "-ERR array index out of range\r\n"
14175        );
14176        assert_eq!(
14177            f.run(&[b"JSON.SET", b"doc", b"$.arr[2]", b"9"]),
14178            "-ERR array index out of range\r\n"
14179        );
14180        assert_eq!(f.run(&[b"JSON.SET", b"doc", b"$.arr[1]", b"9"]), "+OK\r\n");
14181        // NX on a path that is there and XX on a path that is not are both a
14182        // nil and neither changes anything.
14183        assert_eq!(
14184            f.run(&[b"JSON.SET", b"doc", b"$.o.made", b"2", b"NX"]),
14185            "$-1\r\n"
14186        );
14187        assert_eq!(
14188            f.run(&[b"JSON.SET", b"doc", b"$.gone", b"2", b"XX"]),
14189            "$-1\r\n"
14190        );
14191        assert_eq!(
14192            f.run(&[b"JSON.GET", b"doc"]),
14193            bulk(r#"{"o":{"made":1},"s":"x","arr":[1,9]}"#)
14194        );
14195        // Text that is not JSON is refused before the key is touched. The
14196        // line has no `ERR` in front of it, which is this command's and not
14197        // every command's, and is in D-37.
14198        assert!(
14199            f.run(&[b"JSON.SET", b"doc", b"$.s", b"nope"])
14200                .starts_with("-this is not the start of a value")
14201        );
14202        assert_eq!(f.run(&[b"JSON.GET", b"doc", b".s"]), bulk("\"x\""));
14203    }
14204
14205    /// `JSON.DEL`, `JSON.TYPE`, `JSON.TOGGLE` and `JSON.CLEAR`, each of which
14206    /// answers a count or a word rather than text.
14207    #[test]
14208    fn the_json_commands_that_do_not_answer_text() {
14209        let mut f = Fixture::new();
14210        let doc = br#"{"a":1,"t":true,"o":{"x":1},"arr":[1,2],"f":1.5,"s":"x","n":null}"#;
14211        f.run(&[b"JSON.SET", b"doc", b"$", doc]);
14212
14213        assert_eq!(f.run(&[b"JSON.TYPE", b"doc"]), bulk("object"));
14214        assert_eq!(f.run(&[b"JSON.TYPE", b"doc", b".a"]), bulk("integer"));
14215        assert_eq!(f.run(&[b"JSON.TYPE", b"doc", b".f"]), bulk("number"));
14216        assert_eq!(
14217            f.run(&[b"JSON.TYPE", b"doc", b"$.a"]),
14218            format!("*1\r\n{}", bulk("integer"))
14219        );
14220        // The one place a legacy path that matched nothing is a nil rather than
14221        // an error, which lines up with a key that is not there.
14222        assert_eq!(f.run(&[b"JSON.TYPE", b"doc", b".nope"]), "$-1\r\n");
14223        assert_eq!(f.run(&[b"JSON.TYPE", b"nokey"]), "$-1\r\n");
14224
14225        // A boolean flips and answers the value it now has, as an integer on
14226        // one syntax and as the word on the other.
14227        assert_eq!(f.run(&[b"JSON.TOGGLE", b"doc", b"$.t"]), "*1\r\n:0\r\n");
14228        assert_eq!(f.run(&[b"JSON.TOGGLE", b"doc", b".t"]), bulk("true"));
14229        // Something that is not a boolean is a hole on one syntax and one
14230        // sentence covering both cases on the other.
14231        assert_eq!(f.run(&[b"JSON.TOGGLE", b"doc", b"$.a"]), "*1\r\n$-1\r\n");
14232        assert_eq!(
14233            f.run(&[b"JSON.TOGGLE", b"doc", b".a"]),
14234            "-ERR Path does not exist or not a bool\r\n"
14235        );
14236        assert_eq!(
14237            f.run(&[b"JSON.TOGGLE", b"doc", b".nope"]),
14238            "-ERR Path does not exist or not a bool\r\n"
14239        );
14240        assert_eq!(
14241            f.run(&[b"JSON.TOGGLE", b"nokey", b"$.a"]),
14242            "-ERR could not perform this operation on a key that doesn't exist\r\n"
14243        );
14244
14245        // Clearing empties containers and zeroes numbers and leaves everything
14246        // else alone, and counts only what it changed.
14247        assert_eq!(f.run(&[b"JSON.CLEAR", b"doc", b"$.s"]), ":0\r\n");
14248        assert_eq!(f.run(&[b"JSON.CLEAR", b"doc", b"$.*"]), ":4\r\n");
14249        assert_eq!(f.run(&[b"JSON.CLEAR", b"doc", b"$.*"]), ":0\r\n");
14250        assert_eq!(
14251            f.run(&[b"JSON.GET", b"doc"]),
14252            bulk(r#"{"a":0,"f":0,"n":null,"o":{},"s":"x","t":true,"arr":[]}"#)
14253        );
14254
14255        // Deleting counts what it removed, and deleting the root is deleting
14256        // the key.
14257        assert_eq!(f.run(&[b"JSON.DEL", b"doc", b"$.nope"]), ":0\r\n");
14258        assert_eq!(f.run(&[b"JSON.DEL", b"doc", b"$.a"]), ":1\r\n");
14259        // Deleting the last member of the root container deletes the key, the
14260        // same way popping the last element off a list does. It is a rule about
14261        // deleting and not about shape: a document written as an empty object
14262        // by JSON.SET stays, because nothing was removed from it.
14263        assert_eq!(f.run(&[b"JSON.FORGET", b"doc", b"$.*"]), ":6\r\n");
14264        assert_eq!(f.run(&[b"EXISTS", b"doc"]), ":0\r\n");
14265        assert_eq!(f.run(&[b"JSON.GET", b"doc"]), "$-1\r\n");
14266        assert_eq!(f.run(&[b"JSON.DEL", b"doc"]), ":0\r\n");
14267        assert_eq!(f.run(&[b"JSON.SET", b"empty", b"$", b"{}"]), "+OK\r\n");
14268        assert_eq!(f.run(&[b"EXISTS", b"empty"]), ":1\r\n");
14269        assert_eq!(f.run(&[b"JSON.GET", b"empty"]), bulk("{}"));
14270        assert_eq!(f.run(&[b"JSON.DEL", b"nokey"]), ":0\r\n");
14271    }
14272
14273    /// `JSON.GET` with more than one path, and with a layout.
14274    ///
14275    /// The wrapper the reply is built in is laid out too, so what a path
14276    /// matched starts one level in for a single JSONPath and two for one of
14277    /// several, and getting that wrong is the kind of thing only a byte for
14278    /// byte comparison catches.
14279    #[test]
14280    fn json_get_lays_out_the_wrapper_it_builds() {
14281        let mut f = Fixture::new();
14282        f.run(&[b"JSON.SET", b"doc", b"$", br#"{"a":1,"b":[1,{"c":2}]}"#]);
14283
14284        assert_eq!(
14285            f.run(&[b"JSON.GET", b"doc", b"$.a", b"$.b"]),
14286            bulk(r#"{"$.a":[1],"$.b":[[1,{"c":2}]]}"#)
14287        );
14288        // Legacy paths are not wrapped, even when there are several of them.
14289        assert_eq!(
14290            f.run(&[b"JSON.GET", b"doc", b".a", b".b"]),
14291            bulk(r#"{".a":1,".b":[1,{"c":2}]}"#)
14292        );
14293        let fmt: &[&[u8]] = &[b"INDENT", b"  ", b"NEWLINE", b"\n", b"SPACE", b" "];
14294        let mut one = vec![b"JSON.GET".as_slice(), b"doc"];
14295        one.extend_from_slice(fmt);
14296        one.push(b"$.b");
14297        assert_eq!(
14298            f.run(&one),
14299            bulk("[\n  [\n    1,\n    {\n      \"c\": 2\n    }\n  ]\n]")
14300        );
14301        let mut two = vec![b"JSON.GET".as_slice(), b"doc"];
14302        two.extend_from_slice(fmt);
14303        two.push(b"$.a");
14304        two.push(b"$.nope");
14305        assert_eq!(
14306            f.run(&two),
14307            bulk("{\n  \"$.a\": [\n    1\n  ],\n  \"$.nope\": []\n}")
14308        );
14309        // The options are read before the paths and in any order, and a
14310        // document with nothing to lay out is the same either way.
14311        let mut root = vec![b"JSON.GET".as_slice(), b"doc", b"SPACE", b" "];
14312        root.push(b".a");
14313        assert_eq!(f.run(&root), bulk("1"));
14314    }
14315
14316    /// `JSON.MGET`, which is the only command here that reads more than one key
14317    /// and so the only one whose answer has holes in it.
14318    #[test]
14319    fn json_mget_answers_once_per_key_whatever_is_under_them() {
14320        let mut f = Fixture::new();
14321        f.run(&[b"JSON.SET", b"one", b"$", br#"{"a":1}"#]);
14322        f.run(&[b"JSON.SET", b"two", b"$", br#"{"a":2}"#]);
14323        f.run(&[b"SET", b"plain", b"x"]);
14324        assert_eq!(
14325            f.run(&[b"JSON.MGET", b"one", b"two", b"$.a"]),
14326            format!("*2\r\n{}{}", bulk("[1]"), bulk("[2]"))
14327        );
14328        // A key that is not there and a key holding something else are both a
14329        // hole rather than an error, the way MGET treats a hash.
14330        assert_eq!(
14331            f.run(&[b"JSON.MGET", b"one", b"nokey", b"plain", b".a"]),
14332            format!("*3\r\n{}$-1\r\n$-1\r\n", bulk("1"))
14333        );
14334        // A legacy path that matched nothing is a hole too, because one bad
14335        // answer should not lose the others.
14336        assert_eq!(f.run(&[b"JSON.MGET", b"one", b".nope"]), "*1\r\n$-1\r\n");
14337    }
14338
14339    /// The four commands that ask how big something is, and the four different
14340    /// sets of answers they give for the same three failures.
14341    ///
14342    /// There is no pattern in this and there is no reading it off the
14343    /// documentation either. It was read off a running RedisJSON one line at a
14344    /// time, and it is written down here because the error text is what a client
14345    /// library branches on.
14346    #[test]
14347    fn the_json_commands_that_answer_a_size_disagree_about_every_failure() {
14348        let mut f = Fixture::new();
14349        let doc = br#"{"a":[1,2,3],"o":{"x":1,"y":2},"s":"hello","n":7}"#;
14350        f.run(&[b"JSON.SET", b"doc", b"$", doc]);
14351
14352        assert_eq!(f.run(&[b"JSON.ARRLEN", b"doc", b".a"]), ":3\r\n");
14353        assert_eq!(f.run(&[b"JSON.ARRLEN", b"doc", b"$.a"]), "*1\r\n:3\r\n");
14354        assert_eq!(f.run(&[b"JSON.OBJLEN", b"doc", b".o"]), ":2\r\n");
14355        assert_eq!(f.run(&[b"JSON.STRLEN", b"doc", b".s"]), ":5\r\n");
14356        assert_eq!(
14357            f.run(&[b"JSON.OBJKEYS", b"doc", b".o"]),
14358            format!("*2\r\n{}{}", bulk("x"), bulk("y"))
14359        );
14360        // A JSONPath answers one entry per match and a hole for a match of the
14361        // wrong kind, which is the one shape all four agree on.
14362        assert_eq!(
14363            f.run(&[b"JSON.ARRLEN", b"doc", b"$.*"]),
14364            "*4\r\n:3\r\n$-1\r\n$-1\r\n$-1\r\n"
14365        );
14366
14367        // A legacy path that matched nothing. Two of them are an error and two
14368        // of them are a nil, and the two errors do not use the same sentence.
14369        assert_eq!(
14370            f.run(&[b"JSON.ARRLEN", b"doc", b".nope"]),
14371            "-ERR Path does not exist\r\n"
14372        );
14373        assert_eq!(
14374            f.run(&[b"JSON.STRLEN", b"doc", b".nope"]),
14375            "-ERR Path does not exist\r\n"
14376        );
14377        assert_eq!(f.run(&[b"JSON.OBJLEN", b"doc", b".nope"]), "$-1\r\n");
14378        // A nil bulk and not an empty array, even though the answer would have
14379        // been an array, which is what RedisJSON sends here too.
14380        assert_eq!(f.run(&[b"JSON.OBJKEYS", b"doc", b".nope"]), "$-1\r\n");
14381        // The JSONPath spelling of the same question is an empty array, since
14382        // no match is not a failure on that syntax.
14383        assert_eq!(f.run(&[b"JSON.OBJKEYS", b"doc", b"$.nope"]), "*0\r\n");
14384
14385        // A legacy path that matched the wrong kind of value. Now two of them
14386        // are an ERR and two of them are a WRONGTYPE, and it is not the same
14387        // two.
14388        assert_eq!(
14389            f.run(&[b"JSON.ARRLEN", b"doc", b".n"]),
14390            "-ERR Path does not exist or not an array\r\n"
14391        );
14392        assert_eq!(
14393            f.run(&[b"JSON.OBJKEYS", b"doc", b".n"]),
14394            "-ERR Path does not exist or not an object\r\n"
14395        );
14396        assert_eq!(
14397            f.run(&[b"JSON.OBJLEN", b"doc", b".n"]),
14398            "-WRONGTYPE wrong type of path value - expected object\r\n"
14399        );
14400        assert_eq!(
14401            f.run(&[b"JSON.STRLEN", b"doc", b".n"]),
14402            "-WRONGTYPE wrong type of path value - expected string\r\n"
14403        );
14404
14405        // A key that is not there, where the two syntaxes swap over: the legacy
14406        // path is the quiet answer and the JSONPath is the error.
14407        assert_eq!(f.run(&[b"JSON.ARRLEN", b"nokey", b".a"]), "$-1\r\n");
14408        assert_eq!(f.run(&[b"JSON.OBJLEN", b"nokey", b".a"]), "$-1\r\n");
14409        assert_eq!(f.run(&[b"JSON.STRLEN", b"nokey", b".a"]), "$-1\r\n");
14410        assert_eq!(f.run(&[b"JSON.OBJKEYS", b"nokey", b".a"]), "$-1\r\n");
14411        assert_eq!(
14412            f.run(&[b"JSON.ARRLEN", b"nokey", b"$.a"]),
14413            "-ERR could not perform this operation on a key that doesn't exist\r\n"
14414        );
14415        // Except this one, which answers about the path instead.
14416        assert_eq!(
14417            f.run(&[b"JSON.OBJLEN", b"nokey", b"$.a"]),
14418            "-ERR Path does not exist or not an object\r\n"
14419        );
14420    }
14421
14422    /// `JSON.ARRAPPEND`, `JSON.ARRINSERT`, `JSON.ARRTRIM` and `JSON.ARRPOP`.
14423    ///
14424    /// The four of them share one error line for a path that named something
14425    /// that is not an array, and they disagree about what an index outside the
14426    /// array means: insert refuses it and the other two clamp.
14427    #[test]
14428    fn the_json_array_writes_agree_on_the_errors_and_not_on_the_indexes() {
14429        let mut f = Fixture::new();
14430        f.run(&[b"JSON.SET", b"doc", b"$", br#"{"a":[1,2,3],"n":7}"#]);
14431
14432        assert_eq!(f.run(&[b"JSON.ARRAPPEND", b"doc", b".a", b"4"]), ":4\r\n");
14433        assert_eq!(
14434            f.run(&[b"JSON.ARRAPPEND", b"doc", b"$.a", b"5", b"6"]),
14435            "*1\r\n:6\r\n"
14436        );
14437        assert_eq!(f.run(&[b"JSON.GET", b"doc", b".a"]), bulk("[1,2,3,4,5,6]"));
14438
14439        // A negative index counts back from the end, and the end itself is a
14440        // place to insert at, so an insert at the length is an append.
14441        assert_eq!(
14442            f.run(&[b"JSON.ARRINSERT", b"doc", b".a", b"-1", b"0"]),
14443            ":7\r\n"
14444        );
14445        assert_eq!(
14446            f.run(&[b"JSON.GET", b"doc", b".a"]),
14447            bulk("[1,2,3,4,5,0,6]")
14448        );
14449        assert_eq!(
14450            f.run(&[b"JSON.ARRINSERT", b"doc", b".a", b"7", b"9"]),
14451            ":8\r\n"
14452        );
14453        // One past the end is not, and neither is one before the front.
14454        assert_eq!(
14455            f.run(&[b"JSON.ARRINSERT", b"doc", b".a", b"9", b"9"]),
14456            "-ERR index out of bounds\r\n"
14457        );
14458        assert_eq!(
14459            f.run(&[b"JSON.ARRINSERT", b"doc", b".a", b"-9", b"9"]),
14460            "-ERR index out of bounds\r\n"
14461        );
14462
14463        // Trim takes both ends inclusive and clamps both of them, so a start
14464        // past the end leaves an empty array rather than an error.
14465        f.run(&[b"JSON.SET", b"doc", b"$.a", b"[1,2,3,4,5]"]);
14466        assert_eq!(
14467            f.run(&[b"JSON.ARRTRIM", b"doc", b".a", b"1", b"3"]),
14468            ":3\r\n"
14469        );
14470        assert_eq!(f.run(&[b"JSON.GET", b"doc", b".a"]), bulk("[2,3,4]"));
14471        assert_eq!(
14472            f.run(&[b"JSON.ARRTRIM", b"doc", b".a", b"-2", b"99"]),
14473            ":2\r\n"
14474        );
14475        assert_eq!(f.run(&[b"JSON.GET", b"doc", b".a"]), bulk("[3,4]"));
14476        assert_eq!(
14477            f.run(&[b"JSON.ARRTRIM", b"doc", b".a", b"9", b"9"]),
14478            ":0\r\n"
14479        );
14480        assert_eq!(f.run(&[b"JSON.GET", b"doc", b".a"]), bulk("[]"));
14481
14482        // Pop clamps as well, its default is the last element, and an empty
14483        // array pops a nil rather than failing.
14484        f.run(&[b"JSON.SET", b"doc", b"$.a", b"[1,2,3]"]);
14485        assert_eq!(f.run(&[b"JSON.ARRPOP", b"doc", b".a"]), bulk("3"));
14486        assert_eq!(f.run(&[b"JSON.ARRPOP", b"doc", b".a", b"0"]), bulk("1"));
14487        assert_eq!(f.run(&[b"JSON.ARRPOP", b"doc", b".a", b"99"]), bulk("2"));
14488        assert_eq!(f.run(&[b"JSON.ARRPOP", b"doc", b".a"]), "$-1\r\n");
14489
14490        // One sentence covers a path that matched nothing and a path that
14491        // matched the wrong kind of value, for all four of them.
14492        for call in [
14493            &[&b"JSON.ARRAPPEND"[..], b"doc", b"PATH", b"1"][..],
14494            &[&b"JSON.ARRTRIM"[..], b"doc", b"PATH", b"1", b"1"][..],
14495            &[&b"JSON.ARRPOP"[..], b"doc", b"PATH", b"1"][..],
14496            &[&b"JSON.ARRINSERT"[..], b"doc", b"PATH", b"0", b"1"][..],
14497        ] {
14498            for path in [&b".n"[..], &b".nope"[..]] {
14499                let args: Vec<&[u8]> = call
14500                    .iter()
14501                    .map(|a| if *a == b"PATH" { path } else { *a })
14502                    .collect();
14503                assert_eq!(
14504                    f.run(&args),
14505                    "-ERR Path does not exist or not an array\r\n",
14506                    "{} {}",
14507                    String::from_utf8_lossy(call[0]),
14508                    String::from_utf8_lossy(path)
14509                );
14510            }
14511        }
14512
14513        // A key that is not there is the same sentence for all four, on either
14514        // syntax, and it is about the key and not about the path.
14515        assert_eq!(
14516            f.run(&[b"JSON.ARRAPPEND", b"nokey", b".a", b"1"]),
14517            "-ERR could not perform this operation on a key that doesn't exist\r\n"
14518        );
14519        assert_eq!(
14520            f.run(&[b"JSON.ARRPOP", b"nokey", b"$.a"]),
14521            "-ERR could not perform this operation on a key that doesn't exist\r\n"
14522        );
14523
14524        // The values are parsed before the key is touched, so text that is not
14525        // JSON leaves the document alone.
14526        // Text that is not JSON is refused before the key is touched, and
14527        // the line has no `ERR` in front of it, which is D-37.
14528        assert!(
14529            f.run(&[b"JSON.ARRAPPEND", b"doc", b".a", b"nope"])
14530                .starts_with("-this is not the start of a value")
14531        );
14532        assert_eq!(f.run(&[b"JSON.GET", b"doc", b".a"]), bulk("[]"));
14533    }
14534
14535    /// `JSON.ARRINSERT` refuses the whole command when any one of the arrays a
14536    /// path matched cannot take the index, which is D-36.
14537    ///
14538    /// RedisJSON walks the matches, inserts into each one it can, and returns
14539    /// the error on the first one it cannot, leaving the earlier inserts in the
14540    /// document. A write here is one list of edits applied together, so either
14541    /// all of them happen or none of them do.
14542    #[test]
14543    fn json_arrinsert_is_all_or_nothing_across_the_matches() {
14544        let mut f = Fixture::new();
14545        let doc = br#"{"a":[1,2,3],"n":{"a":[9,8],"in":{"a":[1]}}}"#;
14546        f.run(&[b"JSON.SET", b"doc", b"$", doc]);
14547        assert_eq!(
14548            f.run(&[b"JSON.ARRINSERT", b"doc", b"$..a", b"-2", b"0"]),
14549            "-ERR index out of bounds\r\n"
14550        );
14551        assert_eq!(
14552            f.run(&[b"JSON.GET", b"doc"]),
14553            bulk(r#"{"a":[1,2,3],"n":{"a":[9,8],"in":{"a":[1]}}}"#)
14554        );
14555        // Every match can take the index, so every match gets it.
14556        assert_eq!(
14557            f.run(&[b"JSON.ARRINSERT", b"doc", b"$..a", b"0", b"0"]),
14558            "*3\r\n:4\r\n:3\r\n:2\r\n"
14559        );
14560        assert_eq!(
14561            f.run(&[b"JSON.GET", b"doc"]),
14562            bulk(r#"{"a":[0,1,2,3],"n":{"a":[0,9,8],"in":{"a":[0,1]}}}"#)
14563        );
14564    }
14565
14566    /// `JSON.ARRINDEX`, whose stop is exclusive and whose start clamps to the
14567    /// last element rather than to one past it.
14568    ///
14569    /// Both of those read like mistakes and both are what RedisJSON does. The
14570    /// start is the one that bites: a start of five into an array of four still
14571    /// looks at the fourth, so a search that should have run out of array comes
14572    /// back with an answer.
14573    #[test]
14574    fn json_arrindex_has_an_exclusive_stop_and_a_start_that_cannot_run_off_the_end() {
14575        let mut f = Fixture::new();
14576        f.run(&[b"JSON.SET", b"doc", b"$", br#"{"a":[1,2,3,1],"n":7}"#]);
14577
14578        assert_eq!(f.run(&[b"JSON.ARRINDEX", b"doc", b".a", b"2"]), ":1\r\n");
14579        assert_eq!(f.run(&[b"JSON.ARRINDEX", b"doc", b".a", b"9"]), ":-1\r\n");
14580        assert_eq!(
14581            f.run(&[b"JSON.ARRINDEX", b"doc", b"$.a", b"2"]),
14582            "*1\r\n:1\r\n"
14583        );
14584
14585        // Zero as the stop means the end rather than the front, so leaving it
14586        // off and passing it are the same thing.
14587        assert_eq!(
14588            f.run(&[b"JSON.ARRINDEX", b"doc", b".a", b"1", b"1", b"0"]),
14589            ":3\r\n"
14590        );
14591        // The stop is exclusive, so a stop of three does not look at index
14592        // three.
14593        assert_eq!(
14594            f.run(&[b"JSON.ARRINDEX", b"doc", b".a", b"1", b"1", b"3"]),
14595            ":-1\r\n"
14596        );
14597
14598        // The start clamps to the last element in both directions, which is why
14599        // a start of four, five or minus one all find the 1 at index three.
14600        for start in [&b"4"[..], &b"5"[..], &b"-1"[..]] {
14601            assert_eq!(
14602                f.run(&[b"JSON.ARRINDEX", b"doc", b".a", b"1", start]),
14603                ":3\r\n",
14604                "{}",
14605                String::from_utf8_lossy(start)
14606            );
14607        }
14608        assert_eq!(
14609            f.run(&[b"JSON.ARRINDEX", b"doc", b".a", b"1", b"-100"]),
14610            ":0\r\n"
14611        );
14612        // An empty array is the one case that comes back with nothing, since
14613        // the stop is zero and the loop never starts.
14614        f.run(&[b"JSON.SET", b"doc", b"$.a", b"[]"]);
14615        assert_eq!(
14616            f.run(&[b"JSON.ARRINDEX", b"doc", b".a", b"1", b"1"]),
14617            ":-1\r\n"
14618        );
14619
14620        // The comparison is structural rather than one of the encoded bytes,
14621        // because an object in a stored document holds its keys as intern table
14622        // ids where one parsed off the wire holds them as bytes.
14623        f.run(&[b"JSON.SET", b"doc", b"$.a", br#"[{"k":1},[1,2],"s"]"#]);
14624        assert_eq!(
14625            f.run(&[b"JSON.ARRINDEX", b"doc", b".a", br#"{"k":1}"#]),
14626            ":0\r\n"
14627        );
14628        assert_eq!(
14629            f.run(&[b"JSON.ARRINDEX", b"doc", b".a", b"[1,2]"]),
14630            ":1\r\n"
14631        );
14632        assert_eq!(
14633            f.run(&[b"JSON.ARRINDEX", b"doc", b".a", b"[2,1]"]),
14634            ":-1\r\n"
14635        );
14636
14637        // Its errors are a third set again: a missing legacy path is the short
14638        // sentence, the wrong kind of value is a WRONGTYPE, and a key that is
14639        // not there is about the path on either syntax.
14640        assert_eq!(
14641            f.run(&[b"JSON.ARRINDEX", b"doc", b".nope", b"1"]),
14642            "-ERR Path does not exist\r\n"
14643        );
14644        assert_eq!(
14645            f.run(&[b"JSON.ARRINDEX", b"doc", b".n", b"1"]),
14646            "-WRONGTYPE wrong type of path value - expected array\r\n"
14647        );
14648        assert_eq!(
14649            f.run(&[b"JSON.ARRINDEX", b"nokey", b".a", b"1"]),
14650            "-ERR Path does not exist\r\n"
14651        );
14652        assert_eq!(
14653            f.run(&[b"JSON.ARRINDEX", b"nokey", b"$.a", b"1"]),
14654            "-ERR Path does not exist\r\n"
14655        );
14656    }
14657
14658    /// The number family answers text and keeps an integer an integer until
14659    /// something in the sum is not one.
14660    #[test]
14661    fn the_json_number_family_answers_json_text_and_keeps_its_integers() {
14662        let mut f = Fixture::new();
14663        let doc = br#"{"i":7,"f":1.5,"neg":-2,"s":"ab"}"#;
14664        f.run(&[b"JSON.SET", b"doc", b"$", doc]);
14665
14666        // A legacy path answers the new value as JSON text in a bulk string,
14667        // not as a number, which is the shape all three of them use.
14668        assert_eq!(
14669            f.run(&[b"JSON.NUMINCRBY", b"doc", b".i", b"2"]),
14670            bulk("9").as_str()
14671        );
14672        // A JSONPath answers a bulk string holding a JSON array.
14673        assert_eq!(
14674            f.run(&[b"JSON.NUMINCRBY", b"doc", b"$.i", b"2"]),
14675            bulk("[11]").as_str()
14676        );
14677        // Two integers stay an integer and a double anywhere in it makes the
14678        // answer a double, which the document then holds.
14679        assert_eq!(
14680            f.run(&[b"JSON.NUMINCRBY", b"doc", b".i", b"2.0"]),
14681            bulk("13.0").as_str()
14682        );
14683        assert_eq!(
14684            f.run(&[b"JSON.TYPE", b"doc", b".i"]),
14685            bulk("number").as_str()
14686        );
14687        assert_eq!(
14688            f.run(&[b"JSON.NUMMULTBY", b"doc", b".f", b"2"]),
14689            bulk("3.0").as_str()
14690        );
14691        assert_eq!(
14692            f.run(&[b"JSON.NUMPOWBY", b"doc", b".neg", b"3"]),
14693            bulk("-8").as_str()
14694        );
14695        // A power of a half is a square root, and the square root of a negative
14696        // number is the error that says the answer is not a number.
14697        f.run(&[b"JSON.SET", b"doc", b"$.f", b"1.5"]);
14698        assert_eq!(
14699            f.run(&[b"JSON.NUMPOWBY", b"doc", b".f", b"0.5"]),
14700            bulk("1.224744871391589").as_str()
14701        );
14702        assert_eq!(
14703            f.run(&[b"JSON.NUMPOWBY", b"doc", b".neg", b"0.5"]),
14704            "-ERR result is not a number\r\n"
14705        );
14706        // An integer answer that does not fit is refused rather than promoted,
14707        // and a negative exponent lands in the same error because there is no
14708        // integer answer to two to the minus one.
14709        f.run(&[b"JSON.SET", b"doc", b"$.big", b"9223372036854775807"]);
14710        assert_eq!(
14711            f.run(&[b"JSON.NUMINCRBY", b"doc", b".big", b"1"]),
14712            "-ERR numeric overflow\r\n"
14713        );
14714        f.run(&[b"JSON.SET", b"doc", b"$.p", b"2"]);
14715        assert_eq!(
14716            f.run(&[b"JSON.NUMPOWBY", b"doc", b".p", b"-1"]),
14717            "-ERR numeric overflow\r\n"
14718        );
14719        // A double that leaves the finite numbers is the other error.
14720        f.run(&[b"JSON.SET", b"doc", b"$.huge", b"1e308"]);
14721        assert_eq!(
14722            f.run(&[b"JSON.NUMMULTBY", b"doc", b".huge", b"1e10"]),
14723            "-ERR result is not a number\r\n"
14724        );
14725
14726        // A match that is not a number is a null inside the array on a
14727        // JSONPath, and a legacy path that found no number at all is the error
14728        // with the module's own typo in it.
14729        assert_eq!(
14730            f.run(&[b"JSON.NUMINCRBY", b"doc", b"$.s", b"1"]),
14731            bulk("[null]").as_str()
14732        );
14733        assert_eq!(
14734            f.run(&[b"JSON.NUMINCRBY", b"doc", b"$.nope", b"1"]),
14735            bulk("[]").as_str()
14736        );
14737        assert_eq!(
14738            f.run(&[b"JSON.NUMINCRBY", b"doc", b".s", b"1"]),
14739            "-ERR Path does not exist or does not contains a number\r\n"
14740        );
14741        assert_eq!(
14742            f.run(&[b"JSON.NUMINCRBY", b"doc", b".nope", b"1"]),
14743            "-ERR Path does not exist or does not contains a number\r\n"
14744        );
14745        // The operand is JSON and has to be a number. Valid JSON that is not
14746        // one is a line of its own, and it goes out without a prefix.
14747        assert_eq!(
14748            f.run(&[b"JSON.NUMINCRBY", b"doc", b".i", b"true"]),
14749            "-bad input number\r\n"
14750        );
14751        assert_eq!(
14752            f.run(&[b"JSON.NUMINCRBY", b"nokey", b".i", b"1"]),
14753            "-ERR could not perform this operation on a key that doesn't exist\r\n"
14754        );
14755        assert_eq!(
14756            f.run(&[b"JSON.NUMINCRBY", b"nokey", b"$.i", b"1"]),
14757            "-ERR could not perform this operation on a key that doesn't exist\r\n"
14758        );
14759    }
14760
14761    /// `JSON.STRAPPEND` puts its path in the middle and makes it optional,
14762    /// which nothing else in the group does.
14763    #[test]
14764    fn json_strappend_reads_its_shape_off_the_argument_count() {
14765        let mut f = Fixture::new();
14766        f.run(&[b"JSON.SET", b"doc", b"$", br#"{"s":"ab","n":1}"#]);
14767
14768        assert_eq!(
14769            f.run(&[b"JSON.STRAPPEND", b"doc", b".s", br#""c""#]),
14770            ":3\r\n"
14771        );
14772        assert_eq!(
14773            f.run(&[b"JSON.STRAPPEND", b"doc", b"$.s", br#""d""#]),
14774            "*1\r\n:4\r\n"
14775        );
14776        // The length is in bytes and not in characters, so one two byte letter
14777        // takes it up by two.
14778        assert_eq!(
14779            f.run(&[b"JSON.STRAPPEND", b"doc", b".s", br#""\u00e9""#]),
14780            ":6\r\n"
14781        );
14782        // Three arguments means the value is the last one and the path is the
14783        // root, so this appends to a document that is a string on its own.
14784        f.run(&[b"JSON.SET", b"str", b"$", br#""ab""#]);
14785        assert_eq!(f.run(&[b"JSON.STRAPPEND", b"str", br#""c""#]), ":3\r\n");
14786        assert_eq!(f.run(&[b"JSON.GET", b"str"]), bulk("\"abc\"").as_str());
14787
14788        // The value is JSON and has to be a JSON string. A number is a
14789        // WRONGTYPE about a path value even though it was the value that was
14790        // wrong, which is the module's wording and not a slip here.
14791        assert_eq!(
14792            f.run(&[b"JSON.STRAPPEND", b"doc", b".s", b"5"]),
14793            "-WRONGTYPE wrong type of path value - expected string\r\n"
14794        );
14795        assert_eq!(
14796            f.run(&[b"JSON.STRAPPEND", b"doc", b"$.n", br#""c""#]),
14797            "*1\r\n$-1\r\n"
14798        );
14799        assert_eq!(
14800            f.run(&[b"JSON.STRAPPEND", b"doc", b".n", br#""c""#]),
14801            "-ERR Path does not exist or not a string\r\n"
14802        );
14803        assert_eq!(
14804            f.run(&[b"JSON.STRAPPEND", b"doc", b"$.nope", br#""c""#]),
14805            "*0\r\n"
14806        );
14807        assert_eq!(
14808            f.run(&[b"JSON.STRAPPEND", b"nokey", br#""c""#]),
14809            "-ERR could not perform this operation on a key that doesn't exist\r\n"
14810        );
14811    }
14812
14813    /// A legacy path can match more than one value, and which of them the one
14814    /// answer comes from is not the same choice twice.
14815    #[test]
14816    fn a_legacy_wildcard_write_touches_every_match_and_answers_only_one() {
14817        let mut f = Fixture::new();
14818        // Three arrays of one, two and three elements, which tells the first
14819        // match and the last match apart in a single command.
14820        let three = br#"{"a":[[7],[7,7],[7,7,7]]}"#;
14821
14822        f.run(&[b"JSON.SET", b"doc", b"$", three]);
14823        assert_eq!(
14824            f.run(&[b"JSON.ARRAPPEND", b"doc", b".a[*]", b"9"]),
14825            ":4\r\n"
14826        );
14827        f.run(&[b"JSON.SET", b"doc", b"$", three]);
14828        assert_eq!(
14829            f.run(&[b"JSON.ARRINSERT", b"doc", b".a[*]", b"0", b"9"]),
14830            ":2\r\n"
14831        );
14832        f.run(&[b"JSON.SET", b"doc", b"$", three]);
14833        assert_eq!(
14834            f.run(&[b"JSON.ARRTRIM", b"doc", b".a[*]", b"0", b"1"]),
14835            ":1\r\n"
14836        );
14837        f.run(&[b"JSON.SET", b"doc", b"$", br#"{"a":[[1,2,3],[4,5,6]]}"#]);
14838        assert_eq!(
14839            f.run(&[b"JSON.ARRPOP", b"doc", b".a[*]", b"0"]),
14840            bulk("1").as_str()
14841        );
14842        f.run(&[b"JSON.SET", b"doc", b"$", br#"{"a":[1,2,3]}"#]);
14843        assert_eq!(
14844            f.run(&[b"JSON.NUMINCRBY", b"doc", b".a[*]", b"10"]),
14845            bulk("13").as_str()
14846        );
14847        f.run(&[b"JSON.SET", b"doc", b"$", br#"{"a":["p","qq","rrr"]}"#]);
14848        assert_eq!(
14849            f.run(&[b"JSON.STRAPPEND", b"doc", b".a[*]", br#""z""#]),
14850            ":4\r\n"
14851        );
14852        f.run(&[b"JSON.SET", b"doc", b"$", br#"{"a":[true,false,true]}"#]);
14853        assert_eq!(
14854            f.run(&[b"JSON.TOGGLE", b"doc", b".a[*]"]),
14855            bulk("false").as_str()
14856        );
14857        // Every one of them wrote to all three matches, whichever one it chose
14858        // to answer about.
14859        assert_eq!(
14860            f.run(&[b"JSON.GET", b"doc", b".a"]),
14861            bulk("[false,true,false]").as_str()
14862        );
14863
14864        // A match of the wrong kind is skipped rather than being the answer, so
14865        // a path that found a string and then two arrays still answers.
14866        f.run(&[b"JSON.SET", b"doc", b"$", br#"{"a":["x",[1],[1,2]]}"#]);
14867        assert_eq!(
14868            f.run(&[b"JSON.ARRAPPEND", b"doc", b".a[*]", b"9"]),
14869            ":3\r\n"
14870        );
14871        assert_eq!(
14872            f.run(&[b"JSON.GET", b"doc", b".a"]),
14873            bulk(r#"["x",[1,9],[1,2,9]]"#).as_str()
14874        );
14875        // Nothing of the right kind anywhere is the error, and that is the only
14876        // case that is.
14877        f.run(&[b"JSON.SET", b"doc", b"$", br#"{"a":["x","y"]}"#]);
14878        assert_eq!(
14879            f.run(&[b"JSON.ARRAPPEND", b"doc", b".a[*]", b"9"]),
14880            "-ERR Path does not exist or not an array\r\n"
14881        );
14882        assert_eq!(
14883            f.run(&[b"JSON.TOGGLE", b"doc", b".a[*]"]),
14884            "-ERR Path does not exist or not a bool\r\n"
14885        );
14886        // The one array that was there and had nothing in it is an answer and
14887        // not a skip, so the pop answers about it rather than about the array
14888        // after it.
14889        f.run(&[b"JSON.SET", b"doc", b"$", br#"{"a":[[],[2,3]]}"#]);
14890        assert_eq!(f.run(&[b"JSON.ARRPOP", b"doc", b".a[*]"]), "$-1\r\n");
14891        assert_eq!(
14892            f.run(&[b"JSON.GET", b"doc", b".a"]),
14893            bulk("[[],[2]]").as_str()
14894        );
14895    }
14896
14897    /// A path that matched a value and something inside that value writes to
14898    /// both, which is what `$..` and a nested wildcard are for.
14899    #[test]
14900    fn a_write_reaches_a_match_that_sits_inside_another_match() {
14901        let mut f = Fixture::new();
14902        let nested = br#"{"a":[{"a":[7]},{"a":[7,7]}]}"#;
14903
14904        f.run(&[b"JSON.SET", b"doc", b"$", nested]);
14905        assert_eq!(
14906            f.run(&[b"JSON.ARRAPPEND", b"doc", b"$..a", b"9"]),
14907            "*3\r\n:3\r\n:2\r\n:3\r\n"
14908        );
14909        assert_eq!(
14910            f.run(&[b"JSON.GET", b"doc", b"$"]),
14911            bulk(r#"[{"a":[{"a":[7,9]},{"a":[7,7,9]},9]}]"#).as_str()
14912        );
14913
14914        // The same for a trim, where the outer array keeps the two elements the
14915        // inner writes landed in.
14916        f.run(&[b"JSON.SET", b"doc", b"$", nested]);
14917        assert_eq!(
14918            f.run(&[b"JSON.ARRTRIM", b"doc", b"$..a", b"0", b"0"]),
14919            "*3\r\n:1\r\n:1\r\n:1\r\n"
14920        );
14921        assert_eq!(
14922            f.run(&[b"JSON.GET", b"doc", b"$"]),
14923            bulk(r#"[{"a":[{"a":[7]}]}]"#).as_str()
14924        );
14925
14926        // And for a number, where the first match is the object the outer array
14927        // holds and only the two inside it are numbers.
14928        f.run(&[b"JSON.SET", b"doc", b"$", nested]);
14929        assert_eq!(
14930            f.run(&[b"JSON.NUMINCRBY", b"doc", b"$..a[0]", b"1"]),
14931            bulk("[null,8,8]").as_str()
14932        );
14933    }
14934
14935    /// The value a write is given is looked at only once the path has found
14936    /// something of the right kind to use it on.
14937    #[test]
14938    fn a_bad_operand_is_not_the_answer_when_the_path_found_nothing_to_use_it_on() {
14939        let mut f = Fixture::new();
14940        f.run(&[b"JSON.SET", b"doc", b"$", br#"{"n":7,"s":"t"}"#]);
14941
14942        // A string is not a number, so the path answers first and the `"x"` is
14943        // never looked at. Same for the value that is not JSON at all.
14944        assert_eq!(
14945            f.run(&[b"JSON.NUMINCRBY", b"doc", b"$.s", br#""x""#]),
14946            bulk("[null]").as_str()
14947        );
14948        assert_eq!(
14949            f.run(&[b"JSON.NUMINCRBY", b"doc", b"$.s", b"notjson"]),
14950            bulk("[null]").as_str()
14951        );
14952        assert_eq!(
14953            f.run(&[b"JSON.NUMINCRBY", b"doc", b"$.missing", b"notjson"]),
14954            bulk("[]").as_str()
14955        );
14956        assert_eq!(
14957            f.run(&[b"JSON.NUMINCRBY", b"doc", b".s", br#""x""#]),
14958            "-ERR Path does not exist or does not contains a number\r\n"
14959        );
14960        // A number match anywhere and the value is looked at after all.
14961        assert_eq!(
14962            f.run(&[b"JSON.NUMINCRBY", b"doc", b"$.n", br#""x""#]),
14963            "-bad input number\r\n"
14964        );
14965
14966        // JSON.STRAPPEND follows the same order with its own two answers.
14967        assert_eq!(
14968            f.run(&[b"JSON.STRAPPEND", b"doc", b"$.n", b"1"]),
14969            "*1\r\n$-1\r\n"
14970        );
14971        assert_eq!(
14972            f.run(&[b"JSON.STRAPPEND", b"doc", b".n", b"1"]),
14973            "-ERR Path does not exist or not a string\r\n"
14974        );
14975        assert_eq!(
14976            f.run(&[b"JSON.STRAPPEND", b"doc", b"$.s", b"1"]),
14977            "-WRONGTYPE wrong type of path value - expected string\r\n"
14978        );
14979
14980        // A key that is not there still comes before either of them.
14981        assert_eq!(
14982            f.run(&[b"JSON.NUMINCRBY", b"nope", b"$.a", br#""x""#]),
14983            "-ERR could not perform this operation on a key that doesn't exist\r\n"
14984        );
14985        assert_eq!(
14986            f.run(&[b"JSON.STRAPPEND", b"nope", b"$.a", b"1"]),
14987            "-ERR could not perform this operation on a key that doesn't exist\r\n"
14988        );
14989    }
14990
14991    /// RFC 7386 in one test: a null deletes, everything else merges, and a
14992    /// patch that is not an object replaces what it lands on.
14993    #[test]
14994    fn a_merge_patch_adds_replaces_and_deletes_in_one_write() {
14995        let mut f = Fixture::new();
14996
14997        // A key that is not there is created at the root, nulls and all,
14998        // because a deletion with nothing to delete is still what the client
14999        // sent.
15000        assert_eq!(
15001            f.run(&[b"JSON.MERGE", b"doc", b"$", br#"{"x":null,"y":1}"#]),
15002            "+OK\r\n"
15003        );
15004        assert_eq!(
15005            f.run(&[b"JSON.GET", b"doc", b"$"]),
15006            bulk(r#"[{"x":null,"y":1}]"#).as_str()
15007        );
15008
15009        // Onto something that is there, a null deletes the member of that name
15010        // and the rest is merged one level at a time.
15011        f.run(&[b"JSON.SET", b"doc", b"$", br#"{"a":{"b":1,"c":2},"d":3}"#]);
15012        assert_eq!(
15013            f.run(&[b"JSON.MERGE", b"doc", b"$", br#"{"a":{"b":null,"e":4}}"#]),
15014            "+OK\r\n"
15015        );
15016        assert_eq!(
15017            f.run(&[b"JSON.GET", b"doc", b"$"]),
15018            bulk(r#"[{"a":{"c":2,"e":4},"d":3}]"#).as_str()
15019        );
15020
15021        // A patch that is not an object replaces what it is merged onto.
15022        assert_eq!(f.run(&[b"JSON.MERGE", b"doc", b"$.a", b"[1,2]"]), "+OK\r\n");
15023        assert_eq!(
15024            f.run(&[b"JSON.GET", b"doc", b"$"]),
15025            bulk(r#"[{"a":[1,2],"d":3}]"#).as_str()
15026        );
15027
15028        // A patch object onto a value that is not an object starts from an
15029        // empty object, so this time the null has nothing to delete and is
15030        // dropped rather than stored.
15031        assert_eq!(
15032            f.run(&[b"JSON.MERGE", b"doc", b"$.d", br#"{"p":null,"q":9}"#]),
15033            "+OK\r\n"
15034        );
15035        assert_eq!(
15036            f.run(&[b"JSON.GET", b"doc", b"$"]),
15037            bulk(r#"[{"a":[1,2],"d":{"q":9}}]"#).as_str()
15038        );
15039
15040        // A member one level past the end of the document is created and keeps
15041        // its nulls, two levels past it is a write that did not happen, and a
15042        // path that would have to invent where it goes is the unprefixed line.
15043        assert_eq!(
15044            f.run(&[b"JSON.MERGE", b"doc", b"$.new", br#"{"z":null}"#]),
15045            "+OK\r\n"
15046        );
15047        assert_eq!(
15048            f.run(&[b"JSON.GET", b"doc", b"$.new"]),
15049            bulk(r#"[{"z":null}]"#).as_str()
15050        );
15051        assert_eq!(
15052            f.run(&[b"JSON.MERGE", b"doc", b"$.no.deep", b"1"]),
15053            "$-1\r\n"
15054        );
15055        assert_eq!(
15056            f.run(&[b"JSON.MERGE", b"doc", b"$.no.*", b"1"]),
15057            "-Err wrong static path\r\n"
15058        );
15059
15060        // A wildcard merges every match.
15061        f.run(&[b"JSON.SET", b"doc", b"$", br#"{"a":{"n":1},"b":{"n":2}}"#]);
15062        assert_eq!(
15063            f.run(&[b"JSON.MERGE", b"doc", b"$.*", br#"{"m":0}"#]),
15064            "+OK\r\n"
15065        );
15066        assert_eq!(
15067            f.run(&[b"JSON.GET", b"doc", b"$"]),
15068            bulk(r#"[{"a":{"m":0,"n":1},"b":{"m":0,"n":2}}]"#).as_str()
15069        );
15070
15071        // The three ways to get it wrong.
15072        assert_eq!(
15073            f.run(&[b"JSON.MERGE", b"doc", b"$", b"{}", b"more"]),
15074            "-ERR syntax error\r\n"
15075        );
15076        assert_eq!(
15077            f.run(&[b"JSON.MERGE", b"gone", b"$.a", b"1"]),
15078            "-ERR new objects must be created at the root\r\n"
15079        );
15080        f.run(&[b"SET", b"str", b"x"]);
15081        assert_eq!(
15082            f.run(&[b"JSON.MERGE", b"str", b"$", b"1"]),
15083            "-Existing key has wrong Redis type\r\n"
15084        );
15085    }
15086
15087    /// A descent is the one path that matches a value and something inside that
15088    /// same value, and the inner merge has to survive the outer one.
15089    #[test]
15090    fn a_merge_down_a_descent_keeps_what_the_inner_match_did() {
15091        let mut f = Fixture::new();
15092        f.run(&[b"JSON.SET", b"doc", b"$", br#"{"a":{"b":1},"c":[2]}"#]);
15093        assert_eq!(
15094            f.run(&[b"JSON.MERGE", b"doc", b"$..*", br#"{"m":1}"#]),
15095            "+OK\r\n"
15096        );
15097        // `a`, `a.b`, `c` and `c[0]` all match. `a.b` is merged first and `a` is
15098        // merged onto the result, so the `{"m":1}` written into `a.b` is still
15099        // there. Doing it the other way round would leave `{"a":{"b":1,"m":1}}`.
15100        assert_eq!(
15101            f.run(&[b"JSON.GET", b"doc", b"$"]),
15102            bulk(r#"[{"a":{"b":{"m":1},"m":1},"c":{"m":1}}]"#).as_str()
15103        );
15104
15105        // A deletion down the same path, which is the case where the inner
15106        // merge empties the object the outer one then copies.
15107        f.run(&[b"JSON.SET", b"doc", b"$", br#"{"a":{"b":1},"c":[2]}"#]);
15108        assert_eq!(
15109            f.run(&[b"JSON.MERGE", b"doc", b"$..*", br#"{"a":null}"#]),
15110            "+OK\r\n"
15111        );
15112        assert_eq!(
15113            f.run(&[b"JSON.GET", b"doc", b"$"]),
15114            bulk(r#"[{"a":{"b":{}},"c":{}}]"#).as_str()
15115        );
15116    }
15117
15118    /// A filter is a selector like any other, so every command that takes a path
15119    /// takes one, reads and writes alike.
15120    #[test]
15121    fn a_filter_path_reads_and_writes_the_members_it_keeps() {
15122        let mut f = Fixture::new();
15123        let doc = br#"{"book":[{"t":"a","p":8},{"t":"b","p":13},{"t":"c","p":9}],"cap":10}"#;
15124        f.run(&[b"JSON.SET", b"doc", b"$", doc]);
15125
15126        assert_eq!(
15127            f.run(&[b"JSON.GET", b"doc", b"$.book[?(@.p < 10)].t"]),
15128            bulk(r#"["a","c"]"#).as_str()
15129        );
15130        // `$` inside the expression is the document, so a member can be measured
15131        // against something that is not inside it.
15132        assert_eq!(
15133            f.run(&[b"JSON.GET", b"doc", b"$.book[?(@.p < $.cap)].t"]),
15134            bulk(r#"["a","c"]"#).as_str()
15135        );
15136        // The legacy syntax takes one too, and answers the first match.
15137        assert_eq!(
15138            f.run(&[b"JSON.GET", b"doc", b"book[?(@.p < 10)].t"]),
15139            bulk(r#""a""#).as_str()
15140        );
15141        assert_eq!(
15142            f.run(&[b"JSON.TYPE", b"doc", b"$.book[?(@.p > 10)]"]),
15143            "*1\r\n$6\r\nobject\r\n"
15144        );
15145
15146        // A write goes through it as far as a value that is already there. A
15147        // field that is not there yet has nowhere definite to go, which is the
15148        // same refusal a wildcard gets.
15149        assert_eq!(
15150            f.run(&[b"JSON.NUMINCRBY", b"doc", b"$.book[?(@.p < 10)].p", b"1"]),
15151            bulk("[9,10]").as_str()
15152        );
15153        assert_eq!(
15154            f.run(&[b"JSON.SET", b"doc", b"$.book[?(@.p == 13)].t", br#""B""#]),
15155            "+OK\r\n"
15156        );
15157        assert_eq!(
15158            f.run(&[b"JSON.SET", b"doc", b"$.book[?(@.p == 13)].n", b"1"]),
15159            "-Err wrong static path\r\n"
15160        );
15161        assert_eq!(
15162            f.run(&[b"JSON.DEL", b"doc", b"$.book[?(@.p > 9)]"]),
15163            ":2\r\n"
15164        );
15165        assert_eq!(
15166            f.run(&[b"JSON.GET", b"doc", b"$"]),
15167            bulk(r#"[{"cap":10,"book":[{"p":9,"t":"a"}]}]"#).as_str()
15168        );
15169
15170        // A path that does not parse is refused before the document is read, so
15171        // a key that is not there answers the same way.
15172        assert!(
15173            f.run(&[b"JSON.GET", b"doc", b"$.book[?(@.p <)]"])
15174                .starts_with("-ERR")
15175        );
15176        assert!(
15177            f.run(&[b"JSON.GET", b"nokey", b"$.book[?(@.p <)]"])
15178                .starts_with("-ERR")
15179        );
15180    }
15181
15182    /// The operators past the comparisons, over the wire rather than in the
15183    /// parser's own tests, so that a client can reach all of them.
15184    #[test]
15185    fn a_filter_takes_the_membership_operators_and_the_methods_too() {
15186        let mut f = Fixture::new();
15187        let doc = br#"{"box":[{"t":"a","n":[1,2],"g":"x"},{"t":"b","n":[9],"g":"y"}]}"#;
15188        f.run(&[b"JSON.SET", b"doc", b"$", doc]);
15189
15190        for (path, want) in [
15191            (&b"$.box[?(@.g in [\"x\"])].t"[..], r#"["a"]"#),
15192            (b"$.box[?(@.g nin [\"x\"])].t", r#"["b"]"#),
15193            (b"$.box[?(@.n anyof [2,3])].t", r#"["a"]"#),
15194            (b"$.box[?(@.n subsetof [1,2,3])].t", r#"["a"]"#),
15195            (b"$.box[?(@.n size 2)].t", r#"["a"]"#),
15196            (b"$.box[?(@.n empty false)].t", r#"["a","b"]"#),
15197            (b"$.box[?(@.n.length() == 1)].t", r#"["b"]"#),
15198            (b"$.box[?(@.n.sum() > 5)].t", r#"["b"]"#),
15199            (b"$.box[?(@.n[0] + 1 == 2)].t", r#"["a"]"#),
15200            (b"$.box[?(@~ size 3)].t", r#"["a","b"]"#),
15201            (b"$.box[?(@.n~)].t", "[]"),
15202            (b"$.box[?(@.n sizeof 2)].t", r#"["a"]"#),
15203            (b"$.box[?(-@.n[0] == -9)].t", r#"["b"]"#),
15204            (b"$.box[?(1 in @.n)].t", r#"["a"]"#),
15205            (b"$.box[?(\"g\" in @~)].t", r#"["a","b"]"#),
15206        ] {
15207            assert_eq!(f.run(&[b"JSON.GET", b"doc", path]), bulk(want).as_str());
15208        }
15209
15210        // A write goes through one of these the same way it goes through a
15211        // comparison.
15212        assert_eq!(
15213            f.run(&[b"JSON.SET", b"doc", b"$.box[?(@.n size 1)].g", br#""z""#]),
15214            "+OK\r\n"
15215        );
15216        assert_eq!(
15217            f.run(&[b"JSON.GET", b"doc", b"$.box[?(@.g == \"z\")].t"]),
15218            bulk(r#"["b"]"#).as_str()
15219        );
15220    }
15221
15222    /// D-41. RedisJSON refuses this one, and which document it refuses is
15223    /// decided by how it happens to hold an array of numbers.
15224    #[test]
15225    fn a_merge_onto_a_number_inside_an_array_is_a_merge_and_not_an_error() {
15226        let mut f = Fixture::new();
15227        f.run(&[b"JSON.SET", b"doc", b"$", br#"{"a":[1,2]}"#]);
15228        assert_eq!(
15229            f.run(&[b"JSON.MERGE", b"doc", b"$.a[0]", br#"{"x":1}"#]),
15230            "+OK\r\n"
15231        );
15232        assert_eq!(
15233            f.run(&[b"JSON.GET", b"doc", b"$"]),
15234            bulk(r#"[{"a":[{"x":1},2]}]"#).as_str()
15235        );
15236        // The same document with one element that is not an integer is the one
15237        // RedisJSON is happy with, and it goes the same way here.
15238        f.run(&[b"JSON.SET", b"doc", b"$", br#"{"a":[1,"s"]}"#]);
15239        assert_eq!(
15240            f.run(&[b"JSON.MERGE", b"doc", b"$.a[0]", br#"{"x":1}"#]),
15241            "+OK\r\n"
15242        );
15243        assert_eq!(
15244            f.run(&[b"JSON.GET", b"doc", b"$"]),
15245            bulk(r#"[{"a":[{"x":1},"s"]}]"#).as_str()
15246        );
15247    }
15248
15249    /// `JSON.MSET` checks what it can before it writes anything and skips the
15250    /// one thing it cannot, which is a path with nowhere to put its value.
15251    #[test]
15252    fn an_mset_writes_every_triple_it_can_and_checks_the_rest_up_front() {
15253        let mut f = Fixture::new();
15254        assert_eq!(
15255            f.run(&[b"JSON.MSET", b"a", b"$", b"1", b"b", b"$", b"2"]),
15256            "+OK\r\n"
15257        );
15258        assert_eq!(f.run(&[b"JSON.GET", b"a", b"$"]), bulk("[1]").as_str());
15259        assert_eq!(f.run(&[b"JSON.GET", b"b", b"$"]), bulk("[2]").as_str());
15260
15261        // A repeated key takes the last write.
15262        assert_eq!(
15263            f.run(&[b"JSON.MSET", b"a", b"$", b"3", b"a", b"$", b"4"]),
15264            "+OK\r\n"
15265        );
15266        assert_eq!(f.run(&[b"JSON.GET", b"a", b"$"]), bulk("[4]").as_str());
15267
15268        // A triple whose path names nowhere is skipped, the others are still
15269        // written and the reply turns into a nil. Both ways round, because a
15270        // loop that gave up at the first skip would agree with this on one
15271        // order and not on the other.
15272        f.run(&[b"JSON.SET", b"a", b"$", br#"{"n":1}"#]);
15273        assert_eq!(
15274            f.run(&[b"JSON.MSET", b"a", b"$.no.deep", b"9", b"b", b"$", b"5"]),
15275            "$-1\r\n"
15276        );
15277        assert_eq!(f.run(&[b"JSON.GET", b"b", b"$"]), bulk("[5]").as_str());
15278        assert_eq!(
15279            f.run(&[b"JSON.MSET", b"b", b"$", b"6", b"a", b"$.no.deep", b"9"]),
15280            "$-1\r\n"
15281        );
15282        assert_eq!(f.run(&[b"JSON.GET", b"b", b"$"]), bulk("[6]").as_str());
15283
15284        // A value that is not JSON, a key holding something else and a path
15285        // that would have to create a document below its own root are all
15286        // checked before anything is written, so the good triple next to them
15287        // does not happen either.
15288        f.run(&[b"SET", b"str", b"x"]);
15289        assert_eq!(
15290            f.run(&[b"JSON.MSET", b"a", b"$.n", b"7", b"b", b"$", b"notjson"]),
15291            "-this is not the start of a value, at byte 0 of the JSON text\r\n"
15292        );
15293        assert_eq!(
15294            f.run(&[b"JSON.MSET", b"a", b"$.n", b"7", b"str", b"$", b"1"]),
15295            "-Existing key has wrong Redis type\r\n"
15296        );
15297        assert_eq!(
15298            f.run(&[b"JSON.MSET", b"a", b"$.n", b"7", b"gone", b"$.x", b"1"]),
15299            "-ERR new objects must be created at the root\r\n"
15300        );
15301        assert_eq!(f.run(&[b"JSON.GET", b"a", b"$.n"]), bulk("[1]").as_str());
15302
15303        // The two errors a path can be are checked up front as well, so the
15304        // triple before them is not written either. A wildcard that matched
15305        // nothing has nowhere to invent, and an index that is not in the array
15306        // is out of range, and both of them stop the whole command.
15307        assert_eq!(
15308            f.run(&[b"JSON.MSET", b"b", b"$", b"8", b"a", b"$.no.*", b"9"]),
15309            "-Err wrong static path\r\n"
15310        );
15311        assert_eq!(
15312            f.run(&[b"JSON.MSET", b"b", b"$", b"8", b"a", b"$[0]", b"9"]),
15313            "-ERR array index out of range\r\n"
15314        );
15315        assert_eq!(f.run(&[b"JSON.GET", b"b", b"$"]), bulk("[6]").as_str());
15316
15317        // Every triple is worked out against the keyspace as the command found
15318        // it, so a second triple on the same key does not see the first one and
15319        // the last write is the one that stays.
15320        f.run(&[b"JSON.SET", b"c", b"$", br#"{"n":1}"#]);
15321        assert_eq!(
15322            f.run(&[b"JSON.MSET", b"c", b"$", br#"{"n":2}"#, b"c", b"$.n", b"3"]),
15323            "+OK\r\n"
15324        );
15325        assert_eq!(
15326            f.run(&[b"JSON.GET", b"c", b"$"]),
15327            bulk(r#"[{"n":3}]"#).as_str()
15328        );
15329
15330        // An argument count that is not a run of key, path and value is the
15331        // arity error rather than a syntax one.
15332        assert_eq!(
15333            f.run(&[b"JSON.MSET", b"a", b"$", b"1", b"b"]),
15334            "-ERR wrong number of arguments for 'json.mset' command\r\n"
15335        );
15336    }
15337
15338    /// `JSON.RESP` hands back RESP types, and the marker element is what tells
15339    /// an empty array and an empty object apart.
15340    #[test]
15341    fn json_resp_answers_the_document_as_resp_types() {
15342        let mut f = Fixture::new();
15343        f.run(&[b"JSON.SET", b"doc", b"$", br#"{"a":1,"b":[2,"c"]}"#]);
15344        assert_eq!(
15345            f.run(&[b"JSON.RESP", b"doc"]),
15346            "*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"
15347        );
15348        // A JSONPath wraps the same answer in one more array.
15349        assert_eq!(
15350            f.run(&[b"JSON.RESP", b"doc", b"$.b"]),
15351            "*1\r\n*3\r\n+[\r\n:2\r\n$1\r\nc\r\n"
15352        );
15353
15354        f.run(&[
15355            b"JSON.SET",
15356            b"doc",
15357            b"$",
15358            br#"{"f":2.5,"t":true,"z":null,"e":[],"o":{}}"#,
15359        ]);
15360        assert_eq!(f.run(&[b"JSON.RESP", b"doc", b".e"]), "*1\r\n+[\r\n");
15361        assert_eq!(f.run(&[b"JSON.RESP", b"doc", b".o"]), "*1\r\n+{\r\n");
15362        // A double goes out as its text, so a client reads the same digits
15363        // `JSON.GET` would have given it.
15364        assert_eq!(f.run(&[b"JSON.RESP", b"doc", b".f"]), bulk("2.5").as_str());
15365        assert_eq!(f.run(&[b"JSON.RESP", b"doc", b".t"]), "+true\r\n");
15366        assert_eq!(f.run(&[b"JSON.RESP", b"doc", b".z"]), "$-1\r\n");
15367
15368        // A missing legacy path is an error, a missing JSONPath is an empty
15369        // array, and a key that is not there is a nil on either.
15370        assert_eq!(
15371            f.run(&[b"JSON.RESP", b"doc", b".nope"]),
15372            "-ERR Path does not exist\r\n"
15373        );
15374        assert_eq!(f.run(&[b"JSON.RESP", b"doc", b"$.nope"]), "*0\r\n");
15375        assert_eq!(f.run(&[b"JSON.RESP", b"gone"]), "$-1\r\n");
15376        assert_eq!(f.run(&[b"JSON.RESP", b"gone", b"$"]), "$-1\r\n");
15377    }
15378
15379    /// `JSON.DEBUG` answers a byte count that is this encoding's, so the test
15380    /// pins the shapes and that the two syntaxes agree rather than a number
15381    /// read off another server. That is D-42.
15382    #[test]
15383    fn json_debug_answers_a_byte_count_and_its_own_help() {
15384        let mut f = Fixture::new();
15385        f.run(&[b"JSON.SET", b"doc", b"$", br#"{"a":[1,2],"s":"hello"}"#]);
15386        let one = f.run(&[b"JSON.DEBUG", b"MEMORY", b"doc", b".s"]);
15387        assert!(one.starts_with(':'), "{one}");
15388        assert_eq!(
15389            f.run(&[b"JSON.DEBUG", b"memory", b"doc", b"$.s"]),
15390            format!("*1\r\n{one}")
15391        );
15392        let whole = f.run(&[b"JSON.DEBUG", b"MEMORY", b"doc"]);
15393        assert!(whole.starts_with(':') && whole.len() > one.len(), "{whole}");
15394
15395        // A key that is not there is a zero on a legacy path and an empty set
15396        // on a JSONPath, which is the one reader here that does not answer nil
15397        // for it.
15398        assert_eq!(f.run(&[b"JSON.DEBUG", b"MEMORY", b"gone"]), ":0\r\n");
15399        assert_eq!(f.run(&[b"JSON.DEBUG", b"MEMORY", b"gone", b"$"]), "*0\r\n");
15400        assert_eq!(
15401            f.run(&[b"JSON.DEBUG", b"MEMORY", b"doc", b".nope"]),
15402            "-ERR Path does not exist\r\n"
15403        );
15404        assert_eq!(
15405            f.run(&[b"JSON.DEBUG", b"MEMORY", b"doc", b"$.nope"]),
15406            "*0\r\n"
15407        );
15408
15409        assert_eq!(
15410            f.run(&[b"JSON.DEBUG", b"HELP"]),
15411            "*2\r\n$42\r\nMEMORY <key> [path] - reports memory usage\r\n\
15412             $34\r\nHELP                - this message\r\n"
15413        );
15414        assert_eq!(
15415            f.run(&[b"JSON.DEBUG", b"NOPE"]),
15416            "-ERR unknown subcommand - try `JSON.DEBUG HELP`\r\n"
15417        );
15418        assert_eq!(
15419            f.run(&[b"JSON.DEBUG", b"MEMORY"]),
15420            "-ERR wrong number of arguments for 'json.debug' command\r\n"
15421        );
15422    }
15423
15424    // ---------------------------------------------------------------- vector
15425
15426    /// The first `VADD` fixes the dimension and every one after it has to
15427    /// agree, because there is no create command to say it earlier.
15428    #[test]
15429    fn the_first_vadd_decides_how_wide_the_set_is() {
15430        let mut f = Fixture::new();
15431        assert_eq!(
15432            f.run(&[b"VADD", b"v", b"VALUES", b"2", b"1", b"0", b"east"]),
15433            ":1\r\n"
15434        );
15435        assert_eq!(f.run(&[b"VDIM", b"v"]), ":2\r\n");
15436        assert_eq!(f.run(&[b"VCARD", b"v"]), ":1\r\n");
15437        // A second vector under the same name replaces it and says so with a
15438        // zero, so an ingest can count what it created.
15439        assert_eq!(
15440            f.run(&[b"VADD", b"v", b"VALUES", b"2", b"0", b"1", b"east"]),
15441            ":0\r\n"
15442        );
15443        assert_eq!(f.run(&[b"VCARD", b"v"]), ":1\r\n");
15444        // Three dimensions into a two dimensional set names both numbers, since
15445        // a client that gets this wrong needs to know which end is which.
15446        assert_eq!(
15447            f.run(&[b"VADD", b"v", b"VALUES", b"3", b"1", b"0", b"0", b"up"]),
15448            "-ERR Vector dimension mismatch - got 3 but set has 2\r\n"
15449        );
15450        // A vector of zeros has no direction, and it is taken anyway and comes
15451        // back as the origin, because that is what a real server does with it.
15452        assert_eq!(
15453            f.run(&[b"VADD", b"v", b"VALUES", b"2", b"0", b"0", b"nowhere"]),
15454            ":1\r\n"
15455        );
15456        assert_eq!(
15457            f.run(&[b"VEMB", b"v", b"nowhere"]),
15458            "*2\r\n$1\r\n0\r\n$1\r\n0\r\n"
15459        );
15460        // A set is made with one quantisation and keeps it, and a `VADD` that
15461        // names another is refused. Naming none names `Q8`, which is why this
15462        // set is a `Q8` one.
15463        assert_eq!(
15464            f.run(&[b"VADD", b"v", b"VALUES", b"2", b"1", b"1", b"other", b"BIN"]),
15465            "-ERR asked quantization mismatch with existing vector set\r\n"
15466        );
15467        // Nothing above created a key, and a set that never took a vector has
15468        // no dimension to report.
15469        assert_eq!(f.run(&[b"EXISTS", b"fresh"]), ":0\r\n");
15470        assert_eq!(f.run(&[b"VDIM", b"fresh"]), "-ERR key does not exist\r\n");
15471        assert_eq!(f.run(&[b"VCARD", b"fresh"]), ":0\r\n");
15472    }
15473
15474    /// What a client sent comes back out, and what a client asked for is a
15475    /// similarity and not the distance underneath it.
15476    #[test]
15477    fn vemb_gives_back_the_vector_and_vsim_gives_back_a_similarity() {
15478        let mut f = Fixture::new();
15479        f.run(&[b"VADD", b"v", b"VALUES", b"2", b"3", b"4", b"a"]);
15480        // The set stored the direction and the length is multiplied back on the
15481        // way out, so this is `3 4` and not `0.6 0.8`. It is not quite `3 4`
15482        // either, because nobody named a quantisation and that means `Q8`: the
15483        // wider coordinate lands on a code exactly and the other one does not.
15484        // Both numbers are a real server's answers for the same input.
15485        assert_eq!(
15486            f.run(&[b"VEMB", b"v", b"a"]),
15487            "*2\r\n$17\r\n2.992125988006592\r\n$1\r\n4\r\n"
15488        );
15489        // NOQUANT is the way to ask for what went in to come back out.
15490        f.run(&[b"VADD", b"n", b"VALUES", b"2", b"3", b"4", b"a", b"NOQUANT"]);
15491        assert_eq!(
15492            f.run(&[b"VEMB", b"n", b"a"]),
15493            "*2\r\n$1\r\n3\r\n$1\r\n4\r\n"
15494        );
15495        // BIN keeps the signs and nothing else, and does not multiply the
15496        // length back on, since a sign has no length in it to scale.
15497        f.run(&[b"VADD", b"b", b"VALUES", b"2", b"3", b"-4", b"a", b"BIN"]);
15498        assert_eq!(
15499            f.run(&[b"VEMB", b"b", b"a"]),
15500            "*2\r\n$1\r\n1\r\n$2\r\n-1\r\n"
15501        );
15502        assert_eq!(f.run(&[b"VEMB", b"v", b"nobody"]), "*-1\r\n");
15503        assert_eq!(f.run(&[b"VEMB", b"nokey", b"a"]), "*-1\r\n");
15504
15505        // On the axes, where the unit vector is exact and so is the dot
15506        // product, both ends of the scale come out exact: the same direction is
15507        // 1 and the opposite one is 0, with a right angle at a half.
15508        let mut f = Fixture::new();
15509        f.run(&[b"VADD", b"v", b"VALUES", b"2", b"3", b"0", b"a"]);
15510        f.run(&[b"VADD", b"v", b"VALUES", b"2", b"-1", b"0", b"opposite"]);
15511        f.run(&[b"VADD", b"v", b"VALUES", b"2", b"0", b"7", b"across"]);
15512        assert_eq!(
15513            f.run(&[b"VSIM", b"v", b"VALUES", b"2", b"2", b"0", b"WITHSCORES"]),
15514            "*6\r\n$1\r\na\r\n$1\r\n1\r\n$6\r\nacross\r\n$3\r\n0.5\r\n\
15515             $8\r\nopposite\r\n$1\r\n0\r\n"
15516        );
15517        // A search from an element leaves that element out, since it is always
15518        // its own nearest neighbour.
15519        assert_eq!(
15520            f.run(&[b"VSIM", b"v", b"ELE", b"a"]),
15521            "*2\r\n$6\r\nacross\r\n$8\r\nopposite\r\n"
15522        );
15523        // An element that is not there is an empty answer and not an error,
15524        // which is what a missing key gives too.
15525        assert_eq!(f.run(&[b"VSIM", b"v", b"ELE", b"nobody"]), "*0\r\n");
15526        assert_eq!(f.run(&[b"VSIM", b"nokey", b"ELE", b"a"]), "*0\r\n");
15527        // COUNT bounds it and TRUTH reads every vector rather than the codes,
15528        // which has to agree with the index on a set this small.
15529        assert_eq!(
15530            f.run(&[b"VSIM", b"v", b"ELE", b"a", b"COUNT", b"1"]),
15531            "*1\r\n$6\r\nacross\r\n"
15532        );
15533        assert_eq!(
15534            f.run(&[b"VSIM", b"v", b"ELE", b"a", b"TRUTH"]),
15535            "*2\r\n$6\r\nacross\r\n$8\r\nopposite\r\n"
15536        );
15537        // EF widens how much of the index is read and does not change how many
15538        // answers come back, so a wide search still returns what COUNT asked
15539        // for.
15540        assert_eq!(
15541            f.run(&[b"VSIM", b"v", b"ELE", b"a", b"COUNT", b"1", b"EF", b"500"]),
15542            "*1\r\n$6\r\nacross\r\n"
15543        );
15544
15545        // On RESP3 a scored search is a map, which is what the vector set
15546        // module replies and is not what ZRANGE does here.
15547        let mut g = Fixture::new();
15548        g.run(&[b"HELLO", b"3"]);
15549        g.run(&[b"VADD", b"v", b"VALUES", b"2", b"1", b"0", b"east"]);
15550        assert_eq!(
15551            g.run(&[b"VSIM", b"v", b"VALUES", b"2", b"1", b"0", b"WITHSCORES"]),
15552            "%1\r\n$4\r\neast\r\n,1\r\n"
15553        );
15554    }
15555
15556    /// The attribute pair, and the one reply that means two things.
15557    #[test]
15558    fn an_attribute_is_bytes_and_an_empty_one_takes_it_off() {
15559        let mut f = Fixture::new();
15560        f.run(&[b"VADD", b"v", b"VALUES", b"2", b"1", b"0", b"east"]);
15561        assert_eq!(f.run(&[b"VGETATTR", b"v", b"east"]), "$-1\r\n");
15562        assert_eq!(f.run(&[b"VSETATTR", b"v", b"east", b"{\"k\":1}"]), ":1\r\n");
15563        assert_eq!(f.run(&[b"VGETATTR", b"v", b"east"]), "$7\r\n{\"k\":1}\r\n");
15564        // Not parsed as JSON, because nothing reads into it yet and refusing a
15565        // write for a rule nothing enforces would be the wrong trade.
15566        assert_eq!(f.run(&[b"VSETATTR", b"v", b"east", b"not json"]), ":1\r\n");
15567        assert_eq!(f.run(&[b"VGETATTR", b"v", b"east"]), "$8\r\nnot json\r\n");
15568        // An empty string clears it, which is Redis's spelling of the removal.
15569        assert_eq!(f.run(&[b"VSETATTR", b"v", b"east", b""]), ":1\r\n");
15570        assert_eq!(f.run(&[b"VGETATTR", b"v", b"east"]), "$-1\r\n");
15571        // An element that is not there answers zero rather than being created,
15572        // since an attribute with no vector under it is not a thing this holds.
15573        assert_eq!(f.run(&[b"VSETATTR", b"v", b"nobody", b"{}"]), ":0\r\n");
15574        assert_eq!(f.run(&[b"VSETATTR", b"nokey", b"east", b"{}"]), ":0\r\n");
15575        assert_eq!(f.run(&[b"EXISTS", b"nokey"]), ":0\r\n");
15576        // A null for an element with no attribute and a null for one that is
15577        // not there. VISMEMBER is how a client tells the two apart.
15578        assert_eq!(f.run(&[b"VGETATTR", b"v", b"nobody"]), "$-1\r\n");
15579        assert_eq!(f.run(&[b"VISMEMBER", b"v", b"east"]), ":1\r\n");
15580        assert_eq!(f.run(&[b"VISMEMBER", b"v", b"nobody"]), ":0\r\n");
15581        assert_eq!(f.run(&[b"VISMEMBER", b"nokey", b"east"]), ":0\r\n");
15582
15583        // WITHATTRIBS carries it alongside the answers.
15584        f.run(&[b"VSETATTR", b"v", b"east", b"{\"k\":1}"]);
15585        f.run(&[b"VADD", b"v", b"VALUES", b"2", b"0", b"1", b"north"]);
15586        assert_eq!(
15587            f.run(&[b"VSIM", b"v", b"VALUES", b"2", b"1", b"0", b"WITHATTRIBS"]),
15588            "*4\r\n$4\r\neast\r\n$7\r\n{\"k\":1}\r\n$5\r\nnorth\r\n$-1\r\n"
15589        );
15590    }
15591
15592    /// The slot a removed element had is reused, and nothing that was beside it
15593    /// comes back with the next element to get it.
15594    #[test]
15595    fn vrem_takes_the_attribute_with_it() {
15596        let mut f = Fixture::new();
15597        f.run(&[b"VADD", b"v", b"VALUES", b"2", b"1", b"0", b"east"]);
15598        f.run(&[b"VSETATTR", b"v", b"east", b"{\"k\":1}"]);
15599        assert_eq!(f.run(&[b"VREM", b"v", b"east"]), ":1\r\n");
15600        assert_eq!(f.run(&[b"VREM", b"v", b"east"]), ":0\r\n");
15601        assert_eq!(f.run(&[b"VREM", b"nokey", b"east"]), ":0\r\n");
15602        // The key went with the last element, the way every other collection
15603        // here works.
15604        assert_eq!(f.run(&[b"EXISTS", b"v"]), ":0\r\n");
15605
15606        // The next element is given the slot the removed one had, and it comes
15607        // with no attribute on it.
15608        f.run(&[b"VADD", b"v", b"VALUES", b"2", b"1", b"0", b"east"]);
15609        f.run(&[b"VSETATTR", b"v", b"east", b"{\"k\":1}"]);
15610        f.run(&[b"VADD", b"v", b"VALUES", b"2", b"0", b"1", b"north"]);
15611        f.run(&[b"VREM", b"v", b"east"]);
15612        f.run(&[b"VADD", b"v", b"VALUES", b"2", b"1", b"1", b"between"]);
15613        assert_eq!(f.run(&[b"VGETATTR", b"v", b"between"]), "$-1\r\n");
15614    }
15615
15616    /// `VINFO` says what the index is before it says anything a client could
15617    /// mistake for a graph.
15618    #[test]
15619    fn vinfo_says_partition_first() {
15620        let mut f = Fixture::new();
15621        f.run(&[
15622            b"VADD", b"v", b"VALUES", b"2", b"1", b"0", b"east", b"M", b"32",
15623        ]);
15624        f.run(&[b"VSETATTR", b"v", b"east", b"{}"]);
15625        let info = f.run(&[b"VINFO", b"v"]);
15626        assert!(info.starts_with("*24\r\n$10\r\nindex-type\r\n$9\r\npartition\r\n"));
15627        // What the client asked for and not what happened to the tuning, which
15628        // is `10` section 7: M is recorded and changes nothing.
15629        assert!(info.contains("$6\r\nhnsw-m\r\n:32\r\n"), "{info}");
15630        assert!(info.contains("$10\r\nvector-dim\r\n:2\r\n"), "{info}");
15631        assert!(info.contains("$16\r\nattributes-count\r\n:1\r\n"), "{info}");
15632        // Nobody named a quantisation, so this set is a `Q8` one and every
15633        // element in it is stored that way.
15634        assert!(
15635            info.contains("$10\r\nquant-type\r\n$4\r\nint8\r\n"),
15636            "{info}"
15637        );
15638        let mut f = Fixture::new();
15639        f.run(&[b"VADD", b"v", b"VALUES", b"2", b"0", b"1", b"north", b"BIN"]);
15640        assert!(
15641            f.run(&[b"VINFO", b"v"])
15642                .contains("$10\r\nquant-type\r\n$3\r\nbin\r\n")
15643        );
15644        assert_eq!(f.run(&[b"VINFO", b"nokey"]), "$-1\r\n");
15645    }
15646
15647    /// A set to read ranges of names out of.
15648    fn named() -> Fixture {
15649        let mut f = Fixture::new();
15650        for (i, name) in ["alpha", "beta", "gamma", "delta", "epsilon"]
15651            .iter()
15652            .enumerate()
15653        {
15654            let x = (i + 1).to_string();
15655            f.run(&[
15656                b"VADD",
15657                b"r",
15658                b"VALUES",
15659                b"2",
15660                x.as_bytes(),
15661                b"1",
15662                name.as_bytes(),
15663            ]);
15664        }
15665        f
15666    }
15667
15668    /// `VRANGE` reads the names in the order bytes come in and pays no
15669    /// attention to where the vectors point.
15670    #[test]
15671    fn vrange_walks_the_names_and_not_the_vectors() {
15672        let mut f = named();
15673        assert_eq!(
15674            f.run(&[b"VRANGE", b"r", b"-", b"+"]),
15675            "*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"
15676        );
15677        assert_eq!(
15678            f.run(&[b"VRANGE", b"r", b"[a", b"[d"]),
15679            "*2\r\n$5\r\nalpha\r\n$4\r\nbeta\r\n",
15680            "the high end is a name and not a prefix, so delta is past it"
15681        );
15682        assert_eq!(
15683            f.run(&[b"VRANGE", b"r", b"(alpha", b"(gamma"]),
15684            "*3\r\n$4\r\nbeta\r\n$5\r\ndelta\r\n$7\r\nepsilon\r\n"
15685        );
15686        assert_eq!(
15687            f.run(&[b"VRANGE", b"r", b"[beta", b"[beta"]),
15688            "*1\r\n$4\r\nbeta\r\n"
15689        );
15690        assert_eq!(f.run(&[b"VRANGE", b"r", b"[z", b"+"]), "*0\r\n");
15691        // Bytes and not letters, so an upper case name sorts before every lower
15692        // case one rather than beside its own spelling.
15693        f.run(&[b"VADD", b"r", b"VALUES", b"2", b"1", b"1", b"Beta"]);
15694        assert_eq!(
15695            f.run(&[b"VRANGE", b"r", b"-", b"[beta"]),
15696            "*3\r\n$4\r\nBeta\r\n$5\r\nalpha\r\n$4\r\nbeta\r\n"
15697        );
15698        assert_eq!(f.run(&[b"VRANGE", b"nokey", b"-", b"+"]), "*0\r\n");
15699    }
15700
15701    /// The count cuts the answer after the range is decided, and zero is not
15702    /// the same as leaving it out.
15703    #[test]
15704    fn a_vrange_count_of_zero_asks_for_nothing() {
15705        let mut f = named();
15706        assert_eq!(
15707            f.run(&[b"VRANGE", b"r", b"-", b"+", b"2"]),
15708            "*2\r\n$5\r\nalpha\r\n$4\r\nbeta\r\n"
15709        );
15710        assert_eq!(f.run(&[b"VRANGE", b"r", b"-", b"+", b"0"]), "*0\r\n");
15711        assert!(
15712            f.run(&[b"VRANGE", b"r", b"-", b"+", b"-1"])
15713                .starts_with("*5\r\n"),
15714            "a negative count is no limit at all"
15715        );
15716    }
15717
15718    /// Both ends are read before either is placed, and the count is read before
15719    /// either end.
15720    #[test]
15721    fn vrange_says_which_end_it_could_not_read() {
15722        let mut f = named();
15723        assert_eq!(
15724            f.run(&[b"VRANGE", b"r", b"x", b"y"]),
15725            "-ERR invalid start range format\r\n"
15726        );
15727        assert_eq!(
15728            f.run(&[b"VRANGE", b"r", b"+", b"x"]),
15729            "-ERR invalid end range format\r\n",
15730            "the high end is spelled wrong, which is worth saying before the \
15731             low end being on the wrong side"
15732        );
15733        assert_eq!(
15734            f.run(&[b"VRANGE", b"r", b"+", b"-"]),
15735            "-ERR '-' can only be used as first argument, '+' only as second\r\n"
15736        );
15737        // A bracket with nothing after it is not the empty name here, though an
15738        // element really can be called that.
15739        assert_eq!(
15740            f.run(&[b"VRANGE", b"r", b"[", b"+"]),
15741            "-ERR invalid start range format\r\n"
15742        );
15743        assert_eq!(
15744            f.run(&[b"VRANGE", b"r", b"x", b"+", b"z"]),
15745            "-ERR invalid COUNT value\r\n"
15746        );
15747        assert_eq!(
15748            f.run(&[b"VRANGE", b"r", b"-", b"+", b"2", b"extra"]),
15749            "-ERR wrong number of arguments for 'VRANGE' command\r\n"
15750        );
15751        f.run(&[b"SET", b"s", b"x"]);
15752        assert!(
15753            f.run(&[b"VRANGE", b"s", b"-", b"+"])
15754                .starts_with("-WRONGTYPE")
15755        );
15756    }
15757
15758    /// The option that asks for something this index does not have says so
15759    /// rather than doing something else quietly.
15760    #[test]
15761    fn reduce_is_refused_and_not_ignored() {
15762        let mut f = Fixture::new();
15763        let reduce = f.run(&[
15764            b"VADD", b"v", b"REDUCE", b"1", b"VALUES", b"2", b"1", b"0", b"east",
15765        ]);
15766        assert!(
15767            reduce.starts_with("-ERR REDUCE is not supported."),
15768            "{reduce}"
15769        );
15770        assert_eq!(f.run(&[b"EXISTS", b"v"]), ":0\r\n");
15771    }
15772
15773    /// A filtered search answers with the nearest elements that match, and an
15774    /// expression that is not one is an error before the key is looked at.
15775    #[test]
15776    fn vsim_filter_reads_the_attributes() {
15777        let mut f = Fixture::new();
15778        for (name, x, y, attr) in [
15779            ("a", "1", "0", r#"{"lang":"en","year":1999}"#),
15780            ("b", "9", "1", r#"{"lang":"fr","year":2005}"#),
15781            ("c", "8", "2", r#"{"lang":"en","year":1970}"#),
15782            ("d", "7", "3", r#"{"lang":"en","year":2020}"#),
15783        ] {
15784            f.run(&[
15785                b"VADD",
15786                b"v",
15787                b"VALUES",
15788                b"2",
15789                x.as_bytes(),
15790                y.as_bytes(),
15791                name.as_bytes(),
15792                b"SETATTR",
15793                attr.as_bytes(),
15794            ]);
15795        }
15796        // `b` is the nearest to the query and is the one the filter drops, so
15797        // this is the answer a filter applied afterwards would have got wrong.
15798        assert_eq!(
15799            f.run(&[
15800                b"VSIM",
15801                b"v",
15802                b"VALUES",
15803                b"2",
15804                b"9",
15805                b"1",
15806                b"COUNT",
15807                b"2",
15808                b"FILTER",
15809                b".lang == \"en\"",
15810            ]),
15811            "*2\r\n$1\r\na\r\n$1\r\nc\r\n"
15812        );
15813        // A number is compared as a number, and the two halves of an `and` both
15814        // have to hold.
15815        assert_eq!(
15816            f.run(&[
15817                b"VSIM",
15818                b"v",
15819                b"VALUES",
15820                b"2",
15821                b"9",
15822                b"1",
15823                b"FILTER",
15824                b".lang == 'en' and .year > 1980",
15825            ]),
15826            "*2\r\n$1\r\na\r\n$1\r\nd\r\n"
15827        );
15828        // A list, and a field an element does not have.
15829        assert_eq!(
15830            f.run(&[
15831                b"VSIM",
15832                b"v",
15833                b"VALUES",
15834                b"2",
15835                b"9",
15836                b"1",
15837                b"FILTER",
15838                b".lang in ['fr', 'de']",
15839            ]),
15840            "*1\r\n$1\r\nb\r\n"
15841        );
15842        assert_eq!(
15843            f.run(&[
15844                b"VSIM",
15845                b"v",
15846                b"VALUES",
15847                b"2",
15848                b"9",
15849                b"1",
15850                b"FILTER",
15851                b".rating > 3"
15852            ]),
15853            "*0\r\n"
15854        );
15855        // TRUTH measures every vector, and the filter still decides which ones
15856        // are measured.
15857        assert_eq!(
15858            f.run(&[
15859                b"VSIM",
15860                b"v",
15861                b"VALUES",
15862                b"2",
15863                b"9",
15864                b"1",
15865                b"TRUTH",
15866                b"FILTER",
15867                b".year < 1980",
15868            ]),
15869            "*1\r\n$1\r\nc\r\n"
15870        );
15871        // VSETATTR moves an element in and out of a filter, which means the tag
15872        // beside its code was rewritten and not just the string.
15873        f.run(&[b"VSETATTR", b"v", b"b", r#"{"lang":"en"}"#.as_bytes()]);
15874        assert_eq!(
15875            f.run(&[
15876                b"VSIM",
15877                b"v",
15878                b"VALUES",
15879                b"2",
15880                b"9",
15881                b"1",
15882                b"COUNT",
15883                b"1",
15884                b"FILTER",
15885                b".lang == \"en\"",
15886            ]),
15887            "*1\r\n$1\r\nb\r\n"
15888        );
15889        // And a VADD that replaces the vector keeps the attribute and the tag,
15890        // which is the same rewrite from the other end.
15891        f.run(&[b"VADD", b"v", b"VALUES", b"2", b"9", b"2", b"b"]);
15892        assert_eq!(
15893            f.run(&[
15894                b"VSIM",
15895                b"v",
15896                b"VALUES",
15897                b"2",
15898                b"9",
15899                b"1",
15900                b"COUNT",
15901                b"1",
15902                b"FILTER",
15903                b".lang == \"en\"",
15904            ]),
15905            "*1\r\n$1\r\nb\r\n"
15906        );
15907
15908        // The expression is parsed before the key is read, so a bad one is an
15909        // error whether or not the key is there.
15910        let bad = f.run(&[b"VSIM", b"nokey", b"ELE", b"e", b"FILTER", b".k =="]);
15911        assert_eq!(bad, "-ERR invalid FILTER expression\r\n");
15912        assert_eq!(
15913            f.run(&[b"VSIM", b"v", b"ELE", b"a", b"FILTER", b"junk"]),
15914            "-ERR invalid FILTER expression\r\n"
15915        );
15916        // FILTER-EF raises the effort rather than capping it, and zero is
15917        // Redis's word for no limit, so neither is an error.
15918        assert_eq!(
15919            f.run(&[
15920                b"VSIM",
15921                b"v",
15922                b"VALUES",
15923                b"2",
15924                b"9",
15925                b"1",
15926                b"COUNT",
15927                b"1",
15928                b"FILTER-EF",
15929                b"500",
15930                b"FILTER",
15931                b".lang == 'en'",
15932            ]),
15933            "*1\r\n$1\r\nb\r\n"
15934        );
15935        assert_eq!(
15936            f.run(&[
15937                b"VSIM",
15938                b"v",
15939                b"VALUES",
15940                b"2",
15941                b"9",
15942                b"1",
15943                b"COUNT",
15944                b"1",
15945                b"FILTER-EF",
15946                b"0"
15947            ]),
15948            "*1\r\n$1\r\nb\r\n"
15949        );
15950        assert_eq!(
15951            f.run(&[
15952                b"VSIM",
15953                b"v",
15954                b"VALUES",
15955                b"2",
15956                b"9",
15957                b"1",
15958                b"FILTER-EF",
15959                b"lots"
15960            ]),
15961            "-ERR EF must be a positive integer\r\n"
15962        );
15963    }
15964
15965    /// A vector set key is a key, so the keyspace owns it the way it owns every
15966    /// other one and none of those commands know what is inside it.
15967    #[test]
15968    fn the_keyspace_sees_a_vector_set_key_like_any_other() {
15969        let mut f = Fixture::new();
15970        f.run(&[b"VADD", b"v", b"VALUES", b"2", b"1", b"0", b"east"]);
15971        assert_eq!(f.run(&[b"TYPE", b"v"]), "+vectorset\r\n");
15972        assert_eq!(f.run(&[b"EXISTS", b"v"]), ":1\r\n");
15973        assert_eq!(f.run(&[b"OBJECT", b"ENCODING", b"v"]), "$6\r\nrabitq\r\n");
15974        assert_eq!(f.run(&[b"KEYS", b"*"]), "*1\r\n$1\r\nv\r\n");
15975        assert_eq!(f.run(&[b"DBSIZE"]), ":1\r\n");
15976        assert_eq!(f.run(&[b"EXPIRE", b"v", b"100"]), ":1\r\n");
15977        assert_eq!(f.run(&[b"TTL", b"v"]), ":100\r\n");
15978        assert_eq!(f.run(&[b"PERSIST", b"v"]), ":1\r\n");
15979        assert_eq!(f.run(&[b"DEL", b"v"]), ":1\r\n");
15980        assert_eq!(f.run(&[b"EXISTS", b"v"]), ":0\r\n");
15981
15982        // And the wrong type is the wrong type in both directions.
15983        f.run(&[b"SET", b"s", b"1"]);
15984        assert_eq!(
15985            f.run(&[b"VADD", b"s", b"VALUES", b"2", b"1", b"0", b"east"]),
15986            "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n"
15987        );
15988        assert_eq!(
15989            f.run(&[b"VCARD", b"s"]),
15990            "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n"
15991        );
15992        f.run(&[b"VADD", b"v", b"VALUES", b"2", b"1", b"0", b"east"]);
15993        assert_eq!(
15994            f.run(&[b"GET", b"v"]),
15995            "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n"
15996        );
15997        // A graph and a vector set share the escape in the record tag and are
15998        // still two different types, which is the case the tag alone cannot
15999        // decide.
16000        f.run(&[b"G.NADD", b"social", b"ada"]);
16001        assert_eq!(
16002            f.run(&[b"VCARD", b"social"]),
16003            "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n"
16004        );
16005        assert_eq!(
16006            f.run(&[b"G.NGET", b"v", b"ada"]),
16007            "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n"
16008        );
16009    }
16010
16011    /// `VRANDMEMBER` is `SRANDMEMBER` over the element names, in both of its
16012    /// shapes, off the database's own generator.
16013    #[test]
16014    fn vrandmember_has_the_two_shapes_srandmember_has() {
16015        let mut f = Fixture::new();
16016        for (i, name) in [&b"a"[..], b"b", b"c"].iter().enumerate() {
16017            let x = (i + 1).to_string();
16018            f.run(&[b"VADD", b"v", b"VALUES", b"2", x.as_bytes(), b"1", name]);
16019        }
16020        // One element is a bulk string and not an array of one.
16021        let one = f.run(&[b"VRANDMEMBER", b"v"]);
16022        assert!(one.starts_with("$1\r\n"), "{one}");
16023        // A positive count is distinct and stops at the size of the set.
16024        let mut all = f.run(&[b"VRANDMEMBER", b"v", b"9"]);
16025        assert!(all.starts_with("*3\r\n"), "{all}");
16026        for name in ["a", "b", "c"] {
16027            assert!(all.contains(name), "{all} is missing {name}");
16028        }
16029        all = f.run(&[b"VRANDMEMBER", b"v", b"2"]);
16030        assert!(all.starts_with("*2\r\n"), "{all}");
16031        // A negative one draws that many and allows repeats.
16032        let many = f.run(&[b"VRANDMEMBER", b"v", b"-5"]);
16033        assert!(many.starts_with("*5\r\n"), "{many}");
16034        // A key that is not there answers the shape that was asked for.
16035        assert_eq!(f.run(&[b"VRANDMEMBER", b"nokey"]), "$-1\r\n");
16036        assert_eq!(f.run(&[b"VRANDMEMBER", b"nokey", b"3"]), "*0\r\n");
16037    }
16038
16039    /// `VLINKS` answers about the index that is here rather than the graph that
16040    /// is not, which is D-2.
16041    #[test]
16042    fn vlinks_reports_one_layer_of_partition_neighbours() {
16043        let mut f = Fixture::new();
16044        f.run(&[b"VADD", b"v", b"VALUES", b"2", b"1", b"0", b"east"]);
16045        f.run(&[b"VADD", b"v", b"VALUES", b"2", b"0", b"1", b"north"]);
16046        // One layer deep, because the index is one layer deep, so a client
16047        // walking layers gets a short list and not a shape it cannot parse.
16048        assert_eq!(
16049            f.run(&[b"VLINKS", b"v", b"east"]),
16050            "*1\r\n*1\r\n$5\r\nnorth\r\n"
16051        );
16052        assert_eq!(
16053            f.run(&[b"VLINKS", b"v", b"east", b"WITHSCORES"]),
16054            "*1\r\n*2\r\n$5\r\nnorth\r\n$3\r\n0.5\r\n"
16055        );
16056        assert_eq!(f.run(&[b"VLINKS", b"v", b"nobody"]), "*-1\r\n");
16057        assert_eq!(f.run(&[b"VLINKS", b"nokey", b"east"]), "*-1\r\n");
16058    }
16059
16060    /// A vector arrives either as digits or as bytes, and the two have to mean
16061    /// the same thing.
16062    #[test]
16063    fn fp32_and_values_are_the_same_vector() {
16064        let mut f = Fixture::new();
16065        let mut blob = Vec::new();
16066        for x in [3.0f32, 4.0] {
16067            blob.extend_from_slice(&x.to_le_bytes());
16068        }
16069        assert_eq!(f.run(&[b"VADD", b"v", b"FP32", &blob, b"a"]), ":1\r\n");
16070        assert_eq!(f.run(&[b"VDIM", b"v"]), ":2\r\n");
16071        assert_eq!(
16072            f.run(&[b"VEMB", b"v", b"a"]),
16073            "*2\r\n$17\r\n2.992125988006592\r\n$1\r\n4\r\n"
16074        );
16075        // RAW is the stored bytes and the numbers that turn them back into the
16076        // client's vector, which for `Q8` is a code a coordinate, the length the
16077        // vector arrived with and the scale the codes are measured against. The
16078        // name of the form is a simple string, which is a real server's shape,
16079        // and all four of these are a real server's answers.
16080        assert_eq!(
16081            f.run(&[b"VEMB", b"v", b"a", b"RAW"]),
16082            "*4\r\n+int8\r\n$2\r\n_\x7f\r\n$1\r\n5\r\n$17\r\n0.800000011920929\r\n"
16083        );
16084        // A blob that is not a whole number of floats is not a vector.
16085        assert_eq!(
16086            f.run(&[b"VADD", b"w", b"FP32", b"abc", b"a"]),
16087            "-ERR invalid vector specification\r\n"
16088        );
16089        // Neither is a count that promises more than arrived.
16090        assert_eq!(
16091            f.run(&[b"VADD", b"w", b"VALUES", b"4", b"1", b"0", b"a"]),
16092            "-ERR syntax error\r\n"
16093        );
16094        assert_eq!(f.run(&[b"EXISTS", b"w"]), ":0\r\n");
16095    }
16096
16097    // ----------------------------------------------------------------- bloom
16098
16099    /// The filter a client gets when it does not describe one, and the two
16100    /// answers an add can give.
16101    #[test]
16102    fn bf_add_makes_the_filter_and_says_whether_it_was_new() {
16103        let mut f = Fixture::new();
16104        assert_eq!(f.run(&[b"BF.ADD", b"b", b"hello"]), ":1\r\n");
16105        assert_eq!(f.run(&[b"BF.ADD", b"b", b"hello"]), ":0\r\n");
16106        assert_eq!(f.run(&[b"BF.EXISTS", b"b", b"hello"]), ":1\r\n");
16107        assert_eq!(f.run(&[b"BF.EXISTS", b"b", b"never"]), ":0\r\n");
16108        assert_eq!(f.run(&[b"BF.CARD", b"b"]), ":1\r\n");
16109        // The defaults are the module's configs and not anything the command
16110        // said, which is 100 entries at a hundredth and a growth of 2.
16111        assert_eq!(
16112            f.run(&[b"BF.INFO", b"b"]),
16113            "*10\r\n+Capacity\r\n:100\r\n+Size\r\n:240\r\n\
16114             +Number of filters\r\n:1\r\n+Number of items inserted\r\n:1\r\n\
16115             +Expansion rate\r\n:2\r\n"
16116        );
16117        assert_eq!(f.run(&[b"TYPE", b"b"]), "+MBbloom--\r\n");
16118        assert_eq!(f.run(&[b"OBJECT", b"ENCODING", b"b"]), "$3\r\nraw\r\n");
16119        // A key that is not there has no filter to report on, and answers two
16120        // different ways about it depending on which command asked.
16121        assert_eq!(f.run(&[b"BF.CARD", b"gone"]), ":0\r\n");
16122        assert_eq!(f.run(&[b"BF.INFO", b"gone"]), "-ERR not found\r\n");
16123    }
16124
16125    /// `BF.EXISTS` on a key holding something else answers a miss, and
16126    /// everything else in the family answers `WRONGTYPE`.
16127    ///
16128    /// The two halves of a check and set disagree about what that key is, which
16129    /// is the module's behaviour and not a decision taken here.
16130    #[test]
16131    fn a_wrong_type_is_a_miss_to_the_two_that_only_read_bits() {
16132        let mut f = Fixture::new();
16133        f.run(&[b"SET", b"s", b"text"]);
16134        assert_eq!(f.run(&[b"BF.EXISTS", b"s", b"x"]), ":0\r\n");
16135        assert_eq!(f.run(&[b"BF.MEXISTS", b"s", b"x"]), "*1\r\n:0\r\n");
16136        for cmd in [
16137            vec![&b"BF.ADD"[..], b"s", b"x"],
16138            vec![&b"BF.MADD"[..], b"s", b"x"],
16139            vec![&b"BF.CARD"[..], b"s"],
16140            vec![&b"BF.INFO"[..], b"s"],
16141            vec![&b"BF.DEBUG"[..], b"s"],
16142            vec![&b"BF.SCANDUMP"[..], b"s", b"0"],
16143        ] {
16144            let name = String::from_utf8_lossy(cmd[0]).into_owned();
16145            assert!(f.run(&cmd).starts_with("-WRONGTYPE"), "{name}");
16146        }
16147        // The arguments are read before the key is, so a reserve with a bad
16148        // error rate complains about the rate and never learns about the string.
16149        assert_eq!(
16150            f.run(&[b"BF.RESERVE", b"s", b"abc", b"10"]),
16151            "-ERR bad error rate\r\n"
16152        );
16153        assert!(
16154            f.run(&[b"BF.RESERVE", b"s", b"0.01", b"10"])
16155                .starts_with("-WRONGTYPE")
16156        );
16157    }
16158
16159    /// A chain grows by its expansion factor and each link is half as wrong as
16160    /// the one before, which is what makes the whole filter hold its rate.
16161    #[test]
16162    fn a_full_filter_grows_a_link_and_a_fixed_one_says_no() {
16163        let mut f = Fixture::new();
16164        assert_eq!(f.run(&[b"BF.RESERVE", b"g", b"0.01", b"10"]), "+OK\r\n");
16165        for i in 0..10u32 {
16166            assert_eq!(
16167                f.run(&[b"BF.ADD", b"g", i.to_string().as_bytes()]),
16168                ":1\r\n"
16169            );
16170        }
16171        assert_eq!(f.run(&[b"BF.INFO", b"g", b"FILTERS"]), "*1\r\n:1\r\n");
16172        assert_eq!(f.run(&[b"BF.ADD", b"g", b"11"]), ":1\r\n");
16173        assert_eq!(f.run(&[b"BF.INFO", b"g", b"filters"]), "*1\r\n:2\r\n");
16174        // Capacity is the sum of every link and not the number that was asked
16175        // for, so it is 10 and then 10 plus 20.
16176        assert_eq!(f.run(&[b"BF.INFO", b"g", b"CAPACITY"]), "*1\r\n:30\r\n");
16177        assert_eq!(
16178            f.run(&[b"BF.DEBUG", b"g"]),
16179            "*3\r\n$7\r\nsize:11\r\n\
16180             $71\r\nbytes:16 bits:128 hashes:8 hashwidth:64 capacity:10 size:10 ratio:0.005\r\n\
16181             $71\r\nbytes:32 bits:256 hashes:9 hashwidth:64 capacity:20 size:1 ratio:0.0025\r\n"
16182        );
16183
16184        // The same filter told not to grow fills instead.
16185        assert_eq!(
16186            f.run(&[b"BF.RESERVE", b"n", b"0.01", b"2", b"NONSCALING"]),
16187            "+OK\r\n"
16188        );
16189        assert_eq!(f.run(&[b"BF.ADD", b"n", b"a"]), ":1\r\n");
16190        assert_eq!(f.run(&[b"BF.ADD", b"n", b"b"]), ":1\r\n");
16191        assert_eq!(
16192            f.run(&[b"BF.ADD", b"n", b"c"]),
16193            "-ERR non scaling filter is full\r\n"
16194        );
16195        // And an item that is already in it still answers, because membership
16196        // is checked before fullness.
16197        assert_eq!(f.run(&[b"BF.ADD", b"n", b"a"]), ":0\r\n");
16198        // A filter that will not grow has no expansion rate to report, in
16199        // either of the two spellings that make one.
16200        assert_eq!(f.run(&[b"BF.INFO", b"n", b"EXPANSION"]), "*1\r\n$-1\r\n");
16201        f.run(&[b"BF.RESERVE", b"z", b"0.01", b"2", b"EXPANSION", b"0"]);
16202        assert_eq!(f.run(&[b"BF.INFO", b"z", b"EXPANSION"]), "*1\r\n$-1\r\n");
16203        // Asking for both at once is refused, which is one of the module's
16204        // errors that carries no prefix at all.
16205        assert_eq!(
16206            f.run(&[
16207                b"BF.RESERVE",
16208                b"q",
16209                b"0.01",
16210                b"2",
16211                b"NONSCALING",
16212                b"EXPANSION",
16213                b"2"
16214            ]),
16215            "-Nonscaling filters cannot expand\r\n"
16216        );
16217    }
16218
16219    /// A multi add stops where the filter did, so the reply can be shorter than
16220    /// the argument list.
16221    #[test]
16222    fn madd_truncates_its_reply_at_the_item_that_did_not_fit() {
16223        let mut f = Fixture::new();
16224        f.run(&[b"BF.RESERVE", b"n", b"0.01", b"2", b"NONSCALING"]);
16225        assert_eq!(
16226            f.run(&[b"BF.MADD", b"n", b"a", b"b", b"c", b"d"]),
16227            "*3\r\n:1\r\n:1\r\n-ERR non scaling filter is full\r\n"
16228        );
16229        assert_eq!(
16230            f.run(&[b"BF.MEXISTS", b"n", b"a", b"c"]),
16231            "*2\r\n:1\r\n:0\r\n"
16232        );
16233    }
16234
16235    /// `BF.INSERT` describes a filter and fills it in one command, with its own
16236    /// spelling of every complaint.
16237    #[test]
16238    fn insert_is_a_reserve_and_a_madd_with_different_errors() {
16239        let mut f = Fixture::new();
16240        assert_eq!(
16241            f.run(&[
16242                b"BF.INSERT",
16243                b"i",
16244                b"CAPACITY",
16245                b"50",
16246                b"ERROR",
16247                b"0.001",
16248                b"ITEMS",
16249                b"a",
16250                b"b"
16251            ]),
16252            "*2\r\n:1\r\n:1\r\n"
16253        );
16254        assert_eq!(f.run(&[b"BF.INFO", b"i", b"CAPACITY"]), "*1\r\n:50\r\n");
16255        // NOCREATE is the only way to add without making the key.
16256        assert_eq!(
16257            f.run(&[b"BF.INSERT", b"gone", b"NOCREATE", b"ITEMS", b"a"]),
16258            "-ERR not found\r\n"
16259        );
16260        assert_eq!(f.run(&[b"EXISTS", b"gone"]), ":0\r\n");
16261        // The same mistakes as BF.RESERVE, in the sentences this command uses
16262        // for them, and one sentence where BF.RESERVE has two.
16263        assert_eq!(
16264            f.run(&[b"BF.INSERT", b"i", b"CAPACITY", b"abc", b"ITEMS", b"a"]),
16265            "-Bad capacity\r\n"
16266        );
16267        assert_eq!(
16268            f.run(&[b"BF.INSERT", b"i", b"ERROR", b"2", b"ITEMS", b"a"]),
16269            "-Bad error rate\r\n"
16270        );
16271        assert_eq!(
16272            f.run(&[b"BF.INSERT", b"i", b"EXPANSION", b"99999", b"ITEMS", b"a"]),
16273            "-Bad expansion\r\n"
16274        );
16275        // An option is matched on its first letter and not on the word, so a
16276        // token nobody meant as an option is one anyway if it starts with the
16277        // right letter. NOSUCHTHING is NONSCALING here, and the filter it
16278        // builds says so.
16279        assert_eq!(
16280            f.run(&[b"BF.INSERT", b"ns", b"NOSUCHTHING", b"ITEMS", b"a"]),
16281            "*1\r\n:1\r\n"
16282        );
16283        assert_eq!(f.run(&[b"BF.INFO", b"ns", b"EXPANSION"]), "*1\r\n$-1\r\n");
16284        // Only E and N need a second look, one for ERROR against EXPANSION and
16285        // the other for NOCREATE against NONSCALING, and both stop as soon as
16286        // they can tell the two apart.
16287        assert_eq!(
16288            f.run(&[b"BF.INSERT", b"e1", b"E", b"4", b"ITEMS", b"a"]),
16289            "*1\r\n:1\r\n"
16290        );
16291        assert_eq!(f.run(&[b"BF.INFO", b"e1", b"EXPANSION"]), "*1\r\n:4\r\n");
16292        assert_eq!(
16293            f.run(&[b"BF.INSERT", b"e2", b"ER", b"0.5", b"ITEMS", b"a"]),
16294            "*1\r\n:1\r\n"
16295        );
16296        assert_eq!(
16297            f.run(&[b"BF.INSERT", b"gone", b"NOC", b"ITEMS", b"a"]),
16298            "-ERR not found\r\n"
16299        );
16300        // A letter that starts nothing is the one case that is refused.
16301        assert_eq!(
16302            f.run(&[b"BF.INSERT", b"i", b"ZZZ", b"ITEMS", b"a"]),
16303            "-Unknown argument received\r\n"
16304        );
16305        // Everything after ITEMS is an item, even when it spells an option.
16306        assert_eq!(
16307            f.run(&[b"BF.INSERT", b"i", b"ITEMS", b"NOCREATE"]),
16308            "*1\r\n:1\r\n"
16309        );
16310        // And ITEMS with nothing after it is the same as leaving it out.
16311        assert!(
16312            f.run(&[b"BF.INSERT", b"i", b"ITEMS"])
16313                .contains("wrong number of arguments")
16314        );
16315    }
16316
16317    /// A filter dumped a chunk at a time and put back into another key is the
16318    /// same filter.
16319    #[test]
16320    fn a_dump_replays_into_a_filter_that_answers_the_same() {
16321        let mut f = Fixture::new();
16322        f.run(&[b"BF.RESERVE", b"src", b"0.01", b"10"]);
16323        for i in 0..25u32 {
16324            f.run(&[b"BF.ADD", b"src", i.to_string().as_bytes()]);
16325        }
16326        assert_eq!(f.run(&[b"BF.INFO", b"src", b"FILTERS"]), "*1\r\n:2\r\n");
16327
16328        // Iterator zero asks for the header and every one after it is a running
16329        // byte offset, and a chunk never spans two links.
16330        let mut iter = b"0".to_vec();
16331        let mut chunks = 0;
16332        loop {
16333            let raw = f.raw(&[b"BF.SCANDUMP", b"src", &iter]);
16334            let text = String::from_utf8_lossy(&raw).into_owned();
16335            let next = text
16336                .split("\r\n")
16337                .nth(1)
16338                .and_then(|n| n.strip_prefix(':'))
16339                .expect("a two element reply of an iterator and a chunk")
16340                .to_owned();
16341            let body = &raw[raw.iter().position(|&b| b == b'$').expect("a bulk chunk")..];
16342            let data = &body[body
16343                .windows(2)
16344                .position(|w| w == b"\r\n")
16345                .expect("a length line")
16346                + 2..body.len() - 2];
16347            if next == "0" {
16348                assert!(data.is_empty(), "the last chunk is empty");
16349                break;
16350            }
16351            let put = f.run(&[b"BF.LOADCHUNK", b"dst", next.as_bytes(), data]);
16352            assert_eq!(put, "+OK\r\n", "loading chunk {chunks}");
16353            iter = next.into_bytes();
16354            chunks += 1;
16355        }
16356        assert_eq!(chunks, 3, "a header and one chunk per link");
16357
16358        assert_eq!(f.run(&[b"BF.INFO", b"dst"]), f.run(&[b"BF.INFO", b"src"]));
16359        assert_eq!(f.run(&[b"BF.DEBUG", b"dst"]), f.run(&[b"BF.DEBUG", b"src"]));
16360        for i in 0..25u32 {
16361            assert_eq!(
16362                f.run(&[b"BF.EXISTS", b"dst", i.to_string().as_bytes()]),
16363                ":1\r\n"
16364            );
16365        }
16366
16367        // A header on top of a filter is refused rather than merged, and so is
16368        // one that no filter wrote.
16369        assert_eq!(
16370            f.run(&[b"BF.LOADCHUNK", b"dst", b"1", b"anything"]),
16371            "-ERR received bad data\r\n"
16372        );
16373        assert_eq!(
16374            f.run(&[b"BF.LOADCHUNK", b"fresh", b"1", b"anything"]),
16375            "-ERR received bad data\r\n"
16376        );
16377        // An offset past the end of the filter names itself.
16378        assert_eq!(
16379            f.run(&[b"BF.LOADCHUNK", b"dst", b"99999", b"x"]),
16380            "-ERR invalid offset - no link found\r\n"
16381        );
16382        assert_eq!(
16383            f.run(&[b"BF.LOADCHUNK", b"dst", b"nope", b"x"]),
16384            "-ERR Second argument must be numeric\r\n"
16385        );
16386        // The same complaint without the prefix on the way out, which is the
16387        // module's inconsistency and not a slip here.
16388        assert_eq!(
16389            f.run(&[b"BF.SCANDUMP", b"src", b"nope"]),
16390            "-Second argument must be numeric\r\n"
16391        );
16392    }
16393
16394    /// The argument checks, which have a sentence each and read numbers the way
16395    /// Redis reads them everywhere else.
16396    #[test]
16397    fn reserve_reads_its_numbers_the_way_string2ll_does() {
16398        let mut f = Fixture::new();
16399        for (args, want) in [
16400            (vec![&b"abc"[..], b"10"], "-ERR bad error rate\r\n"),
16401            (vec![&b"nan"[..], b"10"], "-ERR bad error rate\r\n"),
16402            (
16403                vec![&b"0"[..], b"10"],
16404                "-ERR error rate must be in the range (0.000000, 1.000000)\r\n",
16405            ),
16406            (
16407                vec![&b"1"[..], b"10"],
16408                "-ERR error rate must be in the range (0.000000, 1.000000)\r\n",
16409            ),
16410            (
16411                vec![&b"inf"[..], b"10"],
16412                "-ERR error rate must be in the range (0.000000, 1.000000)\r\n",
16413            ),
16414            (vec![&b"0.01"[..], b"+10"], "-ERR bad capacity\r\n"),
16415            (vec![&b"0.01"[..], b"1e2"], "-ERR bad capacity\r\n"),
16416            (vec![&b"0.01"[..], b"007"], "-ERR bad capacity\r\n"),
16417            (
16418                vec![&b"0.01"[..], b"0"],
16419                "-ERR capacity must be in the range [1, 1073741824]\r\n",
16420            ),
16421            (
16422                vec![&b"0.01"[..], b"1073741825"],
16423                "-ERR capacity must be in the range [1, 1073741824]\r\n",
16424            ),
16425        ] {
16426            let mut cmd = vec![&b"BF.RESERVE"[..], b"k"];
16427            cmd.extend(args.iter().copied());
16428            assert_eq!(f.run(&cmd), want, "{}", String::from_utf8_lossy(args[0]));
16429        }
16430        assert_eq!(
16431            f.run(&[b"BF.RESERVE", b"k", b"0.01", b"10", b"EXPANSION"]),
16432            "-ERR no expansion\r\n"
16433        );
16434        assert_eq!(
16435            f.run(&[b"BF.RESERVE", b"k", b"0.01", b"10", b"EXPANSION", b"abc"]),
16436            "-ERR bad expansion\r\n"
16437        );
16438        assert_eq!(
16439            f.run(&[b"BF.RESERVE", b"k", b"0.01", b"10", b"EXPANSION", b"32769"]),
16440            "-ERR expansion must be in the range [0, 32768]\r\n"
16441        );
16442        // Trailing rubbish after the capacity is ignored rather than refused.
16443        assert_eq!(
16444            f.run(&[b"BF.RESERVE", b"k", b"0.01", b"10", b"junk"]),
16445            "+OK\r\n"
16446        );
16447        assert_eq!(
16448            f.run(&[b"BF.RESERVE", b"k", b"0.01", b"10"]),
16449            "-ERR item exists\r\n"
16450        );
16451        assert_eq!(
16452            f.run(&[b"BF.INFO", b"k", b"nosuchfield"]),
16453            "-Invalid information value\r\n"
16454        );
16455        assert!(
16456            f.run(&[b"BF.INFO", b"k", b"CAPACITY", b"SIZE"])
16457                .contains("wrong number of arguments")
16458        );
16459    }
16460
16461    /// The RESP3 shapes, which are where this family differs most from RESP2.
16462    #[test]
16463    fn the_bloom_family_answers_in_resp3_spelling_too() {
16464        let mut f = Fixture::new();
16465        f.out.set_proto(Proto::Resp3);
16466        assert_eq!(f.run(&[b"BF.ADD", b"b", b"a"]), "#t\r\n");
16467        assert_eq!(f.run(&[b"BF.ADD", b"b", b"a"]), "#f\r\n");
16468        assert_eq!(f.run(&[b"BF.MADD", b"b", b"a", b"c"]), "*2\r\n#f\r\n#t\r\n");
16469        assert_eq!(f.run(&[b"BF.EXISTS", b"b", b"a"]), "#t\r\n");
16470        assert_eq!(
16471            f.run(&[b"BF.MEXISTS", b"b", b"a", b"z"]),
16472            "*2\r\n#t\r\n#f\r\n"
16473        );
16474        // The count stays an integer, because it counts rather than answers.
16475        assert_eq!(f.run(&[b"BF.CARD", b"b"]), ":2\r\n");
16476        assert_eq!(
16477            f.run(&[b"BF.INFO", b"b"]),
16478            "%5\r\n+Capacity\r\n:100\r\n+Size\r\n:240\r\n\
16479             +Number of filters\r\n:1\r\n+Number of items inserted\r\n:2\r\n\
16480             +Expansion rate\r\n:2\r\n"
16481        );
16482        // One field is a map of one here and a bare array of one on RESP2, so
16483        // this is the reply where the two protocols carry different facts.
16484        assert_eq!(
16485            f.run(&[b"BF.INFO", b"b", b"CAPACITY"]),
16486            "%1\r\n+Capacity\r\n:100\r\n"
16487        );
16488    }
16489
16490    // ---------------------------------------------------------------- cuckoo
16491
16492    /// A dump header, which is the four counts and the three widths a filter
16493    /// writes in front of its fingerprints.
16494    ///
16495    /// Written by hand rather than taken from a `CF.SCANDUMP`, because what the
16496    /// tests below want out of it is the states a filter cannot be put into
16497    /// from the wire.
16498    fn cf_header(
16499        items: u64,
16500        buckets: u64,
16501        deletes: u64,
16502        filters: u64,
16503        geometry: [u16; 3],
16504    ) -> Vec<u8> {
16505        let mut out = Vec::with_capacity(38);
16506        for n in [items, buckets, deletes, filters] {
16507            out.extend_from_slice(&n.to_le_bytes());
16508        }
16509        for n in geometry {
16510            out.extend_from_slice(&n.to_le_bytes());
16511        }
16512        out
16513    }
16514
16515    /// The filter a client gets when it does not describe one, and the thing a
16516    /// cuckoo filter does that a Bloom filter cannot, which is count copies and
16517    /// take them out again.
16518    #[test]
16519    fn cf_add_makes_the_filter_and_counts_the_copies() {
16520        let mut f = Fixture::new();
16521        assert_eq!(f.run(&[b"CF.ADD", b"d", b"hello"]), ":1\r\n");
16522        assert_eq!(f.run(&[b"CF.ADD", b"d", b"hello"]), ":1\r\n");
16523        assert_eq!(f.run(&[b"CF.COUNT", b"d", b"hello"]), ":2\r\n");
16524        // The NX form is the one that looks first, which is why it is a command
16525        // of its own rather than an option.
16526        assert_eq!(f.run(&[b"CF.ADDNX", b"d", b"hello"]), ":0\r\n");
16527        assert_eq!(f.run(&[b"CF.ADDNX", b"d", b"other"]), ":1\r\n");
16528        assert_eq!(f.run(&[b"CF.EXISTS", b"d", b"hello"]), ":1\r\n");
16529        assert_eq!(f.run(&[b"CF.EXISTS", b"d", b"no"]), ":0\r\n");
16530        assert_eq!(
16531            f.run(&[b"CF.MEXISTS", b"d", b"hello", b"no"]),
16532            "*2\r\n:1\r\n:0\r\n"
16533        );
16534        // The defaults are the module's configs: 1024 entries over buckets of
16535        // two, twenty kicks and a chain that grows by one.
16536        assert_eq!(
16537            f.run(&[b"CF.INFO", b"d"]),
16538            "*16\r\n+Size\r\n:1080\r\n+Number of buckets\r\n:512\r\n\
16539             +Number of filters\r\n:1\r\n+Number of items inserted\r\n:3\r\n\
16540             +Number of items deleted\r\n:0\r\n+Bucket size\r\n:2\r\n\
16541             +Expansion rate\r\n:1\r\n+Max iterations\r\n:20\r\n"
16542        );
16543        assert_eq!(
16544            f.run(&[b"CF.DEBUG", b"d"]),
16545            "$79\r\nbktsize:2 buckets:512 items:3 deletes:0 filters:1 \
16546             max_iterations:20 expansion:1\r\n"
16547        );
16548        assert_eq!(f.run(&[b"TYPE", b"d"]), "+MBbloomCF\r\n");
16549        assert_eq!(f.run(&[b"OBJECT", b"ENCODING", b"d"]), "$3\r\nraw\r\n");
16550
16551        // A delete takes one copy, so the same item goes twice and then stops.
16552        assert_eq!(f.run(&[b"CF.DEL", b"d", b"hello"]), ":1\r\n");
16553        assert_eq!(f.run(&[b"CF.COUNT", b"d", b"hello"]), ":1\r\n");
16554        assert_eq!(f.run(&[b"CF.DEL", b"d", b"hello"]), ":1\r\n");
16555        assert_eq!(f.run(&[b"CF.DEL", b"d", b"hello"]), ":0\r\n");
16556        assert_eq!(f.run(&[b"CF.COMPACT", b"d"]), "+OK\r\n");
16557
16558        // A key with no filter under it gets three different sentences and one
16559        // plain miss, depending on which command asked.
16560        assert_eq!(f.run(&[b"CF.INFO", b"gone"]), "-ERR not found\r\n");
16561        assert_eq!(f.run(&[b"CF.DEL", b"gone", b"x"]), "-Not found\r\n");
16562        assert_eq!(
16563            f.run(&[b"CF.COMPACT", b"gone"]),
16564            "-Cuckoo filter was not found\r\n"
16565        );
16566        assert_eq!(f.run(&[b"CF.EXISTS", b"gone", b"x"]), ":0\r\n");
16567        // And `CF.COMPACT` is declared as taking any number of keys and takes
16568        // exactly one, which is the module's own arity being wrong rather than
16569        // this table's.
16570        assert!(
16571            f.run(&[b"CF.COMPACT", b"a", b"b"])
16572                .contains("wrong number of arguments")
16573        );
16574    }
16575
16576    /// The four that only read fingerprints treat a key holding something else
16577    /// as a key with no filter, and everything else answers `WRONGTYPE`.
16578    #[test]
16579    fn a_wrong_type_is_a_miss_to_the_four_that_only_read_fingerprints() {
16580        let mut f = Fixture::new();
16581        f.run(&[b"SET", b"s", b"text"]);
16582        assert_eq!(f.run(&[b"CF.EXISTS", b"s", b"x"]), ":0\r\n");
16583        assert_eq!(f.run(&[b"CF.MEXISTS", b"s", b"x"]), "*1\r\n:0\r\n");
16584        assert_eq!(f.run(&[b"CF.COUNT", b"s", b"x"]), ":0\r\n");
16585        // `CF.DEL` writes and is still in that group, and `CF.COMPACT` writes
16586        // and is declared read only, so neither of the two halves of the family
16587        // is the same set as the flags say.
16588        assert_eq!(f.run(&[b"CF.DEL", b"s", b"x"]), "-Not found\r\n");
16589        assert_eq!(
16590            f.run(&[b"CF.COMPACT", b"s"]),
16591            "-Cuckoo filter was not found\r\n"
16592        );
16593        for cmd in [
16594            vec![&b"CF.ADD"[..], b"s", b"x"],
16595            vec![&b"CF.ADDNX"[..], b"s", b"x"],
16596            vec![&b"CF.INSERT"[..], b"s", b"ITEMS", b"x"],
16597            vec![&b"CF.INSERTNX"[..], b"s", b"ITEMS", b"x"],
16598            vec![&b"CF.INFO"[..], b"s"],
16599            vec![&b"CF.DEBUG"[..], b"s"],
16600            vec![&b"CF.SCANDUMP"[..], b"s", b"0"],
16601            vec![&b"CF.LOADCHUNK"[..], b"s", b"2", b"x"],
16602            vec![&b"CF.RESERVE"[..], b"s", b"64"],
16603        ] {
16604            let name = String::from_utf8_lossy(cmd[0]).into_owned();
16605            assert!(f.run(&cmd).starts_with("-WRONGTYPE"), "{name}");
16606        }
16607    }
16608
16609    /// `CF.RESERVE` reads its options by name in an order of its own, and the
16610    /// first pair with a given name is the only one it looks at.
16611    #[test]
16612    fn reserve_complains_about_its_options_in_the_order_it_looks_for_them() {
16613        let mut f = Fixture::new();
16614        assert_eq!(
16615            f.run(&[
16616                b"CF.RESERVE",
16617                b"r",
16618                b"64",
16619                b"BUCKETSIZE",
16620                b"1",
16621                b"MAXITERATIONS",
16622                b"7",
16623                b"EXPANSION",
16624                b"4"
16625            ]),
16626            "+OK\r\n"
16627        );
16628        assert_eq!(
16629            f.run(&[b"CF.DEBUG", b"r"]),
16630            "$77\r\nbktsize:1 buckets:64 items:0 deletes:0 filters:1 \
16631             max_iterations:7 expansion:4\r\n"
16632        );
16633        assert_eq!(f.run(&[b"CF.RESERVE", b"r", b"64"]), "-ERR item exists\r\n");
16634
16635        assert_eq!(f.run(&[b"CF.RESERVE", b"q", b"abc"]), "-Bad capacity\r\n");
16636        assert_eq!(
16637            f.run(&[b"CF.RESERVE", b"q", b"1"]),
16638            "-Capacity must be in the range [2 * BUCKETSIZE, 1073741824]\r\n"
16639        );
16640        // The range is the bucket size's and not a constant, so a capacity that
16641        // was fine at two slots a bucket is not at four.
16642        assert_eq!(
16643            f.run(&[b"CF.RESERVE", b"q", b"7", b"BUCKETSIZE", b"4"]),
16644            "-Capacity must be in the range [2 * BUCKETSIZE, 1073741824]\r\n"
16645        );
16646        assert_eq!(
16647            f.run(&[b"CF.RESERVE", b"q", b"8", b"BUCKETSIZE", b"4"]),
16648            "+OK\r\n"
16649        );
16650
16651        // The capacity is checked last, so a command that is wrong twice
16652        // answers about the option. Which option it answers about is the order
16653        // the module looks for them in and not the order they were written, so
16654        // a bad kick budget wins over a bad bucket size wherever the two sit.
16655        assert_eq!(
16656            f.run(&[b"CF.RESERVE", b"q2", b"64", b"BUCKETSIZE", b"0"]),
16657            "-BUCKETSIZE: value must be in the range [1, 255]\r\n"
16658        );
16659        assert_eq!(
16660            f.run(&[
16661                b"CF.RESERVE",
16662                b"q2",
16663                b"64",
16664                b"EXPANSION",
16665                b"xx",
16666                b"BUCKETSIZE",
16667                b"0"
16668            ]),
16669            "-BUCKETSIZE: value must be in the range [1, 255]\r\n"
16670        );
16671        assert_eq!(
16672            f.run(&[
16673                b"CF.RESERVE",
16674                b"q2",
16675                b"64",
16676                b"MAXITERATIONS",
16677                b"0",
16678                b"BUCKETSIZE",
16679                b"0"
16680            ]),
16681            "-MAXITERATIONS: value must be in the range [1, 65535]\r\n"
16682        );
16683        // A second pair with a name that has already been read is not looked at
16684        // at all, so this one is a filter with buckets of one rather than an
16685        // error about a bucket size of zero.
16686        assert_eq!(
16687            f.run(&[
16688                b"CF.RESERVE",
16689                b"q3",
16690                b"64",
16691                b"BUCKETSIZE",
16692                b"1",
16693                b"BUCKETSIZE",
16694                b"0"
16695            ]),
16696            "+OK\r\n"
16697        );
16698        // A pair nobody knows is dropped, which is the opposite of what
16699        // `CF.INSERT` does with the same mistake.
16700        assert_eq!(
16701            f.run(&[b"CF.RESERVE", b"q4", b"64", b"NOSUCH", b"9"]),
16702            "+OK\r\n"
16703        );
16704        assert_eq!(
16705            f.run(&[b"CF.DEBUG", b"q4"]),
16706            "$78\r\nbktsize:2 buckets:32 items:0 deletes:0 filters:1 \
16707             max_iterations:20 expansion:1\r\n"
16708        );
16709        // And an option with nothing after it leaves an odd number of them,
16710        // which is an arity error rather than a complaint about the option.
16711        assert!(
16712            f.run(&[b"CF.RESERVE", b"q5", b"64", b"BUCKETSIZE"])
16713                .contains("wrong number of arguments")
16714        );
16715    }
16716
16717    /// `CF.INSERT` is a reserve and a multi add, with a grammar that agrees
16718    /// with `CF.RESERVE` about nothing.
16719    #[test]
16720    fn insert_checks_every_occurrence_and_matches_on_the_first_letter() {
16721        let mut f = Fixture::new();
16722        assert_eq!(
16723            f.run(&[b"CF.INSERT", b"i", b"CAPACITY", b"64", b"ITEMS", b"a", b"b"]),
16724            "*2\r\n:1\r\n:1\r\n"
16725        );
16726        assert_eq!(
16727            f.run(&[b"CF.DEBUG", b"i"]),
16728            "$78\r\nbktsize:2 buckets:32 items:2 deletes:0 filters:1 \
16729             max_iterations:20 expansion:1\r\n"
16730        );
16731        // The NX form has three answers rather than two, which is why it stays
16732        // integers on both protocols.
16733        assert_eq!(
16734            f.run(&[b"CF.INSERTNX", b"i", b"ITEMS", b"a", b"c"]),
16735            "*2\r\n:0\r\n:1\r\n"
16736        );
16737        assert_eq!(
16738            f.run(&[b"CF.INSERT", b"gone", b"NOCREATE", b"ITEMS", b"a"]),
16739            "-ERR not found\r\n"
16740        );
16741        assert_eq!(f.run(&[b"EXISTS", b"gone"]), ":0\r\n");
16742
16743        assert_eq!(
16744            f.run(&[b"CF.INSERT", b"i", b"CAPACITY", b"abc", b"ITEMS", b"a"]),
16745            "-Bad capacity\r\n"
16746        );
16747        // The bucket size cannot be given here, so the range names the config
16748        // that holds it instead of the option `CF.RESERVE` names.
16749        assert_eq!(
16750            f.run(&[b"CF.INSERT", b"i", b"CAPACITY", b"2", b"ITEMS", b"a"]),
16751            "-Capacity must be in the range [cf-bucket-size * 2, 1073741824]\r\n"
16752        );
16753        // Every occurrence is checked, which is where this differs from
16754        // `CF.RESERVE`: the second `CAPACITY` is an error even though the first
16755        // one is the one that would have been used.
16756        assert_eq!(
16757            f.run(&[
16758                b"CF.INSERT",
16759                b"i",
16760                b"CAPACITY",
16761                b"8",
16762                b"CAPACITY",
16763                b"2",
16764                b"ITEMS",
16765                b"a"
16766            ]),
16767            "-Capacity must be in the range [cf-bucket-size * 2, 1073741824]\r\n"
16768        );
16769        // An option is one letter and not a word, so `NOSUCH` is `NOCREATE` and
16770        // `ITEMSXYZ` is `ITEMS`, and only a letter that starts nothing is
16771        // refused.
16772        assert_eq!(
16773            f.run(&[b"CF.INSERT", b"i", b"NOSUCH", b"ITEMS", b"a"]),
16774            "*1\r\n:1\r\n"
16775        );
16776        assert_eq!(
16777            f.run(&[b"CF.INSERT", b"i", b"ITEMSXYZ", b"a"]),
16778            "*1\r\n:1\r\n"
16779        );
16780        assert_eq!(
16781            f.run(&[b"CF.INSERT", b"i", b"ZZZ", b"ITEMS", b"a"]),
16782            "-Unknown argument received\r\n"
16783        );
16784        // Everything after ITEMS is an item, even when it spells an option.
16785        assert_eq!(
16786            f.run(&[b"CF.INSERT", b"i", b"ITEMS", b"NOCREATE"]),
16787            "*1\r\n:1\r\n"
16788        );
16789        // And the two ways of sending no items at all are the same complaint.
16790        assert!(
16791            f.run(&[b"CF.INSERT", b"i", b"ITEMS"])
16792                .contains("wrong number of arguments")
16793        );
16794        assert!(
16795            f.run(&[b"CF.INSERT", b"i", b"CAPACITY"])
16796                .contains("wrong number of arguments")
16797        );
16798    }
16799
16800    /// The two walls a filter can hit, which say different things and are not
16801    /// the same wall.
16802    #[test]
16803    fn a_full_filter_and_one_that_ran_out_of_filters_answer_differently() {
16804        let mut f = Fixture::new();
16805        f.run(&[
16806            b"CF.RESERVE",
16807            b"s",
16808            b"4",
16809            b"BUCKETSIZE",
16810            b"1",
16811            b"EXPANSION",
16812            b"0",
16813        ]);
16814        for i in 0..4u32 {
16815            assert_eq!(
16816                f.run(&[b"CF.ADD", b"s", i.to_string().as_bytes()]),
16817                ":1\r\n"
16818            );
16819        }
16820        assert_eq!(f.run(&[b"CF.ADD", b"s", b"4"]), "-Filter is full\r\n");
16821        assert_eq!(f.run(&[b"CF.ADDNX", b"s", b"zz"]), "-Filter is full\r\n");
16822        // The add commands say it in a sentence and the insert commands say it
16823        // in the array, one value per item, and the array is never short.
16824        assert_eq!(
16825            f.run(&[b"CF.INSERT", b"s", b"ITEMS", b"p", b"q"]),
16826            "*2\r\n:-1\r\n:-1\r\n"
16827        );
16828        assert_eq!(
16829            f.run(&[b"CF.INSERTNX", b"s", b"ITEMS", b"0", b"q"]),
16830            "*2\r\n:0\r\n:-1\r\n"
16831        );
16832
16833        // A chain that is allowed to grow stops for a different reason, and the
16834        // count it stops at is the filter limit rather than the room: this one
16835        // gives up with three slots free. Loading a chain that already has
16836        // every filter it is allowed shows why, since it refuses an item
16837        // straight into an empty one.
16838        let full = cf_header(0, 4, 0, 32, [1, 20, 1]);
16839        assert_eq!(f.run(&[b"CF.LOADCHUNK", b"g", b"1", &full]), "+OK\r\n");
16840        assert_eq!(
16841            f.run(&[b"CF.ADD", b"g", b"q"]),
16842            "-Maximum expansions reached\r\n"
16843        );
16844        assert_eq!(
16845            f.run(&[b"CF.INFO", b"g"]),
16846            "*16\r\n+Size\r\n:680\r\n+Number of buckets\r\n:4\r\n\
16847             +Number of filters\r\n:32\r\n+Number of items inserted\r\n:0\r\n\
16848             +Number of items deleted\r\n:0\r\n+Bucket size\r\n:1\r\n\
16849             +Expansion rate\r\n:1\r\n+Max iterations\r\n:20\r\n"
16850        );
16851    }
16852
16853    /// A filter dumped a chunk at a time and put back under another key is the
16854    /// same filter, and the headers that describe one nobody could build are
16855    /// refused on the way in.
16856    #[test]
16857    fn a_cuckoo_dump_replays_into_a_filter_that_answers_the_same() {
16858        let mut f = Fixture::new();
16859        f.run(&[
16860            b"CF.RESERVE",
16861            b"src",
16862            b"8",
16863            b"BUCKETSIZE",
16864            b"2",
16865            b"EXPANSION",
16866            b"2",
16867        ]);
16868        for i in 0..40u32 {
16869            f.run(&[b"CF.ADD", b"src", i.to_string().as_bytes()]);
16870        }
16871        // Position zero asks for the header and every one after it is a byte
16872        // offset across every filter laid end to end, and the walk ends on a
16873        // zero and a nil rather than an empty chunk.
16874        let mut pos = b"0".to_vec();
16875        let mut chunks = 0;
16876        loop {
16877            let raw = f.raw(&[b"CF.SCANDUMP", b"src", &pos]);
16878            let head = String::from_utf8_lossy(&raw[..raw.len().min(24)]).into_owned();
16879            let next = head
16880                .split("\r\n")
16881                .nth(1)
16882                .and_then(|n| n.strip_prefix(':'))
16883                .expect("a two element reply of a position and a chunk")
16884                .to_owned();
16885            if next == "0" {
16886                assert!(raw.ends_with(b"$-1\r\n"), "the walk ends on a nil");
16887                break;
16888            }
16889            let body = &raw[raw.iter().position(|&b| b == b'$').expect("a bulk chunk")..];
16890            let at = body
16891                .windows(2)
16892                .position(|w| w == b"\r\n")
16893                .expect("a length line")
16894                + 2;
16895            let data = &body[at..body.len() - 2];
16896            assert_eq!(
16897                f.run(&[b"CF.LOADCHUNK", b"dst", next.as_bytes(), data]),
16898                "+OK\r\n",
16899                "loading chunk {chunks}"
16900            );
16901            pos = next.into_bytes();
16902            chunks += 1;
16903        }
16904        assert!(chunks >= 2, "a header and at least one chunk");
16905
16906        assert_eq!(f.run(&[b"CF.INFO", b"dst"]), f.run(&[b"CF.INFO", b"src"]));
16907        assert_eq!(f.run(&[b"CF.DEBUG", b"dst"]), f.run(&[b"CF.DEBUG", b"src"]));
16908        for i in 0..40u32 {
16909            assert_eq!(
16910                f.run(&[b"CF.EXISTS", b"dst", i.to_string().as_bytes()]),
16911                ":1\r\n"
16912            );
16913        }
16914
16915        // A filter with nothing in it hands out no header at all, so a client
16916        // that dumps one has nothing to load back.
16917        f.run(&[b"CF.RESERVE", b"empty", b"4", b"BUCKETSIZE", b"1"]);
16918        assert_eq!(
16919            f.run(&[b"CF.SCANDUMP", b"empty", b"0"]),
16920            "*2\r\n:0\r\n$-1\r\n"
16921        );
16922
16923        // The positions this end will not take, which are not the same set at
16924        // both ends: a dump refuses a negative one and a load takes it as an
16925        // offset and fails to find anything there.
16926        assert_eq!(
16927            f.run(&[b"CF.SCANDUMP", b"src", b"nope"]),
16928            "-Invalid position\r\n"
16929        );
16930        assert_eq!(
16931            f.run(&[b"CF.SCANDUMP", b"src", b"-1"]),
16932            "-Invalid position\r\n"
16933        );
16934        assert_eq!(
16935            f.run(&[b"CF.LOADCHUNK", b"dst", b"0", b"x"]),
16936            "-Invalid position\r\n"
16937        );
16938        assert_eq!(
16939            f.run(&[b"CF.LOADCHUNK", b"dst", b"99999", b"x"]),
16940            "-Couldn't load chunk!\r\n"
16941        );
16942        // A header on top of a filter is refused rather than merged.
16943        let good = cf_header(0, 8, 0, 1, [2, 20, 1]);
16944        assert_eq!(
16945            f.run(&[b"CF.LOADCHUNK", b"dst", b"1", &good]),
16946            "-ERR item exists\r\n"
16947        );
16948        // A chunk that is not the size of a header where a header should have
16949        // been is one sentence, and one that is the size of a header and
16950        // describes a filter nobody could build is another.
16951        assert_eq!(
16952            f.run(&[b"CF.LOADCHUNK", b"n1", b"1", b"short"]),
16953            "-Invalid header\r\n"
16954        );
16955        for (why, bad) in [
16956            ("no filters at all", cf_header(0, 8, 0, 0, [2, 20, 1])),
16957            ("no buckets", cf_header(0, 0, 0, 1, [2, 20, 1])),
16958            (
16959                "a bucket count that is not a power of two",
16960                cf_header(0, 3, 0, 1, [2, 20, 1]),
16961            ),
16962            ("an empty bucket", cf_header(0, 8, 0, 1, [0, 20, 1])),
16963            ("no kicks", cf_header(0, 8, 0, 1, [2, 0, 1])),
16964            (
16965                "a growth nobody could reach",
16966                cf_header(0, 8, 0, 1, [2, 20, 32769]),
16967            ),
16968            (
16969                "a chain that cannot grow and did",
16970                cf_header(0, 8, 0, 2, [2, 20, 0]),
16971            ),
16972            // The count is written in eight bytes and read into two, so a
16973            // number that is a multiple of the second arrives as none.
16974            (
16975                "a filter count that wraps",
16976                cf_header(0, 8, 0, 65_536, [2, 20, 1]),
16977            ),
16978        ] {
16979            assert_eq!(
16980                f.run(&[b"CF.LOADCHUNK", b"bad", b"1", &bad]),
16981                "-Couldn't create filter!\r\n",
16982                "{why}"
16983            );
16984        }
16985    }
16986
16987    /// The RESP3 shapes, which are where this family differs most from RESP2
16988    /// and where one of its answers stops being readable.
16989    #[test]
16990    fn the_cuckoo_family_answers_in_resp3_spelling_too() {
16991        let mut f = Fixture::new();
16992        f.out.set_proto(Proto::Resp3);
16993        assert_eq!(f.run(&[b"CF.ADD", b"c", b"a"]), "#t\r\n");
16994        assert_eq!(f.run(&[b"CF.ADD", b"c", b"a"]), "#t\r\n");
16995        assert_eq!(f.run(&[b"CF.ADDNX", b"c", b"a"]), "#f\r\n");
16996        assert_eq!(f.run(&[b"CF.EXISTS", b"c", b"a"]), "#t\r\n");
16997        assert_eq!(
16998            f.run(&[b"CF.MEXISTS", b"c", b"a", b"z"]),
16999            "*2\r\n#t\r\n#f\r\n"
17000        );
17001        assert_eq!(f.run(&[b"CF.DEL", b"c", b"a"]), "#t\r\n");
17002        assert_eq!(f.run(&[b"CF.DEL", b"c", b"z"]), "#f\r\n");
17003        // The count stays an integer, because it counts rather than answers.
17004        assert_eq!(f.run(&[b"CF.COUNT", b"c", b"a"]), ":1\r\n");
17005        assert_eq!(
17006            f.run(&[b"CF.INFO", b"c"]),
17007            "%8\r\n+Size\r\n:1080\r\n+Number of buckets\r\n:512\r\n\
17008             +Number of filters\r\n:1\r\n+Number of items inserted\r\n:1\r\n\
17009             +Number of items deleted\r\n:1\r\n+Bucket size\r\n:2\r\n\
17010             +Expansion rate\r\n:1\r\n+Max iterations\r\n:20\r\n"
17011        );
17012
17013        // `CF.INSERT` writes a boolean per item here and an integer per item on
17014        // RESP2, and minus one has nowhere to go in a boolean, so a RESP3
17015        // client cannot tell an item that did not fit from one that is already
17016        // there. `CF.INSERTNX` keeps its integers for exactly that reason.
17017        f.run(&[
17018            b"CF.RESERVE",
17019            b"s",
17020            b"4",
17021            b"BUCKETSIZE",
17022            b"1",
17023            b"EXPANSION",
17024            b"0",
17025        ]);
17026        assert_eq!(
17027            f.run(&[
17028                b"CF.INSERT",
17029                b"s",
17030                b"ITEMS",
17031                b"a",
17032                b"b",
17033                b"c",
17034                b"d",
17035                b"e",
17036                b"f"
17037            ]),
17038            "*6\r\n#t\r\n#t\r\n#t\r\n#f\r\n#f\r\n#f\r\n"
17039        );
17040        assert_eq!(
17041            f.run(&[b"CF.INSERTNX", b"s", b"ITEMS", b"a", b"zz"]),
17042            "*2\r\n:0\r\n:-1\r\n"
17043        );
17044        assert_eq!(f.run(&[b"CF.ADD", b"s", b"zzz"]), "-Filter is full\r\n");
17045        // The end of a dump is a nil and not an empty chunk, which is one
17046        // underscore here and a negative length on RESP2.
17047        assert_eq!(f.run(&[b"CF.SCANDUMP", b"c", b"9999"]), "*2\r\n:0\r\n_\r\n");
17048    }
17049
17050    // ------------------------------------------------------------------- cms
17051
17052    /// A sketch is made from either end, and both constructors look at the key
17053    /// before they look at their arguments.
17054    #[test]
17055    fn a_sketch_is_made_from_a_size_or_from_an_error_rate() {
17056        let mut f = Fixture::new();
17057        assert_eq!(f.run(&[b"CMS.INITBYDIM", b"d", b"100", b"5"]), "+OK\r\n");
17058        assert_eq!(
17059            f.run(&[b"CMS.INFO", b"d"]),
17060            "*6\r\n+width\r\n:100\r\n+depth\r\n:5\r\n+count\r\n:0\r\n"
17061        );
17062        assert_eq!(f.run(&[b"TYPE", b"d"]), "+CMSk-TYPE\r\n");
17063        assert_eq!(f.run(&[b"OBJECT", b"ENCODING", b"d"]), "$3\r\nraw\r\n");
17064        // Two over the error rounded up, and the log of the probability over the
17065        // log of a half rounded up, which for these two is 200 by 6.
17066        assert_eq!(
17067            f.run(&[b"CMS.INITBYPROB", b"p", b"0.01", b"0.03"]),
17068            "+OK\r\n"
17069        );
17070        assert_eq!(
17071            f.run(&[b"CMS.INFO", b"p"]),
17072            "*6\r\n+width\r\n:200\r\n+depth\r\n:6\r\n+count\r\n:0\r\n"
17073        );
17074        // The key is checked first, so a width of zero at a key that is already
17075        // there is about the key and not about the width.
17076        assert_eq!(
17077            f.run(&[b"CMS.INITBYDIM", b"d", b"0", b"2"]),
17078            "-CMS: key already exists\r\n"
17079        );
17080        assert_eq!(
17081            f.run(&[b"CMS.INITBYDIM", b"new", b"0", b"2"]),
17082            "-CMS: invalid width\r\n"
17083        );
17084        assert_eq!(
17085            f.run(&[b"CMS.INITBYDIM", b"new", b"2", b"0"]),
17086            "-CMS: invalid depth\r\n"
17087        );
17088        assert_eq!(
17089            f.run(&[b"CMS.INITBYPROB", b"new", b"0", b"0.5"]),
17090            "-CMS: invalid overestimation value\r\n"
17091        );
17092        assert_eq!(
17093            f.run(&[b"CMS.INITBYPROB", b"new", b"0.1", b"1"]),
17094            "-CMS: invalid prob value\r\n"
17095        );
17096        // A probability whose float conversion is zero has no depth, and a width
17097        // past a signed sixty four bit integer has no width, and both are the
17098        // same sentence.
17099        assert_eq!(
17100            f.run(&[b"CMS.INITBYPROB", b"new", b"0.5", b"1e-46"]),
17101            "-CMS: invalid init arguments\r\n"
17102        );
17103        // And a sketch bigger than a gibibyte of counters is refused here where
17104        // the reference reserves address space nobody has touched, which is
17105        // D-47.
17106        assert_eq!(
17107            f.run(&[b"CMS.INITBYDIM", b"new", b"268435457", b"1"]),
17108            "-CMS: Insufficient memory to create the key\r\n"
17109        );
17110        assert_eq!(f.run(&[b"EXISTS", b"new"]), ":0\r\n");
17111    }
17112
17113    /// Every pair is parsed before any of them lands, the counters saturate,
17114    /// and the count is a signed total of what was asked for.
17115    #[test]
17116    fn increments_are_parsed_whole_and_the_counters_saturate() {
17117        let mut f = Fixture::new();
17118        f.run(&[b"CMS.INITBYDIM", b"c", b"100", b"4"]);
17119        assert_eq!(
17120            f.run(&[b"CMS.INCRBY", b"c", b"a", b"3", b"b", b"4"]),
17121            "*2\r\n:3\r\n:4\r\n"
17122        );
17123        // An item that is incremented twice in one call sees its own first
17124        // increment in the reply to the second.
17125        assert_eq!(
17126            f.run(&[b"CMS.INCRBY", b"c", b"a", b"1", b"a", b"1"]),
17127            "*2\r\n:4\r\n:5\r\n"
17128        );
17129        // A bad number anywhere means nothing at all is applied.
17130        assert_eq!(
17131            f.run(&[b"CMS.INCRBY", b"c", b"a", b"9", b"b", b"x"]),
17132            "-CMS: Cannot parse number\r\n"
17133        );
17134        assert_eq!(
17135            f.run(&[b"CMS.INCRBY", b"c", b"a", b"9", b"b", b"-1"]),
17136            "-CMS: Number cannot be negative\r\n"
17137        );
17138        assert_eq!(
17139            f.run(&[b"CMS.QUERY", b"c", b"a", b"b"]),
17140            "*2\r\n:5\r\n:4\r\n"
17141        );
17142        // The counters stop at four billion and the item that stopped says so in
17143        // its own slot while the one beside it answers a number.
17144        f.run(&[b"CMS.INCRBY", b"c", b"a", b"4294967295"]);
17145        assert_eq!(
17146            f.run(&[b"CMS.INCRBY", b"c", b"a", b"1", b"b", b"1"]),
17147            "*2\r\n-CMS: INCRBY overflow\r\n:5\r\n"
17148        );
17149        assert_eq!(f.run(&[b"CMS.QUERY", b"c", b"a"]), "*1\r\n:4294967295\r\n");
17150        // The count is what was asked for rather than what landed, and it is
17151        // signed, so a big enough total comes back negative.
17152        f.run(&[b"CMS.INITBYDIM", b"w", b"4", b"1"]);
17153        f.run(&[b"CMS.INCRBY", b"w", b"x", b"9223372036854775807"]);
17154        f.run(&[b"CMS.INCRBY", b"w", b"x", b"1"]);
17155        assert_eq!(
17156            f.run(&[b"CMS.INFO", b"w"]),
17157            "*6\r\n+width\r\n:4\r\n+depth\r\n:1\r\n+count\r\n:-9223372036854775808\r\n"
17158        );
17159        // An odd number of arguments after the key is an arity error and not a
17160        // syntax one.
17161        assert!(
17162            f.run(&[b"CMS.INCRBY", b"c", b"a", b"1", b"b"])
17163                .contains("wrong number of arguments")
17164        );
17165        assert_eq!(
17166            f.run(&[b"CMS.INCRBY", b"nope", b"a", b"1"]),
17167            "-CMS: key does not exist\r\n"
17168        );
17169        assert_eq!(
17170            f.run(&[b"CMS.QUERY", b"nope", b"a"]),
17171            "-CMS: key does not exist\r\n"
17172        );
17173    }
17174
17175    /// A merge overwrites its destination, and it is worked out in full before
17176    /// any of it is written.
17177    #[test]
17178    fn a_merge_lands_whole_or_not_at_all() {
17179        let mut f = Fixture::new();
17180        for name in [&b"m1"[..], b"m2", b"dst"] {
17181            f.run(&[b"CMS.INITBYDIM", name, b"64", b"3"]);
17182        }
17183        f.run(&[b"CMS.INCRBY", b"m1", b"a", b"5"]);
17184        f.run(&[b"CMS.INCRBY", b"m2", b"a", b"7"]);
17185        assert_eq!(
17186            f.run(&[b"CMS.MERGE", b"dst", b"2", b"m1", b"m2"]),
17187            "+OK\r\n"
17188        );
17189        assert_eq!(f.run(&[b"CMS.QUERY", b"dst", b"a"]), "*1\r\n:12\r\n");
17190        // Overwritten and not added to, so the same merge twice is the same
17191        // answer twice.
17192        assert_eq!(
17193            f.run(&[b"CMS.MERGE", b"dst", b"2", b"m1", b"m2"]),
17194            "+OK\r\n"
17195        );
17196        assert_eq!(f.run(&[b"CMS.QUERY", b"dst", b"a"]), "*1\r\n:12\r\n");
17197        assert_eq!(
17198            f.run(&[
17199                b"CMS.MERGE",
17200                b"dst",
17201                b"2",
17202                b"m1",
17203                b"m2",
17204                b"WEIGHTS",
17205                b"2",
17206                b"3"
17207            ]),
17208            "+OK\r\n"
17209        );
17210        assert_eq!(f.run(&[b"CMS.QUERY", b"dst", b"a"]), "*1\r\n:31\r\n");
17211        // A cell times a weight is checked wide rather than wrapped, so this is
17212        // a refusal and the destination is left exactly as it was.
17213        assert_eq!(
17214            f.run(&[
17215                b"CMS.MERGE",
17216                b"dst",
17217                b"1",
17218                b"m1",
17219                b"WEIGHTS",
17220                b"4611686018427387904"
17221            ]),
17222            "-CMS: MERGE overflow\r\n"
17223        );
17224        assert_eq!(f.run(&[b"CMS.QUERY", b"dst", b"a"]), "*1\r\n:31\r\n");
17225        // The destination comes first, then the count, then the layout, then the
17226        // weights, then the sources one at a time.
17227        f.run(&[b"CMS.INITBYDIM", b"wide", b"128", b"3"]);
17228        assert_eq!(
17229            f.run(&[b"CMS.MERGE", b"gone", b"1", b"m1"]),
17230            "-CMS: key does not exist\r\n"
17231        );
17232        assert_eq!(
17233            f.run(&[b"CMS.MERGE", b"dst", b"0", b"m1"]),
17234            "-CMS: Number of keys must be positive\r\n"
17235        );
17236        assert_eq!(
17237            f.run(&[b"CMS.MERGE", b"dst", b"3", b"m1"]),
17238            "-CMS: wrong number of keys\r\n"
17239        );
17240        assert_eq!(
17241            f.run(&[b"CMS.MERGE", b"dst", b"1", b"m1", b"WEIGHTS", b"1", b"2"]),
17242            "-CMS: wrong number of keys/weights\r\n"
17243        );
17244        assert_eq!(
17245            f.run(&[b"CMS.MERGE", b"dst", b"1", b"wide"]),
17246            "-CMS: width/depth is not equal\r\n"
17247        );
17248        assert_eq!(
17249            f.run(&[b"CMS.MERGE", b"dst", b"1", b"gone"]),
17250            "-CMS: key does not exist\r\n"
17251        );
17252    }
17253
17254    /// A key holding anything else is `WRONGTYPE` to all six, and a key holding
17255    /// a sketch is refused by the two commands that would have to serialise it.
17256    #[test]
17257    fn a_sketch_is_a_module_key_to_the_rest_of_the_keyspace() {
17258        let mut f = Fixture::new();
17259        f.run(&[b"SET", b"s", b"text"]);
17260        for cmd in [
17261            vec![&b"CMS.INITBYDIM"[..], b"s", b"8", b"2"],
17262            vec![&b"CMS.INCRBY"[..], b"s", b"a", b"1"],
17263            vec![&b"CMS.QUERY"[..], b"s", b"a"],
17264            vec![&b"CMS.INFO"[..], b"s"],
17265            vec![&b"CMS.MERGE"[..], b"s", b"1", b"s"],
17266        ] {
17267            let name = String::from_utf8_lossy(cmd[0]).into_owned();
17268            let reply = f.run(&cmd);
17269            // The two constructors see the key before anything else and say so
17270            // in the module's own words, and the rest are `WRONGTYPE`.
17271            assert!(
17272                reply.starts_with("-WRONGTYPE") || reply == "-CMS: key already exists\r\n",
17273                "{name}: {reply}"
17274            );
17275        }
17276        f.run(&[b"CMS.INITBYDIM", b"c", b"64", b"2"]);
17277        // Redis refuses to copy a module key that has no copy callback, and
17278        // these are its words rather than ours. `DUMP` is the other half of
17279        // D-48: the reference has a payload for one of these and we do not.
17280        assert_eq!(
17281            f.run(&[b"COPY", b"c", b"c2"]),
17282            "-ERR not supported for this module key\r\n"
17283        );
17284        assert_eq!(
17285            f.run(&[b"DUMP", b"c"]),
17286            "-ERR DUMP is not supported for this module key\r\n"
17287        );
17288        // A graph is nobody's module and keeps its own sentence.
17289        f.run(&[b"G.NADD", b"g", b"a"]);
17290        assert_eq!(
17291            f.run(&[b"COPY", b"g", b"g2"]),
17292            "-ERR COPY is not supported for a graph\r\n"
17293        );
17294        assert_eq!(
17295            f.run(&[b"DUMP", b"g"]),
17296            "-ERR DUMP is not supported for a graph\r\n"
17297        );
17298        // Everything that does not need a byte shape works on a sketch key the
17299        // way it works on any other.
17300        assert_eq!(f.run(&[b"EXPIRE", b"c", b"100"]), ":1\r\n");
17301        assert_eq!(f.run(&[b"PERSIST", b"c"]), ":1\r\n");
17302        assert_eq!(f.run(&[b"RENAME", b"c", b"c3"]), "+OK\r\n");
17303        assert_eq!(f.run(&[b"TYPE", b"c3"]), "+CMSk-TYPE\r\n");
17304        assert_eq!(f.run(&[b"DEL", b"c3"]), ":1\r\n");
17305    }
17306
17307    // ------------------------------------------------------------------ topk
17308
17309    /// `TOPK.RESERVE` takes three arguments or six, and looks at the key before
17310    /// it looks at any of them.
17311    #[test]
17312    fn a_reserve_takes_three_arguments_or_six() {
17313        let mut f = Fixture::new();
17314        assert_eq!(f.run(&[b"TOPK.RESERVE", b"t", b"5"]), "+OK\r\n");
17315        assert_eq!(
17316            f.run(&[b"TOPK.INFO", b"t"]),
17317            "*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"
17318        );
17319        // Four arguments and five are an arity error rather than a defaulted
17320        // depth or decay.
17321        for cmd in [
17322            vec![&b"TOPK.RESERVE"[..], b"u", b"5", b"8"],
17323            vec![&b"TOPK.RESERVE"[..], b"u", b"5", b"8", b"7"],
17324        ] {
17325            assert!(f.run(&cmd).contains("wrong number of arguments"));
17326        }
17327        assert_eq!(
17328            f.run(&[b"TOPK.RESERVE", b"u", b"5", b"8", b"7", b"0.5"]),
17329            "+OK\r\n"
17330        );
17331        // The key is checked first, so a reserve with nothing else right at a
17332        // key that is taken still says the key is taken.
17333        assert_eq!(
17334            f.run(&[b"TOPK.RESERVE", b"u", b"0", b"0", b"0", b"9"]),
17335            "-TopK: key already exists\r\n"
17336        );
17337        assert_eq!(
17338            f.run(&[b"TOPK.RESERVE", b"v", b"0"]),
17339            "-TopK: invalid k\r\n"
17340        );
17341        assert_eq!(
17342            f.run(&[b"TOPK.RESERVE", b"v", b"1", b"0", b"7", b"0.9"]),
17343            "-TopK: invalid width\r\n"
17344        );
17345        assert_eq!(
17346            f.run(&[b"TOPK.RESERVE", b"v", b"1", b"8", b"x", b"0.9"]),
17347            "-TopK: invalid depth\r\n"
17348        );
17349        // Zero is out and one is in, which is the module's `> 0` and `<= 1`.
17350        assert_eq!(
17351            f.run(&[b"TOPK.RESERVE", b"v", b"1", b"8", b"7", b"0"]),
17352            "-TopK: invalid decay value. must be '<= 1' & '> 0'\r\n"
17353        );
17354        assert_eq!(
17355            f.run(&[b"TOPK.RESERVE", b"v", b"1", b"8", b"7", b"1"]),
17356            "+OK\r\n"
17357        );
17358        // Past the cap, with the one sentence in the family that has a prefix.
17359        assert_eq!(
17360            f.run(&[
17361                b"TOPK.RESERVE",
17362                b"w",
17363                b"1",
17364                b"4294967295",
17365                b"4294967295",
17366                b"0.9"
17367            ]),
17368            "-ERR Insufficient memory to create topk data structure\r\n"
17369        );
17370    }
17371
17372    /// What the sketch keeps, and the three ways of asking about it.
17373    #[test]
17374    fn the_kept_set_is_what_query_and_list_answer_from() {
17375        let mut f = Fixture::new();
17376        f.run(&[b"TOPK.RESERVE", b"t", b"2", b"1000", b"5", b"0.9"]);
17377        // A null an item while there is room, then the name of whatever was
17378        // pushed out.
17379        assert_eq!(
17380            f.run(&[b"TOPK.ADD", b"t", b"a", b"b"]),
17381            "*2\r\n$-1\r\n$-1\r\n"
17382        );
17383        assert_eq!(f.run(&[b"TOPK.INCRBY", b"t", b"a", b"10"]), "*1\r\n$-1\r\n");
17384        // Two slots are full and `c` arrives with a count of one, which is not
17385        // under the smallest kept count, so it takes that slot straight away.
17386        assert_eq!(f.run(&[b"TOPK.ADD", b"t", b"c"]), "*1\r\n$1\r\nb\r\n");
17387        assert_eq!(f.run(&[b"TOPK.INCRBY", b"t", b"c", b"5"]), "*1\r\n$-1\r\n");
17388        assert_eq!(
17389            f.run(&[b"TOPK.QUERY", b"t", b"a", b"b", b"c"]),
17390            "*3\r\n:1\r\n:0\r\n:1\r\n"
17391        );
17392        // The table still counts what the kept set let go of.
17393        assert_eq!(
17394            f.run(&[b"TOPK.COUNT", b"t", b"a", b"b", b"c"]),
17395            "*3\r\n:11\r\n:1\r\n:6\r\n"
17396        );
17397        assert_eq!(f.run(&[b"TOPK.LIST", b"t"]), "*2\r\n$1\r\na\r\n$1\r\nc\r\n");
17398        assert_eq!(
17399            f.run(&[b"TOPK.LIST", b"t", b"WITHCOUNT"]),
17400            "*4\r\n$1\r\na\r\n:11\r\n$1\r\nc\r\n:6\r\n"
17401        );
17402        // Any prefix of the keyword turns the counts on, the empty string
17403        // included, and only a longer word or a different one is refused.
17404        assert_eq!(
17405            f.run(&[b"TOPK.LIST", b"t", b"w"]),
17406            f.run(&[b"TOPK.LIST", b"t", b"WITHCOUNT"])
17407        );
17408        assert_eq!(
17409            f.run(&[b"TOPK.LIST", b"t", b""]),
17410            f.run(&[b"TOPK.LIST", b"t", b"WITHCOUNT"])
17411        );
17412        assert_eq!(
17413            f.run(&[b"TOPK.LIST", b"t", b"WITHCOUNTS"]),
17414            "-WITHCOUNT keyword expected\r\n"
17415        );
17416        // And the keyword is looked at before the key, so a missing key with a
17417        // bad keyword complains about the keyword.
17418        assert_eq!(
17419            f.run(&[b"TOPK.LIST", b"missing", b"nope"]),
17420            "-WITHCOUNT keyword expected\r\n"
17421        );
17422        assert_eq!(
17423            f.run(&[b"TOPK.LIST", b"missing"]),
17424            "-TopK: key does not exist\r\n"
17425        );
17426        // An item counted zero times is kept and not listed.
17427        f.run(&[b"TOPK.RESERVE", b"z", b"3"]);
17428        assert_eq!(
17429            f.run(&[b"TOPK.INCRBY", b"z", b"nothing", b"0"]),
17430            "*1\r\n$-1\r\n"
17431        );
17432        assert_eq!(f.run(&[b"TOPK.QUERY", b"z", b"nothing"]), "*1\r\n:1\r\n");
17433        assert_eq!(f.run(&[b"TOPK.LIST", b"z"]), "*0\r\n");
17434    }
17435
17436    /// `TOPK.INCRBY` applies as it goes, so a bad increment leaves everything
17437    /// before it counted, and the reply counts what it wrote.
17438    #[test]
17439    fn an_increment_is_applied_as_it_goes_and_stops_at_a_bad_one() {
17440        let mut f = Fixture::new();
17441        f.run(&[b"TOPK.RESERVE", b"t", b"5", b"1000", b"5", b"0.9"]);
17442        // Three pairs, the middle one bad: two elements come back, one of them
17443        // the error, and the array header says two rather than three. That last
17444        // part is D-51 and it is why a client here stays in step.
17445        assert_eq!(
17446            f.run(&[b"TOPK.INCRBY", b"t", b"a", b"3", b"b", b"-1", b"c", b"4"]),
17447            format!(
17448                "*2\r\n$-1\r\n-{}\r\n",
17449                "TopK: increment must be an integer greater or equal to 0                            and smaller or equal to 100,000"
17450            )
17451        );
17452        assert_eq!(
17453            f.run(&[b"TOPK.COUNT", b"t", b"a", b"b", b"c"]),
17454            "*3\r\n:3\r\n:0\r\n:0\r\n"
17455        );
17456        // A hundred thousand is in and one more is out.
17457        assert_eq!(
17458            f.run(&[b"TOPK.INCRBY", b"t", b"a", b"100000"]),
17459            "*1\r\n$-1\r\n"
17460        );
17461        assert!(
17462            f.run(&[b"TOPK.INCRBY", b"t", b"a", b"100001"])
17463                .contains("smaller or equal to 100,000")
17464        );
17465        // Pairs have to be pairs.
17466        assert!(
17467            f.run(&[b"TOPK.INCRBY", b"t", b"a", b"1", b"b"])
17468                .contains("wrong number of arguments")
17469        );
17470        assert_eq!(f.run(&[b"TOPK.COUNT", b"t", b"a"]), "*1\r\n:100003\r\n");
17471    }
17472
17473    /// The RESP3 shapes, which are the two the protocols disagree about.
17474    #[test]
17475    fn a_query_is_a_bool_and_info_is_a_map_on_resp3() {
17476        let mut f = Fixture::new();
17477        f.run(&[b"HELLO", b"3"]);
17478        f.run(&[b"TOPK.RESERVE", b"t", b"2", b"8", b"7", b"0.5"]);
17479        f.run(&[b"TOPK.ADD", b"t", b"a"]);
17480        assert_eq!(
17481            f.run(&[b"TOPK.QUERY", b"t", b"a", b"b"]),
17482            "*2\r\n#t\r\n#f\r\n"
17483        );
17484        // The count stays an integer on both protocols.
17485        assert_eq!(f.run(&[b"TOPK.COUNT", b"t", b"a"]), "*1\r\n:1\r\n");
17486        assert_eq!(
17487            f.run(&[b"TOPK.INFO", b"t"]),
17488            "%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"
17489        );
17490        assert_eq!(f.run(&[b"TOPK.ADD", b"t", b"a"]), "*1\r\n_\r\n");
17491    }
17492
17493    /// A top k key answers the module sentences the other sketch families
17494    /// answer, and its own word for its type.
17495    #[test]
17496    fn a_top_k_sketch_is_a_module_key_to_the_rest_of_the_keyspace() {
17497        let mut f = Fixture::new();
17498        f.run(&[b"SET", b"s", b"text"]);
17499        for cmd in [
17500            vec![&b"TOPK.RESERVE"[..], b"s", b"5"],
17501            vec![&b"TOPK.ADD"[..], b"s", b"a"],
17502            vec![&b"TOPK.INCRBY"[..], b"s", b"a", b"1"],
17503            vec![&b"TOPK.QUERY"[..], b"s", b"a"],
17504            vec![&b"TOPK.COUNT"[..], b"s", b"a"],
17505            vec![&b"TOPK.LIST"[..], b"s"],
17506            vec![&b"TOPK.INFO"[..], b"s"],
17507        ] {
17508            let name = String::from_utf8_lossy(cmd[0]).into_owned();
17509            let reply = f.run(&cmd);
17510            assert!(
17511                reply.starts_with("-WRONGTYPE") || reply == "-TopK: key already exists\r\n",
17512                "{name}: {reply}"
17513            );
17514        }
17515        f.run(&[b"TOPK.RESERVE", b"t", b"5"]);
17516        assert_eq!(
17517            f.run(&[b"COPY", b"t", b"t2"]),
17518            "-ERR not supported for this module key\r\n"
17519        );
17520        assert_eq!(
17521            f.run(&[b"DUMP", b"t"]),
17522            "-ERR DUMP is not supported for this module key\r\n"
17523        );
17524        assert_eq!(f.run(&[b"EXPIRE", b"t", b"100"]), ":1\r\n");
17525        assert_eq!(f.run(&[b"PERSIST", b"t"]), ":1\r\n");
17526        assert_eq!(f.run(&[b"RENAME", b"t", b"t3"]), "+OK\r\n");
17527        assert_eq!(f.run(&[b"TYPE", b"t3"]), "+TopK-TYPE\r\n");
17528        assert_eq!(f.run(&[b"OBJECT", b"ENCODING", b"t3"]), "$3\r\nraw\r\n");
17529        assert_eq!(f.run(&[b"DEL", b"t3"]), ":1\r\n");
17530        // Every one of the six that is not the constructor says the same thing
17531        // about a key that is not there.
17532        assert_eq!(
17533            f.run(&[b"TOPK.INFO", b"t3"]),
17534            "-TopK: key does not exist\r\n"
17535        );
17536    }
17537
17538    // --------------------------------------------------------------- tdigest
17539
17540    /// `TDIGEST.CREATE` takes two arguments or four, and the keyword search is a
17541    /// search rather than a lookup.
17542    #[test]
17543    fn a_create_takes_two_arguments_or_four_and_reads_the_last_one() {
17544        let mut f = Fixture::new();
17545        assert_eq!(f.run(&[b"TDIGEST.CREATE", b"t"]), "+OK\r\n");
17546        // A hundred is the default and the capacity is six times it plus ten.
17547        assert_eq!(
17548            f.run(&[b"TDIGEST.INFO", b"t"]),
17549            "*18\r\n+Compression\r\n:100\r\n+Capacity\r\n:610\r\n+Merged nodes\r\n:0\r\n\
17550             +Unmerged nodes\r\n:0\r\n+Merged weight\r\n:0\r\n+Unmerged weight\r\n:0\r\n\
17551             +Observations\r\n:0\r\n+Total compressions\r\n:0\r\n+Memory usage\r\n:9840\r\n"
17552        );
17553        assert_eq!(
17554            f.run(&[b"TDIGEST.CREATE", b"t"]),
17555            "-ERR T-Digest: key already exists\r\n"
17556        );
17557        // Three arguments is an arity error and not a missing keyword.
17558        assert!(
17559            f.run(&[b"TDIGEST.CREATE", b"u", b"COMPRESSION"])
17560                .contains("wrong number of arguments")
17561        );
17562        assert_eq!(
17563            f.run(&[b"TDIGEST.CREATE", b"u", b"COMPRESSION", b"1000"]),
17564            "+OK\r\n"
17565        );
17566        assert_eq!(
17567            f.run(&[b"TDIGEST.CREATE", b"v", b"compression", b"1"]),
17568            "+OK\r\n"
17569        );
17570        // The word is looked for across both trailing arguments and the number
17571        // is then read out of the last one whatever was found, so this looks for
17572        // a number inside the word `COMPRESSION` and does not find one.
17573        assert_eq!(
17574            f.run(&[b"TDIGEST.CREATE", b"w", b"100", b"COMPRESSION"]),
17575            "-ERR T-Digest: error parsing compression parameter\r\n"
17576        );
17577        assert_eq!(
17578            f.run(&[b"TDIGEST.CREATE", b"w", b"NOPE", b"100"]),
17579            "-ERR T-Digest: wrong keyword\r\n"
17580        );
17581        assert_eq!(
17582            f.run(&[b"TDIGEST.CREATE", b"w", b"COMPRESSION", b"1.5"]),
17583            "-ERR T-Digest: error parsing compression parameter\r\n"
17584        );
17585        assert_eq!(
17586            f.run(&[b"TDIGEST.CREATE", b"w", b"COMPRESSION", b"0"]),
17587            "-ERR T-Digest: compression parameter needs to be a positive integer\r\n"
17588        );
17589        // The reference's own ceiling, which is where the capacity stops fitting
17590        // in an int, and one past it.
17591        assert_eq!(
17592            f.run(&[b"TDIGEST.CREATE", b"w", b"COMPRESSION", b"357913942"]),
17593            "-ERR T-Digest: allocation failed\r\n"
17594        );
17595        // And ours, which is a gibibyte of centroids and is D-52.
17596        assert_eq!(
17597            f.run(&[b"TDIGEST.CREATE", b"w", b"COMPRESSION", b"100000000"]),
17598            "-ERR T-Digest: allocation failed\r\n"
17599        );
17600        // The key is checked before the arguments, so a bad compression at a key
17601        // that is already a digest still says the key is taken.
17602        assert_eq!(
17603            f.run(&[b"TDIGEST.CREATE", b"t", b"COMPRESSION", b"0"]),
17604            "-ERR T-Digest: key already exists\r\n"
17605        );
17606    }
17607
17608    /// The four samples every note about this family is written against, and the
17609    /// answers a real 8.10.1 gives for them.
17610    #[test]
17611    fn the_quantile_family_answers_what_the_module_answers() {
17612        let mut f = Fixture::new();
17613        f.run(&[b"TDIGEST.CREATE", b"s"]);
17614        assert_eq!(
17615            f.run(&[b"TDIGEST.ADD", b"s", b"1", b"2", b"3", b"4"]),
17616            "+OK\r\n"
17617        );
17618        assert_eq!(f.run(&[b"TDIGEST.MIN", b"s"]), "$1\r\n1\r\n");
17619        assert_eq!(f.run(&[b"TDIGEST.MAX", b"s"]), "$1\r\n4\r\n");
17620        // The cdf of a sample is the weight below it plus half its own.
17621        assert_eq!(
17622            f.run(&[b"TDIGEST.CDF", b"s", b"1", b"2", b"3", b"4"]),
17623            "*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"
17624        );
17625        assert_eq!(
17626            f.run(&[b"TDIGEST.QUANTILE", b"s", b"0", b"0.5", b"1"]),
17627            "*3\r\n$1\r\n1\r\n$1\r\n3\r\n$1\r\n4\r\n"
17628        );
17629        // Out of order, the walk restarts, and 0.5 answers 3 either way while
17630        // the two after it are read from the front again.
17631        assert_eq!(
17632            f.run(&[b"TDIGEST.QUANTILE", b"s", b"0.5", b"0.1", b"0.9"]),
17633            "*3\r\n$1\r\n3\r\n$1\r\n1\r\n$1\r\n4\r\n"
17634        );
17635        assert_eq!(
17636            f.run(&[b"TDIGEST.RANK", b"s", b"0", b"1", b"3", b"4", b"5"]),
17637            "*5\r\n:-1\r\n:0\r\n:2\r\n:3\r\n:4\r\n"
17638        );
17639        assert_eq!(
17640            f.run(&[b"TDIGEST.REVRANK", b"s", b"0", b"1", b"3", b"4", b"5"]),
17641            "*5\r\n:4\r\n:3\r\n:1\r\n:0\r\n:-1\r\n"
17642        );
17643        assert_eq!(
17644            f.run(&[b"TDIGEST.BYRANK", b"s", b"0", b"1", b"3", b"4"]),
17645            "*4\r\n$1\r\n1\r\n$1\r\n2\r\n$1\r\n4\r\n$3\r\ninf\r\n"
17646        );
17647        assert_eq!(
17648            f.run(&[b"TDIGEST.BYREVRANK", b"s", b"0", b"1", b"3", b"4"]),
17649            "*4\r\n$1\r\n4\r\n$1\r\n3\r\n$1\r\n1\r\n$4\r\n-inf\r\n"
17650        );
17651        assert_eq!(
17652            f.run(&[b"TDIGEST.TRIMMED_MEAN", b"s", b"0", b"1"]),
17653            "$3\r\n2.5\r\n"
17654        );
17655        assert_eq!(
17656            f.run(&[b"TDIGEST.TRIMMED_MEAN", b"s", b"0.25", b"0.75"]),
17657            "$3\r\n2.5\r\n"
17658        );
17659        // The ranges, which are separate sentences from the parse failures.
17660        assert_eq!(
17661            f.run(&[b"TDIGEST.QUANTILE", b"s", b"1.1"]),
17662            "-ERR T-Digest: quantile should be in [0,1]\r\n"
17663        );
17664        assert_eq!(
17665            f.run(&[b"TDIGEST.QUANTILE", b"s", b"zzz"]),
17666            "-ERR T-Digest: error parsing quantile\r\n"
17667        );
17668        assert_eq!(
17669            f.run(&[b"TDIGEST.CDF", b"s", b"zzz"]),
17670            "-ERR T-Digest: error parsing cdf\r\n"
17671        );
17672        assert_eq!(
17673            f.run(&[b"TDIGEST.RANK", b"s", b"zzz"]),
17674            "-ERR T-Digest: error parsing value\r\n"
17675        );
17676        assert_eq!(
17677            f.run(&[b"TDIGEST.BYRANK", b"s", b"-1"]),
17678            "-ERR T-Digest: rank needs to be non negative\r\n"
17679        );
17680        assert_eq!(
17681            f.run(&[b"TDIGEST.BYRANK", b"s", b"1.5"]),
17682            "-ERR T-Digest: error parsing rank\r\n"
17683        );
17684        // Both cuts have their own parse sentence and share the range one, and
17685        // equal cuts are refused rather than answering nothing.
17686        assert_eq!(
17687            f.run(&[b"TDIGEST.TRIMMED_MEAN", b"s", b"zzz", b"0.9"]),
17688            "-ERR T-Digest: error parsing low_cut_percentile\r\n"
17689        );
17690        assert_eq!(
17691            f.run(&[b"TDIGEST.TRIMMED_MEAN", b"s", b"0.1", b"zzz"]),
17692            "-ERR T-Digest: error parsing high_cut_percentile\r\n"
17693        );
17694        assert_eq!(
17695            f.run(&[b"TDIGEST.TRIMMED_MEAN", b"s", b"0.1", b"1.1"]),
17696            "-ERR T-Digest: low_cut_percentile and high_cut_percentile should be in [0,1]\r\n"
17697        );
17698        assert_eq!(
17699            f.run(&[b"TDIGEST.TRIMMED_MEAN", b"s", b"0.5", b"0.5"]),
17700            "-ERR T-Digest: low_cut_percentile should be lower than high_cut_percentile\r\n"
17701        );
17702    }
17703
17704    /// An empty digest answers every question, and answers most of them with
17705    /// something that is not a number.
17706    #[test]
17707    fn an_empty_digest_has_an_answer_for_everything() {
17708        let mut f = Fixture::new();
17709        f.run(&[b"TDIGEST.CREATE", b"e"]);
17710        assert_eq!(f.run(&[b"TDIGEST.MIN", b"e"]), "$3\r\nnan\r\n");
17711        assert_eq!(f.run(&[b"TDIGEST.MAX", b"e"]), "$3\r\nnan\r\n");
17712        assert_eq!(
17713            f.run(&[b"TDIGEST.QUANTILE", b"e", b"0", b"1"]),
17714            "*2\r\n$3\r\nnan\r\n$3\r\nnan\r\n"
17715        );
17716        assert_eq!(f.run(&[b"TDIGEST.CDF", b"e", b"0"]), "*1\r\n$3\r\nnan\r\n");
17717        assert_eq!(
17718            f.run(&[b"TDIGEST.TRIMMED_MEAN", b"e", b"0.1", b"0.9"]),
17719            "$3\r\nnan\r\n"
17720        );
17721        // Minus two, which is a number no rank on a digest with samples in it
17722        // can ever be.
17723        assert_eq!(
17724            f.run(&[b"TDIGEST.RANK", b"e", b"0", b"1"]),
17725            "*2\r\n:-2\r\n:-2\r\n"
17726        );
17727        assert_eq!(
17728            f.run(&[b"TDIGEST.REVRANK", b"e", b"0", b"1"]),
17729            "*2\r\n:-2\r\n:-2\r\n"
17730        );
17731        assert_eq!(
17732            f.run(&[b"TDIGEST.BYRANK", b"e", b"0", b"5"]),
17733            "*2\r\n$3\r\nnan\r\n$3\r\nnan\r\n"
17734        );
17735        // A reset puts a digest with samples back into exactly this state.
17736        f.run(&[b"TDIGEST.ADD", b"e", b"1", b"2", b"3"]);
17737        assert_eq!(f.run(&[b"TDIGEST.RESET", b"e"]), "+OK\r\n");
17738        assert_eq!(f.run(&[b"TDIGEST.MIN", b"e"]), "$3\r\nnan\r\n");
17739        // Down to the compression count, so a reset digest and a fresh one of
17740        // the same compression report the same nine numbers.
17741        f.run(&[b"TDIGEST.CREATE", b"e2"]);
17742        assert_eq!(
17743            f.run(&[b"TDIGEST.INFO", b"e"]),
17744            f.run(&[b"TDIGEST.INFO", b"e2"])
17745        );
17746    }
17747
17748    /// The double parser is Redis's and not this engine's, and the two disagree
17749    /// at both ends of the range.
17750    #[test]
17751    fn a_sample_is_read_the_way_redis_reads_a_double() {
17752        let mut f = Fixture::new();
17753        f.run(&[b"TDIGEST.CREATE", b"a"]);
17754        // Overflow and underflow are parse failures rather than an infinity and
17755        // a zero, which is where this parts company with the rest of the engine.
17756        for bad in [
17757            &b"nan"[..],
17758            b"1e400",
17759            b"-1e400",
17760            b"1e309",
17761            b"1e-400",
17762            b"",
17763            b" 1",
17764            b"1 ",
17765            b"1e",
17766            b"--1",
17767        ] {
17768            assert_eq!(
17769                f.run(&[b"TDIGEST.ADD", b"a", bad]),
17770                "-ERR T-Digest: error parsing val parameter\r\n",
17771                "{}",
17772                String::from_utf8_lossy(bad)
17773            );
17774        }
17775        // An infinity spelled out parses and is then refused for being one, with
17776        // a different sentence.
17777        for word in [&b"inf"[..], b"-inf", b"+INF", b"Infinity"] {
17778            assert_eq!(
17779                f.run(&[b"TDIGEST.ADD", b"a", word]),
17780                "-ERR T-Digest: val parameter needs to be a finite number\r\n",
17781                "{}",
17782                String::from_utf8_lossy(word)
17783            );
17784        }
17785        // These all parse: hex, a bare point either side, and the smallest
17786        // subnormal the reference will take.
17787        for good in [&b"0x10"[..], b".5", b"1.", b"1e-320", b"-0", b"0"] {
17788            assert_eq!(
17789                f.run(&[b"TDIGEST.ADD", b"a", good]),
17790                "+OK\r\n",
17791                "{}",
17792                String::from_utf8_lossy(good)
17793            );
17794        }
17795        // Nothing landed from the failures, so six samples is what there is.
17796        assert!(
17797            f.run(&[b"TDIGEST.INFO", b"a"])
17798                .contains("Observations\r\n:6\r\n")
17799        );
17800        // Every value is parsed before any is added, so this whole command is a
17801        // no op.
17802        assert_eq!(
17803            f.run(&[b"TDIGEST.ADD", b"a", b"1", b"zzz"]),
17804            "-ERR T-Digest: error parsing val parameter\r\n"
17805        );
17806        assert!(
17807            f.run(&[b"TDIGEST.INFO", b"a"])
17808                .contains("Observations\r\n:6\r\n")
17809        );
17810    }
17811
17812    /// What a merge does to its destination, to its inputs and to the buffer
17813    /// split `TDIGEST.INFO` reports.
17814    #[test]
17815    fn a_merge_sweeps_the_destination_between_its_inputs() {
17816        let mut f = Fixture::new();
17817        f.run(&[b"TDIGEST.CREATE", b"m1", b"COMPRESSION", b"100"]);
17818        f.run(&[b"TDIGEST.ADD", b"m1", b"1", b"2", b"3"]);
17819        f.run(&[b"TDIGEST.CREATE", b"m2", b"COMPRESSION", b"200"]);
17820        f.run(&[b"TDIGEST.ADD", b"m2", b"4", b"5", b"6"]);
17821        assert_eq!(
17822            f.run(&[b"TDIGEST.MERGE", b"d", b"2", b"m1", b"m2"]),
17823            "+OK\r\n"
17824        );
17825        // The destination did not exist, so the compression is the largest of
17826        // the inputs. The three from the first input were swept in before the
17827        // three from the second arrived, which is the one visible effect of the
17828        // reference folding one input at a time.
17829        let info = f.run(&[b"TDIGEST.INFO", b"d"]);
17830        assert!(info.contains("Compression\r\n:200\r\n"), "{info}");
17831        assert!(info.contains("Merged nodes\r\n:3\r\n"), "{info}");
17832        assert!(info.contains("Unmerged nodes\r\n:3\r\n"), "{info}");
17833        assert!(info.contains("Total compressions\r\n:1\r\n"), "{info}");
17834        assert_eq!(f.run(&[b"TDIGEST.MIN", b"d"]), "$1\r\n1\r\n");
17835        assert_eq!(f.run(&[b"TDIGEST.MAX", b"d"]), "$1\r\n6\r\n");
17836        // Reading a source sweeps it too, so a merge writes to keys it only
17837        // reads from.
17838        assert!(
17839            f.run(&[b"TDIGEST.INFO", b"m1"])
17840                .contains("Merged nodes\r\n:3\r\n")
17841        );
17842        // Without OVERRIDE the destination joins its own inputs, so this takes
17843        // it to nine observations and keeps its own compression.
17844        f.run(&[b"TDIGEST.MERGE", b"d", b"1", b"m1"]);
17845        let info = f.run(&[b"TDIGEST.INFO", b"d"]);
17846        assert!(info.contains("Observations\r\n:9\r\n"), "{info}");
17847        assert!(info.contains("Compression\r\n:200\r\n"), "{info}");
17848        // With OVERRIDE the old destination is dropped and the compression goes
17849        // back to the largest of the inputs.
17850        f.run(&[b"TDIGEST.MERGE", b"d", b"1", b"m1", b"OVERRIDE"]);
17851        let info = f.run(&[b"TDIGEST.INFO", b"d"]);
17852        assert!(info.contains("Observations\r\n:3\r\n"), "{info}");
17853        assert!(info.contains("Compression\r\n:100\r\n"), "{info}");
17854        // And COMPRESSION beats both.
17855        f.run(&[b"TDIGEST.MERGE", b"d", b"1", b"m1", b"COMPRESSION", b"500"]);
17856        assert!(
17857            f.run(&[b"TDIGEST.INFO", b"d"])
17858                .contains("Compression\r\n:500\r\n")
17859        );
17860        // Naming the destination as a source folds it in twice.
17861        f.run(&[b"TDIGEST.MERGE", b"d", b"1", b"d"]);
17862        assert!(
17863            f.run(&[b"TDIGEST.INFO", b"d"])
17864                .contains("Observations\r\n:12\r\n")
17865        );
17866        // The arguments, in the order the reference checks them.
17867        assert_eq!(
17868            f.run(&[b"TDIGEST.MERGE", b"d", b"zzz", b"m1"]),
17869            "-ERR T-Digest: error parsing numkeys\r\n"
17870        );
17871        assert_eq!(
17872            f.run(&[b"TDIGEST.MERGE", b"d", b"0", b"m1"]),
17873            "-ERR T-Digest: numkeys needs to be a positive integer\r\n"
17874        );
17875        assert!(
17876            f.run(&[b"TDIGEST.MERGE", b"d", b"3", b"m1", b"m2"])
17877                .contains("wrong number of arguments")
17878        );
17879        assert!(
17880            f.run(&[b"TDIGEST.MERGE", b"d", b"1", b"m1", b"COMPRESSION"])
17881                .contains("wrong number of arguments")
17882        );
17883        assert_eq!(
17884            f.run(&[b"TDIGEST.MERGE", b"d", b"1", b"m1", b"NOPE"]),
17885            "-ERR T-Digest: wrong keyword\r\n"
17886        );
17887        // A source that is not there stops the whole thing, and the destination
17888        // is left as it was.
17889        assert_eq!(
17890            f.run(&[b"TDIGEST.MERGE", b"d", b"2", b"m1", b"gone"]),
17891            "-ERR T-Digest: key does not exist\r\n"
17892        );
17893        assert!(
17894            f.run(&[b"TDIGEST.INFO", b"d"])
17895                .contains("Observations\r\n:12\r\n")
17896        );
17897        // A destination that is not there and is also named as a source is the
17898        // same sentence rather than an empty merge.
17899        assert_eq!(
17900            f.run(&[b"TDIGEST.MERGE", b"gone", b"1", b"gone"]),
17901            "-ERR T-Digest: key does not exist\r\n"
17902        );
17903    }
17904
17905    /// The RESP3 shapes, which are the two the protocols disagree about.
17906    #[test]
17907    fn a_digest_answers_doubles_and_a_map_on_resp3() {
17908        let mut f = Fixture::new();
17909        f.run(&[b"HELLO", b"3"]);
17910        f.run(&[b"TDIGEST.CREATE", b"s"]);
17911        f.run(&[b"TDIGEST.ADD", b"s", b"1", b"2", b"3", b"4"]);
17912        assert_eq!(f.run(&[b"TDIGEST.MIN", b"s"]), ",1\r\n");
17913        assert_eq!(
17914            f.run(&[b"TDIGEST.QUANTILE", b"s", b"0", b"1"]),
17915            "*2\r\n,1\r\n,4\r\n"
17916        );
17917        assert_eq!(f.run(&[b"TDIGEST.CDF", b"s", b"1"]), "*1\r\n,0.125\r\n");
17918        // The two infinities and the NaN go out as the bare words.
17919        assert_eq!(f.run(&[b"TDIGEST.BYRANK", b"s", b"4"]), "*1\r\n,inf\r\n");
17920        assert_eq!(
17921            f.run(&[b"TDIGEST.BYREVRANK", b"s", b"4"]),
17922            "*1\r\n,-inf\r\n"
17923        );
17924        f.run(&[b"TDIGEST.CREATE", b"e"]);
17925        assert_eq!(f.run(&[b"TDIGEST.MIN", b"e"]), ",nan\r\n");
17926        // The ranks stay integers on both protocols.
17927        assert_eq!(f.run(&[b"TDIGEST.RANK", b"s", b"1"]), "*1\r\n:0\r\n");
17928        // Every question above swept the buffer in, so the four samples are all
17929        // merged by now and the compression count says it happened once.
17930        assert_eq!(
17931            f.run(&[b"TDIGEST.INFO", b"s"]),
17932            "%9\r\n+Compression\r\n:100\r\n+Capacity\r\n:610\r\n+Merged nodes\r\n:4\r\n\
17933             +Unmerged nodes\r\n:0\r\n+Merged weight\r\n:4\r\n+Unmerged weight\r\n:0\r\n\
17934             +Observations\r\n:4\r\n+Total compressions\r\n:1\r\n+Memory usage\r\n:9840\r\n"
17935        );
17936    }
17937
17938    /// A t digest key answers the module sentences the other sketch families
17939    /// answer, and its own word for its type.
17940    #[test]
17941    fn a_t_digest_is_a_module_key_to_the_rest_of_the_keyspace() {
17942        let mut f = Fixture::new();
17943        f.run(&[b"SET", b"s", b"text"]);
17944        for cmd in [
17945            vec![&b"TDIGEST.CREATE"[..], b"s"],
17946            vec![&b"TDIGEST.RESET"[..], b"s"],
17947            vec![&b"TDIGEST.ADD"[..], b"s", b"1"],
17948            vec![&b"TDIGEST.MIN"[..], b"s"],
17949            vec![&b"TDIGEST.MAX"[..], b"s"],
17950            vec![&b"TDIGEST.QUANTILE"[..], b"s", b"0.5"],
17951            vec![&b"TDIGEST.CDF"[..], b"s", b"1"],
17952            vec![&b"TDIGEST.TRIMMED_MEAN"[..], b"s", b"0.1", b"0.9"],
17953            vec![&b"TDIGEST.RANK"[..], b"s", b"1"],
17954            vec![&b"TDIGEST.REVRANK"[..], b"s", b"1"],
17955            vec![&b"TDIGEST.BYRANK"[..], b"s", b"0"],
17956            vec![&b"TDIGEST.BYREVRANK"[..], b"s", b"0"],
17957            vec![&b"TDIGEST.INFO"[..], b"s"],
17958        ] {
17959            let name = String::from_utf8_lossy(cmd[0]).into_owned();
17960            let reply = f.run(&cmd);
17961            assert!(reply.starts_with("-WRONGTYPE"), "{name}: {reply}");
17962        }
17963        // The merge checks its destination the same way, and its sources too.
17964        f.run(&[b"TDIGEST.CREATE", b"t"]);
17965        assert!(
17966            f.run(&[b"TDIGEST.MERGE", b"s", b"1", b"t"])
17967                .starts_with("-WRONGTYPE")
17968        );
17969        assert!(
17970            f.run(&[b"TDIGEST.MERGE", b"d", b"1", b"s"])
17971                .starts_with("-WRONGTYPE")
17972        );
17973        assert_eq!(
17974            f.run(&[b"COPY", b"t", b"t2"]),
17975            "-ERR not supported for this module key\r\n"
17976        );
17977        assert_eq!(
17978            f.run(&[b"DUMP", b"t"]),
17979            "-ERR DUMP is not supported for this module key\r\n"
17980        );
17981        assert_eq!(f.run(&[b"EXPIRE", b"t", b"100"]), ":1\r\n");
17982        assert_eq!(f.run(&[b"PERSIST", b"t"]), ":1\r\n");
17983        assert_eq!(f.run(&[b"RENAME", b"t", b"t3"]), "+OK\r\n");
17984        assert_eq!(f.run(&[b"TYPE", b"t3"]), "+TDIS-TYPE\r\n");
17985        assert_eq!(f.run(&[b"OBJECT", b"ENCODING", b"t3"]), "$3\r\nraw\r\n");
17986        assert_eq!(f.run(&[b"DEL", b"t3"]), ":1\r\n");
17987        // An empty digest is still a key, so the twelve that are not the
17988        // constructor all say the same thing once it is gone.
17989        assert_eq!(
17990            f.run(&[b"TDIGEST.INFO", b"t3"]),
17991            "-ERR T-Digest: key does not exist\r\n"
17992        );
17993        // The key is looked at before the arguments, so a bad argument at a key
17994        // that is not there still says the key is not there.
17995        assert_eq!(
17996            f.run(&[b"TDIGEST.QUANTILE", b"t3", b"zzz"]),
17997            "-ERR T-Digest: key does not exist\r\n"
17998        );
17999    }
18000
18001    // -------------------------------------------------------------------- ts
18002
18003    /// A `TS.INFO` reply with the memory usage taken out of it.
18004    ///
18005    /// That number is what a series costs here rather than what one costs in the
18006    /// module, which is D-53, and it moves whenever the layout of a chunk does.
18007    /// Everything either side of it is the wire contract and is worth pinning
18008    /// down exactly, so the tests below check the whole reply with the one
18009    /// number lifted out.
18010    fn without_memory(reply: &str) -> String {
18011        let head = "+memoryUsage\r\n:";
18012        let at = reply.find(head).expect("every TS.INFO reports memory");
18013        let rest = &reply[at + head.len()..];
18014        let end = rest.find("\r\n").expect("and it is a whole number");
18015        format!("{}{}", &reply[..at + head.len()], &rest[end..])
18016    }
18017
18018    /// A series is made empty and still says it has a chunk, and the options are
18019    /// read before the key is looked at.
18020    #[test]
18021    fn a_series_is_made_empty_and_reports_on_itself() {
18022        let mut f = Fixture::new();
18023        assert_eq!(f.run(&[b"TS.CREATE", b"t"]), "+OK\r\n");
18024        assert_eq!(f.run(&[b"TYPE", b"t"]), "+TSDB-TYPE\r\n");
18025        assert_eq!(f.run(&[b"OBJECT", b"ENCODING", b"t"]), "$3\r\nraw\r\n");
18026        // Fourteen fields, so twenty eight elements. An empty series reports one
18027        // chunk and zero at both ends, and neither the chunk type nor the
18028        // duplicate policy is ever a nil.
18029        assert_eq!(
18030            without_memory(&f.run(&[b"TS.INFO", b"t"])),
18031            "*28\r\n\
18032             +totalSamples\r\n:0\r\n\
18033             +memoryUsage\r\n:\r\n\
18034             +firstTimestamp\r\n:0\r\n\
18035             +lastTimestamp\r\n:0\r\n\
18036             +retentionTime\r\n:0\r\n\
18037             +chunkCount\r\n:1\r\n\
18038             +chunkSize\r\n:4096\r\n\
18039             +chunkType\r\n+compressed\r\n\
18040             +duplicatePolicy\r\n+block\r\n\
18041             +labels\r\n*0\r\n\
18042             +sourceKey\r\n$-1\r\n\
18043             +rules\r\n*0\r\n\
18044             +ignoreMaxTimeDiff\r\n:0\r\n\
18045             +ignoreMaxValDiff\r\n$1\r\n0\r\n"
18046        );
18047        // A key that is already there is about the key whatever it holds, and
18048        // the existence is what is checked rather than the type.
18049        assert_eq!(
18050            f.run(&[b"TS.CREATE", b"t"]),
18051            "-ERR TSDB: key already exists\r\n"
18052        );
18053        assert_eq!(f.run(&[b"SET", b"str", b"x"]), "+OK\r\n");
18054        assert_eq!(
18055            f.run(&[b"TS.CREATE", b"str"]),
18056            "-ERR TSDB: key already exists\r\n"
18057        );
18058        // But the arguments are read first, so a bad one at a key that is there
18059        // answers about the argument.
18060        assert_eq!(
18061            f.run(&[b"TS.CREATE", b"t", b"RETENTION", b"abc"]),
18062            "-ERR TSDB: Couldn't parse RETENTION\r\n"
18063        );
18064        // The seven that will not make a series say WRONGTYPE about a key
18065        // holding something else, where the two that would say a sentence.
18066        // The word is inside the sentence and not in front of it, because the
18067        // module writes its own error text and Redis puts ERR on the front of
18068        // anything a module writes.
18069        let wrong = "-ERR WRONGTYPE Operation against a key holding the wrong kind of value\r\n";
18070        assert_eq!(f.run(&[b"TS.INFO", b"str"]), wrong);
18071        assert_eq!(f.run(&[b"TS.GET", b"str"]), wrong);
18072        assert_eq!(f.run(&[b"TS.ALTER", b"str"]), wrong);
18073        assert_eq!(f.run(&[b"TS.DEL", b"str", b"0", b"1"]), wrong);
18074        assert_eq!(f.run(&[b"TS.INCRBY", b"str", b"1"]), wrong);
18075        assert_eq!(
18076            f.run(&[b"TS.ADD", b"str", b"1", b"1"]),
18077            "-ERR TSDB: the key is not a TSDB key\r\n"
18078        );
18079        // And the ones that will not make one say so about a key that is gone.
18080        assert_eq!(
18081            f.run(&[b"TS.INFO", b"nope"]),
18082            "-ERR TSDB: the key does not exist\r\n"
18083        );
18084        assert_eq!(
18085            f.run(&[b"TS.GET", b"nope"]),
18086            "-ERR TSDB: the key does not exist\r\n"
18087        );
18088        assert_eq!(
18089            f.run(&[b"TS.ALTER", b"nope"]),
18090            "-ERR TSDB: the key does not exist\r\n"
18091        );
18092        assert_eq!(
18093            f.run(&[b"TS.DEL", b"nope", b"1", b"2"]),
18094            "-ERR TSDB: the key does not exist\r\n"
18095        );
18096    }
18097
18098    /// Every option word, including the ones that are wrong, and the scan that
18099    /// finds them.
18100    #[test]
18101    fn the_options_are_a_keyword_scan_and_not_a_grammar() {
18102        let mut f = Fixture::new();
18103        assert_eq!(
18104            f.run(&[
18105                b"TS.CREATE",
18106                b"t",
18107                b"RETENTION",
18108                b"5000",
18109                b"ENCODING",
18110                b"UNCOMPRESSED",
18111                b"CHUNK_SIZE",
18112                b"128",
18113                b"DUPLICATE_POLICY",
18114                b"LAST",
18115                b"IGNORE",
18116                b"10",
18117                b"0.5",
18118                b"LABELS",
18119                b"room",
18120                b"kitchen"
18121            ]),
18122            "+OK\r\n"
18123        );
18124        let info = f.run(&[b"TS.INFO", b"t"]);
18125        assert!(info.contains("+retentionTime\r\n:5000\r\n"), "{info}");
18126        assert!(info.contains("+chunkSize\r\n:128\r\n"), "{info}");
18127        assert!(info.contains("+chunkType\r\n+uncompressed\r\n"), "{info}");
18128        assert!(info.contains("+duplicatePolicy\r\n+last\r\n"), "{info}");
18129        assert!(info.contains("+ignoreMaxTimeDiff\r\n:10\r\n"), "{info}");
18130        // A plain double here, where a sample value out of TS.GET is the
18131        // shortest digits that read back as the same number.
18132        assert!(
18133            info.contains("+ignoreMaxValDiff\r\n$3\r\n0.5\r\n"),
18134            "{info}"
18135        );
18136        assert!(
18137            info.contains("+labels\r\n*1\r\n*2\r\n$4\r\nroom\r\n$7\r\nkitchen\r\n"),
18138            "{info}"
18139        );
18140
18141        // A word that is not an option is read past rather than refused.
18142        assert_eq!(f.run(&[b"TS.CREATE", b"junk", b"FOO"]), "+OK\r\n");
18143        // LABELS eats everything after it in pairs, and the later scans still
18144        // look inside what it ate, so this sets a retention and stores a label
18145        // called RETENTION at the same time.
18146        assert_eq!(
18147            f.run(&[
18148                b"TS.CREATE",
18149                b"g",
18150                b"LABELS",
18151                b"a",
18152                b"b",
18153                b"RETENTION",
18154                b"5"
18155            ]),
18156            "+OK\r\n"
18157        );
18158        let greedy = f.run(&[b"TS.INFO", b"g"]);
18159        assert!(greedy.contains("+retentionTime\r\n:5\r\n"), "{greedy}");
18160        assert!(
18161            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"),
18162            "{greedy}"
18163        );
18164
18165        // Every way an option can be wrong, in the order the module reads them.
18166        assert_eq!(
18167            f.run(&[b"TS.CREATE", b"e", b"LABELS", b"a", b"b(c"]),
18168            "-ERR TSDB: Couldn't parse LABELS\r\n"
18169        );
18170        assert_eq!(
18171            f.run(&[b"TS.CREATE", b"e", b"LABELS", b"", b"b"]),
18172            "-ERR TSDB: Couldn't parse LABELS\r\n"
18173        );
18174        assert_eq!(
18175            f.run(&[b"TS.CREATE", b"e", b"RETENTION"]),
18176            "-ERR TSDB: Couldn't parse RETENTION\r\n"
18177        );
18178        // A retention below zero is one of the two the module writes with no
18179        // ERR in front of it, where one that is not a number gets one.
18180        assert_eq!(
18181            f.run(&[b"TS.CREATE", b"e", b"RETENTION", b"-1"]),
18182            "-TSDB: Couldn't parse RETENTION\r\n"
18183        );
18184        assert_eq!(
18185            f.run(&[b"TS.CREATE", b"e", b"CHUNK_SIZE", b"abc"]),
18186            "-ERR TSDB: Couldn't parse CHUNK_SIZE\r\n"
18187        );
18188        assert_eq!(
18189            f.run(&[b"TS.CREATE", b"e", b"CHUNK_SIZE", b"100"]),
18190            "-ERR TSDB: CHUNK_SIZE value must be a multiple of 8 in the range [48 .. 1048576]\r\n"
18191        );
18192        assert_eq!(
18193            f.run(&[b"TS.CREATE", b"e", b"ENCODING", b"nope"]),
18194            "-ERR TSDB: unknown ENCODING parameter\r\n"
18195        );
18196        // And an ENCODING with nothing behind it is an arity error where every
18197        // other keyword in the same spot is a sentence.
18198        assert!(
18199            f.run(&[b"TS.CREATE", b"e", b"ENCODING"])
18200                .contains("wrong number of arguments for 'ts.create' command")
18201        );
18202        assert_eq!(
18203            f.run(&[b"TS.CREATE", b"e", b"DUPLICATE_POLICY"]),
18204            "-ERR TSDB: Couldn't parse DUPLICATE_POLICY\r\n"
18205        );
18206        assert_eq!(
18207            f.run(&[b"TS.CREATE", b"e", b"DUPLICATE_POLICY", b"nope"]),
18208            "-ERR TSDB: Unknown DUPLICATE_POLICY\r\n"
18209        );
18210        assert_eq!(
18211            f.run(&[b"TS.CREATE", b"e", b"IGNORE", b"10"]),
18212            "-ERR TSDB: Couldn't parse IGNORE\r\n"
18213        );
18214        assert_eq!(
18215            f.run(&[b"TS.CREATE", b"e", b"IGNORE", b"-1", b"1"]),
18216            "-ERR TSDB: IGNORE arguments cannot be negative\r\n"
18217        );
18218        assert_eq!(f.run(&[b"EXISTS", b"e"]), ":0\r\n");
18219
18220        // An alter changes what was named and leaves the rest alone, and reads
18221        // an encoding only far enough to refuse a bad one.
18222        assert_eq!(f.run(&[b"TS.ALTER", b"t", b"RETENTION", b"9"]), "+OK\r\n");
18223        let after = f.run(&[b"TS.INFO", b"t"]);
18224        assert!(after.contains("+retentionTime\r\n:9\r\n"), "{after}");
18225        assert!(after.contains("+chunkSize\r\n:128\r\n"), "{after}");
18226        assert!(after.contains("+duplicatePolicy\r\n+last\r\n"), "{after}");
18227        assert_eq!(
18228            f.run(&[b"TS.ALTER", b"t", b"ENCODING", b"nope"]),
18229            "-ERR TSDB: unknown ENCODING parameter\r\n"
18230        );
18231        // An encoding it does take is still not applied.
18232        assert_eq!(
18233            f.run(&[b"TS.ALTER", b"t", b"ENCODING", b"COMPRESSED"]),
18234            "+OK\r\n"
18235        );
18236        assert!(
18237            f.run(&[b"TS.INFO", b"t"])
18238                .contains("+chunkType\r\n+uncompressed\r\n")
18239        );
18240    }
18241
18242    /// Samples go in, come back out and are refused for the reasons the module
18243    /// refuses them.
18244    #[test]
18245    fn samples_land_where_they_are_put_and_the_newest_comes_back() {
18246        let mut f = Fixture::new();
18247        assert_eq!(f.run(&[b"TS.ADD", b"t", b"100", b"1.5"]), ":100\r\n");
18248        // The series was made on the way in.
18249        assert_eq!(f.run(&[b"TYPE", b"t"]), "+TSDB-TYPE\r\n");
18250        assert_eq!(f.run(&[b"TS.ADD", b"t", b"200", b"2"]), ":200\r\n");
18251        // A sample value goes out as a simple string of the shortest digits
18252        // that read back as the same number.
18253        assert_eq!(f.run(&[b"TS.GET", b"t"]), "*2\r\n:200\r\n+2\r\n");
18254        assert_eq!(f.run(&[b"TS.ADD", b"t", b"300", b"1e300"]), ":300\r\n");
18255        assert_eq!(f.run(&[b"TS.GET", b"t"]), "*2\r\n:300\r\n+1E300\r\n");
18256        // An empty series has no newest sample and answers an empty array
18257        // rather than a nil.
18258        assert_eq!(f.run(&[b"TS.CREATE", b"empty"]), "+OK\r\n");
18259        assert_eq!(f.run(&[b"TS.GET", b"empty"]), "*0\r\n");
18260
18261        // The value is read before the key, so a bad one against a key holding
18262        // a string is about the value.
18263        assert_eq!(f.run(&[b"SET", b"str", b"x"]), "+OK\r\n");
18264        assert_eq!(
18265            f.run(&[b"TS.ADD", b"str", b"1", b".5"]),
18266            "-ERR TSDB: invalid value\r\n"
18267        );
18268        // The grammar is tighter than the one a number argument usually gets:
18269        // no leading plus, no bare fraction, no infinity and nothing that does
18270        // not fit.
18271        for bad in [
18272            &b".5"[..],
18273            b"1.",
18274            b"+1",
18275            b" 1",
18276            b"0x10",
18277            b"inf",
18278            b"1e400",
18279            b"--1",
18280            b"1e",
18281        ] {
18282            assert_eq!(
18283                f.run(&[b"TS.ADD", b"v", b"1", bad]),
18284                "-ERR TSDB: invalid value\r\n",
18285                "{}",
18286                String::from_utf8_lossy(bad)
18287            );
18288        }
18289        // And a reading that is not a number is one of three words.
18290        assert_eq!(f.run(&[b"TS.ADD", b"v", b"1", b"NaN"]), ":1\r\n");
18291
18292        // A timestamp that is not a number, and one that is and is below zero,
18293        // are two different sentences.
18294        assert_eq!(
18295            f.run(&[b"TS.ADD", b"t", b"abc", b"1"]),
18296            "-ERR TSDB: invalid timestamp\r\n"
18297        );
18298        assert_eq!(
18299            f.run(&[b"TS.ADD", b"t", b"-1", b"1"]),
18300            "-ERR TSDB: invalid timestamp, must be a nonnegative integer\r\n"
18301        );
18302
18303        // A repeated timestamp is blocked by default, and ON_DUPLICATE on the
18304        // command beats what the series was told.
18305        assert_eq!(
18306            f.run(&[b"TS.ADD", b"t", b"300", b"7"]),
18307            "-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"
18308        );
18309        assert_eq!(
18310            f.run(&[b"TS.ADD", b"t", b"300", b"7", b"ON_DUPLICATE", b"LAST"]),
18311            ":300\r\n"
18312        );
18313        assert_eq!(f.run(&[b"TS.GET", b"t"]), "*2\r\n:300\r\n+7\r\n");
18314        // ON_DUPLICATE is only read when the key was already there, which is
18315        // why a policy word that is not a policy passes on a fresh key.
18316        assert_eq!(
18317            f.run(&[b"TS.ADD", b"fresh", b"1", b"1", b"ON_DUPLICATE", b"nope"]),
18318            ":1\r\n"
18319        );
18320        assert_eq!(
18321            f.run(&[b"TS.ADD", b"fresh", b"2", b"1", b"ON_DUPLICATE", b"nope"]),
18322            "-ERR TSDB: Unknown DUPLICATE_POLICY\r\n"
18323        );
18324
18325        // Retention is exact and it is checked before anything else happens, so
18326        // a sample landing behind the window is refused rather than trimmed.
18327        assert_eq!(f.run(&[b"TS.CREATE", b"r", b"RETENTION", b"50"]), "+OK\r\n");
18328        assert_eq!(f.run(&[b"TS.ADD", b"r", b"1000", b"1"]), ":1000\r\n");
18329        assert_eq!(f.run(&[b"TS.ADD", b"r", b"960", b"1"]), ":960\r\n");
18330        assert_eq!(
18331            f.run(&[b"TS.ADD", b"r", b"940", b"1"]),
18332            "-ERR TSDB: Timestamp is older than retention\r\n"
18333        );
18334        // And the window trims as it moves.
18335        assert_eq!(f.run(&[b"TS.ADD", b"r", b"1100", b"1"]), ":1100\r\n");
18336        assert!(
18337            f.run(&[b"TS.INFO", b"r"])
18338                .contains("+totalSamples\r\n:1\r\n")
18339        );
18340
18341        // An ignore window drops a sample close enough to the newest one to be
18342        // uninteresting, and answers the newest timestamp so a client can tell.
18343        assert_eq!(
18344            f.run(&[
18345                b"TS.CREATE",
18346                b"i",
18347                b"DUPLICATE_POLICY",
18348                b"LAST",
18349                b"IGNORE",
18350                b"10",
18351                b"0.5"
18352            ]),
18353            "+OK\r\n"
18354        );
18355        assert_eq!(f.run(&[b"TS.ADD", b"i", b"1000", b"1"]), ":1000\r\n");
18356        assert_eq!(f.run(&[b"TS.ADD", b"i", b"1005", b"1.2"]), ":1000\r\n");
18357        assert_eq!(f.run(&[b"TS.ADD", b"i", b"1005", b"9"]), ":1005\r\n");
18358    }
18359
18360    /// Every triple in a `TS.MADD` is answered on its own, and none of them
18361    /// makes a series.
18362    #[test]
18363    fn a_madd_answers_each_triple_and_creates_nothing() {
18364        let mut f = Fixture::new();
18365        assert_eq!(f.run(&[b"TS.CREATE", b"a"]), "+OK\r\n");
18366        assert_eq!(f.run(&[b"TS.CREATE", b"b"]), "+OK\r\n");
18367        assert_eq!(
18368            f.run(&[
18369                b"TS.MADD", b"a", b"100", b"1", b"b", b"100", b"2", b"a", b"200", b"3"
18370            ]),
18371            "*3\r\n:100\r\n:100\r\n:200\r\n"
18372        );
18373        // A key that is not a series is an error in its own slot and the ones
18374        // after it still land.
18375        assert_eq!(f.run(&[b"SET", b"str", b"x"]), "+OK\r\n");
18376        assert_eq!(
18377            f.run(&[
18378                b"TS.MADD", b"gone", b"1", b"1", b"str", b"1", b"1", b"a", b"300", b"4"
18379            ]),
18380            "*3\r\n\
18381             -ERR TSDB: the key is not a TSDB key\r\n\
18382             -ERR TSDB: the key is not a TSDB key\r\n\
18383             :300\r\n"
18384        );
18385        assert_eq!(f.run(&[b"EXISTS", b"gone"]), ":0\r\n");
18386        // A bad value and a bad timestamp are answered in their slots too.
18387        assert_eq!(
18388            f.run(&[b"TS.MADD", b"a", b"400", b"zzz", b"a", b"abc", b"1"]),
18389            "*2\r\n-ERR TSDB: invalid value\r\n-ERR TSDB: invalid timestamp\r\n"
18390        );
18391        // And a list that is not made of triples is an arity error.
18392        assert!(
18393            f.run(&[b"TS.MADD", b"a", b"1", b"1", b"a"])
18394                .contains("wrong number of arguments for 'ts.madd' command")
18395        );
18396    }
18397
18398    /// The two increments, which only ever write forwards.
18399    #[test]
18400    fn an_increment_walks_the_newest_value_up_and_down() {
18401        let mut f = Fixture::new();
18402        assert_eq!(
18403            f.run(&[b"TS.INCRBY", b"t", b"5", b"TIMESTAMP", b"100"]),
18404            ":100\r\n"
18405        );
18406        assert_eq!(
18407            f.run(&[b"TS.INCRBY", b"t", b"5", b"TIMESTAMP", b"100"]),
18408            ":100\r\n"
18409        );
18410        // Two on one timestamp add up rather than collide, because the sample
18411        // goes in under the last policy whatever the series says.
18412        assert_eq!(f.run(&[b"TS.GET", b"t"]), "*2\r\n:100\r\n+10\r\n");
18413        assert_eq!(
18414            f.run(&[b"TS.DECRBY", b"t", b"3", b"TIMESTAMP", b"200"]),
18415            ":200\r\n"
18416        );
18417        assert_eq!(f.run(&[b"TS.GET", b"t"]), "*2\r\n:200\r\n+7\r\n");
18418        // A timestamp behind the newest sample is the other of the two errors
18419        // the module writes with no ERR in front of it.
18420        assert_eq!(
18421            f.run(&[b"TS.INCRBY", b"t", b"1", b"TIMESTAMP", b"150"]),
18422            "-TSDB: timestamp must be equal to or higher than the maximum existing timestamp\r\n"
18423        );
18424        // The increment goes through the ordinary number reader, so it takes
18425        // what a sample value will not and refuses a NaN that a sample value
18426        // takes.
18427        assert_eq!(
18428            f.run(&[b"TS.INCRBY", b"p", b"+5", b"TIMESTAMP", b"1"]),
18429            ":1\r\n"
18430        );
18431        assert_eq!(
18432            f.run(&[b"TS.INCRBY", b"q", b".5", b"TIMESTAMP", b"1"]),
18433            ":1\r\n"
18434        );
18435        assert_eq!(
18436            f.run(&[b"TS.INCRBY", b"t", b"nan"]),
18437            "-ERR TSDB: invalid increase/decrease value\r\n"
18438        );
18439        assert_eq!(
18440            f.run(&[b"TS.INCRBY", b"t", b"zzz"]),
18441            "-ERR TSDB: invalid increase/decrease value\r\n"
18442        );
18443        // A key holding something else is WRONGTYPE and is answered before the
18444        // number is looked at.
18445        assert_eq!(f.run(&[b"SET", b"str", b"x"]), "+OK\r\n");
18446        assert_eq!(
18447            f.run(&[b"TS.INCRBY", b"str", b"zzz"]),
18448            "-ERR WRONGTYPE Operation against a key holding the wrong kind of value\r\n"
18449        );
18450        // A TIMESTAMP keyword with nothing behind it is about the timestamp.
18451        // The reference reads one past the end of its own arguments here and
18452        // answers whatever was in that memory, so there is nothing to copy and
18453        // this answers the same thing every time.
18454        assert_eq!(
18455            f.run(&[b"TS.INCRBY", b"t", b"1", b"TIMESTAMP"]),
18456            "-ERR TSDB: invalid timestamp\r\n"
18457        );
18458        // And one behind a LABELS is a label name rather than the keyword, so
18459        // this lands at the clock rather than at 5.
18460        assert_eq!(
18461            f.run(&[b"TS.INCRBY", b"lab", b"1", b"LABELS", b"TIMESTAMP", b"5"]),
18462            format!(":{}\r\n", f.server.now_ms())
18463        );
18464        // Adding to a series whose newest value is not a number has no answer.
18465        assert_eq!(f.run(&[b"TS.ADD", b"n", b"1", b"nan"]), ":1\r\n");
18466        assert_eq!(
18467            f.run(&[b"TS.INCRBY", b"n", b"1", b"TIMESTAMP", b"2"]),
18468            "-ERR TSDB: cannot increment/decrement NaN value\r\n"
18469        );
18470    }
18471
18472    /// Deleting a span, both ends included.
18473    #[test]
18474    fn deleting_takes_out_a_span_and_answers_how_many_went() {
18475        let mut f = Fixture::new();
18476        for at in [b"100".as_slice(), b"200", b"300", b"400"] {
18477            f.run(&[b"TS.ADD", b"t", at, b"1"]);
18478        }
18479        assert_eq!(f.run(&[b"TS.DEL", b"t", b"200", b"300"]), ":2\r\n");
18480        assert!(
18481            f.run(&[b"TS.INFO", b"t"])
18482                .contains("+totalSamples\r\n:2\r\n")
18483        );
18484        // Ends the wrong way round take nothing out rather than being an error.
18485        assert_eq!(f.run(&[b"TS.DEL", b"t", b"400", b"100"]), ":0\r\n");
18486        // The two open ends.
18487        assert_eq!(f.run(&[b"TS.DEL", b"t", b"-", b"+"]), ":2\r\n");
18488        // A series everything has been deleted from keeps its chunk and reports
18489        // zero at both ends again.
18490        let empty = f.run(&[b"TS.INFO", b"t"]);
18491        assert!(empty.contains("+totalSamples\r\n:0\r\n"), "{empty}");
18492        assert!(empty.contains("+chunkCount\r\n:1\r\n"), "{empty}");
18493        assert!(empty.contains("+firstTimestamp\r\n:0\r\n"), "{empty}");
18494        assert!(empty.contains("+lastTimestamp\r\n:0\r\n"), "{empty}");
18495        assert_eq!(f.run(&[b"TS.DEL", b"t", b"0", b"1000"]), ":0\r\n");
18496        // The two ends have their own sentences.
18497        assert_eq!(
18498            f.run(&[b"TS.DEL", b"t", b"abc", b"5"]),
18499            "-ERR TSDB: wrong fromTimestamp\r\n"
18500        );
18501        assert_eq!(
18502            f.run(&[b"TS.DEL", b"t", b"5", b"abc"]),
18503            "-ERR TSDB: wrong toTimestamp\r\n"
18504        );
18505        assert_eq!(
18506            f.run(&[b"TS.DEL", b"t", b"-5", b"5"]),
18507            "-ERR TSDB: wrong fromTimestamp\r\n"
18508        );
18509    }
18510
18511    /// What RESP3 changes, which is the two places a number is written and the
18512    /// shape of `TS.INFO`.
18513    #[test]
18514    fn resp3_writes_a_sample_as_a_double_and_the_info_as_a_map() {
18515        let mut f = Fixture::new();
18516        f.out = Out::new(Proto::Resp3);
18517        assert_eq!(
18518            f.run(&[b"TS.CREATE", b"t", b"LABELS", b"room", b"kitchen"]),
18519            "+OK\r\n"
18520        );
18521        assert_eq!(f.run(&[b"TS.ADD", b"t", b"100", b"1e300"]), ":100\r\n");
18522        // A double rather than the simple string RESP2 gets.
18523        assert_eq!(f.run(&[b"TS.GET", b"t"]), "*2\r\n:100\r\n,1e+300\r\n");
18524        assert_eq!(
18525            without_memory(&f.run(&[b"TS.INFO", b"t"])),
18526            "%14\r\n\
18527             +totalSamples\r\n:1\r\n\
18528             +memoryUsage\r\n:\r\n\
18529             +firstTimestamp\r\n:100\r\n\
18530             +lastTimestamp\r\n:100\r\n\
18531             +retentionTime\r\n:0\r\n\
18532             +chunkCount\r\n:1\r\n\
18533             +chunkSize\r\n:4096\r\n\
18534             +chunkType\r\n+compressed\r\n\
18535             +duplicatePolicy\r\n+block\r\n\
18536             +labels\r\n%1\r\n$4\r\nroom\r\n$7\r\nkitchen\r\n\
18537             +sourceKey\r\n_\r\n\
18538             +rules\r\n%0\r\n\
18539             +ignoreMaxTimeDiff\r\n:0\r\n\
18540             +ignoreMaxValDiff\r\n,0\r\n"
18541        );
18542    }
18543
18544    /// Reading a span back, both ways round, with the two ends and the three
18545    /// things that trim what comes out.
18546    #[test]
18547    fn a_range_walks_a_span_and_a_revrange_walks_it_backwards() {
18548        let mut f = Fixture::new();
18549        for (at, v) in [
18550            (b"100".as_slice(), b"1".as_slice()),
18551            (b"200", b"2"),
18552            (b"300", b"3"),
18553            (b"400", b"4"),
18554        ] {
18555            f.run(&[b"TS.ADD", b"t", at, v]);
18556        }
18557        assert_eq!(
18558            f.run(&[b"TS.RANGE", b"t", b"-", b"+"]),
18559            "*4\r\n*2\r\n:100\r\n+1\r\n*2\r\n:200\r\n+2\r\n\
18560             *2\r\n:300\r\n+3\r\n*2\r\n:400\r\n+4\r\n"
18561        );
18562        // Both ends are included.
18563        assert_eq!(
18564            f.run(&[b"TS.RANGE", b"t", b"150", b"350"]),
18565            "*2\r\n*2\r\n:200\r\n+2\r\n*2\r\n:300\r\n+3\r\n"
18566        );
18567        // Backwards, and the count takes from the front of what comes out, so
18568        // backwards it takes the newest.
18569        assert_eq!(
18570            f.run(&[b"TS.REVRANGE", b"t", b"-", b"+", b"COUNT", b"2"]),
18571            "*2\r\n*2\r\n:400\r\n+4\r\n*2\r\n:300\r\n+3\r\n"
18572        );
18573        // Ends the wrong way round are empty rather than an error.
18574        assert_eq!(f.run(&[b"TS.RANGE", b"t", b"400", b"100"]), "*0\r\n");
18575        // The two filters.
18576        assert_eq!(
18577            f.run(&[
18578                b"TS.RANGE",
18579                b"t",
18580                b"-",
18581                b"+",
18582                b"FILTER_BY_VALUE",
18583                b"2",
18584                b"3"
18585            ]),
18586            "*2\r\n*2\r\n:200\r\n+2\r\n*2\r\n:300\r\n+3\r\n"
18587        );
18588        assert_eq!(
18589            f.run(&[
18590                b"TS.RANGE",
18591                b"t",
18592                b"-",
18593                b"+",
18594                b"FILTER_BY_TS",
18595                b"100",
18596                b"400"
18597            ]),
18598            "*2\r\n*2\r\n:100\r\n+1\r\n*2\r\n:400\r\n+4\r\n"
18599        );
18600        // A word that is not an option is ignored wherever it sits.
18601        assert_eq!(
18602            f.run(&[
18603                b"TS.RANGE",
18604                b"t",
18605                b"-",
18606                b"+",
18607                b"ZZZ",
18608                b"FILTER_BY_TS",
18609                b"400"
18610            ]),
18611            "*1\r\n*2\r\n:400\r\n+4\r\n"
18612        );
18613        // `LATEST` means nothing until there is a compaction rule to follow.
18614        assert_eq!(
18615            f.run(&[b"TS.RANGE", b"t", b"-", b"+", b"LATEST", b"COUNT", b"1"]),
18616            "*1\r\n*2\r\n:100\r\n+1\r\n"
18617        );
18618    }
18619
18620    /// The bucketing, which is one column a reduction and a flat row.
18621    #[test]
18622    fn aggregation_puts_one_column_a_reduction_in_a_flat_row() {
18623        let mut f = Fixture::new();
18624        for (at, v) in [
18625            (b"100".as_slice(), b"1".as_slice()),
18626            (b"200", b"2"),
18627            (b"300", b"3"),
18628            (b"400", b"4"),
18629        ] {
18630            f.run(&[b"TS.ADD", b"t", at, v]);
18631        }
18632        assert_eq!(
18633            f.run(&[
18634                b"TS.RANGE",
18635                b"t",
18636                b"-",
18637                b"+",
18638                b"AGGREGATION",
18639                b"avg",
18640                b"200"
18641            ]),
18642            "*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"
18643        );
18644        // Three reductions is a row of four and not a row of two with a nested
18645        // three in it.
18646        assert_eq!(
18647            f.run(&[
18648                b"TS.RANGE",
18649                b"t",
18650                b"-",
18651                b"+",
18652                b"AGGREGATION",
18653                b"min,max,count",
18654                b"200"
18655            ]),
18656            "*3\r\n\
18657             *4\r\n:0\r\n+1\r\n+1\r\n+1\r\n\
18658             *4\r\n:200\r\n+2\r\n+3\r\n+2\r\n\
18659             *4\r\n:400\r\n+4\r\n+4\r\n+1\r\n"
18660        );
18661        // The timestamp a bucket is reported under.
18662        assert_eq!(
18663            f.run(&[
18664                b"TS.RANGE",
18665                b"t",
18666                b"-",
18667                b"+",
18668                b"AGGREGATION",
18669                b"avg",
18670                b"200",
18671                b"BUCKETTIMESTAMP",
18672                b"+"
18673            ]),
18674            "*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"
18675        );
18676        // An alignment moves where the bucket edges land.
18677        assert_eq!(
18678            f.run(&[
18679                b"TS.RANGE",
18680                b"t",
18681                b"100",
18682                b"400",
18683                b"ALIGN",
18684                b"100",
18685                b"AGGREGATION",
18686                b"sum",
18687                b"200"
18688            ]),
18689            "*2\r\n*2\r\n:100\r\n+3\r\n*2\r\n:300\r\n+7\r\n"
18690        );
18691        // A `COUNT` sitting where the reduction name belongs is that name, and
18692        // the scan for a real one starts again two words later.
18693        assert_eq!(
18694            f.run(&[
18695                b"TS.RANGE",
18696                b"t",
18697                b"-",
18698                b"+",
18699                b"AGGREGATION",
18700                b"count",
18701                b"200"
18702            ]),
18703            "*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"
18704        );
18705        assert_eq!(
18706            f.run(&[
18707                b"TS.RANGE",
18708                b"t",
18709                b"-",
18710                b"+",
18711                b"AGGREGATION",
18712                b"count",
18713                b"200",
18714                b"COUNT",
18715                b"1"
18716            ]),
18717            "*1\r\n*2\r\n:0\r\n+1\r\n"
18718        );
18719    }
18720
18721    /// `EMPTY` fills the gaps between readings and nothing else, and `last`
18722    /// carries two different things depending on which kind of empty it is.
18723    #[test]
18724    fn empty_fills_a_gap_and_last_carries_the_reading_before_it() {
18725        let mut f = Fixture::new();
18726        for (at, v) in [
18727            (b"0".as_slice(), b"1".as_slice()),
18728            (b"100", b"2"),
18729            (b"500", b"nan"),
18730            (b"600", b"3"),
18731        ] {
18732            f.run(&[b"TS.ADD", b"g", at, v]);
18733        }
18734        // Without `EMPTY` the buckets with nothing in them are not there at all,
18735        // and neither is the one holding only a reading that is not a number.
18736        assert_eq!(
18737            f.run(&[
18738                b"TS.RANGE",
18739                b"g",
18740                b"-",
18741                b"+",
18742                b"AGGREGATION",
18743                b"avg",
18744                b"100"
18745            ]),
18746            "*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"
18747        );
18748        // The sum of nothing is zero rather than not a number.
18749        assert_eq!(
18750            f.run(&[
18751                b"TS.RANGE",
18752                b"g",
18753                b"-",
18754                b"+",
18755                b"AGGREGATION",
18756                b"sum",
18757                b"100",
18758                b"EMPTY"
18759            ]),
18760            "*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\
18761             *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\
18762             *2\r\n:600\r\n+3\r\n"
18763        );
18764        // Buckets 200 through 400 have no readings at all and carry the reading
18765        // before the gap either way round. Bucket 500 has a reading that is not
18766        // a number, so it carries whatever the bucket before it in the reading
18767        // direction answered, which is 2 forwards and 3 backwards.
18768        assert_eq!(
18769            f.run(&[
18770                b"TS.RANGE",
18771                b"g",
18772                b"-",
18773                b"+",
18774                b"AGGREGATION",
18775                b"last",
18776                b"100",
18777                b"EMPTY"
18778            ]),
18779            "*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\
18780             *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\
18781             *2\r\n:600\r\n+3\r\n"
18782        );
18783        assert_eq!(
18784            f.run(&[
18785                b"TS.REVRANGE",
18786                b"g",
18787                b"-",
18788                b"+",
18789                b"AGGREGATION",
18790                b"last",
18791                b"100",
18792                b"EMPTY"
18793            ]),
18794            "*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\
18795             *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\
18796             *2\r\n:0\r\n+1\r\n"
18797        );
18798        // And a window that opens on that bucket has nothing in range before it
18799        // to carry, so it answers not a number.
18800        assert_eq!(
18801            f.run(&[
18802                b"TS.RANGE",
18803                b"g",
18804                b"500",
18805                b"600",
18806                b"AGGREGATION",
18807                b"last",
18808                b"100",
18809                b"EMPTY"
18810            ]),
18811            "*2\r\n*2\r\n:500\r\n+NaN\r\n*2\r\n:600\r\n+3\r\n"
18812        );
18813    }
18814
18815    /// The sentences a read answers when its options do not add up, which are
18816    /// the module's own word for word.
18817    #[test]
18818    fn a_range_says_what_the_module_says_when_the_options_do_not_add_up() {
18819        let mut f = Fixture::new();
18820        f.run(&[b"TS.ADD", b"t", b"100", b"1"]);
18821        f.run(&[b"SET", b"str", b"x"]);
18822        let cases: &[(&[&[u8]], &str)] = &[
18823            (
18824                &[b"TS.RANGE", b"t"],
18825                "-ERR wrong number of arguments for 'ts.range' command\r\n",
18826            ),
18827            // The key is resolved before a single option is read.
18828            (
18829                &[b"TS.RANGE", b"gone", b"-", b"+", b"COUNT", b"x"],
18830                "-ERR TSDB: the key does not exist\r\n",
18831            ),
18832            (
18833                &[b"TS.RANGE", b"str", b"-", b"+"],
18834                "-ERR WRONGTYPE Operation against a key holding the wrong kind of value\r\n",
18835            ),
18836            (
18837                &[b"TS.RANGE", b"t", b"abc", b"+"],
18838                "-ERR TSDB: wrong fromTimestamp\r\n",
18839            ),
18840            (
18841                &[b"TS.RANGE", b"t", b"-", b"abc"],
18842                "-ERR TSDB: wrong toTimestamp\r\n",
18843            ),
18844            (
18845                &[b"TS.RANGE", b"t", b"-", b"+", b"COUNT"],
18846                "-ERR TSDB: COUNT argument is missing\r\n",
18847            ),
18848            (
18849                &[b"TS.RANGE", b"t", b"-", b"+", b"COUNT", b"x"],
18850                "-ERR TSDB: Couldn't parse COUNT\r\n",
18851            ),
18852            (
18853                &[b"TS.RANGE", b"t", b"-", b"+", b"COUNT", b"0"],
18854                "-ERR TSDB: Invalid COUNT value\r\n",
18855            ),
18856            (
18857                &[b"TS.RANGE", b"t", b"-", b"+", b"AGGREGATION", b"avg"],
18858                "-ERR TSDB: Couldn't parse AGGREGATION\r\n",
18859            ),
18860            (
18861                &[b"TS.RANGE", b"t", b"-", b"+", b"AGGREGATION", b"avg", b"x"],
18862                "-ERR TSDB: Couldn't parse AGGREGATION\r\n",
18863            ),
18864            (
18865                &[
18866                    b"TS.RANGE",
18867                    b"t",
18868                    b"-",
18869                    b"+",
18870                    b"AGGREGATION",
18871                    b"nope",
18872                    b"100",
18873                ],
18874                "-ERR TSDB: Unknown aggregation type\r\n",
18875            ),
18876            (
18877                &[
18878                    b"TS.RANGE",
18879                    b"t",
18880                    b"-",
18881                    b"+",
18882                    b"AGGREGATION",
18883                    b"avg,,min",
18884                    b"100",
18885                ],
18886                "-ERR TSDB: Empty aggregation type in list\r\n",
18887            ),
18888            // The list of names is read before the width is looked at.
18889            (
18890                &[b"TS.RANGE", b"t", b"-", b"+", b"AGGREGATION", b"nope", b"0"],
18891                "-ERR TSDB: Unknown aggregation type\r\n",
18892            ),
18893            (
18894                &[b"TS.RANGE", b"t", b"-", b"+", b"AGGREGATION", b"avg", b"0"],
18895                "-ERR TSDB: bucketDuration must be greater than zero\r\n",
18896            ),
18897            (
18898                &[
18899                    b"TS.RANGE",
18900                    b"t",
18901                    b"-",
18902                    b"+",
18903                    b"AGGREGATION",
18904                    b"avg",
18905                    b"100",
18906                    b"X",
18907                    b"EMPTY",
18908                ],
18909                "-ERR TSDB: EMPTY flag should be the 3rd or 5th flag after AGGREGATION flag\r\n",
18910            ),
18911            (
18912                &[
18913                    b"TS.RANGE",
18914                    b"t",
18915                    b"-",
18916                    b"+",
18917                    b"AGGREGATION",
18918                    b"avg",
18919                    b"100",
18920                    b"BUCKETTIMESTAMP",
18921                    b"z",
18922                ],
18923                "-ERR TSDB: unknown BUCKETTIMESTAMP parameter\r\n",
18924            ),
18925            (
18926                &[
18927                    b"TS.RANGE",
18928                    b"t",
18929                    b"-",
18930                    b"+",
18931                    b"AGGREGATION",
18932                    b"avg",
18933                    b"100",
18934                    b"X",
18935                    b"Y",
18936                    b"BUCKETTIMESTAMP",
18937                    b"-",
18938                ],
18939                "-ERR TSDB: BUCKETTIMESTAMP flag should be the 3rd or 4th flag after \
18940                 AGGREGATION flag\r\n",
18941            ),
18942            (
18943                &[
18944                    b"TS.RANGE",
18945                    b"t",
18946                    b"-",
18947                    b"+",
18948                    b"ALIGN",
18949                    b"z",
18950                    b"AGGREGATION",
18951                    b"avg",
18952                    b"100",
18953                ],
18954                "-ERR TSDB: unknown ALIGN parameter\r\n",
18955            ),
18956            (
18957                &[b"TS.RANGE", b"t", b"-", b"+", b"ALIGN", b"5"],
18958                "-ERR TSDB: ALIGN parameter can only be used with AGGREGATION\r\n",
18959            ),
18960            (
18961                &[
18962                    b"TS.RANGE",
18963                    b"t",
18964                    b"-",
18965                    b"+",
18966                    b"ALIGN",
18967                    b"-",
18968                    b"AGGREGATION",
18969                    b"avg",
18970                    b"100",
18971                ],
18972                "-ERR TSDB: start alignment can only be used with explicit start timestamp\r\n",
18973            ),
18974            (
18975                &[b"TS.RANGE", b"t", b"-", b"+", b"FILTER_BY_VALUE", b"1"],
18976                "-ERR TSDB: FILTER_BY_VALUE one or more arguments are missing\r\n",
18977            ),
18978            (
18979                &[
18980                    b"TS.RANGE",
18981                    b"t",
18982                    b"-",
18983                    b"+",
18984                    b"FILTER_BY_VALUE",
18985                    b"x",
18986                    b"2",
18987                ],
18988                "-ERR TSDB: Couldn't parse MIN\r\n",
18989            ),
18990            (
18991                &[
18992                    b"TS.RANGE",
18993                    b"t",
18994                    b"-",
18995                    b"+",
18996                    b"FILTER_BY_VALUE",
18997                    b"1",
18998                    b"y",
18999                ],
19000                "-ERR TSDB: Couldn't parse MAX\r\n",
19001            ),
19002            (
19003                &[b"TS.RANGE", b"t", b"-", b"+", b"FILTER_BY_TS"],
19004                "-ERR TSDB: FILTER_BY_TS one or more arguments are missing\r\n",
19005            ),
19006        ];
19007        for (argv, want) in cases {
19008            let got = f.run(argv);
19009            assert_eq!(&got, want, "{:?}", argv.last());
19010        }
19011        // The one sentence here that is yo's own rather than the module's, which
19012        // is D-54. A read that would build more rows than yo will build is
19013        // refused instead of attempted.
19014        f.run(&[b"TS.ADD", b"wide", b"0", b"1"]);
19015        f.run(&[b"TS.ADD", b"wide", b"1000000000000", b"2"]);
19016        assert_eq!(
19017            f.run(&[
19018                b"TS.RANGE",
19019                b"wide",
19020                b"-",
19021                b"+",
19022                b"AGGREGATION",
19023                b"avg",
19024                b"1",
19025                b"EMPTY"
19026            ]),
19027            "-ERR TSDB: the requested range holds too many empty buckets\r\n"
19028        );
19029    }
19030
19031    /// What RESP3 changes on a read, which is only how a number is written.
19032    #[test]
19033    fn resp3_writes_a_read_value_as_a_double() {
19034        let mut f = Fixture::new();
19035        f.out = Out::new(Proto::Resp3);
19036        for (at, v) in [
19037            (b"0".as_slice(), b"1".as_slice()),
19038            (b"100", b"2"),
19039            (b"500", b"nan"),
19040            (b"600", b"3"),
19041        ] {
19042            f.run(&[b"TS.ADD", b"g", at, v]);
19043        }
19044        assert_eq!(
19045            f.run(&[
19046                b"TS.RANGE",
19047                b"g",
19048                b"0",
19049                b"100",
19050                b"AGGREGATION",
19051                b"avg,min",
19052                b"200"
19053            ]),
19054            "*1\r\n*3\r\n:0\r\n,1.5\r\n,1\r\n"
19055        );
19056        assert_eq!(
19057            f.run(&[
19058                b"TS.RANGE",
19059                b"g",
19060                b"500",
19061                b"600",
19062                b"AGGREGATION",
19063                b"last",
19064                b"100",
19065                b"EMPTY"
19066            ]),
19067            "*2\r\n*2\r\n:500\r\n,nan\r\n*2\r\n:600\r\n,3\r\n"
19068        );
19069    }
19070
19071    /// Two series with an overlap and a gap each, plus a third holding nothing,
19072    /// which is what the joined reads are measured against.
19073    fn joined() -> Fixture {
19074        let mut f = Fixture::new();
19075        f.run(&[b"TS.CREATE", b"z"]);
19076        for (at, v) in [
19077            (b"10".as_slice(), b"1".as_slice()),
19078            (b"20", b"2"),
19079            (b"40", b"4"),
19080            (b"50", b"5"),
19081        ] {
19082            f.run(&[b"TS.ADD", b"x", at, v]);
19083        }
19084        for (at, v) in [
19085            (b"20".as_slice(), b"20".as_slice()),
19086            (b"30", b"30"),
19087            (b"50", b"50"),
19088            (b"60", b"60"),
19089        ] {
19090            f.run(&[b"TS.ADD", b"y", at, v]);
19091        }
19092        f
19093    }
19094
19095    /// The joined read lines its keys up on the timestamp and writes a row as
19096    /// the timestamp and then a nested array of the columns, which is the one
19097    /// shape in the family that is not the flat pair.
19098    #[test]
19099    fn an_nrange_joins_its_keys_on_the_timestamp() {
19100        let mut f = joined();
19101        // One key still nests, so the shape does not depend on the count.
19102        assert_eq!(
19103            f.run(&[b"TS.NRANGE", b"1", b"x", b"-", b"+"]),
19104            "*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\
19105             *2\r\n:40\r\n*1\r\n+4\r\n*2\r\n:50\r\n*1\r\n+5\r\n"
19106        );
19107        // A key with no reading where another key has one writes NaN there.
19108        assert_eq!(
19109            f.run(&[b"TS.NRANGE", b"2", b"x", b"y", b"-", b"+"]),
19110            "*6\r\n*2\r\n:10\r\n*2\r\n+1\r\n+NaN\r\n\
19111             *2\r\n:20\r\n*2\r\n+2\r\n+20\r\n\
19112             *2\r\n:30\r\n*2\r\n+NaN\r\n+30\r\n\
19113             *2\r\n:40\r\n*2\r\n+4\r\n+NaN\r\n\
19114             *2\r\n:50\r\n*2\r\n+5\r\n+50\r\n\
19115             *2\r\n:60\r\n*2\r\n+NaN\r\n+60\r\n"
19116        );
19117        // A series holding nothing is a column of NaN and never a row of its
19118        // own, and the same key twice answers twice.
19119        assert_eq!(
19120            f.run(&[b"TS.NRANGE", b"2", b"x", b"z", b"20", b"40"]),
19121            "*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"
19122        );
19123        assert_eq!(
19124            f.run(&[b"TS.NRANGE", b"2", b"x", b"x", b"40", b"50"]),
19125            "*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"
19126        );
19127        // COUNT is applied to the joined rows and not to each key, so backwards
19128        // it gives the newest joined row rather than the newest of each.
19129        assert_eq!(
19130            f.run(&[
19131                b"TS.NREVRANGE",
19132                b"2",
19133                b"x",
19134                b"y",
19135                b"-",
19136                b"+",
19137                b"COUNT",
19138                b"1"
19139            ]),
19140            "*1\r\n*2\r\n:60\r\n*2\r\n+NaN\r\n+60\r\n"
19141        );
19142        assert_eq!(
19143            f.run(&[b"TS.NRANGE", b"2", b"x", b"y", b"-", b"+", b"COUNT", b"1"]),
19144            "*1\r\n*2\r\n:10\r\n*2\r\n+1\r\n+NaN\r\n"
19145        );
19146        // The two sample filters are settled a key at a time, before the join.
19147        assert_eq!(
19148            f.run(&[
19149                b"TS.NRANGE",
19150                b"2",
19151                b"x",
19152                b"y",
19153                b"-",
19154                b"+",
19155                b"FILTER_BY_VALUE",
19156                b"2",
19157                b"30"
19158            ]),
19159            "*4\r\n*2\r\n:20\r\n*2\r\n+2\r\n+20\r\n\
19160             *2\r\n:30\r\n*2\r\n+NaN\r\n+30\r\n\
19161             *2\r\n:40\r\n*2\r\n+4\r\n+NaN\r\n\
19162             *2\r\n:50\r\n*2\r\n+5\r\n+NaN\r\n"
19163        );
19164    }
19165
19166    /// The aggregation on a joined read names one reduction a key and then the
19167    /// one bucket width, and each name may be a comma list, so a row can be
19168    /// wider than the key count.
19169    #[test]
19170    fn an_nrange_aggregation_names_one_reduction_a_key() {
19171        let mut f = joined();
19172        assert_eq!(
19173            f.run(&[
19174                b"TS.NRANGE",
19175                b"2",
19176                b"x",
19177                b"y",
19178                b"-",
19179                b"+",
19180                b"AGGREGATION",
19181                b"sum",
19182                b"sum",
19183                b"20"
19184            ]),
19185            "*4\r\n*2\r\n:0\r\n*2\r\n+1\r\n+NaN\r\n\
19186             *2\r\n:20\r\n*2\r\n+2\r\n+50\r\n\
19187             *2\r\n:40\r\n*2\r\n+9\r\n+50\r\n\
19188             *2\r\n:60\r\n*2\r\n+NaN\r\n+60\r\n"
19189        );
19190        // A comma list on the first key widens the row to three columns.
19191        assert_eq!(
19192            f.run(&[
19193                b"TS.NRANGE",
19194                b"2",
19195                b"x",
19196                b"y",
19197                b"-",
19198                b"+",
19199                b"AGGREGATION",
19200                b"sum,count",
19201                b"avg",
19202                b"20"
19203            ]),
19204            "*4\r\n*2\r\n:0\r\n*3\r\n+1\r\n+1\r\n+NaN\r\n\
19205             *2\r\n:20\r\n*3\r\n+2\r\n+1\r\n+25\r\n\
19206             *2\r\n:40\r\n*3\r\n+9\r\n+2\r\n+50\r\n\
19207             *2\r\n:60\r\n*3\r\n+NaN\r\n+NaN\r\n+60\r\n"
19208        );
19209        // Everything behind the width moves along with it, so BUCKETTIMESTAMP
19210        // sits one or two past the width whatever the key count is.
19211        assert_eq!(
19212            f.run(&[
19213                b"TS.NRANGE",
19214                b"2",
19215                b"x",
19216                b"y",
19217                b"-",
19218                b"+",
19219                b"AGGREGATION",
19220                b"avg",
19221                b"sum",
19222                b"100",
19223                b"EMPTY",
19224                b"BUCKETTIMESTAMP",
19225                b"end"
19226            ]),
19227            "*1\r\n*2\r\n:100\r\n*2\r\n+3\r\n+160\r\n"
19228        );
19229        // A COUNT landing in one of the name slots is a reduction name and not
19230        // the keyword, and the read then has no count at all.
19231        assert_eq!(
19232            f.run(&[
19233                b"TS.NRANGE",
19234                b"2",
19235                b"x",
19236                b"y",
19237                b"-",
19238                b"+",
19239                b"AGGREGATION",
19240                b"avg",
19241                b"COUNT",
19242                b"100"
19243            ]),
19244            "*1\r\n*2\r\n:0\r\n*2\r\n+3\r\n+4\r\n"
19245        );
19246    }
19247
19248    /// The sentences a joined read answers when it does not add up, which are
19249    /// the module's own and come out in the module's own order.
19250    #[test]
19251    fn an_nrange_says_what_the_module_says_when_it_does_not_add_up() {
19252        let mut f = joined();
19253        f.run(&[b"SET", b"str", b"hi"]);
19254        let bad_keys = "-ERR TSDB: numkeys must be a positive integer\r\n";
19255        let numkeys = "-ERR TSDB: the number of AGGREGATION arguments \
19256                       must be equal to numkeys\r\n";
19257        let cases: &[(&[&[u8]], &str)] = &[
19258            (&[b"TS.NRANGE", b"0", b"x", b"-", b"+"], bad_keys),
19259            (&[b"TS.NRANGE", b"-1", b"x", b"-", b"+"], bad_keys),
19260            (&[b"TS.NRANGE", b"abc", b"x", b"-", b"+"], bad_keys),
19261            // Not enough words behind the count for the keys and both ends of
19262            // the span, which is an arity error however many keys were named.
19263            (
19264                &[b"TS.NRANGE", b"2", b"x", b"-", b"+"],
19265                "-ERR wrong number of arguments for 'ts.nrange' command\r\n",
19266            ),
19267            (
19268                &[b"TS.NRANGE", b"99", b"x", b"-", b"+"],
19269                "-ERR wrong number of arguments for 'ts.nrange' command\r\n",
19270            ),
19271            // The reduction names are read before the two ends of the span,
19272            // which no other option is.
19273            (
19274                &[
19275                    b"TS.NRANGE",
19276                    b"2",
19277                    b"x",
19278                    b"y",
19279                    b"abc",
19280                    b"+",
19281                    b"AGGREGATION",
19282                    b"nope",
19283                    b"sum",
19284                    b"100",
19285                ],
19286                "-ERR TSDB: Unknown aggregation type\r\n",
19287            ),
19288            (
19289                &[b"TS.NRANGE", b"2", b"x", b"y", b"abc", b"+"],
19290                "-ERR TSDB: wrong fromTimestamp\r\n",
19291            ),
19292            (
19293                &[b"TS.NRANGE", b"2", b"x", b"y", b"-", b"abc"],
19294                "-ERR TSDB: wrong toTimestamp\r\n",
19295            ),
19296            // A name slot that is missing or holds a number is the count
19297            // sentence, and a width slot that is itself a reduction name is
19298            // that sentence as well.
19299            (
19300                &[
19301                    b"TS.NRANGE",
19302                    b"2",
19303                    b"x",
19304                    b"y",
19305                    b"-",
19306                    b"+",
19307                    b"AGGREGATION",
19308                    b"avg",
19309                ],
19310                numkeys,
19311            ),
19312            (
19313                &[
19314                    b"TS.NRANGE",
19315                    b"2",
19316                    b"x",
19317                    b"y",
19318                    b"-",
19319                    b"+",
19320                    b"AGGREGATION",
19321                    b"100",
19322                    b"sum",
19323                    b"100",
19324                ],
19325                numkeys,
19326            ),
19327            (
19328                &[
19329                    b"TS.NRANGE",
19330                    b"2",
19331                    b"x",
19332                    b"y",
19333                    b"-",
19334                    b"+",
19335                    b"AGGREGATION",
19336                    b"avg",
19337                    b"sum",
19338                    b"sum",
19339                    b"100",
19340                ],
19341                numkeys,
19342            ),
19343            (
19344                &[
19345                    b"TS.NRANGE",
19346                    b"2",
19347                    b"x",
19348                    b"y",
19349                    b"-",
19350                    b"+",
19351                    b"AGGREGATION",
19352                    b"avg",
19353                    b"sum",
19354                    b"abc",
19355                ],
19356                "-ERR TSDB: Couldn't parse AGGREGATION\r\n",
19357            ),
19358            (
19359                &[
19360                    b"TS.NRANGE",
19361                    b"2",
19362                    b"x",
19363                    b"y",
19364                    b"-",
19365                    b"+",
19366                    b"AGGREGATION",
19367                    b"avg",
19368                    b"sum",
19369                    b"0",
19370                ],
19371                "-ERR TSDB: bucketDuration must be greater than zero\r\n",
19372            ),
19373            // With one key none of that applies and the plain parser runs, so a
19374            // lone width is a missing width rather than a count mismatch.
19375            (
19376                &[b"TS.NRANGE", b"1", b"x", b"-", b"+", b"AGGREGATION", b"100"],
19377                "-ERR TSDB: Couldn't parse AGGREGATION\r\n",
19378            ),
19379            (
19380                &[
19381                    b"TS.NRANGE",
19382                    b"1",
19383                    b"x",
19384                    b"-",
19385                    b"+",
19386                    b"AGGREGATION",
19387                    b"100",
19388                    b"200",
19389                ],
19390                "-ERR TSDB: Unknown aggregation type\r\n",
19391            ),
19392            // The keys come last and in the order they were named.
19393            (
19394                &[b"TS.NRANGE", b"2", b"x", b"nope", b"-", b"+"],
19395                "-ERR TSDB: the key does not exist\r\n",
19396            ),
19397            (
19398                &[b"TS.NRANGE", b"2", b"str", b"nope", b"-", b"+"],
19399                "-ERR WRONGTYPE Operation against a key \
19400                 holding the wrong kind of value\r\n",
19401            ),
19402        ];
19403        for (argv, want) in cases {
19404            let got = f.run(argv);
19405            assert_eq!(&got, want, "{argv:?}");
19406        }
19407    }
19408
19409    /// `TS.READ`, which is a key, one timestamp and everything from there on.
19410    #[test]
19411    fn a_read_walks_from_a_timestamp_to_the_end_of_the_series() {
19412        let mut f = joined();
19413        assert_eq!(
19414            f.run(&[b"TS.READ", b"x", b"-"]),
19415            "*4\r\n*2\r\n:10\r\n+1\r\n*2\r\n:20\r\n+2\r\n\
19416             *2\r\n:40\r\n+4\r\n*2\r\n:50\r\n+5\r\n"
19417        );
19418        // A plus is the last sample on its own, and a timestamp between two
19419        // samples starts at the one behind it.
19420        assert_eq!(
19421            f.run(&[b"TS.READ", b"x", b"+"]),
19422            "*1\r\n*2\r\n:50\r\n+5\r\n"
19423        );
19424        assert_eq!(
19425            f.run(&[b"TS.READ", b"x", b"25"]),
19426            "*2\r\n*2\r\n:40\r\n+4\r\n*2\r\n:50\r\n+5\r\n"
19427        );
19428        // Past the end, a series holding nothing and a key that is not there
19429        // are all the empty array rather than an error.
19430        assert_eq!(f.run(&[b"TS.READ", b"x", b"99"]), "*0\r\n");
19431        assert_eq!(f.run(&[b"TS.READ", b"z", b"-"]), "*0\r\n");
19432        assert_eq!(f.run(&[b"TS.READ", b"z", b"+"]), "*0\r\n");
19433        assert_eq!(f.run(&[b"TS.READ", b"nope", b"-"]), "*0\r\n");
19434        // The timestamp refusal goes out with nothing in front of it, and a key
19435        // holding something else answers the bare WRONGTYPE rather than the
19436        // module's prefixed one, both unlike the rest of the family.
19437        assert_eq!(
19438            f.run(&[b"TS.READ", b"x", b"abc"]),
19439            "-TSDB: invalid timestamp\r\n"
19440        );
19441        assert_eq!(
19442            f.run(&[b"TS.READ", b"x", b"-1"]),
19443            "-TSDB: invalid timestamp\r\n"
19444        );
19445        f.run(&[b"SET", b"str", b"hi"]);
19446        assert_eq!(
19447            f.run(&[b"TS.READ", b"str", b"-"]),
19448            "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n"
19449        );
19450        // Anything other than exactly three words is an arity error, so there
19451        // is nowhere to put an option even though the table says minus three.
19452        assert_eq!(
19453            f.run(&[b"TS.READ", b"x"]),
19454            "-ERR wrong number of arguments for 'ts.read' command\r\n"
19455        );
19456        assert_eq!(
19457            f.run(&[b"TS.READ", b"x", b"-", b"COUNT", b"1"]),
19458            "-ERR wrong number of arguments for 'ts.read' command\r\n"
19459        );
19460    }
19461
19462    /// The keys of a joined read sit behind a count, so `COMMAND GETKEYS` has
19463    /// to read the count to find them.
19464    #[test]
19465    fn getkeys_reads_the_count_of_a_joined_read() {
19466        let mut f = Fixture::new();
19467        assert_eq!(
19468            f.run(&[
19469                b"COMMAND",
19470                b"GETKEYS",
19471                b"TS.NRANGE",
19472                b"2",
19473                b"a",
19474                b"b",
19475                b"-",
19476                b"+"
19477            ]),
19478            "*2\r\n$1\r\na\r\n$1\r\nb\r\n"
19479        );
19480        assert_eq!(
19481            f.run(&[
19482                b"COMMAND",
19483                b"GETKEYS",
19484                b"TS.NREVRANGE",
19485                b"1",
19486                b"a",
19487                b"-",
19488                b"+"
19489            ]),
19490            "*1\r\n$1\r\na\r\n"
19491        );
19492        // A count of zero, or one too large for the words that follow it, is
19493        // the server's own refusal and not the module's.
19494        for n in [b"0".as_slice(), b"9", b"abc"] {
19495            assert_eq!(
19496                f.run(&[b"COMMAND", b"GETKEYS", b"TS.NRANGE", n, b"a", b"-", b"+"]),
19497                "-ERR Invalid arguments specified for command\r\n"
19498            );
19499        }
19500    }
19501
19502    /// The five series every test of the label surface works against.
19503    fn labelled() -> Fixture {
19504        let mut f = Fixture::new();
19505        f.run(&[
19506            b"TS.CREATE",
19507            b"a",
19508            b"LABELS",
19509            b"room",
19510            b"kitchen",
19511            b"x",
19512            b"1",
19513        ]);
19514        f.run(&[
19515            b"TS.CREATE",
19516            b"b",
19517            b"LABELS",
19518            b"room",
19519            b"bedroom",
19520            b"x",
19521            b"2",
19522        ]);
19523        f.run(&[b"TS.CREATE", b"c", b"LABELS", b"room", b"kitchen"]);
19524        f.run(&[b"TS.CREATE", b"d"]);
19525        f.run(&[b"TS.CREATE", b"e", b"LABELS", b"r", b"bb", b"r", b"b"]);
19526        f.run(&[b"TS.ADD", b"a", b"100", b"1.5"]);
19527        f.run(&[b"TS.ADD", b"b", b"200", b"2"]);
19528        f
19529    }
19530
19531    /// The filter grammar, which is four steps and a `strtok` rather than a
19532    /// grammar, and which every command that searches on labels shares.
19533    #[test]
19534    fn a_filter_is_taken_apart_the_way_the_module_takes_one_apart() {
19535        let mut f = labelled();
19536        let cases: &[(&[&[u8]], &str)] = &[
19537            // The plain forms, and the order the answer comes back in, which is
19538            // by key name and not by anything the series remembers.
19539            (
19540                &[b"TS.QUERYINDEX", b"room=kitchen"],
19541                "*2\r\n$1\r\na\r\n$1\r\nc\r\n",
19542            ),
19543            (
19544                &[b"TS.QUERYINDEX", b"room=kitchen", b"x=1"],
19545                "*1\r\n$1\r\na\r\n",
19546            ),
19547            (
19548                &[b"TS.QUERYINDEX", b"room=(kitchen,bedroom)"],
19549                "*3\r\n$1\r\na\r\n$1\r\nb\r\n$1\r\nc\r\n",
19550            ),
19551            // An empty list still counts as something that says which series to
19552            // take, it just never takes any.
19553            (&[b"TS.QUERYINDEX", b"room=()"], "*0\r\n"),
19554            // Absent and present, neither of which stands on its own.
19555            (
19556                &[b"TS.QUERYINDEX", b"x=", b"room=kitchen"],
19557                "*1\r\n$1\r\nc\r\n",
19558            ),
19559            (
19560                &[b"TS.QUERYINDEX", b"room=kitchen", b"x!="],
19561                "*1\r\n$1\r\na\r\n",
19562            ),
19563            (
19564                &[b"TS.QUERYINDEX", b"room!=kitchen", b"x!="],
19565                "-ERR TSDB: please provide at least one matcher\r\n",
19566            ),
19567            // A run of separators is one separator and everything past the
19568            // second field is dropped, so all three of these ask one question.
19569            (
19570                &[b"TS.QUERYINDEX", b"room==kitchen"],
19571                "*2\r\n$1\r\na\r\n$1\r\nc\r\n",
19572            ),
19573            (
19574                &[b"TS.QUERYINDEX", b"room=kitchen=zz"],
19575                "*2\r\n$1\r\na\r\n$1\r\nc\r\n",
19576            ),
19577            (&[b"TS.QUERYINDEX", b"room!!=kitchen", b"x=1"], "*0\r\n"),
19578            // A bracket is only a list when it sits straight behind the
19579            // separator, and then the label in front of it has to be there.
19580            (&[b"TS.QUERYINDEX", b"()=1"], "*0\r\n"),
19581            (
19582                &[b"TS.QUERYINDEX", b"=(1)"],
19583                "-ERR TSDB: failed parsing labels\r\n",
19584            ),
19585            (
19586                &[b"TS.QUERYINDEX", b"room=(kitchen,)"],
19587                "-ERR TSDB: failed parsing labels\r\n",
19588            ),
19589            (
19590                &[b"TS.QUERYINDEX", b"room=(kitchen"],
19591                "-ERR TSDB: failed parsing labels\r\n",
19592            ),
19593            (&[b"TS.QUERYINDEX", b"room=x()"], "*0\r\n"),
19594            (
19595                &[b"TS.QUERYINDEX", b"nonsense"],
19596                "-ERR TSDB: failed parsing labels\r\n",
19597            ),
19598            // Nothing here says which series to take.
19599            (
19600                &[b"TS.QUERYINDEX", b"room!=kitchen"],
19601                "-ERR TSDB: please provide at least one matcher\r\n",
19602            ),
19603            // Names and values are both compared byte for byte.
19604            (&[b"TS.QUERYINDEX", b"ROOM=kitchen"], "*0\r\n"),
19605            (&[b"TS.QUERYINDEX", b"room=KITCHEN"], "*0\r\n"),
19606            (
19607                &[b"TS.QUERYINDEX"],
19608                "-ERR wrong number of arguments for 'ts.queryindex' command\r\n",
19609            ),
19610        ];
19611        for (argv, want) in cases {
19612            let got = f.run(argv);
19613            assert_eq!(&got, want, "{:?}", argv.last());
19614        }
19615    }
19616
19617    /// `TS.QUERYLABELS`, whose filter is the one that is allowed to be missing.
19618    #[test]
19619    fn querylabels_says_which_names_are_worn_and_what_they_are_set_to() {
19620        let mut f = labelled();
19621        let cases: &[(&[&[u8]], &str)] = &[
19622            (
19623                &[b"TS.QUERYLABELS", b"LABELS"],
19624                "*3\r\n$1\r\nr\r\n$4\r\nroom\r\n$1\r\nx\r\n",
19625            ),
19626            (
19627                &[b"TS.QUERYLABELS", b"LABELS", b"FILTER", b"room=kitchen"],
19628                "*2\r\n$4\r\nroom\r\n$1\r\nx\r\n",
19629            ),
19630            (
19631                &[b"TS.QUERYLABELS", b"VALUES", b"room"],
19632                "*2\r\n$7\r\nbedroom\r\n$7\r\nkitchen\r\n",
19633            ),
19634            // The series wearing `r` twice contributes the smaller of the two
19635            // here, which is not the one it was written down as first.
19636            (&[b"TS.QUERYLABELS", b"VALUES", b"r"], "*1\r\n$1\r\nb\r\n"),
19637            (&[b"TS.QUERYLABELS", b"VALUES", b"nolabel"], "*0\r\n"),
19638            (
19639                &[b"TS.QUERYLABELS", b"VALUES"],
19640                "-ERR wrong number of arguments for 'ts.querylabels' command\r\n",
19641            ),
19642            (
19643                &[b"TS.QUERYLABELS", b"ZZZ"],
19644                "-ERR TSDB: unknown subtype, must be one of LABELS|VALUES\r\n",
19645            ),
19646            (
19647                &[b"TS.QUERYLABELS", b"LABELS", b"ZZZ"],
19648                "-ERR TSDB: unknown argument, expected FILTER\r\n",
19649            ),
19650            (
19651                &[b"TS.QUERYLABELS", b"LABELS", b"FILTER"],
19652                "-ERR TSDB: FILTER given with no filter expressions\r\n",
19653            ),
19654            // With no filter at all every series is taken, which is why the
19655            // first case here answers about `r` as well. A filter that is there
19656            // still has to say which series to take.
19657            (
19658                &[b"TS.QUERYLABELS", b"LABELS", b"FILTER", b"room!=kitchen"],
19659                "-ERR TSDB: please provide at least one matcher\r\n",
19660            ),
19661            (
19662                &[
19663                    b"TS.QUERYLABELS",
19664                    b"LABELS",
19665                    b"FILTER",
19666                    b"room=kitchen",
19667                    b"x=",
19668                ],
19669                "*1\r\n$4\r\nroom\r\n",
19670            ),
19671        ];
19672        for (argv, want) in cases {
19673            let got = f.run(argv);
19674            assert_eq!(&got, want, "{:?}", argv.last());
19675        }
19676    }
19677
19678    /// `TS.MGET`, the newest sample of every series a filter takes, and the two
19679    /// ways of asking for the labels back alongside it.
19680    #[test]
19681    fn mget_writes_the_newest_sample_and_the_labels_that_were_asked_for() {
19682        let mut f = labelled();
19683        let cases: &[(&[&[u8]], &str)] = &[
19684            // A series with no samples writes an empty array where the sample
19685            // goes rather than dropping out of the reply.
19686            (
19687                &[b"TS.MGET", b"FILTER", b"room=kitchen"],
19688                "*2\r\n*3\r\n$1\r\na\r\n*0\r\n*2\r\n:100\r\n+1.5\r\n\
19689                 *3\r\n$1\r\nc\r\n*0\r\n*0\r\n",
19690            ),
19691            (
19692                &[b"TS.MGET", b"WITHLABELS", b"FILTER", b"room=kitchen"],
19693                "*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\
19694                 *2\r\n$1\r\nx\r\n$1\r\n1\r\n*2\r\n:100\r\n+1.5\r\n\
19695                 *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",
19696            ),
19697            // A selected label the series does not wear is a nil, not a gap.
19698            (
19699                &[
19700                    b"TS.MGET",
19701                    b"SELECTED_LABELS",
19702                    b"x",
19703                    b"FILTER",
19704                    b"room=kitchen",
19705                ],
19706                "*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\
19707                 *2\r\n:100\r\n+1.5\r\n\
19708                 *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",
19709            ),
19710            // The other half of the duplicated name rule. This one takes the
19711            // first written down where `TS.QUERYLABELS` takes the smallest.
19712            (
19713                &[b"TS.MGET", b"SELECTED_LABELS", b"r", b"FILTER", b"r=b"],
19714                "*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",
19715            ),
19716            (
19717                &[b"TS.MGET", b"WITHLABELS", b"FILTER", b"r=b"],
19718                "*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\
19719                 *2\r\n$1\r\nr\r\n$1\r\nb\r\n*0\r\n",
19720            ),
19721            // A word that is not an option is ignored, but a missing `FILTER`
19722            // is an arity error whatever else was written.
19723            (
19724                &[b"TS.MGET", b"ZZZ", b"FILTER", b"room=bedroom"],
19725                "*1\r\n*3\r\n$1\r\nb\r\n*0\r\n*2\r\n:200\r\n+2\r\n",
19726            ),
19727            (
19728                &[b"TS.MGET", b"a", b"b", b"c"],
19729                "-ERR wrong number of arguments for 'ts.mget' command\r\n",
19730            ),
19731            (
19732                &[b"TS.MGET", b"FILTER"],
19733                "-ERR wrong number of arguments for 'ts.mget' command\r\n",
19734            ),
19735            // Both keyword checks happen before the filter is read, and the two
19736            // sentences spell the second keyword without its `ED`.
19737            (
19738                &[
19739                    b"TS.MGET",
19740                    b"WITHLABELS",
19741                    b"SELECTED_LABELS",
19742                    b"x",
19743                    b"FILTER",
19744                    b"bad",
19745                ],
19746                "-ERR TSDB: cannot accept WITHLABELS and SELECT_LABELS together\r\n",
19747            ),
19748            (
19749                &[b"TS.MGET", b"SELECTED_LABELS", b"FILTER", b"bad"],
19750                "-ERR TSDB: SELECT_LABELS should have at least 1 parameter\r\n",
19751            ),
19752        ];
19753        for (argv, want) in cases {
19754            let got = f.run(argv);
19755            assert_eq!(&got, want, "{:?}", argv.last());
19756        }
19757    }
19758
19759    /// What RESP3 changes across the label surface, which is a set where there
19760    /// was an array and a map where there was a pair of them.
19761    #[test]
19762    fn resp3_writes_the_label_surface_as_sets_and_maps() {
19763        let mut f = labelled();
19764        f.out = Out::new(Proto::Resp3);
19765        let cases: &[(&[&[u8]], &str)] = &[
19766            (
19767                &[b"TS.QUERYINDEX", b"room=kitchen"],
19768                "~2\r\n$1\r\na\r\n$1\r\nc\r\n",
19769            ),
19770            (
19771                &[b"TS.QUERYLABELS", b"LABELS"],
19772                "~3\r\n$1\r\nr\r\n$4\r\nroom\r\n$1\r\nx\r\n",
19773            ),
19774            (
19775                &[b"TS.QUERYLABELS", b"VALUES", b"room"],
19776                "~2\r\n$7\r\nbedroom\r\n$7\r\nkitchen\r\n",
19777            ),
19778            // The key stops being the first of three and becomes the map key,
19779            // and the labels stop being pairs and become a map of their own.
19780            (
19781                &[b"TS.MGET", b"FILTER", b"room=kitchen"],
19782                "%2\r\n$1\r\na\r\n*2\r\n%0\r\n*2\r\n:100\r\n,1.5\r\n\
19783                 $1\r\nc\r\n*2\r\n%0\r\n*0\r\n",
19784            ),
19785            (
19786                &[b"TS.MGET", b"WITHLABELS", b"FILTER", b"room=kitchen"],
19787                "%2\r\n$1\r\na\r\n*2\r\n%2\r\n$4\r\nroom\r\n$7\r\nkitchen\r\n\
19788                 $1\r\nx\r\n$1\r\n1\r\n*2\r\n:100\r\n,1.5\r\n\
19789                 $1\r\nc\r\n*2\r\n%1\r\n$4\r\nroom\r\n$7\r\nkitchen\r\n*0\r\n",
19790            ),
19791            (
19792                &[
19793                    b"TS.MGET",
19794                    b"SELECTED_LABELS",
19795                    b"x",
19796                    b"FILTER",
19797                    b"room=kitchen",
19798                ],
19799                "%2\r\n$1\r\na\r\n*2\r\n%1\r\n$1\r\nx\r\n$1\r\n1\r\n\
19800                 *2\r\n:100\r\n,1.5\r\n\
19801                 $1\r\nc\r\n*2\r\n%1\r\n$1\r\nx\r\n_\r\n*0\r\n",
19802            ),
19803            // A map with a name in it twice, which is what a series wearing one
19804            // label name twice turns into.
19805            (
19806                &[b"TS.MGET", b"WITHLABELS", b"FILTER", b"r=b"],
19807                "%1\r\n$1\r\ne\r\n*2\r\n%2\r\n$1\r\nr\r\n$2\r\nbb\r\n\
19808                 $1\r\nr\r\n$1\r\nb\r\n*0\r\n",
19809            ),
19810        ];
19811        for (argv, want) in cases {
19812            let got = f.run(argv);
19813            assert_eq!(&got, want, "{:?}", argv.last());
19814        }
19815    }
19816
19817    /// The same five series with enough samples in them for a group to have
19818    /// something to fold.
19819    fn spanned() -> Fixture {
19820        let mut f = labelled();
19821        f.run(&[b"TS.ADD", b"a", b"200", b"2.5"]);
19822        f.run(&[b"TS.ADD", b"c", b"100", b"10"]);
19823        f.run(&[b"TS.ADD", b"c", b"300", b"30"]);
19824        f
19825    }
19826
19827    /// A span read out of every series a filter takes, with and without a group
19828    /// over the top of it.
19829    #[test]
19830    fn mrange_reads_every_series_and_folds_the_groups_it_is_asked_for() {
19831        let mut f = spanned();
19832        let cases: &[(&[&[u8]], &str)] = &[
19833            (
19834                &[b"TS.MRANGE", b"-", b"+", b"FILTER", b"room=kitchen"],
19835                "*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\
19836                 *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",
19837            ),
19838            // Newest first is applied to each series before anything else sees
19839            // the rows.
19840            (
19841                &[
19842                    b"TS.MREVRANGE",
19843                    b"-",
19844                    b"+",
19845                    b"WITHLABELS",
19846                    b"FILTER",
19847                    b"room=kitchen",
19848                ],
19849                "*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\
19850                 *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\
19851                 *3\r\n$1\r\nc\r\n*1\r\n*2\r\n$4\r\nroom\r\n$7\r\nkitchen\r\n\
19852                 *2\r\n*2\r\n:300\r\n+30\r\n*2\r\n:100\r\n+10\r\n",
19853            ),
19854            // A label a series does not wear comes back against a nil rather
19855            // than being left out.
19856            (
19857                &[
19858                    b"TS.MRANGE",
19859                    b"-",
19860                    b"+",
19861                    b"SELECTED_LABELS",
19862                    b"x",
19863                    b"FILTER",
19864                    b"room=kitchen",
19865                ],
19866                "*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\
19867                 *2\r\n*2\r\n:100\r\n+1.5\r\n*2\r\n:200\r\n+2.5\r\n\
19868                 *3\r\n$1\r\nc\r\n*1\r\n*2\r\n$1\r\nx\r\n$-1\r\n\
19869                 *2\r\n*2\r\n:100\r\n+10\r\n*2\r\n:300\r\n+30\r\n",
19870            ),
19871            // The fold: 100 is in both series and adds up, the other two are in
19872            // one each and are still rows.
19873            (
19874                &[
19875                    b"TS.MRANGE",
19876                    b"-",
19877                    b"+",
19878                    b"FILTER",
19879                    b"room=kitchen",
19880                    b"GROUPBY",
19881                    b"room",
19882                    b"REDUCE",
19883                    b"sum",
19884                ],
19885                "*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\
19886                 *2\r\n:200\r\n+2.5\r\n*2\r\n:300\r\n+30\r\n",
19887            ),
19888            // RESP2 has nowhere to put the reducer and the member keys, so a
19889            // group wearing labels writes them as two more labels.
19890            (
19891                &[
19892                    b"TS.MRANGE",
19893                    b"-",
19894                    b"+",
19895                    b"WITHLABELS",
19896                    b"FILTER",
19897                    b"room=kitchen",
19898                    b"GROUPBY",
19899                    b"room",
19900                    b"REDUCE",
19901                    b"max",
19902                ],
19903                "*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\
19904                 *2\r\n$11\r\n__reducer__\r\n$3\r\nmax\r\n\
19905                 *2\r\n$10\r\n__source__\r\n$3\r\na,c\r\n\
19906                 *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",
19907            ),
19908            // A count is applied to each member and then again to the fold.
19909            (
19910                &[
19911                    b"TS.MREVRANGE",
19912                    b"-",
19913                    b"+",
19914                    b"COUNT",
19915                    b"1",
19916                    b"FILTER",
19917                    b"room=kitchen",
19918                    b"GROUPBY",
19919                    b"room",
19920                    b"REDUCE",
19921                    b"count",
19922                ],
19923                "*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",
19924            ),
19925            // Nothing wears the label, so nothing is in any group.
19926            (
19927                &[
19928                    b"TS.MRANGE",
19929                    b"-",
19930                    b"+",
19931                    b"FILTER",
19932                    b"room=kitchen",
19933                    b"GROUPBY",
19934                    b"nope",
19935                    b"REDUCE",
19936                    b"sum",
19937                ],
19938                "*0\r\n",
19939            ),
19940            (
19941                &[
19942                    b"TS.MRANGE",
19943                    b"-",
19944                    b"+",
19945                    b"AGGREGATION",
19946                    b"sum,avg",
19947                    b"100",
19948                    b"FILTER",
19949                    b"room=bedroom",
19950                ],
19951                "*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",
19952            ),
19953            // The errors, in the order they are looked for.
19954            (
19955                &[b"TS.MRANGE", b"-", b"+", b"room=kitchen"],
19956                "-ERR TSDB: missing FILTER argument\r\n",
19957            ),
19958            (
19959                &[b"TS.MRANGE", b"-", b"+", b"FILTER"],
19960                "-ERR TSDB: missing labels for filter argument\r\n",
19961            ),
19962            (
19963                &[
19964                    b"TS.MRANGE",
19965                    b"-",
19966                    b"+",
19967                    b"GROUPBY",
19968                    b"room",
19969                    b"REDUCE",
19970                    b"sum",
19971                    b"FILTER",
19972                    b"room=kitchen",
19973                ],
19974                "-ERR TSDB: GROUPBY should always come after filter\r\n",
19975            ),
19976            // The group is four words from the end here, so the length is what
19977            // is wrong with it.
19978            (
19979                &[
19980                    b"TS.MRANGE",
19981                    b"-",
19982                    b"+",
19983                    b"FILTER",
19984                    b"room=kitchen",
19985                    b"GROUPBY",
19986                    b"room",
19987                    b"REDUCE",
19988                    b"sum",
19989                    b"x",
19990                ],
19991                "-ERR wrong number of arguments for 'ts.mrange' command\r\n",
19992            ),
19993            // And here it is not, so its words are filters and answer first.
19994            (
19995                &[
19996                    b"TS.MRANGE",
19997                    b"-",
19998                    b"+",
19999                    b"FILTER",
20000                    b"nope",
20001                    b"GROUPBY",
20002                    b"room",
20003                    b"REDUCE",
20004                    b"sum",
20005                    b"x",
20006                ],
20007                "-ERR TSDB: failed parsing labels\r\n",
20008            ),
20009            (
20010                &[
20011                    b"TS.MRANGE",
20012                    b"-",
20013                    b"+",
20014                    b"FILTER",
20015                    b"room=kitchen",
20016                    b"GROUPBY",
20017                    b"room",
20018                    b"REDUCE",
20019                    b"twa",
20020                ],
20021                "-ERR TSDB: Invalid reducer type\r\n",
20022            ),
20023            (
20024                &[
20025                    b"TS.MRANGE",
20026                    b"-",
20027                    b"+",
20028                    b"AGGREGATION",
20029                    b"sum,avg",
20030                    b"100",
20031                    b"FILTER",
20032                    b"room=kitchen",
20033                    b"GROUPBY",
20034                    b"room",
20035                    b"REDUCE",
20036                    b"sum",
20037                ],
20038                "-ERR TSDB: GROUPBY is not allowed when multiple aggregators are specified\r\n",
20039            ),
20040            // The label list ends at a keyword, so this is a `COUNT` with a
20041            // `FILTER` where its number should be.
20042            (
20043                &[
20044                    b"TS.MRANGE",
20045                    b"-",
20046                    b"+",
20047                    b"SELECTED_LABELS",
20048                    b"COUNT",
20049                    b"FILTER",
20050                    b"room=kitchen",
20051                ],
20052                "-ERR TSDB: Couldn't parse COUNT\r\n",
20053            ),
20054        ];
20055        for (argv, want) in cases {
20056            let got = f.run(argv);
20057            assert_eq!(&got, want, "{argv:?}");
20058        }
20059    }
20060
20061    /// The multi key reads on RESP3, where the key becomes a map key and the
20062    /// reducer and the member keys become fields of their own.
20063    #[test]
20064    fn resp3_writes_a_multi_key_read_as_a_map_of_four() {
20065        let mut f = spanned();
20066        f.out = Out::new(Proto::Resp3);
20067        let cases: &[(&[&[u8]], &str)] = &[
20068            (
20069                &[b"TS.MRANGE", b"-", b"+", b"FILTER", b"room=bedroom"],
20070                "%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\
20071                 *1\r\n*2\r\n:200\r\n,2\r\n",
20072            ),
20073            // The reductions a read asked for, which RESP2 has no room for at
20074            // all and which is empty on a read that asked for none.
20075            (
20076                &[
20077                    b"TS.MRANGE",
20078                    b"-",
20079                    b"+",
20080                    b"AGGREGATION",
20081                    b"sum,avg",
20082                    b"100",
20083                    b"FILTER",
20084                    b"room=bedroom",
20085                ],
20086                "%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\
20087                 $3\r\navg\r\n*1\r\n*3\r\n:200\r\n,2\r\n,2\r\n",
20088            ),
20089            (
20090                &[
20091                    b"TS.MRANGE",
20092                    b"-",
20093                    b"+",
20094                    b"FILTER",
20095                    b"room=kitchen",
20096                    b"GROUPBY",
20097                    b"room",
20098                    b"REDUCE",
20099                    b"sum",
20100                ],
20101                "%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\
20102                 $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\
20103                 *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",
20104            ),
20105            // The labels hold only the pair the group was made on, because the
20106            // reducer and the sources have somewhere else to go.
20107            (
20108                &[
20109                    b"TS.MRANGE",
20110                    b"-",
20111                    b"+",
20112                    b"WITHLABELS",
20113                    b"FILTER",
20114                    b"room=kitchen",
20115                    b"GROUPBY",
20116                    b"room",
20117                    b"REDUCE",
20118                    b"max",
20119                ],
20120                "%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\
20121                 %1\r\n$8\r\nreducers\r\n*1\r\n$3\r\nmax\r\n\
20122                 %1\r\n$7\r\nsources\r\n*2\r\n$1\r\na\r\n$1\r\nc\r\n\
20123                 *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",
20124            ),
20125            (
20126                &[
20127                    b"TS.MRANGE",
20128                    b"-",
20129                    b"+",
20130                    b"FILTER",
20131                    b"room=kitchen",
20132                    b"GROUPBY",
20133                    b"nope",
20134                    b"REDUCE",
20135                    b"sum",
20136                ],
20137                "%0\r\n",
20138            ),
20139        ];
20140        for (argv, want) in cases {
20141            let got = f.run(argv);
20142            assert_eq!(&got, want, "{argv:?}");
20143        }
20144    }
20145
20146    /// `TS.CREATERULE`, whose refusals come in an order of their own.
20147    #[test]
20148    fn createrule_checks_the_two_keys_last_and_the_two_links_after_that() {
20149        let mut f = Fixture::new();
20150        f.run(&[b"TS.CREATE", b"src"]);
20151        f.run(&[b"TS.CREATE", b"dst"]);
20152        f.run(&[b"SET", b"plain", b"v"]);
20153        let cases: &[(&[&[u8]], &str)] = &[
20154            // The width is read before the reduction, the reduction before the
20155            // width being above zero, and all three before either key is looked
20156            // at, so a command that is wrong twice complains about the first.
20157            (
20158                &[
20159                    b"TS.CREATERULE",
20160                    b"src",
20161                    b"dst",
20162                    b"AGGREGATION",
20163                    b"nope",
20164                    b"x",
20165                ],
20166                "-ERR TSDB: Couldn't parse AGGREGATION\r\n",
20167            ),
20168            (
20169                &[
20170                    b"TS.CREATERULE",
20171                    b"src",
20172                    b"dst",
20173                    b"AGGREGATION",
20174                    b"nope",
20175                    b"10",
20176                ],
20177                "-ERR TSDB: Unknown aggregation type\r\n",
20178            ),
20179            (
20180                &[
20181                    b"TS.CREATERULE",
20182                    b"src",
20183                    b"dst",
20184                    b"AGGREGATION",
20185                    b"avg",
20186                    b"0",
20187                ],
20188                "-ERR TSDB: bucketDuration must be greater than zero\r\n",
20189            ),
20190            (
20191                &[
20192                    b"TS.CREATERULE",
20193                    b"src",
20194                    b"dst",
20195                    b"AGGREGATION",
20196                    b"avg",
20197                    b"10",
20198                    b"x",
20199                ],
20200                "-ERR TSDB: Couldn't parse alignTimestamp\r\n",
20201            ),
20202            (
20203                &[
20204                    b"TS.CREATERULE",
20205                    b"src",
20206                    b"src",
20207                    b"AGGREGATION",
20208                    b"avg",
20209                    b"10",
20210                ],
20211                "-ERR TSDB: the source key and destination key should be different\r\n",
20212            ),
20213            // A key holding something else answers the same as a key that is not
20214            // there at all, because the source is looked up first and neither of
20215            // them is a series.
20216            (
20217                &[
20218                    b"TS.CREATERULE",
20219                    b"nope",
20220                    b"plain",
20221                    b"AGGREGATION",
20222                    b"avg",
20223                    b"10",
20224                ],
20225                "-ERR TSDB: the key does not exist\r\n",
20226            ),
20227            (
20228                &[
20229                    b"TS.CREATERULE",
20230                    b"src",
20231                    b"nope",
20232                    b"AGGREGATION",
20233                    b"avg",
20234                    b"10",
20235                ],
20236                "-ERR TSDB: the key does not exist\r\n",
20237            ),
20238            // A keyword other than AGGREGATION is an arity error rather than a
20239            // syntax one, because the arity is all that is checked.
20240            (
20241                &[b"TS.CREATERULE", b"src", b"dst", b"NOPE", b"avg", b"10"],
20242                "-ERR wrong number of arguments for 'ts.createrule' command\r\n",
20243            ),
20244            (
20245                &[
20246                    b"TS.CREATERULE",
20247                    b"src",
20248                    b"dst",
20249                    b"AGGREGATION",
20250                    b"avg",
20251                    b"10",
20252                ],
20253                "+OK\r\n",
20254            ),
20255            // The link is now in place, so the same rule again is refused from
20256            // the destination's end.
20257            (
20258                &[
20259                    b"TS.CREATERULE",
20260                    b"src",
20261                    b"dst",
20262                    b"AGGREGATION",
20263                    b"avg",
20264                    b"10",
20265                ],
20266                "-ERR TSDB: the destination key already has a src rule\r\n",
20267            ),
20268            // A source that is already someone's destination, and a destination
20269            // that is already someone's source, are two different sentences.
20270            (
20271                &[
20272                    b"TS.CREATERULE",
20273                    b"dst",
20274                    b"src",
20275                    b"AGGREGATION",
20276                    b"avg",
20277                    b"10",
20278                ],
20279                "-ERR TSDB: the source key already has a source rule\r\n",
20280            ),
20281            (&[b"TS.DELETERULE", b"src", b"dst"], "+OK\r\n"),
20282            (
20283                &[b"TS.DELETERULE", b"src", b"dst"],
20284                "-ERR TSDB: compaction rule does not exist\r\n",
20285            ),
20286            // The source is looked up and the destination is not, so a missing
20287            // destination is a missing rule and a missing source is a missing
20288            // key, which is the other way round from `TS.CREATERULE`.
20289            (
20290                &[b"TS.DELETERULE", b"src", b"nope"],
20291                "-ERR TSDB: compaction rule does not exist\r\n",
20292            ),
20293            (
20294                &[b"TS.DELETERULE", b"nope", b"dst"],
20295                "-ERR TSDB: the key does not exist\r\n",
20296            ),
20297        ];
20298        for (argv, want) in cases {
20299            let got = f.run(argv);
20300            assert_eq!(&got, want, "{argv:?}");
20301        }
20302    }
20303
20304    /// What a rule writes, which is every bucket but the one it is filling.
20305    #[test]
20306    fn a_rule_writes_a_bucket_when_a_later_reading_closes_it() {
20307        let mut f = Fixture::new();
20308        f.run(&[b"TS.CREATE", b"src"]);
20309        f.run(&[b"TS.CREATE", b"dst"]);
20310        // The readings written before the rule was made are not folded, so the
20311        // destination is still empty after the first two.
20312        f.run(&[b"TS.ADD", b"src", b"10", b"1"]);
20313        f.run(&[
20314            b"TS.CREATERULE",
20315            b"src",
20316            b"dst",
20317            b"AGGREGATION",
20318            b"sum",
20319            b"100",
20320        ]);
20321        f.run(&[b"TS.ADD", b"src", b"20", b"2"]);
20322        assert_eq!(f.run(&[b"TS.RANGE", b"dst", b"-", b"+"]), "*0\r\n");
20323        // The bucket the rule is filling holds only what it was given, so it is
20324        // 2 rather than 3, and it is written when a reading lands past it.
20325        assert_eq!(f.run(&[b"TS.GET", b"dst", b"LATEST"]), "*2\r\n:0\r\n+2\r\n");
20326        f.run(&[b"TS.ADD", b"src", b"110", b"4"]);
20327        assert_eq!(
20328            f.run(&[b"TS.RANGE", b"dst", b"-", b"+"]),
20329            "*1\r\n*2\r\n:0\r\n+2\r\n"
20330        );
20331        // A reading into a bucket that has already been written works that
20332        // bucket out again over everything the source now holds.
20333        f.run(&[b"TS.ADD", b"src", b"30", b"8"]);
20334        assert_eq!(
20335            f.run(&[b"TS.RANGE", b"dst", b"-", b"+"]),
20336            "*1\r\n*2\r\n:0\r\n+11\r\n"
20337        );
20338        // Deleting from the source works the buckets it touched out again and
20339        // reopens the newest one, so `LATEST` starts from the whole bucket.
20340        assert_eq!(f.run(&[b"TS.DEL", b"src", b"0", b"25"]), ":2\r\n");
20341        assert_eq!(
20342            f.run(&[b"TS.RANGE", b"dst", b"-", b"+"]),
20343            "*1\r\n*2\r\n:0\r\n+8\r\n"
20344        );
20345        assert_eq!(
20346            f.run(&[b"TS.GET", b"dst", b"LATEST"]),
20347            "*2\r\n:100\r\n+4\r\n"
20348        );
20349        // The link shows on both ends, and dropping either key takes it down.
20350        assert!(f.run(&[b"TS.INFO", b"dst"]).contains("sourceKey"));
20351        f.run(&[b"DEL", b"dst"]);
20352        assert_eq!(
20353            f.run(&[b"TS.DELETERULE", b"src", b"dst"]),
20354            "-ERR TSDB: compaction rule does not exist\r\n"
20355        );
20356    }
20357
20358    /// The three shapes an `XADD` id can take, and the one rule behind all of
20359    /// them.
20360    #[test]
20361    fn xadd_ids_only_ever_go_up() {
20362        let mut f = Fixture::new();
20363        // A bare millisecond is that millisecond and sequence zero.
20364        assert_eq!(f.run(&[b"XADD", b"s", b"5", b"a", b"1"]), "$3\r\n5-0\r\n");
20365        // And `5-*` is the next free sequence inside it.
20366        assert_eq!(f.run(&[b"XADD", b"s", b"5-*", b"a", b"2"]), "$3\r\n5-1\r\n");
20367        assert_eq!(f.run(&[b"XADD", b"s", b"5-*", b"a", b"3"]), "$3\r\n5-2\r\n");
20368        assert_eq!(f.run(&[b"XADD", b"s", b"6-9", b"a", b"4"]), "$3\r\n6-9\r\n");
20369        assert_eq!(f.run(&[b"XLEN", b"s"]), ":4\r\n");
20370
20371        assert!(
20372            f.run(&[b"XADD", b"s", b"6-9", b"a", b"5"])
20373                .contains("equal or smaller")
20374        );
20375        assert!(
20376            f.run(&[b"XADD", b"s", b"0-0", b"a", b"5"])
20377                .contains("must be greater than 0-0")
20378        );
20379        assert!(
20380            f.run(&[b"XADD", b"s", b"nonsense", b"a", b"5"])
20381                .contains("Invalid stream ID")
20382        );
20383        // The pairs have to be pairs, and Redis calls an odd one an arity error
20384        // rather than a syntax error even though the table has already passed.
20385        assert!(
20386            f.run(&[b"XADD", b"s", b"*", b"a"])
20387                .contains("wrong number of arguments")
20388        );
20389
20390        // `NOMKSTREAM` on a key that is not there is a null and not a zero, so a
20391        // producer can tell nobody is consuming this yet from the write landed.
20392        assert_eq!(
20393            f.run(&[b"XADD", b"gone", b"NOMKSTREAM", b"*", b"a", b"1"]),
20394            "$-1\r\n"
20395        );
20396        assert_eq!(f.run(&[b"EXISTS", b"gone"]), ":0\r\n");
20397        assert_eq!(f.run(&[b"TYPE", b"s"]), "+stream\r\n");
20398        assert_eq!(f.run(&[b"OBJECT", b"ENCODING", b"s"]), "$6\r\nstream\r\n");
20399    }
20400
20401    /// The trim options, which are three keywords that disagree about how many
20402    /// arguments they take.
20403    #[test]
20404    fn trimming_reads_its_options_the_way_redis_does() {
20405        let mut f = Fixture::new();
20406        for i in 1..=10u32 {
20407            f.run(&[b"XADD", b"s", format!("{i}-1").as_bytes(), b"a", b"1"]);
20408        }
20409        assert_eq!(f.run(&[b"XTRIM", b"s", b"MAXLEN", b"4"]), ":6\r\n");
20410        assert_eq!(f.run(&[b"XLEN", b"s"]), ":4\r\n");
20411        assert_eq!(f.run(&[b"XTRIM", b"s", b"MINID", b"9"]), ":2\r\n");
20412        assert_eq!(f.run(&[b"XLEN", b"s"]), ":2\r\n");
20413
20414        // One argument after the keyword and the `~` is read as the threshold,
20415        // which is what a real server does and is the reason this is a number
20416        // complaint and not a syntax one.
20417        assert!(
20418            f.run(&[b"XTRIM", b"s", b"MAXLEN", b"~"])
20419                .contains("not an integer")
20420        );
20421        assert!(
20422            f.run(&[b"XTRIM", b"s", b"MAXLEN", b"-1"])
20423                .contains("MAXLEN argument must be >= 0")
20424        );
20425        // The strategy check runs before the approximation check, so a LIMIT
20426        // with neither is told about the missing strategy.
20427        assert!(
20428            f.run(&[b"XTRIM", b"s", b"LIMIT", b"5"])
20429                .contains("without specifying a trimming strategy")
20430        );
20431        assert!(
20432            f.run(&[b"XTRIM", b"s", b"MAXLEN", b"5", b"LIMIT", b"5"])
20433                .contains("without the special ~ option")
20434        );
20435        assert!(
20436            f.run(&[b"XTRIM", b"s", b"MAXLEN", b"5", b"MINID", b"5"])
20437                .contains("at the same time are not compatible")
20438        );
20439        // NOMKSTREAM is XADD's and XTRIM does not take it.
20440        assert!(
20441            f.run(&[b"XTRIM", b"s", b"NOMKSTREAM", b"MAXLEN", b"5"])
20442                .contains("syntax error")
20443        );
20444        assert_eq!(f.run(&[b"XTRIM", b"missing", b"MAXLEN", b"5"]), ":0\r\n");
20445    }
20446
20447    /// `XRANGE`, whose two kinds of nothing are the thing worth pinning.
20448    #[test]
20449    fn xrange_looks_the_key_up_before_it_reads_the_count() {
20450        let mut f = Fixture::new();
20451        f.run(&[b"XADD", b"s", b"5-1", b"a", b"1"]);
20452        f.run(&[b"XADD", b"s", b"6-1", b"b", b"2"]);
20453
20454        assert_eq!(
20455            f.run(&[b"XRANGE", b"s", b"-", b"+"]),
20456            "*2\r\n*2\r\n$3\r\n5-1\r\n*2\r\n$1\r\na\r\n$1\r\n1\r\n\
20457             *2\r\n$3\r\n6-1\r\n*2\r\n$1\r\nb\r\n$1\r\n2\r\n"
20458        );
20459        assert_eq!(
20460            f.run(&[b"XREVRANGE", b"s", b"+", b"-", b"COUNT", b"1"]),
20461            "*1\r\n*2\r\n$3\r\n6-1\r\n*2\r\n$1\r\nb\r\n$1\r\n2\r\n"
20462        );
20463        // The exclusive bound is stepped after the missing sequence is filled
20464        // in, so `(6` is `6-` and the largest sequence there is, minus one, and
20465        // `6-1` is still in the range.
20466        assert_eq!(
20467            f.run(&[b"XRANGE", b"s", b"-", b"(6"]),
20468            "*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\
20469             *2\r\n$3\r\n6-1\r\n*2\r\n$1\r\nb\r\n$1\r\n2\r\n"
20470        );
20471        assert_eq!(
20472            f.run(&[b"XRANGE", b"s", b"(5-1", b"+"]),
20473            "*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"
20474        );
20475        assert!(
20476            f.run(&[b"XRANGE", b"s", b"(-", b"+"])
20477                .contains("Invalid stream ID")
20478        );
20479
20480        // The two kinds of nothing. A key that is not there is an empty array
20481        // and a key that is there with a count of zero is a null array, because
20482        // the lookup happens first.
20483        assert_eq!(
20484            f.run(&[b"XRANGE", b"missing", b"-", b"+", b"COUNT", b"0"]),
20485            "*0\r\n"
20486        );
20487        assert_eq!(
20488            f.run(&[b"XRANGE", b"s", b"-", b"+", b"COUNT", b"0"]),
20489            "*-1\r\n"
20490        );
20491        f.run(&[b"SET", b"str", b"v"]);
20492        assert!(
20493            f.run(&[b"XRANGE", b"str", b"-", b"+", b"COUNT", b"0"])
20494                .starts_with("-WRONGTYPE")
20495        );
20496        // The count is read in a loop, so the last one wins.
20497        assert_eq!(
20498            f.run(&[b"XRANGE", b"s", b"-", b"+", b"COUNT", b"2", b"COUNT", b"1"]),
20499            "*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"
20500        );
20501    }
20502
20503    /// `XDEL` and `XACK` check every id before they touch any of them.
20504    #[test]
20505    fn a_bad_id_late_in_the_list_stops_the_whole_command() {
20506        let mut f = Fixture::new();
20507        f.run(&[b"XADD", b"s", b"1-1", b"a", b"1"]);
20508        f.run(&[b"XADD", b"s", b"2-1", b"a", b"2"]);
20509        assert!(
20510            f.run(&[b"XDEL", b"s", b"1-1", b"nonsense"])
20511                .contains("Invalid stream ID")
20512        );
20513        assert_eq!(f.run(&[b"XLEN", b"s"]), ":2\r\n");
20514        assert_eq!(f.run(&[b"XDEL", b"s", b"1-1", b"9-9"]), ":1\r\n");
20515        assert_eq!(f.run(&[b"XLEN", b"s"]), ":1\r\n");
20516        assert_eq!(f.run(&[b"XDEL", b"missing", b"1-1"]), ":0\r\n");
20517        assert_eq!(f.run(&[b"XACK", b"missing", b"g", b"1-1"]), ":0\r\n");
20518    }
20519
20520    /// `XGROUP`, and the two different complaints it makes about arguments.
20521    #[test]
20522    fn xgroup_has_an_arity_per_subcommand() {
20523        let mut f = Fixture::new();
20524        assert!(
20525            f.run(&[b"XGROUP", b"CREATE", b"s", b"g", b"$"])
20526                .contains("requires the key")
20527        );
20528        assert_eq!(
20529            f.run(&[b"XGROUP", b"CREATE", b"s", b"g", b"$", b"MKSTREAM"]),
20530            "+OK\r\n"
20531        );
20532        // A second CREATE is BUSYGROUP and not an ordinary error, because a
20533        // client racing another one to make a group branches on the prefix.
20534        assert!(
20535            f.run(&[b"XGROUP", b"CREATE", b"s", b"g", b"$"])
20536                .starts_with("-BUSYGROUP")
20537        );
20538        assert_eq!(
20539            f.run(&[b"XGROUP", b"CREATECONSUMER", b"s", b"g", b"c"]),
20540            ":1\r\n"
20541        );
20542        assert_eq!(
20543            f.run(&[b"XGROUP", b"CREATECONSUMER", b"s", b"g", b"c"]),
20544            ":0\r\n"
20545        );
20546        assert_eq!(
20547            f.run(&[b"XGROUP", b"DELCONSUMER", b"s", b"g", b"c"]),
20548            ":0\r\n"
20549        );
20550
20551        // Below the subcommand's own arity is an arity error naming the pair.
20552        let short = f.run(&[b"XGROUP", b"DESTROY", b"s"]);
20553        assert!(
20554            short.contains("wrong number of arguments for 'xgroup|destroy' command"),
20555            "{short}"
20556        );
20557        // At or above it in a shape the handler will not take is the other one.
20558        let odd = f.run(&[b"XGROUP", b"SETID", b"s", b"g", b"0", b"ENTRIESREAD"]);
20559        assert!(
20560            odd.contains("unknown subcommand or wrong number of arguments for 'SETID'"),
20561            "{odd}"
20562        );
20563        assert!(
20564            f.run(&[b"XGROUP", b"NOSUCH", b"s"])
20565                .contains("Try XGROUP HELP")
20566        );
20567
20568        assert_eq!(f.run(&[b"XGROUP", b"SETID", b"s", b"g", b"0"]), "+OK\r\n");
20569        assert!(
20570            f.run(&[b"XGROUP", b"SETID", b"s", b"nogroup", b"0"])
20571                .starts_with("-NOGROUP")
20572        );
20573        assert_eq!(f.run(&[b"XGROUP", b"DESTROY", b"s", b"g"]), ":1\r\n");
20574        assert_eq!(f.run(&[b"XGROUP", b"DESTROY", b"s", b"g"]), ":0\r\n");
20575        assert!(
20576            f.run(&[b"XGROUP", b"DESTROY", b"missing", b"g"])
20577                .contains("requires the key")
20578        );
20579    }
20580
20581    /// A group read, an acknowledgement, and what is left in between.
20582    #[test]
20583    fn xreadgroup_hands_out_and_xack_takes_back() {
20584        let mut f = Fixture::new();
20585        f.run(&[b"XADD", b"s", b"1-1", b"a", b"1"]);
20586        f.run(&[b"XADD", b"s", b"2-1", b"a", b"2"]);
20587        f.run(&[b"XGROUP", b"CREATE", b"s", b"g", b"0"]);
20588
20589        let first = f.run(&[
20590            b"XREADGROUP",
20591            b"GROUP",
20592            b"g",
20593            b"c1",
20594            b"COUNT",
20595            b"1",
20596            b"STREAMS",
20597            b"s",
20598            b">",
20599        ]);
20600        assert_eq!(
20601            first,
20602            "*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"
20603        );
20604        // A history read names its stream even with nothing to show, which is
20605        // the difference between it and a `>` read that found nothing.
20606        assert_eq!(
20607            f.run(&[b"XREADGROUP", b"GROUP", b"g", b"c2", b"STREAMS", b"s", b"0"]),
20608            "*1\r\n*2\r\n$1\r\ns\r\n*0\r\n"
20609        );
20610        assert_eq!(
20611            f.run(&[b"XREADGROUP", b"GROUP", b"g", b"c1", b"STREAMS", b"s", b"0"]),
20612            "*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"
20613        );
20614
20615        assert_eq!(
20616            f.run(&[b"XPENDING", b"s", b"g"]),
20617            "*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"
20618        );
20619        assert_eq!(f.run(&[b"XACK", b"s", b"g", b"1-1"]), ":1\r\n");
20620        assert_eq!(f.run(&[b"XACK", b"s", b"g", b"1-1"]), ":0\r\n");
20621        // Empty is four nulls and not a zero with three empty things.
20622        assert_eq!(
20623            f.run(&[b"XPENDING", b"s", b"g"]),
20624            "*4\r\n:0\r\n$-1\r\n$-1\r\n*-1\r\n"
20625        );
20626
20627        // A history read of an entry that has since been deleted is the id with
20628        // a null beside it, so the consumer can still acknowledge it.
20629        f.run(&[b"XREADGROUP", b"GROUP", b"g", b"c1", b"STREAMS", b"s", b">"]);
20630        f.run(&[b"XDEL", b"s", b"2-1"]);
20631        assert_eq!(
20632            f.run(&[b"XREADGROUP", b"GROUP", b"g", b"c1", b"STREAMS", b"s", b"0"]),
20633            "*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"
20634        );
20635
20636        // The group lookup runs before the id parse, so a `+` at a stream with
20637        // no such group is told about the group and not about the id.
20638        assert!(
20639            f.run(&[
20640                b"XREADGROUP",
20641                b"GROUP",
20642                b"nope",
20643                b"c",
20644                b"STREAMS",
20645                b"s",
20646                b"+"
20647            ])
20648            .starts_with("-NOGROUP")
20649        );
20650        assert!(
20651            f.run(&[b"XREADGROUP", b"GROUP", b"g", b"c", b"STREAMS", b"s", b"$"])
20652                .contains("meaningless in the context of XREADGROUP")
20653        );
20654        assert!(
20655            f.run(&[b"XREAD", b"GROUP", b"g", b"c", b"STREAMS", b"s", b"0"])
20656                .contains("only supported by XREADGROUP")
20657        );
20658        assert!(
20659            f.run(&[
20660                b"XREADGROUP",
20661                b"GROUP",
20662                b"g",
20663                b"c",
20664                b"STREAMS",
20665                b"s",
20666                b"a",
20667                b"b"
20668            ])
20669            .contains("Unbalanced 'xreadgroup' list of streams")
20670        );
20671    }
20672
20673    /// `XREAD` without `BLOCK`, which answers now and takes nothing for an
20674    /// answer.
20675    #[test]
20676    fn xread_with_no_block_writes_the_null_itself() {
20677        let mut f = Fixture::new();
20678        f.run(&[b"XADD", b"s", b"1-1", b"a", b"1"]);
20679        assert_eq!(
20680            f.run(&[b"XREAD", b"STREAMS", b"s", b"0"]),
20681            "*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"
20682        );
20683        // Nothing new is a null array and not an empty one, and a stream with
20684        // nothing new is left out rather than sent with an empty list.
20685        assert_eq!(f.run(&[b"XREAD", b"STREAMS", b"s", b"1-1"]), "*-1\r\n");
20686        assert_eq!(f.run(&[b"XREAD", b"STREAMS", b"missing", b"0"]), "*-1\r\n");
20687        f.run(&[b"XADD", b"other", b"1-1", b"b", b"2"]);
20688        assert_eq!(
20689            f.run(&[b"XREAD", b"STREAMS", b"s", b"other", b"1-1", b"0"]),
20690            "*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"
20691        );
20692        // `$` is the last id, so nothing that is already there comes back.
20693        assert_eq!(f.run(&[b"XREAD", b"STREAMS", b"s", b"$"]), "*-1\r\n");
20694        // And `+` is the last entry, whatever COUNT says.
20695        assert_eq!(
20696            f.run(&[b"XREAD", b"COUNT", b"5", b"STREAMS", b"s", b"+"]),
20697            "*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"
20698        );
20699        // A count of zero means unlimited here, which is the opposite of what it
20700        // means to XRANGE.
20701        assert_eq!(
20702            f.run(&[b"XREAD", b"COUNT", b"0", b"STREAMS", b"s", b"0"]),
20703            "*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"
20704        );
20705        // Milliseconds as a whole number, where BLPOP takes seconds as a float.
20706        assert!(
20707            f.run(&[b"XREAD", b"BLOCK", b"0.5", b"STREAMS", b"s", b"$"])
20708                .contains("not an integer")
20709        );
20710        assert!(
20711            f.run(&[b"XREAD", b"BLOCK", b"-1", b"STREAMS", b"s", b"$"])
20712                .contains("timeout is negative")
20713        );
20714        assert!(
20715            f.run(&[b"XREAD", b"STREAMS", b"s", b"other", b"0"])
20716                .contains("Unbalanced 'xread' list of streams")
20717        );
20718    }
20719
20720    /// A blocked reader, and the two ways it stops being blocked.
20721    #[test]
20722    fn a_blocked_xread_wakes_on_the_next_entry() {
20723        let mut f = Fixture::new();
20724        f.run(&[b"XADD", b"s", b"1-1", b"a", b"1"]);
20725        let (flow, reply) = f.flow(&[b"XREAD", b"BLOCK", b"0", b"STREAMS", b"s", b"$"]);
20726        assert_eq!(flow, Flow::Block);
20727        assert!(reply.is_empty());
20728
20729        // Everybody parked on the stream gets the entry, because a read takes
20730        // nothing away. That is the difference between this and BLPOP. Two
20731        // clients rather than one twice, since a client that is waiting is not
20732        // reading and cannot block again.
20733        f.session = Session::new(8);
20734        let (flow, _) = f.flow(&[b"XREAD", b"BLOCK", b"0", b"STREAMS", b"s", b"$"]);
20735        assert_eq!(flow, Flow::Block);
20736        assert_eq!(f.server.parked(), 2);
20737
20738        f.run(&[b"XADD", b"s", b"2-1", b"a", b"2"]);
20739        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";
20740        for client in [7, 8] {
20741            let mut out = Out::new(Proto::Resp2);
20742            assert!(f.server.serve_waiter(client, 0, &mut out));
20743            assert_eq!(core::str::from_utf8(out.as_slice()).expect("ascii"), want);
20744        }
20745
20746        // And a deadline that runs out is a null array, the same as a plain
20747        // XREAD that found nothing.
20748        f.server.forget_waiters(7);
20749        f.server.forget_waiters(8);
20750        let (flow, _) = f.flow(&[b"XREAD", b"BLOCK", b"50", b"STREAMS", b"s", b"$"]);
20751        assert_eq!(flow, Flow::Block);
20752        let mut out = Out::new(Proto::Resp2);
20753        assert!(!f.server.serve_waiter(8, 0, &mut out));
20754        assert!(out.as_slice().is_empty());
20755        assert!(f.server.serve_waiter(8, u64::MAX, &mut out));
20756        assert_eq!(
20757            core::str::from_utf8(out.as_slice()).expect("ascii"),
20758            "*-1\r\n"
20759        );
20760    }
20761
20762    /// A blocked group reader whose group is destroyed under it.
20763    #[test]
20764    fn losing_a_group_while_blocked_is_the_ordinary_sentence() {
20765        let mut f = Fixture::new();
20766        f.run(&[b"XADD", b"s", b"1-1", b"a", b"1"]);
20767        f.run(&[b"XGROUP", b"CREATE", b"s", b"g", b"$"]);
20768        let (flow, _) = f.flow(&[
20769            b"XREADGROUP",
20770            b"GROUP",
20771            b"g",
20772            b"c",
20773            b"BLOCK",
20774            b"0",
20775            b"STREAMS",
20776            b"s",
20777            b">",
20778        ]);
20779        assert_eq!(flow, Flow::Block);
20780
20781        f.run(&[b"XGROUP", b"DESTROY", b"s", b"g"]);
20782        let mut out = Out::new(Proto::Resp2);
20783        assert!(f.server.serve_waiter(7, 0, &mut out));
20784        // The ordinary sentence and not a special one about having been parked,
20785        // which is what a running 8.10 sends.
20786        assert_eq!(
20787            core::str::from_utf8(out.as_slice()).expect("ascii"),
20788            "-NOGROUP No such key 's' or consumer group 'g' in XREADGROUP with GROUP option\r\n"
20789        );
20790    }
20791
20792    /// `XCLAIM`, whose argument shape is the odd one in the group.
20793    #[test]
20794    fn xclaim_reads_ids_until_one_will_not_parse() {
20795        let mut f = Fixture::new();
20796        f.run(&[b"XADD", b"s", b"1-1", b"a", b"1"]);
20797        f.run(&[b"XADD", b"s", b"2-1", b"a", b"2"]);
20798        f.run(&[b"XGROUP", b"CREATE", b"s", b"g", b"0"]);
20799        f.run(&[b"XREADGROUP", b"GROUP", b"g", b"c1", b"STREAMS", b"s", b">"]);
20800
20801        // Everything after the first argument that is not an id is an option, so
20802        // a `-` is an unrecognised option and not a bad id.
20803        assert!(
20804            f.run(&[b"XCLAIM", b"s", b"g", b"c2", b"0", b"-"])
20805                .contains("Unrecognized XCLAIM option '-'")
20806        );
20807        assert_eq!(
20808            f.run(&[b"XCLAIM", b"s", b"g", b"c2", b"0", b"1-1", b"JUSTID"]),
20809            "*1\r\n$3\r\n1-1\r\n"
20810        );
20811        // An id that is pending but whose entry has gone is an empty answer, and
20812        // it leaves the pending list on the way past.
20813        f.run(&[b"XDEL", b"s", b"2-1"]);
20814        assert_eq!(
20815            f.run(&[b"XCLAIM", b"s", b"g", b"c2", b"0", b"2-1"]),
20816            "*0\r\n"
20817        );
20818        assert!(
20819            f.run(&[b"XPENDING", b"s", b"g"])
20820                .starts_with("*4\r\n:1\r\n")
20821        );
20822        assert!(
20823            f.run(&[b"XCLAIM", b"s", b"nope", b"c", b"0", b"1-1"])
20824                .starts_with("-NOGROUP")
20825        );
20826        assert!(
20827            f.run(&[b"XCLAIM", b"s", b"g", b"c", b"nan", b"1-1"])
20828                .contains("Invalid min-idle-time argument for XCLAIM")
20829        );
20830    }
20831
20832    /// `XAUTOCLAIM`, and the third value nobody expects.
20833    #[test]
20834    fn xautoclaim_reports_what_it_dropped() {
20835        let mut f = Fixture::new();
20836        f.run(&[b"XADD", b"s", b"1-1", b"a", b"1"]);
20837        f.run(&[b"XADD", b"s", b"2-1", b"a", b"2"]);
20838        f.run(&[b"XGROUP", b"CREATE", b"s", b"g", b"0"]);
20839        f.run(&[b"XREADGROUP", b"GROUP", b"g", b"c1", b"STREAMS", b"s", b">"]);
20840        f.run(&[b"XDEL", b"s", b"1-1"]);
20841
20842        // The cursor, what was claimed, and what was dropped for no longer being
20843        // in the stream. The third one is what makes a sweep converge.
20844        assert_eq!(
20845            f.run(&[b"XAUTOCLAIM", b"s", b"g", b"c2", b"0", b"-", b"JUSTID"]),
20846            "*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"
20847        );
20848        assert!(
20849            f.run(&[b"XAUTOCLAIM", b"s", b"g", b"c2", b"0", b"-", b"COUNT", b"0"])
20850                .contains("COUNT must be > 0")
20851        );
20852        assert!(
20853            f.run(&[b"XAUTOCLAIM", b"s", b"nope", b"c", b"0", b"-"])
20854                .starts_with("-NOGROUP")
20855        );
20856    }
20857
20858    /// `XDELEX`, which is `XDEL` with a say in what the groups keep.
20859    #[test]
20860    fn xdelex_answers_one_integer_an_id() {
20861        let mut f = Fixture::new();
20862        for i in 1..=4 {
20863            f.run(&[b"XADD", b"s", format!("{i}-1").as_bytes(), b"a", b"1"]);
20864        }
20865        f.run(&[b"XGROUP", b"CREATE", b"s", b"g", b"0"]);
20866        f.run(&[
20867            b"XREADGROUP",
20868            b"GROUP",
20869            b"g",
20870            b"c",
20871            b"COUNT",
20872            b"2",
20873            b"STREAMS",
20874            b"s",
20875            b">",
20876        ]);
20877
20878        // One means gone and minus one means it was not there to start with.
20879        assert_eq!(
20880            f.run(&[b"XDELEX", b"s", b"IDS", b"2", b"1-1", b"9-9"]),
20881            "*2\r\n:1\r\n:-1\r\n"
20882        );
20883        // `KEEPREF` leaves the pending entry behind, so the group still counts
20884        // the one it was handed even though the entry has gone.
20885        assert!(
20886            f.run(&[b"XPENDING", b"s", b"g"])
20887                .starts_with("*4\r\n:2\r\n")
20888        );
20889        // `DELREF` takes it out of every pending list on the way past.
20890        assert_eq!(
20891            f.run(&[b"XDELEX", b"s", b"DELREF", b"IDS", b"1", b"2-1"]),
20892            "*1\r\n:1\r\n"
20893        );
20894        // `1-1` is still in the list, because the delete before it said KEEPREF.
20895        assert_eq!(
20896            f.run(&[b"XPENDING", b"s", b"g"]),
20897            "*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"
20898        );
20899
20900        // Two means somebody still wants it, and the question is wider than the
20901        // name: the group's bookmark is at `2-1`, so `4-1` is above it and is
20902        // refused even though no consumer has ever been handed it.
20903        assert_eq!(
20904            f.run(&[b"XDELEX", b"s", b"ACKED", b"IDS", b"2", b"3-1", b"4-1"]),
20905            "*2\r\n:2\r\n:2\r\n"
20906        );
20907
20908        // A key that is not there answers minus ones without reading the IDs.
20909        assert_eq!(
20910            f.run(&[b"XDELEX", b"nope", b"IDS", b"2", b"bad", b"worse"]),
20911            "*2\r\n:-1\r\n:-1\r\n"
20912        );
20913        // A key that is there validates every ID before deleting any of them.
20914        assert!(
20915            f.run(&[b"XDELEX", b"s", b"IDS", b"2", b"3-1", b"bad"])
20916                .starts_with("-ERR Invalid stream ID")
20917        );
20918        assert_eq!(f.run(&[b"XLEN", b"s"]), ":2\r\n");
20919
20920        assert!(
20921            f.run(&[b"XDELEX", b"s", b"IDS", b"0", b"1-1"])
20922                .contains("Number of IDs must be a positive integer")
20923        );
20924        assert!(
20925            f.run(&[b"XDELEX", b"s", b"IDS", b"2", b"1-1"])
20926                .contains("The `numids` parameter must match the number of arguments")
20927        );
20928        // The condition is one word, so a second one is a syntax error, and so
20929        // is one ID more than the count promised.
20930        assert!(
20931            f.run(&[b"XDELEX", b"s", b"KEEPREF", b"DELREF", b"IDS", b"1", b"1-1"])
20932                .starts_with("-ERR syntax error")
20933        );
20934        assert!(
20935            f.run(&[b"XDELEX", b"s", b"IDS", b"1", b"1-1", b"2-1"])
20936                .starts_with("-ERR syntax error")
20937        );
20938        // The key is looked up first, so the wrong type beats the syntax.
20939        f.run(&[b"SET", b"str", b"v"]);
20940        assert!(
20941            f.run(&[b"XDELEX", b"str", b"BOGUS", b"IDS", b"0", b"1-1"])
20942                .starts_with("-WRONGTYPE")
20943        );
20944    }
20945
20946    /// `XACKDEL`, whose reply is about the pending list and not about the log.
20947    #[test]
20948    fn xackdel_reports_what_the_group_was_holding() {
20949        let mut f = Fixture::new();
20950        for i in 1..=3 {
20951            f.run(&[b"XADD", b"s", format!("{i}-1").as_bytes(), b"a", b"1"]);
20952        }
20953        f.run(&[b"XGROUP", b"CREATE", b"s", b"g", b"0"]);
20954        f.run(&[
20955            b"XREADGROUP",
20956            b"GROUP",
20957            b"g",
20958            b"c",
20959            b"COUNT",
20960            b"1",
20961            b"STREAMS",
20962            b"s",
20963            b">",
20964        ]);
20965
20966        // Minus one is not about the stream: `2-1` is sitting there unread and
20967        // still answers minus one, because the group was not holding it. It also
20968        // stays, since only an ID that was acknowledged is deleted.
20969        assert_eq!(
20970            f.run(&[b"XACKDEL", b"s", b"g", b"IDS", b"2", b"1-1", b"2-1"]),
20971            "*2\r\n:1\r\n:-1\r\n"
20972        );
20973        assert_eq!(f.run(&[b"XLEN", b"s"]), ":2\r\n");
20974
20975        // A missing group is minus one an ID and not a NOGROUP.
20976        assert_eq!(
20977            f.run(&[b"XACKDEL", b"s", b"nope", b"IDS", b"1", b"2-1"]),
20978            "*1\r\n:-1\r\n"
20979        );
20980        assert_eq!(
20981            f.run(&[b"XACKDEL", b"nope", b"g", b"IDS", b"1", b"2-1"]),
20982            "*1\r\n:-1\r\n"
20983        );
20984
20985        // The acknowledgement happens whatever the condition says, so an ACKED
20986        // that answers two has still emptied the pending list.
20987        f.run(&[b"XREADGROUP", b"GROUP", b"g", b"c", b"STREAMS", b"s", b">"]);
20988        f.run(&[b"XGROUP", b"CREATE", b"s", b"g2", b"0"]);
20989        assert_eq!(
20990            f.run(&[b"XACKDEL", b"s", b"g", b"ACKED", b"IDS", b"1", b"2-1"]),
20991            "*1\r\n:2\r\n"
20992        );
20993        assert_eq!(
20994            f.run(&[b"XPENDING", b"s", b"g"]),
20995            "*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"
20996        );
20997        assert_eq!(f.run(&[b"XLEN", b"s"]), ":2\r\n");
20998    }
20999
21000    /// `XNACK`, which hands an entry back to nobody.
21001    #[test]
21002    fn xnack_releases_an_entry_for_the_next_claim() {
21003        let mut f = Fixture::new();
21004        f.run(&[b"XADD", b"s", b"1-1", b"a", b"1"]);
21005        f.run(&[b"XADD", b"s", b"2-1", b"a", b"2"]);
21006        f.run(&[b"XGROUP", b"CREATE", b"s", b"g", b"0"]);
21007        f.run(&[b"XREADGROUP", b"GROUP", b"g", b"c1", b"STREAMS", b"s", b">"]);
21008        // Twice, so the delivery count is two and the words have something to
21009        // do with it.
21010        f.run(&[b"XCLAIM", b"s", b"g", b"c1", b"0", b"1-1", b"2-1"]);
21011
21012        assert_eq!(
21013            f.run(&[b"XNACK", b"s", b"g", b"FAIL", b"IDS", b"1", b"1-1"]),
21014            ":1\r\n"
21015        );
21016        // No owner, no idle time, and the count left where it was. A released
21017        // entry reads as idle for longer than any min-idle-time, which is what
21018        // puts it at the front of the next claim.
21019        assert_eq!(
21020            f.run(&[b"XPENDING", b"s", b"g", b"-", b"+", b"10"]),
21021            "*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"
21022        );
21023        // The consumer no longer holds it, so a filtered XPENDING skips it.
21024        assert_eq!(
21025            f.run(&[b"XPENDING", b"s", b"g", b"-", b"+", b"10", b"c1"]),
21026            "*1\r\n*4\r\n$3\r\n2-1\r\n$2\r\nc1\r\n:0\r\n:2\r\n"
21027        );
21028        // The bookmark did not move, so a `>` read will not hand it out again.
21029        assert_eq!(
21030            f.run(&[b"XREADGROUP", b"GROUP", b"g", b"c2", b"STREAMS", b"s", b">"]),
21031            "*-1\r\n"
21032        );
21033        // A claim at any min-idle-time takes it.
21034        assert_eq!(
21035            f.run(&[
21036                b"XAUTOCLAIM",
21037                b"s",
21038                b"g",
21039                b"c2",
21040                b"99999999",
21041                b"-",
21042                b"JUSTID"
21043            ]),
21044            "*3\r\n$3\r\n0-0\r\n*1\r\n$3\r\n1-1\r\n*0\r\n"
21045        );
21046
21047        // `SILENT` takes one off the count rather than putting it back to zero,
21048        // which only shows on an entry that has been handed out more than once.
21049        // It was delivered and then claimed, so it is on two and goes to one.
21050        f.run(&[b"XNACK", b"s", b"g", b"SILENT", b"IDS", b"1", b"1-1"]);
21051        assert!(
21052            f.run(&[b"XPENDING", b"s", b"g", b"-", b"+", b"10"])
21053                .contains(":-1\r\n:1\r\n")
21054        );
21055        // And it stops at zero rather than wrapping.
21056        f.run(&[b"XNACK", b"s", b"g", b"SILENT", b"IDS", b"1", b"1-1"]);
21057        f.run(&[b"XNACK", b"s", b"g", b"SILENT", b"IDS", b"1", b"1-1"]);
21058        assert!(
21059            f.run(&[b"XPENDING", b"s", b"g", b"-", b"+", b"10"])
21060                .contains(":-1\r\n:0\r\n")
21061        );
21062        // `FATAL` puts it at the ceiling, and `RETRYCOUNT` wins over the word.
21063        f.run(&[b"XNACK", b"s", b"g", b"FATAL", b"IDS", b"1", b"1-1"]);
21064        assert!(
21065            f.run(&[b"XPENDING", b"s", b"g", b"-", b"+", b"10"])
21066                .contains(":9223372036854775807\r\n")
21067        );
21068        f.run(&[
21069            b"XNACK",
21070            b"s",
21071            b"g",
21072            b"FATAL",
21073            b"IDS",
21074            b"1",
21075            b"1-1",
21076            b"RETRYCOUNT",
21077            b"3",
21078        ]);
21079        assert!(
21080            f.run(&[b"XPENDING", b"s", b"g", b"-", b"+", b"10"])
21081                .contains(":-1\r\n:3\r\n")
21082        );
21083
21084        // Releasing something the group is not holding is zero, and `FORCE`
21085        // makes the pending entry rather than answering zero. A forced entry
21086        // starts at zero, since there was no earlier count to keep.
21087        f.run(&[b"XACK", b"s", b"g", b"2-1"]);
21088        assert_eq!(
21089            f.run(&[b"XNACK", b"s", b"g", b"FAIL", b"IDS", b"1", b"2-1"]),
21090            ":0\r\n"
21091        );
21092        assert_eq!(
21093            f.run(&[
21094                b"XNACK", b"s", b"g", b"FAIL", b"IDS", b"1", b"2-1", b"FORCE"
21095            ]),
21096            ":1\r\n"
21097        );
21098        assert!(
21099            f.run(&[b"XPENDING", b"s", b"g", b"-", b"+", b"10"])
21100                .contains(":-1\r\n:0\r\n")
21101        );
21102        // `FORCE` on an ID the stream does not have is still zero.
21103        assert_eq!(
21104            f.run(&[
21105                b"XNACK", b"s", b"g", b"FAIL", b"IDS", b"1", b"9-9", b"FORCE"
21106            ]),
21107            ":0\r\n"
21108        );
21109
21110        // The group is looked up before the mode word, and it raises rather
21111        // than answering per ID the way the two delete commands do.
21112        assert_eq!(
21113            f.run(&[b"XNACK", b"s", b"nope", b"BOGUS", b"IDS", b"1", b"1-1"]),
21114            "-NOGROUP No such key 's' or consumer group 'nope'\r\n"
21115        );
21116        assert!(
21117            f.run(&[b"XNACK", b"s", b"g", b"BOGUS", b"IDS", b"1", b"1-1"])
21118                .starts_with("-ERR")
21119        );
21120        // Its own sentences, which are not the ones XDELEX uses.
21121        assert!(
21122            f.run(&[b"XNACK", b"s", b"g", b"FAIL", b"IDS", b"0", b"1-1"])
21123                .contains("numids must be a positive integer")
21124        );
21125        assert!(
21126            f.run(&[b"XNACK", b"s", b"g", b"FAIL", b"IDS", b"2", b"1-1"])
21127                .contains("number of IDs doesn't match numids")
21128        );
21129        // Everything past the counted IDs is an option, so one too many is an
21130        // option nobody recognises and not a count that does not add up.
21131        assert!(
21132            f.run(&[b"XNACK", b"s", b"g", b"FAIL", b"IDS", b"1", b"1-1", b"2-1"])
21133                .contains("Unrecognized XNACK option '2-1'")
21134        );
21135    }
21136
21137    /// `XINFO`, which is where the shape of the storage shows through.
21138    #[test]
21139    fn xinfo_reports_the_stream_the_groups_and_the_consumers() {
21140        let mut f = Fixture::new();
21141        f.run(&[b"XADD", b"s", b"1-1", b"a", b"1"]);
21142        f.run(&[b"XADD", b"s", b"2-1", b"a", b"2"]);
21143        f.run(&[b"XGROUP", b"CREATE", b"s", b"g", b"0"]);
21144        f.run(&[
21145            b"XREADGROUP",
21146            b"GROUP",
21147            b"g",
21148            b"c1",
21149            b"COUNT",
21150            b"1",
21151            b"STREAMS",
21152            b"s",
21153            b">",
21154        ]);
21155
21156        let info = f.run(&[b"XINFO", b"STREAM", b"s"]);
21157        // Ten pairs, since the six idempotency fields have nothing behind them
21158        // here and a zero would claim they had. That is D-27.
21159        assert!(info.starts_with("*20\r\n"), "{info}");
21160        assert!(info.contains("$6\r\nlength\r\n:2\r\n"), "{info}");
21161        assert!(
21162            info.contains("$17\r\nlast-generated-id\r\n$3\r\n2-1\r\n"),
21163            "{info}"
21164        );
21165        assert!(info.contains("$13\r\nentries-added\r\n:2\r\n"), "{info}");
21166        assert!(info.contains("$6\r\ngroups\r\n:1\r\n"), "{info}");
21167
21168        let groups = f.run(&[b"XINFO", b"GROUPS", b"s"]);
21169        assert!(groups.starts_with("*1\r\n*12\r\n"), "{groups}");
21170        assert!(groups.contains("$9\r\nconsumers\r\n:1\r\n"), "{groups}");
21171        assert!(groups.contains("$7\r\npending\r\n:1\r\n"), "{groups}");
21172        assert!(groups.contains("$3\r\nlag\r\n:1\r\n"), "{groups}");
21173
21174        // A consumer that has never been given anything reports minus one for
21175        // inactive rather than the moment it turned up, which is what tells a
21176        // worker that is stuck from one that has nothing to do.
21177        f.run(&[b"XGROUP", b"CREATECONSUMER", b"s", b"g", b"c2"]);
21178        let consumers = f.run(&[b"XINFO", b"CONSUMERS", b"s", b"g"]);
21179        assert!(consumers.starts_with("*2\r\n"), "{consumers}");
21180        assert!(
21181            consumers.contains("$8\r\ninactive\r\n:-1\r\n"),
21182            "{consumers}"
21183        );
21184        // And in name order, which the storage does not hold them in.
21185        let c1 = consumers.find("c1").unwrap();
21186        let c2 = consumers.find("c2").unwrap();
21187        assert!(c1 < c2, "{consumers}");
21188
21189        let full = f.run(&[b"XINFO", b"STREAM", b"s", b"FULL"]);
21190        assert!(full.starts_with("*18\r\n"), "{full}");
21191        assert!(full.contains("$12\r\nnacked-count\r\n:0\r\n"), "{full}");
21192        assert!(full.contains("$11\r\nactive-time\r\n"), "{full}");
21193
21194        assert!(
21195            f.run(&[b"XINFO", b"STREAM", b"missing"])
21196                .contains("no such key")
21197        );
21198        assert!(
21199            f.run(&[b"XINFO", b"GROUPS", b"missing"])
21200                .contains("no such key")
21201        );
21202        assert!(
21203            f.run(&[b"XINFO", b"CONSUMERS", b"s", b"nope"])
21204                .starts_with("-NOGROUP")
21205        );
21206        assert!(
21207            f.run(&[b"XINFO", b"NOSUCH", b"s"])
21208                .contains("Try XINFO HELP")
21209        );
21210        assert!(f.run(&[b"XINFO", b"HELP"]).contains("XINFO <subcommand>"));
21211        assert!(f.run(&[b"XGROUP", b"HELP"]).contains("XGROUP <subcommand>"));
21212    }
21213
21214    /// `XPENDING`'s long form, which reads its arguments by counting them.
21215    #[test]
21216    fn xpending_takes_the_consumer_only_when_the_count_comes_out_right() {
21217        let mut f = Fixture::new();
21218        f.run(&[b"XADD", b"s", b"1-1", b"a", b"1"]);
21219        f.run(&[b"XGROUP", b"CREATE", b"s", b"g", b"0"]);
21220        f.run(&[b"XREADGROUP", b"GROUP", b"g", b"c1", b"STREAMS", b"s", b">"]);
21221
21222        let list = f.run(&[b"XPENDING", b"s", b"g", b"-", b"+", b"10"]);
21223        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");
21224        assert_eq!(
21225            f.run(&[b"XPENDING", b"s", b"g", b"-", b"+", b"10", b"c1"]),
21226            "*1\r\n*4\r\n$3\r\n1-1\r\n$2\r\nc1\r\n:0\r\n:1\r\n"
21227        );
21228        // A consumer nobody has heard of holds nothing rather than erroring.
21229        assert_eq!(
21230            f.run(&[b"XPENDING", b"s", b"g", b"-", b"+", b"10", b"nope"]),
21231            "*0\r\n"
21232        );
21233        assert_eq!(
21234            f.run(&[b"XPENDING", b"s", b"g", b"IDLE", b"0", b"-", b"+", b"10"]),
21235            list
21236        );
21237        // IDLE is only read at position three.
21238        assert!(
21239            f.run(&[b"XPENDING", b"s", b"g", b"IDLE", b"0"])
21240                .contains("syntax error")
21241        );
21242        assert!(
21243            f.run(&[b"XPENDING", b"s", b"g", b"-", b"+"])
21244                .contains("syntax error")
21245        );
21246        assert_eq!(
21247            f.run(&[b"XPENDING", b"s", b"g", b"-", b"+", b"-1"]),
21248            "*0\r\n"
21249        );
21250        assert!(
21251            f.run(&[b"XPENDING", b"missing", b"g"])
21252                .starts_with("-NOGROUP")
21253        );
21254    }
21255
21256    /// `XSETID`, which is three counters and two refusals.
21257    #[test]
21258    fn xsetid_will_not_go_below_what_is_there() {
21259        let mut f = Fixture::new();
21260        f.run(&[b"XADD", b"s", b"5-5", b"a", b"1"]);
21261        assert_eq!(f.run(&[b"XSETID", b"s", b"9-9"]), "+OK\r\n");
21262        assert_eq!(
21263            f.run(&[
21264                b"XSETID",
21265                b"s",
21266                b"10-1",
21267                b"ENTRIESADDED",
21268                b"7",
21269                b"MAXDELETEDID",
21270                b"9-1"
21271            ]),
21272            "+OK\r\n"
21273        );
21274        let info = f.run(&[b"XINFO", b"STREAM", b"s"]);
21275        assert!(info.contains("$13\r\nentries-added\r\n:7\r\n"), "{info}");
21276        assert!(
21277            info.contains("$20\r\nmax-deleted-entry-id\r\n$3\r\n9-1\r\n"),
21278            "{info}"
21279        );
21280
21281        assert!(
21282            f.run(&[b"XSETID", b"s", b"1-1"])
21283                .contains("smaller than the target stream top item")
21284        );
21285        assert!(
21286            f.run(&[b"XSETID", b"s", b"10-1", b"ENTRIESADDED", b"-1"])
21287                .contains("entries_added must be positive")
21288        );
21289        assert!(
21290            f.run(&[b"XSETID", b"missing", b"1-1"])
21291                .contains("no such key")
21292        );
21293    }
21294
21295    /// RESP3, where the two reads answer a map and the entries stay an array.
21296    #[test]
21297    fn xread_answers_a_map_on_resp3_and_the_fields_stay_flat() {
21298        let mut f = Fixture::new();
21299        f.run(&[b"HELLO", b"3"]);
21300        f.run(&[b"XADD", b"s", b"1-1", b"a", b"1"]);
21301        // A map header and then the key and the entries side by side, with no
21302        // two element array wrapping the pair.
21303        assert_eq!(
21304            f.run(&[b"XREAD", b"STREAMS", b"s", b"0"]),
21305            "%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"
21306        );
21307        // The fields are still one flat array and not a map, which is Redis's
21308        // shape and is what every consumer written before RESP3 expects.
21309        assert_eq!(
21310            f.run(&[b"XRANGE", b"s", b"-", b"+"]),
21311            "*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"
21312        );
21313        assert_eq!(f.run(&[b"XREAD", b"STREAMS", b"s", b"1-1"]), "_\r\n");
21314    }
21315
21316    /// A store to migrate values into, so a test can watch the inversion.
21317    ///
21318    /// A vector rather than a file for the same reason the tier's own tests use
21319    /// one: the file work has not attached a real store yet, and what this is
21320    /// checking is the policy above the store rather than the store.
21321    struct Mem {
21322        blobs: Vec<Vec<u8>>,
21323    }
21324
21325    impl yo_kv::cold::Blocks for Mem {
21326        fn put(&mut self, bytes: &[u8]) -> yo_common::Result<yo_common::Addr> {
21327            self.blobs.push(bytes.to_vec());
21328            Ok(yo_common::Addr::new(
21329                yo_common::Space::Log,
21330                (self.blobs.len() - 1) as u64,
21331            ))
21332        }
21333
21334        fn get(&self, at: yo_common::Addr) -> yo_common::Result<&[u8]> {
21335            self.blobs
21336                .get(at.offset() as usize)
21337                .map(Vec::as_slice)
21338                .ok_or_else(|| {
21339                    yo_common::Error::new(yo_common::Code::Corrupt, "no chunk at that address")
21340                })
21341        }
21342
21343        fn bytes(&self) -> u64 {
21344            self.blobs.iter().map(|b| b.len() as u64).sum()
21345        }
21346    }
21347
21348    /// A server holding several segments of strings, with somewhere to put them.
21349    ///
21350    /// Answers the fixture and what it was holding when it stopped filling.
21351    /// The three tests that call this are the ones Miri is not run over.
21352    ///
21353    /// What they are about is the regime a database is in once the arena has
21354    /// several segments, and a segment is two megabytes, so there is no smaller
21355    /// version of the question: twenty four thousand keys is already the least
21356    /// that gets there. Interpreted, each of them sat for over forty minutes
21357    /// and was still going. The arena's own segment handling is interpreted in
21358    /// full in its own crate, and the policy these three check is ordinary
21359    /// bookkeeping with no unsafe block anywhere in it.
21360    fn filled(attach: bool) -> (Fixture, usize) {
21361        let mut f = Fixture::new();
21362        if attach {
21363            f.server
21364                .striped(0)
21365                .hold_stripe(0)
21366                .attach(Box::new(Mem { blobs: Vec::new() }));
21367        }
21368        let val = vec![b'v'; 256];
21369        for i in 0..24000u32 {
21370            let k = format!("key:{i:08}");
21371            f.run(&[b"SET", k.as_bytes(), &val]);
21372        }
21373        let full = f.server.memory_bytes();
21374        assert!(full > 3 * 1024 * 1024, "the arena is several segments");
21375        (f, full)
21376    }
21377
21378    /// Write until the server is under `limit` or the writes run out.
21379    ///
21380    /// The same shape the eviction test uses. A memory limit is enforced in
21381    /// front of a command, so nothing happens until something is written, and
21382    /// the budget means one command does not do the whole job.
21383    fn press(f: &mut Fixture, limit: usize) {
21384        let val = vec![b'v'; 256];
21385        for i in 0..3000u32 {
21386            let k = format!("new:{i:08}");
21387            assert_eq!(
21388                f.run(&[b"SET", k.as_bytes(), &val]),
21389                "+OK\r\n",
21390                "write {i} was refused"
21391            );
21392            f.server.refresh_memory();
21393            if f.server.memory_bytes() <= limit {
21394                return;
21395            }
21396        }
21397        panic!(
21398            "it never got under: {} against {limit}",
21399            f.server.memory_bytes()
21400        );
21401    }
21402
21403    #[test]
21404    fn the_storage_limit_reads_back_and_minus_one_is_no_limit() {
21405        let mut f = Fixture::new();
21406        assert_eq!(
21407            f.run(&[b"CONFIG", b"GET", b"maxstore"]),
21408            "*2\r\n$8\r\nmaxstore\r\n$2\r\n-1\r\n",
21409            "no limit is the default"
21410        );
21411        // The same memory value parser `maxmemory` uses, and the same trap in
21412        // it, plus the one spelling that means no limit at all.
21413        for (typed, bytes) in [
21414            (&b"0"[..], "0"),
21415            (b"1024", "1024"),
21416            (b"1k", "1000"),
21417            (b"1gb", "1073741824"),
21418            (b"-1", "-1"),
21419        ] {
21420            assert_eq!(f.run(&[b"CONFIG", b"SET", b"maxstore", typed]), "+OK\r\n");
21421            assert_eq!(
21422                f.run(&[b"CONFIG", b"GET", b"maxstore"]),
21423                format!("*2\r\n$8\r\nmaxstore\r\n${}\r\n{bytes}\r\n", bytes.len()),
21424                "set {}",
21425                String::from_utf8_lossy(typed)
21426            );
21427        }
21428        for bad in [&b"1tb"[..], b"-2", b"", b"lots"] {
21429            assert_eq!(
21430                f.run(&[b"CONFIG", b"SET", b"maxstore", bad]),
21431                "-ERR CONFIG SET failed (possibly related to argument 'maxstore') - argument must be a memory value or -1\r\n",
21432                "refused {}",
21433                String::from_utf8_lossy(bad)
21434            );
21435        }
21436        // Nothing is attached, so the answer to a memory limit is still Redis's.
21437        let info = f.run(&[b"INFO", b"memory"]);
21438        assert!(info.contains("maxstore:-1"), "{info}");
21439        assert!(info.contains("yo_memory_regime:evict"), "{info}");
21440        assert!(info.contains("yo_store_bytes:0"), "{info}");
21441    }
21442
21443    #[cfg_attr(miri, ignore = "several megabytes of arena, see `filled`")]
21444    #[test]
21445    fn a_memory_limit_moves_values_to_the_file_instead_of_dropping_keys() {
21446        // The inversion. The same pressure that makes a Redis server throw keys
21447        // away makes this one move values to the file, and afterwards every key
21448        // is still there and still answers with what was stored in it.
21449        let (mut f, full) = filled(true);
21450        let keys = f.run(&[b"DBSIZE"]);
21451        assert!(
21452            f.run(&[b"INFO", b"memory"])
21453                .contains("yo_memory_regime:migrate"),
21454            "a database with somewhere to put values migrates"
21455        );
21456
21457        let limit = full - 2 * 1024 * 1024;
21458        f.run(&[b"CONFIG", b"SET", b"maxmemory-policy", b"allkeys-lru"]);
21459        f.run(&[
21460            b"CONFIG",
21461            b"SET",
21462            b"maxmemory",
21463            limit.to_string().as_bytes(),
21464        ]);
21465        press(&mut f, limit);
21466
21467        assert!(
21468            f.run(&[b"INFO", b"stats"]).contains("evicted_keys:0"),
21469            "nothing was thrown away"
21470        );
21471        let after: usize = f.run(&[b"DBSIZE"])[1..]
21472            .trim_end()
21473            .parse()
21474            .expect("a count");
21475        let before: usize = keys[1..].trim_end().parse().expect("a count");
21476        assert!(after > before, "the keys that came in are all still here");
21477        assert!(
21478            f.server.store_bytes() > 0,
21479            "and what came out of memory went to the file"
21480        );
21481        // And the values read back, which is the part that makes it a migration
21482        // rather than a loss.
21483        let val = format!("$256\r\n{}\r\n", "v".repeat(256));
21484        assert_eq!(f.run(&[b"GET", b"key:00000000"]), val);
21485        assert_eq!(f.run(&[b"GET", b"key:00023999"]), val);
21486    }
21487
21488    #[cfg_attr(miri, ignore = "several megabytes of arena, see `filled`")]
21489    #[test]
21490    fn a_storage_limit_of_zero_restores_redis_behaviour_exactly() {
21491        // The documented setting for a drop in cache. A file that may hold
21492        // nothing cannot be migrated to, so eviction is all that is left, and
21493        // the server behaves exactly as it did before any of this existed.
21494        let (mut f, full) = filled(true);
21495        f.run(&[b"CONFIG", b"SET", b"maxstore", b"0"]);
21496        assert!(
21497            f.run(&[b"INFO", b"memory"])
21498                .contains("yo_memory_regime:evict"),
21499            "nothing may go to the file"
21500        );
21501
21502        let limit = full - 2 * 1024 * 1024;
21503        f.run(&[b"CONFIG", b"SET", b"maxmemory-policy", b"allkeys-lru"]);
21504        f.run(&[
21505            b"CONFIG",
21506            b"SET",
21507            b"maxmemory",
21508            limit.to_string().as_bytes(),
21509        ]);
21510        press(&mut f, limit);
21511
21512        assert!(
21513            !f.run(&[b"INFO", b"stats"]).contains("evicted_keys:0"),
21514            "keys were thrown away, which is what was asked for"
21515        );
21516        assert_eq!(f.server.store_bytes(), 0, "and the file was never written");
21517    }
21518
21519    #[cfg_attr(miri, ignore = "several megabytes of arena, see `filled`")]
21520    #[test]
21521    fn a_full_file_goes_back_to_evicting() {
21522        // A storage limit reached is a storage limit, and eviction is the right
21523        // answer to one. The budget here is a few kilobytes, so the first round
21524        // of migration fills it and everything after that is evicted.
21525        let (mut f, full) = filled(true);
21526        f.run(&[b"CONFIG", b"SET", b"maxstore", b"64kb"]);
21527        let limit = full - 2 * 1024 * 1024;
21528        f.run(&[b"CONFIG", b"SET", b"maxmemory-policy", b"allkeys-lru"]);
21529        f.run(&[
21530            b"CONFIG",
21531            b"SET",
21532            b"maxmemory",
21533            limit.to_string().as_bytes(),
21534        ]);
21535        press(&mut f, limit);
21536
21537        assert!(f.server.store_bytes() >= 64 * 1024, "the file filled up");
21538        assert!(
21539            !f.run(&[b"INFO", b"stats"]).contains("evicted_keys:0"),
21540            "and then it started evicting"
21541        );
21542        assert!(
21543            f.run(&[b"INFO", b"memory"])
21544                .contains("yo_memory_regime:evict"),
21545            "and it says so"
21546        );
21547    }
21548    // ------------------------------------------------------------- stripes
21549
21550    /// Every string command, run twice: once on a database that is one keyspace
21551    /// and once on a database that is eight, with the same commands in the same
21552    /// order and the replies compared byte for byte.
21553    ///
21554    /// This is the whole claim the striping rests on. A key belongs to one
21555    /// stripe and to no other, so the answer to a command cannot depend on how
21556    /// many stripes there are, and the way to check that is to ask the same
21557    /// question of two servers that differ in nothing else.
21558    ///
21559    /// The keys are chosen to land on different stripes rather than to look
21560    /// tidy. `MSET a 1 b 2 c 3` over eight stripes is only a test of anything if
21561    /// those three keys are not all on the same one, and at eight stripes three
21562    /// keys land together about one time in fifty.
21563    #[test]
21564    fn the_string_group_answers_the_same_however_many_stripes_there_are() {
21565        let script: &[&[&[u8]]] = &[
21566            // The single key commands, which are the ones that get handed one
21567            // stripe at the dispatch site.
21568            &[b"SET", b"k1", b"v1"],
21569            &[b"SET", b"k2", b"v2"],
21570            &[b"GET", b"k1"],
21571            &[b"GET", b"nothing"],
21572            &[b"GETSET", b"k1", b"v1b"],
21573            &[b"SETNX", b"k1", b"no"],
21574            &[b"SETNX", b"k3", b"yes"],
21575            &[b"APPEND", b"k3", b"!"],
21576            &[b"STRLEN", b"k3"],
21577            &[b"SETRANGE", b"k3", b"1", b"XY"],
21578            &[b"GETRANGE", b"k3", b"0", b"-1"],
21579            &[b"INCR", b"n1"],
21580            &[b"INCRBY", b"n1", b"41"],
21581            &[b"DECRBY", b"n1", b"2"],
21582            &[b"INCRBYFLOAT", b"f1", b"1.5"],
21583            &[b"SETEX", b"e1", b"100", b"v"],
21584            &[b"PSETEX", b"e2", b"100000", b"v"],
21585            &[b"GETEX", b"e1", b"PERSIST"],
21586            &[b"GETDEL", b"k2"],
21587            &[b"GET", b"k2"],
21588            &[b"DIGEST", b"k1"],
21589            &[b"DELEX", b"k3"],
21590            // The five that name more than one key, which are the ones that
21591            // cannot be handed one stripe at all.
21592            &[b"MSET", b"a", b"1", b"b", b"2", b"c", b"3"],
21593            &[b"MGET", b"a", b"b", b"c", b"missing"],
21594            &[b"MSETNX", b"d", b"4", b"e", b"5"],
21595            &[b"MSETNX", b"e", b"6", b"f", b"7"],
21596            &[b"MGET", b"d", b"e", b"f"],
21597            &[b"MSETEX", b"2", b"g", b"7", b"h", b"8", b"NX"],
21598            &[b"MSETEX", b"2", b"g", b"9", b"h", b"9", b"NX"],
21599            &[b"MSETEX", b"2", b"g", b"9", b"h", b"9", b"XX"],
21600            &[b"MGET", b"g", b"h"],
21601            &[b"SET", b"s1", b"ohmytext"],
21602            &[b"SET", b"s2", b"mynewtext"],
21603            &[b"LCS", b"s1", b"s2"],
21604            &[b"LCS", b"s1", b"s2", b"LEN"],
21605            &[b"LCS", b"s1", b"s2", b"IDX", b"MINMATCHLEN", b"4"],
21606            &[b"LCS", b"s1", b"s2", b"IDX", b"WITHMATCHLEN"],
21607            &[b"LCS", b"s1", b"gone"],
21608            // And the errors, which have to be the same errors.
21609            &[b"MSET", b"odd"],
21610            &[b"LCS", b"s1", b"s2", b"LEN", b"IDX"],
21611            &[b"MGET"],
21612        ];
21613
21614        let mut one = Fixture::new();
21615        let mut many = Fixture::striped(8);
21616        for parts in script {
21617            let a = one.run(parts);
21618            let b = many.run(parts);
21619            assert_eq!(a, b, "{}", String::from_utf8_lossy(parts[0]));
21620        }
21621    }
21622
21623    /// The keys of an `MSET` really do end up on different stripes.
21624    ///
21625    /// Without this the test above could pass on a server whose stripe number
21626    /// happened to be a constant, which is a striped database in name only.
21627    #[test]
21628    fn a_striped_database_spreads_the_keys_it_is_given() {
21629        let mut f = Fixture::striped(8);
21630        for i in 0..256 {
21631            let key = format!("key:{i}");
21632            f.run(&[b"SET", key.as_bytes(), b"v"]);
21633        }
21634        assert_eq!(f.run(&[b"DBSIZE"]), ":256\r\n");
21635    }
21636
21637    /// A wrong type stops an `MGET` no more than it does on one stripe: the key
21638    /// that is not a string comes back nil and the rest of the reply is intact.
21639    #[test]
21640    fn a_wrong_type_in_the_middle_of_an_mget_is_still_one_nil() {
21641        let mut one = Fixture::new();
21642        let mut many = Fixture::striped(8);
21643        for f in [&mut one, &mut many] {
21644            f.run(&[b"SET", b"str", b"v"]);
21645            // Planted rather than pushed. `RPUSH` belongs to the list group,
21646            // which has not been taught about stripes yet and would refuse the
21647            // wide server. What is under test is what `MGET` does when it walks
21648            // onto a key that is not a string, and that does not care how the
21649            // key got there.
21650            f.server
21651                .striped(0)
21652                .hold(b"list")
21653                .push(b"list", yo_kv::End::Right, core::iter::once(&b"v"[..]))
21654                .expect("a new list");
21655        }
21656        assert_eq!(
21657            one.run(&[b"MGET", b"str", b"list", b"gone"]),
21658            many.run(&[b"MGET", b"str", b"list", b"gone"])
21659        );
21660    }
21661
21662    /// The same claim for the keyspace group, and the same way of checking it.
21663    ///
21664    /// `SORT` is not in the script because it is the one command in that file
21665    /// that has not been taught about stripes, and `SCAN`, `KEYS` and
21666    /// `RANDOMKEY` are not in it either, because those three do not promise an
21667    /// order and comparing two replies byte for byte would be asserting one.
21668    /// They get tests of their own below.
21669    #[test]
21670    fn the_keyspace_group_answers_the_same_however_many_stripes_there_are() {
21671        let script: &[&[&[u8]]] = &[
21672            &[b"SET", b"k1", b"v1"],
21673            &[b"SET", b"k2", b"v2"],
21674            &[b"EXISTS", b"k1", b"k2", b"k1", b"gone"],
21675            &[b"TYPE", b"k1"],
21676            &[b"TYPE", b"gone"],
21677            &[b"TOUCH", b"k1", b"k2", b"k1", b"gone"],
21678            &[b"EXPIRE", b"k1", b"100"],
21679            &[b"TTL", b"k1"],
21680            &[b"EXPIRE", b"k1", b"200", b"NX"],
21681            &[b"PERSIST", b"k1"],
21682            &[b"TTL", b"k1"],
21683            &[b"PEXPIREAT", b"k2", b"1900000000000"],
21684            &[b"EXPIRETIME", b"k2"],
21685            &[b"PEXPIRETIME", b"k2"],
21686            &[b"PERSIST", b"k2"],
21687            &[b"OBJECT", b"ENCODING", b"k1"],
21688            &[b"OBJECT", b"REFCOUNT", b"k1"],
21689            &[b"OBJECT", b"IDLETIME", b"k1"],
21690            &[b"OBJECT", b"FREQ", b"k1"],
21691            &[b"OBJECT", b"ENCODING", b"gone"],
21692            &[b"OBJECT", b"HELP"],
21693            &[b"RENAME", b"k1", b"k9"],
21694            &[b"GET", b"k9"],
21695            &[b"RENAME", b"gone", b"x"],
21696            &[b"RENAMENX", b"k9", b"k2"],
21697            &[b"RENAMENX", b"k9", b"k8"],
21698            &[b"GET", b"k8"],
21699            &[b"COPY", b"k8", b"c1"],
21700            &[b"COPY", b"k8", b"c1"],
21701            &[b"COPY", b"k8", b"c1", b"REPLACE"],
21702            &[b"COPY", b"k8", b"k8"],
21703            &[b"COPY", b"gone", b"c2"],
21704            &[b"COPY", b"k8", b"k8", b"DB", b"1"],
21705            &[b"COPY", b"k8", b"c9", b"DB", b"9"],
21706            &[b"MOVE", b"c1", b"1"],
21707            &[b"MOVE", b"c1", b"1"],
21708            &[b"MOVE", b"k8", b"0"],
21709            &[b"DEL", b"k2", b"gone"],
21710            &[b"UNLINK", b"k8", b"k8"],
21711            &[b"DBSIZE"],
21712        ];
21713
21714        let mut one = Fixture::new();
21715        let mut many = Fixture::striped(8);
21716        for parts in script {
21717            let a = one.run(parts);
21718            let b = many.run(parts);
21719            assert_eq!(a, b, "{}", String::from_utf8_lossy(parts[0]));
21720        }
21721
21722        // `RESTORE` needs bytes a client would have got from a `DUMP`, so the
21723        // payload is taken from the store rather than parsed back out of a
21724        // reply that is not text. Both servers dump the same key and the bytes
21725        // are the same bytes, which is the first half of what is being checked
21726        // here.
21727        for f in [&mut one, &mut many] {
21728            f.run(&[b"SET", b"d1", b"payload"]);
21729            let payload = f
21730                .server
21731                .striped(0)
21732                .hold(b"d1")
21733                .dump(b"d1")
21734                .expect("a key that is there");
21735            assert!(
21736                f.run(&[b"DUMP", b"d1"])
21737                    .starts_with(&format!("${}", payload.len())),
21738                "a payload of the length the store gave"
21739            );
21740            assert_eq!(f.run(&[b"DUMP", b"gone"]), "$-1\r\n");
21741            assert_eq!(f.run(&[b"RESTORE", b"d2", b"0", &payload]), "+OK\r\n");
21742            assert_eq!(f.run(&[b"GET", b"d2"]), "$7\r\npayload\r\n");
21743            assert_eq!(
21744                f.run(&[b"RESTORE", b"d2", b"0", &payload]),
21745                "-BUSYKEY Target key name already exists.\r\n"
21746            );
21747            assert_eq!(
21748                f.run(&[b"RESTORE", b"d3", b"0", b"rubbish"]),
21749                "-ERR DUMP payload version or checksum are wrong\r\n"
21750            );
21751        }
21752    }
21753
21754    /// A `SCAN` of a database of eight stripes comes back with all of it.
21755    ///
21756    /// The cursor is the thing under test. It has to carry the stripe as well
21757    /// as the place in it, so a client that stops at one stripe and comes back
21758    /// carries on in that stripe and not at the top of the database, and the
21759    /// walk has to end once rather than eight times.
21760    #[test]
21761    fn a_scan_of_a_striped_database_walks_all_of_it() {
21762        // Eight stripes and a COUNT of ten, so eighty keys is already more than
21763        // one page on every stripe and the cursor has to carry which stripe it
21764        // was on, which is the thing being checked.
21765        let n = if cfg!(miri) { 80 } else { 500 };
21766        let mut f = Fixture::striped(8);
21767        for i in 0..n {
21768            let key = format!("key:{i}");
21769            f.run(&[b"SET", key.as_bytes(), b"v"]);
21770        }
21771
21772        let mut seen = Vec::new();
21773        let mut cursor = "0".to_owned();
21774        let mut calls = 0;
21775        loop {
21776            let reply = f.run(&[b"SCAN", cursor.as_bytes(), b"COUNT", b"10"]);
21777            let (next, keys) = scan_reply(&reply);
21778            seen.extend(keys);
21779            cursor = next;
21780            calls += 1;
21781            assert!(calls < 5_000, "a scan that will not finish");
21782            if cursor == "0" {
21783                break;
21784            }
21785        }
21786        seen.sort();
21787        assert_eq!(seen.len(), n, "a quiet scan answered a key twice");
21788        assert_eq!(seen, sorted(&f.run(&[b"KEYS", b"*"])));
21789
21790        // And the options still work when the walk is over several stripes,
21791        // since a `MATCH` is applied to keys a stripe handed up and a `TYPE` is
21792        // applied by each stripe on the way.
21793        let reply = f.run(&[b"SCAN", b"0", b"COUNT", b"1000", b"MATCH", b"key:4?"]);
21794        let (_, keys) = scan_reply(&reply);
21795        assert_eq!(keys.len(), 10, "key:40 through key:49");
21796        let reply = f.run(&[b"SCAN", b"0", b"COUNT", b"1000", b"TYPE", b"list"]);
21797        let (_, keys) = scan_reply(&reply);
21798        assert!(keys.is_empty(), "nothing here is a list");
21799    }
21800
21801    /// `RANDOMKEY` on a striped database answers a key from any of the stripes.
21802    ///
21803    /// The draw picks the stripe first, so the thing that can go wrong is that
21804    /// it always picks the same one, and two hundred draws over eight stripes
21805    /// would make that obvious.
21806    #[test]
21807    fn a_random_key_can_come_from_any_stripe() {
21808        let mut f = Fixture::striped(8);
21809        assert_eq!(f.run(&[b"RANDOMKEY"]), "$-1\r\n");
21810        for i in 0..200 {
21811            let key = format!("key:{i}");
21812            f.run(&[b"SET", key.as_bytes(), b"v"]);
21813        }
21814        let mut homes = std::collections::HashSet::new();
21815        for _ in 0..200 {
21816            let got = f.run(&[b"RANDOMKEY"]);
21817            let key = got.split("\r\n").nth(1).expect("a key").to_owned();
21818            assert_eq!(f.run(&[b"EXISTS", key.as_bytes()]), ":1\r\n");
21819            homes.insert(f.server.striped(0).stripe_of(key.as_bytes()));
21820        }
21821        assert_eq!(homes.len(), 8, "some stripe was never drawn from");
21822    }
21823
21824    /// Two keys that are not on the same stripe, which is what `RENAME` and
21825    /// `COPY` have to cope with and what a test has to arrange rather than
21826    /// hope for.
21827    fn apart(f: &mut Fixture, src: &str) -> String {
21828        let home = f.server.striped(0).stripe_of(src.as_bytes());
21829        for i in 0..1_000 {
21830            let dst = format!("dst:{i}");
21831            if f.server.striped(0).stripe_of(dst.as_bytes()) != home {
21832                return dst;
21833            }
21834        }
21835        panic!("eight stripes and a thousand keys all landed in one place");
21836    }
21837
21838    /// A rename whose two keys are on two stripes moves the value, the deadline
21839    /// and, for a collection, the body itself.
21840    #[test]
21841    fn a_rename_across_stripes_takes_everything_with_it() {
21842        let mut f = Fixture::striped(8);
21843        let dst = apart(&mut f, "src");
21844        let (src, dst) = (b"src".as_slice(), dst.as_bytes());
21845
21846        f.run(&[b"SET", src, b"v"]);
21847        f.run(&[b"EXPIRE", src, b"100"]);
21848        assert_eq!(f.run(&[b"RENAME", src, dst]), "+OK\r\n");
21849        assert_eq!(f.run(&[b"EXISTS", src, dst]), ":1\r\n");
21850        assert_eq!(f.run(&[b"GET", dst]), "$1\r\nv\r\n");
21851        assert_eq!(f.run(&[b"TTL", dst]), ":100\r\n", "the deadline came too");
21852
21853        // A list, because a string lives in its record and a collection lives
21854        // in a slab, and the second of those is the one that can be left
21855        // behind. Planted through the store, since the list group has not been
21856        // taught about stripes yet.
21857        f.server
21858            .striped(0)
21859            .hold(src)
21860            .push(src, yo_kv::End::Right, [&b"a"[..], &b"b"[..]].into_iter())
21861            .expect("a new list");
21862        assert_eq!(f.run(&[b"RENAME", src, dst]), "+OK\r\n");
21863        assert_eq!(f.run(&[b"TYPE", dst]), "+list\r\n");
21864        assert_eq!(
21865            f.server.striped(0).hold(dst).llen(dst).expect("a list"),
21866            2,
21867            "the members are on the stripe the key moved to"
21868        );
21869
21870        // And `RENAMENX` still refuses a destination that is taken, which is
21871        // the one answer the cross stripe path has to work out for itself.
21872        f.run(&[b"SET", src, b"v"]);
21873        assert_eq!(f.run(&[b"RENAMENX", src, dst]), ":0\r\n");
21874        assert_eq!(f.run(&[b"TYPE", dst]), "+list\r\n", "and left it alone");
21875        assert_eq!(f.run(&[b"GET", src]), "$1\r\nv\r\n", "and left the source");
21876    }
21877
21878    /// And a copy across two stripes leaves both keys behind it.
21879    #[test]
21880    fn a_copy_across_stripes_leaves_the_source_where_it_was() {
21881        let mut f = Fixture::striped(8);
21882        let dst = apart(&mut f, "src");
21883        let (src, dst) = (b"src".as_slice(), dst.as_bytes());
21884
21885        f.run(&[b"SET", src, b"v"]);
21886        assert_eq!(f.run(&[b"COPY", src, dst]), ":1\r\n");
21887        assert_eq!(f.run(&[b"EXISTS", src, dst]), ":2\r\n");
21888        assert_eq!(
21889            f.run(&[b"COPY", src, dst]),
21890            ":0\r\n",
21891            "the destination is taken"
21892        );
21893        f.run(&[b"SET", src, b"w"]);
21894        assert_eq!(f.run(&[b"COPY", src, dst, b"REPLACE"]), ":1\r\n");
21895        assert_eq!(f.run(&[b"GET", dst]), "$1\r\nw\r\n");
21896
21897        // A collection is cloned rather than moved, so both keys have a body of
21898        // their own afterwards and writing to one does not show up in the
21899        // other.
21900        f.run(&[b"DEL", src, dst]);
21901        f.server
21902            .striped(0)
21903            .hold(src)
21904            .push(src, yo_kv::End::Right, [&b"a"[..], &b"b"[..]].into_iter())
21905            .expect("a new list");
21906        assert_eq!(f.run(&[b"COPY", src, dst]), ":1\r\n");
21907        f.server
21908            .striped(0)
21909            .hold(src)
21910            .push(src, yo_kv::End::Right, core::iter::once(&b"c"[..]))
21911            .expect("a list that is there");
21912        assert_eq!(f.server.striped(0).hold(src).llen(src).expect("a list"), 3);
21913        assert_eq!(f.server.striped(0).hold(dst).llen(dst).expect("a list"), 2);
21914    }
21915
21916    /// Every bitmap command, on one stripe and on eight, replies compared byte
21917    /// for byte.
21918    ///
21919    /// `BITOP` is the one that names more than one key and it is where the work
21920    /// went. The rest are single key commands that now find their own stripe,
21921    /// and they are here because the cheapest way to be sure the routing is
21922    /// right is to ask.
21923    #[test]
21924    fn the_bitmap_group_answers_the_same_however_many_stripes_there_are() {
21925        let script: &[&[&[u8]]] = &[
21926            &[b"SET", b"k1", b"foobar"],
21927            &[b"SETBIT", b"b1", b"7", b"1"],
21928            &[b"SETBIT", b"b1", b"7", b"0"],
21929            &[b"GETBIT", b"k1", b"6"],
21930            &[b"GETBIT", b"k1", b"100"],
21931            &[b"BITCOUNT", b"k1"],
21932            &[b"BITCOUNT", b"k1", b"0", b"0"],
21933            &[b"BITCOUNT", b"k1", b"5", b"30", b"BIT"],
21934            &[b"BITPOS", b"k1", b"1"],
21935            &[b"BITPOS", b"k1", b"0", b"2"],
21936            &[b"BITPOS", b"k1", b"1", b"2", b"-1", b"BIT"],
21937            &[
21938                b"BITFIELD",
21939                b"bf",
21940                b"SET",
21941                b"u8",
21942                b"0",
21943                b"255",
21944                b"GET",
21945                b"u8",
21946                b"0",
21947            ],
21948            &[
21949                b"BITFIELD",
21950                b"bf",
21951                b"OVERFLOW",
21952                b"SAT",
21953                b"INCRBY",
21954                b"u8",
21955                b"0",
21956                b"10",
21957            ],
21958            &[b"BITFIELD_RO", b"bf", b"GET", b"u8", b"0"],
21959            // The multi key one, over sources that are not on one stripe unless
21960            // eight stripes have folded into one.
21961            &[b"SET", b"s1", b"abc"],
21962            &[b"SET", b"s2", b"abd"],
21963            &[b"SET", b"s3", b"a"],
21964            &[b"BITOP", b"AND", b"d1", b"s1", b"s2"],
21965            &[b"GET", b"d1"],
21966            &[b"BITOP", b"OR", b"d2", b"s1", b"s2", b"s3"],
21967            &[b"GET", b"d2"],
21968            &[b"BITOP", b"XOR", b"d3", b"s1", b"s2"],
21969            &[b"STRLEN", b"d3"],
21970            &[b"BITOP", b"NOT", b"d4", b"s1"],
21971            &[b"STRLEN", b"d4"],
21972            &[b"BITOP", b"DIFF", b"d5", b"s1", b"s2"],
21973            &[b"BITOP", b"DIFF1", b"d6", b"s1", b"s2"],
21974            &[b"BITOP", b"ANDOR", b"d7", b"s1", b"s2"],
21975            &[b"BITOP", b"ONE", b"d8", b"s1", b"s2"],
21976            // A source that is not there reads as empty, and a result with
21977            // nothing in it deletes the destination rather than writing one.
21978            &[b"BITOP", b"AND", b"d1", b"gone", b"also-gone"],
21979            &[b"EXISTS", b"d1"],
21980            &[b"BITOP", b"OR", b"d9", b"s1", b"gone"],
21981            &[b"GET", b"d9"],
21982            // And the errors, which have to be the same errors. The key that
21983            // is not a string is planted below rather than pushed here, since
21984            // the list group has not been taught about stripes yet.
21985            &[b"BITOP", b"AND", b"d1", b"s1", b"list"],
21986            &[b"BITOP", b"AND", b"list", b"s1", b"s2"],
21987            &[b"BITOP", b"NOT", b"d1", b"s1", b"s2"],
21988            &[b"BITOP", b"DIFF", b"d1", b"s1"],
21989            &[b"BITOP", b"NOPE", b"d1", b"s1"],
21990            &[b"BITCOUNT", b"list"],
21991            &[b"BITFIELD_RO", b"bf", b"SET", b"u8", b"0", b"1"],
21992        ];
21993
21994        let mut one = Fixture::new();
21995        let mut many = Fixture::striped(8);
21996        for f in [&mut one, &mut many] {
21997            plant_list(f, b"list");
21998        }
21999        for parts in script {
22000            let a = one.run(parts);
22001            let b = many.run(parts);
22002            assert_eq!(a, b, "{}", String::from_utf8_lossy(parts[0]));
22003        }
22004    }
22005
22006    /// A list under `key`, put there through the store.
22007    ///
22008    /// What a test does when it wants a key of the wrong type on a striped
22009    /// server, because the command that would make one is in a group that has
22010    /// not been taught about stripes yet.
22011    fn plant_list(f: &mut Fixture, key: &[u8]) {
22012        f.server
22013            .striped(0)
22014            .hold(key)
22015            .push(key, yo_kv::End::Right, core::iter::once(&b"x"[..]))
22016            .expect("a new list");
22017    }
22018
22019    /// A `BITOP` whose keys are on two stripes reads both of them.
22020    ///
22021    /// The test above spreads its keys by hashing and would still pass if one
22022    /// stripe were doing all the work, since the answers would be the same. This
22023    /// one puts the destination and the two sources where they are known not to
22024    /// share a stripe.
22025    #[test]
22026    fn a_bitop_across_stripes_reads_every_source() {
22027        let mut f = Fixture::striped(8);
22028        let other = apart(&mut f, "src");
22029        let (src, far) = (b"src".as_slice(), other.as_bytes());
22030        assert_ne!(
22031            f.server.striped(0).stripe_of(src),
22032            f.server.striped(0).stripe_of(far),
22033            "the two keys are the point of the test"
22034        );
22035
22036        f.run(&[b"SET", src, b"abc"]);
22037        f.run(&[b"SET", far, b"abd"]);
22038        assert_eq!(f.run(&[b"BITOP", b"AND", far, src, far]), ":3\r\n");
22039        assert_eq!(
22040            f.run(&[b"GET", far]),
22041            "$3\r\nab`\r\n",
22042            "a destination that is also a source"
22043        );
22044        f.run(&[b"SET", far, b"abd"]);
22045        assert_eq!(f.run(&[b"BITOP", b"XOR", src, src, far]), ":3\r\n");
22046        assert_eq!(
22047            f.run(&[b"GET", src]),
22048            "$3\r\n\0\0\x07\r\n",
22049            "and the other way round"
22050        );
22051
22052        // A result of nothing deletes a destination on whatever stripe it is
22053        // on, and a source of the wrong type is refused before anything is
22054        // written.
22055        f.run(&[b"SET", src, b"abc"]);
22056        f.run(&[b"DEL", far]);
22057        assert_eq!(f.run(&[b"BITOP", b"AND", src, far, b"gone"]), ":0\r\n");
22058        assert_eq!(f.run(&[b"EXISTS", src]), ":0\r\n");
22059        f.run(&[b"SET", src, b"abc"]);
22060        f.run(&[b"DEL", far]);
22061        plant_list(&mut f, far);
22062        assert_eq!(
22063            f.run(&[b"BITOP", b"OR", b"out", src, far]),
22064            "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n"
22065        );
22066        assert_eq!(f.run(&[b"EXISTS", b"out"]), ":0\r\n");
22067    }
22068
22069    /// Every HyperLogLog command, on one stripe and on eight.
22070    ///
22071    /// Not under Miri, for the reason on
22072    /// `the_debug_forms_answer_four_different_shapes`, and twice over here
22073    /// because the script is run against both shapes of server.
22074    #[cfg_attr(miri, ignore = "sixteen thousand registers a command")]
22075    #[test]
22076    fn the_hyperloglog_group_answers_the_same_however_many_stripes_there_are() {
22077        let script: &[&[&[u8]]] = &[
22078            &[b"PFADD", b"h1", b"a", b"b", b"c"],
22079            &[b"PFADD", b"h1", b"a"],
22080            &[b"PFADD", b"h2"],
22081            &[b"PFADD", b"h2", b"c", b"d", b"e"],
22082            &[b"PFCOUNT", b"h1"],
22083            &[b"PFCOUNT", b"h2"],
22084            &[b"PFCOUNT", b"missing"],
22085            // The two that name more than one key.
22086            &[b"PFCOUNT", b"h1", b"h2"],
22087            &[b"PFCOUNT", b"h1", b"missing"],
22088            &[b"PFMERGE", b"m", b"h1", b"h2"],
22089            &[b"PFCOUNT", b"m"],
22090            &[b"STRLEN", b"m"],
22091            &[b"PFMERGE", b"m"],
22092            &[b"PFCOUNT", b"m"],
22093            &[b"PFMERGE", b"m2", b"missing"],
22094            &[b"PFCOUNT", b"m2"],
22095            // The debugging ones, which are single key and change what they
22096            // look at.
22097            &[b"PFDEBUG", b"ENCODING", b"h1"],
22098            &[b"PFDEBUG", b"DECODE", b"h1"],
22099            &[b"PFDEBUG", b"TODENSE", b"h1"],
22100            &[b"PFDEBUG", b"ENCODING", b"h1"],
22101            &[b"PFDEBUG", b"TODENSE", b"h1"],
22102            &[b"PFCOUNT", b"h1", b"h2"],
22103            &[b"PFSELFTEST"],
22104            // And the errors.
22105            &[b"SET", b"plain", b"not a sketch at all"],
22106            &[b"PFADD", b"plain", b"a"],
22107            &[b"PFCOUNT", b"plain"],
22108            &[b"PFCOUNT", b"h1", b"plain"],
22109            &[b"PFMERGE", b"plain", b"h1"],
22110            &[b"PFMERGE", b"m", b"plain"],
22111            &[b"PFDEBUG", b"ENCODING", b"gone"],
22112            &[b"PFDEBUG", b"NOPE", b"h1"],
22113        ];
22114
22115        let mut one = Fixture::new();
22116        let mut many = Fixture::striped(8);
22117        for parts in script {
22118            let a = one.run(parts);
22119            let b = many.run(parts);
22120            assert_eq!(a, b, "{}", String::from_utf8_lossy(parts[0]));
22121        }
22122    }
22123
22124    /// Every set command, on one stripe and on eight.
22125    ///
22126    /// The commands that answer members answer them in whatever order the set
22127    /// or the table they were built in holds them, so those replies are
22128    /// compared as sets. Everything else is compared byte for byte. Two servers
22129    /// agreeing on the order would be a fact about the tables and not about the
22130    /// answer, and asserting it would make this test fail for a reason nobody
22131    /// cares about.
22132    #[test]
22133    fn the_set_group_answers_the_same_however_many_stripes_there_are() {
22134        const UNORDERED: [&str; 4] = ["SMEMBERS", "SINTER", "SUNION", "SDIFF"];
22135        let script: &[&[&[u8]]] = &[
22136            &[b"SADD", b"s1", b"a", b"b", b"c"],
22137            &[b"SADD", b"s1", b"a"],
22138            &[b"SADD", b"s2", b"b", b"c", b"d"],
22139            &[b"SADD", b"ints", b"1", b"2", b"3"],
22140            &[b"SCARD", b"s1"],
22141            &[b"SISMEMBER", b"s1", b"a"],
22142            &[b"SISMEMBER", b"s1", b"z"],
22143            &[b"SMISMEMBER", b"s1", b"a", b"z", b"c"],
22144            &[b"SMEMBERS", b"s1"],
22145            &[b"SREM", b"s1", b"c"],
22146            &[b"SADD", b"s1", b"c"],
22147            &[b"SSCAN", b"s1", b"0"],
22148            &[b"SSCAN", b"s1", b"0", b"COUNT", b"100", b"MATCH", b"a*"],
22149            // The two draws, on a set of one member, which is the only shape
22150            // whose answer two servers have to agree on.
22151            &[b"SADD", b"one", b"m"],
22152            &[b"SRANDMEMBER", b"one"],
22153            &[b"SRANDMEMBER", b"one", b"-3"],
22154            &[b"SRANDMEMBER", b"gone"],
22155            &[b"SPOP", b"one"],
22156            &[b"SPOP", b"one"],
22157            &[b"SPOP", b"gone", b"2"],
22158            // The one that names two keys.
22159            &[b"SMOVE", b"s1", b"s2", b"a"],
22160            &[b"SMOVE", b"s1", b"s2", b"zzz"],
22161            &[b"SMOVE", b"gone", b"s2", b"a"],
22162            &[b"SMEMBERS", b"s1"],
22163            &[b"SMEMBERS", b"s2"],
22164            // The algebra.
22165            &[b"SINTER", b"s1", b"s2"],
22166            &[b"SUNION", b"s1", b"s2"],
22167            &[b"SDIFF", b"s2", b"s1"],
22168            &[b"SINTER", b"s1", b"gone"],
22169            &[b"SUNION", b"s1", b"gone"],
22170            &[b"SDIFF", b"gone", b"s1"],
22171            &[b"SINTER", b"ints", b"s1"],
22172            &[b"SINTERCARD", b"2", b"s1", b"s2"],
22173            &[b"SINTERCARD", b"2", b"s1", b"s2", b"LIMIT", b"1"],
22174            &[b"SUNIONCARD", b"2", b"s1", b"s2"],
22175            &[b"SDIFFCARD", b"2", b"s2", b"s1"],
22176            &[b"SINTERSTORE", b"d1", b"s1", b"s2"],
22177            &[b"SMEMBERS", b"d1"],
22178            &[b"SUNIONSTORE", b"d2", b"s1", b"s2"],
22179            &[b"SCARD", b"d2"],
22180            &[b"SDIFFSTORE", b"d3", b"s2", b"s1"],
22181            &[b"SCARD", b"d3"],
22182            // An empty result deletes the destination rather than storing a
22183            // set with nothing in it.
22184            &[b"SINTERSTORE", b"d4", b"s1", b"gone"],
22185            &[b"EXISTS", b"d4"],
22186            // And a destination that is also a source.
22187            &[b"SUNIONSTORE", b"s2", b"s1", b"s2"],
22188            &[b"SCARD", b"s2"],
22189            // The errors, which have to be the same errors.
22190            &[b"SET", b"str", b"v"],
22191            &[b"SADD", b"str", b"a"],
22192            &[b"SINTER", b"s1", b"str"],
22193            &[b"SINTERSTORE", b"d5", b"s1", b"str"],
22194            &[b"EXISTS", b"d5"],
22195            &[b"SMOVE", b"str", b"s2", b"a"],
22196            &[b"SMOVE", b"s1", b"str", b"b"],
22197            &[b"SMOVE", b"gone", b"str", b"b"],
22198            &[b"SINTERCARD", b"0", b"s1"],
22199            &[b"SINTERCARD", b"3", b"s1", b"s2"],
22200            &[b"SINTERCARD", b"2", b"s1", b"s2", b"LIMIT", b"-1"],
22201            &[b"SPOP", b"s1", b"-1"],
22202        ];
22203
22204        let mut one = Fixture::new();
22205        let mut many = Fixture::striped(8);
22206        for parts in script {
22207            let a = one.run(parts);
22208            let b = many.run(parts);
22209            let name = String::from_utf8_lossy(parts[0]).to_uppercase();
22210            if UNORDERED.contains(&name.as_str()) && a.starts_with(['*', '~']) {
22211                assert_eq!(sorted(&a), sorted(&b), "{name}");
22212            } else {
22213                assert_eq!(a, b, "{name}");
22214            }
22215        }
22216    }
22217
22218    /// The algebra over sets that are known to be on different stripes.
22219    #[test]
22220    fn a_set_operation_across_stripes_reads_every_set() {
22221        let mut f = Fixture::striped(8);
22222        let second = apart(&mut f, "s1");
22223        let third = apart(&mut f, &second);
22224        let (s1, s2, s3) = (b"s1".as_slice(), second.as_bytes(), third.as_bytes());
22225
22226        f.run(&[b"SADD", s1, b"a", b"b", b"c"]);
22227        f.run(&[b"SADD", s2, b"b", b"c", b"d"]);
22228        assert_eq!(sorted(&f.run(&[b"SINTER", s1, s2])), ["b", "c"]);
22229        assert_eq!(
22230            sorted(&f.run(&[b"SUNION", s1, s2])),
22231            ["a", "b", "c", "d"],
22232            "a union of two stripes is both of them"
22233        );
22234        assert_eq!(sorted(&f.run(&[b"SDIFF", s1, s2])), ["a"]);
22235        assert_eq!(f.run(&[b"SINTERCARD", b"2", s1, s2]), ":2\r\n");
22236        assert_eq!(f.run(&[b"SUNIONCARD", b"2", s1, s2]), ":4\r\n");
22237        assert_eq!(f.run(&[b"SDIFFCARD", b"2", s1, s2]), ":1\r\n");
22238
22239        // A destination on a third stripe, and then one that is also a source.
22240        assert_eq!(f.run(&[b"SINTERSTORE", s3, s1, s2]), ":2\r\n");
22241        assert_eq!(sorted(&f.run(&[b"SMEMBERS", s3])), ["b", "c"]);
22242        assert_eq!(f.run(&[b"SUNIONSTORE", s2, s1, s2]), ":4\r\n");
22243        assert_eq!(sorted(&f.run(&[b"SMEMBERS", s2])), ["a", "b", "c", "d"]);
22244        assert_eq!(f.run(&[b"SDIFFSTORE", s3, s2, s1]), ":1\r\n");
22245        assert_eq!(sorted(&f.run(&[b"SMEMBERS", s3])), ["d"]);
22246
22247        // An empty result deletes a destination wherever it is, and a key of
22248        // the wrong type stops the command before the destination is touched.
22249        assert_eq!(f.run(&[b"SINTERSTORE", s3, s1, b"gone"]), ":0\r\n");
22250        assert_eq!(f.run(&[b"EXISTS", s3]), ":0\r\n");
22251        f.run(&[b"SET", s3, b"v"]);
22252        assert_eq!(
22253            f.run(&[b"SINTER", s1, s3]),
22254            "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n"
22255        );
22256        assert_eq!(f.run(&[b"GET", s3]), "$1\r\nv\r\n", "and left it alone");
22257    }
22258
22259    /// An `SMOVE` whose two keys are on two stripes.
22260    #[test]
22261    fn a_move_across_stripes_takes_the_member_with_it() {
22262        let mut f = Fixture::striped(8);
22263        let other = apart(&mut f, "src");
22264        let (src, dst) = (b"src".as_slice(), other.as_bytes());
22265
22266        f.run(&[b"SADD", src, b"a", b"b"]);
22267        f.run(&[b"SADD", dst, b"c"]);
22268        assert_eq!(f.run(&[b"SMOVE", src, dst, b"a"]), ":1\r\n");
22269        assert_eq!(sorted(&f.run(&[b"SMEMBERS", src])), ["b"]);
22270        assert_eq!(sorted(&f.run(&[b"SMEMBERS", dst])), ["a", "c"]);
22271        assert_eq!(f.run(&[b"SMOVE", src, dst, b"a"]), ":0\r\n", "it has gone");
22272
22273        // A destination that is not there is created on its own stripe, and a
22274        // source that loses its last member is deleted from its own.
22275        f.run(&[b"DEL", dst]);
22276        assert_eq!(f.run(&[b"SMOVE", src, dst, b"b"]), ":1\r\n");
22277        assert_eq!(f.run(&[b"EXISTS", src]), ":0\r\n", "the source is empty");
22278        assert_eq!(sorted(&f.run(&[b"SMEMBERS", dst])), ["b"]);
22279
22280        // And a source that is not there answers zero without ever asking what
22281        // the destination holds, which is Redis's order and not the obvious
22282        // one.
22283        f.run(&[b"SET", dst, b"v"]);
22284        assert_eq!(f.run(&[b"SMOVE", src, dst, b"b"]), ":0\r\n");
22285        f.run(&[b"SADD", src, b"b"]);
22286        assert_eq!(
22287            f.run(&[b"SMOVE", src, dst, b"b"]),
22288            "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n"
22289        );
22290    }
22291
22292    /// A count and a merge over sketches that are known to be on two stripes.
22293    #[test]
22294    fn a_pfcount_and_a_pfmerge_reach_across_stripes() {
22295        let mut f = Fixture::striped(8);
22296        let other = apart(&mut f, "src");
22297        let (src, far) = (b"src".as_slice(), other.as_bytes());
22298
22299        for i in 0..150 {
22300            let ele = format!("e:{i}");
22301            f.run(&[b"PFADD", src, ele.as_bytes()]);
22302        }
22303        for i in 150..200 {
22304            let ele = format!("e:{i}");
22305            f.run(&[b"PFADD", far, ele.as_bytes()]);
22306        }
22307        // The three numbers a real server gives for these elements, which are
22308        // the numbers the single stripe tests in the keyspace crate check too.
22309        assert_eq!(f.run(&[b"PFCOUNT", src]), ":151\r\n");
22310        assert_eq!(f.run(&[b"PFCOUNT", far]), ":49\r\n");
22311        assert_eq!(f.run(&[b"PFCOUNT", src, far]), ":199\r\n");
22312
22313        // A merge whose destination is on a third stripe, and then one that
22314        // writes into a source.
22315        let dest = apart(&mut f, &other);
22316        assert_eq!(f.run(&[b"PFMERGE", dest.as_bytes(), src, far]), "+OK\r\n");
22317        assert_eq!(f.run(&[b"PFCOUNT", dest.as_bytes()]), ":199\r\n");
22318        assert_eq!(f.run(&[b"PFMERGE", far, src]), "+OK\r\n");
22319        assert_eq!(f.run(&[b"PFCOUNT", far]), ":199\r\n", "and kept its own");
22320        assert_eq!(f.run(&[b"PFCOUNT", src]), ":151\r\n", "and left the source");
22321    }
22322
22323    /// Every sorted set command, on one stripe and on eight.
22324    ///
22325    /// Every reply here is compared byte for byte, unlike the set group, because
22326    /// a sorted set answers in rank order and members sharing a score come out
22327    /// in the order of their bytes. There is nothing left for the table the
22328    /// answer was built in to decide.
22329    #[test]
22330    fn the_sorted_set_group_answers_the_same_however_many_stripes_there_are() {
22331        let script: &[&[&[u8]]] = &[
22332            &[b"ZADD", b"z1", b"1", b"a", b"2", b"b", b"3", b"c"],
22333            &[b"ZADD", b"z1", b"NX", b"9", b"a"],
22334            &[b"ZADD", b"z1", b"XX", b"CH", b"5", b"a"],
22335            &[b"ZADD", b"z1", b"GT", b"CH", b"1", b"a"],
22336            &[b"ZADD", b"z1", b"INCR", b"2", b"a"],
22337            &[b"ZINCRBY", b"z1", b"1.5", b"b"],
22338            &[b"ZADD", b"z2", b"1", b"b", b"2", b"c", b"3", b"d"],
22339            &[b"ZADD", b"lex", b"0", b"a", b"0", b"b", b"0", b"c"],
22340            &[b"ZADD", b"one", b"1", b"m"],
22341            &[b"ZCARD", b"z1"],
22342            &[b"ZCARD", b"gone"],
22343            &[b"ZSCORE", b"z1", b"a"],
22344            &[b"ZSCORE", b"z1", b"zz"],
22345            &[b"ZMSCORE", b"z1", b"a", b"zz", b"c"],
22346            &[b"ZRANK", b"z1", b"c"],
22347            &[b"ZRANK", b"z1", b"c", b"WITHSCORE"],
22348            &[b"ZREVRANK", b"z1", b"c"],
22349            &[b"ZRANK", b"z1", b"gone"],
22350            &[b"ZCOUNT", b"z1", b"-inf", b"+inf"],
22351            &[b"ZCOUNT", b"z1", b"(1", b"3"],
22352            &[b"ZLEXCOUNT", b"lex", b"-", b"+"],
22353            // The range commands, which are one parse and one walk.
22354            &[b"ZRANGE", b"z1", b"0", b"-1"],
22355            &[b"ZRANGE", b"z1", b"0", b"-1", b"WITHSCORES"],
22356            &[b"ZRANGE", b"z1", b"1", b"9", b"BYSCORE"],
22357            &[b"ZRANGE", b"z1", b"9", b"1", b"BYSCORE", b"REV"],
22358            &[b"ZRANGE", b"lex", b"[a", b"(c", b"BYLEX"],
22359            &[b"ZREVRANGE", b"z1", b"0", b"-1"],
22360            &[
22361                b"ZRANGEBYSCORE",
22362                b"z1",
22363                b"-inf",
22364                b"+inf",
22365                b"LIMIT",
22366                b"1",
22367                b"1",
22368            ],
22369            &[b"ZREVRANGEBYLEX", b"lex", b"+", b"-"],
22370            &[b"ZSCAN", b"z1", b"0"],
22371            &[b"ZSCAN", b"z1", b"0", b"MATCH", b"a*", b"COUNT", b"100"],
22372            // The draw, on a sorted set of one member, which is the only shape
22373            // whose answer two servers have to agree on.
22374            &[b"ZRANDMEMBER", b"one"],
22375            &[b"ZRANDMEMBER", b"one", b"-3", b"WITHSCORES"],
22376            &[b"ZRANDMEMBER", b"gone"],
22377            // The one that copies a window into another key.
22378            &[b"ZRANGESTORE", b"d0", b"z1", b"0", b"1"],
22379            &[b"ZRANGE", b"d0", b"0", b"-1", b"WITHSCORES"],
22380            &[b"ZRANGESTORE", b"d0", b"z1", b"5", b"1"],
22381            &[b"EXISTS", b"d0"],
22382            // The algebra, in both its shapes.
22383            &[b"ZUNION", b"2", b"z1", b"z2"],
22384            &[b"ZUNION", b"2", b"z1", b"z2", b"WITHSCORES"],
22385            &[
22386                b"ZUNION",
22387                b"2",
22388                b"z1",
22389                b"z2",
22390                b"WEIGHTS",
22391                b"2",
22392                b"3",
22393                b"AGGREGATE",
22394                b"MAX",
22395                b"WITHSCORES",
22396            ],
22397            &[b"ZINTER", b"2", b"z1", b"z2", b"WITHSCORES"],
22398            &[b"ZDIFF", b"2", b"z1", b"z2", b"WITHSCORES"],
22399            &[b"ZDIFF", b"2", b"gone", b"z1"],
22400            &[b"ZINTERCARD", b"2", b"z1", b"z2"],
22401            &[b"ZINTERCARD", b"2", b"z1", b"z2", b"LIMIT", b"1"],
22402            &[b"ZUNIONSTORE", b"d1", b"2", b"z1", b"z2"],
22403            &[b"ZRANGE", b"d1", b"0", b"-1", b"WITHSCORES"],
22404            &[
22405                b"ZINTERSTORE",
22406                b"d2",
22407                b"2",
22408                b"z1",
22409                b"z2",
22410                b"AGGREGATE",
22411                b"MIN",
22412            ],
22413            &[b"ZRANGE", b"d2", b"0", b"-1", b"WITHSCORES"],
22414            &[b"ZDIFFSTORE", b"d3", b"2", b"z1", b"z2"],
22415            &[b"ZCARD", b"d3"],
22416            // An empty result deletes the destination rather than storing a
22417            // sorted set with nothing in it.
22418            &[b"ZINTERSTORE", b"d4", b"2", b"z1", b"gone"],
22419            &[b"EXISTS", b"d4"],
22420            // A plain set is a sorted set where every score is one, so it is a
22421            // legal input to all of these.
22422            &[b"SADD", b"plain", b"a", b"x"],
22423            &[b"ZUNIONSTORE", b"d5", b"2", b"z1", b"plain"],
22424            &[b"ZRANGE", b"d5", b"0", b"-1", b"WITHSCORES"],
22425            // And a destination that is also a source.
22426            &[b"ZUNIONSTORE", b"z2", b"2", b"z1", b"z2"],
22427            &[b"ZRANGE", b"z2", b"0", b"-1", b"WITHSCORES"],
22428            // The three removals and the two pops.
22429            &[b"ZREM", b"d5", b"x", b"nothere"],
22430            &[b"ZREMRANGEBYRANK", b"d5", b"0", b"0"],
22431            &[b"ZREMRANGEBYSCORE", b"d1", b"-inf", b"1"],
22432            &[b"ZREMRANGEBYLEX", b"lex", b"[a", b"[a"],
22433            &[b"ZPOPMIN", b"z1"],
22434            &[b"ZPOPMAX", b"z1", b"2"],
22435            &[b"ZPOPMIN", b"gone"],
22436            &[b"ZMPOP", b"2", b"gone", b"z2", b"MIN"],
22437            &[b"ZMPOP", b"2", b"gone", b"nothere", b"MAX", b"COUNT", b"2"],
22438            // The errors, which have to be the same errors.
22439            &[b"SET", b"str", b"v"],
22440            &[b"ZADD", b"str", b"1", b"a"],
22441            &[b"ZSCORE", b"str", b"a"],
22442            &[b"ZADD", b"z1", b"nan", b"a"],
22443            &[b"ZUNION", b"2", b"z1", b"str"],
22444            &[b"ZUNIONSTORE", b"d6", b"2", b"z1", b"str"],
22445            &[b"EXISTS", b"d6"],
22446            &[b"ZINTERCARD", b"0", b"z1"],
22447            &[b"ZINTERCARD", b"2", b"z1", b"z2", b"LIMIT", b"-1"],
22448            &[b"ZRANGESTORE", b"d7", b"str", b"0", b"-1"],
22449            &[b"ZMPOP", b"1", b"str", b"MIN"],
22450            &[b"ZPOPMIN", b"z1", b"-1"],
22451        ];
22452
22453        let mut one = Fixture::new();
22454        let mut many = Fixture::striped(8);
22455        for parts in script {
22456            let a = one.run(parts);
22457            let b = many.run(parts);
22458            assert_eq!(a, b, "{}", String::from_utf8_lossy(parts[0]));
22459        }
22460    }
22461
22462    /// The algebra over sorted sets that are known to be on different stripes.
22463    #[test]
22464    fn a_sorted_set_operation_across_stripes_reads_every_input() {
22465        let mut f = Fixture::striped(8);
22466        let second = apart(&mut f, "z1");
22467        let third = apart(&mut f, &second);
22468        let (z1, z2, z3) = (b"z1".as_slice(), second.as_bytes(), third.as_bytes());
22469
22470        f.run(&[b"ZADD", z1, b"1", b"a", b"2", b"b"]);
22471        f.run(&[b"ZADD", z2, b"3", b"b", b"4", b"c"]);
22472        // a is 1, c is 4, b is 2 and 3 added together, which is the order they
22473        // come out in and the answer that says both stripes were read.
22474        assert_eq!(
22475            f.run(&[b"ZUNION", b"2", z1, z2]),
22476            "*3\r\n$1\r\na\r\n$1\r\nc\r\n$1\r\nb\r\n"
22477        );
22478        assert_eq!(f.run(&[b"ZINTER", b"2", z1, z2]), "*1\r\n$1\r\nb\r\n");
22479        assert_eq!(f.run(&[b"ZDIFF", b"2", z1, z2]), "*1\r\n$1\r\na\r\n");
22480        assert_eq!(f.run(&[b"ZINTERCARD", b"2", z1, z2]), ":1\r\n");
22481        assert_eq!(
22482            f.run(&[b"ZINTERCARD", b"2", z1, z2, b"LIMIT", b"1"]),
22483            ":1\r\n"
22484        );
22485
22486        // A destination on a third stripe, and the weights and the aggregate
22487        // reaching every input.
22488        assert_eq!(f.run(&[b"ZUNIONSTORE", z3, b"2", z1, z2]), ":3\r\n");
22489        assert_eq!(f.run(&[b"ZSCORE", z3, b"b"]), "$1\r\n5\r\n");
22490        assert_eq!(
22491            f.run(&[
22492                b"ZUNIONSTORE",
22493                z3,
22494                b"2",
22495                z1,
22496                z2,
22497                b"WEIGHTS",
22498                b"2",
22499                b"3",
22500                b"AGGREGATE",
22501                b"MAX"
22502            ]),
22503            ":3\r\n"
22504        );
22505        assert_eq!(f.run(&[b"ZSCORE", z3, b"b"]), "$1\r\n9\r\n");
22506        assert_eq!(f.run(&[b"ZINTERSTORE", z3, b"2", z1, z2]), ":1\r\n");
22507        assert_eq!(f.run(&[b"ZCARD", z3]), ":1\r\n");
22508        assert_eq!(f.run(&[b"ZDIFFSTORE", z3, b"2", z2, z1]), ":1\r\n");
22509        assert_eq!(f.run(&[b"ZSCORE", z3, b"c"]), "$1\r\n4\r\n");
22510
22511        // A pop over keys on several stripes takes from the first one that has
22512        // anything, which is what makes the order of the keys matter.
22513        let popped = format!(
22514            "*2\r\n${}\r\n{second}\r\n*1\r\n*2\r\n$1\r\nb\r\n$1\r\n3\r\n",
22515            second.len()
22516        );
22517        assert_eq!(f.run(&[b"ZMPOP", b"3", b"gone", z2, z1, b"MIN"]), popped);
22518        f.run(&[b"ZADD", z2, b"3", b"b"]);
22519
22520        // An empty result deletes a destination wherever it is, and an input of
22521        // the wrong type stops the command before the destination is touched.
22522        assert_eq!(f.run(&[b"ZINTERSTORE", z3, b"2", z1, b"gone"]), ":0\r\n");
22523        assert_eq!(f.run(&[b"EXISTS", z3]), ":0\r\n");
22524        f.run(&[b"SET", z3, b"v"]);
22525        assert_eq!(
22526            f.run(&[b"ZUNION", b"2", z1, z3]),
22527            "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n"
22528        );
22529        assert_eq!(f.run(&[b"GET", z3]), "$1\r\nv\r\n", "and left it alone");
22530
22531        // And a destination that is also a source works across stripes for the
22532        // reason it works on one: the whole result is built before anything is
22533        // written.
22534        assert_eq!(f.run(&[b"ZUNIONSTORE", z2, b"2", z1, z2]), ":3\r\n");
22535        assert_eq!(f.run(&[b"ZSCORE", z2, b"b"]), "$1\r\n5\r\n");
22536        assert_eq!(f.run(&[b"ZCARD", z2]), ":3\r\n");
22537    }
22538
22539    /// A `ZRANGESTORE` whose two keys are on two stripes.
22540    #[test]
22541    fn a_range_store_across_stripes_copies_the_window() {
22542        let mut f = Fixture::striped(8);
22543        let other = apart(&mut f, "src");
22544        let third = apart(&mut f, &other);
22545        let (src, dst, plain) = (b"src".as_slice(), other.as_bytes(), third.as_bytes());
22546
22547        f.run(&[b"ZADD", src, b"1", b"a", b"2", b"b", b"3", b"c"]);
22548        assert_eq!(f.run(&[b"ZRANGESTORE", dst, src, b"0", b"1"]), ":2\r\n");
22549        assert_eq!(
22550            f.run(&[b"ZRANGE", dst, b"0", b"-1", b"WITHSCORES"]),
22551            "*4\r\n$1\r\na\r\n$1\r\n1\r\n$1\r\nb\r\n$1\r\n2\r\n"
22552        );
22553        assert_eq!(f.run(&[b"ZCARD", src]), ":3\r\n", "the source kept its own");
22554
22555        // A window walked backwards takes the other end of the sorted set and
22556        // still stores what it took in score order.
22557        assert_eq!(
22558            f.run(&[
22559                b"ZRANGESTORE",
22560                dst,
22561                src,
22562                b"+inf",
22563                b"-inf",
22564                b"BYSCORE",
22565                b"REV",
22566                b"LIMIT",
22567                b"0",
22568                b"2"
22569            ]),
22570            ":2\r\n"
22571        );
22572        assert_eq!(
22573            f.run(&[b"ZRANGE", dst, b"0", b"-1"]),
22574            "*2\r\n$1\r\nb\r\n$1\r\nc\r\n"
22575        );
22576
22577        // An empty window deletes the destination on its own stripe, and a
22578        // source of the wrong type is refused before the destination is touched.
22579        assert_eq!(f.run(&[b"ZRANGESTORE", dst, src, b"5", b"1"]), ":0\r\n");
22580        assert_eq!(f.run(&[b"EXISTS", dst]), ":0\r\n");
22581        f.run(&[b"ZRANGESTORE", dst, src, b"0", b"-1"]);
22582        f.run(&[b"SET", plain, b"v"]);
22583        assert_eq!(
22584            f.run(&[b"ZRANGESTORE", dst, plain, b"0", b"-1"]),
22585            "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n"
22586        );
22587        assert_eq!(
22588            f.run(&[b"ZCARD", dst]),
22589            ":3\r\n",
22590            "and left the destination"
22591        );
22592    }
22593
22594    /// Every list command, on one stripe and on eight.
22595    ///
22596    /// The blocking six are in here too, both when they can be answered on the
22597    /// spot and when they cannot, since a command that parks its client writes
22598    /// nothing at all and two servers have to agree about that as much as they
22599    /// agree about a reply.
22600    #[test]
22601    fn the_list_group_answers_the_same_however_many_stripes_there_are() {
22602        let script: &[&[&[u8]]] = &[
22603            &[b"RPUSH", b"l1", b"a", b"b", b"c"],
22604            &[b"LPUSH", b"l1", b"z"],
22605            &[b"RPUSHX", b"l1", b"d"],
22606            &[b"LPUSHX", b"gone", b"x"],
22607            &[b"RPUSHX", b"gone", b"x"],
22608            &[b"LLEN", b"l1"],
22609            &[b"LLEN", b"gone"],
22610            &[b"LRANGE", b"l1", b"0", b"-1"],
22611            &[b"LRANGE", b"l1", b"1", b"2"],
22612            &[b"LRANGE", b"l1", b"5", b"9"],
22613            &[b"LINDEX", b"l1", b"0"],
22614            &[b"LINDEX", b"l1", b"-1"],
22615            &[b"LINDEX", b"l1", b"99"],
22616            &[b"LSET", b"l1", b"0", b"y"],
22617            &[b"LINSERT", b"l1", b"BEFORE", b"b", b"aa"],
22618            &[b"LINSERT", b"l1", b"AFTER", b"nothere", b"x"],
22619            &[b"LPOS", b"l1", b"b"],
22620            &[b"LPOS", b"l1", b"b", b"COUNT", b"0"],
22621            &[b"LPOS", b"l1", b"nothere"],
22622            &[b"LPOS", b"l1", b"b", b"RANK", b"-1", b"MAXLEN", b"2"],
22623            &[b"LREM", b"l1", b"1", b"aa"],
22624            &[b"LTRIM", b"l1", b"0", b"3"],
22625            &[b"LRANGE", b"l1", b"0", b"-1"],
22626            &[b"LPOP", b"l1"],
22627            &[b"RPOP", b"l1"],
22628            &[b"LPOP", b"l1", b"2"],
22629            &[b"LPOP", b"gone"],
22630            &[b"LPOP", b"gone", b"2"],
22631            &[b"EXISTS", b"l1"],
22632            // The ones that name two keys, and the one that takes a block of
22633            // elements rather than the one on the end.
22634            &[b"RPUSH", b"src", b"a", b"b", b"c", b"d"],
22635            &[b"LMOVE", b"src", b"dst", b"LEFT", b"RIGHT"],
22636            &[b"RPOPLPUSH", b"src", b"dst"],
22637            &[b"LRANGE", b"dst", b"0", b"-1"],
22638            &[b"LMOVE", b"gone", b"dst", b"LEFT", b"RIGHT"],
22639            &[b"LMOVEM", b"src", b"dst", b"LEFT", b"RIGHT"],
22640            &[
22641                b"LMOVEM", b"src", b"dst", b"LEFT", b"RIGHT", b"COUNT", b"2", b"BULK",
22642            ],
22643            &[
22644                b"LMOVEM", b"dst", b"dst", b"LEFT", b"RIGHT", b"COUNT", b"2", b"OBO",
22645            ],
22646            &[b"LRANGE", b"dst", b"0", b"-1"],
22647            &[
22648                b"LMOVEM", b"src", b"dst", b"LEFT", b"RIGHT", b"EXACTLY", b"9", b"BULK",
22649            ],
22650            &[b"LMPOP", b"2", b"gone", b"dst", b"LEFT"],
22651            &[b"LMPOP", b"2", b"gone", b"dst", b"RIGHT", b"COUNT", b"2"],
22652            &[b"LMPOP", b"1", b"gone", b"LEFT"],
22653            // The blocking ones, first with something there to answer them and
22654            // then with nothing, which parks the client and writes nothing.
22655            &[b"RPUSH", b"q", b"a", b"b", b"c"],
22656            &[b"BLPOP", b"gone", b"q", b"0"],
22657            &[b"BRPOP", b"q", b"0"],
22658            &[b"BLMPOP", b"0", b"2", b"gone", b"q", b"LEFT"],
22659            &[b"RPUSH", b"q", b"x", b"y", b"z"],
22660            &[b"BLMOVE", b"q", b"dst", b"LEFT", b"RIGHT", b"0"],
22661            &[b"BRPOPLPUSH", b"q", b"dst", b"0"],
22662            &[b"BLMOVEM", b"q", b"dst", b"LEFT", b"RIGHT", b"0"],
22663            &[b"BLPOP", b"q", b"0"],
22664            &[b"BLMOVE", b"q", b"dst", b"LEFT", b"RIGHT", b"0"],
22665            // The errors, which have to be the same errors.
22666            &[b"SET", b"plain", b"v"],
22667            &[b"LPUSH", b"plain", b"a"],
22668            &[b"LLEN", b"plain"],
22669            &[b"LMOVE", b"dst", b"plain", b"LEFT", b"RIGHT"],
22670            &[b"LRANGE", b"dst", b"0", b"-1"],
22671            &[b"LMOVEM", b"dst", b"plain", b"LEFT", b"RIGHT"],
22672            &[b"LSET", b"gone", b"0", b"v"],
22673            &[b"LSET", b"dst", b"99", b"v"],
22674            &[b"LPOP", b"dst", b"-1"],
22675            &[b"LMPOP", b"0", b"dst", b"LEFT"],
22676            &[b"LPOS", b"dst", b"a", b"RANK", b"0"],
22677        ];
22678
22679        let mut one = Fixture::new();
22680        let mut many = Fixture::striped(8);
22681        for parts in script {
22682            let a = one.run(parts);
22683            let b = many.run(parts);
22684            assert_eq!(a, b, "{}", String::from_utf8_lossy(parts[0]));
22685        }
22686    }
22687
22688    /// An `LMOVE` and an `LMOVEM` whose two keys are on two stripes.
22689    #[test]
22690    fn a_list_move_across_stripes_takes_the_elements_with_it() {
22691        let mut f = Fixture::striped(8);
22692        let other = apart(&mut f, "src");
22693        let third = apart(&mut f, &other);
22694        let (src, dst, plain) = (b"src".as_slice(), other.as_bytes(), third.as_bytes());
22695
22696        f.run(&[b"RPUSH", src, b"a", b"b", b"c", b"d"]);
22697        assert_eq!(
22698            f.run(&[b"LMOVE", src, dst, b"LEFT", b"RIGHT"]),
22699            "$1\r\na\r\n"
22700        );
22701        assert_eq!(f.run(&[b"RPOPLPUSH", src, dst]), "$1\r\nd\r\n");
22702        assert_eq!(
22703            f.run(&[b"LRANGE", dst, b"0", b"-1"]),
22704            "*2\r\n$1\r\nd\r\n$1\r\na\r\n",
22705            "one went on each end of the destination"
22706        );
22707        assert_eq!(
22708            f.run(&[b"LRANGE", src, b"0", b"-1"]),
22709            "*2\r\n$1\r\nb\r\n$1\r\nc\r\n"
22710        );
22711
22712        // A block of them, which under BULK arrives in the order it left.
22713        assert_eq!(
22714            f.run(&[
22715                b"LMOVEM", src, dst, b"LEFT", b"RIGHT", b"COUNT", b"2", b"BULK"
22716            ]),
22717            "*2\r\n$1\r\nb\r\n$1\r\nc\r\n"
22718        );
22719        assert_eq!(
22720            f.run(&[b"LRANGE", dst, b"0", b"-1"]),
22721            "*4\r\n$1\r\nd\r\n$1\r\na\r\n$1\r\nb\r\n$1\r\nc\r\n"
22722        );
22723        assert_eq!(
22724            f.run(&[b"EXISTS", src]),
22725            ":0\r\n",
22726            "and the source is gone with its last element"
22727        );
22728
22729        // An `EXACTLY` the source cannot fill moves nothing, and a source that
22730        // is not there at all is the two kinds of nothing the two commands have.
22731        f.run(&[b"RPUSH", src, b"e", b"f"]);
22732        assert_eq!(
22733            f.run(&[
22734                b"LMOVEM", src, dst, b"LEFT", b"RIGHT", b"EXACTLY", b"3", b"BULK"
22735            ]),
22736            "*-1\r\n"
22737        );
22738        assert_eq!(f.run(&[b"LLEN", src]), ":2\r\n", "and took none of them");
22739        assert_eq!(
22740            f.run(&[b"LMOVE", b"gone", dst, b"LEFT", b"RIGHT"]),
22741            "$-1\r\n"
22742        );
22743        assert_eq!(
22744            f.run(&[b"LMOVEM", b"gone", dst, b"LEFT", b"RIGHT"]),
22745            "*-1\r\n"
22746        );
22747
22748        // A destination of the wrong type is refused before anything is taken,
22749        // which is the order that matters most here, since an element already
22750        // out of the source would have nowhere to go back to.
22751        f.run(&[b"SET", plain, b"v"]);
22752        assert_eq!(
22753            f.run(&[b"LMOVE", src, plain, b"LEFT", b"RIGHT"]),
22754            "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n"
22755        );
22756        assert_eq!(
22757            f.run(&[b"LLEN", src]),
22758            ":2\r\n",
22759            "and left the source alone"
22760        );
22761        assert_eq!(
22762            f.run(&[b"LMOVEM", src, plain, b"LEFT", b"RIGHT"]),
22763            "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n"
22764        );
22765        assert_eq!(f.run(&[b"LLEN", src]), ":2\r\n");
22766    }
22767
22768    /// A parked client served by a push that landed on another stripe.
22769    ///
22770    /// A waiter remembers the database and not the stripe, which is the point:
22771    /// serving it runs the same attempt the command ran, and the attempt finds
22772    /// the stripe each of its keys is on for itself.
22773    #[test]
22774    fn a_parked_client_is_served_from_the_stripe_its_key_is_on() {
22775        let mut f = Fixture::striped(8);
22776        let other = apart(&mut f, "q");
22777        let (q, far) = (b"q".as_slice(), other.as_bytes());
22778
22779        assert_eq!(f.flow(&[b"BLPOP", q, far, b"0"]).0, Flow::Block);
22780        assert_eq!(f.server.parked(), 1);
22781        f.run(&[b"RPUSH", far, b"v"]);
22782        let mut out = Out::new(Proto::Resp2);
22783        assert!(f.server.serve_waiter(7, 0, &mut out));
22784        let want = format!("*2\r\n${}\r\n{other}\r\n$1\r\nv\r\n", other.len());
22785        assert_eq!(core::str::from_utf8(out.as_slice()).expect("ascii"), want);
22786        assert_eq!(
22787            f.run(&[b"EXISTS", far]),
22788            ":0\r\n",
22789            "and it took the element with it"
22790        );
22791
22792        // And a move across two stripes is served the same way, by the push
22793        // that fills its source.
22794        f.server.forget_waiters(7);
22795        assert_eq!(
22796            f.flow(&[b"BLMOVE", q, far, b"LEFT", b"RIGHT", b"0"]).0,
22797            Flow::Block
22798        );
22799        f.run(&[b"RPUSH", q, b"w"]);
22800        let mut out = Out::new(Proto::Resp2);
22801        assert!(f.server.serve_waiter(7, 0, &mut out));
22802        assert_eq!(
22803            core::str::from_utf8(out.as_slice()).expect("ascii"),
22804            "$1\r\nw\r\n"
22805        );
22806        assert_eq!(f.run(&[b"LRANGE", far, b"0", b"-1"]), "*1\r\n$1\r\nw\r\n");
22807    }
22808
22809    /// Every stream command, on one stripe and on eight.
22810    ///
22811    /// Every ID is written out rather than left to the clock, so the two servers
22812    /// are being compared on what they store and not on how long the test took
22813    /// to get from one of them to the other.
22814    #[test]
22815    fn the_stream_group_answers_the_same_however_many_stripes_there_are() {
22816        let script: &[&[&[u8]]] = &[
22817            &[b"XADD", b"s", b"1-1", b"a", b"1"],
22818            &[b"XADD", b"s", b"2-1", b"b", b"2", b"c", b"3"],
22819            &[b"XADD", b"s", b"3-1", b"d", b"4"],
22820            &[b"XADD", b"s", b"1-1", b"e", b"5"],
22821            &[b"XADD", b"nomk", b"NOMKSTREAM", b"1-1", b"a", b"1"],
22822            &[b"XLEN", b"s"],
22823            &[b"XLEN", b"gone"],
22824            &[b"XRANGE", b"s", b"-", b"+"],
22825            &[b"XRANGE", b"s", b"2", b"+", b"COUNT", b"1"],
22826            &[b"XRANGE", b"gone", b"-", b"+", b"COUNT", b"0"],
22827            &[b"XRANGE", b"s", b"-", b"+", b"COUNT", b"0"],
22828            &[b"XREVRANGE", b"s", b"+", b"-"],
22829            &[b"XREAD", b"COUNT", b"2", b"STREAMS", b"s", b"0"],
22830            &[b"XREAD", b"STREAMS", b"s", b"gone", b"0", b"0"],
22831            &[b"XREAD", b"STREAMS", b"s", b"$"],
22832            // The groups, which is where most of the state is.
22833            &[b"XGROUP", b"CREATE", b"s", b"g", b"0"],
22834            &[b"XGROUP", b"CREATE", b"s", b"g", b"0"],
22835            &[b"XGROUP", b"CREATE", b"gone", b"g", b"0"],
22836            &[b"XGROUP", b"CREATE", b"made", b"g", b"$", b"MKSTREAM"],
22837            &[b"XGROUP", b"CREATECONSUMER", b"s", b"g", b"idle"],
22838            &[b"XREADGROUP", b"GROUP", b"g", b"c1", b"STREAMS", b"s", b">"],
22839            &[
22840                b"XREADGROUP",
22841                b"GROUP",
22842                b"g",
22843                b"c1",
22844                b"COUNT",
22845                b"1",
22846                b"STREAMS",
22847                b"s",
22848                b"0",
22849            ],
22850            &[
22851                b"XREADGROUP",
22852                b"GROUP",
22853                b"nope",
22854                b"c1",
22855                b"STREAMS",
22856                b"s",
22857                b">",
22858            ],
22859            &[b"XPENDING", b"s", b"g"],
22860            &[b"XPENDING", b"s", b"g", b"-", b"+", b"10"],
22861            &[b"XPENDING", b"s", b"g", b"-", b"+", b"10", b"c1"],
22862            &[b"XPENDING", b"s", b"nope"],
22863            &[b"XCLAIM", b"s", b"g", b"c2", b"0", b"1-1"],
22864            &[b"XCLAIM", b"s", b"g", b"c2", b"0", b"2-1", b"JUSTID"],
22865            &[b"XAUTOCLAIM", b"s", b"g", b"c3", b"0", b"0"],
22866            &[b"XACK", b"s", b"g", b"1-1"],
22867            &[b"XACK", b"s", b"g", b"1-1"],
22868            &[b"XNACK", b"s", b"g", b"FAIL", b"IDS", b"1", b"2-1"],
22869            &[b"XPENDING", b"s", b"g"],
22870            &[b"XINFO", b"STREAM", b"s"],
22871            &[b"XINFO", b"GROUPS", b"s"],
22872            &[b"XINFO", b"CONSUMERS", b"s", b"g"],
22873            &[b"XINFO", b"STREAM", b"gone"],
22874            // Deleting, trimming and moving the ID on.
22875            &[b"XDEL", b"s", b"3-1"],
22876            &[b"XDELEX", b"s", b"DELREF", b"IDS", b"1", b"2-1"],
22877            &[b"XACKDEL", b"s", b"g", b"KEEPREF", b"IDS", b"1", b"1-1"],
22878            &[b"XADD", b"s", b"9-1", b"z", b"9"],
22879            &[b"XTRIM", b"s", b"MAXLEN", b"1"],
22880            &[b"XTRIM", b"s", b"MINID", b"9"],
22881            &[b"XSETID", b"s", b"99-1"],
22882            &[b"XSETID", b"s", b"1-1"],
22883            &[b"XLEN", b"s"],
22884            &[b"XGROUP", b"SETID", b"s", b"g", b"0"],
22885            &[b"XGROUP", b"DELCONSUMER", b"s", b"g", b"c1"],
22886            &[b"XGROUP", b"DESTROY", b"s", b"g"],
22887            &[b"XGROUP", b"DESTROY", b"s", b"g"],
22888            // And the errors.
22889            &[b"SET", b"plain", b"v"],
22890            &[b"XADD", b"plain", b"1-1", b"a", b"1"],
22891            &[b"XLEN", b"plain"],
22892            &[b"XREAD", b"STREAMS", b"plain", b"0"],
22893            &[b"XRANGE", b"s", b"bogus", b"+"],
22894            &[b"XADD", b"s", b"1-1", b"a"],
22895            &[b"XREAD", b"STREAMS", b"s", b"gone", b"0"],
22896            &[b"XREADGROUP", b"GROUP", b"g", b"c", b"STREAMS", b"s", b"$"],
22897        ];
22898
22899        let mut one = Fixture::new();
22900        let mut many = Fixture::striped(8);
22901        for parts in script {
22902            let a = one.run(parts);
22903            let b = many.run(parts);
22904            assert_eq!(a, b, "{}", String::from_utf8_lossy(parts[0]));
22905        }
22906    }
22907
22908    /// An `XREAD` and an `XREADGROUP` naming two keys on two stripes.
22909    ///
22910    /// Nothing is shared between the two streams, so the only thing this can go
22911    /// wrong at is looking both of them up, which is exactly what a read that
22912    /// held one database and walked it would get wrong.
22913    #[test]
22914    fn a_stream_read_across_stripes_reads_every_key() {
22915        let mut f = Fixture::striped(8);
22916        let other = apart(&mut f, "s1");
22917        let (s1, s2) = (b"s1".as_slice(), other.as_bytes());
22918
22919        f.run(&[b"XADD", s1, b"1-1", b"a", b"1"]);
22920        f.run(&[b"XADD", s2, b"2-1", b"b", b"2"]);
22921        let got = f.run(&[b"XREAD", b"STREAMS", s1, s2, b"0", b"0"]);
22922        assert!(got.starts_with("*2\r\n"), "both streams answered: {got}");
22923        assert!(got.contains("1-1"), "the first one is in there: {got}");
22924        assert!(got.contains("2-1"), "and so is the second: {got}");
22925
22926        // A group read looks its group up on every key before it reads any of
22927        // them, so a group that is missing on the far key stops the near one.
22928        f.run(&[b"XGROUP", b"CREATE", s1, b"g", b"0"]);
22929        let got = f.run(&[
22930            b"XREADGROUP",
22931            b"GROUP",
22932            b"g",
22933            b"c",
22934            b"STREAMS",
22935            s1,
22936            s2,
22937            b">",
22938            b">",
22939        ]);
22940        assert!(got.starts_with("-NOGROUP"), "{got}");
22941        assert_eq!(
22942            f.run(&[b"XPENDING", s1, b"g"]),
22943            "*4\r\n:0\r\n$-1\r\n$-1\r\n*-1\r\n",
22944            "and read nothing from the key that did have the group"
22945        );
22946
22947        f.run(&[b"XGROUP", b"CREATE", s2, b"g", b"0"]);
22948        let got = f.run(&[
22949            b"XREADGROUP",
22950            b"GROUP",
22951            b"g",
22952            b"c",
22953            b"STREAMS",
22954            s1,
22955            s2,
22956            b">",
22957            b">",
22958        ]);
22959        assert!(got.starts_with("*2\r\n"), "now both are read: {got}");
22960    }
22961
22962    /// A client parked on an `XREAD` woken by an entry on another stripe.
22963    #[test]
22964    fn a_parked_stream_reader_is_served_from_the_stripe_its_key_is_on() {
22965        let mut f = Fixture::striped(8);
22966        let other = apart(&mut f, "s1");
22967        let (s1, far) = (b"s1".as_slice(), other.as_bytes());
22968        f.run(&[b"XADD", s1, b"1-1", b"a", b"1"]);
22969        f.run(&[b"XADD", far, b"1-1", b"a", b"1"]);
22970
22971        assert_eq!(
22972            f.flow(&[b"XREAD", b"BLOCK", b"0", b"STREAMS", s1, far, b"$", b"$"])
22973                .0,
22974            Flow::Block
22975        );
22976        f.run(&[b"XADD", far, b"2-1", b"b", b"2"]);
22977        let mut out = Out::new(Proto::Resp2);
22978        assert!(f.server.serve_waiter(7, 0, &mut out));
22979        let want = format!(
22980            "*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",
22981            other.len()
22982        );
22983        assert_eq!(core::str::from_utf8(out.as_slice()).expect("ascii"), want);
22984    }
22985
22986    /// Every JSON command, on one stripe and on eight.
22987    #[test]
22988    fn the_json_group_answers_the_same_however_many_stripes_there_are() {
22989        let script: &[&[&[u8]]] = &[
22990            &[
22991                b"JSON.SET",
22992                b"d",
22993                b"$",
22994                br#"{"a":1,"b":[1,2,3],"s":"hi","t":true}"#,
22995            ],
22996            &[b"JSON.SET", b"d", b"$.a", b"2"],
22997            &[b"JSON.SET", b"d", b"$.new", b"9", b"NX"],
22998            &[b"JSON.SET", b"d", b"$.new", b"8", b"NX"],
22999            &[b"JSON.SET", b"d", b"$.nope", b"7", b"XX"],
23000            &[b"JSON.GET", b"d"],
23001            &[b"JSON.GET", b"d", b"$.b"],
23002            &[b"JSON.GET", b"gone", b"$"],
23003            &[b"JSON.TYPE", b"d", b"$.b"],
23004            &[b"JSON.TYPE", b"d", b"$.s"],
23005            &[b"JSON.TOGGLE", b"d", b"$.t"],
23006            &[b"JSON.ARRLEN", b"d", b"$.b"],
23007            &[b"JSON.OBJLEN", b"d", b"$"],
23008            &[b"JSON.OBJKEYS", b"d", b"$"],
23009            &[b"JSON.STRLEN", b"d", b"$.s"],
23010            &[b"JSON.STRAPPEND", b"d", b"$.s", br#""there""#],
23011            &[b"JSON.ARRAPPEND", b"d", b"$.b", b"4"],
23012            &[b"JSON.ARRINSERT", b"d", b"$.b", b"0", b"0"],
23013            &[b"JSON.ARRINDEX", b"d", b"$.b", b"3"],
23014            &[b"JSON.ARRTRIM", b"d", b"$.b", b"1", b"3"],
23015            &[b"JSON.ARRPOP", b"d", b"$.b"],
23016            &[b"JSON.NUMINCRBY", b"d", b"$.a", b"5"],
23017            &[b"JSON.NUMMULTBY", b"d", b"$.a", b"2"],
23018            &[b"JSON.NUMPOWBY", b"d", b"$.a", b"2"],
23019            &[b"JSON.MERGE", b"d", b"$", br#"{"a":null,"m":1}"#],
23020            &[b"JSON.RESP", b"d", b"$.b"],
23021            &[b"JSON.DEBUG", b"MEMORY", b"d"],
23022            &[b"JSON.CLEAR", b"d", b"$.b"],
23023            &[b"JSON.DEL", b"d", b"$.m"],
23024            &[b"JSON.FORGET", b"d", b"$.nothere"],
23025            // The two that name more than one key.
23026            &[
23027                b"JSON.MSET",
23028                b"m1",
23029                b"$",
23030                b"1",
23031                b"m2",
23032                b"$",
23033                b"2",
23034                b"m3",
23035                b"$",
23036                b"3",
23037            ],
23038            &[b"JSON.MGET", b"m1", b"m2", b"m3", b"gone", b"$"],
23039            &[b"JSON.MSET", b"m1", b"$", b"9", b"m2", b"$.deep", b"9"],
23040            &[b"JSON.GET", b"m1", b"$"],
23041            &[b"JSON.MSET", b"m1", b"$", b"nonsense", b"m2", b"$", b"5"],
23042            &[b"JSON.GET", b"m2", b"$"],
23043            // And the errors.
23044            &[b"SET", b"plain", b"v"],
23045            &[b"JSON.GET", b"plain", b"$"],
23046            &[b"JSON.SET", b"plain", b"$", b"1"],
23047            &[b"JSON.MGET", b"m1", b"plain", b"$"],
23048            &[b"JSON.SET", b"d", b"$.b", b"["],
23049            &[b"JSON.DEL", b"plain"],
23050        ];
23051
23052        let mut one = Fixture::new();
23053        let mut many = Fixture::striped(8);
23054        for parts in script {
23055            let a = one.run(parts);
23056            let b = many.run(parts);
23057            assert_eq!(a, b, "{}", String::from_utf8_lossy(parts[0]));
23058        }
23059    }
23060
23061    /// A `JSON.MSET` and a `JSON.MGET` whose keys are on several stripes.
23062    ///
23063    /// `JSON.MSET` works every triple out against the keyspace as it was before
23064    /// the command and writes nothing until all of them are known to work, so
23065    /// the thing to check is that a triple that cannot be written stops the
23066    /// ones on other stripes as well as the ones on its own.
23067    #[test]
23068    fn a_json_multi_write_across_stripes_reaches_every_key() {
23069        let mut f = Fixture::striped(8);
23070        let second = apart(&mut f, "m1");
23071        let third = apart(&mut f, &second);
23072        let (m1, m2, m3) = (b"m1".as_slice(), second.as_bytes(), third.as_bytes());
23073
23074        assert_eq!(
23075            f.run(&[b"JSON.MSET", m1, b"$", b"1", m2, b"$", b"2", m3, b"$", b"3"]),
23076            "+OK\r\n"
23077        );
23078        assert_eq!(
23079            f.run(&[b"JSON.MGET", m1, m2, m3, b"gone", b"$"]),
23080            "*4\r\n$3\r\n[1]\r\n$3\r\n[2]\r\n$3\r\n[3]\r\n$-1\r\n"
23081        );
23082
23083        // A value that is not JSON is refused before anything is written, and
23084        // the key on the far stripe keeps what it had.
23085        assert_eq!(
23086            f.run(&[b"JSON.MSET", m1, b"$", b"9", m2, b"$", b"nonsense"]),
23087            "-this is not the start of a value, at byte 0 of the JSON text\r\n"
23088        );
23089        assert_eq!(f.run(&[b"JSON.GET", m1, b"$"]), "$3\r\n[1]\r\n");
23090
23091        // A path that names nowhere is not an error. That triple is skipped,
23092        // the ones on the other stripes are still written, and the reply is a
23093        // nil rather than OK.
23094        assert_eq!(
23095            f.run(&[
23096                b"JSON.MSET",
23097                m1,
23098                b"$",
23099                b"9",
23100                m2,
23101                b"$.deep",
23102                b"9",
23103                m3,
23104                b"$",
23105                b"7"
23106            ]),
23107            "$-1\r\n"
23108        );
23109        assert_eq!(f.run(&[b"JSON.GET", m1, b"$"]), "$3\r\n[9]\r\n");
23110        assert_eq!(f.run(&[b"JSON.GET", m2, b"$"]), "$3\r\n[2]\r\n");
23111        assert_eq!(f.run(&[b"JSON.GET", m3, b"$"]), "$3\r\n[7]\r\n");
23112    }
23113
23114    /// Every geospatial command, on one stripe and on eight.
23115    #[test]
23116    fn the_geo_group_answers_the_same_however_many_stripes_there_are() {
23117        let script: &[&[&[u8]]] = &[
23118            &[
23119                b"GEOADD",
23120                b"g",
23121                b"13.361389",
23122                b"38.115556",
23123                b"palermo",
23124                b"15.087269",
23125                b"37.502669",
23126                b"catania",
23127            ],
23128            &[
23129                b"GEOADD",
23130                b"g",
23131                b"NX",
23132                b"13.361389",
23133                b"38.115556",
23134                b"palermo",
23135            ],
23136            &[b"GEOADD", b"g", b"XX", b"CH", b"13.4", b"38.1", b"palermo"],
23137            &[b"GEOPOS", b"g", b"palermo", b"nothere"],
23138            &[b"GEOHASH", b"g", b"palermo", b"catania"],
23139            &[b"GEODIST", b"g", b"palermo", b"catania"],
23140            &[b"GEODIST", b"g", b"palermo", b"catania", b"KM"],
23141            &[b"GEODIST", b"g", b"palermo", b"nothere"],
23142            &[
23143                b"GEOSEARCH",
23144                b"g",
23145                b"FROMLONLAT",
23146                b"15",
23147                b"37",
23148                b"BYRADIUS",
23149                b"200",
23150                b"KM",
23151                b"ASC",
23152                b"WITHCOORD",
23153                b"WITHDIST",
23154                b"WITHHASH",
23155            ],
23156            &[
23157                b"GEOSEARCH",
23158                b"g",
23159                b"FROMMEMBER",
23160                b"palermo",
23161                b"BYBOX",
23162                b"400",
23163                b"400",
23164                b"KM",
23165                b"DESC",
23166            ],
23167            &[
23168                b"GEORADIUS",
23169                b"g",
23170                b"15",
23171                b"37",
23172                b"200",
23173                b"KM",
23174                b"COUNT",
23175                b"1",
23176            ],
23177            &[b"GEORADIUSBYMEMBER", b"g", b"palermo", b"200", b"KM"],
23178            &[b"GEORADIUSBYMEMBER_RO", b"g", b"nothere", b"200", b"KM"],
23179            &[
23180                b"GEOSEARCHSTORE",
23181                b"dst",
23182                b"g",
23183                b"FROMLONLAT",
23184                b"15",
23185                b"37",
23186                b"BYRADIUS",
23187                b"200",
23188                b"KM",
23189            ],
23190            &[b"ZRANGE", b"dst", b"0", b"-1"],
23191            &[
23192                b"GEOSEARCHSTORE",
23193                b"dst",
23194                b"g",
23195                b"FROMLONLAT",
23196                b"15",
23197                b"37",
23198                b"BYRADIUS",
23199                b"1",
23200                b"M",
23201                b"STOREDIST",
23202            ],
23203            &[b"EXISTS", b"dst"],
23204            &[
23205                b"GEORADIUS",
23206                b"g",
23207                b"15",
23208                b"37",
23209                b"200",
23210                b"KM",
23211                b"STORE",
23212                b"dst",
23213            ],
23214            &[b"ZCARD", b"dst"],
23215            // And the errors.
23216            &[b"GEOADD", b"g", b"181", b"38", b"nowhere"],
23217            &[b"SET", b"plain", b"v"],
23218            &[b"GEOPOS", b"plain", b"a"],
23219            &[b"GEOSEARCH", b"g", b"FROMLONLAT", b"15", b"37"],
23220            &[
23221                b"GEOSEARCHSTORE",
23222                b"dst",
23223                b"g",
23224                b"FROMLONLAT",
23225                b"15",
23226                b"37",
23227                b"BYRADIUS",
23228                b"200",
23229                b"KM",
23230                b"WITHCOORD",
23231            ],
23232        ];
23233
23234        let mut one = Fixture::new();
23235        let mut many = Fixture::striped(8);
23236        for parts in script {
23237            let a = one.run(parts);
23238            let b = many.run(parts);
23239            assert_eq!(a, b, "{}", String::from_utf8_lossy(parts[0]));
23240        }
23241    }
23242
23243    /// A `GEOSEARCHSTORE` whose two keys are on two stripes.
23244    #[test]
23245    fn a_geo_search_store_across_stripes_writes_what_it_found() {
23246        let mut f = Fixture::striped(8);
23247        let other = apart(&mut f, "g");
23248        let third = apart(&mut f, &other);
23249        let (g, dst, plain) = (b"g".as_slice(), other.as_bytes(), third.as_bytes());
23250
23251        f.run(&[
23252            b"GEOADD",
23253            g,
23254            b"13.361389",
23255            b"38.115556",
23256            b"palermo",
23257            b"15.087269",
23258            b"37.502669",
23259            b"catania",
23260        ]);
23261        assert_eq!(
23262            f.run(&[
23263                b"GEOSEARCHSTORE",
23264                dst,
23265                g,
23266                b"FROMLONLAT",
23267                b"15",
23268                b"37",
23269                b"BYRADIUS",
23270                b"200",
23271                b"KM",
23272                b"ASC",
23273            ]),
23274            ":2\r\n"
23275        );
23276        assert_eq!(
23277            f.run(&[b"ZRANGE", dst, b"0", b"-1"]),
23278            "*2\r\n$7\r\npalermo\r\n$7\r\ncatania\r\n",
23279            "the geohash is the score, so the order is not the search order"
23280        );
23281        assert_eq!(f.run(&[b"ZCARD", g]), ":2\r\n", "the source is untouched");
23282
23283        // `STOREDIST` stores the distance in the unit the search was asked in,
23284        // which is the destination stripe's sorted set and not the source's.
23285        assert_eq!(
23286            f.run(&[
23287                b"GEOSEARCHSTORE",
23288                dst,
23289                g,
23290                b"FROMMEMBER",
23291                b"palermo",
23292                b"BYRADIUS",
23293                b"200",
23294                b"KM",
23295                b"STOREDIST",
23296            ]),
23297            ":2\r\n"
23298        );
23299        assert_eq!(
23300            f.run(&[b"ZSCORE", dst, b"palermo"]),
23301            "$1\r\n0\r\n",
23302            "the centre is nought away from itself"
23303        );
23304
23305        // A search that found nothing deletes the destination on its own
23306        // stripe, and a source of the wrong type is refused with the
23307        // destination left alone.
23308        assert_eq!(
23309            f.run(&[
23310                b"GEOSEARCHSTORE",
23311                dst,
23312                g,
23313                b"FROMLONLAT",
23314                b"0",
23315                b"0",
23316                b"BYRADIUS",
23317                b"1",
23318                b"M",
23319            ]),
23320            ":0\r\n"
23321        );
23322        assert_eq!(f.run(&[b"EXISTS", dst]), ":0\r\n");
23323        f.run(&[
23324            b"GEOSEARCHSTORE",
23325            dst,
23326            g,
23327            b"FROMLONLAT",
23328            b"15",
23329            b"37",
23330            b"BYRADIUS",
23331            b"200",
23332            b"KM",
23333        ]);
23334        f.run(&[b"SET", plain, b"v"]);
23335        assert_eq!(
23336            f.run(&[
23337                b"GEOSEARCHSTORE",
23338                dst,
23339                plain,
23340                b"FROMLONLAT",
23341                b"15",
23342                b"37",
23343                b"BYRADIUS",
23344                b"200",
23345                b"KM",
23346            ]),
23347            "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n"
23348        );
23349        assert_eq!(
23350            f.run(&[b"ZCARD", dst]),
23351            ":2\r\n",
23352            "and left the destination"
23353        );
23354    }
23355
23356    /// Every time series command, on one stripe and on eight.
23357    ///
23358    /// Every timestamp is written out rather than left to the clock, so the two
23359    /// servers are compared on the samples they hold and not on how long the
23360    /// test took to get from one of them to the other.
23361    #[test]
23362    fn the_time_series_group_answers_the_same_however_many_stripes_there_are() {
23363        let script: &[&[&[u8]]] = &[
23364            &[
23365                b"TS.CREATE",
23366                b"ts:a",
23367                b"LABELS",
23368                b"sensor",
23369                b"a",
23370                b"room",
23371                b"1",
23372            ],
23373            &[b"TS.CREATE", b"ts:a"],
23374            &[b"TS.ALTER", b"ts:a", b"RETENTION", b"0"],
23375            &[b"TS.ADD", b"ts:a", b"1000", b"1.5"],
23376            &[
23377                b"TS.ADD", b"ts:b", b"1000", b"2", b"LABELS", b"sensor", b"b", b"room", b"1",
23378            ],
23379            &[
23380                b"TS.MADD", b"ts:a", b"2000", b"2.5", b"ts:b", b"2000", b"3", b"gone", b"1", b"1",
23381            ],
23382            &[b"TS.INCRBY", b"ts:a", b"1", b"TIMESTAMP", b"3000"],
23383            &[b"TS.DECRBY", b"ts:a", b"0.5", b"TIMESTAMP", b"4000"],
23384            &[b"TS.GET", b"ts:a"],
23385            &[b"TS.GET", b"gone"],
23386            &[b"TS.RANGE", b"ts:a", b"-", b"+"],
23387            &[b"TS.RANGE", b"ts:a", b"1000", b"3000", b"COUNT", b"2"],
23388            &[
23389                b"TS.RANGE",
23390                b"ts:a",
23391                b"-",
23392                b"+",
23393                b"AGGREGATION",
23394                b"avg",
23395                b"2000",
23396            ],
23397            &[b"TS.REVRANGE", b"ts:a", b"-", b"+"],
23398            &[b"TS.NRANGE", b"2", b"ts:a", b"ts:b", b"-", b"+"],
23399            &[b"TS.NREVRANGE", b"2", b"ts:a", b"ts:b", b"-", b"+"],
23400            &[b"TS.NRANGE", b"2", b"ts:a", b"gone", b"-", b"+"],
23401            &[b"TS.READ", b"ts:a", b"0"],
23402            &[b"TS.READ", b"ts:a", b"+"],
23403            // The filters, which are the ones that have to walk every stripe.
23404            &[b"TS.QUERYINDEX", b"sensor=a"],
23405            &[b"TS.QUERYINDEX", b"room=1"],
23406            &[b"TS.QUERYINDEX", b"room=9"],
23407            &[b"TS.QUERYLABELS", b"LABELS", b"FILTER", b"room=1"],
23408            &[
23409                b"TS.QUERYLABELS",
23410                b"VALUES",
23411                b"sensor",
23412                b"FILTER",
23413                b"room=1",
23414            ],
23415            &[b"TS.MGET", b"WITHLABELS", b"FILTER", b"room=1"],
23416            &[
23417                b"TS.MGET",
23418                b"SELECTED_LABELS",
23419                b"sensor",
23420                b"FILTER",
23421                b"sensor=a",
23422            ],
23423            &[b"TS.MRANGE", b"-", b"+", b"FILTER", b"room=1"],
23424            &[
23425                b"TS.MREVRANGE",
23426                b"-",
23427                b"+",
23428                b"WITHLABELS",
23429                b"FILTER",
23430                b"sensor=a",
23431            ],
23432            &[
23433                b"TS.MRANGE",
23434                b"-",
23435                b"+",
23436                b"FILTER",
23437                b"room=1",
23438                b"GROUPBY",
23439                b"room",
23440                b"REDUCE",
23441                b"max",
23442            ],
23443            &[b"TS.INFO", b"ts:a"],
23444            // And a rule, which is the one thing here that names two keys.
23445            &[
23446                b"TS.CREATERULE",
23447                b"ts:a",
23448                b"ts:down",
23449                b"AGGREGATION",
23450                b"avg",
23451                b"1000",
23452            ],
23453            &[b"TS.CREATE", b"ts:down"],
23454            &[
23455                b"TS.CREATERULE",
23456                b"ts:a",
23457                b"ts:down",
23458                b"AGGREGATION",
23459                b"avg",
23460                b"1000",
23461            ],
23462            &[b"TS.ADD", b"ts:a", b"5000", b"4"],
23463            &[b"TS.ADD", b"ts:a", b"6000", b"5"],
23464            &[b"TS.RANGE", b"ts:down", b"-", b"+"],
23465            &[b"TS.GET", b"ts:down", b"LATEST"],
23466            &[b"TS.INFO", b"ts:down"],
23467            &[b"TS.DEL", b"ts:a", b"5000", b"6000"],
23468            &[b"TS.RANGE", b"ts:down", b"-", b"+"],
23469            &[b"TS.DELETERULE", b"ts:a", b"ts:down"],
23470            &[b"TS.DELETERULE", b"ts:a", b"ts:down"],
23471            &[b"TS.DEL", b"ts:a", b"0", b"1000"],
23472            // And the errors.
23473            &[b"SET", b"plain", b"v"],
23474            &[b"TS.ADD", b"plain", b"1", b"1"],
23475            &[b"TS.GET", b"plain"],
23476            &[b"TS.READ", b"plain", b"0"],
23477            &[b"TS.ALTER", b"gone", b"RETENTION", b"0"],
23478            &[b"TS.RANGE", b"gone", b"-", b"+"],
23479            &[b"TS.INFO", b"gone"],
23480        ];
23481
23482        let mut one = Fixture::new();
23483        let mut many = Fixture::striped(8);
23484        for parts in script {
23485            let a = one.run(parts);
23486            let b = many.run(parts);
23487            assert_eq!(a, b, "{}", String::from_utf8_lossy(parts[0]));
23488        }
23489    }
23490
23491    /// A compaction rule whose two ends are on two stripes.
23492    ///
23493    /// This is the one thing in the family that walks from a key to another key,
23494    /// and it walks it in both directions: a sample on the source closes a
23495    /// bucket on the destination, a `LATEST` read on the destination folds the
23496    /// bucket the source is still filling, and a delete on the source rewrites
23497    /// what the destination already held. The same script is run against a
23498    /// server one stripe wide, where the two keys share a store, and against one
23499    /// eight stripes wide, where they do not.
23500    #[test]
23501    fn a_compaction_rule_across_stripes_reaches_both_ends() {
23502        let mut many = Fixture::striped(8);
23503        let other = apart(&mut many, "src");
23504        let (src, dst) = (b"src".as_slice(), other.as_bytes());
23505        let mut one = Fixture::new();
23506        let mut both = |parts: &[&[u8]]| {
23507            let a = one.run(parts);
23508            let b = many.run(parts);
23509            assert_eq!(a, b, "{}", String::from_utf8_lossy(parts[0]));
23510            a
23511        };
23512
23513        both(&[b"TS.CREATE", src]);
23514        both(&[b"TS.CREATE", dst]);
23515        assert_eq!(
23516            both(&[b"TS.CREATERULE", src, dst, b"AGGREGATION", b"avg", b"1000"]),
23517            "+OK\r\n"
23518        );
23519        both(&[b"TS.ADD", src, b"1000", b"1"]);
23520        both(&[b"TS.ADD", src, b"1500", b"3"]);
23521        // The bucket the source is filling is not written down yet, and asking
23522        // for it works it out off the source.
23523        assert_eq!(both(&[b"TS.RANGE", dst, b"-", b"+"]), "*0\r\n");
23524        let open = both(&[b"TS.GET", dst, b"LATEST"]);
23525        assert!(open.contains(":1000"), "the open bucket is folded: {open}");
23526
23527        // A sample past the bucket closes it, which is the write that has to
23528        // land on the other stripe.
23529        both(&[b"TS.ADD", src, b"2000", b"5"]);
23530        let got = both(&[b"TS.RANGE", dst, b"-", b"+"]);
23531        assert!(got.starts_with("*1\r\n"), "the bucket was written: {got}");
23532        assert!(got.contains(":1000"), "{got}");
23533
23534        // And a delete on the source takes it away again.
23535        both(&[b"TS.DEL", src, b"1000", b"1999"]);
23536        assert_eq!(both(&[b"TS.RANGE", dst, b"-", b"+"]), "*0\r\n");
23537
23538        // Both ends still know about each other, and the link comes apart from
23539        // the source.
23540        assert!(
23541            both(&[b"TS.INFO", dst]).contains("src"),
23542            "the source is named"
23543        );
23544        assert_eq!(both(&[b"TS.DELETERULE", src, dst]), "+OK\r\n");
23545        assert_eq!(
23546            both(&[b"TS.DELETERULE", src, dst]),
23547            "-ERR TSDB: compaction rule does not exist\r\n"
23548        );
23549    }
23550
23551    /// A label filter takes the series it names wherever they landed.
23552    #[test]
23553    fn a_label_query_across_stripes_finds_every_series() {
23554        let names: [&[u8]; 6] = [b"q:1", b"q:2", b"q:3", b"q:4", b"q:5", b"q:6"];
23555        let mut many = Fixture::striped(8);
23556        let mut homes: Vec<usize> = names
23557            .iter()
23558            .map(|name| many.server.striped(0).stripe_of(name))
23559            .collect();
23560        homes.sort_unstable();
23561        homes.dedup();
23562        assert!(homes.len() > 1, "the six keys are not all on one stripe");
23563
23564        let mut one = Fixture::new();
23565        let mut both = |parts: &[&[u8]]| {
23566            let a = one.run(parts);
23567            let b = many.run(parts);
23568            assert_eq!(a, b, "{}", String::from_utf8_lossy(parts[0]));
23569            a
23570        };
23571        for name in &names {
23572            both(&[b"TS.CREATE", name, b"LABELS", b"room", b"1"]);
23573            both(&[b"TS.ADD", name, b"1000", b"1"]);
23574        }
23575
23576        let got = both(&[b"TS.QUERYINDEX", b"room=1"]);
23577        assert!(got.starts_with("*6\r\n"), "every series answered: {got}");
23578        assert!(both(&[b"TS.MGET", b"FILTER", b"room=1"]).starts_with("*6\r\n"));
23579        assert!(both(&[b"TS.MRANGE", b"-", b"+", b"FILTER", b"room=1"]).starts_with("*6\r\n"));
23580        assert_eq!(
23581            both(&[b"TS.QUERYLABELS", b"LABELS", b"FILTER", b"room=1"]),
23582            "*1\r\n$4\r\nroom\r\n"
23583        );
23584    }
23585
23586    /// Every hash command, and the field import beside it, on one stripe and on
23587    /// eight.
23588    ///
23589    /// `HRANDFIELD` with a count draws from the stripe's own generator and two
23590    /// stripes do not draw the same numbers, so the only draw here is off a hash
23591    /// holding one field, where every generator gives the same answer.
23592    #[test]
23593    fn the_hash_group_answers_the_same_however_many_stripes_there_are() {
23594        let script: &[&[&[u8]]] = &[
23595            &[b"HSET", b"h", b"a", b"1", b"b", b"2"],
23596            &[b"HMSET", b"h", b"c", b"3"],
23597            &[b"HSETNX", b"h", b"a", b"9"],
23598            &[b"HSETNX", b"h", b"d", b"4"],
23599            &[b"HGET", b"h", b"a"],
23600            &[b"HGET", b"h", b"nope"],
23601            &[b"HMGET", b"h", b"a", b"nope"],
23602            &[b"HLEN", b"h"],
23603            &[b"HEXISTS", b"h", b"a"],
23604            &[b"HSTRLEN", b"h", b"a"],
23605            &[b"HGETALL", b"h"],
23606            &[b"HKEYS", b"h"],
23607            &[b"HVALS", b"h"],
23608            &[b"HINCRBY", b"h", b"a", b"5"],
23609            &[b"HINCRBYFLOAT", b"h", b"a", b"1.5"],
23610            &[b"HSCAN", b"h", b"0"],
23611            &[b"HSCAN", b"h", b"0", b"MATCH", b"a", b"COUNT", b"10"],
23612            &[b"HSCAN", b"h", b"0", b"NOVALUES"],
23613            &[b"HDEL", b"h", b"d"],
23614            &[b"HSET", b"one", b"f", b"v"],
23615            &[b"HRANDFIELD", b"one"],
23616            &[b"HRANDFIELD", b"one", b"1", b"WITHVALUES"],
23617            // The field deadlines.
23618            &[b"HEXPIRE", b"h", b"100", b"FIELDS", b"1", b"a"],
23619            &[b"HTTL", b"h", b"FIELDS", b"1", b"a"],
23620            &[b"HPTTL", b"h", b"FIELDS", b"1", b"a"],
23621            &[b"HEXPIRETIME", b"h", b"FIELDS", b"1", b"a"],
23622            &[b"HPEXPIRETIME", b"h", b"FIELDS", b"1", b"a"],
23623            &[b"HPERSIST", b"h", b"FIELDS", b"1", b"a"],
23624            &[b"HPEXPIREAT", b"h", b"1", b"FIELDS", b"1", b"b"],
23625            &[b"HGET", b"h", b"b"],
23626            // The three that came later and word everything their own way.
23627            &[b"HSETEX", b"h", b"EX", b"100", b"FIELDS", b"1", b"e", b"5"],
23628            &[b"HGETEX", b"h", b"PERSIST", b"FIELDS", b"1", b"e"],
23629            &[b"HGETDEL", b"h", b"FIELDS", b"1", b"e"],
23630            &[b"HGET", b"h", b"e"],
23631            // And the import, whose key is the third word.
23632            &[b"HIMPORT", b"PREPARE", b"fs", b"x", b"y"],
23633            &[b"HIMPORT", b"SET", b"imp", b"fs", b"1", b"2"],
23634            &[b"HGETALL", b"imp"],
23635            &[b"HIMPORT", b"SET", b"imp", b"nofs", b"1", b"2"],
23636            &[b"HIMPORT", b"DISCARD", b"fs"],
23637            // And the errors.
23638            &[b"SET", b"plain", b"v"],
23639            &[b"HSET", b"plain", b"a", b"1"],
23640            &[b"HGETALL", b"plain"],
23641            &[b"HGET", b"gone", b"a"],
23642            &[b"HINCRBY", b"h", b"a", b"nan"],
23643        ];
23644
23645        let mut one = Fixture::new();
23646        let mut many = Fixture::striped(8);
23647        // The field deadlines are absolute milliseconds worked out from the
23648        // clock, so both servers are put on the same one rather than left to
23649        // read the wall a moment apart.
23650        one.server.set_clock_ms(1_700_000_000_000);
23651        many.server.set_clock_ms(1_700_000_000_000);
23652        for parts in script {
23653            let a = one.run(parts);
23654            let b = many.run(parts);
23655            assert_eq!(a, b, "{}", String::from_utf8_lossy(parts[0]));
23656        }
23657    }
23658
23659    /// Every array command, on one stripe and on eight.
23660    #[test]
23661    fn the_array_group_answers_the_same_however_many_stripes_there_are() {
23662        let script: &[&[&[u8]]] = &[
23663            &[b"ARSET", b"a", b"0", b"x", b"y", b"z"],
23664            &[b"ARMSET", b"a", b"5", b"p", b"7", b"q"],
23665            &[b"ARGET", b"a", b"1"],
23666            &[b"ARGET", b"a", b"99"],
23667            &[b"ARMGET", b"a", b"0", b"5", b"99"],
23668            &[b"ARGETRANGE", b"a", b"0", b"7"],
23669            &[b"ARLEN", b"a"],
23670            &[b"ARCOUNT", b"a"],
23671            &[b"ARINSERT", b"a", b"m", b"n"],
23672            &[b"ARSCAN", b"a", b"0", b"20"],
23673            &[b"ARSCAN", b"a", b"0", b"20", b"LIMIT", b"2"],
23674            &[b"ARGREP", b"a", b"0", b"20", b"EXACT", b"x"],
23675            &[b"ARGREP", b"a", b"0", b"20", b"GLOB", b"*", b"WITHVALUES"],
23676            &[b"ARLASTITEMS", b"a", b"2"],
23677            &[b"ARLASTITEMS", b"a", b"2", b"REV"],
23678            &[b"ARNEXT", b"a"],
23679            &[b"ARSEEK", b"a", b"3"],
23680            &[b"AROP", b"a", b"0", b"20", b"USED"],
23681            &[b"AROP", b"a", b"0", b"20", b"MATCH", b"x"],
23682            &[b"ARINFO", b"a"],
23683            &[b"ARINFO", b"a", b"FULL"],
23684            &[b"ARDEL", b"a", b"0"],
23685            &[b"ARDELRANGE", b"a", b"1", b"2"],
23686            &[b"ARCOUNT", b"a"],
23687            &[b"ARRING", b"r", b"3", b"1", b"2", b"3", b"4"],
23688            &[b"ARGETRANGE", b"r", b"0", b"9"],
23689            // And the errors.
23690            &[b"SET", b"plain", b"v"],
23691            &[b"ARGET", b"plain", b"0"],
23692            &[b"ARSET", b"plain", b"0", b"v"],
23693            &[b"ARGET", b"gone", b"0"],
23694            &[b"ARSET", b"a", b"bad", b"v"],
23695        ];
23696
23697        let mut one = Fixture::new();
23698        let mut many = Fixture::striped(8);
23699        for parts in script {
23700            let a = one.run(parts);
23701            let b = many.run(parts);
23702            assert_eq!(a, b, "{}", String::from_utf8_lossy(parts[0]));
23703        }
23704    }
23705
23706    /// Every graph and vector set command, on one stripe and on eight.
23707    ///
23708    /// `VRANDMEMBER` is not in here for the reason `HRANDFIELD` with a count is
23709    /// not: it draws from the stripe's generator, and the stripes do not share
23710    /// one.
23711    #[test]
23712    fn the_graph_and_vector_groups_answer_the_same_however_many_stripes_there_are() {
23713        let script: &[&[&[u8]]] = &[
23714            &[b"G.NADD", b"g", b"n1", b"name", b"one"],
23715            &[b"G.NADD", b"g", b"n2", b"name", b"two"],
23716            &[b"G.NADD", b"g", b"n3"],
23717            &[b"G.NGET", b"g", b"n1"],
23718            &[b"G.NGET", b"g", b"gone"],
23719            &[b"G.EADD", b"g", b"n1", b"n2", b"knows"],
23720            &[b"G.EADD", b"g", b"n2", b"n3", b"knows"],
23721            &[b"G.OUT", b"g", b"n1", b"knows"],
23722            &[b"G.IN", b"g", b"n2", b"knows"],
23723            &[b"G.DEG", b"g", b"n1", b"knows"],
23724            &[b"G.DEG", b"g", b"n2", b"knows", b"BOTH"],
23725            &[b"G.NEIGH", b"g", b"n1", b"knows", b"DEPTH", b"2"],
23726            &[b"G.PATH", b"g", b"n1", b"n3"],
23727            &[b"G.EDEL", b"g", b"n1", b"n2", b"knows"],
23728            &[b"G.NDEL", b"g", b"n3"],
23729            &[b"G.NGET", b"g", b"n3"],
23730            // The vector set, which is one index under one key.
23731            &[b"VADD", b"v", b"VALUES", b"2", b"1", b"0", b"e1"],
23732            &[b"VADD", b"v", b"VALUES", b"2", b"0", b"1", b"e2"],
23733            &[b"VCARD", b"v"],
23734            &[b"VDIM", b"v"],
23735            &[b"VEMB", b"v", b"e1"],
23736            &[b"VSIM", b"v", b"VALUES", b"2", b"1", b"0"],
23737            &[b"VSIM", b"v", b"ELE", b"e1"],
23738            &[b"VISMEMBER", b"v", b"e1"],
23739            &[b"VISMEMBER", b"v", b"gone"],
23740            &[b"VSETATTR", b"v", b"e1", b"{\"k\":1}"],
23741            &[b"VGETATTR", b"v", b"e1"],
23742            &[b"VRANGE", b"v", b"-", b"+"],
23743            &[b"VLINKS", b"v", b"e1"],
23744            &[b"VINFO", b"v"],
23745            &[b"VREM", b"v", b"e2"],
23746            &[b"VCARD", b"v"],
23747            // And the errors.
23748            &[b"SET", b"plain", b"v"],
23749            &[b"G.NGET", b"plain", b"n1"],
23750            &[b"VCARD", b"plain"],
23751            &[b"G.NADD", b"gone2", b"n"],
23752            &[b"VEMB", b"gone3", b"e"],
23753        ];
23754
23755        let mut one = Fixture::new();
23756        let mut many = Fixture::striped(8);
23757        for parts in script {
23758            let a = one.run(parts);
23759            let b = many.run(parts);
23760            assert_eq!(a, b, "{}", String::from_utf8_lossy(parts[0]));
23761        }
23762    }
23763
23764    /// Every bloom filter, cuckoo filter, count min sketch, top k and t digest
23765    /// command, on one stripe and on eight.
23766    #[test]
23767    fn the_probabilistic_groups_answer_the_same_however_many_stripes_there_are() {
23768        let script: &[&[&[u8]]] = &[
23769            // The bloom filter.
23770            &[b"BF.RESERVE", b"bf", b"0.01", b"100"],
23771            &[b"BF.ADD", b"bf", b"a"],
23772            &[b"BF.ADD", b"bf", b"a"],
23773            &[b"BF.MADD", b"bf", b"b", b"c"],
23774            &[b"BF.EXISTS", b"bf", b"a"],
23775            &[b"BF.MEXISTS", b"bf", b"a", b"zz"],
23776            &[b"BF.CARD", b"bf"],
23777            &[b"BF.INFO", b"bf"],
23778            &[b"BF.INFO", b"bf", b"CAPACITY"],
23779            &[b"BF.DEBUG", b"bf"],
23780            &[b"BF.INSERT", b"made", b"CAPACITY", b"50", b"ITEMS", b"x"],
23781            &[b"BF.EXISTS", b"made", b"x"],
23782            &[b"BF.SCANDUMP", b"bf", b"0"],
23783            // The cuckoo filter.
23784            &[b"CF.RESERVE", b"cf", b"100"],
23785            &[b"CF.ADD", b"cf", b"a"],
23786            &[b"CF.ADDNX", b"cf", b"a"],
23787            &[b"CF.COUNT", b"cf", b"a"],
23788            &[b"CF.EXISTS", b"cf", b"a"],
23789            &[b"CF.MEXISTS", b"cf", b"a", b"zz"],
23790            &[b"CF.INSERT", b"cf", b"ITEMS", b"b", b"c"],
23791            &[b"CF.DEL", b"cf", b"a"],
23792            &[b"CF.COMPACT", b"cf"],
23793            &[b"CF.INFO", b"cf"],
23794            &[b"CF.DEBUG", b"cf"],
23795            &[b"CF.SCANDUMP", b"cf", b"0"],
23796            // The count min sketch.
23797            &[b"CMS.INITBYDIM", b"cms", b"100", b"5"],
23798            &[b"CMS.INITBYPROB", b"cms2", b"0.01", b"0.01"],
23799            &[b"CMS.INCRBY", b"cms", b"a", b"5", b"b", b"3"],
23800            &[b"CMS.QUERY", b"cms", b"a", b"b", b"gone"],
23801            &[b"CMS.INFO", b"cms"],
23802            // The top k sketch.
23803            &[b"TOPK.RESERVE", b"tk", b"3"],
23804            &[b"TOPK.ADD", b"tk", b"a", b"b", b"a"],
23805            &[b"TOPK.INCRBY", b"tk", b"c", b"4"],
23806            &[b"TOPK.QUERY", b"tk", b"a", b"zz"],
23807            &[b"TOPK.COUNT", b"tk", b"a", b"c"],
23808            &[b"TOPK.LIST", b"tk"],
23809            &[b"TOPK.LIST", b"tk", b"WITHCOUNT"],
23810            &[b"TOPK.INFO", b"tk"],
23811            // The t digest.
23812            &[b"TDIGEST.CREATE", b"td"],
23813            &[b"TDIGEST.ADD", b"td", b"1", b"2", b"3", b"4", b"5"],
23814            &[b"TDIGEST.MIN", b"td"],
23815            &[b"TDIGEST.MAX", b"td"],
23816            &[b"TDIGEST.QUANTILE", b"td", b"0.5"],
23817            &[b"TDIGEST.CDF", b"td", b"3"],
23818            &[b"TDIGEST.RANK", b"td", b"3"],
23819            &[b"TDIGEST.REVRANK", b"td", b"3"],
23820            &[b"TDIGEST.BYRANK", b"td", b"0"],
23821            &[b"TDIGEST.BYREVRANK", b"td", b"0"],
23822            &[b"TDIGEST.TRIMMED_MEAN", b"td", b"0.1", b"0.9"],
23823            &[b"TDIGEST.INFO", b"td"],
23824            &[b"TDIGEST.RESET", b"td"],
23825            &[b"TDIGEST.MIN", b"td"],
23826            // And the errors.
23827            &[b"SET", b"plain", b"v"],
23828            &[b"BF.ADD", b"plain", b"a"],
23829            &[b"CF.ADD", b"plain", b"a"],
23830            &[b"CMS.QUERY", b"plain", b"a"],
23831            &[b"TOPK.ADD", b"plain", b"a"],
23832            &[b"TDIGEST.ADD", b"plain", b"1"],
23833            &[b"CMS.INFO", b"gone"],
23834            &[b"TOPK.INFO", b"gone"],
23835            &[b"TDIGEST.INFO", b"gone"],
23836        ];
23837
23838        let mut one = Fixture::new();
23839        let mut many = Fixture::striped(8);
23840        for parts in script {
23841            let a = one.run(parts);
23842            let b = many.run(parts);
23843            assert_eq!(a, b, "{}", String::from_utf8_lossy(parts[0]));
23844        }
23845    }
23846
23847    /// The two sketch merges, with their sources on stripes of their own.
23848    ///
23849    /// These are the only two commands in the ten groups that name more than one
23850    /// key, and both read a run of sources and write a destination, so both go
23851    /// wrong in the same way if a merge holds one store and looks every source up
23852    /// in it.
23853    #[test]
23854    fn a_sketch_merge_across_stripes_reads_every_source() {
23855        let mut many = Fixture::striped(8);
23856        let other = apart(&mut many, "s1");
23857        let (s1, s2) = (b"s1".as_slice(), other.as_bytes());
23858        let mut one = Fixture::new();
23859        let mut both = |parts: &[&[u8]]| {
23860            let a = one.run(parts);
23861            let b = many.run(parts);
23862            assert_eq!(a, b, "{}", String::from_utf8_lossy(parts[0]));
23863            a
23864        };
23865
23866        // The count min sketch. The destination has to be the sources' shape,
23867        // and it is named first, so all three keys are read before anything is
23868        // written.
23869        for key in [b"cd".as_slice(), s1, s2] {
23870            both(&[b"CMS.INITBYDIM", key, b"100", b"5"]);
23871        }
23872        both(&[b"CMS.INCRBY", s1, b"x", b"5"]);
23873        both(&[b"CMS.INCRBY", s2, b"x", b"3"]);
23874        assert_eq!(
23875            both(&[b"CMS.MERGE", b"cd", b"2", s1, s2]),
23876            "+OK\r\n",
23877            "the merge took both sources"
23878        );
23879        assert_eq!(both(&[b"CMS.QUERY", b"cd", b"x"]), "*1\r\n:8\r\n");
23880        // And with weights, which are read against the sources in order.
23881        both(&[b"CMS.MERGE", b"cd", b"2", s1, s2, b"WEIGHTS", b"2", b"1"]);
23882        assert_eq!(both(&[b"CMS.QUERY", b"cd", b"x"]), "*1\r\n:13\r\n");
23883        // A source that is not a sketch is answered before anything is written.
23884        both(&[b"SET", b"plain", b"v"]);
23885        assert!(both(&[b"CMS.MERGE", b"cd", b"2", s1, b"plain"]).starts_with('-'));
23886        assert_eq!(both(&[b"CMS.QUERY", b"cd", b"x"]), "*1\r\n:13\r\n");
23887
23888        // The t digest, which builds its destination and then puts it in place.
23889        // The two source keys are used again here, so what they held goes first.
23890        both(&[b"FLUSHALL"]);
23891        both(&[b"TDIGEST.CREATE", b"td"]);
23892        both(&[b"TDIGEST.CREATE", s1]);
23893        both(&[b"TDIGEST.CREATE", s2]);
23894        both(&[b"TDIGEST.ADD", s1, b"1", b"2"]);
23895        both(&[b"TDIGEST.ADD", s2, b"9", b"10"]);
23896        assert_eq!(both(&[b"TDIGEST.MERGE", b"td", b"2", s1, s2]), "+OK\r\n");
23897        assert_eq!(both(&[b"TDIGEST.MIN", b"td"]), "$1\r\n1\r\n");
23898        assert_eq!(both(&[b"TDIGEST.MAX", b"td"]), "$2\r\n10\r\n");
23899    }
23900
23901    /// Every shape of `SORT`, on one stripe and on eight.
23902    ///
23903    /// The key it sorts, the keys a `BY` names, the keys a `GET` names and the
23904    /// destination are four different names and nothing lines them up, so on
23905    /// eight stripes this script is reading and writing all over the database
23906    /// while on one it is doing what it always did.
23907    #[test]
23908    fn the_sort_command_answers_the_same_however_many_stripes_there_are() {
23909        let script: &[&[&[u8]]] = &[
23910            &[b"RPUSH", b"l", b"3", b"1", b"2", b"10"],
23911            &[b"SORT", b"l"],
23912            &[b"SORT", b"l", b"DESC"],
23913            &[b"SORT", b"l", b"ALPHA"],
23914            &[b"SORT", b"l", b"LIMIT", b"1", b"2"],
23915            &[b"SORT_RO", b"l"],
23916            // A weight per element, so the order comes off keys the command
23917            // never named.
23918            &[
23919                b"MSET", b"w_1", b"4", b"w_2", b"3", b"w_3", b"2", b"w_10", b"1",
23920            ],
23921            &[b"SORT", b"l", b"BY", b"w_*"],
23922            &[b"SORT", b"l", b"BY", b"w_*", b"DESC"],
23923            &[b"DEL", b"w_2"],
23924            &[b"SORT", b"l", b"BY", b"w_*"],
23925            // And the answer off another set of keys again, with `#` mixed in
23926            // so the rows are not all lookups.
23927            &[b"MSET", b"d_1", b"one", b"d_3", b"three"],
23928            &[b"SORT", b"l", b"BY", b"w_*", b"GET", b"#", b"GET", b"d_*"],
23929            // A pattern that reaches into a hash, which is another key again.
23930            &[b"HSET", b"h_1", b"f", b"9"],
23931            &[b"HSET", b"h_2", b"f", b"8"],
23932            &[b"HSET", b"h_3", b"f", b"7"],
23933            &[b"HSET", b"h_10", b"f", b"6"],
23934            &[b"SORT", b"l", b"BY", b"h_*->f"],
23935            &[b"SORT", b"l", b"BY", b"nosort", b"GET", b"h_*->f"],
23936            // The destination, which is a fourth place to land.
23937            &[b"SORT", b"l", b"BY", b"w_*", b"STORE", b"out"],
23938            &[b"LRANGE", b"out", b"0", b"-1"],
23939            &[b"SORT", b"l", b"STORE", b"l"],
23940            &[b"LRANGE", b"l", b"0", b"-1"],
23941            // An empty result takes the destination away rather than leaving a
23942            // list of nothing behind.
23943            &[b"SORT", b"missing", b"STORE", b"out"],
23944            &[b"EXISTS", b"out"],
23945            // A set and a sorted set sort the same way a list does, and a set
23946            // written to a destination is sorted even when nothing asked.
23947            &[b"SADD", b"s", b"c", b"a", b"b"],
23948            &[b"SORT", b"s", b"ALPHA"],
23949            &[b"SORT", b"s", b"BY", b"nosort", b"STORE", b"out"],
23950            &[b"LRANGE", b"out", b"0", b"-1"],
23951            &[b"ZADD", b"z", b"3", b"c", b"1", b"a", b"2", b"b"],
23952            &[b"SORT", b"z", b"BY", b"nosort"],
23953            &[b"SORT", b"z", b"ALPHA", b"DESC"],
23954            // And the two ways it refuses: a key of the wrong type, and an
23955            // element that is not a number under a numeric sort.
23956            &[b"SET", b"str", b"v"],
23957            &[b"SORT", b"str"],
23958            &[b"RPUSH", b"words", b"one", b"two"],
23959            &[b"SORT", b"words"],
23960            &[b"SORT_RO", b"l", b"STORE", b"out"],
23961        ];
23962
23963        let mut one = Fixture::new();
23964        let mut many = Fixture::striped(8);
23965        for parts in script {
23966            let a = one.run(parts);
23967            let b = many.run(parts);
23968            assert_eq!(a, b, "{}", String::from_utf8_lossy(parts[0]));
23969        }
23970    }
23971
23972    /// One `SORT` whose four kinds of key are on stripes of their own.
23973    ///
23974    /// The script above spreads keys around by writing enough of them, and this
23975    /// one checks the spread rather than trusting it: the list, the weight key
23976    /// for one of its elements and the destination are asserted to be in three
23977    /// places before the command runs.
23978    #[test]
23979    fn a_sort_across_stripes_reads_every_pattern_key() {
23980        let mut f = Fixture::striped(8);
23981        let out = apart(&mut f, "l");
23982        let (list, dest) = (b"l".as_slice(), out.as_bytes());
23983
23984        f.run(&[b"RPUSH", list, b"a", b"b", b"c", b"d"]);
23985        f.run(&[
23986            b"MSET", b"w_a", b"4", b"w_b", b"3", b"w_c", b"2", b"w_d", b"1",
23987        ]);
23988        f.run(&[
23989            b"MSET", b"d_a", b"A", b"d_b", b"B", b"d_c", b"C", b"d_d", b"D",
23990        ]);
23991
23992        // The weights are four keys and they are not all in one place, which is
23993        // the thing that would go unnoticed if the command held a stripe.
23994        let db = f.server.striped(0);
23995        let weights: Vec<usize> = [b"w_a", b"w_b", b"w_c", b"w_d"]
23996            .iter()
23997            .map(|k| db.stripe_of(k.as_slice()))
23998            .collect();
23999        assert!(
24000            weights.iter().any(|s| *s != weights[0]),
24001            "the four weight keys all landed on one stripe, so this proves nothing"
24002        );
24003
24004        assert_eq!(
24005            f.run(&[b"SORT", list, b"BY", b"w_*", b"GET", b"d_*"]),
24006            "*4\r\n$1\r\nD\r\n$1\r\nC\r\n$1\r\nB\r\n$1\r\nA\r\n",
24007            "the order came off the weights and the answer off the data keys"
24008        );
24009        assert_eq!(
24010            f.run(&[b"SORT", list, b"BY", b"w_*", b"STORE", dest]),
24011            ":4\r\n"
24012        );
24013        assert_eq!(
24014            f.run(&[b"LRANGE", dest, b"0", b"-1"]),
24015            "*4\r\n$1\r\nd\r\n$1\r\nc\r\n$1\r\nb\r\n$1\r\na\r\n",
24016            "the destination is on a stripe of its own and got the whole answer"
24017        );
24018    }
24019
24020    /// A `CONFIG SET` reaches every stripe, so where a key landed does not
24021    /// decide what shape it is stored in.
24022    ///
24023    /// This is the setting that would go wrong quietly. A stripe that kept the
24024    /// old ladder would hold the same hash in a different encoding from the
24025    /// stripe next to it, and the only thing that would ever say so is
24026    /// `OBJECT ENCODING`, which is why the check is on that.
24027    #[test]
24028    fn a_setting_reaches_every_stripe_and_reads_back_from_any_of_them() {
24029        let mut f = Fixture::striped(8);
24030        let other = apart(&mut f, "h");
24031        let (first, second) = (b"h".as_slice(), other.as_bytes());
24032
24033        assert_eq!(
24034            f.run(&[b"CONFIG", b"SET", b"hash-max-listpack-entries", b"2"]),
24035            "+OK\r\n"
24036        );
24037        assert_eq!(
24038            f.run(&[b"CONFIG", b"GET", b"hash-max-listpack-entries"]),
24039            "*2\r\n$25\r\nhash-max-listpack-entries\r\n$1\r\n2\r\n",
24040            "the read comes off one stripe and has to answer for all of them"
24041        );
24042        for key in [first, second] {
24043            f.run(&[b"HSET", key, b"a", b"1", b"b", b"2"]);
24044            assert_eq!(
24045                f.run(&[b"OBJECT", b"ENCODING", key]),
24046                "$8\r\nlistpack\r\n",
24047                "two fields is still under the ladder"
24048            );
24049            f.run(&[b"HSET", key, b"c", b"3"]);
24050            assert_eq!(
24051                f.run(&[b"OBJECT", b"ENCODING", key]),
24052                "$9\r\nhashtable\r\n",
24053                "three fields is over it, on whichever stripe the key is on"
24054            );
24055        }
24056
24057        // And the policy, which every stripe has to agree about for the same
24058        // reason: an eviction draws from one stripe at a time.
24059        assert_eq!(
24060            f.run(&[b"CONFIG", b"SET", b"maxmemory-policy", b"allkeys-lru"]),
24061            "+OK\r\n"
24062        );
24063        let db = f.server.striped(0);
24064        assert!(
24065            (0..db.width()).all(|i| db.hold_stripe(i).policy().name() == "allkeys-lru"),
24066            "a stripe kept the old policy"
24067        );
24068    }
24069
24070    /// What an index holds, as the two numbers `FT.INFO` reports about it.
24071    ///
24072    /// Read off the registry rather than parsed back out of an `FT.INFO` reply,
24073    /// because the reply is thirty odd fields and these two are the ones the
24074    /// keyspace hook moves.
24075    fn held(f: &Fixture, name: &[u8]) -> (usize, u32) {
24076        let search = f.server.search.lock();
24077        let index = search.named(name).expect("the index is there");
24078        (index.held.docs.len(), index.held.docs.last())
24079    }
24080
24081    /// A hash written under an index's prefix reaches it, and one written
24082    /// outside the prefix does not.
24083    #[test]
24084    fn a_hash_that_is_written_reaches_the_index_that_follows_it() {
24085        let mut f = Fixture::new();
24086        f.run(&[
24087            b"FT.CREATE",
24088            b"ix",
24089            b"PREFIX",
24090            b"1",
24091            b"p:",
24092            b"SCHEMA",
24093            b"t",
24094            b"TEXT",
24095        ]);
24096        f.run(&[b"HSET", b"p:1", b"t", b"running dogs"]);
24097        assert_eq!(held(&f, b"ix"), (1, 1));
24098        f.run(&[b"HSET", b"other:1", b"t", b"running dogs"]);
24099        assert_eq!(held(&f, b"ix"), (1, 1));
24100
24101        // Every field of the key and not the one the command named, since a
24102        // document is read from nothing every time.
24103        f.run(&[b"HSET", b"p:1", b"u", b"beta"]);
24104        f.run(&[b"HDEL", b"p:1", b"u"]);
24105        assert_eq!(held(&f, b"ix"), (1, 3));
24106        let search = f.server.search.lock();
24107        let index = search.named(b"ix").expect("there");
24108        assert_eq!(index.held.docs.id(b"p:1"), Some(3));
24109    }
24110
24111    /// A fresh index reads the keys that were already there, and walks past a
24112    /// key of the wrong type without counting a failure.
24113    #[test]
24114    fn a_fresh_index_reads_the_keys_that_were_already_there() {
24115        let mut f = Fixture::new();
24116        f.run(&[b"HSET", b"p:1", b"t", b"alpha"]);
24117        f.run(&[b"SET", b"p:str", b"not a hash"]);
24118        f.run(&[b"HSET", b"q:1", b"t", b"beta"]);
24119        f.run(&[
24120            b"FT.CREATE",
24121            b"ix",
24122            b"PREFIX",
24123            b"1",
24124            b"p:",
24125            b"SCHEMA",
24126            b"t",
24127            b"TEXT",
24128        ]);
24129
24130        assert_eq!(held(&f, b"ix"), (1, 1));
24131        let search = f.server.search.lock();
24132        let index = search.named(b"ix").expect("there");
24133        assert_eq!(index.trouble.whole().failures(), 0);
24134    }
24135
24136    /// `SKIPINITIALSCAN` leaves what was there alone, and a later write to one
24137    /// of those keys still lands.
24138    #[test]
24139    fn an_index_that_skipped_the_scan_fills_up_on_the_next_write() {
24140        let mut f = Fixture::new();
24141        f.run(&[b"HSET", b"p:1", b"t", b"alpha"]);
24142        f.run(&[
24143            b"FT.CREATE",
24144            b"ix",
24145            b"PREFIX",
24146            b"1",
24147            b"p:",
24148            b"SKIPINITIALSCAN",
24149            b"SCHEMA",
24150            b"t",
24151            b"TEXT",
24152        ]);
24153        assert_eq!(held(&f, b"ix"), (0, 0));
24154        f.run(&[b"HSET", b"p:1", b"t", b"alpha"]);
24155        assert_eq!(held(&f, b"ix"), (1, 1));
24156    }
24157
24158    /// A command that changed nothing leaves the document where it was, which
24159    /// is not the same as a command that was not a write.
24160    ///
24161    /// All five of these were measured against 8.10.1. Writing the same value
24162    /// again moves the number and a deadline set for later does not, which is
24163    /// the pair that makes the rule "the fields are not what they were" rather
24164    /// than "this was a write".
24165    #[test]
24166    fn only_a_real_change_gives_the_document_a_new_number() {
24167        let mut f = Fixture::new();
24168        f.run(&[
24169            b"FT.CREATE",
24170            b"ix",
24171            b"PREFIX",
24172            b"1",
24173            b"p:",
24174            b"SCHEMA",
24175            b"t",
24176            b"TEXT",
24177        ]);
24178        f.run(&[b"HSET", b"p:1", b"t", b"alpha"]);
24179        assert_eq!(held(&f, b"ix"), (1, 1));
24180
24181        f.run(&[b"HSET", b"p:1", b"t", b"alpha"]);
24182        assert_eq!(held(&f, b"ix"), (1, 2), "the same value still rewrites");
24183
24184        for quiet in [
24185            vec![b"HSETNX".as_slice(), b"p:1", b"t", b"other"],
24186            vec![b"HDEL".as_slice(), b"p:1", b"nosuch"],
24187            vec![b"HGET".as_slice(), b"p:1", b"t"],
24188            vec![b"HGETALL".as_slice(), b"p:1"],
24189            vec![b"HEXPIRE".as_slice(), b"p:1", b"100", b"FIELDS", b"1", b"t"],
24190            vec![b"HPERSIST".as_slice(), b"p:1", b"FIELDS", b"1", b"t"],
24191            vec![
24192                b"HGETEX".as_slice(),
24193                b"p:1",
24194                b"EX",
24195                b"100",
24196                b"FIELDS",
24197                b"1",
24198                b"t",
24199            ],
24200            vec![b"HGETDEL".as_slice(), b"p:1", b"FIELDS", b"1", b"nosuch"],
24201        ] {
24202            f.run(&quiet);
24203            assert_eq!(held(&f, b"ix"), (1, 2), "{:?} moved the document", quiet[0]);
24204        }
24205
24206        // And the ones that do change something.
24207        f.run(&[b"HSET", b"p:2", b"n", b"1"]);
24208        f.run(&[b"HINCRBY", b"p:2", b"n", b"1"]);
24209        assert_eq!(held(&f, b"ix"), (2, 4));
24210        // A deadline that has already passed takes the field away, and taking
24211        // the last field away takes the key and the document with it. The
24212        // number still moves on the way past, because the field going and the
24213        // key going are two separate pieces of news and the first of them
24214        // writes the document one last time.
24215        f.run(&[b"HEXPIRE", b"p:2", b"0", b"FIELDS", b"1", b"n"]);
24216        assert_eq!(held(&f, b"ix"), (1, 5));
24217    }
24218
24219    /// The two ways of emptying a hash, which do not leave the same thing
24220    /// behind. `HDEL` of the last field spends no number and is counted as a
24221    /// refusal, and a deadline that has already passed spends one on a document
24222    /// nobody sees and is counted as nothing. Measured against 8.10.1 and not
24223    /// something anyone would guess.
24224    #[test]
24225    fn a_key_emptied_by_a_deadline_spends_a_number_and_one_emptied_by_hdel_does_not() {
24226        /// The index's own failure count.
24227        fn refused(f: &Fixture, name: &[u8]) -> u64 {
24228            let search = f.server.search.lock();
24229            let index = search.named(name).expect("the index is there");
24230            index.trouble.whole().failures()
24231        }
24232
24233        let mut f = Fixture::new();
24234        f.run(&[
24235            b"FT.CREATE",
24236            b"ix",
24237            b"PREFIX",
24238            b"1",
24239            b"p:",
24240            b"SCHEMA",
24241            b"t",
24242            b"TEXT",
24243        ]);
24244        f.run(&[b"HSET", b"p:1", b"t", b"alpha"]);
24245        assert_eq!(held(&f, b"ix"), (1, 1));
24246        f.run(&[b"HDEL", b"p:1", b"t"]);
24247        assert_eq!(
24248            held(&f, b"ix"),
24249            (0, 1),
24250            "HDEL of the last field spends none"
24251        );
24252        assert_eq!(refused(&f, b"ix"), 1, "and is counted as a refusal");
24253
24254        f.run(&[b"HSET", b"p:2", b"t", b"alpha"]);
24255        assert_eq!(held(&f, b"ix"), (1, 2));
24256        f.run(&[b"HEXPIRE", b"p:2", b"0", b"FIELDS", b"1", b"t"]);
24257        assert_eq!(held(&f, b"ix"), (0, 3), "a deadline spends one");
24258        assert_eq!(refused(&f, b"ix"), 1, "and is counted as nothing");
24259
24260        f.run(&[b"HSET", b"p:3", b"t", b"alpha"]);
24261        assert_eq!(held(&f, b"ix"), (1, 4));
24262        f.run(&[b"HGETDEL", b"p:3", b"FIELDS", b"1", b"t"]);
24263        assert_eq!(held(&f, b"ix"), (0, 5), "and so does HGETDEL");
24264
24265        // Two fields and one command is one rewrite and not two, whichever way
24266        // the fields go.
24267        f.run(&[b"HSET", b"p:4", b"t", b"alpha", b"u", b"beta"]);
24268        assert_eq!(held(&f, b"ix"), (1, 6));
24269        f.run(&[b"HEXPIRE", b"p:4", b"0", b"FIELDS", b"2", b"t", b"u"]);
24270        assert_eq!(held(&f, b"ix"), (0, 7));
24271        assert_eq!(refused(&f, b"ix"), 1);
24272    }
24273
24274    /// `HSETEX` with a deadline that has already passed is two pieces of news
24275    /// from one command, so the number moves twice and the value never reaches
24276    /// the index.
24277    #[test]
24278    fn a_field_written_already_past_its_deadline_moves_the_number_twice() {
24279        let mut f = Fixture::new();
24280        f.run(&[
24281            b"FT.CREATE",
24282            b"ix",
24283            b"PREFIX",
24284            b"1",
24285            b"p:",
24286            b"SCHEMA",
24287            b"t",
24288            b"TEXT",
24289            b"u",
24290            b"TEXT",
24291        ]);
24292        f.run(&[b"HSET", b"p:1", b"u", b"keepme"]);
24293        assert_eq!(held(&f, b"ix"), (1, 1));
24294        f.run(&[
24295            b"HSETEX", b"p:1", b"EXAT", b"1", b"FIELDS", b"1", b"t", b"zqx",
24296        ]);
24297        assert_eq!(
24298            held(&f, b"ix"),
24299            (1, 3),
24300            "the key lived and the field did not"
24301        );
24302
24303        // And the same when the key does not survive it.
24304        f.run(&[b"HSET", b"p:2", b"t", b"alpha"]);
24305        assert_eq!(held(&f, b"ix"), (2, 4));
24306        f.run(&[
24307            b"HSETEX", b"p:2", b"EXAT", b"1", b"FIELDS", b"1", b"t", b"zqx",
24308        ]);
24309        assert_eq!(held(&f, b"ix"), (1, 6));
24310    }
24311
24312    /// The number one key is indexed under, or `None` when it holds no
24313    /// document.
24314    fn number(f: &Fixture, name: &[u8], key: &[u8]) -> Option<u32> {
24315        let search = f.server.search.lock();
24316        let index = search.named(name).expect("the index is there");
24317        index.held.docs.id(key)
24318    }
24319
24320    /// An index over `p:` with one document under `p:1`, which is where four of
24321    /// the tests below start.
24322    fn indexed() -> Fixture {
24323        let mut f = Fixture::new();
24324        f.run(&[
24325            b"FT.CREATE",
24326            b"ix",
24327            b"PREFIX",
24328            b"1",
24329            b"p:",
24330            b"SCHEMA",
24331            b"t",
24332            b"TEXT",
24333        ]);
24334        f.run(&[b"HSET", b"p:1", b"t", b"alpha"]);
24335        f
24336    }
24337
24338    /// Every way a keyspace command takes a key away leaves no document behind,
24339    /// and none of them spends a number or is counted as a refusal.
24340    #[test]
24341    fn a_key_a_keyspace_command_takes_away_loses_its_document() {
24342        for take in [
24343            vec![b"DEL".as_slice(), b"p:1"],
24344            vec![b"UNLINK".as_slice(), b"p:1"],
24345            vec![b"PEXPIREAT".as_slice(), b"p:1", b"1"],
24346            vec![b"EXPIRE".as_slice(), b"p:1", b"-1"],
24347        ] {
24348            let mut f = indexed();
24349            assert_eq!(held(&f, b"ix"), (1, 1));
24350            f.run(&take);
24351            assert_eq!(held(&f, b"ix"), (0, 1), "{:?} left something", take[0]);
24352            let search = f.server.search.lock();
24353            let index = search.named(b"ix").expect("the index is there");
24354            assert_eq!(index.trouble.whole().failures(), 0, "{:?}", take[0]);
24355        }
24356
24357        // A deadline that has not passed yet is not one of them.
24358        let mut f = indexed();
24359        f.run(&[b"EXPIRE", b"p:1", b"1000"]);
24360        assert_eq!(held(&f, b"ix"), (1, 1));
24361        f.run(&[b"PERSIST", b"p:1"]);
24362        assert_eq!(held(&f, b"ix"), (1, 1));
24363    }
24364
24365    /// A rename inside the prefix keeps the number the document had, which is
24366    /// the one write on a followed key that does not spend one. Out of the
24367    /// prefix is an erase and into it is a fresh reading, both measured.
24368    #[test]
24369    fn a_rename_inside_the_prefix_keeps_the_number_the_document_had() {
24370        let mut f = indexed();
24371        f.run(&[b"RENAME", b"p:1", b"p:2"]);
24372        assert_eq!(held(&f, b"ix"), (1, 1), "nothing was read again");
24373        assert_eq!(number(&f, b"ix", b"p:2"), Some(1));
24374        assert_eq!(number(&f, b"ix", b"p:1"), None);
24375
24376        f.run(&[b"RENAME", b"p:2", b"q:1"]);
24377        assert_eq!(held(&f, b"ix"), (0, 1), "out of the prefix is an erase");
24378
24379        f.run(&[b"RENAME", b"q:1", b"p:3"]);
24380        assert_eq!(held(&f, b"ix"), (1, 2), "and into it is a reading");
24381        assert_eq!(number(&f, b"ix", b"p:3"), Some(2));
24382
24383        // `RENAMENX` goes the same way, and the one that answers zero changes
24384        // nothing.
24385        f.run(&[b"HSET", b"p:4", b"t", b"beta"]);
24386        assert_eq!(f.run(&[b"RENAMENX", b"p:3", b"p:4"]), ":0\r\n");
24387        assert_eq!(held(&f, b"ix"), (2, 3));
24388        f.run(&[b"RENAMENX", b"p:3", b"p:5"]);
24389        assert_eq!(number(&f, b"ix", b"p:5"), Some(2));
24390    }
24391
24392    /// A rename over a key that already had a document leaves one document and
24393    /// not two. A real server leaves both, and D-64 is that difference.
24394    #[test]
24395    fn a_rename_over_a_document_leaves_one_of_them() {
24396        let mut f = indexed();
24397        f.run(&[b"HSET", b"p:2", b"t", b"beta"]);
24398        assert_eq!(held(&f, b"ix"), (2, 2));
24399        f.run(&[b"RENAME", b"p:1", b"p:2"]);
24400        assert_eq!(held(&f, b"ix"), (1, 2));
24401        assert_eq!(number(&f, b"ix", b"p:2"), Some(1));
24402    }
24403
24404    /// A key that arrives under the prefix by being copied or restored is read
24405    /// as a new document, and one that is written over by something that is not
24406    /// a hash is erased without a word.
24407    #[test]
24408    fn a_key_that_arrives_under_the_prefix_is_read_and_one_overwritten_is_erased() {
24409        let mut f = indexed();
24410        f.run(&[b"HSET", b"q:1", b"t", b"beta"]);
24411        f.run(&[b"COPY", b"q:1", b"p:2"]);
24412        assert_eq!(held(&f, b"ix"), (2, 2));
24413        assert_eq!(number(&f, b"ix", b"p:2"), Some(2));
24414
24415        // Out of the prefix, where the source keeps the document it had.
24416        f.run(&[b"COPY", b"p:1", b"q:2"]);
24417        assert_eq!(held(&f, b"ix"), (2, 2));
24418
24419        // Over a key that has one, which is a new reading and not a rename.
24420        f.run(&[b"COPY", b"q:1", b"p:1", b"REPLACE"]);
24421        assert_eq!(held(&f, b"ix"), (2, 3));
24422        assert_eq!(number(&f, b"ix", b"p:1"), Some(3));
24423
24424        // And a string landing on top of a document takes it away, spending no
24425        // number and counting no failure.
24426        f.run(&[b"SET", b"s:1", b"plain"]);
24427        f.run(&[b"COPY", b"s:1", b"p:1", b"REPLACE"]);
24428        assert_eq!(held(&f, b"ix"), (1, 3));
24429        let dump = f.run(&[b"DUMP", b"q:1"]);
24430        assert!(dump.starts_with('$'), "{dump}");
24431    }
24432
24433    /// The keyspace group reads a key back on database zero whatever database
24434    /// the command ran on, which is measured and is not what the hash commands
24435    /// do. A `COPY` into another database indexes nothing and takes away
24436    /// whatever the destination had, and a `RESTORE` anywhere else is invisible.
24437    #[test]
24438    fn the_keyspace_group_reads_database_zero_whatever_database_it_ran_on() {
24439        let mut f = indexed();
24440        f.run(&[b"HSET", b"p:2", b"t", b"beta"]);
24441        assert_eq!(held(&f, b"ix"), (2, 2));
24442        // Into database one, so the indexes look for `p:2` on database zero,
24443        // find the one that is still there and read it again.
24444        f.run(&[b"COPY", b"p:1", b"p:2", b"DB", b"1", b"REPLACE"]);
24445        assert_eq!(held(&f, b"ix"), (2, 3));
24446        // And with nothing under that name on database zero, the copy leaves
24447        // the index one document lighter than it found it.
24448        f.run(&[b"DEL", b"p:2"]);
24449        assert_eq!(held(&f, b"ix"), (1, 3));
24450        f.run(&[b"COPY", b"p:1", b"p:2", b"DB", b"1", b"REPLACE"]);
24451        assert_eq!(held(&f, b"ix"), (1, 3), "the copy landed out of sight");
24452
24453        // A restore on another database is the same story.
24454        let dump = f.run(&[b"DUMP", b"p:1"]);
24455        assert!(dump.starts_with('$'), "{dump}");
24456        f.run(&[b"SELECT", b"1"]);
24457        f.run(&[b"HSET", b"q:1", b"t", b"gamma"]);
24458        f.run(&[b"RENAME", b"q:1", b"p:3"]);
24459        assert_eq!(held(&f, b"ix"), (1, 3), "and so is a rename");
24460    }
24461
24462    /// `MOVE` is not a change at all, because an index follows a key by name
24463    /// and a write on any database still reaches it.
24464    #[test]
24465    fn a_move_leaves_the_document_where_it_is() {
24466        let mut f = indexed();
24467        f.run(&[b"MOVE", b"p:1", b"1"]);
24468        assert_eq!(held(&f, b"ix"), (1, 1), "the key moved and nothing else");
24469        assert_eq!(number(&f, b"ix", b"p:1"), Some(1));
24470
24471        f.run(&[b"SELECT", b"1"]);
24472        f.run(&[b"HSET", b"p:1", b"t", b"beta"]);
24473        assert_eq!(held(&f, b"ix"), (1, 2), "and a write there still lands");
24474        f.run(&[b"DEL", b"p:1"]);
24475        assert_eq!(held(&f, b"ix"), (0, 2));
24476    }
24477
24478    /// A flush takes every index with it, whichever database it flushed.
24479    #[test]
24480    fn a_flush_drops_the_indexes() {
24481        for flush in [b"FLUSHALL".as_slice(), b"FLUSHDB"] {
24482            let mut f = indexed();
24483            f.run(&[flush]);
24484            assert!(f.server.search.lock().is_empty(), "{flush:?} kept an index");
24485            assert_eq!(f.run(&[b"FT._LIST"]), "*0\r\n");
24486        }
24487
24488        // Even on a database no index ever read, which is what a real server
24489        // does and is not what anyone would guess.
24490        let mut f = indexed();
24491        f.run(&[b"SELECT", b"9"]);
24492        f.run(&[b"FLUSHDB"]);
24493        assert!(f.server.search.lock().is_empty());
24494    }
24495
24496    /// An index whose schema has one tag field of each kind, plus a number so
24497    /// there is something for `FT.TAGVALS` to refuse.
24498    fn tagged() -> Fixture {
24499        let mut f = Fixture::new();
24500        f.run(&[
24501            b"FT.CREATE",
24502            b"tv",
24503            b"PREFIX",
24504            b"1",
24505            b"tv:",
24506            b"SCHEMA",
24507            b"g",
24508            b"AS",
24509            b"gg",
24510            b"TAG",
24511            b"h",
24512            b"TAG",
24513            b"SEPARATOR",
24514            b"|",
24515            b"CASESENSITIVE",
24516            b"n",
24517            b"NUMERIC",
24518        ]);
24519        f.run(&[
24520            b"HSET",
24521            b"tv:1",
24522            b"g",
24523            b"Red, BLUE ",
24524            b"h",
24525            b"Aa|bB",
24526            b"n",
24527            b"1",
24528        ]);
24529        f.run(&[b"HSET", b"tv:2", b"g", b"red", b"h", b"aa", b"n", b"2"]);
24530        f
24531    }
24532
24533    /// The values come back as they are stored, so an ordinary tag field
24534    /// answers them folded and trimmed and a `CASESENSITIVE` one answers what
24535    /// it was given. Byte order either way, which puts the capital first.
24536    #[test]
24537    fn tag_values_come_back_as_they_are_stored_and_sorted_by_their_bytes() {
24538        let mut f = tagged();
24539        assert_eq!(
24540            f.run(&[b"FT.TAGVALS", b"tv", b"gg"]),
24541            "*2\r\n$4\r\nblue\r\n$3\r\nred\r\n"
24542        );
24543        assert_eq!(
24544            f.run(&[b"FT.TAGVALS", b"tv", b"h"]),
24545            "*3\r\n$2\r\nAa\r\n$2\r\naa\r\n$2\r\nbB\r\n"
24546        );
24547    }
24548
24549    /// The name asked about is the attribute, so the identifier of a field
24550    /// declared `AS` is not a name this knows.
24551    #[test]
24552    fn tag_values_are_asked_for_by_the_attribute_and_not_the_identifier() {
24553        let mut f = tagged();
24554        for (name, want) in [
24555            (b"g".as_slice(), "-SEARCH_ATTR_BAD No such field\r\n"),
24556            (b"zz", "-SEARCH_ATTR_BAD No such field\r\n"),
24557            (b"n", "-SEARCH_ATTR_BAD Not a tag field\r\n"),
24558        ] {
24559            assert_eq!(f.run(&[b"FT.TAGVALS", b"tv", name]), want);
24560        }
24561        assert_eq!(
24562            f.run(&[b"FT.TAGVALS", b"nope", b"g"]),
24563            "-SEARCH_INDEX_NOT_FOUND Index not found: nope\r\n"
24564        );
24565    }
24566
24567    /// Looking up the index counts as a use of it on the roads that refuse the
24568    /// field as well as on the one that answers, which is measured.
24569    #[test]
24570    fn asking_for_tag_values_counts_a_use_of_the_index() {
24571        let mut f = tagged();
24572        let uses = |f: &mut Fixture| {
24573            let reply = f.run(&[b"FT.INFO", b"tv"]);
24574            let at = reply.find("number_of_uses").expect("the field is reported");
24575            let value = reply[at..].split("\r\n").nth(1).unwrap();
24576            value.trim_start_matches(':').parse::<i64>().unwrap()
24577        };
24578        let before = uses(&mut f);
24579        f.run(&[b"FT.TAGVALS", b"tv", b"gg"]);
24580        f.run(&[b"FT.TAGVALS", b"tv", b"zz"]);
24581        // Three more than before: two tag lookups and the second `FT.INFO`.
24582        assert_eq!(uses(&mut f), before + 3);
24583    }
24584
24585    /// A tag field nothing was ever written to has no list at all, which
24586    /// answers the same empty set a list that has been emptied does.
24587    #[test]
24588    fn a_tag_field_with_nothing_in_it_answers_empty() {
24589        let mut f = Fixture::new();
24590        f.run(&[b"FT.CREATE", b"e", b"SCHEMA", b"g", b"TAG"]);
24591        assert_eq!(f.run(&[b"FT.TAGVALS", b"e", b"g"]), "*0\r\n");
24592    }
24593
24594    /// A dictionary is module state and not a key, so nothing in the keyspace
24595    /// can see one.
24596    #[test]
24597    fn a_dictionary_is_not_a_key() {
24598        let mut f = Fixture::new();
24599        assert_eq!(f.run(&[b"FT.DICTADD", b"d", b"a", b"b"]), ":2\r\n");
24600        assert_eq!(f.run(&[b"TYPE", b"d"]), "+none\r\n");
24601        assert_eq!(f.run(&[b"EXISTS", b"d"]), ":0\r\n");
24602        assert_eq!(f.run(&[b"KEYS", b"d"]), "*0\r\n");
24603    }
24604
24605    /// The count is how many terms were new, an empty term is not a term, and
24606    /// the dump is sorted by bytes rather than folded.
24607    #[test]
24608    fn a_dictionary_counts_the_terms_it_had_not_seen() {
24609        let mut f = Fixture::new();
24610        assert_eq!(
24611            f.run(&[b"FT.DICTADD", b"d", b"zeta", b"alpha", b"Beta", b"alpha"]),
24612            ":3\r\n"
24613        );
24614        assert_eq!(f.run(&[b"FT.DICTADD", b"d", b"alpha"]), ":0\r\n");
24615        assert_eq!(f.run(&[b"FT.DICTADD", b"d", b""]), ":0\r\n");
24616        assert_eq!(
24617            f.run(&[b"FT.DICTDUMP", b"d"]),
24618            "*3\r\n$4\r\nBeta\r\n$5\r\nalpha\r\n$4\r\nzeta\r\n"
24619        );
24620        assert_eq!(f.run(&[b"FT.DICTDEL", b"d", b"alpha", b"nope"]), ":1\r\n");
24621    }
24622
24623    /// A name nobody ever added to is not an error on either of the two
24624    /// commands that will take one, which is the only place in the group where
24625    /// a missing name is forgiven.
24626    #[test]
24627    fn a_dictionary_nobody_made_dumps_empty_rather_than_failing() {
24628        let mut f = Fixture::new();
24629        assert_eq!(f.run(&[b"FT.DICTDUMP", b"nope"]), "*0\r\n");
24630        assert_eq!(f.run(&[b"FT.DICTDEL", b"nope", b"a"]), ":0\r\n");
24631    }
24632
24633    /// The dictionaries go when the keyspace does, the same way the indexes do.
24634    #[test]
24635    fn a_flush_drops_the_dictionaries() {
24636        for flush in [b"FLUSHALL".as_slice(), b"FLUSHDB"] {
24637            let mut f = Fixture::new();
24638            f.run(&[b"FT.DICTADD", b"d", b"a"]);
24639            f.run(&[flush]);
24640            assert_eq!(f.run(&[b"FT.DICTDUMP", b"d"]), "*0\r\n", "{flush:?}");
24641        }
24642    }
24643
24644    // -------------------------------------------------------------- profile
24645
24646    /// A fixture holding one index over three documents, two of which hold the
24647    /// first word and two the second.
24648    fn profiling() -> Fixture {
24649        let mut f = Fixture::new();
24650        f.run(&[
24651            b"FT.CREATE",
24652            b"ix",
24653            b"PREFIX",
24654            b"1",
24655            b"p:",
24656            b"SCHEMA",
24657            b"t",
24658            b"TEXT",
24659            b"n",
24660            b"NUMERIC",
24661        ]);
24662        f.run(&[b"HSET", b"p:1", b"t", b"alpha", b"n", b"1"]);
24663        f.run(&[b"HSET", b"p:2", b"t", b"alpha beta", b"n", b"2"]);
24664        f.run(&[b"HSET", b"p:3", b"t", b"beta", b"n", b"3"]);
24665        f
24666    }
24667
24668    /// The reply with every time taken out of it, since no two runs agree on
24669    /// those and everything else about a profile is exact.
24670    fn timeless(reply: &str) -> String {
24671        const KEYS: &[&str] = &[
24672            "+Total profile time",
24673            "+Parsing time",
24674            "+Workers queue time",
24675            "+Pipeline creation time",
24676            "+Time",
24677        ];
24678        let mut out = String::new();
24679        let mut parts = reply.split("\r\n").peekable();
24680        while let Some(part) = parts.next() {
24681            out.push_str(part);
24682            out.push_str("\r\n");
24683            if !KEYS.contains(&part) {
24684                continue;
24685            }
24686            // A double is one line on RESP3 and a bulk header and its digits on
24687            // RESP2, and both of them stand for the same one value.
24688            match parts.next() {
24689                Some(head) if head.starts_with('$') => {
24690                    parts.next();
24691                }
24692                _ => {}
24693            }
24694            out.push_str("<t>\r\n");
24695        }
24696        // The split leaves an empty piece past the last line ending.
24697        out.truncate(out.len() - 2);
24698        out
24699    }
24700
24701    /// The whole envelope on both protocols, which is a two element array on
24702    /// one and a two key map on the other.
24703    #[test]
24704    fn a_profile_wraps_the_reply_it_would_have_answered_anyway() {
24705        let mut f = profiling();
24706        assert_eq!(
24707            timeless(&f.run(&[b"FT.PROFILE", b"ix", b"SEARCH", b"QUERY", b"alpha"])),
24708            "*2\r\n\
24709             *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\
24710             $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\
24711             *4\r\n+Shards\r\n*1\r\n*14\r\n\
24712             +Total profile time\r\n<t>\r\n+Parsing time\r\n<t>\r\n\
24713             +Workers queue time\r\n<t>\r\n+Pipeline creation time\r\n<t>\r\n\
24714             +Warning\r\n*1\r\n+None\r\n\
24715             +Iterators profile\r\n*10\r\n+Type\r\n+TEXT\r\n+Term\r\n$5\r\nalpha\r\n\
24716             +Time\r\n<t>\r\n+Number of reading operations\r\n:2\r\n\
24717             +Estimated number of matches\r\n:2\r\n\
24718             +Result processors profile\r\n*4\r\n\
24719             *6\r\n+Type\r\n+Index\r\n+Time\r\n<t>\r\n+Results processed\r\n:2\r\n\
24720             *6\r\n+Type\r\n+Scorer\r\n+Time\r\n<t>\r\n+Results processed\r\n:2\r\n\
24721             *6\r\n+Type\r\n+Sorter\r\n+Time\r\n<t>\r\n+Results processed\r\n:2\r\n\
24722             *6\r\n+Type\r\n+Loader\r\n+Time\r\n<t>\r\n+Results processed\r\n:2\r\n\
24723             +Coordinator\r\n*0\r\n"
24724        );
24725        let mut g = profiling();
24726        g.run(&[b"HELLO", b"3"]);
24727        let three = timeless(&g.run(&[b"FT.PROFILE", b"ix", b"SEARCH", b"QUERY", b"alpha"]));
24728        assert!(three.starts_with("%2\r\n+Results\r\n"), "{three}");
24729        assert!(
24730            three.contains("+Profile\r\n%2\r\n+Shards\r\n*1\r\n%7\r\n"),
24731            "{three}"
24732        );
24733        assert!(three.ends_with("+Coordinator\r\n%0\r\n"), "{three}");
24734        assert!(
24735            three.contains(
24736                "+Iterators profile\r\n%5\r\n+Type\r\n+TEXT\r\n+Term\r\n$5\r\nalpha\r\n\
24737                 +Time\r\n<t>\r\n+Number of reading operations\r\n:2\r\n\
24738                 +Estimated number of matches\r\n:2\r\n"
24739            ),
24740            "{three}"
24741        );
24742    }
24743
24744    /// Every kind of step names itself, and the three that hold other steps say
24745    /// so in the singular or the plural depending on how many they hold.
24746    #[test]
24747    fn each_kind_of_step_writes_the_keys_that_belong_to_it() {
24748        let mut f = profiling();
24749        let tree = |f: &mut Fixture, query: &[u8]| {
24750            let reply = timeless(&f.run(&[b"FT.PROFILE", b"ix", b"SEARCH", b"QUERY", query]));
24751            let at = reply.find("+Iterators profile").expect("a tree");
24752            let end = reply.find("+Result processors").expect("a list of steps");
24753            reply[at..end].to_string()
24754        };
24755        assert_eq!(
24756            tree(&mut f, b"alpha beta"),
24757            "+Iterators profile\r\n*8\r\n+Type\r\n+INTERSECT\r\n+Time\r\n<t>\r\n\
24758             +Number of reading operations\r\n:1\r\n+Child iterators\r\n*2\r\n\
24759             *10\r\n+Type\r\n+TEXT\r\n+Term\r\n$5\r\nalpha\r\n+Time\r\n<t>\r\n\
24760             +Number of reading operations\r\n:2\r\n+Estimated number of matches\r\n:2\r\n\
24761             *10\r\n+Type\r\n+TEXT\r\n+Term\r\n$4\r\nbeta\r\n+Time\r\n<t>\r\n\
24762             +Number of reading operations\r\n:1\r\n+Estimated number of matches\r\n:2\r\n"
24763        );
24764        assert!(tree(&mut f, b"alpha|beta").starts_with(
24765            "+Iterators profile\r\n*10\r\n+Type\r\n+UNION\r\n+Query type\r\n+UNION\r\n\
24766             +Time\r\n<t>\r\n+Number of reading operations\r\n:3\r\n+Child iterators\r\n*2\r\n"
24767        ));
24768        // One thing under it, named in the singular, which is a different key
24769        // and not a list holding one.
24770        assert!(tree(&mut f, b"-alpha").starts_with(
24771            "+Iterators profile\r\n*8\r\n+Type\r\n+NOT\r\n+Time\r\n<t>\r\n\
24772             +Number of reading operations\r\n:1\r\n+Child iterator\r\n*10\r\n"
24773        ));
24774        assert!(tree(&mut f, b"~alpha").starts_with(
24775            "+Iterators profile\r\n*8\r\n+Type\r\n+OPTIONAL\r\n+Time\r\n<t>\r\n\
24776             +Number of reading operations\r\n:3\r\n+Child iterator\r\n*10\r\n"
24777        ));
24778        // No guess at how many, which is the one leaf that leaves it off.
24779        assert_eq!(
24780            tree(&mut f, b"*"),
24781            "+Iterators profile\r\n*6\r\n+Type\r\n+WILDCARD\r\n+Time\r\n<t>\r\n\
24782             +Number of reading operations\r\n:3\r\n"
24783        );
24784        assert!(tree(&mut f, b"@n:[1 2]").starts_with(
24785            "+Iterators profile\r\n*10\r\n+Type\r\n+NUMERIC\r\n+Term\r\n\
24786             $19\r\n1.000000 - 2.000000\r\n"
24787        ));
24788    }
24789
24790    /// A union an expansion made folds into a count of its branches and a union
24791    /// a client wrote with a bar does not.
24792    #[test]
24793    fn limited_folds_the_branches_an_expansion_made_and_leaves_a_bar_alone() {
24794        let mut f = profiling();
24795        f.run(&[b"HSET", b"p:4", b"t", b"alps"]);
24796        let tree = |f: &mut Fixture, words: &[&[u8]]| {
24797            let mut argv: Vec<&[u8]> = vec![b"FT.PROFILE", b"ix", b"SEARCH"];
24798            argv.extend_from_slice(words);
24799            let reply = timeless(&f.run(&argv));
24800            let at = reply.find("+Iterators profile").expect("a tree");
24801            let end = reply.find("+Result processors").expect("a list of steps");
24802            reply[at..end].to_string()
24803        };
24804        assert_eq!(
24805            tree(&mut f, &[b"LIMITED", b"QUERY", b"al*"]),
24806            "+Iterators profile\r\n*10\r\n+Type\r\n+UNION\r\n\
24807             +Query type\r\n$11\r\nPREFIX - al\r\n+Time\r\n<t>\r\n\
24808             +Number of reading operations\r\n:3\r\n+Child iterators\r\n\
24809             +The number of iterators in the union is 2\r\n"
24810        );
24811        assert!(tree(&mut f, &[b"QUERY", b"al*"]).contains("+Child iterators\r\n*2\r\n"));
24812        assert!(
24813            tree(&mut f, &[b"LIMITED", b"QUERY", b"alpha|beta"])
24814                .contains("+Child iterators\r\n*2\r\n")
24815        );
24816        // A union that says nothing but its own name says it as a status, and
24817        // one that says what it stood for says that as a string. Measured, and
24818        // it is the one place in this reply where the two are told apart.
24819        assert!(tree(&mut f, &[b"QUERY", b"alpha|beta"]).contains("+Query type\r\n+UNION\r\n"));
24820        assert!(
24821            tree(&mut f, &[b"QUERY", b"al*"]).contains("+Query type\r\n$11\r\nPREFIX - al\r\n")
24822        );
24823    }
24824
24825    /// Which steps a search runs the rows through, which turns on the window,
24826    /// on whether anything asked for the fields and on what the order is.
24827    #[test]
24828    fn the_steps_a_search_runs_depend_on_what_was_asked_for() {
24829        let mut f = profiling();
24830        let steps = |f: &mut Fixture, words: &[&[u8]]| {
24831            let mut argv: Vec<&[u8]> = vec![b"FT.PROFILE", b"ix", b"SEARCH", b"QUERY", b"alpha"];
24832            argv.extend_from_slice(words);
24833            let reply = timeless(&f.run(&argv));
24834            let at = reply.find("+Result processors").expect("a list of steps");
24835            let end = reply.find("+Coordinator").expect("an end");
24836            let mut out = Vec::new();
24837            let mut parts = reply[at..end].split("\r\n").peekable();
24838            while let Some(part) = parts.next() {
24839                if part == "+Type" {
24840                    out.push(parts.next().unwrap_or_default().to_string());
24841                }
24842            }
24843            out
24844        };
24845        assert_eq!(
24846            steps(&mut f, &[]),
24847            ["+Index", "+Scorer", "+Sorter", "+Loader"]
24848        );
24849        assert_eq!(
24850            steps(&mut f, &[b"NOCONTENT"]),
24851            ["+Index", "+Scorer", "+Sorter"]
24852        );
24853        // A window of nothing is a client asking for the total and nothing
24854        // else, so nothing is scored and nothing is sorted.
24855        assert_eq!(
24856            steps(&mut f, &[b"LIMIT", b"0", b"0"]),
24857            ["+Index", "+Counter"]
24858        );
24859        // A sort by a field does not need a score, and asking for the scores
24860        // puts the step back.
24861        assert_eq!(
24862            steps(&mut f, &[b"SORTBY", b"n"]),
24863            ["+Index", "+Sorter", "+Loader"]
24864        );
24865        assert_eq!(
24866            steps(&mut f, &[b"SORTBY", b"n", b"WITHSCORES"]),
24867            ["+Index", "+Scorer", "+Sorter", "+Loader"]
24868        );
24869        assert_eq!(
24870            steps(&mut f, &[b"HIGHLIGHT"]),
24871            ["+Index", "+Scorer", "+Sorter", "+Loader", "+Highlighter"]
24872        );
24873        assert_eq!(
24874            steps(&mut f, &[b"SUMMARIZE", b"NOCONTENT"]),
24875            ["+Index", "+Scorer", "+Sorter"]
24876        );
24877    }
24878
24879    /// A pipeline names each of its steps after the expression it runs, which
24880    /// is what a real server prints beside them.
24881    #[test]
24882    fn a_pipeline_names_every_step_after_what_it_runs() {
24883        let mut f = profiling();
24884        let steps = |f: &mut Fixture, words: &[&[u8]]| {
24885            let mut argv: Vec<&[u8]> = vec![b"FT.PROFILE", b"ix", b"AGGREGATE", b"QUERY", b"*"];
24886            argv.extend_from_slice(words);
24887            let reply = timeless(&f.run(&argv));
24888            let at = reply.find("+Result processors").expect("a list of steps");
24889            let end = reply.find("+Coordinator").expect("an end");
24890            let mut out = Vec::new();
24891            let mut parts = reply[at..end].split("\r\n").peekable();
24892            while let Some(part) = parts.next() {
24893                if part == "+Type" {
24894                    out.push(parts.next().unwrap_or_default().to_string());
24895                }
24896            }
24897            out
24898        };
24899        assert_eq!(steps(&mut f, &[]), ["+Index"]);
24900        assert_eq!(
24901            steps(&mut f, &[b"APPLY", b"1", b"AS", b"one"]),
24902            ["+Index", "+Projector - Literal 1"]
24903        );
24904        assert_eq!(
24905            steps(
24906                &mut f,
24907                &[b"LOAD", b"1", b"@n", b"APPLY", b"@n * 2", b"AS", b"d"]
24908            ),
24909            ["+Index", "+Loader", "+Projector - Operator *"]
24910        );
24911        assert_eq!(
24912            steps(&mut f, &[b"LOAD", b"1", b"@n", b"FILTER", b"@n > 1"]),
24913            ["+Index", "+Loader", "+Filter - Predicate >"]
24914        );
24915        assert_eq!(
24916            steps(
24917                &mut f,
24918                &[b"GROUPBY", b"1", b"@n", b"REDUCE", b"COUNT", b"0"]
24919            ),
24920            ["+Index", "+Loader", "+Grouper"]
24921        );
24922        assert_eq!(
24923            steps(&mut f, &[b"SORTBY", b"1", b"@n"]),
24924            ["+Index", "+Loader", "+Sorter"]
24925        );
24926        assert_eq!(
24927            steps(&mut f, &[b"LIMIT", b"0", b"2"]),
24928            ["+Index", "+Pager/Limiter"]
24929        );
24930        // Asking for the score by name is a step of its own, and it goes in
24931        // front of the read rather than after it.
24932        assert_eq!(
24933            steps(
24934                &mut f,
24935                &[
24936                    b"ADDSCORES",
24937                    b"LOAD",
24938                    b"1",
24939                    b"@n",
24940                    b"APPLY",
24941                    b"@__score",
24942                    b"AS",
24943                    b"s"
24944                ]
24945            ),
24946            [
24947                "+Index",
24948                "+Scorer",
24949                "+Loader",
24950                "+Projector - Property __score"
24951            ]
24952        );
24953    }
24954
24955    /// A field the schema marked sortable is held beside the document number,
24956    /// so a pipeline that only names those never opens a key and never reports
24957    /// a read.
24958    ///
24959    /// Measured: on a schema of `n NUMERIC SORTABLE g TAG`, `LOAD 1 @n` has no
24960    /// `Loader` step and `LOAD 1 @g` has one. So does `LOAD *`, because what a
24961    /// key turns out to hold is not knowable without opening it.
24962    #[test]
24963    fn a_sortable_field_is_read_without_the_key_being_opened() {
24964        let mut f = Fixture::new();
24965        f.run(&[
24966            b"FT.CREATE",
24967            b"sx",
24968            b"PREFIX",
24969            b"1",
24970            b"s:",
24971            b"SCHEMA",
24972            b"n",
24973            b"NUMERIC",
24974            b"SORTABLE",
24975            b"g",
24976            b"TAG",
24977        ]);
24978        f.run(&[b"HSET", b"s:1", b"n", b"1", b"g", b"one"]);
24979        f.run(&[b"HSET", b"s:2", b"n", b"2", b"g", b"two"]);
24980        let loads = |f: &mut Fixture, words: &[&[u8]]| {
24981            let mut argv: Vec<&[u8]> = vec![b"FT.PROFILE", b"sx", b"AGGREGATE", b"QUERY", b"*"];
24982            argv.extend_from_slice(words);
24983            f.run(&argv).contains("+Loader")
24984        };
24985        assert!(!loads(&mut f, &[b"LOAD", b"1", b"@n"]));
24986        assert!(!loads(&mut f, &[b"SORTBY", b"1", b"@n"]));
24987        assert!(!loads(&mut f, &[b"APPLY", b"@n * 2", b"AS", b"d"]));
24988        assert!(loads(&mut f, &[b"LOAD", b"1", b"@g"]));
24989        assert!(loads(&mut f, &[b"LOAD", b"2", b"@n", b"@g"]));
24990        assert!(loads(
24991            &mut f,
24992            &[b"GROUPBY", b"1", b"@g", b"REDUCE", b"COUNT", b"0"]
24993        ));
24994        assert!(loads(&mut f, &[b"LOAD", b"*"]));
24995    }
24996
24997    /// The four ways the words can be wrong, none of which reaches the search
24998    /// underneath.
24999    #[test]
25000    fn a_profile_checks_its_own_words_before_it_runs_anything() {
25001        let mut f = profiling();
25002        assert_eq!(
25003            f.run(&[b"FT.PROFILE", b"ix", b"SEARCH", b"QUERY"]),
25004            "-ERR wrong number of arguments for 'FT.PROFILE' command\r\n"
25005        );
25006        assert_eq!(
25007            f.run(&[b"FT.PROFILE", b"ix", b"BOGUS", b"QUERY", b"alpha"]),
25008            "-No `SEARCH`, `AGGREGATE`, or `HYBRID` provided\r\n"
25009        );
25010        // The word goes between the two and nowhere else, so one written in
25011        // front of them is not the word at all.
25012        assert_eq!(
25013            f.run(&[
25014                b"FT.PROFILE",
25015                b"ix",
25016                b"LIMITED",
25017                b"SEARCH",
25018                b"QUERY",
25019                b"alpha"
25020            ]),
25021            "-No `SEARCH`, `AGGREGATE`, or `HYBRID` provided\r\n"
25022        );
25023        assert_eq!(
25024            f.run(&[b"FT.PROFILE", b"ix", b"SEARCH", b"BOGUS", b"alpha"]),
25025            "-The QUERY keyword is expected\r\n"
25026        );
25027        assert_eq!(
25028            f.run(&[
25029                b"FT.PROFILE",
25030                b"ix",
25031                b"AGGREGATE",
25032                b"QUERY",
25033                b"alpha",
25034                b"WITHCURSOR"
25035            ]),
25036            "-FT.PROFILE does not support cursor\r\n"
25037        );
25038        // And what the search itself complains about comes back on its own,
25039        // without an envelope around it saying the command worked.
25040        assert_eq!(
25041            f.run(&[b"FT.PROFILE", b"nope", b"SEARCH", b"QUERY", b"alpha"]),
25042            "-SEARCH_INDEX_NOT_FOUND Index not found: nope\r\n"
25043        );
25044        assert_eq!(
25045            f.run(&[
25046                b"FT.PROFILE",
25047                b"ix",
25048                b"SEARCH",
25049                b"QUERY",
25050                b"alpha",
25051                b"extra"
25052            ]),
25053            "-SEARCH_ARG_UNRECOGNIZED Unknown argument `extra` at position 1 for <main>\r\n"
25054        );
25055    }
25056
25057    /// Every word of the command's own is read without regard to case.
25058    #[test]
25059    fn the_words_of_a_profile_are_read_the_way_every_other_word_is() {
25060        let mut f = profiling();
25061        let one = f.run(&[
25062            b"FT.PROFILE",
25063            b"ix",
25064            b"search",
25065            b"limited",
25066            b"query",
25067            b"alpha",
25068        ]);
25069        let two = f.run(&[
25070            b"FT.PROFILE",
25071            b"ix",
25072            b"SEARCH",
25073            b"LIMITED",
25074            b"QUERY",
25075            b"alpha",
25076        ]);
25077        assert_eq!(timeless(&one), timeless(&two));
25078    }
25079
25080    // -------------------------------------------------------------- dropping
25081
25082    /// The two spellings take opposite defaults, which is measured and is the
25083    /// only difference between them that a client can see.
25084    #[test]
25085    fn the_two_ways_of_dropping_an_index_disagree_about_the_documents() {
25086        let mut f = profiling();
25087        assert_eq!(f.run(&[b"FT.DROPINDEX", b"ix"]), "+OK\r\n");
25088        assert_eq!(f.run(&[b"EXISTS", b"p:1", b"p:2", b"p:3"]), ":3\r\n");
25089
25090        let mut f = profiling();
25091        assert_eq!(f.run(&[b"FT.DROPINDEX", b"ix", b"DD"]), "+OK\r\n");
25092        assert_eq!(f.run(&[b"EXISTS", b"p:1", b"p:2", b"p:3"]), ":0\r\n");
25093
25094        let mut f = profiling();
25095        assert_eq!(f.run(&[b"FT.DROP", b"ix"]), "+OK\r\n");
25096        assert_eq!(f.run(&[b"EXISTS", b"p:1", b"p:2", b"p:3"]), ":0\r\n");
25097
25098        let mut f = profiling();
25099        assert_eq!(f.run(&[b"FT.DROP", b"ix", b"KEEPDOCS"]), "+OK\r\n");
25100        assert_eq!(f.run(&[b"EXISTS", b"p:1", b"p:2", b"p:3"]), ":3\r\n");
25101    }
25102
25103    /// Each spelling takes its own word and refuses the other one's, which
25104    /// reads as an oversight and is what a real server answers.
25105    #[test]
25106    fn neither_way_of_dropping_an_index_takes_the_other_ones_word() {
25107        let mut f = profiling();
25108        let line = "-SEARCH_ARG_UNRECOGNIZED Unknown argument\r\n";
25109        assert_eq!(f.run(&[b"FT.DROPINDEX", b"ix", b"KEEPDOCS"]), line);
25110        assert_eq!(f.run(&[b"FT.DROP", b"ix", b"DD"]), line);
25111        // Refused rather than half done, so the index is still there.
25112        assert_eq!(f.run(&[b"FT._LIST"]), "*1\r\n+ix\r\n");
25113    }
25114
25115    /// Only what the index read is deleted, which is not the same as
25116    /// everything under its prefix.
25117    #[test]
25118    fn dropping_the_documents_leaves_a_key_the_index_never_read() {
25119        let mut f = profiling();
25120        f.run(&[b"SET", b"p:4", b"alpha"]);
25121        f.run(&[b"HSET", b"q:1", b"t", b"alpha"]);
25122        assert_eq!(f.run(&[b"FT.DROPINDEX", b"ix", b"DD"]), "+OK\r\n");
25123        assert_eq!(f.run(&[b"EXISTS", b"p:1", b"p:2", b"p:3"]), ":0\r\n");
25124        assert_eq!(f.run(&[b"EXISTS", b"p:4", b"q:1"]), ":2\r\n");
25125    }
25126
25127    /// An index still standing over the same keys hears about them going,
25128    /// rather than answering later with keys that are not there.
25129    #[test]
25130    fn another_index_over_the_same_keys_loses_the_documents_too() {
25131        let mut f = profiling();
25132        f.run(&[
25133            b"FT.CREATE",
25134            b"other",
25135            b"PREFIX",
25136            b"1",
25137            b"p:",
25138            b"SCHEMA",
25139            b"t",
25140            b"TEXT",
25141        ]);
25142        assert_eq!(f.run(&[b"FT.DROPINDEX", b"ix", b"DD"]), "+OK\r\n");
25143        assert_eq!(
25144            f.run(&[b"FT.SEARCH", b"other", b"alpha", b"NOCONTENT"]),
25145            "*1\r\n:0\r\n"
25146        );
25147    }
25148
25149    /// A drop that found nothing to drop deletes nothing either, which is the
25150    /// one case where the shortcut spelling answers `OK` without a sweep.
25151    #[test]
25152    fn a_drop_of_an_index_that_is_not_there_touches_no_keys() {
25153        let mut f = profiling();
25154        assert_eq!(f.run(&[b"FT._DROPINDEXIFX", b"nope", b"DD"]), "+OK\r\n");
25155        assert_eq!(f.run(&[b"EXISTS", b"p:1", b"p:2", b"p:3"]), ":3\r\n");
25156        assert_eq!(f.run(&[b"FT._DROPIFX", b"nope"]), "+OK\r\n");
25157        assert_eq!(f.run(&[b"EXISTS", b"p:1", b"p:2", b"p:3"]), ":3\r\n");
25158    }
25159
25160    // --------------------------------------------------------------- config
25161
25162    /// The two shapes a dump comes back in, which are the one mix of simple
25163    /// strings and bulk strings the group sends.
25164    #[test]
25165    fn a_setting_reads_back_as_a_pair_on_one_protocol_and_a_map_on_the_other() {
25166        let mut f = Fixture::new();
25167        assert_eq!(
25168            f.run(&[b"FT.CONFIG", b"GET", b"TIMEOUT"]),
25169            "*1\r\n*2\r\n+TIMEOUT\r\n$3\r\n500\r\n"
25170        );
25171        assert_eq!(
25172            f.run(&[b"FT.CONFIG", b"GET", b"EXTLOAD"]),
25173            "*1\r\n*2\r\n+EXTLOAD\r\n$-1\r\n"
25174        );
25175        let mut g = Fixture::new();
25176        g.run(&[b"HELLO", b"3"]);
25177        assert_eq!(
25178            g.run(&[b"FT.CONFIG", b"GET", b"TIMEOUT"]),
25179            "%1\r\n+TIMEOUT\r\n$3\r\n500\r\n"
25180        );
25181        assert_eq!(
25182            g.run(&[b"FT.CONFIG", b"GET", b"EXTLOAD"]),
25183            "%1\r\n+EXTLOAD\r\n_\r\n"
25184        );
25185    }
25186
25187    /// The help text rides along in the middle of the same row, flat on RESP2
25188    /// and as a map of its own on RESP3.
25189    #[test]
25190    fn a_help_row_carries_the_description_and_the_value_together() {
25191        let mut f = Fixture::new();
25192        assert_eq!(
25193            f.run(&[b"FT.CONFIG", b"HELP", b"TIMEOUT"]),
25194            "*1\r\n*5\r\n+TIMEOUT\r\n+Description\r\n+Query (search) timeout\r\n\
25195             +Value\r\n$3\r\n500\r\n"
25196        );
25197        let mut g = Fixture::new();
25198        g.run(&[b"HELLO", b"3"]);
25199        assert_eq!(
25200            g.run(&[b"FT.CONFIG", b"HELP", b"TIMEOUT"]),
25201            "%1\r\n+TIMEOUT\r\n%2\r\n+Description\r\n+Query (search) timeout\r\n\
25202             +Value\r\n$3\r\n500\r\n"
25203        );
25204    }
25205
25206    /// A name is matched whole, ignoring case, and the single word star is the
25207    /// only thing that means all of them.
25208    #[test]
25209    fn only_a_bare_star_asks_for_every_setting_and_nothing_else_globs() {
25210        let mut f = Fixture::new();
25211        assert_eq!(
25212            f.run(&[b"FT.CONFIG", b"GET", b"timeout"]),
25213            "*1\r\n*2\r\n+TIMEOUT\r\n$3\r\n500\r\n"
25214        );
25215        for name in [
25216            b"TIMEOUT*".as_slice(),
25217            b"?IMEOUT",
25218            b"*TIMEOUT*",
25219            b"TIME",
25220            b"NOSUCH",
25221            b"",
25222        ] {
25223            assert_eq!(f.run(&[b"FT.CONFIG", b"GET", name]), "*0\r\n", "{name:?}");
25224        }
25225        assert!(f.run(&[b"FT.CONFIG", b"GET", b"*"]).starts_with("*69\r\n"));
25226        assert!(f.run(&[b"FT.CONFIG", b"HELP", b"*"]).starts_with("*69\r\n"));
25227    }
25228
25229    /// Words after the name are stepped over rather than refused, on both of
25230    /// the two reads.
25231    #[test]
25232    fn a_read_ignores_whatever_follows_the_name() {
25233        let mut f = Fixture::new();
25234        assert_eq!(
25235            f.run(&[b"FT.CONFIG", b"GET", b"timeout", b"extra", b"more"]),
25236            "*1\r\n*2\r\n+TIMEOUT\r\n$3\r\n500\r\n"
25237        );
25238        assert_eq!(
25239            f.run(&[b"FT.CONFIG", b"HELP", b"timeout", b"extra"]),
25240            "*1\r\n*5\r\n+TIMEOUT\r\n+Description\r\n+Query (search) timeout\r\n\
25241             +Value\r\n$3\r\n500\r\n"
25242        );
25243    }
25244
25245    /// The container reports its own name and the subcommand it was given in
25246    /// the two lines the dispatcher writes.
25247    #[test]
25248    fn a_missing_subcommand_and_a_missing_name_are_told_apart() {
25249        let mut f = Fixture::new();
25250        assert_eq!(
25251            f.run(&[b"FT.CONFIG"]),
25252            "-ERR wrong number of arguments for 'FT.CONFIG' command\r\n"
25253        );
25254        for sub in [b"GET".as_slice(), b"SET", b"HELP"] {
25255            let want = format!(
25256                "-ERR wrong number of arguments for 'FT.CONFIG|{}' command\r\n",
25257                String::from_utf8_lossy(sub)
25258            );
25259            assert_eq!(f.run(&[b"FT.CONFIG", sub]), want);
25260        }
25261        assert_eq!(
25262            f.run(&[b"ft.config", b"get"]),
25263            "-ERR wrong number of arguments for 'FT.CONFIG|GET' command\r\n"
25264        );
25265        assert_eq!(
25266            f.run(&[b"FT.CONFIG", b"bogus"]),
25267            "-ERR unknown subcommand 'bogus'. Try FT.CONFIG HELP.\r\n"
25268        );
25269    }
25270
25271    /// The name, then whether it can move, then the value, then the count of
25272    /// words, and each of the first three answers before the next is looked at.
25273    #[test]
25274    fn a_write_checks_the_name_then_the_setting_then_the_value() {
25275        let mut f = Fixture::new();
25276        for tail in [vec![b"1".as_slice()], vec![], vec![b"1", b"2", b"3"]] {
25277            let mut cmd: Vec<&[u8]> = vec![b"FT.CONFIG", b"SET", b"NOSUCH"];
25278            cmd.extend(tail);
25279            assert_eq!(f.run(&cmd), "-SEARCH_OPTION_INVALID Invalid option\r\n");
25280        }
25281        for tail in [vec![b"1000".as_slice()], vec![], vec![b"x", b"y"]] {
25282            let mut cmd: Vec<&[u8]> = vec![b"FT.CONFIG", b"SET", b"MAXDOCTABLESIZE"];
25283            cmd.extend(tail);
25284            assert_eq!(
25285                f.run(&cmd),
25286                "-SEARCH_OPTION_BAD Not modifiable at runtime\r\n"
25287            );
25288        }
25289        assert_eq!(
25290            f.run(&[b"FT.CONFIG", b"SET", b"TIMEOUT", b"x", b"y", b"z"]),
25291            "-SEARCH_PARSE_ARGS Could not convert argument to expected type\r\n"
25292        );
25293    }
25294
25295    /// Too many words is a status and not an error, and the value has already
25296    /// been written by the time it goes out.
25297    #[test]
25298    fn an_excess_of_words_is_noticed_after_the_value_is_kept() {
25299        let mut f = Fixture::new();
25300        assert_eq!(
25301            f.run(&[b"FT.CONFIG", b"SET", b"TIMEOUT", b"500"]),
25302            "+OK\r\n"
25303        );
25304        assert_eq!(
25305            f.run(&[b"FT.CONFIG", b"SET", b"TIMEOUT", b"600", b"junk"]),
25306            "+EXCESSARGS\r\n"
25307        );
25308        assert_eq!(
25309            f.run(&[b"FT.CONFIG", b"GET", b"TIMEOUT"]),
25310            "*1\r\n*2\r\n+TIMEOUT\r\n$3\r\n600\r\n"
25311        );
25312    }
25313
25314    /// Strictly first and loosely second, so a hexadecimal and a leading zero
25315    /// and an exponent all land and a fraction does not.
25316    #[test]
25317    fn a_number_is_read_the_strict_way_and_then_the_loose_one() {
25318        let mut f = Fixture::new();
25319        for (given, want) in [
25320            (b"0x10".as_slice(), "16"),
25321            (b"0X1f", "31"),
25322            (b"+0x10", "16"),
25323            (b"+5", "5"),
25324            (b"010", "10"),
25325            (b"08", "8"),
25326            (b"0777", "777"),
25327            (b"1e3", "1000"),
25328            (b"0.0", "0"),
25329            (b"-0.0", "0"),
25330        ] {
25331            assert_eq!(
25332                f.run(&[b"FT.CONFIG", b"SET", b"TIMEOUT", given]),
25333                "+OK\r\n",
25334                "{given:?}"
25335            );
25336            let want = format!("*1\r\n*2\r\n+TIMEOUT\r\n${}\r\n{want}\r\n", want.len());
25337            assert_eq!(
25338                f.run(&[b"FT.CONFIG", b"GET", b"TIMEOUT"]),
25339                want,
25340                "{given:?}"
25341            );
25342        }
25343        for given in [
25344            b" 5".as_slice(),
25345            b"5 ",
25346            b"1.5",
25347            b"1e-3",
25348            b"x",
25349            b"",
25350            b"0b11",
25351            b"0xg",
25352            b"nan",
25353            b"inf",
25354            b"1e100",
25355            b"99999999999999999999",
25356        ] {
25357            assert_eq!(
25358                f.run(&[b"FT.CONFIG", b"SET", b"TIMEOUT", given]),
25359                "-SEARCH_PARSE_ARGS Could not convert argument to expected type\r\n",
25360                "{given:?}"
25361            );
25362        }
25363    }
25364
25365    /// Which of the two readers found a negative decides what it is told, and
25366    /// on a setting with no range at all neither of them is refused.
25367    #[test]
25368    fn a_negative_is_answered_by_whichever_reader_found_it() {
25369        let mut f = Fixture::new();
25370        for given in [b"-1".as_slice(), b"-16"] {
25371            assert_eq!(
25372                f.run(&[b"FT.CONFIG", b"SET", b"TIMEOUT", given]),
25373                "-SEARCH_PARSE_ARGS Value is outside acceptable bounds\r\n",
25374                "{given:?}"
25375            );
25376        }
25377        for given in [b"-0x10".as_slice(), b"-1e3", b"-010", b"-2.0"] {
25378            assert_eq!(
25379                f.run(&[b"FT.CONFIG", b"SET", b"TIMEOUT", given]),
25380                "-SEARCH_PARSE_ARGS Could not convert argument to expected type\r\n",
25381                "{given:?}"
25382            );
25383        }
25384        let unlimited = "*1\r\n*2\r\n+MAXSEARCHRESULTS\r\n$9\r\nunlimited\r\n";
25385        for given in [b"-1".as_slice(), b"-0x10", b"-1e3", b"-010"] {
25386            assert_eq!(
25387                f.run(&[b"FT.CONFIG", b"SET", b"MAXSEARCHRESULTS", given]),
25388                "+OK\r\n",
25389                "{given:?}"
25390            );
25391            assert_eq!(
25392                f.run(&[b"FT.CONFIG", b"GET", b"MAXSEARCHRESULTS"]),
25393                unlimited,
25394                "{given:?}"
25395            );
25396        }
25397    }
25398
25399    /// The two settings with no range truncate into a signed thirty two bit
25400    /// slot and say so once the number has gone under.
25401    #[test]
25402    fn a_wide_setting_wraps_into_its_slot_before_it_is_read_back() {
25403        let mut f = Fixture::new();
25404        for (given, want) in [
25405            (b"2147483647".as_slice(), "2147483647"),
25406            (b"2147483648", "unlimited"),
25407            (b"4294967295", "unlimited"),
25408            (b"9223372036854775806", "unlimited"),
25409            (b"0", "0"),
25410        ] {
25411            assert_eq!(
25412                f.run(&[b"FT.CONFIG", b"SET", b"MAXSEARCHRESULTS", given]),
25413                "+OK\r\n",
25414                "{given:?}"
25415            );
25416            let want = format!(
25417                "*1\r\n*2\r\n+MAXSEARCHRESULTS\r\n${}\r\n{want}\r\n",
25418                want.len()
25419            );
25420            assert_eq!(
25421                f.run(&[b"FT.CONFIG", b"GET", b"MAXSEARCHRESULTS"]),
25422                want,
25423                "{given:?}"
25424            );
25425        }
25426    }
25427
25428    /// A number past what a setting will take says which way it went, and the
25429    /// ones with a softer roof of their own say what that roof is about.
25430    #[test]
25431    fn a_number_out_of_range_names_the_limit_it_crossed() {
25432        let mut f = Fixture::new();
25433        let bounds = "-SEARCH_PARSE_ARGS Value is outside acceptable bounds\r\n";
25434        for (name, given) in [
25435            (b"MINPREFIX".as_slice(), b"0".as_slice()),
25436            (b"MAX_AGGREGATE_GROUPS", b"0"),
25437            (b"BM25STD_TANH_FACTOR", b"0"),
25438            (b"DEFAULT_DIALECT", b"0"),
25439            (b"MINSTEMLEN", b"4294967296"),
25440            (b"_BG_INDEX_OOM_PAUSE_TIME", b"4294967296"),
25441            (b"INDEXER_YIELD_EVERY_OPS", b"4294967296"),
25442            (b"CONNECT_TIMEOUT", b"2147483648"),
25443        ] {
25444            assert_eq!(
25445                f.run(&[b"FT.CONFIG", b"SET", name, given]),
25446                bounds,
25447                "{name:?}"
25448            );
25449        }
25450        for (name, given, want) in [
25451            (
25452                b"MINSTEMLEN".as_slice(),
25453                b"1".as_slice(),
25454                "-SEARCH_SYNTAX Minimum stem length cannot be lower than 2\r\n",
25455            ),
25456            (
25457                b"MAX_AGGREGATE_GROUPS",
25458                b"67108865",
25459                "-SEARCH_LIMIT_OVER Value exceeds maximum possible aggregate groups\r\n",
25460            ),
25461            (
25462                b"WORKERS",
25463                b"17",
25464                "-SEARCH_LIMIT_OVER Number of worker threads cannot exceed 16\r\n",
25465            ),
25466            (
25467                b"_NUMERIC_RANGES_PARENTS",
25468                b"3",
25469                "-SEARCH_PARSE_ARGS Max depth for range cannot be higher than max \
25470                 depth for balance\r\n",
25471            ),
25472            (
25473                b"DEFAULT_DIALECT",
25474                b"5",
25475                "-SEARCH_VALUE_BAD Default dialect version cannot be higher than 4\r\n",
25476            ),
25477            (
25478                b"_BG_INDEX_MEM_PCT_THR",
25479                b"101",
25480                "-SEARCH_LIMIT_OVER Memory limit for indexing cannot be greater then \
25481                 100%\r\n",
25482            ),
25483            (
25484                b"BM25STD_TANH_FACTOR",
25485                b"10001",
25486                "-SEARCH_LIMIT_OVER BM25STD_TANH_FACTOR must be between 1 and 10000 \
25487                 inclusive\r\n",
25488            ),
25489            (
25490                b"BG_INDEX_SLEEP_DURATION_US",
25491                b"1000000",
25492                "-SEARCH_LIMIT_OVER BG_INDEX_SLEEP_DURATION_US must be between 1 and \
25493                 999999 (usleep POSIX limit)\r\n",
25494            ),
25495        ] {
25496            assert_eq!(
25497                f.run(&[b"FT.CONFIG", b"SET", name, given]),
25498                want,
25499                "{name:?}"
25500            );
25501        }
25502    }
25503
25504    /// The two trimming delays are measured against each other, and the answer
25505    /// names both settings and both numbers.
25506    #[test]
25507    fn the_trimming_delays_are_checked_against_one_another() {
25508        let mut f = Fixture::new();
25509        assert_eq!(
25510            f.run(&[b"FT.CONFIG", b"SET", b"_MIN_TRIM_DELAY_MS", b"5000"]),
25511            "-SEARCH_PARSE_ARGS _MIN_TRIM_DELAY_MS (5000) must be less than \
25512             _MAX_TRIM_DELAY_MS (5000)\r\n"
25513        );
25514        assert_eq!(
25515            f.run(&[b"FT.CONFIG", b"SET", b"_MAX_TRIM_DELAY_MS", b"1999"]),
25516            "-SEARCH_PARSE_ARGS _MAX_TRIM_DELAY_MS (1999) must be greater than \
25517             _MIN_TRIM_DELAY_MS (2000)\r\n"
25518        );
25519        assert_eq!(
25520            f.run(&[b"FT.CONFIG", b"SET", b"_MIN_TRIM_DELAY_MS", b"4999"]),
25521            "+OK\r\n"
25522        );
25523    }
25524
25525    /// Two of the word settings fold the spelling on the way in and the scorer
25526    /// does not, which is the one place in the table case counts.
25527    #[test]
25528    fn a_word_setting_folds_where_a_real_server_folds_and_not_otherwise() {
25529        let mut f = Fixture::new();
25530        assert_eq!(
25531            f.run(&[b"FT.CONFIG", b"SET", b"ON_TIMEOUT", b"RETURN"]),
25532            "+OK\r\n"
25533        );
25534        assert_eq!(
25535            f.run(&[b"FT.CONFIG", b"GET", b"ON_TIMEOUT"]),
25536            "*1\r\n*2\r\n+ON_TIMEOUT\r\n$6\r\nreturn\r\n"
25537        );
25538        assert_eq!(
25539            f.run(&[b"FT.CONFIG", b"SET", b"ON_TIMEOUT", b"nope"]),
25540            "-SEARCH_VALUE_BAD Invalid ON_TIMEOUT value\r\n"
25541        );
25542        assert_eq!(
25543            f.run(&[b"FT.CONFIG", b"SET", b"ON_OOM", b"IGNORE"]),
25544            "+OK\r\n"
25545        );
25546        assert_eq!(
25547            f.run(&[b"FT.CONFIG", b"GET", b"ON_OOM"]),
25548            "*1\r\n*2\r\n+ON_OOM\r\n$6\r\nignore\r\n"
25549        );
25550        assert_eq!(
25551            f.run(&[b"FT.CONFIG", b"SET", b"ON_OOM", b"nope"]),
25552            "-SEARCH_VALUE_BAD Invalid ON_OOM value\r\n"
25553        );
25554        let bad = "-SEARCH_VALUE_BAD Invalid default scorer value\r\n";
25555        for given in [b"bm25std".as_slice(), b"Bm25", b"TFIDF.docnorm", b""] {
25556            assert_eq!(
25557                f.run(&[b"FT.CONFIG", b"SET", b"DEFAULT_SCORER", given]),
25558                bad,
25559                "{given:?}"
25560            );
25561        }
25562        assert_eq!(
25563            f.run(&[b"FT.CONFIG", b"SET", b"DEFAULT_SCORER", b"TFIDF.DOCNORM"]),
25564            "+OK\r\n"
25565        );
25566    }
25567
25568    /// True and false, either case, and none of the other words a client might
25569    /// reach for.
25570    #[test]
25571    fn a_yes_or_no_setting_takes_those_two_words_only() {
25572        let mut f = Fixture::new();
25573        assert_eq!(
25574            f.run(&[b"FT.CONFIG", b"SET", b"_NUMERIC_COMPRESS", b"TRUE"]),
25575            "+OK\r\n"
25576        );
25577        assert_eq!(
25578            f.run(&[b"FT.CONFIG", b"GET", b"_NUMERIC_COMPRESS"]),
25579            "*1\r\n*2\r\n+_NUMERIC_COMPRESS\r\n$4\r\ntrue\r\n"
25580        );
25581        for given in [b"yes".as_slice(), b"no", b"1", b"0", b"enabled", b""] {
25582            assert_eq!(
25583                f.run(&[b"FT.CONFIG", b"SET", b"_NUMERIC_COMPRESS", given]),
25584                "-SEARCH_PARSE_ARGS Could not convert argument to expected type\r\n",
25585                "{given:?}"
25586            );
25587        }
25588    }
25589
25590    /// Two pairs of names sit over one number each, and one of that second pair
25591    /// takes no value at all.
25592    #[test]
25593    fn two_names_for_one_setting_move_together() {
25594        let mut f = Fixture::new();
25595        f.run(&[b"FT.CONFIG", b"SET", b"MAXEXPANSIONS", b"300"]);
25596        assert_eq!(
25597            f.run(&[b"FT.CONFIG", b"GET", b"MAXPREFIXEXPANSIONS"]),
25598            "*1\r\n*2\r\n+MAXPREFIXEXPANSIONS\r\n$3\r\n300\r\n"
25599        );
25600        f.run(&[b"FT.CONFIG", b"SET", b"MAXPREFIXEXPANSIONS", b"200"]);
25601        assert_eq!(
25602            f.run(&[b"FT.CONFIG", b"GET", b"MAXEXPANSIONS"]),
25603            "*1\r\n*2\r\n+MAXEXPANSIONS\r\n$3\r\n200\r\n"
25604        );
25605        let long = b"_FORK_GC_CLEAN_NUMERIC_EMPTY_NODES".as_slice();
25606        let short = b"FORK_GC_CLEAN_NUMERIC_EMPTY_NODES".as_slice();
25607        f.run(&[b"FT.CONFIG", b"SET", long, b"false"]);
25608        assert_eq!(
25609            f.run(&[b"FT.CONFIG", b"GET", short]),
25610            "*1\r\n*2\r\n+FORK_GC_CLEAN_NUMERIC_EMPTY_NODES\r\n$5\r\nfalse\r\n"
25611        );
25612        assert_eq!(f.run(&[b"FT.CONFIG", b"SET", short]), "+OK\r\n");
25613        assert_eq!(
25614            f.run(&[b"FT.CONFIG", b"GET", long]),
25615            "*1\r\n*2\r\n+_FORK_GC_CLEAN_NUMERIC_EMPTY_NODES\r\n$4\r\ntrue\r\n"
25616        );
25617    }
25618
25619    /// The one setting that takes a write and never gives it back.
25620    #[test]
25621    fn a_password_reads_back_as_stars_whatever_was_written() {
25622        let mut f = Fixture::new();
25623        assert_eq!(
25624            f.run(&[b"FT.CONFIG", b"SET", b"OSS_GLOBAL_PASSWORD", b"hunter2"]),
25625            "+OK\r\n"
25626        );
25627        assert_eq!(
25628            f.run(&[b"FT.CONFIG", b"GET", b"OSS_GLOBAL_PASSWORD"]),
25629            "*1\r\n*2\r\n+OSS_GLOBAL_PASSWORD\r\n$17\r\nPassword: *******\r\n"
25630        );
25631    }
25632
25633    /// The settings are not in the keyspace, so unlike the dictionaries and the
25634    /// synonym groups beside them they live through an emptied one.
25635    #[test]
25636    fn a_flush_leaves_the_settings_alone() {
25637        for flush in [b"FLUSHALL".as_slice(), b"FLUSHDB"] {
25638            let mut f = Fixture::new();
25639            f.run(&[b"FT.CONFIG", b"SET", b"TIMEOUT", b"777"]);
25640            f.run(&[flush]);
25641            assert_eq!(
25642                f.run(&[b"FT.CONFIG", b"GET", b"TIMEOUT"]),
25643                "*1\r\n*2\r\n+TIMEOUT\r\n$3\r\n777\r\n",
25644                "{flush:?}"
25645            );
25646        }
25647    }
25648
25649    // ---------------------------------------------------------------- debug
25650
25651    /// A small index with one of everything a dump can read, so the tests below
25652    /// all name the same three documents and the same four fields.
25653    fn debugging() -> Fixture {
25654        let mut f = Fixture::new();
25655        f.run(&[
25656            b"FT.CREATE",
25657            b"dx",
25658            b"PREFIX",
25659            b"1",
25660            b"d:",
25661            b"SCHEMA",
25662            b"t",
25663            b"TEXT",
25664            b"g",
25665            b"TAG",
25666            b"n",
25667            b"NUMERIC",
25668            b"s",
25669            b"TEXT",
25670            b"SORTABLE",
25671        ]);
25672        f.run(&[
25673            b"HSET",
25674            b"d:1",
25675            b"t",
25676            b"running dogs",
25677            b"g",
25678            b"red,blue",
25679            b"n",
25680            b"1",
25681            b"s",
25682            b"Alpha",
25683        ]);
25684        f.run(&[
25685            b"HSET", b"d:2", b"t", b"running", b"g", b"red", b"n", b"2", b"s", b"beta",
25686        ]);
25687        f.run(&[
25688            b"HSET",
25689            b"d:3",
25690            b"t",
25691            b"dogs alpha",
25692            b"g",
25693            b"green",
25694            b"n",
25695            b"3",
25696        ]);
25697        f
25698    }
25699
25700    /// The whole dictionary in byte order, with the stems in it as entries of
25701    /// their own rather than hidden behind the words they came from.
25702    #[test]
25703    fn a_term_dump_lists_the_stems_beside_the_words() {
25704        let mut f = debugging();
25705        assert_eq!(
25706            f.run(&[b"_FT.DEBUG", b"DUMP_TERMS", b"dx"]),
25707            "*6\r\n$4\r\n+dog\r\n$4\r\n+run\r\n$5\r\nalpha\r\n$4\r\nbeta\r\n\
25708             $4\r\ndogs\r\n$7\r\nrunning\r\n"
25709        );
25710    }
25711
25712    /// A posting list is looked up on the bytes given and nothing folds them, so
25713    /// the term that a query would have found is not the term a dump wants.
25714    #[test]
25715    fn a_posting_list_is_read_by_the_bytes_and_not_by_the_word() {
25716        let mut f = debugging();
25717        assert_eq!(
25718            f.run(&[b"_FT.DEBUG", b"DUMP_INVIDX", b"dx", b"running"]),
25719            "*2\r\n:1\r\n:2\r\n"
25720        );
25721        assert_eq!(
25722            f.run(&[b"_FT.DEBUG", b"DUMP_INVIDX", b"dx", b"+run"]),
25723            "*2\r\n:1\r\n:2\r\n"
25724        );
25725        for term in [b"RUNNING".as_slice(), b"nosuchterm", b""] {
25726            assert_eq!(
25727                f.run(&[b"_FT.DEBUG", b"DUMP_INVIDX", b"dx", term]),
25728                "-Can not find the inverted index\r\n",
25729                "{term:?}"
25730            );
25731        }
25732    }
25733
25734    /// Tag values come back folded and in byte order, each with the documents
25735    /// that hold it, and a document with two values is under both of them.
25736    #[test]
25737    fn a_tag_dump_pairs_every_value_with_its_documents() {
25738        let mut f = debugging();
25739        assert_eq!(
25740            f.run(&[b"_FT.DEBUG", b"DUMP_TAGIDX", b"dx", b"g"]),
25741            "*3\r\n*2\r\n$4\r\nblue\r\n*1\r\n:1\r\n*2\r\n$5\r\ngreen\r\n*1\r\n:3\r\n\
25742             *2\r\n$3\r\nred\r\n*2\r\n:1\r\n:2\r\n"
25743        );
25744    }
25745
25746    /// One list holding every document in the field, which is D-96: a range tree
25747    /// answers one list per range and this answers the one it keeps.
25748    #[test]
25749    fn a_number_dump_answers_a_single_range() {
25750        let mut f = debugging();
25751        assert_eq!(
25752            f.run(&[b"_FT.DEBUG", b"DUMP_NUMIDX", b"dx", b"n"]),
25753            "*1\r\n*3\r\n:1\r\n:2\r\n:3\r\n"
25754        );
25755    }
25756
25757    /// A point is a number underneath, so the field that holds points answers
25758    /// the subcommand that dumps numbers and not the one that dumps tags.
25759    #[test]
25760    fn a_geo_field_is_dumped_as_a_numeric_one() {
25761        let mut f = Fixture::new();
25762        f.run(&[
25763            b"FT.CREATE",
25764            b"gx",
25765            b"PREFIX",
25766            b"1",
25767            b"q:",
25768            b"SCHEMA",
25769            b"loc",
25770            b"GEO",
25771            b"gg",
25772            b"AS",
25773            b"tag",
25774            b"TAG",
25775        ]);
25776        f.run(&[b"HSET", b"q:1", b"loc", b"1,2", b"gg", b"red"]);
25777        f.run(&[b"HSET", b"q:2", b"loc", b"3,4", b"gg", b"BLUE"]);
25778        assert_eq!(
25779            f.run(&[b"_FT.DEBUG", b"DUMP_NUMIDX", b"gx", b"loc"]),
25780            "*1\r\n*2\r\n:1\r\n:2\r\n"
25781        );
25782        assert_eq!(
25783            f.run(&[b"_FT.DEBUG", b"DUMP_TAGIDX", b"gx", b"loc"]),
25784            "-Could not find given field in index spec\r\n"
25785        );
25786    }
25787
25788    /// A field is named the way a query names it, so the attribute is the name
25789    /// and the identifier the value was read from is not one.
25790    #[test]
25791    fn a_dump_takes_the_attribute_and_not_the_identifier() {
25792        let mut f = Fixture::new();
25793        f.run(&[
25794            b"FT.CREATE",
25795            b"zx",
25796            b"PREFIX",
25797            b"1",
25798            b"z:",
25799            b"SCHEMA",
25800            b"gg",
25801            b"AS",
25802            b"tag",
25803            b"TAG",
25804        ]);
25805        f.run(&[b"HSET", b"z:1", b"gg", b"red"]);
25806        assert_eq!(
25807            f.run(&[b"_FT.DEBUG", b"DUMP_TAGIDX", b"zx", b"tag"]),
25808            "*1\r\n*2\r\n$3\r\nred\r\n*1\r\n:1\r\n"
25809        );
25810        assert_eq!(
25811            f.run(&[b"_FT.DEBUG", b"DUMP_TAGIDX", b"zx", b"gg"]),
25812            "-Could not find given field in index spec\r\n"
25813        );
25814    }
25815
25816    /// The seven keys, with the score as a bulk string here and a double there,
25817    /// and the whole row flat on one protocol and a map on the other.
25818    #[test]
25819    fn a_document_row_is_flat_on_one_protocol_and_a_map_on_the_other() {
25820        let mut f = debugging();
25821        assert_eq!(
25822            f.run(&[b"_FT.DEBUG", b"DOCINFO", b"dx", b"d:1", b"REVEAL"]),
25823            "*14\r\n+internal_id\r\n:1\r\n$5\r\nflags\r\n\
25824             $36\r\n(0xc):HasSortVector,HasOffsetVector,\r\n+score\r\n$1\r\n1\r\n\
25825             +num_tokens\r\n:3\r\n+max_freq\r\n:1\r\n+refcount\r\n:1\r\n\
25826             +sortables\r\n*1\r\n*6\r\n+index\r\n:0\r\n$5\r\nfield\r\n$6\r\ns AS s\r\n\
25827             $5\r\nvalue\r\n$5\r\nalpha\r\n"
25828        );
25829        let mut g = debugging();
25830        g.run(&[b"HELLO", b"3"]);
25831        assert_eq!(
25832            g.run(&[b"_FT.DEBUG", b"DOCINFO", b"dx", b"d:1", b"REVEAL"]),
25833            "%7\r\n+internal_id\r\n:1\r\n$5\r\nflags\r\n\
25834             $36\r\n(0xc):HasSortVector,HasOffsetVector,\r\n+score\r\n,1\r\n\
25835             +num_tokens\r\n:3\r\n+max_freq\r\n:1\r\n+refcount\r\n:1\r\n\
25836             +sortables\r\n*1\r\n*6\r\n+index\r\n:0\r\n$5\r\nfield\r\n$6\r\ns AS s\r\n\
25837             $5\r\nvalue\r\n$5\r\nalpha\r\n"
25838        );
25839    }
25840
25841    /// A document that wrote nothing into a sortable slot has no sortables key
25842    /// at all, so the row is a key shorter rather than carrying an empty list.
25843    #[test]
25844    fn a_document_with_no_sortable_value_drops_the_key() {
25845        let mut f = debugging();
25846        assert_eq!(
25847            f.run(&[b"_FT.DEBUG", b"DOCINFO", b"dx", b"d:3", b"REVEAL"]),
25848            "*12\r\n+internal_id\r\n:3\r\n$5\r\nflags\r\n$22\r\n(0x8):HasOffsetVector,\r\n\
25849             +score\r\n$1\r\n1\r\n+num_tokens\r\n:2\r\n+max_freq\r\n:1\r\n+refcount\r\n:1\r\n"
25850        );
25851    }
25852
25853    /// The flag word is the number and then the names it stands for, and an
25854    /// index built without offsets has none of the three set.
25855    #[test]
25856    fn the_flag_word_spells_out_the_bits_it_carries() {
25857        let mut f = Fixture::new();
25858        f.run(&[
25859            b"FT.CREATE",
25860            b"nx",
25861            b"NOOFFSETS",
25862            b"PREFIX",
25863            b"1",
25864            b"o:",
25865            b"SCHEMA",
25866            b"t",
25867            b"TEXT",
25868        ]);
25869        f.run(&[b"HSET", b"o:1", b"t", b"alpha"]);
25870        assert!(
25871            f.run(&[b"_FT.DEBUG", b"DOCINFO", b"nx", b"o:1", b"REVEAL"])
25872                .contains("$6\r\n(0x0):\r\n")
25873        );
25874    }
25875
25876    /// Obfuscation replaces the field name with where the field sits in the
25877    /// whole schema, which is not where its value sits among the sortables.
25878    #[test]
25879    fn obfuscation_numbers_a_field_by_its_place_in_the_schema() {
25880        let mut f = debugging();
25881        assert!(
25882            f.run(&[b"_FT.DEBUG", b"DOCINFO", b"dx", b"d:1", b"OBFUSCATE"])
25883                .contains("$22\r\nFieldPath@3 AS Field@3\r\n")
25884        );
25885    }
25886
25887    /// The keyword is read where it belongs and anything after it is stepped
25888    /// over, whatever the line that complains about it says.
25889    #[test]
25890    fn a_document_row_reads_its_keyword_at_a_fixed_place() {
25891        let mut f = debugging();
25892        let want = f.run(&[b"_FT.DEBUG", b"DOCINFO", b"dx", b"d:1", b"REVEAL"]);
25893        assert_eq!(
25894            f.run(&[b"_FT.DEBUG", b"DOCINFO", b"dx", b"d:1", b"REVEAL", b"more"]),
25895            want
25896        );
25897        assert_eq!(
25898            f.run(&[b"_FT.DEBUG", b"DOCINFO", b"dx", b"d:1", b"more", b"REVEAL"]),
25899            "-Invalid argument. Expected REVEAL or OBFUSCATE as the last argument\r\n"
25900        );
25901        assert_eq!(
25902            f.run(&[b"_FT.DEBUG", b"DOCINFO", b"dx", b"d:1"]),
25903            "-ERR wrong number of arguments for '_FT.DEBUG|DOCINFO' command\r\n"
25904        );
25905    }
25906
25907    /// The key is looked up before the keyword is read, so a key nobody indexed
25908    /// beats a keyword nobody wrote.
25909    #[test]
25910    fn a_document_row_looks_the_key_up_before_it_reads_the_keyword() {
25911        let mut f = debugging();
25912        assert_eq!(
25913            f.run(&[b"_FT.DEBUG", b"DOCINFO", b"dx", b"nope", b"zz"]),
25914            "-Document not found in index\r\n"
25915        );
25916        assert_eq!(
25917            f.run(&[b"_FT.DEBUG", b"DOCINFO", b"dx", b"d:1", b"zz"]),
25918            "-Invalid argument. Expected REVEAL or OBFUSCATE as the last argument\r\n"
25919        );
25920    }
25921
25922    /// The two directions of the document table, and the number nobody handed
25923    /// out reads as one that was given up rather than as one that never was.
25924    #[test]
25925    fn a_document_number_goes_both_ways() {
25926        let mut f = debugging();
25927        assert_eq!(
25928            f.run(&[b"_FT.DEBUG", b"IDTODOCID", b"dx", b"2"]),
25929            "$3\r\nd:2\r\n"
25930        );
25931        assert_eq!(
25932            f.run(&[b"_FT.DEBUG", b"DOCIDTOID", b"dx", b"d:2"]),
25933            ":2\r\n"
25934        );
25935        assert_eq!(
25936            f.run(&[b"_FT.DEBUG", b"DOCIDTOID", b"dx", b"nope"]),
25937            ":0\r\n"
25938        );
25939        assert_eq!(f.run(&[b"_FT.DEBUG", b"GET_MAX_DOC_ID", b"dx"]), ":3\r\n");
25940        for id in [b"9".as_slice(), b"0", b"-1", b"9223372036854775807"] {
25941            assert_eq!(
25942                f.run(&[b"_FT.DEBUG", b"IDTODOCID", b"dx", id]),
25943                "-document was removed\r\n",
25944                "{id:?}"
25945            );
25946        }
25947    }
25948
25949    /// A document number is read the strict way Redis reads an integer, so a
25950    /// leading zero, a leading plus and a leading space are all refused.
25951    #[test]
25952    fn a_document_number_is_read_the_strict_way() {
25953        let mut f = debugging();
25954        for id in [
25955            b"x".as_slice(),
25956            b"1.5",
25957            b" 1",
25958            b"+1",
25959            b"01",
25960            b"0x1",
25961            b"",
25962            b"9223372036854775808",
25963            b"18446744073709551615",
25964        ] {
25965            assert_eq!(
25966                f.run(&[b"_FT.DEBUG", b"IDTODOCID", b"dx", id]),
25967                "-bad id given\r\n",
25968                "{id:?}"
25969            );
25970        }
25971    }
25972
25973    /// A number a document has given up is still in every list it was in, so a
25974    /// dump names documents that the table says are gone.
25975    #[test]
25976    fn a_dump_keeps_a_number_the_table_has_given_up() {
25977        let mut f = debugging();
25978        f.run(&[b"DEL", b"d:2"]);
25979        assert_eq!(
25980            f.run(&[b"_FT.DEBUG", b"DUMP_INVIDX", b"dx", b"running"]),
25981            "*2\r\n:1\r\n:2\r\n"
25982        );
25983        assert_eq!(
25984            f.run(&[b"_FT.DEBUG", b"IDTODOCID", b"dx", b"2"]),
25985            "-document was removed\r\n"
25986        );
25987        assert_eq!(
25988            f.run(&[b"_FT.DEBUG", b"DOCIDTOID", b"dx", b"d:2"]),
25989            ":0\r\n"
25990        );
25991    }
25992
25993    /// A rewrite hands out a new number and leaves the old one behind, so the
25994    /// counter climbs past the number of documents there are.
25995    #[test]
25996    fn a_rewrite_takes_a_number_of_its_own() {
25997        let mut f = debugging();
25998        f.run(&[b"HSET", b"d:1", b"t", b"cats"]);
25999        assert_eq!(
26000            f.run(&[b"_FT.DEBUG", b"DOCIDTOID", b"dx", b"d:1"]),
26001            ":4\r\n"
26002        );
26003        assert_eq!(f.run(&[b"_FT.DEBUG", b"GET_MAX_DOC_ID", b"dx"]), ":4\r\n");
26004        assert_eq!(
26005            f.run(&[b"_FT.DEBUG", b"IDTODOCID", b"dx", b"1"]),
26006            "-document was removed\r\n"
26007        );
26008        assert_eq!(
26009            f.run(&[b"_FT.DEBUG", b"DUMP_INVIDX", b"dx", b"running"]),
26010            "*2\r\n:1\r\n:2\r\n"
26011        );
26012    }
26013
26014    /// An alias reads the index it stands for, the same as a query does.
26015    #[test]
26016    fn a_dump_follows_an_alias() {
26017        let mut f = debugging();
26018        f.run(&[b"FT.ALIASADD", b"da", b"dx"]);
26019        assert_eq!(f.run(&[b"_FT.DEBUG", b"GET_MAX_DOC_ID", b"da"]), ":3\r\n");
26020        assert_eq!(
26021            f.run(&[b"_FT.DEBUG", b"IDTODOCID", b"da", b"1"]),
26022            "$3\r\nd:1\r\n"
26023        );
26024    }
26025
26026    /// The index name is matched as written and the subcommand name is not, and
26027    /// an index nobody made is reported as a context that could not be built.
26028    #[test]
26029    fn an_index_name_is_case_sensitive_and_a_subcommand_name_is_not() {
26030        let mut f = debugging();
26031        assert_eq!(f.run(&[b"_FT.DEBUG", b"get_max_doc_id", b"dx"]), ":3\r\n");
26032        assert_eq!(
26033            f.run(&[b"_FT.DEBUG", b"GET_MAX_DOC_ID", b"DX"]),
26034            "-Can not create a search ctx\r\n"
26035        );
26036        assert_eq!(
26037            f.run(&[b"_FT.DEBUG", b"DUMP_TERMS", b"nope"]),
26038            "-Can not create a search ctx\r\n"
26039        );
26040    }
26041
26042    /// A field with nothing written into it answers an empty dump rather than an
26043    /// error, since the field is in the schema and only the values are missing.
26044    #[test]
26045    fn an_empty_field_dumps_as_nothing_at_all() {
26046        let mut f = Fixture::new();
26047        f.run(&[
26048            b"FT.CREATE",
26049            b"ex",
26050            b"PREFIX",
26051            b"1",
26052            b"e:",
26053            b"SCHEMA",
26054            b"t",
26055            b"TEXT",
26056            b"g",
26057            b"TAG",
26058            b"n",
26059            b"NUMERIC",
26060        ]);
26061        assert_eq!(f.run(&[b"_FT.DEBUG", b"DUMP_TERMS", b"ex"]), "*0\r\n");
26062        assert_eq!(
26063            f.run(&[b"_FT.DEBUG", b"DUMP_TAGIDX", b"ex", b"g"]),
26064            "*0\r\n"
26065        );
26066        assert_eq!(
26067            f.run(&[b"_FT.DEBUG", b"DUMP_NUMIDX", b"ex", b"n"]),
26068            "*0\r\n"
26069        );
26070        assert_eq!(f.run(&[b"_FT.DEBUG", b"GET_MAX_DOC_ID", b"ex"]), ":0\r\n");
26071    }
26072
26073    /// The two lines the dispatcher owns are the two that carry a code word, and
26074    /// every subcommand but `DOCINFO` counts its arguments exactly.
26075    #[test]
26076    fn the_two_lines_with_a_code_word_are_the_arity_and_the_unknown_one() {
26077        let mut f = debugging();
26078        for (sub, extra) in [
26079            (b"DUMP_TERMS".as_slice(), 1),
26080            (b"GET_MAX_DOC_ID", 1),
26081            (b"DUMP_INVIDX", 2),
26082            (b"DUMP_TAGIDX", 2),
26083            (b"DUMP_NUMIDX", 2),
26084            (b"IDTODOCID", 2),
26085            (b"DOCIDTOID", 2),
26086        ] {
26087            let want = format!(
26088                "-ERR wrong number of arguments for '_FT.DEBUG|{}' command\r\n",
26089                str::from_utf8(sub).unwrap()
26090            );
26091            for given in [extra - 1, extra + 1] {
26092                let mut cmd: Vec<&[u8]> = vec![b"_FT.DEBUG", sub];
26093                cmd.extend(std::iter::repeat_n(b"dx".as_slice(), given));
26094                assert_eq!(f.run(&cmd), want, "{sub:?} {given}");
26095            }
26096            let mut right: Vec<&[u8]> = vec![b"_FT.DEBUG", sub, b"dx"];
26097            right.extend(std::iter::repeat_n(b"g".as_slice(), extra - 1));
26098            assert_ne!(f.run(&right), want, "{sub:?}");
26099        }
26100        assert_eq!(
26101            f.run(&[b"_FT.DEBUG", b"bogus", b"dx"]),
26102            "-ERR unknown subcommand 'bogus'. Try _FT.DEBUG HELP.\r\n"
26103        );
26104    }
26105
26106    /// The eight names that answer rather than the sixty two a real server
26107    /// registers, which is D-97, and anything after the name is stepped over.
26108    #[test]
26109    fn the_help_names_the_subcommands_that_answer() {
26110        let mut f = Fixture::new();
26111        let want = "*8\r\n$11\r\nDUMP_INVIDX\r\n$11\r\nDUMP_NUMIDX\r\n$11\r\nDUMP_TAGIDX\r\n\
26112             $9\r\nIDTODOCID\r\n$9\r\nDOCIDTOID\r\n$7\r\nDOCINFO\r\n$10\r\nDUMP_TERMS\r\n\
26113             $14\r\nGET_MAX_DOC_ID\r\n";
26114        assert_eq!(f.run(&[b"_FT.DEBUG", b"HELP"]), want);
26115        assert_eq!(f.run(&[b"_FT.DEBUG", b"HELP", b"extra"]), want);
26116    }
26117
26118    // ------------------------------------------------------------- synonyms
26119
26120    /// The terms are folded on the way in and the group ids are not, and one
26121    /// term can be in more than one group.
26122    #[test]
26123    fn a_synonym_dump_folds_the_terms_and_keeps_the_ids_as_given() {
26124        let mut f = Fixture::new();
26125        f.run(&[b"FT.CREATE", b"e", b"SCHEMA", b"t", b"TEXT"]);
26126        assert_eq!(
26127            f.run(&[b"FT.SYNUPDATE", b"e", b"G1", b"BOY", b"kid"]),
26128            "+OK\r\n"
26129        );
26130        assert_eq!(f.run(&[b"FT.SYNUPDATE", b"e", b"g2", b"boy"]), "+OK\r\n");
26131        assert_eq!(
26132            f.run(&[b"FT.SYNDUMP", b"e"]),
26133            "*4\r\n$3\r\nboy\r\n*2\r\n$2\r\nG1\r\n$2\r\ng2\r\n\
26134             $3\r\nkid\r\n*1\r\n$2\r\nG1\r\n"
26135        );
26136    }
26137
26138    /// A group is not a comparison made at query time. It is a term of its
26139    /// own, so a word in a group reads as a union of the word, the groups it
26140    /// is in and its stem.
26141    #[test]
26142    fn a_word_in_a_group_reads_as_a_union_with_the_group_term() {
26143        let mut f = Fixture::new();
26144        f.run(&[b"FT.CREATE", b"e", b"SCHEMA", b"t", b"TEXT"]);
26145        f.run(&[b"FT.SYNUPDATE", b"e", b"gr", b"jogging"]);
26146        assert_eq!(
26147            f.run(&[b"FT.EXPLAIN", b"e", b"jogging"]),
26148            "$69\r\nUNION {\n  jogging\n  ~gr(expanded)\n  +jog(expanded)\n  jog(expanded)\n}\n\r\n"
26149        );
26150    }
26151
26152    /// The lookup on the document side is on the word and never on the stem,
26153    /// and a group written after the documents were still finds them because
26154    /// the index is read again.
26155    ///
26156    /// The group holds `running` and `d2` says `runs`, so a query for another
26157    /// word of the group finds `d1` and leaves `d2` where it is. A query for
26158    /// `running` itself does find `d2`, through the stem branch of the union
26159    /// rather than through the group, which is why the two asserts differ.
26160    #[test]
26161    fn a_group_matches_the_word_it_holds_and_not_a_stem_of_it() {
26162        let mut f = Fixture::new();
26163        f.run(&[b"FT.CREATE", b"e", b"SCHEMA", b"t", b"TEXT"]);
26164        f.run(&[b"HSET", b"d1", b"t", b"boy"]);
26165        f.run(&[b"HSET", b"d2", b"t", b"runs"]);
26166        f.run(&[b"FT.SYNUPDATE", b"e", b"gr", b"boy", b"child", b"running"]);
26167        assert_eq!(
26168            f.run(&[b"FT.SEARCH", b"e", b"child", b"NOCONTENT"]),
26169            "*2\r\n:1\r\n$2\r\nd1\r\n"
26170        );
26171        assert_eq!(
26172            f.run(&[b"FT.SEARCH", b"e", b"running", b"NOCONTENT"]),
26173            "*3\r\n:2\r\n$2\r\nd1\r\n$2\r\nd2\r\n"
26174        );
26175    }
26176
26177    /// Neither command makes an index and neither forgives a name that is not
26178    /// there, in the same words the rest of the group uses.
26179    #[test]
26180    fn a_synonym_command_on_a_name_that_is_not_there_fails() {
26181        let mut f = Fixture::new();
26182        let missing = "-SEARCH_INDEX_NOT_FOUND Index not found: nope\r\n";
26183        assert_eq!(f.run(&[b"FT.SYNDUMP", b"nope"]), missing);
26184        assert_eq!(f.run(&[b"FT.SYNUPDATE", b"nope", b"g", b"a"]), missing);
26185    }
26186
26187    /// The words after `PARAMS n` are counted before their shape is looked at,
26188    /// so a count that reaches past the end of the command and a count that is
26189    /// merely odd are two different errors.
26190    #[test]
26191    fn params_counts_the_words_before_it_pairs_them_up() {
26192        let mut f = Fixture::new();
26193        f.run(&[b"FT.CREATE", b"e", b"SCHEMA", b"t", b"TEXT"]);
26194        let none = "-SEARCH_PARSE_ARGS Bad arguments for PARAMS: \
26195                    Expected an argument, but none provided\r\n";
26196        let odd = "-SEARCH_ADD_ARGS Parameters must be specified in PARAM VALUE pairs\r\n";
26197        assert_eq!(f.run(&[b"FT.SEARCH", b"e", b"x", b"PARAMS", b"1"]), none);
26198        assert_eq!(
26199            f.run(&[b"FT.SEARCH", b"e", b"x", b"PARAMS", b"3", b"a", b"b"]),
26200            none
26201        );
26202        assert_eq!(
26203            f.run(&[b"FT.SEARCH", b"e", b"x", b"PARAMS", b"1", b"a"]),
26204            odd
26205        );
26206        assert_eq!(f.run(&[b"FT.SEARCH", b"e", b"x", b"PARAMS", b"0"]), odd);
26207        assert_eq!(
26208            f.run(&[b"FT.SEARCH", b"e", b"x", b"PARAMS", b"-1"]),
26209            "-SEARCH_PARSE_ARGS Bad arguments for PARAMS: Value is outside acceptable bounds\r\n"
26210        );
26211    }
26212
26213    // --------------------------------------------------------------- vectors
26214
26215    /// Five documents a unit apart along one axis, written in the opposite
26216    /// order to the one they sit in, so a reply in document order and a reply
26217    /// in distance order are two different replies.
26218    ///
26219    /// `d1` is furthest from the origin and `d5` is on it. The text field
26220    /// splits them so a query can narrow before it measures: `d1`, `d2` and
26221    /// `d4` say `alpha` and the other two say `beta`.
26222    fn vectored(f: &mut Fixture) {
26223        f.run(&[
26224            b"FT.CREATE",
26225            b"h",
26226            b"SCHEMA",
26227            b"t",
26228            b"TEXT",
26229            b"v",
26230            b"VECTOR",
26231            b"FLAT",
26232            b"6",
26233            b"TYPE",
26234            b"FLOAT32",
26235            b"DIM",
26236            b"2",
26237            b"DISTANCE_METRIC",
26238            b"L2",
26239        ]);
26240        let at: [&[u8]; 5] = [
26241            b"\x00\x00\x80\x40\x00\x00\x00\x00",
26242            b"\x00\x00\x40\x40\x00\x00\x00\x00",
26243            b"\x00\x00\x00\x40\x00\x00\x00\x00",
26244            b"\x00\x00\x80\x3f\x00\x00\x00\x00",
26245            b"\x00\x00\x00\x00\x00\x00\x00\x00",
26246        ];
26247        for (n, point) in at.iter().enumerate() {
26248            let key = format!("d{}", n + 1);
26249            let word: &[u8] = match n {
26250                0 | 1 | 3 => b"alpha",
26251                _ => b"beta",
26252            };
26253            f.run(&[b"HSET", key.as_bytes(), b"t", word, b"v", point]);
26254        }
26255    }
26256
26257    /// The origin, which every query below asks about.
26258    const ORIGIN: &[u8] = b"\x00\x00\x00\x00\x00\x00\x00\x00";
26259
26260    /// A `KNN` picks the k nearest and then answers them in document order,
26261    /// which is measured: asking for three of five that were written furthest
26262    /// first answers the last three written and not the first three.
26263    #[test]
26264    fn a_knn_picks_the_nearest_and_answers_them_in_document_order() {
26265        let mut f = Fixture::new();
26266        vectored(&mut f);
26267        assert_eq!(
26268            f.run(&[
26269                b"FT.SEARCH",
26270                b"h",
26271                b"*=>[KNN 5 @v $vec]",
26272                b"PARAMS",
26273                b"2",
26274                b"vec",
26275                ORIGIN,
26276                b"DIALECT",
26277                b"2",
26278                b"NOCONTENT",
26279            ]),
26280            "*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"
26281        );
26282        assert_eq!(
26283            f.run(&[
26284                b"FT.SEARCH",
26285                b"h",
26286                b"*=>[KNN 3 @v $vec]",
26287                b"PARAMS",
26288                b"2",
26289                b"vec",
26290                ORIGIN,
26291                b"DIALECT",
26292                b"2",
26293                b"NOCONTENT",
26294            ]),
26295            "*4\r\n:3\r\n$2\r\nd3\r\n$2\r\nd4\r\n$2\r\nd5\r\n"
26296        );
26297    }
26298
26299    /// A range takes what is really inside it, where the distances are squared
26300    /// so the five documents sit at 16, 9, 4, 1 and 0.
26301    #[test]
26302    fn a_range_takes_what_is_inside_it_and_the_distance_is_squared() {
26303        let mut f = Fixture::new();
26304        vectored(&mut f);
26305        for (radius, want) in [
26306            ("0", "*2\r\n:1\r\n$2\r\nd5\r\n"),
26307            ("2", "*3\r\n:2\r\n$2\r\nd4\r\n$2\r\nd5\r\n"),
26308            (
26309                "9",
26310                "*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",
26311            ),
26312        ] {
26313            let query = format!("@v:[VECTOR_RANGE {radius} $vec]");
26314            assert_eq!(
26315                f.run(&[
26316                    b"FT.SEARCH",
26317                    b"h",
26318                    query.as_bytes(),
26319                    b"PARAMS",
26320                    b"2",
26321                    b"vec",
26322                    ORIGIN,
26323                    b"DIALECT",
26324                    b"2",
26325                    b"NOCONTENT",
26326                ]),
26327                want,
26328                "radius {radius}"
26329            );
26330        }
26331    }
26332
26333    /// A `KNN` behind a query is the nearest of what the query matched, so
26334    /// asking for two of the three documents that say `alpha` answers the two
26335    /// of those three that are nearest and not the two nearest overall.
26336    #[test]
26337    fn a_knn_measures_what_the_query_in_front_of_it_matched() {
26338        let mut f = Fixture::new();
26339        vectored(&mut f);
26340        assert_eq!(
26341            f.run(&[
26342                b"FT.SEARCH",
26343                b"h",
26344                b"alpha=>[KNN 2 @v $vec]",
26345                b"PARAMS",
26346                b"2",
26347                b"vec",
26348                ORIGIN,
26349                b"DIALECT",
26350                b"2",
26351                b"NOCONTENT",
26352            ]),
26353            "*3\r\n:2\r\n$2\r\nd2\r\n$2\r\nd4\r\n"
26354        );
26355    }
26356
26357    /// A `KNN` counts in whole numbers and a range measures from zero, and the
26358    /// two are refused in their own words.
26359    ///
26360    /// The count is a token of its own and is checked where it stands, ahead of
26361    /// the field and ahead of the vector. A count that arrives through `PARAMS`
26362    /// is read by looser rules than one written into the query, which is
26363    /// measured: a leading plus is fine in a parameter and a syntax error in
26364    /// the query text.
26365    #[test]
26366    fn a_count_and_a_radius_are_refused_in_their_own_words() {
26367        let mut f = Fixture::new();
26368        vectored(&mut f);
26369        let ask = |f: &mut Fixture, query: &str| {
26370            f.run(&[
26371                b"FT.SEARCH",
26372                b"h",
26373                query.as_bytes(),
26374                b"PARAMS",
26375                b"2",
26376                b"vec",
26377                ORIGIN,
26378                b"DIALECT",
26379                b"2",
26380                b"NOCONTENT",
26381            ])
26382        };
26383        for (query, at, near) in [
26384            ("*=>[KNN -1 @v $vec]", 8, "-1"),
26385            ("*=>[KNN 1.5 @v $vec]", 8, "1.5"),
26386            ("*=>[KNN +3 @v $vec]", 8, "+3"),
26387            ("*=>[KNN 0x10 @v $vec]", 8, "0x10"),
26388            ("*=>[KNN abc @v $vec]", 8, "abc"),
26389            ("*=>[KNN 3 $vec]", 10, "vec"),
26390            ("*=>[KNN 3 @v vec]", 13, "vec"),
26391            ("@v:[VECTOR_RANGE 2 -1]", 19, "-1"),
26392        ] {
26393            assert_eq!(
26394                ask(&mut f, query),
26395                format!("-SEARCH_SYNTAX Syntax error at offset {at} near {near}\r\n"),
26396                "{query}"
26397            );
26398        }
26399
26400        // Read as a double the way a real server reads it, so the bound plus
26401        // thirty two rounds back onto the bound and gets in.
26402        let large = "-SEARCH_QUERY_BAD Error parsing vector similarity query: \
26403                     query KNN K parameter is too large, must not exceed 288230376151711744\r\n";
26404        assert_eq!(
26405            ask(&mut f, "*=>[KNN 288230376151711776 @v $vec]"),
26406            "*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"
26407        );
26408        assert_eq!(ask(&mut f, "*=>[KNN 288230376151711777 @v $vec]"), large);
26409        assert_eq!(ask(&mut f, "*=>[KNN 99999999999999999999 @v $vec]"), large);
26410
26411        for (radius, printed) in [("-1", "-1"), ("-0.5", "-0.5"), ("-1e2", "-100")] {
26412            let query = format!("@v:[VECTOR_RANGE {radius} $vec]");
26413            assert_eq!(
26414                ask(&mut f, &query),
26415                format!(
26416                    "-SEARCH_QUERY_BAD Error parsing vector similarity query: \
26417                     negative radius ({printed}) given in a range query\r\n"
26418                ),
26419                "{query}"
26420            );
26421        }
26422        // A radius of minus zero is not below zero and is a radius of zero.
26423        assert_eq!(
26424            ask(&mut f, "@v:[VECTOR_RANGE -0 $vec]"),
26425            "*2\r\n:1\r\n$2\r\nd5\r\n"
26426        );
26427    }
26428
26429    /// A count passed with `PARAMS` is read the way a real server reads one,
26430    /// which is not the way the same digits are read in the query text.
26431    #[test]
26432    fn a_count_that_came_from_params_is_read_by_its_own_rules() {
26433        let mut f = Fixture::new();
26434        vectored(&mut f);
26435        let ask = |f: &mut Fixture, count: &[u8]| {
26436            f.run(&[
26437                b"FT.SEARCH",
26438                b"h",
26439                b"*=>[KNN $k @v $vec]",
26440                b"PARAMS",
26441                b"4",
26442                b"vec",
26443                ORIGIN,
26444                b"k",
26445                count,
26446                b"DIALECT",
26447                b"2",
26448                b"NOCONTENT",
26449            ])
26450        };
26451        let three = "*4\r\n:3\r\n$2\r\nd3\r\n$2\r\nd4\r\n$2\r\nd5\r\n";
26452        assert_eq!(ask(&mut f, b"3"), three);
26453        assert_eq!(ask(&mut f, b"  3"), three);
26454        assert_eq!(ask(&mut f, b"+3"), three);
26455        for bad in [
26456            &b"3.0"[..],
26457            b"0x3",
26458            b"-1",
26459            b"abc",
26460            b"",
26461            b"99999999999999999999",
26462        ] {
26463            let value = String::from_utf8_lossy(bad).into_owned();
26464            assert_eq!(
26465                ask(&mut f, bad),
26466                format!(
26467                    "-SEARCH_NUMERIC_VALUE_INVALID Invalid numeric value ({value}) \
26468                     for parameter `k`\r\n"
26469                ),
26470                "{value}"
26471            );
26472        }
26473        assert_eq!(
26474            ask(&mut f, b"288230376151711777"),
26475            "-SEARCH_QUERY_BAD Error parsing vector similarity query: \
26476             query KNN K parameter is too large, must not exceed 288230376151711744\r\n"
26477        );
26478    }
26479
26480    /// A vector the wrong size is refused against the field it was passed to,
26481    /// naming both sizes in bytes.
26482    #[test]
26483    fn a_vector_the_wrong_size_is_refused_by_the_field_it_reached() {
26484        let mut f = Fixture::new();
26485        vectored(&mut f);
26486        assert_eq!(
26487            f.run(&[
26488                b"FT.SEARCH",
26489                b"h",
26490                b"*=>[KNN 5 @v $vec]",
26491                b"PARAMS",
26492                b"2",
26493                b"vec",
26494                b"abc",
26495                b"DIALECT",
26496                b"2",
26497                b"NOCONTENT",
26498            ]),
26499            "-SEARCH_QUERY_BAD Error parsing vector similarity query: \
26500             query vector blob size (3) does not match index's expected size (8).\r\n"
26501        );
26502    }
26503
26504    /// A nearest neighbour clause puts its distance on every row it answers,
26505    /// under `__v_score` unless the query renamed it. A range clause puts
26506    /// nothing there at all unless the query named it, which is what
26507    /// `YIELD_DISTANCE_AS` is for.
26508    #[test]
26509    fn a_vector_clause_yields_its_distance_under_the_name_it_was_given() {
26510        let mut f = Fixture::new();
26511        vectored(&mut f);
26512        let ask = |f: &mut Fixture, query: &str| {
26513            f.run(&[
26514                b"FT.SEARCH",
26515                b"h",
26516                query.as_bytes(),
26517                b"PARAMS",
26518                b"2",
26519                b"vec",
26520                ORIGIN,
26521                b"DIALECT",
26522                b"2",
26523                b"LIMIT",
26524                b"0",
26525                b"1",
26526            ])
26527        };
26528        assert_eq!(
26529            ask(&mut f, "*=>[KNN 3 @v $vec]"),
26530            "*3\r\n:3\r\n$2\r\nd3\r\n*6\r\n$9\r\n__v_score\r\n$1\r\n4\r\n$1\r\nt\r\n$4\r\nbeta\r\n$1\r\nv\r\n$8\r\n\0\0\0@\0\0\0\0\r\n"
26531        );
26532        assert_eq!(
26533            ask(&mut f, "*=>[KNN 3 @v $vec AS d]"),
26534            "*3\r\n:3\r\n$2\r\nd3\r\n*6\r\n$1\r\nd\r\n$1\r\n4\r\n$1\r\nt\r\n$4\r\nbeta\r\n$1\r\nv\r\n$8\r\n\0\0\0@\0\0\0\0\r\n"
26535        );
26536        assert_eq!(
26537            ask(&mut f, "@v:[VECTOR_RANGE 4 $vec]"),
26538            "*3\r\n:3\r\n$2\r\nd3\r\n*4\r\n$1\r\nt\r\n$4\r\nbeta\r\n$1\r\nv\r\n$8\r\n\0\0\0@\0\0\0\0\r\n"
26539        );
26540        assert_eq!(
26541            ask(&mut f, "@v:[VECTOR_RANGE 4 $vec]=>{$YIELD_DISTANCE_AS: d}"),
26542            "*3\r\n:3\r\n$2\r\nd3\r\n*6\r\n$1\r\nd\r\n$1\r\n4\r\n$1\r\nt\r\n$4\r\nbeta\r\n$1\r\nv\r\n$8\r\n\0\0\0@\0\0\0\0\r\n"
26543        );
26544    }
26545
26546    /// What decides whether a `RETURN` answers the distance is the name the row
26547    /// would carry it under and not the field it would have been read from,
26548    /// because it is on the row before any key is read.
26549    ///
26550    /// So naming it answers it, renaming it answers nothing at all, and giving
26551    /// its name to another field answers the distance under that name.
26552    #[test]
26553    fn a_return_answers_the_distance_by_the_name_the_row_carries_it_under() {
26554        let mut f = Fixture::new();
26555        vectored(&mut f);
26556        let ask = |f: &mut Fixture, ret: &[&[u8]]| {
26557            let mut args: Vec<&[u8]> = vec![b"FT.SEARCH", b"h", b"*=>[KNN 1 @v $vec]"];
26558            args.extend_from_slice(ret);
26559            args.extend_from_slice(&[b"PARAMS", b"2", b"vec", ORIGIN, b"DIALECT", b"2"]);
26560            f.run(&args)
26561        };
26562        assert_eq!(
26563            ask(&mut f, &[b"RETURN", b"1", b"__v_score"]),
26564            "*3\r\n:1\r\n$2\r\nd5\r\n*2\r\n$9\r\n__v_score\r\n$1\r\n0\r\n"
26565        );
26566        assert_eq!(
26567            ask(&mut f, &[b"RETURN", b"3", b"__v_score", b"AS", b"x"]),
26568            "*3\r\n:1\r\n$2\r\nd5\r\n*0\r\n"
26569        );
26570        assert_eq!(
26571            ask(&mut f, &[b"RETURN", b"3", b"t", b"AS", b"__v_score"]),
26572            "*3\r\n:1\r\n$2\r\nd5\r\n*2\r\n$9\r\n__v_score\r\n$1\r\n0\r\n"
26573        );
26574        assert_eq!(
26575            ask(&mut f, &[b"RETURN", b"1", b"t"]),
26576            "*3\r\n:1\r\n$2\r\nd5\r\n*2\r\n$1\r\nt\r\n$4\r\nbeta\r\n"
26577        );
26578        // The distance goes in front of the rest whatever order they were
26579        // named in, and `NOCONTENT` takes it away with everything else.
26580        assert_eq!(
26581            ask(&mut f, &[b"RETURN", b"2", b"t", b"__v_score"]),
26582            "*3\r\n:1\r\n$2\r\nd5\r\n*4\r\n$9\r\n__v_score\r\n$1\r\n0\r\n$1\r\nt\r\n$4\r\nbeta\r\n"
26583        );
26584        assert_eq!(ask(&mut f, &[b"NOCONTENT"]), "*2\r\n:1\r\n$2\r\nd5\r\n");
26585    }
26586
26587    /// A `SORTBY` can name a distance the query yielded, which sorts by the
26588    /// number rather than by anything the key holds. A name the query did not
26589    /// yield is refused the way any other unknown property is.
26590    #[test]
26591    fn a_sortby_can_name_a_distance_the_query_yielded() {
26592        let mut f = Fixture::new();
26593        vectored(&mut f);
26594        let ask = |f: &mut Fixture, query: &str, by: &[u8], desc: bool| {
26595            let mut args: Vec<&[u8]> = vec![b"FT.SEARCH", b"h", query.as_bytes(), b"SORTBY", by];
26596            if desc {
26597                args.push(b"DESC");
26598            }
26599            args.extend_from_slice(&[
26600                b"PARAMS",
26601                b"2",
26602                b"vec",
26603                ORIGIN,
26604                b"DIALECT",
26605                b"2",
26606                b"NOCONTENT",
26607            ]);
26608            f.run(&args)
26609        };
26610        assert_eq!(
26611            ask(&mut f, "*=>[KNN 3 @v $vec]", b"__v_score", false),
26612            "*4\r\n:3\r\n$2\r\nd5\r\n$2\r\nd4\r\n$2\r\nd3\r\n"
26613        );
26614        assert_eq!(
26615            ask(&mut f, "*=>[KNN 3 @v $vec]", b"__v_score", true),
26616            "*4\r\n:3\r\n$2\r\nd3\r\n$2\r\nd4\r\n$2\r\nd5\r\n"
26617        );
26618        assert_eq!(
26619            ask(&mut f, "*=>[KNN 3 @v $vec AS d]", b"d", false),
26620            "*4\r\n:3\r\n$2\r\nd5\r\n$2\r\nd4\r\n$2\r\nd3\r\n"
26621        );
26622        // Renaming it takes the old name away, and a query with no vector
26623        // clause in it never had the property at all.
26624        let missing = "-SEARCH_PROP_NOT_FOUND Property `__v_score` \
26625                       not loaded nor in schema\r\n";
26626        assert_eq!(
26627            ask(&mut f, "*=>[KNN 3 @v $vec AS d]", b"__v_score", false),
26628            missing
26629        );
26630        assert_eq!(ask(&mut f, "alpha", b"__v_score", false), missing);
26631        // The query is read before the property is looked up, which is
26632        // measured: a query that will not parse is answered first.
26633        assert_eq!(
26634            ask(&mut f, "foo(", b"zz", false),
26635            "-SEARCH_SYNTAX Syntax error at offset 3 near foo\r\n"
26636        );
26637    }
26638
26639    /// Two vector clauses in one query answer two distances, outermost first.
26640    #[test]
26641    fn two_vector_clauses_answer_two_distances() {
26642        let mut f = Fixture::new();
26643        vectored(&mut f);
26644        assert_eq!(
26645            f.run(&[
26646                b"FT.SEARCH",
26647                b"h",
26648                b"@v:[VECTOR_RANGE 9 $vec]=>{$YIELD_DISTANCE_AS: rr}=>[KNN 2 @v $vec]",
26649                b"RETURN",
26650                b"2",
26651                b"rr",
26652                b"__v_score",
26653                b"PARAMS",
26654                b"2",
26655                b"vec",
26656                ORIGIN,
26657                b"DIALECT",
26658                b"2",
26659            ]),
26660            "*5\r\n:2\r\n$2\r\nd4\r\n*4\r\n$9\r\n__v_score\r\n$1\r\n1\r\n$2\r\nrr\r\n$1\r\n1\r\n$2\r\nd5\r\n*4\r\n$9\r\n__v_score\r\n$1\r\n0\r\n$2\r\nrr\r\n$1\r\n0\r\n"
26661        );
26662    }
26663
26664    /// An aggregation carries the distance on every row whether or not the
26665    /// pipeline ever mentions it, and carries it in front of everything a
26666    /// `LOAD` asked for.
26667    #[test]
26668    fn an_aggregation_answers_a_distance_nothing_asked_for() {
26669        let mut f = Fixture::new();
26670        vectored(&mut f);
26671        let ask = |f: &mut Fixture, query: &str, rest: &[&[u8]]| {
26672            let mut args: Vec<&[u8]> = vec![b"FT.AGGREGATE", b"h", query.as_bytes()];
26673            args.extend_from_slice(rest);
26674            args.extend_from_slice(&[b"PARAMS", b"2", b"vec", ORIGIN, b"DIALECT", b"2"]);
26675            f.run(&args)
26676        };
26677        assert_eq!(
26678            ask(&mut f, "*=>[KNN 2 @v $vec]", &[]),
26679            "*3\r\n:1\r\n*2\r\n$9\r\n__v_score\r\n$1\r\n0\r\n*2\r\n$9\r\n__v_score\r\n$1\r\n1\r\n"
26680        );
26681        assert_eq!(
26682            ask(&mut f, "*=>[KNN 2 @v $vec]", &[b"LOAD", b"1", b"@t"]),
26683            "*3\r\n:1\r\n*4\r\n$9\r\n__v_score\r\n$1\r\n0\r\n$1\r\nt\r\n$4\r\nbeta\r\n*4\r\n$9\r\n__v_score\r\n$1\r\n1\r\n$1\r\nt\r\n$5\r\nalpha\r\n"
26684        );
26685        assert_eq!(
26686            ask(&mut f, "*=>[KNN 2 @v $vec AS d]", &[]),
26687            "*3\r\n:1\r\n*2\r\n$1\r\nd\r\n$1\r\n0\r\n*2\r\n$1\r\nd\r\n$1\r\n1\r\n"
26688        );
26689        // A range shows nothing until the query names it.
26690        assert_eq!(
26691            ask(&mut f, "@v:[VECTOR_RANGE 1 $vec]", &[]),
26692            "*3\r\n:1\r\n*0\r\n*0\r\n"
26693        );
26694        assert_eq!(
26695            ask(
26696                &mut f,
26697                "@v:[VECTOR_RANGE 1 $vec]=>{$YIELD_DISTANCE_AS: rr}",
26698                &[]
26699            ),
26700            "*3\r\n:1\r\n*2\r\n$2\r\nrr\r\n$1\r\n1\r\n*2\r\n$2\r\nrr\r\n$1\r\n0\r\n"
26701        );
26702    }
26703
26704    /// A nearest neighbour clause hands its documents back nearest first and an
26705    /// aggregation keeps them that way, where a search sorts them into document
26706    /// order. A tie goes to the document written first.
26707    #[test]
26708    fn an_aggregation_keeps_the_order_a_nearest_neighbour_clause_made() {
26709        let mut f = Fixture::new();
26710        vectored(&mut f);
26711        // Sitting on `d3`, so `d2` and `d4` are the same distance away.
26712        const MIDDLE: &[u8] = b"\x00\x00\x00\x40\x00\x00\x00\x00";
26713        let ask = |f: &mut Fixture, query: &str, vec: &[u8]| {
26714            f.run(&[
26715                b"FT.AGGREGATE",
26716                b"h",
26717                query.as_bytes(),
26718                b"LOAD",
26719                b"1",
26720                b"@t",
26721                b"PARAMS",
26722                b"2",
26723                b"vec",
26724                vec,
26725                b"DIALECT",
26726                b"2",
26727            ])
26728        };
26729        assert_eq!(
26730            ask(&mut f, "*=>[KNN 3 @v $vec]", MIDDLE),
26731            "*4\r\n:1\r\n*4\r\n$9\r\n__v_score\r\n$1\r\n0\r\n$1\r\nt\r\n$4\r\nbeta\r\n*4\r\n$9\r\n__v_score\r\n$1\r\n1\r\n$1\r\nt\r\n$5\r\nalpha\r\n*4\r\n$9\r\n__v_score\r\n$1\r\n1\r\n$1\r\nt\r\n$5\r\nalpha\r\n"
26732        );
26733        // A range does no ordering, so those rows stay in document order.
26734        assert_eq!(
26735            ask(
26736                &mut f,
26737                "@v:[VECTOR_RANGE 1 $vec]=>{$YIELD_DISTANCE_AS: rr}",
26738                MIDDLE
26739            ),
26740            "*4\r\n:1\r\n*4\r\n$2\r\nrr\r\n$1\r\n1\r\n$1\r\nt\r\n$5\r\nalpha\r\n*4\r\n$2\r\nrr\r\n$1\r\n0\r\n$1\r\nt\r\n$4\r\nbeta\r\n*4\r\n$2\r\nrr\r\n$1\r\n1\r\n$1\r\nt\r\n$5\r\nalpha\r\n"
26741        );
26742    }
26743
26744    /// Every step of the pipeline can name a distance the query yielded, and a
26745    /// query with no vector clause in it is refused for the name three
26746    /// different ways depending on which step asked.
26747    #[test]
26748    fn a_pipeline_step_can_name_a_distance_the_query_yielded() {
26749        let mut f = Fixture::new();
26750        vectored(&mut f);
26751        let ask = |f: &mut Fixture, query: &str, rest: &[&[u8]]| {
26752            let mut args: Vec<&[u8]> = vec![b"FT.AGGREGATE", b"h", query.as_bytes()];
26753            args.extend_from_slice(rest);
26754            args.extend_from_slice(&[b"PARAMS", b"2", b"vec", ORIGIN, b"DIALECT", b"2"]);
26755            f.run(&args)
26756        };
26757        let knn = "*=>[KNN 2 @v $vec]";
26758        assert_eq!(
26759            ask(&mut f, knn, &[b"APPLY", b"@__v_score * 2", b"AS", b"x"]),
26760            "*3\r\n:1\r\n*4\r\n$9\r\n__v_score\r\n$1\r\n0\r\n$1\r\nx\r\n$1\r\n0\r\n*4\r\n$9\r\n__v_score\r\n$1\r\n1\r\n$1\r\nx\r\n$1\r\n2\r\n"
26761        );
26762        assert_eq!(
26763            ask(&mut f, knn, &[b"FILTER", b"@__v_score > 0"]),
26764            "*2\r\n:1\r\n*2\r\n$9\r\n__v_score\r\n$1\r\n1\r\n"
26765        );
26766        assert_eq!(
26767            ask(&mut f, knn, &[b"SORTBY", b"2", b"@__v_score", b"DESC"]),
26768            "*3\r\n:2\r\n*2\r\n$9\r\n__v_score\r\n$1\r\n1\r\n*2\r\n$9\r\n__v_score\r\n$1\r\n0\r\n"
26769        );
26770        assert_eq!(
26771            ask(
26772                &mut f,
26773                knn,
26774                &[
26775                    b"GROUPBY",
26776                    b"1",
26777                    b"@t",
26778                    b"REDUCE",
26779                    b"MAX",
26780                    b"1",
26781                    b"@__v_score",
26782                    b"AS",
26783                    b"m"
26784                ]
26785            ),
26786            "*3\r\n:2\r\n*4\r\n$1\r\nt\r\n$4\r\nbeta\r\n$1\r\nm\r\n$1\r\n0\r\n*4\r\n$1\r\nt\r\n$5\r\nalpha\r\n$1\r\nm\r\n$1\r\n1\r\n"
26787        );
26788        assert_eq!(
26789            ask(&mut f, "*", &[b"APPLY", b"@__v_score", b"AS", b"x"]),
26790            "-SEARCH_PROP_NOT_FOUND Property not loaded nor in pipeline: \
26791             `__v_score`\r\n"
26792        );
26793        assert_eq!(
26794            ask(&mut f, "*", &[b"GROUPBY", b"1", b"@__v_score"]),
26795            "-SEARCH_PROP_NOT_FOUND No such property `__v_score`\r\n"
26796        );
26797        assert_eq!(
26798            ask(&mut f, "*", &[b"SORTBY", b"2", b"@__v_score", b"ASC"]),
26799            "-SEARCH_PROP_NOT_FOUND Property `__v_score` not loaded nor in \
26800             schema\r\n"
26801        );
26802    }
26803
26804    /// An aggregation reads every word before it reads the query, and reads the
26805    /// query before it ties anything on the pipeline to a place on the row.
26806    ///
26807    /// So a command with a fault in all three answers the one about the words,
26808    /// a command with a fault in the last two answers the one about the query,
26809    /// and the pipeline speaks last. That is measured, and it is the whole
26810    /// reason the arguments are read twice.
26811    #[test]
26812    fn the_words_come_before_the_query_and_the_query_before_the_pipeline() {
26813        let mut f = Fixture::new();
26814        vectored(&mut f);
26815        let ask = |f: &mut Fixture, rest: &[&[u8]]| {
26816            let mut args: Vec<&[u8]> = vec![b"FT.AGGREGATE", b"h"];
26817            args.extend_from_slice(rest);
26818            f.run(&args)
26819        };
26820        assert_eq!(
26821            ask(
26822                &mut f,
26823                &[b"foo(", b"APPLY", b"@zz", b"AS", b"x", b"LIMIT", b"x", b"1"]
26824            ),
26825            "-SEARCH_PARSE_ARGS LIMIT needs two numeric arguments\r\n"
26826        );
26827        assert_eq!(
26828            ask(&mut f, &[b"foo(", b"APPLY", b"@zz", b"AS", b"x"]),
26829            "-SEARCH_SYNTAX Syntax error at offset 3 near foo\r\n"
26830        );
26831        assert_eq!(
26832            ask(&mut f, &[b"*", b"APPLY", b"@zz", b"AS", b"x"]),
26833            "-SEARCH_PROP_NOT_FOUND Property not loaded nor in pipeline: `zz`\r\n"
26834        );
26835        // An expression that will not read is the pipeline's fault too, so it
26836        // speaks after the query and after a property named before it.
26837        assert_eq!(
26838            ask(&mut f, &[b"foo(", b"APPLY", b"@@@", b"AS", b"x"]),
26839            "-SEARCH_SYNTAX Syntax error at offset 3 near foo\r\n"
26840        );
26841        assert_eq!(
26842            ask(
26843                &mut f,
26844                &[
26845                    b"*", b"APPLY", b"@zz", b"AS", b"x", b"APPLY", b"@@@", b"AS", b"y"
26846                ]
26847            ),
26848            "-SEARCH_PROP_NOT_FOUND Property not loaded nor in pipeline: `zz`\r\n"
26849        );
26850        assert_eq!(
26851            ask(&mut f, &[b"*", b"APPLY", b"@@@", b"AS", b"x"]),
26852            "-SEARCH_EXPR Syntax error at offset 0 near ''\r\n"
26853        );
26854    }
26855
26856    /// A vector clause says which of the ways of answering one it took, and a
26857    /// range says nothing at all when there is no distance to hand back.
26858    #[test]
26859    fn a_vector_step_says_which_way_it_was_answered() {
26860        let mut f = Fixture::new();
26861        vectored(&mut f);
26862        let tree = |f: &mut Fixture, query: &[u8]| {
26863            let reply = timeless(&f.run(&[
26864                b"FT.PROFILE",
26865                b"h",
26866                b"AGGREGATE",
26867                b"QUERY",
26868                query,
26869                b"PARAMS",
26870                b"2",
26871                b"vec",
26872                ORIGIN,
26873                b"DIALECT",
26874                b"2",
26875            ]));
26876            let at = reply.find("+Iterators profile").expect("a tree");
26877            let end = reply.find("+Result processors").expect("a list of steps");
26878            reply[at..end].to_string()
26879        };
26880        assert_eq!(
26881            tree(&mut f, b"*=>[KNN 3 @v $vec]"),
26882            "+Iterators profile\r\n*8\r\n+Type\r\n+VECTOR\r\n+Time\r\n<t>\r\n\
26883             +Number of reading operations\r\n:3\r\n\
26884             +Vector search mode\r\n+STANDARD_KNN\r\n"
26885        );
26886        // Renaming the distance changes nothing about how it was answered.
26887        assert_eq!(
26888            tree(&mut f, b"*=>[KNN 3 @v $vec AS d]"),
26889            tree(&mut f, b"*=>[KNN 3 @v $vec]")
26890        );
26891        // A range with nothing to yield is not a vector step at all, and one
26892        // that yields names the distance in its own type.
26893        assert_eq!(
26894            tree(&mut f, b"@v:[VECTOR_RANGE 9 $vec]"),
26895            "+Iterators profile\r\n*6\r\n+Type\r\n+ID-LIST-SORTED\r\n+Time\r\n<t>\r\n\
26896             +Number of reading operations\r\n:4\r\n"
26897        );
26898        assert_eq!(
26899            tree(
26900                &mut f,
26901                b"@v:[VECTOR_RANGE 9 $vec]=>{$YIELD_DISTANCE_AS: rr}"
26902            ),
26903            "+Iterators profile\r\n*8\r\n\
26904             +Type\r\n+METRIC SORTED BY ID - VECTOR DISTANCE\r\n+Time\r\n<t>\r\n\
26905             +Number of reading operations\r\n:4\r\n\
26906             +Vector search mode\r\n+RANGE_QUERY\r\n"
26907        );
26908    }
26909
26910    /// What a vector clause narrowed itself down with hangs under it as a
26911    /// single child, and the step that works the distances out is behind the
26912    /// index whenever the query yields one.
26913    #[test]
26914    fn a_clause_in_front_of_a_vector_hangs_under_it_as_one_child() {
26915        let mut f = Fixture::new();
26916        vectored(&mut f);
26917        let ask = |f: &mut Fixture, query: &[u8]| {
26918            timeless(&f.run(&[
26919                b"FT.PROFILE",
26920                b"h",
26921                b"AGGREGATE",
26922                b"QUERY",
26923                query,
26924                b"PARAMS",
26925                b"2",
26926                b"vec",
26927                ORIGIN,
26928                b"DIALECT",
26929                b"2",
26930            ]))
26931        };
26932        let cut = |reply: &str| {
26933            let at = reply.find("+Iterators profile").expect("a tree");
26934            reply[at..].to_string()
26935        };
26936        assert_eq!(
26937            cut(&ask(&mut f, b"@t:alpha=>[KNN 3 @v $vec]")),
26938            "+Iterators profile\r\n*10\r\n+Type\r\n+VECTOR\r\n+Time\r\n<t>\r\n\
26939             +Number of reading operations\r\n:3\r\n\
26940             +Vector search mode\r\n+HYBRID_ADHOC_BF\r\n+Child iterator\r\n\
26941             *10\r\n+Type\r\n+TEXT\r\n+Term\r\n$5\r\nalpha\r\n+Time\r\n<t>\r\n\
26942             +Number of reading operations\r\n:3\r\n\
26943             +Estimated number of matches\r\n:3\r\n\
26944             +Result processors profile\r\n*2\r\n\
26945             *6\r\n+Type\r\n+Index\r\n+Time\r\n<t>\r\n+Results processed\r\n:3\r\n\
26946             *6\r\n+Type\r\n+Metrics Applier\r\n+Time\r\n<t>\r\n\
26947             +Results processed\r\n:3\r\n+Coordinator\r\n*0\r\n"
26948        );
26949        // A range nobody named yields nothing, so nothing works a distance out
26950        // and the step is not there.
26951        assert!(ask(&mut f, b"@v:[VECTOR_RANGE 9 $vec]").ends_with(
26952            "+Result processors profile\r\n*1\r\n*6\r\n+Type\r\n+Index\r\n\
26953             +Time\r\n<t>\r\n+Results processed\r\n:4\r\n+Coordinator\r\n*0\r\n"
26954        ));
26955        // A nearest neighbour clause with nothing in front of it yields all
26956        // the same, so the step is there without a child above it.
26957        assert!(ask(&mut f, b"*=>[KNN 3 @v $vec]").contains("+Type\r\n+Metrics Applier\r\n"));
26958    }
26959
26960    /// A `LIMIT 0 0` on an aggregation is a client asking for the total and
26961    /// nothing else, so the step that would have paged the rows counts them
26962    /// instead, whether or not a `SORTBY` put an order in front of it.
26963    #[test]
26964    fn a_window_of_nothing_on_an_aggregation_counts_rather_than_pages() {
26965        let mut f = profiling();
26966        let steps = |f: &mut Fixture, words: &[&[u8]]| {
26967            let mut argv: Vec<&[u8]> = vec![b"FT.PROFILE", b"ix", b"AGGREGATE", b"QUERY", b"*"];
26968            argv.extend_from_slice(words);
26969            let reply = timeless(&f.run(&argv));
26970            let at = reply.find("+Result processors").expect("a list of steps");
26971            reply[at..].to_string()
26972        };
26973        assert_eq!(
26974            steps(&mut f, &[b"LIMIT", b"0", b"0"]),
26975            "+Result processors profile\r\n*2\r\n\
26976             *6\r\n+Type\r\n+Index\r\n+Time\r\n<t>\r\n+Results processed\r\n:3\r\n\
26977             *6\r\n+Type\r\n+Counter\r\n+Time\r\n<t>\r\n+Results processed\r\n:1\r\n\
26978             +Coordinator\r\n*0\r\n"
26979        );
26980        assert!(
26981            steps(
26982                &mut f,
26983                &[b"SORTBY", b"2", b"@n", b"ASC", b"LIMIT", b"0", b"0"]
26984            )
26985            .contains("+Type\r\n+Counter\r\n")
26986        );
26987        // A window that keeps something is still a window.
26988        assert!(steps(&mut f, &[b"LIMIT", b"0", b"2"]).contains(
26989            "+Type\r\n+Pager/Limiter\r\n+Time\r\n<t>\r\n\
26990             +Results processed\r\n:2\r\n"
26991        ));
26992    }
26993
26994    // ----------------------------------------------------------- spellcheck
26995
26996    /// The score is how many documents hold the suggestion over how many
26997    /// documents there are, and how close the suggestion is to the word does
26998    /// not come into it at all, so the nearer of the two words here is second.
26999    #[test]
27000    fn a_spellcheck_scores_a_suggestion_by_how_common_it_is() {
27001        let mut f = Fixture::new();
27002        f.run(&[b"FT.CREATE", b"e", b"SCHEMA", b"t", b"TEXT", b"NOSTEM"]);
27003        f.run(&[b"HSET", b"d1", b"t", b"hello"]);
27004        f.run(&[b"HSET", b"d2", b"t", b"hallo hello"]);
27005        assert_eq!(
27006            f.run(&[b"FT.SPELLCHECK", b"e", b"hellp", b"DISTANCE", b"2"]),
27007            "*1\r\n*3\r\n$4\r\nTERM\r\n$5\r\nhellp\r\n\
27008             *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"
27009        );
27010    }
27011
27012    /// On RESP3 the whole thing is wrapped in a map under one name, a word
27013    /// carries a list of one pair maps, and the score is a double rather than
27014    /// a string.
27015    #[test]
27016    fn a_spellcheck_answers_a_map_of_maps_on_resp3() {
27017        let mut f = Fixture::new();
27018        f.run(&[b"HELLO", b"3"]);
27019        f.run(&[b"FT.CREATE", b"e", b"SCHEMA", b"t", b"TEXT", b"NOSTEM"]);
27020        f.run(&[b"HSET", b"d1", b"t", b"hello"]);
27021        assert_eq!(
27022            f.run(&[b"FT.SPELLCHECK", b"e", b"hellp"]),
27023            "%1\r\n$7\r\nresults\r\n%1\r\n$5\r\nhellp\r\n\
27024             *1\r\n%1\r\n$5\r\nhello\r\n,1\r\n"
27025        );
27026    }
27027
27028    /// A word the index already holds is not a mistake and is left out of the
27029    /// answer, and that check never looks at the field the query named, while
27030    /// the search for candidates does.
27031    #[test]
27032    fn a_word_the_index_holds_is_never_asked_about_whatever_field_it_names() {
27033        let mut f = Fixture::new();
27034        f.run(&[
27035            b"FT.CREATE",
27036            b"e",
27037            b"SCHEMA",
27038            b"a",
27039            b"TEXT",
27040            b"NOSTEM",
27041            b"b",
27042            b"TEXT",
27043            b"NOSTEM",
27044        ]);
27045        f.run(&[b"HSET", b"d1", b"b", b"world"]);
27046        assert_eq!(f.run(&[b"FT.SPELLCHECK", b"e", b"@a:world"]), "*0\r\n");
27047        assert_eq!(
27048            f.run(&[b"FT.SPELLCHECK", b"e", b"@a:worlt"]),
27049            "*1\r\n*3\r\n$4\r\nTERM\r\n$5\r\nworlt\r\n*0\r\n"
27050        );
27051    }
27052
27053    /// A dictionary named by `INCLUDE` adds words the index never read, scored
27054    /// zero and reported in the spelling the dictionary was given, and one
27055    /// named by `EXCLUDE` says a word is spelled right after all.
27056    #[test]
27057    fn a_spellcheck_reads_the_dictionaries_it_is_pointed_at() {
27058        let mut f = Fixture::new();
27059        f.run(&[b"FT.CREATE", b"e", b"SCHEMA", b"t", b"TEXT", b"NOSTEM"]);
27060        f.run(&[b"FT.DICTADD", b"d", b"Hellp", b"hellq"]);
27061        assert_eq!(
27062            f.run(&[b"FT.SPELLCHECK", b"e", b"hellz", b"TERMS", b"INCLUDE", b"d"]),
27063            "*1\r\n*3\r\n$4\r\nTERM\r\n$5\r\nhellz\r\n\
27064             *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"
27065        );
27066        assert_eq!(
27067            f.run(&[b"FT.SPELLCHECK", b"e", b"hellq", b"TERMS", b"EXCLUDE", b"d"]),
27068            "*0\r\n"
27069        );
27070        assert_eq!(
27071            f.run(&[b"FT.SPELLCHECK", b"e", b"x", b"TERMS", b"INCLUDE", b"nope"]),
27072            "-Dict does not exist: nope\r\n"
27073        );
27074    }
27075
27076    /// The first `DISTANCE` counts and the rest are dropped, an argument
27077    /// nobody recognises is stepped over rather than refused, and a distance
27078    /// outside one to four is the one thing here that does fail.
27079    #[test]
27080    fn a_spellcheck_reads_its_arguments_leniently() {
27081        let mut f = Fixture::new();
27082        f.run(&[b"FT.CREATE", b"e", b"SCHEMA", b"t", b"TEXT", b"NOSTEM"]);
27083        f.run(&[b"HSET", b"d1", b"t", b"hello"]);
27084        let one = "*1\r\n*3\r\n$4\r\nTERM\r\n$5\r\nhellp\r\n\
27085                   *1\r\n*2\r\n$1\r\n1\r\n$5\r\nhello\r\n";
27086        assert_eq!(f.run(&[b"FT.SPELLCHECK", b"e", b"hellp", b"BOGUS"]), one);
27087        let none = "*1\r\n*3\r\n$4\r\nTERM\r\n$5\r\nhelqp\r\n*0\r\n";
27088        let args: &[&[u8]] = &[
27089            b"FT.SPELLCHECK",
27090            b"e",
27091            b"helqp",
27092            b"DISTANCE",
27093            b"1",
27094            b"DISTANCE",
27095            b"4",
27096        ];
27097        assert_eq!(f.run(args), none);
27098        assert_eq!(
27099            f.run(&[b"FT.SPELLCHECK", b"e", b"hellp", b"DISTANCE", b"5"]),
27100            "-bad distance given, distance must be a natural number between 1 to 4\r\n"
27101        );
27102        assert_eq!(
27103            f.run(&[b"FT.SPELLCHECK", b"nope", b"hellp"]),
27104            "-SEARCH_INDEX_NOT_FOUND Index not found: nope\r\n"
27105        );
27106    }
27107
27108    // -------------------------------------------------------------- suggest
27109
27110    /// The reply is the size of the dictionary afterwards, which is neither
27111    /// what was added nor whether anything changed.
27112    #[test]
27113    fn an_add_answers_how_many_suggestions_are_in_there_now() {
27114        let mut f = Fixture::new();
27115        assert_eq!(f.run(&[b"FT.SUGADD", b"s", b"one", b"1"]), ":1\r\n");
27116        assert_eq!(f.run(&[b"FT.SUGADD", b"s", b"one", b"9"]), ":1\r\n");
27117        assert_eq!(f.run(&[b"FT.SUGADD", b"s", b"only", b"2"]), ":2\r\n");
27118        assert_eq!(f.run(&[b"FT.SUGLEN", b"s"]), ":2\r\n");
27119        assert_eq!(f.run(&[b"FT.SUGLEN", b"nokey"]), ":0\r\n");
27120    }
27121
27122    /// A suggestion dictionary is the one thing the search module puts in the
27123    /// keyspace, so every keyspace command reaches it.
27124    #[test]
27125    fn a_suggestion_dictionary_is_a_key_with_a_type_of_its_own() {
27126        let mut f = Fixture::new();
27127        f.run(&[b"FT.SUGADD", b"s", b"one", b"1"]);
27128        assert_eq!(f.run(&[b"TYPE", b"s"]), "+trietype0\r\n");
27129        assert_eq!(f.run(&[b"OBJECT", b"ENCODING", b"s"]), "$3\r\nraw\r\n");
27130        assert_eq!(f.run(&[b"EXISTS", b"s"]), ":1\r\n");
27131        assert_eq!(f.run(&[b"KEYS", b"*"]), "*1\r\n$1\r\ns\r\n");
27132        assert_eq!(f.run(&[b"EXPIRE", b"s", b"100"]), ":1\r\n");
27133        assert_eq!(f.run(&[b"TTL", b"s"]), ":100\r\n");
27134        assert_eq!(f.run(&[b"DEL", b"s"]), ":1\r\n");
27135        assert_eq!(f.run(&[b"FT.SUGLEN", b"s"]), ":0\r\n");
27136    }
27137
27138    /// The last suggestion out takes the key with it, which most module types
27139    /// do not do.
27140    #[test]
27141    fn deleting_the_last_suggestion_deletes_the_key() {
27142        let mut f = Fixture::new();
27143        f.run(&[b"FT.SUGADD", b"s", b"one", b"1"]);
27144        assert_eq!(f.run(&[b"FT.SUGDEL", b"s", b"nope"]), ":0\r\n");
27145        assert_eq!(f.run(&[b"FT.SUGDEL", b"s", b"one"]), ":1\r\n");
27146        assert_eq!(f.run(&[b"EXISTS", b"s"]), ":0\r\n");
27147        assert_eq!(f.run(&[b"FT.SUGDEL", b"nokey", b"a"]), ":0\r\n");
27148    }
27149
27150    /// A key holding anything else is refused rather than overwritten, on all
27151    /// four of them.
27152    #[test]
27153    fn a_suggestion_command_on_another_kind_of_key_is_wrongtype() {
27154        let mut f = Fixture::new();
27155        f.run(&[b"SET", b"s", b"x"]);
27156        for cmd in [
27157            vec![&b"FT.SUGADD"[..], b"s", b"t", b"1"],
27158            vec![&b"FT.SUGGET"[..], b"s", b"t"],
27159            vec![&b"FT.SUGDEL"[..], b"s", b"t"],
27160            vec![&b"FT.SUGLEN"[..], b"s"],
27161        ] {
27162            assert!(f.run(&cmd).starts_with("-WRONGTYPE"), "{cmd:?}");
27163        }
27164        assert_eq!(f.run(&[b"GET", b"s"]), "$1\r\nx\r\n");
27165    }
27166
27167    /// The scores in here were read off a real server, single precision and
27168    /// all. An exact match answers a sentinel so it sorts in front.
27169    #[test]
27170    fn a_lookup_answers_a_score_it_works_out_rather_than_the_one_stored() {
27171        let mut f = Fixture::new();
27172        f.run(&[b"FT.SUGADD", b"s", b"one", b"1"]);
27173        f.run(&[b"FT.SUGADD", b"s", b"only", b"2"]);
27174        f.run(&[b"FT.SUGADD", b"s", b"ontario", b"3"]);
27175        assert_eq!(
27176            f.run(&[b"FT.SUGGET", b"s", b"on", b"WITHSCORES"]),
27177            "*6\r\n$7\r\nontario\r\n$18\r\n1.2247449159622192\r\n\
27178             $4\r\nonly\r\n$17\r\n1.154700517654419\r\n\
27179             $3\r\none\r\n$18\r\n0.7071067690849304\r\n"
27180        );
27181        assert_eq!(
27182            f.run(&[b"FT.SUGGET", b"s", b"one", b"WITHSCORES"]),
27183            "*2\r\n$3\r\none\r\n$10\r\n2147483648\r\n"
27184        );
27185        assert_eq!(f.run(&[b"FT.SUGGET", b"nokey", b"a"]), "*0\r\n");
27186    }
27187
27188    /// `FUZZY` is one edit, and the edit is a rune rather than a byte.
27189    #[test]
27190    fn fuzzy_allows_one_edit_and_nothing_allows_two() {
27191        let mut f = Fixture::new();
27192        f.run(&[b"FT.SUGADD", b"s", b"only", b"2"]);
27193        assert_eq!(f.run(&[b"FT.SUGGET", b"s", b"one"]), "*0\r\n");
27194        assert_eq!(
27195            f.run(&[b"FT.SUGGET", b"s", b"one", b"FUZZY", b"WITHSCORES"]),
27196            "*2\r\n$4\r\nonly\r\n$19\r\n0.19139298796653748\r\n"
27197        );
27198        assert_eq!(f.run(&[b"FT.SUGGET", b"s", b"xyz", b"FUZZY"]), "*0\r\n");
27199    }
27200
27201    /// Five without a `MAX`, and the terms come back in score order.
27202    #[test]
27203    fn a_lookup_answers_five_unless_it_is_told_otherwise() {
27204        let mut f = Fixture::new();
27205        for (term, score) in [
27206            (&b"a1"[..], &b"1"[..]),
27207            (b"a2", b"2"),
27208            (b"a3", b"3"),
27209            (b"a4", b"4"),
27210            (b"a5", b"5"),
27211            (b"a6", b"6"),
27212        ] {
27213            f.run(&[b"FT.SUGADD", b"s", term, score]);
27214        }
27215        assert_eq!(
27216            f.run(&[b"FT.SUGGET", b"s", b"a"]),
27217            "*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"
27218        );
27219        assert_eq!(
27220            f.run(&[b"FT.SUGGET", b"s", b"a", b"MAX", b"2"]),
27221            "*2\r\n$2\r\na6\r\n$2\r\na5\r\n"
27222        );
27223        // A `MAX` larger than the dictionary answers what there is.
27224        assert!(
27225            f.run(&[b"FT.SUGGET", b"s", b"a", b"MAX", b"100"])
27226                .starts_with("*6\r\n")
27227        );
27228    }
27229
27230    /// A payload is replaced only when one is given, and an empty one is no
27231    /// payload at all.
27232    #[test]
27233    fn a_payload_comes_back_beside_the_term_or_a_null_does() {
27234        let mut f = Fixture::new();
27235        f.run(&[b"FT.SUGADD", b"s", b"one", b"1", b"PAYLOAD", b"p"]);
27236        assert_eq!(
27237            f.run(&[b"FT.SUGGET", b"s", b"o", b"WITHPAYLOADS"]),
27238            "*2\r\n$3\r\none\r\n$1\r\np\r\n"
27239        );
27240        f.run(&[b"FT.SUGADD", b"s", b"one", b"2"]);
27241        assert_eq!(
27242            f.run(&[b"FT.SUGGET", b"s", b"o", b"WITHPAYLOADS"]),
27243            "*2\r\n$3\r\none\r\n$1\r\np\r\n"
27244        );
27245        // An empty payload is the same as not having given one at all, so it
27246        // leaves the payload where it is rather than clearing it.
27247        f.run(&[b"FT.SUGADD", b"s", b"one", b"2", b"PAYLOAD", b""]);
27248        assert_eq!(
27249            f.run(&[b"FT.SUGGET", b"s", b"o", b"WITHPAYLOADS"]),
27250            "*2\r\n$3\r\none\r\n$1\r\np\r\n"
27251        );
27252        // A term that never had one answers a null.
27253        f.run(&[b"FT.SUGADD", b"s", b"other", b"1", b"PAYLOAD", b""]);
27254        assert_eq!(
27255            f.run(&[b"FT.SUGGET", b"s", b"ot", b"WITHPAYLOADS"]),
27256            "*2\r\n$5\r\nother\r\n$-1\r\n"
27257        );
27258    }
27259
27260    /// `INCR` adds to the score that is there rather than replacing it, and
27261    /// three tenths a tenth at a time is the reading that shows the score is
27262    /// held in single precision.
27263    #[test]
27264    fn incr_adds_to_the_score_that_is_already_there() {
27265        let mut f = Fixture::new();
27266        for _ in 0..3 {
27267            f.run(&[b"FT.SUGADD", b"s", b"xxx", b"0.1", b"INCR"]);
27268        }
27269        assert_eq!(
27270            f.run(&[b"FT.SUGGET", b"s", b"xx", b"WITHSCORES"]),
27271            "*2\r\n$3\r\nxxx\r\n$18\r\n0.2121320366859436\r\n"
27272        );
27273    }
27274
27275    /// The five error sentences, none of which are written the same way.
27276    #[test]
27277    fn the_suggestion_errors_are_the_lines_the_module_sends() {
27278        let mut f = Fixture::new();
27279        f.run(&[b"FT.SUGADD", b"s", b"one", b"1"]);
27280        assert_eq!(
27281            f.run(&[b"FT.SUGADD", b"s", b"t", b"abc"]),
27282            "-ERR invalid score\r\n"
27283        );
27284        // The unknown word is complained about before the score is converted.
27285        assert_eq!(
27286            f.run(&[b"FT.SUGADD", b"s", b"t", b"abc", b"NOPE"]),
27287            "-Unknown argument `NOPE`\r\n"
27288        );
27289        assert_eq!(
27290            f.run(&[b"FT.SUGADD", b"s", b"t", b"1", b"PAYLOAD"]),
27291            "-Invalid payload: Expected an argument, but none provided\r\n"
27292        );
27293        // Too many words is an arity error and not an unknown argument.
27294        assert!(
27295            f.run(&[
27296                b"FT.SUGADD",
27297                b"s",
27298                b"t",
27299                b"1",
27300                b"PAYLOAD",
27301                b"a",
27302                b"PAYLOAD",
27303                b"b"
27304            ])
27305            .contains("wrong number of arguments")
27306        );
27307        assert_eq!(
27308            f.run(&[b"FT.SUGGET", b"s", b"o", b"NOPE"]),
27309            "-SEARCH_PARSE_ARGS Unrecognized argument: NOPE\r\n"
27310        );
27311        // A count read as a whole number and then found to be out of range,
27312        // against one that had to be read as a double first, where anything
27313        // under one is a conversion that failed rather than a range that did.
27314        for max in [&b"0"[..], b"-1", b"4294967296", b"1e10", b"inf"] {
27315            assert_eq!(
27316                f.run(&[b"FT.SUGGET", b"s", b"o", b"MAX", max]),
27317                "-SEARCH_PARSE_ARGS MAX: Value is outside acceptable bounds\r\n",
27318                "{}",
27319                String::from_utf8_lossy(max)
27320            );
27321        }
27322        for max in [
27323            &b"abc"[..],
27324            b"0.0",
27325            b"00",
27326            b"-0",
27327            b"+0",
27328            b"0.5",
27329            b"-1.5",
27330            b"1e400",
27331        ] {
27332            assert_eq!(
27333                f.run(&[b"FT.SUGGET", b"s", b"o", b"MAX", max]),
27334                "-SEARCH_PARSE_ARGS MAX: Could not convert argument to expected type\r\n",
27335                "{}",
27336                String::from_utf8_lossy(max)
27337            );
27338        }
27339        for max in [&b"01"[..], b"+1", b"1.5", b"0x10", b"1e2"] {
27340            assert_eq!(
27341                f.run(&[b"FT.SUGGET", b"s", b"o", b"MAX", max]),
27342                "*1\r\n$3\r\none\r\n",
27343                "{}",
27344                String::from_utf8_lossy(max)
27345            );
27346        }
27347        assert_eq!(
27348            f.run(&[b"FT.SUGGET", b"s", b"o", b"MAX"]),
27349            "-SEARCH_PARSE_ARGS MAX: Expected an argument, but none provided\r\n"
27350        );
27351        // A score too large for a double is refused where one spelled out is
27352        // taken, which is the module reading errno after the conversion.
27353        assert_eq!(
27354            f.run(&[b"FT.SUGADD", b"s", b"t", b"1e400"]),
27355            "-ERR invalid score\r\n"
27356        );
27357        assert_eq!(f.run(&[b"FT.SUGADD", b"s", b"t", b"inf"]), ":2\r\n");
27358    }
27359
27360    /// An empty term is taken and not stored, so the reply is the length that
27361    /// was already there and nothing new comes back. The key is still made,
27362    /// and a delete that finds nothing is what clears it away again.
27363    #[test]
27364    fn an_empty_suggestion_is_taken_and_dropped_but_still_makes_the_key() {
27365        let mut f = Fixture::new();
27366        f.run(&[b"FT.SUGADD", b"s", b"one", b"1"]);
27367        assert_eq!(f.run(&[b"FT.SUGADD", b"s", b"", b"1"]), ":1\r\n");
27368        assert_eq!(f.run(&[b"FT.SUGGET", b"s", b""]), "*1\r\n$3\r\none\r\n");
27369        assert_eq!(f.run(&[b"FT.SUGADD", b"e", b"", b"1"]), ":0\r\n");
27370        assert_eq!(f.run(&[b"EXISTS", b"e"]), ":1\r\n");
27371        assert_eq!(f.run(&[b"TYPE", b"e"]), "+trietype0\r\n");
27372        assert_eq!(f.run(&[b"FT.SUGDEL", b"e", b"nothing"]), ":0\r\n");
27373        assert_eq!(f.run(&[b"EXISTS", b"e"]), ":0\r\n");
27374    }
27375
27376    /// A key that will not read is counted against the index and against the
27377    /// field, and `FT.INFO` says so.
27378    #[test]
27379    fn a_hash_that_will_not_read_is_counted_where_ft_info_reports_it() {
27380        let mut f = Fixture::new();
27381        f.run(&[
27382            b"FT.CREATE",
27383            b"ix",
27384            b"PREFIX",
27385            b"1",
27386            b"p:",
27387            b"SCHEMA",
27388            b"n",
27389            b"NUMERIC",
27390        ]);
27391        f.run(&[b"HSET", b"p:1", b"n", b"notanumber"]);
27392        assert_eq!(held(&f, b"ix"), (0, 0));
27393
27394        let reply = f.run(&[b"FT.INFO", b"ix"]);
27395        assert!(
27396            reply.contains("SEARCH_NUMERIC_VALUE_INVALID Invalid numeric value: 'notanumber'"),
27397            "{reply}"
27398        );
27399        assert!(reply.contains("hash_indexing_failures"), "{reply}");
27400    }
27401
27402    /// An index can only be made on database zero, and the check comes after
27403    /// the `IFNX` shortcut and before everything else.
27404    #[test]
27405    fn an_index_can_only_be_made_on_database_zero() {
27406        let mut f = Fixture::new();
27407        f.run(&[b"FT.CREATE", b"ix", b"SCHEMA", b"t", b"TEXT"]);
27408        f.run(&[b"SELECT", b"1"]);
27409        let refused = "-Cannot create index on db != 0\r\n";
27410        assert_eq!(
27411            f.run(&[b"FT.CREATE", b"jx", b"SCHEMA", b"t", b"TEXT"]),
27412            refused
27413        );
27414        // The name is taken, and it still answers about the database.
27415        assert_eq!(
27416            f.run(&[b"FT.CREATE", b"ix", b"SCHEMA", b"t", b"TEXT"]),
27417            refused
27418        );
27419        // And so does one whose arguments are nonsense.
27420        assert_eq!(
27421            f.run(&[b"FT.CREATE", b"zz", b"BOGUS", b"SCHEMA", b"t", b"TEXT"]),
27422            refused
27423        );
27424        // `IFNX` over a name that is taken is the one that gets through.
27425        assert_eq!(
27426            f.run(&[b"FT._CREATEIFNX", b"ix", b"SCHEMA", b"t", b"TEXT"]),
27427            "+OK\r\n"
27428        );
27429        assert_eq!(f.server.search.lock().len(), 1);
27430    }
27431
27432    /// The scan reads the database the create was run on, and after that the
27433    /// index follows its keys in every database.
27434    ///
27435    /// The asymmetry is a real server's, measured, and it is the sort of thing
27436    /// nobody would arrive at by choosing.
27437    #[test]
27438    fn the_scan_is_one_database_and_the_following_is_all_of_them() {
27439        let mut f = Fixture::new();
27440        f.run(&[b"SELECT", b"1"]);
27441        f.run(&[b"HSET", b"p:9", b"t", b"on one"]);
27442        f.run(&[b"SELECT", b"0"]);
27443        f.run(&[b"HSET", b"p:0", b"t", b"on zero"]);
27444        f.run(&[
27445            b"FT.CREATE",
27446            b"ix",
27447            b"PREFIX",
27448            b"1",
27449            b"p:",
27450            b"SCHEMA",
27451            b"t",
27452            b"TEXT",
27453        ]);
27454        assert_eq!(held(&f, b"ix"), (1, 1), "the scan read database zero only");
27455
27456        f.run(&[b"SELECT", b"1"]);
27457        f.run(&[b"HSET", b"p:8", b"t", b"later"]);
27458        assert_eq!(
27459            held(&f, b"ix"),
27460            (2, 2),
27461            "and then it follows every database"
27462        );
27463    }
27464
27465    /// Four documents over the two kinds of field a query can ask about, which
27466    /// is the corpus the searches below read.
27467    fn corpus(f: &mut Fixture) {
27468        f.run(&[
27469            b"FT.CREATE",
27470            b"sx",
27471            b"PREFIX",
27472            b"1",
27473            b"d:",
27474            b"SCHEMA",
27475            b"t",
27476            b"TEXT",
27477            b"g",
27478            b"TAG",
27479            b"n",
27480            b"NUMERIC",
27481        ]);
27482        for (key, text, tag, number) in [
27483            (b"d:1".as_slice(), "alpha beta", "aa,bb", "1"),
27484            (b"d:2", "alpha gamma", "bb", "2"),
27485            (b"d:3", "delta", "cc", "3"),
27486            (b"d:4", "alpha beta gamma", "aa,cc", "4"),
27487        ] {
27488            f.run(&[
27489                b"HSET",
27490                key,
27491                b"t",
27492                text.as_bytes(),
27493                b"g",
27494                tag.as_bytes(),
27495                b"n",
27496                number.as_bytes(),
27497            ]);
27498        }
27499    }
27500
27501    /// A corpus with something to sort by: a text field the index keeps a copy
27502    /// of, a number, the same text field under another name, and a text field
27503    /// the index keeps nothing of.
27504    fn sortable(f: &mut Fixture) {
27505        f.run(&[
27506            b"FT.CREATE",
27507            b"sy",
27508            b"PREFIX",
27509            b"1",
27510            b"s:",
27511            b"SCHEMA",
27512            b"t",
27513            b"TEXT",
27514            b"SORTABLE",
27515            b"n",
27516            b"NUMERIC",
27517            b"SORTABLE",
27518            b"body",
27519            b"AS",
27520            b"b",
27521            b"TEXT",
27522            b"SORTABLE",
27523            b"p",
27524            b"TEXT",
27525        ]);
27526        for (key, text, number) in [
27527            (b"s:1".as_slice(), "Banana Split", "2"),
27528            (b"s:2", "apple", "10"),
27529        ] {
27530            f.run(&[
27531                b"HSET",
27532                key,
27533                b"t",
27534                text.as_bytes(),
27535                b"n",
27536                number.as_bytes(),
27537                b"body",
27538                text.as_bytes(),
27539                b"p",
27540                b"alpha",
27541            ]);
27542        }
27543        // A key with nothing under either sortable field, which is what sorts
27544        // last whichever way round the sort runs.
27545        f.run(&[b"HSET", b"s:3", b"p", b"alpha"]);
27546    }
27547
27548    /// A sort runs off the copy of the value the index keeps, and a row with no
27549    /// value at all is last both ways round.
27550    #[test]
27551    fn a_search_sorts_by_a_field_the_index_keeps_a_copy_of() {
27552        let mut f = Fixture::new();
27553        sortable(&mut f);
27554        assert_eq!(
27555            f.run(&[b"FT.SEARCH", b"sy", b"alpha", b"SORTBY", b"n", b"NOCONTENT"]),
27556            "*4\r\n:3\r\n$3\r\ns:1\r\n$3\r\ns:2\r\n$3\r\ns:3\r\n"
27557        );
27558        assert_eq!(
27559            f.run(&[
27560                b"FT.SEARCH",
27561                b"sy",
27562                b"alpha",
27563                b"SORTBY",
27564                b"n",
27565                b"DESC",
27566                b"NOCONTENT"
27567            ]),
27568            "*4\r\n:3\r\n$3\r\ns:2\r\n$3\r\ns:1\r\n$3\r\ns:3\r\n"
27569        );
27570        // The copy of a text field is folded, so `apple` sorts before
27571        // `Banana Split` where a comparison of the bytes would not.
27572        assert_eq!(
27573            f.run(&[b"FT.SEARCH", b"sy", b"alpha", b"SORTBY", b"t", b"NOCONTENT"]),
27574            "*4\r\n:3\r\n$3\r\ns:2\r\n$3\r\ns:1\r\n$3\r\ns:3\r\n"
27575        );
27576    }
27577
27578    /// A field the index keeps no copy of is sorted by the value read off the
27579    /// key, which happens after the walk rather than during it.
27580    #[test]
27581    fn a_search_sorts_by_a_field_it_has_to_read_the_key_for() {
27582        let mut f = Fixture::new();
27583        sortable(&mut f);
27584        f.run(&[b"HSET", b"s:1", b"p", b"alpha zulu"]);
27585        assert_eq!(
27586            f.run(&[
27587                b"FT.SEARCH",
27588                b"sy",
27589                b"alpha",
27590                b"SORTBY",
27591                b"p",
27592                b"NOCONTENT",
27593                b"LIMIT",
27594                b"0",
27595                b"2"
27596            ]),
27597            "*3\r\n:3\r\n$3\r\ns:2\r\n$3\r\ns:3\r\n"
27598        );
27599        // Nothing is folded on this side, because the schema never asked for a
27600        // copy to fold, so the value goes into the sort as it was written.
27601        assert_eq!(
27602            f.run(&[
27603                b"FT.SEARCH",
27604                b"sy",
27605                b"alpha",
27606                b"SORTBY",
27607                b"p",
27608                b"WITHSORTKEYS",
27609                b"NOCONTENT",
27610                b"LIMIT",
27611                b"2",
27612                b"1"
27613            ]),
27614            "*3\r\n:3\r\n$3\r\ns:1\r\n$11\r\n$alpha zulu\r\n"
27615        );
27616    }
27617
27618    /// The value the sort compared goes beside every row, as a number after a
27619    /// hash, as text after a dollar, and as a null on a row that had none.
27620    #[test]
27621    fn a_search_can_send_the_value_it_sorted_by_back() {
27622        let mut f = Fixture::new();
27623        sortable(&mut f);
27624        assert_eq!(
27625            f.run(&[
27626                b"FT.SEARCH",
27627                b"sy",
27628                b"alpha",
27629                b"SORTBY",
27630                b"n",
27631                b"WITHSORTKEYS",
27632                b"NOCONTENT"
27633            ]),
27634            concat!(
27635                "*7\r\n:3\r\n",
27636                "$3\r\ns:1\r\n$2\r\n#2\r\n",
27637                "$3\r\ns:2\r\n$3\r\n#10\r\n",
27638                "$3\r\ns:3\r\n$-1\r\n"
27639            )
27640        );
27641        assert_eq!(
27642            f.run(&[
27643                b"FT.SEARCH",
27644                b"sy",
27645                b"alpha",
27646                b"SORTBY",
27647                b"t",
27648                b"WITHSORTKEYS",
27649                b"NOCONTENT"
27650            ]),
27651            concat!(
27652                "*7\r\n:3\r\n",
27653                "$3\r\ns:2\r\n$6\r\n$apple\r\n",
27654                "$3\r\ns:1\r\n$13\r\n$banana split\r\n",
27655                "$3\r\ns:3\r\n$-1\r\n"
27656            )
27657        );
27658        // Asking for a sort key without sorting is taken and answers a null on
27659        // every row, which is what a real server does.
27660        assert_eq!(
27661            f.run(&[
27662                b"FT.SEARCH",
27663                b"sy",
27664                b"banana",
27665                b"WITHSORTKEYS",
27666                b"NOCONTENT"
27667            ]),
27668            "*3\r\n:1\r\n$3\r\ns:1\r\n$-1\r\n"
27669        );
27670    }
27671
27672    /// The field a search sorted by is written in front of the fields of the
27673    /// key, and the key's own value for it wins when the two share a name.
27674    #[test]
27675    fn a_sort_puts_the_field_it_sorted_by_in_front_of_the_row() {
27676        let mut f = Fixture::new();
27677        sortable(&mut f);
27678        // `b` is what the schema calls the field the key calls `body`, so the
27679        // folded copy comes back under one name and the value as it was written
27680        // comes back under the other.
27681        assert_eq!(
27682            f.run(&[
27683                b"FT.SEARCH",
27684                b"sy",
27685                b"alpha",
27686                b"SORTBY",
27687                b"b",
27688                b"LIMIT",
27689                b"0",
27690                b"1"
27691            ]),
27692            concat!(
27693                "*3\r\n:3\r\n$3\r\ns:2\r\n*10\r\n",
27694                "$1\r\nb\r\n$5\r\napple\r\n",
27695                "$1\r\nt\r\n$5\r\napple\r\n",
27696                "$1\r\nn\r\n$2\r\n10\r\n",
27697                "$4\r\nbody\r\n$5\r\napple\r\n",
27698                "$1\r\np\r\n$5\r\nalpha\r\n"
27699            )
27700        );
27701        // With a `RETURN` list there is nothing to put in, so the field is moved
27702        // to the front of the names that were asked for instead.
27703        assert_eq!(
27704            f.run(&[
27705                b"FT.SEARCH",
27706                b"sy",
27707                b"alpha",
27708                b"SORTBY",
27709                b"b",
27710                b"RETURN",
27711                b"2",
27712                b"p",
27713                b"b",
27714                b"LIMIT",
27715                b"0",
27716                b"1"
27717            ]),
27718            concat!(
27719                "*3\r\n:3\r\n$3\r\ns:2\r\n*4\r\n",
27720                "$1\r\nb\r\n$5\r\napple\r\n",
27721                "$1\r\np\r\n$5\r\nalpha\r\n"
27722            )
27723        );
27724    }
27725
27726    /// The four ways a `SORTBY` on a search is refused.
27727    #[test]
27728    fn a_search_refuses_the_sorts_it_cannot_run() {
27729        let mut f = Fixture::new();
27730        sortable(&mut f);
27731        assert_eq!(
27732            f.run(&[b"FT.SEARCH", b"sy", b"alpha", b"SORTBY"]),
27733            "-SEARCH_PARSE_ARGS Bad SORTBY arguments\r\n"
27734        );
27735        assert_eq!(
27736            f.run(&[b"FT.SEARCH", b"sy", b"alpha", b"SORTBY", b"n", b"SORTBY"]),
27737            "-SEARCH_PARSE_ARGS Multiple SORTBY steps are not allowed\r\n"
27738        );
27739        assert_eq!(
27740            f.run(&[b"FT.SEARCH", b"sy", b"alpha", b"SORTBY", b"n", b"MAX", b"2"]),
27741            "-SEARCH_PARSE_ARGS SORTBY MAX is not supported by FT.SEARCH\r\n"
27742        );
27743        assert_eq!(
27744            f.run(&[b"FT.SEARCH", b"sy", b"alpha", b"SORTBY", b"zz"]),
27745            "-SEARCH_PROP_NOT_FOUND Property `zz` not loaded nor in schema\r\n"
27746        );
27747        // The property is looked up once the whole list has read cleanly, so a
27748        // word after it that nobody knows is the error that comes back.
27749        assert_eq!(
27750            f.run(&[b"FT.SEARCH", b"sy", b"alpha", b"SORTBY", b"zz", b"NOPE"]),
27751            "-SEARCH_ARG_UNRECOGNIZED Unknown argument `NOPE` at position 3 for <main>\r\n"
27752        );
27753    }
27754
27755    /// An index over two text fields, a number and a tag, holding one key whose
27756    /// `a` runs long enough to be worth cutting down and whose `b` and `g` hold
27757    /// nothing the query matches.
27758    fn marking(f: &mut Fixture) {
27759        f.run(&[
27760            b"FT.CREATE",
27761            b"mk",
27762            b"ON",
27763            b"HASH",
27764            b"PREFIX",
27765            b"1",
27766            b"m:",
27767            b"SCHEMA",
27768            b"a",
27769            b"TEXT",
27770            b"b",
27771            b"TEXT",
27772            b"n",
27773            b"NUMERIC",
27774            b"g",
27775            b"TAG",
27776        ]);
27777        f.run(&[
27778            b"HSET",
27779            b"m:1",
27780            b"a",
27781            b"c1 c2 c3 fox d1 d2 d3 d4 d5 d6 d7 d8 d9 fox e1 e2 e3",
27782            b"b",
27783            b"t1 t2 t3 t4 t5 t6 t7 t8",
27784            b"n",
27785            b"1",
27786            b"g",
27787            b"red",
27788        ]);
27789    }
27790
27791    /// A field the query matched comes back as fragments and a field it did not
27792    /// comes back as its own front.
27793    #[test]
27794    fn a_summarize_cuts_a_field_down_to_what_matched() {
27795        let mut f = Fixture::new();
27796        marking(&mut f);
27797        let got = f.run(&[b"FT.SEARCH", b"mk", b"fox", b"SUMMARIZE", b"LEN", b"2"]);
27798        assert!(got.contains("c3 fox d1 d2... d9 fox e1 e2... "), "{got}");
27799        // `b` holds no match, so it keeps its front and loses its last word.
27800        assert!(got.contains("t1 t2 t3 t4 t5 t6 t7\r\n"), "{got}");
27801        // And so does the tag, which is a value like any other to this clause.
27802        assert!(got.contains("$1\r\nr\r\n"), "{got}");
27803    }
27804
27805    /// `FRAGS` is applied before the context either side of a fragment is worked
27806    /// out, so the fragment that is left runs over the match of the one that was
27807    /// dropped rather than stopping on it.
27808    #[test]
27809    fn a_dropped_fragment_stops_bounding_the_one_that_was_kept() {
27810        let mut f = Fixture::new();
27811        marking(&mut f);
27812        let got = f.run(&[
27813            b"FT.SEARCH",
27814            b"mk",
27815            b"fox",
27816            b"SUMMARIZE",
27817            b"FRAGS",
27818            b"1",
27819            b"LEN",
27820            b"20",
27821        ]);
27822        assert!(
27823            got.contains("c2 c3 fox d1 d2 d3 d4 d5 d6 d7 d8 d9 fox e1 e2... "),
27824            "{got}"
27825        );
27826        // Keep both and the first stops on the second rather than running over
27827        // it, on the same query and the same budget.
27828        let two = f.run(&[
27829            b"FT.SEARCH",
27830            b"mk",
27831            b"fox",
27832            b"SUMMARIZE",
27833            b"FRAGS",
27834            b"2",
27835            b"LEN",
27836            b"20",
27837        ]);
27838        assert!(
27839            two.contains("c2 c3 fox d1 d2 d3 d4 d5 d6 d7 d8 d9... d1"),
27840            "{two}"
27841        );
27842    }
27843
27844    /// A `HIGHLIGHT` wraps every match, and on a field with no match in it the
27845    /// clause also calls off the cutting down a `SUMMARIZE` would have done.
27846    #[test]
27847    fn a_highlight_marks_the_matches_and_leaves_the_rest_of_the_field_alone() {
27848        let mut f = Fixture::new();
27849        marking(&mut f);
27850        let got = f.run(&[b"FT.SEARCH", b"mk", b"fox", b"HIGHLIGHT"]);
27851        assert!(got.contains("<b>fox</b> d1 d2"), "{got}");
27852        let both = f.run(&[
27853            b"FT.SEARCH",
27854            b"mk",
27855            b"fox",
27856            b"SUMMARIZE",
27857            b"LEN",
27858            b"2",
27859            b"HIGHLIGHT",
27860        ]);
27861        assert!(both.contains("c3 <b>fox</b> d1 d2... "), "{both}");
27862        // `b` still holds no match, and this time it comes back whole.
27863        assert!(both.contains("t1 t2 t3 t4 t5 t6 t7 t8\r\n"), "{both}");
27864        assert!(both.contains("$3\r\nred\r\n"), "{both}");
27865        // Naming a field one clause does not cover leaves it cut down again.
27866        let split = f.run(&[
27867            b"FT.SEARCH",
27868            b"mk",
27869            b"fox",
27870            b"SUMMARIZE",
27871            b"FIELDS",
27872            b"1",
27873            b"b",
27874            b"LEN",
27875            b"2",
27876            b"HIGHLIGHT",
27877            b"FIELDS",
27878            b"1",
27879            b"a",
27880        ]);
27881        assert!(split.contains("t1 t2 t3 t4 t5 t6 t7\r\n"), "{split}");
27882    }
27883
27884    /// A tag is never marked, in its own field or in a text field beside it.
27885    #[test]
27886    fn a_highlight_does_not_mark_a_tag() {
27887        let mut f = Fixture::new();
27888        marking(&mut f);
27889        f.run(&[b"HSET", b"m:1", b"b", b"red and blue"]);
27890        let got = f.run(&[b"FT.SEARCH", b"mk", b"@g:{red}", b"HIGHLIGHT"]);
27891        assert!(!got.contains("<b>"), "{got}");
27892        assert!(got.contains("red and blue"), "{got}");
27893    }
27894
27895    /// A search answers a total and then a row for every key in the window,
27896    /// with the fields of that key after it.
27897    #[test]
27898    fn a_search_answers_a_total_and_then_the_rows() {
27899        let mut f = Fixture::new();
27900        corpus(&mut f);
27901        assert_eq!(
27902            f.run(&[b"FT.SEARCH", b"sx", b"delta"]),
27903            "*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"
27904        );
27905        // The fields are what the key holds and not what the schema names, so
27906        // a field nobody indexed comes back too.
27907        f.run(&[b"HSET", b"d:3", b"extra", b"more"]);
27908        assert!(f.run(&[b"FT.SEARCH", b"sx", b"delta"]).contains("extra"));
27909        // `NOCONTENT` leaves the keys on their own, and `LIMIT 0 0` leaves
27910        // the total on its own.
27911        assert_eq!(
27912            f.run(&[b"FT.SEARCH", b"sx", b"delta", b"NOCONTENT"]),
27913            "*2\r\n:1\r\n$3\r\nd:3\r\n"
27914        );
27915        assert_eq!(
27916            f.run(&[b"FT.SEARCH", b"sx", b"alpha", b"LIMIT", b"0", b"0"]),
27917            "*1\r\n:3\r\n"
27918        );
27919    }
27920
27921    /// The window is ten rows when nobody said, and the cap is on how wide it
27922    /// is rather than on where it starts.
27923    #[test]
27924    fn the_window_is_ten_rows_and_a_million_wide_at_most() {
27925        let mut f = Fixture::new();
27926        corpus(&mut f);
27927        assert_eq!(
27928            f.run(&[
27929                b"FT.SEARCH",
27930                b"sx",
27931                b"alpha",
27932                b"NOCONTENT",
27933                b"LIMIT",
27934                b"1",
27935                b"1"
27936            ]),
27937            "*2\r\n:3\r\n$3\r\nd:2\r\n"
27938        );
27939        assert_eq!(
27940            f.run(&[b"FT.SEARCH", b"sx", b"alpha", b"LIMIT", b"0"]),
27941            "-SEARCH_PARSE_ARGS LIMIT requires two arguments\r\n"
27942        );
27943        assert_eq!(
27944            f.run(&[b"FT.SEARCH", b"sx", b"alpha", b"LIMIT", b"0", b"-1"]),
27945            "-SEARCH_PARSE_ARGS LIMIT needs two numeric arguments\r\n"
27946        );
27947        assert_eq!(
27948            f.run(&[b"FT.SEARCH", b"sx", b"alpha", b"LIMIT", b"0", b"1000001"]),
27949            "-SEARCH_LIMIT_OVER LIMIT exceeds maximum of 1000000\r\n"
27950        );
27951        assert_eq!(
27952            f.run(&[
27953                b"FT.SEARCH",
27954                b"sx",
27955                b"alpha",
27956                b"NOCONTENT",
27957                b"LIMIT",
27958                b"999999",
27959                b"1000000"
27960            ]),
27961            "*1\r\n:3\r\n"
27962        );
27963    }
27964
27965    /// `RETURN 0` reads on the wire like `NOCONTENT` and is not the same
27966    /// thing, because a later `RETURN` puts the fields back and a later
27967    /// `RETURN` after a `NOCONTENT` does not.
27968    #[test]
27969    fn a_return_of_nothing_is_not_the_same_as_nocontent() {
27970        let mut f = Fixture::new();
27971        corpus(&mut f);
27972        let bare = "*2\r\n:1\r\n$3\r\nd:3\r\n";
27973        assert_eq!(
27974            f.run(&[b"FT.SEARCH", b"sx", b"delta", b"RETURN", b"0"]),
27975            bare
27976        );
27977        assert_eq!(
27978            f.run(&[
27979                b"FT.SEARCH",
27980                b"sx",
27981                b"delta",
27982                b"NOCONTENT",
27983                b"RETURN",
27984                b"1",
27985                b"t"
27986            ]),
27987            bare
27988        );
27989        assert_eq!(
27990            f.run(&[
27991                b"FT.SEARCH",
27992                b"sx",
27993                b"delta",
27994                b"RETURN",
27995                b"0",
27996                b"RETURN",
27997                b"1",
27998                b"t"
27999            ]),
28000            "*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"
28001        );
28002    }
28003
28004    /// The count after `RETURN` counts words and not fields, so the `AS` and
28005    /// the name after it are two of them.
28006    #[test]
28007    fn the_count_after_return_counts_words() {
28008        let mut f = Fixture::new();
28009        corpus(&mut f);
28010        // Two words is one renamed field, and the name is the one it comes
28011        // back under.
28012        assert_eq!(
28013            f.run(&[
28014                b"FT.SEARCH",
28015                b"sx",
28016                b"delta",
28017                b"RETURN",
28018                b"3",
28019                b"t",
28020                b"AS",
28021                b"x"
28022            ]),
28023            "*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"
28024        );
28025        // A count that stops on the `AS` has nothing to rename to, and one
28026        // that reaches past the last word is short an argument.
28027        assert_eq!(
28028            f.run(&[b"FT.SEARCH", b"sx", b"delta", b"RETURN", b"2", b"t", b"AS"]),
28029            "-SEARCH_PARSE_ARGS RETURN path AS name - must be accompanied with NAME\r\n"
28030        );
28031        assert_eq!(
28032            f.run(&[b"FT.SEARCH", b"sx", b"delta", b"RETURN", b"3", b"t", b"AS"]),
28033            "-SEARCH_PARSE_ARGS Bad arguments for RETURN: Expected an argument, but none provided\r\n"
28034        );
28035        // A count that stops before the `AS` asks for a field called `AS`,
28036        // which no key holds, and a field the key does not hold is left out
28037        // rather than sent empty.
28038        assert_eq!(
28039            f.run(&[b"FT.SEARCH", b"sx", b"delta", b"RETURN", b"1", b"AS"]),
28040            "*3\r\n:1\r\n$3\r\nd:3\r\n*0\r\n"
28041        );
28042    }
28043
28044    /// A `FILTER` is a numeric range written outside the query, and it is only
28045    /// the wrong way round on a field the schema holds as a number.
28046    #[test]
28047    fn a_filter_is_a_range_written_outside_the_query() {
28048        let mut f = Fixture::new();
28049        corpus(&mut f);
28050        assert_eq!(
28051            f.run(&[
28052                b"FT.SEARCH",
28053                b"sx",
28054                b"alpha",
28055                b"NOCONTENT",
28056                b"FILTER",
28057                b"n",
28058                b"2",
28059                b"4"
28060            ]),
28061            "*3\r\n:2\r\n$3\r\nd:2\r\n$3\r\nd:4\r\n"
28062        );
28063        assert_eq!(
28064            f.run(&[b"FT.SEARCH", b"sx", b"alpha", b"FILTER", b"n", b"2"]),
28065            "-SEARCH_PARSE_ARGS FILTER requires 3 arguments\r\n"
28066        );
28067        assert_eq!(
28068            f.run(&[b"FT.SEARCH", b"sx", b"alpha", b"FILTER", b"n", b"x", b"1"]),
28069            "-SEARCH_PARSE_ARGS Bad lower range: x\r\n"
28070        );
28071        assert_eq!(
28072            f.run(&[b"FT.SEARCH", b"sx", b"alpha", b"FILTER", b"n", b"2", b"1"]),
28073            "-SEARCH_SYNTAX Invalid numeric range (min > max): @n:[2.000000 1.000000]\r\n"
28074        );
28075        // The same range on a field that is not a number at all, and on a
28076        // field that is not there, answers nothing rather than refusing.
28077        for field in [b"g".as_slice(), b"nope"] {
28078            assert_eq!(
28079                f.run(&[b"FT.SEARCH", b"sx", b"alpha", b"FILTER", field, b"2", b"1"]),
28080                "*1\r\n:0\r\n"
28081            );
28082        }
28083    }
28084
28085    /// The index is resolved before the arguments after it are read, so a name
28086    /// that is not there answers about the name whatever else is wrong.
28087    #[test]
28088    fn the_index_is_found_before_the_arguments_are_read() {
28089        let mut f = Fixture::new();
28090        corpus(&mut f);
28091        let missing = "-SEARCH_INDEX_NOT_FOUND Index not found: nope\r\n";
28092        assert_eq!(f.run(&[b"FT.SEARCH", b"nope", b"alpha", b"BOGUS"]), missing);
28093        assert_eq!(
28094            f.run(&[b"FT.EXPLAIN", b"nope", b"alpha", b"BOGUS"]),
28095            missing
28096        );
28097        // And the arguments are read before the query is, so a query that
28098        // will not parse still answers about the argument.
28099        assert_eq!(
28100            f.run(&[b"FT.SEARCH", b"sx", b"@@@", b"BOGUS"]),
28101            "-SEARCH_ARG_UNRECOGNIZED Unknown argument `BOGUS` at position 1 for <main>\r\n"
28102        );
28103    }
28104
28105    /// `INKEYS` filters the answer before the total is taken, which is not
28106    /// where a client would guess it happens.
28107    #[test]
28108    fn inkeys_comes_off_the_total() {
28109        let mut f = Fixture::new();
28110        corpus(&mut f);
28111        assert_eq!(
28112            f.run(&[
28113                b"FT.SEARCH",
28114                b"sx",
28115                b"alpha",
28116                b"NOCONTENT",
28117                b"INKEYS",
28118                b"1",
28119                b"d:1"
28120            ]),
28121            "*2\r\n:1\r\n$3\r\nd:1\r\n"
28122        );
28123        assert_eq!(
28124            f.run(&[b"FT.SEARCH", b"sx", b"alpha", b"NOCONTENT", b"INKEYS", b"0"]),
28125            "*1\r\n:0\r\n"
28126        );
28127    }
28128
28129    /// The fields come from the database the session is on, and a row whose
28130    /// key will not load there is dropped from the reply and taken off the
28131    /// total.
28132    ///
28133    /// Measured against a real server, which follows a key on every database
28134    /// and then loads it from one.
28135    #[test]
28136    fn the_fields_are_read_from_the_session_database() {
28137        let mut f = Fixture::new();
28138        corpus(&mut f);
28139        f.run(&[b"SELECT", b"1"]);
28140        f.run(&[b"HSET", b"d:9", b"t", b"delta", b"n", b"9"]);
28141        // Both documents are in the index, and only one of them is in this
28142        // database.
28143        assert_eq!(
28144            f.run(&[b"FT.SEARCH", b"sx", b"delta", b"NOCONTENT"]),
28145            "*3\r\n:2\r\n$3\r\nd:3\r\n$3\r\nd:9\r\n"
28146        );
28147        assert_eq!(
28148            f.run(&[b"FT.SEARCH", b"sx", b"delta", b"RETURN", b"1", b"n"]),
28149            "*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"
28150        );
28151    }
28152
28153    /// The deeper protocol answers a map of five rather than an array, with
28154    /// every row a map of its own.
28155    #[test]
28156    fn the_third_protocol_answers_a_map_of_five() {
28157        let mut f = Fixture::new();
28158        corpus(&mut f);
28159        f.out = Out::new(Proto::Resp3);
28160        assert_eq!(
28161            f.run(&[b"FT.SEARCH", b"sx", b"delta", b"RETURN", b"1", b"n"]),
28162            concat!(
28163                "%5\r\n+attributes\r\n*0\r\n+format\r\n+STRING\r\n+results\r\n*1\r\n",
28164                "%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",
28165                "+values\r\n*0\r\n+total_results\r\n:1\r\n+warning\r\n*0\r\n"
28166            )
28167        );
28168    }
28169
28170    /// A window of nothing is a client asking for the count on its own, and a
28171    /// window of nothing that starts somewhere else is a contradiction all
28172    /// three commands refuse in the same words.
28173    #[test]
28174    fn a_window_of_nothing_has_to_start_at_the_top() {
28175        let mut f = Fixture::new();
28176        corpus(&mut f);
28177        let refused = "-SEARCH_LIMIT_OVER The `offset` of the LIMIT must be 0 when `num` is 0\r\n";
28178        assert_eq!(
28179            f.run(&[b"FT.SEARCH", b"sx", b"alpha", b"LIMIT", b"1", b"0"]),
28180            refused
28181        );
28182        assert_eq!(
28183            f.run(&[b"FT.EXPLAIN", b"sx", b"alpha", b"LIMIT", b"1", b"0"]),
28184            refused
28185        );
28186        assert_eq!(
28187            f.run(&[b"FT.AGGREGATE", b"sx", b"alpha", b"LIMIT", b"1", b"0"]),
28188            refused
28189        );
28190        assert_eq!(
28191            f.run(&[b"FT.AGGREGATE", b"sx", b"alpha", b"LIMIT", b"0", b"0"]),
28192            "*1\r\n:3\r\n"
28193        );
28194    }
28195
28196    /// An aggregation answers a count and then a list of properties for every
28197    /// row, which is empty until something asks for a field.
28198    #[test]
28199    fn an_aggregation_answers_a_count_and_then_the_properties() {
28200        let mut f = Fixture::new();
28201        corpus(&mut f);
28202        assert_eq!(
28203            f.run(&[b"FT.AGGREGATE", b"sx", b"alpha"]),
28204            "*4\r\n:1\r\n*0\r\n*0\r\n*0\r\n"
28205        );
28206        // Every row, and not the ten a search would have cut it down to. The
28207        // count in front of them is one because that is how far the reply had
28208        // got when it was written, which is measured against a real server.
28209        assert_eq!(
28210            f.run(&[b"FT.AGGREGATE", b"sx", b"alpha", b"LOAD", b"1", b"@t"]),
28211            concat!(
28212                "*4\r\n:1\r\n*2\r\n$1\r\nt\r\n$10\r\nalpha beta\r\n",
28213                "*2\r\n$1\r\nt\r\n$11\r\nalpha gamma\r\n",
28214                "*2\r\n$1\r\nt\r\n$16\r\nalpha beta gamma\r\n"
28215            )
28216        );
28217        // Ascending document number, because nothing sorts the answer. The
28218        // second and fourth documents are the ones the window lands on and the
28219        // best scoring one is not among them.
28220        assert_eq!(
28221            f.run(&[
28222                b"FT.AGGREGATE",
28223                b"sx",
28224                b"alpha",
28225                b"LOAD",
28226                b"1",
28227                b"@n",
28228                b"LIMIT",
28229                b"1",
28230                b"2"
28231            ]),
28232            "*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"
28233        );
28234        // A query nothing answers is a count of nothing and no rows at all.
28235        assert_eq!(
28236            f.run(&[b"FT.AGGREGATE", b"sx", b"nope", b"LOAD", b"1", b"@t"]),
28237            "*1\r\n:0\r\n"
28238        );
28239    }
28240
28241    /// `LOAD` counts words rather than fields, names the property after the
28242    /// path unless an `AS` renames it, and reads everything the key holds when
28243    /// it is given a star.
28244    #[test]
28245    fn a_load_counts_words_and_can_rename_what_it_reads() {
28246        let mut f = Fixture::new();
28247        corpus(&mut f);
28248        // Three words, which are the path, the `AS` and the name.
28249        assert_eq!(
28250            f.run(&[
28251                b"FT.AGGREGATE",
28252                b"sx",
28253                b"alpha",
28254                b"LOAD",
28255                b"3",
28256                b"@t",
28257                b"AS",
28258                b"text"
28259            ]),
28260            concat!(
28261                "*4\r\n:1\r\n*2\r\n$4\r\ntext\r\n$10\r\nalpha beta\r\n",
28262                "*2\r\n$4\r\ntext\r\n$11\r\nalpha gamma\r\n",
28263                "*2\r\n$4\r\ntext\r\n$16\r\nalpha beta gamma\r\n"
28264            )
28265        );
28266        assert_eq!(
28267            f.run(&[
28268                b"FT.AGGREGATE",
28269                b"sx",
28270                b"alpha",
28271                b"LOAD",
28272                b"*",
28273                b"LIMIT",
28274                b"0",
28275                b"1"
28276            ]),
28277            concat!(
28278                "*2\r\n:1\r\n*6\r\n$1\r\nt\r\n$10\r\nalpha beta\r\n",
28279                "$1\r\ng\r\n$5\r\naa,bb\r\n$1\r\nn\r\n$1\r\n1\r\n"
28280            )
28281        );
28282        // A field the key does not hold is left out rather than sent empty.
28283        assert_eq!(
28284            f.run(&[
28285                b"FT.AGGREGATE",
28286                b"sx",
28287                b"alpha",
28288                b"LOAD",
28289                b"2",
28290                b"@n",
28291                b"@nope",
28292                b"LIMIT",
28293                b"0",
28294                b"2"
28295            ]),
28296            "*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"
28297        );
28298    }
28299
28300    /// The `LOAD` grammar, which has four ways to go wrong and one of them is
28301    /// only reported once the rest of the argument list has read cleanly.
28302    #[test]
28303    fn a_load_refuses_a_count_it_cannot_use() {
28304        let mut f = Fixture::new();
28305        corpus(&mut f);
28306        let head = "-SEARCH_PARSE_ARGS Bad arguments for LOAD: ";
28307        assert_eq!(
28308            f.run(&[b"FT.AGGREGATE", b"sx", b"alpha", b"LOAD", b"x"]),
28309            format!("{head}Expected number of fields or `*`\r\n")
28310        );
28311        assert_eq!(
28312            f.run(&[b"FT.AGGREGATE", b"sx", b"alpha", b"LOAD", b"-1", b"@t"]),
28313            format!("{head}Value is outside acceptable bounds\r\n")
28314        );
28315        assert_eq!(
28316            f.run(&[b"FT.AGGREGATE", b"sx", b"alpha", b"LOAD", b"5", b"@t"]),
28317            format!("{head}Expected an argument, but none provided\r\n")
28318        );
28319        assert_eq!(
28320            f.run(&[b"FT.AGGREGATE", b"sx", b"alpha", b"LOAD"]),
28321            format!("{head}Expected an argument, but none provided\r\n")
28322        );
28323        // A count that runs out on the `AS` is held back, because the word
28324        // after it is read as an argument of its own and may be worth an error
28325        // of its own. Nothing follows here, so the held back line is the one.
28326        assert_eq!(
28327            f.run(&[
28328                b"FT.AGGREGATE",
28329                b"sx",
28330                b"alpha",
28331                b"LOAD",
28332                b"2",
28333                b"@t",
28334                b"AS"
28335            ]),
28336            "-SEARCH_PARSE_ARGS LOAD path AS name - must be accompanied with NAME\r\n"
28337        );
28338        // And here the word after it is one an aggregation stops taking once a
28339        // step has been read, so that is what the client hears about.
28340        assert_eq!(
28341            f.run(&[
28342                b"FT.AGGREGATE",
28343                b"sx",
28344                b"alpha",
28345                b"LOAD",
28346                b"2",
28347                b"@t",
28348                b"AS",
28349                b"VERBATIM"
28350            ]),
28351            "-SEARCH_ARG_UNRECOGNIZED Unknown argument `VERBATIM` at position 5 for <main>\r\n"
28352        );
28353        // A `LOAD 0` is a step that names nothing. It shuts the same door
28354        // without becoming a loader, so the count stays the one a query with no
28355        // `LOAD` gets.
28356        assert_eq!(
28357            f.run(&[
28358                b"FT.AGGREGATE",
28359                b"sx",
28360                b"alpha",
28361                b"LOAD",
28362                b"0",
28363                b"LIMIT",
28364                b"0",
28365                b"1"
28366            ]),
28367            "*2\r\n:1\r\n*0\r\n"
28368        );
28369    }
28370
28371    /// Reading a step of the pipeline stops the words about the search itself
28372    /// being taken, and `LIMIT` and `TIMEOUT` are not steps.
28373    #[test]
28374    fn a_pipeline_step_closes_the_door_on_the_search_words() {
28375        let mut f = Fixture::new();
28376        corpus(&mut f);
28377        assert_eq!(
28378            f.run(&[
28379                b"FT.AGGREGATE",
28380                b"sx",
28381                b"alpha",
28382                b"LOAD",
28383                b"1",
28384                b"@t",
28385                b"VERBATIM"
28386            ]),
28387            "-SEARCH_ARG_UNRECOGNIZED Unknown argument `VERBATIM` at position 4 for <main>\r\n"
28388        );
28389        assert_eq!(
28390            f.run(&[
28391                b"FT.AGGREGATE",
28392                b"sx",
28393                b"alpha",
28394                b"LIMIT",
28395                b"0",
28396                b"1",
28397                b"VERBATIM"
28398            ]),
28399            "*2\r\n:1\r\n*0\r\n"
28400        );
28401        // Three words a search takes that this command names in its refusal
28402        // rather than calling them unknown.
28403        for word in [b"RETURN".as_slice(), b"SUMMARIZE", b"HIGHLIGHT"] {
28404            let name = core::str::from_utf8(word).expect("the three words are text");
28405            assert_eq!(
28406                f.run(&[b"FT.AGGREGATE", b"sx", b"alpha", word]),
28407                format!("-SEARCH_PARSE_ARGS {name} is not supported on FT.AGGREGATE\r\n")
28408            );
28409        }
28410    }
28411
28412    /// `ADDSCORES` writes the score as a property to twelve significant digits
28413    /// where `WITHSCORES` writes it beside the row in full.
28414    #[test]
28415    fn addscores_writes_a_shorter_score_than_withscores() {
28416        let mut f = Fixture::new();
28417        corpus(&mut f);
28418        assert_eq!(
28419            f.run(&[
28420                b"FT.AGGREGATE",
28421                b"sx",
28422                b"alpha",
28423                b"ADDSCORES",
28424                b"LOAD",
28425                b"1",
28426                b"@n",
28427                b"LIMIT",
28428                b"0",
28429                b"2"
28430            ]),
28431            concat!(
28432                "*3\r\n:1\r\n",
28433                "*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",
28434                "*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"
28435            )
28436        );
28437        // `NOCONTENT` takes the properties away and leaves whatever was asked
28438        // for beside them, and a sort key is always null because nothing sorts
28439        // by one yet.
28440        assert_eq!(
28441            f.run(&[
28442                b"FT.AGGREGATE",
28443                b"sx",
28444                b"alpha",
28445                b"NOCONTENT",
28446                b"WITHSCORES",
28447                b"LIMIT",
28448                b"0",
28449                b"2"
28450            ]),
28451            "*3\r\n:1\r\n$18\r\n0.3566749439387324\r\n$18\r\n0.3566749439387324\r\n"
28452        );
28453        assert_eq!(
28454            f.run(&[
28455                b"FT.AGGREGATE",
28456                b"sx",
28457                b"alpha",
28458                b"WITHSORTKEYS",
28459                b"LOAD",
28460                b"1",
28461                b"@n",
28462                b"LIMIT",
28463                b"0",
28464                b"1"
28465            ]),
28466            "*3\r\n:1\r\n$-1\r\n*2\r\n$1\r\nn\r\n$1\r\n1\r\n"
28467        );
28468    }
28469
28470    /// The one scorer that has to see the whole answer first turns the count
28471    /// into the real total and hands the rows back backwards.
28472    #[test]
28473    fn a_normalising_scorer_answers_the_rows_backwards() {
28474        let mut f = Fixture::new();
28475        corpus(&mut f);
28476        assert_eq!(
28477            f.run(&[
28478                b"FT.AGGREGATE",
28479                b"sx",
28480                b"alpha",
28481                b"SCORER",
28482                b"BM25STD.NORM",
28483                b"ADDSCORES",
28484                b"LOAD",
28485                b"1",
28486                b"@n",
28487                b"LIMIT",
28488                b"1",
28489                b"2"
28490            ]),
28491            concat!(
28492                "*3\r\n:3\r\n",
28493                "*4\r\n$7\r\n__score\r\n$1\r\n1\r\n$1\r\nn\r\n$1\r\n2\r\n",
28494                "*4\r\n$7\r\n__score\r\n$1\r\n1\r\n$1\r\nn\r\n$1\r\n1\r\n"
28495            )
28496        );
28497        // Without `ADDSCORES` nothing on the row needs the score, so the rows
28498        // come back the way every other query answers them.
28499        assert_eq!(
28500            f.run(&[
28501                b"FT.AGGREGATE",
28502                b"sx",
28503                b"alpha",
28504                b"SCORER",
28505                b"BM25STD.NORM",
28506                b"LOAD",
28507                b"1",
28508                b"@n",
28509                b"LIMIT",
28510                b"1",
28511                b"2"
28512            ]),
28513            "*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"
28514        );
28515    }
28516
28517    /// The deeper protocol answers the same map of five a search answers, with
28518    /// the `id` gone because an aggregation is about the properties.
28519    #[test]
28520    fn an_aggregation_answers_a_map_of_five_as_well() {
28521        let mut f = Fixture::new();
28522        corpus(&mut f);
28523        f.out = Out::new(Proto::Resp3);
28524        assert_eq!(
28525            f.run(&[
28526                b"FT.AGGREGATE",
28527                b"sx",
28528                b"alpha",
28529                b"ADDSCORES",
28530                b"WITHSCORES",
28531                b"WITHSORTKEYS",
28532                b"LOAD",
28533                b"1",
28534                b"@n",
28535                b"LIMIT",
28536                b"0",
28537                b"1"
28538            ]),
28539            concat!(
28540                "%5\r\n+attributes\r\n*0\r\n+format\r\n+STRING\r\n+results\r\n*1\r\n",
28541                "%4\r\n+score\r\n,0.3566749439387324\r\n+sortkey\r\n_\r\n",
28542                "+extra_attributes\r\n%2\r\n$7\r\n__score\r\n$14\r\n0.356674943939\r\n",
28543                "$1\r\nn\r\n$1\r\n1\r\n+values\r\n*0\r\n",
28544                "+total_results\r\n:1\r\n+warning\r\n*0\r\n"
28545            )
28546        );
28547        // The count is worked out from the rows the reply reached under this
28548        // protocol, where under RESP2 it is worked out from the first of them.
28549        assert_eq!(
28550            f.run(&[
28551                b"FT.AGGREGATE",
28552                b"sx",
28553                b"alpha",
28554                b"NOCONTENT",
28555                b"LIMIT",
28556                b"0",
28557                b"1"
28558            ]),
28559            concat!(
28560                "%5\r\n+attributes\r\n*0\r\n+format\r\n+STRING\r\n+results\r\n*1\r\n",
28561                "%1\r\n+values\r\n*0\r\n+total_results\r\n:1\r\n+warning\r\n*0\r\n"
28562            )
28563        );
28564    }
28565}