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