Test nanosecond mtime precision on file and symlink copies (#22)
* Test nanosecond mtime precision on file and symlink copies Existing preserves-times tests only set whole-second timestamps via os.utime(path, (sec, sec)), so a backend that silently truncated to second resolution would still pass them. The Python backend's nanosecond-precision guarantee (documented in README's Limitations section) was otherwise untested at sub-second granularity. Combined with CI already running the full suite on both ubuntu-latest and macos-latest, this closes the "Test supported behavior on Linux and macOS" item in IMPROVEMENTS.md. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> * Rename nanosecond timestamp tests to cover atime too Copilot review feedback on PR #22: both tests assert st_atime_ns and st_mtime_ns, but the names/docstrings said "mtime" only. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> * Assert symlink entry is found before use in nanosecond test Copilot review feedback on PR #22: if the scandir() loop didn't find the symlink, entry stayed None and copy_direntry() would fail with a less-informative error than an explicit assertion. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> --------- Co-authored-by: Maxim Shulga <maxim.shulga@alludo.com> Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
2 files changed
+57
-1
No files matched your search
+1
-1
@@ -143,7 +143,7 @@ Acceptance criteria:
|
||||
- [x] Add an explicit one-filesystem policy.
|
||||
- [x] Make Python and external implementations provide equivalent guarantees or
|
||||
document every difference.
|
||||
- [ ] Test supported behavior on Linux and macOS.
|
||||
- [x] Test supported behavior on Linux and macOS.
|
||||
|
||||
### Make dry-run genuinely non-mutating
|
||||
|
||||
|
||||
@@ -317,6 +317,27 @@ class TestCopyDirEntry:
|
||||
dst_stat = os.lstat(dst_path)
|
||||
assert dst_stat.st_mode == src_stat.st_mode
|
||||
|
||||
def test_copy_file_preserves_nanosecond_timestamps(self, tmp_path):
|
||||
"""README documents nanosecond mtime precision for the Python
|
||||
backend; whole-second os.utime() calls can't catch a backend
|
||||
that silently truncates to second resolution, so this sets
|
||||
sub-second atime/mtime explicitly."""
|
||||
src_path = os.path.join(str(tmp_path), "source.txt")
|
||||
dst_path = os.path.join(str(tmp_path), "dest.txt")
|
||||
|
||||
with open(src_path, "w") as f:
|
||||
f.write("test content")
|
||||
os.utime(src_path, ns=(1_000_000_000_123456789,
|
||||
2_000_000_000_987654321))
|
||||
|
||||
src_stat = os.lstat(src_path)
|
||||
entry = fs.PseudoDirEntry(src_path)
|
||||
fs.copy_direntry(entry, dst_path)
|
||||
|
||||
dst_stat = os.lstat(dst_path)
|
||||
assert dst_stat.st_mtime_ns == src_stat.st_mtime_ns
|
||||
assert dst_stat.st_atime_ns == src_stat.st_atime_ns
|
||||
|
||||
def test_copy_directory_preserves_times(self, tmp_path):
|
||||
"""Test that directory timestamps are preserved"""
|
||||
src_path = os.path.join(str(tmp_path), "srcdir")
|
||||
@@ -410,6 +431,41 @@ class TestCopyDirEntry:
|
||||
# just verify the symlink was created
|
||||
assert os.path.islink(dst_link)
|
||||
|
||||
def test_copy_symlink_preserves_nanosecond_timestamps_if_supported(
|
||||
self, tmp_path):
|
||||
"""Same nanosecond-precision claim as
|
||||
test_copy_file_preserves_nanosecond_timestamps(), but for the
|
||||
follow_symlinks=False code path in copy_direntry()."""
|
||||
target_path = os.path.join(str(tmp_path), "target.txt")
|
||||
src_link = os.path.join(str(tmp_path), "source_link")
|
||||
dst_link = os.path.join(str(tmp_path), "dest_link")
|
||||
|
||||
with open(target_path, "w") as f:
|
||||
f.write("target")
|
||||
os.symlink(target_path, src_link)
|
||||
|
||||
if os.utime not in os.supports_follow_symlinks:
|
||||
pytest.skip("OS doesn't support setting symlink timestamps")
|
||||
|
||||
os.utime(src_link, ns=(1_000_000_000_123456789,
|
||||
2_000_000_000_987654321),
|
||||
follow_symlinks=False)
|
||||
orig_stat = os.lstat(src_link)
|
||||
|
||||
entry = None
|
||||
with os.scandir(str(tmp_path)) as it:
|
||||
for e in it:
|
||||
if e.name == "source_link":
|
||||
entry = e
|
||||
break
|
||||
|
||||
assert entry is not None, "source_link not found via scandir"
|
||||
fs.copy_direntry(entry, dst_link)
|
||||
|
||||
dst_stat = os.lstat(dst_link)
|
||||
assert dst_stat.st_mtime_ns == orig_stat.st_mtime_ns
|
||||
assert dst_stat.st_atime_ns == orig_stat.st_atime_ns
|
||||
|
||||
def test_copy_file_preserves_ownership_if_root(self, tmp_path):
|
||||
"""Test that file ownership is preserved (requires root)"""
|
||||
if os.geteuid() != 0:
|
||||
|
||||
Reference in new issue
Block a user