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  }