You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

328 lines
7.4 KiB

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