From 1415ea2efa29cff5fc5f38f0edcbb09ca6019972 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Henrique=20Guard=C3=A3o=20Gandarez?= Date: Mon, 22 Jul 2024 20:56:33 -0300 Subject: [PATCH 1/3] Make single point of exit --- cmd/root.go | 19 +- cmd/run.go | 97 +++--- cmd/run_internal_test.go | 40 ++- cmd/run_test.go | 622 +++++++++++++++------------------------ pkg/exitcode/exitcode.go | 12 + 5 files changed, 343 insertions(+), 447 deletions(-) diff --git a/cmd/root.go b/cmd/root.go index 99eb9a42..bb683964 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -1,9 +1,12 @@ package cmd import ( + "errors" "fmt" + "os" "github.com/wakatime/wakatime-cli/pkg/api" + "github.com/wakatime/wakatime-cli/pkg/exitcode" "github.com/wakatime/wakatime-cli/pkg/offline" log "github.com/sirupsen/logrus" @@ -25,8 +28,20 @@ func NewRootCMD() *cobra.Command { cmd := &cobra.Command{ Use: "wakatime-cli", Short: "Command line interface used by all WakaTime text editor plugins.", - Run: func(cmd *cobra.Command, _ []string) { - Run(cmd, v) + RunE: func(cmd *cobra.Command, _ []string) error { + if err := RunE(cmd, v); err != nil { + var errexitcode exitcode.Err + + if errors.As(err, &errexitcode) { + os.Exit(errexitcode.Code) + } + + os.Exit(exitcode.ErrGeneric) + } + + os.Exit(exitcode.Success) + + return nil }, } diff --git a/cmd/run.go b/cmd/run.go index 29376d5b..eb15e6d3 100644 --- a/cmd/run.go +++ b/cmd/run.go @@ -45,8 +45,8 @@ type diagnostics struct { Stack string } -// Run executes commands parsed from a command line. -func Run(cmd *cobra.Command, v *viper.Viper) { +// RunE executes commands parsed from a command line. +func RunE(cmd *cobra.Command, v *viper.Viper) error { // force setup logging otherwise log goes to std out _, err := SetupLogging(v) if err != nil { @@ -58,9 +58,9 @@ func Run(cmd *cobra.Command, v *viper.Viper) { log.Errorf("failed to parse config files: %s", err) if v.IsSet("entity") { - saveHeartbeats(v) + _ = saveHeartbeats(v) - os.Exit(exitcode.ErrConfigFileParse) + return exitcode.Err{Code: exitcode.ErrConfigFileParse} } } @@ -75,14 +75,14 @@ func Run(cmd *cobra.Command, v *viper.Viper) { log.Fatalf("failed to register custom lexers: %s", err) } - shutdown := shutdownFn(func() {}) - // start profiling if enabled if logFileParams.Metrics { - shutdown, err = metrics.StartProfiling() + shutdown, err := metrics.StartProfiling() if err != nil { log.Errorf("failed to start profiling: %s", err) } + + defer shutdown() } if v.GetBool("user-agent") { @@ -90,75 +90,73 @@ func Run(cmd *cobra.Command, v *viper.Viper) { fmt.Println(heartbeat.UserAgent(vipertools.GetString(v, "plugin"))) - shutdown() - - os.Exit(exitcode.Success) + return nil } if v.GetBool("version") { log.Debugln("command: version") - RunCmd(v, logFileParams.Verbose, logFileParams.SendDiagsOnErrors, runVersion, shutdown) + return RunCmd(v, logFileParams.Verbose, logFileParams.SendDiagsOnErrors, runVersion) } if v.IsSet("config-read") { log.Debugln("command: config-read") - RunCmd(v, logFileParams.Verbose, logFileParams.SendDiagsOnErrors, configread.Run, shutdown) + return RunCmd(v, logFileParams.Verbose, logFileParams.SendDiagsOnErrors, configread.Run) } if v.IsSet("config-write") { log.Debugln("command: config-write") - RunCmd(v, logFileParams.Verbose, logFileParams.SendDiagsOnErrors, configwrite.Run, shutdown) + return RunCmd(v, logFileParams.Verbose, logFileParams.SendDiagsOnErrors, configwrite.Run) } if v.GetBool("today") { log.Debugln("command: today") - RunCmd(v, logFileParams.Verbose, logFileParams.SendDiagsOnErrors, today.Run, shutdown) + return RunCmd(v, logFileParams.Verbose, logFileParams.SendDiagsOnErrors, today.Run) } if v.IsSet("today-goal") { log.Debugln("command: today-goal") - RunCmd(v, logFileParams.Verbose, logFileParams.SendDiagsOnErrors, todaygoal.Run, shutdown) + return RunCmd(v, logFileParams.Verbose, logFileParams.SendDiagsOnErrors, todaygoal.Run) } if v.GetBool("file-experts") { log.Debugln("command: file-experts") - RunCmd(v, logFileParams.Verbose, logFileParams.SendDiagsOnErrors, fileexperts.Run, shutdown) + return RunCmd(v, logFileParams.Verbose, logFileParams.SendDiagsOnErrors, fileexperts.Run) } if v.IsSet("entity") { log.Debugln("command: heartbeat") if v.GetBool("offline-only") { - saveHeartbeats(v) - shutdown() - os.Exit(exitcode.Success) + exitCode := saveHeartbeats(v) + + os.Exit(exitCode) // nolint:gocritic } - RunCmdWithOfflineSync(v, logFileParams.Verbose, logFileParams.SendDiagsOnErrors, cmdheartbeat.Run, shutdown) + return RunCmdWithOfflineSync(v, logFileParams.Verbose, logFileParams.SendDiagsOnErrors, cmdheartbeat.Run) } if v.IsSet("sync-offline-activity") { log.Debugln("command: sync-offline-activity") - RunCmd(v, logFileParams.Verbose, logFileParams.SendDiagsOnErrors, offlinesync.Run, shutdown) + return RunCmd(v, logFileParams.Verbose, logFileParams.SendDiagsOnErrors, offlinesync.Run) } if v.GetBool("offline-count") { log.Debugln("command: offline-count") - RunCmd(v, logFileParams.Verbose, logFileParams.SendDiagsOnErrors, offlinecount.Run, shutdown) + return RunCmd(v, logFileParams.Verbose, logFileParams.SendDiagsOnErrors, offlinecount.Run) } if v.IsSet("print-offline-heartbeats") { log.Debugln("command: print-offline-heartbeats") - RunCmd(v, logFileParams.Verbose, logFileParams.SendDiagsOnErrors, offlineprint.Run, shutdown) + return RunCmd(v, logFileParams.Verbose, logFileParams.SendDiagsOnErrors, offlineprint.Run) } log.Warnf("one of the following parameters has to be provided: %s", strings.Join([]string{ @@ -177,7 +175,7 @@ func Run(cmd *cobra.Command, v *viper.Viper) { _ = cmd.Help() - os.Exit(exitcode.ErrGeneric) + return exitcode.Err{Code: exitcode.ErrGeneric} } func parseConfigFiles(v *viper.Viper) error { @@ -264,46 +262,31 @@ func SetupLogging(v *viper.Viper) (*logfile.Params, error) { return &logfileParams, nil } -type ( - // cmdFn represents a command function. - cmdFn func(v *viper.Viper) (int, error) - // shutdownFn represents a shutdown function. It will be called before exiting. - shutdownFn func() -) +// cmdFn represents a command function. +type cmdFn func(v *viper.Viper) (int, error) // RunCmd runs a command function and exits with the exit code returned by // the command function. Will send diagnostic on any errors or panics. -func RunCmd(v *viper.Viper, verbose bool, sendDiagsOnErrors bool, cmd cmdFn, shutdown shutdownFn) { - exitCode := runCmd(v, verbose, sendDiagsOnErrors, cmd) - - shutdown() - - os.Exit(exitCode) +func RunCmd(v *viper.Viper, verbose bool, sendDiagsOnErrors bool, cmd cmdFn) error { + return runCmd(v, verbose, sendDiagsOnErrors, cmd) } // RunCmdWithOfflineSync runs a command function and exits with the exit code // returned by the command function. If command run was successful, it will execute // offline sync command afterwards. Will send diagnostic on any errors or panics. -func RunCmdWithOfflineSync(v *viper.Viper, verbose bool, sendDiagsOnErrors bool, cmd cmdFn, shutdown shutdownFn) { - exitCode := runCmd(v, verbose, sendDiagsOnErrors, cmd) - if exitCode != exitcode.Success { - shutdown() - - os.Exit(exitCode) +func RunCmdWithOfflineSync(v *viper.Viper, verbose bool, sendDiagsOnErrors bool, cmd cmdFn) error { + if err := runCmd(v, verbose, sendDiagsOnErrors, cmd); err != nil { + return err } - exitCode = runCmd(v, verbose, sendDiagsOnErrors, offlinesync.Run) - - shutdown() - - os.Exit(exitCode) + return runCmd(v, verbose, sendDiagsOnErrors, offlinesync.Run) } // runCmd contains the main logic of RunCmd. // It will send diagnostic on any errors or panics. // On panic, it will send diagnostic and exit with ErrGeneric exit code. // On error, it will only send diagnostic if sendDiagsOnErrors and verbose is true. -func runCmd(v *viper.Viper, verbose bool, sendDiagsOnErrors bool, cmd cmdFn) (exitCode int) { +func runCmd(v *viper.Viper, verbose bool, sendDiagsOnErrors bool, cmd cmdFn) (errresponse error) { logs := bytes.NewBuffer(nil) resetLogs := captureLogs(logs) @@ -328,14 +311,14 @@ func runCmd(v *viper.Viper, verbose bool, sendDiagsOnErrors bool, cmd cmdFn) (ex log.Warnf("failed to send diagnostics: %s", err) } - exitCode = exitcode.ErrGeneric + errresponse = exitcode.Err{Code: exitcode.ErrGeneric} } }() var err error // run command - exitCode, err = cmd(v) + exitCode, err := cmd(v) // nolint:nestif if err != nil { if errwaka, ok := err.(wakaerror.Error); ok { @@ -362,10 +345,16 @@ func runCmd(v *viper.Viper, verbose bool, sendDiagsOnErrors bool, cmd cmdFn) (ex } } - return exitCode + if exitCode != exitcode.Success { + log.Debugf("command failed with exit code %d", exitCode) + + errresponse = exitcode.Err{Code: exitCode} + } + + return errresponse } -func saveHeartbeats(v *viper.Viper) { +func saveHeartbeats(v *viper.Viper) int { queueFilepath, err := offline.QueueFilepath() if err != nil { log.Warnf("failed to load offline queue filepath: %s", err) @@ -373,7 +362,11 @@ func saveHeartbeats(v *viper.Viper) { if err := cmdoffline.SaveHeartbeats(v, nil, queueFilepath); err != nil { log.Errorf("failed to save heartbeats to offline queue: %s", err) + + return exitcode.ErrGeneric } + + return exitcode.Success } func sendDiagnostics(v *viper.Viper, d diagnostics) error { diff --git a/cmd/run_internal_test.go b/cmd/run_internal_test.go index 78c19e28..425599cd 100644 --- a/cmd/run_internal_test.go +++ b/cmd/run_internal_test.go @@ -25,21 +25,25 @@ import ( func TestRunCmd(t *testing.T) { v := viper.New() - ret := runCmd(v, false, false, func(_ *viper.Viper) (int, error) { + err := runCmd(v, false, false, func(_ *viper.Viper) (int, error) { return exitcode.Success, nil }) - assert.Equal(t, exitcode.Success, ret) + assert.Nil(t, err) } func TestRunCmd_Err(t *testing.T) { v := viper.New() - ret := runCmd(v, false, false, func(_ *viper.Viper) (int, error) { + err := runCmd(v, false, false, func(_ *viper.Viper) (int, error) { return exitcode.ErrGeneric, errors.New("fail") }) - assert.Equal(t, exitcode.ErrGeneric, ret) + var errexitcode exitcode.Err + + require.ErrorAs(t, err, &errexitcode) + + assert.Equal(t, exitcode.ErrGeneric, err.(exitcode.Err).Code) } func TestRunCmd_ErrOfflineEnqueue(t *testing.T) { @@ -95,11 +99,15 @@ func TestRunCmd_ErrOfflineEnqueue(t *testing.T) { v.Set("key", "00000000-0000-4000-8000-000000000000") v.Set("plugin", "vim") - ret := runCmd(v, true, false, func(_ *viper.Viper) (int, error) { + err := runCmd(v, true, false, func(_ *viper.Viper) (int, error) { return exitcode.ErrGeneric, errors.New("fail") }) - assert.Equal(t, exitcode.ErrGeneric, ret) + var errexitcode exitcode.Err + + require.ErrorAs(t, err, &errexitcode) + + assert.Equal(t, exitcode.ErrGeneric, err.(exitcode.Err).Code) } func TestRunCmd_BackoffLoggedWithVerbose(t *testing.T) { @@ -147,10 +155,15 @@ func TestRunCmd_BackoffLoggedWithVerbose(t *testing.T) { v.Set("internal.backoff_retries", "1") v.Set("verbose", verbose) - SetupLogging(v) + _, _ = SetupLogging(v) - exitCode := runCmd(v, verbose, false, cmdheartbeat.Run) - assert.Equal(t, exitcode.ErrBackoff, exitCode) + err = runCmd(v, verbose, false, cmdheartbeat.Run) + + var errexitcode exitcode.Err + + require.ErrorAs(t, err, &errexitcode) + + assert.Equal(t, exitcode.ErrBackoff, err.(exitcode.Err).Code) assert.Equal(t, 0, numCalls) @@ -205,11 +218,14 @@ func TestRunCmd_BackoffNotLogged(t *testing.T) { v.Set("internal.backoff_retries", "1") v.Set("verbose", verbose) - SetupLogging(v) + _, _ = SetupLogging(v) + + err = runCmd(v, verbose, false, cmdheartbeat.Run) - exitCode := runCmd(v, verbose, false, cmdheartbeat.Run) - assert.Equal(t, exitcode.ErrBackoff, exitCode) + var errexitcode exitcode.Err + require.ErrorAs(t, err, &errexitcode) + assert.Equal(t, exitcode.ErrBackoff, err.(exitcode.Err).Code) assert.Equal(t, 0, numCalls) output, err := io.ReadAll(logFile) diff --git a/cmd/run_test.go b/cmd/run_test.go index 609de128..211c918a 100644 --- a/cmd/run_test.go +++ b/cmd/run_test.go @@ -8,11 +8,11 @@ import ( "net/http" "net/http/httptest" "os" - "os/exec" "testing" "time" "github.com/wakatime/wakatime-cli/cmd" + "github.com/wakatime/wakatime-cli/pkg/exitcode" "github.com/wakatime/wakatime-cli/pkg/offline" "github.com/wakatime/wakatime-cli/pkg/version" @@ -23,54 +23,6 @@ import ( ) func TestRunCmd_Err(t *testing.T) { - // this is exclusively run in subprocess - if os.Getenv("TEST_RUN") == "1" { - version.OS = "some os" - version.Arch = "some architecture" - version.Version = "some version" - - tmpDir := t.TempDir() - - offlineQueueFile, err := os.CreateTemp(tmpDir, "") - require.NoError(t, err) - - defer offlineQueueFile.Close() - - logFile, err := os.CreateTemp(tmpDir, "") - require.NoError(t, err) - - defer logFile.Close() - - v := viper.New() - v.Set("api-url", os.Getenv("TEST_SERVER_URL")) - v.Set("entity", "/path/to/file") - v.Set("key", "00000000-0000-4000-8000-000000000000") - v.Set("log-file", logFile.Name()) - v.Set("log-to-stdout", true) - v.Set("offline-queue-file", offlineQueueFile.Name()) - v.Set("plugin", "vim") - - var cmdNumCalls int - - cmdFn := func(_ *viper.Viper) (int, error) { - cmdNumCalls++ - return 42, errors.New("fail") - } - - var shutdownNumCalls int - - shutdownFn := func() { - shutdownNumCalls++ - } - - cmd.RunCmd(v, false, false, cmdFn, shutdownFn) - - assert.Equal(t, 1, cmdNumCalls) - assert.Equal(t, 1, shutdownNumCalls) - - return - } - testServerURL, router, tearDown := setupTestServer() defer tearDown() @@ -80,70 +32,50 @@ func TestRunCmd_Err(t *testing.T) { numCalls++ }) - // run command in another runner, to effectively test os.Exit() - cmd := exec.Command(os.Args[0], "-test.run=TestRunCmd_Err") // nolint:gosec - cmd.Env = append(os.Environ(), "TEST_RUN=1") - cmd.Env = append(cmd.Env, fmt.Sprintf("TEST_SERVER_URL=%s", testServerURL)) - - err := cmd.Run() - - e, ok := err.(*exec.ExitError) - require.True(t, ok) - - assert.Equal(t, 42, e.ExitCode()) - - assert.Eventually(t, func() bool { return numCalls == 0 }, time.Second, 50*time.Millisecond) -} - -func TestRunCmd_Verbose_Err(t *testing.T) { - // this is exclusively run in subprocess - if os.Getenv("TEST_RUN") == "1" { - version.OS = "some os" - version.Arch = "some architecture" - version.Version = "some version" - - tmpDir := t.TempDir() + version.OS = "some os" + version.Arch = "some architecture" + version.Version = "some version" - offlineQueueFile, err := os.CreateTemp(tmpDir, "") - require.NoError(t, err) + tmpDir := t.TempDir() - defer offlineQueueFile.Close() + offlineQueueFile, err := os.CreateTemp(tmpDir, "") + require.NoError(t, err) - logFile, err := os.CreateTemp(tmpDir, "") - require.NoError(t, err) + defer offlineQueueFile.Close() - defer logFile.Close() + logFile, err := os.CreateTemp(tmpDir, "") + require.NoError(t, err) - v := viper.New() - v.Set("api-url", os.Getenv("TEST_SERVER_URL")) - v.Set("entity", "/path/to/file") - v.Set("key", "00000000-0000-4000-8000-000000000000") - v.Set("log-file", logFile.Name()) - v.Set("log-to-stdout", true) - v.Set("offline-queue-file", offlineQueueFile.Name()) - v.Set("plugin", "vim") + defer logFile.Close() - var cmdNumCalls int + v := viper.New() + v.Set("api-url", testServerURL) + v.Set("entity", "/path/to/file") + v.Set("key", "00000000-0000-4000-8000-000000000000") + v.Set("log-file", logFile.Name()) + v.Set("log-to-stdout", true) + v.Set("offline-queue-file", offlineQueueFile.Name()) + v.Set("plugin", "vim") - cmdFn := func(_ *viper.Viper) (int, error) { - cmdNumCalls++ - return 42, errors.New("fail") - } + var cmdNumCalls int - var shutdownNumCalls int + cmdFn := func(_ *viper.Viper) (int, error) { + cmdNumCalls++ + return 42, errors.New("fail") + } - shutdownFn := func() { - shutdownNumCalls++ - } + err = cmd.RunCmd(v, false, false, cmdFn) + require.Error(t, err) - cmd.RunCmd(v, true, false, cmdFn, shutdownFn) + var errexitcode exitcode.Err - assert.Equal(t, 1, cmdNumCalls) - assert.Equal(t, 1, shutdownNumCalls) + assert.ErrorAs(t, err, &errexitcode) - return - } + assert.Equal(t, 1, cmdNumCalls) + assert.Eventually(t, func() bool { return numCalls == 0 }, time.Second, 50*time.Millisecond) +} +func TestRunCmd_Verbose_Err(t *testing.T) { testServerURL, router, tearDown := setupTestServer() defer tearDown() @@ -153,70 +85,50 @@ func TestRunCmd_Verbose_Err(t *testing.T) { numCalls++ }) - // run command in another runner, to effectively test os.Exit() - cmd := exec.Command(os.Args[0], "-test.run=TestRunCmd_Verbose_Err") // nolint:gosec - cmd.Env = append(os.Environ(), "TEST_RUN=1") - cmd.Env = append(cmd.Env, fmt.Sprintf("TEST_SERVER_URL=%s", testServerURL)) - - err := cmd.Run() - - e, ok := err.(*exec.ExitError) - require.True(t, ok) - - assert.Equal(t, 42, e.ExitCode()) - - assert.Eventually(t, func() bool { return numCalls == 0 }, time.Second, 50*time.Millisecond) -} - -func TestRunCmd_SendDiagnostics_Err(t *testing.T) { - // this is exclusively run in subprocess - if os.Getenv("TEST_RUN") == "1" { - version.OS = "some os" - version.Arch = "some architecture" - version.Version = "some version" - - tmpDir := t.TempDir() + version.OS = "some os" + version.Arch = "some architecture" + version.Version = "some version" - offlineQueueFile, err := os.CreateTemp(tmpDir, "") - require.NoError(t, err) + tmpDir := t.TempDir() - defer offlineQueueFile.Close() + offlineQueueFile, err := os.CreateTemp(tmpDir, "") + require.NoError(t, err) - logFile, err := os.CreateTemp(tmpDir, "") - require.NoError(t, err) + defer offlineQueueFile.Close() - defer logFile.Close() + logFile, err := os.CreateTemp(tmpDir, "") + require.NoError(t, err) - v := viper.New() - v.Set("api-url", os.Getenv("TEST_SERVER_URL")) - v.Set("entity", "/path/to/file") - v.Set("key", "00000000-0000-4000-8000-000000000000") - v.Set("log-file", logFile.Name()) - v.Set("log-to-stdout", true) - v.Set("offline-queue-file", offlineQueueFile.Name()) - v.Set("plugin", "vim") + defer logFile.Close() - var cmdNumCalls int + v := viper.New() + v.Set("api-url", testServerURL) + v.Set("entity", "/path/to/file") + v.Set("key", "00000000-0000-4000-8000-000000000000") + v.Set("log-file", logFile.Name()) + v.Set("log-to-stdout", true) + v.Set("offline-queue-file", offlineQueueFile.Name()) + v.Set("plugin", "vim") - cmdFn := func(_ *viper.Viper) (int, error) { - cmdNumCalls++ - return 42, errors.New("fail") - } + var cmdNumCalls int - var shutdownNumCalls int + cmdFn := func(_ *viper.Viper) (int, error) { + cmdNumCalls++ + return 42, errors.New("fail") + } - shutdownFn := func() { - shutdownNumCalls++ - } + err = cmd.RunCmd(v, true, false, cmdFn) - cmd.RunCmd(v, true, true, cmdFn, shutdownFn) + var errexitcode exitcode.Err - assert.Equal(t, 1, cmdNumCalls) - assert.Equal(t, 1, shutdownNumCalls) + require.ErrorAs(t, err, &errexitcode) - return - } + assert.Equal(t, 42, err.(exitcode.Err).Code) + assert.Equal(t, 1, cmdNumCalls) + assert.Eventually(t, func() bool { return numCalls == 0 }, time.Second, 50*time.Millisecond) +} +func TestRunCmd_SendDiagnostics_Err(t *testing.T) { testServerURL, router, tearDown := setupTestServer() defer tearDown() @@ -262,71 +174,50 @@ func TestRunCmd_SendDiagnostics_Err(t *testing.T) { w.WriteHeader(http.StatusCreated) }) - // run command in another runner, to effectively test os.Exit() - cmd := exec.Command(os.Args[0], "-test.run=TestRunCmd_SendDiagnostics_Err") // nolint:gosec - cmd.Env = append(os.Environ(), "TEST_RUN=1") - cmd.Env = append(cmd.Env, fmt.Sprintf("TEST_SERVER_URL=%s", testServerURL)) - - err := cmd.Run() - - e, ok := err.(*exec.ExitError) - require.True(t, ok) + version.OS = "some os" + version.Arch = "some architecture" + version.Version = "some version" - assert.Equal(t, 42, e.ExitCode()) + tmpDir := t.TempDir() - assert.Eventually(t, func() bool { return numCalls == 1 }, time.Second, 50*time.Millisecond) -} - -func TestRunCmd_SendDiagnostics_Panic(t *testing.T) { - // this is exclusively run in subprocess - if os.Getenv("TEST_RUN") == "1" { - version.OS = "some os" - version.Arch = "some architecture" - version.Version = "some version" - - tmpDir := t.TempDir() - - offlineQueueFile, err := os.CreateTemp(tmpDir, "") - require.NoError(t, err) - - defer offlineQueueFile.Close() - - logFile, err := os.CreateTemp(tmpDir, "") - require.NoError(t, err) + offlineQueueFile, err := os.CreateTemp(tmpDir, "") + require.NoError(t, err) - defer logFile.Close() + defer offlineQueueFile.Close() - v := viper.New() - v.Set("api-url", os.Getenv("TEST_SERVER_URL")) - v.Set("entity", "/path/to/file") - v.Set("key", "00000000-0000-4000-8000-000000000000") - v.Set("log-file", logFile.Name()) - v.Set("log-to-stdout", true) - v.Set("offline-queue-file", offlineQueueFile.Name()) - v.Set("plugin", "vim") + logFile, err := os.CreateTemp(tmpDir, "") + require.NoError(t, err) - var cmdNumCalls int + defer logFile.Close() - cmdFn := func(_ *viper.Viper) (int, error) { - cmdNumCalls++ + v := viper.New() + v.Set("api-url", testServerURL) + v.Set("entity", "/path/to/file") + v.Set("key", "00000000-0000-4000-8000-000000000000") + v.Set("log-file", logFile.Name()) + v.Set("log-to-stdout", true) + v.Set("offline-queue-file", offlineQueueFile.Name()) + v.Set("plugin", "vim") - panic("fail") - } + var cmdNumCalls int - var shutdownNumCalls int + cmdFn := func(_ *viper.Viper) (int, error) { + cmdNumCalls++ + return 42, errors.New("fail") + } - shutdownFn := func() { - shutdownNumCalls++ - } + err = cmd.RunCmd(v, true, true, cmdFn) - cmd.RunCmd(v, true, false, cmdFn, shutdownFn) + var errexitcode exitcode.Err - assert.Equal(t, 1, cmdNumCalls) - assert.Equal(t, 1, shutdownNumCalls) + require.ErrorAs(t, err, &errexitcode) - return - } + assert.Equal(t, 42, err.(exitcode.Err).Code) + assert.Equal(t, 1, cmdNumCalls) + assert.Eventually(t, func() bool { return numCalls == 1 }, time.Second, 50*time.Millisecond) +} +func TestRunCmd_SendDiagnostics_Panic(t *testing.T) { testServerURL, router, tearDown := setupTestServer() defer tearDown() @@ -373,71 +264,51 @@ func TestRunCmd_SendDiagnostics_Panic(t *testing.T) { w.WriteHeader(http.StatusCreated) }) - // run command in another runner, to effectively test os.Exit() - cmd := exec.Command(os.Args[0], "-test.run=TestRunCmd_SendDiagnostics_Panic") // nolint:gosec - cmd.Env = append(os.Environ(), "TEST_RUN=1") - cmd.Env = append(cmd.Env, fmt.Sprintf("TEST_SERVER_URL=%s", testServerURL)) - - err := cmd.Run() - - e, ok := err.(*exec.ExitError) - require.True(t, ok) + version.OS = "some os" + version.Arch = "some architecture" + version.Version = "some version" - assert.Equal(t, 1, e.ExitCode()) - - assert.Eventually(t, func() bool { return numCalls == 1 }, time.Second, 50*time.Millisecond) -} + tmpDir := t.TempDir() -func TestRunCmd_SendDiagnostics_NoLogs_Panic(t *testing.T) { - // this is exclusively run in subprocess - if os.Getenv("TEST_RUN") == "1" { - version.OS = "some os" - version.Arch = "some architecture" - version.Version = "some version" - - tmpDir := t.TempDir() - - offlineQueueFile, err := os.CreateTemp(tmpDir, "") - require.NoError(t, err) - - defer offlineQueueFile.Close() + offlineQueueFile, err := os.CreateTemp(tmpDir, "") + require.NoError(t, err) - logFile, err := os.CreateTemp(tmpDir, "") - require.NoError(t, err) + defer offlineQueueFile.Close() - defer logFile.Close() + logFile, err := os.CreateTemp(tmpDir, "") + require.NoError(t, err) - v := viper.New() - v.Set("api-url", os.Getenv("TEST_SERVER_URL")) - v.Set("entity", "/path/to/file") - v.Set("key", "00000000-0000-4000-8000-000000000000") - v.Set("log-file", logFile.Name()) - v.Set("log-to-stdout", true) - v.Set("offline-queue-file", offlineQueueFile.Name()) - v.Set("plugin", "vim") + defer logFile.Close() - var cmdNumCalls int + v := viper.New() + v.Set("api-url", testServerURL) + v.Set("entity", "/path/to/file") + v.Set("key", "00000000-0000-4000-8000-000000000000") + v.Set("log-file", logFile.Name()) + v.Set("log-to-stdout", true) + v.Set("offline-queue-file", offlineQueueFile.Name()) + v.Set("plugin", "vim") - cmdFn := func(_ *viper.Viper) (int, error) { - cmdNumCalls++ + var cmdNumCalls int - panic("fail") - } + cmdFn := func(_ *viper.Viper) (int, error) { + cmdNumCalls++ - var shutdownNumCalls int + panic("fail") + } - shutdownFn := func() { - shutdownNumCalls++ - } + err = cmd.RunCmd(v, true, false, cmdFn) - cmd.RunCmd(v, false, false, cmdFn, shutdownFn) + var errexitcode exitcode.Err - assert.Equal(t, 1, cmdNumCalls) - assert.Equal(t, 1, shutdownNumCalls) + require.ErrorAs(t, err, &errexitcode) - return - } + assert.Equal(t, exitcode.ErrGeneric, err.(exitcode.Err).Code) + assert.Equal(t, 1, cmdNumCalls) + assert.Eventually(t, func() bool { return numCalls == 1 }, time.Second, 50*time.Millisecond) +} +func TestRunCmd_SendDiagnostics_NoLogs_Panic(t *testing.T) { testServerURL, router, tearDown := setupTestServer() defer tearDown() @@ -482,70 +353,51 @@ func TestRunCmd_SendDiagnostics_NoLogs_Panic(t *testing.T) { w.WriteHeader(http.StatusCreated) }) - // run command in another runner, to effectively test os.Exit() - cmd := exec.Command(os.Args[0], "-test.run=TestRunCmd_SendDiagnostics_NoLogs_Panic") // nolint:gosec - cmd.Env = append(os.Environ(), "TEST_RUN=1") - cmd.Env = append(cmd.Env, fmt.Sprintf("TEST_SERVER_URL=%s", testServerURL)) + version.OS = "some os" + version.Arch = "some architecture" + version.Version = "some version" - err := cmd.Run() + tmpDir := t.TempDir() - e, ok := err.(*exec.ExitError) - require.True(t, ok) - - assert.Equal(t, 1, e.ExitCode()) - - assert.Eventually(t, func() bool { return numCalls == 1 }, time.Second, 50*time.Millisecond) -} - -func TestRunCmd_SendDiagnostics_WakaError(t *testing.T) { - // this is exclusively run in subprocess - if os.Getenv("TEST_RUN") == "1" { - version.OS = "some os" - version.Arch = "some architecture" - version.Version = "some version" - - tmpDir := t.TempDir() - - offlineQueueFile, err := os.CreateTemp(tmpDir, "") - require.NoError(t, err) + offlineQueueFile, err := os.CreateTemp(tmpDir, "") + require.NoError(t, err) - defer offlineQueueFile.Close() + defer offlineQueueFile.Close() - logFile, err := os.CreateTemp(tmpDir, "") - require.NoError(t, err) + logFile, err := os.CreateTemp(tmpDir, "") + require.NoError(t, err) - defer logFile.Close() + defer logFile.Close() - v := viper.New() - v.Set("api-url", os.Getenv("TEST_SERVER_URL")) - v.Set("entity", "/path/to/file") - v.Set("key", "00000000-0000-4000-8000-000000000000") - v.Set("log-file", logFile.Name()) - v.Set("log-to-stdout", true) - v.Set("offline-queue-file", offlineQueueFile.Name()) - v.Set("plugin", "vim") + v := viper.New() + v.Set("api-url", testServerURL) + v.Set("entity", "/path/to/file") + v.Set("key", "00000000-0000-4000-8000-000000000000") + v.Set("log-file", logFile.Name()) + v.Set("log-to-stdout", true) + v.Set("offline-queue-file", offlineQueueFile.Name()) + v.Set("plugin", "vim") - var cmdNumCalls int + var cmdNumCalls int - cmdFn := func(_ *viper.Viper) (int, error) { - cmdNumCalls++ - return 42, offline.ErrOpenDB{Err: errors.New("fail")} - } + cmdFn := func(_ *viper.Viper) (int, error) { + cmdNumCalls++ - var shutdownNumCalls int + panic("fail") + } - shutdownFn := func() { - shutdownNumCalls++ - } + err = cmd.RunCmd(v, false, false, cmdFn) - cmd.RunCmd(v, false, false, cmdFn, shutdownFn) + var errexitcode exitcode.Err - assert.Equal(t, 1, cmdNumCalls) - assert.Equal(t, 1, shutdownNumCalls) + require.ErrorAs(t, err, &errexitcode) - return - } + assert.Equal(t, exitcode.ErrGeneric, err.(exitcode.Err).Code) + assert.Equal(t, 1, cmdNumCalls) + assert.Eventually(t, func() bool { return numCalls == 1 }, time.Second, 50*time.Millisecond) +} +func TestRunCmd_SendDiagnostics_WakaError(t *testing.T) { testServerURL, router, tearDown := setupTestServer() defer tearDown() @@ -591,63 +443,90 @@ func TestRunCmd_SendDiagnostics_WakaError(t *testing.T) { w.WriteHeader(http.StatusCreated) }) - // run command in another runner, to effectively test os.Exit() - cmd := exec.Command(os.Args[0], "-test.run=TestRunCmd_SendDiagnostics_WakaError") // nolint:gosec - cmd.Env = append(os.Environ(), "TEST_RUN=1") - cmd.Env = append(cmd.Env, fmt.Sprintf("TEST_SERVER_URL=%s", testServerURL)) + version.OS = "some os" + version.Arch = "some architecture" + version.Version = "some version" + + tmpDir := t.TempDir() - err := cmd.Run() + offlineQueueFile, err := os.CreateTemp(tmpDir, "") + require.NoError(t, err) + + defer offlineQueueFile.Close() - e, ok := err.(*exec.ExitError) - require.True(t, ok) + logFile, err := os.CreateTemp(tmpDir, "") + require.NoError(t, err) - assert.Equal(t, 42, e.ExitCode()) + defer logFile.Close() + v := viper.New() + v.Set("api-url", testServerURL) + v.Set("entity", "/path/to/file") + v.Set("key", "00000000-0000-4000-8000-000000000000") + v.Set("log-file", logFile.Name()) + v.Set("log-to-stdout", true) + v.Set("offline-queue-file", offlineQueueFile.Name()) + v.Set("plugin", "vim") + + var cmdNumCalls int + + cmdFn := func(_ *viper.Viper) (int, error) { + cmdNumCalls++ + return 42, offline.ErrOpenDB{Err: errors.New("fail")} + } + + err = cmd.RunCmd(v, false, false, cmdFn) + + var errexitcode exitcode.Err + + require.ErrorAs(t, err, &errexitcode) + + assert.Equal(t, 42, err.(exitcode.Err).Code) + assert.Equal(t, 1, cmdNumCalls) assert.Eventually(t, func() bool { return numCalls == 1 }, time.Second, 50*time.Millisecond) } func TestRunCmdWithOfflineSync(t *testing.T) { - // this is exclusively run in subprocess - if os.Getenv("TEST_RUN") == "1" { - version.OS = "some os" - version.Arch = "some architecture" - version.Version = "some version" + // setup test server + testServerURL, router, tearDown := setupTestServer() + defer tearDown() - logFile, err := os.CreateTemp(t.TempDir(), "") - require.NoError(t, err) + var numCalls int - defer logFile.Close() + router.HandleFunc("/users/current/heartbeats.bulk", func(w http.ResponseWriter, req *http.Request) { + numCalls++ - v := viper.New() - v.Set("api-url", os.Getenv("TEST_SERVER_URL")) - v.Set("entity", "/path/to/file") - v.Set("key", "00000000-0000-4000-8000-000000000000") - v.Set("log-file", logFile.Name()) - v.Set("log-to-stdout", true) - v.Set("offline-queue-file", os.Getenv("OFFLINE_QUEUE_FILE")) - v.SetDefault("sync-offline-activity", 24) - v.Set("plugin", "vim") + // check headers + assert.Equal(t, http.MethodPost, req.Method) + assert.Equal(t, []string{"application/json"}, req.Header["Accept"]) + assert.Equal(t, []string{"application/json"}, req.Header["Content-Type"]) - var cmdNumCalls int + // check body + expectedBody, err := os.ReadFile("testdata/api_heartbeats_request.json") + require.NoError(t, err) - cmdFn := func(_ *viper.Viper) (int, error) { - cmdNumCalls++ - return 0, nil - } + body, err := io.ReadAll(req.Body) + require.NoError(t, err) - var shutdownNumCalls int + assert.JSONEq(t, string(expectedBody), string(body)) - shutdownFn := func() { - shutdownNumCalls++ - } + // send response + f, err := os.Open("testdata/api_heartbeats_response.json") + require.NoError(t, err) - cmd.RunCmdWithOfflineSync(v, false, false, cmdFn, shutdownFn) + w.WriteHeader(http.StatusCreated) + _, err = io.Copy(w, f) + require.NoError(t, err) + }) - assert.Equal(t, 1, cmdNumCalls) - assert.Equal(t, 1, shutdownNumCalls) + version.OS = "some os" + version.Arch = "some architecture" + version.Version = "some version" - return - } + logFile, err := os.CreateTemp(t.TempDir(), "") + require.NoError(t, err) + + defer logFile.Close() // setup test queue offlineQueueFile, err := os.CreateTemp(t.TempDir(), "") @@ -678,47 +557,28 @@ func TestRunCmdWithOfflineSync(t *testing.T) { err = db.Close() require.NoError(t, err) - // setup test server - testServerURL, router, tearDown := setupTestServer() - defer tearDown() - - var numCalls int - - router.HandleFunc("/users/current/heartbeats.bulk", func(w http.ResponseWriter, req *http.Request) { - numCalls++ - - // check headers - assert.Equal(t, http.MethodPost, req.Method) - assert.Equal(t, []string{"application/json"}, req.Header["Accept"]) - assert.Equal(t, []string{"application/json"}, req.Header["Content-Type"]) - - // check body - expectedBody, err := os.ReadFile("testdata/api_heartbeats_request.json") - require.NoError(t, err) - - body, err := io.ReadAll(req.Body) - require.NoError(t, err) - - assert.JSONEq(t, string(expectedBody), string(body)) - - // send response - f, err := os.Open("testdata/api_heartbeats_response.json") - require.NoError(t, err) - - w.WriteHeader(http.StatusCreated) - _, err = io.Copy(w, f) - require.NoError(t, err) - }) - - // run command in another runner, to effectively test os.Exit() - cmd := exec.Command(os.Args[0], "-test.run=TestRunCmdWithOfflineSync") // nolint:gosec - cmd.Env = append(os.Environ(), "TEST_RUN=1") - cmd.Env = append(cmd.Env, fmt.Sprintf("TEST_SERVER_URL=%s", testServerURL)) - cmd.Env = append(cmd.Env, fmt.Sprintf("OFFLINE_QUEUE_FILE=%s", offlineQueueFile.Name())) + v := viper.New() + v.Set("api-url", testServerURL) + v.Set("entity", "/path/to/file") + v.Set("key", "00000000-0000-4000-8000-000000000000") + v.Set("log-file", logFile.Name()) + v.Set("log-to-stdout", true) + v.Set("offline-queue-file", offlineQueueFile.Name()) + v.SetDefault("sync-offline-activity", 24) + v.Set("plugin", "vim") + + var cmdNumCalls int + + cmdFn := func(_ *viper.Viper) (int, error) { + cmdNumCalls++ + return exitcode.Success, nil + } - err = cmd.Run() + err = cmd.RunCmdWithOfflineSync(v, false, false, cmdFn) require.NoError(t, err) + assert.Equal(t, 1, cmdNumCalls) + // check db db, err = bolt.Open(offlineQueueFile.Name(), 0600, nil) require.NoError(t, err) diff --git a/pkg/exitcode/exitcode.go b/pkg/exitcode/exitcode.go index fc2f42ef..1110dccc 100644 --- a/pkg/exitcode/exitcode.go +++ b/pkg/exitcode/exitcode.go @@ -1,5 +1,7 @@ package exitcode +import "strconv" + const ( // Success is used when a heartbeat was sent successfully. Success = 0 @@ -18,3 +20,13 @@ const ( // ErrBackoff is used when sending heartbeats postponed because we're currently rate limited. ErrBackoff = 112 ) + +// Err represents a type response for exit code errors. A Success response is also wrapped in this type. +type Err struct { + Code int +} + +// Error method to implement error interface. +func (e Err) Error() string { + return strconv.Itoa(e.Code) +} From b1f073ad9934a945a4b9c20b70a942720c2a5638 Mon Sep 17 00:00:00 2001 From: Alan Hamlett Date: Tue, 23 Jul 2024 11:16:55 +0200 Subject: [PATCH 2/3] Remove --offline-only flag --- cmd/heartbeat/heartbeat.go | 2 -- cmd/offline/offline.go | 4 +-- cmd/params/params.go | 5 +--- cmd/params/params_test.go | 2 +- cmd/root.go | 1 - cmd/run.go | 6 ---- main_test.go | 56 -------------------------------------- 7 files changed, 4 insertions(+), 72 deletions(-) diff --git a/cmd/heartbeat/heartbeat.go b/cmd/heartbeat/heartbeat.go index 6bde717d..ef8847b7 100644 --- a/cmd/heartbeat/heartbeat.go +++ b/cmd/heartbeat/heartbeat.go @@ -105,8 +105,6 @@ func SendHeartbeats(v *viper.Viper, queueFilepath string) error { } handleOpts = append(handleOpts, offline.WithQueue(queueFilepath)) - } else if params.Offline.OfflineOnly { - return errors.New("--offline-only can NOT be used with --disable-offline") } handleOpts = append(handleOpts, backoff.WithBackoff(backoff.Config{ diff --git a/cmd/offline/offline.go b/cmd/offline/offline.go index 8de134ee..d84ec422 100644 --- a/cmd/offline/offline.go +++ b/cmd/offline/offline.go @@ -19,8 +19,8 @@ import ( ) // SaveHeartbeats saves heartbeats to the offline db without trying to send to the API. -// Used when we have more heartbeats than `offline.SendLimit`, when --offline-only enabled, -// when we couldn't send heartbeats to the API, or the API returned an auth error. +// Used when we have more heartbeats than `offline.SendLimit`, when we couldn't send +// heartbeats to the API, or the API returned an auth error. func SaveHeartbeats(v *viper.Viper, heartbeats []heartbeat.Heartbeat, queueFilepath string) error { params, err := loadParams(v) if err != nil { diff --git a/cmd/params/params.go b/cmd/params/params.go index f01dde0f..eea875f8 100644 --- a/cmd/params/params.go +++ b/cmd/params/params.go @@ -131,7 +131,6 @@ type ( // Offline contains offline related parameters. Offline struct { Disabled bool - OfflineOnly bool PrintMax int QueueFile string QueueFileLegacy string @@ -657,7 +656,6 @@ func LoadOfflineParams(v *viper.Viper) Offline { return Offline{ Disabled: disabled, - OfflineOnly: v.GetBool("offline-only"), PrintMax: v.GetInt("print-offline-heartbeats"), QueueFile: vipertools.GetString(v, "offline-queue-file"), QueueFileLegacy: vipertools.GetString(v, "offline-queue-file-legacy"), @@ -1041,9 +1039,8 @@ func (p Heartbeat) String() string { // String implements fmt.Stringer interface. func (p Offline) String() string { return fmt.Sprintf( - "disabled: %t, offline only: %t, print max: %d, queue file: '%s', queue file legacy: '%s', num sync max: %d", + "disabled: %t, print max: %d, queue file: '%s', queue file legacy: '%s', num sync max: %d", p.Disabled, - p.OfflineOnly, p.PrintMax, p.QueueFile, p.QueueFileLegacy, diff --git a/cmd/params/params_test.go b/cmd/params/params_test.go index 55b07080..a7286bc4 100644 --- a/cmd/params/params_test.go +++ b/cmd/params/params_test.go @@ -2505,7 +2505,7 @@ func TestOffline_String(t *testing.T) { assert.Equal( t, - "disabled: true, offline only: false, print max: 6, queue file: '/path/to/queue.file',"+ + "disabled: true, print max: 6, queue file: '/path/to/queue.file',"+ " queue file legacy: '/path/to/legacy.file', num sync max: 12", offline.String(), ) diff --git a/cmd/root.go b/cmd/root.go index bb683964..c0dc86f3 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -245,7 +245,6 @@ func setFlags(cmd *cobra.Command, v *viper.Viper) { " new heartbeats.", offline.SyncMaxDefault), ) flags.Bool("offline-count", false, "Prints the number of heartbeats in the offline db, then exits.") - flags.Bool("offline-only", false, "Saves the heartbeat(s) to the offline db, then exits.") flags.Int( "timeout", api.DefaultTimeoutSecs, diff --git a/cmd/run.go b/cmd/run.go index eb15e6d3..25811c73 100644 --- a/cmd/run.go +++ b/cmd/run.go @@ -132,12 +132,6 @@ func RunE(cmd *cobra.Command, v *viper.Viper) error { if v.IsSet("entity") { log.Debugln("command: heartbeat") - if v.GetBool("offline-only") { - exitCode := saveHeartbeats(v) - - os.Exit(exitCode) // nolint:gocritic - } - return RunCmdWithOfflineSync(v, logFileParams.Verbose, logFileParams.SendDiagsOnErrors, cmdheartbeat.Run) } diff --git a/main_test.go b/main_test.go index ab93df32..422c8a2c 100644 --- a/main_test.go +++ b/main_test.go @@ -460,62 +460,6 @@ func TestSendHeartbeats_ExtraHeartbeats_SyncLegacyOfflineActivity(t *testing.T) assert.Eventually(t, func() bool { return numCalls == 4 }, time.Second, 50*time.Millisecond) } -func TestSendHeartbeats_OfflineOnly(t *testing.T) { - apiURL, router, close := setupTestServer() - defer close() - - router.HandleFunc("/users/current/heartbeats.bulk", func(_ http.ResponseWriter, _ *http.Request) { - require.FailNow(t, "Should not make any API request") - }) - - tmpDir := t.TempDir() - - offlineQueueFile, err := os.CreateTemp(tmpDir, "") - require.NoError(t, err) - - defer offlineQueueFile.Close() - - tmpConfigFile, err := os.CreateTemp(tmpDir, "wakatime.cfg") - require.NoError(t, err) - - defer tmpConfigFile.Close() - - tmpInternalConfigFile, err := os.CreateTemp(tmpDir, "wakatime-internal.cfg") - require.NoError(t, err) - - defer tmpInternalConfigFile.Close() - - data, err := os.ReadFile("testdata/extra_heartbeats.json") - require.NoError(t, err) - - buffer := bytes.NewBuffer(data) - - runWakatimeCli( - t, - buffer, - "--api-url", apiURL, - "--key", "00000000-0000-4000-8000-000000000000", - "--config", tmpConfigFile.Name(), - "--internal-config", tmpInternalConfigFile.Name(), - "--entity", "testdata/main.go", - "--extra-heartbeats", "true", - "--cursorpos", "100", - "--offline-queue-file", offlineQueueFile.Name(), - "--offline-only", - "--lineno", "42", - "--lines-in-file", "100", - "--time", "1585598059", - "--hide-branch-names", ".*", - "--write", - "--verbose", - ) - - offlineCount, err := offline.CountHeartbeats(offlineQueueFile.Name()) - require.NoError(t, err) - - assert.Equal(t, 27, offlineCount) -} - func TestSendHeartbeats_Err(t *testing.T) { apiURL, router, close := setupTestServer() defer close() From 28aa0dbf27364c075d3dd4d5df095daeec3a2b88 Mon Sep 17 00:00:00 2001 From: Alan Hamlett Date: Tue, 23 Jul 2024 11:32:59 +0200 Subject: [PATCH 3/3] Fix status bar today when output json missing categories --- pkg/summary/summary.go | 10 +++++++--- pkg/summary/summary_test.go | 3 ++- pkg/summary/testdata/statusbar_today_simplified.json | 2 +- 3 files changed, 10 insertions(+), 5 deletions(-) diff --git a/pkg/summary/summary.go b/pkg/summary/summary.go index 2b617dfc..bfab81da 100644 --- a/pkg/summary/summary.go +++ b/pkg/summary/summary.go @@ -179,7 +179,7 @@ func RenderToday(summary *Summary, hideCategories bool, out output.Output) (stri } s := simplified{ - Text: summary.Data.GrandTotal.Text, + Text: getText(summary, hideCategories), HasTeamFeatures: summary.HasTeamFeatures, } @@ -191,8 +191,12 @@ func RenderToday(summary *Summary, hideCategories bool, out output.Output) (stri return string(data), nil } + return getText(summary, hideCategories), nil +} + +func getText(summary *Summary, hideCategories bool) string { if len(summary.Data.Categories) < 2 || hideCategories { - return summary.Data.GrandTotal.Text, nil + return summary.Data.GrandTotal.Text } var outputs []string @@ -200,5 +204,5 @@ func RenderToday(summary *Summary, hideCategories bool, out output.Output) (stri outputs = append(outputs, fmt.Sprintf("%s %s", category.Text, category.Name)) } - return strings.Join(outputs, ", "), nil + return strings.Join(outputs, ", ") } diff --git a/pkg/summary/summary_test.go b/pkg/summary/summary_test.go index 4acefd11..cade7465 100644 --- a/pkg/summary/summary_test.go +++ b/pkg/summary/summary_test.go @@ -2,6 +2,7 @@ package summary_test import ( "os" + "strings" "testing" "github.com/wakatime/wakatime-cli/pkg/output" @@ -35,7 +36,7 @@ func TestRenderToday(t *testing.T) { rendered, err := summary.RenderToday(testSummary(), false, test.Output) require.NoError(t, err) - assert.Equal(t, test.Expected, rendered) + assert.Equal(t, strings.TrimSpace(test.Expected), strings.TrimSpace(rendered)) }) } } diff --git a/pkg/summary/testdata/statusbar_today_simplified.json b/pkg/summary/testdata/statusbar_today_simplified.json index 2706bd6f..299f431f 100644 --- a/pkg/summary/testdata/statusbar_today_simplified.json +++ b/pkg/summary/testdata/statusbar_today_simplified.json @@ -1 +1 @@ -{"text":"2 hrs 17 mins","has_team_features":true} \ No newline at end of file +{"text":"2 hrs 17 mins Coding, 7 secs Debugging","has_team_features":true}