use slog::{o, Discard, Logger};
use std::{fs, time::SystemTime};
use tempfile::TempDir;
async fn run_temporal_test(
tmp_dir: &TempDir,
expr: &str,
expected_files: Vec<&str>,
not_expected: Vec<&str>,
) {
let mut found = Vec::new();
detect::parse_and_run_fs(
Logger::root(Discard, o!()),
tmp_dir.path(),
false,
expr.to_owned(),
detect::RuntimeConfig::default(),
|p| found.push(p.file_name().unwrap().to_string_lossy().to_string()),
)
.await
.unwrap();
for file in expected_files {
assert!(
found.contains(&file.to_string()),
"Expression '{}' should find '{}', but found: {:?}",
expr,
file,
found
);
}
for file in not_expected {
assert!(
!found.contains(&file.to_string()),
"Expression '{}' should not find '{}', but found: {:?}",
expr,
file,
found
);
}
}
#[tokio::test]
async fn test_relative_time_operations() {
let tmp_dir = tempfile::Builder::new()
.prefix("detect-temporal-relative")
.tempdir()
.unwrap();
let files = vec![
("1sec.txt", 1),
("10secs.txt", 10),
("5mins.txt", 5 * 60),
("2hours.txt", 2 * 60 * 60),
("3days.txt", 3 * 24 * 60 * 60),
("1week.txt", 7 * 24 * 60 * 60 - 1), ("30days.txt", 30 * 24 * 60 * 60),
];
for (name, age_secs) in &files {
let path = tmp_dir.path().join(name);
std::fs::write(&path, "content").unwrap();
let mtime = SystemTime::now() - std::time::Duration::from_secs(*age_secs);
fs::File::open(&path).unwrap().set_modified(mtime).unwrap();
}
let test_cases = vec![
(
"modified > \"-2.seconds\"",
vec!["1sec.txt"],
vec!["10secs.txt"],
),
(
"modified > \"-30.seconds\"",
vec!["1sec.txt", "10secs.txt"],
vec!["5mins.txt"],
),
(
"modified > \"-10.minutes\"",
vec!["1sec.txt", "10secs.txt", "5mins.txt"],
vec!["2hours.txt"],
),
(
"modified > \"-1.minute\"",
vec!["1sec.txt", "10secs.txt"],
vec!["5mins.txt"],
),
(
"modified > \"-3.hours\"",
vec!["1sec.txt", "10secs.txt", "5mins.txt", "2hours.txt"],
vec!["3days.txt"],
),
(
"modified > \"-1.hour\"",
vec!["1sec.txt", "10secs.txt", "5mins.txt"],
vec!["2hours.txt"],
),
(
"modified > \"-5.days\"",
vec![
"1sec.txt",
"10secs.txt",
"5mins.txt",
"2hours.txt",
"3days.txt",
],
vec!["1week.txt"],
),
(
"modified > \"-7.days\"",
vec![
"1sec.txt",
"10secs.txt",
"5mins.txt",
"2hours.txt",
"3days.txt",
"1week.txt",
],
vec!["30days.txt"],
),
(
"modified > \"-2.weeks\"",
vec![
"1sec.txt",
"10secs.txt",
"5mins.txt",
"2hours.txt",
"3days.txt",
"1week.txt",
],
vec!["30days.txt"],
),
(
"modified > -30s",
vec!["1sec.txt", "10secs.txt"],
vec!["5mins.txt"],
),
(
"modified > -10m",
vec!["1sec.txt", "10secs.txt", "5mins.txt"],
vec!["2hours.txt"],
),
(
"modified > -3h",
vec!["1sec.txt", "10secs.txt", "5mins.txt", "2hours.txt"],
vec!["3days.txt"],
),
(
"modified > -5d",
vec![
"1sec.txt",
"10secs.txt",
"5mins.txt",
"2hours.txt",
"3days.txt",
],
vec!["1week.txt"],
),
(
"modified > -2w",
vec![
"1sec.txt",
"10secs.txt",
"5mins.txt",
"2hours.txt",
"3days.txt",
"1week.txt",
],
vec!["30days.txt"],
),
];
for (expr, expected, not_expected) in test_cases {
run_temporal_test(&tmp_dir, expr, expected, not_expected).await;
}
}
#[tokio::test]
async fn test_absolute_dates() {
let tmp_dir = tempfile::Builder::new()
.prefix("detect-temporal-absolute")
.tempdir()
.unwrap();
let today_file = tmp_dir.path().join("today.txt");
let yesterday_file = tmp_dir.path().join("yesterday.txt");
let week_old_file = tmp_dir.path().join("week_old.txt");
let year_2020_file = tmp_dir.path().join("year_2020.txt");
let year_2023_file = tmp_dir.path().join("year_2023.txt");
std::fs::write(&today_file, "today").unwrap();
std::fs::write(&yesterday_file, "yesterday").unwrap();
std::fs::write(&week_old_file, "week").unwrap();
std::fs::write(&year_2020_file, "2020").unwrap();
std::fs::write(&year_2023_file, "2023").unwrap();
let now = SystemTime::now();
let yesterday = now - std::time::Duration::from_secs(24 * 60 * 60);
let week_ago = now - std::time::Duration::from_secs(7 * 24 * 60 * 60);
let year_2020 = SystemTime::UNIX_EPOCH + std::time::Duration::from_secs(1577836800); let year_2023 = SystemTime::UNIX_EPOCH + std::time::Duration::from_secs(1672531200);
fs::File::open(&today_file)
.unwrap()
.set_modified(now)
.unwrap();
fs::File::open(&yesterday_file)
.unwrap()
.set_modified(yesterday)
.unwrap();
fs::File::open(&week_old_file)
.unwrap()
.set_modified(week_ago)
.unwrap();
fs::File::open(&year_2020_file)
.unwrap()
.set_modified(year_2020)
.unwrap();
fs::File::open(&year_2023_file)
.unwrap()
.set_modified(year_2023)
.unwrap();
run_temporal_test(
&tmp_dir,
"modified > \"2021-01-01\"",
vec![
"today.txt",
"yesterday.txt",
"week_old.txt",
"year_2023.txt",
],
vec!["year_2020.txt"],
)
.await;
run_temporal_test(
&tmp_dir,
"modified > 2021-01-01",
vec![
"today.txt",
"yesterday.txt",
"week_old.txt",
"year_2023.txt",
],
vec!["year_2020.txt"],
)
.await;
run_temporal_test(
&tmp_dir,
"modified < 2022-01-01",
vec!["year_2020.txt"],
vec!["year_2023.txt", "today.txt"],
)
.await;
}
#[tokio::test]
async fn test_time_selectors() {
let tmp_dir = tempfile::Builder::new()
.prefix("detect-temporal-selectors")
.tempdir()
.unwrap();
let test_file = tmp_dir.path().join("test.txt");
let old_file = tmp_dir.path().join("old.txt");
std::fs::write(&test_file, "content").unwrap();
std::fs::write(&old_file, "old").unwrap();
let week_ago = SystemTime::now() - std::time::Duration::from_secs(7 * 24 * 60 * 60);
fs::File::open(&old_file)
.unwrap()
.set_modified(week_ago)
.unwrap();
run_temporal_test(
&tmp_dir,
"modified > -1hour",
vec!["test.txt"],
vec!["old.txt"],
)
.await;
run_temporal_test(
&tmp_dir,
"modified > -1hour",
vec!["test.txt"],
vec!["old.txt"],
)
.await;
let mut created_files = Vec::new();
detect::parse_and_run_fs(
Logger::root(Discard, o!()),
tmp_dir.path(),
false,
"created > -1hour".to_owned(),
detect::RuntimeConfig::default(),
|p| created_files.push(p.file_name().unwrap().to_string_lossy().to_string()),
)
.await
.unwrap();
let _ = std::fs::read_to_string(&test_file).unwrap();
run_temporal_test(&tmp_dir, "accessed > -1minute", vec!["test.txt"], vec![]).await;
run_temporal_test(&tmp_dir, "accessed > -1minute", vec!["test.txt"], vec![]).await;
let mut created_files = Vec::new();
detect::parse_and_run_fs(
Logger::root(Discard, o!()),
tmp_dir.path(),
false,
"created > -1hour".to_owned(),
detect::RuntimeConfig::default(),
|p| created_files.push(p.file_name().unwrap().to_string_lossy().to_string()),
)
.await
.unwrap();
}
#[tokio::test]
async fn test_temporal_combined_queries() {
let tmp_dir = tempfile::Builder::new()
.prefix("detect-temporal-combined")
.tempdir()
.unwrap();
let files = vec![
("old.rs", "rust code", 10 * 24 * 60 * 60), ("new.rs", "rust code", 60), ("old.txt", "text content", 10 * 24 * 60 * 60), ("new.txt", "text content", 60), ("old_todo.rs", "// TODO: fix", 10 * 24 * 60 * 60), ("new_todo.rs", "// TODO: implement", 60), ];
for (name, content, age_secs) in &files {
let path = tmp_dir.path().join(name);
std::fs::write(&path, content).unwrap();
let mtime = SystemTime::now() - std::time::Duration::from_secs(*age_secs);
fs::File::open(&path).unwrap().set_modified(mtime).unwrap();
}
run_temporal_test(
&tmp_dir,
"ext == rs && modified > -1day",
vec!["new.rs", "new_todo.rs"],
vec!["old.rs", "new.txt", "old.txt", "old_todo.rs"],
)
.await;
run_temporal_test(
&tmp_dir,
r#"content contains "TODO" && modified > -1day"#,
vec!["new_todo.rs"],
vec!["old_todo.rs", "new.rs", "new.txt"],
)
.await;
run_temporal_test(
&tmp_dir,
"modified > -1hour && accessed > -1hour",
vec!["new.rs", "new.txt", "new_todo.rs"],
vec!["old.rs", "old.txt", "old_todo.rs"],
)
.await;
run_temporal_test(
&tmp_dir,
"size < 100 && modified > -1day",
vec!["new.rs", "new.txt", "new_todo.rs"],
vec!["old.rs", "old.txt", "old_todo.rs"],
)
.await;
run_temporal_test(
&tmp_dir,
r#"!(basename contains "old") && modified > -1day"#,
vec!["new.rs", "new.txt", "new_todo.rs"],
vec!["old.rs", "old.txt", "old_todo.rs"],
)
.await;
}
#[tokio::test]
async fn test_greater_less_or_equal_operators() {
let tmp_dir = tempfile::Builder::new()
.prefix("detect-temporal-gte-lte")
.tempdir()
.unwrap();
let now = SystemTime::now();
let thirty_mins_ago = now - std::time::Duration::from_secs(30 * 60);
let ninety_mins_ago = now - std::time::Duration::from_secs(90 * 60);
let two_and_half_hours_ago = now - std::time::Duration::from_secs(150 * 60);
let four_hours_ago = now - std::time::Duration::from_secs(4 * 60 * 60);
let recent = tmp_dir.path().join("recent.txt");
let thirty_mins = tmp_dir.path().join("thirty_mins.txt");
let ninety_mins = tmp_dir.path().join("ninety_mins.txt");
let two_half_hours = tmp_dir.path().join("two_half_hours.txt");
let four_hours = tmp_dir.path().join("four_hours.txt");
std::fs::write(&recent, "recent").unwrap();
std::fs::write(&thirty_mins, "thirty_mins").unwrap();
std::fs::write(&ninety_mins, "ninety_mins").unwrap();
std::fs::write(&two_half_hours, "two_half_hours").unwrap();
std::fs::write(&four_hours, "four_hours").unwrap();
fs::File::open(&recent).unwrap().set_modified(now).unwrap();
fs::File::open(&thirty_mins)
.unwrap()
.set_modified(thirty_mins_ago)
.unwrap();
fs::File::open(&ninety_mins)
.unwrap()
.set_modified(ninety_mins_ago)
.unwrap();
fs::File::open(&two_half_hours)
.unwrap()
.set_modified(two_and_half_hours_ago)
.unwrap();
fs::File::open(&four_hours)
.unwrap()
.set_modified(four_hours_ago)
.unwrap();
run_temporal_test(
&tmp_dir,
"modified >= -1hour",
vec!["recent.txt", "thirty_mins.txt"],
vec!["ninety_mins.txt", "two_half_hours.txt", "four_hours.txt"],
)
.await;
run_temporal_test(
&tmp_dir,
"modified >= -2hours",
vec!["recent.txt", "thirty_mins.txt", "ninety_mins.txt"],
vec!["two_half_hours.txt", "four_hours.txt"],
)
.await;
run_temporal_test(
&tmp_dir,
"modified <= -1hour",
vec!["ninety_mins.txt", "two_half_hours.txt", "four_hours.txt"],
vec!["recent.txt", "thirty_mins.txt"],
)
.await;
run_temporal_test(
&tmp_dir,
"modified <= -3hours",
vec!["four_hours.txt"],
vec![
"recent.txt",
"thirty_mins.txt",
"ninety_mins.txt",
"two_half_hours.txt",
],
)
.await;
run_temporal_test(
&tmp_dir,
"modified >= -3hours AND modified <= -1hour",
vec!["ninety_mins.txt", "two_half_hours.txt"],
vec!["recent.txt", "thirty_mins.txt", "four_hours.txt"],
)
.await;
}