Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 3 additions & 25 deletions internal/maps/maps.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,35 +6,13 @@
// the future these functions are hopefully supported by the standard library.
package maps

import (
"iter"
)

// Copy created a shallow copy of the given map.
func Copy[K comparable, V any](source map[K]V) map[K]V {
target := make(map[K]V)
for key, value := range source {
target[key] = value
}
return target
}

// Add the given maps to a common base map overriding existing key values pairs
// added from previous maps if a new entry exists in a latter source map.
func Add[K comparable, V any](target map[K]V, sources ...map[K]V) map[K]V {
// Copy merges the given source maps into the target map, overriding existing
// values if the same key appears in a later source map.
func Copy[K comparable, V any](target map[K]V, sources ...map[K]V) map[K]V {
for _, source := range sources {
for k, v := range source {
target[k] = v
}
}
return target
}

// Collect collects all entries from the given iterator into a map.
func Collect[K comparable, V any](source iter.Seq2[K, V]) map[K]V {
target := make(map[K]V)
for k, v := range source {
target[k] = v
}
return target
}
78 changes: 5 additions & 73 deletions internal/maps/maps_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,43 +10,12 @@ import (
)

type CopyParams struct {
input map[string]int
expect map[string]int
}

var copyTestCases = map[string]CopyParams{
"empty-map": {
input: map[string]int{},
expect: map[string]int{},
},
"single-key-value-pair": {
input: map[string]int{"a": 1},
expect: map[string]int{"a": 1},
},
"multiple-key-value-pairs": {
input: map[string]int{"a": 1, "b": 2, "c": 3},
expect: map[string]int{"a": 1, "b": 2, "c": 3},
},
}

func TestCopy(t *testing.T) {
test.Map(t, copyTestCases).
Run(func(t test.Test, param CopyParams) {
// When
expect := maps.Copy(param.input)

// Then
assert.Equal(t, param.expect, expect)
})
}

type AddParams struct {
target map[string]int
sources []map[string]int
expect map[string]int
}

var addTestCases = map[string]AddParams{
var copyTestCases = map[string]CopyParams{
"no-sources": {
target: map[string]int{"a": 1},
sources: []map[string]int{},
Expand All @@ -69,48 +38,11 @@ var addTestCases = map[string]AddParams{
},
}

func TestAdd(t *testing.T) {
test.Map(t, addTestCases).
Run(func(t test.Test, param AddParams) {
// When
result := maps.Add(param.target, param.sources...)

// Then
assert.Equal(t, param.expect, result)
})
}

type CollectParams struct {
input map[string]int
expect map[string]int
}

var collectTestCases = map[string]CollectParams{
"empty-map": {
input: map[string]int{},
expect: map[string]int{},
},
"single-key-value-pair": {
input: map[string]int{"a": 1},
expect: map[string]int{"a": 1},
},
"multiple-key-value-pairs": {
input: map[string]int{"a": 1, "b": 2, "c": 3},
expect: map[string]int{"a": 1, "b": 2, "c": 3},
},
}

func TestCollect(t *testing.T) {
test.Map(t, collectTestCases).
Run(func(t test.Test, param CollectParams) {
func TestCopy(t *testing.T) {
test.Map(t, copyTestCases).
Run(func(t test.Test, param CopyParams) {
// When
result := maps.Collect(func(yield func(string, int) bool) {
for k, v := range param.input {
if !yield(k, v) {
return
}
}
})
result := maps.Copy(param.target, param.sources...)

// Then
assert.Equal(t, param.expect, result)
Expand Down
5 changes: 2 additions & 3 deletions internal/mock/builder.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,8 @@ package mock

import (
"regexp"
"slices"
"strings"

"github.com/tkrop/go-testing/internal/slices"
)

var (
Expand Down Expand Up @@ -180,7 +179,7 @@ func (b *FileBuilder) calcUniqAlias(path string) string {
alias := ""

norm := strings.ToLower(baseReplacer.Replace(path))
for _, prefix := range slices.Reverse(strings.Split(norm, "/")) {
for _, prefix := range slices.Backward(strings.Split(norm, "/")) {
if alias != "" {
alias = prefix + "_" + alias
} else {
Expand Down
28 changes: 1 addition & 27 deletions internal/slices/slices.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,11 @@
// must be consider as highly instable.
package slices

// Reverse reverses the given slice.
func Reverse[T any](slice []T) []T {
for i, j := 0, len(slice)-1; i < j; i, j = i+1, j-1 {
slice[i], slice[j] = slice[j], slice[i]
}
return slice
}

// Permute permutates the given slice as is.
func Permute[T any](slice []T) [][]T {
perms := [][]T{}
PermuteDo(slice, func(perm []T) {
perms = append(perms, Copy(perm))
perms = append(perms, append([]T(nil), perm...))
}, 0)
return perms
}
Expand All @@ -35,21 +27,3 @@ func PermuteDo[T any](slice []T, do func([]T), i int) {
do(slice)
}
}

// Copy makes a shallow copy of the given slice.
func Copy[T any](slice []T) []T {
return append(make([]T, 0, len(slice)), slice...)
}

// Add appends the given slices into a single slice.
func Add[T any](slices ...[]T) []T {
items := 0
for _, slice := range slices {
items += len(slice)
}
result := make([]T, 0, items)
for _, slice := range slices {
result = append(result, slice...)
}
return result
}
81 changes: 0 additions & 81 deletions internal/slices/slices_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,17 +6,8 @@ import (
"github.com/stretchr/testify/assert"

"github.com/tkrop/go-testing/internal/slices"
"github.com/tkrop/go-testing/test"
)

func TestReverse(t *testing.T) {
// When
result := slices.Reverse([]int{0, 1, 2, 3, 4})

// Then
assert.Equal(t, []int{4, 3, 2, 1, 0}, result)
}

func TestPermut(t *testing.T) {
// When
result := slices.Permute([]int{0, 1, 2})
Expand All @@ -31,75 +22,3 @@ func TestPermut(t *testing.T) {
{2, 0, 1},
}, result)
}

type TestAddIntParams struct {
slices [][]int
expect []int
}

var addIntTestCases = map[string]TestAddIntParams{
"add-multiple-int-slices": {
slices: [][]int{{1, 2, 3}, {4, 5, 6}, {7, 8, 9}},
expect: []int{1, 2, 3, 4, 5, 6, 7, 8, 9},
},
"add-two-int-slices": {
slices: [][]int{{1, 2}, {3, 4}},
expect: []int{1, 2, 3, 4},
},
"add-single-int-slice": {
slices: [][]int{{1, 2, 3}},
expect: []int{1, 2, 3},
},
"add-empty-int-slices": {
slices: [][]int{{}, {1, 2}, {}},
expect: []int{1, 2},
},
"add-all-empty-int-slices": {
slices: [][]int{{}, {}, {}},
expect: []int{},
},
"add-no-int-slices": {
slices: [][]int{},
expect: []int{},
},
}

func TestAddInt(t *testing.T) {
test.Map(t, addIntTestCases).
Run(func(t test.Test, param TestAddIntParams) {
// When
result := slices.Add[int](param.slices...)
// Then
assert.Equal(t, param.expect, result)
})
}

type TestAddStringParams struct {
slices [][]string
expect []string
}

var addStringTestCases = map[string]TestAddStringParams{
"add-string-slices": {
slices: [][]string{{"hello", "world"}, {"foo", "bar"}},
expect: []string{"hello", "world", "foo", "bar"},
},
"add-empty-string-slices": {
slices: [][]string{{}, {"test"}, {}},
expect: []string{"test"},
},
"add-no-string-slices": {
slices: [][]string{},
expect: []string{},
},
}

func TestAdd(t *testing.T) {
test.Map(t, addStringTestCases).
Run(func(t test.Test, param TestAddStringParams) {
// When
result := slices.Add(param.slices...)
// Then
assert.Equal(t, param.expect, result)
})
}
31 changes: 21 additions & 10 deletions reflect/reflect.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
package reflect

import (
"fmt"
"reflect"
"slices"
"strings"
Expand Down Expand Up @@ -73,25 +74,25 @@ type builder[T any] struct {
// name.
func NewBuilder[T any]() Builder[T] {
var target T
return NewAccessor[T](target)
return NewAccessor(target)
}

// NewGetter creates a generic getter for a target struct type. The getter
// allows you to access unexported fields of the struct by field name.
func NewGetter[T any](target T) Getter[T] {
return NewAccessor[T](target)
return NewAccessor(target)
}

// NewSetter creates a generic setter for a target struct type. The setter
// allows you to modify unexported fields of the struct by field name.
func NewSetter[T any](target T) Setter[T] {
return NewAccessor[T](target)
return NewAccessor(target)
}

// NewFinder creates a generic finder for a target struct type. The finder
// allows you to access unexported fields of the struct by field name.
func NewFinder[T any](target T) Finder[T] {
return NewAccessor[T](target)
return NewAccessor(target)
}

// NewAccessor creates a generic builder/accessor for a given target struct.
Expand All @@ -111,7 +112,7 @@ func NewAccessor[T any](target T) Builder[T] {
if value.Kind() == reflect.Ptr {
// Create a new instance if the pointer is nil.
if value.Elem().Kind() == reflect.Invalid {
target = reflect.New(value.Type().Elem()).Interface().(T)
target = cast[T](reflect.New(value.Type().Elem()).Interface())
value = reflect.ValueOf(target)
}

Expand Down Expand Up @@ -211,12 +212,12 @@ func (b *builder[T]) Build() T {
if b.wrapped {
target := b.targetValueOf()
if target.IsValid() {
return target.Interface().(T)
return cast[T](target.Interface())
}
var t T
return t
} else {
return b.target.(T)
return cast[T](b.target)
}
}

Expand Down Expand Up @@ -284,11 +285,11 @@ func Find[P, T any](param P, deflt T, names ...string) T {
pt, dt := reflect.TypeOf(param), reflect.TypeOf(deflt)
switch {
case pt.Kind() == dt.Kind():
return reflect.ValueOf(param).Interface().(T)
return cast[T](reflect.ValueOf(param).Interface())
case pt.Kind() == reflect.Struct:
return NewAccessor(param).Find(deflt, names...).(T)
return cast[T](NewAccessor(param).Find(deflt, names...))
case pt.Kind() == reflect.Ptr && pt.Elem().Kind() == reflect.Struct:
return NewAccessor(param).Find(deflt, names...).(T)
return cast[T](NewAccessor(param).Find(deflt, names...))
default:
return deflt
}
Expand All @@ -307,3 +308,13 @@ func Name[P any](name string, param P) string {
}
return ""
}

// cast is a convenience function to cast the given argument to the specified
// type or panic controlled if the cast fails.
func cast[T any](arg any) T {
val, ok := arg.(T)
if !ok {
panic(fmt.Sprintf("cast failed [%T]: %v", val, arg))
}
return val
}
Loading