github.com/netdata/go.d.plugin@v0.58.1/agent/functions/manager_test.go (about)

     1  // SPDX-License-Identifier: GPL-3.0-or-later
     2  
     3  package functions
     4  
     5  import (
     6  	"context"
     7  	"sort"
     8  	"strings"
     9  	"testing"
    10  	"time"
    11  
    12  	"github.com/stretchr/testify/assert"
    13  )
    14  
    15  func TestNewManager(t *testing.T) {
    16  	mgr := NewManager()
    17  
    18  	assert.NotNilf(t, mgr.Input, "Input")
    19  	assert.NotNilf(t, mgr.FunctionRegistry, "FunctionRegistry")
    20  }
    21  
    22  func TestManager_Register(t *testing.T) {
    23  	type testInputFn struct {
    24  		name    string
    25  		invalid bool
    26  	}
    27  	tests := map[string]struct {
    28  		input    []testInputFn
    29  		expected []string
    30  	}{
    31  		"valid registration": {
    32  			input: []testInputFn{
    33  				{name: "fn1"},
    34  				{name: "fn2"},
    35  			},
    36  			expected: []string{"fn1", "fn2"},
    37  		},
    38  		"registration with duplicates": {
    39  			input: []testInputFn{
    40  				{name: "fn1"},
    41  				{name: "fn2"},
    42  				{name: "fn1"},
    43  			},
    44  			expected: []string{"fn1", "fn2"},
    45  		},
    46  		"registration with nil functions": {
    47  			input: []testInputFn{
    48  				{name: "fn1"},
    49  				{name: "fn2", invalid: true},
    50  			},
    51  			expected: []string{"fn1"},
    52  		},
    53  	}
    54  
    55  	for name, test := range tests {
    56  		t.Run(name, func(t *testing.T) {
    57  			mgr := NewManager()
    58  
    59  			for _, v := range test.input {
    60  				if v.invalid {
    61  					mgr.Register(v.name, nil)
    62  				} else {
    63  					mgr.Register(v.name, func(Function) {})
    64  				}
    65  			}
    66  
    67  			var got []string
    68  			for name := range mgr.FunctionRegistry {
    69  				got = append(got, name)
    70  			}
    71  			sort.Strings(got)
    72  			sort.Strings(test.expected)
    73  
    74  			assert.Equal(t, test.expected, got)
    75  		})
    76  	}
    77  }
    78  
    79  func TestManager_Run(t *testing.T) {
    80  	tests := map[string]struct {
    81  		register []string
    82  		input    string
    83  		expected []Function
    84  	}{
    85  		"valid function: single": {
    86  			register: []string{"fn1"},
    87  			input: `
    88  FUNCTION UID 1 "fn1 arg1 arg2"
    89  `,
    90  			expected: []Function{
    91  				{
    92  					key:     "FUNCTION",
    93  					UID:     "UID",
    94  					Timeout: time.Second,
    95  					Name:    "fn1",
    96  					Args:    []string{"arg1", "arg2"},
    97  					Payload: nil,
    98  				},
    99  			},
   100  		},
   101  		"valid function: multiple": {
   102  			register: []string{"fn1", "fn2"},
   103  			input: `
   104  FUNCTION UID 1 "fn1 arg1 arg2"
   105  FUNCTION UID 1 "fn2 arg1 arg2"
   106  `,
   107  			expected: []Function{
   108  				{
   109  					key:     "FUNCTION",
   110  					UID:     "UID",
   111  					Timeout: time.Second,
   112  					Name:    "fn1",
   113  					Args:    []string{"arg1", "arg2"},
   114  					Payload: nil,
   115  				},
   116  				{
   117  					key:     "FUNCTION",
   118  					UID:     "UID",
   119  					Timeout: time.Second,
   120  					Name:    "fn2",
   121  					Args:    []string{"arg1", "arg2"},
   122  					Payload: nil,
   123  				},
   124  			},
   125  		},
   126  		"valid function: single with payload": {
   127  			register: []string{"fn1", "fn2"},
   128  			input: `
   129  FUNCTION_PAYLOAD UID 1 "fn1 arg1 arg2"
   130  payload line1
   131  payload line2
   132  FUNCTION_PAYLOAD_END
   133  `,
   134  			expected: []Function{
   135  				{
   136  					key:     "FUNCTION_PAYLOAD",
   137  					UID:     "UID",
   138  					Timeout: time.Second,
   139  					Name:    "fn1",
   140  					Args:    []string{"arg1", "arg2"},
   141  					Payload: []byte("payload line1\npayload line2"),
   142  				},
   143  			},
   144  		},
   145  		"valid function: multiple with payload": {
   146  			register: []string{"fn1", "fn2"},
   147  			input: `
   148  FUNCTION_PAYLOAD UID 1 "fn1 arg1 arg2"
   149  payload line1
   150  payload line2
   151  FUNCTION_PAYLOAD_END
   152  
   153  FUNCTION_PAYLOAD UID 1 "fn2 arg1 arg2"
   154  payload line3
   155  payload line4
   156  FUNCTION_PAYLOAD_END
   157  `,
   158  			expected: []Function{
   159  				{
   160  					key:     "FUNCTION_PAYLOAD",
   161  					UID:     "UID",
   162  					Timeout: time.Second,
   163  					Name:    "fn1",
   164  					Args:    []string{"arg1", "arg2"},
   165  					Payload: []byte("payload line1\npayload line2"),
   166  				},
   167  				{
   168  					key:     "FUNCTION_PAYLOAD",
   169  					UID:     "UID",
   170  					Timeout: time.Second,
   171  					Name:    "fn2",
   172  					Args:    []string{"arg1", "arg2"},
   173  					Payload: []byte("payload line3\npayload line4"),
   174  				},
   175  			},
   176  		},
   177  		"valid function: multiple with and without payload": {
   178  			register: []string{"fn1", "fn2", "fn3", "fn4"},
   179  			input: `
   180  FUNCTION_PAYLOAD UID 1 "fn1 arg1 arg2"
   181  payload line1
   182  payload line2
   183  FUNCTION_PAYLOAD_END
   184  
   185  FUNCTION UID 1 "fn2 arg1 arg2"
   186  FUNCTION UID 1 "fn3 arg1 arg2"
   187  
   188  FUNCTION_PAYLOAD UID 1 "fn4 arg1 arg2"
   189  payload line3
   190  payload line4
   191  FUNCTION_PAYLOAD_END
   192  `,
   193  			expected: []Function{
   194  				{
   195  					key:     "FUNCTION_PAYLOAD",
   196  					UID:     "UID",
   197  					Timeout: time.Second,
   198  					Name:    "fn1",
   199  					Args:    []string{"arg1", "arg2"},
   200  					Payload: []byte("payload line1\npayload line2"),
   201  				},
   202  				{
   203  					key:     "FUNCTION",
   204  					UID:     "UID",
   205  					Timeout: time.Second,
   206  					Name:    "fn2",
   207  					Args:    []string{"arg1", "arg2"},
   208  					Payload: nil,
   209  				},
   210  				{
   211  					key:     "FUNCTION",
   212  					UID:     "UID",
   213  					Timeout: time.Second,
   214  					Name:    "fn3",
   215  					Args:    []string{"arg1", "arg2"},
   216  					Payload: nil,
   217  				},
   218  				{
   219  					key:     "FUNCTION_PAYLOAD",
   220  					UID:     "UID",
   221  					Timeout: time.Second,
   222  					Name:    "fn4",
   223  					Args:    []string{"arg1", "arg2"},
   224  					Payload: []byte("payload line3\npayload line4"),
   225  				},
   226  			},
   227  		},
   228  	}
   229  
   230  	for name, test := range tests {
   231  		t.Run(name, func(t *testing.T) {
   232  			mgr := NewManager()
   233  
   234  			mgr.Input = strings.NewReader(test.input)
   235  
   236  			mock := &mockFunctionExecutor{}
   237  			for _, v := range test.register {
   238  				mgr.Register(v, mock.execute)
   239  			}
   240  
   241  			testTime := time.Second * 5
   242  			ctx, cancel := context.WithTimeout(context.Background(), testTime)
   243  			defer cancel()
   244  
   245  			done := make(chan struct{})
   246  
   247  			go func() { defer close(done); mgr.Run(ctx) }()
   248  
   249  			timeout := testTime + time.Second*2
   250  			tk := time.NewTimer(timeout)
   251  			defer tk.Stop()
   252  
   253  			select {
   254  			case <-done:
   255  				assert.Equal(t, test.expected, mock.executed)
   256  			case <-tk.C:
   257  				t.Errorf("timed out after %s", timeout)
   258  			}
   259  		})
   260  	}
   261  }
   262  
   263  type mockFunctionExecutor struct {
   264  	executed []Function
   265  }
   266  
   267  func (m *mockFunctionExecutor) execute(fn Function) {
   268  	m.executed = append(m.executed, fn)
   269  }