fix(rm): propagate verbose flag to generate-entry cleanup (#2109)

This commit is contained in:
Emanuele De Cupis
2026-06-11 18:27:39 +02:00
committed by Alessio Biancalana
parent d28032c066
commit 7fa2ed9ef1
4 changed files with 143 additions and 13 deletions
+1
View File
@@ -63,6 +63,7 @@ func rmAction(ctx context.Context, cmd *cli.Command, cfg *config.Values) error {
Force: cmd.Bool("force"),
All: cmd.Bool("all"),
RemoveHome: cmd.Bool("rm-home"),
Verbose: cmd.Bool("verbose"),
ContainerNames: names,
}
+6 -5
View File
@@ -32,6 +32,7 @@ type RmOptions struct {
Force bool
All bool
RemoveHome bool
Verbose bool
ContainerNames []string
}
@@ -68,7 +69,7 @@ func (c *RmCommand) Execute(ctx context.Context, options RmOptions) (*RmResult,
var removedDistroboxes []containermanager.Container
for _, currentDistrobox := range distroboxesToRemove {
err := c.removeContainer(ctx, currentDistrobox, options.Force, options.NoTTY, userHome)
err := c.removeContainer(ctx, currentDistrobox, options.Force, options.NoTTY, options.Verbose, userHome)
if err != nil {
//nolint:forbidigo // waiting for the logger implementation
fmt.Printf("error deleting %s: %s", currentDistrobox.Name, err)
@@ -84,6 +85,7 @@ func (c *RmCommand) removeContainer(
container containermanager.Container,
force bool,
noTTY bool,
verbose bool,
userHome string,
) error {
forceRemove := force
@@ -121,12 +123,12 @@ func (c *RmCommand) removeContainer(
return fmt.Errorf("failed to remove container: %w", err)
}
c.cleanup(ctx, userHome, container.Name)
c.cleanup(ctx, userHome, container.Name, verbose)
return nil
}
func (c *RmCommand) cleanup(ctx context.Context, userHome, containerName string) {
func (c *RmCommand) cleanup(ctx context.Context, userHome, containerName string, verbose bool) {
bins := findExportedBinaries(userHome, containerName)
desktopApps := findExportedDesktopApps(userHome, containerName)
@@ -144,8 +146,7 @@ func (c *RmCommand) cleanup(ctx context.Context, userHome, containerName string)
&GenerateEntryOptions{
ContainerName: containerName,
Delete: true,
// TODO: handle verbose
Verbose: false,
Verbose: verbose,
},
)
if err != nil {
+113
View File
@@ -0,0 +1,113 @@
package commands_test
import (
"bufio"
"context"
"io"
"os"
"path/filepath"
"strings"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/89luca89/distrobox/pkg/commands"
"github.com/89luca89/distrobox/pkg/config"
"github.com/89luca89/distrobox/pkg/containermanager"
"github.com/89luca89/distrobox/pkg/internal/testutil"
"github.com/89luca89/distrobox/pkg/ui"
)
func newTestRmCommand(mock *testutil.MockContainerManager) *commands.RmCommand {
prompter := ui.NewPrompter(*bufio.NewReader(strings.NewReader("")), io.Discard)
return commands.NewRmCommand(&config.Values{}, mock, prompter)
}
// writeExportedDesktopApp writes a minimal desktop file that
// findExportedDesktopApps will match for the given containerName (i.e.
// the per-app desktop entries created by distrobox-export, not the
// one created by `distrobox generate-entry`).
func writeExportedDesktopApp(t *testing.T, userHome, containerName string) string {
t.Helper()
appsDir := filepath.Join(userHome, ".local", "share", "applications")
require.NoError(t, os.MkdirAll(appsDir, 0o755))
desktopFile := filepath.Join(appsDir, containerName+"-app.desktop")
content := "[Desktop Entry]\nExec=/usr/bin/distrobox enter " + containerName + " -- some-app\n"
require.NoError(t, os.WriteFile(desktopFile, []byte(content), 0o644))
return desktopFile
}
// writeGenerateEntryDesktop writes the desktop file produced by
// `distrobox generate-entry <name>` so the generate-entry delete
// branch (invoked from RmCommand.cleanup) can be observed.
func writeGenerateEntryDesktop(t *testing.T, userHome, containerName string) string {
t.Helper()
appsDir := filepath.Join(userHome, ".local", "share", "applications")
require.NoError(t, os.MkdirAll(appsDir, 0o755))
entryFile := filepath.Join(appsDir, containerName+".desktop")
require.NoError(t, os.WriteFile(entryFile, []byte("[Desktop Entry]\nName="+containerName+"\n"), 0o644))
return entryFile
}
func TestRmCommand_Execute_CleanupRemovesExportedDesktopApp(t *testing.T) {
tempHome := t.TempDir()
t.Setenv("HOME", tempHome)
t.Setenv("XDG_DATA_HOME", filepath.Join(tempHome, ".local", "share"))
containerName := "test-rm-cleanup"
exportedDesktopFile := writeExportedDesktopApp(t, tempHome, containerName)
mock := &testutil.MockContainerManager{
ListContainersResult: []containermanager.Container{
{
Name: containerName,
Status: "Exited",
Labels: map[string]string{"manager": "distrobox"},
},
},
InspectContainerResult: &containermanager.InspectResult{
ContainerHome: tempHome,
},
}
cmd := newTestRmCommand(mock)
_, err := cmd.Execute(context.Background(), commands.RmOptions{
ContainerNames: []string{containerName},
Force: true,
NoTTY: true,
})
require.NoError(t, err)
assert.NoFileExists(t, exportedDesktopFile, "cleanup should remove the per-app exported desktop file")
}
func TestRmCommand_Execute_CleanupRemovesGenerateEntryDesktop(t *testing.T) {
tempHome := t.TempDir()
t.Setenv("HOME", tempHome)
t.Setenv("XDG_DATA_HOME", filepath.Join(tempHome, ".local", "share"))
containerName := "test-rm-genentry"
generateEntryFile := writeGenerateEntryDesktop(t, tempHome, containerName)
mock := &testutil.MockContainerManager{
ListContainersResult: []containermanager.Container{
{
Name: containerName,
Status: "Exited",
Labels: map[string]string{"manager": "distrobox"},
},
},
InspectContainerResult: &containermanager.InspectResult{
ContainerHome: tempHome,
},
}
cmd := newTestRmCommand(mock)
_, err := cmd.Execute(context.Background(), commands.RmOptions{
ContainerNames: []string{containerName},
Force: true,
NoTTY: true,
})
require.NoError(t, err)
assert.NoFileExists(t, generateEntryFile, "cleanup should invoke GenerateEntry delete to remove the <name>.desktop file")
}
+23 -8
View File
@@ -34,11 +34,17 @@ type ContainerManagerSpy struct {
// ExistsFn, when non-nil, overrides the default Exists behavior (which
// always returns false). Tests can use it to simulate name collisions
// or other lookup scenarios.
//
// ListContainersResult and InspectContainerResult, when non-nil, override
// the default zero-value return values of ListContainers and
// InspectContainer respectively.
type MockContainerManager struct {
Spy ContainerManagerSpy
Root bool
RootClone *MockContainerManager
ExistsFn func(containerName string) bool
Spy ContainerManagerSpy
Root bool
RootClone *MockContainerManager
ExistsFn func(containerName string) bool
ListContainersResult []containermanager.Container
InspectContainerResult *containermanager.InspectResult
}
func (m *MockContainerManager) Name() string {
@@ -49,11 +55,14 @@ func (m *MockContainerManager) Name() string {
func (m *MockContainerManager) CloneAsRoot() containermanager.ContainerManager {
m.Spy.CloneAsRoot = append(m.Spy.CloneAsRoot, []any{})
if m.RootClone == nil {
// Propagate behavior hooks (e.g. ExistsFn) to the clone so tests
// see consistent results between the rootless and root variants.
// Propagate override fields so tests that set them on the base
// mock see consistent behavior when code paths run on the root
// clone (real providers preserve fields across CloneAsRoot).
m.RootClone = &MockContainerManager{
Root: true,
ExistsFn: m.ExistsFn,
Root: true,
ExistsFn: m.ExistsFn,
ListContainersResult: m.ListContainersResult,
InspectContainerResult: m.InspectContainerResult,
}
}
return m.RootClone
@@ -66,6 +75,9 @@ func (m *MockContainerManager) Enter(_ context.Context, options containermanager
func (m *MockContainerManager) ListContainers(_ context.Context) ([]containermanager.Container, error) {
m.Spy.ListContainers = append(m.Spy.ListContainers, []any{})
if m.ListContainersResult != nil {
return m.ListContainersResult, nil
}
return []containermanager.Container{}, nil
}
@@ -94,6 +106,9 @@ func (m *MockContainerManager) Stop(_ context.Context, containerNames []string)
func (m *MockContainerManager) InspectContainer(_ context.Context, containerName string) (*containermanager.InspectResult, error) {
m.Spy.InspectContainer = append(m.Spy.InspectContainer, []any{containerName})
if m.InspectContainerResult != nil {
return m.InspectContainerResult, nil
}
return &containermanager.InspectResult{}, nil
}