diff --git a/cli.go b/cli.go index bdc5f1a..86502dc 100644 --- a/cli.go +++ b/cli.go @@ -204,9 +204,23 @@ Blocks until the program hits a breakpoint or exits, then returns auto-context.` dap debug app.py -- --config prod.yaml --verbose dap debug --attach localhost:5678 --backend debugpy --break handler.py:15 dap debug --pid 12345 --backend debugpy # attach to running process`, - Args: cobra.MaximumNArgs(1), + Args: func(cmd *cobra.Command, args []string) error { + scriptArgs := args + if dashIdx := cmd.ArgsLenAtDash(); dashIdx >= 0 && dashIdx <= len(args) { + scriptArgs = args[:dashIdx] + } + if len(scriptArgs) > 1 { + return fmt.Errorf("accepts at most 1 arg(s), received %d", len(scriptArgs)) + } + return nil + }, RunE: func(cmd *cobra.Command, args []string) error { - if len(args) == 0 && attach == "" && pid == 0 { + scriptArgs := args + if dashIdx := cmd.ArgsLenAtDash(); dashIdx >= 0 && dashIdx <= len(args) { + scriptArgs = args[:dashIdx] + } + + if len(scriptArgs) == 0 && attach == "" && pid == 0 { return fmt.Errorf("script path, --attach, or --pid required") } @@ -224,16 +238,13 @@ Blocks until the program hits a breakpoint or exits, then returns auto-context.` ExceptionFilters: exceptionFilters, ContextLines: globalFlags.contextLines, } - if len(args) > 0 { - debugArgs.Script = args[0] + if len(scriptArgs) > 0 { + debugArgs.Script = scriptArgs[0] } // Capture program args after -- - if dashIdx := cmd.ArgsLenAtDash(); dashIdx >= 0 { - allArgs := cmd.Flags().Args() - if dashIdx < len(allArgs) { - debugArgs.ProgramArgs = allArgs[dashIdx:] - } + if dashIdx := cmd.ArgsLenAtDash(); dashIdx >= 0 && dashIdx < len(args) { + debugArgs.ProgramArgs = append([]string(nil), args[dashIdx:]...) } rawArgs, _ := json.Marshal(debugArgs) diff --git a/e2e_test.go b/e2e_test.go index 8bd9feb..228877a 100644 --- a/e2e_test.go +++ b/e2e_test.go @@ -197,6 +197,48 @@ func TestE2E_JSONOutput(t *testing.T) { } } +func TestE2E_DebugPython_WithProgramArgs(t *testing.T) { + if err := exec.Command("python3", "-c", "import debugpy").Run(); err != nil { + t.Skip("debugpy not installed") + } + + env := newE2EEnv(t) + + scriptDir := t.TempDir() + scriptPath := filepath.Join(scriptDir, "args.py") + script := `import argparse + +p = argparse.ArgumentParser() +p.add_argument("value") +args = p.parse_args() +value = args.value +print(value) +` + if err := os.WriteFile(scriptPath, []byte(script), 0o644); err != nil { + t.Fatalf("writing script: %v", err) + } + + cmd := exec.Command(env.binary, + "--socket", env.socketPath, + "debug", scriptPath, + "--break", scriptPath+":6", + "--", + "example-value", + ) + cmd.Dir = projectRoot(t) + out, err := cmd.CombinedOutput() + if err != nil { + t.Fatalf("debug with program args failed: %v\n%s", err, out) + } + + if !strings.Contains(string(out), "Stopped: breakpoint") { + t.Errorf("expected breakpoint stop, got:\n%s", out) + } + if !strings.Contains(string(out), "value (str) = 'example-value'") { + t.Errorf("expected parsed CLI arg in locals, got:\n%s", out) + } +} + // TestE2E_DebugPython_Scheduler exercises cross-file breakpoints across a // multifile Python app: main.py → runner.py → resolver.py. func TestE2E_DebugPython_Scheduler(t *testing.T) {