Source file src/os/newfile_windows_test.go

     1  // Copyright 2026 The Go Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  package os_test
     6  
     7  import (
     8  	"errors"
     9  	"internal/poll"
    10  	"internal/syscall/windows"
    11  	"io"
    12  	"os"
    13  	"path/filepath"
    14  	"sync"
    15  	"syscall"
    16  	"testing"
    17  	"time"
    18  )
    19  
    20  func newLazyFile(t testing.TB, overlapped bool) (*os.File, syscall.Handle) {
    21  	t.Helper()
    22  	name := filepath.Join(t.TempDir(), "file")
    23  	if err := os.WriteFile(name, []byte("hello"), 0600); err != nil {
    24  		t.Fatal(err)
    25  	}
    26  	flags := syscall.O_RDWR
    27  	if overlapped {
    28  		flags |= windows.O_FILE_FLAG_OVERLAPPED
    29  	}
    30  	h := openHandle(t, name, flags)
    31  	f := os.NewFile(uintptr(h), name)
    32  	t.Cleanup(func() {
    33  		if err := f.Close(); err != nil && !errors.Is(err, os.ErrClosed) {
    34  			t.Error(err)
    35  		}
    36  	})
    37  	return f, h
    38  }
    39  
    40  func openHandle(t testing.TB, name string, flags int) syscall.Handle {
    41  	t.Helper()
    42  	h, err := syscall.Open(name, flags|syscall.O_CLOEXEC, 0600)
    43  	if err != nil {
    44  		t.Fatal(err)
    45  	}
    46  	return h
    47  }
    48  
    49  func TestNewFileLazyInit(t *testing.T) {
    50  	t.Parallel()
    51  	for _, mode := range []string{"sync", "overlapped"} {
    52  		t.Run(mode, func(t *testing.T) {
    53  			for _, first := range []string{"read", "seek", "deadline"} {
    54  				t.Run(first, func(t *testing.T) {
    55  					t.Parallel()
    56  					f, _ := newLazyFile(t, mode == "overlapped")
    57  					var buf [5]byte
    58  					switch first {
    59  					case "read":
    60  						if n, err := f.Read(buf[:]); err != nil || n != len(buf) || string(buf[:]) != "hello" {
    61  							t.Fatalf("Read = %q, %d, %v", buf, n, err)
    62  						}
    63  					case "seek":
    64  						if off, err := f.Seek(1, io.SeekStart); err != nil || off != 1 {
    65  							t.Fatalf("Seek = %d, %v", off, err)
    66  						}
    67  						if n, err := f.Read(buf[:1]); err != nil || n != 1 || buf[0] != 'e' {
    68  							t.Fatalf("Read after Seek = %q, %d, %v", buf[:1], n, err)
    69  						}
    70  					case "deadline":
    71  						err := f.SetReadDeadline(time.Now().Add(-time.Second))
    72  						if mode == "sync" {
    73  							if !errors.Is(err, os.ErrNoDeadline) {
    74  								t.Fatalf("SetReadDeadline = %v; want ErrNoDeadline", err)
    75  							}
    76  						} else {
    77  							if err != nil {
    78  								t.Fatal(err)
    79  							}
    80  							if _, err := f.Read(buf[:1]); !errors.Is(err, os.ErrDeadlineExceeded) {
    81  								t.Fatalf("Read = %v; want ErrDeadlineExceeded", err)
    82  							}
    83  						}
    84  					}
    85  				})
    86  			}
    87  		})
    88  	}
    89  }
    90  
    91  func TestNewFileBlockedHandle(t *testing.T) {
    92  	t.Parallel()
    93  	for _, action := range []string{"close", "readClose", "deadlineControl"} {
    94  		t.Run(action, func(t *testing.T) {
    95  			t.Parallel()
    96  			name := pipeName()
    97  			writer := newBytePipe(t, name, true)
    98  			h := openHandle(t, name, syscall.O_RDWR)
    99  			var wg sync.WaitGroup
   100  			wg.Go(func() {
   101  				var buf [1]byte
   102  				var n uint32
   103  				syscall.ReadFile(h, buf[:], &n, nil)
   104  			})
   105  			time.Sleep(20 * time.Millisecond) // Let the native read block.
   106  			f := os.NewFile(uintptr(h), name)
   107  			defer func() {
   108  				writer.Close()
   109  				syscall.CancelIoEx(h, nil)
   110  				wg.Wait()
   111  				f.Close()
   112  			}()
   113  
   114  			if action == "readClose" {
   115  				wg.Go(func() {
   116  					var buf [1]byte
   117  					_, err := f.Read(buf[:])
   118  					if !errors.Is(err, os.ErrClosed) {
   119  						t.Errorf("Read = %v; want ErrClosed", err)
   120  					}
   121  				})
   122  				time.Sleep(20 * time.Millisecond) // Let first-use detection block.
   123  			}
   124  			if action == "deadlineControl" {
   125  				// Unlike Read, a deadline setter does not hold the I/O locks.
   126  				// Fd must also avoid waiting for the initialization lock.
   127  				wg.Go(func() {
   128  					if err := f.SetDeadline(time.Time{}); !errors.Is(err, os.ErrNoDeadline) {
   129  						t.Errorf("SetDeadline = %v; want ErrNoDeadline", err)
   130  					}
   131  				})
   132  				time.Sleep(20 * time.Millisecond) // Let first-use detection block.
   133  			}
   134  			if action == "close" || action == "readClose" {
   135  				if err := f.Close(); err != nil {
   136  					t.Fatal(err)
   137  				}
   138  				return
   139  			}
   140  			if got := f.Fd(); got != uintptr(h) {
   141  				t.Errorf("Fd = %d; want %d", got, h)
   142  			}
   143  			raw, err := f.SyscallConn()
   144  			if err == nil {
   145  				err = raw.Control(func(fd uintptr) {
   146  					if err := syscall.CancelIoEx(syscall.Handle(fd), nil); err != nil && err != syscall.ERROR_NOT_FOUND {
   147  						t.Error(err)
   148  					}
   149  				})
   150  			}
   151  			if err != nil {
   152  				t.Fatal(err)
   153  			}
   154  		})
   155  	}
   156  }
   157  
   158  func TestNewFileEventFallback(t *testing.T) {
   159  	t.Parallel()
   160  	for _, fallback := range []string{"fd", "externalIOCP"} {
   161  		t.Run(fallback, func(t *testing.T) {
   162  			t.Parallel()
   163  			f, h := newLazyFile(t, true)
   164  			if fallback == "fd" {
   165  				if got := f.Fd(); got != uintptr(h) {
   166  					t.Fatalf("Fd = %d; want %d", got, h)
   167  				}
   168  			} else {
   169  				iocp, err := windows.CreateIoCompletionPort(syscall.InvalidHandle, 0, 0, 0)
   170  				if err != nil {
   171  					t.Fatal(err)
   172  				}
   173  				defer syscall.CloseHandle(iocp)
   174  				if _, err := windows.CreateIoCompletionPort(h, iocp, 0, 0); err != nil {
   175  					t.Fatal(err)
   176  				}
   177  			}
   178  			if err := f.SetDeadline(time.Time{}); !errors.Is(err, os.ErrNoDeadline) {
   179  				t.Fatalf("SetDeadline = %v; want ErrNoDeadline", err)
   180  			}
   181  			var buf [5]byte
   182  			if n, err := f.ReadAt(buf[:], 0); err != nil || n != len(buf) || string(buf[:]) != "hello" {
   183  				t.Fatalf("ReadAt = %q, %d, %v", buf, n, err)
   184  			}
   185  		})
   186  	}
   187  }
   188  
   189  func TestNewFileLazyInitRace(t *testing.T) {
   190  	t.Parallel()
   191  	for _, action := range []string{"close", "fd"} {
   192  		t.Run(action, func(t *testing.T) {
   193  			for range 100 {
   194  				f, _ := newLazyFile(t, true)
   195  				start := make(chan struct{})
   196  				var wg sync.WaitGroup
   197  				for _, op := range []func() error{
   198  					func() error {
   199  						var b [1]byte
   200  						_, err := f.ReadAt(b[:], 0)
   201  						return err
   202  					},
   203  					func() error { return f.SetDeadline(time.Time{}) },
   204  				} {
   205  					wg.Go(func() {
   206  						<-start
   207  						if err := op(); err != nil && !errors.Is(err, os.ErrClosed) && !errors.Is(err, os.ErrNoDeadline) && err != poll.ErrFileClosing {
   208  							t.Error(err)
   209  						}
   210  					})
   211  				}
   212  				wg.Go(func() {
   213  					<-start
   214  					if action == "close" {
   215  						if err := f.Close(); err != nil {
   216  							t.Error(err)
   217  						}
   218  					} else {
   219  						f.Fd()
   220  					}
   221  				})
   222  				close(start)
   223  				wg.Wait()
   224  				f.Close()
   225  			}
   226  		})
   227  	}
   228  }
   229  

View as plain text