add Walk function similar to filepath.Walk, add test for Walk
diff --git a/fs.go b/fs.go
index 593ca23..b1f1283 100644
--- a/fs.go
+++ b/fs.go
@@ -26,6 +26,7 @@
 	"errors"
 	"io"
 	"os"
+	"path/filepath"
 	"sort"
 	"time"
 )
@@ -119,6 +120,87 @@
 	return list, nil
 }
 
+// readDirNames reads the directory named by dirname and returns
+// a sorted list of directory entries.
+// adapted from https://golang.org/src/path/filepath/path.go
+func readDirNames(dirname string, fs Fs) ([]string, error) {
+	f, err := fs.Open(dirname)
+	if err != nil {
+		return nil, err
+	}
+	names, err := f.Readdirnames(-1)
+	f.Close()
+	if err != nil {
+		return nil, err
+	}
+	sort.Strings(names)
+	return names, nil
+}
+
+// walk recursively descends path, calling walkFn
+// adapted from https://golang.org/src/path/filepath/path.go
+func walk(path string, info os.FileInfo, walkFn filepath.WalkFunc, fs Fs) error {
+	err := walkFn(path, info, nil)
+	if err != nil {
+		if info.IsDir() && err == filepath.SkipDir {
+			return nil
+		}
+		return err
+	}
+
+	if !info.IsDir() {
+		return nil
+	}
+
+	names, err := readDirNames(path, fs)
+	if err != nil {
+		return walkFn(path, info, err)
+	}
+
+	for _, name := range names {
+		filename := filepath.Join(path, name)
+		fileInfo, err := lstatIfOs(filename, fs)
+		if err != nil {
+			if err := walkFn(filename, fileInfo, err); err != nil && err != filepath.SkipDir {
+				return err
+			}
+		} else {
+			err = walk(filename, fileInfo, walkFn, fs)
+			if err != nil {
+				if !fileInfo.IsDir() || err != filepath.SkipDir {
+					return err
+				}
+			}
+		}
+	}
+	return nil
+}
+
+// if the filesystem is OsFs use Lstat, else use fs.Stat
+func lstatIfOs(path string, fs Fs) (info os.FileInfo, err error) {
+	_, ok := fs.(*OsFs)
+	if ok {
+		info, err = os.Lstat(path)
+	} else {
+		info, err = fs.Stat(path)
+	}
+	return
+}
+
+// Walk walks the file tree rooted at root, calling walkFn for each file or
+// directory in the tree, including root. All errors that arise visiting files
+// and directories are filtered by walkFn. The files are walked in lexical
+// order, which makes the output deterministic but means that for very
+// large directories Walk can be inefficient.
+// Walk does not follow symbolic links.
+func Walk(root string, walkFn filepath.WalkFunc, fs Fs) error {
+	info, err := lstatIfOs(root, fs)
+	if err != nil {
+		return walkFn(root, nil, err)
+	}
+	return walk(root, info, walkFn, fs)
+}
+
 // byName implements sort.Interface.
 type byName []os.FileInfo
 
diff --git a/fs_test.go b/fs_test.go
index 63cdc33..0097335 100644
--- a/fs_test.go
+++ b/fs_test.go
@@ -28,14 +28,6 @@
 	"testing"
 )
 
-var dot = []string{
-	"fs.go",
-	"fs_test.go",
-	"httpFs.go",
-	"memfile.go",
-	"memmap.go",
-}
-
 var testDir = "/tmp/afero"
 var testSubDir = "/tmp/afero/we/have/to/go/deeper"
 var testName = "test.txt"
@@ -450,6 +442,42 @@
 	return out
 }
 
+func TestWalk(t *testing.T) {
+	outputs := make([]string, len(Fss))
+	for i, fs := range Fss {
+		walkFn := func(path string, info os.FileInfo, err error) error {
+			var size int64
+			if !info.IsDir() {
+				size = info.Size()
+			}
+			outputs[i] += fmt.Sprintln(path, info.Name(), size, info.IsDir(), err)
+			return nil
+		}
+		err := Walk(testDir, walkFn, fs)
+		if err != nil {
+			t.Error(err)
+		}
+	}
+	fail := false
+	for i, o := range outputs {
+		if i == 0 {
+			continue
+		}
+		if o != outputs[i-1] {
+			fail = true
+			break
+		}
+	}
+	if fail {
+		t.Log("Walk outputs not equal!")
+		for i, o := range outputs {
+			t.Log(Fss[i].Name())
+			t.Log(o)
+		}
+		t.Fail()
+	}
+}
+
 func TestReaddirAll(t *testing.T) {
 	defer removeTestDir(t)
 	for _, fs := range Fss {