1
0
Fork 0
telegraf/plugins/inputs/upsd/upsd_test.go

296 lines
7.3 KiB
Go
Raw Normal View History

package upsd
import (
"bufio"
"bytes"
"context"
"fmt"
"net"
"os"
"path/filepath"
"strings"
"testing"
"time"
"github.com/stretchr/testify/require"
"github.com/influxdata/telegraf"
"github.com/influxdata/telegraf/config"
"github.com/influxdata/telegraf/plugins/inputs"
"github.com/influxdata/telegraf/plugins/parsers/influx"
"github.com/influxdata/telegraf/testutil"
)
func TestBadServer(t *testing.T) {
// Create and start a server without interactions
server := &mockServer{}
ctx, cancel := context.WithCancel(t.Context())
addr, err := server.listen(ctx)
require.NoError(t, err)
defer cancel()
// Setup the plugin
plugin := &Upsd{
Server: addr.IP.String(),
Port: addr.Port,
}
require.NoError(t, plugin.Init())
// Do the query
var acc testutil.Accumulator
require.Error(t, plugin.Gather(&acc))
}
func TestCases(t *testing.T) {
// Get all directories in testdata
folders, err := os.ReadDir("testcases")
require.NoError(t, err)
// Register the plugin
inputs.Add("upsd", func() telegraf.Input {
return &Upsd{}
})
for _, f := range folders {
// Only handle folders
if !f.IsDir() {
continue
}
testcasePath := filepath.Join("testcases", f.Name())
configFilename := filepath.Join(testcasePath, "telegraf.conf")
expectedFilename := filepath.Join(testcasePath, "expected.out")
t.Run(f.Name(), func(t *testing.T) {
// Prepare the influx parser for expectations
parser := &influx.Parser{}
require.NoError(t, parser.Init())
// Read the expected output if any
var expected []telegraf.Metric
if _, err := os.Stat(expectedFilename); err == nil {
var err error
expected, err = testutil.ParseMetricsFromFile(expectedFilename, parser)
require.NoError(t, err)
}
// Setup a server from the input data
server, err := setupServer(testcasePath)
require.NoError(t, err)
// Start the server
ctx, cancel := context.WithCancel(t.Context())
addr, err := server.listen(ctx)
require.NoError(t, err)
defer cancel()
// Configure the plugin
cfg := config.NewConfig()
require.NoError(t, cfg.LoadConfig(configFilename))
require.Len(t, cfg.Inputs, 1)
plugin := cfg.Inputs[0].Input.(*Upsd)
plugin.Server = addr.IP.String()
plugin.Port = addr.Port
require.NoError(t, plugin.Init())
var acc testutil.Accumulator
require.NoError(t, plugin.Gather(&acc))
// Check the metric nevertheless as we might get some metrics despite errors.
actual := acc.GetTelegrafMetrics()
testutil.RequireMetricsEqual(t, expected, actual, testutil.IgnoreTime())
acc.Lock()
defer acc.Unlock()
require.Empty(t, acc.Errors)
})
}
}
type interaction struct {
expected string
response string
}
type variable struct {
name string
value string
}
type mockServer struct {
protocol []interaction
}
func (s *mockServer) init() {
s.protocol = []interaction{
{
expected: "VER\n",
response: "1\n",
},
{
expected: "NETVER\n",
response: "1\n",
},
{
expected: "LIST UPS\n",
response: "BEGIN LIST UPS\nUPS fake \"fake UPS\"\nEND LIST UPS\n",
},
{
expected: "LIST CLIENT fake\n",
response: "BEGIN LIST CLIENT fake\nCLIENT fake 127.0.0.1\nEND LIST CLIENT fake\n",
},
{
expected: "LIST CMD fake\n",
response: "BEGIN LIST CMD fake\nEND LIST CMD fake\n",
},
{
expected: "GET UPSDESC fake\n",
response: "UPSDESC fake \"stub-ups-description\"\n",
},
{
expected: "GET NUMLOGINS fake\n",
response: "NUMLOGINS fake 1\n",
},
}
}
func (s *mockServer) addVariables(variables []variable, types map[string]string) error {
// Add a VAR entries for the variables
values := make([]string, 0, len(variables))
for _, v := range variables {
values = append(values, fmt.Sprintf("VAR fake %s %q", v.name, v.value))
}
s.protocol = append(s.protocol, interaction{
expected: "LIST VAR fake\n",
response: "BEGIN LIST VAR fake\n" + strings.Join(values, "\n") + "\nEND LIST VAR fake\n",
})
// Add a description and type interaction for the variable
for _, v := range variables {
variableType, found := types[v.name]
if !found {
return fmt.Errorf("type for variable %q not found", v.name)
}
s.protocol = append(s.protocol,
interaction{
expected: "GET DESC fake " + v.name + "\n",
response: "DESC fake" + v.name + " \"No description here\"\n",
},
interaction{
expected: "GET TYPE fake " + v.name + "\n",
response: "TYPE fake " + v.name + " " + variableType + "\n",
},
)
}
return nil
}
func (s *mockServer) listen(ctx context.Context) (*net.TCPAddr, error) {
lc := net.ListenConfig{}
ln, err := lc.Listen(ctx, "tcp4", "127.0.0.1:0")
if err != nil {
return nil, err
}
go func() {
defer ln.Close()
for ctx.Err() == nil {
func() {
conn, err := ln.Accept()
if err != nil {
return
}
defer conn.Close()
err = conn.SetReadDeadline(time.Now().Add(time.Minute))
if err != nil {
return
}
in := make([]byte, 128)
for _, interaction := range s.protocol {
n, err := conn.Read(in)
if err != nil {
fmt.Printf("Failed to read from connection: %v\n", err)
return
}
request := in[:n]
if !bytes.Equal([]byte(interaction.expected), request) {
fmt.Printf("Unexpected request %q, expected %q\n", string(request), interaction.expected)
return
}
if _, err := conn.Write([]byte(interaction.response)); err != nil {
fmt.Printf("Cannot write answer for request %q: %v\n", string(request), err)
return
}
}
// Append EOF to end of output bytes
if _, err := conn.Write([]byte{0, 0}); err != nil {
fmt.Printf("Cannot write EOF: %v\n", err)
return
}
}()
}
}()
return ln.Addr().(*net.TCPAddr), nil
}
func setupServer(path string) (*mockServer, error) {
// Read the variables
varbuf, err := os.ReadFile(filepath.Join(path, "variables.dev"))
if err != nil {
return nil, fmt.Errorf("reading variables failed: %w", err)
}
// Parse the information into variable names and values (upsc format)
variables := make([]variable, 0)
scanner := bufio.NewScanner(bytes.NewBuffer(varbuf))
for scanner.Scan() {
line := scanner.Text()
parts := strings.SplitN(line, ":", 2)
if len(parts) != 2 {
return nil, fmt.Errorf("cannot parse line %s", line)
}
name := strings.TrimSpace(parts[0])
value := strings.TrimSpace(parts[1])
variables = append(variables, variable{name, value})
}
if err := scanner.Err(); err != nil {
return nil, fmt.Errorf("processing variables failed: %w", err)
}
// Read the variable-type mapping
typebuf, err := os.ReadFile(filepath.Join(path, "types.dev"))
if err != nil {
return nil, fmt.Errorf("reading variables failed: %w", err)
}
// Parse the information into variable names and values (upsc format)
types := make(map[string]string, 0)
scanner = bufio.NewScanner(bytes.NewBuffer(typebuf))
for scanner.Scan() {
line := scanner.Text()
parts := strings.SplitN(line, ":", 2)
if len(parts) != 2 {
return nil, fmt.Errorf("cannot parse line %s", line)
}
name := strings.TrimSpace(parts[0])
vartype := strings.TrimSpace(parts[1])
types[name] = vartype
}
if err := scanner.Err(); err != nil {
return nil, fmt.Errorf("processing variables failed: %w", err)
}
// Setup the server and add the device information
server := &mockServer{}
server.init()
err = server.addVariables(variables, types)
return server, err
}