refactor: add move in/out tests

This commit is contained in:
CJ van den Berg 2026-03-29 19:44:45 +02:00
parent adf172cea6
commit bfd1125449
Signed by: neurocyte
GPG key ID: 8EB1E1BB660E3FB9
3 changed files with 243 additions and 7 deletions

View file

@ -643,6 +643,146 @@ fn testRenameThenModify(comptime Watcher: type, allocator: std.mem.Allocator) !v
try std.testing.expect(rename_idx < modify_idx);
}
fn testMoveOutFile(comptime Watcher: type, allocator: std.mem.Allocator) !void {
const TH = MakeTestHandler(Watcher);
const watched = try makeTempDir(allocator);
defer {
removeTempDir(watched);
allocator.free(watched);
}
const other = try makeTempDir(allocator);
defer {
removeTempDir(other);
allocator.free(other);
}
const th = try TH.init(allocator);
defer th.deinit();
var watcher = try Watcher.init(allocator, &th.handler);
defer watcher.deinit();
try watcher.watch(watched);
const src_path = try std.fs.path.join(allocator, &.{ watched, "moveme.txt" });
defer allocator.free(src_path);
const dst_path = try std.fs.path.join(allocator, &.{ other, "moveme.txt" });
defer allocator.free(dst_path);
{
const f = try std.fs.createFileAbsolute(src_path, .{});
f.close();
}
try drainEvents(Watcher, &watcher);
try std.fs.renameAbsolute(src_path, dst_path);
try drainEvents(Watcher, &watcher);
// File moved out of the watched tree: appears as deleted (INotify, Windows)
// or renamed (kqueue, which holds a vnode fd and sees NOTE_RENAME).
const src_gone = th.hasChange(src_path, .deleted, .file) or
th.hasChange(src_path, .renamed, .file);
try std.testing.expect(src_gone);
// No event for the destination - it is in an unwatched directory.
try std.testing.expect(!th.hasChange(dst_path, .created, .file));
}
fn testMoveInFile(comptime Watcher: type, allocator: std.mem.Allocator) !void {
const TH = MakeTestHandler(Watcher);
const watched = try makeTempDir(allocator);
defer {
removeTempDir(watched);
allocator.free(watched);
}
const other = try makeTempDir(allocator);
defer {
removeTempDir(other);
allocator.free(other);
}
const th = try TH.init(allocator);
defer th.deinit();
var watcher = try Watcher.init(allocator, &th.handler);
defer watcher.deinit();
try watcher.watch(watched);
const src_path = try std.fs.path.join(allocator, &.{ other, "moveme.txt" });
defer allocator.free(src_path);
const dst_path = try std.fs.path.join(allocator, &.{ watched, "moveme.txt" });
defer allocator.free(dst_path);
{
const f = try std.fs.createFileAbsolute(src_path, .{});
f.close();
}
try std.fs.renameAbsolute(src_path, dst_path);
try drainEvents(Watcher, &watcher);
// File moved into the watched tree: appears as created.
try std.testing.expect(th.hasChange(dst_path, .created, .file));
// No event for the source - it was in an unwatched directory.
try std.testing.expect(!th.hasChange(src_path, .deleted, .file));
}
fn testMoveInSubdir(comptime Watcher: type, allocator: std.mem.Allocator) !void {
const TH = MakeTestHandler(Watcher);
const watched = try makeTempDir(allocator);
defer {
removeTempDir(watched);
allocator.free(watched);
}
const other = try makeTempDir(allocator);
defer {
removeTempDir(other);
allocator.free(other);
}
const th = try TH.init(allocator);
defer th.deinit();
var watcher = try Watcher.init(allocator, &th.handler);
defer watcher.deinit();
try watcher.watch(watched);
const src_sub = try std.fs.path.join(allocator, &.{ other, "sub" });
defer allocator.free(src_sub);
const dst_sub = try std.fs.path.join(allocator, &.{ watched, "sub" });
defer allocator.free(dst_sub);
const src_file = try std.fs.path.join(allocator, &.{ src_sub, "f.txt" });
defer allocator.free(src_file);
const dst_file = try std.fs.path.join(allocator, &.{ dst_sub, "f.txt" });
defer allocator.free(dst_file);
// Create subdir with a file in the unwatched root, then move it in.
try std.fs.makeDirAbsolute(src_sub);
{
const f = try std.fs.createFileAbsolute(src_file, .{});
f.close();
}
try std.fs.renameAbsolute(src_sub, dst_sub);
try drainEvents(Watcher, &watcher);
try std.testing.expect(th.hasChange(dst_sub, .created, .dir));
// Delete the file inside the moved-in subdir.
try std.fs.deleteFileAbsolute(dst_file);
try drainEvents(Watcher, &watcher);
// Object type must be .file, not .unknown - backend must have seeded
// the path_types cache when the subdir was moved in.
try std.testing.expect(th.hasChange(dst_file, .deleted, .file));
// Delete the now-empty subdir.
try std.fs.deleteDirAbsolute(dst_sub);
try drainEvents(Watcher, &watcher);
try std.testing.expect(th.hasChange(dst_sub, .deleted, .dir));
}
// ---------------------------------------------------------------------------
// Test blocks - each runs its case across all available variants.
// ---------------------------------------------------------------------------
@ -724,3 +864,21 @@ test "rename-then-modify: rename event precedes the subsequent modify event" {
try testRenameThenModify(nw.Create(variant), std.testing.allocator);
}
}
test "moving a file out of the watched tree appears as deleted or renamed" {
inline for (comptime std.enums.values(nw.Variant)) |variant| {
try testMoveOutFile(nw.Create(variant), std.testing.allocator);
}
}
test "moving a file into the watched tree appears as created" {
inline for (comptime std.enums.values(nw.Variant)) |variant| {
try testMoveInFile(nw.Create(variant), std.testing.allocator);
}
}
test "moving a subdir into the watched tree: contents can be deleted with correct types" {
inline for (comptime std.enums.values(nw.Variant)) |variant| {
try testMoveInSubdir(nw.Create(variant), std.testing.allocator);
}
}

View file

@ -14,12 +14,14 @@ if (-not (Test-Path $NW)) {
exit 1
}
$TESTDIR = Join-Path $env:TEMP "nightwatch_manual_$PID"
$TESTDIR2 = Join-Path $env:TEMP "nightwatch_manual2_$PID"
New-Item -ItemType Directory -Path $TESTDIR | Out-Null
New-Item -ItemType Directory -Path $TESTDIR2 | Out-Null
$TESTDIR = Join-Path $env:TEMP "nightwatch_manual_$PID"
$TESTDIR2 = Join-Path $env:TEMP "nightwatch_manual2_$PID"
$UNWATCHED = Join-Path $env:TEMP "nightwatch_unwatched_$PID"
New-Item -ItemType Directory -Path $TESTDIR | Out-Null
New-Item -ItemType Directory -Path $TESTDIR2 | Out-Null
New-Item -ItemType Directory -Path $UNWATCHED | Out-Null
Write-Host "--- watching $TESTDIR and $TESTDIR2 ---"
Write-Host "--- watching $TESTDIR and $TESTDIR2 (unwatched: $UNWATCHED) ---"
Write-Host "--- starting nightwatch (Ctrl-C to stop early) ---"
Write-Host ""
@ -104,9 +106,47 @@ Write-Host "[op] rename subA: dir1 -> dir2 (subdir across roots)"
Move-Item -Path "$TESTDIR\subA" -Destination "$TESTDIR2\subA2"
Start-Sleep -Milliseconds 500
Write-Host ""
Write-Host "# move in/out (one side unwatched)"
Write-Host ""
Write-Host "[op] touch outfile.txt in dir1"
New-Item -ItemType File -Path "$TESTDIR\outfile.txt" | Out-Null
Start-Sleep -Milliseconds 400
Write-Host "[op] move outfile.txt: dir1 -> unwatched (move out)"
Move-Item -Path "$TESTDIR\outfile.txt" -Destination "$UNWATCHED\outfile.txt"
Start-Sleep -Milliseconds 400
Write-Host "[op] move outfile.txt: unwatched -> dir1 (move in)"
Move-Item -Path "$UNWATCHED\outfile.txt" -Destination "$TESTDIR\outfile.txt"
Start-Sleep -Milliseconds 400
Write-Host "[op] delete outfile.txt"
Remove-Item -Path "$TESTDIR\outfile.txt"
Start-Sleep -Milliseconds 400
Write-Host "[op] mkdir unwatched\subdir with a file"
New-Item -ItemType Directory -Path "$UNWATCHED\subdir" | Out-Null
New-Item -ItemType File -Path "$UNWATCHED\subdir\inside.txt" | Out-Null
Start-Sleep -Milliseconds 400
Write-Host "[op] move unwatched\subdir -> dir1\subdir (move subdir in)"
Move-Item -Path "$UNWATCHED\subdir" -Destination "$TESTDIR\subdir"
Start-Sleep -Milliseconds 400
Write-Host "[op] delete dir1\subdir\inside.txt"
Remove-Item -Path "$TESTDIR\subdir\inside.txt"
Start-Sleep -Milliseconds 400
Write-Host "[op] rmdir dir1\subdir"
Remove-Item -Path "$TESTDIR\subdir"
Start-Sleep -Milliseconds 500
Write-Host ""
Write-Host "--- done, stopping nightwatch ---"
Stop-Process -Id $proc.Id -ErrorAction SilentlyContinue
$proc.WaitForExit()
Remove-Item -Recurse -Force -Path $TESTDIR
Remove-Item -Recurse -Force -Path $TESTDIR2
Remove-Item -Recurse -Force -Path $UNWATCHED

View file

@ -15,7 +15,8 @@ fi
TESTDIR=$(mktemp -d)
TESTDIR2=$(mktemp -d)
echo "--- watching $TESTDIR and $TESTDIR2 ---"
UNWATCHED=$(mktemp -d)
echo "--- watching $TESTDIR and $TESTDIR2 (unwatched: $UNWATCHED) ---"
echo "--- starting nightwatch (Ctrl-C to stop early) ---"
echo ""
@ -101,8 +102,45 @@ echo "[op] rename subA: dir1 -> dir2 (subdir across roots)"
mv "$TESTDIR/subA" "$TESTDIR2/subA2"
sleep 0.5
echo ""
echo "# move in/out (one side unwatched)"
echo ""
echo "[op] touch outfile.txt in dir1"
touch "$TESTDIR/outfile.txt"
sleep 0.4
echo "[op] move outfile.txt: dir1 -> unwatched (move out)"
mv "$TESTDIR/outfile.txt" "$UNWATCHED/outfile.txt"
sleep 0.4
echo "[op] move outfile.txt: unwatched -> dir1 (move in)"
mv "$UNWATCHED/outfile.txt" "$TESTDIR/outfile.txt"
sleep 0.4
echo "[op] delete outfile.txt"
rm "$TESTDIR/outfile.txt"
sleep 0.4
echo "[op] mkdir unwatched/subdir with a file"
mkdir "$UNWATCHED/subdir"
touch "$UNWATCHED/subdir/inside.txt"
sleep 0.4
echo "[op] move unwatched/subdir -> dir1/subdir (move subdir in)"
mv "$UNWATCHED/subdir" "$TESTDIR/subdir"
sleep 0.4
echo "[op] delete dir1/subdir/inside.txt"
rm "$TESTDIR/subdir/inside.txt"
sleep 0.4
echo "[op] rmdir dir1/subdir"
rmdir "$TESTDIR/subdir"
sleep 0.5
echo ""
echo "--- done, stopping nightwatch ---"
kill $NW_PID 2>/dev/null
wait $NW_PID 2>/dev/null
rm -rf "$TESTDIR" "$TESTDIR2"
rm -rf "$TESTDIR" "$TESTDIR2" "$UNWATCHED"