[tor-commits] [bridgedb/master] Add unittests for bridgedb.Main module.
isis at torproject.org
isis at torproject.org
Sat Mar 21 02:03:02 UTC 2015
commit 2033d83393460d92c8315a9fd8b8b24a534456a9
Author: Isis Lovecruft <isis at torproject.org>
Date: Sun Feb 22 00:18:08 2015 +0000
Add unittests for bridgedb.Main module.
---
lib/bridgedb/test/test_Main.py | 429 ++++++++++++++++++++++++++++++++++++++++
1 file changed, 429 insertions(+)
diff --git a/lib/bridgedb/test/test_Main.py b/lib/bridgedb/test/test_Main.py
new file mode 100644
index 0000000..7525b72
--- /dev/null
+++ b/lib/bridgedb/test/test_Main.py
@@ -0,0 +1,429 @@
+# -*- coding: utf-8 -*-
+#
+# This file is part of BridgeDB, a Tor bridge distribution system.
+#
+# :authors: Isis Lovecruft 0xA3ADB67A2CDB8B35 <isis at torproject.org>
+# please also see AUTHORS file
+# :copyright: (c) 2013-2015 Isis Lovecruft
+# (c) 2007-2015, The Tor Project, Inc.
+# (c) 2007-2015, all entities within the AUTHORS file
+# :license: see included LICENSE for information
+
+"""Tests for :mod:`bridgedb.Main`."""
+
+from __future__ import print_function
+
+import base64
+import logging
+import os
+import random
+import shutil
+import sys
+
+from datetime import datetime
+from time import sleep
+
+from twisted.internet.threads import deferToThread
+from twisted.trial import unittest
+
+from bridgedb import Main
+from bridgedb.Bridges import BridgeHolder
+from bridgedb.parse.options import parseOptions
+
+
+logging.getLogger().disabled = True
+
+
+HERE = os.getcwd()
+TOPDIR = HERE.rstrip('_trial_temp')
+CI_RUNDIR = os.path.join(TOPDIR, 'run')
+
+# A networkstatus descriptor with two invalid ORAddress (127.0.0.1 and ::1)
+# and an invalid port number (70000).
+NETWORKSTATUS_MALFORMED = '''\
+r OracleLunacy LBXW03FIKvo9aXCEYbDdq1BbNtM E0yN8ofiBpg6JHW0iPX5gJ1gKFI 2014-09-05 21:39:24 127.0.0.1 70000 0
+a [::1]:70000
+s Fast Guard Running Stable Valid
+w Bandwidth=2094050
+p reject 1-65535
+'''
+
+def mockAddOrUpdateBridgeHistory(bridge, timestamp):
+ """A mocked version of :func:`bridgedb.Stability.addOrUpdateBridgeHistory`
+ which doesn't access the database (so that we can test functions which
+ call it, like :func:`bridgedb.Main.updateBridgeHistory`).
+ """
+ print("Pretending to update Bridge %s with timestamp %s..." %
+ (bridge.fingerprint, timestamp))
+
+
+class MockBridgeHolder(BridgeHolder):
+ def __init__(self):
+ self._bridges = {}
+ def __len__(self):
+ return len(self._bridges.keys())
+ def insert(self, bridge):
+ self._bridges[bridge.fingerprint] = bridge
+
+
+class MainTests(unittest.TestCase):
+ """Integration tests for :func:`bridgedb.Main.load`."""
+
+ def _appendToFile(self, file, data):
+ """Append **data** to **file**."""
+ fh = open(file, 'a')
+ fh.write(data)
+ fh.flush()
+ fh.close()
+
+ def _copyDescFilesHere(self, files):
+ """Copy all the **files** to the _trial_tmp/ directory.
+
+ :param list files: A list of strings representing the paths to
+ descriptor files. This should probably be taken from a
+ ``bridgedb.persistent.Conf`` object which has parsed the
+ ``bridgedb.conf`` file in the top-level directory of this repo.
+ :rtype: list
+ :returns: A list of the new paths (in the ``_trial_tmp`` directory) to
+ the copied descriptor files. This should be used to update the
+ ``bridgedb.persistent.Conf`` object.
+ """
+ updatedPaths = []
+
+ for f in files:
+ base = os.path.basename(f)
+ src = os.path.join(CI_RUNDIR, base)
+ if os.path.isfile(src):
+ dst = os.path.join(HERE, base)
+ shutil.copy(src, dst)
+ updatedPaths.append(dst)
+ else:
+ self.skip = True
+ raise unittest.SkipTest(
+ "Can't find mock descriptor files in %s directory" %
+ CI_RUNDIR)
+
+ return updatedPaths
+
+ def _cbAssertFingerprints(self, d):
+ """Assert that there are some bridges in the splitter."""
+ self.assertGreater(len(self.splitter), 0)
+ return d
+
+ def _cbCallUpdateBridgeHistory(self, d, splitter):
+ """Fake some timestamps for the bridges in the splitter, and then call
+ Main.updateBridgeHistory().
+ """
+ def timestamp():
+ return datetime.fromtimestamp(random.randint(1324285117, 1524285117))
+
+ bridges = splitter._bridges
+ timestamps = {}
+
+ for fingerprint, _ in bridges.items():
+ timestamps[fingerprint] = [timestamp(), timestamp(), timestamp()]
+
+ return Main.updateBridgeHistory(bridges, timestamps)
+
+ def _eb_Failure(self, failure):
+ """If something produces a twisted.python.failure.Failure, fail the
+ test with it.
+ """
+ self.fail(failure)
+
+ def _writeConfig(self, config):
+ """Write a config into the current working directory.
+
+ :param str config: A big long multiline string that looks like the
+ bridgedb.conf file.
+ :rtype: str
+ :returns: The pathname of the file that the **config** was written to.
+ """
+ configFile = os.path.join(os.getcwd(), 'bridgedb.conf')
+ fh = open(configFile, 'w')
+ fh.write(config)
+ fh.flush()
+ fh.close()
+ return configFile
+
+ def setUp(self):
+ """Find the bridgedb.conf file in the top-level directory of this repo,
+ copy it and the descriptor files it references to the current working
+ directory, produce a state object from the loaded bridgedb.conf file,
+ and make an HMAC key.
+ """
+ # Get the bridgedb.conf file in the top-level directory of this repo:
+ self.configFile = os.path.join(TOPDIR, 'bridgedb.conf')
+ self.config = Main.loadConfig(self.configFile)
+
+ # Copy the referenced descriptor files from bridgedb/run/ to CWD:
+ self.config.STATUS_FILE = self._copyDescFilesHere([self.config.STATUS_FILE])[0]
+ self.config.BRIDGE_FILES = self._copyDescFilesHere(self.config.BRIDGE_FILES)
+ self.config.EXTRA_INFO_FILES = self._copyDescFilesHere(self.config.EXTRA_INFO_FILES)
+
+ # Initialise the state
+ self.state = Main.persistent.State(**self.config.__dict__)
+ self.key = base64.b64decode('TvPS1y36BFguBmSOvhChgtXB2Lt+BOw0mGfz9SZe12Y=')
+
+ # Create a BridgeSplitter
+ self.splitter = MockBridgeHolder()
+
+ # Functions which some tests mock, which we'll need to re-replace
+ # later in tearDown():
+ self._orig_addOrUpdateBridgeHistory = Main.addOrUpdateBridgeHistory
+ self._orig_sys_argv = sys.argv
+
+ def tearDown(self):
+ """Replace the mocked mockAddOrUpdateBridgeHistory() function with the
+ real function, Stability.addOrUpdateBridgeHistory().
+ """
+ Main.addOrUpdateBridgeHistory = self._orig_addOrUpdateBridgeHistory
+ sys.argv = self._orig_sys_argv
+
+ def test_Main_updateBridgeHistory(self):
+ """Main.updateBridgeHistory should update some timestamps for some
+ bridges.
+ """
+ # Mock the addOrUpdateBridgeHistory() function so that we don't try to
+ # access the database:
+ Main.addOrUpdateBridgeHistory = mockAddOrUpdateBridgeHistory
+
+ # Get the bridges into the mocked splitter
+ d = deferToThread(Main.load, self.state, self.splitter)
+ d.addCallback(self._cbAssertFingerprints)
+ d.addErrback(self._eb_Failure)
+ d.addCallback(self._cbCallUpdateBridgeHistory, self.splitter)
+ d.addErrback(self._eb_Failure)
+ return d
+
+ def test_Main_load(self):
+ """Main.load() should run without error."""
+ d = deferToThread(Main.load, self.state, self.splitter)
+ d.addCallback(self._cbAssertFingerprints)
+ d.addErrback(self._eb_Failure)
+ return d
+
+ def test_Main_load_no_state(self):
+ """Main.load() should raise SystemExit without a state object."""
+ self.assertRaises(SystemExit, Main.load, None, self.splitter)
+
+ def test_Main_load_clear(self):
+ """When called with clear=True, load() should run and clear the
+ hashrings.
+ """
+ d = deferToThread(Main.load, self.state, self.splitter, clear=True)
+ d.addCallback(self._cbAssertFingerprints)
+ d.addErrback(self._eb_Failure)
+ return d
+
+ def test_Main_load_collect_timestamps(self):
+ """When COLLECT_TIMESTAMPS=True, Main.load() should call
+ Main.updateBridgeHistory().
+ """
+ # Mock the addOrUpdateBridgeHistory() function so that we don't try to
+ # access the database:
+ Main.addOrUpdateBridgeHistory = mockAddOrUpdateBridgeHistory
+ state = self.state
+ state.COLLECT_TIMESTAMPS = True
+
+ # The reactor is deferring this to a thread, so the test execution
+ # here isn't actually covering the Main.updateBridgeHistory()
+ # function:
+ Main.load(state, self.splitter)
+
+ def test_Main_load_malformed_networkstatus(self):
+ """When called with a networkstatus file with an invalid descriptor,
+ Main.load() should raise a ValueError.
+ """
+ self._appendToFile(self.state.STATUS_FILE, NETWORKSTATUS_MALFORMED)
+ self.assertRaises(ValueError, Main.load, self.state, self.splitter)
+
+ def test_Main_reloadFn(self):
+ """Main._reloadFn() should return True."""
+ self.assertTrue(Main._reloadFn())
+
+ def test_Main_handleSIGHUP(self):
+ """Main._handleSIGHUP() should return True."""
+ raise unittest.SkipTest("_handleSIGHUP touches the reactor.")
+
+ self.assertTrue(Main._handleSIGHUP())
+
+ def test_Main_createBridgeRings(self):
+ """Main.createBridgeRings() should add three hashrings to the
+ splitter.
+ """
+ proxyList = None
+ (splitter, emailDist, httpsDist) = Main.createBridgeRings(self.config,
+ proxyList,
+ self.key)
+ # Should have an IPBasedDistributor ring, an EmailDistributor ring,
+ # and an UnallocatedHolder ring:
+ self.assertEqual(len(splitter.ringsByName.keys()), 3)
+
+ def test_Main_createBridgeRings_with_proxyList(self):
+ """Main.createBridgeRings() should add three hashrings to the
+ splitter and add the proxyList to the IPBasedDistibutor.
+ """
+ exitRelays = ['1.1.1.1', '2.2.2.2', '3.3.3.3']
+ proxyList = Main.proxy.ProxySet()
+ proxyList.addExitRelays(exitRelays)
+ (splitter, emailDist, httpsDist) = Main.createBridgeRings(self.config,
+ proxyList,
+ self.key)
+ # Should have an IPBasedDistributor ring, an EmailDistributor ring,
+ # and an UnallocatedHolder ring:
+ self.assertEqual(len(splitter.ringsByName.keys()), 3)
+ self.assertGreater(len(httpsDist.categories), 0)
+ self.assertItemsEqual(exitRelays, httpsDist.categories[-1])
+
+ def test_Main_createBridgeRings_no_https_dist(self):
+ """When HTTPS_DIST=False, Main.createBridgeRings() should add only
+ two hashrings to the splitter.
+ """
+ proxyList = Main.proxy.ProxySet()
+ config = self.config
+ config.HTTPS_DIST = False
+ (splitter, emailDist, httpsDist) = Main.createBridgeRings(config,
+ proxyList,
+ self.key)
+ # Should have an EmailDistributor ring, and an UnallocatedHolder ring:
+ self.assertEqual(len(splitter.ringsByName.keys()), 2)
+ self.assertNotIn('https', splitter.rings)
+ self.assertNotIn(httpsDist, splitter.ringsByName.values())
+
+ def test_Main_createBridgeRings_no_email_dist(self):
+ """When EMAIL_DIST=False, Main.createBridgeRings() should add only
+ two hashrings to the splitter.
+ """
+ proxyList = Main.proxy.ProxySet()
+ config = self.config
+ config.EMAIL_DIST = False
+ (splitter, emailDist, httpsDist) = Main.createBridgeRings(config,
+ proxyList,
+ self.key)
+ # Should have an IPBasedDistributor ring, and an UnallocatedHolder ring:
+ self.assertEqual(len(splitter.ringsByName.keys()), 2)
+ self.assertNotIn('email', splitter.rings)
+ self.assertNotIn(emailDist, splitter.ringsByName.values())
+
+ def test_Main_createBridgeRings_no_reserved_share(self):
+ """When RESERVED_SHARE=0, Main.createBridgeRings() should add only
+ two hashrings to the splitter.
+ """
+ proxyList = Main.proxy.ProxySet()
+ config = self.config
+ config.RESERVED_SHARE = 0
+ (splitter, emailDist, httpsDist) = Main.createBridgeRings(config,
+ proxyList,
+ self.key)
+ # Should have an IPBasedDistributor ring, and an EmailDistributor ring:
+ self.assertEqual(len(splitter.ringsByName.keys()), 2)
+ self.assertNotIn('unallocated', splitter.rings)
+
+ def test_Main_createBridgeRings_two_file_buckets(self):
+ """When FILE_BUCKETS has two filenames in it, Main.createBridgeRings()
+ should add three hashrings to the splitter, then add two
+ "pseudo-rings".
+ """
+ proxyList = Main.proxy.ProxySet()
+ config = self.config
+ config.FILE_BUCKETS = {
+ 'bridges-for-support-desk': 10,
+ 'bridges-for-ooni-tests': 10,
+ }
+ (splitter, emailDist, httpsDist) = Main.createBridgeRings(config,
+ proxyList,
+ self.key)
+ # Should have an IPBasedDistributor ring, an EmailDistributor, and an
+ # UnallocatedHolder ring:
+ self.assertEqual(len(splitter.ringsByName.keys()), 3)
+
+ # Should have two pseudoRings:
+ self.assertEqual(len(splitter.pseudoRings), 2)
+ self.assertIn('pseudo_bridges-for-support-desk', splitter.pseudoRings)
+ self.assertIn('pseudo_bridges-for-ooni-tests', splitter.pseudoRings)
+
+ def test_Main_run(self):
+ """Main.run() should run and then finally raise SystemExit."""
+ config = """
+BRIDGE_FILES = ["../run/bridge-descriptors"]
+EXTRA_INFO_FILES = ["../run/cached-extrainfo", "../run/cached-extrainfo.new"]
+STATUS_FILE = "../run/networkstatus-bridges"
+HTTPS_CERT_FILE="cert"
+HTTPS_KEY_FILE="privkey.pem"
+LOGFILE = "bridgedb.log"
+PIDFILE = "bridgedb.pid"
+DB_FILE = "bridgedist.db"
+DB_LOG_FILE = "bridgedist.log"
+MASTER_KEY_FILE = "secret_key"
+ASSIGNMENTS_FILE = "assignments.log"
+LOGLEVEL = "DEBUG"
+SAFELOGGING = True
+LOGFILE_COUNT = 5
+LOGFILE_ROTATE_SIZE = 10000000
+LOG_THREADS = False
+LOG_TRACE = True
+LOG_TIME_FORMAT = "%H:%M:%S"
+COLLECT_TIMESTAMPS = False
+NO_DISTRIBUTION_COUNTRIES = ['IR', 'SY']
+PROXY_LIST_FILES = []
+N_IP_CLUSTERS = 3
+FORCE_PORTS = [(443, 1)]
+FORCE_FLAGS = [("Stable", 1)]
+BRIDGE_PURPOSE = "bridge"
+TASKS = {'GET_TOR_EXIT_LIST': 3 * 60 * 60,}
+SERVER_PUBLIC_FQDN = 'bridges.torproject.org'
+SERVER_PUBLIC_EXTERNAL_IP = '38.229.72.19'
+HTTPS_DIST = True
+HTTPS_BIND_IP = None
+HTTPS_PORT = None
+HTTPS_N_BRIDGES_PER_ANSWER = 3
+HTTPS_INCLUDE_FINGERPRINTS = True
+HTTPS_USE_IP_FROM_FORWARDED_HEADER = False
+HTTP_UNENCRYPTED_BIND_IP = "127.0.0.1"
+HTTP_UNENCRYPTED_PORT = 55555
+HTTP_USE_IP_FROM_FORWARDED_HEADER = False
+RECAPTCHA_ENABLED = False
+RECAPTCHA_PUB_KEY = ''
+RECAPTCHA_SEC_KEY = ''
+RECAPTCHA_REMOTEIP = ''
+GIMP_CAPTCHA_ENABLED = False
+GIMP_CAPTCHA_DIR = 'captchas'
+GIMP_CAPTCHA_HMAC_KEYFILE = 'captcha_hmac_key'
+GIMP_CAPTCHA_RSA_KEYFILE = 'captcha_rsa_key'
+EMAIL_DIST = True
+EMAIL_FROM_ADDR = "bridges at torproject.org"
+EMAIL_SMTP_FROM_ADDR = "bridges at torproject.org"
+EMAIL_SMTP_HOST = "127.0.0.1"
+EMAIL_SMTP_PORT = 55556
+EMAIL_USERNAME = "bridges"
+EMAIL_DOMAINS = ["somewhere.com", "somewhereelse.net"]
+EMAIL_DOMAIN_MAP = {
+ "mail.somewhere.com": "somewhere.com",
+ "mail.somewhereelse.net": "somewhereelse.net",
+}
+EMAIL_DOMAIN_RULES = {
+ 'somewhere.com': ["ignore_dots", "dkim"],
+ 'somewhereelse.net': ["dkim"],
+}
+EMAIL_WHITELIST = {}
+EMAIL_BLACKLIST = []
+EMAIL_FUZZY_MATCH = 4
+EMAIL_RESTRICT_IPS = []
+EMAIL_BIND_IP = "127.0.0.1"
+EMAIL_PORT = 55557
+EMAIL_N_BRIDGES_PER_ANSWER = 3
+EMAIL_INCLUDE_FINGERPRINTS = True
+EMAIL_GPG_SIGNING_ENABLED = False
+EMAIL_GPG_SIGNING_KEY = '../gnupghome/TESTING.subkeys.sec'
+HTTPS_SHARE = 10
+EMAIL_SHARE = 5
+RESERVED_SHARE = 2
+FILE_BUCKETS = {}"""
+ configFile = self._writeConfig(config)
+
+ # Fake some options:
+ sys.argv = ['bridgedb', '-r', os.getcwd(), '-c', configFile]
+ options = parseOptions()
+
+ self.assertRaises(SystemExit, Main.run, options, reactor=None)
More information about the tor-commits
mailing list