hs-test: change convention for skipping tests
[vpp.git] / extras / hs-test / README.rst
1 Host stack test framework
2 =========================
3
4 Overview
5 --------
6
7 The goal of the Host stack test framework (**hs-test**) is to ease writing and running end-to-end tests for VPP's Host Stack.
8 End-to-end tests often want multiple VPP instances, network namespaces, different types of interfaces
9 and to execute external tools or commands. With such requirements the existing VPP test framework is not sufficient.
10 For this, ``Go`` was chosen as a high level language, allowing rapid development, with ``Docker`` and ``ip`` being the tools for creating required topology.
11
12 Go's package `testing`_ together with `go test`_ command form the base framework upon which the *hs-test* is built and run.
13
14 Anatomy of a test case
15 ----------------------
16
17 **Prerequisites**:
18
19 * Install hs-test dependencies with ``make install-deps``
20 * Tests use *hs-test*'s own docker image, so building it before starting tests is a prerequisite. Run ``make build[-debug]`` to do so
21 * Docker has to be installed and Go has to be in path of both the running user and root
22 * Root privileges are required to run tests as it uses Linux ``ip`` command for configuring topology
23
24 **Action flow when running a test case**:
25
26 #. It starts with running ``make test``. Optional arguments are VERBOSE, PERSIST (topology configuration isn't cleaned up after test run),
27    and TEST=<test-name> to run specific test.
28 #. ``make list-tests`` (or ``make help``) shows all test names.
29 #. ``go test`` compiles package ``main`` along with any files with names matching the file pattern ``*_test.go``
30    and then runs the resulting test binaries
31 #. The go test framework runs each function matching :ref:`naming convention<test-convention>`. Each of these corresponds to a `test suite`_
32 #. Testify toolkit's ``suite.Run(t *testing.T, suite TestingSuite)`` function runs the suite and does the following:
33
34   #. Suite is initialized. The topology is loaded and configured in this step
35   #. Test suite runs all the tests attached to it
36   #. Execute tear-down functions, which currently consists of stopping running containers
37      and clean-up of test topology
38
39 Adding a test case
40 ------------------
41
42 This describes adding a new test case to an existing suite.
43 For adding a new suite, please see `Modifying the framework`_ below.
44
45 #. To write a new test case, create a file whose name ends with ``_test.go`` or pick one that already exists
46 #. Declare method whose name starts with ``Test`` and specifies its receiver as a pointer to the suite's struct (defined in ``framework_test.go``)
47 #. Implement test behaviour inside the test method. This typically includes the following:
48
49   #. Retrieve a running container in which to run some action. Method ``getContainerByName``
50      from ``HstSuite`` struct serves this purpose
51   #. Interact with VPP through the ``VppInstance`` struct embedded in container. It provides ``vppctl`` method to access debug CLI
52   #. Run arbitrary commands inside the containers with ``exec`` method
53   #. Run other external tool with one of the preexisting functions in the ``utils.go`` file.
54      For example, use ``wget`` with ``startWget`` function
55   #. Use ``exechelper`` or just plain ``exec`` packages to run whatever else
56   #. Verify results of your tests using ``assert`` methods provided by the test suite,
57      implemented by HstSuite struct
58
59 **Example test case**
60
61 Assumed are two docker containers, each with its own VPP instance running. One VPP then pings the other.
62 This can be put in file ``extras/hs-test/my_test.go`` and run with command ``./test -run TestMySuite/TestMyCase``.
63
64 ::
65
66         package main
67
68         import (
69                 "fmt"
70         )
71
72         func (s *MySuite) TestMyCase() {
73                 clientVpp := s.getContainerByName("client-vpp").vppInstance
74
75                 serverVethAddress := s.netInterfaces["server-iface"].AddressString()
76
77                 result := clientVpp.vppctl("ping " + serverVethAddress)
78                 s.assertNotNil(result)
79                 s.log(result)
80         }
81
82 Modifying the framework
83 -----------------------
84
85 **Adding a test suite**
86
87 .. _test-convention:
88
89 #. Adding a new suite takes place in ``framework_test.go`` and by creating a new file for the suite.
90    Naming convention for the suite files is ``suite_name_test.go`` where *name* will be replaced
91    by the actual name
92
93 #. Make a ``struct``, in the suite file, with at least ``HstSuite`` struct as its member.
94    HstSuite provides functionality that can be shared for all suites, like starting containers
95
96         ::
97
98                 type MySuite struct {
99                         HstSuite
100                 }
101
102 #. In suite file, implement ``SetupSuite`` method which testify runs once before starting any of the tests.
103    It's important here to call ``configureNetworkTopology`` method,
104    pass the topology name to the function in a form of file name of one of the *yaml* files in ``topo-network`` folder.
105    Without the extension. In this example, *myTopology* corresponds to file ``extras/hs-test/topo-network/myTopology.yaml``
106    This will ensure network topology, such as network interfaces and namespaces, will be created.
107    Another important method to call is ``loadContainerTopology()`` which will load
108    containers and shared volumes used by the suite. This time the name passed to method corresponds
109    to file in ``extras/hs-test/topo-containers`` folder
110
111         ::
112
113                 func (s *MySuite) SetupSuite() {
114                         // Add custom setup code here
115
116                         s.configureNetworkTopology("myTopology")
117                         s.loadContainerTopology("2peerVeth")
118                 }
119
120 #. In suite file, implement ``SetupTest`` method which gets executed before each test. Starting containers and
121    configuring VPP is usually placed here
122
123         ::
124
125                 func (s *MySuite) SetupTest() {
126                         s.SetupVolumes()
127                         s.SetupContainers()
128                 }
129
130 #. In order for ``go test`` to run this suite, we need to create a normal test function and pass our suite to ``suite.Run``.
131    These functions are placed at the end of ``framework_test.go``
132
133         ::
134
135                 func TestMySuite(t *testing.T) {
136                         var m MySuite
137                         suite.Run(t, &m)
138                 }
139
140 #. Next step is to add test cases to the suite. For that, see section `Adding a test case`_ above
141
142 **Adding a topology element**
143
144 Topology configuration exists as ``yaml`` files in the ``extras/hs-test/topo-network`` and
145 ``extras/hs-test/topo-containers`` folders. Processing of a network topology file for a particular test suite
146 is started by the ``configureNetworkTopology`` method depending on which file's name is passed to it.
147 Specified file is loaded and converted into internal data structures which represent various elements of the topology.
148 After parsing the configuration, framework loops over the elements and configures them one by one on the host system.
149
150 These are currently supported types of network elements.
151
152 * ``netns`` - network namespace
153 * ``veth`` - veth network interface, optionally with target network namespace or IPv4 address
154 * ``bridge`` - ethernet bridge to connect created interfaces, optionally with target network namespace
155 * ``tap`` - tap network interface with IP address
156
157 Similarly, container topology is started by ``loadContainerTopology()``, configuration file is processed
158 so that test suite retains map of defined containers and uses that to start them at the beginning
159 of each test case and stop containers after the test finishes. Container configuration can specify
160 also volumes which allow to share data between containers or between host system and containers.
161
162 Supporting a new type of topology element requires adding code to recognize the new element type during loading.
163 And adding code to set up the element in the host system with some Linux tool, such as *ip*.
164 This should be implemented in ``netconfig.go`` for network and in ``container.go`` for containers and volumes.
165
166 **Communicating between containers**
167
168 When two VPP instances or other applications, each in its own Docker container,
169 want to communicate there are typically two ways this can be done within *hs-test*.
170
171 * Network interfaces. Containers are being created with ``-d --network host`` options,
172   so they are connected with interfaces created in host system
173 * Shared folders. Containers are being created with ``-v`` option to create shared `volumes`_ between host system and containers
174   or just between containers
175
176 Host system connects to VPP instances running in containers using a shared folder
177 where binary API socket is accessible by both sides.
178
179 **Adding an external tool**
180
181 If an external program should be executed as part of a test case, it might be useful to wrap its execution in its own function.
182 These types of functions are placed in the ``utils.go`` file. If the external program is not available by default in Docker image,
183 add its installation to ``extras/hs-test/Dockerfile.vpp`` in ``apt-get install`` command.
184 Alternatively copy the executable from host system to the Docker image, similarly how the VPP executables and libraries are being copied.
185
186 **Skipping tests**
187
188 ``HstSuite`` provides several methods that can be called in tests for skipping it conditionally or unconditionally such as:
189 ``skip()``, ``SkipIfMultiWorker()``, ``SkipUnlessExtendedTestsBuilt()``.
190 However the tests currently run under test suites which set up topology and containers before actual test is run. For the reason of saving
191 test run time it is not advisable to use aforementioned skip methods and instead prefix test name with ``Skip``:
192
193 ::
194
195     func (s *MySuite) SkipTest(){
196
197
198 **Eternal dependencies**
199
200 * Linux tools ``ip``, ``brctl``
201 * Standalone programs ``wget``, ``iperf3`` - since these are downloaded when Docker image is made,
202   they are reasonably up-to-date automatically
203 * Programs in Docker images  - ``envoyproxy/envoy-contrib`` and ``nginx``
204 * ``http_server`` - homegrown application that listens on specified port and sends a test file in response
205 * Non-standard Go libraries - see ``extras/hs-test/go.mod``
206
207 Generally, these will be updated on a per-need basis, for example when a bug is discovered
208 or a new version incompatibility issue occurs.
209
210
211 .. _testing: https://pkg.go.dev/testing
212 .. _go test: https://pkg.go.dev/cmd/go#hdr-Test_packages
213 .. _test suite: https://github.com/stretchr/testify#suite-package
214 .. _volumes: https://docs.docker.com/storage/volumes/
215