diff options
| author | Ophestra <cat@gensokyo.uk> | 2025-08-30 22:49:12 +0900 |
|---|---|---|
| committer | Ophestra <cat@gensokyo.uk> | 2025-08-30 22:49:12 +0900 |
| commit | f5abce9df5727904a1daa246025a87c4bfe62553 (patch) | |
| tree | 7e8c29b83e97532954dee8adb5f86962eaebca34 /system/output_test.go | |
| parent | ddb003e39b64c2417cabc291383d99b4ad64ac8f (diff) | |
system: wrap op errors
This passes more information allowing for better error handling. This eliminates generic WrapErr from system.
Signed-off-by: Ophestra <cat@gensokyo.uk>
Diffstat (limited to 'system/output_test.go')
| -rw-r--r-- | system/output_test.go | 189 |
1 files changed, 189 insertions, 0 deletions
diff --git a/system/output_test.go b/system/output_test.go new file mode 100644 index 00000000..5ad219b7 --- /dev/null +++ b/system/output_test.go @@ -0,0 +1,189 @@ +package system + +import ( + "errors" + "net" + "os" + "reflect" + "syscall" + "testing" + + "hakurei.app/container" + "hakurei.app/internal/hlog" +) + +func TestOpError(t *testing.T) { + testCases := []struct { + name string + err error + s string + is error + isF error + }{ + {"message", newOpErrorMessage("dbus", ErrDBusConfig, + "attempted to create message bus proxy args without session bus config", false), + "attempted to create message bus proxy args without session bus config", + ErrDBusConfig, syscall.ENOTRECOVERABLE}, + + {"apply", newOpError("tmpfile", syscall.EBADE, false), + "cannot apply tmpfile: invalid exchange", + syscall.EBADE, syscall.EBADF}, + + {"revert", newOpError("wayland", syscall.EBADF, true), + "cannot revert wayland: bad file descriptor", + syscall.EBADF, syscall.EBADE}, + + {"path", newOpError("tmpfile", &os.PathError{Op: "stat", Path: "/run/dbus", Err: syscall.EISDIR}, false), + "stat /run/dbus: is a directory", + syscall.EISDIR, syscall.ENOTDIR}, + + {"net", newOpError("wayland", &net.OpError{Op: "dial", Net: "unix", Addr: &net.UnixAddr{Name: "/run/user/1000/wayland-1", Net: "unix"}, Err: syscall.ENOENT}, false), + "dial unix /run/user/1000/wayland-1: no such file or directory", + syscall.ENOENT, syscall.EPERM}, + } + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + t.Run("error", func(t *testing.T) { + if got := tc.err.Error(); got != tc.s { + t.Errorf("Error: %q, want %q", got, tc.s) + } + }) + t.Run("is", func(t *testing.T) { + if !errors.Is(tc.err, tc.is) { + t.Error("Is: unexpected false") + } + if errors.Is(tc.err, tc.isF) { + t.Error("Is: unexpected true") + } + }) + }) + } + + t.Run("new", func(t *testing.T) { + if err := newOpError("check", nil, false); err != nil { + t.Errorf("newOpError: %v", err) + } + if err := newOpErrorMessage("check", nil, "", false); err != nil { + t.Errorf("newOpErrorMessage: %v", err) + } + }) +} + +func TestSetOutput(t *testing.T) { + oldmsg := msg + t.Cleanup(func() { msg = oldmsg }) + msg = nil + + t.Run("nil", func(t *testing.T) { + SetOutput(nil) + if _, ok := msg.(*container.DefaultMsg); !ok { + t.Errorf("SetOutput: %#v", msg) + } + }) + + t.Run("hlog", func(t *testing.T) { + SetOutput(hlog.Output{}) + if _, ok := msg.(hlog.Output); !ok { + t.Errorf("SetOutput: %#v", msg) + } + }) + + t.Run("reset", func(t *testing.T) { + SetOutput(nil) + if _, ok := msg.(*container.DefaultMsg); !ok { + t.Errorf("SetOutput: %#v", msg) + } + }) +} + +func TestPrintJoinedError(t *testing.T) { + testCases := []struct { + name string + err error + want [][]any + }{ + {"nil", nil, [][]any{{"not a joined error:", nil}}}, + {"unwrapped", syscall.EINVAL, [][]any{{"not a joined error:", syscall.EINVAL}}}, + {"single", errors.Join(syscall.EINVAL), [][]any{{"invalid argument"}}}, + + {"many", errors.Join(syscall.ENOTRECOVERABLE, syscall.ETIMEDOUT, syscall.EBADFD), [][]any{ + {"state not recoverable"}, + {"connection timed out"}, + {"file descriptor in bad state"}, + }}, + } + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + var got [][]any + printJoinedError(func(v ...any) { got = append(got, v) }, "not a joined error:", tc.err) + if !reflect.DeepEqual(got, tc.want) { + t.Errorf("printJoinedError: %#v, want %#v", got, tc.want) + } + }) + } +} + +type tcOp struct { + et Enablement + path string +} + +// test an instance of the Op interface +func (ptc tcOp) test(t *testing.T, gotOps []Op, wantOps []Op, fn string) { + if len(gotOps) != len(wantOps) { + t.Errorf("%s: inserted %v Ops, want %v", fn, + len(gotOps), len(wantOps)) + return + } + + t.Run("path", func(t *testing.T) { + if len(gotOps) > 0 { + if got := gotOps[0].Path(); got != ptc.path { + t.Errorf("Path() = %q, want %q", + got, ptc.path) + return + } + } + }) + + for i := range gotOps { + o := gotOps[i] + + t.Run("is", func(t *testing.T) { + if !o.Is(o) { + t.Errorf("Is returned false on self") + return + } + if !o.Is(wantOps[i]) { + t.Errorf("%s: inserted %#v, want %#v", + fn, + o, wantOps[i]) + return + } + }) + + t.Run("criteria", func(t *testing.T) { + testCases := []struct { + name string + ec *Criteria + want bool + }{ + {"nil", nil, ptc.et != User}, + {"self", newCriteria(ptc.et), true}, + {"all", newCriteria(EWayland | EX11 | EDBus | EPulse | User | Process), true}, + {"enablements", newCriteria(EWayland | EX11 | EDBus | EPulse), ptc.et != User && ptc.et != Process}, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + if got := tc.ec.hasType(o); got != tc.want { + t.Errorf("hasType: got %v, want %v", + got, tc.want) + } + }) + } + }) + } +} + +func newCriteria(e Enablement) *Criteria { return (*Criteria)(&e) } |
