Source file src/unique/handle_test.go

     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  
     5  package unique
     6  
     7  import (
     8  	"fmt"
     9  	"internal/abi"
    10  	"reflect"
    11  	"runtime"
    12  	"strconv"
    13  	"strings"
    14  	"testing"
    15  	"time"
    16  	"unsafe"
    17  )
    18  
    19  // Set up special types. Because the internal maps are sharded by type,
    20  // this will ensure that we're not overlapping with other tests.
    21  type testString string
    22  type testIntArray [4]int
    23  type testEface any
    24  type testStringArray [3]string
    25  type testStringStruct struct {
    26  	a string
    27  }
    28  type testStringStructArrayStruct struct {
    29  	s [2]testStringStruct
    30  }
    31  type testStruct struct {
    32  	z float64
    33  	b string
    34  }
    35  type testZeroSize struct{}
    36  type testNestedHandle struct {
    37  	next Handle[testNestedHandle]
    38  	arr  [6]int
    39  }
    40  
    41  func TestHandle(t *testing.T) {
    42  	testHandle(t, testString("foo"))
    43  	testHandle(t, testString("bar"))
    44  	testHandle(t, testString(""))
    45  	testHandle(t, testIntArray{7, 77, 777, 7777})
    46  	testHandle(t, testEface(nil))
    47  	testHandle(t, testStringArray{"a", "b", "c"})
    48  	testHandle(t, testStringStruct{"x"})
    49  	testHandle(t, testStringStructArrayStruct{
    50  		s: [2]testStringStruct{{"y"}, {"z"}},
    51  	})
    52  	testHandle(t, testStruct{0.5, "184"})
    53  	testHandle(t, testEface("hello"))
    54  	testHandle(t, testZeroSize(struct{}{}))
    55  }
    56  
    57  func testHandle[T comparable](t *testing.T, value T) {
    58  	name := reflect.TypeFor[T]().Name()
    59  	t.Run(fmt.Sprintf("%s/%#v", name, value), func(t *testing.T) {
    60  		v0 := Make(value)
    61  		v1 := Make(value)
    62  
    63  		if v0.Value() != v1.Value() {
    64  			t.Error("v0.Value != v1.Value")
    65  		}
    66  		if v0.Value() != value {
    67  			t.Errorf("v0.Value not %#v", value)
    68  		}
    69  		if v0 != v1 {
    70  			t.Error("v0 != v1")
    71  		}
    72  
    73  		drainMaps[T](t)
    74  		checkMapsFor(t, value)
    75  	})
    76  }
    77  
    78  // drainMaps ensures that the internal maps are drained.
    79  func drainMaps[T comparable](t *testing.T) {
    80  	t.Helper()
    81  
    82  	if unsafe.Sizeof(*(new(T))) == 0 {
    83  		return // zero-size types are not inserted.
    84  	}
    85  	drainCleanupQueue(t)
    86  }
    87  
    88  func drainCleanupQueue(t *testing.T) {
    89  	t.Helper()
    90  
    91  	runtime.GC() // Queue up the cleanups.
    92  	runtime_blockUntilEmptyCleanupQueue(int64(5 * time.Second))
    93  }
    94  
    95  func checkMapsFor[T comparable](t *testing.T, value T) {
    96  	// Manually load the value out of the map.
    97  	typ := abi.TypeFor[T]()
    98  	a, ok := uniqueMaps.Load(typ)
    99  	if !ok {
   100  		return
   101  	}
   102  	m := a.(*uniqueMap[T])
   103  	p := m.Load(value)
   104  	if p != nil {
   105  		t.Errorf("value %v still referenced by a handle (or tiny block?): internal pointer %p", value, p)
   106  	}
   107  }
   108  
   109  func TestMakeClonesStrings(t *testing.T) {
   110  	s := strings.Clone("abcdefghijklmnopqrstuvwxyz") // N.B. Must be big enough to not be tiny-allocated.
   111  	ran := make(chan bool)
   112  	runtime.AddCleanup(unsafe.StringData(s), func(ch chan bool) {
   113  		ch <- true
   114  	}, ran)
   115  	h := Make(s)
   116  
   117  	// Clean up s (hopefully) and run the cleanup.
   118  	runtime.GC()
   119  
   120  	select {
   121  	case <-time.After(1 * time.Second):
   122  		t.Fatal("string was improperly retained")
   123  	case <-ran:
   124  	}
   125  	runtime.KeepAlive(h)
   126  }
   127  
   128  func TestHandleUnsafeString(t *testing.T) {
   129  	var testData []string
   130  	for i := range 1024 {
   131  		testData = append(testData, strconv.Itoa(i))
   132  	}
   133  	var buf []byte
   134  	var handles []Handle[string]
   135  	for _, s := range testData {
   136  		if len(buf) < len(s) {
   137  			buf = make([]byte, len(s)*2)
   138  		}
   139  		copy(buf, s)
   140  		sbuf := unsafe.String(&buf[0], len(s))
   141  		handles = append(handles, Make(sbuf))
   142  	}
   143  	for i, s := range testData {
   144  		h := Make(s)
   145  		if handles[i].Value() != h.Value() {
   146  			t.Fatal("unsafe string improperly retained internally")
   147  		}
   148  	}
   149  }
   150  
   151  func nestHandle(n testNestedHandle) testNestedHandle {
   152  	return testNestedHandle{
   153  		next: Make(n),
   154  		arr:  n.arr,
   155  	}
   156  }
   157  
   158  func TestNestedHandle(t *testing.T) {
   159  	n0 := testNestedHandle{arr: [6]int{1, 2, 3, 4, 5, 6}}
   160  	n1 := nestHandle(n0)
   161  	n2 := nestHandle(n1)
   162  	n3 := nestHandle(n2)
   163  
   164  	if v := n3.next.Value(); v != n2 {
   165  		t.Errorf("n3.Value != n2: %#v vs. %#v", v, n2)
   166  	}
   167  	if v := n2.next.Value(); v != n1 {
   168  		t.Errorf("n2.Value != n1: %#v vs. %#v", v, n1)
   169  	}
   170  	if v := n1.next.Value(); v != n0 {
   171  		t.Errorf("n1.Value != n0: %#v vs. %#v", v, n0)
   172  	}
   173  
   174  	// In a good implementation, the entire chain, down to the bottom-most
   175  	// value, should all be gone after we drain the maps.
   176  	drainMaps[testNestedHandle](t)
   177  	checkMapsFor(t, n0)
   178  }
   179  
   180  // Implemented in runtime.
   181  //
   182  // Used only by tests.
   183  //
   184  //go:linkname runtime_blockUntilEmptyCleanupQueue
   185  func runtime_blockUntilEmptyCleanupQueue(timeout int64) bool
   186  

View as plain text