Refactor changes and test functions
Remove change type in favor of explicit change function. Using change function makes it more difficult to unnecessarily add to the change interface. Update test apply functions to use an interface rather than a function type. Signed-off-by: Derek McGowan <derek@mcgstyle.net> (github: dmcgowan)
This commit is contained in:
parent
65e8c07847
commit
d96e6e3952
5 changed files with 172 additions and 193 deletions
|
@ -16,14 +16,14 @@ import (
|
|||
// setxattr fstest.SetXAttr("/home", "trusted.overlay.opaque", "y"),
|
||||
|
||||
func TestCopyDirectory(t *testing.T) {
|
||||
apply := fstest.MultiApply(
|
||||
fstest.CreateDirectory("/etc/", 0755),
|
||||
fstest.NewTestFile("/etc/hosts", []byte("localhost 127.0.0.1"), 0644),
|
||||
apply := fstest.Apply(
|
||||
fstest.CreateDir("/etc/", 0755),
|
||||
fstest.CreateFile("/etc/hosts", []byte("localhost 127.0.0.1"), 0644),
|
||||
fstest.Link("/etc/hosts", "/etc/hosts.allow"),
|
||||
fstest.CreateDirectory("/usr/local/lib", 0755),
|
||||
fstest.NewTestFile("/usr/local/lib/libnothing.so", []byte{0x00, 0x00}, 0755),
|
||||
fstest.CreateDir("/usr/local/lib", 0755),
|
||||
fstest.CreateFile("/usr/local/lib/libnothing.so", []byte{0x00, 0x00}, 0755),
|
||||
fstest.Symlink("libnothing.so", "/usr/local/lib/libnothing.so.2"),
|
||||
fstest.CreateDirectory("/home", 0755),
|
||||
fstest.CreateDir("/home", 0755),
|
||||
)
|
||||
|
||||
if err := testCopy(apply); err != nil {
|
||||
|
@ -41,7 +41,7 @@ func testCopy(apply fstest.Applier) error {
|
|||
return errors.Wrap(err, "failed to create temporary directory")
|
||||
}
|
||||
|
||||
if err := apply(t1); err != nil {
|
||||
if err := apply.Apply(t1); err != nil {
|
||||
return errors.Wrap(err, "failed to apply changes")
|
||||
}
|
||||
|
||||
|
|
95
fs/diff.go
95
fs/diff.go
|
@ -48,12 +48,17 @@ type Change struct {
|
|||
Path string
|
||||
}
|
||||
|
||||
// Changes computes changes between lower and upper calling the
|
||||
// given change function for each computed change. Callbacks
|
||||
// will be done serialially and order by path name.
|
||||
// ChangeFunc is the type of function called for each change
|
||||
// computed during a directory changes calculation.
|
||||
type ChangeFunc func(ChangeKind, string, os.FileInfo, error) error
|
||||
|
||||
// Changes computes changes between two directories calling the
|
||||
// given change function for each computed change. The first
|
||||
// directory is intended to the base directory and second
|
||||
// directory the changed directory.
|
||||
//
|
||||
// Changes are ordered by name and should be appliable in the
|
||||
// order in which they received.
|
||||
// The change callback is called by the order of path names and
|
||||
// should be appliable in that order.
|
||||
// Due to this apply ordering, the following is true
|
||||
// - Removed directory trees only create a single change for the root
|
||||
// directory removed. Remaining changes are implied.
|
||||
|
@ -62,7 +67,7 @@ type Change struct {
|
|||
// by the removal of the parent directory.
|
||||
//
|
||||
// Opaque directories will not be treated specially and each file
|
||||
// removed from the lower will show up as a removal
|
||||
// removed from the base directory will show up as a removal.
|
||||
//
|
||||
// File content comparisons will be done on files which have timestamps
|
||||
// which may have been truncated. If either of the files being compared
|
||||
|
@ -71,22 +76,20 @@ type Change struct {
|
|||
// nanosecond values where one of those values is zero, the files will
|
||||
// be considered unchanged if the content is the same. This behavior
|
||||
// is to account for timestamp truncation during archiving.
|
||||
func Changes(ctx context.Context, upper, lower string, ch func(Change, os.FileInfo) error) error {
|
||||
if lower == "" {
|
||||
logrus.Debugf("Using single walk diff for %s", upper)
|
||||
return addDirChanges(ctx, ch, upper)
|
||||
} else if diffOptions := detectDirDiff(upper, lower); diffOptions != nil {
|
||||
logrus.Debugf("Using single walk diff for %s from %s", diffOptions.diffDir, lower)
|
||||
return diffDirChanges(ctx, ch, lower, diffOptions)
|
||||
func Changes(ctx context.Context, a, b string, changeFn ChangeFunc) error {
|
||||
if a == "" {
|
||||
logrus.Debugf("Using single walk diff for %s", b)
|
||||
return addDirChanges(ctx, changeFn, b)
|
||||
} else if diffOptions := detectDirDiff(b, a); diffOptions != nil {
|
||||
logrus.Debugf("Using single walk diff for %s from %s", diffOptions.diffDir, a)
|
||||
return diffDirChanges(ctx, changeFn, a, diffOptions)
|
||||
}
|
||||
|
||||
logrus.Debugf("Using double walk diff for %s from %s", upper, lower)
|
||||
return doubleWalkDiff(ctx, ch, upper, lower)
|
||||
logrus.Debugf("Using double walk diff for %s from %s", b, a)
|
||||
return doubleWalkDiff(ctx, changeFn, a, b)
|
||||
}
|
||||
|
||||
type changeFn func(Change, os.FileInfo) error
|
||||
|
||||
func addDirChanges(ctx context.Context, changes changeFn, root string) error {
|
||||
func addDirChanges(ctx context.Context, changeFn ChangeFunc, root string) error {
|
||||
return filepath.Walk(root, func(path string, f os.FileInfo, err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
|
@ -105,25 +108,20 @@ func addDirChanges(ctx context.Context, changes changeFn, root string) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
change := Change{
|
||||
Path: path,
|
||||
Kind: ChangeKindAdd,
|
||||
}
|
||||
|
||||
return changes(change, f)
|
||||
return changeFn(ChangeKindAdd, path, f, nil)
|
||||
})
|
||||
}
|
||||
|
||||
// diffDirOptions is used when the diff can be directly calculated from
|
||||
// a diff directory to its lower, without walking both trees.
|
||||
// a diff directory to its base, without walking both trees.
|
||||
type diffDirOptions struct {
|
||||
diffDir string
|
||||
skipChange func(string) (bool, error)
|
||||
deleteChange func(string, string, os.FileInfo) (string, error)
|
||||
}
|
||||
|
||||
// diffDirChanges walks the diff directory and compares changes against the lower.
|
||||
func diffDirChanges(ctx context.Context, changes changeFn, lower string, o *diffDirOptions) error {
|
||||
// diffDirChanges walks the diff directory and compares changes against the base.
|
||||
func diffDirChanges(ctx context.Context, changeFn ChangeFunc, base string, o *diffDirOptions) error {
|
||||
changedDirs := make(map[string]struct{})
|
||||
return filepath.Walk(o.diffDir, func(path string, f os.FileInfo, err error) error {
|
||||
if err != nil {
|
||||
|
@ -152,9 +150,7 @@ func diffDirChanges(ctx context.Context, changes changeFn, lower string, o *diff
|
|||
}
|
||||
}
|
||||
|
||||
change := Change{
|
||||
Path: path,
|
||||
}
|
||||
var kind ChangeKind
|
||||
|
||||
deletedFile, err := o.deleteChange(o.diffDir, path, f)
|
||||
if err != nil {
|
||||
|
@ -163,20 +159,20 @@ func diffDirChanges(ctx context.Context, changes changeFn, lower string, o *diff
|
|||
|
||||
// Find out what kind of modification happened
|
||||
if deletedFile != "" {
|
||||
change.Path = deletedFile
|
||||
change.Kind = ChangeKindDelete
|
||||
path = deletedFile
|
||||
kind = ChangeKindDelete
|
||||
f = nil
|
||||
} else {
|
||||
// Otherwise, the file was added
|
||||
change.Kind = ChangeKindAdd
|
||||
kind = ChangeKindAdd
|
||||
|
||||
// ...Unless it already existed in a lower, in which case, it's a modification
|
||||
stat, err := os.Stat(filepath.Join(lower, path))
|
||||
// ...Unless it already existed in a base, in which case, it's a modification
|
||||
stat, err := os.Stat(filepath.Join(base, path))
|
||||
if err != nil && !os.IsNotExist(err) {
|
||||
return err
|
||||
}
|
||||
if err == nil {
|
||||
// The file existed in the lower, so that's a modification
|
||||
// The file existed in the base, so that's a modification
|
||||
|
||||
// However, if it's a directory, maybe it wasn't actually modified.
|
||||
// If you modify /foo/bar/baz, then /foo will be part of the changed files only because it's the parent of bar
|
||||
|
@ -186,7 +182,7 @@ func diffDirChanges(ctx context.Context, changes changeFn, lower string, o *diff
|
|||
return nil
|
||||
}
|
||||
}
|
||||
change.Kind = ChangeKindModify
|
||||
kind = ChangeKindModify
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -197,30 +193,23 @@ func diffDirChanges(ctx context.Context, changes changeFn, lower string, o *diff
|
|||
if f.IsDir() {
|
||||
changedDirs[path] = struct{}{}
|
||||
}
|
||||
if change.Kind == ChangeKindAdd || change.Kind == ChangeKindDelete {
|
||||
if kind == ChangeKindAdd || kind == ChangeKindDelete {
|
||||
parent := filepath.Dir(path)
|
||||
if _, ok := changedDirs[parent]; !ok && parent != "/" {
|
||||
pi, err := os.Stat(filepath.Join(o.diffDir, parent))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
dirChange := Change{
|
||||
Path: parent,
|
||||
Kind: ChangeKindModify,
|
||||
}
|
||||
if err := changes(dirChange, pi); err != nil {
|
||||
if err := changeFn(ChangeKindModify, parent, pi, err); err != nil {
|
||||
return err
|
||||
}
|
||||
changedDirs[parent] = struct{}{}
|
||||
}
|
||||
}
|
||||
|
||||
return changes(change, f)
|
||||
return changeFn(kind, path, f, nil)
|
||||
})
|
||||
}
|
||||
|
||||
// doubleWalkDiff walks both directories to create a diff
|
||||
func doubleWalkDiff(ctx context.Context, changes changeFn, upper, lower string) (err error) {
|
||||
func doubleWalkDiff(ctx context.Context, changeFn ChangeFunc, a, b string) (err error) {
|
||||
g, ctx := errgroup.WithContext(ctx)
|
||||
|
||||
var (
|
||||
|
@ -232,11 +221,11 @@ func doubleWalkDiff(ctx context.Context, changes changeFn, upper, lower string)
|
|||
)
|
||||
g.Go(func() error {
|
||||
defer close(c1)
|
||||
return pathWalk(ctx, lower, c1)
|
||||
return pathWalk(ctx, a, c1)
|
||||
})
|
||||
g.Go(func() error {
|
||||
defer close(c2)
|
||||
return pathWalk(ctx, upper, c2)
|
||||
return pathWalk(ctx, b, c2)
|
||||
})
|
||||
g.Go(func() error {
|
||||
for c1 != nil || c2 != nil {
|
||||
|
@ -264,8 +253,8 @@ func doubleWalkDiff(ctx context.Context, changes changeFn, upper, lower string)
|
|||
}
|
||||
|
||||
var f os.FileInfo
|
||||
c := pathChange(f1, f2)
|
||||
switch c.Kind {
|
||||
k, p := pathChange(f1, f2)
|
||||
switch k {
|
||||
case ChangeKindAdd:
|
||||
if rmdir != "" {
|
||||
rmdir = ""
|
||||
|
@ -301,7 +290,7 @@ func doubleWalkDiff(ctx context.Context, changes changeFn, upper, lower string)
|
|||
continue
|
||||
}
|
||||
}
|
||||
if err := changes(c, f); err != nil {
|
||||
if err := changeFn(k, p, f, nil); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
|
165
fs/diff_test.go
165
fs/diff_test.go
|
@ -21,21 +21,21 @@ import (
|
|||
// - hardlink test
|
||||
|
||||
func TestSimpleDiff(t *testing.T) {
|
||||
l1 := fstest.MultiApply(
|
||||
fstest.CreateDirectory("/etc", 0755),
|
||||
fstest.NewTestFile("/etc/hosts", []byte("mydomain 10.0.0.1"), 0644),
|
||||
fstest.NewTestFile("/etc/profile", []byte("PATH=/usr/bin"), 0644),
|
||||
fstest.NewTestFile("/etc/unchanged", []byte("PATH=/usr/bin"), 0644),
|
||||
fstest.NewTestFile("/etc/unexpected", []byte("#!/bin/sh"), 0644),
|
||||
l1 := fstest.Apply(
|
||||
fstest.CreateDir("/etc", 0755),
|
||||
fstest.CreateFile("/etc/hosts", []byte("mydomain 10.0.0.1"), 0644),
|
||||
fstest.CreateFile("/etc/profile", []byte("PATH=/usr/bin"), 0644),
|
||||
fstest.CreateFile("/etc/unchanged", []byte("PATH=/usr/bin"), 0644),
|
||||
fstest.CreateFile("/etc/unexpected", []byte("#!/bin/sh"), 0644),
|
||||
)
|
||||
l2 := fstest.MultiApply(
|
||||
fstest.NewTestFile("/etc/hosts", []byte("mydomain 10.0.0.120"), 0644),
|
||||
fstest.NewTestFile("/etc/profile", []byte("PATH=/usr/bin"), 0666),
|
||||
fstest.CreateDirectory("/root", 0700),
|
||||
fstest.NewTestFile("/root/.bashrc", []byte("PATH=/usr/sbin:/usr/bin"), 0644),
|
||||
l2 := fstest.Apply(
|
||||
fstest.CreateFile("/etc/hosts", []byte("mydomain 10.0.0.120"), 0644),
|
||||
fstest.CreateFile("/etc/profile", []byte("PATH=/usr/bin"), 0666),
|
||||
fstest.CreateDir("/root", 0700),
|
||||
fstest.CreateFile("/root/.bashrc", []byte("PATH=/usr/sbin:/usr/bin"), 0644),
|
||||
fstest.RemoveFile("/etc/unexpected"),
|
||||
)
|
||||
diff := []Change{
|
||||
diff := []testChange{
|
||||
Modify("/etc/hosts"),
|
||||
Modify("/etc/profile"),
|
||||
Delete("/etc/unexpected"),
|
||||
|
@ -49,18 +49,18 @@ func TestSimpleDiff(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestDirectoryReplace(t *testing.T) {
|
||||
l1 := fstest.MultiApply(
|
||||
fstest.CreateDirectory("/dir1", 0755),
|
||||
fstest.NewTestFile("/dir1/f1", []byte("#####"), 0644),
|
||||
fstest.CreateDirectory("/dir1/f2", 0755),
|
||||
fstest.NewTestFile("/dir1/f2/f3", []byte("#!/bin/sh"), 0644),
|
||||
l1 := fstest.Apply(
|
||||
fstest.CreateDir("/dir1", 0755),
|
||||
fstest.CreateFile("/dir1/f1", []byte("#####"), 0644),
|
||||
fstest.CreateDir("/dir1/f2", 0755),
|
||||
fstest.CreateFile("/dir1/f2/f3", []byte("#!/bin/sh"), 0644),
|
||||
)
|
||||
l2 := fstest.MultiApply(
|
||||
fstest.NewTestFile("/dir1/f11", []byte("#New file here"), 0644),
|
||||
l2 := fstest.Apply(
|
||||
fstest.CreateFile("/dir1/f11", []byte("#New file here"), 0644),
|
||||
fstest.RemoveFile("/dir1/f2"),
|
||||
fstest.NewTestFile("/dir1/f2", []byte("Now file"), 0666),
|
||||
fstest.CreateFile("/dir1/f2", []byte("Now file"), 0666),
|
||||
)
|
||||
diff := []Change{
|
||||
diff := []testChange{
|
||||
Add("/dir1/f11"),
|
||||
Modify("/dir1/f2"),
|
||||
}
|
||||
|
@ -71,15 +71,15 @@ func TestDirectoryReplace(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestRemoveDirectoryTree(t *testing.T) {
|
||||
l1 := fstest.MultiApply(
|
||||
fstest.CreateDirectory("/dir1/dir2/dir3", 0755),
|
||||
fstest.NewTestFile("/dir1/f1", []byte("f1"), 0644),
|
||||
fstest.NewTestFile("/dir1/dir2/f2", []byte("f2"), 0644),
|
||||
l1 := fstest.Apply(
|
||||
fstest.CreateDir("/dir1/dir2/dir3", 0755),
|
||||
fstest.CreateFile("/dir1/f1", []byte("f1"), 0644),
|
||||
fstest.CreateFile("/dir1/dir2/f2", []byte("f2"), 0644),
|
||||
)
|
||||
l2 := fstest.MultiApply(
|
||||
l2 := fstest.Apply(
|
||||
fstest.RemoveFile("/dir1"),
|
||||
)
|
||||
diff := []Change{
|
||||
diff := []testChange{
|
||||
Delete("/dir1"),
|
||||
}
|
||||
|
||||
|
@ -89,15 +89,15 @@ func TestRemoveDirectoryTree(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestFileReplace(t *testing.T) {
|
||||
l1 := fstest.MultiApply(
|
||||
fstest.NewTestFile("/dir1", []byte("a file, not a directory"), 0644),
|
||||
l1 := fstest.Apply(
|
||||
fstest.CreateFile("/dir1", []byte("a file, not a directory"), 0644),
|
||||
)
|
||||
l2 := fstest.MultiApply(
|
||||
l2 := fstest.Apply(
|
||||
fstest.RemoveFile("/dir1"),
|
||||
fstest.CreateDirectory("/dir1/dir2", 0755),
|
||||
fstest.NewTestFile("/dir1/dir2/f1", []byte("also a file"), 0644),
|
||||
fstest.CreateDir("/dir1/dir2", 0755),
|
||||
fstest.CreateFile("/dir1/dir2/f1", []byte("also a file"), 0644),
|
||||
)
|
||||
diff := []Change{
|
||||
diff := []testChange{
|
||||
Modify("/dir1"),
|
||||
Add("/dir1/dir2"),
|
||||
Add("/dir1/dir2/f1"),
|
||||
|
@ -112,31 +112,31 @@ func TestUpdateWithSameTime(t *testing.T) {
|
|||
tt := time.Now().Truncate(time.Second)
|
||||
t1 := tt.Add(5 * time.Nanosecond)
|
||||
t2 := tt.Add(6 * time.Nanosecond)
|
||||
l1 := fstest.MultiApply(
|
||||
fstest.NewTestFile("/file-modified-time", []byte("1"), 0644),
|
||||
l1 := fstest.Apply(
|
||||
fstest.CreateFile("/file-modified-time", []byte("1"), 0644),
|
||||
fstest.Chtime("/file-modified-time", t1),
|
||||
fstest.NewTestFile("/file-no-change", []byte("1"), 0644),
|
||||
fstest.CreateFile("/file-no-change", []byte("1"), 0644),
|
||||
fstest.Chtime("/file-no-change", t1),
|
||||
fstest.NewTestFile("/file-same-time", []byte("1"), 0644),
|
||||
fstest.CreateFile("/file-same-time", []byte("1"), 0644),
|
||||
fstest.Chtime("/file-same-time", t1),
|
||||
fstest.NewTestFile("/file-truncated-time-1", []byte("1"), 0644),
|
||||
fstest.CreateFile("/file-truncated-time-1", []byte("1"), 0644),
|
||||
fstest.Chtime("/file-truncated-time-1", t1),
|
||||
fstest.NewTestFile("/file-truncated-time-2", []byte("1"), 0644),
|
||||
fstest.CreateFile("/file-truncated-time-2", []byte("1"), 0644),
|
||||
fstest.Chtime("/file-truncated-time-2", tt),
|
||||
)
|
||||
l2 := fstest.MultiApply(
|
||||
fstest.NewTestFile("/file-modified-time", []byte("2"), 0644),
|
||||
l2 := fstest.Apply(
|
||||
fstest.CreateFile("/file-modified-time", []byte("2"), 0644),
|
||||
fstest.Chtime("/file-modified-time", t2),
|
||||
fstest.NewTestFile("/file-no-change", []byte("1"), 0644),
|
||||
fstest.CreateFile("/file-no-change", []byte("1"), 0644),
|
||||
fstest.Chtime("/file-no-change", tt), // use truncated time, should be regarded as no change
|
||||
fstest.NewTestFile("/file-same-time", []byte("2"), 0644),
|
||||
fstest.CreateFile("/file-same-time", []byte("2"), 0644),
|
||||
fstest.Chtime("/file-same-time", t1),
|
||||
fstest.NewTestFile("/file-truncated-time-1", []byte("2"), 0644),
|
||||
fstest.CreateFile("/file-truncated-time-1", []byte("2"), 0644),
|
||||
fstest.Chtime("/file-truncated-time-1", tt),
|
||||
fstest.NewTestFile("/file-truncated-time-2", []byte("2"), 0644),
|
||||
fstest.CreateFile("/file-truncated-time-2", []byte("2"), 0644),
|
||||
fstest.Chtime("/file-truncated-time-2", tt),
|
||||
)
|
||||
diff := []Change{
|
||||
diff := []testChange{
|
||||
// "/file-same-time" excluded because matching non-zero nanosecond values
|
||||
Modify("/file-modified-time"),
|
||||
Modify("/file-truncated-time-1"),
|
||||
|
@ -148,7 +148,7 @@ func TestUpdateWithSameTime(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func testDiffWithBase(base, diff fstest.Applier, expected []Change) error {
|
||||
func testDiffWithBase(base, diff fstest.Applier, expected []testChange) error {
|
||||
t1, err := ioutil.TempDir("", "diff-with-base-lower-")
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to create temp dir")
|
||||
|
@ -160,7 +160,7 @@ func testDiffWithBase(base, diff fstest.Applier, expected []Change) error {
|
|||
}
|
||||
defer os.RemoveAll(t2)
|
||||
|
||||
if err := base(t1); err != nil {
|
||||
if err := base.Apply(t1); err != nil {
|
||||
return errors.Wrap(err, "failed to apply base filesytem")
|
||||
}
|
||||
|
||||
|
@ -168,11 +168,11 @@ func testDiffWithBase(base, diff fstest.Applier, expected []Change) error {
|
|||
return errors.Wrap(err, "failed to copy base directory")
|
||||
}
|
||||
|
||||
if err := diff(t2); err != nil {
|
||||
if err := diff.Apply(t2); err != nil {
|
||||
return errors.Wrap(err, "failed to apply diff filesystem")
|
||||
}
|
||||
|
||||
changes, err := collectChanges(t2, t1)
|
||||
changes, err := collectChanges(t1, t2)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to collect changes")
|
||||
}
|
||||
|
@ -181,14 +181,14 @@ func testDiffWithBase(base, diff fstest.Applier, expected []Change) error {
|
|||
}
|
||||
|
||||
func TestBaseDirectoryChanges(t *testing.T) {
|
||||
apply := fstest.MultiApply(
|
||||
fstest.CreateDirectory("/etc", 0755),
|
||||
fstest.NewTestFile("/etc/hosts", []byte("mydomain 10.0.0.1"), 0644),
|
||||
fstest.NewTestFile("/etc/profile", []byte("PATH=/usr/bin"), 0644),
|
||||
fstest.CreateDirectory("/root", 0700),
|
||||
fstest.NewTestFile("/root/.bashrc", []byte("PATH=/usr/sbin:/usr/bin"), 0644),
|
||||
apply := fstest.Apply(
|
||||
fstest.CreateDir("/etc", 0755),
|
||||
fstest.CreateFile("/etc/hosts", []byte("mydomain 10.0.0.1"), 0644),
|
||||
fstest.CreateFile("/etc/profile", []byte("PATH=/usr/bin"), 0644),
|
||||
fstest.CreateDir("/root", 0700),
|
||||
fstest.CreateFile("/root/.bashrc", []byte("PATH=/usr/sbin:/usr/bin"), 0644),
|
||||
)
|
||||
changes := []Change{
|
||||
changes := []testChange{
|
||||
Add("/etc"),
|
||||
Add("/etc/hosts"),
|
||||
Add("/etc/profile"),
|
||||
|
@ -201,18 +201,18 @@ func TestBaseDirectoryChanges(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func testDiffWithoutBase(apply fstest.Applier, expected []Change) error {
|
||||
func testDiffWithoutBase(apply fstest.Applier, expected []testChange) error {
|
||||
tmp, err := ioutil.TempDir("", "diff-without-base-")
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to create temp dir")
|
||||
}
|
||||
defer os.RemoveAll(tmp)
|
||||
|
||||
if err := apply(tmp); err != nil {
|
||||
if err := apply.Apply(tmp); err != nil {
|
||||
return errors.Wrap(err, "failed to apply filesytem changes")
|
||||
}
|
||||
|
||||
changes, err := collectChanges(tmp, "")
|
||||
changes, err := collectChanges("", tmp)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to collect changes")
|
||||
}
|
||||
|
@ -220,13 +220,13 @@ func testDiffWithoutBase(apply fstest.Applier, expected []Change) error {
|
|||
return checkChanges(tmp, changes, expected)
|
||||
}
|
||||
|
||||
func checkChanges(root string, changes []testChange, expected []Change) error {
|
||||
func checkChanges(root string, changes, expected []testChange) error {
|
||||
if len(changes) != len(expected) {
|
||||
return errors.Errorf("Unexpected number of changes:\n%s", diffString(convertTestChanges(changes), expected))
|
||||
return errors.Errorf("Unexpected number of changes:\n%s", diffString(changes, expected))
|
||||
}
|
||||
for i := range changes {
|
||||
if changes[i].Path != expected[i].Path || changes[i].Kind != expected[i].Kind {
|
||||
return errors.Errorf("Unexpected change at %d:\n%s", i, diffString(convertTestChanges(changes), expected))
|
||||
return errors.Errorf("Unexpected change at %d:\n%s", i, diffString(changes, expected))
|
||||
}
|
||||
if changes[i].Kind != ChangeKindDelete {
|
||||
filename := filepath.Join(root, changes[i].Path)
|
||||
|
@ -254,18 +254,23 @@ func checkChanges(root string, changes []testChange, expected []Change) error {
|
|||
}
|
||||
|
||||
type testChange struct {
|
||||
Change
|
||||
Kind ChangeKind
|
||||
Path string
|
||||
FileInfo os.FileInfo
|
||||
Source string
|
||||
}
|
||||
|
||||
func collectChanges(upper, lower string) ([]testChange, error) {
|
||||
func collectChanges(a, b string) ([]testChange, error) {
|
||||
changes := []testChange{}
|
||||
err := Changes(context.Background(), upper, lower, func(c Change, f os.FileInfo) error {
|
||||
err := Changes(context.Background(), a, b, func(k ChangeKind, p string, f os.FileInfo, err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
changes = append(changes, testChange{
|
||||
Change: c,
|
||||
Kind: k,
|
||||
Path: p,
|
||||
FileInfo: f,
|
||||
Source: filepath.Join(upper, c.Path),
|
||||
Source: filepath.Join(b, p),
|
||||
})
|
||||
return nil
|
||||
})
|
||||
|
@ -276,20 +281,12 @@ func collectChanges(upper, lower string) ([]testChange, error) {
|
|||
return changes, nil
|
||||
}
|
||||
|
||||
func convertTestChanges(c []testChange) []Change {
|
||||
nc := make([]Change, len(c))
|
||||
for i := range c {
|
||||
nc[i] = c[i].Change
|
||||
}
|
||||
return nc
|
||||
}
|
||||
|
||||
func diffString(c1, c2 []Change) string {
|
||||
func diffString(c1, c2 []testChange) string {
|
||||
return fmt.Sprintf("got(%d):\n%s\nexpected(%d):\n%s", len(c1), changesString(c1), len(c2), changesString(c2))
|
||||
|
||||
}
|
||||
|
||||
func changesString(c []Change) string {
|
||||
func changesString(c []testChange) string {
|
||||
strs := make([]string, len(c))
|
||||
for i := range c {
|
||||
strs[i] = fmt.Sprintf("\t%s\t%s", c[i].Kind, c[i].Path)
|
||||
|
@ -297,22 +294,22 @@ func changesString(c []Change) string {
|
|||
return strings.Join(strs, "\n")
|
||||
}
|
||||
|
||||
func Add(p string) Change {
|
||||
return Change{
|
||||
func Add(p string) testChange {
|
||||
return testChange{
|
||||
Kind: ChangeKindAdd,
|
||||
Path: p,
|
||||
}
|
||||
}
|
||||
|
||||
func Delete(p string) Change {
|
||||
return Change{
|
||||
func Delete(p string) testChange {
|
||||
return testChange{
|
||||
Kind: ChangeKindDelete,
|
||||
Path: p,
|
||||
}
|
||||
}
|
||||
|
||||
func Modify(p string) Change {
|
||||
return Change{
|
||||
func Modify(p string) testChange {
|
||||
return testChange{
|
||||
Kind: ChangeKindModify,
|
||||
Path: p,
|
||||
}
|
||||
|
|
|
@ -10,12 +10,20 @@ import (
|
|||
)
|
||||
|
||||
// Applier applies single file changes
|
||||
type Applier func(root string) error
|
||||
type Applier interface {
|
||||
Apply(root string) error
|
||||
}
|
||||
|
||||
// NewTestFile returns a file applier which creates a file as the
|
||||
type applyFn func(root string) error
|
||||
|
||||
func (a applyFn) Apply(root string) error {
|
||||
return a(root)
|
||||
}
|
||||
|
||||
// CreateFile returns a file applier which creates a file as the
|
||||
// provided name with the given content and permission.
|
||||
func NewTestFile(name string, content []byte, perm os.FileMode) Applier {
|
||||
return func(root string) error {
|
||||
func CreateFile(name string, content []byte, perm os.FileMode) Applier {
|
||||
return applyFn(func(root string) error {
|
||||
fullPath := filepath.Join(root, name)
|
||||
if err := ioutil.WriteFile(fullPath, content, perm); err != nil {
|
||||
return err
|
||||
|
@ -26,67 +34,67 @@ func NewTestFile(name string, content []byte, perm os.FileMode) Applier {
|
|||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// RemoveFile returns a file applier which removes the provided file name
|
||||
func RemoveFile(name string) Applier {
|
||||
return func(root string) error {
|
||||
return applyFn(func(root string) error {
|
||||
return os.RemoveAll(filepath.Join(root, name))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// CreateDirectory returns a file applier to create the directory with
|
||||
// CreateDir returns a file applier to create the directory with
|
||||
// the provided name and permission
|
||||
func CreateDirectory(name string, perm os.FileMode) Applier {
|
||||
return func(root string) error {
|
||||
func CreateDir(name string, perm os.FileMode) Applier {
|
||||
return applyFn(func(root string) error {
|
||||
fullPath := filepath.Join(root, name)
|
||||
if err := os.MkdirAll(fullPath, perm); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// Rename returns a file applier which renames a file
|
||||
func Rename(old, new string) Applier {
|
||||
return func(root string) error {
|
||||
return applyFn(func(root string) error {
|
||||
return os.Rename(filepath.Join(root, old), filepath.Join(root, new))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// Chown returns a file applier which changes the ownership of a file
|
||||
func Chown(name string, uid, gid int) Applier {
|
||||
return func(root string) error {
|
||||
return applyFn(func(root string) error {
|
||||
return os.Chown(filepath.Join(root, name), uid, gid)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// Chtime changes access and mod time of file
|
||||
func Chtime(name string, t time.Time) Applier {
|
||||
return func(root string) error {
|
||||
return applyFn(func(root string) error {
|
||||
return os.Chtimes(filepath.Join(root, name), t, t)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// Symlink returns a file applier which creates a symbolic link
|
||||
func Symlink(oldname, newname string) Applier {
|
||||
return func(root string) error {
|
||||
return applyFn(func(root string) error {
|
||||
return os.Symlink(oldname, filepath.Join(root, newname))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// Link returns a file applier which creates a hard link
|
||||
func Link(oldname, newname string) Applier {
|
||||
return func(root string) error {
|
||||
return applyFn(func(root string) error {
|
||||
return os.Link(filepath.Join(root, oldname), filepath.Join(root, newname))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func SetXAttr(name, key, value string) Applier {
|
||||
return func(root string) error {
|
||||
return applyFn(func(root string) error {
|
||||
return sysx.LSetxattr(name, key, []byte(value), 0)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// TODO: Make platform specific, windows applier is always no-op
|
||||
|
@ -96,14 +104,14 @@ func SetXAttr(name, key, value string) Applier {
|
|||
// }
|
||||
//}
|
||||
|
||||
// MultiApply returns a new applier from the given appliers
|
||||
func MultiApply(appliers ...Applier) Applier {
|
||||
return func(root string) error {
|
||||
// Apply returns a new applier from the given appliers
|
||||
func Apply(appliers ...Applier) Applier {
|
||||
return applyFn(func(root string) error {
|
||||
for _, a := range appliers {
|
||||
if err := a(root); err != nil {
|
||||
if err := a.Apply(root); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
27
fs/path.go
27
fs/path.go
|
@ -15,42 +15,27 @@ type currentPath struct {
|
|||
fullPath string
|
||||
}
|
||||
|
||||
func pathChange(lower, upper *currentPath) Change {
|
||||
func pathChange(lower, upper *currentPath) (ChangeKind, string) {
|
||||
if lower == nil {
|
||||
if upper == nil {
|
||||
panic("cannot compare nil paths")
|
||||
}
|
||||
return Change{
|
||||
Kind: ChangeKindAdd,
|
||||
Path: upper.path,
|
||||
}
|
||||
return ChangeKindAdd, upper.path
|
||||
}
|
||||
if upper == nil {
|
||||
return Change{
|
||||
Kind: ChangeKindDelete,
|
||||
Path: lower.path,
|
||||
}
|
||||
return ChangeKindDelete, lower.path
|
||||
}
|
||||
// TODO: compare by directory
|
||||
|
||||
switch i := strings.Compare(lower.path, upper.path); {
|
||||
case i < 0:
|
||||
// File in lower that is not in upper
|
||||
return Change{
|
||||
Kind: ChangeKindDelete,
|
||||
Path: lower.path,
|
||||
}
|
||||
return ChangeKindDelete, lower.path
|
||||
case i > 0:
|
||||
// File in upper that is not in lower
|
||||
return Change{
|
||||
Kind: ChangeKindAdd,
|
||||
Path: upper.path,
|
||||
}
|
||||
return ChangeKindAdd, upper.path
|
||||
default:
|
||||
return Change{
|
||||
Kind: ChangeKindModify,
|
||||
Path: upper.path,
|
||||
}
|
||||
return ChangeKindModify, upper.path
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue