// Copyright 2025 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 tls import ( "encoding/pem" "runtime" "testing" "time" ) func TestWeakCertCache(t *testing.T) { wcc := weakCertCache{} p, _ := pem.Decode([]byte(rsaCertPEM)) if p == nil { t.Fatal("Failed to decode certificate") } certA, err := wcc.newCert(p.Bytes) if err != nil { t.Fatalf("newCert failed: %s", err) } certB, err := wcc.newCert(p.Bytes) if err != nil { t.Fatalf("newCert failed: %s", err) } if certA != certB { t.Fatal("newCert returned a unique reference for a duplicate certificate") } if _, ok := wcc.Load(string(p.Bytes)); !ok { t.Fatal("cache does not contain expected entry") } timeoutRefCheck := func(t *testing.T, key string, present bool) { t.Helper() timeout := time.After(4 * time.Second) for { select { case <-timeout: t.Fatal("timed out waiting for expected ref count") default: _, ok := wcc.Load(key) if ok == present { return } } // Explicitly yield to the scheduler. // // On single-threaded platforms like js/wasm a busy-loop might // never call into the scheduler for the full timeout, meaning // that if we arrive here and the cleanup hasn't already run, // we'll simply loop until the timeout. Busy-loops put us at the // mercy of the Go scheduler, making this test fragile on some // platforms. runtime.Gosched() } } // Keep certA alive until at least now, so that we can // purposefully nil it and force the finalizer to be // called. runtime.KeepAlive(certA) certA = nil runtime.GC() timeoutRefCheck(t, string(p.Bytes), true) // Keep certB alive until at least now, so that we can // purposefully nil it and force the finalizer to be // called. runtime.KeepAlive(certB) certB = nil runtime.GC() timeoutRefCheck(t, string(p.Bytes), false) }