package geo import ( "net" "sync" "sync/atomic" "testing" "time" ) func TestSwappableEmptyAnswersNotLoaded(t *testing.T) { s := NewSwappable() if snap := s.Lookup(net.ParseIP("81.2.69.142")); snap.Loaded || snap.Found || snap.Source != "" { t.Fatalf("empty swappable = %+v", snap) } if _, ok := s.Locate(net.ParseIP("81.2.69.142")); ok { t.Fatal("empty swappable located") } var _ Locator = s } func TestSwappableSnapshotCarriesItsReadersSource(t *testing.T) { s := NewSwappable() m, err := OpenMMDB("testdata/GeoIP2-City-Test.mmdb") if err != nil { t.Fatal(err) } s.Swap(m) snap := s.Lookup(net.ParseIP("81.2.69.142")) if !snap.Loaded || !snap.Found || snap.Source != "GeoIP2-City" || snap.Result.Country != "GB" { t.Fatalf("snapshot = %+v", snap) } miss := s.Lookup(net.ParseIP("10.0.0.1")) if !miss.Loaded || miss.Found || miss.Source != "GeoIP2-City" { t.Fatalf("a miss with a loaded reader must say Loaded and name the source: %+v", miss) } s.Swap(nil) if snap := s.Lookup(net.ParseIP("81.2.69.142")); snap.Loaded { t.Fatalf("after Swap(nil) = %+v", snap) } } // Review Focus 3: no lookup ever runs on a closed reader, and a snapshot's // source always belongs to the reader that answered. func TestSwappableNeverLooksUpAClosedReader(t *testing.T) { s := NewSwappable() var closedWhileInUse atomic.Bool mk := func(src string) *MMDB { var inUse atomic.Int32 var closed atomic.Bool m := &MMDB{source: src} m.lookup = func(_ net.IP, result any) error { inUse.Add(1) defer inUse.Add(-1) if closed.Load() { closedWhileInUse.Store(true) } rec := result.(*mmdbRecord) rec.Country.ISOCode = src[:2] time.Sleep(time.Microsecond) return nil } m.closeHook = func() { if inUse.Load() != 0 { closedWhileInUse.Store(true) } closed.Store(true) } return m } s.Swap(mk("AAsrc")) var wg sync.WaitGroup stop := make(chan struct{}) var mismatched atomic.Bool for i := 0; i < 32; i++ { wg.Add(1) go func() { defer wg.Done() for { select { case <-stop: return default: } snap := s.Lookup(net.ParseIP("81.2.69.142")) if snap.Found && snap.Result.Country != snap.Source[:2] { mismatched.Store(true) } s.Locate(net.ParseIP("81.2.69.142")) } }() } for i := 0; i < 100; i++ { if i%2 == 0 { s.Swap(mk("BBsrc")) } else { s.Swap(mk("AAsrc")) } } close(stop) wg.Wait() if closedWhileInUse.Load() { t.Fatal("a reader was closed while a lookup was using it") } if mismatched.Load() { t.Fatal("a snapshot carried a different reader's source") } }