test: add CI, RingBuffer tests, core proptest, RAII lua fixtures

This commit is contained in:
2026-08-17 21:19:33 +08:00
parent 81c0ce13c4
commit 5f5477d658
12 changed files with 450 additions and 53 deletions

View File

@@ -15,3 +15,4 @@ thiserror = { workspace = true }
[dev-dependencies]
tokio = { workspace = true, features = ["full"] }
tracing-subscriber = { workspace = true }
tempfile = "3"

View File

@@ -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());
}
}

View File

@@ -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 ─────────────────────────────────────