diff --git a/internal/app/azldev/app.go b/internal/app/azldev/app.go index dce4d79..df9084e 100644 --- a/internal/app/azldev/app.go +++ b/internal/app/azldev/app.go @@ -248,7 +248,7 @@ func (a *App) Execute(args []string) int { // stdioLogger := a.initStdioLogging() - if err := setEventListener(stdioLogger, envOptions); err != nil { + if err := setEventListener(stdioLogger, a.quiet, envOptions); err != nil { slog.Error("Error setting event listener.", "err", err) return 1 @@ -357,7 +357,7 @@ func (a *App) reInitLoggingWithLogFile(envOptions *EnvOptions) error { return fmt.Errorf("error re-initializing file logging:\n%w", err) } - err = setEventListener(logger, envOptions) + err = setEventListener(logger, a.quiet, envOptions) if err != nil { return fmt.Errorf("error re-setting event listener:\n%w", err) } @@ -421,8 +421,8 @@ func (a *App) handlePostInitCallbacks(env *Env) error { return nil } -func setEventListener(stdioLogger *slog.Logger, envOptions *EnvOptions) error { - eventListener, err := NewEventListener(stdioLogger) +func setEventListener(stdioLogger *slog.Logger, quiet bool, envOptions *EnvOptions) error { + eventListener, err := NewEventListener(stdioLogger, quiet) if err != nil { return fmt.Errorf("error initializing event listener:\n%w", err) } diff --git a/internal/app/azldev/core/testutils/testenv.go b/internal/app/azldev/core/testutils/testenv.go index 35a996e..6cee8c6 100644 --- a/internal/app/azldev/core/testutils/testenv.go +++ b/internal/app/azldev/core/testutils/testenv.go @@ -97,7 +97,7 @@ func setUpEventListener(t *testing.T, testEnv *TestEnv) { testLogHandler := slogassert.New(t, slog.LevelDebug, nil) testEventLogger := slog.New(testLogHandler) - testEventListener, err := azldev.NewEventListener(testEventLogger) + testEventListener, err := azldev.NewEventListener(testEventLogger, false) require.NoError(t, err) testEnv.EventListener = testEventListener diff --git a/internal/app/azldev/event.go b/internal/app/azldev/event.go index 468394a..027e98d 100644 --- a/internal/app/azldev/event.go +++ b/internal/app/azldev/event.go @@ -18,6 +18,7 @@ type event struct { parentEventListener *appEventListener name string spinner *spinner.Spinner + quiet bool lastReportedCompletionRatio float64 @@ -49,6 +50,10 @@ func (e *event) End() { } func (e *event) SetLongRunning(longRunningText string) { + if e.quiet { + return + } + const percent = 100 // Start an indeterminate spinner to indicate to the user that *something* is happening. @@ -60,6 +65,10 @@ func (e *event) SetLongRunning(longRunningText string) { } func (e *event) SetProgress(unitsComplete int64, totalUnits int64) { + if e.quiet { + return + } + // For now, only update progress visually when the completion ratio has increased by at least 1% // since progress was last rendered. const minRatioIncreaseForUpdate = 0.01 diff --git a/internal/app/azldev/event_test.go b/internal/app/azldev/event_test.go new file mode 100644 index 0000000..0305882 --- /dev/null +++ b/internal/app/azldev/event_test.go @@ -0,0 +1,57 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +//nolint:testpackage // testing unexported internal types +package azldev + +import ( + "io" + "os" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func captureStderr(t *testing.T, action func()) string { + t.Helper() + + originalStderr := os.Stderr + reader, writer, err := os.Pipe() + require.NoError(t, err) + + t.Cleanup(func() { + os.Stderr = originalStderr + _ = writer.Close() + _ = reader.Close() + }) + + os.Stderr = writer + + action() + + require.NoError(t, writer.Close()) + + os.Stderr = originalStderr + + output, err := io.ReadAll(reader) + require.NoError(t, err) + + return string(output) +} + +func TestEvent_QuietModeSkipsLongRunningAndProgressRendering(t *testing.T) { + testEvent := &event{ + quiet: true, + } + + stderrOutput := captureStderr(t, func() { + testEvent.SetLongRunning("working") + testEvent.SetProgress(1, 10) + }) + + assert.Empty(t, stderrOutput) + assert.Nil(t, testEvent.spinner) + assert.False(t, testEvent.initializedProgressBar) + assert.Zero(t, testEvent.lastReportedCompletionRatio) +} diff --git a/internal/app/azldev/eventlistener.go b/internal/app/azldev/eventlistener.go index 831b822..4ca926b 100644 --- a/internal/app/azldev/eventlistener.go +++ b/internal/app/azldev/eventlistener.go @@ -16,13 +16,14 @@ import ( type appEventListener struct { eventLevel int eventLogger *slog.Logger + quiet bool } // Ensure [appEventListener] implements [opctx.EventListener]. var _ opctx.EventListener = &appEventListener{} // NewEventListener creates a new event listener for the environment. -func NewEventListener(eventLogger *slog.Logger) (*appEventListener, error) { +func NewEventListener(eventLogger *slog.Logger, quiet bool) (*appEventListener, error) { if eventLogger == nil { return nil, errors.New("event logger cannot be nil") } @@ -30,6 +31,7 @@ func NewEventListener(eventLogger *slog.Logger) (*appEventListener, error) { return &appEventListener{ eventLevel: 0, eventLogger: eventLogger, + quiet: quiet, }, nil } @@ -42,7 +44,10 @@ func (el *appEventListener) StartEvent(name string, args ...any) opctx.Event { prefix := strings.Repeat(" ", el.eventLevel*spacesPerLevel) - fmt.Fprintf(os.Stderr, "\r") + if !el.quiet { + fmt.Fprintf(os.Stderr, "\r") + } + el.eventLogger.Info(prefix+name, args...) } @@ -51,6 +56,7 @@ func (el *appEventListener) StartEvent(name string, args ...any) opctx.Event { return &event{ parentEventListener: el, name: name, + quiet: el.quiet, } }