1
2
3
4
5 package catmsg
6
7 import (
8 "errors"
9 "strings"
10 "testing"
11
12 "golang.org/x/text/language"
13 )
14
15 type renderer struct {
16 args []int
17 result string
18 }
19
20 func (r *renderer) Arg(i int) interface{} {
21 if i >= len(r.args) {
22 return nil
23 }
24 return r.args[i]
25 }
26
27 func (r *renderer) Render(s string) {
28 if r.result != "" {
29 r.result += "|"
30 }
31 r.result += s
32 }
33
34 func TestCodec(t *testing.T) {
35 type test struct {
36 args []int
37 out string
38 decErr string
39 }
40 single := func(out, err string) []test { return []test{{out: out, decErr: err}} }
41 testCases := []struct {
42 desc string
43 m Message
44 enc string
45 encErr string
46 tests []test
47 }{{
48 desc: "unused variable",
49 m: &Var{"name", String("foo")},
50 encErr: errIsVar.Error(),
51 tests: single("", ""),
52 }, {
53 desc: "empty",
54 m: empty{},
55 tests: single("", ""),
56 }, {
57 desc: "sequence with empty",
58 m: seq{empty{}},
59 tests: single("", ""),
60 }, {
61 desc: "raw string",
62 m: Raw("foo"),
63 tests: single("foo", ""),
64 }, {
65 desc: "raw string no sub",
66 m: Raw("${foo}"),
67 enc: "\x02${foo}",
68 tests: single("${foo}", ""),
69 }, {
70 desc: "simple string",
71 m: String("foo"),
72 tests: single("foo", ""),
73 }, {
74 desc: "affix",
75 m: &Affix{String("foo"), "\t", "\n"},
76 tests: single("\t|foo|\n", ""),
77 }, {
78 desc: "missing var",
79 m: String("foo${bar}"),
80 enc: "\x03\x03foo\x02\x03bar",
81 encErr: `unknown var "bar"`,
82 tests: single("foo|bar", ""),
83 }, {
84 desc: "empty var",
85 m: seq{
86 &Var{"bar", seq{}},
87 String("foo${bar}"),
88 },
89 enc: "\x00\x05\x04\x02bar\x03\x03foo\x00\x00",
90
91 tests: single("foo|bar", ""),
92 }, {
93 desc: "var after value",
94 m: seq{
95 String("foo${bar}"),
96 &Var{"bar", String("baz")},
97 },
98 encErr: errIsVar.Error(),
99 tests: single("foo|bar", ""),
100 }, {
101 desc: "substitution",
102 m: seq{
103 &Var{"bar", String("baz")},
104 String("foo${bar}"),
105 },
106 tests: single("foo|baz", ""),
107 }, {
108 desc: "affix with substitution",
109 m: &Affix{seq{
110 &Var{"bar", String("baz")},
111 String("foo${bar}"),
112 }, "\t", "\n"},
113 tests: single("\t|foo|baz|\n", ""),
114 }, {
115 desc: "shadowed variable",
116 m: seq{
117 &Var{"bar", String("baz")},
118 seq{
119 &Var{"bar", String("BAZ")},
120 String("foo${bar}"),
121 },
122 },
123 tests: single("foo|BAZ", ""),
124 }, {
125 desc: "nested value",
126 m: nestedLang{nestedLang{empty{}}},
127 tests: single("nl|nl", ""),
128 }, {
129 desc: "not shadowed variable",
130 m: seq{
131 &Var{"bar", String("baz")},
132 seq{
133 String("foo${bar}"),
134 &Var{"bar", String("BAZ")},
135 },
136 },
137 encErr: errIsVar.Error(),
138 tests: single("foo|baz", ""),
139 }, {
140 desc: "duplicate variable",
141 m: seq{
142 &Var{"bar", String("baz")},
143 &Var{"bar", String("BAZ")},
144 String("${bar}"),
145 },
146 encErr: "catmsg: duplicate variable \"bar\"",
147 tests: single("baz", ""),
148 }, {
149 desc: "complete incomplete variable",
150 m: seq{
151 &Var{"bar", incomplete{}},
152 String("${bar}"),
153 },
154 enc: "\x00\t\b\x01\x01\x14\x04\x02bar\x03\x00\x00\x00",
155
156 tests: single("bar", ""),
157 }, {
158 desc: "incomplete sequence",
159 m: seq{
160 incomplete{},
161 incomplete{},
162 },
163 encErr: ErrIncomplete.Error(),
164 tests: single("", ErrNoMatch.Error()),
165 }, {
166 desc: "compile error variable",
167 m: seq{
168 &Var{"bar", errorCompileMsg{}},
169 String("${bar}"),
170 },
171 encErr: errCompileTest.Error(),
172 tests: single("bar", ""),
173 }, {
174 desc: "compile error message",
175 m: errorCompileMsg{},
176 encErr: errCompileTest.Error(),
177 tests: single("", ""),
178 }, {
179 desc: "compile error sequence",
180 m: seq{
181 errorCompileMsg{},
182 errorCompileMsg{},
183 },
184 encErr: errCompileTest.Error(),
185 tests: single("", ""),
186 }, {
187 desc: "macro",
188 m: String("${exists(1)}"),
189 tests: single("you betya!", ""),
190 }, {
191 desc: "macro incomplete",
192 m: String("${incomplete(1)}"),
193 enc: "\x03\x00\x01\nincomplete\x01",
194 tests: single("incomplete", ""),
195 }, {
196 desc: "macro undefined at end",
197 m: String("${undefined(1)}"),
198 enc: "\x03\x00\x01\tundefined\x01",
199 tests: single("undefined", "catmsg: undefined macro \"undefined\""),
200 }, {
201 desc: "macro undefined with more text following",
202 m: String("${undefined(1)}."),
203 enc: "\x03\x00\x01\tundefined\x01\x01.",
204 tests: single("undefined|.", "catmsg: undefined macro \"undefined\""),
205 }, {
206 desc: "macro missing paren",
207 m: String("${missing(1}"),
208 encErr: "catmsg: missing ')'",
209 tests: single("$!(MISSINGPAREN)", ""),
210 }, {
211 desc: "macro bad num",
212 m: String("aa${bad(a)}"),
213 encErr: "catmsg: invalid number \"a\"",
214 tests: single("aa$!(BADNUM)", ""),
215 }, {
216 desc: "var missing brace",
217 m: String("a${missing"),
218 encErr: "catmsg: missing '}'",
219 tests: single("a$!(MISSINGBRACE)", ""),
220 }}
221 r := &renderer{}
222 dec := NewDecoder(language.Und, r, macros)
223 for _, tc := range testCases {
224 t.Run(tc.desc, func(t *testing.T) {
225
226
227 data, err := Compile(language.Dutch, macros, tc.m)
228 if failErr(err, tc.encErr) {
229 t.Errorf("encoding error: got %+q; want %+q", err, tc.encErr)
230 }
231 if tc.enc != "" && data != tc.enc {
232 t.Errorf("encoding: got %+q; want %+q", data, tc.enc)
233 }
234 for _, st := range tc.tests {
235 t.Run("", func(t *testing.T) {
236 *r = renderer{args: st.args}
237 if err = dec.Execute(data); failErr(err, st.decErr) {
238 t.Errorf("decoding error: got %+q; want %+q", err, st.decErr)
239 }
240 if r.result != st.out {
241 t.Errorf("decode: got %+q; want %+q", r.result, st.out)
242 }
243 })
244 }
245 })
246 }
247 }
248
249 func failErr(got error, want string) bool {
250 if got == nil {
251 return want != ""
252 }
253 return want == "" || !strings.Contains(got.Error(), want)
254 }
255
256 type seq []Message
257
258 func (s seq) Compile(e *Encoder) (err error) {
259 err = ErrIncomplete
260 e.EncodeMessageType(msgFirst)
261 for _, m := range s {
262
263
264 err = e.EncodeMessage(m)
265 }
266 return err
267 }
268
269 type empty struct{}
270
271 func (empty) Compile(e *Encoder) (err error) { return nil }
272
273 var msgIncomplete = Register(
274 "golang.org/x/text/internal/catmsg.incomplete",
275 func(d *Decoder) bool { return false })
276
277 type incomplete struct{}
278
279 func (incomplete) Compile(e *Encoder) (err error) {
280 e.EncodeMessageType(msgIncomplete)
281 return ErrIncomplete
282 }
283
284 var msgNested = Register(
285 "golang.org/x/text/internal/catmsg.nested",
286 func(d *Decoder) bool {
287 d.Render(d.DecodeString())
288 d.ExecuteMessage()
289 return true
290 })
291
292 type nestedLang struct{ Message }
293
294 func (n nestedLang) Compile(e *Encoder) (err error) {
295 e.EncodeMessageType(msgNested)
296 e.EncodeString(e.Language().String())
297 e.EncodeMessage(n.Message)
298 return nil
299 }
300
301 type errorCompileMsg struct{}
302
303 var errCompileTest = errors.New("catmsg: compile error test")
304
305 func (errorCompileMsg) Compile(e *Encoder) (err error) {
306 return errCompileTest
307 }
308
309 type dictionary struct{}
310
311 var (
312 macros = dictionary{}
313 dictMessages = map[string]string{
314 "exists": compile(String("you betya!")),
315 "incomplete": compile(incomplete{}),
316 }
317 )
318
319 func (d dictionary) Lookup(key string) (data string, ok bool) {
320 data, ok = dictMessages[key]
321 return
322 }
323
324 func compile(m Message) (data string) {
325 data, _ = Compile(language.Und, macros, m)
326 return data
327 }
328
View as plain text