commit 1928f5b0b898c1cdfe817e9432b92543c4763298
Author: Isis Lovecruft <isis(a)torproject.org>
Date: Wed Oct 31 17:10:12 2012 +0000
* Refactored nettest.TestCase to get it to recognize test suboptions.
---
ooni/nettest.py | 194 +++++++++++++++++++++++++++++++++++++++++--------------
1 files changed, 146 insertions(+), 48 deletions(-)
diff --git a/ooni/nettest.py b/ooni/nettest.py
index d35bd4d..3c87b7f 100644
--- a/ooni/nettest.py
+++ b/ooni/nettest.py
@@ -1,9 +1,21 @@
+# -*- coding: utf-8 -*-
+
import itertools
import os
-from twisted.trial import unittest, itrial
-from twisted.internet import defer, utils
-from ooni.utils import log
+from inspect import classify_class_attrs
+from pprint import pprint
+
+from twisted.internet import defer, utils
+from twisted.python import usage
+from twisted.trial import unittest, itrial
+from zope.interface.exceptions import BrokenImplementation
+
+from ooni.inputunit import InputUnitProcessor
+from ooni.utils import log
+from ooni.utils.assertions import isClass, isNotClass
+from ooni.utils.assertions import isOldStyleClass, isNewStyleClass
+
pyunit = __import__('unittest')
@@ -33,19 +45,19 @@ class TestCase(unittest.TestCase):
you will subclass this object.
* inputs: can be set to a static set of inputs. All the tests (the methods
- starting with the "test_" prefix) will be run once per input. At every run
- the _input_ attribute of the TestCase instance will be set to the value of
- the current iteration over inputs. Any python iterable object can be set
- to inputs.
+ starting with the "test_" prefix) will be run once per input. At every
+ run the _input_ attribute of the TestCase instance will be set to the
+ value of the current iteration over inputs. Any python iterable object
+ can be set to inputs.
- * inputFile: attribute should be set to an array containing the command line
- argument that should be used as the input file. Such array looks like
- this:
+ * inputFile: attribute should be set to an array containing the command
+ line argument that should be used as the input file. Such array looks
+ like this:
``["commandlinearg", "c", "The description"]``
- The second value of such arrray is the shorthand for the command line arg.
- The user will then be able to specify inputs to the test via:
+ The second value of such arrray is the shorthand for the command line
+ arg. The user will then be able to specify inputs to the test via:
``ooniprobe mytest.py --commandlinearg path/to/file.txt``
@@ -55,8 +67,8 @@ class TestCase(unittest.TestCase):
* inputProcessor: should be set to a function that takes as argument an
- open file descriptor and it will yield the input to be passed to the test
- instance.
+ open file descriptor and it will yield the input to be passed to the
+ test instance.
* name: should be set to the name of the test.
@@ -67,32 +79,47 @@ class TestCase(unittest.TestCase):
* version: is the version string of the test.
"""
- name = "I Did Not Change The Name"
- author = "John Doe <foo(a)example.com>"
- version = "0"
+ name = "I Did Not Change The Name"
+ author = "John Doe <foo(a)example.com>"
+ version = "0.0.0"
- inputs = [None]
inputFile = None
+ inputs = [None]
report = {}
report['errors'] = []
optParameters = None
+ optFlags = None
+ subCommands = None
- def _raaun(self, methodName, result):
- from twisted.internet import reactor
- method = getattr(self, methodName)
- log.debug("Running %s" % methodName)
- d = defer.maybeDeferred(
- utils.runWithWarningsSuppressed, self._getSuppress(), method)
- d.addBoth(lambda x : call.active() and call.cancel() or x)
- return d
+ 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).__init__(methodName=methodName)
+ #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 such
- method is not present we will only run the test skipping header
+ If we have the reporterFactory set we need to write the header. If
+ such method is not present we will only run the test skipping header
writing.
"""
if result.reporterFactory.firstrun:
@@ -103,31 +130,102 @@ class TestCase(unittest.TestCase):
else:
return unittest.TestCase.deferSetUp(self, ignored, result)
- def inputProcessor(self, fp):
- for x in fp.readlines():
- yield x.strip()
+ def _raaun(self, methodName, result):
+ from twisted.internet import reactor
+ method = getattr(self, methodName)
+ log.debug("Running %s" % methodName)
+ d = defer.maybeDeferred(
+ utils.runWithWarningsSuppressed, self._getSuppress(), method)
+ d.addBoth(lambda x : call.active() and call.cancel() or x)
+ return d
+
+ @staticmethod
+ def inputParser(inputs):
+ """Replace me with a custom function for parsing inputs."""
+ return inputs
+
+ def __input_file_processor__(self, fp):
+ """
+ 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`.
+ """
+ for line in fp.readlines():
+ yield self.inputParser(line.strip())
fp.close()
- def getOptions(self):
+ def __get_inputs__(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`.
+ """
+ processor = InputUnitProcessor(self.inputs,
+ input_filter=None,
+ catch_err=False)
+ processed = processor.process()
+
+ log.msg("Received direct inputs:\n%s" % inputs)
+ log.debug("Our InputUnitProcessor is %s" % processor)
+
+ #while processed is not StopIteration:
+ # self.inputs = processed
+ # yield self.inputs
+ #else:
+ # if self.inputFile:
+
if self.inputFile:
try:
- assert isinstance(self.inputFile, str)
- except AssertionError, ae:
- log.err(ae)
+ fp = open(self.inputFile) ## xxx fixme:
+ except Exception, e: ## bad news to leave file
+ log.err(e) ## descriptors open
+ else:
+ from_file = self.__input_file_processor__(fp)
+ self.inputs = itertools.chain(processor, from_file)
+ elif self.inputFile is False:
+ log.debug("%s specified that it doesn't need inputFile."
+ % self.__class__.__name__)
+ self.inputs = processed
+ else:
+ raise BrokenImplementation
+
+ return self.inputs
+
+ def getOptions(self):
+ '''
+ for attr in attributes:
+ if not attr.name is 'optParameters' or attr.name is 'optFlags':
+ continue
+ elif attr.name is 'optParameters':
+ cls._optParameters = attr.object
+ elif attr.name is 'optFlags':
+ log.debug("Applying %s.%s = %s to data descriptor..."
+ % (cls.__name__, "_"+attr.name, attr.object))
+ cls._optParameters = attr.object
else:
- if os.path.isfile(self.inputFile):
- print self.inputFile
- fp = open(self.inputFile)
- self.inputs = self.inputProcessor(fp)
- elif not self.inputs[0]:
- pass
- elif self.inputFile:
- raise usage.UsageError("No input file specified!")
- # XXX perhaps we may want to name and version to be inside of a
- # different object that is not called options.
- return {'inputs': self.inputs,
- 'name': self.name,
- 'version': self.version}
+ log.debug("How did we get here? attr.name = %s" % attr.name)
+ '''
+ if self.localOptions:
+ if self.inputs[0] is not None or self.inputFile is not None:
+ self.__get_inputs__()
+ return self.localOptions
+ else:
+ raise Exception, "could not find cls.localOptions! 234"
+
+ # if options:
+ # return options
+ # else:
+ # ## is this safe to do? it might turn Hofstaeder...
+ # return self.__dict__
+ ####################
+ ## original return
+ ####################
+ #return {'inputs': self.inputs,
+ # 'name': self.name,
+ # 'version': self.version}
def __repr__(self):
return "<%s inputs=%s>" % (self.__class__, self.inputs)