github.com/u-root/u-root@v7.0.1-0.20200915234505-ad7babab0a8e+incompatible/pkg/boot/boottest/json.go (about) 1 // Copyright 2020 the u-root Authors. All rights reserved 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 // Package boottest contains methods for comparing boot.OSImages to each other 6 // and to JSON representations of themselves for use in tests. 7 // 8 // The JSON representation for boot.OSImages is special because the built-in 9 // json.Marshal function cannot marshal interfaces such as io.ReaderAt nicely, 10 // especially when the underlying members in structs used (such as *os.File or 11 // curl.lazyFile) are not exported. 12 // 13 // They are not json.Marshalers as part of boot.OSImage itself because they're 14 // not a fully accurate representation of an OSImage, not including file 15 // contents and depending for example on the current working directory of the 16 // calling process. 17 package boottest 18 19 import ( 20 "encoding/json" 21 "fmt" 22 "io" 23 "io/ioutil" 24 "os" 25 26 "github.com/google/go-cmp/cmp" 27 "github.com/u-root/u-root/pkg/boot" 28 "github.com/u-root/u-root/pkg/curl" 29 ) 30 31 func module(r io.ReaderAt) map[string]interface{} { 32 m := make(map[string]interface{}) 33 if f, ok := r.(curl.File); ok { 34 m["url"] = f.URL().String() 35 } else if f, ok := r.(fmt.Stringer); ok { 36 m["stringer"] = f.String() 37 } else if f, ok := r.(*os.File); ok { 38 m["name"] = f.Name() 39 } 40 return m 41 } 42 43 // CompareImagesToJSON compares the names, cmdlines, and file URLs in imgs to 44 // the ones stored in jsonEncoded. 45 // 46 // You can obtain such a JSON encoding with ToJSONFile. 47 func CompareImagesToJSON(imgs []boot.OSImage, jsonEncoded []byte) error { 48 var want interface{} 49 if err := json.Unmarshal(jsonEncoded, &want); err != nil { 50 return fmt.Errorf("failed to unmarshall test json %q: %v", jsonEncoded, err) 51 } 52 53 got := ImagesToJSONLike(imgs) 54 if !cmp.Equal(want, got) { 55 return fmt.Errorf("mismatch(-want, +got):\n%s", cmp.Diff(want, got)) 56 } 57 return nil 58 } 59 60 // ToJSONFile can be used to generate JSON-comparable files for use with 61 // CompareImagesToJSON in tests. 62 func ToJSONFile(imgs []boot.OSImage, filename string) error { 63 enc, err := json.MarshalIndent(ImagesToJSONLike(imgs), "", " ") 64 if err != nil { 65 return err 66 } 67 return ioutil.WriteFile(filename, enc, 0644) 68 } 69 70 // ImagesToJSONLike spits out a json-convertible reproducible representation of 71 // the given boot images. This can be used in configuration parser tests (when 72 // the content of the images doesn't matter, but the file URLs, cmdlines, 73 // names, etc.) 74 // 75 // The JSON representation for boot.OSImages is special because the built-in 76 // json.Marshal function cannot marshal interfaces such as io.ReaderAt nicely, 77 // especially when the underlying structs used are not exported. 78 func ImagesToJSONLike(imgs []boot.OSImage) []interface{} { 79 var infs []interface{} 80 for _, img := range imgs { 81 if l, ok := img.(*boot.LinuxImage); ok { 82 infs = append(infs, LinuxImageToJSON(l)) 83 } 84 if m, ok := img.(*boot.MultibootImage); ok { 85 infs = append(infs, MultibootImageToJSON(m)) 86 } 87 } 88 return infs 89 } 90 91 // LinuxImageToJSON is implemented only in order to compare LinuxImages in 92 // tests. 93 // 94 // It should be json-encodable and decodable. 95 func LinuxImageToJSON(li *boot.LinuxImage) map[string]interface{} { 96 m := make(map[string]interface{}) 97 m["image_type"] = "linux" 98 m["name"] = li.Name 99 m["cmdline"] = li.Cmdline 100 if li.Kernel != nil { 101 m["kernel"] = module(li.Kernel) 102 } 103 if li.Initrd != nil { 104 m["initrd"] = module(li.Initrd) 105 } 106 return m 107 } 108 109 // MultibootImageToJSON is implemented only in order to compare MultibootImages 110 // in tests. 111 // 112 // It should be json-encodable and decodable. 113 func MultibootImageToJSON(mi *boot.MultibootImage) map[string]interface{} { 114 m := make(map[string]interface{}) 115 m["image_type"] = "multiboot" 116 m["name"] = mi.Name 117 m["cmdline"] = mi.Cmdline 118 if mi.Kernel != nil { 119 m["kernel"] = module(mi.Kernel) 120 } 121 122 var modules []interface{} 123 for _, mod := range mi.Modules { 124 mmod := module(mod.Module) 125 mmod["cmdline"] = mod.Cmdline 126 mmod["name"] = mod.Name() 127 modules = append(modules, mmod) 128 } 129 m["modules"] = modules 130 return m 131 }