mirror of
https://github.com/prometheus-community/windows_exporter.git
synced 2026-10-26 13:39:03 +01:00
fix: registry, update, file duplicate series, config key and error reporting (#2509)
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Jan-Otto Kröpke <mail@jkroepke.de>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
7b20daa9ac
commit
71b4ba80ed
@@ -20,11 +20,18 @@ package config_test
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"regexp"
|
||||
"slices"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/alecthomas/kingpin/v2"
|
||||
"github.com/prometheus-community/windows_exporter/internal/config"
|
||||
"github.com/prometheus-community/windows_exporter/pkg/collector"
|
||||
"github.com/stretchr/testify/require"
|
||||
"go.yaml.in/yaml/v3"
|
||||
)
|
||||
|
||||
func TestParseConfigFile(t *testing.T) {
|
||||
@@ -91,6 +98,105 @@ func TestNewConfigFileResolver(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
// TestNewConfigFileResolverCollectorFlags verifies that every collector flag can
|
||||
// be set from the configuration file: the nested key derived from the flag name
|
||||
// must exist in collector.Config, pass the strict validation, and be bound back
|
||||
// to the same flag.
|
||||
func TestNewConfigFileResolverCollectorFlags(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
app := kingpin.New("test", "test")
|
||||
collector.NewWithFlags(app)
|
||||
|
||||
for _, flag := range app.Model().Flags {
|
||||
// collector.exchange.list prints the available sub-collectors and exits;
|
||||
// it is not a configuration option.
|
||||
if !strings.HasPrefix(flag.Name, "collector.") || flag.Name == "collector.exchange.list" {
|
||||
continue
|
||||
}
|
||||
|
||||
t.Run(flag.Name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
keys := strings.Split(flag.Name, ".")
|
||||
|
||||
field, ok := configFieldType(reflect.TypeFor[collector.Config](), keys[1:])
|
||||
require.True(t, ok, "collector.Config has no field with the yaml path %s", flag.Name)
|
||||
|
||||
value, want := sampleConfigValue(t, field)
|
||||
|
||||
contents := value
|
||||
for _, key := range slices.Backward(keys) {
|
||||
contents = map[string]any{key: contents}
|
||||
}
|
||||
|
||||
data, err := yaml.Marshal(contents)
|
||||
require.NoError(t, err)
|
||||
|
||||
path := filepath.Join(t.TempDir(), "config.yaml")
|
||||
require.NoError(t, os.WriteFile(path, data, 0o600))
|
||||
|
||||
resolver, err := config.NewConfigFileResolver(path)
|
||||
require.NoError(t, err, "config:\n%s", data)
|
||||
|
||||
bindApp := kingpin.New("test", "test")
|
||||
collector.NewWithFlags(bindApp)
|
||||
|
||||
require.NoError(t, resolver.Bind(bindApp, nil))
|
||||
require.Equal(t, []string{want}, bindApp.GetFlag(flag.Name).Model().Default)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// configFieldType returns the type of the struct field reached by following
|
||||
// the yaml tags in keys, starting at typ.
|
||||
func configFieldType(typ reflect.Type, keys []string) (reflect.Type, bool) {
|
||||
if len(keys) == 0 {
|
||||
return typ, true
|
||||
}
|
||||
|
||||
if typ.Kind() != reflect.Struct {
|
||||
return nil, false
|
||||
}
|
||||
|
||||
for field := range typ.Fields() {
|
||||
if name, _, _ := strings.Cut(field.Tag.Get("yaml"), ","); name == keys[0] {
|
||||
return configFieldType(field.Type, keys[1:])
|
||||
}
|
||||
}
|
||||
|
||||
return nil, false
|
||||
}
|
||||
|
||||
// sampleConfigValue returns a configuration file value that decodes into typ,
|
||||
// together with the flag value the resolver flattens it to.
|
||||
func sampleConfigValue(t *testing.T, typ reflect.Type) (any, string) {
|
||||
t.Helper()
|
||||
|
||||
switch {
|
||||
case typ == reflect.TypeFor[time.Duration]():
|
||||
return "1m", "1m"
|
||||
case typ == reflect.TypeFor[*regexp.Regexp]():
|
||||
return "test", "test"
|
||||
}
|
||||
|
||||
switch typ.Kind() {
|
||||
case reflect.Bool:
|
||||
return true, "true"
|
||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64,
|
||||
reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
|
||||
return 1, "1"
|
||||
case reflect.Slice:
|
||||
return []string{"test"}, "test"
|
||||
case reflect.String:
|
||||
return "test", "test"
|
||||
default:
|
||||
t.Fatalf("no sample configuration value for type %s", typ)
|
||||
|
||||
return nil, ""
|
||||
}
|
||||
}
|
||||
|
||||
func TestParse(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
|
||||
Reference in New Issue
Block a user