package upsd import ( "context" "net" "testing" "time" "github.com/influxdata/telegraf/testutil" "github.com/stretchr/testify/require" ) func TestUpsdGather(t *testing.T) { nut := &Upsd{} var ( tests = []struct { name string forceFloat bool err bool tags map[string]string fields map[string]interface{} out func() []interaction }{ { name: "test listening server with output", forceFloat: false, err: false, tags: map[string]string{ "serial": "ABC123", "ups_name": "fake", "model": "Model 12345", "status_OL": "true", }, fields: map[string]interface{}{ "battery_charge_percent": float64(100), "battery_date": nil, "battery_mfr_date": "2016-07-26", "battery_voltage": float64(13.4), "firmware": "CUSTOM_FIRMWARE", "input_voltage": float64(242), "load_percent": float64(23), "nominal_battery_voltage": float64(24), "nominal_input_voltage": float64(230), "nominal_power": int64(700), "output_voltage": float64(230), "status_flags": uint64(8), "time_left_ns": int64(600000000000), "ups_status": "OL", }, out: genOutput, }, { name: "test listening server with output & force floats", forceFloat: true, err: false, tags: map[string]string{ "serial": "ABC123", "ups_name": "fake", "model": "Model 12345", "status_OL": "true", }, fields: map[string]interface{}{ "battery_charge_percent": float64(100), "battery_date": nil, "battery_mfr_date": "2016-07-26", "battery_voltage": float64(13.4), "firmware": "CUSTOM_FIRMWARE", "input_voltage": float64(242), "load_percent": float64(23), "nominal_battery_voltage": float64(24), "nominal_input_voltage": float64(230), "nominal_power": int64(700), "output_voltage": float64(230), "status_flags": uint64(8), "time_left_ns": int64(600000000000), "ups_status": "OL", }, out: genOutput, }, } acc testutil.Accumulator ) for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) lAddr, err := listen(ctx, t, tt.out()) require.NoError(t, err) nut.Server = (lAddr.(*net.TCPAddr)).IP.String() nut.Port = (lAddr.(*net.TCPAddr)).Port nut.ForceFloat = tt.forceFloat err = nut.Gather(&acc) if tt.err { require.Error(t, err) } else { require.NoError(t, err) acc.AssertContainsTaggedFields(t, "upsd", tt.fields, tt.tags) } cancel() }) } } func TestUpsdGatherFail(t *testing.T) { nut := &Upsd{} var ( tests = []struct { name string err bool tags map[string]string fields map[string]interface{} out func() []interaction }{ { name: "test with bad output", err: true, out: genBadOutput, }, } acc testutil.Accumulator ) for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) lAddr, err := listen(ctx, t, tt.out()) require.NoError(t, err) nut.Server = (lAddr.(*net.TCPAddr)).IP.String() nut.Port = (lAddr.(*net.TCPAddr)).Port err = nut.Gather(&acc) if tt.err { require.Error(t, err) } else { require.NoError(t, err) acc.AssertContainsTaggedFields(t, "upsd", tt.fields, tt.tags) } cancel() }) } } func listen(ctx context.Context, t *testing.T, out []interaction) (net.Addr, 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() require.NoError(t, conn.SetReadDeadline(time.Now().Add(time.Minute))) in := make([]byte, 128) for _, interaction := range out { n, err := conn.Read(in) require.NoError(t, err, "failed to read from connection") expectedBytes := []byte(interaction.Expected) want, got := expectedBytes, in[:n] require.Equal(t, want, got) _, err = conn.Write([]byte(interaction.Response)) require.NoError(t, err, "failed to respond to LIST UPS") } // Append EOF to end of output bytes _, err = conn.Write([]byte{0, 0}) require.NoError(t, err, "failed to write EOF") }() } }() return ln.Addr(), nil } type interaction struct { Expected string Response string } func genOutput() []interaction { m := make([]interaction, 0) m = append(m, interaction{ Expected: "VER\n", Response: "1\n", }) m = append(m, interaction{ Expected: "NETVER\n", Response: "1\n", }) m = append(m, interaction{ Expected: "LIST UPS\n", Response: `BEGIN LIST UPS UPS fake "fakescription" END LIST UPS `, }) m = append(m, interaction{ Expected: "LIST CLIENT fake\n", Response: `BEGIN LIST CLIENT fake CLIENT fake 192.168.1.1 END LIST CLIENT fake `, }) m = append(m, interaction{ Expected: "LIST CMD fake\n", Response: `BEGIN LIST CMD fake END LIST CMD fake `, }) m = append(m, interaction{ Expected: "GET UPSDESC fake\n", Response: "UPSDESC fake \"stub-ups-description\"\n", }) m = append(m, interaction{ Expected: "GET NUMLOGINS fake\n", Response: "NUMLOGINS fake 1\n", }) m = append(m, interaction{ Expected: "LIST VAR fake\n", Response: `BEGIN LIST VAR fake VAR fake device.serial "ABC123" VAR fake device.model "Model 12345" VAR fake input.voltage "242.0" VAR fake ups.load "23.0" VAR fake battery.charge "100.0" VAR fake battery.runtime "600" VAR fake output.voltage "230.0" VAR fake battery.voltage "13.4" VAR fake input.voltage.nominal "230.0" VAR fake battery.voltage.nominal "24.0" VAR fake ups.realpower.nominal "700" VAR fake ups.firmware "CUSTOM_FIRMWARE" VAR fake battery.mfr.date "2016-07-26" VAR fake ups.status "OL" END LIST VAR fake `, }) m = appendVariable(m, "device.serial", "STRING:64") m = appendVariable(m, "device.model", "STRING:64") m = appendVariable(m, "input.voltage", "NUMBER") m = appendVariable(m, "ups.load", "NUMBER") m = appendVariable(m, "battery.charge", "NUMBER") m = appendVariable(m, "battery.runtime", "NUMBER") m = appendVariable(m, "output.voltage", "NUMBER") m = appendVariable(m, "battery.voltage", "NUMBER") m = appendVariable(m, "input.voltage.nominal", "NUMBER") m = appendVariable(m, "battery.voltage.nominal", "NUMBER") m = appendVariable(m, "ups.realpower.nominal", "NUMBER") m = appendVariable(m, "ups.firmware", "STRING:64") m = appendVariable(m, "battery.mfr.date", "STRING:64") m = appendVariable(m, "ups.status", "STRING:64") return m } func appendVariable(m []interaction, name string, typ string) []interaction { m = append(m, interaction{ Expected: "GET DESC fake " + name + "\n", Response: "DESC fake" + name + " \"No description here\"\n", }) m = append(m, interaction{ Expected: "GET TYPE fake " + name + "\n", Response: "TYPE fake " + name + " " + typ + "\n", }) return m } func genBadOutput() []interaction { m := make([]interaction, 0) return m }