From e9281d8a66b81f2140047ba4508f57cc0b2dd4da Mon Sep 17 00:00:00 2001 From: ynn <58146437+your-diary@users.noreply.github.com> Date: Sun, 1 Jun 2025 23:12:44 +0900 Subject: [PATCH] feat(assertions): add `Assert` and `Assertf` (#638) Co-authored-by: ynn --- README.md | 41 +++++++++++++++++++++++ assertions.go | 28 ++++++++++++++++ assertions_example_test.go | 45 +++++++++++++++++++++++++ assertions_test.go | 68 ++++++++++++++++++++++++++++++++++++++ 4 files changed, 182 insertions(+) create mode 100644 assertions.go create mode 100644 assertions_example_test.go create mode 100644 assertions_test.go diff --git a/README.md b/README.md index 5611568..7e23a99 100644 --- a/README.md +++ b/README.md @@ -327,6 +327,11 @@ Error handling: - [TryCatchWithErrorValue](#trycatchwitherrorvalue) - [ErrorsAs](#errorsas) +Assertions: + +- [Assert](#assert) +- [Assertf](#assertf) + Constraints: - Clonable @@ -4095,6 +4100,42 @@ if rateLimitErr, ok := lo.ErrorsAs[*RateLimitError](err); ok { [[play](https://go.dev/play/p/8wk5rH8UfrE)] +### Assert + +C/C++ style assertion. + +It does nothing when the condition is `true`, otherwise it panics with an optional message. + +Think twice before using it, given that [Go intentionally omits assertions from its standard library](https://go.dev/doc/faq#assertions). + +```go +age := getUserAge() + +lo.Assert(age >= 15) +``` + +```go +age := getUserAge() + +lo.Assert(age >= 15, "user age must be >= 15") +``` + +[[play](https://go.dev/play/p/Xv8LLKBMNwI)] + +### Assertf + +Like `Assert`, but with `fmt.Printf`-like formatting. + +Think twice before using it, given that [Go intentionally omits assertions from its standard library](https://go.dev/doc/faq#assertions). + +```go +age := getUserAge() + +lo.Assertf(age >= 15, "user age must be >= 15, got %d", age) +``` + +[[play](https://go.dev/play/p/TVPEmVcyrdY)] + ## 🛩 Benchmark We executed a simple benchmark with a dead-simple `lo.Map` loop: diff --git a/assertions.go b/assertions.go new file mode 100644 index 0000000..e67098a --- /dev/null +++ b/assertions.go @@ -0,0 +1,28 @@ +package lo + +import "fmt" + +const defaultAssertionFailureMessage = "assertion failed" + +// Assert does nothing when the condition is true, otherwise it panics with an optional message. +func Assert(condition bool, message ...string) { + if condition { + return + } + + panicMessage := defaultAssertionFailureMessage + if len(message) > 0 { + panicMessage = fmt.Sprintf("%s: %s", defaultAssertionFailureMessage, message[0]) + } + panic(panicMessage) +} + +// Assertf does nothing when the condition is true, otherwise it panics with a formatted message. +func Assertf(condition bool, format string, args ...any) { + if condition { + return + } + + panicMessage := fmt.Sprintf("%s: %s", defaultAssertionFailureMessage, fmt.Sprintf(format, args...)) + panic(panicMessage) +} diff --git a/assertions_example_test.go b/assertions_example_test.go new file mode 100644 index 0000000..9eaf07b --- /dev/null +++ b/assertions_example_test.go @@ -0,0 +1,45 @@ +package lo + +import "fmt" + +func ExampleAssert() { + defer func() { + if r := recover(); r != nil { + fmt.Println(r) + } + }() + + age := 20 + + // won't panic + Assert(age >= 18) + + // won't panic + Assert(age >= 18, "age must be at least 18") + + // will panic + Assert(age < 18) + + // will panic + Assert(age < 18, "age must be less than 18") + + // Output: assertion failed +} + +func ExampleAssertf() { + defer func() { + if r := recover(); r != nil { + fmt.Println(r) + } + }() + + age := 20 + + // won't panic + Assertf(age >= 18, "age must be at least 18, got %d", age) + + // will panic + Assertf(age < 18, "age must be less than 18, got %d", age) + + // Output: assertion failed: age must be less than 18, got 20 +} diff --git a/assertions_test.go b/assertions_test.go new file mode 100644 index 0000000..0a290d6 --- /dev/null +++ b/assertions_test.go @@ -0,0 +1,68 @@ +package lo + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestAssert(t *testing.T) { + t.Parallel() + is := assert.New(t) + + is.NotPanics(func() { + Assert(true) + }) + + is.NotPanics(func() { + Assert(true, "user defined message") + }) + + is.PanicsWithValue("assertion failed", func() { + Assert(false) + }) + + is.PanicsWithValue("assertion failed: user defined message", func() { + Assert(false, "user defined message") + }) + + //checks that the examples in `README.md` compile + { + age := 20 + is.NotPanics(func() { + Assert(age >= 15) + }) + is.NotPanics(func() { + Assert(age >= 15, "user age must be >= 15") + }) + } +} + +func TestAssertf(t *testing.T) { + t.Parallel() + is := assert.New(t) + + is.NotPanics(func() { + Assertf(true, "user defined message") + }) + + is.NotPanics(func() { + Assertf(true, "user defined message %d %d", 1, 2) + }) + + is.PanicsWithValue("assertion failed: user defined message", func() { + Assertf(false, "user defined message") + }) + + is.PanicsWithValue("assertion failed: user defined message 1 2", func() { + Assertf(false, "user defined message %d %d", 1, 2) + }) + + //checks that the example in `README.md` compiles + { + age := 7 + is.PanicsWithValue("assertion failed: user age must be >= 15, got 7", func() { + Assertf(age >= 15, "user age must be >= 15, got %d", age) + }) + } +}