Skip to content

Commit cc4689b

Browse files
feat(cleaner): support tap diffing in DiffFromLists
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-opencode) Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
1 parent 3a4304e commit cc4689b

2 files changed

Lines changed: 196 additions & 11 deletions

File tree

internal/cleaner/cleaner.go

Lines changed: 35 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -14,41 +14,45 @@ type CleanResult struct {
1414
ExtraFormulae []string
1515
ExtraCasks []string
1616
ExtraNpm []string
17+
ExtraTaps []string
1718

1819
RemovedFormulae []string
1920
RemovedCasks []string
2021
RemovedNpm []string
22+
RemovedTaps []string
2123

2224
FailedFormulae []string
2325
FailedCasks []string
2426
FailedNpm []string
27+
FailedTaps []string
2528
}
2629

2730
func (r *CleanResult) TotalExtra() int {
28-
return len(r.ExtraFormulae) + len(r.ExtraCasks) + len(r.ExtraNpm)
31+
return len(r.ExtraFormulae) + len(r.ExtraCasks) + len(r.ExtraNpm) + len(r.ExtraTaps)
2932
}
3033

3134
func (r *CleanResult) TotalRemoved() int {
32-
return len(r.RemovedFormulae) + len(r.RemovedCasks) + len(r.RemovedNpm)
35+
return len(r.RemovedFormulae) + len(r.RemovedCasks) + len(r.RemovedNpm) + len(r.RemovedTaps)
3336
}
3437

3538
func (r *CleanResult) TotalFailed() int {
36-
return len(r.FailedFormulae) + len(r.FailedCasks) + len(r.FailedNpm)
39+
return len(r.FailedFormulae) + len(r.FailedCasks) + len(r.FailedNpm) + len(r.FailedTaps)
3740
}
3841

3942
func DiffFromSnapshot(snap *snapshot.Snapshot) (*CleanResult, error) {
40-
desiredFormulae := toSet(snap.Packages.Formulae)
41-
desiredCasks := toSet(snap.Packages.Casks)
42-
desiredNpm := toSet(snap.Packages.Npm)
43-
44-
return diff(desiredFormulae, desiredCasks, desiredNpm)
43+
return diff(
44+
toSet(snap.Packages.Formulae),
45+
toSet(snap.Packages.Casks),
46+
toSet(snap.Packages.Npm),
47+
toSet(snap.Packages.Taps),
48+
)
4549
}
4650

47-
func DiffFromLists(formulae, casks, npmPkgs []string) (*CleanResult, error) {
48-
return diff(toSet(formulae), toSet(casks), toSet(npmPkgs))
51+
func DiffFromLists(formulae, casks, npmPkgs, taps []string) (*CleanResult, error) {
52+
return diff(toSet(formulae), toSet(casks), toSet(npmPkgs), toSet(taps))
4953
}
5054

51-
func diff(desiredFormulae, desiredCasks, desiredNpm map[string]bool) (*CleanResult, error) {
55+
func diff(desiredFormulae, desiredCasks, desiredNpm, desiredTaps map[string]bool) (*CleanResult, error) {
5256
result := &CleanResult{}
5357

5458
installedFormulae, installedCasks, err := brew.GetInstalledPackages()
@@ -81,6 +85,19 @@ func diff(desiredFormulae, desiredCasks, desiredNpm map[string]bool) (*CleanResu
8185
}
8286
}
8387

88+
if len(desiredTaps) > 0 {
89+
installedTaps, err := brew.GetInstalledTaps()
90+
if err != nil {
91+
ui.Warn(fmt.Sprintf("Failed to check taps: %v", err))
92+
} else {
93+
for _, tap := range installedTaps {
94+
if !desiredTaps[tap] {
95+
result.ExtraTaps = append(result.ExtraTaps, tap)
96+
}
97+
}
98+
}
99+
}
100+
84101
return result, nil
85102
}
86103

@@ -117,6 +134,13 @@ func Execute(result *CleanResult, dryRun bool) error {
117134
removed: &result.RemovedNpm,
118135
failed: &result.FailedNpm,
119136
},
137+
{
138+
label: "Removing extra taps",
139+
pkgs: result.ExtraTaps,
140+
uninstallOne: func(tap string, dry bool) error { return brew.Untap([]string{tap}, dry) },
141+
removed: &result.RemovedTaps,
142+
failed: &result.FailedTaps,
143+
},
120144
}
121145

122146
var errs []error

internal/cleaner/cleaner_test.go

Lines changed: 161 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,24 @@ package cleaner
22

33
import (
44
"errors"
5+
"os"
6+
"path/filepath"
57
"testing"
68

9+
"github.com/openbootdotdev/openboot/internal/snapshot"
710
"github.com/stretchr/testify/assert"
811
"github.com/stretchr/testify/require"
912
)
1013

14+
func setupFakeBrew(t *testing.T, script string) {
15+
t.Helper()
16+
tmpDir := t.TempDir()
17+
brewPath := filepath.Join(tmpDir, "brew")
18+
require.NoError(t, os.WriteFile(brewPath, []byte(script), 0755))
19+
originalPath := os.Getenv("PATH")
20+
t.Setenv("PATH", tmpDir+string(os.PathListSeparator)+originalPath)
21+
}
22+
1123
func TestToSet(t *testing.T) {
1224
tests := []struct {
1325
name string
@@ -198,3 +210,152 @@ func TestExecute_DryRun_PassedThrough(t *testing.T) {
198210
assert.True(t, sawDryRun)
199211
assert.Equal(t, []string{"typescript", "eslint"}, result.RemovedNpm)
200212
}
213+
214+
func TestExecute_EmptyResult(t *testing.T) {
215+
result := &CleanResult{}
216+
err := Execute(result, false)
217+
assert.NoError(t, err)
218+
assert.Equal(t, 0, result.TotalRemoved())
219+
assert.Equal(t, 0, result.TotalFailed())
220+
}
221+
222+
func TestExecute_DryRun_Formulae(t *testing.T) {
223+
result := &CleanResult{
224+
ExtraFormulae: []string{"wget", "curl"},
225+
}
226+
err := Execute(result, true)
227+
assert.NoError(t, err)
228+
}
229+
230+
func TestExecute_WithFakeBrew_Success(t *testing.T) {
231+
setupFakeBrew(t, "#!/bin/sh\nexit 0\n")
232+
result := &CleanResult{
233+
ExtraFormulae: []string{"wget"},
234+
ExtraCasks: []string{"firefox"},
235+
}
236+
err := Execute(result, false)
237+
assert.NoError(t, err)
238+
assert.Contains(t, result.RemovedFormulae, "wget")
239+
assert.Contains(t, result.RemovedCasks, "firefox")
240+
assert.Empty(t, result.FailedFormulae)
241+
assert.Empty(t, result.FailedCasks)
242+
}
243+
244+
func TestExecute_WithFakeBrew_Failure(t *testing.T) {
245+
setupFakeBrew(t, "#!/bin/sh\necho 'Error: No such keg'\nexit 1\n")
246+
result := &CleanResult{
247+
ExtraFormulae: []string{"bad-pkg"},
248+
}
249+
err := Execute(result, false)
250+
assert.Error(t, err)
251+
assert.Contains(t, result.FailedFormulae, "bad-pkg")
252+
assert.Empty(t, result.RemovedFormulae)
253+
}
254+
255+
func TestDiffFromLists_ExtraPackages(t *testing.T) {
256+
setupFakeBrew(t, "#!/bin/sh\n"+
257+
"if [ \"$1\" = \"list\" ] && [ \"$2\" = \"--formula\" ]; then\n"+
258+
" echo git\n"+
259+
" echo wget\n"+
260+
" exit 0\n"+
261+
"fi\n"+
262+
"if [ \"$1\" = \"list\" ] && [ \"$2\" = \"--cask\" ]; then\n"+
263+
" echo firefox\n"+
264+
" echo slack\n"+
265+
" exit 0\n"+
266+
"fi\n"+
267+
"exit 0\n")
268+
269+
result, err := DiffFromLists([]string{"git"}, []string{"firefox"}, nil, nil)
270+
require.NoError(t, err)
271+
assert.Contains(t, result.ExtraFormulae, "wget")
272+
assert.NotContains(t, result.ExtraFormulae, "git")
273+
assert.Contains(t, result.ExtraCasks, "slack")
274+
assert.NotContains(t, result.ExtraCasks, "firefox")
275+
}
276+
277+
func TestDiffFromLists_NoExtras(t *testing.T) {
278+
setupFakeBrew(t, "#!/bin/sh\n"+
279+
"if [ \"$1\" = \"list\" ] && [ \"$2\" = \"--formula\" ]; then\n"+
280+
" echo git\n"+
281+
" exit 0\n"+
282+
"fi\n"+
283+
"if [ \"$1\" = \"list\" ] && [ \"$2\" = \"--cask\" ]; then\n"+
284+
" exit 0\n"+
285+
"fi\n"+
286+
"exit 0\n")
287+
288+
result, err := DiffFromLists([]string{"git"}, nil, nil, nil)
289+
require.NoError(t, err)
290+
assert.Empty(t, result.ExtraFormulae)
291+
assert.Empty(t, result.ExtraCasks)
292+
}
293+
294+
func TestDiffFromLists_WithExtraTaps(t *testing.T) {
295+
setupFakeBrew(t, "#!/bin/sh\n"+
296+
"if [ \"$1\" = \"list\" ] && [ \"$2\" = \"--formula\" ]; then\n"+
297+
" exit 0\n"+
298+
"fi\n"+
299+
"if [ \"$1\" = \"list\" ] && [ \"$2\" = \"--cask\" ]; then\n"+
300+
" exit 0\n"+
301+
"fi\n"+
302+
"if [ \"$1\" = \"tap\" ]; then\n"+
303+
" echo homebrew/cask-fonts\n"+
304+
" echo hashicorp/tap\n"+
305+
" exit 0\n"+
306+
"fi\n"+
307+
"exit 0\n")
308+
309+
result, err := DiffFromLists(nil, nil, nil, []string{"homebrew/cask-fonts"})
310+
require.NoError(t, err)
311+
assert.Contains(t, result.ExtraTaps, "hashicorp/tap", "tap not in desired list should be extra")
312+
assert.NotContains(t, result.ExtraTaps, "homebrew/cask-fonts", "desired tap should not be extra")
313+
}
314+
315+
func TestDiffFromLists_TapsPathSkippedWhenEmpty(t *testing.T) {
316+
setupFakeBrew(t, "#!/bin/sh\n"+
317+
"if [ \"$1\" = \"list\" ] && [ \"$2\" = \"--formula\" ]; then\n"+
318+
" exit 0\n"+
319+
"fi\n"+
320+
"if [ \"$1\" = \"list\" ] && [ \"$2\" = \"--cask\" ]; then\n"+
321+
" exit 0\n"+
322+
"fi\n"+
323+
"exit 0\n")
324+
325+
result, err := DiffFromLists(nil, nil, nil, nil)
326+
require.NoError(t, err)
327+
assert.Empty(t, result.ExtraTaps)
328+
}
329+
330+
func TestCleanResult_TotalExtra_WithTaps(t *testing.T) {
331+
r := &CleanResult{
332+
ExtraFormulae: []string{"a"},
333+
ExtraCasks: []string{"b"},
334+
ExtraNpm: []string{"c"},
335+
ExtraTaps: []string{"d", "e"},
336+
}
337+
assert.Equal(t, 5, r.TotalExtra())
338+
}
339+
340+
func TestDiffFromSnapshot_ExtraPackages(t *testing.T) {
341+
setupFakeBrew(t, "#!/bin/sh\n"+
342+
"if [ \"$1\" = \"list\" ] && [ \"$2\" = \"--formula\" ]; then\n"+
343+
" echo git\n"+
344+
" echo ripgrep\n"+
345+
" exit 0\n"+
346+
"fi\n"+
347+
"if [ \"$1\" = \"list\" ] && [ \"$2\" = \"--cask\" ]; then\n"+
348+
" exit 0\n"+
349+
"fi\n"+
350+
"exit 0\n")
351+
352+
snap := &snapshot.Snapshot{
353+
Packages: snapshot.PackageSnapshot{
354+
Formulae: []string{"git"},
355+
},
356+
}
357+
result, err := DiffFromSnapshot(snap)
358+
require.NoError(t, err)
359+
assert.Contains(t, result.ExtraFormulae, "ripgrep")
360+
assert.NotContains(t, result.ExtraFormulae, "git")
361+
}

0 commit comments

Comments
 (0)