github.com/GoogleCloudPlatform/compute-image-tools/cli_tools@v0.0.0-20240516224744-de2dabc4ed1b/common/imagefile/qemu_img_test.go (about)

     1  //  Copyright 2020 Google Inc. All Rights Reserved.
     2  //
     3  //  Licensed under the Apache License, Version 2.0 (the "License");
     4  //  you may not use this file except in compliance with the License.
     5  //  You may obtain a copy of the License at
     6  //
     7  //      http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  //  Unless required by applicable law or agreed to in writing, software
    10  //  distributed under the License is distributed on an "AS IS" BASIS,
    11  //  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  //  See the License for the specific language governing permissions and
    13  //  limitations under the License.
    14  
    15  package imagefile
    16  
    17  import (
    18  	"context"
    19  	"io/ioutil"
    20  	"os"
    21  	"os/exec"
    22  	"path"
    23  	"testing"
    24  
    25  	"github.com/GoogleCloudPlatform/compute-image-tools/cli_tools/common/utils/test"
    26  	"github.com/GoogleCloudPlatform/compute-image-tools/cli_tools/mocks"
    27  	"github.com/golang/mock/gomock"
    28  	"github.com/stretchr/testify/assert"
    29  )
    30  
    31  const bytesPerMB = int64(1024 * 1024)
    32  
    33  func TestGetInfo_FormatDetection(t *testing.T) {
    34  	skipIfQemuImgNotInstalled(t)
    35  
    36  	cases := []struct {
    37  		filename              string
    38  		size                  string
    39  		format                string
    40  		expectedVirtualSizeGB int64
    41  	}{
    42  		{
    43  			filename:              "test.vmdk",
    44  			size:                  "50G",
    45  			format:                "vmdk",
    46  			expectedVirtualSizeGB: 50 * bytesPerGB,
    47  		},
    48  
    49  		{
    50  			filename:              "test.vhdx",
    51  			size:                  "500M",
    52  			format:                "vhdx",
    53  			expectedVirtualSizeGB: 500 * bytesPerMB,
    54  		},
    55  
    56  		{
    57  			filename:              "test.vpc",
    58  			size:                  "10M",
    59  			format:                "vpc",
    60  			expectedVirtualSizeGB: 10 * bytesPerMB,
    61  		},
    62  
    63  		{
    64  			filename:              "test.vdi",
    65  			size:                  "1G",
    66  			format:                "vdi",
    67  			expectedVirtualSizeGB: bytesPerGB,
    68  		},
    69  
    70  		{
    71  			filename:              "test.qcow2",
    72  			size:                  "2G",
    73  			format:                "qcow2",
    74  			expectedVirtualSizeGB: 2 * bytesPerGB,
    75  		},
    76  
    77  		{
    78  			filename:              "test.raw",
    79  			size:                  "4G",
    80  			format:                "raw",
    81  			expectedVirtualSizeGB: 4 * bytesPerGB,
    82  		},
    83  	}
    84  
    85  	for _, tt := range cases {
    86  		mockCtrl := gomock.NewController(t)
    87  		defer mockCtrl.Finish()
    88  		mockShell := mocks.NewMockShellExecutor(mockCtrl)
    89  		client := defaultInfoClient{mockShell, "out54321"}
    90  		mockShell.EXPECT().Exec("qemu-img", "dd", gomock.Any(), gomock.Any(),
    91  			"bs=512", "count=200000", "skip=0").DoAndReturn(func(_ interface{}, _ ...interface{}) (string, error) {
    92  			f, _ := os.Create("out543210")
    93  			f.WriteString("test_file_content_0")
    94  			return "", nil
    95  		})
    96  		mockShell.EXPECT().Exec("qemu-img", "dd", gomock.Any(), gomock.Any(),
    97  			"bs=512", "count=2000000", "skip=1800000").DoAndReturn(func(_ interface{}, _ ...interface{}) (string, error) {
    98  			f, _ := os.Create("out543211")
    99  			f.WriteString("test_file_content_1")
   100  			return "", nil
   101  		})
   102  		mockShell.EXPECT().Exec("qemu-img", "dd", gomock.Any(), gomock.Any(),
   103  			"bs=512", "count=20000000", "skip=19800000").DoAndReturn(func(_ interface{}, _ ...interface{}) (string, error) {
   104  			f, _ := os.Create("out543212")
   105  			f.WriteString("test_file_content_2")
   106  			return "", nil
   107  		})
   108  		mockShell.EXPECT().Exec("qemu-img", "dd", gomock.Any(), gomock.Any(),
   109  			"bs=512", gomock.Any(), gomock.Any()).DoAndReturn(func(_ interface{}, _ ...interface{}) (string, error) {
   110  			f, _ := os.Create("out543213")
   111  			f.WriteString("test_file_content_3")
   112  			return "", nil
   113  		})
   114  
   115  		t.Run(tt.filename, func(t *testing.T) {
   116  			// 1. Create temp dir
   117  			dir, err := ioutil.TempDir("", "")
   118  			assert.NoError(t, err)
   119  			absPath := path.Join(dir, tt.filename)
   120  
   121  			// 2. Create image in temp dir
   122  			cmd := exec.Command("qemu-img", "create", absPath, "-f", tt.format, tt.size)
   123  			_, err = cmd.Output()
   124  			assert.NoError(t, err)
   125  
   126  			// 3. Run inspection, and verify results
   127  			imageInfo, err := client.GetInfo(context.Background(), absPath)
   128  			assert.NoError(t, err)
   129  			assert.Equal(t, tt.format, imageInfo.Format)
   130  			// Testing to the nearest GB, since that's what the GCP APIs use, and
   131  			// because some image formats have additional overhead such that
   132  			// the virtual size doesn't match the requested size in qemu-img create.
   133  			assert.Equal(t, tt.expectedVirtualSizeGB/bytesPerGB, imageInfo.VirtualSizeBytes/bytesPerGB)
   134  
   135  			assert.Equal(t, "7db8ff7ab828caf88d4cad4b0f8dd327-2e32e795a3d735fd93bd105b6f1fdf47-6646221c272b17d6206e2a4d1621b69b-f82500bc5c1b309426ac37f25a938966", imageInfo.Checksum)
   136  		})
   137  
   138  	}
   139  }
   140  
   141  func TestGetInfo_ReturnErrorWhenImageNotFound(t *testing.T) {
   142  	skipIfQemuImgNotInstalled(t)
   143  	client := NewInfoClient()
   144  	_, err := client.GetInfo(context.Background(), "/zz/garbage")
   145  	assert.EqualError(t, err, "file \"/zz/garbage\" not found")
   146  }
   147  
   148  func TestGetInfo_PropagateQemuImgError(t *testing.T) {
   149  	skipIfQemuImgNotInstalled(t)
   150  	client := NewInfoClient()
   151  	_, err := client.GetInfo(context.Background(), "/tmp")
   152  	assert.Error(t, err)
   153  	assert.Contains(t, err.Error(), "qemu-img: Could not open '/tmp'")
   154  }
   155  
   156  func TestGetInfo_PropagateContextCancellation(t *testing.T) {
   157  	skipIfQemuImgNotInstalled(t)
   158  	client := NewInfoClient()
   159  	ctx, cancelFunc := context.WithCancel(context.Background())
   160  	cancelFunc()
   161  	_, err := client.GetInfo(ctx, "/tmp")
   162  	assert.Error(t, err)
   163  	assert.Contains(t, err.Error(), "inspection failure: 'context canceled'")
   164  }
   165  
   166  func TestGetInfo_InspectionClassifiesCompressedAsRaw(t *testing.T) {
   167  	skipIfQemuImgNotInstalled(t)
   168  	tempFile, err := ioutil.TempFile("", "")
   169  	assert.NoError(t, err)
   170  
   171  	_, err = tempFile.WriteString(test.CreateCompressedFile())
   172  	assert.NoError(t, err)
   173  	assert.NoError(t, tempFile.Close())
   174  
   175  	client := NewInfoClient()
   176  	info, err := client.GetInfo(context.Background(), tempFile.Name())
   177  	assert.NoError(t, err)
   178  	assert.Equal(t, "raw", info.Format)
   179  }
   180  
   181  func TestLookupFileFormat_ReturnsUnknown_WhenFormatNotFound(t *testing.T) {
   182  	assert.Equal(t, "unknown", lookupFileFormat("not-found"))
   183  }
   184  
   185  func TestLookupFileFormat_PerformsCaseInsensitiveSearch(t *testing.T) {
   186  	assert.Equal(t, "vmdk", lookupFileFormat("VmDK"))
   187  }
   188  
   189  func skipIfQemuImgNotInstalled(t *testing.T) {
   190  	cmd := exec.Command("qemu-img", "--version")
   191  	_, err := cmd.Output()
   192  	if err != nil {
   193  		t.Skipf("Skipping since qemu-img is not installed %v", err)
   194  	}
   195  }