Driver

Simultaneous Start-up

Required files:

test_plan.py

#!/usr/bin/env python
"""
This example is to demonstrate driver scheduling following dependencies.
"""

import sys

from testplan import test_plan
from testplan.common.utils.context import context
from testplan.report.testing.styles import Style, StyleEnum
from testplan.testing.multitest import MultiTest
from testplan.testing.multitest.driver.tcp import TCPServer, TCPClient

from suites import MultiTCPSuite

OUTPUT_STYLE = Style(StyleEnum.ASSERTION_DETAIL, StyleEnum.ASSERTION_DETAIL)


# Hard-coding `pdf_path`, 'stdout_style' and 'pdf_style' so that the
# downloadable example gives meaningful and presentable output.
# NOTE: this programmatic arguments passing approach will cause Testplan
# to ignore any command line arguments related to that functionality.
@test_plan(
    name="TCPConnections",
    pdf_path="report.pdf",
    stdout_style=OUTPUT_STYLE,
    pdf_style=OUTPUT_STYLE,
)
def main(plan):
    """
    Testplan decorated main function to add and execute 2 MultiTests.

    :return: Testplan result object.
    :rtype:  ``testplan.base.TestplanResult``
    """
    server_1 = TCPServer(name="server_1")
    client_1 = TCPClient(
        name="client_1",
        host=context("server_1", "{{host}}"),
        port=context("server_1", "{{port}}"),
    )
    server_2 = TCPServer(name="server_2")
    client_2 = TCPClient(
        name="client_2",
        host=context("server_2", "{{host}}"),
        port=context("server_2", "{{port}}"),
    )
    client_3 = TCPClient(
        name="client_3",
        host=context("server_2", "{{host}}"),
        port=context("server_2", "{{port}}"),
    )

    # If driver A is a dependency for driver B to start, we put driver A in the key
    # of dependencies dictionary and driver B as its corresponding value, so
    # visually driver A appears before driver B.

    # Here server_1 and server_2 will be started simutaneously to reduce the
    # overall test running time while not violating the dependencies.

    plan.add(
        MultiTest(
            name="MultiTCPDrivers",
            suites=[MultiTCPSuite()],
            environment=[
                server_1,
                server_2,
                client_1,
                client_2,
                client_3,
            ],
            dependencies={
                server_1: client_1,
                server_2: (client_2, client_3),
            },
        )
    )


if __name__ == "__main__":
    res = main()
    print("Exiting code: {}".format(res.exit_code))
    sys.exit(res.exit_code)

suites.py

"""Tests TCP communication among multiple servers and multiple clients."""

from testplan.testing.multitest import testsuite, testcase


@testsuite
class MultiTCPSuite:
    """TCP tests for multiple servers and multiple clients."""

    @testcase
    def test_send_and_receive_msg(self, env, result):
        """
        Client sends a message, server received and responds back.
        """
        env.client_1.send_text("zhengshan xiaozhong")
        env.server_1.accept_connection()
        result.equal("zhengshan xiaozhong", env.server_1.receive_text())
        env.server_1.send_text("good black tea")
        result.equal("good black tea", env.client_1.receive_text())

        env.client_2.send_text("whisky")
        conn_2 = env.server_2.accept_connection()
        env.client_3.send_text("soda water")
        conn_3 = env.server_2.accept_connection()
        result.equal("whisky", env.server_2.receive_text(conn_idx=conn_2))
        result.equal("soda water", env.server_2.receive_text(conn_idx=conn_3))
        env.server_2.send_text("good highball", conn_idx=conn_2)
        env.server_2.send_text("good highball", conn_idx=conn_3)
        result.equal("good highball", env.client_2.receive_text())
        result.equal("good highball", env.client_3.receive_text())

Environment Builder

Required files:

test_plan.py

#!/usr/bin/env python
"""
This example demonstrates usage of callable object to construct environment,
intial_context and dependencies for a multitest at runtime.
"""

import sys

from testplan import test_plan
from testplan.testing.multitest import MultiTest
from env_builder import EnvBuilder
from suites import TestOneClient, TestTwoClients


@test_plan(name="EnvBuilderExample")
def main(plan):

    env_builder1 = EnvBuilder("One Client", ["client1", "server1"])
    env_builder2 = EnvBuilder("Two Clients", ["client1", "client2", "server1"])
    plan.add(
        MultiTest(
            name="TestOneClient",
            suites=[TestOneClient()],
            environment=env_builder1.build_env,
            dependencies=env_builder1.build_deps,
            initial_context=env_builder1.init_ctx,
        )
    )

    plan.add(
        MultiTest(
            name="TestTwoClients",
            suites=[TestTwoClients()],
            environment=env_builder2.build_env,
            dependencies=env_builder2.build_deps,
            initial_context=env_builder2.init_ctx,
        )
    )


if __name__ == "__main__":
    res = main()
    print("Exiting code: {}".format(res.exit_code))
    sys.exit(res.exit_code)

suites.py

from testplan.testing.multitest import testsuite, testcase


@testsuite
class TestOneClient:
    def setup(self, env, result):
        result.log(f"Testing with [{env.env_name}] env")
        env.server1.accept_connection()

    @testcase
    def test_send_and_receive_msg(self, env, result):
        env.client1.send_text("hi server")
        result.equal("hi server", env.server1.receive_text())

        env.server1.send_text("hi client")
        result.equal("hi client", env.client1.receive_text())


@testsuite
class TestTwoClients:
    def setup(self, env, result):
        result.log(f"Testing with [{env.env_name}] env")

        env.client1.connect()
        self.conn1 = env.server1.accept_connection()

        env.client2.connect()
        self.conn2 = env.server1.accept_connection()

    @testcase
    def test_send_and_receive_msg(self, env, result):
        env.client1.send_text("hi server from client1")
        env.client2.send_text("hi server from client2")

        result.equal(
            "hi server from client1",
            env.server1.receive_text(conn_idx=self.conn1),
        )
        result.equal(
            "hi server from client2",
            env.server1.receive_text(conn_idx=self.conn2),
        )

        env.server1.send_text("hi client1", conn_idx=self.conn1)
        env.server1.send_text("hi client2", conn_idx=self.conn2)

        result.equal("hi client1", env.client1.receive_text())
        result.equal("hi client2", env.client2.receive_text())

env_builder.py

"""
This demonstrates one possible way of implementing of EnvBuilder class.
"""

from testplan.common.utils.context import context
from testplan.testing.multitest.driver.tcp import TCPClient, TCPServer


class EnvBuilder:
    def __init__(self, env_name: str, drivers: list):
        """
        :param env_name: name of this env builder
        :param drivers: list of drivers to be created
        """
        self.env_name = env_name
        self.drivers = drivers
        self.client_auto_connect = False if len(self.drivers) == 3 else True
        self._client1 = None
        self._client2 = None
        self._server1 = None

    def build_env(self):
        return [getattr(self, driver_name) for driver_name in self.drivers]

    def init_ctx(self):
        return {"env_name": self.env_name}

    def build_deps(self):
        if len(self.drivers) == 2:
            return {self.server1: self.client1}
        elif len(self.drivers) == 3:
            return {self.server1: (self.client1, self.client2)}

    @property
    def client1(self):
        if not self._client1:
            self._client1 = TCPClient(
                name="client1",
                host=context("server1", "{{host}}"),
                port=context("server1", "{{port}}"),
                connect_at_start=self.client_auto_connect,
            )
        return self._client1

    @property
    def client2(self):
        if not self._client2:
            self._client2 = TCPClient(
                name="client2",
                host=context("server1", "{{host}}"),
                port=context("server1", "{{port}}"),
                connect_at_start=self.client_auto_connect,
            )
        return self._client2

    @property
    def server1(self):
        if not self._server1:
            self._server1 = TCPServer(name="server1")
        return self._server1