github.com/philpearl/plenc@v0.0.15/null/null_test.go (about) 1 package null 2 3 import ( 4 "database/sql" 5 "encoding/json" 6 "reflect" 7 "testing" 8 "time" 9 10 "github.com/google/go-cmp/cmp" 11 fuzz "github.com/google/gofuzz" 12 "github.com/philpearl/plenc" 13 "github.com/philpearl/plenc/plenccodec" 14 "github.com/unravelin/null" 15 ) 16 17 func init() { 18 RegisterCodecs() 19 } 20 21 var fuzzFuncs = []interface{}{ 22 func(a *null.Bool, c fuzz.Continue) { 23 a.Valid = c.RandBool() 24 if a.Valid { 25 a.Bool = c.RandBool() 26 } 27 }, 28 func(a *null.Float, c fuzz.Continue) { 29 a.Valid = c.RandBool() 30 if a.Valid { 31 c.Fuzz(&a.Float64) 32 } 33 }, 34 func(a *null.Int, c fuzz.Continue) { 35 a.Valid = c.RandBool() 36 if a.Valid { 37 c.Fuzz(&a.Int64) 38 } 39 }, 40 func(a *null.String, c fuzz.Continue) { 41 a.Valid = c.RandBool() 42 if a.Valid { 43 c.Fuzz(&a.String) 44 } 45 }, 46 func(a *null.Time, c fuzz.Continue) { 47 a.Valid = c.RandBool() 48 if a.Valid { 49 c.Fuzz(&a.Time) 50 } 51 }, 52 } 53 54 func TestNullEmpty(t *testing.T) { 55 type TestThing struct { 56 I null.Int `plenc:"1"` 57 B null.Bool `plenc:"2"` 58 F null.Float `plenc:"3"` 59 S null.String `plenc:"4"` 60 T null.Time `plenc:"5"` 61 } 62 var v TestThing 63 64 data, err := plenc.Marshal(nil, &v) 65 if err != nil { 66 t.Fatal(err) 67 } 68 if len(data) != 0 { 69 t.Fatalf("expected no data, got %x", data) 70 } 71 } 72 73 func TestNullExplicit(t *testing.T) { 74 tests := []struct { 75 name string 76 in interface{} 77 out interface{} 78 exp interface{} 79 }{ 80 { 81 name: "empty valid string", 82 in: &null.String{sql.NullString{Valid: true}}, 83 out: &null.String{}, 84 exp: &null.String{sql.NullString{Valid: true}}, 85 }, 86 { 87 name: "non-empty valid string", 88 in: &null.String{sql.NullString{Valid: true, String: "a"}}, 89 out: &null.String{}, 90 exp: &null.String{sql.NullString{Valid: true, String: "a"}}, 91 }, 92 { 93 name: "zero valid int", 94 in: &null.Int{sql.NullInt64{Valid: true, Int64: 0}}, 95 out: &null.Int{}, 96 exp: &null.Int{sql.NullInt64{Valid: true, Int64: 0}}, 97 }, 98 { 99 name: "positive valid int", 100 in: &null.Int{sql.NullInt64{Valid: true, Int64: 1}}, 101 out: &null.Int{}, 102 exp: &null.Int{sql.NullInt64{Valid: true, Int64: 1}}, 103 }, 104 { 105 name: "negative valid int", 106 in: &null.Int{sql.NullInt64{Valid: true, Int64: -1}}, 107 out: &null.Int{}, 108 exp: &null.Int{sql.NullInt64{Valid: true, Int64: -1}}, 109 }, 110 { 111 name: "zero valid float", 112 in: &null.Float{sql.NullFloat64{Valid: true, Float64: 0}}, 113 out: &null.Float{}, 114 exp: &null.Float{sql.NullFloat64{Valid: true, Float64: 0}}, 115 }, 116 { 117 name: "positive valid float", 118 in: &null.Float{sql.NullFloat64{Valid: true, Float64: 1}}, 119 out: &null.Float{}, 120 exp: &null.Float{sql.NullFloat64{Valid: true, Float64: 1}}, 121 }, 122 { 123 name: "negative valid float", 124 in: &null.Float{sql.NullFloat64{Valid: true, Float64: -1}}, 125 out: &null.Float{}, 126 exp: &null.Float{sql.NullFloat64{Valid: true, Float64: -1}}, 127 }, 128 // Can't test NAN because NAN != NAN! 129 { 130 name: "false valid bool", 131 in: &null.Bool{sql.NullBool{Valid: true, Bool: false}}, 132 out: &null.Bool{}, 133 exp: &null.Bool{sql.NullBool{Valid: true, Bool: false}}, 134 }, 135 { 136 name: "true valid bool", 137 in: &null.Bool{sql.NullBool{Valid: true, Bool: true}}, 138 out: &null.Bool{}, 139 exp: &null.Bool{sql.NullBool{Valid: true, Bool: true}}, 140 }, 141 { 142 name: "zero valid time", 143 in: &null.Time{sql.NullTime{Valid: true}}, 144 out: &null.Time{}, 145 exp: &null.Time{sql.NullTime{Valid: true}}, 146 }, 147 { 148 name: "non-zero valid time", 149 in: &null.Time{sql.NullTime{Valid: true, Time: time.Date(1970, 3, 15, 0, 0, 0, 0, time.UTC)}}, 150 out: &null.Time{}, 151 exp: &null.Time{sql.NullTime{Valid: true, Time: time.Date(1970, 3, 15, 0, 0, 0, 0, time.UTC)}}, 152 }, 153 } 154 for _, test := range tests { 155 t.Run(test.name, func(t *testing.T) { 156 data, err := plenc.Marshal(nil, test.in) 157 if err != nil { 158 t.Fatal(err) 159 } 160 161 if err := plenc.Unmarshal(data, test.out); err != nil { 162 t.Fatal(err) 163 } 164 165 if diff := cmp.Diff(test.exp, test.out); diff != "" { 166 t.Logf("%x", data) 167 t.Fatalf("structs differ. %s", diff) 168 } 169 }) 170 } 171 } 172 173 func TestNull(t *testing.T) { 174 type TestThing struct { 175 I null.Int `plenc:"1"` 176 B null.Bool `plenc:"2"` 177 F null.Float `plenc:"3"` 178 S null.String `plenc:"4"` 179 T null.Time `plenc:"5"` 180 } 181 182 f := fuzz.New().Funcs(fuzzFuncs...) 183 for i := 0; i < 10000; i++ { 184 var in TestThing 185 f.Fuzz(&in) 186 187 data, err := plenc.Marshal(nil, &in) 188 if err != nil { 189 t.Fatal(err) 190 } 191 192 var out TestThing 193 if err := plenc.Unmarshal(data, &out); err != nil { 194 t.Fatal(err) 195 } 196 197 if diff := cmp.Diff(in, out); diff != "" { 198 t.Logf("%x", data) 199 200 var out TestThing 201 if err := plenc.Unmarshal(data, &out); err != nil { 202 t.Fatal(err) 203 } 204 if diff := cmp.Diff(in, out); diff != "" { 205 t.Logf("re-run differs too") 206 } else { 207 t.Logf("re-run does not differ") 208 } 209 210 t.Fatalf("structs differ. %s", diff) 211 } 212 } 213 } 214 215 func TestNullDescription(t *testing.T) { 216 type TestThing struct { 217 I null.Int `plenc:"1"` 218 B null.Bool `plenc:"2"` 219 F null.Float `plenc:"3"` 220 S null.String `plenc:"4"` 221 T null.Time `plenc:"5"` 222 } 223 224 v := TestThing{ 225 I: null.IntFrom(77), 226 S: null.StringFrom("cheese"), 227 } 228 229 data, err := plenc.Marshal(nil, v) 230 if err != nil { 231 t.Fatal(err) 232 } 233 234 c, err := plenc.CodecForType(reflect.TypeOf(v)) 235 if err != nil { 236 t.Fatal(err) 237 } 238 d := c.Descriptor() 239 var j plenccodec.JSONOutput 240 if err := d.Read(&j, data); err != nil { 241 t.Fatal(err) 242 } 243 244 out := string(j.Done()) 245 if out != "{\n \"I\": 77,\n \"S\": \"cheese\"\n}\n" { 246 t.Fatal(out) 247 } 248 } 249 250 func BenchmarkNull(b *testing.B) { 251 v := benchThing{ 252 I: null.IntFrom(42), 253 B: null.BoolFrom(true), 254 F: null.FloatFrom(3.14), 255 S: null.StringFrom("jhsdfkjahskfhkjhsdkf"), 256 U: null.StringFrom("hat"), 257 } 258 259 b.Run("plenc", func(b *testing.B) { 260 b.ReportAllocs() 261 b.RunParallel(func(pb *testing.PB) { 262 var data []byte 263 var w benchThing 264 for pb.Next() { 265 var err error 266 data, err = plenc.Marshal(data[:0], &v) 267 if err != nil { 268 b.Fatal(err) 269 } 270 271 w = benchThing{} 272 if err := plenc.Unmarshal(data, &w); err != nil { 273 b.Fatal(err) 274 } 275 } 276 }) 277 }) 278 279 b.Run("json", func(b *testing.B) { 280 b.ReportAllocs() 281 b.RunParallel(func(pb *testing.PB) { 282 var w benchThing 283 for pb.Next() { 284 var err error 285 data, err := json.Marshal(&v) 286 if err != nil { 287 b.Fatal(err) 288 } 289 290 w = benchThing{} 291 if err := json.Unmarshal(data, &w); err != nil { 292 b.Fatal(err) 293 } 294 } 295 }) 296 }) 297 }