mirror of
https://github.com/prometheus-community/windows_exporter.git
synced 2026-10-17 10:09:05 +02:00
Co-authored-by: EisenbergD <dominik.eisenberg@beiersdorf.com> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: EisenbergD <dominik.eisenberg@beiersdorf.com>
296 lines
7.8 KiB
Go
296 lines
7.8 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 clusapi
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"testing"
|
|
"time"
|
|
|
|
"golang.org/x/sys/windows"
|
|
)
|
|
|
|
func TestControlBuffer(t *testing.T) {
|
|
calls := 0
|
|
|
|
data, err := controlBuffer(time.Time{}, 0, func(buffer []byte) (uint32, error) {
|
|
calls++
|
|
switch calls {
|
|
case 1:
|
|
if len(buffer) != 0 {
|
|
t.Fatal("first query must not supply buffer")
|
|
}
|
|
|
|
return 4, windows.ERROR_MORE_DATA
|
|
case 2:
|
|
if len(buffer) != 4 {
|
|
t.Fatalf("buffer = %d", len(buffer))
|
|
}
|
|
|
|
return 8, windows.ERROR_MORE_DATA
|
|
default:
|
|
if len(buffer) != 8 {
|
|
t.Fatalf("buffer = %d", len(buffer))
|
|
}
|
|
|
|
copy(buffer, []byte{1, 2, 3, 4})
|
|
|
|
return 4, nil
|
|
}
|
|
})
|
|
if err != nil || len(data) != 4 || data[3] != 4 || calls != 3 {
|
|
t.Fatalf("data = %v, calls = %d, err = %v", data, calls, err)
|
|
}
|
|
}
|
|
|
|
func TestControlBufferPresized(t *testing.T) {
|
|
calls := 0
|
|
|
|
data, err := controlBuffer(time.Time{}, 8, func(buffer []byte) (uint32, error) {
|
|
calls++
|
|
|
|
if len(buffer) != 8 {
|
|
t.Fatalf("buffer = %d", len(buffer))
|
|
}
|
|
|
|
copy(buffer, []byte{1, 2, 3, 4, 5, 6, 7, 8})
|
|
|
|
return 8, nil
|
|
})
|
|
if err != nil || calls != 1 || len(data) != 8 || data[7] != 8 {
|
|
t.Fatalf("data = %v, calls = %d, err = %v", data, calls, err)
|
|
}
|
|
|
|
calls = 0
|
|
|
|
data, err = controlBuffer(time.Time{}, 4, func(buffer []byte) (uint32, error) {
|
|
calls++
|
|
if calls == 1 {
|
|
return 6, windows.ERROR_MORE_DATA
|
|
}
|
|
|
|
if len(buffer) != 6 {
|
|
t.Fatalf("buffer = %d", len(buffer))
|
|
}
|
|
|
|
return 6, nil
|
|
})
|
|
if err != nil || calls != 2 || len(data) != 6 {
|
|
t.Fatalf("growth: data = %v, calls = %d, err = %v", data, calls, err)
|
|
}
|
|
|
|
// A pre-sized buffer that is already large enough must not be shrunk or retried.
|
|
_, err = controlBuffer(time.Time{}, 8, func([]byte) (uint32, error) { return 4, windows.ERROR_MORE_DATA })
|
|
if err == nil {
|
|
t.Fatal("accepted ERROR_MORE_DATA without growth")
|
|
}
|
|
}
|
|
|
|
func TestControlBufferInvalidSizes(t *testing.T) {
|
|
for _, tc := range []struct {
|
|
name string
|
|
size uint32
|
|
err error
|
|
}{
|
|
{"zero_more_data", 0, windows.ERROR_MORE_DATA},
|
|
{"oversized_more_data", maxBufferSize + 1, windows.ERROR_MORE_DATA},
|
|
{"oversized_success", maxBufferSize + 1, nil},
|
|
{"unrelated_error", 0, windows.ERROR_ACCESS_DENIED},
|
|
} {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
_, err := controlBuffer(time.Time{}, 0, func([]byte) (uint32, error) { return tc.size, tc.err })
|
|
if err == nil {
|
|
t.Fatal("accepted invalid native response")
|
|
}
|
|
|
|
if errors.Is(tc.err, windows.ERROR_ACCESS_DENIED) && !errors.Is(err, tc.err) {
|
|
t.Fatalf("lost native error: %v", err)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestControlBufferDeadline(t *testing.T) {
|
|
calls := 0
|
|
deadline := time.Now().Add(time.Millisecond)
|
|
|
|
_, err := controlBuffer(deadline, 0, func([]byte) (uint32, error) {
|
|
calls++
|
|
|
|
time.Sleep(2 * time.Millisecond)
|
|
|
|
return 4, windows.ERROR_MORE_DATA
|
|
})
|
|
if !errors.Is(err, context.DeadlineExceeded) || calls != 1 {
|
|
t.Fatalf("calls = %d, err = %v", calls, err)
|
|
}
|
|
|
|
_, err = controlBuffer(time.Now().Add(-time.Second), 0, func([]byte) (uint32, error) {
|
|
t.Fatal("started native call after deadline")
|
|
|
|
return 0, nil
|
|
})
|
|
if !errors.Is(err, context.DeadlineExceeded) {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
|
|
func TestClusterClosed(t *testing.T) {
|
|
c := &Cluster{}
|
|
if err := c.Close(); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
if err := c.Close(); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
if _, err := c.Resources(time.Time{}); err == nil {
|
|
t.Fatal("read closed cluster")
|
|
}
|
|
}
|
|
|
|
func TestStateBuffersGrowthAndBitPatterns(t *testing.T) {
|
|
calls := 0
|
|
|
|
state, node, group, err := stateBuffers(time.Time{}, func(node, group []uint16) (uint32, uint32, uint32, error) {
|
|
calls++
|
|
if calls == 1 {
|
|
return ^uint32(0), 300, 400, windows.ERROR_MORE_DATA
|
|
}
|
|
|
|
if len(node) != 301 || len(group) != 401 {
|
|
t.Fatalf("node/group lengths = %d/%d", len(node), len(group))
|
|
}
|
|
|
|
copy(node, []uint16{'N', 0})
|
|
copy(group, []uint16{'G', 0})
|
|
|
|
return 0x80000000, 1, 1, nil
|
|
})
|
|
if err != nil || state != 0x80000000 || node != "N" || group != "G" || calls != 2 {
|
|
t.Fatalf("state=%d node=%s group=%s calls=%d err=%v", state, node, group, calls, err)
|
|
}
|
|
}
|
|
|
|
// WMI published ClusterResourceStateUnknown with the other resource properties,
|
|
// so the state is a value, not a failure. The names may be left untouched.
|
|
func TestStateBuffersUnknownState(t *testing.T) {
|
|
state, node, group, err := stateBuffers(time.Time{}, func(node, group []uint16) (uint32, uint32, uint32, error) {
|
|
return stateUnknown, uint32(len(node)), uint32(len(group)), nil
|
|
})
|
|
if err != nil || state != stateUnknown || node != "" || group != "" {
|
|
t.Fatalf("untouched: state=%d node=%q group=%q err=%v", state, node, group, err)
|
|
}
|
|
|
|
state, node, group, err = stateBuffers(time.Time{}, func(node, group []uint16) (uint32, uint32, uint32, error) {
|
|
copy(node, []uint16{'N', 0})
|
|
copy(group, []uint16{'G', 0})
|
|
|
|
return stateUnknown, 1, 1, nil
|
|
})
|
|
if err != nil || state != stateUnknown || node != "N" || group != "G" {
|
|
t.Fatalf("filled: state=%d node=%q group=%q err=%v", state, node, group, err)
|
|
}
|
|
}
|
|
|
|
func TestStateBuffersInvalidNativeResponses(t *testing.T) {
|
|
for _, tc := range []struct {
|
|
name string
|
|
state uint32
|
|
nodeSize uint32
|
|
groupSize uint32
|
|
err error
|
|
}{
|
|
{"more_data_without_growth", ^uint32(0), 1, 1, windows.ERROR_MORE_DATA},
|
|
{"oversized_more_data", ^uint32(0), maxBufferSize, 0, windows.ERROR_MORE_DATA},
|
|
{"oversized_success", 2, 256, 0, nil},
|
|
} {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
_, _, _, err := stateBuffers(time.Time{}, func([]uint16, []uint16) (uint32, uint32, uint32, error) {
|
|
return tc.state, tc.nodeSize, tc.groupSize, tc.err
|
|
})
|
|
if err == nil {
|
|
t.Fatal("accepted invalid native response")
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestControlBufferSuccessfulSizeProbe(t *testing.T) {
|
|
calls := 0
|
|
|
|
data, err := controlBuffer(time.Time{}, 0, func(buffer []byte) (uint32, error) {
|
|
calls++
|
|
if calls == 1 {
|
|
return 4, nil
|
|
}
|
|
|
|
copy(buffer, []byte{1, 2, 3, 4})
|
|
|
|
return 4, nil
|
|
})
|
|
if err != nil || calls != 2 || len(data) != 4 || data[3] != 4 {
|
|
t.Fatalf("successful probe: data=%v calls=%d err=%v", data, calls, err)
|
|
}
|
|
}
|
|
|
|
func TestUnopenedClusterExpiredBudget(t *testing.T) {
|
|
c := &Cluster{}
|
|
if _, err := c.Resources(time.Now().Add(-time.Second)); !errors.Is(err, context.DeadlineExceeded) {
|
|
t.Fatalf("expired budget: %v", err)
|
|
}
|
|
|
|
if c.handle != 0 || c.closed {
|
|
t.Fatal("expired scrape changed unopened cluster lifecycle")
|
|
}
|
|
|
|
if err := c.Close(); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
if !c.closed {
|
|
t.Fatal("unopened cluster was not closed")
|
|
}
|
|
}
|
|
|
|
func TestParseResourceClassInfo(t *testing.T) {
|
|
for _, test := range []struct {
|
|
name string
|
|
data []byte
|
|
class uint32
|
|
subclass uint32
|
|
}{
|
|
{"complete", []byte{1, 0, 0, 0, 0, 0, 0, 0x80}, 1, 0x80000000},
|
|
// Captured from the Storage Spaces Direct Health Service resource: only
|
|
// the class DWORD, without the subclass. WMI reports ResourceClass 0.
|
|
{"health service", []byte{0, 0, 0, 0}, 0, 0},
|
|
{"empty", nil, 0, 0},
|
|
{"class only", []byte{2, 0, 0, 0}, 2, 0},
|
|
{"truncated class", []byte{1, 0}, 0, 0},
|
|
} {
|
|
t.Run(test.name, func(t *testing.T) {
|
|
class, subclass := parseResourceClassInfo(test.data)
|
|
if class != test.class || subclass != test.subclass {
|
|
t.Fatalf("class=%#x subclass=%#x, want %#x/%#x", class, subclass, test.class, test.subclass)
|
|
}
|
|
})
|
|
}
|
|
}
|