From 4f64fba814ad01b25022fd6f2188df57342b5f49 Mon Sep 17 00:00:00 2001 From: Papapetrou Patroklos <1743100+ppapapetrou76@users.noreply.github.com> Date: Mon, 18 Oct 2021 15:51:33 +0300 Subject: [PATCH] implement slice sorting assertions (#37) * implement slice sorting assertions * add more tests --- assert/error_messages.go | 4 + assert/slice.go | 22 +++++ assert/slice_test.go | 138 ++++++++++++++++++++++++++++- internal/pkg/values/slice_value.go | 67 ++++++++++++++ types/types.go | 1 + 5 files changed, 231 insertions(+), 1 deletion(-) diff --git a/assert/error_messages.go b/assert/error_messages.go index 454d3d0..f1f5231 100644 --- a/assert/error_messages.go +++ b/assert/error_messages.go @@ -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()) +} diff --git a/assert/slice.go b/assert/slice.go index 5087e92..c5b04f9 100644 --- a/assert/slice.go +++ b/assert/slice.go @@ -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 +} diff --git a/assert/slice_test.go b/assert/slice_test.go index e6df28c..6954846 100644 --- a/assert/slice_test.go +++ b/assert/slice_test.go @@ -1,6 +1,9 @@ package assert -import "testing" +import ( + "testing" + "time" +) func TestAssertableSlice_IsEmpty(t *testing.T) { tests := []struct { @@ -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() + }) +} diff --git a/internal/pkg/values/slice_value.go b/internal/pkg/values/slice_value.go index bef316e..bad5e3f 100644 --- a/internal/pkg/values/slice_value.go +++ b/internal/pkg/values/slice_value.go @@ -2,6 +2,7 @@ package values import ( "reflect" + "sort" ) // SliceValue is a struct that holds a string slice value. @@ -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 diff --git a/types/types.go b/types/types.go index c58e5f4..1d6f19b 100644 --- a/types/types.go +++ b/types/types.go @@ -35,6 +35,7 @@ type Containable interface { ContainsOnly(elements interface{}) bool DoesNotContain(elements interface{}) bool HasUniqueElements() bool + IsSorted(descending bool) bool Sizeable }