mirror of
https://github.com/golang/sys.git
synced 2026-01-29 07:02:06 +03:00
Done with: go get go@1.18 go mod tidy go fix ./... Using go1.21.3. Also update code generators to use only the new go:build lines, not the old +build ones. For golang/go#60268. Change-Id: I6aabc42efb6ab3329981100e1db2263aac5e92a6 Reviewed-on: https://go-review.googlesource.com/c/sys/+/534222 Reviewed-by: Ian Lance Taylor <iant@google.com> Reviewed-by: Dmitri Shuralyov <dmitshur@google.com> Auto-Submit: Dmitri Shuralyov <dmitshur@golang.org> LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
270 lines
8.8 KiB
Go
270 lines
8.8 KiB
Go
// 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
|
|
|
|
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)
|
|
}
|
|
}
|
|
|
|
// getOneRetry wraps EventPort.GetOne which in turn wraps a syscall that can be
|
|
// interrupted causing us to receive EINTR.
|
|
// To prevent our tests from flaking, we retry the syscall until it works
|
|
// rather than get unexpected results in our tests.
|
|
func getOneRetry(t *testing.T, p *EventPort, timeout *Timespec) (e *PortEvent, err error) {
|
|
t.Helper()
|
|
for {
|
|
e, err = p.GetOne(timeout)
|
|
if err != EINTR {
|
|
break
|
|
}
|
|
}
|
|
return e, err
|
|
}
|
|
|
|
// getRetry wraps EventPort.Get which in turn wraps a syscall that can be
|
|
// interrupted causing us to receive EINTR.
|
|
// To prevent our tests from flaking, we retry the syscall until it works
|
|
// rather than get unexpected results in our tests.
|
|
func getRetry(t *testing.T, p *EventPort, s []PortEvent, min int, timeout *Timespec) (n int, err error) {
|
|
t.Helper()
|
|
for {
|
|
n, err = p.Get(s, min, timeout)
|
|
if err != EINTR {
|
|
break
|
|
}
|
|
// If we did get EINTR, make sure we got 0 events
|
|
if n != 0 {
|
|
t.Fatalf("EventPort.Get returned events on EINTR.\ngot: %d\nexpected: 0", n)
|
|
}
|
|
}
|
|
return n, err
|
|
}
|
|
|
|
// 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 := getOneRetry(t, port, 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 = getOneRetry(t, port, 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 = getOneRetry(t, port, 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 = getOneRetry(t, port, 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 = getRetry(t, port, 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)
|
|
}
|
|
}
|