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