xref: /aosp_15_r20/external/starlark-go/internal/chunkedfile/chunkedfile.go (revision 4947cdc739c985f6d86941e22894f5cefe7c9e9a)
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