refactor verify_flowstats and move to testutils

This needs to be centralized for later byte counter fuzzing changes.
diff --git a/src/python/oftest/testutils.py b/src/python/oftest/testutils.py
index 4158acf..0edcfb2 100644
--- a/src/python/oftest/testutils.py
+++ b/src/python/oftest/testutils.py
@@ -1140,4 +1140,66 @@
 assert(parse_version("1.0,1.2,1.3") == set(["1.0", "1.2", "1.3"]))
 assert(parse_version("1.0+") == set(["1.0", "1.1", "1.2", "1.3"]))
 
+def get_stats(test, req):
+    """
+    Retrieve a list of stats entries. Handles OFPSF_REPLY_MORE.
+    """
+    stats = []
+    reply, _ = test.controller.transact(req)
+    test.assertTrue(reply is not None, "No response to stats request")
+    stats.extend(reply.stats)
+    while reply.flags & of10.OFPSF_REPLY_MORE != 0:
+        reply, pkt = self.controller.poll(exp_msg=of10.OFPT_STATS_REPLY)
+        test.assertTrue(reply is not None, "No response to stats request")
+        stats.extend(reply.stats)
+    return stats
+
+def get_flow_stats(test, match, table_id=0xff, out_port=of10.cstruct.OFPP_NONE):
+    """
+    Retrieve a list of flow stats entries.
+    """
+    req = of10.message.flow_stats_request(match=match,
+                                          table_id=table_id,
+                                          out_port=out_port)
+    return get_stats(test, req)
+
+def verify_flow_stats(test, match, table_id=0xff,
+                      out_port=of10.cstruct.OFPP_NONE,
+                      initial=[],
+                      pkts=None, bytes=None):
+    """
+    Verify that flow stats changed as expected.
+
+    Optionally takes an 'initial' list of stats entries, as returned by
+    get_flow_stats(). If 'initial' is not given the counters are assumed to
+    begin at 0.
+    """
+    def accumulate(stats):
+        pkts_acc = bytes_acc = 0
+        for stat in stats:
+            pkts_acc += stat.packet_count
+            bytes_acc += stat.byte_count
+        return (pkts_acc, bytes_acc)
+
+    pkts_before, bytes_before = accumulate(initial)
+
+    # Wait 10s for counters to update
+    pkt_diff = byte_diff = None
+    for i in range(0, 100):
+        stats = get_flow_stats(test, match, table_id=table_id, out_port=out_port)
+        pkts_after, bytes_after = accumulate(stats)
+        pkt_diff = pkts_after - pkts_before
+        byte_diff = bytes_after - bytes_before
+        if (pkts == None or pkt_diff >= pkts) and \
+           (bytes == None or byte_diff >= bytes):
+            break
+        sleep(0.1)
+
+    if pkts != None:
+        test.assertEquals(pkt_diff, pkts, "Flow packet counter not updated properly (expected increase of %d, got increase of %d)" % (pkts, pkt_diff))
+
+    if bytes != None:
+        test.assertEquals(byte_diff, bytes, "Flow byte counter not updated properly (expected increase of %d, got increase of %d)" % (bytes, byte_diff))
+
+
 __all__ = list(set(locals()) - _import_blacklist)
diff --git a/tests/FuncUtils.py b/tests/FuncUtils.py
index 87df355..aa35221 100644
--- a/tests/FuncUtils.py
+++ b/tests/FuncUtils.py
@@ -512,44 +512,6 @@
         self.assertEqual(expect_active, active ,"active counter is not incremented properly")
 
 
-def verify_flowstats(self,match,byte_count=None,packet_count=None):
-    # Verify flow counters : byte_count and packet_count
-
-    stat_req = message.flow_stats_request()
-    stat_req.match = match
-    stat_req.table_id = 0xff
-    stat_req.out_port = ofp.OFPP_NONE
-    
-    for i in range(0,100):
-        logging.info("Sending stats request")
-        # TODO: move REPLY_MORE handling to controller.transact?
-        response, pkt = self.controller.transact(stat_req,
-                                                     timeout=5)
-        self.assertTrue(response is not None,"No response to stats request")
-
-        packet_counter = 0
-        byte_counter = 0 
-
-        for item in response.stats:
-            packet_counter += item.packet_count
-            byte_counter += item.byte_count
-
-        logging.info("Received %d packets", packet_counter)
-        logging.info("Received %d bytes", byte_counter)
-
-        if (packet_count == None or packet_counter >= packet_count) and \
-           (byte_count == None or byte_counter >= byte_count):
-            break
-
-        sleep(0.1)
-    
-    if packet_count != None :
-        self.assertEqual(packet_count, packet_counter, "packet_count counter is not incremented correctly")
-
-    if byte_count != None :   
-        self.assertEqual(byte_count, byte_counter, "byte_count counter is not incremented correctly")
-
-
 def verify_portstats(self, port,tx_packets=None,rx_packets=None,rx_byte=None,tx_byte=None):
 
     
diff --git a/tests/counters.py b/tests/counters.py
index 4caae4f..d23cf75 100644
--- a/tests/counters.py
+++ b/tests/counters.py
@@ -60,9 +60,9 @@
         num_pkts = 5 
         for pkt_cnt in range(num_pkts):
             self.dataplane.send(of_ports[0],str(pkt))
-         
-        #Verify Recieved Packets/Bytes Per Flow  
-        verify_flowstats(self,match,packet_count=num_pkts)
+
+        # Verify the packet counter was updated
+        verify_flow_stats(self, match, pkts=num_pkts)
 
 
 class BytPerFlow(base_tests.SimpleDataPlane):
@@ -93,9 +93,9 @@
         byte_count = num_pkts*len(str(pkt))
         for pkt_cnt in range(num_pkts):
             self.dataplane.send(of_ports[0],str(pkt))
-         
-        #Verify Recieved Packets/Bytes Per Flow  
-        verify_flowstats(self,match,byte_count=byte_count)
+
+        # Verify the byte counter was updated
+        verify_flow_stats(self, match, bytes=byte_count)
 
 
 class DurationPerFlow(base_tests.SimpleDataPlane):
diff --git a/tests/detailed_contr_sw_messages.py b/tests/detailed_contr_sw_messages.py
index 601cc72..4ca6a6b 100644
--- a/tests/detailed_contr_sw_messages.py
+++ b/tests/detailed_contr_sw_messages.py
@@ -140,7 +140,7 @@
         send_packet(self,pkt,of_ports[0],of_ports[1])
 
         # Verify Flow counters have incremented
-        verify_flowstats(self,match,byte_count=len(str(pkt)),packet_count=1)
+        verify_flow_stats(self, match, pkts=1, bytes=len(str(pkt)))
         
         #Send Identical flow 
         (pkt1,match1) = wildcard_all(self,of_ports)
@@ -149,7 +149,7 @@
         verify_tablestats(self,expect_active=1)
 
         # Verify Flow counters reset
-        verify_flowstats(self,match,byte_count=0,packet_count=0)
+        verify_flow_stats(self, match, pkts=0, bytes=0)
 
    
 class EmerFlowTimeout(base_tests.SimpleProtocol): 
@@ -265,7 +265,7 @@
         send_packet(self,pkt,of_ports[0],of_ports[1])
 
         #Verify flow counters
-        verify_flowstats(self,match,byte_count=len(str(pkt)),packet_count=1)
+        verify_flow_stats(self, match, pkts=1, bytes=len(str(pkt)))
 
         #Modify flow- 1 
         modify_flow_action(self,of_ports,match)
@@ -274,7 +274,7 @@
         send_packet(self,pkt,of_ports[0],of_ports[2])
         
         #Verify flow counters are preserved
-        verify_flowstats(self,match,byte_count=(2*len(str(pkt))),packet_count=2)
+        verify_flow_stats(self, match, pkts=2, bytes=len(str(pkt))*2)
 
 
 class StrictModifyAction(base_tests.SimpleDataPlane):
@@ -308,7 +308,7 @@
         send_packet(self,pkt,of_ports[0],of_ports[1])
 
         # Verify flow counters of the flow-1
-        verify_flowstats(self,match,byte_count=len(str(pkt)),packet_count=1)
+        verify_flow_stats(self, match, pkts=1, bytes=len(str(pkt)))
 
         # Strict-Modify flow- 1 
         strict_modify_flow_action(self,of_ports[2],match,priority=100)
@@ -317,7 +317,7 @@
         send_packet(self,pkt,of_ports[0],of_ports[2])
         
         # Verify flow counters are preserved
-        verify_flowstats(self,match,byte_count=(2*len(str(pkt))),packet_count=2)
+        verify_flow_stats(self, match, pkts=2, bytes=2*len(str(pkt)))
 
 
 class DeleteNonexistingFlow(base_tests.SimpleDataPlane):