container: access test case by index in helper
	
		
			
	
		
	
	
		
	
		
			All checks were successful
		
		
	
	
		
			
				
	
				Test / Create distribution (push) Successful in 24s
				
			
		
			
				
	
				Test / Hakurei (push) Successful in 40s
				
			
		
			
				
	
				Test / Sandbox (push) Successful in 38s
				
			
		
			
				
	
				Test / Hakurei (race detector) (push) Successful in 41s
				
			
		
			
				
	
				Test / Sandbox (race detector) (push) Successful in 38s
				
			
		
			
				
	
				Test / Planterette (push) Successful in 39s
				
			
		
			
				
	
				Test / Flake checks (push) Successful in 1m17s
				
			
		
		
	
	
				
					
				
			
		
			All checks were successful
		
		
	
	Test / Create distribution (push) Successful in 24s
				
			Test / Hakurei (push) Successful in 40s
				
			Test / Sandbox (push) Successful in 38s
				
			Test / Hakurei (race detector) (push) Successful in 41s
				
			Test / Sandbox (race detector) (push) Successful in 38s
				
			Test / Planterette (push) Successful in 39s
				
			Test / Flake checks (push) Successful in 1m17s
				
			This is more elegant and allows for much easier extension of the tests. Mountinfo is still serialised however due to libPaths nondeterminism. Signed-off-by: Ophestra <cat@gensokyo.uk>
This commit is contained in:
		
							parent
							
								
									625632c593
								
							
						
					
					
						commit
						bd3fa53a55
					
				| @ -4,13 +4,17 @@ import ( | |||||||
| 	"bytes" | 	"bytes" | ||||||
| 	"context" | 	"context" | ||||||
| 	"encoding/gob" | 	"encoding/gob" | ||||||
|  | 	"errors" | ||||||
|  | 	"fmt" | ||||||
| 	"log" | 	"log" | ||||||
| 	"os" | 	"os" | ||||||
|  | 	"strconv" | ||||||
| 	"strings" | 	"strings" | ||||||
| 	"syscall" | 	"syscall" | ||||||
| 	"testing" | 	"testing" | ||||||
| 	"time" | 	"time" | ||||||
| 
 | 
 | ||||||
|  | 	"hakurei.app/command" | ||||||
| 	"hakurei.app/container" | 	"hakurei.app/container" | ||||||
| 	"hakurei.app/container/seccomp" | 	"hakurei.app/container/seccomp" | ||||||
| 	"hakurei.app/container/vfs" | 	"hakurei.app/container/vfs" | ||||||
| @ -23,11 +27,60 @@ import ( | |||||||
| const ( | const ( | ||||||
| 	ignore  = "\x00" | 	ignore  = "\x00" | ||||||
| 	ignoreV = -1 | 	ignoreV = -1 | ||||||
|  | 
 | ||||||
|  | 	pathWantMnt = "/etc/hakurei/want-mnt" | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| func TestMain(m *testing.M) { | var containerTestCases = []struct { | ||||||
| 	container.TryArgv0(hlog.Output{}, hlog.Prepare, internal.InstallOutput) | 	name    string | ||||||
| 	os.Exit(m.Run()) | 	filter  bool | ||||||
|  | 	session bool | ||||||
|  | 	net     bool | ||||||
|  | 	ops     *container.Ops | ||||||
|  | 
 | ||||||
|  | 	mnt []*vfs.MountInfoEntry | ||||||
|  | 	uid int | ||||||
|  | 	gid int | ||||||
|  | 
 | ||||||
|  | 	rules   []seccomp.NativeRule | ||||||
|  | 	flags   seccomp.ExportFlag | ||||||
|  | 	presets seccomp.FilterPreset | ||||||
|  | }{ | ||||||
|  | 	{"minimal", true, false, false, | ||||||
|  | 		new(container.Ops), nil, | ||||||
|  | 		1000, 100, nil, 0, seccomp.PresetStrict}, | ||||||
|  | 	{"allow", true, true, true, | ||||||
|  | 		new(container.Ops), nil, | ||||||
|  | 		1000, 100, nil, 0, seccomp.PresetExt | seccomp.PresetDenyDevel}, | ||||||
|  | 	{"no filter", false, true, true, | ||||||
|  | 		new(container.Ops), nil, | ||||||
|  | 		1000, 100, nil, 0, seccomp.PresetExt}, | ||||||
|  | 	{"custom rules", true, true, true, | ||||||
|  | 		new(container.Ops), nil, | ||||||
|  | 		1, 31, []seccomp.NativeRule{{seccomp.ScmpSyscall(syscall.SYS_SETUID), seccomp.ScmpErrno(syscall.EPERM), nil}}, 0, seccomp.PresetExt}, | ||||||
|  | 	{"tmpfs", true, false, false, | ||||||
|  | 		new(container.Ops). | ||||||
|  | 			Tmpfs(hst.Tmp, 0, 0755), | ||||||
|  | 		[]*vfs.MountInfoEntry{ | ||||||
|  | 			ent("/", hst.Tmp, "rw,nosuid,nodev,relatime", "tmpfs", "tmpfs", ignore), | ||||||
|  | 		}, | ||||||
|  | 		9, 9, nil, 0, seccomp.PresetStrict}, | ||||||
|  | 	{"dev", true, true /* go test output is not a tty */, false, | ||||||
|  | 		new(container.Ops). | ||||||
|  | 			Dev("/dev"). | ||||||
|  | 			Mqueue("/dev/mqueue"), | ||||||
|  | 		[]*vfs.MountInfoEntry{ | ||||||
|  | 			ent("/", "/dev", "rw,nosuid,nodev,relatime", "tmpfs", "devtmpfs", ignore), | ||||||
|  | 			ent("/null", "/dev/null", "rw,nosuid", "devtmpfs", "devtmpfs", ignore), | ||||||
|  | 			ent("/zero", "/dev/zero", "rw,nosuid", "devtmpfs", "devtmpfs", ignore), | ||||||
|  | 			ent("/full", "/dev/full", "rw,nosuid", "devtmpfs", "devtmpfs", ignore), | ||||||
|  | 			ent("/random", "/dev/random", "rw,nosuid", "devtmpfs", "devtmpfs", ignore), | ||||||
|  | 			ent("/urandom", "/dev/urandom", "rw,nosuid", "devtmpfs", "devtmpfs", ignore), | ||||||
|  | 			ent("/tty", "/dev/tty", "rw,nosuid", "devtmpfs", "devtmpfs", ignore), | ||||||
|  | 			ent("/", "/dev/pts", "rw,nosuid,noexec,relatime", "devpts", "devpts", "rw,mode=620,ptmxmode=666"), | ||||||
|  | 			ent("/", "/dev/mqueue", "rw,nosuid,nodev,noexec,relatime", "mqueue", "mqueue", "rw"), | ||||||
|  | 		}, | ||||||
|  | 		1971, 100, nil, 0, seccomp.PresetStrict}, | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func TestContainer(t *testing.T) { | func TestContainer(t *testing.T) { | ||||||
| @ -39,89 +92,30 @@ func TestContainer(t *testing.T) { | |||||||
| 		t.Cleanup(func() { container.SetOutput(oldOutput) }) | 		t.Cleanup(func() { container.SetOutput(oldOutput) }) | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	testCases := []struct { | 	for i, tc := range containerTestCases { | ||||||
| 		name    string |  | ||||||
| 		filter  bool |  | ||||||
| 		session bool |  | ||||||
| 		net     bool |  | ||||||
| 		ops     *container.Ops |  | ||||||
| 		mnt     []*vfs.MountInfoEntry |  | ||||||
| 		host    string |  | ||||||
| 		rules   []seccomp.NativeRule |  | ||||||
| 		flags   seccomp.ExportFlag |  | ||||||
| 		presets seccomp.FilterPreset |  | ||||||
| 	}{ |  | ||||||
| 		{"minimal", true, false, false, |  | ||||||
| 			new(container.Ops), nil, "test-minimal", |  | ||||||
| 			nil, 0, seccomp.PresetStrict}, |  | ||||||
| 		{"allow", true, true, true, |  | ||||||
| 			new(container.Ops), nil, "test-minimal", |  | ||||||
| 			nil, 0, seccomp.PresetExt | seccomp.PresetDenyDevel}, |  | ||||||
| 		{"no filter", false, true, true, |  | ||||||
| 			new(container.Ops), nil, "test-no-filter", |  | ||||||
| 			nil, 0, seccomp.PresetExt}, |  | ||||||
| 		{"custom rules", true, true, true, |  | ||||||
| 			new(container.Ops), nil, "test-no-filter", |  | ||||||
| 			[]seccomp.NativeRule{ |  | ||||||
| 				{seccomp.ScmpSyscall(syscall.SYS_SETUID), seccomp.ScmpErrno(syscall.EPERM), nil}, |  | ||||||
| 			}, 0, seccomp.PresetExt}, |  | ||||||
| 		{"tmpfs", true, false, false, |  | ||||||
| 			new(container.Ops). |  | ||||||
| 				Tmpfs(hst.Tmp, 0, 0755), |  | ||||||
| 			[]*vfs.MountInfoEntry{ |  | ||||||
| 				e("/", hst.Tmp, "rw,nosuid,nodev,relatime", "tmpfs", "tmpfs", ignore), |  | ||||||
| 			}, "test-tmpfs", |  | ||||||
| 			nil, 0, seccomp.PresetStrict}, |  | ||||||
| 		{"dev", true, true /* go test output is not a tty */, false, |  | ||||||
| 			new(container.Ops). |  | ||||||
| 				Dev("/dev"). |  | ||||||
| 				Mqueue("/dev/mqueue"), |  | ||||||
| 			[]*vfs.MountInfoEntry{ |  | ||||||
| 				e("/", "/dev", "rw,nosuid,nodev,relatime", "tmpfs", "devtmpfs", ignore), |  | ||||||
| 				e("/null", "/dev/null", "rw,nosuid", "devtmpfs", "devtmpfs", ignore), |  | ||||||
| 				e("/zero", "/dev/zero", "rw,nosuid", "devtmpfs", "devtmpfs", ignore), |  | ||||||
| 				e("/full", "/dev/full", "rw,nosuid", "devtmpfs", "devtmpfs", ignore), |  | ||||||
| 				e("/random", "/dev/random", "rw,nosuid", "devtmpfs", "devtmpfs", ignore), |  | ||||||
| 				e("/urandom", "/dev/urandom", "rw,nosuid", "devtmpfs", "devtmpfs", ignore), |  | ||||||
| 				e("/tty", "/dev/tty", "rw,nosuid", "devtmpfs", "devtmpfs", ignore), |  | ||||||
| 				e("/", "/dev/pts", "rw,nosuid,noexec,relatime", "devpts", "devpts", "rw,mode=620,ptmxmode=666"), |  | ||||||
| 				e("/", "/dev/mqueue", "rw,nosuid,nodev,noexec,relatime", "mqueue", "mqueue", "rw"), |  | ||||||
| 			}, "", |  | ||||||
| 			nil, 0, seccomp.PresetStrict}, |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	for _, tc := range testCases { |  | ||||||
| 		t.Run(tc.name, func(t *testing.T) { | 		t.Run(tc.name, func(t *testing.T) { | ||||||
| 			ctx, cancel := context.WithTimeout(t.Context(), 5*time.Second) | 			ctx, cancel := context.WithTimeout(t.Context(), 5*time.Second) | ||||||
| 			defer cancel() | 			defer cancel() | ||||||
| 
 | 
 | ||||||
| 			c := container.New(ctx, "/usr/bin/sandbox.test", "-test.v", | 			hostname := hostnameFromTestCase(tc.name) | ||||||
| 				"-test.run=TestHelperCheckContainer", "--", "check", tc.host) | 			c := container.New(ctx, os.Args[0], "container", strconv.Itoa(i)) | ||||||
| 			c.Uid = 1000 | 			prepareHelper(c) | ||||||
| 			c.Gid = 100 | 			c.Uid = tc.uid | ||||||
| 			c.Hostname = tc.host | 			c.Gid = tc.gid | ||||||
|  | 			c.Hostname = hostname | ||||||
| 			c.Stdout, c.Stderr = os.Stdout, os.Stderr | 			c.Stdout, c.Stderr = os.Stdout, os.Stderr | ||||||
| 			c.Ops = tc.ops | 			*c.Ops = append(*c.Ops, *tc.ops...) | ||||||
| 			c.SeccompRules = tc.rules | 			c.SeccompRules = tc.rules | ||||||
| 			c.SeccompFlags = tc.flags | seccomp.AllowMultiarch | 			c.SeccompFlags = tc.flags | seccomp.AllowMultiarch | ||||||
| 			c.SeccompPresets = tc.presets | 			c.SeccompPresets = tc.presets | ||||||
| 			c.SeccompDisable = !tc.filter | 			c.SeccompDisable = !tc.filter | ||||||
| 			c.RetainSession = tc.session | 			c.RetainSession = tc.session | ||||||
| 			c.HostNet = tc.net | 			c.HostNet = tc.net | ||||||
| 			if c.Args[5] == "" { |  | ||||||
| 				if name, err := os.Hostname(); err != nil { |  | ||||||
| 					t.Fatalf("cannot get hostname: %v", err) |  | ||||||
| 				} else { |  | ||||||
| 					c.Args[5] = name |  | ||||||
| 				} |  | ||||||
| 			} |  | ||||||
| 
 | 
 | ||||||
| 			c. | 			c. | ||||||
| 				Tmpfs("/tmp", 0, 0755). | 				Tmpfs("/tmp", 0, 0755). | ||||||
| 				Bind(os.Args[0], os.Args[0], 0). | 				Bind(os.Args[0], os.Args[0], 0). | ||||||
| 				Mkdir("/usr/bin", 0755). | 				Place("/etc/hostname", []byte(hostname)) | ||||||
| 				Link(os.Args[0], "/usr/bin/sandbox.test"). |  | ||||||
| 				Place("/etc/hostname", []byte(c.Args[5])) |  | ||||||
| 			// in case test has cgo enabled | 			// in case test has cgo enabled | ||||||
| 			var libPaths []string | 			var libPaths []string | ||||||
| 			if entries, err := ldd.Exec(ctx, os.Args[0]); err != nil { | 			if entries, err := ldd.Exec(ctx, os.Args[0]); err != nil { | ||||||
| @ -135,23 +129,33 @@ func TestContainer(t *testing.T) { | |||||||
| 			// needs /proc to check mountinfo | 			// needs /proc to check mountinfo | ||||||
| 			c.Proc("/proc") | 			c.Proc("/proc") | ||||||
| 
 | 
 | ||||||
|  | 			// mountinfo cannot be resolved directly by helper due to libPaths nondeterminism | ||||||
| 			mnt := make([]*vfs.MountInfoEntry, 0, 3+len(libPaths)) | 			mnt := make([]*vfs.MountInfoEntry, 0, 3+len(libPaths)) | ||||||
| 			mnt = append(mnt, e("/sysroot", "/", "rw,nosuid,nodev,relatime", "tmpfs", "rootfs", ignore)) | 			mnt = append(mnt, ent("/sysroot", "/", "rw,nosuid,nodev,relatime", "tmpfs", "rootfs", ignore)) | ||||||
| 			mnt = append(mnt, tc.mnt...) | 			mnt = append(mnt, tc.mnt...) | ||||||
| 			mnt = append(mnt, | 			mnt = append(mnt, | ||||||
| 				e("/", "/tmp", "rw,nosuid,nodev,relatime", "tmpfs", "tmpfs", ignore), | 				// Tmpfs("/tmp", 0, 0755) | ||||||
| 				e(ignore, os.Args[0], "ro,nosuid,nodev,relatime", ignore, ignore, ignore), | 				ent("/", "/tmp", "rw,nosuid,nodev,relatime", "tmpfs", "tmpfs", ignore), | ||||||
| 				e(ignore, "/etc/hostname", "ro,nosuid,nodev,relatime", "tmpfs", "rootfs", ignore), | 				// Bind(os.Args[0], os.Args[0], 0) | ||||||
|  | 				ent(ignore, os.Args[0], "ro,nosuid,nodev,relatime", ignore, ignore, ignore), | ||||||
|  | 				// Place("/etc/hostname", []byte(hostname)) | ||||||
|  | 				ent(ignore, "/etc/hostname", "ro,nosuid,nodev,relatime", "tmpfs", "rootfs", ignore), | ||||||
| 			) | 			) | ||||||
| 			for _, name := range libPaths { | 			for _, name := range libPaths { | ||||||
| 				mnt = append(mnt, e(ignore, name, "ro,nosuid,nodev,relatime", ignore, ignore, ignore)) | 				// Bind(name, name, 0) | ||||||
|  | 				mnt = append(mnt, ent(ignore, name, "ro,nosuid,nodev,relatime", ignore, ignore, ignore)) | ||||||
| 			} | 			} | ||||||
| 			mnt = append(mnt, e("/", "/proc", "rw,nosuid,nodev,noexec,relatime", "proc", "proc", "rw")) | 			mnt = append(mnt, | ||||||
|  | 				// Proc("/proc") | ||||||
|  | 				ent("/", "/proc", "rw,nosuid,nodev,noexec,relatime", "proc", "proc", "rw"), | ||||||
|  | 				// Place(pathWantMnt, want.Bytes()) | ||||||
|  | 				ent(ignore, pathWantMnt, "ro,nosuid,nodev,relatime", "tmpfs", "rootfs", ignore), | ||||||
|  | 			) | ||||||
| 			want := new(bytes.Buffer) | 			want := new(bytes.Buffer) | ||||||
| 			if err := gob.NewEncoder(want).Encode(mnt); err != nil { | 			if err := gob.NewEncoder(want).Encode(mnt); err != nil { | ||||||
| 				t.Fatalf("cannot serialise expected mount points: %v", err) | 				t.Fatalf("cannot serialise expected mount points: %v", err) | ||||||
| 			} | 			} | ||||||
| 			c.Stdin = want | 			c.Place(pathWantMnt, want.Bytes()) | ||||||
| 
 | 
 | ||||||
| 			if err := c.Start(); err != nil { | 			if err := c.Start(); err != nil { | ||||||
| 				hlog.PrintBaseError(err, "start:") | 				hlog.PrintBaseError(err, "start:") | ||||||
| @ -168,7 +172,7 @@ func TestContainer(t *testing.T) { | |||||||
| 	} | 	} | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func e(root, target, vfsOptstr, fsType, source, fsOptstr string) *vfs.MountInfoEntry { | func ent(root, target, vfsOptstr, fsType, source, fsOptstr string) *vfs.MountInfoEntry { | ||||||
| 	return &vfs.MountInfoEntry{ | 	return &vfs.MountInfoEntry{ | ||||||
| 		ID:        ignoreV, | 		ID:        ignoreV, | ||||||
| 		Parent:    ignoreV, | 		Parent:    ignoreV, | ||||||
| @ -183,6 +187,10 @@ func e(root, target, vfsOptstr, fsType, source, fsOptstr string) *vfs.MountInfoE | |||||||
| 	} | 	} | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | func hostnameFromTestCase(name string) string { | ||||||
|  | 	return "test-" + strings.Join(strings.Fields(name), "-") | ||||||
|  | } | ||||||
|  | 
 | ||||||
| func TestContainerString(t *testing.T) { | func TestContainerString(t *testing.T) { | ||||||
| 	c := container.New(t.Context(), "ldd", "/usr/bin/env") | 	c := container.New(t.Context(), "ldd", "/usr/bin/env") | ||||||
| 	c.SeccompFlags |= seccomp.AllowMultiarch | 	c.SeccompFlags |= seccomp.AllowMultiarch | ||||||
| @ -196,72 +204,93 @@ func TestContainerString(t *testing.T) { | |||||||
| 	} | 	} | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func TestHelperCheckContainer(t *testing.T) { | func init() { | ||||||
| 	if len(os.Args) != 6 || os.Args[4] != "check" { | 	helperCommands = append(helperCommands, func(c command.Command) { | ||||||
| 		return | 		c.Command("container", command.UsageInternal, func(args []string) error { | ||||||
| 	} | 			if len(args) != 1 { | ||||||
| 
 | 				return syscall.EINVAL | ||||||
| 	t.Run("user", func(t *testing.T) { |  | ||||||
| 		if uid := syscall.Getuid(); uid != 1000 { |  | ||||||
| 			t.Errorf("Getuid: %d, want 1000", uid) |  | ||||||
| 		} |  | ||||||
| 		if gid := syscall.Getgid(); gid != 100 { |  | ||||||
| 			t.Errorf("Getgid: %d, want 100", gid) |  | ||||||
| 		} |  | ||||||
| 	}) |  | ||||||
| 	t.Run("hostname", func(t *testing.T) { |  | ||||||
| 		if name, err := os.Hostname(); err != nil { |  | ||||||
| 			t.Fatalf("cannot get hostname: %v", err) |  | ||||||
| 		} else if name != os.Args[5] { |  | ||||||
| 			t.Errorf("Hostname: %q, want %q", name, os.Args[5]) |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		if p, err := os.ReadFile("/etc/hostname"); err != nil { |  | ||||||
| 			t.Fatalf("%v", err) |  | ||||||
| 		} else if string(p) != os.Args[5] { |  | ||||||
| 			t.Errorf("/etc/hostname: %q, want %q", string(p), os.Args[5]) |  | ||||||
| 		} |  | ||||||
| 	}) |  | ||||||
| 	t.Run("mount", func(t *testing.T) { |  | ||||||
| 		var mnt []*vfs.MountInfoEntry |  | ||||||
| 		if err := gob.NewDecoder(os.Stdin).Decode(&mnt); err != nil { |  | ||||||
| 			t.Fatalf("cannot receive expected mount points: %v", err) |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		var d *vfs.MountInfoDecoder |  | ||||||
| 		if f, err := os.Open("/proc/self/mountinfo"); err != nil { |  | ||||||
| 			t.Fatalf("cannot open mountinfo: %v", err) |  | ||||||
| 		} else { |  | ||||||
| 			d = vfs.NewMountInfoDecoder(f) |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		i := 0 |  | ||||||
| 		for cur := range d.Entries() { |  | ||||||
| 			if i == len(mnt) { |  | ||||||
| 				t.Errorf("got more than %d entries", len(mnt)) |  | ||||||
| 				break |  | ||||||
| 			} | 			} | ||||||
| 
 | 			tc := containerTestCases[0] | ||||||
| 			// ugly hack but should be reliable and is less likely to false negative than comparing by parsed flags | 			if i, err := strconv.Atoi(args[0]); err != nil { | ||||||
| 			cur.VfsOptstr = strings.TrimSuffix(cur.VfsOptstr, ",relatime") | 				return fmt.Errorf("cannot parse test case index: %v", err) | ||||||
| 			cur.VfsOptstr = strings.TrimSuffix(cur.VfsOptstr, ",noatime") |  | ||||||
| 			mnt[i].VfsOptstr = strings.TrimSuffix(mnt[i].VfsOptstr, ",relatime") |  | ||||||
| 			mnt[i].VfsOptstr = strings.TrimSuffix(mnt[i].VfsOptstr, ",noatime") |  | ||||||
| 
 |  | ||||||
| 			if !cur.EqualWithIgnore(mnt[i], "\x00") { |  | ||||||
| 				t.Errorf("[FAIL] %s", cur) |  | ||||||
| 			} else { | 			} else { | ||||||
| 				t.Logf("[ OK ] %s", cur) | 				tc = containerTestCases[i] | ||||||
| 			} | 			} | ||||||
| 
 | 
 | ||||||
| 			i++ | 			if uid := syscall.Getuid(); uid != tc.uid { | ||||||
| 		} | 				return fmt.Errorf("uid: %d, want %d", uid, tc.uid) | ||||||
| 		if err := d.Err(); err != nil { | 			} | ||||||
| 			t.Errorf("cannot parse mountinfo: %v", err) | 			if gid := syscall.Getgid(); gid != tc.gid { | ||||||
| 		} | 				return fmt.Errorf("gid: %d, want %d", gid, tc.gid) | ||||||
|  | 			} | ||||||
| 
 | 
 | ||||||
| 		if i != len(mnt) { | 			wantHost := hostnameFromTestCase(tc.name) | ||||||
| 			t.Errorf("got %d entries, want %d", i, len(mnt)) | 			if host, err := os.Hostname(); err != nil { | ||||||
| 		} | 				return fmt.Errorf("cannot get hostname: %v", err) | ||||||
|  | 			} else if host != wantHost { | ||||||
|  | 				return fmt.Errorf("hostname: %q, want %q", host, wantHost) | ||||||
|  | 			} | ||||||
|  | 
 | ||||||
|  | 			if p, err := os.ReadFile("/etc/hostname"); err != nil { | ||||||
|  | 				return fmt.Errorf("cannot read /etc/hostname: %v", err) | ||||||
|  | 			} else if string(p) != wantHost { | ||||||
|  | 				return fmt.Errorf("/etc/hostname: %q, want %q", string(p), wantHost) | ||||||
|  | 			} | ||||||
|  | 
 | ||||||
|  | 			{ | ||||||
|  | 				var fail bool | ||||||
|  | 
 | ||||||
|  | 				var mnt []*vfs.MountInfoEntry | ||||||
|  | 				if f, err := os.Open(pathWantMnt); err != nil { | ||||||
|  | 					return fmt.Errorf("cannot open expected mount points: %v", err) | ||||||
|  | 				} else if err = gob.NewDecoder(f).Decode(&mnt); err != nil { | ||||||
|  | 					return fmt.Errorf("cannot parse expected mount points: %v", err) | ||||||
|  | 				} else if err = f.Close(); err != nil { | ||||||
|  | 					return fmt.Errorf("cannot close expected mount points: %v", err) | ||||||
|  | 				} | ||||||
|  | 
 | ||||||
|  | 				var d *vfs.MountInfoDecoder | ||||||
|  | 				if f, err := os.Open("/proc/self/mountinfo"); err != nil { | ||||||
|  | 					return fmt.Errorf("cannot open mountinfo: %v", err) | ||||||
|  | 				} else { | ||||||
|  | 					d = vfs.NewMountInfoDecoder(f) | ||||||
|  | 				} | ||||||
|  | 
 | ||||||
|  | 				i := 0 | ||||||
|  | 				for cur := range d.Entries() { | ||||||
|  | 					if i == len(mnt) { | ||||||
|  | 						return fmt.Errorf("got more than %d entries", len(mnt)) | ||||||
|  | 					} | ||||||
|  | 
 | ||||||
|  | 					// ugly hack but should be reliable and is less likely to false negative than comparing by parsed flags | ||||||
|  | 					cur.VfsOptstr = strings.TrimSuffix(cur.VfsOptstr, ",relatime") | ||||||
|  | 					cur.VfsOptstr = strings.TrimSuffix(cur.VfsOptstr, ",noatime") | ||||||
|  | 					mnt[i].VfsOptstr = strings.TrimSuffix(mnt[i].VfsOptstr, ",relatime") | ||||||
|  | 					mnt[i].VfsOptstr = strings.TrimSuffix(mnt[i].VfsOptstr, ",noatime") | ||||||
|  | 
 | ||||||
|  | 					if !cur.EqualWithIgnore(mnt[i], "\x00") { | ||||||
|  | 						fail = true | ||||||
|  | 						log.Printf("[FAIL] %s", cur) | ||||||
|  | 					} else { | ||||||
|  | 						log.Printf("[ OK ] %s", cur) | ||||||
|  | 					} | ||||||
|  | 
 | ||||||
|  | 					i++ | ||||||
|  | 				} | ||||||
|  | 				if err := d.Err(); err != nil { | ||||||
|  | 					return fmt.Errorf("cannot parse mountinfo: %v", err) | ||||||
|  | 				} | ||||||
|  | 
 | ||||||
|  | 				if i != len(mnt) { | ||||||
|  | 					return fmt.Errorf("got %d entries, want %d", i, len(mnt)) | ||||||
|  | 				} | ||||||
|  | 
 | ||||||
|  | 				if fail { | ||||||
|  | 					return errors.New("one or more mountinfo entries do not match") | ||||||
|  | 				} | ||||||
|  | 			} | ||||||
|  | 
 | ||||||
|  | 			return nil | ||||||
|  | 		}) | ||||||
| 	}) | 	}) | ||||||
| } | } | ||||||
|  | |||||||
| @ -270,6 +270,7 @@ func Init(prepare func(prefix string), setVerbose func(verbose bool)) { | |||||||
| 	cmd.ExtraFiles = extraFiles | 	cmd.ExtraFiles = extraFiles | ||||||
| 	cmd.Dir = params.Dir | 	cmd.Dir = params.Dir | ||||||
| 
 | 
 | ||||||
|  | 	msg.Verbosef("starting initial program %s", params.Path) | ||||||
| 	if err := cmd.Start(); err != nil { | 	if err := cmd.Start(); err != nil { | ||||||
| 		log.Fatalf("%v", err) | 		log.Fatalf("%v", err) | ||||||
| 	} | 	} | ||||||
|  | |||||||
							
								
								
									
										43
									
								
								container/init_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										43
									
								
								container/init_test.go
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,43 @@ | |||||||
|  | package container_test | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	"log" | ||||||
|  | 	"os" | ||||||
|  | 	"testing" | ||||||
|  | 
 | ||||||
|  | 	"hakurei.app/command" | ||||||
|  | 	"hakurei.app/container" | ||||||
|  | 	"hakurei.app/internal" | ||||||
|  | 	"hakurei.app/internal/hlog" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | const ( | ||||||
|  | 	envDoCheck = "HAKUREI_TEST_DO_CHECK" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | var helperCommands []func(c command.Command) | ||||||
|  | 
 | ||||||
|  | func TestMain(m *testing.M) { | ||||||
|  | 	container.TryArgv0(hlog.Output{}, hlog.Prepare, internal.InstallOutput) | ||||||
|  | 
 | ||||||
|  | 	if os.Getenv(envDoCheck) == "1" { | ||||||
|  | 		c := command.New(os.Stderr, log.Printf, "helper", func(args []string) error { | ||||||
|  | 			log.SetFlags(0) | ||||||
|  | 			log.SetPrefix("helper: ") | ||||||
|  | 			return nil | ||||||
|  | 		}) | ||||||
|  | 		for _, f := range helperCommands { | ||||||
|  | 			f(c) | ||||||
|  | 		} | ||||||
|  | 		c.MustParse(os.Args[1:], func(err error) { | ||||||
|  | 			if err != nil { | ||||||
|  | 				log.Fatal(err.Error()) | ||||||
|  | 			} | ||||||
|  | 		}) | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	os.Exit(m.Run()) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func prepareHelper(c *container.Container) { c.Env = append(c.Env, envDoCheck+"=1") } | ||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user