toasty_driver_integration_suite/
instrumented_driver.rs1use async_trait::async_trait;
2use std::{
3 borrow::Cow,
4 collections::VecDeque,
5 fmt,
6 sync::{
7 Arc, Mutex,
8 atomic::{AtomicBool, Ordering},
9 },
10};
11use toasty_core::{
12 Result, Schema,
13 driver::{Capability, ConnectContext, Connection, Driver, ExecResponse, Operation, Rows},
14 schema::{
15 db::{AppliedMigration, Migration},
16 diff,
17 },
18};
19
20#[derive(Debug, Clone)]
25pub enum Fault {
26 OperationFailed,
29
30 ConnectionLost,
36}
37
38#[derive(Debug)]
39pub struct DriverOp {
40 pub operation: Operation,
41 pub response: ExecResponse,
42}
43
44#[derive(Clone, Default)]
49pub struct InstrumentedHandle {
50 inner: Arc<InstrumentedState>,
51}
52
53#[derive(Default)]
54struct InstrumentedState {
55 ops_log: Mutex<Vec<DriverOp>>,
56 faults: Mutex<VecDeque<Fault>>,
57}
58
59impl InstrumentedHandle {
60 pub fn len(&self) -> usize {
62 self.inner.ops_log.lock().unwrap().len()
63 }
64
65 pub fn is_empty(&self) -> bool {
67 self.inner.ops_log.lock().unwrap().is_empty()
68 }
69
70 pub fn clear(&self) {
72 self.inner.ops_log.lock().unwrap().clear();
73 }
74
75 #[track_caller]
77 pub fn pop(&self) -> (Operation, ExecResponse) {
78 let mut ops = self.inner.ops_log.lock().unwrap();
79 if ops.is_empty() {
80 panic!("no operations in log");
81 }
82 let driver_op = ops.remove(0);
83 (driver_op.operation, driver_op.response)
84 }
85
86 #[track_caller]
87 pub fn pop_op(&self) -> Operation {
88 self.pop().0
89 }
90
91 #[track_caller]
93 pub fn pop_last(&self) -> (Operation, ExecResponse) {
94 let mut ops = self.inner.ops_log.lock().unwrap();
95 let Some(driver_op) = ops.pop() else {
96 panic!("no operations in log");
97 };
98 (driver_op.operation, driver_op.response)
99 }
100
101 #[track_caller]
102 pub fn pop_last_op(&self) -> Operation {
103 self.pop_last().0
104 }
105
106 pub fn inject_fault(&self, fault: Fault) {
109 self.inner
110 .faults
111 .lock()
112 .expect("Failed to acquire faults lock")
113 .push_back(fault);
114 }
115}
116
117impl fmt::Debug for InstrumentedHandle {
118 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
119 let ops = self.inner.ops_log.lock().unwrap();
120 f.debug_struct("InstrumentedHandle")
121 .field("ops", &*ops)
122 .finish()
123 }
124}
125
126#[derive(Debug)]
130pub struct InstrumentedDriver {
131 inner: Box<dyn Driver>,
132 handle: InstrumentedHandle,
133}
134
135impl InstrumentedDriver {
136 pub fn new(driver: Box<dyn Driver>) -> Self {
137 Self {
138 inner: driver,
139 handle: InstrumentedHandle::default(),
140 }
141 }
142
143 pub fn handle(&self) -> InstrumentedHandle {
146 self.handle.clone()
147 }
148}
149
150#[async_trait]
151impl Driver for InstrumentedDriver {
152 fn url(&self) -> Cow<'_, str> {
153 self.inner.url()
154 }
155
156 fn capability(&self) -> &'static Capability {
157 self.inner.capability()
158 }
159
160 async fn connect(&self, cx: &ConnectContext) -> Result<Box<dyn Connection>> {
161 Ok(Box::new(InstrumentedConnection {
162 inner: self.inner.connect(cx).await?,
163 handle: self.handle.clone(),
164 valid: AtomicBool::new(true),
165 }))
166 }
167
168 fn generate_migration(&self, schema_diff: &diff::Schema<'_>) -> Migration {
169 self.inner.generate_migration(schema_diff)
170 }
171
172 async fn reset_db(&self) -> Result<()> {
173 self.inner.reset_db().await
174 }
175}
176
177#[derive(Debug)]
180pub struct InstrumentedConnection {
181 inner: Box<dyn Connection>,
183
184 handle: InstrumentedHandle,
186
187 valid: AtomicBool,
192}
193
194#[async_trait]
195impl Connection for InstrumentedConnection {
196 async fn exec(&mut self, schema: &Arc<Schema>, operation: Operation) -> Result<ExecResponse> {
197 let fault = self
200 .handle
201 .inner
202 .faults
203 .lock()
204 .expect("Failed to acquire faults lock")
205 .pop_front();
206 if let Some(fault) = fault {
207 match fault {
208 Fault::OperationFailed => {
209 return Err(toasty_core::Error::driver_operation_failed(
210 std::io::Error::other("injected operation failure"),
211 ));
212 }
213 Fault::ConnectionLost => {
214 self.valid.store(false, Ordering::Release);
215 return Err(toasty_core::Error::connection_lost(std::io::Error::other(
216 "injected connection-lost fault",
217 )));
218 }
219 }
220 }
221
222 let operation_clone = operation.clone();
224
225 let mut response = self.inner.exec(schema, operation).await?;
227
228 let duplicated_response = duplicate_response_mut(&mut response).await?;
230
231 let driver_op = DriverOp {
233 operation: operation_clone,
234 response: duplicated_response,
235 };
236
237 self.handle
238 .inner
239 .ops_log
240 .lock()
241 .expect("Failed to acquire ops log lock")
242 .push(driver_op);
243
244 Ok(response)
245 }
246
247 async fn push_schema(&mut self, schema: &Schema) -> Result<()> {
248 self.inner.push_schema(schema).await
249 }
250
251 async fn applied_migrations(&mut self) -> Result<Vec<AppliedMigration>> {
252 self.inner.applied_migrations().await
253 }
254
255 async fn apply_migration(&mut self, id: u64, name: &str, migration: &Migration) -> Result<()> {
256 self.inner.apply_migration(id, name, migration).await
257 }
258
259 fn is_valid(&self) -> bool {
260 self.valid.load(Ordering::Acquire) && self.inner.is_valid()
261 }
262
263 async fn ping(&mut self) -> Result<()> {
264 let fault = self
268 .handle
269 .inner
270 .faults
271 .lock()
272 .expect("Failed to acquire faults lock")
273 .pop_front();
274 if let Some(fault) = fault {
275 match fault {
276 Fault::OperationFailed => {
277 return Err(toasty_core::Error::driver_operation_failed(
278 std::io::Error::other("injected operation failure"),
279 ));
280 }
281 Fault::ConnectionLost => {
282 self.valid.store(false, Ordering::Release);
283 return Err(toasty_core::Error::connection_lost(std::io::Error::other(
284 "injected connection-lost fault",
285 )));
286 }
287 }
288 }
289 self.inner.ping().await
290 }
291}
292
293async fn duplicate_response_mut(response: &mut ExecResponse) -> Result<ExecResponse> {
296 let values = match &mut response.values {
297 Rows::Count(count) => Rows::Count(*count),
298 Rows::Value(_) => todo!(),
299 Rows::Stream(stream) => {
300 let duplicated_stream = stream.dup().await?;
302 Rows::Stream(duplicated_stream)
303 }
304 };
305
306 Ok(ExecResponse::from_rows(values))
307}