1// Copyright 2024 The Go Authors. All rights reserved. 2// Use of this source code is governed by a BSD-style 3// license that can be found in the LICENSE file. 4 5package unique 6 7import ( 8 "fmt" 9 "internal/abi" 10 "reflect" 11 "runtime" 12 "strings" 13 "testing" 14 "time" 15 "unsafe" 16) 17 18// Set up special types. Because the internal maps are sharded by type, 19// this will ensure that we're not overlapping with other tests. 20type testString string 21type testIntArray [4]int 22type testEface any 23type testStringArray [3]string 24type testStringStruct struct { 25 a string 26} 27type testStringStructArrayStruct struct { 28 s [2]testStringStruct 29} 30type testStruct struct { 31 z float64 32 b string 33} 34 35func TestHandle(t *testing.T) { 36 testHandle[testString](t, "foo") 37 testHandle[testString](t, "bar") 38 testHandle[testString](t, "") 39 testHandle[testIntArray](t, [4]int{7, 77, 777, 7777}) 40 testHandle[testEface](t, nil) 41 testHandle[testStringArray](t, [3]string{"a", "b", "c"}) 42 testHandle[testStringStruct](t, testStringStruct{"x"}) 43 testHandle[testStringStructArrayStruct](t, testStringStructArrayStruct{ 44 s: [2]testStringStruct{testStringStruct{"y"}, testStringStruct{"z"}}, 45 }) 46 testHandle[testStruct](t, testStruct{0.5, "184"}) 47 testHandle[testEface](t, testEface("hello")) 48} 49 50func testHandle[T comparable](t *testing.T, value T) { 51 name := reflect.TypeFor[T]().Name() 52 t.Run(fmt.Sprintf("%s/%#v", name, value), func(t *testing.T) { 53 t.Parallel() 54 55 v0 := Make(value) 56 v1 := Make(value) 57 58 if v0.Value() != v1.Value() { 59 t.Error("v0.Value != v1.Value") 60 } 61 if v0.Value() != value { 62 t.Errorf("v0.Value not %#v", value) 63 } 64 if v0 != v1 { 65 t.Error("v0 != v1") 66 } 67 68 drainMaps(t) 69 checkMapsFor(t, value) 70 }) 71} 72 73// drainMaps ensures that the internal maps are drained. 74func drainMaps(t *testing.T) { 75 t.Helper() 76 77 wait := make(chan struct{}, 1) 78 79 // Set up a one-time notification for the next time the cleanup runs. 80 // Note: this will only run if there's no other active cleanup, so 81 // we can be sure that the next time cleanup runs, it'll see the new 82 // notification. 83 cleanupMu.Lock() 84 cleanupNotify = append(cleanupNotify, func() { 85 select { 86 case wait <- struct{}{}: 87 default: 88 } 89 }) 90 91 runtime.GC() 92 cleanupMu.Unlock() 93 94 // Wait until cleanup runs. 95 <-wait 96} 97 98func checkMapsFor[T comparable](t *testing.T, value T) { 99 // Manually load the value out of the map. 100 typ := abi.TypeFor[T]() 101 a, ok := uniqueMaps.Load(typ) 102 if !ok { 103 return 104 } 105 m := a.(*uniqueMap[T]) 106 wp, ok := m.Load(value) 107 if !ok { 108 return 109 } 110 if wp.Strong() != nil { 111 t.Errorf("value %v still referenced a handle (or tiny block?) ", value) 112 return 113 } 114 t.Errorf("failed to drain internal maps of %v", value) 115} 116 117func TestMakeClonesStrings(t *testing.T) { 118 s := strings.Clone("abcdefghijklmnopqrstuvwxyz") // N.B. Must be big enough to not be tiny-allocated. 119 ran := make(chan bool) 120 runtime.SetFinalizer(unsafe.StringData(s), func(_ *byte) { 121 ran <- true 122 }) 123 h := Make(s) 124 125 // Clean up s (hopefully) and run the finalizer. 126 runtime.GC() 127 128 select { 129 case <-time.After(1 * time.Second): 130 t.Fatal("string was improperly retained") 131 case <-ran: 132 } 133 runtime.KeepAlive(h) 134} 135