buildah/internal/config/convert_test.go

169 lines
6.0 KiB
Go
Raw Normal View History

package config
import (
"reflect"
"slices"
"strconv"
"testing"
dockerclient "github.com/fsouza/go-dockerclient"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.podman.io/image/v5/manifest"
)
// fillAllFields recursively fills in 1 or "1" for every field in the passed-in
// structure, and that slices and maps have at least one value in them.
func fillAllFields[pStruct any](t *testing.T, st pStruct) {
v := reflect.ValueOf(st)
if v.Kind() == reflect.Pointer {
v = reflect.Indirect(v)
}
fillAllValueFields(t, v)
}
func fillAllValueFields(t *testing.T, v reflect.Value) {
fields := reflect.VisibleFields(v.Type())
for _, field := range fields {
if field.Anonymous {
// all right, fine, keep your secrets
continue
}
f := v.FieldByName(field.Name)
var keyType, elemType reflect.Type
if field.Type.Kind() == reflect.Map {
keyType = field.Type.Key()
}
switch field.Type.Kind() {
case reflect.Array, reflect.Chan, reflect.Map, reflect.Pointer, reflect.Slice:
elemType = field.Type.Elem()
}
fillValue(t, f, field.Name, field.Type.Kind(), keyType, elemType)
}
}
func fillValue(t *testing.T, value reflect.Value, name string, kind reflect.Kind, keyType, elemType reflect.Type) {
switch kind {
case reflect.Invalid,
reflect.Array, reflect.Chan, reflect.Func, reflect.Interface, reflect.UnsafePointer,
reflect.Float32, reflect.Float64,
reflect.Complex64, reflect.Complex128:
require.NotEqualf(t, kind, kind, "unhandled %s field %s: tests require updating", kind, name)
case reflect.Bool:
value.SetBool(true)
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
value.SetInt(1)
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
value.SetUint(1)
case reflect.Map:
if value.IsNil() {
value.Set(reflect.MakeMap(value.Type()))
}
keyPtr := reflect.New(keyType)
key := reflect.Indirect(keyPtr)
fillValue(t, key, name, keyType.Kind(), nil, nil)
elemPtr := reflect.New(elemType)
elem := reflect.Indirect(elemPtr)
fillValue(t, elem, name, elemType.Kind(), nil, nil)
value.SetMapIndex(key, reflect.Indirect(elem))
case reflect.Slice:
vPtr := reflect.New(elemType)
v := reflect.Indirect(vPtr)
fillValue(t, v, name, elemType.Kind(), nil, nil)
value.Set(reflect.Append(reflect.MakeSlice(value.Type(), 0, 1), v))
case reflect.String:
value.SetString("1")
case reflect.Struct:
fillAllValueFields(t, value)
case reflect.Pointer:
p := reflect.New(elemType)
fillValue(t, reflect.Indirect(p), name, elemType.Kind(), nil, nil)
value.Set(p)
}
}
// checkAllFields recursively checks that every field not listed in allowZeroed
// is not set to its zero value, that every slice is not empty, and that every
// map has at least one entry. It makes an additional exception for structs
// which have no defined fields.
func checkAllFields[pStruct any](t *testing.T, st pStruct, allowZeroed []string) {
v := reflect.ValueOf(st)
if v.Kind() == reflect.Pointer {
v = reflect.Indirect(v)
}
checkAllValueFields(t, v, "", allowZeroed)
}
func checkAllValueFields(t *testing.T, v reflect.Value, name string, allowedToBeZero []string) {
fields := reflect.VisibleFields(v.Type())
for _, field := range fields {
if field.Anonymous {
// all right, fine, keep your secrets
continue
}
fieldName := field.Name
if name != "" {
fieldName = name + "." + field.Name
}
if slices.Contains(allowedToBeZero, fieldName) {
continue
}
f := v.FieldByName(field.Name)
var elemType reflect.Type
switch field.Type.Kind() {
case reflect.Array, reflect.Chan, reflect.Map, reflect.Pointer, reflect.Slice:
elemType = field.Type.Elem()
}
checkValue(t, f, fieldName, field.Type.Kind(), elemType, allowedToBeZero)
}
}
func checkValue(t *testing.T, value reflect.Value, name string, kind reflect.Kind, elemType reflect.Type, allowedToBeZero []string) {
if kind != reflect.Invalid {
switch kind {
case reflect.Map:
assert.Falsef(t, value.IsZero(), "map field %s not set when it was not already expected to be left unpopulated by conversion", name)
keys := value.MapKeys()
for i := range len(keys) {
v := value.MapIndex(keys[i])
checkValue(t, v, name+"{"+keys[i].String()+"}", elemType.Kind(), nil, allowedToBeZero)
}
case reflect.Slice:
assert.Falsef(t, value.IsZero(), "slice field %s not set when it was not already expected to be left unpopulated by conversion", name)
for i := range value.Len() {
v := value.Index(i)
checkValue(t, v, name+"["+strconv.Itoa(i)+"]", elemType.Kind(), nil, allowedToBeZero)
}
case reflect.Struct:
if fields := reflect.VisibleFields(value.Type()); len(fields) != 0 {
// structs which are defined with no fields are okay
assert.Falsef(t, value.IsZero(), "slice field %s not set when it was not already expected to be left unpopulated by conversion", name)
}
checkAllValueFields(t, value, name, allowedToBeZero)
case reflect.Pointer:
assert.Falsef(t, value.IsZero(), "pointer field %s not set when it was not already expected to be left unpopulated by conversion", name)
checkValue(t, reflect.Indirect(value), name, elemType.Kind(), nil, allowedToBeZero)
}
}
}
func TestGoDockerclientConfigFromSchema2Config(t *testing.T) {
t.Parallel()
var input manifest.Schema2Config
fillAllFields(t, &input)
output := GoDockerclientConfigFromSchema2Config(&input)
// make exceptions for fields in "output" which have no corresponding field in "input"
notInSchema2Config := []string{"CPUSet", "CPUShares", "DNS", "Memory", "KernelMemory", "MemorySwap", "MemoryReservation", "Mounts", "PortSpecs", "PublishService", "SecurityOpts", "VolumeDriver", "VolumesFrom"}
checkAllFields(t, output, notInSchema2Config)
}
func TestSchema2ConfigFromGoDockerclientConfig(t *testing.T) {
t.Parallel()
var input dockerclient.Config
fillAllFields(t, &input)
output := Schema2ConfigFromGoDockerclientConfig(&input)
// make exceptions for fields in "output" which have no corresponding field in "input"
notInDockerConfig := []string{}
checkAllFields(t, output, notInDockerConfig)
}