github.com/keybase/client/go@v0.0.0-20241007131713-f10651d043c8/kbfs/libkbfs/ops_test.go (about)

     1  // Copyright 2016 Keybase Inc. All rights reserved.
     2  // Use of this source code is governed by a BSD
     3  // license that can be found in the LICENSE file.
     4  
     5  package libkbfs
     6  
     7  import (
     8  	"fmt"
     9  	"math/rand"
    10  	"reflect"
    11  	"testing"
    12  	"time"
    13  
    14  	"github.com/keybase/client/go/kbfs/data"
    15  	"github.com/keybase/client/go/kbfs/kbfsblock"
    16  	"github.com/keybase/client/go/kbfs/kbfscodec"
    17  	"github.com/keybase/go-codec/codec"
    18  	"github.com/stretchr/testify/require"
    19  )
    20  
    21  func TestCreateOpCustomUpdate(t *testing.T) {
    22  	oldDir := makeRandomBlockPointer(t)
    23  	co, err := newCreateOp("name", oldDir, data.Exec)
    24  	require.NoError(t, err)
    25  	require.Equal(t, blockUpdate{Unref: oldDir}, co.Dir)
    26  
    27  	// Update to oldDir should update co.Dir.
    28  	newDir := oldDir
    29  	newDir.ID = kbfsblock.FakeID(42)
    30  	co.AddUpdate(oldDir, newDir)
    31  	require.Nil(t, co.Updates)
    32  	require.Equal(t, blockUpdate{Unref: oldDir, Ref: newDir}, co.Dir)
    33  }
    34  
    35  func TestRmOpCustomUpdate(t *testing.T) {
    36  	oldDir := makeRandomBlockPointer(t)
    37  	ro, err := newRmOp("name", oldDir, data.File)
    38  	require.NoError(t, err)
    39  	require.Equal(t, blockUpdate{Unref: oldDir}, ro.Dir)
    40  
    41  	// Update to oldDir should update ro.Dir.
    42  	newDir := oldDir
    43  	newDir.ID = kbfsblock.FakeID(42)
    44  	ro.AddUpdate(oldDir, newDir)
    45  	require.Nil(t, ro.Updates)
    46  	require.Equal(t, blockUpdate{Unref: oldDir, Ref: newDir}, ro.Dir)
    47  }
    48  
    49  func TestRenameOpCustomUpdateWithinDir(t *testing.T) {
    50  	oldDir := makeRandomBlockPointer(t)
    51  	renamed := oldDir
    52  	renamed.ID = kbfsblock.FakeID(42)
    53  	ro, err := newRenameOp(
    54  		"old name", oldDir, "new name", oldDir,
    55  		renamed, data.Exec)
    56  	require.NoError(t, err)
    57  	require.Equal(t, blockUpdate{Unref: oldDir}, ro.OldDir)
    58  	require.Equal(t, data.BlockPointer{}, ro.NewDir.Unref)
    59  	require.Equal(t, data.BlockPointer{}, ro.NewDir.Ref)
    60  
    61  	// Update to oldDir should update ro.OldDir.
    62  	newDir := oldDir
    63  	newDir.ID = kbfsblock.FakeID(43)
    64  	ro.AddUpdate(oldDir, newDir)
    65  	require.Nil(t, ro.Updates)
    66  	require.Equal(t, blockUpdate{Unref: oldDir, Ref: newDir}, ro.OldDir)
    67  	require.Equal(t, blockUpdate{}, ro.NewDir)
    68  }
    69  
    70  func TestRenameOpCustomUpdateAcrossDirs(t *testing.T) {
    71  	oldOldDir := makeRandomBlockPointer(t)
    72  	oldNewDir := oldOldDir
    73  	oldNewDir.ID = kbfsblock.FakeID(42)
    74  	renamed := oldOldDir
    75  	renamed.ID = kbfsblock.FakeID(43)
    76  	ro, err := newRenameOp(
    77  		"old name", oldOldDir, "new name", oldNewDir,
    78  		renamed, data.Exec)
    79  	require.NoError(t, err)
    80  	require.Equal(t, blockUpdate{Unref: oldOldDir}, ro.OldDir)
    81  	require.Equal(t, blockUpdate{Unref: oldNewDir}, ro.NewDir)
    82  
    83  	// Update to oldOldDir should update ro.OldDir.
    84  	newOldDir := oldOldDir
    85  	newOldDir.ID = kbfsblock.FakeID(44)
    86  	ro.AddUpdate(oldOldDir, newOldDir)
    87  	require.Nil(t, ro.Updates)
    88  	require.Equal(t, blockUpdate{Unref: oldOldDir, Ref: newOldDir}, ro.OldDir)
    89  	require.Equal(t, blockUpdate{Unref: oldNewDir}, ro.NewDir)
    90  
    91  	// Update to oldNewDir should update ro.OldDir.
    92  	newNewDir := oldNewDir
    93  	newNewDir.ID = kbfsblock.FakeID(45)
    94  	ro.AddUpdate(oldNewDir, newNewDir)
    95  	require.Nil(t, ro.Updates)
    96  	require.Equal(t, blockUpdate{Unref: oldOldDir, Ref: newOldDir}, ro.OldDir)
    97  	require.Equal(t, blockUpdate{Unref: oldNewDir, Ref: newNewDir}, ro.NewDir)
    98  }
    99  
   100  func TestSyncOpCustomUpdate(t *testing.T) {
   101  	oldFile := makeRandomBlockPointer(t)
   102  	so, err := newSyncOp(oldFile)
   103  	require.NoError(t, err)
   104  	require.Equal(t, blockUpdate{Unref: oldFile}, so.File)
   105  
   106  	// Update to oldFile should update so.File.
   107  	newFile := oldFile
   108  	newFile.ID = kbfsblock.FakeID(42)
   109  	so.AddUpdate(oldFile, newFile)
   110  	require.Nil(t, so.Updates)
   111  	require.Equal(t, blockUpdate{Unref: oldFile, Ref: newFile}, so.File)
   112  }
   113  
   114  func TestSetAttrOpCustomUpdate(t *testing.T) {
   115  	oldDir := makeRandomBlockPointer(t)
   116  	file := oldDir
   117  	file.ID = kbfsblock.FakeID(42)
   118  	sao, err := newSetAttrOp("name", oldDir, mtimeAttr, file)
   119  	require.NoError(t, err)
   120  	require.Equal(t, blockUpdate{Unref: oldDir}, sao.Dir)
   121  
   122  	// Update to oldDir should update sao.Dir.
   123  	newDir := oldDir
   124  	newDir.ID = kbfsblock.FakeID(42)
   125  	sao.AddUpdate(oldDir, newDir)
   126  	require.Nil(t, sao.Updates)
   127  	require.Equal(t, blockUpdate{Unref: oldDir, Ref: newDir}, sao.Dir)
   128  }
   129  
   130  type writeRangeFuture struct {
   131  	WriteRange
   132  	kbfscodec.Extra
   133  }
   134  
   135  func (wrf writeRangeFuture) toCurrent() WriteRange {
   136  	return wrf.WriteRange
   137  }
   138  
   139  func (wrf writeRangeFuture) ToCurrentStruct() kbfscodec.CurrentStruct {
   140  	return wrf.toCurrent()
   141  }
   142  
   143  func makeFakeWriteRangeFuture(t *testing.T) writeRangeFuture {
   144  	wrf := writeRangeFuture{
   145  		WriteRange{
   146  			5,
   147  			10,
   148  			codec.UnknownFieldSetHandler{},
   149  		},
   150  		kbfscodec.MakeExtraOrBust("WriteRange", t),
   151  	}
   152  	return wrf
   153  }
   154  
   155  func TestWriteRangeUnknownFields(t *testing.T) {
   156  	testStructUnknownFields(t, makeFakeWriteRangeFuture(t))
   157  }
   158  
   159  // opPointerizerFuture and registerOpsFuture are the "future" versions
   160  // of opPointerizer and RegisterOps. registerOpsFuture is used by
   161  // testStructUnknownFields.
   162  
   163  func opPointerizerFuture(iface interface{}) reflect.Value {
   164  	switch op := iface.(type) {
   165  	default:
   166  		return reflect.ValueOf(iface)
   167  	case createOpFuture:
   168  		return reflect.ValueOf(&op)
   169  	case rmOpFuture:
   170  		return reflect.ValueOf(&op)
   171  	case renameOpFuture:
   172  		return reflect.ValueOf(&op)
   173  	case syncOpFuture:
   174  		return reflect.ValueOf(&op)
   175  	case setAttrOpFuture:
   176  		return reflect.ValueOf(&op)
   177  	case resolutionOpFuture:
   178  		return reflect.ValueOf(&op)
   179  	case rekeyOpFuture:
   180  		return reflect.ValueOf(&op)
   181  	case gcOpFuture:
   182  		return reflect.ValueOf(&op)
   183  	}
   184  }
   185  
   186  func registerOpsFuture(codec kbfscodec.Codec) {
   187  	codec.RegisterType(reflect.TypeOf(createOpFuture{}), createOpCode)
   188  	codec.RegisterType(reflect.TypeOf(rmOpFuture{}), rmOpCode)
   189  	codec.RegisterType(reflect.TypeOf(renameOpFuture{}), renameOpCode)
   190  	codec.RegisterType(reflect.TypeOf(syncOpFuture{}), syncOpCode)
   191  	codec.RegisterType(reflect.TypeOf(setAttrOpFuture{}), setAttrOpCode)
   192  	codec.RegisterType(reflect.TypeOf(resolutionOpFuture{}), resolutionOpCode)
   193  	codec.RegisterType(reflect.TypeOf(rekeyOpFuture{}), rekeyOpCode)
   194  	codec.RegisterType(reflect.TypeOf(gcOpFuture{}), gcOpCode)
   195  	codec.RegisterIfaceSliceType(reflect.TypeOf(opsList{}), opsListCode,
   196  		opPointerizerFuture)
   197  }
   198  
   199  type createOpFuture struct {
   200  	createOp
   201  	kbfscodec.Extra
   202  }
   203  
   204  func (cof createOpFuture) toCurrent() createOp {
   205  	return cof.createOp
   206  }
   207  
   208  func (cof createOpFuture) ToCurrentStruct() kbfscodec.CurrentStruct {
   209  	return cof.toCurrent()
   210  }
   211  
   212  func makeFakeBlockUpdate(t *testing.T) blockUpdate {
   213  	return blockUpdate{
   214  		makeFakeBlockPointer(t),
   215  		makeFakeBlockPointer(t),
   216  	}
   217  }
   218  
   219  func makeFakeOpCommon(t *testing.T, withRefBlocks bool) OpCommon {
   220  	var refBlocks []data.BlockPointer
   221  	if withRefBlocks {
   222  		refBlocks = []data.BlockPointer{makeFakeBlockPointer(t)}
   223  	}
   224  	oc := OpCommon{
   225  		refBlocks,
   226  		[]data.BlockPointer{makeFakeBlockPointer(t)},
   227  		[]blockUpdate{makeFakeBlockUpdate(t)},
   228  		codec.UnknownFieldSetHandler{},
   229  		writerInfo{},
   230  		data.Path{},
   231  		time.Time{},
   232  	}
   233  	return oc
   234  }
   235  
   236  func makeFakeCreateOpFuture(t *testing.T) createOpFuture {
   237  	cof := createOpFuture{
   238  		createOp{
   239  			makeFakeOpCommon(t, true),
   240  			"new name",
   241  			makeFakeBlockUpdate(t),
   242  			data.Exec,
   243  			false,
   244  			false,
   245  			"",
   246  		},
   247  		kbfscodec.MakeExtraOrBust("createOp", t),
   248  	}
   249  	return cof
   250  }
   251  
   252  func TestCreateOpUnknownFields(t *testing.T) {
   253  	testStructUnknownFields(t, makeFakeCreateOpFuture(t))
   254  }
   255  
   256  type rmOpFuture struct {
   257  	rmOp
   258  	kbfscodec.Extra
   259  }
   260  
   261  func (rof rmOpFuture) toCurrent() rmOp {
   262  	return rof.rmOp
   263  }
   264  
   265  func (rof rmOpFuture) ToCurrentStruct() kbfscodec.CurrentStruct {
   266  	return rof.toCurrent()
   267  }
   268  
   269  func makeFakeRmOpFuture(t *testing.T) rmOpFuture {
   270  	rof := rmOpFuture{
   271  		rmOp{
   272  			makeFakeOpCommon(t, true),
   273  			"old name",
   274  			makeFakeBlockUpdate(t),
   275  			data.File,
   276  			false,
   277  		},
   278  		kbfscodec.MakeExtraOrBust("rmOp", t),
   279  	}
   280  	return rof
   281  }
   282  
   283  func TestRmOpUnknownFields(t *testing.T) {
   284  	testStructUnknownFields(t, makeFakeRmOpFuture(t))
   285  }
   286  
   287  type renameOpFuture struct {
   288  	renameOp
   289  	kbfscodec.Extra
   290  }
   291  
   292  func (rof renameOpFuture) toCurrent() renameOp {
   293  	return rof.renameOp
   294  }
   295  
   296  func (rof renameOpFuture) ToCurrentStruct() kbfscodec.CurrentStruct {
   297  	return rof.toCurrent()
   298  }
   299  
   300  func makeFakeRenameOpFuture(t *testing.T) renameOpFuture {
   301  	rof := renameOpFuture{
   302  		renameOp{
   303  			makeFakeOpCommon(t, true),
   304  			"old name",
   305  			makeFakeBlockUpdate(t),
   306  			"new name",
   307  			makeFakeBlockUpdate(t),
   308  			makeFakeBlockPointer(t),
   309  			data.Exec,
   310  			data.Path{},
   311  		},
   312  		kbfscodec.MakeExtraOrBust("renameOp", t),
   313  	}
   314  	return rof
   315  }
   316  
   317  func TestRenameOpUnknownFields(t *testing.T) {
   318  	testStructUnknownFields(t, makeFakeRenameOpFuture(t))
   319  }
   320  
   321  type syncOpFuture struct {
   322  	syncOp
   323  	// Overrides syncOp.Writes.
   324  	Writes []writeRangeFuture `codec:"w"`
   325  	kbfscodec.Extra
   326  }
   327  
   328  func (sof syncOpFuture) toCurrent() syncOp {
   329  	so := sof.syncOp
   330  	so.Writes = make([]WriteRange, len(sof.Writes))
   331  	for i, w := range sof.Writes {
   332  		so.Writes[i] = w.toCurrent()
   333  	}
   334  	return so
   335  }
   336  
   337  func (sof syncOpFuture) ToCurrentStruct() kbfscodec.CurrentStruct {
   338  	return sof.toCurrent()
   339  }
   340  
   341  func makeFakeSyncOpFuture(t *testing.T) syncOpFuture {
   342  	sof := syncOpFuture{
   343  		syncOp{
   344  			makeFakeOpCommon(t, true),
   345  			makeFakeBlockUpdate(t),
   346  			nil,
   347  			false,
   348  		},
   349  		[]writeRangeFuture{
   350  			makeFakeWriteRangeFuture(t),
   351  			makeFakeWriteRangeFuture(t),
   352  		},
   353  		kbfscodec.MakeExtraOrBust("syncOp", t),
   354  	}
   355  	return sof
   356  }
   357  
   358  func TestSyncOpUnknownFields(t *testing.T) {
   359  	testStructUnknownFields(t, makeFakeSyncOpFuture(t))
   360  }
   361  
   362  type setAttrOpFuture struct {
   363  	setAttrOp
   364  	kbfscodec.Extra
   365  }
   366  
   367  func (sof setAttrOpFuture) toCurrent() setAttrOp {
   368  	return sof.setAttrOp
   369  }
   370  
   371  func (sof setAttrOpFuture) ToCurrentStruct() kbfscodec.CurrentStruct {
   372  	return sof.toCurrent()
   373  }
   374  
   375  func makeFakeSetAttrOpFuture(t *testing.T) setAttrOpFuture {
   376  	sof := setAttrOpFuture{
   377  		setAttrOp{
   378  			makeFakeOpCommon(t, true),
   379  			"name",
   380  			makeFakeBlockUpdate(t),
   381  			mtimeAttr,
   382  			makeFakeBlockPointer(t),
   383  			false,
   384  		},
   385  		kbfscodec.MakeExtraOrBust("setAttrOp", t),
   386  	}
   387  	return sof
   388  }
   389  
   390  func TestSetAttrOpUnknownFields(t *testing.T) {
   391  	testStructUnknownFields(t, makeFakeSetAttrOpFuture(t))
   392  }
   393  
   394  type resolutionOpFuture struct {
   395  	resolutionOp
   396  	kbfscodec.Extra
   397  }
   398  
   399  func (rof resolutionOpFuture) toCurrent() resolutionOp {
   400  	return rof.resolutionOp
   401  }
   402  
   403  func (rof resolutionOpFuture) ToCurrentStruct() kbfscodec.CurrentStruct {
   404  	return rof.toCurrent()
   405  }
   406  
   407  func makeFakeResolutionOpFuture(t *testing.T) resolutionOpFuture {
   408  	rof := resolutionOpFuture{
   409  		resolutionOp{
   410  			makeFakeOpCommon(t, true),
   411  			nil,
   412  		},
   413  		kbfscodec.MakeExtraOrBust("resolutionOp", t),
   414  	}
   415  	return rof
   416  }
   417  
   418  func TestResolutionOpUnknownFields(t *testing.T) {
   419  	testStructUnknownFields(t, makeFakeResolutionOpFuture(t))
   420  }
   421  
   422  type rekeyOpFuture struct {
   423  	rekeyOp
   424  	kbfscodec.Extra
   425  }
   426  
   427  func (rof rekeyOpFuture) toCurrent() rekeyOp {
   428  	return rof.rekeyOp
   429  }
   430  
   431  func (rof rekeyOpFuture) ToCurrentStruct() kbfscodec.CurrentStruct {
   432  	return rof.toCurrent()
   433  }
   434  
   435  func makeFakeRekeyOpFuture(t *testing.T) rekeyOpFuture {
   436  	rof := rekeyOpFuture{
   437  		rekeyOp{
   438  			makeFakeOpCommon(t, true),
   439  		},
   440  		kbfscodec.MakeExtraOrBust("rekeyOp", t),
   441  	}
   442  	return rof
   443  }
   444  
   445  func TestRekeyOpUnknownFields(t *testing.T) {
   446  	testStructUnknownFields(t, makeFakeRekeyOpFuture(t))
   447  }
   448  
   449  type gcOpFuture struct {
   450  	GCOp
   451  	kbfscodec.Extra
   452  }
   453  
   454  func (gof gcOpFuture) toCurrent() GCOp {
   455  	return gof.GCOp
   456  }
   457  
   458  func (gof gcOpFuture) ToCurrentStruct() kbfscodec.CurrentStruct {
   459  	return gof.toCurrent()
   460  }
   461  
   462  func makeFakeGcOpFuture(t *testing.T) gcOpFuture {
   463  	gof := gcOpFuture{
   464  		GCOp{
   465  			makeFakeOpCommon(t, false),
   466  			100,
   467  		},
   468  		kbfscodec.MakeExtraOrBust("gcOp", t),
   469  	}
   470  	return gof
   471  }
   472  
   473  func TestGcOpUnknownFields(t *testing.T) {
   474  	testStructUnknownFields(t, makeFakeGcOpFuture(t))
   475  }
   476  
   477  type testOps struct {
   478  	Ops []interface{}
   479  }
   480  
   481  // Tests that ops can be serialized and deserialized as extensions.
   482  func TestOpSerialization(t *testing.T) {
   483  	c := kbfscodec.NewMsgpack()
   484  	RegisterOps(c)
   485  
   486  	ops := testOps{}
   487  	// add a couple ops of different types
   488  	co, err := newCreateOp("test1", data.BlockPointer{ID: kbfsblock.FakeID(42)}, data.File)
   489  	require.NoError(t, err)
   490  	ro, err := newRmOp("test2", data.BlockPointer{ID: kbfsblock.FakeID(43)}, data.File)
   491  	require.NoError(t, err)
   492  	ops.Ops = append(ops.Ops, co, ro)
   493  
   494  	buf, err := c.Encode(ops)
   495  	if err != nil {
   496  		t.Errorf("Couldn't encode ops: %v", err)
   497  	}
   498  
   499  	ops2 := testOps{}
   500  	err = c.Decode(buf, &ops2)
   501  	if err != nil {
   502  		t.Errorf("Couldn't decode ops: %v", err)
   503  	}
   504  
   505  	op1, ok := ops2.Ops[0].(createOp)
   506  	if !ok {
   507  		t.Errorf("Couldn't decode createOp: %v", reflect.TypeOf(ops2.Ops[0]))
   508  	} else if op1.NewName != "test1" {
   509  		t.Errorf("Wrong name in createOp: %s", op1.NewName)
   510  	}
   511  
   512  	op2, ok := ops2.Ops[1].(rmOp)
   513  	if !ok {
   514  		t.Errorf("Couldn't decode rmOp: %v", reflect.TypeOf(ops2.Ops[1]))
   515  	} else if op2.OldName != "test2" {
   516  		t.Errorf("Wrong name in rmOp: %s", op2.OldName)
   517  	}
   518  }
   519  
   520  func TestOpInversion(t *testing.T) {
   521  	oldPtr1 := data.BlockPointer{ID: kbfsblock.FakeID(42)}
   522  	newPtr1 := data.BlockPointer{ID: kbfsblock.FakeID(82)}
   523  	oldPtr2 := data.BlockPointer{ID: kbfsblock.FakeID(43)}
   524  	newPtr2 := data.BlockPointer{ID: kbfsblock.FakeID(83)}
   525  	filePtr := data.BlockPointer{ID: kbfsblock.FakeID(44)}
   526  
   527  	cop, err := newCreateOp("test1", oldPtr1, data.File)
   528  	require.NoError(t, err)
   529  	cop.AddUpdate(oldPtr1, newPtr1)
   530  	cop.AddUpdate(oldPtr2, newPtr2)
   531  	expectedIOp, err := newRmOp("test1", newPtr1, data.File)
   532  	require.NoError(t, err)
   533  	expectedIOp.AddUpdate(newPtr1, oldPtr1)
   534  	expectedIOp.AddUpdate(newPtr2, oldPtr2)
   535  
   536  	iop1, err := invertOpForLocalNotifications(cop)
   537  	require.NoError(t, err)
   538  	ro, ok := iop1.(*rmOp)
   539  	if !ok || !reflect.DeepEqual(*ro, *expectedIOp) {
   540  		t.Errorf("createOp didn't invert properly, expected %v, got %v",
   541  			expectedIOp, iop1)
   542  	}
   543  
   544  	// convert it back (works because the inversion picks File as the
   545  	// type, which is what we use above)
   546  	iop2, err := invertOpForLocalNotifications(iop1)
   547  	require.NoError(t, err)
   548  	co, ok := iop2.(*createOp)
   549  	if !ok || !reflect.DeepEqual(*co, *cop) {
   550  		t.Errorf("rmOp didn't invert properly, expected %v, got %v",
   551  			expectedIOp, iop2)
   552  	}
   553  
   554  	// rename
   555  	rop, err := newRenameOp("old", oldPtr1, "new", oldPtr2, filePtr, data.File)
   556  	require.NoError(t, err)
   557  	rop.AddUpdate(oldPtr1, newPtr1)
   558  	rop.AddUpdate(oldPtr2, newPtr2)
   559  	expectedIOp3, err := newRenameOp("new", newPtr2, "old", newPtr1, filePtr, data.File)
   560  	require.NoError(t, err)
   561  	expectedIOp3.AddUpdate(newPtr1, oldPtr1)
   562  	expectedIOp3.AddUpdate(newPtr2, oldPtr2)
   563  
   564  	iop3, err := invertOpForLocalNotifications(rop)
   565  	require.NoError(t, err)
   566  	iRenameOp, ok := iop3.(*renameOp)
   567  	if !ok || !reflect.DeepEqual(*iRenameOp, *expectedIOp3) {
   568  		t.Errorf("renameOp didn't invert properly, expected %v, got %v",
   569  			expectedIOp3, iop3)
   570  	}
   571  
   572  	// sync (writes should be the same as before)
   573  	sop, err := newSyncOp(oldPtr1)
   574  	require.NoError(t, err)
   575  	sop.AddUpdate(oldPtr1, newPtr1)
   576  	sop.addWrite(2, 3)
   577  	sop.addTruncate(100)
   578  	sop.addWrite(10, 12)
   579  	expectedIOp4, err := newSyncOp(newPtr1)
   580  	require.NoError(t, err)
   581  	expectedIOp4.AddUpdate(newPtr1, oldPtr1)
   582  	expectedIOp4.Writes = sop.Writes
   583  	iop4, err := invertOpForLocalNotifications(sop)
   584  	require.NoError(t, err)
   585  	so, ok := iop4.(*syncOp)
   586  	if !ok || !reflect.DeepEqual(*so, *expectedIOp4) {
   587  		t.Errorf("syncOp didn't invert properly, expected %v, got %v",
   588  			expectedIOp4, iop4)
   589  	}
   590  
   591  	// setAttr
   592  	saop, err := newSetAttrOp("name", oldPtr1, mtimeAttr, filePtr)
   593  	require.NoError(t, err)
   594  	saop.AddUpdate(oldPtr1, newPtr1)
   595  	expectedIOp5, err := newSetAttrOp("name", newPtr1, mtimeAttr, filePtr)
   596  	require.NoError(t, err)
   597  	expectedIOp5.AddUpdate(newPtr1, oldPtr1)
   598  	iop5, err := invertOpForLocalNotifications(saop)
   599  	require.NoError(t, err)
   600  	sao, ok := iop5.(*setAttrOp)
   601  	if !ok || !reflect.DeepEqual(*sao, *expectedIOp5) {
   602  		t.Errorf("setAttrOp didn't invert properly, expected %v, got %v",
   603  			expectedIOp5, iop5)
   604  	}
   605  
   606  	// rename (same dir)
   607  	rop, err = newRenameOp("old", oldPtr1, "new", oldPtr1, filePtr, data.File)
   608  	require.NoError(t, err)
   609  	rop.AddUpdate(oldPtr1, newPtr1)
   610  	expectedIOp6, err := newRenameOp("new", newPtr1, "old", newPtr1, filePtr, data.File)
   611  	require.NoError(t, err)
   612  	expectedIOp6.AddUpdate(newPtr1, oldPtr1)
   613  
   614  	iop6, err := invertOpForLocalNotifications(rop)
   615  	require.NoError(t, err)
   616  	iRenameOp, ok = iop6.(*renameOp)
   617  	if !ok || !reflect.DeepEqual(*iRenameOp, *expectedIOp6) {
   618  		t.Errorf("renameOp didn't invert properly, expected %v, got %v",
   619  			expectedIOp6, iop6)
   620  	}
   621  }
   622  
   623  func TestOpsCollapseWriteRange(t *testing.T) {
   624  	const numAttempts = 1000
   625  	const fileSize = uint64(1000)
   626  	const numWrites = 25
   627  	const maxWriteSize = uint64(50)
   628  	for i := 0; i < numAttempts; i++ {
   629  		// Make a "file" where dirty bytes are represented by trues.
   630  		var file [fileSize]bool
   631  		var lastByte uint64
   632  		var lastByteIsTruncate bool
   633  		var syncOps []*syncOp
   634  		for j := 0; j < numWrites; j++ {
   635  			// Start a new syncOp?
   636  			if len(syncOps) == 0 || rand.Int()%5 == 0 {
   637  				syncOps = append(syncOps, &syncOp{})
   638  			}
   639  
   640  			op := syncOps[len(syncOps)-1]
   641  			// Generate either a random truncate or random write
   642  			off := uint64(rand.Int()) % fileSize
   643  			var length uint64
   644  			if rand.Int()%5 > 0 {
   645  				// A write, not a truncate
   646  				maxLen := fileSize - off
   647  				if maxLen > maxWriteSize {
   648  					maxLen = maxWriteSize
   649  				}
   650  				maxLen--
   651  				if maxLen == 0 {
   652  					maxLen = 1
   653  				}
   654  				// Writes must have at least one byte
   655  				length = uint64(rand.Int())%maxLen + uint64(1)
   656  				op.addWrite(off, length)
   657  				// Fill in dirty bytes
   658  				for k := off; k < off+length; k++ {
   659  					file[k] = true
   660  				}
   661  				if lastByte < off+length {
   662  					lastByte = off + length
   663  				}
   664  			} else {
   665  				op.addTruncate(off)
   666  				if lastByteIsTruncate && lastByte < off {
   667  					for k := lastByte; k < off; k++ {
   668  						file[k] = true // zero-fill
   669  					}
   670  				}
   671  				for k := off; k < fileSize; k++ {
   672  					file[k] = false
   673  				}
   674  				lastByte = off
   675  				lastByteIsTruncate = true
   676  			}
   677  		}
   678  
   679  		var wrComputed []WriteRange
   680  		for _, op := range syncOps {
   681  			wrComputed = op.collapseWriteRange(wrComputed)
   682  		}
   683  
   684  		var wrExpected []WriteRange
   685  		inWrite := false
   686  		for j := 0; j < int(lastByte); j++ {
   687  			if !inWrite && file[j] {
   688  				inWrite = true
   689  				wrExpected = append(wrExpected, WriteRange{Off: uint64(j)})
   690  			} else if inWrite && !file[j] {
   691  				inWrite = false
   692  				wrExpected[len(wrExpected)-1].Len =
   693  					uint64(j) - wrExpected[len(wrExpected)-1].Off
   694  			}
   695  		}
   696  		if inWrite {
   697  			wrExpected[len(wrExpected)-1].Len =
   698  				lastByte - wrExpected[len(wrExpected)-1].Off
   699  		}
   700  		if lastByteIsTruncate {
   701  			wrExpected = append(wrExpected, WriteRange{Off: lastByte})
   702  		}
   703  
   704  		// Verify that the write range represents what's in the file.
   705  		if g, e := len(wrComputed), len(wrExpected); g != e {
   706  			t.Errorf("Range lengths differ (%d vs %d)", g, e)
   707  			continue
   708  		}
   709  		for j, wc := range wrComputed {
   710  			we := wrExpected[j]
   711  			if wc.Off != we.Off && wc.Len != we.Len {
   712  				t.Errorf("Writes differ at index %d (%v vs %v)", j, we, wc)
   713  			}
   714  		}
   715  	}
   716  }
   717  
   718  func TestCollapseWriteRangeWithLaterTruncate(t *testing.T) {
   719  	so := &syncOp{}
   720  	so.Writes = []WriteRange{{Off: 400, Len: 0}}
   721  	got := so.collapseWriteRange([]WriteRange{{Off: 0, Len: 0}})
   722  	expected := []WriteRange{{Off: 0, Len: 400}, {Off: 400, Len: 0}}
   723  	require.True(t, reflect.DeepEqual(got, expected),
   724  		"Bad write collapse, got=%v, expected=%v", got, expected)
   725  }
   726  
   727  func ExamplecoalesceWrites() {
   728  	fmt.Println(coalesceWrites(
   729  		[]WriteRange{{Off: 7, Len: 5}, {Off: 18, Len: 10},
   730  			{Off: 98, Len: 10}}, WriteRange{Off: 5, Len: 100}))
   731  	// Output: [{5 103 {{map[]}}}]
   732  }
   733  
   734  func ExamplecoalesceWrites_withOldTruncate() {
   735  	fmt.Println(coalesceWrites(
   736  		[]WriteRange{{Off: 7, Len: 5}, {Off: 18, Len: 10},
   737  			{Off: 98, Len: 0}}, WriteRange{Off: 5, Len: 100}))
   738  	// Output: [{5 100 {{map[]}}} {105 0 {{map[]}}}]
   739  }