Skip to content

Commit

Permalink
Merge pull request #70 from JosiahWitt/rename-ensurepkg
Browse files Browse the repository at this point in the history
Rename `ensurepkg` to `ensurer` and `ensurepkg.Ensure` to `ensurer.E`
  • Loading branch information
JosiahWitt authored Feb 5, 2023
2 parents 1a31428 + aaeb614 commit 355c722
Show file tree
Hide file tree
Showing 26 changed files with 265 additions and 235 deletions.
8 changes: 5 additions & 3 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,15 +1,17 @@
generate-mocks:
(cd cmd/ensure; go build -o ../../tmp/ensure) && ./tmp/ensure mocks generate
(cd cmd/ensure; make generate-mocks)

test:
go test ./...
(cd cmd/ensure; make test)

test-coverage:
mkdir -p tmp
go test ./... -coverprofile=tmp/ensure-pkg.coverage && go tool cover -html=tmp/ensure-pkg.coverage -o=tmp/coverage.html
go test ./... -coverprofile=tmp/ensure.coverage && go tool cover -html=tmp/ensure.coverage -o=tmp/coverage.html
(cd cmd/ensure; make test-coverage)

lint:
staticcheck ./...
(cd cmd/ensure; staticcheck ./...)
golangci-lint run
(cd cmd/ensure; golangci-lint run)
(cd cmd/ensure; make lint)
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ func TestBasicExample(t *testing.T) {
...

// Methods can be called on ensure, for example, Run:
ensure.Run("my subtest", func(ensure ensurepkg.Ensure) {
ensure.Run("my subtest", func(ensure ensurer.E) {
...

// To ensure a value is correct, use ensure as a function:
Expand Down
3 changes: 2 additions & 1 deletion cmd/ensure/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@ test:

test-coverage:
mkdir -p tmp
go test ./... -coverprofile=tmp/ensure-cmd.coverage && go tool cover -html=tmp/ensure-cmd.coverage -o=tmp/coverage.html
go test ./... -coverprofile=tmp/ensure.coverage && go tool cover -html=tmp/ensure.coverage -o=tmp/coverage.html

lint:
golangci-lint run
staticcheck ./...
1 change: 1 addition & 0 deletions codecov.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,5 +18,6 @@ flags:
package:
paths:
- "*.go"
- "ensurer/**"
- "ensurepkg/**"
- "internal/**"
12 changes: 6 additions & 6 deletions ensure.go
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
// Package ensure is a balanced testing framework for Go 1.14+.
// It supports modern Go 1.13+ error comparisons (via errors.Is), and provides easy to read diffs (via deep.Equal).
//
// Most of the implementation is in the ensurepkg package.
// Most of the implementation is in the ensurer package.
// ensure.New should be used to create an instance of the ensure framework,
// which allows shadowing the "ensure" package (like with the t variable in tests).
// This provides easy test refactoring, while still being able to access the underlying types via the ensurepkg package.
// This provides easy test refactoring, while still being able to access the underlying types via the ensurer package.
//
// For example:
//
Expand All @@ -13,7 +13,7 @@
// ...
//
// // Methods can be called on ensure, for example, Run:
// ensure.Run("my subtest", func(ensure ensurepkg.Ensure) {
// ensure.Run("my subtest", func(ensure ensurer.Ensure) {
// ...
//
// // To ensure a value is correct, use ensure as a function:
Expand All @@ -30,9 +30,9 @@
// }
package ensure

import "github.com/JosiahWitt/ensure/ensurepkg"
import "github.com/JosiahWitt/ensure/ensurer"

// New creates an instance of the ensure test framework using the current testing context.
func New(t ensurepkg.T) ensurepkg.Ensure {
return ensurepkg.InternalCreateDoNotCallDirectly(t)
func New(t ensurer.T) ensurer.E {
return ensurer.InternalCreateDoNotCallDirectly(t)
}
120 changes: 13 additions & 107 deletions ensurepkg/ensurepkg.go
Original file line number Diff line number Diff line change
@@ -1,118 +1,24 @@
// Package ensurepkg contains the implementation for the ensure test framework.
// Use ensure.New to create a new instance of Ensure.
//
// Deprecated: Use the ensurer package instead.
package ensurepkg

import (
"runtime"
"strings"
"testing"

"github.com/JosiahWitt/ensure/internal/testctx"
"github.com/golang/mock/gomock"
)

//nolint:gochecknoglobals // This is stored as a variable so we can override it for tests in init_test.go.
var newTestContext = testctx.New
import "github.com/JosiahWitt/ensure/ensurer"

// T implements a subset of methods on testing.T.
// T implements a subset of methods on [testing.T].
// More methods may be added to T with a minor ensure release.
type T = testctx.T
//
// Deprecated: Use [ensurer.T] instead.
type T = ensurer.T

// Ensure the actual value is correct using Chain.
// Ensure ensures the actual value is correct using [Chain].
// Ensure also has methods that can be called directly.
type Ensure func(actual interface{}) *Chain

// Chain assertions to the ensure function call.
type Chain struct {
t testctx.T
ctx testctx.Context
actual interface{}
wasRun bool
}

// InternalCreateDoNotCallDirectly should NOT be called directly.
// Instead use `ensure := ensure.New(t)` to allow for easy test refactoring.
func InternalCreateDoNotCallDirectly(t T) Ensure {
const validWrapperFilePathSuffix = "/ensure.go"

_, callerFilePath, _, ok := runtime.Caller(1)
if !ok {
t.Helper()
t.Fatalf("Can't get caller from runtime")
}

if !strings.HasSuffix(callerFilePath, validWrapperFilePathSuffix) {
t.Helper()
t.Fatalf("Do not call `ensurepkg.InternalCreateDoNotCallDirectly(t)` directly. Instead use `ensure.New(t)`.")
}

return wrap(t)
}

// New creates an instance of ensure with the provided testing context.
//
// This allows the `ensure` package to be shadowed by the `ensure` variable,
// while still allowing new instances of ensure to be created.
func (e Ensure) New(t T) Ensure {
return wrap(t)
}

// Failf fails the test immediately with a formatted message.
// The formatted message follows the same format as the fmt package.
func (e Ensure) Failf(format string, args ...interface{}) {
c := e(nil)
c.t.Helper()
c.markRun()
c.t.Fatalf(format, args...)
}
// Deprecated: Use [ensurer.E] instead.
type Ensure = ensurer.E

// T exposes the test context provided to ensure.New(t).
// Chain chains assertions to the ensure function call.
//
// If an instance of *testing.T was not provided to ensure.New(t), this method cannot be used.
// The test will fail immediately.
func (e Ensure) T() *testing.T {
c := e(nil)
c.markRun()

t, ok := c.t.(*testing.T)
if !ok {
c.t.Helper()
c.t.Fatalf("An instance of *testing.T was not provided to ensure.New(t), thus T() cannot be used.")
}

return t
}

// GoMockController exposes a GoMock Controller scoped to the current test context.
// Learn more about GoMock here: https://github.com/golang/mock
func (e Ensure) GoMockController() *gomock.Controller {
c := e(nil)
c.markRun()
return c.ctx.GoMockController()
}

func wrap(t T) Ensure {
// Created outside the callback, so the same context is used across ensure calls
ctx := newTestContext(t)

return func(actual interface{}) *Chain {
c := &Chain{
t: t,
ctx: ctx,
actual: actual,
wasRun: false,
}

// Cleanup should never call Fatalf, otherwise panics are hidden, and
// the Fatal message is displayed instead, which is really tricky for debugging.
t.Helper()
t.Cleanup(func() {
if !c.wasRun {
t.Helper()
t.Errorf("Found ensure(<actual>) without chained assertion.")
}
})

return c
}
}
// Deprecated: Use [ensurer.Chain] instead.
type Chain = ensurer.Chain
2 changes: 1 addition & 1 deletion ensurepkg/chain.go → ensurer/chain.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package ensurepkg
package ensurer

import (
"fmt"
Expand Down
2 changes: 1 addition & 1 deletion ensurepkg/chain_error.go → ensurer/chain_error.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package ensurepkg
package ensurer

import (
"errors"
Expand Down
10 changes: 5 additions & 5 deletions ensurepkg/chain_error_test.go → ensurer/chain_error_test.go
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
package ensurepkg_test
package ensurer_test

import (
"errors"
"fmt"
"testing"

"github.com/JosiahWitt/ensure"
"github.com/JosiahWitt/ensure/ensurepkg"
"github.com/JosiahWitt/ensure/ensurer"
"github.com/JosiahWitt/ensure/internal/mocks/mock_testctx"
"github.com/JosiahWitt/erk"
)
Expand All @@ -25,7 +25,7 @@ func TestChainIsError(t *testing.T) {
ensure(val).IsError(err)
})

sharedIsErrorTests(t, func(mockT *mock_testctx.MockT, chain *ensurepkg.Chain, expected error) {
sharedIsErrorTests(t, func(mockT *mock_testctx.MockT, chain *ensurer.Chain, expected error) {
chain.IsError(expected)
})
}
Expand Down Expand Up @@ -94,7 +94,7 @@ func TestChainMatchesAllErrors(t *testing.T) {
})

t.Run("when one expected error", func(t *testing.T) {
sharedIsErrorTests(t, func(mockT *mock_testctx.MockT, chain *ensurepkg.Chain, expected error) {
sharedIsErrorTests(t, func(mockT *mock_testctx.MockT, chain *ensurer.Chain, expected error) {
mockT.EXPECT().Helper()

chain.MatchesAllErrors(expected)
Expand Down Expand Up @@ -343,7 +343,7 @@ func TestChainMatchesAllErrors(t *testing.T) {
})
}

func sharedIsErrorTests(t *testing.T, run func(mockT *mock_testctx.MockT, chain *ensurepkg.Chain, expected error)) {
func sharedIsErrorTests(t *testing.T, run func(mockT *mock_testctx.MockT, chain *ensurer.Chain, expected error)) {
const errorFormat = "\nActual error is not the expected error:\n\tActual: %s\n\tExpected: %s"

t.Run("when equal error by reference", func(t *testing.T) {
Expand Down
2 changes: 1 addition & 1 deletion ensurepkg/chain_test.go → ensurer/chain_test.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package ensurepkg_test
package ensurer_test

import (
"sync"
Expand Down
120 changes: 120 additions & 0 deletions ensurer/ensurer.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
// Package ensurer contains the implementation for the ensure test framework.
//
// It is in a separate package from ensure to allow shadowing the ensure package
// without losing access to the types. Use [ensure.New] to create a new instance of Ensure.
package ensurer

import (
"runtime"
"strings"
"testing"

"github.com/JosiahWitt/ensure/internal/testctx"
"github.com/golang/mock/gomock"
)

//nolint:gochecknoglobals // This is stored as a variable so we can override it for tests in init_test.go.
var newTestContext = testctx.New

// T implements a subset of methods on [testing.T].
// More methods may be added to T with a minor ensure release.
type T = testctx.T

// E ensures the actual value is correct using [Chain].
// E also has methods that can be called directly.
type E func(actual interface{}) *Chain

// Chain chains assertions to the ensure function call.
type Chain struct {
t testctx.T
ctx testctx.Context
actual interface{}
wasRun bool
}

// InternalCreateDoNotCallDirectly should NOT be called directly.
// Instead use [ensure.New] (`ensure := ensure.New(t)`) to allow for easy test refactoring.
func InternalCreateDoNotCallDirectly(t T) E {
const validWrapperFilePathSuffix = "/ensure.go"

_, callerFilePath, _, ok := runtime.Caller(1)
if !ok {
t.Helper()
t.Fatalf("Can't get caller from runtime")
}

if !strings.HasSuffix(callerFilePath, validWrapperFilePathSuffix) {
t.Helper()
t.Fatalf("Do not call `ensurer.InternalCreateDoNotCallDirectly(t)` directly. Instead use `ensure := ensure.New(t)`.")
}

return wrap(t)
}

// New creates an instance of ensure with the provided testing context.
//
// This allows the `ensure` package to be shadowed by the `ensure` variable,
// while still allowing new instances of ensure to be created.
func (e E) New(t T) E {
return wrap(t)
}

// Failf fails the test immediately with a formatted message.
// The formatted message follows the same format as the fmt package.
func (e E) Failf(format string, args ...interface{}) {
c := e(nil)
c.t.Helper()
c.markRun()
c.t.Fatalf(format, args...)
}

// T exposes the scoped [testing.T].
//
// If an instance of *testing.T was not provided to ensure.New(t), this method cannot be used.
// The test will fail immediately.
func (e E) T() *testing.T {
c := e(nil)
c.markRun()

t, ok := c.t.(*testing.T)
if !ok {
c.t.Helper()
c.t.Fatalf("An instance of *testing.T was not provided to ensure.New(t), thus T() cannot be used.")
}

return t
}

// GoMockController exposes a GoMock Controller scoped to the current test context.
// Learn more about GoMock here: https://github.com/golang/mock
func (e E) GoMockController() *gomock.Controller {
c := e(nil)
c.markRun()
return c.ctx.GoMockController()
}

func wrap(t T) E {
// Created outside the callback, so the same context is used across ensure calls
ctx := newTestContext(t)

return func(actual interface{}) *Chain {
c := &Chain{
t: t,
ctx: ctx,
actual: actual,
wasRun: false,
}

// Cleanup should never call Fatalf, otherwise panics are hidden, and
// the Fatal message is displayed instead, which is really tricky for debugging.
t.Helper()
t.Cleanup(func() {
if !c.wasRun {
t.Helper()
t.Errorf("Found ensure(<actual>) without chained assertion.")
}
})

return c
}
}
Loading

0 comments on commit 355c722

Please sign in to comment.