Tool to help you manage your Grafana dashboards using Git.

scanner_test.go 11KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417
  1. // Copyright 2009 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 scanner
  5. import (
  6. "os"
  7. "strings"
  8. "testing"
  9. )
  10. import (
  11. "github.com/src-d/gcfg/token"
  12. )
  13. var fset = token.NewFileSet()
  14. const /* class */ (
  15. special = iota
  16. literal
  17. operator
  18. )
  19. func tokenclass(tok token.Token) int {
  20. switch {
  21. case tok.IsLiteral():
  22. return literal
  23. case tok.IsOperator():
  24. return operator
  25. }
  26. return special
  27. }
  28. type elt struct {
  29. tok token.Token
  30. lit string
  31. class int
  32. pre string
  33. suf string
  34. }
  35. var tokens = [...]elt{
  36. // Special tokens
  37. {token.COMMENT, "; a comment", special, "", "\n"},
  38. {token.COMMENT, "# a comment", special, "", "\n"},
  39. // Operators and delimiters
  40. {token.ASSIGN, "=", operator, "", "value"},
  41. {token.LBRACK, "[", operator, "", ""},
  42. {token.RBRACK, "]", operator, "", ""},
  43. {token.EOL, "\n", operator, "", ""},
  44. // Identifiers
  45. {token.IDENT, "foobar", literal, "", ""},
  46. {token.IDENT, "a۰۱۸", literal, "", ""},
  47. {token.IDENT, "foo६४", literal, "", ""},
  48. {token.IDENT, "bar9876", literal, "", ""},
  49. {token.IDENT, "foo-bar", literal, "", ""},
  50. {token.IDENT, "foo", literal, ";\n", ""},
  51. // String literals (subsection names)
  52. {token.STRING, `"foobar"`, literal, "", ""},
  53. {token.STRING, `"\""`, literal, "", ""},
  54. // String literals (values)
  55. {token.STRING, `"\n"`, literal, "=", ""},
  56. {token.STRING, `"foobar"`, literal, "=", ""},
  57. {token.STRING, `"foo\nbar"`, literal, "=", ""},
  58. {token.STRING, `"foo\"bar"`, literal, "=", ""},
  59. {token.STRING, `"foo\\bar"`, literal, "=", ""},
  60. {token.STRING, `"foobar"`, literal, "=", ""},
  61. {token.STRING, `"foobar"`, literal, "= ", ""},
  62. {token.STRING, `"foobar"`, literal, "=", "\n"},
  63. {token.STRING, `"foobar"`, literal, "=", ";"},
  64. {token.STRING, `"foobar"`, literal, "=", " ;"},
  65. {token.STRING, `"foobar"`, literal, "=", "#"},
  66. {token.STRING, `"foobar"`, literal, "=", " #"},
  67. {token.STRING, "foobar", literal, "=", ""},
  68. {token.STRING, "foobar", literal, "= ", ""},
  69. {token.STRING, "foobar", literal, "=", " "},
  70. {token.STRING, `"foo" "bar"`, literal, "=", " "},
  71. {token.STRING, "foo\\\nbar", literal, "=", ""},
  72. {token.STRING, "foo\\\r\nbar", literal, "=", ""},
  73. }
  74. const whitespace = " \t \n\n\n" // to separate tokens
  75. var source = func() []byte {
  76. var src []byte
  77. for _, t := range tokens {
  78. src = append(src, t.pre...)
  79. src = append(src, t.lit...)
  80. src = append(src, t.suf...)
  81. src = append(src, whitespace...)
  82. }
  83. return src
  84. }()
  85. func newlineCount(s string) int {
  86. n := 0
  87. for i := 0; i < len(s); i++ {
  88. if s[i] == '\n' {
  89. n++
  90. }
  91. }
  92. return n
  93. }
  94. func checkPos(t *testing.T, lit string, p token.Pos, expected token.Position) {
  95. pos := fset.Position(p)
  96. if pos.Filename != expected.Filename {
  97. t.Errorf("bad filename for %q: got %s, expected %s", lit, pos.Filename, expected.Filename)
  98. }
  99. if pos.Offset != expected.Offset {
  100. t.Errorf("bad position for %q: got %d, expected %d", lit, pos.Offset, expected.Offset)
  101. }
  102. if pos.Line != expected.Line {
  103. t.Errorf("bad line for %q: got %d, expected %d", lit, pos.Line, expected.Line)
  104. }
  105. if pos.Column != expected.Column {
  106. t.Errorf("bad column for %q: got %d, expected %d", lit, pos.Column, expected.Column)
  107. }
  108. }
  109. // Verify that calling Scan() provides the correct results.
  110. func TestScan(t *testing.T) {
  111. // make source
  112. src_linecount := newlineCount(string(source))
  113. whitespace_linecount := newlineCount(whitespace)
  114. index := 0
  115. // error handler
  116. eh := func(_ token.Position, msg string) {
  117. t.Errorf("%d: error handler called (msg = %s)", index, msg)
  118. }
  119. // verify scan
  120. var s Scanner
  121. s.Init(fset.AddFile("", fset.Base(), len(source)), source, eh, ScanComments)
  122. // epos is the expected position
  123. epos := token.Position{
  124. Filename: "",
  125. Offset: 0,
  126. Line: 1,
  127. Column: 1,
  128. }
  129. for {
  130. pos, tok, lit := s.Scan()
  131. if lit == "" {
  132. // no literal value for non-literal tokens
  133. lit = tok.String()
  134. }
  135. e := elt{token.EOF, "", special, "", ""}
  136. if index < len(tokens) {
  137. e = tokens[index]
  138. }
  139. if tok == token.EOF {
  140. lit = "<EOF>"
  141. epos.Line = src_linecount
  142. epos.Column = 2
  143. }
  144. if e.pre != "" && strings.ContainsRune("=;#", rune(e.pre[0])) {
  145. epos.Column = 1
  146. checkPos(t, lit, pos, epos)
  147. var etok token.Token
  148. if e.pre[0] == '=' {
  149. etok = token.ASSIGN
  150. } else {
  151. etok = token.COMMENT
  152. }
  153. if tok != etok {
  154. t.Errorf("bad token for %q: got %q, expected %q", lit, tok, etok)
  155. }
  156. pos, tok, lit = s.Scan()
  157. }
  158. epos.Offset += len(e.pre)
  159. if tok != token.EOF {
  160. epos.Column = 1 + len(e.pre)
  161. }
  162. if e.pre != "" && e.pre[len(e.pre)-1] == '\n' {
  163. epos.Offset--
  164. epos.Column--
  165. checkPos(t, lit, pos, epos)
  166. if tok != token.EOL {
  167. t.Errorf("bad token for %q: got %q, expected %q", lit, tok, token.EOL)
  168. }
  169. epos.Line++
  170. epos.Offset++
  171. epos.Column = 1
  172. pos, tok, lit = s.Scan()
  173. }
  174. checkPos(t, lit, pos, epos)
  175. if tok != e.tok {
  176. t.Errorf("bad token for %q: got %q, expected %q", lit, tok, e.tok)
  177. }
  178. if e.tok.IsLiteral() {
  179. // no CRs in value string literals
  180. elit := e.lit
  181. if strings.ContainsRune(e.pre, '=') {
  182. elit = string(stripCR([]byte(elit)))
  183. epos.Offset += len(e.lit) - len(lit) // correct position
  184. }
  185. if lit != elit {
  186. t.Errorf("bad literal for %q: got %q, expected %q", lit, lit, elit)
  187. }
  188. }
  189. if tokenclass(tok) != e.class {
  190. t.Errorf("bad class for %q: got %d, expected %d", lit, tokenclass(tok), e.class)
  191. }
  192. epos.Offset += len(lit) + len(e.suf) + len(whitespace)
  193. epos.Line += newlineCount(lit) + newlineCount(e.suf) + whitespace_linecount
  194. index++
  195. if tok == token.EOF {
  196. break
  197. }
  198. if e.suf == "value" {
  199. pos, tok, lit = s.Scan()
  200. if tok != token.STRING {
  201. t.Errorf("bad token for %q: got %q, expected %q", lit, tok, token.STRING)
  202. }
  203. } else if strings.ContainsRune(e.suf, ';') || strings.ContainsRune(e.suf, '#') {
  204. pos, tok, lit = s.Scan()
  205. if tok != token.COMMENT {
  206. t.Errorf("bad token for %q: got %q, expected %q", lit, tok, token.COMMENT)
  207. }
  208. }
  209. // skip EOLs
  210. for i := 0; i < whitespace_linecount+newlineCount(e.suf); i++ {
  211. pos, tok, lit = s.Scan()
  212. if tok != token.EOL {
  213. t.Errorf("bad token for %q: got %q, expected %q", lit, tok, token.EOL)
  214. }
  215. }
  216. }
  217. if s.ErrorCount != 0 {
  218. t.Errorf("found %d errors", s.ErrorCount)
  219. }
  220. }
  221. func TestScanValStringEOF(t *testing.T) {
  222. var s Scanner
  223. src := "= value"
  224. f := fset.AddFile("src", fset.Base(), len(src))
  225. s.Init(f, []byte(src), nil, 0)
  226. s.Scan() // =
  227. s.Scan() // value
  228. _, tok, _ := s.Scan() // EOF
  229. if tok != token.EOF {
  230. t.Errorf("bad token: got %s, expected %s", tok, token.EOF)
  231. }
  232. if s.ErrorCount > 0 {
  233. t.Error("scanning error")
  234. }
  235. }
  236. // Verify that initializing the same scanner more then once works correctly.
  237. func TestInit(t *testing.T) {
  238. var s Scanner
  239. // 1st init
  240. src1 := "\nname = value"
  241. f1 := fset.AddFile("src1", fset.Base(), len(src1))
  242. s.Init(f1, []byte(src1), nil, 0)
  243. if f1.Size() != len(src1) {
  244. t.Errorf("bad file size: got %d, expected %d", f1.Size(), len(src1))
  245. }
  246. s.Scan() // \n
  247. s.Scan() // name
  248. _, tok, _ := s.Scan() // =
  249. if tok != token.ASSIGN {
  250. t.Errorf("bad token: got %s, expected %s", tok, token.ASSIGN)
  251. }
  252. // 2nd init
  253. src2 := "[section]"
  254. f2 := fset.AddFile("src2", fset.Base(), len(src2))
  255. s.Init(f2, []byte(src2), nil, 0)
  256. if f2.Size() != len(src2) {
  257. t.Errorf("bad file size: got %d, expected %d", f2.Size(), len(src2))
  258. }
  259. _, tok, _ = s.Scan() // [
  260. if tok != token.LBRACK {
  261. t.Errorf("bad token: got %s, expected %s", tok, token.LBRACK)
  262. }
  263. if s.ErrorCount != 0 {
  264. t.Errorf("found %d errors", s.ErrorCount)
  265. }
  266. }
  267. func TestStdErrorHandler(t *testing.T) {
  268. const src = "@\n" + // illegal character, cause an error
  269. "@ @\n" // two errors on the same line
  270. var list ErrorList
  271. eh := func(pos token.Position, msg string) { list.Add(pos, msg) }
  272. var s Scanner
  273. s.Init(fset.AddFile("File1", fset.Base(), len(src)), []byte(src), eh, 0)
  274. for {
  275. if _, tok, _ := s.Scan(); tok == token.EOF {
  276. break
  277. }
  278. }
  279. if len(list) != s.ErrorCount {
  280. t.Errorf("found %d errors, expected %d", len(list), s.ErrorCount)
  281. }
  282. if len(list) != 3 {
  283. t.Errorf("found %d raw errors, expected 3", len(list))
  284. PrintError(os.Stderr, list)
  285. }
  286. list.Sort()
  287. if len(list) != 3 {
  288. t.Errorf("found %d sorted errors, expected 3", len(list))
  289. PrintError(os.Stderr, list)
  290. }
  291. list.RemoveMultiples()
  292. if len(list) != 2 {
  293. t.Errorf("found %d one-per-line errors, expected 2", len(list))
  294. PrintError(os.Stderr, list)
  295. }
  296. }
  297. type errorCollector struct {
  298. cnt int // number of errors encountered
  299. msg string // last error message encountered
  300. pos token.Position // last error position encountered
  301. }
  302. func checkError(t *testing.T, src string, tok token.Token, pos int, err string) {
  303. var s Scanner
  304. var h errorCollector
  305. eh := func(pos token.Position, msg string) {
  306. h.cnt++
  307. h.msg = msg
  308. h.pos = pos
  309. }
  310. s.Init(fset.AddFile("", fset.Base(), len(src)), []byte(src), eh, ScanComments)
  311. if src[0] == '=' {
  312. _, _, _ = s.Scan()
  313. }
  314. _, tok0, _ := s.Scan()
  315. _, tok1, _ := s.Scan()
  316. if tok0 != tok {
  317. t.Errorf("%q: got %s, expected %s", src, tok0, tok)
  318. }
  319. if tok1 != token.EOF {
  320. t.Errorf("%q: got %s, expected EOF", src, tok1)
  321. }
  322. cnt := 0
  323. if err != "" {
  324. cnt = 1
  325. }
  326. if h.cnt != cnt {
  327. t.Errorf("%q: got cnt %d, expected %d", src, h.cnt, cnt)
  328. }
  329. if h.msg != err {
  330. t.Errorf("%q: got msg %q, expected %q", src, h.msg, err)
  331. }
  332. if h.pos.Offset != pos {
  333. t.Errorf("%q: got offset %d, expected %d", src, h.pos.Offset, pos)
  334. }
  335. }
  336. var errors = []struct {
  337. src string
  338. tok token.Token
  339. pos int
  340. err string
  341. }{
  342. {"\a", token.ILLEGAL, 0, "illegal character U+0007"},
  343. {"/", token.ILLEGAL, 0, "illegal character U+002F '/'"},
  344. {"_", token.ILLEGAL, 0, "illegal character U+005F '_'"},
  345. {`…`, token.ILLEGAL, 0, "illegal character U+2026 '…'"},
  346. {`""`, token.STRING, 0, ""},
  347. {`"`, token.STRING, 0, "string not terminated"},
  348. {"\"\n", token.STRING, 0, "string not terminated"},
  349. {`="`, token.STRING, 1, "string not terminated"},
  350. {"=\"\n", token.STRING, 1, "string not terminated"},
  351. {"=\\", token.STRING, 1, "unquoted '\\' must be followed by new line"},
  352. {"=\\\r", token.STRING, 1, "unquoted '\\' must be followed by new line"},
  353. {`"\z"`, token.STRING, 2, "unknown escape sequence"},
  354. {`"\a"`, token.STRING, 2, "unknown escape sequence"},
  355. {`"\b"`, token.STRING, 2, "unknown escape sequence"},
  356. {`"\f"`, token.STRING, 2, "unknown escape sequence"},
  357. {`"\r"`, token.STRING, 2, "unknown escape sequence"},
  358. {`"\t"`, token.STRING, 2, "unknown escape sequence"},
  359. {`"\v"`, token.STRING, 2, "unknown escape sequence"},
  360. {`"\0"`, token.STRING, 2, "unknown escape sequence"},
  361. }
  362. func TestScanErrors(t *testing.T) {
  363. for _, e := range errors {
  364. checkError(t, e.src, e.tok, e.pos, e.err)
  365. }
  366. }
  367. func BenchmarkScan(b *testing.B) {
  368. b.StopTimer()
  369. fset := token.NewFileSet()
  370. file := fset.AddFile("", fset.Base(), len(source))
  371. var s Scanner
  372. b.StartTimer()
  373. for i := b.N - 1; i >= 0; i-- {
  374. s.Init(file, source, nil, ScanComments)
  375. for {
  376. _, tok, _ := s.Scan()
  377. if tok == token.EOF {
  378. break
  379. }
  380. }
  381. }
  382. }