[tor-commits] [ooni-probe/master] Add unittests for oonicli.
art at torproject.org
art at torproject.org
Fri Mar 7 15:45:36 UTC 2014
commit 17bf4d37a0b7d2c85378922da529f61502126819
Author: Arturo Filastò <art at fuffa.org>
Date: Sat Mar 1 18:59:20 2014 +0100
Add unittests for oonicli.
Allows to programatically run nettests.
---
.travis.yml | 1 +
bin/ooniprobe | 6 ++-
ooni/director.py | 16 +++---
ooni/nettest.py | 11 ++--
ooni/oonicli.py | 31 +++++-------
ooni/reporter.py | 11 ++--
ooni/tasks.py | 8 +--
ooni/tests/__init__.py | 1 +
ooni/tests/mocks.py | 3 --
ooni/tests/test_director.py | 48 ++++++++++--------
ooni/tests/test_oonicli.py | 115 ++++++++++++++++++++++++++++++++++++++++++
ooni/tests/test_templates.py | 4 --
ooni/utils/log.py | 37 +++++++++-----
13 files changed, 210 insertions(+), 82 deletions(-)
diff --git a/.travis.yml b/.travis.yml
index 718e405..6bce942 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -1,6 +1,7 @@
language: python
before_install:
- sudo apt-get install tor libpcap-dev libgeoip-dev
+ - sudo /etc/init.d/tor start
python:
- "2.7"
# command to install dependencies
diff --git a/bin/ooniprobe b/bin/ooniprobe
index 695b137..5e277e8 100755
--- a/bin/ooniprobe
+++ b/bin/ooniprobe
@@ -14,4 +14,8 @@ copy_reg._reduce_ex = patched_reduce_ex
# from ooni.oonicli import run
# run()
from ooni.oonicli import runWithDirector
-runWithDirector()
+d = runWithDirector()
+ at d.addBoth
+def cb(result):
+ reactor.stop()
+reactor.run()
diff --git a/ooni/director.py b/ooni/director.py
index f3d646a..acf50cc 100644
--- a/ooni/director.py
+++ b/ooni/director.py
@@ -217,7 +217,6 @@ class Director(object):
net_test_loader:
an instance of :class:ooni.nettest.NetTestLoader
"""
-
if config.privacy.includepcap:
if not config.reports.pcap:
config.reports.pcap = config.generatePcapFilename(net_test_loader.testDetails)
@@ -231,14 +230,14 @@ class Director(object):
yield net_test.report.open()
yield net_test.initializeInputProcessor()
- self.measurementManager.schedule(net_test.generateMeasurements())
-
- self.activeNetTests.append(net_test)
-
- yield net_test.done
- yield report.close()
+ try:
+ self.activeNetTests.append(net_test)
+ self.measurementManager.schedule(net_test.generateMeasurements())
- self.netTestDone(net_test)
+ yield net_test.done
+ yield report.close()
+ finally:
+ self.netTestDone(net_test)
def startSniffing(self):
""" Start sniffing with Scapy. Exits if required privileges (root) are not
@@ -287,6 +286,7 @@ class Director(object):
Called when we read from stdout that Tor has reached 100%.
"""
log.debug("Building a TorState")
+ config.tor.protocol = proto
state = TorState(proto.tor_protocol)
state.post_bootstrap.addCallback(state_complete)
state.post_bootstrap.addErrback(setup_failed)
diff --git a/ooni/nettest.py b/ooni/nettest.py
index 943aeba..774c962 100644
--- a/ooni/nettest.py
+++ b/ooni/nettest.py
@@ -495,6 +495,9 @@ class NetTest(object):
self.done = defer.Deferred()
self.state = NetTestState(self.done)
+
+ def __str__(self):
+ return ' '.join(tc.name for tc, _ in self.testCases)
def doneReport(self, report_results):
"""
@@ -642,7 +645,6 @@ class NetTestCase(object):
inputFilename = None
report = {}
- report['errors'] = []
usageOptions = usage.Options
@@ -660,6 +662,7 @@ class NetTestCase(object):
This is the internal setup method to be overwritten by templates.
"""
self.report = {}
+ self.inputs = None
def setUp(self):
"""
@@ -742,13 +745,13 @@ class NetTestCase(object):
a generator that will yield one item from the file based on the
inputProcessor.
"""
- if self.inputs:
- return self.inputs
-
if self.inputFileSpecified:
self.inputFilename = self.localOptions[self.inputFile[0]]
return self.inputProcessor(self.inputFilename)
+ if self.inputs:
+ return self.inputs
+
return None
def _checkValidOptions(self):
diff --git a/ooni/oonicli.py b/ooni/oonicli.py
index 904f7fb..f75b31c 100644
--- a/ooni/oonicli.py
+++ b/ooni/oonicli.py
@@ -92,16 +92,7 @@ def parseOptions():
return dict(cmd_line_options)
-def shutdown(result):
- """
- This will get called once all the operations that need to be done in the
- current oonicli session have been completed.
- """
- log.debug("Halting reactor")
- try: reactor.stop()
- except: pass
-
-def runWithDirector():
+def runWithDirector(logging=True, start_tor=True):
"""
Instance the director, parse command line options and start an ooniprobe
test!
@@ -110,8 +101,11 @@ def runWithDirector():
config.global_options = global_options
config.set_paths()
config.read_config_file()
+ if not start_tor:
+ config.advanced.start_tor = False
- log.start(global_options['logfile'])
+ if logging:
+ log.start(global_options['logfile'])
if config.privacy.includepcap:
try:
@@ -172,7 +166,7 @@ def runWithDirector():
sys.exit(2)
def setup_nettest(_):
- try:
+ try:
return deck.setup()
except errors.UnableToLoadDeckInput as error:
return defer.failure.Failure(error)
@@ -241,7 +235,8 @@ def runWithDirector():
raise errors.TorNotRunning
test_details = net_test_loader.testDetails
- yaml_reporter = YAMLReporter(test_details)
+ yaml_reporter = YAMLReporter(test_details,
+ report_filename=global_options['reportfile'])
reporters = [yaml_reporter]
if collector:
@@ -252,15 +247,13 @@ def runWithDirector():
except errors.InvalidOONIBCollectorAddress, e:
raise e
- log.debug("adding callback for startNetTest")
- director.startNetTest(net_test_loader, reporters)
-
- director.allTestsDone.addBoth(shutdown)
+ netTestDone = director.startNetTest(net_test_loader, reporters)
+ return netTestDone
def start():
d.addCallback(setup_nettest)
d.addCallback(post_director_start)
d.addErrback(director_startup_failed)
+ return d
- reactor.callWhenRunning(start)
- reactor.run()
+ return start()
diff --git a/ooni/reporter.py b/ooni/reporter.py
index c8d0748..3159428 100644
--- a/ooni/reporter.py
+++ b/ooni/reporter.py
@@ -165,15 +165,16 @@ class YAMLReporter(OReporter):
the destination directory of the report
"""
- def __init__(self, test_details, report_destination='.'):
+ def __init__(self, test_details, report_destination='.', report_filename=None):
self.reportDestination = report_destination
if not os.path.isdir(report_destination):
raise InvalidDestination
-
- report_filename = "report-" + \
- test_details['test_name'] + "-" + \
- otime.timestamp() + ".yamloo"
+
+ if not report_filename:
+ report_filename = "report-" + \
+ test_details['test_name'] + "-" + \
+ otime.timestamp() + ".yamloo"
report_path = os.path.join(self.reportDestination, report_filename)
diff --git a/ooni/tasks.py b/ooni/tasks.py
index efca9b0..578d8a9 100644
--- a/ooni/tasks.py
+++ b/ooni/tasks.py
@@ -105,11 +105,11 @@ class Measurement(TaskWithTimeout):
"""
self.testInstance = test_instance
self.testInstance.input = test_input
+ self.testInstance._setUp()
if 'input' not in self.testInstance.report.keys():
- self.testInstance.report = {'input': test_input}
- self.testInstance._setUp()
- self.testInstance._start_time = time.time()
- self.testInstance.setUp()
+ self.testInstance.report['input'] = test_input
+ self.testInstance._start_time = time.time()
+ self.testInstance.setUp()
self.netTestMethod = getattr(self.testInstance, test_method)
diff --git a/ooni/tests/__init__.py b/ooni/tests/__init__.py
index 1b5c949..5663ccf 100644
--- a/ooni/tests/__init__.py
+++ b/ooni/tests/__init__.py
@@ -1,3 +1,4 @@
from ooni.settings import config
config.logging = False
+config.advanced.debug = False
diff --git a/ooni/tests/mocks.py b/ooni/tests/mocks.py
index f849344..3fd75aa 100644
--- a/ooni/tests/mocks.py
+++ b/ooni/tests/mocks.py
@@ -1,13 +1,10 @@
from twisted.python import failure
from twisted.internet import defer
-from ooni.settings 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')
diff --git a/ooni/tests/test_director.py b/ooni/tests/test_director.py
index c1ad524..9d86720 100644
--- a/ooni/tests/test_director.py
+++ b/ooni/tests/test_director.py
@@ -6,7 +6,30 @@ from ooni.director import Director
from twisted.internet import defer
from twisted.trial import unittest
+from txtorcon import TorControlProtocol
+proto = MagicMock()
+proto.tor_protocol = TorControlProtocol()
+
+mock_TorState = MagicMock()
+# We use the instance of mock_TorState so that the mock caching will
+# return the same instance when TorState is created.
+mts = mock_TorState()
+mts.protocol.get_conf = lambda x: defer.succeed({'SocksPort': '4242'})
+mts.post_bootstrap = defer.succeed(mts)
+
+# Set the tor_protocol to be already fired
+state = MagicMock()
+proto.tor_protocol.post_bootstrap = defer.succeed(state)
+
+mock_launch_tor = MagicMock()
+mock_launch_tor.return_value = defer.succeed(proto)
+
class TestDirector(unittest.TestCase):
+ def tearDown(self):
+ config.tor_state = None
+ config.tor.socks_port = None
+ config.tor.control_port = None
+
def test_get_net_tests(self):
director = Director()
nettests = director.getNetTests()
@@ -14,34 +37,15 @@ class TestDirector(unittest.TestCase):
assert 'dnsconsistency' in nettests
assert 'http_header_field_manipulation' in nettests
assert 'traceroute' in nettests
-
+
+ @patch('ooni.director.TorState', mock_TorState)
+ @patch('ooni.director.launch_tor', mock_launch_tor)
def test_start_tor(self):
- from txtorcon import TorControlProtocol
- proto = MagicMock()
- proto.tor_protocol = TorControlProtocol()
-
- mock_TorState = MagicMock()
- # We use the instance of mock_TorState so that the mock caching will
- # return the same instance when TorState is created.
- mts = mock_TorState()
- mts.protocol.get_conf = lambda x: defer.succeed({'SocksPort': '4242'})
- mts.post_bootstrap = defer.succeed(mts)
-
- # Set the tor_protocol to be already fired
- state = MagicMock()
- proto.tor_protocol.post_bootstrap = defer.succeed(state)
-
- mock_launch_tor = MagicMock()
- mock_launch_tor.return_value = defer.succeed(proto)
-
- @patch('ooni.director.TorState', mock_TorState)
- @patch('ooni.director.launch_tor', mock_launch_tor)
@defer.inlineCallbacks
def director_start_tor():
director = Director()
yield director.startTor()
assert config.tor.socks_port == 4242
assert config.tor.control_port == 4242
- config.tor_state = None
return director_start_tor()
diff --git a/ooni/tests/test_oonicli.py b/ooni/tests/test_oonicli.py
new file mode 100644
index 0000000..ab10000
--- /dev/null
+++ b/ooni/tests/test_oonicli.py
@@ -0,0 +1,115 @@
+import os
+import sys
+import yaml
+import signal
+
+from twisted.internet import base, defer
+from twisted.trial import unittest
+
+from ooni.settings import config
+from ooni.oonicli import runWithDirector
+
+def verify_header(header):
+ assert 'input_hashes' in header.keys()
+ assert 'options' in header.keys()
+ assert 'probe_asn' in header.keys()
+ assert 'probe_cc' in header.keys()
+ assert 'probe_ip' in header.keys()
+ assert 'software_name' in header.keys()
+ assert 'software_version' in header.keys()
+ assert 'test_name' in header.keys()
+ assert 'test_version' in header.keys()
+
+def verify_entry(entry):
+ assert 'input' in entry
+
+
+class TestRunDirector(unittest.TestCase):
+ def setUp(self):
+ config.tor.socks_port = 9050
+ config.tor.control_port = None
+ with open('example-input.txt', 'w+') as f:
+ f.write('http://torproject.org/\n')
+ f.write('http://bridges.torproject.org/\n')
+ f.write('http://blog.torproject.org/\n')
+
+ def tearDown(self):
+ os.remove('test_report.yaml')
+ os.remove('example-input.txt')
+
+ @defer.inlineCallbacks
+ def run_test(self, test_name, args, verify_function):
+ output_file = 'test_report.yaml'
+ sys.argv = ['', '-n', '-o', output_file, test_name]
+ sys.argv.extend(args)
+ yield runWithDirector(False, False)
+ with open(output_file) as f:
+ entries = yaml.safe_load_all(f)
+ header = entries.next()
+ try:
+ first_entry = entries.next()
+ except StopIteration:
+ raise Exception("Missing entry in report")
+ verify_header(header)
+ verify_entry(first_entry)
+ verify_function(first_entry)
+
+ @defer.inlineCallbacks
+ def test_http_requests(self):
+ def verify_function(entry):
+ assert 'body_length_match' in entry
+ assert 'body_proportion' in entry
+ assert 'control_failure' in entry
+ assert 'experiment_failure' in entry
+ assert 'factor' in entry
+ assert 'headers_diff' in entry
+ assert 'headers_match' in entry
+ yield self.run_test('blocking/http_requests',
+ ['-u', 'http://torproject.org/'],
+ verify_function)
+
+ @defer.inlineCallbacks
+ def test_http_requests_with_file(self):
+ def verify_function(entry):
+ assert 'body_length_match' in entry
+ assert 'body_proportion' in entry
+ assert 'control_failure' in entry
+ assert 'experiment_failure' in entry
+ assert 'factor' in entry
+ assert 'headers_diff' in entry
+ assert 'headers_match' in entry
+ yield self.run_test('blocking/http_requests',
+ ['-f', 'example-input.txt'],
+ verify_function)
+
+ @defer.inlineCallbacks
+ def test_dnsconsistency(self):
+ def verify_function(entry):
+ assert 'queries' in entry
+ assert 'control_resolver' in entry
+ assert 'tampering' in entry
+ assert len(entry['tampering']) == 1
+ yield self.run_test('blocking/dnsconsistency',
+ ['-b', '8.8.8.8:53',
+ '-t', '8.8.8.8',
+ '-f', 'example-input.txt'],
+ verify_function)
+
+ @defer.inlineCallbacks
+ def test_http_header_field_manipulation(self):
+ def verify_function(entry):
+ assert 'agent' in entry
+ assert 'requests' in entry
+ assert 'socksproxy' in entry
+ assert 'tampering' in entry
+ assert 'header_field_name' in entry['tampering']
+ assert 'header_field_number' in entry['tampering']
+ assert 'header_field_value' in entry['tampering']
+ assert 'header_name_capitalization' in entry['tampering']
+ assert 'header_name_diff' in entry['tampering']
+ assert 'request_line_capitalization' in entry['tampering']
+ assert 'total' in entry['tampering']
+
+ yield self.run_test('manipulation/http_header_field_manipulation',
+ ['-b', 'http://64.9.225.221'],
+ verify_function)
diff --git a/ooni/tests/test_templates.py b/ooni/tests/test_templates.py
index 987c7fa..66960c3 100644
--- a/ooni/tests/test_templates.py
+++ b/ooni/tests/test_templates.py
@@ -1,13 +1,9 @@
-from ooni.settings import config
-
from ooni.templates import httpt
from twisted.internet.error import DNSLookupError
from twisted.internet import reactor, defer
from twisted.trial import unittest
-config.logging = False
-
class TestHTTPT(unittest.TestCase):
def setUp(self):
from twisted.web.resource import Resource
diff --git a/ooni/utils/log.py b/ooni/utils/log.py
index e0ad5d9..379b5c7 100644
--- a/ooni/utils/log.py
+++ b/ooni/utils/log.py
@@ -24,25 +24,38 @@ class LogWithNoPrefix(txlog.FileLogObserver):
util.untilConcludes(self.write, "%s\n" % text)
util.untilConcludes(self.flush) # Hoorj!
-def start(logfile=None, application_name="ooniprobe"):
- daily_logfile = None
+class OONILogger(object):
+ def start(self, logfile=None, application_name="ooniprobe"):
+ daily_logfile = None
+
+ if not logfile:
+ logfile = os.path.expanduser(config.basic.logfile)
+
+ log_folder = os.path.dirname(logfile)
+ log_filename = os.path.basename(logfile)
- if not logfile:
- logfile = os.path.expanduser(config.basic.logfile)
+ daily_logfile = DailyLogFile(log_filename, log_folder)
- log_folder = os.path.dirname(logfile)
- log_filename = os.path.basename(logfile)
+ txlog.msg("Starting %s on %s (%s UTC)" % (application_name, otime.prettyDateNow(),
+ otime.utcPrettyDateNow()))
+
+ self.fileObserver = txlog.FileLogObserver(daily_logfile)
+ self.stdoutObserver = LogWithNoPrefix(sys.stdout)
- daily_logfile = DailyLogFile(log_filename, log_folder)
+ txlog.startLoggingWithObserver(self.stdoutObserver.emit)
+ txlog.addObserver(self.fileObserver.emit)
- txlog.msg("Starting %s on %s (%s UTC)" % (application_name, otime.prettyDateNow(),
- otime.utcPrettyDateNow()))
+ def stop(self):
+ self.stdoutObserver.stop()
+ self.fileObserver.stop()
- txlog.startLoggingWithObserver(LogWithNoPrefix(sys.stdout).emit)
- txlog.addObserver(txlog.FileLogObserver(daily_logfile).emit)
+oonilogger = OONILogger()
+
+def start(logfile=None, application_name="ooniprobe"):
+ oonilogger.start(logfile, application_name)
def stop():
- print "Stopping OONI"
+ oonilogger.stop()
def msg(msg, *arg, **kw):
if config.logging:
More information about the tor-commits
mailing list