test: add CI, RingBuffer tests, core proptest, RAII lua fixtures
This commit is contained in:
@@ -15,3 +15,4 @@ thiserror = { workspace = true }
|
||||
[dev-dependencies]
|
||||
tokio = { workspace = true, features = ["full"] }
|
||||
tracing-subscriber = { workspace = true }
|
||||
tempfile = "3"
|
||||
|
||||
@@ -57,3 +57,116 @@ impl RingBuffer<DecodedEntry> {
|
||||
.collect()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::event::DecodedEntry;
|
||||
use pipeview_core::protocol::DecodedData;
|
||||
|
||||
#[test]
|
||||
fn push_evicts_oldest_when_limit_reached() {
|
||||
let mut rb = RingBuffer::new(3);
|
||||
rb.push(1);
|
||||
rb.push(2);
|
||||
rb.push(3);
|
||||
rb.push(4);
|
||||
rb.push(5);
|
||||
|
||||
assert_eq!(rb.len(), 3);
|
||||
assert_eq!(rb.get(0), Some(&3));
|
||||
assert_eq!(rb.get(1), Some(&4));
|
||||
assert_eq!(rb.get(2), Some(&5));
|
||||
assert_eq!(rb.get(3), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn iter_yields_oldest_to_newest() {
|
||||
let mut rb = RingBuffer::new(3);
|
||||
rb.push("a");
|
||||
rb.push("b");
|
||||
rb.push("c");
|
||||
|
||||
let items: Vec<_> = rb.iter().collect();
|
||||
assert_eq!(items, vec![&"a", &"b", &"c"]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn clear_empties_buffer() {
|
||||
let mut rb = RingBuffer::new(3);
|
||||
rb.push(1);
|
||||
rb.push(2);
|
||||
rb.clear();
|
||||
|
||||
assert!(rb.is_empty());
|
||||
assert_eq!(rb.len(), 0);
|
||||
assert_eq!(rb.get(0), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn set_limit_trims_excess() {
|
||||
let mut rb = RingBuffer::new(5);
|
||||
for i in 0..5 {
|
||||
rb.push(i);
|
||||
}
|
||||
rb.set_limit(2);
|
||||
|
||||
assert_eq!(rb.len(), 2);
|
||||
assert_eq!(rb.get(0), Some(&3));
|
||||
assert_eq!(rb.get(1), Some(&4));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn set_limit_can_grow_without_data_loss() {
|
||||
let mut rb = RingBuffer::new(2);
|
||||
rb.push(1);
|
||||
rb.push(2);
|
||||
rb.set_limit(5);
|
||||
|
||||
assert_eq!(rb.len(), 2);
|
||||
assert_eq!(rb.get(0), Some(&1));
|
||||
assert_eq!(rb.get(1), Some(&2));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn drain_recent_respects_count_and_order() {
|
||||
let mut rb = RingBuffer::new(5);
|
||||
for i in 0..5 {
|
||||
rb.push(i);
|
||||
}
|
||||
|
||||
let empty: Vec<i32> = rb.drain_recent(0);
|
||||
assert!(empty.is_empty());
|
||||
|
||||
let last_two = rb.drain_recent(2);
|
||||
assert_eq!(last_two, vec![3, 4]);
|
||||
|
||||
let all = rb.drain_recent(10);
|
||||
assert_eq!(all, vec![0, 1, 2, 3, 4]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn entries_for_pipeline_filters_by_name() {
|
||||
let mut rb = RingBuffer::new(10);
|
||||
rb.push(DecodedEntry {
|
||||
pipeline_name: "a".into(),
|
||||
data: DecodedData::Text("one".into()),
|
||||
});
|
||||
rb.push(DecodedEntry {
|
||||
pipeline_name: "b".into(),
|
||||
data: DecodedData::Text("two".into()),
|
||||
});
|
||||
rb.push(DecodedEntry {
|
||||
pipeline_name: "a".into(),
|
||||
data: DecodedData::Text("three".into()),
|
||||
});
|
||||
|
||||
let a = rb.entries_for_pipeline("a");
|
||||
assert_eq!(a.len(), 2);
|
||||
assert_eq!(a[0].pipeline_name, "a");
|
||||
assert_eq!(a[1].pipeline_name, "a");
|
||||
|
||||
let none = rb.entries_for_pipeline("missing");
|
||||
assert!(none.is_empty());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
use std::io::Write;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::sync::Once;
|
||||
use std::time::Duration;
|
||||
use std::{fs, path::PathBuf};
|
||||
|
||||
use mlua::Lua;
|
||||
use tokio::io::{AsyncReadExt, AsyncWriteExt};
|
||||
@@ -17,14 +18,30 @@ fn init_tracing() {
|
||||
});
|
||||
}
|
||||
|
||||
fn write_temp_lua(name: &str, script: &str) -> PathBuf {
|
||||
let nonce = std::time::SystemTime::now()
|
||||
.duration_since(std::time::UNIX_EPOCH)
|
||||
.unwrap()
|
||||
.as_nanos();
|
||||
let path = std::env::temp_dir().join(format!("pipeview_{name}_{nonce}.lua"));
|
||||
fs::write(&path, script).unwrap();
|
||||
path
|
||||
struct TempScript {
|
||||
path: PathBuf,
|
||||
_guard: tempfile::TempPath,
|
||||
}
|
||||
|
||||
impl TempScript {
|
||||
fn new(name: &str, script: &str) -> Self {
|
||||
let mut file = tempfile::Builder::new()
|
||||
.prefix(&format!("pipeview_{name}_"))
|
||||
.suffix(".lua")
|
||||
.tempfile()
|
||||
.unwrap();
|
||||
file.write_all(script.as_bytes()).unwrap();
|
||||
let path = file.path().to_path_buf();
|
||||
let guard = file.into_temp_path();
|
||||
Self {
|
||||
path,
|
||||
_guard: guard,
|
||||
}
|
||||
}
|
||||
|
||||
fn path(&self) -> &Path {
|
||||
&self.path
|
||||
}
|
||||
}
|
||||
|
||||
fn text_pipeline_lua() -> &'static str {
|
||||
@@ -208,7 +225,7 @@ async fn lua_session_custom_lua_pipeline() {
|
||||
let listener = TcpListener::bind("127.0.0.1:0").await.unwrap();
|
||||
let addr = listener.local_addr().unwrap().to_string();
|
||||
|
||||
let framer_path = write_temp_lua(
|
||||
let framer_script = TempScript::new(
|
||||
"custom_framer",
|
||||
r#"
|
||||
local buffer = ""
|
||||
@@ -239,7 +256,7 @@ async fn lua_session_custom_lua_pipeline() {
|
||||
}
|
||||
"#,
|
||||
);
|
||||
let decoder_path = write_temp_lua(
|
||||
let decoder_script = TempScript::new(
|
||||
"custom_decoder",
|
||||
r#"
|
||||
return {
|
||||
@@ -287,14 +304,12 @@ async fn lua_session_custom_lua_pipeline() {
|
||||
sess:close()
|
||||
"#,
|
||||
addr,
|
||||
framer_path.display(),
|
||||
decoder_path.display()
|
||||
framer_script.path().display(),
|
||||
decoder_script.path().display()
|
||||
);
|
||||
|
||||
lua.load(&script).exec_async().await.unwrap();
|
||||
server.await.unwrap();
|
||||
fs::remove_file(framer_path).unwrap();
|
||||
fs::remove_file(decoder_path).unwrap();
|
||||
}
|
||||
|
||||
// ── session:on_data callback ─────────────────────────────────────
|
||||
|
||||
Reference in New Issue
Block a user