afero icon indicating copy to clipboard operation
afero copied to clipboard

MemMapFS doesn't properly rename directories

Open kklin opened this issue 7 years ago • 5 comments

When I rename a directory with MemMapFS, the files within it don't get moved. I think the files within should also move to the new directory name (as in the behavior of mv <old_dir> <new_dir>), but maybe I'm misunderstanding how Rename works.

I've written a unit test that shows the error:

// TestRenameDirectory creates a directory with a file, and attempts to rename
// the directory. The child file should also be within the renamed directory.
func TestRenameDirectory(t *testing.T) {
        fs := NewMemMapFs()

        srcDir := "/src-dir"
        dstDir := "/dst-dir"
        filename := "file"
        fileInSrcDir := filepath.Join(srcDir, filename)
        fileInDstDir := filepath.Join(dstDir, filename)

        if err := fs.Mkdir(srcDir, 755); err != nil {
                t.Fatalf("Mkdir failed unexpectedly: %s", err)
        }

        if _, err := fs.Create(fileInSrcDir); err != nil {
                t.Fatalf("Create file failed unexpectedly: %s", err)
        }

        if err := fs.Rename(srcDir, dstDir); err != nil {
                t.Fatalf("Rename failed unexpectedly: %s", err)
        }

        if _, err := fs.Stat(fileInSrcDir); err == nil {
                t.Error("The file should not be in the old directory")
        }

        if _, err := fs.Stat(fileInDstDir); err != nil {
                t.Errorf("The file should be in the new directory: %s", err)
        }
}
--- FAIL: TestRenameDirectory (0.00s)
        memmap_test.go:371: The file should not be in the old directory
        memmap_test.go:375: The file should be in the new directory: open /dst-dir/file: file does not exist

I could help out fixing this, but I'm not sure how to approach it. I took a quick glance at the way the filesystem is represented within MemMapFS, and didn't completely understand it.

Thanks!

kklin avatar Oct 20 '17 20:10 kklin

I also noticed this recently while unit testing something that moves directories with MemMapFS; it would work if I tested with the OS default filesystem, but not with MemMapFS.

mtaufen avatar Feb 03 '18 02:02 mtaufen

Confirm

func TestAferoRename(t *testing.T) {
	fs := afero.NewMemMapFs()
	require.NoError(t, fs.MkdirAll("/d1/d2/d3", 755))
	f, err := fs.OpenFile("/d1/d2/d3/f1", os.O_CREATE|os.O_WRONLY, 0644)
	require.NoError(t, err)
	_, err = f.WriteString("asaagsd")
	require.NoError(t, err)
	require.NoError(t, f.Close())

	files, err := afero.Glob(fs, "/d1/d2/d3/*")
	require.NoError(t, err)
	require.Len(t, files, 1)

	require.NoError(t, fs.Rename("/d1/d2", "/d1/d4"))

	files, err = afero.Glob(fs, "/d1/d2/d3/*")
	require.NoError(t, err)
	require.Len(t, files, 0)

	files, err = afero.Glob(fs, "/d1/d4/*")
	require.NoError(t, err)
	require.Len(t, files, 1)
}

output:

=== RUN   TestAferoRename
--- FAIL: TestAferoRename (0.00s)
        Error Trace:    server_test.go:43
        Error:          "[/d1/d2/d3/f1]" should have 0 item(s), but has 1

jeanlucmongrain avatar Apr 24 '18 07:04 jeanlucmongrain

Hello,

Just wanted to chime in and say that I'm having the same trouble with this. I'm unit testing with MemMapFs and finding that fs.Rename doesn't seem to be doing anything. I might be doing something wrong, but the code seems to work fine when we're using an OS filesystem.

(here's my code, if it helps at all)

func (suite *UtilsTestSuite) TestRollbackUpdatePackages_RestoresWhenNoActiveInstallation() {
	_ = suite.FileSystem.MkdirAll("test-library/__OLD__CatsAndOranges", 0755)
	_, _ = suite.FileSystem.Create("test-library/__OLD__CatsAndOranges/DESCRIPTION")
	f, _ := suite.FileSystem.Open("test-library/__OLD__CatsAndOranges/DESCRIPTION")
	f.WriteString("contents")
	defer f.Close()

	updateAttemptFixture := []UpdateAttempt{
		UpdateAttempt{
			Package:                "CatsAndOranges",
			BackupPackageDirectory: "test-library/__OLD__CatsAndOranges",
			ActivePackageDirectory: "test-library/CatsAndOranges",
			NewVersion:             "2",
			OldVersion:             "1",
		},
	}

        // Should call fs.Rename("test-library/__OLD__CatsAndOranges", "test-library/CatsAndOranges")
	RollbackUpdatePackages(suite.FileSystem, updateAttemptFixture)

        //Line fails
	suite.True(afero.DirExists(suite.FileSystem, "/test-library/CatsAndOranges"))
        //Line fails
	suite.True(afero.Exists(suite.FileSystem, "/test-library/CatsAndOranges/DESCRIPTION"))

         //Both of these succeed
	suite.False(afero.DirExists(suite.FileSystem, "/test-library/__OLD__CatsAndOranges"))
	suite.False(afero.Exists(suite.FileSystem, "/test-library/__OLD__CatsAndOranges/DESCRIPTION"))

}

Dreznel avatar Aug 14 '19 19:08 Dreznel

Please fix this issue. This sorta defeat the whole point of using afero, which allows me to use memfs for testing.

maxkaneco avatar Apr 13 '22 10:04 maxkaneco

Please check my PR with fix. I can't use the library with this bug.

hanagantig avatar Jun 25 '22 12:06 hanagantig

Actually this merged PR will not work.

You need to update underlying memdir map as well. here is a case where you will get a panic:

fs := NewMemMapFs()

err := fs.MkdirAll("src/originDir", 0o777)
if err != nil {
    t.Fatalf("MkDirAll failed: %s", err)
}

f, err := fs.Create("src/originDir/originFile.txt")
if err != nil {
    t.Fatalf("Create failed: %s", err)
}
if err = f.Close(); err != nil {
    t.Fatalf("Close failed: %s", err)
}

err = fs.Rename("src/originDir", "src/updatedDir")
if err != nil {
t.Fatalf("Rename failed: %s", err)
}

err = fs.Rename("src/updatedDir/originFile.txt", "src/updatedDir/updatedFile.txt")
if err != nil {
t.Fatalf("Rename failed: %s", err)
}

@bep please have a look to my PR which I did almost a year ago.

hanagantig avatar May 01 '23 18:05 hanagantig