test(windows): probe readonly files with no other attributes

This commit is contained in:
morluto committed 2026-10-15 08:48:44 +08:00
1 parent 84b35d8e7f
commit 0b46b37d37
2 files changed
+17 -3

No files matched your search

+3
View File
@@ -473,6 +473,9 @@ static void dispose(HANDLE handle, const std::wstring& path) {
require(GetFileInformationByHandleEx(handle, FileBasicInfo, &info, sizeof(info)), "Read cleanup attributes failed", path);
if ((info.FileAttributes & FILE_ATTRIBUTE_READONLY) != 0) {
info.FileAttributes &= ~FILE_ATTRIBUTE_READONLY;
// Zero means "leave attributes unchanged" to FileBasicInfo. A file whose
// sole attribute was READONLY needs NORMAL to actually clear that marker.
if (info.FileAttributes == 0) info.FileAttributes = FILE_ATTRIBUTE_NORMAL;
require(SetFileInformationByHandle(handle, FileBasicInfo, &info, sizeof(info)), "Clear owned readonly attribute failed", path);
}
FILE_DISPOSITION_INFO deletion{TRUE};
@@ -30,7 +30,7 @@ const attributes = (path, readonly) => {
const script = `$ErrorActionPreference='Stop';
$path=$env:REA_OWNED_ATTRIBUTE_PROBE;
$current=[int][IO.File]::GetAttributes($path);
$next=if($env:REA_ATTRIBUTE_PROBE_MODE -eq 'set'){ $current -bor 1 }else{ $current -band (-bnot 1) };
$next=if($env:REA_ATTRIBUTE_PROBE_MODE -eq 'only'){1}elseif($env:REA_ATTRIBUTE_PROBE_MODE -eq 'set'){ $current -bor 1 }else{ $current -band (-bnot 1) };
[IO.File]::SetAttributes($path,[IO.FileAttributes]$next);
$observed=[int][IO.File]::GetAttributes($path);
@{readonly=(($observed -band 1) -ne 0); attributes=$observed} | ConvertTo-Json -Compress`;
@@ -45,12 +45,12 @@ $observed=[int][IO.File]::GetAttributes($path);
env: {
...process.env,
REA_OWNED_ATTRIBUTE_PROBE: path,
REA_ATTRIBUTE_PROBE_MODE: readonly ? "set" : "clear",
REA_ATTRIBUTE_PROBE_MODE: readonly === "only" ? "only" : readonly ? "set" : "clear",
},
},
),
);
assert.equal(result.readonly, readonly);
assert.equal(result.readonly, Boolean(readonly));
return result;
};
const create = () => {
@@ -139,6 +139,16 @@ try {
}
close(children);
await assert.rejects(access(children.currentPath), { code: "ENOENT" });
const onlyReadonly = create();
native.call("runtime_write", [onlyReadonly.runtime.handle, "only-readonly.txt", Buffer.from("owned file")]);
const onlyReadonlyPath = join(onlyReadonly.currentPath, "only-readonly.txt");
assert.equal(attributes(onlyReadonlyPath, "only").attributes, 1);
if (expected === "deny-clear") {
assert.throws(() => close(onlyReadonly), error => error.win32Code === 5 && error.constraint === "Delete owned object by handle failed");
attributes(onlyReadonlyPath, false);
}
close(onlyReadonly);
await assert.rejects(access(onlyReadonly.currentPath), { code: "ENOENT" });
console.log(
JSON.stringify({
ok: true,
@@ -149,6 +159,7 @@ try {
renamedOwnedRootRemoved: true,
unrelatedReplacementPreserved: true,
readonlyChildFileAndDirectoryRemoved: true,
readonlyFileWithoutOtherAttributesRemoved: true,
baselineConstraint: failure?.constraint ?? null,
baselineWin32: failure?.win32Code ?? null,
originalIssueDeleteDenialReproduced: false,