aboutsummaryrefslogtreecommitdiffhomepage
path: root/system
diff options
context:
space:
mode:
authorOphestra <cat@gensokyo.uk>2025-09-11 02:02:31 +0900
committerOphestra <cat@gensokyo.uk>2025-09-11 02:02:31 +0900
commit6265aea73a5f4933f4837bc1298c22fcd39b6f4f (patch)
tree6e6f3c8c642747c761df40d1bb387e443424daa7 /system
parentc8a0effe908c22c350c5c492b0e5bbeefc8749f1 (diff)
system: partial I inherit dispatcher
This enables I struct methods to be checked. Signed-off-by: Ophestra <cat@gensokyo.uk>
Diffstat (limited to 'system')
-rw-r--r--system/system.go6
-rw-r--r--system/system_test.go147
2 files changed, 150 insertions, 3 deletions
diff --git a/system/system.go b/system/system.go
index 134e7cee..ea6d9d58 100644
--- a/system/system.go
+++ b/system/system.go
@@ -4,7 +4,6 @@ package system
import (
"context"
"errors"
- "log"
"strings"
)
@@ -116,14 +115,15 @@ func (sys *I) Commit() error {
sys.committed = true
sp := New(sys.ctx, sys.uid)
+ sp.syscallDispatcher = sys.syscallDispatcher
sp.ops = make([]Op, 0, len(sys.ops)) // prevent copies during commits
defer func() {
// sp is set to nil when all ops are applied
if sp != nil {
// rollback partial commit
- msg.Verbosef("commit faulted after %d ops, rolling back partial commit", len(sp.ops))
+ sys.verbosef("commit faulted after %d ops, rolling back partial commit", len(sp.ops))
if err := sp.Revert(nil); err != nil {
- printJoinedError(log.Println, "cannot revert partial commit:", err)
+ printJoinedError(sys.println, "cannot revert partial commit:", err)
}
}
}()
diff --git a/system/system_test.go b/system/system_test.go
index 3ab6c13d..12eb6fe1 100644
--- a/system/system_test.go
+++ b/system/system_test.go
@@ -1,8 +1,15 @@
package system
import (
+ "errors"
+ "os"
+ "reflect"
+ "slices"
"strconv"
"testing"
+
+ "hakurei.app/container/stub"
+ "hakurei.app/system/internal/xcb"
)
func TestCriteria(t *testing.T) {
@@ -153,3 +160,143 @@ func TestEqual(t *testing.T) {
})
}
}
+
+func TestCommitRevert(t *testing.T) {
+ testCases := []struct {
+ name string
+ f func(sys *I)
+ ec Enablement
+
+ commit []stub.Call
+ wantErrCommit error
+
+ revert []stub.Call
+ wantErrRevert error
+ }{
+ {"apply xhost partial mkdir", func(sys *I) {
+ sys.
+ Ephemeral(Process, "/tmp/hakurei.0/f2f3bcd492d0266438fa9bf164fe90d9", 0711).
+ ChangeHosts("chronos")
+ }, 0xff, []stub.Call{
+ call("verbose", stub.ExpectArgs{[]any{"ensuring directory", &mkdirOp{Process, "/tmp/hakurei.0/f2f3bcd492d0266438fa9bf164fe90d9", 0711, true}}}, nil, nil),
+ call("mkdir", stub.ExpectArgs{"/tmp/hakurei.0/f2f3bcd492d0266438fa9bf164fe90d9", os.FileMode(0711)}, nil, nil),
+ call("verbosef", stub.ExpectArgs{"inserting entry %s to X11", []any{xhostOp("chronos")}}, nil, nil),
+ call("xcbChangeHosts", stub.ExpectArgs{xcb.HostMode(xcb.HostModeInsert), xcb.Family(xcb.FamilyServerInterpreted), "localuser\x00chronos"}, nil, stub.UniqueError(2)),
+ call("verbosef", stub.ExpectArgs{"commit faulted after %d ops, rolling back partial commit", []any{1}}, nil, nil),
+ call("verbose", stub.ExpectArgs{[]any{"destroying ephemeral directory", &mkdirOp{Process, "/tmp/hakurei.0/f2f3bcd492d0266438fa9bf164fe90d9", 0711, true}}}, nil, nil),
+ call("remove", stub.ExpectArgs{"/tmp/hakurei.0/f2f3bcd492d0266438fa9bf164fe90d9"}, nil, stub.UniqueError(3)),
+ call("println", stub.ExpectArgs{[]any{"cannot revert mkdir: unique error 3 injected by the test suite"}}, nil, nil),
+ }, &OpError{Op: "xhost", Err: stub.UniqueError(2)}, nil, nil},
+
+ {"apply xhost", func(sys *I) {
+ sys.
+ Ephemeral(Process, "/tmp/hakurei.0/f2f3bcd492d0266438fa9bf164fe90d9", 0711).
+ ChangeHosts("chronos")
+ }, 0xff, []stub.Call{
+ call("verbose", stub.ExpectArgs{[]any{"ensuring directory", &mkdirOp{Process, "/tmp/hakurei.0/f2f3bcd492d0266438fa9bf164fe90d9", 0711, true}}}, nil, nil),
+ call("mkdir", stub.ExpectArgs{"/tmp/hakurei.0/f2f3bcd492d0266438fa9bf164fe90d9", os.FileMode(0711)}, nil, nil),
+ call("verbosef", stub.ExpectArgs{"inserting entry %s to X11", []any{xhostOp("chronos")}}, nil, nil),
+ call("xcbChangeHosts", stub.ExpectArgs{xcb.HostMode(xcb.HostModeInsert), xcb.Family(xcb.FamilyServerInterpreted), "localuser\x00chronos"}, nil, stub.UniqueError(2)),
+ call("verbosef", stub.ExpectArgs{"commit faulted after %d ops, rolling back partial commit", []any{1}}, nil, nil),
+ call("verbose", stub.ExpectArgs{[]any{"destroying ephemeral directory", &mkdirOp{Process, "/tmp/hakurei.0/f2f3bcd492d0266438fa9bf164fe90d9", 0711, true}}}, nil, nil),
+ call("remove", stub.ExpectArgs{"/tmp/hakurei.0/f2f3bcd492d0266438fa9bf164fe90d9"}, nil, nil),
+ }, &OpError{Op: "xhost", Err: stub.UniqueError(2)}, nil, nil},
+
+ {"revert multi", func(sys *I) {
+ sys.
+ Ephemeral(Process, "/tmp/hakurei.0/f2f3bcd492d0266438fa9bf164fe90d9", 0711).
+ ChangeHosts("chronos")
+ }, 0xff, []stub.Call{
+ call("verbose", stub.ExpectArgs{[]any{"ensuring directory", &mkdirOp{Process, "/tmp/hakurei.0/f2f3bcd492d0266438fa9bf164fe90d9", 0711, true}}}, nil, nil),
+ call("mkdir", stub.ExpectArgs{"/tmp/hakurei.0/f2f3bcd492d0266438fa9bf164fe90d9", os.FileMode(0711)}, nil, nil),
+ call("verbosef", stub.ExpectArgs{"inserting entry %s to X11", []any{xhostOp("chronos")}}, nil, nil),
+ call("xcbChangeHosts", stub.ExpectArgs{xcb.HostMode(xcb.HostModeInsert), xcb.Family(xcb.FamilyServerInterpreted), "localuser\x00chronos"}, nil, nil),
+ }, nil, []stub.Call{
+ call("verbosef", stub.ExpectArgs{"deleting entry %s from X11", []any{xhostOp("chronos")}}, nil, nil),
+ call("xcbChangeHosts", stub.ExpectArgs{xcb.HostMode(xcb.HostModeDelete), xcb.Family(xcb.FamilyServerInterpreted), "localuser\x00chronos"}, nil, stub.UniqueError(1)),
+ call("verbose", stub.ExpectArgs{[]any{"destroying ephemeral directory", &mkdirOp{Process, "/tmp/hakurei.0/f2f3bcd492d0266438fa9bf164fe90d9", 0711, true}}}, nil, nil),
+ call("remove", stub.ExpectArgs{"/tmp/hakurei.0/f2f3bcd492d0266438fa9bf164fe90d9"}, nil, stub.UniqueError(0)),
+ }, errors.Join(
+ &OpError{Op: "xhost", Err: stub.UniqueError(1), Revert: true},
+ &OpError{Op: "mkdir", Err: stub.UniqueError(0), Revert: true})},
+
+ {"success", func(sys *I) {
+ sys.
+ Ephemeral(Process, "/tmp/hakurei.0/f2f3bcd492d0266438fa9bf164fe90d9", 0711).
+ ChangeHosts("chronos")
+ }, 0xff, []stub.Call{
+ call("verbose", stub.ExpectArgs{[]any{"ensuring directory", &mkdirOp{Process, "/tmp/hakurei.0/f2f3bcd492d0266438fa9bf164fe90d9", 0711, true}}}, nil, nil),
+ call("mkdir", stub.ExpectArgs{"/tmp/hakurei.0/f2f3bcd492d0266438fa9bf164fe90d9", os.FileMode(0711)}, nil, nil),
+ call("verbosef", stub.ExpectArgs{"inserting entry %s to X11", []any{xhostOp("chronos")}}, nil, nil),
+ call("xcbChangeHosts", stub.ExpectArgs{xcb.HostMode(xcb.HostModeInsert), xcb.Family(xcb.FamilyServerInterpreted), "localuser\x00chronos"}, nil, nil),
+ }, nil, []stub.Call{
+ call("verbosef", stub.ExpectArgs{"deleting entry %s from X11", []any{xhostOp("chronos")}}, nil, nil),
+ call("xcbChangeHosts", stub.ExpectArgs{xcb.HostMode(xcb.HostModeDelete), xcb.Family(xcb.FamilyServerInterpreted), "localuser\x00chronos"}, nil, nil),
+ call("verbose", stub.ExpectArgs{[]any{"destroying ephemeral directory", &mkdirOp{Process, "/tmp/hakurei.0/f2f3bcd492d0266438fa9bf164fe90d9", 0711, true}}}, nil, nil),
+ call("remove", stub.ExpectArgs{"/tmp/hakurei.0/f2f3bcd492d0266438fa9bf164fe90d9"}, nil, nil),
+ }, nil},
+ }
+
+ for _, tc := range testCases {
+ t.Run(tc.name, func(t *testing.T) {
+ var ec *Criteria
+ if tc.ec != 0xff {
+ ec = (*Criteria)(&tc.ec)
+ }
+
+ sys, s := InternalNew(t, stub.Expect{Calls: slices.Concat(tc.commit, []stub.Call{{Name: stub.CallSeparator}}, tc.revert)}, 0xbad)
+ defer stub.HandleExit(t)
+ tc.f(sys)
+ errCommit := sys.Commit()
+ s.Expects(stub.CallSeparator)
+ if !reflect.DeepEqual(errCommit, tc.wantErrCommit) {
+ t.Errorf("Commit: error = %v, want %v", errCommit, tc.wantErrCommit)
+ }
+ if errCommit != nil {
+ goto out
+ }
+
+ if err := sys.Revert(ec); !reflect.DeepEqual(err, tc.wantErrRevert) {
+ t.Errorf("Revert: error = %v, want %v", err, tc.wantErrRevert)
+ }
+
+ out:
+ s.VisitIncomplete(func(s *stub.Stub[syscallDispatcher]) {
+ count := s.Pos() - 1 // separator
+ if count < len(tc.commit) {
+ t.Errorf("Commit: %d calls, want %d", count, len(tc.commit))
+ } else {
+ t.Errorf("Revert: %d calls, want %d", count-len(tc.commit), len(tc.revert))
+ }
+ })
+ })
+ }
+
+ t.Run("panic", func(t *testing.T) {
+ t.Run("committed", func(t *testing.T) {
+ defer func() {
+ want := "attempting to commit twice"
+ if r := recover(); r != want {
+ t.Errorf("Commit: panic = %v, want %v", r, want)
+ }
+ }()
+ _ = (&I{committed: true}).Commit()
+ })
+
+ t.Run("reverted", func(t *testing.T) {
+ defer func() {
+ want := "attempting to revert twice"
+ if r := recover(); r != want {
+ t.Errorf("Revert: panic = %v, want %v", r, want)
+ }
+ }()
+ _ = (&I{reverted: true}).Revert(nil)
+ })
+ })
+}
+
+func TestNop(t *testing.T) {
+ // these do nothing
+ new(noCopy).Unlock()
+ new(noCopy).Lock()
+}