golang.org/x/text@v0.14.0/internal/catmsg/catmsg_test.go (about) 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 catmsg 6 7 import ( 8 "errors" 9 "strings" 10 "testing" 11 12 "golang.org/x/text/language" 13 ) 14 15 type renderer struct { 16 args []int 17 result string 18 } 19 20 func (r *renderer) Arg(i int) interface{} { 21 if i >= len(r.args) { 22 return nil 23 } 24 return r.args[i] 25 } 26 27 func (r *renderer) Render(s string) { 28 if r.result != "" { 29 r.result += "|" 30 } 31 r.result += s 32 } 33 34 func TestCodec(t *testing.T) { 35 type test struct { 36 args []int 37 out string 38 decErr string 39 } 40 single := func(out, err string) []test { return []test{{out: out, decErr: err}} } 41 testCases := []struct { 42 desc string 43 m Message 44 enc string 45 encErr string 46 tests []test 47 }{{ 48 desc: "unused variable", 49 m: &Var{"name", String("foo")}, 50 encErr: errIsVar.Error(), 51 tests: single("", ""), 52 }, { 53 desc: "empty", 54 m: empty{}, 55 tests: single("", ""), 56 }, { 57 desc: "sequence with empty", 58 m: seq{empty{}}, 59 tests: single("", ""), 60 }, { 61 desc: "raw string", 62 m: Raw("foo"), 63 tests: single("foo", ""), 64 }, { 65 desc: "raw string no sub", 66 m: Raw("${foo}"), 67 enc: "\x02${foo}", 68 tests: single("${foo}", ""), 69 }, { 70 desc: "simple string", 71 m: String("foo"), 72 tests: single("foo", ""), 73 }, { 74 desc: "affix", 75 m: &Affix{String("foo"), "\t", "\n"}, 76 tests: single("\t|foo|\n", ""), 77 }, { 78 desc: "missing var", 79 m: String("foo${bar}"), 80 enc: "\x03\x03foo\x02\x03bar", 81 encErr: `unknown var "bar"`, 82 tests: single("foo|bar", ""), 83 }, { 84 desc: "empty var", 85 m: seq{ 86 &Var{"bar", seq{}}, 87 String("foo${bar}"), 88 }, 89 enc: "\x00\x05\x04\x02bar\x03\x03foo\x00\x00", 90 // TODO: recognize that it is cheaper to substitute bar. 91 tests: single("foo|bar", ""), 92 }, { 93 desc: "var after value", 94 m: seq{ 95 String("foo${bar}"), 96 &Var{"bar", String("baz")}, 97 }, 98 encErr: errIsVar.Error(), 99 tests: single("foo|bar", ""), 100 }, { 101 desc: "substitution", 102 m: seq{ 103 &Var{"bar", String("baz")}, 104 String("foo${bar}"), 105 }, 106 tests: single("foo|baz", ""), 107 }, { 108 desc: "affix with substitution", 109 m: &Affix{seq{ 110 &Var{"bar", String("baz")}, 111 String("foo${bar}"), 112 }, "\t", "\n"}, 113 tests: single("\t|foo|baz|\n", ""), 114 }, { 115 desc: "shadowed variable", 116 m: seq{ 117 &Var{"bar", String("baz")}, 118 seq{ 119 &Var{"bar", String("BAZ")}, 120 String("foo${bar}"), 121 }, 122 }, 123 tests: single("foo|BAZ", ""), 124 }, { 125 desc: "nested value", 126 m: nestedLang{nestedLang{empty{}}}, 127 tests: single("nl|nl", ""), 128 }, { 129 desc: "not shadowed variable", 130 m: seq{ 131 &Var{"bar", String("baz")}, 132 seq{ 133 String("foo${bar}"), 134 &Var{"bar", String("BAZ")}, 135 }, 136 }, 137 encErr: errIsVar.Error(), 138 tests: single("foo|baz", ""), 139 }, { 140 desc: "duplicate variable", 141 m: seq{ 142 &Var{"bar", String("baz")}, 143 &Var{"bar", String("BAZ")}, 144 String("${bar}"), 145 }, 146 encErr: "catmsg: duplicate variable \"bar\"", 147 tests: single("baz", ""), 148 }, { 149 desc: "complete incomplete variable", 150 m: seq{ 151 &Var{"bar", incomplete{}}, 152 String("${bar}"), 153 }, 154 enc: "\x00\t\b\x01\x01\x14\x04\x02bar\x03\x00\x00\x00", 155 // TODO: recognize that it is cheaper to substitute bar. 156 tests: single("bar", ""), 157 }, { 158 desc: "incomplete sequence", 159 m: seq{ 160 incomplete{}, 161 incomplete{}, 162 }, 163 encErr: ErrIncomplete.Error(), 164 tests: single("", ErrNoMatch.Error()), 165 }, { 166 desc: "compile error variable", 167 m: seq{ 168 &Var{"bar", errorCompileMsg{}}, 169 String("${bar}"), 170 }, 171 encErr: errCompileTest.Error(), 172 tests: single("bar", ""), 173 }, { 174 desc: "compile error message", 175 m: errorCompileMsg{}, 176 encErr: errCompileTest.Error(), 177 tests: single("", ""), 178 }, { 179 desc: "compile error sequence", 180 m: seq{ 181 errorCompileMsg{}, 182 errorCompileMsg{}, 183 }, 184 encErr: errCompileTest.Error(), 185 tests: single("", ""), 186 }, { 187 desc: "macro", 188 m: String("${exists(1)}"), 189 tests: single("you betya!", ""), 190 }, { 191 desc: "macro incomplete", 192 m: String("${incomplete(1)}"), 193 enc: "\x03\x00\x01\nincomplete\x01", 194 tests: single("incomplete", ""), 195 }, { 196 desc: "macro undefined at end", 197 m: String("${undefined(1)}"), 198 enc: "\x03\x00\x01\tundefined\x01", 199 tests: single("undefined", "catmsg: undefined macro \"undefined\""), 200 }, { 201 desc: "macro undefined with more text following", 202 m: String("${undefined(1)}."), 203 enc: "\x03\x00\x01\tundefined\x01\x01.", 204 tests: single("undefined|.", "catmsg: undefined macro \"undefined\""), 205 }, { 206 desc: "macro missing paren", 207 m: String("${missing(1}"), 208 encErr: "catmsg: missing ')'", 209 tests: single("$!(MISSINGPAREN)", ""), 210 }, { 211 desc: "macro bad num", 212 m: String("aa${bad(a)}"), 213 encErr: "catmsg: invalid number \"a\"", 214 tests: single("aa$!(BADNUM)", ""), 215 }, { 216 desc: "var missing brace", 217 m: String("a${missing"), 218 encErr: "catmsg: missing '}'", 219 tests: single("a$!(MISSINGBRACE)", ""), 220 }} 221 r := &renderer{} 222 dec := NewDecoder(language.Und, r, macros) 223 for _, tc := range testCases { 224 t.Run(tc.desc, func(t *testing.T) { 225 // Use a language other than Und so that we can test 226 // passing the language to nested values. 227 data, err := Compile(language.Dutch, macros, tc.m) 228 if failErr(err, tc.encErr) { 229 t.Errorf("encoding error: got %+q; want %+q", err, tc.encErr) 230 } 231 if tc.enc != "" && data != tc.enc { 232 t.Errorf("encoding: got %+q; want %+q", data, tc.enc) 233 } 234 for _, st := range tc.tests { 235 t.Run("", func(t *testing.T) { 236 *r = renderer{args: st.args} 237 if err = dec.Execute(data); failErr(err, st.decErr) { 238 t.Errorf("decoding error: got %+q; want %+q", err, st.decErr) 239 } 240 if r.result != st.out { 241 t.Errorf("decode: got %+q; want %+q", r.result, st.out) 242 } 243 }) 244 } 245 }) 246 } 247 } 248 249 func failErr(got error, want string) bool { 250 if got == nil { 251 return want != "" 252 } 253 return want == "" || !strings.Contains(got.Error(), want) 254 } 255 256 type seq []Message 257 258 func (s seq) Compile(e *Encoder) (err error) { 259 err = ErrIncomplete 260 e.EncodeMessageType(msgFirst) 261 for _, m := range s { 262 // Pass only the last error, but allow erroneous or complete messages 263 // here to allow testing different scenarios. 264 err = e.EncodeMessage(m) 265 } 266 return err 267 } 268 269 type empty struct{} 270 271 func (empty) Compile(e *Encoder) (err error) { return nil } 272 273 var msgIncomplete = Register( 274 "golang.org/x/text/internal/catmsg.incomplete", 275 func(d *Decoder) bool { return false }) 276 277 type incomplete struct{} 278 279 func (incomplete) Compile(e *Encoder) (err error) { 280 e.EncodeMessageType(msgIncomplete) 281 return ErrIncomplete 282 } 283 284 var msgNested = Register( 285 "golang.org/x/text/internal/catmsg.nested", 286 func(d *Decoder) bool { 287 d.Render(d.DecodeString()) 288 d.ExecuteMessage() 289 return true 290 }) 291 292 type nestedLang struct{ Message } 293 294 func (n nestedLang) Compile(e *Encoder) (err error) { 295 e.EncodeMessageType(msgNested) 296 e.EncodeString(e.Language().String()) 297 e.EncodeMessage(n.Message) 298 return nil 299 } 300 301 type errorCompileMsg struct{} 302 303 var errCompileTest = errors.New("catmsg: compile error test") 304 305 func (errorCompileMsg) Compile(e *Encoder) (err error) { 306 return errCompileTest 307 } 308 309 type dictionary struct{} 310 311 var ( 312 macros = dictionary{} 313 dictMessages = map[string]string{ 314 "exists": compile(String("you betya!")), 315 "incomplete": compile(incomplete{}), 316 } 317 ) 318 319 func (d dictionary) Lookup(key string) (data string, ok bool) { 320 data, ok = dictMessages[key] 321 return 322 } 323 324 func compile(m Message) (data string) { 325 data, _ = Compile(language.Und, macros, m) 326 return data 327 }