github.com/rigado/snapd@v2.42.5-go-mod+incompatible/gadget/filesystemimage_test.go (about) 1 // -*- Mode: Go; indent-tabs-mode: t -*- 2 3 /* 4 * Copyright (C) 2019 Canonical Ltd 5 * 6 * This program is free software: you can redistribute it and/or modify 7 * it under the terms of the GNU General Public License version 3 as 8 * published by the Free Software Foundation. 9 * 10 * This program is distributed in the hope that it will be useful, 11 * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 * GNU General Public License for more details. 14 * 15 * You should have received a copy of the GNU General Public License 16 * along with this program. If not, see <http://www.gnu.org/licenses/>. 17 * 18 */ 19 package gadget_test 20 21 import ( 22 "errors" 23 "fmt" 24 "os" 25 "path/filepath" 26 27 . "gopkg.in/check.v1" 28 29 "github.com/snapcore/snapd/gadget" 30 "github.com/snapcore/snapd/osutil" 31 "github.com/snapcore/snapd/testutil" 32 ) 33 34 type filesystemImageTestSuite struct { 35 testutil.BaseTest 36 37 dir string 38 work string 39 content string 40 psTrivial *gadget.LaidOutStructure 41 } 42 43 var _ = Suite(&filesystemImageTestSuite{}) 44 45 func (s *filesystemImageTestSuite) SetUpTest(c *C) { 46 s.BaseTest.SetUpTest(c) 47 48 s.dir = c.MkDir() 49 // work directory 50 s.work = filepath.Join(s.dir, "work") 51 err := os.MkdirAll(s.work, 0755) 52 c.Assert(err, IsNil) 53 54 // gadget content directory 55 s.content = filepath.Join(s.dir, "content") 56 57 s.psTrivial = &gadget.LaidOutStructure{ 58 VolumeStructure: &gadget.VolumeStructure{ 59 Filesystem: "happyfs", 60 Size: 2 * gadget.SizeMiB, 61 Content: []gadget.VolumeContent{}, 62 }, 63 Index: 1, 64 } 65 } 66 67 func (s *filesystemImageTestSuite) TearDownTest(c *C) { 68 s.BaseTest.TearDownTest(c) 69 } 70 71 func (s *filesystemImageTestSuite) imgForPs(c *C, ps *gadget.LaidOutStructure) string { 72 c.Assert(ps, NotNil) 73 img := filepath.Join(s.dir, "img") 74 makeSizedFile(c, img, ps.Size, nil) 75 return img 76 } 77 78 type filesystemImageMockedTestSuite struct { 79 filesystemImageTestSuite 80 } 81 82 var _ = Suite(&filesystemImageMockedTestSuite{}) 83 84 func (s *filesystemImageMockedTestSuite) SetUpTest(c *C) { 85 s.filesystemImageTestSuite.SetUpTest(c) 86 87 unexpectedMkfs := func(imgFile, label, contentsRootDir string) error { 88 return errors.New("unexpected mkfs call") 89 } 90 s.AddCleanup(gadget.MockMkfsHandlers(map[string]gadget.MkfsFunc{ 91 "happyfs": unexpectedMkfs, 92 })) 93 } 94 95 func (s *filesystemImageMockedTestSuite) TearDownTest(c *C) { 96 s.filesystemImageTestSuite.TearDownTest(c) 97 } 98 99 func (s *filesystemImageMockedTestSuite) TestSimpleErrors(c *C) { 100 psValid := &gadget.LaidOutStructure{ 101 VolumeStructure: &gadget.VolumeStructure{ 102 Filesystem: "ext4", 103 Size: 2 * gadget.SizeMiB, 104 }, 105 } 106 107 fiw, err := gadget.NewFilesystemImageWriter("", psValid, "") 108 c.Assert(err, ErrorMatches, "internal error: gadget content directory cannot be unset") 109 c.Assert(fiw, IsNil) 110 111 fiw, err = gadget.NewFilesystemImageWriter(s.dir, nil, "") 112 c.Assert(err, ErrorMatches, `internal error: \*LaidOutStructure is nil`) 113 c.Assert(fiw, IsNil) 114 115 psNoFs := &gadget.LaidOutStructure{ 116 VolumeStructure: &gadget.VolumeStructure{ 117 Filesystem: "none", 118 Size: 2 * gadget.SizeMiB, 119 }, 120 } 121 122 fiw, err = gadget.NewFilesystemImageWriter(s.dir, psNoFs, "") 123 c.Assert(err, ErrorMatches, "internal error: structure has no filesystem") 124 c.Assert(fiw, IsNil) 125 126 psInvalidFs := &gadget.LaidOutStructure{ 127 VolumeStructure: &gadget.VolumeStructure{ 128 Filesystem: "xfs", 129 Size: 2 * gadget.SizeMiB, 130 }, 131 } 132 fiw, err = gadget.NewFilesystemImageWriter(s.dir, psInvalidFs, "") 133 c.Assert(err, ErrorMatches, `internal error: filesystem "xfs" has no handler`) 134 c.Assert(fiw, IsNil) 135 } 136 137 func (s *filesystemImageMockedTestSuite) TestHappyFull(c *C) { 138 ps := &gadget.LaidOutStructure{ 139 VolumeStructure: &gadget.VolumeStructure{ 140 Filesystem: "happyfs", 141 Label: "so-happy", 142 Size: 2 * gadget.SizeMiB, 143 Content: []gadget.VolumeContent{ 144 {Source: "/foo", Target: "/"}, 145 }, 146 }, 147 Index: 2, 148 } 149 150 // image file 151 img := s.imgForPs(c, ps) 152 153 // mock gadget data 154 gd := []gadgetData{ 155 {name: "foo", target: "foo", content: "hello foo"}, 156 } 157 makeGadgetData(c, s.content, gd) 158 159 var cbCalled bool 160 var mkfsCalled bool 161 162 cb := func(rootDir string, cbPs *gadget.LaidOutStructure) error { 163 c.Assert(cbPs, DeepEquals, ps) 164 c.Assert(rootDir, Equals, filepath.Join(s.work, "snap-stage-content-part-0002")) 165 verifyWrittenGadgetData(c, rootDir, gd) 166 167 cbCalled = true 168 return nil 169 } 170 171 mkfsHappyFs := func(imgFile, label, contentsRootDir string) error { 172 c.Assert(imgFile, Equals, img) 173 c.Assert(label, Equals, "so-happy") 174 c.Assert(contentsRootDir, Equals, filepath.Join(s.work, "snap-stage-content-part-0002")) 175 mkfsCalled = true 176 return nil 177 } 178 179 restore := gadget.MockMkfsHandlers(map[string]gadget.MkfsFunc{ 180 "happyfs": mkfsHappyFs, 181 }) 182 defer restore() 183 184 fiw, err := gadget.NewFilesystemImageWriter(s.content, ps, s.work) 185 c.Assert(err, IsNil) 186 187 err = fiw.Write(img, cb) 188 c.Assert(err, IsNil) 189 c.Assert(cbCalled, Equals, true) 190 c.Assert(mkfsCalled, Equals, true) 191 // nothing left in temporary staging location 192 matches, err := filepath.Glob(s.work + "/*") 193 c.Assert(err, IsNil) 194 c.Assert(matches, HasLen, 0) 195 } 196 197 func (s *filesystemImageMockedTestSuite) TestPostStageOptional(c *C) { 198 var mkfsCalled bool 199 mkfsHappyFs := func(imgFile, label, contentsRootDir string) error { 200 mkfsCalled = true 201 return nil 202 } 203 204 restore := gadget.MockMkfsHandlers(map[string]gadget.MkfsFunc{ 205 "happyfs": mkfsHappyFs, 206 }) 207 defer restore() 208 209 fiw, err := gadget.NewFilesystemImageWriter(s.content, s.psTrivial, s.work) 210 c.Assert(err, IsNil) 211 212 img := s.imgForPs(c, s.psTrivial) 213 214 err = fiw.Write(img, nil) 215 c.Assert(err, IsNil) 216 c.Assert(mkfsCalled, Equals, true) 217 } 218 219 func (s *filesystemImageMockedTestSuite) TestChecksImage(c *C) { 220 cb := func(rootDir string, cbPs *gadget.LaidOutStructure) error { 221 return errors.New("unexpected call") 222 } 223 224 fiw, err := gadget.NewFilesystemImageWriter(s.content, s.psTrivial, s.work) 225 c.Assert(err, IsNil) 226 227 img := filepath.Join(s.dir, "img") 228 229 // no image file 230 err = fiw.Write(img, cb) 231 c.Assert(err, ErrorMatches, "cannot stat image file: .*/img: no such file or directory") 232 233 // image file smaller than expected 234 makeSizedFile(c, img, s.psTrivial.Size/2, nil) 235 236 err = fiw.Write(img, cb) 237 c.Assert(err, ErrorMatches, fmt.Sprintf("size of image file %v is different from declared structure size %v", s.psTrivial.Size/2, s.psTrivial.Size)) 238 } 239 240 func (s *filesystemImageMockedTestSuite) TestPostStageError(c *C) { 241 cb := func(rootDir string, cbPs *gadget.LaidOutStructure) error { 242 return errors.New("post stage exploded") 243 } 244 245 fiw, err := gadget.NewFilesystemImageWriter(s.content, s.psTrivial, s.work) 246 c.Assert(err, IsNil) 247 248 img := s.imgForPs(c, s.psTrivial) 249 250 err = fiw.Write(img, cb) 251 c.Assert(err, ErrorMatches, "post stage callback failed: post stage exploded") 252 } 253 254 func (s *filesystemImageMockedTestSuite) TestMkfsError(c *C) { 255 mkfsHappyFs := func(imgFile, label, contentsRootDir string) error { 256 return errors.New("will not mkfs") 257 } 258 restore := gadget.MockMkfsHandlers(map[string]gadget.MkfsFunc{ 259 "happyfs": mkfsHappyFs, 260 }) 261 defer restore() 262 263 fiw, err := gadget.NewFilesystemImageWriter(s.content, s.psTrivial, s.work) 264 c.Assert(err, IsNil) 265 266 img := s.imgForPs(c, s.psTrivial) 267 268 err = fiw.Write(img, nil) 269 c.Assert(err, ErrorMatches, `cannot create "happyfs" filesystem: will not mkfs`) 270 } 271 272 func (s *filesystemImageMockedTestSuite) TestFilesystemExtraCheckError(c *C) { 273 ps := &gadget.LaidOutStructure{ 274 VolumeStructure: &gadget.VolumeStructure{ 275 Filesystem: "happyfs", 276 Size: 2 * gadget.SizeMiB, 277 Content: []gadget.VolumeContent{}, 278 }, 279 } 280 281 fiw, err := gadget.NewFilesystemImageWriter(s.content, ps, s.work) 282 c.Assert(err, IsNil) 283 284 img := s.imgForPs(c, ps) 285 286 // modify filesystem 287 ps.Filesystem = "foofs" 288 289 err = fiw.Write(img, nil) 290 c.Assert(err, ErrorMatches, `internal error: filesystem "foofs" has no handler`) 291 } 292 293 func (s *filesystemImageMockedTestSuite) TestMountedWriterError(c *C) { 294 ps := &gadget.LaidOutStructure{ 295 VolumeStructure: &gadget.VolumeStructure{ 296 Filesystem: "happyfs", 297 Size: 2 * gadget.SizeMiB, 298 Content: []gadget.VolumeContent{ 299 {Source: "/foo", Target: "/"}, 300 }, 301 }, 302 } 303 304 cb := func(rootDir string, cbPs *gadget.LaidOutStructure) error { 305 return errors.New("unexpected call") 306 } 307 308 fiw, err := gadget.NewFilesystemImageWriter(s.content, ps, s.work) 309 c.Assert(err, IsNil) 310 311 img := s.imgForPs(c, ps) 312 313 // declared content does not exist in the content directory 314 err = fiw.Write(img, cb) 315 c.Assert(err, ErrorMatches, `cannot prepare filesystem content: cannot write filesystem content of source:/foo: .* no such file or directory`) 316 } 317 318 func (s *filesystemImageMockedTestSuite) TestBadWorkDirError(c *C) { 319 cb := func(rootDir string, cbPs *gadget.LaidOutStructure) error { 320 return errors.New("unexpected call") 321 } 322 323 badWork := filepath.Join(s.dir, "bad-work") 324 fiw, err := gadget.NewFilesystemImageWriter(s.content, s.psTrivial, badWork) 325 c.Assert(err, IsNil) 326 327 img := s.imgForPs(c, s.psTrivial) 328 329 err = fiw.Write(img, cb) 330 c.Assert(err, ErrorMatches, `cannot prepare staging directory: mkdir .*/bad-work/snap-stage-content-part-0001: no such file or directory`) 331 332 err = os.MkdirAll(filepath.Join(badWork, "snap-stage-content-part-0001"), 0755) 333 c.Assert(err, IsNil) 334 335 err = fiw.Write(img, cb) 336 c.Assert(err, ErrorMatches, `cannot prepare staging directory .*/bad-work/snap-stage-content-part-0001: path exists`) 337 } 338 339 func (s *filesystemImageMockedTestSuite) TestKeepsStagingDir(c *C) { 340 cb := func(rootDir string, cbPs *gadget.LaidOutStructure) error { 341 return nil 342 } 343 mkfsHappyFs := func(imgFile, label, contentsRootDir string) error { 344 return nil 345 } 346 restore := gadget.MockMkfsHandlers(map[string]gadget.MkfsFunc{ 347 "happyfs": mkfsHappyFs, 348 }) 349 defer restore() 350 351 fiw, err := gadget.NewFilesystemImageWriter(s.content, s.psTrivial, s.work) 352 c.Assert(err, IsNil) 353 354 img := s.imgForPs(c, s.psTrivial) 355 356 os.Setenv("SNAP_DEBUG_IMAGE_NO_CLEANUP", "1") 357 defer os.Unsetenv("SNAP_DEBUG_IMAGE_NO_CLEANUP") 358 err = fiw.Write(img, cb) 359 c.Assert(err, IsNil) 360 361 matches, err := filepath.Glob(s.work + "/*") 362 c.Assert(err, IsNil) 363 c.Assert(matches, HasLen, 1) 364 c.Assert(osutil.IsDirectory(filepath.Join(s.work, "snap-stage-content-part-0001")), Equals, true) 365 } 366 367 type filesystemImageMkfsTestSuite struct { 368 filesystemImageTestSuite 369 370 cmdFakeroot *testutil.MockCmd 371 cmdMkfsVfat *testutil.MockCmd 372 cmdMcopy *testutil.MockCmd 373 } 374 375 func (s *filesystemImageMkfsTestSuite) SetUpTest(c *C) { 376 s.filesystemImageTestSuite.SetUpTest(c) 377 378 // mkfs.ext4 is called via fakeroot wrapper 379 s.cmdFakeroot = testutil.MockCommand(c, "fakeroot", "") 380 s.AddCleanup(s.cmdFakeroot.Restore) 381 382 s.cmdMkfsVfat = testutil.MockCommand(c, "mkfs.vfat", "") 383 s.AddCleanup(s.cmdMkfsVfat.Restore) 384 385 s.cmdMcopy = testutil.MockCommand(c, "mcopy", "") 386 s.AddCleanup(s.cmdMcopy.Restore) 387 } 388 389 func (s *filesystemImageMkfsTestSuite) TearDownTest(c *C) { 390 s.filesystemImageTestSuite.TearDownTest(c) 391 } 392 393 var _ = Suite(&filesystemImageMkfsTestSuite{}) 394 395 func (s *filesystemImageMkfsTestSuite) TestExt4(c *C) { 396 psExt4 := &gadget.LaidOutStructure{ 397 VolumeStructure: &gadget.VolumeStructure{ 398 Filesystem: "ext4", 399 Size: 2 * gadget.SizeMiB, 400 Content: []gadget.VolumeContent{{Source: "/", Target: "/"}}, 401 }, 402 } 403 404 makeSizedFile(c, filepath.Join(s.content, "foo"), 1024, nil) 405 406 fiw, err := gadget.NewFilesystemImageWriter(s.content, psExt4, s.work) 407 c.Assert(err, IsNil) 408 409 img := s.imgForPs(c, psExt4) 410 err = fiw.Write(img, nil) 411 c.Assert(err, IsNil) 412 413 c.Check(s.cmdFakeroot.Calls(), HasLen, 1) 414 c.Check(s.cmdMkfsVfat.Calls(), HasLen, 0) 415 c.Check(s.cmdMcopy.Calls(), HasLen, 0) 416 } 417 418 func (s *filesystemImageMkfsTestSuite) TestVfat(c *C) { 419 psVfat := &gadget.LaidOutStructure{ 420 VolumeStructure: &gadget.VolumeStructure{ 421 Filesystem: "vfat", 422 Size: 2 * gadget.SizeMiB, 423 Content: []gadget.VolumeContent{{Source: "/", Target: "/"}}, 424 }, 425 } 426 427 makeSizedFile(c, filepath.Join(s.content, "foo"), 1024, nil) 428 429 fiw, err := gadget.NewFilesystemImageWriter(s.content, psVfat, s.work) 430 c.Assert(err, IsNil) 431 432 img := s.imgForPs(c, psVfat) 433 err = fiw.Write(img, nil) 434 c.Assert(err, IsNil) 435 436 c.Check(s.cmdFakeroot.Calls(), HasLen, 0) 437 c.Check(s.cmdMkfsVfat.Calls(), HasLen, 1) 438 c.Check(s.cmdMcopy.Calls(), HasLen, 1) 439 }