Skip to content

Commit 8c64fa6

Browse files
Add tests for parse errors, will need to be updated as parser is developed (#75)
* Add tests for parse errors, will need to be updated as parser is developed * go get -u ./...
1 parent 0aa8c3b commit 8c64fa6

19 files changed

+210
-48
lines changed

.rumdl.toml

Lines changed: 0 additions & 34 deletions
This file was deleted.

Taskfile.yml

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -30,16 +30,11 @@ tasks:
3030

3131
fmt:
3232
desc: Run go fmt on all source files
33-
preconditions:
34-
- sh: command -v rumdl
35-
msg: rumdl not installed, see https://github.com/rvben/rumdl#installation
3633
sources:
3734
- "**/*.go"
3835
- .golangci.yml
39-
- "**/*.md"
4036
cmds:
4137
- golangci-lint fmt ./...
42-
- rumdl fmt --fix .
4338

4439
test:
4540
desc: Run the test suite
@@ -100,17 +95,12 @@ tasks:
10095
sources:
10196
- "**/*.go"
10297
- .golangci.yml
103-
- "**/*.md"
10498
preconditions:
105-
- sh: command -v rumdl
106-
msg: rumdl not installed, see https://github.com/rvben/rumdl#installation
107-
10899
- sh: command -v typos
109100
msg: requires typos-cli, run `brew install typos-cli`
110101
cmds:
111102
- golangci-lint run --fix
112103
- typos
113-
- rumdl check --fix .
114104

115105
doc:
116106
desc: Render the pkg docs locally

go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ require (
2828
github.com/charmbracelet/lipgloss v1.1.0 // indirect
2929
github.com/charmbracelet/x/ansi v0.11.1 // indirect
3030
github.com/charmbracelet/x/cellbuf v0.0.14 // indirect
31-
github.com/charmbracelet/x/exp/strings v0.0.0-20251117104201-863d1fef9cfb // indirect
31+
github.com/charmbracelet/x/exp/strings v0.0.0-20251118172736-77d017256798 // indirect
3232
github.com/charmbracelet/x/term v0.2.2 // indirect
3333
github.com/clipperhouse/displaywidth v0.6.0 // indirect
3434
github.com/clipperhouse/stringish v0.1.1 // indirect

go.sum

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,8 @@ github.com/charmbracelet/x/errors v0.0.0-20240508181413-e8d8b6e2de86 h1:JSt3B+U9
3030
github.com/charmbracelet/x/errors v0.0.0-20240508181413-e8d8b6e2de86/go.mod h1:2P0UgXMEa6TsToMSuFqKFQR+fZTO9CNGUNokkPatT/0=
3131
github.com/charmbracelet/x/exp/golden v0.0.0-20241011142426-46044092ad91 h1:payRxjMjKgx2PaCWLZ4p3ro9y97+TVLZNaRZgJwSVDQ=
3232
github.com/charmbracelet/x/exp/golden v0.0.0-20241011142426-46044092ad91/go.mod h1:wDlXFlCrmJ8J+swcL/MnGUuYnqgQdW9rhSD61oNMb6U=
33-
github.com/charmbracelet/x/exp/strings v0.0.0-20251117104201-863d1fef9cfb h1:bKbwRftwpZ3sN1karNah6eIjlaMeE1FKv7INGRFSv1g=
34-
github.com/charmbracelet/x/exp/strings v0.0.0-20251117104201-863d1fef9cfb/go.mod h1:/ehtMPNh9K4odGFkqYJKpIYyePhdp1hLBRvyY4bWkH8=
33+
github.com/charmbracelet/x/exp/strings v0.0.0-20251118172736-77d017256798 h1:g0RVaqkUdTikWLqrBdk2ZvJ9oTQOS0HZlYjYE8Tu7yg=
34+
github.com/charmbracelet/x/exp/strings v0.0.0-20251118172736-77d017256798/go.mod h1:/ehtMPNh9K4odGFkqYJKpIYyePhdp1hLBRvyY4bWkH8=
3535
github.com/charmbracelet/x/term v0.2.2 h1:xVRT/S2ZcKdhhOuSP4t5cLi5o+JxklsoEObBSgfgZRk=
3636
github.com/charmbracelet/x/term v0.2.2/go.mod h1:kF8CY5RddLWrsgVwpw4kAa6TESp6EB5y3uxGLeCqzAI=
3737
github.com/charmbracelet/x/termios v0.1.1 h1:o3Q2bT8eqzGnGPOYheoYS8eEleT5ZVNYNy8JawjaNZY=

internal/syntax/parser/v2/parser_test.go

Lines changed: 111 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,22 @@
22
package parser_test
33

44
import (
5+
"bytes"
56
"flag"
7+
"fmt"
68
"os"
79
"path/filepath"
10+
"slices"
11+
"strings"
12+
"sync"
813
"testing"
914

15+
"github.com/rogpeppe/go-internal/txtar"
1016
"go.followtheprocess.codes/snapshot"
1117
"go.followtheprocess.codes/test"
1218
"go.followtheprocess.codes/zap/internal/syntax"
1319
"go.followtheprocess.codes/zap/internal/syntax/parser/v2"
20+
"go.uber.org/goleak"
1421
)
1522

1623
var (
@@ -19,7 +26,7 @@ var (
1926
)
2027

2128
func TestParse(t *testing.T) {
22-
pattern := filepath.Join("testdata", "src", "*.http")
29+
pattern := filepath.Join("testdata", "valid", "*.http")
2330
files, err := filepath.Glob(pattern)
2431
test.Ok(t, err)
2532

@@ -48,6 +55,72 @@ func TestParse(t *testing.T) {
4855
}
4956
}
5057

58+
// TestInvalid is the primary test for invalid syntax. It does much the same as TestParse
59+
// but instead of failing tests if a syntax error is encounter, it fails if there is not any syntax errors.
60+
//
61+
// Additionally, the errors are compared against a reference.
62+
func TestInvalid(t *testing.T) {
63+
// Force colour for diffs but only locally
64+
test.ColorEnabled(os.Getenv("CI") == "")
65+
66+
pattern := filepath.Join("testdata", "invalid", "*.txtar")
67+
files, err := filepath.Glob(pattern)
68+
test.Ok(t, err)
69+
70+
for _, file := range files {
71+
name := filepath.Base(file)
72+
t.Run(name, func(t *testing.T) {
73+
defer goleak.VerifyNone(t)
74+
75+
archive, err := txtar.ParseFile(file)
76+
test.Ok(t, err)
77+
78+
test.Equal(
79+
t,
80+
len(archive.Files),
81+
2,
82+
test.Context("%s should contain 2 files, got %d", file, len(archive.Files)),
83+
)
84+
test.Equal(
85+
t,
86+
archive.Files[0].Name,
87+
"src.http",
88+
test.Context("first file should be named 'src.http', got %q", archive.Files[0].Name),
89+
)
90+
test.Equal(
91+
t,
92+
archive.Files[1].Name,
93+
"want.txt",
94+
test.Context("second file should be named 'want.txt', got %q", archive.Files[1].Name),
95+
)
96+
97+
src := archive.Files[0].Data
98+
want := archive.Files[1].Data
99+
100+
collector := &errorCollector{}
101+
102+
parser, err := parser.New(name, bytes.NewReader(src), collector.handler())
103+
test.Ok(t, err)
104+
105+
_, err = parser.Parse()
106+
test.Err(t, err, test.Context("Parse() failed to return an error given invalid syntax"))
107+
108+
got := collector.String()
109+
110+
if *update {
111+
archive.Files[1].Data = []byte(got)
112+
113+
err := os.WriteFile(file, txtar.Format(archive), 0o644)
114+
test.Ok(t, err)
115+
116+
return
117+
}
118+
119+
test.DiffBytes(t, []byte(got), want)
120+
})
121+
}
122+
}
123+
51124
// testFailHandler returns a [syntax.ErrorHandler] that handles syntax errors by failing
52125
// the enclosing test.
53126
func testFailHandler(tb testing.TB) syntax.ErrorHandler {
@@ -57,3 +130,40 @@ func testFailHandler(tb testing.TB) syntax.ErrorHandler {
57130
tb.Fatalf("%s: %s", pos, msg)
58131
}
59132
}
133+
134+
// errorCollector is a helper struct that implements a [syntax.ErrorHandler] which
135+
// simply collects the scanning errors internally to be inspected later.
136+
type errorCollector struct {
137+
errs []string
138+
mu sync.RWMutex
139+
}
140+
141+
func (e *errorCollector) String() string {
142+
e.mu.RLock()
143+
defer e.mu.RUnlock()
144+
145+
// Take a copy so as not to alter the original
146+
errsCopy := slices.Clone(e.errs)
147+
148+
var s strings.Builder
149+
150+
slices.Sort(errsCopy) // Deterministic
151+
152+
for _, err := range errsCopy {
153+
s.WriteString(err)
154+
}
155+
156+
return s.String()
157+
}
158+
159+
// handler returns the [syntax.ErrorHandler] to be plugged in to the scanning operation.
160+
func (e *errorCollector) handler() syntax.ErrorHandler {
161+
return func(pos syntax.Position, msg string) {
162+
// Because the scanner runs in it's own goroutine and also makes use of the
163+
// handler
164+
e.mu.Lock()
165+
defer e.mu.Unlock()
166+
167+
e.errs = append(e.errs, fmt.Sprintf("%s: %s\n", pos, msg))
168+
}
169+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
-- src.http --
2+
### Bad
3+
GET https://blowup.com
4+
5+
< £$%^&
6+
-- want.txt --
7+
bad-body-file.txtar:1:1-4: parseStatement: unrecognised token: Separator
8+
bad-body-file.txtar:4:5: unrecognised character: '£'
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
# Content-Type is missing a colon
2+
3+
-- src.http --
4+
### Bad
5+
GET https://api.something.com/v1
6+
Content-Type application/json
7+
-- want.txt --
8+
bad-headers.txtar:1:1-4: parseStatement: unrecognised token: Separator
9+
bad-headers.txtar:3:13: expected ':', got ' '
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
# This one tests our parser synchronisation method, the request after BadMethod
2+
# is fine but if the parser is cascading errors (not synchronising) then it will
3+
# report a bunch of errors in this one too as it will be confused and expecting
4+
# different tokens
5+
6+
-- src.http --
7+
### BadMethod
8+
DESTROY https://api.something.com/v1
9+
10+
### Okay
11+
GET https://api.somethingelse.com/v1
12+
-- want.txt --
13+
bad-method.txtar:1:1-4: parseStatement: unrecognised token: Separator
14+
bad-method.txtar:4:1-4: parseStatement: unrecognised token: Separator
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
-- src.http --
2+
### Bad
3+
GET https://blowup.com
4+
5+
{
6+
"hello": "body"
7+
}
8+
9+
> )(*&^%
10+
-- want.txt --
11+
bad-response-file.txtar:1:1-4: parseStatement: unrecognised token: Separator
12+
bad-response-file.txtar:8:4: unrecognised character: ')'
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
-- src.http --
2+
### Bad
3+
GET https://blowup.com
4+
5+
{
6+
"hello": "body"
7+
}
8+
9+
<> )(*&^%
10+
-- want.txt --
11+
bad-response-ref.txtar:1:1-4: parseStatement: unrecognised token: Separator
12+
bad-response-ref.txtar:8:5: unrecognised character: ')'

0 commit comments

Comments
 (0)