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 }