Home | History | Annotate | Download | only in os
      1 // Copyright 2014 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 	"fmt"
      9 	"internal/syscall/windows"
     10 	"internal/testenv"
     11 	"io"
     12 	"io/ioutil"
     13 	"os"
     14 	osexec "os/exec"
     15 	"path/filepath"
     16 	"reflect"
     17 	"runtime"
     18 	"sort"
     19 	"strings"
     20 	"syscall"
     21 	"testing"
     22 	"unicode/utf16"
     23 	"unsafe"
     24 )
     25 
     26 func TestSameWindowsFile(t *testing.T) {
     27 	temp, err := ioutil.TempDir("", "TestSameWindowsFile")
     28 	if err != nil {
     29 		t.Fatal(err)
     30 	}
     31 	defer os.RemoveAll(temp)
     32 
     33 	wd, err := os.Getwd()
     34 	if err != nil {
     35 		t.Fatal(err)
     36 	}
     37 	err = os.Chdir(temp)
     38 	if err != nil {
     39 		t.Fatal(err)
     40 	}
     41 	defer os.Chdir(wd)
     42 
     43 	f, err := os.Create("a")
     44 	if err != nil {
     45 		t.Fatal(err)
     46 	}
     47 	f.Close()
     48 
     49 	ia1, err := os.Stat("a")
     50 	if err != nil {
     51 		t.Fatal(err)
     52 	}
     53 
     54 	path, err := filepath.Abs("a")
     55 	if err != nil {
     56 		t.Fatal(err)
     57 	}
     58 	ia2, err := os.Stat(path)
     59 	if err != nil {
     60 		t.Fatal(err)
     61 	}
     62 	if !os.SameFile(ia1, ia2) {
     63 		t.Errorf("files should be same")
     64 	}
     65 
     66 	p := filepath.VolumeName(path) + filepath.Base(path)
     67 	if err != nil {
     68 		t.Fatal(err)
     69 	}
     70 	ia3, err := os.Stat(p)
     71 	if err != nil {
     72 		t.Fatal(err)
     73 	}
     74 	if !os.SameFile(ia1, ia3) {
     75 		t.Errorf("files should be same")
     76 	}
     77 }
     78 
     79 type dirLinkTest struct {
     80 	name    string
     81 	mklink  func(link, target string) error
     82 	issueNo int // correspondent issue number (for broken tests)
     83 }
     84 
     85 func testDirLinks(t *testing.T, tests []dirLinkTest) {
     86 	tmpdir, err := ioutil.TempDir("", "testDirLinks")
     87 	if err != nil {
     88 		t.Fatal(err)
     89 	}
     90 	defer os.RemoveAll(tmpdir)
     91 
     92 	oldwd, err := os.Getwd()
     93 	if err != nil {
     94 		t.Fatal(err)
     95 	}
     96 	err = os.Chdir(tmpdir)
     97 	if err != nil {
     98 		t.Fatal(err)
     99 	}
    100 	defer os.Chdir(oldwd)
    101 
    102 	dir := filepath.Join(tmpdir, "dir")
    103 	err = os.Mkdir(dir, 0777)
    104 	if err != nil {
    105 		t.Fatal(err)
    106 	}
    107 	err = ioutil.WriteFile(filepath.Join(dir, "abc"), []byte("abc"), 0644)
    108 	if err != nil {
    109 		t.Fatal(err)
    110 	}
    111 	for _, test := range tests {
    112 		link := filepath.Join(tmpdir, test.name+"_link")
    113 		err := test.mklink(link, dir)
    114 		if err != nil {
    115 			t.Errorf("creating link for %s test failed: %v", test.name, err)
    116 			continue
    117 		}
    118 
    119 		data, err := ioutil.ReadFile(filepath.Join(link, "abc"))
    120 		if err != nil {
    121 			t.Errorf("failed to read abc file: %v", err)
    122 			continue
    123 		}
    124 		if string(data) != "abc" {
    125 			t.Errorf(`abc file is expected to have "abc" in it, but has %v`, data)
    126 			continue
    127 		}
    128 
    129 		if test.issueNo > 0 {
    130 			t.Logf("skipping broken %q test: see issue %d", test.name, test.issueNo)
    131 			continue
    132 		}
    133 
    134 		fi, err := os.Stat(link)
    135 		if err != nil {
    136 			t.Errorf("failed to stat link %v: %v", link, err)
    137 			continue
    138 		}
    139 		expected := filepath.Base(dir)
    140 		got := fi.Name()
    141 		if !fi.IsDir() || expected != got {
    142 			t.Errorf("link should point to %v but points to %v instead", expected, got)
    143 			continue
    144 		}
    145 	}
    146 }
    147 
    148 // reparseData is used to build reparse buffer data required for tests.
    149 type reparseData struct {
    150 	substituteName namePosition
    151 	printName      namePosition
    152 	pathBuf        []uint16
    153 }
    154 
    155 type namePosition struct {
    156 	offset uint16
    157 	length uint16
    158 }
    159 
    160 func (rd *reparseData) addUTF16s(s []uint16) (offset uint16) {
    161 	off := len(rd.pathBuf) * 2
    162 	rd.pathBuf = append(rd.pathBuf, s...)
    163 	return uint16(off)
    164 }
    165 
    166 func (rd *reparseData) addString(s string) (offset, length uint16) {
    167 	p := syscall.StringToUTF16(s)
    168 	return rd.addUTF16s(p), uint16(len(p)-1) * 2 // do not include terminating NUL in the legth (as per PrintNameLength and SubstituteNameLength documentation)
    169 }
    170 
    171 func (rd *reparseData) addSubstituteName(name string) {
    172 	rd.substituteName.offset, rd.substituteName.length = rd.addString(name)
    173 }
    174 
    175 func (rd *reparseData) addPrintName(name string) {
    176 	rd.printName.offset, rd.printName.length = rd.addString(name)
    177 }
    178 
    179 func (rd *reparseData) addStringNoNUL(s string) (offset, length uint16) {
    180 	p := syscall.StringToUTF16(s)
    181 	p = p[:len(p)-1]
    182 	return rd.addUTF16s(p), uint16(len(p)) * 2
    183 }
    184 
    185 func (rd *reparseData) addSubstituteNameNoNUL(name string) {
    186 	rd.substituteName.offset, rd.substituteName.length = rd.addStringNoNUL(name)
    187 }
    188 
    189 func (rd *reparseData) addPrintNameNoNUL(name string) {
    190 	rd.printName.offset, rd.printName.length = rd.addStringNoNUL(name)
    191 }
    192 
    193 // pathBuffeLen returns length of rd pathBuf in bytes.
    194 func (rd *reparseData) pathBuffeLen() uint16 {
    195 	return uint16(len(rd.pathBuf)) * 2
    196 }
    197 
    198 // Windows REPARSE_DATA_BUFFER contains union member, and cannot be
    199 // translated into Go directly. _REPARSE_DATA_BUFFER type is to help
    200 // construct alternative versions of Windows REPARSE_DATA_BUFFER with
    201 // union part of SymbolicLinkReparseBuffer or MountPointReparseBuffer type.
    202 type _REPARSE_DATA_BUFFER struct {
    203 	header windows.REPARSE_DATA_BUFFER_HEADER
    204 	detail [syscall.MAXIMUM_REPARSE_DATA_BUFFER_SIZE]byte
    205 }
    206 
    207 func createDirLink(link string, rdb *_REPARSE_DATA_BUFFER) error {
    208 	err := os.Mkdir(link, 0777)
    209 	if err != nil {
    210 		return err
    211 	}
    212 
    213 	linkp := syscall.StringToUTF16(link)
    214 	fd, err := syscall.CreateFile(&linkp[0], syscall.GENERIC_WRITE, 0, nil, syscall.OPEN_EXISTING,
    215 		syscall.FILE_FLAG_OPEN_REPARSE_POINT|syscall.FILE_FLAG_BACKUP_SEMANTICS, 0)
    216 	if err != nil {
    217 		return err
    218 	}
    219 	defer syscall.CloseHandle(fd)
    220 
    221 	buflen := uint32(rdb.header.ReparseDataLength) + uint32(unsafe.Sizeof(rdb.header))
    222 	var bytesReturned uint32
    223 	return syscall.DeviceIoControl(fd, windows.FSCTL_SET_REPARSE_POINT,
    224 		(*byte)(unsafe.Pointer(&rdb.header)), buflen, nil, 0, &bytesReturned, nil)
    225 }
    226 
    227 func createMountPoint(link string, target *reparseData) error {
    228 	var buf *windows.MountPointReparseBuffer
    229 	buflen := uint16(unsafe.Offsetof(buf.PathBuffer)) + target.pathBuffeLen() // see ReparseDataLength documentation
    230 	byteblob := make([]byte, buflen)
    231 	buf = (*windows.MountPointReparseBuffer)(unsafe.Pointer(&byteblob[0]))
    232 	buf.SubstituteNameOffset = target.substituteName.offset
    233 	buf.SubstituteNameLength = target.substituteName.length
    234 	buf.PrintNameOffset = target.printName.offset
    235 	buf.PrintNameLength = target.printName.length
    236 	copy((*[2048]uint16)(unsafe.Pointer(&buf.PathBuffer[0]))[:], target.pathBuf)
    237 
    238 	var rdb _REPARSE_DATA_BUFFER
    239 	rdb.header.ReparseTag = windows.IO_REPARSE_TAG_MOUNT_POINT
    240 	rdb.header.ReparseDataLength = buflen
    241 	copy(rdb.detail[:], byteblob)
    242 
    243 	return createDirLink(link, &rdb)
    244 }
    245 
    246 func TestDirectoryJunction(t *testing.T) {
    247 	var tests = []dirLinkTest{
    248 		{
    249 			// Create link similar to what mklink does, by inserting \??\ at the front of absolute target.
    250 			name: "standard",
    251 			mklink: func(link, target string) error {
    252 				var t reparseData
    253 				t.addSubstituteName(`\??\` + target)
    254 				t.addPrintName(target)
    255 				return createMountPoint(link, &t)
    256 			},
    257 		},
    258 		{
    259 			// Do as junction utility https://technet.microsoft.com/en-au/sysinternals/bb896768.aspx does - set PrintNameLength to 0.
    260 			name: "have_blank_print_name",
    261 			mklink: func(link, target string) error {
    262 				var t reparseData
    263 				t.addSubstituteName(`\??\` + target)
    264 				t.addPrintName("")
    265 				return createMountPoint(link, &t)
    266 			},
    267 		},
    268 	}
    269 	output, _ := osexec.Command("cmd", "/c", "mklink", "/?").Output()
    270 	mklinkSupportsJunctionLinks := strings.Contains(string(output), " /J ")
    271 	if mklinkSupportsJunctionLinks {
    272 		tests = append(tests,
    273 			dirLinkTest{
    274 				name: "use_mklink_cmd",
    275 				mklink: func(link, target string) error {
    276 					output, err := osexec.Command("cmd", "/c", "mklink", "/J", link, target).CombinedOutput()
    277 					if err != nil {
    278 						t.Errorf("failed to run mklink %v %v: %v %q", link, target, err, output)
    279 					}
    280 					return nil
    281 				},
    282 			},
    283 		)
    284 	} else {
    285 		t.Log(`skipping "use_mklink_cmd" test, mklink does not supports directory junctions`)
    286 	}
    287 	testDirLinks(t, tests)
    288 }
    289 
    290 func enableCurrentThreadPrivilege(privilegeName string) error {
    291 	ct, err := windows.GetCurrentThread()
    292 	if err != nil {
    293 		return err
    294 	}
    295 	var t syscall.Token
    296 	err = windows.OpenThreadToken(ct, syscall.TOKEN_QUERY|windows.TOKEN_ADJUST_PRIVILEGES, false, &t)
    297 	if err != nil {
    298 		return err
    299 	}
    300 	defer syscall.CloseHandle(syscall.Handle(t))
    301 
    302 	var tp windows.TOKEN_PRIVILEGES
    303 
    304 	privStr, err := syscall.UTF16PtrFromString(privilegeName)
    305 	if err != nil {
    306 		return err
    307 	}
    308 	err = windows.LookupPrivilegeValue(nil, privStr, &tp.Privileges[0].Luid)
    309 	if err != nil {
    310 		return err
    311 	}
    312 	tp.PrivilegeCount = 1
    313 	tp.Privileges[0].Attributes = windows.SE_PRIVILEGE_ENABLED
    314 	return windows.AdjustTokenPrivileges(t, false, &tp, 0, nil, nil)
    315 }
    316 
    317 func createSymbolicLink(link string, target *reparseData, isrelative bool) error {
    318 	var buf *windows.SymbolicLinkReparseBuffer
    319 	buflen := uint16(unsafe.Offsetof(buf.PathBuffer)) + target.pathBuffeLen() // see ReparseDataLength documentation
    320 	byteblob := make([]byte, buflen)
    321 	buf = (*windows.SymbolicLinkReparseBuffer)(unsafe.Pointer(&byteblob[0]))
    322 	buf.SubstituteNameOffset = target.substituteName.offset
    323 	buf.SubstituteNameLength = target.substituteName.length
    324 	buf.PrintNameOffset = target.printName.offset
    325 	buf.PrintNameLength = target.printName.length
    326 	if isrelative {
    327 		buf.Flags = windows.SYMLINK_FLAG_RELATIVE
    328 	}
    329 	copy((*[2048]uint16)(unsafe.Pointer(&buf.PathBuffer[0]))[:], target.pathBuf)
    330 
    331 	var rdb _REPARSE_DATA_BUFFER
    332 	rdb.header.ReparseTag = syscall.IO_REPARSE_TAG_SYMLINK
    333 	rdb.header.ReparseDataLength = buflen
    334 	copy(rdb.detail[:], byteblob)
    335 
    336 	return createDirLink(link, &rdb)
    337 }
    338 
    339 func TestDirectorySymbolicLink(t *testing.T) {
    340 	var tests []dirLinkTest
    341 	output, _ := osexec.Command("cmd", "/c", "mklink", "/?").Output()
    342 	mklinkSupportsDirectorySymbolicLinks := strings.Contains(string(output), " /D ")
    343 	if mklinkSupportsDirectorySymbolicLinks {
    344 		tests = append(tests,
    345 			dirLinkTest{
    346 				name: "use_mklink_cmd",
    347 				mklink: func(link, target string) error {
    348 					output, err := osexec.Command("cmd", "/c", "mklink", "/D", link, target).CombinedOutput()
    349 					if err != nil {
    350 						t.Errorf("failed to run mklink %v %v: %v %q", link, target, err, output)
    351 					}
    352 					return nil
    353 				},
    354 			},
    355 		)
    356 	} else {
    357 		t.Log(`skipping "use_mklink_cmd" test, mklink does not supports directory symbolic links`)
    358 	}
    359 
    360 	// The rest of these test requires SeCreateSymbolicLinkPrivilege to be held.
    361 	runtime.LockOSThread()
    362 	defer runtime.UnlockOSThread()
    363 
    364 	err := windows.ImpersonateSelf(windows.SecurityImpersonation)
    365 	if err != nil {
    366 		t.Fatal(err)
    367 	}
    368 	defer windows.RevertToSelf()
    369 
    370 	err = enableCurrentThreadPrivilege("SeCreateSymbolicLinkPrivilege")
    371 	if err != nil {
    372 		t.Skipf(`skipping some tests, could not enable "SeCreateSymbolicLinkPrivilege": %v`, err)
    373 	}
    374 	tests = append(tests,
    375 		dirLinkTest{
    376 			name: "use_os_pkg",
    377 			mklink: func(link, target string) error {
    378 				return os.Symlink(target, link)
    379 			},
    380 		},
    381 		dirLinkTest{
    382 			// Create link similar to what mklink does, by inserting \??\ at the front of absolute target.
    383 			name: "standard",
    384 			mklink: func(link, target string) error {
    385 				var t reparseData
    386 				t.addPrintName(target)
    387 				t.addSubstituteName(`\??\` + target)
    388 				return createSymbolicLink(link, &t, false)
    389 			},
    390 		},
    391 		dirLinkTest{
    392 			name: "relative",
    393 			mklink: func(link, target string) error {
    394 				var t reparseData
    395 				t.addSubstituteNameNoNUL(filepath.Base(target))
    396 				t.addPrintNameNoNUL(filepath.Base(target))
    397 				return createSymbolicLink(link, &t, true)
    398 			},
    399 		},
    400 	)
    401 	testDirLinks(t, tests)
    402 }
    403 
    404 func TestNetworkSymbolicLink(t *testing.T) {
    405 	testenv.MustHaveSymlink(t)
    406 
    407 	dir, err := ioutil.TempDir("", "TestNetworkSymbolicLink")
    408 	if err != nil {
    409 		t.Fatal(err)
    410 	}
    411 	defer os.RemoveAll(dir)
    412 
    413 	oldwd, err := os.Getwd()
    414 	if err != nil {
    415 		t.Fatal(err)
    416 	}
    417 	err = os.Chdir(dir)
    418 	if err != nil {
    419 		t.Fatal(err)
    420 	}
    421 	defer os.Chdir(oldwd)
    422 
    423 	shareName := "GoSymbolicLinkTestShare" // hope no conflictions
    424 	sharePath := filepath.Join(dir, shareName)
    425 	testDir := "TestDir"
    426 
    427 	err = os.MkdirAll(filepath.Join(sharePath, testDir), 0777)
    428 	if err != nil {
    429 		t.Fatal(err)
    430 	}
    431 
    432 	wShareName, err := syscall.UTF16PtrFromString(shareName)
    433 	if err != nil {
    434 		t.Fatal(err)
    435 	}
    436 	wSharePath, err := syscall.UTF16PtrFromString(sharePath)
    437 	if err != nil {
    438 		t.Fatal(err)
    439 	}
    440 
    441 	p := windows.SHARE_INFO_2{
    442 		Netname:     wShareName,
    443 		Type:        windows.STYPE_DISKTREE,
    444 		Remark:      nil,
    445 		Permissions: 0,
    446 		MaxUses:     1,
    447 		CurrentUses: 0,
    448 		Path:        wSharePath,
    449 		Passwd:      nil,
    450 	}
    451 
    452 	err = windows.NetShareAdd(nil, 2, (*byte)(unsafe.Pointer(&p)), nil)
    453 	if err != nil {
    454 		if err == syscall.ERROR_ACCESS_DENIED {
    455 			t.Skip("you don't have enough privileges to add network share")
    456 		}
    457 		t.Fatal(err)
    458 	}
    459 	defer func() {
    460 		err := windows.NetShareDel(nil, wShareName, 0)
    461 		if err != nil {
    462 			t.Fatal(err)
    463 		}
    464 	}()
    465 
    466 	UNCPath := `\\localhost\` + shareName + `\`
    467 
    468 	fi1, err := os.Stat(sharePath)
    469 	if err != nil {
    470 		t.Fatal(err)
    471 	}
    472 	fi2, err := os.Stat(UNCPath)
    473 	if err != nil {
    474 		t.Fatal(err)
    475 	}
    476 	if !os.SameFile(fi1, fi2) {
    477 		t.Fatalf("%q and %q should be the same directory, but not", sharePath, UNCPath)
    478 	}
    479 
    480 	target := filepath.Join(UNCPath, testDir)
    481 	link := "link"
    482 
    483 	err = os.Symlink(target, link)
    484 	if err != nil {
    485 		t.Fatal(err)
    486 	}
    487 	defer os.Remove(link)
    488 
    489 	got, err := os.Readlink(link)
    490 	if err != nil {
    491 		t.Fatal(err)
    492 	}
    493 
    494 	if got != target {
    495 		t.Errorf(`os.Readlink("%s"): got %v, want %v`, link, got, target)
    496 	}
    497 }
    498 
    499 func TestStartProcessAttr(t *testing.T) {
    500 	p, err := os.StartProcess(os.Getenv("COMSPEC"), []string{"/c", "cd"}, new(os.ProcAttr))
    501 	if err != nil {
    502 		return
    503 	}
    504 	defer p.Wait()
    505 	t.Fatalf("StartProcess expected to fail, but succeeded.")
    506 }
    507 
    508 func TestShareNotExistError(t *testing.T) {
    509 	if testing.Short() {
    510 		t.Skip("slow test that uses network; skipping")
    511 	}
    512 	_, err := os.Stat(`\\no_such_server\no_such_share\no_such_file`)
    513 	if err == nil {
    514 		t.Fatal("stat succeeded, but expected to fail")
    515 	}
    516 	if !os.IsNotExist(err) {
    517 		t.Fatalf("os.Stat failed with %q, but os.IsNotExist(err) is false", err)
    518 	}
    519 }
    520 
    521 func TestBadNetPathError(t *testing.T) {
    522 	const ERROR_BAD_NETPATH = syscall.Errno(53)
    523 	if !os.IsNotExist(ERROR_BAD_NETPATH) {
    524 		t.Fatal("os.IsNotExist(syscall.Errno(53)) is false, but want true")
    525 	}
    526 }
    527 
    528 func TestStatDir(t *testing.T) {
    529 	defer chtmpdir(t)()
    530 
    531 	f, err := os.Open(".")
    532 	if err != nil {
    533 		t.Fatal(err)
    534 	}
    535 	defer f.Close()
    536 
    537 	fi, err := f.Stat()
    538 	if err != nil {
    539 		t.Fatal(err)
    540 	}
    541 
    542 	err = os.Chdir("..")
    543 	if err != nil {
    544 		t.Fatal(err)
    545 	}
    546 
    547 	fi2, err := f.Stat()
    548 	if err != nil {
    549 		t.Fatal(err)
    550 	}
    551 
    552 	if !os.SameFile(fi, fi2) {
    553 		t.Fatal("race condition occurred")
    554 	}
    555 }
    556 
    557 func TestOpenVolumeName(t *testing.T) {
    558 	tmpdir, err := ioutil.TempDir("", "TestOpenVolumeName")
    559 	if err != nil {
    560 		t.Fatal(err)
    561 	}
    562 	defer os.RemoveAll(tmpdir)
    563 
    564 	wd, err := os.Getwd()
    565 	if err != nil {
    566 		t.Fatal(err)
    567 	}
    568 	err = os.Chdir(tmpdir)
    569 	if err != nil {
    570 		t.Fatal(err)
    571 	}
    572 	defer os.Chdir(wd)
    573 
    574 	want := []string{"file1", "file2", "file3", "gopher.txt"}
    575 	sort.Strings(want)
    576 	for _, name := range want {
    577 		err := ioutil.WriteFile(filepath.Join(tmpdir, name), nil, 0777)
    578 		if err != nil {
    579 			t.Fatal(err)
    580 		}
    581 	}
    582 
    583 	f, err := os.Open(filepath.VolumeName(tmpdir))
    584 	if err != nil {
    585 		t.Fatal(err)
    586 	}
    587 	defer f.Close()
    588 
    589 	have, err := f.Readdirnames(-1)
    590 	if err != nil {
    591 		t.Fatal(err)
    592 	}
    593 	sort.Strings(have)
    594 
    595 	if strings.Join(want, "/") != strings.Join(have, "/") {
    596 		t.Fatalf("unexpected file list %q, want %q", have, want)
    597 	}
    598 }
    599 
    600 func TestDeleteReadOnly(t *testing.T) {
    601 	tmpdir, err := ioutil.TempDir("", "TestDeleteReadOnly")
    602 	if err != nil {
    603 		t.Fatal(err)
    604 	}
    605 	defer os.RemoveAll(tmpdir)
    606 	p := filepath.Join(tmpdir, "a")
    607 	// This sets FILE_ATTRIBUTE_READONLY.
    608 	f, err := os.OpenFile(p, os.O_CREATE, 0400)
    609 	if err != nil {
    610 		t.Fatal(err)
    611 	}
    612 	f.Close()
    613 
    614 	if err = os.Chmod(p, 0400); err != nil {
    615 		t.Fatal(err)
    616 	}
    617 	if err = os.Remove(p); err != nil {
    618 		t.Fatal(err)
    619 	}
    620 }
    621 
    622 func TestStatSymlinkLoop(t *testing.T) {
    623 	testenv.MustHaveSymlink(t)
    624 
    625 	defer chtmpdir(t)()
    626 
    627 	err := os.Symlink("x", "y")
    628 	if err != nil {
    629 		t.Fatal(err)
    630 	}
    631 	defer os.Remove("y")
    632 
    633 	err = os.Symlink("y", "x")
    634 	if err != nil {
    635 		t.Fatal(err)
    636 	}
    637 	defer os.Remove("x")
    638 
    639 	_, err = os.Stat("x")
    640 	if perr, ok := err.(*os.PathError); !ok || perr.Err != syscall.ELOOP {
    641 		t.Errorf("expected *PathError with ELOOP, got %T: %v\n", err, err)
    642 	}
    643 }
    644 
    645 func TestReadStdin(t *testing.T) {
    646 	old := *os.ReadConsoleFunc
    647 	defer func() {
    648 		*os.ReadConsoleFunc = old
    649 	}()
    650 
    651 	testConsole := os.NewConsoleFile(syscall.Stdin, "test")
    652 
    653 	var tests = []string{
    654 		"abc",
    655 		"",
    656 		"\u3042",
    657 		"hi",
    658 		"hello\x1aworld",
    659 		"\U0001F648\U0001F649\U0001F64A",
    660 	}
    661 
    662 	for _, consoleSize := range []int{1, 2, 3, 10, 16, 100, 1000} {
    663 		for _, readSize := range []int{1, 2, 3, 4, 5, 8, 10, 16, 20, 50, 100} {
    664 			for _, s := range tests {
    665 				t.Run(fmt.Sprintf("c%d/r%d/%s", consoleSize, readSize, s), func(t *testing.T) {
    666 					s16 := utf16.Encode([]rune(s))
    667 					*os.ReadConsoleFunc = func(h syscall.Handle, buf *uint16, toread uint32, read *uint32, inputControl *byte) error {
    668 						if inputControl != nil {
    669 							t.Fatalf("inputControl not nil")
    670 						}
    671 						n := int(toread)
    672 						if n > consoleSize {
    673 							n = consoleSize
    674 						}
    675 						n = copy((*[10000]uint16)(unsafe.Pointer(buf))[:n], s16)
    676 						s16 = s16[n:]
    677 						*read = uint32(n)
    678 						t.Logf("read %d -> %d", toread, *read)
    679 						return nil
    680 					}
    681 
    682 					var all []string
    683 					var buf []byte
    684 					chunk := make([]byte, readSize)
    685 					for {
    686 						n, err := testConsole.Read(chunk)
    687 						buf = append(buf, chunk[:n]...)
    688 						if err == io.EOF {
    689 							all = append(all, string(buf))
    690 							if len(all) >= 5 {
    691 								break
    692 							}
    693 							buf = buf[:0]
    694 						} else if err != nil {
    695 							t.Fatalf("reading %q: error: %v", s, err)
    696 						}
    697 						if len(buf) >= 2000 {
    698 							t.Fatalf("reading %q: stuck in loop: %q", s, buf)
    699 						}
    700 					}
    701 
    702 					want := strings.Split(s, "\x1a")
    703 					for len(want) < 5 {
    704 						want = append(want, "")
    705 					}
    706 					if !reflect.DeepEqual(all, want) {
    707 						t.Errorf("reading %q:\nhave %x\nwant %x", s, all, want)
    708 					}
    709 				})
    710 			}
    711 		}
    712 	}
    713 }
    714 
    715 func TestStatPagefile(t *testing.T) {
    716 	_, err := os.Stat(`c:\pagefile.sys`)
    717 	if err == nil {
    718 		return
    719 	}
    720 	if os.IsNotExist(err) {
    721 		t.Skip(`skipping because c:\pagefile.sys is not found`)
    722 	}
    723 	t.Fatal(err)
    724 }
    725