github.com/josephvusich/fdf@v0.0.0-20230522095411-9326dd32e33f/scanner_test.go (about)

     1  package main
     2  
     3  import (
     4  	"fmt"
     5  	"io"
     6  	"io/ioutil"
     7  	"os"
     8  	"path/filepath"
     9  	"testing"
    10  
    11  	"github.com/josephvusich/go-matchers"
    12  	"github.com/josephvusich/go-matchers/glob"
    13  	"github.com/mattn/go-zglob"
    14  	"github.com/stretchr/testify/require"
    15  )
    16  
    17  func TestScanner_SelectAndSwap(t *testing.T) {
    18  	assert := require.New(t)
    19  
    20  	s := newScanner()
    21  	s.options.Protect = matchers.RuleSet{}
    22  	m, err := glob.NewMatcher("/foo_001")
    23  	assert.NoError(err)
    24  	m2, err := glob.NewMatcher("/foo")
    25  	assert.NoError(err)
    26  	s.options.Protect.Add(m, true)
    27  	s.options.MustKeep.Add(m2, true)
    28  
    29  	current := newFileRecord("/foo", nil, "foo", "")
    30  	match := newFileRecord("/foo_001", nil, "foo_001", "")
    31  
    32  	swapMatch, err := s.selectAndSwap(current, match, matchCopyName)
    33  	assert.ErrorIs(err, fileIsSkipped)
    34  
    35  	s.options.MustKeep = matchers.RuleSet{DefaultInclude: true}
    36  	current.satisfiesKept = nil
    37  	match.satisfiesKept = nil
    38  
    39  	swapMatch, err = s.selectAndSwap(current, match, matchCopyName)
    40  	assert.NoError(err)
    41  	assert.False(swapMatch)
    42  
    43  	s.options.Protect = matchers.RuleSet{}
    44  	current.protect = nil
    45  	match.protect = nil
    46  
    47  	swapMatch, err = s.selectAndSwap(current, match, matchContent)
    48  	assert.NoError(err)
    49  	assert.True(swapMatch)
    50  
    51  	current.protect = nil
    52  	current.satisfiesKept = nil
    53  	match.protect = nil
    54  	match.satisfiesKept = nil
    55  	s.options.MustKeep.Add(m, true)
    56  
    57  	swapMatch, err = s.selectAndSwap(current, match, matchContent)
    58  	assert.NoError(err)
    59  	assert.False(swapMatch)
    60  }
    61  
    62  func TestScanner_SelectAndSwapMustKeep(t *testing.T) {
    63  	assert := require.New(t)
    64  
    65  	s := newScanner()
    66  	s.options.Protect = matchers.RuleSet{}
    67  	m, err := glob.NewMatcher("/foo_001")
    68  	assert.NoError(err)
    69  	s.options.Protect.Add(m, true)
    70  
    71  	current := newFileRecord("/foo", nil, "foo", "")
    72  	match := newFileRecord("/foo_001", nil, "foo_001", "")
    73  
    74  	swapMatch, err := s.selectAndSwap(current, match, matchCopyName)
    75  	assert.NoError(err)
    76  	assert.False(swapMatch)
    77  
    78  	s.options.Protect = matchers.RuleSet{}
    79  	current.protect = nil
    80  	match.protect = nil
    81  
    82  	swapMatch, err = s.selectAndSwap(current, match, matchContent)
    83  	assert.NoError(err)
    84  	assert.True(swapMatch)
    85  }
    86  
    87  func TestScanner_NoVerb(t *testing.T) {
    88  	assert := require.New(t)
    89  	setupTest(assert, func(l *testLayout, validate func(*testLayout)) {
    90  		scanner := newScanner()
    91  		scanner.options.MatchMode = matchContent
    92  		scanner.options.Recursive = true
    93  		scanner.options.minSize = 1
    94  
    95  		assert.NoError(scanner.Scan())
    96  		fmt.Println(scanner.totals.PrettyFormat(scanner.options.Verb()))
    97  		assert.Equal(uint64(16), scanner.totals.Files.count)
    98  		assert.Equal(uint64(73), scanner.totals.Files.size)
    99  		assert.Equal(uint64(6), scanner.totals.Unique.count)
   100  		assert.Equal(uint64(33), scanner.totals.Unique.size)
   101  		assert.Equal(uint64(0), scanner.totals.Links.count)
   102  		assert.Equal(uint64(0), scanner.totals.Links.size)
   103  		assert.Equal(uint64(0), scanner.totals.Cloned.count)
   104  		assert.Equal(uint64(0), scanner.totals.Cloned.size)
   105  		assert.Equal(uint64(10), scanner.totals.Dupes.count)
   106  		assert.Equal(uint64(40), scanner.totals.Dupes.size)
   107  		assert.Equal(uint64(0), scanner.totals.Processed.count)
   108  		assert.Equal(uint64(0), scanner.totals.Processed.size)
   109  		assert.Equal(uint64(6), scanner.totals.Skipped.count)
   110  		assert.Equal(uint64(0), scanner.totals.Skipped.size)
   111  		assert.Equal(uint64(0), scanner.totals.Errors.count)
   112  		assert.Equal(uint64(0), scanner.totals.Errors.size)
   113  		validate(l)
   114  	})
   115  }
   116  
   117  func TestScanner_LinkUnlink(t *testing.T) {
   118  	assert := require.New(t)
   119  	setupTest(assert, func(l *testLayout, validate func(*testLayout)) {
   120  		// Phase 1: Hardlink
   121  		scanner := newScanner()
   122  		scanner.options.makeLinks = true
   123  		scanner.options.Recursive = true
   124  		scanner.options.MatchMode = matchContent
   125  
   126  		assert.NoError(scanner.Scan())
   127  		fmt.Println(scanner.totals.PrettyFormat(scanner.options.Verb()))
   128  		assert.Equal(uint64(22), scanner.totals.Files.count)
   129  		assert.Equal(uint64(73), scanner.totals.Files.size)
   130  		assert.Equal(uint64(7), scanner.totals.Unique.count)
   131  		assert.Equal(uint64(33), scanner.totals.Unique.size)
   132  		assert.Equal(uint64(15), scanner.totals.Links.count)
   133  		assert.Equal(uint64(40), scanner.totals.Links.size)
   134  		assert.Equal(uint64(0), scanner.totals.Cloned.count)
   135  		assert.Equal(uint64(0), scanner.totals.Cloned.size)
   136  		assert.Equal(uint64(0), scanner.totals.Dupes.count)
   137  		assert.Equal(uint64(0), scanner.totals.Dupes.size)
   138  		assert.Equal(uint64(15), scanner.totals.Processed.count)
   139  		assert.Equal(uint64(40), scanner.totals.Processed.size)
   140  		assert.Equal(uint64(0), scanner.totals.Skipped.count)
   141  		assert.Equal(uint64(0), scanner.totals.Skipped.size)
   142  		assert.Equal(uint64(0), scanner.totals.Errors.count)
   143  		assert.Equal(uint64(0), scanner.totals.Errors.size)
   144  		validate(l)
   145  
   146  		// Validate hardlink behavior
   147  		func() {
   148  			f, err := os.OpenFile(filepath.Join(l.dirs[0], "bar"), os.O_WRONLY, 0666)
   149  			assert.NoError(err)
   150  			defer f.Close()
   151  			_, err = f.Seek(0, io.SeekStart)
   152  			assert.NoError(err)
   153  			assert.NoError(f.Truncate(0))
   154  			_, err = f.WriteString("hello world")
   155  			assert.NoError(err)
   156  			l.content["bar"] = "hello world"
   157  			l.content["bar2"] = "hello world"
   158  			l.content["bar3"] = "hello world"
   159  		}()
   160  		validate(l)
   161  
   162  		// Phase 2: Copy
   163  		scanner = newScanner()
   164  		scanner.options.splitLinks = true
   165  		scanner.options.Recursive = true
   166  		scanner.options.MatchMode = matchContent
   167  
   168  		assert.NoError(scanner.Scan())
   169  		fmt.Println(scanner.totals.PrettyFormat(scanner.options.Verb()))
   170  		assert.Equal(uint64(22), scanner.totals.Files.count)
   171  		assert.Equal(uint64(115), scanner.totals.Files.size)
   172  		assert.Equal(uint64(7), scanner.totals.Unique.count)
   173  		assert.Equal(uint64(40), scanner.totals.Unique.size)
   174  		assert.Equal(uint64(0), scanner.totals.Links.count)
   175  		assert.Equal(uint64(0), scanner.totals.Links.size)
   176  		assert.Equal(uint64(0), scanner.totals.Cloned.count)
   177  		assert.Equal(uint64(0), scanner.totals.Cloned.size)
   178  		assert.Equal(uint64(15), scanner.totals.Dupes.count)
   179  		assert.Equal(uint64(75), scanner.totals.Dupes.size)
   180  		assert.Equal(uint64(15), scanner.totals.Processed.count)
   181  		assert.Equal(uint64(75), scanner.totals.Processed.size)
   182  		assert.Equal(uint64(0), scanner.totals.Skipped.count)
   183  		assert.Equal(uint64(0), scanner.totals.Skipped.size)
   184  		assert.Equal(uint64(0), scanner.totals.Errors.count)
   185  		assert.Equal(uint64(0), scanner.totals.Errors.size)
   186  		validate(l)
   187  
   188  		// Validate hardlink split
   189  		for i := 0; i < len(l.dirs); i++ {
   190  			func(i int) {
   191  				f, err := os.OpenFile(filepath.Join(l.dirs[i], "bar"), os.O_WRONLY, 0666)
   192  				assert.NoError(err)
   193  				defer f.Close()
   194  				_, err = f.Seek(0, io.SeekStart)
   195  				assert.NoError(err)
   196  				assert.NoError(f.Truncate(0))
   197  				_, err = f.WriteString("goodbye world")
   198  				assert.NoError(err)
   199  			}(i)
   200  		}
   201  		l.content["bar"] = "goodbye world"
   202  		validate(l)
   203  	})
   204  }
   205  
   206  func TestScanner_Delete(t *testing.T) {
   207  	assert := require.New(t)
   208  	setupTest(assert, func(l *testLayout, validate func(*testLayout)) {
   209  		scanner := newScanner()
   210  		scanner.options.deleteDupes = true
   211  		scanner.options.Recursive = true
   212  		scanner.options.MatchMode = matchContent
   213  
   214  		assert.NoError(scanner.Scan())
   215  		fmt.Println(scanner.totals.PrettyFormat(scanner.options.Verb()))
   216  		assert.Equal(uint64(22), scanner.totals.Files.count)
   217  		assert.Equal(uint64(73), scanner.totals.Files.size)
   218  		assert.Equal(uint64(7), scanner.totals.Unique.count)
   219  		assert.Equal(uint64(33), scanner.totals.Unique.size)
   220  		assert.Equal(uint64(0), scanner.totals.Links.count)
   221  		assert.Equal(uint64(0), scanner.totals.Links.size)
   222  		assert.Equal(uint64(0), scanner.totals.Cloned.count)
   223  		assert.Equal(uint64(0), scanner.totals.Cloned.size)
   224  		assert.Equal(uint64(0), scanner.totals.Dupes.count)
   225  		assert.Equal(uint64(0), scanner.totals.Dupes.size)
   226  		assert.Equal(uint64(15), scanner.totals.Processed.count)
   227  		assert.Equal(uint64(40), scanner.totals.Processed.size)
   228  		assert.Equal(uint64(0), scanner.totals.Skipped.count)
   229  		assert.Equal(uint64(0), scanner.totals.Skipped.size)
   230  		assert.Equal(uint64(0), scanner.totals.Errors.count)
   231  		assert.Equal(uint64(0), scanner.totals.Errors.size)
   232  		l.contentOverride = true
   233  		l.content = map[string]string{
   234  			"a/bar":         "bar\n",
   235  			"a/diffContent": "fizz\n",
   236  			"a/diffSize":    "foobar\n",
   237  			"a/empty":       "",
   238  			"a/foo":         "foo\n",
   239  			"b/diffContent": "buzz\n",
   240  			"b/diffSize":    "foobar2\n",
   241  		}
   242  		validate(l)
   243  	})
   244  }
   245  
   246  func TestScanner_DeleteProtect(t *testing.T) {
   247  	assert := require.New(t)
   248  	setupTest(assert, func(l *testLayout, validate func(*testLayout)) {
   249  		scanner := newScanner()
   250  		assert.Empty(scanner.options.ParseArgs([]string{`fdf`, `-rd`, `--protect`, `./b/**/*`, `-m`, `content`, `-z`, `0`}))
   251  		assert.True(scanner.options.deleteDupes)
   252  		assert.True(scanner.options.Recursive)
   253  		assert.Equal(matchContent, scanner.options.MatchMode)
   254  
   255  		assert.NoError(scanner.Scan())
   256  		fmt.Println(scanner.totals.PrettyFormat(scanner.options.Verb()))
   257  		assert.Equal(uint64(22), scanner.totals.Files.count)
   258  		assert.Equal(uint64(73), scanner.totals.Files.size)
   259  		assert.Equal(uint64(13), scanner.totals.Unique.count)
   260  		assert.Equal(uint64(49), scanner.totals.Unique.size)
   261  		assert.Equal(uint64(0), scanner.totals.Links.count)
   262  		assert.Equal(uint64(0), scanner.totals.Links.size)
   263  		assert.Equal(uint64(0), scanner.totals.Cloned.count)
   264  		assert.Equal(uint64(0), scanner.totals.Cloned.size)
   265  		assert.Equal(uint64(0), scanner.totals.Dupes.count)
   266  		assert.Equal(uint64(0), scanner.totals.Dupes.size)
   267  		assert.Equal(uint64(9), scanner.totals.Processed.count)
   268  		assert.Equal(uint64(24), scanner.totals.Processed.size)
   269  		assert.Equal(uint64(0), scanner.totals.Skipped.count)
   270  		assert.Equal(uint64(0), scanner.totals.Skipped.size)
   271  		assert.Equal(uint64(0), scanner.totals.Errors.count)
   272  		assert.Equal(uint64(0), scanner.totals.Errors.size)
   273  		l.contentOverride = true
   274  		l.content = map[string]string{
   275  			"a/diffContent": "fizz\n",
   276  			"a/diffSize":    "foobar\n",
   277  			"b/bar":         "bar\n",
   278  			"b/bar2":        "bar\n",
   279  			"b/bar3":        "bar\n",
   280  			"b/empty":       "",
   281  			"b/empty2":      "",
   282  			"b/empty3":      "",
   283  			"b/foo":         "foo\n",
   284  			"b/foo2":        "foo\n",
   285  			"b/foo3":        "foo\n",
   286  			"b/diffContent": "buzz\n",
   287  			"b/diffSize":    "foobar2\n",
   288  		}
   289  		validate(l)
   290  	})
   291  }
   292  
   293  func TestScanner_NameSize(t *testing.T) {
   294  	assert := require.New(t)
   295  	setupTest(assert, func(l *testLayout, validate func(*testLayout)) {
   296  		// Phase 1: Hardlink
   297  		scanner := newScanner()
   298  		scanner.options.MatchMode = matchName | matchSize
   299  		scanner.options.makeLinks = true
   300  		scanner.options.Recursive = true
   301  
   302  		assert.NoError(scanner.Scan())
   303  		fmt.Println(scanner.totals.PrettyFormat(scanner.options.Verb()))
   304  		assert.Equal(uint64(22), scanner.totals.Files.count)
   305  		assert.Equal(uint64(73), scanner.totals.Files.size)
   306  		assert.Equal(uint64(12), scanner.totals.Unique.count)
   307  		assert.Equal(uint64(44), scanner.totals.Unique.size)
   308  		assert.Equal(uint64(10), scanner.totals.Links.count)
   309  		assert.Equal(uint64(29), scanner.totals.Links.size)
   310  		assert.Equal(uint64(0), scanner.totals.Cloned.count)
   311  		assert.Equal(uint64(0), scanner.totals.Cloned.size)
   312  		assert.Equal(uint64(0), scanner.totals.Dupes.count)
   313  		assert.Equal(uint64(0), scanner.totals.Dupes.size)
   314  		assert.Equal(uint64(10), scanner.totals.Processed.count)
   315  		assert.Equal(uint64(29), scanner.totals.Processed.size)
   316  		assert.Equal(uint64(0), scanner.totals.Skipped.count)
   317  		assert.Equal(uint64(0), scanner.totals.Skipped.size)
   318  		assert.Equal(uint64(0), scanner.totals.Errors.count)
   319  		assert.Equal(uint64(0), scanner.totals.Errors.size)
   320  		l.diffContent[1] = l.diffContent[0]
   321  		validate(l)
   322  	})
   323  }
   324  
   325  func TestScanner_NameContent(t *testing.T) {
   326  	assert := require.New(t)
   327  	setupTest(assert, func(l *testLayout, validate func(*testLayout)) {
   328  		// Phase 1: Hardlink
   329  		scanner := newScanner()
   330  		scanner.options.MatchMode = matchName | matchContent
   331  		scanner.options.makeLinks = true
   332  		scanner.options.Recursive = true
   333  
   334  		assert.NoError(scanner.Scan())
   335  		fmt.Println(scanner.totals.PrettyFormat(scanner.options.Verb()))
   336  		assert.Equal(uint64(22), scanner.totals.Files.count)
   337  		assert.Equal(uint64(73), scanner.totals.Files.size)
   338  		assert.Equal(uint64(13), scanner.totals.Unique.count)
   339  		assert.Equal(uint64(49), scanner.totals.Unique.size)
   340  		assert.Equal(uint64(9), scanner.totals.Links.count)
   341  		assert.Equal(uint64(24), scanner.totals.Links.size)
   342  		assert.Equal(uint64(0), scanner.totals.Cloned.count)
   343  		assert.Equal(uint64(0), scanner.totals.Cloned.size)
   344  		assert.Equal(uint64(0), scanner.totals.Dupes.count)
   345  		assert.Equal(uint64(0), scanner.totals.Dupes.size)
   346  		assert.Equal(uint64(9), scanner.totals.Processed.count)
   347  		assert.Equal(uint64(24), scanner.totals.Processed.size)
   348  		assert.Equal(uint64(0), scanner.totals.Skipped.count)
   349  		assert.Equal(uint64(0), scanner.totals.Skipped.size)
   350  		assert.Equal(uint64(0), scanner.totals.Errors.count)
   351  		assert.Equal(uint64(0), scanner.totals.Errors.size)
   352  		validate(l)
   353  	})
   354  }
   355  
   356  func TestScanner_CopyNameContent(t *testing.T) {
   357  	assert := require.New(t)
   358  
   359  	l := &testLayout{
   360  		dirs: []string{
   361  			"./a",
   362  			"./b",
   363  		},
   364  		content: map[string]string{
   365  			"bar":         "bar\n",
   366  			"Copy of bar": "bar\n",
   367  			"bar (1)":     "bar\n",
   368  			"bar-01":      "bar\n",
   369  			"foo":         "bar\n",
   370  			"bar.foo":     "bar\n",
   371  		},
   372  		diffContent: []string{
   373  			"fizz\n",
   374  			"buzz\n",
   375  		},
   376  		diffSize: []string{
   377  			"foobar\n",
   378  			"foobar2\n",
   379  		},
   380  	}
   381  
   382  	setupTestLayout(assert, l, func(l *testLayout, validate func(*testLayout)) {
   383  		scanner := newScanner()
   384  		scanner.options.MatchMode = matchCopyName | matchContent
   385  		scanner.options.makeLinks = true
   386  		scanner.options.Recursive = true
   387  
   388  		assert.NoError(scanner.Scan())
   389  		fmt.Println(scanner.totals.PrettyFormat(scanner.options.Verb()))
   390  		assert.Equal(uint64(16), scanner.totals.Files.count)
   391  		assert.Equal(uint64(73), scanner.totals.Files.size)
   392  		assert.Equal(uint64(7), scanner.totals.Unique.count)
   393  		assert.Equal(uint64(37), scanner.totals.Unique.size)
   394  		assert.Equal(uint64(9), scanner.totals.Links.count)
   395  		assert.Equal(uint64(36), scanner.totals.Links.size)
   396  		assert.Equal(uint64(0), scanner.totals.Cloned.count)
   397  		assert.Equal(uint64(0), scanner.totals.Cloned.size)
   398  		assert.Equal(uint64(0), scanner.totals.Dupes.count)
   399  		assert.Equal(uint64(0), scanner.totals.Dupes.size)
   400  		assert.Equal(uint64(9), scanner.totals.Processed.count)
   401  		assert.Equal(uint64(36), scanner.totals.Processed.size)
   402  		assert.Equal(uint64(0), scanner.totals.Skipped.count)
   403  		assert.Equal(uint64(0), scanner.totals.Skipped.size)
   404  		assert.Equal(uint64(0), scanner.totals.Errors.count)
   405  		assert.Equal(uint64(0), scanner.totals.Errors.size)
   406  		validate(l)
   407  	})
   408  }
   409  
   410  func TestScanner_NameSuffix(t *testing.T) {
   411  	assert := require.New(t)
   412  
   413  	l := &testLayout{
   414  		dirs: []string{
   415  			"./a",
   416  			"./b",
   417  		},
   418  		content: map[string]string{
   419  			"bar":         "bar\n",
   420  			"Copy of bar": "bar\n",
   421  			"bar (1)":     "bar\n",
   422  			"bar-01":      "bar\n",
   423  			"foo":         "bar\n",
   424  			"bar.foo":     "bar\n",
   425  		},
   426  		diffContent: []string{
   427  			"fizz\n",
   428  			"buzz\n",
   429  		},
   430  		diffSize: []string{
   431  			"foobar\n",
   432  			"foobar2\n",
   433  		},
   434  	}
   435  
   436  	setupTestLayout(assert, l, func(l *testLayout, validate func(*testLayout)) {
   437  		scanner := newScanner()
   438  		scanner.options.MatchMode = matchNameSuffix | matchContent
   439  		scanner.options.deleteDupes = true
   440  		scanner.options.Recursive = true
   441  
   442  		assert.NoError(scanner.Scan())
   443  		fmt.Println(scanner.totals.PrettyFormat(scanner.options.Verb()))
   444  		assert.Equal(uint64(16), scanner.totals.Files.count)
   445  		assert.Equal(uint64(73), scanner.totals.Files.size)
   446  		assert.Equal(uint64(8), scanner.totals.Unique.count)
   447  		assert.Equal(uint64(41), scanner.totals.Unique.size)
   448  		assert.Equal(uint64(0), scanner.totals.Links.count)
   449  		assert.Equal(uint64(0), scanner.totals.Links.size)
   450  		assert.Equal(uint64(0), scanner.totals.Cloned.count)
   451  		assert.Equal(uint64(0), scanner.totals.Cloned.size)
   452  		assert.Equal(uint64(0), scanner.totals.Dupes.count)
   453  		assert.Equal(uint64(0), scanner.totals.Dupes.size)
   454  		assert.Equal(uint64(8), scanner.totals.Processed.count)
   455  		assert.Equal(uint64(32), scanner.totals.Processed.size)
   456  		assert.Equal(uint64(0), scanner.totals.Skipped.count)
   457  		assert.Equal(uint64(0), scanner.totals.Skipped.size)
   458  		assert.Equal(uint64(0), scanner.totals.Errors.count)
   459  		assert.Equal(uint64(0), scanner.totals.Errors.size)
   460  		l.contentOverride = true
   461  		l.content = map[string]string{
   462  			"a/bar":         "bar\n",
   463  			"a/bar (1)":     "bar\n",
   464  			"a/bar.foo":     "bar\n",
   465  			"a/bar-01":      "bar\n",
   466  			"a/diffContent": "fizz\n",
   467  			"a/diffSize":    "foobar\n",
   468  			"b/diffContent": "buzz\n",
   469  			"b/diffSize":    "foobar2\n",
   470  		}
   471  		validate(l)
   472  	})
   473  }
   474  
   475  func TestScanner_NameOnly(t *testing.T) {
   476  	assert := require.New(t)
   477  	setupTest(assert, func(l *testLayout, validate func(*testLayout)) {
   478  		// Phase 1: Hardlink
   479  		scanner := newScanner()
   480  		scanner.options.MatchMode = matchName
   481  		scanner.options.makeLinks = true
   482  		scanner.options.Recursive = true
   483  
   484  		assert.NoError(scanner.Scan())
   485  		fmt.Println(scanner.totals.PrettyFormat(scanner.options.Verb()))
   486  		assert.Equal(uint64(22), scanner.totals.Files.count)
   487  		assert.Equal(uint64(73), scanner.totals.Files.size)
   488  		assert.Equal(uint64(11), scanner.totals.Unique.count)
   489  		// this might change by one if the other file is found first
   490  		assert.Equal(uint64(36), scanner.totals.Unique.size)
   491  		assert.Equal(uint64(11), scanner.totals.Links.count)
   492  		assert.Equal(uint64(36), scanner.totals.Links.size)
   493  		assert.Equal(uint64(0), scanner.totals.Cloned.count)
   494  		assert.Equal(uint64(0), scanner.totals.Cloned.size)
   495  		assert.Equal(uint64(0), scanner.totals.Dupes.count)
   496  		assert.Equal(uint64(0), scanner.totals.Dupes.size)
   497  		assert.Equal(uint64(11), scanner.totals.Processed.count)
   498  		// this will be one larger than Unique size because b/diffSize becomes smaller
   499  		assert.Equal(uint64(37), scanner.totals.Processed.size)
   500  		assert.Equal(uint64(0), scanner.totals.Skipped.count)
   501  		assert.Equal(uint64(0), scanner.totals.Skipped.size)
   502  		assert.Equal(uint64(0), scanner.totals.Errors.count)
   503  		assert.Equal(uint64(0), scanner.totals.Errors.size)
   504  		l.diffContent[1] = l.diffContent[0]
   505  		l.diffSize[1] = l.diffSize[0]
   506  		validate(l)
   507  	})
   508  }
   509  
   510  func TestScanner_SizeOnly(t *testing.T) {
   511  	assert := require.New(t)
   512  	setupTest(assert, func(l *testLayout, validate func(*testLayout)) {
   513  		// Phase 1: Hardlink
   514  		scanner := newScanner()
   515  		scanner.options.MatchMode = matchSize
   516  		scanner.options.makeLinks = true
   517  		scanner.options.Recursive = true
   518  
   519  		assert.NoError(scanner.Scan())
   520  		fmt.Println(scanner.totals.PrettyFormat(scanner.options.Verb()))
   521  		assert.Equal(uint64(22), scanner.totals.Files.count)
   522  		assert.Equal(uint64(73), scanner.totals.Files.size)
   523  		assert.Equal(uint64(5), scanner.totals.Unique.count)
   524  		assert.Equal(uint64(24), scanner.totals.Unique.size)
   525  		assert.Equal(uint64(17), scanner.totals.Links.count)
   526  		assert.Equal(uint64(49), scanner.totals.Links.size)
   527  		assert.Equal(uint64(0), scanner.totals.Cloned.count)
   528  		assert.Equal(uint64(0), scanner.totals.Cloned.size)
   529  		assert.Equal(uint64(0), scanner.totals.Dupes.count)
   530  		assert.Equal(uint64(0), scanner.totals.Dupes.size)
   531  		assert.Equal(uint64(17), scanner.totals.Processed.count)
   532  		assert.Equal(uint64(49), scanner.totals.Processed.size)
   533  		assert.Equal(uint64(0), scanner.totals.Skipped.count)
   534  		assert.Equal(uint64(0), scanner.totals.Skipped.size)
   535  		assert.Equal(uint64(0), scanner.totals.Errors.count)
   536  		assert.Equal(uint64(0), scanner.totals.Errors.size)
   537  		l.content["foo"] = l.content["bar"]
   538  		l.content["foo2"] = l.content["bar"]
   539  		l.content["foo3"] = l.content["bar"]
   540  		l.diffContent[1] = l.diffContent[0]
   541  		validate(l)
   542  	})
   543  }
   544  
   545  func TestScanner_SkipHeader(t *testing.T) {
   546  	assert := require.New(t)
   547  	setupTest(assert, func(l *testLayout, validate func(*testLayout)) {
   548  		scanner := newScanner()
   549  		scanner.options.MatchMode = matchContent
   550  		scanner.options.makeLinks = true
   551  		scanner.options.Recursive = true
   552  		scanner.options.SkipHeader = 3
   553  
   554  		assert.NoError(scanner.Scan())
   555  		fmt.Println(scanner.totals.PrettyFormat(scanner.options.Verb()))
   556  		assert.Equal(uint64(16), scanner.totals.Files.count)
   557  		assert.Equal(uint64(73), scanner.totals.Files.size)
   558  		assert.Equal(uint64(4), scanner.totals.Unique.count)
   559  		assert.Equal(uint64(24), scanner.totals.Unique.size)
   560  		assert.Equal(uint64(12), scanner.totals.Links.count)
   561  		assert.Equal(uint64(49), scanner.totals.Links.size)
   562  		assert.Equal(uint64(0), scanner.totals.Cloned.count)
   563  		assert.Equal(uint64(0), scanner.totals.Cloned.size)
   564  		assert.Equal(uint64(0), scanner.totals.Dupes.count)
   565  		assert.Equal(uint64(0), scanner.totals.Dupes.size)
   566  		assert.Equal(uint64(12), scanner.totals.Processed.count)
   567  		assert.Equal(uint64(49), scanner.totals.Processed.size)
   568  		assert.Equal(uint64(6), scanner.totals.Skipped.count)
   569  		assert.Equal(uint64(0), scanner.totals.Skipped.size)
   570  		assert.Equal(uint64(0), scanner.totals.Errors.count)
   571  		assert.Equal(uint64(0), scanner.totals.Errors.size)
   572  		l.content["foo"] = l.content["bar"]
   573  		l.content["foo2"] = l.content["bar"]
   574  		l.content["foo3"] = l.content["bar"]
   575  		l.diffContent[1] = l.diffContent[0]
   576  		validate(l)
   577  	})
   578  }
   579  
   580  func TestScanner_Parent(t *testing.T) {
   581  	assert := require.New(t)
   582  	l := &testLayout{
   583  		dirs: []string{
   584  			"./foo/a",
   585  			"./foo/b",
   586  			"./bar/a",
   587  			"./bar/b",
   588  		},
   589  		content: map[string]string{
   590  			"fizz1": "fizz",
   591  			"fizz2": "fizz",
   592  			"buzz":  "buzz",
   593  		},
   594  		diffContent: nil,
   595  		diffSize:    nil,
   596  	}
   597  	setupTestLayout(assert, l, func(l *testLayout, validate func(*testLayout)) {
   598  		scanner := newScanner()
   599  		assert.Empty(scanner.options.ParseArgs([]string{`fdf`, `-rd`, `-m`, `content+parent`, `-z`, `0`}))
   600  		assert.True(scanner.options.deleteDupes)
   601  		assert.True(scanner.options.Recursive)
   602  		assert.Equal(matchContent|matchParent, scanner.options.MatchMode)
   603  
   604  		assert.NoError(scanner.Scan())
   605  		fmt.Println(scanner.totals.PrettyFormat(scanner.options.Verb()))
   606  		assert.Equal(uint64(12), scanner.totals.Files.count)
   607  		assert.Equal(uint64(48), scanner.totals.Files.size)
   608  		assert.Equal(uint64(4), scanner.totals.Unique.count)
   609  		assert.Equal(uint64(16), scanner.totals.Unique.size)
   610  		assert.Equal(uint64(0), scanner.totals.Links.count)
   611  		assert.Equal(uint64(0), scanner.totals.Links.size)
   612  		assert.Equal(uint64(0), scanner.totals.Cloned.count)
   613  		assert.Equal(uint64(0), scanner.totals.Cloned.size)
   614  		assert.Equal(uint64(0), scanner.totals.Dupes.count)
   615  		assert.Equal(uint64(0), scanner.totals.Dupes.size)
   616  		assert.Equal(uint64(8), scanner.totals.Processed.count)
   617  		assert.Equal(uint64(32), scanner.totals.Processed.size)
   618  		assert.Equal(uint64(0), scanner.totals.Skipped.count)
   619  		assert.Equal(uint64(0), scanner.totals.Skipped.size)
   620  		assert.Equal(uint64(0), scanner.totals.Errors.count)
   621  		assert.Equal(uint64(0), scanner.totals.Errors.size)
   622  		l.contentOverride = true
   623  		l.content = map[string]string{
   624  			"bar/a/fizz1": "fizz",
   625  			"bar/a/buzz":  "buzz",
   626  			"bar/b/fizz1": "fizz",
   627  			"bar/b/buzz":  "buzz",
   628  		}
   629  		validate(l)
   630  	})
   631  }
   632  
   633  func TestScanner_Path(t *testing.T) {
   634  	assert := require.New(t)
   635  	l := &testLayout{
   636  		dirs: []string{
   637  			"./foo/a",
   638  			"./foo/b",
   639  			"./bar/a",
   640  			"./bar/b",
   641  		},
   642  		content: map[string]string{
   643  			"fizz1": "fizz",
   644  			"fizz2": "fizz",
   645  			"buzz":  "buzz",
   646  		},
   647  		diffContent: nil,
   648  		diffSize:    nil,
   649  	}
   650  	setupTestLayout(assert, l, func(l *testLayout, validate func(*testLayout)) {
   651  		scanner := newScanner()
   652  		assert.Empty(scanner.options.ParseArgs([]string{`fdf`, `-rd`, `-m`, `path+content`, `-z`, `0`}))
   653  		assert.True(scanner.options.deleteDupes)
   654  		assert.True(scanner.options.Recursive)
   655  		assert.Equal(matchContent|matchParent|matchPathSuffix, scanner.options.MatchMode)
   656  
   657  		assert.NoError(scanner.Scan())
   658  		fmt.Println(scanner.totals.PrettyFormat(scanner.options.Verb()))
   659  		assert.Equal(uint64(12), scanner.totals.Files.count)
   660  		assert.Equal(uint64(48), scanner.totals.Files.size)
   661  		assert.Equal(uint64(8), scanner.totals.Unique.count)
   662  		assert.Equal(uint64(32), scanner.totals.Unique.size)
   663  		assert.Equal(uint64(0), scanner.totals.Links.count)
   664  		assert.Equal(uint64(0), scanner.totals.Links.size)
   665  		assert.Equal(uint64(0), scanner.totals.Cloned.count)
   666  		assert.Equal(uint64(0), scanner.totals.Cloned.size)
   667  		assert.Equal(uint64(0), scanner.totals.Dupes.count)
   668  		assert.Equal(uint64(0), scanner.totals.Dupes.size)
   669  		assert.Equal(uint64(4), scanner.totals.Processed.count)
   670  		assert.Equal(uint64(16), scanner.totals.Processed.size)
   671  		assert.Equal(uint64(0), scanner.totals.Skipped.count)
   672  		assert.Equal(uint64(0), scanner.totals.Skipped.size)
   673  		assert.Equal(uint64(0), scanner.totals.Errors.count)
   674  		assert.Equal(uint64(0), scanner.totals.Errors.size)
   675  		l.contentOverride = true
   676  		l.content = map[string]string{
   677  			"foo/a/fizz1": "fizz",
   678  			"foo/a/buzz":  "buzz",
   679  			"foo/b/fizz1": "fizz",
   680  			"foo/b/buzz":  "buzz",
   681  			"bar/a/fizz1": "fizz",
   682  			"bar/a/buzz":  "buzz",
   683  			"bar/b/fizz1": "fizz",
   684  			"bar/b/buzz":  "buzz",
   685  		}
   686  		validate(l)
   687  	})
   688  }
   689  
   690  func TestScanner_PathSuffix(t *testing.T) {
   691  	assert := require.New(t)
   692  	l := &testLayout{
   693  		dirs: []string{
   694  			"./foo/a",
   695  			"./foo/b",
   696  			"./bar/a",
   697  			"./bar/b",
   698  		},
   699  		content: map[string]string{
   700  			"fizz1": "fizz",
   701  			"fizz2": "fizz",
   702  			"buzz":  "buzz",
   703  		},
   704  		diffContent: nil,
   705  		diffSize:    nil,
   706  	}
   707  	setupTestLayout(assert, l, func(l *testLayout, validate func(*testLayout)) {
   708  		scanner := newScanner()
   709  		assert.Empty(scanner.options.ParseArgs([]string{`fdf`, `-rd`, `-m`, `relpath+content`, `-z`, `0`}))
   710  		assert.True(scanner.options.deleteDupes)
   711  		assert.True(scanner.options.Recursive)
   712  		assert.Equal(matchContent|matchParent|matchPathSuffix, scanner.options.MatchMode)
   713  
   714  		assert.NoError(scanner.Scan("./foo", "./bar"))
   715  		fmt.Println(scanner.totals.PrettyFormat(scanner.options.Verb()))
   716  		assert.Equal(uint64(12), scanner.totals.Files.count)
   717  		assert.Equal(uint64(48), scanner.totals.Files.size)
   718  		assert.Equal(uint64(4), scanner.totals.Unique.count)
   719  		assert.Equal(uint64(16), scanner.totals.Unique.size)
   720  		assert.Equal(uint64(0), scanner.totals.Links.count)
   721  		assert.Equal(uint64(0), scanner.totals.Links.size)
   722  		assert.Equal(uint64(0), scanner.totals.Cloned.count)
   723  		assert.Equal(uint64(0), scanner.totals.Cloned.size)
   724  		assert.Equal(uint64(0), scanner.totals.Dupes.count)
   725  		assert.Equal(uint64(0), scanner.totals.Dupes.size)
   726  		assert.Equal(uint64(8), scanner.totals.Processed.count)
   727  		assert.Equal(uint64(32), scanner.totals.Processed.size)
   728  		assert.Equal(uint64(0), scanner.totals.Skipped.count)
   729  		assert.Equal(uint64(0), scanner.totals.Skipped.size)
   730  		assert.Equal(uint64(0), scanner.totals.Errors.count)
   731  		assert.Equal(uint64(0), scanner.totals.Errors.size)
   732  		l.contentOverride = true
   733  		l.content = map[string]string{
   734  			"foo/a/fizz1": "fizz",
   735  			"foo/a/buzz":  "buzz",
   736  			"foo/b/fizz1": "fizz",
   737  			"foo/b/buzz":  "buzz",
   738  		}
   739  		validate(l)
   740  	})
   741  }
   742  
   743  type testLayout struct {
   744  	dirs []string
   745  
   746  	// Duplicated per dirList[n]
   747  	content map[string]string
   748  
   749  	// Used for certain cases, such as delete, that need to override the default layout
   750  	// If this is set, content keys are relative paths and all other fields are ignored
   751  	contentOverride bool
   752  
   753  	// dirList[n]/different == different[n]
   754  	diffContent []string
   755  
   756  	// dirList[n]/diffsize == diffsize[n]
   757  	diffSize []string
   758  }
   759  
   760  func setupTest(assert *require.Assertions, f func(l *testLayout, validate func(*testLayout))) {
   761  	l := &testLayout{
   762  		dirs: []string{
   763  			"./a",
   764  			"./b",
   765  		},
   766  		content: map[string]string{
   767  			"bar":    "bar\n",
   768  			"bar2":   "bar\n",
   769  			"bar3":   "bar\n",
   770  			"foo":    "foo\n",
   771  			"foo2":   "foo\n",
   772  			"foo3":   "foo\n",
   773  			"empty":  "",
   774  			"empty2": "",
   775  			"empty3": "",
   776  		},
   777  		diffContent: []string{
   778  			"fizz\n",
   779  			"buzz\n",
   780  		},
   781  		diffSize: []string{
   782  			"foobar\n",
   783  			"foobar2\n",
   784  		},
   785  	}
   786  	setupTestLayout(assert, l, f)
   787  }
   788  
   789  func setupTestLayout(assert *require.Assertions, l *testLayout, f func(l *testLayout, validate func(*testLayout))) {
   790  	dir, err := ioutil.TempDir("", "fdftest")
   791  	assert.NoError(err)
   792  	defer os.RemoveAll(dir)
   793  	assert.NoError(os.Chdir(dir))
   794  
   795  	for i, d := range l.dirs {
   796  		assert.NoError(os.MkdirAll(d, 0777))
   797  		for f, c := range l.content {
   798  			assert.NoError(ioutil.WriteFile(filepath.Join(d, fmt.Sprintf("%s", f)), []byte(c), 0666))
   799  		}
   800  		if len(l.diffContent) != 0 {
   801  			assert.NoError(ioutil.WriteFile(filepath.Join(d, "diffContent"), []byte(l.diffContent[i]), 0666))
   802  		}
   803  		if len(l.diffSize) != 0 {
   804  			assert.NoError(ioutil.WriteFile(filepath.Join(d, "diffSize"), []byte(l.diffSize[i]), 0666))
   805  		}
   806  	}
   807  
   808  	f(l, func(l *testLayout) {
   809  		glob, err := zglob.Glob("./**/*")
   810  		var g []string
   811  		for _, x := range glob {
   812  			st, err := os.Stat(x)
   813  			assert.NoError(err)
   814  			if !st.IsDir() {
   815  				g = append(g, x)
   816  			}
   817  		}
   818  		assert.NoError(err)
   819  
   820  		if l.contentOverride {
   821  			assert.Len(g, len(l.content), "wrong number of files")
   822  
   823  			for f, c := range l.content {
   824  				b, err := ioutil.ReadFile(f)
   825  				assert.NoError(err)
   826  				assert.Equalf(c, string(b), "%s", f)
   827  			}
   828  		} else {
   829  			assert.Len(g, (len(l.dirs)*len(l.content))+len(l.diffContent)+len(l.diffSize))
   830  
   831  			for i, d := range l.dirs {
   832  				for f, c := range l.content {
   833  					b, err := ioutil.ReadFile(filepath.Join(d, f))
   834  					assert.NoError(err)
   835  					assert.Equalf(c, string(b), "%s", f)
   836  				}
   837  				if len(l.diffContent) != 0 {
   838  					b, err := ioutil.ReadFile(filepath.Join(d, "diffContent"))
   839  					assert.NoError(err)
   840  					assert.Equal(l.diffContent[i], string(b))
   841  				}
   842  				if len(l.diffSize) != 0 {
   843  					b, err := ioutil.ReadFile(filepath.Join(d, "diffSize"))
   844  					assert.NoError(err)
   845  					assert.Equal(l.diffSize[i], string(b))
   846  				}
   847  			}
   848  		}
   849  	})
   850  }