1// Copyright 2019 The Go Authors. All rights reserved. 2// Use of this source code is governed by a BSD-style 3// license that can be found in the LICENSE file. 4 5package csv 6 7import ( 8 "bytes" 9 "reflect" 10 "slices" 11 "strings" 12 "testing" 13) 14 15func FuzzRoundtrip(f *testing.F) { 16 f.Fuzz(func(t *testing.T, in []byte) { 17 buf := new(bytes.Buffer) 18 19 t.Logf("input = %q", in) 20 for _, tt := range []Reader{ 21 {Comma: ','}, 22 {Comma: ';'}, 23 {Comma: '\t'}, 24 {Comma: ',', LazyQuotes: true}, 25 {Comma: ',', TrimLeadingSpace: true}, 26 {Comma: ',', Comment: '#'}, 27 {Comma: ',', Comment: ';'}, 28 } { 29 t.Logf("With options:") 30 t.Logf(" Comma = %q", tt.Comma) 31 t.Logf(" LazyQuotes = %t", tt.LazyQuotes) 32 t.Logf(" TrimLeadingSpace = %t", tt.TrimLeadingSpace) 33 t.Logf(" Comment = %q", tt.Comment) 34 r := NewReader(bytes.NewReader(in)) 35 r.Comma = tt.Comma 36 r.Comment = tt.Comment 37 r.LazyQuotes = tt.LazyQuotes 38 r.TrimLeadingSpace = tt.TrimLeadingSpace 39 40 records, err := r.ReadAll() 41 if err != nil { 42 continue 43 } 44 t.Logf("first records = %#v", records) 45 46 buf.Reset() 47 w := NewWriter(buf) 48 w.Comma = tt.Comma 49 err = w.WriteAll(records) 50 if err != nil { 51 t.Logf("writer = %#v\n", w) 52 t.Logf("records = %v\n", records) 53 t.Fatal(err) 54 } 55 if tt.Comment != 0 { 56 // Writer doesn't support comments, so it can turn the quoted record "#" 57 // into a non-quoted comment line, failing the roundtrip check below. 58 continue 59 } 60 t.Logf("second input = %q", buf.Bytes()) 61 62 r = NewReader(buf) 63 r.Comma = tt.Comma 64 r.Comment = tt.Comment 65 r.LazyQuotes = tt.LazyQuotes 66 r.TrimLeadingSpace = tt.TrimLeadingSpace 67 result, err := r.ReadAll() 68 if err != nil { 69 t.Logf("reader = %#v\n", r) 70 t.Logf("records = %v\n", records) 71 t.Fatal(err) 72 } 73 74 // The reader turns \r\n into \n. 75 for _, record := range records { 76 for i, s := range record { 77 record[i] = strings.ReplaceAll(s, "\r\n", "\n") 78 } 79 } 80 // Note that the reader parses the quoted record "" as an empty string, 81 // and the writer turns that into an empty line, which the reader skips over. 82 // Filter those out to avoid false positives. 83 records = slices.DeleteFunc(records, func(record []string) bool { 84 return len(record) == 1 && record[0] == "" 85 }) 86 // The reader uses nil when returning no records at all. 87 if len(records) == 0 { 88 records = nil 89 } 90 91 if !reflect.DeepEqual(records, result) { 92 t.Fatalf("first read got %#v, second got %#v", records, result) 93 } 94 } 95 }) 96} 97