commit 9ad8e44a9a430521eb664b3bc508caad1e28e9f8 Author: Isis Lovecruft isis@torproject.org Date: Thu Nov 1 10:03:36 2012 +0000
* Remainer of the TestCase changes, now that the other problems have been sorted out. Restructured most of the logic for test creation, and removed some assertion which are actually incapable of being False. --- ooni/nettest.py | 78 +++++++++++++++++++++++++++++++-------- ooni/runner.py | 100 +++++++++++++++++++++++++++++++++++-------------- ooni/utils/legacy.py | 4 +- 3 files changed, 135 insertions(+), 47 deletions(-)
diff --git a/ooni/nettest.py b/ooni/nettest.py index ff16f0a..e3c7800 100644 --- a/ooni/nettest.py +++ b/ooni/nettest.py @@ -112,6 +112,29 @@ class TestCase(unittest.TestCase): optParameters = None requiresRoot = False
+ def setUpClass(self, *args, **kwargs): + """ + Create a TestCase instance. This function is equivalent to '__init__'. + To add futher setup steps before a set of tests in a TestCase instance + run, create a function called 'setUp'. + + Class attributes, such as `report`, `optParameters`, `name`, and + `author` should be overriden statically as class attributes in any + subclass of :class:`ooni.nettest.TestCase`, so that the calling + functions in ooni.runner can handle them correctly. + """ + methodName = 'runTest' + if kwargs: + if 'methodName' in kwargs: + methodName = kwargs['methodName'] + + super(TestCase, self).setUpClass() + + #for key, value in kwargs.items(): + # setattr(self.__class__, key, value) + # + #self.inputs = self.getInputs() + def deferSetUp(self, ignored, result): """ If we have the reporterFactory set we need to write the header. If @@ -128,30 +151,53 @@ class TestCase(unittest.TestCase): log.debug("Not first run. Running test setup directly") return unittest.TestCase.deferSetUp(self, ignored, result)
- def inputProcessor(self, fp): - log.debug("Running default input processor") - for x in fp.readlines(): - yield x.strip() - fp.close() + @staticmethod + def inputParser(inputs): + """Replace me with a custom function for parsing inputs.""" + log.debug("Running custom input processor") + return inputs + + def _getInputs(self): + """ + I am called from the ooni.runner and you probably should not override + me. I gather the internal inputs from an instantiated test class and + pass them to the rest of the runner. + + If you are looking for a way to parse inputs from inputFile, see + :meth:`inputParser`. + I open :attr:inputFile if there is one, and return inputs one by one + after stripping them of whitespace and running them through the parser + :meth:`inputParser`. + """ + + ## don't burn cycles on testing null inputs: + if len(self.inputs) == 1 and self.inputs[0] == None: + self.inputs = [] + processor = [] + else: + log.msg("Received direct inputs:\n%s" % self.inputs) + processor = [i for i in self.inputProcessor(self.inputs)]
- def getOptions(self): - log.debug("Getting options for test") if self.inputFile: try: - fp = open(self.inputFile) ## xxx fixme: - except Exception, e: ## bad news to leave file - log.err(e) ## descriptors open + fp = open(self.inputFile) + except Exception, e: + log.err(e) + fp.close() else: - from_file = self.__input_file_processor__(fp) - self.inputs = itertools.chain(processor, from_file) + log.debug("Running input file processor") + lines = [line.strip() for line in fp.readlines()] + fp.close() + parsed = [self.inputParser(ln) for ln in lines] + both = itertools.chain(processor, parsed) elif self.inputFile is False: log.debug("%s specified that it doesn't need inputFile." - % self.__class__.__name__) - self.inputs = processed + % self.name) + both = processor else: - raise BrokenImplementation + both = processor + return both
- return self.inputs
def getOptions(self): ''' diff --git a/ooni/runner.py b/ooni/runner.py index 36f5b6e..0e5dbba 100644 --- a/ooni/runner.py +++ b/ooni/runner.py @@ -134,8 +134,12 @@ def processTestOptions(cls, config): if cls.inputFile: cls.optParameters.append(cls.inputFile)
+ if not hasattr(cls, subCommands): + cls.subCommands = [] + class Options(usage.Options): optParameters = cls.optParameters + parseArgs = lambda a: cls.subCommands.append(a)
opts = Options() opts.parseOptions(config['subArgs']) @@ -157,63 +161,101 @@ def processTestOptions(cls, config): except usage.UsageError: options = opts.opt_help() else: - return cls, options """ - - return cls, cls.localOptions + return cls.localOptions
def loadTestsAndOptions(classes, config): """ Takes a list of test classes and returns their testcases and options. Legacy tests will be adapted. """ + from inspect import isclass
method_prefix = 'test' options = [] test_cases = []
- _old_class_type = LegacyOONITest + DEPRECATED = LegacyOONITest
for klass in classes: - if isinstance(klass, _old_class_type): + if isinstance(klass, DEPRECATED) \ + and not issubclass(klass, TestCase): + log.msg("Processing cases and options for legacy test %s" + % ( klass.shortName if hasattr(klass, shortName) + else 'oonitest' )) + if hasattr(klass, description): + log.msg("%s" % klass.description) + + subcmds = [] + if hasattr(klass, options): ## an unitiated Legacy test + log.debug("%s.options found: %s " % (klass, klass.options)) + try: + assert isclass(klass.options), \ + "%s is not class" % klass.qoptions + except AssertionError, ae: + log.debug(ae) + else: + ok = klass.options + ok.parseArgs = lambda x: subcmds.append(x) + try: + opts = ok() + opts.parseOptions(config['subArgs']) + except Exception, e: + log.debug(e) + opts = {} + finally: + opts.append(opts) + + if hasattr(klass, local_options): ## we've been initialized already + log.debug("%s.local_options found" % klass) + try: + assert klass.local_options is not None + opts = klass.local_options + except AttributeError, ae: + opts = {}; log.debug(ae) + finally: + log.debug("type(opts) = %s" % type(opts)) + options.append(opts) try: cases = start_legacy_test(klass) - if cases: - log.debug("Processing cases") - log.debug(str(cases)) - return [], [] + #if cases: ## why are these empty lists here? + # return [], [] ## were nettests having issues due + except Exception, e: ## to legacy tests? + cases = []; log.err(e) + finally: + log.debug(str(cases)) test_cases.append(cases) + + elif issubclass(klass, TestCase): + log.debug("Processing cases and options for OONI %s test" + % ( klass.name if hasattr( klass, 'name' ) \ + else TestCase.name )) + try: + tests = reflect.prefixedMethodNames(klass, method_prefix) except Exception, e: - log.err(e) + tests = []; log.debug(e) else: try: - opts = klass.local_options - options.append(opts) - except AttributeError, ae: - options.append([]) - log.err(ae) - if cases: - print cases - return [], [] - else: - tests = reflect.prefixedMethodNames(klass, method_prefix) - if tests: - cases = makeTestCases(klass, tests, method_prefix) - test_cases.append(cases) + cases = makeTestCases(klass, tests, method_prefix) + except Exception, e: + cases = []; log.err(e) + log.debug("loadTestsAndOptions(): test %s found cases=%s" + % (tests, cases)) + try: opts = processTestOptions(klass, config) except AttributeError, ae: - options.append([]) - log.err(ae) - else: + opts = {}; log.err(ae) + finally: try: instance = klass() - inputs = instance.__get_inputs__() + inputs = instance._getInputs() except Exception, e: - log.err(e) + inputs = []; log.err(e) else: - opts.update(inputs) + opts.update({'inputs': inputs}) options.append(opts) + log.debug("loadTestsAndOptions(): type(opts)=%s" % type(opts))
return test_cases, options
diff --git a/ooni/utils/legacy.py b/ooni/utils/legacy.py index 56a3ca6..61fbe2f 100755 --- a/ooni/utils/legacy.py +++ b/ooni/utils/legacy.py @@ -34,7 +34,7 @@ from ooni import nettest from ooni.plugoo.tests import OONITest from ooni.plugoo import work, reports from ooni.utils import log, date -from ooni.utils.logo import getLogo +from ooni.utils.logo import getlogo
def runTest(test, options, global_options, reactor=reactor): @@ -121,7 +121,7 @@ def run_ooniprobe_py(*args): try: old_api.parseOptions() except: - log.msg("Use of this API is deprecated. Please use /bin/ooniprobe." + log.msg("Use of this API is deprecated. Please use /bin/ooniprobe.") runTest(old_api.sub_command, old_api.subOptions, old_api) reactor.run()