...

Source file src/golang.org/x/text/message/catalog/catalog_test.go

Documentation: golang.org/x/text/message/catalog

     1  // Copyright 2017 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 catalog
     6  
     7  import (
     8  	"bytes"
     9  	"path"
    10  	"reflect"
    11  	"strings"
    12  	"testing"
    13  
    14  	"golang.org/x/text/internal/catmsg"
    15  	"golang.org/x/text/language"
    16  )
    17  
    18  type entry struct {
    19  	tag, key string
    20  	msg      interface{}
    21  }
    22  
    23  func langs(s string) []language.Tag {
    24  	t, _, _ := language.ParseAcceptLanguage(s)
    25  	return t
    26  }
    27  
    28  type testCase struct {
    29  	desc     string
    30  	cat      []entry
    31  	lookup   []entry
    32  	fallback string
    33  	match    []string
    34  	tags     []language.Tag
    35  }
    36  
    37  var testCases = []testCase{{
    38  	desc: "empty catalog",
    39  	lookup: []entry{
    40  		{"en", "key", ""},
    41  		{"en", "", ""},
    42  		{"nl", "", ""},
    43  	},
    44  	match: []string{
    45  		"gr -> und",
    46  		"en-US -> und",
    47  		"af -> und",
    48  	},
    49  	tags: nil, // not an empty list.
    50  }, {
    51  	desc: "one entry",
    52  	cat: []entry{
    53  		{"en", "hello", "Hello!"},
    54  	},
    55  	lookup: []entry{
    56  		{"und", "hello", ""},
    57  		{"nl", "hello", ""},
    58  		{"en", "hello", "Hello!"},
    59  		{"en-US", "hello", "Hello!"},
    60  		{"en-GB", "hello", "Hello!"},
    61  		{"en-oxendict", "hello", "Hello!"},
    62  		{"en-oxendict-u-ms-metric", "hello", "Hello!"},
    63  	},
    64  	match: []string{
    65  		"gr -> en",
    66  		"en-US -> en-u-rg-uszzzz",
    67  	},
    68  	tags: langs("en"),
    69  }, {
    70  	desc: "hierarchical languages",
    71  	cat: []entry{
    72  		{"en", "hello", "Hello!"},
    73  		{"en-GB", "hello", "Hellø!"},
    74  		{"en-US", "hello", "Howdy!"},
    75  		{"en", "greetings", "Greetings!"},
    76  		{"gsw", "hello", "Grüetzi!"},
    77  	},
    78  	lookup: []entry{
    79  		{"und", "hello", ""},
    80  		{"nl", "hello", ""},
    81  		{"en", "hello", "Hello!"},
    82  		{"en-US", "hello", "Howdy!"},
    83  		{"en-GB", "hello", "Hellø!"},
    84  		{"en-oxendict", "hello", "Hello!"},
    85  		{"en-US-oxendict-u-ms-metric", "hello", "Howdy!"},
    86  
    87  		{"und", "greetings", ""},
    88  		{"nl", "greetings", ""},
    89  		{"en", "greetings", "Greetings!"},
    90  		{"en-US", "greetings", "Greetings!"},
    91  		{"en-GB", "greetings", "Greetings!"},
    92  		{"en-oxendict", "greetings", "Greetings!"},
    93  		{"en-US-oxendict-u-ms-metric", "greetings", "Greetings!"},
    94  	},
    95  	fallback: "gsw",
    96  	match: []string{
    97  		"gr -> gsw",
    98  		"en-US -> en-US",
    99  	},
   100  	tags: langs("gsw, en, en-GB, en-US"),
   101  }, {
   102  	desc: "variables",
   103  	cat: []entry{
   104  		{"en", "hello %s", []Message{
   105  			Var("person", String("Jane")),
   106  			String("Hello ${person}!"),
   107  		}},
   108  		{"en", "hello error", []Message{
   109  			Var("person", String("Jane")),
   110  			noMatchMessage{}, // trigger sequence path.
   111  			String("Hello ${person."),
   112  		}},
   113  		{"en", "fallback to var value", []Message{
   114  			Var("you", noMatchMessage{}, noMatchMessage{}),
   115  			String("Hello ${you}."),
   116  		}},
   117  		{"en", "scopes", []Message{
   118  			Var("person1", String("Mark")),
   119  			Var("person2", String("Jane")),
   120  			Var("couple",
   121  				Var("person1", String("Joe")),
   122  				String("${person1} and ${person2}")),
   123  			String("Hello ${couple}."),
   124  		}},
   125  		{"en", "missing var", String("Hello ${missing}.")},
   126  	},
   127  	lookup: []entry{
   128  		{"en", "hello %s", "Hello Jane!"},
   129  		{"en", "hello error", "Hello $!(MISSINGBRACE)"},
   130  		{"en", "fallback to var value", "Hello you."},
   131  		{"en", "scopes", "Hello Joe and Jane."},
   132  		{"en", "missing var", "Hello missing."},
   133  	},
   134  	tags: langs("en"),
   135  }, {
   136  	desc: "macros",
   137  	cat: []entry{
   138  		{"en", "macro1", String("Hello ${macro1(1)}.")},
   139  		{"en", "macro2", String("Hello ${ macro1(2) }!")},
   140  		{"en", "macroWS", String("Hello ${ macro1( 2 ) }!")},
   141  		{"en", "missing", String("Hello ${ missing(1 }.")},
   142  		{"en", "badnum", String("Hello ${ badnum(1b) }.")},
   143  		{"en", "undefined", String("Hello ${ undefined(1) }.")},
   144  		{"en", "macroU", String("Hello ${ macroU(2) }!")},
   145  	},
   146  	lookup: []entry{
   147  		{"en", "macro1", "Hello Joe."},
   148  		{"en", "macro2", "Hello Joe!"},
   149  		{"en-US", "macroWS", "Hello Joe!"},
   150  		{"en-NL", "missing", "Hello $!(MISSINGPAREN)."},
   151  		{"en", "badnum", "Hello $!(BADNUM)."},
   152  		{"en", "undefined", "Hello undefined."},
   153  		{"en", "macroU", "Hello macroU!"},
   154  	},
   155  	tags: langs("en"),
   156  }}
   157  
   158  func setMacros(b *Builder) {
   159  	b.SetMacro(language.English, "macro1", String("Joe"))
   160  	b.SetMacro(language.Und, "macro2", String("${macro1(1)}"))
   161  	b.SetMacro(language.English, "macroU", noMatchMessage{})
   162  }
   163  
   164  type buildFunc func(t *testing.T, tc testCase) Catalog
   165  
   166  func initBuilder(t *testing.T, tc testCase) Catalog {
   167  	options := []Option{}
   168  	if tc.fallback != "" {
   169  		options = append(options, Fallback(language.MustParse(tc.fallback)))
   170  	}
   171  	cat := NewBuilder(options...)
   172  	for _, e := range tc.cat {
   173  		tag := language.MustParse(e.tag)
   174  		switch msg := e.msg.(type) {
   175  		case string:
   176  
   177  			cat.SetString(tag, e.key, msg)
   178  		case Message:
   179  			cat.Set(tag, e.key, msg)
   180  		case []Message:
   181  			cat.Set(tag, e.key, msg...)
   182  		}
   183  	}
   184  	setMacros(cat)
   185  	return cat
   186  }
   187  
   188  type dictionary map[string]string
   189  
   190  func (d dictionary) Lookup(key string) (data string, ok bool) {
   191  	data, ok = d[key]
   192  	return data, ok
   193  }
   194  
   195  func initCatalog(t *testing.T, tc testCase) Catalog {
   196  	m := map[string]Dictionary{}
   197  	for _, e := range tc.cat {
   198  		m[e.tag] = dictionary{}
   199  	}
   200  	for _, e := range tc.cat {
   201  		var msg Message
   202  		switch x := e.msg.(type) {
   203  		case string:
   204  			msg = String(x)
   205  		case Message:
   206  			msg = x
   207  		case []Message:
   208  			msg = firstInSequence(x)
   209  		}
   210  		data, _ := catmsg.Compile(language.MustParse(e.tag), nil, msg)
   211  		m[e.tag].(dictionary)[e.key] = data
   212  	}
   213  	options := []Option{}
   214  	if tc.fallback != "" {
   215  		options = append(options, Fallback(language.MustParse(tc.fallback)))
   216  	}
   217  	c, err := NewFromMap(m, options...)
   218  	if err != nil {
   219  		t.Fatal(err)
   220  	}
   221  	// TODO: implement macros for fixed catalogs.
   222  	b := NewBuilder()
   223  	setMacros(b)
   224  	c.(*catalog).macros.index = b.macros.index
   225  	return c
   226  }
   227  
   228  func TestMatcher(t *testing.T) {
   229  	test := func(t *testing.T, init buildFunc) {
   230  		for _, tc := range testCases {
   231  			for _, s := range tc.match {
   232  				a := strings.Split(s, "->")
   233  				t.Run(path.Join(tc.desc, a[0]), func(t *testing.T) {
   234  					cat := init(t, tc)
   235  					got, _ := language.MatchStrings(cat.Matcher(), a[0])
   236  					want := language.MustParse(strings.TrimSpace(a[1]))
   237  					if got != want {
   238  						t.Errorf("got %q; want %q", got, want)
   239  					}
   240  				})
   241  			}
   242  		}
   243  	}
   244  	t.Run("Builder", func(t *testing.T) { test(t, initBuilder) })
   245  	t.Run("Catalog", func(t *testing.T) { test(t, initCatalog) })
   246  }
   247  
   248  func TestCatalog(t *testing.T) {
   249  	test := func(t *testing.T, init buildFunc) {
   250  		for _, tc := range testCases {
   251  			cat := init(t, tc)
   252  			wantTags := tc.tags
   253  			if got := cat.Languages(); !reflect.DeepEqual(got, wantTags) {
   254  				t.Errorf("%s:Languages: got %v; want %v", tc.desc, got, wantTags)
   255  			}
   256  
   257  			for _, e := range tc.lookup {
   258  				t.Run(path.Join(tc.desc, e.tag, e.key), func(t *testing.T) {
   259  					tag := language.MustParse(e.tag)
   260  					buf := testRenderer{}
   261  					ctx := cat.Context(tag, &buf)
   262  					want := e.msg.(string)
   263  					err := ctx.Execute(e.key)
   264  					gotFound := err != ErrNotFound
   265  					wantFound := want != ""
   266  					if gotFound != wantFound {
   267  						t.Fatalf("err: got %v (%v); want %v", gotFound, err, wantFound)
   268  					}
   269  					if got := buf.buf.String(); got != want {
   270  						t.Errorf("Lookup:\ngot  %q\nwant %q", got, want)
   271  					}
   272  				})
   273  			}
   274  		}
   275  	}
   276  	t.Run("Builder", func(t *testing.T) { test(t, initBuilder) })
   277  	t.Run("Catalog", func(t *testing.T) { test(t, initCatalog) })
   278  }
   279  
   280  type testRenderer struct {
   281  	buf bytes.Buffer
   282  }
   283  
   284  func (f *testRenderer) Arg(i int) interface{} { return nil }
   285  func (f *testRenderer) Render(s string)       { f.buf.WriteString(s) }
   286  
   287  var msgNoMatch = catmsg.Register("no match", func(d *catmsg.Decoder) bool {
   288  	return false // no match
   289  })
   290  
   291  type noMatchMessage struct{}
   292  
   293  func (noMatchMessage) Compile(e *catmsg.Encoder) error {
   294  	e.EncodeMessageType(msgNoMatch)
   295  	return catmsg.ErrIncomplete
   296  }
   297  

View as plain text