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  }