aboutsummaryrefslogtreecommitdiffhomepage
path: root/system/output_test.go
diff options
context:
space:
mode:
authorOphestra <cat@gensokyo.uk>2025-08-30 22:49:12 +0900
committerOphestra <cat@gensokyo.uk>2025-08-30 22:49:12 +0900
commitf5abce9df5727904a1daa246025a87c4bfe62553 (patch)
tree7e8c29b83e97532954dee8adb5f86962eaebca34 /system/output_test.go
parentddb003e39b64c2417cabc291383d99b4ad64ac8f (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.go189
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) }