Files
Shailend ChandandgVisor bot baf615a53c Replace the experimental --mount-cgroup-v2 flag with in-sandbox-cgroup
The new flag is not a boolean flag, in deference to PR#14197.

PiperOrigin-RevId: 970257422
2026-08-24 20:29:27 -07:00

969 lines
25 KiB
Go

// Copyright 2020 The gVisor Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package config
import (
"fmt"
"reflect"
"strings"
"testing"
"github.com/google/go-cmp/cmp"
"gvisor.dev/gvisor/runsc/flag"
)
func TestDefault(t *testing.T) {
testFlags := flag.NewFlagSet("test", flag.ContinueOnError)
RegisterFlags(testFlags)
c, err := NewFromFlags(testFlags)
if err != nil {
t.Fatal(err)
}
// "--root" is always set to something different than the default. Reset it
// to make it easier to test that default values do not generate flags.
c.RootDir = ""
if c.GoferNetworkNamespace != GoferNetworkNamespaceNull {
t.Errorf("GoferNetworkNamespace=%q, want null", c.GoferNetworkNamespace)
}
// All defaults doesn't require setting flags.
flags := c.ToFlags()
if len(flags) > 0 {
t.Errorf("default flags not set correctly for: %s", flags)
}
}
func TestFromFlags(t *testing.T) {
testFlags := flag.NewFlagSet("test", flag.ContinueOnError)
RegisterFlags(testFlags)
if err := testFlags.Lookup("root").Value.Set("some-path"); err != nil {
t.Errorf("Flag set: %v", err)
}
if err := testFlags.Lookup("debug").Value.Set("true"); err != nil {
t.Errorf("Flag set: %v", err)
}
if err := testFlags.Lookup("num-network-channels").Value.Set("123"); err != nil {
t.Errorf("Flag set: %v", err)
}
if err := testFlags.Lookup("network").Value.Set("none"); err != nil {
t.Errorf("Flag set: %v", err)
}
if err := testFlags.Lookup("gofer-network-namespace").Value.Set("host"); err != nil {
t.Errorf("Flag set: %v", err)
}
c, err := NewFromFlags(testFlags)
if err != nil {
t.Fatal(err)
}
if want := "some-path"; c.RootDir != want {
t.Errorf("RootDir=%v, want: %v", c.RootDir, want)
}
if want := true; c.Debug != want {
t.Errorf("Debug=%v, want: %v", c.Debug, want)
}
if want := 123; c.NumNetworkChannels != want {
t.Errorf("NumNetworkChannels=%v, want: %v", c.NumNetworkChannels, want)
}
if want := NetworkNone; c.Network != want {
t.Errorf("Network=%v, want: %v", c.Network, want)
}
if want := GoferNetworkNamespaceHost; c.GoferNetworkNamespace != want {
t.Errorf("GoferNetworkNamespace=%v, want: %v", c.GoferNetworkNamespace, want)
}
}
func TestToFlagsFromFlags(t *testing.T) {
testFlags := flag.NewFlagSet("test", flag.ContinueOnError)
RegisterFlags(testFlags)
testFlags.Set("root", "some-path")
testFlags.Set("debug", "true")
testFlags.Set("profile", "false") // Matches default value.
testFlags.Set("num-network-channels", "123")
testFlags.Set("network", "none")
testFlags.Set("gofer-network-namespace", "host")
c, err := NewFromFlags(testFlags)
if err != nil {
t.Fatal(err)
}
flags := c.ToFlags()
if len(flags) != 6 {
t.Errorf("wrong number of flags set, want: 6, got: %d: %s", len(flags), flags)
}
t.Logf("Flags: %s", flags)
fm := map[string]string{}
for _, f := range flags {
kv := strings.Split(f, "=")
fm[kv[0]] = kv[1]
}
for name, want := range map[string]string{
"--root": "some-path",
"--debug": "true",
"--profile": "false",
"--num-network-channels": "123",
"--network": "none",
"--gofer-network-namespace": "host",
} {
if got, ok := fm[name]; ok {
if got != want {
t.Errorf("flag %q, want: %q, got: %q", name, want, got)
}
} else {
t.Errorf("flag %q not set", name)
}
}
}
func TestToFlagsFromManual(t *testing.T) {
c := &Config{
RootDir: "some-path",
Debug: true,
ProfileEnable: false, // Matches default flag value.
NumNetworkChannels: 123,
Network: NetworkNone,
GoferNetworkNamespace: GoferNetworkNamespaceHost,
}
// Create a second config with flag-default values that we'll copy from.
testFlags := flag.NewFlagSet("test", flag.ContinueOnError)
RegisterFlags(testFlags)
cfgDefault, err := NewFromFlags(testFlags)
if err != nil {
t.Fatal(err)
}
// Set all the unset fields of c to their flag-default value from cfgDefault.
cfgReflect := reflect.ValueOf(c).Elem()
cfgDefaultReflect := reflect.ValueOf(cfgDefault).Elem()
cfgType := cfgReflect.Type()
for i := 0; i < cfgType.NumField(); i++ {
f := cfgType.Field(i)
name, ok := f.Tag.Lookup("flag")
if !ok {
// No flag set for this field.
continue
}
if name == "root" || name == "debug" || name == "profile" || name == "num-network-channels" || name == "network" || name == "gofer-network-namespace" {
continue
}
cfgReflect.Field(i).Set(cfgDefaultReflect.Field(i))
}
flags := c.ToFlags()
if len(flags) != 5 {
t.Errorf("wrong number of flags set, want: 5, got: %d: %s", len(flags), flags)
}
t.Logf("Flags: %s", flags)
fm := map[string]string{}
for _, f := range flags {
kv := strings.Split(f, "=")
fm[kv[0]] = kv[1]
}
for name, want := range map[string]string{
"--root": "some-path",
"--debug": "true",
"--num-network-channels": "123",
"--network": "none",
"--gofer-network-namespace": "host",
} {
if got, ok := fm[name]; ok {
if got != want {
t.Errorf("flag %q, want: %q, got: %q", name, want, got)
}
} else {
t.Errorf("flag %q not set", name)
}
}
if _, hasProfile := fm["--profile"]; hasProfile {
t.Error("--profile flag unexpectedly set")
}
}
// TestInvalidFlags checks that enum flags fail when value is not in enum set.
func TestInvalidFlags(t *testing.T) {
for _, tc := range []struct {
name string
value string
error string
}{
{
name: "file-access",
value: "invalid",
error: "invalid file access type",
},
{
name: "network",
value: "invalid",
error: "invalid network type",
},
{
name: "gofer-network-namespace",
value: "invalid",
error: "invalid gofer network namespace",
},
{
name: "qdisc",
value: "invalid",
error: "invalid qdisc",
},
{
name: "ref-leak-mode",
value: "invalid",
error: "invalid ref leak mode",
},
{
name: "host-uds",
value: "invalid",
error: "invalid host UDS",
},
{
name: "host-fifo",
value: "invalid",
error: "invalid host fifo",
},
{
name: "overlay2",
value: "root:/tmp",
error: "unexpected medium: \"/tmp\"",
},
{
name: "overlay2",
value: "root:dir=tmp",
error: "overlay host file directory should be an absolute path, got \"tmp\"",
},
{
name: "overlay2",
value: "root:memory,sz=sdg",
error: "expected format is --overlay2",
},
{
name: "sidecar-usage-policy",
value: "invalid",
error: "invalid value \"invalid\"; must be DEFAULT, STRICT, or LEGACY_DEPRECATED_SLOW_EMBEDDED_FALLBACK",
},
{
name: "sidecar-release-enforcement-policy",
value: "invalid",
error: "invalid value \"invalid\"; must be NEVER, ALWAYS, or IF_RELEASE_BUILD",
},
} {
t.Run(tc.name, func(t *testing.T) {
testFlags := flag.NewFlagSet("test", flag.ContinueOnError)
RegisterFlags(testFlags)
if err := testFlags.Lookup(tc.name).Value.Set(tc.value); err == nil || !strings.Contains(err.Error(), tc.error) {
t.Errorf("flag.Value.Set(invalid) wrong error reported: %v", err)
}
})
}
}
func TestValidationFail(t *testing.T) {
for _, tc := range []struct {
name string
flags map[string]string
error string
}{
{
name: "shared+overlay",
flags: map[string]string{
"file-access": "shared",
"overlay2": "root:self",
},
error: "overlay flag is incompatible",
},
{
name: "network-channels",
flags: map[string]string{
"num-network-channels": "-1",
},
error: "num_network_channels must be > 0",
},
{
name: "qdisc-tbf-burst-overflow",
flags: map[string]string{
"qdisc-tbf-burst": "4294967296",
},
error: "qdisc-tbf-burst must be <=",
},
{
name: "qdisc-tbf-without-rate",
flags: map[string]string{
"qdisc": "tbf",
"qdisc-tbf-burst": "524288",
},
error: "qdisc=tbf requires setting qdisc-tbf-rate",
},
{
name: "qdisc-tbf-without-burst",
flags: map[string]string{
"qdisc": "tbf",
"qdisc-tbf-rate": "12500000",
},
error: "qdisc=tbf requires setting qdisc-tbf-burst",
},
{
name: "fsgofer-host-uds+host-uds:open",
flags: map[string]string{
"fsgofer-host-uds": "true",
"host-uds": "open",
},
error: "fsgofer-host-uds has been replaced with host-uds flag",
},
{
name: "fsgofer-host-uds+host-uds:create",
flags: map[string]string{
"fsgofer-host-uds": "true",
"host-uds": "create",
},
error: "fsgofer-host-uds has been replaced with host-uds flag",
},
{
name: "fsgofer-host-uds+host-uds:all",
flags: map[string]string{
"fsgofer-host-uds": "true",
"host-uds": "all",
},
error: "fsgofer-host-uds has been replaced with host-uds flag",
},
{
name: "overlay+overlay2:root",
flags: map[string]string{
"overlay": "true",
"overlay2": "root:memory",
},
error: "overlay flag has been replaced with overlay2 flag",
},
{
name: "overlay+overlay2:all",
flags: map[string]string{
"overlay": "true",
"overlay2": "all:memory",
},
error: "overlay flag has been replaced with overlay2 flag",
},
} {
t.Run(tc.name, func(t *testing.T) {
testFlags := flag.NewFlagSet("test", flag.ContinueOnError)
RegisterFlags(testFlags)
for name, val := range tc.flags {
if err := testFlags.Lookup(name).Value.Set(val); err != nil {
t.Errorf("%s=%q: %v", name, val, err)
}
}
if _, err := NewFromFlags(testFlags); err == nil || !strings.Contains(err.Error(), tc.error) {
t.Errorf("NewFromFlags() wrong error reported: %v", err)
}
})
}
}
func TestOverride(t *testing.T) {
testFlags := flag.NewFlagSet("test", flag.ContinueOnError)
RegisterFlags(testFlags)
c, err := NewFromFlags(testFlags)
if err != nil {
t.Fatal(err)
}
c.AllowFlagOverride = true
t.Run("string", func(t *testing.T) {
c.RootDir = "foobar"
if err := c.Override(testFlags, "root", "bar", false); err != nil {
t.Fatalf("Override(root, bar) failed: %v", err)
}
if c.RootDir != "bar" {
t.Errorf("Override(root, bar) didn't work: %+v", c)
}
})
t.Run("bool", func(t *testing.T) {
c.Debug = true
if err := c.Override(testFlags, "debug", "false", false); err != nil {
t.Fatalf("Override(debug, false) failed: %v", err)
}
if c.Debug {
t.Errorf("Override(debug, false) didn't work: %+v", c)
}
})
t.Run("enum", func(t *testing.T) {
c.FileAccess = FileAccessShared
if err := c.Override(testFlags, "file-access", "exclusive", false); err != nil {
t.Fatalf("Override(file-access, exclusive) failed: %v", err)
}
if c.FileAccess != FileAccessExclusive {
t.Errorf("Override(file-access, exclusive) didn't work: %+v", c)
}
})
}
func TestOverrideDisabled(t *testing.T) {
testFlags := flag.NewFlagSet("test", flag.ContinueOnError)
RegisterFlags(testFlags)
c, err := NewFromFlags(testFlags)
if err != nil {
t.Fatal(err)
}
const errMsg = "flag override disabled"
if err := c.Override(testFlags, "root", "path", false); err == nil || !strings.Contains(err.Error(), errMsg) {
t.Errorf("Override() wrong error: %v", err)
}
}
func TestOverrideError(t *testing.T) {
testFlags := flag.NewFlagSet("test", flag.ContinueOnError)
RegisterFlags(testFlags)
c, err := NewFromFlags(testFlags)
if err != nil {
t.Fatal(err)
}
c.AllowFlagOverride = true
for _, tc := range []struct {
name string
value string
error string
}{
{
name: "invalid",
value: "valid",
error: `flag "invalid" not found`,
},
{
name: "debug",
value: "invalid",
error: "error setting flag debug",
},
{
name: "file-access",
value: "invalid",
error: "invalid file access type",
},
{
name: "in-sandbox-cgroup",
value: "invalid",
error: "invalid in-sandbox-cgroup",
},
} {
t.Run(tc.name, func(t *testing.T) {
if err := c.Override(testFlags, tc.name, tc.value, false); err == nil || !strings.Contains(err.Error(), tc.error) {
t.Errorf("Override(%q, %q) wrong error: %v", tc.name, tc.value, err)
}
})
}
}
func TestOverrideAllowlist(t *testing.T) {
testFlags := flag.NewFlagSet("test", flag.ContinueOnError)
RegisterFlags(testFlags)
c, err := NewFromFlags(testFlags)
if err != nil {
t.Fatal(err)
}
for _, tc := range []struct {
flag string
value string
force bool
error string
}{
{
flag: "debug",
value: "true",
},
{
flag: "debug",
value: "123",
error: "error setting flag",
},
{
flag: "in-sandbox-cgroup",
value: "v1",
},
{
flag: "in-sandbox-cgroup",
value: "v2",
},
{
flag: "oci-seccomp",
value: "true",
},
{
flag: "oci-seccomp",
value: "false",
error: `disabling "oci-seccomp" requires flag`,
},
{
flag: "oci-seccomp",
value: "123",
error: "invalid syntax",
},
{
flag: "profile",
value: "true",
error: "flag override disabled",
},
{
flag: "profile",
value: "true",
force: true,
},
{
flag: "profile",
value: "123",
error: "flag override disabled",
},
{
flag: "qdisc",
value: "fifo",
error: `requires flag "allow-flag-override"`,
},
{
flag: "qdisc",
value: "none",
error: `requires flag "allow-flag-override"`,
},
{
flag: "qdisc",
value: "invalid",
error: "invalid qdisc",
},
// Order matters: a successful override mutates the Config ceiling for
// later subtests.
{
flag: "qdisc-tbf-rate",
value: fmt.Sprint(defaultQDiscTBFRate),
},
{
flag: "qdisc-tbf-rate",
value: "18446744073709551616", // > max uint64
error: "invalid",
},
{
flag: "qdisc-tbf-rate",
value: "abc",
error: "invalid",
},
{
flag: "qdisc-tbf-rate",
value: "1000000",
},
{
flag: "qdisc-tbf-rate",
value: "2000000", // attempts to raise above the 1M ceiling without force
error: "raising the limit requires",
},
{
flag: "qdisc-tbf-burst",
value: fmt.Sprint(defaultQDiscTBFBurst),
},
{
flag: "qdisc-tbf-burst",
value: "65536",
},
{
flag: "qdisc-tbf-burst",
value: "131072", // attempts to raise above the 65536 ceiling without force
error: "raising the limit requires",
},
{
flag: "qdisc",
value: "tbf",
},
{
flag: "qdisc-tbf-rate",
value: "2147483648",
force: true, // admin escape hatch bypasses the check
},
{
flag: "qdisc-tbf-burst",
value: "4294967296", // > max uint32; rejected by Validate below
force: true,
},
} {
t.Run(tc.flag, func(t *testing.T) {
err := c.Override(testFlags, tc.flag, tc.value, tc.force)
if len(tc.error) == 0 {
if err != nil {
t.Errorf("Unexpected error: %v", err)
}
} else if err == nil || !strings.Contains(err.Error(), tc.error) {
t.Errorf("Override(%q, %q) wrong error: %v", tc.flag, tc.value, err)
}
})
}
// Override does not validate, so even force=true cannot bypass Validate:
// the out-of-range burst applied above must fail validation.
if err := c.Validate(); err == nil || !strings.Contains(err.Error(), "qdisc-tbf-burst must be <=") {
t.Errorf("Validate() wrong error: %v", err)
}
}
func TestOverrideAllowlistQDiscTBF(t *testing.T) {
testFlags := flag.NewFlagSet("test", flag.ContinueOnError)
RegisterFlags(testFlags)
c, err := NewFromFlags(testFlags)
if err != nil {
t.Fatal(err)
}
if got, want := c.QDisc, QDiscFIFO; got != want {
t.Fatalf("default QDisc = %v, want %v", got, want)
}
if err := c.Override(testFlags, "qdisc-tbf-rate", "12500000", false); err != nil {
t.Fatalf("Override(qdisc-tbf-rate, 12500000) failed: %v", err)
}
if err := c.Override(testFlags, "qdisc-tbf-burst", "524288", false); err != nil {
t.Fatalf("Override(qdisc-tbf-burst, 524288) failed: %v", err)
}
if err := c.Override(testFlags, "qdisc", "tbf", false); err != nil {
t.Fatalf("Override(qdisc, tbf) failed: %v", err)
}
if got, want := c.QDisc, QDiscTBF; got != want {
t.Errorf("QDisc = %v, want %v", got, want)
}
if got, want := c.TBFRate, uint64(12500000); got != want {
t.Errorf("TBFRate = %d, want %d", got, want)
}
if got, want := c.TBFBurst, uint64(524288); got != want {
t.Errorf("TBFBurst = %d, want %d", got, want)
}
}
func TestOverrideDeferredValidation(t *testing.T) {
// qdisc=tbf is rejected by Validate while rate or burst is still zero.
// Override does not validate, so interdependent flags can be applied in
// any order (e.g. from randomly-iterated annotation maps), as long as the
// final state passes Validate.
testFlags := flag.NewFlagSet("test", flag.ContinueOnError)
RegisterFlags(testFlags)
c, err := NewFromFlags(testFlags)
if err != nil {
t.Fatal(err)
}
// Worst-case order: qdisc=tbf is applied while rate and burst are zero.
for _, o := range []struct{ name, value string }{
{"qdisc", "tbf"},
{"qdisc-tbf-rate", "12500000"},
{"qdisc-tbf-burst", "524288"},
} {
if err := c.Override(testFlags, o.name, o.value, false); err != nil {
t.Fatalf("Override(%s, %s) failed: %v", o.name, o.value, err)
}
}
if err := c.Validate(); err != nil {
t.Errorf("Validate() failed: %v", err)
}
if got, want := c.QDisc, QDiscTBF; got != want {
t.Errorf("QDisc = %v, want %v", got, want)
}
if got, want := c.TBFRate, uint64(12500000); got != want {
t.Errorf("TBFRate = %d, want %d", got, want)
}
if got, want := c.TBFBurst, uint64(524288); got != want {
t.Errorf("TBFBurst = %d, want %d", got, want)
}
// Validation is deferred, not skipped: an inconsistent final state must
// still fail.
testFlags = flag.NewFlagSet("test", flag.ContinueOnError)
RegisterFlags(testFlags)
c, err = NewFromFlags(testFlags)
if err != nil {
t.Fatal(err)
}
if err := c.Override(testFlags, "qdisc", "tbf", false); err != nil {
t.Fatalf("Override(qdisc, tbf) failed: %v", err)
}
if err := c.Validate(); err == nil || !strings.Contains(err.Error(), "qdisc=tbf requires setting qdisc-tbf-rate") {
t.Errorf("Validate() wrong error: %v", err)
}
}
func TestBundles(t *testing.T) {
noChange := func(t *testing.T, old, new *Config) {
t.Helper()
if diff := cmp.Diff(old, new, cmp.AllowUnexported(Config{}, Overlay2{})); diff != "" {
t.Errorf("different configs:\n%+v\nvs\n%+v\nDiff:\n%s", old, new, diff)
}
}
for _, test := range []struct {
// Name of the test.
Name string
// List of bundles that exist for the purpose of this test.
BundleConfig map[BundleName]Bundle
// Command-line arguments passed as explicit flags.
CommandLine []string
// Names of the bundles to apply.
Bundles []BundleName
// Whether we expect applying bundles to fail.
WantErr bool
// If bundles were successfully applied, this function is called to compare
// pre-bundle-application and post-bundle-application configs.
Verify func(t *testing.T, old, new *Config)
}{
{
Name: "empty bundle",
BundleConfig: map[BundleName]Bundle{"empty": {}},
Bundles: []BundleName{"empty"},
Verify: noChange,
},
{
Name: "no-op bundle",
BundleConfig: map[BundleName]Bundle{
"no-debug": {
"debug": "false",
},
},
Bundles: []BundleName{"no-debug"},
Verify: noChange,
},
{
Name: "invalid flag",
BundleConfig: map[BundleName]Bundle{
"invalid-flag": {
"not-a-real-flag": "nope.avi",
},
},
Bundles: []BundleName{"invalid-flag"},
WantErr: true,
},
{
Name: "duplicate no-op bundles",
BundleConfig: map[BundleName]Bundle{
"empty": {},
"no-debug": {
"debug": "false",
},
},
Bundles: []BundleName{"no-debug", "no-debug"},
Verify: noChange,
},
{
Name: "simple bundle",
BundleConfig: map[BundleName]Bundle{
"empty": {},
"debug": {
"debug": "true",
},
"no-debug": {
"debug": "false",
},
},
Bundles: []BundleName{"debug"},
Verify: func(t *testing.T, old, new *Config) {
t.Helper()
if old.Debug {
t.Error("debug was previously set to true")
}
if !new.Debug {
t.Error("debug was not set to true")
}
},
},
{
Name: "incompatible bundles",
BundleConfig: map[BundleName]Bundle{
"debug": {
"debug": "true",
},
"no-debug": {
"debug": "false",
},
},
Bundles: []BundleName{"debug", "no-debug"},
WantErr: true,
},
{
Name: "compatible bundles",
BundleConfig: map[BundleName]Bundle{
"debug": {
"debug": "true",
},
"debug-and-profile": {
"debug": "true",
"profile": "true",
},
},
Bundles: []BundleName{"debug", "debug-and-profile"},
Verify: func(t *testing.T, old, new *Config) {
t.Helper()
if old.Debug || old.ProfileEnable {
t.Error("debug/profiling was previously set to true")
}
if !new.Debug {
t.Error("debug was not set to true")
}
if !new.ProfileEnable {
t.Error("profiling was not set to true")
}
},
},
{
Name: "bundle takes precedence over command-line value",
BundleConfig: map[BundleName]Bundle{
"no-debug": {
"debug": "false",
},
},
CommandLine: []string{"-debug=true"},
Bundles: []BundleName{"no-debug"},
Verify: func(t *testing.T, old, new *Config) {
t.Helper()
if new.Debug {
t.Error("debug is still true")
}
},
},
{
Name: "command line matching bundle value",
BundleConfig: map[BundleName]Bundle{
"debug": {
"debug": "true",
},
},
CommandLine: []string{"-debug=true"},
Bundles: []BundleName{"debug"},
Verify: func(t *testing.T, old, new *Config) {
t.Helper()
noChange(t, old, new)
if !new.Debug {
t.Error("debug was set to false")
}
},
},
} {
t.Run(test.Name, func(t *testing.T) {
oldBundles := Bundles
defer func() {
Bundles = oldBundles
}()
Bundles = test.BundleConfig
flagSet := flag.NewFlagSet(test.Name, flag.ContinueOnError)
RegisterFlags(flagSet)
if err := flagSet.Parse(test.CommandLine); err != nil {
t.Fatalf("cannot parse command line %q: %v", test.CommandLine, err)
}
cfg, err := NewFromFlags(flagSet)
if err != nil {
t.Fatalf("cannot generate config from flags: %v", err)
}
oldCfg := *cfg
err = cfg.ApplyBundles(flagSet, test.Bundles...)
if test.WantErr && err == nil {
t.Error("got no error, but expected one")
}
if !test.WantErr && err != nil {
t.Errorf("got unexpected error: %v", err)
}
if t.Failed() {
return
}
if err != nil && test.Verify != nil {
t.Error("cannot specify Verify function for erroring tests")
}
if err == nil && test.Verify != nil {
test.Verify(t, &oldCfg, cfg)
}
})
}
}
func TestBundleValidate(t *testing.T) {
defaultVerify := func(err error) error { return err }
for _, tc := range []struct {
name string
bundle Bundle
verify func(err error) error
}{
{
name: "empty bundle",
bundle: Bundle(map[string]string{}),
verify: defaultVerify,
},
{
name: "invalid flag bundle",
bundle: Bundle(map[string]string{"not-a-real-flag": "true"}),
verify: func(err error) error {
want := `unknown flag "not-a-real-flag"`
if !strings.Contains(err.Error(), want) {
return fmt.Errorf("mismatch error: got: %q want: %q", err.Error(), want)
}
return nil
},
},
{
name: "invalid value",
bundle: Bundle(map[string]string{"debug": "invalid"}),
verify: func(err error) error {
// Error differs in open-source version, and internally at Google
// https://github.com/google/gvisor/pull/11722#issuecomment-2877847616
wantOss := "parse error"
wantInternal := `parsing "invalid": invalid syntax`
if !strings.Contains(err.Error(), wantOss) && !strings.Contains(err.Error(), wantInternal) {
return fmt.Errorf("mismatch error: got: %q want: %q or %s", err.Error(), wantOss, wantInternal)
}
return nil
},
},
{
name: "valid flag bundle",
bundle: Bundle(map[string]string{"debug": "true"}),
verify: defaultVerify,
},
} {
t.Run(tc.name, func(t *testing.T) {
if err := tc.verify(tc.bundle.Validate()); err != nil {
t.Fatalf("Validate failed: %v", err)
}
})
}
}
func TestParseSerializeOverlay2(t *testing.T) {
t.Run("Without size", func(t *testing.T) {
o := Overlay2{}
err := o.Set("all:memory")
if err != nil {
t.Fatalf("Set failed: %v", err)
}
if o.RootOverlaySize() != "" || o.SubMountOverlaySize() != "" {
t.Fatalf("Size mismatch, expecting empty, got %q, %q", o.RootOverlaySize(), o.SubMountOverlaySize())
}
if o.String() != "all:memory" {
t.Fatalf("String mismatch, expecting all:memory, got %q", o.String())
}
})
t.Run("With size", func(t *testing.T) {
o := Overlay2{}
err := o.Set("root:memory,size=1g")
if err != nil {
t.Fatalf("Set failed: %v", err)
}
if o.RootOverlaySize() != "1g" || o.SubMountOverlaySize() != "" {
t.Fatalf("Size mismatch, expecting 1g, empty, got %q, %q", o.RootOverlaySize(), o.SubMountOverlaySize())
}
if o.String() != "root:memory,size=1g" {
t.Fatalf("String mismatch, expecting ll:memory,size=1g, got %q", o.String())
}
})
}