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