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  	"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.
    20  type testString string
    21  type testIntArray [4]int
    22  type testEface any
    23  type testStringArray [3]string
    24  type testStringStruct struct {
    25  	a string
    26  }
    27  type testStringStructArrayStruct struct {
    28  	s [2]testStringStruct
    29  }
    30  type testStruct struct {
    31  	z float64
    32  	b string
    33  }
    34  
    35  func 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  
    50  func 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.
    74  func 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  
    98  func 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  
   117  func 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  

View as plain text