2017-10-06 15:29:20 +00:00
|
|
|
// Copyright 2013 The Go Authors. All rights reserved.
|
|
|
|
// Use of this source code is governed by a BSD-style
|
|
|
|
// license that can be found in the LICENSE file.
|
|
|
|
|
|
|
|
package language
|
|
|
|
|
|
|
|
import (
|
|
|
|
"reflect"
|
|
|
|
"testing"
|
|
|
|
|
|
|
|
"golang.org/x/text/internal/testtext"
|
|
|
|
)
|
|
|
|
|
|
|
|
func TestTagSize(t *testing.T) {
|
|
|
|
id := Tag{}
|
|
|
|
typ := reflect.TypeOf(id)
|
|
|
|
if typ.Size() > 24 {
|
|
|
|
t.Errorf("size of Tag was %d; want 24", typ.Size())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestIsRoot(t *testing.T) {
|
|
|
|
loc := Tag{}
|
|
|
|
if !loc.IsRoot() {
|
|
|
|
t.Errorf("unspecified should be root.")
|
|
|
|
}
|
|
|
|
for i, tt := range parseTests() {
|
|
|
|
loc, _ := Parse(tt.in)
|
|
|
|
undef := tt.lang == "und" && tt.script == "" && tt.region == "" && tt.ext == ""
|
|
|
|
if loc.IsRoot() != undef {
|
|
|
|
t.Errorf("%d: was %v; want %v", i, loc.IsRoot(), undef)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestEquality(t *testing.T) {
|
|
|
|
for i, tt := range parseTests()[48:49] {
|
|
|
|
s := tt.in
|
|
|
|
tag := Make(s)
|
|
|
|
t1 := Make(tag.String())
|
|
|
|
if tag != t1 {
|
|
|
|
t.Errorf("%d:%s: equality test 1 failed\n got: %#v\nwant: %#v)", i, s, t1, tag)
|
|
|
|
}
|
|
|
|
t2, _ := Compose(tag)
|
|
|
|
if tag != t2 {
|
|
|
|
t.Errorf("%d:%s: equality test 2 failed\n got: %#v\nwant: %#v", i, s, t2, tag)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestMakeString(t *testing.T) {
|
|
|
|
tests := []struct{ in, out string }{
|
|
|
|
{"und", "und"},
|
|
|
|
{"und", "und-CW"},
|
|
|
|
{"nl", "nl-NL"},
|
|
|
|
{"de-1901", "nl-1901"},
|
|
|
|
{"de-1901", "de-Arab-1901"},
|
|
|
|
{"x-a-b", "de-Arab-x-a-b"},
|
|
|
|
{"x-a-b", "x-a-b"},
|
|
|
|
}
|
|
|
|
for i, tt := range tests {
|
|
|
|
id, _ := Parse(tt.in)
|
|
|
|
mod, _ := Parse(tt.out)
|
|
|
|
id.setTagsFrom(mod)
|
|
|
|
for j := 0; j < 2; j++ {
|
|
|
|
id.remakeString()
|
|
|
|
if str := id.String(); str != tt.out {
|
|
|
|
t.Errorf("%d:%d: found %s; want %s", i, j, id.String(), tt.out)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// The bytes to string conversion as used in remakeString
|
|
|
|
// occasionally measures as more than one alloc, breaking this test.
|
|
|
|
// To alleviate this we set the number of runs to more than 1.
|
|
|
|
if n := testtext.AllocsPerRun(8, id.remakeString); n > 1 {
|
|
|
|
t.Errorf("%d: # allocs got %.1f; want <= 1", i, n)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestCompactIndex(t *testing.T) {
|
|
|
|
tests := []struct {
|
|
|
|
tag string
|
|
|
|
index int
|
|
|
|
ok bool
|
|
|
|
}{
|
|
|
|
// TODO: these values will change with each CLDR update. This issue
|
|
|
|
// will be solved if we decide to fix the indexes.
|
|
|
|
{"und", 0, true},
|
|
|
|
{"ca-ES-valencia", 1, true},
|
|
|
|
{"ca-ES-valencia-u-va-posix", 0, false},
|
|
|
|
{"ca-ES-valencia-u-co-phonebk", 1, true},
|
|
|
|
{"ca-ES-valencia-u-co-phonebk-va-posix", 0, false},
|
|
|
|
{"x-klingon", 0, false},
|
2018-01-03 19:19:49 +00:00
|
|
|
{"en-US", 232, true},
|
2017-10-06 15:29:20 +00:00
|
|
|
{"en-US-u-va-posix", 2, true},
|
2018-01-03 19:19:49 +00:00
|
|
|
{"en", 136, true},
|
|
|
|
{"en-u-co-phonebk", 136, true},
|
|
|
|
{"en-001", 137, true},
|
2017-10-06 15:29:20 +00:00
|
|
|
{"sh", 0, false}, // We don't normalize.
|
|
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
|
|
x, ok := CompactIndex(Raw.MustParse(tt.tag))
|
|
|
|
if x != tt.index || ok != tt.ok {
|
|
|
|
t.Errorf("%s: got %d, %v; want %d %v", tt.tag, x, ok, tt.index, tt.ok)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-01-03 19:19:49 +00:00
|
|
|
func TestMarshal(t *testing.T) {
|
|
|
|
testCases := []string{
|
|
|
|
// TODO: these values will change with each CLDR update. This issue
|
|
|
|
// will be solved if we decide to fix the indexes.
|
|
|
|
"und",
|
|
|
|
"ca-ES-valencia",
|
|
|
|
"ca-ES-valencia-u-va-posix",
|
|
|
|
"ca-ES-valencia-u-co-phonebk",
|
|
|
|
"ca-ES-valencia-u-co-phonebk-va-posix",
|
|
|
|
"x-klingon",
|
|
|
|
"en-US",
|
|
|
|
"en-US-u-va-posix",
|
|
|
|
"en",
|
|
|
|
"en-u-co-phonebk",
|
|
|
|
"en-001",
|
|
|
|
"sh",
|
|
|
|
}
|
|
|
|
for _, tc := range testCases {
|
|
|
|
var tag Tag
|
|
|
|
err := tag.UnmarshalText([]byte(tc))
|
|
|
|
if err != nil {
|
|
|
|
t.Errorf("UnmarshalText(%q): unexpected error: %v", tc, err)
|
|
|
|
}
|
|
|
|
b, err := tag.MarshalText()
|
|
|
|
if err != nil {
|
|
|
|
t.Errorf("MarshalText(%q): unexpected error: %v", tc, err)
|
|
|
|
}
|
|
|
|
if got := string(b); got != tc {
|
|
|
|
t.Errorf("%s: got %q; want %q", tc, got, tc)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-10-06 15:29:20 +00:00
|
|
|
func TestBase(t *testing.T) {
|
|
|
|
tests := []struct {
|
|
|
|
loc, lang string
|
|
|
|
conf Confidence
|
|
|
|
}{
|
|
|
|
{"und", "en", Low},
|
|
|
|
{"x-abc", "und", No},
|
|
|
|
{"en", "en", Exact},
|
|
|
|
{"und-Cyrl", "ru", High},
|
|
|
|
// If a region is not included, the official language should be English.
|
|
|
|
{"und-US", "en", High},
|
|
|
|
// TODO: not-explicitly listed scripts should probably be und, No
|
|
|
|
// Modify addTags to return info on how the match was derived.
|
|
|
|
// {"und-Aghb", "und", No},
|
|
|
|
}
|
|
|
|
for i, tt := range tests {
|
|
|
|
loc, _ := Parse(tt.loc)
|
|
|
|
lang, conf := loc.Base()
|
|
|
|
if lang.String() != tt.lang {
|
|
|
|
t.Errorf("%d: language was %s; want %s", i, lang, tt.lang)
|
|
|
|
}
|
|
|
|
if conf != tt.conf {
|
|
|
|
t.Errorf("%d: confidence was %d; want %d", i, conf, tt.conf)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestParseBase(t *testing.T) {
|
|
|
|
tests := []struct {
|
|
|
|
in string
|
|
|
|
out string
|
|
|
|
ok bool
|
|
|
|
}{
|
|
|
|
{"en", "en", true},
|
|
|
|
{"EN", "en", true},
|
|
|
|
{"nld", "nl", true},
|
|
|
|
{"dut", "dut", true}, // bibliographic
|
|
|
|
{"aaj", "und", false}, // unknown
|
|
|
|
{"qaa", "qaa", true},
|
|
|
|
{"a", "und", false},
|
|
|
|
{"", "und", false},
|
|
|
|
{"aaaa", "und", false},
|
|
|
|
}
|
|
|
|
for i, tt := range tests {
|
|
|
|
x, err := ParseBase(tt.in)
|
|
|
|
if x.String() != tt.out || err == nil != tt.ok {
|
|
|
|
t.Errorf("%d:%s: was %s, %v; want %s, %v", i, tt.in, x, err == nil, tt.out, tt.ok)
|
|
|
|
}
|
|
|
|
if y, _, _ := Raw.Make(tt.out).Raw(); x != y {
|
|
|
|
t.Errorf("%d:%s: tag was %s; want %s", i, tt.in, x, y)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestScript(t *testing.T) {
|
|
|
|
tests := []struct {
|
|
|
|
loc, scr string
|
|
|
|
conf Confidence
|
|
|
|
}{
|
|
|
|
{"und", "Latn", Low},
|
|
|
|
{"en-Latn", "Latn", Exact},
|
|
|
|
{"en", "Latn", High},
|
|
|
|
{"sr", "Cyrl", Low},
|
|
|
|
{"kk", "Cyrl", High},
|
|
|
|
{"kk-CN", "Arab", Low},
|
|
|
|
{"cmn", "Hans", Low},
|
|
|
|
{"ru", "Cyrl", High},
|
|
|
|
{"ru-RU", "Cyrl", High},
|
|
|
|
{"yue", "Hant", Low},
|
|
|
|
{"x-abc", "Zzzz", Low},
|
|
|
|
{"und-zyyy", "Zyyy", Exact},
|
|
|
|
}
|
|
|
|
for i, tt := range tests {
|
|
|
|
loc, _ := Parse(tt.loc)
|
|
|
|
sc, conf := loc.Script()
|
|
|
|
if sc.String() != tt.scr {
|
|
|
|
t.Errorf("%d:%s: script was %s; want %s", i, tt.loc, sc, tt.scr)
|
|
|
|
}
|
|
|
|
if conf != tt.conf {
|
|
|
|
t.Errorf("%d:%s: confidence was %d; want %d", i, tt.loc, conf, tt.conf)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestParseScript(t *testing.T) {
|
|
|
|
tests := []struct {
|
|
|
|
in string
|
|
|
|
out string
|
|
|
|
ok bool
|
|
|
|
}{
|
|
|
|
{"Latn", "Latn", true},
|
|
|
|
{"zzzz", "Zzzz", true},
|
|
|
|
{"zyyy", "Zyyy", true},
|
|
|
|
{"Latm", "Zzzz", false},
|
|
|
|
{"Zzz", "Zzzz", false},
|
|
|
|
{"", "Zzzz", false},
|
|
|
|
{"Zzzxx", "Zzzz", false},
|
|
|
|
}
|
|
|
|
for i, tt := range tests {
|
|
|
|
x, err := ParseScript(tt.in)
|
|
|
|
if x.String() != tt.out || err == nil != tt.ok {
|
|
|
|
t.Errorf("%d:%s: was %s, %v; want %s, %v", i, tt.in, x, err == nil, tt.out, tt.ok)
|
|
|
|
}
|
|
|
|
if err == nil {
|
|
|
|
if _, y, _ := Raw.Make("und-" + tt.out).Raw(); x != y {
|
|
|
|
t.Errorf("%d:%s: tag was %s; want %s", i, tt.in, x, y)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestRegion(t *testing.T) {
|
|
|
|
tests := []struct {
|
|
|
|
loc, reg string
|
|
|
|
conf Confidence
|
|
|
|
}{
|
|
|
|
{"und", "US", Low},
|
|
|
|
{"en", "US", Low},
|
|
|
|
{"zh-Hant", "TW", Low},
|
|
|
|
{"en-US", "US", Exact},
|
|
|
|
{"cmn", "CN", Low},
|
|
|
|
{"ru", "RU", Low},
|
|
|
|
{"yue", "HK", Low},
|
|
|
|
{"x-abc", "ZZ", Low},
|
|
|
|
}
|
|
|
|
for i, tt := range tests {
|
|
|
|
loc, _ := Raw.Parse(tt.loc)
|
|
|
|
reg, conf := loc.Region()
|
|
|
|
if reg.String() != tt.reg {
|
|
|
|
t.Errorf("%d:%s: region was %s; want %s", i, tt.loc, reg, tt.reg)
|
|
|
|
}
|
|
|
|
if conf != tt.conf {
|
|
|
|
t.Errorf("%d:%s: confidence was %d; want %d", i, tt.loc, conf, tt.conf)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestEncodeM49(t *testing.T) {
|
|
|
|
tests := []struct {
|
|
|
|
m49 int
|
|
|
|
code string
|
|
|
|
ok bool
|
|
|
|
}{
|
|
|
|
{1, "001", true},
|
|
|
|
{840, "US", true},
|
|
|
|
{899, "ZZ", false},
|
|
|
|
}
|
|
|
|
for i, tt := range tests {
|
|
|
|
if r, err := EncodeM49(tt.m49); r.String() != tt.code || err == nil != tt.ok {
|
|
|
|
t.Errorf("%d:%d: was %s, %v; want %s, %v", i, tt.m49, r, err == nil, tt.code, tt.ok)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
for i := 1; i <= 1000; i++ {
|
|
|
|
if r, err := EncodeM49(i); err == nil && r.M49() == 0 {
|
|
|
|
t.Errorf("%d has no error, but maps to undefined region", i)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestParseRegion(t *testing.T) {
|
|
|
|
tests := []struct {
|
|
|
|
in string
|
|
|
|
out string
|
|
|
|
ok bool
|
|
|
|
}{
|
|
|
|
{"001", "001", true},
|
|
|
|
{"840", "US", true},
|
|
|
|
{"899", "ZZ", false},
|
|
|
|
{"USA", "US", true},
|
|
|
|
{"US", "US", true},
|
|
|
|
{"BC", "ZZ", false},
|
|
|
|
{"C", "ZZ", false},
|
|
|
|
{"CCCC", "ZZ", false},
|
|
|
|
{"01", "ZZ", false},
|
|
|
|
}
|
|
|
|
for i, tt := range tests {
|
|
|
|
r, err := ParseRegion(tt.in)
|
|
|
|
if r.String() != tt.out || err == nil != tt.ok {
|
|
|
|
t.Errorf("%d:%s: was %s, %v; want %s, %v", i, tt.in, r, err == nil, tt.out, tt.ok)
|
|
|
|
}
|
|
|
|
if err == nil {
|
|
|
|
if _, _, y := Raw.Make("und-" + tt.out).Raw(); r != y {
|
|
|
|
t.Errorf("%d:%s: tag was %s; want %s", i, tt.in, r, y)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestIsCountry(t *testing.T) {
|
|
|
|
tests := []struct {
|
|
|
|
reg string
|
|
|
|
country bool
|
|
|
|
}{
|
|
|
|
{"US", true},
|
|
|
|
{"001", false},
|
|
|
|
{"958", false},
|
|
|
|
{"419", false},
|
|
|
|
{"203", true},
|
|
|
|
{"020", true},
|
|
|
|
{"900", false},
|
|
|
|
{"999", false},
|
|
|
|
{"QO", false},
|
|
|
|
{"EU", false},
|
|
|
|
{"AA", false},
|
|
|
|
{"XK", true},
|
|
|
|
}
|
|
|
|
for i, tt := range tests {
|
|
|
|
reg, _ := getRegionID([]byte(tt.reg))
|
|
|
|
r := Region{reg}
|
|
|
|
if r.IsCountry() != tt.country {
|
|
|
|
t.Errorf("%d: IsCountry(%s) was %v; want %v", i, tt.reg, r.IsCountry(), tt.country)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestIsGroup(t *testing.T) {
|
|
|
|
tests := []struct {
|
|
|
|
reg string
|
|
|
|
group bool
|
|
|
|
}{
|
|
|
|
{"US", false},
|
|
|
|
{"001", true},
|
|
|
|
{"958", false},
|
|
|
|
{"419", true},
|
|
|
|
{"203", false},
|
|
|
|
{"020", false},
|
|
|
|
{"900", false},
|
|
|
|
{"999", false},
|
|
|
|
{"QO", true},
|
|
|
|
{"EU", true},
|
|
|
|
{"AA", false},
|
|
|
|
{"XK", false},
|
|
|
|
}
|
|
|
|
for i, tt := range tests {
|
|
|
|
reg, _ := getRegionID([]byte(tt.reg))
|
|
|
|
r := Region{reg}
|
|
|
|
if r.IsGroup() != tt.group {
|
|
|
|
t.Errorf("%d: IsGroup(%s) was %v; want %v", i, tt.reg, r.IsGroup(), tt.group)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestContains(t *testing.T) {
|
|
|
|
tests := []struct {
|
|
|
|
enclosing, contained string
|
|
|
|
contains bool
|
|
|
|
}{
|
|
|
|
// A region contains itself.
|
|
|
|
{"US", "US", true},
|
|
|
|
{"001", "001", true},
|
|
|
|
|
|
|
|
// Direct containment.
|
|
|
|
{"001", "002", true},
|
|
|
|
{"039", "XK", true},
|
|
|
|
{"150", "XK", true},
|
|
|
|
{"EU", "AT", true},
|
|
|
|
{"QO", "AQ", true},
|
|
|
|
|
|
|
|
// Indirect containemnt.
|
|
|
|
{"001", "US", true},
|
|
|
|
{"001", "419", true},
|
|
|
|
{"001", "013", true},
|
|
|
|
|
|
|
|
// No containment.
|
|
|
|
{"US", "001", false},
|
|
|
|
{"155", "EU", false},
|
|
|
|
}
|
|
|
|
for i, tt := range tests {
|
|
|
|
enc, _ := getRegionID([]byte(tt.enclosing))
|
|
|
|
con, _ := getRegionID([]byte(tt.contained))
|
|
|
|
r := Region{enc}
|
|
|
|
if got := r.Contains(Region{con}); got != tt.contains {
|
|
|
|
t.Errorf("%d: %s.Contains(%s) was %v; want %v", i, tt.enclosing, tt.contained, got, tt.contains)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestRegionCanonicalize(t *testing.T) {
|
|
|
|
for i, tt := range []struct{ in, out string }{
|
|
|
|
{"UK", "GB"},
|
|
|
|
{"TP", "TL"},
|
|
|
|
{"QU", "EU"},
|
|
|
|
{"SU", "SU"},
|
|
|
|
{"VD", "VN"},
|
|
|
|
{"DD", "DE"},
|
|
|
|
} {
|
|
|
|
r := MustParseRegion(tt.in)
|
|
|
|
want := MustParseRegion(tt.out)
|
|
|
|
if got := r.Canonicalize(); got != want {
|
|
|
|
t.Errorf("%d: got %v; want %v", i, got, want)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestRegionTLD(t *testing.T) {
|
|
|
|
for _, tt := range []struct {
|
|
|
|
in, out string
|
|
|
|
ok bool
|
|
|
|
}{
|
|
|
|
{"EH", "EH", true},
|
|
|
|
{"FR", "FR", true},
|
|
|
|
{"TL", "TL", true},
|
|
|
|
|
|
|
|
// In ccTLD before in ISO.
|
|
|
|
{"GG", "GG", true},
|
|
|
|
|
|
|
|
// Non-standard assignment of ccTLD to ISO code.
|
|
|
|
{"GB", "UK", true},
|
|
|
|
|
|
|
|
// Exceptionally reserved in ISO and valid ccTLD.
|
|
|
|
{"UK", "UK", true},
|
|
|
|
{"AC", "AC", true},
|
|
|
|
{"EU", "EU", true},
|
|
|
|
{"SU", "SU", true},
|
|
|
|
|
|
|
|
// Exceptionally reserved in ISO and invalid ccTLD.
|
|
|
|
{"CP", "ZZ", false},
|
|
|
|
{"DG", "ZZ", false},
|
|
|
|
{"EA", "ZZ", false},
|
|
|
|
{"FX", "ZZ", false},
|
|
|
|
{"IC", "ZZ", false},
|
|
|
|
{"TA", "ZZ", false},
|
|
|
|
|
|
|
|
// Transitionally reserved in ISO (e.g. deprecated) but valid ccTLD as
|
|
|
|
// it is still being phased out.
|
|
|
|
{"AN", "AN", true},
|
|
|
|
{"TP", "TP", true},
|
|
|
|
|
|
|
|
// Transitionally reserved in ISO (e.g. deprecated) and invalid ccTLD.
|
|
|
|
// Defined in package language as it has a mapping in CLDR.
|
|
|
|
{"BU", "ZZ", false},
|
|
|
|
{"CS", "ZZ", false},
|
|
|
|
{"NT", "ZZ", false},
|
|
|
|
{"YU", "ZZ", false},
|
|
|
|
{"ZR", "ZZ", false},
|
|
|
|
// Not defined in package: SF.
|
|
|
|
|
|
|
|
// Indeterminately reserved in ISO.
|
|
|
|
// Defined in package language as it has a legacy mapping in CLDR.
|
|
|
|
{"DY", "ZZ", false},
|
|
|
|
{"RH", "ZZ", false},
|
|
|
|
{"VD", "ZZ", false},
|
|
|
|
// Not defined in package: EW, FL, JA, LF, PI, RA, RB, RC, RI, RL, RM,
|
|
|
|
// RN, RP, WG, WL, WV, and YV.
|
|
|
|
|
|
|
|
// Not assigned in ISO, but legacy definitions in CLDR.
|
|
|
|
{"DD", "ZZ", false},
|
|
|
|
{"YD", "ZZ", false},
|
|
|
|
|
|
|
|
// Normal mappings but somewhat special status in ccTLD.
|
|
|
|
{"BL", "BL", true},
|
|
|
|
{"MF", "MF", true},
|
|
|
|
{"BV", "BV", true},
|
|
|
|
{"SJ", "SJ", true},
|
|
|
|
|
|
|
|
// Have values when normalized, but not as is.
|
|
|
|
{"QU", "ZZ", false},
|
|
|
|
|
|
|
|
// ISO Private Use.
|
|
|
|
{"AA", "ZZ", false},
|
|
|
|
{"QM", "ZZ", false},
|
|
|
|
{"QO", "ZZ", false},
|
|
|
|
{"XA", "ZZ", false},
|
|
|
|
{"XK", "ZZ", false}, // Sometimes used for Kosovo, but invalid ccTLD.
|
|
|
|
} {
|
|
|
|
if tt.in == "" {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
r := MustParseRegion(tt.in)
|
|
|
|
var want Region
|
|
|
|
if tt.out != "ZZ" {
|
|
|
|
want = MustParseRegion(tt.out)
|
|
|
|
}
|
|
|
|
tld, err := r.TLD()
|
|
|
|
if got := err == nil; got != tt.ok {
|
|
|
|
t.Errorf("error(%v): got %v; want %v", r, got, tt.ok)
|
|
|
|
}
|
|
|
|
if tld != want {
|
|
|
|
t.Errorf("TLD(%v): got %v; want %v", r, tld, want)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestCanonicalize(t *testing.T) {
|
|
|
|
// TODO: do a full test using CLDR data in a separate regression test.
|
|
|
|
tests := []struct {
|
|
|
|
in, out string
|
|
|
|
option CanonType
|
|
|
|
}{
|
|
|
|
{"en-Latn", "en", SuppressScript},
|
|
|
|
{"sr-Cyrl", "sr-Cyrl", SuppressScript},
|
|
|
|
{"sh", "sr-Latn", Legacy},
|
|
|
|
{"sh-HR", "sr-Latn-HR", Legacy},
|
|
|
|
{"sh-Cyrl-HR", "sr-Cyrl-HR", Legacy},
|
|
|
|
{"tl", "fil", Legacy},
|
|
|
|
{"no", "no", Legacy},
|
|
|
|
{"no", "nb", Legacy | CLDR},
|
|
|
|
{"cmn", "cmn", Legacy},
|
|
|
|
{"cmn", "zh", Macro},
|
|
|
|
{"cmn-u-co-stroke", "zh-u-co-stroke", Macro},
|
|
|
|
{"yue", "yue", Macro},
|
|
|
|
{"nb", "no", Macro},
|
|
|
|
{"nb", "nb", Macro | CLDR},
|
|
|
|
{"no", "no", Macro},
|
|
|
|
{"no", "no", Macro | CLDR},
|
|
|
|
{"iw", "he", DeprecatedBase},
|
|
|
|
{"iw", "he", Deprecated | CLDR},
|
|
|
|
{"mo", "ro-MD", Deprecated}, // Adopted by CLDR as of version 25.
|
|
|
|
{"alb", "sq", Legacy}, // bibliographic
|
|
|
|
{"dut", "nl", Legacy}, // bibliographic
|
|
|
|
// As of CLDR 25, mo is no longer considered a legacy mapping.
|
|
|
|
{"mo", "mo", Legacy | CLDR},
|
|
|
|
{"und-AN", "und-AN", Deprecated},
|
|
|
|
{"und-YD", "und-YE", DeprecatedRegion},
|
|
|
|
{"und-YD", "und-YD", DeprecatedBase},
|
|
|
|
{"und-Qaai", "und-Zinh", DeprecatedScript},
|
|
|
|
{"und-Qaai", "und-Qaai", DeprecatedBase},
|
|
|
|
{"drh", "mn", All}, // drh -> khk -> mn
|
|
|
|
}
|
|
|
|
for i, tt := range tests {
|
|
|
|
in, _ := Raw.Parse(tt.in)
|
|
|
|
in, _ = tt.option.Canonicalize(in)
|
|
|
|
if in.String() != tt.out {
|
|
|
|
t.Errorf("%d:%s: was %s; want %s", i, tt.in, in.String(), tt.out)
|
|
|
|
}
|
|
|
|
if int(in.pVariant) > int(in.pExt) || int(in.pExt) > len(in.str) {
|
|
|
|
t.Errorf("%d:%s:offsets %d <= %d <= %d must be true", i, tt.in, in.pVariant, in.pExt, len(in.str))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// Test idempotence.
|
|
|
|
for _, base := range Supported.BaseLanguages() {
|
|
|
|
tag, _ := Raw.Compose(base)
|
|
|
|
got, _ := All.Canonicalize(tag)
|
|
|
|
want, _ := All.Canonicalize(got)
|
|
|
|
if got != want {
|
|
|
|
t.Errorf("idem(%s): got %s; want %s", tag, got, want)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestTypeForKey(t *testing.T) {
|
|
|
|
tests := []struct{ key, in, out string }{
|
|
|
|
{"co", "en", ""},
|
|
|
|
{"co", "en-u-abc", ""},
|
|
|
|
{"co", "en-u-co-phonebk", "phonebk"},
|
|
|
|
{"co", "en-u-co-phonebk-cu-aud", "phonebk"},
|
|
|
|
{"co", "x-foo-u-co-phonebk", ""},
|
|
|
|
{"nu", "en-u-co-phonebk-nu-arabic", "arabic"},
|
|
|
|
{"kc", "cmn-u-co-stroke", ""},
|
|
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
|
|
if v := Make(tt.in).TypeForKey(tt.key); v != tt.out {
|
|
|
|
t.Errorf("%q[%q]: was %q; want %q", tt.in, tt.key, v, tt.out)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestSetTypeForKey(t *testing.T) {
|
|
|
|
tests := []struct {
|
|
|
|
key, value, in, out string
|
|
|
|
err bool
|
|
|
|
}{
|
|
|
|
// replace existing value
|
|
|
|
{"co", "pinyin", "en-u-co-phonebk", "en-u-co-pinyin", false},
|
|
|
|
{"co", "pinyin", "en-u-co-phonebk-cu-xau", "en-u-co-pinyin-cu-xau", false},
|
|
|
|
{"co", "pinyin", "en-u-co-phonebk-v-xx", "en-u-co-pinyin-v-xx", false},
|
|
|
|
{"co", "pinyin", "en-u-co-phonebk-x-x", "en-u-co-pinyin-x-x", false},
|
|
|
|
{"nu", "arabic", "en-u-co-phonebk-nu-vaai", "en-u-co-phonebk-nu-arabic", false},
|
|
|
|
// add to existing -u extension
|
|
|
|
{"co", "pinyin", "en-u-ca-gregory", "en-u-ca-gregory-co-pinyin", false},
|
|
|
|
{"co", "pinyin", "en-u-ca-gregory-nu-vaai", "en-u-ca-gregory-co-pinyin-nu-vaai", false},
|
|
|
|
{"co", "pinyin", "en-u-ca-gregory-v-va", "en-u-ca-gregory-co-pinyin-v-va", false},
|
|
|
|
{"co", "pinyin", "en-u-ca-gregory-x-a", "en-u-ca-gregory-co-pinyin-x-a", false},
|
|
|
|
{"ca", "gregory", "en-u-co-pinyin", "en-u-ca-gregory-co-pinyin", false},
|
|
|
|
// remove pair
|
|
|
|
{"co", "", "en-u-co-phonebk", "en", false},
|
|
|
|
{"co", "", "en-u-ca-gregory-co-phonebk", "en-u-ca-gregory", false},
|
|
|
|
{"co", "", "en-u-co-phonebk-nu-arabic", "en-u-nu-arabic", false},
|
|
|
|
{"co", "", "en", "en", false},
|
|
|
|
// add -u extension
|
|
|
|
{"co", "pinyin", "en", "en-u-co-pinyin", false},
|
|
|
|
{"co", "pinyin", "und", "und-u-co-pinyin", false},
|
|
|
|
{"co", "pinyin", "en-a-aaa", "en-a-aaa-u-co-pinyin", false},
|
|
|
|
{"co", "pinyin", "en-x-aaa", "en-u-co-pinyin-x-aaa", false},
|
|
|
|
{"co", "pinyin", "en-v-aa", "en-u-co-pinyin-v-aa", false},
|
|
|
|
{"co", "pinyin", "en-a-aaa-x-x", "en-a-aaa-u-co-pinyin-x-x", false},
|
|
|
|
{"co", "pinyin", "en-a-aaa-v-va", "en-a-aaa-u-co-pinyin-v-va", false},
|
|
|
|
// error on invalid values
|
|
|
|
{"co", "pinyinxxx", "en", "en", true},
|
|
|
|
{"co", "piny.n", "en", "en", true},
|
|
|
|
{"co", "pinyinxxx", "en-a-aaa", "en-a-aaa", true},
|
|
|
|
{"co", "pinyinxxx", "en-u-aaa", "en-u-aaa", true},
|
|
|
|
{"co", "pinyinxxx", "en-u-aaa-co-pinyin", "en-u-aaa-co-pinyin", true},
|
|
|
|
{"co", "pinyi.", "en-u-aaa-co-pinyin", "en-u-aaa-co-pinyin", true},
|
|
|
|
{"col", "pinyin", "en", "en", true},
|
|
|
|
{"co", "cu", "en", "en", true},
|
|
|
|
// error when setting on a private use tag
|
|
|
|
{"co", "phonebook", "x-foo", "x-foo", true},
|
|
|
|
}
|
|
|
|
for i, tt := range tests {
|
|
|
|
tag := Make(tt.in)
|
|
|
|
if v, err := tag.SetTypeForKey(tt.key, tt.value); v.String() != tt.out {
|
|
|
|
t.Errorf("%d:%q[%q]=%q: was %q; want %q", i, tt.in, tt.key, tt.value, v, tt.out)
|
|
|
|
} else if (err != nil) != tt.err {
|
|
|
|
t.Errorf("%d:%q[%q]=%q: error was %v; want %v", i, tt.in, tt.key, tt.value, err != nil, tt.err)
|
|
|
|
} else if val := v.TypeForKey(tt.key); err == nil && val != tt.value {
|
|
|
|
t.Errorf("%d:%q[%q]==%q: was %v; want %v", i, tt.out, tt.key, tt.value, val, tt.value)
|
|
|
|
}
|
|
|
|
if len(tag.String()) <= 3 {
|
|
|
|
// Simulate a tag for which the string has not been set.
|
|
|
|
tag.str, tag.pExt, tag.pVariant = "", 0, 0
|
|
|
|
if tag, err := tag.SetTypeForKey(tt.key, tt.value); err == nil {
|
|
|
|
if val := tag.TypeForKey(tt.key); err == nil && val != tt.value {
|
|
|
|
t.Errorf("%d:%q[%q]==%q: was %v; want %v", i, tt.out, tt.key, tt.value, val, tt.value)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestFindKeyAndType(t *testing.T) {
|
|
|
|
// out is either the matched type in case of a match or the original
|
|
|
|
// string up till the insertion point.
|
|
|
|
tests := []struct {
|
|
|
|
key string
|
|
|
|
hasExt bool
|
|
|
|
in, out string
|
|
|
|
}{
|
|
|
|
// Don't search past a private use extension.
|
|
|
|
{"co", false, "en-x-foo-u-co-pinyin", "en"},
|
|
|
|
{"co", false, "x-foo-u-co-pinyin", ""},
|
|
|
|
{"co", false, "en-s-fff-x-foo", "en-s-fff"},
|
|
|
|
// Insertion points in absence of -u extension.
|
|
|
|
{"cu", false, "en", ""}, // t.str is ""
|
|
|
|
{"cu", false, "en-v-va", "en"},
|
|
|
|
{"cu", false, "en-a-va", "en-a-va"},
|
|
|
|
{"cu", false, "en-a-va-v-va", "en-a-va"},
|
|
|
|
{"cu", false, "en-x-a", "en"},
|
|
|
|
// Tags with the -u extension.
|
|
|
|
{"co", true, "en-u-co-standard", "standard"},
|
|
|
|
{"co", true, "yue-u-co-pinyin", "pinyin"},
|
|
|
|
{"co", true, "en-u-co-abc", "abc"},
|
|
|
|
{"co", true, "en-u-co-abc-def", "abc-def"},
|
|
|
|
{"co", true, "en-u-co-abc-def-x-foo", "abc-def"},
|
|
|
|
{"co", true, "en-u-co-standard-nu-arab", "standard"},
|
|
|
|
{"co", true, "yue-u-co-pinyin-nu-arab", "pinyin"},
|
|
|
|
// Insertion points.
|
|
|
|
{"cu", true, "en-u-co-standard", "en-u-co-standard"},
|
|
|
|
{"cu", true, "yue-u-co-pinyin-x-foo", "yue-u-co-pinyin"},
|
|
|
|
{"cu", true, "en-u-co-abc", "en-u-co-abc"},
|
|
|
|
{"cu", true, "en-u-nu-arabic", "en-u"},
|
|
|
|
{"cu", true, "en-u-co-abc-def-nu-arabic", "en-u-co-abc-def"},
|
|
|
|
}
|
|
|
|
for i, tt := range tests {
|
|
|
|
start, end, hasExt := Make(tt.in).findTypeForKey(tt.key)
|
|
|
|
if start != end {
|
|
|
|
res := tt.in[start:end]
|
|
|
|
if res != tt.out {
|
|
|
|
t.Errorf("%d:%s: was %q; want %q", i, tt.in, res, tt.out)
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
if hasExt != tt.hasExt {
|
|
|
|
t.Errorf("%d:%s: hasExt was %v; want %v", i, tt.in, hasExt, tt.hasExt)
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
if tt.in[:start] != tt.out {
|
|
|
|
t.Errorf("%d:%s: insertion point was %q; want %q", i, tt.in, tt.in[:start], tt.out)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestParent(t *testing.T) {
|
|
|
|
tests := []struct{ in, out string }{
|
|
|
|
// Strip variants and extensions first
|
|
|
|
{"de-u-co-phonebk", "de"},
|
|
|
|
{"de-1994", "de"},
|
|
|
|
{"de-Latn-1994", "de"}, // remove superfluous script.
|
|
|
|
|
|
|
|
// Ensure the canonical Tag for an entry is in the chain for base-script
|
|
|
|
// pairs.
|
|
|
|
{"zh-Hans", "zh"},
|
|
|
|
|
|
|
|
// Skip the script if it is the maximized version. CLDR files for the
|
|
|
|
// skipped tag are always empty.
|
|
|
|
{"zh-Hans-TW", "zh"},
|
|
|
|
{"zh-Hans-CN", "zh"},
|
|
|
|
|
|
|
|
// Insert the script if the maximized script is not the same as the
|
|
|
|
// maximized script of the base language.
|
|
|
|
{"zh-TW", "zh-Hant"},
|
|
|
|
{"zh-HK", "zh-Hant"},
|
|
|
|
{"zh-Hant-TW", "zh-Hant"},
|
|
|
|
{"zh-Hant-HK", "zh-Hant"},
|
|
|
|
|
|
|
|
// Non-default script skips to und.
|
|
|
|
// CLDR
|
|
|
|
{"az-Cyrl", "und"},
|
|
|
|
{"bs-Cyrl", "und"},
|
|
|
|
{"en-Dsrt", "und"},
|
|
|
|
{"ha-Arab", "und"},
|
|
|
|
{"mn-Mong", "und"},
|
|
|
|
{"pa-Arab", "und"},
|
|
|
|
{"shi-Latn", "und"},
|
|
|
|
{"sr-Latn", "und"},
|
|
|
|
{"uz-Arab", "und"},
|
|
|
|
{"uz-Cyrl", "und"},
|
|
|
|
{"vai-Latn", "und"},
|
|
|
|
{"zh-Hant", "und"},
|
|
|
|
// extra
|
|
|
|
{"nl-Cyrl", "und"},
|
|
|
|
|
|
|
|
// World english inherits from en-001.
|
|
|
|
{"en-150", "en-001"},
|
|
|
|
{"en-AU", "en-001"},
|
|
|
|
{"en-BE", "en-001"},
|
|
|
|
{"en-GG", "en-001"},
|
|
|
|
{"en-GI", "en-001"},
|
|
|
|
{"en-HK", "en-001"},
|
|
|
|
{"en-IE", "en-001"},
|
|
|
|
{"en-IM", "en-001"},
|
|
|
|
{"en-IN", "en-001"},
|
|
|
|
{"en-JE", "en-001"},
|
|
|
|
{"en-MT", "en-001"},
|
|
|
|
{"en-NZ", "en-001"},
|
|
|
|
{"en-PK", "en-001"},
|
|
|
|
{"en-SG", "en-001"},
|
|
|
|
|
|
|
|
// Spanish in Latin-American countries have es-419 as parent.
|
|
|
|
{"es-AR", "es-419"},
|
|
|
|
{"es-BO", "es-419"},
|
|
|
|
{"es-CL", "es-419"},
|
|
|
|
{"es-CO", "es-419"},
|
|
|
|
{"es-CR", "es-419"},
|
|
|
|
{"es-CU", "es-419"},
|
|
|
|
{"es-DO", "es-419"},
|
|
|
|
{"es-EC", "es-419"},
|
|
|
|
{"es-GT", "es-419"},
|
|
|
|
{"es-HN", "es-419"},
|
|
|
|
{"es-MX", "es-419"},
|
|
|
|
{"es-NI", "es-419"},
|
|
|
|
{"es-PA", "es-419"},
|
|
|
|
{"es-PE", "es-419"},
|
|
|
|
{"es-PR", "es-419"},
|
|
|
|
{"es-PY", "es-419"},
|
|
|
|
{"es-SV", "es-419"},
|
|
|
|
{"es-US", "es-419"},
|
|
|
|
{"es-UY", "es-419"},
|
|
|
|
{"es-VE", "es-419"},
|
|
|
|
// exceptions (according to CLDR)
|
|
|
|
{"es-CW", "es"},
|
|
|
|
|
|
|
|
// Inherit from pt-PT, instead of pt for these countries.
|
|
|
|
{"pt-AO", "pt-PT"},
|
|
|
|
{"pt-CV", "pt-PT"},
|
|
|
|
{"pt-GW", "pt-PT"},
|
|
|
|
{"pt-MO", "pt-PT"},
|
|
|
|
{"pt-MZ", "pt-PT"},
|
|
|
|
{"pt-ST", "pt-PT"},
|
|
|
|
{"pt-TL", "pt-PT"},
|
|
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
|
|
tag := Raw.MustParse(tt.in)
|
|
|
|
if p := Raw.MustParse(tt.out); p != tag.Parent() {
|
|
|
|
t.Errorf("%s: was %v; want %v", tt.in, tag.Parent(), p)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
var (
|
|
|
|
// Tags without error that don't need to be changed.
|
|
|
|
benchBasic = []string{
|
|
|
|
"en",
|
|
|
|
"en-Latn",
|
|
|
|
"en-GB",
|
|
|
|
"za",
|
|
|
|
"zh-Hant",
|
|
|
|
"zh",
|
|
|
|
"zh-HK",
|
|
|
|
"ar-MK",
|
|
|
|
"en-CA",
|
|
|
|
"fr-CA",
|
|
|
|
"fr-CH",
|
|
|
|
"fr",
|
|
|
|
"lv",
|
|
|
|
"he-IT",
|
|
|
|
"tlh",
|
|
|
|
"ja",
|
|
|
|
"ja-Jpan",
|
|
|
|
"ja-Jpan-JP",
|
|
|
|
"de-1996",
|
|
|
|
"de-CH",
|
|
|
|
"sr",
|
|
|
|
"sr-Latn",
|
|
|
|
}
|
|
|
|
// Tags with extensions, not changes required.
|
|
|
|
benchExt = []string{
|
|
|
|
"x-a-b-c-d",
|
|
|
|
"x-aa-bbbb-cccccccc-d",
|
|
|
|
"en-x_cc-b-bbb-a-aaa",
|
|
|
|
"en-c_cc-b-bbb-a-aaa-x-x",
|
|
|
|
"en-u-co-phonebk",
|
|
|
|
"en-Cyrl-u-co-phonebk",
|
|
|
|
"en-US-u-co-phonebk-cu-xau",
|
|
|
|
"en-nedix-u-co-phonebk",
|
|
|
|
"en-t-t0-abcd",
|
|
|
|
"en-t-nl-latn",
|
|
|
|
"en-t-t0-abcd-x-a",
|
|
|
|
}
|
|
|
|
// Change, but not memory allocation required.
|
|
|
|
benchSimpleChange = []string{
|
|
|
|
"EN",
|
|
|
|
"i-klingon",
|
|
|
|
"en-latn",
|
|
|
|
"zh-cmn-Hans-CN",
|
|
|
|
"iw-NL",
|
|
|
|
}
|
|
|
|
// Change and memory allocation required.
|
|
|
|
benchChangeAlloc = []string{
|
|
|
|
"en-c_cc-b-bbb-a-aaa",
|
|
|
|
"en-u-cu-xua-co-phonebk",
|
|
|
|
"en-u-cu-xua-co-phonebk-a-cd",
|
|
|
|
"en-u-def-abc-cu-xua-co-phonebk",
|
|
|
|
"en-t-en-Cyrl-NL-1994",
|
|
|
|
"en-t-en-Cyrl-NL-1994-t0-abc-def",
|
|
|
|
}
|
|
|
|
// Tags that result in errors.
|
|
|
|
benchErr = []string{
|
|
|
|
// IllFormed
|
|
|
|
"x_A.-B-C_D",
|
|
|
|
"en-u-cu-co-phonebk",
|
|
|
|
"en-u-cu-xau-co",
|
|
|
|
"en-t-nl-abcd",
|
|
|
|
// Invalid
|
|
|
|
"xx",
|
|
|
|
"nl-Uuuu",
|
|
|
|
"nl-QB",
|
|
|
|
}
|
|
|
|
benchChange = append(benchSimpleChange, benchChangeAlloc...)
|
|
|
|
benchAll = append(append(append(benchBasic, benchExt...), benchChange...), benchErr...)
|
|
|
|
)
|
|
|
|
|
|
|
|
func doParse(b *testing.B, tag []string) {
|
|
|
|
for i := 0; i < b.N; i++ {
|
|
|
|
// Use the modulo instead of looping over all tags so that we get a somewhat
|
|
|
|
// meaningful ns/op.
|
|
|
|
Parse(tag[i%len(tag)])
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func BenchmarkParse(b *testing.B) {
|
|
|
|
doParse(b, benchAll)
|
|
|
|
}
|
|
|
|
|
|
|
|
func BenchmarkParseBasic(b *testing.B) {
|
|
|
|
doParse(b, benchBasic)
|
|
|
|
}
|
|
|
|
|
|
|
|
func BenchmarkParseError(b *testing.B) {
|
|
|
|
doParse(b, benchErr)
|
|
|
|
}
|
|
|
|
|
|
|
|
func BenchmarkParseSimpleChange(b *testing.B) {
|
|
|
|
doParse(b, benchSimpleChange)
|
|
|
|
}
|
|
|
|
|
|
|
|
func BenchmarkParseChangeAlloc(b *testing.B) {
|
|
|
|
doParse(b, benchChangeAlloc)
|
|
|
|
}
|