Merge pull request #5505 from kolyshkin/kir/strict-spec

tests/int: check config.json keys in update_config
This commit is contained in:
Kir Kolyshkin authored and GitHub committed 2026-10-23 20:32:21 +00:00
commit 7f0569a822
3 files changed
+113 -1

No files matched your search

+1 -1
View File
@@ -90,7 +90,7 @@ TESTBINDIR := tests/cmd/_bin
$(TESTBINDIR):
mkdir $(TESTBINDIR)
TESTBINS := recvtty sd-helper seccompagent fs-idmap pidfd-kill remap-rootfs key_label
TESTBINS := recvtty sd-helper seccompagent fs-idmap pidfd-kill remap-rootfs key_label check-config
.PHONY: test-binaries $(TESTBINS)
test-binaries: $(TESTBINS)
$(TESTBINS): $(TESTBINDIR)
+110
View File
@@ -0,0 +1,110 @@
// check-config checks that config.json only has the keys known to
// runtime-spec, spelled exactly as in the spec.
//
// This is needed because Go's encoding/json (v1) matches keys
// case-insensitively, and silently ignores unknown keys, so a wrong key
// in a test goes unnoticed by runc (but not by other runtimes).
package main
import (
"encoding/json"
"fmt"
"maps"
"os"
"reflect"
"slices"
"strconv"
"strings"
"github.com/opencontainers/runtime-spec/specs-go"
)
func main() {
if len(os.Args) != 2 {
fmt.Fprintf(os.Stderr, "usage: %s config.json\n", os.Args[0])
os.Exit(2)
}
data, err := os.ReadFile(os.Args[1])
if err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
var v any
if err := json.Unmarshal(data, &v); err != nil {
fmt.Fprintf(os.Stderr, "%s: %v\n", os.Args[1], err)
os.Exit(1)
}
errs := check(v, reflect.TypeFor[specs.Spec](), "")
for _, e := range errs {
fmt.Fprintf(os.Stderr, "%s: %s\n", os.Args[1], e)
}
if len(errs) > 0 {
os.Exit(1)
}
}
func check(v any, t reflect.Type, path string) (errs []string) {
for t.Kind() == reflect.Pointer {
t = t.Elem()
}
switch t.Kind() {
case reflect.Struct:
m, ok := v.(map[string]any)
if !ok {
return nil
}
fields := jsonFields(t)
for _, k := range slices.Sorted(maps.Keys(m)) {
ft, ok := fields[k]
if !ok {
errs = append(errs, unknownKey(path, k, fields))
continue
}
errs = append(errs, check(m[k], ft, path+"/"+k)...)
}
case reflect.Slice, reflect.Array:
s, _ := v.([]any)
for i, e := range s {
errs = append(errs, check(e, t.Elem(), path+"/"+strconv.Itoa(i))...)
}
case reflect.Map:
m, _ := v.(map[string]any)
for _, k := range slices.Sorted(maps.Keys(m)) {
errs = append(errs, check(m[k], t.Elem(), path+"/"+k)...)
}
}
return errs
}
// jsonFields returns a map of JSON keys to field types for a struct type.
func jsonFields(t reflect.Type) map[string]reflect.Type {
fields := make(map[string]reflect.Type)
for f := range t.Fields() {
name, _, _ := strings.Cut(f.Tag.Get("json"), ",")
if name == "-" {
continue
}
// Fields of an embedded struct with no tag are promoted.
if f.Anonymous && name == "" && f.Type.Kind() == reflect.Struct {
maps.Copy(fields, jsonFields(f.Type))
continue
}
if !f.IsExported() {
continue
}
if name == "" {
name = f.Name
}
fields[name] = f.Type
}
return fields
}
func unknownKey(path, key string, fields map[string]reflect.Type) string {
for name := range fields {
if strings.EqualFold(name, key) {
return fmt.Sprintf("%s/%s: wrong case, should be %q", path, key, name)
}
}
return fmt.Sprintf("%s/%s: unknown key", path, key)
}
+2
View File
@@ -82,6 +82,8 @@ function update_config() {
local config
config=$(jq "$@" "./config.json") || return
echo "$config" >"./config.json"
# Make sure the test does not use wrong or unknown keys.
"$TESTBINDIR/check-config" "./config.json"
}
# Shortcut to add additional uids and gids, based on the values set as part of