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  }