simplify test list generation

os.walk is used instead of find/grep. All python files in the test directory
are imported and used as modules if they define a subclass of
unittest.TestCase. A single datastructure of tests cases is produced instead of
two.
diff --git a/oft b/oft
index c891555..8bd9340 100755
--- a/oft
+++ b/oft
@@ -30,10 +30,6 @@
 <pre>
     dbg_level         : logging module value of debug level
     port_map          : Map of dataplane OpenFlow port to OS interface names
-    test_mod_map      : Dictionary indexed by module names and whose value
-                        is the module reference
-    all_tests         : Dictionary indexed by module reference and whose
-                        value is a list of functions in that module
 </pre>
 
 Each test may be assigned a priority by setting test_prio["TestName"] in 
@@ -117,6 +113,7 @@
 import imp
 import random
 import signal
+import fnmatch
 
 root_dir = os.path.dirname(os.path.realpath(__file__))
 
@@ -324,60 +321,49 @@
                         level=config["dbg_level"],
                         format=_format, datefmt=_datefmt)
 
-def test_list_generate(config):
-    """Generate the list of all known tests indexed by module name
+def load_test_modules(config):
+    """
+    Load tests from the test_dir directory.
 
-    Conventions:  Test files must implement the function test_set_init
-
-    Test cases are classes that implement runTest
+    Test cases are subclasses of unittest.TestCase
 
     @param config The oft configuration dictionary
-    @returns An array of triples (mod-name, module, [tests]) where 
-    mod-name is the string (filename) of the module, module is the
-    value returned from __import__'ing the module and [tests] is an
-    array of strings giving the test cases from the module.  
+    @returns A dictionary from test module names to tuples of
+    (module, dictionary from test names to test classes).
     """
 
-    # Find and import test files
-    p1 = Popen(["find", config["test_dir"], "-type","f"], stdout = PIPE)
-    p2 = Popen(["xargs", "grep", "-l", "-e", "^def test_set_init"], 
-                stdin=p1.stdout, stdout=PIPE)
+    result = {}
 
-    all_tests = {}
-    mod_name_map = {}
-    # There's an extra empty entry at the end of the list 
-    filelist = p2.communicate()[0].split("\n")[:-1]
-    for file in filelist:
-        if file[-1:] == '~' or file[0] == '#':
-            continue
-        modname = os.path.splitext(os.path.basename(file))[0]
+    for root, dirs, filenames in os.walk(config["test_dir"]):
+        # Iterate over each python file
+        for filename in fnmatch.filter(filenames, '[!.]*.py'):
+            modname = os.path.splitext(os.path.basename(filename))[0]
 
-        try:
-            if sys.modules.has_key(modname):
-                mod = sys.modules[modname]
-            else:
-                mod = imp.load_module(modname, *imp.find_module(modname, [os.path.dirname(file)]))
-        except:
-            logging.warning("Could not import file " + file)
-            raise
+            try:
+                if sys.modules.has_key(modname):
+                    mod = sys.modules[modname]
+                else:
+                    mod = imp.load_module(modname, *imp.find_module(modname, [root]))
+            except:
+                logging.warning("Could not import file " + filename)
+                raise
 
-        tests = [k for k in dir(mod) if type(getattr(mod, k)) == type and
-                                        issubclass(getattr(mod, k), unittest.TestCase)]
-        if tests:
-            mod_name_map[modname] = mod
-            all_tests[mod] = tests
+            # Find all testcases defined in the module
+            tests = dict((k, v) for (k, v) in mod.__dict__.items() if type(v) == type and
+                                                                      issubclass(v, unittest.TestCase))
+            if tests:
+                result[modname] = (mod, tests)
 
-    config["all_tests"] = all_tests
-    config["mod_name_map"] = mod_name_map
+    return result
 
 def die(msg, exit_val=1):
     print msg
     logging.critical(msg)
     sys.exit(exit_val)
 
-def add_test(suite, mod, name):
-    logging.info("Adding test " + mod.__name__ + "." + name)
-    suite.addTest(getattr(mod, name)())
+def add_test(suite, mod, test):
+    logging.info("Adding test " + mod.__name__ + "." + test.__name__)
+    suite.addTest(test())
 
 def _space_to(n, str):
     """
@@ -419,7 +405,7 @@
 # Allow tests to import each other
 sys.path.append(config["test_dir"])
 
-test_list_generate(config)
+test_modules = load_test_modules(config)
 oft_config = config
 
 load_profile(config)
@@ -430,9 +416,9 @@
     mod_count = 0
     test_count = 0
     print "\nTest List:"
-    for mod in config["all_tests"].keys():
+    for (modname, (mod, tests)) in test_modules.items():
         if config["test_spec"] != "all" and \
-                config["test_spec"] != mod.__name__:
+                config["test_spec"] != modname:
             continue
         mod_count += 1
         did_print = True
@@ -440,16 +426,16 @@
         desc = desc.split('\n')[0]
         start_str = "  Module " + mod.__name__ + ": "
         print start_str + _space_to(22, start_str) + desc
-        for test in config["all_tests"][mod]:
+        for (testname, test) in tests.items():
             try:
-                desc = getattr(mod, test).__doc__.strip()
+                desc = test.__doc__.strip()
                 desc = desc.split('\n')[0]
             except:
                 desc = "No description"
-            if test_prio_get(mod, test) < 0:
-                start_str = "  * " + test + ":"
+            if test_prio_get(mod, testname) < 0:
+                start_str = "  * " + testname + ":"
             else:
-                start_str = "    " + test + ":"
+                start_str = "    " + testname + ":"
             if len(start_str) > 22:
                 desc = "\n" + _space_to(22, "") + desc
             print start_str + _space_to(22, start_str) + desc
@@ -469,15 +455,13 @@
 
 # Check if test list is requested; display and exit if so
 if config["list_test_names"]:
-    for mod in config["all_tests"].keys():
+    for (modname, (mod, tests)) in test_modules.items():
         if config["test_spec"] != "all" and \
-                config["test_spec"] != mod.__name__:
+                config["test_spec"] != modname:
             continue
-        desc = mod.__doc__.strip()
-        desc = desc.split('\n')[0]
-        for test in config["all_tests"][mod]:
-            if test_prio_get(mod, test) >= 0:
-                print "%s.%s" % (mod.__name__, test)
+        for (testname, test) in tests.items():
+            if test_prio_get(mod, testname) >= 0:
+                print "%s.%s" % (modname, testname)
     sys.exit(0)
 
 # Generate the test suite
@@ -486,37 +470,39 @@
 
 #@todo Allow specification of priority to override prio check
 if config["test_spec"] == "all":
-    for mod in config["all_tests"].keys(): 
-       for test in config["all_tests"][mod]:
-           # For now, a way to avoid tests
-           if test_prio_get(mod, test) >= 0:
-               add_test(suite, mod, test)
+    for (modname, (mod, tests)) in test_modules.items():
+        for (testname, test) in tests:
+            # For now, a way to avoid tests
+            if test_prio_get(mod, testname) >= 0:
+                add_test(suite, mod, test)
 
 else:
     for ts_entry in config["test_spec"].split(","):
         parts = ts_entry.split(".")
 
         if len(parts) == 1: # Either a module or test name
-            if ts_entry in config["mod_name_map"].keys():
-                mod = config["mod_name_map"][ts_entry]
-                for test in config["all_tests"][mod]:
-                    if test_prio_get(mod, test) >= 0:
+            if ts_entry in test_modules:
+                (mod, tests) = test_modules[ts_entry]
+                for testname, test in tests.items():
+                    if test_prio_get(mod, testname) >= 0:
                         add_test(suite, mod, test)
             else: # Search for matching tests
                 test_found = False
-                for mod in config["all_tests"].keys():
-                    if ts_entry in config["all_tests"][mod]:
-                        add_test(suite, mod, ts_entry)
+                for (modname, (mod, tests)) in test_modules.items():
+                    if ts_entry in tests:
+                        add_test(suite, mod, tests[ts_entry])
                         test_found = True
                 if not test_found:
                     die("Could not find module or test: " + ts_entry)
 
         elif len(parts) == 2: # module.test
-            if parts[0] not in config["mod_name_map"]:
+            if parts[0] not in test_modules:
                 die("Unknown module in test spec: " + ts_entry)
-            mod = config["mod_name_map"][parts[0]]
-            if parts[1] in config["all_tests"][mod]:
-                add_test(suite, mod, parts[1])
+            modname = parts[0]
+            testname = parts[1]
+            (mod, tests) = test_modules[modname]
+            if testname in tests:
+                add_test(suite, mod, tests[testname])
             else:
                 die("No known test matches: " + ts_entry)
 
@@ -546,7 +532,7 @@
 logging.info("OF port map: " + str(config["port_map"]))
 
 # Init the test sets
-for (modname,mod) in config["mod_name_map"].items():
+for (modname, (mod, tests)) in test_modules.items():
     try:
         mod.test_set_init(config)
     except: