145 lines
3.5 KiB
Go
145 lines
3.5 KiB
Go
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")
|
|
}
|
|
}
|