github.com/m3db/m3@v1.5.1-0.20231129193456-75a402aa583b/src/x/test/testmarshal/marshal.go (about) 1 // Copyright (c) 2019 Uber Technologies, Inc. 2 // 3 // Permission is hereby granted, free of charge, to any person obtaining a copy 4 // of this software and associated documentation files (the "Software"), to deal 5 // in the Software without restriction, including without limitation the rights 6 // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 // copies of the Software, and to permit persons to whom the Software is 8 // furnished to do so, subject to the following conditions: 9 // 10 // The above copyright notice and this permission notice shall be included in 11 // all copies or substantial portions of the Software. 12 // 13 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 // THE SOFTWARE. 20 21 // Package testmarshal provides some assertions around marshalling/unmarshalling 22 // (serialization/deserialization) behavior for types. It is intended to reduce 23 // boilerplate in tests of the form: 24 // 25 // func TestMyTypeUnmarshals(t *testing.T) { 26 // type MyType struct{} 27 // var mt MyType 28 // require.NoError(t, json.Unmarshal([]byte("{}"), &mt)) 29 // assert.Equal(t, MyType{}, mt) 30 // } 31 // 32 // with assertion calls: 33 // func TestMyTypeUnmarshals(t *testing.T) { 34 // type MyType struct{} 35 // testmarshal.AssertUnmarshals(t, testmarshal.JSONMarshaler, MyType{}, []byte("{}")) 36 // } 37 package testmarshal 38 39 import ( 40 "encoding" 41 "encoding/json" 42 "fmt" 43 "reflect" 44 "testing" 45 46 "github.com/stretchr/testify/assert" 47 "gopkg.in/yaml.v2" 48 ) 49 50 // Marshaler represents a serialization protocol, e.g. JSON or YAML 51 type Marshaler interface { 52 // Marshal converts a Go type into bytes 53 Marshal(interface{}) ([]byte, error) 54 55 // Unmarshal converts bytes into a Go type. 56 Unmarshal([]byte, interface{}) error 57 58 // ID identifies the protocol, mostly for use in test naming. 59 ID() string 60 } 61 62 var ( 63 // JSONMarshaler uses the encoding/json package to marshal types 64 JSONMarshaler Marshaler = simpleMarshaler{ 65 id: "json", 66 marshal: json.Marshal, 67 unmarshal: json.Unmarshal, 68 } 69 70 // YAMLMarshaler uses the gopkg.in/yaml.v2 package to marshal types 71 YAMLMarshaler Marshaler = simpleMarshaler{ 72 id: "yaml", 73 marshal: yaml.Marshal, 74 unmarshal: yaml.Unmarshal, 75 } 76 77 // TextMarshaler marshals types which implement both encoding.TextMarshaler 78 // and encoding.TextUnmarshaler 79 TextMarshaler Marshaler = simpleMarshaler{ 80 id: "text", 81 marshal: func(i interface{}) ([]byte, error) { 82 switch m := i.(type) { 83 case encoding.TextMarshaler: 84 return m.MarshalText() 85 default: 86 return nil, fmt.Errorf("not an encoding.TextMarshaler") 87 } 88 }, 89 unmarshal: func(bytes []byte, i interface{}) error { 90 switch m := i.(type) { 91 case encoding.TextUnmarshaler: 92 return m.UnmarshalText(bytes) 93 default: 94 return fmt.Errorf("not an encoding.TextUnmarshaler") 95 } 96 }, 97 } 98 ) 99 100 type simpleMarshaler struct { 101 marshal func(interface{}) ([]byte, error) 102 unmarshal func([]byte, interface{}) error 103 id string 104 } 105 106 func (sm simpleMarshaler) Marshal(v interface{}) ([]byte, error) { 107 return sm.marshal(v) 108 } 109 110 func (sm simpleMarshaler) Unmarshal(d []byte, v interface{}) error { 111 return sm.unmarshal(d, v) 112 } 113 114 func (sm simpleMarshaler) ID() string { 115 return sm.id 116 } 117 118 // AssertMarshalingRoundtrips checks that the marshaller "roundtrips" example 119 // i.e.: 120 // marshaler.Unmarshal(marshaler.Marshal(example)) == example. 121 // 122 // It is intended to replace tests of the form: 123 // 124 // func TestMyTypeRoundtrips(t *testing.T) { 125 // type MyType struct{} 126 // mt := MyType{} 127 // d, err := json.Marshal(mt) 128 // require.NoError(t, err) 129 // 130 // var revived MyType 131 // require.NoError(t, json.Unmarshal(d, &revived)) 132 // assert.Equal(t, mt, revived) 133 // } 134 // 135 // with: 136 137 // func TestMyTypeRoundtrips(t *testing.T) { 138 // type MyType struct{} 139 // testmarshal.AssertMarshalingRoundtrips(t, testmarshal.JSONMarshaler, MyType{}) 140 // } 141 func AssertMarshalingRoundtrips(t *testing.T, marshaller Marshaler, example interface{}) bool { 142 d, err := marshaller.Marshal(example) 143 if !assert.NoError(t, err) { 144 return false 145 } 146 reconstituted, err := unmarshalIntoNewValueOfType(marshaller, example, d) 147 if !assert.NoError(t, err) { 148 return false 149 } 150 151 return assert.Equal(t, example, reconstituted) 152 } 153 154 // AssertUnmarshals checks that the given data successfully unmarshals into a 155 // value which assert.Equal's expected. 156 // It is intended to replace tests of the form: 157 // 158 // func TestMyTypeUnmarshals(t *testing.T) { 159 // type MyType struct{} 160 // var mt MyType 161 // require.NoError(t, json.Unmarshal([]byte("{}"), &mt)) 162 // assert.Equal(t, MyType{}, mt) 163 // } 164 // 165 // with: 166 167 // func TestMyTypeUnmarshals(t *testing.T) { 168 // type MyType struct{} 169 // testmarshal.AssertUnmarshals(t, testmarshal.JSONMarshaler, MyType{}, []byte("{}")) 170 // } 171 172 func AssertUnmarshals(t *testing.T, marshaller Marshaler, expected interface{}, data []byte) bool { 173 unmarshalled, err := unmarshalIntoNewValueOfType(marshaller, expected, data) 174 if !assert.NoError(t, err) { 175 return false 176 } 177 178 return assert.Equal(t, expected, unmarshalled) 179 } 180 181 // AssertMarshals checks that the given value marshals into data equal 182 // to expectedData. 183 // It is intended to replace tests of the form: 184 // 185 // func TestMyTypeMarshals(t *testing.T) { 186 // type MyType struct{} 187 // mt := MyType{} 188 // d, err := json.Marshal(mt) 189 // require.NoError(t, err) 190 // assert.Equal(t, d, []byte("{}")) 191 // } 192 // 193 // with: 194 // 195 // func TestMyTypeUnmarshals(t *testing.T) { 196 // type MyType struct{} 197 // testmarshal.AssertMarshals(t, testmarshal.JSONMarshaler, MyType{}, []byte("{}")) 198 // } 199 func AssertMarshals(t *testing.T, marshaller Marshaler, toMarshal interface{}, expectedData []byte) bool { 200 marshalled, err := marshaller.Marshal(toMarshal) 201 if !assert.NoError(t, err) { 202 return false 203 } 204 205 return assert.Equal(t, string(expectedData), string(marshalled)) 206 } 207 208 // unmarshalIntoNewValueOfType is a helper to unmarshal a new instance of the same type as value 209 // from data. 210 func unmarshalIntoNewValueOfType(marshaller Marshaler, value interface{}, data []byte) (interface{}, error) { 211 ptrToUnmarshalTarget := reflect.New(reflect.ValueOf(value).Type()) 212 unmarshalTarget := ptrToUnmarshalTarget.Elem() 213 if err := marshaller.Unmarshal(data, ptrToUnmarshalTarget.Interface()); err != nil { 214 return nil, err 215 } 216 return unmarshalTarget.Interface(), nil 217 } 218 219 // Require wraps an Assert call and turns it into a require.* call (fails if 220 // the assert fails). 221 func Require(t *testing.T, b bool) { 222 if !b { 223 t.FailNow() 224 } 225 } 226 227 // TestMarshalersRoundtrip is a helper which runs a test for each provided marshaller 228 // on each example in examples (a slice of any type). The test checks that the 229 // marshaler "roundtrips" for each example, i.e.: 230 // marshaler.Unmarshal(marshaler.Marshal(example)) == example. 231 func TestMarshalersRoundtrip(t *testing.T, examples interface{}, marshallers []Marshaler) { 232 for _, m := range marshallers { 233 t.Run(m.ID(), func(t *testing.T) { 234 v := reflect.ValueOf(examples) 235 if v.Type().Kind() != reflect.Slice { 236 t.Fatalf("examples must be a slice; got %+v", examples) 237 } 238 239 // taken from https://stackoverflow.com/questions/14025833/range-over-interface-which-stores-a-slice 240 for i := 0; i < v.Len(); i++ { 241 example := v.Index(i).Interface() 242 Require(t, AssertMarshalingRoundtrips(t, m, example)) 243 } 244 }) 245 246 } 247 }