terraform/helper/schema/set_test.go

218 lines
3.9 KiB
Go
Raw Normal View History

2014-08-21 03:30:28 +02:00
package schema
import (
"reflect"
"testing"
)
func TestSetAdd(t *testing.T) {
s := &Set{F: testSetInt}
s.Add(1)
s.Add(5)
s.Add(25)
Change Set internals and make (extreme) performance improvements Changing the Set internals makes a lot of sense as it saves doing conversions in multiple places and gives a central place to alter the key when a item is computed. This will have no side effects other then that the ordering is now based on strings instead on integers, so the order will be different. This will however have no effect on existing configs as these will use the individual codes/keys and not the ordering to determine if there is a diff or not. Lastly (but I think also most importantly) there is a fix in this PR that makes diffing sets extremely more performand. Before a full diff required reading the complete Set for every single parameter/attribute you wanted to diff, while now it only gets that specific parameter. We have a use case where we have a Set that has 18 parameters and the set consist of about 600 items (don't ask :wink:). So when doing a diff it would take 100% CPU of all cores and stay that way for almost an hour before being able to complete the diff. Debugging this we learned that for retrieving every single parameter it made over 52.000 calls to `func (c *ResourceConfig) get(..)`. In this function a slice is created and used only for the duration of the call, so the time needed to create all needed slices and on the other hand the time the garbage collector needed to clean them up again caused the system to cripple itself. Next to that there are also some expensive reflect calls in this function which also claimed a fair amount of CPU time. After this fix the number of calls needed to get a single parameter dropped from 52.000+ to only 2! :smiley:
2015-11-18 11:24:04 +01:00
expected := []interface{}{1, 25, 5}
2014-08-21 03:30:28 +02:00
actual := s.List()
if !reflect.DeepEqual(actual, expected) {
t.Fatalf("bad: %#v", actual)
}
}
func TestSetAdd_negative(t *testing.T) {
// Since we don't allow negative hashes, this should just hash to the
// same thing...
s := &Set{F: testSetInt}
s.Add(-1)
s.Add(1)
expected := []interface{}{-1}
actual := s.List()
if !reflect.DeepEqual(actual, expected) {
t.Fatalf("bad: %#v", actual)
}
}
2014-08-21 06:02:42 +02:00
func TestSetContains(t *testing.T) {
s := &Set{F: testSetInt}
s.Add(5)
s.Add(-5)
2014-08-21 06:02:42 +02:00
if s.Contains(2) {
t.Fatal("should not contain")
}
if !s.Contains(5) {
t.Fatal("should contain")
}
if !s.Contains(-5) {
t.Fatal("should contain")
}
2014-08-21 06:02:42 +02:00
}
2014-08-21 03:30:28 +02:00
func TestSetDifference(t *testing.T) {
s1 := &Set{F: testSetInt}
2014-08-21 07:24:35 +02:00
s2 := &Set{F: testSetInt}
2014-08-21 03:30:28 +02:00
s1.Add(1)
s1.Add(5)
s2.Add(5)
s2.Add(25)
difference := s1.Difference(s2)
difference.Add(2)
expected := []interface{}{1, 2}
actual := difference.List()
2014-08-21 03:30:28 +02:00
if !reflect.DeepEqual(actual, expected) {
t.Fatalf("bad: %#v", actual)
}
}
func TestSetIntersection(t *testing.T) {
s1 := &Set{F: testSetInt}
2014-08-21 07:24:35 +02:00
s2 := &Set{F: testSetInt}
2014-08-21 03:30:28 +02:00
s1.Add(1)
s1.Add(5)
s2.Add(5)
s2.Add(25)
intersection := s1.Intersection(s2)
intersection.Add(2)
expected := []interface{}{2, 5}
actual := intersection.List()
2014-08-21 03:30:28 +02:00
if !reflect.DeepEqual(actual, expected) {
t.Fatalf("bad: %#v", actual)
}
}
func TestSetUnion(t *testing.T) {
s1 := &Set{F: testSetInt}
2014-08-21 07:24:35 +02:00
s2 := &Set{F: testSetInt}
2014-08-21 03:30:28 +02:00
s1.Add(1)
s1.Add(5)
s2.Add(5)
s2.Add(25)
union := s1.Union(s2)
union.Add(2)
Change Set internals and make (extreme) performance improvements Changing the Set internals makes a lot of sense as it saves doing conversions in multiple places and gives a central place to alter the key when a item is computed. This will have no side effects other then that the ordering is now based on strings instead on integers, so the order will be different. This will however have no effect on existing configs as these will use the individual codes/keys and not the ordering to determine if there is a diff or not. Lastly (but I think also most importantly) there is a fix in this PR that makes diffing sets extremely more performand. Before a full diff required reading the complete Set for every single parameter/attribute you wanted to diff, while now it only gets that specific parameter. We have a use case where we have a Set that has 18 parameters and the set consist of about 600 items (don't ask :wink:). So when doing a diff it would take 100% CPU of all cores and stay that way for almost an hour before being able to complete the diff. Debugging this we learned that for retrieving every single parameter it made over 52.000 calls to `func (c *ResourceConfig) get(..)`. In this function a slice is created and used only for the duration of the call, so the time needed to create all needed slices and on the other hand the time the garbage collector needed to clean them up again caused the system to cripple itself. Next to that there are also some expensive reflect calls in this function which also claimed a fair amount of CPU time. After this fix the number of calls needed to get a single parameter dropped from 52.000+ to only 2! :smiley:
2015-11-18 11:24:04 +01:00
expected := []interface{}{1, 2, 25, 5}
actual := union.List()
2014-08-21 03:30:28 +02:00
if !reflect.DeepEqual(actual, expected) {
t.Fatalf("bad: %#v", actual)
}
}
func testSetInt(v interface{}) int {
return v.(int)
}
func TestHashResource_nil(t *testing.T) {
resource := &Resource{
Schema: map[string]*Schema{
"name": {
Type: TypeString,
Optional: true,
},
},
}
f := HashResource(resource)
idx := f(nil)
if idx != 0 {
t.Fatalf("Expected 0 when hashing nil, given: %d", idx)
}
}
func TestHashEqual(t *testing.T) {
nested := &Resource{
Schema: map[string]*Schema{
"foo": {
Type: TypeString,
Optional: true,
},
},
}
root := &Resource{
Schema: map[string]*Schema{
"bar": {
Type: TypeString,
Optional: true,
},
"nested": {
Type: TypeSet,
Optional: true,
Elem: nested,
},
},
}
n1 := map[string]interface{}{"foo": "bar"}
n2 := map[string]interface{}{"foo": "baz"}
r1 := map[string]interface{}{
"bar": "baz",
"nested": NewSet(HashResource(nested), []interface{}{n1}),
}
r2 := map[string]interface{}{
"bar": "qux",
"nested": NewSet(HashResource(nested), []interface{}{n2}),
}
r3 := map[string]interface{}{
"bar": "baz",
"nested": NewSet(HashResource(nested), []interface{}{n2}),
}
r4 := map[string]interface{}{
"bar": "qux",
"nested": NewSet(HashResource(nested), []interface{}{n1}),
}
s1 := NewSet(HashResource(root), []interface{}{r1})
s2 := NewSet(HashResource(root), []interface{}{r2})
s3 := NewSet(HashResource(root), []interface{}{r3})
s4 := NewSet(HashResource(root), []interface{}{r4})
cases := []struct {
name string
set *Set
compare *Set
expected bool
}{
{
name: "equal",
set: s1,
compare: s1,
expected: true,
},
{
name: "not equal",
set: s1,
compare: s2,
expected: false,
},
{
name: "outer equal, should still not be equal",
set: s1,
compare: s3,
expected: false,
},
{
name: "inner equal, should still not be equal",
set: s1,
compare: s4,
expected: false,
},
}
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
actual := tc.set.HashEqual(tc.compare)
if tc.expected != actual {
t.Fatalf("expected %t, got %t", tc.expected, actual)
}
})
}
}