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