use chrono::Duration; use sd_id128::*; use sd_journal::*; use std::{ffi::CString, path::{Path, PathBuf}}; // testing on sd-journal // Copyright (C) 2020 Christian Klaue ente@ck76.de // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU Affero General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Affero General Public License for more details. // // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . fn open_testdata() -> sd_journal::Journal { let mut test_data = PathBuf::from(env!("CARGO_MANIFEST_DIR")); test_data.push("test-data/"); println!("looking for test data in folder {}", test_data.display()); Journal::open_directory(&test_data, PathFlags::FullPath, UserFlags::AllUsers).unwrap() } #[test] fn log_message() { // send various different "Hello World!" to Journal // the following lines are all synonyms Journal::log_message(Level::Info, "Hello World!").unwrap(); Journal::log_message(Level::Info, String::from("Hello World!").as_str()).unwrap(); Journal::log_message(Level::Info, String::from("Hello World!")).unwrap(); Journal::log_message(Level::Info, CString::new("Hello World!").unwrap()).unwrap(); } #[test] fn log_raw_record() { // send various different "Hello World!" to Journal // the first two lines are synonyms Journal::log_message(Level::Info, "Hello World!").unwrap(); Journal::log_raw_record(&["PRIORITY=6", "MESSAGE=Hello World!"]).unwrap(); // data: &Vec Journal::log_raw_record(&vec![format!("PRIORITY={}", Level::Info), "MESSAGE=Hello World!".to_string(), format!("CODE_LINE={}", line!()), format!("CODE_FILE={}", file!()), "CUSTOM_FIELD=42".to_string()]).unwrap(); // data: &[&str] Journal::log_raw_record(&["MESSAGE=Hello World!", &format!("PRIORITY={}", Level::Info), &format!("CODE_FILE={}", file!()), &format!("CODE_LINE={}", line!()), "CUSTOM_FIELD=42"]).unwrap(); } #[test] fn get_catalog_for_message_id() { // find the very first message with a catalog entry and print it. // if no such message is found: successful anyway // TODO: find a better and more meaningful test let journal = Journal::open(FileFlags::AllFiles, UserFlags::AllUsers).unwrap(); loop { match journal.next() { Ok(CursorMovement::EoF) => break, Ok(_) => (), Err(_) => break } let id = match journal.get_data("MESSAGE_ID") { Err(_) => continue, Ok(value) => value }; println!("Message ID: {}", id); let id128 = sd_id128::ID128::from_str(&id).unwrap(); let catalog = match Journal::get_catalog_for_message_id(id128) { Err(_) => continue, Ok(v) => v }; println!("{}", catalog); break; } } #[test] fn open() { // Open the local system journal using various flags Journal::open(FileFlags::AllFiles, UserFlags::AllUsers).unwrap(); Journal::open(FileFlags::LocalOnly, UserFlags::CurrentUserOnly).unwrap(); Journal::open(FileFlags::RuntimeOnly, UserFlags::CurrentUserAndSystemOnly).unwrap(); Journal::open(FileFlags::LocalOnly, UserFlags::SystemOnly).unwrap(); Journal::open(FileFlags::LocalRuntimeOnly, UserFlags::CurrentUserAndSystemOnly).unwrap(); } #[test] fn open_namespace() { // Open the journal for a namespace including the default namespace Journal::open_namespace("namespace", NamespaceFlags::DefaultNamespaceIncluded, FileFlags::LocalOnly, UserFlags::AllUsers).unwrap(); // open a non-existent namespace and make sure, it is empty let journal = Journal::open_namespace("akjghöowighjökvndsövlljsk", NamespaceFlags::SelectedNamespaceOnly, FileFlags::AllFiles, UserFlags::AllUsers).unwrap(); assert_eq!(journal.next().unwrap(), CursorMovement::EoF); } #[test] fn open_all_namespaces() { // open the journal for all namespaces let journal = Journal::open_all_namespaces(FileFlags::AllFiles, UserFlags::AllUsers).unwrap(); assert_eq!(journal.next().unwrap(), CursorMovement::Done); } #[test] fn open_directory() { // open the system journal by pointing to root with path flags set to // PathToOSRoot Journal::open_directory("/", PathFlags::PathToOSRoot, UserFlags::AllUsers).unwrap(); Journal::open_directory(Path::new("/"), PathFlags::PathToOSRoot, UserFlags::AllUsers).unwrap(); Journal::open_directory(PathBuf::from("/"), PathFlags::PathToOSRoot, UserFlags::AllUsers).unwrap(); // open test data included in a project located in a folder "test-data" in the // project root let mut test_data = PathBuf::from(env!("CARGO_MANIFEST_DIR")); test_data.push("test-data/"); println!("looking for test data in folder {}", test_data.display()); Journal::open_directory(test_data, PathFlags::FullPath, UserFlags::AllUsers).unwrap(); // fail on a non existing folder Journal::open_directory("/...", PathFlags::FullPath, UserFlags::AllUsers).unwrap_err(); } #[test] fn open_files() { // open the curreńt system.journal file in the default location for journals // /var/log/journal//system.journal let machine_id = sd_id128::ID128::machine_id().unwrap() .to_string_sd() .unwrap(); let mut sdjournal_path = PathBuf::from("/var/log/journal/"); sdjournal_path.push(&machine_id); sdjournal_path.push("system.journal"); println!("looking for sd-journal in {}", sdjournal_path.display()); Journal::open_files([sdjournal_path]).unwrap(); // open test data included in a project located in a folder "test-data" in the // project root let mut test_data = PathBuf::from(env!("CARGO_MANIFEST_DIR")); test_data.push("test-data/test.journal"); println!("looking for test data in {}", test_data.display()); Journal::open_files([test_data]).unwrap(); // fail on non-existing file Journal::open_files(vec!["/abcdefghijk.xyz"]).unwrap_err(); } #[test] fn next() { let mut test_data = PathBuf::from(env!("CARGO_MANIFEST_DIR")); test_data.push("test-data/"); println!("looking for test data in folder {}", test_data.display()); let journal = Journal::open_directory(&test_data, PathFlags::FullPath, UserFlags::AllUsers).unwrap(); // loop over a journal & print it's messages while let Ok(CursorMovement::Done) = journal.next() { // do something on each cursor, e.g. print the MESSAGE println!("{}", journal.get_data("MESSAGE").unwrap()); } // do a next() after seek_tail() to hit an EoF journal.seek_tail().unwrap(); // there is a [defect in libsystemd](https://github.com/systemd/systemd/issues/17662) // which requires you to do a previous() after seek_tail() in order to get // to the expected position journal.previous().unwrap(); assert_eq!(journal.next().unwrap(), CursorMovement::EoF); } #[test] fn iter() { let mut test_data = PathBuf::from(env!("CARGO_MANIFEST_DIR")); test_data.push("test-data/"); println!("looking for test data in folder {}", test_data.display()); let journal = Journal::open_directory(&test_data, PathFlags::FullPath, UserFlags::AllUsers).unwrap(); // loop over a journal & print it's messages for cursor in journal.iter() { match cursor { Err(_) => break, Ok(cursor) => println!("{}", cursor.get_data("MESSAGE").unwrap()) } } // ... journal.seek_head().unwrap(); let cursor = journal.iter_reverse().next().unwrap().unwrap(); // the following two lines are actually return the same value let m1 = cursor.get_data("MESSAGE").unwrap(); let m2 = journal.get_data("MESSAGE").unwrap(); assert_eq!(m1, m2); } #[test] fn previous() { let mut test_data = PathBuf::from(env!("CARGO_MANIFEST_DIR")); test_data.push("test-data/"); println!("looking for test data in folder {}", test_data.display()); let journal = Journal::open_directory(&test_data, PathFlags::FullPath, UserFlags::AllUsers).unwrap(); journal.seek_tail().unwrap(); // loop over a journal & print it's messages while let Ok(CursorMovement::Done) = journal.previous() { // do something on each cursor, e.g. print the MESSAGE println!("{}", journal.get_data("MESSAGE").unwrap()); } // do a previous() after seek_head() to hit an EoF journal.seek_head().unwrap(); // there is a [defect in libsystemd](https://github.com/systemd/systemd/issues/17662) // which requires you to do a next() after seek_head() in order to get // the expected EoF journal.next().unwrap(); assert_eq!(journal.previous().unwrap(), CursorMovement::EoF); } #[test] fn iter_reverse() { let mut test_data = PathBuf::from(env!("CARGO_MANIFEST_DIR")); test_data.push("test-data/"); println!("looking for test data in folder {}", test_data.display()); let journal = Journal::open_directory(&test_data, PathFlags::FullPath, UserFlags::AllUsers).unwrap(); journal.seek_tail().unwrap(); // loop over a journal & print it's messages for cursor in journal.iter_reverse() { match cursor { Err(_) => break, Ok(cursor) => println!("{}", cursor.get_data("MESSAGE").unwrap()) } } // ... journal.seek_tail().unwrap(); let cursor = journal.iter_reverse().next().unwrap().unwrap(); // the following two lines are actually return the same value let m1 = cursor.get_data("MESSAGE").unwrap(); let m2 = journal.get_data("MESSAGE").unwrap(); assert_eq!(m1, m2); } #[test] fn next_skip() { // do a next_skip(10) and result in a Limited(5) let journal = Journal::open(FileFlags::AllFiles, UserFlags::AllUsers).unwrap(); journal.seek_tail().unwrap(); // there is a [defect in libsystemd](https://github.com/systemd/systemd/issues/17662) // which requires you to do a previous() after seek_tail() in order to get // to the expected position journal.previous().unwrap(); journal.previous_skip(5).unwrap(); assert_eq!(journal.next_skip(10).unwrap(), CursorMovement::Limited(5)); } #[test] fn previous_skip() { // do a previous_skip(10) and result in a Limited(5) let journal = Journal::open(FileFlags::AllFiles, UserFlags::AllUsers).unwrap(); journal.seek_head().unwrap(); // there is a [defect in libsystemd](https://github.com/systemd/systemd/issues/17662) // which requires you to do a previous() after seek_tail() in order to get // to the expected position journal.next().unwrap(); journal.next_skip(5).unwrap(); assert_eq!(journal.previous_skip(10).unwrap(), CursorMovement::Limited(5)); } #[test] fn seek_head() { // seek_head --> next() --> previous() --> EoF let journal = Journal::open(FileFlags::AllFiles, UserFlags::AllUsers).unwrap(); journal.seek_head().unwrap(); // seek_head() should be followed by a next() before any previous() --> issues journal.next().unwrap(); // previous() should hit EoF assert_eq!(journal.previous(), Ok(CursorMovement::EoF)); } #[test] fn seek_tail() { // seek_tail --> previous() --> next() --> EoF let journal = Journal::open(FileFlags::AllFiles, UserFlags::AllUsers).unwrap(); journal.seek_tail().unwrap(); // seek_head() should be followed by a previous() before any next() --> issues journal.previous().unwrap(); // next() should hit EoF assert_eq!(journal.next(), Ok(CursorMovement::EoF)); } #[test] fn seek_monotonic() { // get monotonic cutoff of current boot id --> seek to start +5 and do a // previous() then get the monotonic time ==> should be equal to start let journal = Journal::open(FileFlags::AllFiles, UserFlags::AllUsers).unwrap(); let boot_id = ID128::boot_id().unwrap(); let (from, _) = journal.get_monotonic_cutoff(boot_id.clone()).unwrap(); journal.seek_monotonic(boot_id.clone(), from).unwrap(); journal.previous().unwrap(); let (mono_journal, bid_journal) = journal.get_monotonic().unwrap(); assert_eq!(mono_journal, from); assert_eq!(bid_journal, boot_id); } #[test] fn seek_realtime() { // get current realtime now // seek_realtime(now) + previous ==> last_entry // clock of last_entry should be < now let journal = Journal::open(FileFlags::AllFiles, UserFlags::AllUsers).unwrap(); let now = chrono::offset::Local::now().naive_local(); journal.seek_realtime(now).unwrap(); journal.previous().unwrap(); let clock_last_entry = journal.get_realtime().unwrap(); assert!(clock_last_entry <= now); // seek_tail + previous // clock of last_entry should match clock of tail journal.seek_tail().unwrap(); journal.previous().unwrap(); let clock_tail = journal.get_realtime().unwrap(); assert_eq!(clock_last_entry, clock_tail); // get realtime_cutoff // clock of last_entry should match end of realtime_cutoff let (start, end) = journal.get_realtime_cutoff().unwrap(); assert_eq!(clock_last_entry, end); // seek to 5 microseconds before start of journal + next() // clock of first entry should match start of cutoff_realtime let bstart = start.checked_sub_signed(chrono::Duration::seconds(6)) .unwrap(); journal.seek_realtime(bstart).unwrap(); journal.next().unwrap(); assert_eq!(start, journal.get_realtime().unwrap()); // seek to 5 microseconds past end of journal + previous() // clock of entry should match the end of cutoff_realtime let aend = end.checked_add_signed(Duration::seconds(10)).unwrap(); journal.seek_realtime(aend).unwrap(); journal.previous().unwrap(); assert_eq!(end, journal.get_realtime().unwrap()); } #[test] fn seek_cursor_id() { // go to 10 items before end --> get cursor // go to head // seek_cursor(cursor) // assert that get_cursor gives the same cursor again // next() --> assert get_cursor gives another cursor this time let journal = Journal::open(FileFlags::AllFiles, UserFlags::AllUsers).unwrap(); journal.seek_tail().unwrap(); journal.previous_skip(10).unwrap(); let cursor = journal.get_cursor_id().unwrap(); println!("initial cursor: {:?}", cursor); journal.seek_head().unwrap(); journal.next().unwrap(); journal.seek_cursor_id(cursor.clone()).unwrap(); journal.previous().unwrap(); let same_cursor = journal.get_cursor_id().unwrap(); println!("same cursor: {:?}", same_cursor); assert_eq!(cursor, same_cursor); journal.next().unwrap(); let other_cursor = journal.get_cursor_id().unwrap(); println!("other cursor: {:?}", other_cursor); assert_ne!(cursor, other_cursor); } #[test] fn add_match() { // add a match for "MESSAGE=Hello World!" should succeed while a match for // "MESSAGE=Hello Woooooorld!" should not return any matches let journal = Journal::open(FileFlags::AllFiles, UserFlags::AllUsers).unwrap(); journal.add_match("MESSAGE=Hello World!").unwrap(); assert_eq!(journal.next().unwrap(), CursorMovement::Done); while let Ok(CursorMovement::Done) = journal.next() { // do something on the journal entries } journal.flush_matches(); journal.add_match("MESSAGE=Hello Woooooorld!").unwrap(); assert_eq!(journal.next().unwrap(), CursorMovement::EoF); // add some more matches to assure type compatibility journal.add_match("MESSAGE=Hello Woooooorld!").unwrap(); journal.add_match(&"MESSAGE=Hello Woooooorld!").unwrap(); journal.add_match("MESSAGE=Hello Woooooorld!".to_string()) .unwrap(); } #[test] fn add_disjunction() { // add a match for "MESSAGE=Hello Woooooorld!" OR "_TRANSPORT=journal" should // find data (i.e. next() return Done) let journal = open_testdata(); journal.add_match(b"MESSAGE=Hello Woooooorld!").unwrap(); journal.add_disjunction().unwrap(); journal.add_match(b"_TRANSPORT=journal").unwrap(); assert_eq!(journal.next().unwrap(), CursorMovement::Done); } #[test] fn add_conjunction() { // add a match for "MESSAGE=Hello Woooooorld!" AND "_TRANSPORT=journal" should // not find any record (i.e. next() return EoF) let journal = open_testdata(); journal.add_match(b"MESSAGE=Hello Woooooorld!").unwrap(); journal.add_conjunction().unwrap(); journal.add_match(b"_TRANSPORT=journal").unwrap(); assert_eq!(journal.next().unwrap(), CursorMovement::EoF); } #[test] fn flush_matches() { let journal = open_testdata(); journal.add_match(b"MESSAGE=Hello Woooooorld!").unwrap(); assert_eq!(journal.next().unwrap(), CursorMovement::EoF); journal.flush_matches(); assert_eq!(journal.next().unwrap(), CursorMovement::Done); } #[test] fn get_realtime_cutoff() { // get realtime cutoff --> from < to let journal = open_testdata(); let (from, to) = journal.get_realtime_cutoff().unwrap(); println!("{} - {}", from, to); assert!(from < to); } #[test] fn get_monotonic_cutoff() { // get current boot id // get monotonic cutoff --> from < to let journal = Journal::open(FileFlags::AllFiles, UserFlags::AllUsers).unwrap(); let (from, to) = journal.get_monotonic_cutoff(sd_id128::ID128::boot_id().unwrap()) .unwrap(); println!("{} - {}", from, to); assert!(from < to); } #[test] fn set_treshold() { // set the treshold without error let journal = Journal::open(FileFlags::AllFiles, UserFlags::AllUsers).unwrap(); journal.set_data_treshold(5).unwrap(); } #[test] fn get_treshold() { // get the old treshold // set the treshold to 5; assert the new value is 5 // assert the new value of 5 differs from the old value (very unlikely to match) // set the treshold to 0 (unlimited) // assert the value is not 0 let journal = Journal::open(FileFlags::AllFiles, UserFlags::AllUsers).unwrap(); let old = journal.get_data_treshold().unwrap(); println!("old value: {}", old); journal.set_data_treshold(5).unwrap(); let new = journal.get_data_treshold().unwrap(); println!("new value: {}", new); assert!(old != new); assert_eq!(new, 5); journal.set_data_treshold(0).unwrap(); let ulimited = journal.get_data_treshold().unwrap(); println!("ulimited value: {}", ulimited); assert_eq!(ulimited, 0); } #[test] fn enumerate_field_names() { // loop once through all fields an print them assuming no error is raised ever let journal = Journal::open(FileFlags::AllFiles, UserFlags::AllUsers).unwrap(); loop { match journal.enumerate_field_names().unwrap() { Enumeration::EoF => break, Enumeration::Value(string) => println!("{}", string) } } } #[test] fn restart_fields() { // enumerate fields until "MESSAGE" is found // restart // enumerate again until "MESSAGE" is found // any error or "MESSAGE" is not found lead to an error let journal = Journal::open(FileFlags::AllFiles, UserFlags::AllUsers).unwrap(); loop { match journal.enumerate_field_names().unwrap() { Enumeration::EoF => break, Enumeration::Value(string) => { if string == "MESSAGE" { println!("Field 'MESSAGE' found for the first time. Let's restart \ enumeration."); journal.restart_field_name_enumeration(); break; } }, } } loop { match journal.enumerate_field_names().unwrap() { Enumeration::EoF => break, Enumeration::Value(string) => { if string == "MESSAGE" { println!("Field 'MESSAGE' found for the second time. Restart seems to work \ fine."); return; } }, } } panic!("Test failed: Field Message was either not existing at all or restart did not work."); } #[test] fn iter_field_names() { // loop once through all fields an print them assuming no error is raised ever let journal = Journal::open(FileFlags::AllFiles, UserFlags::AllUsers).unwrap(); for fieldname in journal.iter_field_names() { println!("{}", fieldname.unwrap()); } } #[test] fn get_fd() { // TODO: do a more meaningful test: open the fd for reading let journal = Journal::open(FileFlags::AllFiles, UserFlags::AllUsers).unwrap(); journal.get_fd().unwrap(); } #[test] fn get_events() { // TODO: do a more meaningful test: open the fd for reading let journal = Journal::open(FileFlags::AllFiles, UserFlags::AllUsers).unwrap(); journal.get_events().unwrap(); } #[test] fn get_timeout() { // TODO: do a more meaningful test: poll the fd and measure the timeout let journal = Journal::open(FileFlags::AllFiles, UserFlags::AllUsers).unwrap(); journal.get_timeout().unwrap(); } #[test] fn process() { // TODO: do a test at all... calling process without a wait seems to return // random numbers let journal = Journal::open(FileFlags::AllFiles, UserFlags::AllUsers).unwrap(); journal.process().unwrap(); } #[test] fn wait() { // TODO: do a more meaningful test: the journal always returns INVALIDATE??? let journal = Journal::open(FileFlags::AllFiles, UserFlags::AllUsers).unwrap(); journal.seek_tail().unwrap(); journal.wait(10).unwrap(); } #[test] fn has_runtime_files() { let journal = Journal::open(FileFlags::AllFiles, UserFlags::AllUsers).unwrap(); journal.has_runtime_files().unwrap(); } #[test] fn has_persistent_files() { let journal = Journal::open(FileFlags::AllFiles, UserFlags::AllUsers).unwrap(); journal.has_persistent_files().unwrap(); } #[test] fn get_usage() { let journal = Journal::open(FileFlags::AllFiles, UserFlags::AllUsers).unwrap(); println!("usage in bytes: {}", journal.get_usage().unwrap()); assert!(journal.get_usage().unwrap() > 0); } #[test] fn get_realtime() { // get realtime_usec on a postioned journal at head and tail let journal = open_testdata(); journal.next().unwrap(); println!("realtime at journal head: {}", journal.get_realtime().unwrap()); journal.seek_tail().unwrap(); journal.previous().unwrap(); println!("realtime at journal tail: {}", journal.get_realtime().unwrap()); } #[test] fn get_monotonic() { // get realtime_usec on a postioned journal at head and tail let journal = open_testdata(); journal.next().unwrap(); println!("monotonic at journal head: ({}, {})", journal.get_monotonic().unwrap().0, journal.get_monotonic().unwrap().1); journal.seek_tail().unwrap(); journal.previous().unwrap(); println!("monotonic at journal tail: ({}, {})", journal.get_monotonic().unwrap().0, journal.get_monotonic().unwrap().1); } #[test] fn get_cursor_id() { // get cursor and print it let journal = Journal::open(FileFlags::AllFiles, UserFlags::AllUsers).unwrap(); journal.next().unwrap(); println!("{:?}", journal.get_cursor_id().unwrap()); } #[test] fn cursor_id_matches() { // get cursor -> test_cursor matches on same position // next() -> test_cursor does not match anymore let journal = Journal::open(FileFlags::AllFiles, UserFlags::AllUsers).unwrap(); journal.next().unwrap(); let cursor = journal.get_cursor_id().unwrap(); assert_eq!(journal.cursor_id_matches(cursor.clone()).unwrap(), true); journal.next().unwrap(); assert_eq!(journal.cursor_id_matches(cursor.clone()).unwrap(), false); } #[test] fn get_catalog() { // if there is a catalogue entry, there must be a field "MESSAGE_ID" filled // iterate over records until you find "MESSAGE_ID" // get catalog for message // go to next without "MESSAGE_ID" --> get_catalogue returns an error let journal = Journal::open(FileFlags::AllFiles, UserFlags::AllUsers).unwrap(); journal.next().unwrap(); while journal.get_data("MESSAGE_ID").is_err() { match journal.next().unwrap() { CursorMovement::EoF => return, _ => () } } let c = journal.get_catalog().unwrap(); println!("{}", c); while journal.get_data("MESSAGE_ID").is_ok() { match journal.next().unwrap() { CursorMovement::EoF => return, _ => () } } journal.get_catalog().unwrap_err(); } #[test] fn get_data() { // get data for field "MESSAGE" and check the result actually contains // "MESSAGE=" let mut test_data = PathBuf::from(env!("CARGO_MANIFEST_DIR")); test_data.push("test-data/"); println!("looking for test data in folder {}", test_data.display()); let journal = Journal::open_directory(&test_data, PathFlags::FullPath, UserFlags::AllUsers).unwrap(); for cursor in &journal { let cursor = cursor.unwrap(); let message = cursor.get_data("MESSAGE") .unwrap_or("[no message available]".to_string()); let datetime = cursor.get_realtime().unwrap(); println!("{} - {}", datetime, message); } } #[test] fn enumerate_fields() { // loop through all fields of a record and print them let journal = Journal::open(FileFlags::AllFiles, UserFlags::AllUsers).unwrap(); journal.next().unwrap(); while let Ok(Enumeration::Value((field, value))) = journal.enumerate_fields() { println!("{}: {}", field, value) } } #[test] fn enumerate_available_fields() { // loop through all fields of a record and print them let journal = Journal::open(FileFlags::AllFiles, UserFlags::AllUsers).unwrap(); journal.next().unwrap(); while let Ok(Enumeration::Value((field, value))) = journal.enumerate_available_fields() { println!("{}: {}", field, value) } } #[test] fn restart_fields_enumeration() { // loop through all fields of a record => if the field contains MESSAGE=restart // loop a second time through all fields: if field MESSAGE= shows up again ==> // success // test fails on: // - no field MESSAGE ever found (first & second loop exit without match) // - restart_data() fails, i.e. second loop does not find "MESSAGE" once more let journal = open_testdata(); journal.next().unwrap(); while let Ok(Enumeration::Value((field, value))) = journal.enumerate_fields() { println!("{}: {}", field, value); if field == "MESSAGE" { journal.restart_fields_enumeration(); break; } } while let Ok(Enumeration::Value((field, value))) = journal.enumerate_fields() { println!("{}: {}", field, value); if field == "MESSAGE" { return; } } panic!("Test failed: Field MESSAGE either does not exist or restart_data() did not succeed."); } #[test] fn iter_fields() { let journal = open_testdata(); journal.next().unwrap(); // The following 2 loops are synonyms while let Ok(Enumeration::Value((field, value))) = journal.enumerate_fields() { println!("{}: {}", field, value); } for field in journal.iter_fields() { let (field, value) = field.unwrap(); println!("{}: {}", field, value); } } #[test] fn query_unique_values() { // run a query for a field without raising an error let journal = open_testdata(); journal.query_unique_values("MESSAGE").unwrap(); } #[test] fn enumerate_unique_values() { // query MESSAGE field 3 times and assert each result differs let journal = open_testdata(); journal.query_unique_values("MESSAGE").unwrap(); let first = journal.enumerate_unique_values().unwrap(); println!("first: {:?}", first); let second = journal.enumerate_unique_values().unwrap(); println!("second: {:?}", second); assert_eq!(first, Enumeration::Value("Hello World!".to_string())); assert_eq!(second, Enumeration::EoF); } #[test] fn enumerate_available_unique_values() { // query MESSAGE field 3 times and assert each result differs let journal = open_testdata(); journal.query_unique_values("MESSAGE").unwrap(); let first = journal.enumerate_unique_values().unwrap(); println!("first: {:?}", first); let second = journal.enumerate_unique_values().unwrap(); println!("second: {:?}", second); assert_eq!(first, Enumeration::Value("Hello World!".to_string())); assert_eq!(second, Enumeration::EoF); } #[test] fn restart_unique_value_enumeration() { // query _PID field 3 times but restart after the second time // assert 1 and 2 differ // assert 1 and 3 match let journal = open_testdata(); journal.query_unique_values("_PID").unwrap(); let first = journal.enumerate_unique_values().unwrap(); println!("first: {:?}", first); let second = journal.enumerate_unique_values().unwrap(); println!("second: {:?}", second); journal.restart_unique_value_enumeration(); let third = journal.enumerate_unique_values().unwrap(); println!("third: {:?}", third); assert_ne!(first, second); assert_eq!(first, third); } #[test] fn iter_unique_values() { let journal = open_testdata(); for value in journal.iter_unique_values("MESSAGE").unwrap() { let value = value.unwrap(); println!("{}", value); } }