bugfixing and testing
This commit is contained in:
parent
e490a2ce04
commit
d0bc7e952c
5 changed files with 411 additions and 109 deletions
|
|
@ -20,20 +20,22 @@ pub struct Filters {
|
||||||
|
|
||||||
undo_pos: usize,
|
undo_pos: usize,
|
||||||
#[serde(skip)]
|
#[serde(skip)]
|
||||||
path: PathBuf,
|
path: Option<PathBuf>,
|
||||||
#[serde(skip)]
|
#[serde(skip)]
|
||||||
error: Option<LastError>,
|
error: Option<LastError>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Filters {
|
impl Filters {
|
||||||
pub fn new(path: PathBuf, error: LastError) -> Self {
|
pub fn new(path: Option<PathBuf>, error: LastError) -> Self {
|
||||||
if path.exists() {
|
if let Some(path) = &path
|
||||||
|
&& path.exists()
|
||||||
|
{
|
||||||
match File::open(&path) {
|
match File::open(&path) {
|
||||||
Ok(f) => match serde_json::from_reader(f) {
|
Ok(f) => match serde_json::from_reader(f) {
|
||||||
Ok(i) => {
|
Ok(i) => {
|
||||||
return Self {
|
return Self {
|
||||||
error: Some(error),
|
error: Some(error),
|
||||||
path,
|
path: Some(path.clone()),
|
||||||
..i
|
..i
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
@ -66,18 +68,19 @@ impl Filters {
|
||||||
fn filters_changed(&self) {
|
fn filters_changed(&self) {
|
||||||
self.direct_children_cache.lock().unwrap().clear();
|
self.direct_children_cache.lock().unwrap().clear();
|
||||||
self.all_children_cache.lock().unwrap().clear();
|
self.all_children_cache.lock().unwrap().clear();
|
||||||
|
if let Some(path) = &self.path {
|
||||||
match File::options()
|
match File::options()
|
||||||
.create(true)
|
.create(true)
|
||||||
.write(true)
|
.write(true)
|
||||||
.truncate(true)
|
.truncate(true)
|
||||||
.open(&self.path)
|
.open(&path)
|
||||||
{
|
{
|
||||||
Ok(f) => {
|
Ok(f) => {
|
||||||
if let Err(e) = serde_json::to_writer(f, self) {
|
if let Err(e) = serde_json::to_writer(f, self) {
|
||||||
self.error.as_ref().inspect(|i| {
|
self.error.as_ref().inspect(|i| {
|
||||||
i.set(format!(
|
i.set(format!(
|
||||||
"failed to write contents of filter file at {}: {e}",
|
"failed to write contents of filter file at {}: {e}",
|
||||||
self.path.display()
|
path.display()
|
||||||
));
|
));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
@ -86,12 +89,13 @@ impl Filters {
|
||||||
self.error.as_ref().inspect(|i| {
|
self.error.as_ref().inspect(|i| {
|
||||||
i.set(format!(
|
i.set(format!(
|
||||||
"failed to open filter file at {}: {e}",
|
"failed to open filter file at {}: {e}",
|
||||||
self.path.display()
|
path.display()
|
||||||
));
|
));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn get(&self) -> &[Arc<Filter>] {
|
pub fn get(&self) -> &[Arc<Filter>] {
|
||||||
&self.filters[0..self.undo_pos]
|
&self.filters[0..self.undo_pos]
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@ use crate::tui::{
|
||||||
input::{FieldMatcher, InputState, InputTarget},
|
input::{FieldMatcher, InputState, InputTarget},
|
||||||
view::LogView,
|
view::LogView,
|
||||||
},
|
},
|
||||||
model::LogEntry,
|
model::{LogEntry, id},
|
||||||
processing::Cursor,
|
processing::Cursor,
|
||||||
widgets::last_error::LastError,
|
widgets::last_error::LastError,
|
||||||
};
|
};
|
||||||
|
|
@ -19,7 +19,7 @@ pub mod input;
|
||||||
pub mod view;
|
pub mod view;
|
||||||
|
|
||||||
pub struct LogViewer {
|
pub struct LogViewer {
|
||||||
cache: HashMap<Vec<usize>, LogView>,
|
cache: HashMap<usize, usize>,
|
||||||
|
|
||||||
pub view: LogView,
|
pub view: LogView,
|
||||||
|
|
||||||
|
|
@ -35,7 +35,7 @@ pub struct LogViewer {
|
||||||
|
|
||||||
impl LogViewer {
|
impl LogViewer {
|
||||||
pub fn new(start: Gc<LogEntry>, filters_path: PathBuf, error: LastError) -> Self {
|
pub fn new(start: Gc<LogEntry>, filters_path: PathBuf, error: LastError) -> Self {
|
||||||
let filters = Filters::new(filters_path, error);
|
let filters = Filters::new(Some(filters_path), error);
|
||||||
Self {
|
Self {
|
||||||
view: LogView {
|
view: LogView {
|
||||||
cursor: Cursor::new(start),
|
cursor: Cursor::new(start),
|
||||||
|
|
@ -200,8 +200,29 @@ impl LogViewer {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn update_offset_from_cache(&mut self) {
|
||||||
|
let cache_key = self.view.cursor.parent().as_ref().map(id).unwrap_or(0);
|
||||||
|
|
||||||
|
self.view.selection_offset = 0;
|
||||||
|
if let Some(offset) = self.cache.get(&cache_key) {
|
||||||
|
for _ in 0..*offset {
|
||||||
|
self.view.selection_offset += 1;
|
||||||
|
self.view.cursor.prev(&self.filters);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn add_to_cache(&mut self) {
|
||||||
|
let cache_key = self.view.cursor.parent().as_ref().map(id).unwrap_or(0);
|
||||||
|
self.cache.insert(cache_key, self.view.selection_offset);
|
||||||
|
}
|
||||||
|
|
||||||
pub fn back(&mut self) {
|
pub fn back(&mut self) {
|
||||||
self.view.cursor.exit(&self.filters);
|
self.add_to_cache();
|
||||||
|
if self.view.cursor.exit(&self.filters) {
|
||||||
|
self.update_offset_from_cache();
|
||||||
|
self.view.cursor.prev(&self.filters);
|
||||||
|
}
|
||||||
// self.cache.insert(self.path(), self.curr.clone());
|
// self.cache.insert(self.path(), self.curr.clone());
|
||||||
self.input_state.reset();
|
self.input_state.reset();
|
||||||
}
|
}
|
||||||
|
|
@ -217,38 +238,18 @@ impl LogViewer {
|
||||||
pub fn enter(&mut self) {
|
pub fn enter(&mut self) {
|
||||||
match self.input_state {
|
match self.input_state {
|
||||||
InputState::None => {
|
InputState::None => {
|
||||||
|
self.add_to_cache();
|
||||||
|
|
||||||
|
let orig = self.view.cursor.clone();
|
||||||
for _ in 0..(self.view.selection_offset + 1) {
|
for _ in 0..(self.view.selection_offset + 1) {
|
||||||
self.view.cursor.next(&self.filters);
|
self.view.cursor.next(&self.filters);
|
||||||
}
|
}
|
||||||
|
|
||||||
self.view.cursor.enter(&self.filters);
|
if self.view.cursor.enter(&self.filters) {
|
||||||
// let Some((s, _)) = self.selected() else {
|
self.update_offset_from_cache();
|
||||||
// return;
|
} else {
|
||||||
// };
|
self.view.cursor = orig;
|
||||||
// let Some(i) = s.from_start(0).map(|i| i.with_filters(self.filters.get())) else {
|
}
|
||||||
// return;
|
|
||||||
// };
|
|
||||||
//
|
|
||||||
// if i.clone().next(self.filters.get()).is_none() {
|
|
||||||
// return;
|
|
||||||
// }
|
|
||||||
// if i.clone()
|
|
||||||
// .next(self.filters.get())
|
|
||||||
// .is_some_and(|(i, _)| i.is_return())
|
|
||||||
// {
|
|
||||||
// return;
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// self.stack.push(mem::replace(
|
|
||||||
// &mut self.curr,
|
|
||||||
// LogView {
|
|
||||||
// cursor: i,
|
|
||||||
// selection_offset: 0,
|
|
||||||
// },
|
|
||||||
// ));
|
|
||||||
// if let Some(cached_view) = self.cache.get(&self.path()) {
|
|
||||||
// self.curr = cached_view.clone();
|
|
||||||
// }
|
|
||||||
}
|
}
|
||||||
InputState::Target(InputTarget::Fields(None)) => {
|
InputState::Target(InputTarget::Fields(None)) => {
|
||||||
self.input_state =
|
self.input_state =
|
||||||
|
|
|
||||||
|
|
@ -2,8 +2,7 @@ use std::{
|
||||||
collections::BTreeMap,
|
collections::BTreeMap,
|
||||||
hash::{DefaultHasher, Hash, Hasher},
|
hash::{DefaultHasher, Hash, Hasher},
|
||||||
path::PathBuf,
|
path::PathBuf,
|
||||||
sync::{Mutex, OnceLock},
|
sync::OnceLock,
|
||||||
thread,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
use dumpster::{Trace, TraceWith, Visitor, sync::Gc};
|
use dumpster::{Trace, TraceWith, Visitor, sync::Gc};
|
||||||
|
|
@ -28,7 +27,7 @@ pub fn pretty_print_value(v: &Value) -> String {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn id(input: &Gc<LogEntry>) -> usize {
|
pub fn id(input: &Gc<LogEntry>) -> usize {
|
||||||
Gc::as_ptr(input) as usize
|
Gc::as_ptr(input) as usize
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,9 @@
|
||||||
use std::{mem, sync::Arc};
|
use std::mem;
|
||||||
|
|
||||||
pub type FilterList = [Arc<Filter>];
|
|
||||||
|
|
||||||
use dumpster::sync::Gc;
|
use dumpster::sync::Gc;
|
||||||
use itertools::Itertools;
|
use itertools::Itertools;
|
||||||
|
|
||||||
use crate::tui::{filter::Filter, log_viewer::filters::Filters, model::LogEntry};
|
use crate::tui::{log_viewer::filters::Filters, model::LogEntry};
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct CursorMeta {
|
pub struct CursorMeta {
|
||||||
|
|
@ -62,8 +60,16 @@ impl Cursor {
|
||||||
pub fn update(&mut self, filters: &Filters) -> bool {
|
pub fn update(&mut self, filters: &Filters) -> bool {
|
||||||
// make a backup now
|
// make a backup now
|
||||||
let old = self.clone();
|
let old = self.clone();
|
||||||
|
|
||||||
|
// if the current one is inlined
|
||||||
|
if self.curr_is_inlined(filters) {
|
||||||
|
// try going one back
|
||||||
|
self.prev(filters);
|
||||||
|
self.next(filters);
|
||||||
|
}
|
||||||
|
|
||||||
// if the current one is removed...
|
// if the current one is removed...
|
||||||
if self.curr_is_removed(filters) {
|
if self.curr_is_removed(filters) || self.curr_is_inlined(filters) {
|
||||||
// try going forwards to the next visible node
|
// try going forwards to the next visible node
|
||||||
if self.next(filters) {
|
if self.next(filters) {
|
||||||
return true;
|
return true;
|
||||||
|
|
@ -92,6 +98,10 @@ impl Cursor {
|
||||||
self.curr.entry.clone()
|
self.curr.entry.clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn parent(&self) -> Option<Gc<LogEntry>> {
|
||||||
|
self.parents.last().map(|i| i.entry.clone())
|
||||||
|
}
|
||||||
|
|
||||||
fn curr_is_removed(&self, filters: &Filters) -> bool {
|
fn curr_is_removed(&self, filters: &Filters) -> bool {
|
||||||
self.curr().is_removed(filters)
|
self.curr().is_removed(filters)
|
||||||
}
|
}
|
||||||
|
|
@ -205,7 +215,6 @@ impl Cursor {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn prev(&mut self, filters: &Filters) -> bool {
|
pub fn prev(&mut self, filters: &Filters) -> bool {
|
||||||
loop {
|
|
||||||
if !self.prev_cont_in_parent(filters) {
|
if !self.prev_cont_in_parent(filters) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
@ -216,11 +225,10 @@ impl Cursor {
|
||||||
|
|
||||||
self.enter_end_internal();
|
self.enter_end_internal();
|
||||||
self.curr.continue_in_parent = true;
|
self.curr.continue_in_parent = true;
|
||||||
}
|
self.update(filters)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn next(&mut self, filters: &Filters) -> bool {
|
pub fn next(&mut self, filters: &Filters) -> bool {
|
||||||
loop {
|
|
||||||
if !self.next_cont_in_parent(filters) {
|
if !self.next_cont_in_parent(filters) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
@ -231,7 +239,7 @@ impl Cursor {
|
||||||
|
|
||||||
self.enter_start_internal();
|
self.enter_start_internal();
|
||||||
self.curr.continue_in_parent = true;
|
self.curr.continue_in_parent = true;
|
||||||
}
|
self.update(filters)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn enter(&mut self, filters: &Filters) -> bool {
|
pub fn enter(&mut self, filters: &Filters) -> bool {
|
||||||
|
|
@ -249,6 +257,10 @@ impl Cursor {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn exit(&mut self, filters: &Filters) -> bool {
|
pub fn exit(&mut self, filters: &Filters) -> bool {
|
||||||
|
if self.curr.continue_in_parent {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
if !self.exit_internal() {
|
if !self.exit_internal() {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,15 +1,12 @@
|
||||||
use std::{
|
use std::{
|
||||||
cell::RefCell,
|
cell::RefCell,
|
||||||
fs::File,
|
fs::File,
|
||||||
io::{self, BufRead, BufReader},
|
io::{self, BufRead, BufReader, Read},
|
||||||
mem,
|
mem,
|
||||||
path::{Path, PathBuf},
|
path::{Path, PathBuf},
|
||||||
rc::Rc,
|
rc::Rc,
|
||||||
sync::{
|
sync::OnceLock,
|
||||||
OnceLock,
|
thread::{self, JoinHandle},
|
||||||
mpsc::{Receiver, channel},
|
|
||||||
},
|
|
||||||
thread,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
use dumpster::sync::Gc;
|
use dumpster::sync::Gc;
|
||||||
|
|
@ -17,7 +14,7 @@ use dumpster::sync::Gc;
|
||||||
use crate::tui::model::{ChildInfo, LogEntry, RawLogEntry};
|
use crate::tui::model::{ChildInfo, LogEntry, RawLogEntry};
|
||||||
|
|
||||||
struct LogFileEntryGenerator {
|
struct LogFileEntryGenerator {
|
||||||
file: BufReader<File>,
|
file: BufReader<Box<dyn Read + Send + Sync + 'static>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl LogFileEntryGenerator {
|
impl LogFileEntryGenerator {
|
||||||
|
|
@ -38,7 +35,11 @@ impl LogFileEntryGenerator {
|
||||||
match serde_json::from_str(&line) {
|
match serde_json::from_str(&line) {
|
||||||
Ok(i) => Some(i),
|
Ok(i) => Some(i),
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
|
#[cfg(test)]
|
||||||
|
panic!("deserializing: {e:?} in {line}");
|
||||||
|
#[cfg(not(test))]
|
||||||
eprintln!("deserializing: {e:?} in {line}");
|
eprintln!("deserializing: {e:?} in {line}");
|
||||||
|
#[cfg(not(test))]
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -106,7 +107,7 @@ impl LogFileEntryGenerator {
|
||||||
|
|
||||||
struct Inner {
|
struct Inner {
|
||||||
pub path: PathBuf,
|
pub path: PathBuf,
|
||||||
|
pub jh: Option<JoinHandle<()>>,
|
||||||
first: Option<Gc<LogEntry>>,
|
first: Option<Gc<LogEntry>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -114,17 +115,21 @@ struct Inner {
|
||||||
pub struct LogfileReader(Rc<RefCell<Inner>>);
|
pub struct LogfileReader(Rc<RefCell<Inner>>);
|
||||||
|
|
||||||
impl LogfileReader {
|
impl LogfileReader {
|
||||||
pub fn new(p: &Path) -> io::Result<Self> {
|
pub fn new(p: impl AsRef<Path>) -> io::Result<Self> {
|
||||||
let file = File::open(p)?;
|
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 {
|
let mut generator = LogFileEntryGenerator {
|
||||||
file: BufReader::new(file),
|
file: BufReader::new(Box::new(r)),
|
||||||
};
|
};
|
||||||
|
|
||||||
let first = generator.next_entry(None);
|
let first = generator.next_entry(None);
|
||||||
|
|
||||||
|
let jh = thread::spawn({
|
||||||
|
let first = first.clone();
|
||||||
|
move || {
|
||||||
if let Some(mut curr_last) = first.clone() {
|
if let Some(mut curr_last) = first.clone() {
|
||||||
thread::spawn(move || {
|
|
||||||
while let Some(new) = generator.next_entry(Some(curr_last.clone())) {
|
while let Some(new) = generator.next_entry(Some(curr_last.clone())) {
|
||||||
assert!(new.prev().is_some());
|
assert!(new.prev().is_some());
|
||||||
curr_last.initialize_next().get_or_init({
|
curr_last.initialize_next().get_or_init({
|
||||||
|
|
@ -133,13 +138,15 @@ impl LogfileReader {
|
||||||
});
|
});
|
||||||
curr_last = new;
|
curr_last = new;
|
||||||
}
|
}
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
Ok(Self(Rc::new(RefCell::new(Inner {
|
Self(Rc::new(RefCell::new(Inner {
|
||||||
path: p.to_path_buf(),
|
path: p.as_ref().to_path_buf(),
|
||||||
first,
|
first,
|
||||||
}))))
|
jh: Some(jh),
|
||||||
|
})))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn path(&self) -> PathBuf {
|
pub fn path(&self) -> PathBuf {
|
||||||
|
|
@ -150,3 +157,282 @@ impl LogfileReader {
|
||||||
self.0.borrow().first.clone()
|
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