diff --git a/chunk/chunk.go b/chunk/chunk.go index 7903813..8ade11c 100644 --- a/chunk/chunk.go +++ b/chunk/chunk.go @@ -252,7 +252,9 @@ func (chunk *ExecutableChunk) WriteBashScript(basedir string, script_name string } // bubble up the env after the script execution - _, err = writer.WriteString("echo \"### ENV ###\"\n") + // include newline in the echo output itself to ensure ENV marker starts on its own line + // even if user script doesn't end with newline (e.g. echo -n) + _, err = writer.WriteString("echo \"\n### ENV ###\"\n") _, err = writer.WriteString("printenv\n") if err != nil { return err diff --git a/chunk/command.go b/chunk/command.go index 15fdf2b..5a982c2 100644 --- a/chunk/command.go +++ b/chunk/command.go @@ -183,6 +183,11 @@ func (command *RunningCommand) Wait() error { command.Ctx.Cfg.Env = bashEnvVars if len(newLines) > 0 { + // Remove the trailing empty line that precedes the ENV marker (added by our \n in the echo) + // This prevents an extra blank line when the ENV section is stripped + if len(newLines) > 0 && newLines[len(newLines)-1] == "" { + newLines = newLines[:len(newLines)-1] + } command.Stdout = strings.Join(newLines, "\n") command.Stdout = command.Stdout + "\n" } else { diff --git a/chunk/command_test.go b/chunk/command_test.go index 8958a23..5817b68 100644 --- a/chunk/command_test.go +++ b/chunk/command_test.go @@ -348,4 +348,64 @@ func TestRunningCommand(t *testing.T) { err = runningCmd.Wait() assert.Error(t, err, "Expected an error when waiting for a failing command") }) + + t.Run("bash env extraction with echo -n command", func(t *testing.T) { + // Regression test for issue #25: ensure ENV marker doesn't get appended + // to command output when the command doesn't end with a newline + tmpDirs := make(map[string]string) + + cfg := &config.Config{MinutesToTimeout: 1, Env: []string{}} + ui := view.NewView("mock") + + chunk := ExecutableChunk{ + Runtime: "bash", + Content: []string{"echo -n 'TEST'"}, + Context: &runnercontext.Context{ + Cfg: cfg, + RView: ui, + }, + } + err := chunk.PrepareForExecution(tmpDirs) + assert.NoError(t, err, "Expected no error when preparing chunk for execution") + + err = chunk.ExecuteSequential() + assert.NoError(t, err, "Expected no error when executing bash chunk") + + // Verify the output is just "TEST\n" without the ENV marker appended to it + // The trailing \n is added by the command processing logic + assert.Equal(t, "TEST\n", chunk.Commands[0].Stdout, "Expected stdout to be 'TEST\\n' without ENV marker concatenated") + // Crucially, verify it's NOT "TEST### ENV ###" which was the bug + assert.NotContains(t, chunk.Commands[0].Stdout, "### ENV ###", "Expected ENV marker to be stripped from output") + + // Verify ENV section was still extracted (should have environment variables) + assert.NotEmpty(t, chunk.Context.Cfg.Env, "Expected environment variables to be extracted") + }) + + t.Run("bash env extraction with normal echo command", func(t *testing.T) { + // Ensure normal commands (with newline) don't get extra blank lines + tmpDirs := make(map[string]string) + + cfg := &config.Config{MinutesToTimeout: 1, Env: []string{}} + ui := view.NewView("mock") + + chunk := ExecutableChunk{ + Runtime: "bash", + Content: []string{"echo 'NORMAL'"}, + Context: &runnercontext.Context{ + Cfg: cfg, + RView: ui, + }, + } + err := chunk.PrepareForExecution(tmpDirs) + assert.NoError(t, err, "Expected no error when preparing chunk for execution") + + err = chunk.ExecuteSequential() + assert.NoError(t, err, "Expected no error when executing bash chunk") + + // Verify the output doesn't have extra blank lines + assert.Equal(t, "NORMAL\n", chunk.Commands[0].Stdout, "Expected stdout to be 'NORMAL\\n' without extra blank lines") + + // Verify ENV section was still extracted + assert.NotEmpty(t, chunk.Context.Cfg.Env, "Expected environment variables to be extracted") + }) }