aboutsummaryrefslogtreecommitdiff
path: root/src/unique/handle_test.go
diff options
context:
space:
mode:
Diffstat (limited to 'src/unique/handle_test.go')
-rw-r--r--src/unique/handle_test.go111
1 files changed, 111 insertions, 0 deletions
diff --git a/src/unique/handle_test.go b/src/unique/handle_test.go
new file mode 100644
index 0000000000..dffe10ac72
--- /dev/null
+++ b/src/unique/handle_test.go
@@ -0,0 +1,111 @@
+// 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)
+}