github.com/unigraph-dev/dgraph@v1.1.1-0.20200923154953-8b52b426f765/testutil/json.go (about)

     1  /*
     2   * Copyright 2019 Dgraph Labs, Inc. and Contributors
     3   *
     4   * Licensed under the Apache License, Version 2.0 (the "License");
     5   * you may not use this file except in compliance with the License.
     6   * You may obtain a copy of the License at
     7   *
     8   *     http://www.apache.org/licenses/LICENSE-2.0
     9   *
    10   * Unless required by applicable law or agreed to in writing, software
    11   * distributed under the License is distributed on an "AS IS" BASIS,
    12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13   * See the License for the specific language governing permissions and
    14   * limitations under the License.
    15   */
    16  
    17  package testutil
    18  
    19  import (
    20  	"encoding/json"
    21  	"fmt"
    22  	"hash/crc64"
    23  	"io/ioutil"
    24  	"os"
    25  	"os/exec"
    26  	"path"
    27  	"reflect"
    28  	"sort"
    29  	"testing"
    30  )
    31  
    32  // CompareJSON compares two JSON objects (passed as strings).
    33  func CompareJSON(t *testing.T, want, got string) {
    34  	wantMap := UnmarshalJSON(t, want)
    35  	gotMap := UnmarshalJSON(t, got)
    36  	CompareJSONMaps(t, wantMap, gotMap)
    37  }
    38  
    39  // CompareJSONMaps compares two JSON objects (passed as maps).
    40  func CompareJSONMaps(t *testing.T, wantMap, gotMap map[string]interface{}) bool {
    41  	return DiffJSONMaps(t, wantMap, gotMap, "", false)
    42  }
    43  
    44  //EqualJSON compares two JSON objects for equality.
    45  func EqualJSON(t *testing.T, want, got string, savepath string, quiet bool) bool {
    46  	wantMap := UnmarshalJSON(t, want)
    47  	gotMap := UnmarshalJSON(t, got)
    48  
    49  	return DiffJSONMaps(t, wantMap, gotMap, savepath, quiet)
    50  }
    51  
    52  // UnmarshalJSON unmarshals the given string into a map.
    53  func UnmarshalJSON(t *testing.T, jsonStr string) map[string]interface{} {
    54  	jsonMap := map[string]interface{}{}
    55  	err := json.Unmarshal([]byte(jsonStr), &jsonMap)
    56  	if err != nil {
    57  		t.Fatalf("Could not unmarshal want JSON: %v", err)
    58  	}
    59  
    60  	return jsonMap
    61  }
    62  
    63  // DiffJSONMaps compares two JSON maps, optionally printing their differences,
    64  // and returning true if they are equal.
    65  func DiffJSONMaps(t *testing.T, wantMap, gotMap map[string]interface{},
    66  	savepath string, quiet bool) bool {
    67  	sortJSON(wantMap)
    68  	sortJSON(gotMap)
    69  
    70  	if !reflect.DeepEqual(wantMap, gotMap) {
    71  		wantBuf, err := json.MarshalIndent(wantMap, "", "  ")
    72  		if err != nil {
    73  			t.Error("Could not marshal JSON:", err)
    74  		}
    75  		gotBuf, err := json.MarshalIndent(gotMap, "", "  ")
    76  		if err != nil {
    77  			t.Error("Could not marshal JSON:", err)
    78  		}
    79  		t.Errorf("Expected JSON and actual JSON differ:\n%s",
    80  			sdiffJSON(wantBuf, gotBuf, savepath, quiet))
    81  		return false
    82  	}
    83  
    84  	return true
    85  }
    86  
    87  // SnipJSON snips the middle of a very long JSON string to make it less than 100 lines
    88  func SnipJSON(buf []byte) string {
    89  	var n int
    90  	for i, ch := range buf {
    91  		if ch == '\n' {
    92  			if n < 100 {
    93  				if n == 99 && i != len(buf) {
    94  					i++
    95  					return string(buf[:i]) + fmt.Sprintf("[%d bytes snipped]", len(buf)-i)
    96  				}
    97  				n++
    98  			}
    99  		}
   100  	}
   101  	return string(buf)
   102  }
   103  
   104  func sdiffJSON(wantBuf, gotBuf []byte, savepath string, quiet bool) string {
   105  	var wantFile, gotFile *os.File
   106  
   107  	if savepath != "" {
   108  		_ = os.MkdirAll(path.Dir(savepath), 0700)
   109  		wantFile, _ = os.Create(savepath + ".expected.json")
   110  		gotFile, _ = os.Create(savepath + ".received.json")
   111  	} else {
   112  		wantFile, _ = ioutil.TempFile("", "testutil.expected.json.*")
   113  		defer os.RemoveAll(wantFile.Name())
   114  		gotFile, _ = ioutil.TempFile("", "testutil.expected.json.*")
   115  		defer os.RemoveAll(gotFile.Name())
   116  	}
   117  
   118  	_ = ioutil.WriteFile(wantFile.Name(), wantBuf, 0600)
   119  	_ = ioutil.WriteFile(gotFile.Name(), gotBuf, 0600)
   120  
   121  	// don't do diff when one side is missing
   122  	if len(gotBuf) == 0 {
   123  		return "Got empty response"
   124  	} else if quiet {
   125  		return "Not showing diff in quiet mode"
   126  	}
   127  
   128  	out, _ := exec.Command("sdiff", wantFile.Name(), gotFile.Name()).CombinedOutput()
   129  
   130  	return string(out)
   131  }
   132  
   133  // sortJSON looks for any arrays in the unmarshalled JSON and sorts them in an
   134  // arbitrary but deterministic order based on their content.
   135  func sortJSON(i interface{}) uint64 {
   136  	if i == nil {
   137  		return 0
   138  	}
   139  	switch i := i.(type) {
   140  	case map[string]interface{}:
   141  		return sortJSONMap(i)
   142  	case []interface{}:
   143  		return sortJSONArray(i)
   144  	default:
   145  		h := crc64.New(crc64.MakeTable(crc64.ISO))
   146  		fmt.Fprint(h, i)
   147  		return h.Sum64()
   148  	}
   149  }
   150  
   151  func sortJSONMap(m map[string]interface{}) uint64 {
   152  	var h uint64
   153  	for _, k := range m {
   154  		// Because xor is commutative, it doesn't matter that map iteration
   155  		// is in random order.
   156  		h ^= sortJSON(k)
   157  	}
   158  	return h
   159  }
   160  
   161  type arrayElement struct {
   162  	elem   interface{}
   163  	sortBy uint64
   164  }
   165  
   166  func sortJSONArray(a []interface{}) uint64 {
   167  	var h uint64
   168  	elements := make([]arrayElement, len(a))
   169  	for i, elem := range a {
   170  		elements[i] = arrayElement{elem, sortJSON(elem)}
   171  		h ^= elements[i].sortBy
   172  	}
   173  	sort.Slice(elements, func(i, j int) bool {
   174  		return elements[i].sortBy < elements[j].sortBy
   175  	})
   176  	for i := range a {
   177  		a[i] = elements[i].elem
   178  	}
   179  	return h
   180  }