github.com/canonical/ubuntu-image@v0.0.0-20240430122802-2202fe98b290/internal/statemachine/classic_test.go (about)

     1  // This test file tests a successful classic run and success/error scenarios for all states
     2  // that are specific to the classic builds
     3  package statemachine
     4  
     5  import (
     6  	"bufio"
     7  	"context"
     8  	"errors"
     9  	"fmt"
    10  	"io"
    11  	"os"
    12  	"os/exec"
    13  	"path"
    14  	"path/filepath"
    15  	"reflect"
    16  	"regexp"
    17  	"runtime"
    18  	"strconv"
    19  	"strings"
    20  	"testing"
    21  
    22  	"github.com/pkg/xattr"
    23  	"github.com/snapcore/snapd/image"
    24  	"github.com/snapcore/snapd/osutil"
    25  	"github.com/snapcore/snapd/seed"
    26  	"github.com/snapcore/snapd/store"
    27  	"github.com/xeipuuv/gojsonschema"
    28  	"gopkg.in/yaml.v2"
    29  
    30  	"github.com/canonical/ubuntu-image/internal/helper"
    31  	"github.com/canonical/ubuntu-image/internal/imagedefinition"
    32  	"github.com/canonical/ubuntu-image/internal/testhelper"
    33  )
    34  
    35  var yamlMarshal = yaml.Marshal
    36  
    37  func TestMain(m *testing.M) {
    38  	basicChroot = NewBasicChroot()
    39  	code := m.Run()
    40  	basicChroot.Clean()
    41  	os.Exit(code)
    42  }
    43  
    44  // TestClassicSetup tests a successful run of the polymorphed Setup function
    45  func TestClassicSetup(t *testing.T) {
    46  	asserter := helper.Asserter{T: t}
    47  	restoreCWD := testhelper.SaveCWD()
    48  	defer restoreCWD()
    49  
    50  	var stateMachine ClassicStateMachine
    51  	stateMachine.commonFlags, stateMachine.stateMachineFlags = helper.InitCommonOpts()
    52  	stateMachine.parent = &stateMachine
    53  	stateMachine.Args.ImageDefinition = filepath.Join("testdata", "image_definitions",
    54  		"test_amd64.yaml")
    55  
    56  	err := stateMachine.Setup()
    57  	asserter.AssertErrNil(err, true)
    58  }
    59  
    60  // TestYAMLSchemaParsing attempts to parse a variety of image definition files, both
    61  // valid and invalid, and ensures the correct result/errors are returned
    62  func TestYAMLSchemaParsing(t *testing.T) {
    63  	t.Parallel()
    64  	testCases := []struct {
    65  		name            string
    66  		imageDefinition string
    67  		shouldPass      bool
    68  		expectedError   string
    69  	}{
    70  		{"valid_image_definition", "test_raspi.yaml", true, ""},
    71  		{"invalid_class", "test_bad_class.yaml", false, "Class must be one of the following"},
    72  		{"invalid_url", "test_bad_url.yaml", false, "Does not match format 'uri'"},
    73  		{"invalid_model_assertion_url", "test_invalid_model_assertion_url.yaml", false, "Does not match format 'uri'"},
    74  		{"invalid_ppa_name", "test_bad_ppa_name.yaml", false, "PPAName: Does not match pattern"},
    75  		{"invalid_ppa_auth", "test_bad_ppa_name.yaml", false, "Auth: Does not match pattern"},
    76  		{"both_seed_and_tasks", "test_both_seed_and_tasks.yaml", false, "Must validate one and only one schema"},
    77  		{"git_gadget_without_url", "test_git_gadget_without_url.yaml", false, "When key gadget:type is specified as git, a URL must be provided"},
    78  		{"file_doesnt_exist", "test_not_exist.yaml", false, "no such file or directory"},
    79  		{"not_valid_yaml", "test_invalid_yaml.yaml", false, "yaml: unmarshal errors"},
    80  		{"missing_yaml_fields", "test_missing_name.yaml", false, "Key \"name\" is required in struct \"ImageDefinition\", but is not in the YAML file!"},
    81  		{"private_ppa_without_fingerprint", "test_private_ppa_without_fingerprint.yaml", false, "Fingerprint is required for private PPAs"},
    82  		{"invalid_paths_in_manual_copy", "test_invalid_paths_in_manual_copy.yaml", false, "needs to be an absolute path (../../malicious)"},
    83  		{"invalid_paths_in_manual_copy_bug", "test_invalid_paths_in_manual_copy.yaml", false, "needs to be an absolute path (/../../malicious)"},
    84  		{"invalid_paths_in_manual_mkdir", "test_invalid_paths_in_manual_mkdir.yaml", false, "needs to be an absolute path (../../malicious)"},
    85  		{"invalid_paths_in_manual_mkdir_bug", "test_invalid_paths_in_manual_mkdir.yaml", false, "needs to be an absolute path (/../../malicious)"},
    86  		{"invalid_paths_in_manual_touch_file", "test_invalid_paths_in_manual_touch_file.yaml", false, "needs to be an absolute path (../../malicious)"},
    87  		{"invalid_paths_in_manual_touch_file_bug", "test_invalid_paths_in_manual_touch_file.yaml", false, "needs to be an absolute path (/../../malicious)"},
    88  		{"img_specified_without_gadget", "test_image_without_gadget.yaml", false, "Key img cannot be used without key gadget:"},
    89  	}
    90  	for _, tc := range testCases {
    91  		t.Run(tc.name, func(t *testing.T) {
    92  			asserter := helper.Asserter{T: t}
    93  			restoreCWD := testhelper.SaveCWD()
    94  			defer restoreCWD()
    95  
    96  			var stateMachine ClassicStateMachine
    97  			stateMachine.commonFlags, stateMachine.stateMachineFlags = helper.InitCommonOpts()
    98  			stateMachine.parent = &stateMachine
    99  			stateMachine.Args.ImageDefinition = filepath.Join("testdata", "image_definitions",
   100  				tc.imageDefinition)
   101  			err := stateMachine.parseImageDefinition()
   102  
   103  			if tc.shouldPass {
   104  				asserter.AssertErrNil(err, false)
   105  			} else {
   106  				asserter.AssertErrContains(err, tc.expectedError)
   107  			}
   108  		})
   109  	}
   110  }
   111  
   112  // TestFailedParseImageDefinition mocks function calls to test
   113  // failure cases in the parseImageDefinition state
   114  func TestFailedParseImageDefinition(t *testing.T) {
   115  	asserter := helper.Asserter{T: t}
   116  	restoreCWD := testhelper.SaveCWD()
   117  	defer restoreCWD()
   118  
   119  	var stateMachine ClassicStateMachine
   120  	stateMachine.commonFlags, stateMachine.stateMachineFlags = helper.InitCommonOpts()
   121  	stateMachine.parent = &stateMachine
   122  	stateMachine.Args.ImageDefinition = filepath.Join("testdata", "image_definitions",
   123  		"test_raspi.yaml")
   124  
   125  	// mock helper.SetDefaults
   126  	helperSetDefaults = mockSetDefaults
   127  	t.Cleanup(func() {
   128  		helperSetDefaults = helper.SetDefaults
   129  	})
   130  	err := stateMachine.parseImageDefinition()
   131  	asserter.AssertErrContains(err, "Test Error")
   132  	helperSetDefaults = helper.SetDefaults
   133  
   134  	// mock helper.CheckEmptyFields
   135  	helperCheckEmptyFields = mockCheckEmptyFields
   136  	t.Cleanup(func() {
   137  		helperCheckEmptyFields = helper.CheckEmptyFields
   138  	})
   139  	err = stateMachine.parseImageDefinition()
   140  	asserter.AssertErrContains(err, "Test Error")
   141  	helperCheckEmptyFields = helper.CheckEmptyFields
   142  
   143  	// mock gojsonschema.Validate
   144  	gojsonschemaValidate = mockGojsonschemaValidateError
   145  	t.Cleanup(func() {
   146  		gojsonschemaValidate = gojsonschema.Validate
   147  	})
   148  	err = stateMachine.parseImageDefinition()
   149  	asserter.AssertErrContains(err, "Schema validation returned an error")
   150  	gojsonschemaValidate = gojsonschema.Validate
   151  
   152  	// mock helper.CheckTags
   153  	// the gadget must be set to nil for this test to work
   154  	stateMachine.Args.ImageDefinition = filepath.Join("testdata", "image_definitions",
   155  		"test_image_without_gadget.yaml")
   156  	helperCheckTags = mockCheckTags
   157  	t.Cleanup(func() {
   158  		helperCheckTags = helper.CheckTags
   159  	})
   160  	err = stateMachine.parseImageDefinition()
   161  	asserter.AssertErrContains(err, "Test Error")
   162  	helperCheckTags = helper.CheckTags
   163  }
   164  
   165  // TestClassicStateMachine_calculateStates reads in a variety of yaml files and ensures
   166  // that the correct states are added to the state machine
   167  // TODO: manually assemble the image definitions instead of relying on the parseImageDefinition() function to make this more of a unit test
   168  func TestClassicStateMachine_calculateStates(t *testing.T) {
   169  	t.Parallel()
   170  	testCases := []struct {
   171  		name            string
   172  		imageDefinition string
   173  		expectedStates  []string
   174  	}{
   175  		{
   176  			name:            "state_build_gadget",
   177  			imageDefinition: "test_build_gadget.yaml",
   178  			expectedStates: []string{
   179  				"build_gadget_tree",
   180  				"prepare_gadget_tree",
   181  				"load_gadget_yaml",
   182  				"verify_artifact_names",
   183  				"germinate",
   184  				"create_chroot",
   185  				"install_packages",
   186  				"prepare_image",
   187  				"preseed_image",
   188  				"clean_rootfs",
   189  				"customize_sources_list",
   190  				"customize_cloud_init",
   191  				"set_default_locale",
   192  				"populate_rootfs_contents",
   193  				"calculate_rootfs_size",
   194  				"populate_bootfs_contents",
   195  				"populate_prepare_partitions",
   196  				"make_disk",
   197  				"update_bootloader",
   198  				"generate_package_manifest",
   199  			},
   200  		},
   201  		{
   202  			name:            "state_prebuilt_gadget",
   203  			imageDefinition: "test_prebuilt_gadget.yaml",
   204  			expectedStates: []string{
   205  				"build_gadget_tree",
   206  				"prepare_gadget_tree",
   207  				"load_gadget_yaml",
   208  				"verify_artifact_names",
   209  				"germinate",
   210  				"create_chroot",
   211  				"install_packages",
   212  				"prepare_image",
   213  				"preseed_image",
   214  				"clean_rootfs",
   215  				"customize_sources_list",
   216  				"customize_cloud_init",
   217  				"set_default_locale",
   218  				"populate_rootfs_contents",
   219  				"calculate_rootfs_size",
   220  				"populate_bootfs_contents",
   221  				"populate_prepare_partitions",
   222  				"make_disk",
   223  				"update_bootloader",
   224  				"generate_package_manifest",
   225  			},
   226  		},
   227  		{
   228  			name:            "state_prebuilt_rootfs_extras",
   229  			imageDefinition: "test_prebuilt_rootfs_extras.yaml",
   230  			expectedStates: []string{
   231  				"build_gadget_tree",
   232  				"prepare_gadget_tree",
   233  				"load_gadget_yaml",
   234  				"verify_artifact_names",
   235  				"extract_rootfs_tar",
   236  				"add_extra_ppas",
   237  				"install_packages",
   238  				"clean_extra_ppas",
   239  				"prepare_image",
   240  				"preseed_image",
   241  				"clean_rootfs",
   242  				"customize_sources_list",
   243  				"customize_cloud_init",
   244  				"set_default_locale",
   245  				"populate_rootfs_contents",
   246  				"calculate_rootfs_size",
   247  				"populate_bootfs_contents",
   248  				"populate_prepare_partitions",
   249  				"make_disk",
   250  				"update_bootloader",
   251  				"generate_package_manifest",
   252  			},
   253  		},
   254  		{
   255  			name:            "state_ppa",
   256  			imageDefinition: "test_amd64.yaml",
   257  			expectedStates: []string{
   258  				"build_gadget_tree",
   259  				"prepare_gadget_tree",
   260  				"load_gadget_yaml",
   261  				"verify_artifact_names",
   262  				"germinate",
   263  				"create_chroot",
   264  				"add_extra_ppas",
   265  				"install_packages",
   266  				"clean_extra_ppas",
   267  				"prepare_image",
   268  				"preseed_image",
   269  				"clean_rootfs",
   270  				"customize_sources_list",
   271  				"customize_cloud_init",
   272  				"perform_manual_customization",
   273  				"set_default_locale",
   274  				"populate_rootfs_contents",
   275  				"calculate_rootfs_size",
   276  				"populate_bootfs_contents",
   277  				"populate_prepare_partitions",
   278  				"make_disk",
   279  				"update_bootloader",
   280  				"make_qcow2_image",
   281  				"generate_package_manifest",
   282  				"generate_filelist",
   283  			},
   284  		},
   285  		{
   286  			name:            "extract_rootfs_tar",
   287  			imageDefinition: "test_extract_rootfs_tar.yaml",
   288  			expectedStates: []string{
   289  				"build_gadget_tree",
   290  				"prepare_gadget_tree",
   291  				"load_gadget_yaml",
   292  				"verify_artifact_names",
   293  				"extract_rootfs_tar",
   294  				"install_packages",
   295  				"clean_rootfs",
   296  				"customize_sources_list",
   297  				"customize_cloud_init",
   298  				"set_default_locale",
   299  				"populate_rootfs_contents",
   300  				"calculate_rootfs_size",
   301  				"populate_bootfs_contents",
   302  				"populate_prepare_partitions",
   303  				"make_disk",
   304  				"update_bootloader",
   305  				"generate_package_manifest",
   306  			},
   307  		},
   308  		{
   309  			name:            "extract_rootfs_tar_no_customization",
   310  			imageDefinition: "test_extract_rootfs_tar_no_customization.yaml",
   311  			expectedStates: []string{
   312  				"build_gadget_tree",
   313  				"prepare_gadget_tree",
   314  				"load_gadget_yaml",
   315  				"verify_artifact_names",
   316  				"extract_rootfs_tar",
   317  				"clean_rootfs",
   318  				"customize_sources_list",
   319  				"set_default_locale",
   320  				"populate_rootfs_contents",
   321  				"calculate_rootfs_size",
   322  				"populate_bootfs_contents",
   323  				"populate_prepare_partitions",
   324  				"make_disk",
   325  				"update_bootloader",
   326  				"generate_package_manifest",
   327  			},
   328  		},
   329  		{
   330  			name:            "build_rootfs_from_seed",
   331  			imageDefinition: "test_rootfs_seed.yaml",
   332  			expectedStates: []string{
   333  				"build_gadget_tree",
   334  				"prepare_gadget_tree",
   335  				"load_gadget_yaml",
   336  				"verify_artifact_names",
   337  				"germinate",
   338  				"create_chroot",
   339  				"install_packages",
   340  				"prepare_image",
   341  				"preseed_image",
   342  				"clean_rootfs",
   343  				"customize_sources_list",
   344  				"customize_cloud_init",
   345  				"set_default_locale",
   346  				"populate_rootfs_contents",
   347  				"calculate_rootfs_size",
   348  				"populate_bootfs_contents",
   349  				"populate_prepare_partitions",
   350  				"make_disk",
   351  				"update_bootloader",
   352  				"generate_package_manifest",
   353  			},
   354  		},
   355  		{
   356  			name:            "build_rootfs_from_tasks",
   357  			imageDefinition: "test_rootfs_tasks.yaml",
   358  			expectedStates: []string{
   359  				"build_gadget_tree",
   360  				"prepare_gadget_tree",
   361  				"load_gadget_yaml",
   362  				"verify_artifact_names",
   363  				"build_rootfs_from_tasks",
   364  				"clean_rootfs",
   365  				"customize_sources_list",
   366  				"customize_cloud_init",
   367  				"set_default_locale",
   368  				"populate_rootfs_contents",
   369  				"calculate_rootfs_size",
   370  				"populate_bootfs_contents",
   371  				"populate_prepare_partitions",
   372  				"make_disk",
   373  				"update_bootloader",
   374  				"generate_package_manifest",
   375  			},
   376  		},
   377  		{
   378  			name:            "customization_states",
   379  			imageDefinition: "test_customization.yaml",
   380  			expectedStates: []string{
   381  				"build_gadget_tree",
   382  				"prepare_gadget_tree",
   383  				"load_gadget_yaml",
   384  				"verify_artifact_names",
   385  				"germinate",
   386  				"create_chroot",
   387  				"add_extra_ppas",
   388  				"install_packages",
   389  				"clean_extra_ppas",
   390  				"prepare_image",
   391  				"preseed_image",
   392  				"clean_rootfs",
   393  				"customize_sources_list",
   394  				"customize_cloud_init",
   395  				"perform_manual_customization",
   396  				"set_default_locale",
   397  				"populate_rootfs_contents",
   398  				"calculate_rootfs_size",
   399  				"populate_bootfs_contents",
   400  				"populate_prepare_partitions",
   401  				"make_disk",
   402  				"update_bootloader",
   403  				"generate_package_manifest",
   404  			},
   405  		},
   406  		{
   407  			name:            "qcow2",
   408  			imageDefinition: "test_qcow2.yaml",
   409  			expectedStates: []string{
   410  				"build_gadget_tree",
   411  				"prepare_gadget_tree",
   412  				"load_gadget_yaml",
   413  				"verify_artifact_names",
   414  				"germinate",
   415  				"create_chroot",
   416  				"add_extra_ppas",
   417  				"install_packages",
   418  				"clean_extra_ppas",
   419  				"prepare_image",
   420  				"preseed_image",
   421  				"clean_rootfs",
   422  				"customize_sources_list",
   423  				"customize_cloud_init",
   424  				"set_default_locale",
   425  				"populate_rootfs_contents",
   426  				"calculate_rootfs_size",
   427  				"populate_bootfs_contents",
   428  				"populate_prepare_partitions",
   429  				"make_disk",
   430  				"update_bootloader",
   431  				"make_qcow2_image",
   432  			},
   433  		},
   434  		{
   435  			name:            "no artifact",
   436  			imageDefinition: "test_no_artifact.yaml",
   437  			expectedStates: []string{
   438  				"build_gadget_tree",
   439  				"prepare_gadget_tree",
   440  				"load_gadget_yaml",
   441  				"germinate",
   442  				"create_chroot",
   443  				"add_extra_ppas",
   444  				"install_packages",
   445  				"clean_extra_ppas",
   446  				"prepare_image",
   447  				"preseed_image",
   448  				"clean_rootfs",
   449  				"customize_sources_list",
   450  				"customize_cloud_init",
   451  				"perform_manual_customization",
   452  				"set_default_locale",
   453  				"populate_rootfs_contents",
   454  			},
   455  		},
   456  	}
   457  	for _, tc := range testCases {
   458  		t.Run(tc.name, func(t *testing.T) {
   459  			asserter := helper.Asserter{T: t}
   460  			restoreCWD := testhelper.SaveCWD()
   461  			defer restoreCWD()
   462  
   463  			var stateMachine ClassicStateMachine
   464  			stateMachine.commonFlags, stateMachine.stateMachineFlags = helper.InitCommonOpts()
   465  			stateMachine.parent = &stateMachine
   466  			stateMachine.Args.ImageDefinition = filepath.Join("testdata", "image_definitions", tc.imageDefinition)
   467  			err := stateMachine.parseImageDefinition()
   468  			asserter.AssertErrNil(err, true)
   469  
   470  			err = stateMachine.calculateStates()
   471  			asserter.AssertErrNil(err, true)
   472  
   473  			stateNames := make([]string, 0)
   474  			for _, f := range stateMachine.states {
   475  				stateNames = append(stateNames, f.name)
   476  			}
   477  
   478  			asserter.AssertEqual(tc.expectedStates, stateNames)
   479  		})
   480  	}
   481  }
   482  
   483  // TestFailedCalculateStates tests failure scenarios in the
   484  // calculateStates function
   485  func TestFailedCalculateStates(t *testing.T) {
   486  	asserter := helper.Asserter{T: t}
   487  	restoreCWD := testhelper.SaveCWD()
   488  	defer restoreCWD()
   489  
   490  	var stateMachine ClassicStateMachine
   491  	stateMachine.commonFlags, stateMachine.stateMachineFlags = helper.InitCommonOpts()
   492  	stateMachine.parent = &stateMachine
   493  	stateMachine.ImageDef = imagedefinition.ImageDefinition{
   494  		Gadget: &imagedefinition.Gadget{
   495  			GadgetType: "git",
   496  		},
   497  		Rootfs: &imagedefinition.Rootfs{
   498  			ArchiveTasks: []string{"test"},
   499  		},
   500  		Customization: &imagedefinition.Customization{},
   501  		Artifacts:     &imagedefinition.Artifact{},
   502  	}
   503  
   504  	// mock helper.CheckTags
   505  	// the gadget must be set to nil for this test to work
   506  	helperCheckTags = mockCheckTags
   507  	t.Cleanup(func() {
   508  		helperCheckTags = helper.CheckTags
   509  	})
   510  	err := stateMachine.calculateStates()
   511  	asserter.AssertErrContains(err, "Test Error")
   512  }
   513  
   514  // TestDisplayStates ensures the states are printed to stdout when the --debug flag is set
   515  func TestDisplayStates(t *testing.T) {
   516  	asserter := helper.Asserter{T: t}
   517  	restoreCWD := testhelper.SaveCWD()
   518  	defer restoreCWD()
   519  
   520  	var stateMachine ClassicStateMachine
   521  	stateMachine.commonFlags, stateMachine.stateMachineFlags = helper.InitCommonOpts()
   522  	stateMachine.parent = &stateMachine
   523  	stateMachine.commonFlags.Debug = true
   524  	stateMachine.commonFlags.DiskInfo = "test" // for coverage!
   525  	stateMachine.Args.ImageDefinition = filepath.Join("testdata", "image_definitions", "test_raspi.yaml")
   526  	err := stateMachine.parseImageDefinition()
   527  	asserter.AssertErrNil(err, true)
   528  
   529  	// capture stdout, calculate the states, and ensure they were printed
   530  	stdout, restoreStdout, err := helper.CaptureStd(&os.Stdout)
   531  	defer restoreStdout()
   532  	asserter.AssertErrNil(err, true)
   533  
   534  	err = stateMachine.calculateStates()
   535  	asserter.AssertErrNil(err, true)
   536  
   537  	stateMachine.displayStates()
   538  	asserter.AssertErrNil(err, true)
   539  
   540  	// restore stdout and examine what was printed
   541  	restoreStdout()
   542  	readStdout, err := io.ReadAll(stdout)
   543  	asserter.AssertErrNil(err, true)
   544  
   545  	expectedStates := `Following states will be executed:
   546  [0] build_gadget_tree
   547  [1] prepare_gadget_tree
   548  [2] load_gadget_yaml
   549  [3] verify_artifact_names
   550  [4] germinate
   551  [5] create_chroot
   552  [6] install_packages
   553  [7] prepare_image
   554  [8] preseed_image
   555  [9] clean_rootfs
   556  [10] customize_sources_list
   557  [11] customize_fstab
   558  [12] perform_manual_customization
   559  [13] set_default_locale
   560  [14] populate_rootfs_contents
   561  [15] generate_disk_info
   562  [16] calculate_rootfs_size
   563  [17] populate_bootfs_contents
   564  [18] populate_prepare_partitions
   565  [19] make_disk
   566  [20] update_bootloader
   567  [21] generate_package_manifest
   568  `
   569  	if !strings.Contains(string(readStdout), expectedStates) {
   570  		t.Errorf("Expected states to be printed in output:\n\"%s\"\n but got \n\"%s\"\n instead",
   571  			expectedStates, string(readStdout))
   572  	}
   573  }
   574  
   575  // TestClassicStateMachine_Setup_Fail_setConfDefDir tests a failure in the Setup() function when setting the configuration definition directory
   576  func TestClassicStateMachine_Setup_Fail_setConfDefDir(t *testing.T) {
   577  	asserter := helper.Asserter{T: t}
   578  	restoreCWD := testhelper.SaveCWD()
   579  	defer restoreCWD()
   580  
   581  	var stateMachine ClassicStateMachine
   582  	stateMachine.commonFlags, stateMachine.stateMachineFlags = helper.InitCommonOpts()
   583  
   584  	tmpDirPath := filepath.Join("/tmp", "test_failed_set_conf_dir")
   585  	err := os.Mkdir(tmpDirPath, 0755)
   586  	t.Cleanup(func() {
   587  		os.RemoveAll(tmpDirPath)
   588  	})
   589  	asserter.AssertErrNil(err, true)
   590  
   591  	err = os.Chdir(tmpDirPath)
   592  	asserter.AssertErrNil(err, true)
   593  
   594  	_ = os.RemoveAll(tmpDirPath)
   595  
   596  	err = stateMachine.Setup()
   597  	asserter.AssertErrContains(err, "unable to determine the configuration definition directory")
   598  	os.RemoveAll(stateMachine.stateMachineFlags.WorkDir)
   599  }
   600  
   601  // TestFailedValidateInputClassic tests a failure in the Setup() function when validating common input
   602  func TestFailedValidateInputClassic(t *testing.T) {
   603  	asserter := helper.Asserter{T: t}
   604  	restoreCWD := testhelper.SaveCWD()
   605  	defer restoreCWD()
   606  
   607  	// use both --until and --thru to trigger this failure
   608  	var stateMachine ClassicStateMachine
   609  	stateMachine.commonFlags, stateMachine.stateMachineFlags = helper.InitCommonOpts()
   610  	stateMachine.stateMachineFlags.Until = "until-test"
   611  	stateMachine.stateMachineFlags.Thru = "thru-test"
   612  
   613  	err := stateMachine.Setup()
   614  	t.Cleanup(func() { os.RemoveAll(stateMachine.stateMachineFlags.WorkDir) })
   615  	asserter.AssertErrContains(err, "cannot specify both --until and --thru")
   616  }
   617  
   618  // TestFailedReadMetadataClassic tests a failed metadata read by passing --resume with no previous partial state machine run
   619  func TestFailedReadMetadataClassic(t *testing.T) {
   620  	asserter := helper.Asserter{T: t}
   621  	restoreCWD := testhelper.SaveCWD()
   622  	defer restoreCWD()
   623  
   624  	// start a --resume with no previous SM run
   625  	var stateMachine ClassicStateMachine
   626  	stateMachine.commonFlags, stateMachine.stateMachineFlags = helper.InitCommonOpts()
   627  	stateMachine.stateMachineFlags.Resume = true
   628  	stateMachine.stateMachineFlags.WorkDir = testDir
   629  	stateMachine.Args.ImageDefinition = filepath.Join("testdata", "image_definitions",
   630  		"test_amd64.yaml")
   631  
   632  	err := stateMachine.Setup()
   633  	asserter.AssertErrContains(err, "error reading metadata file")
   634  	os.RemoveAll(stateMachine.stateMachineFlags.WorkDir)
   635  }
   636  
   637  // TestClassicStateMachine_Setup_Fail_makeTemporaryDirectories tests the Setup function
   638  // with makeTemporaryDirectories failing
   639  func TestClassicStateMachine_Setup_Fail_makeTemporaryDirectories(t *testing.T) {
   640  	asserter := helper.Asserter{T: t}
   641  	restoreCWD := testhelper.SaveCWD()
   642  	defer restoreCWD()
   643  
   644  	var stateMachine ClassicStateMachine
   645  	stateMachine.commonFlags, stateMachine.stateMachineFlags = helper.InitCommonOpts()
   646  	stateMachine.stateMachineFlags.WorkDir = testDir
   647  	stateMachine.Args.ImageDefinition = filepath.Join("testdata", "image_definitions",
   648  		"test_amd64.yaml")
   649  
   650  	// mock os.MkdirAll
   651  	osMkdirAll = mockMkdirAll
   652  	t.Cleanup(func() {
   653  		osMkdirAll = os.MkdirAll
   654  	})
   655  	err := stateMachine.Setup()
   656  	asserter.AssertErrContains(err, "Error creating work directory")
   657  }
   658  
   659  // TestClassicStateMachine_Setup_Fail_determineOutputDirectory tests the Setup function
   660  // with determineOutputDirectory failing
   661  func TestClassicStateMachine_Setup_Fail_determineOutputDirectory(t *testing.T) {
   662  	asserter := helper.Asserter{T: t}
   663  	restoreCWD := testhelper.SaveCWD()
   664  	defer restoreCWD()
   665  
   666  	var stateMachine ClassicStateMachine
   667  	stateMachine.commonFlags, stateMachine.stateMachineFlags = helper.InitCommonOpts()
   668  	stateMachine.Args.ImageDefinition = filepath.Join("testdata", "image_definitions",
   669  		"test_amd64.yaml")
   670  	stateMachine.commonFlags.OutputDir = "/tmp/test"
   671  
   672  	// mock os.MkdirAll
   673  	osMkdirAll = mockMkdirAll
   674  	t.Cleanup(func() {
   675  		osMkdirAll = os.MkdirAll
   676  	})
   677  	err := stateMachine.Setup()
   678  	asserter.AssertErrContains(err, "Error creating OutputDir")
   679  }
   680  
   681  // TestClassicStateMachine_DryRun tests a successful dry-run execution
   682  func TestClassicStateMachine_DryRun(t *testing.T) {
   683  	asserter := helper.Asserter{T: t}
   684  	restoreCWD := testhelper.SaveCWD()
   685  	defer restoreCWD()
   686  
   687  	workDir := "ubuntu-image-test-dry-run"
   688  	err := os.Mkdir(workDir, 0755)
   689  	asserter.AssertErrNil(err, true)
   690  
   691  	t.Cleanup(func() { os.RemoveAll(workDir) })
   692  
   693  	var stateMachine ClassicStateMachine
   694  	stateMachine.commonFlags, stateMachine.stateMachineFlags = helper.InitCommonOpts()
   695  	stateMachine.parent = &stateMachine
   696  	stateMachine.Args.ImageDefinition = filepath.Join("testdata", "image_definitions",
   697  		"test_amd64.yaml")
   698  	stateMachine.stateMachineFlags.WorkDir = workDir
   699  	stateMachine.commonFlags.DryRun = true
   700  
   701  	err = stateMachine.Setup()
   702  	asserter.AssertErrNil(err, true)
   703  
   704  	files, err := osReadDir(workDir)
   705  	asserter.AssertErrNil(err, true)
   706  
   707  	if len(files) != 0 {
   708  		t.Errorf("Some files were created in the workdir but should not. Created files: %s", files)
   709  	}
   710  
   711  	err = stateMachine.Run()
   712  	asserter.AssertErrNil(err, true)
   713  
   714  	err = stateMachine.Teardown()
   715  	asserter.AssertErrNil(err, true)
   716  }
   717  
   718  // TestPrepareGadgetTree runs prepareGadgetTree() and ensures the gadget_tree files
   719  // are placed in the correct locations
   720  func TestPrepareGadgetTree(t *testing.T) {
   721  	t.Parallel()
   722  	asserter := helper.Asserter{T: t}
   723  	restoreCWD := testhelper.SaveCWD()
   724  	defer restoreCWD()
   725  
   726  	var stateMachine ClassicStateMachine
   727  	stateMachine.commonFlags, stateMachine.stateMachineFlags = helper.InitCommonOpts()
   728  	stateMachine.parent = &stateMachine
   729  	stateMachine.ImageDef = imagedefinition.ImageDefinition{
   730  		Architecture: getHostArch(),
   731  		Series:       getHostSuite(),
   732  		Gadget:       &imagedefinition.Gadget{},
   733  	}
   734  
   735  	err := stateMachine.makeTemporaryDirectories()
   736  	asserter.AssertErrNil(err, true)
   737  	t.Cleanup(func() { os.RemoveAll(stateMachine.stateMachineFlags.WorkDir) })
   738  
   739  	// place a test gadget tree in the  scratch directory so we don't have to build one
   740  	gadgetDir := filepath.Join(stateMachine.tempDirs.scratch, "gadget")
   741  	err = os.MkdirAll(gadgetDir, 0755)
   742  	asserter.AssertErrNil(err, true)
   743  
   744  	gadgetSource := filepath.Join("testdata", "gadget_tree")
   745  	err = osutil.CopySpecialFile(gadgetSource, filepath.Join(gadgetDir, "install"))
   746  	asserter.AssertErrNil(err, true)
   747  
   748  	err = stateMachine.prepareGadgetTree()
   749  	asserter.AssertErrNil(err, true)
   750  
   751  	gadgetTreeFiles := []string{"grub.conf", "pc-boot.img", "meta/gadget.yaml"}
   752  	for _, file := range gadgetTreeFiles {
   753  		_, err := os.Stat(filepath.Join(stateMachine.tempDirs.unpack, "gadget", file))
   754  		if err != nil {
   755  			t.Errorf("File %s should be in unpack, but is missing", file)
   756  		}
   757  	}
   758  }
   759  
   760  // TestPrepareGadgetTreePrebuilt tests the prepareGadgetTree function with prebuilt gadgets
   761  func TestPrepareGadgetTreePrebuilt(t *testing.T) {
   762  	t.Parallel()
   763  	asserter := helper.Asserter{T: t}
   764  	restoreCWD := testhelper.SaveCWD()
   765  	defer restoreCWD()
   766  
   767  	var stateMachine ClassicStateMachine
   768  	stateMachine.commonFlags, stateMachine.stateMachineFlags = helper.InitCommonOpts()
   769  	stateMachine.parent = &stateMachine
   770  	stateMachine.ImageDef = imagedefinition.ImageDefinition{
   771  		Architecture: getHostArch(),
   772  		Series:       getHostSuite(),
   773  		Gadget: &imagedefinition.Gadget{
   774  			GadgetType: "prebuilt",
   775  			GadgetURL:  "testdata/gadget_tree/",
   776  		},
   777  	}
   778  
   779  	err := stateMachine.makeTemporaryDirectories()
   780  	asserter.AssertErrNil(err, true)
   781  	t.Cleanup(func() { os.RemoveAll(stateMachine.stateMachineFlags.WorkDir) })
   782  
   783  	err = stateMachine.prepareGadgetTree()
   784  	asserter.AssertErrNil(err, true)
   785  
   786  	gadgetTreeFiles := []string{"grub.conf", "pc-boot.img", "meta/gadget.yaml"}
   787  	for _, file := range gadgetTreeFiles {
   788  		_, err := os.Stat(filepath.Join(stateMachine.tempDirs.unpack, "gadget", file))
   789  		if err != nil {
   790  			t.Errorf("File %s should be in unpack, but is missing", file)
   791  		}
   792  	}
   793  }
   794  
   795  // TestFailedPrepareGadgetTree tests failures in the prepareGadgetTree function
   796  func TestFailedPrepareGadgetTree(t *testing.T) {
   797  	asserter := helper.Asserter{T: t}
   798  	var stateMachine ClassicStateMachine
   799  	stateMachine.commonFlags, stateMachine.stateMachineFlags = helper.InitCommonOpts()
   800  	stateMachine.parent = &stateMachine
   801  	stateMachine.ImageDef = imagedefinition.ImageDefinition{
   802  		Architecture: getHostArch(),
   803  		Series:       getHostSuite(),
   804  		Gadget:       &imagedefinition.Gadget{},
   805  	}
   806  
   807  	err := stateMachine.makeTemporaryDirectories()
   808  	asserter.AssertErrNil(err, true)
   809  
   810  	t.Cleanup(func() { os.RemoveAll(stateMachine.stateMachineFlags.WorkDir) })
   811  
   812  	// place a test gadget tree in the  scratch directory so we don't have to build one
   813  	gadgetDir := filepath.Join(stateMachine.tempDirs.scratch, "gadget")
   814  	err = os.MkdirAll(gadgetDir, 0755)
   815  	asserter.AssertErrNil(err, true)
   816  
   817  	gadgetSource := filepath.Join("testdata", "gadget_tree")
   818  	err = osutil.CopySpecialFile(gadgetSource, filepath.Join(gadgetDir, "install"))
   819  	asserter.AssertErrNil(err, true)
   820  
   821  	// mock os.Mkdir
   822  	osMkdirAll = mockMkdirAll
   823  	t.Cleanup(func() {
   824  		osMkdirAll = os.MkdirAll
   825  	})
   826  	err = stateMachine.prepareGadgetTree()
   827  	asserter.AssertErrContains(err, "Error creating unpack directory")
   828  	osMkdirAll = os.MkdirAll
   829  
   830  	// mock os.ReadDir
   831  	osReadDir = mockReadDir
   832  	t.Cleanup(func() {
   833  		osReadDir = os.ReadDir
   834  	})
   835  	err = stateMachine.prepareGadgetTree()
   836  	asserter.AssertErrContains(err, "Error reading gadget tree")
   837  	osReadDir = os.ReadDir
   838  
   839  	// mock osutil.CopySpecialFile
   840  	osutilCopySpecialFile = mockCopySpecialFile
   841  	t.Cleanup(func() {
   842  		osutilCopySpecialFile = osutil.CopySpecialFile
   843  	})
   844  	err = stateMachine.prepareGadgetTree()
   845  	asserter.AssertErrContains(err, "Error copying gadget tree")
   846  	osutilCopySpecialFile = osutil.CopySpecialFile
   847  }
   848  
   849  // TestVerifyArtifactNames unit tests the verifyArtifactNames function
   850  func TestVerifyArtifactNames(t *testing.T) {
   851  	t.Parallel()
   852  	testCases := []struct {
   853  		name             string
   854  		gadgetYAML       string
   855  		artifacts        *imagedefinition.Artifact
   856  		img              *[]imagedefinition.Img
   857  		qcow2            *[]imagedefinition.Qcow2
   858  		expectedVolNames map[string]string
   859  		shouldPass       bool
   860  	}{
   861  		{
   862  			name:             "no artifact ",
   863  			gadgetYAML:       "gadget_tree/meta/gadget.yaml",
   864  			artifacts:        nil,
   865  			expectedVolNames: nil,
   866  			shouldPass:       true,
   867  		},
   868  		{
   869  			name:       "single_volume_specified",
   870  			gadgetYAML: "gadget_tree/meta/gadget.yaml",
   871  			artifacts: &imagedefinition.Artifact{
   872  				Img: &[]imagedefinition.Img{
   873  					{
   874  						ImgName:   "test1.img",
   875  						ImgVolume: "pc",
   876  					},
   877  				},
   878  			},
   879  			expectedVolNames: map[string]string{
   880  				"pc": "test1.img",
   881  			},
   882  			shouldPass: true,
   883  		},
   884  		{
   885  			name:       "single_volume_not_specified",
   886  			gadgetYAML: "gadget_tree/meta/gadget.yaml",
   887  			artifacts: &imagedefinition.Artifact{
   888  				Img: &[]imagedefinition.Img{
   889  					{
   890  						ImgName: "test-single.img",
   891  					},
   892  				},
   893  			},
   894  			expectedVolNames: map[string]string{
   895  				"pc": "test-single.img",
   896  			},
   897  			shouldPass: true,
   898  		},
   899  		{
   900  			name:       "mutli_volume_specified",
   901  			gadgetYAML: "gadget-multi.yaml",
   902  			artifacts: &imagedefinition.Artifact{
   903  				Img: &[]imagedefinition.Img{
   904  					{
   905  						ImgName:   "test1.img",
   906  						ImgVolume: "first",
   907  					},
   908  					{
   909  						ImgName:   "test2.img",
   910  						ImgVolume: "second",
   911  					},
   912  					{
   913  						ImgName:   "test3.img",
   914  						ImgVolume: "third",
   915  					},
   916  					{
   917  						ImgName:   "test4.img",
   918  						ImgVolume: "fourth",
   919  					},
   920  				},
   921  			},
   922  			expectedVolNames: map[string]string{
   923  				"first":  "test1.img",
   924  				"second": "test2.img",
   925  				"third":  "test3.img",
   926  				"fourth": "test4.img",
   927  			},
   928  			shouldPass: true,
   929  		},
   930  		{
   931  			name:       "mutli_volume_not_specified",
   932  			gadgetYAML: "gadget-multi.yaml",
   933  			artifacts: &imagedefinition.Artifact{
   934  				Img: &[]imagedefinition.Img{
   935  					{
   936  						ImgName: "test1.img",
   937  					},
   938  					{
   939  						ImgName: "test2.img",
   940  					},
   941  					{
   942  						ImgName: "test3.img",
   943  					},
   944  					{
   945  						ImgName: "test4.img",
   946  					},
   947  				},
   948  			},
   949  			expectedVolNames: map[string]string{},
   950  			shouldPass:       false,
   951  		},
   952  		{
   953  			name:       "mutli_volume_some_specified",
   954  			gadgetYAML: "gadget-multi.yaml",
   955  			artifacts: &imagedefinition.Artifact{
   956  				Img: &[]imagedefinition.Img{
   957  					{
   958  						ImgName:   "test1.img",
   959  						ImgVolume: "first",
   960  					},
   961  					{
   962  						ImgName:   "test2.img",
   963  						ImgVolume: "second",
   964  					},
   965  					{
   966  						ImgName: "test3.img",
   967  					},
   968  					{
   969  						ImgName: "test4.img",
   970  					},
   971  				},
   972  			},
   973  			expectedVolNames: map[string]string{},
   974  			shouldPass:       false,
   975  		},
   976  		{
   977  			name:       "mutli_volume_only_create_some_images",
   978  			gadgetYAML: "gadget-multi.yaml",
   979  			artifacts: &imagedefinition.Artifact{
   980  				Img: &[]imagedefinition.Img{
   981  					{
   982  						ImgName:   "test1.img",
   983  						ImgVolume: "first",
   984  					},
   985  					{
   986  						ImgName:   "test2.img",
   987  						ImgVolume: "second",
   988  					},
   989  				},
   990  			},
   991  			expectedVolNames: map[string]string{
   992  				"first":  "test1.img",
   993  				"second": "test2.img",
   994  			},
   995  			shouldPass: true,
   996  		},
   997  		{
   998  			name:       "qcow2_single_volume_no_img",
   999  			gadgetYAML: "gadget_tree/meta/gadget.yaml",
  1000  			artifacts: &imagedefinition.Artifact{
  1001  				Qcow2: &[]imagedefinition.Qcow2{
  1002  					{
  1003  						Qcow2Name:   "test1.qcow2",
  1004  						Qcow2Volume: "pc",
  1005  					},
  1006  				},
  1007  			},
  1008  			expectedVolNames: map[string]string{
  1009  				"pc": "test1.qcow2.img",
  1010  			},
  1011  			shouldPass: true,
  1012  		},
  1013  		{
  1014  			name:       "qcow2_single_volume_not_specified_no_img",
  1015  			gadgetYAML: "gadget_tree/meta/gadget.yaml",
  1016  			artifacts: &imagedefinition.Artifact{
  1017  				Qcow2: &[]imagedefinition.Qcow2{
  1018  					{
  1019  						Qcow2Name: "test1.qcow2",
  1020  					},
  1021  				},
  1022  			},
  1023  			expectedVolNames: map[string]string{
  1024  				"pc": "test1.qcow2.img",
  1025  			},
  1026  			shouldPass: true,
  1027  		},
  1028  		{
  1029  			name:       "qcow2_single_volume_yes_img",
  1030  			gadgetYAML: "gadget_tree/meta/gadget.yaml",
  1031  			artifacts: &imagedefinition.Artifact{
  1032  				Img: &[]imagedefinition.Img{
  1033  					{
  1034  						ImgName:   "test1.img",
  1035  						ImgVolume: "pc",
  1036  					},
  1037  				},
  1038  				Qcow2: &[]imagedefinition.Qcow2{
  1039  					{
  1040  						Qcow2Name:   "test1.img",
  1041  						Qcow2Volume: "pc",
  1042  					},
  1043  				},
  1044  			},
  1045  			expectedVolNames: map[string]string{
  1046  				"pc": "test1.img",
  1047  			},
  1048  			shouldPass: true,
  1049  		},
  1050  		{
  1051  			name:       "qcow2_mutli_volume_not_specified",
  1052  			gadgetYAML: "gadget-multi.yaml",
  1053  			artifacts: &imagedefinition.Artifact{
  1054  				Qcow2: &[]imagedefinition.Qcow2{
  1055  					{
  1056  						Qcow2Name: "test1.img",
  1057  					},
  1058  					{
  1059  						Qcow2Name: "test2.img",
  1060  					},
  1061  					{
  1062  						Qcow2Name: "test3.img",
  1063  					},
  1064  					{
  1065  						Qcow2Name: "test4.img",
  1066  					},
  1067  				},
  1068  			},
  1069  			expectedVolNames: map[string]string{},
  1070  			shouldPass:       false,
  1071  		},
  1072  		{
  1073  			name:       "qcow2_mutli_volume_no_img",
  1074  			gadgetYAML: "gadget-multi.yaml",
  1075  			artifacts: &imagedefinition.Artifact{
  1076  				Qcow2: &[]imagedefinition.Qcow2{
  1077  					{
  1078  						Qcow2Name:   "test1.qcow2",
  1079  						Qcow2Volume: "first",
  1080  					},
  1081  					{
  1082  						Qcow2Name:   "test2.qcow2",
  1083  						Qcow2Volume: "second",
  1084  					},
  1085  					{
  1086  						Qcow2Name:   "test3.qcow2",
  1087  						Qcow2Volume: "third",
  1088  					},
  1089  					{
  1090  						Qcow2Name:   "test4.qcow2",
  1091  						Qcow2Volume: "fourth",
  1092  					},
  1093  				},
  1094  			},
  1095  			expectedVolNames: map[string]string{
  1096  				"first":  "test1.qcow2.img",
  1097  				"second": "test2.qcow2.img",
  1098  				"third":  "test3.qcow2.img",
  1099  				"fourth": "test4.qcow2.img",
  1100  			},
  1101  			shouldPass: true,
  1102  		},
  1103  		{
  1104  			name:       "qcow2_mutli_volume_yes_img",
  1105  			gadgetYAML: "gadget-multi.yaml",
  1106  			artifacts: &imagedefinition.Artifact{
  1107  				Img: &[]imagedefinition.Img{
  1108  					{
  1109  						ImgName:   "test1.img",
  1110  						ImgVolume: "first",
  1111  					},
  1112  					{
  1113  						ImgName:   "test2.img",
  1114  						ImgVolume: "second",
  1115  					},
  1116  					{
  1117  						ImgName:   "test3.img",
  1118  						ImgVolume: "third",
  1119  					},
  1120  					{
  1121  						ImgName:   "test4.img",
  1122  						ImgVolume: "fourth",
  1123  					},
  1124  				},
  1125  				Qcow2: &[]imagedefinition.Qcow2{
  1126  					{
  1127  						Qcow2Name:   "test1.img",
  1128  						Qcow2Volume: "first",
  1129  					},
  1130  					{
  1131  						Qcow2Name:   "test2.img",
  1132  						Qcow2Volume: "second",
  1133  					},
  1134  					{
  1135  						Qcow2Name:   "test3.img",
  1136  						Qcow2Volume: "third",
  1137  					},
  1138  					{
  1139  						Qcow2Name:   "test4.img",
  1140  						Qcow2Volume: "fourth",
  1141  					},
  1142  				},
  1143  			},
  1144  			expectedVolNames: map[string]string{
  1145  				"first":  "test1.img",
  1146  				"second": "test2.img",
  1147  				"third":  "test3.img",
  1148  				"fourth": "test4.img",
  1149  			},
  1150  			shouldPass: true,
  1151  		},
  1152  		{
  1153  			name:       "qcow2_mutli_volume_img_for_different_volume",
  1154  			gadgetYAML: "gadget-multi.yaml",
  1155  			artifacts: &imagedefinition.Artifact{
  1156  				Img: &[]imagedefinition.Img{
  1157  					{
  1158  						ImgName:   "test1.img",
  1159  						ImgVolume: "first",
  1160  					},
  1161  					{
  1162  						ImgName:   "test2.img",
  1163  						ImgVolume: "second",
  1164  					},
  1165  				},
  1166  				Qcow2: &[]imagedefinition.Qcow2{
  1167  					{
  1168  						Qcow2Name:   "test3.qcow2",
  1169  						Qcow2Volume: "third",
  1170  					},
  1171  					{
  1172  						Qcow2Name:   "test4.qcow2",
  1173  						Qcow2Volume: "fourth",
  1174  					},
  1175  				},
  1176  			},
  1177  			expectedVolNames: map[string]string{
  1178  				"first":  "test1.img",
  1179  				"second": "test2.img",
  1180  				"third":  "test3.qcow2.img",
  1181  				"fourth": "test4.qcow2.img",
  1182  			},
  1183  			shouldPass: true,
  1184  		},
  1185  	}
  1186  	for _, tc := range testCases {
  1187  		t.Run(tc.name, func(t *testing.T) {
  1188  			asserter := helper.Asserter{T: t}
  1189  			restoreCWD := testhelper.SaveCWD()
  1190  			defer restoreCWD()
  1191  
  1192  			var stateMachine ClassicStateMachine
  1193  			stateMachine.commonFlags, stateMachine.stateMachineFlags = helper.InitCommonOpts()
  1194  			stateMachine.parent = &stateMachine
  1195  
  1196  			stateMachine.YamlFilePath = filepath.Join("testdata", tc.gadgetYAML)
  1197  			stateMachine.ImageDef = imagedefinition.ImageDefinition{
  1198  				Architecture: getHostArch(),
  1199  				Series:       getHostSuite(),
  1200  				Rootfs: &imagedefinition.Rootfs{
  1201  					Archive: "ubuntu",
  1202  				},
  1203  				Customization: &imagedefinition.Customization{},
  1204  				Artifacts:     tc.artifacts,
  1205  			}
  1206  
  1207  			err := stateMachine.makeTemporaryDirectories()
  1208  			asserter.AssertErrNil(err, true)
  1209  			t.Cleanup(func() { os.RemoveAll(stateMachine.stateMachineFlags.WorkDir) })
  1210  
  1211  			// load gadget yaml
  1212  			err = stateMachine.loadGadgetYaml()
  1213  			asserter.AssertErrNil(err, true)
  1214  
  1215  			// verify artifact names
  1216  			err = stateMachine.verifyArtifactNames()
  1217  			if tc.shouldPass {
  1218  				asserter.AssertErrNil(err, true)
  1219  				if !reflect.DeepEqual(tc.expectedVolNames, stateMachine.VolumeNames) {
  1220  					fmt.Println(tc.expectedVolNames)
  1221  					fmt.Println(stateMachine.VolumeNames)
  1222  					t.Errorf("Expected volume names does not match calculated volume names")
  1223  				}
  1224  			} else {
  1225  				asserter.AssertErrContains(err, "Volume names must be specified for each image")
  1226  			}
  1227  		})
  1228  	}
  1229  }
  1230  
  1231  // TestBuildRootfsFromTasks unit tests the buildRootfsFromTasks function
  1232  func TestBuildRootfsFromTasks(t *testing.T) {
  1233  	t.Parallel()
  1234  	asserter := helper.Asserter{T: t}
  1235  	restoreCWD := testhelper.SaveCWD()
  1236  	defer restoreCWD()
  1237  
  1238  	var stateMachine ClassicStateMachine
  1239  	stateMachine.commonFlags, stateMachine.stateMachineFlags = helper.InitCommonOpts()
  1240  
  1241  	err := stateMachine.buildRootfsFromTasks()
  1242  	asserter.AssertErrNil(err, true)
  1243  
  1244  	os.RemoveAll(stateMachine.stateMachineFlags.WorkDir)
  1245  }
  1246  
  1247  // TestExtractRootfsTar unit tests the extractRootfsTar function
  1248  func TestExtractRootfsTar(t *testing.T) {
  1249  	t.Parallel()
  1250  	wd, _ := os.Getwd() // nolint: errcheck
  1251  	testCases := []struct {
  1252  		name          string
  1253  		rootfsTar     string
  1254  		SHA256sum     string
  1255  		expectedFiles []string
  1256  	}{
  1257  		{
  1258  			name:      "vanilla_tar",
  1259  			rootfsTar: filepath.Join("testdata", "rootfs_tarballs", "rootfs.tar"),
  1260  			SHA256sum: "ec01fd8488b0f35d2ca69e6f82edfaecef5725da70913bab61240419ce574918",
  1261  			expectedFiles: []string{
  1262  				"test_tar1",
  1263  				"test_tar2",
  1264  			},
  1265  		},
  1266  		{
  1267  			name:      "vanilla_tar respecting absolute path",
  1268  			rootfsTar: filepath.Join(wd, "testdata", "rootfs_tarballs", "rootfs.tar"),
  1269  			SHA256sum: "ec01fd8488b0f35d2ca69e6f82edfaecef5725da70913bab61240419ce574918",
  1270  			expectedFiles: []string{
  1271  				"test_tar1",
  1272  				"test_tar2",
  1273  			},
  1274  		},
  1275  		{
  1276  			name:      "vanilla_tar relative path even with dot dot",
  1277  			rootfsTar: filepath.Join("testdata", "../..", filepath.Base(wd), "testdata", "rootfs_tarballs", "rootfs.tar"),
  1278  			SHA256sum: "ec01fd8488b0f35d2ca69e6f82edfaecef5725da70913bab61240419ce574918",
  1279  			expectedFiles: []string{
  1280  				"test_tar1",
  1281  				"test_tar2",
  1282  			},
  1283  		},
  1284  		{
  1285  			name:      "gz",
  1286  			rootfsTar: filepath.Join("testdata", "rootfs_tarballs", "rootfs.tar.gz"),
  1287  			SHA256sum: "29152fd9cadbc92f174815ec642ab3aea98f08f902a4f317ec037f8fe60e40c3",
  1288  			expectedFiles: []string{
  1289  				"test_tar_gz1",
  1290  				"test_tar_gz2",
  1291  			},
  1292  		},
  1293  		{
  1294  			name:      "xz",
  1295  			rootfsTar: filepath.Join("testdata", "rootfs_tarballs", "rootfs.tar.xz"),
  1296  			SHA256sum: "e3708f1d98ccea0e0c36843d9576580505ee36d523bfcf78b0f73a035ae9a14e",
  1297  			expectedFiles: []string{
  1298  				"test_tar_xz1",
  1299  				"test_tar_xz2",
  1300  			},
  1301  		},
  1302  		{
  1303  			name:      "bz2",
  1304  			rootfsTar: filepath.Join("testdata", "rootfs_tarballs", "rootfs.tar.bz2"),
  1305  			SHA256sum: "a1180a73b652d85d7330ef21d433b095363664f2f808363e67f798fae15abf0c",
  1306  			expectedFiles: []string{
  1307  				"test_tar_bz1",
  1308  				"test_tar_bz2",
  1309  			},
  1310  		},
  1311  		{
  1312  			name:      "zst",
  1313  			rootfsTar: filepath.Join("testdata", "rootfs_tarballs", "rootfs.tar.zst"),
  1314  			SHA256sum: "5fb00513f84e28225a3155fd78c59a6a923b222e1c125aab35bbfd4091281829",
  1315  			expectedFiles: []string{
  1316  				"test_tar_zstd1",
  1317  				"test_tar_zstd2",
  1318  			},
  1319  		},
  1320  	}
  1321  	for _, tc := range testCases {
  1322  		t.Run(tc.name, func(t *testing.T) {
  1323  			asserter := helper.Asserter{T: t}
  1324  			restoreCWD := testhelper.SaveCWD()
  1325  			defer restoreCWD()
  1326  
  1327  			var stateMachine ClassicStateMachine
  1328  			stateMachine.commonFlags, stateMachine.stateMachineFlags = helper.InitCommonOpts()
  1329  			stateMachine.parent = &stateMachine
  1330  			stateMachine.ImageDef = imagedefinition.ImageDefinition{
  1331  				Architecture: getHostArch(),
  1332  				Series:       getHostSuite(),
  1333  				Rootfs: &imagedefinition.Rootfs{
  1334  					Tarball: &imagedefinition.Tarball{
  1335  						TarballURL: fmt.Sprintf("file://%s", tc.rootfsTar),
  1336  					},
  1337  				},
  1338  			}
  1339  
  1340  			err := stateMachine.setConfDefDir(filepath.Join(wd, "image_definition.yaml"))
  1341  			asserter.AssertErrNil(err, true)
  1342  
  1343  			err = stateMachine.makeTemporaryDirectories()
  1344  			asserter.AssertErrNil(err, true)
  1345  
  1346  			t.Cleanup(func() { os.RemoveAll(stateMachine.stateMachineFlags.WorkDir) })
  1347  
  1348  			err = stateMachine.extractRootfsTar()
  1349  			asserter.AssertErrNil(err, true)
  1350  
  1351  			for _, testFile := range tc.expectedFiles {
  1352  				_, err := os.Stat(filepath.Join(stateMachine.tempDirs.chroot, testFile))
  1353  				if err != nil {
  1354  					t.Errorf("File %s should be in chroot, but is missing", testFile)
  1355  				}
  1356  			}
  1357  		})
  1358  	}
  1359  }
  1360  
  1361  // TestFailedExtractRootfsTar tests failures in the extractRootfsTar function
  1362  func TestFailedExtractRootfsTar(t *testing.T) {
  1363  	asserter := helper.Asserter{T: t}
  1364  	restoreCWD := testhelper.SaveCWD()
  1365  	defer restoreCWD()
  1366  
  1367  	var stateMachine ClassicStateMachine
  1368  	stateMachine.commonFlags, stateMachine.stateMachineFlags = helper.InitCommonOpts()
  1369  	stateMachine.parent = &stateMachine
  1370  	tarPath := filepath.Join("testdata", "rootfs_tarballs", "rootfs.tar")
  1371  	stateMachine.ImageDef = imagedefinition.ImageDefinition{
  1372  		Architecture: getHostArch(),
  1373  		Series:       getHostSuite(),
  1374  		Rootfs: &imagedefinition.Rootfs{
  1375  			Tarball: &imagedefinition.Tarball{
  1376  				TarballURL: fmt.Sprintf("file://%s", tarPath),
  1377  				SHA256sum:  "fail",
  1378  			},
  1379  		},
  1380  	}
  1381  
  1382  	err := stateMachine.makeTemporaryDirectories()
  1383  	asserter.AssertErrNil(err, true)
  1384  
  1385  	// mock os.Mkdir
  1386  	osMkdir = mockMkdir
  1387  	t.Cleanup(func() {
  1388  		osMkdir = os.Mkdir
  1389  	})
  1390  	err = stateMachine.extractRootfsTar()
  1391  	asserter.AssertErrContains(err, "Failed to create chroot directory")
  1392  	osMkdir = os.Mkdir
  1393  
  1394  	// clean up chroot directory
  1395  	os.RemoveAll(stateMachine.tempDirs.chroot)
  1396  
  1397  	// now test with the incorrect SHA256sum
  1398  	err = stateMachine.extractRootfsTar()
  1399  	asserter.AssertErrContains(err, "Calculated SHA256 sum of rootfs tarball")
  1400  
  1401  	// clean up chroot directory
  1402  	os.RemoveAll(stateMachine.tempDirs.chroot)
  1403  
  1404  	// use a tarball that doesn't exist to trigger a failure in computing
  1405  	// the SHA256 sum
  1406  	stateMachine.ImageDef.Rootfs.Tarball.TarballURL = "file:///fakefile"
  1407  	err = stateMachine.extractRootfsTar()
  1408  	asserter.AssertErrContains(err, "Error opening file \"/fakefile\" to calculate SHA256 sum")
  1409  	os.RemoveAll(stateMachine.stateMachineFlags.WorkDir)
  1410  }
  1411  
  1412  // TestStateMachine_customizeCloudInit unit tests the customizeCloudInit method
  1413  func TestStateMachine_customizeCloudInit(t *testing.T) {
  1414  	testCases := []struct {
  1415  		name                   string
  1416  		cloudInitCustomization imagedefinition.CloudInit
  1417  		wantMetaData           string
  1418  		wantUserData           string
  1419  		wantNetworkConfig      string
  1420  	}{
  1421  		{
  1422  			name: "full cloudinit conf",
  1423  			cloudInitCustomization: imagedefinition.CloudInit{
  1424  				MetaData: `#cloud-config
  1425  
  1426  foo: bar`,
  1427  				UserData: `#cloud-config
  1428  
  1429  foo: baz`,
  1430  				NetworkConfig: `#cloud-config
  1431  
  1432  foobar: foobar`,
  1433  			},
  1434  			wantMetaData: `#cloud-config
  1435  
  1436  foo: bar`,
  1437  			wantUserData: `#cloud-config
  1438  
  1439  foo: baz`,
  1440  			wantNetworkConfig: `#cloud-config
  1441  
  1442  foobar: foobar`,
  1443  		},
  1444  		{
  1445  			name: "empty user data",
  1446  			cloudInitCustomization: imagedefinition.CloudInit{
  1447  				MetaData: `#cloud-config
  1448  
  1449  foo: bar`,
  1450  				UserData: "",
  1451  				NetworkConfig: `#cloud-config
  1452  
  1453  foobar: foobar`,
  1454  			},
  1455  			wantMetaData: `#cloud-config
  1456  
  1457  foo: bar`,
  1458  			wantUserData: "",
  1459  			wantNetworkConfig: `#cloud-config
  1460  
  1461  foobar: foobar`,
  1462  		},
  1463  		{
  1464  			name: "empty metadata",
  1465  			cloudInitCustomization: imagedefinition.CloudInit{
  1466  				UserData: "",
  1467  				NetworkConfig: `#cloud-config
  1468  
  1469  foobar: foobar`,
  1470  			},
  1471  			wantMetaData: "",
  1472  			wantUserData: "",
  1473  			wantNetworkConfig: `#cloud-config
  1474  
  1475  foobar: foobar`,
  1476  		},
  1477  		{
  1478  			name: "multiline user data",
  1479  			cloudInitCustomization: imagedefinition.CloudInit{
  1480  				UserData: `#cloud-config
  1481  
  1482  chpasswd:
  1483  	expire: true
  1484  	users:
  1485  		- name: ubuntu
  1486  		password: ubuntu
  1487  		type: text
  1488  `,
  1489  			},
  1490  			wantMetaData: "",
  1491  			wantUserData: `#cloud-config
  1492  
  1493  chpasswd:
  1494  	expire: true
  1495  	users:
  1496  		- name: ubuntu
  1497  		password: ubuntu
  1498  		type: text
  1499  `,
  1500  			wantNetworkConfig: "",
  1501  		},
  1502  	}
  1503  
  1504  	for i, tc := range testCases {
  1505  		t.Run("test_customize_cloud_init_"+tc.name, func(t *testing.T) {
  1506  			// Test setup
  1507  			asserter := helper.Asserter{T: t}
  1508  			restoreCWD := testhelper.SaveCWD()
  1509  			defer restoreCWD()
  1510  
  1511  			var stateMachine ClassicStateMachine
  1512  			stateMachine.commonFlags, stateMachine.stateMachineFlags = helper.InitCommonOpts()
  1513  			stateMachine.parent = &stateMachine
  1514  			tmpDir, err := os.MkdirTemp("", "")
  1515  			asserter.AssertErrNil(err, true)
  1516  			t.Cleanup(func() {
  1517  				if tmpErr := osRemoveAll(tmpDir); tmpErr != nil {
  1518  					if err != nil {
  1519  						err = fmt.Errorf("%s after previous error: %w", tmpErr, err)
  1520  					} else {
  1521  						err = tmpErr
  1522  					}
  1523  				}
  1524  			})
  1525  			stateMachine.tempDirs.chroot = tmpDir
  1526  
  1527  			// this directory is expected to be present as it is installed by cloud-init
  1528  			err = os.MkdirAll(path.Join(tmpDir, "etc/cloud/cloud.cfg.d"), 0777)
  1529  			asserter.AssertErrNil(err, true)
  1530  
  1531  			stateMachine.ImageDef.Customization = &imagedefinition.Customization{
  1532  				CloudInit: &testCases[i].cloudInitCustomization,
  1533  			}
  1534  
  1535  			// Running function to test
  1536  			err = stateMachine.customizeCloudInit()
  1537  			asserter.AssertErrNil(err, true)
  1538  
  1539  			// Validation
  1540  			seedPath := path.Join(tmpDir, "var/lib/cloud/seed/nocloud")
  1541  
  1542  			metaDataFile, err := os.Open(path.Join(seedPath, "meta-data"))
  1543  			if tc.cloudInitCustomization.MetaData != "" {
  1544  				asserter.AssertErrNil(err, false)
  1545  
  1546  				metaDataFileContent, err := io.ReadAll(metaDataFile)
  1547  				asserter.AssertErrNil(err, false)
  1548  
  1549  				if string(metaDataFileContent) != tc.wantMetaData {
  1550  					t.Errorf("un-expected meta-data content found: expected:\n%v\ngot:%v", tc.wantMetaData, string(metaDataFileContent))
  1551  				}
  1552  			} else {
  1553  				asserter.AssertErrContains(err, "no such file or directory")
  1554  			}
  1555  
  1556  			networkConfigFile, err := os.Open(path.Join(seedPath, "network-config"))
  1557  			if tc.cloudInitCustomization.NetworkConfig != "" {
  1558  				asserter.AssertErrNil(err, false)
  1559  
  1560  				networkConfigFileContent, err := io.ReadAll(networkConfigFile)
  1561  				asserter.AssertErrNil(err, false)
  1562  				if string(networkConfigFileContent) != tc.wantNetworkConfig {
  1563  					t.Errorf("un-expected network-config found: expected:\n%v\ngot:%v", tc.wantNetworkConfig, string(networkConfigFileContent))
  1564  				}
  1565  			} else {
  1566  				asserter.AssertErrContains(err, "no such file or directory")
  1567  			}
  1568  
  1569  			userDataFile, err := os.Open(path.Join(seedPath, "user-data"))
  1570  			if tc.cloudInitCustomization.UserData != "" {
  1571  				asserter.AssertErrNil(err, false)
  1572  
  1573  				userDataFileContent, err := io.ReadAll(userDataFile)
  1574  				asserter.AssertErrNil(err, false)
  1575  
  1576  				if string(userDataFileContent) != tc.wantUserData {
  1577  					t.Errorf("un-expected user-data content found: expected:\n%v\ngot:%v", tc.wantUserData, string(userDataFileContent))
  1578  				}
  1579  			} else {
  1580  				asserter.AssertErrContains(err, "no such file or directory")
  1581  			}
  1582  
  1583  			os.RemoveAll(stateMachine.stateMachineFlags.WorkDir)
  1584  		})
  1585  	}
  1586  }
  1587  
  1588  // TestStatemachine_customizeCloudInit_failed tests failure modes of customizeCloudInit method
  1589  func TestStatemachine_customizeCloudInit_failed(t *testing.T) {
  1590  	asserter := helper.Asserter{T: t}
  1591  	restoreCWD := testhelper.SaveCWD()
  1592  	defer restoreCWD()
  1593  
  1594  	var stateMachine ClassicStateMachine
  1595  	stateMachine.commonFlags, stateMachine.stateMachineFlags = helper.InitCommonOpts()
  1596  	stateMachine.parent = &stateMachine
  1597  	tmpDir, err := os.MkdirTemp("", "")
  1598  	asserter.AssertErrNil(err, true)
  1599  	t.Cleanup(func() { os.RemoveAll(tmpDir) })
  1600  	stateMachine.tempDirs.chroot = tmpDir
  1601  
  1602  	stateMachine.ImageDef.Customization = &imagedefinition.Customization{
  1603  		CloudInit: &imagedefinition.CloudInit{
  1604  			MetaData:      `foo: bar`,
  1605  			NetworkConfig: `foobar: foobar`,
  1606  			UserData: `#cloud-config
  1607  
  1608  chpasswd:
  1609    expire: true
  1610    users:
  1611      - name: ubuntu
  1612        password: ubuntu
  1613        type: text
  1614  `,
  1615  		},
  1616  	}
  1617  
  1618  	// Test if osCreate fails
  1619  	fileList := []string{"meta-data", "user-data", "network-config", "90_dpkg.cfg"}
  1620  	for _, file := range fileList {
  1621  		t.Run("test_failed_customize_cloud_init_"+file, func(t *testing.T) {
  1622  			// this directory is expected to be present as it is installed by cloud-init
  1623  			cloudInitConfigDirPath := path.Join(tmpDir, "etc/cloud/cloud.cfg.d")
  1624  			err = os.MkdirAll(cloudInitConfigDirPath, 0777)
  1625  			asserter.AssertErrNil(err, true)
  1626  			t.Cleanup(func() {
  1627  				os.RemoveAll(cloudInitConfigDirPath)
  1628  			})
  1629  
  1630  			osCreate = func(name string) (*os.File, error) {
  1631  				if strings.Contains(name, file) {
  1632  					return nil, errors.New("test error: failed to create file")
  1633  				}
  1634  				return os.Create(name)
  1635  			}
  1636  
  1637  			err := stateMachine.customizeCloudInit()
  1638  			asserter.AssertErrContains(err, "test error: failed to create file")
  1639  		})
  1640  	}
  1641  
  1642  	// Test if Write fails (file is read only)
  1643  	for _, file := range fileList {
  1644  		t.Run("test_failed_customize_cloud_init_"+file, func(t *testing.T) {
  1645  			// this directory is expected to be present as it is installed by cloud-init
  1646  			cloudInitConfigDirPath := path.Join(tmpDir, "etc/cloud/cloud.cfg.d")
  1647  			err = os.MkdirAll(cloudInitConfigDirPath, 0777)
  1648  			asserter.AssertErrNil(err, true)
  1649  			t.Cleanup(func() {
  1650  				os.RemoveAll(cloudInitConfigDirPath)
  1651  			})
  1652  
  1653  			osCreate = func(name string) (*os.File, error) {
  1654  				if strings.Contains(name, file) {
  1655  					fileReadWrite, err := os.Create(name)
  1656  					asserter.AssertErrNil(err, true)
  1657  					fileReadWrite.Close()
  1658  					return os.Open(name)
  1659  				}
  1660  				return os.Create(name)
  1661  			}
  1662  
  1663  			err := stateMachine.customizeCloudInit()
  1664  			if err == nil {
  1665  				t.Errorf("expected error but got nil")
  1666  			}
  1667  		})
  1668  	}
  1669  
  1670  	// Test if os.MkdirAll fails
  1671  	t.Run("test_failed_customize_cloud_init_mkdir", func(t *testing.T) {
  1672  		// this directory is expected to be present as it is installed by cloud-init
  1673  		cloudInitConfigDirPath := path.Join(tmpDir, "etc/cloud/cloud.cfg.d")
  1674  		err = os.MkdirAll(cloudInitConfigDirPath, 0777)
  1675  		asserter.AssertErrNil(err, true)
  1676  		t.Cleanup(func() {
  1677  			os.RemoveAll(cloudInitConfigDirPath)
  1678  		})
  1679  
  1680  		osMkdirAll = mockMkdirAll
  1681  		t.Cleanup(func() {
  1682  			osMkdirAll = os.MkdirAll
  1683  		})
  1684  
  1685  		err := stateMachine.customizeCloudInit()
  1686  		if err == nil {
  1687  			t.Error()
  1688  		}
  1689  	})
  1690  
  1691  	// Test if yaml.Marshal fails
  1692  	t.Run("test_failed_customize_cloud_init_yaml_marshal", func(t *testing.T) {
  1693  		// this directory is expected to be present as it is installed by cloud-init
  1694  		cloudInitConfigDirPath := path.Join(tmpDir, "etc/cloud/cloud.cfg.d")
  1695  		err = os.MkdirAll(cloudInitConfigDirPath, 0777)
  1696  		asserter.AssertErrNil(err, true)
  1697  		t.Cleanup(func() {
  1698  			os.RemoveAll(cloudInitConfigDirPath)
  1699  		})
  1700  
  1701  		yamlMarshal = mockMarshal
  1702  		defer func() {
  1703  			yamlMarshal = yaml.Marshal
  1704  		}()
  1705  
  1706  		err := stateMachine.customizeCloudInit()
  1707  		if err == nil {
  1708  			t.Error()
  1709  		}
  1710  	})
  1711  
  1712  	// Test cloud-init customization is invalid
  1713  	testCases := []struct {
  1714  		name                   string
  1715  		cloudInitCustomization imagedefinition.CloudInit
  1716  	}{
  1717  		{
  1718  			name: "invalid userdata",
  1719  			cloudInitCustomization: imagedefinition.CloudInit{
  1720  				UserData: "foo: bar",
  1721  			},
  1722  		},
  1723  	}
  1724  
  1725  	for i, tc := range testCases {
  1726  		t.Run("test_failed_customize_cloud_init_invalid_config_"+tc.name, func(t *testing.T) {
  1727  			// this directory is expected to be present as it is installed by cloud-init
  1728  			cloudInitConfigDirPath := path.Join(tmpDir, "etc/cloud/cloud.cfg.d")
  1729  			err = os.MkdirAll(cloudInitConfigDirPath, 0777)
  1730  			asserter.AssertErrNil(err, true)
  1731  			t.Cleanup(func() {
  1732  				os.RemoveAll(cloudInitConfigDirPath)
  1733  			})
  1734  
  1735  			stateMachine.ImageDef.Customization.CloudInit = &testCases[i].cloudInitCustomization
  1736  
  1737  			err := stateMachine.customizeCloudInit()
  1738  			asserter.AssertErrContains(err, "is missing proper header")
  1739  		})
  1740  	}
  1741  }
  1742  
  1743  // TestStateMachine_manualCustomization unit tests the manualCustomization function
  1744  func TestStateMachine_manualCustomization(t *testing.T) {
  1745  	t.Parallel()
  1746  	if testing.Short() {
  1747  		t.Skip("skipping test in short mode.")
  1748  	}
  1749  
  1750  	asserter := helper.Asserter{T: t}
  1751  	restoreCWD := testhelper.SaveCWD()
  1752  	defer restoreCWD()
  1753  
  1754  	var stateMachine ClassicStateMachine
  1755  	stateMachine.commonFlags, stateMachine.stateMachineFlags = helper.InitCommonOpts()
  1756  	stateMachine.parent = &stateMachine
  1757  
  1758  	stateMachine.ImageDef = imagedefinition.ImageDefinition{
  1759  		Architecture: getHostArch(),
  1760  		Series:       getHostSuite(),
  1761  		Rootfs: &imagedefinition.Rootfs{
  1762  			Archive: "ubuntu",
  1763  		},
  1764  		Customization: &imagedefinition.Customization{
  1765  			Manual: &imagedefinition.Manual{
  1766  				MakeDirs: []*imagedefinition.MakeDirs{
  1767  					{
  1768  						Path:        "/etc/foo/bar",
  1769  						Permissions: 0755,
  1770  					},
  1771  					{
  1772  						Path:        "/etc/baz/test",
  1773  						Permissions: 0644,
  1774  					},
  1775  				},
  1776  				CopyFile: []*imagedefinition.CopyFile{
  1777  					{
  1778  						Source: filepath.Join("testdata", "test_script"),
  1779  						Dest:   "/test_copy_file",
  1780  					},
  1781  				},
  1782  				TouchFile: []*imagedefinition.TouchFile{
  1783  					{
  1784  						TouchPath: "/test_touch_file",
  1785  					},
  1786  				},
  1787  				Execute: []*imagedefinition.Execute{
  1788  					{
  1789  						// the file we already copied creates a file /test_execute
  1790  						ExecutePath: "/test_copy_file",
  1791  					},
  1792  				},
  1793  				AddUser: []*imagedefinition.AddUser{
  1794  					{
  1795  						UserName: "testuser",
  1796  						UserID:   "123456",
  1797  					},
  1798  				},
  1799  				AddGroup: []*imagedefinition.AddGroup{
  1800  					{
  1801  						GroupName: "testgroup",
  1802  						GroupID:   "456789",
  1803  					},
  1804  				},
  1805  			},
  1806  		},
  1807  	}
  1808  
  1809  	d, err := os.Getwd()
  1810  	asserter.AssertErrNil(err, true)
  1811  	err = stateMachine.setConfDefDir(filepath.Join(d, "image_definition.yaml"))
  1812  	asserter.AssertErrNil(err, true)
  1813  
  1814  	err = stateMachine.makeTemporaryDirectories()
  1815  	asserter.AssertErrNil(err, true)
  1816  
  1817  	t.Cleanup(func() { os.RemoveAll(stateMachine.stateMachineFlags.WorkDir) })
  1818  
  1819  	err = getBasicChroot(stateMachine.StateMachine)
  1820  	asserter.AssertErrNil(err, true)
  1821  
  1822  	err = stateMachine.manualCustomization()
  1823  	asserter.AssertErrNil(err, true)
  1824  
  1825  	// Check that the correct directories exist
  1826  	testDirectories := []string{"/etc/foo/bar", "/etc/baz/test"}
  1827  	for _, dirName := range testDirectories {
  1828  		_, err := os.Stat(filepath.Join(stateMachine.tempDirs.chroot, dirName))
  1829  		if err != nil {
  1830  			t.Errorf("directory %s should exist, but it does not", dirName)
  1831  		}
  1832  	}
  1833  
  1834  	// Check that the correct files exist
  1835  	testFiles := []string{"test_copy_file", "test_touch_file", "test_execute"}
  1836  	for _, fileName := range testFiles {
  1837  		_, err := os.Stat(filepath.Join(stateMachine.tempDirs.chroot, fileName))
  1838  		if err != nil {
  1839  			t.Errorf("file %s should exist, but it does not", fileName)
  1840  		}
  1841  	}
  1842  
  1843  	// Check that the test user exists with the correct uid
  1844  	passwdFile := filepath.Join(stateMachine.tempDirs.chroot, "etc", "passwd")
  1845  	passwdContents, err := os.ReadFile(passwdFile)
  1846  	asserter.AssertErrNil(err, true)
  1847  	if !strings.Contains(string(passwdContents), "testuser:x:123456") {
  1848  		t.Errorf("Test user was not created in the chroot")
  1849  	}
  1850  
  1851  	// Check that the test group exists with the correct gid
  1852  	groupFile := filepath.Join(stateMachine.tempDirs.chroot, "etc", "group")
  1853  	groupContents, err := os.ReadFile(groupFile)
  1854  	asserter.AssertErrNil(err, true)
  1855  	if !strings.Contains(string(groupContents), "testgroup:x:456789") {
  1856  		t.Errorf("Test group was not created in the chroot")
  1857  	}
  1858  }
  1859  
  1860  // TestStateMachine_manualCustomization_fail tests failures in the manualCustomization function
  1861  func TestStateMachine_manualCustomization_fail(t *testing.T) {
  1862  	if testing.Short() {
  1863  		t.Skip("skipping test in short mode.")
  1864  	}
  1865  
  1866  	t.Run("test_failed_manual_customization", func(t *testing.T) {
  1867  		asserter := helper.Asserter{T: t}
  1868  		restoreCWD := testhelper.SaveCWD()
  1869  		t.Cleanup(restoreCWD)
  1870  
  1871  		var stateMachine ClassicStateMachine
  1872  		stateMachine.commonFlags, stateMachine.stateMachineFlags = helper.InitCommonOpts()
  1873  		stateMachine.parent = &stateMachine
  1874  
  1875  		err := stateMachine.makeTemporaryDirectories()
  1876  		asserter.AssertErrNil(err, true)
  1877  
  1878  		// mock helper.BackupAndCopyResolvConf
  1879  		helperBackupAndCopyResolvConf = mockBackupAndCopyResolvConfFail
  1880  		t.Cleanup(func() {
  1881  			helperBackupAndCopyResolvConf = helper.BackupAndCopyResolvConf
  1882  		})
  1883  		err = stateMachine.manualCustomization()
  1884  		asserter.AssertErrContains(err, "Error setting up /etc/resolv.conf")
  1885  	})
  1886  
  1887  	tests := []struct {
  1888  		name                 string
  1889  		expectedErr          string
  1890  		manualCustomizations *imagedefinition.Manual
  1891  	}{
  1892  		{
  1893  			name:        "failing manualMakeDirs",
  1894  			expectedErr: "not a directory",
  1895  			manualCustomizations: &imagedefinition.Manual{
  1896  				MakeDirs: []*imagedefinition.MakeDirs{
  1897  					{
  1898  						Path:        filepath.Join("/etc", "resolv.conf"),
  1899  						Permissions: 0755,
  1900  					},
  1901  				},
  1902  			},
  1903  		},
  1904  		{
  1905  			name:        "failing manualCopyFile",
  1906  			expectedErr: "cp: cannot stat 'this/path/does/not/exist'",
  1907  			manualCustomizations: &imagedefinition.Manual{
  1908  				CopyFile: []*imagedefinition.CopyFile{
  1909  					{
  1910  						Source: filepath.Join("this", "path", "does", "not", "exist"),
  1911  						Dest:   filepath.Join("this", "path", "does", "not", "exist"),
  1912  					},
  1913  				},
  1914  			},
  1915  		},
  1916  		{
  1917  			name:        "failing manualExecute",
  1918  			expectedErr: "chroot: failed to run command",
  1919  			manualCustomizations: &imagedefinition.Manual{
  1920  				Execute: []*imagedefinition.Execute{
  1921  					{
  1922  						ExecutePath: filepath.Join("this", "path", "does", "not", "exist"),
  1923  					},
  1924  				},
  1925  			},
  1926  		},
  1927  		{
  1928  			name:        "failing manualTouchFile",
  1929  			expectedErr: "no such file or directory",
  1930  			manualCustomizations: &imagedefinition.Manual{
  1931  				TouchFile: []*imagedefinition.TouchFile{
  1932  					{
  1933  						TouchPath: filepath.Join("this", "path", "does", "not", "exist"),
  1934  					},
  1935  				},
  1936  			},
  1937  		},
  1938  		{
  1939  			name:        "failing manualAddGroup",
  1940  			expectedErr: "group 'root' already exists",
  1941  			manualCustomizations: &imagedefinition.Manual{
  1942  				AddGroup: []*imagedefinition.AddGroup{
  1943  					{
  1944  						GroupName: "root",
  1945  						GroupID:   "0",
  1946  					},
  1947  				},
  1948  			},
  1949  		},
  1950  		{
  1951  			name:        "failing manualAddUser",
  1952  			expectedErr: "user 'root' already exists",
  1953  			manualCustomizations: &imagedefinition.Manual{
  1954  				AddUser: []*imagedefinition.AddUser{
  1955  					{
  1956  						UserName: "root",
  1957  						UserID:   "0",
  1958  					},
  1959  				},
  1960  			},
  1961  		},
  1962  	}
  1963  	asserter := helper.Asserter{T: t}
  1964  
  1965  	var stateMachine ClassicStateMachine
  1966  	stateMachine.commonFlags, stateMachine.stateMachineFlags = helper.InitCommonOpts()
  1967  	stateMachine.parent = &stateMachine
  1968  	stateMachine.ImageDef = imagedefinition.ImageDefinition{
  1969  		Architecture: getHostArch(),
  1970  		Series:       getHostSuite(),
  1971  		Rootfs: &imagedefinition.Rootfs{
  1972  			Archive: "ubuntu",
  1973  		},
  1974  	}
  1975  
  1976  	err := stateMachine.makeTemporaryDirectories()
  1977  	asserter.AssertErrNil(err, true)
  1978  
  1979  	t.Cleanup(func() { os.RemoveAll(stateMachine.stateMachineFlags.WorkDir) })
  1980  
  1981  	err = getBasicChroot(stateMachine.StateMachine)
  1982  	asserter.AssertErrNil(err, true)
  1983  
  1984  	// create an /etc/resolv.conf in the chroot
  1985  	err = os.MkdirAll(filepath.Join(stateMachine.tempDirs.chroot, "etc"), 0755)
  1986  	asserter.AssertErrNil(err, true)
  1987  	_, err = os.Create(filepath.Join(stateMachine.tempDirs.chroot, "etc", "resolv.conf"))
  1988  	asserter.AssertErrNil(err, true)
  1989  
  1990  	for _, tc := range tests {
  1991  		t.Run(tc.name, func(t *testing.T) {
  1992  			asserter := helper.Asserter{T: t}
  1993  			restoreCWD := testhelper.SaveCWD()
  1994  			t.Cleanup(restoreCWD)
  1995  
  1996  			stateMachine.ImageDef.Customization = &imagedefinition.Customization{
  1997  				Manual: tc.manualCustomizations,
  1998  			}
  1999  
  2000  			err = stateMachine.manualCustomization()
  2001  
  2002  			if len(tc.expectedErr) == 0 {
  2003  				asserter.AssertErrNil(err, true)
  2004  			} else {
  2005  				asserter.AssertErrContains(err, tc.expectedErr)
  2006  			}
  2007  		})
  2008  	}
  2009  }
  2010  
  2011  // TestPrepareClassicImage unit tests the prepareClassicImage function
  2012  func TestPrepareClassicImage(t *testing.T) {
  2013  	t.Parallel()
  2014  	if testing.Short() {
  2015  		t.Skip("skipping test in short mode.")
  2016  	}
  2017  
  2018  	asserter := helper.Asserter{T: t}
  2019  	restoreCWD := testhelper.SaveCWD()
  2020  	defer restoreCWD()
  2021  
  2022  	var stateMachine ClassicStateMachine
  2023  	stateMachine.commonFlags, stateMachine.stateMachineFlags = helper.InitCommonOpts()
  2024  	stateMachine.parent = &stateMachine
  2025  	stateMachine.Snaps = []string{"core20"}
  2026  	stateMachine.commonFlags.Channel = "stable"
  2027  	stateMachine.ImageDef = imagedefinition.ImageDefinition{
  2028  		Architecture: getHostArch(),
  2029  		Customization: &imagedefinition.Customization{
  2030  			ExtraSnaps: []*imagedefinition.Snap{
  2031  				{
  2032  					SnapName: "hello",
  2033  					Channel:  "candidate",
  2034  				},
  2035  				{
  2036  					SnapName: "lxd",
  2037  					Channel:  "latest/stable",
  2038  				},
  2039  				{
  2040  					SnapName: "core22",
  2041  				},
  2042  			},
  2043  		},
  2044  	}
  2045  
  2046  	err := stateMachine.makeTemporaryDirectories()
  2047  	asserter.AssertErrNil(err, true)
  2048  
  2049  	t.Cleanup(func() { os.RemoveAll(stateMachine.stateMachineFlags.WorkDir) })
  2050  
  2051  	err = stateMachine.prepareClassicImage()
  2052  	asserter.AssertErrNil(err, true)
  2053  
  2054  	// check that the lxd and hello snaps, as well as lxd's base, core20
  2055  	// were prepared in the correct location
  2056  	snaps := map[string]string{"lxd": "stable", "hello": "candidate", "core20": "stable", "core22": "stable"}
  2057  	for snapName, snapChannel := range snaps {
  2058  		// reach out to the snap store to find the revision
  2059  		// of the snap for the specified channel
  2060  		snapStore := store.New(nil, nil)
  2061  		snapSpec := store.SnapSpec{Name: snapName}
  2062  		context := context.TODO()
  2063  		snapInfo, err := snapStore.SnapInfo(context, snapSpec, nil)
  2064  		asserter.AssertErrNil(err, true)
  2065  
  2066  		storeRevision := snapInfo.Channels["latest/"+snapChannel].Revision.N
  2067  		snapFileName := fmt.Sprintf("%s_%d.snap", snapName, storeRevision)
  2068  
  2069  		snapPath := filepath.Join(stateMachine.tempDirs.chroot,
  2070  			"var", "lib", "snapd", "seed", "snaps", snapFileName)
  2071  		_, err = os.Stat(snapPath)
  2072  		if err != nil {
  2073  			if os.IsNotExist(err) {
  2074  				t.Errorf("File %s should exist, but does not", snapPath)
  2075  			}
  2076  		}
  2077  	}
  2078  }
  2079  
  2080  // TestClassicSnapRevisions tests that if revisions are specified in the image definition
  2081  // that the corresponding revisions are staged in the chroot
  2082  func TestClassicSnapRevisions(t *testing.T) {
  2083  	t.Parallel()
  2084  	if testing.Short() {
  2085  		t.Skip("skipping test in short mode.")
  2086  	}
  2087  	if runtime.GOARCH != "amd64" {
  2088  		t.Skip("Test for amd64 only")
  2089  	}
  2090  	asserter := helper.Asserter{T: t}
  2091  	restoreCWD := testhelper.SaveCWD()
  2092  	defer restoreCWD()
  2093  
  2094  	var stateMachine ClassicStateMachine
  2095  	stateMachine.commonFlags, stateMachine.stateMachineFlags = helper.InitCommonOpts()
  2096  	stateMachine.parent = &stateMachine
  2097  	stateMachine.Snaps = []string{"lxd"}
  2098  	stateMachine.commonFlags.Channel = "stable"
  2099  	stateMachine.ImageDef = imagedefinition.ImageDefinition{
  2100  		Architecture: getHostArch(),
  2101  		Customization: &imagedefinition.Customization{
  2102  			ExtraSnaps: []*imagedefinition.Snap{
  2103  				{
  2104  					SnapName:     "hello",
  2105  					SnapRevision: 38,
  2106  				},
  2107  				{
  2108  					SnapName:     "ubuntu-image",
  2109  					SnapRevision: 330,
  2110  				},
  2111  				{
  2112  					SnapName:     "core20",
  2113  					SnapRevision: 1852,
  2114  				},
  2115  			},
  2116  		},
  2117  	}
  2118  
  2119  	err := stateMachine.makeTemporaryDirectories()
  2120  	asserter.AssertErrNil(err, true)
  2121  
  2122  	t.Cleanup(func() { os.RemoveAll(stateMachine.stateMachineFlags.WorkDir) })
  2123  
  2124  	err = stateMachine.prepareClassicImage()
  2125  	asserter.AssertErrNil(err, true)
  2126  
  2127  	for _, snapInfo := range stateMachine.ImageDef.Customization.ExtraSnaps {
  2128  		// compile a regex used to get revision numbers from seed.manifest
  2129  		revRegex, err := regexp.Compile(fmt.Sprintf("%s_(.*?).snap\n", snapInfo.SnapName))
  2130  		asserter.AssertErrNil(err, true)
  2131  		seedData, err := os.ReadFile(filepath.Join(
  2132  			stateMachine.tempDirs.chroot,
  2133  			"var",
  2134  			"lib",
  2135  			"snapd",
  2136  			"seed",
  2137  			"seed.yaml",
  2138  		))
  2139  		asserter.AssertErrNil(err, true)
  2140  		revString := revRegex.FindStringSubmatch(string(seedData))
  2141  		if len(revString) != 2 {
  2142  			t.Fatal("Error finding snap revision via regex")
  2143  		}
  2144  		seededRevision, err := strconv.Atoi(revString[1])
  2145  		asserter.AssertErrNil(err, true)
  2146  
  2147  		if seededRevision != snapInfo.SnapRevision {
  2148  			t.Errorf("Error, expected snap %s to "+
  2149  				"be revision %d, but it was %d",
  2150  				snapInfo.SnapName, snapInfo.SnapRevision, seededRevision)
  2151  		}
  2152  	}
  2153  }
  2154  
  2155  // TestFailedPrepareClassicImage tests failures in the prepareClassicImage function
  2156  func TestFailedPrepareClassicImage(t *testing.T) {
  2157  	if testing.Short() {
  2158  		t.Skip("skipping test in short mode.")
  2159  	}
  2160  	asserter := helper.Asserter{T: t}
  2161  	restoreCWD := testhelper.SaveCWD()
  2162  	defer restoreCWD()
  2163  
  2164  	var stateMachine ClassicStateMachine
  2165  	stateMachine.commonFlags, stateMachine.stateMachineFlags = helper.InitCommonOpts()
  2166  	stateMachine.parent = &stateMachine
  2167  	stateMachine.ImageDef = imagedefinition.ImageDefinition{
  2168  		Architecture: getHostArch(),
  2169  		Customization: &imagedefinition.Customization{
  2170  			ExtraSnaps: []*imagedefinition.Snap{},
  2171  		},
  2172  	}
  2173  
  2174  	err := stateMachine.makeTemporaryDirectories()
  2175  	asserter.AssertErrNil(err, true)
  2176  
  2177  	t.Cleanup(func() { os.RemoveAll(stateMachine.stateMachineFlags.WorkDir) })
  2178  
  2179  	// include an invalid snap snap name to trigger a failure in
  2180  	// parseSnapsAndChannels
  2181  	stateMachine.Snaps = []string{"lxd=test=invalid=name"}
  2182  	err = stateMachine.prepareClassicImage()
  2183  	asserter.AssertErrContains(err, "Invalid syntax")
  2184  
  2185  	// try to include a nonexistent snap to trigger a failure
  2186  	// in snapStore.SnapInfo
  2187  	stateMachine.Snaps = []string{"test-this-snap-name-should-never-exist"}
  2188  	err = stateMachine.prepareClassicImage()
  2189  	asserter.AssertErrContains(err, "Error getting info for snap")
  2190  
  2191  	// mock image.Prepare
  2192  	stateMachine.Snaps = []string{"hello", "core"}
  2193  	imagePrepare = mockImagePrepare
  2194  	t.Cleanup(func() {
  2195  		imagePrepare = image.Prepare
  2196  	})
  2197  	err = stateMachine.prepareClassicImage()
  2198  	asserter.AssertErrContains(err, "Error preparing image")
  2199  	imagePrepare = image.Prepare
  2200  
  2201  	// Test with a model assertion file
  2202  	stateMachine.ImageDef.ModelAssertion = filepath.Join("testdata", "modelAssertionClassic")
  2203  	err = stateMachine.prepareClassicImage()
  2204  	asserter.AssertErrNil(err, true)
  2205  
  2206  	path, err := filepath.Abs(filepath.Join("testdata", "modelAssertionClassic"))
  2207  	asserter.AssertErrNil(err, true)
  2208  	stateMachine.ImageDef.ModelAssertion = path
  2209  	err = stateMachine.prepareClassicImage()
  2210  	asserter.AssertErrNil(err, true)
  2211  
  2212  	stateMachine.ImageDef.ModelAssertion = ""
  2213  	// preseed the chroot, create a state.json file to trigger a reset, and mock some related functions
  2214  	err = stateMachine.prepareClassicImage()
  2215  	asserter.AssertErrNil(err, true)
  2216  	_, err = os.Create(filepath.Join(stateMachine.tempDirs.chroot, "var", "lib", "snapd", "state.json"))
  2217  	asserter.AssertErrNil(err, true)
  2218  
  2219  	seedOpen = mockSeedOpen
  2220  	t.Cleanup(func() {
  2221  		seedOpen = seed.Open
  2222  	})
  2223  	err = stateMachine.prepareClassicImage()
  2224  	asserter.AssertErrContains(err, "Error getting list of preseeded snaps")
  2225  	seedOpen = seed.Open
  2226  
  2227  	// Setup the exec.Command mock
  2228  	testCaseName = "TestFailedPrepareClassicImage"
  2229  	execCommand = fakeExecCommand
  2230  	t.Cleanup(func() {
  2231  		execCommand = exec.Command
  2232  	})
  2233  	err = stateMachine.prepareClassicImage()
  2234  	asserter.AssertErrContains(err, "Error resetting preseeding")
  2235  }
  2236  
  2237  // TestStateMachine_PopulateClassicRootfsContents runs the state machine through populate_rootfs_contents and examines
  2238  // the rootfs to ensure at least some of the correct file are in place
  2239  func TestStateMachine_PopulateClassicRootfsContents(t *testing.T) {
  2240  	t.Parallel()
  2241  	if testing.Short() {
  2242  		t.Skip("skipping test in short mode.")
  2243  	}
  2244  
  2245  	if runtime.GOARCH != "amd64" {
  2246  		t.Skip("Test for amd64 only")
  2247  	}
  2248  	asserter := helper.Asserter{T: t}
  2249  	restoreCWD := testhelper.SaveCWD()
  2250  	defer restoreCWD()
  2251  
  2252  	var stateMachine ClassicStateMachine
  2253  	stateMachine.commonFlags, stateMachine.stateMachineFlags = helper.InitCommonOpts()
  2254  	stateMachine.parent = &stateMachine
  2255  	stateMachine.ImageDef = imagedefinition.ImageDefinition{
  2256  		Architecture: getHostArch(),
  2257  		Series:       getHostSuite(),
  2258  		Rootfs: &imagedefinition.Rootfs{
  2259  			Archive: "ubuntu",
  2260  		},
  2261  		Customization: &imagedefinition.Customization{},
  2262  	}
  2263  
  2264  	err := stateMachine.makeTemporaryDirectories()
  2265  	asserter.AssertErrNil(err, true)
  2266  
  2267  	t.Cleanup(func() { os.RemoveAll(stateMachine.stateMachineFlags.WorkDir) })
  2268  
  2269  	err = getBasicChroot(stateMachine.StateMachine)
  2270  	asserter.AssertErrNil(err, true)
  2271  
  2272  	err = stateMachine.populateClassicRootfsContents()
  2273  	asserter.AssertErrNil(err, true)
  2274  
  2275  	// check the files before Teardown
  2276  	fileList := []string{filepath.Join("etc", "shadow"),
  2277  		filepath.Join("etc", "systemd"),
  2278  		filepath.Join("usr", "lib")}
  2279  	for _, file := range fileList {
  2280  		_, err := os.Stat(filepath.Join(stateMachine.tempDirs.rootfs, file))
  2281  		if err != nil {
  2282  			if os.IsNotExist(err) {
  2283  				t.Errorf("File %s should exist, but does not", file)
  2284  			}
  2285  		}
  2286  	}
  2287  
  2288  	// return when Customization.Fstab is not empty
  2289  	stateMachine.ImageDef.Customization.Fstab = []*imagedefinition.Fstab{
  2290  		{
  2291  			Label:        "writable",
  2292  			Mountpoint:   "/",
  2293  			FSType:       "ext4",
  2294  			MountOptions: "defaults",
  2295  			Dump:         true,
  2296  			FsckOrder:    1,
  2297  		},
  2298  	}
  2299  
  2300  	err = stateMachine.populateClassicRootfsContents()
  2301  	asserter.AssertErrNil(err, true)
  2302  
  2303  	// return when no Customization
  2304  	stateMachine.ImageDef.Customization = nil
  2305  
  2306  	err = stateMachine.populateClassicRootfsContents()
  2307  	asserter.AssertErrNil(err, true)
  2308  }
  2309  
  2310  // TestStateMachine_FailedPopulateClassicRootfsContents tests failed scenarios in populateClassicRootfsContents
  2311  // this is accomplished by mocking functions
  2312  func TestStateMachine_FailedPopulateClassicRootfsContents(t *testing.T) {
  2313  	if testing.Short() {
  2314  		t.Skip("skipping test in short mode.")
  2315  	}
  2316  	asserter := helper.Asserter{T: t}
  2317  	var stateMachine ClassicStateMachine
  2318  	stateMachine.commonFlags, stateMachine.stateMachineFlags = helper.InitCommonOpts()
  2319  	stateMachine.parent = &stateMachine
  2320  	stateMachine.ImageDef = imagedefinition.ImageDefinition{
  2321  		Architecture: getHostArch(),
  2322  		Series:       getHostSuite(),
  2323  		Rootfs: &imagedefinition.Rootfs{
  2324  			Archive: "ubuntu",
  2325  		},
  2326  		Customization: &imagedefinition.Customization{},
  2327  	}
  2328  
  2329  	err := stateMachine.makeTemporaryDirectories()
  2330  	asserter.AssertErrNil(err, true)
  2331  
  2332  	t.Cleanup(func() { os.RemoveAll(stateMachine.stateMachineFlags.WorkDir) })
  2333  
  2334  	err = getBasicChroot(stateMachine.StateMachine)
  2335  	asserter.AssertErrNil(err, true)
  2336  
  2337  	// mock os.ReadDir
  2338  	osReadDir = mockReadDir
  2339  	t.Cleanup(func() {
  2340  		osReadDir = os.ReadDir
  2341  	})
  2342  	err = stateMachine.populateClassicRootfsContents()
  2343  	asserter.AssertErrContains(err, "Error reading chroot dir")
  2344  	osReadDir = os.ReadDir
  2345  
  2346  	// mock osutil.CopySpecialFile
  2347  	osutilCopySpecialFile = mockCopySpecialFile
  2348  	t.Cleanup(func() {
  2349  		osutilCopySpecialFile = osutil.CopySpecialFile
  2350  	})
  2351  	err = stateMachine.populateClassicRootfsContents()
  2352  	asserter.AssertErrContains(err, "Error copying rootfs")
  2353  	osutilCopySpecialFile = osutil.CopySpecialFile
  2354  
  2355  	// mock os.WriteFile
  2356  	osWriteFile = mockWriteFile
  2357  	t.Cleanup(func() {
  2358  		osWriteFile = os.WriteFile
  2359  	})
  2360  	err = stateMachine.populateClassicRootfsContents()
  2361  	asserter.AssertErrContains(err, "Error writing to fstab")
  2362  	osWriteFile = os.WriteFile
  2363  
  2364  	// mock os.ReadFile
  2365  	osReadFile = mockReadFile
  2366  	t.Cleanup(func() {
  2367  		osReadFile = os.ReadFile
  2368  	})
  2369  	err = stateMachine.populateClassicRootfsContents()
  2370  	asserter.AssertErrContains(err, "Error reading fstab")
  2371  	osReadFile = os.ReadFile
  2372  
  2373  	// return when existing fstab contains LABEL=writable
  2374  	//nolint:gosec,G306
  2375  	err = os.WriteFile(filepath.Join(stateMachine.tempDirs.chroot, "etc", "fstab"),
  2376  		[]byte("LABEL=writable\n"),
  2377  		0644)
  2378  	asserter.AssertErrNil(err, true)
  2379  	err = stateMachine.populateClassicRootfsContents()
  2380  	asserter.AssertErrNil(err, true)
  2381  
  2382  	// create an /etc/resolv.conf.tmp in the chroot
  2383  	err = os.MkdirAll(filepath.Join(stateMachine.tempDirs.chroot, "etc"), 0755)
  2384  	asserter.AssertErrNil(err, true)
  2385  	_, err = os.Create(filepath.Join(stateMachine.tempDirs.chroot, "etc", "resolv.conf.tmp"))
  2386  	asserter.AssertErrNil(err, true)
  2387  
  2388  	// mock helper.RestoreResolvConf
  2389  	helperRestoreResolvConf = mockRestoreResolvConf
  2390  	t.Cleanup(func() {
  2391  		helperRestoreResolvConf = helper.RestoreResolvConf
  2392  	})
  2393  	err = stateMachine.populateClassicRootfsContents()
  2394  	asserter.AssertErrContains(err, "Error restoring /etc/resolv.conf")
  2395  	helperRestoreResolvConf = helper.RestoreResolvConf
  2396  }
  2397  
  2398  // TestSateMachine_customizeSourcesList tests functionality of the customizeSourcesList state function
  2399  func TestSateMachine_customizeSourcesList(t *testing.T) {
  2400  	testCases := []struct {
  2401  		name                      string
  2402  		deb822Format              bool
  2403  		existingSourcesList       string
  2404  		existingDeb822SourcesList string
  2405  		customization             *imagedefinition.Customization
  2406  		mockFuncs                 func() func()
  2407  		expectedErr               string
  2408  		expectedSourcesList       string
  2409  		expectedDeb822SourcesList string
  2410  	}{
  2411  		{
  2412  			name:                "set default sources.list",
  2413  			deb822Format:        false,
  2414  			existingSourcesList: "deb http://ports.ubuntu.com/ubuntu-ports jammy main restricted",
  2415  			customization:       &imagedefinition.Customization{},
  2416  			expectedSourcesList: `# See http://help.ubuntu.com/community/UpgradeNotes for how to upgrade to
  2417  # newer versions of the distribution.
  2418  deb http://archive.ubuntu.com/ubuntu/ jammy main restricted universe
  2419  `,
  2420  		},
  2421  		{
  2422  			name:                "set less components sources.list",
  2423  			deb822Format:        false,
  2424  			existingSourcesList: "deb http://ports.ubuntu.com/ubuntu-ports jammy main restricted",
  2425  			customization: &imagedefinition.Customization{
  2426  				Components: []string{"main"},
  2427  			},
  2428  			expectedSourcesList: `# See http://help.ubuntu.com/community/UpgradeNotes for how to upgrade to
  2429  # newer versions of the distribution.
  2430  deb http://archive.ubuntu.com/ubuntu/ jammy main
  2431  `,
  2432  		},
  2433  		{
  2434  			name:                "set components and pocket sources.list",
  2435  			deb822Format:        false,
  2436  			existingSourcesList: "deb http://ports.ubuntu.com/ubuntu-ports jammy main restricted",
  2437  			customization: &imagedefinition.Customization{
  2438  				Components: []string{"main"},
  2439  				Pocket:     "security",
  2440  			},
  2441  			expectedSourcesList: `# See http://help.ubuntu.com/community/UpgradeNotes for how to upgrade to
  2442  # newer versions of the distribution.
  2443  deb http://archive.ubuntu.com/ubuntu/ jammy main
  2444  deb http://security.ubuntu.com/ubuntu/ jammy-security main
  2445  `,
  2446  		},
  2447  		{
  2448  			name:                "fail to write sources.list",
  2449  			deb822Format:        false,
  2450  			existingSourcesList: "deb http://ports.ubuntu.com/ubuntu-ports jammy main restricted",
  2451  			customization: &imagedefinition.Customization{
  2452  				Components: []string{"main"},
  2453  				Pocket:     "security",
  2454  			},
  2455  			expectedSourcesList: "deb http://ports.ubuntu.com/ubuntu-ports jammy main restricted",
  2456  			expectedErr:         "unable to open sources.list file",
  2457  			mockFuncs: func() func() {
  2458  				mock := testhelper.NewOSMock(
  2459  					&testhelper.OSMockConf{
  2460  						OpenFileThreshold: 0,
  2461  					},
  2462  				)
  2463  
  2464  				osOpenFile = mock.OpenFile
  2465  				return func() { osOpenFile = os.OpenFile }
  2466  			},
  2467  		},
  2468  		{
  2469  			name:                "set default ubuntu.sources and commented sources.list",
  2470  			deb822Format:        true,
  2471  			existingSourcesList: "deb http://ports.ubuntu.com/ubuntu-ports jammy main restricted",
  2472  			existingDeb822SourcesList: `Types: deb
  2473  URIs: http://archive.ubuntu.com/
  2474  Suites: jammy
  2475  Components: main universe restricted multiverse
  2476  Signed-By: /usr/share/keyrings/ubuntu-archive-keyring.gpg
  2477  `,
  2478  			customization:       &imagedefinition.Customization{},
  2479  			expectedSourcesList: imagedefinition.LegacySourcesListComment,
  2480  			expectedDeb822SourcesList: `## Ubuntu distribution repository
  2481  ##
  2482  ## The following settings can be adjusted to configure which packages to use from Ubuntu.
  2483  ## Mirror your choices (except for URIs and Suites) in the security section below to
  2484  ## ensure timely security updates.
  2485  ##
  2486  ## Types: Append deb-src to enable the fetching of source package.
  2487  ## URIs: A URL to the repository (you may add multiple URLs)
  2488  ## Suites: The following additional suites can be configured
  2489  ##   <name>-updates   - Major bug fix updates produced after the final release of the
  2490  ##                      distribution.
  2491  ##   <name>-backports - software from this repository may not have been tested as
  2492  ##                      extensively as that contained in the main release, although it includes
  2493  ##                      newer versions of some applications which may provide useful features.
  2494  ##                      Also, please note that software in backports WILL NOT receive any review
  2495  ##                      or updates from the Ubuntu security team.
  2496  ## Components: Aside from main, the following components can be added to the list
  2497  ##   restricted  - Software that may not be under a free license, or protected by patents.
  2498  ##   universe    - Community maintained packages. Software in this repository receives maintenance
  2499  ##                 from volunteers in the Ubuntu community, or a 10 year security maintenance
  2500  ##                 commitment from Canonical when an Ubuntu Pro subscription is attached.
  2501  ##   multiverse  - Community maintained of restricted. Software from this repository is
  2502  ##                 ENTIRELY UNSUPPORTED by the Ubuntu team, and may not be under a free
  2503  ##                 licence. Please satisfy yourself as to your rights to use the software.
  2504  ##                 Also, please note that software in multiverse WILL NOT receive any
  2505  ##                 review or updates from the Ubuntu security team.
  2506  ##
  2507  ## See the sources.list(5) manual page for further settings.
  2508  Types: deb
  2509  URIs: http://archive.ubuntu.com/ubuntu/
  2510  Suites: jammy
  2511  Components: main restricted
  2512  Signed-By: /usr/share/keyrings/ubuntu-archive-keyring.gpg
  2513  
  2514  ## Ubuntu security updates. Aside from URIs and Suites,
  2515  ## this should mirror your choices in the previous section.
  2516  Types: deb
  2517  URIs: http://security.ubuntu.com/ubuntu/
  2518  Suites: jammy
  2519  Components: main restricted
  2520  Signed-By: /usr/share/keyrings/ubuntu-archive-keyring.gpg
  2521  
  2522  `,
  2523  		},
  2524  		{
  2525  			name:                "fail to write ubuntu.sources and commented sources.list",
  2526  			deb822Format:        true,
  2527  			existingSourcesList: "deb http://ports.ubuntu.com/ubuntu-ports jammy main restricted",
  2528  			existingDeb822SourcesList: `Types: deb
  2529  URIs: http://archive.ubuntu.com/
  2530  Suites: jammy
  2531  Components: main universe restricted multiverse
  2532  Signed-By: /usr/share/keyrings/ubuntu-archive-keyring.gpg
  2533  `,
  2534  			customization:       &imagedefinition.Customization{},
  2535  			expectedSourcesList: "deb http://ports.ubuntu.com/ubuntu-ports jammy main restricted",
  2536  			expectedDeb822SourcesList: `Types: deb
  2537  URIs: http://archive.ubuntu.com/
  2538  Suites: jammy
  2539  Components: main universe restricted multiverse
  2540  Signed-By: /usr/share/keyrings/ubuntu-archive-keyring.gpg
  2541  `,
  2542  			expectedErr: "unable to open ubuntu.sources file",
  2543  			mockFuncs: func() func() {
  2544  				mock := testhelper.NewOSMock(
  2545  					&testhelper.OSMockConf{
  2546  						OpenFileThreshold: 0,
  2547  					},
  2548  				)
  2549  
  2550  				osOpenFile = mock.OpenFile
  2551  				return func() { osOpenFile = os.OpenFile }
  2552  			},
  2553  		},
  2554  		{
  2555  			name:                "fail to create sources.list.d",
  2556  			deb822Format:        true,
  2557  			existingSourcesList: "deb http://ports.ubuntu.com/ubuntu-ports jammy main restricted",
  2558  			existingDeb822SourcesList: `Types: deb
  2559  URIs: http://archive.ubuntu.com/
  2560  Suites: jammy
  2561  Components: main universe restricted multiverse
  2562  Signed-By: /usr/share/keyrings/ubuntu-archive-keyring.gpg
  2563  `,
  2564  			customization:       &imagedefinition.Customization{},
  2565  			expectedSourcesList: "deb http://ports.ubuntu.com/ubuntu-ports jammy main restricted",
  2566  			expectedDeb822SourcesList: `Types: deb
  2567  URIs: http://archive.ubuntu.com/
  2568  Suites: jammy
  2569  Components: main universe restricted multiverse
  2570  Signed-By: /usr/share/keyrings/ubuntu-archive-keyring.gpg
  2571  `,
  2572  			expectedErr: "Error /etc/apt/sources.list.d directory",
  2573  			mockFuncs: func() func() {
  2574  				mock := testhelper.NewOSMock(
  2575  					&testhelper.OSMockConf{
  2576  						MkdirAllThreshold: 0,
  2577  					},
  2578  				)
  2579  
  2580  				osMkdirAll = mock.MkdirAll
  2581  				return func() { osMkdirAll = os.MkdirAll }
  2582  			},
  2583  		},
  2584  	}
  2585  
  2586  	for _, tc := range testCases {
  2587  		t.Run(tc.name, func(t *testing.T) {
  2588  			asserter := helper.Asserter{T: t}
  2589  			restoreCWD := testhelper.SaveCWD()
  2590  			defer restoreCWD()
  2591  
  2592  			var stateMachine ClassicStateMachine
  2593  			stateMachine.commonFlags, stateMachine.stateMachineFlags = helper.InitCommonOpts()
  2594  			stateMachine.parent = &stateMachine
  2595  			stateMachine.ImageDef = imagedefinition.ImageDefinition{
  2596  				Architecture: getHostArch(),
  2597  				Series:       getHostSuite(),
  2598  				Rootfs: &imagedefinition.Rootfs{
  2599  					SourcesListDeb822: helper.BoolPtr(tc.deb822Format),
  2600  				},
  2601  				Customization: tc.customization,
  2602  			}
  2603  
  2604  			err := helper.SetDefaults(&stateMachine.ImageDef)
  2605  			asserter.AssertErrNil(err, true)
  2606  
  2607  			err = stateMachine.makeTemporaryDirectories()
  2608  			asserter.AssertErrNil(err, true)
  2609  
  2610  			t.Cleanup(func() { os.RemoveAll(stateMachine.stateMachineFlags.WorkDir) })
  2611  
  2612  			err = os.MkdirAll(filepath.Join(stateMachine.tempDirs.chroot, "etc", "apt", "sources.list.d"), 0644)
  2613  			asserter.AssertErrNil(err, true)
  2614  
  2615  			sourcesListPath := filepath.Join(stateMachine.tempDirs.chroot, "etc", "apt", "sources.list")
  2616  			deb822SourcesListPath := filepath.Join(stateMachine.tempDirs.chroot, "etc", "apt", "sources.list.d", "ubuntu.sources")
  2617  
  2618  			err = osWriteFile(sourcesListPath, []byte(tc.existingSourcesList), 0644)
  2619  			asserter.AssertErrNil(err, true)
  2620  
  2621  			err = osWriteFile(deb822SourcesListPath, []byte(tc.existingDeb822SourcesList), 0644)
  2622  			asserter.AssertErrNil(err, true)
  2623  
  2624  			if tc.mockFuncs != nil {
  2625  				restoreMock := tc.mockFuncs()
  2626  				t.Cleanup(restoreMock)
  2627  			}
  2628  
  2629  			err = stateMachine.customizeSourcesList()
  2630  			if err != nil || len(tc.expectedErr) != 0 {
  2631  				asserter.AssertErrContains(err, tc.expectedErr)
  2632  			}
  2633  
  2634  			sourcesListBytes, err := os.ReadFile(sourcesListPath)
  2635  			asserter.AssertErrNil(err, true)
  2636  
  2637  			asserter.AssertEqual(tc.expectedSourcesList, string(sourcesListBytes))
  2638  
  2639  			deb822SourcesListBytes, err := os.ReadFile(deb822SourcesListPath)
  2640  			asserter.AssertErrNil(err, true)
  2641  
  2642  			asserter.AssertEqual(tc.expectedDeb822SourcesList, string(deb822SourcesListBytes))
  2643  
  2644  		})
  2645  	}
  2646  }
  2647  
  2648  // TestSateMachine_fixFstab tests functionality of the fixFstab function
  2649  func TestSateMachine_fixFstab(t *testing.T) {
  2650  	t.Parallel()
  2651  	testCases := []struct {
  2652  		name          string
  2653  		existingFstab string
  2654  		expectedFstab string
  2655  	}{
  2656  		{
  2657  			name:          "add entry to an existing but empty fstab",
  2658  			existingFstab: "# UNCONFIGURED FSTAB",
  2659  			expectedFstab: `LABEL=writable	/	ext4	discard,errors=remount-ro	0	1
  2660  `,
  2661  		},
  2662  		{
  2663  			name: "fix existing entry amongst several others",
  2664  			existingFstab: `# /etc/fstab: static file system information.
  2665  UUID=1565-1398	/	ext4	defaults	0	0
  2666  #Here is another comment that should be left in place
  2667  /dev/mapper/vgubuntu-swap_1	none	swap	sw	0	0
  2668  `,
  2669  			expectedFstab: `# /etc/fstab: static file system information.
  2670  LABEL=writable	/	ext4	discard,errors=remount-ro	0	1
  2671  #Here is another comment that should be left in place
  2672  /dev/mapper/vgubuntu-swap_1	none	swap	sw	0	0
  2673  `,
  2674  		},
  2675  		{
  2676  			name: "fix existing entry amongst several others (with spaces)",
  2677  			existingFstab: `# /etc/fstab: static file system information.
  2678  UUID=1565-1398	/	ext4	defaults	0	0
  2679  /dev/mapper/vgubuntu-swap_1	none  swap sw      0   0
  2680  `,
  2681  			expectedFstab: `# /etc/fstab: static file system information.
  2682  LABEL=writable	/	ext4	discard,errors=remount-ro	0	1
  2683  /dev/mapper/vgubuntu-swap_1	none	swap	sw	0	0
  2684  `,
  2685  		},
  2686  		{
  2687  			name: "fix only one root mount point",
  2688  			existingFstab: `# /etc/fstab: static file system information.
  2689  UUID=1565-1398	/	ext4	defaults	0	0
  2690  UUID=1234-5678	/	ext4	defaults	0	0
  2691  `,
  2692  			expectedFstab: `# /etc/fstab: static file system information.
  2693  LABEL=writable	/	ext4	discard,errors=remount-ro	0	1
  2694  UUID=1234-5678	/	ext4	defaults	0	0
  2695  `,
  2696  		},
  2697  	}
  2698  
  2699  	for _, tc := range testCases {
  2700  		t.Run(tc.name, func(t *testing.T) {
  2701  			asserter := helper.Asserter{T: t}
  2702  			restoreCWD := testhelper.SaveCWD()
  2703  			defer restoreCWD()
  2704  
  2705  			var stateMachine ClassicStateMachine
  2706  			stateMachine.commonFlags, stateMachine.stateMachineFlags = helper.InitCommonOpts()
  2707  			stateMachine.parent = &stateMachine
  2708  			stateMachine.ImageDef = imagedefinition.ImageDefinition{
  2709  				Architecture:  getHostArch(),
  2710  				Series:        getHostSuite(),
  2711  				Rootfs:        &imagedefinition.Rootfs{},
  2712  				Customization: &imagedefinition.Customization{},
  2713  			}
  2714  
  2715  			// set the defaults for the imageDef
  2716  			err := helper.SetDefaults(&stateMachine.ImageDef)
  2717  			asserter.AssertErrNil(err, true)
  2718  
  2719  			err = stateMachine.makeTemporaryDirectories()
  2720  			asserter.AssertErrNil(err, true)
  2721  
  2722  			t.Cleanup(func() { os.RemoveAll(stateMachine.stateMachineFlags.WorkDir) })
  2723  
  2724  			// create the <chroot>/etc directory
  2725  			err = os.MkdirAll(filepath.Join(stateMachine.tempDirs.rootfs, "etc"), 0644)
  2726  			asserter.AssertErrNil(err, true)
  2727  
  2728  			fstabPath := filepath.Join(stateMachine.tempDirs.rootfs, "etc", "fstab")
  2729  
  2730  			// simulate an already existing fstab file
  2731  			if len(tc.existingFstab) != 0 {
  2732  				err = osWriteFile(fstabPath, []byte(tc.existingFstab), 0644)
  2733  				asserter.AssertErrNil(err, true)
  2734  			}
  2735  
  2736  			err = stateMachine.fixFstab()
  2737  			asserter.AssertErrNil(err, true)
  2738  
  2739  			fstabBytes, err := os.ReadFile(fstabPath)
  2740  			asserter.AssertErrNil(err, true)
  2741  
  2742  			if string(fstabBytes) != tc.expectedFstab {
  2743  				t.Errorf("Expected fstab content \"%s\", but got \"%s\"",
  2744  					tc.expectedFstab, string(fstabBytes))
  2745  			}
  2746  		})
  2747  	}
  2748  }
  2749  
  2750  // TestGeneratePackageManifest tests if classic image manifest generation works
  2751  func TestGeneratePackageManifest(t *testing.T) {
  2752  	asserter := helper.Asserter{T: t}
  2753  
  2754  	// Setup the exec.Command mock
  2755  	testCaseName = "TestGeneratePackageManifest"
  2756  	execCommand = fakeExecCommand
  2757  	t.Cleanup(func() {
  2758  		execCommand = exec.Command
  2759  	})
  2760  	// We need the output directory set for this
  2761  	outputDir, err := os.MkdirTemp("/tmp", "ubuntu-image-")
  2762  	asserter.AssertErrNil(err, true)
  2763  	t.Cleanup(func() { os.RemoveAll(outputDir) })
  2764  
  2765  	var stateMachine ClassicStateMachine
  2766  	stateMachine.commonFlags, stateMachine.stateMachineFlags = helper.InitCommonOpts()
  2767  	stateMachine.parent = &stateMachine
  2768  	stateMachine.commonFlags.OutputDir = outputDir
  2769  	stateMachine.ImageDef = imagedefinition.ImageDefinition{
  2770  		Architecture: getHostArch(),
  2771  		Series:       getHostSuite(),
  2772  		Rootfs: &imagedefinition.Rootfs{
  2773  			Archive: "ubuntu",
  2774  		},
  2775  		Customization: &imagedefinition.Customization{},
  2776  		Artifacts: &imagedefinition.Artifact{
  2777  			Manifest: &imagedefinition.Manifest{
  2778  				ManifestName: "filesystem.manifest",
  2779  			},
  2780  		},
  2781  	}
  2782  	err = osMkdirAll(stateMachine.commonFlags.OutputDir, 0755)
  2783  	asserter.AssertErrNil(err, true)
  2784  	t.Cleanup(func() { os.RemoveAll(stateMachine.commonFlags.OutputDir) })
  2785  
  2786  	err = stateMachine.generatePackageManifest()
  2787  	asserter.AssertErrNil(err, true)
  2788  
  2789  	os.RemoveAll(stateMachine.stateMachineFlags.WorkDir)
  2790  	// Check if manifest file got generated and if it has expected contents
  2791  	manifestPath := filepath.Join(stateMachine.commonFlags.OutputDir, "filesystem.manifest")
  2792  	manifestBytes, err := os.ReadFile(manifestPath)
  2793  	asserter.AssertErrNil(err, true)
  2794  	// The order of packages shouldn't matter
  2795  	examplePackages := []string{"foo 1.2", "bar 1.4-1ubuntu4.1", "libbaz 0.1.3ubuntu2"}
  2796  	for _, pkg := range examplePackages {
  2797  		if !strings.Contains(string(manifestBytes), pkg) {
  2798  			t.Errorf("filesystem.manifest does not contain expected package: %s", pkg)
  2799  		}
  2800  	}
  2801  }
  2802  
  2803  // TestFailedGeneratePackageManifest tests if classic manifest generation failures are reported
  2804  func TestFailedGeneratePackageManifest(t *testing.T) {
  2805  	asserter := helper.Asserter{T: t}
  2806  	var stateMachine ClassicStateMachine
  2807  	stateMachine.commonFlags, stateMachine.stateMachineFlags = helper.InitCommonOpts()
  2808  	stateMachine.parent = &stateMachine
  2809  	stateMachine.ImageDef = imagedefinition.ImageDefinition{
  2810  		Architecture: getHostArch(),
  2811  		Series:       getHostSuite(),
  2812  		Rootfs: &imagedefinition.Rootfs{
  2813  			Archive: "ubuntu",
  2814  		},
  2815  		Customization: &imagedefinition.Customization{},
  2816  		Artifacts: &imagedefinition.Artifact{
  2817  			Manifest: &imagedefinition.Manifest{
  2818  				ManifestName: "filesystem.manifest",
  2819  			},
  2820  		},
  2821  	}
  2822  
  2823  	// We need the output directory set for this
  2824  	outputDir, err := os.MkdirTemp("/tmp", "ubuntu-image-")
  2825  	asserter.AssertErrNil(err, true)
  2826  	t.Cleanup(func() { os.RemoveAll(outputDir) })
  2827  	stateMachine.commonFlags.OutputDir = outputDir
  2828  
  2829  	// Setup the exec.Command mock - version from the success test
  2830  	testCaseName = "TestGeneratePackageManifest"
  2831  	execCommand = fakeExecCommand
  2832  	t.Cleanup(func() {
  2833  		execCommand = exec.Command
  2834  	})
  2835  
  2836  	// Setup the mock for os.Create, making those fail
  2837  	osCreate = mockCreate
  2838  	t.Cleanup(func() {
  2839  		osCreate = os.Create
  2840  	})
  2841  
  2842  	err = stateMachine.generatePackageManifest()
  2843  	asserter.AssertErrContains(err, "Error creating manifest file")
  2844  	osCreate = os.Create
  2845  
  2846  	// Setup the exec.Command mock - version from the fail test
  2847  	testCaseName = "TestFailedGeneratePackageManifest"
  2848  	execCommand = fakeExecCommand
  2849  	t.Cleanup(func() {
  2850  		execCommand = exec.Command
  2851  	})
  2852  	err = stateMachine.generatePackageManifest()
  2853  	asserter.AssertErrContains(err, "Error generating package manifest with command")
  2854  }
  2855  
  2856  // TestGenerateFilelist tests if classic image filelist generation works
  2857  func TestGenerateFilelist(t *testing.T) {
  2858  	asserter := helper.Asserter{T: t}
  2859  
  2860  	// Setup the exec.Command mock
  2861  	testCaseName = "TestGenerateFilelist"
  2862  	execCommand = fakeExecCommand
  2863  	t.Cleanup(func() {
  2864  		execCommand = exec.Command
  2865  	})
  2866  	// We need the output directory set for this
  2867  	outputDir, err := os.MkdirTemp("/tmp", "ubuntu-image-")
  2868  	asserter.AssertErrNil(err, true)
  2869  	t.Cleanup(func() { os.RemoveAll(outputDir) })
  2870  
  2871  	var stateMachine ClassicStateMachine
  2872  	stateMachine.commonFlags, stateMachine.stateMachineFlags = helper.InitCommonOpts()
  2873  	stateMachine.parent = &stateMachine
  2874  	stateMachine.commonFlags.OutputDir = outputDir
  2875  	stateMachine.ImageDef = imagedefinition.ImageDefinition{
  2876  		Architecture: getHostArch(),
  2877  		Series:       getHostSuite(),
  2878  		Rootfs: &imagedefinition.Rootfs{
  2879  			Archive: "ubuntu",
  2880  		},
  2881  		Customization: &imagedefinition.Customization{},
  2882  		Artifacts: &imagedefinition.Artifact{
  2883  			Filelist: &imagedefinition.Filelist{
  2884  				FilelistName: "filesystem.filelist",
  2885  			},
  2886  		},
  2887  	}
  2888  	err = osMkdirAll(stateMachine.commonFlags.OutputDir, 0755)
  2889  	asserter.AssertErrNil(err, true)
  2890  	t.Cleanup(func() { os.RemoveAll(stateMachine.commonFlags.OutputDir) })
  2891  
  2892  	err = stateMachine.generateFilelist()
  2893  	asserter.AssertErrNil(err, true)
  2894  
  2895  	os.RemoveAll(stateMachine.stateMachineFlags.WorkDir)
  2896  	// Check if filelist file got generated
  2897  	filelistPath := filepath.Join(stateMachine.commonFlags.OutputDir, "filesystem.filelist")
  2898  	_, err = os.Stat(filelistPath)
  2899  	asserter.AssertErrNil(err, true)
  2900  }
  2901  
  2902  // TestFailedGenerateFilelist tests if classic filelist generation failures are reported
  2903  func TestFailedGenerateFilelist(t *testing.T) {
  2904  	asserter := helper.Asserter{T: t}
  2905  
  2906  	// Setup the exec.Command mock - version from the success test
  2907  	testCaseName = "TestGenerateFilelist"
  2908  	execCommand = fakeExecCommand
  2909  	defer func() {
  2910  		execCommand = exec.Command
  2911  	}()
  2912  	// Setup the mock for os.Create, making those fail
  2913  	osCreate = mockCreate
  2914  	defer func() {
  2915  		osCreate = os.Create
  2916  	}()
  2917  
  2918  	var stateMachine ClassicStateMachine
  2919  	stateMachine.commonFlags, stateMachine.stateMachineFlags = helper.InitCommonOpts()
  2920  	stateMachine.parent = &stateMachine
  2921  	stateMachine.ImageDef = imagedefinition.ImageDefinition{
  2922  		Architecture: getHostArch(),
  2923  		Series:       getHostSuite(),
  2924  		Rootfs: &imagedefinition.Rootfs{
  2925  			Archive: "ubuntu",
  2926  		},
  2927  		Customization: &imagedefinition.Customization{},
  2928  		Artifacts: &imagedefinition.Artifact{
  2929  			Filelist: &imagedefinition.Filelist{
  2930  				FilelistName: "filesystem.filelist",
  2931  			},
  2932  		},
  2933  	}
  2934  
  2935  	// We need the output directory set for this
  2936  	outputDir, err := os.MkdirTemp("/tmp", "ubuntu-image-")
  2937  	asserter.AssertErrNil(err, true)
  2938  	t.Cleanup(func() { os.RemoveAll(outputDir) })
  2939  	stateMachine.commonFlags.OutputDir = outputDir
  2940  
  2941  	// Setup the exec.Command mock - version from the success test
  2942  	testCaseName = "TestGenerateFilelist"
  2943  	execCommand = fakeExecCommand
  2944  	defer func() {
  2945  		execCommand = exec.Command
  2946  	}()
  2947  
  2948  	// Setup the mock for os.Create, making those fail
  2949  	osCreate = mockCreate
  2950  	defer func() {
  2951  		osCreate = os.Create
  2952  	}()
  2953  
  2954  	err = stateMachine.generateFilelist()
  2955  	asserter.AssertErrContains(err, "Error creating filelist")
  2956  	osCreate = os.Create
  2957  
  2958  	// Setup the exec.Command mock - version from the fail test
  2959  	testCaseName = "TestFailedGenerateFilelist"
  2960  	execCommand = fakeExecCommand
  2961  	defer func() {
  2962  		execCommand = exec.Command
  2963  	}()
  2964  	err = stateMachine.generateFilelist()
  2965  	asserter.AssertErrContains(err, "Error generating file list with command")
  2966  }
  2967  
  2968  // TestSuccessfulClassicRun runs through a full classic state machine run and ensures
  2969  // it is successful. It creates a .img and a .qcow2 file and ensures they are the
  2970  // correct file types it also mounts the resulting .img and ensures grub was updated
  2971  func TestSuccessfulClassicRun(t *testing.T) {
  2972  	if testing.Short() {
  2973  		t.Skip("skipping test in short mode.")
  2974  	}
  2975  
  2976  	asserter := helper.Asserter{T: t}
  2977  	restoreCWD := testhelper.SaveCWD()
  2978  	t.Cleanup(restoreCWD)
  2979  
  2980  	// We need the output directory set for this
  2981  	outputDir, err := os.MkdirTemp("/tmp", "ubuntu-image-")
  2982  	asserter.AssertErrNil(err, true)
  2983  	t.Cleanup(func() { os.RemoveAll(outputDir) })
  2984  
  2985  	var stateMachine ClassicStateMachine
  2986  	stateMachine.commonFlags, stateMachine.stateMachineFlags = helper.InitCommonOpts()
  2987  	stateMachine.parent = &stateMachine
  2988  	stateMachine.commonFlags.Debug = true
  2989  	stateMachine.commonFlags.Size = "5G"
  2990  	stateMachine.commonFlags.OutputDir = outputDir
  2991  	stateMachine.Args.ImageDefinition = filepath.Join("testdata", "image_definitions",
  2992  		"test_amd64.yaml")
  2993  
  2994  	err = stateMachine.Setup()
  2995  	asserter.AssertErrNil(err, true)
  2996  
  2997  	t.Cleanup(func() { os.RemoveAll(stateMachine.stateMachineFlags.WorkDir) })
  2998  
  2999  	err = stateMachine.Run()
  3000  	asserter.AssertErrNil(err, true)
  3001  
  3002  	t.Cleanup(func() {
  3003  		err = stateMachine.Teardown()
  3004  		asserter.AssertErrNil(err, true)
  3005  	})
  3006  
  3007  	testHelperCheckPPAInstalled(t, &asserter, stateMachine.tempDirs.chroot)
  3008  	testHelperCheckSnapInstalled(t, &asserter, stateMachine.tempDirs.chroot)
  3009  
  3010  	artifacts := map[string]string{
  3011  		"pc-amd64.img":            "DOS/MBR boot sector",
  3012  		"pc-amd64.qcow2":          "QEMU QCOW",
  3013  		"filesystem-manifest.txt": "text",
  3014  		"filesystem-filelist.txt": "text",
  3015  	}
  3016  	testHelperCheckArtifacts(t, &asserter, stateMachine.commonFlags.OutputDir, artifacts)
  3017  
  3018  	// create a directory in which to mount the rootfs
  3019  	mountDir := filepath.Join(stateMachine.tempDirs.scratch, "loopback")
  3020  	var mountImageCmds []*exec.Cmd
  3021  	var umountImageCmds []*exec.Cmd
  3022  
  3023  	t.Cleanup(func() {
  3024  		for _, teardownCmd := range umountImageCmds {
  3025  			if tmpErr := teardownCmd.Run(); tmpErr != nil {
  3026  				if err != nil {
  3027  					err = fmt.Errorf("%s after previous error: %w", tmpErr, err)
  3028  				} else {
  3029  					err = tmpErr
  3030  				}
  3031  			}
  3032  		}
  3033  	})
  3034  
  3035  	imgPath := filepath.Join(stateMachine.commonFlags.OutputDir, "pc-amd64.img")
  3036  
  3037  	// set up the loopback
  3038  	mountImageCmds = append(mountImageCmds,
  3039  		//nolint:gosec,G204
  3040  		exec.Command("losetup",
  3041  			filepath.Join("/dev", "loop99"),
  3042  			imgPath,
  3043  		),
  3044  	)
  3045  
  3046  	// unset the loopback
  3047  	umountImageCmds = append(umountImageCmds,
  3048  		//nolint:gosec,G204
  3049  		exec.Command("losetup", "--detach", filepath.Join("/dev", "loop99")),
  3050  	)
  3051  
  3052  	mountImageCmds = append(mountImageCmds,
  3053  		//nolint:gosec,G204
  3054  		exec.Command("kpartx", "-a", filepath.Join("/dev", "loop99")),
  3055  	)
  3056  
  3057  	umountImageCmds = append([]*exec.Cmd{
  3058  		//nolint:gosec,G204
  3059  		exec.Command("kpartx", "-d", filepath.Join("/dev", "loop99")),
  3060  	}, umountImageCmds...,
  3061  	)
  3062  
  3063  	mountImageCmds = append(mountImageCmds,
  3064  		//nolint:gosec,G204
  3065  		exec.Command("mount", filepath.Join("/dev", "mapper", "loop99p3"), mountDir), // with this example the rootfs is partition 3 mountDir
  3066  	)
  3067  
  3068  	umountImageCmds = append([]*exec.Cmd{
  3069  		//nolint:gosec,G204
  3070  		exec.Command("mount", "--make-rprivate", filepath.Join("/dev", "mapper", "loop99p3")),
  3071  		//nolint:gosec,G204
  3072  		exec.Command("umount", "--recursive", filepath.Join("/dev", "mapper", "loop99p3")),
  3073  	}, umountImageCmds...,
  3074  	)
  3075  
  3076  	// set up the mountpoints
  3077  	mountPoints := []mountPoint{
  3078  		{
  3079  			src:      "devtmpfs-build",
  3080  			basePath: mountDir,
  3081  			relpath:  "/dev",
  3082  			typ:      "devtmpfs",
  3083  		},
  3084  		{
  3085  			src:      "devpts-build",
  3086  			basePath: mountDir,
  3087  			relpath:  "/dev/pts",
  3088  			typ:      "devpts",
  3089  			opts:     []string{"nodev", "nosuid"},
  3090  		},
  3091  		{
  3092  			src:      "proc-build",
  3093  			basePath: mountDir,
  3094  			relpath:  "/proc",
  3095  			typ:      "proc",
  3096  		},
  3097  		{
  3098  			src:      "sysfs-build",
  3099  			basePath: mountDir,
  3100  			relpath:  "/sys",
  3101  			typ:      "sysfs",
  3102  		},
  3103  	}
  3104  	for _, mp := range mountPoints {
  3105  		mountCmds, umountCmds, err := mp.getMountCmd()
  3106  		if err != nil {
  3107  			t.Errorf("Error preparing mountpoint \"%s\": \"%s\"",
  3108  				mp.relpath,
  3109  				err.Error(),
  3110  			)
  3111  		}
  3112  		mountImageCmds = append(mountImageCmds, mountCmds...)
  3113  		umountImageCmds = append(umountCmds, umountImageCmds...)
  3114  	}
  3115  	// make sure to unmount the disk too
  3116  	umountImageCmds = append([]*exec.Cmd{exec.Command("umount", "--recursive", mountDir)}, umountImageCmds...)
  3117  
  3118  	// now run all the commands to mount the image
  3119  	for _, cmd := range mountImageCmds {
  3120  		outPut := helper.SetCommandOutput(cmd, true)
  3121  		err := cmd.Run()
  3122  		if err != nil {
  3123  			t.Errorf("Error running command \"%s\". Error is \"%s\". Output is: \n%s",
  3124  				cmd.String(), err.Error(), outPut.String())
  3125  		}
  3126  	}
  3127  
  3128  	testHelperCheckMakeDirs(t, mountDir)
  3129  	testHelperCheckAddUser(t, &asserter, mountDir)
  3130  	testHelperCheckGrubConfig(t, mountDir)
  3131  	testHelperCheckCleanedFiles(t, mountDir)
  3132  	testHelperCheckLocaleFile(t, &asserter, mountDir)
  3133  	testHelperCheckSourcesList(t, &asserter, mountDir)
  3134  }
  3135  
  3136  func testHelperCheckPPAInstalled(t *testing.T, asserter *helper.Asserter, chroot string) {
  3137  	t.Helper()
  3138  	files := []string{
  3139  		filepath.Join(chroot, "usr", "bin", "hello-ubuntu-image-public"),
  3140  		filepath.Join(chroot, "usr", "bin", "hello-ubuntu-image-private"),
  3141  	}
  3142  	for _, file := range files {
  3143  		_, err := os.Stat(file)
  3144  		asserter.AssertErrNil(err, true)
  3145  	}
  3146  }
  3147  
  3148  func testHelperCheckSnapInstalled(t *testing.T, asserter *helper.Asserter, chroot string) {
  3149  	t.Helper()
  3150  	type snapList struct {
  3151  		Snaps []struct {
  3152  			Name    string `yaml:"name"`
  3153  			Channel string `yaml:"channel"`
  3154  		} `yaml:"snaps"`
  3155  	}
  3156  
  3157  	seedYaml := filepath.Join(chroot,
  3158  		"var", "lib", "snapd", "seed", "seed.yaml")
  3159  
  3160  	seedFile, err := os.Open(seedYaml)
  3161  	asserter.AssertErrNil(err, true)
  3162  	defer seedFile.Close()
  3163  
  3164  	var seededSnaps snapList
  3165  	err = yaml.NewDecoder(seedFile).Decode(&seededSnaps)
  3166  	asserter.AssertErrNil(err, true)
  3167  
  3168  	expectedSnapChannels := map[string]string{
  3169  		"hello":  "candidate",
  3170  		"core20": "stable",
  3171  	}
  3172  
  3173  	for _, seededSnap := range seededSnaps.Snaps {
  3174  		channel, found := expectedSnapChannels[seededSnap.Name]
  3175  		if found {
  3176  			if channel != seededSnap.Channel {
  3177  				t.Errorf("Expected snap %s to be pre-seeded with channel %s, but got %s",
  3178  					seededSnap.Name, channel, seededSnap.Channel)
  3179  			}
  3180  		}
  3181  	}
  3182  }
  3183  
  3184  func testHelperCheckArtifacts(t *testing.T, asserter *helper.Asserter, outputDir string, artifacts map[string]string) {
  3185  	t.Helper()
  3186  	for artifact, fileType := range artifacts {
  3187  		fullPath := filepath.Join(outputDir, artifact)
  3188  		_, err := os.Stat(fullPath)
  3189  		if err != nil {
  3190  			if os.IsNotExist(err) {
  3191  				t.Errorf("File \"%s\" should exist, but does not", fullPath)
  3192  			}
  3193  		}
  3194  
  3195  		// check it is the expected file type
  3196  		fileCommand := *exec.Command("file", fullPath)
  3197  		cmdOutput, err := fileCommand.CombinedOutput()
  3198  		asserter.AssertErrNil(err, true)
  3199  		if !strings.Contains(string(cmdOutput), fileType) {
  3200  			t.Errorf("File \"%s\" is the wrong file type. Expected \"%s\" but got \"%s\"",
  3201  				fullPath, fileType, string(cmdOutput))
  3202  		}
  3203  	}
  3204  }
  3205  
  3206  func testHelperCheckMakeDirs(t *testing.T, mountDir string) {
  3207  	t.Helper()
  3208  	addedDir := filepath.Join(mountDir, "etc", "foo", "bar")
  3209  	_, err := os.Stat(addedDir)
  3210  	if err != nil {
  3211  		if os.IsNotExist(err) {
  3212  			t.Errorf("Directory \"%s\" should exist, but does not", addedDir)
  3213  		}
  3214  	}
  3215  }
  3216  
  3217  func testHelperCheckAddUser(t *testing.T, asserter *helper.Asserter, mountDir string) {
  3218  	t.Helper()
  3219  	shadowPath := filepath.Join(mountDir, "etc", "shadow")
  3220  	shadowFile, err := os.Open(shadowPath)
  3221  	asserter.AssertErrNil(err, true)
  3222  	defer shadowFile.Close()
  3223  	ubuntu2Found := false
  3224  	ubuntu2Line := ""
  3225  
  3226  	scanner := bufio.NewScanner(shadowFile)
  3227  
  3228  	for scanner.Scan() {
  3229  		if strings.HasPrefix(scanner.Text(), "ubuntu2") {
  3230  			ubuntu2Line = scanner.Text()
  3231  			ubuntu2Found = true
  3232  			break
  3233  		}
  3234  	}
  3235  
  3236  	if !ubuntu2Found {
  3237  		t.Error("ubuntu2 user not created")
  3238  	}
  3239  
  3240  	expire := strings.Split(ubuntu2Line, ":")[2]
  3241  
  3242  	if expire != "0" {
  3243  		t.Error("ubuntu2 user password should be expired")
  3244  	}
  3245  }
  3246  
  3247  func testHelperCheckGrubConfig(t *testing.T, mountDir string) {
  3248  	t.Helper()
  3249  	grubCfg := filepath.Join(mountDir, "boot", "grub", "grub.cfg")
  3250  	_, err := os.Stat(grubCfg)
  3251  	if err != nil {
  3252  		if os.IsNotExist(err) {
  3253  			t.Errorf("File \"%s\" should exist, but does not", grubCfg)
  3254  		}
  3255  	}
  3256  }
  3257  
  3258  func testHelperCheckCleanedFiles(t *testing.T, mountDir string) {
  3259  	t.Helper()
  3260  	cleaned := []string{
  3261  		filepath.Join(mountDir, "var", "lib", "dbus", "machine-id"),
  3262  		filepath.Join(mountDir, "etc", "ssh", "ssh_host_rsa_key"),
  3263  		filepath.Join(mountDir, "etc", "ssh", "ssh_host_rsa_key.pub"),
  3264  		filepath.Join(mountDir, "etc", "ssh", "ssh_host_ecdsa_key"),
  3265  		filepath.Join(mountDir, "etc", "ssh", "ssh_host_ecdsa_key.pub"),
  3266  		filepath.Join(mountDir, "usr", "sbin", "policy-rc.d"),
  3267  		filepath.Join(mountDir, "sbin", "start-stop-daemon.REAL"),
  3268  		filepath.Join(mountDir, "sbin", "initctl.REAL"),
  3269  	}
  3270  	for _, file := range cleaned {
  3271  		_, err := os.Stat(file)
  3272  		if !os.IsNotExist(err) {
  3273  			t.Errorf("File %s should not exist, but does", file)
  3274  		}
  3275  	}
  3276  
  3277  	truncated := []string{
  3278  		filepath.Join(mountDir, "etc", "machine-id"),
  3279  	}
  3280  	for _, file := range truncated {
  3281  		fileInfo, err := os.Stat(file)
  3282  		if os.IsNotExist(err) {
  3283  			t.Errorf("File %s should exist, but does not", file)
  3284  		}
  3285  
  3286  		if fileInfo.Size() != 0 {
  3287  			t.Errorf("File %s should be empty, but it is not. Size: %v", file, fileInfo.Size())
  3288  		}
  3289  	}
  3290  }
  3291  
  3292  func testHelperCheckLocaleFile(t *testing.T, asserter *helper.Asserter, mountDir string) {
  3293  	t.Helper()
  3294  	localeFile := filepath.Join(mountDir, "etc", "default", "locale")
  3295  	localeBytes, err := os.ReadFile(localeFile)
  3296  	asserter.AssertErrNil(err, true)
  3297  	if !strings.Contains(string(localeBytes), "LANG=C.UTF-8") {
  3298  		t.Errorf("Expected LANG=C.UTF-8 in %s, but got %s", localeFile, string(localeBytes))
  3299  	}
  3300  }
  3301  
  3302  // testHelperCheckSourcesList checks if components and pocket correctly setup in /etc/apt/sources.list.d/ubuntu.sources
  3303  func testHelperCheckSourcesList(t *testing.T, asserter *helper.Asserter, mountDir string) {
  3304  	t.Helper()
  3305  	aptDeb822SourcesListBytes, err := os.ReadFile(filepath.Join(mountDir, "etc", "apt", "sources.list.d", "ubuntu.sources"))
  3306  	asserter.AssertErrNil(err, true)
  3307  	wantAptDeb822SourcesList := `## Ubuntu distribution repository
  3308  ##
  3309  ## The following settings can be adjusted to configure which packages to use from Ubuntu.
  3310  ## Mirror your choices (except for URIs and Suites) in the security section below to
  3311  ## ensure timely security updates.
  3312  ##
  3313  ## Types: Append deb-src to enable the fetching of source package.
  3314  ## URIs: A URL to the repository (you may add multiple URLs)
  3315  ## Suites: The following additional suites can be configured
  3316  ##   <name>-updates   - Major bug fix updates produced after the final release of the
  3317  ##                      distribution.
  3318  ##   <name>-backports - software from this repository may not have been tested as
  3319  ##                      extensively as that contained in the main release, although it includes
  3320  ##                      newer versions of some applications which may provide useful features.
  3321  ##                      Also, please note that software in backports WILL NOT receive any review
  3322  ##                      or updates from the Ubuntu security team.
  3323  ## Components: Aside from main, the following components can be added to the list
  3324  ##   restricted  - Software that may not be under a free license, or protected by patents.
  3325  ##   universe    - Community maintained packages. Software in this repository receives maintenance
  3326  ##                 from volunteers in the Ubuntu community, or a 10 year security maintenance
  3327  ##                 commitment from Canonical when an Ubuntu Pro subscription is attached.
  3328  ##   multiverse  - Community maintained of restricted. Software from this repository is
  3329  ##                 ENTIRELY UNSUPPORTED by the Ubuntu team, and may not be under a free
  3330  ##                 licence. Please satisfy yourself as to your rights to use the software.
  3331  ##                 Also, please note that software in multiverse WILL NOT receive any
  3332  ##                 review or updates from the Ubuntu security team.
  3333  ##
  3334  ## See the sources.list(5) manual page for further settings.
  3335  Types: deb
  3336  URIs: http://archive.ubuntu.com/ubuntu/
  3337  Suites: jammy jammy-updates jammy-proposed
  3338  Components: main universe restricted
  3339  Signed-By: /usr/share/keyrings/ubuntu-archive-keyring.gpg
  3340  
  3341  ## Ubuntu security updates. Aside from URIs and Suites,
  3342  ## this should mirror your choices in the previous section.
  3343  Types: deb
  3344  URIs: http://security.ubuntu.com/ubuntu/
  3345  Suites: jammy jammy-updates jammy-proposed
  3346  Components: main universe restricted
  3347  Signed-By: /usr/share/keyrings/ubuntu-archive-keyring.gpg
  3348  
  3349  `
  3350  	asserter.AssertEqual(wantAptDeb822SourcesList, string(aptDeb822SourcesListBytes))
  3351  
  3352  	// check if components and pocket correctly setup in /etc/apt/sources.list
  3353  	aptSourcesListBytes, err := os.ReadFile(filepath.Join(mountDir, "etc", "apt", "sources.list"))
  3354  	asserter.AssertErrNil(err, true)
  3355  	asserter.AssertEqual(imagedefinition.LegacySourcesListComment, string(aptSourcesListBytes))
  3356  }
  3357  
  3358  // TestSuccessfulClassicRunNoArtifact runs through a full classic state machine run without artifact
  3359  func TestSuccessfulClassicRunNoArtifact(t *testing.T) {
  3360  	if testing.Short() {
  3361  		t.Skip("skipping test in short mode.")
  3362  	}
  3363  
  3364  	asserter := helper.Asserter{T: t}
  3365  	restoreCWD := testhelper.SaveCWD()
  3366  	t.Cleanup(restoreCWD)
  3367  
  3368  	// We need the output directory set for this
  3369  	outputDir, err := os.MkdirTemp("/tmp", "ubuntu-image-")
  3370  	asserter.AssertErrNil(err, true)
  3371  	t.Cleanup(func() { os.RemoveAll(outputDir) })
  3372  
  3373  	var stateMachine ClassicStateMachine
  3374  	stateMachine.commonFlags, stateMachine.stateMachineFlags = helper.InitCommonOpts()
  3375  	stateMachine.parent = &stateMachine
  3376  	stateMachine.commonFlags.Debug = true
  3377  	stateMachine.commonFlags.Size = "5G"
  3378  	stateMachine.commonFlags.OutputDir = outputDir
  3379  	stateMachine.Args.ImageDefinition = filepath.Join("testdata", "image_definitions",
  3380  		"test_no_artifact.yaml")
  3381  
  3382  	err = stateMachine.Setup()
  3383  	asserter.AssertErrNil(err, true)
  3384  
  3385  	t.Cleanup(func() { os.RemoveAll(stateMachine.stateMachineFlags.WorkDir) })
  3386  
  3387  	err = stateMachine.Run()
  3388  	asserter.AssertErrNil(err, true)
  3389  
  3390  	t.Cleanup(func() {
  3391  		err = stateMachine.Teardown()
  3392  		asserter.AssertErrNil(err, true)
  3393  	})
  3394  
  3395  	// make sure packages were successfully installed from public and private ppas
  3396  	testHelperCheckPPAInstalled(t, &asserter, stateMachine.tempDirs.chroot)
  3397  
  3398  	// make sure snaps from the correct channel were installed
  3399  	testHelperCheckSnapInstalled(t, &asserter, stateMachine.tempDirs.chroot)
  3400  }
  3401  
  3402  func TestSuccessfulRootfsGeneration(t *testing.T) {
  3403  	if testing.Short() {
  3404  		t.Skip("skipping test in short mode.")
  3405  	}
  3406  	asserter := helper.Asserter{T: t}
  3407  	restoreCWD := testhelper.SaveCWD()
  3408  	t.Cleanup(restoreCWD)
  3409  
  3410  	// We need the output directory set for this
  3411  	outputDir, err := os.MkdirTemp("/tmp", "ubuntu-image-")
  3412  	asserter.AssertErrNil(err, true)
  3413  	t.Cleanup(func() { os.RemoveAll(outputDir) })
  3414  
  3415  	var stateMachine ClassicStateMachine
  3416  	stateMachine.commonFlags, stateMachine.stateMachineFlags = helper.InitCommonOpts()
  3417  	stateMachine.parent = &stateMachine
  3418  	stateMachine.commonFlags.Debug = true
  3419  	stateMachine.commonFlags.Size = "5G"
  3420  	stateMachine.commonFlags.OutputDir = outputDir
  3421  	stateMachine.Args.ImageDefinition = filepath.Join("testdata", "image_definitions",
  3422  		"test_rootfs_tarball.yaml")
  3423  
  3424  	err = stateMachine.Setup()
  3425  	asserter.AssertErrNil(err, true)
  3426  
  3427  	t.Cleanup(func() { os.RemoveAll(stateMachine.stateMachineFlags.WorkDir) })
  3428  
  3429  	err = stateMachine.Run()
  3430  	asserter.AssertErrNil(err, true)
  3431  
  3432  	t.Cleanup(func() {
  3433  		err = stateMachine.Teardown()
  3434  		asserter.AssertErrNil(err, true)
  3435  	})
  3436  
  3437  	// make sure all the artifacts were created and are the correct file types
  3438  	artifacts := map[string]string{
  3439  		"rootfs.tar": "tar archive",
  3440  	}
  3441  	for artifact, fileType := range artifacts {
  3442  		fullPath := filepath.Join(stateMachine.commonFlags.OutputDir, artifact)
  3443  		_, err := os.Stat(fullPath)
  3444  		if err != nil {
  3445  			if os.IsNotExist(err) {
  3446  				t.Errorf("File \"%s\" should exist, but does not", fullPath)
  3447  			}
  3448  		}
  3449  
  3450  		// check it is the expected file type
  3451  		fileCommand := *exec.Command("file", fullPath)
  3452  		cmdOutput, err := fileCommand.CombinedOutput()
  3453  		asserter.AssertErrNil(err, true)
  3454  		if !strings.Contains(string(cmdOutput), fileType) {
  3455  			t.Errorf("File \"%s\" is the wrong file type. Expected \"%s\" but got \"%s\"",
  3456  				fullPath, fileType, string(cmdOutput))
  3457  		}
  3458  	}
  3459  }
  3460  
  3461  // TestGerminate tests the germinate state and ensures some necessary packages are included
  3462  func TestGerminate(t *testing.T) {
  3463  	if testing.Short() {
  3464  		t.Skip("skipping test in short mode.")
  3465  	}
  3466  	testCases := []struct {
  3467  		name             string
  3468  		flavor           string
  3469  		seedURLs         []string
  3470  		seedNames        []string
  3471  		expectedPackages []string
  3472  		expectedSnaps    []string
  3473  		vcs              bool
  3474  	}{
  3475  		{
  3476  			"git",
  3477  			"ubuntu",
  3478  			[]string{"git://git.launchpad.net/~ubuntu-core-dev/ubuntu-seeds/+git/"},
  3479  			[]string{"server", "minimal", "standard", "cloud-image"},
  3480  			[]string{"python3", "sudo", "cloud-init", "ubuntu-server"},
  3481  			[]string{"lxd"},
  3482  			true,
  3483  		},
  3484  		{
  3485  			"http",
  3486  			"ubuntu",
  3487  			[]string{"https://people.canonical.com/~ubuntu-archive/seeds/"},
  3488  			[]string{"server", "minimal", "standard", "cloud-image"},
  3489  			[]string{"python3", "sudo", "cloud-init", "ubuntu-server"},
  3490  			[]string{"lxd"},
  3491  			false,
  3492  		},
  3493  		{
  3494  			"bzr+git",
  3495  			"ubuntu",
  3496  			[]string{"http://bazaar.launchpad.net/~ubuntu-mate-dev/ubuntu-seeds/",
  3497  				"git://git.launchpad.net/~ubuntu-core-dev/ubuntu-seeds/+git/",
  3498  				"https://people.canonical.com/~ubuntu-archive/seeds/",
  3499  			},
  3500  			[]string{"desktop", "desktop-common", "standard", "minimal"},
  3501  			[]string{"xorg", "wget", "ubuntu-minimal"},
  3502  			[]string{},
  3503  			true,
  3504  		},
  3505  	}
  3506  	for _, tc := range testCases {
  3507  		t.Run("test_germinate_"+tc.name, func(t *testing.T) {
  3508  			asserter := helper.Asserter{T: t}
  3509  			restoreCWD := testhelper.SaveCWD()
  3510  			defer restoreCWD()
  3511  
  3512  			var stateMachine ClassicStateMachine
  3513  			stateMachine.commonFlags, stateMachine.stateMachineFlags = helper.InitCommonOpts()
  3514  			stateMachine.parent = &stateMachine
  3515  
  3516  			err := stateMachine.makeTemporaryDirectories()
  3517  			asserter.AssertErrNil(err, true)
  3518  
  3519  			t.Cleanup(func() { os.RemoveAll(stateMachine.stateMachineFlags.WorkDir) })
  3520  
  3521  			hostArch := getHostArch()
  3522  			hostSuite := getHostSuite()
  3523  			imageDef := imagedefinition.ImageDefinition{
  3524  				Architecture: hostArch,
  3525  				Series:       hostSuite,
  3526  				Rootfs: &imagedefinition.Rootfs{
  3527  					Flavor: tc.flavor,
  3528  					Mirror: "http://archive.ubuntu.com/ubuntu/",
  3529  					Seed: &imagedefinition.Seed{
  3530  						SeedURLs:   tc.seedURLs,
  3531  						SeedBranch: hostSuite,
  3532  						Names:      tc.seedNames,
  3533  						Vcs:        helper.BoolPtr(tc.vcs),
  3534  					},
  3535  				},
  3536  			}
  3537  
  3538  			stateMachine.ImageDef = imageDef
  3539  
  3540  			err = stateMachine.germinate()
  3541  			asserter.AssertErrNil(err, true)
  3542  
  3543  			// spot check some packages that should remain seeded for a long time
  3544  			testHelperCheckGerminatedPackages(t, tc.expectedPackages, stateMachine.Packages)
  3545  			// spot check some snaps that should remain seeded for a long time
  3546  			testHelperCheckGerminatedSnaps(t, tc.expectedSnaps, stateMachine.Snaps)
  3547  		})
  3548  	}
  3549  }
  3550  
  3551  func testHelperCheckGerminatedPackages(t *testing.T, expectedPackages []string, gotPackages []string) {
  3552  	for _, expectedPackage := range expectedPackages {
  3553  		found := false
  3554  		for _, seedPackage := range gotPackages {
  3555  			if expectedPackage == seedPackage {
  3556  				found = true
  3557  			}
  3558  		}
  3559  		if !found {
  3560  			t.Errorf("Expected to find %s in list of packages: %v",
  3561  				expectedPackage, gotPackages)
  3562  		}
  3563  	}
  3564  }
  3565  
  3566  func testHelperCheckGerminatedSnaps(t *testing.T, expectedSnaps []string, gotSnaps []string) {
  3567  	for _, expectedSnap := range expectedSnaps {
  3568  		found := false
  3569  		for _, seedSnap := range gotSnaps {
  3570  			snapName := strings.Split(seedSnap, "=")[0]
  3571  			if expectedSnap == snapName {
  3572  				found = true
  3573  			}
  3574  		}
  3575  		if !found {
  3576  			t.Errorf("Expected to find %s in list of snaps: %v",
  3577  				expectedSnap, gotSnaps)
  3578  		}
  3579  	}
  3580  }
  3581  
  3582  // TestFailedGerminate mocks function calls to test
  3583  // failure cases in the germinate state
  3584  func TestFailedGerminate(t *testing.T) {
  3585  	if testing.Short() {
  3586  		t.Skip("skipping test in short mode.")
  3587  	}
  3588  	asserter := helper.Asserter{T: t}
  3589  	restoreCWD := testhelper.SaveCWD()
  3590  	defer restoreCWD()
  3591  
  3592  	var stateMachine ClassicStateMachine
  3593  	stateMachine.commonFlags, stateMachine.stateMachineFlags = helper.InitCommonOpts()
  3594  	stateMachine.parent = &stateMachine
  3595  
  3596  	err := stateMachine.makeTemporaryDirectories()
  3597  	asserter.AssertErrNil(err, true)
  3598  
  3599  	// create a valid imageDefinition
  3600  	hostArch := getHostArch()
  3601  	hostSuite := getHostSuite()
  3602  	imageDef := imagedefinition.ImageDefinition{
  3603  		Architecture: hostArch,
  3604  		Series:       hostSuite,
  3605  		Rootfs: &imagedefinition.Rootfs{
  3606  			Flavor: "ubuntu",
  3607  			Mirror: "http://archive.ubuntu.com/ubuntu/",
  3608  			Seed: &imagedefinition.Seed{
  3609  				SeedURLs:   []string{"git://git.launchpad.net/~ubuntu-core-dev/ubuntu-seeds/+git/"},
  3610  				SeedBranch: hostSuite,
  3611  				Names:      []string{"server", "minimal", "standard", "cloud-image"},
  3612  				Vcs:        helper.BoolPtr(true),
  3613  			},
  3614  		},
  3615  	}
  3616  	stateMachine.ImageDef = imageDef
  3617  
  3618  	// mock os.Mkdir
  3619  	osMkdir = mockMkdir
  3620  	t.Cleanup(func() {
  3621  		osMkdir = os.Mkdir
  3622  	})
  3623  	err = stateMachine.germinate()
  3624  	asserter.AssertErrContains(err, "Error creating germinate directory")
  3625  	osMkdir = os.Mkdir
  3626  
  3627  	// Setup the exec.Command mock
  3628  	testCaseName = "TestFailedGerminate"
  3629  	execCommand = fakeExecCommand
  3630  	t.Cleanup(func() {
  3631  		execCommand = exec.Command
  3632  	})
  3633  	err = stateMachine.germinate()
  3634  	asserter.AssertErrContains(err, "Error running germinate command")
  3635  	execCommand = exec.Command
  3636  
  3637  	// mock os.Open
  3638  	osOpen = mockOpen
  3639  	t.Cleanup(func() {
  3640  		osOpen = os.Open
  3641  	})
  3642  	err = stateMachine.germinate()
  3643  	asserter.AssertErrContains(err, "Error opening seed file")
  3644  	osOpen = os.Open
  3645  
  3646  	os.RemoveAll(stateMachine.stateMachineFlags.WorkDir)
  3647  }
  3648  
  3649  // TestBuildGadgetTreeGit tests the successful build of a gadget tree
  3650  func TestBuildGadgetTreeGit(t *testing.T) {
  3651  	t.Parallel()
  3652  	if testing.Short() {
  3653  		t.Skip("skipping test in short mode.")
  3654  	}
  3655  	asserter := helper.Asserter{T: t}
  3656  	restoreCWD := testhelper.SaveCWD()
  3657  	defer restoreCWD()
  3658  
  3659  	var stateMachine ClassicStateMachine
  3660  	stateMachine.commonFlags, stateMachine.stateMachineFlags = helper.InitCommonOpts()
  3661  	stateMachine.parent = &stateMachine
  3662  
  3663  	err := stateMachine.makeTemporaryDirectories()
  3664  	asserter.AssertErrNil(err, true)
  3665  
  3666  	t.Cleanup(func() { os.RemoveAll(stateMachine.stateMachineFlags.WorkDir) })
  3667  
  3668  	// test the directory method
  3669  	d, err := os.Getwd()
  3670  	asserter.AssertErrNil(err, true)
  3671  	sourcePath := filepath.Join(d, "testdata", "gadget_source")
  3672  	sourcePath = "file://" + sourcePath
  3673  	imageDef := imagedefinition.ImageDefinition{
  3674  		Architecture: getHostArch(),
  3675  		Series:       getHostSuite(),
  3676  		Gadget: &imagedefinition.Gadget{
  3677  			GadgetURL:  sourcePath,
  3678  			GadgetType: "directory",
  3679  		},
  3680  	}
  3681  
  3682  	stateMachine.ImageDef = imageDef
  3683  
  3684  	err = stateMachine.buildGadgetTree()
  3685  	asserter.AssertErrNil(err, true)
  3686  
  3687  	// test the git method
  3688  	imageDef = imagedefinition.ImageDefinition{
  3689  		Architecture: getHostArch(),
  3690  		Series:       getHostSuite(),
  3691  		Gadget: &imagedefinition.Gadget{
  3692  			GadgetURL:    "https://github.com/snapcore/pc-gadget",
  3693  			GadgetType:   "git",
  3694  			GadgetBranch: "classic",
  3695  		},
  3696  	}
  3697  
  3698  	stateMachine.ImageDef = imageDef
  3699  
  3700  	err = stateMachine.buildGadgetTree()
  3701  	asserter.AssertErrNil(err, true)
  3702  }
  3703  
  3704  // TestBuildGadgetTreeDirectory tests the successful build of a gadget tree
  3705  func TestBuildGadgetTreeDirectory(t *testing.T) {
  3706  	t.Parallel()
  3707  	if testing.Short() {
  3708  		t.Skip("skipping test in short mode.")
  3709  	}
  3710  	asserter := helper.Asserter{T: t}
  3711  	saveCWD := testhelper.SaveCWD()
  3712  	defer saveCWD()
  3713  
  3714  	var stateMachine ClassicStateMachine
  3715  	stateMachine.commonFlags, stateMachine.stateMachineFlags = helper.InitCommonOpts()
  3716  	stateMachine.parent = &stateMachine
  3717  
  3718  	// need workdir set up for this
  3719  	err := stateMachine.makeTemporaryDirectories()
  3720  	asserter.AssertErrNil(err, true)
  3721  	t.Cleanup(func() { os.RemoveAll(stateMachine.stateMachineFlags.WorkDir) })
  3722  
  3723  	// git clone the gadget into a /tmp dir
  3724  	gadgetDir, err := os.MkdirTemp("", "pc-gadget-")
  3725  	asserter.AssertErrNil(err, true)
  3726  	t.Cleanup(func() { os.RemoveAll(gadgetDir) })
  3727  	gitCloneCommand := *exec.Command(
  3728  		"git",
  3729  		"clone",
  3730  		"--depth",
  3731  		"1",
  3732  		"--branch",
  3733  		"classic",
  3734  		"https://github.com/snapcore/pc-gadget",
  3735  		gadgetDir,
  3736  	)
  3737  	err = gitCloneCommand.Run()
  3738  	asserter.AssertErrNil(err, true)
  3739  
  3740  	// now set up the image definition to build from this directory
  3741  	stateMachine.ImageDef = imagedefinition.ImageDefinition{
  3742  		Architecture: getHostArch(),
  3743  		Series:       getHostSuite(),
  3744  		Gadget: &imagedefinition.Gadget{
  3745  			GadgetURL:  fmt.Sprintf("file://%s", gadgetDir),
  3746  			GadgetType: "directory",
  3747  		},
  3748  	}
  3749  
  3750  	err = stateMachine.buildGadgetTree()
  3751  	asserter.AssertErrNil(err, true)
  3752  
  3753  	// now make sure the gadget.yaml is in the expected location
  3754  	// this was a bug reported by the CPC team
  3755  	err = stateMachine.prepareGadgetTree()
  3756  	asserter.AssertErrNil(err, true)
  3757  	err = stateMachine.loadGadgetYaml()
  3758  	asserter.AssertErrNil(err, true)
  3759  }
  3760  
  3761  func TestStateMachine_buildGadgetTree_paths(t *testing.T) {
  3762  	if testing.Short() {
  3763  		t.Skip("skipping test in short mode.")
  3764  	}
  3765  	asserter := helper.Asserter{T: t}
  3766  	// git clone the gadget into a /tmp dir
  3767  	originGadgetDir, err := os.MkdirTemp("", "pc-gadget-")
  3768  	asserter.AssertErrNil(err, true)
  3769  	t.Cleanup(func() {
  3770  		err = os.RemoveAll(originGadgetDir)
  3771  		if err != nil {
  3772  			t.Error(err)
  3773  		}
  3774  	})
  3775  	gitCloneCommand := *exec.Command(
  3776  		"git",
  3777  		"clone",
  3778  		"--depth",
  3779  		"1",
  3780  		"--branch",
  3781  		"classic",
  3782  		"https://github.com/snapcore/pc-gadget",
  3783  		originGadgetDir,
  3784  	)
  3785  	err = gitCloneCommand.Run()
  3786  	asserter.AssertErrNil(err, true)
  3787  
  3788  	tmpDir, err := os.MkdirTemp("", "")
  3789  	t.Cleanup(func() {
  3790  		err := osRemoveAll(tmpDir)
  3791  		if err != nil {
  3792  			t.Error(err)
  3793  		}
  3794  	})
  3795  
  3796  	testCases := []struct {
  3797  		name      string
  3798  		gadgetDir string
  3799  	}{
  3800  		{
  3801  			name:      "gadget URL poiting to an absolute dir",
  3802  			gadgetDir: originGadgetDir,
  3803  		},
  3804  		{
  3805  			name:      "gadget URL pointing to an absolute sub dir",
  3806  			gadgetDir: filepath.Join(tmpDir, "a", "b"),
  3807  		},
  3808  		{
  3809  			name:      "gadget URL pointing to a relative sub dir",
  3810  			gadgetDir: filepath.Join("a", "b"),
  3811  		},
  3812  	}
  3813  
  3814  	for _, tc := range testCases {
  3815  		t.Run("test_build_gadget_tree_paths_"+tc.name, func(t *testing.T) {
  3816  			asserter := helper.Asserter{T: t}
  3817  			restoreCWD := testhelper.SaveCWD()
  3818  			defer restoreCWD()
  3819  
  3820  			var stateMachine ClassicStateMachine
  3821  			stateMachine.commonFlags, stateMachine.stateMachineFlags = helper.InitCommonOpts()
  3822  			stateMachine.parent = &stateMachine
  3823  
  3824  			err := stateMachine.makeTemporaryDirectories()
  3825  			asserter.AssertErrNil(err, true)
  3826  			t.Cleanup(func() {
  3827  				err := os.RemoveAll(stateMachine.stateMachineFlags.WorkDir)
  3828  				if err != nil {
  3829  					t.Error(err)
  3830  				}
  3831  			})
  3832  
  3833  			// move the original gadget dir to the desire location to test it will be found
  3834  			if originGadgetDir != tc.gadgetDir {
  3835  				fullGadgetDir := tc.gadgetDir
  3836  				if !filepath.IsAbs(tc.gadgetDir) {
  3837  					fullGadgetDir = filepath.Join(tmpDir, tc.gadgetDir)
  3838  				}
  3839  
  3840  				err = os.MkdirAll(filepath.Dir(fullGadgetDir), 0777)
  3841  				asserter.AssertErrNil(err, true)
  3842  
  3843  				err = os.Rename(originGadgetDir, fullGadgetDir)
  3844  				asserter.AssertErrNil(err, true)
  3845  				// move it back once the test is done
  3846  				t.Cleanup(func() {
  3847  					err := os.Rename(fullGadgetDir, originGadgetDir)
  3848  					if err != nil {
  3849  						t.Error(err)
  3850  					}
  3851  				})
  3852  			}
  3853  
  3854  			// now set up the image definition to build from this directory
  3855  			stateMachine.ImageDef = imagedefinition.ImageDefinition{
  3856  				Architecture: getHostArch(),
  3857  				Series:       getHostSuite(),
  3858  				Gadget: &imagedefinition.Gadget{
  3859  					GadgetURL:  fmt.Sprintf("file://%s", tc.gadgetDir),
  3860  					GadgetType: "directory",
  3861  				},
  3862  			}
  3863  
  3864  			err = stateMachine.setConfDefDir(filepath.Join(tmpDir, "image_definition.yaml"))
  3865  			asserter.AssertErrNil(err, true)
  3866  
  3867  			err = stateMachine.buildGadgetTree()
  3868  			asserter.AssertErrNil(err, true)
  3869  
  3870  			// now make sure the gadget.yaml is in the expected location
  3871  			// this was a bug reported by the CPC team
  3872  			err = stateMachine.prepareGadgetTree()
  3873  			asserter.AssertErrNil(err, true)
  3874  			err = stateMachine.loadGadgetYaml()
  3875  			asserter.AssertErrNil(err, true)
  3876  		})
  3877  	}
  3878  }
  3879  
  3880  // TestGadgetGadgetTargets tests using alternate make targets with gadget builds
  3881  func TestGadgetGadgetTargets(t *testing.T) {
  3882  	testCases := []struct {
  3883  		name           string
  3884  		target         string
  3885  		expectedOutput string
  3886  	}{
  3887  		{
  3888  			"default",
  3889  			"",
  3890  			"make target test1",
  3891  		},
  3892  		{
  3893  			"test2",
  3894  			"test2",
  3895  			"make target test2",
  3896  		},
  3897  	}
  3898  	for _, tc := range testCases {
  3899  		t.Run("test_gadget_make_targets_"+tc.name, func(t *testing.T) {
  3900  			asserter := helper.Asserter{T: t}
  3901  			restoreCWD := testhelper.SaveCWD()
  3902  			defer restoreCWD()
  3903  
  3904  			var stateMachine ClassicStateMachine
  3905  			stateMachine.commonFlags, stateMachine.stateMachineFlags = helper.InitCommonOpts()
  3906  			stateMachine.parent = &stateMachine
  3907  			stateMachine.commonFlags.Debug = true
  3908  
  3909  			err := stateMachine.makeTemporaryDirectories()
  3910  			asserter.AssertErrNil(err, true)
  3911  
  3912  			wd, err := os.Getwd()
  3913  			asserter.AssertErrNil(err, true)
  3914  			gadgetSrc := filepath.Join(wd, "testdata", "gadget_source")
  3915  			imageDef := imagedefinition.ImageDefinition{
  3916  				Architecture: getHostArch(),
  3917  				Series:       getHostSuite(),
  3918  				Gadget: &imagedefinition.Gadget{
  3919  					GadgetURL:    fmt.Sprintf("file://%s", gadgetSrc),
  3920  					GadgetType:   "directory",
  3921  					GadgetTarget: tc.target,
  3922  				},
  3923  			}
  3924  			stateMachine.ImageDef = imageDef
  3925  
  3926  			// capture stdout, build the gadget tree, and make
  3927  			// sure the expected output matches the make target
  3928  			stdout, restoreStdout, err := helper.CaptureStd(&os.Stdout)
  3929  			defer restoreStdout()
  3930  			asserter.AssertErrNil(err, true)
  3931  
  3932  			err = stateMachine.buildGadgetTree()
  3933  			asserter.AssertErrNil(err, true)
  3934  
  3935  			// restore stdout and examine what was printed
  3936  			restoreStdout()
  3937  			readStdout, err := io.ReadAll(stdout)
  3938  			asserter.AssertErrNil(err, true)
  3939  			if !strings.Contains(string(readStdout), tc.expectedOutput) {
  3940  				t.Errorf("Expected make output\n\"%s\"\nto contain the string \"%s\"",
  3941  					string(readStdout),
  3942  					tc.expectedOutput,
  3943  				)
  3944  			}
  3945  		})
  3946  	}
  3947  }
  3948  
  3949  // TestFailedBuildGadgetTree tests failures in the  buildGadgetTree function
  3950  func TestFailedBuildGadgetTree(t *testing.T) {
  3951  	asserter := helper.Asserter{T: t}
  3952  	restoreCWD := testhelper.SaveCWD()
  3953  	defer restoreCWD()
  3954  
  3955  	var stateMachine ClassicStateMachine
  3956  	stateMachine.commonFlags, stateMachine.stateMachineFlags = helper.InitCommonOpts()
  3957  	stateMachine.parent = &stateMachine
  3958  
  3959  	err := stateMachine.makeTemporaryDirectories()
  3960  	asserter.AssertErrNil(err, true)
  3961  
  3962  	// mock os.MkdirAll
  3963  	osMkdir = mockMkdir
  3964  	t.Cleanup(func() {
  3965  		osMkdir = os.Mkdir
  3966  	})
  3967  	err = stateMachine.buildGadgetTree()
  3968  	asserter.AssertErrContains(err, "Error creating scratch/gadget")
  3969  	osMkdir = os.Mkdir
  3970  
  3971  	// try to clone a repo that doesn't exist
  3972  	imageDef := imagedefinition.ImageDefinition{
  3973  		Architecture: getHostArch(),
  3974  		Series:       getHostSuite(),
  3975  		Gadget: &imagedefinition.Gadget{
  3976  			GadgetURL:  "http://fakerepo.git",
  3977  			GadgetType: "git",
  3978  		},
  3979  	}
  3980  	stateMachine.ImageDef = imageDef
  3981  
  3982  	err = stateMachine.buildGadgetTree()
  3983  	asserter.AssertErrContains(err, "Error cloning gadget repository")
  3984  
  3985  	// try to copy a file that doesn't exist
  3986  	imageDef = imagedefinition.ImageDefinition{
  3987  		Architecture: getHostArch(),
  3988  		Series:       getHostSuite(),
  3989  		Gadget: &imagedefinition.Gadget{
  3990  			GadgetURL:  "file:///fake/file/that/does/not/exist",
  3991  			GadgetType: "directory",
  3992  		},
  3993  	}
  3994  	stateMachine.ImageDef = imageDef
  3995  
  3996  	err = stateMachine.buildGadgetTree()
  3997  	asserter.AssertErrContains(err, "Error reading gadget tree")
  3998  
  3999  	// mock osutil.CopySpecialFile and run with /tmp as the gadget source
  4000  	imageDef = imagedefinition.ImageDefinition{
  4001  		Architecture: getHostArch(),
  4002  		Series:       getHostSuite(),
  4003  		Gadget: &imagedefinition.Gadget{
  4004  			GadgetURL:  "file:///tmp",
  4005  			GadgetType: "directory",
  4006  		},
  4007  	}
  4008  	stateMachine.ImageDef = imageDef
  4009  
  4010  	// mock osutil.CopySpecialFile
  4011  	osutilCopySpecialFile = mockCopySpecialFile
  4012  	t.Cleanup(func() {
  4013  		osutilCopySpecialFile = osutil.CopySpecialFile
  4014  	})
  4015  	err = stateMachine.buildGadgetTree()
  4016  	asserter.AssertErrContains(err, "Error copying gadget source")
  4017  	osutilCopySpecialFile = osutil.CopySpecialFile
  4018  
  4019  	// run a "make" command that will fail by mocking exec.Command
  4020  	testCaseName = "TestFailedBuildGadgetTree"
  4021  	execCommand = fakeExecCommand
  4022  	t.Cleanup(func() {
  4023  		execCommand = exec.Command
  4024  	})
  4025  	wd, err := os.Getwd()
  4026  	asserter.AssertErrNil(err, true)
  4027  	sourcePath := filepath.Join(wd, "testdata", "gadget_source")
  4028  	sourcePath = "file://" + sourcePath
  4029  	imageDef = imagedefinition.ImageDefinition{
  4030  		Architecture: getHostArch(),
  4031  		Series:       getHostSuite(),
  4032  		Gadget: &imagedefinition.Gadget{
  4033  			GadgetURL:  sourcePath,
  4034  			GadgetType: "directory",
  4035  		},
  4036  	}
  4037  	stateMachine.ImageDef = imageDef
  4038  
  4039  	err = stateMachine.buildGadgetTree()
  4040  	asserter.AssertErrContains(err, "Error running \"make\" in gadget source")
  4041  
  4042  	os.RemoveAll(stateMachine.stateMachineFlags.WorkDir)
  4043  }
  4044  
  4045  // TestCreateChroot runs the createChroot step and spot checks that some
  4046  // expected files in the chroot exist
  4047  func TestCreateChroot(t *testing.T) {
  4048  	if testing.Short() {
  4049  		t.Skip("skipping test in short mode.")
  4050  	}
  4051  	asserter := helper.Asserter{T: t}
  4052  	restoreCWD := testhelper.SaveCWD()
  4053  	defer restoreCWD()
  4054  
  4055  	var stateMachine ClassicStateMachine
  4056  	stateMachine.commonFlags, stateMachine.stateMachineFlags = helper.InitCommonOpts()
  4057  	stateMachine.parent = &stateMachine
  4058  	stateMachine.ImageDef = imagedefinition.ImageDefinition{
  4059  		Architecture: getHostArch(),
  4060  		Series:       getHostSuite(),
  4061  		Rootfs: &imagedefinition.Rootfs{
  4062  			Pocket:            "proposed",
  4063  			SourcesListDeb822: helper.BoolPtr(true),
  4064  		},
  4065  	}
  4066  
  4067  	err := helper.SetDefaults(&stateMachine.ImageDef)
  4068  	asserter.AssertErrNil(err, true)
  4069  
  4070  	err = stateMachine.makeTemporaryDirectories()
  4071  	asserter.AssertErrNil(err, true)
  4072  
  4073  	t.Cleanup(func() { os.RemoveAll(stateMachine.stateMachineFlags.WorkDir) })
  4074  
  4075  	err = stateMachine.createChroot()
  4076  	asserter.AssertErrNil(err, true)
  4077  
  4078  	expectedFiles := []string{
  4079  		"etc",
  4080  		"home",
  4081  		"boot",
  4082  		"var",
  4083  	}
  4084  	for _, expectedFile := range expectedFiles {
  4085  		fullPath := filepath.Join(stateMachine.tempDirs.chroot, expectedFile)
  4086  		_, err := os.Stat(fullPath)
  4087  		if err != nil {
  4088  			if os.IsNotExist(err) {
  4089  				t.Errorf("File \"%s\" should exist, but does not", fullPath)
  4090  			}
  4091  		}
  4092  	}
  4093  
  4094  	// check that the hostname is set correctly
  4095  	hostnameFile := filepath.Join(stateMachine.tempDirs.chroot, "etc", "hostname")
  4096  	hostnameData, err := os.ReadFile(hostnameFile)
  4097  	asserter.AssertErrNil(err, true)
  4098  	if string(hostnameData) != "ubuntu\n" {
  4099  		t.Errorf("Expected hostname to be \"ubuntu\", but is \"%s\"", string(hostnameData))
  4100  	}
  4101  
  4102  	// check that the resolv.conf file was truncated
  4103  	resolvConfFile := filepath.Join(stateMachine.tempDirs.chroot, "etc", "resolv.conf")
  4104  	resolvConfData, err := os.ReadFile(resolvConfFile)
  4105  	asserter.AssertErrNil(err, true)
  4106  	if string(resolvConfData) != "" {
  4107  		t.Errorf("Expected resolv.conf to be empty, but is \"%s\"", string(resolvConfData))
  4108  	}
  4109  
  4110  	// check if components and pocket correctly setup in /etc/apt/sources.list.d/ubuntu.sources
  4111  	aptDeb822SourcesListBytes, err := os.ReadFile(filepath.Join(stateMachine.tempDirs.chroot, "etc", "apt", "sources.list.d", "ubuntu.sources"))
  4112  	asserter.AssertErrNil(err, true)
  4113  	wantAptDeb822SourcesList := `## Ubuntu distribution repository
  4114  ##
  4115  ## The following settings can be adjusted to configure which packages to use from Ubuntu.
  4116  ## Mirror your choices (except for URIs and Suites) in the security section below to
  4117  ## ensure timely security updates.
  4118  ##
  4119  ## Types: Append deb-src to enable the fetching of source package.
  4120  ## URIs: A URL to the repository (you may add multiple URLs)
  4121  ## Suites: The following additional suites can be configured
  4122  ##   <name>-updates   - Major bug fix updates produced after the final release of the
  4123  ##                      distribution.
  4124  ##   <name>-backports - software from this repository may not have been tested as
  4125  ##                      extensively as that contained in the main release, although it includes
  4126  ##                      newer versions of some applications which may provide useful features.
  4127  ##                      Also, please note that software in backports WILL NOT receive any review
  4128  ##                      or updates from the Ubuntu security team.
  4129  ## Components: Aside from main, the following components can be added to the list
  4130  ##   restricted  - Software that may not be under a free license, or protected by patents.
  4131  ##   universe    - Community maintained packages. Software in this repository receives maintenance
  4132  ##                 from volunteers in the Ubuntu community, or a 10 year security maintenance
  4133  ##                 commitment from Canonical when an Ubuntu Pro subscription is attached.
  4134  ##   multiverse  - Community maintained of restricted. Software from this repository is
  4135  ##                 ENTIRELY UNSUPPORTED by the Ubuntu team, and may not be under a free
  4136  ##                 licence. Please satisfy yourself as to your rights to use the software.
  4137  ##                 Also, please note that software in multiverse WILL NOT receive any
  4138  ##                 review or updates from the Ubuntu security team.
  4139  ##
  4140  ## See the sources.list(5) manual page for further settings.
  4141  Types: deb
  4142  URIs: http://archive.ubuntu.com/ubuntu/
  4143  Suites: jammy jammy-updates jammy-proposed
  4144  Components: main restricted
  4145  Signed-By: /usr/share/keyrings/ubuntu-archive-keyring.gpg
  4146  
  4147  ## Ubuntu security updates. Aside from URIs and Suites,
  4148  ## this should mirror your choices in the previous section.
  4149  Types: deb
  4150  URIs: http://security.ubuntu.com/ubuntu/
  4151  Suites: jammy jammy-updates jammy-proposed
  4152  Components: main restricted
  4153  Signed-By: /usr/share/keyrings/ubuntu-archive-keyring.gpg
  4154  
  4155  `
  4156  	asserter.AssertEqual(wantAptDeb822SourcesList, string(aptDeb822SourcesListBytes))
  4157  
  4158  }
  4159  
  4160  // TestFailedCreateChroot tests failure cases in createChroot
  4161  func TestFailedCreateChroot(t *testing.T) {
  4162  	if testing.Short() {
  4163  		t.Skip("skipping test in short mode.")
  4164  	}
  4165  	asserter := helper.Asserter{T: t}
  4166  	restoreCWD := testhelper.SaveCWD()
  4167  	defer restoreCWD()
  4168  
  4169  	var stateMachine ClassicStateMachine
  4170  	stateMachine.commonFlags, stateMachine.stateMachineFlags = helper.InitCommonOpts()
  4171  	stateMachine.parent = &stateMachine
  4172  	stateMachine.ImageDef = imagedefinition.ImageDefinition{
  4173  		Architecture: getHostArch(),
  4174  		Series:       getHostSuite(),
  4175  		Rootfs: &imagedefinition.Rootfs{
  4176  			SourcesListDeb822: helper.BoolPtr(false),
  4177  		},
  4178  	}
  4179  
  4180  	err := stateMachine.makeTemporaryDirectories()
  4181  	asserter.AssertErrNil(err, true)
  4182  
  4183  	// mock os.Mkdir
  4184  	osMkdir = mockMkdir
  4185  	t.Cleanup(func() {
  4186  		osMkdir = os.Mkdir
  4187  	})
  4188  	err = stateMachine.createChroot()
  4189  	asserter.AssertErrContains(err, "Failed to create chroot")
  4190  	osMkdir = os.Mkdir
  4191  
  4192  	// Setup the exec.Command mock
  4193  	testCaseName = "TestFailedCreateChroot"
  4194  	execCommand = fakeExecCommand
  4195  	t.Cleanup(func() {
  4196  		execCommand = exec.Command
  4197  	})
  4198  	err = stateMachine.createChroot()
  4199  	asserter.AssertErrContains(err, "Error running debootstrap command")
  4200  	execCommand = exec.Command
  4201  
  4202  	// Check if failure of open hostname file is detected
  4203  
  4204  	os.RemoveAll(stateMachine.stateMachineFlags.WorkDir)
  4205  	err = stateMachine.makeTemporaryDirectories()
  4206  	asserter.AssertErrNil(err, true)
  4207  
  4208  	// Prepare a fallthrough debootstrap
  4209  	testCaseName = "TestFailedCreateChrootNoHostname"
  4210  	execCommand = fakeExecCommand
  4211  	t.Cleanup(func() {
  4212  		execCommand = exec.Command
  4213  	})
  4214  	osOpenFile = mockOpenFile
  4215  	t.Cleanup(func() {
  4216  		osOpenFile = os.OpenFile
  4217  	})
  4218  
  4219  	err = stateMachine.createChroot()
  4220  	asserter.AssertErrContains(err, "unable to open hostname file")
  4221  
  4222  	osOpenFile = os.OpenFile
  4223  	execCommand = exec.Command
  4224  
  4225  	// Check if failure of truncation is detected
  4226  
  4227  	// Clean the work directory
  4228  	os.RemoveAll(stateMachine.stateMachineFlags.WorkDir)
  4229  	err = stateMachine.makeTemporaryDirectories()
  4230  	asserter.AssertErrNil(err, true)
  4231  
  4232  	// Prepare a fallthrough debootstrap
  4233  	testCaseName = "TestFailedCreateChrootSkip"
  4234  	osTruncate = mockTruncate
  4235  	t.Cleanup(func() {
  4236  		osTruncate = os.Truncate
  4237  	})
  4238  	err = stateMachine.createChroot()
  4239  	asserter.AssertErrContains(err, "Error truncating resolv.conf")
  4240  	osTruncate = os.Truncate
  4241  	execCommand = exec.Command
  4242  
  4243  	os.RemoveAll(stateMachine.stateMachineFlags.WorkDir)
  4244  }
  4245  
  4246  // TestStateMachine_installPackages_checkcmds checks commands to install packages order is ok
  4247  func TestStateMachine_installPackages_checkcmds(t *testing.T) {
  4248  	asserter := helper.Asserter{T: t}
  4249  	var stateMachine ClassicStateMachine
  4250  	stateMachine.commonFlags, stateMachine.stateMachineFlags = helper.InitCommonOpts()
  4251  	stateMachine.commonFlags.Debug = true
  4252  	stateMachine.parent = &stateMachine
  4253  	stateMachine.commonFlags.OutputDir = "/tmp"
  4254  
  4255  	err := stateMachine.makeTemporaryDirectories()
  4256  	asserter.AssertErrNil(err, true)
  4257  	err = os.MkdirAll(stateMachine.tempDirs.chroot, 0755)
  4258  	asserter.AssertErrNil(err, true)
  4259  
  4260  	t.Cleanup(func() { os.RemoveAll(stateMachine.stateMachineFlags.WorkDir) })
  4261  
  4262  	// create an /usr/sbin/policy-rc.d in the chroot
  4263  	err = os.MkdirAll(filepath.Join(stateMachine.tempDirs.chroot, "usr", "sbin"), 0755)
  4264  	asserter.AssertErrNil(err, true)
  4265  	_, err = os.Create(filepath.Join(stateMachine.tempDirs.chroot, "usr", "sbin", "policy-rc.d"))
  4266  	asserter.AssertErrNil(err, true)
  4267  
  4268  	// create an /sbin/start-stop-daemon in the chroot
  4269  	err = os.MkdirAll(filepath.Join(stateMachine.tempDirs.chroot, "sbin"), 0755)
  4270  	asserter.AssertErrNil(err, true)
  4271  	_, err = os.Create(filepath.Join(stateMachine.tempDirs.chroot, "sbin", "start-stop-daemon"))
  4272  	asserter.AssertErrNil(err, true)
  4273  
  4274  	// create an /sbin/initctl in the chroot
  4275  	_, err = os.Create(filepath.Join(stateMachine.tempDirs.chroot, "sbin", "initctl"))
  4276  	asserter.AssertErrNil(err, true)
  4277  
  4278  	mockCmder := NewMockExecCommand()
  4279  
  4280  	execCommand = mockCmder.Command
  4281  	t.Cleanup(func() { execCommand = exec.Command })
  4282  
  4283  	stdout, restoreStdout, err := helper.CaptureStd(&os.Stdout)
  4284  	asserter.AssertErrNil(err, true)
  4285  	t.Cleanup(func() { restoreStdout() })
  4286  
  4287  	helperBackupAndCopyResolvConf = mockBackupAndCopyResolvConfSuccess
  4288  	t.Cleanup(func() {
  4289  		helperBackupAndCopyResolvConf = helper.BackupAndCopyResolvConf
  4290  	})
  4291  
  4292  	err = stateMachine.installPackages()
  4293  	asserter.AssertErrNil(err, true)
  4294  
  4295  	restoreStdout()
  4296  	readStdout, err := io.ReadAll(stdout)
  4297  	asserter.AssertErrNil(err, true)
  4298  
  4299  	expectedCmds := []*regexp.Regexp{
  4300  		regexp.MustCompile("^mount -t devtmpfs devtmpfs-build /tmp.*/chroot/dev$"),
  4301  		regexp.MustCompile("^mount -t devpts devpts-build -o nodev,nosuid /tmp.*/chroot/dev/pts$"),
  4302  		regexp.MustCompile("^mount -t proc proc-build /tmp.*/chroot/proc$"),
  4303  		regexp.MustCompile("^mount -t sysfs sysfs-build /tmp.*/chroot/sys$"),
  4304  		regexp.MustCompile("^mount --bind .*/scratch/run.* .*/chroot/run$"),
  4305  		regexp.MustCompile("^chroot /tmp.*/chroot dpkg-divert"),
  4306  		regexp.MustCompile("^chroot /tmp.*/chroot apt update$"),
  4307  		regexp.MustCompile("^chroot /tmp.*/chroot apt install --assume-yes --quiet --option=Dpkg::options::=--force-unsafe-io --option=Dpkg::Options::=--force-confold$"),
  4308  		regexp.MustCompile("^chroot /tmp.*/chroot dpkg-divert --remove"),
  4309  		regexp.MustCompile("^udevadm settle$"),
  4310  		regexp.MustCompile("^mount --make-rprivate /tmp.*/chroot/run$"),
  4311  		regexp.MustCompile("^umount --recursive /tmp.*/chroot/run$"),
  4312  		regexp.MustCompile("^mount --make-rprivate /tmp.*/chroot/sys$"),
  4313  		regexp.MustCompile("^umount --recursive /tmp.*/chroot/sys$"),
  4314  		regexp.MustCompile("^mount --make-rprivate /tmp.*/chroot/proc$"),
  4315  		regexp.MustCompile("^umount --recursive /tmp.*/chroot/proc$"),
  4316  		regexp.MustCompile("^mount --make-rprivate /tmp.*/chroot/dev/pts$"),
  4317  		regexp.MustCompile("^umount --recursive /tmp.*/chroot/dev/pts$"),
  4318  		regexp.MustCompile("^mount --make-rprivate /tmp.*/chroot/dev$"),
  4319  		regexp.MustCompile("^umount --recursive /tmp.*/chroot/dev$"),
  4320  	}
  4321  
  4322  	gotCmds := strings.Split(strings.TrimSpace(string(readStdout)), "\n")
  4323  	if len(expectedCmds) != len(gotCmds) {
  4324  		t.Fatalf("%v commands to be executed, expected %v commands. Got: %v", len(gotCmds), len(expectedCmds), gotCmds)
  4325  	}
  4326  
  4327  	for i, gotCmd := range gotCmds {
  4328  		expected := expectedCmds[i]
  4329  
  4330  		if !expected.Match([]byte(gotCmd)) {
  4331  			t.Errorf("Cmd \"%v\" not matching. Expected %v\n", gotCmd, expected.String())
  4332  		}
  4333  	}
  4334  }
  4335  
  4336  // TestStateMachine_installPackages_checkcmds checks commands to install packages order is ok when failing
  4337  func TestStateMachine_installPackages_checkcmds_failing(t *testing.T) {
  4338  	asserter := helper.Asserter{T: t}
  4339  	var stateMachine ClassicStateMachine
  4340  	stateMachine.commonFlags, stateMachine.stateMachineFlags = helper.InitCommonOpts()
  4341  	stateMachine.commonFlags.Debug = true
  4342  	stateMachine.parent = &stateMachine
  4343  	stateMachine.commonFlags.OutputDir = "/tmp"
  4344  
  4345  	err := stateMachine.makeTemporaryDirectories()
  4346  	asserter.AssertErrNil(err, true)
  4347  
  4348  	t.Cleanup(func() { os.RemoveAll(stateMachine.stateMachineFlags.WorkDir) })
  4349  
  4350  	mockCmder := NewMockExecCommand()
  4351  
  4352  	execCommand = mockCmder.Command
  4353  	t.Cleanup(func() { execCommand = exec.Command })
  4354  
  4355  	stdout, restoreStdout, err := helper.CaptureStd(&os.Stdout)
  4356  	asserter.AssertErrNil(err, true)
  4357  	t.Cleanup(func() { restoreStdout() })
  4358  
  4359  	helperBackupAndCopyResolvConf = mockBackupAndCopyResolvConfSuccess
  4360  	t.Cleanup(func() {
  4361  		helperBackupAndCopyResolvConf = helper.BackupAndCopyResolvConf
  4362  	})
  4363  
  4364  	osMkdirTemp = mockMkdirTemp
  4365  	t.Cleanup(func() {
  4366  		osMkdirTemp = os.MkdirTemp
  4367  	})
  4368  
  4369  	err = stateMachine.installPackages()
  4370  	asserter.AssertErrContains(err, "Test error")
  4371  
  4372  	restoreStdout()
  4373  	readStdout, err := io.ReadAll(stdout)
  4374  	asserter.AssertErrNil(err, true)
  4375  
  4376  	gotCmds := strings.Split(strings.TrimSpace(string(readStdout)), "\n")
  4377  	// Clean empty commands
  4378  	for i, cmd := range gotCmds {
  4379  		if len(cmd) == 0 {
  4380  			copy(gotCmds[i:], gotCmds[i+1:])
  4381  			gotCmds[len(gotCmds)-1] = ""
  4382  			gotCmds = gotCmds[:len(gotCmds)-1]
  4383  		}
  4384  	}
  4385  
  4386  	if len(gotCmds) != 0 {
  4387  		t.Fatalf("%v commands to be executed, expected no commands. Got: %v", len(gotCmds), gotCmds)
  4388  	}
  4389  }
  4390  
  4391  // TestStateMachine_installPackages_fail tests failure cases in installPackages
  4392  func TestStateMachine_installPackages_fail(t *testing.T) {
  4393  	asserter := helper.Asserter{T: t}
  4394  	restoreCWD := testhelper.SaveCWD()
  4395  	defer restoreCWD()
  4396  
  4397  	var stateMachine ClassicStateMachine
  4398  	stateMachine.commonFlags, stateMachine.stateMachineFlags = helper.InitCommonOpts()
  4399  	stateMachine.parent = &stateMachine
  4400  	stateMachine.ImageDef = imagedefinition.ImageDefinition{
  4401  		Architecture: getHostArch(),
  4402  		Series:       getHostSuite(),
  4403  		Rootfs:       &imagedefinition.Rootfs{},
  4404  		Customization: &imagedefinition.Customization{
  4405  			ExtraPackages: []*imagedefinition.Package{
  4406  				{
  4407  					PackageName: "test1",
  4408  				},
  4409  			},
  4410  		},
  4411  	}
  4412  
  4413  	err := stateMachine.makeTemporaryDirectories()
  4414  	asserter.AssertErrNil(err, true)
  4415  
  4416  	t.Cleanup(func() { os.RemoveAll(stateMachine.stateMachineFlags.WorkDir) })
  4417  
  4418  	// create an /etc/resolv.conf in the chroot
  4419  	err = os.MkdirAll(filepath.Join(stateMachine.tempDirs.chroot, "etc"), 0755)
  4420  	asserter.AssertErrNil(err, true)
  4421  	_, err = os.Create(filepath.Join(stateMachine.tempDirs.chroot, "etc", "resolv.conf"))
  4422  	asserter.AssertErrNil(err, true)
  4423  
  4424  	osMkdirTemp = mockMkdirTemp
  4425  	t.Cleanup(func() {
  4426  		osMkdirTemp = os.MkdirTemp
  4427  	})
  4428  	err = stateMachine.installPackages()
  4429  	asserter.AssertErrContains(err, "Error making temporary directory for mountpoint")
  4430  	osMkdirTemp = os.MkdirTemp
  4431  
  4432  	// Setup the exec.Command mock
  4433  	testCaseName = "TestStateMachine_installPackages_fail"
  4434  	execCommand = fakeExecCommand
  4435  	t.Cleanup(func() {
  4436  		execCommand = exec.Command
  4437  	})
  4438  	err = stateMachine.installPackages()
  4439  	asserter.AssertErrContains(err, "Error running command")
  4440  	execCommand = exec.Command
  4441  
  4442  	// delete the backed up resolv.conf to trigger another backup
  4443  	err = os.Remove(filepath.Join(stateMachine.tempDirs.chroot, "etc", "resolv.conf.tmp"))
  4444  	asserter.AssertErrNil(err, true)
  4445  	// mock helper.BackupAndCopyResolvConf
  4446  	helperBackupAndCopyResolvConf = mockBackupAndCopyResolvConfFail
  4447  	t.Cleanup(func() {
  4448  		helperBackupAndCopyResolvConf = helper.BackupAndCopyResolvConf
  4449  	})
  4450  	err = stateMachine.installPackages()
  4451  	asserter.AssertErrContains(err, "Error setting up /etc/resolv.conf")
  4452  	helperBackupAndCopyResolvConf = helper.BackupAndCopyResolvConf
  4453  
  4454  	osMkdirAll = mockMkdirAll
  4455  	t.Cleanup(func() {
  4456  		osMkdirAll = os.MkdirAll
  4457  	})
  4458  	err = stateMachine.installPackages()
  4459  	asserter.AssertErrContains(err, "Error creating policy-rc.d dir")
  4460  	osMkdirAll = os.MkdirAll
  4461  
  4462  	osWriteFile = mockWriteFile
  4463  	t.Cleanup(func() {
  4464  		osWriteFile = os.WriteFile
  4465  	})
  4466  	err = stateMachine.installPackages()
  4467  	asserter.AssertErrContains(err, "Error writing to policy-rc.d")
  4468  	osWriteFile = os.WriteFile
  4469  
  4470  	osRename = mockRename
  4471  	t.Cleanup(func() {
  4472  		osRename = os.Rename
  4473  	})
  4474  	err = stateMachine.installPackages()
  4475  	asserter.AssertErrContains(err, "Error moving file ")
  4476  	osRename = os.Rename
  4477  
  4478  }
  4479  
  4480  // Test_generateMountPointCmds_fail tests when generateMountPointCmds fails
  4481  func Test_generateMountPointCmds_fail(t *testing.T) {
  4482  	asserter := helper.Asserter{T: t}
  4483  
  4484  	tmpDirPath := filepath.Join("/tmp", "test_failed_set_conf_dir")
  4485  	err := os.Mkdir(tmpDirPath, 0755)
  4486  	t.Cleanup(func() {
  4487  		os.RemoveAll(tmpDirPath)
  4488  	})
  4489  	asserter.AssertErrNil(err, true)
  4490  
  4491  	mountPoints := []*mountPoint{
  4492  		{
  4493  			src:      "devtmpfs-build",
  4494  			basePath: tmpDirPath,
  4495  			relpath:  "/dev",
  4496  			typ:      "devtmpfs",
  4497  		},
  4498  		{
  4499  			src:      "doesnotexists",
  4500  			basePath: "/doesnotexists",
  4501  			relpath:  "/doesnotexists",
  4502  			typ:      "devpts",
  4503  			bind:     true,
  4504  			opts:     []string{"nodev", "nosuid"},
  4505  		},
  4506  	}
  4507  
  4508  	gotAllMountCmds, gotAllUmountCmds, err := generateMountPointCmds(mountPoints, tmpDirPath)
  4509  	asserter.AssertErrContains(err, "Error preparing mountpoint")
  4510  	asserter.AssertEqual(nil, gotAllMountCmds)
  4511  	asserter.AssertEqual(nil, gotAllUmountCmds)
  4512  
  4513  }
  4514  
  4515  // TestCustomizeFstab tests functionality of the customizeFstab function
  4516  func TestCustomizeFstab(t *testing.T) {
  4517  	testCases := []struct {
  4518  		name          string
  4519  		fstab         []*imagedefinition.Fstab
  4520  		expectedFstab string
  4521  		existingFstab string
  4522  	}{
  4523  		{
  4524  			name: "one entry to an empty fstab",
  4525  			fstab: []*imagedefinition.Fstab{
  4526  				{
  4527  					Label:        "writable",
  4528  					Mountpoint:   "/",
  4529  					FSType:       "ext4",
  4530  					MountOptions: "defaults",
  4531  					Dump:         true,
  4532  					FsckOrder:    1,
  4533  				},
  4534  			},
  4535  			expectedFstab: `LABEL=writable	/	ext4	defaults	1	1
  4536  `,
  4537  		},
  4538  		{
  4539  			name: "one entry to a non-empty fstab",
  4540  			fstab: []*imagedefinition.Fstab{
  4541  				{
  4542  					Label:        "writable",
  4543  					Mountpoint:   "/",
  4544  					FSType:       "ext4",
  4545  					MountOptions: "defaults",
  4546  					Dump:         true,
  4547  					FsckOrder:    1,
  4548  				},
  4549  			},
  4550  			expectedFstab: `LABEL=writable	/	ext4	defaults	1	1
  4551  `,
  4552  			existingFstab: `LABEL=xxx / ext4 discard,errors=remount-ro 0 1`,
  4553  		},
  4554  		{
  4555  			name: "two entries",
  4556  			fstab: []*imagedefinition.Fstab{
  4557  				{
  4558  					Label:        "writable",
  4559  					Mountpoint:   "/",
  4560  					FSType:       "ext4",
  4561  					MountOptions: "defaults",
  4562  					Dump:         false,
  4563  					FsckOrder:    1,
  4564  				},
  4565  				{
  4566  					Label:        "system-boot",
  4567  					Mountpoint:   "/boot/firmware",
  4568  					FSType:       "vfat",
  4569  					MountOptions: "defaults",
  4570  					Dump:         false,
  4571  					FsckOrder:    1,
  4572  				},
  4573  			},
  4574  			expectedFstab: `LABEL=writable	/	ext4	defaults	0	1
  4575  LABEL=system-boot	/boot/firmware	vfat	defaults	0	1
  4576  `,
  4577  		},
  4578  	}
  4579  
  4580  	for _, tc := range testCases {
  4581  		t.Run(tc.name, func(t *testing.T) {
  4582  			asserter := helper.Asserter{T: t}
  4583  			restoreCWD := testhelper.SaveCWD()
  4584  			defer restoreCWD()
  4585  
  4586  			var stateMachine ClassicStateMachine
  4587  			stateMachine.commonFlags, stateMachine.stateMachineFlags = helper.InitCommonOpts()
  4588  			stateMachine.parent = &stateMachine
  4589  			stateMachine.ImageDef = imagedefinition.ImageDefinition{
  4590  				Architecture: getHostArch(),
  4591  				Series:       getHostSuite(),
  4592  				Rootfs:       &imagedefinition.Rootfs{},
  4593  				Customization: &imagedefinition.Customization{
  4594  					Fstab: tc.fstab,
  4595  				},
  4596  			}
  4597  
  4598  			// set the defaults for the imageDef
  4599  			err := helper.SetDefaults(&stateMachine.ImageDef)
  4600  			asserter.AssertErrNil(err, true)
  4601  
  4602  			err = stateMachine.makeTemporaryDirectories()
  4603  			asserter.AssertErrNil(err, true)
  4604  
  4605  			// create the <chroot>/etc directory
  4606  			err = os.MkdirAll(filepath.Join(stateMachine.tempDirs.chroot, "etc"), 0644)
  4607  			asserter.AssertErrNil(err, true)
  4608  
  4609  			fstabPath := filepath.Join(stateMachine.tempDirs.chroot, "etc", "fstab")
  4610  
  4611  			// simulate an already existing fstab file
  4612  			if len(tc.existingFstab) != 0 {
  4613  				err = osWriteFile(fstabPath, []byte(tc.existingFstab), 0644)
  4614  				asserter.AssertErrNil(err, true)
  4615  			}
  4616  
  4617  			// customize the fstab, ensure no errors, and check the contents
  4618  			err = stateMachine.customizeFstab()
  4619  			asserter.AssertErrNil(err, true)
  4620  
  4621  			fstabBytes, err := os.ReadFile(fstabPath)
  4622  			asserter.AssertErrNil(err, true)
  4623  
  4624  			if string(fstabBytes) != tc.expectedFstab {
  4625  				t.Errorf("Expected fstab contents \"%s\", but got \"%s\"",
  4626  					tc.expectedFstab, string(fstabBytes))
  4627  			}
  4628  		})
  4629  	}
  4630  }
  4631  
  4632  // TestStateMachine_customizeFstab_fail tests failures in the customizeFstab function
  4633  func TestStateMachine_customizeFstab_fail(t *testing.T) {
  4634  	asserter := helper.Asserter{T: t}
  4635  	restoreCWD := testhelper.SaveCWD()
  4636  	defer restoreCWD()
  4637  
  4638  	var stateMachine ClassicStateMachine
  4639  	stateMachine.commonFlags, stateMachine.stateMachineFlags = helper.InitCommonOpts()
  4640  	stateMachine.parent = &stateMachine
  4641  	stateMachine.ImageDef = imagedefinition.ImageDefinition{
  4642  		Architecture: getHostArch(),
  4643  		Series:       getHostSuite(),
  4644  		Rootfs:       &imagedefinition.Rootfs{},
  4645  		Customization: &imagedefinition.Customization{
  4646  			Fstab: []*imagedefinition.Fstab{
  4647  				{
  4648  					Label:        "writable",
  4649  					Mountpoint:   "/",
  4650  					FSType:       "ext4",
  4651  					MountOptions: "defaults",
  4652  					Dump:         false,
  4653  					FsckOrder:    1,
  4654  				},
  4655  			},
  4656  		},
  4657  	}
  4658  
  4659  	osOpenFile = mockOpenFile
  4660  	t.Cleanup(func() {
  4661  		osOpenFile = os.OpenFile
  4662  	})
  4663  	err := stateMachine.customizeFstab()
  4664  	asserter.AssertErrContains(err, "Error opening fstab")
  4665  }
  4666  
  4667  // TestGenerateRootfsTarball tests that a rootfs tarball is generated
  4668  // when appropriate and that it contains the correct files
  4669  func TestGenerateRootfsTarball(t *testing.T) {
  4670  	testCases := []struct {
  4671  		name     string // the name will double as the compression type
  4672  		tarPath  string
  4673  		fileType string
  4674  	}{
  4675  		{
  4676  			"uncompressed",
  4677  			"test_generate_rootfs_tarball.tar",
  4678  			"tar archive",
  4679  		},
  4680  		{
  4681  			"bzip2",
  4682  			"test_generate_rootfs_tarball.tar.bz2",
  4683  			"bzip2 compressed data",
  4684  		},
  4685  		{
  4686  			"gzip",
  4687  			"test_generate_rootfs_tarball.tar.gz",
  4688  			"gzip compressed data",
  4689  		},
  4690  		{
  4691  			"xz",
  4692  			"test_generate_rootfs_tarball.tar.xz",
  4693  			"XZ compressed data",
  4694  		},
  4695  		{
  4696  			"zstd",
  4697  			"test_generate_rootfs_tarball.tar.zst",
  4698  			"Zstandard compressed data",
  4699  		},
  4700  	}
  4701  	for _, tc := range testCases {
  4702  		t.Run(tc.name, func(t *testing.T) {
  4703  			asserter := helper.Asserter{T: t}
  4704  			restoreCWD := testhelper.SaveCWD()
  4705  			defer restoreCWD()
  4706  
  4707  			var stateMachine ClassicStateMachine
  4708  			stateMachine.commonFlags, stateMachine.stateMachineFlags = helper.InitCommonOpts()
  4709  			stateMachine.parent = &stateMachine
  4710  			stateMachine.ImageDef = imagedefinition.ImageDefinition{
  4711  				Architecture: getHostArch(),
  4712  				Series:       getHostSuite(),
  4713  				Rootfs:       &imagedefinition.Rootfs{},
  4714  				Artifacts: &imagedefinition.Artifact{
  4715  					RootfsTar: &imagedefinition.RootfsTar{
  4716  						RootfsTarName: tc.tarPath,
  4717  						Compression:   tc.name,
  4718  					},
  4719  				},
  4720  			}
  4721  
  4722  			err := stateMachine.makeTemporaryDirectories()
  4723  			asserter.AssertErrNil(err, true)
  4724  			stateMachine.commonFlags.OutputDir = stateMachine.stateMachineFlags.WorkDir
  4725  
  4726  			err = stateMachine.generateRootfsTarball()
  4727  			asserter.AssertErrNil(err, true)
  4728  
  4729  			// make sure tar archive exists and is the correct compression type
  4730  			_, err = os.Stat(filepath.Join(stateMachine.stateMachineFlags.WorkDir, tc.tarPath))
  4731  			if err != nil {
  4732  				t.Errorf("File %s should be in workdir, but is missing", tc.tarPath)
  4733  			}
  4734  
  4735  			fullPath := filepath.Join(stateMachine.commonFlags.OutputDir, tc.tarPath)
  4736  			fileCommand := *exec.Command("file", fullPath)
  4737  			cmdOutput, err := fileCommand.CombinedOutput()
  4738  			asserter.AssertErrNil(err, true)
  4739  			if !strings.Contains(string(cmdOutput), tc.fileType) {
  4740  				t.Errorf("File \"%s\" is the wrong file type. Expected \"%s\" but got \"%s\"",
  4741  					fullPath, tc.fileType, string(cmdOutput))
  4742  			}
  4743  		})
  4744  	}
  4745  }
  4746  
  4747  // TestTarXattrs sets an xattr on a file, puts it in a tar archive,
  4748  // extracts the tar archive and ensures the xattr is still present
  4749  func TestTarXattrs(t *testing.T) {
  4750  	asserter := helper.Asserter{T: t}
  4751  	restoreCWD := testhelper.SaveCWD()
  4752  	defer restoreCWD()
  4753  
  4754  	var stateMachine ClassicStateMachine
  4755  	stateMachine.commonFlags, stateMachine.stateMachineFlags = helper.InitCommonOpts()
  4756  	stateMachine.parent = &stateMachine
  4757  
  4758  	// create a file with xattrs in a temporary directory
  4759  	xattrBytes := []byte("ui-test")
  4760  	testDir, err := os.MkdirTemp("/tmp", "ubuntu-image-xattr-test")
  4761  	asserter.AssertErrNil(err, true)
  4762  	extractDir, err := os.MkdirTemp("/tmp", "ubuntu-image-xattr-test")
  4763  	asserter.AssertErrNil(err, true)
  4764  	testFile, err := os.CreateTemp(testDir, "test-xattrs-")
  4765  	asserter.AssertErrNil(err, true)
  4766  	testFileName := filepath.Base(testFile.Name())
  4767  	t.Cleanup(func() { os.RemoveAll(testDir) })
  4768  	t.Cleanup(func() { os.RemoveAll(extractDir) })
  4769  
  4770  	err = xattr.FSet(testFile, "user.test", xattrBytes)
  4771  	asserter.AssertErrNil(err, true)
  4772  
  4773  	// now run the helper tar creation and extraction functions
  4774  	tarPath := filepath.Join(testDir, "test-xattrs.tar")
  4775  	err = helper.CreateTarArchive(testDir, tarPath, "uncompressed", false, false)
  4776  	asserter.AssertErrNil(err, true)
  4777  
  4778  	err = helper.ExtractTarArchive(tarPath, extractDir, false, false)
  4779  	asserter.AssertErrNil(err, true)
  4780  
  4781  	// now read the extracted file's extended attributes
  4782  	finalXattrs, err := xattr.List(filepath.Join(extractDir, testFileName))
  4783  	asserter.AssertErrNil(err, true)
  4784  
  4785  	if !reflect.DeepEqual(finalXattrs, []string{"user.test"}) {
  4786  		t.Errorf("test file \"%s\" does not have correct xattrs set", testFile.Name())
  4787  	}
  4788  }
  4789  
  4790  // TestPingXattrs runs the ExtractTarArchive file on a pre-made test file that contains /bin/ping
  4791  // and ensures that the security.capability extended attribute is still present
  4792  func TestPingXattrs(t *testing.T) {
  4793  	asserter := helper.Asserter{T: t}
  4794  	restoreCWD := testhelper.SaveCWD()
  4795  	defer restoreCWD()
  4796  
  4797  	var stateMachine ClassicStateMachine
  4798  	stateMachine.commonFlags, stateMachine.stateMachineFlags = helper.InitCommonOpts()
  4799  	stateMachine.parent = &stateMachine
  4800  
  4801  	testDir, err := os.MkdirTemp("/tmp", "ubuntu-image-ping-xattr-test")
  4802  	asserter.AssertErrNil(err, true)
  4803  	t.Cleanup(func() { os.RemoveAll(testDir) })
  4804  	testFile := filepath.Join("testdata", "rootfs_tarballs", "ping.tar")
  4805  
  4806  	err = helper.ExtractTarArchive(testFile, testDir, true, true)
  4807  	asserter.AssertErrNil(err, true)
  4808  
  4809  	binPing := filepath.Join(testDir, "bin", "ping")
  4810  	pingXattrs, err := xattr.List(binPing)
  4811  	asserter.AssertErrNil(err, true)
  4812  	if !reflect.DeepEqual(pingXattrs, []string{"security.capability"}) {
  4813  		t.Error("ping has lost the security.capability xattr after tar extraction")
  4814  	}
  4815  }
  4816  
  4817  // TestFailedMakeQcow2Img tests failures in the makeQcow2Img function
  4818  func TestFailedMakeQcow2Img(t *testing.T) {
  4819  	asserter := helper.Asserter{T: t}
  4820  	restoreCWD := testhelper.SaveCWD()
  4821  	defer restoreCWD()
  4822  
  4823  	var stateMachine ClassicStateMachine
  4824  	stateMachine.commonFlags, stateMachine.stateMachineFlags = helper.InitCommonOpts()
  4825  	stateMachine.parent = &stateMachine
  4826  	stateMachine.ImageDef = imagedefinition.ImageDefinition{
  4827  		Architecture: getHostArch(),
  4828  		Series:       getHostSuite(),
  4829  		Artifacts: &imagedefinition.Artifact{
  4830  			Qcow2: &[]imagedefinition.Qcow2{
  4831  				{
  4832  					Qcow2Name: "test.qcow2",
  4833  				},
  4834  			},
  4835  		},
  4836  	}
  4837  
  4838  	// Setup the exec.Command mock
  4839  	testCaseName = "TestFailedMakeQcow2Image"
  4840  	execCommand = fakeExecCommand
  4841  	defer func() {
  4842  		execCommand = exec.Command
  4843  	}()
  4844  
  4845  	err := stateMachine.makeQcow2Img()
  4846  	asserter.AssertErrContains(err, "Error creating qcow2 artifact")
  4847  }
  4848  
  4849  // TestPreseedResetChroot tests that calling prepareClassicImage on a
  4850  // preseeded chroot correctly resets the chroot and preseeds over it
  4851  func TestPreseedResetChroot(t *testing.T) {
  4852  	t.Parallel()
  4853  	if testing.Short() {
  4854  		t.Skip("skipping test in short mode.")
  4855  	}
  4856  	asserter := helper.Asserter{T: t}
  4857  	restoreCWD := testhelper.SaveCWD()
  4858  	defer restoreCWD()
  4859  
  4860  	var stateMachine ClassicStateMachine
  4861  	stateMachine.commonFlags, stateMachine.stateMachineFlags = helper.InitCommonOpts()
  4862  	stateMachine.parent = &stateMachine
  4863  	stateMachine.Snaps = []string{"lxd"}
  4864  	stateMachine.commonFlags.Channel = "stable"
  4865  	stateMachine.ImageDef = imagedefinition.ImageDefinition{
  4866  		Architecture: getHostArch(),
  4867  		Series:       getHostSuite(),
  4868  		Rootfs: &imagedefinition.Rootfs{
  4869  			Archive: "ubuntu",
  4870  		},
  4871  		Customization: &imagedefinition.Customization{
  4872  			ExtraPackages: []*imagedefinition.Package{
  4873  				{
  4874  					PackageName: "squashfs-tools",
  4875  				},
  4876  				{
  4877  					PackageName: "snapd",
  4878  				},
  4879  			},
  4880  			ExtraSnaps: []*imagedefinition.Snap{
  4881  				{
  4882  					SnapName: "hello",
  4883  				},
  4884  				{
  4885  					SnapName: "core",
  4886  				},
  4887  				{
  4888  					SnapName: "core20",
  4889  				},
  4890  			},
  4891  		},
  4892  	}
  4893  
  4894  	err := helper.SetDefaults(&stateMachine.ImageDef)
  4895  	asserter.AssertErrNil(err, true)
  4896  
  4897  	err = stateMachine.makeTemporaryDirectories()
  4898  	asserter.AssertErrNil(err, true)
  4899  
  4900  	t.Cleanup(func() { os.RemoveAll(stateMachine.stateMachineFlags.WorkDir) })
  4901  
  4902  	err = getBasicChroot(stateMachine.StateMachine)
  4903  	asserter.AssertErrNil(err, true)
  4904  
  4905  	// install the packages that snap-preseed needs
  4906  	err = stateMachine.installPackages()
  4907  	asserter.AssertErrNil(err, true)
  4908  
  4909  	// first call prepareClassicImage to eventually preseed it
  4910  	err = stateMachine.prepareClassicImage()
  4911  	asserter.AssertErrNil(err, true)
  4912  
  4913  	// now preseed the chroot
  4914  	err = stateMachine.preseedClassicImage()
  4915  	asserter.AssertErrNil(err, true)
  4916  
  4917  	// set up a new set of snaps to be installed
  4918  	stateMachine.ImageDef = imagedefinition.ImageDefinition{
  4919  		Architecture: getHostArch(),
  4920  		Customization: &imagedefinition.Customization{
  4921  			ExtraSnaps: []*imagedefinition.Snap{
  4922  				{
  4923  					SnapName: "ubuntu-image",
  4924  				},
  4925  			},
  4926  		},
  4927  	}
  4928  
  4929  	// call prepareClassicImage again to trigger the reset
  4930  	err = stateMachine.prepareClassicImage()
  4931  	asserter.AssertErrNil(err, true)
  4932  
  4933  	// make sure the snaps from both prepares are present
  4934  	expectedSnaps := []string{"lxd", "hello", "ubuntu-image"}
  4935  	for _, expectedSnap := range expectedSnaps {
  4936  		snapGlobs, err := filepath.Glob(filepath.Join(stateMachine.tempDirs.chroot,
  4937  			"var", "lib", "snapd", "seed", "snaps", fmt.Sprintf("%s*.snap", expectedSnap)))
  4938  		asserter.AssertErrNil(err, true)
  4939  		if len(snapGlobs) == 0 {
  4940  			t.Errorf("expected snap %s to exist in the chroot but it does not", expectedSnap)
  4941  		}
  4942  	}
  4943  }
  4944  
  4945  // TestFailedUpdateBootloader tests failures in the updateBootloader function
  4946  func TestFailedUpdateBootloader(t *testing.T) {
  4947  	asserter := helper.Asserter{T: t}
  4948  	restoreCWD := testhelper.SaveCWD()
  4949  	defer restoreCWD()
  4950  
  4951  	var stateMachine ClassicStateMachine
  4952  	stateMachine.commonFlags, stateMachine.stateMachineFlags = helper.InitCommonOpts()
  4953  	stateMachine.parent = &stateMachine
  4954  	stateMachine.ImageDef = imagedefinition.ImageDefinition{
  4955  		Architecture: getHostArch(),
  4956  		Series:       getHostSuite(),
  4957  		Gadget:       &imagedefinition.Gadget{},
  4958  	}
  4959  
  4960  	// set up work dir
  4961  	err := stateMachine.makeTemporaryDirectories()
  4962  	asserter.AssertErrNil(err, true)
  4963  
  4964  	t.Cleanup(func() { os.RemoveAll(stateMachine.stateMachineFlags.WorkDir) })
  4965  
  4966  	// first, test that updateBootloader fails when the rootfs partition
  4967  	// has not been found in earlier steps
  4968  	stateMachine.RootfsPartNum = -1
  4969  	stateMachine.RootfsVolName = ""
  4970  	err = stateMachine.updateBootloader()
  4971  	asserter.AssertErrContains(err, "Error: could not determine partition number of the root filesystem")
  4972  
  4973  	// place a test gadget tree in the scratch directory so we don't
  4974  	// have to build one
  4975  	gadgetDir := filepath.Join(stateMachine.tempDirs.scratch, "gadget")
  4976  	err = os.MkdirAll(gadgetDir, 0755)
  4977  	asserter.AssertErrNil(err, true)
  4978  
  4979  	gadgetSource := filepath.Join("testdata", "gadget_tree")
  4980  	gadgetDest := filepath.Join(gadgetDir, "install")
  4981  	err = osutil.CopySpecialFile(gadgetSource, gadgetDest)
  4982  	asserter.AssertErrNil(err, true)
  4983  	// also copy gadget.yaml to the root of the scratch/gadget dir
  4984  	err = osutil.CopyFile(
  4985  		filepath.Join(gadgetDest, "meta", "gadget.yaml"),
  4986  		filepath.Join(gadgetDest, "gadget.yaml"),
  4987  		osutil.CopyFlagDefault,
  4988  	)
  4989  	asserter.AssertErrNil(err, true)
  4990  
  4991  	// prepare state in such a way that the rootfs partition was found in
  4992  	// earlier steps
  4993  	stateMachine.RootfsPartNum = 3
  4994  	stateMachine.RootfsVolName = "pc"
  4995  
  4996  	// parse gadget.yaml and run updateBootloader with the mocked os.Mkdir
  4997  	err = stateMachine.prepareGadgetTree()
  4998  	asserter.AssertErrNil(err, true)
  4999  	err = stateMachine.loadGadgetYaml()
  5000  	asserter.AssertErrNil(err, true)
  5001  	osMkdir = mockMkdir
  5002  	t.Cleanup(func() {
  5003  		osMkdir = os.Mkdir
  5004  	})
  5005  
  5006  	err = stateMachine.updateBootloader()
  5007  	asserter.AssertErrContains(err, "Error creating scratch/loopback directory")
  5008  }
  5009  
  5010  // TestUnsupportedBootloader tests that a warning is thrown if the
  5011  // bootloader specified in gadget.yaml is not supported
  5012  func TestUnsupportedBootloader(t *testing.T) {
  5013  	asserter := helper.Asserter{T: t}
  5014  	restoreCWD := testhelper.SaveCWD()
  5015  	defer restoreCWD()
  5016  
  5017  	var stateMachine ClassicStateMachine
  5018  	stateMachine.commonFlags, stateMachine.stateMachineFlags = helper.InitCommonOpts()
  5019  	stateMachine.parent = &stateMachine
  5020  	stateMachine.ImageDef = imagedefinition.ImageDefinition{
  5021  		Architecture: getHostArch(),
  5022  		Series:       getHostSuite(),
  5023  		Gadget:       &imagedefinition.Gadget{},
  5024  	}
  5025  
  5026  	err := stateMachine.makeTemporaryDirectories()
  5027  	asserter.AssertErrNil(err, true)
  5028  
  5029  	t.Cleanup(func() { os.RemoveAll(stateMachine.stateMachineFlags.WorkDir) })
  5030  
  5031  	// place a test gadget tree in the scratch directory so we don't
  5032  	// have to build one
  5033  	gadgetDir := filepath.Join(stateMachine.tempDirs.scratch, "gadget")
  5034  	err = os.MkdirAll(gadgetDir, 0755)
  5035  	asserter.AssertErrNil(err, true)
  5036  
  5037  	gadgetSource := filepath.Join("testdata", "gadget_tree")
  5038  	gadgetDest := filepath.Join(gadgetDir, "install")
  5039  	err = osutil.CopySpecialFile(gadgetSource, gadgetDest)
  5040  	asserter.AssertErrNil(err, true)
  5041  	// also copy gadget.yaml to the root of the scratch/gadget dir
  5042  	err = osutil.CopyFile(
  5043  		filepath.Join(gadgetDest, "meta", "gadget.yaml"),
  5044  		filepath.Join(gadgetDest, "gadget.yaml"),
  5045  		osutil.CopyFlagDefault,
  5046  	)
  5047  	asserter.AssertErrNil(err, true)
  5048  	// parse gadget.yaml
  5049  	err = stateMachine.prepareGadgetTree()
  5050  	asserter.AssertErrNil(err, true)
  5051  	err = stateMachine.loadGadgetYaml()
  5052  	asserter.AssertErrNil(err, true)
  5053  
  5054  	// prepare state in such a way that the rootfs partition was found in
  5055  	// earlier steps
  5056  	stateMachine.RootfsPartNum = 3
  5057  	stateMachine.RootfsVolName = "pc"
  5058  
  5059  	// set the bootloader for the volume to "test"
  5060  	stateMachine.GadgetInfo.Volumes["pc"].Bootloader = "test"
  5061  
  5062  	// capture stdout, run updateBootloader and make sure the states were printed
  5063  	stdout, restoreStdout, err := helper.CaptureStd(&os.Stdout)
  5064  	defer restoreStdout()
  5065  	asserter.AssertErrNil(err, true)
  5066  
  5067  	err = stateMachine.updateBootloader()
  5068  	asserter.AssertErrNil(err, true)
  5069  
  5070  	// restore stdout and examine what was printed
  5071  	restoreStdout()
  5072  	readStdout, err := io.ReadAll(stdout)
  5073  	asserter.AssertErrNil(err, true)
  5074  	if !strings.Contains(string(readStdout), "WARNING: updating bootloader test not yet supported") {
  5075  		t.Error("Warning for unsupported bootloader not printed")
  5076  	}
  5077  }
  5078  
  5079  // TestPreseedClassicImage unit tests the prepareClassicImage function
  5080  func TestPreseedClassicImage(t *testing.T) {
  5081  	t.Parallel()
  5082  	if testing.Short() {
  5083  		t.Skip("skipping test in short mode.")
  5084  	}
  5085  
  5086  	asserter := helper.Asserter{T: t}
  5087  	restoreCWD := testhelper.SaveCWD()
  5088  	defer restoreCWD()
  5089  
  5090  	var stateMachine ClassicStateMachine
  5091  	stateMachine.commonFlags, stateMachine.stateMachineFlags = helper.InitCommonOpts()
  5092  	stateMachine.parent = &stateMachine
  5093  	stateMachine.Snaps = []string{"lxd"}
  5094  	stateMachine.commonFlags.Channel = "stable"
  5095  	stateMachine.ImageDef = imagedefinition.ImageDefinition{
  5096  		Architecture: getHostArch(),
  5097  		Series:       getHostSuite(),
  5098  		Rootfs: &imagedefinition.Rootfs{
  5099  			Archive: "ubuntu",
  5100  		},
  5101  		Customization: &imagedefinition.Customization{
  5102  			ExtraPackages: []*imagedefinition.Package{
  5103  				{
  5104  					PackageName: "squashfs-tools",
  5105  				},
  5106  				{
  5107  					PackageName: "snapd",
  5108  				},
  5109  			},
  5110  			ExtraSnaps: []*imagedefinition.Snap{
  5111  				{
  5112  					SnapName: "hello",
  5113  				},
  5114  				{
  5115  					SnapName: "core",
  5116  				},
  5117  				{
  5118  					SnapName: "core20",
  5119  				},
  5120  			},
  5121  		},
  5122  	}
  5123  
  5124  	err := helper.SetDefaults(&stateMachine.ImageDef)
  5125  	asserter.AssertErrNil(err, true)
  5126  
  5127  	err = stateMachine.makeTemporaryDirectories()
  5128  	asserter.AssertErrNil(err, true)
  5129  
  5130  	err = getBasicChroot(stateMachine.StateMachine)
  5131  	asserter.AssertErrNil(err, true)
  5132  
  5133  	// install the packages that snap-preseed needs
  5134  	err = stateMachine.installPackages()
  5135  	asserter.AssertErrNil(err, true)
  5136  
  5137  	// first call prepareClassicImage
  5138  	err = stateMachine.prepareClassicImage()
  5139  	asserter.AssertErrNil(err, true)
  5140  
  5141  	// now preseed the chroot
  5142  	err = stateMachine.preseedClassicImage()
  5143  	asserter.AssertErrNil(err, true)
  5144  
  5145  	// make sure the snaps are fully preseeded
  5146  	expectedSnaps := []string{"lxc", "lxd", "hello"}
  5147  	for _, expectedSnap := range expectedSnaps {
  5148  		snapPath := filepath.Join(stateMachine.tempDirs.chroot, "snap", "bin", expectedSnap)
  5149  		_, err := os.Stat(snapPath)
  5150  		if err != nil {
  5151  			t.Errorf("File %s should be in chroot, but is missing", snapPath)
  5152  		}
  5153  	}
  5154  }
  5155  
  5156  // TestFailedPreseedClassicImage tests failures in the preseedClassicImage function
  5157  func TestFailedPreseedClassicImage(t *testing.T) {
  5158  	asserter := helper.Asserter{T: t}
  5159  	restoreCWD := testhelper.SaveCWD()
  5160  	defer restoreCWD()
  5161  
  5162  	var stateMachine ClassicStateMachine
  5163  	stateMachine.commonFlags, stateMachine.stateMachineFlags = helper.InitCommonOpts()
  5164  	stateMachine.parent = &stateMachine
  5165  
  5166  	err := stateMachine.makeTemporaryDirectories()
  5167  	asserter.AssertErrNil(err, true)
  5168  
  5169  	t.Cleanup(func() { os.RemoveAll(stateMachine.stateMachineFlags.WorkDir) })
  5170  
  5171  	// mock os.MkdirAll
  5172  	osMkdirAll = mockMkdirAll
  5173  	t.Cleanup(func() {
  5174  		osMkdirAll = os.MkdirAll
  5175  	})
  5176  	err = stateMachine.preseedClassicImage()
  5177  	asserter.AssertErrContains(err, "Error creating mountpoint")
  5178  	osMkdirAll = os.MkdirAll
  5179  
  5180  	testCaseName = "TestFailedPreseedClassicImage"
  5181  	execCommand = fakeExecCommand
  5182  	t.Cleanup(func() {
  5183  		execCommand = exec.Command
  5184  	})
  5185  	err = stateMachine.preseedClassicImage()
  5186  	asserter.AssertErrContains(err, "Error running command")
  5187  	execCommand = exec.Command
  5188  }
  5189  
  5190  // TestStateMachine_defaultLocale tests that the default locale is set
  5191  func TestStateMachine_defaultLocale(t *testing.T) {
  5192  	testCases := []struct {
  5193  		name           string
  5194  		localeContents string
  5195  		localeExpected string
  5196  	}{
  5197  		{
  5198  			"no_locale",
  5199  			"",
  5200  			"# Default Ubuntu locale\nLANG=C.UTF-8\n",
  5201  		},
  5202  		{
  5203  			"locale_set",
  5204  			"LANG=en_US.UTF-8\n",
  5205  			"LANG=en_US.UTF-8\n",
  5206  		},
  5207  		{
  5208  			"locale_set_non_lang",
  5209  			"LC_ALL=en_US.UTF-8\n",
  5210  			"LC_ALL=en_US.UTF-8\n",
  5211  		},
  5212  		{
  5213  			"locale_set_with_comment",
  5214  			"# some comment\nLANG=en_US.UTF-8\n",
  5215  			"# some comment\nLANG=en_US.UTF-8\n",
  5216  		},
  5217  		{
  5218  			"no_locale_with_comment",
  5219  			"# some comment\n",
  5220  			"# Default Ubuntu locale\nLANG=C.UTF-8\n",
  5221  		},
  5222  		{
  5223  			"no_locale_with_comment_locale",
  5224  			"# LANG=en_US.UTF-8",
  5225  			"# Default Ubuntu locale\nLANG=C.UTF-8\n",
  5226  		},
  5227  	}
  5228  	for _, tc := range testCases {
  5229  		t.Run(tc.name, func(t *testing.T) {
  5230  			asserter := helper.Asserter{T: t}
  5231  			var stateMachine ClassicStateMachine
  5232  			stateMachine.commonFlags, stateMachine.stateMachineFlags = helper.InitCommonOpts()
  5233  			stateMachine.parent = &stateMachine
  5234  
  5235  			err := stateMachine.makeTemporaryDirectories()
  5236  			asserter.AssertErrNil(err, true)
  5237  
  5238  			t.Cleanup(func() { os.RemoveAll(stateMachine.stateMachineFlags.WorkDir) })
  5239  
  5240  			// create the <chroot>/etc/default directory
  5241  			defaultPath := filepath.Join(stateMachine.tempDirs.chroot, "etc", "default")
  5242  			err = os.MkdirAll(defaultPath, 0744)
  5243  			asserter.AssertErrNil(err, true)
  5244  
  5245  			// create the <chroot>/etc/default/locale file
  5246  			localePath := filepath.Join(defaultPath, "locale")
  5247  			err = os.WriteFile(localePath, []byte(tc.localeContents), 0600)
  5248  			asserter.AssertErrNil(err, true)
  5249  
  5250  			// call the function under test
  5251  			err = stateMachine.setDefaultLocale()
  5252  			asserter.AssertErrNil(err, true)
  5253  
  5254  			// read the locale file and make sure it matches the expected contents
  5255  			localeBytes, err := os.ReadFile(localePath)
  5256  			asserter.AssertErrNil(err, true)
  5257  
  5258  			if string(localeBytes) != tc.localeExpected {
  5259  				t.Errorf("Expected locale contents \"%s\", but got \"%s\"",
  5260  					tc.localeExpected, string(localeBytes))
  5261  			}
  5262  		})
  5263  	}
  5264  }
  5265  
  5266  // TestStateMachine_defaultLocaleFailures tests failures in the setDefaultLocale function
  5267  func TestStateMachine_defaultLocaleFailures(t *testing.T) {
  5268  	asserter := helper.Asserter{T: t}
  5269  
  5270  	var stateMachine ClassicStateMachine
  5271  	stateMachine.commonFlags, stateMachine.stateMachineFlags = helper.InitCommonOpts()
  5272  	stateMachine.parent = &stateMachine
  5273  
  5274  	err := stateMachine.makeTemporaryDirectories()
  5275  	asserter.AssertErrNil(err, true)
  5276  
  5277  	t.Cleanup(func() { os.RemoveAll(stateMachine.stateMachineFlags.WorkDir) })
  5278  
  5279  	// check failure in MkDirAll
  5280  	osMkdirAll = mockMkdirAll
  5281  	t.Cleanup(func() {
  5282  		osMkdirAll = os.MkdirAll
  5283  	})
  5284  	err = stateMachine.setDefaultLocale()
  5285  	asserter.AssertErrContains(err, "Error creating default directory")
  5286  	osMkdirAll = os.MkdirAll
  5287  
  5288  	// check failure in WriteFile
  5289  	osWriteFile = mockWriteFile
  5290  	t.Cleanup(func() {
  5291  		osWriteFile = os.WriteFile
  5292  	})
  5293  	err = stateMachine.setDefaultLocale()
  5294  	asserter.AssertErrContains(err, "Error writing to locale file")
  5295  	osWriteFile = os.WriteFile
  5296  }
  5297  
  5298  func TestClassicStateMachine_cleanRootfs_real_rootfs(t *testing.T) {
  5299  	t.Parallel()
  5300  	if testing.Short() {
  5301  		t.Skip("skipping test in short mode.")
  5302  	}
  5303  	asserter := helper.Asserter{T: t}
  5304  	restoreCWD := testhelper.SaveCWD()
  5305  	t.Cleanup(restoreCWD)
  5306  
  5307  	var stateMachine ClassicStateMachine
  5308  	stateMachine.commonFlags, stateMachine.stateMachineFlags = helper.InitCommonOpts()
  5309  	stateMachine.parent = &stateMachine
  5310  	stateMachine.Snaps = []string{"lxd"}
  5311  	stateMachine.commonFlags.Channel = "stable"
  5312  	stateMachine.commonFlags.Debug = true
  5313  	stateMachine.ImageDef = imagedefinition.ImageDefinition{
  5314  		Architecture: getHostArch(),
  5315  		Series:       getHostSuite(),
  5316  		Rootfs: &imagedefinition.Rootfs{
  5317  			Archive: "ubuntu",
  5318  		},
  5319  		Customization: &imagedefinition.Customization{
  5320  			ExtraPackages: []*imagedefinition.Package{
  5321  				{
  5322  					PackageName: "squashfs-tools",
  5323  				},
  5324  				{
  5325  					PackageName: "snapd",
  5326  				},
  5327  			},
  5328  		},
  5329  	}
  5330  
  5331  	err := helper.SetDefaults(&stateMachine.ImageDef)
  5332  	asserter.AssertErrNil(err, true)
  5333  
  5334  	err = stateMachine.makeTemporaryDirectories()
  5335  	asserter.AssertErrNil(err, true)
  5336  
  5337  	t.Cleanup(func() { os.RemoveAll(stateMachine.stateMachineFlags.WorkDir) })
  5338  
  5339  	err = getBasicChroot(stateMachine.StateMachine)
  5340  	asserter.AssertErrNil(err, true)
  5341  
  5342  	// install the packages that snap-preseed needs
  5343  	err = stateMachine.installPackages()
  5344  	asserter.AssertErrNil(err, true)
  5345  
  5346  	err = stateMachine.cleanRootfs()
  5347  	asserter.AssertErrNil(err, true)
  5348  
  5349  	// Check cleaned files were removed
  5350  	cleaned := []string{
  5351  		filepath.Join(stateMachine.tempDirs.chroot, "var", "lib", "dbus", "machine-id"),
  5352  		filepath.Join(stateMachine.tempDirs.chroot, "etc", "ssh", "ssh_host_rsa_key"),
  5353  		filepath.Join(stateMachine.tempDirs.chroot, "etc", "ssh", "ssh_host_rsa_key.pub"),
  5354  		filepath.Join(stateMachine.tempDirs.chroot, "etc", "ssh", "ssh_host_ecdsa_key"),
  5355  		filepath.Join(stateMachine.tempDirs.chroot, "etc", "ssh", "ssh_host_ecdsa_key.pub"),
  5356  	}
  5357  	for _, file := range cleaned {
  5358  		_, err := os.Stat(file)
  5359  		if !os.IsNotExist(err) {
  5360  			t.Errorf("File %s should not exist, but does", file)
  5361  		}
  5362  	}
  5363  
  5364  	truncated := []string{
  5365  		filepath.Join(stateMachine.tempDirs.chroot, "etc", "machine-id"),
  5366  	}
  5367  	for _, file := range truncated {
  5368  		fileInfo, err := os.Stat(file)
  5369  		if os.IsNotExist(err) {
  5370  			t.Errorf("File %s should exist, but does not", file)
  5371  		}
  5372  
  5373  		if fileInfo.Size() != 0 {
  5374  			t.Errorf("File %s should be empty, but it is not. Size: %v", file, fileInfo.Size())
  5375  		}
  5376  	}
  5377  }
  5378  
  5379  func TestClassicStateMachine_cleanRootfs(t *testing.T) {
  5380  	sampleContent := "test"
  5381  	sampleSize := int64(len(sampleContent))
  5382  
  5383  	testCases := []struct {
  5384  		name                 string
  5385  		mockFuncs            func() func()
  5386  		expectedErr          string
  5387  		initialRootfsContent []string
  5388  		wantRootfsContent    map[string]int64 // name: size
  5389  	}{
  5390  		{
  5391  			name: "success",
  5392  			initialRootfsContent: []string{
  5393  				filepath.Join("etc", "machine-id"),
  5394  				filepath.Join("var", "lib", "dbus", "machine-id"),
  5395  				filepath.Join("etc", "udev", "rules.d", "test-persistent-net.rules"),
  5396  				filepath.Join("etc", "udev", "rules.d", "test2-persistent-net.rules"),
  5397  				filepath.Join("var", "cache", "debconf", "test-old"),
  5398  				filepath.Join("var", "lib", "dpkg", "testdpkg-old"),
  5399  			},
  5400  			wantRootfsContent: map[string]int64{
  5401  				filepath.Join("etc", "machine-id"):                                    0,
  5402  				filepath.Join("etc", "udev", "rules.d", "test-persistent-net.rules"):  0,
  5403  				filepath.Join("etc", "udev", "rules.d", "test2-persistent-net.rules"): 0,
  5404  			},
  5405  		},
  5406  		{
  5407  			name: "fail to clean files",
  5408  			mockFuncs: func() func() {
  5409  				mock := testhelper.NewOSMock(
  5410  					&testhelper.OSMockConf{},
  5411  				)
  5412  
  5413  				osRemove = mock.Remove
  5414  				return func() { osRemove = os.Remove }
  5415  			},
  5416  			expectedErr: "Error removing",
  5417  			initialRootfsContent: []string{
  5418  				filepath.Join("etc", "machine-id"),
  5419  				filepath.Join("var", "lib", "dbus", "machine-id"),
  5420  				filepath.Join("etc", "udev", "rules.d", "test-persistent-net.rules"),
  5421  				filepath.Join("var", "cache", "debconf", "test-old"),
  5422  				filepath.Join("var", "lib", "dpkg", "testdpkg-old"),
  5423  			},
  5424  			wantRootfsContent: map[string]int64{
  5425  				filepath.Join("etc", "machine-id"):                                   sampleSize,
  5426  				filepath.Join("var", "lib", "dbus", "machine-id"):                    sampleSize,
  5427  				filepath.Join("etc", "udev", "rules.d", "test-persistent-net.rules"): sampleSize,
  5428  				filepath.Join("var", "cache", "debconf", "test-old"):                 sampleSize,
  5429  				filepath.Join("var", "lib", "dpkg", "testdpkg-old"):                  sampleSize,
  5430  			},
  5431  		},
  5432  		{
  5433  			name: "fail to truncate files",
  5434  			mockFuncs: func() func() {
  5435  				mock := testhelper.NewOSMock(
  5436  					&testhelper.OSMockConf{},
  5437  				)
  5438  
  5439  				osTruncate = mock.Truncate
  5440  				return func() { osTruncate = os.Truncate }
  5441  			},
  5442  			expectedErr: "Error truncating",
  5443  			initialRootfsContent: []string{
  5444  				filepath.Join("etc", "machine-id"),
  5445  				filepath.Join("var", "lib", "dbus", "machine-id"),
  5446  				filepath.Join("etc", "udev", "rules.d", "test-persistent-net.rules"),
  5447  			},
  5448  			wantRootfsContent: map[string]int64{
  5449  				filepath.Join("etc", "machine-id"):                                   sampleSize,
  5450  				filepath.Join("etc", "udev", "rules.d", "test-persistent-net.rules"): sampleSize,
  5451  			},
  5452  		},
  5453  	}
  5454  
  5455  	for _, tc := range testCases {
  5456  		t.Run(tc.name, func(t *testing.T) {
  5457  			asserter := helper.Asserter{T: t}
  5458  			stateMachine := &ClassicStateMachine{}
  5459  			stateMachine.commonFlags, stateMachine.stateMachineFlags = helper.InitCommonOpts()
  5460  			stateMachine.parent = stateMachine
  5461  
  5462  			err := stateMachine.makeTemporaryDirectories()
  5463  			asserter.AssertErrNil(err, true)
  5464  
  5465  			t.Cleanup(func() { os.RemoveAll(stateMachine.stateMachineFlags.WorkDir) })
  5466  
  5467  			if tc.mockFuncs != nil {
  5468  				restoreMock := tc.mockFuncs()
  5469  				t.Cleanup(restoreMock)
  5470  			}
  5471  
  5472  			for _, path := range tc.initialRootfsContent {
  5473  				// create dir if necessary
  5474  				fullPath := filepath.Join(stateMachine.tempDirs.chroot, path)
  5475  				err = os.MkdirAll(filepath.Dir(fullPath), 0777)
  5476  				asserter.AssertErrNil(err, true)
  5477  
  5478  				err := os.WriteFile(fullPath, []byte(sampleContent), 0600)
  5479  				asserter.AssertErrNil(err, true)
  5480  			}
  5481  
  5482  			err = stateMachine.cleanRootfs()
  5483  			if err != nil || len(tc.expectedErr) != 0 {
  5484  				asserter.AssertErrContains(err, tc.expectedErr)
  5485  			}
  5486  
  5487  			for path, size := range tc.wantRootfsContent {
  5488  				fullPath := filepath.Join(stateMachine.tempDirs.chroot, path)
  5489  				s, err := os.Stat(fullPath)
  5490  				if os.IsNotExist(err) {
  5491  					t.Errorf("File %s should exist, but does not", path)
  5492  				}
  5493  
  5494  				if s.Size() != size {
  5495  					t.Errorf("File size of %s is not matching: want %d, got %d", path, size, s.Size())
  5496  				}
  5497  			}
  5498  		})
  5499  	}
  5500  }