aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorOphestra <cat@gensokyo.uk>2025-10-11 02:44:02 +0900
committerOphestra <cat@gensokyo.uk>2025-10-11 02:44:02 +0900
commit9e3df0905b28c336c3b9df95bd8dfbccd36b7e61 (patch)
treea149bd03c16661bcdb4bbc60b6c5de2f903cb118
parent9290748761b9c91c9e379ff2774fefa58bd1b533 (diff)
internal/app/spcontainer: check params init behaviour
This change also significantly reduces duplicate information in test case. Signed-off-by: Ophestra <cat@gensokyo.uk>
-rw-r--r--internal/app/dispatcher_test.go21
-rw-r--r--internal/app/spaccount_test.go24
-rw-r--r--internal/app/spcontainer_test.go140
3 files changed, 160 insertions, 25 deletions
diff --git a/internal/app/dispatcher_test.go b/internal/app/dispatcher_test.go
index 1f5a9f68..e1d8f538 100644
--- a/internal/app/dispatcher_test.go
+++ b/internal/app/dispatcher_test.go
@@ -33,19 +33,17 @@ var checkExpectInstanceId = *(*state.ID)(bytes.Repeat([]byte{0xaa}, len(state.ID
type opBehaviourTestCase struct {
name string
- newOp func() outcomeOp
+ newOp func(isShim, clearUnexported bool) outcomeOp
newConfig func() *hst.Config
pStateSys func(state *outcomeStateSys)
toSystem []stub.Call
- wantOpSys outcomeOp
wantSys *system.I
extraCheckSys func(t *testing.T, state *outcomeStateSys)
wantErrSystem error
pStateContainer func(state *outcomeStateParams)
toContainer []stub.Call
- wantOpContainer outcomeOp
wantParams *container.Params
extraCheckParams func(t *testing.T, state *outcomeStateParams)
wantErrContainer error
@@ -94,11 +92,11 @@ func checkOpBehaviour(t *testing.T, testCases []opBehaviourTestCase) {
if err := s.populateLocal(k, k); err != nil {
t.Fatalf("populateLocal: error = %v", err)
}
- stateSys := s.newSys(config, system.New(panicMsgContext{}, panicMsgContext{}, checkExpectUid))
+ stateSys := s.newSys(config, newI())
if tc.pStateSys != nil {
tc.pStateSys(stateSys)
}
- op := tc.newOp()
+ op := tc.newOp(false, true)
if err := op.toSystem(stateSys); !reflect.DeepEqual(err, tc.wantErrSystem) {
t.Errorf("toSystem: error = %v, want %v", err, tc.wantErrSystem)
@@ -118,8 +116,8 @@ func checkOpBehaviour(t *testing.T, testCases []opBehaviourTestCase) {
if tc.extraCheckSys != nil {
tc.extraCheckSys(t, stateSys)
}
- if !reflect.DeepEqual(op, tc.wantOpSys) {
- t.Errorf("toSystem: op = %#v, want %#v", op, tc.wantOpSys)
+ if wantOpSys := tc.newOp(true, false); !reflect.DeepEqual(op, wantOpSys) {
+ t.Errorf("toSystem: op = %#v, want %#v", op, wantOpSys)
}
}
@@ -133,7 +131,7 @@ func checkOpBehaviour(t *testing.T, testCases []opBehaviourTestCase) {
if tc.pStateContainer != nil {
tc.pStateContainer(stateParams)
}
- op := tc.newOp()
+ op := tc.newOp(true, true)
if err := op.toContainer(stateParams); !reflect.DeepEqual(err, tc.wantErrContainer) {
t.Errorf("toContainer: error = %v, want %v", err, tc.wantErrContainer)
@@ -144,14 +142,11 @@ func checkOpBehaviour(t *testing.T, testCases []opBehaviourTestCase) {
}
if !reflect.DeepEqual(stateParams.params, tc.wantParams) {
- t.Errorf("toContainer: %#v, want %#v", stateParams.params, tc.wantParams)
+ t.Errorf("toContainer:\n%s\nwant\n%s", mustMarshal(stateParams.params), mustMarshal(tc.wantParams))
}
if tc.extraCheckParams != nil {
tc.extraCheckParams(t, stateParams)
}
- if !reflect.DeepEqual(op, tc.wantOpContainer) {
- t.Errorf("toContainer: op = %#v, want %#v", op, tc.wantOpContainer)
- }
}
out:
@@ -167,6 +162,8 @@ func checkOpBehaviour(t *testing.T, testCases []opBehaviourTestCase) {
}
}
+func newI() *system.I { return system.New(panicMsgContext{}, panicMsgContext{}, checkExpectUid) }
+
type kstub struct {
panicDispatcher
*stub.Stub[syscallDispatcher]
diff --git a/internal/app/spaccount_test.go b/internal/app/spaccount_test.go
index 8d96b4bc..a587c122 100644
--- a/internal/app/spaccount_test.go
+++ b/internal/app/spaccount_test.go
@@ -9,45 +9,43 @@ import (
"hakurei.app/container"
"hakurei.app/container/stub"
"hakurei.app/hst"
- "hakurei.app/message"
- "hakurei.app/system"
)
func TestSpAccountOp(t *testing.T) {
config := hst.Template()
checkOpBehaviour(t, []opBehaviourTestCase{
- {"invalid state", func() outcomeOp { return spAccountOp{} }, func() *hst.Config {
+ {"invalid state", func(bool, bool) outcomeOp { return spAccountOp{} }, func() *hst.Config {
c := hst.Template()
c.Container.Shell = nil
return c
}, nil, []stub.Call{
// this op performs basic validation and does not make calls during toSystem
- }, spAccountOp{}, system.New(t.Context(), message.NewMsg(nil), checkExpectUid), nil, syscall.ENOTRECOVERABLE, nil, nil, nil, nil, nil, nil},
+ }, nil, nil, syscall.ENOTRECOVERABLE, nil, nil, nil, nil, nil},
- {"invalid user name", func() outcomeOp { return spAccountOp{} }, func() *hst.Config {
+ {"invalid user name", func(bool, bool) outcomeOp { return spAccountOp{} }, func() *hst.Config {
c := hst.Template()
c.Container.Username = "9"
return c
}, nil, []stub.Call{
// this op performs basic validation and does not make calls during toSystem
- }, spAccountOp{}, nil, nil, &hst.AppError{
+ }, nil, nil, &hst.AppError{
Step: "finalise",
Err: os.ErrInvalid,
Msg: `invalid user name "9"`,
- }, nil, nil, nil, nil, nil, nil},
+ }, nil, nil, nil, nil, nil},
- {"success fallback username", func() outcomeOp { return spAccountOp{} }, func() *hst.Config {
+ {"success fallback username", func(bool, bool) outcomeOp { return spAccountOp{} }, func() *hst.Config {
c := hst.Template()
c.Container.Username = ""
return c
}, nil, []stub.Call{
// this op performs basic validation and does not make calls during toSystem
- }, spAccountOp{}, system.New(t.Context(), message.NewMsg(nil), checkExpectUid), nil, nil, func(state *outcomeStateParams) {
+ }, newI(), nil, nil, func(state *outcomeStateParams) {
state.params.Ops = new(container.Ops)
}, []stub.Call{
// this op configures the container state and does not make calls during toContainer
- }, spAccountOp{}, &container.Params{
+ }, &container.Params{
Dir: config.Container.Home,
Ops: new(container.Ops).
Place(m("/etc/passwd"), []byte("chronos:x:1000:100:Hakurei:/data/data/org.chromium.Chromium:/run/current-system/sw/bin/zsh\n")).
@@ -64,13 +62,13 @@ func TestSpAccountOp(t *testing.T) {
}
}, nil},
- {"success", func() outcomeOp { return spAccountOp{} }, hst.Template, nil, []stub.Call{
+ {"success", func(bool, bool) outcomeOp { return spAccountOp{} }, hst.Template, nil, []stub.Call{
// this op performs basic validation and does not make calls during toSystem
- }, spAccountOp{}, system.New(t.Context(), message.NewMsg(nil), checkExpectUid), nil, nil, func(state *outcomeStateParams) {
+ }, newI(), nil, nil, func(state *outcomeStateParams) {
state.params.Ops = new(container.Ops)
}, []stub.Call{
// this op configures the container state and does not make calls during toContainer
- }, spAccountOp{}, &container.Params{
+ }, &container.Params{
Dir: config.Container.Home,
Ops: new(container.Ops).
Place(m("/etc/passwd"), []byte("chronos:x:1000:100:Hakurei:/data/data/org.chromium.Chromium:/run/current-system/sw/bin/zsh\n")).
diff --git a/internal/app/spcontainer_test.go b/internal/app/spcontainer_test.go
new file mode 100644
index 00000000..653fba13
--- /dev/null
+++ b/internal/app/spcontainer_test.go
@@ -0,0 +1,140 @@
+package app
+
+import (
+ "maps"
+ "os"
+ "reflect"
+ "testing"
+
+ "hakurei.app/container"
+ "hakurei.app/container/bits"
+ "hakurei.app/container/fhs"
+ "hakurei.app/container/seccomp"
+ "hakurei.app/container/stub"
+ "hakurei.app/hst"
+)
+
+func TestSpParamsOp(t *testing.T) {
+ config := hst.Template()
+
+ checkOpBehaviour(t, []opBehaviourTestCase{
+ {"invalid program path", func(isShim, _ bool) outcomeOp {
+ if !isShim {
+ return new(spParamsOp)
+ }
+ return &spParamsOp{Term: "xterm", TermSet: true}
+ }, func() *hst.Config {
+ c := hst.Template()
+ c.Container.Path = nil
+ return c
+ }, nil, []stub.Call{
+ call("lookupEnv", stub.ExpectArgs{"TERM"}, "xterm", nil),
+ }, newI().
+ Ensure(m(container.Nonexistent+"/tmp/hakurei.0"), 0711), nil, nil, nil, []stub.Call{
+ // this op configures the container state and does not make calls during toContainer
+ }, nil, nil, &hst.AppError{
+ Step: "finalise",
+ Err: os.ErrInvalid,
+ Msg: "invalid program path",
+ }},
+
+ {"success defaultargs secure", func(isShim, _ bool) outcomeOp {
+ if !isShim {
+ return new(spParamsOp)
+ }
+ return &spParamsOp{Term: "xterm", TermSet: true}
+ }, func() *hst.Config {
+ c := hst.Template()
+ c.Container.Args = nil
+ c.Container.Multiarch = false
+ c.Container.SeccompCompat = false
+ c.Container.Devel = false
+ c.Container.Userns = false
+ c.Container.Tty = false
+ c.Container.Device = false
+ return c
+ }, nil, []stub.Call{
+ call("lookupEnv", stub.ExpectArgs{"TERM"}, "xterm", nil),
+ }, newI().
+ Ensure(m(container.Nonexistent+"/tmp/hakurei.0"), 0711), nil, nil, nil, []stub.Call{
+ // this op configures the container state and does not make calls during toContainer
+ }, &container.Params{
+ Hostname: config.Container.Hostname,
+ HostNet: config.Container.HostNet,
+ HostAbstract: config.Container.HostAbstract,
+ Path: config.Container.Path,
+ Args: []string{config.Container.Path.String()},
+ SeccompPresets: bits.PresetExt | bits.PresetDenyDevel | bits.PresetDenyNS | bits.PresetDenyTTY,
+ Uid: 1000,
+ Gid: 100,
+ Ops: new(container.Ops).
+ Root(m("/var/lib/hakurei/base/org.debian"), bits.BindWritable).
+ Proc(fhs.AbsProc).Tmpfs(hst.AbsTmp, 1<<12, 0755).
+ DevWritable(fhs.AbsDev, true).
+ Tmpfs(fhs.AbsDev.Append("shm"), 0, 01777),
+ }, func(t *testing.T, state *outcomeStateParams) {
+ wantEnv := map[string]string{
+ "TERM": "xterm",
+ }
+ maps.Copy(wantEnv, config.Container.Env)
+ if !maps.Equal(state.env, wantEnv) {
+ t.Errorf("toContainer: env = %#v, want %#v", state.env, wantEnv)
+ }
+
+ const wantAutoEtcPrefix = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
+ if state.as.AutoEtcPrefix != wantAutoEtcPrefix {
+ t.Errorf("toContainer: as.AutoEtcPrefix = %q, want %q", state.as.AutoEtcPrefix, wantAutoEtcPrefix)
+ }
+
+ wantFilesystems := config.Container.Filesystem[1:]
+ if !reflect.DeepEqual(state.filesystem, wantFilesystems) {
+ t.Errorf("toContainer: filesystem = %#v, want %#v", state.filesystem, wantFilesystems)
+ }
+ }, nil},
+
+ {"success", func(isShim, _ bool) outcomeOp {
+ if !isShim {
+ return new(spParamsOp)
+ }
+ return &spParamsOp{Term: "xterm", TermSet: true}
+ }, hst.Template, nil, []stub.Call{
+ call("lookupEnv", stub.ExpectArgs{"TERM"}, "xterm", nil),
+ }, newI().
+ Ensure(m(container.Nonexistent+"/tmp/hakurei.0"), 0711), nil, nil, nil, []stub.Call{
+ // this op configures the container state and does not make calls during toContainer
+ }, &container.Params{
+ Hostname: config.Container.Hostname,
+ RetainSession: config.Container.Tty,
+ HostNet: config.Container.HostNet,
+ HostAbstract: config.Container.HostAbstract,
+ Path: config.Container.Path,
+ Args: config.Container.Args,
+ SeccompFlags: seccomp.AllowMultiarch,
+ Uid: 1000,
+ Gid: 100,
+ Ops: new(container.Ops).
+ Root(m("/var/lib/hakurei/base/org.debian"), bits.BindWritable).
+ Proc(fhs.AbsProc).Tmpfs(hst.AbsTmp, 1<<12, 0755).
+ Bind(fhs.AbsDev, fhs.AbsDev, bits.BindWritable|bits.BindDevice).
+ Tmpfs(fhs.AbsDev.Append("shm"), 0, 01777),
+ }, func(t *testing.T, state *outcomeStateParams) {
+ wantEnv := map[string]string{
+ "TERM": "xterm",
+ }
+ maps.Copy(wantEnv, config.Container.Env)
+ if !maps.Equal(state.env, wantEnv) {
+ t.Errorf("toContainer: env = %#v, want %#v", state.env, wantEnv)
+ }
+
+ const wantAutoEtcPrefix = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
+ if state.as.AutoEtcPrefix != wantAutoEtcPrefix {
+ t.Errorf("toContainer: as.AutoEtcPrefix = %q, want %q", state.as.AutoEtcPrefix, wantAutoEtcPrefix)
+ }
+
+ wantFilesystems := config.Container.Filesystem[1:]
+ if !reflect.DeepEqual(state.filesystem, wantFilesystems) {
+ t.Errorf("toContainer: filesystem = %#v, want %#v", state.filesystem, wantFilesystems)
+ }
+ }, nil},
+ })
+}