6 from scapy.packet import Raw
7 from scapy.layers.l2 import Ether, Dot1Q, GRE
8 from scapy.layers.inet import IP, UDP
9 from scapy.layers.inet6 import IPv6
10 from scapy.volatile import RandMAC, RandIP
12 from framework import VppTestCase, VppTestRunner
13 from vpp_sub_interface import VppDot1QSubint
14 from vpp_gre_interface import VppGreInterface, VppGre6Interface
15 from vpp_ip import DpoProto
16 from vpp_ip_route import VppIpRoute, VppRoutePath, VppIpTable
17 from vpp_papi_provider import L2_VTR_OP
18 from util import ppp, ppc
27 class TestGRE(VppTestCase):
32 super(TestGRE, cls).setUpClass()
35 super(TestGRE, self).setUp()
37 # create 3 pg interfaces - set one in a non-default table.
38 self.create_pg_interfaces(range(3))
40 self.tbl = VppIpTable(self, 1)
41 self.tbl.add_vpp_config()
42 self.pg1.set_table_ip4(1)
44 for i in self.pg_interfaces:
48 self.pg0.resolve_arp()
50 self.pg1.resolve_arp()
52 self.pg2.resolve_ndp()
55 for i in self.pg_interfaces:
59 self.pg1.set_table_ip4(0)
60 super(TestGRE, self).tearDown()
62 def create_stream_ip4(self, src_if, src_ip, dst_ip):
64 for i in range(0, 257):
65 info = self.create_packet_info(src_if, src_if)
66 payload = self.info_to_payload(info)
67 p = (Ether(dst=src_if.local_mac, src=src_if.remote_mac) /
68 IP(src=src_ip, dst=dst_ip) /
69 UDP(sport=1234, dport=1234) /
75 def create_stream_ip6(self, src_if, src_ip, dst_ip):
77 for i in range(0, 257):
78 info = self.create_packet_info(src_if, src_if)
79 payload = self.info_to_payload(info)
80 p = (Ether(dst=src_if.local_mac, src=src_if.remote_mac) /
81 IPv6(src=src_ip, dst=dst_ip) /
82 UDP(sport=1234, dport=1234) /
88 def create_tunnel_stream_4o4(self, src_if,
89 tunnel_src, tunnel_dst,
92 for i in range(0, 257):
93 info = self.create_packet_info(src_if, src_if)
94 payload = self.info_to_payload(info)
95 p = (Ether(dst=src_if.local_mac, src=src_if.remote_mac) /
96 IP(src=tunnel_src, dst=tunnel_dst) /
98 IP(src=src_ip, dst=dst_ip) /
99 UDP(sport=1234, dport=1234) /
105 def create_tunnel_stream_6o4(self, src_if,
106 tunnel_src, tunnel_dst,
109 for i in range(0, 257):
110 info = self.create_packet_info(src_if, src_if)
111 payload = self.info_to_payload(info)
112 p = (Ether(dst=src_if.local_mac, src=src_if.remote_mac) /
113 IP(src=tunnel_src, dst=tunnel_dst) /
115 IPv6(src=src_ip, dst=dst_ip) /
116 UDP(sport=1234, dport=1234) /
122 def create_tunnel_stream_6o6(self, src_if,
123 tunnel_src, tunnel_dst,
126 for i in range(0, 257):
127 info = self.create_packet_info(src_if, src_if)
128 payload = self.info_to_payload(info)
129 p = (Ether(dst=src_if.local_mac, src=src_if.remote_mac) /
130 IPv6(src=tunnel_src, dst=tunnel_dst) /
132 IPv6(src=src_ip, dst=dst_ip) /
133 UDP(sport=1234, dport=1234) /
139 def create_tunnel_stream_l2o4(self, src_if,
140 tunnel_src, tunnel_dst):
142 for i in range(0, 257):
143 info = self.create_packet_info(src_if, src_if)
144 payload = self.info_to_payload(info)
145 p = (Ether(dst=src_if.local_mac, src=src_if.remote_mac) /
146 IP(src=tunnel_src, dst=tunnel_dst) /
148 Ether(dst=RandMAC('*:*:*:*:*:*'),
149 src=RandMAC('*:*:*:*:*:*')) /
150 IP(src=scapy.compat.raw(RandIP()),
151 dst=scapy.compat.raw(RandIP())) /
152 UDP(sport=1234, dport=1234) /
158 def create_tunnel_stream_vlano4(self, src_if,
159 tunnel_src, tunnel_dst, vlan):
161 for i in range(0, 257):
162 info = self.create_packet_info(src_if, src_if)
163 payload = self.info_to_payload(info)
164 p = (Ether(dst=src_if.local_mac, src=src_if.remote_mac) /
165 IP(src=tunnel_src, dst=tunnel_dst) /
167 Ether(dst=RandMAC('*:*:*:*:*:*'),
168 src=RandMAC('*:*:*:*:*:*')) /
170 IP(src=scapy.compat.raw(RandIP()),
171 dst=scapy.compat.raw(RandIP())) /
172 UDP(sport=1234, dport=1234) /
178 def verify_tunneled_4o4(self, src_if, capture, sent,
179 tunnel_src, tunnel_dst):
181 self.assertEqual(len(capture), len(sent))
183 for i in range(len(capture)):
191 self.assertEqual(rx_ip.src, tunnel_src)
192 self.assertEqual(rx_ip.dst, tunnel_dst)
197 self.assertEqual(rx_ip.src, tx_ip.src)
198 self.assertEqual(rx_ip.dst, tx_ip.dst)
199 # IP processing post pop has decremented the TTL
200 self.assertEqual(rx_ip.ttl + 1, tx_ip.ttl)
203 self.logger.error(ppp("Rx:", rx))
204 self.logger.error(ppp("Tx:", tx))
207 def verify_tunneled_6o6(self, src_if, capture, sent,
208 tunnel_src, tunnel_dst):
210 self.assertEqual(len(capture), len(sent))
212 for i in range(len(capture)):
220 self.assertEqual(rx_ip.src, tunnel_src)
221 self.assertEqual(rx_ip.dst, tunnel_dst)
223 rx_gre = GRE(scapy.compat.raw(rx_ip[IPv6].payload))
226 self.assertEqual(rx_ip.src, tx_ip.src)
227 self.assertEqual(rx_ip.dst, tx_ip.dst)
230 self.logger.error(ppp("Rx:", rx))
231 self.logger.error(ppp("Tx:", tx))
234 def verify_tunneled_4o6(self, src_if, capture, sent,
235 tunnel_src, tunnel_dst):
237 self.assertEqual(len(capture), len(sent))
239 for i in range(len(capture)):
246 self.assertEqual(rx_ip.src, tunnel_src)
247 self.assertEqual(rx_ip.dst, tunnel_dst)
249 rx_gre = GRE(scapy.compat.raw(rx_ip[IPv6].payload))
253 self.assertEqual(rx_ip.src, tx_ip.src)
254 self.assertEqual(rx_ip.dst, tx_ip.dst)
257 self.logger.error(ppp("Rx:", rx))
258 self.logger.error(ppp("Tx:", tx))
261 def verify_tunneled_6o4(self, src_if, capture, sent,
262 tunnel_src, tunnel_dst):
264 self.assertEqual(len(capture), len(sent))
266 for i in range(len(capture)):
273 self.assertEqual(rx_ip.src, tunnel_src)
274 self.assertEqual(rx_ip.dst, tunnel_dst)
276 rx_gre = GRE(scapy.compat.raw(rx_ip[IP].payload))
280 self.assertEqual(rx_ip.src, tx_ip.src)
281 self.assertEqual(rx_ip.dst, tx_ip.dst)
284 self.logger.error(ppp("Rx:", rx))
285 self.logger.error(ppp("Tx:", tx))
288 def verify_tunneled_l2o4(self, src_if, capture, sent,
289 tunnel_src, tunnel_dst):
290 self.assertEqual(len(capture), len(sent))
292 for i in range(len(capture)):
300 self.assertEqual(rx_ip.src, tunnel_src)
301 self.assertEqual(rx_ip.dst, tunnel_dst)
304 rx_l2 = rx_gre[Ether]
307 tx_l2 = tx_gre[Ether]
310 self.assertEqual(rx_ip.src, tx_ip.src)
311 self.assertEqual(rx_ip.dst, tx_ip.dst)
312 # bridged, not L3 forwarded, so no TTL decrement
313 self.assertEqual(rx_ip.ttl, tx_ip.ttl)
316 self.logger.error(ppp("Rx:", rx))
317 self.logger.error(ppp("Tx:", tx))
320 def verify_tunneled_vlano4(self, src_if, capture, sent,
321 tunnel_src, tunnel_dst, vlan):
323 self.assertEqual(len(capture), len(sent))
325 ppc("Unexpected packets captured:", capture)
328 for i in range(len(capture)):
336 self.assertEqual(rx_ip.src, tunnel_src)
337 self.assertEqual(rx_ip.dst, tunnel_dst)
340 rx_l2 = rx_gre[Ether]
341 rx_vlan = rx_l2[Dot1Q]
344 self.assertEqual(rx_vlan.vlan, vlan)
347 tx_l2 = tx_gre[Ether]
350 self.assertEqual(rx_ip.src, tx_ip.src)
351 self.assertEqual(rx_ip.dst, tx_ip.dst)
352 # bridged, not L3 forwarded, so no TTL decrement
353 self.assertEqual(rx_ip.ttl, tx_ip.ttl)
356 self.logger.error(ppp("Rx:", rx))
357 self.logger.error(ppp("Tx:", tx))
360 def verify_decapped_4o4(self, src_if, capture, sent):
361 self.assertEqual(len(capture), len(sent))
363 for i in range(len(capture)):
373 self.assertEqual(rx_ip.src, tx_ip.src)
374 self.assertEqual(rx_ip.dst, tx_ip.dst)
375 # IP processing post pop has decremented the TTL
376 self.assertEqual(rx_ip.ttl + 1, tx_ip.ttl)
379 self.logger.error(ppp("Rx:", rx))
380 self.logger.error(ppp("Tx:", tx))
383 def verify_decapped_6o4(self, src_if, capture, sent):
384 self.assertEqual(len(capture), len(sent))
386 for i in range(len(capture)):
396 self.assertEqual(rx_ip.src, tx_ip.src)
397 self.assertEqual(rx_ip.dst, tx_ip.dst)
398 self.assertEqual(rx_ip.hlim + 1, tx_ip.hlim)
401 self.logger.error(ppp("Rx:", rx))
402 self.logger.error(ppp("Tx:", tx))
406 """ GRE IPv4 tunnel Tests """
409 # Create an L3 GRE tunnel.
411 # - assign an IP Addres
412 # - Add a route via the tunnel
414 gre_if = VppGreInterface(self,
417 gre_if.add_vpp_config()
420 # The double create (create the same tunnel twice) should fail,
421 # and we should still be able to use the original
424 gre_if.add_vpp_config()
428 self.fail("Double GRE tunnel add does not fail")
433 route_via_tun = VppIpRoute(self, "4.4.4.4", 32,
434 [VppRoutePath("0.0.0.0",
435 gre_if.sw_if_index)])
437 route_via_tun.add_vpp_config()
440 # Send a packet stream that is routed into the tunnel
441 # - they are all dropped since the tunnel's desintation IP
442 # is unresolved - or resolves via the default route - which
445 tx = self.create_stream_ip4(self.pg0, "5.5.5.5", "4.4.4.4")
447 self.send_and_assert_no_replies(self.pg0, tx)
450 # Add a route that resolves the tunnel's destination
452 route_tun_dst = VppIpRoute(self, "1.1.1.2", 32,
453 [VppRoutePath(self.pg0.remote_ip4,
454 self.pg0.sw_if_index)])
455 route_tun_dst.add_vpp_config()
458 # Send a packet stream that is routed into the tunnel
459 # - packets are GRE encapped
461 tx = self.create_stream_ip4(self.pg0, "5.5.5.5", "4.4.4.4")
462 rx = self.send_and_expect(self.pg0, tx, self.pg0)
463 self.verify_tunneled_4o4(self.pg0, rx, tx,
464 self.pg0.local_ip4, "1.1.1.2")
467 # Send tunneled packets that match the created tunnel and
468 # are decapped and forwarded
470 tx = self.create_tunnel_stream_4o4(self.pg0,
475 rx = self.send_and_expect(self.pg0, tx, self.pg0)
476 self.verify_decapped_4o4(self.pg0, rx, tx)
479 # Send tunneled packets that do not match the tunnel's src
481 self.vapi.cli("clear trace")
482 tx = self.create_tunnel_stream_4o4(self.pg0,
487 self.send_and_assert_no_replies(
489 remark="GRE packets forwarded despite no SRC address match")
492 # Configure IPv6 on the PG interface so we can route IPv6
495 self.pg0.config_ip6()
496 self.pg0.resolve_ndp()
499 # Send IPv6 tunnel encapslated packets
500 # - dropped since IPv6 is not enabled on the tunnel
502 tx = self.create_tunnel_stream_6o4(self.pg0,
507 self.send_and_assert_no_replies(self.pg0, tx,
508 "IPv6 GRE packets forwarded "
509 "despite IPv6 not enabled on tunnel")
512 # Enable IPv6 on the tunnel
517 # Send IPv6 tunnel encapslated packets
518 # - forwarded since IPv6 is enabled on the tunnel
520 tx = self.create_tunnel_stream_6o4(self.pg0,
525 rx = self.send_and_expect(self.pg0, tx, self.pg0)
526 self.verify_decapped_6o4(self.pg0, rx, tx)
529 # Send v6 packets for v4 encap
531 route6_via_tun = VppIpRoute(
532 self, "2001::1", 128,
535 proto=DpoProto.DPO_PROTO_IP6)],
537 route6_via_tun.add_vpp_config()
539 tx = self.create_stream_ip6(self.pg0, "2001::2", "2001::1")
540 rx = self.send_and_expect(self.pg0, tx, self.pg0)
542 self.verify_tunneled_6o4(self.pg0, rx, tx,
543 self.pg0.local_ip4, "1.1.1.2")
548 route_tun_dst.remove_vpp_config()
549 route_via_tun.remove_vpp_config()
550 route6_via_tun.remove_vpp_config()
551 gre_if.remove_vpp_config()
553 self.pg0.unconfig_ip6()
556 """ GRE IPv6 tunnel Tests """
558 self.pg1.config_ip6()
559 self.pg1.resolve_ndp()
562 # Create an L3 GRE tunnel.
564 # - assign an IP Address
565 # - Add a route via the tunnel
567 gre_if = VppGre6Interface(self,
570 gre_if.add_vpp_config()
574 route_via_tun = VppIpRoute(
575 self, "4004::1", 128,
576 [VppRoutePath("0::0",
578 proto=DpoProto.DPO_PROTO_IP6)],
581 route_via_tun.add_vpp_config()
584 # Send a packet stream that is routed into the tunnel
585 # - they are all dropped since the tunnel's desintation IP
586 # is unresolved - or resolves via the default route - which
589 tx = self.create_stream_ip6(self.pg2, "5005::1", "4004::1")
590 self.send_and_assert_no_replies(
592 "GRE packets forwarded without DIP resolved")
595 # Add a route that resolves the tunnel's destination
597 route_tun_dst = VppIpRoute(
598 self, "1002::1", 128,
599 [VppRoutePath(self.pg2.remote_ip6,
600 self.pg2.sw_if_index,
601 proto=DpoProto.DPO_PROTO_IP6)],
603 route_tun_dst.add_vpp_config()
606 # Send a packet stream that is routed into the tunnel
607 # - packets are GRE encapped
609 tx = self.create_stream_ip6(self.pg2, "5005::1", "4004::1")
610 rx = self.send_and_expect(self.pg2, tx, self.pg2)
611 self.verify_tunneled_6o6(self.pg2, rx, tx,
612 self.pg2.local_ip6, "1002::1")
615 # Test decap. decapped packets go out pg1
617 tx = self.create_tunnel_stream_6o6(self.pg2,
622 rx = self.send_and_expect(self.pg2, tx, self.pg1)
625 # RX'd packet is UDP over IPv6, test the GRE header is gone.
627 self.assertFalse(rx[0].haslayer(GRE))
628 self.assertEqual(rx[0][IPv6].dst, self.pg1.remote_ip6)
633 route4_via_tun = VppIpRoute(self, "1.1.1.1", 32,
634 [VppRoutePath("0.0.0.0",
635 gre_if.sw_if_index)])
636 route4_via_tun.add_vpp_config()
638 tx = self.create_stream_ip4(self.pg0, "1.1.1.2", "1.1.1.1")
639 rx = self.send_and_expect(self.pg0, tx, self.pg2)
641 self.verify_tunneled_4o6(self.pg0, rx, tx,
642 self.pg2.local_ip6, "1002::1")
647 route_tun_dst.remove_vpp_config()
648 route_via_tun.remove_vpp_config()
649 route4_via_tun.remove_vpp_config()
650 gre_if.remove_vpp_config()
652 self.pg2.unconfig_ip6()
653 self.pg1.unconfig_ip6()
655 def test_gre_vrf(self):
656 """ GRE tunnel VRF Tests """
659 # Create an L3 GRE tunnel whose destination is in the non-default
660 # table. The underlay is thus non-default - the overlay is still
663 # - assign an IP Addres
665 gre_if = VppGreInterface(self, self.pg1.local_ip4,
668 gre_if.add_vpp_config()
673 # Add a route via the tunnel - in the overlay
675 route_via_tun = VppIpRoute(self, "9.9.9.9", 32,
676 [VppRoutePath("0.0.0.0",
677 gre_if.sw_if_index)])
678 route_via_tun.add_vpp_config()
681 # Add a route that resolves the tunnel's destination - in the
684 route_tun_dst = VppIpRoute(self, "2.2.2.2", 32, table_id=1,
685 paths=[VppRoutePath(self.pg1.remote_ip4,
686 self.pg1.sw_if_index)])
687 route_tun_dst.add_vpp_config()
690 # Send a packet stream that is routed into the tunnel
691 # packets are sent in on pg0 which is in the default table
692 # - packets are GRE encapped
694 self.vapi.cli("clear trace")
695 tx = self.create_stream_ip4(self.pg0, "5.5.5.5", "9.9.9.9")
696 rx = self.send_and_expect(self.pg0, tx, self.pg1)
697 self.verify_tunneled_4o4(self.pg1, rx, tx,
698 self.pg1.local_ip4, "2.2.2.2")
701 # Send tunneled packets that match the created tunnel and
702 # are decapped and forwarded. This tests the decap lookup
703 # does not happen in the encap table
705 self.vapi.cli("clear trace")
706 tx = self.create_tunnel_stream_4o4(self.pg1,
711 rx = self.send_and_expect(self.pg1, tx, self.pg0)
712 self.verify_decapped_4o4(self.pg0, rx, tx)
715 # Send tunneled packets that match the created tunnel
716 # but arrive on an interface that is not in the tunnel's
717 # encap VRF, these are dropped.
718 # IP enable the interface so they aren't dropped due to
719 # IP not being enabled.
721 self.pg2.config_ip4()
722 self.vapi.cli("clear trace")
723 tx = self.create_tunnel_stream_4o4(self.pg2,
728 rx = self.send_and_assert_no_replies(
730 "GRE decap packets in wrong VRF")
732 self.pg2.unconfig_ip4()
737 route_tun_dst.remove_vpp_config()
738 route_via_tun.remove_vpp_config()
739 gre_if.remove_vpp_config()
741 def test_gre_l2(self):
742 """ GRE tunnel L2 Tests """
745 # Add routes to resolve the tunnel destinations
747 route_tun1_dst = VppIpRoute(self, "2.2.2.2", 32,
748 [VppRoutePath(self.pg0.remote_ip4,
749 self.pg0.sw_if_index)])
750 route_tun2_dst = VppIpRoute(self, "2.2.2.3", 32,
751 [VppRoutePath(self.pg0.remote_ip4,
752 self.pg0.sw_if_index)])
754 route_tun1_dst.add_vpp_config()
755 route_tun2_dst.add_vpp_config()
758 # Create 2 L2 GRE tunnels and x-connect them
760 gre_if1 = VppGreInterface(self, self.pg0.local_ip4,
762 type=GreTunnelTypes.TT_TEB)
763 gre_if2 = VppGreInterface(self, self.pg0.local_ip4,
765 type=GreTunnelTypes.TT_TEB)
766 gre_if1.add_vpp_config()
767 gre_if2.add_vpp_config()
772 self.vapi.sw_interface_set_l2_xconnect(gre_if1.sw_if_index,
775 self.vapi.sw_interface_set_l2_xconnect(gre_if2.sw_if_index,
780 # Send in tunnel encapped L2. expect out tunnel encapped L2
783 tx = self.create_tunnel_stream_l2o4(self.pg0,
786 rx = self.send_and_expect(self.pg0, tx, self.pg0)
787 self.verify_tunneled_l2o4(self.pg0, rx, tx,
791 tx = self.create_tunnel_stream_l2o4(self.pg0,
794 rx = self.send_and_expect(self.pg0, tx, self.pg0)
795 self.verify_tunneled_l2o4(self.pg0, rx, tx,
799 self.vapi.sw_interface_set_l2_xconnect(gre_if1.sw_if_index,
802 self.vapi.sw_interface_set_l2_xconnect(gre_if2.sw_if_index,
807 # Create a VLAN sub-interfaces on the GRE TEB interfaces
808 # then x-connect them
810 gre_if_11 = VppDot1QSubint(self, gre_if1, 11)
811 gre_if_12 = VppDot1QSubint(self, gre_if2, 12)
813 # gre_if_11.add_vpp_config()
814 # gre_if_12.add_vpp_config()
819 self.vapi.sw_interface_set_l2_xconnect(gre_if_11.sw_if_index,
820 gre_if_12.sw_if_index,
822 self.vapi.sw_interface_set_l2_xconnect(gre_if_12.sw_if_index,
823 gre_if_11.sw_if_index,
827 # Configure both to pop thier respective VLAN tags,
828 # so that during the x-coonect they will subsequently push
830 self.vapi.l2_interface_vlan_tag_rewrite(
831 sw_if_index=gre_if_12.sw_if_index, vtr_op=L2_VTR_OP.L2_POP_1,
833 self.vapi.l2_interface_vlan_tag_rewrite(
834 sw_if_index=gre_if_11.sw_if_index, vtr_op=L2_VTR_OP.L2_POP_1,
838 # Send traffic in both directiond - expect the VLAN tags to
841 tx = self.create_tunnel_stream_vlano4(self.pg0,
845 rx = self.send_and_expect(self.pg0, tx, self.pg0)
846 self.verify_tunneled_vlano4(self.pg0, rx, tx,
851 tx = self.create_tunnel_stream_vlano4(self.pg0,
855 rx = self.send_and_expect(self.pg0, tx, self.pg0)
856 self.verify_tunneled_vlano4(self.pg0, rx, tx,
862 # Cleanup Test resources
864 gre_if_11.remove_vpp_config()
865 gre_if_12.remove_vpp_config()
866 gre_if1.remove_vpp_config()
867 gre_if2.remove_vpp_config()
868 route_tun1_dst.add_vpp_config()
869 route_tun2_dst.add_vpp_config()
871 def test_gre_loop(self):
872 """ GRE tunnel loop Tests """
875 # Create an L3 GRE tunnel.
877 # - assign an IP Addres
879 gre_if = VppGreInterface(self,
882 gre_if.add_vpp_config()
887 # add a route to the tunnel's destination that points
888 # through the tunnel, hence forming a loop in the forwarding
891 route_dst = VppIpRoute(self, "1.1.1.2", 32,
892 [VppRoutePath("0.0.0.0",
893 gre_if.sw_if_index)])
894 route_dst.add_vpp_config()
897 # packets to the tunnels destination should be dropped
899 tx = self.create_stream_ip4(self.pg0, "1.1.1.1", "1.1.1.2")
900 self.send_and_assert_no_replies(self.pg2, tx)
902 self.logger.info(self.vapi.ppcli("sh adj 7"))
907 route_dst.modify([VppRoutePath(self.pg1.remote_ip4,
908 self.pg1.sw_if_index)])
909 route_dst.add_vpp_config()
911 rx = self.send_and_expect(self.pg0, tx, self.pg1)
914 # a good route throught the tunnel to check it restacked
916 route_via_tun_2 = VppIpRoute(self, "2.2.2.2", 32,
917 [VppRoutePath("0.0.0.0",
918 gre_if.sw_if_index)])
919 route_via_tun_2.add_vpp_config()
921 tx = self.create_stream_ip4(self.pg0, "2.2.2.3", "2.2.2.2")
922 rx = self.send_and_expect(self.pg0, tx, self.pg1)
923 self.verify_tunneled_4o4(self.pg1, rx, tx,
924 self.pg0.local_ip4, "1.1.1.2")
929 route_via_tun_2.remove_vpp_config()
930 gre_if.remove_vpp_config()
933 if __name__ == '__main__':
934 unittest.main(testRunner=VppTestRunner)