blob: b2a7a9e45f6b45f8b0cb5b9a01ecb72d87df835c [file] [log] [blame] [edit]
// 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
}