1// Copyright 2016 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 plural
6
7import (
8	"fmt"
9	"reflect"
10	"strconv"
11	"strings"
12	"testing"
13
14	"golang.org/x/text/language"
15)
16
17func TestGetIntApprox(t *testing.T) {
18	const big = 1234567890
19	testCases := []struct {
20		digits string
21		start  int
22		end    int
23		nMod   int
24		want   int
25	}{
26		{"123", 0, 1, 1, 1},
27		{"123", 0, 2, 1, big},
28		{"123", 0, 2, 2, 12},
29		{"123", 3, 4, 2, 0},
30		{"12345", 3, 4, 2, 4},
31		{"40", 0, 1, 2, 4},
32		{"1", 0, 7, 2, big},
33
34		{"123", 0, 5, 2, big},
35		{"123", 0, 5, 3, big},
36		{"123", 0, 5, 4, big},
37		{"123", 0, 5, 5, 12300},
38		{"123", 0, 5, 6, 12300},
39		{"123", 0, 5, 7, 12300},
40
41		// Translation of examples in MatchDigits.
42		// Integer parts
43		{"123", 0, 3, 3, 123},  // 123
44		{"1234", 0, 3, 3, 123}, // 123.4
45		{"1", 0, 6, 8, 100000}, // 100000
46
47		// Fraction parts
48		{"123", 3, 3, 3, 0},   // 123
49		{"1234", 3, 4, 3, 4},  // 123.4
50		{"1234", 3, 5, 3, 40}, // 123.40
51		{"1", 6, 8, 8, 0},     // 100000.00
52	}
53	for _, tc := range testCases {
54		t.Run(fmt.Sprintf("%s:%d:%d/%d", tc.digits, tc.start, tc.end, tc.nMod), func(t *testing.T) {
55			got := getIntApprox(mkDigits(tc.digits), tc.start, tc.end, tc.nMod, big)
56			if got != tc.want {
57				t.Errorf("got %d; want %d", got, tc.want)
58			}
59		})
60	}
61}
62
63func mkDigits(s string) []byte {
64	b := []byte(s)
65	for i := range b {
66		b[i] -= '0'
67	}
68	return b
69}
70
71func TestValidForms(t *testing.T) {
72	testCases := []struct {
73		tag  language.Tag
74		want []Form
75	}{
76		{language.AmericanEnglish, []Form{Other, One}},
77		{language.Portuguese, []Form{Other, One}},
78		{language.Latvian, []Form{Other, Zero, One}},
79		{language.Arabic, []Form{Other, Zero, One, Two, Few, Many}},
80		{language.Russian, []Form{Other, One, Few, Many}},
81	}
82	for _, tc := range testCases {
83		got := validForms(cardinal, tc.tag)
84		if !reflect.DeepEqual(got, tc.want) {
85			t.Errorf("validForms(%v): got %v; want %v", tc.tag, got, tc.want)
86		}
87	}
88}
89
90func TestOrdinal(t *testing.T) {
91	testPlurals(t, Ordinal, ordinalTests)
92}
93
94func TestCardinal(t *testing.T) {
95	testPlurals(t, Cardinal, cardinalTests)
96}
97
98func testPlurals(t *testing.T, p *Rules, testCases []pluralTest) {
99	for _, tc := range testCases {
100		for _, loc := range strings.Split(tc.locales, " ") {
101			tag := language.MustParse(loc)
102			// Test integers
103			for _, s := range tc.integer {
104				a := strings.Split(s, "~")
105				from := parseUint(t, a[0])
106				to := from
107				if len(a) > 1 {
108					to = parseUint(t, a[1])
109				}
110				for n := from; n <= to; n++ {
111					t.Run(fmt.Sprintf("%s/int(%d)", loc, n), func(t *testing.T) {
112						if f := p.matchComponents(tag, n, 0, 0); f != Form(tc.form) {
113							t.Errorf("matchComponents: got %v; want %v", f, Form(tc.form))
114						}
115						digits := []byte(fmt.Sprint(n))
116						for i := range digits {
117							digits[i] -= '0'
118						}
119						if f := p.MatchDigits(tag, digits, len(digits), 0); f != Form(tc.form) {
120							t.Errorf("MatchDigits: got %v; want %v", f, Form(tc.form))
121						}
122					})
123				}
124			}
125			// Test decimals
126			for _, s := range tc.decimal {
127				a := strings.Split(s, "~")
128				from, scale := parseFixedPoint(t, a[0])
129				to := from
130				if len(a) > 1 {
131					var toScale int
132					if to, toScale = parseFixedPoint(t, a[1]); toScale != scale {
133						t.Fatalf("%s:%s: non-matching scales %d versus %d", loc, s, scale, toScale)
134					}
135				}
136				m := 1
137				for i := 0; i < scale; i++ {
138					m *= 10
139				}
140				for n := from; n <= to; n++ {
141					num := fmt.Sprintf("%[1]d.%0[3]*[2]d", n/m, n%m, scale)
142					name := fmt.Sprintf("%s:dec(%s)", loc, num)
143					t.Run(name, func(t *testing.T) {
144						ff := n % m
145						tt := ff
146						w := scale
147						for tt > 0 && tt%10 == 0 {
148							w--
149							tt /= 10
150						}
151						if f := p.MatchPlural(tag, n/m, scale, w, ff, tt); f != Form(tc.form) {
152							t.Errorf("MatchPlural: got %v; want %v", f, Form(tc.form))
153						}
154						if f := p.matchComponents(tag, n/m, n%m, scale); f != Form(tc.form) {
155							t.Errorf("matchComponents: got %v; want %v", f, Form(tc.form))
156						}
157						exp := strings.IndexByte(num, '.')
158						digits := []byte(strings.Replace(num, ".", "", 1))
159						for i := range digits {
160							digits[i] -= '0'
161						}
162						if f := p.MatchDigits(tag, digits, exp, scale); f != Form(tc.form) {
163							t.Errorf("MatchDigits: got %v; want %v", f, Form(tc.form))
164						}
165					})
166				}
167			}
168		}
169	}
170}
171
172func parseUint(t *testing.T, s string) int {
173	val, err := strconv.ParseUint(s, 10, 32)
174	if err != nil {
175		t.Fatal(err)
176	}
177	return int(val)
178}
179
180func parseFixedPoint(t *testing.T, s string) (val, scale int) {
181	p := strings.Index(s, ".")
182	s = strings.Replace(s, ".", "", 1)
183	v, err := strconv.ParseUint(s, 10, 32)
184	if err != nil {
185		t.Fatal(err)
186	}
187	return int(v), len(s) - p
188}
189
190func BenchmarkPluralSimpleCases(b *testing.B) {
191	p := Cardinal
192	en := tagToID(language.English)
193	zh := tagToID(language.Chinese)
194	for i := 0; i < b.N; i++ {
195		matchPlural(p, en, 0, 0, 0)  // 0
196		matchPlural(p, en, 1, 0, 0)  // 1
197		matchPlural(p, en, 2, 12, 3) // 2.120
198		matchPlural(p, zh, 0, 0, 0)  // 0
199		matchPlural(p, zh, 1, 0, 0)  // 1
200		matchPlural(p, zh, 2, 12, 3) // 2.120
201	}
202}
203
204func BenchmarkPluralComplexCases(b *testing.B) {
205	p := Cardinal
206	ar := tagToID(language.Arabic)
207	lv := tagToID(language.Latvian)
208	for i := 0; i < b.N; i++ {
209		matchPlural(p, lv, 0, 19, 2)    // 0.19
210		matchPlural(p, lv, 11, 0, 3)    // 11.000
211		matchPlural(p, lv, 100, 123, 4) // 0.1230
212		matchPlural(p, ar, 0, 0, 0)     // 0
213		matchPlural(p, ar, 110, 0, 0)   // 110
214		matchPlural(p, ar, 99, 99, 2)   // 99.99
215	}
216}
217