forked from TrueCloudLab/distribution
a3481c5f1c
This only works for a specific whitelist of error types, which is currently all errors in the storagedriver package. Also improves storagedriver tests to enforce proper error types are returned
406 lines
13 KiB
Go
406 lines
13 KiB
Go
package testsuites
|
|
|
|
import (
|
|
"bytes"
|
|
"io/ioutil"
|
|
"math/rand"
|
|
"path"
|
|
"sort"
|
|
"testing"
|
|
|
|
"github.com/docker/docker-registry/storagedriver"
|
|
"github.com/docker/docker-registry/storagedriver/ipc"
|
|
|
|
"gopkg.in/check.v1"
|
|
)
|
|
|
|
// Test hooks up gocheck into the "go test" runner.
|
|
func Test(t *testing.T) { check.TestingT(t) }
|
|
|
|
// RegisterInProcessSuite registers an in-process storage driver test suite with the go test runner
|
|
func RegisterInProcessSuite(driverConstructor DriverConstructor, skipCheck SkipCheck) {
|
|
check.Suite(&DriverSuite{
|
|
Constructor: driverConstructor,
|
|
SkipCheck: skipCheck,
|
|
})
|
|
}
|
|
|
|
// RegisterIPCSuite registers a storage driver test suite which runs the named driver as a child
|
|
// process with the given parameters
|
|
func RegisterIPCSuite(driverName string, ipcParams map[string]string, skipCheck SkipCheck) {
|
|
suite := &DriverSuite{
|
|
Constructor: func() (storagedriver.StorageDriver, error) {
|
|
d, err := ipc.NewDriverClient(driverName, ipcParams)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
err = d.Start()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return d, nil
|
|
},
|
|
SkipCheck: skipCheck,
|
|
}
|
|
suite.Teardown = func() error {
|
|
if suite.StorageDriver == nil {
|
|
return nil
|
|
}
|
|
|
|
driverClient := suite.StorageDriver.(*ipc.StorageDriverClient)
|
|
return driverClient.Stop()
|
|
}
|
|
check.Suite(suite)
|
|
}
|
|
|
|
// SkipCheck is a function used to determine if a test suite should be skipped
|
|
// If a SkipCheck returns a non-empty skip reason, the suite is skipped with the given reason
|
|
type SkipCheck func() (reason string)
|
|
|
|
// NeverSkip is a default SkipCheck which never skips the suite
|
|
var NeverSkip SkipCheck = func() string { return "" }
|
|
|
|
// DriverConstructor is a function which returns a new storagedriver.StorageDriver
|
|
type DriverConstructor func() (storagedriver.StorageDriver, error)
|
|
|
|
// DriverTeardown is a function which cleans up a suite's storagedriver.StorageDriver
|
|
type DriverTeardown func() error
|
|
|
|
// DriverSuite is a gocheck test suite designed to test a storagedriver.StorageDriver
|
|
// The intended way to create a DriverSuite is with RegisterInProcessSuite or RegisterIPCSuite
|
|
type DriverSuite struct {
|
|
Constructor DriverConstructor
|
|
Teardown DriverTeardown
|
|
SkipCheck
|
|
storagedriver.StorageDriver
|
|
}
|
|
|
|
// SetUpSuite sets up the gocheck test suite
|
|
func (suite *DriverSuite) SetUpSuite(c *check.C) {
|
|
if reason := suite.SkipCheck(); reason != "" {
|
|
c.Skip(reason)
|
|
}
|
|
d, err := suite.Constructor()
|
|
c.Assert(err, check.IsNil)
|
|
suite.StorageDriver = d
|
|
}
|
|
|
|
// TearDownSuite tears down the gocheck test suite
|
|
func (suite *DriverSuite) TearDownSuite(c *check.C) {
|
|
if suite.Teardown != nil {
|
|
err := suite.Teardown()
|
|
c.Assert(err, check.IsNil)
|
|
}
|
|
}
|
|
|
|
// TestWriteRead1 tests a simple write-read workflow
|
|
func (suite *DriverSuite) TestWriteRead1(c *check.C) {
|
|
filename := randomString(32)
|
|
contents := []byte("a")
|
|
suite.writeReadCompare(c, filename, contents, contents)
|
|
}
|
|
|
|
// TestWriteRead2 tests a simple write-read workflow with unicode data
|
|
func (suite *DriverSuite) TestWriteRead2(c *check.C) {
|
|
filename := randomString(32)
|
|
contents := []byte("\xc3\x9f")
|
|
suite.writeReadCompare(c, filename, contents, contents)
|
|
}
|
|
|
|
// TestWriteRead3 tests a simple write-read workflow with a small string
|
|
func (suite *DriverSuite) TestWriteRead3(c *check.C) {
|
|
filename := randomString(32)
|
|
contents := []byte(randomString(32))
|
|
suite.writeReadCompare(c, filename, contents, contents)
|
|
}
|
|
|
|
// TestWriteRead4 tests a simple write-read workflow with 1MB of data
|
|
func (suite *DriverSuite) TestWriteRead4(c *check.C) {
|
|
filename := randomString(32)
|
|
contents := []byte(randomString(1024 * 1024))
|
|
suite.writeReadCompare(c, filename, contents, contents)
|
|
}
|
|
|
|
// TestReadNonexistent tests reading content from an empty path
|
|
func (suite *DriverSuite) TestReadNonexistent(c *check.C) {
|
|
filename := randomString(32)
|
|
_, err := suite.StorageDriver.GetContent(filename)
|
|
c.Assert(err, check.NotNil)
|
|
c.Assert(err, check.FitsTypeOf, storagedriver.PathNotFoundError{})
|
|
}
|
|
|
|
// TestWriteReadStreams1 tests a simple write-read streaming workflow
|
|
func (suite *DriverSuite) TestWriteReadStreams1(c *check.C) {
|
|
filename := randomString(32)
|
|
contents := []byte("a")
|
|
suite.writeReadCompareStreams(c, filename, contents, contents)
|
|
}
|
|
|
|
// TestWriteReadStreams2 tests a simple write-read streaming workflow with
|
|
// unicode data
|
|
func (suite *DriverSuite) TestWriteReadStreams2(c *check.C) {
|
|
filename := randomString(32)
|
|
contents := []byte("\xc3\x9f")
|
|
suite.writeReadCompareStreams(c, filename, contents, contents)
|
|
}
|
|
|
|
// TestWriteReadStreams3 tests a simple write-read streaming workflow with a
|
|
// small amount of data
|
|
func (suite *DriverSuite) TestWriteReadStreams3(c *check.C) {
|
|
filename := randomString(32)
|
|
contents := []byte(randomString(32))
|
|
suite.writeReadCompareStreams(c, filename, contents, contents)
|
|
}
|
|
|
|
// TestWriteReadStreams4 tests a simple write-read streaming workflow with 1MB
|
|
// of data
|
|
func (suite *DriverSuite) TestWriteReadStreams4(c *check.C) {
|
|
filename := randomString(32)
|
|
contents := []byte(randomString(1024 * 1024))
|
|
suite.writeReadCompareStreams(c, filename, contents, contents)
|
|
}
|
|
|
|
// TestContinueStreamAppend tests that a stream write can be appended to without
|
|
// corrupting the data
|
|
func (suite *DriverSuite) TestContinueStreamAppend(c *check.C) {
|
|
filename := randomString(32)
|
|
defer suite.StorageDriver.Delete(filename)
|
|
|
|
chunkSize := uint64(10 * 1024 * 1024)
|
|
|
|
contentsChunk1 := []byte(randomString(chunkSize))
|
|
contentsChunk2 := []byte(randomString(chunkSize))
|
|
contentsChunk3 := []byte(randomString(chunkSize))
|
|
|
|
fullContents := append(append(contentsChunk1, contentsChunk2...), contentsChunk3...)
|
|
|
|
err := suite.StorageDriver.WriteStream(filename, 0, 3*chunkSize, ioutil.NopCloser(bytes.NewReader(contentsChunk1)))
|
|
c.Assert(err, check.IsNil)
|
|
|
|
offset, err := suite.StorageDriver.CurrentSize(filename)
|
|
c.Assert(err, check.IsNil)
|
|
if offset > chunkSize {
|
|
c.Fatalf("Offset too large, %d > %d", offset, chunkSize)
|
|
}
|
|
err = suite.StorageDriver.WriteStream(filename, offset, 3*chunkSize, ioutil.NopCloser(bytes.NewReader(fullContents[offset:2*chunkSize])))
|
|
c.Assert(err, check.IsNil)
|
|
|
|
offset, err = suite.StorageDriver.CurrentSize(filename)
|
|
c.Assert(err, check.IsNil)
|
|
if offset > 2*chunkSize {
|
|
c.Fatalf("Offset too large, %d > %d", offset, 2*chunkSize)
|
|
}
|
|
|
|
err = suite.StorageDriver.WriteStream(filename, offset, 3*chunkSize, ioutil.NopCloser(bytes.NewReader(fullContents[offset:])))
|
|
c.Assert(err, check.IsNil)
|
|
|
|
received, err := suite.StorageDriver.GetContent(filename)
|
|
c.Assert(err, check.IsNil)
|
|
c.Assert(received, check.DeepEquals, fullContents)
|
|
}
|
|
|
|
// TestReadStreamWithOffset tests that the appropriate data is streamed when
|
|
// reading with a given offset
|
|
func (suite *DriverSuite) TestReadStreamWithOffset(c *check.C) {
|
|
filename := randomString(32)
|
|
defer suite.StorageDriver.Delete(filename)
|
|
|
|
chunkSize := uint64(32)
|
|
|
|
contentsChunk1 := []byte(randomString(chunkSize))
|
|
contentsChunk2 := []byte(randomString(chunkSize))
|
|
contentsChunk3 := []byte(randomString(chunkSize))
|
|
|
|
err := suite.StorageDriver.PutContent(filename, append(append(contentsChunk1, contentsChunk2...), contentsChunk3...))
|
|
c.Assert(err, check.IsNil)
|
|
|
|
reader, err := suite.StorageDriver.ReadStream(filename, 0)
|
|
c.Assert(err, check.IsNil)
|
|
defer reader.Close()
|
|
|
|
readContents, err := ioutil.ReadAll(reader)
|
|
c.Assert(err, check.IsNil)
|
|
|
|
c.Assert(readContents, check.DeepEquals, append(append(contentsChunk1, contentsChunk2...), contentsChunk3...))
|
|
|
|
reader, err = suite.StorageDriver.ReadStream(filename, chunkSize)
|
|
c.Assert(err, check.IsNil)
|
|
defer reader.Close()
|
|
|
|
readContents, err = ioutil.ReadAll(reader)
|
|
c.Assert(err, check.IsNil)
|
|
|
|
c.Assert(readContents, check.DeepEquals, append(contentsChunk2, contentsChunk3...))
|
|
|
|
reader, err = suite.StorageDriver.ReadStream(filename, chunkSize*2)
|
|
c.Assert(err, check.IsNil)
|
|
defer reader.Close()
|
|
|
|
readContents, err = ioutil.ReadAll(reader)
|
|
c.Assert(err, check.IsNil)
|
|
|
|
c.Assert(readContents, check.DeepEquals, contentsChunk3)
|
|
}
|
|
|
|
// TestReadNonexistentStream tests that reading a stream for a nonexistent path
|
|
// fails
|
|
func (suite *DriverSuite) TestReadNonexistentStream(c *check.C) {
|
|
filename := randomString(32)
|
|
_, err := suite.StorageDriver.ReadStream(filename, 0)
|
|
c.Assert(err, check.NotNil)
|
|
c.Assert(err, check.FitsTypeOf, storagedriver.PathNotFoundError{})
|
|
}
|
|
|
|
// TestList checks the returned list of keys after populating a directory tree
|
|
func (suite *DriverSuite) TestList(c *check.C) {
|
|
rootDirectory := randomString(uint64(8 + rand.Intn(8)))
|
|
defer suite.StorageDriver.Delete(rootDirectory)
|
|
|
|
parentDirectory := rootDirectory + "/" + randomString(uint64(8+rand.Intn(8)))
|
|
childFiles := make([]string, 50)
|
|
for i := 0; i < len(childFiles); i++ {
|
|
childFile := parentDirectory + "/" + randomString(uint64(8+rand.Intn(8)))
|
|
childFiles[i] = childFile
|
|
err := suite.StorageDriver.PutContent(childFile, []byte(randomString(32)))
|
|
c.Assert(err, check.IsNil)
|
|
}
|
|
sort.Strings(childFiles)
|
|
|
|
keys, err := suite.StorageDriver.List(rootDirectory)
|
|
c.Assert(err, check.IsNil)
|
|
c.Assert(keys, check.DeepEquals, []string{parentDirectory})
|
|
|
|
keys, err = suite.StorageDriver.List(parentDirectory)
|
|
c.Assert(err, check.IsNil)
|
|
|
|
sort.Strings(keys)
|
|
c.Assert(keys, check.DeepEquals, childFiles)
|
|
}
|
|
|
|
// TestMove checks that a moved object no longer exists at the source path and
|
|
// does exist at the destination
|
|
func (suite *DriverSuite) TestMove(c *check.C) {
|
|
contents := []byte(randomString(32))
|
|
sourcePath := randomString(32)
|
|
destPath := randomString(32)
|
|
|
|
defer suite.StorageDriver.Delete(sourcePath)
|
|
defer suite.StorageDriver.Delete(destPath)
|
|
|
|
err := suite.StorageDriver.PutContent(sourcePath, contents)
|
|
c.Assert(err, check.IsNil)
|
|
|
|
err = suite.StorageDriver.Move(sourcePath, destPath)
|
|
c.Assert(err, check.IsNil)
|
|
|
|
received, err := suite.StorageDriver.GetContent(destPath)
|
|
c.Assert(err, check.IsNil)
|
|
c.Assert(received, check.DeepEquals, contents)
|
|
|
|
_, err = suite.StorageDriver.GetContent(sourcePath)
|
|
c.Assert(err, check.NotNil)
|
|
c.Assert(err, check.FitsTypeOf, storagedriver.PathNotFoundError{})
|
|
}
|
|
|
|
// TestMoveNonexistent checks that moving a nonexistent key fails
|
|
func (suite *DriverSuite) TestMoveNonexistent(c *check.C) {
|
|
sourcePath := randomString(32)
|
|
destPath := randomString(32)
|
|
|
|
err := suite.StorageDriver.Move(sourcePath, destPath)
|
|
c.Assert(err, check.NotNil)
|
|
c.Assert(err, check.FitsTypeOf, storagedriver.PathNotFoundError{})
|
|
}
|
|
|
|
// TestDelete checks that the delete operation removes data from the storage
|
|
// driver
|
|
func (suite *DriverSuite) TestDelete(c *check.C) {
|
|
filename := randomString(32)
|
|
contents := []byte(randomString(32))
|
|
|
|
defer suite.StorageDriver.Delete(filename)
|
|
|
|
err := suite.StorageDriver.PutContent(filename, contents)
|
|
c.Assert(err, check.IsNil)
|
|
|
|
err = suite.StorageDriver.Delete(filename)
|
|
c.Assert(err, check.IsNil)
|
|
|
|
_, err = suite.StorageDriver.GetContent(filename)
|
|
c.Assert(err, check.NotNil)
|
|
c.Assert(err, check.FitsTypeOf, storagedriver.PathNotFoundError{})
|
|
}
|
|
|
|
// TestDeleteNonexistent checks that removing a nonexistent key fails
|
|
func (suite *DriverSuite) TestDeleteNonexistent(c *check.C) {
|
|
filename := randomString(32)
|
|
err := suite.StorageDriver.Delete(filename)
|
|
c.Assert(err, check.NotNil)
|
|
c.Assert(err, check.FitsTypeOf, storagedriver.PathNotFoundError{})
|
|
}
|
|
|
|
// TestDeleteFolder checks that deleting a folder removes all child elements
|
|
func (suite *DriverSuite) TestDeleteFolder(c *check.C) {
|
|
dirname := randomString(32)
|
|
filename1 := randomString(32)
|
|
filename2 := randomString(32)
|
|
contents := []byte(randomString(32))
|
|
|
|
defer suite.StorageDriver.Delete(path.Join(dirname, filename1))
|
|
defer suite.StorageDriver.Delete(path.Join(dirname, filename2))
|
|
|
|
err := suite.StorageDriver.PutContent(path.Join(dirname, filename1), contents)
|
|
c.Assert(err, check.IsNil)
|
|
|
|
err = suite.StorageDriver.PutContent(path.Join(dirname, filename2), contents)
|
|
c.Assert(err, check.IsNil)
|
|
|
|
err = suite.StorageDriver.Delete(dirname)
|
|
c.Assert(err, check.IsNil)
|
|
|
|
_, err = suite.StorageDriver.GetContent(path.Join(dirname, filename1))
|
|
c.Assert(err, check.NotNil)
|
|
c.Assert(err, check.FitsTypeOf, storagedriver.PathNotFoundError{})
|
|
|
|
_, err = suite.StorageDriver.GetContent(path.Join(dirname, filename2))
|
|
c.Assert(err, check.NotNil)
|
|
c.Assert(err, check.FitsTypeOf, storagedriver.PathNotFoundError{})
|
|
}
|
|
|
|
func (suite *DriverSuite) writeReadCompare(c *check.C, filename string, contents, expected []byte) {
|
|
defer suite.StorageDriver.Delete(filename)
|
|
|
|
err := suite.StorageDriver.PutContent(filename, contents)
|
|
c.Assert(err, check.IsNil)
|
|
|
|
readContents, err := suite.StorageDriver.GetContent(filename)
|
|
c.Assert(err, check.IsNil)
|
|
|
|
c.Assert(readContents, check.DeepEquals, contents)
|
|
}
|
|
|
|
func (suite *DriverSuite) writeReadCompareStreams(c *check.C, filename string, contents, expected []byte) {
|
|
defer suite.StorageDriver.Delete(filename)
|
|
|
|
err := suite.StorageDriver.WriteStream(filename, 0, uint64(len(contents)), ioutil.NopCloser(bytes.NewReader(contents)))
|
|
c.Assert(err, check.IsNil)
|
|
|
|
reader, err := suite.StorageDriver.ReadStream(filename, 0)
|
|
c.Assert(err, check.IsNil)
|
|
defer reader.Close()
|
|
|
|
readContents, err := ioutil.ReadAll(reader)
|
|
c.Assert(err, check.IsNil)
|
|
|
|
c.Assert(readContents, check.DeepEquals, contents)
|
|
}
|
|
|
|
var pathChars = []byte("abcdefghijklmnopqrstuvwxyz")
|
|
|
|
func randomString(length uint64) string {
|
|
b := make([]byte, length)
|
|
for i := range b {
|
|
b[i] = pathChars[rand.Intn(len(pathChars))]
|
|
}
|
|
return string(b)
|
|
}
|