diff --git a/archive/tar_test.go b/archive/tar_test.go index db216c0df..33b83b063 100644 --- a/archive/tar_test.go +++ b/archive/tar_test.go @@ -1,6 +1,7 @@ package archive import ( + "bytes" "context" "io/ioutil" "os" @@ -46,64 +47,7 @@ func TestBaseDiff(t *testing.T) { } func TestDiffApply(t *testing.T) { - as := []fstest.Applier{ - baseApplier, - fstest.Apply( - fstest.CreateFile("/etc/hosts", []byte("127.0.0.1 localhost.localdomain"), 0644), - fstest.CreateFile("/etc/fstab", []byte("/dev/sda1\t/\text4\tdefaults 1 1\n"), 0600), - fstest.CreateFile("/etc/badfile", []byte(""), 0666), - fstest.CreateFile("/home/derek/.zshrc", []byte("#ZSH is just better\n"), 0640), - ), - fstest.Apply( - fstest.Remove("/etc/badfile"), - fstest.Rename("/home/derek", "/home/notderek"), - ), - fstest.Apply( - fstest.RemoveAll("/usr"), - fstest.Remove("/etc/hosts.allow"), - ), - fstest.Apply( - fstest.RemoveAll("/home"), - fstest.CreateDir("/home/derek", 0700), - fstest.CreateFile("/home/derek/.bashrc", []byte("#not going away\n"), 0640), - fstest.Link("/etc/hosts", "/etc/hosts.allow"), - ), - } - - if err := testDiffApply(as...); err != nil { - t.Fatalf("Test diff apply failed: %+v", err) - } -} - -// TestDiffApplyDeletion checks various deletion scenarios to ensure -// deletions are properly picked up and applied -func TestDiffApplyDeletion(t *testing.T) { - as := []fstest.Applier{ - fstest.Apply( - fstest.CreateDir("/test/somedir", 0755), - fstest.CreateDir("/lib", 0700), - fstest.CreateFile("/lib/hidden", []byte{}, 0644), - ), - fstest.Apply( - fstest.CreateFile("/test/a", []byte{}, 0644), - fstest.CreateFile("/test/b", []byte{}, 0644), - fstest.CreateDir("/test/otherdir", 0755), - fstest.CreateFile("/test/otherdir/.empty", []byte{}, 0644), - fstest.RemoveAll("/lib"), - fstest.CreateDir("/lib", 0700), - fstest.CreateFile("/lib/not-hidden", []byte{}, 0644), - ), - fstest.Apply( - fstest.Remove("/test/a"), - fstest.Remove("/test/b"), - fstest.RemoveAll("/test/otherdir"), - fstest.CreateFile("/lib/newfile", []byte{}, 0644), - ), - } - - if err := testDiffApply(as...); err != nil { - t.Fatalf("Test diff apply failed: %+v", err) - } + fstest.FSSuite(t, diffApplier{}) } func testApply(a fstest.Applier) error { @@ -174,48 +118,43 @@ func testBaseDiff(a fstest.Applier) error { return fstest.CheckDirectoryEqual(td, dest) } -func testDiffApply(as ...fstest.Applier) error { - base, err := ioutil.TempDir("", "test-diff-apply-base-") - if err != nil { - return errors.Wrap(err, "failed to create temp dir") - } - defer os.RemoveAll(base) - dest, err := ioutil.TempDir("", "test-diff-apply-dest-") - if err != nil { - return errors.Wrap(err, "failed to create temp dir") - } - defer os.RemoveAll(dest) +type diffApplier struct{} - ctx := context.Background() - for i, a := range as { - if err := diffApply(ctx, a, base, dest); err != nil { - return errors.Wrapf(err, "diff apply failed at layer %d", i) - } +func (d diffApplier) TestContext(ctx context.Context) (context.Context, func(), error) { + base, err := ioutil.TempDir("", "test-diff-apply-") + if err != nil { + return ctx, nil, errors.Wrap(err, "failed to create temp dir") } - return nil + return context.WithValue(ctx, d, base), func() { + os.RemoveAll(base) + }, nil } -// diffApply applies the given changes on the base and -// computes the diff and applies to the dest. -func diffApply(ctx context.Context, a fstest.Applier, base, dest string) error { - baseCopy, err := ioutil.TempDir("", "test-diff-apply-copy-") +func (d diffApplier) Apply(ctx context.Context, a fstest.Applier) (string, func(), error) { + base := ctx.Value(d).(string) + + applyCopy, err := ioutil.TempDir("", "test-diffapply-apply-copy-") if err != nil { - return errors.Wrap(err, "failed to create temp dir") + return "", nil, errors.Wrap(err, "failed to create temp dir") } - defer os.RemoveAll(baseCopy) - if err := fs.CopyDir(baseCopy, base); err != nil { - return errors.Wrap(err, "failed to copy base") + defer os.RemoveAll(applyCopy) + if err = fs.CopyDir(applyCopy, base); err != nil { + return "", nil, errors.Wrap(err, "failed to copy base") + } + if err := a.Apply(applyCopy); err != nil { + return "", nil, errors.Wrap(err, "failed to apply changes to copy of base") } - if err := a.Apply(base); err != nil { - return errors.Wrap(err, "failed to apply changes to base") + diffBytes, err := ioutil.ReadAll(Diff(ctx, base, applyCopy)) + if err != nil { + return "", nil, errors.Wrap(err, "failed to create diff") } - if _, err := Apply(ctx, dest, Diff(ctx, baseCopy, base)); err != nil { - return errors.Wrap(err, "failed to apply tar stream") + if _, err = Apply(ctx, base, bytes.NewReader(diffBytes)); err != nil { + return "", nil, errors.Wrap(err, "failed to apply tar stream") } - return fstest.CheckDirectoryEqual(base, dest) + return base, nil, nil } func readDirNames(p string) ([]string, error) { diff --git a/fs/fstest/testsuite.go b/fs/fstest/testsuite.go new file mode 100644 index 000000000..372d76f08 --- /dev/null +++ b/fs/fstest/testsuite.go @@ -0,0 +1,161 @@ +package fstest + +import ( + "context" + "io/ioutil" + "os" + "testing" +) + +type TestApplier interface { + TestContext(context.Context) (context.Context, func(), error) + Apply(context.Context, Applier) (string, func(), error) +} + +func FSSuite(t *testing.T, a TestApplier) { + t.Run("Basic", makeTest(t, a, basicTest)) + t.Run("Deletion", makeTest(t, a, deletionTest)) + // TODO: Add hard section, run if command line arg or function arg set to true + // Hard tests + t.Run("HardlinkUnmodified", makeTest(t, a, hardlinkUnmodified)) + t.Run("HardlinkBeforeUnmodified", makeTest(t, a, hardlinkBeforeUnmodified)) + t.Run("HardlinkBeforeModified", makeTest(t, a, hardlinkBeforeModified)) +} + +func makeTest(t *testing.T, ta TestApplier, as []Applier) func(t *testing.T) { + return func(t *testing.T) { + ctx, cleanup, err := ta.TestContext(context.Background()) + if err != nil { + t.Fatalf("Unable to get test context: %+v", err) + } + defer cleanup() + + applyDir, err := ioutil.TempDir("", "test-expected-") + if err != nil { + t.Fatalf("Unable to make temp directory: %+v", err) + } + defer os.RemoveAll(applyDir) + + for i, a := range as { + testDir, c, err := ta.Apply(ctx, a) + if err != nil { + t.Fatalf("Apply failed at %d: %+v", i, err) + } + if err := a.Apply(applyDir); err != nil { + if c != nil { + c() + } + t.Fatalf("Error applying change to apply directory: %+v", err) + } + + err = CheckDirectoryEqual(applyDir, testDir) + if c != nil { + c() + } + if err != nil { + t.Fatalf("Directories not equal at %d (expected <> tested): %+v", i, err) + } + } + } +} + +var ( + // baseApplier creates a basic filesystem layout + // with multiple types of files for basic tests. + baseApplier = Apply( + CreateDir("/etc/", 0755), + CreateFile("/etc/hosts", []byte("127.0.0.1 localhost"), 0644), + Link("/etc/hosts", "/etc/hosts.allow"), + CreateDir("/usr/local/lib", 0755), + CreateFile("/usr/local/lib/libnothing.so", []byte{0x00, 0x00}, 0755), + Symlink("libnothing.so", "/usr/local/lib/libnothing.so.2"), + CreateDir("/home", 0755), + CreateDir("/home/derek", 0700), + ) + + // basicTest covers basic operations + basicTest = []Applier{ + baseApplier, + Apply( + CreateFile("/etc/hosts", []byte("127.0.0.1 localhost.localdomain"), 0644), + CreateFile("/etc/fstab", []byte("/dev/sda1\t/\text4\tdefaults 1 1\n"), 0600), + CreateFile("/etc/badfile", []byte(""), 0666), + CreateFile("/home/derek/.zshrc", []byte("#ZSH is just better\n"), 0640), + ), + Apply( + Remove("/etc/badfile"), + Rename("/home/derek", "/home/notderek"), + ), + Apply( + RemoveAll("/usr"), + Remove("/etc/hosts.allow"), + ), + Apply( + RemoveAll("/home"), + CreateDir("/home/derek", 0700), + CreateFile("/home/derek/.bashrc", []byte("#not going away\n"), 0640), + Link("/etc/hosts", "/etc/hosts.allow"), + ), + } + + // deletionTest covers various deletion scenarios to ensure + // deletions are properly picked up and applied + deletionTest = []Applier{ + Apply( + CreateDir("/test/somedir", 0755), + CreateDir("/lib", 0700), + CreateFile("/lib/hidden", []byte{}, 0644), + ), + Apply( + CreateFile("/test/a", []byte{}, 0644), + CreateFile("/test/b", []byte{}, 0644), + CreateDir("/test/otherdir", 0755), + CreateFile("/test/otherdir/.empty", []byte{}, 0644), + RemoveAll("/lib"), + CreateDir("/lib", 0700), + CreateFile("/lib/not-hidden", []byte{}, 0644), + ), + Apply( + Remove("/test/a"), + Remove("/test/b"), + RemoveAll("/test/otherdir"), + CreateFile("/lib/newfile", []byte{}, 0644), + ), + } + + hardlinkUnmodified = []Applier{ + baseApplier, + Apply( + CreateFile("/etc/hosts", []byte("127.0.0.1 localhost.localdomain"), 0644), + ), + Apply( + Link("/etc/hosts", "/etc/hosts.deny"), + ), + } + + // Hardlink name before with modification + // Tests link is created for unmodified files when new hardlinked file is seen first + hardlinkBeforeUnmodified = []Applier{ + baseApplier, + Apply( + CreateFile("/etc/hosts", []byte("127.0.0.1 localhost.localdomain"), 0644), + ), + Apply( + Link("/etc/hosts", "/etc/before-hosts"), + ), + } + + // Hardlink name after without modification + // tests link is created for modified file with new hardlink + hardlinkBeforeModified = []Applier{ + baseApplier, + Apply( + CreateFile("/etc/hosts", []byte("127.0.0.1 localhost.localdomain"), 0644), + ), + Apply( + Remove("/etc/hosts"), + CreateFile("/etc/hosts", []byte("127.0.0.1 localhost"), 0644), + Link("/etc/hosts", "/etc/before-hosts"), + ), + } +)