From 6ebf9e5bb8a47dd3a350acfa7d82039dd7cbd1df Mon Sep 17 00:00:00 2001 From: "Stanislav (Stas) Katkov" Date: Tue, 10 Mar 2026 13:27:22 +0100 Subject: [PATCH 1/4] Introduce fuzzing to classes --- .github/workflows/fuzz-nightly.yml | 33 +++ Makefile | 27 ++- internal/base64/base64_fuzz_test.go | 66 ++++++ internal/converter/converter.go | 43 +++- internal/converter/converter_fuzz_test.go | 190 ++++++++++++++++++ internal/converter/converter_test.go | 30 +++ .../fuzz/FuzzJSONToXML/03b8b7151108b66b | 2 + .../fuzz/FuzzJSONToXML/9c9314d711b15762 | 2 + .../fuzz/FuzzYAMLToTOML/abff33899e7cd4f9 | 2 + internal/csv2json/csv2json.go | 2 +- internal/csv2json/csv2json_test.go | 99 +++++++++ .../FuzzConvertDoesNotPanic/92197bf3838b3f8b | 2 + internal/htmlfmt/htmlfmt_fuzz_test.go | 17 ++ internal/ui/base_pager_model.go | 24 ++- internal/ui/base_pager_model_test.go | 27 +++ internal/yamlfmt/yamlfmt_fuzz_test.go | 30 +++ tui/base64-decoder/main.go | 2 +- tui/base64-decoder/main_test.go | 16 ++ tui/base64-encoder/main.go | 2 +- tui/base64-encoder/main_test.go | 16 ++ tui/json2toon/fuzz_test.go | 42 ++++ tui/jsonrepair/fuzz_test.go | 28 +++ 22 files changed, 686 insertions(+), 16 deletions(-) create mode 100644 .github/workflows/fuzz-nightly.yml create mode 100644 internal/base64/base64_fuzz_test.go create mode 100644 internal/converter/converter_fuzz_test.go create mode 100644 internal/converter/converter_test.go create mode 100644 internal/converter/testdata/fuzz/FuzzJSONToXML/03b8b7151108b66b create mode 100644 internal/converter/testdata/fuzz/FuzzJSONToXML/9c9314d711b15762 create mode 100644 internal/converter/testdata/fuzz/FuzzYAMLToTOML/abff33899e7cd4f9 create mode 100644 internal/csv2json/csv2json_test.go create mode 100644 internal/csv2json/testdata/fuzz/FuzzConvertDoesNotPanic/92197bf3838b3f8b create mode 100644 internal/htmlfmt/htmlfmt_fuzz_test.go create mode 100644 internal/ui/base_pager_model_test.go create mode 100644 internal/yamlfmt/yamlfmt_fuzz_test.go create mode 100644 tui/base64-decoder/main_test.go create mode 100644 tui/base64-encoder/main_test.go create mode 100644 tui/json2toon/fuzz_test.go create mode 100644 tui/jsonrepair/fuzz_test.go diff --git a/.github/workflows/fuzz-nightly.yml b/.github/workflows/fuzz-nightly.yml new file mode 100644 index 0000000..e511cc9 --- /dev/null +++ b/.github/workflows/fuzz-nightly.yml @@ -0,0 +1,33 @@ +name: nightly-fuzz + +on: + schedule: + - cron: "17 2 * * *" + workflow_dispatch: + +permissions: + contents: read + +jobs: + fuzz: + name: nightly-fuzz + runs-on: ubuntu-latest + timeout-minutes: 90 + steps: + - uses: actions/checkout@v6 + - uses: actions/setup-go@v6 + with: + go-version-file: "go.mod" + cache: true + + - name: Run nightly fuzz suite + run: make fuzz-nightly NIGHTLY_FUZZTIME=45s + + - name: Upload failing fuzz corpus + if: failure() + uses: actions/upload-artifact@v4 + with: + name: nightly-fuzz-corpus + path: | + **/testdata/fuzz/** + if-no-files-found: ignore diff --git a/Makefile b/Makefile index 0c20fb0..4528b2e 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,4 @@ -.PHONY: help build run test test-race lint clean deps tidy docs site release snapshot install +.PHONY: help build run test test-race test-coverage lint fmt vet check fuzz fuzz-nightly clean deps tidy update-deps generate docs site release snapshot install verify all # Default target .DEFAULT_GOAL := help @@ -9,6 +9,8 @@ VERSION?=$(shell git describe --tags --always --dirty 2>/dev/null || echo "dev") COMMIT?=$(shell git rev-parse --short HEAD 2>/dev/null || echo "unknown") DATE?=$(shell date -u +"%Y-%m-%dT%H:%M:%SZ") LDFLAGS=-ldflags "-X github.com/skatkov/devtui/cmd.version=$(VERSION) -X github.com/skatkov/devtui/cmd.commit=$(COMMIT) -X github.com/skatkov/devtui/cmd.date=$(DATE)" +FUZZTIME?=5s +NIGHTLY_FUZZTIME?=45s help: ## Display this help message @echo "DevTUI - Development targets:" @@ -33,6 +35,29 @@ test-race: ## Run tests with race detection (what CI uses) @echo "Running tests with race detection..." go test -v -failfast -race ./... +fuzz: ## Run fuzz tests (set FUZZTIME=30s for deeper runs) + @echo "Running fuzz tests (FUZZTIME=$(FUZZTIME))..." + go test ./internal/base64 -run=^$$ -fuzz=FuzzBase64RoundTrip -fuzztime=$(FUZZTIME) + go test ./internal/base64 -run=^$$ -fuzz=FuzzDecodeMatchesStdlibBehavior -fuzztime=$(FUZZTIME) + go test ./internal/csv2json -run=^$$ -fuzz=FuzzConvertDoesNotPanic -fuzztime=$(FUZZTIME) + go test ./internal/converter -run=^$$ -fuzz=FuzzYAMLToJSON -fuzztime=$(FUZZTIME) + go test ./internal/converter -run=^$$ -fuzz=FuzzJSONToYAML -fuzztime=$(FUZZTIME) + go test ./internal/converter -run=^$$ -fuzz=FuzzTOMLToJSON -fuzztime=$(FUZZTIME) + go test ./internal/converter -run=^$$ -fuzz=FuzzJSONToTOML -fuzztime=$(FUZZTIME) + go test ./internal/converter -run=^$$ -fuzz=FuzzXMLToJSON -fuzztime=$(FUZZTIME) + go test ./internal/converter -run=^$$ -fuzz=FuzzJSONToXML -fuzztime=$(FUZZTIME) + go test ./internal/converter -run=^$$ -fuzz=FuzzYAMLToTOML -fuzztime=$(FUZZTIME) + go test ./internal/converter -run=^$$ -fuzz=FuzzTOMLToYAML -fuzztime=$(FUZZTIME) + go test ./internal/yamlfmt -run=^$$ -fuzz=FuzzFormatYAML -fuzztime=$(FUZZTIME) + go test ./internal/htmlfmt -run=^$$ -fuzz=FuzzFormatHTML -fuzztime=$(FUZZTIME) + go test ./tui/jsonrepair -run=^$$ -fuzz=FuzzRepairJSONProducesValidJSON -fuzztime=$(FUZZTIME) + go test ./tui/json2toon -run=^$$ -fuzz=FuzzConvertNoPanic -fuzztime=$(FUZZTIME) + go test ./tui/json2toon -run=^$$ -fuzz=FuzzConvertWithOptionsNoPanic -fuzztime=$(FUZZTIME) + +fuzz-nightly: ## Run extended fuzz tests (default NIGHTLY_FUZZTIME=45s) + @echo "Running nightly fuzz tests (NIGHTLY_FUZZTIME=$(NIGHTLY_FUZZTIME))..." + $(MAKE) fuzz FUZZTIME=$(NIGHTLY_FUZZTIME) + test-coverage: ## Run tests with coverage report @echo "Running tests with coverage..." go test -coverprofile=coverage.txt ./... diff --git a/internal/base64/base64_fuzz_test.go b/internal/base64/base64_fuzz_test.go new file mode 100644 index 0000000..83faeef --- /dev/null +++ b/internal/base64/base64_fuzz_test.go @@ -0,0 +1,66 @@ +package base64 + +import ( + "bytes" + stdbase64 "encoding/base64" + "strings" + "testing" +) + +func FuzzBase64RoundTrip(f *testing.F) { + f.Add([]byte("")) + f.Add([]byte("hello world")) + f.Add([]byte("\x00\x01\x02\xff\xfe")) + f.Add([]byte("ñáéíóú 中文 🚀")) + + f.Fuzz(func(t *testing.T, data []byte) { + encoded := Encode(data) + + decoded, err := Decode(encoded) + if err != nil { + t.Fatalf("Decode(Encode(data)) returned error: %v", err) + } + + if !bytes.Equal(decoded, data) { + t.Fatalf("round-trip mismatch: got %x, want %x", decoded, data) + } + + decodedString, err := DecodeToString(encoded) + if err != nil { + t.Fatalf("DecodeToString(Encode(data)) returned error: %v", err) + } + + if decodedString != string(data) { + t.Fatalf("string round-trip mismatch: got %q, want %q", decodedString, string(data)) + } + }) +} + +func FuzzDecodeMatchesStdlibBehavior(f *testing.F) { + f.Add("") + f.Add("SGVsbG8=") + f.Add(" SGVsbG8= ") + f.Add("%%%") + f.Add("YWJj\n") + + f.Fuzz(func(t *testing.T, input string) { + trimmed := strings.TrimSpace(input) + want, wantErr := stdbase64.StdEncoding.DecodeString(trimmed) + + got, err := Decode(input) + if wantErr != nil { + if err == nil { + t.Fatalf("expected error for input %q", input) + } + return + } + + if err != nil { + t.Fatalf("unexpected error for input %q: %v", input, err) + } + + if !bytes.Equal(got, want) { + t.Fatalf("decoded bytes mismatch for input %q: got %x, want %x", input, got, want) + } + }) +} diff --git a/internal/converter/converter.go b/internal/converter/converter.go index 7a8eaed..10d06dd 100644 --- a/internal/converter/converter.go +++ b/internal/converter/converter.go @@ -3,7 +3,10 @@ package converter import ( "bytes" "encoding/json" + "encoding/xml" + "errors" "fmt" + "io" "github.com/clbanning/mxj/v2" "github.com/pelletier/go-toml/v2" @@ -77,7 +80,13 @@ func JSONToTOML(jsonContent string) (string, error) { return "", fmt.Errorf("TOML encoding error: %w", err) } - return buf.String(), nil + tomlContent := buf.String() + var validation any + if err := toml.Unmarshal([]byte(tomlContent), &validation); err != nil { + return "", fmt.Errorf("TOML encoding error: %w", err) + } + + return tomlContent, nil } func XMLToJSON(xmlContent string) (string, error) { @@ -105,6 +114,30 @@ func JSONToXML(jsonContent string) (string, error) { return "", fmt.Errorf("XML encoding error: %w", err) } + if len(bytes.TrimSpace(xmlBytes)) == 0 { + return "", errors.New("XML encoding error: empty XML output") + } + + decoder := xml.NewDecoder(bytes.NewReader(xmlBytes)) + seenStart := false + for { + tok, err := decoder.Token() + if err != nil { + if err == io.EOF { + break + } + return "", fmt.Errorf("XML encoding error: %w", err) + } + + if _, ok := tok.(xml.StartElement); ok { + seenStart = true + } + } + + if !seenStart { + return "", errors.New("XML encoding error: empty XML output") + } + var buf bytes.Buffer buf.Write(xmlBytes) return buf.String(), nil @@ -127,7 +160,13 @@ func YAMLToTOML(yamlContent string) (string, error) { return "", fmt.Errorf("TOML encoding error: %w", err) } - return buf.String(), nil + tomlContent := buf.String() + var validation any + if err := toml.Unmarshal([]byte(tomlContent), &validation); err != nil { + return "", fmt.Errorf("TOML encoding error: %w", err) + } + + return tomlContent, nil } func TOMLToYAML(tomlContent string) (string, error) { diff --git a/internal/converter/converter_fuzz_test.go b/internal/converter/converter_fuzz_test.go new file mode 100644 index 0000000..883fe23 --- /dev/null +++ b/internal/converter/converter_fuzz_test.go @@ -0,0 +1,190 @@ +package converter + +import ( + "encoding/json" + "testing" + + "github.com/clbanning/mxj/v2" + "github.com/pelletier/go-toml/v2" + "gopkg.in/yaml.v3" +) + +func FuzzYAMLToJSON(f *testing.F) { + f.Add("name: Alice\nage: 30\n") + f.Add("items:\n - one\n - two\n") + f.Add("{not: valid") + f.Add("") + + f.Fuzz(func(t *testing.T, input string) { + if len(input) > 4096 { + t.Skip() + } + + output, err := YAMLToJSON(input) + if err != nil { + return + } + + if !json.Valid([]byte(output)) { + t.Fatalf("YAMLToJSON returned invalid JSON: %q", output) + } + }) +} + +func FuzzJSONToYAML(f *testing.F) { + f.Add(`{"name":"Alice","age":30}`) + f.Add(`[1,2,3]`) + f.Add(`{invalid`) + f.Add("") + + f.Fuzz(func(t *testing.T, input string) { + if len(input) > 4096 { + t.Skip() + } + + output, err := JSONToYAML(input) + if err != nil { + return + } + + var data any + if err := yaml.Unmarshal([]byte(output), &data); err != nil { + t.Fatalf("JSONToYAML returned invalid YAML: %v", err) + } + }) +} + +func FuzzTOMLToJSON(f *testing.F) { + f.Add("name = \"Alice\"\nage = 30\n") + f.Add("[user]\nname = \"Bob\"\n") + f.Add("[[items]]\nname = \"one\"\n") + f.Add("") + + f.Fuzz(func(t *testing.T, input string) { + if len(input) > 4096 { + t.Skip() + } + + output, err := TOMLToJSON(input) + if err != nil { + return + } + + if !json.Valid([]byte(output)) { + t.Fatalf("TOMLToJSON returned invalid JSON: %q", output) + } + }) +} + +func FuzzJSONToTOML(f *testing.F) { + f.Add(`{"name":"Alice","age":30}`) + f.Add(`{"items":[{"name":"one"}]}`) + f.Add(`[]`) + f.Add("") + + f.Fuzz(func(t *testing.T, input string) { + if len(input) > 4096 { + t.Skip() + } + + output, err := JSONToTOML(input) + if err != nil { + return + } + + var data any + if err := toml.Unmarshal([]byte(output), &data); err != nil { + t.Fatalf("JSONToTOML returned invalid TOML: %v", err) + } + }) +} + +func FuzzXMLToJSON(f *testing.F) { + f.Add("Alice") + f.Add("1") + f.Add("") + f.Add("") + + f.Fuzz(func(t *testing.T, input string) { + if len(input) > 4096 { + t.Skip() + } + + output, err := XMLToJSON(input) + if err != nil { + return + } + + if !json.Valid([]byte(output)) { + t.Fatalf("XMLToJSON returned invalid JSON: %q", output) + } + }) +} + +func FuzzJSONToXML(f *testing.F) { + f.Add(`{"root":{"name":"Alice"}}`) + f.Add(`{"root":{"items":[1,2,3]}}`) + f.Add(`[]`) + f.Add("") + + f.Fuzz(func(t *testing.T, input string) { + if len(input) > 4096 { + t.Skip() + } + + output, err := JSONToXML(input) + if err != nil { + return + } + + if _, err := mxj.NewMapXml([]byte(output)); err != nil { + t.Fatalf("JSONToXML returned invalid XML: %v", err) + } + }) +} + +func FuzzYAMLToTOML(f *testing.F) { + f.Add("name: Alice\nage: 30\n") + f.Add("items:\n - one\n - two\n") + f.Add("{not: valid") + f.Add("") + + f.Fuzz(func(t *testing.T, input string) { + if len(input) > 4096 { + t.Skip() + } + + output, err := YAMLToTOML(input) + if err != nil { + return + } + + var data any + if err := toml.Unmarshal([]byte(output), &data); err != nil { + t.Fatalf("YAMLToTOML returned invalid TOML: %v", err) + } + }) +} + +func FuzzTOMLToYAML(f *testing.F) { + f.Add("name = \"Alice\"\nage = 30\n") + f.Add("[user]\nname = \"Bob\"\n") + f.Add("[[items]]\nname = \"one\"\n") + f.Add("") + + f.Fuzz(func(t *testing.T, input string) { + if len(input) > 4096 { + t.Skip() + } + + output, err := TOMLToYAML(input) + if err != nil { + return + } + + var data any + if err := yaml.Unmarshal([]byte(output), &data); err != nil { + t.Fatalf("TOMLToYAML returned invalid YAML: %v", err) + } + }) +} diff --git a/internal/converter/converter_test.go b/internal/converter/converter_test.go new file mode 100644 index 0000000..5809f43 --- /dev/null +++ b/internal/converter/converter_test.go @@ -0,0 +1,30 @@ +package converter + +import "testing" + +func TestJSONToXMLInvalidElementName(t *testing.T) { + t.Parallel() + + _, err := JSONToXML(`{"0":""}`) + if err == nil { + t.Fatal("expected error for invalid XML element name") + } +} + +func TestJSONToTOMLTopLevelArrayReturnsError(t *testing.T) { + t.Parallel() + + _, err := JSONToTOML(`[]`) + if err == nil { + t.Fatal("expected error for top-level array TOML encoding") + } +} + +func TestYAMLToTOMLScalarReturnsError(t *testing.T) { + t.Parallel() + + _, err := YAMLToTOML("00") + if err == nil { + t.Fatal("expected error for scalar TOML encoding") + } +} diff --git a/internal/converter/testdata/fuzz/FuzzJSONToXML/03b8b7151108b66b b/internal/converter/testdata/fuzz/FuzzJSONToXML/03b8b7151108b66b new file mode 100644 index 0000000..cf9f6a2 --- /dev/null +++ b/internal/converter/testdata/fuzz/FuzzJSONToXML/03b8b7151108b66b @@ -0,0 +1,2 @@ +go test fuzz v1 +string("{\"!\":{}}") diff --git a/internal/converter/testdata/fuzz/FuzzJSONToXML/9c9314d711b15762 b/internal/converter/testdata/fuzz/FuzzJSONToXML/9c9314d711b15762 new file mode 100644 index 0000000..1458670 --- /dev/null +++ b/internal/converter/testdata/fuzz/FuzzJSONToXML/9c9314d711b15762 @@ -0,0 +1,2 @@ +go test fuzz v1 +string("{\"0\":\"\"}") diff --git a/internal/converter/testdata/fuzz/FuzzYAMLToTOML/abff33899e7cd4f9 b/internal/converter/testdata/fuzz/FuzzYAMLToTOML/abff33899e7cd4f9 new file mode 100644 index 0000000..05e4004 --- /dev/null +++ b/internal/converter/testdata/fuzz/FuzzYAMLToTOML/abff33899e7cd4f9 @@ -0,0 +1,2 @@ +go test fuzz v1 +string("00") diff --git a/internal/csv2json/csv2json.go b/internal/csv2json/csv2json.go index 24fe81b..5b8f6f2 100644 --- a/internal/csv2json/csv2json.go +++ b/internal/csv2json/csv2json.go @@ -108,7 +108,7 @@ func arrayContentMatch(str string) (string, int) { i := strings.Index(str, "[") if i >= 0 { j := strings.Index(str, "]") - if j >= 0 { + if j > i { index, err := strconv.Atoi(str[i+1 : j]) if err != nil { return str, -1 diff --git a/internal/csv2json/csv2json_test.go b/internal/csv2json/csv2json_test.go new file mode 100644 index 0000000..8439364 --- /dev/null +++ b/internal/csv2json/csv2json_test.go @@ -0,0 +1,99 @@ +package csv2json + +import ( + "encoding/json" + "strings" + "testing" +) + +func convertNoPanic(t *testing.T, input string) (result string, err error) { + t.Helper() + + defer func() { + if r := recover(); r != nil { + t.Fatalf("Convert panicked: %v", r) + } + }() + + return Convert(input) +} + +func TestConvertConflictingHeadersReturnError(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + input string + }{ + { + name: "scalar_then_object", + input: "a,a.b\n1,2\n", + }, + { + name: "object_then_scalar", + input: "a.b,a\n1,2\n", + }, + { + name: "array_then_object", + input: "a[0],a.b\n1,2\n", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + + _, err := convertNoPanic(t, tt.input) + if err == nil { + t.Fatal("expected error for conflicting CSV headers") + } + }) + } +} + +func TestConvertHighArrayIndexDoesNotPanic(t *testing.T) { + t.Parallel() + + output, err := convertNoPanic(t, "items[2].name\nhello\n") + if err != nil { + t.Fatalf("Convert should not fail for sparse array indexes: %v", err) + } + + if !json.Valid([]byte(output)) { + t.Fatalf("expected valid JSON output, got: %s", output) + } + + if !strings.Contains(output, "hello") { + t.Fatalf("expected output to include converted value, got: %s", output) + } +} + +func TestConvertMalformedArrayHeaderDoesNotPanic(t *testing.T) { + t.Parallel() + + output, err := convertNoPanic(t, "][\n0\n") + if err != nil { + return + } + + if !json.Valid([]byte(output)) { + t.Fatalf("expected valid JSON output, got: %s", output) + } +} + +func FuzzConvertDoesNotPanic(f *testing.F) { + f.Add("name,age\\nAlice,30\\n") + f.Add("a,a.b\\n1,2\\n") + f.Add("items[2].name\\nhello\\n") + f.Add("") + + f.Fuzz(func(t *testing.T, input string) { + defer func() { + if r := recover(); r != nil { + t.Fatalf("Convert panicked for input %q: %v", input, r) + } + }() + + _, _ = Convert(input) + }) +} diff --git a/internal/csv2json/testdata/fuzz/FuzzConvertDoesNotPanic/92197bf3838b3f8b b/internal/csv2json/testdata/fuzz/FuzzConvertDoesNotPanic/92197bf3838b3f8b new file mode 100644 index 0000000..633e204 --- /dev/null +++ b/internal/csv2json/testdata/fuzz/FuzzConvertDoesNotPanic/92197bf3838b3f8b @@ -0,0 +1,2 @@ +go test fuzz v1 +string("][\n0") diff --git a/internal/htmlfmt/htmlfmt_fuzz_test.go b/internal/htmlfmt/htmlfmt_fuzz_test.go new file mode 100644 index 0000000..04b258c --- /dev/null +++ b/internal/htmlfmt/htmlfmt_fuzz_test.go @@ -0,0 +1,17 @@ +package htmlfmt + +import "testing" + +func FuzzFormatHTML(f *testing.F) { + f.Add("

Hello

") + f.Add("
missing end") + f.Add("") + + f.Fuzz(func(t *testing.T, input string) { + if len(input) > 4096 { + t.Skip() + } + + _ = Format(input) + }) +} diff --git a/internal/ui/base_pager_model.go b/internal/ui/base_pager_model.go index 5df9566..dd599ce 100644 --- a/internal/ui/base_pager_model.go +++ b/internal/ui/base_pager_model.go @@ -228,16 +228,12 @@ func (m *BasePagerModel) StatusBarView() string { // FormatHelpColumns formats the help view with columns func (m *BasePagerModel) FormatHelpColumns(col1 []string) string { s := "\n" - s += "k/↑ up " + col1[0] + "\n" - s += "j/↓ down " + col1[1] + "\n" - s += "b/pgup page up " + col1[2] + "\n" - s += "f/pgdn page down " + col1[3] + "\n" - s += "u ½ page up " + col1[4] + "\n" - s += "d ½ page down " - - if len(col1) > 5 { - s += col1[5] - } + s += "k/↑ up " + helpColumnValue(col1, 0) + "\n" + s += "j/↓ down " + helpColumnValue(col1, 1) + "\n" + s += "b/pgup page up " + helpColumnValue(col1, 2) + "\n" + s += "f/pgdn page down " + helpColumnValue(col1, 3) + "\n" + s += "u ½ page up " + helpColumnValue(col1, 4) + "\n" + s += "d ½ page down " + helpColumnValue(col1, 5) s = Indent(s, 2) @@ -255,3 +251,11 @@ func (m *BasePagerModel) FormatHelpColumns(col1 []string) string { return HelpViewStyle(s) } + +func helpColumnValue(columns []string, index int) string { + if index < 0 || index >= len(columns) { + return "" + } + + return columns[index] +} diff --git a/internal/ui/base_pager_model_test.go b/internal/ui/base_pager_model_test.go new file mode 100644 index 0000000..92be31a --- /dev/null +++ b/internal/ui/base_pager_model_test.go @@ -0,0 +1,27 @@ +package ui + +import "testing" + +func TestFormatHelpColumnsHandlesShortColumnList(t *testing.T) { + t.Parallel() + + m := BasePagerModel{Common: &CommonModel{Width: 80, Height: 24}} + + if m.FormatHelpColumns([]string{"copy", "edit"}) == "" { + t.Fatal("expected non-empty help output") + } +} + +func TestHelpColumnValueBounds(t *testing.T) { + t.Parallel() + + columns := []string{"a", "b"} + + if got := helpColumnValue(columns, 0); got != "a" { + t.Fatalf("expected first value to be 'a', got %q", got) + } + + if got := helpColumnValue(columns, 5); got != "" { + t.Fatalf("expected out-of-range value to be empty, got %q", got) + } +} diff --git a/internal/yamlfmt/yamlfmt_fuzz_test.go b/internal/yamlfmt/yamlfmt_fuzz_test.go new file mode 100644 index 0000000..eec3b3a --- /dev/null +++ b/internal/yamlfmt/yamlfmt_fuzz_test.go @@ -0,0 +1,30 @@ +package yamlfmt + +import ( + "testing" + + "gopkg.in/yaml.v3" +) + +func FuzzFormatYAML(f *testing.F) { + f.Add("name: Alice\nage: 30\n") + f.Add("items:\n - one\n - two\n") + f.Add("{not: valid") + f.Add("") + + f.Fuzz(func(t *testing.T, input string) { + if len(input) > 4096 { + t.Skip() + } + + output, err := Format(input) + if err != nil { + return + } + + var data any + if err := yaml.Unmarshal([]byte(output), &data); err != nil { + t.Fatalf("Format returned invalid YAML: %v", err) + } + }) +} diff --git a/tui/base64-decoder/main.go b/tui/base64-decoder/main.go index 97b38b2..9b8df26 100644 --- a/tui/base64-decoder/main.go +++ b/tui/base64-decoder/main.go @@ -135,7 +135,7 @@ func (m Base64Model) helpView() (s string) { s += "j/↓ down " + col1[1] + "\n" s += "b/pgup page up " + col1[2] + "\n" s += "f/pgdn page down " + col1[3] + "\n" - s += "u ½ page up " + col1[4] + "\n" + s += "u ½ page up " + "\n" s += "d ½ page down " if len(col1) > 5 { diff --git a/tui/base64-decoder/main_test.go b/tui/base64-decoder/main_test.go new file mode 100644 index 0000000..7dab144 --- /dev/null +++ b/tui/base64-decoder/main_test.go @@ -0,0 +1,16 @@ +package base64decoder + +import ( + "testing" + + "github.com/skatkov/devtui/internal/ui" +) + +func TestHelpViewDoesNotPanic(t *testing.T) { + t.Parallel() + + m := NewBase64Model(&ui.CommonModel{Width: 80, Height: 24}) + if m.helpView() == "" { + t.Fatal("expected non-empty help view") + } +} diff --git a/tui/base64-encoder/main.go b/tui/base64-encoder/main.go index a697f81..4e1b1ff 100644 --- a/tui/base64-encoder/main.go +++ b/tui/base64-encoder/main.go @@ -172,7 +172,7 @@ func (m Base64Model) helpView() (s string) { s += "j/↓ down " + col1[1] + "\n" s += "b/pgup page up " + col1[2] + "\n" s += "f/pgdn page down " + col1[3] + "\n" - s += "u ½ page up " + col1[4] + "\n" + s += "u ½ page up " + "\n" s += "d ½ page down " if len(col1) > 5 { diff --git a/tui/base64-encoder/main_test.go b/tui/base64-encoder/main_test.go new file mode 100644 index 0000000..88412ab --- /dev/null +++ b/tui/base64-encoder/main_test.go @@ -0,0 +1,16 @@ +package base64encoder + +import ( + "testing" + + "github.com/skatkov/devtui/internal/ui" +) + +func TestHelpViewDoesNotPanic(t *testing.T) { + t.Parallel() + + m := NewBase64Model(&ui.CommonModel{Width: 80, Height: 24}) + if m.helpView() == "" { + t.Fatal("expected non-empty help view") + } +} diff --git a/tui/json2toon/fuzz_test.go b/tui/json2toon/fuzz_test.go new file mode 100644 index 0000000..aca8652 --- /dev/null +++ b/tui/json2toon/fuzz_test.go @@ -0,0 +1,42 @@ +package json2toon + +import ( + "testing" + + "github.com/hannes-sistemica/toon" +) + +func FuzzConvertNoPanic(f *testing.F) { + f.Add(`{"name":"Alice"}`) + f.Add(`{"items":[1,2,3]}`) + f.Add(`{invalid`) + f.Add("") + + f.Fuzz(func(t *testing.T, input string) { + if len(input) > 4096 { + t.Skip() + } + + _, _ = Convert(input) + }) +} + +func FuzzConvertWithOptionsNoPanic(f *testing.F) { + f.Add(`{"name":"Alice"}`, uint8(2), "") + f.Add(`{"items":[{"id":1}]}`, uint8(4), "#") + f.Add(`{invalid`, uint8(0), "!") + + f.Fuzz(func(t *testing.T, input string, indent uint8, marker string) { + if len(input) > 4096 || len(marker) > 8 { + t.Skip() + } + + opts := toon.EncodeOptions{ + Indent: int(indent % 8), + Delimiter: ",", + LengthMarker: marker, + } + + _, _ = ConvertWithOptions(input, opts) + }) +} diff --git a/tui/jsonrepair/fuzz_test.go b/tui/jsonrepair/fuzz_test.go new file mode 100644 index 0000000..602b386 --- /dev/null +++ b/tui/jsonrepair/fuzz_test.go @@ -0,0 +1,28 @@ +package jsonrepair + +import ( + "encoding/json" + "testing" +) + +func FuzzRepairJSONProducesValidJSON(f *testing.F) { + f.Add(`{"name":"Alice"}`) + f.Add(`{'name':'Alice'}`) + f.Add("```json\n{'name': 'Alice'}\n```") + f.Add("") + + f.Fuzz(func(t *testing.T, input string) { + if len(input) > 4096 { + t.Skip() + } + + output, err := RepairJSON(input) + if err != nil { + return + } + + if !json.Valid([]byte(output)) { + t.Fatalf("RepairJSON returned invalid JSON: %q", output) + } + }) +} From de1d3b5c377da9e2d47d121f8d04fd8f5f5488b5 Mon Sep 17 00:00:00 2001 From: "Stanislav (Stas) Katkov" Date: Tue, 10 Mar 2026 13:54:46 +0100 Subject: [PATCH 2/4] improve data used in fuzzing --- internal/base64/base64_fuzz_test.go | 28 +++++++++++++++++++ internal/converter/converter_fuzz_test.go | 23 +++++++++++++++ internal/csv2json/csv2json_test.go | 14 ++++++++++ internal/htmlfmt/htmlfmt_fuzz_test.go | 18 +++++++++++- .../fuzz/FuzzFormatYAML/186423f1acb1ecfa | 2 ++ internal/yamlfmt/yamlfmt.go | 9 +++++- internal/yamlfmt/yamlfmt_fuzz_test.go | 14 ++++++++++ internal/yamlfmt/yamlfmt_test.go | 28 +++++++++++++++++++ tui/json2toon/fuzz_test.go | 26 +++++++++++++++++ tui/jsonrepair/fuzz_test.go | 14 ++++++++++ 10 files changed, 174 insertions(+), 2 deletions(-) create mode 100644 internal/yamlfmt/testdata/fuzz/FuzzFormatYAML/186423f1acb1ecfa create mode 100644 internal/yamlfmt/yamlfmt_test.go diff --git a/internal/base64/base64_fuzz_test.go b/internal/base64/base64_fuzz_test.go index 83faeef..ab192ca 100644 --- a/internal/base64/base64_fuzz_test.go +++ b/internal/base64/base64_fuzz_test.go @@ -3,15 +3,42 @@ package base64 import ( "bytes" stdbase64 "encoding/base64" + "os" + "path/filepath" "strings" "testing" ) +const fuzzSeedDir = "../../testdata" + +func addByteSeedsFromFiles(f *testing.F, fileNames ...string) { + for _, fileName := range fileNames { + content, err := os.ReadFile(filepath.Join(fuzzSeedDir, fileName)) + if err != nil { + continue + } + + f.Add(content) + } +} + +func addStringSeedsFromFiles(f *testing.F, fileNames ...string) { + for _, fileName := range fileNames { + content, err := os.ReadFile(filepath.Join(fuzzSeedDir, fileName)) + if err != nil { + continue + } + + f.Add(string(content)) + } +} + func FuzzBase64RoundTrip(f *testing.F) { f.Add([]byte("")) f.Add([]byte("hello world")) f.Add([]byte("\x00\x01\x02\xff\xfe")) f.Add([]byte("ñáéíóú 中文 🚀")) + addByteSeedsFromFiles(f, "sample.txt", "json.txt", "binary.txt", "example.json", "example.yaml") f.Fuzz(func(t *testing.T, data []byte) { encoded := Encode(data) @@ -42,6 +69,7 @@ func FuzzDecodeMatchesStdlibBehavior(f *testing.F) { f.Add(" SGVsbG8= ") f.Add("%%%") f.Add("YWJj\n") + addStringSeedsFromFiles(f, "sample.base64", "json.base64", "binary.base64", "invalid.base64") f.Fuzz(func(t *testing.T, input string) { trimmed := strings.TrimSpace(input) diff --git a/internal/converter/converter_fuzz_test.go b/internal/converter/converter_fuzz_test.go index 883fe23..6837334 100644 --- a/internal/converter/converter_fuzz_test.go +++ b/internal/converter/converter_fuzz_test.go @@ -2,6 +2,8 @@ package converter import ( "encoding/json" + "os" + "path/filepath" "testing" "github.com/clbanning/mxj/v2" @@ -9,11 +11,25 @@ import ( "gopkg.in/yaml.v3" ) +const fuzzSeedDir = "../../testdata" + +func addStringSeedFiles(f *testing.F, fileNames ...string) { + for _, fileName := range fileNames { + content, err := os.ReadFile(filepath.Join(fuzzSeedDir, fileName)) + if err != nil { + continue + } + + f.Add(string(content)) + } +} + func FuzzYAMLToJSON(f *testing.F) { f.Add("name: Alice\nage: 30\n") f.Add("items:\n - one\n - two\n") f.Add("{not: valid") f.Add("") + addStringSeedFiles(f, "example.yaml", "nested.yaml") f.Fuzz(func(t *testing.T, input string) { if len(input) > 4096 { @@ -36,6 +52,7 @@ func FuzzJSONToYAML(f *testing.F) { f.Add(`[1,2,3]`) f.Add(`{invalid`) f.Add("") + addStringSeedFiles(f, "example.json", "nested.json", "json-with-urls.json") f.Fuzz(func(t *testing.T, input string) { if len(input) > 4096 { @@ -59,6 +76,7 @@ func FuzzTOMLToJSON(f *testing.F) { f.Add("[user]\nname = \"Bob\"\n") f.Add("[[items]]\nname = \"one\"\n") f.Add("") + addStringSeedFiles(f, "example.toml") f.Fuzz(func(t *testing.T, input string) { if len(input) > 4096 { @@ -81,6 +99,7 @@ func FuzzJSONToTOML(f *testing.F) { f.Add(`{"items":[{"name":"one"}]}`) f.Add(`[]`) f.Add("") + addStringSeedFiles(f, "example.json", "nested.json", "json-with-urls.json") f.Fuzz(func(t *testing.T, input string) { if len(input) > 4096 { @@ -104,6 +123,7 @@ func FuzzXMLToJSON(f *testing.F) { f.Add("1") f.Add("") f.Add("") + addStringSeedFiles(f, "example.xml") f.Fuzz(func(t *testing.T, input string) { if len(input) > 4096 { @@ -126,6 +146,7 @@ func FuzzJSONToXML(f *testing.F) { f.Add(`{"root":{"items":[1,2,3]}}`) f.Add(`[]`) f.Add("") + addStringSeedFiles(f, "example.json", "nested.json", "json-with-urls.json") f.Fuzz(func(t *testing.T, input string) { if len(input) > 4096 { @@ -148,6 +169,7 @@ func FuzzYAMLToTOML(f *testing.F) { f.Add("items:\n - one\n - two\n") f.Add("{not: valid") f.Add("") + addStringSeedFiles(f, "example.yaml", "nested.yaml") f.Fuzz(func(t *testing.T, input string) { if len(input) > 4096 { @@ -171,6 +193,7 @@ func FuzzTOMLToYAML(f *testing.F) { f.Add("[user]\nname = \"Bob\"\n") f.Add("[[items]]\nname = \"one\"\n") f.Add("") + addStringSeedFiles(f, "example.toml") f.Fuzz(func(t *testing.T, input string) { if len(input) > 4096 { diff --git a/internal/csv2json/csv2json_test.go b/internal/csv2json/csv2json_test.go index 8439364..0874fe3 100644 --- a/internal/csv2json/csv2json_test.go +++ b/internal/csv2json/csv2json_test.go @@ -2,10 +2,23 @@ package csv2json import ( "encoding/json" + "os" + "path/filepath" "strings" "testing" ) +func addCSVSeedsFromFiles(f *testing.F, fileNames ...string) { + for _, fileName := range fileNames { + content, err := os.ReadFile(filepath.Join("../../testdata", fileName)) + if err != nil { + continue + } + + f.Add(string(content)) + } +} + func convertNoPanic(t *testing.T, input string) (result string, err error) { t.Helper() @@ -86,6 +99,7 @@ func FuzzConvertDoesNotPanic(f *testing.F) { f.Add("a,a.b\\n1,2\\n") f.Add("items[2].name\\nhello\\n") f.Add("") + addCSVSeedsFromFiles(f, "example.csv") f.Fuzz(func(t *testing.T, input string) { defer func() { diff --git a/internal/htmlfmt/htmlfmt_fuzz_test.go b/internal/htmlfmt/htmlfmt_fuzz_test.go index 04b258c..4a19c62 100644 --- a/internal/htmlfmt/htmlfmt_fuzz_test.go +++ b/internal/htmlfmt/htmlfmt_fuzz_test.go @@ -1,11 +1,27 @@ package htmlfmt -import "testing" +import ( + "os" + "path/filepath" + "testing" +) + +func addHTMLSeedsFromFiles(f *testing.F, fileNames ...string) { + for _, fileName := range fileNames { + content, err := os.ReadFile(filepath.Join("../../testdata", fileName)) + if err != nil { + continue + } + + f.Add(string(content)) + } +} func FuzzFormatHTML(f *testing.F) { f.Add("

Hello

") f.Add("
missing end") f.Add("") + addHTMLSeedsFromFiles(f, "html-with-urls.html") f.Fuzz(func(t *testing.T, input string) { if len(input) > 4096 { diff --git a/internal/yamlfmt/testdata/fuzz/FuzzFormatYAML/186423f1acb1ecfa b/internal/yamlfmt/testdata/fuzz/FuzzFormatYAML/186423f1acb1ecfa new file mode 100644 index 0000000..5ead74c --- /dev/null +++ b/internal/yamlfmt/testdata/fuzz/FuzzFormatYAML/186423f1acb1ecfa @@ -0,0 +1,2 @@ +go test fuzz v1 +string("0: \n0.:") diff --git a/internal/yamlfmt/yamlfmt.go b/internal/yamlfmt/yamlfmt.go index 0c003d7..9179a79 100644 --- a/internal/yamlfmt/yamlfmt.go +++ b/internal/yamlfmt/yamlfmt.go @@ -2,6 +2,7 @@ package yamlfmt import ( "bytes" + "fmt" "gopkg.in/yaml.v3" ) @@ -22,5 +23,11 @@ func Format(content string) (string, error) { return "", err } - return buf.String(), nil + formatted := buf.String() + var validation any + if err := yaml.Unmarshal([]byte(formatted), &validation); err != nil { + return "", fmt.Errorf("formatted YAML is invalid: %w", err) + } + + return formatted, nil } diff --git a/internal/yamlfmt/yamlfmt_fuzz_test.go b/internal/yamlfmt/yamlfmt_fuzz_test.go index eec3b3a..2d7f1f5 100644 --- a/internal/yamlfmt/yamlfmt_fuzz_test.go +++ b/internal/yamlfmt/yamlfmt_fuzz_test.go @@ -1,16 +1,30 @@ package yamlfmt import ( + "os" + "path/filepath" "testing" "gopkg.in/yaml.v3" ) +func addYAMLSeedsFromFiles(f *testing.F, fileNames ...string) { + for _, fileName := range fileNames { + content, err := os.ReadFile(filepath.Join("../../testdata", fileName)) + if err != nil { + continue + } + + f.Add(string(content)) + } +} + func FuzzFormatYAML(f *testing.F) { f.Add("name: Alice\nage: 30\n") f.Add("items:\n - one\n - two\n") f.Add("{not: valid") f.Add("") + addYAMLSeedsFromFiles(f, "example.yaml", "nested.yaml") f.Fuzz(func(t *testing.T, input string) { if len(input) > 4096 { diff --git a/internal/yamlfmt/yamlfmt_test.go b/internal/yamlfmt/yamlfmt_test.go new file mode 100644 index 0000000..d4b8bd9 --- /dev/null +++ b/internal/yamlfmt/yamlfmt_test.go @@ -0,0 +1,28 @@ +package yamlfmt + +import ( + "strings" + "testing" +) + +func TestFormatValidYAML(t *testing.T) { + t.Parallel() + + formatted, err := Format("name: Alice\nage: 30\n") + if err != nil { + t.Fatalf("Format returned error for valid YAML: %v", err) + } + + if !strings.Contains(formatted, "name: Alice") { + t.Fatalf("unexpected formatted YAML output: %q", formatted) + } +} + +func TestFormatReturnsErrorForInvalidFormattedYAML(t *testing.T) { + t.Parallel() + + _, err := Format("0: \n0.:") + if err == nil { + t.Fatal("expected error for YAML that formats into invalid output") + } +} diff --git a/tui/json2toon/fuzz_test.go b/tui/json2toon/fuzz_test.go index aca8652..db122be 100644 --- a/tui/json2toon/fuzz_test.go +++ b/tui/json2toon/fuzz_test.go @@ -1,16 +1,41 @@ package json2toon import ( + "os" + "path/filepath" "testing" "github.com/hannes-sistemica/toon" ) +func addJSONSeedsFromFiles(f *testing.F, fileNames ...string) { + for _, fileName := range fileNames { + content, err := os.ReadFile(filepath.Join("../../testdata", fileName)) + if err != nil { + continue + } + + f.Add(string(content)) + } +} + +func addJSONOptionSeedsFromFiles(f *testing.F, fileNames ...string) { + for _, fileName := range fileNames { + content, err := os.ReadFile(filepath.Join("../../testdata", fileName)) + if err != nil { + continue + } + + f.Add(string(content), uint8(2), "") + } +} + func FuzzConvertNoPanic(f *testing.F) { f.Add(`{"name":"Alice"}`) f.Add(`{"items":[1,2,3]}`) f.Add(`{invalid`) f.Add("") + addJSONSeedsFromFiles(f, "example.json", "nested.json", "json-with-urls.json") f.Fuzz(func(t *testing.T, input string) { if len(input) > 4096 { @@ -25,6 +50,7 @@ func FuzzConvertWithOptionsNoPanic(f *testing.F) { f.Add(`{"name":"Alice"}`, uint8(2), "") f.Add(`{"items":[{"id":1}]}`, uint8(4), "#") f.Add(`{invalid`, uint8(0), "!") + addJSONOptionSeedsFromFiles(f, "example.json", "nested.json", "json-with-urls.json") f.Fuzz(func(t *testing.T, input string, indent uint8, marker string) { if len(input) > 4096 || len(marker) > 8 { diff --git a/tui/jsonrepair/fuzz_test.go b/tui/jsonrepair/fuzz_test.go index 602b386..f4f1dee 100644 --- a/tui/jsonrepair/fuzz_test.go +++ b/tui/jsonrepair/fuzz_test.go @@ -2,14 +2,28 @@ package jsonrepair import ( "encoding/json" + "os" + "path/filepath" "testing" ) +func addJSONSeedsFromFiles(f *testing.F, fileNames ...string) { + for _, fileName := range fileNames { + content, err := os.ReadFile(filepath.Join("../../testdata", fileName)) + if err != nil { + continue + } + + f.Add(string(content)) + } +} + func FuzzRepairJSONProducesValidJSON(f *testing.F) { f.Add(`{"name":"Alice"}`) f.Add(`{'name':'Alice'}`) f.Add("```json\n{'name': 'Alice'}\n```") f.Add("") + addJSONSeedsFromFiles(f, "example.json", "nested.json", "json-with-urls.json") f.Fuzz(func(t *testing.T, input string) { if len(input) > 4096 { From 9e43bcef117b89d963dc0f057defb22957c808ba Mon Sep 17 00:00:00 2001 From: "Stanislav (Stas) Katkov" Date: Tue, 10 Mar 2026 13:57:26 +0100 Subject: [PATCH 3/4] remove reproducers --- internal/converter/testdata/fuzz/FuzzJSONToXML/03b8b7151108b66b | 2 -- internal/converter/testdata/fuzz/FuzzJSONToXML/9c9314d711b15762 | 2 -- .../converter/testdata/fuzz/FuzzYAMLToTOML/abff33899e7cd4f9 | 2 -- .../testdata/fuzz/FuzzConvertDoesNotPanic/92197bf3838b3f8b | 2 -- internal/yamlfmt/testdata/fuzz/FuzzFormatYAML/186423f1acb1ecfa | 2 -- 5 files changed, 10 deletions(-) delete mode 100644 internal/converter/testdata/fuzz/FuzzJSONToXML/03b8b7151108b66b delete mode 100644 internal/converter/testdata/fuzz/FuzzJSONToXML/9c9314d711b15762 delete mode 100644 internal/converter/testdata/fuzz/FuzzYAMLToTOML/abff33899e7cd4f9 delete mode 100644 internal/csv2json/testdata/fuzz/FuzzConvertDoesNotPanic/92197bf3838b3f8b delete mode 100644 internal/yamlfmt/testdata/fuzz/FuzzFormatYAML/186423f1acb1ecfa diff --git a/internal/converter/testdata/fuzz/FuzzJSONToXML/03b8b7151108b66b b/internal/converter/testdata/fuzz/FuzzJSONToXML/03b8b7151108b66b deleted file mode 100644 index cf9f6a2..0000000 --- a/internal/converter/testdata/fuzz/FuzzJSONToXML/03b8b7151108b66b +++ /dev/null @@ -1,2 +0,0 @@ -go test fuzz v1 -string("{\"!\":{}}") diff --git a/internal/converter/testdata/fuzz/FuzzJSONToXML/9c9314d711b15762 b/internal/converter/testdata/fuzz/FuzzJSONToXML/9c9314d711b15762 deleted file mode 100644 index 1458670..0000000 --- a/internal/converter/testdata/fuzz/FuzzJSONToXML/9c9314d711b15762 +++ /dev/null @@ -1,2 +0,0 @@ -go test fuzz v1 -string("{\"0\":\"\"}") diff --git a/internal/converter/testdata/fuzz/FuzzYAMLToTOML/abff33899e7cd4f9 b/internal/converter/testdata/fuzz/FuzzYAMLToTOML/abff33899e7cd4f9 deleted file mode 100644 index 05e4004..0000000 --- a/internal/converter/testdata/fuzz/FuzzYAMLToTOML/abff33899e7cd4f9 +++ /dev/null @@ -1,2 +0,0 @@ -go test fuzz v1 -string("00") diff --git a/internal/csv2json/testdata/fuzz/FuzzConvertDoesNotPanic/92197bf3838b3f8b b/internal/csv2json/testdata/fuzz/FuzzConvertDoesNotPanic/92197bf3838b3f8b deleted file mode 100644 index 633e204..0000000 --- a/internal/csv2json/testdata/fuzz/FuzzConvertDoesNotPanic/92197bf3838b3f8b +++ /dev/null @@ -1,2 +0,0 @@ -go test fuzz v1 -string("][\n0") diff --git a/internal/yamlfmt/testdata/fuzz/FuzzFormatYAML/186423f1acb1ecfa b/internal/yamlfmt/testdata/fuzz/FuzzFormatYAML/186423f1acb1ecfa deleted file mode 100644 index 5ead74c..0000000 --- a/internal/yamlfmt/testdata/fuzz/FuzzFormatYAML/186423f1acb1ecfa +++ /dev/null @@ -1,2 +0,0 @@ -go test fuzz v1 -string("0: \n0.:") From 2c87583de88bd7a7778fde4c1d8b314ff6eba9f8 Mon Sep 17 00:00:00 2001 From: "Stanislav (Stas) Katkov" Date: Tue, 10 Mar 2026 13:58:03 +0100 Subject: [PATCH 4/4] Apply suggestion from @Copilot Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- internal/csv2json/csv2json_test.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/internal/csv2json/csv2json_test.go b/internal/csv2json/csv2json_test.go index 0874fe3..e189792 100644 --- a/internal/csv2json/csv2json_test.go +++ b/internal/csv2json/csv2json_test.go @@ -95,9 +95,9 @@ func TestConvertMalformedArrayHeaderDoesNotPanic(t *testing.T) { } func FuzzConvertDoesNotPanic(f *testing.F) { - f.Add("name,age\\nAlice,30\\n") - f.Add("a,a.b\\n1,2\\n") - f.Add("items[2].name\\nhello\\n") + f.Add("name,age\nAlice,30\n") + f.Add("a,a.b\n1,2\n") + f.Add("items[2].name\nhello\n") f.Add("") addCSVSeedsFromFiles(f, "example.csv")