|
1 |
| -use std::{sync::Arc, time::Instant}; |
| 1 | +use std::{io, sync::Arc, time::Instant}; |
2 | 2 |
|
3 | 3 | use env_logger::Env;
|
4 | 4 | use log::info;
|
5 | 5 | use pretty_assertions::assert_matches;
|
| 6 | +use rand::seq::IndexedRandom as _; |
6 | 7 | use spacetimedb::{
|
7 | 8 | db::{
|
8 | 9 | datastore::locking_tx_datastore::datastore::Locking,
|
@@ -63,9 +64,6 @@ async fn can_sync_a_snapshot() -> anyhow::Result<()> {
|
63 | 64 | let dst_snapshot_full = dst_repo.read_snapshot(src.offset, &pool)?;
|
64 | 65 | Locking::restore_from_snapshot(dst_snapshot_full, pool)?;
|
65 | 66 |
|
66 |
| - // Check that `verify_snapshot` agrees. |
67 |
| - verify_snapshot(blob_provider.clone(), dst_path.clone(), src_snapshot.clone()).await?; |
68 |
| - |
69 | 67 | // Let's also check that running `synchronize_snapshot` again does nothing.
|
70 | 68 | let stats = synchronize_snapshot(blob_provider.clone(), dst_path.clone(), src_snapshot.clone()).await?;
|
71 | 69 | assert_eq!(stats.objects_skipped, total_objects);
|
@@ -112,6 +110,70 @@ async fn rejects_overwrite() -> anyhow::Result<()> {
|
112 | 110 | Ok(())
|
113 | 111 | }
|
114 | 112 |
|
| 113 | +#[tokio::test] |
| 114 | +async fn verifies_objects() -> anyhow::Result<()> { |
| 115 | + enable_logging(); |
| 116 | + let tmp = tempdir()?; |
| 117 | + let src = SourceSnapshot::get_or_create().await?; |
| 118 | + |
| 119 | + let dst_path = SnapshotsPath::from_path_unchecked(tmp.path()); |
| 120 | + dst_path.create()?; |
| 121 | + |
| 122 | + let src_snapshot = src.meta.clone(); |
| 123 | + |
| 124 | + synchronize_snapshot(src.objects.clone(), dst_path.clone(), src_snapshot.clone()).await?; |
| 125 | + |
| 126 | + // Read objects for verification from the destination repo. |
| 127 | + let blob_provider = spawn_blocking({ |
| 128 | + let dst_path = dst_path.clone(); |
| 129 | + let snapshot_offset = src_snapshot.tx_offset; |
| 130 | + move || { |
| 131 | + let repo = SnapshotRepository::open(dst_path, Identity::ZERO, 0)?; |
| 132 | + let objects = SnapshotRepository::object_repo(&repo.snapshot_dir_path(snapshot_offset))?; |
| 133 | + anyhow::Ok(Arc::new(objects)) |
| 134 | + } |
| 135 | + }) |
| 136 | + .await |
| 137 | + .unwrap()?; |
| 138 | + // Initially, all should be good. |
| 139 | + verify_snapshot(blob_provider.clone(), dst_path.clone(), src_snapshot.clone()).await?; |
| 140 | + |
| 141 | + // Pick a random object to mess with. |
| 142 | + let random_object_path = { |
| 143 | + let all_objects = src_snapshot.objects().collect::<Box<[_]>>(); |
| 144 | + let random_object = all_objects.choose(&mut rand::rng()).copied().unwrap(); |
| 145 | + blob_provider.file_path(random_object.as_bytes()) |
| 146 | + }; |
| 147 | + |
| 148 | + // Truncate the object file and assert that verification fails. |
| 149 | + tokio::fs::File::options() |
| 150 | + .write(true) |
| 151 | + .open(&random_object_path) |
| 152 | + .await? |
| 153 | + .set_len(1) |
| 154 | + .await?; |
| 155 | + info!("truncated object file {}", random_object_path.display()); |
| 156 | + let err = verify_snapshot(blob_provider.clone(), dst_path.clone(), src_snapshot.clone()) |
| 157 | + .await |
| 158 | + .unwrap_err(); |
| 159 | + assert_matches!( |
| 160 | + err, |
| 161 | + // If the object is a page, we'll get `Deserialize`, |
| 162 | + // otherwise `HashMismatch`. |
| 163 | + SnapshotError::HashMismatch { .. } | SnapshotError::Deserialize { .. } |
| 164 | + ); |
| 165 | + |
| 166 | + // Delete the object file and assert that verification fails. |
| 167 | + tokio::fs::remove_file(&random_object_path).await?; |
| 168 | + info!("deleted object file {}", random_object_path.display()); |
| 169 | + let err = verify_snapshot(blob_provider, dst_path, src_snapshot) |
| 170 | + .await |
| 171 | + .unwrap_err(); |
| 172 | + assert_matches!(err, SnapshotError::ReadObject { cause, ..} if cause.kind() == io::ErrorKind::NotFound); |
| 173 | + |
| 174 | + Ok(()) |
| 175 | +} |
| 176 | + |
115 | 177 | /// Creating a snapshot takes a long time, because we need to commit
|
116 | 178 | /// `SNAPSHOT_FREQUENCY` transactions to trigger one.
|
117 | 179 | ///
|
|
0 commit comments