From a296b96af5050c4209981d804a4c9f23f8de3774 Mon Sep 17 00:00:00 2001 From: Itxaka Date: Wed, 19 Jul 2023 13:51:29 +0200 Subject: [PATCH] Add more testing Signed-off-by: Itxaka --- .../enki/pkg/action/action_suite_test.go | 28 ++ tools-image/enki/pkg/action/build-iso.go | 2 +- tools-image/enki/pkg/action/build_test.go | 195 ++++++++++ tools-image/enki/pkg/config/config.go | 49 +++ .../enki/pkg/utils/utils_suite_test.go | 29 ++ tools-image/enki/pkg/utils/utils_test.go | 368 ++++++++++++++++++ 6 files changed, 670 insertions(+), 1 deletion(-) create mode 100644 tools-image/enki/pkg/action/action_suite_test.go create mode 100644 tools-image/enki/pkg/action/build_test.go create mode 100644 tools-image/enki/pkg/utils/utils_suite_test.go create mode 100644 tools-image/enki/pkg/utils/utils_test.go diff --git a/tools-image/enki/pkg/action/action_suite_test.go b/tools-image/enki/pkg/action/action_suite_test.go new file mode 100644 index 0000000..b6d388b --- /dev/null +++ b/tools-image/enki/pkg/action/action_suite_test.go @@ -0,0 +1,28 @@ +/* +Copyright © 2022 SUSE LLC + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ +package action_test + +import ( + "testing" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" +) + +func TestActionSuite(t *testing.T) { + RegisterFailHandler(Fail) + RunSpecs(t, "Actions test suite") +} diff --git a/tools-image/enki/pkg/action/build-iso.go b/tools-image/enki/pkg/action/build-iso.go index a2e2e8e..5382693 100644 --- a/tools-image/enki/pkg/action/build-iso.go +++ b/tools-image/enki/pkg/action/build-iso.go @@ -37,7 +37,7 @@ func (b *BuildISOAction) ISORun() (err error) { cleanup := utils.NewCleanStack() defer func() { err = cleanup.Cleanup(err) }() - isoTmpDir, err := utils.TempDir(b.cfg.Fs, "", "elemental-iso") + isoTmpDir, err := utils.TempDir(b.cfg.Fs, "", "enki-iso") if err != nil { return err } diff --git a/tools-image/enki/pkg/action/build_test.go b/tools-image/enki/pkg/action/build_test.go new file mode 100644 index 0000000..fc72b16 --- /dev/null +++ b/tools-image/enki/pkg/action/build_test.go @@ -0,0 +1,195 @@ +/* + Copyright © 2022 SUSE LLC + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +package action_test + +import ( + "bytes" + "errors" + "fmt" + "github.com/kairos-io/enki/pkg/action" + "github.com/kairos-io/enki/pkg/config" + "github.com/kairos-io/enki/pkg/constants" + "github.com/kairos-io/enki/pkg/utils" + v1 "github.com/kairos-io/kairos-agent/v2/pkg/types/v1" + v1mock "github.com/kairos-io/kairos-agent/v2/tests/mocks" + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + "github.com/sirupsen/logrus" + "github.com/twpayne/go-vfs" + "github.com/twpayne/go-vfs/vfst" + "path/filepath" +) + +var _ = Describe("Runtime Actions", func() { + var cfg *v1.BuildConfig + var runner *v1mock.FakeRunner + var fs vfs.FS + var logger v1.Logger + var mounter *v1mock.ErrorMounter + var syscall *v1mock.FakeSyscall + var client *v1mock.FakeHTTPClient + var cloudInit *v1mock.FakeCloudInitRunner + var cleanup func() + var memLog *bytes.Buffer + var imageExtractor *v1mock.FakeImageExtractor + BeforeEach(func() { + runner = v1mock.NewFakeRunner() + syscall = &v1mock.FakeSyscall{} + mounter = v1mock.NewErrorMounter() + client = &v1mock.FakeHTTPClient{} + memLog = &bytes.Buffer{} + logger = v1.NewBufferLogger(memLog) + logger.SetLevel(logrus.DebugLevel) + cloudInit = &v1mock.FakeCloudInitRunner{} + fs, cleanup, _ = vfst.NewTestFS(map[string]interface{}{}) + imageExtractor = v1mock.NewFakeImageExtractor(logger) + + cfg = config.NewBuildConfig( + config.WithFs(fs), + config.WithRunner(runner), + config.WithLogger(logger), + config.WithMounter(mounter), + config.WithSyscall(syscall), + config.WithClient(client), + config.WithCloudInitRunner(cloudInit), + config.WithImageExtractor(imageExtractor), + ) + }) + AfterEach(func() { + cleanup() + }) + Describe("Build ISO", Label("iso"), func() { + var iso *v1.LiveISO + BeforeEach(func() { + iso = config.NewISO() + + tmpDir, err := utils.TempDir(fs, "", "test") + Expect(err).ShouldNot(HaveOccurred()) + + cfg.Date = false + cfg.OutDir = tmpDir + + runner.SideEffect = func(cmd string, args ...string) ([]byte, error) { + switch cmd { + case "xorriso": + err := fs.WriteFile(filepath.Join(tmpDir, "elemental.iso"), []byte("profound thoughts"), constants.FilePerm) + return []byte{}, err + default: + return []byte{}, nil + } + } + }) + It("Successfully builds an ISO from a Docker image", func() { + rootSrc, _ := v1.NewSrcFromURI("oci:image:version") + iso.RootFS = []*v1.ImageSource{rootSrc} + uefiSrc, _ := v1.NewSrcFromURI("oci:image:version") + iso.UEFI = []*v1.ImageSource{uefiSrc} + imageSrc, _ := v1.NewSrcFromURI("oci:image:version") + iso.Image = []*v1.ImageSource{imageSrc} + + // Create kernel and vmlinuz + // Thanks to the testfs stuff in utils.TempDir we know what the temp fs is gonna be as + // its predictable + bootDir := filepath.Join("/tmp/enki-iso/rootfs", "boot") + err := utils.MkdirAll(fs, bootDir, constants.DirPerm) + Expect(err).ShouldNot(HaveOccurred()) + _, err = fs.Create(filepath.Join(bootDir, "vmlinuz")) + Expect(err).ShouldNot(HaveOccurred()) + _, err = fs.Create(filepath.Join(bootDir, "initrd")) + Expect(err).ShouldNot(HaveOccurred()) + + buildISO := action.NewBuildISOAction(cfg, iso) + err = buildISO.ISORun() + + Expect(err).ShouldNot(HaveOccurred()) + }) + It("Fails if kernel or initrd is not found in rootfs", func() { + rootSrc, _ := v1.NewSrcFromURI("oci:image:version") + iso.RootFS = []*v1.ImageSource{rootSrc} + uefiSrc, _ := v1.NewSrcFromURI("oci:image:version") + iso.UEFI = []*v1.ImageSource{uefiSrc} + imageSrc, _ := v1.NewSrcFromURI("oci:image:version") + iso.Image = []*v1.ImageSource{imageSrc} + + By("fails without kernel") + buildISO := action.NewBuildISOAction(cfg, iso) + err := buildISO.ISORun() + Expect(err).Should(HaveOccurred()) + Expect(err.Error()).To(ContainSubstring("No file found with prefixes")) + Expect(err.Error()).To(ContainSubstring("uImage Image zImage vmlinuz image")) + + bootDir := filepath.Join("/tmp/enki-iso/rootfs", "boot") + err = utils.MkdirAll(fs, bootDir, constants.DirPerm) + Expect(err).ShouldNot(HaveOccurred()) + _, err = fs.Create(filepath.Join(bootDir, "vmlinuz")) + Expect(err).ShouldNot(HaveOccurred()) + + By("fails without initrd") + buildISO = action.NewBuildISOAction(cfg, iso) + err = buildISO.ISORun() + Expect(err).Should(HaveOccurred()) + Expect(err.Error()).To(ContainSubstring("No file found with prefixes")) + Expect(err.Error()).To(ContainSubstring("initrd initramfs")) + }) + It("Fails installing image sources", func() { + rootSrc, _ := v1.NewSrcFromURI("oci:image:version") + iso.RootFS = []*v1.ImageSource{rootSrc} + uefiSrc, _ := v1.NewSrcFromURI("oci:image:version") + iso.UEFI = []*v1.ImageSource{uefiSrc} + imageSrc, _ := v1.NewSrcFromURI("oci:image:version") + iso.Image = []*v1.ImageSource{imageSrc} + + imageExtractor.SideEffect = func(imageRef, destination, platformRef string) error { + return fmt.Errorf("uh oh") + } + + buildISO := action.NewBuildISOAction(cfg, iso) + err := buildISO.ISORun() + Expect(err).Should(HaveOccurred()) + Expect(err.Error()).To(ContainSubstring("uh oh")) + }) + It("Fails on ISO filesystem creation", func() { + rootSrc, _ := v1.NewSrcFromURI("oci:image:version") + iso.RootFS = []*v1.ImageSource{rootSrc} + uefiSrc, _ := v1.NewSrcFromURI("oci:image:version") + iso.UEFI = []*v1.ImageSource{uefiSrc} + imageSrc, _ := v1.NewSrcFromURI("oci:image:version") + iso.Image = []*v1.ImageSource{imageSrc} + + bootDir := filepath.Join("/tmp/enki-iso/rootfs", "boot") + err := utils.MkdirAll(fs, bootDir, constants.DirPerm) + Expect(err).ShouldNot(HaveOccurred()) + _, err = fs.Create(filepath.Join(bootDir, "vmlinuz")) + Expect(err).ShouldNot(HaveOccurred()) + _, err = fs.Create(filepath.Join(bootDir, "initrd")) + Expect(err).ShouldNot(HaveOccurred()) + + runner.SideEffect = func(command string, args ...string) ([]byte, error) { + if command == "xorriso" { + return []byte{}, errors.New("Burn ISO error") + } + return []byte{}, nil + } + + buildISO := action.NewBuildISOAction(cfg, iso) + err = buildISO.ISORun() + + Expect(err).Should(HaveOccurred()) + Expect(err.Error()).To(ContainSubstring("Burn ISO error")) + }) + }) +}) diff --git a/tools-image/enki/pkg/config/config.go b/tools-image/enki/pkg/config/config.go index 5e8ad02..ba32bf9 100644 --- a/tools-image/enki/pkg/config/config.go +++ b/tools-image/enki/pkg/config/config.go @@ -32,6 +32,13 @@ var decodeHook = viper.DecodeHook( ), ) +func WithFs(fs v1.FS) func(r *v1.Config) error { + return func(r *v1.Config) error { + r.Fs = fs + return nil + } +} + func WithLogger(logger v1.Logger) func(r *v1.Config) error { return func(r *v1.Config) error { r.Logger = logger @@ -39,6 +46,13 @@ func WithLogger(logger v1.Logger) func(r *v1.Config) error { } } +func WithSyscall(syscall v1.SyscallInterface) func(r *v1.Config) error { + return func(r *v1.Config) error { + r.Syscall = syscall + return nil + } +} + func WithMounter(mounter mount.Interface) func(r *v1.Config) error { return func(r *v1.Config) error { r.Mounter = mounter @@ -46,6 +60,41 @@ func WithMounter(mounter mount.Interface) func(r *v1.Config) error { } } +func WithRunner(runner v1.Runner) func(r *v1.Config) error { + return func(r *v1.Config) error { + r.Runner = runner + return nil + } +} + +func WithClient(client v1.HTTPClient) func(r *v1.Config) error { + return func(r *v1.Config) error { + r.Client = client + return nil + } +} + +func WithCloudInitRunner(ci v1.CloudInitRunner) func(r *v1.Config) error { + return func(r *v1.Config) error { + r.CloudInitRunner = ci + return nil + } +} + +func WithArch(arch string) func(r *v1.Config) error { + return func(r *v1.Config) error { + r.Arch = arch + return nil + } +} + +func WithImageExtractor(extractor v1.ImageExtractor) func(r *v1.Config) error { + return func(r *v1.Config) error { + r.ImageExtractor = extractor + return nil + } +} + type GenericOptions func(a *v1.Config) error func ReadConfigBuild(configDir string, flags *pflag.FlagSet, mounter mount.Interface) (*v1.BuildConfig, error) { diff --git a/tools-image/enki/pkg/utils/utils_suite_test.go b/tools-image/enki/pkg/utils/utils_suite_test.go new file mode 100644 index 0000000..ec88947 --- /dev/null +++ b/tools-image/enki/pkg/utils/utils_suite_test.go @@ -0,0 +1,29 @@ +/* +Copyright © 2021 SUSE LLC + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package utils_test + +import ( + "testing" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" +) + +func TestWhitebox(t *testing.T) { + RegisterFailHandler(Fail) + RunSpecs(t, "Utils test suite") +} diff --git a/tools-image/enki/pkg/utils/utils_test.go b/tools-image/enki/pkg/utils/utils_test.go new file mode 100644 index 0000000..fa19baf --- /dev/null +++ b/tools-image/enki/pkg/utils/utils_test.go @@ -0,0 +1,368 @@ +/* +Copyright © 2021 SUSE LLC + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package utils_test + +import ( + "errors" + "fmt" + conf "github.com/kairos-io/enki/pkg/config" + "github.com/kairos-io/enki/pkg/constants" + "github.com/kairos-io/enki/pkg/utils" + v1 "github.com/kairos-io/kairos-agent/v2/pkg/types/v1" + v1mock "github.com/kairos-io/kairos-agent/v2/tests/mocks" + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + "github.com/twpayne/go-vfs" + "github.com/twpayne/go-vfs/vfst" + "os" + "strings" +) + +var _ = Describe("Utils", Label("utils"), func() { + var config *v1.Config + var runner *v1mock.FakeRunner + var logger v1.Logger + var syscall *v1mock.FakeSyscall + var client *v1mock.FakeHTTPClient + var mounter *v1mock.ErrorMounter + var fs vfs.FS + var cleanup func() + + BeforeEach(func() { + runner = v1mock.NewFakeRunner() + syscall = &v1mock.FakeSyscall{} + mounter = v1mock.NewErrorMounter() + client = &v1mock.FakeHTTPClient{} + logger = v1.NewNullLogger() + // Ensure /tmp exists in the VFS + fs, cleanup, _ = vfst.NewTestFS(nil) + fs.Mkdir("/tmp", constants.DirPerm) + fs.Mkdir("/run", constants.DirPerm) + fs.Mkdir("/etc", constants.DirPerm) + + config = conf.NewConfig( + conf.WithFs(fs), + conf.WithRunner(runner), + conf.WithLogger(logger), + conf.WithMounter(mounter), + conf.WithSyscall(syscall), + conf.WithClient(client), + ) + }) + AfterEach(func() { cleanup() }) + + Describe("Chroot", Label("chroot"), func() { + var chroot *utils.Chroot + BeforeEach(func() { + chroot = utils.NewChroot( + "/whatever", + config, + ) + }) + Describe("ChrootedCallback method", func() { + It("runs a callback in a chroot", func() { + err := utils.ChrootedCallback(config, "/somepath", map[string]string{}, func() error { + return nil + }) + Expect(err).ShouldNot(HaveOccurred()) + err = utils.ChrootedCallback(config, "/somepath", map[string]string{}, func() error { + return fmt.Errorf("callback error") + }) + Expect(err).Should(HaveOccurred()) + Expect(err.Error()).To(ContainSubstring("callback error")) + }) + }) + Describe("on success", func() { + It("command should be called in the chroot", func() { + _, err := chroot.Run("chroot-command") + Expect(err).To(BeNil()) + Expect(syscall.WasChrootCalledWith("/whatever")).To(BeTrue()) + }) + It("commands should be called with a customized chroot", func() { + chroot.SetExtraMounts(map[string]string{"/real/path": "/in/chroot/path"}) + Expect(chroot.Prepare()).To(BeNil()) + defer chroot.Close() + _, err := chroot.Run("chroot-command") + Expect(err).To(BeNil()) + Expect(syscall.WasChrootCalledWith("/whatever")).To(BeTrue()) + _, err = chroot.Run("chroot-another-command") + Expect(err).To(BeNil()) + }) + It("runs a callback in a custom chroot", func() { + called := false + callback := func() error { + called = true + return nil + } + err := chroot.RunCallback(callback) + Expect(err).To(BeNil()) + Expect(syscall.WasChrootCalledWith("/whatever")).To(BeTrue()) + Expect(called).To(BeTrue()) + }) + }) + Describe("on failure", func() { + It("should return error if chroot-command fails", func() { + runner.ReturnError = errors.New("run error") + _, err := chroot.Run("chroot-command") + Expect(err).NotTo(BeNil()) + Expect(syscall.WasChrootCalledWith("/whatever")).To(BeTrue()) + }) + It("should return error if callback fails", func() { + called := false + callback := func() error { + called = true + return errors.New("Callback error") + } + err := chroot.RunCallback(callback) + Expect(err).NotTo(BeNil()) + Expect(syscall.WasChrootCalledWith("/whatever")).To(BeTrue()) + Expect(called).To(BeTrue()) + }) + It("should return error if preparing twice before closing", func() { + Expect(chroot.Prepare()).To(BeNil()) + defer chroot.Close() + Expect(chroot.Prepare()).NotTo(BeNil()) + Expect(chroot.Close()).To(BeNil()) + Expect(chroot.Prepare()).To(BeNil()) + }) + It("should return error if failed to chroot", func() { + syscall.ErrorOnChroot = true + _, err := chroot.Run("chroot-command") + Expect(err).ToNot(BeNil()) + Expect(syscall.WasChrootCalledWith("/whatever")).To(BeTrue()) + Expect(err.Error()).To(ContainSubstring("chroot error")) + }) + It("should return error if failed to mount on prepare", Label("mount"), func() { + mounter.ErrorOnMount = true + _, err := chroot.Run("chroot-command") + Expect(err).ToNot(BeNil()) + Expect(err.Error()).To(ContainSubstring("mount error")) + }) + It("should return error if failed to unmount on close", Label("unmount"), func() { + mounter.ErrorOnUnmount = true + _, err := chroot.Run("chroot-command") + Expect(err).ToNot(BeNil()) + Expect(err.Error()).To(ContainSubstring("failed closing chroot")) + }) + }) + }) + Describe("CopyFile", Label("CopyFile"), func() { + It("Copies source file to target file", func() { + err := utils.MkdirAll(fs, "/some", constants.DirPerm) + Expect(err).ShouldNot(HaveOccurred()) + _, err = fs.Create("/some/file") + Expect(err).ShouldNot(HaveOccurred()) + _, err = fs.Stat("/some/otherfile") + Expect(err).Should(HaveOccurred()) + Expect(utils.CopyFile(fs, "/some/file", "/some/otherfile")).ShouldNot(HaveOccurred()) + e, err := utils.Exists(fs, "/some/otherfile") + Expect(err).ShouldNot(HaveOccurred()) + Expect(e).To(BeTrue()) + }) + It("Copies source file to target folder", func() { + err := utils.MkdirAll(fs, "/some", constants.DirPerm) + Expect(err).ShouldNot(HaveOccurred()) + err = utils.MkdirAll(fs, "/someotherfolder", constants.DirPerm) + Expect(err).ShouldNot(HaveOccurred()) + _, err = fs.Create("/some/file") + Expect(err).ShouldNot(HaveOccurred()) + _, err = fs.Stat("/someotherfolder/file") + Expect(err).Should(HaveOccurred()) + Expect(utils.CopyFile(fs, "/some/file", "/someotherfolder")).ShouldNot(HaveOccurred()) + e, err := utils.Exists(fs, "/someotherfolder/file") + Expect(err).ShouldNot(HaveOccurred()) + Expect(e).To(BeTrue()) + }) + It("Fails to open non existing file", func() { + err := utils.MkdirAll(fs, "/some", constants.DirPerm) + Expect(err).ShouldNot(HaveOccurred()) + Expect(utils.CopyFile(fs, "/some/file", "/some/otherfile")).NotTo(BeNil()) + _, err = fs.Stat("/some/otherfile") + Expect(err).NotTo(BeNil()) + }) + It("Fails to copy on non writable target", func() { + err := utils.MkdirAll(fs, "/some", constants.DirPerm) + Expect(err).ShouldNot(HaveOccurred()) + fs.Create("/some/file") + _, err = fs.Stat("/some/otherfile") + Expect(err).NotTo(BeNil()) + fs = vfs.NewReadOnlyFS(fs) + Expect(utils.CopyFile(fs, "/some/file", "/some/otherfile")).NotTo(BeNil()) + _, err = fs.Stat("/some/otherfile") + Expect(err).NotTo(BeNil()) + }) + }) + Describe("CreateDirStructure", Label("CreateDirStructure"), func() { + It("Creates essential directories", func() { + dirList := []string{"sys", "proc", "dev", "tmp", "boot", "usr/local", "oem"} + for _, dir := range dirList { + _, err := fs.Stat(fmt.Sprintf("/my/root/%s", dir)) + Expect(err).NotTo(BeNil()) + } + Expect(utils.CreateDirStructure(fs, "/my/root")).To(BeNil()) + for _, dir := range dirList { + fi, err := fs.Stat(fmt.Sprintf("/my/root/%s", dir)) + Expect(err).To(BeNil()) + if fi.Name() == "tmp" { + Expect(fmt.Sprintf("%04o", fi.Mode().Perm())).To(Equal("0777")) + Expect(fi.Mode() & os.ModeSticky).NotTo(Equal(0)) + } + if fi.Name() == "sys" { + Expect(fmt.Sprintf("%04o", fi.Mode().Perm())).To(Equal("0555")) + } + } + }) + It("Fails on non writable target", func() { + fs = vfs.NewReadOnlyFS(fs) + Expect(utils.CreateDirStructure(fs, "/my/root")).NotTo(BeNil()) + }) + }) + Describe("DirSize", Label("fs"), func() { + BeforeEach(func() { + err := utils.MkdirAll(fs, "/folder/subfolder", constants.DirPerm) + Expect(err).ShouldNot(HaveOccurred()) + f, err := fs.Create("/folder/file") + Expect(err).ShouldNot(HaveOccurred()) + err = f.Truncate(1024) + Expect(err).ShouldNot(HaveOccurred()) + f, err = fs.Create("/folder/subfolder/file") + Expect(err).ShouldNot(HaveOccurred()) + err = f.Truncate(2048) + Expect(err).ShouldNot(HaveOccurred()) + }) + It("Returns the expected size of a test folder", func() { + size, err := utils.DirSize(fs, "/folder") + Expect(err).ShouldNot(HaveOccurred()) + Expect(size).To(Equal(int64(3072))) + }) + }) + Describe("CalcFileChecksum", Label("checksum"), func() { + It("compute correct sha256 checksum", func() { + testData := strings.Repeat("abcdefghilmnopqrstuvz\n", 20) + testDataSHA256 := "7f182529f6362ae9cfa952ab87342a7180db45d2c57b52b50a68b6130b15a422" + + err := fs.Mkdir("/iso", constants.DirPerm) + Expect(err).ShouldNot(HaveOccurred()) + err = fs.WriteFile("/iso/test.iso", []byte(testData), 0644) + Expect(err).ShouldNot(HaveOccurred()) + + checksum, err := utils.CalcFileChecksum(fs, "/iso/test.iso") + Expect(err).ShouldNot(HaveOccurred()) + Expect(checksum).To(Equal(testDataSHA256)) + }) + }) + Describe("CreateSquashFS", Label("CreateSquashFS"), func() { + It("runs with no options if none given", func() { + err := utils.CreateSquashFS(runner, logger, "source", "dest", []string{}) + Expect(runner.IncludesCmds([][]string{ + {"mksquashfs", "source", "dest"}, + })).To(BeNil()) + Expect(err).ToNot(HaveOccurred()) + }) + It("runs with options if given", func() { + err := utils.CreateSquashFS(runner, logger, "source", "dest", constants.GetDefaultSquashfsOptions()) + cmd := []string{"mksquashfs", "source", "dest"} + cmd = append(cmd, constants.GetDefaultSquashfsOptions()...) + Expect(runner.IncludesCmds([][]string{ + cmd, + })).To(BeNil()) + Expect(err).ToNot(HaveOccurred()) + }) + It("returns an error if it fails", func() { + runner.ReturnError = errors.New("error") + err := utils.CreateSquashFS(runner, logger, "source", "dest", []string{}) + Expect(runner.IncludesCmds([][]string{ + {"mksquashfs", "source", "dest"}, + })).To(BeNil()) + Expect(err).To(HaveOccurred()) + }) + }) + Describe("CleanStack", Label("CleanStack"), func() { + var cleaner *utils.CleanStack + BeforeEach(func() { + cleaner = utils.NewCleanStack() + }) + It("Adds a callback to the stack and pops it", func() { + var flag bool + callback := func() error { + flag = true + return nil + } + Expect(cleaner.Pop()).To(BeNil()) + cleaner.Push(callback) + poppedJob := cleaner.Pop() + Expect(poppedJob).NotTo(BeNil()) + poppedJob() + Expect(flag).To(BeTrue()) + }) + It("On Cleanup runs callback stack in reverse order", func() { + result := "" + callback1 := func() error { + result = result + "one " + return nil + } + callback2 := func() error { + result = result + "two " + return nil + } + callback3 := func() error { + result = result + "three " + return nil + } + cleaner.Push(callback1) + cleaner.Push(callback2) + cleaner.Push(callback3) + cleaner.Cleanup(nil) + Expect(result).To(Equal("three two one ")) + }) + It("On Cleanup keeps former error and all callbacks are executed", func() { + err := errors.New("Former error") + count := 0 + callback := func() error { + count++ + if count == 2 { + return errors.New("Cleanup Error") + } + return nil + } + cleaner.Push(callback) + cleaner.Push(callback) + cleaner.Push(callback) + err = cleaner.Cleanup(err) + Expect(count).To(Equal(3)) + Expect(err.Error()).To(ContainSubstring("Former error")) + }) + It("On Cleanup error reports first error and all callbacks are executed", func() { + var err error + count := 0 + callback := func() error { + count++ + if count >= 2 { + return errors.New(fmt.Sprintf("Cleanup error %d", count)) + } + return nil + } + cleaner.Push(callback) + cleaner.Push(callback) + cleaner.Push(callback) + err = cleaner.Cleanup(err) + Expect(count).To(Equal(3)) + Expect(err.Error()).To(ContainSubstring("Cleanup error 2")) + Expect(err.Error()).To(ContainSubstring("Cleanup error 3")) + }) + }) +})