1package main23import (4 "errors"5 "fmt"6 "io"7 "os"8 "strings"9 "testing"10)1112func TestParseGrubEntryFilesSuccess(t *testing.T) {13 files := []string{14 "testdata/entries/0.conf",15 "testdata/entries/1.conf",16 "testdata/entries/2.conf",17 }18 expected := []kernelInfo{19 {20 title: "Fedora Linux (5.18.16-200.fc36.x86_64) 36 (Workstation Edition)",21 version: "5.18.16-200.fc36.x86_64",22 linux: "/vmlinuz-5.18.16-200.fc36.x86_64",23 initrd: "/initramfs-5.18.16-200.fc36.x86_64.img $tuned_initrd",24 options: "root=UUID=random_uuid ro rootflags=subvol=root rd.luks.uuid=luks-some-other-uuid rhgb quiet",25 },26 {27 title: "Fedora Linux (5.18.17-200.fc36.x86_64) 36 (Workstation Edition)",28 version: "5.18.17-200.fc36.x86_64",29 linux: "/vmlinuz-5.18.17-200.fc36.x86_64",30 initrd: "/initramfs-5.18.17-200.fc36.x86_64.img",31 options: "root=UUID=random_uuid ro rootflags=subvol=root rd.luks.uuid=luks-some-other-uuid rhgb quiet",32 },33 {34 title: "Fedora Linux (5.18.18-200.fc36.x86_64) 36 (Workstation Edition)",35 version: "5.18.18-200.fc36.x86_64",36 linux: "/vmlinuz-5.18.18-200.fc36.x86_64",37 initrd: "/initramfs-5.18.18-200.fc36.x86_64.img",38 options: "root=UUID=random_uuid ro rootflags=subvol=root rd.luks.uuid=luks-some-other-uuid rhgb quiet",39 },40 }4142 ks, err := parseGrubEntryFiles(files)43 if err != nil {44 t.Fatalf("unexpected error: %s", err)45 }4647 if len(ks) != len(expected) {48 t.Fatalf("unexpected number of files. got=%d, want=%d",49 len(ks), len(expected))50 }5152 for i := range expected {53 if err := testKernelInfo(&expected[i], &ks[i]); err != nil {54 t.Errorf("Error in kernel %d: %s", i, err)55 }56 }57}5859func TestParseGrubEntryFilesFailures(t *testing.T) {60 tests := []struct {61 Name string62 Filename string63 ErrSubstr string64 }{65 {66 "bad file",67 "testdata/entries/failure.conf",68 "did not contain options",69 },70 {71 "not found",72 "testdata/entries/idonotexist.conf",73 "no such file or directory",74 },75 }7677 for _, tt := range tests {78 t.Run(tt.Name, func(t *testing.T) {79 ks, err := parseGrubEntryFiles([]string{tt.Filename})80 if err == nil {81 t.Fatalf("expected an error")82 }8384 if !strings.Contains(err.Error(), tt.ErrSubstr) {85 t.Errorf(86 "error message did not contain %q: %q",87 tt.ErrSubstr,88 err.Error(),89 )90 }9192 if ks != nil {93 t.Errorf("reterned kernels slice was not nil")94 }95 })96 }97}9899func TestParseGrubEntry(t *testing.T) {100 tests := []struct {101 Name string102 Contents string103 ExpectedInfo *kernelInfo104 HasErr bool105 ErrSubstr string106 }{107 {108 "success",109 `110title Fedora Linux (5.18.16-200.fc36.x86_64) 36 (Workstation Edition)111version 5.18.16-200.fc36.x86_64112linux /vmlinuz-5.18.16-200.fc36.x86_64113initrd /initramfs-5.18.16-200.fc36.x86_64.img114options root=UUID=random_uuid ro rootflags=subvol=root rd.luks.uuid=luks-some-other-uuid rhgb quiet115 `,116 &kernelInfo{117 title: "Fedora Linux (5.18.16-200.fc36.x86_64) 36 (Workstation Edition)",118 version: "5.18.16-200.fc36.x86_64",119 linux: "/vmlinuz-5.18.16-200.fc36.x86_64",120 initrd: "/initramfs-5.18.16-200.fc36.x86_64.img",121 options: "root=UUID=random_uuid ro rootflags=subvol=root rd.luks.uuid=luks-some-other-uuid rhgb quiet",122 },123 false,124 "",125 },126 {127 "missing title",128 ``,129 nil,130 true,131 "did not contain title",132 },133 {134 "missing version",135 `136title Fedora Linux (5.18.16-200.fc36.x86_64) 36 (Workstation Edition)137 `,138 nil,139 true,140 "did not contain version",141 },142 {143 "missing linux",144 `145title Fedora Linux (5.18.16-200.fc36.x86_64) 36 (Workstation Edition)146version 5.18.16-200.fc36.x86_64147 `,148 nil,149 true,150 "did not contain linux",151 },152 {153 "missing initrd",154 `155title Fedora Linux (5.18.16-200.fc36.x86_64) 36 (Workstation Edition)156version 5.18.16-200.fc36.x86_64157linux /vmlinuz-5.18.16-200.fc36.x86_64158 `,159 nil,160 true,161 "did not contain initrd",162 },163 {164 "missing options",165 `166title Fedora Linux (5.18.16-200.fc36.x86_64) 36 (Workstation Edition)167version 5.18.16-200.fc36.x86_64168linux /vmlinuz-5.18.16-200.fc36.x86_64169initrd /initramfs-5.18.16-200.fc36.x86_64.img170 `,171 nil,172 true,173 "did not contain options",174 },175 {176 "bad format",177 `178 title this is a title179 thereisnospacehereohno180 `,181 nil,182 true,183 "file in wrong format",184 },185 }186187 for _, tt := range tests {188 t.Run(tt.Name, func(t *testing.T) {189 k, err := parseGrubEntry(tt.Contents)190 if tt.HasErr {191 if err != nil {192 if !strings.Contains(err.Error(), tt.ErrSubstr) {193 t.Errorf(194 "could not find substring %q in error %q",195 tt.ErrSubstr,196 err.Error(),197 )198 }199 } else {200 t.Errorf("no error when expected")201 }202 } else {203 if err != nil {204 t.Errorf("unexpected error: %s", err)205 }206 }207208 if err := testKernelInfo(tt.ExpectedInfo, k); err != nil {209 t.Errorf("kernelInfo mismatch: %s", err.Error())210 }211 })212 }213}214215func TestGetGrubEntryFilesSuccess(t *testing.T) {216 files := []string{217 "testdata/entries/0.conf",218 "testdata/entries/1.conf",219 "testdata/entries/2.conf",220 "testdata/entries/failure.conf",221 }222223 fs, err := getGrubEntryFiles("testdata/entries/")224 if err != nil {225 t.Fatalf("unexpected error: %s", err)226 }227228 if len(fs) != len(files) {229 t.Fatalf("wrong number of files. got=%d; want=%d", len(fs), len(files))230 }231232 for i := range files {233 if files[i] != fs[i] {234 t.Errorf("name mismatch for file %d: got=%s; want=%s",235 i, files[i], fs[i])236 }237 }238}239240func TestGetGrubEntryFilesFailure(t *testing.T) {241 _, err := getGrubEntryFiles("testdata/entries/idonotexist")242 if err != nil {243 if !errors.Is(err, os.ErrNotExist) {244 t.Errorf("unexpected error returned: %s", err)245 }246 } else {247 t.Error("no error reported when expected")248 }249}250251func TestUserSelectKernelSuccess(t *testing.T) {252 tests := []struct {253 Name string254 Input string255 Output int256 }{257 {"eof", "", -1},258 {"empty line", "\n", 0},259 {"single number", "1\n", 1},260 {"multiple tries", "-1\nabcd\n2\n", 2},261 {"too long", "-1\nabcd\n100\n3\n", 3},262 }263264 for _, tt := range tests {265 t.Run(tt.Name, func(t *testing.T) {266 // TODO: Is it reasonble to test the output?267 n, err := userSelectKernel(268 strings.NewReader(tt.Input),269 io.Discard,270 []kernelInfo{{}, {}, {}, {}},271 )272 if err != nil {273 t.Fatalf("unexpected error: %s", err)274 }275276 if n != tt.Output {277 t.Fatalf("wrong output: got=%d; want=%d", n, tt.Output)278 }279 })280 }281}282283// A struct that implements io.Reader, but only returns errors.284type errorReader struct {285 err error286}287288func (e *errorReader) Read(p []byte) (n int, err error) {289 return 0, e.err290}291292func TestUserSelectKernelScannerError(t *testing.T) {293 errStr := "bad bad bad"294 r := errorReader{fmt.Errorf(errStr)}295296 _, err := userSelectKernel(&r, io.Discard, []kernelInfo{{}, {}, {}})297 if err == nil {298 t.Fatal("unexpected nil error")299 }300301 if !strings.Contains(err.Error(), errStr) {302 t.Errorf("error did not contain %q: %q", errStr, err.Error())303 }304}305306func testKernelInfo(want, got *kernelInfo) error {307 if (want == nil) != (got == nil) {308 return fmt.Errorf(309 "nil mismatch: got=%t; want=%t",310 want == nil,311 got == nil,312 )313 }314315 if want == nil {316 return nil317 }318319 if want.title != got.title {320 return fmt.Errorf("title mismatch: want=%s; got=%s",321 want.title, got.title)322 }323324 if want.version != got.version {325 return fmt.Errorf("version mismatch: want=%s; got=%s",326 want.version, got.version)327 }328329 if want.linux != got.linux {330 return fmt.Errorf("linux mismatch: want=%s; got=%s",331 want.linux, got.linux)332 }333334 if want.initrd != got.initrd {335 return fmt.Errorf("initrd mismatch: want=%s; got=%s",336 want.initrd, got.initrd)337 }338339 if want.options != got.options {340 return fmt.Errorf("options mismatch: want=%s; got=%s",341 want.options, got.options)342 }343344 return nil345}