| // Copyright 2024 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 unique |
| |
| import ( |
| "fmt" |
| "internal/abi" |
| "reflect" |
| "runtime" |
| "testing" |
| ) |
| |
| // Set up special types. Because the internal maps are sharded by type, |
| // this will ensure that we're not overlapping with other tests. |
| type testString string |
| type testIntArray [4]int |
| type testEface any |
| type testStringArray [3]string |
| type testStringStruct struct { |
| a string |
| } |
| type testStringStructArrayStruct struct { |
| s [2]testStringStruct |
| } |
| type testStruct struct { |
| z float64 |
| b string |
| } |
| |
| func TestHandle(t *testing.T) { |
| testHandle[testString](t, "foo") |
| testHandle[testString](t, "bar") |
| testHandle[testString](t, "") |
| testHandle[testIntArray](t, [4]int{7, 77, 777, 7777}) |
| testHandle[testEface](t, nil) |
| testHandle[testStringArray](t, [3]string{"a", "b", "c"}) |
| testHandle[testStringStruct](t, testStringStruct{"x"}) |
| testHandle[testStringStructArrayStruct](t, testStringStructArrayStruct{ |
| s: [2]testStringStruct{testStringStruct{"y"}, testStringStruct{"z"}}, |
| }) |
| testHandle[testStruct](t, testStruct{0.5, "184"}) |
| } |
| |
| func testHandle[T comparable](t *testing.T, value T) { |
| name := reflect.TypeFor[T]().Name() |
| t.Run(fmt.Sprintf("%s/%#v", name, value), func(t *testing.T) { |
| t.Parallel() |
| |
| v0 := Make(value) |
| v1 := Make(value) |
| |
| if v0.Value() != v1.Value() { |
| t.Error("v0.Value != v1.Value") |
| } |
| if v0.Value() != value { |
| t.Errorf("v0.Value not %#v", value) |
| } |
| if v0 != v1 { |
| t.Error("v0 != v1") |
| } |
| |
| drainMaps(t) |
| checkMapsFor(t, value) |
| }) |
| } |
| |
| // drainMaps ensures that the internal maps are drained. |
| func drainMaps(t *testing.T) { |
| t.Helper() |
| |
| wait := make(chan struct{}, 1) |
| |
| // Set up a one-time notification for the next time the cleanup runs. |
| // Note: this will only run if there's no other active cleanup, so |
| // we can be sure that the next time cleanup runs, it'll see the new |
| // notification. |
| cleanupMu.Lock() |
| cleanupNotify = append(cleanupNotify, func() { |
| select { |
| case wait <- struct{}{}: |
| default: |
| } |
| }) |
| |
| runtime.GC() |
| cleanupMu.Unlock() |
| |
| // Wait until cleanup runs. |
| <-wait |
| } |
| |
| func checkMapsFor[T comparable](t *testing.T, value T) { |
| // Manually load the value out of the map. |
| typ := abi.TypeOf(value) |
| a, ok := uniqueMaps.Load(typ) |
| if !ok { |
| return |
| } |
| m := a.(*uniqueMap[T]) |
| wp, ok := m.Load(value) |
| if !ok { |
| return |
| } |
| if wp.Strong() != nil { |
| t.Errorf("value %v still referenced a handle (or tiny block?) ", value) |
| return |
| } |
| t.Errorf("failed to drain internal maps of %v", value) |
| } |