Source file src/encoding/csv/fuzz_test.go

     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  
     5  package csv
     6  
     7  import (
     8  	"bytes"
     9  	"reflect"
    10  	"slices"
    11  	"strings"
    12  	"testing"
    13  )
    14  
    15  func 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  

View as plain text