Merge into master from pull request #131:
update pyloxi and add tcp flags support (https://github.com/floodlight/oftest/pull/131)
diff --git a/oft b/oft
index 9d5df6a..d89ef3a 100755
--- a/oft
+++ b/oft
@@ -78,6 +78,9 @@
     "log_dir"            : None,
     "debug"              : "verbose",
     "profile"            : False,
+    "profile_file"       : "profile.out",
+    "xunit"              : False,
+    "xunit_dir"          : "xunit",
 
     # Test behavior options
     "relax"              : False,
@@ -181,7 +184,10 @@
                      const="verbose", help="Shortcut for --debug=verbose")
     group.add_option("-q", "--quiet", action="store_const", dest="debug",
                      const="warning", help="Shortcut for --debug=warning")
-    group.add_option("--profile", action="store_true", help="Write Python profile to profile.out")
+    group.add_option("--profile", action="store_true", help="Enable Python profiling")
+    group.add_option("--profile-file", help="Output file for Python profiler")
+    group.add_option("--xunit", action="store_true", help="Enable xUnit-formatted results")
+    group.add_option("--xunit-dir", help="Output directory for xUnit-formatted results")
     parser.add_option_group(group)
 
     group = optparse.OptionGroup(parser, "Test behavior options")
@@ -240,6 +246,19 @@
 
     oftest.open_logfile('main')
 
+def xunit_setup(config):
+    """
+    Set up xUnit output based on config
+    """
+
+    if not config["xunit"]:
+        return
+
+    if os.path.exists(config["xunit_dir"]):
+        import shutil
+        shutil.rmtree(config["xunit_dir"])
+    os.makedirs(config["xunit_dir"])
+
 def pcap_setup(config):
     """
     Set up dataplane packet capturing based on config
@@ -252,6 +271,31 @@
         # start_pcap is called per-test in base_tests
         pass
 
+def profiler_setup(config):
+    """
+    Set up profiler based on config
+    """
+
+    if not config["profile"]:
+        return
+
+    import cProfile
+    profiler = cProfile.Profile()
+    profiler.enable()
+
+    return profiler
+
+def profiler_teardown(profiler):
+    """
+    Tear down profiler based on config
+    """
+
+    if not config["profile"]:
+        return
+
+    profiler.disable()
+    profiler.dump_stats(config["profile_file"])
+
 
 def load_test_modules(config):
     """
@@ -371,6 +415,7 @@
 oftest.config.update(new_config)
 
 logging_setup(config)
+xunit_setup(config)
 logging.info("++++++++ " + time.asctime() + " ++++++++")
 
 # Pick an OpenFlow protocol module based on the configured version
@@ -518,10 +563,7 @@
 signal.signal(signal.SIGINT, signal.SIG_DFL)
 
 if __name__ == "__main__":
-    if config["profile"]:
-        import cProfile
-        profiler = cProfile.Profile()
-        profiler.enable()
+    profiler = profiler_setup(config)
 
     # Set up the dataplane
     oftest.dataplane_instance = oftest.dataplane.DataPlane(config)
@@ -530,11 +572,24 @@
         oftest.dataplane_instance.port_add(ifname, of_port)
 
     logging.info("*** TEST RUN START: " + time.asctime())
-    result = unittest.TextTestRunner(verbosity=2).run(suite)
+    if config["xunit"]:
+        try:
+            import xmlrunner  # fail-fast if module missing
+        except ImportError as ex:
+            oftest.dataplane_instance.kill()
+            profiler_teardown(profiler)
+            raise ex
+        runner = xmlrunner.XMLTestRunner(output=config["xunit_dir"],
+                                         outsuffix="",
+                                         verbosity=2)
+        result = runner.run(suite)
+    else:
+        result = unittest.TextTestRunner(verbosity=2).run(suite)
     oftest.open_logfile('main')
     if oftest.testutils.skipped_test_count > 0:
         ts = " tests"
-        if oftest.testutils.skipped_test_count == 1: ts = " test"
+        if oftest.testutils.skipped_test_count == 1:
+            ts = " test"
         logging.info("Skipped " + str(oftest.testutils.skipped_test_count) + ts)
         print("Skipped " + str(oftest.testutils.skipped_test_count) + ts)
     logging.info("*** TEST RUN END  : " + time.asctime())
@@ -543,9 +598,7 @@
     oftest.dataplane_instance.kill()
     oftest.dataplane_instance = None
 
-    if config["profile"]:
-        profiler.disable()
-        profiler.dump_stats("profile.out")
+    profiler_teardown(profiler)
 
     if result.failures or result.errors:
         # exit(1) hangs sometimes
diff --git a/src/python/oftest/testutils.py b/src/python/oftest/testutils.py
index c865b32..85ccfc3 100644
--- a/src/python/oftest/testutils.py
+++ b/src/python/oftest/testutils.py
@@ -1582,7 +1582,7 @@
             # Found a matching message
             break
 
-    test.assertTrue(msg is not None, 'Packet in message not received on port %d' % in_port)
+    test.assertTrue(msg is not None, 'Packet in message not received on port %r' % in_port)
     return msg
 
 def verify_no_packet_in(test, data, in_port, controller=None):