pub mod effect;
pub mod live_folder;
pub mod pulse;
#[cfg(feature = "rhai")]
pub mod rhai_effect;
pub mod shape;
pub mod value_gen;
use tokio::sync::broadcast;
use tokio::task::JoinHandle;
use vantage_diorama::ChangeEvent;
use vantage_vista::Vista;
use vantage_vista::mocks::MockShell;
use vantage_vista::source::TableShell as _;
pub use effect::{FakerCtx, FakerEffect, FifoEffect, StaticEffect};
pub use live_folder::{
EVENT_TYPES, Entry, EntryKind, LiveFolderConfig, LiveFolderSim, PushMode, format_ts,
};
pub use pulse::{PulseConfig, PulseKey, PulseRole, PulseSim};
#[cfg(feature = "rhai")]
pub use rhai_effect::RhaiEffect;
pub use shape::{
BackendShape, ExtraFields, FaultSchedule, Latency, LatencyModel, Offline, ShapedShell,
};
pub use value_gen::ValueGen;
#[derive(Clone, Debug)]
pub struct FakerColumn {
pub name: String,
pub ty: String,
pub flags: Vec<String>,
}
pub struct FakerTable {
pub vista: Vista,
pub events: broadcast::Sender<ChangeEvent>,
_task: Option<AbortOnDrop>,
}
fn faker_metadata(columns: &[FakerColumn], id_column: &str) -> vantage_vista::VistaMetadata {
let mut meta = vantage_vista::VistaMetadata::default();
for c in columns {
let mut col = vantage_vista::Column::new(&c.name, &c.ty)
.with_flag(vantage_vista::flags::ORDERABLE)
.with_flag(vantage_vista::flags::SEARCHABLE);
for f in &c.flags {
col = col.with_flag(f);
}
if c.name == id_column {
col = col.with_flag(vantage_vista::flags::ID);
}
meta.columns.insert(c.name.clone(), col);
}
meta
}
impl FakerTable {
pub fn build(
name: impl Into<String>,
columns: Vec<FakerColumn>,
id_column: impl Into<String>,
effect: Box<dyn FakerEffect>,
) -> Self {
let id_column = id_column.into();
let shell = MockShell::new().with_metadata(faker_metadata(&columns, &id_column));
let (events, _) = broadcast::channel(EVENT_CAPACITY);
let ctx = std::sync::Arc::new(FakerCtx::new(
shell.clone(),
events.clone(),
columns,
id_column,
));
effect.seed(&ctx);
let vista = Vista::new(name, Box::new(shell));
let task = effect.is_live().then(|| {
AbortOnDrop(tokio::spawn(async move {
effect.run(ctx).await;
}))
});
Self {
vista,
events,
_task: task,
}
}
pub fn build_shaped(
name: impl Into<String>,
columns: Vec<FakerColumn>,
id_column: impl Into<String>,
effect: Box<dyn FakerEffect>,
shape: BackendShape,
) -> Self {
let id_column = id_column.into();
let shell = MockShell::new().with_metadata(faker_metadata(&columns, &id_column));
let (events, _) = broadcast::channel(EVENT_CAPACITY);
let values = match shape.seed {
Some(seed) => ValueGen::seeded(seed),
None => ValueGen::new(),
}
.with_weirdness(shape.weirdness);
let ctx = std::sync::Arc::new(
FakerCtx::new(shell.clone(), events.clone(), columns, id_column)
.with_values(values)
.with_extra_fields(shape.extra_fields)
.with_seed(shape.seed),
);
effect.seed(&ctx);
let inner = shell
.clone_shell()
.expect("MockShell::clone_shell always succeeds");
let vista = Vista::new(name, Box::new(ShapedShell::new(inner, shape)));
let task = effect.is_live().then(|| {
AbortOnDrop(tokio::spawn(async move {
effect.run(ctx).await;
}))
});
Self {
vista,
events,
_task: task,
}
}
pub fn split(self) -> (Vista, FakerHandle) {
let Self {
vista,
events,
_task,
} = self;
(vista, FakerHandle { events, _task })
}
}
pub struct FakerHandle {
pub events: broadcast::Sender<ChangeEvent>,
_task: Option<AbortOnDrop>,
}
const EVENT_CAPACITY: usize = 1024;
struct AbortOnDrop(JoinHandle<()>);
impl Drop for AbortOnDrop {
fn drop(&mut self) {
self.0.abort();
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::time::Duration;
use vantage_dataset::prelude::ReadableValueSet;
fn columns() -> Vec<FakerColumn> {
vec![
FakerColumn {
name: "id".into(),
ty: "string".into(),
flags: vec!["id".into()],
},
FakerColumn {
name: "email".into(),
ty: "string".into(),
flags: vec![],
},
]
}
#[tokio::test]
async fn static_table_seeds_rows_and_has_no_live_task() {
let table = FakerTable::build(
"events",
columns(),
"id",
Box::new(StaticEffect { count: 20 }),
);
let rows = table.vista.list_values().await.unwrap();
assert_eq!(rows.len(), 20);
}
#[tokio::test]
async fn fifo_table_broadcasts_inserts_as_it_runs() {
let table = FakerTable::build(
"events",
columns(),
"id",
Box::new(FifoEffect {
interval: Duration::from_millis(5),
retention_lo: Duration::from_secs(30),
retention_hi: Duration::from_secs(60),
}),
);
let mut rx = table.events.subscribe();
let got_insert = tokio::time::timeout(Duration::from_secs(1), recv_insert(&mut rx))
.await
.expect("expected an Inserted within 1s");
assert!(got_insert);
drop(table);
}
async fn recv_insert(rx: &mut broadcast::Receiver<ChangeEvent>) -> bool {
loop {
match rx.recv().await {
Ok(ChangeEvent::Inserted { .. }) => return true,
Ok(_) => continue,
Err(_) => return false,
}
}
}
}