+++ /dev/null
-// Copyright 2019 The Go Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE file.
-
-//go:build gofuzz
-
-package csv
-
-import (
- "bytes"
- "fmt"
- "reflect"
-)
-
-func Fuzz(data []byte) int {
- score := 0
- buf := new(bytes.Buffer)
-
- for _, tt := range []Reader{
- {},
- {Comma: ';'},
- {Comma: '\t'},
- {LazyQuotes: true},
- {TrimLeadingSpace: true},
- {Comment: '#'},
- {Comment: ';'},
- } {
- r := NewReader(bytes.NewReader(data))
- r.Comma = tt.Comma
- r.Comment = tt.Comment
- r.LazyQuotes = tt.LazyQuotes
- r.TrimLeadingSpace = tt.TrimLeadingSpace
-
- records, err := r.ReadAll()
- if err != nil {
- continue
- }
- score = 1
-
- buf.Reset()
- w := NewWriter(buf)
- w.Comma = tt.Comma
- err = w.WriteAll(records)
- if err != nil {
- fmt.Printf("writer = %#v\n", w)
- fmt.Printf("records = %v\n", records)
- panic(err)
- }
-
- r = NewReader(buf)
- r.Comma = tt.Comma
- r.Comment = tt.Comment
- r.LazyQuotes = tt.LazyQuotes
- r.TrimLeadingSpace = tt.TrimLeadingSpace
- result, err := r.ReadAll()
- if err != nil {
- fmt.Printf("reader = %#v\n", r)
- fmt.Printf("records = %v\n", records)
- panic(err)
- }
-
- if !reflect.DeepEqual(records, result) {
- fmt.Println("records = \n", records)
- fmt.Println("result = \n", records)
- panic("not equal")
- }
- }
-
- return score
-}
--- /dev/null
+// Copyright 2019 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package csv
+
+import (
+ "bytes"
+ "reflect"
+ "slices"
+ "strings"
+ "testing"
+)
+
+func FuzzRoundtrip(f *testing.F) {
+ f.Fuzz(func(t *testing.T, in []byte) {
+ buf := new(bytes.Buffer)
+
+ t.Logf("input = %q", in)
+ for _, tt := range []Reader{
+ {Comma: ','},
+ {Comma: ';'},
+ {Comma: '\t'},
+ {Comma: ',', LazyQuotes: true},
+ {Comma: ',', TrimLeadingSpace: true},
+ {Comma: ',', Comment: '#'},
+ {Comma: ',', Comment: ';'},
+ } {
+ t.Logf("With options:")
+ t.Logf(" Comma = %q", tt.Comma)
+ t.Logf(" LazyQuotes = %t", tt.LazyQuotes)
+ t.Logf(" TrimLeadingSpace = %t", tt.TrimLeadingSpace)
+ t.Logf(" Comment = %q", tt.Comment)
+ r := NewReader(bytes.NewReader(in))
+ r.Comma = tt.Comma
+ r.Comment = tt.Comment
+ r.LazyQuotes = tt.LazyQuotes
+ r.TrimLeadingSpace = tt.TrimLeadingSpace
+
+ records, err := r.ReadAll()
+ if err != nil {
+ continue
+ }
+ t.Logf("first records = %#v", records)
+
+ buf.Reset()
+ w := NewWriter(buf)
+ w.Comma = tt.Comma
+ err = w.WriteAll(records)
+ if err != nil {
+ t.Logf("writer = %#v\n", w)
+ t.Logf("records = %v\n", records)
+ t.Fatal(err)
+ }
+ if tt.Comment != 0 {
+ // Writer doesn't support comments, so it can turn the quoted record "#"
+ // into a non-quoted comment line, failing the roundtrip check below.
+ continue
+ }
+ t.Logf("second input = %q", buf.Bytes())
+
+ r = NewReader(buf)
+ r.Comma = tt.Comma
+ r.Comment = tt.Comment
+ r.LazyQuotes = tt.LazyQuotes
+ r.TrimLeadingSpace = tt.TrimLeadingSpace
+ result, err := r.ReadAll()
+ if err != nil {
+ t.Logf("reader = %#v\n", r)
+ t.Logf("records = %v\n", records)
+ t.Fatal(err)
+ }
+
+ // The reader turns \r\n into \n.
+ for _, record := range records {
+ for i, s := range record {
+ record[i] = strings.ReplaceAll(s, "\r\n", "\n")
+ }
+ }
+ // Note that the reader parses the quoted record "" as an empty string,
+ // and the writer turns that into an empty line, which the reader skips over.
+ // Filter those out to avoid false positives.
+ records = slices.DeleteFunc(records, func(record []string) bool {
+ return len(record) == 1 && record[0] == ""
+ })
+ // The reader uses nil when returning no records at all.
+ if len(records) == 0 {
+ records = nil
+ }
+
+ if !reflect.DeepEqual(records, result) {
+ t.Fatalf("first read got %#v, second got %#v", records, result)
+ }
+ }
+ })
+}