github.com/xhghs/rclone@v1.51.1-0.20200430155106-e186a28cced8/fs/sync/pipe_test.go (about)

     1  package sync
     2  
     3  import (
     4  	"context"
     5  	"sync"
     6  	"sync/atomic"
     7  	"testing"
     8  
     9  	"github.com/rclone/rclone/fs"
    10  	"github.com/rclone/rclone/fstest/mockobject"
    11  	"github.com/stretchr/testify/assert"
    12  	"github.com/stretchr/testify/require"
    13  )
    14  
    15  func TestPipe(t *testing.T) {
    16  	var queueLength int
    17  	var queueSize int64
    18  	stats := func(n int, size int64) {
    19  		queueLength, queueSize = n, size
    20  	}
    21  
    22  	// Make a new pipe
    23  	p, err := newPipe("", stats, 10)
    24  	require.NoError(t, err)
    25  
    26  	checkStats := func(expectedN int, expectedSize int64) {
    27  		n, size := p.Stats()
    28  		assert.Equal(t, expectedN, n)
    29  		assert.Equal(t, expectedSize, size)
    30  		assert.Equal(t, expectedN, queueLength)
    31  		assert.Equal(t, expectedSize, queueSize)
    32  	}
    33  
    34  	checkStats(0, 0)
    35  
    36  	ctx := context.Background()
    37  
    38  	obj1 := mockobject.New("potato").WithContent([]byte("hello"), mockobject.SeekModeNone)
    39  
    40  	pair1 := fs.ObjectPair{Src: obj1, Dst: nil}
    41  
    42  	// Put an object
    43  	ok := p.Put(ctx, pair1)
    44  	assert.Equal(t, true, ok)
    45  	checkStats(1, 5)
    46  
    47  	// Close the pipe showing reading on closed pipe is OK
    48  	p.Close()
    49  
    50  	// Read from pipe
    51  	pair2, ok := p.Get(ctx)
    52  	assert.Equal(t, pair1, pair2)
    53  	assert.Equal(t, true, ok)
    54  	checkStats(0, 0)
    55  
    56  	// Check read on closed pipe
    57  	pair2, ok = p.Get(ctx)
    58  	assert.Equal(t, fs.ObjectPair{}, pair2)
    59  	assert.Equal(t, false, ok)
    60  
    61  	// Check panic on write to closed pipe
    62  	assert.Panics(t, func() { p.Put(ctx, pair1) })
    63  
    64  	// Make a new pipe
    65  	p, err = newPipe("", stats, 10)
    66  	require.NoError(t, err)
    67  	ctx2, cancel := context.WithCancel(ctx)
    68  
    69  	// cancel it in the background - check read ceases
    70  	go cancel()
    71  	pair2, ok = p.Get(ctx2)
    72  	assert.Equal(t, fs.ObjectPair{}, pair2)
    73  	assert.Equal(t, false, ok)
    74  
    75  	// check we can't write
    76  	ok = p.Put(ctx2, pair1)
    77  	assert.Equal(t, false, ok)
    78  
    79  }
    80  
    81  // TestPipeConcurrent runs concurrent Get and Put to flush out any
    82  // race conditions and concurrency problems.
    83  func TestPipeConcurrent(t *testing.T) {
    84  	const (
    85  		N           = 1000
    86  		readWriters = 10
    87  	)
    88  
    89  	stats := func(n int, size int64) {}
    90  
    91  	// Make a new pipe
    92  	p, err := newPipe("", stats, 10)
    93  	require.NoError(t, err)
    94  
    95  	var wg sync.WaitGroup
    96  	obj1 := mockobject.New("potato").WithContent([]byte("hello"), mockobject.SeekModeNone)
    97  	pair1 := fs.ObjectPair{Src: obj1, Dst: nil}
    98  	ctx := context.Background()
    99  	var count int64
   100  
   101  	for j := 0; j < readWriters; j++ {
   102  		wg.Add(2)
   103  		go func() {
   104  			defer wg.Done()
   105  			for i := 0; i < N; i++ {
   106  				// Read from pipe
   107  				pair2, ok := p.Get(ctx)
   108  				assert.Equal(t, pair1, pair2)
   109  				assert.Equal(t, true, ok)
   110  				atomic.AddInt64(&count, -1)
   111  			}
   112  		}()
   113  		go func() {
   114  			defer wg.Done()
   115  			for i := 0; i < N; i++ {
   116  				// Put an object
   117  				ok := p.Put(ctx, pair1)
   118  				assert.Equal(t, true, ok)
   119  				atomic.AddInt64(&count, 1)
   120  			}
   121  		}()
   122  	}
   123  	wg.Wait()
   124  
   125  	assert.Equal(t, int64(0), count)
   126  }
   127  
   128  func TestPipeOrderBy(t *testing.T) {
   129  	var (
   130  		stats = func(n int, size int64) {}
   131  		ctx   = context.Background()
   132  		obj1  = mockobject.New("b").WithContent([]byte("1"), mockobject.SeekModeNone)
   133  		obj2  = mockobject.New("a").WithContent([]byte("22"), mockobject.SeekModeNone)
   134  		pair1 = fs.ObjectPair{Src: obj1}
   135  		pair2 = fs.ObjectPair{Src: obj2}
   136  	)
   137  
   138  	for _, test := range []struct {
   139  		orderBy  string
   140  		swapped1 bool
   141  		swapped2 bool
   142  	}{
   143  		{"", false, true},
   144  		{"size", false, false},
   145  		{"name", true, true},
   146  		{"modtime", false, true},
   147  		{"size,ascending", false, false},
   148  		{"name,asc", true, true},
   149  		{"modtime,ascending", false, true},
   150  		{"size,descending", true, true},
   151  		{"name,desc", false, false},
   152  		{"modtime,descending", true, false},
   153  	} {
   154  		t.Run(test.orderBy, func(t *testing.T) {
   155  			p, err := newPipe(test.orderBy, stats, 10)
   156  			require.NoError(t, err)
   157  
   158  			ok := p.Put(ctx, pair1)
   159  			assert.True(t, ok)
   160  			ok = p.Put(ctx, pair2)
   161  			assert.True(t, ok)
   162  
   163  			readAndCheck := func(swapped bool) {
   164  				readFirst, ok := p.Get(ctx)
   165  				assert.True(t, ok)
   166  				readSecond, ok := p.Get(ctx)
   167  				assert.True(t, ok)
   168  
   169  				if swapped {
   170  					assert.True(t, readFirst == pair2 && readSecond == pair1)
   171  				} else {
   172  					assert.True(t, readFirst == pair1 && readSecond == pair2)
   173  				}
   174  			}
   175  
   176  			readAndCheck(test.swapped1)
   177  
   178  			// insert other way round
   179  
   180  			ok = p.Put(ctx, pair2)
   181  			assert.True(t, ok)
   182  			ok = p.Put(ctx, pair1)
   183  			assert.True(t, ok)
   184  
   185  			readAndCheck(test.swapped2)
   186  		})
   187  	}
   188  }
   189  
   190  func TestNewLess(t *testing.T) {
   191  	t.Run("blankOK", func(t *testing.T) {
   192  		less, err := newLess("")
   193  		require.NoError(t, err)
   194  		assert.Nil(t, less)
   195  	})
   196  
   197  	t.Run("tooManyParts", func(t *testing.T) {
   198  		_, err := newLess("too,many,parts")
   199  		require.Error(t, err)
   200  		assert.Contains(t, err.Error(), "bad --order-by string")
   201  	})
   202  
   203  	t.Run("unknownComparison", func(t *testing.T) {
   204  		_, err := newLess("potato")
   205  		require.Error(t, err)
   206  		assert.Contains(t, err.Error(), "unknown --order-by comparison")
   207  	})
   208  
   209  	t.Run("unknownSortDirection", func(t *testing.T) {
   210  		_, err := newLess("name,sideways")
   211  		require.Error(t, err)
   212  		assert.Contains(t, err.Error(), "unknown --order-by sort direction")
   213  	})
   214  
   215  	var (
   216  		obj1  = mockobject.New("b").WithContent([]byte("1"), mockobject.SeekModeNone)
   217  		obj2  = mockobject.New("a").WithContent([]byte("22"), mockobject.SeekModeNone)
   218  		pair1 = fs.ObjectPair{Src: obj1}
   219  		pair2 = fs.ObjectPair{Src: obj2}
   220  	)
   221  
   222  	for _, test := range []struct {
   223  		orderBy        string
   224  		pair1LessPair2 bool
   225  		pair2LessPair1 bool
   226  	}{
   227  		{"size", true, false},
   228  		{"name", false, true},
   229  		{"modtime", false, false},
   230  		{"size,ascending", true, false},
   231  		{"name,asc", false, true},
   232  		{"modtime,ascending", false, false},
   233  		{"size,descending", false, true},
   234  		{"name,desc", true, false},
   235  		{"modtime,descending", true, true},
   236  	} {
   237  		t.Run(test.orderBy, func(t *testing.T) {
   238  			less, err := newLess(test.orderBy)
   239  			require.NoError(t, err)
   240  			require.NotNil(t, less)
   241  			pair1LessPair2 := less(pair1, pair2)
   242  			assert.Equal(t, test.pair1LessPair2, pair1LessPair2)
   243  			pair2LessPair1 := less(pair2, pair1)
   244  			assert.Equal(t, test.pair2LessPair1, pair2LessPair1)
   245  		})
   246  	}
   247  
   248  }