blob: 6342fa416db3ea3a14a7c31bdf2ee616c909a9d5 [file] [log] [blame]
// 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)
}
}
})
}