| // Copyright 2017 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 catmsg | |
| import ( | |
| "errors" | |
| "strings" | |
| "testing" | |
| "golang.org/x/text/language" | |
| ) | |
| type renderer struct { | |
| args []int | |
| result string | |
| } | |
| func (r *renderer) Arg(i int) interface{} { | |
| if i >= len(r.args) { | |
| return nil | |
| } | |
| return r.args[i] | |
| } | |
| func (r *renderer) Render(s string) { | |
| if r.result != "" { | |
| r.result += "|" | |
| } | |
| r.result += s | |
| } | |
| func TestCodec(t *testing.T) { | |
| type test struct { | |
| args []int | |
| out string | |
| decErr string | |
| } | |
| single := func(out, err string) []test { return []test{{out: out, decErr: err}} } | |
| testCases := []struct { | |
| desc string | |
| m Message | |
| enc string | |
| encErr string | |
| tests []test | |
| }{{ | |
| desc: "unused variable", | |
| m: &Var{"name", String("foo")}, | |
| encErr: errIsVar.Error(), | |
| tests: single("", ""), | |
| }, { | |
| desc: "empty", | |
| m: empty{}, | |
| tests: single("", ""), | |
| }, { | |
| desc: "sequence with empty", | |
| m: seq{empty{}}, | |
| tests: single("", ""), | |
| }, { | |
| desc: "raw string", | |
| m: Raw("foo"), | |
| tests: single("foo", ""), | |
| }, { | |
| desc: "raw string no sub", | |
| m: Raw("${foo}"), | |
| enc: "\x02${foo}", | |
| tests: single("${foo}", ""), | |
| }, { | |
| desc: "simple string", | |
| m: String("foo"), | |
| tests: single("foo", ""), | |
| }, { | |
| desc: "affix", | |
| m: &Affix{String("foo"), "\t", "\n"}, | |
| tests: single("\t|foo|\n", ""), | |
| }, { | |
| desc: "missing var", | |
| m: String("foo${bar}"), | |
| enc: "\x03\x03foo\x02\x03bar", | |
| encErr: `unknown var "bar"`, | |
| tests: single("foo|bar", ""), | |
| }, { | |
| desc: "empty var", | |
| m: seq{ | |
| &Var{"bar", seq{}}, | |
| String("foo${bar}"), | |
| }, | |
| enc: "\x00\x05\x04\x02bar\x03\x03foo\x00\x00", | |
| // TODO: recognize that it is cheaper to substitute bar. | |
| tests: single("foo|bar", ""), | |
| }, { | |
| desc: "var after value", | |
| m: seq{ | |
| String("foo${bar}"), | |
| &Var{"bar", String("baz")}, | |
| }, | |
| encErr: errIsVar.Error(), | |
| tests: single("foo|bar", ""), | |
| }, { | |
| desc: "substitution", | |
| m: seq{ | |
| &Var{"bar", String("baz")}, | |
| String("foo${bar}"), | |
| }, | |
| tests: single("foo|baz", ""), | |
| }, { | |
| desc: "affix with substitution", | |
| m: &Affix{seq{ | |
| &Var{"bar", String("baz")}, | |
| String("foo${bar}"), | |
| }, "\t", "\n"}, | |
| tests: single("\t|foo|baz|\n", ""), | |
| }, { | |
| desc: "shadowed variable", | |
| m: seq{ | |
| &Var{"bar", String("baz")}, | |
| seq{ | |
| &Var{"bar", String("BAZ")}, | |
| String("foo${bar}"), | |
| }, | |
| }, | |
| tests: single("foo|BAZ", ""), | |
| }, { | |
| desc: "nested value", | |
| m: nestedLang{nestedLang{empty{}}}, | |
| tests: single("nl|nl", ""), | |
| }, { | |
| desc: "not shadowed variable", | |
| m: seq{ | |
| &Var{"bar", String("baz")}, | |
| seq{ | |
| String("foo${bar}"), | |
| &Var{"bar", String("BAZ")}, | |
| }, | |
| }, | |
| encErr: errIsVar.Error(), | |
| tests: single("foo|baz", ""), | |
| }, { | |
| desc: "duplicate variable", | |
| m: seq{ | |
| &Var{"bar", String("baz")}, | |
| &Var{"bar", String("BAZ")}, | |
| String("${bar}"), | |
| }, | |
| encErr: "catmsg: duplicate variable \"bar\"", | |
| tests: single("baz", ""), | |
| }, { | |
| desc: "complete incomplete variable", | |
| m: seq{ | |
| &Var{"bar", incomplete{}}, | |
| String("${bar}"), | |
| }, | |
| enc: "\x00\t\b\x01\x01\x14\x04\x02bar\x03\x00\x00\x00", | |
| // TODO: recognize that it is cheaper to substitute bar. | |
| tests: single("bar", ""), | |
| }, { | |
| desc: "incomplete sequence", | |
| m: seq{ | |
| incomplete{}, | |
| incomplete{}, | |
| }, | |
| encErr: ErrIncomplete.Error(), | |
| tests: single("", ErrNoMatch.Error()), | |
| }, { | |
| desc: "compile error variable", | |
| m: seq{ | |
| &Var{"bar", errorCompileMsg{}}, | |
| String("${bar}"), | |
| }, | |
| encErr: errCompileTest.Error(), | |
| tests: single("bar", ""), | |
| }, { | |
| desc: "compile error message", | |
| m: errorCompileMsg{}, | |
| encErr: errCompileTest.Error(), | |
| tests: single("", ""), | |
| }, { | |
| desc: "compile error sequence", | |
| m: seq{ | |
| errorCompileMsg{}, | |
| errorCompileMsg{}, | |
| }, | |
| encErr: errCompileTest.Error(), | |
| tests: single("", ""), | |
| }, { | |
| desc: "macro", | |
| m: String("${exists(1)}"), | |
| tests: single("you betya!", ""), | |
| }, { | |
| desc: "macro incomplete", | |
| m: String("${incomplete(1)}"), | |
| enc: "\x03\x00\x01\nincomplete\x01", | |
| tests: single("incomplete", ""), | |
| }, { | |
| desc: "macro undefined at end", | |
| m: String("${undefined(1)}"), | |
| enc: "\x03\x00\x01\tundefined\x01", | |
| tests: single("undefined", "catmsg: undefined macro \"undefined\""), | |
| }, { | |
| desc: "macro undefined with more text following", | |
| m: String("${undefined(1)}."), | |
| enc: "\x03\x00\x01\tundefined\x01\x01.", | |
| tests: single("undefined|.", "catmsg: undefined macro \"undefined\""), | |
| }, { | |
| desc: "macro missing paren", | |
| m: String("${missing(1}"), | |
| encErr: "catmsg: missing ')'", | |
| tests: single("$!(MISSINGPAREN)", ""), | |
| }, { | |
| desc: "macro bad num", | |
| m: String("aa${bad(a)}"), | |
| encErr: "catmsg: invalid number \"a\"", | |
| tests: single("aa$!(BADNUM)", ""), | |
| }, { | |
| desc: "var missing brace", | |
| m: String("a${missing"), | |
| encErr: "catmsg: missing '}'", | |
| tests: single("a$!(MISSINGBRACE)", ""), | |
| }} | |
| r := &renderer{} | |
| dec := NewDecoder(language.Und, r, macros) | |
| for _, tc := range testCases { | |
| t.Run(tc.desc, func(t *testing.T) { | |
| // Use a language other than Und so that we can test | |
| // passing the language to nested values. | |
| data, err := Compile(language.Dutch, macros, tc.m) | |
| if failErr(err, tc.encErr) { | |
| t.Errorf("encoding error: got %+q; want %+q", err, tc.encErr) | |
| } | |
| if tc.enc != "" && data != tc.enc { | |
| t.Errorf("encoding: got %+q; want %+q", data, tc.enc) | |
| } | |
| for _, st := range tc.tests { | |
| t.Run("", func(t *testing.T) { | |
| *r = renderer{args: st.args} | |
| if err = dec.Execute(data); failErr(err, st.decErr) { | |
| t.Errorf("decoding error: got %+q; want %+q", err, st.decErr) | |
| } | |
| if r.result != st.out { | |
| t.Errorf("decode: got %+q; want %+q", r.result, st.out) | |
| } | |
| }) | |
| } | |
| }) | |
| } | |
| } | |
| func failErr(got error, want string) bool { | |
| if got == nil { | |
| return want != "" | |
| } | |
| return want == "" || !strings.Contains(got.Error(), want) | |
| } | |
| type seq []Message | |
| func (s seq) Compile(e *Encoder) (err error) { | |
| err = ErrIncomplete | |
| e.EncodeMessageType(msgFirst) | |
| for _, m := range s { | |
| // Pass only the last error, but allow erroneous or complete messages | |
| // here to allow testing different scenarios. | |
| err = e.EncodeMessage(m) | |
| } | |
| return err | |
| } | |
| type empty struct{} | |
| func (empty) Compile(e *Encoder) (err error) { return nil } | |
| var msgIncomplete = Register( | |
| "golang.org/x/text/internal/catmsg.incomplete", | |
| func(d *Decoder) bool { return false }) | |
| type incomplete struct{} | |
| func (incomplete) Compile(e *Encoder) (err error) { | |
| e.EncodeMessageType(msgIncomplete) | |
| return ErrIncomplete | |
| } | |
| var msgNested = Register( | |
| "golang.org/x/text/internal/catmsg.nested", | |
| func(d *Decoder) bool { | |
| d.Render(d.DecodeString()) | |
| d.ExecuteMessage() | |
| return true | |
| }) | |
| type nestedLang struct{ Message } | |
| func (n nestedLang) Compile(e *Encoder) (err error) { | |
| e.EncodeMessageType(msgNested) | |
| e.EncodeString(e.Language().String()) | |
| e.EncodeMessage(n.Message) | |
| return nil | |
| } | |
| type errorCompileMsg struct{} | |
| var errCompileTest = errors.New("catmsg: compile error test") | |
| func (errorCompileMsg) Compile(e *Encoder) (err error) { | |
| return errCompileTest | |
| } | |
| type dictionary struct{} | |
| var ( | |
| macros = dictionary{} | |
| dictMessages = map[string]string{ | |
| "exists": compile(String("you betya!")), | |
| "incomplete": compile(incomplete{}), | |
| } | |
| ) | |
| func (d dictionary) Lookup(key string) (data string, ok bool) { | |
| data, ok = dictMessages[key] | |
| return | |
| } | |
| func compile(m Message) (data string) { | |
| data, _ = Compile(language.Und, macros, m) | |
| return data | |
| } |