All checks were successful
Test / Create distribution (push) Successful in 1m17s
Test / Sandbox (push) Successful in 3m13s
Test / Hakurei (push) Successful in 4m18s
Test / ShareFS (push) Successful in 4m24s
Test / Sandbox (race detector) (push) Successful in 5m35s
Test / Hakurei (race detector) (push) Successful in 6m42s
Test / Flake checks (push) Successful in 1m25s
There are many subtleties when recovering from errors in the event loop, and coldboot requires internals to drain the receive buffer as synthetic uevents are being arranged. Signed-off-by: Ophestra <cat@gensokyo.uk>
258 lines
5.3 KiB
Go
258 lines
5.3 KiB
Go
package uevent_test
|
|
|
|
import (
|
|
"context"
|
|
"encoding"
|
|
"os"
|
|
"reflect"
|
|
"sync"
|
|
"syscall"
|
|
"testing"
|
|
"time"
|
|
|
|
"hakurei.app/fhs"
|
|
"hakurei.app/internal/uevent"
|
|
)
|
|
|
|
// adeT sets up a parallel subtest for a textual appender/decoder/encoder.
|
|
func adeT[V any, S interface {
|
|
encoding.TextAppender
|
|
encoding.TextMarshaler
|
|
encoding.TextUnmarshaler
|
|
|
|
*V
|
|
}](t *testing.T, name string, v V, want string, wantErr, wantErrE error) {
|
|
t.Helper()
|
|
f := func(t *testing.T) {
|
|
if name != "" {
|
|
t.Parallel()
|
|
}
|
|
t.Helper()
|
|
|
|
t.Run("decode", func(t *testing.T) {
|
|
t.Parallel()
|
|
t.Helper()
|
|
|
|
var got V
|
|
if err := S(&got).UnmarshalText([]byte(want)); !reflect.DeepEqual(err, wantErr) {
|
|
t.Fatalf("UnmarshalText: error = %v, want %v", err, wantErr)
|
|
}
|
|
if wantErr != nil {
|
|
return
|
|
}
|
|
|
|
if !reflect.DeepEqual(&got, &v) {
|
|
t.Errorf("UnmarshalText: %#v, want %#v", got, v)
|
|
}
|
|
})
|
|
|
|
t.Run("encode", func(t *testing.T) {
|
|
t.Parallel()
|
|
t.Helper()
|
|
|
|
if got, err := S(&v).MarshalText(); !reflect.DeepEqual(err, wantErrE) {
|
|
t.Fatalf("MarshalText: error = %v, want %v", err, wantErrE)
|
|
} else if err == nil && string(got) != want {
|
|
t.Errorf("MarshalText: %q, want %q", string(got), want)
|
|
}
|
|
})
|
|
}
|
|
if name != "" {
|
|
t.Run(name, f)
|
|
} else {
|
|
f(t)
|
|
}
|
|
}
|
|
|
|
// adeT sets up a binary subtest for a textual appender/decoder/encoder.
|
|
func adeB[V any, S interface {
|
|
encoding.BinaryAppender
|
|
encoding.BinaryMarshaler
|
|
encoding.BinaryUnmarshaler
|
|
|
|
*V
|
|
}](t *testing.T, name string, v V, want string, wantErr, wantErrE error) {
|
|
t.Helper()
|
|
f := func(t *testing.T) {
|
|
if name != "" {
|
|
t.Parallel()
|
|
}
|
|
t.Helper()
|
|
|
|
t.Run("decode", func(t *testing.T) {
|
|
t.Parallel()
|
|
t.Helper()
|
|
|
|
var got V
|
|
if err := S(&got).UnmarshalBinary([]byte(want)); !reflect.DeepEqual(err, wantErr) {
|
|
t.Fatalf("UnmarshalBinary: error = %v, want %v", err, wantErr)
|
|
}
|
|
if wantErr != nil {
|
|
return
|
|
}
|
|
|
|
if !reflect.DeepEqual(&got, &v) {
|
|
t.Errorf("UnmarshalBinary: %#v, want %#v", got, v)
|
|
}
|
|
})
|
|
|
|
t.Run("encode", func(t *testing.T) {
|
|
t.Parallel()
|
|
t.Helper()
|
|
|
|
if got, err := S(&v).MarshalBinary(); !reflect.DeepEqual(err, wantErrE) {
|
|
t.Fatalf("MarshalBinary: error = %v, want %v", err, wantErrE)
|
|
} else if err == nil && string(got) != want {
|
|
t.Errorf("MarshalBinary: %q, want %q", string(got), want)
|
|
}
|
|
})
|
|
}
|
|
if name != "" {
|
|
t.Run(name, f)
|
|
} else {
|
|
f(t)
|
|
}
|
|
}
|
|
|
|
func TestDialConsume(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
c, err := uevent.Dial(0)
|
|
if err != nil {
|
|
t.Fatalf("Dial: error = %v", err)
|
|
}
|
|
t.Cleanup(func() {
|
|
if closeErr := c.Close(); closeErr != nil {
|
|
t.Fatal(err)
|
|
}
|
|
})
|
|
|
|
// check kernel-assigned port id
|
|
c0, err0 := uevent.Dial(0)
|
|
if err0 != nil {
|
|
t.Fatalf("Dial: error = %v", err)
|
|
}
|
|
t.Cleanup(func() {
|
|
if closeErr := c0.Close(); closeErr != nil {
|
|
t.Fatal(closeErr)
|
|
}
|
|
})
|
|
|
|
var wg sync.WaitGroup
|
|
done := make(chan struct{})
|
|
events := make(chan *uevent.Message, 1<<10)
|
|
go func() {
|
|
defer close(done)
|
|
for msg := range events {
|
|
t.Log(msg)
|
|
}
|
|
}()
|
|
t.Cleanup(func() {
|
|
wg.Wait()
|
|
close(events)
|
|
<-done
|
|
})
|
|
|
|
ctx, cancel := context.WithCancel(t.Context())
|
|
defer cancel()
|
|
|
|
consume := func(c *uevent.Conn, ctx context.Context) error {
|
|
return c.Consume(ctx, fhs.Sys, events, false, func(path string) {
|
|
t.Log("coldboot visited", path)
|
|
}, func(err error) bool {
|
|
t.Log(err)
|
|
_, ok := err.(uevent.NeedsColdboot)
|
|
return !ok
|
|
}, nil)
|
|
}
|
|
|
|
wg.Go(func() {
|
|
if err = consume(c, ctx); err != context.Canceled {
|
|
panic(err)
|
|
}
|
|
})
|
|
wg.Go(func() {
|
|
if err0 = consume(c0, ctx); err0 != context.Canceled {
|
|
panic(err0)
|
|
}
|
|
})
|
|
|
|
if testing.Verbose() {
|
|
if d, perr := time.ParseDuration(os.Getenv(
|
|
"ROSA_UEVENT_TEST_DURATION",
|
|
)); perr != nil {
|
|
t.Logf("skipping long test: error = %v", perr)
|
|
} else {
|
|
time.Sleep(d)
|
|
}
|
|
}
|
|
cancel()
|
|
wg.Wait()
|
|
|
|
ctx, cancel = context.WithCancel(t.Context())
|
|
defer cancel()
|
|
|
|
var errs [2]error
|
|
exclExit := make(chan struct{})
|
|
wg.Go(func() {
|
|
defer func() { exclExit <- struct{}{} }()
|
|
errs[0] = consume(c, ctx)
|
|
})
|
|
wg.Go(func() {
|
|
defer func() { exclExit <- struct{}{} }()
|
|
errs[1] = consume(c, ctx)
|
|
})
|
|
<-exclExit
|
|
cancel()
|
|
<-exclExit
|
|
if errs[0] != syscall.EAGAIN && errs[1] != syscall.EAGAIN {
|
|
t.Fatalf("enterExcl: err0 = %v, err1 = %v", errs[0], errs[1])
|
|
}
|
|
}
|
|
|
|
func TestErrors(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
testCases := []struct {
|
|
name string
|
|
err error
|
|
want string
|
|
}{
|
|
{"UnsupportedActionError", uevent.UnsupportedActionError("explode"),
|
|
`unsupported kobject_action "explode"`},
|
|
|
|
{"MissingHeaderError", uevent.MissingHeaderError("move"),
|
|
`message "move" has no header`},
|
|
|
|
{"MessageError MErrorKindHeaderSep", &uevent.MessageError{
|
|
Data: "move\x00",
|
|
Section: "move",
|
|
Kind: uevent.MErrorKindHeaderSep,
|
|
}, `header "move" missing separator`},
|
|
|
|
{"MessageError MErrorKindFinalNUL", &uevent.MessageError{
|
|
Data: "move\x00truncated",
|
|
Section: "truncated",
|
|
Kind: uevent.MErrorKindFinalNUL,
|
|
}, `entry "truncated" missing NUL`},
|
|
|
|
{"MessageError bad", &uevent.MessageError{
|
|
Data: "\x00",
|
|
Kind: 0xbad,
|
|
}, `section "" is invalid`},
|
|
|
|
{"BadPortError", &uevent.BadPortError{
|
|
Pid: 1,
|
|
}, "unexpected message from port id 1 on NETLINK_KOBJECT_UEVENT"},
|
|
}
|
|
for _, tc := range testCases {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
if got := tc.err.Error(); got != tc.want {
|
|
t.Errorf("Error: %q, want %q", got, tc.want)
|
|
}
|
|
})
|
|
}
|
|
}
|