package command import "testing" // The offset key is a persistence contract: a changed form orphans saved // checkpoints, so the historical forms must survive verbatim and the // target-scoped form must stay stable. func TestGetSignaturePrefixByPath_KeyForms(t *testing.T) { cases := []struct { sourcePath, targetPath, want string }{ // historical forms, kept so existing deployments resume unchanged {"/", "/", "sync."}, {"/data", "/", "sync./data"}, // the target path participates once it deviates from "/" {"/", "/backup-a", "sync.\x00/backup-a"}, {"/data", "/backup-a", "sync./data\x00/backup-a"}, } for _, tc := range cases { if got := getSignaturePrefixByPath(tc.sourcePath, tc.targetPath); got != tc.want { t.Errorf("getSignaturePrefixByPath(%q, %q) = %q, want %q", tc.sourcePath, tc.targetPath, got, tc.want) } } } // The NUL separator keeps the key injective: a source directory whose name // spells out a separator must not alias a different (source, target) pair. func TestGetSignaturePrefixByPath_NoAliasing(t *testing.T) { if a, b := getSignaturePrefixByPath("/archive=>/backup", "/"), getSignaturePrefixByPath("/archive", "/backup"); a == b { t.Errorf("source path spelling a separator aliases key %q", a) } } // Two syncs from the same source filer and path to different directories on // the same target filer must keep separate checkpoints, or the running one // pushes the shared offset past events the other never processed. func TestGetSignaturePrefixByPath_DistinctTargets(t *testing.T) { a := getSignaturePrefixByPath("/", "/backup-a") b := getSignaturePrefixByPath("/", "/backup-b") if a == b { t.Errorf("different target paths share offset key %q", a) } if root := getSignaturePrefixByPath("/", "/"); a == root { t.Errorf("non-root target path shares offset key %q with the root form", a) } } // Over the real KV wire path: a sync that predates target-scoped keys resumes // from the historical key, and once each sync checkpoints under its own key // they no longer disturb each other. func TestSyncOffset_HistoricalFallbackThenIndependence(t *testing.T) { filerAddr, dial := startKvFiler(t) const sourceSig = int32(12345) historical := getSignaturePrefixByPath("/data", "/") prefixA := getSignaturePrefixByPath("/data", "/backup-a") prefixB := getSignaturePrefixByPath("/data", "/backup-b") // pre-upgrade state: both syncs shared the historical key if err := setOffset(dial, filerAddr, historical, sourceSig, 111); err != nil { t.Fatalf("seed historical offset: %v", err) } if got, err := getOffsetWithFallback(dial, filerAddr, prefixA, sourceSig, historical, sourceSig); err != nil || got != 111 { t.Fatalf("A fallback read = (%d, %v), want (111, nil)", got, err) } // A checkpoints under its own key; B still resumes from the historical one if err := setOffset(dial, filerAddr, prefixA, sourceSig, 222); err != nil { t.Fatalf("write A offset: %v", err) } if got, err := getOffsetWithFallback(dial, filerAddr, prefixA, sourceSig, historical, sourceSig); err != nil || got != 222 { t.Fatalf("A read = (%d, %v), want (222, nil)", got, err) } if got, err := getOffsetWithFallback(dial, filerAddr, prefixB, sourceSig, historical, sourceSig); err != nil || got != 111 { t.Fatalf("B read = (%d, %v), want (111, nil)", got, err) } }