treerack/json_test.go
2017-06-25 23:42:42 +02:00

558 lines
9.4 KiB
Go

package parse
import (
"bytes"
"encoding/json"
"errors"
"fmt"
"io"
"math"
"math/rand"
"testing"
)
type jsonValueType int
const (
jsonNone jsonValueType = iota
jsonTrue
jsonFalse
jsonNull
jsonString
jsonNumber
jsonObject
jsonArray
)
const (
maxStringLength = 64
meanStringLength = 18
maxKeyLength = 24
meanKeyLength = 6
maxObjectLength = 12
meanObjectLength = 6
maxArrayLength = 64
meanArrayLength = 8
)
func randomLength(max, mean int) int {
return int(rand.NormFloat64()*float64(max)/math.MaxFloat64 + float64(mean))
}
func generateString(max, mean int) string {
l := randomLength(max, mean)
b := make([]byte, l)
for i := range b {
b[i] = byte(rand.Intn(int('z')-int('a')+1)) + 'a'
}
return string(b)
}
func generateJSONString() string {
return generateString(maxStringLength, meanStringLength)
}
func generateJSONNumber() interface{} {
if rand.Intn(2) == 1 {
return rand.NormFloat64()
}
n := rand.Int()
if rand.Intn(2) == 0 {
return n
}
return -n
}
func generateKey() string {
return generateString(maxKeyLength, meanKeyLength)
}
func generateJSONObject(minDepth int) map[string]interface{} {
l := randomLength(maxObjectLength, meanObjectLength)
o := make(map[string]interface{})
for i := 0; i < l; i++ {
o[generateKey()] = generateJSON(0)
}
if minDepth > 0 {
o[generateKey()] = generateJSON(minDepth)
}
return o
}
func generateJSONArray(minDepth int) []interface{} {
l := randomLength(maxArrayLength, meanArrayLength)
a := make([]interface{}, l, l+1)
for i := 0; i < l; i++ {
a[i] = generateJSON(0)
}
if minDepth > 0 {
a = append(a, generateJSON(minDepth))
}
return a
}
func generateJSONObjectOrArray(minDepth int) interface{} {
if rand.Intn(2) == 0 {
return generateJSONObject(minDepth - 1)
}
return generateJSONArray(minDepth - 1)
}
func generateJSON(minDepth int) interface{} {
if minDepth > 0 {
return generateJSONObjectOrArray(minDepth)
}
switch jsonValueType(rand.Intn(int(jsonNumber)) + 1) {
case jsonTrue:
return true
case jsonFalse:
return false
case jsonNull:
return nil
case jsonString:
return generateJSONString()
case jsonNumber:
return generateJSONNumber()
default:
panic("invalid json type")
}
}
func unqouteJSONString(t string) (string, error) {
var s string
err := json.Unmarshal([]byte(t), &s)
return s, err
}
func parseJSONNumber(t string) (interface{}, error) {
n := json.Number(t)
if i, err := n.Int64(); err == nil {
return int(i), nil
}
return n.Float64()
}
func nodeToJSONObject(n *Node) (map[string]interface{}, error) {
o := make(map[string]interface{})
for _, ni := range n.Nodes {
if len(ni.Nodes) != 2 {
return nil, errors.New("invalid json object")
}
key, err := unqouteJSONString(ni.Nodes[0].Text())
if err != nil {
return nil, err
}
val, err := treeToJSON(ni.Nodes[1])
if err != nil {
return nil, err
}
o[key] = val
}
return o, nil
}
func nodeToJSONArray(n *Node) ([]interface{}, error) {
a := make([]interface{}, 0, len(n.Nodes))
for _, ni := range n.Nodes {
item, err := treeToJSON(ni)
if err != nil {
return nil, err
}
a = append(a, item)
}
return a, nil
}
func treeToJSON(n *Node) (interface{}, error) {
switch n.Name {
case "true":
return true, nil
case "false":
return false, nil
case "null":
return nil, nil
case "string":
return unqouteJSONString(n.Text())
case "number":
return parseJSONNumber(n.Text())
case "object":
return nodeToJSONObject(n)
case "array":
return nodeToJSONArray(n)
default:
return nil, fmt.Errorf("invalid json node name: %s", n.Name)
}
}
func checkJSON(t *testing.T, got, expected interface{}) {
if expected == nil {
if got != nil {
t.Error("expected nil", got)
}
return
}
switch v := expected.(type) {
case bool:
if v != got.(bool) {
t.Error("expected bool", got)
}
case string:
if v != got.(string) {
t.Error("expected string", got)
}
case int:
if v != got.(int) {
t.Error("expected int", got)
}
case float64:
if v != got.(float64) {
t.Error("expected float64", got)
}
case map[string]interface{}:
o, ok := got.(map[string]interface{})
if !ok {
t.Error("expected object", got)
return
}
if len(v) != len(o) {
t.Error("invalid object length, expected: %d, got: %d", len(v), len(o))
return
}
for key, val := range v {
gotVal, ok := o[key]
if !ok {
t.Error("expected key not found: %s", key)
return
}
checkJSON(t, gotVal, val)
if t.Failed() {
return
}
}
case []interface{}:
a, ok := got.([]interface{})
if !ok {
t.Error("expected array", got)
}
if len(v) != len(a) {
t.Error("invalid array length, expected: %d, got: %d", len(v), len(a))
return
}
for i := range v {
checkJSON(t, a[i], v[i])
if t.Failed() {
return
}
}
default:
t.Error("unexpected parsed type", v)
}
}
func jsonTreeToJSON(n *Node) (interface{}, error) {
if n.Name != "json" {
return nil, fmt.Errorf("invalid root node name: %s", n.Name)
}
if len(n.Nodes) != 1 {
return nil, fmt.Errorf("invalid root node length: %d", len(n.Nodes))
}
return treeToJSON(n.Nodes[0])
}
func TestJSON(t *testing.T) {
test(t, "json.parser", "value", []testItem{{
msg: "true",
text: "true",
node: &Node{
Name: "json",
Nodes: []*Node{{
Name: "true",
}},
},
ignorePosition: true,
}, {
msg: "false",
text: "false",
node: &Node{
Name: "json",
Nodes: []*Node{{
Name: "false",
}},
},
ignorePosition: true,
}, {
msg: "null",
text: "null",
node: &Node{
Name: "json",
Nodes: []*Node{{
Name: "null",
}},
},
ignorePosition: true,
}, {
msg: "string",
text: `"\"\\n\b\t\uabcd"`,
node: &Node{
Name: "json",
Nodes: []*Node{{
Name: "string",
}},
},
ignorePosition: true,
}, {
msg: "number",
text: "6.62e-34",
node: &Node{
Name: "json",
Nodes: []*Node{{
Name: "number",
}},
},
ignorePosition: true,
}, {
msg: "object",
text: `{
"true": true,
"false": false,
"null": null,
"string": "string",
"number": 42,
"object": {},
"array": []
}`,
node: &Node{
Name: "json",
Nodes: []*Node{{
Name: "object",
Nodes: []*Node{{
Name: "entry",
Nodes: []*Node{{
Name: "string",
}, {
Name: "true",
}},
}, {
Name: "entry",
Nodes: []*Node{{
Name: "string",
}, {
Name: "false",
}},
}, {
Name: "entry",
Nodes: []*Node{{
Name: "string",
}, {
Name: "null",
}},
}, {
Name: "entry",
Nodes: []*Node{{
Name: "string",
}, {
Name: "string",
}},
}, {
Name: "entry",
Nodes: []*Node{{
Name: "string",
}, {
Name: "number",
}},
}, {
Name: "entry",
Nodes: []*Node{{
Name: "string",
}, {
Name: "object",
}},
}, {
Name: "entry",
Nodes: []*Node{{
Name: "string",
}, {
Name: "array",
}},
}},
}},
},
ignorePosition: true,
}, {
msg: "array",
text: `[true, false, null, "string", 42, {
"true": true,
"false": false,
"null": null,
"string": "string",
"number": 42,
"object": {},
"array": []
}, []]`,
node: &Node{
Name: "json",
Nodes: []*Node{{
Name: "array",
Nodes: []*Node{{
Name: "true",
}, {
Name: "false",
}, {
Name: "null",
}, {
Name: "string",
}, {
Name: "number",
}, {
Name: "object",
Nodes: []*Node{{
Name: "entry",
Nodes: []*Node{{
Name: "string",
}, {
Name: "true",
}},
}, {
Name: "entry",
Nodes: []*Node{{
Name: "string",
}, {
Name: "false",
}},
}, {
Name: "entry",
Nodes: []*Node{{
Name: "string",
}, {
Name: "null",
}},
}, {
Name: "entry",
Nodes: []*Node{{
Name: "string",
}, {
Name: "string",
}},
}, {
Name: "entry",
Nodes: []*Node{{
Name: "string",
}, {
Name: "number",
}},
}, {
Name: "entry",
Nodes: []*Node{{
Name: "string",
}, {
Name: "object",
}},
}, {
Name: "entry",
Nodes: []*Node{{
Name: "string",
}, {
Name: "array",
}},
}},
}, {
Name: "array",
}},
}},
},
ignorePosition: true,
}, {
msg: "bugfix, 100",
text: "100",
node: &Node{
Name: "json",
Nodes: []*Node{{
Name: "number",
}},
},
ignorePosition: true,
}})
}
func TestRandomJSON(t *testing.T) {
j := generateJSON(48)
b, err := json.Marshal(j)
if err != nil {
t.Error(err)
return
}
buf := bytes.NewBuffer(b)
s, err := testSyntax("json.parser", 0)
if err != nil {
t.Error(err)
return
}
testParse := func(t *testing.T, buf io.Reader) {
n, err := s.Parse(buf)
if err != nil {
t.Error(err)
return
}
jback, err := jsonTreeToJSON(n)
if err != nil {
t.Error(err)
return
}
checkJSON(t, jback, j)
}
t.Run("unindented", func(t *testing.T) {
testParse(t, buf)
})
indented := bytes.NewBuffer(nil)
if err := json.Indent(indented, b, "", " "); err != nil {
t.Error(err)
return
}
t.Run("indented", func(t *testing.T) {
testParse(t, indented)
})
indentedTabs := bytes.NewBuffer(nil)
if err := json.Indent(indentedTabs, b, "", "\t"); err != nil {
t.Error(err)
return
}
t.Run("indented with tabs", func(t *testing.T) {
testParse(t, indentedTabs)
})
}