1
0
mirror of https://github.com/IBM/fp-go.git synced 2025-11-23 22:14:53 +02:00
Files
fp-go/v2/option/iter_test.go
Dr. Carsten Leue 02d0be9dad fix: add traversal for sequences
Signed-off-by: Dr. Carsten Leue <carsten.leue@de.ibm.com>
2025-11-14 14:12:44 +01:00

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
}