github.com/3JoB/vfs@v1.0.0/walk_test.go (about)

     1  package vfs
     2  
     3  import (
     4  	"errors"
     5  	"os"
     6  	"path/filepath"
     7  	"reflect"
     8  	"runtime"
     9  	"strings"
    10  	"testing"
    11  )
    12  
    13  type Node struct {
    14  	name    string
    15  	entries []*Node // nil if the entry is a file
    16  	mark    int
    17  }
    18  
    19  var tree = &Node{
    20  	name: "testdata",
    21  	entries: []*Node{
    22  		{"a", nil, 0},
    23  		{"b", []*Node{}, 0},
    24  		{"c", nil, 0},
    25  		{
    26  			"d",
    27  			[]*Node{
    28  				{"x", nil, 0},
    29  				{"y", []*Node{}, 0},
    30  				{
    31  					"z",
    32  					[]*Node{
    33  						{"u", nil, 0},
    34  						{"v", nil, 0},
    35  					},
    36  					0,
    37  				},
    38  			},
    39  			0,
    40  		},
    41  	},
    42  	mark: 0,
    43  }
    44  
    45  func walkTree(n *Node, path string, f func(path string, n *Node)) {
    46  	f(path, n)
    47  	for _, e := range n.entries {
    48  		walkTree(e, filepath.Join(path, e.name), f)
    49  	}
    50  }
    51  
    52  func makeTree(t *testing.T, fs Filesystem) {
    53  	walkTree(tree, tree.name, func(path string, n *Node) {
    54  		if n.entries == nil {
    55  			fd, err := fs.OpenFile(path, os.O_CREATE, os.ModePerm)
    56  			if err != nil {
    57  				t.Errorf("makeTree: %v", err)
    58  				return
    59  			}
    60  			fd.Close()
    61  		} else {
    62  			fs.Mkdir(path, 0770)
    63  		}
    64  	})
    65  }
    66  
    67  func markTree(n *Node) { walkTree(n, "", func(path string, n *Node) { n.mark++ }) }
    68  
    69  func checkMarks(t *testing.T, report bool) {
    70  	walkTree(tree, tree.name, func(path string, n *Node) {
    71  		if n.mark != 1 && report {
    72  			t.Errorf("node %s mark = %d; expected 1", path, n.mark)
    73  		}
    74  		n.mark = 0
    75  	})
    76  }
    77  
    78  // Assumes that each node name is unique. Good enough for a test.
    79  // If clear is true, any incoming error is cleared before return. The errors
    80  // are always accumulated, though.
    81  func mark(info os.FileInfo, err error, errors *[]error, clear bool) error {
    82  	name := info.Name()
    83  	walkTree(tree, tree.name, func(path string, n *Node) {
    84  		if n.name == name {
    85  			n.mark++
    86  		}
    87  	})
    88  	if err != nil {
    89  		*errors = append(*errors, err)
    90  		if clear {
    91  			return nil
    92  		}
    93  		return err
    94  	}
    95  	return nil
    96  }
    97  
    98  func chtmpdir(t *testing.T) (restore func()) {
    99  	oldwd, err := os.Getwd()
   100  	if err != nil {
   101  		t.Fatalf("chtmpdir: %v", err)
   102  	}
   103  	d, err := os.MkdirTemp("", "test")
   104  	if err != nil {
   105  		t.Fatalf("chtmpdir: %v", err)
   106  	}
   107  	if err := os.Chdir(d); err != nil {
   108  		t.Fatalf("chtmpdir: %v", err)
   109  	}
   110  	return func() {
   111  		if err := os.Chdir(oldwd); err != nil {
   112  			t.Fatalf("chtmpdir: %v", err)
   113  		}
   114  		os.RemoveAll(d)
   115  	}
   116  }
   117  
   118  func TestWalk(t *testing.T) {
   119  	if runtime.GOOS == "darwin" {
   120  		switch runtime.GOARCH {
   121  		case "arm", "arm64":
   122  			restore := chtmpdir(t)
   123  			defer restore()
   124  		}
   125  	}
   126  
   127  	tmpDir, err := os.MkdirTemp("", "TestWalk")
   128  	if err != nil {
   129  		t.Fatal("creating temp dir:", err)
   130  	}
   131  	defer os.RemoveAll(tmpDir)
   132  
   133  	origDir, err := os.Getwd()
   134  	if err != nil {
   135  		t.Fatal("finding working dir:", err)
   136  	}
   137  	if err = os.Chdir(tmpDir); err != nil {
   138  		t.Fatal("entering temp dir:", err)
   139  	}
   140  	defer os.Chdir(origDir)
   141  
   142  	fs := OS()
   143  
   144  	makeTree(t, fs)
   145  	errors := make([]error, 0, 10)
   146  	clear := true
   147  	markFn := func(path string, info os.FileInfo, err error) error {
   148  		return mark(info, err, &errors, clear)
   149  	}
   150  	// Expect no errors.
   151  	err = Walk(fs, tree.name, markFn)
   152  	if err != nil {
   153  		t.Fatalf("no error expected, found: %s", err)
   154  	}
   155  	if len(errors) != 0 {
   156  		t.Fatalf("unexpected errors: %s", errors)
   157  	}
   158  	checkMarks(t, true)
   159  	errors = errors[0:0]
   160  
   161  	// Test permission errors. Only possible if we're not root
   162  	// and only on some file systems (AFS, FAT).  To avoid errors during
   163  	// all.bash on those file systems, skip during go test -short.
   164  	if os.Getuid() > 0 && !testing.Short() {
   165  		// introduce 2 errors: chmod top-level directories to 0
   166  		os.Chmod(filepath.Join(tree.name, tree.entries[1].name), 0)
   167  		os.Chmod(filepath.Join(tree.name, tree.entries[3].name), 0)
   168  
   169  		// 3) capture errors, expect two.
   170  		// mark respective subtrees manually
   171  		markTree(tree.entries[1])
   172  		markTree(tree.entries[3])
   173  		// correct double-marking of directory itself
   174  		tree.entries[1].mark--
   175  		tree.entries[3].mark--
   176  		err := Walk(fs, tree.name, markFn)
   177  		if err != nil {
   178  			t.Fatalf("expected no error return from Walk, got %s", err)
   179  		}
   180  		if len(errors) != 2 {
   181  			t.Errorf("expected 2 errors, got %d: %s", len(errors), errors)
   182  		}
   183  		// the inaccessible subtrees were marked manually
   184  		checkMarks(t, true)
   185  		errors = errors[0:0]
   186  
   187  		// 4) capture errors, stop after first error.
   188  		// mark respective subtrees manually
   189  		markTree(tree.entries[1])
   190  		markTree(tree.entries[3])
   191  		// correct double-marking of directory itself
   192  		tree.entries[1].mark--
   193  		tree.entries[3].mark--
   194  		clear = false // error will stop processing
   195  		err = Walk(fs, tree.name, markFn)
   196  		if err == nil {
   197  			t.Fatalf("expected error return from Walk")
   198  		}
   199  		if len(errors) != 1 {
   200  			t.Errorf("expected 1 error, got %d: %s", len(errors), errors)
   201  		}
   202  		// the inaccessible subtrees were marked manually
   203  		checkMarks(t, false)
   204  		errors = errors[0:0]
   205  
   206  		// restore permissions
   207  		os.Chmod(filepath.Join(tree.name, tree.entries[1].name), 0770)
   208  		os.Chmod(filepath.Join(tree.name, tree.entries[3].name), 0770)
   209  	}
   210  }
   211  
   212  func touch(t *testing.T, fs Filesystem, name string) {
   213  	f, err := fs.OpenFile(name, os.O_CREATE, os.ModePerm)
   214  	if err != nil {
   215  		t.Fatal(err)
   216  	}
   217  	if err := f.Close(); err != nil {
   218  		t.Fatal(err)
   219  	}
   220  }
   221  
   222  func TestWalkSkipDirOnFile(t *testing.T) {
   223  	td, err := os.MkdirTemp("", "walktest")
   224  	if err != nil {
   225  		t.Fatal(err)
   226  	}
   227  	defer os.RemoveAll(td)
   228  
   229  	if err := os.MkdirAll(filepath.Join(td, "dir"), 0755); err != nil {
   230  		t.Fatal(err)
   231  	}
   232  
   233  	fs := OS()
   234  
   235  	touch(t, fs, filepath.Join(td, "dir/foo1"))
   236  	touch(t, fs, filepath.Join(td, "dir/foo2"))
   237  
   238  	sawFoo2 := false
   239  	walker := func(path string, info os.FileInfo, err error) error {
   240  		if strings.HasSuffix(path, "foo2") {
   241  			sawFoo2 = true
   242  		}
   243  		if strings.HasSuffix(path, "foo1") {
   244  			return filepath.SkipDir
   245  		}
   246  		return nil
   247  	}
   248  
   249  	err = Walk(fs, td, walker)
   250  	if err != nil {
   251  		t.Fatal(err)
   252  	}
   253  	if sawFoo2 {
   254  		t.Errorf("SkipDir on file foo1 did not block processing of foo2")
   255  	}
   256  
   257  	err = Walk(fs, filepath.Join(td, "dir"), walker)
   258  	if err != nil {
   259  		t.Fatal(err)
   260  	}
   261  	if sawFoo2 {
   262  		t.Errorf("SkipDir on file foo1 did not block processing of foo2")
   263  	}
   264  }
   265  
   266  type statWrapper struct {
   267  	Filesystem
   268  
   269  	statErr error
   270  }
   271  
   272  func (s *statWrapper) Lstat(path string) (os.FileInfo, error) {
   273  	if strings.HasSuffix(path, "stat-error") {
   274  		return nil, s.statErr
   275  	}
   276  	return os.Lstat(path)
   277  }
   278  
   279  func TestWalkFileError(t *testing.T) {
   280  	td, err := os.MkdirTemp("", "walktest")
   281  	if err != nil {
   282  		t.Fatal(err)
   283  	}
   284  	defer os.RemoveAll(td)
   285  
   286  	fs := Filesystem(OS())
   287  
   288  	touch(t, fs, filepath.Join(td, "foo"))
   289  	touch(t, fs, filepath.Join(td, "bar"))
   290  	dir := filepath.Join(td, "dir")
   291  	if err := MkdirAll(fs, filepath.Join(td, "dir"), 0755); err != nil {
   292  		t.Fatal(err)
   293  	}
   294  	touch(t, fs, filepath.Join(dir, "baz"))
   295  	touch(t, fs, filepath.Join(dir, "stat-error"))
   296  	statErr := errors.New("some stat error")
   297  
   298  	fs = &statWrapper{Filesystem: fs, statErr: statErr}
   299  
   300  	got := map[string]error{}
   301  	err = Walk(fs, td, func(path string, fi os.FileInfo, err error) error {
   302  		rel, _ := filepath.Rel(td, path)
   303  		got[filepath.ToSlash(rel)] = err
   304  		return nil
   305  	})
   306  	if err != nil {
   307  		t.Errorf("Walk error: %v", err)
   308  	}
   309  	want := map[string]error{
   310  		".":              nil,
   311  		"foo":            nil,
   312  		"bar":            nil,
   313  		"dir":            nil,
   314  		"dir/baz":        nil,
   315  		"dir/stat-error": statErr,
   316  	}
   317  	if !reflect.DeepEqual(got, want) {
   318  		t.Errorf("Walked %#v; want %#v", got, want)
   319  	}
   320  }