Switch to github.com/golang/dep for vendoring

Signed-off-by: Mrunal Patel <mrunalp@gmail.com>
This commit is contained in:
Mrunal Patel 2017-01-31 16:45:59 -08:00
parent d6ab91be27
commit 8e5b17cf13
15431 changed files with 3971413 additions and 8881 deletions

View file

@ -220,7 +220,7 @@ func CompressStream(dest io.Writer, compression Compression) (io.WriteCloser, er
return writeBufWrapper, nil
case Bzip2, Xz:
// archive/bzip2 does not support writing, and there is no xz support at all
// However, this is not a problem as docker only currently generates gzipped tars
// However, this is not a problem as we only currently generates gzipped tars
return nil, fmt.Errorf("Unsupported compression format %s", (&compression).Extension())
default:
return nil, fmt.Errorf("Unsupported compression format %s", (&compression).Extension())

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,245 @@
// +build !windows
package archive
import (
"bytes"
"fmt"
"io/ioutil"
"os"
"path/filepath"
"syscall"
"testing"
"github.com/containers/storage/pkg/system"
)
func TestCanonicalTarNameForPath(t *testing.T) {
cases := []struct{ in, expected string }{
{"foo", "foo"},
{"foo/bar", "foo/bar"},
{"foo/dir/", "foo/dir/"},
}
for _, v := range cases {
if out, err := CanonicalTarNameForPath(v.in); err != nil {
t.Fatalf("cannot get canonical name for path: %s: %v", v.in, err)
} else if out != v.expected {
t.Fatalf("wrong canonical tar name. expected:%s got:%s", v.expected, out)
}
}
}
func TestCanonicalTarName(t *testing.T) {
cases := []struct {
in string
isDir bool
expected string
}{
{"foo", false, "foo"},
{"foo", true, "foo/"},
{"foo/bar", false, "foo/bar"},
{"foo/bar", true, "foo/bar/"},
}
for _, v := range cases {
if out, err := canonicalTarName(v.in, v.isDir); err != nil {
t.Fatalf("cannot get canonical name for path: %s: %v", v.in, err)
} else if out != v.expected {
t.Fatalf("wrong canonical tar name. expected:%s got:%s", v.expected, out)
}
}
}
func TestChmodTarEntry(t *testing.T) {
cases := []struct {
in, expected os.FileMode
}{
{0000, 0000},
{0777, 0777},
{0644, 0644},
{0755, 0755},
{0444, 0444},
}
for _, v := range cases {
if out := chmodTarEntry(v.in); out != v.expected {
t.Fatalf("wrong chmod. expected:%v got:%v", v.expected, out)
}
}
}
func TestTarWithHardLink(t *testing.T) {
origin, err := ioutil.TempDir("", "docker-test-tar-hardlink")
if err != nil {
t.Fatal(err)
}
defer os.RemoveAll(origin)
if err := ioutil.WriteFile(filepath.Join(origin, "1"), []byte("hello world"), 0700); err != nil {
t.Fatal(err)
}
if err := os.Link(filepath.Join(origin, "1"), filepath.Join(origin, "2")); err != nil {
t.Fatal(err)
}
var i1, i2 uint64
if i1, err = getNlink(filepath.Join(origin, "1")); err != nil {
t.Fatal(err)
}
// sanity check that we can hardlink
if i1 != 2 {
t.Skipf("skipping since hardlinks don't work here; expected 2 links, got %d", i1)
}
dest, err := ioutil.TempDir("", "docker-test-tar-hardlink-dest")
if err != nil {
t.Fatal(err)
}
defer os.RemoveAll(dest)
// we'll do this in two steps to separate failure
fh, err := Tar(origin, Uncompressed)
if err != nil {
t.Fatal(err)
}
// ensure we can read the whole thing with no error, before writing back out
buf, err := ioutil.ReadAll(fh)
if err != nil {
t.Fatal(err)
}
bRdr := bytes.NewReader(buf)
err = Untar(bRdr, dest, &TarOptions{Compression: Uncompressed})
if err != nil {
t.Fatal(err)
}
if i1, err = getInode(filepath.Join(dest, "1")); err != nil {
t.Fatal(err)
}
if i2, err = getInode(filepath.Join(dest, "2")); err != nil {
t.Fatal(err)
}
if i1 != i2 {
t.Errorf("expected matching inodes, but got %d and %d", i1, i2)
}
}
func getNlink(path string) (uint64, error) {
stat, err := os.Stat(path)
if err != nil {
return 0, err
}
statT, ok := stat.Sys().(*syscall.Stat_t)
if !ok {
return 0, fmt.Errorf("expected type *syscall.Stat_t, got %t", stat.Sys())
}
// We need this conversion on ARM64
return uint64(statT.Nlink), nil
}
func getInode(path string) (uint64, error) {
stat, err := os.Stat(path)
if err != nil {
return 0, err
}
statT, ok := stat.Sys().(*syscall.Stat_t)
if !ok {
return 0, fmt.Errorf("expected type *syscall.Stat_t, got %t", stat.Sys())
}
return statT.Ino, nil
}
func TestTarWithBlockCharFifo(t *testing.T) {
origin, err := ioutil.TempDir("", "docker-test-tar-hardlink")
if err != nil {
t.Fatal(err)
}
defer os.RemoveAll(origin)
if err := ioutil.WriteFile(filepath.Join(origin, "1"), []byte("hello world"), 0700); err != nil {
t.Fatal(err)
}
if err := system.Mknod(filepath.Join(origin, "2"), syscall.S_IFBLK, int(system.Mkdev(int64(12), int64(5)))); err != nil {
t.Fatal(err)
}
if err := system.Mknod(filepath.Join(origin, "3"), syscall.S_IFCHR, int(system.Mkdev(int64(12), int64(5)))); err != nil {
t.Fatal(err)
}
if err := system.Mknod(filepath.Join(origin, "4"), syscall.S_IFIFO, int(system.Mkdev(int64(12), int64(5)))); err != nil {
t.Fatal(err)
}
dest, err := ioutil.TempDir("", "docker-test-tar-hardlink-dest")
if err != nil {
t.Fatal(err)
}
defer os.RemoveAll(dest)
// we'll do this in two steps to separate failure
fh, err := Tar(origin, Uncompressed)
if err != nil {
t.Fatal(err)
}
// ensure we can read the whole thing with no error, before writing back out
buf, err := ioutil.ReadAll(fh)
if err != nil {
t.Fatal(err)
}
bRdr := bytes.NewReader(buf)
err = Untar(bRdr, dest, &TarOptions{Compression: Uncompressed})
if err != nil {
t.Fatal(err)
}
changes, err := ChangesDirs(origin, dest)
if err != nil {
t.Fatal(err)
}
if len(changes) > 0 {
t.Fatalf("Tar with special device (block, char, fifo) should keep them (recreate them when untar) : %v", changes)
}
}
// TestTarUntarWithXattr is Unix as Lsetxattr is not supported on Windows
func TestTarUntarWithXattr(t *testing.T) {
origin, err := ioutil.TempDir("", "docker-test-untar-origin")
if err != nil {
t.Fatal(err)
}
defer os.RemoveAll(origin)
if err := ioutil.WriteFile(filepath.Join(origin, "1"), []byte("hello world"), 0700); err != nil {
t.Fatal(err)
}
if err := ioutil.WriteFile(filepath.Join(origin, "2"), []byte("welcome!"), 0700); err != nil {
t.Fatal(err)
}
if err := ioutil.WriteFile(filepath.Join(origin, "3"), []byte("will be ignored"), 0700); err != nil {
t.Fatal(err)
}
if err := system.Lsetxattr(filepath.Join(origin, "2"), "security.capability", []byte{0x00}, 0); err != nil {
t.Fatal(err)
}
for _, c := range []Compression{
Uncompressed,
Gzip,
} {
changes, err := tarUntar(t, origin, &TarOptions{
Compression: c,
ExcludePatterns: []string{"3"},
})
if err != nil {
t.Fatalf("Error tar/untar for compression %s: %s", c.Extension(), err)
}
if len(changes) != 1 || changes[0].Path != "/3" {
t.Fatalf("Unexpected differences after tarUntar: %v", changes)
}
capability, _ := system.Lgetxattr(filepath.Join(origin, "2"), "security.capability")
if capability == nil && capability[0] != 0x00 {
t.Fatalf("Untar should have kept the 'security.capability' xattr.")
}
}
}

View file

@ -0,0 +1,91 @@
// +build windows
package archive
import (
"io/ioutil"
"os"
"path/filepath"
"testing"
)
func TestCopyFileWithInvalidDest(t *testing.T) {
// TODO Windows: This is currently failing. Not sure what has
// recently changed in CopyWithTar as used to pass. Further investigation
// is required.
t.Skip("Currently fails")
folder, err := ioutil.TempDir("", "docker-archive-test")
if err != nil {
t.Fatal(err)
}
defer os.RemoveAll(folder)
dest := "c:dest"
srcFolder := filepath.Join(folder, "src")
src := filepath.Join(folder, "src", "src")
err = os.MkdirAll(srcFolder, 0740)
if err != nil {
t.Fatal(err)
}
ioutil.WriteFile(src, []byte("content"), 0777)
err = CopyWithTar(src, dest)
if err == nil {
t.Fatalf("archiver.CopyWithTar should throw an error on invalid dest.")
}
}
func TestCanonicalTarNameForPath(t *testing.T) {
cases := []struct {
in, expected string
shouldFail bool
}{
{"foo", "foo", false},
{"foo/bar", "___", true}, // unix-styled windows path must fail
{`foo\bar`, "foo/bar", false},
}
for _, v := range cases {
if out, err := CanonicalTarNameForPath(v.in); err != nil && !v.shouldFail {
t.Fatalf("cannot get canonical name for path: %s: %v", v.in, err)
} else if v.shouldFail && err == nil {
t.Fatalf("canonical path call should have failed with error. in=%s out=%s", v.in, out)
} else if !v.shouldFail && out != v.expected {
t.Fatalf("wrong canonical tar name. expected:%s got:%s", v.expected, out)
}
}
}
func TestCanonicalTarName(t *testing.T) {
cases := []struct {
in string
isDir bool
expected string
}{
{"foo", false, "foo"},
{"foo", true, "foo/"},
{`foo\bar`, false, "foo/bar"},
{`foo\bar`, true, "foo/bar/"},
}
for _, v := range cases {
if out, err := canonicalTarName(v.in, v.isDir); err != nil {
t.Fatalf("cannot get canonical name for path: %s: %v", v.in, err)
} else if out != v.expected {
t.Fatalf("wrong canonical tar name. expected:%s got:%s", v.expected, out)
}
}
}
func TestChmodTarEntry(t *testing.T) {
cases := []struct {
in, expected os.FileMode
}{
{0000, 0111},
{0777, 0755},
{0644, 0755},
{0755, 0755},
{0444, 0555},
}
for _, v := range cases {
if out := chmodTarEntry(v.in); out != v.expected {
t.Fatalf("wrong chmod. expected:%v got:%v", v.expected, out)
}
}
}

View file

@ -0,0 +1,127 @@
package archive
import (
"archive/tar"
"fmt"
"io"
"io/ioutil"
"os"
"path"
"sort"
"testing"
)
func TestHardLinkOrder(t *testing.T) {
names := []string{"file1.txt", "file2.txt", "file3.txt"}
msg := []byte("Hey y'all")
// Create dir
src, err := ioutil.TempDir("", "docker-hardlink-test-src-")
if err != nil {
t.Fatal(err)
}
//defer os.RemoveAll(src)
for _, name := range names {
func() {
fh, err := os.Create(path.Join(src, name))
if err != nil {
t.Fatal(err)
}
defer fh.Close()
if _, err = fh.Write(msg); err != nil {
t.Fatal(err)
}
}()
}
// Create dest, with changes that includes hardlinks
dest, err := ioutil.TempDir("", "docker-hardlink-test-dest-")
if err != nil {
t.Fatal(err)
}
os.RemoveAll(dest) // we just want the name, at first
if err := copyDir(src, dest); err != nil {
t.Fatal(err)
}
defer os.RemoveAll(dest)
for _, name := range names {
for i := 0; i < 5; i++ {
if err := os.Link(path.Join(dest, name), path.Join(dest, fmt.Sprintf("%s.link%d", name, i))); err != nil {
t.Fatal(err)
}
}
}
// get changes
changes, err := ChangesDirs(dest, src)
if err != nil {
t.Fatal(err)
}
// sort
sort.Sort(changesByPath(changes))
// ExportChanges
ar, err := ExportChanges(dest, changes, nil, nil)
if err != nil {
t.Fatal(err)
}
hdrs, err := walkHeaders(ar)
if err != nil {
t.Fatal(err)
}
// reverse sort
sort.Sort(sort.Reverse(changesByPath(changes)))
// ExportChanges
arRev, err := ExportChanges(dest, changes, nil, nil)
if err != nil {
t.Fatal(err)
}
hdrsRev, err := walkHeaders(arRev)
if err != nil {
t.Fatal(err)
}
// line up the two sets
sort.Sort(tarHeaders(hdrs))
sort.Sort(tarHeaders(hdrsRev))
// compare Size and LinkName
for i := range hdrs {
if hdrs[i].Name != hdrsRev[i].Name {
t.Errorf("headers - expected name %q; but got %q", hdrs[i].Name, hdrsRev[i].Name)
}
if hdrs[i].Size != hdrsRev[i].Size {
t.Errorf("headers - %q expected size %d; but got %d", hdrs[i].Name, hdrs[i].Size, hdrsRev[i].Size)
}
if hdrs[i].Typeflag != hdrsRev[i].Typeflag {
t.Errorf("headers - %q expected type %d; but got %d", hdrs[i].Name, hdrs[i].Typeflag, hdrsRev[i].Typeflag)
}
if hdrs[i].Linkname != hdrsRev[i].Linkname {
t.Errorf("headers - %q expected linkname %q; but got %q", hdrs[i].Name, hdrs[i].Linkname, hdrsRev[i].Linkname)
}
}
}
type tarHeaders []tar.Header
func (th tarHeaders) Len() int { return len(th) }
func (th tarHeaders) Swap(i, j int) { th[j], th[i] = th[i], th[j] }
func (th tarHeaders) Less(i, j int) bool { return th[i].Name < th[j].Name }
func walkHeaders(r io.Reader) ([]tar.Header, error) {
t := tar.NewReader(r)
headers := []tar.Header{}
for {
hdr, err := t.Next()
if err != nil {
if err == io.EOF {
break
}
return headers, err
}
headers = append(headers, *hdr)
}
return headers, nil
}

View file

@ -0,0 +1,565 @@
package archive
import (
"io/ioutil"
"os"
"os/exec"
"path"
"runtime"
"sort"
"testing"
"time"
"github.com/containers/storage/pkg/system"
)
func max(x, y int) int {
if x >= y {
return x
}
return y
}
func copyDir(src, dst string) error {
cmd := exec.Command("cp", "-a", src, dst)
if err := cmd.Run(); err != nil {
return err
}
return nil
}
type FileType uint32
const (
Regular FileType = iota
Dir
Symlink
)
type FileData struct {
filetype FileType
path string
contents string
permissions os.FileMode
}
func createSampleDir(t *testing.T, root string) {
files := []FileData{
{Regular, "file1", "file1\n", 0600},
{Regular, "file2", "file2\n", 0666},
{Regular, "file3", "file3\n", 0404},
{Regular, "file4", "file4\n", 0600},
{Regular, "file5", "file5\n", 0600},
{Regular, "file6", "file6\n", 0600},
{Regular, "file7", "file7\n", 0600},
{Dir, "dir1", "", 0740},
{Regular, "dir1/file1-1", "file1-1\n", 01444},
{Regular, "dir1/file1-2", "file1-2\n", 0666},
{Dir, "dir2", "", 0700},
{Regular, "dir2/file2-1", "file2-1\n", 0666},
{Regular, "dir2/file2-2", "file2-2\n", 0666},
{Dir, "dir3", "", 0700},
{Regular, "dir3/file3-1", "file3-1\n", 0666},
{Regular, "dir3/file3-2", "file3-2\n", 0666},
{Dir, "dir4", "", 0700},
{Regular, "dir4/file3-1", "file4-1\n", 0666},
{Regular, "dir4/file3-2", "file4-2\n", 0666},
{Symlink, "symlink1", "target1", 0666},
{Symlink, "symlink2", "target2", 0666},
{Symlink, "symlink3", root + "/file1", 0666},
{Symlink, "symlink4", root + "/symlink3", 0666},
{Symlink, "dirSymlink", root + "/dir1", 0740},
}
now := time.Now()
for _, info := range files {
p := path.Join(root, info.path)
if info.filetype == Dir {
if err := os.MkdirAll(p, info.permissions); err != nil {
t.Fatal(err)
}
} else if info.filetype == Regular {
if err := ioutil.WriteFile(p, []byte(info.contents), info.permissions); err != nil {
t.Fatal(err)
}
} else if info.filetype == Symlink {
if err := os.Symlink(info.contents, p); err != nil {
t.Fatal(err)
}
}
if info.filetype != Symlink {
// Set a consistent ctime, atime for all files and dirs
if err := system.Chtimes(p, now, now); err != nil {
t.Fatal(err)
}
}
}
}
func TestChangeString(t *testing.T) {
modifiyChange := Change{"change", ChangeModify}
toString := modifiyChange.String()
if toString != "C change" {
t.Fatalf("String() of a change with ChangeModifiy Kind should have been %s but was %s", "C change", toString)
}
addChange := Change{"change", ChangeAdd}
toString = addChange.String()
if toString != "A change" {
t.Fatalf("String() of a change with ChangeAdd Kind should have been %s but was %s", "A change", toString)
}
deleteChange := Change{"change", ChangeDelete}
toString = deleteChange.String()
if toString != "D change" {
t.Fatalf("String() of a change with ChangeDelete Kind should have been %s but was %s", "D change", toString)
}
}
func TestChangesWithNoChanges(t *testing.T) {
// TODO Windows. There may be a way of running this, but turning off for now
// as createSampleDir uses symlinks.
if runtime.GOOS == "windows" {
t.Skip("symlinks on Windows")
}
rwLayer, err := ioutil.TempDir("", "docker-changes-test")
if err != nil {
t.Fatal(err)
}
defer os.RemoveAll(rwLayer)
layer, err := ioutil.TempDir("", "docker-changes-test-layer")
if err != nil {
t.Fatal(err)
}
defer os.RemoveAll(layer)
createSampleDir(t, layer)
changes, err := Changes([]string{layer}, rwLayer)
if err != nil {
t.Fatal(err)
}
if len(changes) != 0 {
t.Fatalf("Changes with no difference should have detect no changes, but detected %d", len(changes))
}
}
func TestChangesWithChanges(t *testing.T) {
// TODO Windows. There may be a way of running this, but turning off for now
// as createSampleDir uses symlinks.
if runtime.GOOS == "windows" {
t.Skip("symlinks on Windows")
}
// Mock the readonly layer
layer, err := ioutil.TempDir("", "docker-changes-test-layer")
if err != nil {
t.Fatal(err)
}
defer os.RemoveAll(layer)
createSampleDir(t, layer)
os.MkdirAll(path.Join(layer, "dir1/subfolder"), 0740)
// Mock the RW layer
rwLayer, err := ioutil.TempDir("", "docker-changes-test")
if err != nil {
t.Fatal(err)
}
defer os.RemoveAll(rwLayer)
// Create a folder in RW layer
dir1 := path.Join(rwLayer, "dir1")
os.MkdirAll(dir1, 0740)
deletedFile := path.Join(dir1, ".wh.file1-2")
ioutil.WriteFile(deletedFile, []byte{}, 0600)
modifiedFile := path.Join(dir1, "file1-1")
ioutil.WriteFile(modifiedFile, []byte{0x00}, 01444)
// Let's add a subfolder for a newFile
subfolder := path.Join(dir1, "subfolder")
os.MkdirAll(subfolder, 0740)
newFile := path.Join(subfolder, "newFile")
ioutil.WriteFile(newFile, []byte{}, 0740)
changes, err := Changes([]string{layer}, rwLayer)
if err != nil {
t.Fatal(err)
}
expectedChanges := []Change{
{"/dir1", ChangeModify},
{"/dir1/file1-1", ChangeModify},
{"/dir1/file1-2", ChangeDelete},
{"/dir1/subfolder", ChangeModify},
{"/dir1/subfolder/newFile", ChangeAdd},
}
checkChanges(expectedChanges, changes, t)
}
// See https://github.com/docker/docker/pull/13590
func TestChangesWithChangesGH13590(t *testing.T) {
// TODO Windows. There may be a way of running this, but turning off for now
// as createSampleDir uses symlinks.
if runtime.GOOS == "windows" {
t.Skip("symlinks on Windows")
}
baseLayer, err := ioutil.TempDir("", "docker-changes-test.")
defer os.RemoveAll(baseLayer)
dir3 := path.Join(baseLayer, "dir1/dir2/dir3")
os.MkdirAll(dir3, 07400)
file := path.Join(dir3, "file.txt")
ioutil.WriteFile(file, []byte("hello"), 0666)
layer, err := ioutil.TempDir("", "docker-changes-test2.")
defer os.RemoveAll(layer)
// Test creating a new file
if err := copyDir(baseLayer+"/dir1", layer+"/"); err != nil {
t.Fatalf("Cmd failed: %q", err)
}
os.Remove(path.Join(layer, "dir1/dir2/dir3/file.txt"))
file = path.Join(layer, "dir1/dir2/dir3/file1.txt")
ioutil.WriteFile(file, []byte("bye"), 0666)
changes, err := Changes([]string{baseLayer}, layer)
if err != nil {
t.Fatal(err)
}
expectedChanges := []Change{
{"/dir1/dir2/dir3", ChangeModify},
{"/dir1/dir2/dir3/file1.txt", ChangeAdd},
}
checkChanges(expectedChanges, changes, t)
// Now test changing a file
layer, err = ioutil.TempDir("", "docker-changes-test3.")
defer os.RemoveAll(layer)
if err := copyDir(baseLayer+"/dir1", layer+"/"); err != nil {
t.Fatalf("Cmd failed: %q", err)
}
file = path.Join(layer, "dir1/dir2/dir3/file.txt")
ioutil.WriteFile(file, []byte("bye"), 0666)
changes, err = Changes([]string{baseLayer}, layer)
if err != nil {
t.Fatal(err)
}
expectedChanges = []Change{
{"/dir1/dir2/dir3/file.txt", ChangeModify},
}
checkChanges(expectedChanges, changes, t)
}
// Create a directory, copy it, make sure we report no changes between the two
func TestChangesDirsEmpty(t *testing.T) {
// TODO Windows. There may be a way of running this, but turning off for now
// as createSampleDir uses symlinks.
if runtime.GOOS == "windows" {
t.Skip("symlinks on Windows")
}
src, err := ioutil.TempDir("", "docker-changes-test")
if err != nil {
t.Fatal(err)
}
defer os.RemoveAll(src)
createSampleDir(t, src)
dst := src + "-copy"
if err := copyDir(src, dst); err != nil {
t.Fatal(err)
}
defer os.RemoveAll(dst)
changes, err := ChangesDirs(dst, src)
if err != nil {
t.Fatal(err)
}
if len(changes) != 0 {
t.Fatalf("Reported changes for identical dirs: %v", changes)
}
os.RemoveAll(src)
os.RemoveAll(dst)
}
func mutateSampleDir(t *testing.T, root string) {
// Remove a regular file
if err := os.RemoveAll(path.Join(root, "file1")); err != nil {
t.Fatal(err)
}
// Remove a directory
if err := os.RemoveAll(path.Join(root, "dir1")); err != nil {
t.Fatal(err)
}
// Remove a symlink
if err := os.RemoveAll(path.Join(root, "symlink1")); err != nil {
t.Fatal(err)
}
// Rewrite a file
if err := ioutil.WriteFile(path.Join(root, "file2"), []byte("fileNN\n"), 0777); err != nil {
t.Fatal(err)
}
// Replace a file
if err := os.RemoveAll(path.Join(root, "file3")); err != nil {
t.Fatal(err)
}
if err := ioutil.WriteFile(path.Join(root, "file3"), []byte("fileMM\n"), 0404); err != nil {
t.Fatal(err)
}
// Touch file
if err := system.Chtimes(path.Join(root, "file4"), time.Now().Add(time.Second), time.Now().Add(time.Second)); err != nil {
t.Fatal(err)
}
// Replace file with dir
if err := os.RemoveAll(path.Join(root, "file5")); err != nil {
t.Fatal(err)
}
if err := os.MkdirAll(path.Join(root, "file5"), 0666); err != nil {
t.Fatal(err)
}
// Create new file
if err := ioutil.WriteFile(path.Join(root, "filenew"), []byte("filenew\n"), 0777); err != nil {
t.Fatal(err)
}
// Create new dir
if err := os.MkdirAll(path.Join(root, "dirnew"), 0766); err != nil {
t.Fatal(err)
}
// Create a new symlink
if err := os.Symlink("targetnew", path.Join(root, "symlinknew")); err != nil {
t.Fatal(err)
}
// Change a symlink
if err := os.RemoveAll(path.Join(root, "symlink2")); err != nil {
t.Fatal(err)
}
if err := os.Symlink("target2change", path.Join(root, "symlink2")); err != nil {
t.Fatal(err)
}
// Replace dir with file
if err := os.RemoveAll(path.Join(root, "dir2")); err != nil {
t.Fatal(err)
}
if err := ioutil.WriteFile(path.Join(root, "dir2"), []byte("dir2\n"), 0777); err != nil {
t.Fatal(err)
}
// Touch dir
if err := system.Chtimes(path.Join(root, "dir3"), time.Now().Add(time.Second), time.Now().Add(time.Second)); err != nil {
t.Fatal(err)
}
}
func TestChangesDirsMutated(t *testing.T) {
// TODO Windows. There may be a way of running this, but turning off for now
// as createSampleDir uses symlinks.
if runtime.GOOS == "windows" {
t.Skip("symlinks on Windows")
}
src, err := ioutil.TempDir("", "docker-changes-test")
if err != nil {
t.Fatal(err)
}
createSampleDir(t, src)
dst := src + "-copy"
if err := copyDir(src, dst); err != nil {
t.Fatal(err)
}
defer os.RemoveAll(src)
defer os.RemoveAll(dst)
mutateSampleDir(t, dst)
changes, err := ChangesDirs(dst, src)
if err != nil {
t.Fatal(err)
}
sort.Sort(changesByPath(changes))
expectedChanges := []Change{
{"/dir1", ChangeDelete},
{"/dir2", ChangeModify},
{"/dirnew", ChangeAdd},
{"/file1", ChangeDelete},
{"/file2", ChangeModify},
{"/file3", ChangeModify},
{"/file4", ChangeModify},
{"/file5", ChangeModify},
{"/filenew", ChangeAdd},
{"/symlink1", ChangeDelete},
{"/symlink2", ChangeModify},
{"/symlinknew", ChangeAdd},
}
for i := 0; i < max(len(changes), len(expectedChanges)); i++ {
if i >= len(expectedChanges) {
t.Fatalf("unexpected change %s\n", changes[i].String())
}
if i >= len(changes) {
t.Fatalf("no change for expected change %s\n", expectedChanges[i].String())
}
if changes[i].Path == expectedChanges[i].Path {
if changes[i] != expectedChanges[i] {
t.Fatalf("Wrong change for %s, expected %s, got %s\n", changes[i].Path, changes[i].String(), expectedChanges[i].String())
}
} else if changes[i].Path < expectedChanges[i].Path {
t.Fatalf("unexpected change %s\n", changes[i].String())
} else {
t.Fatalf("no change for expected change %s != %s\n", expectedChanges[i].String(), changes[i].String())
}
}
}
func TestApplyLayer(t *testing.T) {
// TODO Windows. There may be a way of running this, but turning off for now
// as createSampleDir uses symlinks.
if runtime.GOOS == "windows" {
t.Skip("symlinks on Windows")
}
src, err := ioutil.TempDir("", "docker-changes-test")
if err != nil {
t.Fatal(err)
}
createSampleDir(t, src)
defer os.RemoveAll(src)
dst := src + "-copy"
if err := copyDir(src, dst); err != nil {
t.Fatal(err)
}
mutateSampleDir(t, dst)
defer os.RemoveAll(dst)
changes, err := ChangesDirs(dst, src)
if err != nil {
t.Fatal(err)
}
layer, err := ExportChanges(dst, changes, nil, nil)
if err != nil {
t.Fatal(err)
}
layerCopy, err := NewTempArchive(layer, "")
if err != nil {
t.Fatal(err)
}
if _, err := ApplyLayer(src, layerCopy); err != nil {
t.Fatal(err)
}
changes2, err := ChangesDirs(src, dst)
if err != nil {
t.Fatal(err)
}
if len(changes2) != 0 {
t.Fatalf("Unexpected differences after reapplying mutation: %v", changes2)
}
}
func TestChangesSizeWithHardlinks(t *testing.T) {
// TODO Windows. There may be a way of running this, but turning off for now
// as createSampleDir uses symlinks.
if runtime.GOOS == "windows" {
t.Skip("hardlinks on Windows")
}
srcDir, err := ioutil.TempDir("", "docker-test-srcDir")
if err != nil {
t.Fatal(err)
}
defer os.RemoveAll(srcDir)
destDir, err := ioutil.TempDir("", "docker-test-destDir")
if err != nil {
t.Fatal(err)
}
defer os.RemoveAll(destDir)
creationSize, err := prepareUntarSourceDirectory(100, destDir, true)
if err != nil {
t.Fatal(err)
}
changes, err := ChangesDirs(destDir, srcDir)
if err != nil {
t.Fatal(err)
}
got := ChangesSize(destDir, changes)
if got != int64(creationSize) {
t.Errorf("Expected %d bytes of changes, got %d", creationSize, got)
}
}
func TestChangesSizeWithNoChanges(t *testing.T) {
size := ChangesSize("/tmp", nil)
if size != 0 {
t.Fatalf("ChangesSizes with no changes should be 0, was %d", size)
}
}
func TestChangesSizeWithOnlyDeleteChanges(t *testing.T) {
changes := []Change{
{Path: "deletedPath", Kind: ChangeDelete},
}
size := ChangesSize("/tmp", changes)
if size != 0 {
t.Fatalf("ChangesSizes with only delete changes should be 0, was %d", size)
}
}
func TestChangesSize(t *testing.T) {
parentPath, err := ioutil.TempDir("", "docker-changes-test")
defer os.RemoveAll(parentPath)
addition := path.Join(parentPath, "addition")
if err := ioutil.WriteFile(addition, []byte{0x01, 0x01, 0x01}, 0744); err != nil {
t.Fatal(err)
}
modification := path.Join(parentPath, "modification")
if err = ioutil.WriteFile(modification, []byte{0x01, 0x01, 0x01}, 0744); err != nil {
t.Fatal(err)
}
changes := []Change{
{Path: "addition", Kind: ChangeAdd},
{Path: "modification", Kind: ChangeModify},
}
size := ChangesSize(parentPath, changes)
if size != 6 {
t.Fatalf("Expected 6 bytes of changes, got %d", size)
}
}
func checkChanges(expectedChanges, changes []Change, t *testing.T) {
sort.Sort(changesByPath(expectedChanges))
sort.Sort(changesByPath(changes))
for i := 0; i < max(len(changes), len(expectedChanges)); i++ {
if i >= len(expectedChanges) {
t.Fatalf("unexpected change %s\n", changes[i].String())
}
if i >= len(changes) {
t.Fatalf("no change for expected change %s\n", expectedChanges[i].String())
}
if changes[i].Path == expectedChanges[i].Path {
if changes[i] != expectedChanges[i] {
t.Fatalf("Wrong change for %s, expected %s, got %s\n", changes[i].Path, changes[i].String(), expectedChanges[i].String())
}
} else if changes[i].Path < expectedChanges[i].Path {
t.Fatalf("unexpected change %s\n", changes[i].String())
} else {
t.Fatalf("no change for expected change %s != %s\n", expectedChanges[i].String(), changes[i].String())
}
}
}

View file

@ -0,0 +1,978 @@
// +build !windows
// TODO Windows: Some of these tests may be salvagable and portable to Windows.
package archive
import (
"bytes"
"crypto/sha256"
"encoding/hex"
"fmt"
"io"
"io/ioutil"
"os"
"path/filepath"
"strings"
"testing"
)
func removeAllPaths(paths ...string) {
for _, path := range paths {
os.RemoveAll(path)
}
}
func getTestTempDirs(t *testing.T) (tmpDirA, tmpDirB string) {
var err error
if tmpDirA, err = ioutil.TempDir("", "archive-copy-test"); err != nil {
t.Fatal(err)
}
if tmpDirB, err = ioutil.TempDir("", "archive-copy-test"); err != nil {
t.Fatal(err)
}
return
}
func isNotDir(err error) bool {
return strings.Contains(err.Error(), "not a directory")
}
func joinTrailingSep(pathElements ...string) string {
joined := filepath.Join(pathElements...)
return fmt.Sprintf("%s%c", joined, filepath.Separator)
}
func fileContentsEqual(t *testing.T, filenameA, filenameB string) (err error) {
t.Logf("checking for equal file contents: %q and %q\n", filenameA, filenameB)
fileA, err := os.Open(filenameA)
if err != nil {
return
}
defer fileA.Close()
fileB, err := os.Open(filenameB)
if err != nil {
return
}
defer fileB.Close()
hasher := sha256.New()
if _, err = io.Copy(hasher, fileA); err != nil {
return
}
hashA := hasher.Sum(nil)
hasher.Reset()
if _, err = io.Copy(hasher, fileB); err != nil {
return
}
hashB := hasher.Sum(nil)
if !bytes.Equal(hashA, hashB) {
err = fmt.Errorf("file content hashes not equal - expected %s, got %s", hex.EncodeToString(hashA), hex.EncodeToString(hashB))
}
return
}
func dirContentsEqual(t *testing.T, newDir, oldDir string) (err error) {
t.Logf("checking for equal directory contents: %q and %q\n", newDir, oldDir)
var changes []Change
if changes, err = ChangesDirs(newDir, oldDir); err != nil {
return
}
if len(changes) != 0 {
err = fmt.Errorf("expected no changes between directories, but got: %v", changes)
}
return
}
func logDirContents(t *testing.T, dirPath string) {
logWalkedPaths := filepath.WalkFunc(func(path string, info os.FileInfo, err error) error {
if err != nil {
t.Errorf("stat error for path %q: %s", path, err)
return nil
}
if info.IsDir() {
path = joinTrailingSep(path)
}
t.Logf("\t%s", path)
return nil
})
t.Logf("logging directory contents: %q", dirPath)
if err := filepath.Walk(dirPath, logWalkedPaths); err != nil {
t.Fatal(err)
}
}
func testCopyHelper(t *testing.T, srcPath, dstPath string) (err error) {
t.Logf("copying from %q to %q (not follow symbol link)", srcPath, dstPath)
return CopyResource(srcPath, dstPath, false)
}
func testCopyHelperFSym(t *testing.T, srcPath, dstPath string) (err error) {
t.Logf("copying from %q to %q (follow symbol link)", srcPath, dstPath)
return CopyResource(srcPath, dstPath, true)
}
// Basic assumptions about SRC and DST:
// 1. SRC must exist.
// 2. If SRC ends with a trailing separator, it must be a directory.
// 3. DST parent directory must exist.
// 4. If DST exists as a file, it must not end with a trailing separator.
// First get these easy error cases out of the way.
// Test for error when SRC does not exist.
func TestCopyErrSrcNotExists(t *testing.T) {
tmpDirA, tmpDirB := getTestTempDirs(t)
defer removeAllPaths(tmpDirA, tmpDirB)
if _, err := CopyInfoSourcePath(filepath.Join(tmpDirA, "file1"), false); !os.IsNotExist(err) {
t.Fatalf("expected IsNotExist error, but got %T: %s", err, err)
}
}
// Test for error when SRC ends in a trailing
// path separator but it exists as a file.
func TestCopyErrSrcNotDir(t *testing.T) {
tmpDirA, tmpDirB := getTestTempDirs(t)
defer removeAllPaths(tmpDirA, tmpDirB)
// Load A with some sample files and directories.
createSampleDir(t, tmpDirA)
if _, err := CopyInfoSourcePath(joinTrailingSep(tmpDirA, "file1"), false); !isNotDir(err) {
t.Fatalf("expected IsNotDir error, but got %T: %s", err, err)
}
}
// Test for error when SRC is a valid file or directory,
// but the DST parent directory does not exist.
func TestCopyErrDstParentNotExists(t *testing.T) {
tmpDirA, tmpDirB := getTestTempDirs(t)
defer removeAllPaths(tmpDirA, tmpDirB)
// Load A with some sample files and directories.
createSampleDir(t, tmpDirA)
srcInfo := CopyInfo{Path: filepath.Join(tmpDirA, "file1"), Exists: true, IsDir: false}
// Try with a file source.
content, err := TarResource(srcInfo)
if err != nil {
t.Fatalf("unexpected error %T: %s", err, err)
}
defer content.Close()
// Copy to a file whose parent does not exist.
if err = CopyTo(content, srcInfo, filepath.Join(tmpDirB, "fakeParentDir", "file1")); err == nil {
t.Fatal("expected IsNotExist error, but got nil instead")
}
if !os.IsNotExist(err) {
t.Fatalf("expected IsNotExist error, but got %T: %s", err, err)
}
// Try with a directory source.
srcInfo = CopyInfo{Path: filepath.Join(tmpDirA, "dir1"), Exists: true, IsDir: true}
content, err = TarResource(srcInfo)
if err != nil {
t.Fatalf("unexpected error %T: %s", err, err)
}
defer content.Close()
// Copy to a directory whose parent does not exist.
if err = CopyTo(content, srcInfo, joinTrailingSep(tmpDirB, "fakeParentDir", "fakeDstDir")); err == nil {
t.Fatal("expected IsNotExist error, but got nil instead")
}
if !os.IsNotExist(err) {
t.Fatalf("expected IsNotExist error, but got %T: %s", err, err)
}
}
// Test for error when DST ends in a trailing
// path separator but exists as a file.
func TestCopyErrDstNotDir(t *testing.T) {
tmpDirA, tmpDirB := getTestTempDirs(t)
defer removeAllPaths(tmpDirA, tmpDirB)
// Load A and B with some sample files and directories.
createSampleDir(t, tmpDirA)
createSampleDir(t, tmpDirB)
// Try with a file source.
srcInfo := CopyInfo{Path: filepath.Join(tmpDirA, "file1"), Exists: true, IsDir: false}
content, err := TarResource(srcInfo)
if err != nil {
t.Fatalf("unexpected error %T: %s", err, err)
}
defer content.Close()
if err = CopyTo(content, srcInfo, joinTrailingSep(tmpDirB, "file1")); err == nil {
t.Fatal("expected IsNotDir error, but got nil instead")
}
if !isNotDir(err) {
t.Fatalf("expected IsNotDir error, but got %T: %s", err, err)
}
// Try with a directory source.
srcInfo = CopyInfo{Path: filepath.Join(tmpDirA, "dir1"), Exists: true, IsDir: true}
content, err = TarResource(srcInfo)
if err != nil {
t.Fatalf("unexpected error %T: %s", err, err)
}
defer content.Close()
if err = CopyTo(content, srcInfo, joinTrailingSep(tmpDirB, "file1")); err == nil {
t.Fatal("expected IsNotDir error, but got nil instead")
}
if !isNotDir(err) {
t.Fatalf("expected IsNotDir error, but got %T: %s", err, err)
}
}
// Possibilities are reduced to the remaining 10 cases:
//
// case | srcIsDir | onlyDirContents | dstExists | dstIsDir | dstTrSep | action
// ===================================================================================================
// A | no | - | no | - | no | create file
// B | no | - | no | - | yes | error
// C | no | - | yes | no | - | overwrite file
// D | no | - | yes | yes | - | create file in dst dir
// E | yes | no | no | - | - | create dir, copy contents
// F | yes | no | yes | no | - | error
// G | yes | no | yes | yes | - | copy dir and contents
// H | yes | yes | no | - | - | create dir, copy contents
// I | yes | yes | yes | no | - | error
// J | yes | yes | yes | yes | - | copy dir contents
//
// A. SRC specifies a file and DST (no trailing path separator) doesn't
// exist. This should create a file with the name DST and copy the
// contents of the source file into it.
func TestCopyCaseA(t *testing.T) {
tmpDirA, tmpDirB := getTestTempDirs(t)
defer removeAllPaths(tmpDirA, tmpDirB)
// Load A with some sample files and directories.
createSampleDir(t, tmpDirA)
srcPath := filepath.Join(tmpDirA, "file1")
dstPath := filepath.Join(tmpDirB, "itWorks.txt")
var err error
if err = testCopyHelper(t, srcPath, dstPath); err != nil {
t.Fatalf("unexpected error %T: %s", err, err)
}
if err = fileContentsEqual(t, srcPath, dstPath); err != nil {
t.Fatal(err)
}
os.Remove(dstPath)
symlinkPath := filepath.Join(tmpDirA, "symlink3")
symlinkPath1 := filepath.Join(tmpDirA, "symlink4")
linkTarget := filepath.Join(tmpDirA, "file1")
if err = testCopyHelperFSym(t, symlinkPath, dstPath); err != nil {
t.Fatalf("unexpected error %T: %s", err, err)
}
if err = fileContentsEqual(t, linkTarget, dstPath); err != nil {
t.Fatal(err)
}
os.Remove(dstPath)
if err = testCopyHelperFSym(t, symlinkPath1, dstPath); err != nil {
t.Fatalf("unexpected error %T: %s", err, err)
}
if err = fileContentsEqual(t, linkTarget, dstPath); err != nil {
t.Fatal(err)
}
}
// B. SRC specifies a file and DST (with trailing path separator) doesn't
// exist. This should cause an error because the copy operation cannot
// create a directory when copying a single file.
func TestCopyCaseB(t *testing.T) {
tmpDirA, tmpDirB := getTestTempDirs(t)
defer removeAllPaths(tmpDirA, tmpDirB)
// Load A with some sample files and directories.
createSampleDir(t, tmpDirA)
srcPath := filepath.Join(tmpDirA, "file1")
dstDir := joinTrailingSep(tmpDirB, "testDir")
var err error
if err = testCopyHelper(t, srcPath, dstDir); err == nil {
t.Fatal("expected ErrDirNotExists error, but got nil instead")
}
if err != ErrDirNotExists {
t.Fatalf("expected ErrDirNotExists error, but got %T: %s", err, err)
}
symlinkPath := filepath.Join(tmpDirA, "symlink3")
if err = testCopyHelperFSym(t, symlinkPath, dstDir); err == nil {
t.Fatal("expected ErrDirNotExists error, but got nil instead")
}
if err != ErrDirNotExists {
t.Fatalf("expected ErrDirNotExists error, but got %T: %s", err, err)
}
}
// C. SRC specifies a file and DST exists as a file. This should overwrite
// the file at DST with the contents of the source file.
func TestCopyCaseC(t *testing.T) {
tmpDirA, tmpDirB := getTestTempDirs(t)
defer removeAllPaths(tmpDirA, tmpDirB)
// Load A and B with some sample files and directories.
createSampleDir(t, tmpDirA)
createSampleDir(t, tmpDirB)
srcPath := filepath.Join(tmpDirA, "file1")
dstPath := filepath.Join(tmpDirB, "file2")
var err error
// Ensure they start out different.
if err = fileContentsEqual(t, srcPath, dstPath); err == nil {
t.Fatal("expected different file contents")
}
if err = testCopyHelper(t, srcPath, dstPath); err != nil {
t.Fatalf("unexpected error %T: %s", err, err)
}
if err = fileContentsEqual(t, srcPath, dstPath); err != nil {
t.Fatal(err)
}
}
// C. Symbol link following version:
// SRC specifies a file and DST exists as a file. This should overwrite
// the file at DST with the contents of the source file.
func TestCopyCaseCFSym(t *testing.T) {
tmpDirA, tmpDirB := getTestTempDirs(t)
defer removeAllPaths(tmpDirA, tmpDirB)
// Load A and B with some sample files and directories.
createSampleDir(t, tmpDirA)
createSampleDir(t, tmpDirB)
symlinkPathBad := filepath.Join(tmpDirA, "symlink1")
symlinkPath := filepath.Join(tmpDirA, "symlink3")
linkTarget := filepath.Join(tmpDirA, "file1")
dstPath := filepath.Join(tmpDirB, "file2")
var err error
// first to test broken link
if err = testCopyHelperFSym(t, symlinkPathBad, dstPath); err == nil {
t.Fatalf("unexpected error %T: %s", err, err)
}
// test symbol link -> symbol link -> target
// Ensure they start out different.
if err = fileContentsEqual(t, linkTarget, dstPath); err == nil {
t.Fatal("expected different file contents")
}
if err = testCopyHelperFSym(t, symlinkPath, dstPath); err != nil {
t.Fatalf("unexpected error %T: %s", err, err)
}
if err = fileContentsEqual(t, linkTarget, dstPath); err != nil {
t.Fatal(err)
}
}
// D. SRC specifies a file and DST exists as a directory. This should place
// a copy of the source file inside it using the basename from SRC. Ensure
// this works whether DST has a trailing path separator or not.
func TestCopyCaseD(t *testing.T) {
tmpDirA, tmpDirB := getTestTempDirs(t)
defer removeAllPaths(tmpDirA, tmpDirB)
// Load A and B with some sample files and directories.
createSampleDir(t, tmpDirA)
createSampleDir(t, tmpDirB)
srcPath := filepath.Join(tmpDirA, "file1")
dstDir := filepath.Join(tmpDirB, "dir1")
dstPath := filepath.Join(dstDir, "file1")
var err error
// Ensure that dstPath doesn't exist.
if _, err = os.Stat(dstPath); !os.IsNotExist(err) {
t.Fatalf("did not expect dstPath %q to exist", dstPath)
}
if err = testCopyHelper(t, srcPath, dstDir); err != nil {
t.Fatalf("unexpected error %T: %s", err, err)
}
if err = fileContentsEqual(t, srcPath, dstPath); err != nil {
t.Fatal(err)
}
// Now try again but using a trailing path separator for dstDir.
if err = os.RemoveAll(dstDir); err != nil {
t.Fatalf("unable to remove dstDir: %s", err)
}
if err = os.MkdirAll(dstDir, os.FileMode(0755)); err != nil {
t.Fatalf("unable to make dstDir: %s", err)
}
dstDir = joinTrailingSep(tmpDirB, "dir1")
if err = testCopyHelper(t, srcPath, dstDir); err != nil {
t.Fatalf("unexpected error %T: %s", err, err)
}
if err = fileContentsEqual(t, srcPath, dstPath); err != nil {
t.Fatal(err)
}
}
// D. Symbol link following version:
// SRC specifies a file and DST exists as a directory. This should place
// a copy of the source file inside it using the basename from SRC. Ensure
// this works whether DST has a trailing path separator or not.
func TestCopyCaseDFSym(t *testing.T) {
tmpDirA, tmpDirB := getTestTempDirs(t)
defer removeAllPaths(tmpDirA, tmpDirB)
// Load A and B with some sample files and directories.
createSampleDir(t, tmpDirA)
createSampleDir(t, tmpDirB)
srcPath := filepath.Join(tmpDirA, "symlink4")
linkTarget := filepath.Join(tmpDirA, "file1")
dstDir := filepath.Join(tmpDirB, "dir1")
dstPath := filepath.Join(dstDir, "symlink4")
var err error
// Ensure that dstPath doesn't exist.
if _, err = os.Stat(dstPath); !os.IsNotExist(err) {
t.Fatalf("did not expect dstPath %q to exist", dstPath)
}
if err = testCopyHelperFSym(t, srcPath, dstDir); err != nil {
t.Fatalf("unexpected error %T: %s", err, err)
}
if err = fileContentsEqual(t, linkTarget, dstPath); err != nil {
t.Fatal(err)
}
// Now try again but using a trailing path separator for dstDir.
if err = os.RemoveAll(dstDir); err != nil {
t.Fatalf("unable to remove dstDir: %s", err)
}
if err = os.MkdirAll(dstDir, os.FileMode(0755)); err != nil {
t.Fatalf("unable to make dstDir: %s", err)
}
dstDir = joinTrailingSep(tmpDirB, "dir1")
if err = testCopyHelperFSym(t, srcPath, dstDir); err != nil {
t.Fatalf("unexpected error %T: %s", err, err)
}
if err = fileContentsEqual(t, linkTarget, dstPath); err != nil {
t.Fatal(err)
}
}
// E. SRC specifies a directory and DST does not exist. This should create a
// directory at DST and copy the contents of the SRC directory into the DST
// directory. Ensure this works whether DST has a trailing path separator or
// not.
func TestCopyCaseE(t *testing.T) {
tmpDirA, tmpDirB := getTestTempDirs(t)
defer removeAllPaths(tmpDirA, tmpDirB)
// Load A with some sample files and directories.
createSampleDir(t, tmpDirA)
srcDir := filepath.Join(tmpDirA, "dir1")
dstDir := filepath.Join(tmpDirB, "testDir")
var err error
if err = testCopyHelper(t, srcDir, dstDir); err != nil {
t.Fatalf("unexpected error %T: %s", err, err)
}
if err = dirContentsEqual(t, dstDir, srcDir); err != nil {
t.Log("dir contents not equal")
logDirContents(t, tmpDirA)
logDirContents(t, tmpDirB)
t.Fatal(err)
}
// Now try again but using a trailing path separator for dstDir.
if err = os.RemoveAll(dstDir); err != nil {
t.Fatalf("unable to remove dstDir: %s", err)
}
dstDir = joinTrailingSep(tmpDirB, "testDir")
if err = testCopyHelper(t, srcDir, dstDir); err != nil {
t.Fatalf("unexpected error %T: %s", err, err)
}
if err = dirContentsEqual(t, dstDir, srcDir); err != nil {
t.Fatal(err)
}
}
// E. Symbol link following version:
// SRC specifies a directory and DST does not exist. This should create a
// directory at DST and copy the contents of the SRC directory into the DST
// directory. Ensure this works whether DST has a trailing path separator or
// not.
func TestCopyCaseEFSym(t *testing.T) {
tmpDirA, tmpDirB := getTestTempDirs(t)
defer removeAllPaths(tmpDirA, tmpDirB)
// Load A with some sample files and directories.
createSampleDir(t, tmpDirA)
srcDir := filepath.Join(tmpDirA, "dirSymlink")
linkTarget := filepath.Join(tmpDirA, "dir1")
dstDir := filepath.Join(tmpDirB, "testDir")
var err error
if err = testCopyHelperFSym(t, srcDir, dstDir); err != nil {
t.Fatalf("unexpected error %T: %s", err, err)
}
if err = dirContentsEqual(t, dstDir, linkTarget); err != nil {
t.Log("dir contents not equal")
logDirContents(t, tmpDirA)
logDirContents(t, tmpDirB)
t.Fatal(err)
}
// Now try again but using a trailing path separator for dstDir.
if err = os.RemoveAll(dstDir); err != nil {
t.Fatalf("unable to remove dstDir: %s", err)
}
dstDir = joinTrailingSep(tmpDirB, "testDir")
if err = testCopyHelperFSym(t, srcDir, dstDir); err != nil {
t.Fatalf("unexpected error %T: %s", err, err)
}
if err = dirContentsEqual(t, dstDir, linkTarget); err != nil {
t.Fatal(err)
}
}
// F. SRC specifies a directory and DST exists as a file. This should cause an
// error as it is not possible to overwrite a file with a directory.
func TestCopyCaseF(t *testing.T) {
tmpDirA, tmpDirB := getTestTempDirs(t)
defer removeAllPaths(tmpDirA, tmpDirB)
// Load A and B with some sample files and directories.
createSampleDir(t, tmpDirA)
createSampleDir(t, tmpDirB)
srcDir := filepath.Join(tmpDirA, "dir1")
symSrcDir := filepath.Join(tmpDirA, "dirSymlink")
dstFile := filepath.Join(tmpDirB, "file1")
var err error
if err = testCopyHelper(t, srcDir, dstFile); err == nil {
t.Fatal("expected ErrCannotCopyDir error, but got nil instead")
}
if err != ErrCannotCopyDir {
t.Fatalf("expected ErrCannotCopyDir error, but got %T: %s", err, err)
}
// now test with symbol link
if err = testCopyHelperFSym(t, symSrcDir, dstFile); err == nil {
t.Fatal("expected ErrCannotCopyDir error, but got nil instead")
}
if err != ErrCannotCopyDir {
t.Fatalf("expected ErrCannotCopyDir error, but got %T: %s", err, err)
}
}
// G. SRC specifies a directory and DST exists as a directory. This should copy
// the SRC directory and all its contents to the DST directory. Ensure this
// works whether DST has a trailing path separator or not.
func TestCopyCaseG(t *testing.T) {
tmpDirA, tmpDirB := getTestTempDirs(t)
defer removeAllPaths(tmpDirA, tmpDirB)
// Load A and B with some sample files and directories.
createSampleDir(t, tmpDirA)
createSampleDir(t, tmpDirB)
srcDir := filepath.Join(tmpDirA, "dir1")
dstDir := filepath.Join(tmpDirB, "dir2")
resultDir := filepath.Join(dstDir, "dir1")
var err error
if err = testCopyHelper(t, srcDir, dstDir); err != nil {
t.Fatalf("unexpected error %T: %s", err, err)
}
if err = dirContentsEqual(t, resultDir, srcDir); err != nil {
t.Fatal(err)
}
// Now try again but using a trailing path separator for dstDir.
if err = os.RemoveAll(dstDir); err != nil {
t.Fatalf("unable to remove dstDir: %s", err)
}
if err = os.MkdirAll(dstDir, os.FileMode(0755)); err != nil {
t.Fatalf("unable to make dstDir: %s", err)
}
dstDir = joinTrailingSep(tmpDirB, "dir2")
if err = testCopyHelper(t, srcDir, dstDir); err != nil {
t.Fatalf("unexpected error %T: %s", err, err)
}
if err = dirContentsEqual(t, resultDir, srcDir); err != nil {
t.Fatal(err)
}
}
// G. Symbol link version:
// SRC specifies a directory and DST exists as a directory. This should copy
// the SRC directory and all its contents to the DST directory. Ensure this
// works whether DST has a trailing path separator or not.
func TestCopyCaseGFSym(t *testing.T) {
tmpDirA, tmpDirB := getTestTempDirs(t)
defer removeAllPaths(tmpDirA, tmpDirB)
// Load A and B with some sample files and directories.
createSampleDir(t, tmpDirA)
createSampleDir(t, tmpDirB)
srcDir := filepath.Join(tmpDirA, "dirSymlink")
linkTarget := filepath.Join(tmpDirA, "dir1")
dstDir := filepath.Join(tmpDirB, "dir2")
resultDir := filepath.Join(dstDir, "dirSymlink")
var err error
if err = testCopyHelperFSym(t, srcDir, dstDir); err != nil {
t.Fatalf("unexpected error %T: %s", err, err)
}
if err = dirContentsEqual(t, resultDir, linkTarget); err != nil {
t.Fatal(err)
}
// Now try again but using a trailing path separator for dstDir.
if err = os.RemoveAll(dstDir); err != nil {
t.Fatalf("unable to remove dstDir: %s", err)
}
if err = os.MkdirAll(dstDir, os.FileMode(0755)); err != nil {
t.Fatalf("unable to make dstDir: %s", err)
}
dstDir = joinTrailingSep(tmpDirB, "dir2")
if err = testCopyHelperFSym(t, srcDir, dstDir); err != nil {
t.Fatalf("unexpected error %T: %s", err, err)
}
if err = dirContentsEqual(t, resultDir, linkTarget); err != nil {
t.Fatal(err)
}
}
// H. SRC specifies a directory's contents only and DST does not exist. This
// should create a directory at DST and copy the contents of the SRC
// directory (but not the directory itself) into the DST directory. Ensure
// this works whether DST has a trailing path separator or not.
func TestCopyCaseH(t *testing.T) {
tmpDirA, tmpDirB := getTestTempDirs(t)
defer removeAllPaths(tmpDirA, tmpDirB)
// Load A with some sample files and directories.
createSampleDir(t, tmpDirA)
srcDir := joinTrailingSep(tmpDirA, "dir1") + "."
dstDir := filepath.Join(tmpDirB, "testDir")
var err error
if err = testCopyHelper(t, srcDir, dstDir); err != nil {
t.Fatalf("unexpected error %T: %s", err, err)
}
if err = dirContentsEqual(t, dstDir, srcDir); err != nil {
t.Log("dir contents not equal")
logDirContents(t, tmpDirA)
logDirContents(t, tmpDirB)
t.Fatal(err)
}
// Now try again but using a trailing path separator for dstDir.
if err = os.RemoveAll(dstDir); err != nil {
t.Fatalf("unable to remove dstDir: %s", err)
}
dstDir = joinTrailingSep(tmpDirB, "testDir")
if err = testCopyHelper(t, srcDir, dstDir); err != nil {
t.Fatalf("unexpected error %T: %s", err, err)
}
if err = dirContentsEqual(t, dstDir, srcDir); err != nil {
t.Log("dir contents not equal")
logDirContents(t, tmpDirA)
logDirContents(t, tmpDirB)
t.Fatal(err)
}
}
// H. Symbol link following version:
// SRC specifies a directory's contents only and DST does not exist. This
// should create a directory at DST and copy the contents of the SRC
// directory (but not the directory itself) into the DST directory. Ensure
// this works whether DST has a trailing path separator or not.
func TestCopyCaseHFSym(t *testing.T) {
tmpDirA, tmpDirB := getTestTempDirs(t)
defer removeAllPaths(tmpDirA, tmpDirB)
// Load A with some sample files and directories.
createSampleDir(t, tmpDirA)
srcDir := joinTrailingSep(tmpDirA, "dirSymlink") + "."
linkTarget := filepath.Join(tmpDirA, "dir1")
dstDir := filepath.Join(tmpDirB, "testDir")
var err error
if err = testCopyHelperFSym(t, srcDir, dstDir); err != nil {
t.Fatalf("unexpected error %T: %s", err, err)
}
if err = dirContentsEqual(t, dstDir, linkTarget); err != nil {
t.Log("dir contents not equal")
logDirContents(t, tmpDirA)
logDirContents(t, tmpDirB)
t.Fatal(err)
}
// Now try again but using a trailing path separator for dstDir.
if err = os.RemoveAll(dstDir); err != nil {
t.Fatalf("unable to remove dstDir: %s", err)
}
dstDir = joinTrailingSep(tmpDirB, "testDir")
if err = testCopyHelperFSym(t, srcDir, dstDir); err != nil {
t.Fatalf("unexpected error %T: %s", err, err)
}
if err = dirContentsEqual(t, dstDir, linkTarget); err != nil {
t.Log("dir contents not equal")
logDirContents(t, tmpDirA)
logDirContents(t, tmpDirB)
t.Fatal(err)
}
}
// I. SRC specifies a directory's contents only and DST exists as a file. This
// should cause an error as it is not possible to overwrite a file with a
// directory.
func TestCopyCaseI(t *testing.T) {
tmpDirA, tmpDirB := getTestTempDirs(t)
defer removeAllPaths(tmpDirA, tmpDirB)
// Load A and B with some sample files and directories.
createSampleDir(t, tmpDirA)
createSampleDir(t, tmpDirB)
srcDir := joinTrailingSep(tmpDirA, "dir1") + "."
symSrcDir := filepath.Join(tmpDirB, "dirSymlink")
dstFile := filepath.Join(tmpDirB, "file1")
var err error
if err = testCopyHelper(t, srcDir, dstFile); err == nil {
t.Fatal("expected ErrCannotCopyDir error, but got nil instead")
}
if err != ErrCannotCopyDir {
t.Fatalf("expected ErrCannotCopyDir error, but got %T: %s", err, err)
}
// now try with symbol link of dir
if err = testCopyHelperFSym(t, symSrcDir, dstFile); err == nil {
t.Fatal("expected ErrCannotCopyDir error, but got nil instead")
}
if err != ErrCannotCopyDir {
t.Fatalf("expected ErrCannotCopyDir error, but got %T: %s", err, err)
}
}
// J. SRC specifies a directory's contents only and DST exists as a directory.
// This should copy the contents of the SRC directory (but not the directory
// itself) into the DST directory. Ensure this works whether DST has a
// trailing path separator or not.
func TestCopyCaseJ(t *testing.T) {
tmpDirA, tmpDirB := getTestTempDirs(t)
defer removeAllPaths(tmpDirA, tmpDirB)
// Load A and B with some sample files and directories.
createSampleDir(t, tmpDirA)
createSampleDir(t, tmpDirB)
srcDir := joinTrailingSep(tmpDirA, "dir1") + "."
dstDir := filepath.Join(tmpDirB, "dir5")
var err error
// first to create an empty dir
if err = os.MkdirAll(dstDir, os.FileMode(0755)); err != nil {
t.Fatalf("unable to make dstDir: %s", err)
}
if err = testCopyHelper(t, srcDir, dstDir); err != nil {
t.Fatalf("unexpected error %T: %s", err, err)
}
if err = dirContentsEqual(t, dstDir, srcDir); err != nil {
t.Fatal(err)
}
// Now try again but using a trailing path separator for dstDir.
if err = os.RemoveAll(dstDir); err != nil {
t.Fatalf("unable to remove dstDir: %s", err)
}
if err = os.MkdirAll(dstDir, os.FileMode(0755)); err != nil {
t.Fatalf("unable to make dstDir: %s", err)
}
dstDir = joinTrailingSep(tmpDirB, "dir5")
if err = testCopyHelper(t, srcDir, dstDir); err != nil {
t.Fatalf("unexpected error %T: %s", err, err)
}
if err = dirContentsEqual(t, dstDir, srcDir); err != nil {
t.Fatal(err)
}
}
// J. Symbol link following version:
// SRC specifies a directory's contents only and DST exists as a directory.
// This should copy the contents of the SRC directory (but not the directory
// itself) into the DST directory. Ensure this works whether DST has a
// trailing path separator or not.
func TestCopyCaseJFSym(t *testing.T) {
tmpDirA, tmpDirB := getTestTempDirs(t)
defer removeAllPaths(tmpDirA, tmpDirB)
// Load A and B with some sample files and directories.
createSampleDir(t, tmpDirA)
createSampleDir(t, tmpDirB)
srcDir := joinTrailingSep(tmpDirA, "dirSymlink") + "."
linkTarget := filepath.Join(tmpDirA, "dir1")
dstDir := filepath.Join(tmpDirB, "dir5")
var err error
// first to create an empty dir
if err = os.MkdirAll(dstDir, os.FileMode(0755)); err != nil {
t.Fatalf("unable to make dstDir: %s", err)
}
if err = testCopyHelperFSym(t, srcDir, dstDir); err != nil {
t.Fatalf("unexpected error %T: %s", err, err)
}
if err = dirContentsEqual(t, dstDir, linkTarget); err != nil {
t.Fatal(err)
}
// Now try again but using a trailing path separator for dstDir.
if err = os.RemoveAll(dstDir); err != nil {
t.Fatalf("unable to remove dstDir: %s", err)
}
if err = os.MkdirAll(dstDir, os.FileMode(0755)); err != nil {
t.Fatalf("unable to make dstDir: %s", err)
}
dstDir = joinTrailingSep(tmpDirB, "dir5")
if err = testCopyHelperFSym(t, srcDir, dstDir); err != nil {
t.Fatalf("unexpected error %T: %s", err, err)
}
if err = dirContentsEqual(t, dstDir, linkTarget); err != nil {
t.Fatal(err)
}
}

View file

@ -106,7 +106,7 @@ func UnpackLayer(dest string, layer Reader, options *TarOptions) (size int64, er
basename := filepath.Base(hdr.Name)
aufsHardlinks[basename] = hdr
if aufsTempdir == "" {
if aufsTempdir, err = ioutil.TempDir("", "dockerplnk"); err != nil {
if aufsTempdir, err = ioutil.TempDir("", "storageplnk"); err != nil {
return 0, err
}
defer os.RemoveAll(aufsTempdir)

View file

@ -0,0 +1,386 @@
package archive
import (
"archive/tar"
"io"
"io/ioutil"
"os"
"path/filepath"
"reflect"
"runtime"
"testing"
"github.com/containers/storage/pkg/ioutils"
)
func TestApplyLayerInvalidFilenames(t *testing.T) {
// TODO Windows: Figure out how to fix this test.
if runtime.GOOS == "windows" {
t.Skip("Passes but hits breakoutError: platform and architecture is not supported")
}
for i, headers := range [][]*tar.Header{
{
{
Name: "../victim/dotdot",
Typeflag: tar.TypeReg,
Mode: 0644,
},
},
{
{
// Note the leading slash
Name: "/../victim/slash-dotdot",
Typeflag: tar.TypeReg,
Mode: 0644,
},
},
} {
if err := testBreakout("applylayer", "docker-TestApplyLayerInvalidFilenames", headers); err != nil {
t.Fatalf("i=%d. %v", i, err)
}
}
}
func TestApplyLayerInvalidHardlink(t *testing.T) {
if runtime.GOOS == "windows" {
t.Skip("TypeLink support on Windows")
}
for i, headers := range [][]*tar.Header{
{ // try reading victim/hello (../)
{
Name: "dotdot",
Typeflag: tar.TypeLink,
Linkname: "../victim/hello",
Mode: 0644,
},
},
{ // try reading victim/hello (/../)
{
Name: "slash-dotdot",
Typeflag: tar.TypeLink,
// Note the leading slash
Linkname: "/../victim/hello",
Mode: 0644,
},
},
{ // try writing victim/file
{
Name: "loophole-victim",
Typeflag: tar.TypeLink,
Linkname: "../victim",
Mode: 0755,
},
{
Name: "loophole-victim/file",
Typeflag: tar.TypeReg,
Mode: 0644,
},
},
{ // try reading victim/hello (hardlink, symlink)
{
Name: "loophole-victim",
Typeflag: tar.TypeLink,
Linkname: "../victim",
Mode: 0755,
},
{
Name: "symlink",
Typeflag: tar.TypeSymlink,
Linkname: "loophole-victim/hello",
Mode: 0644,
},
},
{ // Try reading victim/hello (hardlink, hardlink)
{
Name: "loophole-victim",
Typeflag: tar.TypeLink,
Linkname: "../victim",
Mode: 0755,
},
{
Name: "hardlink",
Typeflag: tar.TypeLink,
Linkname: "loophole-victim/hello",
Mode: 0644,
},
},
{ // Try removing victim directory (hardlink)
{
Name: "loophole-victim",
Typeflag: tar.TypeLink,
Linkname: "../victim",
Mode: 0755,
},
{
Name: "loophole-victim",
Typeflag: tar.TypeReg,
Mode: 0644,
},
},
} {
if err := testBreakout("applylayer", "docker-TestApplyLayerInvalidHardlink", headers); err != nil {
t.Fatalf("i=%d. %v", i, err)
}
}
}
func TestApplyLayerInvalidSymlink(t *testing.T) {
if runtime.GOOS == "windows" {
t.Skip("TypeSymLink support on Windows")
}
for i, headers := range [][]*tar.Header{
{ // try reading victim/hello (../)
{
Name: "dotdot",
Typeflag: tar.TypeSymlink,
Linkname: "../victim/hello",
Mode: 0644,
},
},
{ // try reading victim/hello (/../)
{
Name: "slash-dotdot",
Typeflag: tar.TypeSymlink,
// Note the leading slash
Linkname: "/../victim/hello",
Mode: 0644,
},
},
{ // try writing victim/file
{
Name: "loophole-victim",
Typeflag: tar.TypeSymlink,
Linkname: "../victim",
Mode: 0755,
},
{
Name: "loophole-victim/file",
Typeflag: tar.TypeReg,
Mode: 0644,
},
},
{ // try reading victim/hello (symlink, symlink)
{
Name: "loophole-victim",
Typeflag: tar.TypeSymlink,
Linkname: "../victim",
Mode: 0755,
},
{
Name: "symlink",
Typeflag: tar.TypeSymlink,
Linkname: "loophole-victim/hello",
Mode: 0644,
},
},
{ // try reading victim/hello (symlink, hardlink)
{
Name: "loophole-victim",
Typeflag: tar.TypeSymlink,
Linkname: "../victim",
Mode: 0755,
},
{
Name: "hardlink",
Typeflag: tar.TypeLink,
Linkname: "loophole-victim/hello",
Mode: 0644,
},
},
{ // try removing victim directory (symlink)
{
Name: "loophole-victim",
Typeflag: tar.TypeSymlink,
Linkname: "../victim",
Mode: 0755,
},
{
Name: "loophole-victim",
Typeflag: tar.TypeReg,
Mode: 0644,
},
},
} {
if err := testBreakout("applylayer", "docker-TestApplyLayerInvalidSymlink", headers); err != nil {
t.Fatalf("i=%d. %v", i, err)
}
}
}
func TestApplyLayerWhiteouts(t *testing.T) {
// TODO Windows: Figure out why this test fails
if runtime.GOOS == "windows" {
t.Skip("Failing on Windows")
}
wd, err := ioutil.TempDir("", "graphdriver-test-whiteouts")
if err != nil {
return
}
defer os.RemoveAll(wd)
base := []string{
".baz",
"bar/",
"bar/bax",
"bar/bay/",
"baz",
"foo/",
"foo/.abc",
"foo/.bcd/",
"foo/.bcd/a",
"foo/cde/",
"foo/cde/def",
"foo/cde/efg",
"foo/fgh",
"foobar",
}
type tcase struct {
change, expected []string
}
tcases := []tcase{
{
base,
base,
},
{
[]string{
".bay",
".wh.baz",
"foo/",
"foo/.bce",
"foo/.wh..wh..opq",
"foo/cde/",
"foo/cde/efg",
},
[]string{
".bay",
".baz",
"bar/",
"bar/bax",
"bar/bay/",
"foo/",
"foo/.bce",
"foo/cde/",
"foo/cde/efg",
"foobar",
},
},
{
[]string{
".bay",
".wh..baz",
".wh.foobar",
"foo/",
"foo/.abc",
"foo/.wh.cde",
"bar/",
},
[]string{
".bay",
"bar/",
"bar/bax",
"bar/bay/",
"foo/",
"foo/.abc",
"foo/.bce",
},
},
{
[]string{
".abc",
".wh..wh..opq",
"foobar",
},
[]string{
".abc",
"foobar",
},
},
}
for i, tc := range tcases {
l, err := makeTestLayer(tc.change)
if err != nil {
t.Fatal(err)
}
_, err = UnpackLayer(wd, l, nil)
if err != nil {
t.Fatal(err)
}
err = l.Close()
if err != nil {
t.Fatal(err)
}
paths, err := readDirContents(wd)
if err != nil {
t.Fatal(err)
}
if !reflect.DeepEqual(tc.expected, paths) {
t.Fatalf("invalid files for layer %d: expected %q, got %q", i, tc.expected, paths)
}
}
}
func makeTestLayer(paths []string) (rc io.ReadCloser, err error) {
tmpDir, err := ioutil.TempDir("", "graphdriver-test-mklayer")
if err != nil {
return
}
defer func() {
if err != nil {
os.RemoveAll(tmpDir)
}
}()
for _, p := range paths {
if p[len(p)-1] == filepath.Separator {
if err = os.MkdirAll(filepath.Join(tmpDir, p), 0700); err != nil {
return
}
} else {
if err = ioutil.WriteFile(filepath.Join(tmpDir, p), nil, 0600); err != nil {
return
}
}
}
archive, err := Tar(tmpDir, Uncompressed)
if err != nil {
return
}
return ioutils.NewReadCloserWrapper(archive, func() error {
err := archive.Close()
os.RemoveAll(tmpDir)
return err
}), nil
}
func readDirContents(root string) ([]string, error) {
var files []string
err := filepath.Walk(root, func(path string, info os.FileInfo, err error) error {
if err != nil {
return err
}
if path == root {
return nil
}
rel, err := filepath.Rel(root, path)
if err != nil {
return err
}
if info.IsDir() {
rel = rel + "/"
}
files = append(files, rel)
return nil
})
if err != nil {
return nil, err
}
return files, nil
}

View file

@ -39,7 +39,7 @@ func main() {
if len(*flNewDir) == 0 {
var err error
newDir, err = ioutil.TempDir("", "docker-test-newDir")
newDir, err = ioutil.TempDir("", "storage-test-newDir")
if err != nil {
log.Fatal(err)
}
@ -52,7 +52,7 @@ func main() {
}
if len(*flOldDir) == 0 {
oldDir, err := ioutil.TempDir("", "docker-test-oldDir")
oldDir, err := ioutil.TempDir("", "storage-test-oldDir")
if err != nil {
log.Fatal(err)
}

Binary file not shown.

View file

@ -0,0 +1,166 @@
package archive
import (
"archive/tar"
"bytes"
"fmt"
"io"
"io/ioutil"
"os"
"path/filepath"
"time"
)
var testUntarFns = map[string]func(string, io.Reader) error{
"untar": func(dest string, r io.Reader) error {
return Untar(r, dest, nil)
},
"applylayer": func(dest string, r io.Reader) error {
_, err := ApplyLayer(dest, Reader(r))
return err
},
}
// testBreakout is a helper function that, within the provided `tmpdir` directory,
// creates a `victim` folder with a generated `hello` file in it.
// `untar` extracts to a directory named `dest`, the tar file created from `headers`.
//
// Here are the tested scenarios:
// - removed `victim` folder (write)
// - removed files from `victim` folder (write)
// - new files in `victim` folder (write)
// - modified files in `victim` folder (write)
// - file in `dest` with same content as `victim/hello` (read)
//
// When using testBreakout make sure you cover one of the scenarios listed above.
func testBreakout(untarFn string, tmpdir string, headers []*tar.Header) error {
tmpdir, err := ioutil.TempDir("", tmpdir)
if err != nil {
return err
}
defer os.RemoveAll(tmpdir)
dest := filepath.Join(tmpdir, "dest")
if err := os.Mkdir(dest, 0755); err != nil {
return err
}
victim := filepath.Join(tmpdir, "victim")
if err := os.Mkdir(victim, 0755); err != nil {
return err
}
hello := filepath.Join(victim, "hello")
helloData, err := time.Now().MarshalText()
if err != nil {
return err
}
if err := ioutil.WriteFile(hello, helloData, 0644); err != nil {
return err
}
helloStat, err := os.Stat(hello)
if err != nil {
return err
}
reader, writer := io.Pipe()
go func() {
t := tar.NewWriter(writer)
for _, hdr := range headers {
t.WriteHeader(hdr)
}
t.Close()
}()
untar := testUntarFns[untarFn]
if untar == nil {
return fmt.Errorf("could not find untar function %q in testUntarFns", untarFn)
}
if err := untar(dest, reader); err != nil {
if _, ok := err.(breakoutError); !ok {
// If untar returns an error unrelated to an archive breakout,
// then consider this an unexpected error and abort.
return err
}
// Here, untar detected the breakout.
// Let's move on verifying that indeed there was no breakout.
fmt.Printf("breakoutError: %v\n", err)
}
// Check victim folder
f, err := os.Open(victim)
if err != nil {
// codepath taken if victim folder was removed
return fmt.Errorf("archive breakout: error reading %q: %v", victim, err)
}
defer f.Close()
// Check contents of victim folder
//
// We are only interested in getting 2 files from the victim folder, because if all is well
// we expect only one result, the `hello` file. If there is a second result, it cannot
// hold the same name `hello` and we assume that a new file got created in the victim folder.
// That is enough to detect an archive breakout.
names, err := f.Readdirnames(2)
if err != nil {
// codepath taken if victim is not a folder
return fmt.Errorf("archive breakout: error reading directory content of %q: %v", victim, err)
}
for _, name := range names {
if name != "hello" {
// codepath taken if new file was created in victim folder
return fmt.Errorf("archive breakout: new file %q", name)
}
}
// Check victim/hello
f, err = os.Open(hello)
if err != nil {
// codepath taken if read permissions were removed
return fmt.Errorf("archive breakout: could not lstat %q: %v", hello, err)
}
defer f.Close()
b, err := ioutil.ReadAll(f)
if err != nil {
return err
}
fi, err := f.Stat()
if err != nil {
return err
}
if helloStat.IsDir() != fi.IsDir() ||
// TODO: cannot check for fi.ModTime() change
helloStat.Mode() != fi.Mode() ||
helloStat.Size() != fi.Size() ||
!bytes.Equal(helloData, b) {
// codepath taken if hello has been modified
return fmt.Errorf("archive breakout: file %q has been modified. Contents: expected=%q, got=%q. FileInfo: expected=%#v, got=%#v", hello, helloData, b, helloStat, fi)
}
// Check that nothing in dest/ has the same content as victim/hello.
// Since victim/hello was generated with time.Now(), it is safe to assume
// that any file whose content matches exactly victim/hello, managed somehow
// to access victim/hello.
return filepath.Walk(dest, func(path string, info os.FileInfo, err error) error {
if info.IsDir() {
if err != nil {
// skip directory if error
return filepath.SkipDir
}
// enter directory
return nil
}
if err != nil {
// skip file if error
return nil
}
b, err := ioutil.ReadFile(path)
if err != nil {
// Houston, we have a problem. Aborting (space)walk.
return err
}
if bytes.Equal(helloData, b) {
return fmt.Errorf("archive breakout: file %q has been accessed via %q", hello, path)
}
return nil
})
}

View file

@ -0,0 +1,98 @@
package archive
import (
"archive/tar"
"bytes"
"io"
"testing"
)
func TestGenerateEmptyFile(t *testing.T) {
archive, err := Generate("emptyFile")
if err != nil {
t.Fatal(err)
}
if archive == nil {
t.Fatal("The generated archive should not be nil.")
}
expectedFiles := [][]string{
{"emptyFile", ""},
}
tr := tar.NewReader(archive)
actualFiles := make([][]string, 0, 10)
i := 0
for {
hdr, err := tr.Next()
if err == io.EOF {
break
}
if err != nil {
t.Fatal(err)
}
buf := new(bytes.Buffer)
buf.ReadFrom(tr)
content := buf.String()
actualFiles = append(actualFiles, []string{hdr.Name, content})
i++
}
if len(actualFiles) != len(expectedFiles) {
t.Fatalf("Number of expected file %d, got %d.", len(expectedFiles), len(actualFiles))
}
for i := 0; i < len(expectedFiles); i++ {
actual := actualFiles[i]
expected := expectedFiles[i]
if actual[0] != expected[0] {
t.Fatalf("Expected name '%s', Actual name '%s'", expected[0], actual[0])
}
if actual[1] != expected[1] {
t.Fatalf("Expected content '%s', Actual content '%s'", expected[1], actual[1])
}
}
}
func TestGenerateWithContent(t *testing.T) {
archive, err := Generate("file", "content")
if err != nil {
t.Fatal(err)
}
if archive == nil {
t.Fatal("The generated archive should not be nil.")
}
expectedFiles := [][]string{
{"file", "content"},
}
tr := tar.NewReader(archive)
actualFiles := make([][]string, 0, 10)
i := 0
for {
hdr, err := tr.Next()
if err == io.EOF {
break
}
if err != nil {
t.Fatal(err)
}
buf := new(bytes.Buffer)
buf.ReadFrom(tr)
content := buf.String()
actualFiles = append(actualFiles, []string{hdr.Name, content})
i++
}
if len(actualFiles) != len(expectedFiles) {
t.Fatalf("Number of expected file %d, got %d.", len(expectedFiles), len(actualFiles))
}
for i := 0; i < len(expectedFiles); i++ {
actual := actualFiles[i]
expected := expectedFiles[i]
if actual[0] != expected[0] {
t.Fatalf("Expected name '%s', Actual name '%s'", expected[0], actual[0])
}
if actual[1] != expected[1] {
t.Fatalf("Expected content '%s', Actual content '%s'", expected[1], actual[1])
}
}
}

View file

@ -0,0 +1,394 @@
package chrootarchive
import (
"bytes"
"fmt"
"hash/crc32"
"io"
"io/ioutil"
"os"
"path/filepath"
"runtime"
"strings"
"testing"
"time"
"github.com/containers/storage/pkg/archive"
"github.com/containers/storage/pkg/reexec"
"github.com/containers/storage/pkg/system"
)
func init() {
reexec.Init()
}
func TestChrootTarUntar(t *testing.T) {
tmpdir, err := ioutil.TempDir("", "docker-TestChrootTarUntar")
if err != nil {
t.Fatal(err)
}
defer os.RemoveAll(tmpdir)
src := filepath.Join(tmpdir, "src")
if err := system.MkdirAll(src, 0700); err != nil {
t.Fatal(err)
}
if err := ioutil.WriteFile(filepath.Join(src, "toto"), []byte("hello toto"), 0644); err != nil {
t.Fatal(err)
}
if err := ioutil.WriteFile(filepath.Join(src, "lolo"), []byte("hello lolo"), 0644); err != nil {
t.Fatal(err)
}
stream, err := archive.Tar(src, archive.Uncompressed)
if err != nil {
t.Fatal(err)
}
dest := filepath.Join(tmpdir, "src")
if err := system.MkdirAll(dest, 0700); err != nil {
t.Fatal(err)
}
if err := Untar(stream, dest, &archive.TarOptions{ExcludePatterns: []string{"lolo"}}); err != nil {
t.Fatal(err)
}
}
// gh#10426: Verify the fix for having a huge excludes list (like on `docker load` with large # of
// local images)
func TestChrootUntarWithHugeExcludesList(t *testing.T) {
tmpdir, err := ioutil.TempDir("", "docker-TestChrootUntarHugeExcludes")
if err != nil {
t.Fatal(err)
}
defer os.RemoveAll(tmpdir)
src := filepath.Join(tmpdir, "src")
if err := system.MkdirAll(src, 0700); err != nil {
t.Fatal(err)
}
if err := ioutil.WriteFile(filepath.Join(src, "toto"), []byte("hello toto"), 0644); err != nil {
t.Fatal(err)
}
stream, err := archive.Tar(src, archive.Uncompressed)
if err != nil {
t.Fatal(err)
}
dest := filepath.Join(tmpdir, "dest")
if err := system.MkdirAll(dest, 0700); err != nil {
t.Fatal(err)
}
options := &archive.TarOptions{}
//65534 entries of 64-byte strings ~= 4MB of environment space which should overflow
//on most systems when passed via environment or command line arguments
excludes := make([]string, 65534, 65534)
for i := 0; i < 65534; i++ {
excludes[i] = strings.Repeat(string(i), 64)
}
options.ExcludePatterns = excludes
if err := Untar(stream, dest, options); err != nil {
t.Fatal(err)
}
}
func TestChrootUntarEmptyArchive(t *testing.T) {
tmpdir, err := ioutil.TempDir("", "docker-TestChrootUntarEmptyArchive")
if err != nil {
t.Fatal(err)
}
defer os.RemoveAll(tmpdir)
if err := Untar(nil, tmpdir, nil); err == nil {
t.Fatal("expected error on empty archive")
}
}
func prepareSourceDirectory(numberOfFiles int, targetPath string, makeSymLinks bool) (int, error) {
fileData := []byte("fooo")
for n := 0; n < numberOfFiles; n++ {
fileName := fmt.Sprintf("file-%d", n)
if err := ioutil.WriteFile(filepath.Join(targetPath, fileName), fileData, 0700); err != nil {
return 0, err
}
if makeSymLinks {
if err := os.Symlink(filepath.Join(targetPath, fileName), filepath.Join(targetPath, fileName+"-link")); err != nil {
return 0, err
}
}
}
totalSize := numberOfFiles * len(fileData)
return totalSize, nil
}
func getHash(filename string) (uint32, error) {
stream, err := ioutil.ReadFile(filename)
if err != nil {
return 0, err
}
hash := crc32.NewIEEE()
hash.Write(stream)
return hash.Sum32(), nil
}
func compareDirectories(src string, dest string) error {
changes, err := archive.ChangesDirs(dest, src)
if err != nil {
return err
}
if len(changes) > 0 {
return fmt.Errorf("Unexpected differences after untar: %v", changes)
}
return nil
}
func compareFiles(src string, dest string) error {
srcHash, err := getHash(src)
if err != nil {
return err
}
destHash, err := getHash(dest)
if err != nil {
return err
}
if srcHash != destHash {
return fmt.Errorf("%s is different from %s", src, dest)
}
return nil
}
func TestChrootTarUntarWithSymlink(t *testing.T) {
// TODO Windows: Figure out why this is failing
if runtime.GOOS == "windows" {
t.Skip("Failing on Windows")
}
tmpdir, err := ioutil.TempDir("", "docker-TestChrootTarUntarWithSymlink")
if err != nil {
t.Fatal(err)
}
defer os.RemoveAll(tmpdir)
src := filepath.Join(tmpdir, "src")
if err := system.MkdirAll(src, 0700); err != nil {
t.Fatal(err)
}
if _, err := prepareSourceDirectory(10, src, true); err != nil {
t.Fatal(err)
}
dest := filepath.Join(tmpdir, "dest")
if err := TarUntar(src, dest); err != nil {
t.Fatal(err)
}
if err := compareDirectories(src, dest); err != nil {
t.Fatal(err)
}
}
func TestChrootCopyWithTar(t *testing.T) {
// TODO Windows: Figure out why this is failing
if runtime.GOOS == "windows" {
t.Skip("Failing on Windows")
}
tmpdir, err := ioutil.TempDir("", "docker-TestChrootCopyWithTar")
if err != nil {
t.Fatal(err)
}
defer os.RemoveAll(tmpdir)
src := filepath.Join(tmpdir, "src")
if err := system.MkdirAll(src, 0700); err != nil {
t.Fatal(err)
}
if _, err := prepareSourceDirectory(10, src, true); err != nil {
t.Fatal(err)
}
// Copy directory
dest := filepath.Join(tmpdir, "dest")
if err := CopyWithTar(src, dest); err != nil {
t.Fatal(err)
}
if err := compareDirectories(src, dest); err != nil {
t.Fatal(err)
}
// Copy file
srcfile := filepath.Join(src, "file-1")
dest = filepath.Join(tmpdir, "destFile")
destfile := filepath.Join(dest, "file-1")
if err := CopyWithTar(srcfile, destfile); err != nil {
t.Fatal(err)
}
if err := compareFiles(srcfile, destfile); err != nil {
t.Fatal(err)
}
// Copy symbolic link
srcLinkfile := filepath.Join(src, "file-1-link")
dest = filepath.Join(tmpdir, "destSymlink")
destLinkfile := filepath.Join(dest, "file-1-link")
if err := CopyWithTar(srcLinkfile, destLinkfile); err != nil {
t.Fatal(err)
}
if err := compareFiles(srcLinkfile, destLinkfile); err != nil {
t.Fatal(err)
}
}
func TestChrootCopyFileWithTar(t *testing.T) {
tmpdir, err := ioutil.TempDir("", "docker-TestChrootCopyFileWithTar")
if err != nil {
t.Fatal(err)
}
defer os.RemoveAll(tmpdir)
src := filepath.Join(tmpdir, "src")
if err := system.MkdirAll(src, 0700); err != nil {
t.Fatal(err)
}
if _, err := prepareSourceDirectory(10, src, true); err != nil {
t.Fatal(err)
}
// Copy directory
dest := filepath.Join(tmpdir, "dest")
if err := CopyFileWithTar(src, dest); err == nil {
t.Fatal("Expected error on copying directory")
}
// Copy file
srcfile := filepath.Join(src, "file-1")
dest = filepath.Join(tmpdir, "destFile")
destfile := filepath.Join(dest, "file-1")
if err := CopyFileWithTar(srcfile, destfile); err != nil {
t.Fatal(err)
}
if err := compareFiles(srcfile, destfile); err != nil {
t.Fatal(err)
}
// Copy symbolic link
srcLinkfile := filepath.Join(src, "file-1-link")
dest = filepath.Join(tmpdir, "destSymlink")
destLinkfile := filepath.Join(dest, "file-1-link")
if err := CopyFileWithTar(srcLinkfile, destLinkfile); err != nil {
t.Fatal(err)
}
if err := compareFiles(srcLinkfile, destLinkfile); err != nil {
t.Fatal(err)
}
}
func TestChrootUntarPath(t *testing.T) {
// TODO Windows: Figure out why this is failing
if runtime.GOOS == "windows" {
t.Skip("Failing on Windows")
}
tmpdir, err := ioutil.TempDir("", "docker-TestChrootUntarPath")
if err != nil {
t.Fatal(err)
}
defer os.RemoveAll(tmpdir)
src := filepath.Join(tmpdir, "src")
if err := system.MkdirAll(src, 0700); err != nil {
t.Fatal(err)
}
if _, err := prepareSourceDirectory(10, src, true); err != nil {
t.Fatal(err)
}
dest := filepath.Join(tmpdir, "dest")
// Untar a directory
if err := UntarPath(src, dest); err == nil {
t.Fatal("Expected error on untaring a directory")
}
// Untar a tar file
stream, err := archive.Tar(src, archive.Uncompressed)
if err != nil {
t.Fatal(err)
}
buf := new(bytes.Buffer)
buf.ReadFrom(stream)
tarfile := filepath.Join(tmpdir, "src.tar")
if err := ioutil.WriteFile(tarfile, buf.Bytes(), 0644); err != nil {
t.Fatal(err)
}
if err := UntarPath(tarfile, dest); err != nil {
t.Fatal(err)
}
if err := compareDirectories(src, dest); err != nil {
t.Fatal(err)
}
}
type slowEmptyTarReader struct {
size int
offset int
chunkSize int
}
// Read is a slow reader of an empty tar (like the output of "tar c --files-from /dev/null")
func (s *slowEmptyTarReader) Read(p []byte) (int, error) {
time.Sleep(100 * time.Millisecond)
count := s.chunkSize
if len(p) < s.chunkSize {
count = len(p)
}
for i := 0; i < count; i++ {
p[i] = 0
}
s.offset += count
if s.offset > s.size {
return count, io.EOF
}
return count, nil
}
func TestChrootUntarEmptyArchiveFromSlowReader(t *testing.T) {
tmpdir, err := ioutil.TempDir("", "docker-TestChrootUntarEmptyArchiveFromSlowReader")
if err != nil {
t.Fatal(err)
}
defer os.RemoveAll(tmpdir)
dest := filepath.Join(tmpdir, "dest")
if err := system.MkdirAll(dest, 0700); err != nil {
t.Fatal(err)
}
stream := &slowEmptyTarReader{size: 10240, chunkSize: 1024}
if err := Untar(stream, dest, nil); err != nil {
t.Fatal(err)
}
}
func TestChrootApplyEmptyArchiveFromSlowReader(t *testing.T) {
tmpdir, err := ioutil.TempDir("", "docker-TestChrootApplyEmptyArchiveFromSlowReader")
if err != nil {
t.Fatal(err)
}
defer os.RemoveAll(tmpdir)
dest := filepath.Join(tmpdir, "dest")
if err := system.MkdirAll(dest, 0700); err != nil {
t.Fatal(err)
}
stream := &slowEmptyTarReader{size: 10240, chunkSize: 1024}
if _, err := ApplyLayer(dest, stream); err != nil {
t.Fatal(err)
}
}
func TestChrootApplyDotDotFile(t *testing.T) {
tmpdir, err := ioutil.TempDir("", "docker-TestChrootApplyDotDotFile")
if err != nil {
t.Fatal(err)
}
defer os.RemoveAll(tmpdir)
src := filepath.Join(tmpdir, "src")
if err := system.MkdirAll(src, 0700); err != nil {
t.Fatal(err)
}
if err := ioutil.WriteFile(filepath.Join(src, "..gitme"), []byte(""), 0644); err != nil {
t.Fatal(err)
}
stream, err := archive.Tar(src, archive.Uncompressed)
if err != nil {
t.Fatal(err)
}
dest := filepath.Join(tmpdir, "dest")
if err := system.MkdirAll(dest, 0700); err != nil {
t.Fatal(err)
}
if _, err := ApplyLayer(dest, stream); err != nil {
t.Fatal(err)
}
}

View file

@ -16,7 +16,7 @@ import (
"github.com/containers/storage/pkg/reexec"
)
// untar is the entry-point for docker-untar on re-exec. This is not used on
// untar is the entry-point for storage-untar on re-exec. This is not used on
// Windows as it does not support chroot, hence no point sandboxing through
// chroot and rexec.
func untar() {
@ -57,7 +57,7 @@ func invokeUnpack(decompressedArchive io.Reader, dest string, options *archive.T
return fmt.Errorf("Untar pipe failure: %v", err)
}
cmd := reexec.Command("docker-untar", dest)
cmd := reexec.Command("storage-untar", dest)
cmd.Stdin = decompressedArchive
cmd.ExtraFiles = append(cmd.ExtraFiles, r)
@ -75,7 +75,7 @@ func invokeUnpack(decompressedArchive io.Reader, dest string, options *archive.T
w.Close()
if err := cmd.Wait(); err != nil {
// when `xz -d -c -q | docker-untar ...` failed on docker-untar side,
// when `xz -d -c -q | storage-untar ...` failed on storage-untar side,
// we need to exhaust `xz`'s output, otherwise the `xz` side will be
// pending on write pipe forever
io.Copy(ioutil.Discard, decompressedArchive)

View file

@ -21,7 +21,7 @@ type applyLayerResponse struct {
LayerSize int64 `json:"layerSize"`
}
// applyLayer is the entry-point for docker-applylayer on re-exec. This is not
// applyLayer is the entry-point for storage-applylayer on re-exec. This is not
// used on Windows as it does not support chroot, hence no point sandboxing
// through chroot and rexec.
func applyLayer() {
@ -49,7 +49,7 @@ func applyLayer() {
fatal(err)
}
if tmpDir, err = ioutil.TempDir("/", "temp-docker-extract"); err != nil {
if tmpDir, err = ioutil.TempDir("/", "temp-storage-extract"); err != nil {
fatal(err)
}
@ -98,7 +98,7 @@ func applyLayerHandler(dest string, layer archive.Reader, options *archive.TarOp
return 0, fmt.Errorf("ApplyLayer json encode: %v", err)
}
cmd := reexec.Command("docker-applyLayer", dest)
cmd := reexec.Command("storage-applyLayer", dest)
cmd.Stdin = layer
cmd.Env = append(cmd.Env, fmt.Sprintf("OPT=%s", data))

View file

@ -29,9 +29,9 @@ func applyLayerHandler(dest string, layer archive.Reader, options *archive.TarOp
layer = decompressed
}
tmpDir, err := ioutil.TempDir(os.Getenv("temp"), "temp-docker-extract")
tmpDir, err := ioutil.TempDir(os.Getenv("temp"), "temp-storage-extract")
if err != nil {
return 0, fmt.Errorf("ApplyLayer failed to create temp-docker-extract under %s. %s", dest, err)
return 0, fmt.Errorf("ApplyLayer failed to create temp-storage-extract under %s. %s", dest, err)
}
s, err := archive.UnpackLayer(dest, layer, nil)

View file

@ -12,8 +12,8 @@ import (
)
func init() {
reexec.Register("docker-applyLayer", applyLayer)
reexec.Register("docker-untar", untar)
reexec.Register("storage-applyLayer", applyLayer)
reexec.Register("storage-untar", untar)
}
func fatal(err error) {

View file

@ -0,0 +1,192 @@
package directory
import (
"io/ioutil"
"os"
"path/filepath"
"reflect"
"sort"
"testing"
)
// Size of an empty directory should be 0
func TestSizeEmpty(t *testing.T) {
var dir string
var err error
if dir, err = ioutil.TempDir(os.TempDir(), "testSizeEmptyDirectory"); err != nil {
t.Fatalf("failed to create directory: %s", err)
}
var size int64
if size, _ = Size(dir); size != 0 {
t.Fatalf("empty directory has size: %d", size)
}
}
// Size of a directory with one empty file should be 0
func TestSizeEmptyFile(t *testing.T) {
var dir string
var err error
if dir, err = ioutil.TempDir(os.TempDir(), "testSizeEmptyFile"); err != nil {
t.Fatalf("failed to create directory: %s", err)
}
var file *os.File
if file, err = ioutil.TempFile(dir, "file"); err != nil {
t.Fatalf("failed to create file: %s", err)
}
var size int64
if size, _ = Size(file.Name()); size != 0 {
t.Fatalf("directory with one file has size: %d", size)
}
}
// Size of a directory with one 5-byte file should be 5
func TestSizeNonemptyFile(t *testing.T) {
var dir string
var err error
if dir, err = ioutil.TempDir(os.TempDir(), "testSizeNonemptyFile"); err != nil {
t.Fatalf("failed to create directory: %s", err)
}
var file *os.File
if file, err = ioutil.TempFile(dir, "file"); err != nil {
t.Fatalf("failed to create file: %s", err)
}
d := []byte{97, 98, 99, 100, 101}
file.Write(d)
var size int64
if size, _ = Size(file.Name()); size != 5 {
t.Fatalf("directory with one 5-byte file has size: %d", size)
}
}
// Size of a directory with one empty directory should be 0
func TestSizeNestedDirectoryEmpty(t *testing.T) {
var dir string
var err error
if dir, err = ioutil.TempDir(os.TempDir(), "testSizeNestedDirectoryEmpty"); err != nil {
t.Fatalf("failed to create directory: %s", err)
}
if dir, err = ioutil.TempDir(dir, "nested"); err != nil {
t.Fatalf("failed to create nested directory: %s", err)
}
var size int64
if size, _ = Size(dir); size != 0 {
t.Fatalf("directory with one empty directory has size: %d", size)
}
}
// Test directory with 1 file and 1 empty directory
func TestSizeFileAndNestedDirectoryEmpty(t *testing.T) {
var dir string
var err error
if dir, err = ioutil.TempDir(os.TempDir(), "testSizeFileAndNestedDirectoryEmpty"); err != nil {
t.Fatalf("failed to create directory: %s", err)
}
if dir, err = ioutil.TempDir(dir, "nested"); err != nil {
t.Fatalf("failed to create nested directory: %s", err)
}
var file *os.File
if file, err = ioutil.TempFile(dir, "file"); err != nil {
t.Fatalf("failed to create file: %s", err)
}
d := []byte{100, 111, 99, 107, 101, 114}
file.Write(d)
var size int64
if size, _ = Size(dir); size != 6 {
t.Fatalf("directory with 6-byte file and empty directory has size: %d", size)
}
}
// Test directory with 1 file and 1 non-empty directory
func TestSizeFileAndNestedDirectoryNonempty(t *testing.T) {
var dir, dirNested string
var err error
if dir, err = ioutil.TempDir(os.TempDir(), "TestSizeFileAndNestedDirectoryNonempty"); err != nil {
t.Fatalf("failed to create directory: %s", err)
}
if dirNested, err = ioutil.TempDir(dir, "nested"); err != nil {
t.Fatalf("failed to create nested directory: %s", err)
}
var file *os.File
if file, err = ioutil.TempFile(dir, "file"); err != nil {
t.Fatalf("failed to create file: %s", err)
}
data := []byte{100, 111, 99, 107, 101, 114}
file.Write(data)
var nestedFile *os.File
if nestedFile, err = ioutil.TempFile(dirNested, "file"); err != nil {
t.Fatalf("failed to create file in nested directory: %s", err)
}
nestedData := []byte{100, 111, 99, 107, 101, 114}
nestedFile.Write(nestedData)
var size int64
if size, _ = Size(dir); size != 12 {
t.Fatalf("directory with 6-byte file and nested directory with 6-byte file has size: %d", size)
}
}
// Test migration of directory to a subdir underneath itself
func TestMoveToSubdir(t *testing.T) {
var outerDir, subDir string
var err error
if outerDir, err = ioutil.TempDir(os.TempDir(), "TestMoveToSubdir"); err != nil {
t.Fatalf("failed to create directory: %v", err)
}
if subDir, err = ioutil.TempDir(outerDir, "testSub"); err != nil {
t.Fatalf("failed to create subdirectory: %v", err)
}
// write 4 temp files in the outer dir to get moved
filesList := []string{"a", "b", "c", "d"}
for _, fName := range filesList {
if file, err := os.Create(filepath.Join(outerDir, fName)); err != nil {
t.Fatalf("couldn't create temp file %q: %v", fName, err)
} else {
file.WriteString(fName)
file.Close()
}
}
if err = MoveToSubdir(outerDir, filepath.Base(subDir)); err != nil {
t.Fatalf("Error during migration of content to subdirectory: %v", err)
}
// validate that the files were moved to the subdirectory
infos, err := ioutil.ReadDir(subDir)
if err != nil {
t.Fatal(err)
}
if len(infos) != 4 {
t.Fatalf("Should be four files in the subdir after the migration: actual length: %d", len(infos))
}
var results []string
for _, info := range infos {
results = append(results, info.Name())
}
sort.Sort(sort.StringSlice(results))
if !reflect.DeepEqual(filesList, results) {
t.Fatalf("Results after migration do not equal list of files: expected: %v, got: %v", filesList, results)
}
}
// Test a non-existing directory
func TestSizeNonExistingDirectory(t *testing.T) {
if _, err := Size("/thisdirectoryshouldnotexist/TestSizeNonExistingDirectory"); err == nil {
t.Fatalf("error is expected")
}
}

View file

@ -0,0 +1,585 @@
package fileutils
import (
"io/ioutil"
"os"
"path"
"path/filepath"
"runtime"
"strings"
"testing"
)
// CopyFile with invalid src
func TestCopyFileWithInvalidSrc(t *testing.T) {
tempFolder, err := ioutil.TempDir("", "docker-fileutils-test")
defer os.RemoveAll(tempFolder)
if err != nil {
t.Fatal(err)
}
bytes, err := CopyFile("/invalid/file/path", path.Join(tempFolder, "dest"))
if err == nil {
t.Fatal("Should have fail to copy an invalid src file")
}
if bytes != 0 {
t.Fatal("Should have written 0 bytes")
}
}
// CopyFile with invalid dest
func TestCopyFileWithInvalidDest(t *testing.T) {
tempFolder, err := ioutil.TempDir("", "docker-fileutils-test")
defer os.RemoveAll(tempFolder)
if err != nil {
t.Fatal(err)
}
src := path.Join(tempFolder, "file")
err = ioutil.WriteFile(src, []byte("content"), 0740)
if err != nil {
t.Fatal(err)
}
bytes, err := CopyFile(src, path.Join(tempFolder, "/invalid/dest/path"))
if err == nil {
t.Fatal("Should have fail to copy an invalid src file")
}
if bytes != 0 {
t.Fatal("Should have written 0 bytes")
}
}
// CopyFile with same src and dest
func TestCopyFileWithSameSrcAndDest(t *testing.T) {
tempFolder, err := ioutil.TempDir("", "docker-fileutils-test")
defer os.RemoveAll(tempFolder)
if err != nil {
t.Fatal(err)
}
file := path.Join(tempFolder, "file")
err = ioutil.WriteFile(file, []byte("content"), 0740)
if err != nil {
t.Fatal(err)
}
bytes, err := CopyFile(file, file)
if err != nil {
t.Fatal(err)
}
if bytes != 0 {
t.Fatal("Should have written 0 bytes as it is the same file.")
}
}
// CopyFile with same src and dest but path is different and not clean
func TestCopyFileWithSameSrcAndDestWithPathNameDifferent(t *testing.T) {
tempFolder, err := ioutil.TempDir("", "docker-fileutils-test")
defer os.RemoveAll(tempFolder)
if err != nil {
t.Fatal(err)
}
testFolder := path.Join(tempFolder, "test")
err = os.MkdirAll(testFolder, 0740)
if err != nil {
t.Fatal(err)
}
file := path.Join(testFolder, "file")
sameFile := testFolder + "/../test/file"
err = ioutil.WriteFile(file, []byte("content"), 0740)
if err != nil {
t.Fatal(err)
}
bytes, err := CopyFile(file, sameFile)
if err != nil {
t.Fatal(err)
}
if bytes != 0 {
t.Fatal("Should have written 0 bytes as it is the same file.")
}
}
func TestCopyFile(t *testing.T) {
tempFolder, err := ioutil.TempDir("", "docker-fileutils-test")
defer os.RemoveAll(tempFolder)
if err != nil {
t.Fatal(err)
}
src := path.Join(tempFolder, "src")
dest := path.Join(tempFolder, "dest")
ioutil.WriteFile(src, []byte("content"), 0777)
ioutil.WriteFile(dest, []byte("destContent"), 0777)
bytes, err := CopyFile(src, dest)
if err != nil {
t.Fatal(err)
}
if bytes != 7 {
t.Fatalf("Should have written %d bytes but wrote %d", 7, bytes)
}
actual, err := ioutil.ReadFile(dest)
if err != nil {
t.Fatal(err)
}
if string(actual) != "content" {
t.Fatalf("Dest content was '%s', expected '%s'", string(actual), "content")
}
}
// Reading a symlink to a directory must return the directory
func TestReadSymlinkedDirectoryExistingDirectory(t *testing.T) {
// TODO Windows: Port this test
if runtime.GOOS == "windows" {
t.Skip("Needs porting to Windows")
}
var err error
if err = os.Mkdir("/tmp/testReadSymlinkToExistingDirectory", 0777); err != nil {
t.Errorf("failed to create directory: %s", err)
}
if err = os.Symlink("/tmp/testReadSymlinkToExistingDirectory", "/tmp/dirLinkTest"); err != nil {
t.Errorf("failed to create symlink: %s", err)
}
var path string
if path, err = ReadSymlinkedDirectory("/tmp/dirLinkTest"); err != nil {
t.Fatalf("failed to read symlink to directory: %s", err)
}
if path != "/tmp/testReadSymlinkToExistingDirectory" {
t.Fatalf("symlink returned unexpected directory: %s", path)
}
if err = os.Remove("/tmp/testReadSymlinkToExistingDirectory"); err != nil {
t.Errorf("failed to remove temporary directory: %s", err)
}
if err = os.Remove("/tmp/dirLinkTest"); err != nil {
t.Errorf("failed to remove symlink: %s", err)
}
}
// Reading a non-existing symlink must fail
func TestReadSymlinkedDirectoryNonExistingSymlink(t *testing.T) {
var path string
var err error
if path, err = ReadSymlinkedDirectory("/tmp/test/foo/Non/ExistingPath"); err == nil {
t.Fatalf("error expected for non-existing symlink")
}
if path != "" {
t.Fatalf("expected empty path, but '%s' was returned", path)
}
}
// Reading a symlink to a file must fail
func TestReadSymlinkedDirectoryToFile(t *testing.T) {
// TODO Windows: Port this test
if runtime.GOOS == "windows" {
t.Skip("Needs porting to Windows")
}
var err error
var file *os.File
if file, err = os.Create("/tmp/testReadSymlinkToFile"); err != nil {
t.Fatalf("failed to create file: %s", err)
}
file.Close()
if err = os.Symlink("/tmp/testReadSymlinkToFile", "/tmp/fileLinkTest"); err != nil {
t.Errorf("failed to create symlink: %s", err)
}
var path string
if path, err = ReadSymlinkedDirectory("/tmp/fileLinkTest"); err == nil {
t.Fatalf("ReadSymlinkedDirectory on a symlink to a file should've failed")
}
if path != "" {
t.Fatalf("path should've been empty: %s", path)
}
if err = os.Remove("/tmp/testReadSymlinkToFile"); err != nil {
t.Errorf("failed to remove file: %s", err)
}
if err = os.Remove("/tmp/fileLinkTest"); err != nil {
t.Errorf("failed to remove symlink: %s", err)
}
}
func TestWildcardMatches(t *testing.T) {
match, _ := Matches("fileutils.go", []string{"*"})
if match != true {
t.Errorf("failed to get a wildcard match, got %v", match)
}
}
// A simple pattern match should return true.
func TestPatternMatches(t *testing.T) {
match, _ := Matches("fileutils.go", []string{"*.go"})
if match != true {
t.Errorf("failed to get a match, got %v", match)
}
}
// An exclusion followed by an inclusion should return true.
func TestExclusionPatternMatchesPatternBefore(t *testing.T) {
match, _ := Matches("fileutils.go", []string{"!fileutils.go", "*.go"})
if match != true {
t.Errorf("failed to get true match on exclusion pattern, got %v", match)
}
}
// A folder pattern followed by an exception should return false.
func TestPatternMatchesFolderExclusions(t *testing.T) {
match, _ := Matches("docs/README.md", []string{"docs", "!docs/README.md"})
if match != false {
t.Errorf("failed to get a false match on exclusion pattern, got %v", match)
}
}
// A folder pattern followed by an exception should return false.
func TestPatternMatchesFolderWithSlashExclusions(t *testing.T) {
match, _ := Matches("docs/README.md", []string{"docs/", "!docs/README.md"})
if match != false {
t.Errorf("failed to get a false match on exclusion pattern, got %v", match)
}
}
// A folder pattern followed by an exception should return false.
func TestPatternMatchesFolderWildcardExclusions(t *testing.T) {
match, _ := Matches("docs/README.md", []string{"docs/*", "!docs/README.md"})
if match != false {
t.Errorf("failed to get a false match on exclusion pattern, got %v", match)
}
}
// A pattern followed by an exclusion should return false.
func TestExclusionPatternMatchesPatternAfter(t *testing.T) {
match, _ := Matches("fileutils.go", []string{"*.go", "!fileutils.go"})
if match != false {
t.Errorf("failed to get false match on exclusion pattern, got %v", match)
}
}
// A filename evaluating to . should return false.
func TestExclusionPatternMatchesWholeDirectory(t *testing.T) {
match, _ := Matches(".", []string{"*.go"})
if match != false {
t.Errorf("failed to get false match on ., got %v", match)
}
}
// A single ! pattern should return an error.
func TestSingleExclamationError(t *testing.T) {
_, err := Matches("fileutils.go", []string{"!"})
if err == nil {
t.Errorf("failed to get an error for a single exclamation point, got %v", err)
}
}
// A string preceded with a ! should return true from Exclusion.
func TestExclusion(t *testing.T) {
exclusion := exclusion("!")
if !exclusion {
t.Errorf("failed to get true for a single !, got %v", exclusion)
}
}
// Matches with no patterns
func TestMatchesWithNoPatterns(t *testing.T) {
matches, err := Matches("/any/path/there", []string{})
if err != nil {
t.Fatal(err)
}
if matches {
t.Fatalf("Should not have match anything")
}
}
// Matches with malformed patterns
func TestMatchesWithMalformedPatterns(t *testing.T) {
matches, err := Matches("/any/path/there", []string{"["})
if err == nil {
t.Fatal("Should have failed because of a malformed syntax in the pattern")
}
if matches {
t.Fatalf("Should not have match anything")
}
}
// Test lots of variants of patterns & strings
func TestMatches(t *testing.T) {
// TODO Windows: Port this test
if runtime.GOOS == "windows" {
t.Skip("Needs porting to Windows")
}
tests := []struct {
pattern string
text string
pass bool
}{
{"**", "file", true},
{"**", "file/", true},
{"**/", "file", true}, // weird one
{"**/", "file/", true},
{"**", "/", true},
{"**/", "/", true},
{"**", "dir/file", true},
{"**/", "dir/file", false},
{"**", "dir/file/", true},
{"**/", "dir/file/", true},
{"**/**", "dir/file", true},
{"**/**", "dir/file/", true},
{"dir/**", "dir/file", true},
{"dir/**", "dir/file/", true},
{"dir/**", "dir/dir2/file", true},
{"dir/**", "dir/dir2/file/", true},
{"**/dir2/*", "dir/dir2/file", true},
{"**/dir2/*", "dir/dir2/file/", false},
{"**/dir2/**", "dir/dir2/dir3/file", true},
{"**/dir2/**", "dir/dir2/dir3/file/", true},
{"**file", "file", true},
{"**file", "dir/file", true},
{"**/file", "dir/file", true},
{"**file", "dir/dir/file", true},
{"**/file", "dir/dir/file", true},
{"**/file*", "dir/dir/file", true},
{"**/file*", "dir/dir/file.txt", true},
{"**/file*txt", "dir/dir/file.txt", true},
{"**/file*.txt", "dir/dir/file.txt", true},
{"**/file*.txt*", "dir/dir/file.txt", true},
{"**/**/*.txt", "dir/dir/file.txt", true},
{"**/**/*.txt2", "dir/dir/file.txt", false},
{"**/*.txt", "file.txt", true},
{"**/**/*.txt", "file.txt", true},
{"a**/*.txt", "a/file.txt", true},
{"a**/*.txt", "a/dir/file.txt", true},
{"a**/*.txt", "a/dir/dir/file.txt", true},
{"a/*.txt", "a/dir/file.txt", false},
{"a/*.txt", "a/file.txt", true},
{"a/*.txt**", "a/file.txt", true},
{"a[b-d]e", "ae", false},
{"a[b-d]e", "ace", true},
{"a[b-d]e", "aae", false},
{"a[^b-d]e", "aze", true},
{".*", ".foo", true},
{".*", "foo", false},
{"abc.def", "abcdef", false},
{"abc.def", "abc.def", true},
{"abc.def", "abcZdef", false},
{"abc?def", "abcZdef", true},
{"abc?def", "abcdef", false},
{"a\\*b", "a*b", true},
{"a\\", "a", false},
{"a\\", "a\\", false},
{"a\\\\", "a\\", true},
{"**/foo/bar", "foo/bar", true},
{"**/foo/bar", "dir/foo/bar", true},
{"**/foo/bar", "dir/dir2/foo/bar", true},
{"abc/**", "abc", false},
{"abc/**", "abc/def", true},
{"abc/**", "abc/def/ghi", true},
}
for _, test := range tests {
res, _ := regexpMatch(test.pattern, test.text)
if res != test.pass {
t.Fatalf("Failed: %v - res:%v", test, res)
}
}
}
// An empty string should return true from Empty.
func TestEmpty(t *testing.T) {
empty := empty("")
if !empty {
t.Errorf("failed to get true for an empty string, got %v", empty)
}
}
func TestCleanPatterns(t *testing.T) {
cleaned, _, _, _ := CleanPatterns([]string{"docs", "config"})
if len(cleaned) != 2 {
t.Errorf("expected 2 element slice, got %v", len(cleaned))
}
}
func TestCleanPatternsStripEmptyPatterns(t *testing.T) {
cleaned, _, _, _ := CleanPatterns([]string{"docs", "config", ""})
if len(cleaned) != 2 {
t.Errorf("expected 2 element slice, got %v", len(cleaned))
}
}
func TestCleanPatternsExceptionFlag(t *testing.T) {
_, _, exceptions, _ := CleanPatterns([]string{"docs", "!docs/README.md"})
if !exceptions {
t.Errorf("expected exceptions to be true, got %v", exceptions)
}
}
func TestCleanPatternsLeadingSpaceTrimmed(t *testing.T) {
_, _, exceptions, _ := CleanPatterns([]string{"docs", " !docs/README.md"})
if !exceptions {
t.Errorf("expected exceptions to be true, got %v", exceptions)
}
}
func TestCleanPatternsTrailingSpaceTrimmed(t *testing.T) {
_, _, exceptions, _ := CleanPatterns([]string{"docs", "!docs/README.md "})
if !exceptions {
t.Errorf("expected exceptions to be true, got %v", exceptions)
}
}
func TestCleanPatternsErrorSingleException(t *testing.T) {
_, _, _, err := CleanPatterns([]string{"!"})
if err == nil {
t.Errorf("expected error on single exclamation point, got %v", err)
}
}
func TestCleanPatternsFolderSplit(t *testing.T) {
_, dirs, _, _ := CleanPatterns([]string{"docs/config/CONFIG.md"})
if dirs[0][0] != "docs" {
t.Errorf("expected first element in dirs slice to be docs, got %v", dirs[0][1])
}
if dirs[0][1] != "config" {
t.Errorf("expected first element in dirs slice to be config, got %v", dirs[0][1])
}
}
func TestCreateIfNotExistsDir(t *testing.T) {
tempFolder, err := ioutil.TempDir("", "docker-fileutils-test")
if err != nil {
t.Fatal(err)
}
defer os.RemoveAll(tempFolder)
folderToCreate := filepath.Join(tempFolder, "tocreate")
if err := CreateIfNotExists(folderToCreate, true); err != nil {
t.Fatal(err)
}
fileinfo, err := os.Stat(folderToCreate)
if err != nil {
t.Fatalf("Should have create a folder, got %v", err)
}
if !fileinfo.IsDir() {
t.Fatalf("Should have been a dir, seems it's not")
}
}
func TestCreateIfNotExistsFile(t *testing.T) {
tempFolder, err := ioutil.TempDir("", "docker-fileutils-test")
if err != nil {
t.Fatal(err)
}
defer os.RemoveAll(tempFolder)
fileToCreate := filepath.Join(tempFolder, "file/to/create")
if err := CreateIfNotExists(fileToCreate, false); err != nil {
t.Fatal(err)
}
fileinfo, err := os.Stat(fileToCreate)
if err != nil {
t.Fatalf("Should have create a file, got %v", err)
}
if fileinfo.IsDir() {
t.Fatalf("Should have been a file, seems it's not")
}
}
// These matchTests are stolen from go's filepath Match tests.
type matchTest struct {
pattern, s string
match bool
err error
}
var matchTests = []matchTest{
{"abc", "abc", true, nil},
{"*", "abc", true, nil},
{"*c", "abc", true, nil},
{"a*", "a", true, nil},
{"a*", "abc", true, nil},
{"a*", "ab/c", false, nil},
{"a*/b", "abc/b", true, nil},
{"a*/b", "a/c/b", false, nil},
{"a*b*c*d*e*/f", "axbxcxdxe/f", true, nil},
{"a*b*c*d*e*/f", "axbxcxdxexxx/f", true, nil},
{"a*b*c*d*e*/f", "axbxcxdxe/xxx/f", false, nil},
{"a*b*c*d*e*/f", "axbxcxdxexxx/fff", false, nil},
{"a*b?c*x", "abxbbxdbxebxczzx", true, nil},
{"a*b?c*x", "abxbbxdbxebxczzy", false, nil},
{"ab[c]", "abc", true, nil},
{"ab[b-d]", "abc", true, nil},
{"ab[e-g]", "abc", false, nil},
{"ab[^c]", "abc", false, nil},
{"ab[^b-d]", "abc", false, nil},
{"ab[^e-g]", "abc", true, nil},
{"a\\*b", "a*b", true, nil},
{"a\\*b", "ab", false, nil},
{"a?b", "a☺b", true, nil},
{"a[^a]b", "a☺b", true, nil},
{"a???b", "a☺b", false, nil},
{"a[^a][^a][^a]b", "a☺b", false, nil},
{"[a-ζ]*", "α", true, nil},
{"*[a-ζ]", "A", false, nil},
{"a?b", "a/b", false, nil},
{"a*b", "a/b", false, nil},
{"[\\]a]", "]", true, nil},
{"[\\-]", "-", true, nil},
{"[x\\-]", "x", true, nil},
{"[x\\-]", "-", true, nil},
{"[x\\-]", "z", false, nil},
{"[\\-x]", "x", true, nil},
{"[\\-x]", "-", true, nil},
{"[\\-x]", "a", false, nil},
{"[]a]", "]", false, filepath.ErrBadPattern},
{"[-]", "-", false, filepath.ErrBadPattern},
{"[x-]", "x", false, filepath.ErrBadPattern},
{"[x-]", "-", false, filepath.ErrBadPattern},
{"[x-]", "z", false, filepath.ErrBadPattern},
{"[-x]", "x", false, filepath.ErrBadPattern},
{"[-x]", "-", false, filepath.ErrBadPattern},
{"[-x]", "a", false, filepath.ErrBadPattern},
{"\\", "a", false, filepath.ErrBadPattern},
{"[a-b-c]", "a", false, filepath.ErrBadPattern},
{"[", "a", false, filepath.ErrBadPattern},
{"[^", "a", false, filepath.ErrBadPattern},
{"[^bc", "a", false, filepath.ErrBadPattern},
{"a[", "a", false, filepath.ErrBadPattern}, // was nil but IMO its wrong
{"a[", "ab", false, filepath.ErrBadPattern},
{"*x", "xxx", true, nil},
}
func errp(e error) string {
if e == nil {
return "<nil>"
}
return e.Error()
}
// TestMatch test's our version of filepath.Match, called regexpMatch.
func TestMatch(t *testing.T) {
for _, tt := range matchTests {
pattern := tt.pattern
s := tt.s
if runtime.GOOS == "windows" {
if strings.Index(pattern, "\\") >= 0 {
// no escape allowed on windows.
continue
}
pattern = filepath.Clean(pattern)
s = filepath.Clean(s)
}
ok, err := regexpMatch(pattern, s)
if ok != tt.match || err != tt.err {
t.Fatalf("Match(%#q, %#q) = %v, %q want %v, %q", pattern, s, ok, errp(err), tt.match, errp(tt.err))
}
}
}

View file

@ -0,0 +1,24 @@
package homedir
import (
"path/filepath"
"testing"
)
func TestGet(t *testing.T) {
home := Get()
if home == "" {
t.Fatal("returned home directory is empty")
}
if !filepath.IsAbs(home) {
t.Fatalf("returned path is not absolute: %s", home)
}
}
func TestGetShortcutString(t *testing.T) {
shortcut := GetShortcutString()
if shortcut == "" {
t.Fatal("returned shortcut string is empty")
}
}

View file

@ -0,0 +1,271 @@
// +build !windows
package idtools
import (
"fmt"
"io/ioutil"
"os"
"path/filepath"
"syscall"
"testing"
)
type node struct {
uid int
gid int
}
func TestMkdirAllAs(t *testing.T) {
dirName, err := ioutil.TempDir("", "mkdirall")
if err != nil {
t.Fatalf("Couldn't create temp dir: %v", err)
}
defer os.RemoveAll(dirName)
testTree := map[string]node{
"usr": {0, 0},
"usr/bin": {0, 0},
"lib": {33, 33},
"lib/x86_64": {45, 45},
"lib/x86_64/share": {1, 1},
}
if err := buildTree(dirName, testTree); err != nil {
t.Fatal(err)
}
// test adding a directory to a pre-existing dir; only the new dir is owned by the uid/gid
if err := MkdirAllAs(filepath.Join(dirName, "usr", "share"), 0755, 99, 99); err != nil {
t.Fatal(err)
}
testTree["usr/share"] = node{99, 99}
verifyTree, err := readTree(dirName, "")
if err != nil {
t.Fatal(err)
}
if err := compareTrees(testTree, verifyTree); err != nil {
t.Fatal(err)
}
// test 2-deep new directories--both should be owned by the uid/gid pair
if err := MkdirAllAs(filepath.Join(dirName, "lib", "some", "other"), 0755, 101, 101); err != nil {
t.Fatal(err)
}
testTree["lib/some"] = node{101, 101}
testTree["lib/some/other"] = node{101, 101}
verifyTree, err = readTree(dirName, "")
if err != nil {
t.Fatal(err)
}
if err := compareTrees(testTree, verifyTree); err != nil {
t.Fatal(err)
}
// test a directory that already exists; should be chowned, but nothing else
if err := MkdirAllAs(filepath.Join(dirName, "usr"), 0755, 102, 102); err != nil {
t.Fatal(err)
}
testTree["usr"] = node{102, 102}
verifyTree, err = readTree(dirName, "")
if err != nil {
t.Fatal(err)
}
if err := compareTrees(testTree, verifyTree); err != nil {
t.Fatal(err)
}
}
func TestMkdirAllNewAs(t *testing.T) {
dirName, err := ioutil.TempDir("", "mkdirnew")
if err != nil {
t.Fatalf("Couldn't create temp dir: %v", err)
}
defer os.RemoveAll(dirName)
testTree := map[string]node{
"usr": {0, 0},
"usr/bin": {0, 0},
"lib": {33, 33},
"lib/x86_64": {45, 45},
"lib/x86_64/share": {1, 1},
}
if err := buildTree(dirName, testTree); err != nil {
t.Fatal(err)
}
// test adding a directory to a pre-existing dir; only the new dir is owned by the uid/gid
if err := MkdirAllNewAs(filepath.Join(dirName, "usr", "share"), 0755, 99, 99); err != nil {
t.Fatal(err)
}
testTree["usr/share"] = node{99, 99}
verifyTree, err := readTree(dirName, "")
if err != nil {
t.Fatal(err)
}
if err := compareTrees(testTree, verifyTree); err != nil {
t.Fatal(err)
}
// test 2-deep new directories--both should be owned by the uid/gid pair
if err := MkdirAllNewAs(filepath.Join(dirName, "lib", "some", "other"), 0755, 101, 101); err != nil {
t.Fatal(err)
}
testTree["lib/some"] = node{101, 101}
testTree["lib/some/other"] = node{101, 101}
verifyTree, err = readTree(dirName, "")
if err != nil {
t.Fatal(err)
}
if err := compareTrees(testTree, verifyTree); err != nil {
t.Fatal(err)
}
// test a directory that already exists; should NOT be chowned
if err := MkdirAllNewAs(filepath.Join(dirName, "usr"), 0755, 102, 102); err != nil {
t.Fatal(err)
}
verifyTree, err = readTree(dirName, "")
if err != nil {
t.Fatal(err)
}
if err := compareTrees(testTree, verifyTree); err != nil {
t.Fatal(err)
}
}
func TestMkdirAs(t *testing.T) {
dirName, err := ioutil.TempDir("", "mkdir")
if err != nil {
t.Fatalf("Couldn't create temp dir: %v", err)
}
defer os.RemoveAll(dirName)
testTree := map[string]node{
"usr": {0, 0},
}
if err := buildTree(dirName, testTree); err != nil {
t.Fatal(err)
}
// test a directory that already exists; should just chown to the requested uid/gid
if err := MkdirAs(filepath.Join(dirName, "usr"), 0755, 99, 99); err != nil {
t.Fatal(err)
}
testTree["usr"] = node{99, 99}
verifyTree, err := readTree(dirName, "")
if err != nil {
t.Fatal(err)
}
if err := compareTrees(testTree, verifyTree); err != nil {
t.Fatal(err)
}
// create a subdir under a dir which doesn't exist--should fail
if err := MkdirAs(filepath.Join(dirName, "usr", "bin", "subdir"), 0755, 102, 102); err == nil {
t.Fatalf("Trying to create a directory with Mkdir where the parent doesn't exist should have failed")
}
// create a subdir under an existing dir; should only change the ownership of the new subdir
if err := MkdirAs(filepath.Join(dirName, "usr", "bin"), 0755, 102, 102); err != nil {
t.Fatal(err)
}
testTree["usr/bin"] = node{102, 102}
verifyTree, err = readTree(dirName, "")
if err != nil {
t.Fatal(err)
}
if err := compareTrees(testTree, verifyTree); err != nil {
t.Fatal(err)
}
}
func buildTree(base string, tree map[string]node) error {
for path, node := range tree {
fullPath := filepath.Join(base, path)
if err := os.MkdirAll(fullPath, 0755); err != nil {
return fmt.Errorf("Couldn't create path: %s; error: %v", fullPath, err)
}
if err := os.Chown(fullPath, node.uid, node.gid); err != nil {
return fmt.Errorf("Couldn't chown path: %s; error: %v", fullPath, err)
}
}
return nil
}
func readTree(base, root string) (map[string]node, error) {
tree := make(map[string]node)
dirInfos, err := ioutil.ReadDir(base)
if err != nil {
return nil, fmt.Errorf("Couldn't read directory entries for %q: %v", base, err)
}
for _, info := range dirInfos {
s := &syscall.Stat_t{}
if err := syscall.Stat(filepath.Join(base, info.Name()), s); err != nil {
return nil, fmt.Errorf("Can't stat file %q: %v", filepath.Join(base, info.Name()), err)
}
tree[filepath.Join(root, info.Name())] = node{int(s.Uid), int(s.Gid)}
if info.IsDir() {
// read the subdirectory
subtree, err := readTree(filepath.Join(base, info.Name()), filepath.Join(root, info.Name()))
if err != nil {
return nil, err
}
for path, nodeinfo := range subtree {
tree[path] = nodeinfo
}
}
}
return tree, nil
}
func compareTrees(left, right map[string]node) error {
if len(left) != len(right) {
return fmt.Errorf("Trees aren't the same size")
}
for path, nodeLeft := range left {
if nodeRight, ok := right[path]; ok {
if nodeRight.uid != nodeLeft.uid || nodeRight.gid != nodeLeft.gid {
// mismatch
return fmt.Errorf("mismatched ownership for %q: expected: %d:%d, got: %d:%d", path,
nodeLeft.uid, nodeLeft.gid, nodeRight.uid, nodeRight.gid)
}
continue
}
return fmt.Errorf("right tree didn't contain path %q", path)
}
return nil
}
func TestParseSubidFileWithNewlinesAndComments(t *testing.T) {
tmpDir, err := ioutil.TempDir("", "parsesubid")
if err != nil {
t.Fatal(err)
}
fnamePath := filepath.Join(tmpDir, "testsubuid")
fcontent := `tss:100000:65536
# empty default subuid/subgid file
dockremap:231072:65536`
if err := ioutil.WriteFile(fnamePath, []byte(fcontent), 0644); err != nil {
t.Fatal(err)
}
ranges, err := parseSubidFile(fnamePath, "dockremap")
if err != nil {
t.Fatal(err)
}
if len(ranges) != 1 {
t.Fatalf("wanted 1 element in ranges, got %d instead", len(ranges))
}
if ranges[0].Start != 231072 {
t.Fatalf("wanted 231072, got %d instead", ranges[0].Start)
}
if ranges[0].Length != 65536 {
t.Fatalf("wanted 65536, got %d instead", ranges[0].Length)
}
}

View file

@ -0,0 +1,46 @@
// Package checker provides Docker specific implementations of the go-check.Checker interface.
package checker
import (
"github.com/go-check/check"
"github.com/vdemeester/shakers"
)
// As a commodity, we bring all check.Checker variables into the current namespace to avoid having
// to think about check.X versus checker.X.
var (
DeepEquals = check.DeepEquals
ErrorMatches = check.ErrorMatches
FitsTypeOf = check.FitsTypeOf
HasLen = check.HasLen
Implements = check.Implements
IsNil = check.IsNil
Matches = check.Matches
Not = check.Not
NotNil = check.NotNil
PanicMatches = check.PanicMatches
Panics = check.Panics
Contains = shakers.Contains
ContainsAny = shakers.ContainsAny
Count = shakers.Count
Equals = shakers.Equals
EqualFold = shakers.EqualFold
False = shakers.False
GreaterOrEqualThan = shakers.GreaterOrEqualThan
GreaterThan = shakers.GreaterThan
HasPrefix = shakers.HasPrefix
HasSuffix = shakers.HasSuffix
Index = shakers.Index
IndexAny = shakers.IndexAny
IsAfter = shakers.IsAfter
IsBefore = shakers.IsBefore
IsBetween = shakers.IsBetween
IsLower = shakers.IsLower
IsUpper = shakers.IsUpper
LessOrEqualThan = shakers.LessOrEqualThan
LessThan = shakers.LessThan
TimeEquals = shakers.TimeEquals
True = shakers.True
TimeIgnore = shakers.TimeIgnore
)

View file

@ -0,0 +1,78 @@
package integration
import (
"fmt"
"os/exec"
"strings"
"time"
"github.com/go-check/check"
)
// We use the elongated quote mechanism for quoting error returns as
// the use of strconv.Quote or %q in fmt.Errorf will escape characters. This
// has a big downside on Windows where the args include paths, so instead
// of something like c:\directory\file.txt, the output would be
// c:\\directory\\file.txt. This is highly misleading.
const quote = `"`
var execCommand = exec.Command
// DockerCmdWithError executes a docker command that is supposed to fail and returns
// the output, the exit code and the error.
func DockerCmdWithError(dockerBinary string, args ...string) (string, int, error) {
return RunCommandWithOutput(execCommand(dockerBinary, args...))
}
// DockerCmdWithStdoutStderr executes a docker command and returns the content of the
// stdout, stderr and the exit code. If a check.C is passed, it will fail and stop tests
// if the error is not nil.
func DockerCmdWithStdoutStderr(dockerBinary string, c *check.C, args ...string) (string, string, int) {
stdout, stderr, status, err := RunCommandWithStdoutStderr(execCommand(dockerBinary, args...))
if c != nil {
c.Assert(err, check.IsNil, check.Commentf(quote+"%v"+quote+" failed with errors: %s, %v", strings.Join(args, " "), stderr, err))
}
return stdout, stderr, status
}
// DockerCmd executes a docker command and returns the output and the exit code. If the
// command returns an error, it will fail and stop the tests.
func DockerCmd(dockerBinary string, c *check.C, args ...string) (string, int) {
out, status, err := RunCommandWithOutput(execCommand(dockerBinary, args...))
c.Assert(err, check.IsNil, check.Commentf(quote+"%v"+quote+" failed with errors: %s, %v", strings.Join(args, " "), out, err))
return out, status
}
// DockerCmdWithTimeout executes a docker command with a timeout, and returns the output,
// the exit code and the error (if any).
func DockerCmdWithTimeout(dockerBinary string, timeout time.Duration, args ...string) (string, int, error) {
out, status, err := RunCommandWithOutputAndTimeout(execCommand(dockerBinary, args...), timeout)
if err != nil {
return out, status, fmt.Errorf(quote+"%v"+quote+" failed with errors: %v : %q", strings.Join(args, " "), err, out)
}
return out, status, err
}
// DockerCmdInDir executes a docker command in a directory and returns the output, the
// exit code and the error (if any).
func DockerCmdInDir(dockerBinary string, path string, args ...string) (string, int, error) {
dockerCommand := execCommand(dockerBinary, args...)
dockerCommand.Dir = path
out, status, err := RunCommandWithOutput(dockerCommand)
if err != nil {
return out, status, fmt.Errorf(quote+"%v"+quote+" failed with errors: %v : %q", strings.Join(args, " "), err, out)
}
return out, status, err
}
// DockerCmdInDirWithTimeout executes a docker command in a directory with a timeout and
// returns the output, the exit code and the error (if any).
func DockerCmdInDirWithTimeout(dockerBinary string, timeout time.Duration, path string, args ...string) (string, int, error) {
dockerCommand := execCommand(dockerBinary, args...)
dockerCommand.Dir = path
out, status, err := RunCommandWithOutputAndTimeout(dockerCommand, timeout)
if err != nil {
return out, status, fmt.Errorf(quote+"%v"+quote+" failed with errors: %v : %q", strings.Join(args, " "), err, out)
}
return out, status, err
}

View file

@ -0,0 +1,405 @@
package integration
import (
"fmt"
"os"
"os/exec"
"testing"
"io/ioutil"
"strings"
"time"
"github.com/go-check/check"
)
const dockerBinary = "docker"
// Setup go-check for this test
func Test(t *testing.T) {
check.TestingT(t)
}
func init() {
check.Suite(&DockerCmdSuite{})
}
type DockerCmdSuite struct{}
// Fake the exec.Command to use our mock.
func (s *DockerCmdSuite) SetUpTest(c *check.C) {
execCommand = fakeExecCommand
}
// And bring it back to normal after the test.
func (s *DockerCmdSuite) TearDownTest(c *check.C) {
execCommand = exec.Command
}
// DockerCmdWithError tests
func (s *DockerCmdSuite) TestDockerCmdWithError(c *check.C) {
cmds := []struct {
binary string
args []string
expectedOut string
expectedExitCode int
expectedError error
}{
{
"doesnotexists",
[]string{},
"Command doesnotexists not found.",
1,
fmt.Errorf("exit status 1"),
},
{
dockerBinary,
[]string{"an", "error"},
"an error has occurred",
1,
fmt.Errorf("exit status 1"),
},
{
dockerBinary,
[]string{"an", "exitCode", "127"},
"an error has occurred with exitCode 127",
127,
fmt.Errorf("exit status 127"),
},
{
dockerBinary,
[]string{"run", "-ti", "ubuntu", "echo", "hello"},
"hello",
0,
nil,
},
}
for _, cmd := range cmds {
out, exitCode, error := DockerCmdWithError(cmd.binary, cmd.args...)
c.Assert(out, check.Equals, cmd.expectedOut, check.Commentf("Expected output %q for arguments %v, got %q", cmd.expectedOut, cmd.args, out))
c.Assert(exitCode, check.Equals, cmd.expectedExitCode, check.Commentf("Expected exitCode %q for arguments %v, got %q", cmd.expectedExitCode, cmd.args, exitCode))
if cmd.expectedError != nil {
c.Assert(error, check.NotNil, check.Commentf("Expected an error %q, got nothing", cmd.expectedError))
c.Assert(error.Error(), check.Equals, cmd.expectedError.Error(), check.Commentf("Expected error %q for arguments %v, got %q", cmd.expectedError.Error(), cmd.args, error.Error()))
} else {
c.Assert(error, check.IsNil, check.Commentf("Expected no error, got %v", error))
}
}
}
// DockerCmdWithStdoutStderr tests
type dockerCmdWithStdoutStderrErrorSuite struct{}
func (s *dockerCmdWithStdoutStderrErrorSuite) Test(c *check.C) {
// Should fail, the test too
DockerCmdWithStdoutStderr(dockerBinary, c, "an", "error")
}
type dockerCmdWithStdoutStderrSuccessSuite struct{}
func (s *dockerCmdWithStdoutStderrSuccessSuite) Test(c *check.C) {
stdout, stderr, exitCode := DockerCmdWithStdoutStderr(dockerBinary, c, "run", "-ti", "ubuntu", "echo", "hello")
c.Assert(stdout, check.Equals, "hello")
c.Assert(stderr, check.Equals, "")
c.Assert(exitCode, check.Equals, 0)
}
func (s *DockerCmdSuite) TestDockerCmdWithStdoutStderrError(c *check.C) {
// Run error suite, should fail.
output := String{}
result := check.Run(&dockerCmdWithStdoutStderrErrorSuite{}, &check.RunConf{Output: &output})
c.Check(result.Succeeded, check.Equals, 0)
c.Check(result.Failed, check.Equals, 1)
}
func (s *DockerCmdSuite) TestDockerCmdWithStdoutStderrSuccess(c *check.C) {
// Run error suite, should fail.
output := String{}
result := check.Run(&dockerCmdWithStdoutStderrSuccessSuite{}, &check.RunConf{Output: &output})
c.Check(result.Succeeded, check.Equals, 1)
c.Check(result.Failed, check.Equals, 0)
}
// DockerCmd tests
type dockerCmdErrorSuite struct{}
func (s *dockerCmdErrorSuite) Test(c *check.C) {
// Should fail, the test too
DockerCmd(dockerBinary, c, "an", "error")
}
type dockerCmdSuccessSuite struct{}
func (s *dockerCmdSuccessSuite) Test(c *check.C) {
stdout, exitCode := DockerCmd(dockerBinary, c, "run", "-ti", "ubuntu", "echo", "hello")
c.Assert(stdout, check.Equals, "hello")
c.Assert(exitCode, check.Equals, 0)
}
func (s *DockerCmdSuite) TestDockerCmdError(c *check.C) {
// Run error suite, should fail.
output := String{}
result := check.Run(&dockerCmdErrorSuite{}, &check.RunConf{Output: &output})
c.Check(result.Succeeded, check.Equals, 0)
c.Check(result.Failed, check.Equals, 1)
}
func (s *DockerCmdSuite) TestDockerCmdSuccess(c *check.C) {
// Run error suite, should fail.
output := String{}
result := check.Run(&dockerCmdSuccessSuite{}, &check.RunConf{Output: &output})
c.Check(result.Succeeded, check.Equals, 1)
c.Check(result.Failed, check.Equals, 0)
}
// DockerCmdWithTimeout tests
func (s *DockerCmdSuite) TestDockerCmdWithTimeout(c *check.C) {
cmds := []struct {
binary string
args []string
timeout time.Duration
expectedOut string
expectedExitCode int
expectedError error
}{
{
"doesnotexists",
[]string{},
200 * time.Millisecond,
`Command doesnotexists not found.`,
1,
fmt.Errorf(`"" failed with errors: exit status 1 : "Command doesnotexists not found."`),
},
{
dockerBinary,
[]string{"an", "error"},
200 * time.Millisecond,
`an error has occurred`,
1,
fmt.Errorf(`"an error" failed with errors: exit status 1 : "an error has occurred"`),
},
{
dockerBinary,
[]string{"a", "command", "that", "times", "out"},
5 * time.Millisecond,
"",
0,
fmt.Errorf(`"a command that times out" failed with errors: command timed out : ""`),
},
{
dockerBinary,
[]string{"run", "-ti", "ubuntu", "echo", "hello"},
200 * time.Millisecond,
"hello",
0,
nil,
},
}
for _, cmd := range cmds {
out, exitCode, error := DockerCmdWithTimeout(cmd.binary, cmd.timeout, cmd.args...)
c.Assert(out, check.Equals, cmd.expectedOut, check.Commentf("Expected output %q for arguments %v, got %q", cmd.expectedOut, cmd.args, out))
c.Assert(exitCode, check.Equals, cmd.expectedExitCode, check.Commentf("Expected exitCode %q for arguments %v, got %q", cmd.expectedExitCode, cmd.args, exitCode))
if cmd.expectedError != nil {
c.Assert(error, check.NotNil, check.Commentf("Expected an error %q, got nothing", cmd.expectedError))
c.Assert(error.Error(), check.Equals, cmd.expectedError.Error(), check.Commentf("Expected error %q for arguments %v, got %q", cmd.expectedError.Error(), cmd.args, error.Error()))
} else {
c.Assert(error, check.IsNil, check.Commentf("Expected no error, got %v", error))
}
}
}
// DockerCmdInDir tests
func (s *DockerCmdSuite) TestDockerCmdInDir(c *check.C) {
tempFolder, err := ioutil.TempDir("", "test-docker-cmd-in-dir")
c.Assert(err, check.IsNil)
cmds := []struct {
binary string
args []string
expectedOut string
expectedExitCode int
expectedError error
}{
{
"doesnotexists",
[]string{},
`Command doesnotexists not found.`,
1,
fmt.Errorf(`"dir:%s" failed with errors: exit status 1 : "Command doesnotexists not found."`, tempFolder),
},
{
dockerBinary,
[]string{"an", "error"},
`an error has occurred`,
1,
fmt.Errorf(`"dir:%s an error" failed with errors: exit status 1 : "an error has occurred"`, tempFolder),
},
{
dockerBinary,
[]string{"run", "-ti", "ubuntu", "echo", "hello"},
"hello",
0,
nil,
},
}
for _, cmd := range cmds {
// We prepend the arguments with dir:thefolder.. the fake command will check
// that the current workdir is the same as the one we are passing.
args := append([]string{"dir:" + tempFolder}, cmd.args...)
out, exitCode, error := DockerCmdInDir(cmd.binary, tempFolder, args...)
c.Assert(out, check.Equals, cmd.expectedOut, check.Commentf("Expected output %q for arguments %v, got %q", cmd.expectedOut, cmd.args, out))
c.Assert(exitCode, check.Equals, cmd.expectedExitCode, check.Commentf("Expected exitCode %q for arguments %v, got %q", cmd.expectedExitCode, cmd.args, exitCode))
if cmd.expectedError != nil {
c.Assert(error, check.NotNil, check.Commentf("Expected an error %q, got nothing", cmd.expectedError))
c.Assert(error.Error(), check.Equals, cmd.expectedError.Error(), check.Commentf("Expected error %q for arguments %v, got %q", cmd.expectedError.Error(), cmd.args, error.Error()))
} else {
c.Assert(error, check.IsNil, check.Commentf("Expected no error, got %v", error))
}
}
}
// DockerCmdInDirWithTimeout tests
func (s *DockerCmdSuite) TestDockerCmdInDirWithTimeout(c *check.C) {
tempFolder, err := ioutil.TempDir("", "test-docker-cmd-in-dir")
c.Assert(err, check.IsNil)
cmds := []struct {
binary string
args []string
timeout time.Duration
expectedOut string
expectedExitCode int
expectedError error
}{
{
"doesnotexists",
[]string{},
200 * time.Millisecond,
`Command doesnotexists not found.`,
1,
fmt.Errorf(`"dir:%s" failed with errors: exit status 1 : "Command doesnotexists not found."`, tempFolder),
},
{
dockerBinary,
[]string{"an", "error"},
200 * time.Millisecond,
`an error has occurred`,
1,
fmt.Errorf(`"dir:%s an error" failed with errors: exit status 1 : "an error has occurred"`, tempFolder),
},
{
dockerBinary,
[]string{"a", "command", "that", "times", "out"},
5 * time.Millisecond,
"",
0,
fmt.Errorf(`"dir:%s a command that times out" failed with errors: command timed out : ""`, tempFolder),
},
{
dockerBinary,
[]string{"run", "-ti", "ubuntu", "echo", "hello"},
200 * time.Millisecond,
"hello",
0,
nil,
},
}
for _, cmd := range cmds {
// We prepend the arguments with dir:thefolder.. the fake command will check
// that the current workdir is the same as the one we are passing.
args := append([]string{"dir:" + tempFolder}, cmd.args...)
out, exitCode, error := DockerCmdInDirWithTimeout(cmd.binary, cmd.timeout, tempFolder, args...)
c.Assert(out, check.Equals, cmd.expectedOut, check.Commentf("Expected output %q for arguments %v, got %q", cmd.expectedOut, cmd.args, out))
c.Assert(exitCode, check.Equals, cmd.expectedExitCode, check.Commentf("Expected exitCode %q for arguments %v, got %q", cmd.expectedExitCode, cmd.args, exitCode))
if cmd.expectedError != nil {
c.Assert(error, check.NotNil, check.Commentf("Expected an error %q, got nothing", cmd.expectedError))
c.Assert(error.Error(), check.Equals, cmd.expectedError.Error(), check.Commentf("Expected error %q for arguments %v, got %q", cmd.expectedError.Error(), cmd.args, error.Error()))
} else {
c.Assert(error, check.IsNil, check.Commentf("Expected no error, got %v", error))
}
}
}
// Helpers :)
// Type implementing the io.Writer interface for analyzing output.
type String struct {
value string
}
// The only function required by the io.Writer interface. Will append
// written data to the String.value string.
func (s *String) Write(p []byte) (n int, err error) {
s.value += string(p)
return len(p), nil
}
// Helper function that mock the exec.Command call (and call the test binary)
func fakeExecCommand(command string, args ...string) *exec.Cmd {
cs := []string{"-test.run=TestHelperProcess", "--", command}
cs = append(cs, args...)
cmd := exec.Command(os.Args[0], cs...)
cmd.Env = []string{"GO_WANT_HELPER_PROCESS=1"}
return cmd
}
func TestHelperProcess(t *testing.T) {
if os.Getenv("GO_WANT_HELPER_PROCESS") != "1" {
return
}
args := os.Args
// Previous arguments are tests stuff, that looks like :
// /tmp/go-build970079519/…/_test/integration.test -test.run=TestHelperProcess --
cmd, args := args[3], args[4:]
// Handle the case where args[0] is dir:...
if len(args) > 0 && strings.HasPrefix(args[0], "dir:") {
expectedCwd := args[0][4:]
if len(args) > 1 {
args = args[1:]
}
cwd, err := os.Getwd()
if err != nil {
fmt.Fprintf(os.Stderr, "Failed to get workingdir: %v", err)
os.Exit(1)
}
// This checks that the given path is the same as the currend working dire
if expectedCwd != cwd {
fmt.Fprintf(os.Stderr, "Current workdir should be %q, but is %q", expectedCwd, cwd)
}
}
switch cmd {
case dockerBinary:
argsStr := strings.Join(args, " ")
switch argsStr {
case "an exitCode 127":
fmt.Fprintf(os.Stderr, "an error has occurred with exitCode 127")
os.Exit(127)
case "an error":
fmt.Fprintf(os.Stderr, "an error has occurred")
os.Exit(1)
case "a command that times out":
time.Sleep(10 * time.Second)
fmt.Fprintf(os.Stdout, "too long, should be killed")
// A random exit code (that should never happened in tests)
os.Exit(7)
case "run -ti ubuntu echo hello":
fmt.Fprintf(os.Stdout, "hello")
default:
fmt.Fprintf(os.Stdout, "no arguments")
}
default:
fmt.Fprintf(os.Stderr, "Command %s not found.", cmd)
os.Exit(1)
}
// some code here to check arguments perhaps?
os.Exit(0)
}

View file

@ -0,0 +1,361 @@
package integration
import (
"archive/tar"
"bytes"
"encoding/json"
"errors"
"fmt"
"io"
"os"
"os/exec"
"path/filepath"
"reflect"
"strings"
"syscall"
"time"
"github.com/containers/storage/pkg/stringutils"
)
// GetExitCode returns the ExitStatus of the specified error if its type is
// exec.ExitError, returns 0 and an error otherwise.
func GetExitCode(err error) (int, error) {
exitCode := 0
if exiterr, ok := err.(*exec.ExitError); ok {
if procExit, ok := exiterr.Sys().(syscall.WaitStatus); ok {
return procExit.ExitStatus(), nil
}
}
return exitCode, fmt.Errorf("failed to get exit code")
}
// ProcessExitCode process the specified error and returns the exit status code
// if the error was of type exec.ExitError, returns nothing otherwise.
func ProcessExitCode(err error) (exitCode int) {
if err != nil {
var exiterr error
if exitCode, exiterr = GetExitCode(err); exiterr != nil {
// TODO: Fix this so we check the error's text.
// we've failed to retrieve exit code, so we set it to 127
exitCode = 127
}
}
return
}
// IsKilled process the specified error and returns whether the process was killed or not.
func IsKilled(err error) bool {
if exitErr, ok := err.(*exec.ExitError); ok {
status, ok := exitErr.Sys().(syscall.WaitStatus)
if !ok {
return false
}
// status.ExitStatus() is required on Windows because it does not
// implement Signal() nor Signaled(). Just check it had a bad exit
// status could mean it was killed (and in tests we do kill)
return (status.Signaled() && status.Signal() == os.Kill) || status.ExitStatus() != 0
}
return false
}
// RunCommandWithOutput runs the specified command and returns the combined output (stdout/stderr)
// with the exitCode different from 0 and the error if something bad happened
func RunCommandWithOutput(cmd *exec.Cmd) (output string, exitCode int, err error) {
exitCode = 0
out, err := cmd.CombinedOutput()
exitCode = ProcessExitCode(err)
output = string(out)
return
}
// RunCommandWithStdoutStderr runs the specified command and returns stdout and stderr separately
// with the exitCode different from 0 and the error if something bad happened
func RunCommandWithStdoutStderr(cmd *exec.Cmd) (stdout string, stderr string, exitCode int, err error) {
var (
stderrBuffer, stdoutBuffer bytes.Buffer
)
exitCode = 0
cmd.Stderr = &stderrBuffer
cmd.Stdout = &stdoutBuffer
err = cmd.Run()
exitCode = ProcessExitCode(err)
stdout = stdoutBuffer.String()
stderr = stderrBuffer.String()
return
}
// RunCommandWithOutputForDuration runs the specified command "timeboxed" by the specified duration.
// If the process is still running when the timebox is finished, the process will be killed and .
// It will returns the output with the exitCode different from 0 and the error if something bad happened
// and a boolean whether it has been killed or not.
func RunCommandWithOutputForDuration(cmd *exec.Cmd, duration time.Duration) (output string, exitCode int, timedOut bool, err error) {
var outputBuffer bytes.Buffer
if cmd.Stdout != nil {
err = errors.New("cmd.Stdout already set")
return
}
cmd.Stdout = &outputBuffer
if cmd.Stderr != nil {
err = errors.New("cmd.Stderr already set")
return
}
cmd.Stderr = &outputBuffer
// Start the command in the main thread..
err = cmd.Start()
if err != nil {
err = fmt.Errorf("Fail to start command %v : %v", cmd, err)
}
type exitInfo struct {
exitErr error
exitCode int
}
done := make(chan exitInfo, 1)
go func() {
// And wait for it to exit in the goroutine :)
info := exitInfo{}
info.exitErr = cmd.Wait()
info.exitCode = ProcessExitCode(info.exitErr)
done <- info
}()
select {
case <-time.After(duration):
killErr := cmd.Process.Kill()
if killErr != nil {
fmt.Printf("failed to kill (pid=%d): %v\n", cmd.Process.Pid, killErr)
}
timedOut = true
case info := <-done:
err = info.exitErr
exitCode = info.exitCode
}
output = outputBuffer.String()
return
}
var errCmdTimeout = fmt.Errorf("command timed out")
// RunCommandWithOutputAndTimeout runs the specified command "timeboxed" by the specified duration.
// It returns the output with the exitCode different from 0 and the error if something bad happened or
// if the process timed out (and has been killed).
func RunCommandWithOutputAndTimeout(cmd *exec.Cmd, timeout time.Duration) (output string, exitCode int, err error) {
var timedOut bool
output, exitCode, timedOut, err = RunCommandWithOutputForDuration(cmd, timeout)
if timedOut {
err = errCmdTimeout
}
return
}
// RunCommand runs the specified command and returns the exitCode different from 0
// and the error if something bad happened.
func RunCommand(cmd *exec.Cmd) (exitCode int, err error) {
exitCode = 0
err = cmd.Run()
exitCode = ProcessExitCode(err)
return
}
// RunCommandPipelineWithOutput runs the array of commands with the output
// of each pipelined with the following (like cmd1 | cmd2 | cmd3 would do).
// It returns the final output, the exitCode different from 0 and the error
// if something bad happened.
func RunCommandPipelineWithOutput(cmds ...*exec.Cmd) (output string, exitCode int, err error) {
if len(cmds) < 2 {
return "", 0, errors.New("pipeline does not have multiple cmds")
}
// connect stdin of each cmd to stdout pipe of previous cmd
for i, cmd := range cmds {
if i > 0 {
prevCmd := cmds[i-1]
cmd.Stdin, err = prevCmd.StdoutPipe()
if err != nil {
return "", 0, fmt.Errorf("cannot set stdout pipe for %s: %v", cmd.Path, err)
}
}
}
// start all cmds except the last
for _, cmd := range cmds[:len(cmds)-1] {
if err = cmd.Start(); err != nil {
return "", 0, fmt.Errorf("starting %s failed with error: %v", cmd.Path, err)
}
}
var pipelineError error
defer func() {
// wait all cmds except the last to release their resources
for _, cmd := range cmds[:len(cmds)-1] {
if err := cmd.Wait(); err != nil {
pipelineError = fmt.Errorf("command %s failed with error: %v", cmd.Path, err)
break
}
}
}()
if pipelineError != nil {
return "", 0, pipelineError
}
// wait on last cmd
return RunCommandWithOutput(cmds[len(cmds)-1])
}
// UnmarshalJSON deserialize a JSON in the given interface.
func UnmarshalJSON(data []byte, result interface{}) error {
if err := json.Unmarshal(data, result); err != nil {
return err
}
return nil
}
// ConvertSliceOfStringsToMap converts a slices of string in a map
// with the strings as key and an empty string as values.
func ConvertSliceOfStringsToMap(input []string) map[string]struct{} {
output := make(map[string]struct{})
for _, v := range input {
output[v] = struct{}{}
}
return output
}
// CompareDirectoryEntries compares two sets of FileInfo (usually taken from a directory)
// and returns an error if different.
func CompareDirectoryEntries(e1 []os.FileInfo, e2 []os.FileInfo) error {
var (
e1Entries = make(map[string]struct{})
e2Entries = make(map[string]struct{})
)
for _, e := range e1 {
e1Entries[e.Name()] = struct{}{}
}
for _, e := range e2 {
e2Entries[e.Name()] = struct{}{}
}
if !reflect.DeepEqual(e1Entries, e2Entries) {
return fmt.Errorf("entries differ")
}
return nil
}
// ListTar lists the entries of a tar.
func ListTar(f io.Reader) ([]string, error) {
tr := tar.NewReader(f)
var entries []string
for {
th, err := tr.Next()
if err == io.EOF {
// end of tar archive
return entries, nil
}
if err != nil {
return entries, err
}
entries = append(entries, th.Name)
}
}
// RandomTmpDirPath provides a temporary path with rand string appended.
// does not create or checks if it exists.
func RandomTmpDirPath(s string, platform string) string {
tmp := "/tmp"
if platform == "windows" {
tmp = os.Getenv("TEMP")
}
path := filepath.Join(tmp, fmt.Sprintf("%s.%s", s, stringutils.GenerateRandomAlphaOnlyString(10)))
if platform == "windows" {
return filepath.FromSlash(path) // Using \
}
return filepath.ToSlash(path) // Using /
}
// ConsumeWithSpeed reads chunkSize bytes from reader before sleeping
// for interval duration. Returns total read bytes. Send true to the
// stop channel to return before reading to EOF on the reader.
func ConsumeWithSpeed(reader io.Reader, chunkSize int, interval time.Duration, stop chan bool) (n int, err error) {
buffer := make([]byte, chunkSize)
for {
var readBytes int
readBytes, err = reader.Read(buffer)
n += readBytes
if err != nil {
if err == io.EOF {
err = nil
}
return
}
select {
case <-stop:
return
case <-time.After(interval):
}
}
}
// ParseCgroupPaths parses 'procCgroupData', which is output of '/proc/<pid>/cgroup', and returns
// a map which cgroup name as key and path as value.
func ParseCgroupPaths(procCgroupData string) map[string]string {
cgroupPaths := map[string]string{}
for _, line := range strings.Split(procCgroupData, "\n") {
parts := strings.Split(line, ":")
if len(parts) != 3 {
continue
}
cgroupPaths[parts[1]] = parts[2]
}
return cgroupPaths
}
// ChannelBuffer holds a chan of byte array that can be populate in a goroutine.
type ChannelBuffer struct {
C chan []byte
}
// Write implements Writer.
func (c *ChannelBuffer) Write(b []byte) (int, error) {
c.C <- b
return len(b), nil
}
// Close closes the go channel.
func (c *ChannelBuffer) Close() error {
close(c.C)
return nil
}
// ReadTimeout reads the content of the channel in the specified byte array with
// the specified duration as timeout.
func (c *ChannelBuffer) ReadTimeout(p []byte, n time.Duration) (int, error) {
select {
case b := <-c.C:
return copy(p[0:], b), nil
case <-time.After(n):
return -1, fmt.Errorf("timeout reading from channel")
}
}
// RunAtDifferentDate runs the specified function with the given time.
// It changes the date of the system, which can led to weird behaviors.
func RunAtDifferentDate(date time.Time, block func()) {
// Layout for date. MMDDhhmmYYYY
const timeLayout = "010203042006"
// Ensure we bring time back to now
now := time.Now().Format(timeLayout)
dateReset := exec.Command("date", now)
defer RunCommand(dateReset)
dateChange := exec.Command("date", date.Format(timeLayout))
RunCommand(dateChange)
block()
return
}

View file

@ -0,0 +1,572 @@
package integration
import (
"io"
"io/ioutil"
"os"
"os/exec"
"path/filepath"
"runtime"
"strconv"
"strings"
"testing"
"time"
)
func TestIsKilledFalseWithNonKilledProcess(t *testing.T) {
var lsCmd *exec.Cmd
if runtime.GOOS != "windows" {
lsCmd = exec.Command("ls")
} else {
lsCmd = exec.Command("cmd", "/c", "dir")
}
err := lsCmd.Run()
if IsKilled(err) {
t.Fatalf("Expected the ls command to not be killed, was.")
}
}
func TestIsKilledTrueWithKilledProcess(t *testing.T) {
var longCmd *exec.Cmd
if runtime.GOOS != "windows" {
longCmd = exec.Command("top")
} else {
longCmd = exec.Command("powershell", "while ($true) { sleep 1 }")
}
// Start a command
err := longCmd.Start()
if err != nil {
t.Fatal(err)
}
// Capture the error when *dying*
done := make(chan error, 1)
go func() {
done <- longCmd.Wait()
}()
// Then kill it
longCmd.Process.Kill()
// Get the error
err = <-done
if !IsKilled(err) {
t.Fatalf("Expected the command to be killed, was not.")
}
}
func TestRunCommandWithOutput(t *testing.T) {
var (
echoHelloWorldCmd *exec.Cmd
expected string
)
if runtime.GOOS != "windows" {
echoHelloWorldCmd = exec.Command("echo", "hello", "world")
expected = "hello world\n"
} else {
echoHelloWorldCmd = exec.Command("cmd", "/s", "/c", "echo", "hello", "world")
expected = "hello world\r\n"
}
out, exitCode, err := RunCommandWithOutput(echoHelloWorldCmd)
if out != expected || exitCode != 0 || err != nil {
t.Fatalf("Expected command to output %s, got %s, %v with exitCode %v", expected, out, err, exitCode)
}
}
func TestRunCommandWithOutputError(t *testing.T) {
var (
p string
wrongCmd *exec.Cmd
expected string
expectedExitCode int
)
if runtime.GOOS != "windows" {
p = "$PATH"
wrongCmd = exec.Command("ls", "-z")
expected = `ls: invalid option -- 'z'
Try 'ls --help' for more information.
`
expectedExitCode = 2
} else {
p = "%PATH%"
wrongCmd = exec.Command("cmd", "/s", "/c", "dir", "/Z")
expected = "Invalid switch - " + strconv.Quote("Z") + ".\r\n"
expectedExitCode = 1
}
cmd := exec.Command("doesnotexists")
out, exitCode, err := RunCommandWithOutput(cmd)
expectedError := `exec: "doesnotexists": executable file not found in ` + p
if out != "" || exitCode != 127 || err == nil || err.Error() != expectedError {
t.Fatalf("Expected command to output %s, got %s, %v with exitCode %v", expectedError, out, err, exitCode)
}
out, exitCode, err = RunCommandWithOutput(wrongCmd)
if out != expected || exitCode != expectedExitCode || err == nil || !strings.Contains(err.Error(), "exit status "+strconv.Itoa(expectedExitCode)) {
t.Fatalf("Expected command to output %s, got out:xxx%sxxx, err:%v with exitCode %v", expected, out, err, exitCode)
}
}
func TestRunCommandWithStdoutStderr(t *testing.T) {
echoHelloWorldCmd := exec.Command("echo", "hello", "world")
stdout, stderr, exitCode, err := RunCommandWithStdoutStderr(echoHelloWorldCmd)
expected := "hello world\n"
if stdout != expected || stderr != "" || exitCode != 0 || err != nil {
t.Fatalf("Expected command to output %s, got stdout:%s, stderr:%s, err:%v with exitCode %v", expected, stdout, stderr, err, exitCode)
}
}
func TestRunCommandWithStdoutStderrError(t *testing.T) {
p := "$PATH"
if runtime.GOOS == "windows" {
p = "%PATH%"
}
cmd := exec.Command("doesnotexists")
stdout, stderr, exitCode, err := RunCommandWithStdoutStderr(cmd)
expectedError := `exec: "doesnotexists": executable file not found in ` + p
if stdout != "" || stderr != "" || exitCode != 127 || err == nil || err.Error() != expectedError {
t.Fatalf("Expected command to output out:%s, stderr:%s, got stdout:%s, stderr:%s, err:%v with exitCode %v", "", "", stdout, stderr, err, exitCode)
}
wrongLsCmd := exec.Command("ls", "-z")
expected := `ls: invalid option -- 'z'
Try 'ls --help' for more information.
`
stdout, stderr, exitCode, err = RunCommandWithStdoutStderr(wrongLsCmd)
if stdout != "" && stderr != expected || exitCode != 2 || err == nil || err.Error() != "exit status 2" {
t.Fatalf("Expected command to output out:%s, stderr:%s, got stdout:%s, stderr:%s, err:%v with exitCode %v", "", expectedError, stdout, stderr, err, exitCode)
}
}
func TestRunCommandWithOutputForDurationFinished(t *testing.T) {
// TODO Windows: Port this test
if runtime.GOOS == "windows" {
t.Skip("Needs porting to Windows")
}
cmd := exec.Command("ls")
out, exitCode, timedOut, err := RunCommandWithOutputForDuration(cmd, 50*time.Millisecond)
if out == "" || exitCode != 0 || timedOut || err != nil {
t.Fatalf("Expected the command to run for less 50 milliseconds and thus not time out, but did not : out:[%s], exitCode:[%d], timedOut:[%v], err:[%v]", out, exitCode, timedOut, err)
}
}
func TestRunCommandWithOutputForDurationKilled(t *testing.T) {
// TODO Windows: Port this test
if runtime.GOOS == "windows" {
t.Skip("Needs porting to Windows")
}
cmd := exec.Command("sh", "-c", "while true ; do echo 1 ; sleep .1 ; done")
out, exitCode, timedOut, err := RunCommandWithOutputForDuration(cmd, 500*time.Millisecond)
ones := strings.Split(out, "\n")
if len(ones) != 6 || exitCode != 0 || !timedOut || err != nil {
t.Fatalf("Expected the command to run for 500 milliseconds (and thus print six lines (five with 1, one empty) and time out, but did not : out:[%s], exitCode:%d, timedOut:%v, err:%v", out, exitCode, timedOut, err)
}
}
func TestRunCommandWithOutputForDurationErrors(t *testing.T) {
cmd := exec.Command("ls")
cmd.Stdout = os.Stdout
if _, _, _, err := RunCommandWithOutputForDuration(cmd, 1*time.Millisecond); err == nil || err.Error() != "cmd.Stdout already set" {
t.Fatalf("Expected an error as cmd.Stdout was already set, did not (err:%s).", err)
}
cmd = exec.Command("ls")
cmd.Stderr = os.Stderr
if _, _, _, err := RunCommandWithOutputForDuration(cmd, 1*time.Millisecond); err == nil || err.Error() != "cmd.Stderr already set" {
t.Fatalf("Expected an error as cmd.Stderr was already set, did not (err:%s).", err)
}
}
func TestRunCommandWithOutputAndTimeoutFinished(t *testing.T) {
// TODO Windows: Port this test
if runtime.GOOS == "windows" {
t.Skip("Needs porting to Windows")
}
cmd := exec.Command("ls")
out, exitCode, err := RunCommandWithOutputAndTimeout(cmd, 50*time.Millisecond)
if out == "" || exitCode != 0 || err != nil {
t.Fatalf("Expected the command to run for less 50 milliseconds and thus not time out, but did not : out:[%s], exitCode:[%d], err:[%v]", out, exitCode, err)
}
}
func TestRunCommandWithOutputAndTimeoutKilled(t *testing.T) {
// TODO Windows: Port this test
if runtime.GOOS == "windows" {
t.Skip("Needs porting to Windows")
}
cmd := exec.Command("sh", "-c", "while true ; do echo 1 ; sleep .1 ; done")
out, exitCode, err := RunCommandWithOutputAndTimeout(cmd, 500*time.Millisecond)
ones := strings.Split(out, "\n")
if len(ones) != 6 || exitCode != 0 || err == nil || err.Error() != "command timed out" {
t.Fatalf("Expected the command to run for 500 milliseconds (and thus print six lines (five with 1, one empty) and time out with an error 'command timed out', but did not : out:[%s], exitCode:%d, err:%v", out, exitCode, err)
}
}
func TestRunCommandWithOutputAndTimeoutErrors(t *testing.T) {
cmd := exec.Command("ls")
cmd.Stdout = os.Stdout
if _, _, err := RunCommandWithOutputAndTimeout(cmd, 1*time.Millisecond); err == nil || err.Error() != "cmd.Stdout already set" {
t.Fatalf("Expected an error as cmd.Stdout was already set, did not (err:%s).", err)
}
cmd = exec.Command("ls")
cmd.Stderr = os.Stderr
if _, _, err := RunCommandWithOutputAndTimeout(cmd, 1*time.Millisecond); err == nil || err.Error() != "cmd.Stderr already set" {
t.Fatalf("Expected an error as cmd.Stderr was already set, did not (err:%s).", err)
}
}
func TestRunCommand(t *testing.T) {
// TODO Windows: Port this test
if runtime.GOOS == "windows" {
t.Skip("Needs porting to Windows")
}
p := "$PATH"
if runtime.GOOS == "windows" {
p = "%PATH%"
}
lsCmd := exec.Command("ls")
exitCode, err := RunCommand(lsCmd)
if exitCode != 0 || err != nil {
t.Fatalf("Expected runCommand to run the command successfully, got: exitCode:%d, err:%v", exitCode, err)
}
var expectedError string
exitCode, err = RunCommand(exec.Command("doesnotexists"))
expectedError = `exec: "doesnotexists": executable file not found in ` + p
if exitCode != 127 || err == nil || err.Error() != expectedError {
t.Fatalf("Expected runCommand to run the command successfully, got: exitCode:%d, err:%v", exitCode, err)
}
wrongLsCmd := exec.Command("ls", "-z")
expected := 2
expectedError = `exit status 2`
exitCode, err = RunCommand(wrongLsCmd)
if exitCode != expected || err == nil || err.Error() != expectedError {
t.Fatalf("Expected runCommand to run the command successfully, got: exitCode:%d, err:%v", exitCode, err)
}
}
func TestRunCommandPipelineWithOutputWithNotEnoughCmds(t *testing.T) {
_, _, err := RunCommandPipelineWithOutput(exec.Command("ls"))
expectedError := "pipeline does not have multiple cmds"
if err == nil || err.Error() != expectedError {
t.Fatalf("Expected an error with %s, got err:%s", expectedError, err)
}
}
func TestRunCommandPipelineWithOutputErrors(t *testing.T) {
p := "$PATH"
if runtime.GOOS == "windows" {
p = "%PATH%"
}
cmd1 := exec.Command("ls")
cmd1.Stdout = os.Stdout
cmd2 := exec.Command("anything really")
_, _, err := RunCommandPipelineWithOutput(cmd1, cmd2)
if err == nil || err.Error() != "cannot set stdout pipe for anything really: exec: Stdout already set" {
t.Fatalf("Expected an error, got %v", err)
}
cmdWithError := exec.Command("doesnotexists")
cmdCat := exec.Command("cat")
_, _, err = RunCommandPipelineWithOutput(cmdWithError, cmdCat)
if err == nil || err.Error() != `starting doesnotexists failed with error: exec: "doesnotexists": executable file not found in `+p {
t.Fatalf("Expected an error, got %v", err)
}
}
func TestRunCommandPipelineWithOutput(t *testing.T) {
cmds := []*exec.Cmd{
// Print 2 characters
exec.Command("echo", "-n", "11"),
// Count the number or char from stdin (previous command)
exec.Command("wc", "-m"),
}
out, exitCode, err := RunCommandPipelineWithOutput(cmds...)
expectedOutput := "2\n"
if out != expectedOutput || exitCode != 0 || err != nil {
t.Fatalf("Expected %s for commands %v, got out:%s, exitCode:%d, err:%v", expectedOutput, cmds, out, exitCode, err)
}
}
// Simple simple test as it is just a passthrough for json.Unmarshal
func TestUnmarshalJSON(t *testing.T) {
emptyResult := struct{}{}
if err := UnmarshalJSON([]byte(""), &emptyResult); err == nil {
t.Fatalf("Expected an error, got nothing")
}
result := struct{ Name string }{}
if err := UnmarshalJSON([]byte(`{"name": "name"}`), &result); err != nil {
t.Fatal(err)
}
if result.Name != "name" {
t.Fatalf("Expected result.name to be 'name', was '%s'", result.Name)
}
}
func TestConvertSliceOfStringsToMap(t *testing.T) {
input := []string{"a", "b"}
actual := ConvertSliceOfStringsToMap(input)
for _, key := range input {
if _, ok := actual[key]; !ok {
t.Fatalf("Expected output to contains key %s, did not: %v", key, actual)
}
}
}
func TestCompareDirectoryEntries(t *testing.T) {
tmpFolder, err := ioutil.TempDir("", "integration-cli-utils-compare-directories")
if err != nil {
t.Fatal(err)
}
defer os.RemoveAll(tmpFolder)
file1 := filepath.Join(tmpFolder, "file1")
file2 := filepath.Join(tmpFolder, "file2")
os.Create(file1)
os.Create(file2)
fi1, err := os.Stat(file1)
if err != nil {
t.Fatal(err)
}
fi1bis, err := os.Stat(file1)
if err != nil {
t.Fatal(err)
}
fi2, err := os.Stat(file2)
if err != nil {
t.Fatal(err)
}
cases := []struct {
e1 []os.FileInfo
e2 []os.FileInfo
shouldError bool
}{
// Empty directories
{
[]os.FileInfo{},
[]os.FileInfo{},
false,
},
// Same FileInfos
{
[]os.FileInfo{fi1},
[]os.FileInfo{fi1},
false,
},
// Different FileInfos but same names
{
[]os.FileInfo{fi1},
[]os.FileInfo{fi1bis},
false,
},
// Different FileInfos, different names
{
[]os.FileInfo{fi1},
[]os.FileInfo{fi2},
true,
},
}
for _, elt := range cases {
err := CompareDirectoryEntries(elt.e1, elt.e2)
if elt.shouldError && err == nil {
t.Fatalf("Should have return an error, did not with %v and %v", elt.e1, elt.e2)
}
if !elt.shouldError && err != nil {
t.Fatalf("Should have not returned an error, but did : %v with %v and %v", err, elt.e1, elt.e2)
}
}
}
// FIXME make an "unhappy path" test for ListTar without "panicking" :-)
func TestListTar(t *testing.T) {
// TODO Windows: Figure out why this fails. Should be portable.
if runtime.GOOS == "windows" {
t.Skip("Failing on Windows - needs further investigation")
}
tmpFolder, err := ioutil.TempDir("", "integration-cli-utils-list-tar")
if err != nil {
t.Fatal(err)
}
defer os.RemoveAll(tmpFolder)
// Let's create a Tar file
srcFile := filepath.Join(tmpFolder, "src")
tarFile := filepath.Join(tmpFolder, "src.tar")
os.Create(srcFile)
cmd := exec.Command("sh", "-c", "tar cf "+tarFile+" "+srcFile)
_, err = cmd.CombinedOutput()
if err != nil {
t.Fatal(err)
}
reader, err := os.Open(tarFile)
if err != nil {
t.Fatal(err)
}
defer reader.Close()
entries, err := ListTar(reader)
if err != nil {
t.Fatal(err)
}
if len(entries) != 1 && entries[0] != "src" {
t.Fatalf("Expected a tar file with 1 entry (%s), got %v", srcFile, entries)
}
}
func TestRandomTmpDirPath(t *testing.T) {
path := RandomTmpDirPath("something", runtime.GOOS)
prefix := "/tmp/something"
if runtime.GOOS == "windows" {
prefix = os.Getenv("TEMP") + `\something`
}
expectedSize := len(prefix) + 11
if !strings.HasPrefix(path, prefix) {
t.Fatalf("Expected generated path to have '%s' as prefix, got %s'", prefix, path)
}
if len(path) != expectedSize {
t.Fatalf("Expected generated path to be %d, got %d", expectedSize, len(path))
}
}
func TestConsumeWithSpeed(t *testing.T) {
reader := strings.NewReader("1234567890")
chunksize := 2
bytes1, err := ConsumeWithSpeed(reader, chunksize, 1*time.Second, nil)
if err != nil {
t.Fatal(err)
}
if bytes1 != 10 {
t.Fatalf("Expected to have read 10 bytes, got %d", bytes1)
}
}
func TestConsumeWithSpeedWithStop(t *testing.T) {
reader := strings.NewReader("1234567890")
chunksize := 2
stopIt := make(chan bool)
go func() {
time.Sleep(1 * time.Millisecond)
stopIt <- true
}()
bytes1, err := ConsumeWithSpeed(reader, chunksize, 20*time.Millisecond, stopIt)
if err != nil {
t.Fatal(err)
}
if bytes1 != 2 {
t.Fatalf("Expected to have read 2 bytes, got %d", bytes1)
}
}
func TestParseCgroupPathsEmpty(t *testing.T) {
cgroupMap := ParseCgroupPaths("")
if len(cgroupMap) != 0 {
t.Fatalf("Expected an empty map, got %v", cgroupMap)
}
cgroupMap = ParseCgroupPaths("\n")
if len(cgroupMap) != 0 {
t.Fatalf("Expected an empty map, got %v", cgroupMap)
}
cgroupMap = ParseCgroupPaths("something:else\nagain:here")
if len(cgroupMap) != 0 {
t.Fatalf("Expected an empty map, got %v", cgroupMap)
}
}
func TestParseCgroupPaths(t *testing.T) {
cgroupMap := ParseCgroupPaths("2:memory:/a\n1:cpuset:/b")
if len(cgroupMap) != 2 {
t.Fatalf("Expected a map with 2 entries, got %v", cgroupMap)
}
if value, ok := cgroupMap["memory"]; !ok || value != "/a" {
t.Fatalf("Expected cgroupMap to contains an entry for 'memory' with value '/a', got %v", cgroupMap)
}
if value, ok := cgroupMap["cpuset"]; !ok || value != "/b" {
t.Fatalf("Expected cgroupMap to contains an entry for 'cpuset' with value '/b', got %v", cgroupMap)
}
}
func TestChannelBufferTimeout(t *testing.T) {
expected := "11"
buf := &ChannelBuffer{make(chan []byte, 1)}
defer buf.Close()
done := make(chan struct{}, 1)
go func() {
time.Sleep(100 * time.Millisecond)
io.Copy(buf, strings.NewReader(expected))
done <- struct{}{}
}()
// Wait long enough
b := make([]byte, 2)
_, err := buf.ReadTimeout(b, 50*time.Millisecond)
if err == nil && err.Error() != "timeout reading from channel" {
t.Fatalf("Expected an error, got %s", err)
}
<-done
}
func TestChannelBuffer(t *testing.T) {
expected := "11"
buf := &ChannelBuffer{make(chan []byte, 1)}
defer buf.Close()
go func() {
time.Sleep(100 * time.Millisecond)
io.Copy(buf, strings.NewReader(expected))
}()
// Wait long enough
b := make([]byte, 2)
_, err := buf.ReadTimeout(b, 200*time.Millisecond)
if err != nil {
t.Fatal(err)
}
if string(b) != expected {
t.Fatalf("Expected '%s', got '%s'", expected, string(b))
}
}
// FIXME doesn't work
// func TestRunAtDifferentDate(t *testing.T) {
// var date string
// // Layout for date. MMDDhhmmYYYY
// const timeLayout = "20060102"
// expectedDate := "20100201"
// theDate, err := time.Parse(timeLayout, expectedDate)
// if err != nil {
// t.Fatal(err)
// }
// RunAtDifferentDate(theDate, func() {
// cmd := exec.Command("date", "+%Y%M%d")
// out, err := cmd.Output()
// if err != nil {
// t.Fatal(err)
// }
// date = string(out)
// })
// }

View file

@ -0,0 +1,75 @@
package ioutils
import (
"bytes"
"testing"
)
func TestFixedBufferWrite(t *testing.T) {
buf := &fixedBuffer{buf: make([]byte, 0, 64)}
n, err := buf.Write([]byte("hello"))
if err != nil {
t.Fatal(err)
}
if n != 5 {
t.Fatalf("expected 5 bytes written, got %d", n)
}
if string(buf.buf[:5]) != "hello" {
t.Fatalf("expected \"hello\", got %q", string(buf.buf[:5]))
}
n, err = buf.Write(bytes.Repeat([]byte{1}, 64))
if err != errBufferFull {
t.Fatalf("expected errBufferFull, got %v - %v", err, buf.buf[:64])
}
}
func TestFixedBufferRead(t *testing.T) {
buf := &fixedBuffer{buf: make([]byte, 0, 64)}
if _, err := buf.Write([]byte("hello world")); err != nil {
t.Fatal(err)
}
b := make([]byte, 5)
n, err := buf.Read(b)
if err != nil {
t.Fatal(err)
}
if n != 5 {
t.Fatalf("expected 5 bytes read, got %d - %s", n, buf.String())
}
if string(b) != "hello" {
t.Fatalf("expected \"hello\", got %q", string(b))
}
n, err = buf.Read(b)
if err != nil {
t.Fatal(err)
}
if n != 5 {
t.Fatalf("expected 5 bytes read, got %d", n)
}
if string(b) != " worl" {
t.Fatalf("expected \" worl\", got %s", string(b))
}
b = b[:1]
n, err = buf.Read(b)
if err != nil {
t.Fatal(err)
}
if n != 1 {
t.Fatalf("expected 1 byte read, got %d - %s", n, buf.String())
}
if string(b) != "d" {
t.Fatalf("expected \"d\", got %s", string(b))
}
}

View file

@ -0,0 +1,17 @@
package ioutils
import "testing"
func TestFprintfIfNotEmpty(t *testing.T) {
wc := NewWriteCounter(&NopWriter{})
n, _ := FprintfIfNotEmpty(wc, "foo%s", "")
if wc.Count != 0 || n != 0 {
t.Errorf("Wrong count: %v vs. %v vs. 0", wc.Count, n)
}
n, _ = FprintfIfNotEmpty(wc, "foo%s", "bar")
if wc.Count != 6 || n != 6 {
t.Errorf("Wrong count: %v vs. %v vs. 6", wc.Count, n)
}
}

View file

@ -0,0 +1,39 @@
package ioutils
import (
"bytes"
"io/ioutil"
"os"
"path/filepath"
"testing"
)
func TestAtomicWriteToFile(t *testing.T) {
tmpDir, err := ioutil.TempDir("", "atomic-writers-test")
if err != nil {
t.Fatalf("Error when creating temporary directory: %s", err)
}
defer os.RemoveAll(tmpDir)
expected := []byte("barbaz")
if err := AtomicWriteFile(filepath.Join(tmpDir, "foo"), expected, 0666); err != nil {
t.Fatalf("Error writing to file: %v", err)
}
actual, err := ioutil.ReadFile(filepath.Join(tmpDir, "foo"))
if err != nil {
t.Fatalf("Error reading from file: %v", err)
}
if bytes.Compare(actual, expected) != 0 {
t.Fatalf("Data mismatch, expected %q, got %q", expected, actual)
}
st, err := os.Stat(filepath.Join(tmpDir, "foo"))
if err != nil {
t.Fatalf("Error statting file: %v", err)
}
if expected := os.FileMode(0666); st.Mode() != expected {
t.Fatalf("Mode mismatched, expected %o, got %o", expected, st.Mode())
}
}

View file

@ -0,0 +1,149 @@
package ioutils
import (
"bytes"
"fmt"
"io"
"io/ioutil"
"os"
"strings"
"testing"
)
func TestMultiReadSeekerReadAll(t *testing.T) {
str := "hello world"
s1 := strings.NewReader(str + " 1")
s2 := strings.NewReader(str + " 2")
s3 := strings.NewReader(str + " 3")
mr := MultiReadSeeker(s1, s2, s3)
expectedSize := int64(s1.Len() + s2.Len() + s3.Len())
b, err := ioutil.ReadAll(mr)
if err != nil {
t.Fatal(err)
}
expected := "hello world 1hello world 2hello world 3"
if string(b) != expected {
t.Fatalf("ReadAll failed, got: %q, expected %q", string(b), expected)
}
size, err := mr.Seek(0, os.SEEK_END)
if err != nil {
t.Fatal(err)
}
if size != expectedSize {
t.Fatalf("reader size does not match, got %d, expected %d", size, expectedSize)
}
// Reset the position and read again
pos, err := mr.Seek(0, os.SEEK_SET)
if err != nil {
t.Fatal(err)
}
if pos != 0 {
t.Fatalf("expected position to be set to 0, got %d", pos)
}
b, err = ioutil.ReadAll(mr)
if err != nil {
t.Fatal(err)
}
if string(b) != expected {
t.Fatalf("ReadAll failed, got: %q, expected %q", string(b), expected)
}
}
func TestMultiReadSeekerReadEach(t *testing.T) {
str := "hello world"
s1 := strings.NewReader(str + " 1")
s2 := strings.NewReader(str + " 2")
s3 := strings.NewReader(str + " 3")
mr := MultiReadSeeker(s1, s2, s3)
var totalBytes int64
for i, s := range []*strings.Reader{s1, s2, s3} {
sLen := int64(s.Len())
buf := make([]byte, s.Len())
expected := []byte(fmt.Sprintf("%s %d", str, i+1))
if _, err := mr.Read(buf); err != nil && err != io.EOF {
t.Fatal(err)
}
if !bytes.Equal(buf, expected) {
t.Fatalf("expected %q to be %q", string(buf), string(expected))
}
pos, err := mr.Seek(0, os.SEEK_CUR)
if err != nil {
t.Fatalf("iteration: %d, error: %v", i+1, err)
}
// check that the total bytes read is the current position of the seeker
totalBytes += sLen
if pos != totalBytes {
t.Fatalf("expected current position to be: %d, got: %d, iteration: %d", totalBytes, pos, i+1)
}
// This tests not only that SEEK_SET and SEEK_CUR give the same values, but that the next iteration is in the expected position as well
newPos, err := mr.Seek(pos, os.SEEK_SET)
if err != nil {
t.Fatal(err)
}
if newPos != pos {
t.Fatalf("expected to get same position when calling SEEK_SET with value from SEEK_CUR, cur: %d, set: %d", pos, newPos)
}
}
}
func TestMultiReadSeekerReadSpanningChunks(t *testing.T) {
str := "hello world"
s1 := strings.NewReader(str + " 1")
s2 := strings.NewReader(str + " 2")
s3 := strings.NewReader(str + " 3")
mr := MultiReadSeeker(s1, s2, s3)
buf := make([]byte, s1.Len()+3)
_, err := mr.Read(buf)
if err != nil {
t.Fatal(err)
}
// expected is the contents of s1 + 3 bytes from s2, ie, the `hel` at the end of this string
expected := "hello world 1hel"
if string(buf) != expected {
t.Fatalf("expected %s to be %s", string(buf), expected)
}
}
func TestMultiReadSeekerNegativeSeek(t *testing.T) {
str := "hello world"
s1 := strings.NewReader(str + " 1")
s2 := strings.NewReader(str + " 2")
s3 := strings.NewReader(str + " 3")
mr := MultiReadSeeker(s1, s2, s3)
s1Len := s1.Len()
s2Len := s2.Len()
s3Len := s3.Len()
s, err := mr.Seek(int64(-1*s3.Len()), os.SEEK_END)
if err != nil {
t.Fatal(err)
}
if s != int64(s1Len+s2Len) {
t.Fatalf("expected %d to be %d", s, s1.Len()+s2.Len())
}
buf := make([]byte, s3Len)
if _, err := mr.Read(buf); err != nil && err != io.EOF {
t.Fatal(err)
}
expected := fmt.Sprintf("%s %d", str, 3)
if string(buf) != fmt.Sprintf("%s %d", str, 3) {
t.Fatalf("expected %q to be %q", string(buf), expected)
}
}

View file

@ -0,0 +1,94 @@
package ioutils
import (
"fmt"
"io/ioutil"
"strings"
"testing"
"time"
"golang.org/x/net/context"
)
// Implement io.Reader
type errorReader struct{}
func (r *errorReader) Read(p []byte) (int, error) {
return 0, fmt.Errorf("Error reader always fail.")
}
func TestReadCloserWrapperClose(t *testing.T) {
reader := strings.NewReader("A string reader")
wrapper := NewReadCloserWrapper(reader, func() error {
return fmt.Errorf("This will be called when closing")
})
err := wrapper.Close()
if err == nil || !strings.Contains(err.Error(), "This will be called when closing") {
t.Fatalf("readCloserWrapper should have call the anonymous func and thus, fail.")
}
}
func TestReaderErrWrapperReadOnError(t *testing.T) {
called := false
reader := &errorReader{}
wrapper := NewReaderErrWrapper(reader, func() {
called = true
})
_, err := wrapper.Read([]byte{})
if err == nil || !strings.Contains(err.Error(), "Error reader always fail.") {
t.Fatalf("readErrWrapper should returned an error")
}
if !called {
t.Fatalf("readErrWrapper should have call the anonymous function on failure")
}
}
func TestReaderErrWrapperRead(t *testing.T) {
reader := strings.NewReader("a string reader.")
wrapper := NewReaderErrWrapper(reader, func() {
t.Fatalf("readErrWrapper should not have called the anonymous function")
})
// Read 20 byte (should be ok with the string above)
num, err := wrapper.Read(make([]byte, 20))
if err != nil {
t.Fatal(err)
}
if num != 16 {
t.Fatalf("readerErrWrapper should have read 16 byte, but read %d", num)
}
}
func TestHashData(t *testing.T) {
reader := strings.NewReader("hash-me")
actual, err := HashData(reader)
if err != nil {
t.Fatal(err)
}
expected := "sha256:4d11186aed035cc624d553e10db358492c84a7cd6b9670d92123c144930450aa"
if actual != expected {
t.Fatalf("Expecting %s, got %s", expected, actual)
}
}
type perpetualReader struct{}
func (p *perpetualReader) Read(buf []byte) (n int, err error) {
for i := 0; i != len(buf); i++ {
buf[i] = 'a'
}
return len(buf), nil
}
func TestCancelReadCloser(t *testing.T) {
ctx, _ := context.WithTimeout(context.Background(), 100*time.Millisecond)
cancelReadCloser := NewCancelReadCloser(ctx, ioutil.NopCloser(&perpetualReader{}))
for {
var buf [128]byte
_, err := cancelReadCloser.Read(buf[:])
if err == context.DeadlineExceeded {
break
} else if err != nil {
t.Fatalf("got unexpected error: %v", err)
}
}
}

View file

@ -0,0 +1,65 @@
package ioutils
import (
"bytes"
"strings"
"testing"
)
func TestWriteCloserWrapperClose(t *testing.T) {
called := false
writer := bytes.NewBuffer([]byte{})
wrapper := NewWriteCloserWrapper(writer, func() error {
called = true
return nil
})
if err := wrapper.Close(); err != nil {
t.Fatal(err)
}
if !called {
t.Fatalf("writeCloserWrapper should have call the anonymous function.")
}
}
func TestNopWriteCloser(t *testing.T) {
writer := bytes.NewBuffer([]byte{})
wrapper := NopWriteCloser(writer)
if err := wrapper.Close(); err != nil {
t.Fatal("NopWriteCloser always return nil on Close.")
}
}
func TestNopWriter(t *testing.T) {
nw := &NopWriter{}
l, err := nw.Write([]byte{'c'})
if err != nil {
t.Fatal(err)
}
if l != 1 {
t.Fatalf("Expected 1 got %d", l)
}
}
func TestWriteCounter(t *testing.T) {
dummy1 := "This is a dummy string."
dummy2 := "This is another dummy string."
totalLength := int64(len(dummy1) + len(dummy2))
reader1 := strings.NewReader(dummy1)
reader2 := strings.NewReader(dummy2)
var buffer bytes.Buffer
wc := NewWriteCounter(&buffer)
reader1.WriteTo(wc)
reader2.WriteTo(wc)
if wc.Count != totalLength {
t.Errorf("Wrong count: %d vs. %d", wc.Count, totalLength)
}
if buffer.String() != dummy1+dummy2 {
t.Error("Wrong message written")
}
}

View file

@ -0,0 +1,26 @@
// longpath introduces some constants and helper functions for handling long paths
// in Windows, which are expected to be prepended with `\\?\` and followed by either
// a drive letter, a UNC server\share, or a volume identifier.
package longpath
import (
"strings"
)
// Prefix is the longpath prefix for Windows file paths.
const Prefix = `\\?\`
// AddPrefix will add the Windows long path prefix to the path provided if
// it does not already have it.
func AddPrefix(path string) string {
if !strings.HasPrefix(path, Prefix) {
if strings.HasPrefix(path, `\\`) {
// This is a UNC path, so we need to add 'UNC' to the path as well.
path = Prefix + `UNC` + path[1:]
} else {
path = Prefix + path
}
}
return path
}

View file

@ -0,0 +1,22 @@
package longpath
import (
"strings"
"testing"
)
func TestStandardLongPath(t *testing.T) {
c := `C:\simple\path`
longC := AddPrefix(c)
if !strings.EqualFold(longC, `\\?\C:\simple\path`) {
t.Errorf("Wrong long path returned. Original = %s ; Long = %s", c, longC)
}
}
func TestUNCLongPath(t *testing.T) {
c := `\\server\share\path`
longC := AddPrefix(c)
if !strings.EqualFold(longC, `\\?\UNC\server\share\path`) {
t.Errorf("Wrong UNC long path returned. Original = %s ; Long = %s", c, longC)
}
}

View file

@ -0,0 +1,36 @@
package main
import (
"fmt"
flag "github.com/containers/storage/pkg/mflag"
)
var (
i int
str string
b, b2, h bool
)
func init() {
flag.Bool([]string{"#hp", "#-halp"}, false, "display the halp")
flag.BoolVar(&b, []string{"b", "#bal", "#bol", "-bal"}, false, "a simple bool")
flag.BoolVar(&b, []string{"g", "#gil"}, false, "a simple bool")
flag.BoolVar(&b2, []string{"#-bool"}, false, "a simple bool")
flag.IntVar(&i, []string{"-integer", "-number"}, -1, "a simple integer")
flag.StringVar(&str, []string{"s", "#hidden", "-string"}, "", "a simple string") //-s -hidden and --string will work, but -hidden won't be in the usage
flag.BoolVar(&h, []string{"h", "#help", "-help"}, false, "display the help")
flag.StringVar(&str, []string{"mode"}, "mode1", "set the mode\nmode1: use the mode1\nmode2: use the mode2\nmode3: use the mode3")
flag.Parse()
}
func main() {
if h {
flag.PrintDefaults()
} else {
fmt.Printf("s/#hidden/-string: %s\n", str)
fmt.Printf("b: %t\n", b)
fmt.Printf("-bool: %t\n", b2)
fmt.Printf("s/#hidden/-string(via lookup): %s\n", flag.Lookup("s").Value.String())
fmt.Printf("ARGS: %v\n", flag.Args())
}
}

1280
vendor/github.com/containers/storage/pkg/mflag/flag.go generated vendored Normal file

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,527 @@
// Copyright 2014-2016 The Docker & Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package mflag
import (
"bytes"
"fmt"
"os"
"sort"
"strings"
"testing"
"time"
)
// ResetForTesting clears all flag state and sets the usage function as directed.
// After calling ResetForTesting, parse errors in flag handling will not
// exit the program.
func ResetForTesting(usage func()) {
CommandLine = NewFlagSet(os.Args[0], ContinueOnError)
Usage = usage
}
func boolString(s string) string {
if s == "0" {
return "false"
}
return "true"
}
func TestEverything(t *testing.T) {
ResetForTesting(nil)
Bool([]string{"test_bool"}, false, "bool value")
Int([]string{"test_int"}, 0, "int value")
Int64([]string{"test_int64"}, 0, "int64 value")
Uint([]string{"test_uint"}, 0, "uint value")
Uint64([]string{"test_uint64"}, 0, "uint64 value")
String([]string{"test_string"}, "0", "string value")
Float64([]string{"test_float64"}, 0, "float64 value")
Duration([]string{"test_duration"}, 0, "time.Duration value")
m := make(map[string]*Flag)
desired := "0"
visitor := func(f *Flag) {
for _, name := range f.Names {
if len(name) > 5 && name[0:5] == "test_" {
m[name] = f
ok := false
switch {
case f.Value.String() == desired:
ok = true
case name == "test_bool" && f.Value.String() == boolString(desired):
ok = true
case name == "test_duration" && f.Value.String() == desired+"s":
ok = true
}
if !ok {
t.Error("Visit: bad value", f.Value.String(), "for", name)
}
}
}
}
VisitAll(visitor)
if len(m) != 8 {
t.Error("VisitAll misses some flags")
for k, v := range m {
t.Log(k, *v)
}
}
m = make(map[string]*Flag)
Visit(visitor)
if len(m) != 0 {
t.Errorf("Visit sees unset flags")
for k, v := range m {
t.Log(k, *v)
}
}
// Now set all flags
Set("test_bool", "true")
Set("test_int", "1")
Set("test_int64", "1")
Set("test_uint", "1")
Set("test_uint64", "1")
Set("test_string", "1")
Set("test_float64", "1")
Set("test_duration", "1s")
desired = "1"
Visit(visitor)
if len(m) != 8 {
t.Error("Visit fails after set")
for k, v := range m {
t.Log(k, *v)
}
}
// Now test they're visited in sort order.
var flagNames []string
Visit(func(f *Flag) {
for _, name := range f.Names {
flagNames = append(flagNames, name)
}
})
if !sort.StringsAreSorted(flagNames) {
t.Errorf("flag names not sorted: %v", flagNames)
}
}
func TestGet(t *testing.T) {
ResetForTesting(nil)
Bool([]string{"test_bool"}, true, "bool value")
Int([]string{"test_int"}, 1, "int value")
Int64([]string{"test_int64"}, 2, "int64 value")
Uint([]string{"test_uint"}, 3, "uint value")
Uint64([]string{"test_uint64"}, 4, "uint64 value")
String([]string{"test_string"}, "5", "string value")
Float64([]string{"test_float64"}, 6, "float64 value")
Duration([]string{"test_duration"}, 7, "time.Duration value")
visitor := func(f *Flag) {
for _, name := range f.Names {
if len(name) > 5 && name[0:5] == "test_" {
g, ok := f.Value.(Getter)
if !ok {
t.Errorf("Visit: value does not satisfy Getter: %T", f.Value)
return
}
switch name {
case "test_bool":
ok = g.Get() == true
case "test_int":
ok = g.Get() == int(1)
case "test_int64":
ok = g.Get() == int64(2)
case "test_uint":
ok = g.Get() == uint(3)
case "test_uint64":
ok = g.Get() == uint64(4)
case "test_string":
ok = g.Get() == "5"
case "test_float64":
ok = g.Get() == float64(6)
case "test_duration":
ok = g.Get() == time.Duration(7)
}
if !ok {
t.Errorf("Visit: bad value %T(%v) for %s", g.Get(), g.Get(), name)
}
}
}
}
VisitAll(visitor)
}
func testParse(f *FlagSet, t *testing.T) {
if f.Parsed() {
t.Error("f.Parse() = true before Parse")
}
boolFlag := f.Bool([]string{"bool"}, false, "bool value")
bool2Flag := f.Bool([]string{"bool2"}, false, "bool2 value")
f.Bool([]string{"bool3"}, false, "bool3 value")
bool4Flag := f.Bool([]string{"bool4"}, false, "bool4 value")
intFlag := f.Int([]string{"-int"}, 0, "int value")
int64Flag := f.Int64([]string{"-int64"}, 0, "int64 value")
uintFlag := f.Uint([]string{"uint"}, 0, "uint value")
uint64Flag := f.Uint64([]string{"-uint64"}, 0, "uint64 value")
stringFlag := f.String([]string{"string"}, "0", "string value")
f.String([]string{"string2"}, "0", "string2 value")
singleQuoteFlag := f.String([]string{"squote"}, "", "single quoted value")
doubleQuoteFlag := f.String([]string{"dquote"}, "", "double quoted value")
mixedQuoteFlag := f.String([]string{"mquote"}, "", "mixed quoted value")
mixed2QuoteFlag := f.String([]string{"mquote2"}, "", "mixed2 quoted value")
nestedQuoteFlag := f.String([]string{"nquote"}, "", "nested quoted value")
nested2QuoteFlag := f.String([]string{"nquote2"}, "", "nested2 quoted value")
float64Flag := f.Float64([]string{"float64"}, 0, "float64 value")
durationFlag := f.Duration([]string{"duration"}, 5*time.Second, "time.Duration value")
extra := "one-extra-argument"
args := []string{
"-bool",
"-bool2=true",
"-bool4=false",
"--int", "22",
"--int64", "0x23",
"-uint", "24",
"--uint64", "25",
"-string", "hello",
"-squote='single'",
`-dquote="double"`,
`-mquote='mixed"`,
`-mquote2="mixed2'`,
`-nquote="'single nested'"`,
`-nquote2='"double nested"'`,
"-float64", "2718e28",
"-duration", "2m",
extra,
}
if err := f.Parse(args); err != nil {
t.Fatal(err)
}
if !f.Parsed() {
t.Error("f.Parse() = false after Parse")
}
if *boolFlag != true {
t.Error("bool flag should be true, is ", *boolFlag)
}
if *bool2Flag != true {
t.Error("bool2 flag should be true, is ", *bool2Flag)
}
if !f.IsSet("bool2") {
t.Error("bool2 should be marked as set")
}
if f.IsSet("bool3") {
t.Error("bool3 should not be marked as set")
}
if !f.IsSet("bool4") {
t.Error("bool4 should be marked as set")
}
if *bool4Flag != false {
t.Error("bool4 flag should be false, is ", *bool4Flag)
}
if *intFlag != 22 {
t.Error("int flag should be 22, is ", *intFlag)
}
if *int64Flag != 0x23 {
t.Error("int64 flag should be 0x23, is ", *int64Flag)
}
if *uintFlag != 24 {
t.Error("uint flag should be 24, is ", *uintFlag)
}
if *uint64Flag != 25 {
t.Error("uint64 flag should be 25, is ", *uint64Flag)
}
if *stringFlag != "hello" {
t.Error("string flag should be `hello`, is ", *stringFlag)
}
if !f.IsSet("string") {
t.Error("string flag should be marked as set")
}
if f.IsSet("string2") {
t.Error("string2 flag should not be marked as set")
}
if *singleQuoteFlag != "single" {
t.Error("single quote string flag should be `single`, is ", *singleQuoteFlag)
}
if *doubleQuoteFlag != "double" {
t.Error("double quote string flag should be `double`, is ", *doubleQuoteFlag)
}
if *mixedQuoteFlag != `'mixed"` {
t.Error("mixed quote string flag should be `'mixed\"`, is ", *mixedQuoteFlag)
}
if *mixed2QuoteFlag != `"mixed2'` {
t.Error("mixed2 quote string flag should be `\"mixed2'`, is ", *mixed2QuoteFlag)
}
if *nestedQuoteFlag != "'single nested'" {
t.Error("nested quote string flag should be `'single nested'`, is ", *nestedQuoteFlag)
}
if *nested2QuoteFlag != `"double nested"` {
t.Error("double quote string flag should be `\"double nested\"`, is ", *nested2QuoteFlag)
}
if *float64Flag != 2718e28 {
t.Error("float64 flag should be 2718e28, is ", *float64Flag)
}
if *durationFlag != 2*time.Minute {
t.Error("duration flag should be 2m, is ", *durationFlag)
}
if len(f.Args()) != 1 {
t.Error("expected one argument, got", len(f.Args()))
} else if f.Args()[0] != extra {
t.Errorf("expected argument %q got %q", extra, f.Args()[0])
}
}
func testPanic(f *FlagSet, t *testing.T) {
f.Int([]string{"-int"}, 0, "int value")
if f.Parsed() {
t.Error("f.Parse() = true before Parse")
}
args := []string{
"-int", "21",
}
f.Parse(args)
}
func TestParsePanic(t *testing.T) {
ResetForTesting(func() {})
testPanic(CommandLine, t)
}
func TestParse(t *testing.T) {
ResetForTesting(func() { t.Error("bad parse") })
testParse(CommandLine, t)
}
func TestFlagSetParse(t *testing.T) {
testParse(NewFlagSet("test", ContinueOnError), t)
}
// Declare a user-defined flag type.
type flagVar []string
func (f *flagVar) String() string {
return fmt.Sprint([]string(*f))
}
func (f *flagVar) Set(value string) error {
*f = append(*f, value)
return nil
}
func TestUserDefined(t *testing.T) {
var flags FlagSet
flags.Init("test", ContinueOnError)
var v flagVar
flags.Var(&v, []string{"v"}, "usage")
if err := flags.Parse([]string{"-v", "1", "-v", "2", "-v=3"}); err != nil {
t.Error(err)
}
if len(v) != 3 {
t.Fatal("expected 3 args; got ", len(v))
}
expect := "[1 2 3]"
if v.String() != expect {
t.Errorf("expected value %q got %q", expect, v.String())
}
}
// Declare a user-defined boolean flag type.
type boolFlagVar struct {
count int
}
func (b *boolFlagVar) String() string {
return fmt.Sprintf("%d", b.count)
}
func (b *boolFlagVar) Set(value string) error {
if value == "true" {
b.count++
}
return nil
}
func (b *boolFlagVar) IsBoolFlag() bool {
return b.count < 4
}
func TestUserDefinedBool(t *testing.T) {
var flags FlagSet
flags.Init("test", ContinueOnError)
var b boolFlagVar
var err error
flags.Var(&b, []string{"b"}, "usage")
if err = flags.Parse([]string{"-b", "-b", "-b", "-b=true", "-b=false", "-b", "barg", "-b"}); err != nil {
if b.count < 4 {
t.Error(err)
}
}
if b.count != 4 {
t.Errorf("want: %d; got: %d", 4, b.count)
}
if err == nil {
t.Error("expected error; got none")
}
}
func TestSetOutput(t *testing.T) {
var flags FlagSet
var buf bytes.Buffer
flags.SetOutput(&buf)
flags.Init("test", ContinueOnError)
flags.Parse([]string{"-unknown"})
if out := buf.String(); !strings.Contains(out, "-unknown") {
t.Logf("expected output mentioning unknown; got %q", out)
}
}
// This tests that one can reset the flags. This still works but not well, and is
// superseded by FlagSet.
func TestChangingArgs(t *testing.T) {
ResetForTesting(func() { t.Fatal("bad parse") })
oldArgs := os.Args
defer func() { os.Args = oldArgs }()
os.Args = []string{"cmd", "-before", "subcmd", "-after", "args"}
before := Bool([]string{"before"}, false, "")
if err := CommandLine.Parse(os.Args[1:]); err != nil {
t.Fatal(err)
}
cmd := Arg(0)
os.Args = Args()
after := Bool([]string{"after"}, false, "")
Parse()
args := Args()
if !*before || cmd != "subcmd" || !*after || len(args) != 1 || args[0] != "args" {
t.Fatalf("expected true subcmd true [args] got %v %v %v %v", *before, cmd, *after, args)
}
}
// Test that -help invokes the usage message and returns ErrHelp.
func TestHelp(t *testing.T) {
var helpCalled = false
fs := NewFlagSet("help test", ContinueOnError)
fs.Usage = func() { helpCalled = true }
var flag bool
fs.BoolVar(&flag, []string{"flag"}, false, "regular flag")
// Regular flag invocation should work
err := fs.Parse([]string{"-flag=true"})
if err != nil {
t.Fatal("expected no error; got ", err)
}
if !flag {
t.Error("flag was not set by -flag")
}
if helpCalled {
t.Error("help called for regular flag")
helpCalled = false // reset for next test
}
// Help flag should work as expected.
err = fs.Parse([]string{"-help"})
if err == nil {
t.Fatal("error expected")
}
if err != ErrHelp {
t.Fatal("expected ErrHelp; got ", err)
}
if !helpCalled {
t.Fatal("help was not called")
}
// If we define a help flag, that should override.
var help bool
fs.BoolVar(&help, []string{"help"}, false, "help flag")
helpCalled = false
err = fs.Parse([]string{"-help"})
if err != nil {
t.Fatal("expected no error for defined -help; got ", err)
}
if helpCalled {
t.Fatal("help was called; should not have been for defined help flag")
}
}
// Test the flag count functions.
func TestFlagCounts(t *testing.T) {
fs := NewFlagSet("help test", ContinueOnError)
var flag bool
fs.BoolVar(&flag, []string{"flag1"}, false, "regular flag")
fs.BoolVar(&flag, []string{"#deprecated1"}, false, "regular flag")
fs.BoolVar(&flag, []string{"f", "flag2"}, false, "regular flag")
fs.BoolVar(&flag, []string{"#d", "#deprecated2"}, false, "regular flag")
fs.BoolVar(&flag, []string{"flag3"}, false, "regular flag")
fs.BoolVar(&flag, []string{"g", "#flag4", "-flag4"}, false, "regular flag")
if fs.FlagCount() != 6 {
t.Fatal("FlagCount wrong. ", fs.FlagCount())
}
if fs.FlagCountUndeprecated() != 4 {
t.Fatal("FlagCountUndeprecated wrong. ", fs.FlagCountUndeprecated())
}
if fs.NFlag() != 0 {
t.Fatal("NFlag wrong. ", fs.NFlag())
}
err := fs.Parse([]string{"-fd", "-g", "-flag4"})
if err != nil {
t.Fatal("expected no error for defined -help; got ", err)
}
if fs.NFlag() != 4 {
t.Fatal("NFlag wrong. ", fs.NFlag())
}
}
// Show up bug in sortFlags
func TestSortFlags(t *testing.T) {
fs := NewFlagSet("help TestSortFlags", ContinueOnError)
var err error
var b bool
fs.BoolVar(&b, []string{"b", "-banana"}, false, "usage")
err = fs.Parse([]string{"--banana=true"})
if err != nil {
t.Fatal("expected no error; got ", err)
}
count := 0
fs.VisitAll(func(flag *Flag) {
count++
if flag == nil {
t.Fatal("VisitAll should not return a nil flag")
}
})
flagcount := fs.FlagCount()
if flagcount != count {
t.Fatalf("FlagCount (%d) != number (%d) of elements visited", flagcount, count)
}
// Make sure its idempotent
if flagcount != fs.FlagCount() {
t.Fatalf("FlagCount (%d) != fs.FlagCount() (%d) of elements visited", flagcount, fs.FlagCount())
}
count = 0
fs.Visit(func(flag *Flag) {
count++
if flag == nil {
t.Fatal("Visit should not return a nil flag")
}
})
nflag := fs.NFlag()
if nflag != count {
t.Fatalf("NFlag (%d) != number (%d) of elements visited", nflag, count)
}
if nflag != fs.NFlag() {
t.Fatalf("NFlag (%d) != fs.NFlag() (%d) of elements visited", nflag, fs.NFlag())
}
}
func TestMergeFlags(t *testing.T) {
base := NewFlagSet("base", ContinueOnError)
base.String([]string{"f"}, "", "")
fs := NewFlagSet("test", ContinueOnError)
Merge(fs, base)
if len(fs.formal) != 1 {
t.Fatalf("FlagCount (%d) != number (1) of elements merged", len(fs.formal))
}
}

View file

@ -0,0 +1,162 @@
// +build !windows
package mount
import (
"os"
"path"
"testing"
)
func TestMountOptionsParsing(t *testing.T) {
options := "noatime,ro,size=10k"
flag, data := parseOptions(options)
if data != "size=10k" {
t.Fatalf("Expected size=10 got %s", data)
}
expectedFlag := NOATIME | RDONLY
if flag != expectedFlag {
t.Fatalf("Expected %d got %d", expectedFlag, flag)
}
}
func TestMounted(t *testing.T) {
tmp := path.Join(os.TempDir(), "mount-tests")
if err := os.MkdirAll(tmp, 0777); err != nil {
t.Fatal(err)
}
defer os.RemoveAll(tmp)
var (
sourceDir = path.Join(tmp, "source")
targetDir = path.Join(tmp, "target")
sourcePath = path.Join(sourceDir, "file.txt")
targetPath = path.Join(targetDir, "file.txt")
)
os.Mkdir(sourceDir, 0777)
os.Mkdir(targetDir, 0777)
f, err := os.Create(sourcePath)
if err != nil {
t.Fatal(err)
}
f.WriteString("hello")
f.Close()
f, err = os.Create(targetPath)
if err != nil {
t.Fatal(err)
}
f.Close()
if err := Mount(sourceDir, targetDir, "none", "bind,rw"); err != nil {
t.Fatal(err)
}
defer func() {
if err := Unmount(targetDir); err != nil {
t.Fatal(err)
}
}()
mounted, err := Mounted(targetDir)
if err != nil {
t.Fatal(err)
}
if !mounted {
t.Fatalf("Expected %s to be mounted", targetDir)
}
if _, err := os.Stat(targetDir); err != nil {
t.Fatal(err)
}
}
func TestMountReadonly(t *testing.T) {
tmp := path.Join(os.TempDir(), "mount-tests")
if err := os.MkdirAll(tmp, 0777); err != nil {
t.Fatal(err)
}
defer os.RemoveAll(tmp)
var (
sourceDir = path.Join(tmp, "source")
targetDir = path.Join(tmp, "target")
sourcePath = path.Join(sourceDir, "file.txt")
targetPath = path.Join(targetDir, "file.txt")
)
os.Mkdir(sourceDir, 0777)
os.Mkdir(targetDir, 0777)
f, err := os.Create(sourcePath)
if err != nil {
t.Fatal(err)
}
f.WriteString("hello")
f.Close()
f, err = os.Create(targetPath)
if err != nil {
t.Fatal(err)
}
f.Close()
if err := Mount(sourceDir, targetDir, "none", "bind,ro"); err != nil {
t.Fatal(err)
}
defer func() {
if err := Unmount(targetDir); err != nil {
t.Fatal(err)
}
}()
f, err = os.OpenFile(targetPath, os.O_RDWR, 0777)
if err == nil {
t.Fatal("Should not be able to open a ro file as rw")
}
}
func TestGetMounts(t *testing.T) {
mounts, err := GetMounts()
if err != nil {
t.Fatal(err)
}
root := false
for _, entry := range mounts {
if entry.Mountpoint == "/" {
root = true
}
}
if !root {
t.Fatal("/ should be mounted at least")
}
}
func TestMergeTmpfsOptions(t *testing.T) {
options := []string{"noatime", "ro", "size=10k", "defaults", "atime", "defaults", "rw", "rprivate", "size=1024k", "slave"}
expected := []string{"atime", "rw", "size=1024k", "slave"}
merged, err := MergeTmpfsOptions(options)
if err != nil {
t.Fatal(err)
}
if len(expected) != len(merged) {
t.Fatalf("Expected %s got %s", expected, merged)
}
for index := range merged {
if merged[index] != expected[index] {
t.Fatalf("Expected %s for the %dth option, got %s", expected, index, merged)
}
}
options = []string{"noatime", "ro", "size=10k", "atime", "rw", "rprivate", "size=1024k", "slave", "size"}
_, err = MergeTmpfsOptions(options)
if err == nil {
t.Fatal("Expected error got nil")
}
}

View file

@ -0,0 +1,476 @@
// +build linux
package mount
import (
"bytes"
"testing"
)
const (
fedoraMountinfo = `15 35 0:3 / /proc rw,nosuid,nodev,noexec,relatime shared:5 - proc proc rw
16 35 0:14 / /sys rw,nosuid,nodev,noexec,relatime shared:6 - sysfs sysfs rw,seclabel
17 35 0:5 / /dev rw,nosuid shared:2 - devtmpfs devtmpfs rw,seclabel,size=8056484k,nr_inodes=2014121,mode=755
18 16 0:15 / /sys/kernel/security rw,nosuid,nodev,noexec,relatime shared:7 - securityfs securityfs rw
19 16 0:13 / /sys/fs/selinux rw,relatime shared:8 - selinuxfs selinuxfs rw
20 17 0:16 / /dev/shm rw,nosuid,nodev shared:3 - tmpfs tmpfs rw,seclabel
21 17 0:10 / /dev/pts rw,nosuid,noexec,relatime shared:4 - devpts devpts rw,seclabel,gid=5,mode=620,ptmxmode=000
22 35 0:17 / /run rw,nosuid,nodev shared:21 - tmpfs tmpfs rw,seclabel,mode=755
23 16 0:18 / /sys/fs/cgroup rw,nosuid,nodev,noexec shared:9 - tmpfs tmpfs rw,seclabel,mode=755
24 23 0:19 / /sys/fs/cgroup/systemd rw,nosuid,nodev,noexec,relatime shared:10 - cgroup cgroup rw,xattr,release_agent=/usr/lib/systemd/systemd-cgroups-agent,name=systemd
25 16 0:20 / /sys/fs/pstore rw,nosuid,nodev,noexec,relatime shared:20 - pstore pstore rw
26 23 0:21 / /sys/fs/cgroup/cpuset rw,nosuid,nodev,noexec,relatime shared:11 - cgroup cgroup rw,cpuset,clone_children
27 23 0:22 / /sys/fs/cgroup/cpu,cpuacct rw,nosuid,nodev,noexec,relatime shared:12 - cgroup cgroup rw,cpuacct,cpu,clone_children
28 23 0:23 / /sys/fs/cgroup/memory rw,nosuid,nodev,noexec,relatime shared:13 - cgroup cgroup rw,memory,clone_children
29 23 0:24 / /sys/fs/cgroup/devices rw,nosuid,nodev,noexec,relatime shared:14 - cgroup cgroup rw,devices,clone_children
30 23 0:25 / /sys/fs/cgroup/freezer rw,nosuid,nodev,noexec,relatime shared:15 - cgroup cgroup rw,freezer,clone_children
31 23 0:26 / /sys/fs/cgroup/net_cls rw,nosuid,nodev,noexec,relatime shared:16 - cgroup cgroup rw,net_cls,clone_children
32 23 0:27 / /sys/fs/cgroup/blkio rw,nosuid,nodev,noexec,relatime shared:17 - cgroup cgroup rw,blkio,clone_children
33 23 0:28 / /sys/fs/cgroup/perf_event rw,nosuid,nodev,noexec,relatime shared:18 - cgroup cgroup rw,perf_event,clone_children
34 23 0:29 / /sys/fs/cgroup/hugetlb rw,nosuid,nodev,noexec,relatime shared:19 - cgroup cgroup rw,hugetlb,clone_children
35 1 253:2 / / rw,relatime shared:1 - ext4 /dev/mapper/ssd-root--f20 rw,seclabel,data=ordered
36 15 0:30 / /proc/sys/fs/binfmt_misc rw,relatime shared:22 - autofs systemd-1 rw,fd=38,pgrp=1,timeout=300,minproto=5,maxproto=5,direct
37 17 0:12 / /dev/mqueue rw,relatime shared:23 - mqueue mqueue rw,seclabel
38 35 0:31 / /tmp rw shared:24 - tmpfs tmpfs rw,seclabel
39 17 0:32 / /dev/hugepages rw,relatime shared:25 - hugetlbfs hugetlbfs rw,seclabel
40 16 0:7 / /sys/kernel/debug rw,relatime shared:26 - debugfs debugfs rw
41 16 0:33 / /sys/kernel/config rw,relatime shared:27 - configfs configfs rw
42 35 0:34 / /var/lib/nfs/rpc_pipefs rw,relatime shared:28 - rpc_pipefs sunrpc rw
43 15 0:35 / /proc/fs/nfsd rw,relatime shared:29 - nfsd sunrpc rw
45 35 8:17 / /boot rw,relatime shared:30 - ext4 /dev/sdb1 rw,seclabel,data=ordered
46 35 253:4 / /home rw,relatime shared:31 - ext4 /dev/mapper/ssd-home rw,seclabel,data=ordered
47 35 253:5 / /var/lib/libvirt/images rw,noatime,nodiratime shared:32 - ext4 /dev/mapper/ssd-virt rw,seclabel,discard,data=ordered
48 35 253:12 / /mnt/old rw,relatime shared:33 - ext4 /dev/mapper/HelpDeskRHEL6-FedoraRoot rw,seclabel,data=ordered
121 22 0:36 / /run/user/1000/gvfs rw,nosuid,nodev,relatime shared:104 - fuse.gvfsd-fuse gvfsd-fuse rw,user_id=1000,group_id=1000
124 16 0:37 / /sys/fs/fuse/connections rw,relatime shared:107 - fusectl fusectl rw
165 38 253:3 / /tmp/mnt rw,relatime shared:147 - ext4 /dev/mapper/ssd-root rw,seclabel,data=ordered
167 35 253:15 / /var/lib/docker/devicemapper/mnt/aae4076022f0e2b80a2afbf8fc6df450c52080191fcef7fb679a73e6f073e5c2 rw,relatime shared:149 - ext4 /dev/mapper/docker-253:2-425882-aae4076022f0e2b80a2afbf8fc6df450c52080191fcef7fb679a73e6f073e5c2 rw,seclabel,discard,stripe=16,data=ordered
171 35 253:16 / /var/lib/docker/devicemapper/mnt/c71be651f114db95180e472f7871b74fa597ee70a58ccc35cb87139ddea15373 rw,relatime shared:153 - ext4 /dev/mapper/docker-253:2-425882-c71be651f114db95180e472f7871b74fa597ee70a58ccc35cb87139ddea15373 rw,seclabel,discard,stripe=16,data=ordered
175 35 253:17 / /var/lib/docker/devicemapper/mnt/1bac6ab72862d2d5626560df6197cf12036b82e258c53d981fa29adce6f06c3c rw,relatime shared:157 - ext4 /dev/mapper/docker-253:2-425882-1bac6ab72862d2d5626560df6197cf12036b82e258c53d981fa29adce6f06c3c rw,seclabel,discard,stripe=16,data=ordered
179 35 253:18 / /var/lib/docker/devicemapper/mnt/d710a357d77158e80d5b2c55710ae07c94e76d34d21ee7bae65ce5418f739b09 rw,relatime shared:161 - ext4 /dev/mapper/docker-253:2-425882-d710a357d77158e80d5b2c55710ae07c94e76d34d21ee7bae65ce5418f739b09 rw,seclabel,discard,stripe=16,data=ordered
183 35 253:19 / /var/lib/docker/devicemapper/mnt/6479f52366114d5f518db6837254baab48fab39f2ac38d5099250e9a6ceae6c7 rw,relatime shared:165 - ext4 /dev/mapper/docker-253:2-425882-6479f52366114d5f518db6837254baab48fab39f2ac38d5099250e9a6ceae6c7 rw,seclabel,discard,stripe=16,data=ordered
187 35 253:20 / /var/lib/docker/devicemapper/mnt/8d9df91c4cca5aef49eeb2725292aab324646f723a7feab56be34c2ad08268e1 rw,relatime shared:169 - ext4 /dev/mapper/docker-253:2-425882-8d9df91c4cca5aef49eeb2725292aab324646f723a7feab56be34c2ad08268e1 rw,seclabel,discard,stripe=16,data=ordered
191 35 253:21 / /var/lib/docker/devicemapper/mnt/c8240b768603d32e920d365dc9d1dc2a6af46cd23e7ae819947f969e1b4ec661 rw,relatime shared:173 - ext4 /dev/mapper/docker-253:2-425882-c8240b768603d32e920d365dc9d1dc2a6af46cd23e7ae819947f969e1b4ec661 rw,seclabel,discard,stripe=16,data=ordered
195 35 253:22 / /var/lib/docker/devicemapper/mnt/2eb3a01278380bbf3ed12d86ac629eaa70a4351301ee307a5cabe7b5f3b1615f rw,relatime shared:177 - ext4 /dev/mapper/docker-253:2-425882-2eb3a01278380bbf3ed12d86ac629eaa70a4351301ee307a5cabe7b5f3b1615f rw,seclabel,discard,stripe=16,data=ordered
199 35 253:23 / /var/lib/docker/devicemapper/mnt/37a17fb7c9d9b80821235d5f2662879bd3483915f245f9b49cdaa0e38779b70b rw,relatime shared:181 - ext4 /dev/mapper/docker-253:2-425882-37a17fb7c9d9b80821235d5f2662879bd3483915f245f9b49cdaa0e38779b70b rw,seclabel,discard,stripe=16,data=ordered
203 35 253:24 / /var/lib/docker/devicemapper/mnt/aea459ae930bf1de913e2f29428fd80ee678a1e962d4080019d9f9774331ee2b rw,relatime shared:185 - ext4 /dev/mapper/docker-253:2-425882-aea459ae930bf1de913e2f29428fd80ee678a1e962d4080019d9f9774331ee2b rw,seclabel,discard,stripe=16,data=ordered
207 35 253:25 / /var/lib/docker/devicemapper/mnt/928ead0bc06c454bd9f269e8585aeae0a6bd697f46dc8754c2a91309bc810882 rw,relatime shared:189 - ext4 /dev/mapper/docker-253:2-425882-928ead0bc06c454bd9f269e8585aeae0a6bd697f46dc8754c2a91309bc810882 rw,seclabel,discard,stripe=16,data=ordered
211 35 253:26 / /var/lib/docker/devicemapper/mnt/0f284d18481d671644706e7a7244cbcf63d590d634cc882cb8721821929d0420 rw,relatime shared:193 - ext4 /dev/mapper/docker-253:2-425882-0f284d18481d671644706e7a7244cbcf63d590d634cc882cb8721821929d0420 rw,seclabel,discard,stripe=16,data=ordered
215 35 253:27 / /var/lib/docker/devicemapper/mnt/d9dd16722ab34c38db2733e23f69e8f4803ce59658250dd63e98adff95d04919 rw,relatime shared:197 - ext4 /dev/mapper/docker-253:2-425882-d9dd16722ab34c38db2733e23f69e8f4803ce59658250dd63e98adff95d04919 rw,seclabel,discard,stripe=16,data=ordered
219 35 253:28 / /var/lib/docker/devicemapper/mnt/bc4500479f18c2c08c21ad5282e5f826a016a386177d9874c2764751c031d634 rw,relatime shared:201 - ext4 /dev/mapper/docker-253:2-425882-bc4500479f18c2c08c21ad5282e5f826a016a386177d9874c2764751c031d634 rw,seclabel,discard,stripe=16,data=ordered
223 35 253:29 / /var/lib/docker/devicemapper/mnt/7770c8b24eb3d5cc159a065910076938910d307ab2f5d94e1dc3b24c06ee2c8a rw,relatime shared:205 - ext4 /dev/mapper/docker-253:2-425882-7770c8b24eb3d5cc159a065910076938910d307ab2f5d94e1dc3b24c06ee2c8a rw,seclabel,discard,stripe=16,data=ordered
227 35 253:30 / /var/lib/docker/devicemapper/mnt/c280cd3d0bf0aa36b478b292279671624cceafc1a67eaa920fa1082601297adf rw,relatime shared:209 - ext4 /dev/mapper/docker-253:2-425882-c280cd3d0bf0aa36b478b292279671624cceafc1a67eaa920fa1082601297adf rw,seclabel,discard,stripe=16,data=ordered
231 35 253:31 / /var/lib/docker/devicemapper/mnt/8b59a7d9340279f09fea67fd6ad89ddef711e9e7050eb647984f8b5ef006335f rw,relatime shared:213 - ext4 /dev/mapper/docker-253:2-425882-8b59a7d9340279f09fea67fd6ad89ddef711e9e7050eb647984f8b5ef006335f rw,seclabel,discard,stripe=16,data=ordered
235 35 253:32 / /var/lib/docker/devicemapper/mnt/1a28059f29eda821578b1bb27a60cc71f76f846a551abefabce6efd0146dce9f rw,relatime shared:217 - ext4 /dev/mapper/docker-253:2-425882-1a28059f29eda821578b1bb27a60cc71f76f846a551abefabce6efd0146dce9f rw,seclabel,discard,stripe=16,data=ordered
239 35 253:33 / /var/lib/docker/devicemapper/mnt/e9aa60c60128cad1 rw,relatime shared:221 - ext4 /dev/mapper/docker-253:2-425882-e9aa60c60128cad1 rw,seclabel,discard,stripe=16,data=ordered
243 35 253:34 / /var/lib/docker/devicemapper/mnt/5fec11304b6f4713fea7b6ccdcc1adc0a1966187f590fe25a8227428a8df275d-init rw,relatime shared:225 - ext4 /dev/mapper/docker-253:2-425882-5fec11304b6f4713fea7b6ccdcc1adc0a1966187f590fe25a8227428a8df275d-init rw,seclabel,discard,stripe=16,data=ordered
247 35 253:35 / /var/lib/docker/devicemapper/mnt/5fec11304b6f4713fea7b6ccdcc1adc0a1966187f590fe25a8227428a8df275d rw,relatime shared:229 - ext4 /dev/mapper/docker-253:2-425882-5fec11304b6f4713fea7b6ccdcc1adc0a1966187f590fe25a8227428a8df275d rw,seclabel,discard,stripe=16,data=ordered
31 21 0:23 / /DATA/foo_bla_bla rw,relatime - cifs //foo/BLA\040BLA\040BLA/ rw,sec=ntlm,cache=loose,unc=\\foo\BLA BLA BLA,username=my_login,domain=mydomain.com,uid=12345678,forceuid,gid=12345678,forcegid,addr=10.1.30.10,file_mode=0755,dir_mode=0755,nounix,rsize=61440,wsize=65536,actimeo=1`
ubuntuMountInfo = `15 20 0:14 / /sys rw,nosuid,nodev,noexec,relatime - sysfs sysfs rw
16 20 0:3 / /proc rw,nosuid,nodev,noexec,relatime - proc proc rw
17 20 0:5 / /dev rw,relatime - devtmpfs udev rw,size=1015140k,nr_inodes=253785,mode=755
18 17 0:11 / /dev/pts rw,nosuid,noexec,relatime - devpts devpts rw,gid=5,mode=620,ptmxmode=000
19 20 0:15 / /run rw,nosuid,noexec,relatime - tmpfs tmpfs rw,size=205044k,mode=755
20 1 253:0 / / rw,relatime - ext4 /dev/disk/by-label/DOROOT rw,errors=remount-ro,data=ordered
21 15 0:16 / /sys/fs/cgroup rw,relatime - tmpfs none rw,size=4k,mode=755
22 15 0:17 / /sys/fs/fuse/connections rw,relatime - fusectl none rw
23 15 0:6 / /sys/kernel/debug rw,relatime - debugfs none rw
24 15 0:10 / /sys/kernel/security rw,relatime - securityfs none rw
25 19 0:18 / /run/lock rw,nosuid,nodev,noexec,relatime - tmpfs none rw,size=5120k
26 21 0:19 / /sys/fs/cgroup/cpuset rw,relatime - cgroup cgroup rw,cpuset,clone_children
27 19 0:20 / /run/shm rw,nosuid,nodev,relatime - tmpfs none rw
28 21 0:21 / /sys/fs/cgroup/cpu rw,relatime - cgroup cgroup rw,cpu
29 19 0:22 / /run/user rw,nosuid,nodev,noexec,relatime - tmpfs none rw,size=102400k,mode=755
30 15 0:23 / /sys/fs/pstore rw,relatime - pstore none rw
31 21 0:24 / /sys/fs/cgroup/cpuacct rw,relatime - cgroup cgroup rw,cpuacct
32 21 0:25 / /sys/fs/cgroup/memory rw,relatime - cgroup cgroup rw,memory
33 21 0:26 / /sys/fs/cgroup/devices rw,relatime - cgroup cgroup rw,devices
34 21 0:27 / /sys/fs/cgroup/freezer rw,relatime - cgroup cgroup rw,freezer
35 21 0:28 / /sys/fs/cgroup/blkio rw,relatime - cgroup cgroup rw,blkio
36 21 0:29 / /sys/fs/cgroup/perf_event rw,relatime - cgroup cgroup rw,perf_event
37 21 0:30 / /sys/fs/cgroup/hugetlb rw,relatime - cgroup cgroup rw,hugetlb
38 21 0:31 / /sys/fs/cgroup/systemd rw,nosuid,nodev,noexec,relatime - cgroup systemd rw,name=systemd
39 20 0:32 / /var/lib/docker/aufs/mnt/b750fe79269d2ec9a3c593ef05b4332b1d1a02a62b4accb2c21d589ff2f5f2dc rw,relatime - aufs none rw,si=caafa54fdc06525
40 20 0:33 / /var/lib/docker/aufs/mnt/2eed44ac7ce7c75af04f088ed6cb4ce9d164801e91d78c6db65d7ef6d572bba8-init rw,relatime - aufs none rw,si=caafa54f882b525
41 20 0:34 / /var/lib/docker/aufs/mnt/2eed44ac7ce7c75af04f088ed6cb4ce9d164801e91d78c6db65d7ef6d572bba8 rw,relatime - aufs none rw,si=caafa54f8829525
42 20 0:35 / /var/lib/docker/aufs/mnt/16f4d7e96dd612903f425bfe856762f291ff2e36a8ecd55a2209b7d7cd81c30b rw,relatime - aufs none rw,si=caafa54f882d525
43 20 0:36 / /var/lib/docker/aufs/mnt/63ca08b75d7438a9469a5954e003f48ffede73541f6286ce1cb4d7dd4811da7e-init rw,relatime - aufs none rw,si=caafa54f882f525
44 20 0:37 / /var/lib/docker/aufs/mnt/63ca08b75d7438a9469a5954e003f48ffede73541f6286ce1cb4d7dd4811da7e rw,relatime - aufs none rw,si=caafa54f88ba525
45 20 0:38 / /var/lib/docker/aufs/mnt/283f35a910233c756409313be71ecd8fcfef0df57108b8d740b61b3e88860452 rw,relatime - aufs none rw,si=caafa54f88b8525
46 20 0:39 / /var/lib/docker/aufs/mnt/2c6c7253d4090faa3886871fb21bd660609daeb0206588c0602007f7d0f254b1-init rw,relatime - aufs none rw,si=caafa54f88be525
47 20 0:40 / /var/lib/docker/aufs/mnt/2c6c7253d4090faa3886871fb21bd660609daeb0206588c0602007f7d0f254b1 rw,relatime - aufs none rw,si=caafa54f882c525
48 20 0:41 / /var/lib/docker/aufs/mnt/de2b538c97d6366cc80e8658547c923ea1d042f85580df379846f36a4df7049d rw,relatime - aufs none rw,si=caafa54f85bb525
49 20 0:42 / /var/lib/docker/aufs/mnt/94a3d8ed7c27e5b0aa71eba46c736bfb2742afda038e74f2dd6035fb28415b49-init rw,relatime - aufs none rw,si=caafa54fdc00525
50 20 0:43 / /var/lib/docker/aufs/mnt/94a3d8ed7c27e5b0aa71eba46c736bfb2742afda038e74f2dd6035fb28415b49 rw,relatime - aufs none rw,si=caafa54fbaec525
51 20 0:44 / /var/lib/docker/aufs/mnt/6ac1cace985c9fc9bea32234de8b36dba49bdd5e29a2972b327ff939d78a6274 rw,relatime - aufs none rw,si=caafa54f8e1a525
52 20 0:45 / /var/lib/docker/aufs/mnt/dff147033e3a0ef061e1de1ad34256b523d4a8c1fa6bba71a0ab538e8628ff0b-init rw,relatime - aufs none rw,si=caafa54f8e1d525
53 20 0:46 / /var/lib/docker/aufs/mnt/dff147033e3a0ef061e1de1ad34256b523d4a8c1fa6bba71a0ab538e8628ff0b rw,relatime - aufs none rw,si=caafa54f8e1b525
54 20 0:47 / /var/lib/docker/aufs/mnt/cabb117d997f0f93519185aea58389a9762770b7496ed0b74a3e4a083fa45902 rw,relatime - aufs none rw,si=caafa54f810a525
55 20 0:48 / /var/lib/docker/aufs/mnt/e1c8a94ffaa9d532bbbdc6ef771ce8a6c2c06757806ecaf8b68e9108fec65f33-init rw,relatime - aufs none rw,si=caafa54f8529525
56 20 0:49 / /var/lib/docker/aufs/mnt/e1c8a94ffaa9d532bbbdc6ef771ce8a6c2c06757806ecaf8b68e9108fec65f33 rw,relatime - aufs none rw,si=caafa54f852f525
57 20 0:50 / /var/lib/docker/aufs/mnt/16a1526fa445b84ce84f89506d219e87fa488a814063baf045d88b02f21166b3 rw,relatime - aufs none rw,si=caafa54f9e1d525
58 20 0:51 / /var/lib/docker/aufs/mnt/57b9c92e1e368fa7dbe5079f7462e917777829caae732828b003c355fe49da9f-init rw,relatime - aufs none rw,si=caafa54f854d525
59 20 0:52 / /var/lib/docker/aufs/mnt/57b9c92e1e368fa7dbe5079f7462e917777829caae732828b003c355fe49da9f rw,relatime - aufs none rw,si=caafa54f854e525
60 20 0:53 / /var/lib/docker/aufs/mnt/e370c3e286bea027917baa0e4d251262681a472a87056e880dfd0513516dffd9 rw,relatime - aufs none rw,si=caafa54f840a525
61 20 0:54 / /var/lib/docker/aufs/mnt/6b00d3b4f32b41997ec07412b5e18204f82fbe643e7122251cdeb3582abd424e-init rw,relatime - aufs none rw,si=caafa54f8408525
62 20 0:55 / /var/lib/docker/aufs/mnt/6b00d3b4f32b41997ec07412b5e18204f82fbe643e7122251cdeb3582abd424e rw,relatime - aufs none rw,si=caafa54f8409525
63 20 0:56 / /var/lib/docker/aufs/mnt/abd0b5ea5d355a67f911475e271924a5388ee60c27185fcd60d095afc4a09dc7 rw,relatime - aufs none rw,si=caafa54f9eb1525
64 20 0:57 / /var/lib/docker/aufs/mnt/336222effc3f7b89867bb39ff7792ae5412c35c749f127c29159d046b6feedd2-init rw,relatime - aufs none rw,si=caafa54f85bf525
65 20 0:58 / /var/lib/docker/aufs/mnt/336222effc3f7b89867bb39ff7792ae5412c35c749f127c29159d046b6feedd2 rw,relatime - aufs none rw,si=caafa54f85b8525
66 20 0:59 / /var/lib/docker/aufs/mnt/912e1bf28b80a09644503924a8a1a4fb8ed10b808ca847bda27a369919aa52fa rw,relatime - aufs none rw,si=caafa54fbaea525
67 20 0:60 / /var/lib/docker/aufs/mnt/386f722875013b4a875118367abc783fc6617a3cb7cf08b2b4dcf550b4b9c576-init rw,relatime - aufs none rw,si=caafa54f8472525
68 20 0:61 / /var/lib/docker/aufs/mnt/386f722875013b4a875118367abc783fc6617a3cb7cf08b2b4dcf550b4b9c576 rw,relatime - aufs none rw,si=caafa54f8474525
69 20 0:62 / /var/lib/docker/aufs/mnt/5aaebb79ef3097dfca377889aeb61a0c9d5e3795117d2b08d0751473c671dfb2 rw,relatime - aufs none rw,si=caafa54f8c5e525
70 20 0:63 / /var/lib/docker/aufs/mnt/5ba3e493279d01277d583600b81c7c079e691b73c3a2bdea8e4b12a35a418be2-init rw,relatime - aufs none rw,si=caafa54f8c3b525
71 20 0:64 / /var/lib/docker/aufs/mnt/5ba3e493279d01277d583600b81c7c079e691b73c3a2bdea8e4b12a35a418be2 rw,relatime - aufs none rw,si=caafa54f8c3d525
72 20 0:65 / /var/lib/docker/aufs/mnt/2777f0763da4de93f8bebbe1595cc77f739806a158657b033eca06f827b6028a rw,relatime - aufs none rw,si=caafa54f8c3e525
73 20 0:66 / /var/lib/docker/aufs/mnt/5d7445562acf73c6f0ae34c3dd0921d7457de1ba92a587d9e06a44fa209eeb3e-init rw,relatime - aufs none rw,si=caafa54f8c39525
74 20 0:67 / /var/lib/docker/aufs/mnt/5d7445562acf73c6f0ae34c3dd0921d7457de1ba92a587d9e06a44fa209eeb3e rw,relatime - aufs none rw,si=caafa54f854f525
75 20 0:68 / /var/lib/docker/aufs/mnt/06400b526ec18b66639c96efc41a84f4ae0b117cb28dafd56be420651b4084a0 rw,relatime - aufs none rw,si=caafa54f840b525
76 20 0:69 / /var/lib/docker/aufs/mnt/e051d45ec42d8e3e1cc57bb39871a40de486dc123522e9c067fbf2ca6a357785-init rw,relatime - aufs none rw,si=caafa54fdddf525
77 20 0:70 / /var/lib/docker/aufs/mnt/e051d45ec42d8e3e1cc57bb39871a40de486dc123522e9c067fbf2ca6a357785 rw,relatime - aufs none rw,si=caafa54f854b525
78 20 0:71 / /var/lib/docker/aufs/mnt/1ff414fa93fd61ec81b0ab7b365a841ff6545accae03cceac702833aaeaf718f rw,relatime - aufs none rw,si=caafa54f8d85525
79 20 0:72 / /var/lib/docker/aufs/mnt/c661b2f871dd5360e46a2aebf8f970f6d39a2ff64e06979aa0361227c88128b8-init rw,relatime - aufs none rw,si=caafa54f8da3525
80 20 0:73 / /var/lib/docker/aufs/mnt/c661b2f871dd5360e46a2aebf8f970f6d39a2ff64e06979aa0361227c88128b8 rw,relatime - aufs none rw,si=caafa54f8da2525
81 20 0:74 / /var/lib/docker/aufs/mnt/b68b1d4fe4d30016c552398e78b379a39f651661d8e1fa5f2460c24a5e723420 rw,relatime - aufs none rw,si=caafa54f8d81525
82 20 0:75 / /var/lib/docker/aufs/mnt/c5c5979c936cd0153a4c626fa9d69ce4fce7d924cc74fa68b025d2f585031739-init rw,relatime - aufs none rw,si=caafa54f8da1525
83 20 0:76 / /var/lib/docker/aufs/mnt/c5c5979c936cd0153a4c626fa9d69ce4fce7d924cc74fa68b025d2f585031739 rw,relatime - aufs none rw,si=caafa54f8da0525
84 20 0:77 / /var/lib/docker/aufs/mnt/53e10b0329afc0e0d3322d31efaed4064139dc7027fe6ae445cffd7104bcc94f rw,relatime - aufs none rw,si=caafa54f8c35525
85 20 0:78 / /var/lib/docker/aufs/mnt/3bfafd09ff2603e2165efacc2215c1f51afabba6c42d04a68cc2df0e8cc31494-init rw,relatime - aufs none rw,si=caafa54f8db8525
86 20 0:79 / /var/lib/docker/aufs/mnt/3bfafd09ff2603e2165efacc2215c1f51afabba6c42d04a68cc2df0e8cc31494 rw,relatime - aufs none rw,si=caafa54f8dba525
87 20 0:80 / /var/lib/docker/aufs/mnt/90fdd2c03eeaf65311f88f4200e18aef6d2772482712d9aea01cd793c64781b5 rw,relatime - aufs none rw,si=caafa54f8315525
88 20 0:81 / /var/lib/docker/aufs/mnt/7bdf2591c06c154ceb23f5e74b1d03b18fbf6fe96e35fbf539b82d446922442f-init rw,relatime - aufs none rw,si=caafa54f8fc6525
89 20 0:82 / /var/lib/docker/aufs/mnt/7bdf2591c06c154ceb23f5e74b1d03b18fbf6fe96e35fbf539b82d446922442f rw,relatime - aufs none rw,si=caafa54f8468525
90 20 0:83 / /var/lib/docker/aufs/mnt/8cf9a993f50f3305abad3da268c0fc44ff78a1e7bba595ef9de963497496c3f9 rw,relatime - aufs none rw,si=caafa54f8c59525
91 20 0:84 / /var/lib/docker/aufs/mnt/ecc896fd74b21840a8d35e8316b92a08b1b9c83d722a12acff847e9f0ff17173-init rw,relatime - aufs none rw,si=caafa54f846a525
92 20 0:85 / /var/lib/docker/aufs/mnt/ecc896fd74b21840a8d35e8316b92a08b1b9c83d722a12acff847e9f0ff17173 rw,relatime - aufs none rw,si=caafa54f846b525
93 20 0:86 / /var/lib/docker/aufs/mnt/d8c8288ec920439a48b5796bab5883ee47a019240da65e8d8f33400c31bac5df rw,relatime - aufs none rw,si=caafa54f8dbf525
94 20 0:87 / /var/lib/docker/aufs/mnt/ecba66710bcd03199b9398e46c005cd6b68d0266ec81dc8b722a29cc417997c6-init rw,relatime - aufs none rw,si=caafa54f810f525
95 20 0:88 / /var/lib/docker/aufs/mnt/ecba66710bcd03199b9398e46c005cd6b68d0266ec81dc8b722a29cc417997c6 rw,relatime - aufs none rw,si=caafa54fbae9525
96 20 0:89 / /var/lib/docker/aufs/mnt/befc1c67600df449dddbe796c0d06da7caff1d2bbff64cde1f0ba82d224996b5 rw,relatime - aufs none rw,si=caafa54f8dab525
97 20 0:90 / /var/lib/docker/aufs/mnt/c9f470e73d2742629cdc4084a1b2c1a8302914f2aa0d0ec4542371df9a050562-init rw,relatime - aufs none rw,si=caafa54fdc02525
98 20 0:91 / /var/lib/docker/aufs/mnt/c9f470e73d2742629cdc4084a1b2c1a8302914f2aa0d0ec4542371df9a050562 rw,relatime - aufs none rw,si=caafa54f9eb0525
99 20 0:92 / /var/lib/docker/aufs/mnt/2a31f10029f04ff9d4381167a9b739609853d7220d55a56cb654779a700ee246 rw,relatime - aufs none rw,si=caafa54f8c37525
100 20 0:93 / /var/lib/docker/aufs/mnt/8c4261b8e3e4b21ebba60389bd64b6261217e7e6b9fd09e201d5a7f6760f6927-init rw,relatime - aufs none rw,si=caafa54fd173525
101 20 0:94 / /var/lib/docker/aufs/mnt/8c4261b8e3e4b21ebba60389bd64b6261217e7e6b9fd09e201d5a7f6760f6927 rw,relatime - aufs none rw,si=caafa54f8108525
102 20 0:95 / /var/lib/docker/aufs/mnt/eaa0f57403a3dc685268f91df3fbcd7a8423cee50e1a9ee5c3e1688d9d676bb4 rw,relatime - aufs none rw,si=caafa54f852d525
103 20 0:96 / /var/lib/docker/aufs/mnt/9cfe69a2cbffd9bfc7f396d4754f6fe5cc457ef417b277797be3762dfe955a6b-init rw,relatime - aufs none rw,si=caafa54f8d80525
104 20 0:97 / /var/lib/docker/aufs/mnt/9cfe69a2cbffd9bfc7f396d4754f6fe5cc457ef417b277797be3762dfe955a6b rw,relatime - aufs none rw,si=caafa54f8fc3525
105 20 0:98 / /var/lib/docker/aufs/mnt/d1b322ae17613c6adee84e709641a9244ac56675244a89a64dc0075075fcbb83 rw,relatime - aufs none rw,si=caafa54f8c58525
106 20 0:99 / /var/lib/docker/aufs/mnt/d46c2a8e9da7e91ab34fd9c192851c246a4e770a46720bda09e55c7554b9dbbd-init rw,relatime - aufs none rw,si=caafa54f8c63525
107 20 0:100 / /var/lib/docker/aufs/mnt/d46c2a8e9da7e91ab34fd9c192851c246a4e770a46720bda09e55c7554b9dbbd rw,relatime - aufs none rw,si=caafa54f8c67525
108 20 0:101 / /var/lib/docker/aufs/mnt/bc9d2a264158f83a617a069bf17cbbf2a2ba453db7d3951d9dc63cc1558b1c2b rw,relatime - aufs none rw,si=caafa54f8dbe525
109 20 0:102 / /var/lib/docker/aufs/mnt/9e6abb8d72bbeb4d5cf24b96018528015ba830ce42b4859965bd482cbd034e99-init rw,relatime - aufs none rw,si=caafa54f9e0d525
110 20 0:103 / /var/lib/docker/aufs/mnt/9e6abb8d72bbeb4d5cf24b96018528015ba830ce42b4859965bd482cbd034e99 rw,relatime - aufs none rw,si=caafa54f9e1b525
111 20 0:104 / /var/lib/docker/aufs/mnt/d4dca7b02569c732e740071e1c654d4ad282de5c41edb619af1f0aafa618be26 rw,relatime - aufs none rw,si=caafa54f8dae525
112 20 0:105 / /var/lib/docker/aufs/mnt/fea63da40fa1c5ffbad430dde0bc64a8fc2edab09a051fff55b673c40a08f6b7-init rw,relatime - aufs none rw,si=caafa54f8c5c525
113 20 0:106 / /var/lib/docker/aufs/mnt/fea63da40fa1c5ffbad430dde0bc64a8fc2edab09a051fff55b673c40a08f6b7 rw,relatime - aufs none rw,si=caafa54fd172525
114 20 0:107 / /var/lib/docker/aufs/mnt/e60c57499c0b198a6734f77f660cdbbd950a5b78aa23f470ca4f0cfcc376abef rw,relatime - aufs none rw,si=caafa54909c4525
115 20 0:108 / /var/lib/docker/aufs/mnt/099c78e7ccd9c8717471bb1bbfff838c0a9913321ba2f214fbeaf92c678e5b35-init rw,relatime - aufs none rw,si=caafa54909c3525
116 20 0:109 / /var/lib/docker/aufs/mnt/099c78e7ccd9c8717471bb1bbfff838c0a9913321ba2f214fbeaf92c678e5b35 rw,relatime - aufs none rw,si=caafa54909c7525
117 20 0:110 / /var/lib/docker/aufs/mnt/2997be666d58b9e71469759bcb8bd9608dad0e533a1a7570a896919ba3388825 rw,relatime - aufs none rw,si=caafa54f8557525
118 20 0:111 / /var/lib/docker/aufs/mnt/730694eff438ef20569df38dfb38a920969d7ff2170cc9aa7cb32a7ed8147a93-init rw,relatime - aufs none rw,si=caafa54c6e88525
119 20 0:112 / /var/lib/docker/aufs/mnt/730694eff438ef20569df38dfb38a920969d7ff2170cc9aa7cb32a7ed8147a93 rw,relatime - aufs none rw,si=caafa54c6e8e525
120 20 0:113 / /var/lib/docker/aufs/mnt/a672a1e2f2f051f6e19ed1dfbe80860a2d774174c49f7c476695f5dd1d5b2f67 rw,relatime - aufs none rw,si=caafa54c6e15525
121 20 0:114 / /var/lib/docker/aufs/mnt/aba3570e17859f76cf29d282d0d150659c6bd80780fdc52a465ba05245c2a420-init rw,relatime - aufs none rw,si=caafa54f8dad525
122 20 0:115 / /var/lib/docker/aufs/mnt/aba3570e17859f76cf29d282d0d150659c6bd80780fdc52a465ba05245c2a420 rw,relatime - aufs none rw,si=caafa54f8d84525
123 20 0:116 / /var/lib/docker/aufs/mnt/2abc86007aca46fb4a817a033e2a05ccacae40b78ea4b03f8ea616b9ada40e2e rw,relatime - aufs none rw,si=caafa54c6e8b525
124 20 0:117 / /var/lib/docker/aufs/mnt/36352f27f7878e648367a135bd1ec3ed497adcb8ac13577ee892a0bd921d2374-init rw,relatime - aufs none rw,si=caafa54c6e8d525
125 20 0:118 / /var/lib/docker/aufs/mnt/36352f27f7878e648367a135bd1ec3ed497adcb8ac13577ee892a0bd921d2374 rw,relatime - aufs none rw,si=caafa54f8c34525
126 20 0:119 / /var/lib/docker/aufs/mnt/2f95ca1a629cea8363b829faa727dd52896d5561f2c96ddee4f697ea2fc872c2 rw,relatime - aufs none rw,si=caafa54c6e8a525
127 20 0:120 / /var/lib/docker/aufs/mnt/f108c8291654f179ef143a3e07de2b5a34adbc0b28194a0ab17742b6db9a7fb2-init rw,relatime - aufs none rw,si=caafa54f8e19525
128 20 0:121 / /var/lib/docker/aufs/mnt/f108c8291654f179ef143a3e07de2b5a34adbc0b28194a0ab17742b6db9a7fb2 rw,relatime - aufs none rw,si=caafa54fa8c6525
129 20 0:122 / /var/lib/docker/aufs/mnt/c1d04dfdf8cccb3676d5a91e84e9b0781ce40623d127d038bcfbe4c761b27401 rw,relatime - aufs none rw,si=caafa54f8c30525
130 20 0:123 / /var/lib/docker/aufs/mnt/3f4898ffd0e1239aeebf1d1412590cdb7254207fa3883663e2c40cf772e5f05a-init rw,relatime - aufs none rw,si=caafa54c6e1a525
131 20 0:124 / /var/lib/docker/aufs/mnt/3f4898ffd0e1239aeebf1d1412590cdb7254207fa3883663e2c40cf772e5f05a rw,relatime - aufs none rw,si=caafa54c6e1c525
132 20 0:125 / /var/lib/docker/aufs/mnt/5ae3b6fccb1539fc02d420e86f3e9637bef5b711fed2ca31a2f426c8f5deddbf rw,relatime - aufs none rw,si=caafa54c4fea525
133 20 0:126 / /var/lib/docker/aufs/mnt/310bfaf80d57020f2e73b06aeffb0b9b0ca2f54895f88bf5e4d1529ccac58fe0-init rw,relatime - aufs none rw,si=caafa54c6e1e525
134 20 0:127 / /var/lib/docker/aufs/mnt/310bfaf80d57020f2e73b06aeffb0b9b0ca2f54895f88bf5e4d1529ccac58fe0 rw,relatime - aufs none rw,si=caafa54fa8c0525
135 20 0:128 / /var/lib/docker/aufs/mnt/f382bd5aaccaf2d04a59089ac7cb12ec87efd769fd0c14d623358fbfd2a3f896 rw,relatime - aufs none rw,si=caafa54c4fec525
136 20 0:129 / /var/lib/docker/aufs/mnt/50d45e9bb2d779bc6362824085564c7578c231af5ae3b3da116acf7e17d00735-init rw,relatime - aufs none rw,si=caafa54c4fef525
137 20 0:130 / /var/lib/docker/aufs/mnt/50d45e9bb2d779bc6362824085564c7578c231af5ae3b3da116acf7e17d00735 rw,relatime - aufs none rw,si=caafa54c4feb525
138 20 0:131 / /var/lib/docker/aufs/mnt/a9c5ee0854dc083b6bf62b7eb1e5291aefbb10702289a446471ce73aba0d5d7d rw,relatime - aufs none rw,si=caafa54909c6525
139 20 0:134 / /var/lib/docker/aufs/mnt/03a613e7bd5078819d1fd92df4e671c0127559a5e0b5a885cc8d5616875162f0-init rw,relatime - aufs none rw,si=caafa54804fe525
140 20 0:135 / /var/lib/docker/aufs/mnt/03a613e7bd5078819d1fd92df4e671c0127559a5e0b5a885cc8d5616875162f0 rw,relatime - aufs none rw,si=caafa54804fa525
141 20 0:136 / /var/lib/docker/aufs/mnt/7ec3277e5c04c907051caf9c9c35889f5fcd6463e5485971b25404566830bb70 rw,relatime - aufs none rw,si=caafa54804f9525
142 20 0:139 / /var/lib/docker/aufs/mnt/26b5b5d71d79a5b2bfcf8bc4b2280ee829f261eb886745dd90997ed410f7e8b8-init rw,relatime - aufs none rw,si=caafa54c6ef6525
143 20 0:140 / /var/lib/docker/aufs/mnt/26b5b5d71d79a5b2bfcf8bc4b2280ee829f261eb886745dd90997ed410f7e8b8 rw,relatime - aufs none rw,si=caafa54c6ef5525
144 20 0:356 / /var/lib/docker/aufs/mnt/e6ecde9e2c18cd3c75f424c67b6d89685cfee0fc67abf2cb6bdc0867eb998026 rw,relatime - aufs none rw,si=caafa548068e525`
gentooMountinfo = `15 1 8:6 / / rw,noatime,nodiratime - ext4 /dev/sda6 rw,data=ordered
16 15 0:3 / /proc rw,nosuid,nodev,noexec,relatime - proc proc rw
17 15 0:14 / /run rw,nosuid,nodev,relatime - tmpfs tmpfs rw,size=3292172k,mode=755
18 15 0:5 / /dev rw,nosuid,relatime - devtmpfs udev rw,size=10240k,nr_inodes=4106451,mode=755
19 18 0:12 / /dev/mqueue rw,nosuid,nodev,noexec,relatime - mqueue mqueue rw
20 18 0:10 / /dev/pts rw,nosuid,noexec,relatime - devpts devpts rw,gid=5,mode=620,ptmxmode=000
21 18 0:15 / /dev/shm rw,nosuid,nodev,noexec,relatime - tmpfs shm rw
22 15 0:16 / /sys rw,nosuid,nodev,noexec,relatime - sysfs sysfs rw
23 22 0:7 / /sys/kernel/debug rw,nosuid,nodev,noexec,relatime - debugfs debugfs rw
24 22 0:17 / /sys/fs/cgroup rw,nosuid,nodev,noexec,relatime - tmpfs cgroup_root rw,size=10240k,mode=755
25 24 0:18 / /sys/fs/cgroup/openrc rw,nosuid,nodev,noexec,relatime - cgroup openrc rw,release_agent=/lib64/rc/sh/cgroup-release-agent.sh,name=openrc
26 24 0:19 / /sys/fs/cgroup/cpuset rw,nosuid,nodev,noexec,relatime - cgroup cpuset rw,cpuset,clone_children
27 24 0:20 / /sys/fs/cgroup/cpu rw,nosuid,nodev,noexec,relatime - cgroup cpu rw,cpu,clone_children
28 24 0:21 / /sys/fs/cgroup/cpuacct rw,nosuid,nodev,noexec,relatime - cgroup cpuacct rw,cpuacct,clone_children
29 24 0:22 / /sys/fs/cgroup/memory rw,nosuid,nodev,noexec,relatime - cgroup memory rw,memory,clone_children
30 24 0:23 / /sys/fs/cgroup/devices rw,nosuid,nodev,noexec,relatime - cgroup devices rw,devices,clone_children
31 24 0:24 / /sys/fs/cgroup/freezer rw,nosuid,nodev,noexec,relatime - cgroup freezer rw,freezer,clone_children
32 24 0:25 / /sys/fs/cgroup/blkio rw,nosuid,nodev,noexec,relatime - cgroup blkio rw,blkio,clone_children
33 15 8:1 / /boot rw,noatime,nodiratime - vfat /dev/sda1 rw,fmask=0022,dmask=0022,codepage=437,iocharset=iso8859-1,shortname=mixed,errors=remount-ro
34 15 8:18 / /mnt/xfs rw,noatime,nodiratime - xfs /dev/sdb2 rw,attr2,inode64,noquota
35 15 0:26 / /tmp rw,relatime - tmpfs tmpfs rw
36 16 0:27 / /proc/sys/fs/binfmt_misc rw,nosuid,nodev,noexec,relatime - binfmt_misc binfmt_misc rw
42 15 0:33 / /var/lib/nfs/rpc_pipefs rw,relatime - rpc_pipefs rpc_pipefs rw
43 16 0:34 / /proc/fs/nfsd rw,nosuid,nodev,noexec,relatime - nfsd nfsd rw
44 15 0:35 / /home/tianon/.gvfs rw,nosuid,nodev,relatime - fuse.gvfs-fuse-daemon gvfs-fuse-daemon rw,user_id=1000,group_id=1000
68 15 0:3336 / /var/lib/docker/aufs/mnt/3597a1a6d6298c1decc339ebb90aad6f7d6ba2e15af3131b1f85e7ee4787a0cd rw,relatime - aufs none rw,si=9b4a7640128db39c
86 68 8:6 /var/lib/docker/containers/3597a1a6d6298c1decc339ebb90aad6f7d6ba2e15af3131b1f85e7ee4787a0cd/config.env /var/lib/docker/aufs/mnt/3597a1a6d6298c1decc339ebb90aad6f7d6ba2e15af3131b1f85e7ee4787a0cd/.dockerenv rw,noatime,nodiratime - ext4 /dev/sda6 rw,data=ordered
87 68 8:6 /etc/resolv.conf /var/lib/docker/aufs/mnt/3597a1a6d6298c1decc339ebb90aad6f7d6ba2e15af3131b1f85e7ee4787a0cd/etc/resolv.conf rw,noatime,nodiratime - ext4 /dev/sda6 rw,data=ordered
88 68 8:6 /var/lib/docker/containers/3597a1a6d6298c1decc339ebb90aad6f7d6ba2e15af3131b1f85e7ee4787a0cd/hostname /var/lib/docker/aufs/mnt/3597a1a6d6298c1decc339ebb90aad6f7d6ba2e15af3131b1f85e7ee4787a0cd/etc/hostname rw,noatime,nodiratime - ext4 /dev/sda6 rw,data=ordered
89 68 8:6 /var/lib/docker/containers/3597a1a6d6298c1decc339ebb90aad6f7d6ba2e15af3131b1f85e7ee4787a0cd/hosts /var/lib/docker/aufs/mnt/3597a1a6d6298c1decc339ebb90aad6f7d6ba2e15af3131b1f85e7ee4787a0cd/etc/hosts rw,noatime,nodiratime - ext4 /dev/sda6 rw,data=ordered
38 15 0:3384 / /var/lib/docker/aufs/mnt/0292005a9292401bb5197657f2b682d97d8edcb3b72b5e390d2a680139985b55 rw,relatime - aufs none rw,si=9b4a7642b584939c
39 15 0:3385 / /var/lib/docker/aufs/mnt/59db98c889de5f71b70cfb82c40cbe47b64332f0f56042a2987a9e5df6e5e3aa rw,relatime - aufs none rw,si=9b4a7642b584e39c
40 15 0:3386 / /var/lib/docker/aufs/mnt/0545f0f2b6548eb9601d08f35a08f5a0a385407d36027a28f58e06e9f61e0278 rw,relatime - aufs none rw,si=9b4a7642b584b39c
41 15 0:3387 / /var/lib/docker/aufs/mnt/d882cfa16d1aa8fe0331a36e79be3d80b151e49f24fc39a39c3fed1735d5feb5 rw,relatime - aufs none rw,si=9b4a76453040039c
45 15 0:3388 / /var/lib/docker/aufs/mnt/055ca3befcb1626e74f5344b3398724ff05c0de0e20021683d04305c9e70a3f6 rw,relatime - aufs none rw,si=9b4a76453040739c
46 15 0:3389 / /var/lib/docker/aufs/mnt/b899e4567a351745d4285e7f1c18fdece75d877deb3041981cd290be348b7aa6 rw,relatime - aufs none rw,si=9b4a7647def4039c
47 15 0:3390 / /var/lib/docker/aufs/mnt/067ca040292c58954c5129f953219accfae0d40faca26b4d05e76ca76a998f16 rw,relatime - aufs none rw,si=9b4a7647def4239c
48 15 0:3391 / /var/lib/docker/aufs/mnt/8c995e7cb6e5082742daeea720e340b021d288d25d92e0412c03d200df308a11 rw,relatime - aufs none rw,si=9b4a764479c1639c
49 15 0:3392 / /var/lib/docker/aufs/mnt/07cc54dfae5b45300efdacdd53cc72c01b9044956a86ce7bff42d087e426096d rw,relatime - aufs none rw,si=9b4a764479c1739c
50 15 0:3393 / /var/lib/docker/aufs/mnt/0a9c95cf4c589c05b06baa79150b0cc1d8e7102759fe3ce4afaabb8247ca4f85 rw,relatime - aufs none rw,si=9b4a7644059c839c
51 15 0:3394 / /var/lib/docker/aufs/mnt/468fa98cececcf4e226e8370f18f4f848d63faf287fb8321a07f73086441a3a0 rw,relatime - aufs none rw,si=9b4a7644059ca39c
52 15 0:3395 / /var/lib/docker/aufs/mnt/0b826192231c5ce066fffb5beff4397337b5fc19a377aa7c6282c7c0ce7f111f rw,relatime - aufs none rw,si=9b4a764479c1339c
53 15 0:3396 / /var/lib/docker/aufs/mnt/93b8ba1b772fbe79709b909c43ea4b2c30d712e53548f467db1ffdc7a384f196 rw,relatime - aufs none rw,si=9b4a7640798a739c
54 15 0:3397 / /var/lib/docker/aufs/mnt/0c0d0acfb506859b12ef18cdfef9ebed0b43a611482403564224bde9149d373c rw,relatime - aufs none rw,si=9b4a7640798a039c
55 15 0:3398 / /var/lib/docker/aufs/mnt/33648c39ab6c7c74af0243d6d6a81b052e9e25ad1e04b19892eb2dde013e358b rw,relatime - aufs none rw,si=9b4a7644b439b39c
56 15 0:3399 / /var/lib/docker/aufs/mnt/0c12bea97a1c958a3c739fb148536c1c89351d48e885ecda8f0499b5cc44407e rw,relatime - aufs none rw,si=9b4a7640798a239c
57 15 0:3400 / /var/lib/docker/aufs/mnt/ed443988ce125f172d7512e84a4de2627405990fd767a16adefa8ce700c19ce8 rw,relatime - aufs none rw,si=9b4a7644c8ed339c
59 15 0:3402 / /var/lib/docker/aufs/mnt/f61612c324ff3c924d3f7a82fb00a0f8d8f73c248c41897061949e9f5ab7e3b1 rw,relatime - aufs none rw,si=9b4a76442810c39c
60 15 0:3403 / /var/lib/docker/aufs/mnt/0f1ee55c6c4e25027b80de8e64b8b6fb542b3b41aa0caab9261da75752e22bfd rw,relatime - aufs none rw,si=9b4a76442810e39c
61 15 0:3404 / /var/lib/docker/aufs/mnt/956f6cc4af5785cb3ee6963dcbca668219437d9b28f513290b1453ac64a34f97 rw,relatime - aufs none rw,si=9b4a7644303ec39c
62 15 0:3405 / /var/lib/docker/aufs/mnt/1099769158c4b4773e2569e38024e8717e400f87a002c41d8cf47cb81b051ba6 rw,relatime - aufs none rw,si=9b4a7644303ee39c
63 15 0:3406 / /var/lib/docker/aufs/mnt/11890ceb98d4442595b676085cd7b21550ab85c5df841e0fba997ff54e3d522d rw,relatime - aufs none rw,si=9b4a7644303ed39c
64 15 0:3407 / /var/lib/docker/aufs/mnt/acdb90dc378e8ed2420b43a6d291f1c789a081cd1904018780cc038fcd7aae53 rw,relatime - aufs none rw,si=9b4a76434be2139c
65 15 0:3408 / /var/lib/docker/aufs/mnt/120e716f19d4714fbe63cc1ed246204f2c1106eefebc6537ba2587d7e7711959 rw,relatime - aufs none rw,si=9b4a76434be2339c
66 15 0:3409 / /var/lib/docker/aufs/mnt/b197b7fffb61d89e0ba1c40de9a9fc0d912e778b3c1bd828cf981ff37c1963bc rw,relatime - aufs none rw,si=9b4a76434be2039c
70 15 0:3412 / /var/lib/docker/aufs/mnt/1434b69d2e1bb18a9f0b96b9cdac30132b2688f5d1379f68a39a5e120c2f93eb rw,relatime - aufs none rw,si=9b4a76434be2639c
71 15 0:3413 / /var/lib/docker/aufs/mnt/16006e83caf33ab5eb0cd6afc92ea2ee8edeff897496b0bb3ec3a75b767374b3 rw,relatime - aufs none rw,si=9b4a7644d790439c
72 15 0:3414 / /var/lib/docker/aufs/mnt/55bfa5f44e94d27f91f79ba901b118b15098449165c87abf1b53ffff147ff164 rw,relatime - aufs none rw,si=9b4a7644d790239c
73 15 0:3415 / /var/lib/docker/aufs/mnt/1912b97a07ab21ccd98a2a27bc779bf3cf364a3138afa3c3e6f7f169a3c3eab5 rw,relatime - aufs none rw,si=9b4a76441822739c
76 15 0:3418 / /var/lib/docker/aufs/mnt/1a7c3292e8879bd91ffd9282e954f643b1db5683093574c248ff14a9609f2f56 rw,relatime - aufs none rw,si=9b4a76438cb7239c
77 15 0:3419 / /var/lib/docker/aufs/mnt/bb1faaf0d076ddba82c2318305a85f490dafa4e8a8640a8db8ed657c439120cc rw,relatime - aufs none rw,si=9b4a76438cb7339c
78 15 0:3420 / /var/lib/docker/aufs/mnt/1ab869f21d2241a73ac840c7f988490313f909ac642eba71d092204fec66dd7c rw,relatime - aufs none rw,si=9b4a76438cb7639c
79 15 0:3421 / /var/lib/docker/aufs/mnt/fd7245b2cfe3890fa5f5b452260e4edf9e7fb7746532ed9d83f7a0d7dbaa610e rw,relatime - aufs none rw,si=9b4a7644bdc0139c
80 15 0:3422 / /var/lib/docker/aufs/mnt/1e5686c5301f26b9b3cd24e322c608913465cc6c5d0dcd7c5e498d1314747d61 rw,relatime - aufs none rw,si=9b4a7644bdc0639c
81 15 0:3423 / /var/lib/docker/aufs/mnt/52edf6ee6e40bfec1e9301a4d4a92ab83d144e2ae4ce5099e99df6138cb844bf rw,relatime - aufs none rw,si=9b4a7644bdc0239c
82 15 0:3424 / /var/lib/docker/aufs/mnt/1ea10fb7085d28cda4904657dff0454e52598d28e1d77e4f2965bbc3666e808f rw,relatime - aufs none rw,si=9b4a76438cb7139c
83 15 0:3425 / /var/lib/docker/aufs/mnt/9c03e98c3593946dbd4087f8d83f9ca262f4a2efdc952ce60690838b9ba6c526 rw,relatime - aufs none rw,si=9b4a76443020639c
84 15 0:3426 / /var/lib/docker/aufs/mnt/220a2344d67437602c6d2cee9a98c46be13f82c2a8063919dd2fad52bf2fb7dd rw,relatime - aufs none rw,si=9b4a76434bff339c
94 15 0:3427 / /var/lib/docker/aufs/mnt/3b32876c5b200312c50baa476ff342248e88c8ea96e6a1032cd53a88738a1cf2 rw,relatime - aufs none rw,si=9b4a76434bff139c
95 15 0:3428 / /var/lib/docker/aufs/mnt/23ee2b8b0d4ae8db6f6d1e168e2c6f79f8a18f953b09f65e0d22cc1e67a3a6fa rw,relatime - aufs none rw,si=9b4a7646c305c39c
96 15 0:3429 / /var/lib/docker/aufs/mnt/e86e6daa70b61b57945fa178222615f3c3d6bcef12c9f28e9f8623d44dc2d429 rw,relatime - aufs none rw,si=9b4a7646c305f39c
97 15 0:3430 / /var/lib/docker/aufs/mnt/2413d07623e80860bb2e9e306fbdee699afd07525785c025c591231e864aa162 rw,relatime - aufs none rw,si=9b4a76434bff039c
98 15 0:3431 / /var/lib/docker/aufs/mnt/adfd622eb22340fc80b429e5564b125668e260bf9068096c46dd59f1386a4b7d rw,relatime - aufs none rw,si=9b4a7646a7a1039c
102 15 0:3435 / /var/lib/docker/aufs/mnt/27cd92e7a91d02e2d6b44d16679a00fb6d169b19b88822891084e7fd1a84882d rw,relatime - aufs none rw,si=9b4a7646f25ec39c
103 15 0:3436 / /var/lib/docker/aufs/mnt/27dfdaf94cfbf45055c748293c37dd68d9140240bff4c646cb09216015914a88 rw,relatime - aufs none rw,si=9b4a7646732f939c
104 15 0:3437 / /var/lib/docker/aufs/mnt/5ed7524aff68dfbf0fc601cbaeac01bab14391850a973dabf3653282a627920f rw,relatime - aufs none rw,si=9b4a7646732f839c
105 15 0:3438 / /var/lib/docker/aufs/mnt/2a0d4767e536beb5785b60e071e3ac8e5e812613ab143a9627bee77d0c9ab062 rw,relatime - aufs none rw,si=9b4a7646732fe39c
106 15 0:3439 / /var/lib/docker/aufs/mnt/dea3fc045d9f4ae51ba952450b948a822cf85c39411489ca5224f6d9a8d02bad rw,relatime - aufs none rw,si=9b4a764012ad839c
107 15 0:3440 / /var/lib/docker/aufs/mnt/2d140a787160798da60cb67c21b1210054ad4dafecdcf832f015995b9aa99cfd rw,relatime - aufs none rw,si=9b4a764012add39c
108 15 0:3441 / /var/lib/docker/aufs/mnt/cb190b2a8e984475914430fbad2382e0d20b9b659f8ef83ae8d170cc672e519c rw,relatime - aufs none rw,si=9b4a76454d9c239c
109 15 0:3442 / /var/lib/docker/aufs/mnt/2f4a012d5a7ffd90256a6e9aa479054b3dddbc3c6a343f26dafbf3196890223b rw,relatime - aufs none rw,si=9b4a76454d9c439c
110 15 0:3443 / /var/lib/docker/aufs/mnt/63cc77904b80c4ffbf49cb974c5d8733dc52ad7640d3ae87554b325d7312d87f rw,relatime - aufs none rw,si=9b4a76454d9c339c
111 15 0:3444 / /var/lib/docker/aufs/mnt/30333e872c451482ea2d235ff2192e875bd234006b238ae2bdde3b91a86d7522 rw,relatime - aufs none rw,si=9b4a76422cebf39c
112 15 0:3445 / /var/lib/docker/aufs/mnt/6c54fc1125da3925cae65b5c9a98f3be55b0a2c2666082e5094a4ba71beb5bff rw,relatime - aufs none rw,si=9b4a7646dd5a439c
113 15 0:3446 / /var/lib/docker/aufs/mnt/3087d48cb01cda9d0a83a9ca301e6ea40e8593d18c4921be4794c91a420ab9a3 rw,relatime - aufs none rw,si=9b4a7646dd5a739c
114 15 0:3447 / /var/lib/docker/aufs/mnt/cc2607462a8f55b179a749b144c3fdbb50678e1a4f3065ea04e283e9b1f1d8e2 rw,relatime - aufs none rw,si=9b4a7646dd5a239c
117 15 0:3450 / /var/lib/docker/aufs/mnt/310c5e8392b29e8658a22e08d96d63936633b7e2c38e8d220047928b00a03d24 rw,relatime - aufs none rw,si=9b4a7647932d739c
118 15 0:3451 / /var/lib/docker/aufs/mnt/38a1f0029406ba9c3b6058f2f406d8a1d23c855046cf355c91d87d446fcc1460 rw,relatime - aufs none rw,si=9b4a76445abc939c
119 15 0:3452 / /var/lib/docker/aufs/mnt/42e109ab7914ae997a11ccd860fd18e4d488c50c044c3240423ce15774b8b62e rw,relatime - aufs none rw,si=9b4a76445abca39c
120 15 0:3453 / /var/lib/docker/aufs/mnt/365d832af0402d052b389c1e9c0d353b48487533d20cd4351df8e24ec4e4f9d8 rw,relatime - aufs none rw,si=9b4a7644066aa39c
121 15 0:3454 / /var/lib/docker/aufs/mnt/d3fa8a24d695b6cda9b64f96188f701963d28bef0473343f8b212df1a2cf1d2b rw,relatime - aufs none rw,si=9b4a7644066af39c
122 15 0:3455 / /var/lib/docker/aufs/mnt/37d4f491919abc49a15d0c7a7cc8383f087573525d7d288accd14f0b4af9eae0 rw,relatime - aufs none rw,si=9b4a7644066ad39c
123 15 0:3456 / /var/lib/docker/aufs/mnt/93902707fe12cbdd0068ce73f2baad4b3a299189b1b19cb5f8a2025e106ae3f5 rw,relatime - aufs none rw,si=9b4a76444445f39c
126 15 0:3459 / /var/lib/docker/aufs/mnt/3b49291670a625b9bbb329ffba99bf7fa7abff80cefef040f8b89e2b3aad4f9f rw,relatime - aufs none rw,si=9b4a7640798a339c
127 15 0:3460 / /var/lib/docker/aufs/mnt/8d9c7b943cc8f854f4d0d4ec19f7c16c13b0cc4f67a41472a072648610cecb59 rw,relatime - aufs none rw,si=9b4a76427383039c
128 15 0:3461 / /var/lib/docker/aufs/mnt/3b6c90036526c376307df71d49c9f5fce334c01b926faa6a78186842de74beac rw,relatime - aufs none rw,si=9b4a7644badd439c
130 15 0:3463 / /var/lib/docker/aufs/mnt/7b24158eeddfb5d31b7e932e406ea4899fd728344335ff8e0765e89ddeb351dd rw,relatime - aufs none rw,si=9b4a7644badd539c
131 15 0:3464 / /var/lib/docker/aufs/mnt/3ead6dd5773765c74850cf6c769f21fe65c29d622ffa712664f9f5b80364ce27 rw,relatime - aufs none rw,si=9b4a7642f469939c
132 15 0:3465 / /var/lib/docker/aufs/mnt/3f825573b29547744a37b65597a9d6d15a8350be4429b7038d126a4c9a8e178f rw,relatime - aufs none rw,si=9b4a7642f469c39c
133 15 0:3466 / /var/lib/docker/aufs/mnt/f67aaaeb3681e5dcb99a41f847087370bd1c206680cb8c7b6a9819fd6c97a331 rw,relatime - aufs none rw,si=9b4a7647cc25939c
134 15 0:3467 / /var/lib/docker/aufs/mnt/41afe6cfb3c1fc2280b869db07699da88552786e28793f0bc048a265c01bd942 rw,relatime - aufs none rw,si=9b4a7647cc25c39c
135 15 0:3468 / /var/lib/docker/aufs/mnt/b8092ea59da34a40b120e8718c3ae9fa8436996edc4fc50e4b99c72dfd81e1af rw,relatime - aufs none rw,si=9b4a76445abc439c
136 15 0:3469 / /var/lib/docker/aufs/mnt/42c69d2cc179e2684458bb8596a9da6dad182c08eae9b74d5f0e615b399f75a5 rw,relatime - aufs none rw,si=9b4a76455ddbe39c
137 15 0:3470 / /var/lib/docker/aufs/mnt/ea0871954acd2d62a211ac60e05969622044d4c74597870c4f818fbb0c56b09b rw,relatime - aufs none rw,si=9b4a76455ddbf39c
138 15 0:3471 / /var/lib/docker/aufs/mnt/4307906b275ab3fc971786b3841ae3217ac85b6756ddeb7ad4ba09cd044c2597 rw,relatime - aufs none rw,si=9b4a76455ddb839c
139 15 0:3472 / /var/lib/docker/aufs/mnt/4390b872928c53500a5035634f3421622ed6299dc1472b631fc45de9f56dc180 rw,relatime - aufs none rw,si=9b4a76402f2fd39c
140 15 0:3473 / /var/lib/docker/aufs/mnt/6bb41e78863b85e4aa7da89455314855c8c3bda64e52a583bab15dc1fa2e80c2 rw,relatime - aufs none rw,si=9b4a76402f2fa39c
141 15 0:3474 / /var/lib/docker/aufs/mnt/4444f583c2a79c66608f4673a32c9c812154f027045fbd558c2d69920c53f835 rw,relatime - aufs none rw,si=9b4a764479dbd39c
142 15 0:3475 / /var/lib/docker/aufs/mnt/6f11883af4a05ea362e0c54df89058da4859f977efd07b6f539e1f55c1d2a668 rw,relatime - aufs none rw,si=9b4a76402f30b39c
143 15 0:3476 / /var/lib/docker/aufs/mnt/453490dd32e7c2e9ef906f995d8fb3c2753923d1a5e0ba3fd3296e2e4dc238e7 rw,relatime - aufs none rw,si=9b4a76402f30c39c
144 15 0:3477 / /var/lib/docker/aufs/mnt/45e5945735ee102b5e891c91650c57ec4b52bb53017d68f02d50ea8a6e230610 rw,relatime - aufs none rw,si=9b4a76423260739c
147 15 0:3480 / /var/lib/docker/aufs/mnt/4727a64a5553a1125f315b96bed10d3073d6988225a292cce732617c925b56ab rw,relatime - aufs none rw,si=9b4a76443030339c
150 15 0:3483 / /var/lib/docker/aufs/mnt/4e348b5187b9a567059306afc72d42e0ec5c893b0d4abd547526d5f9b6fb4590 rw,relatime - aufs none rw,si=9b4a7644f5d8c39c
151 15 0:3484 / /var/lib/docker/aufs/mnt/4efc616bfbc3f906718b052da22e4335f8e9f91ee9b15866ed3a8029645189ef rw,relatime - aufs none rw,si=9b4a7644f5d8939c
152 15 0:3485 / /var/lib/docker/aufs/mnt/83e730ae9754d5adb853b64735472d98dfa17136b8812ac9cfcd1eba7f4e7d2d rw,relatime - aufs none rw,si=9b4a76469aa7139c
153 15 0:3486 / /var/lib/docker/aufs/mnt/4fc5ba8a5b333be2b7eefacccb626772eeec0ae8a6975112b56c9fb36c0d342f rw,relatime - aufs none rw,si=9b4a7640128dc39c
154 15 0:3487 / /var/lib/docker/aufs/mnt/50200d5edff5dfe8d1ef3c78b0bbd709793ac6e936aa16d74ff66f7ea577b6f9 rw,relatime - aufs none rw,si=9b4a7640128da39c
155 15 0:3488 / /var/lib/docker/aufs/mnt/51e5e51604361448f0b9777f38329f414bc5ba9cf238f26d465ff479bd574b61 rw,relatime - aufs none rw,si=9b4a76444f68939c
156 15 0:3489 / /var/lib/docker/aufs/mnt/52a142149aa98bba83df8766bbb1c629a97b9799944ead90dd206c4bdf0b8385 rw,relatime - aufs none rw,si=9b4a76444f68b39c
157 15 0:3490 / /var/lib/docker/aufs/mnt/52dd21a94a00f58a1ed489312fcfffb91578089c76c5650364476f1d5de031bc rw,relatime - aufs none rw,si=9b4a76444f68f39c
158 15 0:3491 / /var/lib/docker/aufs/mnt/ee562415ddaad353ed22c88d0ca768a0c74bfba6333b6e25c46849ee22d990da rw,relatime - aufs none rw,si=9b4a7640128d839c
159 15 0:3492 / /var/lib/docker/aufs/mnt/db47a9e87173f7554f550c8a01891de79cf12acdd32e01f95c1a527a08bdfb2c rw,relatime - aufs none rw,si=9b4a764405a1d39c
160 15 0:3493 / /var/lib/docker/aufs/mnt/55e827bf6d44d930ec0b827c98356eb8b68c3301e2d60d1429aa72e05b4c17df rw,relatime - aufs none rw,si=9b4a764405a1a39c
162 15 0:3495 / /var/lib/docker/aufs/mnt/578dc4e0a87fc37ec081ca098430499a59639c09f6f12a8f48de29828a091aa6 rw,relatime - aufs none rw,si=9b4a76406d7d439c
163 15 0:3496 / /var/lib/docker/aufs/mnt/728cc1cb04fa4bc6f7bf7a90980beda6d8fc0beb71630874c0747b994efb0798 rw,relatime - aufs none rw,si=9b4a76444f20e39c
164 15 0:3497 / /var/lib/docker/aufs/mnt/5850cc4bd9b55aea46c7ad598f1785117607974084ea643580f58ce3222e683a rw,relatime - aufs none rw,si=9b4a7644a824239c
165 15 0:3498 / /var/lib/docker/aufs/mnt/89443b3f766d5a37bc8b84e29da8b84e6a3ea8486d3cf154e2aae1816516e4a8 rw,relatime - aufs none rw,si=9b4a7644a824139c
166 15 0:3499 / /var/lib/docker/aufs/mnt/f5ae8fd5a41a337907d16515bc3162525154b59c32314c695ecd092c3b47943d rw,relatime - aufs none rw,si=9b4a7644a824439c
167 15 0:3500 / /var/lib/docker/aufs/mnt/5a430854f2a03a9e5f7cbc9f3fb46a8ebca526a5b3f435236d8295e5998798f5 rw,relatime - aufs none rw,si=9b4a7647fc82439c
168 15 0:3501 / /var/lib/docker/aufs/mnt/eda16901ae4cead35070c39845cbf1e10bd6b8cb0ffa7879ae2d8a186e460f91 rw,relatime - aufs none rw,si=9b4a76441e0df39c
169 15 0:3502 / /var/lib/docker/aufs/mnt/5a593721430c2a51b119ff86a7e06ea2b37e3b4131f8f1344d402b61b0c8d868 rw,relatime - aufs none rw,si=9b4a764248bad39c
170 15 0:3503 / /var/lib/docker/aufs/mnt/d662ad0a30fbfa902e0962108685b9330597e1ee2abb16dc9462eb5a67fdd23f rw,relatime - aufs none rw,si=9b4a764248bae39c
171 15 0:3504 / /var/lib/docker/aufs/mnt/5bc9de5c79812843fb36eee96bef1ddba812407861f572e33242f4ee10da2c15 rw,relatime - aufs none rw,si=9b4a764248ba839c
172 15 0:3505 / /var/lib/docker/aufs/mnt/5e763de8e9b0f7d58d2e12a341e029ab4efb3b99788b175090d8209e971156c1 rw,relatime - aufs none rw,si=9b4a764248baa39c
173 15 0:3506 / /var/lib/docker/aufs/mnt/b4431dc2739936f1df6387e337f5a0c99cf051900c896bd7fd46a870ce61c873 rw,relatime - aufs none rw,si=9b4a76401263539c
174 15 0:3507 / /var/lib/docker/aufs/mnt/5f37830e5a02561ab8c67ea3113137ba69f67a60e41c05cb0e7a0edaa1925b24 rw,relatime - aufs none rw,si=9b4a76401263639c
184 15 0:3508 / /var/lib/docker/aufs/mnt/62ea10b957e6533538a4633a1e1d678502f50ddcdd354b2ca275c54dd7a7793a rw,relatime - aufs none rw,si=9b4a76401263039c
187 15 0:3509 / /var/lib/docker/aufs/mnt/d56ee9d44195fe390e042fda75ec15af5132adb6d5c69468fa8792f4e54a6953 rw,relatime - aufs none rw,si=9b4a76401263239c
188 15 0:3510 / /var/lib/docker/aufs/mnt/6a300930673174549c2b62f36c933f0332a20735978c007c805a301f897146c5 rw,relatime - aufs none rw,si=9b4a76455d4c539c
189 15 0:3511 / /var/lib/docker/aufs/mnt/64496c45c84d348c24d410015456d101601c30cab4d1998c395591caf7e57a70 rw,relatime - aufs none rw,si=9b4a76455d4c639c
190 15 0:3512 / /var/lib/docker/aufs/mnt/65a6a645883fe97a7422cd5e71ebe0bc17c8e6302a5361edf52e89747387e908 rw,relatime - aufs none rw,si=9b4a76455d4c039c
191 15 0:3513 / /var/lib/docker/aufs/mnt/672be40695f7b6e13b0a3ed9fc996c73727dede3481f58155950fcfad57ed616 rw,relatime - aufs none rw,si=9b4a76455d4c239c
192 15 0:3514 / /var/lib/docker/aufs/mnt/d42438acb2bfb2169e1c0d8e917fc824f7c85d336dadb0b0af36dfe0f001b3ba rw,relatime - aufs none rw,si=9b4a7642bfded39c
193 15 0:3515 / /var/lib/docker/aufs/mnt/b48a54abf26d01cb2ddd908b1ed6034d17397c1341bf0eb2b251a3e5b79be854 rw,relatime - aufs none rw,si=9b4a7642bfdee39c
194 15 0:3516 / /var/lib/docker/aufs/mnt/76f27134491f052bfb87f59092126e53ef875d6851990e59195a9da16a9412f8 rw,relatime - aufs none rw,si=9b4a7642bfde839c
195 15 0:3517 / /var/lib/docker/aufs/mnt/6bd626a5462b4f8a8e1cc7d10351326dca97a59b2758e5ea549a4f6350ce8a90 rw,relatime - aufs none rw,si=9b4a7642bfdea39c
196 15 0:3518 / /var/lib/docker/aufs/mnt/f1fe3549dbd6f5ca615e9139d9b53f0c83a3b825565df37628eacc13e70cbd6d rw,relatime - aufs none rw,si=9b4a7642bfdf539c
197 15 0:3519 / /var/lib/docker/aufs/mnt/6d0458c8426a9e93d58d0625737e6122e725c9408488ed9e3e649a9984e15c34 rw,relatime - aufs none rw,si=9b4a7642bfdf639c
198 15 0:3520 / /var/lib/docker/aufs/mnt/6e4c97db83aa82145c9cf2bafc20d500c0b5389643b689e3ae84188c270a48c5 rw,relatime - aufs none rw,si=9b4a7642bfdf039c
199 15 0:3521 / /var/lib/docker/aufs/mnt/eb94d6498f2c5969eaa9fa11ac2934f1ab90ef88e2d002258dca08e5ba74ea27 rw,relatime - aufs none rw,si=9b4a7642bfdf239c
200 15 0:3522 / /var/lib/docker/aufs/mnt/fe3f88f0c511608a2eec5f13a98703aa16e55dbf930309723d8a37101f539fe1 rw,relatime - aufs none rw,si=9b4a7642bfc3539c
201 15 0:3523 / /var/lib/docker/aufs/mnt/6f40c229fb9cad85fabf4b64a2640a5403ec03fe5ac1a57d0609fb8b606b9c83 rw,relatime - aufs none rw,si=9b4a7642bfc3639c
202 15 0:3524 / /var/lib/docker/aufs/mnt/7513e9131f7a8acf58ff15248237feb767c78732ca46e159f4d791e6ef031dbc rw,relatime - aufs none rw,si=9b4a7642bfc3039c
203 15 0:3525 / /var/lib/docker/aufs/mnt/79f48b00aa713cdf809c6bb7c7cb911b66e9a8076c81d6c9d2504139984ea2da rw,relatime - aufs none rw,si=9b4a7642bfc3239c
204 15 0:3526 / /var/lib/docker/aufs/mnt/c3680418350d11358f0a96c676bc5aa74fa00a7c89e629ef5909d3557b060300 rw,relatime - aufs none rw,si=9b4a7642f47cd39c
205 15 0:3527 / /var/lib/docker/aufs/mnt/7a1744dd350d7fcc0cccb6f1757ca4cbe5453f203a5888b0f1014d96ad5a5ef9 rw,relatime - aufs none rw,si=9b4a7642f47ce39c
206 15 0:3528 / /var/lib/docker/aufs/mnt/7fa99662db046be9f03c33c35251afda9ccdc0085636bbba1d90592cec3ff68d rw,relatime - aufs none rw,si=9b4a7642f47c839c
207 15 0:3529 / /var/lib/docker/aufs/mnt/f815021ef20da9c9b056bd1d52d8aaf6e2c0c19f11122fc793eb2b04eb995e35 rw,relatime - aufs none rw,si=9b4a7642f47ca39c
208 15 0:3530 / /var/lib/docker/aufs/mnt/801086ae3110192d601dfcebdba2db92e86ce6b6a9dba6678ea04488e4513669 rw,relatime - aufs none rw,si=9b4a7642dc6dd39c
209 15 0:3531 / /var/lib/docker/aufs/mnt/822ba7db69f21daddda87c01cfbfbf73013fc03a879daf96d16cdde6f9b1fbd6 rw,relatime - aufs none rw,si=9b4a7642dc6de39c
210 15 0:3532 / /var/lib/docker/aufs/mnt/834227c1a950fef8cae3827489129d0dd220541e60c6b731caaa765bf2e6a199 rw,relatime - aufs none rw,si=9b4a7642dc6d839c
211 15 0:3533 / /var/lib/docker/aufs/mnt/83dccbc385299bd1c7cf19326e791b33a544eea7b4cdfb6db70ea94eed4389fb rw,relatime - aufs none rw,si=9b4a7642dc6da39c
212 15 0:3534 / /var/lib/docker/aufs/mnt/f1b8e6f0e7c8928b5dcdab944db89306ebcae3e0b32f9ff40d2daa8329f21600 rw,relatime - aufs none rw,si=9b4a7645a126039c
213 15 0:3535 / /var/lib/docker/aufs/mnt/970efb262c7a020c2404cbcc5b3259efba0d110a786079faeef05bc2952abf3a rw,relatime - aufs none rw,si=9b4a7644c8ed139c
214 15 0:3536 / /var/lib/docker/aufs/mnt/84b6d73af7450f3117a77e15a5ca1255871fea6182cd8e8a7be6bc744be18c2c rw,relatime - aufs none rw,si=9b4a76406559139c
215 15 0:3537 / /var/lib/docker/aufs/mnt/88be2716e026bc681b5e63fe7942068773efbd0b6e901ca7ba441412006a96b6 rw,relatime - aufs none rw,si=9b4a76406559339c
216 15 0:3538 / /var/lib/docker/aufs/mnt/c81939aa166ce50cd8bca5cfbbcc420a78e0318dd5cd7c755209b9166a00a752 rw,relatime - aufs none rw,si=9b4a76406559239c
217 15 0:3539 / /var/lib/docker/aufs/mnt/e0f241645d64b7dc5ff6a8414087cca226be08fb54ce987d1d1f6350c57083aa rw,relatime - aufs none rw,si=9b4a7647cfc0f39c
218 15 0:3540 / /var/lib/docker/aufs/mnt/e10e2bf75234ed51d8a6a4bb39e465404fecbe318e54400d3879cdb2b0679c78 rw,relatime - aufs none rw,si=9b4a7647cfc0939c
219 15 0:3541 / /var/lib/docker/aufs/mnt/8f71d74c8cfc3228b82564aa9f09b2e576cff0083ddfb6aa5cb350346063f080 rw,relatime - aufs none rw,si=9b4a7647cfc0a39c
220 15 0:3542 / /var/lib/docker/aufs/mnt/9159f1eba2aef7f5205cc18d015cda7f5933cd29bba3b1b8aed5ccb5824c69ee rw,relatime - aufs none rw,si=9b4a76468cedd39c
221 15 0:3543 / /var/lib/docker/aufs/mnt/932cad71e652e048e500d9fbb5b8ea4fc9a269d42a3134ce527ceef42a2be56b rw,relatime - aufs none rw,si=9b4a76468cede39c
222 15 0:3544 / /var/lib/docker/aufs/mnt/bf1e1b5f529e8943cc0144ee86dbaaa37885c1ddffcef29537e0078ee7dd316a rw,relatime - aufs none rw,si=9b4a76468ced839c
223 15 0:3545 / /var/lib/docker/aufs/mnt/949d93ecf3322e09f858ce81d5f4b434068ec44ff84c375de03104f7b45ee955 rw,relatime - aufs none rw,si=9b4a76468ceda39c
224 15 0:3546 / /var/lib/docker/aufs/mnt/d65c6087f92dc2a3841b5251d2fe9ca07d4c6e5b021597692479740816e4e2a1 rw,relatime - aufs none rw,si=9b4a7645a126239c
225 15 0:3547 / /var/lib/docker/aufs/mnt/98a0153119d0651c193d053d254f6e16a68345a141baa80c87ae487e9d33f290 rw,relatime - aufs none rw,si=9b4a7640787cf39c
226 15 0:3548 / /var/lib/docker/aufs/mnt/99daf7fe5847c017392f6e59aa9706b3dfdd9e6d1ba11dae0f7fffde0a60b5e5 rw,relatime - aufs none rw,si=9b4a7640787c839c
227 15 0:3549 / /var/lib/docker/aufs/mnt/9ad1f2fe8a5599d4e10c5a6effa7f03d932d4e92ee13149031a372087a359079 rw,relatime - aufs none rw,si=9b4a7640787ca39c
228 15 0:3550 / /var/lib/docker/aufs/mnt/c26d64494da782ddac26f8370d86ac93e7c1666d88a7b99110fc86b35ea6a85d rw,relatime - aufs none rw,si=9b4a7642fc6b539c
229 15 0:3551 / /var/lib/docker/aufs/mnt/a49e4a8275133c230ec640997f35f172312eb0ea5bd2bbe10abf34aae98f30eb rw,relatime - aufs none rw,si=9b4a7642fc6b639c
230 15 0:3552 / /var/lib/docker/aufs/mnt/b5e2740c867ed843025f49d84e8d769de9e8e6039b3c8cb0735b5bf358994bc7 rw,relatime - aufs none rw,si=9b4a7642fc6b039c
231 15 0:3553 / /var/lib/docker/aufs/mnt/a826fdcf3a7039b30570054579b65763db605a314275d7aef31b872c13311b4b rw,relatime - aufs none rw,si=9b4a7642fc6b239c
232 15 0:3554 / /var/lib/docker/aufs/mnt/addf3025babf5e43b5a3f4a0da7ad863dda3c01fb8365c58fd8d28bb61dc11bc rw,relatime - aufs none rw,si=9b4a76407871d39c
233 15 0:3555 / /var/lib/docker/aufs/mnt/c5b6c6813ab3e5ebdc6d22cb2a3d3106a62095f2c298be52b07a3b0fa20ff690 rw,relatime - aufs none rw,si=9b4a76407871e39c
234 15 0:3556 / /var/lib/docker/aufs/mnt/af0609eaaf64e2392060cb46f5a9f3d681a219bb4c651d4f015bf573fbe6c4cf rw,relatime - aufs none rw,si=9b4a76407871839c
235 15 0:3557 / /var/lib/docker/aufs/mnt/e7f20e3c37ecad39cd90a97cd3549466d0d106ce4f0a930b8495442634fa4a1f rw,relatime - aufs none rw,si=9b4a76407871a39c
237 15 0:3559 / /var/lib/docker/aufs/mnt/b57a53d440ffd0c1295804fa68cdde35d2fed5409484627e71b9c37e4249fd5c rw,relatime - aufs none rw,si=9b4a76444445a39c
238 15 0:3560 / /var/lib/docker/aufs/mnt/b5e7d7b8f35e47efbba3d80c5d722f5e7bd43e54c824e54b4a4b351714d36d42 rw,relatime - aufs none rw,si=9b4a7647932d439c
239 15 0:3561 / /var/lib/docker/aufs/mnt/f1b136def157e9465640658f277f3347de593c6ae76412a2e79f7002f091cae2 rw,relatime - aufs none rw,si=9b4a76445abcd39c
240 15 0:3562 / /var/lib/docker/aufs/mnt/b750fe79269d2ec9a3c593ef05b4332b1d1a02a62b4accb2c21d589ff2f5f2dc rw,relatime - aufs none rw,si=9b4a7644403b339c
241 15 0:3563 / /var/lib/docker/aufs/mnt/b89b140cdbc95063761864e0a23346207fa27ee4c5c63a1ae85c9069a9d9cf1d rw,relatime - aufs none rw,si=9b4a7644aa19739c
242 15 0:3564 / /var/lib/docker/aufs/mnt/bc6a69ed51c07f5228f6b4f161c892e6a949c0e7e86a9c3432049d4c0e5cd298 rw,relatime - aufs none rw,si=9b4a7644aa19139c
243 15 0:3565 / /var/lib/docker/aufs/mnt/be4e2ba3f136933e239f7cf3d136f484fb9004f1fbdfee24a62a2c7b0ab30670 rw,relatime - aufs none rw,si=9b4a7644aa19339c
244 15 0:3566 / /var/lib/docker/aufs/mnt/e04ca1a4a5171e30d20f0c92f90a50b8b6f8600af5459c4b4fb25e42e864dfe1 rw,relatime - aufs none rw,si=9b4a7647932d139c
245 15 0:3567 / /var/lib/docker/aufs/mnt/be61576b31db893129aaffcd3dcb5ce35e49c4b71b30c392a78609a45c7323d8 rw,relatime - aufs none rw,si=9b4a7642d85f739c
246 15 0:3568 / /var/lib/docker/aufs/mnt/dda42c191e56becf672327658ab84fcb563322db3764b91c2fefe4aaef04c624 rw,relatime - aufs none rw,si=9b4a7642d85f139c
247 15 0:3569 / /var/lib/docker/aufs/mnt/c0a7995053330f3d88969247a2e72b07e2dd692133f5668a4a35ea3905561072 rw,relatime - aufs none rw,si=9b4a7642d85f339c
249 15 0:3571 / /var/lib/docker/aufs/mnt/c3594b2e5f08c59ff5ed338a1ba1eceeeb1f7fc5d180068338110c00b1eb8502 rw,relatime - aufs none rw,si=9b4a7642738c739c
250 15 0:3572 / /var/lib/docker/aufs/mnt/c58dce03a0ab0a7588393880379dc3bce9f96ec08ed3f99cf1555260ff0031e8 rw,relatime - aufs none rw,si=9b4a7642738c139c
251 15 0:3573 / /var/lib/docker/aufs/mnt/c73e9f1d109c9d14cb36e1c7489df85649be3911116d76c2fd3648ec8fd94e23 rw,relatime - aufs none rw,si=9b4a7642738c339c
252 15 0:3574 / /var/lib/docker/aufs/mnt/c9eef28c344877cd68aa09e543c0710ab2b305a0ff96dbb859bfa7808c3e8d01 rw,relatime - aufs none rw,si=9b4a7642d85f439c
253 15 0:3575 / /var/lib/docker/aufs/mnt/feb67148f548d70cb7484f2aaad2a86051cd6867a561741a2f13b552457d666e rw,relatime - aufs none rw,si=9b4a76468c55739c
254 15 0:3576 / /var/lib/docker/aufs/mnt/cdf1f96c36d35a96041a896bf398ec0f7dc3b0fb0643612a0f4b6ff96e04e1bb rw,relatime - aufs none rw,si=9b4a76468c55139c
255 15 0:3577 / /var/lib/docker/aufs/mnt/ec6e505872353268451ac4bc034c1df00f3bae4a3ea2261c6e48f7bd5417c1b3 rw,relatime - aufs none rw,si=9b4a76468c55339c
256 15 0:3578 / /var/lib/docker/aufs/mnt/d6dc8aca64efd90e0bc10274001882d0efb310d42ccbf5712b99b169053b8b1a rw,relatime - aufs none rw,si=9b4a7642738c439c
257 15 0:3579 / /var/lib/docker/aufs/mnt/d712594e2ff6eaeb895bfd150d694bd1305fb927e7a186b2dab7df2ea95f8f81 rw,relatime - aufs none rw,si=9b4a76401268f39c
259 15 0:3581 / /var/lib/docker/aufs/mnt/dbfa1174cd78cde2d7410eae442af0b416c4a0e6f87ed4ff1e9f169a0029abc0 rw,relatime - aufs none rw,si=9b4a76401268b39c
260 15 0:3582 / /var/lib/docker/aufs/mnt/e883f5a82316d7856fbe93ee8c0af5a920b7079619dd95c4ffd88bbd309d28dd rw,relatime - aufs none rw,si=9b4a76468c55439c
261 15 0:3583 / /var/lib/docker/aufs/mnt/fdec3eff581c4fc2b09f87befa2fa021f3f2d373bea636a87f1fb5b367d6347a rw,relatime - aufs none rw,si=9b4a7644aa1af39c
262 15 0:3584 / /var/lib/docker/aufs/mnt/ef764e26712184653067ecf7afea18a80854c41331ca0f0ef03e1bacf90a6ffc rw,relatime - aufs none rw,si=9b4a7644aa1a939c
263 15 0:3585 / /var/lib/docker/aufs/mnt/f3176b40c41fce8ce6942936359a2001a6f1b5c1bb40ee224186db0789ec2f76 rw,relatime - aufs none rw,si=9b4a7644aa1ab39c
264 15 0:3586 / /var/lib/docker/aufs/mnt/f5daf06785d3565c6dd18ea7d953d9a8b9606107781e63270fe0514508736e6a rw,relatime - aufs none rw,si=9b4a76401268c39c
58 15 0:3587 / /var/lib/docker/aufs/mnt/cde8c40f6524b7361af4f5ad05bb857dc9ee247c20852ba666195c0739e3a2b8-init rw,relatime - aufs none rw,si=9b4a76444445839c
67 15 0:3588 / /var/lib/docker/aufs/mnt/cde8c40f6524b7361af4f5ad05bb857dc9ee247c20852ba666195c0739e3a2b8 rw,relatime - aufs none rw,si=9b4a7644badd339c
265 15 0:3610 / /var/lib/docker/aufs/mnt/e812472cd2c8c4748d1ef71fac4e77e50d661b9349abe66ce3e23511ed44f414 rw,relatime - aufs none rw,si=9b4a76427937d39c
270 15 0:3615 / /var/lib/docker/aufs/mnt/997636e7c5c9d0d1376a217e295c14c205350b62bc12052804fb5f90abe6f183 rw,relatime - aufs none rw,si=9b4a76406540739c
273 15 0:3618 / /var/lib/docker/aufs/mnt/d5794d080417b6e52e69227c3873e0e4c1ff0d5a845ebe3860ec2f89a47a2a1e rw,relatime - aufs none rw,si=9b4a76454814039c
278 15 0:3623 / /var/lib/docker/aufs/mnt/586bdd48baced671bb19bc4d294ec325f26c55545ae267db426424f157d59c48 rw,relatime - aufs none rw,si=9b4a7644b439f39c
281 15 0:3626 / /var/lib/docker/aufs/mnt/69739d022f89f8586908bbd5edbbdd95ea5256356f177f9ffcc6ef9c0ea752d2 rw,relatime - aufs none rw,si=9b4a7644a0f1b39c
286 15 0:3631 / /var/lib/docker/aufs/mnt/ff28c27d5f894363993622de26d5dd352dba072f219e4691d6498c19bbbc15a9 rw,relatime - aufs none rw,si=9b4a7642265b339c
289 15 0:3634 / /var/lib/docker/aufs/mnt/aa128fe0e64fdede333aa48fd9de39530c91a9244a0f0649a3c411c61e372daa rw,relatime - aufs none rw,si=9b4a764012ada39c
99 15 8:33 / /media/REMOVE\040ME rw,nosuid,nodev,relatime - fuseblk /dev/sdc1 rw,user_id=0,group_id=0,allow_other,blksize=4096`
)
func TestParseFedoraMountinfo(t *testing.T) {
r := bytes.NewBuffer([]byte(fedoraMountinfo))
_, err := parseInfoFile(r)
if err != nil {
t.Fatal(err)
}
}
func TestParseUbuntuMountinfo(t *testing.T) {
r := bytes.NewBuffer([]byte(ubuntuMountInfo))
_, err := parseInfoFile(r)
if err != nil {
t.Fatal(err)
}
}
func TestParseGentooMountinfo(t *testing.T) {
r := bytes.NewBuffer([]byte(gentooMountinfo))
_, err := parseInfoFile(r)
if err != nil {
t.Fatal(err)
}
}
func TestParseFedoraMountinfoFields(t *testing.T) {
r := bytes.NewBuffer([]byte(fedoraMountinfo))
infos, err := parseInfoFile(r)
if err != nil {
t.Fatal(err)
}
expectedLength := 58
if len(infos) != expectedLength {
t.Fatalf("Expected %d entries, got %d", expectedLength, len(infos))
}
mi := Info{
ID: 15,
Parent: 35,
Major: 0,
Minor: 3,
Root: "/",
Mountpoint: "/proc",
Opts: "rw,nosuid,nodev,noexec,relatime",
Optional: "shared:5",
Fstype: "proc",
Source: "proc",
VfsOpts: "rw",
}
if *infos[0] != mi {
t.Fatalf("expected %#v, got %#v", mi, infos[0])
}
}

View file

@ -0,0 +1,331 @@
// +build linux
package mount
import (
"os"
"path"
"syscall"
"testing"
)
// nothing is propagated in or out
func TestSubtreePrivate(t *testing.T) {
tmp := path.Join(os.TempDir(), "mount-tests")
if err := os.MkdirAll(tmp, 0777); err != nil {
t.Fatal(err)
}
defer os.RemoveAll(tmp)
var (
sourceDir = path.Join(tmp, "source")
targetDir = path.Join(tmp, "target")
outside1Dir = path.Join(tmp, "outside1")
outside2Dir = path.Join(tmp, "outside2")
outside1Path = path.Join(outside1Dir, "file.txt")
outside2Path = path.Join(outside2Dir, "file.txt")
outside1CheckPath = path.Join(targetDir, "a", "file.txt")
outside2CheckPath = path.Join(sourceDir, "b", "file.txt")
)
if err := os.MkdirAll(path.Join(sourceDir, "a"), 0777); err != nil {
t.Fatal(err)
}
if err := os.MkdirAll(path.Join(sourceDir, "b"), 0777); err != nil {
t.Fatal(err)
}
if err := os.Mkdir(targetDir, 0777); err != nil {
t.Fatal(err)
}
if err := os.Mkdir(outside1Dir, 0777); err != nil {
t.Fatal(err)
}
if err := os.Mkdir(outside2Dir, 0777); err != nil {
t.Fatal(err)
}
if err := createFile(outside1Path); err != nil {
t.Fatal(err)
}
if err := createFile(outside2Path); err != nil {
t.Fatal(err)
}
// mount the shared directory to a target
if err := Mount(sourceDir, targetDir, "none", "bind,rw"); err != nil {
t.Fatal(err)
}
defer func() {
if err := Unmount(targetDir); err != nil {
t.Fatal(err)
}
}()
// next, make the target private
if err := MakePrivate(targetDir); err != nil {
t.Fatal(err)
}
defer func() {
if err := Unmount(targetDir); err != nil {
t.Fatal(err)
}
}()
// mount in an outside path to a mounted path inside the _source_
if err := Mount(outside1Dir, path.Join(sourceDir, "a"), "none", "bind,rw"); err != nil {
t.Fatal(err)
}
defer func() {
if err := Unmount(path.Join(sourceDir, "a")); err != nil {
t.Fatal(err)
}
}()
// check that this file _does_not_ show in the _target_
if _, err := os.Stat(outside1CheckPath); err != nil && !os.IsNotExist(err) {
t.Fatal(err)
} else if err == nil {
t.Fatalf("%q should not be visible, but is", outside1CheckPath)
}
// next mount outside2Dir into the _target_
if err := Mount(outside2Dir, path.Join(targetDir, "b"), "none", "bind,rw"); err != nil {
t.Fatal(err)
}
defer func() {
if err := Unmount(path.Join(targetDir, "b")); err != nil {
t.Fatal(err)
}
}()
// check that this file _does_not_ show in the _source_
if _, err := os.Stat(outside2CheckPath); err != nil && !os.IsNotExist(err) {
t.Fatal(err)
} else if err == nil {
t.Fatalf("%q should not be visible, but is", outside2CheckPath)
}
}
// Testing that when a target is a shared mount,
// then child mounts propagate to the source
func TestSubtreeShared(t *testing.T) {
tmp := path.Join(os.TempDir(), "mount-tests")
if err := os.MkdirAll(tmp, 0777); err != nil {
t.Fatal(err)
}
defer os.RemoveAll(tmp)
var (
sourceDir = path.Join(tmp, "source")
targetDir = path.Join(tmp, "target")
outsideDir = path.Join(tmp, "outside")
outsidePath = path.Join(outsideDir, "file.txt")
sourceCheckPath = path.Join(sourceDir, "a", "file.txt")
)
if err := os.MkdirAll(path.Join(sourceDir, "a"), 0777); err != nil {
t.Fatal(err)
}
if err := os.Mkdir(targetDir, 0777); err != nil {
t.Fatal(err)
}
if err := os.Mkdir(outsideDir, 0777); err != nil {
t.Fatal(err)
}
if err := createFile(outsidePath); err != nil {
t.Fatal(err)
}
// mount the source as shared
if err := MakeShared(sourceDir); err != nil {
t.Fatal(err)
}
defer func() {
if err := Unmount(sourceDir); err != nil {
t.Fatal(err)
}
}()
// mount the shared directory to a target
if err := Mount(sourceDir, targetDir, "none", "bind,rw"); err != nil {
t.Fatal(err)
}
defer func() {
if err := Unmount(targetDir); err != nil {
t.Fatal(err)
}
}()
// mount in an outside path to a mounted path inside the target
if err := Mount(outsideDir, path.Join(targetDir, "a"), "none", "bind,rw"); err != nil {
t.Fatal(err)
}
defer func() {
if err := Unmount(path.Join(targetDir, "a")); err != nil {
t.Fatal(err)
}
}()
// NOW, check that the file from the outside directory is available in the source directory
if _, err := os.Stat(sourceCheckPath); err != nil {
t.Fatal(err)
}
}
// testing that mounts to a shared source show up in the slave target,
// and that mounts into a slave target do _not_ show up in the shared source
func TestSubtreeSharedSlave(t *testing.T) {
tmp := path.Join(os.TempDir(), "mount-tests")
if err := os.MkdirAll(tmp, 0777); err != nil {
t.Fatal(err)
}
defer os.RemoveAll(tmp)
var (
sourceDir = path.Join(tmp, "source")
targetDir = path.Join(tmp, "target")
outside1Dir = path.Join(tmp, "outside1")
outside2Dir = path.Join(tmp, "outside2")
outside1Path = path.Join(outside1Dir, "file.txt")
outside2Path = path.Join(outside2Dir, "file.txt")
outside1CheckPath = path.Join(targetDir, "a", "file.txt")
outside2CheckPath = path.Join(sourceDir, "b", "file.txt")
)
if err := os.MkdirAll(path.Join(sourceDir, "a"), 0777); err != nil {
t.Fatal(err)
}
if err := os.MkdirAll(path.Join(sourceDir, "b"), 0777); err != nil {
t.Fatal(err)
}
if err := os.Mkdir(targetDir, 0777); err != nil {
t.Fatal(err)
}
if err := os.Mkdir(outside1Dir, 0777); err != nil {
t.Fatal(err)
}
if err := os.Mkdir(outside2Dir, 0777); err != nil {
t.Fatal(err)
}
if err := createFile(outside1Path); err != nil {
t.Fatal(err)
}
if err := createFile(outside2Path); err != nil {
t.Fatal(err)
}
// mount the source as shared
if err := MakeShared(sourceDir); err != nil {
t.Fatal(err)
}
defer func() {
if err := Unmount(sourceDir); err != nil {
t.Fatal(err)
}
}()
// mount the shared directory to a target
if err := Mount(sourceDir, targetDir, "none", "bind,rw"); err != nil {
t.Fatal(err)
}
defer func() {
if err := Unmount(targetDir); err != nil {
t.Fatal(err)
}
}()
// next, make the target slave
if err := MakeSlave(targetDir); err != nil {
t.Fatal(err)
}
defer func() {
if err := Unmount(targetDir); err != nil {
t.Fatal(err)
}
}()
// mount in an outside path to a mounted path inside the _source_
if err := Mount(outside1Dir, path.Join(sourceDir, "a"), "none", "bind,rw"); err != nil {
t.Fatal(err)
}
defer func() {
if err := Unmount(path.Join(sourceDir, "a")); err != nil {
t.Fatal(err)
}
}()
// check that this file _does_ show in the _target_
if _, err := os.Stat(outside1CheckPath); err != nil {
t.Fatal(err)
}
// next mount outside2Dir into the _target_
if err := Mount(outside2Dir, path.Join(targetDir, "b"), "none", "bind,rw"); err != nil {
t.Fatal(err)
}
defer func() {
if err := Unmount(path.Join(targetDir, "b")); err != nil {
t.Fatal(err)
}
}()
// check that this file _does_not_ show in the _source_
if _, err := os.Stat(outside2CheckPath); err != nil && !os.IsNotExist(err) {
t.Fatal(err)
} else if err == nil {
t.Fatalf("%q should not be visible, but is", outside2CheckPath)
}
}
func TestSubtreeUnbindable(t *testing.T) {
tmp := path.Join(os.TempDir(), "mount-tests")
if err := os.MkdirAll(tmp, 0777); err != nil {
t.Fatal(err)
}
defer os.RemoveAll(tmp)
var (
sourceDir = path.Join(tmp, "source")
targetDir = path.Join(tmp, "target")
)
if err := os.MkdirAll(sourceDir, 0777); err != nil {
t.Fatal(err)
}
if err := os.MkdirAll(targetDir, 0777); err != nil {
t.Fatal(err)
}
// next, make the source unbindable
if err := MakeUnbindable(sourceDir); err != nil {
t.Fatal(err)
}
defer func() {
if err := Unmount(sourceDir); err != nil {
t.Fatal(err)
}
}()
// then attempt to mount it to target. It should fail
if err := Mount(sourceDir, targetDir, "none", "bind,rw"); err != nil && err != syscall.EINVAL {
t.Fatal(err)
} else if err == nil {
t.Fatalf("%q should not have been bindable", sourceDir)
}
defer func() {
if err := Unmount(targetDir); err != nil {
t.Fatal(err)
}
}()
}
func createFile(path string) error {
f, err := os.Create(path)
if err != nil {
return err
}
f.WriteString("hello world!")
return f.Close()
}

View file

@ -0,0 +1,96 @@
// +build !windows
package kernel
import (
"fmt"
"testing"
)
func assertParseRelease(t *testing.T, release string, b *VersionInfo, result int) {
var (
a *VersionInfo
)
a, _ = ParseRelease(release)
if r := CompareKernelVersion(*a, *b); r != result {
t.Fatalf("Unexpected kernel version comparison result for (%v,%v). Found %d, expected %d", release, b, r, result)
}
if a.Flavor != b.Flavor {
t.Fatalf("Unexpected parsed kernel flavor. Found %s, expected %s", a.Flavor, b.Flavor)
}
}
// TestParseRelease tests the ParseRelease() function
func TestParseRelease(t *testing.T) {
assertParseRelease(t, "3.8.0", &VersionInfo{Kernel: 3, Major: 8, Minor: 0}, 0)
assertParseRelease(t, "3.4.54.longterm-1", &VersionInfo{Kernel: 3, Major: 4, Minor: 54, Flavor: ".longterm-1"}, 0)
assertParseRelease(t, "3.4.54.longterm-1", &VersionInfo{Kernel: 3, Major: 4, Minor: 54, Flavor: ".longterm-1"}, 0)
assertParseRelease(t, "3.8.0-19-generic", &VersionInfo{Kernel: 3, Major: 8, Minor: 0, Flavor: "-19-generic"}, 0)
assertParseRelease(t, "3.12.8tag", &VersionInfo{Kernel: 3, Major: 12, Minor: 8, Flavor: "tag"}, 0)
assertParseRelease(t, "3.12-1-amd64", &VersionInfo{Kernel: 3, Major: 12, Minor: 0, Flavor: "-1-amd64"}, 0)
assertParseRelease(t, "3.8.0", &VersionInfo{Kernel: 4, Major: 8, Minor: 0}, -1)
// Errors
invalids := []string{
"3",
"a",
"a.a",
"a.a.a-a",
}
for _, invalid := range invalids {
expectedMessage := fmt.Sprintf("Can't parse kernel version %v", invalid)
if _, err := ParseRelease(invalid); err == nil || err.Error() != expectedMessage {
}
}
}
func assertKernelVersion(t *testing.T, a, b VersionInfo, result int) {
if r := CompareKernelVersion(a, b); r != result {
t.Fatalf("Unexpected kernel version comparison result. Found %d, expected %d", r, result)
}
}
// TestCompareKernelVersion tests the CompareKernelVersion() function
func TestCompareKernelVersion(t *testing.T) {
assertKernelVersion(t,
VersionInfo{Kernel: 3, Major: 8, Minor: 0},
VersionInfo{Kernel: 3, Major: 8, Minor: 0},
0)
assertKernelVersion(t,
VersionInfo{Kernel: 2, Major: 6, Minor: 0},
VersionInfo{Kernel: 3, Major: 8, Minor: 0},
-1)
assertKernelVersion(t,
VersionInfo{Kernel: 3, Major: 8, Minor: 0},
VersionInfo{Kernel: 2, Major: 6, Minor: 0},
1)
assertKernelVersion(t,
VersionInfo{Kernel: 3, Major: 8, Minor: 0},
VersionInfo{Kernel: 3, Major: 8, Minor: 0},
0)
assertKernelVersion(t,
VersionInfo{Kernel: 3, Major: 8, Minor: 5},
VersionInfo{Kernel: 3, Major: 8, Minor: 0},
1)
assertKernelVersion(t,
VersionInfo{Kernel: 3, Major: 0, Minor: 20},
VersionInfo{Kernel: 3, Major: 8, Minor: 0},
-1)
assertKernelVersion(t,
VersionInfo{Kernel: 3, Major: 7, Minor: 20},
VersionInfo{Kernel: 3, Major: 8, Minor: 0},
-1)
assertKernelVersion(t,
VersionInfo{Kernel: 3, Major: 8, Minor: 20},
VersionInfo{Kernel: 3, Major: 7, Minor: 0},
1)
assertKernelVersion(t,
VersionInfo{Kernel: 3, Major: 8, Minor: 20},
VersionInfo{Kernel: 3, Major: 8, Minor: 0},
1)
assertKernelVersion(t,
VersionInfo{Kernel: 3, Major: 8, Minor: 0},
VersionInfo{Kernel: 3, Major: 8, Minor: 20},
-1)
}

View file

@ -0,0 +1,70 @@
package parsers
import (
"reflect"
"testing"
)
func TestParseKeyValueOpt(t *testing.T) {
invalids := map[string]string{
"": "Unable to parse key/value option: ",
"key": "Unable to parse key/value option: key",
}
for invalid, expectedError := range invalids {
if _, _, err := ParseKeyValueOpt(invalid); err == nil || err.Error() != expectedError {
t.Fatalf("Expected error %v for %v, got %v", expectedError, invalid, err)
}
}
valids := map[string][]string{
"key=value": {"key", "value"},
" key = value ": {"key", "value"},
"key=value1=value2": {"key", "value1=value2"},
" key = value1 = value2 ": {"key", "value1 = value2"},
}
for valid, expectedKeyValue := range valids {
key, value, err := ParseKeyValueOpt(valid)
if err != nil {
t.Fatal(err)
}
if key != expectedKeyValue[0] || value != expectedKeyValue[1] {
t.Fatalf("Expected {%v: %v} got {%v: %v}", expectedKeyValue[0], expectedKeyValue[1], key, value)
}
}
}
func TestParseUintList(t *testing.T) {
valids := map[string]map[int]bool{
"": {},
"7": {7: true},
"1-6": {1: true, 2: true, 3: true, 4: true, 5: true, 6: true},
"0-7": {0: true, 1: true, 2: true, 3: true, 4: true, 5: true, 6: true, 7: true},
"0,3-4,7,8-10": {0: true, 3: true, 4: true, 7: true, 8: true, 9: true, 10: true},
"0-0,0,1-4": {0: true, 1: true, 2: true, 3: true, 4: true},
"03,1-3": {1: true, 2: true, 3: true},
"3,2,1": {1: true, 2: true, 3: true},
"0-2,3,1": {0: true, 1: true, 2: true, 3: true},
}
for k, v := range valids {
out, err := ParseUintList(k)
if err != nil {
t.Fatalf("Expected not to fail, got %v", err)
}
if !reflect.DeepEqual(out, v) {
t.Fatalf("Expected %v, got %v", v, out)
}
}
invalids := []string{
"this",
"1--",
"1-10,,10",
"10-1",
"-1",
"-1,0",
}
for _, v := range invalids {
if out, err := ParseUintList(v); err == nil {
t.Fatalf("Expected failure with %s but got %v", v, out)
}
}
}

View file

@ -0,0 +1,188 @@
package plugins
import (
"bytes"
"encoding/json"
"io"
"io/ioutil"
"net/http"
"net/url"
"time"
"github.com/Sirupsen/logrus"
"github.com/containers/storage/pkg/plugins/transport"
"github.com/docker/go-connections/sockets"
"github.com/docker/go-connections/tlsconfig"
)
const (
defaultTimeOut = 30
)
// NewClient creates a new plugin client (http).
func NewClient(addr string, tlsConfig *tlsconfig.Options) (*Client, error) {
tr := &http.Transport{}
if tlsConfig != nil {
c, err := tlsconfig.Client(*tlsConfig)
if err != nil {
return nil, err
}
tr.TLSClientConfig = c
}
u, err := url.Parse(addr)
if err != nil {
return nil, err
}
socket := u.Host
if socket == "" {
// valid local socket addresses have the host empty.
socket = u.Path
}
if err := sockets.ConfigureTransport(tr, u.Scheme, socket); err != nil {
return nil, err
}
scheme := httpScheme(u)
clientTransport := transport.NewHTTPTransport(tr, scheme, socket)
return NewClientWithTransport(clientTransport), nil
}
// NewClientWithTransport creates a new plugin client with a given transport.
func NewClientWithTransport(tr transport.Transport) *Client {
return &Client{
http: &http.Client{
Transport: tr,
},
requestFactory: tr,
}
}
// Client represents a plugin client.
type Client struct {
http *http.Client // http client to use
requestFactory transport.RequestFactory
}
// Call calls the specified method with the specified arguments for the plugin.
// It will retry for 30 seconds if a failure occurs when calling.
func (c *Client) Call(serviceMethod string, args interface{}, ret interface{}) error {
var buf bytes.Buffer
if args != nil {
if err := json.NewEncoder(&buf).Encode(args); err != nil {
return err
}
}
body, err := c.callWithRetry(serviceMethod, &buf, true)
if err != nil {
return err
}
defer body.Close()
if ret != nil {
if err := json.NewDecoder(body).Decode(&ret); err != nil {
logrus.Errorf("%s: error reading plugin resp: %v", serviceMethod, err)
return err
}
}
return nil
}
// Stream calls the specified method with the specified arguments for the plugin and returns the response body
func (c *Client) Stream(serviceMethod string, args interface{}) (io.ReadCloser, error) {
var buf bytes.Buffer
if err := json.NewEncoder(&buf).Encode(args); err != nil {
return nil, err
}
return c.callWithRetry(serviceMethod, &buf, true)
}
// SendFile calls the specified method, and passes through the IO stream
func (c *Client) SendFile(serviceMethod string, data io.Reader, ret interface{}) error {
body, err := c.callWithRetry(serviceMethod, data, true)
if err != nil {
return err
}
defer body.Close()
if err := json.NewDecoder(body).Decode(&ret); err != nil {
logrus.Errorf("%s: error reading plugin resp: %v", serviceMethod, err)
return err
}
return nil
}
func (c *Client) callWithRetry(serviceMethod string, data io.Reader, retry bool) (io.ReadCloser, error) {
req, err := c.requestFactory.NewRequest(serviceMethod, data)
if err != nil {
return nil, err
}
var retries int
start := time.Now()
for {
resp, err := c.http.Do(req)
if err != nil {
if !retry {
return nil, err
}
timeOff := backoff(retries)
if abort(start, timeOff) {
return nil, err
}
retries++
logrus.Warnf("Unable to connect to plugin: %s%s: %v, retrying in %v", req.URL.Host, req.URL.Path, err, timeOff)
time.Sleep(timeOff)
continue
}
if resp.StatusCode != http.StatusOK {
b, err := ioutil.ReadAll(resp.Body)
resp.Body.Close()
if err != nil {
return nil, &statusError{resp.StatusCode, serviceMethod, err.Error()}
}
// Plugins' Response(s) should have an Err field indicating what went
// wrong. Try to unmarshal into ResponseErr. Otherwise fallback to just
// return the string(body)
type responseErr struct {
Err string
}
remoteErr := responseErr{}
if err := json.Unmarshal(b, &remoteErr); err == nil {
if remoteErr.Err != "" {
return nil, &statusError{resp.StatusCode, serviceMethod, remoteErr.Err}
}
}
// old way...
return nil, &statusError{resp.StatusCode, serviceMethod, string(b)}
}
return resp.Body, nil
}
}
func backoff(retries int) time.Duration {
b, max := 1, defaultTimeOut
for b < max && retries > 0 {
b *= 2
retries--
}
if b > max {
b = max
}
return time.Duration(b) * time.Second
}
func abort(start time.Time, timeOff time.Duration) bool {
return timeOff+time.Since(start) >= time.Duration(defaultTimeOut)*time.Second
}
func httpScheme(u *url.URL) string {
scheme := u.Scheme
if scheme != "https" {
scheme = "http"
}
return scheme
}

View file

@ -0,0 +1,134 @@
package plugins
import (
"io"
"net/http"
"net/http/httptest"
"net/url"
"reflect"
"testing"
"time"
"github.com/containers/storage/pkg/plugins/transport"
"github.com/docker/go-connections/tlsconfig"
)
var (
mux *http.ServeMux
server *httptest.Server
)
func setupRemotePluginServer() string {
mux = http.NewServeMux()
server = httptest.NewServer(mux)
return server.URL
}
func teardownRemotePluginServer() {
if server != nil {
server.Close()
}
}
func TestFailedConnection(t *testing.T) {
c, _ := NewClient("tcp://127.0.0.1:1", &tlsconfig.Options{InsecureSkipVerify: true})
_, err := c.callWithRetry("Service.Method", nil, false)
if err == nil {
t.Fatal("Unexpected successful connection")
}
}
func TestEchoInputOutput(t *testing.T) {
addr := setupRemotePluginServer()
defer teardownRemotePluginServer()
m := Manifest{[]string{"VolumeDriver", "NetworkDriver"}}
mux.HandleFunc("/Test.Echo", func(w http.ResponseWriter, r *http.Request) {
if r.Method != "POST" {
t.Fatalf("Expected POST, got %s\n", r.Method)
}
header := w.Header()
header.Set("Content-Type", transport.VersionMimetype)
io.Copy(w, r.Body)
})
c, _ := NewClient(addr, &tlsconfig.Options{InsecureSkipVerify: true})
var output Manifest
err := c.Call("Test.Echo", m, &output)
if err != nil {
t.Fatal(err)
}
if !reflect.DeepEqual(output, m) {
t.Fatalf("Expected %v, was %v\n", m, output)
}
err = c.Call("Test.Echo", nil, nil)
if err != nil {
t.Fatal(err)
}
}
func TestBackoff(t *testing.T) {
cases := []struct {
retries int
expTimeOff time.Duration
}{
{0, time.Duration(1)},
{1, time.Duration(2)},
{2, time.Duration(4)},
{4, time.Duration(16)},
{6, time.Duration(30)},
{10, time.Duration(30)},
}
for _, c := range cases {
s := c.expTimeOff * time.Second
if d := backoff(c.retries); d != s {
t.Fatalf("Retry %v, expected %v, was %v\n", c.retries, s, d)
}
}
}
func TestAbortRetry(t *testing.T) {
cases := []struct {
timeOff time.Duration
expAbort bool
}{
{time.Duration(1), false},
{time.Duration(2), false},
{time.Duration(10), false},
{time.Duration(30), true},
{time.Duration(40), true},
}
for _, c := range cases {
s := c.timeOff * time.Second
if a := abort(time.Now(), s); a != c.expAbort {
t.Fatalf("Duration %v, expected %v, was %v\n", c.timeOff, s, a)
}
}
}
func TestClientScheme(t *testing.T) {
cases := map[string]string{
"tcp://127.0.0.1:8080": "http",
"unix:///usr/local/plugins/foo": "http",
"http://127.0.0.1:8080": "http",
"https://127.0.0.1:8080": "https",
}
for addr, scheme := range cases {
u, err := url.Parse(addr)
if err != nil {
t.Fatal(err)
}
s := httpScheme(u)
if s != scheme {
t.Fatalf("URL scheme mismatch, expected %s, got %s", scheme, s)
}
}
}

View file

@ -0,0 +1,132 @@
package plugins
import (
"encoding/json"
"errors"
"fmt"
"io/ioutil"
"net/url"
"os"
"path/filepath"
"strings"
"sync"
)
var (
// ErrNotFound plugin not found
ErrNotFound = errors.New("plugin not found")
socketsPath = "/run/oci-storage/plugins"
specsPaths = []string{"/etc/oci-storage/plugins", "/usr/lib/oci-storage/plugins"}
)
// localRegistry defines a registry that is local (using unix socket).
type localRegistry struct{}
func newLocalRegistry() localRegistry {
return localRegistry{}
}
// Scan scans all the plugin paths and returns all the names it found
func Scan() ([]string, error) {
var names []string
if err := filepath.Walk(socketsPath, func(path string, fi os.FileInfo, err error) error {
if err != nil {
return nil
}
if fi.Mode()&os.ModeSocket != 0 {
name := strings.TrimSuffix(fi.Name(), filepath.Ext(fi.Name()))
names = append(names, name)
}
return nil
}); err != nil {
return nil, err
}
for _, path := range specsPaths {
if err := filepath.Walk(path, func(p string, fi os.FileInfo, err error) error {
if err != nil || fi.IsDir() {
return nil
}
name := strings.TrimSuffix(fi.Name(), filepath.Ext(fi.Name()))
names = append(names, name)
return nil
}); err != nil {
return nil, err
}
}
return names, nil
}
// Plugin returns the plugin registered with the given name (or returns an error).
func (l *localRegistry) Plugin(name string) (*Plugin, error) {
socketpaths := pluginPaths(socketsPath, name, ".sock")
for _, p := range socketpaths {
if fi, err := os.Stat(p); err == nil && fi.Mode()&os.ModeSocket != 0 {
return NewLocalPlugin(name, "unix://"+p), nil
}
}
var txtspecpaths []string
for _, p := range specsPaths {
txtspecpaths = append(txtspecpaths, pluginPaths(p, name, ".spec")...)
txtspecpaths = append(txtspecpaths, pluginPaths(p, name, ".json")...)
}
for _, p := range txtspecpaths {
if _, err := os.Stat(p); err == nil {
if strings.HasSuffix(p, ".json") {
return readPluginJSONInfo(name, p)
}
return readPluginInfo(name, p)
}
}
return nil, ErrNotFound
}
func readPluginInfo(name, path string) (*Plugin, error) {
content, err := ioutil.ReadFile(path)
if err != nil {
return nil, err
}
addr := strings.TrimSpace(string(content))
u, err := url.Parse(addr)
if err != nil {
return nil, err
}
if len(u.Scheme) == 0 {
return nil, fmt.Errorf("Unknown protocol")
}
return NewLocalPlugin(name, addr), nil
}
func readPluginJSONInfo(name, path string) (*Plugin, error) {
f, err := os.Open(path)
if err != nil {
return nil, err
}
defer f.Close()
var p Plugin
if err := json.NewDecoder(f).Decode(&p); err != nil {
return nil, err
}
p.name = name
if len(p.TLSConfig.CAFile) == 0 {
p.TLSConfig.InsecureSkipVerify = true
}
p.activateWait = sync.NewCond(&sync.Mutex{})
return &p, nil
}
func pluginPaths(base, name, ext string) []string {
return []string{
filepath.Join(base, name+ext),
filepath.Join(base, name, name+ext),
}
}

View file

@ -0,0 +1,119 @@
package plugins
import (
"io/ioutil"
"os"
"path/filepath"
"testing"
)
func Setup(t *testing.T) (string, func()) {
tmpdir, err := ioutil.TempDir("", "docker-test")
if err != nil {
t.Fatal(err)
}
backup := socketsPath
socketsPath = tmpdir
specsPaths = []string{tmpdir}
return tmpdir, func() {
socketsPath = backup
os.RemoveAll(tmpdir)
}
}
func TestFileSpecPlugin(t *testing.T) {
tmpdir, unregister := Setup(t)
defer unregister()
cases := []struct {
path string
name string
addr string
fail bool
}{
// TODO Windows: Factor out the unix:// variants.
{filepath.Join(tmpdir, "echo.spec"), "echo", "unix://var/lib/oci-storage/plugins/echo.sock", false},
{filepath.Join(tmpdir, "echo", "echo.spec"), "echo", "unix://var/lib/oci-storage/plugins/echo.sock", false},
{filepath.Join(tmpdir, "foo.spec"), "foo", "tcp://localhost:8080", false},
{filepath.Join(tmpdir, "foo", "foo.spec"), "foo", "tcp://localhost:8080", false},
{filepath.Join(tmpdir, "bar.spec"), "bar", "localhost:8080", true}, // unknown transport
}
for _, c := range cases {
if err := os.MkdirAll(filepath.Dir(c.path), 0755); err != nil {
t.Fatal(err)
}
if err := ioutil.WriteFile(c.path, []byte(c.addr), 0644); err != nil {
t.Fatal(err)
}
r := newLocalRegistry()
p, err := r.Plugin(c.name)
if c.fail && err == nil {
continue
}
if err != nil {
t.Fatal(err)
}
if p.name != c.name {
t.Fatalf("Expected plugin `%s`, got %s\n", c.name, p.Name)
}
if p.Addr != c.addr {
t.Fatalf("Expected plugin addr `%s`, got %s\n", c.addr, p.Addr)
}
if p.TLSConfig.InsecureSkipVerify != true {
t.Fatalf("Expected TLS verification to be skipped")
}
}
}
func TestFileJSONSpecPlugin(t *testing.T) {
tmpdir, unregister := Setup(t)
defer unregister()
p := filepath.Join(tmpdir, "example.json")
spec := `{
"Name": "plugin-example",
"Addr": "https://example.com/oci-storage/plugin",
"TLSConfig": {
"CAFile": "/usr/shared/oci-storage/certs/example-ca.pem",
"CertFile": "/usr/shared/oci-storage/certs/example-cert.pem",
"KeyFile": "/usr/shared/oci-storage/certs/example-key.pem"
}
}`
if err := ioutil.WriteFile(p, []byte(spec), 0644); err != nil {
t.Fatal(err)
}
r := newLocalRegistry()
plugin, err := r.Plugin("example")
if err != nil {
t.Fatal(err)
}
if plugin.name != "example" {
t.Fatalf("Expected plugin `plugin-example`, got %s\n", plugin.Name)
}
if plugin.Addr != "https://example.com/oci-storage/plugin" {
t.Fatalf("Expected plugin addr `https://example.com/oci-storage/plugin`, got %s\n", plugin.Addr)
}
if plugin.TLSConfig.CAFile != "/usr/shared/oci-storage/certs/example-ca.pem" {
t.Fatalf("Expected plugin CA `/usr/shared/oci-storage/certs/example-ca.pem`, got %s\n", plugin.TLSConfig.CAFile)
}
if plugin.TLSConfig.CertFile != "/usr/shared/oci-storage/certs/example-cert.pem" {
t.Fatalf("Expected plugin Certificate `/usr/shared/oci-storage/certs/example-cert.pem`, got %s\n", plugin.TLSConfig.CertFile)
}
if plugin.TLSConfig.KeyFile != "/usr/shared/oci-storage/certs/example-key.pem" {
t.Fatalf("Expected plugin Key `/usr/shared/oci-storage/certs/example-key.pem`, got %s\n", plugin.TLSConfig.KeyFile)
}
}

View file

@ -0,0 +1,61 @@
// +build !windows
package plugins
import (
"fmt"
"net"
"os"
"path/filepath"
"reflect"
"testing"
)
func TestLocalSocket(t *testing.T) {
// TODO Windows: Enable a similar version for Windows named pipes
tmpdir, unregister := Setup(t)
defer unregister()
cases := []string{
filepath.Join(tmpdir, "echo.sock"),
filepath.Join(tmpdir, "echo", "echo.sock"),
}
for _, c := range cases {
if err := os.MkdirAll(filepath.Dir(c), 0755); err != nil {
t.Fatal(err)
}
l, err := net.Listen("unix", c)
if err != nil {
t.Fatal(err)
}
r := newLocalRegistry()
p, err := r.Plugin("echo")
if err != nil {
t.Fatal(err)
}
pp, err := r.Plugin("echo")
if err != nil {
t.Fatal(err)
}
if !reflect.DeepEqual(p, pp) {
t.Fatalf("Expected %v, was %v\n", p, pp)
}
if p.name != "echo" {
t.Fatalf("Expected plugin `echo`, got %s\n", p.Name)
}
addr := fmt.Sprintf("unix://%s", c)
if p.Addr != addr {
t.Fatalf("Expected plugin addr `%s`, got %s\n", addr, p.Addr)
}
if p.TLSConfig.InsecureSkipVerify != true {
t.Fatalf("Expected TLS verification to be skipped")
}
l.Close()
}
}

View file

@ -0,0 +1,33 @@
package plugins
import (
"fmt"
"net/http"
)
type statusError struct {
status int
method string
err string
}
// Error returns a formatted string for this error type
func (e *statusError) Error() string {
return fmt.Sprintf("%s: %v", e.method, e.err)
}
// IsNotFound indicates if the passed in error is from an http.StatusNotFound from the plugin
func IsNotFound(err error) bool {
return isStatusError(err, http.StatusNotFound)
}
func isStatusError(err error, status int) bool {
if err == nil {
return false
}
e, ok := err.(*statusError)
if !ok {
return false
}
return e.status == status
}

View file

@ -0,0 +1,89 @@
package foo
import (
"fmt"
aliasedio "io"
"github.com/containers/storage/pkg/plugins/pluginrpc-gen/fixtures/otherfixture"
)
var (
errFakeImport = fmt.Errorf("just to import fmt for imports tests")
)
type wobble struct {
Some string
Val string
Inception *wobble
}
// Fooer is an empty interface used for tests.
type Fooer interface{}
// Fooer2 is an interface used for tests.
type Fooer2 interface {
Foo()
}
// Fooer3 is an interface used for tests.
type Fooer3 interface {
Foo()
Bar(a string)
Baz(a string) (err error)
Qux(a, b string) (val string, err error)
Wobble() (w *wobble)
Wiggle() (w wobble)
WiggleWobble(a []*wobble, b []wobble, c map[string]*wobble, d map[*wobble]wobble, e map[string][]wobble, f []*otherfixture.Spaceship) (g map[*wobble]wobble, h [][]*wobble, i otherfixture.Spaceship, j *otherfixture.Spaceship, k map[*otherfixture.Spaceship]otherfixture.Spaceship, l []otherfixture.Spaceship)
}
// Fooer4 is an interface used for tests.
type Fooer4 interface {
Foo() error
}
// Bar is an interface used for tests.
type Bar interface {
Boo(a string, b string) (s string, err error)
}
// Fooer5 is an interface used for tests.
type Fooer5 interface {
Foo()
Bar
}
// Fooer6 is an interface used for tests.
type Fooer6 interface {
Foo(a otherfixture.Spaceship)
}
// Fooer7 is an interface used for tests.
type Fooer7 interface {
Foo(a *otherfixture.Spaceship)
}
// Fooer8 is an interface used for tests.
type Fooer8 interface {
Foo(a map[string]otherfixture.Spaceship)
}
// Fooer9 is an interface used for tests.
type Fooer9 interface {
Foo(a map[string]*otherfixture.Spaceship)
}
// Fooer10 is an interface used for tests.
type Fooer10 interface {
Foo(a []otherfixture.Spaceship)
}
// Fooer11 is an interface used for tests.
type Fooer11 interface {
Foo(a []*otherfixture.Spaceship)
}
// Fooer12 is an interface used for tests.
type Fooer12 interface {
Foo(a aliasedio.Reader)
}

View file

@ -0,0 +1,4 @@
package otherfixture
// Spaceship is a fixture for tests
type Spaceship struct{}

View file

@ -0,0 +1,91 @@
package main
import (
"bytes"
"flag"
"fmt"
"go/format"
"io/ioutil"
"os"
"unicode"
"unicode/utf8"
)
type stringSet struct {
values map[string]struct{}
}
func (s stringSet) String() string {
return ""
}
func (s stringSet) Set(value string) error {
s.values[value] = struct{}{}
return nil
}
func (s stringSet) GetValues() map[string]struct{} {
return s.values
}
var (
typeName = flag.String("type", "", "interface type to generate plugin rpc proxy for")
rpcName = flag.String("name", *typeName, "RPC name, set if different from type")
inputFile = flag.String("i", "", "input file path")
outputFile = flag.String("o", *inputFile+"_proxy.go", "output file path")
skipFuncs map[string]struct{}
flSkipFuncs = stringSet{make(map[string]struct{})}
flBuildTags = stringSet{make(map[string]struct{})}
)
func errorOut(msg string, err error) {
if err == nil {
return
}
fmt.Fprintf(os.Stderr, "%s: %v\n", msg, err)
os.Exit(1)
}
func checkFlags() error {
if *outputFile == "" {
return fmt.Errorf("missing required flag `-o`")
}
if *inputFile == "" {
return fmt.Errorf("missing required flag `-i`")
}
return nil
}
func main() {
flag.Var(flSkipFuncs, "skip", "skip parsing for function")
flag.Var(flBuildTags, "tag", "build tags to add to generated files")
flag.Parse()
skipFuncs = flSkipFuncs.GetValues()
errorOut("error", checkFlags())
pkg, err := Parse(*inputFile, *typeName)
errorOut(fmt.Sprintf("error parsing requested type %s", *typeName), err)
var analysis = struct {
InterfaceType string
RPCName string
BuildTags map[string]struct{}
*ParsedPkg
}{toLower(*typeName), *rpcName, flBuildTags.GetValues(), pkg}
var buf bytes.Buffer
errorOut("parser error", generatedTempl.Execute(&buf, analysis))
src, err := format.Source(buf.Bytes())
errorOut("error formatting generated source:\n"+buf.String(), err)
errorOut("error writing file", ioutil.WriteFile(*outputFile, src, 0644))
}
func toLower(s string) string {
if s == "" {
return ""
}
r, n := utf8.DecodeRuneInString(s)
return string(unicode.ToLower(r)) + s[n:]
}

View file

@ -0,0 +1,263 @@
package main
import (
"errors"
"fmt"
"go/ast"
"go/parser"
"go/token"
"path"
"reflect"
"strings"
)
var errBadReturn = errors.New("found return arg with no name: all args must be named")
type errUnexpectedType struct {
expected string
actual interface{}
}
func (e errUnexpectedType) Error() string {
return fmt.Sprintf("got wrong type expecting %s, got: %v", e.expected, reflect.TypeOf(e.actual))
}
// ParsedPkg holds information about a package that has been parsed,
// its name and the list of functions.
type ParsedPkg struct {
Name string
Functions []function
Imports []importSpec
}
type function struct {
Name string
Args []arg
Returns []arg
Doc string
}
type arg struct {
Name string
ArgType string
PackageSelector string
}
func (a *arg) String() string {
return a.Name + " " + a.ArgType
}
type importSpec struct {
Name string
Path string
}
func (s *importSpec) String() string {
var ss string
if len(s.Name) != 0 {
ss += s.Name
}
ss += s.Path
return ss
}
// Parse parses the given file for an interface definition with the given name.
func Parse(filePath string, objName string) (*ParsedPkg, error) {
fs := token.NewFileSet()
pkg, err := parser.ParseFile(fs, filePath, nil, parser.AllErrors)
if err != nil {
return nil, err
}
p := &ParsedPkg{}
p.Name = pkg.Name.Name
obj, exists := pkg.Scope.Objects[objName]
if !exists {
return nil, fmt.Errorf("could not find object %s in %s", objName, filePath)
}
if obj.Kind != ast.Typ {
return nil, fmt.Errorf("exected type, got %s", obj.Kind)
}
spec, ok := obj.Decl.(*ast.TypeSpec)
if !ok {
return nil, errUnexpectedType{"*ast.TypeSpec", obj.Decl}
}
iface, ok := spec.Type.(*ast.InterfaceType)
if !ok {
return nil, errUnexpectedType{"*ast.InterfaceType", spec.Type}
}
p.Functions, err = parseInterface(iface)
if err != nil {
return nil, err
}
// figure out what imports will be needed
imports := make(map[string]importSpec)
for _, f := range p.Functions {
args := append(f.Args, f.Returns...)
for _, arg := range args {
if len(arg.PackageSelector) == 0 {
continue
}
for _, i := range pkg.Imports {
if i.Name != nil {
if i.Name.Name != arg.PackageSelector {
continue
}
imports[i.Path.Value] = importSpec{Name: arg.PackageSelector, Path: i.Path.Value}
break
}
_, name := path.Split(i.Path.Value)
splitName := strings.Split(name, "-")
if len(splitName) > 1 {
name = splitName[len(splitName)-1]
}
// import paths have quotes already added in, so need to remove them for name comparison
name = strings.TrimPrefix(name, `"`)
name = strings.TrimSuffix(name, `"`)
if name == arg.PackageSelector {
imports[i.Path.Value] = importSpec{Path: i.Path.Value}
break
}
}
}
}
for _, spec := range imports {
p.Imports = append(p.Imports, spec)
}
return p, nil
}
func parseInterface(iface *ast.InterfaceType) ([]function, error) {
var functions []function
for _, field := range iface.Methods.List {
switch f := field.Type.(type) {
case *ast.FuncType:
method, err := parseFunc(field)
if err != nil {
return nil, err
}
if method == nil {
continue
}
functions = append(functions, *method)
case *ast.Ident:
spec, ok := f.Obj.Decl.(*ast.TypeSpec)
if !ok {
return nil, errUnexpectedType{"*ast.TypeSpec", f.Obj.Decl}
}
iface, ok := spec.Type.(*ast.InterfaceType)
if !ok {
return nil, errUnexpectedType{"*ast.TypeSpec", spec.Type}
}
funcs, err := parseInterface(iface)
if err != nil {
fmt.Println(err)
continue
}
functions = append(functions, funcs...)
default:
return nil, errUnexpectedType{"*astFuncType or *ast.Ident", f}
}
}
return functions, nil
}
func parseFunc(field *ast.Field) (*function, error) {
f := field.Type.(*ast.FuncType)
method := &function{Name: field.Names[0].Name}
if _, exists := skipFuncs[method.Name]; exists {
fmt.Println("skipping:", method.Name)
return nil, nil
}
if f.Params != nil {
args, err := parseArgs(f.Params.List)
if err != nil {
return nil, err
}
method.Args = args
}
if f.Results != nil {
returns, err := parseArgs(f.Results.List)
if err != nil {
return nil, fmt.Errorf("error parsing function returns for %q: %v", method.Name, err)
}
method.Returns = returns
}
return method, nil
}
func parseArgs(fields []*ast.Field) ([]arg, error) {
var args []arg
for _, f := range fields {
if len(f.Names) == 0 {
return nil, errBadReturn
}
for _, name := range f.Names {
p, err := parseExpr(f.Type)
if err != nil {
return nil, err
}
args = append(args, arg{name.Name, p.value, p.pkg})
}
}
return args, nil
}
type parsedExpr struct {
value string
pkg string
}
func parseExpr(e ast.Expr) (parsedExpr, error) {
var parsed parsedExpr
switch i := e.(type) {
case *ast.Ident:
parsed.value += i.Name
case *ast.StarExpr:
p, err := parseExpr(i.X)
if err != nil {
return parsed, err
}
parsed.value += "*"
parsed.value += p.value
parsed.pkg = p.pkg
case *ast.SelectorExpr:
p, err := parseExpr(i.X)
if err != nil {
return parsed, err
}
parsed.pkg = p.value
parsed.value += p.value + "."
parsed.value += i.Sel.Name
case *ast.MapType:
parsed.value += "map["
p, err := parseExpr(i.Key)
if err != nil {
return parsed, err
}
parsed.value += p.value
parsed.value += "]"
p, err = parseExpr(i.Value)
if err != nil {
return parsed, err
}
parsed.value += p.value
parsed.pkg = p.pkg
case *ast.ArrayType:
parsed.value += "[]"
p, err := parseExpr(i.Elt)
if err != nil {
return parsed, err
}
parsed.value += p.value
parsed.pkg = p.pkg
default:
return parsed, errUnexpectedType{"*ast.Ident or *ast.StarExpr", i}
}
return parsed, nil
}

View file

@ -0,0 +1,222 @@
package main
import (
"fmt"
"path/filepath"
"runtime"
"strings"
"testing"
)
const testFixture = "fixtures/foo.go"
func TestParseEmptyInterface(t *testing.T) {
pkg, err := Parse(testFixture, "Fooer")
if err != nil {
t.Fatal(err)
}
assertName(t, "foo", pkg.Name)
assertNum(t, 0, len(pkg.Functions))
}
func TestParseNonInterfaceType(t *testing.T) {
_, err := Parse(testFixture, "wobble")
if _, ok := err.(errUnexpectedType); !ok {
t.Fatal("expected type error when parsing non-interface type")
}
}
func TestParseWithOneFunction(t *testing.T) {
pkg, err := Parse(testFixture, "Fooer2")
if err != nil {
t.Fatal(err)
}
assertName(t, "foo", pkg.Name)
assertNum(t, 1, len(pkg.Functions))
assertName(t, "Foo", pkg.Functions[0].Name)
assertNum(t, 0, len(pkg.Functions[0].Args))
assertNum(t, 0, len(pkg.Functions[0].Returns))
}
func TestParseWithMultipleFuncs(t *testing.T) {
pkg, err := Parse(testFixture, "Fooer3")
if err != nil {
t.Fatal(err)
}
assertName(t, "foo", pkg.Name)
assertNum(t, 7, len(pkg.Functions))
f := pkg.Functions[0]
assertName(t, "Foo", f.Name)
assertNum(t, 0, len(f.Args))
assertNum(t, 0, len(f.Returns))
f = pkg.Functions[1]
assertName(t, "Bar", f.Name)
assertNum(t, 1, len(f.Args))
assertNum(t, 0, len(f.Returns))
arg := f.Args[0]
assertName(t, "a", arg.Name)
assertName(t, "string", arg.ArgType)
f = pkg.Functions[2]
assertName(t, "Baz", f.Name)
assertNum(t, 1, len(f.Args))
assertNum(t, 1, len(f.Returns))
arg = f.Args[0]
assertName(t, "a", arg.Name)
assertName(t, "string", arg.ArgType)
arg = f.Returns[0]
assertName(t, "err", arg.Name)
assertName(t, "error", arg.ArgType)
f = pkg.Functions[3]
assertName(t, "Qux", f.Name)
assertNum(t, 2, len(f.Args))
assertNum(t, 2, len(f.Returns))
arg = f.Args[0]
assertName(t, "a", f.Args[0].Name)
assertName(t, "string", f.Args[0].ArgType)
arg = f.Args[1]
assertName(t, "b", arg.Name)
assertName(t, "string", arg.ArgType)
arg = f.Returns[0]
assertName(t, "val", arg.Name)
assertName(t, "string", arg.ArgType)
arg = f.Returns[1]
assertName(t, "err", arg.Name)
assertName(t, "error", arg.ArgType)
f = pkg.Functions[4]
assertName(t, "Wobble", f.Name)
assertNum(t, 0, len(f.Args))
assertNum(t, 1, len(f.Returns))
arg = f.Returns[0]
assertName(t, "w", arg.Name)
assertName(t, "*wobble", arg.ArgType)
f = pkg.Functions[5]
assertName(t, "Wiggle", f.Name)
assertNum(t, 0, len(f.Args))
assertNum(t, 1, len(f.Returns))
arg = f.Returns[0]
assertName(t, "w", arg.Name)
assertName(t, "wobble", arg.ArgType)
f = pkg.Functions[6]
assertName(t, "WiggleWobble", f.Name)
assertNum(t, 6, len(f.Args))
assertNum(t, 6, len(f.Returns))
expectedArgs := [][]string{
{"a", "[]*wobble"},
{"b", "[]wobble"},
{"c", "map[string]*wobble"},
{"d", "map[*wobble]wobble"},
{"e", "map[string][]wobble"},
{"f", "[]*otherfixture.Spaceship"},
}
for i, arg := range f.Args {
assertName(t, expectedArgs[i][0], arg.Name)
assertName(t, expectedArgs[i][1], arg.ArgType)
}
expectedReturns := [][]string{
{"g", "map[*wobble]wobble"},
{"h", "[][]*wobble"},
{"i", "otherfixture.Spaceship"},
{"j", "*otherfixture.Spaceship"},
{"k", "map[*otherfixture.Spaceship]otherfixture.Spaceship"},
{"l", "[]otherfixture.Spaceship"},
}
for i, ret := range f.Returns {
assertName(t, expectedReturns[i][0], ret.Name)
assertName(t, expectedReturns[i][1], ret.ArgType)
}
}
func TestParseWithUnamedReturn(t *testing.T) {
_, err := Parse(testFixture, "Fooer4")
if !strings.HasSuffix(err.Error(), errBadReturn.Error()) {
t.Fatalf("expected ErrBadReturn, got %v", err)
}
}
func TestEmbeddedInterface(t *testing.T) {
pkg, err := Parse(testFixture, "Fooer5")
if err != nil {
t.Fatal(err)
}
assertName(t, "foo", pkg.Name)
assertNum(t, 2, len(pkg.Functions))
f := pkg.Functions[0]
assertName(t, "Foo", f.Name)
assertNum(t, 0, len(f.Args))
assertNum(t, 0, len(f.Returns))
f = pkg.Functions[1]
assertName(t, "Boo", f.Name)
assertNum(t, 2, len(f.Args))
assertNum(t, 2, len(f.Returns))
arg := f.Args[0]
assertName(t, "a", arg.Name)
assertName(t, "string", arg.ArgType)
arg = f.Args[1]
assertName(t, "b", arg.Name)
assertName(t, "string", arg.ArgType)
arg = f.Returns[0]
assertName(t, "s", arg.Name)
assertName(t, "string", arg.ArgType)
arg = f.Returns[1]
assertName(t, "err", arg.Name)
assertName(t, "error", arg.ArgType)
}
func TestParsedImports(t *testing.T) {
cases := []string{"Fooer6", "Fooer7", "Fooer8", "Fooer9", "Fooer10", "Fooer11"}
for _, testCase := range cases {
pkg, err := Parse(testFixture, testCase)
if err != nil {
t.Fatal(err)
}
assertNum(t, 1, len(pkg.Imports))
importPath := strings.Split(pkg.Imports[0].Path, "/")
assertName(t, "otherfixture\"", importPath[len(importPath)-1])
assertName(t, "", pkg.Imports[0].Name)
}
}
func TestAliasedImports(t *testing.T) {
pkg, err := Parse(testFixture, "Fooer12")
if err != nil {
t.Fatal(err)
}
assertNum(t, 1, len(pkg.Imports))
assertName(t, "aliasedio", pkg.Imports[0].Name)
}
func assertName(t *testing.T, expected, actual string) {
if expected != actual {
fatalOut(t, fmt.Sprintf("expected name to be `%s`, got: %s", expected, actual))
}
}
func assertNum(t *testing.T, expected, actual int) {
if expected != actual {
fatalOut(t, fmt.Sprintf("expected number to be %d, got: %d", expected, actual))
}
}
func fatalOut(t *testing.T, msg string) {
_, file, ln, _ := runtime.Caller(2)
t.Fatalf("%s:%d: %s", filepath.Base(file), ln, msg)
}

View file

@ -0,0 +1,118 @@
package main
import (
"strings"
"text/template"
)
func printArgs(args []arg) string {
var argStr []string
for _, arg := range args {
argStr = append(argStr, arg.String())
}
return strings.Join(argStr, ", ")
}
func buildImports(specs []importSpec) string {
if len(specs) == 0 {
return `import "errors"`
}
imports := "import(\n"
imports += "\t\"errors\"\n"
for _, i := range specs {
imports += "\t" + i.String() + "\n"
}
imports += ")"
return imports
}
func marshalType(t string) string {
switch t {
case "error":
// convert error types to plain strings to ensure the values are encoded/decoded properly
return "string"
default:
return t
}
}
func isErr(t string) bool {
switch t {
case "error":
return true
default:
return false
}
}
// Need to use this helper due to issues with go-vet
func buildTag(s string) string {
return "+build " + s
}
var templFuncs = template.FuncMap{
"printArgs": printArgs,
"marshalType": marshalType,
"isErr": isErr,
"lower": strings.ToLower,
"title": title,
"tag": buildTag,
"imports": buildImports,
}
func title(s string) string {
if strings.ToLower(s) == "id" {
return "ID"
}
return strings.Title(s)
}
var generatedTempl = template.Must(template.New("rpc_cient").Funcs(templFuncs).Parse(`
// generated code - DO NOT EDIT
{{ range $k, $v := .BuildTags }}
// {{ tag $k }} {{ end }}
package {{ .Name }}
{{ imports .Imports }}
type client interface{
Call(string, interface{}, interface{}) error
}
type {{ .InterfaceType }}Proxy struct {
client
}
{{ range .Functions }}
type {{ $.InterfaceType }}Proxy{{ .Name }}Request struct{
{{ range .Args }}
{{ title .Name }} {{ .ArgType }} {{ end }}
}
type {{ $.InterfaceType }}Proxy{{ .Name }}Response struct{
{{ range .Returns }}
{{ title .Name }} {{ marshalType .ArgType }} {{ end }}
}
func (pp *{{ $.InterfaceType }}Proxy) {{ .Name }}({{ printArgs .Args }}) ({{ printArgs .Returns }}) {
var(
req {{ $.InterfaceType }}Proxy{{ .Name }}Request
ret {{ $.InterfaceType }}Proxy{{ .Name }}Response
)
{{ range .Args }}
req.{{ title .Name }} = {{ lower .Name }} {{ end }}
if err = pp.Call("{{ $.RPCName }}.{{ .Name }}", req, &ret); err != nil {
return
}
{{ range $r := .Returns }}
{{ if isErr .ArgType }}
if ret.{{ title .Name }} != "" {
{{ lower .Name }} = errors.New(ret.{{ title .Name }})
} {{ end }}
{{ if isErr .ArgType | not }} {{ lower .Name }} = ret.{{ title .Name }} {{ end }} {{ end }}
return
}
{{ end }}
`))

View file

@ -0,0 +1,270 @@
// Package plugins provides structures and helper functions to manage Docker
// plugins.
//
// Storage discovers plugins by looking for them in the plugin directory whenever
// a user or container tries to use one by name. UNIX domain socket files must
// be located under /run/oci-storage/plugins, whereas spec files can be located
// either under /etc/oci-storage/plugins or /usr/lib/oci-storage/plugins. This
// is handled by the Registry interface, which lets you list all plugins or get
// a plugin by its name if it exists.
//
// The plugins need to implement an HTTP server and bind this to the UNIX socket
// or the address specified in the spec files.
// A handshake is send at /Plugin.Activate, and plugins are expected to return
// a Manifest with a list of subsystems which this plugin implements. As of
// this writing, the known subsystem is "GraphDriver".
//
// In order to use a plugins, you can use the ``Get`` with the name of the
// plugin and the subsystem it implements.
//
// plugin, err := plugins.Get("example", "VolumeDriver")
// if err != nil {
// return fmt.Errorf("Error looking up volume plugin example: %v", err)
// }
package plugins
import (
"errors"
"sync"
"time"
"github.com/Sirupsen/logrus"
"github.com/docker/go-connections/tlsconfig"
)
var (
// ErrNotImplements is returned if the plugin does not implement the requested driver.
ErrNotImplements = errors.New("Plugin does not implement the requested driver")
)
type plugins struct {
sync.Mutex
plugins map[string]*Plugin
}
var (
storage = plugins{plugins: make(map[string]*Plugin)}
extpointHandlers = make(map[string]func(string, *Client))
)
// Manifest lists what a plugin implements.
type Manifest struct {
// List of subsystem the plugin implements.
Implements []string
}
// Plugin is the definition of a storage plugin.
type Plugin struct {
// Name of the plugin
name string
// Address of the plugin
Addr string
// TLS configuration of the plugin
TLSConfig *tlsconfig.Options
// Client attached to the plugin
client *Client
// Manifest of the plugin (see above)
Manifest *Manifest `json:"-"`
// error produced by activation
activateErr error
// specifies if the activation sequence is completed (not if it is successful or not)
activated bool
// wait for activation to finish
activateWait *sync.Cond
}
// Name returns the name of the plugin.
func (p *Plugin) Name() string {
return p.name
}
// Client returns a ready-to-use plugin client that can be used to communicate with the plugin.
func (p *Plugin) Client() *Client {
return p.client
}
// NewLocalPlugin creates a new local plugin.
func NewLocalPlugin(name, addr string) *Plugin {
return &Plugin{
name: name,
Addr: addr,
// TODO: change to nil
TLSConfig: &tlsconfig.Options{InsecureSkipVerify: true},
activateWait: sync.NewCond(&sync.Mutex{}),
}
}
func (p *Plugin) activate() error {
p.activateWait.L.Lock()
if p.activated {
p.activateWait.L.Unlock()
return p.activateErr
}
p.activateErr = p.activateWithLock()
p.activated = true
p.activateWait.L.Unlock()
p.activateWait.Broadcast()
return p.activateErr
}
func (p *Plugin) activateWithLock() error {
c, err := NewClient(p.Addr, p.TLSConfig)
if err != nil {
return err
}
p.client = c
m := new(Manifest)
if err = p.client.Call("Plugin.Activate", nil, m); err != nil {
return err
}
p.Manifest = m
for _, iface := range m.Implements {
handler, handled := extpointHandlers[iface]
if !handled {
continue
}
handler(p.name, p.client)
}
return nil
}
func (p *Plugin) waitActive() error {
p.activateWait.L.Lock()
for !p.activated {
p.activateWait.Wait()
}
p.activateWait.L.Unlock()
return p.activateErr
}
func (p *Plugin) implements(kind string) bool {
if err := p.waitActive(); err != nil {
return false
}
for _, driver := range p.Manifest.Implements {
if driver == kind {
return true
}
}
return false
}
func load(name string) (*Plugin, error) {
return loadWithRetry(name, true)
}
func loadWithRetry(name string, retry bool) (*Plugin, error) {
registry := newLocalRegistry()
start := time.Now()
var retries int
for {
pl, err := registry.Plugin(name)
if err != nil {
if !retry {
return nil, err
}
timeOff := backoff(retries)
if abort(start, timeOff) {
return nil, err
}
retries++
logrus.Warnf("Unable to locate plugin: %s, retrying in %v", name, timeOff)
time.Sleep(timeOff)
continue
}
storage.Lock()
storage.plugins[name] = pl
storage.Unlock()
err = pl.activate()
if err != nil {
storage.Lock()
delete(storage.plugins, name)
storage.Unlock()
}
return pl, err
}
}
func get(name string) (*Plugin, error) {
storage.Lock()
pl, ok := storage.plugins[name]
storage.Unlock()
if ok {
return pl, pl.activate()
}
return load(name)
}
// Get returns the plugin given the specified name and requested implementation.
func Get(name, imp string) (*Plugin, error) {
pl, err := get(name)
if err != nil {
return nil, err
}
if pl.implements(imp) {
logrus.Debugf("%s implements: %s", name, imp)
return pl, nil
}
return nil, ErrNotImplements
}
// Handle adds the specified function to the extpointHandlers.
func Handle(iface string, fn func(string, *Client)) {
extpointHandlers[iface] = fn
}
// GetAll returns all the plugins for the specified implementation
func GetAll(imp string) ([]*Plugin, error) {
pluginNames, err := Scan()
if err != nil {
return nil, err
}
type plLoad struct {
pl *Plugin
err error
}
chPl := make(chan *plLoad, len(pluginNames))
var wg sync.WaitGroup
for _, name := range pluginNames {
if pl, ok := storage.plugins[name]; ok {
chPl <- &plLoad{pl, nil}
continue
}
wg.Add(1)
go func(name string) {
defer wg.Done()
pl, err := loadWithRetry(name, false)
chPl <- &plLoad{pl, err}
}(name)
}
wg.Wait()
close(chPl)
var out []*Plugin
for pl := range chPl {
if pl.err != nil {
logrus.Error(pl.err)
continue
}
if pl.pl.implements(imp) {
out = append(out, pl.pl)
}
}
return out, nil
}

View file

@ -0,0 +1,36 @@
package transport
import (
"io"
"net/http"
)
// httpTransport holds an http.RoundTripper
// and information about the scheme and address the transport
// sends request to.
type httpTransport struct {
http.RoundTripper
scheme string
addr string
}
// NewHTTPTransport creates a new httpTransport.
func NewHTTPTransport(r http.RoundTripper, scheme, addr string) Transport {
return httpTransport{
RoundTripper: r,
scheme: scheme,
addr: addr,
}
}
// NewRequest creates a new http.Request and sets the URL
// scheme and address with the transport's fields.
func (t httpTransport) NewRequest(path string, data io.Reader) (*http.Request, error) {
req, err := newHTTPRequest(path, data)
if err != nil {
return nil, err
}
req.URL.Scheme = t.scheme
req.URL.Host = t.addr
return req, nil
}

View file

@ -0,0 +1,36 @@
package transport
import (
"io"
"net/http"
"strings"
)
// VersionMimetype is the Content-Type the engine sends to plugins.
const VersionMimetype = "application/vnd.docker.plugins.v1.2+json"
// RequestFactory defines an interface that
// transports can implement to create new requests.
type RequestFactory interface {
NewRequest(path string, data io.Reader) (*http.Request, error)
}
// Transport defines an interface that plugin transports
// must implement.
type Transport interface {
http.RoundTripper
RequestFactory
}
// newHTTPRequest creates a new request with a path and a body.
func newHTTPRequest(path string, data io.Reader) (*http.Request, error) {
if !strings.HasPrefix(path, "/") {
path = "/" + path
}
req, err := http.NewRequest("POST", path, data)
if err != nil {
return nil, err
}
req.Header.Add("Accept", VersionMimetype)
return req, nil
}

View file

@ -0,0 +1,161 @@
package pools
import (
"bufio"
"bytes"
"io"
"strings"
"testing"
)
func TestBufioReaderPoolGetWithNoReaderShouldCreateOne(t *testing.T) {
reader := BufioReader32KPool.Get(nil)
if reader == nil {
t.Fatalf("BufioReaderPool should have create a bufio.Reader but did not.")
}
}
func TestBufioReaderPoolPutAndGet(t *testing.T) {
sr := bufio.NewReader(strings.NewReader("foobar"))
reader := BufioReader32KPool.Get(sr)
if reader == nil {
t.Fatalf("BufioReaderPool should not return a nil reader.")
}
// verify the first 3 byte
buf1 := make([]byte, 3)
_, err := reader.Read(buf1)
if err != nil {
t.Fatal(err)
}
if actual := string(buf1); actual != "foo" {
t.Fatalf("The first letter should have been 'foo' but was %v", actual)
}
BufioReader32KPool.Put(reader)
// Try to read the next 3 bytes
_, err = sr.Read(make([]byte, 3))
if err == nil || err != io.EOF {
t.Fatalf("The buffer should have been empty, issue an EOF error.")
}
}
type simpleReaderCloser struct {
io.Reader
closed bool
}
func (r *simpleReaderCloser) Close() error {
r.closed = true
return nil
}
func TestNewReadCloserWrapperWithAReadCloser(t *testing.T) {
br := bufio.NewReader(strings.NewReader(""))
sr := &simpleReaderCloser{
Reader: strings.NewReader("foobar"),
closed: false,
}
reader := BufioReader32KPool.NewReadCloserWrapper(br, sr)
if reader == nil {
t.Fatalf("NewReadCloserWrapper should not return a nil reader.")
}
// Verify the content of reader
buf := make([]byte, 3)
_, err := reader.Read(buf)
if err != nil {
t.Fatal(err)
}
if actual := string(buf); actual != "foo" {
t.Fatalf("The first 3 letter should have been 'foo' but were %v", actual)
}
reader.Close()
// Read 3 more bytes "bar"
_, err = reader.Read(buf)
if err != nil {
t.Fatal(err)
}
if actual := string(buf); actual != "bar" {
t.Fatalf("The first 3 letter should have been 'bar' but were %v", actual)
}
if !sr.closed {
t.Fatalf("The ReaderCloser should have been closed, it is not.")
}
}
func TestBufioWriterPoolGetWithNoReaderShouldCreateOne(t *testing.T) {
writer := BufioWriter32KPool.Get(nil)
if writer == nil {
t.Fatalf("BufioWriterPool should have create a bufio.Writer but did not.")
}
}
func TestBufioWriterPoolPutAndGet(t *testing.T) {
buf := new(bytes.Buffer)
bw := bufio.NewWriter(buf)
writer := BufioWriter32KPool.Get(bw)
if writer == nil {
t.Fatalf("BufioReaderPool should not return a nil writer.")
}
written, err := writer.Write([]byte("foobar"))
if err != nil {
t.Fatal(err)
}
if written != 6 {
t.Fatalf("Should have written 6 bytes, but wrote %v bytes", written)
}
// Make sure we Flush all the way ?
writer.Flush()
bw.Flush()
if len(buf.Bytes()) != 6 {
t.Fatalf("The buffer should contain 6 bytes ('foobar') but contains %v ('%v')", buf.Bytes(), string(buf.Bytes()))
}
// Reset the buffer
buf.Reset()
BufioWriter32KPool.Put(writer)
// Try to write something
if _, err = writer.Write([]byte("barfoo")); err != nil {
t.Fatal(err)
}
// If we now try to flush it, it should panic (the writer is nil)
// recover it
defer func() {
if r := recover(); r == nil {
t.Fatal("Trying to flush the writter should have 'paniced', did not.")
}
}()
writer.Flush()
}
type simpleWriterCloser struct {
io.Writer
closed bool
}
func (r *simpleWriterCloser) Close() error {
r.closed = true
return nil
}
func TestNewWriteCloserWrapperWithAWriteCloser(t *testing.T) {
buf := new(bytes.Buffer)
bw := bufio.NewWriter(buf)
sw := &simpleWriterCloser{
Writer: new(bytes.Buffer),
closed: false,
}
bw.Flush()
writer := BufioWriter32KPool.NewWriteCloserWrapper(bw, sw)
if writer == nil {
t.Fatalf("BufioReaderPool should not return a nil writer.")
}
written, err := writer.Write([]byte("foobar"))
if err != nil {
t.Fatal(err)
}
if written != 6 {
t.Fatalf("Should have written 6 bytes, but wrote %v bytes", written)
}
writer.Close()
if !sw.closed {
t.Fatalf("The ReaderCloser should have been closed, it is not.")
}
}

View file

@ -0,0 +1,22 @@
package random
import (
"math/rand"
"sync"
"testing"
)
// for go test -v -race
func TestConcurrency(t *testing.T) {
rnd := rand.New(NewSource())
var wg sync.WaitGroup
for i := 0; i < 10; i++ {
wg.Add(1)
go func() {
rnd.Int63()
wg.Done()
}()
}
wg.Wait()
}

View file

@ -0,0 +1,56 @@
package stringid
import (
"strings"
"testing"
)
func TestGenerateRandomID(t *testing.T) {
id := GenerateRandomID()
if len(id) != 64 {
t.Fatalf("Id returned is incorrect: %s", id)
}
}
func TestShortenId(t *testing.T) {
id := GenerateRandomID()
truncID := TruncateID(id)
if len(truncID) != 12 {
t.Fatalf("Id returned is incorrect: truncate on %s returned %s", id, truncID)
}
}
func TestShortenIdEmpty(t *testing.T) {
id := ""
truncID := TruncateID(id)
if len(truncID) > len(id) {
t.Fatalf("Id returned is incorrect: truncate on %s returned %s", id, truncID)
}
}
func TestShortenIdInvalid(t *testing.T) {
id := "1234"
truncID := TruncateID(id)
if len(truncID) != len(id) {
t.Fatalf("Id returned is incorrect: truncate on %s returned %s", id, truncID)
}
}
func TestIsShortIDNonHex(t *testing.T) {
id := "some non-hex value"
if IsShortID(id) {
t.Fatalf("%s is not a short ID", id)
}
}
func TestIsShortIDNotCorrectSize(t *testing.T) {
id := strings.Repeat("a", shortLen+1)
if IsShortID(id) {
t.Fatalf("%s is not a short ID", id)
}
id = strings.Repeat("a", shortLen-1)
if IsShortID(id) {
t.Fatalf("%s is not a short ID", id)
}
}

View file

@ -0,0 +1,87 @@
// Package stringutils provides helper functions for dealing with strings.
package stringutils
import (
"bytes"
"math/rand"
"strings"
"github.com/containers/storage/pkg/random"
)
// GenerateRandomAlphaOnlyString generates an alphabetical random string with length n.
func GenerateRandomAlphaOnlyString(n int) string {
// make a really long string
letters := []byte("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ")
b := make([]byte, n)
for i := range b {
b[i] = letters[random.Rand.Intn(len(letters))]
}
return string(b)
}
// GenerateRandomASCIIString generates an ASCII random string with length n.
func GenerateRandomASCIIString(n int) string {
chars := "abcdefghijklmnopqrstuvwxyz" +
"ABCDEFGHIJKLMNOPQRSTUVWXYZ" +
"~!@#$%^&*()-_+={}[]\\|<,>.?/\"';:` "
res := make([]byte, n)
for i := 0; i < n; i++ {
res[i] = chars[rand.Intn(len(chars))]
}
return string(res)
}
// Truncate truncates a string to maxlen.
func Truncate(s string, maxlen int) string {
if len(s) <= maxlen {
return s
}
return s[:maxlen]
}
// InSlice tests whether a string is contained in a slice of strings or not.
// Comparison is case insensitive
func InSlice(slice []string, s string) bool {
for _, ss := range slice {
if strings.ToLower(s) == strings.ToLower(ss) {
return true
}
}
return false
}
func quote(word string, buf *bytes.Buffer) {
// Bail out early for "simple" strings
if word != "" && !strings.ContainsAny(word, "\\'\"`${[|&;<>()~*?! \t\n") {
buf.WriteString(word)
return
}
buf.WriteString("'")
for i := 0; i < len(word); i++ {
b := word[i]
if b == '\'' {
// Replace literal ' with a close ', a \', and a open '
buf.WriteString("'\\''")
} else {
buf.WriteByte(b)
}
}
buf.WriteString("'")
}
// ShellQuoteArguments takes a list of strings and escapes them so they will be
// handled right when passed as arguments to a program via a shell
func ShellQuoteArguments(args []string) string {
var buf bytes.Buffer
for i, arg := range args {
if i != 0 {
buf.WriteByte(' ')
}
quote(arg, &buf)
}
return buf.String()
}

View file

@ -0,0 +1,105 @@
package stringutils
import "testing"
func testLengthHelper(generator func(int) string, t *testing.T) {
expectedLength := 20
s := generator(expectedLength)
if len(s) != expectedLength {
t.Fatalf("Length of %s was %d but expected length %d", s, len(s), expectedLength)
}
}
func testUniquenessHelper(generator func(int) string, t *testing.T) {
repeats := 25
set := make(map[string]struct{}, repeats)
for i := 0; i < repeats; i = i + 1 {
str := generator(64)
if len(str) != 64 {
t.Fatalf("Id returned is incorrect: %s", str)
}
if _, ok := set[str]; ok {
t.Fatalf("Random number is repeated")
}
set[str] = struct{}{}
}
}
func isASCII(s string) bool {
for _, c := range s {
if c > 127 {
return false
}
}
return true
}
func TestGenerateRandomAlphaOnlyStringLength(t *testing.T) {
testLengthHelper(GenerateRandomAlphaOnlyString, t)
}
func TestGenerateRandomAlphaOnlyStringUniqueness(t *testing.T) {
testUniquenessHelper(GenerateRandomAlphaOnlyString, t)
}
func TestGenerateRandomAsciiStringLength(t *testing.T) {
testLengthHelper(GenerateRandomASCIIString, t)
}
func TestGenerateRandomAsciiStringUniqueness(t *testing.T) {
testUniquenessHelper(GenerateRandomASCIIString, t)
}
func TestGenerateRandomAsciiStringIsAscii(t *testing.T) {
str := GenerateRandomASCIIString(64)
if !isASCII(str) {
t.Fatalf("%s contained non-ascii characters", str)
}
}
func TestTruncate(t *testing.T) {
str := "teststring"
newstr := Truncate(str, 4)
if newstr != "test" {
t.Fatalf("Expected test, got %s", newstr)
}
newstr = Truncate(str, 20)
if newstr != "teststring" {
t.Fatalf("Expected teststring, got %s", newstr)
}
}
func TestInSlice(t *testing.T) {
slice := []string{"test", "in", "slice"}
test := InSlice(slice, "test")
if !test {
t.Fatalf("Expected string test to be in slice")
}
test = InSlice(slice, "SLICE")
if !test {
t.Fatalf("Expected string SLICE to be in slice")
}
test = InSlice(slice, "notinslice")
if test {
t.Fatalf("Expected string notinslice not to be in slice")
}
}
func TestShellQuoteArgumentsEmpty(t *testing.T) {
actual := ShellQuoteArguments([]string{})
expected := ""
if actual != expected {
t.Fatalf("Expected an empty string")
}
}
func TestShellQuoteArguments(t *testing.T) {
simpleString := "simpleString"
complexString := "This is a 'more' complex $tring with some special char *"
actual := ShellQuoteArguments([]string{simpleString, complexString})
expected := "simpleString 'This is a '\\''more'\\'' complex $tring with some special char *'"
if actual != expected {
t.Fatalf("Expected \"%v\", got \"%v\"", expected, actual)
}
}

View file

@ -0,0 +1,94 @@
package system
import (
"io/ioutil"
"os"
"path/filepath"
"testing"
"time"
)
// prepareTempFile creates a temporary file in a temporary directory.
func prepareTempFile(t *testing.T) (string, string) {
dir, err := ioutil.TempDir("", "docker-system-test")
if err != nil {
t.Fatal(err)
}
file := filepath.Join(dir, "exist")
if err := ioutil.WriteFile(file, []byte("hello"), 0644); err != nil {
t.Fatal(err)
}
return file, dir
}
// TestChtimes tests Chtimes on a tempfile. Test only mTime, because aTime is OS dependent
func TestChtimes(t *testing.T) {
file, dir := prepareTempFile(t)
defer os.RemoveAll(dir)
beforeUnixEpochTime := time.Unix(0, 0).Add(-100 * time.Second)
unixEpochTime := time.Unix(0, 0)
afterUnixEpochTime := time.Unix(100, 0)
unixMaxTime := maxTime
// Test both aTime and mTime set to Unix Epoch
Chtimes(file, unixEpochTime, unixEpochTime)
f, err := os.Stat(file)
if err != nil {
t.Fatal(err)
}
if f.ModTime() != unixEpochTime {
t.Fatalf("Expected: %s, got: %s", unixEpochTime, f.ModTime())
}
// Test aTime before Unix Epoch and mTime set to Unix Epoch
Chtimes(file, beforeUnixEpochTime, unixEpochTime)
f, err = os.Stat(file)
if err != nil {
t.Fatal(err)
}
if f.ModTime() != unixEpochTime {
t.Fatalf("Expected: %s, got: %s", unixEpochTime, f.ModTime())
}
// Test aTime set to Unix Epoch and mTime before Unix Epoch
Chtimes(file, unixEpochTime, beforeUnixEpochTime)
f, err = os.Stat(file)
if err != nil {
t.Fatal(err)
}
if f.ModTime() != unixEpochTime {
t.Fatalf("Expected: %s, got: %s", unixEpochTime, f.ModTime())
}
// Test both aTime and mTime set to after Unix Epoch (valid time)
Chtimes(file, afterUnixEpochTime, afterUnixEpochTime)
f, err = os.Stat(file)
if err != nil {
t.Fatal(err)
}
if f.ModTime() != afterUnixEpochTime {
t.Fatalf("Expected: %s, got: %s", afterUnixEpochTime, f.ModTime())
}
// Test both aTime and mTime set to Unix max time
Chtimes(file, unixMaxTime, unixMaxTime)
f, err = os.Stat(file)
if err != nil {
t.Fatal(err)
}
if f.ModTime().Truncate(time.Second) != unixMaxTime.Truncate(time.Second) {
t.Fatalf("Expected: %s, got: %s", unixMaxTime.Truncate(time.Second), f.ModTime().Truncate(time.Second))
}
}

View file

@ -0,0 +1,91 @@
// +build !windows
package system
import (
"os"
"syscall"
"testing"
"time"
)
// TestChtimes tests Chtimes access time on a tempfile on Linux
func TestChtimesLinux(t *testing.T) {
file, dir := prepareTempFile(t)
defer os.RemoveAll(dir)
beforeUnixEpochTime := time.Unix(0, 0).Add(-100 * time.Second)
unixEpochTime := time.Unix(0, 0)
afterUnixEpochTime := time.Unix(100, 0)
unixMaxTime := maxTime
// Test both aTime and mTime set to Unix Epoch
Chtimes(file, unixEpochTime, unixEpochTime)
f, err := os.Stat(file)
if err != nil {
t.Fatal(err)
}
stat := f.Sys().(*syscall.Stat_t)
aTime := time.Unix(int64(stat.Atim.Sec), int64(stat.Atim.Nsec))
if aTime != unixEpochTime {
t.Fatalf("Expected: %s, got: %s", unixEpochTime, aTime)
}
// Test aTime before Unix Epoch and mTime set to Unix Epoch
Chtimes(file, beforeUnixEpochTime, unixEpochTime)
f, err = os.Stat(file)
if err != nil {
t.Fatal(err)
}
stat = f.Sys().(*syscall.Stat_t)
aTime = time.Unix(int64(stat.Atim.Sec), int64(stat.Atim.Nsec))
if aTime != unixEpochTime {
t.Fatalf("Expected: %s, got: %s", unixEpochTime, aTime)
}
// Test aTime set to Unix Epoch and mTime before Unix Epoch
Chtimes(file, unixEpochTime, beforeUnixEpochTime)
f, err = os.Stat(file)
if err != nil {
t.Fatal(err)
}
stat = f.Sys().(*syscall.Stat_t)
aTime = time.Unix(int64(stat.Atim.Sec), int64(stat.Atim.Nsec))
if aTime != unixEpochTime {
t.Fatalf("Expected: %s, got: %s", unixEpochTime, aTime)
}
// Test both aTime and mTime set to after Unix Epoch (valid time)
Chtimes(file, afterUnixEpochTime, afterUnixEpochTime)
f, err = os.Stat(file)
if err != nil {
t.Fatal(err)
}
stat = f.Sys().(*syscall.Stat_t)
aTime = time.Unix(int64(stat.Atim.Sec), int64(stat.Atim.Nsec))
if aTime != afterUnixEpochTime {
t.Fatalf("Expected: %s, got: %s", afterUnixEpochTime, aTime)
}
// Test both aTime and mTime set to Unix max time
Chtimes(file, unixMaxTime, unixMaxTime)
f, err = os.Stat(file)
if err != nil {
t.Fatal(err)
}
stat = f.Sys().(*syscall.Stat_t)
aTime = time.Unix(int64(stat.Atim.Sec), int64(stat.Atim.Nsec))
if aTime.Truncate(time.Second) != unixMaxTime.Truncate(time.Second) {
t.Fatalf("Expected: %s, got: %s", unixMaxTime.Truncate(time.Second), aTime.Truncate(time.Second))
}
}

View file

@ -0,0 +1,86 @@
// +build windows
package system
import (
"os"
"syscall"
"testing"
"time"
)
// TestChtimes tests Chtimes access time on a tempfile on Windows
func TestChtimesWindows(t *testing.T) {
file, dir := prepareTempFile(t)
defer os.RemoveAll(dir)
beforeUnixEpochTime := time.Unix(0, 0).Add(-100 * time.Second)
unixEpochTime := time.Unix(0, 0)
afterUnixEpochTime := time.Unix(100, 0)
unixMaxTime := maxTime
// Test both aTime and mTime set to Unix Epoch
Chtimes(file, unixEpochTime, unixEpochTime)
f, err := os.Stat(file)
if err != nil {
t.Fatal(err)
}
aTime := time.Unix(0, f.Sys().(*syscall.Win32FileAttributeData).LastAccessTime.Nanoseconds())
if aTime != unixEpochTime {
t.Fatalf("Expected: %s, got: %s", unixEpochTime, aTime)
}
// Test aTime before Unix Epoch and mTime set to Unix Epoch
Chtimes(file, beforeUnixEpochTime, unixEpochTime)
f, err = os.Stat(file)
if err != nil {
t.Fatal(err)
}
aTime = time.Unix(0, f.Sys().(*syscall.Win32FileAttributeData).LastAccessTime.Nanoseconds())
if aTime != unixEpochTime {
t.Fatalf("Expected: %s, got: %s", unixEpochTime, aTime)
}
// Test aTime set to Unix Epoch and mTime before Unix Epoch
Chtimes(file, unixEpochTime, beforeUnixEpochTime)
f, err = os.Stat(file)
if err != nil {
t.Fatal(err)
}
aTime = time.Unix(0, f.Sys().(*syscall.Win32FileAttributeData).LastAccessTime.Nanoseconds())
if aTime != unixEpochTime {
t.Fatalf("Expected: %s, got: %s", unixEpochTime, aTime)
}
// Test both aTime and mTime set to after Unix Epoch (valid time)
Chtimes(file, afterUnixEpochTime, afterUnixEpochTime)
f, err = os.Stat(file)
if err != nil {
t.Fatal(err)
}
aTime = time.Unix(0, f.Sys().(*syscall.Win32FileAttributeData).LastAccessTime.Nanoseconds())
if aTime != afterUnixEpochTime {
t.Fatalf("Expected: %s, got: %s", afterUnixEpochTime, aTime)
}
// Test both aTime and mTime set to Unix max time
Chtimes(file, unixMaxTime, unixMaxTime)
f, err = os.Stat(file)
if err != nil {
t.Fatal(err)
}
aTime = time.Unix(0, f.Sys().(*syscall.Win32FileAttributeData).LastAccessTime.Nanoseconds())
if aTime.Truncate(time.Second) != unixMaxTime.Truncate(time.Second) {
t.Fatalf("Expected: %s, got: %s", unixMaxTime.Truncate(time.Second), aTime.Truncate(time.Second))
}
}

View file

@ -0,0 +1,30 @@
// +build linux freebsd
package system
import (
"os"
"testing"
)
// TestLstat tests Lstat for existing and non existing files
func TestLstat(t *testing.T) {
file, invalid, _, dir := prepareFiles(t)
defer os.RemoveAll(dir)
statFile, err := Lstat(file)
if err != nil {
t.Fatal(err)
}
if statFile == nil {
t.Fatal("returned empty stat for existing file")
}
statInvalid, err := Lstat(invalid)
if err == nil {
t.Fatal("did not return error for non-existing file")
}
if statInvalid != nil {
t.Fatal("returned non-nil stat for non-existing file")
}
}

View file

@ -0,0 +1,40 @@
// +build linux freebsd
package system
import (
"strings"
"testing"
"github.com/docker/go-units"
)
// TestMemInfo tests parseMemInfo with a static meminfo string
func TestMemInfo(t *testing.T) {
const input = `
MemTotal: 1 kB
MemFree: 2 kB
SwapTotal: 3 kB
SwapFree: 4 kB
Malformed1:
Malformed2: 1
Malformed3: 2 MB
Malformed4: X kB
`
meminfo, err := parseMemInfo(strings.NewReader(input))
if err != nil {
t.Fatal(err)
}
if meminfo.MemTotal != 1*units.KiB {
t.Fatalf("Unexpected MemTotal: %d", meminfo.MemTotal)
}
if meminfo.MemFree != 2*units.KiB {
t.Fatalf("Unexpected MemFree: %d", meminfo.MemFree)
}
if meminfo.SwapTotal != 3*units.KiB {
t.Fatalf("Unexpected SwapTotal: %d", meminfo.SwapTotal)
}
if meminfo.SwapFree != 4*units.KiB {
t.Fatalf("Unexpected SwapFree: %d", meminfo.SwapFree)
}
}

View file

@ -0,0 +1,78 @@
// +build windows
package system
import "testing"
// TestCheckSystemDriveAndRemoveDriveLetter tests CheckSystemDriveAndRemoveDriveLetter
func TestCheckSystemDriveAndRemoveDriveLetter(t *testing.T) {
// Fails if not C drive.
path, err := CheckSystemDriveAndRemoveDriveLetter(`d:\`)
if err == nil || (err != nil && err.Error() != "The specified path is not on the system drive (C:)") {
t.Fatalf("Expected error for d:")
}
// Single character is unchanged
if path, err = CheckSystemDriveAndRemoveDriveLetter("z"); err != nil {
t.Fatalf("Single character should pass")
}
if path != "z" {
t.Fatalf("Single character should be unchanged")
}
// Two characters without colon is unchanged
if path, err = CheckSystemDriveAndRemoveDriveLetter("AB"); err != nil {
t.Fatalf("2 characters without colon should pass")
}
if path != "AB" {
t.Fatalf("2 characters without colon should be unchanged")
}
// Abs path without drive letter
if path, err = CheckSystemDriveAndRemoveDriveLetter(`\l`); err != nil {
t.Fatalf("abs path no drive letter should pass")
}
if path != `\l` {
t.Fatalf("abs path without drive letter should be unchanged")
}
// Abs path without drive letter, linux style
if path, err = CheckSystemDriveAndRemoveDriveLetter(`/l`); err != nil {
t.Fatalf("abs path no drive letter linux style should pass")
}
if path != `\l` {
t.Fatalf("abs path without drive letter linux failed %s", path)
}
// Drive-colon should be stripped
if path, err = CheckSystemDriveAndRemoveDriveLetter(`c:\`); err != nil {
t.Fatalf("An absolute path should pass")
}
if path != `\` {
t.Fatalf(`An absolute path should have been shortened to \ %s`, path)
}
// Verify with a linux-style path
if path, err = CheckSystemDriveAndRemoveDriveLetter(`c:/`); err != nil {
t.Fatalf("An absolute path should pass")
}
if path != `\` {
t.Fatalf(`A linux style absolute path should have been shortened to \ %s`, path)
}
// Failure on c:
if path, err = CheckSystemDriveAndRemoveDriveLetter(`c:`); err == nil {
t.Fatalf("c: should fail")
}
if err.Error() != `No relative path specified in "c:"` {
t.Fatalf(path, err)
}
// Failure on d:
if path, err = CheckSystemDriveAndRemoveDriveLetter(`d:`); err == nil {
t.Fatalf("c: should fail")
}
if err.Error() != `No relative path specified in "d:"` {
t.Fatalf(path, err)
}
}

View file

@ -0,0 +1,39 @@
// +build linux freebsd
package system
import (
"os"
"syscall"
"testing"
)
// TestFromStatT tests fromStatT for a tempfile
func TestFromStatT(t *testing.T) {
file, _, _, dir := prepareFiles(t)
defer os.RemoveAll(dir)
stat := &syscall.Stat_t{}
err := syscall.Lstat(file, stat)
s, err := fromStatT(stat)
if err != nil {
t.Fatal(err)
}
if stat.Mode != s.Mode() {
t.Fatal("got invalid mode")
}
if stat.Uid != s.UID() {
t.Fatal("got invalid uid")
}
if stat.Gid != s.GID() {
t.Fatal("got invalid gid")
}
if stat.Rdev != s.Rdev() {
t.Fatal("got invalid rdev")
}
if stat.Mtim != s.Mtim() {
t.Fatal("got invalid mtim")
}
}

View file

@ -0,0 +1,9 @@
package system
import "testing"
func TestHasWin32KSupport(t *testing.T) {
s := HasWin32KSupport() // make sure this doesn't panic
t.Logf("win32k: %v", s) // will be different on different platforms -- informative only
}

View file

@ -0,0 +1,68 @@
// +build linux freebsd
package system
import (
"io/ioutil"
"os"
"path/filepath"
"syscall"
"testing"
)
// prepareFiles creates files for testing in the temp directory
func prepareFiles(t *testing.T) (string, string, string, string) {
dir, err := ioutil.TempDir("", "docker-system-test")
if err != nil {
t.Fatal(err)
}
file := filepath.Join(dir, "exist")
if err := ioutil.WriteFile(file, []byte("hello"), 0644); err != nil {
t.Fatal(err)
}
invalid := filepath.Join(dir, "doesnt-exist")
symlink := filepath.Join(dir, "symlink")
if err := os.Symlink(file, symlink); err != nil {
t.Fatal(err)
}
return file, invalid, symlink, dir
}
func TestLUtimesNano(t *testing.T) {
file, invalid, symlink, dir := prepareFiles(t)
defer os.RemoveAll(dir)
before, err := os.Stat(file)
if err != nil {
t.Fatal(err)
}
ts := []syscall.Timespec{{0, 0}, {0, 0}}
if err := LUtimesNano(symlink, ts); err != nil {
t.Fatal(err)
}
symlinkInfo, err := os.Lstat(symlink)
if err != nil {
t.Fatal(err)
}
if before.ModTime().Unix() == symlinkInfo.ModTime().Unix() {
t.Fatal("The modification time of the symlink should be different")
}
fileInfo, err := os.Stat(file)
if err != nil {
t.Fatal(err)
}
if before.ModTime().Unix() != fileInfo.ModTime().Unix() {
t.Fatal("The modification time of the file should be same")
}
if err := LUtimesNano(invalid, ts); err == nil {
t.Fatal("Doesn't return an error on a non-existing file")
}
}

View file

@ -0,0 +1,70 @@
// Package assert contains functions for making assertions in unit tests
package assert
import (
"fmt"
"path/filepath"
"runtime"
"strings"
)
// TestingT is an interface which defines the methods of testing.T that are
// required by this package
type TestingT interface {
Fatalf(string, ...interface{})
}
// Equal compare the actual value to the expected value and fails the test if
// they are not equal.
func Equal(t TestingT, actual, expected interface{}) {
if expected != actual {
fatal(t, fmt.Sprintf("Expected '%v' (%T) got '%v' (%T)", expected, expected, actual, actual))
}
}
//EqualStringSlice compares two slices and fails the test if they do not contain
// the same items.
func EqualStringSlice(t TestingT, actual, expected []string) {
if len(actual) != len(expected) {
t.Fatalf("Expected (length %d): %q\nActual (length %d): %q",
len(expected), expected, len(actual), actual)
}
for i, item := range actual {
if item != expected[i] {
t.Fatalf("Slices differ at element %d, expected %q got %q",
i, expected[i], item)
}
}
}
// NilError asserts that the error is nil, otherwise it fails the test.
func NilError(t TestingT, err error) {
if err != nil {
fatal(t, fmt.Sprintf("Expected no error, got: %s", err.Error()))
}
}
// Error asserts that error is not nil, and contains the expected text,
// otherwise it fails the test.
func Error(t TestingT, err error, contains string) {
if err == nil {
fatal(t, "Expected an error, but error was nil")
}
if !strings.Contains(err.Error(), contains) {
fatal(t, fmt.Sprintf("Expected error to contain '%s', got '%s'", contains, err.Error()))
}
}
// Contains asserts that the string contains a substring, otherwise it fails the
// test.
func Contains(t TestingT, actual, contains string) {
if !strings.Contains(actual, contains) {
fatal(t, fmt.Sprintf("Expected '%s' to contain '%s'", actual, contains))
}
}
func fatal(t TestingT, msg string) {
_, file, line, _ := runtime.Caller(2)
t.Fatalf("%s:%d: %s", filepath.Base(file), line, msg)
}

View file

@ -0,0 +1 @@
package testutil