All checks were successful
Test / Create distribution (push) Successful in 1m3s
Test / Sandbox (push) Successful in 2m42s
Test / Hakurei (push) Successful in 3m49s
Test / ShareFS (push) Successful in 3m47s
Test / Sandbox (race detector) (push) Successful in 5m12s
Test / Hakurei (race detector) (push) Successful in 6m20s
Test / Flake checks (push) Successful in 1m20s
This enables rejection of non-coldboot synthetic events. Signed-off-by: Ophestra <cat@gensokyo.uk>
314 lines
7.0 KiB
Go
314 lines
7.0 KiB
Go
package uevent_test
|
|
|
|
import (
|
|
"context"
|
|
"encoding"
|
|
"encoding/hex"
|
|
"os"
|
|
"reflect"
|
|
"strings"
|
|
"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()
|
|
|
|
noEncode := strings.HasSuffix(name, "\x00")
|
|
if noEncode {
|
|
name = name[:len(name)-1]
|
|
}
|
|
|
|
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)
|
|
}
|
|
})
|
|
|
|
if noEncode {
|
|
return
|
|
}
|
|
|
|
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 TestUUID(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
adeT(t, "sample", uevent.UUID{
|
|
0xfe, 0x4d, 0x7c, 0x9d,
|
|
0xb8, 0xc6,
|
|
0x4a, 0x70,
|
|
0x9e, 0xf1,
|
|
0x3d, 0x8a, 0x58, 0xd1, 0x8e, 0xed,
|
|
}, "fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed", nil, nil)
|
|
|
|
adeT(t, "auto\x00", uevent.UUID{}, "0", uevent.ErrAutoUUID, nil)
|
|
adeT(t, "short\x00", uevent.UUID{}, "1", uevent.UUIDSizeError(1), nil)
|
|
|
|
adeT(t, "bad0\x00", uevent.UUID{}, "fe4d7c9\x00-b8c6-4a70-9ef1-3d8a58d18eed",
|
|
hex.InvalidByteError(0), nil)
|
|
adeT(t, "sep0\x00", uevent.UUID{}, "fe4d7c9d\x00b8c6-4a70-9ef1-3d8a58d18eed",
|
|
uevent.UUIDSeparatorError(0), nil)
|
|
|
|
adeT(t, "bad1\x00", uevent.UUID{}, "fe4d7c9d-b8c\x00-4a70-9ef1-3d8a58d18eed",
|
|
hex.InvalidByteError(0), nil)
|
|
adeT(t, "sep1\x00", uevent.UUID{}, "fe4d7c9d-b8c6\x004a70-9ef1-3d8a58d18eed",
|
|
uevent.UUIDSeparatorError(0), nil)
|
|
|
|
adeT(t, "bad2\x00", uevent.UUID{}, "fe4d7c9d-b8c6-4a7\x00-9ef1-3d8a58d18eed",
|
|
hex.InvalidByteError(0), nil)
|
|
adeT(t, "sep2\x00", uevent.UUID{}, "fe4d7c9d-b8c6-4a70\x009ef1-3d8a58d18eed",
|
|
uevent.UUIDSeparatorError(0), nil)
|
|
|
|
adeT(t, "bad3\x00", uevent.UUID{}, "fe4d7c9d-b8c6-4a70-9ef\x00-3d8a58d18eed",
|
|
hex.InvalidByteError(0), nil)
|
|
adeT(t, "sep3\x00", uevent.UUID{}, "fe4d7c9d-b8c6-4a70-9ef1\x003d8a58d18eed",
|
|
uevent.UUIDSeparatorError(0), nil)
|
|
|
|
adeT(t, "bad4\x00", uevent.UUID{}, "fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18ee\x00",
|
|
hex.InvalidByteError(0), nil)
|
|
|
|
}
|
|
|
|
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, nil, 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"},
|
|
|
|
{"UUIDSizeError", uevent.UUIDSizeError(0xbad),
|
|
"got 2989 bytes instead of 36"},
|
|
{"UUIDSeparatorError", uevent.UUIDSeparatorError(0xfd),
|
|
"invalid UUID separator: U+00FD 'ý'"},
|
|
}
|
|
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)
|
|
}
|
|
})
|
|
}
|
|
}
|