github.com/netdata/go.d.plugin@v0.58.1/pkg/logs/csv_test.go (about)

     1  // SPDX-License-Identifier: GPL-3.0-or-later
     2  
     3  package logs
     4  
     5  import (
     6  	"strings"
     7  	"testing"
     8  
     9  	"github.com/stretchr/testify/assert"
    10  	"github.com/stretchr/testify/require"
    11  )
    12  
    13  var testCSVConfig = CSVConfig{
    14  	Delimiter: " ",
    15  	Format:    "$A %B",
    16  }
    17  
    18  func TestNewCSVParser(t *testing.T) {
    19  	tests := []struct {
    20  		name    string
    21  		format  string
    22  		wantErr bool
    23  	}{
    24  		{name: "valid format", format: "$A $B"},
    25  		{name: "empty format", wantErr: true},
    26  		{name: "bad format: csv read error", format: "$A $B \"$C", wantErr: true},
    27  		{name: "bad format: duplicate fields", format: "$A $A", wantErr: true},
    28  		{name: "bad format: zero fields", format: "!A !B", wantErr: true},
    29  	}
    30  
    31  	for _, tt := range tests {
    32  		t.Run(tt.name, func(t *testing.T) {
    33  			c := testCSVConfig
    34  			c.Format = tt.format
    35  			p, err := NewCSVParser(c, nil)
    36  			if tt.wantErr {
    37  				assert.Error(t, err)
    38  				assert.Nil(t, p)
    39  			} else {
    40  				assert.NoError(t, err)
    41  				assert.NotNil(t, p)
    42  			}
    43  		})
    44  	}
    45  }
    46  
    47  func TestNewCSVFormat(t *testing.T) {
    48  	tests := []struct {
    49  		format     string
    50  		wantFormat csvFormat
    51  		wantErr    bool
    52  	}{
    53  		{format: "$A $B", wantFormat: csvFormat{maxIndex: 1, fields: []csvField{{"$A", 0}, {"$B", 1}}}},
    54  		{format: "$A $B !C $E", wantFormat: csvFormat{maxIndex: 3, fields: []csvField{{"$A", 0}, {"$B", 1}, {"$E", 3}}}},
    55  		{format: "!A !B !C $E", wantFormat: csvFormat{maxIndex: 3, fields: []csvField{{"$E", 3}}}},
    56  		{format: "$A $OFFSET $B", wantFormat: csvFormat{maxIndex: 3, fields: []csvField{{"$A", 0}, {"$B", 3}}}},
    57  		{format: "$A $OFFSET $B $OFFSET !A", wantFormat: csvFormat{maxIndex: 3, fields: []csvField{{"$A", 0}, {"$B", 3}}}},
    58  		{format: "$A $OFFSET $OFFSET $B", wantFormat: csvFormat{maxIndex: 5, fields: []csvField{{"$A", 0}, {"$B", 5}}}},
    59  		{format: "$OFFSET $A $OFFSET $B", wantFormat: csvFormat{maxIndex: 5, fields: []csvField{{"$A", 2}, {"$B", 5}}}},
    60  		{format: "$A \"$A", wantErr: true},
    61  		{format: "$A $A", wantErr: true},
    62  		{format: "!A !A", wantErr: true},
    63  		{format: "", wantErr: true},
    64  	}
    65  
    66  	for _, tt := range tests {
    67  		t.Run(tt.format, func(t *testing.T) {
    68  			c := testCSVConfig
    69  			c.Format = tt.format
    70  			c.CheckField = testCheckCSVFormatField
    71  			tt.wantFormat.raw = tt.format
    72  
    73  			f, err := newCSVFormat(c)
    74  
    75  			if tt.wantErr {
    76  				assert.Error(t, err)
    77  				assert.Nil(t, f)
    78  			} else {
    79  				assert.NoError(t, err)
    80  				assert.Equal(t, tt.wantFormat, *f)
    81  			}
    82  		})
    83  	}
    84  }
    85  
    86  func TestCSVParser_ReadLine(t *testing.T) {
    87  	tests := []struct {
    88  		name         string
    89  		row          string
    90  		format       string
    91  		wantErr      bool
    92  		wantParseErr bool
    93  	}{
    94  		{name: "match and no error", row: "1 2 3", format: `$A $B $C`},
    95  		{name: "match but error on assigning", row: "1 2 3", format: `$A $B $ERR`, wantErr: true, wantParseErr: true},
    96  		{name: "not match", row: "1 2 3", format: `$A $B $C $d`, wantErr: true, wantParseErr: true},
    97  		{name: "error on reading csv.Err", row: "1 2\"3", format: `$A $B $C`, wantErr: true, wantParseErr: true},
    98  		{name: "error on reading EOF", row: "", format: `$A $B $C`, wantErr: true},
    99  	}
   100  
   101  	for _, tt := range tests {
   102  		t.Run(tt.name, func(t *testing.T) {
   103  			var line logLine
   104  			r := strings.NewReader(tt.row)
   105  			c := testCSVConfig
   106  			c.Format = tt.format
   107  			p, err := NewCSVParser(c, r)
   108  			require.NoError(t, err)
   109  
   110  			err = p.ReadLine(&line)
   111  
   112  			if tt.wantErr {
   113  				require.Error(t, err)
   114  				if tt.wantParseErr {
   115  					assert.True(t, IsParseError(err))
   116  				} else {
   117  					assert.False(t, IsParseError(err))
   118  				}
   119  			} else {
   120  				assert.NoError(t, err)
   121  			}
   122  		})
   123  	}
   124  }
   125  
   126  func TestCSVParser_Parse(t *testing.T) {
   127  	tests := []struct {
   128  		name    string
   129  		row     string
   130  		format  string
   131  		wantErr bool
   132  	}{
   133  		{name: "match and no error", row: "1 2 3", format: `$A $B $C`},
   134  		{name: "match but error on assigning", row: "1 2 3", format: `$A $B $ERR`, wantErr: true},
   135  		{name: "not match", row: "1 2 3", format: `$A $B $C $d`, wantErr: true},
   136  		{name: "error on reading csv.Err", row: "1 2\"3", format: `$A $B $C`, wantErr: true},
   137  	}
   138  
   139  	for _, tt := range tests {
   140  		t.Run(tt.name, func(t *testing.T) {
   141  			var line logLine
   142  			r := strings.NewReader(tt.row)
   143  			c := testCSVConfig
   144  			c.Format = tt.format
   145  			p, err := NewCSVParser(c, r)
   146  			require.NoError(t, err)
   147  
   148  			err = p.ReadLine(&line)
   149  
   150  			if tt.wantErr {
   151  				require.Error(t, err)
   152  				assert.True(t, IsParseError(err))
   153  			} else {
   154  				assert.NoError(t, err)
   155  			}
   156  		})
   157  	}
   158  
   159  }
   160  
   161  func TestCSVParser_Info(t *testing.T) {
   162  	p, err := NewCSVParser(testCSVConfig, nil)
   163  	require.NoError(t, err)
   164  	assert.NotZero(t, p.Info())
   165  }
   166  
   167  func testCheckCSVFormatField(name string) (newName string, offset int, valid bool) {
   168  	if len(name) < 2 || !strings.HasPrefix(name, "$") {
   169  		return "", 0, false
   170  	}
   171  	if name == "$OFFSET" {
   172  		return "", 1, false
   173  	}
   174  	return name, 0, true
   175  }