Files
windows_exporter/internal/mi/unmarshal_test.go
T
2026-10-10 16:40:12 +02:00

167 lines
6.4 KiB
Go

// SPDX-License-Identifier: Apache-2.0
//
// Copyright The Prometheus Authors
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//go:build windows
package mi
import (
"math"
"reflect"
"testing"
"github.com/stretchr/testify/require"
"golang.org/x/sys/windows"
)
// newField returns a settable reflect.Value holding the zero value of T.
func newField[T any]() reflect.Value {
return reflect.New(reflect.TypeFor[T]()).Elem()
}
func TestSetField(t *testing.T) {
t.Parallel()
tests := []struct {
name string
element Element
field reflect.Value
want any
wantErr bool
}{
{"boolean", Element{value: 1, valueType: ValueTypeBOOLEAN}, newField[bool](), true, false},
{"boolean_into_int", Element{value: 1, valueType: ValueTypeBOOLEAN}, newField[int](), nil, true},
{"uint32_into_uint64", Element{value: math.MaxUint32, valueType: ValueTypeUINT32}, newField[uint64](), uint64(math.MaxUint32), false},
{"uint64_overflows_uint32", Element{value: math.MaxUint32 + 1, valueType: ValueTypeUINT64}, newField[uint32](), nil, true},
{"uint32_into_string", Element{value: 1, valueType: ValueTypeUINT32}, newField[string](), nil, true},
{"sint8_sign_extends", Element{value: 0xFF, valueType: ValueTypeSINT8}, newField[int64](), int64(-1), false},
{"sint16_sign_extends", Element{value: 0xFFFF, valueType: ValueTypeSINT16}, newField[int64](), int64(-1), false},
{"sint32_sign_extends", Element{value: 0xFFFFFFFF, valueType: ValueTypeSINT32}, newField[int64](), int64(-1), false},
{"sint16_positive_into_uint16", Element{value: 42, valueType: ValueTypeSINT16}, newField[uint16](), uint16(42), false},
{"sint16_negative_into_uint16", Element{value: 0xFFFF, valueType: ValueTypeSINT16}, newField[uint16](), nil, true},
{"sint32_overflows_int8", Element{value: 1000, valueType: ValueTypeSINT32}, newField[int8](), nil, true},
{"real32_into_float32", Element{value: uintptr(math.Float32bits(1.5)), valueType: ValueTypeREAL32}, newField[float32](), float32(1.5), false},
{"real32_into_float64", Element{value: uintptr(math.Float32bits(-2.25)), valueType: ValueTypeREAL32}, newField[float64](), float64(-2.25), false},
{"real64_into_float64", Element{value: uintptr(math.Float64bits(9.875)), valueType: ValueTypeREAL64}, newField[float64](), float64(9.875), false},
{"real64_into_float32", Element{value: uintptr(math.Float64bits(1)), valueType: ValueTypeREAL64}, newField[float32](), nil, true},
{"string_null_keeps_zero_value", Element{value: 0, valueType: ValueTypeSTRING}, newField[string](), "", false},
{"string_into_int", Element{value: 0, valueType: ValueTypeSTRING}, newField[int](), nil, true},
{"char16_into_string", Element{value: 'A', valueType: ValueTypeCHAR16}, newField[string](), "A", false},
{"char16_euro_into_string", Element{value: 0x20AC, valueType: ValueTypeCHAR16}, newField[string](), "€", false},
{"char16_surrogate_into_string", Element{value: 0xD83D, valueType: ValueTypeCHAR16}, newField[string](), "�", false},
{"char16_into_uint16", Element{value: 0xD83D, valueType: ValueTypeCHAR16}, newField[uint16](), uint16(0xD83D), false},
{"char16_overflows_uint8", Element{value: 0x20AC, valueType: ValueTypeCHAR16}, newField[uint8](), nil, true},
{"char16_into_bool", Element{value: 'A', valueType: ValueTypeCHAR16}, newField[bool](), nil, true},
{"null_uint32_keeps_zero_value", Element{value: 42, valueType: ValueTypeUINT32, flags: flagNull}, newField[uint32](), uint32(0), false},
{"null_real64_keeps_zero_value", Element{value: uintptr(math.Float64bits(9.875)), valueType: ValueTypeREAL64, flags: flagNull}, newField[float64](), float64(0), false},
{"null_char16_into_string_keeps_zero_value", Element{value: 'A', valueType: ValueTypeCHAR16, flags: flagNull}, newField[string](), "", false},
{"null_still_rejects_wrong_type", Element{value: 42, valueType: ValueTypeUINT32, flags: flagNull}, newField[string](), nil, true},
{"uint16a_into_wrong_slice", Element{value: 0, valueType: ValueTypeUINT16A}, newField[[]uint32](), nil, true},
{"unsupported_type", Element{value: 0, valueType: ValueTypeDATETIME}, newField[string](), nil, true},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
err := setField("Val", tt.field, &tt.element)
if tt.wantErr {
require.Error(t, err)
return
}
require.NoError(t, err)
require.Equal(t, tt.want, tt.field.Interface())
})
}
}
func TestPrepareUnmarshal(t *testing.T) {
t.Parallel()
type row struct {
Name string `mi:"Name"`
Internal int
ID uint32 `mi:"ProcessId"`
}
t.Run("resets_valid_slice", func(t *testing.T) {
t.Parallel()
dst := []row{{Name: "stale"}}
fields, err := prepareUnmarshal(&dst)
require.NoError(t, err)
require.Empty(t, dst)
require.Len(t, fields, 2)
require.Equal(t, 0, fields[0].index)
require.Equal(t, "Name", fields[0].tag)
require.Equal(t, "Name", windows.UTF16PtrToString(fields[0].name))
require.Equal(t, 2, fields[1].index)
require.Equal(t, "ProcessId", fields[1].tag)
require.Equal(t, "ProcessId", windows.UTF16PtrToString(fields[1].name))
})
t.Run("fields_are_cached", func(t *testing.T) {
t.Parallel()
first, err := prepareUnmarshal(new([]row))
require.NoError(t, err)
second, err := prepareUnmarshal(new([]row))
require.NoError(t, err)
require.Same(t, &first[0], &second[0])
})
t.Run("tag_with_nul", func(t *testing.T) {
t.Parallel()
_, err := prepareUnmarshal(new([]struct {
Name string `mi:"Na\x00me"`
}))
require.Error(t, err)
})
t.Run("nil_pointer", func(t *testing.T) {
t.Parallel()
_, err := prepareUnmarshal((*[]row)(nil))
require.ErrorIs(t, err, ErrInvalidEntityType)
})
t.Run("slice_of_int", func(t *testing.T) {
t.Parallel()
_, err := prepareUnmarshal(new([]int))
require.ErrorIs(t, err, ErrInvalidEntityType)
})
t.Run("slice_of_struct_pointer", func(t *testing.T) {
t.Parallel()
_, err := prepareUnmarshal(new([]*row))
require.ErrorIs(t, err, ErrInvalidEntityType)
})
}