github.com/AndrusGerman/vfs@v1.0.1/walk_test.go (about)

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