package datasets import ( "context" "os" "testing" "time" ) func TestLocalStoreLockSerializes(t *testing.T) { dir := t.TempDir() store, _ := NewLocalStore(dir, "gfs-test") ctx := context.Background() release, err := store.Lock(ctx) if err != nil { t.Fatalf("first Lock: %v", err) } // A second acquisition must block until the first releases. got := make(chan struct{}) go func() { r2, err := store.Lock(ctx) if err == nil { r2() } close(got) }() select { case <-got: t.Fatal("second Lock acquired while first was held") case <-time.After(100 * time.Millisecond): // expected: still blocked } release() select { case <-got: // expected: acquired after release case <-time.After(2 * time.Second): t.Fatal("second Lock did not acquire after release") } } func TestLocalStoreLockContextCancel(t *testing.T) { dir := t.TempDir() store, _ := NewLocalStore(dir, "gfs-test") release, err := store.Lock(context.Background()) if err != nil { t.Fatalf("Lock: %v", err) } defer release() ctx, cancel := context.WithCancel(context.Background()) cancel() if _, err := store.Lock(ctx); err == nil { t.Error("expected Lock to fail on cancelled context while held elsewhere") } } func TestLocalStoreBeginWriteResume(t *testing.T) { dir := t.TempDir() store, err := NewLocalStore(dir, "gfs-test") if err != nil { t.Fatalf("NewLocalStore: %v", err) } id := DatasetID{Epoch: time.Date(2026, 1, 1, 0, 0, 0, 0, time.UTC)} h, err := store.BeginWrite(id) if err != nil { t.Fatalf("BeginWrite: %v", err) } if err := os.WriteFile(h.Path(), []byte("partial"), 0o644); err != nil { t.Fatalf("write partial: %v", err) } if err := h.Manifest().Mark("step000-A"); err != nil { t.Fatalf("mark: %v", err) } // Re-open should see the previous manifest entry. h2, err := store.BeginWrite(id) if err != nil { t.Fatalf("BeginWrite resume: %v", err) } if !h2.Manifest().Has("step000-A") { t.Errorf("resumed manifest missing step000-A; units = %v", h2.Manifest().Units()) } if err := h2.Commit(); err != nil { t.Fatalf("Commit: %v", err) } if !store.Exists(id) { t.Errorf("Exists after commit returned false") } if _, err := os.Stat(store.manifestPath(id)); !os.IsNotExist(err) { t.Errorf("manifest should be removed, got err=%v", err) } stored, err := store.List() if err != nil { t.Fatalf("List: %v", err) } if len(stored) != 1 || !stored[0].Epoch.Equal(id.Epoch) { t.Errorf("List = %v, want one item with epoch %v", stored, id.Epoch) } if err := store.Remove(id); err != nil { t.Fatalf("Remove: %v", err) } if store.Exists(id) { t.Errorf("Exists after remove returned true") } } func TestLocalStoreSubsetPath(t *testing.T) { dir := t.TempDir() store, _ := NewLocalStore(dir, "gfs-test") epoch := time.Date(2026, 1, 1, 0, 0, 0, 0, time.UTC) regional := DatasetID{ Epoch: epoch, Subset: SubsetSpec{ Region: &Region{MinLat: -10, MaxLat: 10, MinLng: 0, MaxLng: 30}, HourRange: &HourRange{MinHour: 0, MaxHour: 72}, }, } global := DatasetID{Epoch: epoch} if store.Path(global) == store.Path(regional) { t.Errorf("global and regional should have distinct paths") } } func TestSubsetSpecCoverage(t *testing.T) { r := Region{MinLat: -10, MaxLat: 10, MinLng: 350, MaxLng: 10} // wraps antimeridian s := SubsetSpec{Region: &r} if !s.IncludesLatLng(0, 0) { t.Errorf("(0,0) should be inside antimeridian region") } if !s.IncludesLatLng(0, 359) { t.Errorf("(0,359) should be inside antimeridian region") } if s.IncludesLatLng(0, 180) { t.Errorf("(0,180) should be outside antimeridian region") } }