diff --git a/runner/engine.go b/runner/engine.go index ce2fa843..ce5fdd1e 100644 --- a/runner/engine.go +++ b/runner/engine.go @@ -591,7 +591,8 @@ func (e *Engine) runBin() error { case <-killCh: return default: - command := strings.Join(append([]string{e.config.Build.Bin}, e.runArgs...), " ") + formattedBin := formatPath(e.config.Build.Bin) + command := strings.Join(append([]string{formattedBin}, e.runArgs...), " ") cmd, stdout, stderr, err := e.startCmd(command) if err != nil { e.mainLog("failed to start %s, error: %s", e.config.rel(e.config.binPath()), err.Error()) diff --git a/runner/util.go b/runner/util.go index 03d32afe..79700b92 100644 --- a/runner/util.go +++ b/runner/util.go @@ -429,3 +429,17 @@ func joinPath(root, path string) string { return filepath.Join(root, path) } + +func formatPath(path string) string { + if !filepath.IsAbs(path) || !strings.Contains(path, " ") { + return path + } + + quotedPath := fmt.Sprintf(`"%s"`, path) + + if runtime.GOOS == PlatformWindows { + return fmt.Sprintf(`& %s`, quotedPath) + } + + return quotedPath +} diff --git a/runner/util_test.go b/runner/util_test.go index 3c18cf36..b526f05c 100644 --- a/runner/util_test.go +++ b/runner/util_test.go @@ -318,3 +318,87 @@ func TestJoinPathAbsolute(t *testing.T) { assert.Equal(t, result, path) } + +func TestFormatPath(t *testing.T) { + type testCase struct { + name string + path string + expected string + } + + runTests := func(t *testing.T, tests []testCase) { + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + result := formatPath(tt.path) + if result != tt.expected { + t.Errorf("formatPath(%q) = %q, want %q", tt.path, result, tt.expected) + } + }) + } + } + + t.Run("PathPlatformSpecific", func(t *testing.T) { + if runtime.GOOS == PlatformWindows { + // Windows-specific tests + tests := []testCase{ + { + name: "Windows style absolute path with spaces", + path: `C:\My Documents\My Project\tmp\app.exe`, + expected: `& "C:\My Documents\My Project\tmp\app.exe"`, + }, + { + name: "Windows style relative path with spaces", + path: `My Project\tmp\app.exe`, + expected: `My Project\tmp\app.exe`, + }, + { + name: "Windows style absolute path without spaces", + path: `C:\Documents\Project\tmp\app.exe`, + expected: `C:\Documents\Project\tmp\app.exe`, + }, + } + runTests(t, tests) + } else { + // Unix-specific tests + tests := []testCase{ + { + name: "Unix style absolute path with spaces", + path: `/usr/local/my project/tmp/main`, + expected: `"/usr/local/my project/tmp/main"`, + }, + { + name: "Unix style relative path with spaces", + path: "./my project/tmp/main", + expected: "./my project/tmp/main", + }, + { + name: "Unix style absolute path without spaces", + path: `/usr/local/project/tmp/main`, + expected: `/usr/local/project/tmp/main`, + }, + } + runTests(t, tests) + } + }) + + t.Run("CommonCases", func(t *testing.T) { + tests := []testCase{ + { + name: "Empty path", + path: "", + expected: "", + }, + { + name: "Simple path", + path: "main.go", + expected: "main.go", + }, + { + name: "TestShouldIncludeIncludedFile", + path: "sh main.sh", + expected: "sh main.sh", + }, + } + runTests(t, tests) + }) +}