winfsnotify: delete package

Users who still need to resolve imports of this package can require
the last extant version of the module carved out in the prior CL.

Also delete fsnotify/README.txt. The substitute import path it
suggests is no longer accurate, and users have had seven years(!) to
migrate.

Fixes golang/go#51447.
Fixes golang/go#49734.

Change-Id: Ibf7e4566b65dd3927083d1516b0a53324ce1c4ad
Reviewed-on: https://go-review.googlesource.com/c/exp/+/389615
Reviewed-by: Russ Cox <rsc@golang.org>
Auto-Submit: Bryan Mills <bcmills@google.com>
Run-TryBot: Bryan Mills <bcmills@google.com>
TryBot-Result: Gopher Robot <gobot@golang.org>
diff --git a/fsnotify/README.txt b/fsnotify/README.txt
deleted file mode 100644
index 2ba49f6..0000000
--- a/fsnotify/README.txt
+++ /dev/null
@@ -1,3 +0,0 @@
-Please use gopkg.in/fsnotify.v0 instead.
-
-For updates, see: https://fsnotify.org/
diff --git a/winfsnotify/go.mod b/winfsnotify/go.mod
deleted file mode 100644
index 248af1a..0000000
--- a/winfsnotify/go.mod
+++ /dev/null
@@ -1,3 +0,0 @@
-module golang.org/x/exp/winfsnotify
-
-go 1.11
diff --git a/winfsnotify/winfsnotify.go b/winfsnotify/winfsnotify.go
deleted file mode 100644
index e25ecfa..0000000
--- a/winfsnotify/winfsnotify.go
+++ /dev/null
@@ -1,586 +0,0 @@
-// Copyright 2011 The Go Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE file.
-
-//go:build windows
-// +build windows
-
-// Package winfsnotify allows the user to receive
-// file system event notifications on Windows.
-//
-// Deprecated: winfsnotify has been unmaintained since 2016 (see
-// https://go.dev/issue/51447). Forks of this package with more recent
-// maintenance and cross-platform support can be found at
-// https://pkg.go.dev/search?q=fsnotify.
-package winfsnotify // import "golang.org/x/exp/winfsnotify"
-
-import (
-	"errors"
-	"fmt"
-	"os"
-	"path/filepath"
-	"runtime"
-	"syscall"
-	"unsafe"
-)
-
-// Event is the type of the notification messages
-// received on the watcher's Event channel.
-type Event struct {
-	Mask   uint32 // Mask of events
-	Cookie uint32 // Unique cookie associating related events (for rename)
-	Name   string // File name (optional)
-}
-
-const (
-	opAddWatch = iota
-	opRemoveWatch
-)
-
-const (
-	provisional uint64 = 1 << (32 + iota)
-)
-
-type input struct {
-	op    int
-	path  string
-	flags uint32
-	reply chan error
-}
-
-type inode struct {
-	handle syscall.Handle
-	volume uint32
-	index  uint64
-}
-
-type watch struct {
-	ov     syscall.Overlapped
-	ino    *inode            // i-number
-	path   string            // Directory path
-	mask   uint64            // Directory itself is being watched with these notify flags
-	names  map[string]uint64 // Map of names being watched and their notify flags
-	rename string            // Remembers the old name while renaming a file
-	buf    [4096]byte
-}
-
-type indexMap map[uint64]*watch
-type watchMap map[uint32]indexMap
-
-// A Watcher waits for and receives event notifications
-// for a specific set of files and directories.
-type Watcher struct {
-	port     syscall.Handle // Handle to completion port
-	watches  watchMap       // Map of watches (key: i-number)
-	input    chan *input    // Inputs to the reader are sent on this channel
-	Event    chan *Event    // Events are returned on this channel
-	Error    chan error     // Errors are sent on this channel
-	isClosed bool           // Set to true when Close() is first called
-	quit     chan chan<- error
-	cookie   uint32
-}
-
-// NewWatcher creates and returns a Watcher.
-func NewWatcher() (*Watcher, error) {
-	port, e := syscall.CreateIoCompletionPort(syscall.InvalidHandle, 0, 0, 0)
-	if e != nil {
-		return nil, os.NewSyscallError("CreateIoCompletionPort", e)
-	}
-	w := &Watcher{
-		port:    port,
-		watches: make(watchMap),
-		input:   make(chan *input, 1),
-		Event:   make(chan *Event, 50),
-		Error:   make(chan error),
-		quit:    make(chan chan<- error, 1),
-	}
-	go w.readEvents()
-	return w, nil
-}
-
-// Close closes a Watcher.
-// It sends a message to the reader goroutine to quit and removes all watches
-// associated with the watcher.
-func (w *Watcher) Close() error {
-	if w.isClosed {
-		return nil
-	}
-	w.isClosed = true
-
-	// Send "quit" message to the reader goroutine
-	ch := make(chan error)
-	w.quit <- ch
-	if err := w.wakeupReader(); err != nil {
-		return err
-	}
-	return <-ch
-}
-
-// AddWatch adds path to the watched file set.
-func (w *Watcher) AddWatch(path string, flags uint32) error {
-	if w.isClosed {
-		return errors.New("watcher already closed")
-	}
-	in := &input{
-		op:    opAddWatch,
-		path:  filepath.Clean(path),
-		flags: flags,
-		reply: make(chan error),
-	}
-	w.input <- in
-	if err := w.wakeupReader(); err != nil {
-		return err
-	}
-	return <-in.reply
-}
-
-// Watch adds path to the watched file set, watching all events.
-func (w *Watcher) Watch(path string) error {
-	return w.AddWatch(path, FS_ALL_EVENTS)
-}
-
-// RemoveWatch removes path from the watched file set.
-func (w *Watcher) RemoveWatch(path string) error {
-	in := &input{
-		op:    opRemoveWatch,
-		path:  filepath.Clean(path),
-		reply: make(chan error),
-	}
-	w.input <- in
-	if err := w.wakeupReader(); err != nil {
-		return err
-	}
-	return <-in.reply
-}
-
-func (w *Watcher) wakeupReader() error {
-	e := syscall.PostQueuedCompletionStatus(w.port, 0, 0, nil)
-	if e != nil {
-		return os.NewSyscallError("PostQueuedCompletionStatus", e)
-	}
-	return nil
-}
-
-func getDir(pathname string) (dir string, err error) {
-	pathnamep, e := syscall.UTF16PtrFromString(pathname)
-	if e != nil {
-		return "", e
-	}
-	attr, e := syscall.GetFileAttributes(pathnamep)
-	if e != nil {
-		return "", os.NewSyscallError("GetFileAttributes", e)
-	}
-	if attr&syscall.FILE_ATTRIBUTE_DIRECTORY != 0 {
-		dir = pathname
-	} else {
-		dir, _ = filepath.Split(pathname)
-		dir = filepath.Clean(dir)
-	}
-	return
-}
-
-func getIno(path string) (ino *inode, err error) {
-	pathp, e := syscall.UTF16PtrFromString(path)
-	if e != nil {
-		return nil, e
-	}
-	h, e := syscall.CreateFile(pathp,
-		syscall.FILE_LIST_DIRECTORY,
-		syscall.FILE_SHARE_READ|syscall.FILE_SHARE_WRITE|syscall.FILE_SHARE_DELETE,
-		nil, syscall.OPEN_EXISTING,
-		syscall.FILE_FLAG_BACKUP_SEMANTICS|syscall.FILE_FLAG_OVERLAPPED, 0)
-	if e != nil {
-		return nil, os.NewSyscallError("CreateFile", e)
-	}
-	var fi syscall.ByHandleFileInformation
-	if e = syscall.GetFileInformationByHandle(h, &fi); e != nil {
-		syscall.CloseHandle(h)
-		return nil, os.NewSyscallError("GetFileInformationByHandle", e)
-	}
-	ino = &inode{
-		handle: h,
-		volume: fi.VolumeSerialNumber,
-		index:  uint64(fi.FileIndexHigh)<<32 | uint64(fi.FileIndexLow),
-	}
-	return ino, nil
-}
-
-// Must run within the I/O thread.
-func (m watchMap) get(ino *inode) *watch {
-	if i := m[ino.volume]; i != nil {
-		return i[ino.index]
-	}
-	return nil
-}
-
-// Must run within the I/O thread.
-func (m watchMap) set(ino *inode, watch *watch) {
-	i := m[ino.volume]
-	if i == nil {
-		i = make(indexMap)
-		m[ino.volume] = i
-	}
-	i[ino.index] = watch
-}
-
-// Must run within the I/O thread.
-func (w *Watcher) addWatch(pathname string, flags uint64) error {
-	dir, err := getDir(pathname)
-	if err != nil {
-		return err
-	}
-	if flags&FS_ONLYDIR != 0 && pathname != dir {
-		return nil
-	}
-	ino, err := getIno(dir)
-	if err != nil {
-		return err
-	}
-	watchEntry := w.watches.get(ino)
-	if watchEntry == nil {
-		if _, e := syscall.CreateIoCompletionPort(ino.handle, w.port, 0, 0); e != nil {
-			syscall.CloseHandle(ino.handle)
-			return os.NewSyscallError("CreateIoCompletionPort", e)
-		}
-		watchEntry = &watch{
-			ino:   ino,
-			path:  dir,
-			names: make(map[string]uint64),
-		}
-		w.watches.set(ino, watchEntry)
-		flags |= provisional
-	} else {
-		syscall.CloseHandle(ino.handle)
-	}
-	if pathname == dir {
-		watchEntry.mask |= flags
-	} else {
-		watchEntry.names[filepath.Base(pathname)] |= flags
-	}
-	if err = w.startRead(watchEntry); err != nil {
-		return err
-	}
-	if pathname == dir {
-		watchEntry.mask &= ^provisional
-	} else {
-		watchEntry.names[filepath.Base(pathname)] &= ^provisional
-	}
-	return nil
-}
-
-// Must run within the I/O thread.
-func (w *Watcher) removeWatch(pathname string) error {
-	dir, err := getDir(pathname)
-	if err != nil {
-		return err
-	}
-	ino, err := getIno(dir)
-	if err != nil {
-		return err
-	}
-	watch := w.watches.get(ino)
-	if watch == nil {
-		return fmt.Errorf("can't remove non-existent watch for: %s", pathname)
-	}
-	if pathname == dir {
-		w.sendEvent(watch.path, watch.mask&FS_IGNORED)
-		watch.mask = 0
-	} else {
-		name := filepath.Base(pathname)
-		w.sendEvent(watch.path+"/"+name, watch.names[name]&FS_IGNORED)
-		delete(watch.names, name)
-	}
-	return w.startRead(watch)
-}
-
-// Must run within the I/O thread.
-func (w *Watcher) deleteWatch(watch *watch) {
-	for name, mask := range watch.names {
-		if mask&provisional == 0 {
-			w.sendEvent(watch.path+"/"+name, mask&FS_IGNORED)
-		}
-		delete(watch.names, name)
-	}
-	if watch.mask != 0 {
-		if watch.mask&provisional == 0 {
-			w.sendEvent(watch.path, watch.mask&FS_IGNORED)
-		}
-		watch.mask = 0
-	}
-}
-
-// Must run within the I/O thread.
-func (w *Watcher) startRead(watch *watch) error {
-	if e := syscall.CancelIo(watch.ino.handle); e != nil {
-		w.Error <- os.NewSyscallError("CancelIo", e)
-		w.deleteWatch(watch)
-	}
-	mask := toWindowsFlags(watch.mask)
-	for _, m := range watch.names {
-		mask |= toWindowsFlags(m)
-	}
-	if mask == 0 {
-		if e := syscall.CloseHandle(watch.ino.handle); e != nil {
-			w.Error <- os.NewSyscallError("CloseHandle", e)
-		}
-		delete(w.watches[watch.ino.volume], watch.ino.index)
-		return nil
-	}
-	e := syscall.ReadDirectoryChanges(watch.ino.handle, &watch.buf[0],
-		uint32(unsafe.Sizeof(watch.buf)), false, mask, nil, &watch.ov, 0)
-	if e != nil {
-		err := os.NewSyscallError("ReadDirectoryChanges", e)
-		if e == syscall.ERROR_ACCESS_DENIED && watch.mask&provisional == 0 {
-			// Watched directory was probably removed
-			if w.sendEvent(watch.path, watch.mask&FS_DELETE_SELF) {
-				if watch.mask&FS_ONESHOT != 0 {
-					watch.mask = 0
-				}
-			}
-			err = nil
-		}
-		w.deleteWatch(watch)
-		w.startRead(watch)
-		return err
-	}
-	return nil
-}
-
-// readEvents reads from the I/O completion port, converts the
-// received events into Event objects and sends them via the Event channel.
-// Entry point to the I/O thread.
-func (w *Watcher) readEvents() {
-	var (
-		n, key uint32
-		ov     *syscall.Overlapped
-	)
-	runtime.LockOSThread()
-
-	for {
-		e := syscall.GetQueuedCompletionStatus(w.port, &n, &key, &ov, syscall.INFINITE)
-		watch := (*watch)(unsafe.Pointer(ov))
-
-		if watch == nil {
-			select {
-			case ch := <-w.quit:
-				for _, index := range w.watches {
-					for _, watch := range index {
-						w.deleteWatch(watch)
-						w.startRead(watch)
-					}
-				}
-				var err error
-				if e := syscall.CloseHandle(w.port); e != nil {
-					err = os.NewSyscallError("CloseHandle", e)
-				}
-				close(w.Event)
-				close(w.Error)
-				ch <- err
-				return
-			case in := <-w.input:
-				switch in.op {
-				case opAddWatch:
-					in.reply <- w.addWatch(in.path, uint64(in.flags))
-				case opRemoveWatch:
-					in.reply <- w.removeWatch(in.path)
-				}
-			default:
-			}
-			continue
-		}
-
-		switch e {
-		case syscall.ERROR_ACCESS_DENIED:
-			// Watched directory was probably removed
-			w.sendEvent(watch.path, watch.mask&FS_DELETE_SELF)
-			w.deleteWatch(watch)
-			w.startRead(watch)
-			continue
-		case syscall.ERROR_OPERATION_ABORTED:
-			// CancelIo was called on this handle
-			continue
-		default:
-			w.Error <- os.NewSyscallError("GetQueuedCompletionPort", e)
-			continue
-		case nil:
-		}
-
-		var offset uint32
-		for {
-			if n == 0 {
-				w.Event <- &Event{Mask: FS_Q_OVERFLOW}
-				w.Error <- errors.New("short read in readEvents()")
-				break
-			}
-
-			// Point "raw" to the event in the buffer
-			raw := (*syscall.FileNotifyInformation)(unsafe.Pointer(&watch.buf[offset]))
-			buf := (*[syscall.MAX_PATH]uint16)(unsafe.Pointer(&raw.FileName))
-			name := syscall.UTF16ToString(buf[:raw.FileNameLength/2])
-			fullname := watch.path + "/" + name
-
-			var mask uint64
-			switch raw.Action {
-			case syscall.FILE_ACTION_REMOVED:
-				mask = FS_DELETE_SELF
-			case syscall.FILE_ACTION_MODIFIED:
-				mask = FS_MODIFY
-			case syscall.FILE_ACTION_RENAMED_OLD_NAME:
-				watch.rename = name
-			case syscall.FILE_ACTION_RENAMED_NEW_NAME:
-				if watch.names[watch.rename] != 0 {
-					watch.names[name] |= watch.names[watch.rename]
-					delete(watch.names, watch.rename)
-					mask = FS_MOVE_SELF
-				}
-			}
-
-			sendNameEvent := func() {
-				if w.sendEvent(fullname, watch.names[name]&mask) {
-					if watch.names[name]&FS_ONESHOT != 0 {
-						delete(watch.names, name)
-					}
-				}
-			}
-			if raw.Action != syscall.FILE_ACTION_RENAMED_NEW_NAME {
-				sendNameEvent()
-			}
-			if raw.Action == syscall.FILE_ACTION_REMOVED {
-				w.sendEvent(fullname, watch.names[name]&FS_IGNORED)
-				delete(watch.names, name)
-			}
-			if w.sendEvent(fullname, watch.mask&toFSnotifyFlags(raw.Action)) {
-				if watch.mask&FS_ONESHOT != 0 {
-					watch.mask = 0
-				}
-			}
-			if raw.Action == syscall.FILE_ACTION_RENAMED_NEW_NAME {
-				fullname = watch.path + "/" + watch.rename
-				sendNameEvent()
-			}
-
-			// Move to the next event in the buffer
-			if raw.NextEntryOffset == 0 {
-				break
-			}
-			offset += raw.NextEntryOffset
-		}
-
-		if err := w.startRead(watch); err != nil {
-			w.Error <- err
-		}
-	}
-}
-
-func (w *Watcher) sendEvent(name string, mask uint64) bool {
-	if mask == 0 {
-		return false
-	}
-	event := &Event{Mask: uint32(mask), Name: name}
-	if mask&FS_MOVE != 0 {
-		if mask&FS_MOVED_FROM != 0 {
-			w.cookie++
-		}
-		event.Cookie = w.cookie
-	}
-	select {
-	case ch := <-w.quit:
-		w.quit <- ch
-	case w.Event <- event:
-	}
-	return true
-}
-
-// String formats the event e in the form
-// "filename: 0xEventMask = FS_ACCESS|FS_ATTRIB_|..."
-func (e *Event) String() string {
-	var events string
-	m := e.Mask
-	for _, b := range eventBits {
-		if m&b.Value != 0 {
-			m &^= b.Value
-			events += "|" + b.Name
-		}
-	}
-	if m != 0 {
-		events += fmt.Sprintf("|%#x", m)
-	}
-	if len(events) > 0 {
-		events = " == " + events[1:]
-	}
-	return fmt.Sprintf("%q: %#x%s", e.Name, e.Mask, events)
-}
-
-func toWindowsFlags(mask uint64) uint32 {
-	var m uint32
-	if mask&FS_ACCESS != 0 {
-		m |= syscall.FILE_NOTIFY_CHANGE_LAST_ACCESS
-	}
-	if mask&FS_MODIFY != 0 {
-		m |= syscall.FILE_NOTIFY_CHANGE_LAST_WRITE
-	}
-	if mask&FS_ATTRIB != 0 {
-		m |= syscall.FILE_NOTIFY_CHANGE_ATTRIBUTES
-	}
-	if mask&(FS_MOVE|FS_CREATE|FS_DELETE) != 0 {
-		m |= syscall.FILE_NOTIFY_CHANGE_FILE_NAME | syscall.FILE_NOTIFY_CHANGE_DIR_NAME
-	}
-	return m
-}
-
-func toFSnotifyFlags(action uint32) uint64 {
-	switch action {
-	case syscall.FILE_ACTION_ADDED:
-		return FS_CREATE
-	case syscall.FILE_ACTION_REMOVED:
-		return FS_DELETE
-	case syscall.FILE_ACTION_MODIFIED:
-		return FS_MODIFY
-	case syscall.FILE_ACTION_RENAMED_OLD_NAME:
-		return FS_MOVED_FROM
-	case syscall.FILE_ACTION_RENAMED_NEW_NAME:
-		return FS_MOVED_TO
-	}
-	return 0
-}
-
-const (
-	// Options for AddWatch
-	FS_ONESHOT = 0x80000000
-	FS_ONLYDIR = 0x1000000
-
-	// Events
-	FS_ACCESS      = 0x1
-	FS_ALL_EVENTS  = 0xfff
-	FS_ATTRIB      = 0x4
-	FS_CLOSE       = 0x18
-	FS_CREATE      = 0x100
-	FS_DELETE      = 0x200
-	FS_DELETE_SELF = 0x400
-	FS_MODIFY      = 0x2
-	FS_MOVE        = 0xc0
-	FS_MOVED_FROM  = 0x40
-	FS_MOVED_TO    = 0x80
-	FS_MOVE_SELF   = 0x800
-
-	// Special events
-	FS_IGNORED    = 0x8000
-	FS_Q_OVERFLOW = 0x4000
-)
-
-var eventBits = []struct {
-	Value uint32
-	Name  string
-}{
-	{FS_ACCESS, "FS_ACCESS"},
-	{FS_ATTRIB, "FS_ATTRIB"},
-	{FS_CREATE, "FS_CREATE"},
-	{FS_DELETE, "FS_DELETE"},
-	{FS_DELETE_SELF, "FS_DELETE_SELF"},
-	{FS_MODIFY, "FS_MODIFY"},
-	{FS_MOVED_FROM, "FS_MOVED_FROM"},
-	{FS_MOVED_TO, "FS_MOVED_TO"},
-	{FS_MOVE_SELF, "FS_MOVE_SELF"},
-	{FS_IGNORED, "FS_IGNORED"},
-	{FS_Q_OVERFLOW, "FS_Q_OVERFLOW"},
-}
diff --git a/winfsnotify/winfsnotify_test.go b/winfsnotify/winfsnotify_test.go
deleted file mode 100644
index de4f514..0000000
--- a/winfsnotify/winfsnotify_test.go
+++ /dev/null
@@ -1,131 +0,0 @@
-// Copyright 2011 The Go Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE file.
-
-//go:build windows
-// +build windows
-
-package winfsnotify
-
-import (
-	"io/ioutil"
-	"os"
-	"sync/atomic"
-	"testing"
-	"time"
-)
-
-func expect(t *testing.T, eventstream <-chan *Event, name string, mask uint32) {
-	t.Logf(`expected: "%s": 0x%x`, name, mask)
-	select {
-	case event := <-eventstream:
-		if event == nil {
-			t.Fatal("nil event received")
-		}
-		t.Logf("received: %s", event)
-		if event.Name != name || event.Mask != mask {
-			t.Fatal("did not receive expected event")
-		}
-	case <-time.After(1 * time.Second):
-		t.Fatal("timed out waiting for event")
-	}
-}
-
-func TestNotifyEvents(t *testing.T) {
-	watcher, err := NewWatcher()
-	if err != nil {
-		t.Fatalf("NewWatcher() failed: %s", err)
-	}
-
-	testDir := "TestNotifyEvents.testdirectory"
-	testFile := testDir + "/TestNotifyEvents.testfile"
-	testFile2 := testFile + ".new"
-	const mask = FS_ALL_EVENTS & ^(FS_ATTRIB|FS_CLOSE) | FS_IGNORED
-
-	// Add a watch for testDir
-	os.RemoveAll(testDir)
-	if err = os.Mkdir(testDir, 0777); err != nil {
-		t.Fatalf("Failed to create test directory: %s", err)
-	}
-	defer os.RemoveAll(testDir)
-	err = watcher.AddWatch(testDir, mask)
-	if err != nil {
-		t.Fatalf("Watcher.Watch() failed: %s", err)
-	}
-
-	// Receive errors on the error channel on a separate goroutine
-	go func() {
-		for err := range watcher.Error {
-			t.Fatalf("error received: %s", err)
-		}
-	}()
-
-	// Create a file
-	file, err := os.Create(testFile)
-	if err != nil {
-		t.Fatalf("creating test file failed: %s", err)
-	}
-	expect(t, watcher.Event, testFile, FS_CREATE)
-
-	err = watcher.AddWatch(testFile, mask)
-	if err != nil {
-		t.Fatalf("Watcher.Watch() failed: %s", err)
-	}
-
-	if _, err = file.WriteString("hello, world"); err != nil {
-		t.Fatalf("failed to write to test file: %s", err)
-	}
-	if err = file.Close(); err != nil {
-		t.Fatalf("failed to close test file: %s", err)
-	}
-	expect(t, watcher.Event, testFile, FS_MODIFY)
-	expect(t, watcher.Event, testFile, FS_MODIFY)
-
-	if err = os.Rename(testFile, testFile2); err != nil {
-		t.Fatalf("failed to rename test file: %s", err)
-	}
-	expect(t, watcher.Event, testFile, FS_MOVED_FROM)
-	expect(t, watcher.Event, testFile2, FS_MOVED_TO)
-	expect(t, watcher.Event, testFile, FS_MOVE_SELF)
-
-	if err = os.RemoveAll(testDir); err != nil {
-		t.Fatalf("failed to remove test directory: %s", err)
-	}
-	expect(t, watcher.Event, testFile2, FS_DELETE_SELF)
-	expect(t, watcher.Event, testFile2, FS_IGNORED)
-	expect(t, watcher.Event, testFile2, FS_DELETE)
-	expect(t, watcher.Event, testDir, FS_DELETE_SELF)
-	expect(t, watcher.Event, testDir, FS_IGNORED)
-
-	t.Log("calling Close()")
-	if err = watcher.Close(); err != nil {
-		t.Fatalf("failed to close watcher: %s", err)
-	}
-}
-
-func TestNotifyClose(t *testing.T) {
-	watcher, _ := NewWatcher()
-	watcher.Close()
-
-	var done int32
-	go func() {
-		watcher.Close()
-		atomic.StoreInt32(&done, 1)
-	}()
-
-	time.Sleep(50 * time.Millisecond)
-	if atomic.LoadInt32(&done) == 0 {
-		t.Fatal("double Close() test failed: second Close() call didn't return")
-	}
-
-	dir, err := ioutil.TempDir("", "wininotify")
-	if err != nil {
-		t.Fatalf("TempDir failed: %s", err)
-	}
-	defer os.RemoveAll(dir)
-
-	err = watcher.Watch(dir)
-	if err == nil {
-		t.Fatal("expected error on Watch() after Close(), got nil")
-	}
-}