/internal/chunkedfile/chunkedfile.go

https://github.com/google/starlark-go · Go · 124 lines · 74 code · 11 blank · 39 comment · 15 complexity · 05fe069e6bc8dcc270f3064772a1da8e MD5 · raw file

  1. // Copyright 2017 The Bazel 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 chunkedfile provides utilities for testing that source code
  5. // errors are reported in the appropriate places.
  6. //
  7. // A chunked file consists of several chunks of input text separated by
  8. // "---" lines. Each chunk is an input to the program under test, such
  9. // as an evaluator. Lines containing "###" are interpreted as
  10. // expectations of failure: the following text is a Go string literal
  11. // denoting a regular expression that should match the failure message.
  12. //
  13. // Example:
  14. //
  15. // x = 1 / 0 ### "division by zero"
  16. // ---
  17. // x = 1
  18. // print(x + "") ### "int + string not supported"
  19. //
  20. // A client test feeds each chunk of text into the program under test,
  21. // then calls chunk.GotError for each error that actually occurred. Any
  22. // discrepancy between the actual and expected errors is reported using
  23. // the client's reporter, which is typically a testing.T.
  24. package chunkedfile // import "go.starlark.net/internal/chunkedfile"
  25. import (
  26. "fmt"
  27. "io/ioutil"
  28. "regexp"
  29. "strconv"
  30. "strings"
  31. )
  32. const debug = false
  33. // A Chunk is a portion of a source file.
  34. // It contains a set of expected errors.
  35. type Chunk struct {
  36. Source string
  37. filename string
  38. report Reporter
  39. wantErrs map[int]*regexp.Regexp
  40. }
  41. // Reporter is implemented by *testing.T.
  42. type Reporter interface {
  43. Errorf(format string, args ...interface{})
  44. }
  45. // Read parses a chunked file and returns its chunks.
  46. // It reports failures using the reporter.
  47. //
  48. // Error messages of the form "file.star:line:col: ..." are prefixed
  49. // by a newline so that the Go source position added by (*testing.T).Errorf
  50. // appears on a separate line so as not to confused editors.
  51. func Read(filename string, report Reporter) (chunks []Chunk) {
  52. data, err := ioutil.ReadFile(filename)
  53. if err != nil {
  54. report.Errorf("%s", err)
  55. return
  56. }
  57. linenum := 1
  58. for i, chunk := range strings.Split(string(data), "\n---\n") {
  59. if debug {
  60. fmt.Printf("chunk %d at line %d: %s\n", i, linenum, chunk)
  61. }
  62. // Pad with newlines so the line numbers match the original file.
  63. src := strings.Repeat("\n", linenum-1) + chunk
  64. wantErrs := make(map[int]*regexp.Regexp)
  65. // Parse comments of the form:
  66. // ### "expected error".
  67. lines := strings.Split(chunk, "\n")
  68. for j := 0; j < len(lines); j, linenum = j+1, linenum+1 {
  69. line := lines[j]
  70. hashes := strings.Index(line, "###")
  71. if hashes < 0 {
  72. continue
  73. }
  74. rest := strings.TrimSpace(line[hashes+len("###"):])
  75. pattern, err := strconv.Unquote(rest)
  76. if err != nil {
  77. report.Errorf("\n%s:%d: not a quoted regexp: %s", filename, linenum, rest)
  78. continue
  79. }
  80. rx, err := regexp.Compile(pattern)
  81. if err != nil {
  82. report.Errorf("\n%s:%d: %v", filename, linenum, err)
  83. continue
  84. }
  85. wantErrs[linenum] = rx
  86. if debug {
  87. fmt.Printf("\t%d\t%s\n", linenum, rx)
  88. }
  89. }
  90. linenum++
  91. chunks = append(chunks, Chunk{src, filename, report, wantErrs})
  92. }
  93. return chunks
  94. }
  95. // GotError should be called by the client to report an error at a particular line.
  96. // GotError reports unexpected errors to the chunk's reporter.
  97. func (chunk *Chunk) GotError(linenum int, msg string) {
  98. if rx, ok := chunk.wantErrs[linenum]; ok {
  99. delete(chunk.wantErrs, linenum)
  100. if !rx.MatchString(msg) {
  101. chunk.report.Errorf("\n%s:%d: error %q does not match pattern %q", chunk.filename, linenum, msg, rx)
  102. }
  103. } else {
  104. chunk.report.Errorf("\n%s:%d: unexpected error: %v", chunk.filename, linenum, msg)
  105. }
  106. }
  107. // Done should be called by the client to indicate that the chunk has no more errors.
  108. // Done reports expected errors that did not occur to the chunk's reporter.
  109. func (chunk *Chunk) Done() {
  110. for linenum, rx := range chunk.wantErrs {
  111. chunk.report.Errorf("\n%s:%d: expected error matching %q", chunk.filename, linenum, rx)
  112. }
  113. }