all repos

clerk @ 9ee2ed8

missing tooling for ledger/hledger

clerk/journal/parser/parser_test.go (view raw)

Oleksandr Smirnov Oleksandr Smirnov
olexsmir@gmail.com
lexer: fix include path lexer..., 2 months ago
1
package parser
2
3
import (
4
	"testing"
5
6
	"olexsmir.xyz/clerk/internal/testutil/golden"
7
	"olexsmir.xyz/clerk/journal/ast"
8
	"olexsmir.xyz/clerk/journal/lexer"
9
)
10
11
func TestParser_ParseFile(t *testing.T) {
12
	tests := []string{
13
		"blank line",
14
		"comment semicolon",
15
		"comment hash",
16
		"comment percent",
17
		"comment star",
18
		"alias directive",
19
		"tag directive",
20
		"year directive",
21
		"decimal-mark directive",
22
		"C directive",
23
		"D directive",
24
		"P directive",
25
		"P directive with time",
26
		"N directive",
27
		"inclue directive",
28
		"inclue directive with comment",
29
		"inclue directive digit path",
30
		"apply tag directive",
31
		"apply fixed directive",
32
		"end apply directive",
33
		"comment directive",
34
		"comment directive end alone",
35
		"comment directive with header",
36
		"empty comment block",
37
		"never ending comment directive",
38
		"nested apply tag directives",
39
		"account directive",
40
		"account directive with comment",
41
		"account with subdirectives",
42
		"comodity directive",
43
		"comodity directive word",
44
		"comodity directive no space",
45
		"commodity quantity first",
46
		"commodity quantity after",
47
		"commodity with subdirectives",
48
		"payee directive",
49
		"transaction",
50
		"automated transaction",
51
		"transaction with payee",
52
		"transaction with digit payees",
53
		"account with spaces",
54
		"transaction with multiword payee",
55
		"transaction pending",
56
		"transaction clearerd",
57
		"transaction with note",
58
		"transaction with comment",
59
		"transaction with secondary date",
60
		"transaction with costs",
61
		"transaction with cost and assertion",
62
		"transaction with posting",
63
		"transaction with unicode commodity symbols",
64
		"transaction with quoted commodity",
65
		"transaction with strange commodity symbols",
66
		"special chars in description",
67
		"transaction with tabs",
68
		"transaction in ukrainian",
69
		"transaction with code",
70
		"transaction with posting amounts",
71
		"transaction with spaced account name",
72
		"transaction with inline comment",
73
		"transaction with header comment",
74
		"transaction with trailing indent",
75
		"transaction with balance assertion",
76
		"transaction with inclusive balance assertion",
77
		"transaction with balance assignment",
78
		"transaction with scientific amounts",
79
		"transaction with virtual accounts",
80
		"virtual postings with statuses",
81
		"period transaction expressions",
82
		"unexpected token",
83
		"recovery after bad posting",
84
		"illegal only",
85
		"illegal at start",
86
		"illegal in posting",
87
		"illegal between transactions",
88
		"multiple bad postings",
89
		"three bad postings",
90
		"quoted payee names",
91
		"digit group marks",
92
		"directive prefixes",
93
		"bad between good",
94
		"all postings bad",
95
		"bad then next transaction",
96
		"comment between bad postings",
97
		"bad posting at end",
98
	}
99
	for _, tt := range tests {
100
		t.Run(tt, func(t *testing.T) {
101
			a := golden.Read(t, tt)
102
			l := lexer.New("j", a.Get("input"))
103
			f := New(l).ParseJournal()
104
			golden.Assert(t, a, ast.Dump(f))
105
		})
106
	}
107
}
108
109
func FuzzParser(f *testing.F) {
110
	f.Add([]byte(""))
111
	f.Add([]byte("account expenses:food\n"))
112
	f.Add([]byte("account a\n  ; subdirective\n"))
113
	f.Add([]byte("commodity 1,000.00 UAH\n"))
114
	f.Add([]byte("include other.journal\n"))
115
	f.Add([]byte("alias checking = assets:bank:checking\n"))
116
	f.Add([]byte("2024/01/01 * groceries\n    expenses:food  $10.00\n    assets:checking\n"))
117
	f.Add([]byte("2024/01/01=2024/01/02 groceries\n"))
118
	f.Add([]byte("2024/01/01 groceries\n  expenses:food  $10.00\n  assets:checking\n"))
119
	f.Add([]byte("2008/06/03 * eat & shop\n    expenses:food      $1\n    expenses:supplies  $1\n    assets:cash\n"))
120
	f.Add([]byte("2015-01-03 * Money exchange office\n    Assets:Cash  -20 EUR @ 7.53 HRK\n    Assets:Cash  150.60 HRK\n"))
121
	f.Add([]byte("2024/01/01 t ; inline comment\n  a  $10\n"))
122
	f.Add([]byte("2024/01/01 t\n  (a)  10 @@ $20\n  [b]  30\n"))
123
	f.Add([]byte("2024/01/01 ß\n  (ß)  10 ß\n"))
124
	f.Add([]byte("2024/01/01 t\n  (! a)  10\n"))
125
	f.Add([]byte("2024/01/01 t\n  a  $10 == $10\n"))
126
	f.Add([]byte("  2024/01/01 t\n  a  $10\n"))
127
	f.Add([]byte("P 2024/01/01 USD 41.50 UAH\n"))
128
	f.Add([]byte("P 2024-01-01 12:00:00 USD 41.50 UAH\n"))
129
	f.Add([]byte("P 2024-01-01 12:00 USD 41.50 UAH\n"))
130
	f.Add([]byte("~ monthly\n    expenses:food  $100\n    assets:checking\n"))
131
	f.Add([]byte("= /^Income/\n  expenses:food  $10\n"))
132
	f.Add([]byte("; a comment\n"))
133
	f.Add([]byte("comment\nbody\nend\n"))
134
	f.Add([]byte("\n\n\n"))
135
	f.Add([]byte("перевірка\n"))
136
	f.Add([]byte("N $\n"))
137
	f.Add([]byte("apply tag foo\nend\n"))
138
	f.Add([]byte("@@@\n"))
139
	f.Add([]byte("   \n"))
140
	f.Add([]byte("0\n"))
141
	f.Add([]byte{0xff, 0xfe, 0x00})
142
143
	f.Fuzz(func(t *testing.T, data []byte) {
144
		l := lexer.New("j", data)
145
		j := New(l).ParseJournal()
146
		if j == nil {
147
			t.Fatalf("nil journal for input %q", string(data))
148
		}
149
150
		// error spans must be in bounds (allow sentinel extension past input)
151
		dataLen := len(data)
152
		for _, e := range j.Errors {
153
			if e.Span.Start.Offset < 0 {
154
				t.Fatal("error span start is negative")
155
			}
156
			if e.Span.End.Offset > dataLen+1 {
157
				t.Fatalf("error span end out of bounds: [%d,%d] len=%d", e.Span.Start.Offset, e.Span.End.Offset, dataLen)
158
			}
159
			if len(e.Message) == 0 {
160
				t.Fatal("empty error message")
161
			}
162
		}
163
164
		// dump must not panic and must be deterministic
165
		dump1, dump2 := ast.Dump(j), ast.Dump(j)
166
		if dump1 != dump2 {
167
			t.Fatal("non-deterministic dump")
168
		}
169
	})
170
}