File: error.go

package info (click to toggle)
golang-github-maxatome-go-testdeep 1.14.0-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 2,416 kB
  • sloc: perl: 1,012; yacc: 130; makefile: 2
file content (238 lines) | stat: -rw-r--r-- 6,057 bytes parent folder | download | duplicates (2)
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
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
// Copyright (c) 2018-2021, Maxime Soulé
// All rights reserved.
//
// This source code is licensed under the BSD-style license found in the
// LICENSE file in the root directory of this source tree.

package ctxerr

import (
	"reflect"
	"strings"

	"github.com/maxatome/go-testdeep/internal/color"
	"github.com/maxatome/go-testdeep/internal/location"
	"github.com/maxatome/go-testdeep/internal/types"
	"github.com/maxatome/go-testdeep/internal/util"
)

// Error represents errors generated by td (go-testdeep) functions.
type Error struct {
	// Context when the error occurred
	Context Context
	// Message describes the error
	Message string
	// Got value
	Got any
	// Expected value
	Expected any
	// If not nil, Summary is used to display summary instead of using
	// Got + Expected fields
	Summary ErrorSummary
	// If initialized, location of TestDeep operator originator of the error
	Location location.Location
	// If defined, the current Error comes from this Error
	Origin *Error
	// If defined, points to the next Error
	Next *Error
}

// BooleanError is the [*Error] returned when an error occurs in a
// boolean context.
var BooleanError = &Error{}

// ErrTooManyErrors is chained to the last error encountered when
// the maximum number of errors has been reached.
var ErrTooManyErrors = &Error{
	Message: "Too many errors (use TESTDEEP_MAX_ERRORS=-1 to see all)",
}

// TypeMismatch returns a "type mismatch" error. It is the caller
// responsibility to check that both types differ.
//
// If they resolve to the same name (via their String method), it
// tries to deeply dump the full package name of each type.
//
// It works pretty well with the exception of identical anomymous
// structs in 2 different packages with the same last name: in this
// case reflect does not allow us to retrieve the package from which
// each type comes.
//
//	package foo // in a/
//	var Foo struct { a int }
//
//	package foo // in b/
//	var Foo struct { a int }
//
//	package ctxerr
//	import(
//	  a_foo "a/foo"
//	  b_foo "b/foo"
//	)
//	…
//	TypeMismatch(reflect.TypeOf(a_foo.Foo), reflect.TypeOf(b_foo.Foo))
//
// returns an error producing:
//
//	type mismatch
//	     got: struct { a int }
//	expected: struct { a int }
func TypeMismatch(got, expected reflect.Type) *Error {
	gs, es := got.String(), expected.String()
	if gs == es {
		gs, es = util.TypeFullName(got), util.TypeFullName(expected)
	}
	return &Error{
		Message:  "type mismatch",
		Got:      types.RawString(gs),
		Expected: types.RawString(es),
	}
}

// Error implements error interface.
func (e *Error) Error() string {
	buf := strings.Builder{}

	e.Append(&buf, "", true)

	return buf.String()
}

// ErrorWithoutColors is the same as [Error.Error] but guarantees the
// resulting string does not contain any ANSI color escape sequences.
func (e *Error) ErrorWithoutColors() string {
	buf := strings.Builder{}

	e.Append(&buf, "", false)

	return buf.String()
}

// Append appends the a contents to buf using prefix prefix for each
// line.
func (e *Error) Append(buf *strings.Builder, prefix string, colorized bool) {
	if e == BooleanError {
		return
	}

	var badOn, badOff, okOn, okOff string
	if colorized {
		color.Init()
		badOn, badOff = color.BadOn, color.BadOff
		okOn, okOff = color.OKOn, color.OKOff
	}

	var writeEolPrefix func()
	if prefix != "" {
		eolPrefix := make([]byte, 1+len(prefix))
		eolPrefix[0] = '\n'
		copy(eolPrefix[1:], prefix)

		writeEolPrefix = func() {
			buf.Write(eolPrefix)
		}
		buf.WriteString(prefix)
	} else {
		writeEolPrefix = func() {
			buf.WriteByte('\n')
		}
	}

	if e == ErrTooManyErrors {
		if colorized {
			buf.WriteString(color.TitleOn)
		}
		buf.WriteString(e.Message)
		if colorized {
			buf.WriteString(color.TitleOff)
		}
		return
	}

	if colorized {
		buf.WriteString(color.TitleOn)
	}
	if pos := strings.Index(e.Message, "%%"); pos >= 0 {
		buf.WriteString(e.Message[:pos])
		buf.WriteString(e.Context.Path.String())
		buf.WriteString(e.Message[pos+2:])
	} else {
		buf.WriteString(e.Context.Path.String())
		buf.WriteString(": ")
		buf.WriteString(e.Message)
	}
	if colorized {
		buf.WriteString(color.TitleOff)
	}

	if e.Summary != nil {
		buf.WriteByte('\n')
		e.Summary.AppendSummary(buf, prefix+"\t", colorized)
	} else {
		writeEolPrefix()
		if colorized {
			buf.WriteString(color.BadOnBold)
		}
		buf.WriteString("\t     got: ")
		util.IndentColorizeStringIn(buf, e.GotString(), prefix+"\t          ", badOn, badOff)
		writeEolPrefix()
		if colorized {
			buf.WriteString(color.OKOnBold)
		}
		buf.WriteString("\texpected: ")
		util.IndentColorizeStringIn(buf, e.ExpectedString(), prefix+"\t          ", okOn, okOff)
	}

	// This error comes from another one
	if e.Origin != nil {
		writeEolPrefix()
		buf.WriteString("Originates from following error:\n")

		e.Origin.Append(buf, prefix+"\t", colorized)
	}

	if e.Location.IsInitialized() &&
		!e.Location.BehindCmp && // no need to log Cmp* func
		(e.Next == nil || e.Next.Location != e.Location) {
		writeEolPrefix()
		buf.WriteString("[under operator ")
		buf.WriteString(e.Location.String())
		buf.WriteByte(']')
	}

	if e.Next != nil {
		buf.WriteByte('\n')
		e.Next.Append(buf, prefix, colorized) // next error at same level
	}
}

// GotString returns the string corresponding to the Got
// field. Returns the empty string if the e Summary field is not nil.
func (e *Error) GotString() string {
	if e.Summary != nil {
		return ""
	}
	return util.ToString(e.Got)
}

// ExpectedString returns the string corresponding to the Expected
// field. Returns the empty string if the e Summary field is not nil.
func (e *Error) ExpectedString() string {
	if e.Summary != nil {
		return ""
	}
	return util.ToString(e.Expected)
}

// SummaryString returns the string corresponding to the Summary field
// without any ANSI color escape sequences. Returns the empty string
// if the e Summary field is nil.
func (e *Error) SummaryString() string {
	if e.Summary == nil {
		return ""
	}

	var buf strings.Builder
	e.Summary.AppendSummary(&buf, "", false)
	return buf.String()
}