fix(crash): platform-split unix code, harden test assertions, add key-value redaction test
Move collectCoreDumpDiagnostics/formatRlimitCore and enableCrashForensics/logCoreDumpLimits/formatRlimit into build-constrained _unix.go files with stubs for non-unix platforms. Guard type assertions in TestWriteUnexpectedCloseReport to produce clear failures instead of panics. Add key_value_params subtest for secretKeyRegex coverage.
- commit
- 8b3e7477c16bfd730d181aa751fc4f88d9ad519a
- author
- brice · · 2026-02-08 09:57 UTC
- parent
- 01565b7
- signature
- GPG Verified
CD88F9E564E0102646FB6077A1A07C7C7C3E349D· arch-gpg Verified for the committer email
7 files · +119 -66
M cmd/dumber/main.go +0 -38
| @@ -7,8 +7,6 @@ import ( | |||
| 7 | 7 | "os" | |
| 8 | 8 | "os/signal" | |
| 9 | 9 | "runtime" | |
| 10 | - | "runtime/debug" | |
| 11 | - | "strconv" | |
| 12 | 10 | "syscall" | |
| 13 | 11 | ||
| 14 | 12 | "github.com/bnema/dumber/internal/application/port" | |
| @@ -33,7 +31,6 @@ import ( | |||
| 33 | 31 | "github.com/bnema/dumber/internal/ui/component" | |
| 34 | 32 | "github.com/bnema/dumber/internal/ui/theme" | |
| 35 | 33 | "github.com/rs/zerolog" | |
| 36 | - | "golang.org/x/sys/unix" | |
| 37 | 34 | ) | |
| 38 | 35 | ||
| 39 | 36 | // Build-time variables (set via ldflags). | |
| @@ -394,41 +391,6 @@ func setupSignalHandler(ctx context.Context, app *ui.App) { | |||
| 394 | 391 | }() | |
| 395 | 392 | } | |
| 396 | 393 | ||
| 397 | - | func enableCrashForensics() { | |
| 398 | - | debug.SetTraceback("crash") | |
| 399 | - | ||
| 400 | - | var limit unix.Rlimit | |
| 401 | - | if err := unix.Getrlimit(unix.RLIMIT_CORE, &limit); err != nil { | |
| 402 | - | return | |
| 403 | - | } | |
| 404 | - | if limit.Cur >= limit.Max { | |
| 405 | - | return | |
| 406 | - | } | |
| 407 | - | limit.Cur = limit.Max | |
| 408 | - | _ = unix.Setrlimit(unix.RLIMIT_CORE, &limit) | |
| 409 | - | } | |
| 410 | - | ||
| 411 | - | func logCoreDumpLimits(ctx context.Context) { | |
| 412 | - | log := logging.FromContext(ctx) | |
| 413 | - | var limit unix.Rlimit | |
| 414 | - | if err := unix.Getrlimit(unix.RLIMIT_CORE, &limit); err != nil { | |
| 415 | - | log.Debug().Err(err).Msg("failed to read RLIMIT_CORE") | |
| 416 | - | return | |
| 417 | - | } | |
| 418 | - | ||
| 419 | - | log.Debug(). | |
| 420 | - | Str("soft", formatRlimit(limit.Cur)). | |
| 421 | - | Str("hard", formatRlimit(limit.Max)). | |
| 422 | - | Msg("core dump limits") | |
| 423 | - | } | |
| 424 | - | ||
| 425 | - | func formatRlimit(value uint64) string { | |
| 426 | - | if value == unix.RLIM_INFINITY { | |
| 427 | - | return "infinity" | |
| 428 | - | } | |
| 429 | - | return strconv.FormatUint(value, 10) | |
| 430 | - | } | |
| 431 | - | ||
| 432 | 394 | // repositories groups infrastructure layer repository implementations. | |
| 433 | 395 | type repositories struct { | |
| 434 | 396 | history repository.HistoryRepository | |
| @@ -7,8 +7,6 @@ import ( | |||||
| 7 | "os" | 7 | "os" | ||
| 8 | "os/signal" | 8 | "os/signal" | ||
| 9 | "runtime" | 9 | "runtime" | ||
| 10 | - | "runtime/debug" | |||
| 11 | - | "strconv" | |||
| 12 | "syscall" | 10 | "syscall" | ||
| 13 | 11 | ||||
| 14 | "github.com/bnema/dumber/internal/application/port" | 12 | "github.com/bnema/dumber/internal/application/port" | ||
| @@ -33,7 +31,6 @@ import ( | |||||
| 33 | "github.com/bnema/dumber/internal/ui/component" | 31 | "github.com/bnema/dumber/internal/ui/component" | ||
| 34 | "github.com/bnema/dumber/internal/ui/theme" | 32 | "github.com/bnema/dumber/internal/ui/theme" | ||
| 35 | "github.com/rs/zerolog" | 33 | "github.com/rs/zerolog" | ||
| 36 | - | "golang.org/x/sys/unix" | |||
| 37 | ) | 34 | ) | ||
| 38 | 35 | ||||
| 39 | // Build-time variables (set via ldflags). | 36 | // Build-time variables (set via ldflags). | ||
| @@ -394,41 +391,6 @@ func setupSignalHandler(ctx context.Context, app *ui.App) { | |||||
| 394 | }() | 391 | }() | ||
| 395 | } | 392 | } | ||
| 396 | 393 | ||||
| 397 | - | func enableCrashForensics() { | |||
| 398 | - | debug.SetTraceback("crash") | |||
| 399 | - | ||||
| 400 | - | var limit unix.Rlimit | |||
| 401 | - | if err := unix.Getrlimit(unix.RLIMIT_CORE, &limit); err != nil { | |||
| 402 | - | return | |||
| 403 | - | } | |||
| 404 | - | if limit.Cur >= limit.Max { | |||
| 405 | - | return | |||
| 406 | - | } | |||
| 407 | - | limit.Cur = limit.Max | |||
| 408 | - | _ = unix.Setrlimit(unix.RLIMIT_CORE, &limit) | |||
| 409 | - | } | |||
| 410 | - | ||||
| 411 | - | func logCoreDumpLimits(ctx context.Context) { | |||
| 412 | - | log := logging.FromContext(ctx) | |||
| 413 | - | var limit unix.Rlimit | |||
| 414 | - | if err := unix.Getrlimit(unix.RLIMIT_CORE, &limit); err != nil { | |||
| 415 | - | log.Debug().Err(err).Msg("failed to read RLIMIT_CORE") | |||
| 416 | - | return | |||
| 417 | - | } | |||
| 418 | - | ||||
| 419 | - | log.Debug(). | |||
| 420 | - | Str("soft", formatRlimit(limit.Cur)). | |||
| 421 | - | Str("hard", formatRlimit(limit.Max)). | |||
| 422 | - | Msg("core dump limits") | |||
| 423 | - | } | |||
| 424 | - | ||||
| 425 | - | func formatRlimit(value uint64) string { | |||
| 426 | - | if value == unix.RLIM_INFINITY { | |||
| 427 | - | return "infinity" | |||
| 428 | - | } | |||
| 429 | - | return strconv.FormatUint(value, 10) | |||
| 430 | - | } | |||
| 431 | - | ||||
| 432 | // repositories groups infrastructure layer repository implementations. | 394 | // repositories groups infrastructure layer repository implementations. | ||
| 433 | type repositories struct { | 395 | type repositories struct { | ||
| 434 | history repository.HistoryRepository | 396 | history repository.HistoryRepository | ||
A cmd/dumber/main_other.go +9 -0
| @@ -0,0 +1,9 @@ | |||
| 1 | + | //go:build !linux && !darwin | |
| 2 | + | ||
| 3 | + | package main | |
| 4 | + | ||
| 5 | + | import "context" | |
| 6 | + | ||
| 7 | + | func enableCrashForensics() {} | |
| 8 | + | ||
| 9 | + | func logCoreDumpLimits(_ context.Context) {} | |
| @@ -0,0 +1,9 @@ | |||||
| 1 | + | //go:build !linux && !darwin | |||
| 2 | + | ||||
| 3 | + | package main | |||
| 4 | + | ||||
| 5 | + | import "context" | |||
| 6 | + | ||||
| 7 | + | func enableCrashForensics() {} | |||
| 8 | + | ||||
| 9 | + | func logCoreDumpLimits(_ context.Context) {} | |||
A cmd/dumber/main_unix.go +47 -0
| @@ -0,0 +1,47 @@ | |||
| 1 | + | //go:build linux || darwin | |
| 2 | + | ||
| 3 | + | package main | |
| 4 | + | ||
| 5 | + | import ( | |
| 6 | + | "context" | |
| 7 | + | "runtime/debug" | |
| 8 | + | "strconv" | |
| 9 | + | ||
| 10 | + | "github.com/bnema/dumber/internal/logging" | |
| 11 | + | "golang.org/x/sys/unix" | |
| 12 | + | ) | |
| 13 | + | ||
| 14 | + | func enableCrashForensics() { | |
| 15 | + | debug.SetTraceback("crash") | |
| 16 | + | ||
| 17 | + | var limit unix.Rlimit | |
| 18 | + | if err := unix.Getrlimit(unix.RLIMIT_CORE, &limit); err != nil { | |
| 19 | + | return | |
| 20 | + | } | |
| 21 | + | if limit.Cur >= limit.Max { | |
| 22 | + | return | |
| 23 | + | } | |
| 24 | + | limit.Cur = limit.Max | |
| 25 | + | _ = unix.Setrlimit(unix.RLIMIT_CORE, &limit) | |
| 26 | + | } | |
| 27 | + | ||
| 28 | + | func logCoreDumpLimits(ctx context.Context) { | |
| 29 | + | log := logging.FromContext(ctx) | |
| 30 | + | var limit unix.Rlimit | |
| 31 | + | if err := unix.Getrlimit(unix.RLIMIT_CORE, &limit); err != nil { | |
| 32 | + | log.Debug().Err(err).Msg("failed to read RLIMIT_CORE") | |
| 33 | + | return | |
| 34 | + | } | |
| 35 | + | ||
| 36 | + | log.Debug(). | |
| 37 | + | Str("soft", formatRlimit(limit.Cur)). | |
| 38 | + | Str("hard", formatRlimit(limit.Max)). | |
| 39 | + | Msg("core dump limits") | |
| 40 | + | } | |
| 41 | + | ||
| 42 | + | func formatRlimit(value uint64) string { | |
| 43 | + | if value == unix.RLIM_INFINITY { | |
| 44 | + | return "infinity" | |
| 45 | + | } | |
| 46 | + | return strconv.FormatUint(value, 10) | |
| 47 | + | } | |
| @@ -0,0 +1,47 @@ | |||||
| 1 | + | //go:build linux || darwin | |||
| 2 | + | ||||
| 3 | + | package main | |||
| 4 | + | ||||
| 5 | + | import ( | |||
| 6 | + | "context" | |||
| 7 | + | "runtime/debug" | |||
| 8 | + | "strconv" | |||
| 9 | + | ||||
| 10 | + | "github.com/bnema/dumber/internal/logging" | |||
| 11 | + | "golang.org/x/sys/unix" | |||
| 12 | + | ) | |||
| 13 | + | ||||
| 14 | + | func enableCrashForensics() { | |||
| 15 | + | debug.SetTraceback("crash") | |||
| 16 | + | ||||
| 17 | + | var limit unix.Rlimit | |||
| 18 | + | if err := unix.Getrlimit(unix.RLIMIT_CORE, &limit); err != nil { | |||
| 19 | + | return | |||
| 20 | + | } | |||
| 21 | + | if limit.Cur >= limit.Max { | |||
| 22 | + | return | |||
| 23 | + | } | |||
| 24 | + | limit.Cur = limit.Max | |||
| 25 | + | _ = unix.Setrlimit(unix.RLIMIT_CORE, &limit) | |||
| 26 | + | } | |||
| 27 | + | ||||
| 28 | + | func logCoreDumpLimits(ctx context.Context) { | |||
| 29 | + | log := logging.FromContext(ctx) | |||
| 30 | + | var limit unix.Rlimit | |||
| 31 | + | if err := unix.Getrlimit(unix.RLIMIT_CORE, &limit); err != nil { | |||
| 32 | + | log.Debug().Err(err).Msg("failed to read RLIMIT_CORE") | |||
| 33 | + | return | |||
| 34 | + | } | |||
| 35 | + | ||||
| 36 | + | log.Debug(). | |||
| 37 | + | Str("soft", formatRlimit(limit.Cur)). | |||
| 38 | + | Str("hard", formatRlimit(limit.Max)). | |||
| 39 | + | Msg("core dump limits") | |||
| 40 | + | } | |||
| 41 | + | ||||
| 42 | + | func formatRlimit(value uint64) string { | |||
| 43 | + | if value == unix.RLIM_INFINITY { | |||
| 44 | + | return "infinity" | |||
| 45 | + | } | |||
| 46 | + | return strconv.FormatUint(value, 10) | |||
| 47 | + | } | |||
M internal/bootstrap/crash_report.go +0 -23
| @@ -16,7 +16,6 @@ import ( | |||
| 16 | 16 | "time" | |
| 17 | 17 | ||
| 18 | 18 | corelogging "github.com/bnema/dumber/internal/logging" | |
| 19 | - | "golang.org/x/sys/unix" | |
| 20 | 19 | ) | |
| 21 | 20 | ||
| 22 | 21 | const ( | |
| @@ -168,28 +167,6 @@ func currentReporter() unexpectedCloseReporter { | |||
| 168 | 167 | } | |
| 169 | 168 | } | |
| 170 | 169 | ||
| 171 | - | func collectCoreDumpDiagnostics() unexpectedCloseCoreDump { | |
| 172 | - | d := unexpectedCloseCoreDump{ | |
| 173 | - | Hint: "Run `coredumpctl list | rg -i \"dumber\"` and include matching entries in the issue.", | |
| 174 | - | } | |
| 175 | - | var limit unix.Rlimit | |
| 176 | - | if err := unix.Getrlimit(unix.RLIMIT_CORE, &limit); err != nil { | |
| 177 | - | d.RLimitCoreSoft = "unknown" | |
| 178 | - | d.RLimitCoreHard = "unknown" | |
| 179 | - | return d | |
| 180 | - | } | |
| 181 | - | d.RLimitCoreSoft = formatRlimitCore(limit.Cur) | |
| 182 | - | d.RLimitCoreHard = formatRlimitCore(limit.Max) | |
| 183 | - | return d | |
| 184 | - | } | |
| 185 | - | ||
| 186 | - | func formatRlimitCore(value uint64) string { | |
| 187 | - | if value == unix.RLIM_INFINITY { | |
| 188 | - | return "infinity" | |
| 189 | - | } | |
| 190 | - | return strconv.FormatUint(value, 10) | |
| 191 | - | } | |
| 192 | - | ||
| 193 | 170 | func readStartupProcessIDs(lockDir, sessionID string) (int, int) { | |
| 194 | 171 | raw, err := os.ReadFile(startupMarkerPath(lockDir, sessionID)) | |
| 195 | 172 | if err != nil { | |
| @@ -16,7 +16,6 @@ import ( | |||||
| 16 | "time" | 16 | "time" | ||
| 17 | 17 | ||||
| 18 | corelogging "github.com/bnema/dumber/internal/logging" | 18 | corelogging "github.com/bnema/dumber/internal/logging" | ||
| 19 | - | "golang.org/x/sys/unix" | |||
| 20 | ) | 19 | ) | ||
| 21 | 20 | ||||
| 22 | const ( | 21 | const ( | ||
| @@ -168,28 +167,6 @@ func currentReporter() unexpectedCloseReporter { | |||||
| 168 | } | 167 | } | ||
| 169 | } | 168 | } | ||
| 170 | 169 | ||||
| 171 | - | func collectCoreDumpDiagnostics() unexpectedCloseCoreDump { | |||
| 172 | - | d := unexpectedCloseCoreDump{ | |||
| 173 | - | Hint: "Run `coredumpctl list | rg -i \"dumber\"` and include matching entries in the issue.", | |||
| 174 | - | } | |||
| 175 | - | var limit unix.Rlimit | |||
| 176 | - | if err := unix.Getrlimit(unix.RLIMIT_CORE, &limit); err != nil { | |||
| 177 | - | d.RLimitCoreSoft = "unknown" | |||
| 178 | - | d.RLimitCoreHard = "unknown" | |||
| 179 | - | return d | |||
| 180 | - | } | |||
| 181 | - | d.RLimitCoreSoft = formatRlimitCore(limit.Cur) | |||
| 182 | - | d.RLimitCoreHard = formatRlimitCore(limit.Max) | |||
| 183 | - | return d | |||
| 184 | - | } | |||
| 185 | - | ||||
| 186 | - | func formatRlimitCore(value uint64) string { | |||
| 187 | - | if value == unix.RLIM_INFINITY { | |||
| 188 | - | return "infinity" | |||
| 189 | - | } | |||
| 190 | - | return strconv.FormatUint(value, 10) | |||
| 191 | - | } | |||
| 192 | - | ||||
| 193 | func readStartupProcessIDs(lockDir, sessionID string) (int, int) { | 170 | func readStartupProcessIDs(lockDir, sessionID string) (int, int) { | ||
| 194 | raw, err := os.ReadFile(startupMarkerPath(lockDir, sessionID)) | 171 | raw, err := os.ReadFile(startupMarkerPath(lockDir, sessionID)) | ||
| 195 | if err != nil { | 172 | if err != nil { | ||
A internal/bootstrap/crash_report_other.go +11 -0
| @@ -0,0 +1,11 @@ | |||
| 1 | + | //go:build !linux && !darwin | |
| 2 | + | ||
| 3 | + | package bootstrap | |
| 4 | + | ||
| 5 | + | func collectCoreDumpDiagnostics() unexpectedCloseCoreDump { | |
| 6 | + | return unexpectedCloseCoreDump{ | |
| 7 | + | RLimitCoreSoft: "unsupported", | |
| 8 | + | RLimitCoreHard: "unsupported", | |
| 9 | + | Hint: "Core dump diagnostics are only available on Linux and macOS.", | |
| 10 | + | } | |
| 11 | + | } | |
| @@ -0,0 +1,11 @@ | |||||
| 1 | + | //go:build !linux && !darwin | |||
| 2 | + | ||||
| 3 | + | package bootstrap | |||
| 4 | + | ||||
| 5 | + | func collectCoreDumpDiagnostics() unexpectedCloseCoreDump { | |||
| 6 | + | return unexpectedCloseCoreDump{ | |||
| 7 | + | RLimitCoreSoft: "unsupported", | |||
| 8 | + | RLimitCoreHard: "unsupported", | |||
| 9 | + | Hint: "Core dump diagnostics are only available on Linux and macOS.", | |||
| 10 | + | } | |||
| 11 | + | } | |||
M internal/bootstrap/crash_report_test.go +21 -5
| @@ -35,6 +35,15 @@ func TestRedactSensitiveContent(t *testing.T) { | |||
| 35 | 35 | assert.Contains(t, redacted, `"password":"[REDACTED]"`) | |
| 36 | 36 | }) | |
| 37 | 37 | ||
| 38 | + | t.Run("key_value_params", func(t *testing.T) { | |
| 39 | + | line := "callback received password=secret123 token=abc456" | |
| 40 | + | redacted := redactSensitiveContent(line) | |
| 41 | + | assert.NotContains(t, redacted, "secret123") | |
| 42 | + | assert.NotContains(t, redacted, "abc456") | |
| 43 | + | assert.Contains(t, redacted, "password=[REDACTED]") | |
| 44 | + | assert.Contains(t, redacted, "token=[REDACTED]") | |
| 45 | + | }) | |
| 46 | + | ||
| 38 | 47 | t.Run("authorization_headers", func(t *testing.T) { | |
| 39 | 48 | line := "Authorization: Bearer eyJhbGciOiJSUz" | |
| 40 | 49 | redacted := redactSensitiveContent(line) | |
| @@ -105,13 +114,20 @@ func TestWriteUnexpectedCloseReport(t *testing.T) { | |||
| 105 | 114 | require.NoError(t, json.Unmarshal(raw, &payload)) | |
| 106 | 115 | assert.Equal(t, sessionID, payload["session_id"]) | |
| 107 | 116 | ||
| 108 | - | classification := payload["classification"].(map[string]any) | |
| 109 | - | classValue, _ := classification["class"].(string) | |
| 117 | + | classificationRaw, ok := payload["classification"].(map[string]any) | |
| 118 | + | require.True(t, ok, "classification should be a map[string]any") | |
| 119 | + | classValue, ok := classificationRaw["class"].(string) | |
| 120 | + | require.True(t, ok, "classification.class should be a string") | |
| 110 | 121 | assert.Equal(t, string(SessionExitMainProcessCrashOrAbrupt), classValue) | |
| 111 | 122 | ||
| 112 | - | tail := payload["session_log_tail_redacted"].([]any) | |
| 113 | - | require.NotEmpty(t, tail) | |
| 114 | - | text := tail[0].(string) + tail[len(tail)-1].(string) | |
| 123 | + | tail, ok := payload["session_log_tail_redacted"].([]any) | |
| 124 | + | require.True(t, ok, "session_log_tail_redacted should be a []any") | |
| 125 | + | require.GreaterOrEqual(t, len(tail), 2, "expected at least 2 log tail entries") | |
| 126 | + | first, ok := tail[0].(string) | |
| 127 | + | require.True(t, ok, "first tail entry should be a string") | |
| 128 | + | last, ok := tail[len(tail)-1].(string) | |
| 129 | + | require.True(t, ok, "last tail entry should be a string") | |
| 130 | + | text := first + last | |
| 115 | 131 | assert.NotContains(t, text, "?a=1&b=2") | |
| 116 | 132 | assert.NotContains(t, text, "code=abc") | |
| 117 | 133 | assert.NotContains(t, text, "token=def") | |
| @@ -35,6 +35,15 @@ func TestRedactSensitiveContent(t *testing.T) { | |||||
| 35 | assert.Contains(t, redacted, `"password":"[REDACTED]"`) | 35 | assert.Contains(t, redacted, `"password":"[REDACTED]"`) | ||
| 36 | }) | 36 | }) | ||
| 37 | 37 | ||||
| 38 | + | t.Run("key_value_params", func(t *testing.T) { | |||
| 39 | + | line := "callback received password=secret123 token=abc456" | |||
| 40 | + | redacted := redactSensitiveContent(line) | |||
| 41 | + | assert.NotContains(t, redacted, "secret123") | |||
| 42 | + | assert.NotContains(t, redacted, "abc456") | |||
| 43 | + | assert.Contains(t, redacted, "password=[REDACTED]") | |||
| 44 | + | assert.Contains(t, redacted, "token=[REDACTED]") | |||
| 45 | + | }) | |||
| 46 | + | ||||
| 38 | t.Run("authorization_headers", func(t *testing.T) { | 47 | t.Run("authorization_headers", func(t *testing.T) { | ||
| 39 | line := "Authorization: Bearer eyJhbGciOiJSUz" | 48 | line := "Authorization: Bearer eyJhbGciOiJSUz" | ||
| 40 | redacted := redactSensitiveContent(line) | 49 | redacted := redactSensitiveContent(line) | ||
| @@ -105,13 +114,20 @@ func TestWriteUnexpectedCloseReport(t *testing.T) { | |||||
| 105 | require.NoError(t, json.Unmarshal(raw, &payload)) | 114 | require.NoError(t, json.Unmarshal(raw, &payload)) | ||
| 106 | assert.Equal(t, sessionID, payload["session_id"]) | 115 | assert.Equal(t, sessionID, payload["session_id"]) | ||
| 107 | 116 | ||||
| 108 | - | classification := payload["classification"].(map[string]any) | 117 | + | classificationRaw, ok := payload["classification"].(map[string]any) |
| 109 | - | classValue, _ := classification["class"].(string) | 118 | + | require.True(t, ok, "classification should be a map[string]any") |
| 119 | + | classValue, ok := classificationRaw["class"].(string) | |||
| 120 | + | require.True(t, ok, "classification.class should be a string") | |||
| 110 | assert.Equal(t, string(SessionExitMainProcessCrashOrAbrupt), classValue) | 121 | assert.Equal(t, string(SessionExitMainProcessCrashOrAbrupt), classValue) | ||
| 111 | 122 | ||||
| 112 | - | tail := payload["session_log_tail_redacted"].([]any) | 123 | + | tail, ok := payload["session_log_tail_redacted"].([]any) |
| 113 | - | require.NotEmpty(t, tail) | 124 | + | require.True(t, ok, "session_log_tail_redacted should be a []any") |
| 114 | - | text := tail[0].(string) + tail[len(tail)-1].(string) | 125 | + | require.GreaterOrEqual(t, len(tail), 2, "expected at least 2 log tail entries") |
| 126 | + | first, ok := tail[0].(string) | |||
| 127 | + | require.True(t, ok, "first tail entry should be a string") | |||
| 128 | + | last, ok := tail[len(tail)-1].(string) | |||
| 129 | + | require.True(t, ok, "last tail entry should be a string") | |||
| 130 | + | text := first + last | |||
| 115 | assert.NotContains(t, text, "?a=1&b=2") | 131 | assert.NotContains(t, text, "?a=1&b=2") | ||
| 116 | assert.NotContains(t, text, "code=abc") | 132 | assert.NotContains(t, text, "code=abc") | ||
| 117 | assert.NotContains(t, text, "token=def") | 133 | assert.NotContains(t, text, "token=def") | ||
A internal/bootstrap/crash_report_unix.go +31 -0
| @@ -0,0 +1,31 @@ | |||
| 1 | + | //go:build linux || darwin | |
| 2 | + | ||
| 3 | + | package bootstrap | |
| 4 | + | ||
| 5 | + | import ( | |
| 6 | + | "strconv" | |
| 7 | + | ||
| 8 | + | "golang.org/x/sys/unix" | |
| 9 | + | ) | |
| 10 | + | ||
| 11 | + | func collectCoreDumpDiagnostics() unexpectedCloseCoreDump { | |
| 12 | + | d := unexpectedCloseCoreDump{ | |
| 13 | + | Hint: "Run `coredumpctl list | rg -i \"dumber\"` and include matching entries in the issue.", | |
| 14 | + | } | |
| 15 | + | var limit unix.Rlimit | |
| 16 | + | if err := unix.Getrlimit(unix.RLIMIT_CORE, &limit); err != nil { | |
| 17 | + | d.RLimitCoreSoft = "unknown" | |
| 18 | + | d.RLimitCoreHard = "unknown" | |
| 19 | + | return d | |
| 20 | + | } | |
| 21 | + | d.RLimitCoreSoft = formatRlimitCore(limit.Cur) | |
| 22 | + | d.RLimitCoreHard = formatRlimitCore(limit.Max) | |
| 23 | + | return d | |
| 24 | + | } | |
| 25 | + | ||
| 26 | + | func formatRlimitCore(value uint64) string { | |
| 27 | + | if value == unix.RLIM_INFINITY { | |
| 28 | + | return "infinity" | |
| 29 | + | } | |
| 30 | + | return strconv.FormatUint(value, 10) | |
| 31 | + | } | |
| @@ -0,0 +1,31 @@ | |||||
| 1 | + | //go:build linux || darwin | |||
| 2 | + | ||||
| 3 | + | package bootstrap | |||
| 4 | + | ||||
| 5 | + | import ( | |||
| 6 | + | "strconv" | |||
| 7 | + | ||||
| 8 | + | "golang.org/x/sys/unix" | |||
| 9 | + | ) | |||
| 10 | + | ||||
| 11 | + | func collectCoreDumpDiagnostics() unexpectedCloseCoreDump { | |||
| 12 | + | d := unexpectedCloseCoreDump{ | |||
| 13 | + | Hint: "Run `coredumpctl list | rg -i \"dumber\"` and include matching entries in the issue.", | |||
| 14 | + | } | |||
| 15 | + | var limit unix.Rlimit | |||
| 16 | + | if err := unix.Getrlimit(unix.RLIMIT_CORE, &limit); err != nil { | |||
| 17 | + | d.RLimitCoreSoft = "unknown" | |||
| 18 | + | d.RLimitCoreHard = "unknown" | |||
| 19 | + | return d | |||
| 20 | + | } | |||
| 21 | + | d.RLimitCoreSoft = formatRlimitCore(limit.Cur) | |||
| 22 | + | d.RLimitCoreHard = formatRlimitCore(limit.Max) | |||
| 23 | + | return d | |||
| 24 | + | } | |||
| 25 | + | ||||
| 26 | + | func formatRlimitCore(value uint64) string { | |||
| 27 | + | if value == unix.RLIM_INFINITY { | |||
| 28 | + | return "infinity" | |||
| 29 | + | } | |||
| 30 | + | return strconv.FormatUint(value, 10) | |||
| 31 | + | } | |||