[Author Prev][Author Next][Thread Prev][Thread Next][Author Index][Thread Index]
[tor-commits] [stem/master] Moving tutorial tests to be unit tests
commit eb28305879647bd628794674517da4377c127819
Author: Damian Johnson <atagar@xxxxxxxxxxxxxx>
Date: Sun Oct 14 13:20:16 2012 -0700
Moving tutorial tests to be unit tests
Both of our integ tests for the tutorial examples were a bit clunky, the
controller test because it could only run with newish tor versions and the
descriptor test because it had to make use of metrics descriptors. Oh, and they
didn't actually assert anything.
Moving the tests to be unit tests instead. This included greatly expanding our
mocking capabilities, supporting arbitrary mock objects. I'm not sure if the
end result is any better than our prior integ tests since we're mocking pretty
much everything (and in the case of the descriptor test it's pretty ugly). On
the other hand the tutorial tests are primarily for basic syntax and
compatability with our current version of stem so guess it's ok...
All this said, I *really* like our arbitrary object mocking capability...
---
docs/index.rst | 1 +
run_tests.py | 2 +
test/__init__.py | 1 +
test/integ/control/controller.py | 25 ---------
test/integ/descriptor/server_descriptor.py | 29 ----------
test/mocking.py | 38 +++++++++++++-
test/unit/tutorial.py | 80 ++++++++++++++++++++++++++++
7 files changed, 121 insertions(+), 55 deletions(-)
diff --git a/docs/index.rst b/docs/index.rst
index d6d7f01..35806d2 100644
--- a/docs/index.rst
+++ b/docs/index.rst
@@ -52,6 +52,7 @@ You'll need to restart Tor or issue a SIGHUP for these new settings to take effe
bytes_written = controller.get_info("traffic/written")
print "My Tor relay has read %s bytes and written %s." % (bytes_read, bytes_written)
+ controller.close()
::
diff --git a/run_tests.py b/run_tests.py
index ef172bf..c891bf6 100755
--- a/run_tests.py
+++ b/run_tests.py
@@ -42,6 +42,7 @@ import test.unit.util.tor_tools
import test.unit.exit_policy.policy
import test.unit.exit_policy.rule
import test.unit.version
+import test.unit.tutorial
import test.integ.connection.authentication
import test.integ.connection.connect
import test.integ.control.base_controller
@@ -127,6 +128,7 @@ UNIT_TESTS = (
test.unit.exit_policy.rule.TestExitPolicyRule,
test.unit.exit_policy.policy.TestExitPolicy,
test.unit.version.TestVersion,
+ test.unit.tutorial.TestTutorial,
test.unit.response.control_message.TestControlMessage,
test.unit.response.control_line.TestControlLine,
test.unit.response.getinfo.TestGetInfoResponse,
diff --git a/test/__init__.py b/test/__init__.py
index e46822c..e7add66 100644
--- a/test/__init__.py
+++ b/test/__init__.py
@@ -8,6 +8,7 @@ __all__ = [
"output",
"prompt",
"runner",
+ "tutorial",
"utils",
]
diff --git a/test/integ/control/controller.py b/test/integ/control/controller.py
index 36ac14a..bd17703 100644
--- a/test/integ/control/controller.py
+++ b/test/integ/control/controller.py
@@ -18,31 +18,6 @@ import test.runner
import test.util
class TestController(unittest.TestCase):
- def test_tutorial(self):
- """
- Tests the tutorial from our front page.
- """
-
- if test.runner.require_control(self): return
- if test.runner.require_version(self, stem.version.Version("0.2.3.1")): return # uses a relatively new feature
- elif not test.runner.Torrc.PORT in test.runner.get_runner().get_options():
- test.runner.skip(self, "no port")
- return
-
- from stem.control import Controller
-
- controller = Controller.from_port(control_port = test.runner.CONTROL_PORT)
-
- try:
- controller.authenticate(test.runner.CONTROL_PASSWORD)
-
- bytes_read = controller.get_info("traffic/read")
- bytes_written = controller.get_info("traffic/written")
-
- printed_msg = "My Tor relay has read %s bytes and written %s." % (bytes_read, bytes_written)
- finally:
- controller.close()
-
def test_from_port(self):
"""
Basic sanity check for the from_port constructor.
diff --git a/test/integ/descriptor/server_descriptor.py b/test/integ/descriptor/server_descriptor.py
index 0790131..55e6545 100644
--- a/test/integ/descriptor/server_descriptor.py
+++ b/test/integ/descriptor/server_descriptor.py
@@ -16,35 +16,6 @@ import test.runner
import test.integ.descriptor
class TestServerDescriptor(unittest.TestCase):
- def test_tutorial(self):
- """
- Runs the tutorial example for parsing server descriptors. We use our
- metrics consensus rather than the cached one so it won't take overly long.
- """
-
- from stem.descriptor.reader import DescriptorReader
-
- bw_to_relay = {} # mapping of observed bandwidth to the relay nicknames
-
- descriptor_path = test.integ.descriptor.get_resource("example_descriptor")
- with DescriptorReader([descriptor_path]) as reader:
- for desc in reader:
- if desc.exit_policy.is_exiting_allowed():
- bw_to_relay.setdefault(desc.observed_bandwidth, []).append(desc.nickname)
-
- sorted_bw = sorted(bw_to_relay.keys(), reverse = True)
-
- # prints the top fifteen relays
-
- count = 1
- for bw_value in sorted_bw:
- for nickname in bw_to_relay[bw_value]:
- printed_line = "%i. %s (%i bytes/s)" % (count, nickname, bw_value)
- count += 1
-
- if count > 15:
- return
-
def test_metrics_descriptor(self):
"""
Parses and checks our results against a server descriptor from metrics.
diff --git a/test/mocking.py b/test/mocking.py
index 1a8aa41..4cfe91a 100644
--- a/test/mocking.py
+++ b/test/mocking.py
@@ -11,6 +11,7 @@ calling :func:`test.mocking.revert_mocking`.
get_real_function - provides the non-mocked version of a function
get_all_combinations - provides all combinations of attributes
support_with - makes object be compatable for use via the 'with' keyword
+ get_object - get an abitrary mock object of any class
Mocking Functions
no_op - does nothing
@@ -211,11 +212,15 @@ def return_for_args(args_to_return_value, default = None):
"""
def _return_value(*args):
+ # strip off the 'self' for mock clases
+ if args and 'MockClass' in str(type(args[0])):
+ args = args[1:] if len(args) > 2 else args[1]
+
if args in args_to_return_value:
return args_to_return_value[args]
elif default is None:
arg_label = ", ".join([str(v) for v in args])
- raise ValueError("Unrecognized argument sent for return_for_args(): %s" % arg_label)
+ raise ValueError("Unrecognized argument sent for return_for_args(). Got '%s' but we only recognize '%s'." % (arg_label, ", ".join(args_to_return_value.keys())))
else:
return default(args)
@@ -384,6 +389,37 @@ def get_all_combinations(attr, include_empty = False):
seen.add(item)
yield item
+def get_object(object_class, methods = None):
+ """
+ Provides a mock Controller instance. Its methods are mocked with the given
+ replacements, and calling any others will result in an exception.
+
+ :param class object_class: class that we're making an instance of
+ :param dict methods: mapping of method names to their mocked implementation
+
+ :returns: stem.control.Controller instance
+ """
+
+ if methods is None:
+ methods = {}
+
+ mock_methods = {}
+
+ for method_name in dir(object_class):
+ if method_name in methods:
+ mock_methods[method_name] = methods[method_name]
+ elif method_name.startswith('__') and method_name.endswith('__'):
+ pass # messing with most private methods makes for a broken mock object
+ else:
+ mock_methods[method_name] = raise_exception(ValueError("Unexpected call of '%s' on a mock object" % method_name))
+
+ # makes it so our constructor won't need any arguments
+ mock_methods['__init__'] = no_op()
+
+ mock_class = type('MockClass', (object_class,), mock_methods)
+
+ return mock_class()
+
def get_message(content, reformat = True):
"""
Provides a ControlMessage with content modified to be parsable. This makes
diff --git a/test/unit/tutorial.py b/test/unit/tutorial.py
new file mode 100644
index 0000000..13ef645
--- /dev/null
+++ b/test/unit/tutorial.py
@@ -0,0 +1,80 @@
+"""
+Tests for the examples given in stem's tutorial.
+"""
+
+from __future__ import with_statement
+import unittest
+
+import test.mocking as mocking
+
+class TestTutorial(unittest.TestCase):
+ def tearDown(self):
+ mocking.revert_mocking()
+
+ def test_the_little_relay_that_could(self):
+ from stem.control import Controller
+
+ controller = mocking.get_object(Controller, {
+ 'authenticate': mocking.no_op(),
+ 'close': mocking.no_op(),
+ 'get_info': mocking.return_for_args({
+ 'traffic/read': '1234',
+ 'traffic/written': '5678',
+ }),
+ })
+
+ controller.authenticate()
+
+ bytes_read = controller.get_info("traffic/read")
+ bytes_written = controller.get_info("traffic/written")
+
+ expected_line = "My Tor relay has read 1234 bytes and written 5678."
+ printed_line = "My Tor relay has read %s bytes and written %s." % (bytes_read, bytes_written)
+ self.assertEqual(expected_line, printed_line)
+
+ controller.close()
+
+ def test_mirror_mirror_on_the_wall(self):
+ from stem.descriptor.server_descriptor import RelayDescriptor
+ from stem.descriptor.reader import DescriptorReader
+
+ exit_descriptor = RelayDescriptor(mocking.get_relay_server_descriptor({
+ 'router': 'speedyexit 149.255.97.109 9001 0 0'
+ }, content = True).replace('reject *:*', 'accept *:*'))
+
+ reader_wrapper = mocking.get_object(DescriptorReader, {
+ '__enter__': lambda x: x,
+ '__exit__': mocking.no_op(),
+ '__iter__': mocking.return_value(iter((
+ exit_descriptor,
+ mocking.get_relay_server_descriptor(), # non-exit
+ exit_descriptor,
+ exit_descriptor,
+ )))
+ })
+
+ bw_to_relay = {} # mapping of observed bandwidth to the relay nicknames
+
+ with reader_wrapper as reader:
+ for desc in reader:
+ if desc.exit_policy.is_exiting_allowed():
+ bw_to_relay.setdefault(desc.observed_bandwidth, []).append(desc.nickname)
+
+ sorted_bw = sorted(bw_to_relay.keys(), reverse = True)
+
+ # prints the top fifteen relays
+
+ count = 1
+ for bw_value in sorted_bw:
+ for nickname in bw_to_relay[bw_value]:
+ expected_line = "%i. speedyexit (104590 bytes/s)" % count
+ printed_line = "%i. %s (%i bytes/s)" % (count, nickname, bw_value)
+ self.assertEqual(expected_line, printed_line)
+
+ count += 1
+
+ if count > 15:
+ return
+
+ self.assertEqual(4, count)
+
_______________________________________________
tor-commits mailing list
tor-commits@xxxxxxxxxxxxxxxxxxxx
https://lists.torproject.org/cgi-bin/mailman/listinfo/tor-commits