| 1 | package sessioncatalog |
| 2 | |
| 3 | import "testing" |
| 4 | |
| 5 | func TestReconcileTokenUsesLatestPreDispatchTargetOnce(t *testing.T) { |
| 6 | catalog := &Catalog{ |
| 7 | reconcileCh: make(chan DirectoryTarget, 2), reconcileDirty: map[string]DirectoryTarget{}, stop: make(chan struct{}), |
| 8 | } |
| 9 | a := DirectoryTarget{Path: "/sessions", Scope: "global", mutationSeq: 1} |
| 10 | b := DirectoryTarget{Path: "/sessions", Scope: "workspace", WorkspaceRoot: "/work", mutationSeq: 2} |
| 11 | if !catalog.RequestReconcile(a) || !catalog.RequestReconcile(b) { |
| 12 | t.Fatal("failed to queue targets") |
| 13 | } |
| 14 | token := <-catalog.reconcileCh |
| 15 | got, ok := catalog.resolveReconcileToken(token) |
| 16 | if !ok || got.Scope != b.Scope || got.WorkspaceRoot != b.WorkspaceRoot || got.mutationSeq <= a.mutationSeq { |
| 17 | t.Fatalf("resolved target = %+v ok=%v, want latest B", got, ok) |
| 18 | } |
| 19 | if _, dirty := catalog.takeReconcileDirty(); dirty { |
| 20 | t.Fatal("pre-dispatch dirty target remained queued") |
| 21 | } |
| 22 | select { |
| 23 | case extra := <-catalog.reconcileCh: |
| 24 | t.Fatalf("unexpected second channel token: %+v", extra) |
| 25 | default: |
| 26 | } |
| 27 | } |
| 28 | |
| 29 | func TestReconcileRunningUpdatesKeepOnlyLatestFollowUp(t *testing.T) { |
| 30 | catalog := &Catalog{reconcileDirty: map[string]DirectoryTarget{}} |
| 31 | key := queuePathKey("/sessions") |
| 32 | catalog.reconcileQueued.Store(key, DirectoryTarget{Path: "/sessions", mutationSeq: 1}) |
| 33 | catalog.markReconcileDirty(DirectoryTarget{Path: "/sessions", Scope: "global", mutationSeq: 2}) |
| 34 | catalog.markReconcileDirty(DirectoryTarget{Path: "/sessions", Scope: "workspace", WorkspaceRoot: "/latest", mutationSeq: 3}) |
| 35 | got, ok := catalog.takeReconcileDirty() |
| 36 | if !ok || got.mutationSeq != 3 || got.WorkspaceRoot != "/latest" { |
| 37 | t.Fatalf("follow-up = %+v ok=%v, want latest only", got, ok) |
| 38 | } |
| 39 | if _, ok := catalog.takeReconcileDirty(); ok { |
| 40 | t.Fatal("more than one follow-up remained") |
| 41 | } |
| 42 | } |
| 43 | |
| 44 | func TestReconcileDirtyNeverDowngradesToLateOlderTarget(t *testing.T) { |
| 45 | newer := DirectoryTarget{Path: "/sessions", Scope: "workspace", WorkspaceRoot: "/new", mutationSeq: 3} |
| 46 | older := DirectoryTarget{Path: "/sessions", Scope: "global", mutationSeq: 2} |
| 47 | for _, tc := range []struct { |
| 48 | name string |
| 49 | seedDirty bool |
| 50 | }{ |
| 51 | {name: "queued target"}, |
| 52 | {name: "dirty target", seedDirty: true}, |
| 53 | } { |
| 54 | t.Run(tc.name, func(t *testing.T) { |
| 55 | catalog := &Catalog{reconcileDirty: map[string]DirectoryTarget{}} |
| 56 | key := queuePathKey(newer.Path) |
| 57 | catalog.reconcileQueued.Store(key, newer) |
| 58 | if tc.seedDirty { |
| 59 | catalog.reconcileDirty[key] = newer |
| 60 | } |
| 61 | catalog.markReconcileDirty(older) |
| 62 | got, ok := catalog.takeReconcileDirty() |
| 63 | if !ok || got.mutationSeq != newer.mutationSeq || got.WorkspaceRoot != newer.WorkspaceRoot { |
| 64 | t.Fatalf("dirty target = %+v ok=%v, want newer target", got, ok) |
| 65 | } |
| 66 | }) |
| 67 | } |
| 68 | } |
| 69 |