diff options
Diffstat (limited to 'decode_scan_test.go')
-rw-r--r-- | decode_scan_test.go | 352 |
1 files changed, 114 insertions, 238 deletions
diff --git a/decode_scan_test.go b/decode_scan_test.go index 6a430ab..d0725e5 100644 --- a/decode_scan_test.go +++ b/decode_scan_test.go @@ -11,7 +11,6 @@ import ( "testing" "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" "git.lukeshu.com/go/lowmemjson/internal" ) @@ -23,8 +22,26 @@ type ReadRuneTypeResult struct { e error } +const ( + unreadRune = -1 + pushReadBarrier = -2 + popReadBarrier = -3 + reset = -4 +) + func (r ReadRuneTypeResult) String() string { - return fmt.Sprintf("{%q, %d, %#v, %v}", r.r, r.s, r.t, r.e) + switch r.s { + case unreadRune: + return fmt.Sprintf("{%q, unreadRune, %#v, %v}", r.r, r.t, r.e) + case pushReadBarrier: + return fmt.Sprintf("{%q, pushReadBarrier, %#v, %v}", r.r, r.t, r.e) + case popReadBarrier: + return fmt.Sprintf("{%q, popReadBarrier, %#v, %v}", r.r, r.t, r.e) + case reset: + return fmt.Sprintf("{%q, reset, %#v, %v}", r.r, r.t, r.e) + default: + return fmt.Sprintf("{%q, %d, %#v, %v}", r.r, r.s, r.t, r.e) + } } type runeTypeScannerTestcase struct { @@ -33,31 +50,6 @@ type runeTypeScannerTestcase struct { Exp []ReadRuneTypeResult } -func testRuneTypeScanner(t *testing.T, testcases map[string]runeTypeScannerTestcase, factory func(io.RuneScanner) runeTypeScanner) { - for tcName, tc := range testcases { - tc := tc - t.Run(tcName, func(t *testing.T) { - t.Parallel() - reader := strings.NewReader(tc.Input) - sc := factory(reader) - 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) - assert.Equal(t, tc.ExpRemainder, tc.Input[len(tc.Input)-reader.Len():]) - }) - } -} - func TestRuneTypeScanner(t *testing.T) { t.Parallel() testcases := map[string]runeTypeScannerTestcase{ @@ -69,7 +61,6 @@ func TestRuneTypeScanner(t *testing.T) { {'o', 1, internal.RuneTypeStringChar, nil}, {'"', 1, internal.RuneTypeStringEnd, nil}, {':', 1, internal.RuneTypeObjectColon, nil}, - {' ', 1, internal.RuneTypeSpace, nil}, {'1', 1, internal.RuneTypeNumberIntDig, nil}, {'2', 1, internal.RuneTypeNumberIntDig, nil}, {'.', 1, internal.RuneTypeNumberFracDot, nil}, @@ -86,9 +77,8 @@ func TestRuneTypeScanner(t *testing.T) { {'o', 1, internal.RuneTypeStringChar, nil}, {'"', 1, internal.RuneTypeStringEnd, nil}, {':', 1, internal.RuneTypeObjectColon, nil}, - {' ', 1, internal.RuneTypeSpace, nil}, {'1', 1, internal.RuneTypeNumberIntDig, nil}, - {0, -1, 0, nil}, + {0, unreadRune, 0, nil}, {'1', 1, internal.RuneTypeNumberIntDig, nil}, {'2', 1, internal.RuneTypeNumberIntDig, nil}, {'.', 1, internal.RuneTypeNumberFracDot, nil}, @@ -105,10 +95,9 @@ func TestRuneTypeScanner(t *testing.T) { {'o', 1, internal.RuneTypeStringChar, nil}, {'"', 1, internal.RuneTypeStringEnd, nil}, {':', 1, internal.RuneTypeObjectColon, nil}, - {' ', 1, internal.RuneTypeSpace, nil}, {'1', 1, internal.RuneTypeNumberIntDig, nil}, - {0, -1, 0, nil}, - {0, -1, 0, ErrInvalidUnreadRune}, + {0, unreadRune, 0, nil}, + {0, unreadRune, 0, ErrInvalidUnreadRune}, {'1', 1, internal.RuneTypeNumberIntDig, nil}, {'2', 1, internal.RuneTypeNumberIntDig, nil}, {'.', 1, internal.RuneTypeNumberFracDot, nil}, @@ -125,14 +114,29 @@ func TestRuneTypeScanner(t *testing.T) { {'o', 1, internal.RuneTypeStringChar, nil}, {'"', 1, internal.RuneTypeStringEnd, nil}, {':', 1, internal.RuneTypeObjectColon, nil}, - {' ', 1, internal.RuneTypeSpace, nil}, {'1', 1, internal.RuneTypeNumberIntDig, nil}, {'2', 1, internal.RuneTypeNumberIntDig, nil}, {'.', 1, internal.RuneTypeNumberFracDot, nil}, {'0', 1, internal.RuneTypeNumberFracDig, nil}, {'}', 1, internal.RuneTypeObjectEnd, nil}, {0, 0, internal.RuneTypeEOF, nil}, - {0, -1, 0, ErrInvalidUnreadRune}, + {0, unreadRune, 0, ErrInvalidUnreadRune}, + {0, 0, internal.RuneTypeEOF, nil}, + {0, 0, internal.RuneTypeEOF, nil}, + }}, + "tail-ws": {`{"foo": 12.0} `, ``, []ReadRuneTypeResult{ + {'{', 1, internal.RuneTypeObjectBeg, nil}, + {'"', 1, internal.RuneTypeStringBeg, nil}, + {'f', 1, internal.RuneTypeStringChar, nil}, + {'o', 1, internal.RuneTypeStringChar, nil}, + {'o', 1, internal.RuneTypeStringChar, nil}, + {'"', 1, internal.RuneTypeStringEnd, nil}, + {':', 1, internal.RuneTypeObjectColon, nil}, + {'1', 1, internal.RuneTypeNumberIntDig, nil}, + {'2', 1, internal.RuneTypeNumberIntDig, nil}, + {'.', 1, internal.RuneTypeNumberFracDot, nil}, + {'0', 1, internal.RuneTypeNumberFracDig, nil}, + {'}', 1, internal.RuneTypeObjectEnd, nil}, {0, 0, internal.RuneTypeEOF, nil}, {0, 0, internal.RuneTypeEOF, nil}, }}, @@ -151,63 +155,30 @@ func TestRuneTypeScanner(t *testing.T) { {'{', 1, internal.RuneTypeEOF, nil}, {'{', 1, internal.RuneTypeEOF, nil}, }}, - "early-eof": {`{`, ``, []ReadRuneTypeResult{ + "early-eof": {` {`, ``, []ReadRuneTypeResult{ {'{', 1, internal.RuneTypeObjectBeg, nil}, - {0, 0, internal.RuneTypeError, &DecodeSyntaxError{Offset: 1, Err: io.ErrUnexpectedEOF}}, - {0, 0, internal.RuneTypeError, &DecodeSyntaxError{Offset: 1, Err: io.ErrUnexpectedEOF}}, - {0, 0, internal.RuneTypeError, &DecodeSyntaxError{Offset: 1, Err: io.ErrUnexpectedEOF}}, + {0, 0, internal.RuneTypeError, &DecodeSyntaxError{Offset: 2, Err: io.ErrUnexpectedEOF}}, + {0, 0, internal.RuneTypeError, &DecodeSyntaxError{Offset: 2, Err: io.ErrUnexpectedEOF}}, + {0, 0, internal.RuneTypeError, &DecodeSyntaxError{Offset: 2, Err: io.ErrUnexpectedEOF}}, }}, "empty": {``, ``, []ReadRuneTypeResult{ {0, 0, internal.RuneTypeError, &DecodeSyntaxError{Offset: 0, Err: io.EOF}}, {0, 0, internal.RuneTypeError, &DecodeSyntaxError{Offset: 0, Err: io.EOF}}, {0, 0, internal.RuneTypeError, &DecodeSyntaxError{Offset: 0, Err: io.EOF}}, }}, - } - testRuneTypeScanner(t, testcases, func(reader io.RuneScanner) runeTypeScanner { - return &runeTypeScannerImpl{ - inner: reader, - } - }) -} - -func TestNoWSRuneTypeScanner(t *testing.T) { - t.Parallel() - testcases := map[string]runeTypeScannerTestcase{ - "basic": {`{"foo": 12.0}`, ``, []ReadRuneTypeResult{ - {'{', 1, internal.RuneTypeObjectBeg, nil}, - {'"', 1, internal.RuneTypeStringBeg, nil}, - {'f', 1, internal.RuneTypeStringChar, nil}, - {'o', 1, internal.RuneTypeStringChar, nil}, - {'o', 1, internal.RuneTypeStringChar, nil}, - {'"', 1, internal.RuneTypeStringEnd, nil}, - {':', 1, internal.RuneTypeObjectColon, nil}, + "basic2": {`1`, ``, []ReadRuneTypeResult{ {'1', 1, internal.RuneTypeNumberIntDig, nil}, - {'2', 1, internal.RuneTypeNumberIntDig, nil}, - {'.', 1, internal.RuneTypeNumberFracDot, nil}, - {'0', 1, internal.RuneTypeNumberFracDig, nil}, - {'}', 1, internal.RuneTypeObjectEnd, nil}, + {0, 0, internal.RuneTypeEOF, nil}, {0, 0, internal.RuneTypeEOF, nil}, {0, 0, internal.RuneTypeEOF, nil}, }}, - "unread": {`{"foo": 12.0}`, ``, []ReadRuneTypeResult{ - {'{', 1, internal.RuneTypeObjectBeg, nil}, - {'"', 1, internal.RuneTypeStringBeg, nil}, - {'f', 1, internal.RuneTypeStringChar, nil}, - {'o', 1, internal.RuneTypeStringChar, nil}, - {'o', 1, internal.RuneTypeStringChar, nil}, - {'"', 1, internal.RuneTypeStringEnd, nil}, - {':', 1, internal.RuneTypeObjectColon, nil}, + "fragment": {`1,`, ``, []ReadRuneTypeResult{ {'1', 1, internal.RuneTypeNumberIntDig, nil}, - {0, -1, 0, nil}, - {'1', 1, internal.RuneTypeNumberIntDig, nil}, - {'2', 1, internal.RuneTypeNumberIntDig, nil}, - {'.', 1, internal.RuneTypeNumberFracDot, nil}, - {'0', 1, internal.RuneTypeNumberFracDig, nil}, - {'}', 1, internal.RuneTypeObjectEnd, nil}, - {0, 0, internal.RuneTypeEOF, nil}, - {0, 0, internal.RuneTypeEOF, nil}, + {',', 1, internal.RuneTypeEOF, nil}, + {',', 1, internal.RuneTypeEOF, nil}, + {',', 1, internal.RuneTypeEOF, nil}, }}, - "tail": {`{"foo": 12.0} `, ``, []ReadRuneTypeResult{ + "elem": {` { "foo" : 12.0 } `, ``, []ReadRuneTypeResult{ {'{', 1, internal.RuneTypeObjectBeg, nil}, {'"', 1, internal.RuneTypeStringBeg, nil}, {'f', 1, internal.RuneTypeStringChar, nil}, @@ -215,178 +186,83 @@ func TestNoWSRuneTypeScanner(t *testing.T) { {'o', 1, internal.RuneTypeStringChar, nil}, {'"', 1, internal.RuneTypeStringEnd, nil}, {':', 1, internal.RuneTypeObjectColon, nil}, + {0, pushReadBarrier, 0, nil}, {'1', 1, internal.RuneTypeNumberIntDig, nil}, {'2', 1, internal.RuneTypeNumberIntDig, nil}, {'.', 1, internal.RuneTypeNumberFracDot, nil}, {'0', 1, internal.RuneTypeNumberFracDig, nil}, + {'}', 1, internal.RuneTypeEOF, nil}, + {'}', 1, internal.RuneTypeEOF, nil}, + {0, popReadBarrier, 0, nil}, {'}', 1, internal.RuneTypeObjectEnd, nil}, {0, 0, internal.RuneTypeEOF, nil}, {0, 0, internal.RuneTypeEOF, nil}, }}, - "multi-value": {`1{}`, `}`, []ReadRuneTypeResult{ - {'1', 1, internal.RuneTypeNumberIntDig, nil}, - {'{', 1, internal.RuneTypeEOF, nil}, - {'{', 1, internal.RuneTypeEOF, nil}, - {'{', 1, internal.RuneTypeEOF, nil}, - }}, - "early-eof": {` {`, ``, []ReadRuneTypeResult{ - {'{', 1, internal.RuneTypeObjectBeg, nil}, - {0, 0, internal.RuneTypeError, &DecodeSyntaxError{Offset: 2, Err: io.ErrUnexpectedEOF}}, - {0, 0, internal.RuneTypeError, &DecodeSyntaxError{Offset: 2, Err: io.ErrUnexpectedEOF}}, - {0, 0, internal.RuneTypeError, &DecodeSyntaxError{Offset: 2, Err: io.ErrUnexpectedEOF}}, - }}, - } - testRuneTypeScanner(t, testcases, func(reader io.RuneScanner) runeTypeScanner { - return &noWSRuneTypeScanner{ - inner: &runeTypeScannerImpl{ - inner: reader, - }, - } - }) -} - -func TestElemRuneTypeScanner(t *testing.T) { - t.Parallel() - toplevelTestcases := map[string]runeTypeScannerTestcase{ - "basic": {`1`, ``, []ReadRuneTypeResult{ - {'1', 1, internal.RuneTypeNumberIntDig, nil}, - {0, 0, internal.RuneTypeEOF, nil}, - {0, 0, internal.RuneTypeEOF, nil}, - {0, 0, internal.RuneTypeEOF, nil}, - }}, - "syntax-error": {`[[0,]`, ``, []ReadRuneTypeResult{ - {'[', 1, internal.RuneTypeArrayBeg, nil}, - {'[', 1, internal.RuneTypeArrayBeg, nil}, - {'0', 1, internal.RuneTypeNumberIntZero, nil}, - {',', 1, internal.RuneTypeArrayComma, nil}, - {']', 1, internal.RuneTypeError, &DecodeSyntaxError{Offset: 5, Err: fmt.Errorf("invalid character %q looking for beginning of value", ']')}}, - {']', 1, internal.RuneTypeError, &DecodeSyntaxError{Offset: 5, Err: fmt.Errorf("invalid character %q looking for beginning of value", ']')}}, - {']', 1, internal.RuneTypeError, &DecodeSyntaxError{Offset: 5, Err: fmt.Errorf("invalid character %q looking for beginning of value", ']')}}, - }}, - "multi-value": {`1{}`, `{}`, []ReadRuneTypeResult{ - {'1', 1, internal.RuneTypeNumberIntDig, nil}, - {0, 0, internal.RuneTypeEOF, nil}, - {0, 0, internal.RuneTypeEOF, nil}, - {0, 0, internal.RuneTypeEOF, nil}, - }}, - "fragment": {`1,`, `,`, []ReadRuneTypeResult{ - {'1', 1, internal.RuneTypeNumberIntDig, nil}, - {0, 0, internal.RuneTypeEOF, nil}, - {0, 0, internal.RuneTypeEOF, nil}, - {0, 0, internal.RuneTypeEOF, nil}, - }}, - "early-eof": {`{`, ``, []ReadRuneTypeResult{ - {'{', 1, internal.RuneTypeObjectBeg, nil}, - {0, 0, internal.RuneTypeError, &DecodeSyntaxError{Offset: 1, Err: io.ErrUnexpectedEOF}}, - {0, 0, internal.RuneTypeError, &DecodeSyntaxError{Offset: 1, Err: io.ErrUnexpectedEOF}}, - {0, 0, internal.RuneTypeError, &DecodeSyntaxError{Offset: 1, Err: io.ErrUnexpectedEOF}}, - }}, } - - childTestcases := make(map[string]runeTypeScannerTestcase, len(toplevelTestcases)) - for tcName, tc := range toplevelTestcases { - tc.Input = `[` + tc.Input - tc.Exp = append([]ReadRuneTypeResult(nil), tc.Exp...) // copy - for i, res := range tc.Exp { - if se, ok := res.e.(*DecodeSyntaxError); ok { - seCopy := *se - seCopy.Offset++ - tc.Exp[i].e = &seCopy + func() { + childTestcases := make(map[string]runeTypeScannerTestcase) + for tcName, tc := range testcases { + canChild := true + for _, res := range tc.Exp { + if res.s == pushReadBarrier { + canChild = false + break + } } - } - childTestcases[tcName] = tc - } - - t.Run("top-level", func(t *testing.T) { - t.Parallel() - testRuneTypeScanner(t, toplevelTestcases, func(reader io.RuneScanner) runeTypeScanner { - return &elemRuneTypeScanner{ - inner: &noWSRuneTypeScanner{ - inner: &runeTypeScannerImpl{ - inner: reader, - }, - }, + if !canChild { + continue } - }) - }) - t.Run("child", func(t *testing.T) { - t.Parallel() - testRuneTypeScanner(t, childTestcases, func(reader io.RuneScanner) runeTypeScanner { - inner := &noWSRuneTypeScanner{ - inner: &runeTypeScannerImpl{ - inner: reader, - }, + tc.Input = `[1,` + tc.Input + tc.Exp = append([]ReadRuneTypeResult{ + {'[', 1, internal.RuneTypeArrayBeg, nil}, + {'1', 1, internal.RuneTypeNumberIntDig, nil}, + {',', 1, internal.RuneTypeArrayComma, nil}, + {0, pushReadBarrier, 0, nil}, + }, tc.Exp...) + for i := 2; i < len(tc.Exp); i++ { + if se, ok := tc.Exp[i].e.(*DecodeSyntaxError); ok { + seCopy := *se + seCopy.Offset += 3 + tc.Exp[i].e = &seCopy + } } - var res ReadRuneTypeResult - res.r, res.s, res.t, res.e = inner.ReadRuneType() - require.Equal(t, - ReadRuneTypeResult{'[', 1, internal.RuneTypeArrayBeg, nil}.String(), - res.String()) - - return &elemRuneTypeScanner{ - inner: inner, + childTestcases["child-"+tcName] = tc + } + for tcName, tc := range childTestcases { + testcases[tcName] = tc + } + }() + for tcName, tc := range testcases { + tc := tc + t.Run(tcName, func(t *testing.T) { + t.Parallel() + t.Logf("input=%q", tc.Input) + reader := strings.NewReader(tc.Input) + sc := &runeTypeScanner{inner: reader} + var exp, act []string + for _, iExp := range tc.Exp { + var iAct ReadRuneTypeResult + switch iExp.s { + case unreadRune: + iAct.s = iExp.s + iAct.e = sc.UnreadRune() + case pushReadBarrier: + sc.PushReadBarrier() + iAct.s = iExp.s + case popReadBarrier: + sc.PopReadBarrier() + iAct.s = iExp.s + case reset: + sc.Reset() + iAct.s = iExp.s + default: + 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) + assert.Equal(t, tc.ExpRemainder, tc.Input[len(tc.Input)-reader.Len():]) }) - }) -} - -func TestElemRuneTypeScanner2(t *testing.T) { - t.Parallel() - parent := &noWSRuneTypeScanner{ - inner: &runeTypeScannerImpl{ - inner: strings.NewReader(` { "foo" : 12.0 } `), - }, - } - exp := []ReadRuneTypeResult{ - {'{', 1, internal.RuneTypeObjectBeg, nil}, - {'"', 1, internal.RuneTypeStringBeg, nil}, - {'f', 1, internal.RuneTypeStringChar, nil}, - {'o', 1, internal.RuneTypeStringChar, nil}, - {'o', 1, internal.RuneTypeStringChar, nil}, - {'"', 1, internal.RuneTypeStringEnd, nil}, - {':', 1, internal.RuneTypeObjectColon, nil}, - } - expStr := make([]string, 0, len(exp)) - actStr := make([]string, 0, len(exp)) - 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, internal.RuneTypeNumberIntDig, nil}, - {'2', 1, internal.RuneTypeNumberIntDig, nil}, - {'.', 1, internal.RuneTypeNumberFracDot, nil}, - {'0', 1, internal.RuneTypeNumberFracDig, nil}, - {0, 0, internal.RuneTypeEOF, nil}, - {0, 0, internal.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, internal.RuneTypeObjectEnd, nil}, - {0, 0, internal.RuneTypeEOF, nil}, - {0, 0, internal.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) } } |