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, ¬hing); 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 }