Patch 3/8: Implement stat and dir creation for FSTestOSWrapper
Implements Stat() and Mkdir(), and adds testing for Stat(), Mkdir(),
and MkdirAll().
Bug: 436025865
Change-Id: I0c9ece64a46bdb271cfca23e08d6a28f9df63995
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/256456
Commit-Queue: Brian Sheedy <bsheedy@google.com>
Auto-Submit: Ryan Harrison <rharrison@chromium.org>
Commit-Queue: Ryan Harrison <rharrison@chromium.org>
Reviewed-by: Brian Sheedy <bsheedy@google.com>
diff --git a/tools/src/oswrapper/fstestoswrapper.go b/tools/src/oswrapper/fstestoswrapper.go
index aebf559..9f00d4f 100644
--- a/tools/src/oswrapper/fstestoswrapper.go
+++ b/tools/src/oswrapper/fstestoswrapper.go
@@ -145,7 +145,7 @@
}
func (w FSTestFilesystemReaderWriter) Stat(name string) (os.FileInfo, error) {
- panic("Stat() is not currently implemented in fstest wrapper")
+ return fs.Stat(w.fs(), w.CleanPath(name))
}
func (w FSTestFilesystemReaderWriter) Walk(root string, fn filepath.WalkFunc) error {
@@ -158,37 +158,68 @@
panic("Create() is not currently implemented in fstest wrapper")
}
-func (w FSTestFilesystemReaderWriter) Mkdir(path string, perm os.FileMode) error {
- panic("Mkdir() is not currently implemented in fstest wrapper")
-}
-
-func (w FSTestFilesystemReaderWriter) MkdirAll(path string, perm os.FileMode) error {
- p := w.CleanPath(path)
+func (w FSTestFilesystemReaderWriter) Mkdir(dir string, perm os.FileMode) error {
+ p := w.CleanPath(dir)
if p == "." {
+ // The root directory always exists in a fs.FS.
+ // os.Mkdir returns EEXIST in this case.
+ return &os.PathError{Op: "mkdir", Path: dir, Err: os.ErrExist}
+ }
+
+ if _, exists := w.FS[p]; exists {
+ return &os.PathError{Op: "mkdir", Path: dir, Err: os.ErrExist}
+ }
+
+ parent := filepath.Dir(p)
+ if parent != "." {
+ parentInfo, parentExists := w.FS[parent]
+ if !parentExists {
+ return &os.PathError{Op: "mkdir", Path: dir, Err: os.ErrNotExist}
+ }
+ if !parentInfo.Mode.IsDir() {
+ return &os.PathError{Op: "mkdir", Path: dir, Err: fmt.Errorf("not a directory")}
+ }
+ }
+ w.FS[p] = &fstest.MapFile{Mode: os.ModeDir | perm, ModTime: time.Now()}
+ return nil
+}
+
+func (w FSTestFilesystemReaderWriter) MkdirAll(dir string, perm os.FileMode) error {
+ err := w.Mkdir(dir, perm)
+ if err == nil {
return nil
}
- parts := strings.Split(p, "/")
- current := ""
- for i, part := range parts {
- if current == "" {
- current = part
- } else {
- current = current + "/" + part // Internally FSTestOSWrapper uses '/' for separators
+ if os.IsExist(err) {
+ info, statErr := w.Stat(dir)
+ if statErr == nil && info.IsDir() {
+ // p already exists and is a directory, so return success
+ return nil
}
- if file, exists := w.FS[current]; !exists {
- w.FS[current] = &fstest.MapFile{Mode: os.ModeDir | perm, ModTime: time.Now()}
- } else if !file.Mode.IsDir() {
- // If this is the last part of the path, the error is that the file exists.
- // Otherwise, an intermediate path component is not a directory.
- if i < len(parts)-1 {
- return &os.PathError{Op: "mkdir", Path: path, Err: fmt.Errorf("not a directory")}
- }
- return &os.PathError{Op: "mkdir", Path: path, Err: os.ErrExist}
- }
+ // p already exists and is not a directory, so propagate the error
+ return err
}
- return nil
+
+ if !os.IsNotExist(err) {
+ // Unexpected failure, probably indicating a bad path, i.e. parent is a file, so propagate the error
+ return err
+ }
+
+ // At this point, it is known the problem was the parent didn't exist, so need to recursively create it.
+ p := w.CleanPath(dir)
+ parent := filepath.Dir(p)
+ if parent == "." || parent == p {
+ // The parent is the root of the filesystem, so Mkdir should have succeeded, so propagating the error.
+ return err
+ }
+
+ if err := w.MkdirAll(parent, perm); err != nil {
+ return err
+ }
+
+ // Creating the directory now that the parent exists.
+ return w.Mkdir(p, perm)
}
func (w FSTestFilesystemReaderWriter) MkdirTemp(dir, pattern string) (string, error) {
diff --git a/tools/src/oswrapper/fstestoswrapper_test.go b/tools/src/oswrapper/fstestoswrapper_test.go
index 2bf1a20..f9c960d 100644
--- a/tools/src/oswrapper/fstestoswrapper_test.go
+++ b/tools/src/oswrapper/fstestoswrapper_test.go
@@ -273,7 +273,7 @@
name: "Read a directory",
path: filepath.Join(root, "mydir"),
setup: unittestSetup{
- initialDirs: []string{"/mydir"},
+ initialDirs: []string{filepath.Join(root, "mydir")},
},
expectedError: expectedError{
wantErrMsg: "is a directory",
@@ -342,8 +342,478 @@
}
}
+func TestFSTestOSWrapper_Stat(t *testing.T) {
+ root := getTestRoot()
+ tests := []struct {
+ name string
+ setup unittestSetup
+ path string
+ verify func(t *testing.T, info os.FileInfo)
+ expectedError
+ }{
+ {
+ name: "Stat a file",
+ path: filepath.Join(root, "file.txt"),
+ setup: unittestSetup{
+ initialFiles: map[string]string{filepath.Join(root, "file.txt"): "content"},
+ },
+ verify: func(t *testing.T, info os.FileInfo) {
+ require.False(t, info.IsDir())
+ require.Equal(t, "file.txt", info.Name())
+ require.Equal(t, int64(7), info.Size())
+ },
+ },
+ {
+ name: "Stat a directory",
+ path: filepath.Join(root, "dir"),
+ setup: unittestSetup{
+ initialDirs: []string{filepath.Join(root, "dir")},
+ },
+ verify: func(t *testing.T, info os.FileInfo) {
+ require.True(t, info.IsDir())
+ require.Equal(t, "dir", info.Name())
+ },
+ },
+ {
+ name: "Stat non-existent path",
+ path: filepath.Join(root, "nonexistent"),
+ expectedError: expectedError{
+ wantErrIs: os.ErrNotExist,
+ },
+ },
+ }
+
+ for _, tc := range tests {
+ t.Run(tc.name, func(t *testing.T) {
+ wrapper := tc.setup.setup(t)
+ info, err := wrapper.Stat(tc.path)
+
+ if tc.expectedError.Check(t, err) {
+ return
+ }
+
+ if tc.verify != nil {
+ tc.verify(t, info)
+ }
+ })
+ }
+}
+
+func TestFSTestOSWrapper_Stat_MatchesReal(t *testing.T) {
+ tests := []struct {
+ name string
+ setup matchesRealSetup
+ path string
+ }{
+ {
+ name: "Stat a file",
+ setup: matchesRealSetup{unittestSetup{
+ initialFiles: map[string]string{
+ "file.txt": "content",
+ },
+ }},
+ path: "file.txt",
+ },
+ {
+ name: "Stat a directory",
+ setup: matchesRealSetup{unittestSetup{
+ initialDirs: []string{
+ "dir",
+ },
+ }},
+ path: "dir",
+ },
+ {
+ name: "Stat non-existent path",
+ path: "nonexistent",
+ },
+ }
+
+ for _, tc := range tests {
+ t.Run(tc.name, func(t *testing.T) {
+ realRoot, realFS, testFS := tc.setup.setup(t)
+ defer os.RemoveAll(realRoot)
+
+ // Execute
+ realInfo, realErr := realFS.Stat(filepath.Join(realRoot, tc.path))
+ testInfo, testErr := testFS.Stat(tc.path)
+
+ requireErrorsMatch(t, realErr, testErr)
+ if realErr == nil {
+ require.Equal(t, realInfo.Name(), testInfo.Name())
+ require.Equal(t, realInfo.IsDir(), testInfo.IsDir())
+ // The size of a directory is system-dependent, so only compare sizes for files.
+ if !realInfo.IsDir() {
+ require.Equal(t, realInfo.Size(), testInfo.Size())
+ }
+ }
+ })
+ }
+}
+
// --- FilesystemWriter tests ---
+func TestFSTestOSWrapper_Mkdir(t *testing.T) {
+ root := getTestRoot()
+ tests := []struct {
+ name string
+ setup unittestSetup
+ path string
+ mode os.FileMode
+ expectedMode os.FileMode
+ verify func(t *testing.T, fs oswrapper.FSTestOSWrapper)
+ expectedError
+ }{
+ {
+ name: "Create new directory",
+ path: filepath.Join(root, "newdir"),
+ mode: 0755,
+ expectedMode: 0755,
+ verify: func(t *testing.T, fs oswrapper.FSTestOSWrapper) {
+ info, err := fs.Stat(filepath.Join(root, "newdir"))
+ require.NoError(t, err)
+ require.True(t, info.IsDir())
+ },
+ },
+ {
+ name: "Create in existing subdirectory",
+ path: filepath.Join(root, "existing", "newdir"),
+ mode: 0755,
+ expectedMode: 0755,
+ setup: unittestSetup{
+ initialDirs: []string{filepath.Join(root, "existing")},
+ },
+ verify: func(t *testing.T, fs oswrapper.FSTestOSWrapper) {
+ info, err := fs.Stat(filepath.Join(root, "existing", "newdir"))
+ require.NoError(t, err)
+ require.True(t, info.IsDir())
+ },
+ },
+ {
+ name: "Create new directory with specific mode",
+ path: filepath.Join(root, "newdir_mode"),
+ mode: 0700,
+ expectedMode: 0700,
+ },
+ {
+ name: "Directory already exists",
+ path: filepath.Join(root, "mydir"),
+ setup: unittestSetup{
+ initialDirs: []string{filepath.Join(root, "mydir")},
+ },
+ expectedError: expectedError{
+ wantErrIs: os.ErrExist,
+ },
+ },
+ {
+ name: "Path is a file",
+ path: filepath.Join(root, "myfile.txt"),
+ setup: unittestSetup{
+ initialFiles: map[string]string{filepath.Join(root, "myfile.txt"): ""},
+ },
+ expectedError: expectedError{
+ wantErrIs: os.ErrExist,
+ },
+ },
+ {
+ name: "Parent directory does not exist",
+ path: filepath.Join(root, "nonexistent", "newdir"),
+ expectedError: expectedError{
+ wantErrIs: os.ErrNotExist,
+ },
+ },
+ {
+ name: "Parent path is a file",
+ path: filepath.Join(root, "file.txt", "newdir"),
+ setup: unittestSetup{
+ initialFiles: map[string]string{filepath.Join(root, "file.txt"): ""},
+ },
+ expectedError: expectedError{
+ wantErrMsg: "not a directory",
+ },
+ },
+ {
+ name: "Path is dot",
+ path: ".",
+ expectedError: expectedError{
+ wantErrIs: os.ErrExist,
+ },
+ },
+ }
+
+ for _, tc := range tests {
+ t.Run(tc.name, func(t *testing.T) {
+ wrapper := tc.setup.setup(t)
+ err := wrapper.Mkdir(tc.path, tc.mode)
+
+ if tc.expectedError.Check(t, err) {
+ return
+ }
+
+ if tc.verify != nil {
+ tc.verify(t, wrapper)
+ }
+
+ if tc.expectedMode != 0 {
+ cleanedPath := wrapper.CleanPath(tc.path)
+ file, ok := wrapper.FS[cleanedPath]
+ require.True(t, ok, "file %v not found in test fs", cleanedPath)
+ require.Equal(t, tc.expectedMode, file.Mode.Perm())
+ }
+ })
+ }
+}
+
+func TestFSTestOSWrapper_Mkdir_MatchesReal(t *testing.T) {
+ tests := []struct {
+ name string
+ setup matchesRealSetup
+ path string // path to Mkdir
+ }{
+ {
+ name: "Create new directory",
+ path: "newdir",
+ },
+ {
+ name: "Create in existing subdirectory",
+ setup: matchesRealSetup{unittestSetup{
+ initialDirs: []string{"foo"},
+ }},
+ path: filepath.Join("foo", "newdir"),
+ },
+ {
+ name: "Create in existing nested subdirectory",
+ setup: matchesRealSetup{unittestSetup{
+ initialDirs: []string{filepath.Join("foo", "bar")},
+ }},
+ path: filepath.Join("foo", "bar", "newdir"),
+ },
+ {
+ name: "Error on existing directory",
+ setup: matchesRealSetup{unittestSetup{
+ initialDirs: []string{"mydir"},
+ }},
+ path: "mydir",
+ },
+ {
+ name: "Error on existing file",
+ setup: matchesRealSetup{unittestSetup{
+ initialFiles: map[string]string{"myfile": "content"},
+ }},
+ path: "myfile",
+ },
+ {
+ name: "Error on non-existent parent",
+ path: filepath.Join("nonexistent", "newdir"),
+ },
+ {
+ name: "Error on parent is file",
+ setup: matchesRealSetup{unittestSetup{
+ initialFiles: map[string]string{"file.txt": "content"},
+ }},
+ path: filepath.Join("file.txt", "newdir"),
+ },
+ {
+ name: "Error on dot",
+ path: ".",
+ },
+ }
+
+ for _, tc := range tests {
+ t.Run(tc.name, func(t *testing.T) {
+ realRoot, realFS, testFS := tc.setup.setup(t)
+ defer os.RemoveAll(realRoot)
+
+ // Execute
+ realErr := realFS.Mkdir(filepath.Join(realRoot, tc.path), 0755)
+ testErr := testFS.Mkdir(tc.path, 0755)
+
+ requireErrorsMatch(t, realErr, testErr)
+ if realErr == nil {
+ requireFileSystemsMatch(t, realRoot, testFS)
+ }
+ })
+ }
+}
+
+func TestFSTestOSWrapper_MkdirAll(t *testing.T) {
+ root := getTestRoot()
+ tests := []struct {
+ name string
+ setup unittestSetup
+ path string
+ mode os.FileMode
+ expectedMode os.FileMode
+ verify func(t *testing.T, fs oswrapper.FSTestOSWrapper)
+ expectedError
+ }{
+ {
+ name: "Create new nested directory",
+ path: filepath.Join(root, "a", "b", "c"),
+ mode: 0755,
+ expectedMode: 0755,
+ verify: func(t *testing.T, fs oswrapper.FSTestOSWrapper) {
+ for _, p := range []string{
+ filepath.Join(root, "a"),
+ filepath.Join(root, "a", "b"),
+ filepath.Join(root, "a", "b", "c"),
+ } {
+ info, err := fs.Stat(p)
+ require.NoError(t, err)
+ require.True(t, info.IsDir())
+ file, ok := fs.FS[fs.CleanPath(p)]
+ require.True(t, ok)
+ require.Equal(t, os.FileMode(0755), file.Mode.Perm())
+ }
+ },
+ },
+ {
+ name: "Create in existing subdirectory",
+ path: filepath.Join(root, "a", "b"),
+ mode: 0755,
+ expectedMode: 0755,
+ setup: unittestSetup{
+ initialDirs: []string{filepath.Join(root, "a")},
+ },
+ verify: func(t *testing.T, fs oswrapper.FSTestOSWrapper) {
+ info, err := fs.Stat(filepath.Join(root, "a", "b"))
+ require.NoError(t, err)
+ require.True(t, info.IsDir())
+ },
+ },
+ {
+ name: "Create new nested directory with specific mode",
+ path: filepath.Join(root, "d", "e", "f"),
+ mode: 0700,
+ expectedMode: 0700,
+ verify: func(t *testing.T, fs oswrapper.FSTestOSWrapper) {
+ for _, p := range []string{
+ filepath.Join(root, "d"),
+ filepath.Join(root, "d", "e"),
+ filepath.Join(root, "d", "e", "f"),
+ } {
+ info, err := fs.Stat(p)
+ require.NoError(t, err)
+ require.True(t, info.IsDir())
+ file, ok := fs.FS[fs.CleanPath(p)]
+ require.True(t, ok)
+ require.Equal(t, os.FileMode(0700), file.Mode.Perm())
+ }
+ },
+ },
+ {
+ name: "Path already exists as directory",
+ path: filepath.Join(root, "a", "b"),
+ setup: unittestSetup{
+ initialDirs: []string{filepath.Join(root, "a", "b")},
+ },
+ },
+ {
+ name: "Part of path is a file",
+ path: filepath.Join(root, "a", "b", "c"),
+ setup: unittestSetup{
+ initialFiles: map[string]string{filepath.Join(root, "a", "b"): "i am a file"},
+ initialDirs: []string{filepath.Join(root, "a")},
+ },
+ expectedError: expectedError{
+ wantErrMsg: "not a directory",
+ },
+ },
+ {
+ name: "Destination path is a file",
+ path: filepath.Join(root, "a", "b"),
+ setup: unittestSetup{
+ initialFiles: map[string]string{filepath.Join(root, "a", "b"): "i am a file"},
+ initialDirs: []string{filepath.Join(root, "a")},
+ },
+ expectedError: expectedError{
+ wantErrIs: os.ErrExist,
+ },
+ },
+ }
+
+ for _, tc := range tests {
+ t.Run(tc.name, func(t *testing.T) {
+ wrapper := tc.setup.setup(t)
+ err := wrapper.MkdirAll(tc.path, tc.mode)
+
+ if tc.expectedError.Check(t, err) {
+ return
+ }
+
+ if tc.verify != nil {
+ tc.verify(t, wrapper)
+ }
+
+ if tc.expectedMode != 0 {
+ cleanedPath := wrapper.CleanPath(tc.path)
+ file, ok := wrapper.FS[cleanedPath]
+ require.True(t, ok, "file %v not found in test fs", cleanedPath)
+ require.Equal(t, tc.expectedMode, file.Mode.Perm())
+ }
+ })
+ }
+}
+
+func TestFSTestOSWrapper_MkdirAll_MatchesReal(t *testing.T) {
+ tests := []struct {
+ name string
+ setup matchesRealSetup
+ path string // path to MkdirAll
+ }{
+ {
+ name: "Create new nested directory",
+ path: filepath.Join("a", "b", "c"),
+ },
+ {
+ name: "Create in existing subdirectory",
+ setup: matchesRealSetup{unittestSetup{
+ initialDirs: []string{"a"},
+ }},
+ path: filepath.Join("a", "b"),
+ },
+ {
+ name: "Path already exists as directory",
+ setup: matchesRealSetup{unittestSetup{
+ initialDirs: []string{filepath.Join("a", "b", "c")},
+ }},
+ path: filepath.Join("a", "b", "c"),
+ },
+ {
+ name: "Part of path is a file",
+ setup: matchesRealSetup{unittestSetup{
+ initialFiles: map[string]string{filepath.Join("a", "b"): "i am a file"},
+ initialDirs: []string{"a"},
+ }},
+ path: filepath.Join("a", "b", "c"),
+ },
+ {
+ name: "Destination is a file",
+ setup: matchesRealSetup{unittestSetup{
+ initialFiles: map[string]string{filepath.Join("a", "b"): "i am a file"},
+ initialDirs: []string{"a"},
+ }},
+ path: filepath.Join("a", "b"),
+ },
+ }
+
+ for _, tc := range tests {
+ t.Run(tc.name, func(t *testing.T) {
+ realRoot, realFS, testFS := tc.setup.setup(t)
+ defer os.RemoveAll(realRoot)
+
+ // Execute
+ realErr := realFS.MkdirAll(filepath.Join(realRoot, tc.path), 0755)
+ testErr := testFS.MkdirAll(tc.path, 0755)
+
+ requireErrorsMatch(t, realErr, testErr)
+ if realErr == nil {
+ requireFileSystemsMatch(t, realRoot, testFS)
+ }
+ })
+ }
+}
+
func TestFSTestOSWrapper_WriteFile(t *testing.T) {
root := getTestRoot()
tests := []struct {