github.com/v2fly/tools@v0.100.0/internal/fastwalk/fastwalk_test.go (about)

     1  // Copyright 2016 The Go Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  package fastwalk_test
     6  
     7  import (
     8  	"bytes"
     9  	"flag"
    10  	"fmt"
    11  	"io/ioutil"
    12  	"os"
    13  	"path/filepath"
    14  	"reflect"
    15  	"runtime"
    16  	"sort"
    17  	"strings"
    18  	"sync"
    19  	"testing"
    20  
    21  	"github.com/v2fly/tools/internal/fastwalk"
    22  )
    23  
    24  func formatFileModes(m map[string]os.FileMode) string {
    25  	var keys []string
    26  	for k := range m {
    27  		keys = append(keys, k)
    28  	}
    29  	sort.Strings(keys)
    30  	var buf bytes.Buffer
    31  	for _, k := range keys {
    32  		fmt.Fprintf(&buf, "%-20s: %v\n", k, m[k])
    33  	}
    34  	return buf.String()
    35  }
    36  
    37  func testFastWalk(t *testing.T, files map[string]string, callback func(path string, typ os.FileMode) error, want map[string]os.FileMode) {
    38  	tempdir, err := ioutil.TempDir("", "test-fast-walk")
    39  	if err != nil {
    40  		t.Fatal(err)
    41  	}
    42  	defer os.RemoveAll(tempdir)
    43  
    44  	symlinks := map[string]string{}
    45  	for path, contents := range files {
    46  		file := filepath.Join(tempdir, "/src", path)
    47  		if err := os.MkdirAll(filepath.Dir(file), 0755); err != nil {
    48  			t.Fatal(err)
    49  		}
    50  		var err error
    51  		if strings.HasPrefix(contents, "LINK:") {
    52  			symlinks[file] = filepath.FromSlash(strings.TrimPrefix(contents, "LINK:"))
    53  		} else {
    54  			err = ioutil.WriteFile(file, []byte(contents), 0644)
    55  		}
    56  		if err != nil {
    57  			t.Fatal(err)
    58  		}
    59  	}
    60  
    61  	// Create symlinks after all other files. Otherwise, directory symlinks on
    62  	// Windows are unusable (see https://golang.org/issue/39183).
    63  	for file, dst := range symlinks {
    64  		err = os.Symlink(dst, file)
    65  		if err != nil {
    66  			if writeErr := ioutil.WriteFile(file, []byte(dst), 0644); writeErr == nil {
    67  				// Couldn't create symlink, but could write the file.
    68  				// Probably this filesystem doesn't support symlinks.
    69  				// (Perhaps we are on an older Windows and not running as administrator.)
    70  				t.Skipf("skipping because symlinks appear to be unsupported: %v", err)
    71  			}
    72  		}
    73  	}
    74  
    75  	got := map[string]os.FileMode{}
    76  	var mu sync.Mutex
    77  	err = fastwalk.Walk(tempdir, func(path string, typ os.FileMode) error {
    78  		mu.Lock()
    79  		defer mu.Unlock()
    80  		if !strings.HasPrefix(path, tempdir) {
    81  			t.Errorf("bogus prefix on %q, expect %q", path, tempdir)
    82  		}
    83  		key := filepath.ToSlash(strings.TrimPrefix(path, tempdir))
    84  		if old, dup := got[key]; dup {
    85  			t.Errorf("callback called twice for key %q: %v -> %v", key, old, typ)
    86  		}
    87  		got[key] = typ
    88  		return callback(path, typ)
    89  	})
    90  
    91  	if err != nil {
    92  		t.Fatalf("callback returned: %v", err)
    93  	}
    94  	if !reflect.DeepEqual(got, want) {
    95  		t.Errorf("walk mismatch.\n got:\n%v\nwant:\n%v", formatFileModes(got), formatFileModes(want))
    96  	}
    97  }
    98  
    99  func TestFastWalk_Basic(t *testing.T) {
   100  	testFastWalk(t, map[string]string{
   101  		"foo/foo.go":   "one",
   102  		"bar/bar.go":   "two",
   103  		"skip/skip.go": "skip",
   104  	},
   105  		func(path string, typ os.FileMode) error {
   106  			return nil
   107  		},
   108  		map[string]os.FileMode{
   109  			"":                  os.ModeDir,
   110  			"/src":              os.ModeDir,
   111  			"/src/bar":          os.ModeDir,
   112  			"/src/bar/bar.go":   0,
   113  			"/src/foo":          os.ModeDir,
   114  			"/src/foo/foo.go":   0,
   115  			"/src/skip":         os.ModeDir,
   116  			"/src/skip/skip.go": 0,
   117  		})
   118  }
   119  
   120  func TestFastWalk_LongFileName(t *testing.T) {
   121  	longFileName := strings.Repeat("x", 255)
   122  
   123  	testFastWalk(t, map[string]string{
   124  		longFileName: "one",
   125  	},
   126  		func(path string, typ os.FileMode) error {
   127  			return nil
   128  		},
   129  		map[string]os.FileMode{
   130  			"":                     os.ModeDir,
   131  			"/src":                 os.ModeDir,
   132  			"/src/" + longFileName: 0,
   133  		},
   134  	)
   135  }
   136  
   137  func TestFastWalk_Symlink(t *testing.T) {
   138  	testFastWalk(t, map[string]string{
   139  		"foo/foo.go":       "one",
   140  		"bar/bar.go":       "LINK:../foo/foo.go",
   141  		"symdir":           "LINK:foo",
   142  		"broken/broken.go": "LINK:../nonexistent",
   143  	},
   144  		func(path string, typ os.FileMode) error {
   145  			return nil
   146  		},
   147  		map[string]os.FileMode{
   148  			"":                      os.ModeDir,
   149  			"/src":                  os.ModeDir,
   150  			"/src/bar":              os.ModeDir,
   151  			"/src/bar/bar.go":       os.ModeSymlink,
   152  			"/src/foo":              os.ModeDir,
   153  			"/src/foo/foo.go":       0,
   154  			"/src/symdir":           os.ModeSymlink,
   155  			"/src/broken":           os.ModeDir,
   156  			"/src/broken/broken.go": os.ModeSymlink,
   157  		})
   158  }
   159  
   160  func TestFastWalk_SkipDir(t *testing.T) {
   161  	testFastWalk(t, map[string]string{
   162  		"foo/foo.go":   "one",
   163  		"bar/bar.go":   "two",
   164  		"skip/skip.go": "skip",
   165  	},
   166  		func(path string, typ os.FileMode) error {
   167  			if typ == os.ModeDir && strings.HasSuffix(path, "skip") {
   168  				return filepath.SkipDir
   169  			}
   170  			return nil
   171  		},
   172  		map[string]os.FileMode{
   173  			"":                os.ModeDir,
   174  			"/src":            os.ModeDir,
   175  			"/src/bar":        os.ModeDir,
   176  			"/src/bar/bar.go": 0,
   177  			"/src/foo":        os.ModeDir,
   178  			"/src/foo/foo.go": 0,
   179  			"/src/skip":       os.ModeDir,
   180  		})
   181  }
   182  
   183  func TestFastWalk_SkipFiles(t *testing.T) {
   184  	// Directory iteration order is undefined, so there's no way to know
   185  	// which file to expect until the walk happens. Rather than mess
   186  	// with the test infrastructure, just mutate want.
   187  	var mu sync.Mutex
   188  	want := map[string]os.FileMode{
   189  		"":              os.ModeDir,
   190  		"/src":          os.ModeDir,
   191  		"/src/zzz":      os.ModeDir,
   192  		"/src/zzz/c.go": 0,
   193  	}
   194  
   195  	testFastWalk(t, map[string]string{
   196  		"a_skipfiles.go": "a",
   197  		"b_skipfiles.go": "b",
   198  		"zzz/c.go":       "c",
   199  	},
   200  		func(path string, typ os.FileMode) error {
   201  			if strings.HasSuffix(path, "_skipfiles.go") {
   202  				mu.Lock()
   203  				defer mu.Unlock()
   204  				want["/src/"+filepath.Base(path)] = 0
   205  				return fastwalk.ErrSkipFiles
   206  			}
   207  			return nil
   208  		},
   209  		want)
   210  	if len(want) != 5 {
   211  		t.Errorf("saw too many files: wanted 5, got %v (%v)", len(want), want)
   212  	}
   213  }
   214  
   215  func TestFastWalk_TraverseSymlink(t *testing.T) {
   216  	testFastWalk(t, map[string]string{
   217  		"foo/foo.go":   "one",
   218  		"bar/bar.go":   "two",
   219  		"skip/skip.go": "skip",
   220  		"symdir":       "LINK:foo",
   221  	},
   222  		func(path string, typ os.FileMode) error {
   223  			if typ == os.ModeSymlink {
   224  				return fastwalk.ErrTraverseLink
   225  			}
   226  			return nil
   227  		},
   228  		map[string]os.FileMode{
   229  			"":                   os.ModeDir,
   230  			"/src":               os.ModeDir,
   231  			"/src/bar":           os.ModeDir,
   232  			"/src/bar/bar.go":    0,
   233  			"/src/foo":           os.ModeDir,
   234  			"/src/foo/foo.go":    0,
   235  			"/src/skip":          os.ModeDir,
   236  			"/src/skip/skip.go":  0,
   237  			"/src/symdir":        os.ModeSymlink,
   238  			"/src/symdir/foo.go": 0,
   239  		})
   240  }
   241  
   242  var benchDir = flag.String("benchdir", runtime.GOROOT(), "The directory to scan for BenchmarkFastWalk")
   243  
   244  func BenchmarkFastWalk(b *testing.B) {
   245  	b.ReportAllocs()
   246  	for i := 0; i < b.N; i++ {
   247  		err := fastwalk.Walk(*benchDir, func(path string, typ os.FileMode) error { return nil })
   248  		if err != nil {
   249  			b.Fatal(err)
   250  		}
   251  	}
   252  }