bugfixing and testing
This commit is contained in:
parent
e490a2ce04
commit
d0bc7e952c
5 changed files with 411 additions and 109 deletions
|
|
@ -1,15 +1,12 @@
|
|||
use std::{
|
||||
cell::RefCell,
|
||||
fs::File,
|
||||
io::{self, BufRead, BufReader},
|
||||
io::{self, BufRead, BufReader, Read},
|
||||
mem,
|
||||
path::{Path, PathBuf},
|
||||
rc::Rc,
|
||||
sync::{
|
||||
OnceLock,
|
||||
mpsc::{Receiver, channel},
|
||||
},
|
||||
thread,
|
||||
sync::OnceLock,
|
||||
thread::{self, JoinHandle},
|
||||
};
|
||||
|
||||
use dumpster::sync::Gc;
|
||||
|
|
@ -17,7 +14,7 @@ use dumpster::sync::Gc;
|
|||
use crate::tui::model::{ChildInfo, LogEntry, RawLogEntry};
|
||||
|
||||
struct LogFileEntryGenerator {
|
||||
file: BufReader<File>,
|
||||
file: BufReader<Box<dyn Read + Send + Sync + 'static>>,
|
||||
}
|
||||
|
||||
impl LogFileEntryGenerator {
|
||||
|
|
@ -38,7 +35,11 @@ impl LogFileEntryGenerator {
|
|||
match serde_json::from_str(&line) {
|
||||
Ok(i) => Some(i),
|
||||
Err(e) => {
|
||||
#[cfg(test)]
|
||||
panic!("deserializing: {e:?} in {line}");
|
||||
#[cfg(not(test))]
|
||||
eprintln!("deserializing: {e:?} in {line}");
|
||||
#[cfg(not(test))]
|
||||
None
|
||||
}
|
||||
}
|
||||
|
|
@ -106,7 +107,7 @@ impl LogFileEntryGenerator {
|
|||
|
||||
struct Inner {
|
||||
pub path: PathBuf,
|
||||
|
||||
pub jh: Option<JoinHandle<()>>,
|
||||
first: Option<Gc<LogEntry>>,
|
||||
}
|
||||
|
||||
|
|
@ -114,32 +115,38 @@ struct Inner {
|
|||
pub struct LogfileReader(Rc<RefCell<Inner>>);
|
||||
|
||||
impl LogfileReader {
|
||||
pub fn new(p: &Path) -> io::Result<Self> {
|
||||
let file = File::open(p)?;
|
||||
pub fn new(p: impl AsRef<Path>) -> io::Result<Self> {
|
||||
Ok(Self::new_from_read(File::open(p.as_ref())?, p))
|
||||
}
|
||||
|
||||
pub fn new_from_read(r: impl Read + Send + Sync + 'static, p: impl AsRef<Path>) -> Self {
|
||||
let mut generator = LogFileEntryGenerator {
|
||||
file: BufReader::new(file),
|
||||
file: BufReader::new(Box::new(r)),
|
||||
};
|
||||
|
||||
let first = generator.next_entry(None);
|
||||
|
||||
if let Some(mut curr_last) = first.clone() {
|
||||
thread::spawn(move || {
|
||||
while let Some(new) = generator.next_entry(Some(curr_last.clone())) {
|
||||
assert!(new.prev().is_some());
|
||||
curr_last.initialize_next().get_or_init({
|
||||
let new = new.clone();
|
||||
|| new
|
||||
});
|
||||
curr_last = new;
|
||||
let jh = thread::spawn({
|
||||
let first = first.clone();
|
||||
move || {
|
||||
if let Some(mut curr_last) = first.clone() {
|
||||
while let Some(new) = generator.next_entry(Some(curr_last.clone())) {
|
||||
assert!(new.prev().is_some());
|
||||
curr_last.initialize_next().get_or_init({
|
||||
let new = new.clone();
|
||||
|| new
|
||||
});
|
||||
curr_last = new;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
Ok(Self(Rc::new(RefCell::new(Inner {
|
||||
path: p.to_path_buf(),
|
||||
Self(Rc::new(RefCell::new(Inner {
|
||||
path: p.as_ref().to_path_buf(),
|
||||
first,
|
||||
}))))
|
||||
jh: Some(jh),
|
||||
})))
|
||||
}
|
||||
|
||||
pub fn path(&self) -> PathBuf {
|
||||
|
|
@ -150,3 +157,282 @@ impl LogfileReader {
|
|||
self.0.borrow().first.clone()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::sync::Arc;
|
||||
|
||||
use super::*;
|
||||
use crate::tui::{
|
||||
filter::{Filter, FilterKind, Matcher, MatcherValue},
|
||||
log_viewer::filters::Filters,
|
||||
processing::Cursor,
|
||||
widgets::last_error::LastError,
|
||||
};
|
||||
|
||||
fn parse(data: &str) -> Cursor {
|
||||
let r = LogfileReader::new_from_read(
|
||||
std::io::Cursor::new(data.trim().as_bytes().to_vec()),
|
||||
"test",
|
||||
);
|
||||
r.0.borrow_mut().jh.take().unwrap().join().unwrap();
|
||||
Cursor::new(r.first().unwrap())
|
||||
}
|
||||
|
||||
fn with_fields(fields: &str) -> String {
|
||||
format!(
|
||||
r#"{{"timestamp": "2026-03-30 23:47Z", "level": "DEBUG", "filename": "foo", "line_number": 30, "fields": {fields}}}"#
|
||||
)
|
||||
}
|
||||
|
||||
fn filters() -> Filters {
|
||||
let le = LastError::new();
|
||||
Filters::new(None, le)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn get_message() {
|
||||
let c = parse(&[with_fields(r#"{"message": "foo"}"#)].join("\n"));
|
||||
assert_eq!(c.curr().message_or_name(), Some("foo".to_string()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn bounds() {
|
||||
let mut c = parse(&[with_fields(r#"{"message": "foo"}"#)].join("\n"));
|
||||
let f = filters();
|
||||
|
||||
assert_eq!(c.curr().message_or_name(), Some("foo".to_string()));
|
||||
assert!(!c.next(&f));
|
||||
assert!(!c.prev(&f));
|
||||
assert!(!c.enter(&f));
|
||||
assert!(!c.exit(&f));
|
||||
assert_eq!(c.curr().message_or_name(), Some("foo".to_string()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn two_singles() {
|
||||
let mut c = parse(
|
||||
&[
|
||||
with_fields(r#"{"message": "foo"}"#),
|
||||
with_fields(r#"{"message": "bar"}"#),
|
||||
]
|
||||
.join("\n"),
|
||||
);
|
||||
let f = filters();
|
||||
|
||||
assert_eq!(c.curr().message_or_name(), Some("foo".to_string()));
|
||||
assert!(c.next(&f));
|
||||
assert_eq!(c.curr().message_or_name(), Some("bar".to_string()));
|
||||
assert!(!c.next(&f));
|
||||
assert_eq!(c.curr().message_or_name(), Some("bar".to_string()));
|
||||
assert!(c.prev(&f));
|
||||
assert_eq!(c.curr().message_or_name(), Some("foo".to_string()));
|
||||
assert!(!c.prev(&f));
|
||||
assert!(!c.enter(&f));
|
||||
assert!(!c.exit(&f));
|
||||
assert_eq!(c.curr().message_or_name(), Some("foo".to_string()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn enter_exit() {
|
||||
let mut c = parse(
|
||||
&[
|
||||
with_fields(r#"{"message": "foo"}"#),
|
||||
with_fields(r#"{"message": "enter"}"#),
|
||||
with_fields(r#"{"message": "baz"}"#),
|
||||
with_fields(r#"{"message": "meow"}"#),
|
||||
with_fields(r#"{"message": "exit"}"#),
|
||||
with_fields(r#"{"message": "bar"}"#),
|
||||
]
|
||||
.join("\n"),
|
||||
);
|
||||
let f = filters();
|
||||
|
||||
assert_eq!(c.curr().message_or_name(), Some("foo".to_string()));
|
||||
assert!(c.next(&f));
|
||||
assert!(matches!(Gc::as_ref(&c.curr()), LogEntry::Sub { .. }));
|
||||
assert!(c.next(&f));
|
||||
assert_eq!(c.curr().message_or_name(), Some("bar".to_string()));
|
||||
assert!(c.prev(&f));
|
||||
assert!(c.enter(&f));
|
||||
assert_eq!(c.curr().message_or_name(), Some("baz".to_string()));
|
||||
assert!(!c.enter(&f));
|
||||
assert!(!c.prev(&f));
|
||||
assert!(c.exit(&f));
|
||||
assert!(matches!(Gc::as_ref(&c.curr()), LogEntry::Sub { .. }));
|
||||
assert!(c.enter(&f));
|
||||
assert!(c.next(&f));
|
||||
assert!(!c.enter(&f));
|
||||
assert!(!c.next(&f));
|
||||
assert_eq!(c.curr().message_or_name(), Some("meow".to_string()));
|
||||
assert!(c.exit(&f));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn empty_enter() {
|
||||
let mut c = parse(
|
||||
&[
|
||||
with_fields(r#"{"message": "foo"}"#),
|
||||
with_fields(r#"{"message": "enter"}"#),
|
||||
with_fields(r#"{"message": "exit"}"#),
|
||||
with_fields(r#"{"message": "bar"}"#),
|
||||
]
|
||||
.join("\n"),
|
||||
);
|
||||
let f = filters();
|
||||
|
||||
assert_eq!(c.curr().message_or_name(), Some("foo".to_string()));
|
||||
assert!(c.next(&f));
|
||||
assert!(
|
||||
matches!(Gc::as_ref(&c.curr()), LogEntry::Sub { children, .. } if children.first_child.is_none() )
|
||||
);
|
||||
assert!(!c.enter(&f));
|
||||
assert!(!c.exit(&f));
|
||||
assert!(matches!(Gc::as_ref(&c.curr()), LogEntry::Sub { .. }));
|
||||
assert!(c.next(&f));
|
||||
assert_eq!(c.curr().message_or_name(), Some("bar".to_string()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn remove_first() {
|
||||
let mut c = parse(
|
||||
&[
|
||||
with_fields(r#"{"message": "foo"}"#),
|
||||
with_fields(r#"{"message": "baz"}"#),
|
||||
with_fields(r#"{"message": "meow"}"#),
|
||||
with_fields(r#"{"message": "bar"}"#),
|
||||
]
|
||||
.join("\n"),
|
||||
);
|
||||
let mut f = filters();
|
||||
f.push(Arc::new(Filter {
|
||||
matcher: Matcher::Field {
|
||||
name: "message".to_string(),
|
||||
value: MatcherValue::Exact("foo".to_string()),
|
||||
},
|
||||
kind: FilterKind::Remove,
|
||||
}));
|
||||
|
||||
assert!(c.next(&f));
|
||||
assert_eq!(c.curr().message_or_name(), Some("baz".to_string()));
|
||||
assert!(!c.prev(&f));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn remove_middle() {
|
||||
let mut c = parse(
|
||||
&[
|
||||
with_fields(r#"{"message": "foo"}"#),
|
||||
with_fields(r#"{"message": "baz"}"#),
|
||||
with_fields(r#"{"message": "meow"}"#),
|
||||
with_fields(r#"{"message": "bar"}"#),
|
||||
]
|
||||
.join("\n"),
|
||||
);
|
||||
let mut f = filters();
|
||||
f.push(Arc::new(Filter {
|
||||
matcher: Matcher::Field {
|
||||
name: "message".to_string(),
|
||||
value: MatcherValue::Exact("baz".to_string()),
|
||||
},
|
||||
kind: FilterKind::Remove,
|
||||
}));
|
||||
|
||||
assert_eq!(c.curr().message_or_name(), Some("foo".to_string()));
|
||||
assert!(c.next(&f));
|
||||
assert_eq!(c.curr().message_or_name(), Some("meow".to_string()));
|
||||
assert!(c.prev(&f));
|
||||
assert_eq!(c.curr().message_or_name(), Some("foo".to_string()));
|
||||
assert!(!c.prev(&f));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn inline() {
|
||||
let mut c = parse(
|
||||
&[
|
||||
with_fields(r#"{"message": "foo"}"#),
|
||||
with_fields(r#"{"message": "enter", "name": "nest"}"#),
|
||||
with_fields(r#"{"message": "baz"}"#),
|
||||
with_fields(r#"{"message": "meow"}"#),
|
||||
with_fields(r#"{"message": "exit"}"#),
|
||||
with_fields(r#"{"message": "bar"}"#),
|
||||
]
|
||||
.join("\n"),
|
||||
);
|
||||
let mut f = filters();
|
||||
f.push(Arc::new(Filter {
|
||||
matcher: Matcher::Field {
|
||||
name: "name".to_string(),
|
||||
value: MatcherValue::Exact("nest".to_string()),
|
||||
},
|
||||
kind: FilterKind::Inline,
|
||||
}));
|
||||
|
||||
assert_eq!(c.curr().message_or_name(), Some("foo".to_string()));
|
||||
assert!(c.next(&f));
|
||||
assert_eq!(c.curr().message_or_name(), Some("baz".to_string()));
|
||||
assert!(c.next(&f));
|
||||
assert!(!c.exit(&f));
|
||||
assert_eq!(c.curr().message_or_name(), Some("meow".to_string()));
|
||||
assert!(c.next(&f));
|
||||
assert_eq!(c.curr().message_or_name(), Some("bar".to_string()));
|
||||
assert!(c.prev(&f));
|
||||
assert_eq!(c.curr().message_or_name(), Some("meow".to_string()));
|
||||
assert!(!c.exit(&f));
|
||||
assert!(c.prev(&f));
|
||||
assert_eq!(c.curr().message_or_name(), Some("baz".to_string()));
|
||||
assert!(c.prev(&f));
|
||||
assert_eq!(c.curr().message_or_name(), Some("foo".to_string()));
|
||||
assert!(!c.prev(&f));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn inline_while_inside() {
|
||||
let mut c = parse(
|
||||
&[
|
||||
with_fields(r#"{"message": "foo"}"#),
|
||||
with_fields(r#"{"message": "enter", "name": "nest"}"#),
|
||||
with_fields(r#"{"message": "baz"}"#),
|
||||
with_fields(r#"{"message": "meow"}"#),
|
||||
with_fields(r#"{"message": "exit"}"#),
|
||||
with_fields(r#"{"message": "bar"}"#),
|
||||
]
|
||||
.join("\n"),
|
||||
);
|
||||
let mut f = filters();
|
||||
|
||||
assert_eq!(c.curr().message_or_name(), Some("foo".to_string()));
|
||||
assert!(c.next(&f));
|
||||
assert!(c.enter(&f));
|
||||
assert_eq!(c.curr().message_or_name(), Some("baz".to_string()));
|
||||
// inline the current item
|
||||
f.push(Arc::new(Filter {
|
||||
matcher: Matcher::Field {
|
||||
name: "name".to_string(),
|
||||
value: MatcherValue::Exact("nest".to_string()),
|
||||
},
|
||||
kind: FilterKind::Inline,
|
||||
}));
|
||||
c.update_with_parents(&f);
|
||||
assert_eq!(c.curr().message_or_name(), Some("baz".to_string()));
|
||||
println!("undo");
|
||||
f.undo();
|
||||
c.update_with_parents(&f);
|
||||
assert_eq!(c.curr().message_or_name(), Some("nest".to_string()));
|
||||
assert!(c.next(&f));
|
||||
assert_eq!(c.curr().message_or_name(), Some("bar".to_string()));
|
||||
assert!(!c.next(&f));
|
||||
assert!(c.prev(&f));
|
||||
assert!(c.enter(&f));
|
||||
assert_eq!(c.curr().message_or_name(), Some("baz".to_string()));
|
||||
assert!(c.next(&f));
|
||||
assert_eq!(c.curr().message_or_name(), Some("meow".to_string()));
|
||||
f.redo();
|
||||
c.update_with_parents(&f);
|
||||
// redo inlines, and goes to start of inlined part
|
||||
assert_eq!(c.curr().message_or_name(), Some("baz".to_string()));
|
||||
assert!(c.prev(&f));
|
||||
assert_eq!(c.curr().message_or_name(), Some("foo".to_string()));
|
||||
assert!(!c.prev(&f));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue