From ebef05b82fc7dd5fbd646a3790a75a960fc69abd Mon Sep 17 00:00:00 2001 From: kadogo Date: Sat, 10 Feb 2024 22:07:34 +0100 Subject: [PATCH 1/5] Add export-file in interactive mode Signed-off-by: kadogo --- cmd/gdu/app/app.go | 7 ++++ cmd/gdu/main.go | 1 + gdu.1.md | 102 --------------------------------------------- tui/keys.go | 21 ++++++++++ tui/show.go | 1 + tui/tui.go | 6 +++ 6 files changed, 36 insertions(+), 102 deletions(-) delete mode 100644 gdu.1.md diff --git a/cmd/gdu/app/app.go b/cmd/gdu/app/app.go index 61973500e..ca060a9e6 100644 --- a/cmd/gdu/app/app.go +++ b/cmd/gdu/app/app.go @@ -43,6 +43,7 @@ type Flags struct { CfgFile string `yaml:"-"` LogFile string `yaml:"log-file"` InputFile string `yaml:"input-file"` + ExportTree string `yaml:"export-file"` OutputFile string `yaml:"output-file"` IgnoreDirs []string `yaml:"ignore-dirs"` IgnoreDirPatterns []string `yaml:"ignore-dir-patterns"` @@ -253,6 +254,12 @@ func (a *App) createUI() (UI, error) { }) } + if a.Flags.ExportTree != "" { + opts = append(opts, func(ui *tui.UI) { + ui.SetExportTree(a.Flags.ExportTree) + }) + } + ui = tui.CreateUI( a.TermApp, a.Screen, diff --git a/cmd/gdu/main.go b/cmd/gdu/main.go index 0c28296c5..1f1434765 100644 --- a/cmd/gdu/main.go +++ b/cmd/gdu/main.go @@ -42,6 +42,7 @@ func init() { flags.StringVarP(&af.LogFile, "log-file", "l", "/dev/null", "Path to a logfile") flags.StringVarP(&af.OutputFile, "output-file", "o", "", "Export all info into file as JSON") flags.StringVarP(&af.InputFile, "input-file", "f", "", "Import analysis from JSON file") + flags.StringVarP(&af.ExportTree, "export-file", "e", "", "Export all info into file as JSON when doing uppercase S") flags.IntVarP(&af.MaxCores, "max-cores", "m", runtime.NumCPU(), fmt.Sprintf("Set max cores that GDU will use. %d cores available", runtime.NumCPU())) flags.BoolVarP(&af.ShowVersion, "version", "v", false, "Print version") diff --git a/gdu.1.md b/gdu.1.md deleted file mode 100644 index e6d4dc6a7..000000000 --- a/gdu.1.md +++ /dev/null @@ -1,102 +0,0 @@ ---- -date: {{date}} -section: 1 -title: gdu ---- - -# NAME - -gdu - Pretty fast disk usage analyzer written in Go - -# SYNOPSIS - -**gdu \[flags\] \[directory_to_scan\]** - -# DESCRIPTION - -Pretty fast disk usage analyzer written in Go. - -Gdu is intended primarily for SSD disks where it can fully utilize -parallel processing. However HDDs work as well, but the performance gain -is not so huge. - -# OPTIONS - -**-h**, **\--help**\[=false\] help for gdu - -**-i**, **\--ignore-dirs**=\[/proc,/dev,/sys,/run\] Absolute paths to -ignore (separated by comma) - -**-I**, **\--ignore-dirs-pattern** Absolute path patterns to -ignore (separated by comma) - -**-X**, **\--ignore-from** Read absolute path patterns to ignore from file - -**-l**, **\--log-file**=\"/dev/null\" Path to a logfile - -**-m**, **\--max-cores** Set max cores that GDU will use. - -**-c**, **\--no-color**\[=false\] Do not use colorized output - -**-x**, **\--no-cross**\[=false\] Do not cross filesystem boundaries - -**-H**, **\--no-hidden**\[=false\] Ignore hidden directories (beginning with dot) - -**-L**, **\--follow-symlinks**\[=false\] Follow symlinks for files, i.e. show the -size of the file to which symlink points to (symlinks to directories are not followed) - -**-n**, **\--non-interactive**\[=false\] Do not run in interactive mode - -**-p**, **\--no-progress**\[=false\] Do not show progress in -non-interactive mode - -**-s**, **\--summarize**\[=false\] Show only a total in non-interactive mode - -**-d**, **\--show-disks**\[=false\] Show all mounted disks - -**-a**, **\--show-apparent-size**\[=false\] Show apparent size - -**\--si**\[=false\] Show sizes with decimal SI prefixes (kB, MB, GB) instead of binary prefixes (KiB, MiB, GiB) - -**\--no-prefix**\[=false\] Show sizes as raw numbers without any prefixes (SI or binary) in non-interactive mode - -**\--no-mouse**\[=false\] Do not use mouse - -**-f**, **\----input-file** Import analysis from JSON file. If the file is \"-\", read from standard input. - -**-o**, **\----output-file** Export all info into file as JSON. If the file is \"-\", write to standard output. - -**\--config-file**=\"$HOME/.gdu.yaml\" Read config from file - -**\--write-config**\[=false\] Write current configuration to file (default is $HOME/.gdu.yaml) - -**-g**, **\--const-gc**\[=false\] Enable memory garbage collection during analysis with constant level set by GOGC - -**\--enable-profiling**\[=false\] Enable collection of profiling data and provide it on http://localhost:6060/debug/pprof/ - -**-v**, **\--version**\[=false\] Print version - -# FILE FLAGS - -Files and directories may be prefixed by a one-character -flag with following meaning: - -**!** - -: An error occurred while reading this directory. - -**.** - -: An error occurred while reading a subdirectory, size may be not correct. - -**\@** - -: File is symlink or socket. - -**H** - -: Same file was already counted (hard link). - -**e** - -: Directory is empty. diff --git a/tui/keys.go b/tui/keys.go index 76629dcbf..775ee6af2 100644 --- a/tui/keys.go +++ b/tui/keys.go @@ -2,6 +2,10 @@ package tui import ( "fmt" + "bytes" + "strconv" + "time" + "os" "github.com/dundee/gdu/v5/pkg/fs" "github.com/gdamore/tcell/v2" @@ -231,6 +235,23 @@ func (ui *UI) handleMainActions(key *tcell.EventKey) *tcell.EventKey { if ui.currentDir != nil { ui.rescanDir() } + case 'S': + var buff bytes.Buffer + + buff.Write([]byte(`[1,2,{"progname":"gdu","progver":"`)) + //~ buff.Write([]byte(build.Version)) + buff.Write([]byte(`","timestamp":`)) + buff.Write([]byte(strconv.FormatInt(time.Now().Unix(), 10))) + buff.Write([]byte("},\n")) + + file, fileErr := os.Create(ui.exportTree) + if fileErr != nil { + fmt.Println(fileErr) + } + + ui.topDir.EncodeJSON(&buff, true) + buff.Write([]byte("]\n")) + buff.WriteTo(file) case 's': ui.setSorting("size") case 'C': diff --git a/tui/show.go b/tui/show.go index 765a6a67a..4b14b1e0f 100644 --- a/tui/show.go +++ b/tui/show.go @@ -17,6 +17,7 @@ const helpText = ` [::b]up/down, k/j [white:black:-]Move cursor up/down [::b]left, h [white:black:-]Go to parent directory [::b]r [white:black:-]Rescan current directory + [::b]S [white:black:-]Export actual tree [::b]/ [white:black:-]Search items by name [::b]a [white:black:-]Toggle between showing disk usage and apparent size [::b]B [white:black:-]Toggle bar alignment to biggest file or directory diff --git a/tui/tui.go b/tui/tui.go index baaee7348..f3a8fb285 100644 --- a/tui/tui.go +++ b/tui/tui.go @@ -33,6 +33,7 @@ type UI struct { topDir fs.Item topDirPath string currentDirPath string + exportTree string askBeforeDelete bool showItemCount bool showMtime bool @@ -181,6 +182,11 @@ func (ui *UI) SetCurrentItemNameMaxLen(len int) { ui.currentItemNameMaxLen = len } +// SetExportTree set the path where to export when doing the key "S" +func (ui *UI) SetExportTree(path string) { + ui.exportTree = path +} + // UseOldSizeBar uses the old size bar (# chars) instead of the new one (unicode block elements) func (ui *UI) UseOldSizeBar() { ui.useOldSizeBar = true From 65e1582ecea9c0c27608b7ed4da26dbfa45b8ab4 Mon Sep 17 00:00:00 2001 From: kadogo Date: Sat, 10 Feb 2024 22:10:24 +0100 Subject: [PATCH 2/5] Add againd gdu.1.md because it was removed by error Signed-off-by: kadogo --- gdu.1.md | 102 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 102 insertions(+) create mode 100644 gdu.1.md diff --git a/gdu.1.md b/gdu.1.md new file mode 100644 index 000000000..e6d4dc6a7 --- /dev/null +++ b/gdu.1.md @@ -0,0 +1,102 @@ +--- +date: {{date}} +section: 1 +title: gdu +--- + +# NAME + +gdu - Pretty fast disk usage analyzer written in Go + +# SYNOPSIS + +**gdu \[flags\] \[directory_to_scan\]** + +# DESCRIPTION + +Pretty fast disk usage analyzer written in Go. + +Gdu is intended primarily for SSD disks where it can fully utilize +parallel processing. However HDDs work as well, but the performance gain +is not so huge. + +# OPTIONS + +**-h**, **\--help**\[=false\] help for gdu + +**-i**, **\--ignore-dirs**=\[/proc,/dev,/sys,/run\] Absolute paths to +ignore (separated by comma) + +**-I**, **\--ignore-dirs-pattern** Absolute path patterns to +ignore (separated by comma) + +**-X**, **\--ignore-from** Read absolute path patterns to ignore from file + +**-l**, **\--log-file**=\"/dev/null\" Path to a logfile + +**-m**, **\--max-cores** Set max cores that GDU will use. + +**-c**, **\--no-color**\[=false\] Do not use colorized output + +**-x**, **\--no-cross**\[=false\] Do not cross filesystem boundaries + +**-H**, **\--no-hidden**\[=false\] Ignore hidden directories (beginning with dot) + +**-L**, **\--follow-symlinks**\[=false\] Follow symlinks for files, i.e. show the +size of the file to which symlink points to (symlinks to directories are not followed) + +**-n**, **\--non-interactive**\[=false\] Do not run in interactive mode + +**-p**, **\--no-progress**\[=false\] Do not show progress in +non-interactive mode + +**-s**, **\--summarize**\[=false\] Show only a total in non-interactive mode + +**-d**, **\--show-disks**\[=false\] Show all mounted disks + +**-a**, **\--show-apparent-size**\[=false\] Show apparent size + +**\--si**\[=false\] Show sizes with decimal SI prefixes (kB, MB, GB) instead of binary prefixes (KiB, MiB, GiB) + +**\--no-prefix**\[=false\] Show sizes as raw numbers without any prefixes (SI or binary) in non-interactive mode + +**\--no-mouse**\[=false\] Do not use mouse + +**-f**, **\----input-file** Import analysis from JSON file. If the file is \"-\", read from standard input. + +**-o**, **\----output-file** Export all info into file as JSON. If the file is \"-\", write to standard output. + +**\--config-file**=\"$HOME/.gdu.yaml\" Read config from file + +**\--write-config**\[=false\] Write current configuration to file (default is $HOME/.gdu.yaml) + +**-g**, **\--const-gc**\[=false\] Enable memory garbage collection during analysis with constant level set by GOGC + +**\--enable-profiling**\[=false\] Enable collection of profiling data and provide it on http://localhost:6060/debug/pprof/ + +**-v**, **\--version**\[=false\] Print version + +# FILE FLAGS + +Files and directories may be prefixed by a one-character +flag with following meaning: + +**!** + +: An error occurred while reading this directory. + +**.** + +: An error occurred while reading a subdirectory, size may be not correct. + +**\@** + +: File is symlink or socket. + +**H** + +: Same file was already counted (hard link). + +**e** + +: Directory is empty. From 2f1191261953d3401e0fe48be85528863942741a Mon Sep 17 00:00:00 2001 From: Daniel Milde Date: Tue, 13 Feb 2024 00:57:03 +0100 Subject: [PATCH 3/5] feat: export with dialog for file name --- cmd/gdu/app/app.go | 7 ----- cmd/gdu/main.go | 1 - tui/actions.go | 72 ++++++++++++++++++++++++++++++++++++++++++++++ tui/keys.go | 26 +++-------------- tui/show.go | 9 +++++- tui/tui.go | 8 ++---- tui/utils.go | 11 +++++++ 7 files changed, 97 insertions(+), 37 deletions(-) diff --git a/cmd/gdu/app/app.go b/cmd/gdu/app/app.go index ca060a9e6..61973500e 100644 --- a/cmd/gdu/app/app.go +++ b/cmd/gdu/app/app.go @@ -43,7 +43,6 @@ type Flags struct { CfgFile string `yaml:"-"` LogFile string `yaml:"log-file"` InputFile string `yaml:"input-file"` - ExportTree string `yaml:"export-file"` OutputFile string `yaml:"output-file"` IgnoreDirs []string `yaml:"ignore-dirs"` IgnoreDirPatterns []string `yaml:"ignore-dir-patterns"` @@ -254,12 +253,6 @@ func (a *App) createUI() (UI, error) { }) } - if a.Flags.ExportTree != "" { - opts = append(opts, func(ui *tui.UI) { - ui.SetExportTree(a.Flags.ExportTree) - }) - } - ui = tui.CreateUI( a.TermApp, a.Screen, diff --git a/cmd/gdu/main.go b/cmd/gdu/main.go index 1f1434765..0c28296c5 100644 --- a/cmd/gdu/main.go +++ b/cmd/gdu/main.go @@ -42,7 +42,6 @@ func init() { flags.StringVarP(&af.LogFile, "log-file", "l", "/dev/null", "Path to a logfile") flags.StringVarP(&af.OutputFile, "output-file", "o", "", "Export all info into file as JSON") flags.StringVarP(&af.InputFile, "input-file", "f", "", "Import analysis from JSON file") - flags.StringVarP(&af.ExportTree, "export-file", "e", "", "Export all info into file as JSON when doing uppercase S") flags.IntVarP(&af.MaxCores, "max-cores", "m", runtime.NumCPU(), fmt.Sprintf("Set max cores that GDU will use. %d cores available", runtime.NumCPU())) flags.BoolVarP(&af.ShowVersion, "version", "v", false, "Print version") diff --git a/tui/actions.go b/tui/actions.go index c9b39d603..1fd2a4d07 100644 --- a/tui/actions.go +++ b/tui/actions.go @@ -2,13 +2,16 @@ package tui import ( "bufio" + "bytes" "fmt" "io" "os" "os/exec" "runtime" "runtime/debug" + "strconv" "strings" + "time" "github.com/dundee/gdu/v5/build" "github.com/dundee/gdu/v5/pkg/analyze" @@ -380,3 +383,72 @@ func (ui *UI) openItem() { ui.showErr("Error opening", err) } } + +func (ui *UI) confirmExport() { + form := tview.NewForm(). + AddInputField("File name", "export.json", 30, nil, func(v string) { + ui.exportName = v + }). + AddButton("Export", ui.exportAnalysis). + SetButtonsAlign(tview.AlignCenter) + form.SetBorder(true). + SetTitle(" Export data to JSON "). + SetInputCapture(func(key *tcell.EventKey) *tcell.EventKey { + if key.Key() == tcell.KeyEsc { + ui.pages.RemovePage("export") + ui.app.SetFocus(ui.table) + return nil + } + return key + }) + flex := modal(form, 50, 7) + ui.pages.AddPage("export", flex, true, true) + ui.app.SetFocus(form) +} + +func (ui *UI) exportAnalysis() { + ui.pages.RemovePage("export") + + text := tview.NewTextView().SetText("Export in progress...").SetTextAlign(tview.AlignCenter) + text.SetBorder(true).SetTitle(" Export data to JSON ") + flex := modal(text, 50, 3) + ui.pages.AddPage("exporting", flex, true, true) + + go func() { + var err error + defer ui.app.QueueUpdateDraw(func() { + ui.pages.RemovePage("exporting") + if err == nil { + ui.app.SetFocus(ui.table) + } + }) + + var buff bytes.Buffer + + buff.Write([]byte(`[1,2,{"progname":"gdu","progver":"`)) + buff.Write([]byte(build.Version)) + buff.Write([]byte(`","timestamp":`)) + buff.Write([]byte(strconv.FormatInt(time.Now().Unix(), 10))) + buff.Write([]byte("},\n")) + + file, err := os.Create(ui.exportName) + if err != nil { + ui.showErrFromGo("Error creating file", err) + return + } + + if err = ui.topDir.EncodeJSON(&buff, true); err != nil { + ui.showErrFromGo("Error encoding JSON", err) + return + } + + if _, err = buff.Write([]byte("]\n")); err != nil { + ui.showErrFromGo("Error writting to buffer", err) + return + } + if _, err = buff.WriteTo(file); err != nil { + ui.showErrFromGo("Error writting to file", err) + return + } + }() +} diff --git a/tui/keys.go b/tui/keys.go index 775ee6af2..1bb475bfa 100644 --- a/tui/keys.go +++ b/tui/keys.go @@ -2,10 +2,6 @@ package tui import ( "fmt" - "bytes" - "strconv" - "time" - "os" "github.com/dundee/gdu/v5/pkg/fs" "github.com/gdamore/tcell/v2" @@ -17,7 +13,7 @@ func (ui *UI) keyPressed(key *tcell.EventKey) *tcell.EventKey { return nil } - if ui.pages.HasPage("file") { + if ui.pages.HasPage("file") || ui.pages.HasPage("export") { return key // send event to primitive } if ui.filtering { @@ -235,23 +231,9 @@ func (ui *UI) handleMainActions(key *tcell.EventKey) *tcell.EventKey { if ui.currentDir != nil { ui.rescanDir() } - case 'S': - var buff bytes.Buffer - - buff.Write([]byte(`[1,2,{"progname":"gdu","progver":"`)) - //~ buff.Write([]byte(build.Version)) - buff.Write([]byte(`","timestamp":`)) - buff.Write([]byte(strconv.FormatInt(time.Now().Unix(), 10))) - buff.Write([]byte("},\n")) - - file, fileErr := os.Create(ui.exportTree) - if fileErr != nil { - fmt.Println(fileErr) - } - - ui.topDir.EncodeJSON(&buff, true) - buff.Write([]byte("]\n")) - buff.WriteTo(file) + case 'E': + ui.confirmExport() + return nil case 's': ui.setSorting("size") case 'C': diff --git a/tui/show.go b/tui/show.go index 4b14b1e0f..abf35cab9 100644 --- a/tui/show.go +++ b/tui/show.go @@ -17,7 +17,7 @@ const helpText = ` [::b]up/down, k/j [white:black:-]Move cursor up/down [::b]left, h [white:black:-]Go to parent directory [::b]r [white:black:-]Rescan current directory - [::b]S [white:black:-]Export actual tree + [::b]E [white:black:-]Export analysis data to file as JSON [::b]/ [white:black:-]Search items by name [::b]a [white:black:-]Toggle between showing disk usage and apparent size [::b]B [white:black:-]Toggle bar alignment to biggest file or directory @@ -234,6 +234,13 @@ func (ui *UI) showErr(msg string, err error) { } ui.pages.AddPage("error", modal, true, true) + ui.app.SetFocus(modal) +} + +func (ui *UI) showErrFromGo(msg string, err error) { + ui.app.QueueUpdateDraw(func() { + ui.showErr(msg, err) + }) } func (ui *UI) showHelp() { diff --git a/tui/tui.go b/tui/tui.go index f3a8fb285..dec7eb750 100644 --- a/tui/tui.go +++ b/tui/tui.go @@ -33,7 +33,6 @@ type UI struct { topDir fs.Item topDirPath string currentDirPath string - exportTree string askBeforeDelete bool showItemCount bool showMtime bool @@ -55,6 +54,7 @@ type UI struct { defaultSortBy string defaultSortOrder string markedRows map[int]struct{} + exportName string } // Option is optional function customizing the bahaviour of UI @@ -96,6 +96,7 @@ func CreateUI( defaultSortBy: "size", defaultSortOrder: "desc", markedRows: make(map[int]struct{}), + exportName: "export.json", } for _, o := range opts { o(ui) @@ -182,11 +183,6 @@ func (ui *UI) SetCurrentItemNameMaxLen(len int) { ui.currentItemNameMaxLen = len } -// SetExportTree set the path where to export when doing the key "S" -func (ui *UI) SetExportTree(path string) { - ui.exportTree = path -} - // UseOldSizeBar uses the old size bar (# chars) instead of the new one (unicode block elements) func (ui *UI) UseOldSizeBar() { ui.useOldSizeBar = true diff --git a/tui/utils.go b/tui/utils.go index f38aa3342..351f679c9 100644 --- a/tui/utils.go +++ b/tui/utils.go @@ -2,6 +2,7 @@ package tui import ( "github.com/dundee/gdu/v5/pkg/device" + "github.com/rivo/tview" ) var ( @@ -65,3 +66,13 @@ func min(a, b int) int { } return b } + +func modal(p tview.Primitive, width, height int) tview.Primitive { + return tview.NewFlex(). + AddItem(nil, 0, 1, false). + AddItem(tview.NewFlex().SetDirection(tview.FlexRow). + AddItem(nil, 0, 1, false). + AddItem(p, height, 1, true). + AddItem(nil, 0, 1, false), width, 1, true). + AddItem(nil, 0, 1, false) +} From ec3d1a5ebbf7aa6ebd9b3e029d5920d031371cb8 Mon Sep 17 00:00:00 2001 From: Daniel Milde Date: Tue, 13 Feb 2024 01:29:24 +0100 Subject: [PATCH 4/5] test: basic test for export --- tui/actions.go | 4 ++++ tui/actions_test.go | 51 +++++++++++++++++++++++++++++++++++++++++++++ tui/tui_test.go | 4 ++-- 3 files changed, 57 insertions(+), 2 deletions(-) diff --git a/tui/actions.go b/tui/actions.go index 1fd2a4d07..ca13429ba 100644 --- a/tui/actions.go +++ b/tui/actions.go @@ -450,5 +450,9 @@ func (ui *UI) exportAnalysis() { ui.showErrFromGo("Error writting to file", err) return } + + if ui.done != nil { + ui.done <- struct{}{} + } }() } diff --git a/tui/actions_test.go b/tui/actions_test.go index c64212797..388272814 100644 --- a/tui/actions_test.go +++ b/tui/actions_test.go @@ -492,3 +492,54 @@ func TestExitViewFile(t *testing.T) { assert.False(t, ui.pages.HasPage("file")) } + +func TestExportAnalysis(t *testing.T) { + parentDir := &analyze.Dir{ + File: &analyze.File{ + Name: "parent", + }, + Files: make([]fs.Item, 0, 1), + } + currentDir := &analyze.Dir{ + File: &analyze.File{ + Name: "sub", + Parent: parentDir, + }, + } + + simScreen := testapp.CreateSimScreen() + defer simScreen.Fini() + + app := testapp.CreateMockedApp(true) + ui := CreateUI(app, simScreen, &bytes.Buffer{}, false, true, false, false, false) + ui.done = make(chan struct{}) + ui.Analyzer = &testanalyze.MockedAnalyzer{} + ui.currentDir = currentDir + ui.topDir = parentDir + + assert.Equal(t, "sub", ui.currentDir.GetName()) + + ui.keyPressed(tcell.NewEventKey(tcell.KeyRune, 'E', 0)) + + assert.True(t, ui.pages.HasPage("export")) + + ui.keyPressed(tcell.NewEventKey(tcell.KeyEnter, 0, 0)) + + assert.True(t, ui.pages.HasPage("export")) + + // we cannot send Enter to the form from here + + ui.exportAnalysis() + + assert.True(t, ui.pages.HasPage("exporting")) + + <-ui.done + + assert.FileExists(t, "export.json") + err := os.Remove("export.json") + assert.NoError(t, err) + + for _, f := range ui.app.(*testapp.MockedApp).GetUpdateDraws() { + f() + } +} diff --git a/tui/tui_test.go b/tui/tui_test.go index db2c5ce06..43c89089d 100644 --- a/tui/tui_test.go +++ b/tui/tui_test.go @@ -93,7 +93,7 @@ func TestHelp(t *testing.T) { b, _, _ := simScreen.GetContents() - cells := b[406 : 406+9] + cells := b[456 : 456+9] text := []byte("directory") for i, r := range cells { @@ -114,7 +114,7 @@ func TestHelpBw(t *testing.T) { b, _, _ := simScreen.GetContents() - cells := b[406 : 406+9] + cells := b[456 : 456+9] text := []byte("directory") for i, r := range cells { From 51797c3ac3dc4d9835b22c514989e806fb689d3e Mon Sep 17 00:00:00 2001 From: Daniel Milde Date: Tue, 13 Feb 2024 23:27:03 +0100 Subject: [PATCH 5/5] test: more coverage --- tui/actions.go | 12 +-- tui/actions_test.go | 51 ----------- tui/export_test.go | 203 ++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 210 insertions(+), 56 deletions(-) create mode 100644 tui/export_test.go diff --git a/tui/actions.go b/tui/actions.go index ca13429ba..7f60f6a67 100644 --- a/tui/actions.go +++ b/tui/actions.go @@ -384,7 +384,7 @@ func (ui *UI) openItem() { } } -func (ui *UI) confirmExport() { +func (ui *UI) confirmExport() *tview.Form { form := tview.NewForm(). AddInputField("File name", "export.json", 30, nil, func(v string) { ui.exportName = v @@ -404,6 +404,7 @@ func (ui *UI) confirmExport() { flex := modal(form, 50, 7) ui.pages.AddPage("export", flex, true, true) ui.app.SetFocus(form) + return form } func (ui *UI) exportAnalysis() { @@ -422,6 +423,11 @@ func (ui *UI) exportAnalysis() { ui.app.SetFocus(ui.table) } }) + if ui.done != nil { + defer func() { + ui.done <- struct{}{} + }() + } var buff bytes.Buffer @@ -450,9 +456,5 @@ func (ui *UI) exportAnalysis() { ui.showErrFromGo("Error writting to file", err) return } - - if ui.done != nil { - ui.done <- struct{}{} - } }() } diff --git a/tui/actions_test.go b/tui/actions_test.go index 388272814..c64212797 100644 --- a/tui/actions_test.go +++ b/tui/actions_test.go @@ -492,54 +492,3 @@ func TestExitViewFile(t *testing.T) { assert.False(t, ui.pages.HasPage("file")) } - -func TestExportAnalysis(t *testing.T) { - parentDir := &analyze.Dir{ - File: &analyze.File{ - Name: "parent", - }, - Files: make([]fs.Item, 0, 1), - } - currentDir := &analyze.Dir{ - File: &analyze.File{ - Name: "sub", - Parent: parentDir, - }, - } - - simScreen := testapp.CreateSimScreen() - defer simScreen.Fini() - - app := testapp.CreateMockedApp(true) - ui := CreateUI(app, simScreen, &bytes.Buffer{}, false, true, false, false, false) - ui.done = make(chan struct{}) - ui.Analyzer = &testanalyze.MockedAnalyzer{} - ui.currentDir = currentDir - ui.topDir = parentDir - - assert.Equal(t, "sub", ui.currentDir.GetName()) - - ui.keyPressed(tcell.NewEventKey(tcell.KeyRune, 'E', 0)) - - assert.True(t, ui.pages.HasPage("export")) - - ui.keyPressed(tcell.NewEventKey(tcell.KeyEnter, 0, 0)) - - assert.True(t, ui.pages.HasPage("export")) - - // we cannot send Enter to the form from here - - ui.exportAnalysis() - - assert.True(t, ui.pages.HasPage("exporting")) - - <-ui.done - - assert.FileExists(t, "export.json") - err := os.Remove("export.json") - assert.NoError(t, err) - - for _, f := range ui.app.(*testapp.MockedApp).GetUpdateDraws() { - f() - } -} diff --git a/tui/export_test.go b/tui/export_test.go new file mode 100644 index 000000000..52fd22cc7 --- /dev/null +++ b/tui/export_test.go @@ -0,0 +1,203 @@ +package tui + +import ( + "bytes" + "os" + "testing" + + "github.com/dundee/gdu/v5/internal/testanalyze" + "github.com/dundee/gdu/v5/internal/testapp" + "github.com/dundee/gdu/v5/pkg/analyze" + "github.com/dundee/gdu/v5/pkg/fs" + "github.com/gdamore/tcell/v2" + "github.com/rivo/tview" + "github.com/stretchr/testify/assert" +) + +func TestConfirmExport(t *testing.T) { + simScreen := testapp.CreateSimScreen() + defer simScreen.Fini() + + app := testapp.CreateMockedApp(true) + ui := CreateUI(app, simScreen, &bytes.Buffer{}, false, true, false, false, false) + ui.done = make(chan struct{}) + ui.Analyzer = &testanalyze.MockedAnalyzer{} + + ui.keyPressed(tcell.NewEventKey(tcell.KeyRune, 'E', 0)) + + assert.True(t, ui.pages.HasPage("export")) + + ui.keyPressed(tcell.NewEventKey(tcell.KeyRune, 'n', 0)) + ui.keyPressed(tcell.NewEventKey(tcell.KeyEnter, 0, 0)) + + assert.True(t, ui.pages.HasPage("export")) +} + +func TestExportAnalysis(t *testing.T) { + parentDir := &analyze.Dir{ + File: &analyze.File{ + Name: "parent", + }, + Files: make([]fs.Item, 0, 1), + } + currentDir := &analyze.Dir{ + File: &analyze.File{ + Name: "sub", + Parent: parentDir, + }, + } + + simScreen := testapp.CreateSimScreen() + defer simScreen.Fini() + + app := testapp.CreateMockedApp(true) + ui := CreateUI(app, simScreen, &bytes.Buffer{}, false, true, false, false, false) + ui.done = make(chan struct{}) + ui.Analyzer = &testanalyze.MockedAnalyzer{} + ui.currentDir = currentDir + ui.topDir = parentDir + + ui.exportAnalysis() + + assert.True(t, ui.pages.HasPage("exporting")) + + <-ui.done + + assert.FileExists(t, "export.json") + err := os.Remove("export.json") + assert.NoError(t, err) + + for _, f := range ui.app.(*testapp.MockedApp).GetUpdateDraws() { + f() + } +} + +func TestExportAnalysisEsc(t *testing.T) { + parentDir := &analyze.Dir{ + File: &analyze.File{ + Name: "parent", + }, + Files: make([]fs.Item, 0, 1), + } + currentDir := &analyze.Dir{ + File: &analyze.File{ + Name: "sub", + Parent: parentDir, + }, + } + + simScreen := testapp.CreateSimScreen() + defer simScreen.Fini() + + app := testapp.CreateMockedApp(true) + ui := CreateUI(app, simScreen, &bytes.Buffer{}, false, true, false, false, false) + ui.done = make(chan struct{}) + ui.Analyzer = &testanalyze.MockedAnalyzer{} + ui.currentDir = currentDir + ui.topDir = parentDir + + form := ui.confirmExport() + formInputFn := form.GetInputCapture() + + assert.True(t, ui.pages.HasPage("export")) + + formInputFn(tcell.NewEventKey(tcell.KeyEsc, 0, 0)) + + assert.False(t, ui.pages.HasPage("export")) +} + +func TestExportAnalysisWithName(t *testing.T) { + parentDir := &analyze.Dir{ + File: &analyze.File{ + Name: "parent", + }, + Files: make([]fs.Item, 0, 1), + } + currentDir := &analyze.Dir{ + File: &analyze.File{ + Name: "sub", + Parent: parentDir, + }, + } + + simScreen := testapp.CreateSimScreen() + defer simScreen.Fini() + + app := testapp.CreateMockedApp(true) + ui := CreateUI(app, simScreen, &bytes.Buffer{}, false, true, false, false, false) + ui.done = make(chan struct{}) + ui.Analyzer = &testanalyze.MockedAnalyzer{} + ui.currentDir = currentDir + ui.topDir = parentDir + + form := ui.confirmExport() + // formInputFn := form.GetInputCapture() + item := form.GetFormItemByLabel("File name") + inputFn := item.(*tview.InputField).InputHandler() + + // send 'n' to input + inputFn(tcell.NewEventKey(tcell.KeyRune, 'n', 0), nil) + assert.Equal(t, "export.jsonn", ui.exportName) + + assert.True(t, ui.pages.HasPage("export")) + + form.GetButton(0).InputHandler()(tcell.NewEventKey(tcell.KeyEnter, 0, 0), nil) + + assert.True(t, ui.pages.HasPage("exporting")) + + <-ui.done + + assert.FileExists(t, "export.jsonn") + err := os.Remove("export.jsonn") + assert.NoError(t, err) + + for _, f := range ui.app.(*testapp.MockedApp).GetUpdateDraws() { + f() + } +} + +func TestExportAnalysisWithoutRights(t *testing.T) { + parentDir := &analyze.Dir{ + File: &analyze.File{ + Name: "parent", + }, + Files: make([]fs.Item, 0, 1), + } + currentDir := &analyze.Dir{ + File: &analyze.File{ + Name: "sub", + Parent: parentDir, + }, + } + + _, err := os.Create("export.json") + assert.NoError(t, err) + err = os.Chmod("export.json", 0) + assert.NoError(t, err) + defer func() { + err = os.Chmod("export.json", 0755) + assert.Nil(t, err) + err = os.Remove("export.json") + assert.NoError(t, err) + }() + + simScreen := testapp.CreateSimScreen() + defer simScreen.Fini() + + app := testapp.CreateMockedApp(true) + ui := CreateUI(app, simScreen, &bytes.Buffer{}, false, true, false, false, false) + ui.done = make(chan struct{}) + ui.Analyzer = &testanalyze.MockedAnalyzer{} + ui.currentDir = currentDir + ui.topDir = parentDir + + ui.exportAnalysis() + + <-ui.done + + for _, f := range ui.app.(*testapp.MockedApp).GetUpdateDraws() { + f() + } + + assert.True(t, ui.pages.HasPage("error")) +}