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