Consolidate duplicate code.
Support "relax" option when sending and verifying dataplane packets.
Add OFPSF_REPLY_MORE support to TwoFlowStats test,
but this should ultimately be moved to controller.transact().
diff --git a/tests/flow_stats.py b/tests/flow_stats.py
index 91c67a6..e5976c3 100644
--- a/tests/flow_stats.py
+++ b/tests/flow_stats.py
@@ -58,6 +58,31 @@
     fs_port_map = config["port_map"]
     fs_config = config
 
+def sendPacket(obj, pkt, ingress_port, egress_port, test_timeout):
+
+    fs_logger.info("Sending packet to dp port " + str(ingress_port) +
+                   ", expecting output on " + str(egress_port))
+    obj.dataplane.send(ingress_port, str(pkt))
+
+    exp_pkt_arg = None
+    exp_port = None
+    if fs_config["relax"]:
+        exp_pkt_arg = pkt
+        exp_port = egress_port
+
+    (rcv_port, rcv_pkt, pkt_time) = obj.dataplane.poll(timeout=1, 
+                                                       port_number=exp_port,
+                                                       exp_pkt=exp_pkt_arg)
+    obj.assertTrue(rcv_pkt is not None,
+                   "Packet not received on port " + str(egress_port))
+    fs_logger.debug("Packet len " + str(len(rcv_pkt)) + " in on " + 
+                    str(rcv_port))
+    obj.assertEqual(rcv_port, egress_port,
+                    "Packet received on port " + str(rcv_port) +
+                    ", expected port " + str(egress_port))
+    obj.assertEqual(str(pkt), str(rcv_pkt),
+                    'Response packet does not match send packet')
+
 class SingleFlowStats(basic.SimpleDataPlane):
     """
     Verify flow stats are properly retrieved.
@@ -151,21 +176,8 @@
         num_sends = random.randint(10,20)
         fs_logger.info("Sending " + str(num_sends) + " test packets")
         for i in range(0,num_sends):
-            fs_logger.info("Sending packet to dp port " + 
-                           str(ingress_port))
-            self.dataplane.send(ingress_port, str(pkt))
-            (rcv_port, rcv_pkt, pkt_time) = self.dataplane.poll(timeout=
-                                                                test_timeout)
-            self.assertTrue(rcv_pkt is not None, "Did not receive packet")
-            fs_logger.debug("Packet len " + str(len(pkt)) + " in on " + 
-                            str(rcv_port))
-            self.assertEqual(rcv_port, egress_port, "Unexpected receive port")
-            for j in range(0,test_timeout):
-                if str(pkt) == str(rcv_pkt):
-                    break
-                sleep(1)
-            self.assertTrue(j < test_timeout,
-                            'Timeout sending packets for flow stats test')
+            sendPacket(self, pkt, ingress_port, egress_port,
+                       test_timeout)
 
         self.verifyStats(match, ofp.OFPP_NONE, test_timeout, num_sends)
         self.verifyStats(match, egress_port, test_timeout, num_sends)
@@ -209,22 +221,19 @@
 
         return flow_mod_msg
 
-    def sendPacket(self, pkt, ingress_port, egress_port, test_timeout):
-        fs_logger.info("Sending packet to dp port " + 
-                       str(ingress_port))
-        self.dataplane.send(ingress_port, str(pkt))
-        (rcv_port, rcv_pkt, pkt_time) = self.dataplane.poll(timeout=
-                                                            test_timeout)
-        self.assertTrue(rcv_pkt is not None, "Did not receive packet")
-        fs_logger.debug("Packet len " + str(len(pkt)) + " in on " + 
-                        str(rcv_port))
-        self.assertEqual(rcv_port, egress_port, "Unexpected receive port")
-        for j in range(0,test_timeout):
-            if str(pkt) == str(rcv_pkt):
-                break
-            sleep(1)
-        self.assertTrue(j < test_timeout,
-                        'Timeout sending packets for flow stats test')
+    def sumStatsReplyCounts(self, response):
+        total_packets = 0
+        for obj in response.stats:
+            # TODO: pad1 and pad2 fields may be nonzero, is this a bug?
+            # for now, just clear them so the assert is simpler
+            #obj.match.pad1 = 0
+            #obj.match.pad2 = [0, 0]
+            #self.assertEqual(match, obj.match,
+            #                 "Matches do not match")
+           fs_logger.info("Received " + str(obj.packet_count)
+                          + " packets")
+           total_packets += obj.packet_count
+        return total_packets
 
     def verifyStats(self, match, out_port, test_timeout, packet_count):
         stat_req = message.flow_stats_request()
@@ -235,29 +244,27 @@
         all_packets_received = 0
         for i in range(0,test_timeout):
             fs_logger.info("Sending stats request")
+            # TODO: move REPLY_MORE handling to controller.transact?
             response, pkt = self.controller.transact(stat_req,
                                                      timeout=test_timeout)
             self.assertTrue(response is not None,
                             "No response to stats request")
-            self.assertTrue(len(response.stats) >= 1,
-                            "Did not receive flow stats reply")
-            total_packets = 0
-            for obj in response.stats:
-                # TODO: pad1 and pad2 fields may be nonzero, is this a bug?
-                # for now, just clear them so the assert is simpler
-                #obj.match.pad1 = 0
-                #obj.match.pad2 = [0, 0]
-                #self.assertEqual(match, obj.match,
-                #                 "Matches do not match")
-                fs_logger.info("Received " + str(obj.packet_count) + " packets")
-                total_packets += obj.packet_count
+            total_packets = self.sumStatsReplyCounts(response)
+
+            while response.flags == ofp.OFPSF_REPLY_MORE:
+               response, pkt = self.controller.poll(exp_msg=
+                                                    ofp.OFPT_STATS_REPLY,
+                                                    timeout=test_timeout)
+               total_packets += self.sumStatsReplyCounts(response)
+
             if total_packets == packet_count:
                 all_packets_received = 1
                 break
             sleep(1)
 
         self.assertTrue(all_packets_received,
-                        "Packet count does not match number sent")
+                        "Total stats packet count " + str(total_packets) +
+                        " does not match number sent " + str(packet_count))
 
     def runTest(self):
         global fs_port_map
@@ -294,15 +301,18 @@
         num_pkt2s = random.randint(10,30)
         fs_logger.info("Sending " + str(num_pkt2s) + " pkt2s")
         for i in range(0,num_pkt1s):
-            self.sendPacket(pkt1, ingress_port, egress_port1, test_timeout)
+            sendPacket(self, pkt1, ingress_port, egress_port1, test_timeout)
         for i in range(0,num_pkt2s):
-            self.sendPacket(pkt2, ingress_port, egress_port2, test_timeout)
+            sendPacket(self, pkt2, ingress_port, egress_port2, test_timeout)
             
         match1 = parse.packet_to_flow_match(pkt1)
+        fs_logger.info("Verifying flow1's " + str(num_pkt1s) + " packets")
         self.verifyStats(match1, ofp.OFPP_NONE, test_timeout, num_pkt1s)
         match2 = parse.packet_to_flow_match(pkt2)
+        fs_logger.info("Verifying flow2's " + str(num_pkt2s) + " packets")
         self.verifyStats(match2, ofp.OFPP_NONE, test_timeout, num_pkt2s)
         match1.wildcards |= ofp.OFPFW_DL_SRC
+        fs_logger.info("Verifying combined " + str(num_pkt1s+num_pkt2s) + " packets")
         self.verifyStats(match1, ofp.OFPP_NONE, test_timeout, 
                          num_pkt1s+num_pkt2s)
         # TODO: sweep through the wildcards to verify matching?
@@ -341,23 +351,6 @@
 
         return flow_mod_msg
 
-    def sendPacket(self, pkt, ingress_port, egress_port, test_timeout):
-        fs_logger.info("Sending packet to dp port " + 
-                       str(ingress_port))
-        self.dataplane.send(ingress_port, str(pkt))
-        (rcv_port, rcv_pkt, pkt_time) = self.dataplane.poll(timeout=
-                                                            test_timeout)
-        self.assertTrue(rcv_pkt is not None, "Did not receive packet")
-        fs_logger.debug("Packet len " + str(len(pkt)) + " in on " + 
-                        str(rcv_port))
-        self.assertEqual(rcv_port, egress_port, "Unexpected receive port")
-        for j in range(0,test_timeout):
-            if str(pkt) == str(rcv_pkt):
-                break
-            sleep(1)
-        self.assertTrue(j < test_timeout,
-                        'Timeout sending packets for flow stats test')
-
     def verifyAggFlowStats(self, match, out_port, test_timeout, 
                            flow_count, packet_count):
         stat_req = message.aggregate_stats_request()
@@ -424,9 +417,9 @@
         num_pkt2s = random.randint(10,30)
         fs_logger.info("Sending " + str(num_pkt2s) + " pkt2s")
         for i in range(0,num_pkt1s):
-            self.sendPacket(pkt1, ingress_port, egress_port1, test_timeout)
+            sendPacket(self, pkt1, ingress_port, egress_port1, test_timeout)
         for i in range(0,num_pkt2s):
-            self.sendPacket(pkt2, ingress_port, egress_port2, test_timeout)
+            sendPacket(self, pkt2, ingress_port, egress_port2, test_timeout)
             
         # loop on flow stats request until timeout
         match = parse.packet_to_flow_match(pkt1)