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

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion fn/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ module github.com/lightningnetwork/lnd/fn
go 1.19

require (
github.com/stretchr/testify v1.8.1
github.com/stretchr/testify v1.9.0
golang.org/x/exp v0.0.0-20231226003508-02704c960a9b
golang.org/x/sync v0.7.0
)
Expand Down
11 changes: 2 additions & 9 deletions fn/go.sum
Original file line number Diff line number Diff line change
@@ -1,21 +1,14 @@
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
golang.org/x/exp v0.0.0-20231226003508-02704c960a9b h1:kLiC65FbiHWFAOu+lxwNPujcsl8VYyTYYEZnsOO1WK4=
golang.org/x/exp v0.0.0-20231226003508-02704c960a9b/go.mod h1:iRJReGqOEeBhDZGkGbynYwcHlctCvnjTYIamk7uXpHI=
golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M=
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
14 changes: 9 additions & 5 deletions fn/option.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
package fn

import "testing"

// Option[A] represents a value which may or may not be there. This is very
// often preferable to nil-able pointers.
type Option[A any] struct {
Expand Down Expand Up @@ -58,14 +56,20 @@ func (o Option[A]) UnwrapOrFunc(f func() A) A {

// UnwrapOrFail is used to extract a value from an option within a test
// context. If the option is None, then the test fails.
func (o Option[A]) UnwrapOrFail(t *testing.T) A {
t.Helper()
func (o Option[A]) UnwrapOrFail(t TestingT) A {
if helper, ok := t.(TestingHelper); ok {
helper.Helper()
}

if o.isSome {
return o.some
}

t.Fatalf("Option[%T] was None()", o.some)
t.Errorf("Option[%T] was None()", o.some)

if failer, ok := t.(TestingFailer); ok {
failer.FailNow()
}

var zero A
return zero
Expand Down
40 changes: 40 additions & 0 deletions fn/option_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package fn

import (
"testing"
"time"

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)

func TestOption(t *testing.T) {
t.Run("UnwrapOrFail", func(t *testing.T) {
// Test Some case with real t.
opt := Some(123)
n := opt.UnwrapOrFail(t)
require.Equal(t, 123, n)

// Test Some case with mock t.
mockT := &testingMock{}
n = opt.UnwrapOrFail(mockT)
require.Equal(t, 123, n)
require.True(t, mockT.helperCalled)
require.False(t, mockT.errorfCalled)
require.False(t, mockT.failNowCalled)

// Make sure it works with assert.CollectT.
require.EventuallyWithT(t, func(t *assert.CollectT) {
n = opt.UnwrapOrFail(t)
require.Equal(t, 123, n)
}, time.Second, time.Millisecond)

// Test None case with mock t.
opt = None[int]()
mockT = &testingMock{}
_ = opt.UnwrapOrFail(mockT)
require.True(t, mockT.helperCalled)
require.True(t, mockT.errorfCalled)
require.True(t, mockT.failNowCalled)
})
}
17 changes: 10 additions & 7 deletions fn/result.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ package fn

import (
"fmt"
"testing"
)

// Result represents a value that can either be a success (T) or an error.
Expand Down Expand Up @@ -111,16 +110,20 @@ func (r Result[T]) UnwrapOrElse(f func() T) T {
}

// UnwrapOrFail returns the success value or fails the test if it's an error.
func (r Result[T]) UnwrapOrFail(t *testing.T) T {
t.Helper()
func (r Result[T]) UnwrapOrFail(t TestingT) T {
if helper, ok := t.(TestingHelper); ok {
helper.Helper()
}

if r.IsErr() {
t.Fatalf("Result[%T] contained error: %v", r.left, r.right)
}
t.Errorf("Result[%T] contained error: %v", r.left, r.right)

var zero T
if failer, ok := t.(TestingFailer); ok {
failer.FailNow()
}
}

return zero
return r.left
}

// FlatMap applies a function that returns a Result to the success value if it
Expand Down
41 changes: 41 additions & 0 deletions fn/result_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package fn

import (
"errors"
"testing"
"time"

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)

func TestResult(t *testing.T) {
t.Run("UnwrapOrFail", func(t *testing.T) {
// Test Ok case with real t.
opt := Ok(123)
n := opt.UnwrapOrFail(t)
require.Equal(t, 123, n)

// Test Ok case with mock t.
mockT := &testingMock{}
n = opt.UnwrapOrFail(mockT)
require.Equal(t, 123, n)
require.True(t, mockT.helperCalled)
require.False(t, mockT.errorfCalled)
require.False(t, mockT.failNowCalled)

// Make sure it works with assert.CollectT.
require.EventuallyWithT(t, func(t *assert.CollectT) {
n = opt.UnwrapOrFail(t)
require.Equal(t, 123, n)
}, time.Second, time.Millisecond)

// Test None case with mock t.
opt = Err[int](errors.New("test error"))
mockT = &testingMock{}
_ = opt.UnwrapOrFail(mockT)
require.True(t, mockT.helperCalled)
require.True(t, mockT.errorfCalled)
require.True(t, mockT.failNowCalled)
})
}
32 changes: 32 additions & 0 deletions fn/testing.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package fn

// TestingT abstracts *testing.T, *testing.B, assert.TestingT etc types
// that are passed to testing functions. It has only the methods needed
// by Option.UnwrapOrFail and Result.UnwrapOrFail.
type TestingT interface {
// Errorf formats its arguments according to the format, analogous to
// Printf, and records the text in the error log, then marks the
// function as having failed.
Errorf(format string, args ...any)
}

// TestingHelper abstracts *testing.T, *testing.B etc types that are passed to
// testing functions. It has only Helper method.
type TestingHelper interface {
// Helper marks the calling function as a test helper function. When
// printing file and line information, that function will be skipped.
// Helper may be called simultaneously from multiple goroutines.
Helper()
}

// TestingFailer abstracts *testing.T, *testing.B etc types that are passed to
// testing functions. It has only FailNow method.
type TestingFailer interface {
// FailNow marks the function as having failed and stops its execution
// by calling runtime.Goexit (which then runs all deferred calls in the
// current goroutine). Execution will continue at the next test or
// benchmark. FailNow must be called from the goroutine running the test
// or benchmark function, not from other goroutines created during the
// test. Calling FailNow does not stop those other goroutines.
FailNow()
}
25 changes: 25 additions & 0 deletions fn/testing_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package fn

// testingMock is a mock used in tests of UnwrapOrFail methods of Option and
// Result. It implements TestingT, TestingHelper, and TestingFailer and just
// records if a method was called.
type testingMock struct {
errorfCalled bool
helperCalled bool
failNowCalled bool
}

// Errorf records the fact that the method was called.
func (t *testingMock) Errorf(format string, args ...any) {
t.errorfCalled = true
}

// Helper records the fact that the method was called.
func (t *testingMock) Helper() {
t.helperCalled = true
}

// FailNow records the fact that the method was called.
func (t *testingMock) FailNow() {
t.failNowCalled = true
}