(t *testing.T)
| 560 | } |
| 561 | |
| 562 | func TestActivateConcurrentSameName(t *testing.T) { |
| 563 | td := t.TempDir() |
| 564 | metadb := filepath.Join(td, "mounts.db") |
| 565 | targetdir := filepath.Join(td, "m") |
| 566 | db, err := bolt.Open(metadb, 0600, nil) |
| 567 | require.NoError(t, err) |
| 568 | ctx := namespaces.WithNamespace(context.Background(), "test") |
| 569 | |
| 570 | mountC := new(atomic.Int32) |
| 571 | m, err := NewManager(db, targetdir, WithMountHandler("noop", &noopHandler{mounts: mountC})) |
| 572 | require.NoError(t, err) |
| 573 | t.Cleanup(func() { assert.NoError(t, m.(io.Closer).Close()) }) |
| 574 | |
| 575 | mounts := []mount.Mount{{Type: "noop"}} |
| 576 | |
| 577 | // Launch two concurrent activations with the same name. |
| 578 | // The per-name lock should serialize them: first succeeds, |
| 579 | // second gets ErrAlreadyExists (not a stale-recovery race). |
| 580 | errs := make(chan error, 2) |
| 581 | for i := 0; i < 2; i++ { |
| 582 | go func() { |
| 583 | _, err := m.Activate(ctx, "task1", mounts) |
| 584 | errs <- err |
| 585 | }() |
| 586 | } |
| 587 | |
| 588 | err1 := <-errs |
| 589 | err2 := <-errs |
| 590 | |
| 591 | // Exactly one should succeed and one should get ErrAlreadyExists |
| 592 | if err1 == nil && errdefs.IsAlreadyExists(err2) { |
| 593 | // ok |
| 594 | } else if err2 == nil && errdefs.IsAlreadyExists(err1) { |
| 595 | // ok |
| 596 | } else { |
| 597 | t.Fatalf("expected one nil and one ErrAlreadyExists, got: %v, %v", err1, err2) |
| 598 | } |
| 599 | |
| 600 | assert.NoError(t, m.Deactivate(ctx, "task1")) |
| 601 | } |
| 602 | |
| 603 | // TODO: Test deactivate |
| 604 | // TODO: Test Sync |
nothing calls this directly
no test coverage detected
searching dependent graphs…