File: error_test.go

package info (click to toggle)
golang-github-cue-lang-cue 0.14.2-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 19,644 kB
  • sloc: makefile: 20; sh: 15
file content (163 lines) | stat: -rw-r--r-- 5,141 bytes parent folder | download
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
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
// Copyright 2018 The CUE Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

// This file implements a parser test harness. The files in the testdata
// directory are parsed and the errors reported are compared against the
// error messages expected in the test files. The test files must end in
// .src rather than .go so that they are not disturbed by gofmt runs.
//
// Expected errors are indicated in the test files by putting a comment
// of the form /* ERROR "rx" */ immediately following an offending
// The harness will verify that an error matching the regular expression
// rx is reported at that source position.
//
// For instance, the following test file indicates that a "not declared"
// error should be reported for the undeclared variable x:
//
//	package p
//	{
//		a = x /* ERROR "not declared" */ + 1
//	}

package parser

import (
	"regexp"
	"testing"

	"cuelang.org/go/cue/errors"
	"cuelang.org/go/cue/scanner"
	"cuelang.org/go/cue/token"
	"cuelang.org/go/internal/source"
)

func getPos(f *token.File, offset int) token.Pos {
	if f != nil {
		return f.Pos(offset, 0)
	}
	return token.NoPos
}

// ERROR comments must be of the form /* ERROR "rx" */ and rx is
// a regular expression that matches the expected error message.
// The special form /* ERROR HERE "rx" */ must be used for error
// messages that appear immediately after a token, rather than at
// a token's position.
var errRx = regexp.MustCompile(`^/\* *ERROR *(HERE)? *"([^"]*)" *\*/$`)

// expectedErrors collects the regular expressions of ERROR comments found
// in files and returns them as a map of error positions to error messages.
func expectedErrors(t *testing.T, file *token.File, src []byte) map[token.Pos]string {
	errors := make(map[token.Pos]string)

	var s scanner.Scanner
	// file was parsed already - do not add it again to the file
	// set otherwise the position information returned here will
	// not match the position information collected by the parser
	// file := token.NewFile(filename, -1, len(src))
	s.Init(file, src, nil, scanner.ScanComments)
	var prev token.Pos // position of last non-comment, non-semicolon token
	var here token.Pos // position immediately after the token at position prev

	for {
		pos, tok, lit := s.Scan()
		pos = pos.WithRel(0)
		switch tok {
		case token.EOF:
			return errors
		case token.COMMENT:
			s := errRx.FindStringSubmatch(lit)
			if len(s) == 3 {
				pos := prev
				if s[1] == "HERE" {
					pos = here
				}
				errors[pos] = s[2]
			}
		default:
			prev = pos
			var l int // token length
			if tok.IsLiteral() {
				l = len(lit)
			} else {
				l = len(tok.String())
			}
			here = prev.Add(l)
		}
	}
}

// compareErrors compares the map of expected error messages with the list
// of found errors and reports discrepancies.
func compareErrors(t *testing.T, file *token.File, expected map[token.Pos]string, found []errors.Error) {
	t.Helper()
	for _, error := range found {
		// error.Pos is a Position, but we want
		// a Pos so we can do a map lookup
		ePos := error.Position()
		eMsg := error.Error()
		pos := getPos(file, ePos.Offset()).WithRel(0)
		if msg, found := expected[pos]; found {
			// we expect a message at pos; check if it matches
			rx, err := regexp.Compile(msg)
			if err != nil {
				t.Errorf("%s: %v", ePos, err)
				continue
			}
			if match := rx.MatchString(eMsg); !match {
				t.Errorf("%s: %q does not match %q", ePos, eMsg, msg)
				continue
			}
			// we have a match - eliminate this error
			delete(expected, pos)
		} else {
			// To keep in mind when analyzing failed test output:
			// If the same error position occurs multiple times in errors,
			// this message will be triggered (because the first error at
			// the position removes this position from the expected errors).
			t.Errorf("%s: unexpected error: -%q-", ePos, eMsg)
		}
	}

	// there should be no expected errors left
	if len(expected) > 0 {
		t.Errorf("%d errors not reported:", len(expected))
		for pos, msg := range expected {
			t.Errorf("%s: -%q-\n", pos, msg)
		}
	}
}

func checkErrors(t *testing.T, filename string, input interface{}) {
	t.Helper()
	src, err := source.ReadAll(filename, input)
	if err != nil {
		t.Error(err)
		return
	}

	f, err := ParseFile(filename, src, DeclarationErrors, AllErrors)
	file := f.Pos().File()
	found := errors.Errors(err)

	// we are expecting the following errors
	// (collect these after parsing a file so that it is found in the file set)
	if file == nil {
		t.Fatal("no token.File for ast.File")
	}
	expected := expectedErrors(t, file, src)

	// verify errors returned by the parser
	compareErrors(t, file, expected, found)
}