gvisor.dev/gvisor@v0.0.0-20240520182842-f9d4d51c7e0f/runsc/config/config_test.go (about)

     1  // Copyright 2020 The gVisor Authors.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package config
    16  
    17  import (
    18  	"fmt"
    19  	"reflect"
    20  	"strings"
    21  	"testing"
    22  
    23  	"github.com/google/go-cmp/cmp"
    24  	"gvisor.dev/gvisor/runsc/flag"
    25  )
    26  
    27  func TestDefault(t *testing.T) {
    28  	testFlags := flag.NewFlagSet("test", flag.ContinueOnError)
    29  	RegisterFlags(testFlags)
    30  	c, err := NewFromFlags(testFlags)
    31  	if err != nil {
    32  		t.Fatal(err)
    33  	}
    34  	// "--root" is always set to something different than the default. Reset it
    35  	// to make it easier to test that default values do not generate flags.
    36  	c.RootDir = ""
    37  
    38  	// All defaults doesn't require setting flags.
    39  	flags := c.ToFlags()
    40  	if len(flags) > 0 {
    41  		t.Errorf("default flags not set correctly for: %s", flags)
    42  	}
    43  }
    44  
    45  func TestFromFlags(t *testing.T) {
    46  	testFlags := flag.NewFlagSet("test", flag.ContinueOnError)
    47  	RegisterFlags(testFlags)
    48  	if err := testFlags.Lookup("root").Value.Set("some-path"); err != nil {
    49  		t.Errorf("Flag set: %v", err)
    50  	}
    51  	if err := testFlags.Lookup("debug").Value.Set("true"); err != nil {
    52  		t.Errorf("Flag set: %v", err)
    53  	}
    54  	if err := testFlags.Lookup("num-network-channels").Value.Set("123"); err != nil {
    55  		t.Errorf("Flag set: %v", err)
    56  	}
    57  	if err := testFlags.Lookup("network").Value.Set("none"); err != nil {
    58  		t.Errorf("Flag set: %v", err)
    59  	}
    60  
    61  	c, err := NewFromFlags(testFlags)
    62  	if err != nil {
    63  		t.Fatal(err)
    64  	}
    65  	if want := "some-path"; c.RootDir != want {
    66  		t.Errorf("RootDir=%v, want: %v", c.RootDir, want)
    67  	}
    68  	if want := true; c.Debug != want {
    69  		t.Errorf("Debug=%v, want: %v", c.Debug, want)
    70  	}
    71  	if want := 123; c.NumNetworkChannels != want {
    72  		t.Errorf("NumNetworkChannels=%v, want: %v", c.NumNetworkChannels, want)
    73  	}
    74  	if want := NetworkNone; c.Network != want {
    75  		t.Errorf("Network=%v, want: %v", c.Network, want)
    76  	}
    77  }
    78  
    79  func TestToFlagsFromFlags(t *testing.T) {
    80  	testFlags := flag.NewFlagSet("test", flag.ContinueOnError)
    81  	RegisterFlags(testFlags)
    82  	testFlags.Set("root", "some-path")
    83  	testFlags.Set("debug", "true")
    84  	testFlags.Set("profile", "false") // Matches default value.
    85  	testFlags.Set("num-network-channels", "123")
    86  	testFlags.Set("network", "none")
    87  	c, err := NewFromFlags(testFlags)
    88  	if err != nil {
    89  		t.Fatal(err)
    90  	}
    91  
    92  	flags := c.ToFlags()
    93  	if len(flags) != 5 {
    94  		t.Errorf("wrong number of flags set, want: 5, got: %d: %s", len(flags), flags)
    95  	}
    96  	t.Logf("Flags: %s", flags)
    97  	fm := map[string]string{}
    98  	for _, f := range flags {
    99  		kv := strings.Split(f, "=")
   100  		fm[kv[0]] = kv[1]
   101  	}
   102  	for name, want := range map[string]string{
   103  		"--root":                 "some-path",
   104  		"--debug":                "true",
   105  		"--profile":              "false",
   106  		"--num-network-channels": "123",
   107  		"--network":              "none",
   108  	} {
   109  		if got, ok := fm[name]; ok {
   110  			if got != want {
   111  				t.Errorf("flag %q, want: %q, got: %q", name, want, got)
   112  			}
   113  		} else {
   114  			t.Errorf("flag %q not set", name)
   115  		}
   116  	}
   117  }
   118  
   119  func TestToFlagsFromManual(t *testing.T) {
   120  	c := &Config{
   121  		RootDir:            "some-path",
   122  		Debug:              true,
   123  		ProfileEnable:      false, // Matches default flag value.
   124  		NumNetworkChannels: 123,
   125  		Network:            NetworkNone,
   126  	}
   127  
   128  	// Create a second config with flag-default values that we'll copy from.
   129  	testFlags := flag.NewFlagSet("test", flag.ContinueOnError)
   130  	RegisterFlags(testFlags)
   131  	cfgDefault, err := NewFromFlags(testFlags)
   132  	if err != nil {
   133  		t.Fatal(err)
   134  	}
   135  
   136  	// Set all the unset fields of c to their flag-default value from cfgDefault.
   137  	cfgReflect := reflect.ValueOf(c).Elem()
   138  	cfgDefaultReflect := reflect.ValueOf(cfgDefault).Elem()
   139  	cfgType := cfgReflect.Type()
   140  	for i := 0; i < cfgType.NumField(); i++ {
   141  		f := cfgType.Field(i)
   142  		name, ok := f.Tag.Lookup("flag")
   143  		if !ok {
   144  			// No flag set for this field.
   145  			continue
   146  		}
   147  		if name == "root" || name == "debug" || name == "profile" || name == "num-network-channels" || name == "network" {
   148  			continue
   149  		}
   150  		cfgReflect.Field(i).Set(cfgDefaultReflect.Field(i))
   151  	}
   152  
   153  	flags := c.ToFlags()
   154  	if len(flags) != 4 {
   155  		t.Errorf("wrong number of flags set, want: 4, got: %d: %s", len(flags), flags)
   156  	}
   157  	t.Logf("Flags: %s", flags)
   158  	fm := map[string]string{}
   159  	for _, f := range flags {
   160  		kv := strings.Split(f, "=")
   161  		fm[kv[0]] = kv[1]
   162  	}
   163  	for name, want := range map[string]string{
   164  		"--root":                 "some-path",
   165  		"--debug":                "true",
   166  		"--num-network-channels": "123",
   167  		"--network":              "none",
   168  	} {
   169  		if got, ok := fm[name]; ok {
   170  			if got != want {
   171  				t.Errorf("flag %q, want: %q, got: %q", name, want, got)
   172  			}
   173  		} else {
   174  			t.Errorf("flag %q not set", name)
   175  		}
   176  	}
   177  	if _, hasProfile := fm["--profile"]; hasProfile {
   178  		t.Error("--profile flag unexpectedly set")
   179  	}
   180  }
   181  
   182  // TestInvalidFlags checks that enum flags fail when value is not in enum set.
   183  func TestInvalidFlags(t *testing.T) {
   184  	for _, tc := range []struct {
   185  		name  string
   186  		value string
   187  		error string
   188  	}{
   189  		{
   190  			name:  "file-access",
   191  			value: "invalid",
   192  			error: "invalid file access type",
   193  		},
   194  		{
   195  			name:  "network",
   196  			value: "invalid",
   197  			error: "invalid network type",
   198  		},
   199  		{
   200  			name:  "qdisc",
   201  			value: "invalid",
   202  			error: "invalid qdisc",
   203  		},
   204  		{
   205  			name:  "watchdog-action",
   206  			value: "invalid",
   207  			error: "invalid watchdog action",
   208  		},
   209  		{
   210  			name:  "ref-leak-mode",
   211  			value: "invalid",
   212  			error: "invalid ref leak mode",
   213  		},
   214  		{
   215  			name:  "host-uds",
   216  			value: "invalid",
   217  			error: "invalid host UDS",
   218  		},
   219  		{
   220  			name:  "host-fifo",
   221  			value: "invalid",
   222  			error: "invalid host fifo",
   223  		},
   224  		{
   225  			name:  "overlay2",
   226  			value: "root:/tmp",
   227  			error: "unexpected medium: \"/tmp\"",
   228  		},
   229  		{
   230  			name:  "overlay2",
   231  			value: "root:dir=tmp",
   232  			error: "overlay host file directory should be an absolute path, got \"tmp\"",
   233  		},
   234  	} {
   235  		t.Run(tc.name, func(t *testing.T) {
   236  			testFlags := flag.NewFlagSet("test", flag.ContinueOnError)
   237  			RegisterFlags(testFlags)
   238  			if err := testFlags.Lookup(tc.name).Value.Set(tc.value); err == nil || !strings.Contains(err.Error(), tc.error) {
   239  				t.Errorf("flag.Value.Set(invalid) wrong error reported: %v", err)
   240  			}
   241  		})
   242  	}
   243  }
   244  
   245  func TestValidationFail(t *testing.T) {
   246  	for _, tc := range []struct {
   247  		name  string
   248  		flags map[string]string
   249  		error string
   250  	}{
   251  		{
   252  			name: "shared+overlay",
   253  			flags: map[string]string{
   254  				"file-access": "shared",
   255  				"overlay2":    "root:self",
   256  			},
   257  			error: "overlay flag is incompatible",
   258  		},
   259  		{
   260  			name: "network-channels",
   261  			flags: map[string]string{
   262  				"num-network-channels": "-1",
   263  			},
   264  			error: "num_network_channels must be > 0",
   265  		},
   266  		{
   267  			name: "fsgofer-host-uds+host-uds:open",
   268  			flags: map[string]string{
   269  				"fsgofer-host-uds": "true",
   270  				"host-uds":         "open",
   271  			},
   272  			error: "fsgofer-host-uds has been replaced with host-uds flag",
   273  		},
   274  		{
   275  			name: "fsgofer-host-uds+host-uds:create",
   276  			flags: map[string]string{
   277  				"fsgofer-host-uds": "true",
   278  				"host-uds":         "create",
   279  			},
   280  			error: "fsgofer-host-uds has been replaced with host-uds flag",
   281  		},
   282  		{
   283  			name: "fsgofer-host-uds+host-uds:all",
   284  			flags: map[string]string{
   285  				"fsgofer-host-uds": "true",
   286  				"host-uds":         "all",
   287  			},
   288  			error: "fsgofer-host-uds has been replaced with host-uds flag",
   289  		},
   290  		{
   291  			name: "overlay+overlay2:root",
   292  			flags: map[string]string{
   293  				"overlay":  "true",
   294  				"overlay2": "root:memory",
   295  			},
   296  			error: "overlay flag has been replaced with overlay2 flag",
   297  		},
   298  		{
   299  			name: "overlay+overlay2:all",
   300  			flags: map[string]string{
   301  				"overlay":  "true",
   302  				"overlay2": "all:memory",
   303  			},
   304  			error: "overlay flag has been replaced with overlay2 flag",
   305  		},
   306  	} {
   307  		t.Run(tc.name, func(t *testing.T) {
   308  			testFlags := flag.NewFlagSet("test", flag.ContinueOnError)
   309  			RegisterFlags(testFlags)
   310  			for name, val := range tc.flags {
   311  				if err := testFlags.Lookup(name).Value.Set(val); err != nil {
   312  					t.Errorf("%s=%q: %v", name, val, err)
   313  				}
   314  			}
   315  			if _, err := NewFromFlags(testFlags); err == nil || !strings.Contains(err.Error(), tc.error) {
   316  				t.Errorf("NewFromFlags() wrong error reported: %v", err)
   317  			}
   318  		})
   319  	}
   320  }
   321  
   322  func TestOverride(t *testing.T) {
   323  	testFlags := flag.NewFlagSet("test", flag.ContinueOnError)
   324  	RegisterFlags(testFlags)
   325  	c, err := NewFromFlags(testFlags)
   326  	if err != nil {
   327  		t.Fatal(err)
   328  	}
   329  	c.AllowFlagOverride = true
   330  
   331  	t.Run("string", func(t *testing.T) {
   332  		c.RootDir = "foobar"
   333  		if err := c.Override(testFlags, "root", "bar", false); err != nil {
   334  			t.Fatalf("Override(root, bar) failed: %v", err)
   335  		}
   336  		if c.RootDir != "bar" {
   337  			t.Errorf("Override(root, bar) didn't work: %+v", c)
   338  		}
   339  	})
   340  
   341  	t.Run("bool", func(t *testing.T) {
   342  		c.Debug = true
   343  		if err := c.Override(testFlags, "debug", "false", false); err != nil {
   344  			t.Fatalf("Override(debug, false) failed: %v", err)
   345  		}
   346  		if c.Debug {
   347  			t.Errorf("Override(debug, false) didn't work: %+v", c)
   348  		}
   349  	})
   350  
   351  	t.Run("enum", func(t *testing.T) {
   352  		c.FileAccess = FileAccessShared
   353  		if err := c.Override(testFlags, "file-access", "exclusive", false); err != nil {
   354  			t.Fatalf("Override(file-access, exclusive) failed: %v", err)
   355  		}
   356  		if c.FileAccess != FileAccessExclusive {
   357  			t.Errorf("Override(file-access, exclusive) didn't work: %+v", c)
   358  		}
   359  	})
   360  }
   361  
   362  func TestOverrideDisabled(t *testing.T) {
   363  	testFlags := flag.NewFlagSet("test", flag.ContinueOnError)
   364  	RegisterFlags(testFlags)
   365  	c, err := NewFromFlags(testFlags)
   366  	if err != nil {
   367  		t.Fatal(err)
   368  	}
   369  	const errMsg = "flag override disabled"
   370  	if err := c.Override(testFlags, "root", "path", false); err == nil || !strings.Contains(err.Error(), errMsg) {
   371  		t.Errorf("Override() wrong error: %v", err)
   372  	}
   373  }
   374  
   375  func TestOverrideError(t *testing.T) {
   376  	testFlags := flag.NewFlagSet("test", flag.ContinueOnError)
   377  	RegisterFlags(testFlags)
   378  	c, err := NewFromFlags(testFlags)
   379  	if err != nil {
   380  		t.Fatal(err)
   381  	}
   382  	c.AllowFlagOverride = true
   383  	for _, tc := range []struct {
   384  		name  string
   385  		value string
   386  		error string
   387  	}{
   388  		{
   389  			name:  "invalid",
   390  			value: "valid",
   391  			error: `flag "invalid" not found`,
   392  		},
   393  		{
   394  			name:  "debug",
   395  			value: "invalid",
   396  			error: "error setting flag debug",
   397  		},
   398  		{
   399  			name:  "file-access",
   400  			value: "invalid",
   401  			error: "invalid file access type",
   402  		},
   403  	} {
   404  		t.Run(tc.name, func(t *testing.T) {
   405  			if err := c.Override(testFlags, tc.name, tc.value, false); err == nil || !strings.Contains(err.Error(), tc.error) {
   406  				t.Errorf("Override(%q, %q) wrong error: %v", tc.name, tc.value, err)
   407  			}
   408  		})
   409  	}
   410  }
   411  
   412  func TestOverrideAllowlist(t *testing.T) {
   413  	testFlags := flag.NewFlagSet("test", flag.ContinueOnError)
   414  	RegisterFlags(testFlags)
   415  	c, err := NewFromFlags(testFlags)
   416  	if err != nil {
   417  		t.Fatal(err)
   418  	}
   419  	for _, tc := range []struct {
   420  		flag  string
   421  		value string
   422  		force bool
   423  		error string
   424  	}{
   425  		{
   426  			flag:  "debug",
   427  			value: "true",
   428  		},
   429  		{
   430  			flag:  "debug",
   431  			value: "123",
   432  			error: "error setting flag",
   433  		},
   434  		{
   435  			flag:  "oci-seccomp",
   436  			value: "true",
   437  		},
   438  		{
   439  			flag:  "oci-seccomp",
   440  			value: "false",
   441  			error: `disabling "oci-seccomp" requires flag`,
   442  		},
   443  		{
   444  			flag:  "oci-seccomp",
   445  			value: "123",
   446  			error: "invalid syntax",
   447  		},
   448  		{
   449  			flag:  "profile",
   450  			value: "true",
   451  			error: "flag override disabled",
   452  		},
   453  		{
   454  			flag:  "profile",
   455  			value: "true",
   456  			force: true,
   457  		},
   458  		{
   459  			flag:  "profile",
   460  			value: "123",
   461  			error: "flag override disabled",
   462  		},
   463  	} {
   464  		t.Run(tc.flag, func(t *testing.T) {
   465  			err := c.Override(testFlags, tc.flag, tc.value, tc.force)
   466  			if len(tc.error) == 0 {
   467  				if err != nil {
   468  					t.Errorf("Unexpected error: %v", err)
   469  				}
   470  			} else if err == nil || !strings.Contains(err.Error(), tc.error) {
   471  				t.Errorf("Override(%q, %q) wrong error: %v", tc.flag, tc.value, err)
   472  			}
   473  		})
   474  	}
   475  }
   476  
   477  func TestBundles(t *testing.T) {
   478  	noChange := func(t *testing.T, old, new *Config) {
   479  		t.Helper()
   480  		if diff := cmp.Diff(old, new, cmp.AllowUnexported(Config{}, Overlay2{})); diff != "" {
   481  			t.Errorf("different configs:\n%+v\nvs\n%+v\nDiff:\n%s", old, new, diff)
   482  		}
   483  	}
   484  	for _, test := range []struct {
   485  		// Name of the test.
   486  		Name string
   487  
   488  		// List of bundles that exist for the purpose of this test.
   489  		BundleConfig map[BundleName]Bundle
   490  
   491  		// Command-line arguments passed as explicit flags.
   492  		CommandLine []string
   493  
   494  		// Names of the bundles to apply.
   495  		Bundles []BundleName
   496  
   497  		// Whether we expect applying bundles to fail.
   498  		WantErr bool
   499  
   500  		// If bundles were successfully applied, this function is called to compare
   501  		// pre-bundle-application and post-bundle-application configs.
   502  		Verify func(t *testing.T, old, new *Config)
   503  	}{
   504  		{
   505  			Name:         "empty bundle",
   506  			BundleConfig: map[BundleName]Bundle{"empty": {}},
   507  			Bundles:      []BundleName{"empty"},
   508  			Verify:       noChange,
   509  		},
   510  		{
   511  			Name: "no-op bundle",
   512  			BundleConfig: map[BundleName]Bundle{
   513  				"no-debug": {
   514  					"debug": "false",
   515  				},
   516  			},
   517  			Bundles: []BundleName{"no-debug"},
   518  			Verify:  noChange,
   519  		},
   520  		{
   521  			Name: "invalid flag",
   522  			BundleConfig: map[BundleName]Bundle{
   523  				"invalid-flag": {
   524  					"not-a-real-flag": "nope.avi",
   525  				},
   526  			},
   527  			Bundles: []BundleName{"invalid-flag"},
   528  			WantErr: true,
   529  		},
   530  		{
   531  			Name: "duplicate no-op bundles",
   532  			BundleConfig: map[BundleName]Bundle{
   533  				"empty": {},
   534  				"no-debug": {
   535  					"debug": "false",
   536  				},
   537  			},
   538  			Bundles: []BundleName{"no-debug", "no-debug"},
   539  			Verify:  noChange,
   540  		},
   541  		{
   542  			Name: "simple bundle",
   543  			BundleConfig: map[BundleName]Bundle{
   544  				"empty": {},
   545  				"debug": {
   546  					"debug": "true",
   547  				},
   548  				"no-debug": {
   549  					"debug": "false",
   550  				},
   551  			},
   552  			Bundles: []BundleName{"debug"},
   553  			Verify: func(t *testing.T, old, new *Config) {
   554  				t.Helper()
   555  				if old.Debug {
   556  					t.Error("debug was previously set to true")
   557  				}
   558  				if !new.Debug {
   559  					t.Error("debug was not set to true")
   560  				}
   561  			},
   562  		},
   563  		{
   564  			Name: "incompatible bundles",
   565  			BundleConfig: map[BundleName]Bundle{
   566  				"debug": {
   567  					"debug": "true",
   568  				},
   569  				"no-debug": {
   570  					"debug": "false",
   571  				},
   572  			},
   573  			Bundles: []BundleName{"debug", "no-debug"},
   574  			WantErr: true,
   575  		},
   576  		{
   577  			Name: "compatible bundles",
   578  			BundleConfig: map[BundleName]Bundle{
   579  				"debug": {
   580  					"debug": "true",
   581  				},
   582  				"debug-and-profile": {
   583  					"debug":   "true",
   584  					"profile": "true",
   585  				},
   586  			},
   587  			Bundles: []BundleName{"debug", "debug-and-profile"},
   588  			Verify: func(t *testing.T, old, new *Config) {
   589  				t.Helper()
   590  				if old.Debug || old.ProfileEnable {
   591  					t.Error("debug/profiling was previously set to true")
   592  				}
   593  				if !new.Debug {
   594  					t.Error("debug was not set to true")
   595  				}
   596  				if !new.ProfileEnable {
   597  					t.Error("profiling was not set to true")
   598  				}
   599  			},
   600  		},
   601  		{
   602  			Name: "bundle takes precedence over command-line value",
   603  			BundleConfig: map[BundleName]Bundle{
   604  				"no-debug": {
   605  					"debug": "false",
   606  				},
   607  			},
   608  			CommandLine: []string{"-debug=true"},
   609  			Bundles:     []BundleName{"no-debug"},
   610  			Verify: func(t *testing.T, old, new *Config) {
   611  				t.Helper()
   612  				if new.Debug {
   613  					t.Error("debug is still true")
   614  				}
   615  			},
   616  		},
   617  		{
   618  			Name: "command line matching bundle value",
   619  			BundleConfig: map[BundleName]Bundle{
   620  				"debug": {
   621  					"debug": "true",
   622  				},
   623  			},
   624  			CommandLine: []string{"-debug=true"},
   625  			Bundles:     []BundleName{"debug"},
   626  			Verify: func(t *testing.T, old, new *Config) {
   627  				t.Helper()
   628  				noChange(t, old, new)
   629  				if !new.Debug {
   630  					t.Error("debug was set to false")
   631  				}
   632  			},
   633  		},
   634  	} {
   635  		t.Run(test.Name, func(t *testing.T) {
   636  			oldBundles := Bundles
   637  			defer func() {
   638  				Bundles = oldBundles
   639  			}()
   640  			Bundles = test.BundleConfig
   641  			flagSet := flag.NewFlagSet(test.Name, flag.ContinueOnError)
   642  			RegisterFlags(flagSet)
   643  			if err := flagSet.Parse(test.CommandLine); err != nil {
   644  				t.Fatalf("cannot parse command line %q: %v", test.CommandLine, err)
   645  			}
   646  			cfg, err := NewFromFlags(flagSet)
   647  			if err != nil {
   648  				t.Fatalf("cannot generate config from flags: %v", err)
   649  			}
   650  			oldCfg := *cfg
   651  			err = cfg.ApplyBundles(flagSet, test.Bundles...)
   652  			if test.WantErr && err == nil {
   653  				t.Error("got no error, but expected one")
   654  			}
   655  			if !test.WantErr && err != nil {
   656  				t.Errorf("got unexpected error: %v", err)
   657  			}
   658  			if t.Failed() {
   659  				return
   660  			}
   661  			if err != nil && test.Verify != nil {
   662  				t.Error("cannot specify Verify function for erroring tests")
   663  			}
   664  			if err == nil && test.Verify != nil {
   665  				test.Verify(t, &oldCfg, cfg)
   666  			}
   667  		})
   668  	}
   669  }
   670  
   671  func TestBundleValidate(t *testing.T) {
   672  	defaultVerify := func(err error) error { return err }
   673  	for _, tc := range []struct {
   674  		name   string
   675  		bundle Bundle
   676  		verify func(err error) error
   677  	}{
   678  		{
   679  			name:   "empty bundle",
   680  			bundle: Bundle(map[string]string{}),
   681  			verify: defaultVerify,
   682  		},
   683  		{
   684  			name:   "invalid flag bundle",
   685  			bundle: Bundle(map[string]string{"not-a-real-flag": "true"}),
   686  			verify: func(err error) error {
   687  				want := `unknown flag "not-a-real-flag"`
   688  				if !strings.Contains(err.Error(), want) {
   689  					return fmt.Errorf("mismatch error: got: %q want: %q", err.Error(), want)
   690  				}
   691  				return nil
   692  			},
   693  		},
   694  		{
   695  			name:   "invalid value",
   696  			bundle: Bundle(map[string]string{"debug": "invalid"}),
   697  			verify: func(err error) error {
   698  				want := `parsing "invalid": invalid syntax`
   699  				if !strings.Contains(err.Error(), want) {
   700  					return fmt.Errorf("mismatch error: got: %q want: %q", err.Error(), want)
   701  				}
   702  				return nil
   703  			},
   704  		},
   705  		{
   706  			name:   "valid flag bundle",
   707  			bundle: Bundle(map[string]string{"debug": "true"}),
   708  			verify: defaultVerify,
   709  		},
   710  	} {
   711  		t.Run(tc.name, func(t *testing.T) {
   712  			if err := tc.verify(tc.bundle.Validate()); err != nil {
   713  				t.Fatalf("Validate failed: %v", err)
   714  			}
   715  		})
   716  	}
   717  }
   718  
   719  func TestToContinerdConfigTOML(t *testing.T) {
   720  	header := `binary_name = "%s"
   721  root = "%s"
   722  `
   723  	opt := ContainerdConfigOptions{
   724  		BinaryPath: "/path/to/runsc",
   725  		RootPath:   "/path/to/root",
   726  	}
   727  	header = fmt.Sprintf(header, opt.BinaryPath, opt.RootPath)
   728  
   729  	for _, tc := range []struct {
   730  		name        string
   731  		bundle      Bundle
   732  		want        string
   733  		createError error
   734  	}{
   735  		{
   736  			name: "empty bundle",
   737  			want: header,
   738  		},
   739  		{
   740  			name:   "valid flag bundle",
   741  			bundle: Bundle(map[string]string{"debug": "true"}),
   742  			want: func() string {
   743  				flagStr := "[runsc_config]\n  debug = \"true\"\n"
   744  				return strings.Join([]string{header, flagStr}, "")
   745  			}(),
   746  		},
   747  		{
   748  			name:        "invalid flag bundle",
   749  			bundle:      Bundle(map[string]string{"not-a-real-flag": "true"}),
   750  			createError: fmt.Errorf("unknown flag \"not-a-real-flag\""),
   751  		},
   752  	} {
   753  		t.Run(tc.name, func(t *testing.T) {
   754  			cfg, err := NewFromBundle(tc.bundle)
   755  			if tc.createError != nil {
   756  				if err == nil {
   757  					t.Fatalf("got no error, but expected one")
   758  				}
   759  				if !strings.Contains(err.Error(), tc.createError.Error()) {
   760  					t.Fatalf("mismatch error: got: %q want: %q", err.Error(), tc.createError.Error())
   761  				}
   762  				return
   763  			}
   764  
   765  			if err != nil {
   766  				t.Fatalf("NewFromBundle failed: %v", err)
   767  			}
   768  
   769  			toml, err := cfg.ToContainerdConfigTOML(opt)
   770  			if err != nil {
   771  				t.Fatalf("ToContainerdConfigTOML failed: %v", err)
   772  			}
   773  			if diff := cmp.Diff(tc.want, toml); diff != "" {
   774  				t.Fatalf("mismatch strings: %s", diff)
   775  			}
   776  
   777  		})
   778  	}
   779  
   780  }