// Copyright 2017 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 pprof import ( "context" "fmt" "reflect" "slices" "strings" "testing" ) func labelsSorted(ctx context.Context) []label { ls := []label{} ForLabels(ctx, func(key, value string) bool { ls = append(ls, label{key, value}) return true }) slices.SortFunc(ls, func(a, b label) int { return strings.Compare(a.key, b.key) }) return ls } func TestContextLabels(t *testing.T) { // Background context starts with no labels. ctx := context.Background() labels := labelsSorted(ctx) if len(labels) != 0 { t.Errorf("labels on background context: want [], got %v ", labels) } // Add a single label. ctx = WithLabels(ctx, Labels("key", "value")) // Retrieve it with Label. v, ok := Label(ctx, "key") if !ok || v != "value" { t.Errorf(`Label(ctx, "key"): got %v, %v; want "value", ok`, v, ok) } gotLabels := labelsSorted(ctx) wantLabels := []label{{"key", "value"}} if !reflect.DeepEqual(gotLabels, wantLabels) { t.Errorf("(sorted) labels on context: got %v, want %v", gotLabels, wantLabels) } // Add a label with a different key. ctx = WithLabels(ctx, Labels("key2", "value2")) v, ok = Label(ctx, "key2") if !ok || v != "value2" { t.Errorf(`Label(ctx, "key2"): got %v, %v; want "value2", ok`, v, ok) } gotLabels = labelsSorted(ctx) wantLabels = []label{{"key", "value"}, {"key2", "value2"}} if !reflect.DeepEqual(gotLabels, wantLabels) { t.Errorf("(sorted) labels on context: got %v, want %v", gotLabels, wantLabels) } // Add label with first key to test label replacement. ctx = WithLabels(ctx, Labels("key", "value3")) v, ok = Label(ctx, "key") if !ok || v != "value3" { t.Errorf(`Label(ctx, "key3"): got %v, %v; want "value3", ok`, v, ok) } gotLabels = labelsSorted(ctx) wantLabels = []label{{"key", "value3"}, {"key2", "value2"}} if !reflect.DeepEqual(gotLabels, wantLabels) { t.Errorf("(sorted) labels on context: got %v, want %v", gotLabels, wantLabels) } // Labels called with two labels with the same key should pick the second. ctx = WithLabels(ctx, Labels("key4", "value4a", "key4", "value4b")) v, ok = Label(ctx, "key4") if !ok || v != "value4b" { t.Errorf(`Label(ctx, "key4"): got %v, %v; want "value4b", ok`, v, ok) } gotLabels = labelsSorted(ctx) wantLabels = []label{{"key", "value3"}, {"key2", "value2"}, {"key4", "value4b"}} if !reflect.DeepEqual(gotLabels, wantLabels) { t.Errorf("(sorted) labels on context: got %v, want %v", gotLabels, wantLabels) } } func TestLabelMapStringer(t *testing.T) { for _, tbl := range []struct { m labelMap expected string }{ { m: labelMap{ // empty map }, expected: "{}", }, { m: labelMap{ "foo": "bar", }, expected: `{"foo":"bar"}`, }, { m: labelMap{ "foo": "bar", "key1": "value1", "key2": "value2", "key3": "value3", "key4WithNewline": "\nvalue4", }, expected: `{"foo":"bar", "key1":"value1", "key2":"value2", "key3":"value3", "key4WithNewline":"\nvalue4"}`, }, } { if got := tbl.m.String(); tbl.expected != got { t.Errorf("%#v.String() = %q; want %q", tbl.m, got, tbl.expected) } } } func BenchmarkLabels(b *testing.B) { b.Run("set-one", func(b *testing.B) { b.ReportAllocs() b.ResetTimer() for i := 0; i < b.N; i++ { Do(context.Background(), Labels("key", "value"), func(context.Context) {}) } }) b.Run("merge-one", func(b *testing.B) { ctx := WithLabels(context.Background(), Labels("key1", "val1")) b.ReportAllocs() b.ResetTimer() for i := 0; i < b.N; i++ { Do(ctx, Labels("key2", "value2"), func(context.Context) {}) } }) b.Run("overwrite-one", func(b *testing.B) { ctx := WithLabels(context.Background(), Labels("key", "val")) b.ReportAllocs() b.ResetTimer() for i := 0; i < b.N; i++ { Do(ctx, Labels("key", "value"), func(context.Context) {}) } }) for _, scenario := range []string{"ordered", "unordered"} { var labels []string for i := 0; i < 10; i++ { labels = append(labels, fmt.Sprintf("key%03d", i), fmt.Sprintf("value%03d", i)) } if scenario == "unordered" { labels[0], labels[len(labels)-1] = labels[len(labels)-1], labels[0] } b.Run(scenario, func(b *testing.B) { b.Run("set-many", func(b *testing.B) { b.ReportAllocs() b.ResetTimer() for i := 0; i < b.N; i++ { Do(context.Background(), Labels(labels...), func(context.Context) {}) } }) b.Run("merge-many", func(b *testing.B) { ctx := WithLabels(context.Background(), Labels(labels[:len(labels)/2]...)) b.ResetTimer() b.ReportAllocs() for i := 0; i < b.N; i++ { Do(ctx, Labels(labels[len(labels)/2:]...), func(context.Context) {}) } }) b.Run("overwrite-many", func(b *testing.B) { ctx := WithLabels(context.Background(), Labels(labels...)) b.ReportAllocs() b.ResetTimer() for i := 0; i < b.N; i++ { Do(ctx, Labels(labels...), func(context.Context) {}) } }) }) } // TODO: hit slow path in Labels }