1// Copyright 2014 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 cldr
6
7import (
8	"fmt"
9	"strings"
10	"testing"
11)
12
13// A recorder implements the RuleProcessor interface, whereby its methods
14// simply record the invocations.
15type recorder struct {
16	calls []string
17}
18
19func (r *recorder) Reset(anchor string, before int) error {
20	if before > 5 {
21		return fmt.Errorf("before %d > 5", before)
22	}
23	r.calls = append(r.calls, fmt.Sprintf("R:%s-%d", anchor, before))
24	return nil
25}
26
27func (r *recorder) Insert(level int, str, context, extend string) error {
28	s := fmt.Sprintf("O:%d:%s", level, str)
29	if context != "" {
30		s += "|" + context
31	}
32	if extend != "" {
33		s += "/" + extend
34	}
35	r.calls = append(r.calls, s)
36	return nil
37}
38
39func (r *recorder) Index(id string) {
40	r.calls = append(r.calls, fmt.Sprintf("I:%s", id))
41}
42
43func (r *recorder) Error(err error) {
44	r.calls = append(r.calls, fmt.Sprintf("E:%v", err))
45}
46
47func TestRuleProcessor(t *testing.T) {
48	for _, tt := range []struct {
49		desc string
50		in   string
51		out  string
52	}{
53		{desc: "empty"},
54		{desc: "whitespace and comments only",
55			in: `
56
57
58		  		# adsfads
59# adfadf
60		`,
61		},
62		{
63			desc: "reset anchor",
64			in: `
65			& a
66			&b    #
67			&  [    before 3  ]  c
68			& [before 4] d & ee
69			& [first tertiary ignorable]
70			&'g'
71			& 	'h''h'h'h'
72			&'\u0069'  # LATIN SMALL LETTER I
73			`,
74			out: `
75			R:a-0
76			R:b-0
77			R:c-3
78			R:d-4
79			R:ee-0
80			R:<first tertiary ignorable/>-0
81			R:g-0
82			R:hhhh-0
83			R:i-0
84			`,
85		},
86		{
87			desc: "ordering",
88			in: `
89			& 0
90			< 1 <<''2#
91<<<			3'3''33'3#
92			<<<<4
93			= 5 << 6 | s
94			<<<< 7 / z
95			<< 8'' | s / ch
96			`,
97			out: `
98			R:0-0
99			O:1:1
100			O:2:'2
101			O:3:33333
102			O:4:4
103			O:5:5
104			O:2:6|s
105			O:4:7/z
106			O:2:8'|s/ch
107			`,
108		},
109		{
110			desc: "index",
111			in:   "< '\ufdd0'A",
112			out:  "I:A",
113		},
114		{
115			desc: "sequence",
116			in: `
117			& 0
118			<<* 1234
119			<* a-cde-f
120			=* q-q
121			`,
122			out: `
123			R:0-0
124			O:2:1
125			O:2:2
126			O:2:3
127			O:2:4
128			O:1:a
129			O:1:b
130			O:1:c
131			O:1:d
132			O:1:e
133			O:1:f
134			O:5:q
135			`,
136		},
137		{
138			desc: "compact",
139			in:   "&B<t<<<T<s<<<S<e<<<E",
140			out: `
141			R:B-0
142			O:1:t
143			O:3:T
144			O:1:s
145			O:3:S
146			O:1:e
147			O:3:E
148			`,
149		},
150		{
151			desc: "err operator",
152			in:   "a",
153			out:  "E:1: illegal operator 'a'",
154		},
155		{
156			desc: "err line number",
157			in: `& a
158			<< b
159			a`,
160			out: `
161			R:a-0
162			O:2:b
163			E:3: illegal operator 'a'`,
164		},
165		{
166			desc: "err empty anchor",
167			in: " &			",
168			out: "E:1: missing string",
169		},
170		{
171			desc: "err anchor invalid special 1",
172			in: " &	[ foo ",
173			out: "E:1: unmatched bracket",
174		},
175		{
176			desc: "err anchor invalid special 2",
177			in:   "&[",
178			out:  "E:1: unmatched bracket",
179		},
180		{
181			desc: "err anchor invalid before 1",
182			in:   "&[before a]",
183			out:  `E:1: strconv.ParseUint: parsing "a": invalid syntax`,
184		},
185		{
186			desc: "err anchor invalid before 2",
187			in:   "&[before 12]",
188			out:  `E:1: strconv.ParseUint: parsing "12": value out of range`,
189		},
190		{
191			desc: "err anchor invalid before 3",
192			in:   "&[before 2]",
193			out:  "E:1: missing string",
194		},
195		{
196			desc: "err anchor invalid before 4",
197			in:   "&[before 6] a",
198			out:  "E:1: before 6 > 5",
199		},
200		{
201			desc: "err empty order",
202			in:   " < ",
203			out:  "E:1: missing string",
204		},
205		{
206			desc: "err empty identity",
207			in:   " = ",
208			out:  "E:1: missing string",
209		},
210		{
211			desc: "err empty context",
212			in:   " < a |  ",
213			out:  "E:1: missing string after context",
214		},
215		{
216			desc: "err empty extend",
217			in:   " < a /  ",
218			out:  "E:1: missing string after extension",
219		},
220		{
221			desc: "err empty sequence",
222			in:   " <* ",
223			out:  "E:1: empty sequence",
224		},
225		{
226			desc: "err sequence 1",
227			in:   " <* -a",
228			out:  "E:1: range without starter value",
229		},
230		{
231			desc: "err sequence 3",
232			in:   " <* a-a-b",
233			out: `O:1:a
234			E:1: range without starter value
235			`,
236		},
237		{
238			desc: "err sequence 3",
239			in:   " <* b-a",
240			out: `O:1:b
241			E:1: invalid range 'b'-'a'
242			`,
243		},
244		{
245			desc: "err unmatched quote",
246			in:   " < 'b",
247			out: ` E:1: unmatched single quote
248			`,
249		},
250	} {
251		rec := &recorder{}
252		err := Collation{
253			Cr: []*Common{
254				{hidden: hidden{CharData: tt.in}},
255			},
256		}.Process(rec)
257		if err != nil {
258			rec.Error(err)
259		}
260		got := rec.calls
261		want := strings.Split(strings.TrimSpace(tt.out), "\n")
262		if tt.out == "" {
263			want = nil
264		}
265		if len(got) != len(want) {
266			t.Errorf("%s: nResults: got %d; want %d", tt.desc, len(got), len(want))
267			continue
268		}
269		for i, g := range got {
270			if want := strings.TrimSpace(want[i]); g != want {
271				t.Errorf("%s:%d: got %q; want %q", tt.desc, i, g, want)
272			}
273		}
274	}
275}
276