Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Qa wrappers #19

Open
wants to merge 10 commits into
base: master
Choose a base branch
from
76 changes: 76 additions & 0 deletions testlibs/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
# Testlibs for Go-Waku Bindings

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe you should add the Key Components section from PR description here as well, what do you think?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

right. since the Key Components contain more details it's better to include it in the README file too

Done


`testlibs` is a dedicated testing framework for the [Go-Waku Bindings](https://github.com/waku-org/waku-go-bindings). It provides an organized structure for writing and executing tests to validate the behavior of Waku nodes and their associated functionalities.

## Overview

The primary goal of `testlibs` is to simplify and enhance the testing process for Go-Waku Bindings by offering:

- **Test Wrappers**: High-level abstractions for managing Waku nodes, including operations like starting, stopping, and destroying nodes.
- **Peer Management**: Tools for validating peer-to-peer connections, such as connecting, disconnecting, and verifying connected peers.
- **Relay Protocol Testing**: Functions to test relay features like subscribing and unsubscribing to pubsub topics and checking relay peer connections.
- **Utility Functions**: Logging and helper functions to streamline debugging and test execution.
## Key Components

### 1. Wrappers

The `wrapper` files define abstractions around the `WakuNode` to streamline node management and interaction during testing. Key functionalities include:

- **Node Lifecycle Management**:
- `Wrappers_StartWakuNode`: Starts a Waku node with a custom or default configuration.
- `Wrappers_Stop`: Stops the Waku node gracefully.
- `Wrappers_Destroy`: Cleans up and destroys the Waku node.
- `Wrappers_StopAndDestroy`: Combines stop and destroy operations for efficient cleanup.

- **Peer Management**:
- `Wrappers_ConnectPeer`: Simplifies connecting nodes by taking a `WakuNode` instance instead of a `peerID`, constructing the `peerID` and connection details internally.
- `Wrappers_DisconnectPeer`: Disconnects a Waku node from a target peer with validation and error handling.
- `Wrappers_GetConnectedPeers`: Retrieves a list of peers connected to the Waku node.
- `Wrappers_GetNumConnectedRelayPeers`: Returns the number of relay peers connected for a specific pubsub topic.

- **Relay Subscription**:
- `Wrappers_RelaySubscribe`: Subscribes the Waku node to a given pubsub topic, ensuring subscription and verifying peer connections.
- `Wrappers_RelayUnsubscribe`: Unsubscribes from a specific pubsub topic and validates the unsubscription process.

---

### 2. Utilities

The `utilities` package provides helper functions and constants to facilitate testing. Key highlights include:

- **Default Configuration**:
- `DefaultWakuConfig`: Provides a baseline Waku node configuration with sensible defaults for testing purposes.

- **Logging Support**:
- Centralized logging via `zap` for debugging during tests.
- Debug-level messages are used extensively to trace the flow and identify issues.

- **Port Management**:
- `GenerateUniquePort`: Dynamically allocates unique ports for Waku nodes during tests to avoid conflicts.

- **Timeout and Error Handling**:
- Constants for peer connection timeouts.
- Enhanced error messaging for debugging failures in Waku node operations.

---

### 3. Test Files

#### Relay Tests (`relay_test.go`)
- **Highlights**:
- Tests the behavior of the relay protocol.
- Example: `TestRelaySubscribeToDefaultTopic` validates subscription to the default pubsub topic and ensures connected relay peers increase.

#### Peer Connection Tests (`Peers_connection_test.go`)
- **Highlights**:
- Simplifies peer connectivity testing using the wrappers.
- Example: `TestConnectMultipleNodesToSingleNode` verifies multiple nodes can connect to a single node efficiently.

#### Basic Node Tests (`Nodes_basic_test.go`)
- **Highlights**:
- Validates fundamental node lifecycle management using wrapper APIs.
- Example: `TestBasicWakuNodes` covers node creation, startup, and cleanup using `Wrappers_StartWakuNode` and `Wrappers_StopAndDestroy`.

## Purpose

This framework is designed to ensure the reliability and robustness of the Go-Waku Bindings, which enable Go applications to interface with the Waku protocol. With `testlibs`, developers can simulate various conditions, verify expected behaviors, and maintain confidence in their implementations.
282 changes: 282 additions & 0 deletions testlibs/src/main-wrappers.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,282 @@
package testlibs

import (
"context"
"errors"

"github.com/libp2p/go-libp2p/core/peer"

utilities "github.com/waku-org/waku-go-bindings/testlibs/utilities"
"github.com/waku-org/waku-go-bindings/waku"
"go.uber.org/zap"
)

type WakuNodeWrapper struct {
*waku.WakuNode
}

// This function create waku node from config and start it
func Wrappers_StartWakuNode(customCfg *waku.WakuConfig, logger *zap.Logger) (*WakuNodeWrapper, error) {

var nodeCfg waku.WakuConfig

if customCfg == nil {
nodeCfg = *utilities.DefaultWakuConfig
} else {
nodeCfg = *customCfg
}

nodeCfg.Discv5UdpPort = utilities.GenerateUniquePort()
nodeCfg.TcpPort = utilities.GenerateUniquePort()

utilities.Debug("Create node with default config")
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe this log is misleading? because if customCfg is passed, then it's not being created with the default config

node, err := waku.NewWakuNode(&nodeCfg, logger)
if err != nil {
utilities.Error("Can't create node")
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should we add the error in the log? something like

utilities.Error("Can't create node", zap.Error(err))

return nil, err
}

utilities.Debug("Attempting to start WakuNode")
wrapper := &WakuNodeWrapper{WakuNode: node}
if err := utilities.CheckWakuNodeNull(logger, wrapper.WakuNode); err != nil {
utilities.Error("Failed to start WakuNode", zap.Error(err))
return nil, err
}

err = wrapper.WakuNode.Start()
if err != nil {
utilities.Error("Failed to start WakuNode", zap.Error(err))
return nil, err
}
utilities.Debug("Successfully started WakuNode")

return wrapper, nil
}

// Stops the WakuNode.
func (node *WakuNodeWrapper) Wrappers_Stop() error {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As this is a method of WakuNodeWrapper, maybe we can name it Stop() instead of Wrappers_Stop()?

Same for all the methods of WakuNodeWrapper, the Wrappers prefix might be redundant

if err := utilities.CheckWakuNodeNull(nil, node.WakuNode); err != nil {
utilities.Error("Failed to stop WakuNode", zap.Error(err))
return err
}

utilities.Debug("Attempting to stop WakuNode")
err := node.WakuNode.Stop()
if err != nil {
utilities.Error("Failed to stop WakuNode", zap.Error(err))
return err
}

utilities.Debug("Successfully stopped WakuNode")
return nil
}

// Destroys the WakuNode.
func (node *WakuNodeWrapper) Wrappers_Destroy() error {
if err := utilities.CheckWakuNodeNull(nil, node.WakuNode); err != nil {
utilities.Error("Failed to destroy WakuNode", zap.Error(err))
return err
}

utilities.Debug("Attempting to destroy WakuNode")
err := node.WakuNode.Destroy()
if err != nil {
utilities.Error("Failed to destroy WakuNode", zap.Error(err))
return err
}

utilities.Debug("Successfully destroyed WakuNode")
return nil
}

func (wrapper *WakuNodeWrapper) Wrappers_StopAndDestroy() error {
if err := utilities.CheckWakuNodeNull(nil, wrapper.WakuNode); err != nil {
utilities.Error("Failed to stop or destroy WakuNode", zap.Error(err))
return err
}

utilities.Debug("Attempting to stop WakuNode")
err := wrapper.Stop()
if err != nil {
utilities.Error("Failed to stop WakuNode", zap.Error(err))
return err
}

utilities.Debug("Attempting to destroy WakuNode")
err = wrapper.Destroy()
if err != nil {
utilities.Error("Failed to destroy WakuNode", zap.Error(err))
return err
}

utilities.Debug("Successfully stopped and destroyed WakuNode")
return nil
}

func (wrapper *WakuNodeWrapper) Wrappers_GetConnectedPeers() ([]peer.ID, error) {
if err := utilities.CheckWakuNodeNull(nil, wrapper.WakuNode); err != nil {
utilities.Error("Cannot proceed; node is nil", zap.Error(err))
return nil, err
}

peerID, err := wrapper.WakuNode.PeerID()
if err != nil {
utilities.Error("Failed to get PeerID of node", zap.Error(err))
return nil, err
}

utilities.Debug("Getting number of connected peers to node", zap.String("node", peerID.String()))

peers, err := wrapper.WakuNode.GetConnectedPeers()
if err != nil {
utilities.Error("Failed to get connected peers", zap.Error(err))
return nil, err
}

utilities.Debug("Successfully fetched connected peers",
zap.Int("count", len(peers)),
)
return peers, nil
}

func (wrapper *WakuNodeWrapper) Wrappers_GetNumConnectedRelayPeers(optPubsubTopic ...string) (int, error) {
utilities.Debug("Wrappers_GetNumConnectedRelayPeers called")

if err := utilities.CheckWakuNodeNull(nil, wrapper.WakuNode); err != nil {
utilities.Error("Cannot proceed; node is nil", zap.Error(err))
return 0, err
}

numPeers, err := wrapper.WakuNode.GetNumConnectedRelayPeers(optPubsubTopic...)
if err != nil {
utilities.Error("Failed to get number of connected relay peers", zap.Error(err))
return 0, err
}

utilities.Debug("Successfully fetched number of connected relay peers",
zap.Int("count", numPeers),
)
return numPeers, nil
}

func (wrapper *WakuNodeWrapper) Wrappers_ConnectPeer(targetNode *WakuNodeWrapper) error {

utilities.Debug("Connect node to peer")
if err := utilities.CheckWakuNodeNull(nil, wrapper.WakuNode); err != nil {
utilities.Error("Cannot call Connect; caller node is nil", zap.Error(err))
return err
}
if err := utilities.CheckWakuNodeNull(nil, targetNode.WakuNode); err != nil {
utilities.Error("Cannot connect; target node is nil", zap.Error(err))
return err
}

targetPeerID, err := targetNode.WakuNode.PeerID()
if err != nil {
utilities.Error("Failed to get PeerID of target node", zap.Error(err))
return err
}

utilities.Debug("Get connected peers before attempting to connect")

connectedPeersBefore, err := wrapper.Wrappers_GetConnectedPeers()
if err != nil {
utilities.Debug("Could not fetch connected peers before connecting (might be none yet)", zap.Error(err))
} else {
utilities.Debug("Connected peers before connecting", zap.Int("count", len(connectedPeersBefore)))
}

utilities.Debug("Attempt to connect to the target node")
ctx, cancel := context.WithTimeout(context.Background(), utilities.ConnectPeerTimeout)
defer cancel()

targetAddr, err := targetNode.WakuNode.ListenAddresses()
if err != nil || len(targetAddr) == 0 {
utilities.Error("Failed to get listen addresses for target node", zap.Error(err))
return errors.New("target node has no listen addresses")
}

utilities.Debug("Connecting to peer", zap.String("address", targetAddr[0].String()))
err = wrapper.WakuNode.Connect(ctx, targetAddr[0])
if err != nil {
utilities.Error("Failed to connect to peer", zap.Error(err))
return err
}

utilities.Debug("Get connected peers after attempting to connect")
connectedPeersAfter, err := wrapper.Wrappers_GetConnectedPeers()
if err != nil {
utilities.Error("Failed to get connected peers after connecting", zap.Error(err))
return err
}

utilities.Debug("Connected peers after connecting", zap.Int("count", len(connectedPeersAfter)))

utilities.Debug("Check if the target peer is now connected")
isConnected := false
for _, peerID := range connectedPeersAfter {
if peerID == targetPeerID {
isConnected = true
break
}
}

if !isConnected {
err := errors.New("failed to connect; target peer is not in connected peers list")
utilities.Error("Connect operation failed", zap.Error(err))
return err
}

utilities.Debug("Successfully connected to target peer", zap.String("targetPeerID", targetPeerID.String()))
return nil
}

func (wrapper *WakuNodeWrapper) Wrappers_DisconnectPeer(target *WakuNodeWrapper) error {

if err := utilities.CheckWakuNodeNull(nil, wrapper.WakuNode); err != nil {
utilities.Error("Cannot call Disconnect; caller node is nil", zap.Error(err))
return err
}
if err := utilities.CheckWakuNodeNull(nil, target.WakuNode); err != nil {
utilities.Error("Cannot disconnect; target node is nil", zap.Error(err))
return err
}

utilities.Debug("Check if nodes are peers first")

peerID, err := target.WakuNode.PeerID()
if err != nil {
utilities.Error("Failed to get PeerID of target node", zap.Error(err))
return err
}

connectedPeers, err := wrapper.Wrappers_GetConnectedPeers()
if err != nil {
utilities.Error("Failed to get connected peers", zap.Error(err))
return err
}

isPeer := false
for _, connectedPeerID := range connectedPeers {
if connectedPeerID == peerID {
isPeer = true
break
}
}

if !isPeer {
err = errors.New("nodes are not connected as peers")
utilities.Error("Cannot disconnect; nodes are not peers", zap.Error(err))
return err
}

utilities.Debug("Nodes are peers.. attempting to disconnect")
err = wrapper.WakuNode.DisconnectPeerByID(peerID)
if err != nil {
utilities.Error("Failed to disconnect peer", zap.Error(err))
return err
}

utilities.Debug("Successfully disconnected peer", zap.String("peerID", peerID.String()))
return nil
}
Loading