1// Copyright 2017 The Go Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style
3// license that can be found in the LICENSE file.
4
5package catmsg
6
7import (
8	"errors"
9	"strings"
10	"testing"
11
12	"golang.org/x/text/language"
13)
14
15type renderer struct {
16	args   []int
17	result string
18}
19
20func (r *renderer) Arg(i int) interface{} {
21	if i >= len(r.args) {
22		return nil
23	}
24	return r.args[i]
25}
26
27func (r *renderer) Render(s string) {
28	if r.result != "" {
29		r.result += "|"
30	}
31	r.result += s
32}
33
34func 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		// TODO: recognize that it is cheaper to substitute bar.
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		// TODO: recognize that it is cheaper to substitute bar.
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			// Use a language other than Und so that we can test
226			// passing the language to nested values.
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
249func failErr(got error, want string) bool {
250	if got == nil {
251		return want != ""
252	}
253	return want == "" || !strings.Contains(got.Error(), want)
254}
255
256type seq []Message
257
258func (s seq) Compile(e *Encoder) (err error) {
259	err = ErrIncomplete
260	e.EncodeMessageType(msgFirst)
261	for _, m := range s {
262		// Pass only the last error, but allow erroneous or complete messages
263		// here to allow testing different scenarios.
264		err = e.EncodeMessage(m)
265	}
266	return err
267}
268
269type empty struct{}
270
271func (empty) Compile(e *Encoder) (err error) { return nil }
272
273var msgIncomplete = Register(
274	"golang.org/x/text/internal/catmsg.incomplete",
275	func(d *Decoder) bool { return false })
276
277type incomplete struct{}
278
279func (incomplete) Compile(e *Encoder) (err error) {
280	e.EncodeMessageType(msgIncomplete)
281	return ErrIncomplete
282}
283
284var 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
292type nestedLang struct{ Message }
293
294func (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
301type errorCompileMsg struct{}
302
303var errCompileTest = errors.New("catmsg: compile error test")
304
305func (errorCompileMsg) Compile(e *Encoder) (err error) {
306	return errCompileTest
307}
308
309type dictionary struct{}
310
311var (
312	macros       = dictionary{}
313	dictMessages = map[string]string{
314		"exists":     compile(String("you betya!")),
315		"incomplete": compile(incomplete{}),
316	}
317)
318
319func (d dictionary) Lookup(key string) (data string, ok bool) {
320	data, ok = dictMessages[key]
321	return
322}
323
324func compile(m Message) (data string) {
325	data, _ = Compile(language.Und, macros, m)
326	return data
327}
328