Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 33 additions & 6 deletions _codegen/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 "// <Name>".
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 {
Expand Down
18 changes: 15 additions & 3 deletions assert/assertion_format.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This change seems unexpected

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi @ccoVeille, thanks for catching that! The comment was updated to clarify that both [Lenf] and [Len] will fail if the object’s type isn’t accepted by Go’s built-in [len()] function. This makes the documentation more explicit for users who might be unsure about which types are supported. If you think the wording could be improved or if it’s redundant, I’m happy to adjust further.

//
// assert.Lenf(t, mySlice, 3, "error message %s", "formatted")
func Lenf(t TestingT, object interface{}, length int, msg string, args ...interface{}) bool {
Expand Down Expand Up @@ -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()
Expand Down Expand Up @@ -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()
Expand Down
34 changes: 29 additions & 5 deletions assert/assertion_forward.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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()
Expand All @@ -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()
Expand Down Expand Up @@ -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()
Expand All @@ -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()
Expand Down
46 changes: 36 additions & 10 deletions assert/assertions.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Comment on lines 1736 to +1740
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would use godoc links

Suggested change
// 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.
// 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].

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the suggestion, @ccoVeille! I’ve updated the doc comments to use GoDoc link syntax for both [regexp.Regexp] and [regexp.Compile] as you recommended. This should make the documentation clearer and more helpful for users. Let me know if you have any other feedback

//
// 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...)
Expand All @@ -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...)
Expand Down
19 changes: 19 additions & 0 deletions assert/regexp_invalid_test.go
Original file line number Diff line number Diff line change
@@ -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"))
})
}
34 changes: 29 additions & 5 deletions require/require.go
Original file line number Diff line number Diff line change
Expand Up @@ -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{}) {
Expand Down Expand Up @@ -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()
Expand All @@ -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()
Expand Down Expand Up @@ -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()
Expand All @@ -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()
Expand Down
Loading