1package proptools 2 3import ( 4 "strings" 5 "testing" 6) 7 8func mustHash(t *testing.T, data interface{}) uint64 { 9 t.Helper() 10 result, err := CalculateHash(data) 11 if err != nil { 12 t.Fatal(err) 13 } 14 return result 15} 16 17func TestHashingMapGetsSameResults(t *testing.T) { 18 data := map[string]string{"foo": "bar", "baz": "qux"} 19 first := mustHash(t, data) 20 second := mustHash(t, data) 21 third := mustHash(t, data) 22 fourth := mustHash(t, data) 23 if first != second || second != third || third != fourth { 24 t.Fatal("Did not get the same result every time for a map") 25 } 26} 27 28func TestHashingNonSerializableTypesFails(t *testing.T) { 29 testCases := []struct { 30 name string 31 data interface{} 32 }{ 33 { 34 name: "function pointer", 35 data: []func(){nil}, 36 }, 37 { 38 name: "channel", 39 data: []chan int{make(chan int)}, 40 }, 41 { 42 name: "list with non-serializable type", 43 data: []interface{}{"foo", make(chan int)}, 44 }, 45 } 46 for _, testCase := range testCases { 47 t.Run(testCase.name, func(t *testing.T) { 48 _, err := CalculateHash(testCase) 49 if err == nil { 50 t.Fatal("Expected hashing error but didn't get one") 51 } 52 expected := "data may only contain primitives, strings, arrays, slices, structs, maps, and pointers" 53 if !strings.Contains(err.Error(), expected) { 54 t.Fatalf("Expected %q, got %q", expected, err.Error()) 55 } 56 }) 57 } 58} 59 60var hashTestCases = []struct { 61 name string 62 data interface{} 63}{ 64 { 65 name: "int", 66 data: 5, 67 }, 68 { 69 name: "string", 70 data: "foo", 71 }, 72 { 73 name: "*string", 74 data: StringPtr("foo"), 75 }, 76 { 77 name: "array", 78 data: [3]string{"foo", "bar", "baz"}, 79 }, 80 { 81 name: "slice", 82 data: []string{"foo", "bar", "baz"}, 83 }, 84 { 85 name: "struct", 86 data: struct { 87 foo string 88 bar int 89 }{ 90 foo: "foo", 91 bar: 3, 92 }, 93 }, 94 { 95 name: "map", 96 data: map[string]int{ 97 "foo": 3, 98 "bar": 4, 99 }, 100 }, 101 { 102 name: "list of interfaces with different types", 103 data: []interface{}{"foo", 3, []string{"bar", "baz"}}, 104 }, 105 { 106 name: "nested maps", 107 data: map[string]map[string]map[string]map[string]map[string]int{ 108 "foo": {"foo": {"foo": {"foo": {"foo": 5}}}}, 109 }, 110 }, 111 { 112 name: "multiple maps", 113 data: struct { 114 foo map[string]int 115 bar map[string]int 116 baz map[string]int 117 qux map[string]int 118 quux map[string]int 119 }{ 120 foo: map[string]int{"foo": 1, "bar": 2}, 121 bar: map[string]int{"bar": 2}, 122 baz: map[string]int{"baz": 3, "foo": 1}, 123 qux: map[string]int{"qux": 4}, 124 quux: map[string]int{"quux": 5}, 125 }, 126 }, 127 { 128 name: "nested structs", 129 data: nestableStruct{ 130 foo: nestableStruct{ 131 foo: nestableStruct{ 132 foo: nestableStruct{ 133 foo: nestableStruct{ 134 foo: "foo", 135 }, 136 }, 137 }, 138 }, 139 }, 140 }, 141} 142 143func TestHashSuccessful(t *testing.T) { 144 for _, testCase := range hashTestCases { 145 t.Run(testCase.name, func(t *testing.T) { 146 mustHash(t, testCase.data) 147 }) 148 } 149} 150 151func TestHashingDereferencePointers(t *testing.T) { 152 str1 := "this is a hash test for pointers" 153 str2 := "this is a hash test for pointers" 154 data := []struct { 155 content *string 156 }{ 157 {content: &str1}, 158 {content: &str2}, 159 } 160 first := mustHash(t, data[0]) 161 second := mustHash(t, data[1]) 162 if first != second { 163 t.Fatal("Got different results for the same string") 164 } 165} 166 167type nestableStruct struct { 168 foo interface{} 169} 170 171func TestContainsConfigurable(t *testing.T) { 172 testCases := []struct { 173 name string 174 value any 175 result bool 176 }{ 177 { 178 name: "struct without configurable", 179 value: struct { 180 S string 181 }{}, 182 result: false, 183 }, 184 { 185 name: "struct with configurable", 186 value: struct { 187 S Configurable[string] 188 }{}, 189 result: true, 190 }, 191 { 192 name: "struct with allowed configurable", 193 value: struct { 194 S Configurable[string] `blueprint:"allow_configurable_in_provider"` 195 }{}, 196 result: false, 197 }, 198 } 199 200 for _, testCase := range testCases { 201 t.Run(testCase.name, func(t *testing.T) { 202 got := ContainsConfigurable(testCase.value) 203 if got != testCase.result { 204 t.Errorf("expected %v, got %v", testCase.value, got) 205 } 206 }) 207 } 208} 209 210func BenchmarkCalculateHash(b *testing.B) { 211 for _, testCase := range hashTestCases { 212 b.Run(testCase.name, func(b *testing.B) { 213 b.ReportAllocs() 214 for i := 0; i < b.N; i++ { 215 _, err := CalculateHash(testCase.data) 216 if err != nil { 217 panic(err) 218 } 219 } 220 }) 221 } 222} 223