aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorOphestra <cat@gensokyo.uk>2026-10-07 03:11:54 +0900
committerOphestra <cat@gensokyo.uk>2026-10-13 20:49:53 +0900
commitbf52aed01a451dc622201bdf6925e537da8c1140 (patch)
treee8ab0ddb3a7fe577ad3f394412b798367f3563ee
parentdf3987f291c45b38a8c3ea96a95cfad24d1e2860 (diff)
cmd/hakurei/testsuite: exercise setup/teardown
This exposes a race during simultaneous startup between chmod and acl_set_file, where the ACL mask is clobbered by chmod. This remained undiscovered until now due to the previous integration test suite's complete inability to handle concurrency (the nixos vm test machinery could not even support python multithreading). Signed-off-by: Ophestra <cat@gensokyo.uk>
-rw-r--r--cmd/hakurei/testsuite/sandbox/main.go75
1 files changed, 68 insertions, 7 deletions
diff --git a/cmd/hakurei/testsuite/sandbox/main.go b/cmd/hakurei/testsuite/sandbox/main.go
index 820b7e2f..a2d1cb5e 100644
--- a/cmd/hakurei/testsuite/sandbox/main.go
+++ b/cmd/hakurei/testsuite/sandbox/main.go
@@ -12,6 +12,7 @@ import (
"log"
"os"
"os/exec"
+ "os/signal"
"path/filepath"
"slices"
"strconv"
@@ -19,6 +20,7 @@ import (
"sync"
"sync/atomic"
"syscall"
+ "time"
"hakurei.app/check"
"hakurei.app/fhs"
@@ -49,7 +51,7 @@ func mustScanFor(f func(ps *testsuite.StatScanner) bool) int {
// the container. This process must be terminated by the caller.
func mustStart(
ctx context.Context,
- serial uint64,
+ serial uint64, identity int,
cred *syscall.Credential,
files ...*os.File,
) (pid int, done <-chan error) {
@@ -57,6 +59,7 @@ func mustStart(
_, done = testsuite.MustStartWith(
ctx, cred, nil, files,
"hakurei", "exec",
+ "-a", strconv.Itoa(identity),
"sleep", "infinity", _serial,
)
@@ -106,13 +109,74 @@ func mustStart(
return
}
+// serial is the previous value returned by newSerial.
+var serial atomic.Uint64
+
+// newSerial returns a unique number.
+func newSerial() uint64 { defer serial.Add(1); return serial.Load() }
+
func main() {
+ cred := syscall.Credential{Uid: 1000, Gid: 100}
+
+ if p, ok := os.LookupEnv("HAKUREI_TESTSUITE_EXERCISE"); ok {
+ n, err := strconv.Atoi(p)
+ if err != nil {
+ log.Fatalf("invalid stress concurrency %q", p)
+ }
+
+ ctx, stop := signal.NotifyContext(context.Background(),
+ os.Interrupt,
+ syscall.SIGTERM,
+ )
+ defer stop()
+
+ const identCount = 8
+
+ var wg sync.WaitGroup
+ in := identCount
+ var identity int
+
+ wg.Add(n)
+ for range n {
+ in--
+ if in < 0 {
+ identity++
+ in = identCount
+ }
+ go func(identity int) {
+ defer wg.Done()
+
+ t := time.NewTicker(500 * time.Millisecond)
+ start:
+ pid, done := mustStart(ctx, newSerial(), identity, &cred)
+ select {
+ case <-t.C:
+ if _err := syscall.Kill(pid, syscall.SIGTERM); _err != nil {
+ log.Fatal(_err)
+ }
+
+ case <-ctx.Done():
+ if _err := syscall.Kill(pid, syscall.SIGTERM); _err != nil {
+ log.Fatal(_err)
+ }
+ }
+ if _err := testsuite.FilterTerminated(<-done); _err != nil {
+ log.Fatal(_err)
+ }
+ if ctx.Err() == nil {
+ goto start
+ }
+ }(identity)
+ }
+ log.Printf("exercising cmd/hakurei with %d concurrent instances", n)
+ wg.Wait()
+ }
+
go testsuite.ReceiveSignals()
// the signal handler does not wait for termination
ctx := context.Background()
- cred := syscall.Credential{Uid: 1000, Gid: 100}
if err := os.MkdirAll("/opt/test-helper/bin", 0755); err != nil {
log.Fatal(err)
}
@@ -133,9 +197,6 @@ func main() {
var wg sync.WaitGroup
defer wg.Wait()
- var serial atomic.Uint64
- newSerial := func() uint64 { serial.Add(1); return serial.Load() }
-
testsuite.MustRun(
&cred, nil,
"hakurei", "exec", "capsh", "--print",
@@ -166,7 +227,7 @@ func main() {
c, cancel := context.WithCancel(ctx)
defer cancel()
- pid, done := mustStart(c, newSerial(), &cred)
+ pid, done := mustStart(c, newSerial(), 0, &cred)
testsuite.MustCheckFilter(pid, testdata.SumPD)
if err := testsuite.FilterTerminated(<-done); err != nil {
log.Fatal(err)
@@ -179,7 +240,7 @@ func main() {
c, cancel := context.WithCancel(ctx)
defer cancel()
- pid, done := mustStart(c, newSerial(), &cred, os.Stdin, os.Stdout, os.Stderr)
+ pid, done := mustStart(c, newSerial(), 0, &cred, os.Stdin, os.Stdout, os.Stderr)
prefix := filepath.Join(fhs.Proc, strconv.Itoa(pid), "fd")
var fail bool