Skip to content

Commit 7401784

Browse files
committed
💥 --{newer|older}-{c|m}time-{than} options now use strict >/< check
1 parent d7e9b4a commit 7401784

13 files changed

+219
-87
lines changed

cli/src/command/core/time_filter.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,9 @@ use std::{fs, time::SystemTime};
44

55
/// Represents a filter based on time, allowing for inclusion of files newer or older than a specific `SystemTime`.
66
pub(crate) struct TimeFilter {
7-
/// If `Some`, only includes files newer than the specified `SystemTime`.
7+
/// If `Some`, only includes files strictly newer than the specified `SystemTime`.
88
pub(crate) newer_than: Option<SystemTime>,
9-
/// If `Some`, only includes files older than the specified `SystemTime`.
9+
/// If `Some`, only includes files strictly older than the specified `SystemTime`.
1010
pub(crate) older_than: Option<SystemTime>,
1111
}
1212

@@ -33,14 +33,14 @@ impl TimeFilter {
3333
fn is_retain(&self, time: Option<SystemTime>) -> bool {
3434
if let Some(newer) = self.newer_than {
3535
if let Some(t) = time {
36-
if t < newer {
36+
if t <= newer {
3737
return false;
3838
}
3939
}
4040
}
4141
if let Some(older) = self.older_than {
4242
if let Some(t) = time {
43-
if t > older {
43+
if t >= older {
4444
return false;
4545
}
4646
}

cli/tests/cli/append/option_newer_ctime_than.rs

Lines changed: 52 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,15 @@
11
use crate::utils::{archive, setup};
22
use clap::Parser;
33
use portable_network_archive::cli;
4-
use std::{collections::HashSet, fs, thread, time};
4+
use std::{
5+
collections::HashSet,
6+
fs, thread,
7+
time::{Duration, SystemTime},
8+
};
59

610
/// Precondition: Create an archive with an older file, then prepare reference and newer files.
711
/// Action: Run `pna append` with `--newer-ctime-than reference.txt`, specifying older, reference, and newer files.
8-
/// Expectation: Files with ctime >= reference are appended (reference and newer); older is not re-added.
12+
/// Expectation: Files with ctime > reference are appended (newer only); older and reference are not re-added.
913
/// Note: This test requires filesystem support for creation time (birth time).
1014
#[test]
1115
fn append_with_newer_ctime_than() {
@@ -40,16 +44,21 @@ fn append_with_newer_ctime_than() {
4044
.unwrap();
4145

4246
// Wait to ensure distinct ctime
43-
thread::sleep(time::Duration::from_millis(10));
47+
thread::sleep(Duration::from_millis(10));
4448

4549
// Create the reference file
4650
fs::write(reference_file, "reference time marker").unwrap();
4751

4852
// Wait to ensure the next file has a newer ctime
49-
thread::sleep(time::Duration::from_millis(10));
53+
thread::sleep(Duration::from_millis(10));
5054

5155
// Create the newer file
5256
fs::write(newer_file, "newer file content").unwrap();
57+
let reference_ctime = fs::metadata(reference_file).unwrap().created().unwrap();
58+
if !ensure_ctime_order(older_file, newer_file, reference_ctime) {
59+
eprintln!("Skipping test: unable to produce strict ctime ordering on this filesystem");
60+
return;
61+
}
5362

5463
// Append to the archive with the `--newer-ctime-than` option
5564
cli::Cli::try_parse_from([
@@ -81,23 +90,55 @@ fn append_with_newer_ctime_than() {
8190
"older file should be in archive from initial creation: {older_file}"
8291
);
8392

84-
// reference_file should be included (appended because ctime >= reference)
93+
// reference_file should NOT be included (ctime == reference)
8594
assert!(
86-
seen.contains(reference_file),
87-
"reference file should be appended: {reference_file}"
95+
!seen.contains(reference_file),
96+
"reference file should NOT be appended: {reference_file}"
8897
);
8998

90-
// newer_file should be included (appended because ctime >= reference)
99+
// newer_file should be included (appended because ctime > reference)
91100
assert!(
92101
seen.contains(newer_file),
93102
"newer file should be appended: {newer_file}"
94103
);
95104

96-
// Verify that exactly three entries exist
105+
// Verify that exactly two entries exist
97106
assert_eq!(
98107
seen.len(),
99-
3,
100-
"Expected exactly 3 entries, but found {}: {seen:?}",
108+
2,
109+
"Expected exactly 2 entries, but found {}: {seen:?}",
101110
seen.len()
102111
);
103112
}
113+
114+
fn ensure_ctime_order(older: &str, newer: &str, reference: SystemTime) -> bool {
115+
if !confirm_ctime_older_than(older, reference) {
116+
return false;
117+
}
118+
wait_until_ctime_newer_than(newer, reference)
119+
}
120+
121+
fn wait_until_ctime_newer_than(path: &str, baseline: SystemTime) -> bool {
122+
const MAX_ATTEMPTS: usize = 500;
123+
const SLEEP_MS: u64 = 10;
124+
for _ in 0..MAX_ATTEMPTS {
125+
if fs::metadata(path)
126+
.ok()
127+
.and_then(|meta| meta.created().ok())
128+
.map(|ctime| ctime > baseline)
129+
.unwrap_or(false)
130+
{
131+
return true;
132+
}
133+
thread::sleep(Duration::from_millis(SLEEP_MS));
134+
}
135+
false
136+
}
137+
138+
fn confirm_ctime_older_than(path: &str, baseline: SystemTime) -> bool {
139+
fs::metadata(path)
140+
.ok()
141+
.and_then(|meta| meta.created().ok())
142+
.map(|ctime| ctime < baseline)
143+
.unwrap_or(false)
144+
}

cli/tests/cli/append/option_newer_mtime_than.rs

Lines changed: 52 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,15 @@
11
use crate::utils::{archive, setup};
22
use clap::Parser;
33
use portable_network_archive::cli;
4-
use std::{collections::HashSet, fs, thread, time};
4+
use std::{
5+
collections::HashSet,
6+
fs, thread,
7+
time::{Duration, SystemTime},
8+
};
59

610
/// Precondition: Create an archive with an older file, then prepare reference and newer files.
711
/// Action: Run `pna append` with `--newer-mtime-than reference.txt`, specifying older, reference, and newer files.
8-
/// Expectation: Files with mtime >= reference are appended (reference and newer); older is not re-added.
12+
/// Expectation: Files with mtime > reference are appended (newer only); older and reference are not re-added.
913
#[test]
1014
fn append_with_newer_mtime_than() {
1115
setup();
@@ -33,16 +37,21 @@ fn append_with_newer_mtime_than() {
3337
.unwrap();
3438

3539
// Wait to ensure distinct mtime
36-
thread::sleep(time::Duration::from_millis(10));
40+
thread::sleep(Duration::from_millis(10));
3741

3842
// Create the reference file
3943
fs::write(reference_file, "reference time marker").unwrap();
4044

4145
// Wait to ensure the next file has a newer mtime
42-
thread::sleep(time::Duration::from_millis(10));
46+
thread::sleep(Duration::from_millis(10));
4347

4448
// Create the newer file
4549
fs::write(newer_file, "newer file content").unwrap();
50+
let reference_mtime = fs::metadata(reference_file).unwrap().modified().unwrap();
51+
if !ensure_mtime_order(older_file, newer_file, reference_mtime) {
52+
eprintln!("Skipping test: unable to produce strict mtime ordering on this filesystem");
53+
return;
54+
}
4655

4756
// Append to the archive with the `--newer-mtime-than` option
4857
cli::Cli::try_parse_from([
@@ -74,23 +83,55 @@ fn append_with_newer_mtime_than() {
7483
"older file should be in archive from initial creation: {older_file}"
7584
);
7685

77-
// reference_file should be included (appended because mtime >= reference)
86+
// reference_file should NOT be included (mtime == reference)
7887
assert!(
79-
seen.contains(reference_file),
80-
"reference file should be appended: {reference_file}"
88+
!seen.contains(reference_file),
89+
"reference file should NOT be appended: {reference_file}"
8190
);
8291

83-
// newer_file should be included (appended because mtime >= reference)
92+
// newer_file should be included (appended because mtime > reference)
8493
assert!(
8594
seen.contains(newer_file),
8695
"newer file should be appended: {newer_file}"
8796
);
8897

89-
// Verify that exactly three entries exist
98+
// Verify that exactly two entries exist
9099
assert_eq!(
91100
seen.len(),
92-
3,
93-
"Expected exactly 3 entries, but found {}: {seen:?}",
101+
2,
102+
"Expected exactly 2 entries, but found {}: {seen:?}",
94103
seen.len()
95104
);
96105
}
106+
107+
fn ensure_mtime_order(older: &str, newer: &str, reference: SystemTime) -> bool {
108+
if !confirm_mtime_older_than(older, reference) {
109+
return false;
110+
}
111+
wait_until_mtime_newer_than(newer, reference)
112+
}
113+
114+
fn wait_until_mtime_newer_than(path: &str, baseline: SystemTime) -> bool {
115+
const MAX_ATTEMPTS: usize = 500;
116+
const SLEEP_MS: u64 = 10;
117+
for _ in 0..MAX_ATTEMPTS {
118+
if fs::metadata(path)
119+
.ok()
120+
.and_then(|meta| meta.modified().ok())
121+
.map(|mtime| mtime > baseline)
122+
.unwrap_or(false)
123+
{
124+
return true;
125+
}
126+
thread::sleep(Duration::from_millis(SLEEP_MS));
127+
}
128+
false
129+
}
130+
131+
fn confirm_mtime_older_than(path: &str, baseline: SystemTime) -> bool {
132+
fs::metadata(path)
133+
.ok()
134+
.and_then(|meta| meta.modified().ok())
135+
.map(|mtime| mtime < baseline)
136+
.unwrap_or(false)
137+
}

cli/tests/cli/append/option_older_ctime_than.rs

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ use std::{
1010
/// Precondition: Archive already contains `older.txt`. Prepare `reference.txt` and `newer.txt` with
1111
/// strictly ordered creation times (older < reference < newer).
1212
/// Action: Run `pna append` with `--older-ctime-than reference.txt`, appending both candidate files.
13-
/// Expectation: Only files whose ctime <= reference (i.e., reference itself) are appended; `newer` is skipped.
13+
/// Expectation: Only files whose ctime < reference are appended; `reference` and `newer` are skipped.
1414
/// Note: Requires filesystem support for birth time.
1515
#[test]
1616
fn append_with_older_ctime_than() {
@@ -49,7 +49,7 @@ fn append_with_older_ctime_than() {
4949
thread::sleep(Duration::from_millis(10));
5050
fs::write(&newer_file, "newer content").unwrap();
5151

52-
if !confirm_ctime_not_newer_than(&older_file, reference_ctime)
52+
if !confirm_ctime_older_than(&older_file, reference_ctime)
5353
|| !wait_until_ctime_newer_than(&newer_file, reference_ctime)
5454
{
5555
eprintln!(
@@ -84,17 +84,17 @@ fn append_with_older_ctime_than() {
8484
"older file should remain from initial archive: {older_file}"
8585
);
8686
assert!(
87-
seen.contains(&reference_file),
88-
"reference file should be appended: {reference_file}"
87+
!seen.contains(&reference_file),
88+
"reference file should NOT be appended: {reference_file}"
8989
);
9090
assert!(
9191
!seen.contains(&newer_file),
9292
"newer file should NOT be appended: {newer_file}"
9393
);
9494
assert_eq!(
9595
seen.len(),
96-
2,
97-
"Expected exactly 2 entries (older + reference) but found {}: {seen:?}",
96+
1,
97+
"Expected exactly 1 entry (older only) but found {}: {seen:?}",
9898
seen.len()
9999
);
100100
}
@@ -116,10 +116,10 @@ fn wait_until_ctime_newer_than(path: &str, baseline: SystemTime) -> bool {
116116
false
117117
}
118118

119-
fn confirm_ctime_not_newer_than(path: &str, baseline: SystemTime) -> bool {
119+
fn confirm_ctime_older_than(path: &str, baseline: SystemTime) -> bool {
120120
fs::metadata(path)
121121
.ok()
122122
.and_then(|meta| meta.created().ok())
123-
.map(|ctime| ctime <= baseline)
123+
.map(|ctime| ctime < baseline)
124124
.unwrap_or(false)
125125
}

cli/tests/cli/append/option_older_mtime_than.rs

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ use std::{
1010
/// Precondition: Archive contains `older.txt`. Prepare `reference.txt` and `newer.txt` with strictly
1111
/// increasing modification times.
1212
/// Action: Run `pna append` with `--older-mtime-than reference.txt`, appending both candidates.
13-
/// Expectation: Only files whose mtime <= reference (reference itself) are appended; `newer` is skipped.
13+
/// Expectation: Only files whose mtime < reference are appended; `reference` and `newer` are skipped.
1414
#[test]
1515
fn append_with_older_mtime_than() {
1616
setup();
@@ -42,7 +42,7 @@ fn append_with_older_mtime_than() {
4242
thread::sleep(Duration::from_millis(10));
4343
fs::write(&newer_file, "newer mtime content").unwrap();
4444

45-
if !confirm_mtime_not_newer_than(&older_file, reference_mtime)
45+
if !confirm_mtime_older_than(&older_file, reference_mtime)
4646
|| !wait_until_mtime_newer_than(&newer_file, reference_mtime)
4747
{
4848
eprintln!(
@@ -77,17 +77,17 @@ fn append_with_older_mtime_than() {
7777
"older file should remain from initial archive: {older_file}"
7878
);
7979
assert!(
80-
seen.contains(&reference_file),
81-
"reference file should be appended: {reference_file}"
80+
!seen.contains(&reference_file),
81+
"reference file should NOT be appended: {reference_file}"
8282
);
8383
assert!(
8484
!seen.contains(&newer_file),
8585
"newer file should NOT be appended: {newer_file}"
8686
);
8787
assert_eq!(
8888
seen.len(),
89-
2,
90-
"Expected exactly 2 entries (older + reference) but found {}: {seen:?}",
89+
1,
90+
"Expected exactly 1 entry (older only) but found {}: {seen:?}",
9191
seen.len()
9292
);
9393
}
@@ -109,10 +109,10 @@ fn wait_until_mtime_newer_than(path: &str, baseline: SystemTime) -> bool {
109109
false
110110
}
111111

112-
fn confirm_mtime_not_newer_than(path: &str, baseline: SystemTime) -> bool {
112+
fn confirm_mtime_older_than(path: &str, baseline: SystemTime) -> bool {
113113
fs::metadata(path)
114114
.ok()
115115
.and_then(|meta| meta.modified().ok())
116-
.map(|mtime| mtime <= baseline)
116+
.map(|mtime| mtime < baseline)
117117
.unwrap_or(false)
118118
}

0 commit comments

Comments
 (0)