Files
docker-cli/components/engine/daemon/graphdriver/copy/copy_test.go
Sargun Dhillon 20a2865e53 Fix setting mtimes on directories
Previously, the code would set the mtime on the directories before
creating files in the directory itself. This was problematic
because it resulted in the mtimes on the directories being
incorrectly set. This change makes it so that the mtime is
set only _after_ all of the files have been created.

Signed-off-by: Sargun Dhillon <sargun@sargun.me>
Upstream-commit: 77a2bc3e5bbc9be3fe166ed8321b7cd04e7bd097
Component: engine
2017-12-01 09:12:43 -08:00

180 lines
5.1 KiB
Go

// +build linux
package copy
import (
"fmt"
"io/ioutil"
"math/rand"
"os"
"path/filepath"
"syscall"
"testing"
"time"
"github.com/docker/docker/pkg/parsers/kernel"
"github.com/docker/docker/pkg/system"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"golang.org/x/sys/unix"
)
func TestIsCopyFileRangeSyscallAvailable(t *testing.T) {
// Verifies:
// 1. That copyFileRangeEnabled is being set to true when copy_file_range syscall is available
// 2. That isCopyFileRangeSyscallAvailable() works on "new" kernels
v, err := kernel.GetKernelVersion()
require.NoError(t, err)
copyWithFileRange := true
copyWithFileClone := false
doCopyTest(t, &copyWithFileRange, &copyWithFileClone)
if kernel.CompareKernelVersion(*v, kernel.VersionInfo{Kernel: 4, Major: 5, Minor: 0}) < 0 {
assert.False(t, copyWithFileRange)
} else {
assert.True(t, copyWithFileRange)
}
}
func TestCopy(t *testing.T) {
copyWithFileRange := true
copyWithFileClone := true
doCopyTest(t, &copyWithFileRange, &copyWithFileClone)
}
func TestCopyWithoutRange(t *testing.T) {
copyWithFileRange := false
copyWithFileClone := false
doCopyTest(t, &copyWithFileRange, &copyWithFileClone)
}
func TestCopyDir(t *testing.T) {
srcDir, err := ioutil.TempDir("", "srcDir")
require.NoError(t, err)
populateSrcDir(t, srcDir, 3)
dstDir, err := ioutil.TempDir("", "testdst")
require.NoError(t, err)
defer os.RemoveAll(dstDir)
assert.NoError(t, DirCopy(srcDir, dstDir, Content, false))
require.NoError(t, filepath.Walk(srcDir, func(srcPath string, f os.FileInfo, err error) error {
if err != nil {
return err
}
// Rebase path
relPath, err := filepath.Rel(srcDir, srcPath)
require.NoError(t, err)
if relPath == "." {
return nil
}
dstPath := filepath.Join(dstDir, relPath)
require.NoError(t, err)
// If we add non-regular dirs and files to the test
// then we need to add more checks here.
dstFileInfo, err := os.Lstat(dstPath)
require.NoError(t, err)
srcFileSys := f.Sys().(*syscall.Stat_t)
dstFileSys := dstFileInfo.Sys().(*syscall.Stat_t)
t.Log(relPath)
if srcFileSys.Dev == dstFileSys.Dev {
assert.NotEqual(t, srcFileSys.Ino, dstFileSys.Ino)
}
// Todo: check size, and ctim is not equal
/// on filesystems that have granular ctimes
assert.Equal(t, srcFileSys.Mode, dstFileSys.Mode)
assert.Equal(t, srcFileSys.Uid, dstFileSys.Uid)
assert.Equal(t, srcFileSys.Gid, dstFileSys.Gid)
assert.Equal(t, srcFileSys.Mtim, dstFileSys.Mtim)
return nil
}))
}
func randomMode(baseMode int) os.FileMode {
for i := 0; i < 7; i++ {
baseMode = baseMode | (1&rand.Intn(2))<<uint(i)
}
return os.FileMode(baseMode)
}
func populateSrcDir(t *testing.T, srcDir string, remainingDepth int) {
if remainingDepth == 0 {
return
}
aTime := time.Unix(rand.Int63(), 0)
mTime := time.Unix(rand.Int63(), 0)
for i := 0; i < 10; i++ {
dirName := filepath.Join(srcDir, fmt.Sprintf("srcdir-%d", i))
// Owner all bits set
require.NoError(t, os.Mkdir(dirName, randomMode(0700)))
populateSrcDir(t, dirName, remainingDepth-1)
require.NoError(t, system.Chtimes(dirName, aTime, mTime))
}
for i := 0; i < 10; i++ {
fileName := filepath.Join(srcDir, fmt.Sprintf("srcfile-%d", i))
// Owner read bit set
require.NoError(t, ioutil.WriteFile(fileName, []byte{}, randomMode(0400)))
require.NoError(t, system.Chtimes(fileName, aTime, mTime))
}
}
func doCopyTest(t *testing.T, copyWithFileRange, copyWithFileClone *bool) {
dir, err := ioutil.TempDir("", "docker-copy-check")
require.NoError(t, err)
defer os.RemoveAll(dir)
srcFilename := filepath.Join(dir, "srcFilename")
dstFilename := filepath.Join(dir, "dstilename")
r := rand.New(rand.NewSource(0))
buf := make([]byte, 1024)
_, err = r.Read(buf)
require.NoError(t, err)
require.NoError(t, ioutil.WriteFile(srcFilename, buf, 0777))
fileinfo, err := os.Stat(srcFilename)
require.NoError(t, err)
require.NoError(t, copyRegular(srcFilename, dstFilename, fileinfo, copyWithFileRange, copyWithFileClone))
readBuf, err := ioutil.ReadFile(dstFilename)
require.NoError(t, err)
assert.Equal(t, buf, readBuf)
}
func TestCopyHardlink(t *testing.T) {
var srcFile1FileInfo, srcFile2FileInfo, dstFile1FileInfo, dstFile2FileInfo unix.Stat_t
srcDir, err := ioutil.TempDir("", "srcDir")
require.NoError(t, err)
defer os.RemoveAll(srcDir)
dstDir, err := ioutil.TempDir("", "dstDir")
require.NoError(t, err)
defer os.RemoveAll(dstDir)
srcFile1 := filepath.Join(srcDir, "file1")
srcFile2 := filepath.Join(srcDir, "file2")
dstFile1 := filepath.Join(dstDir, "file1")
dstFile2 := filepath.Join(dstDir, "file2")
require.NoError(t, ioutil.WriteFile(srcFile1, []byte{}, 0777))
require.NoError(t, os.Link(srcFile1, srcFile2))
assert.NoError(t, DirCopy(srcDir, dstDir, Content, false))
require.NoError(t, unix.Stat(srcFile1, &srcFile1FileInfo))
require.NoError(t, unix.Stat(srcFile2, &srcFile2FileInfo))
require.Equal(t, srcFile1FileInfo.Ino, srcFile2FileInfo.Ino)
require.NoError(t, unix.Stat(dstFile1, &dstFile1FileInfo))
require.NoError(t, unix.Stat(dstFile2, &dstFile2FileInfo))
assert.Equal(t, dstFile1FileInfo.Ino, dstFile2FileInfo.Ino)
}