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  
    37  func TestHandle(t *testing.T) {
    38  	testHandle(t, testString("foo"))
    39  	testHandle(t, testString("bar"))
    40  	testHandle(t, testString(""))
    41  	testHandle(t, testIntArray{7, 77, 777, 7777})
    42  	testHandle(t, testEface(nil))
    43  	testHandle(t, testStringArray{"a", "b", "c"})
    44  	testHandle(t, testStringStruct{"x"})
    45  	testHandle(t, testStringStructArrayStruct{
    46  		s: [2]testStringStruct{{"y"}, {"z"}},
    47  	})
    48  	testHandle(t, testStruct{0.5, "184"})
    49  	testHandle(t, testEface("hello"))
    50  	testHandle(t, testZeroSize(struct{}{}))
    51  }
    52  
    53  func testHandle[T comparable](t *testing.T, value T) {
    54  	name := reflect.TypeFor[T]().Name()
    55  	t.Run(fmt.Sprintf("%s/%#v", name, value), func(t *testing.T) {
    56  		t.Parallel()
    57  
    58  		v0 := Make(value)
    59  		v1 := Make(value)
    60  
    61  		if v0.Value() != v1.Value() {
    62  			t.Error("v0.Value != v1.Value")
    63  		}
    64  		if v0.Value() != value {
    65  			t.Errorf("v0.Value not %#v", value)
    66  		}
    67  		if v0 != v1 {
    68  			t.Error("v0 != v1")
    69  		}
    70  
    71  		drainMaps[T](t)
    72  		checkMapsFor(t, value)
    73  	})
    74  }
    75  
    76  // drainMaps ensures that the internal maps are drained.
    77  func drainMaps[T comparable](t *testing.T) {
    78  	t.Helper()
    79  
    80  	if unsafe.Sizeof(*(new(T))) == 0 {
    81  		return // zero-size types are not inserted.
    82  	}
    83  
    84  	wait := make(chan struct{}, 1)
    85  
    86  	// Set up a one-time notification for the next time the cleanup runs.
    87  	// Note: this will only run if there's no other active cleanup, so
    88  	// we can be sure that the next time cleanup runs, it'll see the new
    89  	// notification.
    90  	cleanupMu.Lock()
    91  	cleanupNotify = append(cleanupNotify, func() {
    92  		select {
    93  		case wait <- struct{}{}:
    94  		default:
    95  		}
    96  	})
    97  
    98  	runtime.GC()
    99  	cleanupMu.Unlock()
   100  
   101  	// Wait until cleanup runs.
   102  	<-wait
   103  }
   104  
   105  func checkMapsFor[T comparable](t *testing.T, value T) {
   106  	// Manually load the value out of the map.
   107  	typ := abi.TypeFor[T]()
   108  	a, ok := uniqueMaps.Load(typ)
   109  	if !ok {
   110  		return
   111  	}
   112  	m := a.(*uniqueMap[T])
   113  	wp, ok := m.Load(value)
   114  	if !ok {
   115  		return
   116  	}
   117  	if wp.Strong() != nil {
   118  		t.Errorf("value %v still referenced a handle (or tiny block?) ", value)
   119  		return
   120  	}
   121  	t.Errorf("failed to drain internal maps of %v", value)
   122  }
   123  
   124  func TestMakeClonesStrings(t *testing.T) {
   125  	s := strings.Clone("abcdefghijklmnopqrstuvwxyz") // N.B. Must be big enough to not be tiny-allocated.
   126  	ran := make(chan bool)
   127  	runtime.SetFinalizer(unsafe.StringData(s), func(_ *byte) {
   128  		ran <- true
   129  	})
   130  	h := Make(s)
   131  
   132  	// Clean up s (hopefully) and run the finalizer.
   133  	runtime.GC()
   134  
   135  	select {
   136  	case <-time.After(1 * time.Second):
   137  		t.Fatal("string was improperly retained")
   138  	case <-ran:
   139  	}
   140  	runtime.KeepAlive(h)
   141  }
   142  
   143  func TestHandleUnsafeString(t *testing.T) {
   144  	var testData []string
   145  	for i := range 1024 {
   146  		testData = append(testData, strconv.Itoa(i))
   147  	}
   148  	var buf []byte
   149  	var handles []Handle[string]
   150  	for _, s := range testData {
   151  		if len(buf) < len(s) {
   152  			buf = make([]byte, len(s)*2)
   153  		}
   154  		copy(buf, s)
   155  		sbuf := unsafe.String(&buf[0], len(s))
   156  		handles = append(handles, Make(sbuf))
   157  	}
   158  	for i, s := range testData {
   159  		h := Make(s)
   160  		if handles[i].Value() != h.Value() {
   161  			t.Fatal("unsafe string improperly retained internally")
   162  		}
   163  	}
   164  }
   165  

View as plain text