1package cortexpb
2
3import (
4	"encoding/json"
5	stdlibjson "encoding/json"
6	"math"
7	"testing"
8	"unsafe"
9
10	jsoniter "github.com/json-iterator/go"
11	"github.com/prometheus/prometheus/pkg/labels"
12	"github.com/prometheus/prometheus/pkg/textparse"
13	"github.com/stretchr/testify/assert"
14	"github.com/stretchr/testify/require"
15	"github.com/thanos-io/thanos/pkg/testutil"
16)
17
18// This test verifies that jsoninter uses our custom method for marshalling.
19// We do that by using "test sample" recognized by marshal function when in testing mode.
20func TestJsoniterMarshalForSample(t *testing.T) {
21	testMarshalling(t, jsoniter.Marshal, "test sample")
22}
23
24func TestStdlibJsonMarshalForSample(t *testing.T) {
25	testMarshalling(t, stdlibjson.Marshal, "json: error calling MarshalJSON for type cortexpb.Sample: test sample")
26}
27
28func testMarshalling(t *testing.T, marshalFn func(v interface{}) ([]byte, error), expectedError string) {
29	isTesting = true
30	defer func() { isTesting = false }()
31
32	out, err := marshalFn(Sample{Value: 12345, TimestampMs: 98765})
33	require.NoError(t, err)
34	require.Equal(t, `[98.765,"12345"]`, string(out))
35
36	_, err = marshalFn(Sample{Value: math.NaN(), TimestampMs: 0})
37	require.EqualError(t, err, expectedError)
38
39	// If not testing, we get normal output.
40	isTesting = false
41	out, err = marshalFn(Sample{Value: math.NaN(), TimestampMs: 0})
42	require.NoError(t, err)
43	require.Equal(t, `[0,"NaN"]`, string(out))
44}
45
46// This test verifies that jsoninter uses our custom method for unmarshalling Sample.
47// As with Marshal, we rely on testing mode and special value that reports error.
48func TestJsoniterUnmarshalForSample(t *testing.T) {
49	testUnmarshalling(t, jsoniter.Unmarshal, "test sample")
50}
51
52func TestStdlibJsonUnmarshalForSample(t *testing.T) {
53	testUnmarshalling(t, json.Unmarshal, "test sample")
54}
55
56func testUnmarshalling(t *testing.T, unmarshalFn func(data []byte, v interface{}) error, expectedError string) {
57	isTesting = true
58	defer func() { isTesting = false }()
59
60	sample := Sample{}
61
62	err := unmarshalFn([]byte(`[98.765,"12345"]`), &sample)
63	require.NoError(t, err)
64	require.Equal(t, Sample{Value: 12345, TimestampMs: 98765}, sample)
65
66	err = unmarshalFn([]byte(`[0.0,"NaN"]`), &sample)
67	require.EqualError(t, err, expectedError)
68
69	isTesting = false
70	err = unmarshalFn([]byte(`[0.0,"NaN"]`), &sample)
71	require.NoError(t, err)
72	require.Equal(t, int64(0), sample.TimestampMs)
73	require.True(t, math.IsNaN(sample.Value))
74}
75
76func TestMetricMetadataToMetricTypeToMetricType(t *testing.T) {
77	tc := []struct {
78		desc     string
79		input    MetricMetadata_MetricType
80		expected textparse.MetricType
81	}{
82		{
83			desc:     "with a single-word metric",
84			input:    COUNTER,
85			expected: textparse.MetricTypeCounter,
86		},
87		{
88			desc:     "with a two-word metric",
89			input:    STATESET,
90			expected: textparse.MetricTypeStateset,
91		},
92		{
93			desc:     "with an unknown metric",
94			input:    MetricMetadata_MetricType(100),
95			expected: textparse.MetricTypeUnknown,
96		},
97	}
98
99	for _, tt := range tc {
100		t.Run(tt.desc, func(t *testing.T) {
101			m := MetricMetadataMetricTypeToMetricType(tt.input)
102			testutil.Equals(t, tt.expected, m)
103		})
104	}
105}
106
107func TestFromLabelAdaptersToLabels(t *testing.T) {
108	input := []LabelAdapter{{Name: "hello", Value: "world"}}
109	expected := labels.Labels{labels.Label{Name: "hello", Value: "world"}}
110	actual := FromLabelAdaptersToLabels(input)
111
112	assert.Equal(t, expected, actual)
113
114	// All strings must NOT be copied.
115	assert.Equal(t, uintptr(unsafe.Pointer(&input[0].Name)), uintptr(unsafe.Pointer(&actual[0].Name)))
116	assert.Equal(t, uintptr(unsafe.Pointer(&input[0].Value)), uintptr(unsafe.Pointer(&actual[0].Value)))
117}
118
119func TestFromLabelAdaptersToLabelsWithCopy(t *testing.T) {
120	input := []LabelAdapter{{Name: "hello", Value: "world"}}
121	expected := labels.Labels{labels.Label{Name: "hello", Value: "world"}}
122	actual := FromLabelAdaptersToLabelsWithCopy(input)
123
124	assert.Equal(t, expected, actual)
125
126	// All strings must be copied.
127	assert.NotEqual(t, uintptr(unsafe.Pointer(&input[0].Name)), uintptr(unsafe.Pointer(&actual[0].Name)))
128	assert.NotEqual(t, uintptr(unsafe.Pointer(&input[0].Value)), uintptr(unsafe.Pointer(&actual[0].Value)))
129}
130
131func BenchmarkFromLabelAdaptersToLabelsWithCopy(b *testing.B) {
132	input := []LabelAdapter{
133		{Name: "hello", Value: "world"},
134		{Name: "some label", Value: "and its value"},
135		{Name: "long long long long long label name", Value: "perhaps even longer label value, but who's counting anyway?"}}
136
137	for i := 0; i < b.N; i++ {
138		FromLabelAdaptersToLabelsWithCopy(input)
139	}
140}
141