From 1cf03c0f4b1b4fbd1914f1c565cee2e9175813a0 Mon Sep 17 00:00:00 2001 From: Daniel Milde Date: Sun, 14 Apr 2024 21:12:41 +0200 Subject: [PATCH 1/3] refactor: move removal logic to separate package --- internal/testanalyze/analyze.go | 3 +- pkg/analyze/file.go | 42 ------ pkg/analyze/file_test.go | 120 ---------------- pkg/remove/remove.go | 49 +++++++ .../remove_linux_test.go} | 11 +- pkg/remove/remove_test.go | 130 ++++++++++++++++++ tui/tui.go | 5 +- 7 files changed, 190 insertions(+), 170 deletions(-) create mode 100644 pkg/remove/remove.go rename pkg/{analyze/file_linux_test.go => remove/remove_linux_test.go} (79%) create mode 100644 pkg/remove/remove_test.go diff --git a/internal/testanalyze/analyze.go b/internal/testanalyze/analyze.go index 9911a982d..3bb053279 100644 --- a/internal/testanalyze/analyze.go +++ b/internal/testanalyze/analyze.go @@ -7,6 +7,7 @@ import ( "github.com/dundee/gdu/v5/internal/common" "github.com/dundee/gdu/v5/pkg/analyze" "github.com/dundee/gdu/v5/pkg/fs" + "github.com/dundee/gdu/v5/pkg/remove" ) // MockedAnalyzer returns dir with files with different size exponents @@ -91,7 +92,7 @@ func RemoveItemFromDirWithErr(dir fs.Item, file fs.Item) error { // RemoveItemFromDirWithSleep returns error func RemoveItemFromDirWithSleep(dir fs.Item, file fs.Item) error { time.Sleep(time.Millisecond * 600) - return analyze.RemoveItemFromDir(dir, file) + return remove.RemoveItemFromDir(dir, file) } // RemoveItemFromDirWithSleepAndErr returns error diff --git a/pkg/analyze/file.go b/pkg/analyze/file.go index 401a82288..a933d8fc1 100644 --- a/pkg/analyze/file.go +++ b/pkg/analyze/file.go @@ -1,7 +1,6 @@ package analyze import ( - "os" "path/filepath" "sync" "time" @@ -256,44 +255,3 @@ func (f *Dir) RLock() func() { f.m.RLock() return f.m.RUnlock } - -// RemoveItemFromDir removes item from dir -func RemoveItemFromDir(dir fs.Item, item fs.Item) error { - err := os.RemoveAll(item.GetPath()) - if err != nil { - return err - } - - dir.RemoveFile(item) - return nil -} - -// EmptyFileFromDir empty file from dir -func EmptyFileFromDir(dir fs.Item, file fs.Item) error { - err := os.Truncate(file.GetPath(), 0) - if err != nil { - return err - } - - cur := dir.(*Dir) - for { - cur.Size -= file.GetSize() - cur.Usage -= file.GetUsage() - - if cur.Parent == nil { - break - } - cur = cur.Parent.(*Dir) - } - - dir.SetFiles(dir.GetFiles().Remove(file)) - newFile := &File{ - Name: file.GetName(), - Flag: file.GetFlag(), - Size: 0, - Parent: dir, - } - dir.AddFile(newFile) - - return nil -} diff --git a/pkg/analyze/file_test.go b/pkg/analyze/file_test.go index e5652c432..025589370 100644 --- a/pkg/analyze/file_test.go +++ b/pkg/analyze/file_test.go @@ -4,7 +4,6 @@ import ( "testing" "time" - "github.com/dundee/gdu/v5/internal/testdir" "github.com/dundee/gdu/v5/pkg/fs" "github.com/stretchr/testify/assert" ) @@ -201,125 +200,6 @@ func TestRemoveByNameNotInDir(t *testing.T) { assert.Equal(t, 1, len(dir.Files)) } -func TestRemoveFile(t *testing.T) { - dir := &Dir{ - File: &File{ - Name: "xxx", - Size: 5, - Usage: 12, - }, - ItemCount: 3, - BasePath: ".", - } - - subdir := &Dir{ - File: &File{ - Name: "yyy", - Size: 4, - Usage: 8, - Parent: dir, - }, - ItemCount: 2, - } - file := &File{ - Name: "zzz", - Size: 3, - Usage: 4, - Parent: subdir, - } - dir.Files = fs.Files{subdir} - subdir.Files = fs.Files{file} - - err := RemoveItemFromDir(subdir, file) - assert.Nil(t, err) - - assert.Equal(t, 0, len(subdir.Files)) - assert.Equal(t, 1, subdir.ItemCount) - assert.Equal(t, int64(1), subdir.Size) - assert.Equal(t, int64(4), subdir.Usage) - assert.Equal(t, 1, len(dir.Files)) - assert.Equal(t, 2, dir.ItemCount) - assert.Equal(t, int64(2), dir.Size) -} - -func TestTruncateFile(t *testing.T) { - fin := testdir.CreateTestDir() - defer fin() - - dir := &Dir{ - File: &File{ - Name: "test_dir", - Size: 5, - Usage: 12, - }, - ItemCount: 3, - BasePath: ".", - } - - subdir := &Dir{ - File: &File{ - Name: "nested", - Size: 4, - Usage: 8, - Parent: dir, - }, - ItemCount: 2, - } - file := &File{ - Name: "file2", - Size: 3, - Usage: 4, - Parent: subdir, - } - dir.Files = fs.Files{subdir} - subdir.Files = fs.Files{file} - - err := EmptyFileFromDir(subdir, file) - - assert.Nil(t, err) - assert.Equal(t, 1, len(subdir.Files)) - assert.Equal(t, 2, subdir.ItemCount) - assert.Equal(t, int64(1), subdir.Size) - assert.Equal(t, int64(4), subdir.Usage) - assert.Equal(t, 1, len(dir.Files)) - assert.Equal(t, 3, dir.ItemCount) - assert.Equal(t, int64(2), dir.Size) -} - -func TestTruncateFileWithErr(t *testing.T) { - dir := &Dir{ - File: &File{ - Name: "xxx", - Size: 5, - Usage: 12, - }, - ItemCount: 3, - BasePath: ".", - } - - subdir := &Dir{ - File: &File{ - Name: "yyy", - Size: 4, - Usage: 8, - Parent: dir, - }, - ItemCount: 2, - } - file := &File{ - Name: "zzz", - Size: 3, - Usage: 4, - Parent: subdir, - } - dir.Files = fs.Files{subdir} - subdir.Files = fs.Files{file} - - err := EmptyFileFromDir(subdir, file) - - assert.Contains(t, err.Error(), "no such file or directory") -} - func TestUpdateStats(t *testing.T) { dir := Dir{ File: &File{ diff --git a/pkg/remove/remove.go b/pkg/remove/remove.go new file mode 100644 index 000000000..1d17985ba --- /dev/null +++ b/pkg/remove/remove.go @@ -0,0 +1,49 @@ +package remove + +import ( + "os" + + "github.com/dundee/gdu/v5/pkg/analyze" + "github.com/dundee/gdu/v5/pkg/fs" +) + +// RemoveItemFromDir removes item from dir +func RemoveItemFromDir(dir fs.Item, item fs.Item) error { + err := os.RemoveAll(item.GetPath()) + if err != nil { + return err + } + + dir.RemoveFile(item) + return nil +} + +// EmptyFileFromDir empty file from dir +func EmptyFileFromDir(dir fs.Item, file fs.Item) error { + err := os.Truncate(file.GetPath(), 0) + if err != nil { + return err + } + + cur := dir.(*analyze.Dir) + for { + cur.Size -= file.GetSize() + cur.Usage -= file.GetUsage() + + if cur.Parent == nil { + break + } + cur = cur.Parent.(*analyze.Dir) + } + + dir.SetFiles(dir.GetFiles().Remove(file)) + newFile := &analyze.File{ + Name: file.GetName(), + Flag: file.GetFlag(), + Size: 0, + Parent: dir, + } + dir.AddFile(newFile) + + return nil +} diff --git a/pkg/analyze/file_linux_test.go b/pkg/remove/remove_linux_test.go similarity index 79% rename from pkg/analyze/file_linux_test.go rename to pkg/remove/remove_linux_test.go index 049c0b11a..c09d85d53 100644 --- a/pkg/analyze/file_linux_test.go +++ b/pkg/remove/remove_linux_test.go @@ -1,13 +1,14 @@ //go:build linux // +build linux -package analyze +package remove import ( "os" "testing" "github.com/dundee/gdu/v5/internal/testdir" + "github.com/dundee/gdu/v5/pkg/analyze" "github.com/stretchr/testify/assert" ) @@ -22,15 +23,15 @@ func TestRemoveFileWithErr(t *testing.T) { assert.Nil(t, err) }() - dir := &Dir{ - File: &File{ + dir := &analyze.Dir{ + File: &analyze.File{ Name: "test_dir", }, BasePath: ".", } - subdir := &Dir{ - File: &File{ + subdir := &analyze.Dir{ + File: &analyze.File{ Name: "nested", Parent: dir, }, diff --git a/pkg/remove/remove_test.go b/pkg/remove/remove_test.go new file mode 100644 index 000000000..11a1c2362 --- /dev/null +++ b/pkg/remove/remove_test.go @@ -0,0 +1,130 @@ +package remove + +import ( + "testing" + + "github.com/stretchr/testify/assert" + + "github.com/dundee/gdu/v5/internal/testdir" + "github.com/dundee/gdu/v5/pkg/analyze" + "github.com/dundee/gdu/v5/pkg/fs" +) + +func TestTruncateFile(t *testing.T) { + fin := testdir.CreateTestDir() + defer fin() + + dir := &analyze.Dir{ + File: &analyze.File{ + Name: "test_dir", + Size: 5, + Usage: 12, + }, + ItemCount: 3, + BasePath: ".", + } + + subdir := &analyze.Dir{ + File: &analyze.File{ + Name: "nested", + Size: 4, + Usage: 8, + Parent: dir, + }, + ItemCount: 2, + } + file := &analyze.File{ + Name: "file2", + Size: 3, + Usage: 4, + Parent: subdir, + } + dir.Files = fs.Files{subdir} + subdir.Files = fs.Files{file} + + err := EmptyFileFromDir(subdir, file) + + assert.Nil(t, err) + assert.Equal(t, 1, len(subdir.Files)) + assert.Equal(t, 2, subdir.ItemCount) + assert.Equal(t, int64(1), subdir.Size) + assert.Equal(t, int64(4), subdir.Usage) + assert.Equal(t, 1, len(dir.Files)) + assert.Equal(t, 3, dir.ItemCount) + assert.Equal(t, int64(2), dir.Size) +} + +func TestRemoveFile(t *testing.T) { + dir := &analyze.Dir{ + File: &analyze.File{ + Name: "xxx", + Size: 5, + Usage: 12, + }, + ItemCount: 3, + BasePath: ".", + } + + subdir := &analyze.Dir{ + File: &analyze.File{ + Name: "yyy", + Size: 4, + Usage: 8, + Parent: dir, + }, + ItemCount: 2, + } + file := &analyze.File{ + Name: "zzz", + Size: 3, + Usage: 4, + Parent: subdir, + } + dir.Files = fs.Files{subdir} + subdir.Files = fs.Files{file} + + err := RemoveItemFromDir(subdir, file) + assert.Nil(t, err) + + assert.Equal(t, 0, len(subdir.Files)) + assert.Equal(t, 1, subdir.ItemCount) + assert.Equal(t, int64(1), subdir.Size) + assert.Equal(t, int64(4), subdir.Usage) + assert.Equal(t, 1, len(dir.Files)) + assert.Equal(t, 2, dir.ItemCount) + assert.Equal(t, int64(2), dir.Size) +} + +func TestTruncateFileWithErr(t *testing.T) { + dir := &analyze.Dir{ + File: &analyze.File{ + Name: "xxx", + Size: 5, + Usage: 12, + }, + ItemCount: 3, + BasePath: ".", + } + + subdir := &analyze.Dir{ + File: &analyze.File{ + Name: "yyy", + Size: 4, + Usage: 8, + Parent: dir, + }, + ItemCount: 2, + } + file := &analyze.File{ + Name: "zzz", + Size: 3, + Usage: 4, + Parent: subdir, + } + dir.Files = fs.Files{subdir} + subdir.Files = fs.Files{file} + + err := EmptyFileFromDir(subdir, file) + + assert.Contains(t, err.Error(), "no such file or directory") +} diff --git a/tui/tui.go b/tui/tui.go index b7a376a26..03a35d49e 100644 --- a/tui/tui.go +++ b/tui/tui.go @@ -14,6 +14,7 @@ import ( "github.com/dundee/gdu/v5/pkg/analyze" "github.com/dundee/gdu/v5/pkg/device" "github.com/dundee/gdu/v5/pkg/fs" + "github.com/dundee/gdu/v5/pkg/remove" "github.com/gdamore/tcell/v2" "github.com/rivo/tview" ) @@ -105,8 +106,8 @@ func CreateUI( output: output, askBeforeDelete: true, showItemCount: false, - remover: analyze.RemoveItemFromDir, - emptier: analyze.EmptyFileFromDir, + remover: remove.RemoveItemFromDir, + emptier: remove.EmptyFileFromDir, exec: Execute, linkedItems: make(fs.HardLinkedItems, 10), selectedTextColor: tview.Styles.TitleColor, From 506dfb879d550ae7627f231635646067456c6fd5 Mon Sep 17 00:00:00 2001 From: Daniel Milde Date: Sun, 14 Apr 2024 22:23:19 +0200 Subject: [PATCH 2/3] feat: delete directory items in parallel --- README.md | 16 +++++++ cmd/gdu/app/app.go | 6 +++ pkg/analyze/file.go | 2 +- pkg/analyze/stored.go | 3 +- pkg/remove/parallel.go | 58 +++++++++++++++++++++++ pkg/remove/parallel_linux_test.go | 42 +++++++++++++++++ pkg/remove/parallel_test.go | 69 +++++++++++++++++++++++++++ tui/tui.go | 5 ++ tui/tui_test.go | 78 +++++++++++++++++++++++++++++++ 9 files changed, 277 insertions(+), 2 deletions(-) create mode 100644 pkg/remove/parallel.go create mode 100644 pkg/remove/parallel_linux_test.go create mode 100644 pkg/remove/parallel_test.go diff --git a/README.md b/README.md index fb0f2eef1..8b3e6381b 100644 --- a/README.md +++ b/README.md @@ -180,6 +180,22 @@ style: background-color: "#ff0000" ``` +## Deletion in background and in parallel (experimental) + +Gdu can delete items in the background, thus not blocking the UI for additional work. +To enable: + +``` +echo "delete-in-background: true" >> ~/.gdu.yaml +``` + +Directory items can be also deleted in parallel, which can increase the speed of deletion. +To enable: + +``` +echo "delete-in-parallel: true" >> ~/.gdu.yaml +``` + ## Memory usage ### Automatic balancing diff --git a/cmd/gdu/app/app.go b/cmd/gdu/app/app.go index 4ac669665..96bc77e31 100644 --- a/cmd/gdu/app/app.go +++ b/cmd/gdu/app/app.go @@ -76,6 +76,7 @@ type Flags struct { WriteConfig bool `yaml:"-"` ChangeCwd bool `yaml:"change-cwd"` DeleteInBackground bool `yaml:"delete-in-background"` + DeleteInParallel bool `yaml:"delete-in-parallel"` Style Style `yaml:"style"` Sorting Sorting `yaml:"sorting"` } @@ -286,6 +287,11 @@ func (a *App) createUI() (UI, error) { ui.SetDeleteInBackground() }) } + if a.Flags.DeleteInParallel { + opts = append(opts, func(ui *tui.UI) { + ui.SetDeleteInParallel() + }) + } ui = tui.CreateUI( a.TermApp, diff --git a/pkg/analyze/file.go b/pkg/analyze/file.go index a933d8fc1..8184ec7dc 100644 --- a/pkg/analyze/file.go +++ b/pkg/analyze/file.go @@ -230,7 +230,7 @@ func (f *Dir) UpdateStats(linkedItems fs.HardLinkedItems) { f.Usage = totalUsage } -// RemoveFile panics on file +// RemoveFile removes item from dir, updates size and item count func (f *Dir) RemoveFile(item fs.Item) { f.m.Lock() defer f.m.Unlock() diff --git a/pkg/analyze/stored.go b/pkg/analyze/stored.go index 79bdd037c..4e0ba1041 100644 --- a/pkg/analyze/stored.go +++ b/pkg/analyze/stored.go @@ -282,7 +282,8 @@ func (f *StoredDir) SetFiles(files fs.Files) { f.Files = files } -// RemoveFile panics on file +// RemoveFile removes file from stored directory +// It also updates size and item count of parent directories func (f *StoredDir) RemoveFile(item fs.Item) { if !DefaultStorage.IsOpen() { f.dbLock.Lock() diff --git a/pkg/remove/parallel.go b/pkg/remove/parallel.go new file mode 100644 index 000000000..1f9c57e67 --- /dev/null +++ b/pkg/remove/parallel.go @@ -0,0 +1,58 @@ +package remove + +import ( + "os" + "runtime" + "sync" + + "github.com/dundee/gdu/v5/pkg/fs" +) + +var concurrencyLimit = make(chan struct{}, 3*runtime.GOMAXPROCS(0)) + +// RemoveItemFromDirParallel removes item from dir +func RemoveItemFromDirParallel(dir fs.Item, item fs.Item) error { + if !item.IsDir() { + return RemoveItemFromDir(dir, item) + } + errChan := make(chan error, 1) // we show only first error + var wait sync.WaitGroup + + // remove all files in the directory in parallel + for _, file := range item.GetFilesLocked() { + wait.Add(1) + go func(itemPath string) { + concurrencyLimit <- struct{}{} + defer func() { <-concurrencyLimit }() + + err := os.RemoveAll(itemPath) + if err != nil { + select { + // write error to channel if it's empty + case errChan <- err: + default: + } + } + wait.Done() + }(file.GetPath()) + } + + wait.Wait() + + // check if there was an error + select { + case err := <-errChan: + return err + default: + } + + // remove the directory itself + err := os.RemoveAll(item.GetPath()) + if err != nil { + return err + } + + // update parent directory + dir.RemoveFile(item) + return nil +} diff --git a/pkg/remove/parallel_linux_test.go b/pkg/remove/parallel_linux_test.go new file mode 100644 index 000000000..1bd05199e --- /dev/null +++ b/pkg/remove/parallel_linux_test.go @@ -0,0 +1,42 @@ +//go:build linux +// +build linux + +package remove + +import ( + "os" + "testing" + + "github.com/dundee/gdu/v5/internal/testdir" + "github.com/dundee/gdu/v5/pkg/analyze" + "github.com/stretchr/testify/assert" +) + +func TestRemoveItemFromDirParallelWithErr(t *testing.T) { + fin := testdir.CreateTestDir() + defer fin() + + err := os.Chmod("test_dir/nested", 0) + assert.Nil(t, err) + defer func() { + err = os.Chmod("test_dir/nested", 0755) + assert.Nil(t, err) + }() + + dir := &analyze.Dir{ + File: &analyze.File{ + Name: "test_dir", + }, + BasePath: ".", + } + + subdir := &analyze.Dir{ + File: &analyze.File{ + Name: "nested", + Parent: dir, + }, + } + + err = RemoveItemFromDirParallel(dir, subdir) + assert.Contains(t, err.Error(), "permission denied") +} diff --git a/pkg/remove/parallel_test.go b/pkg/remove/parallel_test.go new file mode 100644 index 000000000..6196d7875 --- /dev/null +++ b/pkg/remove/parallel_test.go @@ -0,0 +1,69 @@ +package remove + +import ( + "testing" + + "github.com/stretchr/testify/assert" + + "github.com/dundee/gdu/v5/internal/testdir" + "github.com/dundee/gdu/v5/pkg/analyze" + "github.com/dundee/gdu/v5/pkg/fs" +) + +func TestRemoveFileParallel(t *testing.T) { + dir := &analyze.Dir{ + File: &analyze.File{ + Name: "xxx", + Size: 5, + Usage: 12, + }, + ItemCount: 3, + BasePath: ".", + } + + subdir := &analyze.Dir{ + File: &analyze.File{ + Name: "yyy", + Size: 4, + Usage: 8, + Parent: dir, + }, + ItemCount: 2, + } + file := &analyze.File{ + Name: "zzz", + Size: 3, + Usage: 4, + Parent: subdir, + } + dir.Files = fs.Files{subdir} + subdir.Files = fs.Files{file} + + err := RemoveItemFromDirParallel(subdir, file) + assert.Nil(t, err) + + assert.Equal(t, 0, len(subdir.Files)) + assert.Equal(t, 1, subdir.ItemCount) + assert.Equal(t, int64(1), subdir.Size) + assert.Equal(t, int64(4), subdir.Usage) + assert.Equal(t, 1, len(dir.Files)) + assert.Equal(t, 2, dir.ItemCount) + assert.Equal(t, int64(2), dir.Size) +} + +func TestRemoveDirParallel(t *testing.T) { + fin := testdir.CreateTestDir() + defer fin() + + analyzer := analyze.CreateAnalyzer() + dir := analyzer.AnalyzeDir( + "test_dir", func(_, _ string) bool { return false }, false, + ).(*analyze.Dir) + analyzer.GetDone().Wait() + dir.UpdateStats(make(fs.HardLinkedItems)) + + subdir := dir.Files[0].(*analyze.Dir) + + err := RemoveItemFromDirParallel(dir, subdir) + assert.Nil(t, err) +} diff --git a/tui/tui.go b/tui/tui.go index 03a35d49e..57edb985e 100644 --- a/tui/tui.go +++ b/tui/tui.go @@ -217,6 +217,11 @@ func (ui *UI) SetChangeCwdFn(fn func(string) error) { ui.changeCwdFn = fn } +// SetDeleteInParallel sets the flag to delete files in parallel +func (ui *UI) SetDeleteInParallel() { + ui.remover = remove.RemoveItemFromDirParallel +} + // StartUILoop starts tview application func (ui *UI) StartUILoop() error { return ui.app.Run() diff --git a/tui/tui_test.go b/tui/tui_test.go index 6974de1b1..abf3d9556 100644 --- a/tui/tui_test.go +++ b/tui/tui_test.go @@ -330,6 +330,29 @@ func TestDeleteSelected(t *testing.T) { assert.NoDirExists(t, "test_dir/nested") } +func TestDeleteSelectedInParallel(t *testing.T) { + fin := testdir.CreateTestDir() + defer fin() + + ui := getAnalyzedPathMockedApp(t, false, true, false) + ui.done = make(chan struct{}) + ui.SetDeleteInParallel() + + assert.Equal(t, 1, ui.table.GetRowCount()) + + ui.table.Select(0, 0) + + ui.deleteSelected(false) + + <-ui.done + + for _, f := range ui.app.(*testapp.MockedApp).GetUpdateDraws() { + f() + } + + assert.NoDirExists(t, "test_dir/nested") +} + func TestDeleteSelectedInBackground(t *testing.T) { fin := testdir.CreateTestDir() defer fin() @@ -354,6 +377,31 @@ func TestDeleteSelectedInBackground(t *testing.T) { assert.NoDirExists(t, "test_dir/nested") } +func TestDeleteSelectedInBackgroundAndParallel(t *testing.T) { + fin := testdir.CreateTestDir() + defer fin() + + ui := getAnalyzedPathMockedApp(t, true, true, false) + ui.remover = testanalyze.RemoveItemFromDirWithSleep + ui.done = make(chan struct{}) + ui.SetDeleteInBackground() + ui.SetDeleteInParallel() + + assert.Equal(t, 1, ui.table.GetRowCount()) + + ui.table.Select(0, 0) + + ui.deleteSelected(false) + + <-ui.done + + for _, f := range ui.app.(*testapp.MockedApp).GetUpdateDraws() { + f() + } + + assert.NoDirExists(t, "test_dir/nested") +} + func TestDeleteSelectedInBackgroundBW(t *testing.T) { fin := testdir.CreateTestDir() defer fin() @@ -568,6 +616,36 @@ func TestDeleteMarkedInBackgroundWithStorage(t *testing.T) { assert.NoFileExists(t, "test_dir/nested/file2") } +func TestDeleteMarkedInBackgroundWithStorageAndParallel(t *testing.T) { + fin := testdir.CreateTestDir() + defer fin() + + ui := getAnalyzedPathMockedApp(t, false, true, false) + ui.SetAnalyzer(analyze.CreateStoredAnalyzer("/tmp/badger")) + ui.SetDeleteInBackground() + ui.SetDeleteInParallel() + + assert.Equal(t, 1, ui.table.GetRowCount()) + + ui.fileItemSelected(0, 0) // nested + + ui.markedRows[1] = struct{}{} // subnested + ui.markedRows[2] = struct{}{} // file2 + + ui.deleteMarked(false) + + <-ui.done // wait for deletion of subnested + <-ui.done // wait for deletion of file2 + + for _, f := range ui.app.(*testapp.MockedApp).GetUpdateDraws() { + f() + } + + assert.DirExists(t, "test_dir/nested") + assert.NoDirExists(t, "test_dir/nested/subnested") + assert.NoFileExists(t, "test_dir/nested/file2") +} + func TestDeleteMarkedInBackgroundWithErr(t *testing.T) { fin := testdir.CreateTestDir() defer fin() From 794a2fafcc44018af585c7a8bb2268f3d6ab7a92 Mon Sep 17 00:00:00 2001 From: Daniel Milde Date: Sun, 14 Apr 2024 22:58:57 +0200 Subject: [PATCH 3/3] test for subitem --- pkg/remove/parallel_linux_test.go | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/pkg/remove/parallel_linux_test.go b/pkg/remove/parallel_linux_test.go index 1bd05199e..0f6e61ff7 100644 --- a/pkg/remove/parallel_linux_test.go +++ b/pkg/remove/parallel_linux_test.go @@ -9,6 +9,7 @@ import ( "github.com/dundee/gdu/v5/internal/testdir" "github.com/dundee/gdu/v5/pkg/analyze" + "github.com/dundee/gdu/v5/pkg/fs" "github.com/stretchr/testify/assert" ) @@ -40,3 +41,27 @@ func TestRemoveItemFromDirParallelWithErr(t *testing.T) { err = RemoveItemFromDirParallel(dir, subdir) assert.Contains(t, err.Error(), "permission denied") } + +func TestRemoveItemFromDirParallelWithErr2(t *testing.T) { + fin := testdir.CreateTestDir() + defer fin() + + err := os.Chmod("test_dir/nested/subnested", 0) + assert.Nil(t, err) + defer func() { + err = os.Chmod("test_dir/nested/subnested", 0755) + assert.Nil(t, err) + }() + + analyzer := analyze.CreateAnalyzer() + dir := analyzer.AnalyzeDir( + "test_dir", func(_, _ string) bool { return false }, false, + ).(*analyze.Dir) + analyzer.GetDone().Wait() + dir.UpdateStats(make(fs.HardLinkedItems)) + + subdir := dir.Files[0].(*analyze.Dir) + + err = RemoveItemFromDirParallel(dir, subdir) + assert.Contains(t, err.Error(), "permission denied") +}