//go:build windows

package win_perf_counters

import (
	"errors"
	"fmt"
	"os"
	"testing"
	"time"

	"github.com/stretchr/testify/require"

	"github.com/influxdata/telegraf/config"
	"github.com/influxdata/telegraf/testutil"
)

type testCounter struct {
	handle pdhCounterHandle
	path   string
	value  float64
	status uint32 // allows for tests against specific pdh_error codes, rather than assuming all cases of "value == 0" to indicate error conditions
}

type fakePerformanceQuery struct {
	counters      map[string]testCounter
	vistaAndNewer bool
	expandPaths   map[string][]string
	openCalled    bool
}

var metricTime = time.Date(2018, 5, 28, 12, 0, 0, 0, time.UTC)

func (m *testCounter) toCounterValue(raw bool) *counterValue {
	//nolint:dogsled,errcheck // only instance is needed for this helper function in tests
	_, _, inst, _, _ := extractCounterInfoFromCounterPath(m.path)
	if inst == "" {
		inst = "--"
	}
	var val interface{}
	if raw {
		val = int64(m.value)
	} else {
		val = m.value
	}

	return &counterValue{inst, val}
}

func (m *fakePerformanceQuery) open() error {
	if m.openCalled {
		err := m.close()
		if err != nil {
			return err
		}
	}
	m.openCalled = true
	return nil
}

func (m *fakePerformanceQuery) close() error {
	if !m.openCalled {
		return errors.New("in close: uninitialized query")
	}
	m.openCalled = false
	return nil
}

func (m *fakePerformanceQuery) addCounterToQuery(counterPath string) (pdhCounterHandle, error) {
	if !m.openCalled {
		return 0, errors.New("in addCounterToQuery: uninitialized query")
	}
	if c, ok := m.counters[counterPath]; ok {
		return c.handle, nil
	}
	return 0, fmt.Errorf("in addCounterToQuery: invalid counter path: %q", counterPath)
}

func (m *fakePerformanceQuery) addEnglishCounterToQuery(counterPath string) (pdhCounterHandle, error) {
	if !m.openCalled {
		return 0, errors.New("in addEnglishCounterToQuery: uninitialized query")
	}
	if c, ok := m.counters[counterPath]; ok {
		return c.handle, nil
	}
	return 0, fmt.Errorf("in addEnglishCounterToQuery: invalid counter path: %q", counterPath)
}

func (m *fakePerformanceQuery) getCounterPath(counterHandle pdhCounterHandle) (string, error) {
	for _, counter := range m.counters {
		if counter.handle == counterHandle {
			return counter.path, nil
		}
	}
	return "", fmt.Errorf("in getCounterPath: invalid handle: %v", counterHandle)
}

func (m *fakePerformanceQuery) expandWildCardPath(counterPath string) ([]string, error) {
	if e, ok := m.expandPaths[counterPath]; ok {
		return e, nil
	}
	return nil, fmt.Errorf("in expandWildCardPath: invalid counter path: %q", counterPath)
}

func (m *fakePerformanceQuery) getFormattedCounterValueDouble(counterHandle pdhCounterHandle) (float64, error) {
	if !m.openCalled {
		return 0, errors.New("in getFormattedCounterValueDouble: uninitialized query")
	}
	for _, counter := range m.counters {
		if counter.handle == counterHandle {
			if counter.status > 0 {
				return 0, newPdhError(counter.status)
			}
			return counter.value, nil
		}
	}
	return 0, fmt.Errorf("in getFormattedCounterValueDouble: invalid handle: %v", counterHandle)
}

func (m *fakePerformanceQuery) getRawCounterValue(counterHandle pdhCounterHandle) (int64, error) {
	if !m.openCalled {
		return 0, errors.New("in getRawCounterValue: uninitialised query")
	}
	for _, counter := range m.counters {
		if counter.handle == counterHandle {
			if counter.status > 0 {
				return 0, newPdhError(counter.status)
			}
			return int64(counter.value), nil
		}
	}
	return 0, fmt.Errorf("in getRawCounterValue: invalid handle: %v", counterHandle)
}

func (m *fakePerformanceQuery) findCounterByPath(counterPath string) *testCounter {
	for _, c := range m.counters {
		if c.path == counterPath {
			return &c
		}
	}
	return nil
}

func (m *fakePerformanceQuery) getFormattedCounterArrayDouble(hCounter pdhCounterHandle) ([]counterValue, error) {
	if !m.openCalled {
		return nil, errors.New("in getFormattedCounterArrayDouble: uninitialized query")
	}
	for _, c := range m.counters {
		if c.handle == hCounter {
			if e, ok := m.expandPaths[c.path]; ok {
				counters := make([]counterValue, 0, len(e))
				for _, p := range e {
					counter := m.findCounterByPath(p)
					if counter == nil {
						return nil, fmt.Errorf("in getFormattedCounterArrayDouble: invalid counter: %q", p)
					}
					if counter.status > 0 {
						return nil, newPdhError(counter.status)
					}
					counters = append(counters, *counter.toCounterValue(false))
				}
				return counters, nil
			}
			return nil, fmt.Errorf("in getFormattedCounterArrayDouble: invalid counter: %v", hCounter)
		}
	}
	return nil, fmt.Errorf("in getFormattedCounterArrayDouble: invalid counter: %v, no paths found", hCounter)
}

func (m *fakePerformanceQuery) getRawCounterArray(hCounter pdhCounterHandle) ([]counterValue, error) {
	if !m.openCalled {
		return nil, errors.New("in getRawCounterArray: uninitialised query")
	}
	for _, c := range m.counters {
		if c.handle == hCounter {
			if e, ok := m.expandPaths[c.path]; ok {
				counters := make([]counterValue, 0, len(e))
				for _, p := range e {
					counter := m.findCounterByPath(p)
					if counter == nil {
						return nil, fmt.Errorf("in getRawCounterArray: invalid counter: %q", p)
					}
					if counter.status > 0 {
						return nil, newPdhError(counter.status)
					}
					counters = append(counters, *counter.toCounterValue(true))
				}
				return counters, nil
			}
			return nil, fmt.Errorf("in getRawCounterArray: invalid counter: %v", hCounter)
		}
	}
	return nil, fmt.Errorf("in getRawCounterArray: invalid counter: %v, no paths found", hCounter)
}

func (m *fakePerformanceQuery) collectData() error {
	if !m.openCalled {
		return errors.New("in collectData: uninitialized query")
	}
	return nil
}

func (m *fakePerformanceQuery) collectDataWithTime() (time.Time, error) {
	if !m.openCalled {
		return time.Now(), errors.New("in collectDataWithTime: uninitialized query")
	}
	return metricTime, nil
}

func (m *fakePerformanceQuery) isVistaOrNewer() bool {
	return m.vistaAndNewer
}

type fakePerformanceQueryCreator struct {
	fakeQueries map[string]*fakePerformanceQuery
}

func (m fakePerformanceQueryCreator) newPerformanceQuery(computer string, _ uint32) performanceQuery {
	var ret performanceQuery
	var ok bool
	if ret, ok = m.fakeQueries[computer]; !ok {
		panic(fmt.Errorf("query for %q not found", computer))
	}
	return ret
}

//nolint:revive //argument-limit allowed for helper function
func createPerfObject(
	computer string,
	measurement string,
	object string,
	instances []string,
	counters []string,
	failOnMissing bool,
	includeTotal bool,
	useRawValues bool,
) []perfObject {
	perfObj := perfObject{
		ObjectName:    object,
		Instances:     instances,
		Counters:      counters,
		Measurement:   measurement,
		WarnOnMissing: false,
		FailOnMissing: failOnMissing,
		IncludeTotal:  includeTotal,
		UseRawValues:  useRawValues,
	}

	if computer != "" {
		perfObj.Sources = []string{computer}
	}
	return []perfObject{perfObj}
}

func createCounterMap(counterPaths []string, values []float64, status []uint32) map[string]testCounter {
	counters := make(map[string]testCounter)
	for i, cp := range counterPaths {
		counters[cp] = testCounter{
			pdhCounterHandle(i),
			cp,
			values[i],
			status[i],
		}
	}
	return counters
}

var cachedHostname string

func hostname() string {
	if cachedHostname == "" {
		var err error
		if cachedHostname, err = os.Hostname(); err != nil {
			cachedHostname = "localhost"
		}
	}
	return cachedHostname
}

var counterPathsAndRes = map[string][]string{
	"\\O\\CT":                           {"", "O", "", "CT"},
	"\\O\\CT(i)":                        {"", "O", "", "CT(i)"},
	"\\O\\CT(d:\\f\\i)":                 {"", "O", "", "CT(d:\\f\\i)"},
	"\\\\CM\\O\\CT":                     {"CM", "O", "", "CT"},
	"\\O(I)\\CT":                        {"", "O", "I", "CT"},
	"\\O(I)\\CT(i)":                     {"", "O", "I", "CT(i)"},
	"\\O(I)\\CT(i)x":                    {"", "O", "I", "CT(i)x"},
	"\\O(I)\\CT(d:\\f\\i)":              {"", "O", "I", "CT(d:\\f\\i)"},
	"\\\\CM\\O(I)\\CT":                  {"CM", "O", "I", "CT"},
	"\\O(d:\\f\\I)\\CT":                 {"", "O", "d:\\f\\I", "CT"},
	"\\O(d:\\f\\I(d))\\CT":              {"", "O", "d:\\f\\I(d)", "CT"},
	"\\O(d:\\f\\I(d)x)\\CT":             {"", "O", "d:\\f\\I(d)x", "CT"},
	"\\O(d:\\f\\I)\\CT(i)":              {"", "O", "d:\\f\\I", "CT(i)"},
	"\\O(d:\\f\\I)\\CT(d:\\f\\i)":       {"", "O", "d:\\f\\I", "CT(d:\\f\\i)"},
	"\\\\CM\\O(d:\\f\\I)\\CT":           {"CM", "O", "d:\\f\\I", "CT"},
	"\\\\CM\\O(d:\\f\\I)\\CT(d:\\f\\i)": {"CM", "O", "d:\\f\\I", "CT(d:\\f\\i)"},
	"\\O(I(info))\\CT":                  {"", "O", "I(info)", "CT"},
	"\\\\CM\\O(I(info))\\CT":            {"CM", "O", "I(info)", "CT"},
}

var invalidCounterPaths = []string{
	"\\O(I\\C",
	"\\OI)\\C",
	"\\O(I\\C",
	"\\O/C",
	"\\O(I/C",
	"\\O(I/C)",
	"\\O(I\\)C",
	"\\O(I\\C)",
	"\\CM\\O(I)\\C",
	"\\CM\\O\\C",
	"\\\\C\\O(I)\\C)",
	"\\\\C\\O\\C)",
}

func TestCounterPathParsing(t *testing.T) {
	for path, vals := range counterPathsAndRes {
		h, o, i, c, err := extractCounterInfoFromCounterPath(path)
		require.NoError(t, err)
		require.Equalf(t, []string{h, o, i, c}, vals, "arrays: %#v and %#v are not equal", vals, []string{o, i, c})
	}
	for _, path := range invalidCounterPaths {
		_, _, _, _, err := extractCounterInfoFromCounterPath(path)
		require.Error(t, err)
	}
}

func TestAddItemSimple(t *testing.T) {
	var err error
	cps1 := []string{"\\O(I)\\C"}
	m := WinPerfCounters{
		Log:        testutil.Logger{},
		PrintValid: false,
		Object:     nil,
		queryCreator: &fakePerformanceQueryCreator{
			fakeQueries: map[string]*fakePerformanceQuery{"localhost": {
				counters: createCounterMap(cps1, []float64{1.1}, []uint32{0}),
				expandPaths: map[string][]string{
					cps1[0]: cps1,
				},
				vistaAndNewer: true,
			},
			},
		},
	}
	err = m.addItem(cps1[0], "localhost", "O", "I", "c", "test", false, false)
	require.NoError(t, err)
	counters, ok := m.hostCounters["localhost"]
	require.True(t, ok)
	require.Len(t, counters.counters, 1)
	require.Equal(t, "localhost", counters.counters[0].computer)
	require.Equal(t, "O", counters.counters[0].objectName)
	require.Equal(t, "I", counters.counters[0].instance)
	require.Equal(t, "c", counters.counters[0].counter)
	require.Equal(t, "test", counters.counters[0].measurement)
	require.False(t, counters.counters[0].includeTotal)
}

func TestAddItemInvalidCountPath(t *testing.T) {
	var err error
	cps1 := []string{"\\O\\C"}
	m := WinPerfCounters{
		Log:                   testutil.Logger{},
		PrintValid:            false,
		Object:                nil,
		UseWildcardsExpansion: true,
		queryCreator: &fakePerformanceQueryCreator{
			fakeQueries: map[string]*fakePerformanceQuery{"localhost": {
				counters: createCounterMap(cps1, []float64{1.1}, []uint32{0}),
				expandPaths: map[string][]string{
					cps1[0]: {"\\O/C"},
				},
				vistaAndNewer: true,
			},
			},
		},
	}
	require.NoError(t, err)
	err = m.addItem("\\O\\C", "localhost", "O", "------", "C", "test", false, false)
	require.Error(t, err)
}

func TestParseConfigBasic(t *testing.T) {
	var err error
	perfObjects := createPerfObject("", "m", "O", []string{"I1", "I2"}, []string{"C1", "C2"}, false, false, false)
	cps1 := []string{"\\O(I1)\\C1", "\\O(I1)\\C2", "\\O(I2)\\C1", "\\O(I2)\\C2"}
	m := WinPerfCounters{
		Sources:    []string{"localhost"},
		Log:        testutil.Logger{},
		PrintValid: false,
		Object:     perfObjects,
		queryCreator: &fakePerformanceQueryCreator{
			fakeQueries: map[string]*fakePerformanceQuery{"localhost": {
				counters: createCounterMap(cps1, []float64{1.1, 1.2, 1.3, 1.4}, []uint32{0, 0, 0, 0}),
				expandPaths: map[string][]string{
					cps1[0]: {cps1[0]},
					cps1[1]: {cps1[1]},
					cps1[2]: {cps1[2]},
					cps1[3]: {cps1[3]},
				},
				vistaAndNewer: true,
			},
			},
		},
	}
	require.NoError(t, err)
	err = m.parseConfig()
	require.NoError(t, err)
	counters, ok := m.hostCounters["localhost"]
	require.True(t, ok)
	require.Len(t, counters.counters, 4)
	err = m.cleanQueries()
	require.NoError(t, err)
	m.UseWildcardsExpansion = true

	err = m.parseConfig()
	require.NoError(t, err)
	counters, ok = m.hostCounters["localhost"]
	require.True(t, ok)
	require.Len(t, counters.counters, 4)
}

func TestParseConfigMultiComps(t *testing.T) {
	var err error
	perfObjects := []perfObject{
		createPerfObject("", "m", "O", []string{"I"}, []string{"C"}, false, false, false)[0],
		createPerfObject("", "m", "O1", []string{"I1", "I2"}, []string{"C1", "C2"}, false, false, false)[0],
		createPerfObject("", "m", "O2", []string{"I"}, []string{"C1", "C2", "C3"}, false, false, false)[0],
	}
	//nolint:prealloc // Do not want this to share the underlying array after appending
	cps11 := []string{"\\O(I)\\C"}
	//nolint:prealloc // Do not want this to share the underlying array after appending
	cps12 := []string{"\\\\cmp1\\O(I)\\C"}
	//nolint:prealloc // Do not want this to share the underlying array after appending
	cps13 := []string{"\\\\cmp2\\O(I)\\C"}
	cps21 := []string{"\\O1(I1)\\C1", "\\O1(I1)\\C2", "\\O1(I2)\\C1", "\\O1(I2)\\C2"}
	cps22 := []string{"\\\\cmp1\\O1(I1)\\C1", "\\\\cmp1\\O1(I1)\\C2", "\\\\cmp1\\O1(I2)\\C1", "\\\\cmp1\\O1(I2)\\C2"}
	cps23 := []string{"\\\\cmp2\\O1(I1)\\C1", "\\\\cmp2\\O1(I1)\\C2", "\\\\cmp2\\O1(I2)\\C1", "\\\\cmp2\\O1(I2)\\C2"}
	cps31 := []string{"\\O2(I)\\C1", "\\O2(I)\\C2", "\\O2(I)\\C3"}
	cps32 := []string{"\\\\cmp1\\O2(I)\\C1", "\\\\cmp1\\O2(I)\\C2", "\\\\cmp1\\O2(I)\\C3"}
	cps33 := []string{"\\\\cmp2\\O2(I)\\C1", "\\\\cmp2\\O2(I)\\C2", "\\\\cmp2\\O2(I)\\C3"}
	m := WinPerfCounters{
		Sources:    []string{"localhost", "cmp1", "cmp2"},
		Log:        testutil.Logger{},
		PrintValid: false,
		Object:     perfObjects,
		queryCreator: &fakePerformanceQueryCreator{
			fakeQueries: map[string]*fakePerformanceQuery{
				"localhost": {
					counters: createCounterMap(append(append(cps11, cps21...), cps31...),
						[]float64{1.1, 1.1, 1.2, 2.1, 2.2, 1.1, 1.2, 1.3},
						[]uint32{0, 0, 0, 0, 0, 0, 0, 0}),
					expandPaths: map[string][]string{
						cps11[0]: {cps11[0]},
						cps21[0]: {cps21[0]},
						cps21[1]: {cps21[1]},
						cps21[2]: {cps21[2]},
						cps21[3]: {cps21[3]},
						cps31[0]: {cps31[0]},
						cps31[1]: {cps31[1]},
						cps31[2]: {cps31[2]},
					},
					vistaAndNewer: true,
				},
				"cmp1": {
					counters: createCounterMap(append(append(cps12, cps22...), cps32...),
						[]float64{1.1, 1.1, 1.2, 2.1, 2.2, 1.1, 1.2, 1.3},
						[]uint32{0, 0, 0, 0, 0, 0, 0, 0}),
					expandPaths: map[string][]string{
						cps12[0]: {cps12[0]},
						cps22[0]: {cps22[0]},
						cps22[1]: {cps22[1]},
						cps22[2]: {cps22[2]},
						cps22[3]: {cps22[3]},
						cps32[0]: {cps32[0]},
						cps32[1]: {cps32[1]},
						cps32[2]: {cps32[2]},
					},
					vistaAndNewer: true,
				},
				"cmp2": {
					counters: createCounterMap(append(append(cps13, cps23...), cps33...),
						[]float64{1.1, 1.1, 1.2, 2.1, 2.2, 1.1, 1.2, 1.3},
						[]uint32{0, 0, 0, 0, 0, 0, 0, 0}),
					expandPaths: map[string][]string{
						cps13[0]: {cps13[0]},
						cps23[0]: {cps23[0]},
						cps23[1]: {cps23[1]},
						cps23[2]: {cps23[2]},
						cps23[3]: {cps23[3]},
						cps33[0]: {cps33[0]},
						cps33[1]: {cps33[1]},
						cps33[2]: {cps33[2]},
					},
					vistaAndNewer: true,
				},
			},
		},
	}
	require.NoError(t, err)
	err = m.parseConfig()
	require.NoError(t, err)
	require.Len(t, m.hostCounters, 3)

	counters, ok := m.hostCounters["localhost"]
	require.True(t, ok)
	require.Len(t, counters.counters, 8)
	require.Equal(t, counters.tag, hostname())
	require.Equal(t, "localhost", counters.counters[0].computer)
	require.Equal(t, "O", counters.counters[0].objectName)
	require.Equal(t, "I", counters.counters[0].instance)
	require.Equal(t, "C", counters.counters[0].counter)
	require.Equal(t, "m", counters.counters[0].measurement)
	require.False(t, counters.counters[0].includeTotal)
	require.Equal(t, "localhost", counters.counters[1].computer)
	require.Equal(t, "O1", counters.counters[1].objectName)
	require.Equal(t, "I1", counters.counters[1].instance)
	require.Equal(t, "C1", counters.counters[1].counter)
	require.Equal(t, "m", counters.counters[1].measurement)
	require.False(t, counters.counters[1].includeTotal)
	require.Equal(t, "localhost", counters.counters[2].computer)
	require.Equal(t, "O1", counters.counters[2].objectName)
	require.Equal(t, "I2", counters.counters[2].instance)
	require.Equal(t, "C1", counters.counters[2].counter)
	require.Equal(t, "m", counters.counters[2].measurement)
	require.False(t, counters.counters[2].includeTotal)
	require.Equal(t, "localhost", counters.counters[3].computer)
	require.Equal(t, "O1", counters.counters[3].objectName)
	require.Equal(t, "I1", counters.counters[3].instance)
	require.Equal(t, "C2", counters.counters[3].counter)
	require.Equal(t, "m", counters.counters[3].measurement)
	require.False(t, counters.counters[3].includeTotal)
	require.Equal(t, "localhost", counters.counters[4].computer)
	require.Equal(t, "O1", counters.counters[4].objectName)
	require.Equal(t, "I2", counters.counters[4].instance)
	require.Equal(t, "C2", counters.counters[4].counter)
	require.Equal(t, "m", counters.counters[4].measurement)
	require.False(t, counters.counters[4].includeTotal)
	require.Equal(t, "localhost", counters.counters[5].computer)
	require.Equal(t, "O2", counters.counters[5].objectName)
	require.Equal(t, "I", counters.counters[5].instance)
	require.Equal(t, "C1", counters.counters[5].counter)
	require.Equal(t, "m", counters.counters[5].measurement)
	require.False(t, counters.counters[5].includeTotal)
	require.Equal(t, "localhost", counters.counters[6].computer)
	require.Equal(t, "O2", counters.counters[6].objectName)
	require.Equal(t, "I", counters.counters[6].instance)
	require.Equal(t, "C2", counters.counters[6].counter)
	require.Equal(t, "m", counters.counters[6].measurement)
	require.False(t, counters.counters[6].includeTotal)
	require.Equal(t, "localhost", counters.counters[7].computer)
	require.Equal(t, "O2", counters.counters[7].objectName)
	require.Equal(t, "I", counters.counters[7].instance)
	require.Equal(t, "C3", counters.counters[7].counter)
	require.Equal(t, "m", counters.counters[7].measurement)
	require.False(t, counters.counters[7].includeTotal)

	counters, ok = m.hostCounters["cmp1"]
	require.True(t, ok)
	require.Len(t, counters.counters, 8)
	require.Equal(t, "cmp1", counters.tag)
	require.Equal(t, "cmp1", counters.counters[0].computer)
	require.Equal(t, "O", counters.counters[0].objectName)
	require.Equal(t, "I", counters.counters[0].instance)
	require.Equal(t, "C", counters.counters[0].counter)
	require.Equal(t, "m", counters.counters[0].measurement)
	require.False(t, counters.counters[0].includeTotal)
	require.Equal(t, "cmp1", counters.counters[1].computer)
	require.Equal(t, "O1", counters.counters[1].objectName)
	require.Equal(t, "I1", counters.counters[1].instance)
	require.Equal(t, "C1", counters.counters[1].counter)
	require.Equal(t, "m", counters.counters[1].measurement)
	require.False(t, counters.counters[1].includeTotal)
	require.Equal(t, "cmp1", counters.counters[2].computer)
	require.Equal(t, "O1", counters.counters[2].objectName)
	require.Equal(t, "I2", counters.counters[2].instance)
	require.Equal(t, "C1", counters.counters[2].counter)
	require.Equal(t, "m", counters.counters[2].measurement)
	require.False(t, counters.counters[2].includeTotal)
	require.Equal(t, "cmp1", counters.counters[3].computer)
	require.Equal(t, "O1", counters.counters[3].objectName)
	require.Equal(t, "I1", counters.counters[3].instance)
	require.Equal(t, "C2", counters.counters[3].counter)
	require.Equal(t, "m", counters.counters[3].measurement)
	require.False(t, counters.counters[3].includeTotal)
	require.Equal(t, "cmp1", counters.counters[4].computer)
	require.Equal(t, "O1", counters.counters[4].objectName)
	require.Equal(t, "I2", counters.counters[4].instance)
	require.Equal(t, "C2", counters.counters[4].counter)
	require.Equal(t, "m", counters.counters[4].measurement)
	require.False(t, counters.counters[4].includeTotal)
	require.Equal(t, "cmp1", counters.counters[5].computer)
	require.Equal(t, "O2", counters.counters[5].objectName)
	require.Equal(t, "I", counters.counters[5].instance)
	require.Equal(t, "C1", counters.counters[5].counter)
	require.Equal(t, "m", counters.counters[5].measurement)
	require.False(t, counters.counters[5].includeTotal)
	require.Equal(t, "cmp1", counters.counters[6].computer)
	require.Equal(t, "O2", counters.counters[6].objectName)
	require.Equal(t, "I", counters.counters[6].instance)
	require.Equal(t, "C2", counters.counters[6].counter)
	require.Equal(t, "m", counters.counters[6].measurement)
	require.False(t, counters.counters[6].includeTotal)
	require.Equal(t, "cmp1", counters.counters[7].computer)
	require.Equal(t, "O2", counters.counters[7].objectName)
	require.Equal(t, "I", counters.counters[7].instance)
	require.Equal(t, "C3", counters.counters[7].counter)
	require.Equal(t, "m", counters.counters[7].measurement)
	require.False(t, counters.counters[7].includeTotal)

	counters, ok = m.hostCounters["cmp2"]
	require.True(t, ok)
	require.Len(t, counters.counters, 8)
	require.Equal(t, "cmp2", counters.tag)
	require.Equal(t, "cmp2", counters.counters[0].computer)
	require.Equal(t, "O", counters.counters[0].objectName)
	require.Equal(t, "I", counters.counters[0].instance)
	require.Equal(t, "C", counters.counters[0].counter)
	require.Equal(t, "m", counters.counters[0].measurement)
	require.False(t, counters.counters[0].includeTotal)
	require.Equal(t, "cmp2", counters.counters[1].computer)
	require.Equal(t, "O1", counters.counters[1].objectName)
	require.Equal(t, "I1", counters.counters[1].instance)
	require.Equal(t, "C1", counters.counters[1].counter)
	require.Equal(t, "m", counters.counters[1].measurement)
	require.False(t, counters.counters[1].includeTotal)
	require.Equal(t, "cmp2", counters.counters[2].computer)
	require.Equal(t, "O1", counters.counters[2].objectName)
	require.Equal(t, "I2", counters.counters[2].instance)
	require.Equal(t, "C1", counters.counters[2].counter)
	require.Equal(t, "m", counters.counters[2].measurement)
	require.False(t, counters.counters[2].includeTotal)
	require.Equal(t, "cmp2", counters.counters[3].computer)
	require.Equal(t, "O1", counters.counters[3].objectName)
	require.Equal(t, "I1", counters.counters[3].instance)
	require.Equal(t, "C2", counters.counters[3].counter)
	require.Equal(t, "m", counters.counters[3].measurement)
	require.False(t, counters.counters[3].includeTotal)
	require.Equal(t, "cmp2", counters.counters[4].computer)
	require.Equal(t, "O1", counters.counters[4].objectName)
	require.Equal(t, "I2", counters.counters[4].instance)
	require.Equal(t, "C2", counters.counters[4].counter)
	require.Equal(t, "m", counters.counters[4].measurement)
	require.False(t, counters.counters[4].includeTotal)
	require.Equal(t, "cmp2", counters.counters[5].computer)
	require.Equal(t, "O2", counters.counters[5].objectName)
	require.Equal(t, "I", counters.counters[5].instance)
	require.Equal(t, "C1", counters.counters[5].counter)
	require.Equal(t, "m", counters.counters[5].measurement)
	require.False(t, counters.counters[5].includeTotal)
	require.Equal(t, "cmp2", counters.counters[6].computer)
	require.Equal(t, "O2", counters.counters[6].objectName)
	require.Equal(t, "I", counters.counters[6].instance)
	require.Equal(t, "C2", counters.counters[6].counter)
	require.Equal(t, "m", counters.counters[6].measurement)
	require.False(t, counters.counters[6].includeTotal)
	require.Equal(t, "cmp2", counters.counters[7].computer)
	require.Equal(t, "O2", counters.counters[7].objectName)
	require.Equal(t, "I", counters.counters[7].instance)
	require.Equal(t, "C3", counters.counters[7].counter)
	require.Equal(t, "m", counters.counters[7].measurement)
	require.False(t, counters.counters[7].includeTotal)
}

func TestParseConfigMultiCompsOverrideMultiplePerfObjects(t *testing.T) {
	var err error
	perfObjects := []perfObject{
		createPerfObject("localhost", "m", "O", []string{"I1", "I2"}, []string{"C1", "C2"}, false, false, false)[0],
		createPerfObject("cmp1", "m", "O1", []string{"I1", "I2"}, []string{"C1", "C2"}, false, false, false)[0],
		createPerfObject("cmp2", "m", "O2", []string{"I1", "I2"}, []string{"C1", "C2"}, false, false, false)[0],
	}
	cps1 := []string{"\\O(I1)\\C1", "\\O(I1)\\C2", "\\O(I2)\\C1", "\\O(I2)\\C2"}
	cps2 := []string{"\\\\cmp1\\O1(I1)\\C1", "\\\\cmp1\\O1(I1)\\C2", "\\\\cmp1\\O1(I2)\\C1", "\\\\cmp1\\O1(I2)\\C2"}
	cps3 := []string{"\\\\cmp2\\O2(I1)\\C1", "\\\\cmp2\\O2(I1)\\C2", "\\\\cmp2\\O2(I2)\\C1", "\\\\cmp2\\O2(I2)\\C2"}
	m := WinPerfCounters{
		Log:        testutil.Logger{},
		PrintValid: false,
		Object:     perfObjects,
		queryCreator: &fakePerformanceQueryCreator{
			fakeQueries: map[string]*fakePerformanceQuery{"localhost": {
				counters: createCounterMap(cps1,
					[]float64{1.1, 1.2, 1.3, 1.4},
					[]uint32{0, 0, 0, 0}),
				expandPaths: map[string][]string{
					cps1[0]: {cps1[0]},
					cps1[1]: {cps1[1]},
					cps1[2]: {cps1[2]},
					cps1[3]: {cps1[3]},
				},
				vistaAndNewer: true,
			},
				"cmp1": {
					counters: createCounterMap(cps2,
						[]float64{2.1, 2.2, 2.3, 2.4},
						[]uint32{0, 0, 0, 0}),
					expandPaths: map[string][]string{
						cps2[0]: {cps2[0]},
						cps2[1]: {cps2[1]},
						cps2[2]: {cps2[2]},
						cps2[3]: {cps2[3]},
					},
					vistaAndNewer: true,
				},
				"cmp2": {
					counters: createCounterMap(cps3,
						[]float64{3.1, 3.2, 3.3, 3.4},
						[]uint32{0, 0, 0, 0}),
					expandPaths: map[string][]string{
						cps3[0]: {cps3[0]},
						cps3[1]: {cps3[1]},
						cps3[2]: {cps3[2]},
						cps3[3]: {cps3[3]},
					},
					vistaAndNewer: true,
				},
			},
		},
	}
	require.NoError(t, err)
	err = m.parseConfig()
	require.NoError(t, err)
	require.Len(t, m.hostCounters, 3)

	counters, ok := m.hostCounters["localhost"]
	require.True(t, ok)
	require.Len(t, counters.counters, 4)
	require.Equal(t, "localhost", counters.counters[0].computer)
	require.Equal(t, "O", counters.counters[0].objectName)
	require.Equal(t, "I1", counters.counters[0].instance)
	require.Equal(t, "C1", counters.counters[0].counter)
	require.Equal(t, "m", counters.counters[0].measurement)
	require.False(t, counters.counters[0].includeTotal)
	require.Equal(t, "localhost", counters.counters[1].computer)
	require.Equal(t, "O", counters.counters[1].objectName)
	require.Equal(t, "I2", counters.counters[1].instance)
	require.Equal(t, "C1", counters.counters[1].counter)
	require.Equal(t, "m", counters.counters[1].measurement)
	require.False(t, counters.counters[1].includeTotal)
	require.Equal(t, "localhost", counters.counters[2].computer)
	require.Equal(t, "O", counters.counters[2].objectName)
	require.Equal(t, "I1", counters.counters[2].instance)
	require.Equal(t, "C2", counters.counters[2].counter)
	require.Equal(t, "m", counters.counters[2].measurement)
	require.False(t, counters.counters[2].includeTotal)
	require.Equal(t, "localhost", counters.counters[3].computer)
	require.Equal(t, "O", counters.counters[3].objectName)
	require.Equal(t, "I2", counters.counters[3].instance)
	require.Equal(t, "C2", counters.counters[3].counter)
	require.Equal(t, "m", counters.counters[3].measurement)
	require.False(t, counters.counters[3].includeTotal)

	counters, ok = m.hostCounters["cmp1"]
	require.True(t, ok)
	require.Len(t, counters.counters, 4)
	require.Equal(t, "cmp1", counters.counters[0].computer)
	require.Equal(t, "O1", counters.counters[0].objectName)
	require.Equal(t, "I1", counters.counters[0].instance)
	require.Equal(t, "C1", counters.counters[0].counter)
	require.Equal(t, "m", counters.counters[0].measurement)
	require.False(t, counters.counters[0].includeTotal)
	require.Equal(t, "cmp1", counters.counters[1].computer)
	require.Equal(t, "O1", counters.counters[1].objectName)
	require.Equal(t, "I2", counters.counters[1].instance)
	require.Equal(t, "C1", counters.counters[1].counter)
	require.Equal(t, "m", counters.counters[1].measurement)
	require.False(t, counters.counters[1].includeTotal)
	require.Equal(t, "cmp1", counters.counters[2].computer)
	require.Equal(t, "O1", counters.counters[2].objectName)
	require.Equal(t, "I1", counters.counters[2].instance)
	require.Equal(t, "C2", counters.counters[2].counter)
	require.Equal(t, "m", counters.counters[2].measurement)
	require.False(t, counters.counters[2].includeTotal)
	require.Equal(t, "cmp1", counters.counters[3].computer)
	require.Equal(t, "O1", counters.counters[3].objectName)
	require.Equal(t, "I2", counters.counters[3].instance)
	require.Equal(t, "C2", counters.counters[3].counter)
	require.Equal(t, "m", counters.counters[3].measurement)
	require.False(t, counters.counters[3].includeTotal)

	counters, ok = m.hostCounters["cmp2"]
	require.True(t, ok)
	require.Len(t, counters.counters, 4)
	require.Equal(t, "cmp2", counters.counters[0].computer)
	require.Equal(t, "O2", counters.counters[0].objectName)
	require.Equal(t, "I1", counters.counters[0].instance)
	require.Equal(t, "C1", counters.counters[0].counter)
	require.Equal(t, "m", counters.counters[0].measurement)
	require.False(t, counters.counters[0].includeTotal)
	require.Equal(t, "cmp2", counters.counters[1].computer)
	require.Equal(t, "O2", counters.counters[1].objectName)
	require.Equal(t, "I2", counters.counters[1].instance)
	require.Equal(t, "C1", counters.counters[1].counter)
	require.Equal(t, "m", counters.counters[1].measurement)
	require.False(t, counters.counters[1].includeTotal)
	require.Equal(t, "cmp2", counters.counters[2].computer)
	require.Equal(t, "O2", counters.counters[2].objectName)
	require.Equal(t, "I1", counters.counters[2].instance)
	require.Equal(t, "C2", counters.counters[2].counter)
	require.Equal(t, "m", counters.counters[2].measurement)
	require.False(t, counters.counters[2].includeTotal)
	require.Equal(t, "cmp2", counters.counters[3].computer)
	require.Equal(t, "O2", counters.counters[3].objectName)
	require.Equal(t, "I2", counters.counters[3].instance)
	require.Equal(t, "C2", counters.counters[3].counter)
	require.Equal(t, "m", counters.counters[3].measurement)
	require.False(t, counters.counters[3].includeTotal)
}

func TestParseConfigMultiCompsOverrideOnePerfObject(t *testing.T) {
	var err error

	perfObj := perfObject{
		Sources:       []string{"cmp1", "cmp2"},
		ObjectName:    "O",
		Instances:     []string{"I1", "I2"},
		Counters:      []string{"C1", "C2"},
		Measurement:   "m",
		WarnOnMissing: false,
		FailOnMissing: false,
		IncludeTotal:  false,
	}
	//nolint:prealloc // Do not want this to share the underlying array after appending
	cps11 := []string{"\\\\cmp1\\O(I1)\\C1", "\\\\cmp1\\O(I1)\\C2", "\\\\cmp1\\O(I2)\\C1", "\\\\cmp1\\O(I2)\\C2"}
	cps12 := []string{"\\\\cmp2\\O(I1)\\C1", "\\\\cmp2\\O(I1)\\C2", "\\\\cmp2\\O(I2)\\C1", "\\\\cmp2\\O(I2)\\C2"}
	cps21 := []string{"\\O1(I)\\C"}
	cps22 := []string{"\\\\cmp1\\O1(I)\\C"}
	m := WinPerfCounters{
		Sources:    []string{"localhost", "cmp1"},
		Log:        testutil.Logger{},
		PrintValid: false,
		Object:     []perfObject{perfObj, createPerfObject("", "m", "O1", []string{"I"}, []string{"C"}, false, false, false)[0]},
		queryCreator: &fakePerformanceQueryCreator{
			fakeQueries: map[string]*fakePerformanceQuery{
				"localhost": {
					counters: createCounterMap(cps21,
						[]float64{1.1},
						[]uint32{0}),
					expandPaths: map[string][]string{
						cps21[0]: {cps21[0]},
					},
					vistaAndNewer: true,
				},
				"cmp1": {
					counters: createCounterMap(append(cps11, cps22...),
						[]float64{2.1, 2.1, 2.2, 2.3, 2.4},
						[]uint32{0, 0, 0, 0, 0}),
					expandPaths: map[string][]string{
						cps11[0]: {cps11[0]},
						cps11[1]: {cps11[1]},
						cps11[2]: {cps11[2]},
						cps11[3]: {cps11[3]},
						cps22[0]: {cps22[0]},
					},
					vistaAndNewer: true,
				},
				"cmp2": {
					counters: createCounterMap(cps12,
						[]float64{3.1, 3.2, 3.3, 3.4},
						[]uint32{0, 0, 0, 0}),
					expandPaths: map[string][]string{
						cps12[0]: {cps12[0]},
						cps12[1]: {cps12[1]},
						cps12[2]: {cps12[2]},
						cps12[3]: {cps12[3]},
					},
					vistaAndNewer: true,
				},
			},
		},
	}
	require.NoError(t, err)
	err = m.parseConfig()
	require.NoError(t, err)
	require.Len(t, m.hostCounters, 3)

	counters, ok := m.hostCounters["localhost"]
	require.True(t, ok)
	require.Len(t, counters.counters, 1)
	require.Equal(t, counters.tag, hostname())
	require.Equal(t, "localhost", counters.counters[0].computer)
	require.Equal(t, "O1", counters.counters[0].objectName)
	require.Equal(t, "I", counters.counters[0].instance)
	require.Equal(t, "C", counters.counters[0].counter)
	require.Equal(t, "m", counters.counters[0].measurement)
	require.False(t, counters.counters[0].includeTotal)

	counters, ok = m.hostCounters["cmp1"]
	require.True(t, ok)
	require.Len(t, counters.counters, 5)
	require.Equal(t, "cmp1", counters.tag)
	require.Equal(t, "cmp1", counters.counters[0].computer)
	require.Equal(t, "O", counters.counters[0].objectName)
	require.Equal(t, "I1", counters.counters[0].instance)
	require.Equal(t, "C1", counters.counters[0].counter)
	require.Equal(t, "m", counters.counters[0].measurement)
	require.False(t, counters.counters[0].includeTotal)
	require.Equal(t, "cmp1", counters.counters[0].computer)
	require.Equal(t, "O", counters.counters[1].objectName)
	require.Equal(t, "I2", counters.counters[1].instance)
	require.Equal(t, "C1", counters.counters[1].counter)
	require.Equal(t, "m", counters.counters[1].measurement)
	require.False(t, counters.counters[1].includeTotal)
	require.Equal(t, "cmp1", counters.counters[2].computer)
	require.Equal(t, "O", counters.counters[2].objectName)
	require.Equal(t, "I1", counters.counters[2].instance)
	require.Equal(t, "C2", counters.counters[2].counter)
	require.Equal(t, "m", counters.counters[2].measurement)
	require.False(t, counters.counters[2].includeTotal)
	require.Equal(t, "cmp1", counters.counters[3].computer)
	require.Equal(t, "O", counters.counters[3].objectName)
	require.Equal(t, "I2", counters.counters[3].instance)
	require.Equal(t, "C2", counters.counters[3].counter)
	require.Equal(t, "m", counters.counters[3].measurement)
	require.False(t, counters.counters[3].includeTotal)
	require.Equal(t, "cmp1", counters.counters[4].computer)
	require.Equal(t, "O1", counters.counters[4].objectName)
	require.Equal(t, "I", counters.counters[4].instance)
	require.Equal(t, "C", counters.counters[4].counter)
	require.Equal(t, "m", counters.counters[4].measurement)
	require.False(t, counters.counters[4].includeTotal)

	counters, ok = m.hostCounters["cmp2"]
	require.True(t, ok)
	require.Len(t, counters.counters, 4)
	require.Equal(t, "cmp2", counters.tag)
	require.Equal(t, "cmp2", counters.counters[0].computer)
	require.Equal(t, "O", counters.counters[0].objectName)
	require.Equal(t, "I1", counters.counters[0].instance)
	require.Equal(t, "C1", counters.counters[0].counter)
	require.Equal(t, "m", counters.counters[0].measurement)
	require.False(t, counters.counters[0].includeTotal)
	require.Equal(t, "cmp2", counters.counters[1].computer)
	require.Equal(t, "O", counters.counters[1].objectName)
	require.Equal(t, "I2", counters.counters[1].instance)
	require.Equal(t, "C1", counters.counters[1].counter)
	require.Equal(t, "m", counters.counters[1].measurement)
	require.False(t, counters.counters[1].includeTotal)
	require.Equal(t, "cmp2", counters.counters[2].computer)
	require.Equal(t, "O", counters.counters[2].objectName)
	require.Equal(t, "I1", counters.counters[2].instance)
	require.Equal(t, "C2", counters.counters[2].counter)
	require.Equal(t, "m", counters.counters[2].measurement)
	require.False(t, counters.counters[2].includeTotal)
	require.Equal(t, "cmp2", counters.counters[3].computer)
	require.Equal(t, "O", counters.counters[3].objectName)
	require.Equal(t, "I2", counters.counters[3].instance)
	require.Equal(t, "C2", counters.counters[3].counter)
	require.Equal(t, "m", counters.counters[3].measurement)
	require.False(t, counters.counters[3].includeTotal)
}

func TestParseConfigLocalhost(t *testing.T) {
	var err error
	perfObjects := createPerfObject("localhost", "m", "O", []string{"------"}, []string{"C"}, false, false, false)
	cps1 := []string{"\\O\\C"}
	m := WinPerfCounters{
		Log:        testutil.Logger{},
		PrintValid: false,
		Object:     perfObjects,
		queryCreator: &fakePerformanceQueryCreator{
			fakeQueries: map[string]*fakePerformanceQuery{"localhost": {
				counters: createCounterMap(cps1, []float64{1.1}, []uint32{0}),
				expandPaths: map[string][]string{
					cps1[0]: {cps1[0]},
				},
				vistaAndNewer: true,
			},
			},
		},
	}

	err = m.parseConfig()
	require.NoError(t, err)

	hostCounters, ok := m.hostCounters["localhost"]
	require.True(t, ok)
	require.Len(t, hostCounters.counters, 1)
	require.Equal(t, "localhost", hostCounters.counters[0].computer)
	require.Equal(t, "localhost", hostCounters.computer, hostCounters.computer)
	require.Equal(t, hostname(), hostCounters.tag)

	err = m.cleanQueries()
	require.NoError(t, err)

	m.Object[0].Sources = []string{""}

	err = m.parseConfig()
	require.NoError(t, err)

	hostCounters, ok = m.hostCounters["localhost"]
	require.True(t, ok)
	require.Len(t, hostCounters.counters, 1)
	require.Equal(t, "localhost", hostCounters.counters[0].computer)
	require.Equal(t, "localhost", hostCounters.computer, hostCounters.computer)
	require.Equal(t, hostname(), hostCounters.tag)
}

func TestParseConfigNoInstance(t *testing.T) {
	var err error
	perfObjects := createPerfObject("", "m", "O", []string{"------"}, []string{"C1", "C2"}, false, false, false)
	cps1 := []string{"\\O\\C1", "\\O\\C2"}
	m := WinPerfCounters{
		Log:                   testutil.Logger{},
		PrintValid:            false,
		Object:                perfObjects,
		UseWildcardsExpansion: false,
		queryCreator: &fakePerformanceQueryCreator{
			fakeQueries: map[string]*fakePerformanceQuery{"localhost": {
				counters: createCounterMap(cps1, []float64{1.1, 1.2}, []uint32{0, 0}),
				expandPaths: map[string][]string{
					cps1[0]: {cps1[0]},
					cps1[1]: {cps1[1]},
				},
				vistaAndNewer: true,
			},
			},
		},
	}

	err = m.parseConfig()
	require.NoError(t, err)
	counters, ok := m.hostCounters["localhost"]
	require.True(t, ok)
	require.Len(t, counters.counters, 2)

	m.UseWildcardsExpansion = true
	err = m.cleanQueries()
	require.NoError(t, err)

	err = m.parseConfig()
	require.NoError(t, err)
	counters, ok = m.hostCounters["localhost"]
	require.True(t, ok)
	require.Len(t, counters.counters, 2)
}

func TestParseConfigInvalidCounterError(t *testing.T) {
	var err error
	perfObjects := createPerfObject("", "m", "O", []string{"I1", "I2"}, []string{"C1", "C2"}, true, false, false)
	cps1 := []string{"\\O(I1)\\C2", "\\O(I2)\\C1", "\\O(I2)\\C2"}
	m := WinPerfCounters{
		Log:        testutil.Logger{},
		PrintValid: false,
		Object:     perfObjects,
		queryCreator: &fakePerformanceQueryCreator{
			fakeQueries: map[string]*fakePerformanceQuery{"localhost": {
				counters: createCounterMap(cps1, []float64{1.1, 1.2, 1.3}, []uint32{0, 0, 0}),
				expandPaths: map[string][]string{
					cps1[0]: {cps1[0]},
					cps1[1]: {cps1[1]},
					cps1[2]: {cps1[2]},
				},
				vistaAndNewer: true,
			},
			},
		},
	}

	err = m.parseConfig()
	require.Error(t, err)

	err = m.cleanQueries()
	require.NoError(t, err)
	m.UseWildcardsExpansion = true

	err = m.parseConfig()
	require.Error(t, err)
	err = m.cleanQueries()
	require.NoError(t, err)
}

func TestParseConfigInvalidCounterNoError(t *testing.T) {
	var err error
	perfObjects := createPerfObject("", "m", "O", []string{"I1", "I2"}, []string{"C1", "C2"}, false, false, false)
	cps1 := []string{"\\O(I1)\\C2", "\\O(I2)\\C1", "\\O(I2)\\C2"}
	m := WinPerfCounters{
		Log:        testutil.Logger{},
		PrintValid: false,
		Object:     perfObjects,
		queryCreator: &fakePerformanceQueryCreator{
			fakeQueries: map[string]*fakePerformanceQuery{"localhost": {
				counters: createCounterMap(cps1, []float64{1.1, 1.2, 1.3}, []uint32{0, 0, 0}),
				expandPaths: map[string][]string{
					cps1[0]: {cps1[0]},
					cps1[1]: {cps1[1]},
					cps1[2]: {cps1[2]},
				},
				vistaAndNewer: true,
			},
			},
		},
	}

	err = m.parseConfig()
	require.NoError(t, err)
	err = m.cleanQueries()
	require.NoError(t, err)

	m.UseWildcardsExpansion = true

	err = m.parseConfig()
	require.NoError(t, err)
	err = m.cleanQueries()
	require.NoError(t, err)
}

func TestParseConfigTotalExpansion(t *testing.T) {
	var err error
	perfObjects := createPerfObject("", "m", "O", []string{"*"}, []string{"*"}, true, true, false)
	cps1 := []string{"\\O(I1)\\C1", "\\O(I1)\\C2", "\\O(_Total)\\C1", "\\O(_Total)\\C2"}
	m := WinPerfCounters{
		Log:                   testutil.Logger{},
		PrintValid:            false,
		UseWildcardsExpansion: true,
		Object:                perfObjects,
		queryCreator: &fakePerformanceQueryCreator{
			fakeQueries: map[string]*fakePerformanceQuery{"localhost": {
				counters: createCounterMap(append(cps1, "\\O(*)\\*"), []float64{1.1, 1.2, 1.3, 1.4, 0}, []uint32{0, 0, 0, 0, 0}),
				expandPaths: map[string][]string{
					"\\O(*)\\*": cps1,
				},
				vistaAndNewer: true,
			},
			},
		},
		LocalizeWildcardsExpansion: true,
	}
	err = m.parseConfig()
	require.NoError(t, err)
	counters, ok := m.hostCounters["localhost"]
	require.True(t, ok)
	require.Len(t, counters.counters, 4)
	err = m.cleanQueries()
	require.NoError(t, err)

	perfObjects[0].IncludeTotal = false

	m = WinPerfCounters{
		Log:                   testutil.Logger{},
		PrintValid:            false,
		UseWildcardsExpansion: true,
		Object:                perfObjects,
		queryCreator: &fakePerformanceQueryCreator{
			fakeQueries: map[string]*fakePerformanceQuery{"localhost": {
				counters: createCounterMap(append(cps1, "\\O(*)\\*"), []float64{1.1, 1.2, 1.3, 1.4, 0}, []uint32{0, 0, 0, 0, 0}),
				expandPaths: map[string][]string{
					"\\O(*)\\*": cps1,
				},
				vistaAndNewer: true,
			},
			},
		},
		LocalizeWildcardsExpansion: true,
	}

	err = m.parseConfig()
	require.NoError(t, err)
	counters, ok = m.hostCounters["localhost"]
	require.True(t, ok)
	require.Len(t, counters.counters, 2)
	err = m.cleanQueries()
	require.NoError(t, err)
}

func TestParseConfigExpand(t *testing.T) {
	var err error
	perfObjects := createPerfObject("", "m", "O", []string{"*"}, []string{"*"}, false, false, false)
	//nolint:prealloc // Do not want this to share the underlying array after appending
	cps1 := []string{"\\O(I1)\\C1", "\\O(I1)\\C2", "\\O(I2)\\C1", "\\O(I2)\\C2"}
	m := WinPerfCounters{
		Log:                   testutil.Logger{},
		PrintValid:            false,
		UseWildcardsExpansion: true,
		Object:                perfObjects,
		queryCreator: &fakePerformanceQueryCreator{
			fakeQueries: map[string]*fakePerformanceQuery{"localhost": {
				counters: createCounterMap(append(cps1, "\\O(*)\\*"), []float64{1.1, 1.2, 1.3, 1.4, 0}, []uint32{0, 0, 0, 0, 0}),
				expandPaths: map[string][]string{
					"\\O(*)\\*": cps1,
				},
				vistaAndNewer: true,
			},
			},
		},
		LocalizeWildcardsExpansion: true,
	}
	err = m.parseConfig()
	require.NoError(t, err)
	counters, ok := m.hostCounters["localhost"]
	require.True(t, ok)
	require.Len(t, counters.counters, 4)
	err = m.cleanQueries()
	require.NoError(t, err)
}

func TestSimpleGather(t *testing.T) {
	var err error
	if testing.Short() {
		t.Skip("Skipping long taking test in short mode")
	}
	measurement := "test"
	perfObjects := createPerfObject("", measurement, "O", []string{"I"}, []string{"C"}, false, false, false)
	cp1 := "\\O(I)\\C"
	m := WinPerfCounters{
		Log:        testutil.Logger{},
		PrintValid: false,
		Object:     perfObjects,
		queryCreator: &fakePerformanceQueryCreator{
			fakeQueries: map[string]*fakePerformanceQuery{"localhost": {
				counters: createCounterMap([]string{cp1}, []float64{1.2}, []uint32{0}),
				expandPaths: map[string][]string{
					cp1: {cp1},
				},
				vistaAndNewer: false,
			},
			},
		},
	}
	var acc1 testutil.Accumulator
	err = m.Gather(&acc1)
	require.NoError(t, err)

	fields1 := map[string]interface{}{
		"C": 1.2,
	}
	tags1 := map[string]string{
		"instance":   "I",
		"objectname": "O",
		"source":     hostname(),
	}
	acc1.AssertContainsTaggedFields(t, measurement, fields1, tags1)

	m.UseWildcardsExpansion = true
	err = m.cleanQueries()
	require.NoError(t, err)
	m.lastRefreshed = time.Time{}

	var acc2 testutil.Accumulator

	err = m.Gather(&acc2)
	require.NoError(t, err)
	acc1.AssertContainsTaggedFields(t, measurement, fields1, tags1)
	err = m.cleanQueries()
	require.NoError(t, err)
}

func TestSimpleGatherNoData(t *testing.T) {
	var err error
	if testing.Short() {
		t.Skip("Skipping long taking test in short mode")
	}
	measurement := "test"
	perfObjects := createPerfObject("", measurement, "O", []string{"I"}, []string{"C"}, false, false, false)
	cp1 := "\\O(I)\\C"
	m := WinPerfCounters{
		Log:        testutil.Logger{},
		PrintValid: false,
		Object:     perfObjects,
		queryCreator: &fakePerformanceQueryCreator{
			fakeQueries: map[string]*fakePerformanceQuery{"localhost": {
				counters: createCounterMap([]string{cp1}, []float64{1.2}, []uint32{pdhNoData}),
				expandPaths: map[string][]string{
					cp1: {cp1},
				},
				vistaAndNewer: false,
			},
			},
		},
	}
	var acc1 testutil.Accumulator
	err = m.Gather(&acc1)
	// this "PDH_NO_DATA" error should not be returned to caller, but checked, and handled
	require.NoError(t, err)

	// fields would contain if the error was ignored, and we simply added garbage
	fields1 := map[string]interface{}{
		"C": 1.2,
	}
	// tags would contain if the error was ignored, and we simply added garbage
	tags1 := map[string]string{
		"instance":   "I",
		"objectname": "O",
	}
	acc1.AssertDoesNotContainsTaggedFields(t, measurement, fields1, tags1)

	m.UseWildcardsExpansion = true
	err = m.cleanQueries()
	require.NoError(t, err)
	m.lastRefreshed = time.Time{}

	var acc2 testutil.Accumulator

	err = m.Gather(&acc2)
	require.NoError(t, err)
	acc1.AssertDoesNotContainsTaggedFields(t, measurement, fields1, tags1)
	err = m.cleanQueries()
	require.NoError(t, err)
}

func TestSimpleGatherWithTimestamp(t *testing.T) {
	var err error
	if testing.Short() {
		t.Skip("Skipping long taking test in short mode")
	}
	measurement := "test"
	perfObjects := createPerfObject("", measurement, "O", []string{"I"}, []string{"C"}, false, false, false)
	cp1 := "\\O(I)\\C"
	m := WinPerfCounters{
		Log:                testutil.Logger{},
		PrintValid:         false,
		UsePerfCounterTime: true,
		Object:             perfObjects,
		queryCreator: &fakePerformanceQueryCreator{
			fakeQueries: map[string]*fakePerformanceQuery{"localhost": {
				counters: createCounterMap([]string{cp1}, []float64{1.2}, []uint32{0}),
				expandPaths: map[string][]string{
					cp1: {cp1},
				},
				vistaAndNewer: true,
			},
			},
		},
	}

	var acc1 testutil.Accumulator
	err = m.Gather(&acc1)
	require.NoError(t, err)

	fields1 := map[string]interface{}{
		"C": 1.2,
	}
	tags1 := map[string]string{
		"instance":   "I",
		"objectname": "O",
		"source":     hostname(),
	}
	acc1.AssertContainsTaggedFields(t, measurement, fields1, tags1)
	require.True(t, acc1.HasTimestamp(measurement, metricTime))
	err = m.cleanQueries()
	require.NoError(t, err)
}

func TestGatherError(t *testing.T) {
	if testing.Short() {
		t.Skip("Skipping long taking test in short mode")
	}

	measurement := "test"
	perfObjects := createPerfObject("", measurement, "O", []string{"I"}, []string{"C"}, false, false, false)
	cp1 := "\\O(I)\\C"
	m := WinPerfCounters{
		Log:        testutil.Logger{},
		PrintValid: false,
		Object:     perfObjects,
		queryCreator: &fakePerformanceQueryCreator{
			fakeQueries: map[string]*fakePerformanceQuery{"localhost": {
				counters: createCounterMap([]string{cp1}, []float64{-2}, []uint32{pdhPlaValidationWarning}),
				expandPaths: map[string][]string{
					cp1: {cp1},
				},
				vistaAndNewer: false,
			},
			},
		},
	}

	expectedError := fmt.Sprintf("error during collecting data on host %q: error while getting value for counter %q: "+
		"The information passed is not valid.\r\n", "localhost", "\\O(I)\\C")
	var acc1 testutil.Accumulator
	require.NoError(t, m.Gather(&acc1))
	require.Len(t, acc1.Errors, 1)
	require.Equal(t, expectedError, acc1.Errors[0].Error())

	m.UseWildcardsExpansion = true
	require.NoError(t, m.cleanQueries())

	m.lastRefreshed = time.Time{}
	var acc2 testutil.Accumulator
	require.NoError(t, m.Gather(&acc2))
	require.Len(t, acc2.Errors, 1)
	require.Equal(t, expectedError, acc2.Errors[0].Error())

	require.NoError(t, m.cleanQueries())
}

func TestGatherInvalidDataIgnore(t *testing.T) {
	var err error
	if testing.Short() {
		t.Skip("Skipping long taking test in short mode")
	}
	measurement := "test"
	perfObjects := createPerfObject("", measurement, "O", []string{"I"}, []string{"C1", "C2", "C3"}, false, false, false)
	cps1 := []string{"\\O(I)\\C1", "\\O(I)\\C2", "\\O(I)\\C3"}
	m := WinPerfCounters{
		Log:        testutil.Logger{},
		PrintValid: false,
		Object:     perfObjects,
		queryCreator: &fakePerformanceQueryCreator{
			fakeQueries: map[string]*fakePerformanceQuery{"localhost": {
				counters: createCounterMap(cps1, []float64{1.2, 1, 0}, []uint32{0, pdhInvalidData, 0}),
				expandPaths: map[string][]string{
					cps1[0]: {cps1[0]},
					cps1[1]: {cps1[1]},
					cps1[2]: {cps1[2]},
				},
				vistaAndNewer: false,
			},
			},
		},
	}
	var acc1 testutil.Accumulator
	err = m.Gather(&acc1)
	require.NoError(t, err)

	fields1 := map[string]interface{}{
		"C1": 1.2,
		"C3": float64(0),
	}
	tags1 := map[string]string{
		"instance":   "I",
		"objectname": "O",
		"source":     hostname(),
	}
	acc1.AssertContainsTaggedFields(t, measurement, fields1, tags1)

	m.UseWildcardsExpansion = true
	err = m.cleanQueries()
	require.NoError(t, err)
	m.lastRefreshed = time.Time{}

	var acc2 testutil.Accumulator
	err = m.Gather(&acc2)
	require.NoError(t, err)
	acc1.AssertContainsTaggedFields(t, measurement, fields1, tags1)
	err = m.cleanQueries()
	require.NoError(t, err)
}

// tests with expansion
func TestGatherRefreshingWithExpansion(t *testing.T) {
	var err error
	if testing.Short() {
		t.Skip("Skipping long taking test in short mode")
	}
	measurement := "test"
	perfObjects := createPerfObject("", measurement, "O", []string{"*"}, []string{"*"}, true, false, false)
	//nolint:prealloc // Do not want this to share the underlying array after appending
	cps1 := []string{"\\O(I1)\\C1", "\\O(I1)\\C2", "\\O(I2)\\C1", "\\O(I2)\\C2"}
	fpm := &fakePerformanceQuery{
		counters: createCounterMap(append(cps1, "\\O(*)\\*"), []float64{1.1, 1.2, 1.3, 1.4, 0}, []uint32{0, 0, 0, 0, 0}),
		expandPaths: map[string][]string{
			"\\O(*)\\*": cps1,
		},
		vistaAndNewer: true,
	}
	m := WinPerfCounters{
		Log:                   testutil.Logger{},
		PrintValid:            false,
		Object:                perfObjects,
		UseWildcardsExpansion: true,
		queryCreator: &fakePerformanceQueryCreator{
			fakeQueries: map[string]*fakePerformanceQuery{"localhost": fpm},
		},
		CountersRefreshInterval:    config.Duration(time.Second * 10),
		LocalizeWildcardsExpansion: true,
	}
	var acc1 testutil.Accumulator
	err = m.Gather(&acc1)
	require.NoError(t, err)

	counters, ok := m.hostCounters["localhost"]
	require.True(t, ok)
	require.Len(t, counters.counters, 4)

	require.Len(t, acc1.Metrics, 2)

	fields1 := map[string]interface{}{
		"C1": 1.1,
		"C2": 1.2,
	}
	tags1 := map[string]string{
		"instance":   "I1",
		"objectname": "O",
		"source":     hostname(),
	}
	acc1.AssertContainsTaggedFields(t, measurement, fields1, tags1)

	fields2 := map[string]interface{}{
		"C1": 1.3,
		"C2": 1.4,
	}
	tags2 := map[string]string{
		"instance":   "I2",
		"objectname": "O",
		"source":     hostname(),
	}
	acc1.AssertContainsTaggedFields(t, measurement, fields2, tags2)

	//nolint:prealloc // Do not want this to share the underlying array after appending
	cps2 := []string{"\\O(I1)\\C1", "\\O(I1)\\C2", "\\O(I2)\\C1", "\\O(I2)\\C2", "\\O(I3)\\C1", "\\O(I3)\\C2"}
	fpm = &fakePerformanceQuery{
		counters: createCounterMap(append(cps2, "\\O(*)\\*"), []float64{1.1, 1.2, 1.3, 1.4, 1.5, 1.6, 0}, []uint32{0, 0, 0, 0, 0, 0, 0}),
		expandPaths: map[string][]string{
			"\\O(*)\\*": cps2,
		},
		vistaAndNewer: true,
	}

	m.queryCreator = &fakePerformanceQueryCreator{
		fakeQueries: map[string]*fakePerformanceQuery{"localhost": fpm},
	}
	var acc2 testutil.Accumulator

	fields3 := map[string]interface{}{
		"C1": 1.5,
		"C2": 1.6,
	}
	tags3 := map[string]string{
		"instance":   "I3",
		"objectname": "O",
		"source":     hostname(),
	}

	// test before elapsing CounterRefreshRate counters are not refreshed
	err = m.Gather(&acc2)
	require.NoError(t, err)
	counters, ok = m.hostCounters["localhost"]
	require.True(t, ok)
	require.Len(t, counters.counters, 4)
	require.Len(t, acc2.Metrics, 2)

	acc2.AssertContainsTaggedFields(t, measurement, fields1, tags1)
	acc2.AssertContainsTaggedFields(t, measurement, fields2, tags2)
	acc2.AssertDoesNotContainsTaggedFields(t, measurement, fields3, tags3)
	time.Sleep(time.Duration(m.CountersRefreshInterval))

	var acc3 testutil.Accumulator
	err = m.Gather(&acc3)
	require.NoError(t, err)
	require.Len(t, acc3.Metrics, 3)

	acc3.AssertContainsTaggedFields(t, measurement, fields1, tags1)
	acc3.AssertContainsTaggedFields(t, measurement, fields2, tags2)

	acc3.AssertContainsTaggedFields(t, measurement, fields3, tags3)
	err = m.cleanQueries()
	require.NoError(t, err)
}

func TestGatherRefreshingWithoutExpansion(t *testing.T) {
	var err error
	if testing.Short() {
		t.Skip("Skipping long taking test in short mode")
	}
	measurement := "test"
	perfObjects := createPerfObject("", measurement, "O", []string{"*"}, []string{"C1", "C2"}, true, false, false)
	cps1 := []string{"\\O(I1)\\C1", "\\O(I1)\\C2", "\\O(I2)\\C1", "\\O(I2)\\C2"}
	fpm := &fakePerformanceQuery{
		counters: createCounterMap(append([]string{"\\O(*)\\C1", "\\O(*)\\C2"}, cps1...), []float64{0, 0, 1.1, 1.2, 1.3, 1.4}, []uint32{0, 0, 0, 0, 0, 0}),
		expandPaths: map[string][]string{
			"\\O(*)\\C1": {cps1[0], cps1[2]},
			"\\O(*)\\C2": {cps1[1], cps1[3]},
		},
		vistaAndNewer: true,
	}
	m := WinPerfCounters{
		Log:                   testutil.Logger{},
		PrintValid:            false,
		Object:                perfObjects,
		UseWildcardsExpansion: false,
		queryCreator: &fakePerformanceQueryCreator{
			fakeQueries: map[string]*fakePerformanceQuery{"localhost": fpm},
		},
		CountersRefreshInterval: config.Duration(time.Second * 10)}
	var acc1 testutil.Accumulator
	err = m.Gather(&acc1)
	counters, ok := m.hostCounters["localhost"]
	require.True(t, ok)
	require.Len(t, counters.counters, 2)
	require.NoError(t, err)
	require.Len(t, acc1.Metrics, 2)

	fields1 := map[string]interface{}{
		"C1": 1.1,
		"C2": 1.2,
	}
	tags1 := map[string]string{
		"instance":   "I1",
		"objectname": "O",
		"source":     hostname(),
	}
	acc1.AssertContainsTaggedFields(t, measurement, fields1, tags1)

	fields2 := map[string]interface{}{
		"C1": 1.3,
		"C2": 1.4,
	}
	tags2 := map[string]string{
		"instance":   "I2",
		"objectname": "O",
		"source":     hostname(),
	}
	acc1.AssertContainsTaggedFields(t, measurement, fields2, tags2)
	// test finding new instance
	cps2 := []string{"\\O(I1)\\C1", "\\O(I1)\\C2", "\\O(I2)\\C1", "\\O(I2)\\C2", "\\O(I3)\\C1", "\\O(I3)\\C2"}
	fpm = &fakePerformanceQuery{
		counters: createCounterMap(
			append([]string{"\\O(*)\\C1", "\\O(*)\\C2"}, cps2...),
			[]float64{0, 0, 1.1, 1.2, 1.3, 1.4, 1.5, 1.6},
			[]uint32{0, 0, 0, 0, 0, 0, 0, 0},
		),
		expandPaths: map[string][]string{
			"\\O(*)\\C1": {cps2[0], cps2[2], cps2[4]},
			"\\O(*)\\C2": {cps2[1], cps2[3], cps2[5]},
		},
		vistaAndNewer: true,
	}

	err = m.cleanQueries()
	require.NoError(t, err)
	m.lastRefreshed = time.Time{}
	m.queryCreator = &fakePerformanceQueryCreator{
		fakeQueries: map[string]*fakePerformanceQuery{"localhost": fpm},
	}

	var acc2 testutil.Accumulator

	fields3 := map[string]interface{}{
		"C1": 1.5,
		"C2": 1.6,
	}
	tags3 := map[string]string{
		"instance":   "I3",
		"objectname": "O",
		"source":     hostname(),
	}

	// test before elapsing CounterRefreshRate counters are not refreshed

	err = m.Gather(&acc2)
	require.NoError(t, err)
	counters, ok = m.hostCounters["localhost"]
	require.True(t, ok)
	require.Len(t, counters.counters, 2)
	require.Len(t, acc2.Metrics, 3)

	acc2.AssertContainsTaggedFields(t, measurement, fields1, tags1)
	acc2.AssertContainsTaggedFields(t, measurement, fields2, tags2)
	acc2.AssertContainsTaggedFields(t, measurement, fields3, tags3)
	// test changed configuration
	perfObjects = createPerfObject("", measurement, "O", []string{"*"}, []string{"C1", "C2", "C3"}, true, false, false)
	cps3 := []string{"\\O(I1)\\C1", "\\O(I1)\\C2", "\\O(I1)\\C3", "\\O(I2)\\C1", "\\O(I2)\\C2", "\\O(I2)\\C3"}
	fpm = &fakePerformanceQuery{
		counters: createCounterMap(
			append([]string{"\\O(*)\\C1", "\\O(*)\\C2", "\\O(*)\\C3"}, cps3...),
			[]float64{0, 0, 0, 1.1, 1.2, 1.3, 1.4, 1.5, 1.6},
			[]uint32{0, 0, 0, 0, 0, 0, 0, 0, 0},
		),
		expandPaths: map[string][]string{
			"\\O(*)\\C1": {cps3[0], cps3[3]},
			"\\O(*)\\C2": {cps3[1], cps3[4]},
			"\\O(*)\\C3": {cps3[2], cps3[5]},
		},
		vistaAndNewer: true,
	}
	err = m.cleanQueries()
	m.lastRefreshed = time.Time{}
	require.NoError(t, err)
	m.queryCreator = &fakePerformanceQueryCreator{
		fakeQueries: map[string]*fakePerformanceQuery{"localhost": fpm},
	}
	m.Object = perfObjects

	time.Sleep(time.Duration(m.CountersRefreshInterval))

	var acc3 testutil.Accumulator
	err = m.Gather(&acc3)
	require.NoError(t, err)
	require.Len(t, acc3.Metrics, 2)
	fields4 := map[string]interface{}{
		"C1": 1.1,
		"C2": 1.2,
		"C3": 1.3,
	}
	tags4 := map[string]string{
		"instance":   "I1",
		"objectname": "O",
		"source":     hostname(),
	}
	fields5 := map[string]interface{}{
		"C1": 1.4,
		"C2": 1.5,
		"C3": 1.6,
	}
	tags5 := map[string]string{
		"instance":   "I2",
		"objectname": "O",
		"source":     hostname(),
	}

	acc3.AssertContainsTaggedFields(t, measurement, fields4, tags4)
	acc3.AssertContainsTaggedFields(t, measurement, fields5, tags5)
	err = m.cleanQueries()
	require.NoError(t, err)
}

func TestGatherTotalNoExpansion(t *testing.T) {
	if testing.Short() {
		t.Skip("Skipping long taking test in short mode")
	}
	var err error
	measurement := "m"
	perfObjects := createPerfObject("", measurement, "O", []string{"*"}, []string{"C1", "C2"}, true, true, false)
	cps1 := []string{"\\O(I1)\\C1", "\\O(I1)\\C2", "\\O(_Total)\\C1", "\\O(_Total)\\C2"}
	m := WinPerfCounters{
		Log:                   testutil.Logger{},
		PrintValid:            false,
		UseWildcardsExpansion: false,
		Object:                perfObjects,
		queryCreator: &fakePerformanceQueryCreator{
			fakeQueries: map[string]*fakePerformanceQuery{"localhost": {
				counters: createCounterMap(
					append([]string{"\\O(*)\\C1", "\\O(*)\\C2"}, cps1...),
					[]float64{0, 0, 1.1, 1.2, 1.3, 1.4},
					[]uint32{0, 0, 0, 0, 0, 0}),
				expandPaths: map[string][]string{
					"\\O(*)\\C1": {cps1[0], cps1[2]},
					"\\O(*)\\C2": {cps1[1], cps1[3]},
				},
				vistaAndNewer: true,
			},
			},
		},
	}
	var acc1 testutil.Accumulator
	err = m.Gather(&acc1)
	require.NoError(t, err)
	counters, ok := m.hostCounters["localhost"]
	require.True(t, ok)
	require.Len(t, counters.counters, 2)
	require.Len(t, acc1.Metrics, 2)
	fields1 := map[string]interface{}{
		"C1": 1.1,
		"C2": 1.2,
	}
	tags1 := map[string]string{
		"instance":   "I1",
		"objectname": "O",
		"source":     hostname(),
	}
	acc1.AssertContainsTaggedFields(t, measurement, fields1, tags1)

	fields2 := map[string]interface{}{
		"C1": 1.3,
		"C2": 1.4,
	}
	tags2 := map[string]string{
		"instance":   "_Total",
		"objectname": "O",
		"source":     hostname(),
	}
	acc1.AssertContainsTaggedFields(t, measurement, fields2, tags2)

	perfObjects[0].IncludeTotal = false

	err = m.cleanQueries()
	require.NoError(t, err)
	m.UseWildcardsExpansion = true
	m.lastRefreshed = time.Time{}

	var acc2 testutil.Accumulator
	err = m.Gather(&acc2)
	require.NoError(t, err)
	counters, ok = m.hostCounters["localhost"]
	require.True(t, ok)
	require.Len(t, counters.counters, 2)
	require.Len(t, acc2.Metrics, 1)

	acc2.AssertContainsTaggedFields(t, measurement, fields1, tags1)
	acc2.AssertDoesNotContainsTaggedFields(t, measurement, fields2, tags2)
	err = m.cleanQueries()
	require.NoError(t, err)
}

func TestGatherMultiComps(t *testing.T) {
	var err error
	perfObjects := []perfObject{
		createPerfObject("", "m", "O", []string{"I1", "I2"}, []string{"C1", "C2"}, false, false, false)[0],
		createPerfObject("cmp1", "m1", "O", []string{"I1", "I2"}, []string{"C1", "C2"}, false, false, false)[0],
		createPerfObject("cmp2", "m2", "O", []string{"I1", "I2"}, []string{"C1", "C2"}, false, false, false)[0],
	}
	cps1 := []string{"\\O(I1)\\C1", "\\O(I1)\\C2", "\\O(I2)\\C1", "\\O(I2)\\C2"}
	cps2 := []string{"\\\\cmp1\\O(I1)\\C1", "\\\\cmp1\\O(I1)\\C2", "\\\\cmp1\\O(I2)\\C1", "\\\\cmp1\\O(I2)\\C2"}
	cps3 := []string{"\\\\cmp2\\O(I1)\\C1", "\\\\cmp2\\O(I1)\\C2", "\\\\cmp2\\O(I2)\\C1", "\\\\cmp2\\O(I2)\\C2"}
	m := WinPerfCounters{
		Log:        testutil.Logger{},
		PrintValid: false,
		Object:     perfObjects,
		queryCreator: &fakePerformanceQueryCreator{
			fakeQueries: map[string]*fakePerformanceQuery{"localhost": {
				counters: createCounterMap(cps1,
					[]float64{1.1, 1.2, 1.3, 1.4},
					[]uint32{0, 0, 0, 0}),
				expandPaths: map[string][]string{
					cps1[0]: {cps1[0]},
					cps1[1]: {cps1[1]},
					cps1[2]: {cps1[2]},
					cps1[3]: {cps1[3]},
				},
				vistaAndNewer: true,
			},
				"cmp1": {
					counters: createCounterMap(cps2,
						[]float64{2.1, 2.2, 2.3, 2.4},
						[]uint32{0, 0, 0, 0}),
					expandPaths: map[string][]string{
						cps2[0]: {cps2[0]},
						cps2[1]: {cps2[1]},
						cps2[2]: {cps2[2]},
						cps2[3]: {cps2[3]},
					},
					vistaAndNewer: true,
				},
				"cmp2": {
					counters: createCounterMap(cps3,
						[]float64{3.1, 3.2, 3.3, 3.4},
						[]uint32{0, 0, 0, 0}),
					expandPaths: map[string][]string{
						cps3[0]: {cps3[0]},
						cps3[1]: {cps3[1]},
						cps3[2]: {cps3[2]},
						cps3[3]: {cps3[3]},
					},
					vistaAndNewer: true,
				},
			},
		},
	}
	var acc testutil.Accumulator
	err = m.Gather(&acc)
	require.NoError(t, err)
	require.Len(t, acc.Metrics, 6)
	fields1 := map[string]interface{}{
		"C1": 1.1,
		"C2": 1.2,
	}
	tags1 := map[string]string{
		"instance":   "I1",
		"objectname": "O",
		"source":     hostname(),
	}
	fields2 := map[string]interface{}{
		"C1": 1.3,
		"C2": 1.4,
	}
	tags2 := map[string]string{
		"instance":   "I2",
		"objectname": "O",
		"source":     hostname(),
	}
	acc.AssertContainsTaggedFields(t, "m", fields1, tags1)
	acc.AssertContainsTaggedFields(t, "m", fields2, tags2)
	fields3 := map[string]interface{}{
		"C1": 2.1,
		"C2": 2.2,
	}
	tags3 := map[string]string{
		"instance":   "I1",
		"objectname": "O",
		"source":     "cmp1",
	}
	fields4 := map[string]interface{}{
		"C1": 2.3,
		"C2": 2.4,
	}
	tags4 := map[string]string{
		"instance":   "I2",
		"objectname": "O",
		"source":     "cmp1",
	}
	acc.AssertContainsTaggedFields(t, "m1", fields3, tags3)
	acc.AssertContainsTaggedFields(t, "m1", fields4, tags4)
	fields5 := map[string]interface{}{
		"C1": 3.1,
		"C2": 3.2,
	}
	tags5 := map[string]string{
		"instance":   "I1",
		"objectname": "O",
		"source":     "cmp2",
	}
	fields6 := map[string]interface{}{
		"C1": 3.3,
		"C2": 3.4,
	}
	tags6 := map[string]string{
		"instance":   "I2",
		"objectname": "O",
		"source":     "cmp2",
	}
	acc.AssertContainsTaggedFields(t, "m2", fields5, tags5)
	acc.AssertContainsTaggedFields(t, "m2", fields6, tags6)
}

func TestGatherRaw(t *testing.T) {
	if testing.Short() {
		t.Skip("Skipping long taking test in short mode")
	}
	var err error
	measurement := "m"
	perfObjects := createPerfObject("", measurement, "O", []string{"*"}, []string{"C1", "C2"}, true, true, true)
	cps1 := []string{"\\O(I1)\\C1", "\\O(I1)\\C2", "\\O(_Total)\\C1", "\\O(_Total)\\C2"}
	m := WinPerfCounters{
		Log:                   testutil.Logger{},
		PrintValid:            false,
		UseWildcardsExpansion: false,
		Object:                perfObjects,
		queryCreator: &fakePerformanceQueryCreator{
			fakeQueries: map[string]*fakePerformanceQuery{"localhost": {
				counters: createCounterMap(
					append([]string{"\\O(*)\\C1", "\\O(*)\\C2"}, cps1...),
					[]float64{0, 0, 1.1, 2.2, 3.3, 4.4},
					[]uint32{0, 0, 0, 0, 0, 0}),
				expandPaths: map[string][]string{
					"\\O(*)\\C1": {cps1[0], cps1[2]},
					"\\O(*)\\C2": {cps1[1], cps1[3]},
				},
				vistaAndNewer: true,
			},
			},
		},
	}
	var acc1 testutil.Accumulator
	err = m.Gather(&acc1)
	require.NoError(t, err)
	counters, ok := m.hostCounters["localhost"]
	require.True(t, ok)
	require.Len(t, counters.counters, 2)
	require.Len(t, acc1.Metrics, 2)
	fields1 := map[string]interface{}{
		"C1_Raw": int64(1),
		"C2_Raw": int64(2),
	}
	tags1 := map[string]string{
		"instance":   "I1",
		"objectname": "O",
		"source":     hostname(),
	}
	acc1.AssertContainsTaggedFields(t, measurement, fields1, tags1)

	fields2 := map[string]interface{}{
		"C1_Raw": int64(3),
		"C2_Raw": int64(4),
	}
	tags2 := map[string]string{
		"instance":   "_Total",
		"objectname": "O",
		"source":     hostname(),
	}
	acc1.AssertContainsTaggedFields(t, measurement, fields2, tags2)

	m.UseWildcardsExpansion = true
	err = m.cleanQueries()
	require.NoError(t, err)
	m.lastRefreshed = time.Time{}

	var acc2 testutil.Accumulator
	err = m.Gather(&acc2)
	require.NoError(t, err)

	counters, ok = m.hostCounters["localhost"]
	require.True(t, ok)
	require.Len(t, counters.counters, 4) // expanded counters
	require.Len(t, acc2.Metrics, 2)

	acc2.AssertContainsTaggedFields(t, measurement, fields1, tags1)
	acc2.AssertContainsTaggedFields(t, measurement, fields2, tags2)
}

// list of nul terminated strings from WinAPI
var unicodeStringListWithEnglishChars = []uint16{0x5c, 0x5c, 0x54, 0x34, 0x38, 0x30, 0x5c, 0x50, 0x68, 0x79, 0x73, 0x69, 0x63, 0x61, 0x6c, 0x44, 0x69, 0x73,
	0x6b, 0x28, 0x30, 0x20, 0x43, 0x3a, 0x29, 0x5c, 0x43, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x74, 0x20, 0x44, 0x69, 0x73, 0x6b, 0x20, 0x51, 0x75, 0x65, 0x75,
	0x65, 0x20, 0x4c, 0x65, 0x6e, 0x67, 0x74, 0x68, 0x0, 0x5c, 0x5c, 0x54, 0x34, 0x38, 0x30, 0x5c, 0x50, 0x68, 0x79, 0x73, 0x69, 0x63, 0x61, 0x6c, 0x44,
	0x69, 0x73, 0x6b, 0x28, 0x5f, 0x54, 0x6f, 0x74, 0x61, 0x6c, 0x29, 0x5c, 0x43, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x74, 0x20, 0x44, 0x69, 0x73, 0x6b, 0x20,
	0x51, 0x75, 0x65, 0x75, 0x65, 0x20, 0x4c, 0x65, 0x6e, 0x67, 0x74, 0x68, 0x0, 0x0}
var unicodeStringListWithCzechChars = []uint16{0x5c, 0x5c, 0x54, 0x34, 0x38, 0x30, 0x5c, 0x46, 0x79, 0x7a, 0x69, 0x63, 0x6b, 0xfd, 0x20, 0x64, 0x69, 0x73,
	0x6b, 0x28, 0x30, 0x20, 0x43, 0x3a, 0x29, 0x5c, 0x41, 0x6b, 0x74, 0x75, 0xe1, 0x6c, 0x6e, 0xed, 0x20, 0x64, 0xe9, 0x6c, 0x6b, 0x61, 0x20, 0x66, 0x72,
	0x6f, 0x6e, 0x74, 0x79, 0x20, 0x64, 0x69, 0x73, 0x6b, 0x75, 0x0, 0x5c, 0x5c, 0x54, 0x34, 0x38, 0x30, 0x5c, 0x46, 0x79, 0x7a, 0x69, 0x63, 0x6b, 0xfd,
	0x20, 0x64, 0x69, 0x73, 0x6b, 0x28, 0x5f, 0x54, 0x6f, 0x74, 0x61, 0x6c, 0x29, 0x5c, 0x41, 0x6b, 0x74, 0x75, 0xe1, 0x6c, 0x6e, 0xed, 0x20, 0x64, 0xe9,
	0x6c, 0x6b, 0x61, 0x20, 0x66, 0x72, 0x6f, 0x6e, 0x74, 0x79, 0x20, 0x64, 0x69, 0x73, 0x6b, 0x75, 0x0, 0x0}
var unicodeStringListSingleItem = []uint16{0x5c, 0x5c, 0x54, 0x34, 0x38, 0x30, 0x5c, 0x50, 0x68, 0x79, 0x73, 0x69, 0x63, 0x61, 0x6c, 0x44, 0x69, 0x73, 0x6b,
	0x28, 0x30, 0x20, 0x43, 0x3a, 0x29, 0x5c, 0x43, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x74, 0x20, 0x44, 0x69, 0x73, 0x6b, 0x20, 0x51, 0x75, 0x65, 0x75, 0x65, 0x20,
	0x4c, 0x65, 0x6e, 0x67, 0x74, 0x68, 0x0, 0x0}
var unicodeStringListNoItem = []uint16{0x0}

var stringArrayWithEnglishChars = []string{
	"\\\\T480\\PhysicalDisk(0 C:)\\Current Disk Queue Length",
	"\\\\T480\\PhysicalDisk(_Total)\\Current Disk Queue Length",
}
var stringArrayWithCzechChars = []string{
	"\\\\T480\\Fyzick\u00fd disk(0 C:)\\Aktu\u00e1ln\u00ed d\u00e9lka fronty disku",
	"\\\\T480\\Fyzick\u00fd disk(_Total)\\Aktu\u00e1ln\u00ed d\u00e9lka fronty disku",
}

var stringArraySingleItem = []string{
	"\\\\T480\\PhysicalDisk(0 C:)\\Current Disk Queue Length",
}

func TestUTF16ToStringArray(t *testing.T) {
	singleItem := utf16ToStringArray(unicodeStringListSingleItem)
	require.Equal(t, singleItem, stringArraySingleItem, "Not equal single arrays")

	noItem := utf16ToStringArray(unicodeStringListNoItem)
	require.Nil(t, noItem)

	engStrings := utf16ToStringArray(unicodeStringListWithEnglishChars)
	require.Equal(t, engStrings, stringArrayWithEnglishChars, "Not equal eng arrays")

	czechStrings := utf16ToStringArray(unicodeStringListWithCzechChars)
	require.Equal(t, czechStrings, stringArrayWithCzechChars, "Not equal czech arrays")
}

func TestNoWildcards(t *testing.T) {
	m := WinPerfCounters{
		Object:                     createPerfObject("", "measurement", "object", []string{"instance"}, []string{"counter*"}, false, false, false),
		UseWildcardsExpansion:      true,
		LocalizeWildcardsExpansion: false,
		Log:                        testutil.Logger{},
	}
	require.Error(t, m.Init())
	m = WinPerfCounters{
		Object:                     createPerfObject("", "measurement", "object?", []string{"instance"}, []string{"counter"}, false, false, false),
		UseWildcardsExpansion:      true,
		LocalizeWildcardsExpansion: false,
		Log:                        testutil.Logger{},
	}
	require.Error(t, m.Init())
}

func TestLocalizeWildcardsExpansion(t *testing.T) {
	// this test is valid only on localized windows
	if testing.Short() {
		t.Skip("Skipping long taking test in short mode")
	}

	const counter = "% Processor Time"
	m := WinPerfCounters{
		queryCreator:            &performanceQueryCreatorImpl{},
		CountersRefreshInterval: config.Duration(time.Second * 60),
		Object: createPerfObject("", "measurement", "Processor Information",
			[]string{"_Total"}, []string{counter}, true, false, false),
		LocalizeWildcardsExpansion: false,
		UseWildcardsExpansion:      true,
		MaxBufferSize:              defaultMaxBufferSize,
		Log:                        testutil.Logger{},
	}

	require.NoError(t, m.Init())
	var acc testutil.Accumulator
	require.NoError(t, m.Gather(&acc))
	require.Len(t, acc.Metrics, 1)

	// running on localized windows with UseWildcardsExpansion and
	// with LocalizeWildcardsExpansion, this will be localized. Using LocalizeWildcardsExpansion=false it will
	// be English.
	require.Contains(t, acc.Metrics[0].Fields, sanitizedChars.Replace(counter))
}

func TestCheckError(t *testing.T) {
	tests := []struct {
		Name          string
		Err           error
		IgnoredErrors []string
		ExpectedErr   error
	}{
		{
			Name: "Ignore PDH_NO_DATA",
			Err: &pdhError{
				errorCode: uint32(pdhNoData),
			},
			IgnoredErrors: []string{
				"PDH_NO_DATA",
			},
			ExpectedErr: nil,
		},
		{
			Name: "Don't ignore PDH_NO_DATA",
			Err: &pdhError{
				errorCode: uint32(pdhNoData),
			},
			ExpectedErr: &pdhError{
				errorCode: uint32(pdhNoData),
			},
		},
	}

	for _, tc := range tests {
		t.Run(tc.Name, func(t *testing.T) {
			m := WinPerfCounters{
				IgnoredErrors: tc.IgnoredErrors,
			}

			err := m.checkError(tc.Err)
			require.Equal(t, tc.ExpectedErr, err)
		})
	}
}
