github.com/rclone/rclone@v1.66.1-0.20240517100346-7b89735ae726/backend/swift/swift_test.go (about)

     1  // Test Swift filesystem interface
     2  package swift
     3  
     4  import (
     5  	"bytes"
     6  	"context"
     7  	"errors"
     8  	"io"
     9  	"testing"
    10  
    11  	"github.com/ncw/swift/v2"
    12  	"github.com/rclone/rclone/fs"
    13  	"github.com/rclone/rclone/fs/hash"
    14  	"github.com/rclone/rclone/fs/object"
    15  	"github.com/rclone/rclone/fstest"
    16  	"github.com/rclone/rclone/fstest/fstests"
    17  	"github.com/rclone/rclone/lib/random"
    18  	"github.com/rclone/rclone/lib/readers"
    19  	"github.com/stretchr/testify/assert"
    20  	"github.com/stretchr/testify/require"
    21  )
    22  
    23  // TestIntegration runs integration tests against the remote
    24  func TestIntegration(t *testing.T) {
    25  	fstests.Run(t, &fstests.Opt{
    26  		RemoteName: "TestSwiftAIO:",
    27  		NilObject:  (*Object)(nil),
    28  	})
    29  }
    30  
    31  func (f *Fs) SetUploadChunkSize(cs fs.SizeSuffix) (fs.SizeSuffix, error) {
    32  	return f.setUploadChunkSize(cs)
    33  }
    34  
    35  var _ fstests.SetUploadChunkSizer = (*Fs)(nil)
    36  
    37  // Check that PutStream works with NoChunk as it is the major code
    38  // deviation
    39  func (f *Fs) testNoChunk(t *testing.T) {
    40  	ctx := context.Background()
    41  	f.opt.NoChunk = true
    42  	defer func() {
    43  		f.opt.NoChunk = false
    44  	}()
    45  
    46  	file := fstest.Item{
    47  		ModTime: fstest.Time("2001-02-03T04:05:06.499999999Z"),
    48  		Path:    "piped data no chunk.txt",
    49  		Size:    -1, // use unknown size during upload
    50  	}
    51  
    52  	const contentSize = 100
    53  
    54  	contents := random.String(contentSize)
    55  	buf := bytes.NewBufferString(contents)
    56  	uploadHash := hash.NewMultiHasher()
    57  	in := io.TeeReader(buf, uploadHash)
    58  
    59  	file.Size = -1
    60  	obji := object.NewStaticObjectInfo(file.Path, file.ModTime, file.Size, true, nil, nil)
    61  	obj, err := f.Features().PutStream(ctx, in, obji)
    62  	require.NoError(t, err)
    63  
    64  	file.Hashes = uploadHash.Sums()
    65  	file.Size = int64(contentSize) // use correct size when checking
    66  	file.Check(t, obj, f.Precision())
    67  
    68  	// Re-read the object and check again
    69  	obj, err = f.NewObject(ctx, file.Path)
    70  	require.NoError(t, err)
    71  	file.Check(t, obj, f.Precision())
    72  
    73  	// Delete the object
    74  	assert.NoError(t, obj.Remove(ctx))
    75  }
    76  
    77  // Additional tests that aren't in the framework
    78  func (f *Fs) InternalTest(t *testing.T) {
    79  	t.Run("NoChunk", f.testNoChunk)
    80  	t.Run("WithChunk", f.testWithChunk)
    81  	t.Run("WithChunkFail", f.testWithChunkFail)
    82  	t.Run("CopyLargeObject", f.testCopyLargeObject)
    83  }
    84  
    85  func (f *Fs) testWithChunk(t *testing.T) {
    86  	preConfChunkSize := f.opt.ChunkSize
    87  	preConfChunk := f.opt.NoChunk
    88  	f.opt.NoChunk = false
    89  	f.opt.ChunkSize = 1024 * fs.SizeSuffixBase
    90  	defer func() {
    91  		//restore old config after test
    92  		f.opt.ChunkSize = preConfChunkSize
    93  		f.opt.NoChunk = preConfChunk
    94  	}()
    95  
    96  	file := fstest.Item{
    97  		ModTime: fstest.Time("2020-12-31T04:05:06.499999999Z"),
    98  		Path:    "piped data chunk.txt",
    99  		Size:    -1, // use unknown size during upload
   100  	}
   101  	const contentSize = 2048
   102  	contents := random.String(contentSize)
   103  	buf := bytes.NewBufferString(contents)
   104  	uploadHash := hash.NewMultiHasher()
   105  	in := io.TeeReader(buf, uploadHash)
   106  
   107  	file.Size = -1
   108  	obji := object.NewStaticObjectInfo(file.Path, file.ModTime, file.Size, true, nil, nil)
   109  	ctx := context.TODO()
   110  	obj, err := f.Features().PutStream(ctx, in, obji)
   111  	require.NoError(t, err)
   112  	require.NotEmpty(t, obj)
   113  }
   114  
   115  func (f *Fs) testWithChunkFail(t *testing.T) {
   116  	preConfChunkSize := f.opt.ChunkSize
   117  	preConfChunk := f.opt.NoChunk
   118  	f.opt.NoChunk = false
   119  	f.opt.ChunkSize = 1024 * fs.SizeSuffixBase
   120  	segmentContainer := f.root + "_segments"
   121  	if !f.opt.UseSegmentsContainer.Value {
   122  		segmentContainer = f.root
   123  	}
   124  	defer func() {
   125  		//restore config
   126  		f.opt.ChunkSize = preConfChunkSize
   127  		f.opt.NoChunk = preConfChunk
   128  	}()
   129  	path := "piped data chunk with error.txt"
   130  	file := fstest.Item{
   131  		ModTime: fstest.Time("2021-01-04T03:46:00.499999999Z"),
   132  		Path:    path,
   133  		Size:    -1, // use unknown size during upload
   134  	}
   135  	const contentSize = 4096
   136  	const errPosition = 3072
   137  	contents := random.String(contentSize)
   138  	buf := bytes.NewBufferString(contents[:errPosition])
   139  	errMessage := "potato"
   140  	er := &readers.ErrorReader{Err: errors.New(errMessage)}
   141  	in := io.NopCloser(io.MultiReader(buf, er))
   142  
   143  	file.Size = contentSize
   144  	obji := object.NewStaticObjectInfo(file.Path, file.ModTime, file.Size, true, nil, nil)
   145  	ctx := context.TODO()
   146  	_, err := f.Features().PutStream(ctx, in, obji)
   147  	// error is potato
   148  	require.NotNil(t, err)
   149  	require.Equal(t, errMessage, err.Error())
   150  	_, _, err = f.c.Object(ctx, f.rootContainer, path)
   151  	assert.Equal(t, swift.ObjectNotFound, err)
   152  	prefix := path
   153  	if !f.opt.UseSegmentsContainer.Value {
   154  		prefix = segmentsDirectory + "/" + prefix
   155  	}
   156  	objs, err := f.c.Objects(ctx, segmentContainer, &swift.ObjectsOpts{
   157  		Prefix: prefix,
   158  	})
   159  	require.NoError(t, err)
   160  	require.Empty(t, objs)
   161  }
   162  
   163  func (f *Fs) testCopyLargeObject(t *testing.T) {
   164  	preConfChunkSize := f.opt.ChunkSize
   165  	preConfChunk := f.opt.NoChunk
   166  	f.opt.NoChunk = false
   167  	f.opt.ChunkSize = 1024 * fs.SizeSuffixBase
   168  	defer func() {
   169  		//restore old config after test
   170  		f.opt.ChunkSize = preConfChunkSize
   171  		f.opt.NoChunk = preConfChunk
   172  	}()
   173  
   174  	file := fstest.Item{
   175  		ModTime: fstest.Time("2020-12-31T04:05:06.499999999Z"),
   176  		Path:    "large.txt",
   177  		Size:    -1, // use unknown size during upload
   178  	}
   179  	const contentSize = 2048
   180  	contents := random.String(contentSize)
   181  	buf := bytes.NewBufferString(contents)
   182  	uploadHash := hash.NewMultiHasher()
   183  	in := io.TeeReader(buf, uploadHash)
   184  
   185  	file.Size = -1
   186  	obji := object.NewStaticObjectInfo(file.Path, file.ModTime, file.Size, true, nil, nil)
   187  	ctx := context.TODO()
   188  	obj, err := f.Features().PutStream(ctx, in, obji)
   189  	require.NoError(t, err)
   190  	require.NotEmpty(t, obj)
   191  	remoteTarget := "large.txt (copy)"
   192  	objTarget, err := f.Features().Copy(ctx, obj, remoteTarget)
   193  	require.NoError(t, err)
   194  	require.NotEmpty(t, objTarget)
   195  	require.Equal(t, obj.Size(), objTarget.Size())
   196  }
   197  
   198  var _ fstests.InternalTester = (*Fs)(nil)