#![allow(dead_code, unused_imports, clippy::all)]
use apply as test_isolated;
#[rustfmt::skip]
macro_rules! set_doctest_prelude {
(
[$_:tt] $($prelude:tt)*
) => (
$($prelude)*
macro_rules! doctest {(
$_(# $attr:tt)*
$_($_(@$if_async:tt)?
async
)?
fn $fname:ident() {
$_($body:tt)*
}
) => (
$_(#$attr)*
#[doc = stringify! {
$($prelude)*
$_($_($if_async)?
let fut = async
)?
{ $_($body)* }
$_($_($if_async)?
;
::futures::executor::block_on(fut);
)?
}]
mod $fname {}
)}
#[allow(unused_macros)]
macro_rules! doctest_ignore {(
$_(# $attr:tt)*
$_($_(@$if_async:tt)?
async
)?
fn $fname:ident() {
$_($body:tt)*
}
) => (
$_(#$attr)*
#[doc = stringify! {
$($prelude)*
$_($_($if_async)?
let fut = async
)?
{ $_($body)* }
$_($_($if_async)?
;
::futures::executor::block_on(fut);
)?
}]
mod $fname {}
)}
);
(
$($prelude:tt)*
) => (
set_doctest_prelude! { [$] $($prelude)* }
);
}
set_doctest_prelude! {
use dittolive_ditto::{
error::{CoreApiErrorKind, ErrorKind},
prelude::*,
};
fn init_ditto() -> Ditto {
let ditto = Ditto::builder()
.with_temp_dir()
.with_identity(identity::OfflinePlayground::random).unwrap()
.build().unwrap();
ditto
}
fn init_ditto_in(path: &::std::path::Path) -> Ditto {
let ditto = Ditto::builder()
.with_root(::std::sync::Arc::new(PersistentRoot::new(path).unwrap()))
.with_identity(identity::OfflinePlayground::random).unwrap()
.build().unwrap();
ditto
}
fn tempdir() -> ::tempfile::TempDir {
::tempfile::TempDir::new().expect("tempdir() to succeed")
}
fn read_gz_contents(path: impl AsRef<::std::path::Path>) -> ::std::io::Result<String> {
let mut gz = ::flate2::read::GzDecoder::new(::std::fs::File::open(path)?);
let mut s = String::new();
::std::io::Read::read_to_string(&mut gz, &mut s)?;
Ok(s)
}
}
#[test_isolated(doctest)]
async fn export() {
use uuid::Uuid;
let _d = init_ditto();
let tempdir = &tempdir();
let path = &tempdir.path().join("exported-logs.jsonl.gz");
let needle_1 = Uuid::new_v4().to_string();
let needle_2 = Uuid::new_v4().to_string();
DittoLogger::__log_error(needle_1.as_str());
DittoLogger::__log_error(needle_2.as_str());
let _ = DittoLogger::export_to_file(path).await.unwrap();
let data = read_gz_contents(path).unwrap();
let lines = data.lines().collect::<Vec<_>>();
assert!(
lines.len() >= 2,
"exported logs should have contained at least our 2 log lines"
);
assert!(lines.iter().any(|line| line.contains(needle_1.as_str())));
assert!(lines.iter().any(|line| line.contains(needle_2.as_str())));
}
#[test_isolated(doctest_ignore)]
async fn export_writes_empty_gzipped_file_if_no_logs_exist() {
panic!("No practical way to test this at the moment");
}
#[test_isolated(doctest)]
async fn export_writes_all_logs_of_all_ditto_instances_since_the_last_instance_has_been_created() {
use uuid::Uuid;
let blah_a = format!("Blah A {}", Uuid::new_v4());
let _ditto_a = init_ditto();
DittoLogger::__log_error(&blah_a);
let _ditto_b = init_ditto();
let blah_b = format!("Blah B {}", Uuid::new_v4());
DittoLogger::__log_error(&blah_b);
let _ditto_c = init_ditto();
let blah_c = format!("Blah C {}", Uuid::new_v4());
DittoLogger::__log_error(&blah_c);
let blub_a = format!("Blub A {}", Uuid::new_v4());
let blub_b = format!("Blub B {}", Uuid::new_v4());
let blub_c = format!("Blub C {}", Uuid::new_v4());
DittoLogger::__log_error(&blub_a);
DittoLogger::__log_error(&blub_b);
DittoLogger::__log_error(&blub_c);
let tempdir = tempdir();
let path = tempdir.path().join("exported-logs.jsonl.gz");
let number_of_bytes_written = DittoLogger::export_to_file(&path).await.unwrap() as usize;
let raw_data = std::fs::read(&path).unwrap();
assert_eq!(number_of_bytes_written, raw_data.len());
let data = read_gz_contents(&path).unwrap();
assert!(data.contains(&blah_c));
assert!(data.contains(&blub_a));
assert!(data.contains(&blub_b));
assert!(data.contains(&blub_c));
}
#[test_isolated(doctest)]
async fn export_writes_all_logs_of_all_ditto_instances_even_if_the_last_instance_has_been_closed() {
use uuid::Uuid;
let [a, b, c] = &::core::array::from_fn(|_| tempdir());
let ditto_a = init_ditto_in(a.path());
let blah_a = format!("Blah A {}", Uuid::new_v4());
DittoLogger::__log_error(&blah_a);
let ditto_b = init_ditto_in(b.path());
let blah_b = format!("Blah B {}", Uuid::new_v4());
DittoLogger::__log_error(&blah_b);
let ditto_c = init_ditto_in(c.path());
let blah_c = format!("Blah C {}", Uuid::new_v4());
DittoLogger::__log_error(&blah_c);
let blub_c = format!("Blub C {}", Uuid::new_v4());
DittoLogger::__log_error(&blub_c);
ditto_c.close();
let blub_b = format!("Blub B {}", Uuid::new_v4());
DittoLogger::__log_error(&blub_b);
ditto_b.close();
let blub_a = format!("Blub A {}", Uuid::new_v4());
DittoLogger::__log_error(&blub_a);
ditto_a.close();
let tempdir = tempdir();
let path = tempdir.path().join("exported-logs.jsonl.gz");
let number_of_bytes_written = DittoLogger::export_to_file(&path).await.unwrap() as usize;
let raw_data = std::fs::read(&path).unwrap();
assert_eq!(number_of_bytes_written, raw_data.len());
let data = read_gz_contents(path).unwrap();
let log_lines = data.lines().collect::<Vec<_>>();
assert!(data.contains(&blah_c));
assert!(data.contains(&blub_c));
assert!(data.contains(&blub_b));
assert!(data.contains(&blub_a));
}
#[test_isolated(doctest)]
async fn export_throws_io_not_found_when_last_ditto_instance_created_is_closed_again_and_persistence_directory_removed_but_others_exist(
) {
let [a, b] = &::core::array::from_fn(|_| tempdir());
let ditto_a = init_ditto_in(a.path());
DittoLogger::__log_error("Blah A");
let ditto_b = init_ditto_in(b.path());
DittoLogger::__log_error("Blah B");
let ditto_c = init_ditto();
DittoLogger::__log_error("Blah C");
DittoLogger::__log_error("Blub C");
ditto_c.close();
DittoLogger::__log_error("Blub B");
ditto_b.close();
DittoLogger::__log_error("Blub A");
ditto_a.close();
let tempdir = &tempdir();
let path = &tempdir.path().join("exported-logs.jsonl.gz");
let result = DittoLogger::export_to_file(path).await;
assert!(
matches!(
&result,
Err(err) if err.kind() == ErrorKind::CoreApi(CoreApiErrorKind::IoNotFound)
),
"expected `CoreApiErrorKind::IoAlreadyExists`, got {result:?}",
);
}
#[test_isolated(doctest)]
async fn export_throws_io_error_already_exists() {
let _d = init_ditto();
let tempdir = &tempdir();
let path = &tempdir.path().join("exported-logs.jsonl.gz");
DittoLogger::export_to_file(path).await.unwrap();
let result = DittoLogger::export_to_file(path).await;
assert!(
matches!(
&result,
Err(err) if err.kind() == ErrorKind::CoreApi(CoreApiErrorKind::IoAlreadyExists)
),
"expected `CoreApiErrorKind::IoAlreadyExists`, got {result:?}",
);
}
#[test_isolated(doctest)]
async fn export_throws_io_error_not_found() {
let _d = init_ditto();
let tempdir = &tempdir();
let path = &tempdir
.path()
.join("travolta-pulp-fiction")
.join("exported-logs.jsonl.gz");
let result = DittoLogger::export_to_file(path).await;
assert!(
matches!(
&result,
Err(err) if err.kind() == ErrorKind::CoreApi(CoreApiErrorKind::IoNotFound)
),
"expected `CoreApiErrorKind::IoNotFound`, got {result:?}",
);
}
#[cfg(unix)]
#[test_isolated(doctest)]
async fn export_throws_io_error_permission_denied() {
use std::{fs::Permissions, os::unix::fs::PermissionsExt};
let _d = init_ditto();
let tempdir = &tempdir();
::std::fs::set_permissions(tempdir.path(), Permissions::from_mode(0o000))
.expect("chmod to succeed");
let path = &tempdir
.path()
.join("travolta-pulp-fiction")
.join("exported-logs.jsonl.gz");
let result = DittoLogger::export_to_file(path).await;
assert!(
matches!(
&result,
Err(err) if err.kind() == ErrorKind::CoreApi(CoreApiErrorKind::IoPermissionDenied)
),
"expected `CoreApiErrorKind::IoPermissionDenied`, got {result:?}",
);
}
#[test_isolated(doctest)]
async fn export_throws_io_error_operation_failed_if_directory_along_path_is_a_file() {
let _d = init_ditto();
let tempdir = &tempdir();
let path = &tempdir.path().join("exported-logs.jsonl.gz");
DittoLogger::export_to_file(path).await.unwrap();
let result = DittoLogger::export_to_file(&path.join("exported-logs.jsonl.gz")).await;
assert!(
matches!(
&result,
Err(err) if err.kind() == ErrorKind::CoreApi(CoreApiErrorKind::IoOperationFailed)
),
"expected `CoreApiErrorKind::IoOperationFailed`, got {result:?}",
);
}
#[test_isolated(doctest)]
async fn export_works_with_deepish_directory_paths_and_longish_file_names() {
let _d = init_ditto();
let tempdir = &tempdir();
DittoLogger::__log_error("Blah");
DittoLogger::__log_error("Blub");
let mut path = tempdir.path().to_owned();
let long_directory_name = &format!("deep-recursive-directory-{}", "x".repeat(28));
(0..12).for_each(|_| path.push(long_directory_name));
::std::fs::create_dir_all(&path).expect("`mkdir -p` to succeed");
let long_file_name = format!("exported-logs-{}.jsonl.gz", "x".repeat(28));
path.push(long_file_name);
let number_of_bytes_written = DittoLogger::export_to_file(&path).await.unwrap() as usize;
let raw_data = std::fs::read(&path).unwrap();
assert_eq!(number_of_bytes_written, raw_data.len());
let data = read_gz_contents(path).unwrap();
assert!(data.contains("Blah"));
assert!(data.contains("Blub"));
}
#[test_isolated(doctest)]
async fn export_throws_io_error_operation_failed_if_file_name_is_too_long() {
let _d = init_ditto();
let tempdir = &tempdir();
let mut path = tempdir.path().to_owned();
let long_directory_name = &format!("deep-recursive-directory-{}", "x".repeat(28));
(0..12).for_each(|_| path.push(long_directory_name));
::std::fs::create_dir_all(&path).expect("`mkdir -p` to succeed");
let long_file_name = format!("exported-logs-{}.jsonl.gz", "x".repeat(256));
path.push(long_file_name);
let result = DittoLogger::export_to_file(&path).await;
assert!(
matches!(
&result,
Err(err) if err.kind() == ErrorKind::CoreApi(CoreApiErrorKind::IoOperationFailed)
),
"expected `CoreApiErrorKind::IoOperationFailed`, got {result:?}",
);
}
#[test_isolated(doctest)]
async fn export_throws_unknown_error_if_no_ditto_instance_has_been_created_within_process_yet() {
let tempdir = &tempdir();
let result = DittoLogger::export_to_file(&tempdir.path().join("exported-logs.jsonl.gz")).await;
assert!(
result.is_err(),
"expected `CoreApiErrorKind::Unknown`, got {result:?}",
);
}