Skip to content

Commit 3aa8f4f

Browse files
authored
Merge pull request containerd#3391 from apostasie/dev-defensive-testing
Add a locking mechanism to prevent concurrent subpackage testing
2 parents ed44077 + c5d3309 commit 3aa8f4f

File tree

1 file changed

+53
-2
lines changed

1 file changed

+53
-2
lines changed

pkg/testutil/testutil.go

Lines changed: 53 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,12 +37,14 @@ import (
3737
"gotest.tools/v3/icmd"
3838

3939
"github.com/containerd/containerd/v2/defaults"
40+
"github.com/containerd/log"
4041

4142
"github.com/containerd/nerdctl/v2/pkg/buildkitutil"
4243
"github.com/containerd/nerdctl/v2/pkg/imgutil"
4344
"github.com/containerd/nerdctl/v2/pkg/infoutil"
4445
"github.com/containerd/nerdctl/v2/pkg/inspecttypes/dockercompat"
4546
"github.com/containerd/nerdctl/v2/pkg/inspecttypes/native"
47+
"github.com/containerd/nerdctl/v2/pkg/lockutil"
4648
"github.com/containerd/nerdctl/v2/pkg/platformutil"
4749
"github.com/containerd/nerdctl/v2/pkg/rootlessutil"
4850
)
@@ -549,14 +551,63 @@ var (
549551
flagTestKube bool
550552
)
551553

554+
var (
555+
testLockFile = filepath.Join(os.TempDir(), "nerdctl-test-prevent-concurrency", ".lock")
556+
)
557+
552558
func M(m *testing.M) {
553559
flag.StringVar(&flagTestTarget, "test.target", Nerdctl, "target to test")
554560
flag.BoolVar(&flagTestKillDaemon, "test.allow-kill-daemon", false, "enable tests that kill the daemon")
555561
flag.BoolVar(&flagTestIPv6, "test.only-ipv6", false, "enable tests on IPv6")
556562
flag.BoolVar(&flagTestKube, "test.only-kubernetes", false, "enable tests on Kubernetes")
557563
flag.Parse()
558-
fmt.Fprintf(os.Stderr, "test target: %q\n", flagTestTarget)
559-
os.Exit(m.Run())
564+
565+
os.Exit(func() int {
566+
// If there is a lockfile (no err), or if we error-ed stating it (permission), another test run is currently going.
567+
// Note that this could be racy. The .lock file COULD get acquired after this and before we hit the lock section.
568+
// This is not a big deal then: we will just wait for the lock to free.
569+
if _, err := os.Stat(testLockFile); err == nil || !errors.Is(err, os.ErrNotExist) {
570+
log.L.Errorf("Another test binary is already running. If you think this is an error, manually remove %s", testLockFile)
571+
return 1
572+
}
573+
574+
err := os.MkdirAll(filepath.Dir(testLockFile), 0o777)
575+
if err != nil {
576+
log.L.WithError(err).Errorf("failed creating testing lock directory %q", filepath.Dir(testLockFile))
577+
return 1
578+
}
579+
580+
// Ensure that permissions are set to 777 (regardless of umask value), so that we do not lock people out when
581+
// switching between rootful and rootless locking
582+
os.Chmod(filepath.Dir(testLockFile), 0o777)
583+
584+
// Acquire lock
585+
lock, err := lockutil.Lock(filepath.Dir(testLockFile))
586+
if err != nil {
587+
log.L.WithError(err).Errorf("failed acquiring testing lock %q", filepath.Dir(testLockFile))
588+
return 1
589+
}
590+
591+
// Release...
592+
defer lockutil.Unlock(lock)
593+
594+
// Create marker file
595+
err = os.WriteFile(testLockFile, []byte("prevent testing from running in parallel for subpackages integration tests"), 0o666)
596+
if err != nil {
597+
log.L.WithError(err).Errorf("failed writing lock file %q", testLockFile)
598+
return 1
599+
}
600+
601+
// Ensure cleanup
602+
defer func() {
603+
os.Remove(testLockFile)
604+
}()
605+
606+
// Now, run the tests
607+
fmt.Fprintf(os.Stderr, "test target: %q\n", flagTestTarget)
608+
609+
return m.Run()
610+
}())
560611
}
561612

562613
func GetTarget() string {

0 commit comments

Comments
 (0)