| // Copyright 2014 The LUCI Authors. |
| // |
| // 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 deployer |
| |
| import ( |
| "bytes" |
| "context" |
| "fmt" |
| "io" |
| "io/ioutil" |
| "os" |
| "path/filepath" |
| "runtime" |
| "sort" |
| "strings" |
| "testing" |
| |
| "go.chromium.org/luci/common/data/stringset" |
| "go.chromium.org/luci/common/logging" |
| "go.chromium.org/luci/common/logging/memlogger" |
| |
| "go.chromium.org/luci/cipd/client/cipd/fs" |
| "go.chromium.org/luci/cipd/client/cipd/pkg" |
| . "go.chromium.org/luci/cipd/common" |
| |
| . "github.com/smartystreets/goconvey/convey" |
| . "go.chromium.org/luci/common/testing/assertions" |
| ) |
| |
| func mkTempDir() string { |
| tempDir, err := ioutil.TempDir("", "cipd_test") |
| So(err, ShouldBeNil) |
| Reset(func() { os.RemoveAll(tempDir) }) |
| return tempDir |
| } |
| |
| func withMemLogger() context.Context { |
| return memlogger.Use(context.Background()) |
| } |
| |
| func loggerWarnings(c context.Context) (out []string) { |
| for _, m := range logging.Get(c).(*memlogger.MemLogger).Messages() { |
| if m.Level >= logging.Warning { |
| out = append(out, m.Msg) |
| } |
| } |
| return |
| } |
| |
| func TestUtilities(t *testing.T) { |
| t.Parallel() |
| |
| ctx := context.Background() |
| |
| Convey("Given a temp directory", t, func() { |
| tempDir := mkTempDir() |
| |
| // Wrappers that accept paths relative to tempDir. |
| touch := func(rel string) { |
| abs := filepath.Join(tempDir, filepath.FromSlash(rel)) |
| err := os.MkdirAll(filepath.Dir(abs), 0777) |
| So(err, ShouldBeNil) |
| f, err := os.Create(abs) |
| So(err, ShouldBeNil) |
| f.Close() |
| } |
| ensureLink := func(symlinkRel string, target string) { |
| err := os.Symlink(target, filepath.Join(tempDir, symlinkRel)) |
| So(err, ShouldBeNil) |
| } |
| |
| Convey("scanPackageDir works with empty dir", func() { |
| err := os.Mkdir(filepath.Join(tempDir, "dir"), 0777) |
| So(err, ShouldBeNil) |
| files, err := scanPackageDir(ctx, filepath.Join(tempDir, "dir")) |
| So(err, ShouldBeNil) |
| So(len(files), ShouldEqual, 0) |
| }) |
| |
| Convey("scanPackageDir works", func() { |
| touch("unrelated/1") |
| touch("dir/a/1") |
| touch("dir/a/2") |
| touch("dir/b/1") |
| touch("dir/.cipdpkg/abc") |
| touch("dir/.cipd/abc") |
| |
| runScanPackageDir := func() sort.StringSlice { |
| files, err := scanPackageDir(ctx, filepath.Join(tempDir, "dir")) |
| So(err, ShouldBeNil) |
| names := sort.StringSlice{} |
| for _, f := range files { |
| names = append(names, f.Name) |
| } |
| names.Sort() |
| return names |
| } |
| |
| // Symlinks doesn't work on Windows, test them only on Posix. |
| if runtime.GOOS == "windows" { |
| Convey("works on Windows", func() { |
| So(runScanPackageDir(), ShouldResemble, sort.StringSlice{ |
| "a/1", |
| "a/2", |
| "b/1", |
| }) |
| }) |
| } else { |
| Convey("works on Posix", func() { |
| ensureLink("dir/a/sym_link", "target") |
| So(runScanPackageDir(), ShouldResemble, sort.StringSlice{ |
| "a/1", |
| "a/2", |
| "a/sym_link", |
| "b/1", |
| }) |
| }) |
| } |
| }) |
| }) |
| } |
| |
| func TestDeployInstance(t *testing.T) { |
| t.Parallel() |
| |
| ctx := context.Background() |
| |
| Convey("Given a temp directory", t, func() { |
| tempDir := mkTempDir() |
| |
| Convey("Try to deploy package instance with bad package name", func() { |
| _, err := New(tempDir).DeployInstance( |
| ctx, "", makeTestInstance("../test/package", nil, pkg.InstallModeCopy)) |
| So(err, ShouldErrLike, "invalid package name") |
| }) |
| |
| Convey("Try to deploy package instance with bad instance ID", func() { |
| inst := makeTestInstance("test/package", nil, pkg.InstallModeCopy) |
| inst.instanceID = "../000000000" |
| _, err := New(tempDir).DeployInstance(ctx, "", inst) |
| So(err, ShouldErrLike, "not a valid package instance ID") |
| }) |
| |
| Convey("Try to deploy package instance in bad subdir", func() { |
| inst := makeTestInstance("test/package", nil, pkg.InstallModeCopy) |
| inst.instanceID = "../000000000" |
| _, err := New(tempDir).DeployInstance(ctx, "/abspath", inst) |
| So(err, ShouldErrLike, "bad subdir") |
| }) |
| }) |
| } |
| |
| func TestDeployInstanceSymlinkMode(t *testing.T) { |
| t.Parallel() |
| |
| if runtime.GOOS == "windows" { |
| t.Skip("Skipping on Windows: no symlinks") |
| } |
| |
| ctx := context.Background() |
| |
| Convey("Given a temp directory", t, func() { |
| tempDir := mkTempDir() |
| |
| Convey("DeployInstance new empty package instance", func() { |
| inst := makeTestInstance("test/package", nil, pkg.InstallModeSymlink) |
| info, err := New(tempDir).DeployInstance(ctx, "", inst) |
| So(err, ShouldBeNil) |
| So(info, ShouldResemble, inst.Pin()) |
| So(scanDir(tempDir), ShouldResemble, []string{ |
| ".cipd/pkgs/0/-wEu41lw0_aOomrCDp4gKs0uClIlMg25S2j-UMHKwFYC/.cipdpkg/manifest.json", |
| ".cipd/pkgs/0/_current:-wEu41lw0_aOomrCDp4gKs0uClIlMg25S2j-UMHKwFYC", |
| ".cipd/pkgs/0/description.json", |
| ".cipd/tmp!", |
| }) |
| fInfo, err := os.Stat(filepath.Join(tempDir, ".cipd", "pkgs", "0")) |
| So(err, ShouldBeNil) |
| So(fInfo.Mode(), ShouldEqual, os.FileMode(0755)|os.ModeDir) |
| |
| Convey("in subdir", func() { |
| info, err := New(tempDir).DeployInstance(ctx, "subdir", inst) |
| So(err, ShouldBeNil) |
| So(info, ShouldResemble, inst.Pin()) |
| So(scanDir(tempDir), ShouldResemble, []string{ |
| ".cipd/pkgs/0/-wEu41lw0_aOomrCDp4gKs0uClIlMg25S2j-UMHKwFYC/.cipdpkg/manifest.json", |
| ".cipd/pkgs/0/_current:-wEu41lw0_aOomrCDp4gKs0uClIlMg25S2j-UMHKwFYC", |
| ".cipd/pkgs/0/description.json", |
| ".cipd/pkgs/1/-wEu41lw0_aOomrCDp4gKs0uClIlMg25S2j-UMHKwFYC/.cipdpkg/manifest.json", |
| ".cipd/pkgs/1/_current:-wEu41lw0_aOomrCDp4gKs0uClIlMg25S2j-UMHKwFYC", |
| ".cipd/pkgs/1/description.json", |
| ".cipd/tmp!", |
| "subdir!", |
| }) |
| }) |
| }) |
| |
| Convey("DeployInstance new non-empty package instance", func() { |
| inst := makeTestInstance("test/package", []fs.File{ |
| fs.NewTestFile("some/file/path", "data a", fs.TestFileOpts{}), |
| fs.NewTestFile("some/executable", "data b", fs.TestFileOpts{Executable: true}), |
| fs.NewTestSymlink("some/symlink", "executable"), |
| fs.NewTestFile(".cipd/pkg/0/description.json", "{}", fs.TestFileOpts{}), // should be ignored |
| }, pkg.InstallModeSymlink) |
| _, err := New(tempDir).DeployInstance(ctx, "", inst) |
| So(err, ShouldBeNil) |
| So(scanDir(tempDir), ShouldResemble, []string{ |
| ".cipd/pkgs/0/-wEu41lw0_aOomrCDp4gKs0uClIlMg25S2j-UMHKwFYC/.cipdpkg/manifest.json", |
| ".cipd/pkgs/0/-wEu41lw0_aOomrCDp4gKs0uClIlMg25S2j-UMHKwFYC/some/executable*", |
| ".cipd/pkgs/0/-wEu41lw0_aOomrCDp4gKs0uClIlMg25S2j-UMHKwFYC/some/file/path", |
| ".cipd/pkgs/0/-wEu41lw0_aOomrCDp4gKs0uClIlMg25S2j-UMHKwFYC/some/symlink:executable", |
| ".cipd/pkgs/0/_current:-wEu41lw0_aOomrCDp4gKs0uClIlMg25S2j-UMHKwFYC", |
| ".cipd/pkgs/0/description.json", |
| ".cipd/tmp!", |
| "some/executable:../.cipd/pkgs/0/_current/some/executable", |
| "some/file/path:../../.cipd/pkgs/0/_current/some/file/path", |
| "some/symlink:../.cipd/pkgs/0/_current/some/symlink", |
| }) |
| // Ensure symlinks are actually traversable. |
| body, err := ioutil.ReadFile(filepath.Join(tempDir, "some", "file", "path")) |
| So(err, ShouldBeNil) |
| So(string(body), ShouldEqual, "data a") |
| // Symlink to symlink is traversable too. |
| body, err = ioutil.ReadFile(filepath.Join(tempDir, "some", "symlink")) |
| So(err, ShouldBeNil) |
| So(string(body), ShouldEqual, "data b") |
| |
| Convey("in subdir", func() { |
| _, err := New(tempDir).DeployInstance(ctx, "subdir", inst) |
| So(err, ShouldBeNil) |
| So(scanDir(tempDir), ShouldResemble, []string{ |
| ".cipd/pkgs/0/-wEu41lw0_aOomrCDp4gKs0uClIlMg25S2j-UMHKwFYC/.cipdpkg/manifest.json", |
| ".cipd/pkgs/0/-wEu41lw0_aOomrCDp4gKs0uClIlMg25S2j-UMHKwFYC/some/executable*", |
| ".cipd/pkgs/0/-wEu41lw0_aOomrCDp4gKs0uClIlMg25S2j-UMHKwFYC/some/file/path", |
| ".cipd/pkgs/0/-wEu41lw0_aOomrCDp4gKs0uClIlMg25S2j-UMHKwFYC/some/symlink:executable", |
| ".cipd/pkgs/0/_current:-wEu41lw0_aOomrCDp4gKs0uClIlMg25S2j-UMHKwFYC", |
| ".cipd/pkgs/0/description.json", |
| ".cipd/pkgs/1/-wEu41lw0_aOomrCDp4gKs0uClIlMg25S2j-UMHKwFYC/.cipdpkg/manifest.json", |
| ".cipd/pkgs/1/-wEu41lw0_aOomrCDp4gKs0uClIlMg25S2j-UMHKwFYC/some/executable*", |
| ".cipd/pkgs/1/-wEu41lw0_aOomrCDp4gKs0uClIlMg25S2j-UMHKwFYC/some/file/path", |
| ".cipd/pkgs/1/-wEu41lw0_aOomrCDp4gKs0uClIlMg25S2j-UMHKwFYC/some/symlink:executable", |
| ".cipd/pkgs/1/_current:-wEu41lw0_aOomrCDp4gKs0uClIlMg25S2j-UMHKwFYC", |
| ".cipd/pkgs/1/description.json", |
| ".cipd/tmp!", |
| "some/executable:../.cipd/pkgs/0/_current/some/executable", |
| "some/file/path:../../.cipd/pkgs/0/_current/some/file/path", |
| "some/symlink:../.cipd/pkgs/0/_current/some/symlink", |
| "subdir/some/executable:../../.cipd/pkgs/1/_current/some/executable", |
| "subdir/some/file/path:../../../.cipd/pkgs/1/_current/some/file/path", |
| "subdir/some/symlink:../../.cipd/pkgs/1/_current/some/symlink", |
| }) |
| |
| // Ensure symlinks are actually traversable. |
| body, err := ioutil.ReadFile(filepath.Join(tempDir, "subdir", "some", "file", "path")) |
| So(err, ShouldBeNil) |
| So(string(body), ShouldEqual, "data a") |
| // Symlink to symlink is traversable too. |
| body, err = ioutil.ReadFile(filepath.Join(tempDir, "subdir", "some", "symlink")) |
| So(err, ShouldBeNil) |
| So(string(body), ShouldEqual, "data b") |
| }) |
| }) |
| |
| Convey("Redeploy same package instance", func() { |
| inst := makeTestInstance("test/package", []fs.File{ |
| fs.NewTestFile("some/file/path", "data a", fs.TestFileOpts{}), |
| fs.NewTestFile("some/executable", "data b", fs.TestFileOpts{Executable: true}), |
| fs.NewTestSymlink("some/symlink", "executable"), |
| }, pkg.InstallModeSymlink) |
| _, err := New(tempDir).DeployInstance(ctx, "", inst) |
| So(err, ShouldBeNil) |
| _, err = New(tempDir).DeployInstance(ctx, "", inst) |
| So(err, ShouldBeNil) |
| So(scanDir(tempDir), ShouldResemble, []string{ |
| ".cipd/pkgs/0/-wEu41lw0_aOomrCDp4gKs0uClIlMg25S2j-UMHKwFYC/.cipdpkg/manifest.json", |
| ".cipd/pkgs/0/-wEu41lw0_aOomrCDp4gKs0uClIlMg25S2j-UMHKwFYC/some/executable*", |
| ".cipd/pkgs/0/-wEu41lw0_aOomrCDp4gKs0uClIlMg25S2j-UMHKwFYC/some/file/path", |
| ".cipd/pkgs/0/-wEu41lw0_aOomrCDp4gKs0uClIlMg25S2j-UMHKwFYC/some/symlink:executable", |
| ".cipd/pkgs/0/_current:-wEu41lw0_aOomrCDp4gKs0uClIlMg25S2j-UMHKwFYC", |
| ".cipd/pkgs/0/description.json", |
| ".cipd/tmp!", |
| ".cipd/trash!", |
| "some/executable:../.cipd/pkgs/0/_current/some/executable", |
| "some/file/path:../../.cipd/pkgs/0/_current/some/file/path", |
| "some/symlink:../.cipd/pkgs/0/_current/some/symlink", |
| }) |
| |
| Convey("in subdir", func() { |
| _, err := New(tempDir).DeployInstance(ctx, "subdir", inst) |
| So(err, ShouldBeNil) |
| _, err = New(tempDir).DeployInstance(ctx, "subdir", inst) |
| So(err, ShouldBeNil) |
| So(scanDir(tempDir), ShouldResemble, []string{ |
| ".cipd/pkgs/0/-wEu41lw0_aOomrCDp4gKs0uClIlMg25S2j-UMHKwFYC/.cipdpkg/manifest.json", |
| ".cipd/pkgs/0/-wEu41lw0_aOomrCDp4gKs0uClIlMg25S2j-UMHKwFYC/some/executable*", |
| ".cipd/pkgs/0/-wEu41lw0_aOomrCDp4gKs0uClIlMg25S2j-UMHKwFYC/some/file/path", |
| ".cipd/pkgs/0/-wEu41lw0_aOomrCDp4gKs0uClIlMg25S2j-UMHKwFYC/some/symlink:executable", |
| ".cipd/pkgs/0/_current:-wEu41lw0_aOomrCDp4gKs0uClIlMg25S2j-UMHKwFYC", |
| ".cipd/pkgs/0/description.json", |
| ".cipd/pkgs/1/-wEu41lw0_aOomrCDp4gKs0uClIlMg25S2j-UMHKwFYC/.cipdpkg/manifest.json", |
| ".cipd/pkgs/1/-wEu41lw0_aOomrCDp4gKs0uClIlMg25S2j-UMHKwFYC/some/executable*", |
| ".cipd/pkgs/1/-wEu41lw0_aOomrCDp4gKs0uClIlMg25S2j-UMHKwFYC/some/file/path", |
| ".cipd/pkgs/1/-wEu41lw0_aOomrCDp4gKs0uClIlMg25S2j-UMHKwFYC/some/symlink:executable", |
| ".cipd/pkgs/1/_current:-wEu41lw0_aOomrCDp4gKs0uClIlMg25S2j-UMHKwFYC", |
| ".cipd/pkgs/1/description.json", |
| ".cipd/tmp!", |
| ".cipd/trash!", |
| "some/executable:../.cipd/pkgs/0/_current/some/executable", |
| "some/file/path:../../.cipd/pkgs/0/_current/some/file/path", |
| "some/symlink:../.cipd/pkgs/0/_current/some/symlink", |
| "subdir/some/executable:../../.cipd/pkgs/1/_current/some/executable", |
| "subdir/some/file/path:../../../.cipd/pkgs/1/_current/some/file/path", |
| "subdir/some/symlink:../../.cipd/pkgs/1/_current/some/symlink", |
| }) |
| }) |
| }) |
| |
| Convey("DeployInstance package update", func() { |
| oldPkg := makeTestInstance("test/package", []fs.File{ |
| fs.NewTestFile("some/file/path", "data a old", fs.TestFileOpts{}), |
| fs.NewTestFile("some/executable", "data b old", fs.TestFileOpts{Executable: true}), |
| fs.NewTestFile("some/to-be-empty-dir/file", "data", fs.TestFileOpts{}), |
| fs.NewTestFile("old only", "data c old", fs.TestFileOpts{Executable: true}), |
| fs.NewTestFile("mode change 1", "data d", fs.TestFileOpts{Executable: true}), |
| fs.NewTestFile("mode change 2", "data e", fs.TestFileOpts{}), |
| fs.NewTestSymlink("symlink unchanged", "target"), |
| fs.NewTestSymlink("symlink changed", "old target"), |
| fs.NewTestSymlink("symlink removed", "target"), |
| }, pkg.InstallModeSymlink) |
| oldPkg.instanceID = "000000000_aOomrCDp4gKs0uClIlMg25S2j-UMHKwFYC" |
| |
| newPkg := makeTestInstance("test/package", []fs.File{ |
| fs.NewTestFile("some/file/path", "data a new", fs.TestFileOpts{}), |
| fs.NewTestFile("some/executable", "data b new", fs.TestFileOpts{Executable: true}), |
| fs.NewTestFile("mode change 1", "data d", fs.TestFileOpts{}), |
| fs.NewTestFile("mode change 2", "data d", fs.TestFileOpts{Executable: true}), |
| fs.NewTestSymlink("symlink unchanged", "target"), |
| fs.NewTestSymlink("symlink changed", "new target"), |
| }, pkg.InstallModeSymlink) |
| newPkg.instanceID = "111111111_aOomrCDp4gKs0uClIlMg25S2j-UMHKwFYC" |
| |
| _, err := New(tempDir).DeployInstance(ctx, "", oldPkg) |
| So(err, ShouldBeNil) |
| _, err = New(tempDir).DeployInstance(ctx, "", newPkg) |
| So(err, ShouldBeNil) |
| |
| So(scanDir(tempDir), ShouldResemble, []string{ |
| ".cipd/pkgs/0/111111111_aOomrCDp4gKs0uClIlMg25S2j-UMHKwFYC/.cipdpkg/manifest.json", |
| ".cipd/pkgs/0/111111111_aOomrCDp4gKs0uClIlMg25S2j-UMHKwFYC/mode change 1", |
| ".cipd/pkgs/0/111111111_aOomrCDp4gKs0uClIlMg25S2j-UMHKwFYC/mode change 2*", |
| ".cipd/pkgs/0/111111111_aOomrCDp4gKs0uClIlMg25S2j-UMHKwFYC/some/executable*", |
| ".cipd/pkgs/0/111111111_aOomrCDp4gKs0uClIlMg25S2j-UMHKwFYC/some/file/path", |
| ".cipd/pkgs/0/111111111_aOomrCDp4gKs0uClIlMg25S2j-UMHKwFYC/symlink changed:new target", |
| ".cipd/pkgs/0/111111111_aOomrCDp4gKs0uClIlMg25S2j-UMHKwFYC/symlink unchanged:target", |
| ".cipd/pkgs/0/_current:111111111_aOomrCDp4gKs0uClIlMg25S2j-UMHKwFYC", |
| ".cipd/pkgs/0/description.json", |
| ".cipd/tmp!", |
| "mode change 1:.cipd/pkgs/0/_current/mode change 1", |
| "mode change 2:.cipd/pkgs/0/_current/mode change 2", |
| "some/executable:../.cipd/pkgs/0/_current/some/executable", |
| "some/file/path:../../.cipd/pkgs/0/_current/some/file/path", |
| "symlink changed:.cipd/pkgs/0/_current/symlink changed", |
| "symlink unchanged:.cipd/pkgs/0/_current/symlink unchanged", |
| }) |
| |
| Convey("in subdir", func() { |
| _, err := New(tempDir).DeployInstance(ctx, "subdir", oldPkg) |
| So(err, ShouldBeNil) |
| _, err = New(tempDir).DeployInstance(ctx, "subdir", newPkg) |
| So(err, ShouldBeNil) |
| |
| So(scanDir(tempDir), ShouldResemble, []string{ |
| ".cipd/pkgs/0/111111111_aOomrCDp4gKs0uClIlMg25S2j-UMHKwFYC/.cipdpkg/manifest.json", |
| ".cipd/pkgs/0/111111111_aOomrCDp4gKs0uClIlMg25S2j-UMHKwFYC/mode change 1", |
| ".cipd/pkgs/0/111111111_aOomrCDp4gKs0uClIlMg25S2j-UMHKwFYC/mode change 2*", |
| ".cipd/pkgs/0/111111111_aOomrCDp4gKs0uClIlMg25S2j-UMHKwFYC/some/executable*", |
| ".cipd/pkgs/0/111111111_aOomrCDp4gKs0uClIlMg25S2j-UMHKwFYC/some/file/path", |
| ".cipd/pkgs/0/111111111_aOomrCDp4gKs0uClIlMg25S2j-UMHKwFYC/symlink changed:new target", |
| ".cipd/pkgs/0/111111111_aOomrCDp4gKs0uClIlMg25S2j-UMHKwFYC/symlink unchanged:target", |
| ".cipd/pkgs/0/_current:111111111_aOomrCDp4gKs0uClIlMg25S2j-UMHKwFYC", |
| ".cipd/pkgs/0/description.json", |
| ".cipd/pkgs/1/111111111_aOomrCDp4gKs0uClIlMg25S2j-UMHKwFYC/.cipdpkg/manifest.json", |
| ".cipd/pkgs/1/111111111_aOomrCDp4gKs0uClIlMg25S2j-UMHKwFYC/mode change 1", |
| ".cipd/pkgs/1/111111111_aOomrCDp4gKs0uClIlMg25S2j-UMHKwFYC/mode change 2*", |
| ".cipd/pkgs/1/111111111_aOomrCDp4gKs0uClIlMg25S2j-UMHKwFYC/some/executable*", |
| ".cipd/pkgs/1/111111111_aOomrCDp4gKs0uClIlMg25S2j-UMHKwFYC/some/file/path", |
| ".cipd/pkgs/1/111111111_aOomrCDp4gKs0uClIlMg25S2j-UMHKwFYC/symlink changed:new target", |
| ".cipd/pkgs/1/111111111_aOomrCDp4gKs0uClIlMg25S2j-UMHKwFYC/symlink unchanged:target", |
| ".cipd/pkgs/1/_current:111111111_aOomrCDp4gKs0uClIlMg25S2j-UMHKwFYC", |
| ".cipd/pkgs/1/description.json", |
| ".cipd/tmp!", |
| "mode change 1:.cipd/pkgs/0/_current/mode change 1", |
| "mode change 2:.cipd/pkgs/0/_current/mode change 2", |
| "some/executable:../.cipd/pkgs/0/_current/some/executable", |
| "some/file/path:../../.cipd/pkgs/0/_current/some/file/path", |
| "subdir/mode change 1:../.cipd/pkgs/1/_current/mode change 1", |
| "subdir/mode change 2:../.cipd/pkgs/1/_current/mode change 2", |
| "subdir/some/executable:../../.cipd/pkgs/1/_current/some/executable", |
| "subdir/some/file/path:../../../.cipd/pkgs/1/_current/some/file/path", |
| "subdir/symlink changed:../.cipd/pkgs/1/_current/symlink changed", |
| "subdir/symlink unchanged:../.cipd/pkgs/1/_current/symlink unchanged", |
| "symlink changed:.cipd/pkgs/0/_current/symlink changed", |
| "symlink unchanged:.cipd/pkgs/0/_current/symlink unchanged", |
| }) |
| }) |
| }) |
| |
| Convey("DeployInstance two different packages", func() { |
| pkg1 := makeTestInstance("test/package", []fs.File{ |
| fs.NewTestFile("some/file/path", "data a old", fs.TestFileOpts{}), |
| fs.NewTestFile("some/executable", "data b old", fs.TestFileOpts{Executable: true}), |
| fs.NewTestFile("pkg1 file", "data c", fs.TestFileOpts{}), |
| }, pkg.InstallModeSymlink) |
| pkg1.instanceID = "000000000_aOomrCDp4gKs0uClIlMg25S2j-UMHKwFYC" |
| |
| // Nesting in package names is allowed. |
| pkg2 := makeTestInstance("test/package/another", []fs.File{ |
| fs.NewTestFile("some/file/path", "data a new", fs.TestFileOpts{}), |
| fs.NewTestFile("some/executable", "data b new", fs.TestFileOpts{Executable: true}), |
| fs.NewTestFile("pkg2 file", "data d", fs.TestFileOpts{}), |
| }, pkg.InstallModeSymlink) |
| pkg2.instanceID = "111111111_aOomrCDp4gKs0uClIlMg25S2j-UMHKwFYC" |
| |
| _, err := New(tempDir).DeployInstance(ctx, "", pkg1) |
| So(err, ShouldBeNil) |
| _, err = New(tempDir).DeployInstance(ctx, "", pkg2) |
| So(err, ShouldBeNil) |
| |
| // TODO: Conflicting symlinks point to last installed package, it is not |
| // very deterministic. |
| So(scanDir(tempDir), ShouldResemble, []string{ |
| ".cipd/pkgs/0/000000000_aOomrCDp4gKs0uClIlMg25S2j-UMHKwFYC/.cipdpkg/manifest.json", |
| ".cipd/pkgs/0/000000000_aOomrCDp4gKs0uClIlMg25S2j-UMHKwFYC/pkg1 file", |
| ".cipd/pkgs/0/000000000_aOomrCDp4gKs0uClIlMg25S2j-UMHKwFYC/some/executable*", |
| ".cipd/pkgs/0/000000000_aOomrCDp4gKs0uClIlMg25S2j-UMHKwFYC/some/file/path", |
| ".cipd/pkgs/0/_current:000000000_aOomrCDp4gKs0uClIlMg25S2j-UMHKwFYC", |
| ".cipd/pkgs/0/description.json", |
| ".cipd/pkgs/1/111111111_aOomrCDp4gKs0uClIlMg25S2j-UMHKwFYC/.cipdpkg/manifest.json", |
| ".cipd/pkgs/1/111111111_aOomrCDp4gKs0uClIlMg25S2j-UMHKwFYC/pkg2 file", |
| ".cipd/pkgs/1/111111111_aOomrCDp4gKs0uClIlMg25S2j-UMHKwFYC/some/executable*", |
| ".cipd/pkgs/1/111111111_aOomrCDp4gKs0uClIlMg25S2j-UMHKwFYC/some/file/path", |
| ".cipd/pkgs/1/_current:111111111_aOomrCDp4gKs0uClIlMg25S2j-UMHKwFYC", |
| ".cipd/pkgs/1/description.json", |
| ".cipd/tmp!", |
| "pkg1 file:.cipd/pkgs/0/_current/pkg1 file", |
| "pkg2 file:.cipd/pkgs/1/_current/pkg2 file", |
| "some/executable:../.cipd/pkgs/1/_current/some/executable", |
| "some/file/path:../../.cipd/pkgs/1/_current/some/file/path", |
| }) |
| |
| Convey("in subdir", func() { |
| _, err := New(tempDir).DeployInstance(ctx, "subdir", pkg1) |
| So(err, ShouldBeNil) |
| _, err = New(tempDir).DeployInstance(ctx, "subdir", pkg2) |
| So(err, ShouldBeNil) |
| |
| // TODO: Conflicting symlinks point to last installed package, it is not |
| // very deterministic. |
| So(scanDir(tempDir), ShouldResemble, []string{ |
| ".cipd/pkgs/0/000000000_aOomrCDp4gKs0uClIlMg25S2j-UMHKwFYC/.cipdpkg/manifest.json", |
| ".cipd/pkgs/0/000000000_aOomrCDp4gKs0uClIlMg25S2j-UMHKwFYC/pkg1 file", |
| ".cipd/pkgs/0/000000000_aOomrCDp4gKs0uClIlMg25S2j-UMHKwFYC/some/executable*", |
| ".cipd/pkgs/0/000000000_aOomrCDp4gKs0uClIlMg25S2j-UMHKwFYC/some/file/path", |
| ".cipd/pkgs/0/_current:000000000_aOomrCDp4gKs0uClIlMg25S2j-UMHKwFYC", |
| ".cipd/pkgs/0/description.json", |
| ".cipd/pkgs/1/111111111_aOomrCDp4gKs0uClIlMg25S2j-UMHKwFYC/.cipdpkg/manifest.json", |
| ".cipd/pkgs/1/111111111_aOomrCDp4gKs0uClIlMg25S2j-UMHKwFYC/pkg2 file", |
| ".cipd/pkgs/1/111111111_aOomrCDp4gKs0uClIlMg25S2j-UMHKwFYC/some/executable*", |
| ".cipd/pkgs/1/111111111_aOomrCDp4gKs0uClIlMg25S2j-UMHKwFYC/some/file/path", |
| ".cipd/pkgs/1/_current:111111111_aOomrCDp4gKs0uClIlMg25S2j-UMHKwFYC", |
| ".cipd/pkgs/1/description.json", |
| ".cipd/pkgs/2/000000000_aOomrCDp4gKs0uClIlMg25S2j-UMHKwFYC/.cipdpkg/manifest.json", |
| ".cipd/pkgs/2/000000000_aOomrCDp4gKs0uClIlMg25S2j-UMHKwFYC/pkg1 file", |
| ".cipd/pkgs/2/000000000_aOomrCDp4gKs0uClIlMg25S2j-UMHKwFYC/some/executable*", |
| ".cipd/pkgs/2/000000000_aOomrCDp4gKs0uClIlMg25S2j-UMHKwFYC/some/file/path", |
| ".cipd/pkgs/2/_current:000000000_aOomrCDp4gKs0uClIlMg25S2j-UMHKwFYC", |
| ".cipd/pkgs/2/description.json", |
| ".cipd/pkgs/3/111111111_aOomrCDp4gKs0uClIlMg25S2j-UMHKwFYC/.cipdpkg/manifest.json", |
| ".cipd/pkgs/3/111111111_aOomrCDp4gKs0uClIlMg25S2j-UMHKwFYC/pkg2 file", |
| ".cipd/pkgs/3/111111111_aOomrCDp4gKs0uClIlMg25S2j-UMHKwFYC/some/executable*", |
| ".cipd/pkgs/3/111111111_aOomrCDp4gKs0uClIlMg25S2j-UMHKwFYC/some/file/path", |
| ".cipd/pkgs/3/_current:111111111_aOomrCDp4gKs0uClIlMg25S2j-UMHKwFYC", |
| ".cipd/pkgs/3/description.json", |
| ".cipd/tmp!", |
| "pkg1 file:.cipd/pkgs/0/_current/pkg1 file", |
| "pkg2 file:.cipd/pkgs/1/_current/pkg2 file", |
| "some/executable:../.cipd/pkgs/1/_current/some/executable", |
| "some/file/path:../../.cipd/pkgs/1/_current/some/file/path", |
| "subdir/pkg1 file:../.cipd/pkgs/2/_current/pkg1 file", |
| "subdir/pkg2 file:../.cipd/pkgs/3/_current/pkg2 file", |
| "subdir/some/executable:../../.cipd/pkgs/3/_current/some/executable", |
| "subdir/some/file/path:../../../.cipd/pkgs/3/_current/some/file/path", |
| }) |
| }) |
| }) |
| }) |
| } |
| |
| func TestDeployInstanceCopyModePosix(t *testing.T) { |
| t.Parallel() |
| |
| if runtime.GOOS == "windows" { |
| t.Skip("Skipping on windows") |
| } |
| |
| ctx := context.Background() |
| |
| Convey("Given a temp directory", t, func() { |
| tempDir := mkTempDir() |
| |
| Convey("DeployInstance new empty package instance", func() { |
| inst := makeTestInstance("test/package", nil, pkg.InstallModeCopy) |
| info, err := New(tempDir).DeployInstance(ctx, "", inst) |
| So(err, ShouldBeNil) |
| So(info, ShouldResemble, inst.Pin()) |
| So(scanDir(tempDir), ShouldResemble, []string{ |
| ".cipd/pkgs/0/-wEu41lw0_aOomrCDp4gKs0uClIlMg25S2j-UMHKwFYC/.cipdpkg/manifest.json", |
| ".cipd/pkgs/0/_current:-wEu41lw0_aOomrCDp4gKs0uClIlMg25S2j-UMHKwFYC", |
| ".cipd/pkgs/0/description.json", |
| ".cipd/tmp!", |
| }) |
| |
| Convey("in subdir", func() { |
| inst := makeTestInstance("test/package", nil, pkg.InstallModeCopy) |
| info, err := New(tempDir).DeployInstance(ctx, "subdir", inst) |
| So(err, ShouldBeNil) |
| So(info, ShouldResemble, inst.Pin()) |
| So(scanDir(tempDir), ShouldResemble, []string{ |
| ".cipd/pkgs/0/-wEu41lw0_aOomrCDp4gKs0uClIlMg25S2j-UMHKwFYC/.cipdpkg/manifest.json", |
| ".cipd/pkgs/0/_current:-wEu41lw0_aOomrCDp4gKs0uClIlMg25S2j-UMHKwFYC", |
| ".cipd/pkgs/0/description.json", |
| ".cipd/pkgs/1/-wEu41lw0_aOomrCDp4gKs0uClIlMg25S2j-UMHKwFYC/.cipdpkg/manifest.json", |
| ".cipd/pkgs/1/_current:-wEu41lw0_aOomrCDp4gKs0uClIlMg25S2j-UMHKwFYC", |
| ".cipd/pkgs/1/description.json", |
| ".cipd/tmp!", |
| "subdir!", |
| }) |
| }) |
| }) |
| |
| Convey("DeployInstance new non-empty package instance", func() { |
| inst := makeTestInstance("test/package", []fs.File{ |
| fs.NewTestFile("some/file/path", "data a", fs.TestFileOpts{}), |
| fs.NewTestFile("some/executable", "data b", fs.TestFileOpts{Executable: true}), |
| fs.NewTestSymlink("some/symlink", "executable"), |
| fs.NewTestFile(".cipd/pkg/0/description.json", "{}", fs.TestFileOpts{}), // should be ignored |
| }, pkg.InstallModeCopy) |
| _, err := New(tempDir).DeployInstance(ctx, "", inst) |
| So(err, ShouldBeNil) |
| So(scanDir(tempDir), ShouldResemble, []string{ |
| ".cipd/pkgs/0/-wEu41lw0_aOomrCDp4gKs0uClIlMg25S2j-UMHKwFYC/.cipdpkg/manifest.json", |
| ".cipd/pkgs/0/_current:-wEu41lw0_aOomrCDp4gKs0uClIlMg25S2j-UMHKwFYC", |
| ".cipd/pkgs/0/description.json", |
| ".cipd/tmp!", |
| "some/executable*", |
| "some/file/path", |
| "some/symlink:executable", |
| }) |
| |
| Convey("in subdir", func() { |
| _, err := New(tempDir).DeployInstance(ctx, "subdir", inst) |
| So(err, ShouldBeNil) |
| So(scanDir(tempDir), ShouldResemble, []string{ |
| ".cipd/pkgs/0/-wEu41lw0_aOomrCDp4gKs0uClIlMg25S2j-UMHKwFYC/.cipdpkg/manifest.json", |
| ".cipd/pkgs/0/_current:-wEu41lw0_aOomrCDp4gKs0uClIlMg25S2j-UMHKwFYC", |
| ".cipd/pkgs/0/description.json", |
| ".cipd/pkgs/1/-wEu41lw0_aOomrCDp4gKs0uClIlMg25S2j-UMHKwFYC/.cipdpkg/manifest.json", |
| ".cipd/pkgs/1/_current:-wEu41lw0_aOomrCDp4gKs0uClIlMg25S2j-UMHKwFYC", |
| ".cipd/pkgs/1/description.json", |
| ".cipd/tmp!", |
| "some/executable*", |
| "some/file/path", |
| "some/symlink:executable", |
| "subdir/some/executable*", |
| "subdir/some/file/path", |
| "subdir/some/symlink:executable", |
| }) |
| }) |
| }) |
| |
| Convey("Redeploy same package instance", func() { |
| inst := makeTestInstance("test/package", []fs.File{ |
| fs.NewTestFile("some/file/path", "data a", fs.TestFileOpts{}), |
| fs.NewTestFile("some/executable", "data b", fs.TestFileOpts{Executable: true}), |
| fs.NewTestSymlink("some/symlink", "executable"), |
| }, pkg.InstallModeCopy) |
| _, err := New(tempDir).DeployInstance(ctx, "", inst) |
| So(err, ShouldBeNil) |
| _, err = New(tempDir).DeployInstance(ctx, "", inst) |
| So(err, ShouldBeNil) |
| So(scanDir(tempDir), ShouldResemble, []string{ |
| ".cipd/pkgs/0/-wEu41lw0_aOomrCDp4gKs0uClIlMg25S2j-UMHKwFYC/.cipdpkg/manifest.json", |
| ".cipd/pkgs/0/_current:-wEu41lw0_aOomrCDp4gKs0uClIlMg25S2j-UMHKwFYC", |
| ".cipd/pkgs/0/description.json", |
| ".cipd/tmp!", |
| ".cipd/trash!", |
| "some/executable*", |
| "some/file/path", |
| "some/symlink:executable", |
| }) |
| |
| Convey("in subdir", func() { |
| _, err := New(tempDir).DeployInstance(ctx, "somedir", inst) |
| So(err, ShouldBeNil) |
| _, err = New(tempDir).DeployInstance(ctx, "somedir", inst) |
| So(err, ShouldBeNil) |
| So(scanDir(tempDir), ShouldResemble, []string{ |
| ".cipd/pkgs/0/-wEu41lw0_aOomrCDp4gKs0uClIlMg25S2j-UMHKwFYC/.cipdpkg/manifest.json", |
| ".cipd/pkgs/0/_current:-wEu41lw0_aOomrCDp4gKs0uClIlMg25S2j-UMHKwFYC", |
| ".cipd/pkgs/0/description.json", |
| ".cipd/pkgs/1/-wEu41lw0_aOomrCDp4gKs0uClIlMg25S2j-UMHKwFYC/.cipdpkg/manifest.json", |
| ".cipd/pkgs/1/_current:-wEu41lw0_aOomrCDp4gKs0uClIlMg25S2j-UMHKwFYC", |
| ".cipd/pkgs/1/description.json", |
| ".cipd/tmp!", |
| ".cipd/trash!", |
| "some/executable*", |
| "some/file/path", |
| "some/symlink:executable", |
| "somedir/some/executable*", |
| "somedir/some/file/path", |
| "somedir/some/symlink:executable", |
| }) |
| }) |
| }) |
| |
| Convey("DeployInstance package update", func() { |
| oldPkg := makeTestInstance("test/package", []fs.File{ |
| fs.NewTestFile("some/file/path", "data a old", fs.TestFileOpts{}), |
| fs.NewTestFile("some/executable", "data b old", fs.TestFileOpts{Executable: true}), |
| fs.NewTestFile("some/to-be-empty-dir/file", "data", fs.TestFileOpts{}), |
| fs.NewTestFile("old only", "data c old", fs.TestFileOpts{Executable: true}), |
| fs.NewTestFile("mode change 1", "data d", fs.TestFileOpts{Executable: true}), |
| fs.NewTestFile("mode change 2", "data e", fs.TestFileOpts{}), |
| fs.NewTestSymlink("symlink unchanged", "target"), |
| fs.NewTestSymlink("symlink changed", "old target"), |
| fs.NewTestSymlink("symlink removed", "target"), |
| }, pkg.InstallModeCopy) |
| oldPkg.instanceID = "000000000_aOomrCDp4gKs0uClIlMg25S2j-UMHKwFYC" |
| |
| newPkg := makeTestInstance("test/package", []fs.File{ |
| fs.NewTestFile("some/file/path", "data a new", fs.TestFileOpts{}), |
| fs.NewTestFile("some/executable", "data b new", fs.TestFileOpts{Executable: true}), |
| fs.NewTestFile("mode change 1", "data d", fs.TestFileOpts{}), |
| fs.NewTestFile("mode change 2", "data d", fs.TestFileOpts{Executable: true}), |
| fs.NewTestSymlink("symlink unchanged", "target"), |
| fs.NewTestSymlink("symlink changed", "new target"), |
| }, pkg.InstallModeCopy) |
| newPkg.instanceID = "111111111_aOomrCDp4gKs0uClIlMg25S2j-UMHKwFYC" |
| |
| _, err := New(tempDir).DeployInstance(ctx, "", oldPkg) |
| So(err, ShouldBeNil) |
| _, err = New(tempDir).DeployInstance(ctx, "", newPkg) |
| So(err, ShouldBeNil) |
| |
| So(scanDir(tempDir), ShouldResemble, []string{ |
| ".cipd/pkgs/0/111111111_aOomrCDp4gKs0uClIlMg25S2j-UMHKwFYC/.cipdpkg/manifest.json", |
| ".cipd/pkgs/0/_current:111111111_aOomrCDp4gKs0uClIlMg25S2j-UMHKwFYC", |
| ".cipd/pkgs/0/description.json", |
| ".cipd/tmp!", |
| "mode change 1", |
| "mode change 2*", |
| "some/executable*", |
| "some/file/path", |
| "symlink changed:new target", |
| "symlink unchanged:target", |
| }) |
| |
| Convey("in subdir", func() { |
| _, err := New(tempDir).DeployInstance(ctx, "subdir", oldPkg) |
| So(err, ShouldBeNil) |
| _, err = New(tempDir).DeployInstance(ctx, "subdir", newPkg) |
| So(err, ShouldBeNil) |
| |
| So(scanDir(tempDir), ShouldResemble, []string{ |
| ".cipd/pkgs/0/111111111_aOomrCDp4gKs0uClIlMg25S2j-UMHKwFYC/.cipdpkg/manifest.json", |
| ".cipd/pkgs/0/_current:111111111_aOomrCDp4gKs0uClIlMg25S2j-UMHKwFYC", |
| ".cipd/pkgs/0/description.json", |
| ".cipd/pkgs/1/111111111_aOomrCDp4gKs0uClIlMg25S2j-UMHKwFYC/.cipdpkg/manifest.json", |
| ".cipd/pkgs/1/_current:111111111_aOomrCDp4gKs0uClIlMg25S2j-UMHKwFYC", |
| ".cipd/pkgs/1/description.json", |
| ".cipd/tmp!", |
| "mode change 1", |
| "mode change 2*", |
| "some/executable*", |
| "some/file/path", |
| "subdir/mode change 1", |
| "subdir/mode change 2*", |
| "subdir/some/executable*", |
| "subdir/some/file/path", |
| "subdir/symlink changed:new target", |
| "subdir/symlink unchanged:target", |
| "symlink changed:new target", |
| "symlink unchanged:target", |
| }) |
| }) |
| }) |
| |
| Convey("DeployInstance two different packages", func() { |
| pkg1 := makeTestInstance("test/package", []fs.File{ |
| fs.NewTestFile("some/file/path", "data a old", fs.TestFileOpts{}), |
| fs.NewTestFile("some/executable", "data b old", fs.TestFileOpts{Executable: true}), |
| fs.NewTestFile("pkg1 file", "data c", fs.TestFileOpts{}), |
| }, pkg.InstallModeCopy) |
| pkg1.instanceID = "000000000_aOomrCDp4gKs0uClIlMg25S2j-UMHKwFYC" |
| |
| // Nesting in package names is allowed. |
| pkg2 := makeTestInstance("test/package/another", []fs.File{ |
| fs.NewTestFile("some/file/path", "data a new", fs.TestFileOpts{}), |
| fs.NewTestFile("some/executable", "data b new", fs.TestFileOpts{Executable: true}), |
| fs.NewTestFile("pkg2 file", "data d", fs.TestFileOpts{}), |
| }, pkg.InstallModeCopy) |
| pkg2.instanceID = "111111111_aOomrCDp4gKs0uClIlMg25S2j-UMHKwFYC" |
| |
| _, err := New(tempDir).DeployInstance(ctx, "", pkg1) |
| So(err, ShouldBeNil) |
| _, err = New(tempDir).DeployInstance(ctx, "", pkg2) |
| So(err, ShouldBeNil) |
| |
| So(scanDir(tempDir), ShouldResemble, []string{ |
| ".cipd/pkgs/0/000000000_aOomrCDp4gKs0uClIlMg25S2j-UMHKwFYC/.cipdpkg/manifest.json", |
| ".cipd/pkgs/0/_current:000000000_aOomrCDp4gKs0uClIlMg25S2j-UMHKwFYC", |
| ".cipd/pkgs/0/description.json", |
| ".cipd/pkgs/1/111111111_aOomrCDp4gKs0uClIlMg25S2j-UMHKwFYC/.cipdpkg/manifest.json", |
| ".cipd/pkgs/1/_current:111111111_aOomrCDp4gKs0uClIlMg25S2j-UMHKwFYC", |
| ".cipd/pkgs/1/description.json", |
| ".cipd/tmp!", |
| "pkg1 file", |
| "pkg2 file", |
| "some/executable*", |
| "some/file/path", |
| }) |
| |
| Convey("in subdir", func() { |
| _, err := New(tempDir).DeployInstance(ctx, "somedir", pkg1) |
| So(err, ShouldBeNil) |
| _, err = New(tempDir).DeployInstance(ctx, "somedir", pkg2) |
| So(err, ShouldBeNil) |
| |
| So(scanDir(tempDir), ShouldResemble, []string{ |
| ".cipd/pkgs/0/000000000_aOomrCDp4gKs0uClIlMg25S2j-UMHKwFYC/.cipdpkg/manifest.json", |
| ".cipd/pkgs/0/_current:000000000_aOomrCDp4gKs0uClIlMg25S2j-UMHKwFYC", |
| ".cipd/pkgs/0/description.json", |
| ".cipd/pkgs/1/111111111_aOomrCDp4gKs0uClIlMg25S2j-UMHKwFYC/.cipdpkg/manifest.json", |
| ".cipd/pkgs/1/_current:111111111_aOomrCDp4gKs0uClIlMg25S2j-UMHKwFYC", |
| ".cipd/pkgs/1/description.json", |
| ".cipd/pkgs/2/000000000_aOomrCDp4gKs0uClIlMg25S2j-UMHKwFYC/.cipdpkg/manifest.json", |
| ".cipd/pkgs/2/_current:000000000_aOomrCDp4gKs0uClIlMg25S2j-UMHKwFYC", |
| ".cipd/pkgs/2/description.json", |
| ".cipd/pkgs/3/111111111_aOomrCDp4gKs0uClIlMg25S2j-UMHKwFYC/.cipdpkg/manifest.json", |
| ".cipd/pkgs/3/_current:111111111_aOomrCDp4gKs0uClIlMg25S2j-UMHKwFYC", |
| ".cipd/pkgs/3/description.json", |
| ".cipd/tmp!", |
| "pkg1 file", |
| "pkg2 file", |
| "some/executable*", |
| "some/file/path", |
| "somedir/pkg1 file", |
| "somedir/pkg2 file", |
| "somedir/some/executable*", |
| "somedir/some/file/path", |
| }) |
| }) |
| }) |
| }) |
| } |
| |
| func TestDeployInstanceCopyModeWindows(t *testing.T) { |
| t.Parallel() |
| |
| if runtime.GOOS != "windows" { |
| t.Skip("Skipping on posix") |
| } |
| |
| ctx := context.Background() |
| |
| Convey("Given a temp directory", t, func() { |
| tempDir := mkTempDir() |
| |
| Convey("DeployInstance new empty package instance", func() { |
| inst := makeTestInstance("test/package", nil, pkg.InstallModeCopy) |
| info, err := New(tempDir).DeployInstance(ctx, "", inst) |
| So(err, ShouldBeNil) |
| So(info, ShouldResemble, inst.Pin()) |
| So(scanDir(tempDir), ShouldResemble, []string{ |
| ".cipd/pkgs/0/-wEu41lw0_aOomrCDp4gKs0uClIlMg25S2j-UMHKwFYC/.cipdpkg/manifest.json", |
| ".cipd/pkgs/0/_current.txt", |
| ".cipd/pkgs/0/description.json", |
| ".cipd/tmp!", |
| }) |
| cur := readFile(tempDir, ".cipd/pkgs/0/_current.txt") |
| So(cur, ShouldEqual, "-wEu41lw0_aOomrCDp4gKs0uClIlMg25S2j-UMHKwFYC") |
| |
| Convey("in subdir", func() { |
| info, err := New(tempDir).DeployInstance(ctx, "subdir", inst) |
| So(err, ShouldBeNil) |
| So(info, ShouldResemble, inst.Pin()) |
| So(scanDir(tempDir), ShouldResemble, []string{ |
| ".cipd/pkgs/0/-wEu41lw0_aOomrCDp4gKs0uClIlMg25S2j-UMHKwFYC/.cipdpkg/manifest.json", |
| ".cipd/pkgs/0/_current.txt", |
| ".cipd/pkgs/0/description.json", |
| ".cipd/pkgs/1/-wEu41lw0_aOomrCDp4gKs0uClIlMg25S2j-UMHKwFYC/.cipdpkg/manifest.json", |
| ".cipd/pkgs/1/_current.txt", |
| ".cipd/pkgs/1/description.json", |
| ".cipd/tmp!", |
| "subdir!", |
| }) |
| cur := readFile(tempDir, ".cipd/pkgs/0/_current.txt") |
| So(cur, ShouldEqual, "-wEu41lw0_aOomrCDp4gKs0uClIlMg25S2j-UMHKwFYC") |
| cur = readFile(tempDir, ".cipd/pkgs/1/_current.txt") |
| So(cur, ShouldEqual, "-wEu41lw0_aOomrCDp4gKs0uClIlMg25S2j-UMHKwFYC") |
| }) |
| }) |
| |
| Convey("DeployInstance new non-empty package instance", func() { |
| inst := makeTestInstance("test/package", []fs.File{ |
| fs.NewTestFile("some/file/path", "data a", fs.TestFileOpts{}), |
| fs.NewTestFile("some/executable", "data b", fs.TestFileOpts{Executable: true}), |
| fs.NewTestFile(".cipd/pkg/0/description.json", "{}", fs.TestFileOpts{}), // should be ignored |
| }, pkg.InstallModeCopy) |
| _, err := New(tempDir).DeployInstance(ctx, "", inst) |
| So(err, ShouldBeNil) |
| So(scanDir(tempDir), ShouldResemble, []string{ |
| ".cipd/pkgs/0/-wEu41lw0_aOomrCDp4gKs0uClIlMg25S2j-UMHKwFYC/.cipdpkg/manifest.json", |
| ".cipd/pkgs/0/_current.txt", |
| ".cipd/pkgs/0/description.json", |
| ".cipd/tmp!", |
| "some/executable", |
| "some/file/path", |
| }) |
| cur := readFile(tempDir, ".cipd/pkgs/0/_current.txt") |
| So(cur, ShouldEqual, "-wEu41lw0_aOomrCDp4gKs0uClIlMg25S2j-UMHKwFYC") |
| |
| Convey("in subdir", func() { |
| _, err := New(tempDir).DeployInstance(ctx, "subdir", inst) |
| So(err, ShouldBeNil) |
| So(scanDir(tempDir), ShouldResemble, []string{ |
| ".cipd/pkgs/0/-wEu41lw0_aOomrCDp4gKs0uClIlMg25S2j-UMHKwFYC/.cipdpkg/manifest.json", |
| ".cipd/pkgs/0/_current.txt", |
| ".cipd/pkgs/0/description.json", |
| ".cipd/pkgs/1/-wEu41lw0_aOomrCDp4gKs0uClIlMg25S2j-UMHKwFYC/.cipdpkg/manifest.json", |
| ".cipd/pkgs/1/_current.txt", |
| ".cipd/pkgs/1/description.json", |
| ".cipd/tmp!", |
| "some/executable", |
| "some/file/path", |
| "subdir/some/executable", |
| "subdir/some/file/path", |
| }) |
| cur := readFile(tempDir, ".cipd/pkgs/0/_current.txt") |
| So(cur, ShouldEqual, "-wEu41lw0_aOomrCDp4gKs0uClIlMg25S2j-UMHKwFYC") |
| cur = readFile(tempDir, ".cipd/pkgs/1/_current.txt") |
| So(cur, ShouldEqual, "-wEu41lw0_aOomrCDp4gKs0uClIlMg25S2j-UMHKwFYC") |
| }) |
| }) |
| |
| Convey("Redeploy same package instance", func() { |
| inst := makeTestInstance("test/package", []fs.File{ |
| fs.NewTestFile("some/file/path", "data a", fs.TestFileOpts{}), |
| fs.NewTestFile("some/executable", "data b", fs.TestFileOpts{Executable: true}), |
| }, pkg.InstallModeCopy) |
| _, err := New(tempDir).DeployInstance(ctx, "", inst) |
| So(err, ShouldBeNil) |
| _, err = New(tempDir).DeployInstance(ctx, "", inst) |
| So(err, ShouldBeNil) |
| So(scanDir(tempDir), ShouldResemble, []string{ |
| ".cipd/pkgs/0/-wEu41lw0_aOomrCDp4gKs0uClIlMg25S2j-UMHKwFYC/.cipdpkg/manifest.json", |
| ".cipd/pkgs/0/_current.txt", |
| ".cipd/pkgs/0/description.json", |
| ".cipd/tmp!", |
| ".cipd/trash!", |
| "some/executable", |
| "some/file/path", |
| }) |
| cur := readFile(tempDir, ".cipd/pkgs/0/_current.txt") |
| So(cur, ShouldEqual, "-wEu41lw0_aOomrCDp4gKs0uClIlMg25S2j-UMHKwFYC") |
| |
| Convey("in subdir", func() { |
| _, err := New(tempDir).DeployInstance(ctx, "subdir", inst) |
| So(err, ShouldBeNil) |
| _, err = New(tempDir).DeployInstance(ctx, "subdir", inst) |
| So(err, ShouldBeNil) |
| So(scanDir(tempDir), ShouldResemble, []string{ |
| ".cipd/pkgs/0/-wEu41lw0_aOomrCDp4gKs0uClIlMg25S2j-UMHKwFYC/.cipdpkg/manifest.json", |
| ".cipd/pkgs/0/_current.txt", |
| ".cipd/pkgs/0/description.json", |
| ".cipd/pkgs/1/-wEu41lw0_aOomrCDp4gKs0uClIlMg25S2j-UMHKwFYC/.cipdpkg/manifest.json", |
| ".cipd/pkgs/1/_current.txt", |
| ".cipd/pkgs/1/description.json", |
| ".cipd/tmp!", |
| ".cipd/trash!", |
| "some/executable", |
| "some/file/path", |
| "subdir/some/executable", |
| "subdir/some/file/path", |
| }) |
| cur := readFile(tempDir, ".cipd/pkgs/0/_current.txt") |
| So(cur, ShouldEqual, "-wEu41lw0_aOomrCDp4gKs0uClIlMg25S2j-UMHKwFYC") |
| cur = readFile(tempDir, ".cipd/pkgs/1/_current.txt") |
| So(cur, ShouldEqual, "-wEu41lw0_aOomrCDp4gKs0uClIlMg25S2j-UMHKwFYC") |
| }) |
| }) |
| |
| Convey("DeployInstance package update", func() { |
| oldPkg := makeTestInstance("test/package", []fs.File{ |
| fs.NewTestFile("some/file/path", "data a old", fs.TestFileOpts{}), |
| fs.NewTestFile("some/executable", "data b old", fs.TestFileOpts{Executable: true}), |
| fs.NewTestFile("some/to-be-empty-dir/file", "data", fs.TestFileOpts{}), |
| fs.NewTestFile("old only", "data c old", fs.TestFileOpts{Executable: true}), |
| fs.NewTestFile("mode change 1", "data d", fs.TestFileOpts{Executable: true}), |
| fs.NewTestFile("mode change 2", "data e", fs.TestFileOpts{}), |
| }, pkg.InstallModeCopy) |
| oldPkg.instanceID = "000000000_aOomrCDp4gKs0uClIlMg25S2j-UMHKwFYC" |
| |
| newPkg := makeTestInstance("test/package", []fs.File{ |
| fs.NewTestFile("some/file/path", "data a new", fs.TestFileOpts{}), |
| fs.NewTestFile("some/executable", "data b new", fs.TestFileOpts{Executable: true}), |
| fs.NewTestFile("mode change 1", "data d", fs.TestFileOpts{}), |
| fs.NewTestFile("mode change 2", "data d", fs.TestFileOpts{Executable: true}), |
| }, pkg.InstallModeCopy) |
| newPkg.instanceID = "111111111_aOomrCDp4gKs0uClIlMg25S2j-UMHKwFYC" |
| |
| _, err := New(tempDir).DeployInstance(ctx, "", oldPkg) |
| So(err, ShouldBeNil) |
| _, err = New(tempDir).DeployInstance(ctx, "", newPkg) |
| So(err, ShouldBeNil) |
| |
| So(scanDir(tempDir), ShouldResemble, []string{ |
| ".cipd/pkgs/0/111111111_aOomrCDp4gKs0uClIlMg25S2j-UMHKwFYC/.cipdpkg/manifest.json", |
| ".cipd/pkgs/0/_current.txt", |
| ".cipd/pkgs/0/description.json", |
| ".cipd/tmp!", |
| "mode change 1", |
| "mode change 2", |
| "some/executable", |
| "some/file/path", |
| }) |
| cur := readFile(tempDir, ".cipd/pkgs/0/_current.txt") |
| So(cur, ShouldEqual, "111111111_aOomrCDp4gKs0uClIlMg25S2j-UMHKwFYC") |
| |
| Convey("in subdir", func() { |
| _, err := New(tempDir).DeployInstance(ctx, "subdir", oldPkg) |
| So(err, ShouldBeNil) |
| _, err = New(tempDir).DeployInstance(ctx, "subdir", newPkg) |
| So(err, ShouldBeNil) |
| |
| So(scanDir(tempDir), ShouldResemble, []string{ |
| ".cipd/pkgs/0/111111111_aOomrCDp4gKs0uClIlMg25S2j-UMHKwFYC/.cipdpkg/manifest.json", |
| ".cipd/pkgs/0/_current.txt", |
| ".cipd/pkgs/0/description.json", |
| ".cipd/pkgs/1/111111111_aOomrCDp4gKs0uClIlMg25S2j-UMHKwFYC/.cipdpkg/manifest.json", |
| ".cipd/pkgs/1/_current.txt", |
| ".cipd/pkgs/1/description.json", |
| ".cipd/tmp!", |
| "mode change 1", |
| "mode change 2", |
| "some/executable", |
| "some/file/path", |
| "subdir/mode change 1", |
| "subdir/mode change 2", |
| "subdir/some/executable", |
| "subdir/some/file/path", |
| }) |
| cur := readFile(tempDir, ".cipd/pkgs/0/_current.txt") |
| So(cur, ShouldEqual, "111111111_aOomrCDp4gKs0uClIlMg25S2j-UMHKwFYC") |
| cur = readFile(tempDir, ".cipd/pkgs/1/_current.txt") |
| So(cur, ShouldEqual, "111111111_aOomrCDp4gKs0uClIlMg25S2j-UMHKwFYC") |
| }) |
| }) |
| |
| Convey("DeployInstance two different packages", func() { |
| pkg1 := makeTestInstance("test/package", []fs.File{ |
| fs.NewTestFile("some/file/path", "data a old", fs.TestFileOpts{}), |
| fs.NewTestFile("some/executable", "data b old", fs.TestFileOpts{Executable: true}), |
| fs.NewTestFile("pkg1 file", "data c", fs.TestFileOpts{}), |
| }, pkg.InstallModeCopy) |
| pkg1.instanceID = "000000000_aOomrCDp4gKs0uClIlMg25S2j-UMHKwFYC" |
| |
| // Nesting in package names is allowed. |
| pkg2 := makeTestInstance("test/package/another", []fs.File{ |
| fs.NewTestFile("some/file/path", "data a new", fs.TestFileOpts{}), |
| fs.NewTestFile("some/executable", "data b new", fs.TestFileOpts{Executable: true}), |
| fs.NewTestFile("pkg2 file", "data d", fs.TestFileOpts{}), |
| }, pkg.InstallModeCopy) |
| pkg2.instanceID = "111111111_aOomrCDp4gKs0uClIlMg25S2j-UMHKwFYC" |
| |
| _, err := New(tempDir).DeployInstance(ctx, "", pkg1) |
| So(err, ShouldBeNil) |
| _, err = New(tempDir).DeployInstance(ctx, "", pkg2) |
| So(err, ShouldBeNil) |
| |
| So(scanDir(tempDir), ShouldResemble, []string{ |
| ".cipd/pkgs/0/000000000_aOomrCDp4gKs0uClIlMg25S2j-UMHKwFYC/.cipdpkg/manifest.json", |
| ".cipd/pkgs/0/_current.txt", |
| ".cipd/pkgs/0/description.json", |
| ".cipd/pkgs/1/111111111_aOomrCDp4gKs0uClIlMg25S2j-UMHKwFYC/.cipdpkg/manifest.json", |
| ".cipd/pkgs/1/_current.txt", |
| ".cipd/pkgs/1/description.json", |
| ".cipd/tmp!", |
| "pkg1 file", |
| "pkg2 file", |
| "some/executable", |
| "some/file/path", |
| }) |
| cur1 := readFile(tempDir, ".cipd/pkgs/1/_current.txt") |
| So(cur1, ShouldEqual, "111111111_aOomrCDp4gKs0uClIlMg25S2j-UMHKwFYC") |
| cur2 := readFile(tempDir, ".cipd/pkgs/0/_current.txt") |
| So(cur2, ShouldEqual, "000000000_aOomrCDp4gKs0uClIlMg25S2j-UMHKwFYC") |
| |
| Convey("in subdir", func() { |
| _, err := New(tempDir).DeployInstance(ctx, "subdir", pkg1) |
| So(err, ShouldBeNil) |
| _, err = New(tempDir).DeployInstance(ctx, "subdir", pkg2) |
| So(err, ShouldBeNil) |
| |
| So(scanDir(tempDir), ShouldResemble, []string{ |
| ".cipd/pkgs/0/000000000_aOomrCDp4gKs0uClIlMg25S2j-UMHKwFYC/.cipdpkg/manifest.json", |
| ".cipd/pkgs/0/_current.txt", |
| ".cipd/pkgs/0/description.json", |
| ".cipd/pkgs/1/111111111_aOomrCDp4gKs0uClIlMg25S2j-UMHKwFYC/.cipdpkg/manifest.json", |
| ".cipd/pkgs/1/_current.txt", |
| ".cipd/pkgs/1/description.json", |
| ".cipd/pkgs/2/000000000_aOomrCDp4gKs0uClIlMg25S2j-UMHKwFYC/.cipdpkg/manifest.json", |
| ".cipd/pkgs/2/_current.txt", |
| ".cipd/pkgs/2/description.json", |
| ".cipd/pkgs/3/111111111_aOomrCDp4gKs0uClIlMg25S2j-UMHKwFYC/.cipdpkg/manifest.json", |
| ".cipd/pkgs/3/_current.txt", |
| ".cipd/pkgs/3/description.json", |
| ".cipd/tmp!", |
| "pkg1 file", |
| "pkg2 file", |
| "some/executable", |
| "some/file/path", |
| "subdir/pkg1 file", |
| "subdir/pkg2 file", |
| "subdir/some/executable", |
| "subdir/some/file/path", |
| }) |
| cur1 := readFile(tempDir, ".cipd/pkgs/1/_current.txt") |
| So(cur1, ShouldEqual, "111111111_aOomrCDp4gKs0uClIlMg25S2j-UMHKwFYC") |
| cur2 := readFile(tempDir, ".cipd/pkgs/0/_current.txt") |
| So(cur2, ShouldEqual, "000000000_aOomrCDp4gKs0uClIlMg25S2j-UMHKwFYC") |
| }) |
| }) |
| }) |
| } |
| |
| func TestDeployInstanceSwitchingModes(t *testing.T) { |
| t.Parallel() |
| |
| if runtime.GOOS == "windows" { |
| t.Skip("Skipping on Windows: no symlinks") |
| } |
| |
| ctx := context.Background() |
| |
| Convey("Given a temp directory", t, func() { |
| tempDir := mkTempDir() |
| |
| files := []fs.File{ |
| fs.NewTestFile("some/file/path", "data a", fs.TestFileOpts{}), |
| fs.NewTestFile("some/executable", "data b", fs.TestFileOpts{Executable: true}), |
| fs.NewTestSymlink("some/symlink", "executable"), |
| } |
| |
| Convey("InstallModeCopy => InstallModeSymlink", func() { |
| inst := makeTestInstance("test/package", files, pkg.InstallModeCopy) |
| inst.instanceID = "000000000_aOomrCDp4gKs0uClIlMg25S2j-UMHKwFYC" |
| _, err := New(tempDir).DeployInstance(ctx, "", inst) |
| So(err, ShouldBeNil) |
| |
| inst = makeTestInstance("test/package", files, pkg.InstallModeSymlink) |
| inst.instanceID = "111111111_aOomrCDp4gKs0uClIlMg25S2j-UMHKwFYC" |
| _, err = New(tempDir).DeployInstance(ctx, "", inst) |
| |
| So(err, ShouldBeNil) |
| So(scanDir(tempDir), ShouldResemble, []string{ |
| ".cipd/pkgs/0/111111111_aOomrCDp4gKs0uClIlMg25S2j-UMHKwFYC/.cipdpkg/manifest.json", |
| ".cipd/pkgs/0/111111111_aOomrCDp4gKs0uClIlMg25S2j-UMHKwFYC/some/executable*", |
| ".cipd/pkgs/0/111111111_aOomrCDp4gKs0uClIlMg25S2j-UMHKwFYC/some/file/path", |
| ".cipd/pkgs/0/111111111_aOomrCDp4gKs0uClIlMg25S2j-UMHKwFYC/some/symlink:executable", |
| ".cipd/pkgs/0/_current:111111111_aOomrCDp4gKs0uClIlMg25S2j-UMHKwFYC", |
| ".cipd/pkgs/0/description.json", |
| ".cipd/tmp!", |
| "some/executable:../.cipd/pkgs/0/_current/some/executable", |
| "some/file/path:../../.cipd/pkgs/0/_current/some/file/path", |
| "some/symlink:../.cipd/pkgs/0/_current/some/symlink", |
| }) |
| |
| Convey("in subidr", func() { |
| inst := makeTestInstance("test/package", files, pkg.InstallModeCopy) |
| inst.instanceID = "000000000_aOomrCDp4gKs0uClIlMg25S2j-UMHKwFYC" |
| _, err := New(tempDir).DeployInstance(ctx, "subdir", inst) |
| So(err, ShouldBeNil) |
| |
| inst = makeTestInstance("test/package", files, pkg.InstallModeSymlink) |
| inst.instanceID = "111111111_aOomrCDp4gKs0uClIlMg25S2j-UMHKwFYC" |
| _, err = New(tempDir).DeployInstance(ctx, "subdir", inst) |
| |
| So(err, ShouldBeNil) |
| So(scanDir(tempDir), ShouldResemble, []string{ |
| ".cipd/pkgs/0/111111111_aOomrCDp4gKs0uClIlMg25S2j-UMHKwFYC/.cipdpkg/manifest.json", |
| ".cipd/pkgs/0/111111111_aOomrCDp4gKs0uClIlMg25S2j-UMHKwFYC/some/executable*", |
| ".cipd/pkgs/0/111111111_aOomrCDp4gKs0uClIlMg25S2j-UMHKwFYC/some/file/path", |
| ".cipd/pkgs/0/111111111_aOomrCDp4gKs0uClIlMg25S2j-UMHKwFYC/some/symlink:executable", |
| ".cipd/pkgs/0/_current:111111111_aOomrCDp4gKs0uClIlMg25S2j-UMHKwFYC", |
| ".cipd/pkgs/0/description.json", |
| ".cipd/pkgs/1/111111111_aOomrCDp4gKs0uClIlMg25S2j-UMHKwFYC/.cipdpkg/manifest.json", |
| ".cipd/pkgs/1/111111111_aOomrCDp4gKs0uClIlMg25S2j-UMHKwFYC/some/executable*", |
| ".cipd/pkgs/1/111111111_aOomrCDp4gKs0uClIlMg25S2j-UMHKwFYC/some/file/path", |
| ".cipd/pkgs/1/111111111_aOomrCDp4gKs0uClIlMg25S2j-UMHKwFYC/some/symlink:executable", |
| ".cipd/pkgs/1/_current:111111111_aOomrCDp4gKs0uClIlMg25S2j-UMHKwFYC", |
| ".cipd/pkgs/1/description.json", |
| ".cipd/tmp!", |
| "some/executable:../.cipd/pkgs/0/_current/some/executable", |
| "some/file/path:../../.cipd/pkgs/0/_current/some/file/path", |
| "some/symlink:../.cipd/pkgs/0/_current/some/symlink", |
| "subdir/some/executable:../../.cipd/pkgs/1/_current/some/executable", |
| "subdir/some/file/path:../../../.cipd/pkgs/1/_current/some/file/path", |
| "subdir/some/symlink:../../.cipd/pkgs/1/_current/some/symlink", |
| }) |
| }) |
| }) |
| |
| Convey("InstallModeSymlink => InstallModeCopy", func() { |
| inst := makeTestInstance("test/package", files, pkg.InstallModeSymlink) |
| inst.instanceID = "000000000_aOomrCDp4gKs0uClIlMg25S2j-UMHKwFYC" |
| _, err := New(tempDir).DeployInstance(ctx, "", inst) |
| So(err, ShouldBeNil) |
| |
| inst = makeTestInstance("test/package", files, pkg.InstallModeCopy) |
| inst.instanceID = "111111111_aOomrCDp4gKs0uClIlMg25S2j-UMHKwFYC" |
| _, err = New(tempDir).DeployInstance(ctx, "", inst) |
| |
| So(err, ShouldBeNil) |
| So(scanDir(tempDir), ShouldResemble, []string{ |
| ".cipd/pkgs/0/111111111_aOomrCDp4gKs0uClIlMg25S2j-UMHKwFYC/.cipdpkg/manifest.json", |
| ".cipd/pkgs/0/_current:111111111_aOomrCDp4gKs0uClIlMg25S2j-UMHKwFYC", |
| ".cipd/pkgs/0/description.json", |
| ".cipd/tmp!", |
| "some/executable*", |
| "some/file/path", |
| "some/symlink:executable", |
| }) |
| |
| Convey("in subdir", func() { |
| inst := makeTestInstance("test/package", files, pkg.InstallModeSymlink) |
| inst.instanceID = "000000000_aOomrCDp4gKs0uClIlMg25S2j-UMHKwFYC" |
| _, err := New(tempDir).DeployInstance(ctx, "subdir", inst) |
| So(err, ShouldBeNil) |
| |
| inst = makeTestInstance("test/package", files, pkg.InstallModeCopy) |
| inst.instanceID = "111111111_aOomrCDp4gKs0uClIlMg25S2j-UMHKwFYC" |
| _, err = New(tempDir).DeployInstance(ctx, "subdir", inst) |
| |
| So(err, ShouldBeNil) |
| So(scanDir(tempDir), ShouldResemble, []string{ |
| ".cipd/pkgs/0/111111111_aOomrCDp4gKs0uClIlMg25S2j-UMHKwFYC/.cipdpkg/manifest.json", |
| ".cipd/pkgs/0/_current:111111111_aOomrCDp4gKs0uClIlMg25S2j-UMHKwFYC", |
| ".cipd/pkgs/0/description.json", |
| ".cipd/pkgs/1/111111111_aOomrCDp4gKs0uClIlMg25S2j-UMHKwFYC/.cipdpkg/manifest.json", |
| ".cipd/pkgs/1/_current:111111111_aOomrCDp4gKs0uClIlMg25S2j-UMHKwFYC", |
| ".cipd/pkgs/1/description.json", |
| ".cipd/tmp!", |
| "some/executable*", |
| "some/file/path", |
| "some/symlink:executable", |
| "subdir/some/executable*", |
| "subdir/some/file/path", |
| "subdir/some/symlink:executable", |
| }) |
| }) |
| }) |
| }) |
| } |
| |
| func TestDeployInstanceUpgradeFileToDir(t *testing.T) { |
| t.Parallel() |
| |
| Convey("DeployInstance can replace files with directories", t, func() { |
| ctx := withMemLogger() |
| tempDir := mkTempDir() |
| |
| // Here "some/path" is a file. |
| oldPkg := makeTestInstance("test/package", []fs.File{ |
| fs.NewTestFile("some/path", "data old", fs.TestFileOpts{}), |
| }, pkg.InstallModeCopy) |
| oldPkg.instanceID = "000000000_aOomrCDp4gKs0uClIlMg25S2j-UMHKwFYC" |
| |
| // And here "some/path" is a directory. |
| newPkg := makeTestInstance("test/package", []fs.File{ |
| fs.NewTestFile("some/path/file", "data new", fs.TestFileOpts{}), |
| }, pkg.InstallModeCopy) |
| newPkg.instanceID = "111111111_aOomrCDp4gKs0uClIlMg25S2j-UMHKwFYC" |
| |
| // Note: specifically use '/' even on Windows here, to make sure deployer |
| // guts do slash conversion correctly inside. |
| _, err := New(tempDir).DeployInstance(ctx, "sub/dir", oldPkg) |
| So(err, ShouldBeNil) |
| _, err = New(tempDir).DeployInstance(ctx, "sub/dir", newPkg) |
| So(err, ShouldBeNil) |
| |
| // The new file is deployed successfully. |
| So(readFile(tempDir, "sub/dir/some/path/file"), ShouldEqual, "data new") |
| |
| // No complaints during the upgrade. |
| So(loggerWarnings(ctx), ShouldResemble, []string(nil)) |
| }) |
| } |
| |
| func TestDeployInstanceDirAndSymlinkSwaps(t *testing.T) { |
| if runtime.GOOS == "windows" { |
| t.Skip("Skipping on windows") |
| } |
| |
| t.Parallel() |
| |
| Convey("With packages", t, func() { |
| ctx := withMemLogger() |
| tempDir := mkTempDir() |
| |
| // Here "some/path" is a directory. |
| pkgWithDir := makeTestInstance("test/package", []fs.File{ |
| fs.NewTestFile("some/path/file1", "old data 1", fs.TestFileOpts{}), |
| fs.NewTestFile("some/path/a/file2", "old data 2", fs.TestFileOpts{}), |
| fs.NewTestFile("some/path/a/b/file3", "old data 3", fs.TestFileOpts{}), |
| }, pkg.InstallModeCopy) |
| pkgWithDir.instanceID = "000000000_aOomrCDp4gKs0uClIlMg25S2j-UMHKwFYC" |
| |
| // And here "some/path" is a symlink to some new directory that also has |
| // the same files (and a bunch more). |
| pkgWithSym := makeTestInstance("test/package", []fs.File{ |
| fs.NewTestSymlink("some/path", "another"), |
| fs.NewTestFile("some/another/file1", "new data 1", fs.TestFileOpts{}), |
| fs.NewTestFile("some/another/a/file2", "new data 2", fs.TestFileOpts{}), |
| fs.NewTestFile("some/another/a/b/file3", "new data 3", fs.TestFileOpts{}), |
| fs.NewTestFile("some/another/a/file4", "new data 4", fs.TestFileOpts{}), |
| }, pkg.InstallModeCopy) |
| pkgWithSym.instanceID = "111111111_aOomrCDp4gKs0uClIlMg25S2j-UMHKwFYC" |
| |
| Convey("Replace directory with symlink", func() { |
| _, err := New(tempDir).DeployInstance(ctx, "sub/dir", pkgWithDir) |
| So(err, ShouldBeNil) |
| _, err = New(tempDir).DeployInstance(ctx, "sub/dir", pkgWithSym) |
| So(err, ShouldBeNil) |
| |
| So(scanDir(filepath.Join(tempDir, "sub", "dir")), ShouldResemble, []string{ |
| "some/another/a/b/file3", |
| "some/another/a/file2", |
| "some/another/a/file4", |
| "some/another/file1", |
| "some/path:another", |
| }) |
| |
| So(readFile(tempDir, "sub/dir/some/path/file1"), ShouldEqual, "new data 1") |
| So(readFile(tempDir, "sub/dir/some/path/a/file2"), ShouldEqual, "new data 2") |
| |
| So(loggerWarnings(ctx), ShouldResemble, []string(nil)) |
| }) |
| |
| Convey("Replace symlink with directory", func() { |
| _, err := New(tempDir).DeployInstance(ctx, "sub/dir", pkgWithSym) |
| So(err, ShouldBeNil) |
| _, err = New(tempDir).DeployInstance(ctx, "sub/dir", pkgWithDir) |
| So(err, ShouldBeNil) |
| |
| So(scanDir(filepath.Join(tempDir, "sub", "dir")), ShouldResemble, []string{ |
| "some/path/a/b/file3", |
| "some/path/a/file2", |
| "some/path/file1", |
| }) |
| |
| So(readFile(tempDir, "sub/dir/some/path/file1"), ShouldEqual, "old data 1") |
| So(readFile(tempDir, "sub/dir/some/path/a/file2"), ShouldEqual, "old data 2") |
| |
| So(loggerWarnings(ctx), ShouldResemble, []string(nil)) |
| }) |
| }) |
| } |
| |
| func TestFindDeployed(t *testing.T) { |
| t.Parallel() |
| |
| ctx := context.Background() |
| |
| Convey("Given a temp directory", t, func() { |
| tempDir := mkTempDir() |
| |
| Convey("FindDeployed works with empty dir", func() { |
| out, err := New(tempDir).FindDeployed(ctx) |
| So(err, ShouldBeNil) |
| So(out, ShouldBeNil) |
| }) |
| |
| Convey("FindDeployed works", func() { |
| d := New(tempDir) |
| |
| // Deploy a bunch of stuff. |
| _, err := d.DeployInstance(ctx, "", makeTestInstance("test/pkg/123", nil, pkg.InstallModeCopy)) |
| So(err, ShouldBeNil) |
| _, err = d.DeployInstance(ctx, "", makeTestInstance("test/pkg/456", nil, pkg.InstallModeCopy)) |
| So(err, ShouldBeNil) |
| _, err = d.DeployInstance(ctx, "", makeTestInstance("test/pkg", nil, pkg.InstallModeCopy)) |
| So(err, ShouldBeNil) |
| _, err = d.DeployInstance(ctx, "", makeTestInstance("test", nil, pkg.InstallModeCopy)) |
| So(err, ShouldBeNil) |
| _, err = d.DeployInstance(ctx, "subdir", makeTestInstance("test/pkg/123", nil, pkg.InstallModeCopy)) |
| So(err, ShouldBeNil) |
| _, err = d.DeployInstance(ctx, "subdir", makeTestInstance("test/pkg/456", nil, pkg.InstallModeCopy)) |
| So(err, ShouldBeNil) |
| _, err = d.DeployInstance(ctx, "subdir", makeTestInstance("test/pkg", nil, pkg.InstallModeCopy)) |
| So(err, ShouldBeNil) |
| _, err = d.DeployInstance(ctx, "subdir", makeTestInstance("test", nil, pkg.InstallModeCopy)) |
| So(err, ShouldBeNil) |
| |
| // including some broken packages |
| _, err = d.DeployInstance(ctx, "", makeTestInstance("broken", nil, pkg.InstallModeCopy)) |
| So(err, ShouldBeNil) |
| if runtime.GOOS == "windows" { |
| err = os.Remove(filepath.Join(tempDir, fs.SiteServiceDir, "pkgs", "8", "_current.txt")) |
| } else { |
| err = os.Remove(filepath.Join(tempDir, fs.SiteServiceDir, "pkgs", "8", "_current")) |
| } |
| So(err, ShouldBeNil) |
| |
| // Verify it is discoverable. |
| out, err := d.FindDeployed(ctx) |
| So(err, ShouldBeNil) |
| So(out, ShouldResemble, PinSliceBySubdir{ |
| "": PinSlice{ |
| {"test", "-wEu41lw0_aOomrCDp4gKs0uClIlMg25S2j-UMHKwFYC"}, |
| {"test/pkg", "-wEu41lw0_aOomrCDp4gKs0uClIlMg25S2j-UMHKwFYC"}, |
| {"test/pkg/123", "-wEu41lw0_aOomrCDp4gKs0uClIlMg25S2j-UMHKwFYC"}, |
| {"test/pkg/456", "-wEu41lw0_aOomrCDp4gKs0uClIlMg25S2j-UMHKwFYC"}, |
| }, |
| "subdir": PinSlice{ |
| {"test", "-wEu41lw0_aOomrCDp4gKs0uClIlMg25S2j-UMHKwFYC"}, |
| {"test/pkg", "-wEu41lw0_aOomrCDp4gKs0uClIlMg25S2j-UMHKwFYC"}, |
| {"test/pkg/123", "-wEu41lw0_aOomrCDp4gKs0uClIlMg25S2j-UMHKwFYC"}, |
| {"test/pkg/456", "-wEu41lw0_aOomrCDp4gKs0uClIlMg25S2j-UMHKwFYC"}, |
| }, |
| }) |
| }) |
| }) |
| } |
| |
| func TestRemoveDeployedCommon(t *testing.T) { |
| ctx := context.Background() |
| |
| Convey("Given a temp directory", t, func() { |
| tempDir := mkTempDir() |
| |
| Convey("RemoveDeployed works with missing package", func() { |
| err := New(tempDir).RemoveDeployed(ctx, "", "package/path") |
| So(err, ShouldBeNil) |
| err = New(tempDir).RemoveDeployed(ctx, "subdir", "package/path") |
| So(err, ShouldBeNil) |
| }) |
| }) |
| } |
| |
| func TestRemoveDeployedPosix(t *testing.T) { |
| t.Parallel() |
| |
| if runtime.GOOS == "windows" { |
| t.Skip("Skipping on windows") |
| } |
| |
| ctx := context.Background() |
| |
| Convey("Given a temp directory", t, func() { |
| tempDir := mkTempDir() |
| |
| Convey("RemoveDeployed works", func() { |
| d := New(tempDir) |
| |
| // Deploy some instance (to keep it). |
| inst := makeTestInstance("test/package/123", []fs.File{ |
| fs.NewTestFile("some/file/path1", "data a", fs.TestFileOpts{}), |
| fs.NewTestFile("some/executable1", "data b", fs.TestFileOpts{Executable: true}), |
| }, pkg.InstallModeCopy) |
| _, err := d.DeployInstance(ctx, "", inst) |
| So(err, ShouldBeNil) |
| |
| // Deploy another instance (to remove it). |
| inst2 := makeTestInstance("test/package", []fs.File{ |
| fs.NewTestFile("some/file/path2", "data a", fs.TestFileOpts{}), |
| fs.NewTestFile("some/to-be-empty-dir/file", "data", fs.TestFileOpts{}), |
| fs.NewTestFile("some/executable2", "data b", fs.TestFileOpts{Executable: true}), |
| fs.NewTestSymlink("some/symlink", "executable"), |
| }, pkg.InstallModeCopy) |
| _, err = d.DeployInstance(ctx, "", inst2) |
| So(err, ShouldBeNil) |
| |
| // Now remove the second package. |
| err = d.RemoveDeployed(ctx, "", "test/package") |
| So(err, ShouldBeNil) |
| |
| // Verify the final state (only first package should survive). |
| So(scanDir(tempDir), ShouldResemble, []string{ |
| ".cipd/pkgs/0/-wEu41lw0_aOomrCDp4gKs0uClIlMg25S2j-UMHKwFYC/.cipdpkg/manifest.json", |
| ".cipd/pkgs/0/_current:-wEu41lw0_aOomrCDp4gKs0uClIlMg25S2j-UMHKwFYC", |
| ".cipd/pkgs/0/description.json", |
| ".cipd/tmp!", |
| "some/executable1*", |
| "some/file/path1", |
| }) |
| |
| Convey("in subdir", func() { |
| // Deploy some instance (to keep it). |
| _, err := d.DeployInstance(ctx, "subdir", inst2) |
| So(err, ShouldBeNil) |
| |
| // Deploy another instance (to remove it). |
| _, err = d.DeployInstance(ctx, "subdir", inst) |
| So(err, ShouldBeNil) |
| |
| // Now remove the second package. |
| err = d.RemoveDeployed(ctx, "subdir", "test/package") |
| So(err, ShouldBeNil) |
| |
| // Verify the final state (only first package should survive). |
| So(scanDir(tempDir), ShouldResemble, []string{ |
| ".cipd/pkgs/0/-wEu41lw0_aOomrCDp4gKs0uClIlMg25S2j-UMHKwFYC/.cipdpkg/manifest.json", |
| ".cipd/pkgs/0/_current:-wEu41lw0_aOomrCDp4gKs0uClIlMg25S2j-UMHKwFYC", |
| ".cipd/pkgs/0/description.json", |
| // it's 2 because we flipped inst2 and inst in the installation order. |
| // When we RemoveDeployed, we remove index 1. |
| ".cipd/pkgs/2/-wEu41lw0_aOomrCDp4gKs0uClIlMg25S2j-UMHKwFYC/.cipdpkg/manifest.json", |
| ".cipd/pkgs/2/_current:-wEu41lw0_aOomrCDp4gKs0uClIlMg25S2j-UMHKwFYC", |
| ".cipd/pkgs/2/description.json", |
| ".cipd/tmp!", |
| "some/executable1*", |
| "some/file/path1", |
| "subdir/some/executable1*", |
| "subdir/some/file/path1", |
| }) |
| }) |
| }) |
| }) |
| } |
| |
| func TestRemoveDeployedWindows(t *testing.T) { |
| t.Parallel() |
| |
| if runtime.GOOS != "windows" { |
| t.Skip("Skipping on posix") |
| } |
| |
| ctx := context.Background() |
| |
| Convey("Given a temp directory", t, func() { |
| tempDir := mkTempDir() |
| |
| Convey("RemoveDeployed works", func() { |
| d := New(tempDir) |
| |
| // Deploy some instance (to keep it). |
| inst := makeTestInstance("test/package/123", []fs.File{ |
| fs.NewTestFile("some/file/path1", "data a", fs.TestFileOpts{}), |
| fs.NewTestFile("some/executable1", "data b", fs.TestFileOpts{Executable: true}), |
| }, pkg.InstallModeCopy) |
| _, err := d.DeployInstance(ctx, "", inst) |
| So(err, ShouldBeNil) |
| |
| // Deploy another instance (to remove it). |
| inst2 := makeTestInstance("test/package", []fs.File{ |
| fs.NewTestFile("some/file/path2", "data a", fs.TestFileOpts{}), |
| fs.NewTestFile("some/executable2", "data b", fs.TestFileOpts{Executable: true}), |
| fs.NewTestFile("some/to-be-empty-dir/file", "data", fs.TestFileOpts{}), |
| }, pkg.InstallModeCopy) |
| _, err = d.DeployInstance(ctx, "", inst2) |
| So(err, ShouldBeNil) |
| |
| // Now remove the second package. |
| err = d.RemoveDeployed(ctx, "", "test/package") |
| So(err, ShouldBeNil) |
| |
| // Verify the final state (only first package should survive). |
| So(scanDir(tempDir), ShouldResemble, []string{ |
| ".cipd/pkgs/0/-wEu41lw0_aOomrCDp4gKs0uClIlMg25S2j-UMHKwFYC/.cipdpkg/manifest.json", |
| ".cipd/pkgs/0/_current.txt", |
| ".cipd/pkgs/0/description.json", |
| ".cipd/tmp!", |
| "some/executable1", |
| "some/file/path1", |
| }) |
| |
| Convey("in subdir", func() { |
| // Deploy some instance (to keep it). |
| _, err := d.DeployInstance(ctx, "subdir", inst2) |
| So(err, ShouldBeNil) |
| |
| // Deploy another instance (to remove it). |
| _, err = d.DeployInstance(ctx, "subdir", inst) |
| So(err, ShouldBeNil) |
| |
| // Now remove the second package. |
| err = d.RemoveDeployed(ctx, "subdir", "test/package") |
| So(err, ShouldBeNil) |
| |
| // Verify the final state (only first package should survive). |
| So(scanDir(tempDir), ShouldResemble, []string{ |
| ".cipd/pkgs/0/-wEu41lw0_aOomrCDp4gKs0uClIlMg25S2j-UMHKwFYC/.cipdpkg/manifest.json", |
| ".cipd/pkgs/0/_current.txt", |
| ".cipd/pkgs/0/description.json", |
| ".cipd/pkgs/2/-wEu41lw0_aOomrCDp4gKs0uClIlMg25S2j-UMHKwFYC/.cipdpkg/manifest.json", |
| ".cipd/pkgs/2/_current.txt", |
| ".cipd/pkgs/2/description.json", |
| ".cipd/tmp!", |
| "some/executable1", |
| "some/file/path1", |
| "subdir/some/executable1", |
| "subdir/some/file/path1", |
| }) |
| }) |
| }) |
| }) |
| } |
| |
| func TestCheckDeployedAndRepair(t *testing.T) { |
| t.Parallel() |
| |
| ctx := context.Background() |
| |
| Convey("Given a temp directory", t, func() { |
| tempDir := mkTempDir() |
| dep := New(tempDir) |
| |
| deployInst := func(mode pkg.InstallMode, f ...fs.File) pkg.Instance { |
| inst := makeTestInstance("test/package", f, mode) |
| inst.instanceID = "111111111_aOomrCDp4gKs0uClIlMg25S2j-UMHKwFYC" |
| _, err := dep.DeployInstance(ctx, "subdir", inst) |
| So(err, ShouldBeNil) |
| return inst |
| } |
| |
| rm := func(p string) { |
| So(os.Remove(filepath.Join(tempDir, filepath.FromSlash(p))), ShouldBeNil) |
| } |
| |
| check := func(expected *DeployedPackage) *DeployedPackage { |
| dp, err := dep.CheckDeployed(ctx, "subdir", "test/package", CheckPresence, pkg.WithoutManifest) |
| So(err, ShouldBeNil) |
| So(dp.Manifest, ShouldNotBeNil) |
| dp.Manifest = nil |
| So(dp, ShouldResemble, expected) |
| return dp |
| } |
| |
| repair := func(p *DeployedPackage, inst pkg.Instance) { |
| if len(p.ToRedeploy) == 0 { |
| inst = nil |
| } |
| err := dep.RepairDeployed(ctx, "subdir", p.Pin, RepairParams{ |
| Instance: inst, |
| ToRedeploy: p.ToRedeploy, |
| ToRelink: p.ToRelink, |
| }) |
| So(err, ShouldBeNil) |
| } |
| |
| checkHealty := func() { |
| dp, err := dep.CheckDeployed(ctx, "subdir", "test/package", CheckPresence, pkg.WithoutManifest) |
| So(err, ShouldBeNil) |
| So(dp.Deployed, ShouldBeTrue) |
| So(dp.ToRedeploy, ShouldHaveLength, 0) |
| So(dp.ToRelink, ShouldHaveLength, 0) |
| } |
| |
| Convey("Copy install mode, no symlinks", func() { |
| inst := deployInst(pkg.InstallModeCopy, |
| fs.NewTestFile("some/file/path", "data a", fs.TestFileOpts{}), |
| fs.NewTestFile("another-file", "data b", fs.TestFileOpts{}), |
| fs.NewTestFile("some/executable", "data c", fs.TestFileOpts{Executable: true}), |
| ) |
| |
| expected := check(&DeployedPackage{ |
| Deployed: true, |
| Pin: inst.Pin(), |
| Subdir: "subdir", |
| InstallMode: pkg.InstallModeCopy, |
| packagePath: filepath.Join(tempDir, ".cipd/pkgs/0"), |
| instancePath: filepath.Join(tempDir, ".cipd/pkgs/0/"+inst.Pin().InstanceID), |
| }) |
| |
| rm("subdir/some/file/path") |
| rm("subdir/another-file") |
| expected.ToRedeploy = []string{"some/file/path", "another-file"} |
| |
| check(expected) |
| repair(expected, inst) |
| checkHealty() |
| }) |
| |
| if runtime.GOOS != "windows" { |
| Convey("Copy install mode, with symlink files", func() { |
| inst := deployInst(pkg.InstallModeCopy, |
| fs.NewTestFile("some/file/path", "data a", fs.TestFileOpts{}), |
| fs.NewTestFile("another-file", "data b", fs.TestFileOpts{}), |
| fs.NewTestFile("some/executable", "data c", fs.TestFileOpts{Executable: true}), |
| fs.NewTestSymlink("some/symlink", "executable"), |
| fs.NewTestSymlink("working_abs_symlink", "/bin"), |
| // Even though this symlink points to a missing file, it should not |
| // be considered broken, since it was specified like this in the |
| // package file (so "repairing" it won't change anything). |
| fs.NewTestSymlink("broken_abs_symlink", "/i_hope_this_dir_is_missing_on_bots"), |
| ) |
| |
| expected := check(&DeployedPackage{ |
| Deployed: true, |
| Pin: inst.Pin(), |
| Subdir: "subdir", |
| InstallMode: pkg.InstallModeCopy, |
| packagePath: filepath.Join(tempDir, ".cipd/pkgs/0"), |
| instancePath: filepath.Join(tempDir, ".cipd/pkgs/0/"+inst.Pin().InstanceID), |
| }) |
| |
| Convey("Symlink itself is gone but the target is not", func() { |
| rm("subdir/some/symlink") |
| expected.ToRelink = []string{"some/symlink"} |
| |
| check(expected) |
| repair(expected, inst) |
| checkHealty() |
| }) |
| |
| Convey("Target is gone, but the symlink is not", func() { |
| rm("subdir/some/executable") |
| expected.ToRedeploy = []string{"some/executable"} |
| expected.ToRelink = []string{"some/symlink"} // ~ noop |
| |
| check(expected) |
| repair(expected, inst) |
| checkHealty() |
| }) |
| |
| Convey("Absolute symlinks are gone", func() { |
| rm("subdir/working_abs_symlink") |
| rm("subdir/broken_abs_symlink") |
| expected.ToRelink = []string{"working_abs_symlink", "broken_abs_symlink"} |
| |
| check(expected) |
| repair(expected, inst) |
| checkHealty() |
| }) |
| }) |
| |
| Convey("Symlink install mode", func() { |
| inst := deployInst(pkg.InstallModeSymlink, |
| fs.NewTestFile("some/file/path", "data a", fs.TestFileOpts{}), |
| fs.NewTestFile("another-file", "data b", fs.TestFileOpts{}), |
| fs.NewTestFile("some/executable", "data c", fs.TestFileOpts{Executable: true}), |
| fs.NewTestSymlink("some/symlink", "executable"), |
| fs.NewTestSymlink("working_abs_symlink", "/bin"), |
| fs.NewTestSymlink("broken_abs_symlink", "/i_hope_this_dir_is_missing_on_bots"), |
| ) |
| |
| expected := check(&DeployedPackage{ |
| Deployed: true, |
| Pin: inst.Pin(), |
| Subdir: "subdir", |
| InstallMode: pkg.InstallModeSymlink, |
| packagePath: filepath.Join(tempDir, ".cipd/pkgs/0"), |
| instancePath: filepath.Join(tempDir, ".cipd/pkgs/0/"+inst.Pin().InstanceID), |
| }) |
| |
| Convey("Site root files are gone, but gut files are OK", func() { |
| rm("subdir/some/file/path") |
| rm("subdir/some/symlink") |
| rm("subdir/broken_abs_symlink") |
| expected.ToRelink = []string{"some/file/path", "some/symlink", "broken_abs_symlink"} |
| |
| check(expected) |
| repair(expected, inst) |
| checkHealty() |
| }) |
| |
| Convey("Regular gut files are gone", func() { |
| rm(".cipd/pkgs/0/111111111_aOomrCDp4gKs0uClIlMg25S2j-UMHKwFYC/some/executable") |
| expected.ToRedeploy = []string{"some/executable"} |
| expected.ToRelink = []string{"some/symlink"} |
| |
| check(expected) |
| repair(expected, inst) |
| checkHealty() |
| }) |
| |
| Convey("Rel symlink gut files are gone", func() { |
| rm(".cipd/pkgs/0/111111111_aOomrCDp4gKs0uClIlMg25S2j-UMHKwFYC/some/symlink") |
| expected.ToRelink = []string{"some/symlink"} |
| |
| check(expected) |
| repair(expected, inst) |
| checkHealty() |
| }) |
| |
| Convey("Abs symlink gut files are gone", func() { |
| rm(".cipd/pkgs/0/111111111_aOomrCDp4gKs0uClIlMg25S2j-UMHKwFYC/working_abs_symlink") |
| rm(".cipd/pkgs/0/111111111_aOomrCDp4gKs0uClIlMg25S2j-UMHKwFYC/broken_abs_symlink") |
| expected.ToRelink = []string{"working_abs_symlink", "broken_abs_symlink"} |
| |
| check(expected) |
| repair(expected, inst) |
| checkHealty() |
| }) |
| }) |
| } |
| }) |
| } |
| |
| func TestUpgradeOldPkgDir(t *testing.T) { |
| t.Parallel() |
| |
| ctx := context.Background() |
| |
| Convey("Given an old-style pkgs dir", t, func() { |
| tempDir := mkTempDir() |
| |
| d := New(tempDir) |
| trashDir := filepath.Join(tempDir, fs.SiteServiceDir, "trash") |
| fs := fs.NewFileSystem(tempDir, trashDir) |
| |
| inst := makeTestInstance("test/package", nil, pkg.InstallModeSymlink) |
| _, err := d.DeployInstance(ctx, "", inst) |
| So(err, ShouldBeNil) |
| |
| currentLine := func(folder, inst string) string { |
| if runtime.GOOS == "windows" { |
| return fmt.Sprintf(".cipd/pkgs/%s/_current.txt", folder) |
| } |
| return fmt.Sprintf(".cipd/pkgs/%s/_current:%s", folder, inst) |
| } |
| |
| pkg0 := filepath.Join(tempDir, ".cipd", "pkgs", "0") |
| pkgOldStyle := filepath.Join(tempDir, ".cipd", "pkgs", "test_package-deadbeef") |
| So(fs.EnsureFileGone(ctx, filepath.Join(pkg0, descriptionName)), ShouldBeNil) |
| So(fs.Replace(ctx, pkg0, pkgOldStyle), ShouldBeNil) |
| So(scanDir(tempDir), ShouldResemble, []string{ |
| ".cipd/pkgs/test_package-deadbeef/-wEu41lw0_aOomrCDp4gKs0uClIlMg25S2j-UMHKwFYC/.cipdpkg/manifest.json", |
| currentLine("test_package-deadbeef", "-wEu41lw0_aOomrCDp4gKs0uClIlMg25S2j-UMHKwFYC"), |
| ".cipd/tmp!", |
| }) |
| |
| Convey("reading the packages finds it", func() { |
| pins, err := d.FindDeployed(ctx) |
| So(err, ShouldBeNil) |
| So(pins, ShouldResemble, PinSliceBySubdir{ |
| "": PinSlice{ |
| {"test/package", "-wEu41lw0_aOomrCDp4gKs0uClIlMg25S2j-UMHKwFYC"}, |
| }, |
| }) |
| |
| Convey("and upgrades the package", func() { |
| So(scanDir(tempDir), ShouldResemble, []string{ |
| ".cipd/pkgs/test_package-deadbeef/-wEu41lw0_aOomrCDp4gKs0uClIlMg25S2j-UMHKwFYC/.cipdpkg/manifest.json", |
| currentLine("test_package-deadbeef", "-wEu41lw0_aOomrCDp4gKs0uClIlMg25S2j-UMHKwFYC"), |
| ".cipd/pkgs/test_package-deadbeef/description.json", |
| ".cipd/tmp!", |
| }) |
| }) |
| }) |
| |
| Convey("can deploy new instance", func() { |
| inst.instanceID = "111111111_aOomrCDp4gKs0uClIlMg25S2j-UMHKwFYC" |
| _, err := d.DeployInstance(ctx, "", inst) |
| So(err, ShouldBeNil) |
| So(scanDir(tempDir), ShouldResemble, []string{ |
| ".cipd/pkgs/test_package-deadbeef/111111111_aOomrCDp4gKs0uClIlMg25S2j-UMHKwFYC/.cipdpkg/manifest.json", |
| currentLine("test_package-deadbeef", "111111111_aOomrCDp4gKs0uClIlMg25S2j-UMHKwFYC"), |
| ".cipd/pkgs/test_package-deadbeef/description.json", |
| ".cipd/tmp!", |
| }) |
| }) |
| |
| Convey("can deploy other package", func() { |
| inst.packageName = "something/cool" |
| inst.instanceID = "111111111_aOomrCDp4gKs0uClIlMg25S2j-UMHKwFYC" |
| _, err := d.DeployInstance(ctx, "", inst) |
| So(err, ShouldBeNil) |
| So(scanDir(tempDir), ShouldResemble, []string{ |
| ".cipd/pkgs/0/111111111_aOomrCDp4gKs0uClIlMg25S2j-UMHKwFYC/.cipdpkg/manifest.json", |
| currentLine("0", "111111111_aOomrCDp4gKs0uClIlMg25S2j-UMHKwFYC"), |
| ".cipd/pkgs/0/description.json", |
| ".cipd/pkgs/test_package-deadbeef/-wEu41lw0_aOomrCDp4gKs0uClIlMg25S2j-UMHKwFYC/.cipdpkg/manifest.json", |
| currentLine("test_package-deadbeef", "-wEu41lw0_aOomrCDp4gKs0uClIlMg25S2j-UMHKwFYC"), |
| ".cipd/pkgs/test_package-deadbeef/description.json", |
| ".cipd/tmp!", |
| }) |
| }) |
| |
| }) |
| } |
| |
| func TestNumSet(t *testing.T) { |
| t.Parallel() |
| |
| Convey("numSet", t, func() { |
| ns := numSet{} |
| |
| Convey("can add numbers out of order", func() { |
| for _, n := range []int{392, 1, 7, 29, 4} { |
| ns.addNum(n) |
| } |
| So(ns, ShouldResemble, numSet{1, 4, 7, 29, 392}) |
| |
| Convey("and rejects duplicates", func() { |
| ns.addNum(7) |
| So(ns, ShouldResemble, numSet{1, 4, 7, 29, 392}) |
| }) |
| }) |
| |
| Convey("smallestNewNum", func() { |
| ns = numSet{1, 4, 7, 29, 392} |
| |
| smallNums := []int{0, 2, 3, 5, 6, 8} |
| for _, sn := range smallNums { |
| So(ns.smallestNewNum(), ShouldEqual, sn) |
| ns.addNum(sn) |
| } |
| }) |
| |
| }) |
| } |
| |
| func TestResolveValidPackageDirs(t *testing.T) { |
| t.Parallel() |
| |
| ctx := context.Background() |
| |
| Convey("resolveValidPackageDirs", t, func() { |
| tempDir := mkTempDir() |
| d := New(tempDir).(*deployerImpl) |
| pkgdir, err := d.fs.RootRelToAbs(filepath.FromSlash(packagesDir)) |
| So(err, ShouldBeNil) |
| |
| writeFiles := func(files ...fs.File) { |
| for _, f := range files { |
| name := filepath.Join(tempDir, f.Name()) |
| if f.Symlink() { |
| targ, err := f.SymlinkTarget() |
| So(err, ShouldBeNil) |
| err = d.fs.EnsureSymlink(ctx, name, targ) |
| So(err, ShouldBeNil) |
| } else { |
| err := d.fs.EnsureFile(ctx, name, func(wf *os.File) error { |
| reader, err := f.Open() |
| if err != nil { |
| return err |
| } |
| defer reader.Close() |
| _, err = io.Copy(wf, reader) |
| return err |
| }) |
| So(err, ShouldBeNil) |
| } |
| } |
| } |
| desc := func(pkgFolder, subdir, packageName string) fs.File { |
| return fs.NewTestFile( |
| fmt.Sprintf(".cipd/pkgs/%s/description.json", pkgFolder), |
| fmt.Sprintf(`{"subdir": %q, "package_name": %q}`, subdir, packageName), |
| fs.TestFileOpts{}, |
| ) |
| } |
| resolve := func() (numSet, map[description]string) { |
| nums, all := d.resolveValidPackageDirs(ctx, pkgdir) |
| for desc, absPath := range all { |
| rel, err := filepath.Rel(tempDir, absPath) |
| So(err, ShouldBeNil) |
| all[desc] = strings.Replace(filepath.Clean(rel), "\\", "/", -1) |
| } |
| return nums, all |
| } |
| |
| Convey("packagesDir with just description.json", func() { |
| writeFiles( |
| desc("0", "", "some/package/name"), |
| ) |
| nums, all := resolve() |
| So(nums, ShouldResemble, numSet{0}) |
| So(all, ShouldResemble, map[description]string{ |
| {"", "some/package/name"}: ".cipd/pkgs/0", |
| }) |
| So(scanDir(tempDir), ShouldResemble, []string{ |
| ".cipd/pkgs/0/description.json", |
| }) |
| }) |
| |
| Convey("packagesDir with duplicates", func() { |
| writeFiles( |
| desc("0", "", "some/package/name"), |
| desc("some_other", "", "some/package/name"), |
| desc("1", "", "some/package/name"), |
| ) |
| nums, all := resolve() |
| So(nums, ShouldResemble, numSet{0}) |
| So(all, ShouldResemble, map[description]string{ |
| {"", "some/package/name"}: ".cipd/pkgs/0", |
| }) |
| So(scanDir(tempDir), ShouldResemble, []string{ |
| ".cipd/pkgs/0/description.json", |
| }) |
| }) |
| |
| Convey("bogus file", func() { |
| writeFiles( |
| fs.NewTestFile(".cipd/pkgs/wat", "hello", fs.TestFileOpts{}), |
| ) |
| nums, all := resolve() |
| So(nums, ShouldResemble, numSet(nil)) |
| So(all, ShouldResemble, map[description]string{}) |
| So(scanDir(tempDir), ShouldResemble, []string{".cipd/pkgs!"}) |
| }) |
| |
| Convey("bad description.json", func() { |
| writeFiles( |
| fs.NewTestFile(".cipd/pkgs/0/description.json", "hello", fs.TestFileOpts{}), |
| ) |
| nums, all := resolve() |
| So(nums, ShouldResemble, numSet(nil)) |
| So(all, ShouldResemble, map[description]string{}) |
| So(scanDir(tempDir), ShouldResemble, []string{".cipd/pkgs!"}) |
| }) |
| |
| Convey("package with no manifest", func() { |
| writeFiles( |
| fs.NewTestFile(".cipd/pkgs/0/deadbeef/something", "hello", fs.TestFileOpts{}), |
| fs.NewTestSymlink(".cipd/pkgs/0/_current", "deadbeef"), |
| ) |
| nums, all := resolve() |
| So(nums, ShouldResemble, numSet(nil)) |
| So(all, ShouldResemble, map[description]string{}) |
| So(scanDir(tempDir), ShouldResemble, []string{".cipd/pkgs!"}) |
| }) |
| |
| Convey("package with manifest", func() { |
| curLink := fs.NewTestSymlink(".cipd/pkgs/oldskool/_current", "-wEu41lw0_aOomrCDp4gKs0uClIlMg25S2j-UMHKwFYC") |
| if runtime.GOOS == "windows" { |
| curLink = fs.NewTestFile(".cipd/pkgs/oldskool/_current.txt", "-wEu41lw0_aOomrCDp4gKs0uClIlMg25S2j-UMHKwFYC", fs.TestFileOpts{}) |
| } |
| writeFiles( |
| fs.NewTestFile(".cipd/pkgs/oldskool/-wEu41lw0_aOomrCDp4gKs0uClIlMg25S2j-UMHKwFYC/something", "hello", fs.TestFileOpts{}), |
| fs.NewTestFile(".cipd/pkgs/oldskool/-wEu41lw0_aOomrCDp4gKs0uClIlMg25S2j-UMHKwFYC/.cipdpkg/manifest.json", |
| `{"format_version": "1", "package_name": "cool/cats"}`, fs.TestFileOpts{}), |
| curLink, |
| ) |
| nums, all := resolve() |
| So(nums, ShouldResemble, numSet(nil)) |
| So(all, ShouldResemble, map[description]string{ |
| {"", "cool/cats"}: ".cipd/pkgs/oldskool", |
| }) |
| linkExpect := curLink.Name() |
| if curLink.Symlink() { |
| targ, err := curLink.SymlinkTarget() |
| So(err, ShouldBeNil) |
| linkExpect = fmt.Sprintf("%s:%s", curLink.Name(), targ) |
| } |
| |
| So(scanDir(tempDir), ShouldResemble, []string{ |
| ".cipd/pkgs/oldskool/-wEu41lw0_aOomrCDp4gKs0uClIlMg25S2j-UMHKwFYC/.cipdpkg/manifest.json", |
| ".cipd/pkgs/oldskool/-wEu41lw0_aOomrCDp4gKs0uClIlMg25S2j-UMHKwFYC/something", |
| linkExpect, |
| ".cipd/pkgs/oldskool/description.json", |
| }) |
| }) |
| |
| }) |
| |
| } |
| |
| func TestRemoveEmptyTrees(t *testing.T) { |
| t.Parallel() |
| |
| ctx := context.Background() |
| |
| Convey("Given a temp directory", t, func() { |
| tempDir := mkTempDir() |
| |
| absPath := func(rel string) string { |
| return filepath.Join(tempDir, filepath.FromSlash(rel)) |
| } |
| touch := func(rel string) { |
| abs := absPath(rel) |
| err := os.MkdirAll(filepath.Dir(abs), 0777) |
| So(err, ShouldBeNil) |
| f, err := os.Create(abs) |
| So(err, ShouldBeNil) |
| f.Close() |
| } |
| delete := func(rel string) { |
| So(os.Remove(absPath(rel)), ShouldBeNil) |
| } |
| |
| dirSet := func(rel ...string) stringset.Set { |
| out := stringset.New(len(rel)) |
| for _, r := range rel { |
| out.Add(absPath(r)) |
| } |
| return out |
| } |
| |
| Convey("Simple case", func() { |
| touch("1/2/3/4") |
| delete("1/2/3/4") |
| removeEmptyTrees(ctx, absPath("1/2"), dirSet("1/2/3")) |
| So(scanDir(tempDir), ShouldResemble, []string{"1!"}) |
| }) |
| |
| Convey("Non empty", func() { |
| touch("1/2/3/4") |
| removeEmptyTrees(ctx, absPath("1/2"), dirSet("1/2/3")) |
| So(scanDir(tempDir), ShouldResemble, []string{"1/2/3/4"}) |
| }) |
| |
| Convey("Multiple empty", func() { |
| touch("1/2/3a/4") |
| touch("1/2/3b/4") |
| |
| delete("1/2/3a/4") |
| delete("1/2/3b/4") |
| |
| removeEmptyTrees(ctx, absPath("1/2"), dirSet("1/2/3a", "1/2/3b")) |
| So(scanDir(tempDir), ShouldResemble, []string{"1!"}) |
| }) |
| |
| Convey("Respects 'empty' set", func() { |
| touch("1/2/3a/4") |
| touch("1/2/3b/4") |
| |
| delete("1/2/3a/4") |
| delete("1/2/3b/4") |
| |
| removeEmptyTrees(ctx, absPath("1/2"), dirSet("1/2/3b")) |
| So(scanDir(tempDir), ShouldResemble, []string{"1/2/3a!"}) |
| }) |
| }) |
| } |
| |
| //////////////////////////////////////////////////////////////////////////////// |
| |
| type testPackageInstance struct { |
| packageName string |
| instanceID string |
| files []fs.File |
| installMode pkg.InstallMode |
| } |
| |
| // makeTestInstance returns pkg.Instance implementation with mocked guts. |
| func makeTestInstance(name string, files []fs.File, installMode pkg.InstallMode) *testPackageInstance { |
| // Generate and append manifest file. |
| out := bytes.Buffer{} |
| err := pkg.WriteManifest(&pkg.Manifest{ |
| FormatVersion: pkg.ManifestFormatVersion, |
| PackageName: name, |
| InstallMode: installMode, |
| }, &out) |
| if err != nil { |
| panic("Failed to write a manifest") |
| } |
| files = append(files, fs.NewTestFile(pkg.ManifestName, string(out.Bytes()), fs.TestFileOpts{})) |
| return &testPackageInstance{ |
| packageName: name, |
| instanceID: "-wEu41lw0_aOomrCDp4gKs0uClIlMg25S2j-UMHKwFYC", // some "representative" SHA256 IID |
| files: files, |
| } |
| } |
| |
| func (f *testPackageInstance) Pin() Pin { return Pin{f.packageName, f.instanceID} } |
| func (f *testPackageInstance) Files() []fs.File { return f.files } |
| func (f *testPackageInstance) Source() io.ReadSeeker { panic("Not implemented") } |
| func (f *testPackageInstance) Close(context.Context, bool) error { return nil } |
| |
| //////////////////////////////////////////////////////////////////////////////// |
| |
| // scanDir returns list of files (regular, symlinks and directories if they are |
| // empty) it finds in a directory. Symlinks are returned as "path:target". |
| // Empty directories are suffixed with '!'. Regular executable files are |
| // suffixed with '*'. All paths are relative to the scanned directory and slash |
| // separated. Symlink targets are slash separated too, but otherwise not |
| // modified. Does not look inside symlinked directories. |
| func scanDir(root string) (out []string) { |
| err := filepath.Walk(root, func(path string, info os.FileInfo, err error) error { |
| if err != nil { |
| return err |
| } |
| rel, err := filepath.Rel(root, path) |
| switch { |
| case err != nil: |
| return err |
| case rel == ".": |
| return nil |
| case info.Mode().IsDir() && !isEmptyDir(path): |
| return nil |
| } |
| |
| rel = filepath.ToSlash(rel) |
| item := rel |
| |
| if !info.Mode().IsRegular() && !info.Mode().IsDir() { // probably a symlink |
| if target, err := os.Readlink(path); err == nil { |
| item = fmt.Sprintf("%s:%s", rel, filepath.ToSlash(target)) |
| } else { |
| item = fmt.Sprintf("%s:??????", rel) |
| } |
| } |
| |
| suffix := "" |
| switch { |
| case info.Mode().IsRegular() && (info.Mode().Perm()&0100) != 0: |
| suffix = "*" |
| case info.Mode().IsDir(): |
| suffix = "!" |
| } |
| |
| out = append(out, item+suffix) |
| return nil |
| }) |
| if err != nil { |
| panic("Failed to walk a directory") |
| } |
| return |
| } |
| |
| // isEmptyDir return true if 'path' refers to an empty directory. |
| func isEmptyDir(path string) bool { |
| infos, err := ioutil.ReadDir(path) |
| return err == nil && len(infos) == 0 |
| } |
| |
| // readFile reads content of an existing text file. Root path is provided as |
| // a native path, rel - as a slash-separated path. |
| func readFile(root, rel string) string { |
| body, err := ioutil.ReadFile(filepath.Join(root, filepath.FromSlash(rel))) |
| So(err, ShouldBeNil) |
| return string(body) |
| } |