Skip to content

Commit e4e2cd9

Browse files
committed
pkg/config/configtest: move package cfgtest from chainlink/v2
1 parent 46f0d8f commit e4e2cd9

File tree

2 files changed

+196
-0
lines changed

2 files changed

+196
-0
lines changed

pkg/config/configtest/configtest.go

+109
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
package configtest
2+
3+
import (
4+
"encoding"
5+
"fmt"
6+
"reflect"
7+
"testing"
8+
9+
"github.com/stretchr/testify/assert"
10+
"github.com/stretchr/testify/require"
11+
"go.uber.org/multierr"
12+
13+
"github.com/smartcontractkit/chainlink-common/pkg/config"
14+
)
15+
16+
func AssertFieldsNotNil(t *testing.T, s interface{}) {
17+
err := assertValNotNil(t, "", reflect.ValueOf(s))
18+
_, err = config.MultiErrorList(err)
19+
assert.NoError(t, err)
20+
}
21+
22+
// assertFieldsNotNil recursively checks the struct s for nil fields.
23+
func assertFieldsNotNil(t *testing.T, prefix string, s reflect.Value) (err error) {
24+
t.Helper()
25+
require.Equal(t, reflect.Struct, s.Kind())
26+
27+
typ := s.Type()
28+
for i := 0; i < s.NumField(); i++ {
29+
f := s.Field(i)
30+
key := prefix
31+
if tf := typ.Field(i); !tf.Anonymous {
32+
if key != "" {
33+
key += "."
34+
}
35+
key += tf.Name
36+
}
37+
err = multierr.Combine(err, assertValNotNil(t, key, f))
38+
}
39+
return
40+
}
41+
42+
// assertValuesNotNil recursively checks the map m for nil values.
43+
func assertValuesNotNil(t *testing.T, prefix string, m reflect.Value) (err error) {
44+
t.Helper()
45+
require.Equal(t, reflect.Map, m.Kind())
46+
if prefix != "" {
47+
prefix += "."
48+
}
49+
50+
mi := m.MapRange()
51+
for mi.Next() {
52+
key := prefix + mi.Key().String()
53+
err = multierr.Combine(err, assertValNotNil(t, key, mi.Value()))
54+
}
55+
return
56+
}
57+
58+
// assertElementsNotNil recursively checks the slice s for nil values.
59+
func assertElementsNotNil(t *testing.T, prefix string, s reflect.Value) (err error) {
60+
t.Helper()
61+
require.Equal(t, reflect.Slice, s.Kind())
62+
63+
for i := 0; i < s.Len(); i++ {
64+
err = multierr.Combine(err, assertValNotNil(t, prefix, s.Index(i)))
65+
}
66+
return
67+
}
68+
69+
var (
70+
textUnmarshaler encoding.TextUnmarshaler
71+
textUnmarshalerType = reflect.TypeOf(&textUnmarshaler).Elem()
72+
)
73+
74+
// assertValNotNil recursively checks that val is not nil. val must be a struct, map, slice, or point to one.
75+
func assertValNotNil(t *testing.T, key string, val reflect.Value) error {
76+
t.Helper()
77+
k := val.Kind()
78+
switch k { //nolint:exhaustive
79+
case reflect.Ptr:
80+
if val.IsNil() {
81+
return fmt.Errorf("%s: nil", key)
82+
}
83+
}
84+
if k == reflect.Ptr {
85+
if val.Type().Implements(textUnmarshalerType) {
86+
return nil // skip values unmarshaled from strings
87+
}
88+
val = val.Elem()
89+
}
90+
switch val.Kind() {
91+
case reflect.Struct:
92+
if val.Type().Implements(textUnmarshalerType) {
93+
return nil // skip values unmarshaled from strings
94+
}
95+
return assertFieldsNotNil(t, key, val)
96+
case reflect.Map:
97+
if val.IsNil() {
98+
return nil // not actually a problem
99+
}
100+
return assertValuesNotNil(t, key, val)
101+
case reflect.Slice:
102+
if val.IsNil() {
103+
return nil // not actually a problem
104+
}
105+
return assertElementsNotNil(t, key, val)
106+
default:
107+
return nil
108+
}
109+
}

pkg/config/configtest/defaults.go

+87
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
package configtest
2+
3+
import (
4+
"bufio"
5+
"fmt"
6+
"io"
7+
"reflect"
8+
"strings"
9+
10+
"github.com/pkg/errors"
11+
)
12+
13+
// DocDefaultsOnly reads only the default values from a docs TOML file and decodes in to cfg.
14+
// Fields without defaults will set to zero values.
15+
func DocDefaultsOnly(r io.Reader, cfg any, decode func(io.Reader, any) error) error {
16+
pr, pw := io.Pipe()
17+
defer pr.Close()
18+
go writeDefaults(r, pw)
19+
if err := decode(pr, cfg); err != nil {
20+
return errors.Wrapf(err, "failed to decode default core configuration")
21+
}
22+
// replace niled examples with zero values.
23+
nilToZero(reflect.ValueOf(cfg))
24+
return nil
25+
}
26+
27+
// writeDefaults writes default lines from defaultsTOML to w.
28+
func writeDefaults(r io.Reader, w *io.PipeWriter) {
29+
defer w.Close()
30+
s := bufio.NewScanner(r)
31+
for s.Scan() {
32+
t := s.Text()
33+
// Skip comments and examples (which become zero values)
34+
if strings.HasPrefix(t, "#") || strings.HasSuffix(t, "# Example") {
35+
continue
36+
}
37+
if _, err := io.WriteString(w, t); err != nil {
38+
w.CloseWithError(err)
39+
}
40+
if _, err := w.Write([]byte{'\n'}); err != nil {
41+
w.CloseWithError(err)
42+
}
43+
}
44+
if err := s.Err(); err != nil {
45+
w.CloseWithError(fmt.Errorf("failed to scan core defaults: %w", err))
46+
}
47+
}
48+
49+
func nilToZero(val reflect.Value) {
50+
if val.Kind() == reflect.Ptr {
51+
if val.IsNil() {
52+
t := val.Type().Elem()
53+
val.Set(reflect.New(t))
54+
}
55+
if val.Type().Implements(textUnmarshalerType) {
56+
return // don't descend inside - leave whole zero value
57+
}
58+
val = val.Elem()
59+
}
60+
switch val.Kind() {
61+
case reflect.Struct:
62+
if val.Type().Implements(textUnmarshalerType) {
63+
return // skip values unmarshaled from strings
64+
}
65+
for i := 0; i < val.NumField(); i++ {
66+
f := val.Field(i)
67+
nilToZero(f)
68+
}
69+
return
70+
case reflect.Map:
71+
if !val.IsNil() {
72+
for _, k := range val.MapKeys() {
73+
nilToZero(val.MapIndex(k))
74+
}
75+
}
76+
return
77+
case reflect.Slice:
78+
if !val.IsNil() {
79+
for i := 0; i < val.Len(); i++ {
80+
nilToZero(val.Index(i))
81+
}
82+
}
83+
return
84+
default:
85+
return
86+
}
87+
}

0 commit comments

Comments
 (0)