diff --git a/cmd/rollapp/start/start.go b/cmd/rollapp/start/start.go index fd3e4c30..393ac51b 100644 --- a/cmd/rollapp/start/start.go +++ b/cmd/rollapp/start/start.go @@ -33,6 +33,8 @@ import ( var ( DaLcEndpoint string DaLogPath string + + execCmdFollowFunc = bash.ExecCmdFollow ) func Cmd() *cobra.Command { @@ -120,8 +122,8 @@ Consider using 'services' if you want to run a 'systemd'(unix) or 'launchd'(mac) go healthagent.Start(home, rollappConfig.HealthAgent, rollerLogger) } - done := make(chan error, 1) // nolint: errcheck + var promptResponses map[string]string if rollappConfig.KeyringBackend == consts.SupportedKeyringBackends.OS { pswFileName, err := filesystem.GetOsKeyringPswFileName( consts.Executables.RollappEVM, @@ -138,51 +140,20 @@ Consider using 'services' if you want to run a 'systemd'(unix) or 'launchd'(mac) return } - pr := map[string]string{ + promptResponses = map[string]string{ "Enter keyring passphrase": psw, "Re-enter keyring passphrase": psw, } + } - ctx, cancel := context.WithCancel(cmd.Context()) - defer cancel() - go func() { - err := bash.ExecCmdFollow( - done, - ctx, - startRollappCmd, - pr, - ) - - done <- err - }() - } else { - ctx, cancel := context.WithCancel(cmd.Context()) - defer cancel() - - go func() { - err := bash.ExecCmdFollow( - done, - ctx, - startRollappCmd, - nil, // No need for printOutput since we configured output above - ) - - done <- err - }() - select { - case err := <-done: - if err != nil { - pterm.Error.Println("rollapp's process returned an error: ", err) - os.Exit(1) - } - case <-ctx.Done(): - pterm.Error.Println("context cancelled, terminating command") - return - } + ctx, cancel := context.WithCancel(cmd.Context()) + defer cancel() + err = runRollappCommand(ctx, startRollappCmd, promptResponses) + if err != nil { + pterm.Error.Println(err) + os.Exit(1) } - - select {} }, } cmd.Flags().String("log-level", "debug", "pass the log level to the rollapp") @@ -190,6 +161,32 @@ Consider using 'services' if you want to run a 'systemd'(unix) or 'launchd'(mac) return cmd } +func runRollappCommand( + ctx context.Context, + cmd *exec.Cmd, + promptResponses map[string]string, +) error { + signalChan := make(chan error, 1) + execDone := make(chan error, 1) + + go func() { + err := execCmdFollowFunc(signalChan, ctx, cmd, promptResponses) + execDone <- err + }() + + select { + case err := <-signalChan: + if err != nil { + return fmt.Errorf("rollapp process received signal: %w", err) + } + return nil + case err := <-execDone: + return err + case <-ctx.Done(): + return fmt.Errorf("context cancelled: %w", ctx.Err()) + } +} + func PrintOutput( rlpCfg roller.RollappConfig, withBalance, diff --git a/cmd/rollapp/start/start_test.go b/cmd/rollapp/start/start_test.go new file mode 100644 index 00000000..714ef0ad --- /dev/null +++ b/cmd/rollapp/start/start_test.go @@ -0,0 +1,77 @@ +package start + +import ( + "context" + "errors" + "os/exec" + "testing" + "time" + + "github.com/dymensionxyz/roller/utils/bash" +) + +func TestRunRollappCommandSuccess(t *testing.T) { + t.Cleanup(func() { execCmdFollowFunc = bashExecCmdFollowWrapper }) + execCmdFollowFunc = func(done chan<- error, ctx context.Context, cmd *exec.Cmd, _ map[string]string) error { + done <- nil + return nil + } + + ctx, cancel := context.WithTimeout(context.Background(), time.Second) + defer cancel() + + if err := runRollappCommand(ctx, exec.Command("echo"), nil); err != nil { + t.Fatalf("expected no error, got %v", err) + } +} + +func TestRunRollappCommandReturnsProcessError(t *testing.T) { + t.Cleanup(func() { execCmdFollowFunc = bashExecCmdFollowWrapper }) + execCmdFollowFunc = func(done chan<- error, ctx context.Context, cmd *exec.Cmd, _ map[string]string) error { + return errors.New("boom") + } + + ctx, cancel := context.WithTimeout(context.Background(), time.Second) + defer cancel() + + err := runRollappCommand(ctx, exec.Command("echo"), nil) + if err == nil { + t.Fatalf("expected error, got nil") + } +} + +func TestRunRollappCommandSignals(t *testing.T) { + t.Cleanup(func() { execCmdFollowFunc = bashExecCmdFollowWrapper }) + execCmdFollowFunc = func(done chan<- error, ctx context.Context, cmd *exec.Cmd, _ map[string]string) error { + done <- errors.New("SIGTERM") + return nil + } + + ctx, cancel := context.WithTimeout(context.Background(), time.Second) + defer cancel() + + if err := runRollappCommand(ctx, exec.Command("echo"), nil); err == nil { + t.Fatalf("expected signal error") + } +} + +func TestRunRollappCommandContextCancelled(t *testing.T) { + t.Cleanup(func() { execCmdFollowFunc = bashExecCmdFollowWrapper }) + execCmdFollowFunc = func(done chan<- error, ctx context.Context, cmd *exec.Cmd, _ map[string]string) error { + select { + case <-ctx.Done(): + return ctx.Err() + } + } + + ctx, cancel := context.WithCancel(context.Background()) + cancel() + + if err := runRollappCommand(ctx, exec.Command("echo"), nil); err == nil { + t.Fatalf("expected context error") + } +} + +func bashExecCmdFollowWrapper(done chan<- error, ctx context.Context, cmd *exec.Cmd, prompts map[string]string) error { + return bash.ExecCmdFollow(done, ctx, cmd, prompts) +}