github.com/9elements/firmware-action/action@v0.0.0-20240514065043-044ed91c9ed8/recipes/stitching_test.go (about) 1 // SPDX-License-Identifier: MIT 2 3 // Package recipes / linux 4 package recipes 5 6 import ( 7 "context" 8 "crypto/sha256" 9 "encoding/hex" 10 "fmt" 11 "log" 12 "os" 13 "os/exec" 14 "path/filepath" 15 "testing" 16 17 "dagger.io/dagger" 18 "github.com/9elements/firmware-action/action/filesystem" 19 "github.com/google/go-cmp/cmp" 20 "github.com/stretchr/testify/assert" 21 ) 22 23 func TestExtractSizeFromString(t *testing.T) { 24 testCases := []struct { 25 name string 26 stdout string 27 expected []uint64 28 wantErr error 29 }{ 30 { 31 name: "empty string", 32 stdout: "", 33 expected: []uint64{}, 34 wantErr: errFailedToDetectRomSize, 35 }, 36 { 37 name: "component 1; 2 unused", 38 stdout: "Component 2 Density: UNUSED\nComponent 1 Density: 16MB", 39 expected: []uint64{16 * 1024 * 1024, 0}, 40 wantErr: nil, 41 }, 42 { 43 name: "component 1, nothing about 2", 44 stdout: "Component 1 Density: 16MB", 45 expected: []uint64{}, 46 wantErr: errFailedToDetectRomSize, 47 }, 48 { 49 name: "component 1 and 2", 50 stdout: "Component 2 Density: 4MB\nComponent 1 Density: 8MB", 51 expected: []uint64{8 * 1024 * 1024, 4 * 1024 * 1024}, 52 wantErr: nil, 53 }, 54 } 55 for _, tc := range testCases { 56 t.Run(tc.name, func(t *testing.T) { 57 result, err := ExtractSizeFromString(tc.stdout) 58 equal := cmp.Equal(tc.expected, result) 59 if !equal { 60 fmt.Println(cmp.Diff(tc.expected, result)) 61 assert.True(t, equal, "failed to extract size of ROM from string") 62 } 63 assert.ErrorIs(t, err, tc.wantErr) 64 }) 65 } 66 } 67 68 func TestStringToSizeMB(t *testing.T) { 69 testCases := []struct { 70 name string 71 text string 72 expected uint64 73 wantErr error 74 }{ 75 { 76 name: "empty string", 77 text: "", 78 expected: 0, 79 wantErr: errFailedToDetectRomSize, 80 }, 81 { 82 name: "UNUSED", 83 text: "UNUSED", 84 expected: 0, 85 wantErr: nil, 86 }, 87 { 88 name: "unused", 89 text: "unused", 90 expected: 0, 91 wantErr: nil, 92 }, 93 { 94 name: "4MB", 95 text: "4MB", 96 expected: 4 * 1024 * 1024, 97 wantErr: nil, 98 }, 99 { 100 name: "64MB", 101 text: "64MB", 102 expected: 64 * 1024 * 1024, 103 wantErr: nil, 104 }, 105 { 106 name: "64MB with white space", 107 text: " 64MB ", 108 expected: 64 * 1024 * 1024, 109 wantErr: nil, 110 }, 111 { 112 name: "64MB with whitespace and newlines", 113 text: "\n 64MB \n", 114 expected: 64 * 1024 * 1024, 115 wantErr: nil, 116 }, 117 { 118 name: "bogus string", 119 text: "bogus string", 120 expected: 0, 121 wantErr: errFailedToDetectRomSize, 122 }, 123 } 124 for _, tc := range testCases { 125 t.Run(tc.name, func(t *testing.T) { 126 result, err := StringToSizeMB(tc.text) 127 equal := cmp.Equal(tc.expected, result) 128 if !equal { 129 fmt.Println(cmp.Diff(tc.expected, result)) 130 assert.True(t, equal, "failed to decipher size") 131 } 132 assert.ErrorIs(t, err, tc.wantErr) 133 }) 134 } 135 } 136 137 type makeFile struct { 138 Path string 139 Content string 140 SourcePath string 141 } 142 143 func (base makeFile) MakeMe() error { 144 // If file does not exist, make it 145 if _, err := os.Stat(base.Path); os.IsNotExist(err) { 146 if base.Content != "" { 147 // Create file with content 148 file, err := os.Create(base.Path) 149 if err != nil { 150 return err 151 } 152 _, err = file.Write([]byte(base.Content)) 153 if err != nil { 154 return err 155 } 156 } else { 157 // Copy file from somewhere 158 if _, err := os.Stat(base.SourcePath); os.IsNotExist(err) { 159 log.Printf("[Mock MakeMe] file '%s' does not exists", base.SourcePath) 160 return os.ErrNotExist 161 } 162 return filesystem.CopyFile(base.SourcePath, base.Path) 163 } 164 } 165 return nil 166 } 167 168 func TestStitching(t *testing.T) { 169 if testing.Short() { 170 t.Skip("skipping test in short mode") 171 } 172 173 baseFileName := "base.img" 174 common := CommonOpts{ 175 SdkURL: "ghcr.io/9elements/firmware-action/coreboot_4.19:main", 176 OutputDir: "output", 177 ContainerOutputFiles: []string{ 178 fmt.Sprintf("new_%s", baseFileName), 179 }, 180 } 181 182 // Download blobs (contains example IFD.bin) 183 blobDir := "__tmp_files__/blobs" 184 if _, err := os.Stat(blobDir); os.IsNotExist(err) { 185 err := exec.Command("git", "clone", "https://review.coreboot.org/blobs.git", blobDir).Run() 186 assert.NoError(t, err) 187 } 188 189 testCases := []struct { 190 name string 191 stitchingOpts FirmwareStitchingOpts 192 files []makeFile 193 expectedSha256 string 194 wantErr error 195 }{ 196 { 197 name: "real test - inject ME into IFD", 198 stitchingOpts: FirmwareStitchingOpts{ 199 CommonOpts: common, 200 BaseFilePath: baseFileName, 201 IfdtoolEntries: []IfdtoolEntry{ 202 { 203 Path: "me.bin", 204 TargetRegion: "ME", 205 }, 206 }, 207 }, 208 files: []makeFile{ 209 { 210 Path: baseFileName, 211 SourcePath: "__tmp_files__/blobs/mainboard/intel/emeraldlake2/descriptor.bin", 212 }, 213 { 214 Path: "me.bin", 215 SourcePath: "__tmp_files__/blobs/mainboard/intel/emeraldlake2/me.bin", 216 }, 217 }, 218 expectedSha256: "a09cf57dae3062b18ae84f6695a22c5e1e61e3a84a9c9de69af40a0e54b658d4", 219 // this magic value was obtained by doing all steps manually 220 wantErr: nil, 221 }, 222 } 223 for _, tc := range testCases { 224 t.Run(tc.name, func(t *testing.T) { 225 // Prep 226 // vars 227 tmpDir := t.TempDir() 228 tc.stitchingOpts.RepoPath = filepath.Join(tmpDir, "stitch") 229 // make repo dir 230 err := os.Mkdir(tc.stitchingOpts.RepoPath, os.ModePerm) 231 assert.NoError(t, err) 232 outputPath := filepath.Join(tmpDir, tc.stitchingOpts.OutputDir) 233 err = os.MkdirAll(outputPath, os.ModePerm) 234 assert.NoError(t, err) 235 236 // Change current working directory 237 pwd, err := os.Getwd() 238 defer os.Chdir(pwd) // nolint:errcheck 239 assert.NoError(t, err) 240 err = os.Chdir(tmpDir) 241 assert.NoError(t, err) 242 243 // Move files 244 for i := range tc.files { 245 tc.files[i].SourcePath = filepath.Join(pwd, tc.files[i].SourcePath) 246 err = tc.files[i].MakeMe() 247 assert.NoError(t, err) 248 } 249 250 // Stitch 251 ctx := context.Background() 252 client, err := dagger.Connect(ctx, dagger.WithLogOutput(os.Stdout)) 253 assert.NoError(t, err) 254 defer client.Close() 255 256 _, err = tc.stitchingOpts.buildFirmware(ctx, client, "") 257 assert.ErrorIs(t, err, tc.wantErr) 258 if tc.wantErr != nil { 259 return 260 } 261 262 // Check artifacts 263 finalImageFile := filepath.Join( 264 outputPath, 265 tc.stitchingOpts.ContainerOutputFiles[0], 266 ) 267 if tc.wantErr == nil { 268 assert.ErrorIs(t, filesystem.CheckFileExists(finalImageFile), os.ErrExist) 269 } 270 271 // Compare 272 newContent, err := os.ReadFile(finalImageFile) 273 assert.NoError(t, err) 274 hash := sha256.New() 275 hash.Write(newContent) 276 hashHex := hex.EncodeToString(hash.Sum(nil)) 277 // TODO: fix expected vs actual values in all of these 278 assert.Equal(t, tc.expectedSha256, hashHex) 279 }) 280 } 281 }