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 }