diff --git a/cmd/httprunner/main.go b/cmd/httprunner/main.go index f889c41..7834f41 100644 --- a/cmd/httprunner/main.go +++ b/cmd/httprunner/main.go @@ -34,6 +34,7 @@ func main() { reportDetail := flag.String("detail", "summary", "Report detail level: summary, goroutine, iteration, full") verbose := flag.Bool("v", false, "Verbose mode: print request result JSON for each request") rawFile := flag.String("raw", "", "Path to raw results .jsonl file to generate report without executing") + csvShortcut := flag.Bool("csv", false, "Shorthand to output CSV from -raw to stdout (forces -report=csv, -detail=summary)") flag.Parse() @@ -49,6 +50,19 @@ func main() { os.Exit(1) } + // If -csv shorthand is provided, force CSV formatting and summary detail; + // also prefer printing to stdout for easy redirection. + forceStdout := false + if *csvShortcut { + *reportFormat = string(types.FormatCSV) + *reportDetail = string(types.DetailSummary) + forceStdout = true + if !offlineMode { + fmt.Println("Error: -csv requires -raw to be provided") + os.Exit(1) + } + } + // Validate runtime vs iterations parameters (only for execution mode) if !offlineMode { if *runtime < 0 { @@ -117,7 +131,7 @@ func main() { // Output handling mirrors execution mode format := types.ReportFormat(strings.ToLower(*reportFormat)) - if format == types.FormatConsole { + if format == types.FormatConsole || forceStdout { fmt.Print(reportContent) } else { // Ensure output directory exists diff --git a/cmd/httprunner/main_test.go b/cmd/httprunner/main_test.go index 5f0f460..8b814d8 100644 --- a/cmd/httprunner/main_test.go +++ b/cmd/httprunner/main_test.go @@ -150,3 +150,31 @@ func TestRunnerExitsOnInvalidDetailLevel(t *testing.T) { t.Fatalf("expected invalid detail message; got: %s", string(out)) } } + +func TestOfflineCSVShortcutPrintsToStdout(t *testing.T) { + dir := t.TempDir() + // Write minimal JSONL with a single RequestResult + jsonl := `{"Name":"Req1","Verb":"GET","URL":"http://example.com","StatusCode":200,"ResponseTime":100000000,"Success":true,"Error":"","Timestamp":"2024-01-01T00:00:00Z","VirtualUserID":1,"IterationID":1,"Checks":[]}` + p := filepath.Join(dir, "results.jsonl") + if err := os.WriteFile(p, []byte(jsonl+"\n"), 0644); err != nil { + t.Fatalf("write jsonl: %v", err) + } + + // Isolate flags + oldFS := flag.CommandLine + flag.CommandLine = flag.NewFlagSet("test", flag.ContinueOnError) + flag.CommandLine.SetOutput(new(strings.Builder)) + defer func() { flag.CommandLine = oldFS }() + + oldArgs := os.Args + os.Args = []string{"httprunner", "-raw", p, "-csv"} + defer func() { os.Args = oldArgs }() + + out := captureStdout(func() { main() }) + if !strings.Contains(out, "Index,Name,Method,URL,Success,StatusCode,ResponseTime,Error,CheckFailures,Timestamp") { + t.Fatalf("expected CSV header in output; got:\n%s", out) + } + if !strings.Contains(out, "Req1") { + t.Fatalf("expected CSV row with request name; got:\n%s", out) + } +}