mirror of
https://github.com/MetaCubeX/mihomo.git
synced 2026-02-27 01:07:10 +00:00
chore: switch to our own common/orderedmap package, remove two unneeded json dependence
This commit is contained in:
338
common/orderedmap/json_test.go
Normal file
338
common/orderedmap/json_test.go
Normal file
@@ -0,0 +1,338 @@
|
||||
package orderedmap
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
// to test marshalling TextMarshalers and unmarshalling TextUnmarshalers
|
||||
type marshallable int
|
||||
|
||||
func (m marshallable) MarshalText() ([]byte, error) {
|
||||
return []byte(fmt.Sprintf("#%d#", m)), nil
|
||||
}
|
||||
|
||||
func (m *marshallable) UnmarshalText(text []byte) error {
|
||||
if len(text) < 3 {
|
||||
return errors.New("too short")
|
||||
}
|
||||
if text[0] != '#' || text[len(text)-1] != '#' {
|
||||
return errors.New("missing prefix or suffix")
|
||||
}
|
||||
|
||||
value, err := strconv.Atoi(string(text[1 : len(text)-1]))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
*m = marshallable(value)
|
||||
return nil
|
||||
}
|
||||
|
||||
func TestMarshalJSON(t *testing.T) {
|
||||
t.Run("int key", func(t *testing.T) {
|
||||
om := New[int, any]()
|
||||
om.Set(1, "bar")
|
||||
om.Set(7, "baz")
|
||||
om.Set(2, 28)
|
||||
om.Set(3, 100)
|
||||
om.Set(4, "baz")
|
||||
om.Set(5, "28")
|
||||
om.Set(6, "100")
|
||||
om.Set(8, "baz")
|
||||
om.Set(8, "baz")
|
||||
om.Set(9, "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Quisque auctor augue accumsan mi maximus, quis viverra massa pretium. Phasellus imperdiet sapien a interdum sollicitudin. Duis at commodo lectus, a lacinia sem.")
|
||||
|
||||
b, err := json.Marshal(om)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, `{"1":"bar","7":"baz","2":28,"3":100,"4":"baz","5":"28","6":"100","8":"baz","9":"Lorem ipsum dolor sit amet, consectetur adipiscing elit. Quisque auctor augue accumsan mi maximus, quis viverra massa pretium. Phasellus imperdiet sapien a interdum sollicitudin. Duis at commodo lectus, a lacinia sem."}`, string(b))
|
||||
})
|
||||
|
||||
t.Run("string key", func(t *testing.T) {
|
||||
om := New[string, any]()
|
||||
om.Set("test", "bar")
|
||||
om.Set("abc", true)
|
||||
|
||||
b, err := json.Marshal(om)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, `{"test":"bar","abc":true}`, string(b))
|
||||
})
|
||||
|
||||
t.Run("typed string key", func(t *testing.T) {
|
||||
type myString string
|
||||
om := New[myString, any]()
|
||||
om.Set("test", "bar")
|
||||
om.Set("abc", true)
|
||||
|
||||
b, err := json.Marshal(om)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, `{"test":"bar","abc":true}`, string(b))
|
||||
})
|
||||
|
||||
t.Run("typed int key", func(t *testing.T) {
|
||||
type myInt uint32
|
||||
om := New[myInt, any]()
|
||||
om.Set(1, "bar")
|
||||
om.Set(7, "baz")
|
||||
om.Set(2, 28)
|
||||
om.Set(3, 100)
|
||||
om.Set(4, "baz")
|
||||
|
||||
b, err := json.Marshal(om)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, `{"1":"bar","7":"baz","2":28,"3":100,"4":"baz"}`, string(b))
|
||||
})
|
||||
|
||||
t.Run("TextMarshaller key", func(t *testing.T) {
|
||||
om := New[marshallable, any]()
|
||||
om.Set(marshallable(1), "bar")
|
||||
om.Set(marshallable(28), true)
|
||||
|
||||
b, err := json.Marshal(om)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, `{"#1#":"bar","#28#":true}`, string(b))
|
||||
})
|
||||
|
||||
t.Run("empty map", func(t *testing.T) {
|
||||
om := New[string, any]()
|
||||
|
||||
b, err := json.Marshal(om)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, `{}`, string(b))
|
||||
})
|
||||
}
|
||||
|
||||
func TestUnmarshallJSON(t *testing.T) {
|
||||
t.Run("int key", func(t *testing.T) {
|
||||
data := `{"1":"bar","7":"baz","2":28,"3":100,"4":"baz","5":"28","6":"100","8":"baz"}`
|
||||
|
||||
om := New[int, any]()
|
||||
require.NoError(t, json.Unmarshal([]byte(data), &om))
|
||||
|
||||
assertOrderedPairsEqual(t, om,
|
||||
[]int{1, 7, 2, 3, 4, 5, 6, 8},
|
||||
[]any{"bar", "baz", float64(28), float64(100), "baz", "28", "100", "baz"})
|
||||
})
|
||||
|
||||
t.Run("string key", func(t *testing.T) {
|
||||
data := `{"test":"bar","abc":true}`
|
||||
|
||||
om := New[string, any]()
|
||||
require.NoError(t, json.Unmarshal([]byte(data), &om))
|
||||
|
||||
assertOrderedPairsEqual(t, om,
|
||||
[]string{"test", "abc"},
|
||||
[]any{"bar", true})
|
||||
})
|
||||
|
||||
t.Run("typed string key", func(t *testing.T) {
|
||||
data := `{"test":"bar","abc":true}`
|
||||
|
||||
type myString string
|
||||
om := New[myString, any]()
|
||||
require.NoError(t, json.Unmarshal([]byte(data), &om))
|
||||
|
||||
assertOrderedPairsEqual(t, om,
|
||||
[]myString{"test", "abc"},
|
||||
[]any{"bar", true})
|
||||
})
|
||||
|
||||
t.Run("typed int key", func(t *testing.T) {
|
||||
data := `{"1":"bar","7":"baz","2":28,"3":100,"4":"baz","5":"28","6":"100","8":"baz"}`
|
||||
|
||||
type myInt uint32
|
||||
om := New[myInt, any]()
|
||||
require.NoError(t, json.Unmarshal([]byte(data), &om))
|
||||
|
||||
assertOrderedPairsEqual(t, om,
|
||||
[]myInt{1, 7, 2, 3, 4, 5, 6, 8},
|
||||
[]any{"bar", "baz", float64(28), float64(100), "baz", "28", "100", "baz"})
|
||||
})
|
||||
|
||||
t.Run("TextUnmarshaler key", func(t *testing.T) {
|
||||
data := `{"#1#":"bar","#28#":true}`
|
||||
|
||||
om := New[marshallable, any]()
|
||||
require.NoError(t, json.Unmarshal([]byte(data), &om))
|
||||
|
||||
assertOrderedPairsEqual(t, om,
|
||||
[]marshallable{1, 28},
|
||||
[]any{"bar", true})
|
||||
})
|
||||
|
||||
t.Run("when fed with an input that's not an object", func(t *testing.T) {
|
||||
for _, data := range []string{"true", `["foo"]`, "42", `"foo"`} {
|
||||
om := New[int, any]()
|
||||
require.Error(t, json.Unmarshal([]byte(data), &om))
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("empty map", func(t *testing.T) {
|
||||
data := `{}`
|
||||
|
||||
om := New[int, any]()
|
||||
require.NoError(t, json.Unmarshal([]byte(data), &om))
|
||||
|
||||
assertLenEqual(t, om, 0)
|
||||
})
|
||||
}
|
||||
|
||||
// const specialCharacters = "\\\\/\"\b\f\n\r\t\x00\uffff\ufffd世界\u007f\u00ff\U0010FFFF"
|
||||
const specialCharacters = "\uffff\ufffd世界\u007f\u00ff\U0010FFFF"
|
||||
|
||||
func TestJSONSpecialCharacters(t *testing.T) {
|
||||
baselineMap := map[string]any{specialCharacters: specialCharacters}
|
||||
baselineData, err := json.Marshal(baselineMap)
|
||||
require.NoError(t, err) // baseline proves this key is supported by official json library
|
||||
t.Logf("specialCharacters: %#v as []rune:%v", specialCharacters, []rune(specialCharacters))
|
||||
t.Logf("baseline json data: %s", baselineData)
|
||||
|
||||
t.Run("marshal special characters", func(t *testing.T) {
|
||||
om := New[string, any]()
|
||||
om.Set(specialCharacters, specialCharacters)
|
||||
b, err := json.Marshal(om)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, baselineData, b)
|
||||
|
||||
type myString string
|
||||
om2 := New[myString, myString]()
|
||||
om2.Set(specialCharacters, specialCharacters)
|
||||
b, err = json.Marshal(om2)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, baselineData, b)
|
||||
})
|
||||
|
||||
t.Run("unmarshall special characters", func(t *testing.T) {
|
||||
om := New[string, any]()
|
||||
require.NoError(t, json.Unmarshal(baselineData, &om))
|
||||
assertOrderedPairsEqual(t, om,
|
||||
[]string{specialCharacters},
|
||||
[]any{specialCharacters})
|
||||
|
||||
type myString string
|
||||
om2 := New[myString, myString]()
|
||||
require.NoError(t, json.Unmarshal(baselineData, &om2))
|
||||
assertOrderedPairsEqual(t, om2,
|
||||
[]myString{specialCharacters},
|
||||
[]myString{specialCharacters})
|
||||
})
|
||||
}
|
||||
|
||||
// to test structs that have nested map fields
|
||||
type nestedMaps struct {
|
||||
X int `json:"x" yaml:"x"`
|
||||
M *OrderedMap[string, []*OrderedMap[int, *OrderedMap[string, any]]] `json:"m" yaml:"m"`
|
||||
}
|
||||
|
||||
func TestJSONRoundTrip(t *testing.T) {
|
||||
for _, testCase := range []struct {
|
||||
name string
|
||||
input string
|
||||
targetFactory func() any
|
||||
isPrettyPrinted bool
|
||||
}{
|
||||
{
|
||||
name: "",
|
||||
input: `{
|
||||
"x": 28,
|
||||
"m": {
|
||||
"foo": [
|
||||
{
|
||||
"12": {
|
||||
"i": 12,
|
||||
"b": true,
|
||||
"n": null,
|
||||
"m": {
|
||||
"a": "b",
|
||||
"c": 28
|
||||
}
|
||||
},
|
||||
"28": {
|
||||
"a": false,
|
||||
"b": [
|
||||
1,
|
||||
2,
|
||||
3
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"3": {
|
||||
"c": null,
|
||||
"d": 87
|
||||
},
|
||||
"4": {
|
||||
"e": true
|
||||
},
|
||||
"5": {
|
||||
"f": 4,
|
||||
"g": 5,
|
||||
"h": 6
|
||||
}
|
||||
}
|
||||
],
|
||||
"bar": [
|
||||
{
|
||||
"5": {
|
||||
"foo": "bar"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}`,
|
||||
targetFactory: func() any { return &nestedMaps{} },
|
||||
isPrettyPrinted: true,
|
||||
},
|
||||
{
|
||||
name: "with UTF-8 special chars in key",
|
||||
input: `{"<22>":0}`,
|
||||
targetFactory: func() any { return &OrderedMap[string, int]{} },
|
||||
},
|
||||
} {
|
||||
t.Run(testCase.name, func(t *testing.T) {
|
||||
target := testCase.targetFactory()
|
||||
|
||||
require.NoError(t, json.Unmarshal([]byte(testCase.input), target))
|
||||
|
||||
var (
|
||||
out []byte
|
||||
err error
|
||||
)
|
||||
if testCase.isPrettyPrinted {
|
||||
out, err = json.MarshalIndent(target, "", " ")
|
||||
} else {
|
||||
out, err = json.Marshal(target)
|
||||
}
|
||||
|
||||
if assert.NoError(t, err) {
|
||||
assert.Equal(t, strings.TrimSpace(testCase.input), string(out))
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkMarshalJSON(b *testing.B) {
|
||||
om := New[int, any]()
|
||||
om.Set(1, "bar")
|
||||
om.Set(7, "baz")
|
||||
om.Set(2, 28)
|
||||
om.Set(3, 100)
|
||||
om.Set(4, "baz")
|
||||
om.Set(5, "28")
|
||||
om.Set(6, "100")
|
||||
om.Set(8, "baz")
|
||||
om.Set(8, "baz")
|
||||
|
||||
b.ResetTimer()
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
_, _ = json.Marshal(om)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user