func init() {
	Describe("renderedJobApplier", func() {
		var (
			jobsBc                 *fakebc.FakeBundleCollection
			jobSupervisor          *fakejobsuper.FakeJobSupervisor
			packageApplierProvider *fakepa.FakePackageApplierProvider
			blobstore              *fakeblob.FakeBlobstore
			compressor             *fakecmd.FakeCompressor
			fs                     *fakesys.FakeFileSystem
			applier                JobApplier
		)

		BeforeEach(func() {
			jobsBc = fakebc.NewFakeBundleCollection()
			jobSupervisor = fakejobsuper.NewFakeJobSupervisor()
			packageApplierProvider = fakepa.NewFakePackageApplierProvider()
			blobstore = fakeblob.NewFakeBlobstore()
			fs = fakesys.NewFakeFileSystem()
			compressor = fakecmd.NewFakeCompressor()
			logger := boshlog.NewLogger(boshlog.LevelNone)
			applier = NewRenderedJobApplier(
				jobsBc,
				jobSupervisor,
				packageApplierProvider,
				blobstore,
				compressor,
				fs,
				logger,
			)
		})

		Describe("Prepare & Apply", func() {
			var (
				job    models.Job
				bundle *fakebc.FakeBundle
			)

			BeforeEach(func() {
				job, bundle = buildJob(jobsBc)
			})

			ItInstallsJob := func(act func() error) {
				It("returns error when installing job fails", func() {
					bundle.InstallError = errors.New("fake-install-error")

					err := act()
					Expect(err).To(HaveOccurred())
					Expect(err.Error()).To(ContainSubstring("fake-install-error"))
				})

				It("downloads and later cleans up downloaded job template blob", func() {
					blobstore.GetFileName = "/fake-blobstore-file-name"

					err := act()
					Expect(err).ToNot(HaveOccurred())
					Expect(blobstore.GetBlobIDs[0]).To(Equal("fake-blobstore-id"))
					Expect(blobstore.GetFingerprints[0]).To(Equal("fake-blob-sha1"))

					// downloaded file is cleaned up
					Expect(blobstore.CleanUpFileName).To(Equal("/fake-blobstore-file-name"))
				})

				It("returns error when downloading job template blob fails", func() {
					blobstore.GetError = errors.New("fake-get-error")

					err := act()
					Expect(err).To(HaveOccurred())
					Expect(err.Error()).To(ContainSubstring("fake-get-error"))
				})

				It("decompresses job template blob to tmp path and later cleans it up", func() {
					fs.TempDirDir = "/fake-tmp-dir"
					blobstore.GetFileName = "/fake-blobstore-file-name"

					var tmpDirExistsBeforeInstall bool

					bundle.InstallCallBack = func() {
						tmpDirExistsBeforeInstall = true
					}

					err := act()
					Expect(err).ToNot(HaveOccurred())

					Expect(compressor.DecompressFileToDirTarballPaths[0]).To(Equal("/fake-blobstore-file-name"))
					Expect(compressor.DecompressFileToDirDirs[0]).To(Equal("/fake-tmp-dir"))

					// tmp dir exists before bundle install
					Expect(tmpDirExistsBeforeInstall).To(BeTrue())

					// tmp dir is cleaned up after install
					Expect(fs.FileExists(fs.TempDirDir)).To(BeFalse())
				})

				It("returns error when temporary directory creation fails", func() {
					fs.TempDirError = errors.New("fake-filesystem-tempdir-error")

					err := act()
					Expect(err).To(HaveOccurred())
					Expect(err.Error()).To(ContainSubstring("fake-filesystem-tempdir-error"))
				})

				It("returns error when decompressing job template fails", func() {
					compressor.DecompressFileToDirErr = errors.New("fake-decompress-error")

					err := act()
					Expect(err).To(HaveOccurred())
					Expect(err.Error()).To(ContainSubstring("fake-decompress-error"))
				})

				It("returns error when getting the list of bin files fails", func() {
					fs.GlobErr = errors.New("fake-glob-error")

					err := act()
					Expect(err).To(HaveOccurred())
					Expect(err.Error()).To(ContainSubstring("fake-glob-error"))
				})

				It("returns error when changing permissions on bin files fails", func() {
					fs.TempDirDir = "/fake-tmp-dir"

					fs.SetGlob("/fake-tmp-dir/fake-path-in-archive/bin/*", []string{
						"/fake-tmp-dir/fake-path-in-archive/bin/test",
					})

					fs.ChmodErr = errors.New("fake-chmod-error")

					err := act()
					Expect(err).To(HaveOccurred())
					Expect(err.Error()).To(ContainSubstring("fake-chmod-error"))
				})

				It("installs bundle from decompressed tmp path of a job template", func() {
					fs.TempDirDir = "/fake-tmp-dir"

					var installedBeforeDecompression bool

					compressor.DecompressFileToDirCallBack = func() {
						installedBeforeDecompression = bundle.Installed
					}

					err := act()
					Expect(err).ToNot(HaveOccurred())

					// bundle installation did not happen before decompression
					Expect(installedBeforeDecompression).To(BeFalse())

					// make sure that bundle install happened after decompression
					Expect(bundle.InstallSourcePath).To(Equal("/fake-tmp-dir/fake-path-in-archive"))
				})

				It("sets executable bit for files in bin", func() {
					fs.TempDirDir = "/fake-tmp-dir"

					compressor.DecompressFileToDirCallBack = func() {
						fs.WriteFile("/fake-tmp-dir/fake-path-in-archive/bin/test1", []byte{})
						fs.WriteFile("/fake-tmp-dir/fake-path-in-archive/bin/test2", []byte{})
						fs.WriteFile("/fake-tmp-dir/fake-path-in-archive/config/test", []byte{})
					}

					fs.SetGlob("/fake-tmp-dir/fake-path-in-archive/bin/*", []string{
						"/fake-tmp-dir/fake-path-in-archive/bin/test1",
						"/fake-tmp-dir/fake-path-in-archive/bin/test2",
					})

					var binTest1Stats, binTest2Stats, configTestStats *fakesys.FakeFileStats

					bundle.InstallCallBack = func() {
						binTest1Stats = fs.GetFileTestStat("/fake-tmp-dir/fake-path-in-archive/bin/test1")
						binTest2Stats = fs.GetFileTestStat("/fake-tmp-dir/fake-path-in-archive/bin/test2")
						configTestStats = fs.GetFileTestStat("/fake-tmp-dir/fake-path-in-archive/config/test")
					}

					err := act()
					Expect(err).ToNot(HaveOccurred())

					// bin files are executable
					Expect(int(binTest1Stats.FileMode)).To(Equal(0755))
					Expect(int(binTest2Stats.FileMode)).To(Equal(0755))

					// non-bin files are not made executable
					Expect(int(configTestStats.FileMode)).ToNot(Equal(0755))
				})
			}

			ItUpdatesPackages := func(act func() error) {
				var packageApplier *fakepa.FakePackageApplier

				BeforeEach(func() {
					packageApplier = fakepa.NewFakePackageApplier()
					packageApplierProvider.JobSpecificPackageAppliers[job.Name] = packageApplier
				})

				It("applies each package that job depends on and then cleans up packages", func() {
					err := act()
					Expect(err).ToNot(HaveOccurred())
					Expect(packageApplier.ActionsCalled).To(Equal([]string{"Apply", "Apply", "KeepOnly"}))
					Expect(len(packageApplier.AppliedPackages)).To(Equal(2)) // present
					Expect(packageApplier.AppliedPackages).To(Equal(job.Packages))
				})

				It("returns error when applying package that job depends on fails", func() {
					packageApplier.ApplyError = errors.New("fake-apply-err")

					err := act()
					Expect(err).To(HaveOccurred())
					Expect(err.Error()).To(ContainSubstring("fake-apply-err"))
				})

				It("keeps only currently required packages but does not completely uninstall them", func() {
					err := act()
					Expect(err).ToNot(HaveOccurred())
					Expect(len(packageApplier.KeptOnlyPackages)).To(Equal(2)) // present
					Expect(packageApplier.KeptOnlyPackages).To(Equal(job.Packages))
				})

				It("returns error when keeping only currently required packages fails", func() {
					packageApplier.KeepOnlyErr = errors.New("fake-keep-only-err")

					err := act()
					Expect(err).To(HaveOccurred())
					Expect(err.Error()).To(ContainSubstring("fake-keep-only-err"))
				})
			}

			Describe("Prepare", func() {
				act := func() error { return applier.Prepare(job) }

				It("return an error if getting file bundle fails", func() {
					jobsBc.GetErr = errors.New("fake-get-bundle-error")

					err := act()
					Expect(err).To(HaveOccurred())
					Expect(err.Error()).To(ContainSubstring("fake-get-bundle-error"))
				})

				It("returns an error if checking for installed path fails", func() {
					bundle.IsInstalledErr = errors.New("fake-is-installed-error")

					err := act()
					Expect(err).To(HaveOccurred())
					Expect(err.Error()).To(ContainSubstring("fake-is-installed-error"))
				})

				Context("when job is already installed", func() {
					BeforeEach(func() {
						bundle.Installed = true
					})

					It("does not install", func() {
						err := act()
						Expect(err).ToNot(HaveOccurred())
						Expect(bundle.ActionsCalled).To(Equal([]string{})) // no Install
					})

					It("does not download the job template", func() {
						err := act()
						Expect(err).ToNot(HaveOccurred())
						Expect(blobstore.GetBlobIDs).To(BeNil())
					})
				})

				Context("when job is not installed", func() {
					BeforeEach(func() {
						bundle.Installed = false
					})

					It("installs job (but does not enable)", func() {
						err := act()
						Expect(err).ToNot(HaveOccurred())
						Expect(bundle.ActionsCalled).To(Equal([]string{"Install"}))
					})

					ItInstallsJob(act)
				})
			})

			Describe("Apply", func() {
				act := func() error { return applier.Apply(job) }

				It("return an error if getting file bundle fails", func() {
					jobsBc.GetErr = errors.New("fake-get-bundle-error")

					err := act()
					Expect(err).To(HaveOccurred())
					Expect(err.Error()).To(ContainSubstring("fake-get-bundle-error"))
				})

				It("returns an error if checking for installed path fails", func() {
					bundle.IsInstalledErr = errors.New("fake-is-installed-error")

					err := act()
					Expect(err).To(HaveOccurred())
					Expect(err.Error()).To(ContainSubstring("fake-is-installed-error"))
				})

				Context("when job is already installed", func() {
					BeforeEach(func() {
						bundle.Installed = true
					})

					It("does not install but only enables job", func() {
						err := act()
						Expect(err).ToNot(HaveOccurred())
						Expect(bundle.ActionsCalled).To(Equal([]string{"Enable"})) // no Install
					})

					It("returns error when job enable fails", func() {
						bundle.EnableError = errors.New("fake-enable-error")

						err := act()
						Expect(err).To(HaveOccurred())
						Expect(err.Error()).To(ContainSubstring("fake-enable-error"))
					})

					It("does not download the job template", func() {
						err := act()
						Expect(err).ToNot(HaveOccurred())
						Expect(blobstore.GetBlobIDs).To(BeNil())
					})

					ItUpdatesPackages(act)
				})

				Context("when job is not installed", func() {
					BeforeEach(func() {
						bundle.Installed = false
					})

					It("installs and enables job", func() {
						err := act()
						Expect(err).ToNot(HaveOccurred())
						Expect(bundle.ActionsCalled).To(Equal([]string{"Install", "Enable"}))
					})

					It("returns error when job enable fails", func() {
						bundle.EnableError = errors.New("fake-enable-error")

						err := act()
						Expect(err).To(HaveOccurred())
						Expect(err.Error()).To(ContainSubstring("fake-enable-error"))
					})

					ItInstallsJob(act)

					ItUpdatesPackages(act)
				})
			})
		})

		Describe("Configure", func() {
			It("adds job to the job supervisor", func() {
				job, bundle := buildJob(jobsBc)

				fs := fakesys.NewFakeFileSystem()
				fs.WriteFileString("/path/to/job/monit", "some conf")
				fs.SetGlob("/path/to/job/*.monit", []string{"/path/to/job/subjob.monit"})

				bundle.GetDirPath = "/path/to/job"
				bundle.GetDirFs = fs

				err := applier.Configure(job, 0)
				Expect(err).ToNot(HaveOccurred())

				Expect(len(jobSupervisor.AddJobArgs)).To(Equal(2))

				Expect(jobSupervisor.AddJobArgs[0]).To(Equal(fakejobsuper.AddJobArgs{
					Name:       job.Name,
					Index:      0,
					ConfigPath: "/path/to/job/monit",
				}))

				Expect(jobSupervisor.AddJobArgs[1]).To(Equal(fakejobsuper.AddJobArgs{
					Name:       job.Name + "_subjob",
					Index:      0,
					ConfigPath: "/path/to/job/subjob.monit",
				}))
			})

			It("does not require monit script", func() {
				job, bundle := buildJob(jobsBc)

				fs := fakesys.NewFakeFileSystem()
				bundle.GetDirFs = fs

				err := applier.Configure(job, 0)
				Expect(err).ToNot(HaveOccurred())
				Expect(len(jobSupervisor.AddJobArgs)).To(Equal(0))
			})
		})

		Describe("KeepOnly", func() {
			It("first disables and then uninstalls jobs that are not in keeponly list", func() {
				_, bundle1 := buildJob(jobsBc)
				job2, bundle2 := buildJob(jobsBc)
				_, bundle3 := buildJob(jobsBc)
				job4, bundle4 := buildJob(jobsBc)

				jobsBc.ListBundles = []boshbc.Bundle{bundle1, bundle2, bundle3, bundle4}

				err := applier.KeepOnly([]models.Job{job4, job2})
				Expect(err).ToNot(HaveOccurred())

				Expect(bundle1.ActionsCalled).To(Equal([]string{"Disable", "Uninstall"}))
				Expect(bundle2.ActionsCalled).To(Equal([]string{}))
				Expect(bundle3.ActionsCalled).To(Equal([]string{"Disable", "Uninstall"}))
				Expect(bundle4.ActionsCalled).To(Equal([]string{}))
			})

			It("returns error when bundle collection fails to return list of installed bundles", func() {
				jobsBc.ListErr = errors.New("fake-bc-list-error")

				err := applier.KeepOnly([]models.Job{})
				Expect(err).To(HaveOccurred())
				Expect(err.Error()).To(ContainSubstring("fake-bc-list-error"))
			})

			It("returns error when bundle collection cannot retrieve bundle for keep-only job", func() {
				job1, bundle1 := buildJob(jobsBc)

				jobsBc.ListBundles = []boshbc.Bundle{bundle1}
				jobsBc.GetErr = errors.New("fake-bc-get-error")

				err := applier.KeepOnly([]models.Job{job1})
				Expect(err).To(HaveOccurred())
				Expect(err.Error()).To(ContainSubstring("fake-bc-get-error"))
			})

			It("returns error when at least one bundle cannot be disabled", func() {
				_, bundle1 := buildJob(jobsBc)

				jobsBc.ListBundles = []boshbc.Bundle{bundle1}
				bundle1.DisableErr = errors.New("fake-bc-disable-error")

				err := applier.KeepOnly([]models.Job{})
				Expect(err).To(HaveOccurred())
				Expect(err.Error()).To(ContainSubstring("fake-bc-disable-error"))
			})

			It("returns error when at least one bundle cannot be uninstalled", func() {
				_, bundle1 := buildJob(jobsBc)

				jobsBc.ListBundles = []boshbc.Bundle{bundle1}
				bundle1.UninstallErr = errors.New("fake-bc-uninstall-error")

				err := applier.KeepOnly([]models.Job{})
				Expect(err).To(HaveOccurred())
				Expect(err.Error()).To(ContainSubstring("fake-bc-uninstall-error"))
			})
		})
	})
}
Example #2
0
			Expect(err).ToNot(HaveOccurred())

			defer file.Close()

			handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
				io.Copy(w, file)
				Expect(r.Method).To(Equal("GET"))
				Expect(r.URL.Path).To(Equal("/_status2"))
				Expect(r.URL.Query().Get("format")).To(Equal("xml"))
			})

			ts := httptest.NewServer(handler)

			defer ts.Close()

			logger := boshlog.NewLogger(boshlog.LevelNone)
			client := NewHTTPClient(
				ts.Listener.Addr().String(),
				"fake-user",
				"fake-pass",
				http.DefaultClient,
				1*time.Millisecond,
				logger,
			)

			status, err := client.Status()
			Expect(err).ToNot(HaveOccurred())

			expectedServices := []Service{
				Service{Monitored: true, Status: "running"},
				Service{Monitored: false, Status: "unknown"},
func init() {
	Describe("concreteManagerProvider", func() {
		Describe("NewManager", func() {
			It("returns manager with tasks.json as its tasks path", func() {
				logger := boshlog.NewLogger(boshlog.LevelNone)
				fs := fakesys.NewFakeFileSystem()

				taskInfo := boshtask.TaskInfo{
					TaskID:  "fake-task-id",
					Method:  "fake-method",
					Payload: []byte("fake-payload"),
				}

				manager := boshtask.NewManagerProvider().NewManager(logger, fs, "/dir/path")
				err := manager.AddTaskInfo(taskInfo)
				Expect(err).ToNot(HaveOccurred())

				// Check expected file location with another manager
				otherManager := boshtask.NewManager(logger, fs, "/dir/path/tasks.json")

				taskInfos, err := otherManager.GetTaskInfos()
				Expect(err).ToNot(HaveOccurred())
				Expect(taskInfos).To(Equal([]boshtask.TaskInfo{taskInfo}))
			})
		})
	})

	Describe("concreteManager", func() {
		var (
			logger  boshlog.Logger
			fs      *fakesys.FakeFileSystem
			manager boshtask.Manager
		)

		BeforeEach(func() {
			logger = boshlog.NewLogger(boshlog.LevelNone)
			fs = fakesys.NewFakeFileSystem()
			manager = boshtask.NewManager(logger, fs, "/dir/path")
		})

		Describe("GetTaskInfos", func() {
			It("can load multiple tasks", func() {
				err := manager.AddTaskInfo(boshtask.TaskInfo{
					TaskID:  "fake-task-id-1",
					Method:  "fake-method-1",
					Payload: []byte("fake-payload-1"),
				})
				Expect(err).ToNot(HaveOccurred())

				err = manager.AddTaskInfo(boshtask.TaskInfo{
					TaskID:  "fake-task-id-2",
					Method:  "fake-method-2",
					Payload: []byte("fake-payload-2"),
				})
				Expect(err).ToNot(HaveOccurred())

				// Make sure we are not getting cached copy of taskInfos
				reloadedManager := boshtask.NewManager(logger, fs, "/dir/path")

				taskInfos, err := reloadedManager.GetTaskInfos()
				Expect(err).ToNot(HaveOccurred())
				Expect(taskInfos).To(Equal([]boshtask.TaskInfo{
					boshtask.TaskInfo{
						TaskID:  "fake-task-id-1",
						Method:  "fake-method-1",
						Payload: []byte("fake-payload-1"),
					},
					boshtask.TaskInfo{
						TaskID:  "fake-task-id-2",
						Method:  "fake-method-2",
						Payload: []byte("fake-payload-2"),
					},
				}))
			})

			It("succeeds when there is no tasks (file is not present)", func() {
				taskInfos, err := manager.GetTaskInfos()
				Expect(err).ToNot(HaveOccurred())
				Expect(len(taskInfos)).To(Equal(0))
			})

			It("returns an error when failing to load tasks from the file that exists", func() {
				err := manager.AddTaskInfo(boshtask.TaskInfo{
					TaskID:  "fake-task-id-2",
					Method:  "fake-method-2",
					Payload: []byte("fake-payload-2"),
				})
				Expect(err).ToNot(HaveOccurred())

				fs.ReadFileError = errors.New("fake-read-error")

				_, err = manager.GetTaskInfos()
				Expect(err).To(HaveOccurred())
				Expect(err.Error()).To(ContainSubstring("fake-read-error"))
			})
		})

		Describe("AddTaskInfo", func() {
			It("can add multiple tasks", func() {
				err := manager.AddTaskInfo(boshtask.TaskInfo{
					TaskID:  "fake-task-id-1",
					Method:  "fake-method-1",
					Payload: []byte("fake-payload-1"),
				})
				Expect(err).ToNot(HaveOccurred())

				err = manager.AddTaskInfo(boshtask.TaskInfo{
					TaskID:  "fake-task-id-2",
					Method:  "fake-method-2",
					Payload: []byte("fake-payload-2"),
				})
				Expect(err).ToNot(HaveOccurred())

				content, err := fs.ReadFile("/dir/path")
				Expect(err).ToNot(HaveOccurred())

				var decodedMap map[string]boshtask.TaskInfo

				err = json.Unmarshal(content, &decodedMap)
				Expect(err).ToNot(HaveOccurred())
				Expect(decodedMap).To(Equal(map[string]boshtask.TaskInfo{
					"fake-task-id-1": boshtask.TaskInfo{
						TaskID:  "fake-task-id-1",
						Method:  "fake-method-1",
						Payload: []byte("fake-payload-1"),
					},
					"fake-task-id-2": boshtask.TaskInfo{
						TaskID:  "fake-task-id-2",
						Method:  "fake-method-2",
						Payload: []byte("fake-payload-2"),
					},
				}))
			})

			It("returns an error when failing to save task", func() {
				fs.WriteToFileError = errors.New("fake-write-error")

				err := manager.AddTaskInfo(boshtask.TaskInfo{
					TaskID:  "fake-task-id",
					Method:  "fake-method",
					Payload: []byte("fake-payload"),
				})
				Expect(err).To(HaveOccurred())
				Expect(err.Error()).To(ContainSubstring("fake-write-error"))
			})
		})

		Describe("RemoveTaskInfo", func() {
			BeforeEach(func() {
				err := manager.AddTaskInfo(boshtask.TaskInfo{
					TaskID:  "fake-task-id-1",
					Method:  "fake-method-1",
					Payload: []byte("fake-payload-1"),
				})
				Expect(err).ToNot(HaveOccurred())

				err = manager.AddTaskInfo(boshtask.TaskInfo{
					TaskID:  "fake-task-id-2",
					Method:  "fake-method-2",
					Payload: []byte("fake-payload-2"),
				})
				Expect(err).ToNot(HaveOccurred())
			})

			It("removes the task", func() {
				err := manager.RemoveTaskInfo("fake-task-id-1")
				Expect(err).ToNot(HaveOccurred())

				content, err := fs.ReadFile("/dir/path")
				Expect(err).ToNot(HaveOccurred())

				var decodedMap map[string]boshtask.TaskInfo

				err = json.Unmarshal(content, &decodedMap)
				Expect(err).ToNot(HaveOccurred())
				Expect(decodedMap).To(Equal(map[string]boshtask.TaskInfo{
					"fake-task-id-2": boshtask.TaskInfo{
						TaskID:  "fake-task-id-2",
						Method:  "fake-method-2",
						Payload: []byte("fake-payload-2"),
					},
				}))
			})

			It("does not return error when removing task that does not exist", func() {
				err := manager.RemoveTaskInfo("fake-unknown-task-id")
				Expect(err).ToNot(HaveOccurred())
			})

			It("returns an error when failing to remove task", func() {
				fs.WriteToFileError = errors.New("fake-write-error")

				err := manager.RemoveTaskInfo("fake-task-id")
				Expect(err).To(HaveOccurred())
				Expect(err.Error()).To(ContainSubstring("fake-write-error"))
			})
		})
	})
}
func init() {
	Describe("concretePackageApplier", func() {
		var (
			packagesBc *fakebc.FakeBundleCollection
			blobstore  *fakeblob.FakeBlobstore
			compressor *fakecmd.FakeCompressor
			fs         *fakesys.FakeFileSystem
			logger     boshlog.Logger
			applier    PackageApplier
		)

		BeforeEach(func() {
			packagesBc = fakebc.NewFakeBundleCollection()
			blobstore = fakeblob.NewFakeBlobstore()
			compressor = fakecmd.NewFakeCompressor()
			fs = fakesys.NewFakeFileSystem()
			logger = boshlog.NewLogger(boshlog.LevelNone)
			applier = NewConcretePackageApplier(packagesBc, true, blobstore, compressor, fs, logger)
		})

		Describe("Prepare & Apply", func() {
			var (
				pkg    models.Package
				bundle *fakebc.FakeBundle
			)

			BeforeEach(func() {
				pkg, bundle = buildPkg(packagesBc)
			})

			ItInstallsPkg := func(act func() error) {
				It("returns error when installing package fails", func() {
					bundle.InstallError = errors.New("fake-install-error")

					err := act()
					Expect(err).To(HaveOccurred())
					Expect(err.Error()).To(ContainSubstring("fake-install-error"))
				})

				It("downloads and later cleans up downloaded package blob", func() {
					blobstore.GetFileName = "/fake-blobstore-file-name"

					err := act()
					Expect(err).ToNot(HaveOccurred())
					Expect(blobstore.GetBlobIDs[0]).To(Equal("fake-blobstore-id"))
					Expect(blobstore.GetFingerprints[0]).To(Equal("fake-blob-sha1"))

					// downloaded file is cleaned up
					Expect(blobstore.CleanUpFileName).To(Equal("/fake-blobstore-file-name"))
				})

				It("returns error when downloading package blob fails", func() {
					blobstore.GetError = errors.New("fake-get-error")

					err := act()
					Expect(err).To(HaveOccurred())
					Expect(err.Error()).To(ContainSubstring("fake-get-error"))
				})

				It("decompresses package blob to tmp path and later cleans it up", func() {
					fs.TempDirDir = "/fake-tmp-dir"
					blobstore.GetFileName = "/fake-blobstore-file-name"

					var tmpDirExistsBeforeInstall bool

					bundle.InstallCallBack = func() {
						tmpDirExistsBeforeInstall = true
					}

					err := act()
					Expect(err).ToNot(HaveOccurred())

					Expect(compressor.DecompressFileToDirTarballPaths[0]).To(Equal("/fake-blobstore-file-name"))
					Expect(compressor.DecompressFileToDirDirs[0]).To(Equal("/fake-tmp-dir"))

					// tmp dir exists before bundle install
					Expect(tmpDirExistsBeforeInstall).To(BeTrue())

					// tmp dir is cleaned up after install
					Expect(fs.FileExists(fs.TempDirDir)).To(BeFalse())
				})

				It("returns error when temporary directory creation fails", func() {
					fs.TempDirError = errors.New("fake-filesystem-tempdir-error")

					err := act()
					Expect(err).To(HaveOccurred())
					Expect(err.Error()).To(ContainSubstring("fake-filesystem-tempdir-error"))
				})

				It("returns error when decompressing package blob fails", func() {
					compressor.DecompressFileToDirErr = errors.New("fake-decompress-error")

					err := act()
					Expect(err).To(HaveOccurred())
					Expect(err.Error()).To(ContainSubstring("fake-decompress-error"))
				})

				It("installs bundle from decompressed tmp path of a package blob", func() {
					fs.TempDirDir = "/fake-tmp-dir"

					var installedBeforeDecompression bool

					compressor.DecompressFileToDirCallBack = func() {
						installedBeforeDecompression = bundle.Installed
					}

					err := act()
					Expect(err).ToNot(HaveOccurred())

					// bundle installation did not happen before decompression
					Expect(installedBeforeDecompression).To(BeFalse())

					// make sure that bundle install happened after decompression
					Expect(bundle.InstallSourcePath).To(Equal("/fake-tmp-dir"))
				})
			}

			Describe("Prepare", func() {
				act := func() error { return applier.Prepare(pkg) }

				It("return an error if getting file bundle fails", func() {
					packagesBc.GetErr = errors.New("fake-get-bundle-error")

					err := act()
					Expect(err).To(HaveOccurred())
					Expect(err.Error()).To(ContainSubstring("fake-get-bundle-error"))
				})

				It("returns an error if checking for package installation fails", func() {
					bundle.IsInstalledErr = errors.New("fake-is-installed-error")

					err := act()
					Expect(err).To(HaveOccurred())
					Expect(err.Error()).To(ContainSubstring("fake-is-installed-error"))
				})

				Context("when package is already installed", func() {
					BeforeEach(func() {
						bundle.Installed = true
					})

					It("does not install", func() {
						err := act()
						Expect(err).ToNot(HaveOccurred())
						Expect(bundle.ActionsCalled).To(Equal([]string{})) // no Install
					})

					It("does not download the package", func() {
						err := act()
						Expect(err).ToNot(HaveOccurred())
						Expect(blobstore.GetBlobIDs).To(BeNil())
					})
				})

				Context("when package is not installed", func() {
					BeforeEach(func() {
						bundle.Installed = false
					})

					It("installs package (but does not enable it)", func() {
						err := act()
						Expect(err).ToNot(HaveOccurred())
						Expect(bundle.ActionsCalled).To(Equal([]string{"Install"}))
					})

					ItInstallsPkg(act)
				})
			})

			Describe("Apply", func() {
				act := func() error { return applier.Apply(pkg) }

				It("return an error if getting file bundle fails", func() {
					packagesBc.GetErr = errors.New("fake-get-bundle-error")

					err := act()
					Expect(err).To(HaveOccurred())
					Expect(err.Error()).To(ContainSubstring("fake-get-bundle-error"))
				})

				It("returns an error if checking for package installation fails", func() {
					bundle.IsInstalledErr = errors.New("fake-is-installed-error")

					err := act()
					Expect(err).To(HaveOccurred())
					Expect(err.Error()).To(ContainSubstring("fake-is-installed-error"))
				})

				Context("when package is already installed", func() {
					BeforeEach(func() {
						bundle.Installed = true
					})

					It("does not install but only enables package", func() {
						err := act()
						Expect(err).ToNot(HaveOccurred())
						Expect(bundle.ActionsCalled).To(Equal([]string{"Enable"})) // no Install
					})

					It("returns error when package enable fails", func() {
						bundle.EnableError = errors.New("fake-enable-error")

						err := act()
						Expect(err).To(HaveOccurred())
						Expect(err.Error()).To(ContainSubstring("fake-enable-error"))
					})

					It("does not download the package", func() {
						err := act()
						Expect(err).ToNot(HaveOccurred())
						Expect(blobstore.GetBlobIDs).To(BeNil())
					})
				})

				Context("when package is not installed", func() {
					BeforeEach(func() {
						bundle.Installed = false
					})

					It("installs and enables package", func() {
						err := act()
						Expect(err).ToNot(HaveOccurred())
						Expect(bundle.ActionsCalled).To(Equal([]string{"Install", "Enable"}))
					})

					It("returns error when package enable fails", func() {
						bundle.EnableError = errors.New("fake-enable-error")

						err := act()
						Expect(err).To(HaveOccurred())
						Expect(err.Error()).To(ContainSubstring("fake-enable-error"))
					})

					ItInstallsPkg(act)
				})
			})
		})

		Describe("KeepOnly", func() {
			ItReturnsErrors := func() {
				It("returns error when bundle collection fails to return list of installed bundles", func() {
					packagesBc.ListErr = errors.New("fake-bc-list-error")

					err := applier.KeepOnly([]models.Package{})
					Expect(err).To(HaveOccurred())
					Expect(err.Error()).To(ContainSubstring("fake-bc-list-error"))
				})

				It("returns error when bundle collection cannot retrieve bundle for keep-only package", func() {
					pkg1, bundle1 := buildPkg(packagesBc)

					packagesBc.ListBundles = []boshbc.Bundle{bundle1}
					packagesBc.GetErr = errors.New("fake-bc-get-error")

					err := applier.KeepOnly([]models.Package{pkg1})
					Expect(err).To(HaveOccurred())
					Expect(err.Error()).To(ContainSubstring("fake-bc-get-error"))
				})

				It("returns error when at least one bundle cannot be disabled", func() {
					_, bundle1 := buildPkg(packagesBc)

					packagesBc.ListBundles = []boshbc.Bundle{bundle1}
					bundle1.DisableErr = errors.New("fake-bc-disable-error")

					err := applier.KeepOnly([]models.Package{})
					Expect(err).To(HaveOccurred())
					Expect(err.Error()).To(ContainSubstring("fake-bc-disable-error"))
				})
			}

			Context("when operating on packages as a package owner", func() {
				BeforeEach(func() {
					applier = NewConcretePackageApplier(packagesBc, true, blobstore, compressor, fs, logger)
				})

				It("first disables and then uninstalls packages that are not in keeponly list", func() {
					_, bundle1 := buildPkg(packagesBc)
					pkg2, bundle2 := buildPkg(packagesBc)
					_, bundle3 := buildPkg(packagesBc)
					pkg4, bundle4 := buildPkg(packagesBc)

					packagesBc.ListBundles = []boshbc.Bundle{bundle1, bundle2, bundle3, bundle4}

					err := applier.KeepOnly([]models.Package{pkg4, pkg2})
					Expect(err).ToNot(HaveOccurred())

					Expect(bundle1.ActionsCalled).To(Equal([]string{"Disable", "Uninstall"}))
					Expect(bundle2.ActionsCalled).To(Equal([]string{}))
					Expect(bundle3.ActionsCalled).To(Equal([]string{"Disable", "Uninstall"}))
					Expect(bundle4.ActionsCalled).To(Equal([]string{}))
				})

				ItReturnsErrors()

				It("returns error when at least one bundle cannot be uninstalled", func() {
					_, bundle1 := buildPkg(packagesBc)

					packagesBc.ListBundles = []boshbc.Bundle{bundle1}
					bundle1.UninstallErr = errors.New("fake-bc-uninstall-error")

					err := applier.KeepOnly([]models.Package{})
					Expect(err).To(HaveOccurred())
					Expect(err.Error()).To(ContainSubstring("fake-bc-uninstall-error"))
				})
			})

			Context("when operating on packages not as a package owner", func() {
				BeforeEach(func() {
					applier = NewConcretePackageApplier(packagesBc, false, blobstore, compressor, fs, logger)
				})

				It("disables and but does not uninstall packages that are not in keeponly list", func() {
					_, bundle1 := buildPkg(packagesBc)
					pkg2, bundle2 := buildPkg(packagesBc)
					_, bundle3 := buildPkg(packagesBc)
					pkg4, bundle4 := buildPkg(packagesBc)

					packagesBc.ListBundles = []boshbc.Bundle{bundle1, bundle2, bundle3, bundle4}

					err := applier.KeepOnly([]models.Package{pkg4, pkg2})
					Expect(err).ToNot(HaveOccurred())

					Expect(bundle1.ActionsCalled).To(Equal([]string{"Disable"})) // no Uninstall
					Expect(bundle2.ActionsCalled).To(Equal([]string{}))
					Expect(bundle3.ActionsCalled).To(Equal([]string{"Disable"})) // no Uninstall
					Expect(bundle4.ActionsCalled).To(Equal([]string{}))
				})

				ItReturnsErrors()
			})

		})
	})
}
func init() {
	Describe("concreteBuilder", func() {
		var (
			settingsService *fakesettings.FakeSettingsService
			builder         Builder
		)

		BeforeEach(func() {
			logger := boshlog.NewLogger(boshlog.LevelNone)
			settingsService = &fakesettings.FakeSettingsService{}
			builder = NewBuilder(settingsService, logger)
		})

		Describe("Build", func() {
			It("builds alert with id, severity and other monit related info", func() {
				builtAlert, err := builder.Build(buildMonitAlert())
				Expect(err).ToNot(HaveOccurred())
				Expect(builtAlert).To(Equal(Alert{
					ID:        "some random id",
					Severity:  SeverityAlert,
					Title:     "nats - does not exist - restart",
					Summary:   "process is not running",
					CreatedAt: 1306076861,
				}))
			})

			It("sets the severity based on event", func() {
				alerts := map[string]SeverityLevel{
					"action done": SeverityIgnored,
					"Action done": SeverityIgnored,
					"action Done": SeverityIgnored,
				}

				for event, expectedSeverity := range alerts {
					inputAlert := buildMonitAlert()
					inputAlert.Event = event
					builtAlert, _ := builder.Build(inputAlert)
					Expect(builtAlert.Severity).To(Equal(expectedSeverity))
				}
			})

			It("sets default severity to critical", func() {
				inputAlert := buildMonitAlert()
				inputAlert.Event = "some unknown event"

				builtAlert, _ := builder.Build(inputAlert)
				Expect(builtAlert.Severity).To(Equal(SeverityCritical))
			})

			It("sets created at", func() {
				inputAlert := buildMonitAlert()
				inputAlert.Date = "Thu, 02 May 2013 20:07:41 +0500"

				builtAlert, _ := builder.Build(inputAlert)
				Expect(int(builtAlert.CreatedAt)).To(Equal(int(1367507261)))
			})

			It("defaults created at to now on parse error", func() {
				inputAlert := buildMonitAlert()
				inputAlert.Date = "Thu, 02 May 2013 20:07:0"

				builtAlert, _ := builder.Build(inputAlert)
				createdAt := time.Unix(builtAlert.CreatedAt, 0)
				assert.WithinDuration(GinkgoT(), time.Now(), createdAt, 1*time.Second)
			})

			It("sets the title with ips", func() {
				inputAlert := buildMonitAlert()
				settingsService.Settings.Networks = boshsettings.Networks{
					"fake-net1": boshsettings.Network{IP: "192.168.0.1"},
					"fake-net2": boshsettings.Network{IP: "10.0.0.1"},
				}

				builtAlert, _ := builder.Build(inputAlert)
				Expect(builtAlert.Title).To(Equal("nats (10.0.0.1, 192.168.0.1) - does not exist - restart"))
			})
		})
	})
}
func createSfdiskPartitionerForTests(runner *fakesys.FakeCmdRunner) (partitioner Partitioner) {
	logger := boshlog.NewLogger(boshlog.LevelNone)
	partitioner = NewSfdiskPartitioner(logger, runner)
	return
}
Example #7
0
func init() {
	Describe("provider", func() {
		var (
			platform              *fakeplatform.FakePlatform
			client                *fakemonit.FakeMonitClient
			logger                boshlog.Logger
			dirProvider           boshdir.DirectoriesProvider
			jobFailuresServerPort int
			handler               *fakembus.FakeHandler
			provider              Provider
		)

		BeforeEach(func() {
			platform = fakeplatform.NewFakePlatform()
			client = fakemonit.NewFakeMonitClient()
			logger = boshlog.NewLogger(boshlog.LevelNone)
			dirProvider = boshdir.NewDirectoriesProvider("/fake-base-dir")
			jobFailuresServerPort = 2825
			handler = &fakembus.FakeHandler{}

			provider = NewProvider(
				platform,
				client,
				logger,
				dirProvider,
				handler,
			)
		})

		It("provides a monit job supervisor", func() {
			actualSupervisor, err := provider.Get("monit")
			Expect(err).ToNot(HaveOccurred())

			expectedSupervisor := NewMonitJobSupervisor(
				platform.Fs,
				platform.Runner,
				client,
				logger,
				dirProvider,
				jobFailuresServerPort,
				MonitReloadOptions{
					MaxTries:               3,
					MaxCheckTries:          6,
					DelayBetweenCheckTries: 5 * time.Second,
				},
			)
			Expect(actualSupervisor).To(Equal(expectedSupervisor))
		})

		It("provides a dummy job supervisor", func() {
			actualSupervisor, err := provider.Get("dummy")
			Expect(err).ToNot(HaveOccurred())

			expectedSupervisor := NewDummyJobSupervisor()
			Expect(actualSupervisor).To(Equal(expectedSupervisor))
		})

		It("provides a dummy nats job supervisor", func() {
			actualSupervisor, err := provider.Get("dummy-nats")
			Expect(err).NotTo(HaveOccurred())

			expectedSupervisor := NewDummyNatsJobSupervisor(handler)
			Expect(actualSupervisor).To(Equal(expectedSupervisor))
		})

		It("returns an error when the supervisor is not found", func() {
			_, err := provider.Get("does-not-exist")
			Expect(err).To(HaveOccurred())
			Expect(err.Error()).To(ContainSubstring("does-not-exist could not be found"))
		})
	})
}
func init() {
	Describe("asyncTaskService", func() {
		var (
			uuidGen *fakeuuid.FakeGenerator
			service Service
		)

		BeforeEach(func() {
			uuidGen = &fakeuuid.FakeGenerator{}
			service = NewAsyncTaskService(uuidGen, boshlog.NewLogger(boshlog.LevelNone))
		})

		Describe("StartTask", func() {
			startAndWaitForTaskCompletion := func(task Task) Task {
				service.StartTask(task)
				for task.State == TaskStateRunning {
					time.Sleep(time.Nanosecond)
					task, _ = service.FindTaskWithID(task.ID)
				}
				return task
			}

			It("sets return value on a successful task", func() {
				runFunc := func() (interface{}, error) { return 123, nil }

				task, err := service.CreateTask(runFunc, nil, nil)
				Expect(err).ToNot(HaveOccurred())

				task = startAndWaitForTaskCompletion(task)
				Expect(task.State).To(BeEquivalentTo(TaskStateDone))
				Expect(task.Value).To(Equal(123))
				Expect(task.Error).To(BeNil())
			})

			It("sets task error on a failing task", func() {
				err := errors.New("fake-error")
				runFunc := func() (interface{}, error) { return nil, err }

				task, createErr := service.CreateTask(runFunc, nil, nil)
				Expect(createErr).ToNot(HaveOccurred())

				task = startAndWaitForTaskCompletion(task)
				Expect(task.State).To(BeEquivalentTo(TaskStateFailed))
				Expect(task.Value).To(BeNil())
				Expect(task.Error).To(Equal(err))
			})

			Describe("CreateTask", func() {
				It("can run task created with CreateTask which does not have end func", func() {
					ranFunc := false
					runFunc := func() (interface{}, error) { ranFunc = true; return nil, nil }

					task, err := service.CreateTask(runFunc, nil, nil)
					Expect(err).ToNot(HaveOccurred())

					startAndWaitForTaskCompletion(task)
					Expect(ranFunc).To(BeTrue())
				})

				It("can run task created with CreateTask which has end func", func() {
					ranFunc := false
					runFunc := func() (interface{}, error) { ranFunc = true; return nil, nil }

					ranEndFunc := false
					endFunc := func(Task) { ranEndFunc = true }

					task, err := service.CreateTask(runFunc, nil, endFunc)
					Expect(err).ToNot(HaveOccurred())

					startAndWaitForTaskCompletion(task)
					Expect(ranFunc).To(BeTrue())
					Expect(ranEndFunc).To(BeTrue())
				})

				It("returns an error if generate uuid fails", func() {
					uuidGen.GenerateError = errors.New("fake-generate-uuid-error")
					_, err := service.CreateTask(nil, nil, nil)
					Expect(err).To(HaveOccurred())
					Expect(err.Error()).To(ContainSubstring("fake-generate-uuid-error"))
				})
			})

			Describe("CreateTaskWithID", func() {
				It("can run task created with CreateTaskWithID which does not have end func", func() {
					ranFunc := false
					runFunc := func() (interface{}, error) { ranFunc = true; return nil, nil }

					task := service.CreateTaskWithID("fake-task-id", runFunc, nil, nil)

					startAndWaitForTaskCompletion(task)
					Expect(ranFunc).To(BeTrue())
				})

				It("can run task created with CreateTaskWithID which has end func", func() {
					ranFunc := false
					runFunc := func() (interface{}, error) { ranFunc = true; return nil, nil }

					ranEndFunc := false
					endFunc := func(Task) { ranEndFunc = true }

					task := service.CreateTaskWithID("fake-task-id", runFunc, nil, endFunc)

					startAndWaitForTaskCompletion(task)
					Expect(ranFunc).To(BeTrue())
					Expect(ranEndFunc).To(BeTrue())
				})
			})

			It("can process many tasks simultaneously", func() {
				taskFunc := func() (interface{}, error) {
					time.Sleep(10 * time.Millisecond)
					return nil, nil
				}

				ids := []string{}
				for id := 1; id < 200; id++ {
					idStr := fmt.Sprintf("%d", id)
					uuidGen.GeneratedUuid = idStr
					ids = append(ids, idStr)

					task, err := service.CreateTask(taskFunc, nil, nil)
					Expect(err).ToNot(HaveOccurred())
					go service.StartTask(task)
				}

				for {
					allDone := true
					for _, id := range ids {
						task, _ := service.FindTaskWithID(id)
						if task.State != TaskStateDone {
							allDone = false
							break
						}
					}

					if allDone {
						break
					}
					time.Sleep(200 * time.Millisecond)
				}
			})
		})

		Describe("CreateTask", func() {
			It("creates a task with auto-assigned id", func() {
				uuidGen.GeneratedUuid = "fake-uuid"

				runFuncCalled := false
				runFunc := func() (interface{}, error) {
					runFuncCalled = true
					return nil, nil
				}

				cancelFuncCalled := false
				cancelFunc := func(_ Task) error {
					cancelFuncCalled = true
					return nil
				}

				endFuncCalled := false
				endFunc := func(_ Task) {
					endFuncCalled = true
				}

				task, err := service.CreateTask(runFunc, cancelFunc, endFunc)
				Expect(err).ToNot(HaveOccurred())
				Expect(task.ID).To(Equal("fake-uuid"))
				Expect(task.State).To(Equal(TaskStateRunning))

				task.TaskFunc()
				Expect(runFuncCalled).To(BeTrue())

				task.CancelFunc(task)
				Expect(cancelFuncCalled).To(BeTrue())

				task.TaskEndFunc(task)
				Expect(endFuncCalled).To(BeTrue())
			})
		})

		Describe("CreateTaskWithID", func() {
			It("creates a task with given id", func() {
				runFuncCalled := false
				runFunc := func() (interface{}, error) {
					runFuncCalled = true
					return nil, nil
				}

				cancelFuncCalled := false
				cancelFunc := func(_ Task) error {
					cancelFuncCalled = true
					return nil
				}

				endFuncCalled := false
				endFunc := func(_ Task) {
					endFuncCalled = true
				}

				task := service.CreateTaskWithID("fake-task-id", runFunc, cancelFunc, endFunc)
				Expect(task.ID).To(Equal("fake-task-id"))
				Expect(task.State).To(Equal(TaskStateRunning))

				task.TaskFunc()
				Expect(runFuncCalled).To(BeTrue())

				task.CancelFunc(task)
				Expect(cancelFuncCalled).To(BeTrue())

				task.TaskEndFunc(task)
				Expect(endFuncCalled).To(BeTrue())
			})
		})
	})
}
func init() {
	Describe("execCmdRunner", func() {
		var (
			runner CmdRunner
		)

		BeforeEach(func() {
			runner = NewExecCmdRunner(boshlog.NewLogger(boshlog.LevelNone))
		})

		Describe("RunComplexCommand", func() {
			It("run complex command with working directory", func() {
				cmd := Command{
					Name:       "ls",
					Args:       []string{"-l"},
					WorkingDir: "..",
				}
				stdout, stderr, status, err := runner.RunComplexCommand(cmd)
				Expect(err).ToNot(HaveOccurred())
				Expect(stdout).To(ContainSubstring("README.md"))
				Expect(stdout).To(ContainSubstring("total"))
				Expect(stderr).To(BeEmpty())
				Expect(status).To(Equal(0))
			})

			It("run complex command with env", func() {
				cmd := Command{
					Name: "env",
					Env: map[string]string{
						"FOO": "BAR",
					},
				}
				stdout, stderr, status, err := runner.RunComplexCommand(cmd)
				Expect(err).ToNot(HaveOccurred())
				Expect(stdout).To(ContainSubstring("FOO=BAR"))
				Expect(stdout).To(ContainSubstring("PATH="))
				Expect(stderr).To(BeEmpty())
				Expect(status).To(Equal(0))
			})

			It("prints stdout/stderr to provided I/O object", func() {
				fs := fakesys.NewFakeFileSystem()
				stdoutFile, err := fs.OpenFile("/fake-stdout-path", os.O_RDWR, os.FileMode(0644))
				Expect(err).ToNot(HaveOccurred())

				stderrFile, err := fs.OpenFile("/fake-stderr-path", os.O_RDWR, os.FileMode(0644))
				Expect(err).ToNot(HaveOccurred())

				cmd := Command{
					Name:   "bash",
					Args:   []string{"-c", "echo fake-out >&1; echo fake-err >&2"},
					Stdout: stdoutFile,
					Stderr: stderrFile,
				}

				stdout, stderr, status, err := runner.RunComplexCommand(cmd)
				Expect(err).ToNot(HaveOccurred())

				Expect(stdout).To(BeEmpty())
				Expect(stderr).To(BeEmpty())
				Expect(status).To(Equal(0))

				stdoutContents := make([]byte, 1024)
				_, err = stdoutFile.Read(stdoutContents)
				Expect(err).ToNot(HaveOccurred())
				Expect(string(stdoutContents)).To(ContainSubstring("fake-out"))

				stderrContents := make([]byte, 1024)
				_, err = stderrFile.Read(stderrContents)
				Expect(err).ToNot(HaveOccurred())
				Expect(string(stderrContents)).To(ContainSubstring("fake-err"))
			})
		})

		Describe("RunComplexCommandAsync", func() {
			It("populates stdout and stderr", func() {
				cmd := Command{Name: "ls"}
				process, err := runner.RunComplexCommandAsync(cmd)
				Expect(err).ToNot(HaveOccurred())

				result := <-process.Wait()
				Expect(result.Error).ToNot(HaveOccurred())
				Expect(result.ExitStatus).To(Equal(0))
			})

			It("populates stdout and stderr", func() {
				cmd := Command{Name: "bash", Args: []string{"-c", "echo stdout >&1; echo stderr >&2"}}
				process, err := runner.RunComplexCommandAsync(cmd)
				Expect(err).ToNot(HaveOccurred())

				result := <-process.Wait()
				Expect(result.Error).ToNot(HaveOccurred())
				Expect(result.Stdout).To(Equal("stdout\n"))
				Expect(result.Stderr).To(Equal("stderr\n"))
			})

			It("returns error and sets status to exit status of comamnd if it command exits with non-0 status", func() {
				cmd := Command{Name: "bash", Args: []string{"-c", "exit 10"}}
				process, err := runner.RunComplexCommandAsync(cmd)
				Expect(err).ToNot(HaveOccurred())

				result := <-process.Wait()
				Expect(result.Error).To(HaveOccurred())
				Expect(result.ExitStatus).To(Equal(10))
			})

			It("allows setting custom env variable in addition to inheriting process env variables", func() {
				cmd := Command{
					Name: "env",
					Env: map[string]string{
						"FOO": "BAR",
					},
				}
				process, err := runner.RunComplexCommandAsync(cmd)
				Expect(err).ToNot(HaveOccurred())

				result := <-process.Wait()
				Expect(result.Error).ToNot(HaveOccurred())
				Expect(result.Stdout).To(ContainSubstring("FOO=BAR"))
				Expect(result.Stdout).To(ContainSubstring("PATH="))
			})

			It("changes working dir", func() {
				cmd := Command{Name: "bash", Args: []string{"-c", "echo $PWD"}, WorkingDir: "/tmp"}
				process, err := runner.RunComplexCommandAsync(cmd)
				Expect(err).ToNot(HaveOccurred())

				result := <-process.Wait()
				Expect(result.Error).ToNot(HaveOccurred())
				Expect(result.Stdout).To(ContainSubstring("/tmp"))
			})
		})

		Describe("RunCommand", func() {
			It("run command", func() {
				stdout, stderr, status, err := runner.RunCommand("echo", "Hello World!")
				Expect(err).ToNot(HaveOccurred())
				Expect(stdout).To(Equal("Hello World!\n"))
				Expect(stderr).To(BeEmpty())
				Expect(status).To(Equal(0))
			})

			It("run command with error output", func() {
				stdout, stderr, status, err := runner.RunCommand("bash", "-c", "echo error-output >&2")
				Expect(err).ToNot(HaveOccurred())
				Expect(stdout).To(BeEmpty())
				Expect(stderr).To(ContainSubstring("error-output"))
				Expect(status).To(Equal(0))
			})

			It("run command with non-0 exit status", func() {
				stdout, stderr, status, err := runner.RunCommand("bash", "-c", "exit 14")
				Expect(err).To(HaveOccurred())
				Expect(stdout).To(BeEmpty())
				Expect(stderr).To(BeEmpty())
				Expect(status).To(Equal(14))
			})

			It("run command with error", func() {
				stdout, stderr, status, err := runner.RunCommand("false")
				Expect(err).To(HaveOccurred())
				Expect(err.Error()).To(Equal("Running command: 'false', stdout: '', stderr: '': exit status 1"))
				Expect(stderr).To(BeEmpty())
				Expect(stdout).To(BeEmpty())
				Expect(status).To(Equal(1))
			})

			It("run command with error with args", func() {
				stdout, stderr, status, err := runner.RunCommand("false", "second arg")
				Expect(err).To(HaveOccurred())
				Expect(err.Error()).To(Equal("Running command: 'false second arg', stdout: '', stderr: '': exit status 1"))
				Expect(stderr).To(BeEmpty())
				Expect(stdout).To(BeEmpty())
				Expect(status).To(Equal(1))
			})

			It("run command with cmd not found", func() {
				stdout, stderr, status, err := runner.RunCommand("something that does not exist")
				Expect(err).To(HaveOccurred())
				Expect(err.Error()).To(ContainSubstring("not found"))
				Expect(stderr).To(BeEmpty())
				Expect(stdout).To(BeEmpty())
				Expect(status).To(Equal(-1))
			})
		})

		Describe("CommandExists", func() {
			It("run command with input", func() {
				stdout, stderr, status, err := runner.RunCommandWithInput("foo\nbar\nbaz", "grep", "ba")
				Expect(err).ToNot(HaveOccurred())
				Expect(stdout).To(Equal("bar\nbaz\n"))
				Expect(stderr).To(BeEmpty())
				Expect(status).To(Equal(0))
			})
		})

		Describe("CommandExists", func() {
			It("command exists", func() {
				Expect(runner.CommandExists("env")).To(BeTrue())
				Expect(runner.CommandExists("absolutely-does-not-exist-ever-please-unicorns")).To(BeFalse())
			})
		})
	})

	Describe("execProcess", func() {
		var (
			runner CmdRunner
		)

		BeforeEach(func() {
			runner = NewExecCmdRunner(boshlog.NewLogger(boshlog.LevelNone))
		})

		Describe("TerminateNicely", func() {
			var (
				buildDir string
			)

			hasProcessesFromBuildDir := func() (bool, string) {
				// Make sure to show all processes on the system
				output, err := exec.Command("ps", "-A", "-o", "pid,args").Output()
				Expect(err).ToNot(HaveOccurred())

				// Cannot check for PID existence directly because
				// PID could have been recycled by the OS; make sure it's not the same process
				for _, line := range strings.Split(strings.TrimSpace(string(output)), "\n") {
					if strings.Contains(line, buildDir) {
						return true, line
					}
				}

				return false, ""
			}

			expectProcessesToNotExist := func() {
				exists, ps := hasProcessesFromBuildDir()
				Expect(exists).To(BeFalse(), "Expected following process to not exist %s", ps)
			}

			BeforeEach(func() {
				var (
					err error
				)

				buildDir, err = ioutil.TempDir("", "TerminateNicely")
				Expect(err).ToNot(HaveOccurred())

				exesToCompile := []string{
					"exe_exits",
					"child_ignore_term",
					"child_term",
					"parent_ignore_term",
					"parent_term",
				}

				for _, exe := range exesToCompile {
					dst := filepath.Join(buildDir, exe)
					src := filepath.Join("exec_cmd_runner_fixtures", exe+".go")
					err := exec.Command("go", "build", "-o", dst, src).Run()
					Expect(err).ToNot(HaveOccurred())
				}
			})

			AfterEach(func() {
				os.RemoveAll(buildDir)
			})

			Context("when parent and child terminate after receiving SIGTERM", func() {
				It("sends term signal to the whole group and returns with exit status that parent exited", func() {
					cmd := Command{Name: filepath.Join(buildDir, "parent_term")}
					process, err := runner.RunComplexCommandAsync(cmd)
					Expect(err).ToNot(HaveOccurred())

					// Wait for script to start and output pids
					time.Sleep(2 * time.Second)

					waitCh := process.Wait()

					err = process.TerminateNicely(1 * time.Minute)
					Expect(err).ToNot(HaveOccurred())

					result := <-waitCh
					Expect(result.Error).To(HaveOccurred())

					// Parent exit code is returned
					// bash adds 128 to signal status as exit code
					Expect(result.ExitStatus).To(Equal(13))

					// Term signal was sent to all processes in the group
					Expect(result.Stdout).To(ContainSubstring("Parent received SIGTERM"))
					Expect(result.Stdout).To(ContainSubstring("Child received SIGTERM"))

					// All processes are gone
					expectProcessesToNotExist()
				})
			})

			Context("when parent and child do not exit after receiving SIGTERM in small amount of time", func() {
				It("sends kill signal to the whole group and returns with ? exit status", func() {
					cmd := Command{Name: filepath.Join(buildDir, "parent_ignore_term")}
					process, err := runner.RunComplexCommandAsync(cmd)
					Expect(err).ToNot(HaveOccurred())

					// Wait for script to start and output pids
					time.Sleep(2 * time.Second)

					waitCh := process.Wait()

					err = process.TerminateNicely(2 * time.Second)
					Expect(err).ToNot(HaveOccurred())

					result := <-waitCh
					Expect(result.Error).To(HaveOccurred())

					// Parent exit code is returned
					Expect(result.ExitStatus).To(Equal(128 + 9))

					// Term signal was sent to all processes in the group before kill
					Expect(result.Stdout).To(ContainSubstring("Parent received SIGTERM"))
					Expect(result.Stdout).To(ContainSubstring("Child received SIGTERM"))

					// Parent and child are killed
					expectProcessesToNotExist()
				})
			})

			Context("when parent and child already exited before calling TerminateNicely", func() {
				It("returns without an error since all processes are gone", func() {
					cmd := Command{Name: filepath.Join(buildDir, "exe_exits")}
					process, err := runner.RunComplexCommandAsync(cmd)
					Expect(err).ToNot(HaveOccurred())

					// Wait for script to exit
					for i := 0; i < 20; i++ {
						if exists, _ := hasProcessesFromBuildDir(); !exists {
							break
						}
						if i == 19 {
							Fail("Expected process did not exit fast enough")
						}
						time.Sleep(500 * time.Millisecond)
					}

					waitCh := process.Wait()

					err = process.TerminateNicely(2 * time.Second)
					Expect(err).ToNot(HaveOccurred())

					result := <-waitCh
					Expect(result.Error).ToNot(HaveOccurred())
					Expect(result.Stdout).To(Equal(""))
					Expect(result.Stderr).To(Equal(""))
					Expect(result.ExitStatus).To(Equal(0))
				})
			})
		})
	})

	Describe("ExecError", func() {
		Describe("Error", func() {
			It("returns error message with full stdout and full stderr to aid debugging", func() {
				execErr := NewExecError("fake-cmd", "fake-stdout", "fake-stderr")
				expectedMsg := "Running command: 'fake-cmd', stdout: 'fake-stdout', stderr: 'fake-stderr'"
				Expect(execErr.Error()).To(Equal(expectedMsg))
			})
		})

		Describe("ShortError", func() {
			buildLines := func(start, stop int, suffix string) string {
				var result []string
				for i := start; i <= stop; i++ {
					result = append(result, fmt.Sprintf("%d %s", i, suffix))
				}
				return strings.Join(result, "\n")
			}

			Context("when stdout and stderr contains more than 100 lines", func() {
				It("returns error message with truncated stdout and stderr to 100 lines", func() {
					fullStdout101 := buildLines(1, 101, "stdout")
					truncatedStdout100 := buildLines(2, 101, "stdout")

					fullStderr101 := buildLines(1, 101, "stderr")
					truncatedStderr100 := buildLines(2, 101, "stderr")

					execErr := NewExecError("fake-cmd", fullStdout101, fullStderr101)

					expectedMsg := fmt.Sprintf(
						"Running command: 'fake-cmd', stdout: '%s', stderr: '%s'",
						truncatedStdout100, truncatedStderr100,
					)

					Expect(execErr.ShortError()).To(Equal(expectedMsg))
				})
			})

			Context("when stdout and stderr contains exactly 100 lines", func() {
				It("returns error message with full lines", func() {
					stdout100 := buildLines(1, 100, "stdout")
					stderr100 := buildLines(1, 100, "stderr")
					execErr := NewExecError("fake-cmd", stdout100, stderr100)
					expectedMsg := fmt.Sprintf("Running command: 'fake-cmd', stdout: '%s', stderr: '%s'", stdout100, stderr100)
					Expect(execErr.ShortError()).To(Equal(expectedMsg))
				})
			})

			Context("when stdout and stderr contains less than 100 lines", func() {
				It("returns error message with full lines", func() {
					stdout99 := buildLines(1, 99, "stdout")
					stderr99 := buildLines(1, 99, "stderr")
					execErr := NewExecError("fake-cmd", stdout99, stderr99)
					expectedMsg := fmt.Sprintf("Running command: 'fake-cmd', stdout: '%s', stderr: '%s'", stdout99, stderr99)
					Expect(execErr.ShortError()).To(Equal(expectedMsg))
				})
			})
		})
	})
}
func init() {
	Describe("Testing with Ginkgo", func() {
		It("home dir", func() {
			osFs, _ := createOsFs()

			homeDir, err := osFs.HomeDir("root")
			Expect(err).ToNot(HaveOccurred())
			assert.Contains(GinkgoT(), homeDir, "/root")
		})

		It("mkdir all", func() {
			osFs, _ := createOsFs()
			tmpPath := os.TempDir()
			testPath := filepath.Join(tmpPath, "MkdirAllTestDir", "bar", "baz")
			defer os.RemoveAll(filepath.Join(tmpPath, "MkdirAllTestDir"))

			_, err := os.Stat(testPath)
			Expect(err).To(HaveOccurred())
			Expect(os.IsNotExist(err)).To(BeTrue())

			fileMode := os.FileMode(0700)

			err = osFs.MkdirAll(testPath, fileMode)
			Expect(err).ToNot(HaveOccurred())

			stat, err := os.Stat(testPath)
			Expect(err).ToNot(HaveOccurred())
			Expect(stat.IsDir()).To(BeTrue())
			Expect(stat.Mode().Perm()).To(Equal(fileMode))

			err = osFs.MkdirAll(testPath, fileMode)
			Expect(err).ToNot(HaveOccurred())
		})

		It("chown", func() {
			osFs, _ := createOsFs()
			testPath := filepath.Join(os.TempDir(), "ChownTestDir")

			err := os.Mkdir(testPath, os.FileMode(0700))
			Expect(err).ToNot(HaveOccurred())
			defer os.RemoveAll(testPath)

			err = osFs.Chown(testPath, "root")
			Expect(err).To(HaveOccurred())
			Expect(err.Error()).To(ContainSubstring("not permitted"))
		})

		It("chmod", func() {
			osFs, _ := createOsFs()
			testPath := filepath.Join(os.TempDir(), "ChmodTestDir")

			_, err := os.Create(testPath)
			Expect(err).ToNot(HaveOccurred())
			defer os.Remove(testPath)

			os.Chmod(testPath, os.FileMode(0666))

			err = osFs.Chmod(testPath, os.FileMode(0644))
			Expect(err).ToNot(HaveOccurred())

			fileStat, err := os.Stat(testPath)
			Expect(err).ToNot(HaveOccurred())
			Expect(fileStat.Mode()).To(Equal(os.FileMode(0644)))
		})

		It("opens file", func() {
			osFs, _ := createOsFs()
			testPath := filepath.Join(os.TempDir(), "OpenFileTestFile")

			file, err := osFs.OpenFile(testPath, os.O_RDWR|os.O_CREATE|os.O_TRUNC, os.FileMode(0644))
			defer os.Remove(testPath)

			Expect(err).ToNot(HaveOccurred())

			file.Write([]byte("testing new file"))
			file.Close()

			createdFile, err := os.Open(testPath)
			Expect(err).ToNot(HaveOccurred())
			defer createdFile.Close()
			Expect(readFile(createdFile)).To(Equal("testing new file"))
		})

		Context("the file already exists and is not write only", func() {
			It("writes to file", func() {
				osFs, _ := createOsFs()
				testPath := filepath.Join(os.TempDir(), "subDir", "ConvergeFileContentsTestFile")

				_, err := os.Stat(testPath)
				Expect(err).To(HaveOccurred())

				written, err := osFs.ConvergeFileContents(testPath, []byte("initial write"))
				Expect(err).ToNot(HaveOccurred())
				Expect(written).To(BeTrue())
				defer os.Remove(testPath)

				file, err := os.Open(testPath)
				Expect(err).ToNot(HaveOccurred())
				defer file.Close()

				Expect(readFile(file)).To(Equal("initial write"))

				written, err = osFs.ConvergeFileContents(testPath, []byte("second write"))
				Expect(err).ToNot(HaveOccurred())
				Expect(written).To(BeTrue())

				file.Close()
				file, err = os.Open(testPath)
				Expect(err).ToNot(HaveOccurred())

				Expect(readFile(file)).To(Equal("second write"))

				file.Close()
				file, err = os.Open(testPath)

				written, err = osFs.ConvergeFileContents(testPath, []byte("second write"))
				Expect(err).ToNot(HaveOccurred())
				Expect(written).To(BeFalse())
				Expect(readFile(file)).To(Equal("second write"))
			})
		})

		Context("the file already exists and is write only", func() {
			It("writes to file", func() {
				osFs, _ := createOsFs()
				testPath := filepath.Join(os.TempDir(), "subDir", "ConvergeFileContentsTestFile")

				_, err := os.OpenFile(testPath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, os.FileMode(0200))
				Expect(err).ToNot(HaveOccurred())
				defer os.Remove(testPath)

				err = osFs.WriteFile(testPath, []byte("test"))
				Expect(err).ToNot(HaveOccurred())
			})
		})

		Context("the file parent fir does not exist", func() {
			BeforeEach(func() {
				err := os.RemoveAll(filepath.Join(os.TempDir(), "subDirNew"))
				Expect(err).ToNot(HaveOccurred())
			})

			AfterEach(func() {
				err := os.RemoveAll(filepath.Join(os.TempDir(), "subDirNew"))
				Expect(err).ToNot(HaveOccurred())
			})

			It("writes to file", func() {
				osFs, _ := createOsFs()

				testPath := filepath.Join(os.TempDir(), "subDirNew", "ConvergeFileContentsTestFile")

				err := osFs.WriteFile(testPath, []byte("test"))
				Expect(err).ToNot(HaveOccurred())
			})
		})

		It("read file", func() {
			osFs, _ := createOsFs()
			testPath := filepath.Join(os.TempDir(), "ReadFileTestFile")

			osFs.WriteFileString(testPath, "some contents")
			defer os.Remove(testPath)

			content, err := osFs.ReadFile(testPath)
			Expect(err).ToNot(HaveOccurred())
			Expect("some contents").To(Equal(string(content)))
		})

		It("file exists", func() {
			osFs, _ := createOsFs()
			testPath := filepath.Join(os.TempDir(), "FileExistsTestFile")

			Expect(osFs.FileExists(testPath)).To(BeFalse())

			osFs.WriteFileString(testPath, "initial write")
			defer os.Remove(testPath)

			Expect(osFs.FileExists(testPath)).To(BeTrue())
		})

		It("rename", func() {
			osFs, _ := createOsFs()
			tempDir := os.TempDir()
			oldPath := filepath.Join(tempDir, "old")
			oldFilePath := filepath.Join(oldPath, "test.txt")
			newPath := filepath.Join(tempDir, "new")

			os.Mkdir(oldPath, os.ModePerm)
			_, err := os.Create(oldFilePath)
			Expect(err).ToNot(HaveOccurred())

			err = osFs.Rename(oldPath, newPath)
			Expect(err).ToNot(HaveOccurred())

			Expect(osFs.FileExists(newPath)).To(BeTrue())

			newFilePath := filepath.Join(newPath, "test.txt")
			Expect(osFs.FileExists(newFilePath)).To(BeTrue())
		})

		It("symlink", func() {
			osFs, _ := createOsFs()
			filePath := filepath.Join(os.TempDir(), "SymlinkTestFile")
			containingDir := filepath.Join(os.TempDir(), "SubDir")
			os.Remove(containingDir)
			symlinkPath := filepath.Join(containingDir, "SymlinkTestSymlink")

			osFs.WriteFileString(filePath, "some content")
			defer os.Remove(filePath)

			osFs.Symlink(filePath, symlinkPath)
			defer os.Remove(containingDir)

			symlinkStats, err := os.Lstat(symlinkPath)
			Expect(err).ToNot(HaveOccurred())
			Expect(os.ModeSymlink).To(Equal(os.ModeSymlink & symlinkStats.Mode()))

			symlinkFile, err := os.Open(symlinkPath)
			Expect(err).ToNot(HaveOccurred())
			Expect("some content").To(Equal(readFile(symlinkFile)))
		})

		It("symlink when link already exists and links to the intended path", func() {
			osFs, _ := createOsFs()
			filePath := filepath.Join(os.TempDir(), "SymlinkTestIdempotent1File")
			symlinkPath := filepath.Join(os.TempDir(), "SymlinkTestIdempotent1Symlink")

			osFs.WriteFileString(filePath, "some content")
			defer os.Remove(filePath)

			osFs.Symlink(filePath, symlinkPath)
			defer os.Remove(symlinkPath)

			firstSymlinkStats, err := os.Lstat(symlinkPath)
			Expect(err).ToNot(HaveOccurred())

			err = osFs.Symlink(filePath, symlinkPath)
			Expect(err).ToNot(HaveOccurred())

			secondSymlinkStats, err := os.Lstat(symlinkPath)
			Expect(err).ToNot(HaveOccurred())
			Expect(firstSymlinkStats.ModTime()).To(Equal(secondSymlinkStats.ModTime()))
		})

		It("symlink when link already exists and does not link to the intended path", func() {
			osFs, _ := createOsFs()
			filePath := filepath.Join(os.TempDir(), "SymlinkTestIdempotent1File")
			otherFilePath := filepath.Join(os.TempDir(), "SymlinkTestIdempotent1OtherFile")
			symlinkPath := filepath.Join(os.TempDir(), "SymlinkTestIdempotent1Symlink")

			osFs.WriteFileString(filePath, "some content")
			defer os.Remove(filePath)

			osFs.WriteFileString(otherFilePath, "other content")
			defer os.Remove(otherFilePath)

			err := osFs.Symlink(otherFilePath, symlinkPath)
			Expect(err).ToNot(HaveOccurred())

			err = osFs.Symlink(filePath, symlinkPath)
			Expect(err).ToNot(HaveOccurred())

			defer os.Remove(symlinkPath)

			symlinkStats, err := os.Lstat(symlinkPath)
			Expect(err).ToNot(HaveOccurred())
			Expect(os.ModeSymlink).To(Equal(os.ModeSymlink & symlinkStats.Mode()))

			symlinkFile, err := os.Open(symlinkPath)
			Expect(err).ToNot(HaveOccurred())
			Expect("some content").To(Equal(readFile(symlinkFile)))
		})

		It("symlink when a file exists at intended path", func() {
			osFs, _ := createOsFs()
			filePath := filepath.Join(os.TempDir(), "SymlinkTestIdempotent1File")
			symlinkPath := filepath.Join(os.TempDir(), "SymlinkTestIdempotent1Symlink")

			osFs.WriteFileString(filePath, "some content")
			defer os.Remove(filePath)

			osFs.WriteFileString(symlinkPath, "some other content")
			defer os.Remove(symlinkPath)

			err := osFs.Symlink(filePath, symlinkPath)
			Expect(err).ToNot(HaveOccurred())

			defer os.Remove(symlinkPath)

			symlinkStats, err := os.Lstat(symlinkPath)
			Expect(err).ToNot(HaveOccurred())
			Expect(os.ModeSymlink).To(Equal(os.ModeSymlink & symlinkStats.Mode()))

			symlinkFile, err := os.Open(symlinkPath)
			Expect(err).ToNot(HaveOccurred())
			Expect("some content").To(Equal(readFile(symlinkFile)))
		})

		It("read link", func() {
			osFs, _ := createOsFs()
			filePath := filepath.Join(os.TempDir(), "SymlinkTestFile")
			containingDir := filepath.Join(os.TempDir(), "SubDir")
			os.Remove(containingDir)
			symlinkPath := filepath.Join(containingDir, "SymlinkTestSymlink")

			osFs.WriteFileString(filePath, "some content")
			defer os.Remove(filePath)

			err := osFs.Symlink(filePath, symlinkPath)
			Expect(err).ToNot(HaveOccurred())
			defer os.Remove(containingDir)

			actualFilePath, err := osFs.ReadLink(symlinkPath)
			Expect(err).ToNot(HaveOccurred())
			Expect(actualFilePath).To(Equal(filePath))
		})

		It("temp file", func() {
			osFs, _ := createOsFs()

			file1, err := osFs.TempFile("fake-prefix")
			Expect(err).ToNot(HaveOccurred())
			assert.NotEmpty(GinkgoT(), file1)

			defer os.Remove(file1.Name())

			file2, err := osFs.TempFile("fake-prefix")
			Expect(err).ToNot(HaveOccurred())
			assert.NotEmpty(GinkgoT(), file2)

			defer os.Remove(file2.Name())

			assert.NotEqual(GinkgoT(), file1.Name(), file2.Name())
		})

		It("temp dir", func() {
			osFs, _ := createOsFs()

			path1, err := osFs.TempDir("fake-prefix")
			Expect(err).ToNot(HaveOccurred())
			assert.NotEmpty(GinkgoT(), path1)

			defer os.Remove(path1)

			path2, err := osFs.TempDir("fake-prefix")
			Expect(err).ToNot(HaveOccurred())
			assert.NotEmpty(GinkgoT(), path2)

			defer os.Remove(path2)

			assert.NotEqual(GinkgoT(), path1, path2)
		})

		Describe("CopyFile", func() {
			It("copies file", func() {
				osFs, _ := createOsFs()
				srcPath := "../Fixtures/test_copy_dir_entries/foo.txt"
				dstFile, err := osFs.TempFile("CopyFileTestFile")
				Expect(err).ToNot(HaveOccurred())
				defer os.Remove(dstFile.Name())

				err = osFs.CopyFile(srcPath, dstFile.Name())

				fooContent, err := osFs.ReadFileString(dstFile.Name())
				Expect(err).ToNot(HaveOccurred())
				Expect(fooContent).To(Equal("foo\n"))
			})

			It("does not leak file descriptors", func() {
				osFs, _ := createOsFs()

				srcFile, err := osFs.TempFile("srcPath")
				Expect(err).ToNot(HaveOccurred())

				err = srcFile.Close()
				Expect(err).ToNot(HaveOccurred())

				dstFile, err := osFs.TempFile("dstPath")
				Expect(err).ToNot(HaveOccurred())

				err = dstFile.Close()
				Expect(err).ToNot(HaveOccurred())

				err = osFs.CopyFile(srcFile.Name(), dstFile.Name())
				Expect(err).ToNot(HaveOccurred())

				runner := NewExecCmdRunner(boshlog.NewLogger(boshlog.LevelNone))
				stdout, _, _, err := runner.RunCommand("lsof")
				Expect(err).ToNot(HaveOccurred())

				for _, line := range strings.Split(stdout, "\n") {
					if strings.Contains(line, srcFile.Name()) {
						Fail(fmt.Sprintf("CopyFile did not close: srcFile: %s", srcFile.Name()))
					}
					if strings.Contains(line, dstFile.Name()) {
						Fail(fmt.Sprintf("CopyFile did not close: dstFile: %s", dstFile.Name()))
					}
				}

				os.Remove(srcFile.Name())
				os.Remove(dstFile.Name())
			})
		})

		It("remove all", func() {
			osFs, _ := createOsFs()
			dstFile, err := osFs.TempFile("CopyFileTestFile")
			Expect(err).ToNot(HaveOccurred())
			defer os.Remove(dstFile.Name())

			err = osFs.RemoveAll(dstFile.Name())
			Expect(err).ToNot(HaveOccurred())

			_, err = os.Stat(dstFile.Name())
			Expect(os.IsNotExist(err)).To(BeTrue())
		})
	})
}
func createOsFs() (fs FileSystem, runner CmdRunner) {
	logger := boshlog.NewLogger(boshlog.LevelNone)
	fs = NewOsFileSystem(logger)
	return
}