github.com/graybobo/golang.org-package-offline-cache@v0.0.0-20200626051047-6608995c132f/x/text/language/language_test.go (about)

     1  // Copyright 2013 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  
     5  package language
     6  
     7  import (
     8  	"reflect"
     9  	"testing"
    10  )
    11  
    12  func TestTagSize(t *testing.T) {
    13  	id := Tag{}
    14  	typ := reflect.TypeOf(id)
    15  	if typ.Size() > 24 {
    16  		t.Errorf("size of Tag was %d; want 24", typ.Size())
    17  	}
    18  }
    19  
    20  func TestIsRoot(t *testing.T) {
    21  	loc := Tag{}
    22  	if !loc.IsRoot() {
    23  		t.Errorf("unspecified should be root.")
    24  	}
    25  	for i, tt := range parseTests() {
    26  		loc, _ := Parse(tt.in)
    27  		undef := tt.lang == "und" && tt.script == "" && tt.region == "" && tt.ext == ""
    28  		if loc.IsRoot() != undef {
    29  			t.Errorf("%d: was %v; want %v", i, loc.IsRoot(), undef)
    30  		}
    31  	}
    32  }
    33  
    34  func TestEquality(t *testing.T) {
    35  	for i, tt := range parseTests()[48:49] {
    36  		s := tt.in
    37  		tag := Make(s)
    38  		t1 := Make(tag.String())
    39  		if tag != t1 {
    40  			t.Errorf("%d:%s: equality test 1 failed\n got: %#v\nwant: %#v)", i, s, t1, tag)
    41  		}
    42  		t2, _ := Compose(tag)
    43  		if tag != t2 {
    44  			t.Errorf("%d:%s: equality test 2 failed\n got: %#v\nwant: %#v", i, s, t2, tag)
    45  		}
    46  	}
    47  }
    48  
    49  func TestMakeString(t *testing.T) {
    50  	tests := []struct{ in, out string }{
    51  		{"und", "und"},
    52  		{"und", "und-CW"},
    53  		{"nl", "nl-NL"},
    54  		{"de-1901", "nl-1901"},
    55  		{"de-1901", "de-Arab-1901"},
    56  		{"x-a-b", "de-Arab-x-a-b"},
    57  		{"x-a-b", "x-a-b"},
    58  	}
    59  	for i, tt := range tests {
    60  		id, _ := Parse(tt.in)
    61  		mod, _ := Parse(tt.out)
    62  		id.setTagsFrom(mod)
    63  		for j := 0; j < 2; j++ {
    64  			id.remakeString()
    65  			if str := id.String(); str != tt.out {
    66  				t.Errorf("%d:%d: found %s; want %s", i, j, id.String(), tt.out)
    67  			}
    68  		}
    69  		// The bytes to string conversion as used in remakeString
    70  		// occasionally measures as more than one alloc, breaking this test.
    71  		// To alleviate this we set the number of runs to more than 1.
    72  		if n := testing.AllocsPerRun(8, id.remakeString); n > 1 {
    73  			t.Errorf("%d: # allocs got %.1f; want <= 1", i, n)
    74  		}
    75  	}
    76  }
    77  
    78  func TestCompactIndex(t *testing.T) {
    79  	tests := []struct {
    80  		tag   string
    81  		index int
    82  		ok    bool
    83  	}{
    84  		// TODO: these values will change with each CLDR update. This issue
    85  		// will be solved if we decide to fix the indexes.
    86  		{"und", 0, true},
    87  		{"ca-ES-valencia", 1, true},
    88  		{"ca-ES-valencia-u-va-posix", 0, false},
    89  		{"ca-ES-valencia-u-co-phonebk", 1, true},
    90  		{"ca-ES-valencia-u-co-phonebk-va-posix", 0, false},
    91  		{"x-klingon", 0, false},
    92  		{"en-US", 225, true},
    93  		{"en-US-u-va-posix", 2, true},
    94  		{"en", 129, true},
    95  		{"en-u-co-phonebk", 129, true},
    96  		{"en-001", 130, true},
    97  		{"sh", 0, false}, // We don't normalize.
    98  	}
    99  	for _, tt := range tests {
   100  		x, ok := CompactIndex(Raw.MustParse(tt.tag))
   101  		if x != tt.index || ok != tt.ok {
   102  			t.Errorf("%s: got %d, %v; want %d %v", tt.tag, x, ok, tt.index, tt.ok)
   103  		}
   104  	}
   105  }
   106  
   107  func TestBase(t *testing.T) {
   108  	tests := []struct {
   109  		loc, lang string
   110  		conf      Confidence
   111  	}{
   112  		{"und", "en", Low},
   113  		{"x-abc", "und", No},
   114  		{"en", "en", Exact},
   115  		{"und-Cyrl", "ru", High},
   116  		// If a region is not included, the official language should be English.
   117  		{"und-US", "en", High},
   118  		// TODO: not-explicitly listed scripts should probably be und, No
   119  		// Modify addTags to return info on how the match was derived.
   120  		// {"und-Aghb", "und", No},
   121  	}
   122  	for i, tt := range tests {
   123  		loc, _ := Parse(tt.loc)
   124  		lang, conf := loc.Base()
   125  		if lang.String() != tt.lang {
   126  			t.Errorf("%d: language was %s; want %s", i, lang, tt.lang)
   127  		}
   128  		if conf != tt.conf {
   129  			t.Errorf("%d: confidence was %d; want %d", i, conf, tt.conf)
   130  		}
   131  	}
   132  }
   133  
   134  func TestParseBase(t *testing.T) {
   135  	tests := []struct {
   136  		in  string
   137  		out string
   138  		ok  bool
   139  	}{
   140  		{"en", "en", true},
   141  		{"EN", "en", true},
   142  		{"nld", "nl", true},
   143  		{"dut", "dut", true},  // bibliographic
   144  		{"aaj", "und", false}, // unknown
   145  		{"qaa", "qaa", true},
   146  		{"a", "und", false},
   147  		{"", "und", false},
   148  		{"aaaa", "und", false},
   149  	}
   150  	for i, tt := range tests {
   151  		x, err := ParseBase(tt.in)
   152  		if x.String() != tt.out || err == nil != tt.ok {
   153  			t.Errorf("%d:%s: was %s, %v; want %s, %v", i, tt.in, x, err == nil, tt.out, tt.ok)
   154  		}
   155  		if y, _, _ := Raw.Make(tt.out).Raw(); x != y {
   156  			t.Errorf("%d:%s: tag was %s; want %s", i, tt.in, x, y)
   157  		}
   158  	}
   159  }
   160  
   161  func TestScript(t *testing.T) {
   162  	tests := []struct {
   163  		loc, scr string
   164  		conf     Confidence
   165  	}{
   166  		{"und", "Latn", Low},
   167  		{"en-Latn", "Latn", Exact},
   168  		{"en", "Latn", High},
   169  		{"sr", "Cyrl", Low},
   170  		{"kk", "Cyrl", High},
   171  		{"kk-CN", "Arab", Low},
   172  		{"cmn", "Hans", Low},
   173  		{"ru", "Cyrl", High},
   174  		{"ru-RU", "Cyrl", High},
   175  		{"yue", "Zzzz", No},
   176  		{"x-abc", "Zzzz", Low},
   177  		{"und-zyyy", "Zyyy", Exact},
   178  	}
   179  	for i, tt := range tests {
   180  		loc, _ := Parse(tt.loc)
   181  		sc, conf := loc.Script()
   182  		if sc.String() != tt.scr {
   183  			t.Errorf("%d:%s: script was %s; want %s", i, tt.loc, sc, tt.scr)
   184  		}
   185  		if conf != tt.conf {
   186  			t.Errorf("%d:%s: confidence was %d; want %d", i, tt.loc, conf, tt.conf)
   187  		}
   188  	}
   189  }
   190  
   191  func TestParseScript(t *testing.T) {
   192  	tests := []struct {
   193  		in  string
   194  		out string
   195  		ok  bool
   196  	}{
   197  		{"Latn", "Latn", true},
   198  		{"zzzz", "Zzzz", true},
   199  		{"zyyy", "Zyyy", true},
   200  		{"Latm", "Zzzz", false},
   201  		{"Zzz", "Zzzz", false},
   202  		{"", "Zzzz", false},
   203  		{"Zzzxx", "Zzzz", false},
   204  	}
   205  	for i, tt := range tests {
   206  		x, err := ParseScript(tt.in)
   207  		if x.String() != tt.out || err == nil != tt.ok {
   208  			t.Errorf("%d:%s: was %s, %v; want %s, %v", i, tt.in, x, err == nil, tt.out, tt.ok)
   209  		}
   210  		if err == nil {
   211  			if _, y, _ := Raw.Make("und-" + tt.out).Raw(); x != y {
   212  				t.Errorf("%d:%s: tag was %s; want %s", i, tt.in, x, y)
   213  			}
   214  		}
   215  	}
   216  }
   217  
   218  func TestRegion(t *testing.T) {
   219  	tests := []struct {
   220  		loc, reg string
   221  		conf     Confidence
   222  	}{
   223  		{"und", "US", Low},
   224  		{"en", "US", Low},
   225  		{"zh-Hant", "TW", Low},
   226  		{"en-US", "US", Exact},
   227  		{"cmn", "CN", Low},
   228  		{"ru", "RU", Low},
   229  		{"yue", "ZZ", No},
   230  		{"x-abc", "ZZ", Low},
   231  	}
   232  	for i, tt := range tests {
   233  		loc, _ := Raw.Parse(tt.loc)
   234  		reg, conf := loc.Region()
   235  		if reg.String() != tt.reg {
   236  			t.Errorf("%d: region was %s; want %s", i, reg, tt.reg)
   237  		}
   238  		if conf != tt.conf {
   239  			t.Errorf("%d: confidence was %d; want %d", i, conf, tt.conf)
   240  		}
   241  	}
   242  }
   243  
   244  func TestEncodeM49(t *testing.T) {
   245  	tests := []struct {
   246  		m49  int
   247  		code string
   248  		ok   bool
   249  	}{
   250  		{1, "001", true},
   251  		{840, "US", true},
   252  		{899, "ZZ", false},
   253  	}
   254  	for i, tt := range tests {
   255  		if r, err := EncodeM49(tt.m49); r.String() != tt.code || err == nil != tt.ok {
   256  			t.Errorf("%d:%d: was %s, %v; want %s, %v", i, tt.m49, r, err == nil, tt.code, tt.ok)
   257  		}
   258  	}
   259  	for i := 1; i <= 1000; i++ {
   260  		if r, err := EncodeM49(i); err == nil && r.M49() == 0 {
   261  			t.Errorf("%d has no error, but maps to undefined region", i)
   262  		}
   263  	}
   264  }
   265  
   266  func TestParseRegion(t *testing.T) {
   267  	tests := []struct {
   268  		in  string
   269  		out string
   270  		ok  bool
   271  	}{
   272  		{"001", "001", true},
   273  		{"840", "US", true},
   274  		{"899", "ZZ", false},
   275  		{"USA", "US", true},
   276  		{"US", "US", true},
   277  		{"BC", "ZZ", false},
   278  		{"C", "ZZ", false},
   279  		{"CCCC", "ZZ", false},
   280  		{"01", "ZZ", false},
   281  	}
   282  	for i, tt := range tests {
   283  		r, err := ParseRegion(tt.in)
   284  		if r.String() != tt.out || err == nil != tt.ok {
   285  			t.Errorf("%d:%s: was %s, %v; want %s, %v", i, tt.in, r, err == nil, tt.out, tt.ok)
   286  		}
   287  		if err == nil {
   288  			if _, _, y := Raw.Make("und-" + tt.out).Raw(); r != y {
   289  				t.Errorf("%d:%s: tag was %s; want %s", i, tt.in, r, y)
   290  			}
   291  		}
   292  	}
   293  }
   294  
   295  func TestIsCountry(t *testing.T) {
   296  	tests := []struct {
   297  		reg     string
   298  		country bool
   299  	}{
   300  		{"US", true},
   301  		{"001", false},
   302  		{"958", false},
   303  		{"419", false},
   304  		{"203", true},
   305  		{"020", true},
   306  		{"900", false},
   307  		{"999", false},
   308  		{"QO", false},
   309  		{"EU", false},
   310  		{"AA", false},
   311  		{"XK", true},
   312  	}
   313  	for i, tt := range tests {
   314  		reg, _ := getRegionID([]byte(tt.reg))
   315  		r := Region{reg}
   316  		if r.IsCountry() != tt.country {
   317  			t.Errorf("%d: IsCountry(%s) was %v; want %v", i, tt.reg, r.IsCountry(), tt.country)
   318  		}
   319  	}
   320  }
   321  
   322  func TestIsGroup(t *testing.T) {
   323  	tests := []struct {
   324  		reg   string
   325  		group bool
   326  	}{
   327  		{"US", false},
   328  		{"001", true},
   329  		{"958", false},
   330  		{"419", true},
   331  		{"203", false},
   332  		{"020", false},
   333  		{"900", false},
   334  		{"999", false},
   335  		{"QO", true},
   336  		{"EU", true},
   337  		{"AA", false},
   338  		{"XK", false},
   339  	}
   340  	for i, tt := range tests {
   341  		reg, _ := getRegionID([]byte(tt.reg))
   342  		r := Region{reg}
   343  		if r.IsGroup() != tt.group {
   344  			t.Errorf("%d: IsGroup(%s) was %v; want %v", i, tt.reg, r.IsGroup(), tt.group)
   345  		}
   346  	}
   347  }
   348  
   349  func TestContains(t *testing.T) {
   350  	tests := []struct {
   351  		enclosing, contained string
   352  		contains             bool
   353  	}{
   354  		// A region contains itself.
   355  		{"US", "US", true},
   356  		{"001", "001", true},
   357  
   358  		// Direct containment.
   359  		{"001", "002", true},
   360  		{"039", "XK", true},
   361  		{"150", "XK", true},
   362  		{"EU", "AT", true},
   363  		{"QO", "AQ", true},
   364  
   365  		// Indirect containemnt.
   366  		{"001", "US", true},
   367  		{"001", "419", true},
   368  		{"001", "013", true},
   369  
   370  		// No containment.
   371  		{"US", "001", false},
   372  		{"155", "EU", false},
   373  	}
   374  	for i, tt := range tests {
   375  		enc, _ := getRegionID([]byte(tt.enclosing))
   376  		con, _ := getRegionID([]byte(tt.contained))
   377  		r := Region{enc}
   378  		if got := r.Contains(Region{con}); got != tt.contains {
   379  			t.Errorf("%d: %s.Contains(%s) was %v; want %v", i, tt.enclosing, tt.contained, got, tt.contains)
   380  		}
   381  	}
   382  }
   383  
   384  func TestRegionCanonicalize(t *testing.T) {
   385  	for i, tt := range []struct{ in, out string }{
   386  		{"UK", "GB"},
   387  		{"TP", "TL"},
   388  		{"QU", "EU"},
   389  		{"SU", "SU"},
   390  		{"VD", "VN"},
   391  		{"DD", "DE"},
   392  	} {
   393  		r := MustParseRegion(tt.in)
   394  		want := MustParseRegion(tt.out)
   395  		if got := r.Canonicalize(); got != want {
   396  			t.Errorf("%d: got %v; want %v", i, got, want)
   397  		}
   398  	}
   399  }
   400  
   401  func TestRegionTLD(t *testing.T) {
   402  	for _, tt := range []struct {
   403  		in, out string
   404  		ok      bool
   405  	}{
   406  		{"EH", "EH", true},
   407  		{"FR", "FR", true},
   408  		{"TL", "TL", true},
   409  
   410  		// In ccTLD before in ISO.
   411  		{"GG", "GG", true},
   412  
   413  		// Non-standard assignment of ccTLD to ISO code.
   414  		{"GB", "UK", true},
   415  
   416  		// Exceptionally reserved in ISO and valid ccTLD.
   417  		{"UK", "UK", true},
   418  		{"AC", "AC", true},
   419  		{"EU", "EU", true},
   420  		{"SU", "SU", true},
   421  
   422  		// Exceptionally reserved in ISO and invalid ccTLD.
   423  		{"CP", "ZZ", false},
   424  		{"DG", "ZZ", false},
   425  		{"EA", "ZZ", false},
   426  		{"FX", "ZZ", false},
   427  		{"IC", "ZZ", false},
   428  		{"TA", "ZZ", false},
   429  
   430  		// Transitionally reserved in ISO (e.g. deprecated) but valid ccTLD as
   431  		// it is still being phased out.
   432  		{"AN", "AN", true},
   433  		{"TP", "TP", true},
   434  
   435  		// Transitionally reserved in ISO (e.g. deprecated) and invalid ccTLD.
   436  		// Defined in package language as it has a mapping in CLDR.
   437  		{"BU", "ZZ", false},
   438  		{"CS", "ZZ", false},
   439  		{"NT", "ZZ", false},
   440  		{"YU", "ZZ", false},
   441  		{"ZR", "ZZ", false},
   442  		// Not defined in package: SF.
   443  
   444  		// Indeterminately reserved in ISO.
   445  		// Defined in package language as it has a legacy mapping in CLDR.
   446  		{"DY", "ZZ", false},
   447  		{"RH", "ZZ", false},
   448  		{"VD", "ZZ", false},
   449  		// Not defined in package: EW, FL, JA, LF, PI, RA, RB, RC, RI, RL, RM,
   450  		// RN, RP, WG, WL, WV, and YV.
   451  
   452  		// Not assigned in ISO, but legacy definitions in CLDR.
   453  		{"DD", "ZZ", false},
   454  		{"YD", "ZZ", false},
   455  
   456  		// Normal mappings but somewhat special status in ccTLD.
   457  		{"BL", "BL", true},
   458  		{"MF", "MF", true},
   459  		{"BV", "BV", true},
   460  		{"SJ", "SJ", true},
   461  
   462  		// Have values when normalized, but not as is.
   463  		{"QU", "ZZ", false},
   464  
   465  		// ISO Private Use.
   466  		{"AA", "ZZ", false},
   467  		{"QM", "ZZ", false},
   468  		{"QO", "ZZ", false},
   469  		{"XA", "ZZ", false},
   470  		{"XK", "ZZ", false}, // Sometimes used for Kosovo, but invalid ccTLD.
   471  	} {
   472  		if tt.in == "" {
   473  			continue
   474  		}
   475  
   476  		r := MustParseRegion(tt.in)
   477  		var want Region
   478  		if tt.out != "ZZ" {
   479  			want = MustParseRegion(tt.out)
   480  		}
   481  		tld, err := r.TLD()
   482  		if got := err == nil; got != tt.ok {
   483  			t.Errorf("error(%v): got %v; want %v", r, got, tt.ok)
   484  		}
   485  		if tld != want {
   486  			t.Errorf("TLD(%v): got %v; want %v", r, tld, want)
   487  		}
   488  	}
   489  }
   490  
   491  func TestCanonicalize(t *testing.T) {
   492  	// TODO: do a full test using CLDR data in a separate regression test.
   493  	tests := []struct {
   494  		in, out string
   495  		option  CanonType
   496  	}{
   497  		{"en-Latn", "en", SuppressScript},
   498  		{"sr-Cyrl", "sr-Cyrl", SuppressScript},
   499  		{"sh", "sr-Latn", Legacy},
   500  		{"sh-HR", "sr-Latn-HR", Legacy},
   501  		{"sh-Cyrl-HR", "sr-Cyrl-HR", Legacy},
   502  		{"tl", "fil", Legacy},
   503  		{"no", "no", Legacy},
   504  		{"no", "nb", Legacy | CLDR},
   505  		{"cmn", "cmn", Legacy},
   506  		{"cmn", "zh", Macro},
   507  		{"yue", "yue", Macro},
   508  		{"nb", "no", Macro},
   509  		{"nb", "nb", Macro | CLDR},
   510  		{"no", "no", Macro},
   511  		{"no", "no", Macro | CLDR},
   512  		{"iw", "he", DeprecatedBase},
   513  		{"iw", "he", Deprecated | CLDR},
   514  		{"mo", "ro-MD", Deprecated}, // Adopted by CLDR as of version 25.
   515  		{"alb", "sq", Legacy},       // bibliographic
   516  		{"dut", "nl", Legacy},       // bibliographic
   517  		// As of CLDR 25, mo is no longer considered a legacy mapping.
   518  		{"mo", "mo", Legacy | CLDR},
   519  		{"und-AN", "und-AN", Deprecated},
   520  		{"und-YD", "und-YE", DeprecatedRegion},
   521  		{"und-YD", "und-YD", DeprecatedBase},
   522  		{"und-Qaai", "und-Zinh", DeprecatedScript},
   523  		{"und-Qaai", "und-Qaai", DeprecatedBase},
   524  		{"drh", "mn", All}, // drh -> khk -> mn
   525  	}
   526  	for i, tt := range tests {
   527  		in, _ := Raw.Parse(tt.in)
   528  		in, _ = tt.option.Canonicalize(in)
   529  		if in.String() != tt.out {
   530  			t.Errorf("%d:%s: was %s; want %s", i, tt.in, in.String(), tt.out)
   531  		}
   532  	}
   533  	// Test idempotence.
   534  	for _, base := range Supported.BaseLanguages() {
   535  		tag, _ := Raw.Compose(base)
   536  		got, _ := All.Canonicalize(tag)
   537  		want, _ := All.Canonicalize(got)
   538  		if got != want {
   539  			t.Errorf("idem(%s): got %s; want %s", tag, got, want)
   540  		}
   541  	}
   542  }
   543  
   544  func TestTypeForKey(t *testing.T) {
   545  	tests := []struct{ key, in, out string }{
   546  		{"co", "en", ""},
   547  		{"co", "en-u-abc", ""},
   548  		{"co", "en-u-co-phonebk", "phonebk"},
   549  		{"co", "en-u-co-phonebk-cu-aud", "phonebk"},
   550  		{"co", "x-foo-u-co-phonebk", ""},
   551  		{"nu", "en-u-co-phonebk-nu-arabic", "arabic"},
   552  	}
   553  	for _, tt := range tests {
   554  		if v := Make(tt.in).TypeForKey(tt.key); v != tt.out {
   555  			t.Errorf("%q[%q]: was %q; want %q", tt.in, tt.key, v, tt.out)
   556  		}
   557  	}
   558  }
   559  
   560  func TestSetTypeForKey(t *testing.T) {
   561  	tests := []struct {
   562  		key, value, in, out string
   563  		err                 bool
   564  	}{
   565  		// replace existing value
   566  		{"co", "pinyin", "en-u-co-phonebk", "en-u-co-pinyin", false},
   567  		{"co", "pinyin", "en-u-co-phonebk-cu-xau", "en-u-co-pinyin-cu-xau", false},
   568  		{"co", "pinyin", "en-u-co-phonebk-v-xx", "en-u-co-pinyin-v-xx", false},
   569  		{"co", "pinyin", "en-u-co-phonebk-x-x", "en-u-co-pinyin-x-x", false},
   570  		{"nu", "arabic", "en-u-co-phonebk-nu-vaai", "en-u-co-phonebk-nu-arabic", false},
   571  		// add to existing -u extension
   572  		{"co", "pinyin", "en-u-ca-gregory", "en-u-ca-gregory-co-pinyin", false},
   573  		{"co", "pinyin", "en-u-ca-gregory-nu-vaai", "en-u-ca-gregory-co-pinyin-nu-vaai", false},
   574  		{"co", "pinyin", "en-u-ca-gregory-v-va", "en-u-ca-gregory-co-pinyin-v-va", false},
   575  		{"co", "pinyin", "en-u-ca-gregory-x-a", "en-u-ca-gregory-co-pinyin-x-a", false},
   576  		{"ca", "gregory", "en-u-co-pinyin", "en-u-ca-gregory-co-pinyin", false},
   577  		// remove pair
   578  		{"co", "", "en-u-co-phonebk", "en", false},
   579  		{"co", "", "en-u-ca-gregory-co-phonebk", "en-u-ca-gregory", false},
   580  		{"co", "", "en-u-co-phonebk-nu-arabic", "en-u-nu-arabic", false},
   581  		{"co", "", "en", "en", false},
   582  		// add -u extension
   583  		{"co", "pinyin", "en", "en-u-co-pinyin", false},
   584  		{"co", "pinyin", "und", "und-u-co-pinyin", false},
   585  		{"co", "pinyin", "en-a-aaa", "en-a-aaa-u-co-pinyin", false},
   586  		{"co", "pinyin", "en-x-aaa", "en-u-co-pinyin-x-aaa", false},
   587  		{"co", "pinyin", "en-v-aa", "en-u-co-pinyin-v-aa", false},
   588  		{"co", "pinyin", "en-a-aaa-x-x", "en-a-aaa-u-co-pinyin-x-x", false},
   589  		{"co", "pinyin", "en-a-aaa-v-va", "en-a-aaa-u-co-pinyin-v-va", false},
   590  		// error on invalid values
   591  		{"co", "pinyinxxx", "en", "en", true},
   592  		{"co", "piny.n", "en", "en", true},
   593  		{"co", "pinyinxxx", "en-a-aaa", "en-a-aaa", true},
   594  		{"co", "pinyinxxx", "en-u-aaa", "en-u-aaa", true},
   595  		{"co", "pinyinxxx", "en-u-aaa-co-pinyin", "en-u-aaa-co-pinyin", true},
   596  		{"co", "pinyi.", "en-u-aaa-co-pinyin", "en-u-aaa-co-pinyin", true},
   597  		{"col", "pinyin", "en", "en", true},
   598  		{"co", "cu", "en", "en", true},
   599  		// error when setting on a private use tag
   600  		{"co", "phonebook", "x-foo", "x-foo", true},
   601  	}
   602  	for i, tt := range tests {
   603  		tag := Make(tt.in)
   604  		if v, err := tag.SetTypeForKey(tt.key, tt.value); v.String() != tt.out {
   605  			t.Errorf("%d:%q[%q]=%q: was %q; want %q", i, tt.in, tt.key, tt.value, v, tt.out)
   606  		} else if (err != nil) != tt.err {
   607  			t.Errorf("%d:%q[%q]=%q: error was %v; want %v", i, tt.in, tt.key, tt.value, err != nil, tt.err)
   608  		} else if val := v.TypeForKey(tt.key); err == nil && val != tt.value {
   609  			t.Errorf("%d:%q[%q]==%q: was %v; want %v", i, tt.out, tt.key, tt.value, val, tt.value)
   610  		}
   611  		if len(tag.String()) <= 3 {
   612  			// Simulate a tag for which the string has not been set.
   613  			tag.str, tag.pExt, tag.pVariant = "", 0, 0
   614  			if tag, err := tag.SetTypeForKey(tt.key, tt.value); err == nil {
   615  				if val := tag.TypeForKey(tt.key); err == nil && val != tt.value {
   616  					t.Errorf("%d:%q[%q]==%q: was %v; want %v", i, tt.out, tt.key, tt.value, val, tt.value)
   617  				}
   618  			}
   619  		}
   620  	}
   621  }
   622  
   623  func TestFindKeyAndType(t *testing.T) {
   624  	// out is either the matched type in case of a match or the original
   625  	// string up till the insertion point.
   626  	tests := []struct {
   627  		key     string
   628  		hasExt  bool
   629  		in, out string
   630  	}{
   631  		// Don't search past a private use extension.
   632  		{"co", false, "en-x-foo-u-co-pinyin", "en"},
   633  		{"co", false, "x-foo-u-co-pinyin", ""},
   634  		{"co", false, "en-s-fff-x-foo", "en-s-fff"},
   635  		// Insertion points in absence of -u extension.
   636  		{"cu", false, "en", ""}, // t.str is ""
   637  		{"cu", false, "en-v-va", "en"},
   638  		{"cu", false, "en-a-va", "en-a-va"},
   639  		{"cu", false, "en-a-va-v-va", "en-a-va"},
   640  		{"cu", false, "en-x-a", "en"},
   641  		// Tags with the -u extension.
   642  		{"co", true, "en-u-co-standard", "standard"},
   643  		{"co", true, "yue-u-co-pinyin", "pinyin"},
   644  		{"co", true, "en-u-co-abc", "abc"},
   645  		{"co", true, "en-u-co-abc-def", "abc-def"},
   646  		{"co", true, "en-u-co-abc-def-x-foo", "abc-def"},
   647  		{"co", true, "en-u-co-standard-nu-arab", "standard"},
   648  		{"co", true, "yue-u-co-pinyin-nu-arab", "pinyin"},
   649  		// Insertion points.
   650  		{"cu", true, "en-u-co-standard", "en-u-co-standard"},
   651  		{"cu", true, "yue-u-co-pinyin-x-foo", "yue-u-co-pinyin"},
   652  		{"cu", true, "en-u-co-abc", "en-u-co-abc"},
   653  		{"cu", true, "en-u-nu-arabic", "en-u"},
   654  		{"cu", true, "en-u-co-abc-def-nu-arabic", "en-u-co-abc-def"},
   655  	}
   656  	for i, tt := range tests {
   657  		start, end, hasExt := Make(tt.in).findTypeForKey(tt.key)
   658  		if start != end {
   659  			res := tt.in[start:end]
   660  			if res != tt.out {
   661  				t.Errorf("%d:%s: was %q; want %q", i, tt.in, res, tt.out)
   662  			}
   663  		} else {
   664  			if hasExt != tt.hasExt {
   665  				t.Errorf("%d:%s: hasExt was %v; want %v", i, tt.in, hasExt, tt.hasExt)
   666  				continue
   667  			}
   668  			if tt.in[:start] != tt.out {
   669  				t.Errorf("%d:%s: insertion point was %q; want %q", i, tt.in, tt.in[:start], tt.out)
   670  			}
   671  		}
   672  	}
   673  }
   674  
   675  func TestParent(t *testing.T) {
   676  	tests := []struct{ in, out string }{
   677  		// Strip variants and extensions first
   678  		{"de-u-co-phonebk", "de"},
   679  		{"de-1994", "de"},
   680  		{"de-Latn-1994", "de"}, // remove superfluous script.
   681  
   682  		// Ensure the canonical Tag for an entry is in the chain for base-script
   683  		// pairs.
   684  		{"zh-Hans", "zh"},
   685  
   686  		// Skip the script if it is the maximized version. CLDR files for the
   687  		// skipped tag are always empty.
   688  		{"zh-Hans-TW", "zh"},
   689  		{"zh-Hans-CN", "zh"},
   690  
   691  		// Insert the script if the maximized script is not the same as the
   692  		// maximized script of the base language.
   693  		{"zh-TW", "zh-Hant"},
   694  		{"zh-HK", "zh-Hant"},
   695  		{"zh-Hant-TW", "zh-Hant"},
   696  		{"zh-Hant-HK", "zh-Hant"},
   697  
   698  		// Non-default script skips to und.
   699  		// CLDR
   700  		{"az-Cyrl", "und"},
   701  		{"bs-Cyrl", "und"},
   702  		{"en-Dsrt", "und"},
   703  		{"ha-Arab", "und"},
   704  		{"mn-Mong", "und"},
   705  		{"pa-Arab", "und"},
   706  		{"shi-Latn", "und"},
   707  		{"sr-Latn", "und"},
   708  		{"uz-Arab", "und"},
   709  		{"uz-Cyrl", "und"},
   710  		{"vai-Latn", "und"},
   711  		{"zh-Hant", "und"},
   712  		// extra
   713  		{"nl-Cyrl", "und"},
   714  
   715  		// World english inherits from en-001.
   716  		{"en-150", "en-001"},
   717  		{"en-AU", "en-001"},
   718  		{"en-BE", "en-001"},
   719  		{"en-GG", "en-001"},
   720  		{"en-GI", "en-001"},
   721  		{"en-HK", "en-001"},
   722  		{"en-IE", "en-001"},
   723  		{"en-IM", "en-001"},
   724  		{"en-IN", "en-001"},
   725  		{"en-JE", "en-001"},
   726  		{"en-MT", "en-001"},
   727  		{"en-NZ", "en-001"},
   728  		{"en-PK", "en-001"},
   729  		{"en-SG", "en-001"},
   730  
   731  		// Spanish in Latin-American countries have es-419 as parent.
   732  		{"es-AR", "es-419"},
   733  		{"es-BO", "es-419"},
   734  		{"es-CL", "es-419"},
   735  		{"es-CO", "es-419"},
   736  		{"es-CR", "es-419"},
   737  		{"es-CU", "es-419"},
   738  		{"es-DO", "es-419"},
   739  		{"es-EC", "es-419"},
   740  		{"es-GT", "es-419"},
   741  		{"es-HN", "es-419"},
   742  		{"es-MX", "es-419"},
   743  		{"es-NI", "es-419"},
   744  		{"es-PA", "es-419"},
   745  		{"es-PE", "es-419"},
   746  		{"es-PR", "es-419"},
   747  		{"es-PY", "es-419"},
   748  		{"es-SV", "es-419"},
   749  		{"es-US", "es-419"},
   750  		{"es-UY", "es-419"},
   751  		{"es-VE", "es-419"},
   752  		// exceptions (according to CLDR)
   753  		{"es-CW", "es"},
   754  
   755  		// Inherit from pt-PT, instead of pt for these countries.
   756  		{"pt-AO", "pt-PT"},
   757  		{"pt-CV", "pt-PT"},
   758  		{"pt-GW", "pt-PT"},
   759  		{"pt-MO", "pt-PT"},
   760  		{"pt-MZ", "pt-PT"},
   761  		{"pt-ST", "pt-PT"},
   762  		{"pt-TL", "pt-PT"},
   763  	}
   764  	for _, tt := range tests {
   765  		tag := Raw.MustParse(tt.in)
   766  		if p := Raw.MustParse(tt.out); p != tag.Parent() {
   767  			t.Errorf("%s: was %v; want %v", tt.in, tag.Parent(), p)
   768  		}
   769  	}
   770  }
   771  
   772  var (
   773  	// Tags without error that don't need to be changed.
   774  	benchBasic = []string{
   775  		"en",
   776  		"en-Latn",
   777  		"en-GB",
   778  		"za",
   779  		"zh-Hant",
   780  		"zh",
   781  		"zh-HK",
   782  		"ar-MK",
   783  		"en-CA",
   784  		"fr-CA",
   785  		"fr-CH",
   786  		"fr",
   787  		"lv",
   788  		"he-IT",
   789  		"tlh",
   790  		"ja",
   791  		"ja-Jpan",
   792  		"ja-Jpan-JP",
   793  		"de-1996",
   794  		"de-CH",
   795  		"sr",
   796  		"sr-Latn",
   797  	}
   798  	// Tags with extensions, not changes required.
   799  	benchExt = []string{
   800  		"x-a-b-c-d",
   801  		"x-aa-bbbb-cccccccc-d",
   802  		"en-x_cc-b-bbb-a-aaa",
   803  		"en-c_cc-b-bbb-a-aaa-x-x",
   804  		"en-u-co-phonebk",
   805  		"en-Cyrl-u-co-phonebk",
   806  		"en-US-u-co-phonebk-cu-xau",
   807  		"en-nedix-u-co-phonebk",
   808  		"en-t-t0-abcd",
   809  		"en-t-nl-latn",
   810  		"en-t-t0-abcd-x-a",
   811  	}
   812  	// Change, but not memory allocation required.
   813  	benchSimpleChange = []string{
   814  		"EN",
   815  		"i-klingon",
   816  		"en-latn",
   817  		"zh-cmn-Hans-CN",
   818  		"iw-NL",
   819  	}
   820  	// Change and memory allocation required.
   821  	benchChangeAlloc = []string{
   822  		"en-c_cc-b-bbb-a-aaa",
   823  		"en-u-cu-xua-co-phonebk",
   824  		"en-u-cu-xua-co-phonebk-a-cd",
   825  		"en-u-def-abc-cu-xua-co-phonebk",
   826  		"en-t-en-Cyrl-NL-1994",
   827  		"en-t-en-Cyrl-NL-1994-t0-abc-def",
   828  	}
   829  	// Tags that result in errors.
   830  	benchErr = []string{
   831  		// IllFormed
   832  		"x_A.-B-C_D",
   833  		"en-u-cu-co-phonebk",
   834  		"en-u-cu-xau-co",
   835  		"en-t-nl-abcd",
   836  		// Invalid
   837  		"xx",
   838  		"nl-Uuuu",
   839  		"nl-QB",
   840  	}
   841  	benchChange = append(benchSimpleChange, benchChangeAlloc...)
   842  	benchAll    = append(append(append(benchBasic, benchExt...), benchChange...), benchErr...)
   843  )
   844  
   845  func doParse(b *testing.B, tag []string) {
   846  	for i := 0; i < b.N; i++ {
   847  		// Use the modulo instead of looping over all tags so that we get a somewhat
   848  		// meaningful ns/op.
   849  		Parse(tag[i%len(tag)])
   850  	}
   851  }
   852  
   853  func BenchmarkParse(b *testing.B) {
   854  	doParse(b, benchAll)
   855  }
   856  
   857  func BenchmarkParseBasic(b *testing.B) {
   858  	doParse(b, benchBasic)
   859  }
   860  
   861  func BenchmarkParseError(b *testing.B) {
   862  	doParse(b, benchErr)
   863  }
   864  
   865  func BenchmarkParseSimpleChange(b *testing.B) {
   866  	doParse(b, benchSimpleChange)
   867  }
   868  
   869  func BenchmarkParseChangeAlloc(b *testing.B) {
   870  	doParse(b, benchChangeAlloc)
   871  }