package ldvalue import ( "fmt" "sort" "testing" "github.com/stretchr/testify/assert" ) func TestNilValueMap(t *testing.T) { m := ValueMap{} assert.False(t, m.IsDefined()) assert.Equal(t, 0, m.Count()) assert.Equal(t, Null(), m.Get("item")) } func TestCopyValueMap(t *testing.T) { m := map[string]Value{"item0": Int(1), "item1": Int(2)} vm1 := CopyValueMap(m) assert.Equal(t, m, vm1.data) shouldNotBeSameMap(t, m, vm1.data) vm2 := CopyValueMap(nil) assert.Nil(t, vm2.data) assert.False(t, vm2.IsDefined()) vm3 := CopyValueMap(map[string]Value{}) assert.NotNil(t, vm3.data) assert.Equal(t, 0, len(vm3.data)) assert.True(t, vm3.IsDefined()) } func TestCopyArbitraryValueMap(t *testing.T) { m := map[string]interface{}{"item0": "a", "item1": "b"} vm1 := CopyArbitraryValueMap(m) assert.Equal(t, map[string]Value{"item0": String("a"), "item1": String("b")}, vm1.data) vm2 := CopyArbitraryValueMap(nil) assert.Nil(t, vm2.data) } func TestValueMapBuild(t *testing.T) { item0 := String("a") item1 := Int(1) b := ValueMapBuild().Set("item0", item0).Set("item1", item1) valueMap := b.Build() assert.Equal(t, 2, valueMap.Count()) keys := valueMap.Keys(nil) sort.Strings(keys) assert.Equal(t, []string{"item0", "item1"}, keys) item0x := Bool(true) b.Set("item0", item0x) valueMapAfterModifyingBuilder := b.Build() assert.Equal(t, item0x, valueMapAfterModifyingBuilder.Get("item0")) assert.Equal(t, item0, valueMap.Get("item0")) // verifies builder's copy-on-write behavior assert.True(t, valueMap.IsDefined()) m2 := ValueMapBuildWithCapacity(3).Set("item0", item0).Set("item1", item1).Build() assert.Equal(t, valueMap, m2) } func TestValueMapBuildFromMap(t *testing.T) { m0 := ValueMapBuild().Set("item0", Int(1)).Set("item1", Int(2)).Build() m1 := ValueMapBuildFromMap(m0).Build() assert.Equal(t, m0, m1) shouldBeSameMap(t, m0.data, m1.data) m2 := ValueMapBuild().Set("item2", Int(3)) m2.SetAllFromValueMap(m0) assert.Equal(t, ValueMapBuild().Set("item0", Int(1)).Set("item1", Int(2)).Set("item2", Int(3)).Build(), m2.Build()) // test copy-on-write behavior m3 := ValueMapBuild().Set("item0", Int(1)).Build() m4 := ValueMapBuildFromMap(m3).Set("item1", Int(2)).Build() assert.Equal(t, ValueMapBuild().Set("item0", Int(1)).Set("item1", Int(2)).Build(), m4) shouldNotBeSameMap(t, m3.data, m4.data) m5 := ValueMapBuild().SetAllFromValueMap(m3).Set("item1", Int(2)).Build() assert.NotEqual(t, m3, m5) shouldNotBeSameMap(t, m3.data, m5.data) assert.Equal(t, m4, m5) } func TestValueMapBuilderRemove(t *testing.T) { m0 := ValueMapBuild().Set("item0", Int(1)).Set("item1", Int(2)).Remove("item0").Build() assert.Equal(t, ValueMapBuild().Set("item1", Int(2)).Build(), m0) m1 := ValueMapBuildFromMap(m0).Remove("item1").Build() assert.Equal(t, ValueMapBuild().Build(), m1) assert.NotEqual(t, m0, m1) shouldNotBeSameMap(t, m0.data, m1.data) } func TestValueMapBuilderHasKey(t *testing.T) { var b ValueMapBuilder assert.False(t, b.HasKey("key1")) assert.False(t, b.HasKey("key2")) b.Set("key1", Int(1)) assert.True(t, b.HasKey("key1")) assert.False(t, b.HasKey("key2")) b.Set("key2", Int(2)) assert.True(t, b.HasKey("key1")) assert.True(t, b.HasKey("key2")) b.Remove("key1") assert.False(t, b.HasKey("key1")) assert.True(t, b.HasKey("key2")) } func TestValueMapBuilderSafety(t *testing.T) { // empty instance is safe to use var emptyInstance ValueMapBuilder emptyInstance.Set("key", Int(1)) assert.Equal(t, ValueMapBuild().Set("key", Int(1)).Build(), emptyInstance.Build()) // nil pointer is safe to use var nilPtr *ValueMapBuilder assert.Nil(t, nilPtr.Set("key", Int(1))) assert.Nil(t, nilPtr.SetAllFromValueMap(ValueMap{})) assert.Nil(t, nilPtr.Remove("key1")) assert.Equal(t, ValueMap{}, nilPtr.Build()) } func TestValueMapGetByKey(t *testing.T) { item0 := String("a") item1 := Int(1) m := ValueMapBuild().Set("item0", item0).Set("item1", item1).Build() assert.Equal(t, item0, m.Get("item0")) assert.Equal(t, item1, m.Get("item1")) assert.Equal(t, Null(), m.Get("bad-key")) item, ok := m.TryGet("item0") assert.True(t, ok) assert.Equal(t, item0, item) item, ok = m.TryGet("bad-key") assert.False(t, ok) assert.Equal(t, Null(), item) } func TestConvertValueMapToArbitraryValues(t *testing.T) { m := ValueMapBuild().Set("x", ArrayOf(Int(2))).Build() expected := map[string]interface{}{"x": []interface{}{float64(2)}} assert.Equal(t, expected, m.AsArbitraryValueMap()) } func TestConvertValueMapFromArbitraryValuesAndBackAgain(t *testing.T) { mapValue0 := map[string]interface{}{"x": []interface{}{"b"}} m := CopyArbitraryValueMap(mapValue0) mapValue1 := m.AsArbitraryValueMap() assert.Equal(t, mapValue0, mapValue1) // Verify that the map was deep-copied mapValue0["x"].([]interface{})[0] = "c" assert.NotEqual(t, mapValue0, mapValue1) } func TestValueMapEqual(t *testing.T) { valueFns := []func() ValueMap{ func() ValueMap { return ValueMap{} }, func() ValueMap { return ValueMapBuild().Build() }, func() ValueMap { return ValueMapBuild().Set("a", Int(1)).Build() }, func() ValueMap { return ValueMapBuild().Set("a", Int(2)).Build() }, func() ValueMap { return ValueMapBuild().Set("a", Int(1)).Set("b", Int(1)).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 TestValueMapKeys(t *testing.T) { assert.Nil(t, ValueMap{}.Keys(nil)) assert.Nil(t, ValueMapBuild().Build().Keys(nil)) m := ValueMapBuild().Set("a", Int(1)).Build() assert.Equal(t, []string{"a"}, m.Keys(nil)) slice1 := []string{"x", "y", "z"} assert.Equal(t, []string{"a"}, m.Keys(slice1)) assert.Equal(t, []string{"a", "y", "z"}, slice1) // proves slice was reused slice2 := []string{} assert.Equal(t, []string{"a"}, m.Keys(slice2)) m1 := ValueMapBuild().Set("a", Int(1)).Set("b", Int(2)).Set("c", Int(3)).Build() keys := m1.Keys(nil) sort.Strings(keys) assert.Equal(t, []string{"a", "b", "c"}, keys) } func TestValueMapAsValue(t *testing.T) { assert.Equal(t, Null(), ValueMap{}.AsValue()) m := ValueMapBuild().Set("a", Int(1)).Set("b", Int(2)).Build() v := m.AsValue() assert.Equal(t, ObjectBuild().Set("a", Int(1)).Set("b", Int(2)).Build(), v) shouldBeSameMap(t, m.data, v.objectValue.data) } func TestValueMapAsMap(t *testing.T) { assert.Nil(t, ValueMap{}.AsMap()) m := ValueMapBuild().Set("a", Bool(false)).Set("b", Bool(true)).Build() mm := m.AsMap() assert.Equal(t, map[string]Value{"a": Bool(false), "b": Bool(true)}, mm) shouldNotBeSameMap(t, m.data, mm) } func TestValueMapAsArbitraryValueMap(t *testing.T) { assert.Nil(t, ValueMap{}.AsArbitraryValueMap()) m := ValueMapBuild().Set("a", Bool(false)).Set("b", Bool(true)).Build() mm := m.AsArbitraryValueMap() assert.Equal(t, map[string]interface{}{"a": false, "b": true}, mm) } func TestTransformValueMap(t *testing.T) { fnNoChanges := func(key string, value Value) (string, Value, bool) { return key, value, true } fnAbsoluteValuesAndNoOddNumbers := func(key string, value Value) (string, Value, bool) { if value.IntValue()%2 == 1 { return key, value, false // first return value should be ignored since second one is false } if value.IntValue() < 0 { return key, Int(-value.IntValue()), true } return key, value, true } fnTransformUsingKey := func(key string, value Value) (string, Value, bool) { return key, String(fmt.Sprintf("%s=%s", key, value.JSONString())), true } m1 := ValueMapBuild().Set("a", Int(2)).Set("b", Int(4)).Set("c", Int(6)).Build() m1a := m1.Transform(fnNoChanges) m1b := m1.Transform(fnAbsoluteValuesAndNoOddNumbers) // Should have no changes... assert.Equal(t, m1, m1a) assert.Equal(t, m1, m1b) // ...and should be wrapping the *same* map, not a copy m1.data["a"] = Int(0) assert.Equal(t, m1, m1a) assert.Equal(t, m1, m1b) m2 := ValueMapBuild().Set("a", Int(2)).Set("b", Int(4)).Set("c", Int(1)).Set("d", Int(-6)).Build() m2a := m2.Transform(fnNoChanges) m2b := m2.Transform(fnAbsoluteValuesAndNoOddNumbers) // m2a should have no changes, and should be wrapping the same map assert.Equal(t, m2, m2a) m2.data["a"] = Int(0) assert.Equal(t, m2, m2a) // m2b should have a transformed map assert.Equal(t, ValueMapBuild().Set("a", Int(2)).Set("b", Int(4)).Set("d", Int(6)).Build(), m2b) // Edge case where the only element is dropped m3 := ValueMapBuild().Set("a", Int(1)).Build() assert.Equal(t, ValueMapBuild().Build(), m3.Transform(fnAbsoluteValuesAndNoOddNumbers)) // Transformation function that uses the key parameter m4 := ValueMapBuild().Set("a", Int(2)).Set("b", Int(4)).Build() assert.Equal(t, ValueMapBuild().Set("a", String("a=2")).Set("b", String("b=4")).Build(), m4.Transform(fnTransformUsingKey)) // Case where we guarantee that the first element we iterated through is *not* modified - map // iteration order is nondeterministic, we just want to verify that we've hit all code paths n := 0 fnTransformUsingKeyButNotFirst := func(key string, value Value) (string, Value, bool) { n++ if n > 1 { return fnTransformUsingKey(key, value) } return key, value, true } m5 := m4.Transform(fnTransformUsingKeyButNotFirst) assert.NotEqual(t, m4, m5) assert.Equal(t, m4.Count(), m5.Count()) if m5.Get("a").IsNumber() { assert.Equal(t, Int(2), m5.Get("a")) assert.Equal(t, String("b=4"), m5.Get("b")) } else { assert.Equal(t, String("a=2"), m5.Get("a")) assert.Equal(t, Int(4), m5.Get("b")) } shouldNotCallThis := func(key string, value Value) (string, Value, bool) { assert.Fail(t, "should not have called function") return key, value, true } assert.Equal(t, ValueMap{}, ValueMap{}.Transform(shouldNotCallThis)) assert.Equal(t, ValueMapBuild().Build(), ValueMapBuild().Build().Transform(shouldNotCallThis)) } func shouldBeSameMap(t *testing.T, m0 map[string]Value, m1 map[string]Value) { m0["temp-property"] = Bool(true) assert.Equal(t, m0, m1, "ValueMaps should be sharing same map but it was copied instead") delete(m0, "temp-property") } func shouldNotBeSameMap(t *testing.T, m0 map[string]Value, m1 map[string]Value) { m0["temp-property"] = Bool(true) assert.NotEqual(t, m0, m1, "ValueMaps should not be sharing same map but they are") delete(m0, "temp-property") }