crdb_test_utils/
lib.rs

1use anyhow::Context;
2use crdb_core::DynSized;
3use std::{any::Any, fmt::Debug};
4
5mod full_object;
6mod fuzz_object_full;
7mod fuzz_remote_perms;
8mod fuzz_simple;
9mod mem_db;
10mod object_delegate_perms;
11mod object_full;
12mod object_perms;
13mod object_simple;
14mod smoke_test;
15mod stubs;
16
17pub use crdb_core::{self, Error, Result};
18
19pub use anyhow;
20pub use paste;
21pub use rust_decimal;
22pub use ulid;
23
24pub use full_object::FullObject;
25pub use mem_db::MemDb;
26pub use object_delegate_perms::{TestEventDelegatePerms, TestObjectDelegatePerms};
27pub use object_full::{TestEventFull, TestObjectFull};
28pub use object_perms::{TestEventPerms, TestObjectPerms};
29pub use object_simple::{TestEventSimple, TestObjectSimple};
30pub use stubs::*;
31
32crdb_macros::db! {
33    pub struct Config {
34        delegate_perms: TestObjectDelegatePerms,
35        full: TestObjectFull,
36        perms: TestObjectPerms,
37        simple: TestObjectSimple,
38    }
39}
40
41fn eq<T: 'static + Any + Send + Sync + Eq>(
42    l: &dyn DynSized,
43    r: &dyn DynSized,
44) -> anyhow::Result<bool> {
45    Ok(l.ref_to_any()
46        .downcast_ref::<T>()
47        .context("downcasting lhs")?
48        == r.ref_to_any()
49            .downcast_ref::<T>()
50            .context("downcasting rhs")?)
51}
52
53pub fn cmp_err(pg: &crate::Error, mem: &crate::Error) -> bool {
54    use crate::Error::*;
55    match (pg, mem) {
56        (MissingBinaries(a), MissingBinaries(b)) => a == b,
57        (ObjectAlreadyExists(a), ObjectAlreadyExists(b)) => a == b,
58        (EventAlreadyExists(a), EventAlreadyExists(b)) => a == b,
59        (ObjectDoesNotExist(a), ObjectDoesNotExist(b)) => a == b,
60        (TypeDoesNotExist(a), TypeDoesNotExist(b)) => a == b,
61        (BinaryHashMismatch(a), BinaryHashMismatch(b)) => a == b,
62        (NullByteInString, NullByteInString) => true,
63        (InvalidNumber, InvalidNumber) => true,
64        (InvalidToken(a), InvalidToken(b)) => a == b,
65        (
66            EventTooEarly {
67                event_id: event_id_1,
68                object_id: object_id_1,
69                created_at: created_at_1,
70            },
71            EventTooEarly {
72                event_id: event_id_2,
73                object_id: object_id_2,
74                created_at: created_at_2,
75            },
76        ) => event_id_1 == event_id_2 && object_id_1 == object_id_2 && created_at_1 == created_at_2,
77        (
78            WrongType {
79                object_id: object_id_1,
80                expected_type_id: expected_type_id_1,
81                real_type_id: real_type_id_1,
82            },
83            WrongType {
84                object_id: object_id_2,
85                expected_type_id: expected_type_id_2,
86                real_type_id: real_type_id_2,
87            },
88        ) => {
89            object_id_1 == object_id_2
90                && expected_type_id_1 == expected_type_id_2
91                && real_type_id_1 == real_type_id_2
92        }
93        _ => false,
94    }
95}
96
97pub fn cmp<T: Debug + Eq>(
98    testdb_res: crate::Result<T>,
99    mem_res: crate::Result<T>,
100) -> anyhow::Result<()> {
101    let is_eq = match (&testdb_res, &mem_res) {
102        (_, Err(crate::Error::Other(mem))) => panic!("MemDb hit an internal server error: {mem:?}"),
103        (Ok(testdb), Ok(mem)) => testdb == mem,
104        (Err(testdb_err), Err(mem_err)) => cmp_err(testdb_err, mem_err),
105        _ => false,
106    };
107    anyhow::ensure!(is_eq, "tested db result != mem result:\n==========\nTested DB:\n{testdb_res:?}\n==========\nMem:\n{mem_res:?}\n==========");
108    Ok(())
109}
110
111#[macro_export] // used by the client-js.rs integration test
112macro_rules! make_fuzzer_stuffs {
113    (
114        $db_type:tt,
115        $( ($name:ident, $object:ident, $event:ident), )*
116    ) => { $crate::paste::paste! {
117        use $crate::{*, crdb_core::*};
118        use std::collections::HashSet;
119
120        #[derive(Debug, arbitrary::Arbitrary, serde::Deserialize, serde::Serialize)]
121        enum Op {
122            $(
123                [< Create $name >] {
124                    object_id: ObjectId,
125                    created_at: EventId,
126                    object: Arc<$object>,
127                    updatedness: Option<Updatedness>,
128                    importance: Importance,
129                },
130                [< Submit $name >] {
131                    object_id: usize,
132                    event_id: EventId,
133                    event: Arc<$event>,
134                    updatedness: Option<Updatedness>,
135                    additional_importance: Importance,
136                },
137                [< GetLatest $name >] {
138                    object_id: usize,
139                    importance: Importance,
140                },
141                // TODO(test-high): also test GetAll
142                // TODO(test-high): also test query subscription / locking for client db's
143                [< Query $name >] {
144                    user: User,
145                    only_updated_since: Option<Updatedness>,
146                    query: Arc<Query>,
147                },
148                [< Recreate $name >] {
149                    object_id: usize,
150                    new_created_at: EventId,
151                    object: Arc<$object>,
152                    updatedness: Option<Updatedness>,
153                    additional_importance: Importance,
154                },
155            )*
156            CreateBinary {
157                data: Arc<[u8]>,
158                fake_id: Option<BinPtr>,
159            },
160            GetBinary {
161                binary_id: usize,
162            },
163            Remove { object_id: usize },
164            SetObjectImportance { object_id: usize, new_importance: Importance },
165            SetImportanceFromQueries { object_id: usize, new_importance_from_queries: Importance },
166            ClientVacuum,
167            ServerVacuum { recreate_at: Option<EventId>, updatedness: Updatedness },
168            // TODO(test-high): test all methods of *Db
169        }
170
171        impl Op {
172            async fn apply(&self, db: &Database, s: &mut FuzzState) -> anyhow::Result<()> {
173                match self {
174                    $(
175                        Op::[< Create $name >] {
176                            object_id,
177                            created_at,
178                            object,
179                            updatedness,
180                            mut importance,
181                        } => {
182                            let updatedness = s.updatedness(updatedness);
183                            let mut object = object.clone();
184                            Arc::make_mut(&mut object).standardize(*object_id);
185                            s.add_object(*object_id);
186                            if s.is_server {
187                                importance |= Importance::LOCK;
188                            }
189                            let db = db
190                                .create(*object_id, *created_at, object.clone(), updatedness, importance)
191                                .await;
192                            let mem = s
193                                .mem_db
194                                .create(*object_id, *created_at, object.clone(), updatedness, importance)
195                                .await;
196                            cmp(db, mem)?;
197                        }
198                        Op::[< Submit $name >] {
199                            object_id,
200                            event_id,
201                            event,
202                            updatedness,
203                            additional_importance,
204                        } => {
205                            let updatedness = s.updatedness(updatedness);
206                            let object_id = s.object(*object_id);
207                            let db = db
208                                .submit::<$object>(object_id, *event_id, event.clone(), updatedness, *additional_importance)
209                                .await;
210                            let mem = s
211                                .mem_db
212                                .submit::<$object>(object_id, *event_id, event.clone(), updatedness, *additional_importance)
213                                .await;
214                            cmp(db, mem)?;
215                        }
216                        Op::[< GetLatest $name >] {
217                            object_id,
218                            importance,
219                        } => {
220                            let object_id = s.object(*object_id);
221                            let db = db
222                                .get_latest::<$object>(object_id, *importance)
223                                .await
224                                .wrap_context(&format!("getting {object_id:?} in database"));
225                            let mem = s
226                                .mem_db
227                                .get_latest::<$object>(object_id, *importance)
228                                .await
229                                .wrap_context(&format!("getting {object_id:?} in mem db"));
230                            cmp(db, mem)?;
231                        }
232                        Op::[< Query $name >] { user, only_updated_since, query } => {
233                            $crate::make_fuzzer_stuffs!(@if-client $db_type {
234                                let _ = user;
235                                let _ = only_updated_since;
236                                let db = db
237                                    .client_query(*$object::type_ulid(), query.clone())
238                                    .await
239                                    .wrap_context("querying postgres")
240                                    .map(|r| r.into_iter().collect::<HashSet<_>>());
241                                let mem = s.mem_db
242                                    .memdb_query::<$object>(USER_ID_NULL, None, &query)
243                                    .await
244                                    .wrap_context("querying mem")
245                                    .map(|r| r.into_iter().collect::<HashSet<_>>());
246                                cmp(db, mem)?;
247                            });
248                            $crate::make_fuzzer_stuffs!(@if-server $db_type {
249                                let pg = db
250                                    .server_query(*user, *$object::type_ulid(), *only_updated_since, query.clone())
251                                    .await
252                                    .wrap_context("querying postgres")
253                                    .map(|r| r.into_iter().collect::<HashSet<_>>());
254                                let mem = s.mem_db
255                                    .memdb_query::<$object>(*user, *only_updated_since, &query)
256                                    .await
257                                    .map(|r| r.into_iter().collect::<HashSet<_>>());
258                                cmp(pg, mem)?;
259                            });
260                        }
261                        Op::[< Recreate $name >] {
262                            object_id,
263                            new_created_at,
264                            object,
265                            updatedness,
266                            additional_importance,
267                        } => {
268                            $crate::make_fuzzer_stuffs!(@if-client $db_type {
269                                let updatedness = s.updatedness(updatedness);
270                                let object_id = s.object(*object_id);
271                                let mut object = object.clone();
272                                Arc::make_mut(&mut object).standardize(object_id);
273                                let db = db
274                                    .recreate::<$object>(
275                                        object_id,
276                                        *new_created_at,
277                                        object.clone(),
278                                        updatedness,
279                                        *additional_importance,
280                                    )
281                                    .await;
282                                let mem = s
283                                    .mem_db
284                                    .recreate::<$object>(
285                                        object_id,
286                                        *new_created_at,
287                                        object.clone(),
288                                        updatedness,
289                                        *additional_importance,
290                                    )
291                                    .await;
292                                cmp(db, mem)?;
293                            });
294                        }
295                    )*
296                    Op::CreateBinary { data, fake_id } => {
297                        let real_hash = hash_binary(&data);
298                        s.add_binary(real_hash);
299                        let binary_id = match fake_id {
300                            Some(id) => {
301                                s.add_binary(*id);
302                                *id
303                            }
304                            None => real_hash,
305                        };
306                        let mem = s.mem_db.create_binary(binary_id, data.clone()).await.wrap_context("creating binary");
307                        let pg = db.create_binary(binary_id, data.clone()).await.wrap_context("creating binary");
308                        cmp(pg, mem)?;
309                    }
310                    Op::GetBinary { binary_id } => {
311                        let binary_id = s.binary(*binary_id);
312                        let mem = s.mem_db.get_binary(binary_id).await.wrap_context("getting binary");
313                        let pg = db.get_binary(binary_id).await.wrap_context("getting binary");
314                        cmp(pg, mem)?;
315                    }
316                    Op::Remove { object_id } => {
317                        $crate::make_fuzzer_stuffs!(@if-client $db_type {
318                            let object_id = s.object(*object_id);
319                            let db = db.remove(object_id).await;
320                            let mem = s.mem_db.remove(object_id).await;
321                            cmp(db, mem)?;
322                        });
323                    }
324                    Op::SetObjectImportance { object_id, new_importance } => {
325                        $crate::make_fuzzer_stuffs!(@if-client $db_type {
326                            let object_id = s.object(*object_id);
327                            let db = db.set_object_importance(object_id, *new_importance).await;
328                            let mem = s.mem_db.set_object_importance(object_id, *new_importance).await;
329                            cmp(db, mem)?;
330                        });
331                    }
332                    Op::SetImportanceFromQueries { object_id, new_importance_from_queries } => {
333                        $crate::make_fuzzer_stuffs!(@if-client $db_type {
334                            let object_id = s.object(*object_id);
335                            let db = db.set_importance_from_queries(object_id, *new_importance_from_queries).await;
336                            let mem = s.mem_db.set_importance_from_queries(object_id, *new_importance_from_queries).await;
337                            cmp(db, mem)?;
338                        });
339                    }
340                    Op::ClientVacuum => {
341                        $crate::make_fuzzer_stuffs!(@if-client $db_type {
342                            let db = db.client_vacuum(|_| (), |_| ()).await;
343                            let mem = s.mem_db.client_vacuum(|_| (), |_| ()).await;
344                            cmp(db, mem)?;
345                        });
346                    }
347                    Op::ServerVacuum { recreate_at, updatedness} => {
348                        $crate::make_fuzzer_stuffs!(@if-server $db_type {
349                            // TODO(test-high): use MemDb's implementation of server_vacuum once it's done
350                            match recreate_at {
351                                None => {
352                                    let db = db
353                                        .server_vacuum(None, Updatedness::now(), None, |r, _| {
354                                            panic!("got unexpected recreation {r:?}");
355                                        })
356                                        .await;
357                                    let mem = s.mem_db.client_vacuum(|_| (), |_| ()).await;
358                                    cmp(db, mem)?;
359                                }
360                                Some(_) => {
361                                    let db = db
362                                        .server_vacuum(*recreate_at, *updatedness, None, |_, _| {
363                                            // TODO(test-high): validate that the notified recreations are the same as in memdb
364                                        })
365                                        .await;
366                                    let mem = async move {
367                                        if let Some(recreate_at) = recreate_at {
368                                            $(
369                                                s.mem_db.recreate_all::<$object>(*recreate_at, Some(*updatedness)).await?;
370                                            )*
371                                        }
372                                        s.mem_db.client_vacuum(|_| (), |_| ()).await?;
373                                        Ok(())
374                                    }
375                                    .await;
376                                    cmp(db, mem)?;
377                                }
378                            }
379                        });
380                    }
381                }
382                Ok(())
383            }
384        }
385
386        struct FuzzState {
387            is_server: bool,
388            ulids: Vec<Ulid>,
389            mem_db: MemDb,
390        }
391
392        impl FuzzState {
393            fn new(is_server: bool) -> FuzzState {
394                FuzzState {
395                    is_server,
396                    ulids: Vec::new(),
397                    mem_db: MemDb::new(is_server),
398                }
399            }
400
401            fn add_object(&mut self, id: ObjectId) {
402                self.ulids.push(id.0)
403            }
404
405            fn add_binary(&mut self, id: BinPtr) {
406                self.ulids.push(id.0)
407            }
408
409            fn object(&self, id: usize) -> ObjectId {
410                #[cfg(target_arch = "wasm32")]
411                let id = id % (self.ulids.len() + 1); // make valid inputs more likely
412                self.ulids.get(id).copied().map(ObjectId).unwrap_or_else(ObjectId::now)
413            }
414
415            fn binary(&self, id: usize) -> BinPtr {
416                #[cfg(target_arch = "wasm32")]
417                let id = id % (self.ulids.len() + 1); // make valid inputs more likely
418                self.ulids.get(id).copied().map(BinPtr).unwrap_or_else(BinPtr::now)
419            }
420
421            fn updatedness(&self, updatedness: &Option<Updatedness>) -> Option<Updatedness> {
422                if self.is_server {
423                    Some(updatedness.clone().unwrap_or(Updatedness::now()))
424                } else {
425                    *updatedness
426                }
427            }
428        }
429
430        async fn fuzz_impl((cluster, is_server): &(SetupState, bool), ops: Arc<Vec<Op>>) -> Database {
431            #[cfg(not(fuzzing))]
432            eprintln!("Fuzzing with:\n{}", serde_json::to_string(&ops).unwrap());
433            let (db, _keepalive) = make_db(cluster).await;
434            let mut s = FuzzState::new(*is_server);
435            for (i, op) in ops.iter().enumerate() {
436                op.apply(&db, &mut s)
437                    .await
438                    .with_context(|| format!("applying {i}th op: {op:?}"))
439                    .unwrap();
440                db.assert_invariants_generic().await;
441                $(
442                    db.assert_invariants_for::<$object>().await;
443                )*
444            }
445            db
446        }
447    } };
448
449    (@if-client client $($t:tt)*) => { $($t)* };
450    (@if-client server $($t:tt)*) => {};
451
452    (@if-server server $($t:tt)*) => { $($t)* };
453    (@if-server client $($t:tt)*) => {};
454}