summaryrefslogtreecommitdiffstats
path: root/dependencies/pkg/mod/golang.org/x/sys@v0.1.0/unix/syscall_internal_solaris_test.go
blob: a92753a14de706ce460d0e7339c5b91e53ea9953 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
// Copyright 2022 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 solaris
// +build solaris

package unix

import (
	"fmt"
	"os"
	"runtime"
	"testing"
)

func (e *EventPort) checkInternals(t *testing.T, fds, paths, cookies, pending int) {
	t.Helper()
	p, err := e.Pending()
	if err != nil {
		t.Fatalf("failed to query how many events are pending")
	}
	if len(e.fds) != fds || len(e.paths) != paths || len(e.cookies) != cookies || p != pending {
		format := "| fds: %d | paths: %d | cookies: %d | pending: %d |"
		expected := fmt.Sprintf(format, fds, paths, cookies, pending)
		got := fmt.Sprintf(format, len(e.fds), len(e.paths), len(e.cookies), p)
		t.Errorf("Internal state mismatch\nfound:    %s\nexpected: %s", got, expected)
	}
}

// Regression test for DissociatePath returning ENOENT
// This test is intended to create a linear worst
// case scenario of events being associated and
// fired but not consumed before additional
// calls to dissociate and associate happen
// This needs to be an internal test so that
// we can validate the state of the private maps
func TestEventPortDissociateAlreadyGone(t *testing.T) {
	port, err := NewEventPort()
	if err != nil {
		t.Fatalf("failed to create an EventPort")
	}
	defer port.Close()
	dir := t.TempDir()
	tmpfile, err := os.CreateTemp(dir, "eventport")
	if err != nil {
		t.Fatalf("unable to create a tempfile: %v", err)
	}
	path := tmpfile.Name()
	stat, err := os.Stat(path)
	if err != nil {
		t.Fatalf("unexpected failure to Stat file: %v", err)
	}
	err = port.AssociatePath(path, stat, FILE_MODIFIED, "cookie1")
	if err != nil {
		t.Fatalf("unexpected failure associating file: %v", err)
	}
	// We should have 1 path registered and 1 cookie in the jar
	port.checkInternals(t, 0, 1, 1, 0)
	// The path is associated, let's delete it.
	err = os.Remove(path)
	if err != nil {
		t.Fatalf("unexpected failure deleting file: %v", err)
	}
	// The file has been deleted, some sort of pending event is probably
	// queued in the kernel waiting for us to get it AND the kernel is
	// no longer watching for events on it. BUT... Because we haven't
	// consumed the event, this API thinks it's still watched:
	watched := port.PathIsWatched(path)
	if !watched {
		t.Errorf("unexpected result from PathIsWatched")
	}
	// Ok, let's dissociate the file even before reading the event.
	// Oh, ENOENT. I guess it's not associated any more
	err = port.DissociatePath(path)
	if err != ENOENT {
		t.Errorf("unexpected result dissociating a seemingly associated path we know isn't: %v", err)
	}
	// As established by the return value above, this should clearly be false now:
	// Narrator voice: in the first version of this API, it wasn't.
	watched = port.PathIsWatched(path)
	if watched {
		t.Errorf("definitively unwatched file still in the map")
	}
	// We should have nothing registered, but 1 pending event corresponding
	// to the cookie in the jar
	port.checkInternals(t, 0, 0, 1, 1)
	f, err := os.OpenFile(path, os.O_WRONLY|os.O_CREATE, 0666)
	if err != nil {
		t.Fatalf("creating test file failed: %s", err)
	}
	err = f.Close()
	if err != nil {
		t.Fatalf("unexpected failure closing file: %v", err)
	}
	stat, err = os.Stat(path)
	if err != nil {
		t.Fatalf("unexpected failure to Stat file: %v", err)
	}
	c := "cookie2" // c is for cookie, that's good enough for me
	err = port.AssociatePath(path, stat, FILE_MODIFIED, c)
	if err != nil {
		t.Errorf("unexpected failure associating file: %v", err)
	}
	// We should have 1 registered path and its cookie
	// as well as a second cookie corresponding to the pending event
	port.checkInternals(t, 0, 1, 2, 1)

	// Fire another event
	err = os.Remove(path)
	if err != nil {
		t.Fatalf("unexpected failure deleting file: %v", err)
	}
	port.checkInternals(t, 0, 1, 2, 2)
	err = port.DissociatePath(path)
	if err != ENOENT {
		t.Errorf("unexpected result dissociating a seemingly associated path we know isn't: %v", err)
	}
	// By dissociating this path after deletion we ensure that the paths map is now empty
	// If we're not careful we could trigger a nil pointer exception
	port.checkInternals(t, 0, 0, 2, 2)

	f, err = os.OpenFile(path, os.O_WRONLY|os.O_CREATE, 0666)
	if err != nil {
		t.Fatalf("creating test file failed: %s", err)
	}
	err = f.Close()
	if err != nil {
		t.Fatalf("unexpected failure closing file: %v", err)
	}
	stat, err = os.Stat(path)
	if err != nil {
		t.Fatalf("unexpected failure to Stat file: %v", err)
	}
	// Put a seemingly duplicate cookie in the jar to see if we can trigger an incorrect removal from the paths map
	err = port.AssociatePath(path, stat, FILE_MODIFIED, c)
	if err != nil {
		t.Errorf("unexpected failure associating file: %v", err)
	}
	port.checkInternals(t, 0, 1, 3, 2)

	// run the garbage collector so that if we messed up it should be painfully clear
	runtime.GC()

	// Before the fix, this would cause a nil pointer exception
	e, err := port.GetOne(nil)
	if err != nil {
		t.Errorf("failed to get an event: %v", err)
	}
	port.checkInternals(t, 0, 1, 2, 1)
	if e.Cookie != "cookie1" {
		t.Errorf(`expected "cookie1", got "%v"`, e.Cookie)
	}
	// Make sure that a cookie of the same value doesn't cause removal from the paths map incorrectly
	e, err = port.GetOne(nil)
	if err != nil {
		t.Errorf("failed to get an event: %v", err)
	}
	port.checkInternals(t, 0, 1, 1, 0)
	if e.Cookie != "cookie2" {
		t.Errorf(`expected "cookie2", got "%v"`, e.Cookie)
	}

	err = os.Remove(path)
	if err != nil {
		t.Fatalf("unexpected failure deleting file: %v", err)
	}
	// Event has fired, but until processed it should still be in the map
	port.checkInternals(t, 0, 1, 1, 1)
	e, err = port.GetOne(nil)
	if err != nil {
		t.Errorf("failed to get an event: %v", err)
	}
	if e.Cookie != "cookie2" {
		t.Errorf(`expected "cookie2", got "%v"`, e.Cookie)
	}
	// The maps should be empty and there should be no pending events
	port.checkInternals(t, 0, 0, 0, 0)
}

// Regression test for spuriously triggering a panic about memory mismanagement
// that can be triggered by an event processing thread trying to process an event
// after a different thread has already called port.Close().
// Implemented as an internal test so that we can just simulate the Close()
// because if you call close first in the same thread, things work properly
// anyway.
func TestEventPortGetAfterClose(t *testing.T) {
	port, err := NewEventPort()
	if err != nil {
		t.Fatalf("NewEventPort failed: %v", err)
	}
	// Create, associate, and delete 2 files
	for i := 0; i < 2; i++ {
		tmpfile, err := os.CreateTemp("", "eventport")
		if err != nil {
			t.Fatalf("unable to create tempfile: %v", err)
		}
		path := tmpfile.Name()
		stat, err := os.Stat(path)
		if err != nil {
			t.Fatalf("unable to stat tempfile: %v", err)
		}
		err = port.AssociatePath(path, stat, FILE_MODIFIED, nil)
		if err != nil {
			t.Fatalf("unable to AssociatePath tempfile: %v", err)
		}
		err = os.Remove(path)
		if err != nil {
			t.Fatalf("unable to Remove tempfile: %v", err)
		}
	}
	n, err := port.Pending()
	if err != nil {
		t.Errorf("Pending failed: %v", err)
	}
	if n != 2 {
		t.Errorf("expected 2 pending events, got %d", n)
	}
	// Simulate a close from a different thread
	port.fds = nil
	port.paths = nil
	port.cookies = nil
	// Ensure that we get back reasonable errors rather than panic
	_, err = port.GetOne(nil)
	if err == nil || err.Error() != "this EventPort is already closed" {
		t.Errorf("didn't receive expected error of 'this EventPort is already closed'; got: %v", err)
	}
	events := make([]PortEvent, 2)
	n, err = port.Get(events, 1, nil)
	if n != 0 {
		t.Errorf("expected to get back 0 events, got %d", n)
	}
	if err == nil || err.Error() != "this EventPort is already closed" {
		t.Errorf("didn't receive expected error of 'this EventPort is already closed'; got: %v", err)
	}
}