diff --git a/README.md b/README.md index 16ef8133d..8f876507a 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,7 @@ **The v2 our tests wanted** -A set of `go` packages that provide tools for testifying that your code behaves as you intended. +A set of `go` packages that provide tools for testifying (verifying) that your code behaves as you intended. This is the go-openapi fork of the great [testify](https://github.com/stretchr/testify) package. @@ -25,7 +25,7 @@ Main features: * zero external dependencies * opt-in dependencies for extra features (e.g. asserting YAML, colorized output) -* assertions using generic types (see [a basic example][example-with-generics-url]) +* assertions using generic types (see [a basic example][example-with-generics-url]). [Read the fully story with generics][doc-generics] * [searchable documentation][doc-url] ## Announcements @@ -40,26 +40,42 @@ Or join our Slack channel: [![Slack Channel][slack-logo]![slack-badge]][slack-ur ### Status -Design and exploration phase. Contributions and proposals are welcome. +Design and exploration phase. Feedback, contributions and proposals are welcome. > **Recent news** -> Fully refactored how assertions are generated and documented. -> Fixed hangs & panics when using `spew`. Fuzzed `spew`. -> Fixed go routine leaks with `EventuallyWithT` and co. -> Added `Kind` & `NotKind` -> Fix deterministic order of keys in diff -> Fixed edge cases with `InDelta`, `InEpsilon` -> Added opt-in support for colorized output -> Introduced generics (round 1): 16 new type-safe assertions with generic types (added benchmark) > -> See our [ROADMAP][roadmap]. +> ✅ Fully refactored how assertions are generated and documented. +> +> ✅ Fixed hangs & panics when using `spew`. Fuzzed `spew`. +> +> ✅ Fixed go routine leaks with `EventuallyWithT` and co. +> +> ✅ Added `Kind` & `NotKind` +> +> ✅ Fix deterministic order of keys in diff +> +> ✅ Fixed edge cases with `InDelta`, `InEpsilon` +> +> ✅ Fixed edge cases with `EqualValues` +> +> ✅ Fixed wrong logic with `IsNonIncreasing`, `InNonDecreasing` +> +> ✅ Added opt-in support for colorized output +> +> ✅ Introduced generics: 38 new type-safe assertions with generic types (doc: added usage guide, examples and benchmark) +> +> See also our [ROADMAP][doc-roadmap]. + +## Getting started -## Import this library in your project +Import this library in your project like so. ```cmd go get github.com/go-openapi/testify/v2 ``` +... and start writing tests. Look at our [examples][doc-examples]. + ## Basic usage `testify` simplifies your test assertions like so. @@ -123,9 +139,10 @@ distributed with this fork, including internalized libraries. ## Other documentation -* [Getting started](https://go-openapi.github.io/testify/examples/) +* [Getting started][doc-examples] +* [Usage](https://go-openapi.github.io/testify/usage/) * [Motivations](https://go-openapi.github.io/testify/project/readme) -* [Roadmap][roadmap] +* [Roadmap][doc-roadmap] * [Internal architecture](https://go-openapi.github.io/testify/project/maintainers/architecture) * [All-time contributors](./CONTRIBUTORS.md) @@ -138,7 +155,7 @@ distributed with this fork, including internalized libraries. Maintainers can cut a new release by either: -* running [this workflow](https://github.com/go-openapi/testify/actions/workflows/bump-release.yml) (recommended) +* running [this workflow][ci-release-workflow] (recommended) * or : 1. preparing go.mod files with the next tag, merge 2. pushing a semver tag @@ -146,7 +163,7 @@ Maintainers can cut a new release by either: * The tag message is prepended to release notes -[roadmap]: https://go-openapi.github.io/testify/project/maintainers/roadmap +[doc-roadmap]: https://go-openapi.github.io/testify/project/maintainers/roadmap [test-badge]: https://github.com/go-openapi/testify/actions/workflows/go-test.yml/badge.svg [test-url]: https://github.com/go-openapi/testify/actions/workflows/go-test.yml @@ -169,6 +186,8 @@ Maintainers can cut a new release by either: [doc-badge]: https://img.shields.io/badge/doc-site-blue?link=https%3A%2F%2Fgo-openapi.github.io%2Ftestify%2F [doc-url]: https://go-openapi.github.io/testify +[doc-examples]: https://go-openapi.github.io/testify/usage/examples +[doc-generics]: https://go-openapi.github.io/testify/usage/generics [example-with-generics-url]: https://go-openapi.github.io/testify#usage-with-generics [godoc-badge]: https://pkg.go.dev/badge/github.com/go-openapi/testify [godoc-url]: http://pkg.go.dev/github.com/go-openapi/testify @@ -186,3 +205,4 @@ Maintainers can cut a new release by either: [goversion-url]: https://github.com/go-openapi/testify/blob/master/go.mod [top-badge]: https://img.shields.io/github/languages/top/go-openapi/testify [commits-badge]: https://img.shields.io/github/commits-since/go-openapi/testify/latest +[ci-release-workflow]: https://github.com/go-openapi/testify/actions/workflows/bump-release.yml diff --git a/SECURITY.md b/SECURITY.md index 9fa0369e6..b1969f387 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -8,12 +8,30 @@ This policy outlines the commitment and practices of the go-openapi maintainers | ------- | ------------------ | | 2.1.x | :white_check_mark: | +## Vulnerability checks in place + +This repository uses automated vulnerability scans, at every merged commit and at least once a week. + +We use: + +* [`GitHub CodeQL`][codeql-url] +* [`trivy`][trivy-url] +* [`govulncheck`][govulncheck-url] + +Reports are centralized in github security reports and visible only to the maintainers. + ## Reporting a vulnerability If you become aware of a security vulnerability that affects the current repository, -please report it privately to the maintainers. +**please report it privately to the maintainers** +rather than opening a publicly visible GitHub issue. + +Please follow the instructions provided by github to [Privately report a security vulnerability][github-guidance-url]. -Please follow the instructions provided by github to -[Privately report a security vulnerability](https://docs.github.com/en/code-security/security-advisories/guidance-on-reporting-and-writing-information-about-vulnerabilities/privately-reporting-a-security-vulnerability#privately-reporting-a-security-vulnerability). +> [!NOTE] +> On Github, navigate to the project's "Security" tab then click on "Report a vulnerability". -TL;DR: on Github, navigate to the project's "Security" tab then click on "Report a vulnerability". +[codeql-url]: https://github.com/github/codeql +[trivy-url]: https://trivy.dev/docs/latest/getting-started +[govulncheck-url]: https://go.dev/blog/govulncheck +[github-guidance-url]: https://docs.github.com/en/code-security/security-advisories/guidance-on-reporting-and-writing-information-about-vulnerabilities/privately-reporting-a-security-vulnerability#privately-reporting-a-security-vulnerability diff --git a/all-docs.md b/all-docs.md new file mode 100644 index 000000000..5306f1795 --- /dev/null +++ b/all-docs.md @@ -0,0 +1,70 @@ +# Documentation map + +## internals + +* ./hack/doc-site/hugo/README.md +* ./internal/difflib/README.md +* ./internal/spew/README.md +* ./internal/testintegration/README.md + +## repo + +* [x] ./README.md +* [x] ./CONTRIBUTORS.md +* [x] ./.github/CONTRIBUTING.md +* [x] ./.github/DCO.md +* [x] ./CODE_OF_CONDUCT.md +* [x] ./SECURITY.md + +## site + +### API docs (generated) + +* ./docs/doc-site/api/yaml.md +* ./docs/doc-site/api/number.md +* ./docs/doc-site/api/_index.md +* ./docs/doc-site/api/panic.md +* ./docs/doc-site/api/string.md +* ./docs/doc-site/api/equality.md +* ./docs/doc-site/api/file.md +* ./docs/doc-site/api/boolean.md +* ./docs/doc-site/api/common.md +* ./docs/doc-site/api/ordering.md +* ./docs/doc-site/api/json.md +* ./docs/doc-site/api/collection.md +* ./docs/doc-site/api/time.md +* ./docs/doc-site/api/comparison.md +* ./docs/doc-site/api/error.md +* ./docs/doc-site/api/testing.md +* ./docs/doc-site/api/type.md +* ./docs/doc-site/api/http.md +* ./docs/doc-site/api/condition.md + +### project docs + +* [x] ./docs/doc-site/_index.md + +* [x] ./docs/doc-site/usage/_index.md +* ./docs/doc-site/usage/TUTORIAL.md +* ./docs/doc-site/usage/EXAMPLES.md +* ./docs/doc-site/usage/GENERICS.md +* [x] ./docs/doc-site/usage/CHANGES.md + +* [x] ./docs/doc-site/project/_index.md +* [x] ./docs/doc-site/project/LICENSE.md +* [x] ./docs/doc-site/project/NOTICE.md +* [x] ./docs/doc-site/project/maintainers/_index.md +* [x] ./docs/doc-site/project/maintainers/MAINTAINERS.md +* ./docs/doc-site/project/maintainers/ARCHITECTURE.md +* ./docs/doc-site/project/maintainers/CODEGEN.md +* [x] ./docs/doc-site/project/maintainers/BENCHMARKS.md +* [x] ./docs/doc-site/project/maintainers/ROADMAP.md +* ./docs/doc-site/project/maintainers/ORIGINAL.md +* ./docs/doc-site/project/README.md +* [x] ./docs/doc-site/project/SECURITY.md +* ./docs/doc-site/project/contributing/STYLE.md +* [x] ./docs/doc-site/project/contributing/CONTRIBUTING.md +* [x] ./docs/doc-site/project/contributing/_index.md +* [x] ./docs/doc-site/project/contributing/CONTRIBUTORS.md +* [x] ./docs/doc-site/project/contributing/DCO.md +* [x] ./docs/doc-site/project/contributing/CODE_OF_CONDUCT.md diff --git a/codegen/internal/generator/templates/doc_index.md.gotmpl b/codegen/internal/generator/templates/doc_index.md.gotmpl index 0279fdfad..af6c39265 100644 --- a/codegen/internal/generator/templates/doc_index.md.gotmpl +++ b/codegen/internal/generator/templates/doc_index.md.gotmpl @@ -9,7 +9,7 @@ weight: 1 modified: {{ date }} --- -**Go testing assertions for the rest of us** +**The v2 our tests wanted** The [`testify/v2`][testifyv2] package has a fairly large API surface. @@ -25,7 +25,7 @@ with all documented exported variants documented in a more concise form than the ## Domains -The `testify` API is organized in {{ .RefCount }} domains shown below. +The `testify` API is organized in {{ .RefCount }} logical domains shown below. Each domain contains assertions regrouped by their use case (e.g. http, json, error). {{ print "{{" }}< children type="card" description="true" >{{ print "}}" }} diff --git a/docs/doc-site/_index.md b/docs/doc-site/_index.md index cdbf25322..905e68601 100644 --- a/docs/doc-site/_index.md +++ b/docs/doc-site/_index.md @@ -18,43 +18,44 @@ This is the go-openapi fork of the great [testify](https://github.com/stretchr/t ### Status {{% button href="https://github.com/go-openapi/testify/fork" hint="fork me on github" style=primary icon=code-fork %}}Fork me{{% /button %}} -Design and exploration phase. Contributions and proposals are welcome. +Design and exploration phase. Feedback, contributions and proposals are welcome. ### Motivation From the maintainers of `testify`, it looks like a v2 will eventually be released, but they'll do it at their own pace. -We like all the principles they put forward to build this v2. [See discussion about v2](https://github.com/stretchr/testify/discussions/1560) +We like all the principles they exposed to build this v2. [See discussion about v2](https://github.com/stretchr/testify/discussions/1560). However, at `go-openapi` we would like to address the well-known issues in `testify` with different priorities. With this fork, we want to: -1. remove all external dependencies. -2. make it easy to maintain and extend. -3. pare down some of the chrome that has been added over the years. +1. [x] remove all external dependencies. +2. [x] make it easy to maintain and extend. +3. [x] pare down some of the chrome that has been added over the years. {{% notice style="primary" title="Extended hand" icon="hand" %}} We hope that this endeavor will help the original project with a live-drill of what a v2 could look like. -Hopefully, some of our ideas here will eventually percolate back into the original project. -We are always happy to discuss with people who face the same problems as we do: avoid breaking changes, +Hopefully, some of our ideas will eventually percolate back into the original project and help the wider +community of go developers write better, clearer test code. + +Feedback is welcome and we are always happy to discuss with people who face the same problems as we do: avoid breaking changes, APIs that became bloated over a decade or so, uncontrolled dependencies, difficult choices when it comes to introduce breaking changes, conflicting demands from users etc. {{% /notice %}} -More about our motivations in the project's [README](README.md#motivation). - +Find more about our motivations in the project's [README](README.md#motivation). You might also be curious about our [ROADMAP](project/maintainers/ROADMAP.md). ### Getting started -To use this package in your projects: +Import this library in your project like so. ```cmd go get github.com/go-openapi/testify/v2 ``` -... and start writing tests. Look at our [examples](./examples/). +... and start writing tests. Look at our [examples][doc-examples]. ### Basic usage @@ -109,6 +110,23 @@ A formatted variant suffixed with `Tf` is also exposed. Obviously, the `Assertion` type cannot be extended with generic methods, as of `go1.25`. +{{< cards >}} +{{% card title="EqualT" %}} +```go + import ( + "testing" + + "github.com/go-openapi/testify/v2/require" + ) + ... + + const expected = "Hello World" + var input := "World" + + result := someRamblingTextGeneration(input) + require.EqualT(t, expected, result) +``` +{{% /card %}} {{% card title="InDeltaT" %}} ```go import ( @@ -128,6 +146,7 @@ Obviously, the `Assertion` type cannot be extended with generic methods, as of ` require.InDeltaT(t, expected, input, delta) ``` {{% /card %}} +{{< /cards >}} ## Licensing @@ -140,17 +159,16 @@ distributed with this fork, including internalized libraries. ## Contributing -Please feel free to submit issues, fork the repository and send pull requests! - -When submitting an issue, we ask that you please include a complete test function that demonstrates the issue. -Extra credit for those using Testify to write the test code that demonstrates it. +Feel free to submit issues, fork the repository and send pull requests! {{% notice style="primary" title="Info" icon="info" %}} Code generation is used. Run `go generate ./...` to update generated files. {{% /notice %}} -See also the [CONTRIBUTING guidelines](./project/contributing/CONTRIBUTING.md). +See also our [CONTRIBUTING guidelines](./project/contributing/CONTRIBUTING.md). --- {{< children type="card" description="true" >}} + +[doc-examples]: ./usage/examples diff --git a/docs/doc-site/examples/_index.md b/docs/doc-site/examples/_index.md deleted file mode 100644 index 6cb17a773..000000000 --- a/docs/doc-site/examples/_index.md +++ /dev/null @@ -1,7 +0,0 @@ ---- -title: Examples -description: Examples and tutorials. -weight: 2 ---- - -{{< children type="card" description="true" >}} diff --git a/docs/doc-site/project/SECURITY.md b/docs/doc-site/project/SECURITY.md index e47d72adb..eb9e9c32b 100644 --- a/docs/doc-site/project/SECURITY.md +++ b/docs/doc-site/project/SECURITY.md @@ -23,7 +23,7 @@ We use: * [`trivy`][trivy-url] * [`govulncheck`][govulncheck-url] -Reports are centralized in github security reports and visible to the maintainers. +Reports are centralized in github security reports and visible only to the maintainers. ## Reporting a vulnerability diff --git a/docs/doc-site/project/contributing/CONTRIBUTING.md b/docs/doc-site/project/contributing/CONTRIBUTING.md index 6ca50d1a6..89e1ea00a 100644 --- a/docs/doc-site/project/contributing/CONTRIBUTING.md +++ b/docs/doc-site/project/contributing/CONTRIBUTING.md @@ -30,12 +30,12 @@ You'll find more detailed (or repo-specific) instructions in the [maintainer's d {{% tab title="There are many ways to contribute" %}} There are many ways in which you can contribute, not just code. Here are a few ideas: - * Reporting Issues / Bugs - * Suggesting Improvements + * Reporting issues or bugs + * Suggesting improvements * Documentation * Art work that makes the project look great * Code - * bug fixes and new features that are within the main project scope + * proposing bug fixes and new features that are within the main project scope * improving test coverage * addressing code quality issues {{< /tab >}} @@ -96,10 +96,12 @@ If there's a problem with the implementation, hopefully you've received feedback If you have a lot of ideas or a lot of issues to solve, try to refrain a bit and post focused pull requests. -Think that they must be reviewed by a maintainer and it is easy to lost track of things on big PRs. +Think that they must be reviewed by a maintainer and it is easy to lose track of things on big PRs. We're trying very hard to keep the go-openapi packages lean and focused. -Together, these packages constitute a toolkit: it won't do everything for everybody out of the box, + +Together, these packages constitute a toolkit for go developers: +it won't do everything for everybody out of the box, but everybody can use it to do just about everything related to OpenAPI. This means that we might decide against incorporating a new feature. @@ -141,7 +143,7 @@ github will propose to open a pull request on the original repository. Typically you'd follow some common naming conventions: -- if it's a bugfix branch, name it `fix/XXX-something`where XXX is the number of the +- if it's a bug fixing branch, name it `fix/XXX-something` where XXX is the number of the issue on github - if it's a feature branch, create an enhancement issue to announce your intentions, and name it `feature/XXX-something` where XXX is the number of the issue. @@ -179,7 +181,7 @@ Don't forget to update the documentation when creating or modifying a feature. Most documentation for this library is directly found in code as comments for godoc. -The documentation for the go-openapi packages is published on [the public go docs site][go-doc]. +The documentation for this go-openapi package is published on [the public go docs site][go-doc]. --- @@ -211,7 +213,7 @@ reference to all the issues that they address. Pull requests must not contain commits from other users or branches. Commit messages are not required to follow the "conventional commit" rule, but it's certainly a good -thing to follow this guideline (e.g. "fix: blah blah", "ci: did this", "feat: did that" ...). +thing to follow that convention (e.g. "fix: fixed panic in XYZ", "ci: did this", "feat: did that" ...). The title in your commit message is used directly to produce our release notes: try to keep them neat. @@ -235,7 +237,7 @@ Be sure to post a comment after pushing. The new commits will show up in the pul request automatically, but the reviewers will not be notified unless you comment. Before the pull request is merged, -**make sure that you've squasheq your commits into logical units of work** +**make sure that you've squashed your commits into logical units of work** using `git rebase -i` and `git push -f`. After every commit the test suite should be passing. diff --git a/docs/doc-site/project/contributing/CONTRIBUTORS.md b/docs/doc-site/project/contributing/CONTRIBUTORS.md index b20229154..cfcb0d551 100644 --- a/docs/doc-site/project/contributing/CONTRIBUTORS.md +++ b/docs/doc-site/project/contributing/CONTRIBUTORS.md @@ -4,15 +4,273 @@ description: Acknowledgements to all-time contributors weight: 10 --- -# Emeritus +## Emeritus -We would like to acknowledge previous testify maintainers and their huge contributions to our collective success: +We thank these members for their service to this community. - * @matryer - * @glesica - * @ernesto-jimenez - * @mvdkleijn - * @georgelesica-wf - * @bencampbell-wf +- Repository: ['go-openapi/testify'] -We thank these members for their service to this community. +| Total Contributors | Total Contributions | +| --- | --- | +| 256 | 1124 | + +| Username | All Time Contribution Count | All Commits | +| --- | --- | --- | +| @ernesto-jimenez | 129 | https://github.com/go-openapi/testify/commits?author=ernesto-jimenez | +| @brackendawson | 110 | https://github.com/go-openapi/testify/commits?author=brackendawson | +| @dolmen | 103 | https://github.com/go-openapi/testify/commits?author=dolmen | +| @fredbi | 66 | https://github.com/go-openapi/testify/commits?author=fredbi | +| @tylerb | 64 | https://github.com/go-openapi/testify/commits?author=tylerb | +| @boyan-soubachov | 35 | https://github.com/go-openapi/testify/commits?author=boyan-soubachov | +| @leighmcculloch | 26 | https://github.com/go-openapi/testify/commits?author=leighmcculloch | +| @pmezard | 19 | https://github.com/go-openapi/testify/commits?author=pmezard | +| @alexandear | 18 | https://github.com/go-openapi/testify/commits?author=alexandear | +| @nelsam | 17 | https://github.com/go-openapi/testify/commits?author=nelsam | +| @alexpantyukhin | 16 | https://github.com/go-openapi/testify/commits?author=alexpantyukhin | +| @DAddYE | 14 | https://github.com/go-openapi/testify/commits?author=DAddYE | +| @MovieStoreGuy | 11 | https://github.com/go-openapi/testify/commits?author=MovieStoreGuy | +| @mikeauclair | 11 | https://github.com/go-openapi/testify/commits?author=mikeauclair | +| @architagr | 11 | https://github.com/go-openapi/testify/commits?author=architagr | +| @arjunmahishi | 10 | https://github.com/go-openapi/testify/commits?author=arjunmahishi | +| @arjun-1 | 9 | https://github.com/go-openapi/testify/commits?author=arjun-1 | +| @esdrasbeleza | 9 | https://github.com/go-openapi/testify/commits?author=esdrasbeleza | +| @matryer | 9 | https://github.com/go-openapi/testify/commits?author=matryer | +| @ccoVeille | 8 | https://github.com/go-openapi/testify/commits?author=ccoVeille | +| @AdRiley | 8 | https://github.com/go-openapi/testify/commits?author=AdRiley | +| @mvdkleijn | 8 | https://github.com/go-openapi/testify/commits?author=mvdkleijn | +| @linusbarth | 8 | https://github.com/go-openapi/testify/commits?author=linusbarth | +| @craig65535 | 8 | https://github.com/go-openapi/testify/commits?author=craig65535 | +| @muesli | 7 | https://github.com/go-openapi/testify/commits?author=muesli | +| @emou | 7 | https://github.com/go-openapi/testify/commits?author=emou | +| @HaraldNordgren | 7 | https://github.com/go-openapi/testify/commits?author=HaraldNordgren | +| @cszczepaniak | 7 | https://github.com/go-openapi/testify/commits?author=cszczepaniak | +| @Ogoyukin | 6 | https://github.com/go-openapi/testify/commits?author=Ogoyukin | +| @Neokil | 6 | https://github.com/go-openapi/testify/commits?author=Neokil | +| @torkelrogstad | 6 | https://github.com/go-openapi/testify/commits?author=torkelrogstad | +| @gohargasparyan | 6 | https://github.com/go-openapi/testify/commits?author=gohargasparyan | +| @mutaiib | 5 | https://github.com/go-openapi/testify/commits?author=mutaiib | +| @vkryukov | 5 | https://github.com/go-openapi/testify/commits?author=vkryukov | +| @posener | 5 | https://github.com/go-openapi/testify/commits?author=posener | +| @kevinburkesegment | 5 | https://github.com/go-openapi/testify/commits?author=kevinburkesegment | +| @hendrywiranto | 5 | https://github.com/go-openapi/testify/commits?author=hendrywiranto | +| @Saluev | 5 | https://github.com/go-openapi/testify/commits?author=Saluev | +| @vyas-git | 4 | https://github.com/go-openapi/testify/commits?author=vyas-git | +| @tsioftas | 4 | https://github.com/go-openapi/testify/commits?author=tsioftas | +| @viblo | 4 | https://github.com/go-openapi/testify/commits?author=viblo | +| @paulbellamy | 4 | https://github.com/go-openapi/testify/commits?author=paulbellamy | +| @palsivertsen | 4 | https://github.com/go-openapi/testify/commits?author=palsivertsen | +| @olivergondza | 4 | https://github.com/go-openapi/testify/commits?author=olivergondza | +| @mvrahden | 4 | https://github.com/go-openapi/testify/commits?author=mvrahden | +| @npxcomplete | 4 | https://github.com/go-openapi/testify/commits?author=npxcomplete | +| @jveski | 4 | https://github.com/go-openapi/testify/commits?author=jveski | +| @anpez | 4 | https://github.com/go-openapi/testify/commits?author=anpez | +| @bsoubachov | 4 | https://github.com/go-openapi/testify/commits?author=bsoubachov | +| @uberbo | 4 | https://github.com/go-openapi/testify/commits?author=uberbo | +| @redachl | 3 | https://github.com/go-openapi/testify/commits?author=redachl | +| @spirin | 3 | https://github.com/go-openapi/testify/commits?author=spirin | +| @taichi | 3 | https://github.com/go-openapi/testify/commits?author=taichi | +| @zjx20 | 3 | https://github.com/go-openapi/testify/commits?author=zjx20 | +| @senyast4745 | 3 | https://github.com/go-openapi/testify/commits?author=senyast4745 | +| @andreas | 3 | https://github.com/go-openapi/testify/commits?author=andreas | +| @AngelKitty | 3 | https://github.com/go-openapi/testify/commits?author=AngelKitty | +| @hanzei | 3 | https://github.com/go-openapi/testify/commits?author=hanzei | +| @jonhoo | 3 | https://github.com/go-openapi/testify/commits?author=jonhoo | +| @reynieroz | 3 | https://github.com/go-openapi/testify/commits?author=reynieroz | +| @josephholsten | 3 | https://github.com/go-openapi/testify/commits?author=josephholsten | +| @nmiyake | 3 | https://github.com/go-openapi/testify/commits?author=nmiyake | +| @obeattie | 3 | https://github.com/go-openapi/testify/commits?author=obeattie | +| @ryu-ton10 | 2 | https://github.com/go-openapi/testify/commits?author=ryu-ton10 | +| @snirye | 2 | https://github.com/go-openapi/testify/commits?author=snirye | +| @srenatus | 2 | https://github.com/go-openapi/testify/commits?author=srenatus | +| @xsleonard | 2 | https://github.com/go-openapi/testify/commits?author=xsleonard | +| @SuperQ | 2 | https://github.com/go-openapi/testify/commits?author=SuperQ | +| @viswajithiii | 2 | https://github.com/go-openapi/testify/commits?author=viswajithiii | +| @hawkingrei | 2 | https://github.com/go-openapi/testify/commits?author=hawkingrei | +| @yiminc-zz | 2 | https://github.com/go-openapi/testify/commits?author=yiminc-zz | +| @izaurio | 2 | https://github.com/go-openapi/testify/commits?author=izaurio | +| @gz-c | 2 | https://github.com/go-openapi/testify/commits?author=gz-c | +| @techfg | 2 | https://github.com/go-openapi/testify/commits?author=techfg | +| @visualfc | 2 | https://github.com/go-openapi/testify/commits?author=visualfc | +| @everdance | 2 | https://github.com/go-openapi/testify/commits?author=everdance | +| @greg0ire | 2 | https://github.com/go-openapi/testify/commits?author=greg0ire | +| @sikehish | 2 | https://github.com/go-openapi/testify/commits?author=sikehish | +| @LandonTClipp | 2 | https://github.com/go-openapi/testify/commits?author=LandonTClipp | +| @anupcshan | 2 | https://github.com/go-openapi/testify/commits?author=anupcshan | +| @AchoArnold | 2 | https://github.com/go-openapi/testify/commits?author=AchoArnold | +| @2pi | 2 | https://github.com/go-openapi/testify/commits?author=2pi | +| @bboreham | 2 | https://github.com/go-openapi/testify/commits?author=bboreham | +| @bmatsuo | 2 | https://github.com/go-openapi/testify/commits?author=bmatsuo | +| @dcormier | 2 | https://github.com/go-openapi/testify/commits?author=dcormier | +| @dencold | 2 | https://github.com/go-openapi/testify/commits?author=dencold | +| @siliconbrain | 2 | https://github.com/go-openapi/testify/commits?author=siliconbrain | +| @czeslavo | 2 | https://github.com/go-openapi/testify/commits?author=czeslavo | +| @ikravets | 2 | https://github.com/go-openapi/testify/commits?author=ikravets | +| @jeffwidman | 2 | https://github.com/go-openapi/testify/commits?author=jeffwidman | +| @joshrendek | 2 | https://github.com/go-openapi/testify/commits?author=joshrendek | +| @jcelliott | 2 | https://github.com/go-openapi/testify/commits?author=jcelliott | +| @mhamrle | 2 | https://github.com/go-openapi/testify/commits?author=mhamrle | +| @rinchsan | 2 | https://github.com/go-openapi/testify/commits?author=rinchsan | +| @mrbroll | 2 | https://github.com/go-openapi/testify/commits?author=mrbroll | +| @rubensayshi | 2 | https://github.com/go-openapi/testify/commits?author=rubensayshi | +| @renatoprime | 2 | https://github.com/go-openapi/testify/commits?author=renatoprime | +| @oalders | 2 | https://github.com/go-openapi/testify/commits?author=oalders | +| @rdwilliamson | 1 | https://github.com/go-openapi/testify/commits?author=rdwilliamson | +| @RmbRT | 1 | https://github.com/go-openapi/testify/commits?author=RmbRT | +| @RichardKnop | 1 | https://github.com/go-openapi/testify/commits?author=RichardKnop | +| @rgarcia | 1 | https://github.com/go-openapi/testify/commits?author=rgarcia | +| @pietern | 1 | https://github.com/go-openapi/testify/commits?author=pietern | +| @peterebden | 1 | https://github.com/go-openapi/testify/commits?author=peterebden | +| @pdbrito | 1 | https://github.com/go-openapi/testify/commits?author=pdbrito | +| @pquerna | 1 | https://github.com/go-openapi/testify/commits?author=pquerna | +| @pdufour | 1 | https://github.com/go-openapi/testify/commits?author=pdufour | +| @moolmanruan | 1 | https://github.com/go-openapi/testify/commits?author=moolmanruan | +| @rleungx | 1 | https://github.com/go-openapi/testify/commits?author=rleungx | +| @sam-nelson-mcn | 1 | https://github.com/go-openapi/testify/commits?author=sam-nelson-mcn | +| @g7r | 1 | https://github.com/go-openapi/testify/commits?author=g7r | +| @s-urbaniak | 1 | https://github.com/go-openapi/testify/commits?author=s-urbaniak | +| @shaneHowearth | 1 | https://github.com/go-openapi/testify/commits?author=shaneHowearth | +| @simonmulser | 1 | https://github.com/go-openapi/testify/commits?author=simonmulser | +| @brahmaroutu | 1 | https://github.com/go-openapi/testify/commits?author=brahmaroutu | +| @stdedos | 1 | https://github.com/go-openapi/testify/commits?author=stdedos | +| @aldas | 1 | https://github.com/go-openapi/testify/commits?author=aldas | +| @comogo | 1 | https://github.com/go-openapi/testify/commits?author=comogo | +| @TechnotronicOz | 1 | https://github.com/go-openapi/testify/commits?author=TechnotronicOz | +| @n9netales | 1 | https://github.com/go-openapi/testify/commits?author=n9netales | +| @anacrolix | 1 | https://github.com/go-openapi/testify/commits?author=anacrolix | +| @mmorel-35 | 1 | https://github.com/go-openapi/testify/commits?author=mmorel-35 | +| @wallclockbuilder | 1 | https://github.com/go-openapi/testify/commits?author=wallclockbuilder | +| @MAnyKey | 1 | https://github.com/go-openapi/testify/commits?author=MAnyKey | +| @mchlp | 1 | https://github.com/go-openapi/testify/commits?author=mchlp | +| @lesichkovm | 1 | https://github.com/go-openapi/testify/commits?author=lesichkovm | +| @mlsteele | 1 | https://github.com/go-openapi/testify/commits?author=mlsteele | +| @neilconway | 1 | https://github.com/go-openapi/testify/commits?author=neilconway | +| @nbaztec | 1 | https://github.com/go-openapi/testify/commits?author=nbaztec | +| @bobuhiro11 | 1 | https://github.com/go-openapi/testify/commits?author=bobuhiro11 | +| @doloopwhile | 1 | https://github.com/go-openapi/testify/commits?author=doloopwhile | +| @OladapoAjala | 1 | https://github.com/go-openapi/testify/commits?author=OladapoAjala | +| @omarkohl | 1 | https://github.com/go-openapi/testify/commits?author=omarkohl | +| @parkr | 1 | https://github.com/go-openapi/testify/commits?author=parkr | +| @pwfcurry | 1 | https://github.com/go-openapi/testify/commits?author=pwfcurry | +| @phemmer | 1 | https://github.com/go-openapi/testify/commits?author=phemmer | +| @stevenh | 1 | https://github.com/go-openapi/testify/commits?author=stevenh | +| @dave | 1 | https://github.com/go-openapi/testify/commits?author=dave | +| @GlenDC | 1 | https://github.com/go-openapi/testify/commits?author=GlenDC | +| @egawata | 1 | https://github.com/go-openapi/testify/commits?author=egawata | +| @hectorj | 1 | https://github.com/go-openapi/testify/commits?author=hectorj | +| @hidu | 1 | https://github.com/go-openapi/testify/commits?author=hidu | +| @lisitsky | 1 | https://github.com/go-openapi/testify/commits?author=lisitsky | +| @lummie | 1 | https://github.com/go-openapi/testify/commits?author=lummie | +| @myxo | 1 | https://github.com/go-openapi/testify/commits?author=myxo | +| @nicoche | 1 | https://github.com/go-openapi/testify/commits?author=nicoche | +| @perrydunn | 1 | https://github.com/go-openapi/testify/commits?author=perrydunn | +| @philtay | 1 | https://github.com/go-openapi/testify/commits?author=philtay | +| @renzoarreaza | 1 | https://github.com/go-openapi/testify/commits?author=renzoarreaza | +| @sarathsp06 | 1 | https://github.com/go-openapi/testify/commits?author=sarathsp06 | +| @sunpe | 1 | https://github.com/go-openapi/testify/commits?author=sunpe | +| @timfeirg | 1 | https://github.com/go-openapi/testify/commits?author=timfeirg | +| @tscales | 1 | https://github.com/go-openapi/testify/commits?author=tscales | +| @api-card | 1 | https://github.com/go-openapi/testify/commits?author=api-card | +| @wwade | 1 | https://github.com/go-openapi/testify/commits?author=wwade | +| @agonzalezro | 1 | https://github.com/go-openapi/testify/commits?author=agonzalezro | +| @hikyaru-suzuki | 1 | https://github.com/go-openapi/testify/commits?author=hikyaru-suzuki | +| @groz | 1 | https://github.com/go-openapi/testify/commits?author=groz | +| @tedeh | 1 | https://github.com/go-openapi/testify/commits?author=tedeh | +| @terinjokes | 1 | https://github.com/go-openapi/testify/commits?author=terinjokes | +| @guettli | 1 | https://github.com/go-openapi/testify/commits?author=guettli | +| @tschaub | 1 | https://github.com/go-openapi/testify/commits?author=tschaub | +| @tobikris | 1 | https://github.com/go-openapi/testify/commits?author=tobikris | +| @TomWright | 1 | https://github.com/go-openapi/testify/commits?author=TomWright | +| @prochac | 1 | https://github.com/go-openapi/testify/commits?author=prochac | +| @tabboud | 1 | https://github.com/go-openapi/testify/commits?author=tabboud | +| @xtonyjiang | 1 | https://github.com/go-openapi/testify/commits?author=xtonyjiang | +| @tcsc | 1 | https://github.com/go-openapi/testify/commits?author=tcsc | +| @qerdcv | 1 | https://github.com/go-openapi/testify/commits?author=qerdcv | +| @vincentcr | 1 | https://github.com/go-openapi/testify/commits?author=vincentcr | +| @vitalyisaev2 | 1 | https://github.com/go-openapi/testify/commits?author=vitalyisaev2 | +| @marshall-lee | 1 | https://github.com/go-openapi/testify/commits?author=marshall-lee | +| @willogden | 1 | https://github.com/go-openapi/testify/commits?author=willogden | +| @ybrustin | 1 | https://github.com/go-openapi/testify/commits?author=ybrustin | +| @yarikk | 1 | https://github.com/go-openapi/testify/commits?author=yarikk | +| @ac7 | 1 | https://github.com/go-openapi/testify/commits?author=ac7 | +| @cuishuang | 1 | https://github.com/go-openapi/testify/commits?author=cuishuang | +| @bogdandrutu | 1 | https://github.com/go-openapi/testify/commits?author=bogdandrutu | +| @docbobo | 1 | https://github.com/go-openapi/testify/commits?author=docbobo | +| @moorereason | 1 | https://github.com/go-openapi/testify/commits?author=moorereason | +| @chakrit | 1 | https://github.com/go-openapi/testify/commits?author=chakrit | +| @lazywei | 1 | https://github.com/go-openapi/testify/commits?author=lazywei | +| @hugelgupf | 1 | https://github.com/go-openapi/testify/commits?author=hugelgupf | +| @csmarchbanks | 1 | https://github.com/go-openapi/testify/commits?author=csmarchbanks | +| @cam72cam | 1 | https://github.com/go-openapi/testify/commits?author=cam72cam | +| @connor4312 | 1 | https://github.com/go-openapi/testify/commits?author=connor4312 | +| @shousper | 1 | https://github.com/go-openapi/testify/commits?author=shousper | +| @coryb | 1 | https://github.com/go-openapi/testify/commits?author=coryb | +| @SchumacherFM | 1 | https://github.com/go-openapi/testify/commits?author=SchumacherFM | +| @danielheller | 1 | https://github.com/go-openapi/testify/commits?author=danielheller | +| @danielchatfield | 1 | https://github.com/go-openapi/testify/commits?author=danielchatfield | +| @danielwhite | 1 | https://github.com/go-openapi/testify/commits?author=danielwhite | +| @hairyhenderson | 1 | https://github.com/go-openapi/testify/commits?author=hairyhenderson | +| @davidjb | 1 | https://github.com/go-openapi/testify/commits?author=davidjb | +| @dpw | 1 | https://github.com/go-openapi/testify/commits?author=dpw | +| @dennwc | 1 | https://github.com/go-openapi/testify/commits?author=dennwc | +| @lowjoel | 1 | https://github.com/go-openapi/testify/commits?author=lowjoel | +| @3scalation | 1 | https://github.com/go-openapi/testify/commits?author=3scalation | +| @AutomateAaron | 1 | https://github.com/go-openapi/testify/commits?author=AutomateAaron | +| @adamluzsi | 1 | https://github.com/go-openapi/testify/commits?author=adamluzsi | +| @adamwg | 1 | https://github.com/go-openapi/testify/commits?author=adamwg | +| @packrat386 | 1 | https://github.com/go-openapi/testify/commits?author=packrat386 | +| @atombender | 1 | https://github.com/go-openapi/testify/commits?author=atombender | +| @AlekSi | 1 | https://github.com/go-openapi/testify/commits?author=AlekSi | +| @kejadlen | 1 | https://github.com/go-openapi/testify/commits?author=kejadlen | +| @alxn | 1 | https://github.com/go-openapi/testify/commits?author=alxn | +| @aud10slave | 1 | https://github.com/go-openapi/testify/commits?author=aud10slave | +| @ErebusBat | 1 | https://github.com/go-openapi/testify/commits?author=ErebusBat | +| @shazow | 1 | https://github.com/go-openapi/testify/commits?author=shazow | +| @tamccall | 1 | https://github.com/go-openapi/testify/commits?author=tamccall | +| @Ararsa-Derese | 1 | https://github.com/go-openapi/testify/commits?author=Ararsa-Derese | +| @bozaro | 1 | https://github.com/go-openapi/testify/commits?author=bozaro | +| @bartventer | 1 | https://github.com/go-openapi/testify/commits?author=bartventer | +| @bwplotka | 1 | https://github.com/go-openapi/testify/commits?author=bwplotka | +| @bayesianmind | 1 | https://github.com/go-openapi/testify/commits?author=bayesianmind | +| @benbjohnson | 1 | https://github.com/go-openapi/testify/commits?author=benbjohnson | +| @blakewilson-wf | 1 | https://github.com/go-openapi/testify/commits?author=blakewilson-wf | +| @jonasfj | 1 | https://github.com/go-openapi/testify/commits?author=jonasfj | +| @jcamenisch | 1 | https://github.com/go-openapi/testify/commits?author=jcamenisch | +| @JonCrowther | 1 | https://github.com/go-openapi/testify/commits?author=JonCrowther | +| @ernsheong | 1 | https://github.com/go-openapi/testify/commits?author=ernsheong | +| @jinnovation | 1 | https://github.com/go-openapi/testify/commits?author=jinnovation | +| @xordspar0 | 1 | https://github.com/go-openapi/testify/commits?author=xordspar0 | +| @jayd3e | 1 | https://github.com/go-openapi/testify/commits?author=jayd3e | +| @capoferro | 1 | https://github.com/go-openapi/testify/commits?author=capoferro | +| @nyarly | 1 | https://github.com/go-openapi/testify/commits?author=nyarly | +| @justbuchanan | 1 | https://github.com/go-openapi/testify/commits?author=justbuchanan | +| @jedevc | 1 | https://github.com/go-openapi/testify/commits?author=jedevc | +| @ingwarsw | 1 | https://github.com/go-openapi/testify/commits?author=ingwarsw | +| @navytux | 1 | https://github.com/go-openapi/testify/commits?author=navytux | +| @itzamna314 | 1 | https://github.com/go-openapi/testify/commits?author=itzamna314 | +| @leepa | 1 | https://github.com/go-openapi/testify/commits?author=leepa | +| @luan | 1 | https://github.com/go-openapi/testify/commits?author=luan | +| @Lucaber | 1 | https://github.com/go-openapi/testify/commits?author=Lucaber | +| @miparnisari | 1 | https://github.com/go-openapi/testify/commits?author=miparnisari | +| @martin-sucha | 1 | https://github.com/go-openapi/testify/commits?author=martin-sucha | +| @dominicbarnes | 1 | https://github.com/go-openapi/testify/commits?author=dominicbarnes | +| @dlclark | 1 | https://github.com/go-openapi/testify/commits?author=dlclark | +| @bits01 | 1 | https://github.com/go-openapi/testify/commits?author=bits01 | +| @dmacvicar | 1 | https://github.com/go-openapi/testify/commits?author=dmacvicar | +| @echarrod | 1 | https://github.com/go-openapi/testify/commits?author=echarrod | +| @evanphx | 1 | https://github.com/go-openapi/testify/commits?author=evanphx | +| @fahimbagar | 1 | https://github.com/go-openapi/testify/commits?author=fahimbagar | +| @gavincabbage | 1 | https://github.com/go-openapi/testify/commits?author=gavincabbage | +| @glesica | 1 | https://github.com/go-openapi/testify/commits?author=glesica | +| @guncha | 1 | https://github.com/go-openapi/testify/commits?author=guncha | +| @cryptix | 1 | https://github.com/go-openapi/testify/commits?author=cryptix | +| @henrahmagix | 1 | https://github.com/go-openapi/testify/commits?author=henrahmagix | +| @hslatman | 1 | https://github.com/go-openapi/testify/commits?author=hslatman | +| @chirino | 1 | https://github.com/go-openapi/testify/commits?author=chirino | +| @ianrose14 | 1 | https://github.com/go-openapi/testify/commits?author=ianrose14 | +| @ifraixedes | 1 | https://github.com/go-openapi/testify/commits?author=ifraixedes | +| @jszwec | 1 | https://github.com/go-openapi/testify/commits?author=jszwec | +| @programmer04 | 1 | https://github.com/go-openapi/testify/commits?author=programmer04 | +| @jaguilar | 1 | https://github.com/go-openapi/testify/commits?author=jaguilar | +| @jbowes | 1 | https://github.com/go-openapi/testify/commits?author=jbowes | + + _this file was generated by the [Contributors GitHub Action](https://github.com/github/contributors)_ diff --git a/docs/doc-site/project/contributing/STYLE.md b/docs/doc-site/project/contributing/STYLE.md index 145f817ca..64dd44a65 100644 --- a/docs/doc-site/project/contributing/STYLE.md +++ b/docs/doc-site/project/contributing/STYLE.md @@ -27,9 +27,13 @@ You should run `golangci-lint run` before committing your changes. Many editors have plugins that do that automatically. > We use the `golangci-lint` meta-linter. The configuration lies in -> [`.golangci.yml`]([golangci.yml]). +> [`.golangci.yml`][golangci-yml]. > You may read [the linter's configuration reference][golangci-doc] for additional reference. +This configuration is essentially the same across all `go-openapi` projects. + +Some projects may require slightly different settings. + ## Linting rules posture Thanks to go's original design, we developers don't have to waste much time arguing about code figures of style. @@ -92,5 +96,5 @@ When this is possible, we enable linters with relaxed constraints. Final note: since we have switched to a forked version of `stretchr/testify`, we no longer benefit from the great `testifylint` linter for tests. -[golangci.yml]: https://github.com/go-openapi/testify/blob/master/.golangci.yml +[golangci-yml]: https://github.com/go-openapi/testify/blob/master/.golangci.yml [golangci-doc]: https://golangci-lint.run/docs/linters/configuration/ diff --git a/docs/doc-site/project/maintainers/BENCHMARKS.md b/docs/doc-site/project/maintainers/BENCHMARKS.md new file mode 100644 index 000000000..42d503946 --- /dev/null +++ b/docs/doc-site/project/maintainers/BENCHMARKS.md @@ -0,0 +1,324 @@ +--- +title: 'Benchmarks' +description: 'Performance measurement of assertions' +weight: 10 +--- + +# Performance Benchmarks: Generic vs Reflection + +**Last Updated**: 2026-01-20 + +## Quick Summary + +We added **38 generic assertion functions** to testify v2, providing type-safe alternatives to reflection-based assertions. While the primary goal was **compile-time type safety**, comprehensive benchmarking revealed an unexpected bonus: **dramatic performance improvements**. + +**Key Results:** +- **Type Safety**: Catch errors when writing tests, not when running them +- **Performance**: 1.2x to 81x faster depending on the operation +- **Memory**: Up to 99% reduction in allocations for collection operations +- **Zero Downside**: Generic variants are always as fast or faster + +## The Type Safety Story + +The main reason for adding generics wasn't performance—it was catching bugs earlier. + +### Before: Runtime Surprises + +```go +// Compiles fine, fails mysteriously at runtime +assert.Equal(t, []int{1, 2}, []string{"a", "b"}) +assert.ElementsMatch(t, userIDs, orderIDs) // Wrong comparison! +``` + +### After: Compile-Time Safety + +```go +// Compiler catches the error immediately +assert.EqualT(t, []int{1, 2}, []string{"a", "b"}) // ❌ Compile error! +assert.ElementsMatchT(t, userIDs, orderIDs) // ❌ Type mismatch! +``` + +**Real-world benefit**: When refactoring changes a type from `[]int` to `[]string`, generic assertions immediately flag all broken tests. Reflection-based assertions compile but fail during test runs—or worse, pass with wrong comparisons. + +## Performance Highlights + +While type safety was the goal, benchmarking revealed impressive performance gains across all domains. + +### 🏆 Collection Operations: The Big Winner + +| Function | Speedup | Memory Savings | +|----------|---------|----------------| +| **ElementsMatch (10 items)** | **21x faster** | 568 B → 320 B (44% reduction) | +| **ElementsMatch (100 items)** | **39x faster** | 41 KB → 3.6 KB (91% reduction) | +| **ElementsMatch (1000 items)** | **81x faster** | 4 MB → 33 KB (99% reduction) | +| **SliceContains** | **16x faster** | 4 allocs → 0 | +| **SeqContains (iter.Seq)** | **25x faster** | 55 allocs → 9 | +| **SliceSubset** | **43x faster** | 17 allocs → 0 | + +**Why it matters**: Collection operations are common in tests. ElementsMatchT is up to **81x faster** and uses **99% less memory** for large slices. + +### ⚡ Comparison Operations + +| Function | Speedup | Benefit | +|----------|---------|---------| +| **Greater/Less** | **10-15x faster** | Zero allocations | +| **Positive/Negative** | **16-22x faster** | Zero allocations | +| **GreaterOrEqual/LessOrEqual** | **10-11x faster** | Zero allocations | + +**Why it matters**: Direct operator usage (`>`, `<`) eliminates reflection overhead and boxing. + +### ✓ Equality Checks + +| Function | Speedup | Notes | +|----------|---------|-------| +| **Equal** | **10-13x faster** | All numeric types, strings | +| **NotEqual** | **11x faster** | Zero allocations | +| **IsOfType** | **9-11x faster** | Type checks without reflection | + +**Why it matters**: Equality checks are the most common assertion. 10x speedup adds up quickly. + +### 📊 Ordering Operations + +| Function | Speedup | Notes | +|----------|---------|-------| +| **IsIncreasing** | **7.4x faster** | 11 allocs → 0 | +| **IsDecreasing** | **9.5x faster** | 11 allocs → 0 | +| **IsNonIncreasing** | **6.5x faster** | 4 allocs → 0 | +| **IsNonDecreasing** | **8x faster** | 4 allocs → 0 | + +**Why it matters**: Ordering checks iterate over collections. Generics eliminate per-element reflection overhead. + +## What This Means for You + +### Always Prefer Generic Variants + +When a generic variant is available (functions ending in `T`), use it: + +```go +// OLD: Reflection-based +assert.Equal(t, 42, result) +assert.Greater(t, count, 0) +assert.ElementsMatch(t, expected, actual) + +// NEW: Type-safe + faster +assert.EqualT(t, 42, result) // 13x faster + compile-time safety +assert.GreaterT(t, count, 0) // 16x faster + compile-time safety +assert.ElementsMatchT(t, expected, actual) // 21-81x faster + compile-time safety +``` + +### Type Safety Catches Real Bugs + +**Example 1: Refactoring Safety** + +```go +// Your test +assert.ElementsMatchT(t, userIDs, orderIDs) + +// Someone changes OrderID type +type OrderID string // Was: int + +// Generic version: Compiler catches the error +assert.ElementsMatchT(t, userIDs, orderIDs) // ❌ Compile error! + +// Reflection version: Test compiles, fails mysteriously +assert.ElementsMatch(t, userIDs, orderIDs) // ✓ Compiles, ✗ Wrong at runtime +``` + +**Example 2: IDE Assistance** + +Generic variants enable IDE autocomplete to suggest only correctly-typed variables, preventing copy-paste errors. + +### When to Use Reflection Variants + +Keep reflection-based assertions for: +- **Intentional cross-type comparisons** (e.g., `int` vs `int64` with EqualValues) +- **Heterogeneous collections** (`[]any`) +- **Dynamic type scenarios** where compile-time type is unknown +- **Backward compatibility** with existing tests + +## Performance Tiers + +### Tier 1: Dramatic Improvements (10x+) +These operations see the biggest speedups because reflection overhead dominates: +- ElementsMatch: **21-81x** (scales with collection size) +- Equal/NotEqual: **10-13x** +- Comparison operators: **10-22x** +- Type checks: **9-11x** + +### Tier 2: Significant Improvements (3-10x) +Solid gains from eliminating reflection: +- Ordering checks: **6.5-9.5x** +- Collection operations: **7.5-43x** + +### Tier 3: Modest Improvements (1.2-3x) +Operations already optimized see smaller gains: +- Same/NotSame: **1.5-2x** +- Numeric comparisons: **1.2-1.5x** +- Boolean checks: **2x** + +### Tier 4: Comparable Performance +Operations where expensive processing dominates: +- JSONEq: JSON parsing dominates (marginal difference) +- Regexp: Regex compilation dominates (marginal difference) + +**Key insight**: Even when performance gains are modest, type safety alone justifies using generic variants. + +## Real Benchmark Results + +### ElementsMatch: The Star Performer + +``` +BenchmarkElementsMatch/reflect/10-16 3259 ns/op 568 B/op 67 allocs/op +BenchmarkElementsMatch/generic/10-16 154 ns/op 320 B/op 2 allocs/op + ↑ 21x faster + +BenchmarkElementsMatch/reflect/100-16 291692 ns/op 41360 B/op 5153 allocs/op +BenchmarkElementsMatch/generic/100-16 7429 ns/op 3696 B/op 3 allocs/op + ↑ 39x faster + +BenchmarkElementsMatch/reflect/1000-16 25.5 ms/op 4.0 MB/op 501503 allocs/op +BenchmarkElementsMatch/generic/1000-16 316 µs/op 33 KB/op 3 allocs/op + ↑ 81x faster ↑ 99% less memory +``` + +### Comparison Operations + +``` +BenchmarkGreater/reflect/int-16 139.1 ns/op 34 B/op 1 allocs/op +BenchmarkGreater/generic/int-16 17.9 ns/op 0 B/op 0 allocs/op + ↑ 7.8x faster + +BenchmarkPositive/reflect/int-16 121.5 ns/op 26 B/op 1 allocs/op +BenchmarkPositive/generic/int-16 7.6 ns/op 0 B/op 0 allocs/op + ↑ 16x faster +``` + +### Equality Checks + +``` +BenchmarkEqual/reflect/int-16 44.8 ns/op 0 B/op 0 allocs/op +BenchmarkEqual/generic/int-16 3.5 ns/op 0 B/op 0 allocs/op + ↑ 13x faster + +BenchmarkEqual/reflect/string-16 34.8 ns/op 0 B/op 0 allocs/op +BenchmarkEqual/generic/string-16 4.1 ns/op 0 B/op 0 allocs/op + ↑ 8.5x faster +``` + +## Why These Numbers Matter + +### 1. Allocation Elimination +The most dramatic speedups come from eliminating allocations entirely: +- **ElementsMatch**: 501,503 → 3 allocations (1000 elements) +- **All comparisons**: 1 → 0 allocations +- **Ordering checks**: 4-11 → 0 allocations + +Less allocation pressure means faster execution and reduced GC overhead. + +### 2. Superlinear Scaling +ElementsMatch's O(n²) complexity amplifies the benefits: +- 10 elements: 21x faster +- 100 elements: 39x faster +- 1000 elements: 81x faster + +The speedup **increases** with collection size. + +### 3. Cumulative Impact +If your test suite uses assertions thousands of times: +- 10x speedup per assertion = significantly faster test runs +- Especially impactful in CI/CD pipelines + +## Migration Guide + +### Step 1: Identify Generic-Capable Assertions + +Look for these common assertions in your tests: +- Equal, NotEqual → EqualT, NotEqualT +- Greater, Less, Positive, Negative → GreaterT, LessT, PositiveT, NegativeT +- Contains, ElementsMatch, Subset → ContainsT, ElementsMatchT, SubsetT +- IsIncreasing, IsDecreasing → IsIncreasingT, IsDecreasingT +- IsOfType → IsOfTypeT (eliminates need for dummy values!) + +### Step 2: Add Type Parameters + +```go +// Before +assert.Equal(t, expected, actual) + +// After: Add T suffix, compiler checks types +assert.EqualT(t, expected, actual) +``` + +### Step 3: Fix Type Mismatches + +The compiler will now catch type errors: + +```go +// This will now fail to compile +assert.EqualT(t, int64(42), int32(42)) + +// Fix by using the same type +assert.EqualT(t, int64(42), int64(actual)) + +// Or use reflection-based Equal for intentional cross-type comparison +assert.Equal(t, int64(42), int32(42)) // Uses reflection, still works +``` + +## Conclusion + +**Generic assertions deliver two major benefits:** + +1. **Type Safety (Primary Goal)**: Catch errors when writing tests + - Compiler catches type mismatches immediately + - IDE autocomplete guides to correct types + - Refactoring safety: broken tests identified at compile time + +2. **Performance (Unexpected Bonus)**: 1.2-81x faster + - Zero allocation overhead for most operations + - Dramatic gains for collection operations + - Cumulative benefits across large test suites + +**Recommendation**: Prefer generic variants (`*T` functions) wherever available. The type safety alone justifies the switch; the performance improvement is a bonus. + +### The Bottom Line + +```go +// What we wanted: Catch this when writing tests +assert.ElementsMatchT(t, []int{1,2}, []string{"a","b"}) // ❌ Compiler error + +// What we got as bonus: 81x faster when types match +assert.ElementsMatchT(t, []int{1,2}, []int{2,1}) // ✓ Type safe AND blazing fast +``` + +The performance improvements validate the design choice, but **type safety was always the goal**. + +--- + +## Running Your Own Benchmarks + +```bash +# Run all benchmarks +go test -run=^$ -bench=. -benchmem ./internal/assertions + +# Specific domain (equality, comparison, collection, etc.) +go test -run=^$ -bench='Benchmark(Equal|Same)' -benchmem ./internal/assertions + +# Compare specific function +go test -run=^$ -bench='BenchmarkElementsMatch' -benchmem ./internal/assertions +``` + +## Coverage + +**38 generic functions benchmarked across 10 domains:** +- Boolean (2): TrueT, FalseT +- Collection (12): StringContainsT, SliceContainsT, MapContainsT, SeqContainsT, ElementsMatchT, SliceSubsetT, and negative variants +- Comparison (6): GreaterT, LessT, GreaterOrEqualT, LessOrEqualT, PositiveT, NegativeT +- Equality (4): EqualT, NotEqualT, SameT, NotSameT +- JSON (1): JSONEqT +- Number (2): InDeltaT, InEpsilonT +- Ordering (6): IsIncreasingT, IsDecreasingT, IsNonIncreasingT, IsNonDecreasingT, SortedT, NotSortedT +- String (2): RegexpT, NotRegexpT +- Type (2): IsOfTypeT, IsNotOfTypeT +- YAML (1): YAMLEqT (benchmarked separately in enable/yaml module) + +--- diff --git a/docs/doc-site/project/maintainers/MAINTAINERS.md b/docs/doc-site/project/maintainers/MAINTAINERS.md index 4a2d15a88..52a0fe250 100644 --- a/docs/doc-site/project/maintainers/MAINTAINERS.md +++ b/docs/doc-site/project/maintainers/MAINTAINERS.md @@ -1,24 +1,24 @@ --- -title: Maintainer's guide -description: Guidelines for maintainers +title: Maintainer's Guide +description: General guidelines for maintainers weight: 1 --- ## Repo structure -Monorepo with multiple go modules. +This project is organized as a monorepo with multiple go modules. ## Repo configuration -* default branch: master -* protected branches: master -* branch protection rules: +* Default branch: master +* Protected branches: master +* Branch protection rules: * require pull requests and approval * required status checks: - DCO (simple email sign-off) - Lint - - tests completed -* auto-merge enabled (used for dependabot updates) + - All tests completed +* Auto-merge enabled (used for dependabot updates and other auto-merged PR's, e.g. contributors update) ## Continuous Integration @@ -90,6 +90,10 @@ This allows for minimal test dependencies. * `go.mod` should be updated (manually) whenever there is a new go minor release (e.g. every 6 months). + > This means that our projects always have a 6 months lag to enforce new features from the go compiler. + > + > However, new features may be used with a "go:build" tag + * contributors * a [`CONTRIBUTORS.md`][contributors-doc] file is updated weekly, with all-time contributors to the repository * the `github-actions[bot]` posts a pull request to do that automatically @@ -123,7 +127,10 @@ The release process is minimalist: * the CI handles this to generate a github release with release notes * release notes generator: git-cliff -* configuration: [`cliff.toml`][cliff-config] +* configuration: the `.cliff.toml` is defined as a share configuration on + remote repo [ci-worflows/.cliff.toml][remote-cliff-config] + +Commits from maintainers are preferably PGP-signed. Tags are preferably PGP-signed. @@ -138,8 +145,10 @@ before pushing a tag. A bump release workflow (mono-repo) can be triggered from the github actions UI to cut a release with a few clicks. -It works the same as the one for single module repos, and first creates a PR (auto-merged) -that updates the different go.mod files. +It works with the same input as the one for single module repos, and first creates a PR (auto-merged) +that updates the different go.mod files _before_ pushing the desired git tag. + +Commits and tags pushed by the workflow bot are PGP-signed ("go-openapi[bot]"). ## Other files @@ -160,16 +169,17 @@ Reference documentation (released): A few things remain ahead to ease a bit a maintainer's job: -* [ ] reuse CI workflows (e.g. in ) -* [ ] reusable actions with custom tools pinned (e.g. in ) -* open-source license checks -* [ ] auto-merge for CONTRIBUTORS.md (requires a github app to produce tokens) +* [x] reuse CI workflows (e.g. in ) +* [x] reusable actions with custom tools pinned (e.g. in ) +* [x] auto-merge for CONTRIBUTORS.md (requires a github app to produce tokens) * [x] more automated code renovation / relinting work * organization-level documentation web site +* open-source license checks * ... [linter-config]: https://github.com/go-openapi/testify/blob/master/.golangci.yml -[cliff-config]: https://github.com/go-openapi/testify/blob/master/.cliff.toml +[local-cliff-config]: https://github.com/go-openapi/testify/blob/master/.cliff.toml +[remote-cliff-config]: https://github.com/go-openapi/ci-workflows/blob/master/.cliff.toml [dependabot-config]: https://github.com/go-openapi/testify/blob/master/.github/dependabot.yaml [gocard-url]: https://goreportcard.com/report/github.com/go-openapi/testify [codefactor-url]: https://www.codefactor.io/repository/github/go-openapi/testify diff --git a/docs/doc-site/project/maintainers/ROADMAP.md b/docs/doc-site/project/maintainers/ROADMAP.md index da24f14ca..2c0c87a0a 100644 --- a/docs/doc-site/project/maintainers/ROADMAP.md +++ b/docs/doc-site/project/maintainers/ROADMAP.md @@ -10,24 +10,24 @@ weight: 4 timeline title Planned releases section Q4 2025 - v2.0 (Nov 2025) : zero dependencies + ✅ v2.0 (Nov 2025) : zero dependencies : optional dependencies (YAML) : modernized code (relint) : JSONEqBytes section Q1 2026 - v2.1 (Jan 2026) : generated assertions + ✅ v2.1 (Jan 2026) : generated assertions : complete refactoring : documentation site : panic handling fixes : removed deprecated - : optional dependencies (colorized) - : upstream PRs: Kind/NotKind - v2.2 (Fev 2026) : : generics + 📝 v2.2 (Fev 2026) : generics + : Kind/NotKind : SortedT, NotSortedT - : JSON assertions. JSONMarshalsAs... : complete test refactoring : more benchmarks. Perf improvements - v2.3 (Mar 2026) : other extensions (TBD) + : optional dependencies (colorized) + ⏳ v2.3 (Mar 2026) : other extensions + : JSON assertions. JSONMarshalsAs... : more documentation and examples : export internal tools (spew, difflib, benchviz) section Q2 2026 @@ -42,61 +42,24 @@ timeline 5. [x] More testing and bug fixes (from upstream or detected during our testing) 6. [x] Introduces colorization (opt-in) 7. [x] Introduces generics -8. [ ] New features following test simplification effort in go-openapi repos (e.g. JSONMarshalsAs ...) -9. [ ] Unclear assertions may be provided an alternative verb (e.g. `InDelta`) -10. [ ] Inject this test dependency into the `go-swagger` tool +8. [x] Realign behavior re quirks, bugs, unexpected logics ... (e.g. IsNonDecreasing, EventuallyWithT...) +9. [ ] New features following test simplification effort in go-openapi repos (e.g. JSONMarshalsAs ...) +10. [ ] Unclear assertions might be provided an alternative verb (e.g. `InDelta`) +11. [ ] Inject this test dependency into the `go-swagger` tool ### What won't come anytime soon -* mocks: we use [mockery](https://github.com/vektra/mockery) and prefer the simpler `matryer` mocking-style. +* mocks: we use [mockery](https://https://github.com/vektra/mockery) and prefer the simpler `matryer` mocking-style. testify-style mocks are thus not going to be supported anytime soon. * extra convoluted stuff in the like of `InDeltaSlice` (more likely to be removed) -## PRs and issues from the original repo - -We monitor github.com/stretchr/testify (upstream) for updates, new issues and proposals. - -### Already merged or incorporated / adapted - -The following proposed contributions to the original repo have been merged or incorporated with -some adaptations into this fork: - -* [x] github.com/stretchr/testify#1147 - General discussion about generics adoption (marked "Not Planned") -* [x] github.com/stretchr/testify#1223 - Display uint values in decimal instead of hex in diffs [merged] -* [x] github.com/stretchr/testify#1232 - Colorized output for expected/actual/errors -* [x] github.com/stretchr/testify#1308 - Comprehensive refactor replacing `interface{}` with generic type parameters across assertions (Draft, v2.0.0 milestone) -* [x] github.com/stretchr/testify#1356 - panic(nil) handling for Go 1.21+ -* [x] github.com/stretchr/testify#1467 - Colorized output with terminal detection (most mature implementation) -* [x] github.com/stretchr/testify#1480 - Colorized diffs via TESTIFY_COLORED_DIFF env var -* [x] github.com/stretchr/testify#1513 - JSONEqBytes for byte slice JSON comparison -* [x] github.com/stretchr/testify#1772 - YAML library migration to maintained fork (go.yaml.in/yaml) -* [x] github.com/stretchr/testify#1797 - Codegen package consolidation and licensing -* [x] github.com/stretchr/testify#1816 - Fix panic on unexported struct key in map (internalized go-spew - may need deeper fix) -* [x] github.com/stretchr/testify#1818 - Fix panic on invalid regex in Regexp/NotRegexp assertions [merged] -* [x] github.com/stretchr/testify#1822 - Deterministic map ordering in diffs (internalized go-spew) -* [x] github.com/stretchr/testify#1825 - Fix panic when using EqualValues with uncomparable types [merged] -* [x] github.com/stretchr/testify#1829 - Fix time.Time rendering in diffs (internalized go-spew) -* [x] github.com/stretchr/testify#994 - Colorize expected vs actual values -* [x] github.com/stretchr/testify/issues/1611 - Go routine leak -* [x] github.com/stretchr/testify/issues/1813 -* [x] github.com/stretchr/testify/issues/1826 - type safety with spew -* ⛔ https://github.com/stretchr/testify/pull/1824 - No longer relevant in our context - -### Merges from upstream under consideration - -* [ ] **github.com/stretchr/testify#1685** - Iterator support (`iter.Seq`) for Contains/ElementsMatch assertions (Go 1.23+) -* [ ] **github.com/stretchr/testify#1805** - Proposal for generic `IsOfType[T]()` to avoid dummy value instantiation in type checks - -## Generics adoption - -The original repository's exploration of generics revealed several design challenges: +## Upstream Tracking -1. **Type inference limitations**: Go's type inference struggles with complex generic signatures, often requiring explicit type parameters that burden the API (e.g., `Contains[int, int](arr1, arr2)`) +We actively monitor [github.com/stretchr/testify](https://github.com/stretchr/testify) for updates, new issues, and proposals. -2. **Overly broad type constraints**: PR #1308's approach used constraints like `ConvertibleToFloat64` that accepted more types than intended, weakening type safety +**Review frequency**: Quarterly (next review: April 2026) -3. **Loss of flexibility**: Testify currently compares non-comparable types (slices, maps) via `reflect.DeepEqual`. Generic constraints would eliminate this capability, as Go generics require comparable or explicitly constrained types +**Processed items**: 28 upstream PRs and issues have been reviewed, with 21 implemented/merged, 3 superseded by our implementation, and 2 currently under consideration. -4. **Breaking changes**: Any comprehensive generics adoption requires a major version bump and Go 1.18+ minimum version +For a complete catalog of all upstream PRs and issues we've processed (implemented, adapted, superseded, or monitoring), see the [Upstream Tracking](../../usage/TRACKING.md). -5. **Inconsistent design patterns**: Different assertions would need different constraint strategies, making a uniform approach difficult diff --git a/docs/doc-site/usage/CHANGES.md b/docs/doc-site/usage/CHANGES.md new file mode 100644 index 000000000..007efb0d2 --- /dev/null +++ b/docs/doc-site/usage/CHANGES.md @@ -0,0 +1,535 @@ +--- +title: "Changes from v1" +description: "All changes from testify/v1" +weight: 15 +--- + +## Summary + +**Key Changes:** +- **Dependencies**: Zero external (internalized 2, optional 1 via enable pattern) +- **New functions**: 51 total (38 generic + 13 reflection-based) +- **Performance**: ~10x for generic variants (from 1.2x to 81x, your mileage may vary) +- **Architecture**: 100% code generation from single source +- **Breaking changes**: Requires go1.24, removed suites, mocks, http tooling, and deprecated functions. YAMLEq becomes optional (panics by default). + +--- + +**Testify v2 represents a comprehensive modernization** + +- ✅ **Zero Dependencies**: Completely self-contained +- ✅ **Type Safety**: 38 generic assertions catch errors at compile time +- ✅ **Performance**: Up to 81x faster with generics +- ✅ **Documentation**: compelling Hugo site to document the API by use-case domain +- ✅ **Quality**: 96% test coverage, extensive fuzzing & benchmarking +- ✅ **Maintainability**: 100% code generation from single source + +This fork maintains compatibility where possible while making bold improvements in architecture, safety, and performance. + +**Fork Information:** +- **Upstream repository**: [github.com/stretchr/testify](https://github.com/stretchr/testify) +- **Fork date**: 2025-01-09 +- **Fork commit**: `feb1324bc3d000fed7b21dfe20bec72ecca27502` + +See also a quick [migration guide](./MIGRATION.md). + +## Cross-Domain Changes + +{{% tabs %}} +{{% tab title="Additions" color=green %}} +### Major Additions + +#### Usage + +| Change | Origin | Description | +|--------|--------|-------------| +| **Generic assertions** | Multiple upstream proposals | Added 38 type-safe assertion functions with `T` suffix across 10 domains | +| **Zero dependencies** | Design goal | Internalized go-spew and difflib; removed all external dependencies | +| **Optional YAML support** | Design goal | YAML assertions are now enabled via opt-in `enable/yaml` module | +| **Colorized output** | [#1467], [#1480], [#1232], [#994] | Optional colorization via `enable/color` module with themes | +| **Enhanced diff output** | [#1829] | Improved time.Time rendering, deterministic map ordering | + +#### Maintenability + +| Change | Origin | Description | +|--------|--------|-------------| +| **Code generation** | Design goal | 100% generated assert/require packages (608+ functions from 76 assertions) | +| **Code modernization** | Design goal | Relinted, refactored and modernized the code base, including internalized difflib and go-spew| +| **Refactored tests** | Design goal | Full refactoring of tests on assertion functions, with unified test scenarios for reflection-based/generic assertions | + +[#1467]: https://github.com/stretchr/testify/pull/1467 +[#1480]: https://github.com/stretchr/testify/pull/1480 +[#1232]: https://github.com/stretchr/testify/pull/1232 +[#994]: https://github.com/stretchr/testify/pull/994 +[#1829]: https://github.com/stretchr/testify/issues/1829 +{{% /tab %}} +{{% tab title="Removals" style=warning %}} + +### Major Removals (Breaking Changes) + +| Removed | Reason | +|---------|--------| +| **Suite package** | Complex interactions with dependencies; might re-introduce this feature later | +| **Mock package** | Use specialized [mockery](https://github.com/vektra/mockery) tool instead | +| **HTTP package** | Simplified focus; may be reintroduced later | +| **Deprecated functions** | Clean slate for v2 | +| **Renaming** | `NoDirExists` renamed into `DirNotExists`. `NoFileExists` renamed into `FileNotExists`| + +### Infrastructure Improvements + +| Change | Description | +|--------|-------------| +| **Internalized dependencies** | go-spew and difflib internalized with modernized code | +| **Module structure** | Clean separation: core (zero deps), enable modules (optional) | +| **Documentation site** | Hugo-based site with domain-organized API reference | +| **Fuzz testing** | Fuzz test on spew.Sdump based on random data structures generation | +| **Comprehensive benchmarks** | 37 benchmarks comparing generic vs reflection performance | +| **Advanced CI** | Reuse go-openapi workflows with tests and coverage reporting, fuzz testing, release automation | +{{% /tab %}} +{{% /tabs %}} + +## Bug Fixes and Safety Improvements + +{{% tabs %}} +{{% tab title="Bug fixes" color=green %}} + +### Critical Fixes reported upstream + +| Issue/PR | Domain | Description | +|----------|--------|-------------| +| [#1223] | Display | Display uint values in decimal instead of hex | +| [#1611] | Condition | Fixed goroutine leak in Eventually/Never | +| [#1813] | Internal (spew) | Fixed panic with unexported fields (via #1828) | +| [#1818] | String | Fixed panic on invalid regex in Regexp/NotRegexp | +| [#1822] | Internal (spew) | Deterministic map ordering in diffs | +| [#1825] | Equality | Fixed panic when using EqualValues with uncomparable types | +| [#1828] | Internal (spew) | Fixed panic with unexported fields in maps | + +[#1223]: https://github.com/stretchr/testify/pull/1223 +[#1611]: https://github.com/stretchr/testify/issues/1611 +[#1813]: https://github.com/stretchr/testify/issues/1813 +[#1818]: https://github.com/stretchr/testify/pull/1818 +[#1822]: https://github.com/stretchr/testify/issues/1822 +[#1825]: https://github.com/stretchr/testify/pull/1825 +[#1828]: https://github.com/stretchr/testify/pull/1828 +{{% /tab %}} +{{% tab title="Safety Improvements" color=blue %}} + +### Comprehensive Spew Testing + +- Added property-based fuzzing for go-spew with random type generator +- Fixed circular reference edge cases (pointer wrapped in interface, circular map reference) +- Supersedes upstream [#1824] + +[#1824]: https://github.com/stretchr/testify/pull/1824 + +### Reflection Safety + +- More defensive guards re-reflect panic risk in `EqualExportedValues` +- Fixed 50 unchecked type assertions across test codebase +- Zero linting issues with `forcetypeassert` linter +{{% /tab %}} +{{% /tabs %}} + +## Changes by Domain +### Boolean + +{{% expand title="Generics" %}} + +#### New Generic Functions (2) + +| Function | Type | Origin | Description | +|----------|------|--------|-------------| +| `TrueT[B ~bool]` | Generic | Generics initiative | Type-safe boolean true assertion | +| `FalseT[B ~bool]` | Generic | Generics initiative | Type-safe boolean false assertion | + +{{% /expand %}} + +**Behavior changes**: None + +### Collection + +{{% expand title="Generics" %}} + +#### New Generic Functions (12) + +| Function | Type Parameters | Description | +|----------|-----------------|-------------| +| `StringContainsT[S Text]` | String or []byte | Type-safe string/bytes contains check | +| `StringNotContainsT[S Text]` | String or []byte | Type-safe string/bytes not-contains check | +| `SliceContainsT[E comparable]` | Comparable element | Type-safe slice membership check | +| `SliceNotContainsT[E comparable]` | Comparable element | Type-safe slice non-membership check | +| `MapContainsT[K comparable, V any]` | Key type | Type-safe map key check | +| `MapNotContainsT[K comparable, V any]` | Key type | Type-safe map key absence check | +| `SeqContainsT[E comparable]` | Iterator element | Type-safe iterator membership check (Go 1.23+) | +| `SeqNotContainsT[E comparable]` | Iterator element | Type-safe iterator non-membership check (Go 1.23+) | +| `ElementsMatchT[E comparable]` | Slice element | Type-safe slice equality (any order) | +| `NotElementsMatchT[E comparable]` | Slice element | Type-safe slice inequality | +| `SliceSubsetT[E comparable]` | Slice element | Type-safe subset relationship check | +| `SliceNotSubsetT[E comparable]` | Slice element | Type-safe non-subset check | + +**Origin**: Generic initiative + [#1685] (partial - SeqContains variants only) + +**Performance**: 16-81x faster than reflection-based variants (see [benchmarks](../../project/maintainers/BENCHMARKS.md)) + +[#1685]: https://github.com/stretchr/testify/pull/1685 +{{% /expand %}} + + +**Behavior changes**: None + +### Comparison + +{{% expand title="Generics" %}} + +#### New Generic Functions (6) + +| Function | Type Parameters | Description | +|----------|-----------------|-------------| +| `GreaterT[V Ordered]` | extended Ordered type[^1] | Type-safe greater-than comparison | +| `GreaterOrEqualT[V Ordered]` | Ordered type | Type-safe >= comparison | +| `LessT[V Ordered]` | Ordered type | Type-safe less-than comparison | +| `LessOrEqualT[V Ordered]` | Ordered type | Type-safe <= comparison | +| `PositiveT[V Ordered]` | Ordered type | Type-safe positive value check (> 0) | +| `NegativeT[V Ordered]` | Ordered type | Type-safe negative value check (< 0) | + +[^1]: Ordered is defined as the union of standard go ordered types, plus `[]byte` and `time.Time`. + +**Origin**: Generics initiative + +**Performance**: 10-22x faster than reflection-based variants +{{% /expand %}} + +**Behavior changes**: None + +### Condition + +**New functions**: None + +#### ⚠️ Behavior Changes + +| Change | Origin | Description | +|--------|--------|-------------| +| Fixed goroutine leak | [#1611] | Consolidated `Eventually`, `Never`, and `EventuallyWithT` into single `pollCondition` function | +| Context-based polling | Internal refactoring | Reimplemented with context-based approach for better resource management | +| Unified implementation | Internal refactoring | Single implementation eliminates code duplication and prevents resource leaks | + +**Impact**: This fix eliminates goroutine leaks that could occur when using `Eventually` or `Never` assertions. The new implementation uses a context-based approach that properly manages resources and provides a cleaner shutdown mechanism. Callers should **NOT** assume that the call to `Eventually` or `Never` exits before the condition is evaluated. Callers should **NOT** assume that the call to `Eventually` or `Never` exits before the condition is evaluated. + +**Supersedes**: This implementation also supersedes upstream proposals [#1819] (handle unexpected exits) and [#1830] (CollectT.Halt) with a more comprehensive solution. + +[#1611]: https://github.com/stretchr/testify/issues/1611 +[#1819]: https://github.com/stretchr/testify/pull/1819 +[#1830]: https://github.com/stretchr/testify/pull/1830 + +### Equality + +{{% expand title="Generics" %}} + +#### New Generic Functions (4) + +| Function | Type Parameters | Description | +|----------|-----------------|-------------| +| `EqualT[V comparable]` | Comparable type | Type-safe equality check | +| `NotEqualT[V comparable]` | Comparable type | Type-safe inequality check | +| `SameT[V comparable]` | Comparable type | Type-safe pointer identity check | +| `NotSameT[V comparable]` | Comparable type | Type-safe pointer difference check | + +**Origin**: Generics initiative + +**Performance**: 10-13x faster for Equal/NotEqual, 1.5-2x for Same/NotSame +{{% /expand %}} + +#### ⚠️ Behavior Changes + +| Function | Change | Reason | +|----------|--------|--------| +| `EqualValues` | Now fails with function types (like `Equal`) | [#1825] - Consistency and safety | +| `Same`/`NotSame` | Two nil pointers of same type now correctly considered "same" | Edge case fix | + +[#1825]: https://github.com/stretchr/testify/pull/1825 + +### Error + +**New functions**: None + +**Behavior changes**: None + +### File + +| Function | Type | Origin | Description | +|----------|------|--------|-------------| +| `FileEmpty` | Reflection | New addition | Assert file exists and is empty (0 bytes) | +| `FileNotEmpty` | Reflection | New addition | Assert file exists and is not empty | + +**Note**: `DirExists` was already present in upstream, `NoDirExists` renamed into `DirNotExists`. `NoFileExists` renamed into `FileNotExists`.² + +**Behavior changes**: None + +### HTTP + +**New functions**: None + +**Behavior changes**: None + +### JSON + +{{% expand title="Generics" %}} + +#### New Generic Function (1) + +| Function | Type Parameters | Description | +|----------|-----------------|-------------| +| `JSONEqT[S Text]` | String or []byte | Type-safe JSON semantic equality | + +**Performance**: Comparable (JSON parsing dominates) +{{% /expand %}} + +{{% expand title="Reflection-based" %}} + +#### New Reflection Function (1) + +| Function | Origin | Description | +|----------|--------|-------------| +| `JSONEqBytes` | [#1513] | JSON equality for byte slices | + +[#1513]: https://github.com/stretchr/testify/pull/1513 +{{% /expand %}} + +**Behavior changes**: None + +### Number + +{{% expand title="Generics" %}} + +#### New Generic Functions (2) + +| Function | Type Parameters | Description | +|----------|-----------------|-------------| +| `InDeltaT[V Float\|Integer]` | Numeric type | Type-safe float comparison with absolute delta | +| `InEpsilonT[V Float]` | Float type | Type-safe float comparison with relative epsilon | + +**Origin**: Generics initiative + +**Performance**: 1.2-1.5x faster +{{% /expand %}} + +#### ⚠️ Behavior Changes + +- Fixed IEEE 754 edge case handling (NaN, Inf) +- Added support for zero expected value in `InEpsilon` (falls back to absolute error) +- Fixed invalid type conversion for `uintptr` in reflect-based compare + +### Ordering + +{{% expand title="Generics" %}} + +#### New Generic Functions (6) + +| Function | Type Parameters | Description | +|----------|-----------------|-------------| +| `IsIncreasingT[E Ordered]` | Ordered[^1] slice element | Type-safe strictly increasing check | +| `IsDecreasingT[E Ordered]` | Ordered slice element | Type-safe strictly decreasing check | +| `IsNonIncreasingT[E Ordered]` | Ordered slice element | Type-safe non-increasing check (allows equal) | +| `IsNonDecreasingT[E Ordered]` | Ordered slice element | Type-safe non-decreasing check (allows equal) | +| `SortedT[E cmp.Ordered]` | Ordered slice element | Type-safe sorted check (generic-only function) | +| `NotSortedT[E cmp.Ordered]` | Ordered slice element | Type-safe unsorted check (generic-only function) | + +**Origin**: Generics initiative + +**Performance**: 6.5-9.5x faster + +**Note**: `SortedT` and `NotSortedT` are generic-only (no reflection equivalents) +{{% /expand %}} + +#### ⚠️ Behavior Changes + +| Function | Change | Reason | +|----------|--------|--------| +| `IsNonDecreasing` | Logic corrected to match documentation | Inverted logic fixed | +| `IsNonIncreasing` | Logic corrected to match documentation | Inverted logic fixed | + +### Panic + +**New functions**: None + +**Behavior changes**: None + +### String + +{{% expand title="Generics" %}} + +#### New Generic Functions (2) + +| Function | Type Parameters | Description | +|----------|-----------------|-------------| +| `RegexpT[S Text]` | String or []byte | Type-safe regex match check | +| `NotRegexpT[S Text]` | String or []byte | Type-safe regex non-match check | + +**Origin**: Generics initiative + +**Performance**: 1.2x faster (regex compilation dominates) +{{% /expand %}} + +#### ⚠️ Behavior Changes + +| Change | Origin | Description | +|--------|--------|-------------| +| Fix panic on invalid regex | [#1818] | Handle invalid regex patterns gracefully | +| Refactored regex handling | Internal | Fixed quirks with unexpected behavior on some input types | + +[#1818]: https://github.com/stretchr/testify/pull/1818 + +### Testing + +**New functions**: None + +**Behavior changes**: None + +### Time + +**New functions**: None + +#### ⚠️ Behavior Changes + +| Change | Origin | Description | +|--------|--------|-------------| +| Fix time.Time rendering in diffs | [#1829] | Improved time display in failure messages | + +[1829]: https://github.com/stretchr/testify/issues/ + +### Type + +{{% expand title="Generics" %}} + +#### New Generic Functions (2) + +| Function | Type Parameters | Description | +|----------|-----------------|-------------| +| `IsOfTypeT[EType any]` | Expected type | Type assertion without dummy value | +| `IsNotOfTypeT[EType any]` | Expected type | Negative type assertion without dummy value | + +**Origin**: [#1805] +**Performance**: 9-11x faster + +[#1805]: https://github.com/stretchr/testify/issues/1805 + +{{% /expand %}} + +{{% expand title="Reflection-based" %}} +#### New Reflection Functions (2) + +| Function | Origin | Description | +|----------|--------|-------------| +| `Kind` | [#1803] | Assert value is of specific reflect.Kind | +| `NotKind` | [#1803] | Assert value is not of specific reflect.Kind | + +[#1803]: https://github.com/stretchr/testify/pull/1803 +{{% /expand %}} + +**Behavior changes**: None + +### YAML + +{{% expand title="Generics" %}} + +#### New Generic Function (1) + +| Function | Type Parameters | Description | +|----------|-----------------|-------------| +| `YAMLEqT[S Text]` | String or []byte | Type-safe YAML semantic equality | + +**Performance**: Comparable (YAML parsing dominates) +{{% /expand %}} + +{{% expand title="Reflection-based" %}} + +#### New Reflection Function (1) + +| Function | Origin | Description | +|----------|--------|-------------| +| `YAMLEqBytes` | Consistency | YAML equality for byte slices (matches JSONEqBytes) | +{{% /expand %}} + + +#### ⚠️ Behavior Changes + +**Architecture change**: YAML support is now opt-in via `import _ "github.com/go-openapi/testify/v2/enable/yaml"` +**Behavior changes**: None + +## Other changes + +### Performance Improvements + +See [Performance Benchmarks](../../project/maintainers/BENCHMARKS.md) for a detailed presentation. + +#### Generic vs Reflection Performance + +| Domain | Function | Speedup | Key Benefit | +|--------|----------|---------|-------------| +| Collection | ElementsMatchT | **21-81x** | Scales with collection size | +| Equality | EqualT | **10-13x** | Zero allocations | +| Comparison | GreaterT/LessT | **10-22x** | Zero allocations | +| Collection | SliceContainsT | **16x** | Zero allocations | +| Collection | SeqContainsT | **25x** | Iterator optimization | +| Ordering | IsIncreasingT | **7-9x** | Zero allocations | +| Type | IsOfTypeT | **9-11x** | No reflection overhead | + +**Memory savings**: Up to 99% reduction in allocations for large collections + +### Architecture Changes + +These affect the way the project is maintained, but not how it is used. + +#### Code Generation + +All assert and require packages are 100% generated from a single source: +- **Source**: `internal/assertions/` (~5,000 LOC) +- **Generated**: ~600+ functions across assert/require packages +- **Variants**: 8 variants per assertion (assert/require x standard/format/forward/forward+format), + 4 variants for generic assertions (assert/require x standard/format) + +> NOTE: generic assertions obviously can't be propagated as a "forward variant", i.e +> as a method of the `Assertion` object. + +#### Module Structure + +The project adopts a mono-repo structure (with the appropriate changes made in CI). + +This means that the github repo exposes several independant go modules. + +``` +github.com/go-openapi/testify/v2 # Core (zero deps) [go.mod] +├── assert/ # Generated package +├── require/ # Generated package +├── internal/ # Internalized dependencies +│ ├── spew/ # Internalized go-spew +│ ├── difflib/ # Internalized go-difflib +│ └── assertions/ # Single source of truth +├── enable/ # Modules for optional features +│ ├── yaml/ # Optional YAML support [go.mod] +│ └── color/ # Optional colorization [go.mod] +│ +└── codegen/ # Code and documentation generator [go.mod] +``` + +### Documentation + +- Hugo-based documentation site +- Domain-organized API reference (18 domains) +- Comprehensive examples and tutorials +- Performance benchmarks + +## Project Metrics + +| Metric | Value | +|--------|-------| +| **New functions** | 51 (38 generic + 13 reflection) | +| **Total assertions** | 76 base assertions | +| **Generated functions** | ~600 (76 × 8 variants - generics get 4 variants only) | +| **Generic coverage** | 10 domains | +| **Performance improvement** | 1.2x to 81x faster | +| **Dependencies** | 0 external (was 2 requiredl) | +| **Test coverage** | 96% overall, 100% on public APIs | +| **Documentation domains** | 18 logical categories | + diff --git a/docs/doc-site/examples/EXAMPLES.md b/docs/doc-site/usage/EXAMPLES.md similarity index 99% rename from docs/doc-site/examples/EXAMPLES.md rename to docs/doc-site/usage/EXAMPLES.md index 24731d146..db52bdaa0 100644 --- a/docs/doc-site/examples/EXAMPLES.md +++ b/docs/doc-site/usage/EXAMPLES.md @@ -1,13 +1,15 @@ --- title: "Examples" description: "Practical examples for using testify v2" -weight: 1 +weight: 2 --- {{% notice primary "TL;DR" "meteor" %}} > If you've already used `github.com/stretchr/testify`, adopting v2 will be straightforward. {{% /notice %}} +More examples to showcase generic assertions specifically may be found [here](./GENERICS.md). + ## Quick Start The simplest way to get started with testify is using the `assert` package: @@ -545,3 +547,4 @@ func TestNew(t *testing.T) { - Better error messages - shows expected vs actual automatically - Less boilerplate - no manual formatting - More assertions - Contains, ElementsMatch, JSONEq, etc. + diff --git a/docs/doc-site/usage/GENERICS.md b/docs/doc-site/usage/GENERICS.md new file mode 100644 index 000000000..a1f42be95 --- /dev/null +++ b/docs/doc-site/usage/GENERICS.md @@ -0,0 +1,567 @@ +--- +title: Generics +description: Using generic assertions. +weight: 10 +--- + +# Using Generic Assertions + +Testify v2 provides **38 generic assertion functions** that offer compile-time type safety alongside the traditional reflection-based assertions. Generic variants are identified by the `T` suffix (e.g., `EqualT`, `GreaterT`, `ElementsMatchT`). + +{{% notice style="success" title="Type Safety First" icon="check" %}} +Generic assertions catch type mismatches **when writing tests**, not when running them. The performance improvements (1.2x-81x faster) are a bonus on top of this primary benefit. +{{% /notice %}} + +## Quick Start + +Generic assertions work exactly like their reflection-based counterparts, but with compile-time type checking: + +{{< cards >}} +{{% card title="Reflection-based" %}} +```go +import "github.com/go-openapi/testify/v2/assert" + +func TestUser(t *testing.T) { + expected := 42 + actual := getUserAge() + + // Compiles, but type errors appear at runtime + assert.Equal(t, expected, actual) +} +``` +{{% /card %}} + +{{% card title="Generic (Type-safe)" %}} +```go +import "github.com/go-openapi/testify/v2/assert" + +func TestUser(t *testing.T) { + expected := 42 + actual := getUserAge() + + // Compiler checks types immediately + assert.EqualT(t, expected, actual) +} +``` +{{% /card %}} +{{< /cards >}} + +## When to Use Generic Variants + +### ✅ Use Generic Variants (`*T` functions) When: + +1. **Testing with known concrete types** - The most common case + ```go + assert.EqualT(t, 42, result) // int comparison + assert.GreaterT(t, count, 0) // numeric comparison + assert.ElementsMatchT(t, expected, actual) // slice comparison + ``` + +2. **You want refactoring safety** - Compiler catches broken tests immediately + ```go + // If getUserIDs() changes from []int to []string, + // the compiler flags this line immediately + assert.ElementsMatchT(t, expectedIDs, getUserIDs()) + ``` + +3. **IDE assistance matters** - Autocomplete suggests only correctly-typed variables + ```go + // Typing: assert.EqualT(t, expectedUser, actual + // ^ + // IDE suggests: actualUser ✓ (correct type) + // actualOrder ✗ (wrong type - grayed out) + ``` + +4. **Performance-critical tests** - See [benchmarks](../../project/maintainers/BENCHMARKS.md) for 1.2-81x speedups + +### 🔄 Use Reflection Variants (no suffix) When: + +1. **Intentionally comparing different types** - Especially with `EqualValues` + ```go + // Comparing int and int64 for semantic equality + assert.EqualValues(t, int64(42), int32(42)) // ✓ Reflection handles this + assert.EqualT(t, int64(42), int32(42)) // ❌ Compiler error + ``` + +2. **Working with heterogeneous collections** - `[]any` or `interface{}` slices + ```go + mixed := []any{1, "string", true} + assert.Contains(t, mixed, "string") // ✓ Reflection works + ``` + +3. **Dynamic type scenarios** - Where compile-time type is unknown + ```go + var result interface{} = getResult() + assert.Equal(t, expected, result) // ✓ Reflection handles dynamic types + ``` + +4. **Backward compatibility** - Existing test code using reflection-based assertions + +## Type Safety Benefits + +### Catching Refactoring Errors + +Generic assertions act as a safety net during refactoring: + +{{< cards >}} +{{% card title="Without Generics ❌" %}} +```go +// Original code +type UserID int +var userIDs []UserID + +assert.ElementsMatch(t, userIDs, getActiveUsers()) + +// Later: UserID changes to string +type UserID string +var userIDs []UserID + +// Test still compiles! +// Fails mysteriously at runtime or passes with wrong comparison +assert.ElementsMatch(t, userIDs, getActiveUsers()) +``` +{{% /card %}} + +{{% card title="With Generics ✅" %}} +```go +// Original code +type UserID int +var userIDs []UserID + +assert.ElementsMatchT(t, userIDs, getActiveUsers()) + +// Later: UserID changes to string +type UserID string +var userIDs []UserID + +// Compiler immediately flags the error +assert.ElementsMatchT(t, userIDs, getActiveUsers()) +// ❌ Compile error: type mismatch! +``` +{{% /card %}} +{{< /cards >}} + +### Preventing Wrong Comparisons + +Generic assertions force you to think about what you're comparing: + +{{< cards >}} +{{% card title="Pointer vs Value Comparison" %}} +```go +expected := &User{ID: 1, Name: "Alice"} +actual := &User{ID: 1, Name: "Alice"} + +// Reflection: Compares pointer addresses (probably wrong) +assert.Equal(t, expected, actual) // ✗ Fails (different addresses) + +// Generic: Makes the intent explicit +assert.EqualT(t, expected, actual) // Compares pointers +assert.EqualT(t, *expected, *actual) // Compares values ✓ +``` +{{% /card %}} + +{{% card title="Type Confusion Prevention" %}} +```go +userID := 42 +orderID := "ORD-123" + +// Reflection: Compiles, wrong comparison +assert.Equal(t, userID, orderID) // Runtime failure + +// Generic: Compiler catches the mistake +assert.EqualT(t, userID, orderID) // ❌ Compile error! +``` +{{% /card %}} +{{< /cards >}} + +## Available Generic Functions + +Testify v2 provides generic variants across all major domains: + +### Equality (4 functions) +- `EqualT[V comparable]` - Type-safe equality for comparable types +- `NotEqualT[V comparable]` - Type-safe inequality +- `SameT[V comparable]` - Pointer identity check +- `NotSameT[V comparable]` - Different pointer check + +### Comparison (6 functions) +- `GreaterT[V Ordered]` - Type-safe greater-than comparison +- `GreaterOrEqualT[V Ordered]` - Type-safe >= +- `LessT[V Ordered]` - Type-safe less-than comparison +- `LessOrEqualT[V Ordered]` - Type-safe <= +- `PositiveT[V SignedNumeric]` - Assert value > 0 +- `NegativeT[V SignedNumeric]` - Assert value < 0 + +### Collection (12 functions) +- `StringContainsT[S Text]` - String/byte slice contains substring +- `SliceContainsT[E comparable]` - Slice contains element +- `MapContainsT[K comparable, V any]` - Map contains key +- `SeqContainsT[E comparable]` - Iterator contains element (Go 1.23+) +- `ElementsMatchT[E comparable]` - Slices have same elements (any order) +- `SliceSubsetT[E comparable]` - Slice is subset of another +- Plus negative variants: `*NotContainsT`, `NotElementsMatchT`, `SliceNotSubsetT` + +### Ordering (6 functions) +- `IsIncreasingT[E Ordered]` - Slice elements strictly increasing +- `IsDecreasingT[E Ordered]` - Slice elements strictly decreasing +- `IsNonIncreasingT[E Ordered]` - Slice elements non-increasing (allows equal) +- `IsNonDecreasingT[E Ordered]` - Slice elements non-decreasing (allows equal) +- `SortedT[E Ordered]` - Slice is sorted (generic-only function) +- `NotSortedT[E Ordered]` - Slice is not sorted (generic-only function) + +### Numeric (2 functions) +- `InDeltaT[V Measurable]` - Numeric comparison with absolute delta (supports integers and floats) +- `InEpsilonT[V Measurable]` - Numeric comparison with relative epsilon (supports integers and floats) + +### Boolean (2 functions) +- `TrueT[B Boolean]` - Assert boolean is true +- `FalseT[B Boolean]` - Assert boolean is false + +### String (2 functions) +- `RegexpT[S Text]` - String matches regex (string or []byte) +- `NotRegexpT[S Text]` - String doesn't match regex + +### Type (2 functions) +- `IsOfTypeT[EType any]` - Assert value is of type EType (no dummy value needed!) +- `IsNotOfTypeT[EType any]` - Assert value is not of type EType + +### JSON & YAML (2 functions) +- `JSONEqT[S Text]` - JSON strings are semantically equal +- `YAMLEqT[S Text]` - YAML strings are semantically equal + +{{% notice style="info" title="See Complete API" icon="book" %}} +For detailed documentation of all generic functions, see the [API Reference](../../api/_index.md) organized by domain. +{{% /notice %}} + +## Practical Examples + +### Example 1: Collection Testing + +{{< cards >}} +{{% card title="Type-Safe Collection Assertions" %}} +```go +func TestUserPermissions(t *testing.T) { + user := getUser(123) + + expectedPerms := []string{"read", "write"} + actualPerms := user.Permissions + + // Compiler ensures both slices are []string + assert.ElementsMatchT(t, expectedPerms, actualPerms) + + // Check subset relationship + assert.SliceSubsetT(t, []string{"read"}, actualPerms) +} +``` +{{% /card %}} + +{{% card title="Iterator Support (Go 1.23+)" %}} +```go +func TestSequenceContains(t *testing.T) { + // iter.Seq[int] from Go 1.23 + numbers := slices.Values([]int{1, 2, 3, 4, 5}) + + // Type-safe iterator checking + assert.SeqContainsT(t, numbers, 3) + assert.SeqNotContainsT(t, numbers, 99) +} +``` +{{% /card %}} +{{< /cards >}} + +### Example 2: Numeric Comparisons + +{{< cards >}} +{{% card title="Ordered Types" %}} +```go +func TestPricing(t *testing.T) { + price := calculatePrice(item) + discount := calculateDiscount(item) + + // Type-safe numeric comparisons + assert.PositiveT(t, price) + assert.GreaterT(t, price, discount) + assert.LessOrEqualT(t, discount, price) +} +``` +{{% /card %}} + +{{% card title="Float Comparisons" %}} +```go +func TestPhysicsCalculation(t *testing.T) { + result := calculateVelocity(mass, force) + expected := 42.0 + + // Type-safe float comparison with delta + assert.InDeltaT(t, expected, result, 1e-6) + + // Or with epsilon (relative error) + assert.InEpsilonT(t, expected, result, 0.001) +} +``` +{{% /card %}} +{{< /cards >}} + +### Example 3: Type Checking Without Dummy Values + +The `IsOfTypeT` function eliminates the need for dummy values: + +{{< cards >}} +{{% card title="Old Way (Reflection)" %}} +```go +func TestGetUser(t *testing.T) { + result := getUser(123) + + // Need to create a dummy User instance + assert.IsType(t, User{}, result) + + // Or use a pointer dummy + assert.IsType(t, (*User)(nil), result) +} +``` +{{% /card %}} + +{{% card title="New Way (Generic)" %}} +```go +func TestGetUser(t *testing.T) { + result := getUser(123) + + // No dummy value needed! + assert.IsOfTypeT[User](t, result) + + // For pointer types + assert.IsOfTypeT[*User](t, result) +} +``` +{{% /card %}} +{{< /cards >}} + +### Example 4: Sorting and Ordering + +{{< cards >}} +{{% card title="Ordering Checks" %}} +```go +func TestSortedData(t *testing.T) { + timestamps := []int64{ + 1640000000, + 1640000100, + 1640000200, + } + + // Type-safe ordering assertions + assert.IsIncreasingT(t, timestamps) + assert.SortedT(t, timestamps) // Generic-only function +} +``` +{{% /card %}} + +{{% card title="Custom Ordered Types" %}} +```go +type Priority int + +const ( + Low Priority = iota + Medium + High +) + +func TestPriorities(t *testing.T) { + tasks := []Priority{Low, Medium, High} + + // Works with Ordered types (custom types supported) + assert.IsNonDecreasingT(t, tasks) +} +``` +{{% /card %}} +{{< /cards >}} + +## Migration Guide + +### Step 1: Identify High-Value Targets + +Start with the most common assertions that benefit most from type safety: + +```go +// High value: Collection operations (also get big performance wins) +assert.Equal → assert.EqualT +assert.ElementsMatch → assert.ElementsMatchT +assert.Contains → assert.ContainsT (SliceContainsT/MapContainsT/StringContainsT) + +// High value: Comparisons (eliminate allocations) +assert.Greater → assert.GreaterT +assert.Less → assert.LessT +assert.Positive → assert.PositiveT + +// High value: Type checks (cleaner API) +assert.IsType(t, User{}, v) → assert.IsOfTypeT[User](t, v) +``` + +### Step 2: Automated Search & Replace + +Use your IDE or tools to find and replace systematically: + +```bash +# Find all Equal assertions +grep -r "assert\.Equal(" . --include="*_test.go" + +# Find all require.Greater assertions +grep -r "require\.Greater(" . --include="*_test.go" +``` + +### Step 3: Fix Compiler Errors + +The compiler will catch type mismatches. This is a feature, not a bug: + +{{< cards >}} +{{% card title="Compiler Error" %}} +```go +// Original code +assert.EqualT(t, int64(result), count) +// ❌ Error: mismatched types int64 and int +``` +{{% /card %}} + +{{% card title="Fix Option 1: Same Type" %}} +```go +// Convert to same type +assert.EqualT(t, int64(result), int64(count)) +``` +{{% /card %}} + +{{% card title="Fix Option 2: Use Reflection" %}} +```go +// If cross-type comparison is intentional +assert.Equal(t, int64(result), count) +``` +{{% /card %}} +{{< /cards >}} + +### Step 4: Incremental Adoption + +You don't need to migrate everything at once: + +```go +func TestMixedAssertions(t *testing.T) { + // Use generic where types are known + assert.EqualT(t, 42, getAge()) + assert.GreaterT(t, count, 0) + + // Keep reflection for dynamic types + var result interface{} = getResult() + assert.Equal(t, expected, result) + + // Both styles coexist peacefully +} +``` + +## Performance Benefits + +Generic assertions provide significant performance improvements, especially for collection operations: + +| Operation | Speedup | When It Matters | +|-----------|---------|-----------------| +| **ElementsMatchT** | **21-81x faster** | Large collections, hot test paths | +| **EqualT** | **10-13x faster** | Most common assertion | +| **GreaterT/LessT** | **10-22x faster** | Numeric comparisons | +| **SliceContainsT** | **16x faster** | Collection membership tests | + +{{% notice style="success" title="Learn More" icon="chart-line" %}} +See the complete [Performance Benchmarks](../../project/maintainers/BENCHMARKS.md) for detailed analysis and real benchmark results. +{{% /notice %}} + +## Best Practices + +### ✅ Do + +1. **Prefer generic variants by default** - Type safety is always valuable + ```go + assert.EqualT(t, expected, actual) // ✓ Type safe + ``` + +2. **Let the compiler guide you** - Type errors reveal design issues + ```go + // Compiler error reveals you're comparing wrong types + assert.EqualT(t, userID, orderID) // ❌ Good - catches mistake! + ``` + +3. **Use explicit types for clarity** + ```go + assert.IsOfTypeT[*User](t, result) // ✓ Clear intent + ``` + +4. **Leverage performance wins in hot paths** - Generic assertions are faster + ```go + // Table-driven tests with many iterations + for _, tc := range testCases { + assert.EqualT(t, tc.expected, tc.actual) // ✓ Fast + } + ``` + +### ❌ Don't + +1. **Don't force generics for dynamic types** + ```go + var result interface{} = getResult() + assert.Equal(t, expected, result) // ✓ Reflection is fine here + ``` + +2. **Don't use reflection to avoid fixing types** + ```go + // Bad: Using reflection to bypass type safety + assert.Equal(t, expected, actual) // ✗ Defeats the purpose + + // Good: Fix the types or use EqualValues if intentional + assert.EqualT(t, expected, actual) // ✓ Type safe + ``` + +3. **Don't create unnecessary type conversions** + ```go + // Bad: Unnecessary conversion + assert.EqualT(t, int64(42), int64(result)) + + // Good: Work with natural types + assert.EqualT(t, 42, result) + ``` + +## Type Constraints Reference + +Generic assertions use custom type constraints defined in `internal/assertions/generics.go`: + +| Constraint | Definition | Description | Example Types | +|------------|------------|-------------|---------------| +| `comparable` | Go built-in | Types that support `==` and `!=` | `int`, `string`, `bool`, pointers, structs (if all fields are comparable) | +| `Boolean` | `~bool` | Boolean and named bool types | `bool`, `type MyBool bool` | +| `Text` | `~string \| ~[]byte` | String or byte slice types | `string`, `[]byte`, custom string/byte types | +| `Ordered` | `cmp.Ordered \| []byte \| time.Time` | **Extends** `cmp.Ordered` with byte slices and time | Standard ordered types plus `[]byte` and `time.Time` | +| `SignedNumeric` | `~int... \| ~float32 \| ~float64` | Signed integers and floats | `int`, `int8`-`int64`, `float32`, `float64` | +| `UnsignedNumeric` | `~uint...` | Unsigned integers | `uint`, `uint8`-`uint64` | +| `Measurable` | `SignedNumeric \| UnsignedNumeric` | All numeric types (for delta comparisons) | Used by `InDeltaT`/`InEpsilonT` - supports **integers AND floats** | +| `RegExp` | `Text \| *regexp.Regexp` | Regex pattern or compiled regexp | `string`, `[]byte`, `*regexp.Regexp` | + +{{% notice style="primary" title="Key Differences from Standard Go Constraints" icon="info" %}} +- **`Ordered` is extended**: Adds `[]byte` and `time.Time` to `cmp.Ordered` for seamless `bytes.Compare()` and `time.Time.Compare()` support +- **`Measurable` supports integers**: `InDeltaT` and `InEpsilonT` work with both integers and floats, not just floating-point types +- **Custom type support**: All constraints use the `~` operator to support custom types (e.g., `type UserID int`) +{{% /notice %}} + +## Summary + +**Generic assertions in testify v2 provide:** + +✅ **Type Safety**: Catch errors when writing tests, not when running them +✅ **Performance**: 1.2x to 81x faster than reflection-based assertions +✅ **Better IDE Support**: Autocomplete suggests correctly-typed values +✅ **Refactoring Safety**: Compiler catches broken tests immediately +✅ **Zero Downside**: Always as fast or faster than reflection variants + +**Start using generic assertions today** - add the `T` suffix to your existing assertions and let the compiler guide you to better, safer tests. + +--- + +{{% notice style="tip" title="Quick Reference" icon="lightbulb" %}} +- **Generic functions**: Add `T` suffix (e.g., `EqualT`, `GreaterT`, `ElementsMatchT`) +- **Format variants**: Add `Tf` suffix (e.g., `EqualTf`, `GreaterTf`) +- **When to use**: Prefer generics for known concrete types +- **When not to**: Keep reflection for dynamic types and cross-type comparisons +- **Performance**: See [benchmarks](../../project/maintainers/BENCHMARKS.md) for dramatic speedups +{{% /notice %}} diff --git a/docs/doc-site/usage/MIGRATION.md b/docs/doc-site/usage/MIGRATION.md new file mode 100644 index 000000000..134cbcfca --- /dev/null +++ b/docs/doc-site/usage/MIGRATION.md @@ -0,0 +1,87 @@ +--- +title: "Migration Guide" +description: "Migrating from testify/v1" +weight: 20 +--- + +## Migration Guide from stretchr/testify v1 + +### 1. Update Import Path + +```go +// Old +import "github.com/stretchr/testify/assert" +import "github.com/stretchr/testify/require" + +// New +import "github.com/go-openapi/testify/v2/assert" +import "github.com/go-openapi/testify/v2/require" +``` + +### 2. Optional: Enable YAML Support + +If you use `YAMLEq` assertions: + +```go +import _ "github.com/go-openapi/testify/v2/enable/yaml" +``` + +Without this import, YAML assertions will panic with a helpful error message. + +### 3. Optional: Enable Colorized Output + +```go +import _ "github.com/go-openapi/testify/v2/enable/color" +``` + +Use go additional test flags or environment variables: `TESTIFY_COLORIZED=true`, `TESTIFY_THEME=dark|light` + +### 4. Optional: Adopt Generic Assertions + +For better type safety and performance: + +```go +// Reflection-based (still works) +assert.Equal(t, expected, actual) +assert.ElementsMatch(t, slice1, slice2) + +// Generic (type-safe + faster) +assert.EqualT(t, expected, actual) +assert.ElementsMatchT(t, slice1, slice2) +``` + +See [Generics Guide](../GENERICS.md) for complete migration guide. + +### 5. Remove Suite/Mock Usage + +Replace testify suites and mocks with: +- [mockery](https://github.com/vektra/mockery) for mocking +- Standard Go subtests for test organization + +### 6. Remove HTTP Assertion Usage + +If you used `assert.HTTPSuccess`, `assert.HTTPStatusCode`, etc., you'll need to replace with standard HTTP testing or wait for potential reintroduction. + +## Breaking Changes Summary + +### Removed Packages + +- ❌ `suite` - Use standard Go subtests +- ❌ `mock` - Use [mockery](https://github.com/vektra/mockery) +- ❌ `http` - May be reintroduced later + +### Removed Functions + +- ❌ All deprecated functions from v1 removed + +### Behavior Changes + +- `EqualValues` now fails with function types (consistency with `Equal`) +- `IsNonDecreasing`/`IsNonIncreasing` logic corrected (previously inverted) +- Nil pointer identity handling corrected (`Same`/`NotSame`) + +### Dependency Changes + +- ✅ Zero external dependencies +- ✅ YAML support requires opt-in via `enable/yaml` module + diff --git a/docs/doc-site/usage/TRACKING.md b/docs/doc-site/usage/TRACKING.md new file mode 100644 index 000000000..495db5a4a --- /dev/null +++ b/docs/doc-site/usage/TRACKING.md @@ -0,0 +1,114 @@ +--- +title: "Upstream Tracking" +description: "All issues and PRs reviewed for this fork" +weight: 16 +--- + +## Upstream Tracking + +We continue to monitor and selectively adopt changes from upstream: + +### Implemented from Upstream +- ✅ [#1513] - JSONEqBytes +- ✅ [#1803] - Kind/NotKind assertions +- ✅ [#1805] - IsOfTypeT[T] generic assertions +- ✅ [#1685] - Partial iterator support (SeqContainsT variants) +- ✅ [#1828] - Spew panic fixes +- ✅ [#1825], [#1818], [#1223], [#1813], [#1611], [#1822], [#1829] - Various bug fixes + +### Monitoring +- 🔍 [#1087] - Consistently assertion +- 🔍 [#1601] - NoFieldIsZero + +### Superseded by Our Implementation +- ⛔ [#1830] - CollectT.Halt() (superseded by context-based pollCondition) +- ⛔ [#1819] - Handle unexpected exits (superseded by context-based pollCondition) +- ⛔ [#1824] - Spew testing (superseded by property-based fuzzing) + +[#1087]: https://github.com/stretchr/testify/pull/1087 +[#1601]: https://github.com/stretchr/testify/issues/1601 +[#1830]: https://github.com/stretchr/testify/pull/1830 +[#1824]: https://github.com/stretchr/testify/pull/1824 +[#1819]: https://github.com/stretchr/testify/pull/1819 + +**Review frequency**: Quarterly (next review: April 2026) + +--- + +## Appendix: Upstream References + +This table catalogs all upstream PRs and issues from [github.com/stretchr/testify](https://github.com/stretchr/testify) that we have processed. + +### Implemented (Adapted or Merged) + +| Reference | Type | Summary | Outcome in Fork | +|-----------|------|---------|-----------------| +| [#994] | PR | Colorize expected vs actual values | ✅ Adapted into `enable/color` module with themes and configuration | +| [#1223] | PR | Display uint values in decimal instead of hex | ✅ Merged - Applied to diff output | +| [#1232] | PR | Colorized output for expected/actual/errors | ✅ Adapted into `enable/color` module | +| [#1356] | PR | panic(nil) handling for Go 1.21+ | ✅ Merged - Updated panic assertions | +| [#1467] | PR | Colorized output with terminal detection | ✅ Adapted into `enable/color` module (most mature implementation) | +| [#1480] | PR | Colorized diffs via TESTIFY_COLORED_DIFF env var | ✅ Adapted with env var support in `enable/color` | +| [#1513] | PR | JSONEqBytes for byte slice JSON comparison | ✅ Merged - Added to JSON domain | +| [#1685] | PR | Iterator support (`iter.Seq`) for Contains/ElementsMatch | ✅ Partial - Implemented SeqContainsT and SeqNotContainsT only | +| [#1772] | PR | YAML library migration to maintained fork | ✅ Adapted - Used gopkg.in/yaml.v3 in optional `enable/yaml` module | +| [#1797] | PR | Codegen package consolidation and licensing | ✅ Adapted - Complete rewrite of code generation system | +| [#1803] | PR | Kind/NotKind assertions | ✅ Merged - Added to Type domain | +| [#1805] | Issue | Generic `IsOfType[T]()` without dummy value | ✅ Implemented - IsOfTypeT and IsNotOfTypeT in Type domain | +| [#1816] | Issue | Fix panic on unexported struct key in map | ✅ Fixed in internalized go-spew | +| [#1818] | PR | Fix panic on invalid regex in Regexp/NotRegexp | ✅ Merged - Added graceful error handling | +| [#1822] | Issue | Deterministic map ordering in diffs | ✅ Fixed in internalized go-spew | +| [#1825] | PR | Fix panic using EqualValues with uncomparable types | ✅ Merged - Enhanced type safety in EqualValues | +| [#1826] | Issue | Type safety with spew (meta-issue) | ✅ Addressed through comprehensive fuzzing and fixes | +| [#1828] | PR | Fixed panic with unexported fields in maps | ✅ Merged into internalized go-spew | +| [#1829] | Issue | Fix time.Time rendering in diffs | ✅ Fixed in internalized go-spew | +| [#1611] | Issue | Goroutine leak in Eventually/Never | ✅ Fixed by using context.Context (consolidation into single pollCondition function) | +| [#1813] | Issue | Panic with unexported fields | ✅ Fixed via #1828 in internalized spew | + +[#994]: https://github.com/stretchr/testify/pull/994 +[#1356]: https://github.com/stretchr/testify/pull/1356 +[#1772]: https://github.com/stretchr/testify/pull/1772 +[#1797]: https://github.com/stretchr/testify/pull/1797 +[#1816]: https://github.com/stretchr/testify/issues/1816 +[#1826]: https://github.com/stretchr/testify/issues/1826 + +### Superseded by Our Implementation + +| Reference | Type | Summary | Why Superseded | +|-----------|------|---------|----------------| +| [#1819] | PR | Handle unexpected exits in Eventually | Superseded by context-based pollCondition implementation | +| [#1824] | PR | Spew testing improvements | Superseded by property-based fuzzing with random type generator | +| [#1830] | PR | CollectT.Halt() for stopping tests | Superseded by context-based pollCondition implementation | + +[#1819]: https://github.com/stretchr/testify/pull/1819 + +### Under Consideration (Monitoring) + +| Reference | Type | Summary | Status | +|-----------|------|---------|--------| +| [#1087] | PR | Consistently assertion | 🔍 Monitoring - Evaluating usefulness | +| [#1601] | Issue | NoFieldIsZero assertion | 🔍 Monitoring - Considering implementation | + +### Informational (Not Implemented) + +| Reference | Type | Summary | Outcome | +|-----------|------|---------|---------| +| [#1147] | Issue | General discussion about generics adoption | ℹ️ Marked "Not Planned" upstream - We implemented our own generics approach (38 functions) | +| [#1308] | PR | Comprehensive refactor with generic type parameters | ℹ️ Draft for v2.0.0 upstream - We took a different approach with the same objective | + +[#1147]: https://github.com/stretchr/testify/issues/1147 +[#1308]: https://github.com/stretchr/testify/pull/1308 + +### Summary Statistics + +| Category | Count | +|----------|-------| +| **Implemented/Merged** | 21 | +| **Superseded** | 3 | +| **Monitoring** | 2 | +| **Informational** | 2 | +| **Total Processed** | 28 | + +**Note**: This fork maintains an active relationship with upstream, regularly reviewing new PRs and issues. The quarterly review process ensures we stay informed about upstream developments while maintaining our architectural independence. + +--- diff --git a/docs/doc-site/examples/TUTORIAL.md b/docs/doc-site/usage/TUTORIAL.md similarity index 99% rename from docs/doc-site/examples/TUTORIAL.md rename to docs/doc-site/usage/TUTORIAL.md index ace8f93f4..bf54ea4fe 100644 --- a/docs/doc-site/examples/TUTORIAL.md +++ b/docs/doc-site/usage/TUTORIAL.md @@ -1,7 +1,7 @@ --- title: "Tutorial" -description: "How to write great tests with go" -weight: 10 +description: "How to write great tests with go and testify" +weight: 3 --- ## What makes a good test? diff --git a/docs/doc-site/usage/USAGE.md b/docs/doc-site/usage/USAGE.md new file mode 100644 index 000000000..7403f5610 --- /dev/null +++ b/docs/doc-site/usage/USAGE.md @@ -0,0 +1,423 @@ +--- +title: Usage +description: "Introduction Guide" +weight: 1 +--- + +Testify v2 provides **over 40 core assertion types** (76+ functions including inverse variants and all naming styles) organized into clear domains. This guide explains how to navigate the API and use the naming conventions effectively. + +## API Conventions + +Understanding the naming patterns helps you find and use the right assertions quickly. + +### Package Choice: `assert` vs `require` + +{{< cards >}} +{{% card title="assert - Non-Fatal" %}} +**Use when**: Tests should continue after failures to gather more context + +```go +import "github.com/go-openapi/testify/v2/assert" + +func TestUser(t *testing.T) { + user := getUser() + + assert.NotNil(t, user) // ✓ Returns false + assert.Equal(t, "Alice", user.Name) // Still runs + assert.True(t, user.Active) // Still runs +} +``` + +**Returns**: `bool` indicating success/failure +{{% /card %}} + +{{% card title="require - Fatal" %}} +**Use when**: Test cannot continue meaningfully after failure + +```go +import "github.com/go-openapi/testify/v2/require" + +func TestUser(t *testing.T) { + user := getUser() + + require.NotNil(t, user) // ✓ Calls t.FailNow() if fails + require.Equal(t, "Alice", user.Name) // Safe to proceed + require.True(t, user.Active) // user is guaranteed non-nil +} +``` + +**Returns**: Nothing (void) - stops test on failure +{{% /card %}} +{{< /cards >}} + +### Function Variants + +Each assertion comes in multiple variants following consistent patterns: + +| Pattern | Example | Description | +|---------|---------|-------------| +| **Base** | `Equal(t, expected, actual)` | Standard assertion | +| **Format** (`f` suffix) | `Equalf(t, expected, actual, "checking %s", name)` | With custom message | +| **Generic** (`T` suffix) | `EqualT(t, expected, actual)` | Type-safe variant | +| **Generic + Format** (`Tf` suffix) | `EqualTf(t, expected, actual, "checking %s", name)` | Type-safe with message | + +The `f` suffix follows Go's standard library convention (like `Printf`, `Errorf`): it accepts a format string followed by arguments for custom failure messages. + +{{% notice style="tip" title="When to Use Each Variant" icon="lightbulb" %}} +- **Base/Generic**: Use by default - testify provides detailed failure messages +- **Format variants**: Add context when testing similar values in loops or complex scenarios +- **Generic (`T` suffix)**: Prefer for compile-time type safety and better performance +{{% /notice %}} + +One (historical) exception: `EventuallyWithT` is not generic... + +### Inverse Assertions + +Most assertions come with their opposite variant, typically formed by adding a `Not` prefix: + +| Assertion | Inverse | Pattern | +|-----------|---------|---------| +| `Equal` | `NotEqual` | `Not` prefix | +| `Nil` | `NotNil` | `Not` prefix | +| `Empty` | `NotEmpty` | `Not` prefix | +| `Contains` | `NotContains` | `Not` prefix | +| `Zero` | `NotZero` | `Not` prefix | +| `Same` | `NotSame` | `Not` prefix | +| `Panics` | `NotPanics` | `Not` prefix | +| `Regexp` | `NotRegexp` | `Not` prefix | + +**Exceptions:** Some assertions use semantic opposites instead of the `Not` prefix: + +| Assertion | Inverse | Reason | +|-----------|---------|--------| +| `True` | `False` | Semantic opposites (`NotTrue` doesn't sound natural) | +| `Positive` | `Negative` | Semantic opposites, except for 0 which is neither | +| `Greater` | `LessOrEqual` | Comparative opposites (and not `NotGreater`) | +| `GreaterOrEqual` | `Less` | Comparative opposites | + +{{% notice style="info" title="Why Semantic Opposites?" icon="question" %}} +These exceptions follow natural English usage: +- Testing for `False` is clearer than testing for "not true" +- (strictly) `Negative` numbers are semantically opposite to (strictly) `Positive`, unless when `Zero`, and not "not positive" +- `Less` is the natural opposite of `Greater` in comparisons +{{% /notice %}} + +**More semantic opposites:** + +| Assertion | Inverse | Reason | +|-----------|---------|--------| +| `Eventually` | `Never` | Semantic opposites for polling conditions | + +**Not inverses:** Some assertions are independent checks, not inverses of each other: + +| Assertions | Relationship | +|------------|--------------| +| `IsIncreasing` / `IsDecreasing` | Independent checks (a sequence can be neither) | +| `IsNonIncreasing` / `IsNonDecreasing` | Independent checks (a sequence can be neither) | +| `Sorted` / `NotSorted` | True inverse pair using `Not` prefix | + +**Generic variants:** All inverse assertions have corresponding generic variants (suffix `T` or `Tf`): +- `NotEqualT`, `FalseT`, `NegativeT`, `IsDecreasingT`, etc. + +### Argument Order Patterns + +Most assertions follow the **"expected, actual"** pattern, but several categories use different conventions: + +#### Standard Pattern: Expected, Actual + +The majority of assertions check an actual value against an expected value: + +```go +assert.Equal(t, expected, actual) +assert.NotEqual(t, expected, actual) +assert.InDelta(t, expected, actual, delta) +assert.JSONEq(t, expected, actual) +assert.YAMLEq(t, expected, actual) +assert.WithinDuration(t, expected, actual, delta) +assert.Implements(t, (*interface)(nil), object) // Expected interface, actual object +``` + +#### Comparison Operators: e1, e2 + +Comparison assertions express the relationship directly (reads as "assert e1 > e2"): + +```go +assert.Greater(t, e1, e2) // Asserts: e1 > e2 +assert.GreaterOrEqual(t, e1, e2) // Asserts: e1 >= e2 +assert.Less(t, e1, e2) // Asserts: e1 < e2 +assert.LessOrEqual(t, e1, e2) // Asserts: e1 <= e2 +``` + +#### Exceptions using Different Argument Orders + +{{% tabs %}} +{{% tab title="Unary checks" color=green %}} + +**Unary checks** (test a single value): +```go +assert.True(t, value) +assert.False(t, value) +assert.Nil(t, value) +assert.Empty(t, object) +assert.Zero(t, value) +assert.Positive(t, value) +assert.Negative(t, value) +assert.Error(t, err) +assert.NoError(t, err) +assert.Panics(t, panicFunc) +``` +{{% /tab %}} +{{% tab title="Object-first" color=green %}} + +**Object-first pattern** (object under test, then expected property): +```go +assert.Len(t, object, expectedLength) // Object first, expected length second +assert.IsType(t, expectedType, object) // Expected type first, object second +``` +{{% /tab %}} +{{% tab title="Container-first" color=green %}} + +**Container-first pattern** (container, then element/subset): +```go +assert.Contains(t, container, element) // Container first, element second +assert.StringContains(t, str, substring) // String first, substring second +assert.SliceContains(t, slice, element) // Slice first, element second +assert.Subset(t, list, subset) // Superset first, subset second +assert.ElementsMatch(t, listA, listB) // Either order works (symmetric) +``` +{{% /tab %}} +{{% tab title="Error assertions" color=green %}} + +**Error assertions** (error first, then expected property): +```go +assert.ErrorContains(t, err, substring) // Error first, expected substring second +assert.ErrorIs(t, err, target) // Error first, target error second +assert.ErrorAs(t, err, &target) // Error first, target pointer second +assert.EqualError(t, err, expectedString) // Error first, expected message second +``` +{{% /tab %}} +{{% tab title="Special cases" color=green %}} + +**Special cases**: +```go +assert.HTTPSuccess(t, handler, method, url, values) // Handler first, HTTP params follow +assert.Eventually(t, condition, waitFor, tick) // Condition first, timing params follow +``` +{{% /tab %}} +{{% /tabs %}} + +{{% notice style="tip" title="Finding Argument Order" icon="lightbulb" %}} +When unsure about argument order: +- Check the [API Reference](../../api/_index.md) for detailed signatures +- Use IDE autocomplete to see parameter names +- Consult [pkg.go.dev](https://pkg.go.dev/github.com/go-openapi/testify/v2/assert) for complete documentation +{{% /notice %}} + +### Forward Methods (Chaining Style) + +Create an `Assertions` object to reduce repetition in tests with many assertions: + +{{< cards >}} +{{% card title="Package-Level Functions" %}} +```go +func TestUser(t *testing.T) { + user := getUser() + + assert.NotNil(t, user) + assert.Equal(t, "Alice", user.Name) + assert.True(t, user.Active) + assert.Greater(t, user.Age, 0) +} +``` +{{% /card %}} + +{{% card title="Forward Methods" %}} +```go +func TestUser(t *testing.T) { + a := assert.New(t) // Create once + user := getUser() + + a.NotNil(user) // No 't' needed + a.Equal("Alice", user.Name) + a.True(user.Active) +} +``` +{{% /card %}} +{{< /cards >}} + +**Both styles are equivalent** - choose based on your preference and test structure. + +**⚠️ Generic assertions are not available as forward methods** (this is a limitation of go generics). + + +## How the API is Organized + +Assertions are grouped by domain for easier discovery: + +| Domain | Examples | Count | +|--------|----------|-------| +| **Boolean** | `True`, `False` | 2 | +| **Equality** | `Equal`, `NotEqual`, `EqualValues`, `Same`, `Exactly` | 8 | +| **Comparison** | `Greater`, `Less`, `Positive` | 8 | +| **Collection** | `Contains`, `Len`, `Empty`, `ElementsMatch` | 18 | +| **Error** | `Error`, `NoError`, `ErrorIs`, `ErrorAs`, `ErrorContains` | 8 | +| **Type** | `IsType`, `Implements`, `Zero` | 7 | +| **String** | `Regexp`, `NotRegexp` | 4 | +| **Numeric** | `InDelta`, `InEpsilon` | 6 | +| **Ordering** | `IsIncreasing`, `Sorted` | 8 | +| **Panic** | `Panics`, `NotPanics` | 4 | +| **Others** | HTTP, JSON, YAML, Time, File assertions | 12 | + +{{% notice style="info" title="Browse by Domain" icon="book" %}} +See the complete [API Reference](../../api/_index.md) organized by domain for detailed documentation of all assertions. +{{% /notice %}} + +## Navigating the Documentation + +### Quick Reference + +- **[Examples](../examples)** - Practical code examples for common testing scenarios +- **[API Reference](../../api/_index.md)** - Complete assertion catalog organized by domain +- **[Generics Guide](../GENERICS.md)** - Using type-safe assertions with the `T` suffix +- **[Changes](../CHANGES.md)** - All changes since fork from stretchr/testify +- **[pkg.go.dev](https://pkg.go.dev/github.com/go-openapi/testify/v2)** - Generated API reference with full signatures + +### Finding the Right Assertion + +**By task:** +1. Browse the [API Reference](../../api/_index.md) by domain (e.g., "Collection" for slice operations) +2. Check [Examples](../examples) for practical usage patterns +3. Use your IDE's autocomplete - type `assert.` and explore + +**By name:** +- Search in the [API Reference](../../api/_index.md) (use search box) +- Check [pkg.go.dev](https://pkg.go.dev/github.com/go-openapi/testify/v2/assert) for alphabetical listing +- Use your editor's Go to Definition on any assertion + +## Common Usage Patterns + +{{% tabs %}} +{{% tab title="Table-driven tests" color=green %}} + +**Pattern 1: Table-Driven Tests** + +```go +func TestCalculation(t *testing.T) { + tests := slices.Values([]struct { + name string + input int + expected int + }{ + {"positive", 5, 25}, + {"negative", -3, 9}, + {"zero", 0, 0}, + }) + + for tt := range tests { + t.Run(tt.name, func(t *testing.T) { + result := square(tt.input) + assert.Equal(t, tt.expected, result) + }) + } +} +``` +{{% /tab %}} + +{{% tab title="Multiple assertions" color=green %}} + +**Pattern 2: Multiple Assertions (assert for context)** + +```go +func TestUserValidation(t *testing.T) { + user := createUser() + + // Use assert to see all failures + assert.NotEmpty(t, user.Name) // Check name + assert.NotEmpty(t, user.Email) // Check email + assert.Greater(t, user.Age, 0) // Check age + // All assertions run - see complete picture +} +``` +{{% /tab %}} +{{% tab title="Early exit" color=green %}} + +**Pattern 3: Early Exit (use require for prerequisites)** + +```go +func TestDatabaseQuery(t *testing.T) { + db := connectDB() + require.NotNil(t, db) // Stop if no connection + + result := db.Query("SELECT * FROM users") + require.NoError(t, result.Error) // Stop if query fails + + // Safe to proceed - db and result are valid + assert.NotEmpty(t, result.Rows) +} +``` +{{% /tab %}} +{{% tab title="Type-safe" color=green %}} + +**Pattern 4: Type-Safe Generics** + +```go +func TestTypeSafety(t *testing.T) { + expected := []int{1, 2, 3} + actual := getNumbers() + + // Compiler checks types at compile time + assert.ElementsMatchT(t, expected, actual) + assert.GreaterT(t, len(actual), 0) + + // If getNumbers() changes return type, + // compiler catches it immediately +} +``` +{{% /tab %}} +{{% /tabs %}} + +## Getting Started + +1. **Import the package:** + ```go + import "github.com/go-openapi/testify/v2/assert" + // or + import "github.com/go-openapi/testify/v2/require" + ``` + +2. **Choose your style:** + - Package-level: `assert.Equal(t, expected, actual)` + - Forward methods: `a := assert.New(t); a.Equal(expected, actual)` + +3. **Explore by domain:** + - Browse [API Reference](../../api/_index.md) to discover assertions + - Check [Examples](../examples) for practical patterns + +4. **Use generics for type safety:** + - See [Generics Guide](../GENERICS.md) for type-safe assertions + - Add `T` suffix for compile-time type checking + +## Best Practices + +✅ **Do:** +- Use `require` for prerequisites that make subsequent assertions meaningless (or will possibly panic) +- Use `assert` when you want to see all failures in a test +- Prefer generic variants (`*T` functions) for compile-time type safety +- Use format variants (`*f`) to add context in complex scenarios +- Browse by domain in the API reference to discover relevant assertions + +❌ **Don't:** +- Don't mix `assert` and `require` randomly - be intentional +- Don't add unnecessary format messages - testify provides detailed output +- Don't ignore compiler errors from generic variants - they reveal design issues +- Don't forget that both packages provide the same assertions with different behavior + +--- + +{{% notice style="success" title="Ready to Test" icon="check" %}} +**Next Steps:** +- Explore [Examples](../examples) for practical usage patterns +- Browse the [API Reference](../../api/_index.md) to discover assertions +- Read the [Generics Guide](../GENERICS.md) for type-safe testing +- Check [pkg.go.dev](https://pkg.go.dev/github.com/go-openapi/testify/v2) for complete reference +{{% /notice %}} diff --git a/docs/doc-site/usage/_index.md b/docs/doc-site/usage/_index.md new file mode 100644 index 000000000..5825caee8 --- /dev/null +++ b/docs/doc-site/usage/_index.md @@ -0,0 +1,9 @@ +--- +title: Usage +description: Guides, examples and tutorials. +weight: 2 +--- + +Guides, examples and tutorials about `testify/v2`. + +{{< children type="card" description="true" >}}