1
0
mirror of https://github.com/IBM/fp-go.git synced 2026-03-12 13:36:56 +02:00

Compare commits

..

4 Commits

Author SHA1 Message Date
Dr. Carsten Leue
8d5dc7ea1f fix: increase test coverage
Signed-off-by: Dr. Carsten Leue <carsten.leue@de.ibm.com>
2026-03-08 23:52:08 +01:00
Dr. Carsten Leue
69a11bc681 fix: increase test coverage
Signed-off-by: Dr. Carsten Leue <carsten.leue@de.ibm.com>
2026-03-08 23:51:44 +01:00
Dr. Carsten Leue
a0910b8279 fix: add -coverpkg=./... to v2
Signed-off-by: Dr. Carsten Leue <carsten.leue@de.ibm.com>
2026-03-08 23:34:17 +01:00
Dr. Carsten Leue
029d7be52d fix: better collection of coverage results
Signed-off-by: Dr. Carsten Leue <carsten.leue@de.ibm.com>
2026-03-08 23:32:14 +01:00
5 changed files with 1452 additions and 2 deletions

View File

@@ -39,7 +39,7 @@ jobs:
- name: Run tests
run: |
go mod tidy
go test -v -race -coverprofile=coverage.txt -covermode=atomic ./...
go test -race -coverprofile=coverage.txt -covermode=atomic -coverpkg=./... ./...
- name: Upload coverage to Coveralls
continue-on-error: true
@@ -79,7 +79,7 @@ jobs:
run: |
cd v2
go mod tidy
go test -v -race -coverprofile=coverage.txt -covermode=atomic ./...
go test -race -coverprofile=coverage.txt -covermode=atomic -coverpkg=./... ./...
- name: Upload coverage to Coveralls
continue-on-error: true

195
v2/iooption/array_test.go Normal file
View File

@@ -0,0 +1,195 @@
// Copyright (c) 2023 - 2025 IBM Corp.
// All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package iooption
import (
"fmt"
"testing"
F "github.com/IBM/fp-go/v2/function"
O "github.com/IBM/fp-go/v2/option"
"github.com/stretchr/testify/assert"
)
func TestTraverseArray_Success(t *testing.T) {
f := func(n int) IOOption[int] {
return Of(n * 2)
}
input := []int{1, 2, 3, 4, 5}
result := TraverseArray(f)(input)()
assert.Equal(t, O.Some([]int{2, 4, 6, 8, 10}), result)
}
func TestTraverseArray_WithNone(t *testing.T) {
f := func(n int) IOOption[int] {
if n > 0 {
return Of(n * 2)
}
return None[int]()
}
input := []int{1, 2, -3, 4}
result := TraverseArray(f)(input)()
assert.Equal(t, O.None[[]int](), result)
}
func TestTraverseArray_EmptyArray(t *testing.T) {
f := func(n int) IOOption[int] {
return Of(n * 2)
}
input := []int{}
result := TraverseArray(f)(input)()
assert.Equal(t, O.Some([]int{}), result)
}
func TestTraverseArrayWithIndex_Success(t *testing.T) {
f := func(idx, n int) IOOption[int] {
return Of(n + idx)
}
input := []int{10, 20, 30}
result := TraverseArrayWithIndex(f)(input)()
assert.Equal(t, O.Some([]int{10, 21, 32}), result)
}
func TestTraverseArrayWithIndex_WithNone(t *testing.T) {
f := func(idx, n int) IOOption[int] {
if idx < 2 {
return Of(n + idx)
}
return None[int]()
}
input := []int{10, 20, 30}
result := TraverseArrayWithIndex(f)(input)()
assert.Equal(t, O.None[[]int](), result)
}
func TestTraverseArrayWithIndex_EmptyArray(t *testing.T) {
f := func(idx, n int) IOOption[int] {
return Of(n + idx)
}
input := []int{}
result := TraverseArrayWithIndex(f)(input)()
assert.Equal(t, O.Some([]int{}), result)
}
func TestSequenceArray_AllSome(t *testing.T) {
input := []IOOption[int]{
Of(1),
Of(2),
Of(3),
}
result := SequenceArray(input)()
assert.Equal(t, O.Some([]int{1, 2, 3}), result)
}
func TestSequenceArray_WithNone(t *testing.T) {
input := []IOOption[int]{
Of(1),
None[int](),
Of(3),
}
result := SequenceArray(input)()
assert.Equal(t, O.None[[]int](), result)
}
func TestSequenceArray_Empty(t *testing.T) {
input := []IOOption[int]{}
result := SequenceArray(input)()
assert.Equal(t, O.Some([]int{}), result)
}
func TestSequenceArray_AllNone(t *testing.T) {
input := []IOOption[int]{
None[int](),
None[int](),
None[int](),
}
result := SequenceArray(input)()
assert.Equal(t, O.None[[]int](), result)
}
func TestTraverseArray_Composition(t *testing.T) {
// Test composing traverse with other operations
f := func(n int) IOOption[int] {
if n%2 == 0 {
return Of(n / 2)
}
return None[int]()
}
input := []int{2, 4, 6, 8}
result := F.Pipe1(
input,
TraverseArray(f),
)()
assert.Equal(t, O.Some([]int{1, 2, 3, 4}), result)
}
func TestTraverseArray_WithMap(t *testing.T) {
// Test traverse followed by map
f := func(n int) IOOption[int] {
return Of(n * 2)
}
input := []int{1, 2, 3}
result := F.Pipe2(
input,
TraverseArray(f),
Map(func(arr []int) int {
sum := 0
for _, v := range arr {
sum += v
}
return sum
}),
)()
assert.Equal(t, O.Some(12), result) // (1*2 + 2*2 + 3*2) = 12
}
func TestTraverseArrayWithIndex_UseIndex(t *testing.T) {
// Test that index is properly used
f := func(idx, n int) IOOption[string] {
return Of(fmt.Sprintf("%d", idx*n*2))
}
input := []int{1, 2, 3}
result := TraverseArrayWithIndex(f)(input)()
assert.Equal(t, O.Some([]string{"0", "4", "12"}), result)
}

View File

@@ -0,0 +1,433 @@
// Copyright (c) 2023 - 2025 IBM Corp.
// All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package iooption
import (
"fmt"
"testing"
"time"
ET "github.com/IBM/fp-go/v2/either"
F "github.com/IBM/fp-go/v2/function"
"github.com/IBM/fp-go/v2/internal/utils"
I "github.com/IBM/fp-go/v2/io"
O "github.com/IBM/fp-go/v2/option"
"github.com/stretchr/testify/assert"
)
func TestOf(t *testing.T) {
result := Of(42)()
assert.Equal(t, O.Some(42), result)
}
func TestSome(t *testing.T) {
result := Some("test")()
assert.Equal(t, O.Some("test"), result)
}
func TestNone(t *testing.T) {
result := None[int]()()
assert.Equal(t, O.None[int](), result)
}
func TestMonadOf(t *testing.T) {
result := MonadOf(100)()
assert.Equal(t, O.Some(100), result)
}
func TestFromOptionComprehensive(t *testing.T) {
t.Run("from Some", func(t *testing.T) {
result := FromOption(O.Some(42))()
assert.Equal(t, O.Some(42), result)
})
t.Run("from None", func(t *testing.T) {
result := FromOption(O.None[int]())()
assert.Equal(t, O.None[int](), result)
})
}
func TestFromIO(t *testing.T) {
ioValue := I.Of(42)
result := FromIO(ioValue)()
assert.Equal(t, O.Some(42), result)
}
func TestMonadMap(t *testing.T) {
t.Run("map over Some", func(t *testing.T) {
result := MonadMap(Of(5), utils.Double)()
assert.Equal(t, O.Some(10), result)
})
t.Run("map over None", func(t *testing.T) {
result := MonadMap(None[int](), utils.Double)()
assert.Equal(t, O.None[int](), result)
})
}
func TestMonadChain(t *testing.T) {
t.Run("chain Some to Some", func(t *testing.T) {
f := func(n int) IOOption[int] {
return Of(n * 2)
}
result := MonadChain(Of(5), f)()
assert.Equal(t, O.Some(10), result)
})
t.Run("chain Some to None", func(t *testing.T) {
f := func(n int) IOOption[int] {
return None[int]()
}
result := MonadChain(Of(5), f)()
assert.Equal(t, O.None[int](), result)
})
t.Run("chain None", func(t *testing.T) {
f := func(n int) IOOption[int] {
return Of(n * 2)
}
result := MonadChain(None[int](), f)()
assert.Equal(t, O.None[int](), result)
})
}
func TestChain(t *testing.T) {
f := func(n int) IOOption[string] {
if n > 0 {
return Of("positive")
}
return None[string]()
}
t.Run("chain positive", func(t *testing.T) {
result := F.Pipe1(Of(5), Chain(f))()
assert.Equal(t, O.Some("positive"), result)
})
t.Run("chain negative", func(t *testing.T) {
result := F.Pipe1(Of(-5), Chain(f))()
assert.Equal(t, O.None[string](), result)
})
}
func TestMonadAp(t *testing.T) {
t.Run("apply Some function to Some value", func(t *testing.T) {
mab := Of(utils.Double)
ma := Of(5)
result := MonadAp(mab, ma)()
assert.Equal(t, O.Some(10), result)
})
t.Run("apply None function", func(t *testing.T) {
mab := None[func(int) int]()
ma := Of(5)
result := MonadAp(mab, ma)()
assert.Equal(t, O.None[int](), result)
})
t.Run("apply to None value", func(t *testing.T) {
mab := Of(utils.Double)
ma := None[int]()
result := MonadAp(mab, ma)()
assert.Equal(t, O.None[int](), result)
})
}
func TestAp(t *testing.T) {
ma := Of(5)
result := F.Pipe1(Of(utils.Double), Ap[int, int](ma))()
assert.Equal(t, O.Some(10), result)
}
func TestApSeq(t *testing.T) {
ma := Of(5)
result := F.Pipe1(Of(utils.Double), ApSeq[int, int](ma))()
assert.Equal(t, O.Some(10), result)
}
func TestApPar(t *testing.T) {
ma := Of(5)
result := F.Pipe1(Of(utils.Double), ApPar[int, int](ma))()
assert.Equal(t, O.Some(10), result)
}
func TestFlatten(t *testing.T) {
t.Run("flatten Some(Some)", func(t *testing.T) {
nested := Of(Of(42))
result := Flatten(nested)()
assert.Equal(t, O.Some(42), result)
})
t.Run("flatten Some(None)", func(t *testing.T) {
nested := Of(None[int]())
result := Flatten(nested)()
assert.Equal(t, O.None[int](), result)
})
t.Run("flatten None", func(t *testing.T) {
nested := None[IOOption[int]]()
result := Flatten(nested)()
assert.Equal(t, O.None[int](), result)
})
}
func TestOptionize0(t *testing.T) {
f := func() (int, bool) {
return 42, true
}
result := Optionize0(f)()()
assert.Equal(t, O.Some(42), result)
f2 := func() (int, bool) {
return 0, false
}
result2 := Optionize0(f2)()()
assert.Equal(t, O.None[int](), result2)
}
func TestOptionize2(t *testing.T) {
f := func(a, b int) (int, bool) {
if b != 0 {
return a / b, true
}
return 0, false
}
result := Optionize2(f)(10, 2)()
assert.Equal(t, O.Some(5), result)
result2 := Optionize2(f)(10, 0)()
assert.Equal(t, O.None[int](), result2)
}
func TestOptionize3(t *testing.T) {
f := func(a, b, c int) (int, bool) {
if c != 0 {
return (a + b) / c, true
}
return 0, false
}
result := Optionize3(f)(10, 5, 3)()
assert.Equal(t, O.Some(5), result)
result2 := Optionize3(f)(10, 5, 0)()
assert.Equal(t, O.None[int](), result2)
}
func TestOptionize4(t *testing.T) {
f := func(a, b, c, d int) (int, bool) {
if d != 0 {
return (a + b + c) / d, true
}
return 0, false
}
result := Optionize4(f)(10, 5, 3, 2)()
assert.Equal(t, O.Some(9), result)
result2 := Optionize4(f)(10, 5, 3, 0)()
assert.Equal(t, O.None[int](), result2)
}
func TestMemoize(t *testing.T) {
callCount := 0
ioOpt := func() Option[int] {
callCount++
return O.Some(42)
}
memoized := Memoize(ioOpt)
// First call
result1 := memoized()
assert.Equal(t, O.Some(42), result1)
assert.Equal(t, 1, callCount)
// Second call should use cached value
result2 := memoized()
assert.Equal(t, O.Some(42), result2)
assert.Equal(t, 1, callCount)
}
func TestFold(t *testing.T) {
onNone := I.Of("none")
onSome := func(n int) I.IO[string] {
return I.Of(fmt.Sprintf("%d", n))
}
t.Run("fold Some", func(t *testing.T) {
result := Fold(onNone, onSome)(Of(42))()
assert.Equal(t, "42", result)
})
t.Run("fold None", func(t *testing.T) {
result := Fold(onNone, onSome)(None[int]())()
assert.Equal(t, "none", result)
})
}
func TestDefer(t *testing.T) {
callCount := 0
gen := func() IOOption[int] {
callCount++
return Of(42)
}
deferred := Defer(gen)
// Each call should invoke the generator
result1 := deferred()
assert.Equal(t, O.Some(42), result1)
assert.Equal(t, 1, callCount)
result2 := deferred()
assert.Equal(t, O.Some(42), result2)
assert.Equal(t, 2, callCount)
}
func TestFromEither(t *testing.T) {
t.Run("from Right", func(t *testing.T) {
either := ET.Right[string](42)
result := FromEither(either)()
assert.Equal(t, O.Some(42), result)
})
t.Run("from Left", func(t *testing.T) {
either := ET.Left[int]("error")
result := FromEither(either)()
assert.Equal(t, O.None[int](), result)
})
}
func TestMonadAlt(t *testing.T) {
t.Run("first is Some", func(t *testing.T) {
result := MonadAlt(Of(1), Of(2))()
assert.Equal(t, O.Some(1), result)
})
t.Run("first is None, second is Some", func(t *testing.T) {
result := MonadAlt(None[int](), Of(2))()
assert.Equal(t, O.Some(2), result)
})
t.Run("both are None", func(t *testing.T) {
result := MonadAlt(None[int](), None[int]())()
assert.Equal(t, O.None[int](), result)
})
}
func TestAlt(t *testing.T) {
t.Run("first is Some", func(t *testing.T) {
result := F.Pipe1(Of(1), Alt(Of(2)))()
assert.Equal(t, O.Some(1), result)
})
t.Run("first is None", func(t *testing.T) {
result := F.Pipe1(None[int](), Alt(Of(2)))()
assert.Equal(t, O.Some(2), result)
})
}
func TestMonadChainFirst(t *testing.T) {
sideEffect := 0
f := func(n int) IOOption[string] {
sideEffect = n * 2
return Of("side effect")
}
result := MonadChainFirst(Of(5), f)()
assert.Equal(t, O.Some(5), result)
assert.Equal(t, 10, sideEffect)
}
func TestChainFirst(t *testing.T) {
sideEffect := 0
f := func(n int) IOOption[string] {
sideEffect = n * 2
return Of("side effect")
}
result := F.Pipe1(Of(5), ChainFirst(f))()
assert.Equal(t, O.Some(5), result)
assert.Equal(t, 10, sideEffect)
}
func TestMonadChainFirstIOK(t *testing.T) {
sideEffect := 0
f := func(n int) I.IO[string] {
return func() string {
sideEffect = n * 2
return "side effect"
}
}
result := MonadChainFirstIOK(Of(5), f)()
assert.Equal(t, O.Some(5), result)
assert.Equal(t, 10, sideEffect)
}
func TestChainFirstIOK(t *testing.T) {
sideEffect := 0
f := func(n int) I.IO[string] {
return func() string {
sideEffect = n * 2
return "side effect"
}
}
result := F.Pipe1(Of(5), ChainFirstIOK(f))()
assert.Equal(t, O.Some(5), result)
assert.Equal(t, 10, sideEffect)
}
func TestDelay(t *testing.T) {
start := time.Now()
delay := 50 * time.Millisecond
result := F.Pipe1(Of(42), Delay[int](delay))()
elapsed := time.Since(start)
assert.Equal(t, O.Some(42), result)
assert.True(t, elapsed >= delay, "Expected delay of at least %v, got %v", delay, elapsed)
}
func TestAfter(t *testing.T) {
timestamp := time.Now().Add(50 * time.Millisecond)
result := F.Pipe1(Of(42), After[int](timestamp))()
assert.Equal(t, O.Some(42), result)
assert.True(t, time.Now().After(timestamp) || time.Now().Equal(timestamp))
}
func TestMonadChainIOK(t *testing.T) {
f := func(n int) I.IO[string] {
return I.Of(fmt.Sprintf("%d", n))
}
t.Run("chain Some", func(t *testing.T) {
result := MonadChainIOK(Of(42), f)()
assert.Equal(t, O.Some("42"), result)
})
t.Run("chain None", func(t *testing.T) {
result := MonadChainIOK(None[int](), f)()
assert.Equal(t, O.None[string](), result)
})
}

241
v2/ioresult/bracket_test.go Normal file
View File

@@ -0,0 +1,241 @@
// Copyright (c) 2023 - 2025 IBM Corp.
// All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package ioresult
import (
"errors"
"testing"
F "github.com/IBM/fp-go/v2/function"
"github.com/IBM/fp-go/v2/result"
"github.com/stretchr/testify/assert"
)
func TestBracket_Success(t *testing.T) {
acquired := false
used := false
released := false
acquire := func() IOResult[int] {
return func() Result[int] {
acquired = true
return result.Of(42)
}
}()
use := func(n int) IOResult[string] {
return func() Result[string] {
used = true
return result.Of("success")
}
}
release := func(n int, res Result[string]) IOResult[F.Void] {
return func() Result[F.Void] {
released = true
return result.Of(F.VOID)
}
}
res := Bracket(acquire, use, release)()
assert.True(t, acquired, "Resource should be acquired")
assert.True(t, used, "Resource should be used")
assert.True(t, released, "Resource should be released")
assert.Equal(t, result.Of("success"), res)
}
func TestBracket_UseFailure(t *testing.T) {
acquired := false
released := false
releaseResult := result.Result[string]{}
acquire := func() IOResult[int] {
return func() Result[int] {
acquired = true
return result.Of(42)
}
}()
useErr := errors.New("use error")
use := func(n int) IOResult[string] {
return func() Result[string] {
return result.Left[string](useErr)
}
}
release := func(n int, res Result[string]) IOResult[F.Void] {
return func() Result[F.Void] {
released = true
releaseResult = res
return result.Of(F.VOID)
}
}
res := Bracket(acquire, use, release)()
assert.True(t, acquired, "Resource should be acquired")
assert.True(t, released, "Resource should be released even on use failure")
assert.Equal(t, result.Left[string](useErr), res)
assert.Equal(t, result.Left[string](useErr), releaseResult)
}
func TestBracket_AcquireFailure(t *testing.T) {
used := false
released := false
acquireErr := errors.New("acquire error")
acquire := func() IOResult[int] {
return func() Result[int] {
return result.Left[int](acquireErr)
}
}()
use := func(n int) IOResult[string] {
return func() Result[string] {
used = true
return result.Of("success")
}
}
release := func(n int, res Result[string]) IOResult[F.Void] {
return func() Result[F.Void] {
released = true
return result.Of(F.VOID)
}
}
res := Bracket(acquire, use, release)()
assert.False(t, used, "Use should not be called if acquire fails")
assert.False(t, released, "Release should not be called if acquire fails")
assert.Equal(t, result.Left[string](acquireErr), res)
}
func TestBracket_ReleaseFailure(t *testing.T) {
acquired := false
used := false
released := false
acquire := func() IOResult[int] {
return func() Result[int] {
acquired = true
return result.Of(42)
}
}()
use := func(n int) IOResult[string] {
return func() Result[string] {
used = true
return result.Of("success")
}
}
releaseErr := errors.New("release error")
release := func(n int, res Result[string]) IOResult[F.Void] {
return func() Result[F.Void] {
released = true
return result.Left[F.Void](releaseErr)
}
}
res := Bracket(acquire, use, release)()
assert.True(t, acquired, "Resource should be acquired")
assert.True(t, used, "Resource should be used")
assert.True(t, released, "Release should be attempted")
// When release fails, the release error is returned
assert.Equal(t, result.Left[string](releaseErr), res)
}
func TestBracket_BothUseAndReleaseFail(t *testing.T) {
acquired := false
released := false
acquire := func() IOResult[int] {
return func() Result[int] {
acquired = true
return result.Of(42)
}
}()
useErr := errors.New("use error")
use := func(n int) IOResult[string] {
return func() Result[string] {
return result.Left[string](useErr)
}
}
releaseErr := errors.New("release error")
release := func(n int, res Result[string]) IOResult[F.Void] {
return func() Result[F.Void] {
released = true
return result.Left[F.Void](releaseErr)
}
}
res := Bracket(acquire, use, release)()
assert.True(t, acquired, "Resource should be acquired")
assert.True(t, released, "Release should be attempted")
// When both fail, the release error is returned
assert.Equal(t, result.Left[string](releaseErr), res)
}
func TestBracket_ResourceValue(t *testing.T) {
// Test that the acquired resource value is passed correctly
var usedValue int
var releasedValue int
acquire := Of(100)
use := func(n int) IOResult[string] {
usedValue = n
return Of("result")
}
release := func(n int, res Result[string]) IOResult[F.Void] {
releasedValue = n
return Of(F.VOID)
}
Bracket(acquire, use, release)()
assert.Equal(t, 100, usedValue, "Use should receive acquired value")
assert.Equal(t, 100, releasedValue, "Release should receive acquired value")
}
func TestBracket_ResultValue(t *testing.T) {
// Test that the use result is passed to release
var releaseReceivedResult Result[string]
acquire := Of(42)
use := func(n int) IOResult[string] {
return Of("test result")
}
release := func(n int, res Result[string]) IOResult[F.Void] {
releaseReceivedResult = res
return Of(F.VOID)
}
Bracket(acquire, use, release)()
assert.Equal(t, result.Of("test result"), releaseReceivedResult)
}

View File

@@ -0,0 +1,581 @@
// Copyright (c) 2023 - 2025 IBM Corp.
// All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package ioresult
import (
"errors"
"fmt"
"testing"
ET "github.com/IBM/fp-go/v2/either"
F "github.com/IBM/fp-go/v2/function"
"github.com/IBM/fp-go/v2/internal/utils"
"github.com/IBM/fp-go/v2/io"
O "github.com/IBM/fp-go/v2/option"
"github.com/IBM/fp-go/v2/result"
"github.com/stretchr/testify/assert"
)
func TestLeft(t *testing.T) {
err := errors.New("test error")
res := Left[int](err)()
assert.Equal(t, result.Left[int](err), res)
}
func TestRight(t *testing.T) {
res := Right(42)()
assert.Equal(t, result.Of(42), res)
}
func TestOf(t *testing.T) {
res := Of(42)()
assert.Equal(t, result.Of(42), res)
}
func TestMonadOf(t *testing.T) {
res := MonadOf(42)()
assert.Equal(t, result.Of(42), res)
}
func TestLeftIO(t *testing.T) {
err := errors.New("test error")
res := LeftIO[int](io.Of(err))()
assert.Equal(t, result.Left[int](err), res)
}
func TestRightIO(t *testing.T) {
res := RightIO(io.Of(42))()
assert.Equal(t, result.Of(42), res)
}
func TestFromEither(t *testing.T) {
t.Run("from Right", func(t *testing.T) {
either := result.Of(42)
res := FromEither(either)()
assert.Equal(t, result.Of(42), res)
})
t.Run("from Left", func(t *testing.T) {
err := errors.New("test error")
either := result.Left[int](err)
res := FromEither(either)()
assert.Equal(t, result.Left[int](err), res)
})
}
func TestFromResult(t *testing.T) {
t.Run("from success", func(t *testing.T) {
res := FromResult(result.Of(42))()
assert.Equal(t, result.Of(42), res)
})
t.Run("from error", func(t *testing.T) {
err := errors.New("test error")
res := FromResult(result.Left[int](err))()
assert.Equal(t, result.Left[int](err), res)
})
}
func TestFromEitherI(t *testing.T) {
t.Run("with nil error", func(t *testing.T) {
res := FromEitherI(42, nil)()
assert.Equal(t, result.Of(42), res)
})
t.Run("with error", func(t *testing.T) {
err := errors.New("test error")
res := FromEitherI(0, err)()
assert.Equal(t, result.Left[int](err), res)
})
}
func TestFromResultI(t *testing.T) {
t.Run("with nil error", func(t *testing.T) {
res := FromResultI(42, nil)()
assert.Equal(t, result.Of(42), res)
})
t.Run("with error", func(t *testing.T) {
err := errors.New("test error")
res := FromResultI(0, err)()
assert.Equal(t, result.Left[int](err), res)
})
}
func TestFromOption_Success(t *testing.T) {
onNone := func() error {
return errors.New("none")
}
t.Run("from Some", func(t *testing.T) {
res := FromOption[int](onNone)(O.Some(42))()
assert.Equal(t, result.Of(42), res)
})
t.Run("from None", func(t *testing.T) {
res := FromOption[int](onNone)(O.None[int]())()
assert.Equal(t, result.Left[int](errors.New("none")), res)
})
}
func TestFromIO(t *testing.T) {
ioValue := io.Of(42)
res := FromIO(ioValue)()
assert.Equal(t, result.Of(42), res)
}
func TestFromLazy(t *testing.T) {
lazy := func() int { return 42 }
res := FromLazy(lazy)()
assert.Equal(t, result.Of(42), res)
}
func TestMonadMap(t *testing.T) {
t.Run("map over Right", func(t *testing.T) {
res := MonadMap(Of(5), utils.Double)()
assert.Equal(t, result.Of(10), res)
})
t.Run("map over Left", func(t *testing.T) {
err := errors.New("test error")
res := MonadMap(Left[int](err), utils.Double)()
assert.Equal(t, result.Left[int](err), res)
})
}
func TestMap_Comprehensive(t *testing.T) {
double := func(n int) int { return n * 2 }
t.Run("map Right", func(t *testing.T) {
res := F.Pipe1(Of(5), Map(double))()
assert.Equal(t, result.Of(10), res)
})
t.Run("map Left", func(t *testing.T) {
err := errors.New("test error")
res := F.Pipe1(Left[int](err), Map(double))()
assert.Equal(t, result.Left[int](err), res)
})
}
func TestMonadMapTo(t *testing.T) {
t.Run("mapTo Right", func(t *testing.T) {
res := MonadMapTo(Of(5), "constant")()
assert.Equal(t, result.Of("constant"), res)
})
t.Run("mapTo Left", func(t *testing.T) {
err := errors.New("test error")
res := MonadMapTo(Left[int](err), "constant")()
assert.Equal(t, result.Left[string](err), res)
})
}
func TestMapTo(t *testing.T) {
res := F.Pipe1(Of(5), MapTo[int]("constant"))()
assert.Equal(t, result.Of("constant"), res)
}
func TestMonadChain(t *testing.T) {
f := func(n int) IOResult[int] {
return Of(n * 2)
}
t.Run("chain Right to Right", func(t *testing.T) {
res := MonadChain(Of(5), f)()
assert.Equal(t, result.Of(10), res)
})
t.Run("chain Right to Left", func(t *testing.T) {
err := errors.New("test error")
f := func(n int) IOResult[int] {
return Left[int](err)
}
res := MonadChain(Of(5), f)()
assert.Equal(t, result.Left[int](err), res)
})
t.Run("chain Left", func(t *testing.T) {
err := errors.New("test error")
res := MonadChain(Left[int](err), f)()
assert.Equal(t, result.Left[int](err), res)
})
}
func TestChain_Comprehensive(t *testing.T) {
f := func(n int) IOResult[string] {
if n > 0 {
return Of(fmt.Sprintf("%d", n))
}
return Left[string](errors.New("negative"))
}
t.Run("chain positive", func(t *testing.T) {
res := F.Pipe1(Of(5), Chain(f))()
assert.Equal(t, result.Of("5"), res)
})
t.Run("chain negative", func(t *testing.T) {
res := F.Pipe1(Of(-5), Chain(f))()
assert.Equal(t, result.Left[string](errors.New("negative")), res)
})
}
func TestMonadChainEitherK(t *testing.T) {
f := func(n int) result.Result[int] {
if n > 0 {
return result.Of(n * 2)
}
return result.Left[int](errors.New("non-positive"))
}
t.Run("chain to success", func(t *testing.T) {
res := MonadChainEitherK(Of(5), f)()
assert.Equal(t, result.Of(10), res)
})
t.Run("chain to error", func(t *testing.T) {
res := MonadChainEitherK(Of(-5), f)()
assert.Equal(t, result.Left[int](errors.New("non-positive")), res)
})
}
func TestMonadChainResultK(t *testing.T) {
f := func(n int) result.Result[int] {
return result.Of(n * 2)
}
res := MonadChainResultK(Of(5), f)()
assert.Equal(t, result.Of(10), res)
}
func TestChainResultK(t *testing.T) {
f := func(n int) result.Result[int] {
return result.Of(n * 2)
}
res := F.Pipe1(Of(5), ChainResultK(f))()
assert.Equal(t, result.Of(10), res)
}
func TestMonadAp_Comprehensive(t *testing.T) {
t.Run("apply Right function to Right value", func(t *testing.T) {
mab := Of(utils.Double)
ma := Of(5)
res := MonadAp(mab, ma)()
assert.Equal(t, result.Of(10), res)
})
t.Run("apply Left function", func(t *testing.T) {
err := errors.New("function error")
mab := Left[func(int) int](err)
ma := Of(5)
res := MonadAp(mab, ma)()
assert.Equal(t, result.Left[int](err), res)
})
t.Run("apply to Left value", func(t *testing.T) {
err := errors.New("value error")
mab := Of(utils.Double)
ma := Left[int](err)
res := MonadAp(mab, ma)()
assert.Equal(t, result.Left[int](err), res)
})
}
func TestAp_Comprehensive(t *testing.T) {
ma := Of(5)
res := F.Pipe1(Of(utils.Double), Ap[int, int](ma))()
assert.Equal(t, result.Of(10), res)
}
func TestApPar(t *testing.T) {
ma := Of(5)
res := F.Pipe1(Of(utils.Double), ApPar[int, int](ma))()
assert.Equal(t, result.Of(10), res)
}
func TestApSeq(t *testing.T) {
ma := Of(5)
res := F.Pipe1(Of(utils.Double), ApSeq[int, int](ma))()
assert.Equal(t, result.Of(10), res)
}
func TestFlatten_Comprehensive(t *testing.T) {
t.Run("flatten Right(Right)", func(t *testing.T) {
nested := Of(Of(42))
res := Flatten(nested)()
assert.Equal(t, result.Of(42), res)
})
t.Run("flatten Right(Left)", func(t *testing.T) {
err := errors.New("inner error")
nested := Of(Left[int](err))
res := Flatten(nested)()
assert.Equal(t, result.Left[int](err), res)
})
t.Run("flatten Left", func(t *testing.T) {
err := errors.New("outer error")
nested := Left[IOResult[int]](err)
res := Flatten(nested)()
assert.Equal(t, result.Left[int](err), res)
})
}
func TestTryCatch(t *testing.T) {
t.Run("successful function", func(t *testing.T) {
f := func() (int, error) {
return 42, nil
}
res := TryCatch(f, F.Identity[error])()
assert.Equal(t, result.Of(42), res)
})
t.Run("failing function", func(t *testing.T) {
err := errors.New("test error")
f := func() (int, error) {
return 0, err
}
res := TryCatch(f, F.Identity[error])()
assert.Equal(t, result.Left[int](err), res)
})
t.Run("with error transformation", func(t *testing.T) {
err := errors.New("original")
f := func() (int, error) {
return 0, err
}
onThrow := func(e error) error {
return fmt.Errorf("wrapped: %w", e)
}
res := TryCatch(f, onThrow)()
assert.True(t, result.IsLeft(res))
})
}
func TestTryCatchError_Comprehensive(t *testing.T) {
t.Run("successful function", func(t *testing.T) {
f := func() (int, error) {
return 42, nil
}
res := TryCatchError(f)()
assert.Equal(t, result.Of(42), res)
})
t.Run("failing function", func(t *testing.T) {
err := errors.New("test error")
f := func() (int, error) {
return 0, err
}
res := TryCatchError(f)()
assert.Equal(t, result.Left[int](err), res)
})
}
func TestMemoize_Comprehensive(t *testing.T) {
callCount := 0
ioRes := func() Result[int] {
callCount++
return result.Of(42)
}
memoized := Memoize(ioRes)
// First call
res1 := memoized()
assert.Equal(t, result.Of(42), res1)
assert.Equal(t, 1, callCount)
// Second call should use cached value
res2 := memoized()
assert.Equal(t, result.Of(42), res2)
assert.Equal(t, 1, callCount)
}
func TestMonadMapLeft(t *testing.T) {
t.Run("map Left error", func(t *testing.T) {
err := errors.New("original")
f := func(e error) string {
return e.Error()
}
res := MonadMapLeft(Left[int](err), f)()
// Result is IOEither[string, int], check it's a left
assert.True(t, ET.IsLeft(res))
})
t.Run("map Right unchanged", func(t *testing.T) {
f := func(e error) string {
return e.Error()
}
res := MonadMapLeft(Of(42), f)()
// MapLeft changes the error type, so result is IOEither[string, int]
assert.True(t, ET.IsRight(res))
assert.Equal(t, 42, ET.MonadFold(res, func(string) int { return 0 }, F.Identity[int]))
})
}
func TestMapLeft_Comprehensive(t *testing.T) {
f := func(e error) string {
return fmt.Sprintf("wrapped: %s", e.Error())
}
t.Run("map Left", func(t *testing.T) {
err := errors.New("original")
res := F.Pipe1(Left[int](err), MapLeft[int](f))()
// Result is IOEither[string, int], check it's a left
assert.True(t, ET.IsLeft(res))
})
t.Run("map Right unchanged", func(t *testing.T) {
res := F.Pipe1(Of(42), MapLeft[int](f))()
// MapLeft changes the error type, so result is IOEither[string, int]
assert.True(t, ET.IsRight(res))
assert.Equal(t, 42, ET.MonadFold(res, func(string) int { return 0 }, F.Identity[int]))
})
}
func TestMonadBiMap(t *testing.T) {
leftF := func(e error) string {
return e.Error()
}
rightF := func(n int) string {
return fmt.Sprintf("%d", n)
}
t.Run("bimap Right", func(t *testing.T) {
res := MonadBiMap(Of(42), leftF, rightF)()
// BiMap changes both types, so result is IOEither[string, string]
assert.True(t, ET.IsRight(res))
assert.Equal(t, "42", ET.MonadFold(res, F.Identity[string], F.Identity[string]))
})
t.Run("bimap Left", func(t *testing.T) {
err := errors.New("test")
res := MonadBiMap(Left[int](err), leftF, rightF)()
// Result is IOEither[string, string], check it's a left
assert.True(t, ET.IsLeft(res))
})
}
func TestBiMap_Comprehensive(t *testing.T) {
leftF := func(e error) string {
return e.Error()
}
rightF := func(n int) string {
return fmt.Sprintf("%d", n)
}
t.Run("bimap Right", func(t *testing.T) {
res := F.Pipe1(Of(42), BiMap(leftF, rightF))()
// BiMap changes both types, so result is IOEither[string, string]
assert.True(t, ET.IsRight(res))
assert.Equal(t, "42", ET.MonadFold(res, F.Identity[string], F.Identity[string]))
})
t.Run("bimap Left", func(t *testing.T) {
err := errors.New("test")
res := F.Pipe1(Left[int](err), BiMap(leftF, rightF))()
// Result is IOEither[string, string], check it's a left
assert.True(t, ET.IsLeft(res))
})
}
func TestFold_Comprehensive(t *testing.T) {
onLeft := func(e error) io.IO[string] {
return io.Of(fmt.Sprintf("error: %s", e.Error()))
}
onRight := func(n int) io.IO[string] {
return io.Of(fmt.Sprintf("value: %d", n))
}
t.Run("fold Right", func(t *testing.T) {
res := Fold(onLeft, onRight)(Of(42))()
assert.Equal(t, "value: 42", res)
})
t.Run("fold Left", func(t *testing.T) {
err := errors.New("test")
res := Fold(onLeft, onRight)(Left[int](err))()
assert.Equal(t, "error: test", res)
})
}
func TestGetOrElse_Comprehensive(t *testing.T) {
onLeft := func(e error) io.IO[int] {
return io.Of(0)
}
t.Run("get Right value", func(t *testing.T) {
res := GetOrElse(onLeft)(Of(42))()
assert.Equal(t, 42, res)
})
t.Run("get default on Left", func(t *testing.T) {
err := errors.New("test")
res := GetOrElse(onLeft)(Left[int](err))()
assert.Equal(t, 0, res)
})
}
func TestGetOrElseOf(t *testing.T) {
onLeft := func(e error) int {
return 0
}
t.Run("get Right value", func(t *testing.T) {
res := GetOrElseOf(onLeft)(Of(42))()
assert.Equal(t, 42, res)
})
t.Run("get default on Left", func(t *testing.T) {
err := errors.New("test")
res := GetOrElseOf(onLeft)(Left[int](err))()
assert.Equal(t, 0, res)
})
}
func TestMonadChainTo(t *testing.T) {
t.Run("chain Right to Right", func(t *testing.T) {
res := MonadChainTo(Of(1), Of(2))()
assert.Equal(t, result.Of(2), res)
})
t.Run("chain Right to Left", func(t *testing.T) {
err := errors.New("test")
res := MonadChainTo(Of(1), Left[int](err))()
assert.Equal(t, result.Left[int](err), res)
})
t.Run("chain Left", func(t *testing.T) {
err := errors.New("test")
res := MonadChainTo(Left[int](err), Of(2))()
assert.Equal(t, result.Left[int](err), res)
})
}
func TestChainLazyK(t *testing.T) {
f := func(n int) Lazy[string] {
return func() string {
return fmt.Sprintf("%d", n)
}
}
res := F.Pipe1(Of(42), ChainLazyK(f))()
assert.Equal(t, result.Of("42"), res)
}