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] macro_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 [< 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 }
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 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 })
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); 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); 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}