mirror of
https://github.com/lightningnetwork/lnd.git
synced 2024-11-19 01:43:16 +01:00
679 lines
15 KiB
Go
679 lines
15 KiB
Go
package fn
|
|
|
|
import (
|
|
"fmt"
|
|
"math/rand"
|
|
"slices"
|
|
"testing"
|
|
"testing/quick"
|
|
"time"
|
|
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
func even(a int) bool { return a%2 == 0 }
|
|
func odd(a int) bool { return a%2 != 0 }
|
|
|
|
func TestAll(t *testing.T) {
|
|
x := []int{0, 2, 4, 6, 8}
|
|
require.True(t, All(x, even))
|
|
require.False(t, All(x, odd))
|
|
|
|
y := []int{1, 3, 5, 7, 9}
|
|
require.False(t, All(y, even))
|
|
require.True(t, All(y, odd))
|
|
|
|
z := []int{0, 2, 4, 6, 9}
|
|
require.False(t, All(z, even))
|
|
require.False(t, All(z, odd))
|
|
}
|
|
|
|
func TestAny(t *testing.T) {
|
|
x := []int{1, 3, 5, 7, 9}
|
|
require.False(t, Any(x, even))
|
|
require.True(t, Any(x, odd))
|
|
|
|
y := []int{0, 3, 5, 7, 9}
|
|
require.True(t, Any(y, even))
|
|
require.True(t, Any(y, odd))
|
|
|
|
z := []int{0, 2, 4, 6, 8}
|
|
require.True(t, Any(z, even))
|
|
require.False(t, Any(z, odd))
|
|
}
|
|
|
|
func TestMap(t *testing.T) {
|
|
inc := func(i int) int { return i + 1 }
|
|
|
|
x := []int{0, 2, 4, 6, 8}
|
|
|
|
y := Map(x, inc)
|
|
|
|
z := []int{1, 3, 5, 7, 9}
|
|
|
|
require.True(t, slices.Equal(y, z))
|
|
}
|
|
|
|
func TestFilter(t *testing.T) {
|
|
x := []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
|
|
|
|
y := Filter(x, even)
|
|
|
|
require.True(t, All(y, even))
|
|
|
|
z := Filter(y, odd)
|
|
|
|
require.Zero(t, len(z))
|
|
}
|
|
|
|
func TestFoldl(t *testing.T) {
|
|
seed := []int{}
|
|
stupid := func(s []int, a int) []int { return append(s, a) }
|
|
|
|
x := []int{0, 1, 2, 3, 4}
|
|
|
|
r := Foldl(seed, x, stupid)
|
|
|
|
require.True(t, slices.Equal(x, r))
|
|
}
|
|
|
|
func TestFoldr(t *testing.T) {
|
|
seed := []int{}
|
|
stupid := func(a int, s []int) []int { return append(s, a) }
|
|
|
|
x := []int{0, 1, 2, 3, 4}
|
|
|
|
z := Foldr(seed, x, stupid)
|
|
|
|
slices.Reverse[[]int](x)
|
|
|
|
require.True(t, slices.Equal(x, z))
|
|
}
|
|
|
|
func TestFind(t *testing.T) {
|
|
x := []int{10, 11, 12, 13, 14, 15}
|
|
|
|
div3 := func(a int) bool { return a%3 == 0 }
|
|
div8 := func(a int) bool { return a%8 == 0 }
|
|
|
|
require.Equal(t, Find(x, div3), Some(12))
|
|
|
|
require.Equal(t, Find(x, div8), None[int]())
|
|
}
|
|
|
|
func TestFlatten(t *testing.T) {
|
|
x := [][]int{{0}, {1}, {2}}
|
|
|
|
y := Flatten(x)
|
|
|
|
require.True(t, slices.Equal(y, []int{0, 1, 2}))
|
|
}
|
|
|
|
func TestReplicate(t *testing.T) {
|
|
require.True(t, slices.Equal([]int{1, 1, 1, 1, 1}, Replicate(5, 1)))
|
|
}
|
|
|
|
func TestSpan(t *testing.T) {
|
|
x := []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
|
|
lt5 := func(a int) bool { return a < 5 }
|
|
|
|
low, high := Span(x, lt5)
|
|
|
|
require.True(t, slices.Equal(low, []int{0, 1, 2, 3, 4}))
|
|
require.True(t, slices.Equal(high, []int{5, 6, 7, 8, 9}))
|
|
}
|
|
|
|
func TestSplitAt(t *testing.T) {
|
|
x := []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
|
|
fst, snd := SplitAt(5, x)
|
|
|
|
require.True(t, slices.Equal(fst, []int{0, 1, 2, 3, 4}))
|
|
require.True(t, slices.Equal(snd, []int{5, 6, 7, 8, 9}))
|
|
}
|
|
|
|
func TestZipWith(t *testing.T) {
|
|
eq := func(a, b int) bool { return a == b }
|
|
x := []int{0, 1, 2, 3, 4}
|
|
y := Replicate(5, 1)
|
|
z := ZipWith(x, y, eq)
|
|
require.True(t, slices.Equal(
|
|
z, []bool{false, true, false, false, false},
|
|
))
|
|
}
|
|
|
|
// TestSum checks if the Sum function correctly calculates the sum of the
|
|
// numbers in the slice.
|
|
func TestSum(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
items interface{}
|
|
result interface{}
|
|
}{
|
|
{
|
|
name: "Sum of positive integers",
|
|
items: []int{1, 2, 3},
|
|
result: 6,
|
|
},
|
|
{
|
|
name: "Sum of negative integers",
|
|
items: []int{-1, -2, -3},
|
|
result: -6,
|
|
},
|
|
{
|
|
name: "Sum of float numbers",
|
|
items: []float64{1.1, 2.2, 3.3},
|
|
result: 6.6,
|
|
},
|
|
{
|
|
name: "Sum of complex numbers",
|
|
items: []complex128{
|
|
complex(1, 1),
|
|
complex(2, 2),
|
|
complex(3, 3),
|
|
},
|
|
result: complex(6, 6),
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
switch v := tt.items.(type) {
|
|
case []int:
|
|
require.Equal(t, tt.result, Sum(v))
|
|
case []float64:
|
|
require.Equal(t, tt.result, Sum(v))
|
|
case []complex128:
|
|
require.Equal(t, tt.result, Sum(v))
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
// TestSliceToMap tests the SliceToMap function.
|
|
func TestSliceToMap(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
slice []int
|
|
keyFunc func(int) int
|
|
valueFunc func(int) string
|
|
expected map[int]string
|
|
}{
|
|
{
|
|
name: "Integers to string map",
|
|
slice: []int{1, 2, 3},
|
|
keyFunc: func(a int) int { return a },
|
|
valueFunc: func(a int) string {
|
|
return fmt.Sprintf("Value%d", a)
|
|
},
|
|
expected: map[int]string{
|
|
1: "Value1",
|
|
2: "Value2",
|
|
3: "Value3",
|
|
},
|
|
},
|
|
{
|
|
name: "Duplicates in slice",
|
|
slice: []int{1, 2, 2, 3},
|
|
keyFunc: func(a int) int { return a },
|
|
valueFunc: func(a int) string {
|
|
return fmt.Sprintf("Value%d", a)
|
|
},
|
|
expected: map[int]string{
|
|
1: "Value1",
|
|
2: "Value2",
|
|
3: "Value3",
|
|
},
|
|
},
|
|
{
|
|
name: "Empty slice",
|
|
slice: []int{},
|
|
keyFunc: func(a int) int { return a },
|
|
valueFunc: func(a int) string {
|
|
return fmt.Sprintf("Value%d", a)
|
|
},
|
|
expected: map[int]string{},
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
require.Equal(
|
|
t, tt.expected,
|
|
SliceToMap(tt.slice, tt.keyFunc, tt.valueFunc),
|
|
)
|
|
})
|
|
}
|
|
}
|
|
|
|
// TestHasDuplicates tests the HasDuplicates function.
|
|
func TestHasDuplicates(t *testing.T) {
|
|
// Define test cases.
|
|
testCases := []struct {
|
|
name string
|
|
items []int
|
|
want bool
|
|
}{
|
|
{
|
|
name: "All unique",
|
|
items: []int{1, 2, 3, 4, 5},
|
|
want: false,
|
|
},
|
|
{
|
|
name: "Some duplicates",
|
|
items: []int{1, 2, 2, 3, 4},
|
|
want: true,
|
|
},
|
|
{
|
|
name: "No items",
|
|
items: []int{},
|
|
want: false,
|
|
},
|
|
{
|
|
name: "All duplicates",
|
|
items: []int{1, 1, 1, 1},
|
|
want: true,
|
|
},
|
|
}
|
|
|
|
// Execute each test case.
|
|
for _, tc := range testCases {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
got := HasDuplicates(tc.items)
|
|
|
|
require.Equal(t, tc.want, got)
|
|
})
|
|
}
|
|
}
|
|
|
|
// TestPropForEachConcMapIsomorphism ensures the property that ForEachConc and
|
|
// Map always yield the same results.
|
|
func TestPropForEachConcMapIsomorphism(t *testing.T) {
|
|
f := func(incSize int, s []int) bool {
|
|
inc := func(i int) int { return i + incSize }
|
|
mapped := Map(s, inc)
|
|
conc := ForEachConc(s, inc)
|
|
|
|
return slices.Equal(mapped, conc)
|
|
}
|
|
if err := quick.Check(f, nil); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
|
|
// TestPropForEachConcOutperformsMapWhenExpensive ensures the property that
|
|
// ForEachConc will beat Map in a race in circumstances where the computation in
|
|
// the argument closure is expensive.
|
|
func TestPropForEachConcOutperformsMapWhenExpensive(t *testing.T) {
|
|
f := func(incSize int, s []int) bool {
|
|
if len(s) < 2 {
|
|
// Intuitively we don't expect the extra overhead of
|
|
// ForEachConc to justify itself for list sizes of 1 or
|
|
// 0.
|
|
return true
|
|
}
|
|
|
|
inc := func(i int) int {
|
|
time.Sleep(time.Millisecond)
|
|
return i + incSize
|
|
}
|
|
c := make(chan bool, 1)
|
|
|
|
go func() {
|
|
Map(s, inc)
|
|
select {
|
|
case c <- false:
|
|
default:
|
|
}
|
|
}()
|
|
|
|
go func() {
|
|
ForEachConc(s, inc)
|
|
select {
|
|
case c <- true:
|
|
default:
|
|
}
|
|
}()
|
|
|
|
return <-c
|
|
}
|
|
|
|
if err := quick.Check(f, nil); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
|
|
func TestPropFindIdxFindIdentity(t *testing.T) {
|
|
f := func(div, mod uint8, s []uint8) bool {
|
|
if div == 0 || div == 1 {
|
|
return true
|
|
}
|
|
|
|
pred := func(i uint8) bool {
|
|
return i%div == mod
|
|
}
|
|
|
|
foundIdx := FindIdx(s, pred)
|
|
|
|
// onlyVal :: Option[T2[A, B]] -> Option[B]
|
|
onlyVal := MapOption(func(t2 T2[int, uint8]) uint8 {
|
|
return t2.Second()
|
|
})
|
|
|
|
valuesEqual := Find(s, pred) == onlyVal(foundIdx)
|
|
|
|
idxGetsVal := ElimOption(
|
|
foundIdx,
|
|
func() bool { return true },
|
|
func(t2 T2[int, uint8]) bool {
|
|
return s[t2.First()] == t2.Second()
|
|
})
|
|
|
|
return valuesEqual && idxGetsVal
|
|
}
|
|
|
|
if err := quick.Check(f, nil); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
|
|
func TestPropLastTailIsLast(t *testing.T) {
|
|
f := func(s []uint8) bool {
|
|
// We exclude the singleton case because the Tail is empty.
|
|
if len(s) <= 1 {
|
|
return true
|
|
}
|
|
|
|
return Last(s) == FlatMapOption(Last[uint8])(Tail(s))
|
|
}
|
|
|
|
require.NoError(t, quick.Check(f, nil))
|
|
}
|
|
|
|
func TestPropHeadInitIsHead(t *testing.T) {
|
|
f := func(s []uint8) bool {
|
|
// We exclude the singleton case because the Init is empty.
|
|
if len(s) <= 1 {
|
|
return true
|
|
}
|
|
|
|
return Head(s) == FlatMapOption(Head[uint8])(Init(s))
|
|
}
|
|
|
|
require.NoError(t, quick.Check(f, nil))
|
|
}
|
|
|
|
func TestPropTailDecrementsLength(t *testing.T) {
|
|
f := func(s []uint8) bool {
|
|
if len(s) == 0 {
|
|
return true
|
|
}
|
|
|
|
return Some(Len(s)-1) == MapOption(Len[uint8])(Tail(s))
|
|
}
|
|
|
|
require.NoError(t, quick.Check(f, nil))
|
|
}
|
|
|
|
func TestSingletonTailIsEmpty(t *testing.T) {
|
|
require.Equal(t, Tail([]int{1}), Some([]int{}))
|
|
}
|
|
|
|
func TestSingletonInitIsEmpty(t *testing.T) {
|
|
require.Equal(t, Init([]int{1}), Some([]int{}))
|
|
}
|
|
|
|
// TestPropAlwaysNoneEmptyFilterMap ensures the property that if we were to
|
|
// always return none from our filter function then we would end up with an
|
|
// empty slice.
|
|
func TestPropAlwaysNoneEmptyFilterMap(t *testing.T) {
|
|
f := func(s []int) bool {
|
|
filtered := FilterMap(s, Const[int](None[int]()))
|
|
return len(filtered) == 0
|
|
}
|
|
|
|
require.NoError(t, quick.Check(f, nil))
|
|
}
|
|
|
|
// TestPropFilterMapSomeIdentity ensures that if the filter function is a
|
|
// trivial lift into Option space, then we will get back the original slice.
|
|
func TestPropFilterMapSomeIdentity(t *testing.T) {
|
|
f := func(s []int) bool {
|
|
filtered := FilterMap(s, Some[int])
|
|
return slices.Equal(s, filtered)
|
|
}
|
|
|
|
require.NoError(t, quick.Check(f, nil))
|
|
}
|
|
|
|
// TestPropFilterMapCantGrow ensures that regardless of the filter functions
|
|
// return values, we will never end up with a slice larger than the original.
|
|
func TestPropFilterMapCantGrow(t *testing.T) {
|
|
f := func(s []int) bool {
|
|
filterFunc := func(i int) Option[int] {
|
|
if rand.Int()%2 == 0 {
|
|
return None[int]()
|
|
}
|
|
|
|
return Some(i + rand.Int())
|
|
}
|
|
|
|
return len(FilterMap(s, filterFunc)) <= len(s)
|
|
}
|
|
|
|
require.NoError(t, quick.Check(f, nil))
|
|
}
|
|
|
|
// TestPropFilterMapBisectIdentity ensures that the concatenation of the
|
|
// FilterMaps is the same as the FilterMap of the concatenation.
|
|
func TestPropFilterMapBisectIdentity(t *testing.T) {
|
|
f := func(s []int) bool {
|
|
sz := len(s)
|
|
first := s[0 : sz/2]
|
|
second := s[sz/2 : sz]
|
|
|
|
filterFunc := func(i int) Option[int] {
|
|
if i%2 == 0 {
|
|
return None[int]()
|
|
}
|
|
|
|
return Some(i)
|
|
}
|
|
|
|
firstFiltered := FilterMap(first, filterFunc)
|
|
secondFiltered := FilterMap(second, filterFunc)
|
|
allFiltered := FilterMap(s, filterFunc)
|
|
reassembled := slices.Concat(firstFiltered, secondFiltered)
|
|
|
|
return slices.Equal(allFiltered, reassembled)
|
|
}
|
|
|
|
require.NoError(t, quick.Check(f, nil))
|
|
}
|
|
|
|
// TestTraverseOkIdentity ensures that trivially lifting the elements of a slice
|
|
// via the Ok function during a Traverse is equivalent to just lifting the
|
|
// entire slice via the Ok function.
|
|
func TestPropTraverseOkIdentity(t *testing.T) {
|
|
f := func(s []int) bool {
|
|
traversed := TraverseResult(s, Ok[int])
|
|
|
|
traversedOk := traversed.UnwrapOrFail(t)
|
|
|
|
return slices.Equal(s, traversedOk)
|
|
}
|
|
|
|
require.NoError(t, quick.Check(f, nil))
|
|
}
|
|
|
|
// TestPropTraverseSingleErrEjection ensures that if the traverse function
|
|
// returns even a single error, then the entire Traverse will error.
|
|
func TestPropTraverseSingleErrEjection(t *testing.T) {
|
|
f := func(s []int, errIdx uint8) bool {
|
|
if len(s) == 0 {
|
|
return true
|
|
}
|
|
|
|
errIdxMut := int(errIdx) % len(s)
|
|
f := func(i int) Result[int] {
|
|
if errIdxMut == 0 {
|
|
return Errf[int]("err")
|
|
}
|
|
|
|
errIdxMut--
|
|
|
|
return Ok(i)
|
|
}
|
|
|
|
return TraverseResult(s, f).IsErr()
|
|
}
|
|
|
|
require.NoError(t, quick.Check(f, nil))
|
|
}
|
|
|
|
func TestPropInitDecrementsLength(t *testing.T) {
|
|
f := func(s []uint8) bool {
|
|
if len(s) == 0 {
|
|
return true
|
|
}
|
|
|
|
return Some(Len(s)-1) == MapOption(Len[uint8])(Init(s))
|
|
}
|
|
|
|
require.NoError(t, quick.Check(f, nil))
|
|
}
|
|
|
|
// TestPropTrimNonesEqualsFilterMapIden checks that if we use the Iden
|
|
// function when calling FilterMap on a slice of Options that we get the same
|
|
// result as we would if we called TrimNones on it.
|
|
func TestPropTrimNonesEqualsFilterMapIden(t *testing.T) {
|
|
f := func(s []uint8) bool {
|
|
withNones := make([]Option[uint8], len(s))
|
|
for i, x := range s {
|
|
if x%3 == 0 {
|
|
withNones[i] = None[uint8]()
|
|
} else {
|
|
withNones[i] = Some(x)
|
|
}
|
|
}
|
|
|
|
return slices.Equal(
|
|
FilterMap(withNones, Iden[Option[uint8]]),
|
|
TrimNones(withNones),
|
|
)
|
|
}
|
|
|
|
require.NoError(t, quick.Check(f, nil))
|
|
}
|
|
|
|
// TestPropCollectResultsSingleErrEjection ensures that if there is even a
|
|
// single error in the batch, then CollectResults will return an error.
|
|
func TestPropCollectResultsSingleErrEjection(t *testing.T) {
|
|
f := func(s []int, errIdx uint8) bool {
|
|
if len(s) == 0 {
|
|
return true
|
|
}
|
|
|
|
errIdxMut := int(errIdx) % len(s)
|
|
f := func(i int) Result[int] {
|
|
if errIdxMut == 0 {
|
|
return Errf[int]("err")
|
|
}
|
|
|
|
errIdxMut--
|
|
|
|
return Ok(i)
|
|
}
|
|
|
|
return CollectResults(Map(s, f)).IsErr()
|
|
}
|
|
|
|
require.NoError(t, quick.Check(f, nil))
|
|
}
|
|
|
|
// TestPropCollectResultsNoErrUnwrap ensures that if there are no errors in the
|
|
// results then we end up with unwrapping all of the Results in the slice.
|
|
func TestPropCollectResultsNoErrUnwrap(t *testing.T) {
|
|
f := func(s []int) bool {
|
|
res := CollectResults(Map(s, Ok[int]))
|
|
return !res.isRight && slices.Equal(res.left, s)
|
|
}
|
|
|
|
require.NoError(t, quick.Check(f, nil))
|
|
}
|
|
|
|
// TestPropTraverseSomeIdentity ensures that trivially lifting the elements of a
|
|
// slice via the Some function during a Traverse is equivalent to just lifting
|
|
// the entire slice via the Some function.
|
|
func TestPropTraverseSomeIdentity(t *testing.T) {
|
|
f := func(s []int) bool {
|
|
traversed := TraverseOption(s, Some[int])
|
|
|
|
traversedSome := traversed.UnwrapOrFail(t)
|
|
|
|
return slices.Equal(s, traversedSome)
|
|
}
|
|
|
|
require.NoError(t, quick.Check(f, nil))
|
|
}
|
|
|
|
// TestTraverseSingleNoneEjection ensures that if the traverse function returns
|
|
// even a single None, then the entire Traverse will return None.
|
|
func TestTraverseSingleNoneEjection(t *testing.T) {
|
|
f := func(s []int, errIdx uint8) bool {
|
|
if len(s) == 0 {
|
|
return true
|
|
}
|
|
|
|
errIdxMut := int(errIdx) % len(s)
|
|
f := func(i int) Option[int] {
|
|
if errIdxMut == 0 {
|
|
return None[int]()
|
|
}
|
|
|
|
errIdxMut--
|
|
|
|
return Some(i)
|
|
}
|
|
|
|
return TraverseOption(s, f).IsNone()
|
|
}
|
|
|
|
require.NoError(t, quick.Check(f, nil))
|
|
}
|
|
|
|
// TestPropCollectOptionsSingleNoneEjection ensures that if there is even a
|
|
// single None in the batch, then CollectOptions will return a None.
|
|
func TestPropCollectOptionsSingleNoneEjection(t *testing.T) {
|
|
f := func(s []int, errIdx uint8) bool {
|
|
if len(s) == 0 {
|
|
return true
|
|
}
|
|
|
|
errIdxMut := int(errIdx) % len(s)
|
|
f := func(i int) Option[int] {
|
|
if errIdxMut == 0 {
|
|
return None[int]()
|
|
}
|
|
|
|
errIdxMut--
|
|
|
|
return Some(i)
|
|
}
|
|
|
|
return CollectOptions(Map(s, f)).IsNone()
|
|
}
|
|
|
|
require.NoError(t, quick.Check(f, nil))
|
|
}
|
|
|
|
// TestPropCollectOptionsNoNoneUnwrap ensures that if there are no nones in the
|
|
// options then we end up with unwrapping all of the Options in the slice.
|
|
func TestPropCollectOptionsNoNoneUnwrap(t *testing.T) {
|
|
f := func(s []int) bool {
|
|
res := CollectOptions(Map(s, Some[int]))
|
|
return res.isSome && slices.Equal(res.some, s)
|
|
}
|
|
|
|
require.NoError(t, quick.Check(f, nil))
|
|
}
|