//go:build linux package hostbox import ( "context" "errors" "os" "path/filepath" "testing" "golang.org/x/sys/unix" ) func TestPlan9Fix4MigrationSuccessRecoveryHoldsLock(t *testing.T) { for _, marked := range []bool{false, true} { t.Run(map[bool]string{false: "commit", true: "completed"}[marked], func(t *testing.T) { service, _, _ := journalFixture(t) legacy := t.TempDir() if marked { if err := service.MigrateLegacy(t.Context(), legacy, "box", "enrollment", nil, nil); err != nil { t.Fatal(err) } } lockPath := filepath.Join(filepath.Dir(service.Launches.path), "migration.lock") called := false err := service.MigrateLegacy(t.Context(), legacy, "box", "enrollment", nil, nil, func(ctx context.Context) error { called = true _, _, _, present, err := service.journalMigrationMarker(ctx) if err != nil || !present { t.Fatalf("recovery before commit: %v %v", present, err) } fd, err := unix.Open(lockPath, unix.O_RDWR|unix.O_CLOEXEC, 0) if err != nil { t.Fatal(err) } defer unix.Close(fd) err = unix.Flock(fd, unix.LOCK_EX|unix.LOCK_NB) if !errors.Is(err, unix.EWOULDBLOCK) { t.Fatalf("recovery outside migration lock: %v", err) } return nil }) if err != nil || !called { t.Fatalf("success recovery: called=%v err=%v", called, err) } fd, err := unix.Open(lockPath, unix.O_RDWR|unix.O_CLOEXEC, 0) if err != nil { t.Fatal(err) } defer unix.Close(fd) if err = unix.Flock(fd, unix.LOCK_EX|unix.LOCK_NB); err != nil { t.Fatalf("migration kept lock after recovery: %v", err) } }) } } func TestPlan9Fix4FailedMigrationDoesNotRecoverMask(t *testing.T) { service, _, _ := journalFixture(t) legacy := t.TempDir() if err := os.WriteFile(filepath.Join(legacy, "launches.db"), []byte("partial legacy pair"), 0600); err != nil { t.Fatal(err) } called := false err := service.MigrateLegacy(t.Context(), legacy, "box", "enrollment", nil, nil, func(context.Context) error { called = true; return nil }) if err == nil || called { t.Fatalf("failed import ran success recovery: called=%v err=%v", called, err) } }