diff --git a/cmd/sic/buildchain_test.go b/cmd/sic/buildchain_test.go new file mode 100644 index 0000000..6781afc --- /dev/null +++ b/cmd/sic/buildchain_test.go @@ -0,0 +1,31 @@ +package main + +import ( + "bytes" + "testing" +) + +// WP3: buildChain folds wrapHop over the resolved per-hop verbs (verbs[0] outermost) around the +// innermost v2Frame(cmd, payload). Relational: each want is built from the real v2Frame/wrapHop, +// so this pins the composition and fold ORDER, not a re-derived byte layout. +func TestBuildChain(t *testing.T) { + cmd := []byte("cat") + payload := []byte("hi") + cases := []struct { + name string + verbs []string + want []byte + }{ + {"zero hops nil = single v2 frame", nil, v2Frame(cmd, payload)}, + {"zero hops empty slice", []string{}, v2Frame(cmd, payload)}, + {"one hop", []string{"incus exec c --"}, + wrapHop("incus exec c --", v2Frame(cmd, payload))}, + {"two hops, verbs[0] is the OUTERMOST layer", []string{"incus exec c --", "docker exec d"}, + wrapHop("incus exec c --", wrapHop("docker exec d", v2Frame(cmd, payload)))}, + } + for _, tc := range cases { + if got := buildChain(tc.verbs, cmd, payload); !bytes.Equal(got, tc.want) { + t.Fatalf("%s:\n got % x\n want % x", tc.name, got, tc.want) + } + } +} diff --git a/cmd/sic/frame.go b/cmd/sic/frame.go index 4973134..41df077 100644 --- a/cmd/sic/frame.go +++ b/cmd/sic/frame.go @@ -33,3 +33,12 @@ func v2Frame(command []byte, payload []byte) []byte { func wrapHop(verb string, inner []byte) []byte { return v2Frame([]byte(verb+" sicd"), inner) } + +// buildChain builds a chain of wrapped frames from verbs, with the innermost being v2Frame(cmd, payload). +func buildChain(verbs []string, cmd []byte, payload []byte) []byte { + frame := v2Frame(cmd, payload) + for i := len(verbs) - 1; i >= 0; i-- { + frame = wrapHop(verbs[i], frame) + } + return frame +}