// Copyright (C) 2022 Luke Shumaker // // SPDX-License-Identifier: GPL-2.0-or-later package lowmemjson import ( "fmt" "strings" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) type ReadRuneTypeResult struct { r rune s int t RuneType e error } func (r ReadRuneTypeResult) String() string { return fmt.Sprintf("{%q, %d, %#v, %v}", r.r, r.s, r.t, r.e) } func TestRuneTypeScanner(t *testing.T) { type testcase struct { Input string Exp []ReadRuneTypeResult } testcases := map[string]testcase{ "basic": {`{"foo": 12.0}`, []ReadRuneTypeResult{ {'{', 1, RuneTypeObjectBeg, nil}, {'"', 1, RuneTypeStringBeg, nil}, {'f', 1, RuneTypeStringChar, nil}, {'o', 1, RuneTypeStringChar, nil}, {'o', 1, RuneTypeStringChar, nil}, {'"', 1, RuneTypeStringEnd, nil}, {':', 1, RuneTypeObjectColon, nil}, {' ', 1, RuneTypeSpace, nil}, {'1', 1, RuneTypeNumberIntDig, nil}, {'2', 1, RuneTypeNumberIntDig, nil}, {'.', 1, RuneTypeNumberFracDot, nil}, {'0', 1, RuneTypeNumberFracDig, nil}, {'}', 1, RuneTypeObjectEnd, nil}, {0, 0, RuneTypeEOF, nil}, {0, 0, RuneTypeEOF, nil}, }}, "unread": {`{"foo": 12.0}`, []ReadRuneTypeResult{ {'{', 1, RuneTypeObjectBeg, nil}, {'"', 1, RuneTypeStringBeg, nil}, {'f', 1, RuneTypeStringChar, nil}, {'o', 1, RuneTypeStringChar, nil}, {'o', 1, RuneTypeStringChar, nil}, {'"', 1, RuneTypeStringEnd, nil}, {':', 1, RuneTypeObjectColon, nil}, {' ', 1, RuneTypeSpace, nil}, {'1', 1, RuneTypeNumberIntDig, nil}, {0, -1, 0, nil}, {'1', 1, RuneTypeNumberIntDig, nil}, {'2', 1, RuneTypeNumberIntDig, nil}, {'.', 1, RuneTypeNumberFracDot, nil}, {'0', 1, RuneTypeNumberFracDig, nil}, {'}', 1, RuneTypeObjectEnd, nil}, {0, 0, RuneTypeEOF, nil}, {0, 0, RuneTypeEOF, nil}, }}, "unread2": {`{"foo": 12.0}`, []ReadRuneTypeResult{ {'{', 1, RuneTypeObjectBeg, nil}, {'"', 1, RuneTypeStringBeg, nil}, {'f', 1, RuneTypeStringChar, nil}, {'o', 1, RuneTypeStringChar, nil}, {'o', 1, RuneTypeStringChar, nil}, {'"', 1, RuneTypeStringEnd, nil}, {':', 1, RuneTypeObjectColon, nil}, {' ', 1, RuneTypeSpace, nil}, {'1', 1, RuneTypeNumberIntDig, nil}, {0, -1, 0, nil}, {0, -1, 0, ErrInvalidUnreadRune}, {'1', 1, RuneTypeNumberIntDig, nil}, {'2', 1, RuneTypeNumberIntDig, nil}, {'.', 1, RuneTypeNumberFracDot, nil}, {'0', 1, RuneTypeNumberFracDig, nil}, {'}', 1, RuneTypeObjectEnd, nil}, {0, 0, RuneTypeEOF, nil}, {0, 0, RuneTypeEOF, nil}, }}, "unread-eof": {`{"foo": 12.0}`, []ReadRuneTypeResult{ {'{', 1, RuneTypeObjectBeg, nil}, {'"', 1, RuneTypeStringBeg, nil}, {'f', 1, RuneTypeStringChar, nil}, {'o', 1, RuneTypeStringChar, nil}, {'o', 1, RuneTypeStringChar, nil}, {'"', 1, RuneTypeStringEnd, nil}, {':', 1, RuneTypeObjectColon, nil}, {' ', 1, RuneTypeSpace, nil}, {'1', 1, RuneTypeNumberIntDig, nil}, {'2', 1, RuneTypeNumberIntDig, nil}, {'.', 1, RuneTypeNumberFracDot, nil}, {'0', 1, RuneTypeNumberFracDig, nil}, {'}', 1, RuneTypeObjectEnd, nil}, {0, 0, RuneTypeEOF, nil}, {0, -1, 0, ErrInvalidUnreadRune}, {0, 0, RuneTypeEOF, nil}, {0, 0, RuneTypeEOF, nil}, }}, } for tcName, tc := range testcases { t.Run(tcName, func(t *testing.T) { sc := &runeTypeScannerImpl{ inner: strings.NewReader(tc.Input), } var exp, act []string for _, iExp := range tc.Exp { var iAct ReadRuneTypeResult if iExp.s < 0 { iAct.s = iExp.s iAct.e = sc.UnreadRune() } else { iAct.r, iAct.s, iAct.t, iAct.e = sc.ReadRuneType() } exp = append(exp, iExp.String()) act = append(act, iAct.String()) } assert.Equal(t, exp, act) }) } } func TestNoWSRuneTypeScanner(t *testing.T) { type testcase struct { Input string Exp []ReadRuneTypeResult } testcases := map[string]testcase{ "basic": {`{"foo": 12.0}`, []ReadRuneTypeResult{ {'{', 1, RuneTypeObjectBeg, nil}, {'"', 1, RuneTypeStringBeg, nil}, {'f', 1, RuneTypeStringChar, nil}, {'o', 1, RuneTypeStringChar, nil}, {'o', 1, RuneTypeStringChar, nil}, {'"', 1, RuneTypeStringEnd, nil}, {':', 1, RuneTypeObjectColon, nil}, {'1', 1, RuneTypeNumberIntDig, nil}, {'2', 1, RuneTypeNumberIntDig, nil}, {'.', 1, RuneTypeNumberFracDot, nil}, {'0', 1, RuneTypeNumberFracDig, nil}, {'}', 1, RuneTypeObjectEnd, nil}, {0, 0, RuneTypeEOF, nil}, {0, 0, RuneTypeEOF, nil}, }}, "unread": {`{"foo": 12.0}`, []ReadRuneTypeResult{ {'{', 1, RuneTypeObjectBeg, nil}, {'"', 1, RuneTypeStringBeg, nil}, {'f', 1, RuneTypeStringChar, nil}, {'o', 1, RuneTypeStringChar, nil}, {'o', 1, RuneTypeStringChar, nil}, {'"', 1, RuneTypeStringEnd, nil}, {':', 1, RuneTypeObjectColon, nil}, {'1', 1, RuneTypeNumberIntDig, nil}, {0, -1, 0, nil}, {'1', 1, RuneTypeNumberIntDig, nil}, {'2', 1, RuneTypeNumberIntDig, nil}, {'.', 1, RuneTypeNumberFracDot, nil}, {'0', 1, RuneTypeNumberFracDig, nil}, {'}', 1, RuneTypeObjectEnd, nil}, {0, 0, RuneTypeEOF, nil}, {0, 0, RuneTypeEOF, nil}, }}, "tail": {`{"foo": 12.0} `, []ReadRuneTypeResult{ {'{', 1, RuneTypeObjectBeg, nil}, {'"', 1, RuneTypeStringBeg, nil}, {'f', 1, RuneTypeStringChar, nil}, {'o', 1, RuneTypeStringChar, nil}, {'o', 1, RuneTypeStringChar, nil}, {'"', 1, RuneTypeStringEnd, nil}, {':', 1, RuneTypeObjectColon, nil}, {'1', 1, RuneTypeNumberIntDig, nil}, {'2', 1, RuneTypeNumberIntDig, nil}, {'.', 1, RuneTypeNumberFracDot, nil}, {'0', 1, RuneTypeNumberFracDig, nil}, {'}', 1, RuneTypeObjectEnd, nil}, {0, 0, RuneTypeEOF, nil}, {0, 0, RuneTypeEOF, nil}, }}, } for tcName, tc := range testcases { t.Run(tcName, func(t *testing.T) { sc := &noWSRuneTypeScanner{ inner: &runeTypeScannerImpl{ inner: strings.NewReader(tc.Input), }, } var exp, act []string for _, iExp := range tc.Exp { var iAct ReadRuneTypeResult if iExp.s < 0 { iAct.s = iExp.s iAct.e = sc.UnreadRune() } else { iAct.r, iAct.s, iAct.t, iAct.e = sc.ReadRuneType() } exp = append(exp, iExp.String()) act = append(act, iAct.String()) } assert.Equal(t, exp, act) }) } } func TestElemRuneTypeScanner(t *testing.T) { parent := &noWSRuneTypeScanner{ inner: &runeTypeScannerImpl{ inner: strings.NewReader(` { "foo" : 12.0 } `), }, } exp := []ReadRuneTypeResult{ {'{', 1, RuneTypeObjectBeg, nil}, {'"', 1, RuneTypeStringBeg, nil}, {'f', 1, RuneTypeStringChar, nil}, {'o', 1, RuneTypeStringChar, nil}, {'o', 1, RuneTypeStringChar, nil}, {'"', 1, RuneTypeStringEnd, nil}, {':', 1, RuneTypeObjectColon, nil}, } var expStr, actStr []string for _, iExp := range exp { var iAct ReadRuneTypeResult iAct.r, iAct.s, iAct.t, iAct.e = parent.ReadRuneType() expStr = append(expStr, iExp.String()) actStr = append(actStr, iAct.String()) require.Equal(t, expStr, actStr) } child := &elemRuneTypeScanner{ inner: parent, } exp = []ReadRuneTypeResult{ {'1', 1, RuneTypeNumberIntDig, nil}, {'2', 1, RuneTypeNumberIntDig, nil}, {'.', 1, RuneTypeNumberFracDot, nil}, {'0', 1, RuneTypeNumberFracDig, nil}, {0, 0, RuneTypeEOF, nil}, {0, 0, RuneTypeEOF, nil}, } expStr, actStr = nil, nil for _, iExp := range exp { var iAct ReadRuneTypeResult iAct.r, iAct.s, iAct.t, iAct.e = child.ReadRuneType() expStr = append(expStr, iExp.String()) actStr = append(actStr, iAct.String()) require.Equal(t, expStr, actStr) } exp = []ReadRuneTypeResult{ {'}', 1, RuneTypeObjectEnd, nil}, {0, 0, RuneTypeEOF, nil}, {0, 0, RuneTypeEOF, nil}, } expStr, actStr = nil, nil for _, iExp := range exp { var iAct ReadRuneTypeResult iAct.r, iAct.s, iAct.t, iAct.e = parent.ReadRuneType() expStr = append(expStr, iExp.String()) actStr = append(actStr, iAct.String()) require.Equal(t, expStr, actStr) } }