Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

implement slice sorting assertions #37

Merged
merged 2 commits into from Oct 18, 2021
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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
4 changes: 4 additions & 0 deletions assert/error_messages.go
Expand Up @@ -204,3 +204,7 @@ func shouldNotBeDefined(actual types.Assertable) string {
func shouldContainUniqueElements(actual types.Assertable) string {
return fmt.Sprintf("assertion failed: expected value of = %+v, to contain unique elements but it doesn't", actual.Value())
}

func shouldBeSorted(actual types.Assertable, order string) string {
return fmt.Sprintf("assertion failed: expected value of = %+v, to be sorted in %s order but it isn't", order, actual.Value())
}
22 changes: 22 additions & 0 deletions assert/slice.go
Expand Up @@ -123,3 +123,25 @@ func (a AssertableSlice) HasUniqueElements() AssertableSlice {
}
return a
}

// IsSortedAscending asserts if the assertable slice is sorted on ascending order.
// It supports the following slices : []string, []int, []int32, []int64, []float64 and any slice that implements
// sort.Interface
// If a non-supported type is given then the assertion will fail.
func (a AssertableSlice) IsSortedAscending() AssertableSlice {
if !(a.actual.IsSorted(false)) {
a.t.Error(shouldBeSorted(a.actual, "ascending"))
}
return a
}

// IsSortedDescending asserts if the assertable slice is sorted on descending order.
// It supports the following slices : []string, []int, []int32, []int64, []float64 and any slice that implements
// sort.Interface
// If a non-supported type is given then the assertion will fail.
func (a AssertableSlice) IsSortedDescending() AssertableSlice {
if !(a.actual.IsSorted(true)) {
a.t.Error(shouldBeSorted(a.actual, "descending"))
}
return a
}
138 changes: 137 additions & 1 deletion assert/slice_test.go
@@ -1,6 +1,9 @@
package assert

import "testing"
import (
"testing"
"time"
)

func TestAssertableSlice_IsEmpty(t *testing.T) {
tests := []struct {
Expand Down Expand Up @@ -360,3 +363,136 @@ func TestAssertableSlice_HasUniqueElements(t *testing.T) {
ThatSlice(test, []int{1, 1, 3}).HasUniqueElements()
ThatBool(t, test.Failed()).IsEqualTo(true)
}

func TestAssertableSlice_IsSorted(t *testing.T) {
t.Run("should fail if it's not a slice", func(t *testing.T) {
test := &testing.T{}
ThatSlice(test, "some string").IsSortedAscending()
ThatBool(t, test.Failed()).IsTrue()
})
t.Run("should fail if it's not a slice", func(t *testing.T) {
test := &testing.T{}
ThatSlice(test, "some string").IsSortedAscending()
ThatBool(t, test.Failed()).IsTrue()
})
t.Run("should not fail if string slice is sorted", func(t *testing.T) {
test := &testing.T{}
ThatSlice(test, []string{"element", "element2", "element3"}).IsSortedAscending()
ThatBool(t, test.Failed()).IsFalse()
})
t.Run("should fail if string slice is not sorted", func(t *testing.T) {
test := &testing.T{}
ThatSlice(test, []string{"element2", "element", "element3"}).IsSortedAscending()
ThatBool(t, test.Failed()).IsTrue()
})
t.Run("should fail if string slice is sorted descending", func(t *testing.T) {
test := &testing.T{}
ThatSlice(test, []string{"element3", "element2", "element"}).IsSortedAscending()
ThatBool(t, test.Failed()).IsTrue()
})
t.Run("should not fail if string slice is sorted descending", func(t *testing.T) {
test := &testing.T{}
ThatSlice(test, []string{"element3", "element2", "element"}).IsSortedDescending()
ThatBool(t, test.Failed()).IsFalse()
})
t.Run("should not fail if float64 slice is sorted", func(t *testing.T) {
test := &testing.T{}
ThatSlice(test, []float64{0.1, 0.2, 2.12}).IsSortedAscending()
ThatBool(t, test.Failed()).IsFalse()
})
t.Run("should fail if float64 slice is sorted desc", func(t *testing.T) {
test := &testing.T{}
ThatSlice(test, []float64{10.1, 5.2, 2.12}).IsSortedAscending()
ThatBool(t, test.Failed()).IsTrue()
})
t.Run("should not fail if float64 slice is sorted desc", func(t *testing.T) {
test := &testing.T{}
ThatSlice(test, []float64{10.1, 5.2, 2.12}).IsSortedDescending()
ThatBool(t, test.Failed()).IsFalse()
})
t.Run("should fail if float64 slice is not sorted", func(t *testing.T) {
test := &testing.T{}
ThatSlice(test, []float64{0.1, 5.547, 2.12}).IsSortedAscending()
ThatBool(t, test.Failed()).IsTrue()
})
t.Run("should not fail if int slice is sorted", func(t *testing.T) {
test := &testing.T{}
ThatSlice(test, []int{1, 2, 12}).IsSortedAscending()
ThatBool(t, test.Failed()).IsFalse()
})
t.Run("should fail if int slice is not sorted", func(t *testing.T) {
test := &testing.T{}
ThatSlice(test, []int{1, 5, 2}).IsSortedAscending()
ThatBool(t, test.Failed()).IsTrue()
})
t.Run("should not fail if int slice is sorted desc", func(t *testing.T) {
test := &testing.T{}
ThatSlice(test, []int{10, 5, 2}).IsSortedDescending()
ThatBool(t, test.Failed()).IsFalse()
})
t.Run("should fail if int slice is not sorted", func(t *testing.T) {
test := &testing.T{}
ThatSlice(test, []int{10, 5, 2}).IsSortedAscending()
ThatBool(t, test.Failed()).IsTrue()
})
t.Run("should not fail if int32 slice is sorted", func(t *testing.T) {
test := &testing.T{}
ThatSlice(test, []int32{1, 2, 12}).IsSortedAscending()
ThatBool(t, test.Failed()).IsFalse()
})
t.Run("should fail if int32 slice is not sorted", func(t *testing.T) {
test := &testing.T{}
ThatSlice(test, []int32{1, 5, 2}).IsSortedAscending()
ThatBool(t, test.Failed()).IsTrue()
})
t.Run("should not fail if int32 slice is sorted desc", func(t *testing.T) {
test := &testing.T{}
ThatSlice(test, []int32{10, 5, 2}).IsSortedDescending()
ThatBool(t, test.Failed()).IsFalse()
})
t.Run("should fail if int32 slice is not sorted desc", func(t *testing.T) {
test := &testing.T{}
ThatSlice(test, []int32{1, 5, 2}).IsSortedDescending()
ThatBool(t, test.Failed()).IsTrue()
})
t.Run("should fail if int32 slice is not sorted", func(t *testing.T) {
test := &testing.T{}
ThatSlice(test, []int32{10, 5, 2}).IsSortedAscending()
ThatBool(t, test.Failed()).IsTrue()
})
t.Run("should not fail if int64 slice is sorted", func(t *testing.T) {
test := &testing.T{}
ThatSlice(test, []int64{1, 2, 12}).IsSortedAscending()
ThatBool(t, test.Failed()).IsFalse()
})
t.Run("should fail if int64 slice is not sorted", func(t *testing.T) {
test := &testing.T{}
ThatSlice(test, []int64{1, 5, 2}).IsSortedAscending()
ThatBool(t, test.Failed()).IsTrue()
})
t.Run("should not fail if int64 slice is sorted desc", func(t *testing.T) {
test := &testing.T{}
ThatSlice(test, []int64{10, 5, 2}).IsSortedDescending()
ThatBool(t, test.Failed()).IsFalse()
})
t.Run("should fail if int64 slice is not sorted", func(t *testing.T) {
test := &testing.T{}
ThatSlice(test, []int32{10, 5, 2}).IsSortedAscending()
ThatBool(t, test.Failed()).IsTrue()
})
t.Run("should fail if it's a slice that cannot be detected how to figure out sorting", func(t *testing.T) {
test := &testing.T{}
ThatSlice(test, []time.Time{time.Now(), time.Now()}).IsSortedAscending()
ThatBool(t, test.Failed()).IsTrue()
})
t.Run("should not fail if the slice has one element", func(t *testing.T) {
test := &testing.T{}
ThatSlice(test, []string{"123"}).IsSortedAscending()
ThatBool(t, test.Failed()).IsFalse()
})
t.Run("should not fail if the slice has no elements", func(t *testing.T) {
test := &testing.T{}
ThatSlice(test, []string{""}).IsSortedAscending()
ThatBool(t, test.Failed()).IsFalse()
})
}
67 changes: 67 additions & 0 deletions internal/pkg/values/slice_value.go
Expand Up @@ -2,6 +2,7 @@ package values

import (
"reflect"
"sort"
)

// SliceValue is a struct that holds a string slice value.
Expand Down Expand Up @@ -100,6 +101,72 @@ func (s SliceValue) HasUniqueElements() bool {
return true
}

// IsSorted returns true if the slice is sorted else false.
func (s SliceValue) IsSorted(desc bool) bool {
if !IsSlice(s.Value()) {
return false
}
sliceValue := reflect.ValueOf(s.value)
if sliceValue.Len() <= 1 {
return true
}
switch sliceType := s.value.(type) {
case []int:
if desc {
sliceType = reverseInts(sliceType)
}
return sort.IntsAreSorted(sliceType)
case []int32, []int64:
sliceLen := sliceValue.Len()
intSlice := make([]int, 0, sliceLen)
for i := 0; i < sliceLen; i++ {
intSlice = append(intSlice, int(sliceValue.Index(i).Int()))
}
if desc {
intSlice = reverseInts(intSlice)
}
return sort.IntsAreSorted(intSlice)
case []float64:
if desc {
sliceType = reverseFloats(sliceType)
}
return sort.Float64sAreSorted(sliceType)
case []string:
if desc {
sliceType = reverseStrings(sliceType)
}
return sort.StringsAreSorted(sliceType)
case sort.Interface:
if desc {
sliceType = sort.Reverse(sliceType)
}
return sort.IsSorted(sliceType)
}

return false
}

func reverseInts(ints []int) []int {
for i, j := 0, len(ints)-1; i < j; i, j = i+1, j-1 {
ints[i], ints[j] = ints[j], ints[i]
}
return ints
}

func reverseFloats(floats []float64) []float64 {
for i, j := 0, len(floats)-1; i < j; i, j = i+1, j-1 {
floats[i], floats[j] = floats[j], floats[i]
}
return floats
}

func reverseStrings(strings []string) []string {
for i, j := 0, len(strings)-1; i < j; i, j = i+1, j-1 {
strings[i], strings[j] = strings[j], strings[i]
}
return strings
}

// Value returns the actual value of the structure.
func (s SliceValue) Value() interface{} {
return s.value
Expand Down
1 change: 1 addition & 0 deletions types/types.go
Expand Up @@ -35,6 +35,7 @@ type Containable interface {
ContainsOnly(elements interface{}) bool
DoesNotContain(elements interface{}) bool
HasUniqueElements() bool
IsSorted(descending bool) bool
Sizeable
}

Expand Down