package ldvalue import ( "fmt" "testing" "github.com/stretchr/testify/assert" ) func TestNilValueArray(t *testing.T) { a := ValueArray{} assert.False(t, a.IsDefined()) assert.Equal(t, 0, a.Count()) assert.Equal(t, Null(), a.Get(0)) } func TestCopyValueArray(t *testing.T) { s := []Value{String("a"), String("b")} a1 := CopyValueArray(s) assert.Equal(t, s, a1.data) shouldNotBeSameSlice(t, s, a1.data) a2 := CopyValueArray(nil) assert.Nil(t, a2.data) s3 := []Value{} a3 := CopyValueArray(s3) assert.Equal(t, []Value{}, a3.data) } func TestCopyArbitraryValueArray(t *testing.T) { a1 := CopyArbitraryValueArray([]interface{}{"a", "b"}) assert.Equal(t, []Value{String("a"), String("b")}, a1.data) a2 := CopyArbitraryValueArray(nil) assert.Nil(t, a2.data) a3 := CopyArbitraryValueArray([]interface{}{}) assert.Equal(t, []Value{}, a3.data) } func TestValueArrayOf(t *testing.T) { item0 := String("a") item1 := Int(1) a1 := ValueArrayOf(item0, item1) assert.Equal(t, 2, a1.Count()) assert.True(t, a1.IsDefined()) a2 := ValueArrayOf() assert.Equal(t, 0, a2.Count()) assert.True(t, a2.IsDefined()) } func TestValueArrayBuild(t *testing.T) { item0 := String("a") item1 := Int(1) builder := ValueArrayBuild().Add(item0).Add(item1) a := builder.Build() assert.True(t, a.IsDefined()) assert.Equal(t, 2, a.Count()) assert.Equal(t, ValueArrayOf(item0, item1), a) item2 := Bool(true) builder.Add(item2) valueAfterModifyingBuilder := builder.Build() assert.Equal(t, 3, valueAfterModifyingBuilder.Count()) assert.Equal(t, item2, valueAfterModifyingBuilder.Get(2)) assert.Equal(t, 2, a.Count()) // verifies builder's copy-on-write behavior assert.Equal(t, ValueArrayOf(), ValueArrayBuild().Build()) assert.Equal(t, a, ValueArrayBuildWithCapacity(3).Add(item0).Add(item1).Build()) } func TestValueArrayBuildFromArray(t *testing.T) { a0 := ValueArrayOf(String("a"), String("b")) a1 := ValueArrayBuildFromArray(a0).Build() assert.Equal(t, a0, a1) shouldBeSameSlice(t, a0.data, a1.data) // test copy-on-write behavior a3 := ValueArrayOf(String("a")) a4 := ValueArrayBuildFromArray(a3).Add(String("b")).Build() assert.Equal(t, ValueArrayOf(String("a"), String("b")), a4) shouldNotBeSameSlice(t, a3.data, a4.data) a5 := ValueArrayBuild().AddAllFromValueArray(a3).Add(String("b")).Build() assert.NotEqual(t, a3, a5) shouldNotBeSameSlice(t, a3.data, a5.data) assert.Equal(t, a4, a5) } func TestValueArrayBuilderSafety(t *testing.T) { // empty instance is safe to use var emptyInstance ValueArrayBuilder emptyInstance.Add(Int(1)) assert.Equal(t, ValueArrayBuild().Add(Int(1)).Build(), emptyInstance.Build()) // nil pointer is safe to use var nilPtr *ValueArrayBuilder assert.Nil(t, nilPtr.Add(Int(1))) assert.Nil(t, nilPtr.AddAllFromValueArray(ValueArray{})) assert.Equal(t, ValueArray{}, nilPtr.Build()) } func TestValueArrayGetByIndex(t *testing.T) { item0 := String("a") item1 := Int(1) a := ValueArrayOf(item0, item1) assert.Equal(t, item0, a.Get(0)) assert.Equal(t, item1, a.Get(1)) assert.Equal(t, Null(), a.Get(-1)) assert.Equal(t, Null(), a.Get(2)) item, ok := a.TryGet(0) assert.True(t, ok) assert.Equal(t, item0, item) item, ok = a.TryGet(2) assert.False(t, ok) assert.Equal(t, Null(), item) } func TestConvertValueArrayToArbitraryValues(t *testing.T) { a := ValueArrayBuild().Add(String("a")).Add(String("b")).Build() expected := []interface{}{"a", "b"} assert.Equal(t, expected, a.AsArbitraryValueSlice()) } func TestConvertValueArrayFromArbitraryValuesAndBackAgain(t *testing.T) { slice0 := []interface{}{"a", "b"} a := CopyArbitraryValueArray(slice0) slice1 := a.AsArbitraryValueSlice() assert.Equal(t, slice0, slice1) // Verify that the slice was deep-copied slice0[0] = "c" assert.NotEqual(t, slice0, slice1) } func TestValueArrayEqual(t *testing.T) { valueFns := []func() ValueArray{ func() ValueArray { return ValueArray{} }, func() ValueArray { return ValueArrayBuild().Build() }, func() ValueArray { return ValueArrayBuild().Add(String("a")).Build() }, } for i, fn0 := range valueFns { v0 := fn0() for j, fn1 := range valueFns { v1 := fn1() if i == j { assert.True(t, v0.Equal(v1), "%s should equal %s", v0, v1) assert.True(t, v0.Equal(v1), "%s should equal %s conversely", v1, v0) } else { assert.False(t, v0.Equal(v1), "%s should not equal %s", v0, v1) assert.False(t, v1.Equal(v0), "%s should not equal %s", v1, v0) } } } } func TestValueArrayAsValue(t *testing.T) { assert.Equal(t, Null(), ValueArray{}.AsValue()) a := ValueArrayOf(String("a"), String("b")) v := a.AsValue() assert.Equal(t, ArrayOf(String("a"), String("b")), v) shouldBeSameSlice(t, a.data, v.arrayValue.data) } func TestValueArrayAsSlice(t *testing.T) { assert.Nil(t, ValueArray{}.AsSlice()) a := ValueArrayOf(String("a"), String("b")) s := a.AsSlice() assert.Equal(t, []Value{String("a"), String("b")}, s) shouldNotBeSameSlice(t, a.data, s) } func TestValueArrayAsArbitraryValueSlice(t *testing.T) { assert.Nil(t, ValueArray{}.AsArbitraryValueSlice()) a := ValueArrayOf(String("a"), String("b")) s := a.AsArbitraryValueSlice() assert.Equal(t, []interface{}{"a", "b"}, s) } func TestValueArrayTransform(t *testing.T) { fnNoChanges := func(index int, value Value) (Value, bool) { return value, true } fnAbsoluteValuesAndNoOddNumbers := func(index int, value Value) (Value, bool) { if value.IntValue()%2 == 1 { return value, false // first return value should be ignored since second one is false } if value.IntValue() < 0 { return Int(-value.IntValue()), true } return value, true } fnTransformUsingIndex := func(index int, value Value) (Value, bool) { return String(fmt.Sprintf("%d=%s", index, value.StringValue())), true } array1 := ValueArrayOf(Int(2), Int(4), Int(6)) array1a := array1.Transform(fnNoChanges) array1b := array1.Transform(fnAbsoluteValuesAndNoOddNumbers) // Should have no changes... assert.Equal(t, array1, array1a) assert.Equal(t, array1, array1b) // ...and should be wrapping the *same* slice, not a copy shouldBeSameSlice(t, array1.data, array1a.data) shouldBeSameSlice(t, array1.data, array1b.data) array2 := ValueArrayOf(Int(2), Int(4), Int(1), Int(-6)) array2a := array2.Transform(fnNoChanges) array2b := array2.Transform(fnAbsoluteValuesAndNoOddNumbers) // array2a should have no changes, and should be wrapping the same slice assert.Equal(t, array2, array2a) shouldBeSameSlice(t, array2.data, array2a.data) // array2b should have a transformed slice assert.Equal(t, ValueArrayOf(Int(2), Int(4), Int(6)), array2b) // Same as the array2 tests, except that the first change is a modification, not a deletion array3 := ValueArrayOf(Int(2), Int(4), Int(-6), Int(1)) array3a := array3.Transform(fnNoChanges) array3b := array3.Transform(fnAbsoluteValuesAndNoOddNumbers) // array3a should have no changes, and should be wrapping the same slice assert.Equal(t, array3, array3a) shouldBeSameSlice(t, array3.data, array3a.data) // array3b should have a transformed slice assert.Equal(t, ValueArrayOf(Int(2), Int(4), Int(6)), array3b) // Edge case where the very first element is dropped array4 := ValueArrayOf(Int(1), Int(2), Int(4)) array4b := array4.Transform(fnAbsoluteValuesAndNoOddNumbers) assert.Equal(t, ValueArrayOf(Int(2), Int(4)), array4b) // Edge case where the only element is dropped array5 := ValueArrayOf(Int(1)) assert.Equal(t, ValueArrayOf(), array5.Transform(fnAbsoluteValuesAndNoOddNumbers)) // Transformation function that uses the index parameter array6 := ValueArrayOf(String("a"), String("b")) assert.Equal(t, ValueArrayOf(String("0=a"), String("1=b")), array6.Transform(fnTransformUsingIndex)) shouldNotCallThis := func(index int, value Value) (Value, bool) { assert.Fail(t, "should not have called function") return value, true } assert.Equal(t, ValueArray{}, ValueArray{}.Transform(shouldNotCallThis)) assert.Equal(t, ValueArrayOf(), ValueArrayOf().Transform(shouldNotCallThis)) } func shouldBeSameSlice(t *testing.T, s0 []Value, s1 []Value) { old := s0[0] s0[0] = String("temp-value") assert.Equal(t, s0, s1, "ValueArrays should be sharing same slice but it was copied instead") s0[0] = old } func shouldNotBeSameSlice(t *testing.T, s0 []Value, s1 []Value) { old := s0[0] s0[0] = String("temp-value") assert.NotEqual(t, s0, s1, "ValueArrays should not be sharing same slice but they are") s0[0] = old }