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