mirror of
https://github.com/restic/restic.git
synced 2024-08-21 20:03:23 +02:00
317 lines
7.1 KiB
Go
317 lines
7.1 KiB
Go
// 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: "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: "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\x04\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
|
|
}
|