mirror of
https://github.com/johnkerl/miller.git
synced 2026-07-17 16:38:54 +00:00
* fix: preserve key order in YAML output
MlrmapToYAMLNative converted the ordered Mlrmap to map[string]interface{},
which loses insertion order. yaml.v3.Marshal then sorts Go map keys
alphabetically, so YAML output always had sorted keys despite other formats
(JSON, pretty-print) correctly preserving order.
Switch to building yaml.Node trees that preserve the Mlrmap's linked-list
iteration order. Use yaml.Node.Encode for scalar values to correctly handle
edge cases (NaN/Inf, non-UTF-8 strings).
Fixes #2028.
* Update pkg/mlrval/mlrval_yaml_test.go with copilot code-review feedback
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
---------
Co-authored-by: lawrence3699 <lawrence3699@users.noreply.github.com>
Co-authored-by: John Kerl <kerl.john.r@gmail.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
226 lines
5.8 KiB
Go
226 lines
5.8 KiB
Go
// Tests for YAML decode/encode (MlrvalDecodeFromYAML, MlrmapToYAMLNative).
|
|
|
|
package mlrval
|
|
|
|
import (
|
|
"bytes"
|
|
"strings"
|
|
"testing"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
|
|
"gopkg.in/yaml.v3"
|
|
)
|
|
|
|
func TestMlrvalFromYAMLNativeScalars(t *testing.T) {
|
|
mv, err := mlrvalFromYAMLNative("hello")
|
|
assert.NoError(t, err)
|
|
assert.True(t, mv.IsString())
|
|
s, _ := mv.GetStringValue()
|
|
assert.Equal(t, "hello", s)
|
|
|
|
mv, err = mlrvalFromYAMLNative(42)
|
|
assert.NoError(t, err)
|
|
assert.True(t, mv.IsInt())
|
|
i, _ := mv.GetIntValue()
|
|
assert.Equal(t, int64(42), i)
|
|
|
|
mv, err = mlrvalFromYAMLNative(3.14)
|
|
assert.NoError(t, err)
|
|
assert.True(t, mv.IsFloat())
|
|
f, _ := mv.GetFloatValue()
|
|
assert.Equal(t, 3.14, f)
|
|
|
|
mv, err = mlrvalFromYAMLNative(true)
|
|
assert.NoError(t, err)
|
|
assert.True(t, mv.IsBool())
|
|
b, _ := mv.GetBoolValue()
|
|
assert.True(t, b)
|
|
|
|
mv, err = mlrvalFromYAMLNative(nil)
|
|
assert.NoError(t, err)
|
|
assert.True(t, mv.IsNull())
|
|
}
|
|
|
|
func TestMlrvalFromYAMLNativeMap(t *testing.T) {
|
|
// gopkg.in/yaml.v3 unmarshals into map[string]interface{}
|
|
m := map[string]interface{}{
|
|
"a": 1,
|
|
"b": "two",
|
|
"c": true,
|
|
}
|
|
mv, err := mlrvalFromYAMLNative(m)
|
|
assert.NoError(t, err)
|
|
assert.True(t, mv.IsMap())
|
|
rec := mv.GetMap()
|
|
assert.NotNil(t, rec)
|
|
assert.Equal(t, int64(3), rec.FieldCount)
|
|
av := rec.Get("a")
|
|
assert.True(t, av.IsInt())
|
|
ai, _ := av.GetIntValue()
|
|
assert.Equal(t, int64(1), ai)
|
|
bv := rec.Get("b")
|
|
assert.True(t, bv.IsString())
|
|
bs, _ := bv.GetStringValue()
|
|
assert.Equal(t, "two", bs)
|
|
}
|
|
|
|
func TestMlrvalDecodeFromYAML(t *testing.T) {
|
|
input := "a: 1\nb: 2\n"
|
|
decoder := yaml.NewDecoder(bytes.NewBufferString(input))
|
|
mv, eof, err := MlrvalDecodeFromYAML(decoder)
|
|
assert.NoError(t, err)
|
|
assert.False(t, eof)
|
|
assert.NotNil(t, mv)
|
|
assert.True(t, mv.IsMap())
|
|
rec := mv.GetMap()
|
|
assert.Equal(t, int64(2), rec.FieldCount)
|
|
v := rec.Get("a")
|
|
vi, _ := v.GetIntValue()
|
|
assert.Equal(t, int64(1), vi)
|
|
v = rec.Get("b")
|
|
vi, _ = v.GetIntValue()
|
|
assert.Equal(t, int64(2), vi)
|
|
|
|
// EOF on next decode
|
|
_, eof, err = MlrvalDecodeFromYAML(decoder)
|
|
assert.NoError(t, err)
|
|
assert.True(t, eof)
|
|
}
|
|
|
|
func TestMlrmapToYAMLNativeRoundTrip(t *testing.T) {
|
|
rec := NewMlrmapAsRecord()
|
|
rec.PutReference("x", FromInt(10))
|
|
rec.PutReference("y", FromString("hello"))
|
|
rec.PutReference("z", FromBool(true))
|
|
|
|
native, err := MlrmapToYAMLNative(rec)
|
|
assert.NoError(t, err)
|
|
assert.NotNil(t, native)
|
|
|
|
out, err := yaml.Marshal(native)
|
|
assert.NoError(t, err)
|
|
decoder := yaml.NewDecoder(bytes.NewReader(out))
|
|
mv, eof, err := MlrvalDecodeFromYAML(decoder)
|
|
assert.NoError(t, err)
|
|
assert.False(t, eof)
|
|
assert.True(t, mv.IsMap())
|
|
back := mv.GetMap()
|
|
xv := back.Get("x")
|
|
xi, _ := xv.GetIntValue()
|
|
assert.Equal(t, int64(10), xi)
|
|
yv := back.Get("y")
|
|
ys, _ := yv.GetStringValue()
|
|
assert.Equal(t, "hello", ys)
|
|
zv := back.Get("z")
|
|
zb, _ := zv.GetBoolValue()
|
|
assert.True(t, zb)
|
|
}
|
|
|
|
func TestMlrvalDecodeFromYAMLArrayOfMaps(t *testing.T) {
|
|
input := "- a: 1\n b: 2\n- a: 3\n b: 4\n"
|
|
decoder := yaml.NewDecoder(bytes.NewBufferString(input))
|
|
mv, eof, err := MlrvalDecodeFromYAML(decoder)
|
|
assert.NoError(t, err)
|
|
assert.False(t, eof)
|
|
assert.True(t, mv.IsArray())
|
|
arr := mv.GetArray()
|
|
assert.Len(t, arr, 2)
|
|
assert.True(t, arr[0].IsMap())
|
|
assert.True(t, arr[1].IsMap())
|
|
r0 := arr[0].GetMap()
|
|
av := r0.Get("a")
|
|
ai, _ := av.GetIntValue()
|
|
assert.Equal(t, int64(1), ai)
|
|
r1 := arr[1].GetMap()
|
|
av = r1.Get("a")
|
|
ai, _ = av.GetIntValue()
|
|
assert.Equal(t, int64(3), ai)
|
|
}
|
|
|
|
func TestMlrvalDecodeFromYAMLMultiDoc(t *testing.T) {
|
|
input := "a: 1\nb: 2\n---\nc: 3\nd: 4\n"
|
|
decoder := yaml.NewDecoder(bytes.NewBufferString(input))
|
|
|
|
mv1, eof, err := MlrvalDecodeFromYAML(decoder)
|
|
assert.NoError(t, err)
|
|
assert.False(t, eof)
|
|
assert.True(t, mv1.IsMap())
|
|
assert.Equal(t, int64(2), mv1.GetMap().FieldCount)
|
|
|
|
mv2, eof, err := MlrvalDecodeFromYAML(decoder)
|
|
assert.NoError(t, err)
|
|
assert.False(t, eof)
|
|
assert.True(t, mv2.IsMap())
|
|
assert.Equal(t, int64(2), mv2.GetMap().FieldCount)
|
|
|
|
_, eof, err = MlrvalDecodeFromYAML(decoder)
|
|
assert.NoError(t, err)
|
|
assert.True(t, eof)
|
|
}
|
|
|
|
func TestYAMLKeyStringNonStringKeys(t *testing.T) {
|
|
// map[interface{}]interface{} with int key
|
|
m := map[interface{}]interface{}{
|
|
1: "one",
|
|
"two": 2,
|
|
}
|
|
mv, err := mlrvalFromYAMLMap(m)
|
|
assert.NoError(t, err)
|
|
assert.True(t, mv.IsMap())
|
|
rec := mv.GetMap()
|
|
v := rec.Get("1")
|
|
assert.True(t, v.IsString())
|
|
s, _ := v.GetStringValue()
|
|
assert.Equal(t, "one", s)
|
|
v = rec.Get("two")
|
|
assert.True(t, v.IsInt())
|
|
}
|
|
|
|
func TestMlrmapToYAMLNativeKeyOrder(t *testing.T) {
|
|
rec := NewMlrmapAsRecord()
|
|
rec.PutReference("b", FromInt(22))
|
|
rec.PutReference("c", FromInt(33))
|
|
rec.PutReference("a", FromInt(11))
|
|
|
|
native, err := MlrmapToYAMLNative(rec)
|
|
assert.NoError(t, err)
|
|
out, err := yaml.Marshal(native)
|
|
assert.NoError(t, err)
|
|
|
|
var node yaml.Node
|
|
err = yaml.Unmarshal(out, &node)
|
|
assert.NoError(t, err)
|
|
if !assert.Len(t, node.Content, 1, "expected a single YAML document node") {
|
|
return
|
|
}
|
|
|
|
mapping := node.Content[0]
|
|
if !assert.Equal(t, yaml.MappingNode, mapping.Kind, "expected top-level YAML mapping") {
|
|
return
|
|
}
|
|
|
|
// Keys must appear in insertion order (b, c, a), not sorted (a, b, c).
|
|
if !assert.GreaterOrEqual(t, len(mapping.Content), 6, "expected at least three key/value pairs") {
|
|
return
|
|
}
|
|
assert.Equal(t, "b", mapping.Content[0].Value)
|
|
assert.Equal(t, "c", mapping.Content[2].Value)
|
|
assert.Equal(t, "a", mapping.Content[4].Value)
|
|
}
|
|
|
|
func TestMlrmapToYAMLNativeNested(t *testing.T) {
|
|
inner := NewMlrmapAsRecord()
|
|
inner.PutReference("p", FromInt(1))
|
|
inner.PutReference("q", FromInt(2))
|
|
rec := NewMlrmapAsRecord()
|
|
rec.PutReference("a", FromString("x"))
|
|
rec.PutReference("nested", FromMap(inner))
|
|
|
|
native, err := MlrmapToYAMLNative(rec)
|
|
assert.NoError(t, err)
|
|
out, err := yaml.Marshal(native)
|
|
assert.NoError(t, err)
|
|
assert.True(t, strings.Contains(string(out), "nested:"))
|
|
assert.True(t, strings.Contains(string(out), "p: 1"))
|
|
}
|