305 lines
8.5 KiB
Go
305 lines
8.5 KiB
Go
|
package fs
|
||
|
|
||
|
import (
|
||
|
"context"
|
||
|
"fmt"
|
||
|
"io/ioutil"
|
||
|
"os"
|
||
|
"path/filepath"
|
||
|
"strings"
|
||
|
"testing"
|
||
|
"time"
|
||
|
|
||
|
"github.com/docker/containerd/fs/fstest"
|
||
|
"github.com/pkg/errors"
|
||
|
)
|
||
|
|
||
|
// TODO: Additional tests
|
||
|
// - capability test (requires privilege)
|
||
|
// - chown test (requires privilege)
|
||
|
// - symlink test
|
||
|
// - 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),
|
||
|
)
|
||
|
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),
|
||
|
fstest.RemoveFile("/etc/unexpected"),
|
||
|
)
|
||
|
diff := []Change{
|
||
|
Modify("/etc/hosts"),
|
||
|
Modify("/etc/profile"),
|
||
|
Delete("/etc/unexpected"),
|
||
|
Add("/root"),
|
||
|
Add("/root/.bashrc"),
|
||
|
}
|
||
|
|
||
|
if err := testDiffWithBase(l1, l2, diff); err != nil {
|
||
|
t.Fatalf("Failed diff with base: %+v", err)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
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),
|
||
|
)
|
||
|
l2 := fstest.MultiApply(
|
||
|
fstest.NewTestFile("/dir1/f11", []byte("#New file here"), 0644),
|
||
|
fstest.RemoveFile("/dir1/f2"),
|
||
|
fstest.NewTestFile("/dir1/f2", []byte("Now file"), 0666),
|
||
|
)
|
||
|
diff := []Change{
|
||
|
Add("/dir1/f11"),
|
||
|
Modify("/dir1/f2"),
|
||
|
}
|
||
|
|
||
|
if err := testDiffWithBase(l1, l2, diff); err != nil {
|
||
|
t.Fatalf("Failed diff with base: %+v", err)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
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),
|
||
|
)
|
||
|
l2 := fstest.MultiApply(
|
||
|
fstest.RemoveFile("/dir1"),
|
||
|
)
|
||
|
diff := []Change{
|
||
|
Delete("/dir1"),
|
||
|
}
|
||
|
|
||
|
if err := testDiffWithBase(l1, l2, diff); err != nil {
|
||
|
t.Fatalf("Failed diff with base: %+v", err)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func TestFileReplace(t *testing.T) {
|
||
|
l1 := fstest.MultiApply(
|
||
|
fstest.NewTestFile("/dir1", []byte("a file, not a directory"), 0644),
|
||
|
)
|
||
|
l2 := fstest.MultiApply(
|
||
|
fstest.RemoveFile("/dir1"),
|
||
|
fstest.CreateDirectory("/dir1/dir2", 0755),
|
||
|
fstest.NewTestFile("/dir1/dir2/f1", []byte("also a file"), 0644),
|
||
|
)
|
||
|
diff := []Change{
|
||
|
Modify("/dir1"),
|
||
|
Add("/dir1/dir2"),
|
||
|
Add("/dir1/dir2/f1"),
|
||
|
}
|
||
|
|
||
|
if err := testDiffWithBase(l1, l2, diff); err != nil {
|
||
|
t.Fatalf("Failed diff with base: %+v", err)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
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),
|
||
|
fstest.Chtime("/file-modified-time", t1),
|
||
|
fstest.NewTestFile("/file-no-change", []byte("1"), 0644),
|
||
|
fstest.Chtime("/file-no-change", t1),
|
||
|
fstest.NewTestFile("/file-same-time", []byte("1"), 0644),
|
||
|
fstest.Chtime("/file-same-time", t1),
|
||
|
fstest.NewTestFile("/file-truncated-time-1", []byte("1"), 0644),
|
||
|
fstest.Chtime("/file-truncated-time-1", t1),
|
||
|
fstest.NewTestFile("/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),
|
||
|
fstest.Chtime("/file-modified-time", t2),
|
||
|
fstest.NewTestFile("/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.Chtime("/file-same-time", t1),
|
||
|
fstest.NewTestFile("/file-truncated-time-1", []byte("2"), 0644),
|
||
|
fstest.Chtime("/file-truncated-time-1", tt),
|
||
|
fstest.NewTestFile("/file-truncated-time-2", []byte("2"), 0644),
|
||
|
fstest.Chtime("/file-truncated-time-2", tt),
|
||
|
)
|
||
|
diff := []Change{
|
||
|
// "/file-same-time" excluded because matching non-zero nanosecond values
|
||
|
Modify("/file-modified-time"),
|
||
|
Modify("/file-truncated-time-1"),
|
||
|
Modify("/file-truncated-time-2"),
|
||
|
}
|
||
|
|
||
|
if err := testDiffWithBase(l1, l2, diff); err != nil {
|
||
|
t.Fatalf("Failed diff with base: %+v", err)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func testDiffWithBase(base, diff fstest.Applier, expected []Change) error {
|
||
|
t1, err := ioutil.TempDir("", "diff-with-base-lower-")
|
||
|
if err != nil {
|
||
|
return errors.Wrap(err, "failed to create temp dir")
|
||
|
}
|
||
|
defer os.RemoveAll(t1)
|
||
|
t2, err := ioutil.TempDir("", "diff-with-base-upper-")
|
||
|
if err != nil {
|
||
|
return errors.Wrap(err, "failed to create temp dir")
|
||
|
}
|
||
|
defer os.RemoveAll(t2)
|
||
|
|
||
|
if err := base(t1); err != nil {
|
||
|
return errors.Wrap(err, "failed to apply base filesytem")
|
||
|
}
|
||
|
|
||
|
if err := CopyDirectory(t2, t1); err != nil {
|
||
|
return errors.Wrap(err, "failed to copy base directory")
|
||
|
}
|
||
|
|
||
|
if err := diff(t2); err != nil {
|
||
|
return errors.Wrap(err, "failed to apply diff filesystem")
|
||
|
}
|
||
|
|
||
|
changes, err := collectChanges(t2, t1)
|
||
|
if err != nil {
|
||
|
return errors.Wrap(err, "failed to collect changes")
|
||
|
}
|
||
|
|
||
|
return checkChanges(t2, changes, expected)
|
||
|
}
|
||
|
|
||
|
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),
|
||
|
)
|
||
|
changes := []Change{
|
||
|
Add("/etc"),
|
||
|
Add("/etc/hosts"),
|
||
|
Add("/etc/profile"),
|
||
|
Add("/root"),
|
||
|
Add("/root/.bashrc"),
|
||
|
}
|
||
|
|
||
|
if err := testDiffWithoutBase(apply, changes); err != nil {
|
||
|
t.Fatalf("Failed diff without base: %+v", err)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func testDiffWithoutBase(apply fstest.Applier, expected []Change) 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 {
|
||
|
return errors.Wrap(err, "failed to apply filesytem changes")
|
||
|
}
|
||
|
|
||
|
changes, err := collectChanges(tmp, "")
|
||
|
if err != nil {
|
||
|
return errors.Wrap(err, "failed to collect changes")
|
||
|
}
|
||
|
|
||
|
return checkChanges(tmp, changes, expected)
|
||
|
}
|
||
|
|
||
|
func checkChanges(root string, changes, expected []Change) error {
|
||
|
if len(changes) != len(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(changes, expected))
|
||
|
}
|
||
|
if changes[i].Kind != ChangeKindDelete {
|
||
|
filename := filepath.Join(root, changes[i].Path)
|
||
|
efi, err := os.Stat(filename)
|
||
|
if err != nil {
|
||
|
return errors.Wrapf(err, "failed to stat %q", filename)
|
||
|
}
|
||
|
afi := changes[i].FileInfo
|
||
|
if afi.Size() != efi.Size() {
|
||
|
return errors.Errorf("Unexpected change size %d, %q has size %d", afi.Size(), filename, efi.Size())
|
||
|
}
|
||
|
if afi.Mode() != efi.Mode() {
|
||
|
return errors.Errorf("Unexpected change mode %s, %q has mode %s", afi.Mode(), filename, efi.Mode())
|
||
|
}
|
||
|
if afi.ModTime() != efi.ModTime() {
|
||
|
return errors.Errorf("Unexpected change modtime %s, %q has modtime %s", afi.ModTime(), filename, efi.ModTime())
|
||
|
}
|
||
|
if expected := filepath.Join(root, changes[i].Path); changes[i].Source != expected {
|
||
|
return errors.Errorf("Unexpected source path %s, expected %s", changes[i].Source, expected)
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
func collectChanges(upper, lower string) ([]Change, error) {
|
||
|
ctx, changeC := Changes(context.Background(), upper, lower)
|
||
|
changes := []Change{}
|
||
|
for {
|
||
|
select {
|
||
|
case <-ctx.Done():
|
||
|
return nil, ctx.Err()
|
||
|
case c, ok := <-changeC:
|
||
|
if !ok {
|
||
|
return changes, nil
|
||
|
}
|
||
|
changes = append(changes, c)
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func diffString(c1, c2 []Change) 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 {
|
||
|
strs := make([]string, len(c))
|
||
|
for i := range c {
|
||
|
strs[i] = fmt.Sprintf("\t%s\t%s", c[i].Kind, c[i].Path)
|
||
|
}
|
||
|
return strings.Join(strs, "\n")
|
||
|
}
|
||
|
|
||
|
func Add(p string) Change {
|
||
|
return Change{
|
||
|
Kind: ChangeKindAdd,
|
||
|
Path: p,
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func Delete(p string) Change {
|
||
|
return Change{
|
||
|
Kind: ChangeKindDelete,
|
||
|
Path: p,
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func Modify(p string) Change {
|
||
|
return Change{
|
||
|
Kind: ChangeKindModify,
|
||
|
Path: p,
|
||
|
}
|
||
|
}
|