mirror of
https://github.com/IBM/fp-go.git
synced 2025-11-23 22:14:53 +02:00
330 lines
8.2 KiB
Go
330 lines
8.2 KiB
Go
// Copyright (c) 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 option
|
|
|
|
import (
|
|
"fmt"
|
|
"slices"
|
|
"strconv"
|
|
"testing"
|
|
|
|
F "github.com/IBM/fp-go/v2/function"
|
|
"github.com/stretchr/testify/assert"
|
|
)
|
|
|
|
// Helper function to create a sequence from a slice
|
|
func seqFromSlice[T any](items []T) Seq[T] {
|
|
return slices.Values(items)
|
|
}
|
|
|
|
// Helper function to collect a sequence into a slice
|
|
func collectSeq[T any](seq Seq[T]) []T {
|
|
return slices.Collect(seq)
|
|
}
|
|
|
|
func TestTraverseIter_AllSome(t *testing.T) {
|
|
// Test case where all transformations succeed
|
|
parse := func(s string) Option[int] {
|
|
n, err := strconv.Atoi(s)
|
|
if err != nil {
|
|
return None[int]()
|
|
}
|
|
return Some(n)
|
|
}
|
|
|
|
input := seqFromSlice([]string{"1", "2", "3", "4", "5"})
|
|
result := TraverseIter(parse)(input)
|
|
|
|
assert.True(t, IsSome(result), "Expected Some result when all transformations succeed")
|
|
|
|
collected := MonadFold(result, func() []int { return nil }, collectSeq[int])
|
|
expected := []int{1, 2, 3, 4, 5}
|
|
assert.Equal(t, expected, collected)
|
|
}
|
|
|
|
func TestTraverseIter_ContainsNone(t *testing.T) {
|
|
// Test case where one transformation fails
|
|
parse := func(s string) Option[int] {
|
|
n, err := strconv.Atoi(s)
|
|
if err != nil {
|
|
return None[int]()
|
|
}
|
|
return Some(n)
|
|
}
|
|
|
|
input := seqFromSlice([]string{"1", "invalid", "3"})
|
|
result := TraverseIter(parse)(input)
|
|
|
|
assert.True(t, IsNone(result), "Expected None when any transformation fails")
|
|
}
|
|
|
|
func TestTraverseIter_EmptySequence(t *testing.T) {
|
|
// Test with empty sequence
|
|
double := func(x int) Option[int] {
|
|
return Some(x * 2)
|
|
}
|
|
|
|
input := seqFromSlice([]int{})
|
|
result := TraverseIter(double)(input)
|
|
|
|
assert.True(t, IsSome(result), "Expected Some for empty sequence")
|
|
|
|
collected := MonadFold(result, func() []int { return nil }, collectSeq[int])
|
|
assert.Empty(t, collected)
|
|
}
|
|
|
|
func TestTraverseIter_SingleElement(t *testing.T) {
|
|
// Test with single element - success case
|
|
validate := func(x int) Option[int] {
|
|
if x > 0 {
|
|
return Some(x * 2)
|
|
}
|
|
return None[int]()
|
|
}
|
|
|
|
input := seqFromSlice([]int{5})
|
|
result := TraverseIter(validate)(input)
|
|
|
|
assert.True(t, IsSome(result))
|
|
collected := MonadFold(result, func() []int { return nil }, collectSeq[int])
|
|
assert.Equal(t, []int{10}, collected)
|
|
}
|
|
|
|
func TestTraverseIter_SingleElementFails(t *testing.T) {
|
|
// Test with single element - failure case
|
|
validate := func(x int) Option[int] {
|
|
if x > 0 {
|
|
return Some(x * 2)
|
|
}
|
|
return None[int]()
|
|
}
|
|
|
|
input := seqFromSlice([]int{-5})
|
|
result := TraverseIter(validate)(input)
|
|
|
|
assert.True(t, IsNone(result))
|
|
}
|
|
|
|
func TestTraverseIter_Validation(t *testing.T) {
|
|
// Test validation use case
|
|
validatePositive := func(x int) Option[int] {
|
|
if x > 0 {
|
|
return Some(x)
|
|
}
|
|
return None[int]()
|
|
}
|
|
|
|
// All positive
|
|
input1 := seqFromSlice([]int{1, 2, 3, 4})
|
|
result1 := TraverseIter(validatePositive)(input1)
|
|
assert.True(t, IsSome(result1))
|
|
|
|
// Contains negative
|
|
input2 := seqFromSlice([]int{1, -2, 3})
|
|
result2 := TraverseIter(validatePositive)(input2)
|
|
assert.True(t, IsNone(result2))
|
|
|
|
// Contains zero
|
|
input3 := seqFromSlice([]int{1, 0, 3})
|
|
result3 := TraverseIter(validatePositive)(input3)
|
|
assert.True(t, IsNone(result3))
|
|
}
|
|
|
|
func TestTraverseIter_Transformation(t *testing.T) {
|
|
// Test transformation use case
|
|
safeDivide := func(x int) Option[float64] {
|
|
if x != 0 {
|
|
return Some(100.0 / float64(x))
|
|
}
|
|
return None[float64]()
|
|
}
|
|
|
|
// All non-zero
|
|
input1 := seqFromSlice([]int{1, 2, 4, 5})
|
|
result1 := TraverseIter(safeDivide)(input1)
|
|
assert.True(t, IsSome(result1))
|
|
|
|
collected := MonadFold(result1, func() []float64 { return nil }, collectSeq[float64])
|
|
expected := []float64{100.0, 50.0, 25.0, 20.0}
|
|
assert.Equal(t, expected, collected)
|
|
|
|
// Contains zero
|
|
input2 := seqFromSlice([]int{1, 0, 4})
|
|
result2 := TraverseIter(safeDivide)(input2)
|
|
assert.True(t, IsNone(result2))
|
|
}
|
|
|
|
func TestTraverseIter_ShortCircuit(t *testing.T) {
|
|
// Test that traversal short-circuits on first None
|
|
callCount := 0
|
|
countingFunc := func(x int) Option[int] {
|
|
callCount++
|
|
if x < 0 {
|
|
return None[int]()
|
|
}
|
|
return Some(x * 2)
|
|
}
|
|
|
|
// First element fails
|
|
input := seqFromSlice([]int{-1, 2, 3, 4, 5})
|
|
result := TraverseIter(countingFunc)(input)
|
|
|
|
assert.True(t, IsNone(result))
|
|
// Should have called the function for elements until the first failure
|
|
// Note: The exact count depends on implementation details of the traverse function
|
|
assert.Greater(t, callCount, 0, "Function should be called at least once")
|
|
}
|
|
|
|
func TestTraverseIter_LazyEvaluation(t *testing.T) {
|
|
// Test that the result sequence is lazy
|
|
transform := func(x int) Option[int] {
|
|
return Some(x * 2)
|
|
}
|
|
|
|
input := seqFromSlice([]int{1, 2, 3, 4, 5})
|
|
result := TraverseIter(transform)(input)
|
|
|
|
assert.True(t, IsSome(result))
|
|
|
|
// Partially consume the sequence
|
|
callCount := 0
|
|
MonadFold(result, func() int { return 0 }, func(seq Seq[int]) int {
|
|
for val := range seq {
|
|
callCount++
|
|
_ = val
|
|
if callCount == 2 {
|
|
break
|
|
}
|
|
}
|
|
return callCount
|
|
})
|
|
|
|
assert.Equal(t, 2, callCount, "Should only evaluate consumed elements")
|
|
}
|
|
|
|
func TestTraverseIter_ComplexTransformation(t *testing.T) {
|
|
// Test with more complex transformation
|
|
type Person struct {
|
|
Name string
|
|
Age int
|
|
}
|
|
|
|
validatePerson := func(name string) Option[Person] {
|
|
if name == "" {
|
|
return None[Person]()
|
|
}
|
|
return Some(Person{Name: name, Age: len(name)})
|
|
}
|
|
|
|
input := seqFromSlice([]string{"Alice", "Bob", "Charlie"})
|
|
result := TraverseIter(validatePerson)(input)
|
|
|
|
assert.True(t, IsSome(result))
|
|
|
|
collected := MonadFold(result, func() []Person { return nil }, collectSeq[Person])
|
|
expected := []Person{
|
|
{Name: "Alice", Age: 5},
|
|
{Name: "Bob", Age: 3},
|
|
{Name: "Charlie", Age: 7},
|
|
}
|
|
assert.Equal(t, expected, collected)
|
|
}
|
|
|
|
func TestTraverseIter_WithPipeline(t *testing.T) {
|
|
// Test TraverseIter in a functional pipeline
|
|
parse := func(s string) Option[int] {
|
|
n, err := strconv.Atoi(s)
|
|
if err != nil {
|
|
return None[int]()
|
|
}
|
|
return Some(n)
|
|
}
|
|
|
|
input := seqFromSlice([]string{"1", "2", "3", "4", "5"})
|
|
|
|
result := F.Pipe2(
|
|
input,
|
|
TraverseIter(parse),
|
|
Map(collectSeq[int]),
|
|
)
|
|
|
|
collected := MonadFold(result, func() []int { return nil }, F.Identity[[]int])
|
|
expected := []int{1, 2, 3, 4, 5}
|
|
assert.Equal(t, expected, collected)
|
|
}
|
|
|
|
func TestTraverseIter_ChainedTransformations(t *testing.T) {
|
|
// Test chaining multiple transformations
|
|
parseAndValidate := func(s string) Option[int] {
|
|
n, err := strconv.Atoi(s)
|
|
if err != nil {
|
|
return None[int]()
|
|
}
|
|
if n > 0 {
|
|
return Some(n)
|
|
}
|
|
return None[int]()
|
|
}
|
|
|
|
// All valid
|
|
input1 := seqFromSlice([]string{"1", "2", "3"})
|
|
result1 := TraverseIter(parseAndValidate)(input1)
|
|
assert.True(t, IsSome(result1))
|
|
|
|
// Contains invalid number
|
|
input2 := seqFromSlice([]string{"1", "invalid", "3"})
|
|
result2 := TraverseIter(parseAndValidate)(input2)
|
|
assert.True(t, IsNone(result2))
|
|
|
|
// Contains non-positive number
|
|
input3 := seqFromSlice([]string{"1", "0", "3"})
|
|
result3 := TraverseIter(parseAndValidate)(input3)
|
|
assert.True(t, IsNone(result3))
|
|
}
|
|
|
|
// Example test demonstrating usage
|
|
func ExampleTraverseIter() {
|
|
// Parse a sequence of strings to integers
|
|
parse := func(s string) Option[int] {
|
|
n, err := strconv.Atoi(s)
|
|
if err != nil {
|
|
return None[int]()
|
|
}
|
|
return Some(n)
|
|
}
|
|
|
|
// Create a sequence of valid strings
|
|
validStrings := seqFromSlice([]string{"1", "2", "3"})
|
|
result := TraverseIter(parse)(validStrings)
|
|
|
|
if IsSome(result) {
|
|
numbers := MonadFold(result, func() []int { return nil }, collectSeq[int])
|
|
fmt.Println(numbers)
|
|
}
|
|
|
|
// Create a sequence with invalid string
|
|
invalidStrings := seqFromSlice([]string{"1", "invalid", "3"})
|
|
result2 := TraverseIter(parse)(invalidStrings)
|
|
|
|
if IsNone(result2) {
|
|
fmt.Println("Parsing failed")
|
|
}
|
|
|
|
// Output:
|
|
// [1 2 3]
|
|
// Parsing failed
|
|
}
|