test: cover delete-on-close on the windows mount (#10561)

* test: cover delete-on-close on the windows mount

Windows software creates temporaries with FILE_FLAG_DELETE_ON_CLOSE and
never deletes them explicitly. The conformance suite showed a file
outliving its last handle — an aborted test left its file behind and
every later test hit a name collision — but nothing in this suite asks
for the flag, because os offers no way to.

Skips where the flag is unavailable rather than passing quietly.

* test: fail delete-on-close on a real error instead of skipping

Skipping on any error meant a refused flag looked the same as a platform
that cannot ask for it, so the test could pass by never running. It now
skips only on that one sentinel and reports everything else.

Also stops printing a nil error when the file is still there after its
last handle closed, and checks the closes it was discarding.
This commit is contained in:
Chris Lu
2026-08-04 17:42:30 -07:00
committed by GitHub
parent 312cfe5ae1
commit d01ed36118
4 changed files with 79 additions and 0 deletions
+7
View File
@@ -0,0 +1,7 @@
package winfsp
import "errors"
// errWindowsOnly marks the platforms with no way to ask for delete-on-close,
// so the test can tell that apart from the flag being refused.
var errWindowsOnly = errors.New("delete-on-close is a windows flag")
+9
View File
@@ -0,0 +1,9 @@
//go:build !windows
package winfsp
type handle uintptr
func createDeleteOnClose(path string) (handle, error) { return 0, errWindowsOnly }
func closeHandle(h handle) error { return errWindowsOnly }
+24
View File
@@ -0,0 +1,24 @@
package winfsp
import "golang.org/x/sys/windows"
// createDeleteOnClose opens a new file that Windows removes when the last
// handle to it closes. Installers and editors use this for temporaries, and
// os has no way to ask for it.
func createDeleteOnClose(path string) (windows.Handle, error) {
p, err := windows.UTF16PtrFromString(path)
if err != nil {
return windows.InvalidHandle, err
}
return windows.CreateFile(
p,
windows.GENERIC_READ|windows.GENERIC_WRITE,
windows.FILE_SHARE_READ|windows.FILE_SHARE_WRITE,
nil,
windows.CREATE_NEW,
windows.FILE_ATTRIBUTE_NORMAL|windows.FILE_FLAG_DELETE_ON_CLOSE,
0,
)
}
func closeHandle(h windows.Handle) error { return windows.CloseHandle(h) }
+39
View File
@@ -2,6 +2,7 @@ package winfsp
import (
"bytes"
"errors"
"fmt"
"os"
"path/filepath"
@@ -461,3 +462,41 @@ func TestStatfsIsSelfConsistent(t *testing.T) {
t.Fatalf("total free (%d) exceeds total (%d)", totalFree, total)
}
}
// TestDeleteOnClose covers the pattern Windows software uses for temporaries:
// the file goes away when the last handle closes, with no explicit delete. A
// filesystem that ignores the flag leaves the name behind, and the next
// program to create it gets a collision.
func TestDeleteOnClose(t *testing.T) {
dir := testRoot(t)
path := filepath.Join(dir, "ephemeral.tmp")
h, err := createDeleteOnClose(path)
if err != nil {
if errors.Is(err, errWindowsOnly) {
t.Skip("delete-on-close needs the Win32 create call")
}
t.Fatalf("open with delete-on-close: %v", err)
}
if _, err := os.Stat(path); err != nil {
closeHandle(h)
t.Fatalf("file is not visible while its handle is open: %v", err)
}
if err := closeHandle(h); err != nil {
t.Fatalf("close: %v", err)
}
if _, err := os.Stat(path); err == nil {
t.Fatal("file outlived its last handle")
} else if !os.IsNotExist(err) {
t.Fatalf("stat after close: %v", err)
}
// The name has to be free again, which is what the conformance suite
// tripped over when an aborted test left its file behind.
h, err = createDeleteOnClose(path)
if err != nil {
t.Fatalf("recreating the same name failed: %v", err)
}
if err := closeHandle(h); err != nil {
t.Fatalf("close after recreate: %v", err)
}
}