| // Copyright 2020 The Go Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style |
| // license that can be found in the LICENSE file. |
| |
| package intern |
| |
| import ( |
| "fmt" |
| "runtime" |
| "testing" |
| ) |
| |
| func TestBasics(t *testing.T) { |
| clearMap() |
| foo := Get("foo") |
| bar := Get("bar") |
| empty := Get("") |
| nilEface := Get(nil) |
| i := Get(0x7777777) |
| foo2 := Get("foo") |
| bar2 := Get("bar") |
| empty2 := Get("") |
| nilEface2 := Get(nil) |
| i2 := Get(0x7777777) |
| foo3 := GetByString("foo") |
| empty3 := GetByString("") |
| |
| if foo.Get() != foo2.Get() { |
| t.Error("foo/foo2 values differ") |
| } |
| if foo.Get() != foo3.Get() { |
| t.Error("foo/foo3 values differ") |
| } |
| if foo.Get() != "foo" { |
| t.Error("foo.Get not foo") |
| } |
| if foo != foo2 { |
| t.Error("foo/foo2 pointers differ") |
| } |
| if foo != foo3 { |
| t.Error("foo/foo3 pointers differ") |
| } |
| |
| if bar.Get() != bar2.Get() { |
| t.Error("bar values differ") |
| } |
| if bar.Get() != "bar" { |
| t.Error("bar.Get not bar") |
| } |
| if bar != bar2 { |
| t.Error("bar pointers differ") |
| } |
| |
| if i.Get() != i.Get() { |
| t.Error("i values differ") |
| } |
| if i.Get() != 0x7777777 { |
| t.Error("i.Get not 0x7777777") |
| } |
| if i != i2 { |
| t.Error("i pointers differ") |
| } |
| |
| if empty.Get() != empty2.Get() { |
| t.Error("empty/empty2 values differ") |
| } |
| if empty.Get() != empty.Get() { |
| t.Error("empty/empty3 values differ") |
| } |
| if empty.Get() != "" { |
| t.Error("empty.Get not empty string") |
| } |
| if empty != empty2 { |
| t.Error("empty/empty2 pointers differ") |
| } |
| if empty != empty3 { |
| t.Error("empty/empty3 pointers differ") |
| } |
| |
| if nilEface.Get() != nilEface2.Get() { |
| t.Error("nilEface values differ") |
| } |
| if nilEface.Get() != nil { |
| t.Error("nilEface.Get not nil") |
| } |
| if nilEface != nilEface2 { |
| t.Error("nilEface pointers differ") |
| } |
| |
| if n := mapLen(); n != 5 { |
| if runtime.Compiler != "gccgo" { |
| t.Errorf("map len = %d; want 4", n) |
| } |
| } |
| |
| wantEmpty(t) |
| } |
| |
| func wantEmpty(t testing.TB) { |
| if runtime.Compiler == "gccgo" { |
| // Fails with conservative GC. |
| return |
| } |
| t.Helper() |
| const gcTries = 5000 |
| for try := 0; try < gcTries; try++ { |
| runtime.GC() |
| n := mapLen() |
| if n == 0 { |
| break |
| } |
| if try == gcTries-1 { |
| t.Errorf("map len = %d after (%d GC tries); want 0, contents: %v", n, gcTries, mapKeys()) |
| } |
| } |
| } |
| |
| func TestStress(t *testing.T) { |
| iters := 10000 |
| if testing.Short() { |
| iters = 1000 |
| } |
| var sink []byte |
| for i := 0; i < iters; i++ { |
| _ = Get("foo") |
| sink = make([]byte, 1<<20) |
| } |
| _ = sink |
| } |
| |
| func BenchmarkStress(b *testing.B) { |
| done := make(chan struct{}) |
| defer close(done) |
| go func() { |
| for { |
| select { |
| case <-done: |
| return |
| default: |
| } |
| runtime.GC() |
| } |
| }() |
| |
| clearMap() |
| v1 := Get("foo") |
| b.ReportAllocs() |
| b.RunParallel(func(pb *testing.PB) { |
| for pb.Next() { |
| v2 := Get("foo") |
| if v1 != v2 { |
| b.Fatal("wrong value") |
| } |
| // And also a key we don't retain: |
| _ = Get("bar") |
| } |
| }) |
| runtime.GC() |
| wantEmpty(b) |
| } |
| |
| func mapLen() int { |
| mu.Lock() |
| defer mu.Unlock() |
| return len(valMap) |
| } |
| |
| func mapKeys() (keys []string) { |
| mu.Lock() |
| defer mu.Unlock() |
| for k := range valMap { |
| keys = append(keys, fmt.Sprint(k)) |
| } |
| return keys |
| } |
| |
| func clearMap() { |
| mu.Lock() |
| defer mu.Unlock() |
| for k := range valMap { |
| delete(valMap, k) |
| } |
| } |
| |
| var ( |
| globalString = "not a constant" |
| sink string |
| ) |
| |
| func TestGetByStringAllocs(t *testing.T) { |
| allocs := int(testing.AllocsPerRun(100, func() { |
| GetByString(globalString) |
| })) |
| if allocs != 0 { |
| t.Errorf("GetString allocated %d objects, want 0", allocs) |
| } |
| } |
| |
| func BenchmarkGetByString(b *testing.B) { |
| b.ReportAllocs() |
| for i := 0; i < b.N; i++ { |
| v := GetByString(globalString) |
| sink = v.Get().(string) |
| } |
| } |