[tor-commits] [ooni-probe/master] Reorganization of code tree

art at torproject.org art at torproject.org
Wed Jun 19 12:32:45 UTC 2013


commit 7393a4e3eb57bcf3c14685b240e3ab5eebfe97d3
Author: Arturo Filastò <art at fuffa.org>
Date:   Wed Mar 6 15:48:48 2013 +0100

    Reorganization of code tree
    
    * Move unittests into ooni directory
---
 bin/ooniprobe                     |    4 +-
 nettests/examples/example_myip.py |    4 +
 nettests/tls-handshake.py         |   32 -----
 ooni/api/spec.py                  |   92 +++++++++++++
 ooni/config.py                    |  134 ++++++++-----------
 ooni/director.py                  |   29 +++-
 ooni/nettest.py                   |  208 ++++++++++++++++++++--------
 ooni/oonicli.py                   |    7 +-
 ooni/oonid.py                     |   20 +++
 ooni/reporter.py                  |    2 +-
 ooni/runner.py                    |  241 ---------------------------------
 ooni/tests/mocks.py               |  172 ++++++++++++++++++++++++
 ooni/tests/test-class-design.py   |  101 ++++++++++++++
 ooni/tests/test_director.py       |   58 ++++++++
 ooni/tests/test_dns.py            |   24 ++++
 ooni/tests/test_managers.py       |  215 +++++++++++++++++++++++++++++
 ooni/tests/test_mutate.py         |   15 +++
 ooni/tests/test_nettest.py        |  268 +++++++++++++++++++++++++++++++++++++
 ooni/tests/test_otime.py          |   15 +++
 ooni/tests/test_reporter.py       |  238 ++++++++++++++++++++++++++++++++
 ooni/tests/test_safe_represent.py |   14 ++
 ooni/tests/test_trueheaders.py    |   41 ++++++
 ooni/tests/test_utils.py          |   20 +++
 ooni/utils/log.py                 |    8 +-
 ooniprobe.conf.sample             |    2 +
 tests/mocks.py                    |  168 -----------------------
 tests/test-class-design.py        |  101 --------------
 tests/test_director.py            |   59 --------
 tests/test_dns.py                 |   24 ----
 tests/test_inputunit.py           |   29 ----
 tests/test_managers.py            |  215 -----------------------------
 tests/test_mutate.py              |   15 ---
 tests/test_nettest.py             |  268 -------------------------------------
 tests/test_otime.py               |   15 ---
 tests/test_reporter.py            |  238 --------------------------------
 tests/test_safe_represent.py      |   14 --
 tests/test_trueheaders.py         |   41 ------
 tests/test_utils.py               |   20 ---
 38 files changed, 1545 insertions(+), 1626 deletions(-)

diff --git a/bin/ooniprobe b/bin/ooniprobe
index 695b137..ba537ab 100755
--- a/bin/ooniprobe
+++ b/bin/ooniprobe
@@ -8,10 +8,10 @@ sys.path[:] = map(os.path.abspath, sys.path)
 sys.path.insert(0, os.path.abspath(os.getcwd()))
 
 # This is a hack to overcome a bug in python
-from ooni.utils.hacks import patched_reduce_ex
+from ooniprobe.utils.hacks import patched_reduce_ex
 copy_reg._reduce_ex = patched_reduce_ex
 
 # from ooni.oonicli import run
 # run()
-from ooni.oonicli import runWithDirector
+from ooniprobe.oonicli import runWithDirector
 runWithDirector()
diff --git a/nettests/examples/example_myip.py b/nettests/examples/example_myip.py
index 40a4849..70cf773 100644
--- a/nettests/examples/example_myip.py
+++ b/nettests/examples/example_myip.py
@@ -6,6 +6,10 @@
 from ooni.templates import httpt
 class MyIP(httpt.HTTPTest):
     inputs = ['https://check.torproject.org']
+
+    def test_lookup(self):
+        return self.doRequest(self.input)
+
     def processResponseBody(self, body):
         import re
         regexp = "Your IP address appears to be: <b>(.+?)<\/b>"
diff --git a/nettests/tls-handshake.py b/nettests/tls-handshake.py
deleted file mode 100644
index eba950e..0000000
--- a/nettests/tls-handshake.py
+++ /dev/null
@@ -1,32 +0,0 @@
-#!/usr/bin/env python
-
-import subprocess
-from subprocess import PIPE
-serverport = "129.21.124.215:443"
-# a subset of those from firefox
-ciphers = [
-  "ECDHE-ECDSA-AES256-SHA",
-  "ECDHE-RSA-AES256-SHA",
-  "DHE-RSA-CAMELLIA256-SHA",
-  "DHE-DSS-CAMELLIA256-SHA",
-  "DHE-RSA-AES256-SHA",
-  "DHE-DSS-AES256-SHA",
-  "ECDH-ECDSA-AES256-CBC-SHA",
-  "ECDH-RSA-AES256-CBC-SHA",
-  "CAMELLIA256-SHA",
-  "AES256-SHA",
-  "ECDHE-ECDSA-RC4-SHA",
-  "ECDHE-ECDSA-AES128-SHA",
-  "ECDHE-RSA-RC4-SHA",
-  "ECDHE-RSA-AES128-SHA",
-  "DHE-RSA-CAMELLIA128-SHA",
-  "DHE-DSS-CAMELLIA128-SHA"
-]
-def checkBridgeConnection(host, port)
-  cipher_arg = ":".join(ciphers)
-  cmd  = ["openssl", "s_client", "-connect", "%s:%s" % (host,port)]
-  cmd += ["-cipher", cipher_arg]
-  proc = subprocess.Popen(cmd, stdout=PIPE, stderr=PIPE,stdin=PIPE)
-  out, error = proc.communicate()
-  success = "Cipher is DHE-RSA-AES256-SHA" in out
-  return success
diff --git a/ooni/api/__init__.py b/ooni/api/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/ooni/api/spec.py b/ooni/api/spec.py
new file mode 100644
index 0000000..af238f4
--- /dev/null
+++ b/ooni/api/spec.py
@@ -0,0 +1,92 @@
+import os
+import re
+import json
+import types
+
+from cyclone import web, escape
+
+from ooni import config
+
+class InvalidInputFilename(Exception):
+    pass
+
+class FilenameExists(Exception):
+    pass
+
+class ORequestHandler(web.RequestHandler):
+    serialize_lists = True
+
+    def write(self, chunk):
+        """
+        XXX This is a patch that can be removed once
+        https://github.com/fiorix/cyclone/pull/92 makes it into a release.
+        """
+        if isinstance(chunk, types.ListType):
+            chunk = escape.json_encode(chunk)
+            self.set_header("Content-Type", "application/json")
+        web.RequestHandler.write(self, chunk)
+
+class Status(ORequestHandler):
+    def get(self):
+        result = {'active_tests': oonidApplication.director.activeNetTests}
+        self.write(result)
+
+def list_inputs():
+    input_list = []
+    for filename in os.listdir(config.inputs_directory):
+        input_list.append({'filename': filename})
+    return input_list
+
+class Inputs(ORequestHandler):
+    def get(self):
+        self.write(input_list)
+
+    def post(self):
+        filename = self.get_argument("fullname", None)
+        if not filename or not re.match('(\w.*\.\w.*).*', filename):
+            raise InvalidInputFilename
+
+        if os.path.exists(filename):
+            raise FilenameExists
+
+        input_file = self.request.files.get("input_file")
+        content_type = input_file["content_type"]
+        body = input_file["body"]
+
+        fn = os.path.join(config.inputs_directory, filename)
+        with open(os.path.abspath(fn), "w") as fp:
+            fp.write(body)
+
+class ListTests(ORequestHandler):
+    def get(self):
+        self.write(oonidApplication.director.netTests)
+
+class StartTest(ORequestHandler):
+    def post(self, test_name):
+        """
+        Starts a test with the specified options.
+        """
+        json.decode(self.request.body)
+
+class StopTest(ORequestHandler):
+    def delete(self, test_name):
+        pass
+
+class TestStatus(ORequestHandler):
+    def get(self, test_id):
+        pass
+
+oonidAPI = [
+    (r"/status", Status),
+    (r"/inputs", Inputs),
+    (r"/test", ListTests),
+    (r"/test/(.*)/start", StartTest),
+    (r"/test/(.*)/stop", StopTest),
+    (r"/test/(.*)", TestStatus),
+    (r"/(.*)", web.StaticFileHandler,
+        {"path": os.path.join(config.data_directory, 'ui', 'app'),
+         "default_filename": "index.html"})
+]
+
+oonidApplication = web.Application(oonidAPI, debug=True)
+
diff --git a/ooni/config.py b/ooni/config.py
index 74a1668..5aeb49d 100644
--- a/ooni/config.py
+++ b/ooni/config.py
@@ -6,33 +6,8 @@ from twisted.internet import reactor, threads, defer
 from ooni import otime
 from ooni.utils import Storage
 
-reports = Storage()
-scapyFactory = None
-stateDict = None
-state = Storage()
-
-# XXX refactor this to use a database
-resume_lock = defer.DeferredLock()
-
-basic = None
-cmd_line_options = None
-resume_filename = None
-
-# XXX-Twisted this is used to check if we have started the reactor or not. It
-# is necessary because if the tests are already concluded because we have
-# resumed a test session then it will call reactor.run() even though there is
-# no condition that will ever stop it.
-# There should be a more twisted way of doing this.
-start_reactor = True
-
-tor_state = None
-tor_control = None
-
-config_file = None
-sample_config_file = None
-
-# This is used to store the probes IP address obtained via Tor
-probe_ip = None
+class TestFilenameNotSet(Exception):
+    pass
 
 def get_root_path():
     this_directory = os.path.dirname(__file__)
@@ -46,50 +21,6 @@ def createConfigFile():
     """
     sample_config_file = os.path.join(get_root_path(), 'ooniprobe.conf.sample')
 
-def loadConfigFile():
-    """
-    This is a helper function that makes sure that the configuration attributes
-    are singletons.
-    """
-    config_file = os.path.join(get_root_path(), 'ooniprobe.conf')
-    try:
-        f = open(config_file)
-    except IOError:
-        createConfigFile()
-        raise Exception("Unable to open config file. "\
-                    "Copy ooniprobe.conf.sample to ooniprobe.conf")
-
-    config_file_contents = '\n'.join(f.readlines())
-    configuration = yaml.safe_load(config_file_contents)
-
-    # Process the basic configuration options
-    basic = Storage()
-    for k, v in configuration['basic'].items():
-        basic[k] = v
-
-    # Process the privacy configuration options
-    privacy = Storage()
-    for k, v in configuration['privacy'].items():
-        privacy[k] = v
-
-    # Process the advanced configuration options
-    advanced = Storage()
-    for k, v in configuration['advanced'].items():
-        advanced[k] = v
-
-    # Process the tor configuration options
-    tor = Storage()
-    try:
-        for k, v in configuration['tor'].items():
-            tor[k] = v
-    except AttributeError:
-        pass
-
-    return basic, privacy, advanced, tor
-
-class TestFilenameNotSet(Exception):
-    pass
-
 def generatePcapFilename():
     if cmd_line_options['pcapfile']:
         reports.pcap = cmd_line_options['pcapfile']
@@ -103,9 +34,61 @@ def generatePcapFilename():
         frm_str = "report_%s_"+otime.timestamp()+".%s"
         reports.pcap = frm_str % (test_name, "pcap")
 
-if not basic:
-    # Here we make sure that we instance the config file attributes only once
-    basic, privacy, advanced, tor = loadConfigFile()
+class ConfigurationSetting(Storage):
+    def __init__(self, key):
+        config_file = os.path.join(get_root_path(), 'ooniprobe.conf')
+        try:
+            f = open(config_file)
+        except IOError:
+            createConfigFile()
+            raise Exception("Unable to open config file. "\
+                        "Copy ooniprobe.conf.sample to ooniprobe.conf")
+
+        config_file_contents = '\n'.join(f.readlines())
+        configuration = yaml.safe_load(config_file_contents)
+
+        try:
+            for k, v in configuration[key].items():
+                self[k] = v
+        except AttributeError:
+            pass
+
+basic = ConfigurationSetting('basic')
+advanced = ConfigurationSetting('advanced')
+privacy = ConfigurationSetting('privacy')
+tor = ConfigurationSetting('tor')
+
+data_directory = os.path.join(get_root_path(), 'data')
+nettest_directory = os.path.join(get_root_path(), 'nettests')
+inputs_directory = os.path.join(get_root_path(), 'inputs')
+
+reports = Storage()
+state = Storage()
+scapyFactory = None
+stateDict = None
+
+# XXX refactor this to use a database
+resume_lock = defer.DeferredLock()
+
+cmd_line_options = None
+resume_filename = None
+
+# XXX-Twisted this is used to check if we have started the reactor or not. It
+# is necessary because if the tests are already concluded because we have
+# resumed a test session then it will call reactor.run() even though there is
+# no condition that will ever stop it.
+# There should be a more twisted way of doing this.
+start_reactor = True
+tor_state = None
+tor_control = None
+config_file = None
+sample_config_file = None
+# This is used to store the probes IP address obtained via Tor
+probe_ip = None
+# This is used to keep track of the state of the sniffer
+sniffer_running = None
+
+logging = True
 
 if not resume_filename:
     resume_filename = os.path.join(get_root_path(), 'ooniprobe.resume')
@@ -113,6 +96,3 @@ if not resume_filename:
         with open(resume_filename) as f: pass
     except IOError as e:
         with open(resume_filename, 'w+') as f: pass
-
-# This is used to keep track of the state of the sniffer
-sniffer_running = None
diff --git a/ooni/director.py b/ooni/director.py
index 8365ebd..a9daf84 100644
--- a/ooni/director.py
+++ b/ooni/director.py
@@ -6,7 +6,7 @@ from ooni.managers import ReportEntryManager, MeasurementManager
 from ooni.reporter import Report
 from ooni.utils import log, checkForRoot, NotRootError
 from ooni.utils.net import randomFreePort
-from ooni.nettest import NetTest
+from ooni.nettest import NetTest, getNetTestInformation
 from ooni.errors import UnableToStartTor
 
 from txtorcon import TorConfig
@@ -57,10 +57,12 @@ class Director(object):
 
     """
     _scheduledTests = 0
+    # Only list NetTests belonging to these categories
+    categories = ['blocking', 'manipulation']
 
     def __init__(self):
-        self.netTests = []
         self.activeNetTests = []
+        self.netTests = self.getNetTests()
 
         self.measurementManager = MeasurementManager()
         self.measurementManager.director = self
@@ -80,6 +82,29 @@ class Director(object):
 
         self.torControlProtocol = None
 
+    def getNetTests(self):
+        nettests = {}
+        def is_nettest(filename):
+            return not filename == '__init__.py' \
+                    and filename.endswith('.py')
+
+        for category in self.categories:
+            dirname = os.path.join(config.nettest_directory, category)
+            # print path to all filenames.
+            for filename in os.listdir(dirname):
+                if is_nettest(filename):
+                    net_test_file = os.path.join(dirname, filename)
+                    nettest = getNetTestInformation(net_test_file)
+
+                    if nettest['id'] in nettests:
+                        log.err("Found a two tests with the same name %s, %s" %
+                                (nettest_path, nettests[nettest['id']]['path']))
+                    else:
+                        category = dirname.replace(config.nettest_directory, '')
+                        nettests[nettest['id']] = nettest
+
+        return nettests
+
     def start(self):
         if config.privacy.includepcap:
             log.msg("Starting")
diff --git a/ooni/nettest.py b/ooni/nettest.py
index 1fe19f1..dc72ce8 100644
--- a/ooni/nettest.py
+++ b/ooni/nettest.py
@@ -18,15 +18,162 @@ from StringIO import StringIO
 class NoTestCasesFound(Exception):
     pass
 
+def get_test_methods(item, method_prefix="test_"):
+    """
+    Look for test_ methods in subclasses of NetTestCase
+    """
+    test_cases = []
+    try:
+        assert issubclass(item, NetTestCase)
+        methods = reflect.prefixedMethodNames(item, method_prefix)
+        test_methods = []
+        for method in methods:
+            test_methods.append(method_prefix + method)
+        if test_methods:
+            test_cases.append((item, test_methods))
+    except (TypeError, AssertionError):
+        pass
+    return test_cases
+
+def loadNetTestString(net_test_string):
+    """
+    Load NetTest from a string.
+    WARNING input to this function *MUST* be sanitized and *NEVER* be
+    untrusted.
+    Failure to do so will result in code exec.
+
+    net_test_string:
+
+        a string that contains the net test to be run.
+    """
+    net_test_file_object = StringIO(net_test_string)
+
+    ns = {}
+    test_cases = []
+    exec net_test_file_object.read() in ns
+    for item in ns.itervalues():
+        test_cases.extend(get_test_methods(item))
+
+    if not test_cases:
+        raise NoTestCasesFound
+
+    return test_cases
+
+def loadNetTestFile(net_test_file):
+    """
+    Load NetTest from a file.
+    """
+    test_cases = []
+    module = filenameToModule(net_test_file)
+    for __, item in getmembers(module):
+        test_cases.extend(get_test_methods(item))
+
+    if not test_cases:
+        raise NoTestCasesFound
+
+    return test_cases
+
+def getTestClassFromFile(net_test_file):
+    """
+    Will return the first class that is an instance of NetTestCase.
+
+    XXX this means that if inside of a test there are more than 1 test case
+        then we will only run the first one.
+    """
+    module = filenameToModule(net_test_file)
+    for __, item in getmembers(module):
+        try:
+            assert issubclass(item, NetTestCase)
+            return item
+        except (TypeError, AssertionError):
+            pass
+
+def getOption(opt_parameter, required_options, type='text'):
+    """
+    Arguments:
+        usage_options: a list as should be the optParameters of an UsageOptions class.
+
+        required_options: a list containing the strings of the options that are
+            required.
+
+        type: a string containing the type of the option.
+
+    Returns:
+        a dict containing
+            {
+                'description': the description of the option,
+                'default': the default value of the option,
+                'required': True|False if the option is required or not,
+                'type': the type of the option ('text' or 'file')
+            }
+    """
+    option_name, _, default, description = opt_parameter
+    if option_name in required_options:
+        required = True
+    else:
+        required = False
+
+    return {'description': description,
+        'default': default, 'required': required,
+        'type': type
+    }
+
+def getArguments(test_class):
+    arguments = {}
+    if test_class.inputFile:
+        option_name = test_class.inputFile[0]
+        arguments[option_name] = getOption(test_class.inputFile,
+                test_class.requiredOptions, type='file')
+    try:
+        list(test_class.usageOptions.optParameters)
+    except AttributeError:
+        return arguments
+
+    for opt_parameter in test_class.usageOptions.optParameters:
+        option_name = opt_parameter[0]
+        arguments[option_name] = getOption(opt_parameter,
+                test_class.requiredOptions)
+
+    return arguments
+
+def getNetTestInformation(net_test_file):
+    """
+    Returns a dict containing:
+
+    {
+        'id': the test filename excluding the .py extension,
+        'name': the full name of the test,
+        'description': the description of the test,
+        'version': version number of this test,
+        'arguments': a dict containing as keys the supported arguments and as
+            values the argument description.
+    }
+    """
+    test_class = getTestClassFromFile(net_test_file)
+
+    test_id = os.path.basename(net_test_file).replace('.py', '')
+    information = {'id': test_id,
+        'name': test_class.name,
+        'description': test_class.description,
+        'version': test_class.version,
+        'arguments': getArguments(test_class)
+    }
+    return information
+
 class NetTestLoader(object):
     method_prefix = 'test'
 
     def __init__(self, options, test_file=None, test_string=None):
         self.options = options
+        test_cases = None
+
         if test_file:
-            self.loadNetTestFile(test_file)
+            test_cases = loadNetTestFile(test_file)
         elif test_string:
-            self.loadNetTestString(test_string)
+            test_cases = loadNetTestString(test_string)
+
+        if test_cases:
+            self.setupTestCases(test_cases)
 
     @property
     def testDetails(self):
@@ -115,44 +262,6 @@ class NetTestLoader(object):
                 assert usage_options == test_class.usageOptions
         return usage_options
 
-    def loadNetTestString(self, net_test_string):
-        """
-        Load NetTest from a string.
-        WARNING input to this function *MUST* be sanitized and *NEVER* be
-        untrusted.
-        Failure to do so will result in code exec.
-
-        net_test_string:
-
-            a string that contains the net test to be run.
-        """
-        net_test_file_object = StringIO(net_test_string)
-
-        ns = {}
-        test_cases = []
-        exec net_test_file_object.read() in ns
-        for item in ns.itervalues():
-            test_cases.extend(self._get_test_methods(item))
-
-        if not test_cases:
-            raise NoTestCasesFound
-
-        self.setupTestCases(test_cases)
-
-    def loadNetTestFile(self, net_test_file):
-        """
-        Load NetTest from a file.
-        """
-        test_cases = []
-        module = filenameToModule(net_test_file)
-        for __, item in getmembers(module):
-            test_cases.extend(self._get_test_methods(item))
-
-        if not test_cases:
-            raise NoTestCasesFound
-
-        self.setupTestCases(test_cases)
-
     def setupTestCases(self, test_cases):
         """
         Creates all the necessary test_cases (a list of tuples containing the
@@ -205,22 +314,6 @@ class NetTestLoader(object):
                 inputs = [None]
             klass.inputs = inputs
 
-    def _get_test_methods(self, item):
-        """
-        Look for test_ methods in subclasses of NetTestCase
-        """
-        test_cases = []
-        try:
-            assert issubclass(item, NetTestCase)
-            methods = reflect.prefixedMethodNames(item, self.method_prefix)
-            test_methods = []
-            for method in methods:
-                test_methods.append(self.method_prefix + method)
-            if test_methods:
-                test_cases.append((item, test_methods))
-        except (TypeError, AssertionError):
-            pass
-        return test_cases
 
 class NetTestState(object):
     def __init__(self, allTasksDone):
@@ -409,9 +502,10 @@ class NetTestCase(object):
     Quirks:
     Every class that is prefixed with test *must* return a twisted.internet.defer.Deferred.
     """
-    name = "I Did Not Change The Name"
+    name = "This test is nameless"
     author = "Jane Doe <foo at example.com>"
     version = "0.0.0"
+    description = "Sorry, this test has no description :("
 
     inputs = [None]
     inputFile = None
diff --git a/ooni/oonicli.py b/ooni/oonicli.py
index 06aa20c..a99386d 100644
--- a/ooni/oonicli.py
+++ b/ooni/oonicli.py
@@ -10,12 +10,11 @@ from twisted.internet import reactor
 from twisted.python import usage
 from twisted.python.util import spewer
 
-from ooni.errors import InvalidOONIBCollectorAddress
-
+from ooni import errors
 from ooni import config
+
 from ooni.director import Director
 from ooni.reporter import YAMLReporter, OONIBReporter
-
 from ooni.nettest import NetTestLoader, MissingRequiredOption
 
 from ooni.utils import log
@@ -147,7 +146,7 @@ def runWithDirector():
                     oonib_reporter = OONIBReporter(test_details,
                             global_options['collector'])
                     reporters.append(oonib_reporter)
-                except InvalidOONIBCollectorAddress:
+                except errors.InvalidOONIBCollectorAddress:
                     log.err("Invalid format for oonib collector address.")
                     log.msg("Should be in the format http://<collector_address>:<port>")
                     log.msg("for example: ooniprobe -c httpo://nkvphnp3p6agi5qq.onion")
diff --git a/ooni/oonid.py b/ooni/oonid.py
new file mode 100644
index 0000000..dde768e
--- /dev/null
+++ b/ooni/oonid.py
@@ -0,0 +1,20 @@
+import os
+import random
+
+from twisted.application import service, internet
+from twisted.web import static, server
+
+from ooni import config
+from ooni.api.spec import oonidApplication
+from ooni.director import Director
+from ooni.reporter import YAMLReporter, OONIBReporter
+
+def getOonid():
+    director = Director()
+    director.start()
+    oonidApplication.director = director
+    return internet.TCPServer(int(config.advanced.oonid_api_port), oonidApplication)
+
+application = service.Application("ooniprobe")
+service = getOonid()
+service.setServiceParent(application)
diff --git a/ooni/reporter.py b/ooni/reporter.py
index 84dad2f..a7bd933 100644
--- a/ooni/reporter.py
+++ b/ooni/reporter.py
@@ -26,7 +26,7 @@ except ImportError:
     log.err("Scapy is not installed.")
 
 
-from ooni.errors import InvalidOONIBCollectorAddress
+from ooni.errors import InvalidOONIBCollectorAddress, NoMoreReporters
 from ooni.errors import ReportNotCreated, ReportAlreadyClosed
 
 from ooni import otime
diff --git a/ooni/runner.py b/ooni/runner.py
deleted file mode 100644
index 080db18..0000000
--- a/ooni/runner.py
+++ /dev/null
@@ -1,241 +0,0 @@
-import os
-import time
-import random
-
-import yaml
-
-from twisted.internet import defer
-from twisted.internet import reactor
-
-from txtorcon import TorConfig
-from txtorcon import TorState, launch_tor
-
-from ooni import config
-from ooni.reporter import OONIBReporter, YAMLReporter, OONIBReportError
-from ooni.inputunit import InputUnitFactory
-from ooni.nettest import NetTestCase, NoPostProcessor
-from ooni.utils import log, checkForRoot, pushFilenameStack
-from ooni.utils import NotRootError, Storage
-from ooni.utils.net import randomFreePort
-
-class InvalidResumeFile(Exception):
-    pass
-
-class noResumeSession(Exception):
-    pass
-
-def loadResumeFile():
-    """
-    Sets the singleton stateDict object to the content of the resume file.
-    If the file is empty then it will create an empty one.
-
-    Raises:
-
-        :class:ooni.runner.InvalidResumeFile if the resume file is not valid
-
-    """
-    if not config.stateDict:
-        try:
-            with open(config.resume_filename) as f:
-                config.stateDict = yaml.safe_load(f)
-        except:
-            log.err("Error loading YAML file")
-            raise InvalidResumeFile
-
-        if not config.stateDict:
-            with open(config.resume_filename, 'w+') as f:
-                yaml.safe_dump(dict(), f)
-            config.stateDict = dict()
-
-        elif isinstance(config.stateDict, dict):
-            return
-        else:
-            log.err("The resume file is of the wrong format")
-            raise InvalidResumeFile
-
-def resumeTest(test_filename, input_unit_factory):
-    """
-    Returns the an input_unit_factory that is at the index of the previous run of the test 
-    for the specified test_filename.
-
-    Args:
-
-        test_filename (str): the filename of the test that is being run
-            including the .py extension.
-
-        input_unit_factory (:class:ooni.inputunit.InputUnitFactory): with the
-            same input of the past run.
-
-    Returns:
-
-        :class:ooni.inputunit.InputUnitFactory that is at the index of the
-            previous test run.
-
-    """
-    try:
-        idx = config.stateDict[test_filename]
-        for x in range(idx):
-            try:
-                input_unit_factory.next()
-            except StopIteration:
-                log.msg("Previous run was complete")
-                return input_unit_factory
-
-        return input_unit_factory
-
-    except KeyError:
-        log.debug("No resume key found for selected test name. It is therefore 0")
-        config.stateDict[test_filename] = 0
-        return input_unit_factory
-
- at defer.inlineCallbacks
-def updateResumeFile(test_filename):
-    """
-    update the resume file with the current stateDict state.
-    """
-    log.debug("Acquiring lock for %s" % test_filename)
-    yield config.resume_lock.acquire()
-
-    current_resume_state = yaml.safe_load(open(config.resume_filename))
-    current_resume_state = config.stateDict
-    yaml.safe_dump(current_resume_state, open(config.resume_filename, 'w+'))
-
-    log.debug("Releasing lock for %s" % test_filename)
-    config.resume_lock.release()
-    defer.returnValue(config.stateDict[test_filename])
-
- at defer.inlineCallbacks
-def increaseInputUnitIdx(test_filename):
-    """
-    Args:
-
-        test_filename (str): the filename of the test that is being run
-            including the .py extension.
-
-        input_unit_idx (int): the current input unit index for the test.
-
-    """
-    config.stateDict[test_filename] += 1
-    yield updateResumeFile(test_filename)
-
-def updateProgressMeters(test_filename, input_unit_factory, 
-        test_case_number):
-    """
-    Update the progress meters for keeping track of test state.
-    """
-    if not config.state.test_filename:
-        config.state[test_filename] = Storage()
-
-    config.state[test_filename].per_item_average = 2.0
-
-    input_unit_idx = float(config.stateDict[test_filename])
-    input_unit_items = len(input_unit_factory)
-    test_case_number = float(test_case_number)
-    total_iterations = input_unit_items * test_case_number
-    current_iteration = input_unit_idx * test_case_number
-
-    log.debug("input_unit_items: %s" % input_unit_items)
-    log.debug("test_case_number: %s" % test_case_number)
-
-    log.debug("Test case number: %s" % test_case_number)
-    log.debug("Total iterations: %s" % total_iterations)
-    log.debug("Current iteration: %s" % current_iteration)
-
-    def progress():
-        return (current_iteration / total_iterations) * 100.0
-
-    config.state[test_filename].progress = progress
-
-    def eta():
-        return (total_iterations - current_iteration) \
-                * config.state[test_filename].per_item_average
-    config.state[test_filename].eta = eta
-
-    config.state[test_filename].input_unit_idx = input_unit_idx
-    config.state[test_filename].input_unit_items = input_unit_items
-
-
- at defer.inlineCallbacks
-def runTestCases(test_cases, options, cmd_line_options):
-    log.debug("Running %s" % test_cases)
-    log.debug("Options %s" % options)
-    log.debug("cmd_line_options %s" % dict(cmd_line_options))
-
-    test_inputs = options['inputs']
-
-    # Set a default reporter
-    if not cmd_line_options['collector'] and not \
-        cmd_line_options['no-default-reporter']:
-        with open('collector') as f:
-            reporter_url = random.choice(f.readlines())
-            reporter_url = reporter_url.split('#')[0].strip()
-            cmd_line_options['collector'] = reporter_url
-
-    oonib_reporter = OONIBReporter(cmd_line_options)
-    yaml_reporter = YAMLReporter(cmd_line_options)
-
-    if cmd_line_options['collector']:
-        log.msg("Using remote collector, please be patient while we create the report.")
-        try:
-            yield oonib_reporter.createReport(options)
-        except OONIBReportError:
-            log.err("Error in creating new report")
-            log.msg("We will only create reports to a file")
-            oonib_reporter = None
-    else:
-        oonib_reporter = None
-
-    yield yaml_reporter.createReport(options)
-    log.msg("Reporting to file %s" % yaml_reporter._stream.name)
-
-    try:
-        input_unit_factory = InputUnitFactory(test_inputs)
-        input_unit_factory.inputUnitSize = int(cmd_line_options['parallelism'])
-    except Exception, e:
-        log.exception(e)
-
-    try:
-        loadResumeFile()
-    except InvalidResumeFile:
-        log.err("Error in loading resume file %s" % config.resume_filename)
-        log.err("Try deleting the resume file")
-        raise InvalidResumeFile
-
-    test_filename = os.path.basename(cmd_line_options['test'])
-
-    if cmd_line_options['resume']:
-        log.debug("Resuming %s" % test_filename)
-        resumeTest(test_filename, input_unit_factory)
-    else:
-        log.debug("Not going to resume %s" % test_filename)
-        config.stateDict[test_filename] = 0
-
-    updateProgressMeters(test_filename, input_unit_factory, len(test_cases))
-
-    try:
-        for input_unit in input_unit_factory:
-            log.debug("Running %s with input unit %s" % (test_filename, input_unit))
-
-            yield runTestCasesWithInputUnit(test_cases, input_unit,
-                    yaml_reporter, oonib_reporter)
-
-            yield increaseInputUnitIdx(test_filename)
-
-            updateProgressMeters(test_filename, input_unit_factory, len(test_cases))
-
-    except Exception:
-        log.exception("Problem in running test")
-    yaml_reporter.finish()
-
-def loadTest(cmd_line_options):
-    """
-    Takes care of parsing test command line arguments and loading their
-    options.
-    """
-    # XXX here there is too much strong coupling with cmd_line_options
-    # Ideally this would get all wrapped in a nice little class that get's
-    # instanced with it's cmd_line_options as an instance attribute
-    classes = findTestClassesFromFile(cmd_line_options)
-    test_cases, options = loadTestsAndOptions(classes, cmd_line_options)
-
-    return test_cases, options, cmd_line_options
diff --git a/ooni/tests/__init__.py b/ooni/tests/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/ooni/tests/mocks.py b/ooni/tests/mocks.py
new file mode 100644
index 0000000..99e5200
--- /dev/null
+++ b/ooni/tests/mocks.py
@@ -0,0 +1,172 @@
+from twisted.python import failure
+from twisted.internet import defer
+
+from ooni import config
+from ooni.tasks import BaseTask, TaskWithTimeout
+from ooni.nettest import NetTest
+from ooni.managers import TaskManager
+
+config.logging = False
+
+class MockMeasurementFailOnce(BaseTask):
+    def run(self):
+        f = open('dummyTaskFailOnce.txt', 'w')
+        f.write('fail')
+        f.close()
+        if self.failure >= 1:
+            return defer.succeed(self)
+        else:
+            return defer.fail(failure.Failure)
+
+class MockMeasurementManager(TaskManager):
+    def __init__(self):
+        self.successes = []
+        TaskManager.__init__(self)
+
+    def failed(self, failure, task):
+        pass
+
+    def succeeded(self, result, task):
+        self.successes.append((result, task))
+
+class MockReporter(object):
+    def __init__(self):
+        self.created = defer.Deferred()
+
+    def writeReportEntry(self, entry):
+        pass
+
+    def createReport(self):
+        self.created.callback(self)
+
+    def finish(self):
+        pass
+
+class MockFailure(Exception):
+    pass
+
+## from test_managers
+mockFailure = failure.Failure(MockFailure('mock'))
+
+class MockSuccessTask(BaseTask):
+    def run(self):
+        return defer.succeed(42)
+
+class MockFailTask(BaseTask):
+    def run(self):
+        return defer.fail(mockFailure)
+
+class MockFailOnceTask(BaseTask):
+    def run(self):
+        if self.failures >= 1:
+            return defer.succeed(42)
+        else:
+            return defer.fail(mockFailure)
+
+class MockSuccessTaskWithTimeout(TaskWithTimeout):
+    def run(self):
+        return defer.succeed(42)
+
+class MockFailTaskThatTimesOut(TaskWithTimeout):
+    def run(self):
+        return defer.Deferred()
+
+class MockTimeoutOnceTask(TaskWithTimeout):
+    def run(self):
+        if self.failures >= 1:
+            return defer.succeed(42)
+        else:
+            return defer.Deferred()
+
+class MockFailTaskWithTimeout(TaskWithTimeout):
+    def run(self):
+        return defer.fail(mockFailure)
+
+
+class MockNetTest(object):
+    def __init__(self):
+        self.successes = []
+
+    def succeeded(self, measurement):
+        self.successes.append(measurement)
+
+class MockMeasurement(TaskWithTimeout):
+    def __init__(self, net_test):
+        TaskWithTimeout.__init__(self)
+        self.netTest = net_test
+
+    def succeeded(self, result):
+        return self.netTest.succeeded(42)
+
+class MockSuccessMeasurement(MockMeasurement):
+    def run(self):
+        return defer.succeed(42)
+
+class MockFailMeasurement(MockMeasurement):
+    def run(self):
+        return defer.fail(mockFailure)
+
+class MockFailOnceMeasurement(MockMeasurement):
+    def run(self):
+        if self.failures >= 1:
+            return defer.succeed(42)
+        else:
+            return defer.fail(mockFailure)
+
+class MockDirector(object):
+    def __init__(self):
+        self.successes = []
+
+    def measurementFailed(self, failure, measurement):
+        pass
+
+    def measurementSucceeded(self, measurement):
+        self.successes.append(measurement)
+
+## from test_reporter.py
+class MockOReporter(object):
+    def __init__(self):
+        self.created = defer.Deferred()
+
+    def writeReportEntry(self, entry):
+        return defer.succeed(42)
+
+    def finish(self):
+        pass
+
+    def createReport(self):
+        from ooni.utils import log
+        log.debug("Creating report with %s" % self)
+        self.created.callback(self)
+
+class MockOReporterThatFailsWrite(MockOReporter):
+    def writeReportEntry(self, entry):
+        raise MockFailure
+
+class MockOReporterThatFailsOpen(MockOReporter):
+    def createReport(self):
+        self.created.errback(failure.Failure(MockFailure()))
+
+class MockOReporterThatFailsWriteOnce(MockOReporter):
+    def __init__(self):
+        self.failure = 0
+        MockOReporter.__init__(self)
+
+    def writeReportEntry(self, entry):
+        if self.failure >= 1:
+            return defer.succeed(42)
+        else:
+            self.failure += 1
+            raise MockFailure 
+
+class MockTaskManager(TaskManager):
+    def __init__(self):
+        self.successes = []
+        TaskManager.__init__(self)
+
+    def failed(self, failure, task):
+        pass
+
+    def succeeded(self, result, task):
+        self.successes.append((result, task))
+
diff --git a/ooni/tests/test-class-design.py b/ooni/tests/test-class-design.py
new file mode 100644
index 0000000..bb80cd3
--- /dev/null
+++ b/ooni/tests/test-class-design.py
@@ -0,0 +1,101 @@
+#!/usr/bin/env python
+#
+# testing classes to test multiple inheritance.
+# these are not meant to be run by trial, though they could be made to be so.
+# i didn't know where to put them. --isis
+
+import abc
+from pprint import pprint
+from inspect import classify_class_attrs
+
+class PluginBase(object):
+    __metaclass__ = abc.ABCMeta
+
+    @abc.abstractproperty
+    def name(self):
+        return 'you should not see this'
+
+    @name.setter
+    def name(self, value):
+        return 'you should not set this'
+
+    @name.deleter
+    def name(self):
+        return 'you should not del this'
+
+    @abc.abstractmethod
+    def inputParser(self, line):
+        """Do something to parse something."""
+        return
+
+class Foo(object):
+    woo = "this class has some shit in it"
+    def bar(self):
+        print "i'm a Foo.bar()!"
+        print woo
+
+class KwargTest(Foo):
+    _name = "isis"
+
+    #def __new__(cls, *a, **kw):
+    #    return super(KwargTest, cls).__new__(cls, *a, **kw)
+
+    @property
+    def name(self):
+        return self._name
+
+    @name.setter
+    def name(self, value):
+        self._name = value
+
+    def __init__(self, *a, **kw):
+        super(KwargTest, self).__init__()
+
+        ## this causes the instantion args to override the class attrs
+        for key, value in kw.items():
+            setattr(self.__class__, key, value)
+
+        print "%s.__init__(): self.__dict__ = %s" \
+            % (type(self), pprint(type(self).__dict__))
+
+        for attr in classify_class_attrs(self):
+            print attr
+
+    @classmethod
+    def sayname(cls):
+        print cls.name
+
+class KwargTestChild(KwargTest):
+    name = "arturo"
+    def __init__(self):
+        super(KwargTestChild, self).__init__()
+        print self.name
+
+class KwargTestChildOther(KwargTest):
+    def __init__(self, name="robot", does="lasers"):
+        super(KwargTestChildOther, self).__init__()
+        print self.name
+
+
+if __name__ == "__main__":
+    print "class KwargTest attr name: %s" % KwargTest.name
+    kwargtest = KwargTest()
+    print "KwargTest instantiated wo args"
+    print "kwargtest.name: %s" % kwargtest.name
+    print "kwargtest.sayname(): %s" % kwargtest.sayname()
+    kwargtest2 = KwargTest(name="lovecruft", does="hacking")
+    print "KwargTest instantiated with name args"
+    print "kwargtest.name: %s" % kwargtest2.name
+    print "kwargtest.sayname(): %s" % kwargtest2.sayname()
+
+    print "class KwargTestChild attr name: %s" % KwargTestChild.name
+    kwargtestchild = KwargTestChild()
+    print "KwargTestChild instantiated wo args"
+    print "kwargtestchild.name: %s" % kwargtestchild.name
+    print "kwargtestchild.sayname(): %s" % kwargtestchild.sayname()
+
+    print "class KwargTestChildOther attr name: %s" % KwargTestChildOther.name
+    kwargtestchildother = KwargTestChildOther()
+    print "KwargTestChildOther instantiated wo args"
+    print "kwargtestchildother.name: %s" % kwargtestchildother.name
+    print "kwargtestchildother.sayname(): %s" % kwargtestchildother.sayname()
diff --git a/ooni/tests/test_director.py b/ooni/tests/test_director.py
new file mode 100644
index 0000000..7920fcb
--- /dev/null
+++ b/ooni/tests/test_director.py
@@ -0,0 +1,58 @@
+from twisted.internet import defer, base
+from twisted.trial import unittest
+
+from ooni.director import Director
+from ooni.nettest import NetTestLoader
+from ooni.tests.mocks import MockReporter
+base.DelayedCall.debug = True
+
+net_test_string = """
+from twisted.python import usage
+from ooni.nettest import NetTestCase
+
+class UsageOptions(usage.Options):
+    optParameters = [['spam', 's', None, 'ham']]
+
+class DummyTestCase(NetTestCase):
+    inputFile = ['file', 'f', None, 'The input File']
+
+    usageOptions = UsageOptions
+
+    def test_a(self):
+        self.report['bar'] = 'bar'
+
+    def test_b(self):
+        self.report['foo'] = 'foo'
+"""
+
+
+dummyArgs = ('--spam', 1, '--file', 'dummyInputFile.txt')
+
+class TestDirector(unittest.TestCase):
+    timeout = 1
+    def setUp(self):
+        with open('dummyInputFile.txt', 'w') as f:
+            for i in range(10):
+                f.write("%s\n" % i)
+
+        self.reporters = [MockReporter()]
+        self.director = Director()
+
+    def tearDown(self):
+        pass
+
+    def test_start_net_test(self):
+        ntl = NetTestLoader(dummyArgs, test_string=net_test_string)
+
+        ntl.checkOptions()
+        d = self.director.startNetTest('', ntl, self.reporters)
+
+        @d.addCallback
+        def done(result):
+            self.assertEqual(self.director.successfulMeasurements, 20)
+
+        return d
+
+    def test_stop_net_test(self):
+        pass
+
diff --git a/ooni/tests/test_dns.py b/ooni/tests/test_dns.py
new file mode 100644
index 0000000..e9bb524
--- /dev/null
+++ b/ooni/tests/test_dns.py
@@ -0,0 +1,24 @@
+#
+# This unittest is to verify that our usage of the twisted DNS resolver does
+# not break with new versions of twisted.
+
+import pdb
+from twisted.trial import unittest
+
+from twisted.internet import reactor
+
+from twisted.names import dns
+from twisted.names.client import Resolver
+
+class DNSTest(unittest.TestCase):
+    def test_a_lookup_ooni_query(self):
+        def done_query(message, *arg):
+            answer = message.answers[0]
+            self.assertEqual(answer.type, 1)
+
+        dns_query = [dns.Query('ooni.nu', type=dns.A)]
+        resolver = Resolver(servers=[('8.8.8.8', 53)])
+        d = resolver.queryUDP(dns_query)
+        d.addCallback(done_query)
+        return d
+
diff --git a/ooni/tests/test_managers.py b/ooni/tests/test_managers.py
new file mode 100644
index 0000000..e2af7b3
--- /dev/null
+++ b/ooni/tests/test_managers.py
@@ -0,0 +1,215 @@
+from twisted.trial import unittest
+from twisted.python import failure
+from twisted.internet import defer, task
+
+from ooni.tasks import BaseTask, TaskWithTimeout, TaskTimedOut
+from ooni.managers import TaskManager, MeasurementManager
+
+from ooni.tests.mocks import MockSuccessTask, MockFailTask, MockFailOnceTask, MockFailure
+from ooni.tests.mocks import MockSuccessTaskWithTimeout, MockFailTaskThatTimesOut
+from ooni.tests.mocks import MockTimeoutOnceTask, MockFailTaskWithTimeout
+from ooni.tests.mocks import MockTaskManager, mockFailure, MockDirector
+from ooni.tests.mocks import MockNetTest, MockMeasurement, MockSuccessMeasurement
+from ooni.tests.mocks import MockFailMeasurement, MockFailOnceMeasurement
+
+class TestTaskManager(unittest.TestCase):
+    timeout = 1
+    def setUp(self):
+        self.measurementManager = MockTaskManager()
+        self.measurementManager.concurrency = 20
+        self.measurementManager.retries = 2
+
+        self.measurementManager.start()
+
+        self.clock = task.Clock()
+
+    def schedule_successful_tasks(self, task_type, number=1):
+        all_done = []
+        for x in range(number):
+            mock_task = task_type()
+            all_done.append(mock_task.done)
+            self.measurementManager.schedule(mock_task)
+
+        d = defer.DeferredList(all_done)
+        @d.addCallback
+        def done(res):
+            for task_result, task_instance in self.measurementManager.successes:
+                self.assertEqual(task_result, 42)
+                self.assertIsInstance(task_instance, task_type)
+
+        return d
+
+    def schedule_failing_tasks(self, task_type, number=1):
+        all_done = []
+        for x in range(number):
+            mock_task = task_type()
+            all_done.append(mock_task.done)
+            self.measurementManager.schedule(mock_task)
+
+        d = defer.DeferredList(all_done)
+        @d.addCallback
+        def done(res):
+            # 10*2 because 2 is the number of retries
+            self.assertEqual(len(self.measurementManager.failures), number*3)
+            for task_result, task_instance in self.measurementManager.failures:
+                self.assertEqual(task_result, mockFailure)
+                self.assertIsInstance(task_instance, task_type)
+
+        return d
+
+    def test_schedule_failing_with_mock_failure_task(self):
+        mock_task = MockFailTask()
+        self.measurementManager.schedule(mock_task)
+        self.assertFailure(mock_task.done, MockFailure)
+        return mock_task.done
+
+    def test_schedule_successful_one_task(self):
+        return self.schedule_successful_tasks(MockSuccessTask)
+
+    def test_schedule_successful_one_task_with_timeout(self):
+        return self.schedule_successful_tasks(MockSuccessTaskWithTimeout)
+
+    def test_schedule_failing_tasks_that_timesout(self):
+        self.measurementManager.retries = 0
+
+        task_type = MockFailTaskThatTimesOut
+        task_timeout = 5
+
+        mock_task = task_type()
+        mock_task.timeout = task_timeout
+        mock_task.clock = self.clock
+
+        self.measurementManager.schedule(mock_task)
+
+        self.clock.advance(task_timeout)
+
+        @mock_task.done.addBoth
+        def done(res):
+            self.assertEqual(len(self.measurementManager.failures), 1)
+            for task_result, task_instance in self.measurementManager.failures:
+                self.assertIsInstance(task_instance, task_type)
+
+        return mock_task.done
+
+    def test_schedule_time_out_once(self):
+        task_type = MockTimeoutOnceTask
+        task_timeout = 5
+
+        mock_task = task_type()
+        mock_task.timeout = task_timeout
+        mock_task.clock = self.clock
+
+        self.measurementManager.schedule(mock_task)
+
+        self.clock.advance(task_timeout)
+
+        @mock_task.done.addBoth
+        def done(res):
+            self.assertEqual(len(self.measurementManager.failures), 1)
+            for task_result, task_instance in self.measurementManager.failures:
+                self.assertIsInstance(task_instance, task_type)
+
+            for task_result, task_instance in self.measurementManager.successes:
+                self.assertEqual(task_result, 42)
+                self.assertIsInstance(task_instance, task_type)
+
+        return mock_task.done
+
+
+    def test_schedule_failing_one_task(self):
+        return self.schedule_failing_tasks(MockFailTask)
+
+    def test_schedule_failing_one_task_with_timeout(self):
+        return self.schedule_failing_tasks(MockFailTaskWithTimeout)
+
+    def test_schedule_successful_ten_tasks(self):
+        return self.schedule_successful_tasks(MockSuccessTask, number=10)
+
+    def test_schedule_failing_ten_tasks(self):
+        return self.schedule_failing_tasks(MockFailTask, number=10)
+
+    def test_schedule_successful_27_tasks(self):
+        return self.schedule_successful_tasks(MockSuccessTask, number=27)
+
+    def test_schedule_failing_27_tasks(self):
+        return self.schedule_failing_tasks(MockFailTask, number=27)
+
+    def test_task_retry_and_succeed(self):
+        mock_task = MockFailOnceTask()
+        self.measurementManager.schedule(mock_task)
+
+        @mock_task.done.addCallback
+        def done(res):
+            self.assertEqual(len(self.measurementManager.failures), 1)
+
+            self.assertEqual(self.measurementManager.failures,
+                    [(mockFailure, mock_task)])
+            self.assertEqual(self.measurementManager.successes,
+                    [(42, mock_task)])
+
+        return mock_task.done
+
+    def dd_test_task_retry_and_succeed_56_tasks(self):
+        """
+        XXX this test fails in a non-deterministic manner.
+        """
+        all_done = []
+        number = 56
+        for x in range(number):
+            mock_task = MockFailOnceTask()
+            all_done.append(mock_task.done)
+            self.measurementManager.schedule(mock_task)
+
+        d = defer.DeferredList(all_done)
+
+        @d.addCallback
+        def done(res):
+            self.assertEqual(len(self.measurementManager.failures), number)
+
+            for task_result, task_instance in self.measurementManager.successes:
+                self.assertEqual(task_result, 42)
+                self.assertIsInstance(task_instance, MockFailOnceTask)
+
+        return d
+
+class TestMeasurementManager(unittest.TestCase):
+    def setUp(self):
+        mock_director = MockDirector()
+
+        self.measurementManager = MeasurementManager()
+        self.measurementManager.director = mock_director
+
+        self.measurementManager.concurrency = 10
+        self.measurementManager.retries = 2
+
+        self.measurementManager.start()
+
+        self.mockNetTest = MockNetTest()
+
+    def test_schedule_and_net_test_notified(self, number=1):
+        # XXX we should probably be inheriting from the base test class
+        mock_task = MockSuccessMeasurement(self.mockNetTest)
+        self.measurementManager.schedule(mock_task)
+
+        @mock_task.done.addCallback
+        def done(res):
+            self.assertEqual(self.mockNetTest.successes,
+                    [42])
+
+            self.assertEqual(len(self.mockNetTest.successes), 1)
+        return mock_task.done
+
+    def test_schedule_failing_one_measurement(self):
+        mock_task = MockFailMeasurement(self.mockNetTest)
+        self.measurementManager.schedule(mock_task)
+
+        @mock_task.done.addErrback
+        def done(failure):
+            self.assertEqual(len(self.measurementManager.failures), 3)
+
+            self.assertEqual(failure, mockFailure)
+            self.assertEqual(len(self.mockNetTest.successes), 0)
+
+        return mock_task.done
+
+
diff --git a/ooni/tests/test_mutate.py b/ooni/tests/test_mutate.py
new file mode 100644
index 0000000..7e30586
--- /dev/null
+++ b/ooni/tests/test_mutate.py
@@ -0,0 +1,15 @@
+import unittest
+from ooni.kit import daphn3
+
+class TestDaphn3(unittest.TestCase):
+    def test_mutate_string(self):
+        original_string = '\x00\x00\x00'
+        mutated = daphn3.daphn3MutateString(original_string, 1)
+        self.assertEqual(mutated, '\x00\x01\x00')
+    def test_mutate_daphn3(self):
+        original_dict = [{'client': '\x00\x00\x00'},
+                {'server': '\x00\x00\x00'}]
+        mutated_dict = daphn3.daphn3Mutate(original_dict,  1, 1)
+        self.assertEqual(mutated_dict, [{'client': '\x00\x00\x00'},
+            {'server': '\x00\x01\x00'}])
+
diff --git a/ooni/tests/test_nettest.py b/ooni/tests/test_nettest.py
new file mode 100644
index 0000000..4d72a84
--- /dev/null
+++ b/ooni/tests/test_nettest.py
@@ -0,0 +1,268 @@
+import os
+from StringIO import StringIO
+from tempfile import TemporaryFile, mkstemp
+
+from twisted.trial import unittest
+from twisted.internet import defer, reactor
+from twisted.python.usage import UsageError
+
+from ooni.nettest import NetTest, InvalidOption, MissingRequiredOption
+from ooni.nettest import NetTestLoader, FailureToLoadNetTest, loadNetTestString, loadNetTestFile
+from ooni.tasks import BaseTask
+from ooni.utils import NotRootError
+
+from ooni.director import Director
+
+from ooni.managers import TaskManager
+
+from ooni.tests.mocks import MockMeasurement, MockMeasurementFailOnce
+from ooni.tests.mocks import MockNetTest, MockDirector, MockReporter
+from ooni.tests.mocks import MockMeasurementManager
+defer.setDebugging(True)
+
+net_test_string = """
+from twisted.python import usage
+from ooni.nettest import NetTestCase
+
+class UsageOptions(usage.Options):
+    optParameters = [['spam', 's', None, 'ham']]
+
+class DummyTestCase(NetTestCase):
+
+    usageOptions = UsageOptions
+
+    def test_a(self):
+        self.report['bar'] = 'bar'
+
+    def test_b(self):
+        self.report['foo'] = 'foo'
+"""
+
+net_test_root_required = net_test_string+"""
+    requiresRoot = True
+"""
+
+net_test_string_with_file = """
+from twisted.python import usage
+from ooni.nettest import NetTestCase
+
+class UsageOptions(usage.Options):
+    optParameters = [['spam', 's', None, 'ham']]
+
+class DummyTestCase(NetTestCase):
+    inputFile = ['file', 'f', None, 'The input File']
+
+    usageOptions = UsageOptions
+
+    def test_a(self):
+        self.report['bar'] = 'bar'
+
+    def test_b(self):
+        self.report['foo'] = 'foo'
+"""
+
+net_test_string_with_required_option = """
+from twisted.python import usage
+from ooni.nettest import NetTestCase
+
+class UsageOptions(usage.Options):
+    optParameters = [['spam', 's', None, 'ham'],
+                     ['foo', 'o', None, 'moo'],
+                     ['bar', 'o', None, 'baz'],
+    ]
+
+class DummyTestCase(NetTestCase):
+    inputFile = ['file', 'f', None, 'The input File']
+
+    usageOptions = UsageOptions
+
+    def test_a(self):
+        self.report['bar'] = 'bar'
+
+    def test_b(self):
+        self.report['foo'] = 'foo'
+
+    requiredOptions = ['foo', 'bar']
+"""
+
+dummyInputs = range(1)
+dummyArgs = ('--spam', 'notham')
+dummyOptions = {'spam':'notham'}
+dummyInvalidArgs = ('--cram', 'jam')
+dummyInvalidOptions= {'cram':'jam'}
+dummyArgsWithRequiredOptions = ('--foo', 'moo', '--bar', 'baz')
+dummyRequiredOptions = {'foo':'moo', 'bar':'baz'}
+dummyArgsWithFile = ('--spam', 'notham', '--file', 'dummyInputFile.txt')
+
+class TestNetTest(unittest.TestCase):
+    timeout = 1
+    def setUp(self):
+        with open('dummyInputFile.txt', 'w') as f:
+            for i in range(10):
+                f.write("%s\n" % i)
+
+    def assertCallable(self, thing):
+        self.assertIn('__call__', dir(thing))
+
+    def verifyMethods(self, testCases):
+        uniq_test_methods = set()
+        for test_class, test_methods in testCases:
+            instance = test_class()
+            for test_method in test_methods:
+                c = getattr(instance, test_method)
+                self.assertCallable(c)
+                uniq_test_methods.add(test_method)
+        self.assertEqual(set(['test_a', 'test_b']), uniq_test_methods)
+
+    def test_load_net_test_from_file(self):
+        """
+        Given a file verify that the net test cases are properly
+        generated.
+        """
+        __, net_test_file = mkstemp()
+        with open(net_test_file, 'w') as f:
+            f.write(net_test_string)
+        f.close()
+
+        ntl = NetTestLoader(dummyArgs)
+        ntl.setupTestCases(loadNetTestFile(net_test_file))
+
+        self.verifyMethods(ntl.testCases)
+        os.unlink(net_test_file)
+
+    def test_load_net_test_from_str(self):
+        """
+        Given a file like object verify that the net test cases are properly
+        generated.
+        """
+        ntl = NetTestLoader(dummyArgs)
+        ntl.setupTestCases(loadNetTestString(net_test_string))
+
+        self.verifyMethods(ntl.testCases)
+
+    def test_load_net_test_from_StringIO(self):
+        """
+        Given a file like object verify that the net test cases are properly
+        generated.
+        """
+        ntl = NetTestLoader(dummyArgs)
+        ntl.setupTestCases(loadNetTestString(net_test_string))
+
+        self.verifyMethods(ntl.testCases)
+
+    def test_load_with_option(self):
+        ntl = NetTestLoader(dummyArgs)
+        ntl.setupTestCases(loadNetTestString(net_test_string))
+
+        self.assertIsInstance(ntl, NetTestLoader)
+        for test_klass, test_meth in ntl.testCases:
+            for option in dummyOptions.keys():
+                self.assertIn(option, test_klass.usageOptions())
+
+    def test_load_with_invalid_option(self):
+        try:
+            ntl = NetTestLoader(dummyInvalidArgs)
+            ntl.setupTestCases(loadNetTestString(net_test_string))
+
+            ntl.checkOptions()
+            raise Exception
+        except UsageError:
+            pass
+
+    def test_load_with_required_option(self):
+        ntl = NetTestLoader(dummyArgsWithRequiredOptions)
+        ntl.setupTestCases(loadNetTestString(net_test_string_with_required_option))
+
+        self.assertIsInstance(ntl, NetTestLoader)
+
+    def test_load_with_missing_required_option(self):
+        try:
+            ntl = NetTestLoader(dummyArgs)
+            ntl.setupTestCases(loadNetTestString(net_test_string_with_required_option))
+
+        except MissingRequiredOption:
+            pass
+
+    def test_net_test_inputs(self):
+        ntl = NetTestLoader(dummyArgsWithFile)
+        ntl.setupTestCases(loadNetTestString(net_test_string_with_file))
+
+        ntl.checkOptions()
+
+        # XXX: if you use the same test_class twice you will have consumed all
+        # of its inputs!
+        tested = set([])
+        for test_class, test_method in ntl.testCases:
+            if test_class not in tested:
+                tested.update([test_class])
+                self.assertEqual(len(list(test_class.inputs)), 10)
+
+    def test_setup_local_options_in_test_cases(self):
+        ntl = NetTestLoader(dummyArgs)
+        ntl.setupTestCases(loadNetTestString(net_test_string))
+
+        ntl.checkOptions()
+
+        for test_class, test_method in ntl.testCases:
+            self.assertEqual(test_class.localOptions, dummyOptions)
+
+    def test_generate_measurements_size(self):
+        ntl = NetTestLoader(dummyArgsWithFile)
+        ntl.setupTestCases(loadNetTestString(net_test_string_with_file))
+
+        ntl.checkOptions()
+        net_test = NetTest(ntl, None)
+
+        measurements = list(net_test.generateMeasurements())
+        self.assertEqual(len(measurements), 20)
+
+    def test_net_test_completed_callback(self):
+        ntl = NetTestLoader(dummyArgsWithFile)
+        ntl.setupTestCases(loadNetTestString(net_test_string_with_file))
+
+        ntl.checkOptions()
+        director = Director()
+
+        d = director.startNetTest('', ntl, [MockReporter()])
+
+        @d.addCallback
+        def complete(result):
+            #XXX: why is the return type (True, None) ?
+            self.assertEqual(result, [(True,None)])
+            self.assertEqual(director.successfulMeasurements, 20)
+
+        return d
+
+    def test_require_root_succeed(self):
+        #XXX: will require root to run
+        ntl = NetTestLoader(dummyArgs)
+        ntl.setupTestCases(loadNetTestString(net_test_root_required))
+
+        for test_class, method in ntl.testCases:
+            self.assertTrue(test_class.requiresRoot)
+
+    #def test_require_root_failed(self):
+    #    #XXX: will fail if you run as root
+    #    try:
+    #        net_test = NetTestLoader(StringIO(net_test_root_required),
+    #                dummyArgs)
+    #    except NotRootError:
+    #        pass
+
+    #def test_create_report_succeed(self):
+    #    pass
+
+    #def test_create_report_failed(self):
+    #    pass
+
+    #def test_run_all_test(self):
+    #    raise NotImplementedError
+
+    #def test_resume_test(self):
+    #    pass
+
+    #def test_progress(self):
+    #    pass
+
+    #def test_time_out(self):
+    #    raise NotImplementedError
diff --git a/ooni/tests/test_otime.py b/ooni/tests/test_otime.py
new file mode 100644
index 0000000..80979f2
--- /dev/null
+++ b/ooni/tests/test_otime.py
@@ -0,0 +1,15 @@
+import unittest
+from datetime import datetime
+from ooni import otime
+
+test_date = datetime(2002, 6, 26, 22, 45, 49)
+
+class TestOtime(unittest.TestCase):
+    def test_timestamp(self):
+        self.assertEqual(otime.timestamp(test_date), "2002-06-26T224549Z")
+
+    def test_fromTimestamp(self):
+        time_stamp = otime.timestamp(test_date)
+        self.assertEqual(test_date, otime.fromTimestamp(time_stamp))
+
+
diff --git a/ooni/tests/test_reporter.py b/ooni/tests/test_reporter.py
new file mode 100644
index 0000000..d7ee907
--- /dev/null
+++ b/ooni/tests/test_reporter.py
@@ -0,0 +1,238 @@
+from twisted.internet import defer
+from twisted.trial import unittest
+
+from ooni.reporter import Report, YAMLReporter, OONIBReporter, safe_dump
+from ooni.managers import ReportEntryManager, TaskManager
+from ooni.nettest import NetTest, NetTestState
+from ooni.errors import ReportNotCreated, ReportAlreadyClosed
+
+from ooni.tasks import TaskWithTimeout
+from ooni.tests.mocks import MockOReporter, MockTaskManager
+from ooni.tests.mocks import MockMeasurement, MockNetTest
+from ooni.tests.mocks import MockOReporterThatFailsWrite
+from ooni.tests.mocks import MockOReporterThatFailsWriteOnce
+from ooni.tests.mocks import MockOReporterThatFailsOpen
+
+from twisted.python import failure
+import yaml
+
+class TestReport(unittest.TestCase):
+    def setUp(self):
+        pass
+    def tearDown(self):
+        pass
+    def test_create_report_with_no_reporter(self):
+        report = Report([],ReportEntryManager())
+        self.assertIsInstance(report, Report)
+
+    def test_create_report_with_single_reporter(self):
+        report = Report([MockOReporter()], ReportEntryManager())
+        self.assertIsInstance(report, Report)
+
+    def test_create_report_with_multiple_reporters(self):
+        report = Report([MockOReporter() for x in xrange(3)],
+                ReportEntryManager())
+        self.assertIsInstance(report, Report)
+
+    def test_report_open_with_single_reporter(self):
+        report = Report([MockOReporter()],ReportEntryManager())
+        d = report.open()
+        return d
+
+    def test_report_open_with_multiple_reporter(self):
+        report = Report([MockOReporter() for x in xrange(3)],
+                ReportEntryManager())
+        d = report.open()
+        return d
+
+    def test_fail_to_open_report_with_single_reporter(self):
+        report = Report([MockOReporterThatFailsOpen()],
+                ReportEntryManager())
+        d = report.open()
+        def f(x):
+            self.assertEquals(len(report.reporters), 0)
+        d.addCallback(f)
+        return d
+
+    def test_fail_to_open_single_report_with_multiple_reporter(self):
+        report = Report([MockOReporterThatFailsOpen(), MockOReporter(),
+                MockOReporter()], ReportEntryManager())
+        d = report.open()
+        def f(x):
+            self.assertEquals(len(report.reporters),2)
+        d.addCallback(f)
+        return d
+
+    def test_fail_to_open_all_reports_with_multiple_reporter(self):
+        report = Report([MockOReporterThatFailsOpen() for x in xrange(3)],
+                ReportEntryManager())
+        d = report.open()
+        def f(x):
+            self.assertEquals(len(report.reporters),0)
+        d.addCallback(f)
+        return d
+
+    def test_write_report_with_single_reporter_and_succeed(self):
+        #XXX: verify that the MockOReporter writeReportEntry succeeds
+        report = Report([MockOReporter()], ReportEntryManager())
+        report.open()
+        d = report.write(MockMeasurement(MockNetTest()))
+        return d
+
+    def test_write_report_with_single_reporter_and_fail_after_timeout(self):
+        report = Report([MockOReporterThatFailsWrite()], ReportEntryManager())
+        report.open()
+        d = report.write(MockMeasurement(MockNetTest()))
+        def f(err):
+            self.assertEquals(len(report.reporters),0)
+        d.addBoth(f)
+        return d
+
+    def test_write_report_with_single_reporter_and_succeed_after_timeout(self):
+        report = Report([MockOReporterThatFailsWriteOnce()], ReportEntryManager())
+        report.open()
+        d = report.write(MockMeasurement(MockNetTest()))
+        return d
+
+    def test_write_report_with_multiple_reporter_and_succeed(self):
+        report = Report([MockOReporter() for x in xrange(3)], ReportEntryManager())
+        report.open()
+        d = report.write(MockMeasurement(MockNetTest()))
+        return d
+
+    def test_write_report_with_multiple_reporter_and_fail_a_single_reporter(self):
+        report = Report([MockOReporter(), MockOReporter(), MockOReporterThatFailsWrite()], ReportEntryManager())
+        d = report.open()
+
+        self.assertEquals(len(report.reporters),3)
+        d = report.write(MockMeasurement(MockNetTest()))
+
+        def f(x):
+            # one of the reporters should have been removed
+            self.assertEquals(len(report.reporters), 2)
+        d.addBoth(f)
+        return d
+
+    def test_write_report_with_multiple_reporter_and_fail_all_reporter(self):
+        report = Report([MockOReporterThatFailsWrite() for x in xrange(3)], ReportEntryManager())
+        report.open()
+        d = report.write(MockMeasurement(MockNetTest()))
+        def f(err):
+            self.assertEquals(len(report.reporters),0)
+        d.addErrback(f)
+        return d
+
+class TestYAMLReporter(unittest.TestCase):
+    def setUp(self):
+        self.testDetails = {'software_name': 'ooniprobe', 'options':
+        {'pcapfile': None, 'help': 0, 'subargs': ['-f', 'alexa_10'], 'resume':
+        0, 'parallelism': '10', 'no-default-reporter': 0, 'testdeck': None,
+        'test': 'nettests/blocking/http_requests.py', 'logfile': None,
+        'collector': None, 'reportfile': None}, 'test_version': '0.2.3',
+        'software_version': '0.0.10', 'test_name': 'http_requests_test',
+        'start_time': 1362054343.0, 'probe_asn': 'AS0', 'probe_ip':
+        '127.0.0.1', 'probe_cc': 'US'}
+
+    def tearDown(self):
+        pass
+    def test_create_yaml_reporter(self):
+        self.assertIsInstance(YAMLReporter(self.testDetails),
+                YAMLReporter)
+        
+    def test_open_yaml_report_and_succeed(self):
+        r = YAMLReporter(self.testDetails)
+        r.createReport()
+        # verify that testDetails was written to report properly
+        def f(r):
+            r._stream.seek(0)
+            details, = yaml.safe_load_all(r._stream)
+            self.assertEqual(details, self.testDetails)
+        r.created.addCallback(f)
+        return r.created
+
+    #def test_open_yaml_report_and_fail(self):
+    #    #XXX: YAMLReporter does not handle failures of this type
+    #    pass
+
+    def test_write_yaml_report_entry(self):
+        r = YAMLReporter(self.testDetails)
+        r.createReport()
+
+        report_entry = {'foo':'bar', 'bin':'baz'}
+        r.writeReportEntry(report_entry)
+
+        # verify that details and entry were written to report
+        def f(r):
+            r._stream.seek(0)
+            report = yaml.safe_load_all(r._stream)
+            details, entry  = report
+            self.assertEqual(details, self.testDetails)
+            self.assertEqual(entry, report_entry)
+        r.created.addCallback(f)
+        return r.created
+
+    def test_write_multiple_yaml_report_entry(self):
+        r = YAMLReporter(self.testDetails)
+        r.createReport()
+        def reportEntry():
+            for x in xrange(10):
+                yield {'foo':'bar', 'bin':'baz', 'item':x}
+        for entry in reportEntry():
+            r.writeReportEntry(entry)
+        # verify that details and multiple entries were written to report
+        def f(r):
+            r._stream.seek(0)
+            report = yaml.safe_load_all(r._stream)
+            details = report.next()
+            self.assertEqual(details, self.testDetails)
+            self.assertEqual([r for r in report], [r for r in reportEntry()])
+        r.created.addCallback(f)
+        return r.created
+
+    def test_close_yaml_report(self):
+        r = YAMLReporter(self.testDetails)
+        r.createReport()
+        r.finish()
+        self.assertTrue(r._stream.closed)
+
+    def test_write_yaml_report_after_close(self):
+        r = YAMLReporter(self.testDetails)
+        r.createReport()
+        r.finish()
+        def f(r):
+            r.writeReportEntry("foo")
+        r.created.addCallback(f)
+        self.assertFailure(r.created, ReportAlreadyClosed)
+
+    def test_write_yaml_report_before_open(self):
+        r = YAMLReporter(self.testDetails)
+        def f(r):
+            r.writeReportEntry("foo")
+        r.created.addCallback(f)
+        self.assertFailure(r.created, ReportNotCreated)
+
+#class TestOONIBReporter(unittest.TestCase):
+#    def setUp(self):
+#        pass
+#    def tearDown(self):
+#        pass
+#    def test_create_oonib_reporter(self):
+#        raise NotImplementedError
+#    def test_open_oonib_report_and_succeed(self):
+#        raise NotImplementedError
+#    def test_open_oonib_report_and_fail(self):
+#        raise NotImplementedError
+#    def test_write_oonib_report_entry_and_succeed(self):
+#        raise NotImplementedError
+#    def test_write_oonib_report_entry_and_succeed_after_timeout(self):
+#        raise NotImplementedError
+#    def test_write_oonib_report_entry_and_fail_after_timeout(self):
+#        raise NotImplementedError
+#    def test_write_oonib_report_after_close(self):
+#        raise NotImplementedError
+#    def test_write_oonib_report_before_open(self):
+#        raise NotImplementedError
+#    def test_close_oonib_report_and_succeed(self):
+#        raise NotImplementedError
+#    def test_close_oonib_report_and_fail(self):
+#        raise NotImplementedError
diff --git a/ooni/tests/test_safe_represent.py b/ooni/tests/test_safe_represent.py
new file mode 100644
index 0000000..82a5196
--- /dev/null
+++ b/ooni/tests/test_safe_represent.py
@@ -0,0 +1,14 @@
+import yaml
+
+from twisted.trial import unittest
+
+from ooni.reporter import OSafeDumper
+
+from scapy.all import IP, UDP
+
+class TestScapyRepresent(unittest.TestCase):
+    def test_represent_scapy(self):
+        data = IP()/UDP()
+        yaml.dump_all([data], Dumper=OSafeDumper)
+
+
diff --git a/ooni/tests/test_trueheaders.py b/ooni/tests/test_trueheaders.py
new file mode 100644
index 0000000..9ac0a27
--- /dev/null
+++ b/ooni/tests/test_trueheaders.py
@@ -0,0 +1,41 @@
+from twisted.trial import unittest
+
+from ooni.utils.txagentwithsocks import TrueHeaders
+
+dummy_headers_dict = {
+        'Header1': ['Value1', 'Value2'],
+        'Header2': ['ValueA', 'ValueB']
+}
+
+dummy_headers_dict2 = {
+        'Header1': ['Value1', 'Value2'],
+        'Header2': ['ValueA', 'ValueB'],
+        'Header3': ['ValueA', 'ValueB'],
+}
+
+dummy_headers_dict3 = {
+        'Header1': ['Value1', 'Value2'],
+        'Header2': ['ValueA', 'ValueB'],
+        'Header4': ['ValueA', 'ValueB'],
+}
+
+
+class TestTrueHeaders(unittest.TestCase):
+    def test_names_match(self):
+        th = TrueHeaders(dummy_headers_dict)
+        self.assertEqual(th.getDiff(TrueHeaders(dummy_headers_dict)), set())
+
+    def test_names_not_match(self):
+        th = TrueHeaders(dummy_headers_dict)
+        self.assertEqual(th.getDiff(TrueHeaders(dummy_headers_dict2)), set(['Header3']))
+
+        th = TrueHeaders(dummy_headers_dict3)
+        self.assertEqual(th.getDiff(TrueHeaders(dummy_headers_dict2)), set(['Header3', 'Header4']))
+
+    def test_names_match_expect_ignore(self):
+        th = TrueHeaders(dummy_headers_dict)
+        self.assertEqual(th.getDiff(TrueHeaders(dummy_headers_dict2), ignore=['Header3']), set())
+
+
+
+
diff --git a/ooni/tests/test_utils.py b/ooni/tests/test_utils.py
new file mode 100644
index 0000000..cc648e0
--- /dev/null
+++ b/ooni/tests/test_utils.py
@@ -0,0 +1,20 @@
+import unittest
+from ooni.utils import pushFilenameStack
+
+class TestUtils(unittest.TestCase):
+    def test_pushFilenameStack(self):
+        f = open("dummyfile", "w+")
+        f.write("0\n")
+        f.close()
+        for i in xrange(1, 5):
+            f = open("dummyfile.%s" % i, "w+")
+            f.write("%s\n" % i)
+            f.close()
+
+        pushFilenameStack("dummyfile")
+        for i in xrange(1, 5):
+            f = open("dummyfile.%s" % i)
+            c = f.readlines()[0].strip()
+            self.assertEqual(str(i-1), str(c))
+            f.close()
+
diff --git a/ooni/utils/log.py b/ooni/utils/log.py
index 0740c10..141116e 100644
--- a/ooni/utils/log.py
+++ b/ooni/utils/log.py
@@ -45,14 +45,16 @@ def stop():
     print "Stopping OONI"
 
 def msg(msg, *arg, **kw):
-    print "%s" % msg
+    if config.logging:
+        print "%s" % msg
 
 def debug(msg, *arg, **kw):
-    if config.advanced.debug:
+    if config.advanced.debug and config.logging:
         print "[D] %s" % msg
 
 def err(msg, *arg, **kw):
-    print "[!] %s" % msg
+    if config.logging:
+        print "[!] %s" % msg
 
 def exception(error):
     """
diff --git a/ooniprobe.conf.sample b/ooniprobe.conf.sample
index 51c60f5..8a6b825 100644
--- a/ooniprobe.conf.sample
+++ b/ooniprobe.conf.sample
@@ -30,6 +30,8 @@ advanced:
     # If you do not specify start_tor, you will have to have Tor running and
     # explicitly set the control port and SOCKS port
     start_tor: true
+    # On which port the oonid API should be listening on
+    oonid_api_port: 50666
 tor:
     #socks_port: 9050
     #control_port: 9051
diff --git a/tests/__init__.py b/tests/__init__.py
deleted file mode 100644
index e69de29..0000000
diff --git a/tests/mocks.py b/tests/mocks.py
deleted file mode 100644
index fed683e..0000000
--- a/tests/mocks.py
+++ /dev/null
@@ -1,168 +0,0 @@
-from ooni.tasks import BaseTask, TaskWithTimeout
-from twisted.python import failure
-from ooni.nettest import NetTest
-from ooni.managers import TaskManager
-from twisted.internet import defer
-
-class MockMeasurementFailOnce(BaseTask):
-    def run(self):
-        f = open('dummyTaskFailOnce.txt', 'w')
-        f.write('fail')
-        f.close()
-        if self.failure >= 1:
-            return defer.succeed(self)
-        else:
-            return defer.fail(failure.Failure)
-
-class MockMeasurementManager(TaskManager):
-    def __init__(self):
-        self.successes = []
-        TaskManager.__init__(self)
-
-    def failed(self, failure, task):
-        pass
-
-    def succeeded(self, result, task):
-        self.successes.append((result, task))
-
-class MockReporter(object):
-    def __init__(self):
-        self.created = defer.Deferred()
-
-    def writeReportEntry(self, entry):
-        pass
-
-    def createReport(self):
-        self.created.callback(self)
-
-    def finish(self):
-        pass
-
-class MockFailure(Exception):
-    pass
-
-## from test_managers
-mockFailure = failure.Failure(MockFailure('mock'))
-
-class MockSuccessTask(BaseTask):
-    def run(self):
-        return defer.succeed(42)
-
-class MockFailTask(BaseTask):
-    def run(self):
-        return defer.fail(mockFailure)
-
-class MockFailOnceTask(BaseTask):
-    def run(self):
-        if self.failures >= 1:
-            return defer.succeed(42)
-        else:
-            return defer.fail(mockFailure)
-
-class MockSuccessTaskWithTimeout(TaskWithTimeout):
-    def run(self):
-        return defer.succeed(42)
-
-class MockFailTaskThatTimesOut(TaskWithTimeout):
-    def run(self):
-        return defer.Deferred()
-
-class MockTimeoutOnceTask(TaskWithTimeout):
-    def run(self):
-        if self.failures >= 1:
-            return defer.succeed(42)
-        else:
-            return defer.Deferred()
-
-class MockFailTaskWithTimeout(TaskWithTimeout):
-    def run(self):
-        return defer.fail(mockFailure)
-
-
-class MockNetTest(object):
-    def __init__(self):
-        self.successes = []
-
-    def succeeded(self, measurement):
-        self.successes.append(measurement)
-
-class MockMeasurement(TaskWithTimeout):
-    def __init__(self, net_test):
-        TaskWithTimeout.__init__(self)
-        self.netTest = net_test
-
-    def succeeded(self, result):
-        return self.netTest.succeeded(42)
-
-class MockSuccessMeasurement(MockMeasurement):
-    def run(self):
-        return defer.succeed(42)
-
-class MockFailMeasurement(MockMeasurement):
-    def run(self):
-        return defer.fail(mockFailure)
-
-class MockFailOnceMeasurement(MockMeasurement):
-    def run(self):
-        if self.failures >= 1:
-            return defer.succeed(42)
-        else:
-            return defer.fail(mockFailure)
-
-class MockDirector(object):
-    def __init__(self):
-        self.successes = []
-
-    def measurementFailed(self, failure, measurement):
-        pass
-
-    def measurementSucceeded(self, measurement):
-        self.successes.append(measurement)
-
-## from test_reporter.py
-class MockOReporter(object):
-    def __init__(self):
-        self.created = defer.Deferred()
-
-    def writeReportEntry(self, entry):
-        return defer.succeed(42)
-
-    def finish(self):
-        pass
-
-    def createReport(self):
-        from ooni.utils import log
-        log.debug("Creating report with %s" % self)
-        self.created.callback(self)
-
-class MockOReporterThatFailsWrite(MockOReporter):
-    def writeReportEntry(self, entry):
-        raise MockFailure
-
-class MockOReporterThatFailsOpen(MockOReporter):
-    def createReport(self):
-        self.created.errback(failure.Failure(MockFailure()))
-
-class MockOReporterThatFailsWriteOnce(MockOReporter):
-    def __init__(self):
-        self.failure = 0
-        MockOReporter.__init__(self)
-
-    def writeReportEntry(self, entry):
-        if self.failure >= 1:
-            return defer.succeed(42)
-        else:
-            self.failure += 1
-            raise MockFailure 
-
-class MockTaskManager(TaskManager):
-    def __init__(self):
-        self.successes = []
-        TaskManager.__init__(self)
-
-    def failed(self, failure, task):
-        pass
-
-    def succeeded(self, result, task):
-        self.successes.append((result, task))
-
diff --git a/tests/test-class-design.py b/tests/test-class-design.py
deleted file mode 100644
index bb80cd3..0000000
--- a/tests/test-class-design.py
+++ /dev/null
@@ -1,101 +0,0 @@
-#!/usr/bin/env python
-#
-# testing classes to test multiple inheritance.
-# these are not meant to be run by trial, though they could be made to be so.
-# i didn't know where to put them. --isis
-
-import abc
-from pprint import pprint
-from inspect import classify_class_attrs
-
-class PluginBase(object):
-    __metaclass__ = abc.ABCMeta
-
-    @abc.abstractproperty
-    def name(self):
-        return 'you should not see this'
-
-    @name.setter
-    def name(self, value):
-        return 'you should not set this'
-
-    @name.deleter
-    def name(self):
-        return 'you should not del this'
-
-    @abc.abstractmethod
-    def inputParser(self, line):
-        """Do something to parse something."""
-        return
-
-class Foo(object):
-    woo = "this class has some shit in it"
-    def bar(self):
-        print "i'm a Foo.bar()!"
-        print woo
-
-class KwargTest(Foo):
-    _name = "isis"
-
-    #def __new__(cls, *a, **kw):
-    #    return super(KwargTest, cls).__new__(cls, *a, **kw)
-
-    @property
-    def name(self):
-        return self._name
-
-    @name.setter
-    def name(self, value):
-        self._name = value
-
-    def __init__(self, *a, **kw):
-        super(KwargTest, self).__init__()
-
-        ## this causes the instantion args to override the class attrs
-        for key, value in kw.items():
-            setattr(self.__class__, key, value)
-
-        print "%s.__init__(): self.__dict__ = %s" \
-            % (type(self), pprint(type(self).__dict__))
-
-        for attr in classify_class_attrs(self):
-            print attr
-
-    @classmethod
-    def sayname(cls):
-        print cls.name
-
-class KwargTestChild(KwargTest):
-    name = "arturo"
-    def __init__(self):
-        super(KwargTestChild, self).__init__()
-        print self.name
-
-class KwargTestChildOther(KwargTest):
-    def __init__(self, name="robot", does="lasers"):
-        super(KwargTestChildOther, self).__init__()
-        print self.name
-
-
-if __name__ == "__main__":
-    print "class KwargTest attr name: %s" % KwargTest.name
-    kwargtest = KwargTest()
-    print "KwargTest instantiated wo args"
-    print "kwargtest.name: %s" % kwargtest.name
-    print "kwargtest.sayname(): %s" % kwargtest.sayname()
-    kwargtest2 = KwargTest(name="lovecruft", does="hacking")
-    print "KwargTest instantiated with name args"
-    print "kwargtest.name: %s" % kwargtest2.name
-    print "kwargtest.sayname(): %s" % kwargtest2.sayname()
-
-    print "class KwargTestChild attr name: %s" % KwargTestChild.name
-    kwargtestchild = KwargTestChild()
-    print "KwargTestChild instantiated wo args"
-    print "kwargtestchild.name: %s" % kwargtestchild.name
-    print "kwargtestchild.sayname(): %s" % kwargtestchild.sayname()
-
-    print "class KwargTestChildOther attr name: %s" % KwargTestChildOther.name
-    kwargtestchildother = KwargTestChildOther()
-    print "KwargTestChildOther instantiated wo args"
-    print "kwargtestchildother.name: %s" % kwargtestchildother.name
-    print "kwargtestchildother.sayname(): %s" % kwargtestchildother.sayname()
diff --git a/tests/test_director.py b/tests/test_director.py
deleted file mode 100644
index a9dfbe8..0000000
--- a/tests/test_director.py
+++ /dev/null
@@ -1,59 +0,0 @@
-from twisted.internet import defer, base
-from twisted.trial import unittest
-
-from ooni.director import Director
-from ooni.nettest import NetTestLoader
-from tests.mocks import MockReporter
-base.DelayedCall.debug = True
-
-net_test_string = """
-from twisted.python import usage
-from ooni.nettest import NetTestCase
-
-class UsageOptions(usage.Options):
-    optParameters = [['spam', 's', None, 'ham']]
-
-class DummyTestCase(NetTestCase):
-    inputFile = ['file', 'f', None, 'The input File']
-
-    usageOptions = UsageOptions
-
-    def test_a(self):
-        self.report['bar'] = 'bar'
-
-    def test_b(self):
-        self.report['foo'] = 'foo'
-"""
-
-
-dummyArgs = ('--spam', 1, '--file', 'dummyInputFile.txt')
-
-class TestDirector(unittest.TestCase):
-    timeout = 1
-    def setUp(self):
-        with open('dummyInputFile.txt', 'w') as f:
-            for i in range(10):
-                f.write("%s\n" % i)
-
-        self.reporters = [MockReporter()]
-        self.director = Director()
-
-    def tearDown(self):
-        pass
-
-    def test_start_net_test(self):
-        ntl = NetTestLoader(dummyArgs)
-        ntl.loadNetTestString(net_test_string)
-
-        ntl.checkOptions()
-        d = self.director.startNetTest('', ntl, self.reporters)
-
-        @d.addCallback
-        def done(result):
-            self.assertEqual(self.director.successfulMeasurements, 20)
-
-        return d
-
-    def test_stop_net_test(self):
-        pass
-
diff --git a/tests/test_dns.py b/tests/test_dns.py
deleted file mode 100644
index e9bb524..0000000
--- a/tests/test_dns.py
+++ /dev/null
@@ -1,24 +0,0 @@
-#
-# This unittest is to verify that our usage of the twisted DNS resolver does
-# not break with new versions of twisted.
-
-import pdb
-from twisted.trial import unittest
-
-from twisted.internet import reactor
-
-from twisted.names import dns
-from twisted.names.client import Resolver
-
-class DNSTest(unittest.TestCase):
-    def test_a_lookup_ooni_query(self):
-        def done_query(message, *arg):
-            answer = message.answers[0]
-            self.assertEqual(answer.type, 1)
-
-        dns_query = [dns.Query('ooni.nu', type=dns.A)]
-        resolver = Resolver(servers=[('8.8.8.8', 53)])
-        d = resolver.queryUDP(dns_query)
-        d.addCallback(done_query)
-        return d
-
diff --git a/tests/test_inputunit.py b/tests/test_inputunit.py
deleted file mode 100644
index 1f9043c..0000000
--- a/tests/test_inputunit.py
+++ /dev/null
@@ -1,29 +0,0 @@
-import unittest
-from ooni.inputunit import InputUnit, InputUnitFactory
-
-def dummyGenerator():
-    for x in range(100):
-        yield x
-
-class TestInputUnit(unittest.TestCase):
-    def test_input_unit_factory(self):
-        inputUnit = InputUnitFactory(range(100))
-        for i in inputUnit:
-            self.assertEqual(len(list(i)), inputUnit.inputUnitSize)
-
-    def test_input_unit(self):
-        inputs = range(100)
-        inputUnit = InputUnit(inputs)
-        idx = 0
-        for i in inputUnit:
-            idx += 1
-
-        self.assertEqual(idx, 100)
-
-    def test_input_unit_factory_length(self):
-        inputUnitFactory = InputUnitFactory(range(100))
-        l1 = len(inputUnitFactory)
-        l2 = sum(1 for _ in inputUnitFactory)
-        self.assertEqual(l1, 10)
-        self.assertEqual(l2, 10)
-
diff --git a/tests/test_managers.py b/tests/test_managers.py
deleted file mode 100644
index 39f0881..0000000
--- a/tests/test_managers.py
+++ /dev/null
@@ -1,215 +0,0 @@
-from twisted.trial import unittest
-from twisted.python import failure
-from twisted.internet import defer, task
-
-from ooni.tasks import BaseTask, TaskWithTimeout, TaskTimedOut
-from ooni.managers import TaskManager, MeasurementManager
-
-from tests.mocks import MockSuccessTask, MockFailTask, MockFailOnceTask, MockFailure
-from tests.mocks import MockSuccessTaskWithTimeout, MockFailTaskThatTimesOut
-from tests.mocks import MockTimeoutOnceTask, MockFailTaskWithTimeout
-from tests.mocks import MockTaskManager, mockFailure, MockDirector
-from tests.mocks import MockNetTest, MockMeasurement, MockSuccessMeasurement
-from tests.mocks import MockFailMeasurement, MockFailOnceMeasurement
-
-class TestTaskManager(unittest.TestCase):
-    timeout = 1
-    def setUp(self):
-        self.measurementManager = MockTaskManager()
-        self.measurementManager.concurrency = 20
-        self.measurementManager.retries = 2
-
-        self.measurementManager.start()
-
-        self.clock = task.Clock()
-
-    def schedule_successful_tasks(self, task_type, number=1):
-        all_done = []
-        for x in range(number):
-            mock_task = task_type()
-            all_done.append(mock_task.done)
-            self.measurementManager.schedule(mock_task)
-
-        d = defer.DeferredList(all_done)
-        @d.addCallback
-        def done(res):
-            for task_result, task_instance in self.measurementManager.successes:
-                self.assertEqual(task_result, 42)
-                self.assertIsInstance(task_instance, task_type)
-
-        return d
-
-    def schedule_failing_tasks(self, task_type, number=1):
-        all_done = []
-        for x in range(number):
-            mock_task = task_type()
-            all_done.append(mock_task.done)
-            self.measurementManager.schedule(mock_task)
-
-        d = defer.DeferredList(all_done)
-        @d.addCallback
-        def done(res):
-            # 10*2 because 2 is the number of retries
-            self.assertEqual(len(self.measurementManager.failures), number*3)
-            for task_result, task_instance in self.measurementManager.failures:
-                self.assertEqual(task_result, mockFailure)
-                self.assertIsInstance(task_instance, task_type)
-
-        return d
-
-    def test_schedule_failing_with_mock_failure_task(self):
-        mock_task = MockFailTask()
-        self.measurementManager.schedule(mock_task)
-        self.assertFailure(mock_task.done, MockFailure)
-        return mock_task.done
-
-    def test_schedule_successful_one_task(self):
-        return self.schedule_successful_tasks(MockSuccessTask)
-
-    def test_schedule_successful_one_task_with_timeout(self):
-        return self.schedule_successful_tasks(MockSuccessTaskWithTimeout)
-
-    def test_schedule_failing_tasks_that_timesout(self):
-        self.measurementManager.retries = 0
-
-        task_type = MockFailTaskThatTimesOut
-        task_timeout = 5
-
-        mock_task = task_type()
-        mock_task.timeout = task_timeout
-        mock_task.clock = self.clock
-
-        self.measurementManager.schedule(mock_task)
-
-        self.clock.advance(task_timeout)
-
-        @mock_task.done.addBoth
-        def done(res):
-            self.assertEqual(len(self.measurementManager.failures), 1)
-            for task_result, task_instance in self.measurementManager.failures:
-                self.assertIsInstance(task_instance, task_type)
-
-        return mock_task.done
-
-    def test_schedule_time_out_once(self):
-        task_type = MockTimeoutOnceTask
-        task_timeout = 5
-
-        mock_task = task_type()
-        mock_task.timeout = task_timeout
-        mock_task.clock = self.clock
-
-        self.measurementManager.schedule(mock_task)
-
-        self.clock.advance(task_timeout)
-
-        @mock_task.done.addBoth
-        def done(res):
-            self.assertEqual(len(self.measurementManager.failures), 1)
-            for task_result, task_instance in self.measurementManager.failures:
-                self.assertIsInstance(task_instance, task_type)
-
-            for task_result, task_instance in self.measurementManager.successes:
-                self.assertEqual(task_result, 42)
-                self.assertIsInstance(task_instance, task_type)
-
-        return mock_task.done
-
-
-    def test_schedule_failing_one_task(self):
-        return self.schedule_failing_tasks(MockFailTask)
-
-    def test_schedule_failing_one_task_with_timeout(self):
-        return self.schedule_failing_tasks(MockFailTaskWithTimeout)
-
-    def test_schedule_successful_ten_tasks(self):
-        return self.schedule_successful_tasks(MockSuccessTask, number=10)
-
-    def test_schedule_failing_ten_tasks(self):
-        return self.schedule_failing_tasks(MockFailTask, number=10)
-
-    def test_schedule_successful_27_tasks(self):
-        return self.schedule_successful_tasks(MockSuccessTask, number=27)
-
-    def test_schedule_failing_27_tasks(self):
-        return self.schedule_failing_tasks(MockFailTask, number=27)
-
-    def test_task_retry_and_succeed(self):
-        mock_task = MockFailOnceTask()
-        self.measurementManager.schedule(mock_task)
-
-        @mock_task.done.addCallback
-        def done(res):
-            self.assertEqual(len(self.measurementManager.failures), 1)
-
-            self.assertEqual(self.measurementManager.failures,
-                    [(mockFailure, mock_task)])
-            self.assertEqual(self.measurementManager.successes,
-                    [(42, mock_task)])
-
-        return mock_task.done
-
-    def dd_test_task_retry_and_succeed_56_tasks(self):
-        """
-        XXX this test fails in a non-deterministic manner.
-        """
-        all_done = []
-        number = 56
-        for x in range(number):
-            mock_task = MockFailOnceTask()
-            all_done.append(mock_task.done)
-            self.measurementManager.schedule(mock_task)
-
-        d = defer.DeferredList(all_done)
-
-        @d.addCallback
-        def done(res):
-            self.assertEqual(len(self.measurementManager.failures), number)
-
-            for task_result, task_instance in self.measurementManager.successes:
-                self.assertEqual(task_result, 42)
-                self.assertIsInstance(task_instance, MockFailOnceTask)
-
-        return d
-
-class TestMeasurementManager(unittest.TestCase):
-    def setUp(self):
-        mock_director = MockDirector()
-
-        self.measurementManager = MeasurementManager()
-        self.measurementManager.director = mock_director
-
-        self.measurementManager.concurrency = 10
-        self.measurementManager.retries = 2
-
-        self.measurementManager.start()
-
-        self.mockNetTest = MockNetTest()
-
-    def test_schedule_and_net_test_notified(self, number=1):
-        # XXX we should probably be inheriting from the base test class
-        mock_task = MockSuccessMeasurement(self.mockNetTest)
-        self.measurementManager.schedule(mock_task)
-
-        @mock_task.done.addCallback
-        def done(res):
-            self.assertEqual(self.mockNetTest.successes,
-                    [42])
-
-            self.assertEqual(len(self.mockNetTest.successes), 1)
-        return mock_task.done
-
-    def test_schedule_failing_one_measurement(self):
-        mock_task = MockFailMeasurement(self.mockNetTest)
-        self.measurementManager.schedule(mock_task)
-
-        @mock_task.done.addErrback
-        def done(failure):
-            self.assertEqual(len(self.measurementManager.failures), 3)
-
-            self.assertEqual(failure, mockFailure)
-            self.assertEqual(len(self.mockNetTest.successes), 0)
-
-        return mock_task.done
-
-
diff --git a/tests/test_mutate.py b/tests/test_mutate.py
deleted file mode 100644
index 7e30586..0000000
--- a/tests/test_mutate.py
+++ /dev/null
@@ -1,15 +0,0 @@
-import unittest
-from ooni.kit import daphn3
-
-class TestDaphn3(unittest.TestCase):
-    def test_mutate_string(self):
-        original_string = '\x00\x00\x00'
-        mutated = daphn3.daphn3MutateString(original_string, 1)
-        self.assertEqual(mutated, '\x00\x01\x00')
-    def test_mutate_daphn3(self):
-        original_dict = [{'client': '\x00\x00\x00'},
-                {'server': '\x00\x00\x00'}]
-        mutated_dict = daphn3.daphn3Mutate(original_dict,  1, 1)
-        self.assertEqual(mutated_dict, [{'client': '\x00\x00\x00'},
-            {'server': '\x00\x01\x00'}])
-
diff --git a/tests/test_nettest.py b/tests/test_nettest.py
deleted file mode 100644
index 78240d5..0000000
--- a/tests/test_nettest.py
+++ /dev/null
@@ -1,268 +0,0 @@
-import os
-from StringIO import StringIO
-from tempfile import TemporaryFile, mkstemp
-
-from twisted.trial import unittest
-from twisted.internet import defer, reactor
-from twisted.python.usage import UsageError
-
-from ooni.nettest import NetTest, InvalidOption, MissingRequiredOption
-from ooni.nettest import NetTestLoader, FailureToLoadNetTest
-from ooni.tasks import BaseTask
-from ooni.utils import NotRootError
-
-from ooni.director import Director
-
-from ooni.managers import TaskManager
-
-from tests.mocks import MockMeasurement, MockMeasurementFailOnce
-from tests.mocks import MockNetTest, MockDirector, MockReporter
-from tests.mocks import MockMeasurementManager
-defer.setDebugging(True)
-
-net_test_string = """
-from twisted.python import usage
-from ooni.nettest import NetTestCase
-
-class UsageOptions(usage.Options):
-    optParameters = [['spam', 's', None, 'ham']]
-
-class DummyTestCase(NetTestCase):
-
-    usageOptions = UsageOptions
-
-    def test_a(self):
-        self.report['bar'] = 'bar'
-
-    def test_b(self):
-        self.report['foo'] = 'foo'
-"""
-
-net_test_root_required = net_test_string+"""
-    requiresRoot = True
-"""
-
-net_test_string_with_file = """
-from twisted.python import usage
-from ooni.nettest import NetTestCase
-
-class UsageOptions(usage.Options):
-    optParameters = [['spam', 's', None, 'ham']]
-
-class DummyTestCase(NetTestCase):
-    inputFile = ['file', 'f', None, 'The input File']
-
-    usageOptions = UsageOptions
-
-    def test_a(self):
-        self.report['bar'] = 'bar'
-
-    def test_b(self):
-        self.report['foo'] = 'foo'
-"""
-
-net_test_string_with_required_option = """
-from twisted.python import usage
-from ooni.nettest import NetTestCase
-
-class UsageOptions(usage.Options):
-    optParameters = [['spam', 's', None, 'ham'],
-                     ['foo', 'o', None, 'moo'],
-                     ['bar', 'o', None, 'baz'],
-    ]
-
-class DummyTestCase(NetTestCase):
-    inputFile = ['file', 'f', None, 'The input File']
-
-    usageOptions = UsageOptions
-
-    def test_a(self):
-        self.report['bar'] = 'bar'
-
-    def test_b(self):
-        self.report['foo'] = 'foo'
-
-    requiredOptions = ['foo', 'bar']
-"""
-
-dummyInputs = range(1)
-dummyArgs = ('--spam', 'notham')
-dummyOptions = {'spam':'notham'}
-dummyInvalidArgs = ('--cram', 'jam')
-dummyInvalidOptions= {'cram':'jam'}
-dummyArgsWithRequiredOptions = ('--foo', 'moo', '--bar', 'baz')
-dummyRequiredOptions = {'foo':'moo', 'bar':'baz'}
-dummyArgsWithFile = ('--spam', 'notham', '--file', 'dummyInputFile.txt')
-
-class TestNetTest(unittest.TestCase):
-    timeout = 1
-    def setUp(self):
-        with open('dummyInputFile.txt', 'w') as f:
-            for i in range(10):
-                f.write("%s\n" % i)
-
-    def assertCallable(self, thing):
-        self.assertIn('__call__', dir(thing))
-
-    def verifyMethods(self, testCases):
-        uniq_test_methods = set()
-        for test_class, test_methods in testCases:
-            instance = test_class()
-            for test_method in test_methods:
-                c = getattr(instance, test_method)
-                self.assertCallable(c)
-                uniq_test_methods.add(test_method)
-        self.assertEqual(set(['test_a', 'test_b']), uniq_test_methods)
-
-    def test_load_net_test_from_file(self):
-        """
-        Given a file verify that the net test cases are properly
-        generated.
-        """
-        __, net_test_file = mkstemp()
-        with open(net_test_file, 'w') as f:
-            f.write(net_test_string)
-        f.close()
-
-        ntl = NetTestLoader(dummyArgs)
-        ntl.loadNetTestFile(net_test_file)
-
-        self.verifyMethods(ntl.testCases)
-        os.unlink(net_test_file)
-
-    def test_load_net_test_from_str(self):
-        """
-        Given a file like object verify that the net test cases are properly
-        generated.
-        """
-        ntl = NetTestLoader(dummyArgs)
-        ntl.loadNetTestString(net_test_string)
-
-        self.verifyMethods(ntl.testCases)
-
-    def test_load_net_test_from_StringIO(self):
-        """
-        Given a file like object verify that the net test cases are properly
-        generated.
-        """
-        ntl = NetTestLoader(dummyArgs)
-        ntl.loadNetTestString(net_test_string)
-
-        self.verifyMethods(ntl.testCases)
-
-    def test_load_with_option(self):
-        ntl = NetTestLoader(dummyArgs)
-        ntl.loadNetTestString(net_test_string)
-
-        self.assertIsInstance(ntl, NetTestLoader)
-        for test_klass, test_meth in ntl.testCases:
-            for option in dummyOptions.keys():
-                self.assertIn(option, test_klass.usageOptions())
-
-    def test_load_with_invalid_option(self):
-        try:
-            ntl = NetTestLoader(dummyInvalidArgs)
-            ntl.loadNetTestString(net_test_string)
-
-            ntl.checkOptions()
-            raise Exception
-        except UsageError:
-            pass
-
-    def test_load_with_required_option(self):
-        ntl = NetTestLoader(dummyArgsWithRequiredOptions)
-        ntl.loadNetTestString(net_test_string_with_required_option)
-
-        self.assertIsInstance(ntl, NetTestLoader)
-
-    def test_load_with_missing_required_option(self):
-        try:
-            ntl = NetTestLoader(dummyArgs)
-            ntl.loadNetTestString(net_test_string_with_required_option)
-
-        except MissingRequiredOption:
-            pass
-
-    def test_net_test_inputs(self):
-        ntl = NetTestLoader(dummyArgsWithFile)
-        ntl.loadNetTestString(net_test_string_with_file)
-
-        ntl.checkOptions()
-
-        # XXX: if you use the same test_class twice you will have consumed all
-        # of its inputs!
-        tested = set([])
-        for test_class, test_method in ntl.testCases:
-            if test_class not in tested:
-                tested.update([test_class])
-                self.assertEqual(len(list(test_class.inputs)), 10)
-
-    def test_setup_local_options_in_test_cases(self):
-        ntl = NetTestLoader(dummyArgs)
-        ntl.loadNetTestString(net_test_string)
-
-        ntl.checkOptions()
-
-        for test_class, test_method in ntl.testCases:
-            self.assertEqual(test_class.localOptions, dummyOptions)
-
-    def test_generate_measurements_size(self):
-        ntl = NetTestLoader(dummyArgsWithFile)
-        ntl.loadNetTestString(net_test_string_with_file)
-
-        ntl.checkOptions()
-        net_test = NetTest(ntl, None)
-
-        measurements = list(net_test.generateMeasurements())
-        self.assertEqual(len(measurements), 20)
-
-    def test_net_test_completed_callback(self):
-        ntl = NetTestLoader(dummyArgsWithFile)
-        ntl.loadNetTestString(net_test_string_with_file)
-
-        ntl.checkOptions()
-        director = Director()
-
-        d = director.startNetTest('', ntl, [MockReporter()])
-
-        @d.addCallback
-        def complete(result):
-            #XXX: why is the return type (True, None) ?
-            self.assertEqual(result, [(True,None)])
-            self.assertEqual(director.successfulMeasurements, 20)
-
-        return d
-
-    def test_require_root_succeed(self):
-        #XXX: will require root to run
-        ntl = NetTestLoader(dummyArgs)
-        ntl.loadNetTestString(net_test_root_required)
-
-        for test_class, method in ntl.testCases:
-            self.assertTrue(test_class.requiresRoot)
-
-    #def test_require_root_failed(self):
-    #    #XXX: will fail if you run as root
-    #    try:
-    #        net_test = NetTestLoader(StringIO(net_test_root_required),
-    #                dummyArgs)
-    #    except NotRootError:
-    #        pass
-
-    #def test_create_report_succeed(self):
-    #    pass
-
-    #def test_create_report_failed(self):
-    #    pass
-
-    #def test_run_all_test(self):
-    #    raise NotImplementedError
-
-    #def test_resume_test(self):
-    #    pass
-
-    #def test_progress(self):
-    #    pass
-
-    #def test_time_out(self):
-    #    raise NotImplementedError
diff --git a/tests/test_otime.py b/tests/test_otime.py
deleted file mode 100644
index 80979f2..0000000
--- a/tests/test_otime.py
+++ /dev/null
@@ -1,15 +0,0 @@
-import unittest
-from datetime import datetime
-from ooni import otime
-
-test_date = datetime(2002, 6, 26, 22, 45, 49)
-
-class TestOtime(unittest.TestCase):
-    def test_timestamp(self):
-        self.assertEqual(otime.timestamp(test_date), "2002-06-26T224549Z")
-
-    def test_fromTimestamp(self):
-        time_stamp = otime.timestamp(test_date)
-        self.assertEqual(test_date, otime.fromTimestamp(time_stamp))
-
-
diff --git a/tests/test_reporter.py b/tests/test_reporter.py
deleted file mode 100644
index e21b7a1..0000000
--- a/tests/test_reporter.py
+++ /dev/null
@@ -1,238 +0,0 @@
-from twisted.internet import defer
-from twisted.trial import unittest
-
-from ooni.reporter import Report, YAMLReporter, OONIBReporter, safe_dump
-from ooni.managers import ReportEntryManager, TaskManager
-from ooni.nettest import NetTest, NetTestState
-from ooni.errors import ReportNotCreated, ReportAlreadyClosed
-
-from ooni.tasks import TaskWithTimeout
-from tests.mocks import MockOReporter, MockTaskManager
-from tests.mocks import MockMeasurement, MockNetTest
-from tests.mocks import MockOReporterThatFailsWrite
-from tests.mocks import MockOReporterThatFailsWriteOnce
-from tests.mocks import MockOReporterThatFailsOpen
-
-from twisted.python import failure
-import yaml
-
-class TestReport(unittest.TestCase):
-    def setUp(self):
-        pass
-    def tearDown(self):
-        pass
-    def test_create_report_with_no_reporter(self):
-        report = Report([],ReportEntryManager())
-        self.assertIsInstance(report, Report)
-
-    def test_create_report_with_single_reporter(self):
-        report = Report([MockOReporter()], ReportEntryManager())
-        self.assertIsInstance(report, Report)
-
-    def test_create_report_with_multiple_reporters(self):
-        report = Report([MockOReporter() for x in xrange(3)],
-                ReportEntryManager())
-        self.assertIsInstance(report, Report)
-
-    def test_report_open_with_single_reporter(self):
-        report = Report([MockOReporter()],ReportEntryManager())
-        d = report.open()
-        return d
-
-    def test_report_open_with_multiple_reporter(self):
-        report = Report([MockOReporter() for x in xrange(3)],
-                ReportEntryManager())
-        d = report.open()
-        return d
-
-    def test_fail_to_open_report_with_single_reporter(self):
-        report = Report([MockOReporterThatFailsOpen()],
-                ReportEntryManager())
-        d = report.open()
-        def f(x):
-            self.assertEquals(len(report.reporters), 0)
-        d.addCallback(f)
-        return d
-
-    def test_fail_to_open_single_report_with_multiple_reporter(self):
-        report = Report([MockOReporterThatFailsOpen(), MockOReporter(),
-                MockOReporter()], ReportEntryManager())
-        d = report.open()
-        def f(x):
-            self.assertEquals(len(report.reporters),2)
-        d.addCallback(f)
-        return d
-
-    def test_fail_to_open_all_reports_with_multiple_reporter(self):
-        report = Report([MockOReporterThatFailsOpen() for x in xrange(3)],
-                ReportEntryManager())
-        d = report.open()
-        def f(x):
-            self.assertEquals(len(report.reporters),0)
-        d.addCallback(f)
-        return d
-
-    def test_write_report_with_single_reporter_and_succeed(self):
-        #XXX: verify that the MockOReporter writeReportEntry succeeds
-        report = Report([MockOReporter()], ReportEntryManager())
-        report.open()
-        d = report.write(MockMeasurement(MockNetTest()))
-        return d
-
-    def test_write_report_with_single_reporter_and_fail_after_timeout(self):
-        report = Report([MockOReporterThatFailsWrite()], ReportEntryManager())
-        report.open()
-        d = report.write(MockMeasurement(MockNetTest()))
-        def f(err):
-            self.assertEquals(len(report.reporters),0)
-        d.addBoth(f)
-        return d
-
-    def test_write_report_with_single_reporter_and_succeed_after_timeout(self):
-        report = Report([MockOReporterThatFailsWriteOnce()], ReportEntryManager())
-        report.open()
-        d = report.write(MockMeasurement(MockNetTest()))
-        return d
-
-    def test_write_report_with_multiple_reporter_and_succeed(self):
-        report = Report([MockOReporter() for x in xrange(3)], ReportEntryManager())
-        report.open()
-        d = report.write(MockMeasurement(MockNetTest()))
-        return d
-
-    def test_write_report_with_multiple_reporter_and_fail_a_single_reporter(self):
-        report = Report([MockOReporter(), MockOReporter(), MockOReporterThatFailsWrite()], ReportEntryManager())
-        d = report.open()
-
-        self.assertEquals(len(report.reporters),3)
-        d = report.write(MockMeasurement(MockNetTest()))
-
-        def f(x):
-            # one of the reporters should have been removed
-            self.assertEquals(len(report.reporters), 2)
-        d.addBoth(f)
-        return d
-
-    def test_write_report_with_multiple_reporter_and_fail_all_reporter(self):
-        report = Report([MockOReporterThatFailsWrite() for x in xrange(3)], ReportEntryManager())
-        report.open()
-        d = report.write(MockMeasurement(MockNetTest()))
-        def f(err):
-            self.assertEquals(len(report.reporters),0)
-        d.addErrback(f)
-        return d
-
-class TestYAMLReporter(unittest.TestCase):
-    def setUp(self):
-        self.testDetails = {'software_name': 'ooniprobe', 'options':
-        {'pcapfile': None, 'help': 0, 'subargs': ['-f', 'alexa_10'], 'resume':
-        0, 'parallelism': '10', 'no-default-reporter': 0, 'testdeck': None,
-        'test': 'nettests/blocking/http_requests.py', 'logfile': None,
-        'collector': None, 'reportfile': None}, 'test_version': '0.2.3',
-        'software_version': '0.0.10', 'test_name': 'http_requests_test',
-        'start_time': 1362054343.0, 'probe_asn': 'AS0', 'probe_ip':
-        '127.0.0.1', 'probe_cc': 'US'}
-
-    def tearDown(self):
-        pass
-    def test_create_yaml_reporter(self):
-        self.assertIsInstance(YAMLReporter(self.testDetails),
-                YAMLReporter)
-        
-    def test_open_yaml_report_and_succeed(self):
-        r = YAMLReporter(self.testDetails)
-        r.createReport()
-        # verify that testDetails was written to report properly
-        def f(r):
-            r._stream.seek(0)
-            details, = yaml.safe_load_all(r._stream)
-            self.assertEqual(details, self.testDetails)
-        r.created.addCallback(f)
-        return r.created
-
-    #def test_open_yaml_report_and_fail(self):
-    #    #XXX: YAMLReporter does not handle failures of this type
-    #    pass
-
-    def test_write_yaml_report_entry(self):
-        r = YAMLReporter(self.testDetails)
-        r.createReport()
-
-        report_entry = {'foo':'bar', 'bin':'baz'}
-        r.writeReportEntry(report_entry)
-
-        # verify that details and entry were written to report
-        def f(r):
-            r._stream.seek(0)
-            report = yaml.safe_load_all(r._stream)
-            details, entry  = report
-            self.assertEqual(details, self.testDetails)
-            self.assertEqual(entry, report_entry)
-        r.created.addCallback(f)
-        return r.created
-
-    def test_write_multiple_yaml_report_entry(self):
-        r = YAMLReporter(self.testDetails)
-        r.createReport()
-        def reportEntry():
-            for x in xrange(10):
-                yield {'foo':'bar', 'bin':'baz', 'item':x}
-        for entry in reportEntry():
-            r.writeReportEntry(entry)
-        # verify that details and multiple entries were written to report
-        def f(r):
-            r._stream.seek(0)
-            report = yaml.safe_load_all(r._stream)
-            details = report.next()
-            self.assertEqual(details, self.testDetails)
-            self.assertEqual([r for r in report], [r for r in reportEntry()])
-        r.created.addCallback(f)
-        return r.created
-
-    def test_close_yaml_report(self):
-        r = YAMLReporter(self.testDetails)
-        r.createReport()
-        r.finish()
-        self.assertTrue(r._stream.closed)
-
-    def test_write_yaml_report_after_close(self):
-        r = YAMLReporter(self.testDetails)
-        r.createReport()
-        r.finish()
-        def f(r):
-            r.writeReportEntry("foo")
-        r.created.addCallback(f)
-        self.assertFailure(r.created, ReportAlreadyClosed)
-
-    def test_write_yaml_report_before_open(self):
-        r = YAMLReporter(self.testDetails)
-        def f(r):
-            r.writeReportEntry("foo")
-        r.created.addCallback(f)
-        self.assertFailure(r.created, ReportNotCreated)
-
-#class TestOONIBReporter(unittest.TestCase):
-#    def setUp(self):
-#        pass
-#    def tearDown(self):
-#        pass
-#    def test_create_oonib_reporter(self):
-#        raise NotImplementedError
-#    def test_open_oonib_report_and_succeed(self):
-#        raise NotImplementedError
-#    def test_open_oonib_report_and_fail(self):
-#        raise NotImplementedError
-#    def test_write_oonib_report_entry_and_succeed(self):
-#        raise NotImplementedError
-#    def test_write_oonib_report_entry_and_succeed_after_timeout(self):
-#        raise NotImplementedError
-#    def test_write_oonib_report_entry_and_fail_after_timeout(self):
-#        raise NotImplementedError
-#    def test_write_oonib_report_after_close(self):
-#        raise NotImplementedError
-#    def test_write_oonib_report_before_open(self):
-#        raise NotImplementedError
-#    def test_close_oonib_report_and_succeed(self):
-#        raise NotImplementedError
-#    def test_close_oonib_report_and_fail(self):
-#        raise NotImplementedError
diff --git a/tests/test_safe_represent.py b/tests/test_safe_represent.py
deleted file mode 100644
index 82a5196..0000000
--- a/tests/test_safe_represent.py
+++ /dev/null
@@ -1,14 +0,0 @@
-import yaml
-
-from twisted.trial import unittest
-
-from ooni.reporter import OSafeDumper
-
-from scapy.all import IP, UDP
-
-class TestScapyRepresent(unittest.TestCase):
-    def test_represent_scapy(self):
-        data = IP()/UDP()
-        yaml.dump_all([data], Dumper=OSafeDumper)
-
-
diff --git a/tests/test_trueheaders.py b/tests/test_trueheaders.py
deleted file mode 100644
index 9ac0a27..0000000
--- a/tests/test_trueheaders.py
+++ /dev/null
@@ -1,41 +0,0 @@
-from twisted.trial import unittest
-
-from ooni.utils.txagentwithsocks import TrueHeaders
-
-dummy_headers_dict = {
-        'Header1': ['Value1', 'Value2'],
-        'Header2': ['ValueA', 'ValueB']
-}
-
-dummy_headers_dict2 = {
-        'Header1': ['Value1', 'Value2'],
-        'Header2': ['ValueA', 'ValueB'],
-        'Header3': ['ValueA', 'ValueB'],
-}
-
-dummy_headers_dict3 = {
-        'Header1': ['Value1', 'Value2'],
-        'Header2': ['ValueA', 'ValueB'],
-        'Header4': ['ValueA', 'ValueB'],
-}
-
-
-class TestTrueHeaders(unittest.TestCase):
-    def test_names_match(self):
-        th = TrueHeaders(dummy_headers_dict)
-        self.assertEqual(th.getDiff(TrueHeaders(dummy_headers_dict)), set())
-
-    def test_names_not_match(self):
-        th = TrueHeaders(dummy_headers_dict)
-        self.assertEqual(th.getDiff(TrueHeaders(dummy_headers_dict2)), set(['Header3']))
-
-        th = TrueHeaders(dummy_headers_dict3)
-        self.assertEqual(th.getDiff(TrueHeaders(dummy_headers_dict2)), set(['Header3', 'Header4']))
-
-    def test_names_match_expect_ignore(self):
-        th = TrueHeaders(dummy_headers_dict)
-        self.assertEqual(th.getDiff(TrueHeaders(dummy_headers_dict2), ignore=['Header3']), set())
-
-
-
-
diff --git a/tests/test_utils.py b/tests/test_utils.py
deleted file mode 100644
index cc648e0..0000000
--- a/tests/test_utils.py
+++ /dev/null
@@ -1,20 +0,0 @@
-import unittest
-from ooni.utils import pushFilenameStack
-
-class TestUtils(unittest.TestCase):
-    def test_pushFilenameStack(self):
-        f = open("dummyfile", "w+")
-        f.write("0\n")
-        f.close()
-        for i in xrange(1, 5):
-            f = open("dummyfile.%s" % i, "w+")
-            f.write("%s\n" % i)
-            f.close()
-
-        pushFilenameStack("dummyfile")
-        for i in xrange(1, 5):
-            f = open("dummyfile.%s" % i)
-            c = f.readlines()[0].strip()
-            self.assertEqual(str(i-1), str(c))
-            f.close()
-





More information about the tor-commits mailing list