Add tests

This commit is contained in:
Pim van Pelt
2025-06-24 07:03:34 +02:00
parent bdaa2e366b
commit e93156324d
3 changed files with 540 additions and 0 deletions

190
src/vpp/vpp_iface_test.go Normal file
View File

@ -0,0 +1,190 @@
// Copyright 2025, IPng Networks GmbH, Pim van Pelt <pim@ipng.ch>
package vpp
import (
"go.fd.io/govpp/binapi/interface_types"
"testing"
)
func TestNewInterfaceManager(t *testing.T) {
client := NewVPPClient()
manager := NewInterfaceManager(client)
if manager == nil {
t.Fatal("NewInterfaceManager() returned nil")
}
if manager.client != client {
t.Error("InterfaceManager should store the provided client")
}
if manager.eventCallback != nil {
t.Error("InterfaceManager should have nil callback initially")
}
}
func TestInterfaceManagerSetEventCallback(t *testing.T) {
client := NewVPPClient()
manager := NewInterfaceManager(client)
var callbackCalled bool
var receivedDetails []InterfaceDetails
callback := func(details []InterfaceDetails) {
callbackCalled = true
receivedDetails = details
}
manager.SetEventCallback(callback)
if manager.eventCallback == nil {
t.Error("SetEventCallback() should store the callback")
}
// Test callback execution
testDetails := []InterfaceDetails{
{
SwIfIndex: 1,
InterfaceName: "test-interface",
MacAddress: []byte{0xde, 0xad, 0xbe, 0xef, 0x00, 0x01},
Speed: 1000000000,
AdminStatus: true,
OperStatus: true,
MTU: 1500,
},
}
manager.eventCallback(testDetails)
if !callbackCalled {
t.Error("Callback should have been called")
}
if len(receivedDetails) != 1 {
t.Errorf("Expected 1 interface detail, got %d", len(receivedDetails))
}
if receivedDetails[0].InterfaceName != "test-interface" {
t.Errorf("Expected interface name 'test-interface', got %q", receivedDetails[0].InterfaceName)
}
}
func TestInterfaceManagerGetAllInterfaceDetailsWithoutConnection(t *testing.T) {
client := NewVPPClient()
manager := NewInterfaceManager(client)
_, err := manager.GetAllInterfaceDetails()
if err == nil {
t.Error("GetAllInterfaceDetails() should return error when not connected")
}
vppErr, ok := err.(*VPPError)
if !ok {
t.Errorf("Expected VPPError, got %T", err)
}
if vppErr.Message != "VPP client not connected" {
t.Errorf("Expected specific error message, got: %s", vppErr.Message)
}
}
func TestInterfaceManagerStartEventWatcherWithoutConnection(t *testing.T) {
client := NewVPPClient()
manager := NewInterfaceManager(client)
err := manager.StartEventWatcher()
if err == nil {
t.Error("StartEventWatcher() should return error when not connected")
}
vppErr, ok := err.(*VPPError)
if !ok {
t.Errorf("Expected VPPError, got %T", err)
}
if vppErr.Message != "VPP client not connected" {
t.Errorf("Expected specific error message, got: %s", vppErr.Message)
}
}
func TestInterfaceManagerHandleInterfaceEventWithoutCallback(t *testing.T) {
client := NewVPPClient()
manager := NewInterfaceManager(client)
// Should not panic when callback is nil
manager.handleInterfaceEvent()
}
func TestInterfaceDetails(t *testing.T) {
details := InterfaceDetails{
SwIfIndex: interface_types.InterfaceIndex(42),
InterfaceName: "GigabitEthernet0/8/0",
MacAddress: []byte{0x02, 0xfe, 0x3c, 0x4d, 0x5e, 0x6f},
Speed: 10000000000, // 10 Gbps
AdminStatus: true,
OperStatus: false,
MTU: 9000,
}
if details.SwIfIndex != 42 {
t.Errorf("Expected SwIfIndex 42, got %d", details.SwIfIndex)
}
if details.InterfaceName != "GigabitEthernet0/8/0" {
t.Errorf("Expected interface name 'GigabitEthernet0/8/0', got %q", details.InterfaceName)
}
if len(details.MacAddress) != 6 {
t.Errorf("Expected MAC address length 6, got %d", len(details.MacAddress))
}
if details.Speed != 10000000000 {
t.Errorf("Expected speed 10000000000, got %d", details.Speed)
}
if !details.AdminStatus {
t.Error("Expected AdminStatus true")
}
if details.OperStatus {
t.Error("Expected OperStatus false")
}
if details.MTU != 9000 {
t.Errorf("Expected MTU 9000, got %d", details.MTU)
}
}
func TestInterfaceEventCallback(t *testing.T) {
var callbackInvoked bool
var callbackDetails []InterfaceDetails
callback := InterfaceEventCallback(func(details []InterfaceDetails) {
callbackInvoked = true
callbackDetails = details
})
testDetails := []InterfaceDetails{
{SwIfIndex: 1, InterfaceName: "test1"},
{SwIfIndex: 2, InterfaceName: "test2"},
}
callback(testDetails)
if !callbackInvoked {
t.Error("Callback should have been invoked")
}
if len(callbackDetails) != 2 {
t.Errorf("Expected 2 interface details, got %d", len(callbackDetails))
}
if callbackDetails[0].InterfaceName != "test1" {
t.Errorf("Expected first interface 'test1', got %q", callbackDetails[0].InterfaceName)
}
if callbackDetails[1].InterfaceName != "test2" {
t.Errorf("Expected second interface 'test2', got %q", callbackDetails[1].InterfaceName)
}
}

250
src/vpp/vpp_stats_test.go Normal file
View File

@ -0,0 +1,250 @@
// Copyright 2025, IPng Networks GmbH, Pim van Pelt <pim@ipng.ch>
package vpp
import (
"testing"
"time"
"go.fd.io/govpp/api"
)
func TestNewStatsManager(t *testing.T) {
client := NewVPPClient()
interfaceManager := NewInterfaceManager(client)
manager := NewStatsManager(client, interfaceManager)
if manager == nil {
t.Fatal("NewStatsManager() returned nil")
}
if manager.client != client {
t.Error("StatsManager should store the provided client")
}
if manager.interfaceManager != interfaceManager {
t.Error("StatsManager should store the provided interface manager")
}
if manager.period != time.Duration(*Period)*time.Second {
t.Errorf("Expected period %v, got %v", time.Duration(*Period)*time.Second, manager.period)
}
if manager.running {
t.Error("StatsManager should not be running initially")
}
if manager.statsCallback != nil {
t.Error("StatsManager should have nil callback initially")
}
}
func TestStatsManagerSetStatsCallback(t *testing.T) {
client := NewVPPClient()
interfaceManager := NewInterfaceManager(client)
manager := NewStatsManager(client, interfaceManager)
var callbackCalled bool
var receivedStats *api.InterfaceStats
callback := func(stats *api.InterfaceStats) {
callbackCalled = true
receivedStats = stats
}
manager.SetStatsCallback(callback)
if manager.statsCallback == nil {
t.Error("SetStatsCallback() should store the callback")
}
// Test callback execution
testStats := &api.InterfaceStats{
Interfaces: []api.InterfaceCounters{
{
InterfaceIndex: 1,
InterfaceName: "test-interface",
Rx: api.InterfaceCounterCombined{Packets: 100, Bytes: 1500},
Tx: api.InterfaceCounterCombined{Packets: 50, Bytes: 750},
},
},
}
manager.statsCallback(testStats)
if !callbackCalled {
t.Error("Callback should have been called")
}
if receivedStats != testStats {
t.Error("Callback should receive the same stats object")
}
if len(receivedStats.Interfaces) != 1 {
t.Errorf("Expected 1 interface, got %d", len(receivedStats.Interfaces))
}
if receivedStats.Interfaces[0].InterfaceName != "test-interface" {
t.Errorf("Expected interface name 'test-interface', got %q", receivedStats.Interfaces[0].InterfaceName)
}
}
func TestStatsManagerSetPeriod(t *testing.T) {
client := NewVPPClient()
interfaceManager := NewInterfaceManager(client)
manager := NewStatsManager(client, interfaceManager)
newPeriod := 5 * time.Second
manager.SetPeriod(newPeriod)
if manager.period != newPeriod {
t.Errorf("Expected period %v, got %v", newPeriod, manager.period)
}
}
func TestStatsManagerStartStopStatsRoutine(t *testing.T) {
client := NewVPPClient()
interfaceManager := NewInterfaceManager(client)
manager := NewStatsManager(client, interfaceManager)
if manager.running {
t.Error("StatsManager should not be running initially")
}
manager.StartStatsRoutine()
if !manager.running {
t.Error("StatsManager should be running after StartStatsRoutine()")
}
// Test starting again (should be safe)
manager.StartStatsRoutine()
if !manager.running {
t.Error("StatsManager should still be running after second StartStatsRoutine()")
}
manager.StopStatsRoutine()
if manager.running {
t.Error("StatsManager should not be running after StopStatsRoutine()")
}
}
func TestStatsManagerGetInterfaceStatsWithoutConnection(t *testing.T) {
client := NewVPPClient()
interfaceManager := NewInterfaceManager(client)
manager := NewStatsManager(client, interfaceManager)
_, err := manager.GetInterfaceStats()
if err == nil {
t.Error("GetInterfaceStats() should return error when not connected")
}
vppErr, ok := err.(*VPPError)
if !ok {
t.Errorf("Expected VPPError, got %T", err)
}
if vppErr.Message != "VPP client not connected" {
t.Errorf("Expected specific error message, got: %s", vppErr.Message)
}
}
func TestStatsCallback(t *testing.T) {
var callbackInvoked bool
var callbackStats *api.InterfaceStats
callback := StatsCallback(func(stats *api.InterfaceStats) {
callbackInvoked = true
callbackStats = stats
})
testStats := &api.InterfaceStats{
Interfaces: []api.InterfaceCounters{
{
InterfaceIndex: 42,
InterfaceName: "test-callback-interface",
Rx: api.InterfaceCounterCombined{Packets: 200, Bytes: 3000},
Tx: api.InterfaceCounterCombined{Packets: 100, Bytes: 1500},
RxUnicast: api.InterfaceCounterCombined{Packets: 180, Bytes: 2700},
TxUnicast: api.InterfaceCounterCombined{Packets: 90, Bytes: 1350},
},
},
}
callback(testStats)
if !callbackInvoked {
t.Error("Callback should have been invoked")
}
if callbackStats != testStats {
t.Error("Callback should receive the same stats object")
}
if len(callbackStats.Interfaces) != 1 {
t.Errorf("Expected 1 interface, got %d", len(callbackStats.Interfaces))
}
iface := callbackStats.Interfaces[0]
if iface.InterfaceIndex != 42 {
t.Errorf("Expected interface index 42, got %d", iface.InterfaceIndex)
}
if iface.InterfaceName != "test-callback-interface" {
t.Errorf("Expected interface name 'test-callback-interface', got %q", iface.InterfaceName)
}
if iface.Rx.Packets != 200 {
t.Errorf("Expected RX packets 200, got %d", iface.Rx.Packets)
}
if iface.Tx.Bytes != 1500 {
t.Errorf("Expected TX bytes 1500, got %d", iface.Tx.Bytes)
}
if iface.RxUnicast.Packets != 180 {
t.Errorf("Expected RX unicast packets 180, got %d", iface.RxUnicast.Packets)
}
if iface.TxUnicast.Bytes != 1350 {
t.Errorf("Expected TX unicast bytes 1350, got %d", iface.TxUnicast.Bytes)
}
}
func TestStatsManagerQueryAndReportStatsWithoutConnection(t *testing.T) {
client := NewVPPClient()
interfaceManager := NewInterfaceManager(client)
manager := NewStatsManager(client, interfaceManager)
// Should return false when not connected
if manager.queryAndReportStats() {
t.Error("queryAndReportStats() should return false when not connected")
}
}
func TestStatsManagerWithShortPeriod(t *testing.T) {
client := NewVPPClient()
interfaceManager := NewInterfaceManager(client)
manager := NewStatsManager(client, interfaceManager)
// Set a very short period for testing
manager.SetPeriod(10 * time.Millisecond)
if manager.period != 10*time.Millisecond {
t.Errorf("Expected period 10ms, got %v", manager.period)
}
manager.StartStatsRoutine()
// Let it run briefly
time.Sleep(50 * time.Millisecond)
manager.StopStatsRoutine()
// Should stop gracefully
if manager.running {
t.Error("StatsManager should have stopped")
}
}

100
src/vpp/vpp_test.go Normal file
View File

@ -0,0 +1,100 @@
// Copyright 2025, IPng Networks GmbH, Pim van Pelt <pim@ipng.ch>
package vpp
import (
"testing"
)
func TestNewVPPClient(t *testing.T) {
client := NewVPPClient()
if client == nil {
t.Fatal("NewVPPClient() returned nil")
}
if client.IsConnected() {
t.Error("NewVPPClient() should return disconnected client")
}
if client.GetAPIConnection() != nil {
t.Error("NewVPPClient() should have nil API connection initially")
}
if client.GetStatsConnection() != nil {
t.Error("NewVPPClient() should have nil stats connection initially")
}
}
func TestVPPClientDisconnect(t *testing.T) {
client := NewVPPClient()
// Should be safe to call disconnect on unconnected client
client.Disconnect()
if client.IsConnected() {
t.Error("Client should not be connected after Disconnect()")
}
}
func TestVPPClientNewAPIChannelWithoutConnection(t *testing.T) {
client := NewVPPClient()
_, err := client.NewAPIChannel()
if err == nil {
t.Error("NewAPIChannel() should return error when not connected")
}
vppErr, ok := err.(*VPPError)
if !ok {
t.Errorf("Expected VPPError, got %T", err)
}
if vppErr.Message != "API connection not established" {
t.Errorf("Expected specific error message, got: %s", vppErr.Message)
}
}
func TestVPPClientCheckLivenessWithoutConnection(t *testing.T) {
client := NewVPPClient()
if client.CheckLiveness() {
t.Error("CheckLiveness() should return false when not connected")
}
}
func TestVPPError(t *testing.T) {
err := &VPPError{Message: "test error"}
if err.Error() != "test error" {
t.Errorf("VPPError.Error() returned %q, expected %q", err.Error(), "test error")
}
}
func TestVPPClientConnectWithInvalidPaths(t *testing.T) {
// Save original values
origApiAddr := *ApiAddr
origStatsAddr := *StatsAddr
// Set invalid paths
*ApiAddr = "/tmp/nonexistent_api.sock"
*StatsAddr = "/tmp/nonexistent_stats.sock"
// Restore original values after test
defer func() {
*ApiAddr = origApiAddr
*StatsAddr = origStatsAddr
}()
client := NewVPPClient()
err := client.Connect()
if err == nil {
t.Error("Connect() should fail with invalid socket paths")
client.Disconnect() // Clean up if somehow it connected
}
if client.IsConnected() {
t.Error("Client should not be connected after failed Connect()")
}
}