Skip to content

Commit 193cc5d

Browse files
committed
test: cover batch install classification
1 parent 5b13328 commit 193cc5d

4 files changed

Lines changed: 167 additions & 21 deletions

File tree

internal/brew/brew.go

Lines changed: 37 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -198,6 +198,31 @@ type installResult struct {
198198
errMsg string
199199
}
200200

201+
var (
202+
getInstalledPackagesFn = GetInstalledPackages
203+
preInstallChecksFn = PreInstallChecks
204+
205+
runBrewInstallBatchFn = func(args ...string) (string, error) {
206+
cmd := brewInstallCmd(args...)
207+
output, err := cmd.CombinedOutput()
208+
return string(output), err
209+
}
210+
211+
runBrewInstallBatchWithTTYFn = func(args ...string) (string, error) {
212+
cmd := brewInstallCmd(args...)
213+
tty, opened := system.OpenTTY()
214+
if opened {
215+
cmd.Stdin = tty
216+
defer tty.Close()
217+
}
218+
output, err := cmd.CombinedOutput()
219+
return string(output), err
220+
}
221+
222+
installFormulaWithErrorFn = installFormulaWithError
223+
installSmartCaskWithErrorFn = installSmartCaskWithError
224+
)
225+
201226
func InstallWithProgress(cliPkgs, caskPkgs []string, dryRun bool) (installedFormulae []string, installedCasks []string, err error) {
202227
total := len(cliPkgs) + len(caskPkgs)
203228
if total == 0 {
@@ -215,7 +240,7 @@ func InstallWithProgress(cliPkgs, caskPkgs []string, dryRun bool) (installedForm
215240
return nil, nil, nil
216241
}
217242

218-
alreadyFormulae, alreadyCasks, checkErr := GetInstalledPackages()
243+
alreadyFormulae, alreadyCasks, checkErr := getInstalledPackagesFn()
219244
if checkErr != nil {
220245
return nil, nil, fmt.Errorf("list installed packages: %w", checkErr)
221246
}
@@ -248,7 +273,7 @@ func InstallWithProgress(cliPkgs, caskPkgs []string, dryRun bool) (installedForm
248273
return installedFormulae, installedCasks, nil
249274
}
250275

251-
if preErr := PreInstallChecks(len(newCli) + len(newCask)); preErr != nil {
276+
if preErr := preInstallChecksFn(len(newCli) + len(newCask)); preErr != nil {
252277
return installedFormulae, installedCasks, preErr
253278
}
254279

@@ -260,14 +285,14 @@ func InstallWithProgress(cliPkgs, caskPkgs []string, dryRun bool) (installedForm
260285

261286
if len(newCli) > 0 {
262287
progress.PrintLine(" Installing %d CLI packages via brew install...", len(newCli))
288+
progress.PauseForInteractive()
263289

264290
args := append([]string{"install"}, newCli...)
265-
cmd := brewInstallCmd(args...)
266-
cmdOutput, cmdErr := cmd.CombinedOutput()
267-
cmdOutputStr := string(cmdOutput)
291+
cmdOutputStr, cmdErr := runBrewInstallBatchFn(args...)
292+
progress.ResumeAfterInteractive()
268293

269294
// Re-check installed packages to determine actual success
270-
postFormulae, _, postErr := GetInstalledPackages()
295+
postFormulae, _, postErr := getInstalledPackagesFn()
271296
if postErr != nil {
272297
// Fallback: use command error to determine status
273298
for _, pkg := range newCli {
@@ -300,22 +325,14 @@ func InstallWithProgress(cliPkgs, caskPkgs []string, dryRun bool) (installedForm
300325

301326
if len(newCask) > 0 {
302327
progress.PrintLine(" Installing %d GUI apps via brew install --cask...", len(newCask))
328+
progress.PauseForInteractive()
303329

304330
args := append([]string{"install", "--cask"}, newCask...)
305-
cmd := brewInstallCmd(args...)
306-
// Open TTY for password prompts
307-
tty, opened := system.OpenTTY()
308-
if opened {
309-
cmd.Stdin = tty
310-
}
311-
cmdOutput, cmdErr := cmd.CombinedOutput()
312-
cmdOutputStr := string(cmdOutput)
313-
if opened {
314-
tty.Close()
315-
}
331+
cmdOutputStr, cmdErr := runBrewInstallBatchWithTTYFn(args...)
332+
progress.ResumeAfterInteractive()
316333

317334
// Re-check installed casks to determine actual success
318-
_, postCasks, postErr := GetInstalledPackages()
335+
_, postCasks, postErr := getInstalledPackagesFn()
319336
if postErr != nil {
320337
// Fallback: use command error to determine status
321338
for _, pkg := range newCask {
@@ -354,9 +371,9 @@ func InstallWithProgress(cliPkgs, caskPkgs []string, dryRun bool) (installedForm
354371
for _, f := range allFailed {
355372
var errMsg string
356373
if f.isCask {
357-
errMsg = installSmartCaskWithError(f.name)
374+
errMsg = installSmartCaskWithErrorFn(f.name)
358375
} else {
359-
errMsg = installFormulaWithError(f.name)
376+
errMsg = installFormulaWithErrorFn(f.name)
360377
}
361378
if errMsg == "" {
362379
fmt.Printf(" ✔ %s (retry succeeded)\n", f.name)

internal/brew/brew_test.go

Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -178,6 +178,133 @@ func TestInstallWithProgress_DryRunReturnsNoInstalledPackages(t *testing.T) {
178178
assert.Empty(t, casks, "dry-run should not report casks as installed")
179179
}
180180

181+
func TestInstallWithProgress_ClassifiesFromPostInstallLookup(t *testing.T) {
182+
oldGetInstalledPackages := getInstalledPackagesFn
183+
oldPreInstallChecks := preInstallChecksFn
184+
oldRunBrewInstallBatch := runBrewInstallBatchFn
185+
oldInstallFormulaWithError := installFormulaWithErrorFn
186+
oldInstallSmartCaskWithError := installSmartCaskWithErrorFn
187+
188+
t.Cleanup(func() {
189+
getInstalledPackagesFn = oldGetInstalledPackages
190+
preInstallChecksFn = oldPreInstallChecks
191+
runBrewInstallBatchFn = oldRunBrewInstallBatch
192+
installFormulaWithErrorFn = oldInstallFormulaWithError
193+
installSmartCaskWithErrorFn = oldInstallSmartCaskWithError
194+
})
195+
196+
calls := 0
197+
var retryCalls []string
198+
getInstalledPackagesFn = func() (map[string]bool, map[string]bool, error) {
199+
calls++
200+
switch calls {
201+
case 1:
202+
return map[string]bool{}, map[string]bool{}, nil
203+
case 2:
204+
return map[string]bool{"git": true}, map[string]bool{}, nil
205+
default:
206+
return nil, nil, assert.AnError
207+
}
208+
}
209+
preInstallChecksFn = func(int) error { return nil }
210+
runBrewInstallBatchFn = func(args ...string) (string, error) {
211+
assert.Equal(t, []string{"install", "git", "curl"}, args)
212+
return "", nil
213+
}
214+
installFormulaWithErrorFn = func(pkg string) string {
215+
retryCalls = append(retryCalls, pkg)
216+
return ""
217+
}
218+
installSmartCaskWithErrorFn = func(pkg string) string {
219+
t.Fatalf("unexpected cask retry for %s", pkg)
220+
return ""
221+
}
222+
223+
oldStdout := os.Stdout
224+
r, w, err := os.Pipe()
225+
require.NoError(t, err)
226+
os.Stdout = w
227+
228+
formulae, casks, runErr := InstallWithProgress([]string{"git", "curl"}, nil, false)
229+
230+
w.Close()
231+
os.Stdout = oldStdout
232+
233+
outputBytes, copyErr := io.ReadAll(r)
234+
require.NoError(t, copyErr)
235+
_ = outputBytes
236+
237+
assert.NoError(t, runErr)
238+
assert.ElementsMatch(t, []string{"git", "curl"}, formulae)
239+
assert.Empty(t, casks)
240+
assert.Equal(t, []string{"curl"}, retryCalls)
241+
assert.Equal(t, 2, calls)
242+
}
243+
244+
func TestInstallWithProgress_FallsBackWhenPostInstallLookupFails(t *testing.T) {
245+
oldGetInstalledPackages := getInstalledPackagesFn
246+
oldPreInstallChecks := preInstallChecksFn
247+
oldRunBrewInstallBatch := runBrewInstallBatchFn
248+
oldInstallFormulaWithError := installFormulaWithErrorFn
249+
oldInstallSmartCaskWithError := installSmartCaskWithErrorFn
250+
251+
t.Cleanup(func() {
252+
getInstalledPackagesFn = oldGetInstalledPackages
253+
preInstallChecksFn = oldPreInstallChecks
254+
runBrewInstallBatchFn = oldRunBrewInstallBatch
255+
installFormulaWithErrorFn = oldInstallFormulaWithError
256+
installSmartCaskWithErrorFn = oldInstallSmartCaskWithError
257+
})
258+
259+
calls := 0
260+
installFormulaRetries := 0
261+
getInstalledPackagesFn = func() (map[string]bool, map[string]bool, error) {
262+
calls++
263+
switch calls {
264+
case 1:
265+
return map[string]bool{}, map[string]bool{}, nil
266+
case 2:
267+
return nil, nil, assert.AnError
268+
default:
269+
return nil, nil, assert.AnError
270+
}
271+
}
272+
preInstallChecksFn = func(int) error { return nil }
273+
runBrewInstallBatchFn = func(args ...string) (string, error) {
274+
assert.Equal(t, []string{"install", "git", "curl"}, args)
275+
return "", nil
276+
}
277+
installFormulaWithErrorFn = func(pkg string) string {
278+
installFormulaRetries++
279+
t.Fatalf("unexpected retry for %s", pkg)
280+
return ""
281+
}
282+
installSmartCaskWithErrorFn = func(pkg string) string {
283+
t.Fatalf("unexpected cask retry for %s", pkg)
284+
return ""
285+
}
286+
287+
oldStdout := os.Stdout
288+
r, w, err := os.Pipe()
289+
require.NoError(t, err)
290+
os.Stdout = w
291+
292+
formulae, casks, runErr := InstallWithProgress([]string{"git", "curl"}, nil, false)
293+
294+
w.Close()
295+
os.Stdout = oldStdout
296+
297+
outputBytes, copyErr := io.ReadAll(r)
298+
require.NoError(t, copyErr)
299+
_ = outputBytes
300+
301+
assert.NoError(t, runErr)
302+
assert.ElementsMatch(t, []string{"git", "curl"}, formulae)
303+
assert.Empty(t, casks)
304+
assert.Equal(t, 0, installFormulaRetries)
305+
assert.Equal(t, 2, calls)
306+
}
307+
181308
func TestUpdate_DryRun(t *testing.T) {
182309
err := Update(true)
183310
assert.NoError(t, err)

internal/ui/progress.go

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -208,7 +208,6 @@ func (sp *StickyProgress) ResumeAfterInteractive() {
208208
sp.mu.Lock()
209209
defer sp.mu.Unlock()
210210
sp.active = true
211-
sp.render()
212211
}
213212

214213
func (sp *StickyProgress) Finish() {

internal/ui/progress_test.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -149,4 +149,7 @@ func TestStickyProgressPauseResume(t *testing.T) {
149149

150150
sp.PauseForInteractive()
151151
assert.False(t, sp.active)
152+
153+
sp.ResumeAfterInteractive()
154+
assert.True(t, sp.active)
152155
}

0 commit comments

Comments
 (0)