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  }