| // 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{ | |
| Labels("foo", "bar"), | |
| }, | |
| expected: `{"foo":"bar"}`, | |
| }, { | |
| m: labelMap{ | |
| Labels( | |
| "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 | |
| } |