Merge branch 'annotations'
Conflicts:
oft
diff --git a/oft b/oft
index bfea291..7205505 100755
--- a/oft
+++ b/oft
@@ -157,7 +157,7 @@
"controller_host" : "0.0.0.0", # For passive bind
"controller_port" : 6633,
"relax" : False,
- "test_spec" : "all",
+ "test_spec" : "",
"log_file" : "oft.log",
"log_append" : False,
"list" : False,
@@ -178,10 +178,6 @@
"priority" : 0,
}
-# Default test priority
-TEST_PRIO_DEFAULT=100
-TEST_PRIO_SKIP=-1
-
#@todo Set up a dict of config params so easier to manage:
# <param> <cmdline flags> <default value> <help> <optional parser>
@@ -211,7 +207,26 @@
object and args is any additional arguments from the command line
"""
- parser = OptionParser(version="%prog 0.1")
+ usage = "usage: %prog [options] (test|group)..."
+
+ description = """
+OFTest is a framework and set of tests for validating OpenFlow switches.
+
+The default configuration assumes that an OpenFlow 1.0 switch is attempting to
+connect to a controller on the machine running OFTest, port 6633. Additionally,
+the interfaces veth1, veth3, veth5, and veth7 should be connected to the switch's
+dataplane.
+
+If no positional arguments are given then OFTest will run all tests that
+depend only on standard OpenFlow 1.0. Otherwise each positional argument
+is interpreted as either a test name or a test group name. The union of
+these will be executed. To see what groups each test belongs to use the
+--list option.
+"""
+
+ parser = OptionParser(version="%prog 0.1",
+ usage=usage,
+ description=description)
#@todo parse port map as option?
# Set up default values
@@ -233,8 +248,7 @@
type="int", help="Port number of the test controller")
test_list_help = """Indicate tests to run. Valid entries are "all" (the
default) or a comma separated list of:
- module Run all tests in the named module
- testcase Run tests in all modules with the name testcase
+ group Run all tests in the named group
module.testcase Run the specific test case
"""
parser.add_option("-T", "--test-spec", "--test-list", help=test_list_help)
@@ -244,22 +258,14 @@
help="Do not delete log file if specified")
parser.add_option("--debug",
help="Debug lvl: debug, info, warning, error, critical")
- parser.add_option("--port-count", type="int",
- help="Number of ports to use (optional)")
- parser.add_option("--base-of-port", type="int",
- help="Base OpenFlow port number (optional)")
- parser.add_option("--base-if-index", type="int",
- help="Base interface index number (optional)")
parser.add_option("--list-test-names", action='store_true',
- help="List only test names.", default=False)
+ help="List test names matching the test spec and exit", default=False)
parser.add_option("--list", action="store_true",
help="List all tests and exit")
parser.add_option("-v", "--verbose", action="store_true",
help="Short cut for --debug=verbose")
parser.add_option("--relax", action="store_true",
help="Relax packet match checks allowing other packets")
- parser.add_option("--param", type="int",
- help="Parameter sent to test (for debugging)")
parser.add_option("--profile",
help="File listing tests to skip/run")
parser.add_option("-t", "--test-params",
@@ -284,9 +290,6 @@
help="Directory containing platform modules")
parser.add_option("--profile-dir", type="string",
help="Directory containing profile modules")
- parser.add_option("--priority", type="int",
- help="Minimum test priority",
- default=0)
# Might need this if other parsers want command line
# parser.allow_interspersed_args = False
@@ -330,6 +333,9 @@
Test cases are subclasses of unittest.TestCase
+ Also updates the _groups member to include "standard" and
+ module test groups if appropriate.
+
@param config The oft configuration dictionary
@returns A dictionary from test module names to tuples of
(module, dictionary from test names to test classes).
@@ -355,65 +361,48 @@
tests = dict((k, v) for (k, v) in mod.__dict__.items() if type(v) == type and
issubclass(v, unittest.TestCase))
if tests:
+ for (testname, test) in tests.items():
+ # Set default annotation values
+ if not hasattr(test, "_groups"):
+ test._groups = []
+ if not hasattr(test, "_nonstandard"):
+ test._nonstandard = False
+ if not hasattr(test, "_disabled"):
+ test._disabled = False
+
+ # Put test in its module's test group
+ if not test._disabled:
+ test._groups.append(modname)
+
+ # Put test in the standard test group
+ if not test._disabled and not test._nonstandard:
+ test._groups.append("standard")
+ test._groups.append("all") # backwards compatibility
+
result[modname] = (mod, tests)
return result
-def prune_tests(test_spec, test_modules):
+def prune_tests(test_specs, test_modules):
"""
- Return tests matching a given test-spec.
- @param test_spec A test-spec string.
+ Return tests matching the given test-specs
+ @param test_specs A list of group names or test names.
@param test_modules Same format as the output of load_test_modules.
@returns Same format as the output of load_test_modules.
"""
result = {}
- for (spec_modname, spec_testname) in parse_test_spec(test_spec):
+ for e in test_specs:
matched = False
for (modname, (mod, tests)) in test_modules.items():
- if (spec_modname == None or spec_modname == modname):
- for (testname, test) in tests.items():
- if (spec_testname == None or spec_testname == testname):
- result.setdefault(modname, (mod, {}))
- result[modname][1][testname] = test
- matched = True
+ for (testname, test) in tests.items():
+ if e in test._groups or e == "%s.%s" % (modname, testname):
+ result.setdefault(modname, (mod, {}))
+ result[modname][1][testname] = test
+ matched = True
if not matched:
- if spec_modname and spec_testname:
- el = "%s.%s" % (spec_modname, spec_testname)
- else:
- el = spec_modname or spec_testname or "all"
- die("test-spec element %s did not match any tests" % el)
+ die("test-spec element %s did not match any tests" % e)
return result
-def parse_test_spec(test_spec):
- """
- The input string is split on commas and each element is parsed
- individually into a module name and test name. Either may be None
- for a wildcard. The case of the first letter resolves ambiguity
- of whether a word is a test or module name. The special string
- "all" results in both fields wildcarded.
-
- Examples:
- basic.Echo -> ("basic", "Echo")
- basic -> ("basic", None)
- Echo -> (None, "Echo")
- all -> (None, None)
- """
- results = []
- for ts_entry in test_spec.split(","):
- parts = ts_entry.split(".")
- if len(parts) == 1:
- if ts_entry == "all":
- results.append((None, None))
- elif ts_entry[0].isupper():
- results.append((None, ts_entry))
- else:
- results.append((ts_entry, None))
- elif len(parts) == 2:
- results.append((parts[0], parts[1]))
- else:
- die("Bad test spec: " + ts_entry)
- return results
-
def die(msg, exit_val=1):
print msg
logging.critical(msg)
@@ -429,23 +418,6 @@
return " " * spaces
return " "
-def test_prio_get(test):
- """
- Return the priority of a test
-
- If test is in "skip list" from profile, return the skip value
-
- If the priority property is set in the class, return
- that value. Otherwise return 100 (default)
- """
- if test.__name__ in profile_mod.skip_test_list:
- logging.info("Skipping test %s due to profile" % test.__name__)
- return TEST_PRIO_SKIP
- if test.__name__ in profile_mod.run_test_list:
- logging.info("Add test %s due to profile" % test.__name__)
- return TEST_PRIO_DEFAULT
- return getattr(test, "priority", TEST_PRIO_DEFAULT)
-
#
# Main script
#
@@ -460,7 +432,14 @@
# Allow tests to import each other
sys.path.append(config["test_dir"])
-test_modules = prune_tests(config["test_spec"], load_test_modules(config))
+test_specs = args
+if config["test_spec"] != "":
+ print >> sys.stderr, "WARNING: The --test-spec option is deprecated"
+ test_specs += config["test_spec"].split(',')
+if test_specs == []:
+ test_specs = ["standard"]
+
+test_modules = load_test_modules(config)
load_profile(config)
@@ -468,18 +447,43 @@
if config["list"]:
mod_count = 0
test_count = 0
- print "\nTest List:"
+ all_groups = set()
+ print """\
+Tests are shown grouped by module. If a test is in any groups beyond "standard"
+and its module's group then they are shown in parentheses."""
+ print
+ print """\
+Tests marked with '*' are non-standard and may require vendor extensions or
+special switch configuration. These are not part of the "standard" test group."""
+ print
+ print """\
+Tests marked with '!' are disabled because they are experimental, special-purpose,
+or are too long to be run normally. These are not part of the "standard" test
+group or their module's test group."""
+ print
+ print "Tests marked (TP1) after name take --test-params including:"
+ print " 'vid=N;strip_vlan=bool;add_vlan=bool'"
+ print "Note that --profile may override which tests are run"
+ print
+ print "Test List:"
for (modname, (mod, tests)) in test_modules.items():
mod_count += 1
desc = (mod.__doc__ or "No description").strip().split('\n')[0]
start_str = " Module " + mod.__name__ + ": "
print start_str + _space_to(22, start_str) + desc
for (testname, test) in tests.items():
- desc = (test.__doc__ or "No description").strip().split('\n')[0]
- if test_prio_get(test) < config["priority"]:
- start_str = " * " + testname + ":"
- else:
- start_str = " " + testname + ":"
+ try:
+ desc = (test.__doc__ or "").strip()
+ desc = desc.split('\n')[0]
+ except:
+ desc = "No description"
+ groups = set(test._groups) - set(["all", "standard", modname])
+ all_groups.update(test._groups)
+ if groups:
+ desc = "(%s) %s" % (",".join(groups), desc)
+ start_str = " %s%s %s:" % (test._nonstandard and "*" or " ",
+ test._disabled and "!" or " ",
+ testname)
if len(start_str) > 22:
desc = "\n" + _space_to(22, "") + desc
print start_str + _space_to(22, start_str) + desc
@@ -488,18 +492,17 @@
print "%d modules shown with a total of %d tests" % \
(mod_count, test_count)
print
- print "Tests preceded by * are not run by default"
- print "Tests marked (TP1) after name take --test-params including:"
- print " 'vid=N;strip_vlan=bool;add_vlan=bool'"
- print "Note that --profile may override which tests are run"
+ print "Test groups: %s" % (', '.join(sorted(all_groups)))
+
sys.exit(0)
+test_modules = prune_tests(test_specs, test_modules)
+
# Check if test list is requested; display and exit if so
if config["list_test_names"]:
for (modname, (mod, tests)) in test_modules.items():
for (testname, test) in tests.items():
- if test_prio_get(test) >= config["priority"]:
- print "%s.%s" % (modname, testname)
+ print "%s.%s" % (modname, testname)
sys.exit(0)
# Generate the test suite
@@ -508,9 +511,8 @@
for (modname, (mod, tests)) in test_modules.items():
for (testname, test) in tests.items():
- if test_prio_get(test) >= config["priority"]:
- logging.info("Adding test " + modname + "." + testname)
- suite.addTest(test())
+ logging.info("Adding test " + modname + "." + testname)
+ suite.addTest(test())
# Allow platforms to import each other
sys.path.append(config["platform_dir"])
diff --git a/src/python/oftest/testutils.py b/src/python/oftest/testutils.py
index 943d41e..e8dec6e 100644
--- a/src/python/oftest/testutils.py
+++ b/src/python/oftest/testutils.py
@@ -1020,3 +1020,31 @@
finally:
sys.stdout = backup
return out
+
+def nonstandard(cls):
+ """
+ Testcase decorator that marks the test as being non-standard.
+ These tests are not automatically added to the "standard" group.
+ """
+ cls._nonstandard = True
+ return cls
+
+def disabled(cls):
+ """
+ Testcase decorator that marks the test as being disabled.
+ These tests are not automatically added to the "standard" group or
+ their module's group.
+ """
+ cls._disabled = True
+ return cls
+
+def group(name):
+ """
+ Testcase decorator that adds the test to a group.
+ """
+ def fn(cls):
+ if not hasattr(cls, "_groups"):
+ cls._groups = []
+ cls._groups.append(name)
+ return cls
+ return fn
diff --git a/tests/basic.py b/tests/basic.py
index 8bb6b00..16f3b31 100644
--- a/tests/basic.py
+++ b/tests/basic.py
@@ -33,6 +33,7 @@
TEST_VID_DEFAULT = 2
+@group('smoke')
class Echo(base_tests.SimpleProtocol):
"""
Test echo response with no data
@@ -64,6 +65,7 @@
self.assertEqual(request.data, response.data,
'response data does not match request')
+@group('smoke')
class PacketIn(base_tests.SimpleDataPlane):
"""
Test packet in function
@@ -115,6 +117,7 @@
'Response packet does not match send packet' +
' for port ' + str(of_port))
+@nonstandard
class PacketInDefaultDrop(base_tests.SimpleDataPlane):
"""
Test packet in function
@@ -123,8 +126,6 @@
in message is received from the controller for each
"""
- priority = -1
-
def runTest(self):
delete_all_flows(self.controller)
do_barrier(self.controller)
@@ -149,6 +150,7 @@
'Packet in message received on port ' +
str(of_port))
+@nonstandard
class PacketInBroadcastCheck(base_tests.SimpleDataPlane):
"""
Check if bcast pkts leak when no flows are present
@@ -158,8 +160,6 @@
Look for the packet on other dataplane ports.
"""
- priority = -1
-
def runTest(self):
# Need at least two ports
self.assertTrue(len(config["port_map"]) > 1, "Too few ports for test")
@@ -178,6 +178,7 @@
self.assertTrue(pkt_in is None,
'BCast packet received on port ' + str(of_port))
+@group('smoke')
class PacketOut(base_tests.SimpleDataPlane):
"""
Test packet out function
@@ -266,6 +267,7 @@
set(of_ports).difference(dp_ports),
self)
+@disabled
class FlowStatsGet(base_tests.SimpleProtocol):
"""
Get stats
@@ -273,8 +275,6 @@
Simply verify stats get transaction
"""
- priority = -1
-
def runTest(self):
logging.info("Running StatsGet")
logging.info("Inserting trial flow")
@@ -337,6 +337,7 @@
request = flow_mod_gen(config["port_map"], True)
self.controller.message_send(request)
+@group('smoke')
class PortConfigMod(base_tests.SimpleProtocol):
"""
Modify a bit in port config and verify changed
@@ -416,6 +417,7 @@
self.assertTrue(response is not None, 'Did not receive error message')
+@group('smoke')
class BadMessage(base_tests.SimpleProtocol):
"""
Send a message with a bad type and verify an error is returned
diff --git a/tests/bsn_ipmask.py b/tests/bsn_ipmask.py
index 9f0838f..fea7caa 100644
--- a/tests/bsn_ipmask.py
+++ b/tests/bsn_ipmask.py
@@ -36,13 +36,12 @@
else:
return (1 << (63 - index)) - 1
+@nonstandard
class BSNConfigIPMask(base_tests.SimpleDataPlane):
"""
Exercise BSN vendor extension for configuring IP source/dest match mask
"""
- priority = -1
-
def bsn_set_ip_mask(self, index, mask):
"""
Use the BSN_SET_IP_MASK vendor command to change the IP mask for the
diff --git a/tests/bsn_mirror.py b/tests/bsn_mirror.py
index e2d895e..10c3f93 100644
--- a/tests/bsn_mirror.py
+++ b/tests/bsn_mirror.py
@@ -75,14 +75,13 @@
action_list.action_object_map[ofp.OFPAT_VENDOR] = bsn_action_mirror
+@nonstandard
class BSNMirrorAction(base_tests.SimpleDataPlane):
"""
Exercise BSN vendor extension for copying packets to a mirror destination
port
"""
- priority = -1
-
def bsn_set_mirroring(self, enabled):
"""
Use the BSN_SET_MIRRORING vendor command to enable/disable
diff --git a/tests/caps.py b/tests/caps.py
index 55bedd8..c75bd56 100644
--- a/tests/caps.py
+++ b/tests/caps.py
@@ -90,7 +90,7 @@
time.sleep(flow_count / 100)
-
+@disabled
class FillTableExact(base_tests.SimpleProtocol):
"""
Fill the flow table with exact matches; can take a while
@@ -109,12 +109,11 @@
you can control which table to check.
"""
- priority = -1
-
def runTest(self):
logging.info("Running " + str(self))
flow_caps_common(self)
+@disabled
class FillTableWC(base_tests.SimpleProtocol):
"""
Fill the flow table with wildcard matches
@@ -135,8 +134,6 @@
"""
- priority = -1
-
def runTest(self):
logging.info("Running " + str(self))
flow_caps_common(self, is_exact=False)
diff --git a/tests/cxn.py b/tests/cxn.py
index 5718c66..4b3ee59 100644
--- a/tests/cxn.py
+++ b/tests/cxn.py
@@ -19,12 +19,12 @@
from oftest.testutils import *
+@disabled
class BaseHandshake(unittest.TestCase):
"""
Base handshake case to set up controller, but do not send hello.
"""
- priority = -1
controllers = []
default_timeout = 2
@@ -100,13 +100,12 @@
self.assertTrue(self.controllers[0].wait_disconnected(timeout=10),
"Not notified of controller disconnect")
+@disabled
class CompleteHandshake(BaseHandshake):
"""
Set up multiple controllers and complete handshake, but otherwise do nothing.
"""
- priority = -1
-
def buildControllerList(self):
# controller_list is a list of IP:port tuples
con_list = test_param_get('controller_list')
@@ -247,34 +246,31 @@
break
time.sleep(tick)
+@disabled
class HandshakeAndKeepalive(CompleteHandshake):
"""
Complete handshake and respond to echo request, but otherwise do nothing.
Good for manual testing.
"""
- priority = -1
-
def __init__(self):
CompleteHandshake.__init__(self, keep_alive=True)
+@disabled
class HandshakeNoEcho(CompleteHandshake):
"""
Complete handshake, but otherwise do nothing, and do not respond to echo.
"""
- priority = -1
-
def __init__(self):
CompleteHandshake.__init__(self, keep_alive=False)
+@disabled
class HandshakeAndDrop(CompleteHandshake):
"""
Complete handshake, but otherwise do nothing, and drop connection after a while.
"""
- priority = -1
-
def __init__(self):
CompleteHandshake.__init__(self, keep_alive=True, controller_timeout=10)
diff --git a/tests/flow_query.py b/tests/flow_query.py
index 5c5954e..476cd75 100644
--- a/tests/flow_query.py
+++ b/tests/flow_query.py
@@ -1666,6 +1666,7 @@
# Disabled.
# Should be DUT dependent.
+@nonstandard
class Flow_Add_5_1(base_tests.SimpleProtocol):
"""
Test FLOW_ADD_5.1 from draft top-half test plan
@@ -1674,8 +1675,6 @@
None
"""
- priority = -1
-
def runTest(self):
logging.info("Flow_Add_5_1 TEST BEGIN")
@@ -1783,6 +1782,7 @@
# Disabled because of bogus capacity reported by OVS.
# Should be DUT dependent.
+@nonstandard
class Flow_Add_6(base_tests.SimpleProtocol):
"""
Test FLOW_ADD_6 from draft top-half test plan
@@ -1791,8 +1791,6 @@
num_flows - Number of flows to generate
"""
- priority = -1
-
def runTest(self):
logging.info("Flow_Add_6 TEST BEGIN")
diff --git a/tests/load.py b/tests/load.py
index 9977027..0ad14e9 100644
--- a/tests/load.py
+++ b/tests/load.py
@@ -30,6 +30,7 @@
from oftest.testutils import *
+@nonstandard
class LoadBarrier(base_tests.SimpleProtocol):
"""
Test barrier under load with loopback
@@ -44,8 +45,6 @@
the test fails.
"""
- priority = -1
-
def runTest(self):
# Set up flow to send from port 1 to port 2 and copy to CPU
# Test parameter gives LB port base (assumes consecutive)
diff --git a/tests/message_types.py b/tests/message_types.py
index 889c281..7785f36 100644
--- a/tests/message_types.py
+++ b/tests/message_types.py
@@ -111,6 +111,7 @@
'Message field code is not OFPBRC_BAD_VERSION')
+@group('smoke')
class FeaturesReplyBody(base_tests.SimpleProtocol):
"""Verify the body of Features Reply message"""
@@ -491,14 +492,12 @@
self.assertEqual(response.in_port,of_ports[0],"PacketIn in_port or recieved port field is incorrect")
-
+@nonstandard
class PortStatusMessage(base_tests.SimpleDataPlane):
"""Verify Port Status Messages are sent to the controller
whenever physical ports are added, modified or deleted"""
- priority = -1
-
def runTest(self):
logging.info("Running PortStatusMessage Test")
diff --git a/tests/nicira_role.py b/tests/nicira_role.py
index a8ac8cf..fa4cc97 100644
--- a/tests/nicira_role.py
+++ b/tests/nicira_role.py
@@ -19,13 +19,12 @@
NXT_ROLE_VALUE = dict( other=0, slave=1, master=2 )
+@nonstandard
class NiciraRoleRequest(base_tests.SimpleDataPlane):
"""
Exercise Nicira vendor extension for requesting HA roles
"""
- priority = 0
-
def nicira_role_request(self, role):
"""
Use the BSN_SET_IP_MASK vendor command to change the IP mask for the
diff --git a/tests/pktact.py b/tests/pktact.py
index 17b555f..7d30698 100644
--- a/tests/pktact.py
+++ b/tests/pktact.py
@@ -78,6 +78,7 @@
TEST_VID_DEFAULT = 2
+@group('smoke')
class DirectPacket(base_tests.SimpleDataPlane):
"""
Send packet to single egress port
@@ -146,6 +147,7 @@
self.assertEqual(str(pkt), str(rcv_pkt),
'Response packet does not match send packet')
+@group('smoke')
class DirectPacketController(base_tests.SimpleDataPlane):
"""
Send packet to the controller port
@@ -927,13 +929,12 @@
vid = test_param_get('vid', default=TEST_VID_DEFAULT)
flow_match_test(self, config["port_map"], dl_vlan=vid)
+@disabled
class ExactMatchTaggedMany(BaseMatchCase):
"""
ExactMatchTagged with many VLANS
"""
- priority = -1
-
def runTest(self):
for vid in range(2,100,10):
flow_match_test(self, config["port_map"], dl_vlan=vid, max_test=5)
@@ -1103,6 +1104,7 @@
self.verifyFlow(of_ports[0], of_ports[2])
+@group("smoke")
class WildcardPriorityWithDelete(SingleWildcardMatchPriority):
"""
1. Add wildcard match flow, verify packet received.
@@ -1236,7 +1238,7 @@
flow_match_test(self, config["port_map"], wildcards=ofp.OFPFW_ALL,
dl_vlan=vid)
-
+@group('smoke')
class AddVLANTag(BaseMatchCase):
"""
Add a VLAN tag to an untagged packet
@@ -1259,13 +1261,12 @@
flow_match_test(self, config["port_map"], pkt=pkt,
exp_pkt=exp_pkt, action_list=[vid_act])
+@disabled
class PacketOnly(base_tests.DataPlaneOnly):
"""
Just send a packet thru the switch
"""
- priority = -1
-
def runTest(self):
pkt = simple_tcp_packet()
of_ports = config["port_map"].keys()
@@ -1275,13 +1276,12 @@
logging.debug("Data: " + str(pkt).encode('hex'))
self.dataplane.send(ing_port, str(pkt))
+@disabled
class PacketOnlyTagged(base_tests.DataPlaneOnly):
"""
Just send a packet thru the switch
"""
- priority = -1
-
def runTest(self):
vid = test_param_get('vid', default=TEST_VID_DEFAULT)
pkt = simple_tcp_packet(dl_vlan_enable=True, dl_vlan=vid)
@@ -1688,6 +1688,7 @@
flow_match_test(self, config["port_map"], pkt=pkt, exp_pkt=exp_pkt,
action_list=acts, max_test=2, egr_count=-1)
+@group("smoke")
class ModifyAll(BaseMatchCase):
"""
Modify all supported fields and output to a port
@@ -1828,6 +1829,7 @@
ModifyL2SrcDstMC
]
+@disabled
class IterCases(BaseMatchCase):
"""
Iterate over a bunch of test cases
@@ -1835,8 +1837,6 @@
The cases come from the list above
"""
- priority = -1
-
def runTest(self):
count = test_param_get('iter_count', default=10)
tests_done = 0
@@ -1872,6 +1872,7 @@
# and modifies tag 4 to tag 5. Then verify (in addition) that
# tag 6 does not get modified.
+@disabled
class MixedVLAN(BaseMatchCase):
"""
Test mixture of VLAN tag actions
@@ -1895,8 +1896,7 @@
If only VID 5 distinguishes pkt, this will fail on some platforms
"""
- priority = -1
-
+@group('smoke')
class MatchEach(base_tests.SimpleDataPlane):
"""
Check that each match field is actually matched on.
diff --git a/tests/port_stats.py b/tests/port_stats.py
index 8a762e8..5e0648f 100644
--- a/tests/port_stats.py
+++ b/tests/port_stats.py
@@ -129,6 +129,7 @@
obj.assertTrue(all_packets_received,
"Packet received does not match number sent")
+@group('smoke')
class SingleFlowStats(base_tests.SimpleDataPlane):
"""
Verify flow stats are properly retrieved.