diff --git a/_codegen/main.go b/_codegen/main.go index 574985181..d63608e24 100644 --- a/_codegen/main.go +++ b/_codegen/main.go @@ -281,15 +281,42 @@ func (f *testFunc) ForwardedParamsFormat() string { } func (f *testFunc) Comment() string { - return "// " + strings.Replace(strings.TrimSpace(f.DocInfo.Doc), "\n", "\n// ", -1) + // Preserve original indentation, but ensure lines that start with a tab are + // prefixed with "//\t" (no extra space) to match canonical formatting of code examples. + raw := strings.TrimSpace(f.DocInfo.Doc) + lines := strings.Split(raw, "\n") + for i, line := range lines { + if strings.HasPrefix(line, "\t") { + // Example/code line: keep the leading tab directly after // + lines[i] = "//\t" + strings.TrimPrefix(line, "\t") + } else { + lines[i] = "// " + line + } + } + return strings.Join(lines, "\n") } func (f *testFunc) CommentFormat() string { - search := fmt.Sprintf("%s", f.DocInfo.Name) - replace := fmt.Sprintf("%sf", f.DocInfo.Name) - comment := strings.Replace(f.Comment(), search, replace, -1) - exp := regexp.MustCompile(replace + `\(((\(\)|[^\n])+)\)`) - return exp.ReplaceAllString(comment, replace+`($1, "error message %s", "formatted")`) + // Start from the original comment text + comment := f.Comment() + + // 1) Ensure the leading doc header uses the formatted name (e.g., "Equalf asserts ..."). + // Only change the very first line that starts with "// ". + headerPattern := regexp.MustCompile(`^//\s*` + regexp.QuoteMeta(f.DocInfo.Name) + `\b`) + comment = headerPattern.ReplaceAllString(comment, "// "+f.DocInfo.Name+"f") + + // 2) Replace only function call occurrences of the current function name with the formatted variant (suffix 'f'). + // This avoids accidentally changing type names such as "*regexp.Regexp" into "*regexp.Regexpf" by requiring a following '('. + fnCallPattern := regexp.MustCompile(`\b` + regexp.QuoteMeta(f.DocInfo.Name) + `\s*\(`) + comment = fnCallPattern.ReplaceAllString(comment, f.DocInfo.Name+"f(") + + // 3) Append formatted message args at the end of each Namef(...) call within a single line. + // Use a greedy match until the last closing paren on the same line to avoid inserting inside nested parentheses. + // Example: Namef(t, uint32(123), int32(123)) -> Namef(t, uint32(123), int32(123), "error message %s", "formatted") + addArgsLine := regexp.MustCompile(`(?m)` + regexp.QuoteMeta(f.DocInfo.Name+"f(") + `(.*)\)`) + comment = addArgsLine.ReplaceAllString(comment, f.DocInfo.Name+`f($1, "error message %s", "formatted")`) + + return comment } func (f *testFunc) CommentWithoutT(receiver string) string { diff --git a/assert/assertion_format.go b/assert/assertion_format.go index 2d089991a..844bb68ad 100644 --- a/assert/assertion_format.go +++ b/assert/assertion_format.go @@ -476,7 +476,7 @@ func JSONEqf(t TestingT, expected string, actual string, msg string, args ...int } // Lenf asserts that the specified object has specific length. -// Lenf also fails if the object has a type that len() not accept. +// Len also fails if the object has a type that len() not accept. // // assert.Lenf(t, mySlice, 3, "error message %s", "formatted") func Lenf(t TestingT, object interface{}, length int, msg string, args ...interface{}) bool { @@ -689,8 +689,14 @@ func NotPanicsf(t TestingT, f PanicTestFunc, msg string, args ...interface{}) bo // NotRegexpf asserts that a specified regexp does not match a string. // +// The rx (expression) argument should be a *regexp.Regexp. For backward +// compatibility, if rx is any other type, its value will be formatted with +// %v and compiled using regexp.Compile. +// +// Examples: +// // assert.NotRegexpf(t, regexp.MustCompile("starts"), "it's starting", "error message %s", "formatted") -// assert.NotRegexpf(t, "^start", "it's not starting", "error message %s", "formatted") +// assert.NotRegexpf(t, "^start", "it's not starting", "error message %s", "formatted") // string is compiled func NotRegexpf(t TestingT, rx interface{}, str interface{}, msg string, args ...interface{}) bool { if h, ok := t.(tHelper); ok { h.Helper() @@ -781,8 +787,14 @@ func Positivef(t TestingT, e interface{}, msg string, args ...interface{}) bool // Regexpf asserts that a specified regexp matches a string. // +// The rx (expression) argument should be a *regexp.Regexp. For backward +// compatibility, if rx is any other type, its value will be formatted with +// %v and compiled using regexp.Compile. +// +// Examples: +// // assert.Regexpf(t, regexp.MustCompile("start"), "it's starting", "error message %s", "formatted") -// assert.Regexpf(t, "start...$", "it's not starting", "error message %s", "formatted") +// assert.Regexpf(t, "start...$", "it's not starting", "error message %s", "formatted") // string is compiled func Regexpf(t TestingT, rx interface{}, str interface{}, msg string, args ...interface{}) bool { if h, ok := t.(tHelper); ok { h.Helper() diff --git a/assert/assertion_forward.go b/assert/assertion_forward.go index d8300af73..da75955b0 100644 --- a/assert/assertion_forward.go +++ b/assert/assertion_forward.go @@ -954,7 +954,7 @@ func (a *Assertions) Len(object interface{}, length int, msgAndArgs ...interface } // Lenf asserts that the specified object has specific length. -// Lenf also fails if the object has a type that len() not accept. +// Len also fails if the object has a type that len() not accept. // // a.Lenf(mySlice, 3, "error message %s", "formatted") func (a *Assertions) Lenf(object interface{}, length int, msg string, args ...interface{}) bool { @@ -1368,8 +1368,14 @@ func (a *Assertions) NotPanicsf(f PanicTestFunc, msg string, args ...interface{} // NotRegexp asserts that a specified regexp does not match a string. // +// The rx (expression) argument should be a *regexp.Regexp. For backward +// compatibility, if rx is any other type, its value will be formatted with +// %v and compiled using regexp.Compile. +// +// Examples: +// // a.NotRegexp(regexp.MustCompile("starts"), "it's starting") -// a.NotRegexp("^start", "it's not starting") +// a.NotRegexp("^start", "it's not starting") // string is compiled func (a *Assertions) NotRegexp(rx interface{}, str interface{}, msgAndArgs ...interface{}) bool { if h, ok := a.t.(tHelper); ok { h.Helper() @@ -1379,8 +1385,14 @@ func (a *Assertions) NotRegexp(rx interface{}, str interface{}, msgAndArgs ...in // NotRegexpf asserts that a specified regexp does not match a string. // +// The rx (expression) argument should be a *regexp.Regexp. For backward +// compatibility, if rx is any other type, its value will be formatted with +// %v and compiled using regexp.Compile. +// +// Examples: +// // a.NotRegexpf(regexp.MustCompile("starts"), "it's starting", "error message %s", "formatted") -// a.NotRegexpf("^start", "it's not starting", "error message %s", "formatted") +// a.NotRegexpf("^start", "it's not starting", "error message %s", "formatted") // string is compiled func (a *Assertions) NotRegexpf(rx interface{}, str interface{}, msg string, args ...interface{}) bool { if h, ok := a.t.(tHelper); ok { h.Helper() @@ -1552,8 +1564,14 @@ func (a *Assertions) Positivef(e interface{}, msg string, args ...interface{}) b // Regexp asserts that a specified regexp matches a string. // +// The rx (expression) argument should be a *regexp.Regexp. For backward +// compatibility, if rx is any other type, its value will be formatted with +// %v and compiled using regexp.Compile. +// +// Examples: +// // a.Regexp(regexp.MustCompile("start"), "it's starting") -// a.Regexp("start...$", "it's not starting") +// a.Regexp("start...$", "it's not starting") // string is compiled func (a *Assertions) Regexp(rx interface{}, str interface{}, msgAndArgs ...interface{}) bool { if h, ok := a.t.(tHelper); ok { h.Helper() @@ -1563,8 +1581,14 @@ func (a *Assertions) Regexp(rx interface{}, str interface{}, msgAndArgs ...inter // Regexpf asserts that a specified regexp matches a string. // +// The rx (expression) argument should be a *regexp.Regexp. For backward +// compatibility, if rx is any other type, its value will be formatted with +// %v and compiled using regexp.Compile. +// +// Examples: +// // a.Regexpf(regexp.MustCompile("start"), "it's starting", "error message %s", "formatted") -// a.Regexpf("start...$", "it's not starting", "error message %s", "formatted") +// a.Regexpf("start...$", "it's not starting", "error message %s", "formatted") // string is compiled func (a *Assertions) Regexpf(rx interface{}, str interface{}, msg string, args ...interface{}) bool { if h, ok := a.t.(tHelper); ok { h.Helper() diff --git a/assert/assertions.go b/assert/assertions.go index a27e70546..9e6c5f44e 100644 --- a/assert/assertions.go +++ b/assert/assertions.go @@ -1708,35 +1708,51 @@ func ErrorContains(t TestingT, theError error, contains string, msgAndArgs ...in return true } -// matchRegexp return true if a specified regexp matches a string. -func matchRegexp(rx interface{}, str interface{}) bool { +// matchRegexp returns whether the provided regular expression matches the input string. +// If the rx cannot be compiled into a valid regexp, an error is returned. +func matchRegexp(rx interface{}, str interface{}) (bool, error) { var r *regexp.Regexp if rr, ok := rx.(*regexp.Regexp); ok { r = rr } else { - r = regexp.MustCompile(fmt.Sprint(rx)) + // Safely attempt to compile the pattern; on error, report it so callers can fail the assertion. + compiled, err := regexp.Compile(fmt.Sprint(rx)) + if err != nil { + return false, err + } + r = compiled } switch v := str.(type) { case []byte: - return r.Match(v) + return r.Match(v), nil case string: - return r.MatchString(v) + return r.MatchString(v), nil default: - return r.MatchString(fmt.Sprint(v)) + return r.MatchString(fmt.Sprint(v)), nil } } // Regexp asserts that a specified regexp matches a string. // +// The rx (expression) argument should be a *regexp.Regexp. For backward +// compatibility, if rx is any other type, its value will be formatted with +// %v and compiled using regexp.Compile. +// +// Examples: +// // assert.Regexp(t, regexp.MustCompile("start"), "it's starting") -// assert.Regexp(t, "start...$", "it's not starting") +// assert.Regexp(t, "start...$", "it's not starting") // string is compiled func Regexp(t TestingT, rx interface{}, str interface{}, msgAndArgs ...interface{}) bool { if h, ok := t.(tHelper); ok { h.Helper() } - match := matchRegexp(rx, str) + match, err := matchRegexp(rx, str) + if err != nil { + Fail(t, fmt.Sprintf("Invalid regular expression: %v", err), msgAndArgs...) + return false + } if !match { Fail(t, fmt.Sprintf("Expect \"%v\" to match \"%v\"", str, rx), msgAndArgs...) @@ -1747,13 +1763,23 @@ func Regexp(t TestingT, rx interface{}, str interface{}, msgAndArgs ...interface // NotRegexp asserts that a specified regexp does not match a string. // +// The rx (expression) argument should be a *regexp.Regexp. For backward +// compatibility, if rx is any other type, its value will be formatted with +// %v and compiled using regexp.Compile. +// +// Examples: +// // assert.NotRegexp(t, regexp.MustCompile("starts"), "it's starting") -// assert.NotRegexp(t, "^start", "it's not starting") +// assert.NotRegexp(t, "^start", "it's not starting") // string is compiled func NotRegexp(t TestingT, rx interface{}, str interface{}, msgAndArgs ...interface{}) bool { if h, ok := t.(tHelper); ok { h.Helper() } - match := matchRegexp(rx, str) + match, err := matchRegexp(rx, str) + if err != nil { + Fail(t, fmt.Sprintf("Invalid regular expression: %v", err), msgAndArgs...) + return false + } if match { Fail(t, fmt.Sprintf("Expect \"%v\" to NOT match \"%v\"", str, rx), msgAndArgs...) diff --git a/assert/regexp_invalid_test.go b/assert/regexp_invalid_test.go new file mode 100644 index 000000000..3e2f50739 --- /dev/null +++ b/assert/regexp_invalid_test.go @@ -0,0 +1,19 @@ +package assert + +import "testing" + +// Verifies that invalid patterns no longer cause a panic when using Regexp/NotRegexp. +// Instead, the assertion should fail and return false. +func TestRegexp_InvalidPattern_NoPanic(t *testing.T) { + NotPanics(t, func() { + mockT := new(testing.T) + False(t, Regexp(mockT, "\\C", "whatever")) + }) +} + +func TestNotRegexp_InvalidPattern_NoPanic(t *testing.T) { + NotPanics(t, func() { + mockT := new(testing.T) + False(t, NotRegexp(mockT, "\\C", "whatever")) + }) +} diff --git a/require/require.go b/require/require.go index 23a3be780..61e5e9322 100644 --- a/require/require.go +++ b/require/require.go @@ -1204,7 +1204,7 @@ func Len(t TestingT, object interface{}, length int, msgAndArgs ...interface{}) } // Lenf asserts that the specified object has specific length. -// Lenf also fails if the object has a type that len() not accept. +// Len also fails if the object has a type that len() not accept. // // require.Lenf(t, mySlice, 3, "error message %s", "formatted") func Lenf(t TestingT, object interface{}, length int, msg string, args ...interface{}) { @@ -1729,8 +1729,14 @@ func NotPanicsf(t TestingT, f assert.PanicTestFunc, msg string, args ...interfac // NotRegexp asserts that a specified regexp does not match a string. // +// The rx (expression) argument should be a *regexp.Regexp. For backward +// compatibility, if rx is any other type, its value will be formatted with +// %v and compiled using regexp.Compile. +// +// Examples: +// // require.NotRegexp(t, regexp.MustCompile("starts"), "it's starting") -// require.NotRegexp(t, "^start", "it's not starting") +// require.NotRegexp(t, "^start", "it's not starting") // string is compiled func NotRegexp(t TestingT, rx interface{}, str interface{}, msgAndArgs ...interface{}) { if h, ok := t.(tHelper); ok { h.Helper() @@ -1743,8 +1749,14 @@ func NotRegexp(t TestingT, rx interface{}, str interface{}, msgAndArgs ...interf // NotRegexpf asserts that a specified regexp does not match a string. // +// The rx (expression) argument should be a *regexp.Regexp. For backward +// compatibility, if rx is any other type, its value will be formatted with +// %v and compiled using regexp.Compile. +// +// Examples: +// // require.NotRegexpf(t, regexp.MustCompile("starts"), "it's starting", "error message %s", "formatted") -// require.NotRegexpf(t, "^start", "it's not starting", "error message %s", "formatted") +// require.NotRegexpf(t, "^start", "it's not starting", "error message %s", "formatted") // string is compiled func NotRegexpf(t TestingT, rx interface{}, str interface{}, msg string, args ...interface{}) { if h, ok := t.(tHelper); ok { h.Helper() @@ -1961,8 +1973,14 @@ func Positivef(t TestingT, e interface{}, msg string, args ...interface{}) { // Regexp asserts that a specified regexp matches a string. // +// The rx (expression) argument should be a *regexp.Regexp. For backward +// compatibility, if rx is any other type, its value will be formatted with +// %v and compiled using regexp.Compile. +// +// Examples: +// // require.Regexp(t, regexp.MustCompile("start"), "it's starting") -// require.Regexp(t, "start...$", "it's not starting") +// require.Regexp(t, "start...$", "it's not starting") // string is compiled func Regexp(t TestingT, rx interface{}, str interface{}, msgAndArgs ...interface{}) { if h, ok := t.(tHelper); ok { h.Helper() @@ -1975,8 +1993,14 @@ func Regexp(t TestingT, rx interface{}, str interface{}, msgAndArgs ...interface // Regexpf asserts that a specified regexp matches a string. // +// The rx (expression) argument should be a *regexp.Regexp. For backward +// compatibility, if rx is any other type, its value will be formatted with +// %v and compiled using regexp.Compile. +// +// Examples: +// // require.Regexpf(t, regexp.MustCompile("start"), "it's starting", "error message %s", "formatted") -// require.Regexpf(t, "start...$", "it's not starting", "error message %s", "formatted") +// require.Regexpf(t, "start...$", "it's not starting", "error message %s", "formatted") // string is compiled func Regexpf(t TestingT, rx interface{}, str interface{}, msg string, args ...interface{}) { if h, ok := t.(tHelper); ok { h.Helper() diff --git a/require/require_forward.go b/require/require_forward.go index 38d985a55..8fb2d090e 100644 --- a/require/require_forward.go +++ b/require/require_forward.go @@ -955,7 +955,7 @@ func (a *Assertions) Len(object interface{}, length int, msgAndArgs ...interface } // Lenf asserts that the specified object has specific length. -// Lenf also fails if the object has a type that len() not accept. +// Len also fails if the object has a type that len() not accept. // // a.Lenf(mySlice, 3, "error message %s", "formatted") func (a *Assertions) Lenf(object interface{}, length int, msg string, args ...interface{}) { @@ -1369,8 +1369,14 @@ func (a *Assertions) NotPanicsf(f assert.PanicTestFunc, msg string, args ...inte // NotRegexp asserts that a specified regexp does not match a string. // +// The rx (expression) argument should be a *regexp.Regexp. For backward +// compatibility, if rx is any other type, its value will be formatted with +// %v and compiled using regexp.Compile. +// +// Examples: +// // a.NotRegexp(regexp.MustCompile("starts"), "it's starting") -// a.NotRegexp("^start", "it's not starting") +// a.NotRegexp("^start", "it's not starting") // string is compiled func (a *Assertions) NotRegexp(rx interface{}, str interface{}, msgAndArgs ...interface{}) { if h, ok := a.t.(tHelper); ok { h.Helper() @@ -1380,8 +1386,14 @@ func (a *Assertions) NotRegexp(rx interface{}, str interface{}, msgAndArgs ...in // NotRegexpf asserts that a specified regexp does not match a string. // +// The rx (expression) argument should be a *regexp.Regexp. For backward +// compatibility, if rx is any other type, its value will be formatted with +// %v and compiled using regexp.Compile. +// +// Examples: +// // a.NotRegexpf(regexp.MustCompile("starts"), "it's starting", "error message %s", "formatted") -// a.NotRegexpf("^start", "it's not starting", "error message %s", "formatted") +// a.NotRegexpf("^start", "it's not starting", "error message %s", "formatted") // string is compiled func (a *Assertions) NotRegexpf(rx interface{}, str interface{}, msg string, args ...interface{}) { if h, ok := a.t.(tHelper); ok { h.Helper() @@ -1553,8 +1565,14 @@ func (a *Assertions) Positivef(e interface{}, msg string, args ...interface{}) { // Regexp asserts that a specified regexp matches a string. // +// The rx (expression) argument should be a *regexp.Regexp. For backward +// compatibility, if rx is any other type, its value will be formatted with +// %v and compiled using regexp.Compile. +// +// Examples: +// // a.Regexp(regexp.MustCompile("start"), "it's starting") -// a.Regexp("start...$", "it's not starting") +// a.Regexp("start...$", "it's not starting") // string is compiled func (a *Assertions) Regexp(rx interface{}, str interface{}, msgAndArgs ...interface{}) { if h, ok := a.t.(tHelper); ok { h.Helper() @@ -1564,8 +1582,14 @@ func (a *Assertions) Regexp(rx interface{}, str interface{}, msgAndArgs ...inter // Regexpf asserts that a specified regexp matches a string. // +// The rx (expression) argument should be a *regexp.Regexp. For backward +// compatibility, if rx is any other type, its value will be formatted with +// %v and compiled using regexp.Compile. +// +// Examples: +// // a.Regexpf(regexp.MustCompile("start"), "it's starting", "error message %s", "formatted") -// a.Regexpf("start...$", "it's not starting", "error message %s", "formatted") +// a.Regexpf("start...$", "it's not starting", "error message %s", "formatted") // string is compiled func (a *Assertions) Regexpf(rx interface{}, str interface{}, msg string, args ...interface{}) { if h, ok := a.t.(tHelper); ok { h.Helper()