github.com/charlievieth/fastwalk@v1.0.3/adapters_test.go (about)

     1  package fastwalk_test
     2  
     3  import (
     4  	"errors"
     5  	"io/fs"
     6  	"os"
     7  	"path/filepath"
     8  	"reflect"
     9  	"runtime"
    10  	"sort"
    11  	"strings"
    12  	"sync"
    13  	"sync/atomic"
    14  	"testing"
    15  
    16  	"github.com/charlievieth/fastwalk"
    17  )
    18  
    19  func TestIgnoreDuplicateDirs(t *testing.T) {
    20  	tempdir, err := os.MkdirTemp("", "test-fast-walk")
    21  	if err != nil {
    22  		t.Fatal(err)
    23  	}
    24  	// on macOS the tempdir is a symlink
    25  	tempdir, err = filepath.EvalSymlinks(tempdir)
    26  	if err != nil {
    27  		t.Fatal(err)
    28  	}
    29  	defer cleanupOrLogTempDir(t, tempdir)
    30  
    31  	files := map[string]string{
    32  		"bar/bar.go":  "one",
    33  		"foo/foo.go":  "two",
    34  		"skip/baz.go": "three", // we skip "skip", but visit "baz.go" via "symdir"
    35  		"symdir":      "LINK:skip",
    36  		"bar/symdir":  "LINK:../foo/",
    37  		"bar/loop":    "LINK:../bar/", // symlink loop
    38  	}
    39  	testCreateFiles(t, tempdir, files)
    40  
    41  	want := map[string]os.FileMode{
    42  		"":                   os.ModeDir,
    43  		"/src":               os.ModeDir,
    44  		"/src/bar":           os.ModeDir,
    45  		"/src/bar/bar.go":    0,
    46  		"/src/bar/symdir":    os.ModeSymlink,
    47  		"/src/bar/loop":      os.ModeSymlink,
    48  		"/src/foo":           os.ModeDir,
    49  		"/src/foo/foo.go":    0,
    50  		"/src/symdir":        os.ModeSymlink,
    51  		"/src/symdir/baz.go": 0,
    52  		"/src/skip":          os.ModeDir,
    53  	}
    54  
    55  	runTest := func(t *testing.T, conf *fastwalk.Config) {
    56  		var mu sync.Mutex
    57  		got := make(map[string]os.FileMode)
    58  		walkFn := fastwalk.IgnoreDuplicateDirs(func(path string, de fs.DirEntry, err error) error {
    59  			requireNoError(t, err)
    60  			if err != nil {
    61  				return err
    62  			}
    63  
    64  			// Resolve links for regular files since we don't know which directory
    65  			// or link we traversed to visit them. Exclude "baz.go" because we want
    66  			// to test that we visited it through it's link.
    67  			if de.Type().IsRegular() && de.Name() != "baz.go" {
    68  				realpath, err := filepath.EvalSymlinks(path)
    69  				if err != nil {
    70  					t.Error(err)
    71  					return err
    72  				}
    73  				path = realpath
    74  			}
    75  			if !strings.HasPrefix(path, tempdir) {
    76  				t.Errorf("Path %q not a child of TMPDIR %q", path, tempdir)
    77  				return errors.New("abort")
    78  			}
    79  			key := filepath.ToSlash(strings.TrimPrefix(path, tempdir))
    80  
    81  			mu.Lock()
    82  			defer mu.Unlock()
    83  			got[key] = de.Type().Type()
    84  
    85  			if de.Name() == "skip" {
    86  				return filepath.SkipDir
    87  			}
    88  			return nil
    89  		})
    90  		if err := fastwalk.Walk(conf, tempdir, walkFn); err != nil {
    91  			t.Error("fastwalk:", err)
    92  		}
    93  		if !reflect.DeepEqual(want, got) {
    94  			t.Errorf("walk mismatch.\n got:\n%v\nwant:\n%v", formatFileModes(got), formatFileModes(want))
    95  			diffFileModes(t, got, want)
    96  		}
    97  	}
    98  
    99  	t.Run("NoFollow", func(t *testing.T) {
   100  		runTest(t, &fastwalk.Config{Follow: false})
   101  	})
   102  
   103  	// Test that setting Follow to true has no impact on the behavior
   104  	t.Run("Follow", func(t *testing.T) {
   105  		runTest(t, &fastwalk.Config{Follow: true})
   106  	})
   107  
   108  	t.Run("Error", func(t *testing.T) {
   109  		tempdir := t.TempDir()
   110  		if err := os.WriteFile(tempdir+"/error_test", []byte("error"), 0644); err != nil {
   111  			t.Fatal(err)
   112  		}
   113  		want := errors.New("my error")
   114  		var callCount int32
   115  		walkFn := fastwalk.IgnoreDuplicateDirs(func(path string, de fs.DirEntry, err error) error {
   116  			atomic.AddInt32(&callCount, 1)
   117  			return want
   118  		})
   119  		err := fastwalk.Walk(nil, tempdir, walkFn)
   120  		if !errors.Is(err, want) {
   121  			t.Errorf("Error: want: %v got: %v", want, err)
   122  		}
   123  
   124  	})
   125  }
   126  
   127  func TestIgnoreDuplicateFiles(t *testing.T) {
   128  	tempdir := t.TempDir()
   129  	files := map[string]string{
   130  		"foo/foo.go":       "one",
   131  		"bar/bar.go":       "LINK:../foo/foo.go",
   132  		"bar/baz.go":       "two",
   133  		"broken/broken.go": "LINK:../nonexistent",
   134  		"bar/loop":         "LINK:../bar/", // symlink loop
   135  		"file.go":          "three",
   136  
   137  		// Use multiple symdirs to increase the chance that one
   138  		// of these and not "foo" is followed first.
   139  		"symdir1": "LINK:foo",
   140  		"symdir2": "LINK:foo",
   141  		"symdir3": "LINK:foo",
   142  		"symdir4": "LINK:foo",
   143  	}
   144  	if runtime.GOOS == "windows" {
   145  		delete(files, "broken/broken.go")
   146  	}
   147  	testCreateFiles(t, tempdir, files)
   148  
   149  	var expectedContents []string
   150  	for _, contents := range files {
   151  		if !strings.HasPrefix(contents, "LINK:") {
   152  			expectedContents = append(expectedContents, contents)
   153  		}
   154  	}
   155  	sort.Strings(expectedContents)
   156  
   157  	var (
   158  		mu       sync.Mutex
   159  		seen     []os.FileInfo
   160  		contents []string
   161  	)
   162  	walkFn := fastwalk.IgnoreDuplicateFiles(func(path string, de fs.DirEntry, err error) error {
   163  		requireNoError(t, err)
   164  		fi1, err := fastwalk.StatDirEntry(path, de)
   165  		if err != nil {
   166  			t.Error(err)
   167  			return err
   168  		}
   169  		mu.Lock()
   170  		defer mu.Unlock()
   171  		for _, fi2 := range seen {
   172  			if os.SameFile(fi1, fi2) {
   173  				t.Errorf("Visited file twice: %q (%s) and %q (%s)",
   174  					path, fi1.Mode(), fi2.Name(), fi2.Mode())
   175  			}
   176  		}
   177  		seen = append(seen, fi1)
   178  		if fi1.Mode().IsRegular() {
   179  			data, err := os.ReadFile(path)
   180  			if err != nil {
   181  				return err
   182  			}
   183  			contents = append(contents, string(data))
   184  		}
   185  		return nil
   186  	})
   187  	if err := fastwalk.Walk(nil, tempdir, walkFn); err != nil {
   188  		t.Fatal(err)
   189  	}
   190  
   191  	sort.Strings(contents)
   192  	if !reflect.DeepEqual(expectedContents, contents) {
   193  		t.Errorf("File contents want: %q got: %q", expectedContents, contents)
   194  	}
   195  }
   196  
   197  func TestIgnorePermissionErrors(t *testing.T) {
   198  	var called bool
   199  	fn := fastwalk.IgnorePermissionErrors(func(path string, _ fs.DirEntry, err error) error {
   200  		called = true
   201  		if err != nil {
   202  			t.Fatal(err)
   203  		}
   204  		return nil
   205  	})
   206  
   207  	t.Run("PermissionError", func(t *testing.T) {
   208  		err := fn("", nil, &os.PathError{Op: "open", Path: "foo.go", Err: os.ErrPermission})
   209  		if err != nil {
   210  			t.Fatal(err)
   211  		}
   212  		if called {
   213  			t.Fatal("walkFn should not have been called with os.ErrPermission")
   214  		}
   215  	})
   216  
   217  	t.Run("NilError", func(t *testing.T) {
   218  		called = false
   219  		if err := fn("", nil, nil); err != nil {
   220  			t.Fatal(err)
   221  		}
   222  		if !called {
   223  			t.Fatal("walkFn should have been called with nil error")
   224  		}
   225  	})
   226  
   227  	t.Run("OtherError", func(t *testing.T) {
   228  		fn := fastwalk.IgnorePermissionErrors(func(path string, _ fs.DirEntry, err error) error {
   229  			return err
   230  		})
   231  		want := &os.PathError{Op: "open", Path: "foo.go", Err: os.ErrExist}
   232  		if got := fn("", nil, want); got != want {
   233  			t.Fatalf("want error: %v got: %v", want, got)
   234  		}
   235  	})
   236  }