mirror of
https://github.com/IBM/fp-go.git
synced 2025-11-23 22:14:53 +02:00
420 lines
12 KiB
Go
420 lines
12 KiB
Go
// 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 result
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"strconv"
|
|
"testing"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
// TestTraverseArrayG_Success tests successful traversal of an array with all valid elements
|
|
func TestTraverseArrayG_Success(t *testing.T) {
|
|
parse := func(s string) (int, error) {
|
|
return strconv.Atoi(s)
|
|
}
|
|
|
|
result, err := TraverseArrayG[[]string, []int](parse)([]string{"1", "2", "3"})
|
|
|
|
require.NoError(t, err)
|
|
assert.Equal(t, []int{1, 2, 3}, result)
|
|
}
|
|
|
|
// TestTraverseArrayG_Error tests that traversal short-circuits on first error
|
|
func TestTraverseArrayG_Error(t *testing.T) {
|
|
parse := func(s string) (int, error) {
|
|
return strconv.Atoi(s)
|
|
}
|
|
|
|
result, err := TraverseArrayG[[]string, []int](parse)([]string{"1", "bad", "3"})
|
|
|
|
require.Error(t, err)
|
|
assert.Nil(t, result)
|
|
}
|
|
|
|
// TestTraverseArrayG_EmptyArray tests traversal of an empty array
|
|
func TestTraverseArrayG_EmptyArray(t *testing.T) {
|
|
parse := func(s string) (int, error) {
|
|
return strconv.Atoi(s)
|
|
}
|
|
|
|
result, err := TraverseArrayG[[]string, []int](parse)([]string{})
|
|
|
|
require.NoError(t, err)
|
|
assert.Empty(t, result)
|
|
assert.NotNil(t, result) // Should be an empty slice, not nil
|
|
}
|
|
|
|
// TestTraverseArrayG_SingleElement tests traversal with a single element
|
|
func TestTraverseArrayG_SingleElement(t *testing.T) {
|
|
parse := func(s string) (int, error) {
|
|
return strconv.Atoi(s)
|
|
}
|
|
|
|
result, err := TraverseArrayG[[]string, []int](parse)([]string{"42"})
|
|
|
|
require.NoError(t, err)
|
|
assert.Equal(t, []int{42}, result)
|
|
}
|
|
|
|
// TestTraverseArrayG_ShortCircuit tests that processing stops at first error
|
|
func TestTraverseArrayG_ShortCircuit(t *testing.T) {
|
|
callCount := 0
|
|
parse := func(s string) (int, error) {
|
|
callCount++
|
|
if s == "error" {
|
|
return 0, errors.New("parse error")
|
|
}
|
|
return len(s), nil
|
|
}
|
|
|
|
_, err := TraverseArrayG[[]string, []int](parse)([]string{"ok", "error", "should-not-process"})
|
|
|
|
require.Error(t, err)
|
|
assert.Equal(t, 2, callCount, "should stop after encountering error")
|
|
}
|
|
|
|
// TestTraverseArrayG_CustomSliceType tests with custom slice types
|
|
func TestTraverseArrayG_CustomSliceType(t *testing.T) {
|
|
type StringSlice []string
|
|
type IntSlice []int
|
|
|
|
parse := func(s string) (int, error) {
|
|
return strconv.Atoi(s)
|
|
}
|
|
|
|
input := StringSlice{"1", "2", "3"}
|
|
result, err := TraverseArrayG[StringSlice, IntSlice](parse)(input)
|
|
|
|
require.NoError(t, err)
|
|
assert.Equal(t, IntSlice{1, 2, 3}, result)
|
|
}
|
|
|
|
// TestTraverseArray_Success tests successful traversal
|
|
func TestTraverseArray_Success(t *testing.T) {
|
|
validate := func(s string) (int, error) {
|
|
n, err := strconv.Atoi(s)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
if n < 0 {
|
|
return 0, errors.New("negative number")
|
|
}
|
|
return n * 2, nil
|
|
}
|
|
|
|
result, err := TraverseArray(validate)([]string{"1", "2", "3"})
|
|
|
|
require.NoError(t, err)
|
|
assert.Equal(t, []int{2, 4, 6}, result)
|
|
}
|
|
|
|
// TestTraverseArray_ValidationError tests validation failure
|
|
func TestTraverseArray_ValidationError(t *testing.T) {
|
|
validate := func(s string) (int, error) {
|
|
n, err := strconv.Atoi(s)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
if n < 0 {
|
|
return 0, errors.New("negative number")
|
|
}
|
|
return n * 2, nil
|
|
}
|
|
|
|
result, err := TraverseArray(validate)([]string{"1", "-5", "3"})
|
|
|
|
require.Error(t, err)
|
|
assert.Contains(t, err.Error(), "negative number")
|
|
assert.Nil(t, result)
|
|
}
|
|
|
|
// TestTraverseArray_ParseError tests parse failure
|
|
func TestTraverseArray_ParseError(t *testing.T) {
|
|
validate := func(s string) (int, error) {
|
|
n, err := strconv.Atoi(s)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
return n, nil
|
|
}
|
|
|
|
result, err := TraverseArray(validate)([]string{"1", "not-a-number", "3"})
|
|
|
|
require.Error(t, err)
|
|
assert.Nil(t, result)
|
|
}
|
|
|
|
// TestTraverseArray_EmptyArray tests empty array
|
|
func TestTraverseArray_EmptyArray(t *testing.T) {
|
|
identity := func(n int) (int, error) {
|
|
return n, nil
|
|
}
|
|
|
|
result, err := TraverseArray(identity)([]int{})
|
|
|
|
require.NoError(t, err)
|
|
assert.Empty(t, result)
|
|
assert.NotNil(t, result)
|
|
}
|
|
|
|
// TestTraverseArray_DifferentTypes tests transformation between different types
|
|
func TestTraverseArray_DifferentTypes(t *testing.T) {
|
|
toLength := func(s string) (int, error) {
|
|
if len(s) == 0 {
|
|
return 0, errors.New("empty string")
|
|
}
|
|
return len(s), nil
|
|
}
|
|
|
|
result, err := TraverseArray(toLength)([]string{"a", "bb", "ccc"})
|
|
|
|
require.NoError(t, err)
|
|
assert.Equal(t, []int{1, 2, 3}, result)
|
|
}
|
|
|
|
// TestTraverseArrayWithIndexG_Success tests successful indexed traversal
|
|
func TestTraverseArrayWithIndexG_Success(t *testing.T) {
|
|
annotate := func(i int, s string) (string, error) {
|
|
if len(s) == 0 {
|
|
return "", fmt.Errorf("empty string at index %d", i)
|
|
}
|
|
return fmt.Sprintf("[%d]=%s", i, s), nil
|
|
}
|
|
|
|
result, err := TraverseArrayWithIndexG[[]string, []string](annotate)([]string{"a", "b", "c"})
|
|
|
|
require.NoError(t, err)
|
|
assert.Equal(t, []string{"[0]=a", "[1]=b", "[2]=c"}, result)
|
|
}
|
|
|
|
// TestTraverseArrayWithIndexG_Error tests error handling with index
|
|
func TestTraverseArrayWithIndexG_Error(t *testing.T) {
|
|
annotate := func(i int, s string) (string, error) {
|
|
if len(s) == 0 {
|
|
return "", fmt.Errorf("empty string at index %d", i)
|
|
}
|
|
return fmt.Sprintf("[%d]=%s", i, s), nil
|
|
}
|
|
|
|
result, err := TraverseArrayWithIndexG[[]string, []string](annotate)([]string{"a", "", "c"})
|
|
|
|
require.Error(t, err)
|
|
assert.Contains(t, err.Error(), "index 1")
|
|
assert.Nil(t, result)
|
|
}
|
|
|
|
// TestTraverseArrayWithIndexG_EmptyArray tests empty array
|
|
func TestTraverseArrayWithIndexG_EmptyArray(t *testing.T) {
|
|
annotate := func(i int, s string) (string, error) {
|
|
return fmt.Sprintf("%d:%s", i, s), nil
|
|
}
|
|
|
|
result, err := TraverseArrayWithIndexG[[]string, []string](annotate)([]string{})
|
|
|
|
require.NoError(t, err)
|
|
assert.Empty(t, result)
|
|
assert.NotNil(t, result)
|
|
}
|
|
|
|
// TestTraverseArrayWithIndexG_IndexValidation tests that indices are correct
|
|
func TestTraverseArrayWithIndexG_IndexValidation(t *testing.T) {
|
|
indices := []int{}
|
|
collect := func(i int, s string) (string, error) {
|
|
indices = append(indices, i)
|
|
return s, nil
|
|
}
|
|
|
|
_, err := TraverseArrayWithIndexG[[]string, []string](collect)([]string{"a", "b", "c", "d"})
|
|
|
|
require.NoError(t, err)
|
|
assert.Equal(t, []int{0, 1, 2, 3}, indices)
|
|
}
|
|
|
|
// TestTraverseArrayWithIndexG_ShortCircuit tests short-circuit behavior
|
|
func TestTraverseArrayWithIndexG_ShortCircuit(t *testing.T) {
|
|
maxIndex := -1
|
|
process := func(i int, s string) (string, error) {
|
|
maxIndex = i
|
|
if i == 2 {
|
|
return "", errors.New("stop at index 2")
|
|
}
|
|
return s, nil
|
|
}
|
|
|
|
_, err := TraverseArrayWithIndexG[[]string, []string](process)([]string{"a", "b", "c", "d", "e"})
|
|
|
|
require.Error(t, err)
|
|
assert.Equal(t, 2, maxIndex, "should stop at index 2")
|
|
}
|
|
|
|
// TestTraverseArrayWithIndexG_CustomSliceType tests with custom slice types
|
|
func TestTraverseArrayWithIndexG_CustomSliceType(t *testing.T) {
|
|
type StringSlice []string
|
|
type ResultSlice []string
|
|
|
|
annotate := func(i int, s string) (string, error) {
|
|
return fmt.Sprintf("%d:%s", i, s), nil
|
|
}
|
|
|
|
input := StringSlice{"x", "y", "z"}
|
|
result, err := TraverseArrayWithIndexG[StringSlice, ResultSlice](annotate)(input)
|
|
|
|
require.NoError(t, err)
|
|
assert.Equal(t, ResultSlice{"0:x", "1:y", "2:z"}, result)
|
|
}
|
|
|
|
// TestTraverseArrayWithIndex_Success tests successful indexed traversal
|
|
func TestTraverseArrayWithIndex_Success(t *testing.T) {
|
|
check := func(i int, s string) (string, error) {
|
|
if len(s) == 0 {
|
|
return "", fmt.Errorf("empty value at position %d", i)
|
|
}
|
|
return fmt.Sprintf("%d_%s", i, s), nil
|
|
}
|
|
|
|
result, err := TraverseArrayWithIndex(check)([]string{"a", "b", "c"})
|
|
|
|
require.NoError(t, err)
|
|
assert.Equal(t, []string{"0_a", "1_b", "2_c"}, result)
|
|
}
|
|
|
|
// TestTraverseArrayWithIndex_Error tests error with position info
|
|
func TestTraverseArrayWithIndex_Error(t *testing.T) {
|
|
check := func(i int, s string) (string, error) {
|
|
if len(s) == 0 {
|
|
return "", fmt.Errorf("empty value at position %d", i)
|
|
}
|
|
return s, nil
|
|
}
|
|
|
|
result, err := TraverseArrayWithIndex(check)([]string{"ok", "ok", "", "ok"})
|
|
|
|
require.Error(t, err)
|
|
assert.Contains(t, err.Error(), "position 2")
|
|
assert.Nil(t, result)
|
|
}
|
|
|
|
// TestTraverseArrayWithIndex_TypeTransformation tests transforming types with index
|
|
func TestTraverseArrayWithIndex_TypeTransformation(t *testing.T) {
|
|
multiply := func(i int, n int) (int, error) {
|
|
return n * (i + 1), nil
|
|
}
|
|
|
|
result, err := TraverseArrayWithIndex(multiply)([]int{10, 20, 30})
|
|
|
|
require.NoError(t, err)
|
|
assert.Equal(t, []int{10, 40, 90}, result) // [10*(0+1), 20*(1+1), 30*(2+1)]
|
|
}
|
|
|
|
// TestTraverseArrayWithIndex_EmptyArray tests empty array
|
|
func TestTraverseArrayWithIndex_EmptyArray(t *testing.T) {
|
|
process := func(i int, s string) (int, error) {
|
|
return i, nil
|
|
}
|
|
|
|
result, err := TraverseArrayWithIndex(process)([]string{})
|
|
|
|
require.NoError(t, err)
|
|
assert.Empty(t, result)
|
|
assert.NotNil(t, result)
|
|
}
|
|
|
|
// TestTraverseArrayWithIndex_SingleElement tests single element processing
|
|
func TestTraverseArrayWithIndex_SingleElement(t *testing.T) {
|
|
annotate := func(i int, s string) (string, error) {
|
|
return fmt.Sprintf("item_%d:%s", i, s), nil
|
|
}
|
|
|
|
result, err := TraverseArrayWithIndex(annotate)([]string{"solo"})
|
|
|
|
require.NoError(t, err)
|
|
assert.Equal(t, []string{"item_0:solo"}, result)
|
|
}
|
|
|
|
// TestTraverseArrayWithIndex_ConditionalLogic tests using index for conditional logic
|
|
func TestTraverseArrayWithIndex_ConditionalLogic(t *testing.T) {
|
|
// Only accept even indices
|
|
process := func(i int, s string) (string, error) {
|
|
if i%2 != 0 {
|
|
return "", fmt.Errorf("odd index %d not allowed", i)
|
|
}
|
|
return s, nil
|
|
}
|
|
|
|
result, err := TraverseArrayWithIndex(process)([]string{"ok", "fail"})
|
|
|
|
require.Error(t, err)
|
|
assert.Contains(t, err.Error(), "odd index 1")
|
|
assert.Nil(t, result)
|
|
}
|
|
|
|
// TestTraverseArray_LargeArray tests traversal with a larger array
|
|
func TestTraverseArray_LargeArray(t *testing.T) {
|
|
// Create array with 1000 elements
|
|
input := make([]int, 1000)
|
|
for i := range input {
|
|
input[i] = i
|
|
}
|
|
|
|
double := func(n int) (int, error) {
|
|
return n * 2, nil
|
|
}
|
|
|
|
result, err := TraverseArray(double)(input)
|
|
|
|
require.NoError(t, err)
|
|
assert.Len(t, result, 1000)
|
|
assert.Equal(t, 0, result[0])
|
|
assert.Equal(t, 1998, result[999])
|
|
}
|
|
|
|
// TestTraverseArrayG_PreservesOrder tests that order is preserved
|
|
func TestTraverseArrayG_PreservesOrder(t *testing.T) {
|
|
reverse := func(s string) (string, error) {
|
|
runes := []rune(s)
|
|
for i, j := 0, len(runes)-1; i < j; i, j = i+1, j-1 {
|
|
runes[i], runes[j] = runes[j], runes[i]
|
|
}
|
|
return string(runes), nil
|
|
}
|
|
|
|
result, err := TraverseArrayG[[]string, []string](reverse)([]string{"abc", "def", "ghi"})
|
|
|
|
require.NoError(t, err)
|
|
assert.Equal(t, []string{"cba", "fed", "ihg"}, result)
|
|
}
|
|
|
|
// TestTraverseArrayWithIndex_BoundaryCheck tests boundary conditions with index
|
|
func TestTraverseArrayWithIndex_BoundaryCheck(t *testing.T) {
|
|
// Reject if index exceeds a threshold
|
|
limitedProcess := func(i int, s string) (string, error) {
|
|
if i >= 100 {
|
|
return "", errors.New("index too large")
|
|
}
|
|
return s, nil
|
|
}
|
|
|
|
// Should succeed with index < 100
|
|
result, err := TraverseArrayWithIndex(limitedProcess)([]string{"a", "b", "c"})
|
|
require.NoError(t, err)
|
|
assert.Equal(t, []string{"a", "b", "c"}, result)
|
|
}
|