Add fs package
Add diff comparison with support for double walking two trees for comparison or single walking a diff tree. Single walking requires further implementation for specific mount types. Add directory copy function which is intended to provide fastest possible local copy of file system directories without hardlinking. Add test package to make creating filesystems for test easy and comparisons deep and informative. Signed-off-by: Derek McGowan <derek@mcgstyle.net> (github: dmcgowan)
This commit is contained in:
parent
5f08e609c0
commit
574862fd89
16 changed files with 1672 additions and 0 deletions
37
fs/fstest/compare.go
Normal file
37
fs/fstest/compare.go
Normal file
|
@ -0,0 +1,37 @@
|
|||
package fstest
|
||||
|
||||
import (
|
||||
"github.com/pkg/errors"
|
||||
"github.com/stevvooe/continuity"
|
||||
)
|
||||
|
||||
// CheckDirectoryEqual compares two directory paths to make sure that
|
||||
// the content of the directories is the same.
|
||||
func CheckDirectoryEqual(d1, d2 string) error {
|
||||
c1, err := continuity.NewContext(d1)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to build context")
|
||||
}
|
||||
|
||||
c2, err := continuity.NewContext(d2)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to build context")
|
||||
}
|
||||
|
||||
m1, err := continuity.BuildManifest(c1)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to build manifest")
|
||||
}
|
||||
|
||||
m2, err := continuity.BuildManifest(c2)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to build manifest")
|
||||
}
|
||||
|
||||
diff := diffResourceList(m1.Resources, m2.Resources)
|
||||
if diff.HasDiff() {
|
||||
return errors.Errorf("directory diff between %s and %s\n%s", d1, d2, diff.String())
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
189
fs/fstest/continuity_util.go
Normal file
189
fs/fstest/continuity_util.go
Normal file
|
@ -0,0 +1,189 @@
|
|||
package fstest
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
|
||||
"github.com/stevvooe/continuity"
|
||||
)
|
||||
|
||||
type resourceUpdate struct {
|
||||
Original continuity.Resource
|
||||
Updated continuity.Resource
|
||||
}
|
||||
|
||||
func (u resourceUpdate) String() string {
|
||||
return fmt.Sprintf("%s(mode: %o, uid: %s, gid: %s) -> %s(mode: %o, uid: %s, gid: %s)",
|
||||
u.Original.Path(), u.Original.Mode(), u.Original.UID(), u.Original.GID(),
|
||||
u.Updated.Path(), u.Updated.Mode(), u.Updated.UID(), u.Updated.GID(),
|
||||
)
|
||||
}
|
||||
|
||||
type resourceListDifference struct {
|
||||
Additions []continuity.Resource
|
||||
Deletions []continuity.Resource
|
||||
Updates []resourceUpdate
|
||||
}
|
||||
|
||||
func (l resourceListDifference) HasDiff() bool {
|
||||
return len(l.Additions) > 0 || len(l.Deletions) > 0 || len(l.Updates) > 0
|
||||
}
|
||||
|
||||
func (l resourceListDifference) String() string {
|
||||
buf := bytes.NewBuffer(nil)
|
||||
for _, add := range l.Additions {
|
||||
fmt.Fprintf(buf, "+ %s\n", add.Path())
|
||||
}
|
||||
for _, del := range l.Deletions {
|
||||
fmt.Fprintf(buf, "- %s\n", del.Path())
|
||||
}
|
||||
for _, upt := range l.Updates {
|
||||
fmt.Fprintf(buf, "~ %s\n", upt.String())
|
||||
}
|
||||
return string(buf.Bytes())
|
||||
}
|
||||
|
||||
// diffManifest compares two resource lists and returns the list
|
||||
// of adds updates and deletes, resource lists are not reordered
|
||||
// before doing difference.
|
||||
func diffResourceList(r1, r2 []continuity.Resource) resourceListDifference {
|
||||
i1 := 0
|
||||
i2 := 0
|
||||
var d resourceListDifference
|
||||
|
||||
for i1 < len(r1) && i2 < len(r2) {
|
||||
p1 := r1[i1].Path()
|
||||
p2 := r2[i2].Path()
|
||||
switch {
|
||||
case p1 < p2:
|
||||
d.Deletions = append(d.Deletions, r1[i1])
|
||||
i1++
|
||||
case p1 == p2:
|
||||
if !compareResource(r1[i1], r2[i2]) {
|
||||
d.Updates = append(d.Updates, resourceUpdate{
|
||||
Original: r1[i1],
|
||||
Updated: r2[i2],
|
||||
})
|
||||
}
|
||||
i1++
|
||||
i2++
|
||||
case p1 > p2:
|
||||
d.Additions = append(d.Additions, r2[i2])
|
||||
i2++
|
||||
}
|
||||
}
|
||||
|
||||
for i1 < len(r1) {
|
||||
d.Deletions = append(d.Deletions, r1[i1])
|
||||
i1++
|
||||
|
||||
}
|
||||
for i2 < len(r2) {
|
||||
d.Additions = append(d.Additions, r2[i2])
|
||||
i2++
|
||||
}
|
||||
|
||||
return d
|
||||
}
|
||||
|
||||
func compareResource(r1, r2 continuity.Resource) bool {
|
||||
if r1.Path() != r2.Path() {
|
||||
return false
|
||||
}
|
||||
if r1.Mode() != r2.Mode() {
|
||||
return false
|
||||
}
|
||||
if r1.UID() != r2.UID() {
|
||||
return false
|
||||
}
|
||||
if r1.GID() != r2.GID() {
|
||||
return false
|
||||
}
|
||||
|
||||
// TODO(dmcgowan): Check if is XAttrer
|
||||
|
||||
return compareResourceTypes(r1, r2)
|
||||
|
||||
}
|
||||
|
||||
func compareResourceTypes(r1, r2 continuity.Resource) bool {
|
||||
switch t1 := r1.(type) {
|
||||
case continuity.RegularFile:
|
||||
t2, ok := r2.(continuity.RegularFile)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
return compareRegularFile(t1, t2)
|
||||
case continuity.Directory:
|
||||
t2, ok := r2.(continuity.Directory)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
return compareDirectory(t1, t2)
|
||||
case continuity.SymLink:
|
||||
t2, ok := r2.(continuity.SymLink)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
return compareSymLink(t1, t2)
|
||||
case continuity.NamedPipe:
|
||||
t2, ok := r2.(continuity.NamedPipe)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
return compareNamedPipe(t1, t2)
|
||||
case continuity.Device:
|
||||
t2, ok := r2.(continuity.Device)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
return compareDevice(t1, t2)
|
||||
default:
|
||||
// TODO(dmcgowan): Should this panic?
|
||||
return r1 == r2
|
||||
}
|
||||
}
|
||||
|
||||
func compareRegularFile(r1, r2 continuity.RegularFile) bool {
|
||||
if r1.Size() != r2.Size() {
|
||||
return false
|
||||
}
|
||||
p1 := r1.Paths()
|
||||
p2 := r2.Paths()
|
||||
if len(p1) != len(p2) {
|
||||
return false
|
||||
}
|
||||
for i := range p1 {
|
||||
if p1[i] != p2[i] {
|
||||
return false
|
||||
}
|
||||
}
|
||||
d1 := r1.Digests()
|
||||
d2 := r2.Digests()
|
||||
if len(d1) != len(d2) {
|
||||
return false
|
||||
}
|
||||
for i := range d1 {
|
||||
if d1[i] != d2[i] {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func compareSymLink(r1, r2 continuity.SymLink) bool {
|
||||
return r1.Target() == r2.Target()
|
||||
}
|
||||
|
||||
func compareDirectory(r1, r2 continuity.Directory) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func compareNamedPipe(r1, r2 continuity.NamedPipe) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func compareDevice(r1, r2 continuity.Device) bool {
|
||||
return r1.Major() == r2.Major() && r1.Minor() == r2.Minor()
|
||||
}
|
109
fs/fstest/file.go
Normal file
109
fs/fstest/file.go
Normal file
|
@ -0,0 +1,109 @@
|
|||
package fstest
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"time"
|
||||
|
||||
"github.com/stevvooe/continuity/sysx"
|
||||
)
|
||||
|
||||
// Applier applies single file changes
|
||||
type Applier func(root string) error
|
||||
|
||||
// NewTestFile 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 {
|
||||
fullPath := filepath.Join(root, name)
|
||||
if err := ioutil.WriteFile(fullPath, content, perm); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := os.Chmod(fullPath, perm); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// RemoveFile returns a file applier which removes the provided file name
|
||||
func RemoveFile(name string) Applier {
|
||||
return func(root string) error {
|
||||
return os.RemoveAll(filepath.Join(root, name))
|
||||
}
|
||||
}
|
||||
|
||||
// CreateDirectory 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 {
|
||||
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 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 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 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 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 os.Link(filepath.Join(root, oldname), filepath.Join(root, newname))
|
||||
}
|
||||
}
|
||||
|
||||
func SetXAttr(name, key, value string) Applier {
|
||||
return func(root string) error {
|
||||
return sysx.LSetxattr(name, key, []byte(value), 0)
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Make platform specific, windows applier is always no-op
|
||||
//func Mknod(name string, mode int32, dev int) Applier {
|
||||
// return func(root string) error {
|
||||
// return return syscall.Mknod(path, mode, dev)
|
||||
// }
|
||||
//}
|
||||
|
||||
// MultiApply returns a new applier from the given appliers
|
||||
func MultiApply(appliers ...Applier) Applier {
|
||||
return func(root string) error {
|
||||
for _, a := range appliers {
|
||||
if err := a(root); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue