package xpath

import (
	"math"
	"os"
	"path/filepath"
	"strings"
	"testing"
	"time"

	"github.com/google/go-cmp/cmp"
	"github.com/influxdata/toml"
	"github.com/stretchr/testify/require"

	"github.com/influxdata/telegraf"
	"github.com/influxdata/telegraf/config"
	"github.com/influxdata/telegraf/metric"
	"github.com/influxdata/telegraf/plugins/inputs"
	"github.com/influxdata/telegraf/plugins/inputs/file"
	"github.com/influxdata/telegraf/plugins/parsers/influx"
	"github.com/influxdata/telegraf/testutil"
)

const invalidXML = `
<?xml version="1.0"?>
	<Device_1>This one has to fail due to missing end-tag
`

const singleMetricValuesXML = `
<?xml version="1.0"?>
	<Device_1>
		<Name>Device TestDevice1</Name>
		<State>ok</State>
		<Timestamp_unix>1577923199</Timestamp_unix>
		<Timestamp_unix_ms>1577923199128</Timestamp_unix_ms>
		<Timestamp_unix_us>1577923199128256</Timestamp_unix_us>
		<Timestamp_unix_ns>1577923199128256512</Timestamp_unix_ns>
		<Timestamp_iso>2020-01-01T23:59:59Z</Timestamp_iso>
		<value_int>98247</value_int>
		<value_float>98695.81</value_float>
		<value_bool>true</value_bool>
		<value_string>this is a test</value_string>
		<value_position>42;23</value_position>
	</Device_1>
`
const singleMetricAttributesXML = `
<?xml version="1.0"?>
	<Device_1>
		<Name value="Device TestDevice1"/>
		<State _="ok"/>
		<Timestamp_unix value="1577923199"/>
		<Timestamp_iso value="2020-01-01T23:59:59Z"/>
		<attr_int _="12345"/>
		<attr_float _="12345.678"/>
		<attr_bool _="true"/>
		<attr_bool_numeric _="1"/>
		<attr_string _="this is a test"/>
	</Device_1>
`
const singleMetricMultiValuesXML = `
<?xml version="1.0"?>
	<Timestamp value="1577923199"/>
	<Device>
		<Value>1</Value>
		<Value>2</Value>
		<Value>3</Value>
		<Value>4</Value>
		<Value>5</Value>
		<Value>6</Value>
	</Device>
`
const multipleNodesXML = `
<?xml version="1.0"?>
	<Timestamp value="1577923199"/>
	<Device name="Device 1">
		<Value mode="0">42.0</Value>
		<Active>1</Active>
		<State>ok</State>
	</Device>
	<Device name="Device 2">
		<Value mode="1">42.1</Value>
		<Active>0</Active>
		<State>ok</State>
	</Device>
	<Device name="Device 3">
		<Value mode="2">42.2</Value>
		<Active>1</Active>
		<State>ok</State>
	</Device>
	<Device name="Device 4">
		<Value mode="3">42.3</Value>
		<Active>0</Active>
		<State>failed</State>
	</Device>
	<Device name="Device 5">
		<Value mode="4">42.4</Value>
		<Active>1</Active>
		<State>failed</State>
	</Device>
`

const metricNameQueryXML = `
<?xml version="1.0"?>
	<Device_1>
		<Timestamp_unix>1577923199</Timestamp_unix>
		<Metric state="ok"/>
	</Device_1>
`

func TestParseInvalidXML(t *testing.T) {
	var tests = []struct {
		name          string
		input         string
		configs       []Config
		defaultTags   map[string]string
		expectedError string
	}{
		{
			name:  "invalid XML (missing close tag)",
			input: invalidXML,
			configs: []Config{
				{
					MetricQuery: "test",
					Timestamp:   "/Device_1/Timestamp_unix",
				},
			},
			defaultTags:   map[string]string{},
			expectedError: "XML syntax error on line 4: unexpected EOF",
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			parser := &Parser{
				DefaultMetricName: "xml",
				Configs:           tt.configs,
				DefaultTags:       tt.defaultTags,
				Log:               testutil.Logger{Name: "parsers.xml"},
			}
			require.NoError(t, parser.Init())

			_, err := parser.ParseLine(tt.input)
			require.Error(t, err)
			require.Equal(t, tt.expectedError, err.Error())
		})
	}
}

func TestInvalidTypeQueriesFail(t *testing.T) {
	var tests = []struct {
		name          string
		input         string
		configs       []Config
		defaultTags   map[string]string
		expectedError string
	}{
		{
			name:  "invalid field (int) type",
			input: singleMetricValuesXML,
			configs: []Config{
				{
					Timestamp: "/Device_1/Timestamp_unix",
					FieldsInt: map[string]string{
						"a": "/Device_1/value_string",
					},
				},
			},
			defaultTags:   map[string]string{},
			expectedError: `failed to parse field (int) "a": strconv.ParseInt: parsing "this is a test": invalid syntax`,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			parser := &Parser{
				DefaultMetricName: "xml",
				Configs:           tt.configs,
				DefaultTags:       tt.defaultTags,
				Log:               testutil.Logger{Name: "parsers.xml"},
			}
			require.NoError(t, parser.Init())

			_, err := parser.ParseLine(tt.input)
			require.Error(t, err)
			require.Equal(t, tt.expectedError, err.Error())
		})
	}
}

func TestInvalidTypeQueries(t *testing.T) {
	var tests = []struct {
		name        string
		input       string
		configs     []Config
		defaultTags map[string]string
		expected    telegraf.Metric
	}{
		{
			name:  "invalid field type (number)",
			input: singleMetricValuesXML,
			configs: []Config{
				{
					Timestamp: "/Device_1/Timestamp_unix",
					Fields: map[string]string{
						"a": "number(/Device_1/value_string)",
					},
				},
			},
			defaultTags: map[string]string{},
			expected: testutil.MustMetric(
				"test",
				map[string]string{},
				map[string]interface{}{
					"a": math.NaN(),
				},
				time.Unix(1577923199, 0),
			),
		},
		{
			name:  "invalid field type (boolean)",
			input: singleMetricValuesXML,
			configs: []Config{
				{
					Timestamp: "/Device_1/Timestamp_unix",
					Fields: map[string]string{
						"a": "boolean(/Device_1/value_string)",
					},
				},
			},
			defaultTags: map[string]string{},
			expected: testutil.MustMetric(
				"test",
				map[string]string{},
				map[string]interface{}{
					"a": true,
				},
				time.Unix(1577923199, 0),
			),
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			parser := &Parser{
				DefaultMetricName: "test",
				Configs:           tt.configs,
				DefaultTags:       tt.defaultTags,
				Log:               testutil.Logger{Name: "parsers.xml"},
			}
			require.NoError(t, parser.Init())

			actual, err := parser.ParseLine(tt.input)
			require.NoError(t, err)

			testutil.RequireMetricEqual(t, tt.expected, actual)
		})
	}
}

func TestParseTimestamps(t *testing.T) {
	var tests = []struct {
		name        string
		input       string
		configs     []Config
		defaultTags map[string]string
		expected    telegraf.Metric
	}{
		{
			name:  "parse timestamp (no fmt)",
			input: singleMetricValuesXML,
			configs: []Config{
				{
					Timestamp: "/Device_1/Timestamp_unix",
				},
			},
			defaultTags: map[string]string{},
			expected: testutil.MustMetric(
				"test",
				map[string]string{},
				map[string]interface{}{},
				time.Unix(1577923199, 0),
			),
		},
		{
			name:  "parse timestamp (unix)",
			input: singleMetricValuesXML,
			configs: []Config{
				{
					Timestamp:    "/Device_1/Timestamp_unix",
					TimestampFmt: "unix",
				},
			},
			defaultTags: map[string]string{},
			expected: testutil.MustMetric(
				"test",
				map[string]string{},
				map[string]interface{}{},
				time.Unix(1577923199, 0),
			),
		},
		{
			name:  "parse timestamp (unix_ms)",
			input: singleMetricValuesXML,
			configs: []Config{
				{
					Timestamp:    "/Device_1/Timestamp_unix_ms",
					TimestampFmt: "unix_ms",
				},
			},
			defaultTags: map[string]string{},
			expected: testutil.MustMetric(
				"test",
				map[string]string{},
				map[string]interface{}{},
				time.Unix(0, int64(1577923199128*1e6)),
			),
		},
		{
			name:  "parse timestamp (unix_us)",
			input: singleMetricValuesXML,
			configs: []Config{
				{
					Timestamp:    "/Device_1/Timestamp_unix_us",
					TimestampFmt: "unix_us",
				},
			},
			defaultTags: map[string]string{},
			expected: testutil.MustMetric(
				"test",
				map[string]string{},
				map[string]interface{}{},
				time.Unix(0, int64(1577923199128256*1e3)),
			),
		},
		{
			name:  "parse timestamp (unix_us)",
			input: singleMetricValuesXML,
			configs: []Config{
				{
					Timestamp:    "/Device_1/Timestamp_unix_ns",
					TimestampFmt: "unix_ns",
				},
			},
			defaultTags: map[string]string{},
			expected: testutil.MustMetric(
				"test",
				map[string]string{},
				map[string]interface{}{},
				time.Unix(0, int64(1577923199128256512)),
			),
		},
		{
			name:  "parse timestamp (RFC3339)",
			input: singleMetricValuesXML,
			configs: []Config{
				{
					Timestamp:    "/Device_1/Timestamp_iso",
					TimestampFmt: "2006-01-02T15:04:05Z",
				},
			},
			defaultTags: map[string]string{},
			expected: testutil.MustMetric(
				"test",
				map[string]string{},
				map[string]interface{}{},
				time.Unix(1577923199, 0),
			),
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			parser := &Parser{
				DefaultMetricName: "test",
				Configs:           tt.configs,
				DefaultTags:       tt.defaultTags,
				Log:               testutil.Logger{Name: "parsers.xml"},
			}
			require.NoError(t, parser.Init())

			actual, err := parser.ParseLine(tt.input)
			require.NoError(t, err)

			testutil.RequireMetricEqual(t, tt.expected, actual)
		})
	}
}

func TestParseSingleValues(t *testing.T) {
	var tests = []struct {
		name        string
		input       string
		configs     []Config
		defaultTags map[string]string
		expected    telegraf.Metric
	}{
		{
			name:  "parse scalar values as string fields",
			input: singleMetricValuesXML,
			configs: []Config{
				{
					Timestamp: "/Device_1/Timestamp_unix",
					Fields: map[string]string{
						"a": "/Device_1/value_int",
						"b": "/Device_1/value_float",
						"c": "/Device_1/value_bool",
						"d": "/Device_1/value_string",
					},
				},
			},
			defaultTags: map[string]string{},
			expected: testutil.MustMetric(
				"test",
				map[string]string{},
				map[string]interface{}{
					"a": "98247",
					"b": "98695.81",
					"c": "true",
					"d": "this is a test",
				},
				time.Unix(1577923199, 0),
			),
		},
		{
			name:  "parse scalar values as typed fields (w/o int)",
			input: singleMetricValuesXML,
			configs: []Config{
				{
					Timestamp: "/Device_1/Timestamp_unix",
					Fields: map[string]string{
						"a": "number(Device_1/value_int)",
						"b": "number(/Device_1/value_float)",
						"c": "boolean(/Device_1/value_bool)",
						"d": "/Device_1/value_string",
					},
				},
			},
			defaultTags: map[string]string{},
			expected: testutil.MustMetric(
				"test",
				map[string]string{},
				map[string]interface{}{
					"a": 98247.0,
					"b": 98695.81,
					"c": true,
					"d": "this is a test",
				},
				time.Unix(1577923199, 0),
			),
		},
		{
			name:  "parse values as typed fields (w/ int)",
			input: singleMetricValuesXML,
			configs: []Config{
				{
					Timestamp: "/Device_1/Timestamp_unix",
					Fields: map[string]string{
						"b": "number(/Device_1/value_float)",
						"c": "boolean(/Device_1/value_bool)",
						"d": "/Device_1/value_string",
					},
					FieldsInt: map[string]string{
						"a": "/Device_1/value_int",
					},
				},
			},
			defaultTags: map[string]string{},
			expected: testutil.MustMetric(
				"test",
				map[string]string{},
				map[string]interface{}{
					"a": 98247,
					"b": 98695.81,
					"c": true,
					"d": "this is a test",
				},
				time.Unix(1577923199, 0),
			),
		},
		{
			name:  "parse substring values",
			input: singleMetricValuesXML,
			configs: []Config{
				{
					Timestamp: "/Device_1/Timestamp_unix",
					Fields: map[string]string{
						"x": "substring-before(/Device_1/value_position, ';')",
						"y": "substring-after(/Device_1/value_position, ';')",
					},
				},
			},
			defaultTags: map[string]string{},
			expected: testutil.MustMetric(
				"test",
				map[string]string{},
				map[string]interface{}{
					"x": "42",
					"y": "23",
				},
				time.Unix(1577923199, 0),
			),
		},
		{
			name:  "parse substring values (typed)",
			input: singleMetricValuesXML,
			configs: []Config{
				{
					Timestamp: "/Device_1/Timestamp_unix",
					Fields: map[string]string{
						"x": "number(substring-before(/Device_1/value_position, ';'))",
						"y": "number(substring-after(/Device_1/value_position, ';'))",
					},
				},
			},
			defaultTags: map[string]string{},
			expected: testutil.MustMetric(
				"test",
				map[string]string{},
				map[string]interface{}{
					"x": 42.0,
					"y": 23.0,
				},
				time.Unix(1577923199, 0),
			),
		},
		{
			name:  "parse substring values (typed int)",
			input: singleMetricValuesXML,
			configs: []Config{
				{
					Timestamp: "/Device_1/Timestamp_unix",
					FieldsInt: map[string]string{
						"x": "substring-before(/Device_1/value_position, ';')",
						"y": "substring-after(/Device_1/value_position, ';')",
					},
				},
			},
			defaultTags: map[string]string{},
			expected: testutil.MustMetric(
				"test",
				map[string]string{},
				map[string]interface{}{
					"x": 42,
					"y": 23,
				},
				time.Unix(1577923199, 0),
			),
		},
		{
			name:  "parse tags",
			input: singleMetricValuesXML,
			configs: []Config{
				{
					Timestamp: "/Device_1/Timestamp_unix",
					Tags: map[string]string{
						"state": "/Device_1/State",
						"name":  "substring-after(/Device_1/Name, ' ')",
					},
				},
			},
			defaultTags: map[string]string{},
			expected: testutil.MustMetric(
				"test",
				map[string]string{
					"state": "ok",
					"name":  "TestDevice1",
				},
				map[string]interface{}{},
				time.Unix(1577923199, 0),
			),
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			parser := &Parser{
				DefaultMetricName: "test",
				Configs:           tt.configs,
				DefaultTags:       tt.defaultTags,
				Log:               testutil.Logger{Name: "parsers.xml"},
			}
			require.NoError(t, parser.Init())

			actual, err := parser.ParseLine(tt.input)
			require.NoError(t, err)

			testutil.RequireMetricEqual(t, tt.expected, actual)
		})
	}
}

func TestParseSingleAttributes(t *testing.T) {
	var tests = []struct {
		name        string
		input       string
		configs     []Config
		defaultTags map[string]string
		expected    telegraf.Metric
	}{
		{
			name:  "parse attr timestamp (unix)",
			input: singleMetricAttributesXML,
			configs: []Config{
				{
					Timestamp: "/Device_1/Timestamp_unix/@value",
				},
			},
			defaultTags: map[string]string{},
			expected: testutil.MustMetric(
				"test",
				map[string]string{},
				map[string]interface{}{},
				time.Unix(1577923199, 0),
			),
		},
		{
			name:  "parse attr timestamp (RFC3339)",
			input: singleMetricAttributesXML,
			configs: []Config{
				{
					Timestamp:    "/Device_1/Timestamp_iso/@value",
					TimestampFmt: "2006-01-02T15:04:05Z",
				},
			},
			defaultTags: map[string]string{},
			expected: testutil.MustMetric(
				"test",
				map[string]string{},
				map[string]interface{}{},
				time.Unix(1577923199, 0),
			),
		},
		{
			name:  "parse attr as string fields",
			input: singleMetricAttributesXML,
			configs: []Config{
				{
					Timestamp: "/Device_1/Timestamp_unix/@value",
					Fields: map[string]string{
						"a": "/Device_1/attr_int/@_",
						"b": "/Device_1/attr_float/@_",
						"c": "/Device_1/attr_bool/@_",
						"d": "/Device_1/attr_string/@_",
					},
				},
			},
			defaultTags: map[string]string{},
			expected: testutil.MustMetric(
				"test",
				map[string]string{},
				map[string]interface{}{
					"a": "12345",
					"b": "12345.678",
					"c": "true",
					"d": "this is a test",
				},
				time.Unix(1577923199, 0),
			),
		},
		{
			name:  "parse attr as typed fields (w/o int)",
			input: singleMetricAttributesXML,
			configs: []Config{
				{
					Timestamp: "/Device_1/Timestamp_unix/@value",
					Fields: map[string]string{
						"a": "number(/Device_1/attr_int/@_)",
						"b": "number(/Device_1/attr_float/@_)",
						"c": "boolean(/Device_1/attr_bool/@_)",
						"d": "/Device_1/attr_string/@_",
					},
				},
			},
			defaultTags: map[string]string{},
			expected: testutil.MustMetric(
				"test",
				map[string]string{},
				map[string]interface{}{
					"a": 12345.0,
					"b": 12345.678,
					"c": true,
					"d": "this is a test",
				},
				time.Unix(1577923199, 0),
			),
		},
		{
			name:  "parse attr as typed fields (w/ int)",
			input: singleMetricAttributesXML,
			configs: []Config{
				{
					Timestamp: "/Device_1/Timestamp_unix/@value",
					Fields: map[string]string{
						"b": "number(/Device_1/attr_float/@_)",
						"c": "boolean(/Device_1/attr_bool/@_)",
						"d": "/Device_1/attr_string/@_",
					},
					FieldsInt: map[string]string{
						"a": "/Device_1/attr_int/@_",
					},
				},
			},
			defaultTags: map[string]string{},
			expected: testutil.MustMetric(
				"test",
				map[string]string{},
				map[string]interface{}{
					"a": 12345,
					"b": 12345.678,
					"c": true,
					"d": "this is a test",
				},
				time.Unix(1577923199, 0),
			),
		},
		{
			name:  "parse attr substring",
			input: singleMetricAttributesXML,
			configs: []Config{
				{
					Timestamp: "/Device_1/Timestamp_unix/@value",
					Fields: map[string]string{
						"name": "substring-after(/Device_1/Name/@value, ' ')",
					},
				},
			},
			defaultTags: map[string]string{},
			expected: testutil.MustMetric(
				"test",
				map[string]string{},
				map[string]interface{}{
					"name": "TestDevice1",
				},
				time.Unix(1577923199, 0),
			),
		},
		{
			name:  "parse attr tags",
			input: singleMetricAttributesXML,
			configs: []Config{
				{
					Timestamp: "/Device_1/Timestamp_unix/@value",
					Tags: map[string]string{
						"state": "/Device_1/State/@_",
						"name":  "substring-after(/Device_1/Name/@value, ' ')",
					},
				},
			},
			defaultTags: map[string]string{},
			expected: testutil.MustMetric(
				"test",
				map[string]string{
					"state": "ok",
					"name":  "TestDevice1",
				},
				map[string]interface{}{},
				time.Unix(1577923199, 0),
			),
		},
		{
			name:  "parse attr bool",
			input: singleMetricAttributesXML,
			configs: []Config{
				{
					Timestamp: "/Device_1/Timestamp_unix/@value",
					Fields: map[string]string{
						"a": "/Device_1/attr_bool_numeric/@_ = 1",
					},
				},
			},
			defaultTags: map[string]string{},
			expected: testutil.MustMetric(
				"test",
				map[string]string{},
				map[string]interface{}{
					"a": true,
				},
				time.Unix(1577923199, 0),
			),
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			parser := &Parser{
				DefaultMetricName: "test",
				Configs:           tt.configs,
				DefaultTags:       tt.defaultTags,
				Log:               testutil.Logger{Name: "parsers.xml"},
			}
			require.NoError(t, parser.Init())

			actual, err := parser.ParseLine(tt.input)
			require.NoError(t, err)

			testutil.RequireMetricEqual(t, tt.expected, actual)
		})
	}
}

func TestParseMultiValues(t *testing.T) {
	var tests = []struct {
		name        string
		input       string
		configs     []Config
		defaultTags map[string]string
		expected    telegraf.Metric
	}{
		{
			name:  "select values (float)",
			input: singleMetricMultiValuesXML,
			configs: []Config{
				{
					Timestamp: "/Timestamp/@value",
					Fields: map[string]string{
						"a": "number(/Device/Value[1])",
						"b": "number(/Device/Value[2])",
						"c": "number(/Device/Value[3])",
						"d": "number(/Device/Value[4])",
						"e": "number(/Device/Value[5])",
						"f": "number(/Device/Value[6])",
					},
				},
			},
			defaultTags: map[string]string{},
			expected: testutil.MustMetric(
				"test",
				map[string]string{},
				map[string]interface{}{
					"a": 1.0,
					"b": 2.0,
					"c": 3.0,
					"d": 4.0,
					"e": 5.0,
					"f": 6.0,
				},
				time.Unix(1577923199, 0),
			),
		},
		{
			name:  "select values (int)",
			input: singleMetricMultiValuesXML,
			configs: []Config{
				{
					Timestamp: "/Timestamp/@value",
					FieldsInt: map[string]string{
						"a": "/Device/Value[1]",
						"b": "/Device/Value[2]",
						"c": "/Device/Value[3]",
						"d": "/Device/Value[4]",
						"e": "/Device/Value[5]",
						"f": "/Device/Value[6]",
					},
				},
			},
			defaultTags: map[string]string{},
			expected: testutil.MustMetric(
				"test",
				map[string]string{},
				map[string]interface{}{
					"a": 1,
					"b": 2,
					"c": 3,
					"d": 4,
					"e": 5,
					"f": 6,
				},
				time.Unix(1577923199, 0),
			),
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			parser := &Parser{
				DefaultMetricName: "test",
				Configs:           tt.configs,
				DefaultTags:       tt.defaultTags,
				Log:               testutil.Logger{Name: "parsers.xml"},
			}
			require.NoError(t, parser.Init())

			actual, err := parser.ParseLine(tt.input)
			require.NoError(t, err)

			testutil.RequireMetricEqual(t, tt.expected, actual)
		})
	}
}

func TestParseMultiNodes(t *testing.T) {
	var tests = []struct {
		name        string
		input       string
		configs     []Config
		defaultTags map[string]string
		expected    []telegraf.Metric
	}{
		{
			name:  "select all devices",
			input: multipleNodesXML,
			configs: []Config{
				{
					Selection: "/Device",
					Timestamp: "/Timestamp/@value",
					Fields: map[string]string{
						"value":  "number(Value)",
						"active": "Active = 1",
					},
					FieldsInt: map[string]string{
						"mode": "Value/@mode",
					},
					Tags: map[string]string{
						"name":  "@name",
						"state": "State",
					},
				},
			},
			defaultTags: map[string]string{},
			expected: []telegraf.Metric{
				testutil.MustMetric(
					"test",
					map[string]string{
						"name":  "Device 1",
						"state": "ok",
					},
					map[string]interface{}{
						"value":  42.0,
						"active": true,
						"mode":   0,
					},
					time.Unix(1577923199, 0),
				),
				testutil.MustMetric(
					"test",
					map[string]string{
						"name":  "Device 2",
						"state": "ok",
					},
					map[string]interface{}{
						"value":  42.1,
						"active": false,
						"mode":   1,
					},
					time.Unix(1577923199, 0),
				),
				testutil.MustMetric(
					"test",
					map[string]string{
						"name":  "Device 3",
						"state": "ok",
					},
					map[string]interface{}{
						"value":  42.2,
						"active": true,
						"mode":   2,
					},
					time.Unix(1577923199, 0),
				),
				testutil.MustMetric(
					"test",
					map[string]string{
						"name":  "Device 4",
						"state": "failed",
					},
					map[string]interface{}{
						"value":  42.3,
						"active": false,
						"mode":   3,
					},
					time.Unix(1577923199, 0),
				),
				testutil.MustMetric(
					"test",
					map[string]string{
						"name":  "Device 5",
						"state": "failed",
					},
					map[string]interface{}{
						"value":  42.4,
						"active": true,
						"mode":   4,
					},
					time.Unix(1577923199, 0),
				),
			},
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			parser := &Parser{
				DefaultMetricName: "test",
				Configs:           tt.configs,
				DefaultTags:       tt.defaultTags,
				Log:               testutil.Logger{Name: "parsers.xml"},
			}
			require.NoError(t, parser.Init())

			actual, err := parser.Parse([]byte(tt.input))
			require.NoError(t, err)

			testutil.RequireMetricsEqual(t, tt.expected, actual)
		})
	}
}

func TestParseMetricQuery(t *testing.T) {
	var tests = []struct {
		name        string
		input       string
		configs     []Config
		defaultTags map[string]string
		expected    telegraf.Metric
	}{
		{
			name:  "parse metric name query",
			input: metricNameQueryXML,
			configs: []Config{
				{
					MetricQuery: "name(/Device_1/Metric/@*[1])",
					Timestamp:   "/Device_1/Timestamp_unix",
					Fields: map[string]string{
						"value": "/Device_1/Metric/@*[1]",
					},
				},
			},
			defaultTags: map[string]string{},
			expected: testutil.MustMetric(
				"state",
				map[string]string{},
				map[string]interface{}{
					"value": "ok",
				},
				time.Unix(1577923199, 0),
			),
		},
		{
			name:  "parse metric name constant",
			input: metricNameQueryXML,
			configs: []Config{
				{
					MetricQuery: "'the_metric'",
					Timestamp:   "/Device_1/Timestamp_unix",
					Fields: map[string]string{
						"value": "/Device_1/Metric/@*[1]",
					},
				},
			},
			defaultTags: map[string]string{},
			expected: testutil.MustMetric(
				"the_metric",
				map[string]string{},
				map[string]interface{}{
					"value": "ok",
				},
				time.Unix(1577923199, 0),
			),
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			parser := &Parser{
				DefaultMetricName: "test",
				Configs:           tt.configs,
				DefaultTags:       tt.defaultTags,
				Log:               testutil.Logger{Name: "parsers.xml"},
			}
			require.NoError(t, parser.Init())

			actual, err := parser.ParseLine(tt.input)
			require.NoError(t, err)

			testutil.RequireMetricEqual(t, tt.expected, actual)
		})
	}
}

func TestParseErrors(t *testing.T) {
	var tests = []struct {
		name     string
		input    string
		configs  []Config
		expected string
	}{
		{
			name:  "string metric name query",
			input: metricNameQueryXML,
			configs: []Config{
				{
					MetricQuery: "arbitrary",
					Timestamp:   "/Device_1/Timestamp_unix",
					Fields: map[string]string{
						"value": "/Device_1/Metric/@*[1]",
					},
				},
			},
			expected: "failed to query metric name: query result is of type <nil> not 'string'",
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			parser := &Parser{
				DefaultMetricName: "test",
				Configs:           tt.configs,
				DefaultTags:       map[string]string{},
				Log:               testutil.Logger{Name: "parsers.xml"},
			}
			require.NoError(t, parser.Init())

			_, err := parser.ParseLine(tt.input)
			require.Error(t, err)
			require.Equal(t, tt.expected, err.Error())
		})
	}
}

func TestEmptySelection(t *testing.T) {
	var tests = []struct {
		name    string
		input   string
		configs []Config
	}{
		{
			name:  "empty path",
			input: multipleNodesXML,
			configs: []Config{
				{
					Selection: "/Device/NonExisting",
					Fields:    map[string]string{"value": "number(Value)"},
					FieldsInt: map[string]string{"mode": "Value/@mode"},
					Tags:      map[string]string{},
				},
			},
		},
		{
			name:  "empty pattern",
			input: multipleNodesXML,
			configs: []Config{
				{
					Selection: "//NonExisting",
					Fields:    map[string]string{"value": "number(Value)"},
					FieldsInt: map[string]string{"mode": "Value/@mode"},
					Tags:      map[string]string{},
				},
			},
		},
		{
			name:  "empty axis",
			input: multipleNodesXML,
			configs: []Config{
				{
					Selection: "/Device/child::NonExisting",
					Fields:    map[string]string{"value": "number(Value)"},
					FieldsInt: map[string]string{"mode": "Value/@mode"},
					Tags:      map[string]string{},
				},
			},
		},
		{
			name:  "empty predicate",
			input: multipleNodesXML,
			configs: []Config{
				{
					Selection: "/Device[@NonExisting=true]",
					Fields:    map[string]string{"value": "number(Value)"},
					FieldsInt: map[string]string{"mode": "Value/@mode"},
					Tags:      map[string]string{},
				},
			},
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			parser := &Parser{
				DefaultMetricName: "test",
				Configs:           tt.configs,
				DefaultTags:       map[string]string{},
				Log:               testutil.Logger{Name: "parsers.xml"},
			}
			require.NoError(t, parser.Init())

			_, err := parser.Parse([]byte(tt.input))
			require.Error(t, err)
			require.Equal(t, "cannot parse with empty selection node", err.Error())
		})
	}
}

func TestEmptySelectionAllowed(t *testing.T) {
	var tests = []struct {
		name    string
		input   string
		configs []Config
	}{
		{
			name:  "empty path",
			input: multipleNodesXML,
			configs: []Config{
				{
					Selection: "/Device/NonExisting",
					Fields:    map[string]string{"value": "number(Value)"},
					FieldsInt: map[string]string{"mode": "Value/@mode"},
					Tags:      map[string]string{},
				},
			},
		},
		{
			name:  "empty pattern",
			input: multipleNodesXML,
			configs: []Config{
				{
					Selection: "//NonExisting",
					Fields:    map[string]string{"value": "number(Value)"},
					FieldsInt: map[string]string{"mode": "Value/@mode"},
					Tags:      map[string]string{},
				},
			},
		},
		{
			name:  "empty axis",
			input: multipleNodesXML,
			configs: []Config{
				{
					Selection: "/Device/child::NonExisting",
					Fields:    map[string]string{"value": "number(Value)"},
					FieldsInt: map[string]string{"mode": "Value/@mode"},
					Tags:      map[string]string{},
				},
			},
		},
		{
			name:  "empty predicate",
			input: multipleNodesXML,
			configs: []Config{
				{
					Selection: "/Device[@NonExisting=true]",
					Fields:    map[string]string{"value": "number(Value)"},
					FieldsInt: map[string]string{"mode": "Value/@mode"},
					Tags:      map[string]string{},
				},
			},
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			parser := &Parser{
				DefaultMetricName:   "xml",
				Configs:             tt.configs,
				AllowEmptySelection: true,
				DefaultTags:         map[string]string{},
				Log:                 testutil.Logger{Name: "parsers.xml"},
			}
			require.NoError(t, parser.Init())

			_, err := parser.Parse([]byte(tt.input))
			require.NoError(t, err)
		})
	}
}

func TestTestCases(t *testing.T) {
	var tests = []struct {
		name     string
		filename string
	}{
		{
			name:     "explicit basic",
			filename: "testcases/multisensor_explicit_basic.conf",
		},
		{
			name:     "explicit batch",
			filename: "testcases/multisensor_explicit_batch.conf",
		},
		{
			name:     "field selection batch",
			filename: "testcases/multisensor_selection_batch.conf",
		},
		{
			name:     "earthquakes quakeml",
			filename: "testcases/earthquakes.conf",
		},
		{
			name:     "openweathermap forecast (xml)",
			filename: "testcases/openweathermap_xml.conf",
		},
		{
			name:     "openweathermap forecast (json)",
			filename: "testcases/openweathermap_json.conf",
		},
		{
			name:     "addressbook tutorial (protobuf)",
			filename: "testcases/addressbook.conf",
		},
		{
			name:     "message-pack",
			filename: "testcases/tracker_msgpack.conf",
		},
		{
			name:     "field and tag batch (json)",
			filename: "testcases/field_tag_batch.conf",
		},
	}

	parser := &influx.Parser{}
	require.NoError(t, parser.Init())

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			filename := filepath.FromSlash(tt.filename)
			cfg, header, err := loadTestConfiguration(filename)
			require.NoError(t, err)

			// Load the xml-content
			input, err := testutil.ParseRawLinesFrom(header, "File:")
			require.NoError(t, err)
			require.Len(t, input, 1)

			filefields := strings.Fields(input[0])
			require.NotEmpty(t, filefields)
			datafile := filepath.FromSlash(filefields[0])
			fileformat := ""
			if len(filefields) > 1 {
				fileformat = filefields[1]
			}

			// Load the protocol buffer information if required
			var pbmsgdef, pbmsgtype string
			if fileformat == "xpath_protobuf" {
				input, err := testutil.ParseRawLinesFrom(header, "Protobuf:")
				require.NoError(t, err)
				require.Len(t, input, 1)

				protofields := strings.Fields(input[0])
				require.Len(t, protofields, 2)
				pbmsgdef = protofields[0]
				pbmsgtype = protofields[1]
			}

			content, err := os.ReadFile(datafile)
			require.NoError(t, err)

			// Get the expectations
			expectedOutputs, err := testutil.ParseMetricsFrom(header, "Expected Output:", parser)
			require.NoError(t, err)

			expectedErrors, _ := testutil.ParseRawLinesFrom(header, "Expected Error:")

			// Setup the parser and run it.
			metricName := "xml"
			if fileformat != "" {
				metricName = fileformat
			}
			parser := &Parser{
				DefaultMetricName:   metricName,
				Format:              fileformat,
				ProtobufMessageDef:  pbmsgdef,
				ProtobufMessageType: pbmsgtype,
				Configs:             []Config{*cfg},
				Log:                 testutil.Logger{Name: "parsers.xml"},
			}
			require.NoError(t, parser.Init())
			outputs, err := parser.Parse(content)
			if len(expectedErrors) == 0 {
				require.NoError(t, err)
			}

			// If no timestamp is given we cannot test it. So use the one of the output
			if cfg.Timestamp == "" {
				testutil.RequireMetricsEqual(t, expectedOutputs, outputs, testutil.IgnoreTime())
			} else {
				testutil.RequireMetricsEqual(t, expectedOutputs, outputs)
			}
		})
	}
}

func TestProtobufImporting(t *testing.T) {
	// Setup the parser and run it.
	parser := &Parser{
		DefaultMetricName:   "xpath_protobuf",
		Format:              "xpath_protobuf",
		ProtobufMessageDef:  "person.proto",
		ProtobufMessageType: "importtest.Person",
		ProtobufImportPaths: []string{"testcases/protos"},
		Configs:             []Config{},
		Log:                 testutil.Logger{Name: "parsers.protobuf"},
	}
	require.NoError(t, parser.Init())
}

func TestMultipleConfigs(t *testing.T) {
	// Get all directories in testdata
	folders, err := os.ReadDir("testcases")
	require.NoError(t, err)

	// Make sure the folder contains data
	require.NotEmpty(t, folders)

	// Register the wrapper plugin
	inputs.Add("file", func() telegraf.Input {
		return &file.File{}
	})

	for _, f := range folders {
		// Only handle folders
		if !f.IsDir() || f.Name() == "protos" {
			continue
		}
		testcasePath := filepath.Join("testcases", f.Name())
		configFilename := filepath.Join(testcasePath, "telegraf.conf")
		expectedFilename := filepath.Join(testcasePath, "expected.out")
		expectedErrorFilename := filepath.Join(testcasePath, "expected.err")

		t.Run(f.Name(), func(t *testing.T) {
			// Prepare the influx parser for expectations
			parser := &influx.Parser{}
			require.NoError(t, parser.Init())
			parser.SetTimeFunc(func() time.Time { return time.Time{} })

			// Compare options
			options := []cmp.Option{testutil.SortMetrics()}

			// Read the expected output if any
			var expected []telegraf.Metric
			if _, err := os.Stat(expectedFilename); err == nil {
				var err error
				expected, err = testutil.ParseMetricsFromFile(expectedFilename, parser)
				require.NoError(t, err)
			}
			if len(expected) > 0 && expected[0].Time().IsZero() {
				options = append(options, testutil.IgnoreTime())
			}

			// Read the expected output if any
			var expectedErrors []string
			if _, err := os.Stat(expectedErrorFilename); err == nil {
				var err error
				expectedErrors, err = testutil.ParseLinesFromFile(expectedErrorFilename)
				require.NoError(t, err)
				require.NotEmpty(t, expectedErrors)
			}

			// Configure the plugin
			cfg := config.NewConfig()
			require.NoError(t, cfg.LoadConfig(configFilename))
			require.NotEmpty(t, cfg.Inputs)

			// Gather the metrics from the input file configure
			var acc testutil.Accumulator
			var errs []error
			for _, input := range cfg.Inputs {
				require.NoError(t, input.Init())
				err := input.Gather(&acc)
				if err != nil {
					errs = append(errs, err)
				}
			}

			// Check for errors if we expect any
			if len(expectedErrors) > 0 {
				require.Len(t, errs, len(expectedErrors))
				for i, err := range errs {
					require.ErrorContains(t, err, expectedErrors[i])
				}
			} else {
				require.Empty(t, errs)
			}

			// Process expected metrics and compare with resulting metrics
			actual := acc.GetTelegrafMetrics()
			testutil.RequireMetricsEqual(t, expected, actual, options...)
		})
	}
}

func loadTestConfiguration(filename string) (*Config, []string, error) {
	buf, err := os.ReadFile(filename)
	if err != nil {
		return nil, nil, err
	}

	header := make([]string, 0)
	for _, line := range strings.Split(string(buf), "\n") {
		line = strings.TrimSpace(line)
		if strings.HasPrefix(line, "#") {
			header = append(header, line)
		}
	}
	cfg := Config{}
	err = toml.Unmarshal(buf, &cfg)
	return &cfg, header, err
}

var benchmarkExpectedMetrics = []telegraf.Metric{
	metric.New(
		"benchmark",
		map[string]string{
			"tags_host":     "myhost",
			"tags_platform": "python",
			"tags_sdkver":   "3.11.5",
		},
		map[string]interface{}{
			"value": 5.0,
		},
		time.Unix(1577923199, 0),
	),
	metric.New(
		"benchmark",
		map[string]string{
			"tags_host":     "myhost",
			"tags_platform": "python",
			"tags_sdkver":   "3.11.4",
		},
		map[string]interface{}{
			"value": 4.0,
		},
		time.Unix(1577923199, 0),
	),
}

const benchmarkDataXML = `
<?xml version="1.0"?>
	<Timestamp value="1577923199"/>
	<Benchmark>
		<tags_host>myhost</tags_host>
		<tags_sdkver>3.11.5</tags_sdkver>
		<tags_platform>python</tags_platform>
		<value>5</value>
	</Benchmark>
	<Benchmark>
		<tags_host>myhost</tags_host>
		<tags_sdkver>3.11.4</tags_sdkver>
		<tags_platform>python</tags_platform>
		<value>4</value>
	</Benchmark>
`

var benchmarkConfigXML = Config{
	Selection: "/Benchmark",
	Tags: map[string]string{
		"tags_host":     "tags_host",
		"tags_sdkver":   "tags_sdkver",
		"tags_platform": "tags_platform",
	},
	Fields: map[string]string{
		"value": "number(value)",
	},
	Timestamp:    "/Timestamp/@value",
	TimestampFmt: "unix",
}

func TestBenchmarkDataXML(t *testing.T) {
	plugin := &Parser{
		DefaultMetricName: "benchmark",
		Format:            "xml",
		Configs:           []Config{benchmarkConfigXML},
		Log:               testutil.Logger{Name: "parsers.xpath"},
	}
	require.NoError(t, plugin.Init())

	actual, err := plugin.Parse([]byte(benchmarkDataXML))
	require.NoError(t, err)
	testutil.RequireMetricsEqual(t, benchmarkExpectedMetrics, actual)
}

func BenchmarkParsingXML(b *testing.B) {
	plugin := &Parser{
		DefaultMetricName: "benchmark",
		Format:            "xml",
		Configs:           []Config{benchmarkConfigXML},
		Log:               testutil.Logger{Name: "parsers.xpath", Quiet: true},
	}
	require.NoError(b, plugin.Init())

	for n := 0; n < b.N; n++ {
		_, _ = plugin.Parse([]byte(benchmarkDataXML))
	}
}

const benchmarkDataJSON = `
{
	"timestamp": 1577923199,
	"data": [
		{
			"tags_host": "myhost",
			"tags_sdkver": "3.11.5",
			"tags_platform": "python",
			"value": 5.0
		},
		{
			"tags_host": "myhost",
			"tags_sdkver": "3.11.4",
			"tags_platform": "python",
			"value": 4.0
		}
	]
}
`

var benchmarkConfigJSON = Config{
	Selection: "data/*",
	Tags: map[string]string{
		"tags_host":     "tags_host",
		"tags_sdkver":   "tags_sdkver",
		"tags_platform": "tags_platform",
	},
	Fields: map[string]string{
		"value": "number(value)",
	},
	Timestamp:    "//timestamp",
	TimestampFmt: "unix",
}

func TestBenchmarkDataJSON(t *testing.T) {
	plugin := &Parser{
		DefaultMetricName: "benchmark",
		Format:            "xpath_json",
		Configs:           []Config{benchmarkConfigJSON},
		Log:               testutil.Logger{Name: "parsers.xpath"},
	}
	require.NoError(t, plugin.Init())

	actual, err := plugin.Parse([]byte(benchmarkDataJSON))
	require.NoError(t, err)
	testutil.RequireMetricsEqual(t, benchmarkExpectedMetrics, actual)
}

func BenchmarkParsingJSON(b *testing.B) {
	plugin := &Parser{
		DefaultMetricName: "benchmark",
		Format:            "xpath_json",
		Configs:           []Config{benchmarkConfigJSON},
		Log:               testutil.Logger{Name: "parsers.xpath", Quiet: true},
	}
	require.NoError(b, plugin.Init())

	for n := 0; n < b.N; n++ {
		_, _ = plugin.Parse([]byte(benchmarkDataJSON))
	}
}

func BenchmarkParsingProtobuf(b *testing.B) {
	plugin := &Parser{
		DefaultMetricName:   "benchmark",
		Format:              "xpath_protobuf",
		ProtobufMessageDef:  "benchmark.proto",
		ProtobufMessageType: "benchmark.BenchmarkData",
		ProtobufImportPaths: []string{".", "./testcases/protobuf_benchmark"},
		NativeTypes:         true,
		Configs: []Config{
			{
				Selection:    "//data",
				Timestamp:    "timestamp",
				TimestampFmt: "unix_ns",
				Tags: map[string]string{
					"source":        "source",
					"tags_sdkver":   "tags_sdkver",
					"tags_platform": "tags_platform",
				},
				Fields: map[string]string{
					"value": "value",
				},
			},
		},
		Log: testutil.Logger{Name: "parsers.xpath", Quiet: true},
	}
	require.NoError(b, plugin.Init())

	benchmarkData, err := os.ReadFile(filepath.Join("testcases", "protobuf_benchmark", "message.bin"))
	require.NoError(b, err)

	for n := 0; n < b.N; n++ {
		_, _ = plugin.Parse(benchmarkData)
	}
}

var benchmarkDataMsgPack = [][]byte{
	{
		0xdf, 0x00, 0x00, 0x00, 0x05, 0xa9, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0xce,
		0x62, 0x90, 0x98, 0x9d, 0xa5, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x05, 0xa6, 0x73, 0x6f, 0x75, 0x72,
		0x63, 0x65, 0xa6, 0x6d, 0x79, 0x68, 0x6f, 0x73, 0x74, 0xad, 0x74, 0x61, 0x67, 0x73, 0x5f, 0x70,
		0x6c, 0x61, 0x74, 0x66, 0x6f, 0x72, 0x6d, 0xa6, 0x70, 0x79, 0x74, 0x68, 0x6f, 0x6e, 0xab, 0x74,
		0x61, 0x67, 0x73, 0x5f, 0x73, 0x64, 0x6b, 0x76, 0x65, 0x72, 0xa6, 0x33, 0x2e, 0x31, 0x31, 0x2e,
		0x35,
	},
	{
		0x85, 0xA6, 0x73, 0x6F, 0x75, 0x72, 0x63, 0x65, 0xA6, 0x6D, 0x79, 0x68, 0x6F, 0x73, 0x74, 0xAB,
		0x74, 0x61, 0x67, 0x73, 0x5F, 0x73, 0x64, 0x6B, 0x76, 0x65, 0x72, 0xA6, 0x33, 0x2E, 0x31, 0x31,
		0x2E, 0x34, 0xAD, 0x74, 0x61, 0x67, 0x73, 0x5F, 0x70, 0x6C, 0x61, 0x74, 0x66, 0x6F, 0x72, 0x6D,
		0xA6, 0x70, 0x79, 0x74, 0x68, 0x6F, 0x6E, 0xA5, 0x76, 0x61, 0x6C, 0x75, 0x65, 0x04, 0xA9, 0x74,
		0x69, 0x6D, 0x65, 0x73, 0x74, 0x61, 0x6D, 0x70, 0xCE, 0x62, 0x90, 0x98, 0x9D,
	},
}

func TestBenchmarkDataMsgPack(t *testing.T) {
	plugin := &Parser{
		DefaultMetricName: "benchmark",
		Format:            "xpath_msgpack",
		Configs: []Config{
			{
				Tags: map[string]string{
					"source":        "source",
					"tags_sdkver":   "tags_sdkver",
					"tags_platform": "tags_platform",
				},
				Fields: map[string]string{
					"value": "number(value)",
				},
				Timestamp:    "timestamp",
				TimestampFmt: "unix",
			},
		},
		Log: testutil.Logger{Name: "parsers.xpath", Quiet: true},
	}
	require.NoError(t, plugin.Init())

	expected := []telegraf.Metric{
		metric.New(
			"benchmark",
			map[string]string{
				"source":        "myhost",
				"tags_platform": "python",
				"tags_sdkver":   "3.11.5",
			},
			map[string]interface{}{
				"value": 5.0,
			},
			time.Unix(1653643421, 0),
		),
		metric.New(
			"benchmark",
			map[string]string{
				"source":        "myhost",
				"tags_platform": "python",
				"tags_sdkver":   "3.11.4",
			},
			map[string]interface{}{
				"value": 4.0,
			},
			time.Unix(1653643421, 0),
		),
	}

	actual := make([]telegraf.Metric, 0, 2)
	for _, msg := range benchmarkDataMsgPack {
		m, err := plugin.Parse(msg)
		require.NoError(t, err)
		actual = append(actual, m...)
	}
	testutil.RequireMetricsEqual(t, expected, actual, testutil.SortMetrics())
}

func BenchmarkParsingMsgPack(b *testing.B) {
	plugin := &Parser{
		DefaultMetricName: "benchmark",
		Format:            "xpath_msgpack",
		Configs: []Config{
			{
				Tags: map[string]string{
					"source":        "source",
					"tags_sdkver":   "tags_sdkver",
					"tags_platform": "tags_platform",
				},
				Fields: map[string]string{
					"value": "number(value)",
				},
				Timestamp:    "timestamp",
				TimestampFmt: "unix",
			},
		},
		Log: testutil.Logger{Name: "parsers.xpath", Quiet: true},
	}
	require.NoError(b, plugin.Init())

	for n := 0; n < b.N; n++ {
		_, _ = plugin.Parse(benchmarkDataMsgPack[n%2])
	}
}

func BenchmarkParsingCBOR(b *testing.B) {
	plugin := &Parser{
		DefaultMetricName: "benchmark",
		Format:            "xpath_cbor",
		NativeTypes:       true,
		Configs: []Config{
			{
				Selection:    "//data",
				Timestamp:    "timestamp",
				TimestampFmt: "unix_ns",
				Tags: map[string]string{
					"source":        "source",
					"tags_sdkver":   "tags_sdkver",
					"tags_platform": "tags_platform",
				},
				Fields: map[string]string{
					"value": "value",
				},
			},
		},
		Log: testutil.Logger{Name: "parsers.xpath", Quiet: true},
	}
	require.NoError(b, plugin.Init())

	benchmarkData, err := os.ReadFile(filepath.Join("testcases", "cbor_benchmark", "message.bin"))
	require.NoError(b, err)

	for n := 0; n < b.N; n++ {
		_, _ = plugin.Parse(benchmarkData)
	}
}
