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  }