Add parallel test run info to test/doc
[vpp.git] / test / doc / overview.rst
1 .. _unittest: https://docs.python.org/2/library/unittest.html
2 .. _TestCase: https://docs.python.org/2/library/unittest.html#unittest.TestCase
3 .. _AssertionError: https://docs.python.org/2/library/exceptions.html#exceptions.AssertionError
4 .. _SkipTest: https://docs.python.org/2/library/unittest.html#unittest.SkipTest
5 .. _virtualenv: http://docs.python-guide.org/en/latest/dev/virtualenvs/
6 .. _scapy: http://www.secdev.org/projects/scapy/
7 .. _logging: https://docs.python.org/2/library/logging.html
8 .. _process: https://docs.python.org/2/library/multiprocessing.html#the-process-class
9 .. _pipes: https://docs.python.org/2/library/multiprocessing.html#multiprocessing.Pipe
10 .. _managed: https://docs.python.org/2/library/multiprocessing.html#managers
11
12 .. |vtf| replace:: VPP Test Framework
13
14 |vtf|
15 =====
16
17 .. contents::
18    :local:
19    :depth: 1
20
21 Overview
22 ########
23
24 The goal of the |vtf| is to ease writing, running and debugging
25 unit tests for the VPP. For this, python was chosen as a high level language
26 allowing rapid development with scapy_ providing the necessary tool for creating
27 and dissecting packets.
28
29 Anatomy of a test case
30 ######################
31
32 Python's unittest_ is used as the base framework upon which the VPP test
33 framework is built. A test suite in the |vtf| consists of multiple classes
34 derived from `VppTestCase`, which is itself derived from TestCase_.
35 The test class defines one or more test functions, which act as test cases.
36
37 Function flow when running a test case is:
38
39 1. `setUpClass <VppTestCase.setUpClass>`:
40    This function is called once for each test class, allowing a one-time test
41    setup to be executed. If this functions throws an exception,
42    none of the test functions are executed.
43 2. `setUp <VppTestCase.setUp>`:
44    The setUp function runs before each of the test functions. If this function
45    throws an exception other than AssertionError_ or SkipTest_, then this is
46    considered an error, not a test failure.
47 3. *test_<name>*:
48    This is the guts of the test case. It should execute the test scenario
49    and use the various assert functions from the unittest framework to check
50    necessary. Multiple test_<name> methods can exist in a test case.
51 4. `tearDown <VppTestCase.tearDown>`:
52    The tearDown function is called after each test function with the purpose
53    of doing partial cleanup.
54 5. `tearDownClass <VppTestCase.tearDownClass>`:
55    Method called once after running all of the test functions to perform
56    the final cleanup.
57
58 Logging
59 #######
60
61 Each test case has a logger automatically created for it, stored in
62 'logger' property, based on logging_. Use the logger's standard methods
63 debug(), info(), error(), ... to emit log messages to the logger.
64
65 All the log messages go always into a log file in temporary directory
66 (see below).
67
68 To control the messages printed to console, specify the V= parameter.
69
70 .. code-block:: shell
71
72    make test         # minimum verbosity
73    make test V=1     # moderate verbosity
74    make test V=2     # maximum verbosity
75
76 Parallel test execution
77 #######################
78
79 |vtf| test suites can be run in parallel. Each test suite is executed
80 in a separate process spawned by Python multiprocessing process_.
81
82 The results from child test suites are sent to parent through pipes_, which are
83 aggregated and summarized at the end of the run.
84
85 Stdout, stderr and logs logged in child processes are redirected to individual
86 parent managed_ queues. The data from these queues are then emitted to stdout
87 of the parent process in the order the test suites have finished. In case there
88 are no finished test suites (such as at the beginning of the run), the data
89 from last started test suite are emitted in real time.
90
91 To enable parallel test run, specify the number of parallel processes:
92
93 .. code-block:: shell
94
95    make test TEST_JOBS=n       # at most n processes will be spawned
96    make test TEST_JOBS=auto    # chosen based on the number of cores
97                                # and the size of shared memory
98
99 Test temporary directory and VPP life cycle
100 ###########################################
101
102 Test separation is achieved by separating the test files and vpp instances.
103 Each test creates a temporary directory and it's name is used to create
104 a shared memory prefix which is used to run a VPP instance.
105 The temporary directory name contains the testcase class name for easy
106 reference, so for testcase named 'TestVxlan' the directory could be named
107 e.g. vpp-unittest-TestVxlan-UNUP3j.
108 This way, there is no conflict between any other VPP instances running
109 on the box and the test VPP. Any temporary files created by the test case
110 are stored in this temporary test directory.
111
112 The test temporary directory holds the following interesting files:
113
114 * log.txt - this contains the logger output on max verbosity
115 * pg*_in.pcap - last injected packet stream into VPP, named after the interface,
116   so for pg0, the file will be named pg0_in.pcap
117 * pg*_out.pcap - last capture file created by VPP for interface, similarly,
118   named after the interface, so for e.g. pg1, the file will be named
119   pg1_out.pcap
120 * history files - whenever the capture is restarted or a new stream is added,
121   the existing files are rotated and renamed, soo all the pcap files
122   are always saved for later debugging if needed
123 * core - if vpp dumps a core, it'll be stored in the temporary directory
124 * vpp_stdout.txt - file containing output which vpp printed to stdout
125 * vpp_stderr.txt - file containing output which vpp printed to stderr
126
127 *NOTE*: existing temporary directories named vpp-unittest-* are automatically
128 removed when invoking 'make test*' or 'make retest*' to keep the temporary
129 directory clean.
130
131 Virtual environment
132 ###################
133
134 Virtualenv_ is a python module which provides a means to create an environment
135 containing the dependencies required by the |vtf|, allowing a separation
136 from any existing system-wide packages. |vtf|'s Makefile automatically
137 creates a virtualenv_ inside build-root and installs the required packages
138 in that environment. The environment is entered whenever executing a test
139 via one of the make test targets.
140
141 Naming conventions
142 ##################
143
144 Most unit tests do some kind of packet manipulation - sending and receiving
145 packets between VPP and virtual hosts connected to the VPP. Referring
146 to the sides, addresses, etc. is always done as if looking from the VPP side,
147 thus:
148
149 * *local_* prefix is used for the VPP side.
150   So e.g. `local_ip4 <VppInterface.local_ip4>` address is the IPv4 address
151   assigned to the VPP interface.
152 * *remote_* prefix is used for the virtual host side.
153   So e.g. `remote_mac <VppInterface.remote_mac>` address is the MAC address
154   assigned to the virtual host connected to the VPP.
155
156 Automatically generated addresses
157 #################################
158
159 To send packets, one needs to typically provide some addresses, otherwise
160 the packets will be dropped. The interface objects in |vtf| automatically
161 provide addresses based on (typically) their indexes, which ensures
162 there are no conflicts and eases debugging by making the addressing scheme
163 consistent.
164
165 The developer of a test case typically doesn't need to work with the actual
166 numbers, rather using the properties of the objects. The addresses typically
167 come in two flavors: '<address>' and '<address>n' - note the 'n' suffix.
168 The former address is a Python string, while the latter is translated using
169 socket.inet_pton to raw format in network byte order - this format is suitable
170 for passing as an argument to VPP APIs.
171
172 e.g. for the IPv4 address assigned to the VPP interface:
173
174 * local_ip4 - Local IPv4 address on VPP interface (string)
175 * local_ip4n - Local IPv4 address - raw, suitable as API parameter.
176
177 These addresses need to be configured in VPP to be usable using e.g.
178 `config_ip4` API. Please see the documentation to `VppInterface` for more
179 details.
180
181 By default, there is one remote address of each kind created for L3:
182 remote_ip4 and remote_ip6. If the test needs more addresses, because it's
183 simulating more remote hosts, they can be generated using
184 `generate_remote_hosts` API and the entries for them inserted into the ARP
185 table using `configure_ipv4_neighbors` API.
186
187 Packet flow in the |vtf|
188 ########################
189
190 Test framework -> VPP
191 ~~~~~~~~~~~~~~~~~~~~~
192
193 |vtf| doesn't send any packets to VPP directly. Traffic is instead injected
194 using packet-generator interfaces, represented by the `VppPGInterface` class.
195 Packets are written into a temporary .pcap file, which is then read by the VPP
196 and the packets are injected into the VPP world.
197
198 To add a list of packets to an interface, call the `add_stream` method on that
199 interface. Once everything is prepared, call `pg_start` method to start
200 the packet generator on the VPP side.
201
202 VPP -> test framework
203 ~~~~~~~~~~~~~~~~~~~~~
204
205 Similarly, VPP doesn't send any packets to |vtf| directly. Instead, packet
206 capture feature is used to capture and write traffic to a temporary .pcap file,
207 which is then read and analyzed by the |vtf|.
208
209 The following APIs are available to the test case for reading pcap files.
210
211 * `get_capture`: this API is suitable for bulk & batch style of test, where
212   a list of packets is prepared & sent, then the received packets are read
213   and verified. The API needs the number of packets which are expected to
214   be captured (ignoring filtered packets - see below) to know when the pcap
215   file is completely written by the VPP. If using packet infos for verifying
216   packets, then the counts of the packet infos can be automatically used
217   by `get_capture` to get the proper count (in this case the default value
218   None can be supplied as expected_count or ommitted altogether).
219 * `wait_for_packet`: this API is suitable for interactive style of test,
220   e.g. when doing session management, three-way handsakes, etc. This API waits
221   for and returns a single packet, keeping the capture file in place
222   and remembering context. Repeated invocations return following packets
223   (or raise Exception if timeout is reached) from the same capture file
224   (= packets arriving on the same interface).
225
226 *NOTE*: it is not recommended to mix these APIs unless you understand how they
227 work internally. None of these APIs rotate the pcap capture file, so calling
228 e.g. `get_capture` after `wait_for_packet` will return already read packets.
229 It is safe to switch from one API to another after calling `enable_capture`
230 as that API rotates the capture file.
231
232 Automatic filtering of packets:
233 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
234
235 Both APIs (`get_capture` and `wait_for_packet`) by default filter the packet
236 capture, removing known uninteresting packets from it - these are IPv6 Router
237 Advertisments and IPv6 Router Alerts. These packets are unsolicitated
238 and from the point of |vtf| are random. If a test wants to receive these
239 packets, it should specify either None or a custom filtering function
240 as the value to the 'filter_out_fn' argument.
241
242 Common API flow for sending/receiving packets:
243 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
244
245 We will describe a simple scenario, where packets are sent from pg0 to pg1
246 interface, assuming that the interfaces were created using
247 `create_pg_interfaces` API.
248
249 1. Create a list of packets for pg0::
250
251      packet_count = 10
252      packets = create_packets(src=self.pg0, dst=self.pg1,
253                               count=packet_count)
254
255 2. Add that list of packets to the source interface::
256
257      self.pg0.add_stream(packets)
258
259 3. Enable capture on the destination interface::
260
261      self.pg1.enable_capture()
262
263 4. Start the packet generator::
264
265      self.pg_start()
266
267 5. Wait for capture file to appear and read it::
268
269      capture = self.pg1.get_capture(expected_count=packet_count)
270
271 6. Verify packets match sent packets::
272
273      self.verify_capture(send=packets, captured=capture)
274
275 Test framework objects
276 ######################
277
278 The following objects provide VPP abstraction and provide a means to do
279 common tasks easily in the test cases.
280
281 * `VppInterface`: abstract class representing generic VPP interface
282   and contains some common functionality, which is then used by derived classes
283 * `VppPGInterface`: class representing VPP packet-generator interface.
284   The interface is created/destroyed when the object is created/destroyed.
285 * `VppSubInterface`: VPP sub-interface abstract class, containing common
286   functionality for e.g. `VppDot1QSubint` and `VppDot1ADSubint` classes
287
288 How VPP APIs/CLIs are called
289 ############################
290
291 Vpp provides python bindings in a python module called vpp-papi, which the test
292 framework installs in the virtual environment. A shim layer represented by
293 the `VppPapiProvider` class is built on top of the vpp-papi, serving these
294 purposes:
295
296 1. Automatic return value checks:
297    After each API is called, the return value is checked against the expected
298    return value (by default 0, but can be overridden) and an exception
299    is raised if the check fails.
300 2. Automatic call of hooks:
301
302    a. `before_cli <Hook.before_cli>` and `before_api <Hook.before_api>` hooks
303       are used for debug logging and stepping through the test
304    b. `after_cli <Hook.after_cli>` and `after_api <Hook.after_api>` hooks
305       are used for monitoring the vpp process for crashes
306 3. Simplification of API calls:
307    Many of the VPP APIs take a lot of parameters and by providing sane defaults
308    for these, the API is much easier to use in the common case and the code is
309    more readable. E.g. ip_add_del_route API takes ~25 parameters, of which
310    in the common case, only 3 are needed.
311
312 Utility methods
313 ###############
314
315 Some interesting utility methods are:
316
317 * `ppp`: 'Pretty Print Packet' - returns a string containing the same output
318   as Scapy's packet.show() would print
319 * `ppc`: 'Pretty Print Capture' - returns a string containing printout of
320   a capture (with configurable limit on the number of packets printed from it)
321   using `ppp`
322
323 *NOTE*: Do not use Scapy's packet.show() in the tests, because it prints
324 the output to stdout. All output should go to the logger associated with
325 the test case.
326
327 Example: how to add a new test
328 ##############################
329
330 In this example, we will describe how to add a new test case which tests
331 basic IPv4 forwarding.
332
333 1. Add a new file called test_ip4_fwd.py in the test directory, starting
334    with a few imports::
335
336      from framework import VppTestCase
337      from scapy.layers.l2 import Ether
338      from scapy.packet import Raw
339      from scapy.layers.inet import IP, UDP
340      from random import randint
341
342 2. Create a class inherited from the VppTestCase::
343
344      class IP4FwdTestCase(VppTestCase):
345          """ IPv4 simple forwarding test case """
346
347 2. Add a setUpClass function containing the setup needed for our test to run::
348
349          @classmethod
350          def setUpClass(self):
351              super(IP4FwdTestCase, self).setUpClass()
352              self.create_pg_interfaces(range(2))  #  create pg0 and pg1
353              for i in self.pg_interfaces:
354                  i.admin_up()  # put the interface up
355                  i.config_ip4()  # configure IPv4 address on the interface
356                  i.resolve_arp()  # resolve ARP, so that we know VPP MAC
357
358 3. Create a helper method to create the packets to send::
359
360          def create_stream(self, src_if, dst_if, count):
361              packets = []
362              for i in range(count):
363                  # create packet info stored in the test case instance
364                  info = self.create_packet_info(src_if, dst_if)
365                  # convert the info into packet payload
366                  payload = self.info_to_payload(info)
367                  # create the packet itself
368                  p = (Ether(dst=src_if.local_mac, src=src_if.remote_mac) /
369                       IP(src=src_if.remote_ip4, dst=dst_if.remote_ip4) /
370                       UDP(sport=randint(1000, 2000), dport=5678) /
371                       Raw(payload))
372                  # store a copy of the packet in the packet info
373                  info.data = p.copy()
374                  # append the packet to the list
375                  packets.append(p)
376
377              # return the created packet list
378              return packets
379
380 4. Create a helper method to verify the capture::
381
382          def verify_capture(self, src_if, dst_if, capture):
383              packet_info = None
384              for packet in capture:
385                  try:
386                      ip = packet[IP]
387                      udp = packet[UDP]
388                      # convert the payload to packet info object
389                      payload_info = self.payload_to_info(str(packet[Raw]))
390                      # make sure the indexes match
391                      self.assert_equal(payload_info.src, src_if.sw_if_index,
392                                        "source sw_if_index")
393                      self.assert_equal(payload_info.dst, dst_if.sw_if_index,
394                                        "destination sw_if_index")
395                      packet_info = self.get_next_packet_info_for_interface2(
396                                        src_if.sw_if_index,
397                                        dst_if.sw_if_index,
398                                        packet_info)
399                      # make sure we didn't run out of saved packets
400                      self.assertIsNotNone(packet_info)
401                      self.assert_equal(payload_info.index, packet_info.index,
402                                        "packet info index")
403                      saved_packet = packet_info.data  # fetch the saved packet
404                      # assert the values match
405                      self.assert_equal(ip.src, saved_packet[IP].src,
406                                        "IP source address")
407                      # ... more assertions here
408                      self.assert_equal(udp.sport, saved_packet[UDP].sport,
409                                        "UDP source port")
410                  except:
411                      self.logger.error(ppp("Unexpected or invalid packet:",
412                                        packet))
413                      raise
414              remaining_packet = self.get_next_packet_info_for_interface2(
415                         src_if.sw_if_index,
416                         dst_if.sw_if_index,
417                         packet_info)
418              self.assertIsNone(remaining_packet,
419                                "Interface %s: Packet expected from interface "
420                                "%s didn't arrive" % (dst_if.name, src_if.name))
421
422 5. Add the test code to test_basic function::
423
424          def test_basic(self):
425              count = 10
426              # create the packet stream
427              packets = self.create_stream(self.pg0, self.pg1, count)
428              # add the stream to the source interface
429              self.pg0.add_stream(packets)
430              # enable capture on both interfaces
431              self.pg0.enable_capture()
432              self.pg1.enable_capture()
433              # start the packet generator
434              self.pg_start()
435              # get capture - the proper count of packets was saved by
436              # create_packet_info() based on dst_if parameter
437              capture = self.pg1.get_capture()
438              # assert nothing captured on pg0 (always do this last, so that
439              # some time has already passed since pg_start())
440              self.pg0.assert_nothing_captured()
441              # verify capture
442              self.verify_capture(self.pg0, self.pg1, capture)
443
444 6. Run the test by issuing 'make test'.