github.com/honeycombio/honeytail@v1.9.0/parsers/csv/csv_test.go (about) 1 package csv 2 3 import ( 4 "reflect" 5 "regexp" 6 "testing" 7 "time" 8 9 "github.com/stretchr/testify/assert" 10 11 "github.com/honeycombio/honeytail/event" 12 "github.com/honeycombio/honeytail/parsers" 13 ) 14 15 const ( 16 commonLogFormatTimeLayout = "02/Jan/2006:15:04:05 -0700" 17 iso8601TimeLayout = "2006-01-02T15:04:05-07:00" 18 ) 19 20 // Test Init(...) success/fail 21 22 type testInitMap struct { 23 options *Options 24 expectedPass bool 25 } 26 27 var testInitCases = []testInitMap{ 28 { 29 expectedPass: true, 30 options: &Options{ 31 NumParsers: 5, 32 TimeFieldName: "local_time", 33 TimeFieldFormat: "%d/%b/%Y:%H:%M:%S %z", 34 Fields: "one, two, three", 35 }, 36 }, 37 { 38 expectedPass: false, 39 options: &Options{ 40 NumParsers: 5, 41 TimeFieldName: "local_time", 42 TimeFieldFormat: "%d/%b/%Y:%H:%M:%S %z", 43 Fields: "", // No fields specified should cause a failure 44 }, 45 }, 46 } 47 48 func TestInit(t *testing.T) { 49 for _, testCase := range testInitCases { 50 p := &Parser{} 51 err := p.Init(testCase.options) 52 if (err == nil) != testCase.expectedPass { 53 if err == nil { 54 t.Error("Parser Init(...) passed; expected it to fail.") 55 } else { 56 t.Error("Parser Init(...) failed; expected it to pass. Error:", err) 57 } 58 } else { 59 t.Logf("Init pass status is %t as expected", (err == nil)) 60 } 61 } 62 } 63 64 type testLineMap struct { 65 fields string 66 input string 67 expected map[string]interface{} 68 err bool 69 } 70 71 var tlms = []testLineMap{ 72 { 73 fields: "one,two,three", 74 input: "exx, why, zee", 75 expected: map[string]interface{}{ 76 "one": "exx", 77 "two": " why", 78 "three": " zee", 79 }, 80 }, 81 { 82 // No data should return of error (field num mismatch) 83 fields: "one,two,three", 84 input: "", 85 expected: nil, 86 err: true, 87 }, 88 { 89 // Too few fields should return an error 90 fields: "one,two,three", 91 input: "foo,bar", 92 expected: nil, 93 err: true, 94 }, 95 { 96 // Too many fields should return an error 97 fields: "one, two", 98 input: "foo,bar,xyz", 99 expected: nil, 100 err: true, 101 }, 102 { 103 // Test that int and float are converted successfully 104 fields: "one,two,three", 105 input: "1,2.4,xyz", 106 expected: map[string]interface{}{ 107 "one": 1, 108 "two": 2.4, 109 "three": "xyz", 110 }, 111 }, 112 } 113 114 func TestParseLine(t *testing.T) { 115 for _, tlm := range tlms { 116 p := &Parser{} 117 err := p.Init(&Options{ 118 Fields: tlm.fields, 119 }) 120 assert.NoError(t, err, "could not instantiate parser with fields: %s", tlm.fields) 121 resp, err := p.lineParser.ParseLine(tlm.input) 122 t.Logf("%+v", resp) 123 if tlm.err { 124 assert.Error(t, err, "p.ParseLine did not return error as expected") 125 } else { 126 assert.NoError(t, err, "p.ParseLine unexpectedly returned error %v", err) 127 } 128 if !reflect.DeepEqual(resp, tlm.expected) { 129 t.Errorf("response %+v didn't match expected %+v", resp, tlm.expected) 130 } 131 } 132 } 133 134 func TestParseLineTrimWhitespace(t *testing.T) { 135 tlm := testLineMap{ 136 fields: "one, two, three", 137 input: "exx, why, zee", 138 expected: map[string]interface{}{ 139 "one": "exx", 140 "two": "why", 141 "three": "zee", 142 }, 143 } 144 145 p := &Parser{} 146 err := p.Init(&Options{ 147 Fields: tlm.fields, 148 TrimLeadingSpace: true, 149 }) 150 assert.NoError(t, err, "could not instantiate parser with fields: %s", tlm.fields) 151 resp, err := p.lineParser.ParseLine(tlm.input) 152 t.Logf("%+v", resp) 153 if tlm.err { 154 assert.Error(t, err, "p.ParseLine did not return error as expected") 155 } else { 156 assert.NoError(t, err, "p.ParseLine unexpectedly returned error %v", err) 157 } 158 if !reflect.DeepEqual(resp, tlm.expected) { 159 t.Errorf("response %+v didn't match expected %+v", resp, tlm.expected) 160 } 161 } 162 163 type testLineMaps struct { 164 line string 165 trimmedLine string 166 resp map[string]interface{} 167 typedResp map[string]interface{} 168 ev event.Event 169 } 170 171 // Test event emitted from ProcessLines 172 func TestProcessLines(t *testing.T) { 173 t1, _ := time.ParseInLocation(commonLogFormatTimeLayout, "08/Oct/2015:00:26:26 -0000", time.UTC) 174 preReg := &parsers.ExtRegexp{regexp.MustCompile("^(?P<pre_hostname>[a-zA-Z-.]+): ")} 175 tlm := []testLineMaps{ 176 { 177 line: "somehost: 08/Oct/2015:00:26:26 +0000,123,xyz,:::", 178 ev: event.Event{ 179 Timestamp: t1, 180 Data: map[string]interface{}{ 181 "pre_hostname": "somehost", 182 "one": 123, 183 "two": "xyz", 184 "three": ":::", 185 }, 186 }, 187 }, 188 } 189 p := &Parser{} 190 err := p.Init(&Options{ 191 NumParsers: 5, 192 TimeFieldName: "local_time", 193 TimeFieldFormat: "%d/%b/%Y:%H:%M:%S %z", 194 Fields: "local_time,one,two,three", 195 }) 196 assert.NoError(t, err, "Couldn't instantiate Parser") 197 198 lines := make(chan string) 199 send := make(chan event.Event) 200 go func() { 201 for _, pair := range tlm { 202 lines <- pair.line 203 } 204 close(lines) 205 }() 206 go p.ProcessLines(lines, send, preReg) 207 for _, pair := range tlm { 208 resp := <-send 209 if !reflect.DeepEqual(resp, pair.ev) { 210 t.Fatalf("line resp didn't match up for %s. Expected: %+v, actual: %+v", 211 pair.line, pair.ev, resp) 212 } 213 } 214 }