mirror of
				https://github.com/tailscale/tailscale.git
				synced 2025-10-31 08:11:32 +01:00 
			
		
		
		
	Also perform minor cleanups on the ctxkey package itself. Provide guidance on when to use ctxkey.Key[T] over ctxkey.New. Also, allow for interface kinds because the value wrapping trick also happens to fix edge cases with interfaces in Go. Updates #cleanup Signed-off-by: Joe Tsai <joetsai@digital-static.net>
		
			
				
	
	
		
			105 lines
		
	
	
		
			3.5 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			105 lines
		
	
	
		
			3.5 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| // Copyright (c) Tailscale Inc & AUTHORS
 | |
| // SPDX-License-Identifier: BSD-3-Clause
 | |
| 
 | |
| package ctxkey
 | |
| 
 | |
| import (
 | |
| 	"context"
 | |
| 	"fmt"
 | |
| 	"io"
 | |
| 	"regexp"
 | |
| 	"testing"
 | |
| 	"time"
 | |
| 
 | |
| 	qt "github.com/frankban/quicktest"
 | |
| )
 | |
| 
 | |
| func TestKey(t *testing.T) {
 | |
| 	c := qt.New(t)
 | |
| 	ctx := context.Background()
 | |
| 
 | |
| 	// Test keys with the same name as being distinct.
 | |
| 	k1 := New("same.Name", "")
 | |
| 	c.Assert(k1.String(), qt.Equals, "same.Name")
 | |
| 	k2 := New("same.Name", "")
 | |
| 	c.Assert(k2.String(), qt.Equals, "same.Name")
 | |
| 	c.Assert(k1 == k2, qt.Equals, false)
 | |
| 	ctx = k1.WithValue(ctx, "hello")
 | |
| 	c.Assert(k1.Has(ctx), qt.Equals, true)
 | |
| 	c.Assert(k1.Value(ctx), qt.Equals, "hello")
 | |
| 	c.Assert(k2.Has(ctx), qt.Equals, false)
 | |
| 	c.Assert(k2.Value(ctx), qt.Equals, "")
 | |
| 	ctx = k2.WithValue(ctx, "goodbye")
 | |
| 	c.Assert(k1.Has(ctx), qt.Equals, true)
 | |
| 	c.Assert(k1.Value(ctx), qt.Equals, "hello")
 | |
| 	c.Assert(k2.Has(ctx), qt.Equals, true)
 | |
| 	c.Assert(k2.Value(ctx), qt.Equals, "goodbye")
 | |
| 
 | |
| 	// Test default value.
 | |
| 	k3 := New("mapreduce.Timeout", time.Hour)
 | |
| 	c.Assert(k3.Has(ctx), qt.Equals, false)
 | |
| 	c.Assert(k3.Value(ctx), qt.Equals, time.Hour)
 | |
| 	ctx = k3.WithValue(ctx, time.Minute)
 | |
| 	c.Assert(k3.Has(ctx), qt.Equals, true)
 | |
| 	c.Assert(k3.Value(ctx), qt.Equals, time.Minute)
 | |
| 
 | |
| 	// Test incomparable value.
 | |
| 	k4 := New("slice", []int(nil))
 | |
| 	c.Assert(k4.Has(ctx), qt.Equals, false)
 | |
| 	c.Assert(k4.Value(ctx), qt.DeepEquals, []int(nil))
 | |
| 	ctx = k4.WithValue(ctx, []int{1, 2, 3})
 | |
| 	c.Assert(k4.Has(ctx), qt.Equals, true)
 | |
| 	c.Assert(k4.Value(ctx), qt.DeepEquals, []int{1, 2, 3})
 | |
| 
 | |
| 	// Accessors should be allocation free.
 | |
| 	c.Assert(testing.AllocsPerRun(100, func() {
 | |
| 		k1.Value(ctx)
 | |
| 		k1.Has(ctx)
 | |
| 		k1.ValueOk(ctx)
 | |
| 	}), qt.Equals, 0.0)
 | |
| 
 | |
| 	// Test keys that are created without New.
 | |
| 	var k5 Key[string]
 | |
| 	c.Assert(k5.String(), qt.Equals, "string")
 | |
| 	c.Assert(k1 == k5, qt.Equals, false) // should be different from key created by New
 | |
| 	c.Assert(k5.Has(ctx), qt.Equals, false)
 | |
| 	ctx = k5.WithValue(ctx, "fizz")
 | |
| 	c.Assert(k5.Value(ctx), qt.Equals, "fizz")
 | |
| 	var k6 Key[string]
 | |
| 	c.Assert(k6.String(), qt.Equals, "string")
 | |
| 	c.Assert(k5 == k6, qt.Equals, true)
 | |
| 	c.Assert(k6.Has(ctx), qt.Equals, true)
 | |
| 	ctx = k6.WithValue(ctx, "fizz")
 | |
| 
 | |
| 	// Test interface value types.
 | |
| 	var k7 Key[any]
 | |
| 	c.Assert(k7.Has(ctx), qt.Equals, false)
 | |
| 	ctx = k7.WithValue(ctx, "whatever")
 | |
| 	c.Assert(k7.Value(ctx), qt.DeepEquals, "whatever")
 | |
| 	ctx = k7.WithValue(ctx, []int{1, 2, 3})
 | |
| 	c.Assert(k7.Value(ctx), qt.DeepEquals, []int{1, 2, 3})
 | |
| 	ctx = k7.WithValue(ctx, nil)
 | |
| 	c.Assert(k7.Has(ctx), qt.Equals, true)
 | |
| 	c.Assert(k7.Value(ctx), qt.DeepEquals, nil)
 | |
| 	k8 := New[error]("error", io.EOF)
 | |
| 	c.Assert(k8.Has(ctx), qt.Equals, false)
 | |
| 	c.Assert(k8.Value(ctx), qt.Equals, io.EOF)
 | |
| 	ctx = k8.WithValue(ctx, nil)
 | |
| 	c.Assert(k8.Value(ctx), qt.Equals, nil)
 | |
| 	c.Assert(k8.Has(ctx), qt.Equals, true)
 | |
| 	err := fmt.Errorf("read error: %w", io.ErrUnexpectedEOF)
 | |
| 	ctx = k8.WithValue(ctx, err)
 | |
| 	c.Assert(k8.Value(ctx), qt.Equals, err)
 | |
| 	c.Assert(k8.Has(ctx), qt.Equals, true)
 | |
| }
 | |
| 
 | |
| func TestStringer(t *testing.T) {
 | |
| 	t.SkipNow() // TODO(https://go.dev/cl/555697): Enable this after fix is merged upstream.
 | |
| 	c := qt.New(t)
 | |
| 	ctx := context.Background()
 | |
| 	c.Assert(fmt.Sprint(New("foo.Bar", "").WithValue(ctx, "baz")), qt.Matches, regexp.MustCompile("foo.Bar.*baz"))
 | |
| 	c.Assert(fmt.Sprint(New("", []int{}).WithValue(ctx, []int{1, 2, 3})), qt.Matches, regexp.MustCompile(fmt.Sprintf("%[1]T.*%[1]v", []int{1, 2, 3})))
 | |
| 	c.Assert(fmt.Sprint(New("", 0).WithValue(ctx, 5)), qt.Matches, regexp.MustCompile("int.*5"))
 | |
| 	c.Assert(fmt.Sprint(Key[time.Duration]{}.WithValue(ctx, time.Hour)), qt.Matches, regexp.MustCompile(fmt.Sprintf("%[1]T.*%[1]v", time.Hour)))
 | |
| }
 |