github.com/philpearl/plenc@v0.0.15/marshal_test.go (about)

     1  package plenc
     2  
     3  import (
     4  	"encoding/json"
     5  	"fmt"
     6  	"testing"
     7  	"time"
     8  
     9  	"github.com/google/go-cmp/cmp"
    10  	fuzz "github.com/google/gofuzz"
    11  	"github.com/philpearl/plenc/plenccore"
    12  )
    13  
    14  type InnerThing struct {
    15  	A string    `plenc:"1"`
    16  	B float64   `plenc:"2"`
    17  	C time.Time `plenc:"3"`
    18  }
    19  
    20  type SliceThing []InnerThing
    21  
    22  type RecursiveThing struct {
    23  	A []RecursiveThing `plenc:"1"`
    24  	B int              `plenc:"2"`
    25  }
    26  
    27  type TestThing struct {
    28  	A   float64     `plenc:"1"`
    29  	B   []float64   `plenc:"2"`
    30  	C   *float64    `plenc:"3"`
    31  	D   float32     `plenc:"4"`
    32  	E   []float32   `plenc:"5"`
    33  	F   *float32    `plenc:"6"`
    34  	G   int         `plenc:"7"`
    35  	H   []int       `plenc:"8"`
    36  	I   *int        `plenc:"9"`
    37  	J   uint        `plenc:"10"`
    38  	K   []uint      `plenc:"11"`
    39  	L   *uint       `plenc:"12"`
    40  	M   bool        `plenc:"13"`
    41  	N   []bool      `plenc:"14"`
    42  	O   *bool       `plenc:"15"`
    43  	P   string      `plenc:"16"`
    44  	Q   []string    `plenc:"17"`
    45  	R   *string     `plenc:"18"`
    46  	S   time.Time   `plenc:"19"`
    47  	T   []time.Time `plenc:"20"`
    48  	U   *time.Time  `plenc:"21"`
    49  	V   int32       `plenc:"22"`
    50  	W   []int32     `plenc:"23"`
    51  	X   *int32      `plenc:"24"`
    52  	Y   int64       `plenc:"25"`
    53  	Z   []int64     `plenc:"26"`
    54  	A1  *int64      `plenc:"27"`
    55  	A2  int16       `plenc:"29"`
    56  	A3  []int16     `plenc:"30"`
    57  	A4  *int16      `plenc:"31"`
    58  	A5  uint8       `plenc:"32"`
    59  	A6  []uint8     `plenc:"33"`
    60  	A7  *uint8      `plenc:"34"`
    61  	A8  int8        `plenc:"37"`
    62  	A9  []int8      `plenc:"38"`
    63  	A10 *int8       `plenc:"39"`
    64  	A11 uint64      `plenc:"40"`
    65  	A12 []uint64    `plenc:"41"`
    66  	A13 *uint64     `plenc:"42"`
    67  	A14 uint16      `plenc:"43"`
    68  	A15 []uint16    `plenc:"44"`
    69  	A16 *uint16     `plenc:"45"`
    70  
    71  	Z1 InnerThing    `plenc:"28"`
    72  	Z2 []InnerThing  `plenc:"35"`
    73  	Z3 *InnerThing   `plenc:"36"`
    74  	ZZ SliceThing    `plenc:"46"`
    75  	Z4 []*InnerThing `plenc:"48"`
    76  
    77  	M1 map[string]string `plenc:"47"`
    78  
    79  	// These two are not currently supported. And I may have made it difficult
    80  	// to efficiently support them!
    81  	// X1 [][]InnerThing `plenc:"49"`
    82  	// X2 [][]string     `plenc:"50"`
    83  
    84  	X3 [][]uint    `plenc:"51"`
    85  	X4 [][]float32 `plenc:"52"`
    86  
    87  	R1 RecursiveThing `plenc:"53"`
    88  }
    89  
    90  func TestMarshal(t *testing.T) {
    91  	f := fuzz.New().Funcs(func(out **InnerThing, cont fuzz.Continue) {
    92  		// We don't support having nil entries in slices of pointers
    93  		var v InnerThing
    94  		cont.Fuzz(&v)
    95  		*out = &v
    96  	}).MaxDepth(4)
    97  	for i := 0; i < 10000; i++ {
    98  		var in TestThing
    99  		f.Fuzz(&in)
   100  
   101  		data, err := Marshal(nil, &in)
   102  		if err != nil {
   103  			t.Fatal(err)
   104  		}
   105  
   106  		var out TestThing
   107  		if err := Unmarshal(data, &out); err != nil {
   108  			t.Fatal(err)
   109  		}
   110  
   111  		if diff := cmp.Diff(in, out); diff != "" {
   112  			t.Logf("%x", data)
   113  
   114  			var out TestThing
   115  			if err := Unmarshal(data, &out); err != nil {
   116  				t.Fatal(err)
   117  			}
   118  			if diff := cmp.Diff(in, out); diff != "" {
   119  				t.Logf("re-run differs too")
   120  			} else {
   121  				t.Logf("re-run does not differ")
   122  			}
   123  
   124  			t.Fatalf("structs differ. %s", diff)
   125  		}
   126  	}
   127  }
   128  
   129  func TestMarshalSlice(t *testing.T) {
   130  	tests := []SliceThing{
   131  		{{A: "a"}},
   132  		nil,
   133  		// Non-nil empty slices will show up as nil slices
   134  	}
   135  
   136  	for _, test := range tests {
   137  		t.Run(fmt.Sprintf("%#v", test), func(t *testing.T) {
   138  			data, err := Marshal(nil, test)
   139  			if err != nil {
   140  				t.Fatal(err)
   141  			}
   142  			var b SliceThing
   143  
   144  			if err := Unmarshal(data, &b); err != nil {
   145  				t.Fatal(err)
   146  			}
   147  
   148  			if diff := cmp.Diff(test, b); diff != "" {
   149  				t.Fatalf("not as expected. %s\n data %x", diff, data)
   150  			}
   151  		})
   152  	}
   153  }
   154  
   155  func TestMarshalSliceFloat(t *testing.T) {
   156  	tests := [][]float32{
   157  		{1.0, 2.0},
   158  		nil,
   159  		// Non-nil empty slices will show up as nil slices
   160  	}
   161  
   162  	for _, test := range tests {
   163  		t.Run(fmt.Sprintf("%#v", test), func(t *testing.T) {
   164  			data, err := Marshal(nil, test)
   165  			if err != nil {
   166  				t.Fatal(err)
   167  			}
   168  			var b []float32
   169  
   170  			if err := Unmarshal(data, &b); err != nil {
   171  				t.Fatal(err)
   172  			}
   173  
   174  			if diff := cmp.Diff(test, b); diff != "" {
   175  				t.Fatalf("not as expected. %s\n data %x", diff, data)
   176  			}
   177  		})
   178  	}
   179  }
   180  
   181  func TestMarshalPtrSliceFloat(t *testing.T) {
   182  	one, two := 1.0, 2.0
   183  	in := []*float64{&one, &two}
   184  	_, err := Marshal(nil, in)
   185  	if err == nil {
   186  		t.Fatalf("expected an error marshaling an array of floats")
   187  	}
   188  	if err.Error() != "slices of pointers to float32 & float64 are not supported" {
   189  		t.Errorf("error %q not as expected", err)
   190  	}
   191  }
   192  
   193  func TestMarshalPtrSliceInt(t *testing.T) {
   194  	one, two := 1, 2
   195  	tests := []struct {
   196  		in  []*int
   197  		exp []*int
   198  	}{
   199  		{in: []*int{&one, &two}, exp: []*int{&one, &two}},
   200  		{in: nil, exp: nil},
   201  		// nils in arrays are problematic. This is basically not allowed
   202  		{in: []*int{&one, nil, &two}, exp: []*int{&one, &two}},
   203  		// empty arrays translate to nil arrays
   204  		{in: []*int{}, exp: nil},
   205  	}
   206  	for _, test := range tests {
   207  		t.Run(fmt.Sprintf("%#v", test.in), func(t *testing.T) {
   208  			data, err := Marshal(nil, test.in)
   209  			if err != nil {
   210  				t.Fatal(err)
   211  			}
   212  
   213  			var out []*int
   214  			if err := Unmarshal(data, &out); err != nil {
   215  				t.Fatal(err)
   216  			}
   217  
   218  			if diff := cmp.Diff(test.exp, out); diff != "" {
   219  				t.Fatalf("not as expected. %s\n data %x", diff, data)
   220  			}
   221  		})
   222  	}
   223  }
   224  
   225  func TestSkip(t *testing.T) {
   226  	f := fuzz.New().MaxDepth(4)
   227  	for i := 0; i < 100; i++ {
   228  		var in TestThing
   229  		f.Fuzz(&in)
   230  
   231  		data, err := Marshal(nil, &in)
   232  		if err != nil {
   233  			t.Fatal(err)
   234  		}
   235  
   236  		// This should skip everything, but we don't know unless it errors
   237  		type nowt struct{}
   238  		var nothing nowt
   239  		if err := Unmarshal(data, &nothing); err != nil {
   240  			t.Fatal(err)
   241  		}
   242  
   243  		// So lets do a lower level skip
   244  		i := 0
   245  		for i < len(data) {
   246  			wt, _, n := plenccore.ReadTag(data[i:])
   247  			if n < 0 {
   248  				t.Fatalf("problem reading tag")
   249  			}
   250  			i += n
   251  			n, err := plenccore.Skip(data[i:], wt)
   252  			if err != nil {
   253  				t.Fatal(err)
   254  			}
   255  			i += n
   256  		}
   257  		if i != len(data) {
   258  			t.Fatal("data length not as expected")
   259  		}
   260  	}
   261  }
   262  
   263  func TestMarshalUnmarked(t *testing.T) {
   264  	type unmarked struct {
   265  		A string
   266  	}
   267  
   268  	var in unmarked
   269  	_, err := Marshal(nil, &in)
   270  	if err == nil {
   271  		t.Errorf("expected an error as field has no plenc tag")
   272  	}
   273  	if err.Error() != "no plenc tag on field 0 A of unmarked" {
   274  		t.Errorf("error %q not as expected", err)
   275  	}
   276  }
   277  
   278  func TestMarshalDuplicate(t *testing.T) {
   279  	type duplicate struct {
   280  		A string  `plenc:"1"`
   281  		B *string `plenc:"1"`
   282  	}
   283  
   284  	var in duplicate
   285  	_, err := Marshal(nil, &in)
   286  	if err == nil {
   287  		t.Errorf("expected an error as fields have duplicate plenc tags")
   288  	}
   289  	if err.Error() != "failed building codec for duplicate. Multiple fields have index 1" {
   290  		t.Errorf("error %q not as expected", err)
   291  	}
   292  }
   293  
   294  func TestMarshalComplex(t *testing.T) {
   295  	type my struct {
   296  		A complex64 `plenc:"1"`
   297  	}
   298  
   299  	var in my
   300  	_, err := Marshal(nil, &in)
   301  	if err == nil {
   302  		t.Errorf("expected an error as complex types aren't supported")
   303  	}
   304  	if err.Error() != "failed to find codec for field 0 (A, \"\") of my. could not find or create a codec for complex64" {
   305  		t.Errorf("error %q not as expected", err)
   306  	}
   307  }
   308  
   309  func TestUnMarshalComplex(t *testing.T) {
   310  	type my struct {
   311  		A complex64 `plenc:"1"`
   312  	}
   313  
   314  	var in my
   315  	err := Unmarshal(nil, &in)
   316  	if err == nil {
   317  		t.Errorf("expected an error as complex types aren't supported")
   318  	}
   319  	if err.Error() != "failed to find codec for field 0 (A, \"\") of my. could not find or create a codec for complex64" {
   320  		t.Errorf("error %q not as expected", err)
   321  	}
   322  }
   323  
   324  func TestUnmarshalNoPtr(t *testing.T) {
   325  	var a int
   326  	err := Unmarshal([]byte{}, a)
   327  	if err == nil {
   328  		t.Fatal("expected an error from unmarshal as is requires a pointer")
   329  	}
   330  	if err.Error() != "you must pass in a non-nil pointer" {
   331  		t.Errorf("error %q not as expected", err)
   332  	}
   333  }
   334  
   335  func TestUnmarshalNilPtr(t *testing.T) {
   336  	var a *int
   337  	err := Unmarshal([]byte{}, a)
   338  	if err == nil {
   339  		t.Fatal("expected an error from unmarshal as is requires a pointer")
   340  	}
   341  	if err.Error() != "you must pass in a non-nil pointer" {
   342  		t.Errorf("error %q not as expected", err)
   343  	}
   344  }
   345  
   346  func BenchmarkCycle(b *testing.B) {
   347  	f := fuzz.NewWithSeed(1337).MaxDepth(4)
   348  	var in TestThing
   349  	f.Fuzz(&in)
   350  
   351  	b.Run("plenc", func(b *testing.B) {
   352  		b.ReportAllocs()
   353  		b.RunParallel(func(pb *testing.PB) {
   354  			var data []byte
   355  			for pb.Next() {
   356  				var err error
   357  				data, err = Marshal(data[:0], &in)
   358  				if err != nil {
   359  					b.Fatal(err)
   360  				}
   361  				var out TestThing
   362  				if err := Unmarshal(data, &out); err != nil {
   363  					b.Fatal(err)
   364  				}
   365  			}
   366  		})
   367  	})
   368  
   369  	b.Run("json", func(b *testing.B) {
   370  		b.ReportAllocs()
   371  		b.RunParallel(func(pb *testing.PB) {
   372  			for pb.Next() {
   373  				var err error
   374  				data, err := json.Marshal(&in)
   375  				if err != nil {
   376  					b.Fatal(err)
   377  				}
   378  				var out TestThing
   379  				if err := json.Unmarshal(data, &out); err != nil {
   380  					b.Fatal(err)
   381  				}
   382  			}
   383  		})
   384  	})
   385  }
   386  
   387  func TestNamedTypes(t *testing.T) {
   388  	type Bool bool
   389  	type Int int
   390  	type Int64 int64
   391  	type Int32 int32
   392  	type Int16 int16
   393  	type Int8 int8
   394  	type Float64 float64
   395  	type Float32 float32
   396  	type Uint uint
   397  	type Uint64 uint64
   398  	type Uint32 uint32
   399  	type Uint16 uint16
   400  	type Uint8 uint8
   401  	type String string
   402  
   403  	type MyStruct struct {
   404  		V1  Bool    `plenc:"1"`
   405  		V2  Int     `plenc:"2"`
   406  		V3  Float64 `plenc:"3"`
   407  		V4  Float32 `plenc:"4"`
   408  		V5  Uint    `plenc:"5"`
   409  		V6  String  `plenc:"6"`
   410  		V7  Int64   `plenc:"7"`
   411  		V8  Int32   `plenc:"8"`
   412  		V9  Int16   `plenc:"9"`
   413  		V10 Int8    `plenc:"10"`
   414  		V11 Uint64  `plenc:"11"`
   415  		V12 Uint32  `plenc:"12"`
   416  		V13 Uint16  `plenc:"13"`
   417  		V14 Uint8   `plenc:"14"`
   418  	}
   419  
   420  	var in, out MyStruct
   421  
   422  	f := fuzz.New()
   423  	f.Fuzz(&in)
   424  
   425  	data, err := Marshal(nil, &in)
   426  	if err != nil {
   427  		t.Fatal(err)
   428  	}
   429  
   430  	if err := Unmarshal(data, &out); err != nil {
   431  		t.Fatal(err)
   432  	}
   433  
   434  	if diff := cmp.Diff(in, out); diff != "" {
   435  		t.Fatalf("results differ. %s", diff)
   436  	}
   437  }