From c554983436bd7b61f9d5d7543d9cd498c0d002c7 Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Mon, 5 Jun 2023 13:27:34 -0700 Subject: [PATCH] docs: adding tutorial 4 --- docs/tutorials/tutorial4.md | 121 ++++++++++++++++++ mkdocs.yml | 1 + .../tutorials/tutorial4/tests/conftest.py | 24 ++++ .../tutorials/tutorial4/tests/test_ping.py | 35 +++++ 4 files changed, 181 insertions(+) create mode 100644 docs/tutorials/tutorial4.md create mode 100644 package/examples/tutorials/tutorial4/tests/conftest.py create mode 100644 package/examples/tutorials/tutorial4/tests/test_ping.py diff --git a/docs/tutorials/tutorial4.md b/docs/tutorials/tutorial4.md new file mode 100644 index 00000000..77ac1c94 --- /dev/null +++ b/docs/tutorials/tutorial4.md @@ -0,0 +1,121 @@ +# Tutorial 4 - Tests + +## Overview + +A use case for CORE would be to help automate integration tests for running +software within a network. This tutorial covers using CORE with the python +pytest testing framework. It will show how you can define tests, for different +use cases to validate software and outcomes within a defined network. Using +pytest, you would create tests using all the standard pytest functionality. +Creating a test file, and then defining test functions to run. For these tests, +we are leveraging the CORE library directly and the API it provides. + +Refer to the [pytest documentation](https://docs.pytest.org) for indepth +information on how to write tests with pytest. + +## Files + +A directory is used for containing your tests. Within this directory we need a +**conftest.py**, which pytest will pick up to help define and provide +test fixtures, which will be leveraged within our tests. + +* tests + * conftest.py - file used by pytest to define fixtures, which can be shared across tests + * test_ping.py - defines test classes/functions to run + +## Test Fixtures + +Below are the definitions for fixture you can define to facilitate and make +creating CORE based tests easier. + +The global session fixture creates one **CoreEmu** object for the entire +test session, yields it for testing, and calls shutdown when everything +is over. + +``` python +@pytest.fixture(scope="session") +def global_session(): + core = CoreEmu() + session = core.create_session() + session.set_state(EventTypes.CONFIGURATION_STATE) + yield session + core.shutdown() +``` + +The regular session fixture leverages the global session fixture. It +will set the correct state for each test case, yield the session for a test, +and then clear the session after a test finishes to prepare for the next +test. + +``` python +@pytest.fixture +def session(global_session): + global_session.set_state(EventTypes.CONFIGURATION_STATE) + yield global_session + global_session.clear() +``` + +The ip prefixes fixture help provide a preconfigured convenience for +creating and assigning interfaces to nodes, when creating your network +within a test. The address subnet can be whatever you desire. + +``` python +@pytest.fixture(scope="session") +def ip_prefixes(): + return IpPrefixes(ip4_prefix="10.0.0.0/24") +``` + +## Test Functions + +Within a pytest test file, you have the freedom to create any kind of +test you like, but they will all follow a similar formula. + +* define a test function that will leverage the session and ip prefixes fixtures +* then create a network to test, using the session fixture +* run commands within nodes as desired, to test out your use case +* validate command result or output for expected behavior to pass or fail + +In the test below, we create a simple 2 node wired network and validate +node1 can ping node2 successfully. + +``` python +def test_success(self, session: Session, ip_prefixes: IpPrefixes): + # create nodes + node1 = session.add_node(CoreNode) + node2 = session.add_node(CoreNode) + + # link nodes together + iface1_data = ip_prefixes.create_iface(node1) + iface2_data = ip_prefixes.create_iface(node2) + session.add_link(node1.id, node2.id, iface1_data, iface2_data) + + # ping node, expect a successful command + node1.cmd(f"ping -c 1 {iface2_data.ip4}") +``` + +## Install Pytest + +Since we are running an automated test within CORE, we will need to install +pytest within the python interpreter used by CORE. + +``` shell +sudo /opt/core/venv/bin/python -m pip install pytest +``` + +## Running Tests + +You can run your own or the provided tests, by running the following. + +``` shell +cd +sudo /opt/core/venv/bin/python -m pytest -v +``` + +If you run the provided tests, you would expect to see the two tests +running and passing. + +``` shell +tests/test_ping.py::TestPing::test_success PASSED [ 50%] +tests/test_ping.py::TestPing::test_failure PASSED [100%] +``` + diff --git a/mkdocs.yml b/mkdocs.yml index d44b7ca2..74bfd331 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -41,6 +41,7 @@ nav: - Tutorials: - Setup: tutorials/setup.md - Tutorial 1: tutorials/tutorial1.md + - Tutorial 4: tutorials/tutorial4.md - Detailed Topics: - GUI: gui.md - Node Types: diff --git a/package/examples/tutorials/tutorial4/tests/conftest.py b/package/examples/tutorials/tutorial4/tests/conftest.py new file mode 100644 index 00000000..fcd558aa --- /dev/null +++ b/package/examples/tutorials/tutorial4/tests/conftest.py @@ -0,0 +1,24 @@ +import pytest +from core.emulator.coreemu import CoreEmu +from core.emulator.data import IpPrefixes +from core.emulator.enumerations import EventTypes + + +@pytest.fixture(scope="session") +def global_session(): + core = CoreEmu() + session = core.create_session() + yield session + core.shutdown() + + +@pytest.fixture +def session(global_session): + global_session.set_state(EventTypes.CONFIGURATION_STATE) + yield global_session + global_session.clear() + + +@pytest.fixture(scope="session") +def ip_prefixes(): + return IpPrefixes(ip4_prefix="10.0.0.0/24") diff --git a/package/examples/tutorials/tutorial4/tests/test_ping.py b/package/examples/tutorials/tutorial4/tests/test_ping.py new file mode 100644 index 00000000..afb9f8cd --- /dev/null +++ b/package/examples/tutorials/tutorial4/tests/test_ping.py @@ -0,0 +1,35 @@ +import pytest +from core.emulator.data import IpPrefixes, LinkOptions +from core.emulator.session import Session +from core.errors import CoreCommandError +from core.nodes.base import CoreNode + + +class TestPing: + def test_success(self, session: Session, ip_prefixes: IpPrefixes): + # create nodes + node1 = session.add_node(CoreNode) + node2 = session.add_node(CoreNode) + + # link nodes together + iface1_data = ip_prefixes.create_iface(node1) + iface2_data = ip_prefixes.create_iface(node2) + session.add_link(node1.id, node2.id, iface1_data, iface2_data) + + # ping node, expect a successful command + node1.cmd(f"ping -c 1 {iface2_data.ip4}") + + def test_failure(self, session: Session, ip_prefixes: IpPrefixes): + # create nodes + node1 = session.add_node(CoreNode) + node2 = session.add_node(CoreNode) + + # link nodes together + iface1_data = ip_prefixes.create_iface(node1) + iface2_data = ip_prefixes.create_iface(node2) + options = LinkOptions(loss=100.0) + session.add_link(node1.id, node2.id, iface1_data, iface2_data, options) + + # ping node, expect command to fail and raise exception due to 100% loss + with pytest.raises(CoreCommandError): + node1.cmd(f"ping -c 1 {iface2_data.ip4}")