github.com/cockroachdb/cockroach@v20.2.0-alpha.1+incompatible/pkg/util/json/encode_test.go (about)

     1  // Copyright 2017 The Cockroach Authors.
     2  //
     3  // Use of this software is governed by the Business Source License
     4  // included in the file licenses/BSL.txt.
     5  //
     6  // As of the Change Date specified in that file, in accordance with
     7  // the Business Source License, use of this software will be governed
     8  // by the Apache License, Version 2.0, included in the file
     9  // licenses/APL.txt.
    10  
    11  package json
    12  
    13  import (
    14  	"flag"
    15  	"fmt"
    16  	"io/ioutil"
    17  	"math/rand"
    18  	"path/filepath"
    19  	"runtime"
    20  	"strings"
    21  	"testing"
    22  
    23  	"github.com/cockroachdb/cockroach/pkg/util/timeutil"
    24  )
    25  
    26  var rewriteResultsInTestfiles = flag.Bool(
    27  	"rewrite-results-in-testfiles", false,
    28  	"ignore the expected results and rewrite the test files with the actual results from this "+
    29  		"run. Used to update tests when a change affects many cases; please verify the testfile "+
    30  		"diffs carefully!",
    31  )
    32  
    33  func assertEncodeRoundTrip(t *testing.T, j JSON) {
    34  	encoded, err := EncodeJSON(nil, j)
    35  	if err != nil {
    36  		t.Fatal(j, err)
    37  	}
    38  	_, decoded, err := DecodeJSON(encoded)
    39  	if err != nil {
    40  		t.Fatal(j, err)
    41  	}
    42  
    43  	c, err := j.Compare(decoded)
    44  	if err != nil {
    45  		t.Fatal(j, err)
    46  	}
    47  	if c != 0 {
    48  		t.Fatalf("expected %s, got %s (encoding %v)", j, decoded, encoded)
    49  	}
    50  }
    51  
    52  func TestJSONRandomEncodeRoundTrip(t *testing.T) {
    53  	rng := rand.New(rand.NewSource(timeutil.Now().Unix()))
    54  	for i := 0; i < 1000; i++ {
    55  		j, err := Random(20, rng)
    56  		if err != nil {
    57  			t.Fatal(err)
    58  		}
    59  
    60  		assertEncodeRoundTrip(t, j)
    61  	}
    62  }
    63  
    64  func TestFilesEncode(t *testing.T) {
    65  	_, fname, _, ok := runtime.Caller(0)
    66  	if !ok {
    67  		t.Fatal("couldn't get directory")
    68  	}
    69  	dir := filepath.Join(filepath.Dir(fname), "testdata", "raw")
    70  	dirContents, err := ioutil.ReadDir(dir)
    71  	if err != nil {
    72  		t.Fatal(err)
    73  	}
    74  
    75  	numFilesRan := 0
    76  
    77  	for _, tc := range dirContents {
    78  		if !strings.HasSuffix(tc.Name(), ".json") {
    79  			continue
    80  		}
    81  		t.Run(tc.Name(), func(t *testing.T) {
    82  			numFilesRan++
    83  			path := filepath.Join(dir, tc.Name())
    84  			contents, err := ioutil.ReadFile(path)
    85  			if err != nil {
    86  				t.Fatal(err)
    87  			}
    88  			jsonString := string(contents)
    89  
    90  			j, err := ParseJSON(jsonString)
    91  			if err != nil {
    92  				t.Fatal(err)
    93  			}
    94  
    95  			encoded, err := EncodeJSON(nil, j)
    96  			if err != nil {
    97  				t.Fatal(err)
    98  			}
    99  
   100  			t.Run(`round trip`, func(t *testing.T) {
   101  				_, decoded, err := DecodeJSON(encoded)
   102  				if err != nil {
   103  					t.Fatal(err)
   104  				}
   105  
   106  				newStr := decoded.String()
   107  				if newStr != j.String() {
   108  					t.Fatalf("expected %s, got %s", jsonString, newStr)
   109  				}
   110  			})
   111  
   112  			// If this test is failing because you changed the encoding of JSON values,
   113  			// rerun with -rewrite-results-in-testfiles.
   114  			t.Run(`explicit encoding`, func(t *testing.T) {
   115  				stringifiedEncoding := fmt.Sprintf("%v", encoded)
   116  				fixtureFilename := filepath.Join(
   117  					filepath.Dir(fname),
   118  					"testdata", "encoded",
   119  					tc.Name()+".bytes",
   120  				)
   121  
   122  				if *rewriteResultsInTestfiles {
   123  					err := ioutil.WriteFile(fixtureFilename, []byte(stringifiedEncoding), 0644)
   124  					if err != nil {
   125  						t.Fatal(err)
   126  					}
   127  				}
   128  
   129  				expected, err := ioutil.ReadFile(fixtureFilename)
   130  				if err != nil {
   131  					t.Fatal(err)
   132  				}
   133  
   134  				if string(expected) != stringifiedEncoding {
   135  					t.Fatalf("expected %s, got %s", string(expected), stringifiedEncoding)
   136  				}
   137  			})
   138  		})
   139  	}
   140  
   141  	// Sanity check.
   142  	if numFilesRan == 0 {
   143  		t.Fatal("didn't find any test files!")
   144  	}
   145  }
   146  
   147  func TestJSONEncodeRoundTrip(t *testing.T) {
   148  	cases := []string{
   149  		`true`,
   150  		`false`,
   151  		`null`,
   152  		`""`,
   153  		`"🤔"`,
   154  		`"\""`,
   155  		`"\n"`,
   156  		`"hello"`,
   157  		`"goodbye"`,
   158  		`1`,
   159  		`1.00`,
   160  		`100`,
   161  		`-1`,
   162  		`1000000000000000`,
   163  		`100000000000000000000000000000000000`,
   164  		`0e1`,
   165  		`[]`,
   166  		`["hello"]`,
   167  		`[1]`,
   168  		`[1, 2]`,
   169  		`[1, 2, 3]`,
   170  		`[100000000000000000000000000000000000]`,
   171  		`[1, true, "three"]`,
   172  		`[[1]]`,
   173  		`[[1], [1], [[1]]]`,
   174  		`[[[["hello"]]]]`,
   175  		`{}`,
   176  		`{"a": 1, "b": 2}`,
   177  		`{"b": 1, "a": 2}`,
   178  		`{"a": [1, 2, 3]}`,
   179  		`{"a": [{"b": 2}, {"b": 4}]}`,
   180  		`[1, {"a": 3}, null, {"b": null}]`,
   181  		`{"🤔": "foo"}`,
   182  		`"6U閆崬밺뀫颒myj츥휘:$薈mY햚#rz飏+玭V㭢뾿愴YꖚX亥ᮉ푊\u0006垡㐭룝\"厓ᔧḅ^Sqpv媫\"⤽걒\"˽Ἆ?ꇆ䬔未tv{DV鯀Tἆl凸g\\㈭ĭ즿UH㽤"`,
   183  		`{"a":"b","c":"d"}`,
   184  	}
   185  
   186  	for _, tc := range cases {
   187  		j, err := ParseJSON(tc)
   188  		if err != nil {
   189  			t.Fatal(err)
   190  		}
   191  
   192  		assertEncodeRoundTrip(t, j)
   193  	}
   194  }
   195  
   196  // This tests that the stringified version is the same, for testing precision
   197  // is maintained for numbers.  This will not maintain, for example, the
   198  // ordering of object keys.
   199  func TestJSONEncodeStrictRoundTrip(t *testing.T) {
   200  	cases := []string{
   201  		`1`,
   202  		`1.00`,
   203  		`100`,
   204  		`-1`,
   205  		`-1.000000000`,
   206  		`-1000000000`,
   207  		`1.1231231230`,
   208  		`1.1231231230000`,
   209  		`1.1231231230000000`,
   210  		`0E+1`,
   211  	}
   212  
   213  	for _, tc := range cases {
   214  		j, err := ParseJSON(tc)
   215  		if err != nil {
   216  			t.Fatal(err)
   217  		}
   218  
   219  		encoded, err := EncodeJSON(nil, j)
   220  		if err != nil {
   221  			t.Fatal(err)
   222  		}
   223  		_, decoded, err := DecodeJSON(encoded)
   224  		if err != nil {
   225  			t.Fatal(err)
   226  		}
   227  
   228  		newStr := decoded.String()
   229  		if newStr != tc {
   230  			t.Fatalf("expected %s, got %s", tc, newStr)
   231  		}
   232  	}
   233  }
   234  
   235  // Taken from Wikipedia's JSON page.
   236  const sampleJSON = `{
   237    "firstName": "John",
   238    "lastName": "Smith",
   239    "isAlive": true,
   240    "age": 25,
   241    "address": {
   242      "streetAddress": "21 2nd Street",
   243      "city": "New York",
   244      "state": "NY",
   245      "postalCode": "10021-3100"
   246    },
   247    "phoneNumbers": [
   248      {
   249        "type": "home",
   250        "number": "212 555-1234"
   251      },
   252      {
   253        "type": "office",
   254        "number": "646 555-4567"
   255      },
   256      {
   257        "type": "mobile",
   258        "number": "123 456-7890"
   259      }
   260    ],
   261    "children": [],
   262    "spouse": null
   263  }`
   264  
   265  func BenchmarkEncodeJSON(b *testing.B) {
   266  	j := jsonTestShorthand(sampleJSON)
   267  
   268  	b.ResetTimer()
   269  
   270  	for i := 0; i < b.N; i++ {
   271  		_, _ = EncodeJSON(nil, j)
   272  	}
   273  }
   274  
   275  func BenchmarkDecodeJSON(b *testing.B) {
   276  	j := jsonTestShorthand(sampleJSON)
   277  
   278  	b.ResetTimer()
   279  	bytes, err := EncodeJSON(nil, j)
   280  	if err != nil {
   281  		b.Fatal(err)
   282  	}
   283  
   284  	for i := 0; i < b.N; i++ {
   285  		_, _, _ = DecodeJSON(bytes)
   286  	}
   287  }