container: check cancel signal delivery
	
		
			
	
		
	
	
		
	
		
			All checks were successful
		
		
	
	
		
			
				
	
				Test / Create distribution (push) Successful in 32s
				
			
		
			
				
	
				Test / Sandbox (push) Successful in 1m55s
				
			
		
			
				
	
				Test / Hakurei (push) Successful in 2m50s
				
			
		
			
				
	
				Test / Sandbox (race detector) (push) Successful in 3m46s
				
			
		
			
				
	
				Test / Planterette (push) Successful in 3m52s
				
			
		
			
				
	
				Test / Hakurei (race detector) (push) Successful in 4m28s
				
			
		
			
				
	
				Test / Flake checks (push) Successful in 1m18s
				
			
		
		
	
	
				
					
				
			
		
			All checks were successful
		
		
	
	Test / Create distribution (push) Successful in 32s
				
			Test / Sandbox (push) Successful in 1m55s
				
			Test / Hakurei (push) Successful in 2m50s
				
			Test / Sandbox (race detector) (push) Successful in 3m46s
				
			Test / Planterette (push) Successful in 3m52s
				
			Test / Hakurei (race detector) (push) Successful in 4m28s
				
			Test / Flake checks (push) Successful in 1m18s
				
			This change also makes some parts of the test more robust. Signed-off-by: Ophestra <cat@gensokyo.uk>
This commit is contained in:
		
							parent
							
								
									a1e5f020f4
								
							
						
					
					
						commit
						65fe09caf9
					
				| @ -71,6 +71,7 @@ type ( | |||||||
| 		Hostname string | 		Hostname string | ||||||
| 		// Sequential container setup ops. | 		// Sequential container setup ops. | ||||||
| 		*Ops | 		*Ops | ||||||
|  | 
 | ||||||
| 		// Seccomp system call filter rules. | 		// Seccomp system call filter rules. | ||||||
| 		SeccompRules []seccomp.NativeRule | 		SeccompRules []seccomp.NativeRule | ||||||
| 		// Extra seccomp flags. | 		// Extra seccomp flags. | ||||||
| @ -79,6 +80,7 @@ type ( | |||||||
| 		SeccompPresets seccomp.FilterPreset | 		SeccompPresets seccomp.FilterPreset | ||||||
| 		// Do not load seccomp program. | 		// Do not load seccomp program. | ||||||
| 		SeccompDisable bool | 		SeccompDisable bool | ||||||
|  | 
 | ||||||
| 		// Permission bits of newly created parent directories. | 		// Permission bits of newly created parent directories. | ||||||
| 		// The zero value is interpreted as 0755. | 		// The zero value is interpreted as 0755. | ||||||
| 		ParentPerm os.FileMode | 		ParentPerm os.FileMode | ||||||
| @ -121,7 +123,7 @@ func (p *Container) Start() error { | |||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	p.cmd = exec.CommandContext(ctx, MustExecutable()) | 	p.cmd = exec.CommandContext(ctx, MustExecutable()) | ||||||
| 	p.cmd.Args = []string{"init"} | 	p.cmd.Args = []string{initName} | ||||||
| 	p.cmd.Stdin, p.cmd.Stdout, p.cmd.Stderr = p.Stdin, p.Stdout, p.Stderr | 	p.cmd.Stdin, p.cmd.Stdout, p.cmd.Stderr = p.Stdin, p.Stdout, p.Stderr | ||||||
| 	p.cmd.WaitDelay = p.WaitDelay | 	p.cmd.WaitDelay = p.WaitDelay | ||||||
| 	if p.Cancel != nil { | 	if p.Cancel != nil { | ||||||
|  | |||||||
| @ -12,7 +12,6 @@ import ( | |||||||
| 	"strings" | 	"strings" | ||||||
| 	"syscall" | 	"syscall" | ||||||
| 	"testing" | 	"testing" | ||||||
| 	"time" |  | ||||||
| 
 | 
 | ||||||
| 	"hakurei.app/command" | 	"hakurei.app/command" | ||||||
| 	"hakurei.app/container" | 	"hakurei.app/container" | ||||||
| @ -21,7 +20,6 @@ import ( | |||||||
| 	"hakurei.app/hst" | 	"hakurei.app/hst" | ||||||
| 	"hakurei.app/internal" | 	"hakurei.app/internal" | ||||||
| 	"hakurei.app/internal/hlog" | 	"hakurei.app/internal/hlog" | ||||||
| 	"hakurei.app/ldd" |  | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| const ( | const ( | ||||||
| @ -92,18 +90,54 @@ func TestContainer(t *testing.T) { | |||||||
| 		t.Cleanup(func() { container.SetOutput(oldOutput) }) | 		t.Cleanup(func() { container.SetOutput(oldOutput) }) | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
|  | 	t.Run("cancel", func(t *testing.T) { | ||||||
|  | 		ctx, cancel := context.WithTimeout(t.Context(), helperDefaultTimeout) | ||||||
|  | 
 | ||||||
|  | 		c := helperNewContainer(ctx, "block") | ||||||
|  | 		c.Stdout, c.Stderr = os.Stdout, os.Stderr | ||||||
|  | 		c.WaitDelay = helperDefaultTimeout | ||||||
|  | 
 | ||||||
|  | 		ready := make(chan struct{}) | ||||||
|  | 		if r, w, err := os.Pipe(); err != nil { | ||||||
|  | 			t.Fatalf("cannot pipe: %v", err) | ||||||
|  | 		} else { | ||||||
|  | 			c.ExtraFiles = append(c.ExtraFiles, w) | ||||||
|  | 			go func() { | ||||||
|  | 				defer close(ready) | ||||||
|  | 				if _, err = r.Read(make([]byte, 1)); err != nil { | ||||||
|  | 					panic(err.Error()) | ||||||
|  | 				} | ||||||
|  | 			}() | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		if err := c.Start(); err != nil { | ||||||
|  | 			hlog.PrintBaseError(err, "start:") | ||||||
|  | 			t.Fatalf("cannot start container: %v", err) | ||||||
|  | 		} else if err = c.Serve(); err != nil { | ||||||
|  | 			hlog.PrintBaseError(err, "serve:") | ||||||
|  | 			t.Errorf("cannot serve setup params: %v", err) | ||||||
|  | 		} | ||||||
|  | 		<-ready | ||||||
|  | 		cancel() | ||||||
|  | 		wantErr := context.Canceled | ||||||
|  | 		if err := c.Wait(); !errors.Is(err, wantErr) { | ||||||
|  | 			hlog.PrintBaseError(err, "wait:") | ||||||
|  | 			t.Fatalf("Wait: error = %v, want %v", err, wantErr) | ||||||
|  | 		} | ||||||
|  | 	}) | ||||||
|  | 
 | ||||||
| 	for i, tc := range containerTestCases { | 	for i, tc := range containerTestCases { | ||||||
| 		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(), helperDefaultTimeout) | ||||||
| 			defer cancel() | 			defer cancel() | ||||||
| 
 | 
 | ||||||
| 			hostname := hostnameFromTestCase(tc.name) | 			var libPaths []string | ||||||
| 			c := container.New(ctx, os.Args[0], "container", strconv.Itoa(i)) | 			c := helperNewContainerLibPaths(ctx, &libPaths, "container", strconv.Itoa(i)) | ||||||
| 			prepareHelper(c) |  | ||||||
| 			c.Uid = tc.uid | 			c.Uid = tc.uid | ||||||
| 			c.Gid = tc.gid | 			c.Gid = tc.gid | ||||||
| 			c.Hostname = hostname | 			c.Hostname = hostnameFromTestCase(tc.name) | ||||||
| 			c.Stdout, c.Stderr = os.Stdout, os.Stderr | 			c.Stdout, c.Stderr = os.Stdout, os.Stderr | ||||||
|  | 			c.WaitDelay = helperDefaultTimeout | ||||||
| 			*c.Ops = append(*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 | ||||||
| @ -114,38 +148,27 @@ func TestContainer(t *testing.T) { | |||||||
| 
 | 
 | ||||||
| 			c. | 			c. | ||||||
| 				Tmpfs("/tmp", 0, 0755). | 				Tmpfs("/tmp", 0, 0755). | ||||||
| 				Bind(os.Args[0], os.Args[0], 0). | 				Place("/etc/hostname", []byte(c.Hostname)) | ||||||
| 				Place("/etc/hostname", []byte(hostname)) |  | ||||||
| 			// in case test has cgo enabled |  | ||||||
| 			var libPaths []string |  | ||||||
| 			if entries, err := ldd.Exec(ctx, os.Args[0]); err != nil { |  | ||||||
| 				log.Fatalf("ldd: %v", err) |  | ||||||
| 			} else { |  | ||||||
| 				libPaths = ldd.Path(entries) |  | ||||||
| 			} |  | ||||||
| 			for _, name := range libPaths { |  | ||||||
| 				c.Bind(name, name, 0) |  | ||||||
| 			} |  | ||||||
| 			// 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 | 			// 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, ent("/sysroot", "/", "rw,nosuid,nodev,relatime", "tmpfs", "rootfs", ignore)) |  | ||||||
| 			mnt = append(mnt, tc.mnt...) |  | ||||||
| 			mnt = append(mnt, | 			mnt = append(mnt, | ||||||
| 				// Tmpfs("/tmp", 0, 0755) | 				ent("/sysroot", "/", "rw,nosuid,nodev,relatime", "tmpfs", "rootfs", ignore), | ||||||
| 				ent("/", "/tmp", "rw,nosuid,nodev,relatime", "tmpfs", "tmpfs", ignore), | 				// Bind(os.Args[0], helperInnerPath, 0) | ||||||
| 				// Bind(os.Args[0], os.Args[0], 0) | 				ent(ignore, helperInnerPath, "ro,nosuid,nodev,relatime", ignore, ignore, ignore), | ||||||
| 				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 { | ||||||
| 				// Bind(name, name, 0) | 				// Bind(name, name, 0) | ||||||
| 				mnt = append(mnt, ent(ignore, name, "ro,nosuid,nodev,relatime", ignore, ignore, ignore)) | 				mnt = append(mnt, ent(ignore, name, "ro,nosuid,nodev,relatime", ignore, ignore, ignore)) | ||||||
| 			} | 			} | ||||||
|  | 			mnt = append(mnt, tc.mnt...) | ||||||
| 			mnt = append(mnt, | 			mnt = append(mnt, | ||||||
|  | 				// Tmpfs("/tmp", 0, 0755) | ||||||
|  | 				ent("/", "/tmp", "rw,nosuid,nodev,relatime", "tmpfs", "tmpfs", ignore), | ||||||
|  | 				// Place("/etc/hostname", []byte(hostname)) | ||||||
|  | 				ent(ignore, "/etc/hostname", "ro,nosuid,nodev,relatime", "tmpfs", "rootfs", ignore), | ||||||
| 				// Proc("/proc") | 				// Proc("/proc") | ||||||
| 				ent("/", "/proc", "rw,nosuid,nodev,noexec,relatime", "proc", "proc", "rw"), | 				ent("/", "/proc", "rw,nosuid,nodev,noexec,relatime", "proc", "proc", "rw"), | ||||||
| 				// Place(pathWantMnt, want.Bytes()) | 				// Place(pathWantMnt, want.Bytes()) | ||||||
| @ -206,6 +229,13 @@ func TestContainerString(t *testing.T) { | |||||||
| 
 | 
 | ||||||
| func init() { | func init() { | ||||||
| 	helperCommands = append(helperCommands, func(c command.Command) { | 	helperCommands = append(helperCommands, func(c command.Command) { | ||||||
|  | 		c.Command("block", command.UsageInternal, func(args []string) error { | ||||||
|  | 			if _, err := os.NewFile(3, "sync").Write([]byte{0}); err != nil { | ||||||
|  | 				return fmt.Errorf("write to sync pipe: %v", err) | ||||||
|  | 			} | ||||||
|  | 			select {} | ||||||
|  | 		}) | ||||||
|  | 
 | ||||||
| 		c.Command("container", command.UsageInternal, func(args []string) error { | 		c.Command("container", command.UsageInternal, func(args []string) error { | ||||||
| 			if len(args) != 1 { | 			if len(args) != 1 { | ||||||
| 				return syscall.EINVAL | 				return syscall.EINVAL | ||||||
|  | |||||||
| @ -367,9 +367,11 @@ func Init(prepare func(prefix string), setVerbose func(verbose bool)) { | |||||||
| 	} | 	} | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | const initName = "init" | ||||||
|  | 
 | ||||||
| // TryArgv0 calls [Init] if the last element of argv0 is "init". | // TryArgv0 calls [Init] if the last element of argv0 is "init". | ||||||
| func TryArgv0(v Msg, prepare func(prefix string), setVerbose func(verbose bool)) { | func TryArgv0(v Msg, prepare func(prefix string), setVerbose func(verbose bool)) { | ||||||
| 	if len(os.Args) > 0 && path.Base(os.Args[0]) == "init" { | 	if len(os.Args) > 0 && path.Base(os.Args[0]) == initName { | ||||||
| 		msg = v | 		msg = v | ||||||
| 		Init(prepare, setVerbose) | 		Init(prepare, setVerbose) | ||||||
| 		msg.BeforeExit() | 		msg.BeforeExit() | ||||||
|  | |||||||
| @ -1,18 +1,24 @@ | |||||||
| package container_test | package container_test | ||||||
| 
 | 
 | ||||||
| import ( | import ( | ||||||
|  | 	"context" | ||||||
| 	"log" | 	"log" | ||||||
| 	"os" | 	"os" | ||||||
| 	"testing" | 	"testing" | ||||||
|  | 	"time" | ||||||
| 
 | 
 | ||||||
| 	"hakurei.app/command" | 	"hakurei.app/command" | ||||||
| 	"hakurei.app/container" | 	"hakurei.app/container" | ||||||
| 	"hakurei.app/internal" | 	"hakurei.app/internal" | ||||||
| 	"hakurei.app/internal/hlog" | 	"hakurei.app/internal/hlog" | ||||||
|  | 	"hakurei.app/ldd" | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| const ( | const ( | ||||||
| 	envDoCheck = "HAKUREI_TEST_DO_CHECK" | 	envDoCheck = "HAKUREI_TEST_DO_CHECK" | ||||||
|  | 
 | ||||||
|  | 	helperDefaultTimeout = 5 * time.Second | ||||||
|  | 	helperInnerPath      = "/usr/bin/helper" | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| var helperCommands []func(c command.Command) | var helperCommands []func(c command.Command) | ||||||
| @ -40,4 +46,24 @@ func TestMain(m *testing.M) { | |||||||
| 	os.Exit(m.Run()) | 	os.Exit(m.Run()) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func prepareHelper(c *container.Container) { c.Env = append(c.Env, envDoCheck+"=1") } | func helperNewContainerLibPaths(ctx context.Context, libPaths *[]string, args ...string) (c *container.Container) { | ||||||
|  | 	c = container.New(ctx, helperInnerPath, args...) | ||||||
|  | 	c.Env = append(c.Env, envDoCheck+"=1") | ||||||
|  | 	c.Bind(os.Args[0], helperInnerPath, 0) | ||||||
|  | 
 | ||||||
|  | 	// in case test has cgo enabled | ||||||
|  | 	if entries, err := ldd.Exec(ctx, os.Args[0]); err != nil { | ||||||
|  | 		log.Fatalf("ldd: %v", err) | ||||||
|  | 	} else { | ||||||
|  | 		*libPaths = ldd.Path(entries) | ||||||
|  | 	} | ||||||
|  | 	for _, name := range *libPaths { | ||||||
|  | 		c.Bind(name, name, 0) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	return | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func helperNewContainer(ctx context.Context, args ...string) (c *container.Container) { | ||||||
|  | 	return helperNewContainerLibPaths(ctx, new([]string), args...) | ||||||
|  | } | ||||||
|  | |||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user