github.com/Equinix-Metal/virtlet@v1.5.2-0.20210807010419-342346535dc5/tests/gm/data.go (about) 1 /* 2 Copyright 2017 Mirantis 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 gm 18 19 import ( 20 "bytes" 21 "encoding/json" 22 "fmt" 23 "io/ioutil" 24 "os" 25 "path/filepath" 26 "reflect" 27 "regexp" 28 "strings" 29 30 "github.com/davecgh/go-spew/spew" 31 "github.com/ghodss/yaml" 32 "github.com/golang/glog" 33 ) 34 35 const ( 36 jsonDataIndent = " " 37 ) 38 39 // Verifier describes a type that can verify its contents 40 // against a Golden Master data file and also generate 41 // the contents of such file 42 type Verifier interface { 43 // Suffix returns the suffix for the file name of the Golden Master 44 // data file for this value. 45 Suffix() string 46 // Marshal generates the contents of the Golden Master data file. 47 Marshal() ([]byte, error) 48 // Verify returns true if the contents can be considered 49 // the same as the value of the Verifier. It should not return 50 // an error if content is invalid. 51 Verify(content []byte) (bool, error) 52 } 53 54 type textVerifier string 55 56 var _ Verifier = textVerifier("") 57 58 func (v textVerifier) Suffix() string { 59 return ".txt" 60 } 61 62 func (v textVerifier) Verify(content []byte) (bool, error) { 63 return string(v) == string(content), nil 64 } 65 66 func (v textVerifier) Marshal() ([]byte, error) { 67 return []byte(v), nil 68 } 69 70 type JSONVerifier struct { 71 data interface{} 72 } 73 74 var _ Verifier = JSONVerifier{} 75 76 func NewJSONVerifier(data interface{}) JSONVerifier { 77 return JSONVerifier{data} 78 } 79 80 func (v JSONVerifier) Suffix() string { 81 return ".json" 82 } 83 84 func (v JSONVerifier) Verify(content []byte) (bool, error) { 85 var curData interface{} 86 if err := json.Unmarshal(content, &curData); err != nil { 87 glog.Warningf("Failed to unmarshal to JSON: %v:\n%s", err, content) 88 return false, nil 89 } 90 91 out, err := json.Marshal(v.data) 92 if err != nil { 93 return false, fmt.Errorf("failed to marshal data: %v. Input:\n%s", 94 err, spew.Sdump(v)) 95 } 96 97 var newData interface{} 98 if err := json.Unmarshal(out, &newData); err != nil { 99 return false, fmt.Errorf("failed to unmarshal back marshalled value: %v. JSON:\n%s\nOriginal data:\n%s", 100 err, string(out), spew.Sdump(v)) 101 } 102 103 return reflect.DeepEqual(curData, newData), nil 104 } 105 106 func (v JSONVerifier) Marshal() ([]byte, error) { 107 switch d := v.data.(type) { 108 case []byte: 109 return d, nil 110 case string: 111 return []byte(d), nil 112 default: 113 out, err := json.MarshalIndent(v.data, "", jsonDataIndent) 114 if err != nil { 115 return nil, fmt.Errorf("failed to marshal json data: %v. Input:\n%s", 116 err, spew.Sdump(v.data)) 117 } 118 return out, nil 119 } 120 } 121 122 // YamlVerifier verifies the data using YAML representation. 123 type YamlVerifier struct { 124 data interface{} 125 } 126 127 var _ Verifier = YamlVerifier{} 128 129 // NewYamlVerifier makes a YamlVerifier with the specified content. 130 func NewYamlVerifier(data interface{}) YamlVerifier { 131 return YamlVerifier{data} 132 } 133 134 // Suffix implements Suffix method of the Verifier interface. 135 func (v YamlVerifier) Suffix() string { 136 return ".yaml" 137 } 138 139 func marshalMultiple(data []interface{}) ([]byte, error) { 140 var out bytes.Buffer 141 for _, v := range data { 142 bs, err := yaml.Marshal(v) 143 if err != nil { 144 return nil, err 145 } 146 fmt.Fprintf(&out, "---\n%s", bs) 147 } 148 return out.Bytes(), nil 149 } 150 151 func unmarshalMultiple(in []byte) ([]interface{}, error) { 152 var r []interface{} 153 for _, part := range bytes.Split(in, []byte("---\n")) { 154 if len(bytes.TrimSpace(part)) == 0 { 155 continue 156 } 157 var data interface{} 158 if err := yaml.Unmarshal(part, &data); err != nil { 159 return nil, err 160 } 161 r = append(r, data) 162 } 163 return r, nil 164 } 165 166 func (v YamlVerifier) verifyMultiple(content []byte) (bool, error) { 167 curData, err := unmarshalMultiple(content) 168 if err != nil { 169 glog.Warningf("Failed to unmarshal to YAML: %v:\n%s", err, content) 170 return false, nil 171 } 172 173 out, err := v.Marshal() 174 if err != nil { 175 return false, err 176 } 177 178 newData, err := unmarshalMultiple(out) 179 if err != nil { 180 return false, fmt.Errorf("failed to unmarshal back marshalled value: %v. YAML:\n%s\nOriginal data:\n%s", 181 err, string(out), content) 182 } 183 184 return reflect.DeepEqual(curData, newData), nil 185 } 186 187 // Verify implements Verify method of the Verifier interface. 188 func (v YamlVerifier) Verify(content []byte) (bool, error) { 189 switch v.data.(type) { 190 case []byte: 191 return v.verifyMultiple(content) 192 case string: 193 return v.verifyMultiple(content) 194 } 195 196 var curData interface{} 197 if err := yaml.Unmarshal(content, &curData); err != nil { 198 glog.Warningf("Failed to unmarshal to YAML: %v:\n%s", err, content) 199 return false, nil 200 } 201 202 out, err := v.Marshal() 203 if err != nil { 204 return false, err 205 } 206 207 var newData interface{} 208 if err := yaml.Unmarshal(out, &newData); err != nil { 209 return false, fmt.Errorf("failed to unmarshal back marshalled value: %v. YAML:\n%s\nOriginal data:\n%s", 210 err, string(out), spew.Sdump(v)) 211 } 212 213 return reflect.DeepEqual(curData, newData), nil 214 } 215 216 // Marshal implements Marshal method of the Verifier interface. 217 func (v YamlVerifier) Marshal() ([]byte, error) { 218 switch d := v.data.(type) { 219 case []byte: 220 return d, nil 221 case string: 222 return []byte(d), nil 223 default: 224 out, err := yaml.Marshal(v.data) 225 if err != nil { 226 return nil, fmt.Errorf("failed to marshal yaml data: %v. Input:\n%s", 227 err, spew.Sdump(v.data)) 228 } 229 return out, nil 230 } 231 } 232 233 // Replacement specifies a replacement for SubstVerifier. 234 type Replacement struct { 235 Old string 236 New string 237 } 238 239 // SubstVerifier wraps another verifier and replaces the specified 240 // substrings in the data it generates. 241 type SubstVerifier struct { 242 next Verifier 243 replacements []Replacement 244 } 245 246 var _ Verifier = SubstVerifier{} 247 248 // NewSubstVerifier makes a SubstVerifier that wraps another verifier 249 // and does the specified replacements. 250 func NewSubstVerifier(next Verifier, replacements []Replacement) SubstVerifier { 251 return SubstVerifier{next, replacements} 252 } 253 254 // Suffix implements Suffix method of the Verifier interface. 255 func (v SubstVerifier) Suffix() string { 256 return v.next.Suffix() 257 } 258 259 // Verify implements Verify method of the Verifier interface. 260 func (v SubstVerifier) Verify(content []byte) (bool, error) { 261 return v.next.Verify(content) 262 } 263 264 // Marshal implements Marshal method of the Verifier interface. 265 func (v SubstVerifier) Marshal() ([]byte, error) { 266 d, err := v.next.Marshal() 267 if err != nil { 268 return nil, err 269 } 270 for _, rep := range v.replacements { 271 d = bytes.Replace(d, []byte(rep.Old), []byte(rep.New), -1) 272 } 273 return d, nil 274 } 275 276 func getVerifier(data interface{}) Verifier { 277 switch v := data.(type) { 278 case Verifier: 279 return v 280 case string: 281 return textVerifier(v) 282 case []byte: 283 return textVerifier(string(v)) 284 default: 285 return NewJSONVerifier(v) 286 } 287 } 288 289 var badFilenameCharRx = regexp.MustCompile(`[^\w-]`) 290 291 // GetFilenameForTest converts a Go test name to filename 292 func GetFilenameForTest(testName string, v interface{}) (string, error) { 293 suffix := ".out" + getVerifier(v).Suffix() 294 filename := strings.Replace(testName, "/", "__", -1) 295 filename = badFilenameCharRx.ReplaceAllString(filename, "_") + suffix 296 wd, err := os.Getwd() 297 if err != nil { 298 return "", fmt.Errorf("can't get current directory: %v", err) 299 } 300 return filepath.Join(wd, filename), nil 301 } 302 303 // WriteDataFile serializes the specified value into a data file 304 func WriteDataFile(filename string, v interface{}) error { 305 out, err := getVerifier(v).Marshal() 306 if err != nil { 307 return fmt.Errorf("error generating the data for %q: %v", filename, err) 308 } 309 if err := ioutil.WriteFile(filename, out, 0777); err != nil { 310 return fmt.Errorf("error writing %q: %v", filename, err) 311 } 312 return nil 313 } 314 315 // DataFileDiffers compares the specified value against the stored data file 316 func DataFileDiffers(filename string, v interface{}) (bool, error) { 317 content, err := ioutil.ReadFile(filename) 318 if err != nil { 319 if os.IsNotExist(err) { 320 return true, nil 321 } 322 return false, fmt.Errorf("error reading %q: %v", filename, err) 323 } 324 325 ok, err := getVerifier(v).Verify(content) 326 if err != nil { 327 return false, fmt.Errorf("error parsing %q: %v", filename, err) 328 } 329 330 return !ok, nil 331 }