New Upstream Release - golang-github-bep-overlayfs

Ready changes

Summary

Merged new upstream version: 0.8.0 (was: 0.6.0).

Resulting package

Built on 2023-01-20T11:45 (took 3m24s)

The resulting binary packages can be installed (if you have the apt repository enabled) by running one of:

apt install -t fresh-releases golang-github-bep-overlayfs-dev

Lintian Result

Diff

diff --git a/debian/changelog b/debian/changelog
index 492f3a5..0d240e8 100644
--- a/debian/changelog
+++ b/debian/changelog
@@ -1,3 +1,9 @@
+golang-github-bep-overlayfs (0.8.0-1) UNRELEASED; urgency=low
+
+  * New upstream release.
+
+ -- Debian Janitor <janitor@jelmer.uk>  Fri, 20 Jan 2023 11:42:51 -0000
+
 golang-github-bep-overlayfs (0.6.0-2) unstable; urgency=medium
 
   * Source-only upload for migration to testing
diff --git a/go.mod b/go.mod
index dab02d8..bb96eee 100644
--- a/go.mod
+++ b/go.mod
@@ -4,7 +4,7 @@ go 1.18
 
 require (
 	github.com/frankban/quicktest v1.14.2
-	github.com/spf13/afero v1.8.2
+	github.com/spf13/afero v1.9.0
 	golang.org/x/tools v0.1.0
 )
 
@@ -13,6 +13,6 @@ require (
 	github.com/kr/pretty v0.3.0 // indirect
 	github.com/kr/text v0.2.0 // indirect
 	github.com/rogpeppe/go-internal v1.6.1 // indirect
-	golang.org/x/text v0.3.4 // indirect
+	golang.org/x/text v0.3.7 // indirect
 	golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect
 )
diff --git a/go.sum b/go.sum
index 68a2aa1..9c62a55 100644
--- a/go.sum
+++ b/go.sum
@@ -137,8 +137,8 @@ github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:
 github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
 github.com/rogpeppe/go-internal v1.6.1 h1:/FiVV8dS/e+YqF2JvO3yXRFbBLTIuSDkuC7aBOAvL+k=
 github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
-github.com/spf13/afero v1.8.2 h1:xehSyVa0YnHWsJ49JFljMpg1HX19V6NDZ1fkm1Xznbo=
-github.com/spf13/afero v1.8.2/go.mod h1:CtAatgMJh6bJEIs48Ay/FOnkljP3WeGUG0MC1RfAqwo=
+github.com/spf13/afero v1.9.0 h1:sFSLUHgxdnN32Qy38hK3QkYBFXZj9DKjVjCUCtD7juY=
+github.com/spf13/afero v1.9.0/go.mod h1:iUV7ddyEEZPO5gA3zD4fJt6iStLlL+Lg4m2cihcDf8Y=
 github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
 github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
 github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
@@ -283,8 +283,9 @@ golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
 golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
 golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
 golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
-golang.org/x/text v0.3.4 h1:0YWbFKbhXG/wIiuHDSKpS0Iy7FSA+u45VtBMfQcFTTc=
 golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
+golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk=
+golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
 golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
 golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
 golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
diff --git a/overlayfs.go b/overlayfs.go
index 42cca0a..f88b1e5 100644
--- a/overlayfs.go
+++ b/overlayfs.go
@@ -2,6 +2,8 @@ package overlayfs
 
 import (
 	"io"
+	"io/fs"
+	iofs "io/fs"
 	"os"
 	"sync"
 
@@ -13,6 +15,7 @@ var (
 	_ afero.Fs           = (*OverlayFs)(nil)
 	_ afero.Lstater      = (*OverlayFs)(nil)
 	_ afero.File         = (*Dir)(nil)
+	_ fs.ReadDirFile     = (*Dir)(nil)
 )
 
 // FilesystemIterator is an interface for iterating over the wrapped filesystems in order.
@@ -21,6 +24,7 @@ type FilesystemIterator interface {
 	NumFilesystems() int
 }
 
+// Options for the OverlayFs.
 type Options struct {
 	// The filesystems to overlay ordered in priority from left to right.
 	Fss []afero.Fs
@@ -144,9 +148,9 @@ func (ofs *OverlayFs) writeFs() afero.Fs {
 }
 
 // DirsMerger is used to merge two directories.
-type DirsMerger func(lofi, bofi []os.FileInfo) []os.FileInfo
+type DirsMerger func(lofi, bofi []fs.DirEntry) []fs.DirEntry
 
-var defaultDirMerger = func(lofi, bofi []os.FileInfo) []os.FileInfo {
+var defaultDirMerger = func(lofi, bofi []fs.DirEntry) []fs.DirEntry {
 	for _, bofi := range bofi {
 		var found bool
 		for _, lofi := range lofi {
@@ -160,7 +164,6 @@ var defaultDirMerger = func(lofi, bofi []os.FileInfo) []os.FileInfo {
 		}
 	}
 	return lofi
-
 }
 
 var dirPool = &sync.Pool{
@@ -176,50 +179,114 @@ func getDir() *Dir {
 func releaseDir(dir *Dir) {
 	dir.fss = dir.fss[:0]
 	dir.fis = dir.fis[:0]
+	dir.dirOpeners = dir.dirOpeners[:0]
 	dir.offset = 0
 	dir.name = ""
 	dir.err = nil
 	dirPool.Put(dir)
 }
 
+// OpenDir opens a new Dir with dirs to be merged by the given merge func.
+// If merge is nil, a default DirsMerger is used.
+func OpenDir(merge DirsMerger, dirOpeners ...func() (afero.File, error)) (*Dir, error) {
+	if merge == nil {
+		merge = defaultDirMerger
+	}
+	dir := getDir()
+	dir.dirOpeners = dirOpeners
+	dir.merge = merge
+	return dir, nil
+}
+
 // Dir is an afero.File that represents list of directories that will be merged in Readdir and Readdirnames.
 type Dir struct {
-	name  string
-	fss   []afero.Fs
+	// It's either a named directory in a slice of filesystems or a slice of directories.
+	name string
+	fss  []afero.Fs
+
+	// Set if fss is not set.
+	dirOpeners []func() (afero.File, error)
+
 	merge DirsMerger
 
 	err    error
 	offset int
-	fis    []os.FileInfo
+	fis    []fs.DirEntry
 }
 
 // Readdir implements afero.File.Readdir.
 // If n > 0, Readdir returns at most n.
+// Note that Dir also implements fs.ReadDirFile, which is more efficient.
 func (d *Dir) Readdir(n int) ([]os.FileInfo, error) {
+	dirEntries, err := d.ReadDir(n)
+	if err != nil {
+		return nil, err
+	}
+	fis := make([]os.FileInfo, len(dirEntries))
+	for i, dirEntry := range dirEntries {
+		fi, err := dirEntry.Info()
+		if err != nil {
+			return nil, err
+		}
+		fis[i] = fi
+	}
+	return fis, nil
+}
+
+// ReadDir implements fs.ReadDirFile.
+func (d *Dir) ReadDir(n int) ([]fs.DirEntry, error) {
 	if d.err != nil {
 		return nil, d.err
 	}
-	if len(d.fss) == 0 {
+	if d.isClosed() {
 		return nil, os.ErrClosed
 	}
 
 	if d.offset == 0 {
-		readDir := func(fs afero.Fs) error {
-			f, err := fs.Open(d.name)
-			if err != nil {
-				return err
+		readDir := func(fs afero.Fs, f afero.File) error {
+			var err error
+			if f == nil {
+				f, err = fs.Open(d.name)
+				if err != nil {
+					return err
+				}
 			}
 			defer f.Close()
-			fi, err := f.Readdir(-1)
-			if err != nil {
-				return err
+
+			var dirEntries []iofs.DirEntry
+
+			if rdf, ok := f.(iofs.ReadDirFile); ok {
+				dirEntries, err = rdf.ReadDir(-1)
+				if err != nil {
+					return err
+				}
+			} else {
+				var fis []os.FileInfo
+				fis, err = f.Readdir(-1)
+				if err != nil {
+					return err
+				}
+				dirEntries = make([]iofs.DirEntry, len(fis))
+				for i, fi := range fis {
+					dirEntries[i] = dirEntry{fi}
+				}
 			}
-			d.fis = d.merge(d.fis, fi)
+
+			d.fis = d.merge(d.fis, dirEntries)
 			return nil
 		}
 
 		for _, fs := range d.fss {
-			if err := readDir(fs); err != nil {
+			if err := readDir(fs, nil); err != nil {
+				return nil, err
+			}
+		}
+		for _, open := range d.dirOpeners {
+			f, err := open()
+			if err != nil {
+				return nil, err
+			}
+			if err := readDir(nil, f); err != nil {
 				return nil, err
 			}
 		}
@@ -232,7 +299,7 @@ func (d *Dir) Readdir(n int) ([]os.FileInfo, error) {
 		if d.offset > 0 && len(fis) == 0 {
 			return nil, d.err
 		}
-		fisc := make([]os.FileInfo, len(fis))
+		fisc := make([]fs.DirEntry, len(fis))
 		copy(fisc, fis)
 		return fisc, nil
 	}
@@ -248,21 +315,20 @@ func (d *Dir) Readdir(n int) ([]os.FileInfo, error) {
 
 	defer func() { d.offset += n }()
 
-	fisc := make([]os.FileInfo, len(fis[:n]))
+	fisc := make([]fs.DirEntry, len(fis[:n]))
 	copy(fisc, fis[:n])
 
 	return fisc, nil
-
 }
 
 // Readdirnames implements afero.File.Readdirnames.
 // If n > 0, Readdirnames returns at most n.
 func (d *Dir) Readdirnames(n int) ([]string, error) {
-	if len(d.fss) == 0 {
+	if d.isClosed() {
 		return nil, os.ErrClosed
 	}
 
-	fis, err := d.Readdir(n)
+	fis, err := d.ReadDir(n)
 	if err != nil {
 		return nil, err
 	}
@@ -334,3 +400,18 @@ func (d *Dir) Truncate(size int64) error {
 func (d *Dir) WriteString(s string) (ret int, err error) {
 	panic("not supported")
 }
+
+func (d *Dir) isClosed() bool {
+	return len(d.fss) == 0 && len(d.dirOpeners) == 0
+}
+
+// dirEntry is an adapter from os.FileInfo to fs.DirEntry
+type dirEntry struct {
+	fs.FileInfo
+}
+
+var _ fs.DirEntry = dirEntry{}
+
+func (d dirEntry) Type() fs.FileMode { return d.FileInfo.Mode().Type() }
+
+func (d dirEntry) Info() (fs.FileInfo, error) { return d.FileInfo, nil }
diff --git a/overlayfs_test.go b/overlayfs_test.go
index 6307dc8..75e176f 100644
--- a/overlayfs_test.go
+++ b/overlayfs_test.go
@@ -3,9 +3,11 @@ package overlayfs
 import (
 	"bytes"
 	"errors"
+	"fmt"
 	"io"
 	"io/fs"
 	"os"
+	"path/filepath"
 	"sort"
 	"strings"
 	"testing"
@@ -54,15 +56,40 @@ func TestFileystemIterator(t *testing.T) {
 	c.Assert(ofs.Filesystem(2), qt.IsNil)
 }
 
+func TestOpenDir(t *testing.T) {
+	c := qt.New(t)
+	fs1, fs2, fs3 := basicFs("1", "1"), basicFs("1", "2"), basicFs("2", "2")
+	dir, err := OpenDir(
+		nil,
+		func() (afero.File, error) {
+			return fs1.Open("mydir")
+		},
+		func() (afero.File, error) {
+			return fs2.Open("mydir")
+		},
+		func() (afero.File, error) {
+			return fs3.Open("mydir")
+		},
+	)
+	c.Assert(err, qt.IsNil)
+
+	dirEntries, err := dir.ReadDir(-1)
+
+	c.Assert(err, qt.IsNil)
+	c.Assert(dirEntries, qt.HasLen, 4)
+	c.Assert(dir.Close(), qt.IsNil)
+}
+
 func TestReadOps(t *testing.T) {
 	c := qt.New(t)
-	fs1, fs2 := basicFs("1", "1"), basicFs("1", "2")
+	fs1, fs2 := basicFs("1", "1"), basicFs("2", "2")
 	ofs := New(Options{Fss: []afero.Fs{fs1, fs2}})
 
 	c.Assert(ofs.Name(), qt.Equals, "overlayfs")
 
 	// Open
 	c.Assert(readFile(c, ofs, "mydir/f1-1.txt"), qt.Equals, "f1-1")
+	c.Assert(readFile(c, ofs, "mydir/f2-2.txt"), qt.Equals, "f2-2")
 
 	// Stat
 	fi, err := ofs.Stat("mydir/f1-1.txt")
@@ -70,6 +97,9 @@ func TestReadOps(t *testing.T) {
 	c.Assert(fi.Name(), qt.Equals, "f1-1.txt")
 	_, err = ofs.Stat("mydir/notfound.txt")
 	c.Assert(err, qt.ErrorIs, fs.ErrNotExist)
+	fi, err = ofs.Stat("mydir/f2-2.txt")
+	c.Assert(err, qt.IsNil)
+	c.Assert(fi.Name(), qt.Equals, "f2-2.txt")
 
 	// LstatIfPossible
 	fi, _, err = ofs.LstatIfPossible("mydir/f2-1.txt")
@@ -77,7 +107,6 @@ func TestReadOps(t *testing.T) {
 	c.Assert(fi.Name(), qt.Equals, "f2-1.txt")
 	_, _, err = ofs.LstatIfPossible("mydir/notfound.txt")
 	c.Assert(err, qt.ErrorIs, fs.ErrNotExist)
-
 }
 
 func TestReadOpsErrors(t *testing.T) {
@@ -98,7 +127,6 @@ func TestReadOpsErrors(t *testing.T) {
 	c.Assert(fi.Name(), qt.Equals, "f2-1.txt")
 	_, _, err = ofs.LstatIfPossible("mydir/notfound.txt")
 	c.Assert(err, qt.ErrorIs, statErr)
-
 }
 
 func TestOpenRecursive(t *testing.T) {
@@ -111,7 +139,6 @@ func TestOpenRecursive(t *testing.T) {
 
 	c.Assert(readFile(c, ofs1, "mydir/f1-1.txt"), qt.Equals, "f1-1")
 	c.Assert(readFile(c, ofs1, "mydir/f1-2.txt"), qt.Equals, "f1-3")
-
 }
 
 func TestWriteOpsReadonly(t *testing.T) {
@@ -122,9 +149,9 @@ func TestWriteOpsReadonly(t *testing.T) {
 	_, err := ofsReadOnly.Create("mydir/foo.txt")
 	c.Assert(err, qt.ErrorIs, fs.ErrPermission)
 
-	_, err = ofsReadOnly.OpenFile("mydir/foo.txt", os.O_CREATE, 0777)
+	_, err = ofsReadOnly.OpenFile("mydir/foo.txt", os.O_CREATE, 0o777)
 
-	err = ofsReadOnly.Chmod("mydir/foo.txt", 0666)
+	err = ofsReadOnly.Chmod("mydir/foo.txt", 0o666)
 	c.Assert(err, qt.ErrorIs, fs.ErrPermission)
 
 	err = ofsReadOnly.Chown("mydir/foo.txt", 1, 2)
@@ -133,10 +160,10 @@ func TestWriteOpsReadonly(t *testing.T) {
 	err = ofsReadOnly.Chtimes("mydir/foo.txt", time.Now(), time.Now())
 	c.Assert(err, qt.ErrorIs, fs.ErrPermission)
 
-	err = ofsReadOnly.Mkdir("mydir", 0777)
+	err = ofsReadOnly.Mkdir("mydir", 0o777)
 	c.Assert(err, qt.ErrorIs, fs.ErrPermission)
 
-	err = ofsReadOnly.MkdirAll("mydir", 0777)
+	err = ofsReadOnly.MkdirAll("mydir", 0o777)
 	c.Assert(err, qt.ErrorIs, fs.ErrPermission)
 
 	err = ofsReadOnly.Remove("mydir")
@@ -159,7 +186,7 @@ func TestWriteOpsFirstWriteable(t *testing.T) {
 	f.Close()
 }
 
-func TestReadDir(t *testing.T) {
+func TestReaddir(t *testing.T) {
 	c := qt.New(t)
 	fs1, fs2 := basicFs("1", "1"), basicFs("1", "2")
 	fs3, fs4 := basicFs("2", "3"), basicFs("1", "4")
@@ -177,7 +204,7 @@ func TestReadDir(t *testing.T) {
 	c.Assert(dirnames, qt.DeepEquals, []string{"f1-1.txt", "f2-1.txt"})
 }
 
-func TestReadDirN(t *testing.T) {
+func TestReaddirN(t *testing.T) {
 	c := qt.New(t)
 	// 6 files.
 	ofs := New(Options{Fss: []afero.Fs{basicFs("1", "1"), basicFs("2", "2"), basicFs("3", "3")}})
@@ -224,10 +251,9 @@ func TestReadDirN(t *testing.T) {
 	_, err = d.Readdir(-1)
 	c.Assert(err, qt.ErrorIs, io.EOF)
 	c.Assert(d.Close(), qt.IsNil)
-
 }
 
-func TestReadDirStable(t *testing.T) {
+func TestReaddirStable(t *testing.T) {
 	c := qt.New(t)
 
 	// 6 files.
@@ -256,6 +282,21 @@ func TestReadDirStable(t *testing.T) {
 	}
 	checkFi()
 }
+
+func TestReadDir(t *testing.T) {
+	c := qt.New(t)
+	// 6 files.
+	ofs := New(Options{Fss: []afero.Fs{basicFs("1", "1"), basicFs("2", "2"), basicFs("3", "3")}})
+
+	d, _ := ofs.Open("mydir")
+
+	dirEntries, err := d.(fs.ReadDirFile).ReadDir(-1)
+	c.Assert(err, qt.IsNil)
+	c.Assert(len(dirEntries), qt.Equals, 6)
+	c.Assert(dirEntries[0].Name(), qt.Equals, "f1-1.txt")
+
+}
+
 func TestDirOps(t *testing.T) {
 	c := qt.New(t)
 	ofs := New(Options{Fss: []afero.Fs{basicFs("1", "1"), basicFs("2", "1")}})
@@ -318,10 +359,9 @@ func fsFromTxtTar(s string) afero.Fs {
 	data := txtar.Parse([]byte(s))
 	fs := afero.NewMemMapFs()
 	for _, f := range data.Files {
-		if err := afero.WriteFile(fs, f.Name, bytes.TrimSuffix(f.Data, []byte("\n")), 0666); err != nil {
+		if err := afero.WriteFile(fs, f.Name, bytes.TrimSuffix(f.Data, []byte("\n")), 0o666); err != nil {
 			panic(err)
 		}
-
 	}
 	return fs
 }
@@ -387,39 +427,83 @@ func (fs *testFs) Chtimes(name string, atime time.Time, mtime time.Time) error {
 }
 
 func BenchmarkOverlayFs(b *testing.B) {
-	fs1, fs2 := basicFs("1", "1"), basicFs("1", "2")
-	ofs := New(Options{FirstWritable: true, Fss: []afero.Fs{fs1, fs2}})
-	cfs := afero.NewCopyOnWriteFs(fs2, fs1)
-
-	runBenchMark := func(fs afero.Fs, b *testing.B) {
-		for i := 0; i < b.N; i++ {
-			_, err := afero.ReadDir(fs, "mydir")
-			if err != nil {
-				b.Fatal(err)
-			}
-			f, err := fs.Open("mydir/f1-1.txt")
-			if err != nil {
-				b.Fatal(err)
-			}
-			f.Close()
-			d, err := fs.Open("mydir")
-			if err != nil {
-				b.Fatal(err)
-			}
-			d.Close()
-			_, err = ofs.Stat("mydir/f1-1.txt")
-			if err != nil {
+	createFs := func(dir, fileID string, numFiles int) afero.Fs {
+		fs := afero.NewMemMapFs()
+		for i := 0; i < numFiles; i++ {
+			if err := afero.WriteFile(fs, filepath.Join(dir, fmt.Sprintf("f%s-%d.txt", fileID, i)), []byte("foo"), 0o666); err != nil {
 				b.Fatal(err)
 			}
 		}
+		return fs
+	}
+	fs1, fs2, fs3 := createFs("mydir", "1", 10), createFs("mydir", "2", 10), createFs("mydir", "3", 10)
+	fs4, fs5 := createFs("mydir", "1", 4), createFs("myotherdir", "1", 4)
+	ofs := New(Options{FirstWritable: true, Fss: []afero.Fs{fs1, fs2, fs3, fs4, fs5}})
+
+	runBenchMark := func(name string, fn func(b *testing.B)) {
+		b.Run(name, func(b *testing.B) {
+			for i := 0; i < b.N; i++ {
+				fn(b)
+			}
+		})
 	}
 
-	b.Run("OverlayFs", func(b *testing.B) {
-		runBenchMark(ofs, b)
+	runBenchMark("Stat", func(b *testing.B) {
+		_, err := ofs.Stat("mydir/f2-2.txt")
+		if err != nil {
+			b.Fatal(err)
+		}
 	})
 
-	b.Run("CopyOnWriteFs", func(b *testing.B) {
-		runBenchMark(cfs, b)
+	runBenchMark("Open file", func(b *testing.B) {
+		f, err := ofs.Open("mydir/f2-2.txt")
+		if err != nil {
+			b.Fatal(err)
+		}
+		f.Close()
 	})
 
+	runBenchMark("Open dir", func(b *testing.B) {
+		f, err := ofs.Open("mydir")
+		if err != nil {
+			b.Fatal(err)
+		}
+		f.Close()
+	})
+
+	runBenchMark("Readdir all", func(b *testing.B) {
+		f, err := ofs.Open("mydir")
+		if err != nil {
+			b.Fatal(err)
+		}
+		_, err = f.Readdir(-1)
+		f.Close()
+	})
+
+	runBenchMark("Readdir in one fs all", func(b *testing.B) {
+		f, err := ofs.Open("myotherdir")
+		if err != nil {
+			b.Fatal(err)
+		}
+		_, err = f.Readdir(-1)
+		f.Close()
+	})
+
+	runBenchMark("Readdir some", func(b *testing.B) {
+		f, err := ofs.Open("mydir")
+		if err != nil {
+			b.Fatal(err)
+		}
+		_, err = f.Readdir(2)
+		f.Close()
+	})
+
+	runBenchMark("Readdir in one fs some", func(b *testing.B) {
+		f, err := ofs.Open("myotherdir")
+		if err != nil {
+			b.Fatal(err)
+		}
+		_, err = f.Readdir(2)
+		f.Close()
+	})
 }

Debdiff

File lists identical (after any substitutions)

No differences were encountered in the control files

More details

Full run details