aboutsummaryrefslogtreecommitdiffhomepage
path: root/internal/uevent/coldboot_test.go
diff options
context:
space:
mode:
authorOphestra <cat@gensokyo.uk>2026-03-30 21:20:42 +0900
committerOphestra <cat@gensokyo.uk>2026-03-30 23:01:08 +0900
commitf03c0fb249ea8787ae8ec086970eb089109ebf6f (patch)
tree5e89ba203112c5e3ca984da96b9dc60be19ade2a /internal/uevent/coldboot_test.go
parenta6600be34ad812ff13c89f45c3cadaac6d994e67 (diff)
internal/uevent: synthetic events for coldboot
This causes the kernel to regenerate events that happened before earlyinit started. Signed-off-by: Ophestra <cat@gensokyo.uk>
Diffstat (limited to 'internal/uevent/coldboot_test.go')
-rw-r--r--internal/uevent/coldboot_test.go227
1 files changed, 227 insertions, 0 deletions
diff --git a/internal/uevent/coldboot_test.go b/internal/uevent/coldboot_test.go
new file mode 100644
index 00000000..e3491e64
--- /dev/null
+++ b/internal/uevent/coldboot_test.go
@@ -0,0 +1,227 @@
+package uevent_test
+
+import (
+ "context"
+ "os"
+ "path/filepath"
+ "reflect"
+ "slices"
+ "sync"
+ "syscall"
+ "testing"
+
+ "hakurei.app/check"
+ "hakurei.app/internal/pkg"
+ "hakurei.app/internal/uevent"
+)
+
+func TestColdboot(t *testing.T) {
+ t.Parallel()
+
+ d := t.TempDir()
+ if err := os.Chmod(d, 0700); err != nil {
+ t.Fatal(err)
+ }
+
+ for _, s := range []string{
+ "devices",
+ "devices/sub",
+ "devices/empty",
+ "block",
+ } {
+ if err := os.MkdirAll(filepath.Join(d, s), 0700); err != nil {
+ t.Fatal(err)
+ }
+ }
+
+ for _, f := range [][2]string{
+ {"devices/uevent", ""},
+ {"devices/sub/uevent", ""},
+ {"block/uevent", ""},
+ } {
+ if err := os.WriteFile(
+ filepath.Join(d, f[0]),
+ []byte(f[1]),
+ 0600,
+ ); err != nil {
+ t.Fatal(err)
+ }
+ }
+
+ var wg sync.WaitGroup
+ defer wg.Wait()
+
+ visited := make(chan string)
+ var got []string
+ wg.Go(func() {
+ for path := range visited {
+ got = append(got, path)
+ }
+ })
+
+ err := uevent.Coldboot(t.Context(), d, visited, func(err error) error {
+ t.Errorf("handleWalkErr: %v", err)
+ return err
+ })
+ close(visited)
+ if err != nil {
+ t.Fatalf("Coldboot: error = %v", err)
+ }
+
+ wg.Wait()
+ want := []string{
+ "devices/sub/uevent",
+ "devices/uevent",
+ }
+ for i, rel := range want {
+ want[i] = filepath.Join(d, rel)
+ }
+ if !slices.Equal(got, want) {
+ t.Errorf("Coldboot: %#v, want %#v", got, want)
+ }
+
+ var checksum pkg.Checksum
+ if err = pkg.HashDir(&checksum, check.MustAbs(d)); err != nil {
+ t.Fatalf("HashDir: error = %v", err)
+ }
+
+ wantChecksum := pkg.MustDecode("mEy_Lf5KotThm7OwMx7yTKZh5HCCyaB41pVAvI9uDMgVQFM91iosBLYsRm8bDsX8")
+ if checksum != wantChecksum {
+ t.Errorf(
+ "Coldboot: checksum = %s, want %s",
+ pkg.Encode(checksum),
+ pkg.Encode(wantChecksum),
+ )
+ }
+}
+
+func TestColdbootError(t *testing.T) {
+ t.Parallel()
+
+ testCases := []struct {
+ name string
+ dF func(t *testing.T, d string) (wantErr error)
+ vF func(<-chan string, context.Context, context.CancelFunc)
+ hF func(d string, err error) error
+ }{
+ {"walk", func(t *testing.T, d string) (wantErr error) {
+ wantErr = &os.PathError{
+ Op: "open",
+ Path: filepath.Join(d, "devices"),
+ Err: syscall.EACCES,
+ }
+ if err := os.Mkdir(filepath.Join(d, "devices"), 0); err != nil {
+ t.Fatal(err)
+ }
+ return
+ }, nil, nil},
+
+ {"write", func(t *testing.T, d string) (wantErr error) {
+ wantErr = &os.PathError{
+ Op: "open",
+ Path: filepath.Join(d, "devices/uevent"),
+ Err: syscall.EACCES,
+ }
+ if err := os.Mkdir(filepath.Join(d, "devices"), 0700); err != nil {
+ t.Fatal(err)
+ } else if err = os.WriteFile(filepath.Join(d, "devices/uevent"), nil, 0); err != nil {
+ t.Fatal(err)
+ }
+ return
+ }, nil, nil},
+
+ {"deref", func(t *testing.T, d string) (wantErr error) {
+ if err := os.Mkdir(filepath.Join(d, "devices"), 0700); err != nil {
+ t.Fatal(err)
+ } else if err = os.Symlink("/proc/nonexistent", filepath.Join(d, "devices/uevent")); err != nil {
+ t.Fatal(err)
+ }
+ return
+ }, nil, nil},
+
+ {"deref handle", func(t *testing.T, d string) (wantErr error) {
+ if err := os.Mkdir(filepath.Join(d, "devices"), 0700); err != nil {
+ t.Fatal(err)
+ } else if err = os.Symlink("/proc/nonexistent", filepath.Join(d, "devices/uevent")); err != nil {
+ t.Fatal(err)
+ }
+ return
+ }, nil, func(d string, err error) error {
+ if reflect.DeepEqual(err, &os.PathError{
+ Op: "open",
+ Path: filepath.Join(d, "devices/uevent"),
+ Err: syscall.ENOENT,
+ }) {
+ return nil
+ }
+ return err
+ }},
+
+ {"cancel early", func(t *testing.T, d string) (wantErr error) {
+ wantErr = context.Canceled
+ if err := os.Mkdir(filepath.Join(d, "devices"), 0700); err != nil {
+ t.Fatal(err)
+ }
+ return
+ }, func(visited <-chan string, ctx context.Context, cancel context.CancelFunc) {
+ if visited == nil {
+ cancel()
+ }
+ return
+ }, nil},
+
+ {"cancel", func(t *testing.T, d string) (wantErr error) {
+ wantErr = context.Canceled
+ if err := os.Mkdir(filepath.Join(d, "devices"), 0700); err != nil {
+ t.Fatal(err)
+ } else if err = os.WriteFile(filepath.Join(d, "devices/uevent"), nil, 0600); err != nil {
+ t.Fatal(err)
+ } else if err = os.Mkdir(filepath.Join(d, "devices/sub"), 0700); err != nil {
+ t.Fatal(err)
+ } else if err = os.WriteFile(filepath.Join(d, "devices/sub/uevent"), nil, 0600); err != nil {
+ t.Fatal(err)
+ }
+ return
+ }, func(visited <-chan string, ctx context.Context, cancel context.CancelFunc) {
+ if visited == nil {
+ return
+ }
+ <-visited
+ cancel()
+ return
+ }, nil},
+ }
+ for _, tc := range testCases {
+ t.Run(tc.name, func(t *testing.T) {
+ t.Parallel()
+
+ d := t.TempDir()
+ wantErr := tc.dF(t, d)
+
+ var wg sync.WaitGroup
+ defer wg.Wait()
+
+ ctx, cancel := context.WithCancel(t.Context())
+ defer cancel()
+
+ var visited chan string
+ if tc.vF != nil {
+ tc.vF(nil, ctx, cancel)
+ visited = make(chan string)
+ defer close(visited)
+ wg.Go(func() { tc.vF(visited, ctx, cancel) })
+ }
+
+ var handleWalkErr func(error) error
+ if tc.hF != nil {
+ handleWalkErr = func(err error) error {
+ return tc.hF(d, err)
+ }
+ }
+
+ if err := uevent.Coldboot(ctx, d, visited, handleWalkErr); !reflect.DeepEqual(err, wantErr) {
+ t.Errorf("Coldboot: error = %v, want %v", err, wantErr)
+ }
+ })
+ }
+}