commit 62ecde1d4432eb96a3346289c8257561baa4faff Author: Damian Johnson atagar@torproject.org Date: Fri Nov 25 22:13:56 2011 -0800
Refactoring version functions into module
Moving the last of the types.py contents and a related function from process.py into a module specifically for handling tor versions and requirements (the later part will grow as the library matures). --- run_tests.py | 4 +- stem/__init__.py | 2 +- stem/connection.py | 16 ++-- stem/process.py | 48 ----------- stem/types.py | 98 --------------------- stem/version.py | 156 ++++++++++++++++++++++++++++++++++ test/integ/socket/control_message.py | 7 +- test/unit/connection/protocolinfo.py | 5 +- test/unit/types/__init__.py | 6 -- test/unit/types/version.py | 133 ----------------------------- test/unit/version.py | 133 +++++++++++++++++++++++++++++ 11 files changed, 307 insertions(+), 301 deletions(-)
diff --git a/run_tests.py b/run_tests.py index ba74b66..8fa7107 100755 --- a/run_tests.py +++ b/run_tests.py @@ -13,9 +13,9 @@ import unittest import StringIO
import test.runner +import test.unit.version import test.unit.socket.control_message import test.unit.socket.control_line -import test.unit.types.version import test.unit.connection.protocolinfo import test.unit.util.enum import test.unit.util.system @@ -34,7 +34,7 @@ DIVIDER = "=" * 70 # (name, class) tuples for all of our unit and integration tests UNIT_TESTS = (("stem.socket.ControlMessage", test.unit.socket.control_message.TestControlMessage), ("stem.socket.ControlLine", test.unit.socket.control_line.TestControlLine), - ("stem.types.Version", test.unit.types.version.TestVerion), + ("stem.types.Version", test.unit.version.TestVerion), ("stem.connection.ProtocolInfoResponse", test.unit.connection.protocolinfo.TestProtocolInfoResponse), ("stem.util.enum", test.unit.util.enum.TestEnum), ("stem.util.system", test.unit.util.system.TestSystem), diff --git a/stem/__init__.py b/stem/__init__.py index d4ede49..5b2cdd0 100644 --- a/stem/__init__.py +++ b/stem/__init__.py @@ -4,5 +4,5 @@ Library for working with the tor process.
import stem.util # suppresses log handler warnings
-__all__ = ["connection", "process", "socket", "types"] +__all__ = ["connection", "process", "socket", "version"]
diff --git a/stem/connection.py b/stem/connection.py index ac548aa..ba851a2 100644 --- a/stem/connection.py +++ b/stem/connection.py @@ -21,7 +21,7 @@ import logging import threading
import stem.socket -import stem.types +import stem.version import stem.util.enum import stem.util.system
@@ -176,12 +176,12 @@ class ProtocolInfoResponse(stem.socket.ControlMessage): response, so all other values are None if undefined or empty if a collecion.
Attributes: - protocol_version (int) - protocol version of the response - tor_version (stem.types.Version) - version of the tor process - auth_methods (tuple) - AuthMethod types that tor will accept - unknown_auth_methods (tuple) - strings of unrecognized auth methods - cookie_path (str) - path of tor's authentication cookie - socket (socket.socket) - socket used to make the query + protocol_version (int) - protocol version of the response + tor_version (stem.version.Version) - version of the tor process + auth_methods (tuple) - AuthMethod types that tor will accept + unknown_auth_methods (tuple) - strings of unrecognized auth methods + cookie_path (str) - path of tor's authentication cookie + socket (socket.socket) - socket used to make the query """
def convert(control_message): @@ -302,7 +302,7 @@ class ProtocolInfoResponse(stem.socket.ControlMessage): torversion = line.pop_mapping(True)[1]
try: - self.tor_version = stem.types.Version(torversion) + self.tor_version = stem.version.Version(torversion) except ValueError, exc: raise stem.socket.ProtocolError(exc) else: diff --git a/stem/process.py b/stem/process.py index 2974989..0bb9740 100644 --- a/stem/process.py +++ b/stem/process.py @@ -2,7 +2,6 @@ Helper functions for working with tor as a process. These are mostly os dependent, only working on linux, osx, and bsd.
-get_tor_version - gets the version of our system's tor installation launch_tor - starts up a tor process """
@@ -11,56 +10,9 @@ import os import signal import subprocess
-import stem.types -import stem.util.system - # number of seconds before we time out our attempt to start a tor instance DEFAULT_INIT_TIMEOUT = 90
-# cache for the get_tor_version function -VERSION_CACHE = {} - -def get_tor_version(tor_cmd = "tor"): - """ - Queries tor for its version. - - Arguments: - tor_cmd (str) - command used to run tor - - Returns: - stem.types.Version provided by the tor command - - Raises: - IOError if unable to query or parse the version - """ - - if not tor_cmd in VERSION_CACHE: - try: - version_cmd = "%s --version" % tor_cmd - version_output = stem.util.system.call(version_cmd) - except OSError, exc: - raise IOError(exc) - - if version_output: - # output example: - # Oct 21 07:19:27.438 [notice] Tor v0.2.1.30. This is experimental software. Do not rely on it for strong anonymity. (Running on Linux i686) - # Tor version 0.2.1.30. - - last_line = version_output[-1] - - if last_line.startswith("Tor version ") and last_line.endswith("."): - try: - version_str = last_line[12:-1] - VERSION_CACHE[tor_cmd] = stem.types.Version(version_str) - except ValueError, exc: - raise IOError(exc) - else: - raise IOError("Unexpected response from '%s': %s" % (version_cmd, last_line)) - else: - raise IOError("'%s' didn't have any output" % version_cmd) - - return VERSION_CACHE[tor_cmd] - def launch_tor(torrc_path, completion_percent = 100, init_msg_handler = None, timeout = DEFAULT_INIT_TIMEOUT): """ Initializes a tor process. This blocks until initialization completes or we diff --git a/stem/types.py b/stem/types.py deleted file mode 100644 index 3d62582..0000000 --- a/stem/types.py +++ /dev/null @@ -1,98 +0,0 @@ -""" -Class representations for a variety of tor objects. These are most commonly -return values rather than being instantiated by users directly. - -Version - Tor versioning information. - |- __str__ - string representation - +- __cmp__ - compares with another Version -""" - -import re -import socket -import logging -import threading - -LOGGER = logging.getLogger("stem") - -class Version: - """ - Comparable tor version, as per the 'new version' of the version-spec... - https://gitweb.torproject.org/torspec.git/blob/HEAD:/version-spec.txt - - Attributes: - major (int) - major version - minor (int) - minor version - micro (int) - micro version - patch (int) - optional patch level (None if undefined) - status (str) - optional status tag without the preceding dash such as - 'alpha', 'beta-dev', etc (None if undefined) - """ - - def __init__(self, version_str): - """ - Parses a valid tor version string, for instance "0.1.4" or - "0.2.2.23-alpha". - - Raises: - ValueError if input isn't a valid tor version - """ - - m = re.match(r'^([0-9]+).([0-9]+).([0-9]+)(.[0-9]+)?(-\S*)?$', version_str) - - if m: - major, minor, micro, patch, status = m.groups() - - # The patch and status matches are optional (may be None) and have an extra - # proceeding period or dash if they exist. Stripping those off. - - if patch: patch = int(patch[1:]) - if status: status = status[1:] - - self.major = int(major) - self.minor = int(minor) - self.micro = int(micro) - self.patch = patch - self.status = status - else: raise ValueError("'%s' isn't a properly formatted tor version" % version_str) - - def __str__(self): - """ - Provides the normal representation for the version, for instance: - "0.2.2.23-alpha" - """ - - suffix = "" - - if self.patch: - suffix += ".%i" % self.patch - - if self.status: - suffix += "-%s" % self.status - - return "%i.%i.%i%s" % (self.major, self.minor, self.micro, suffix) - - def __cmp__(self, other): - """ - Simple comparison of versions. An undefined patch level is treated as zero - and status tags are compared lexically (as per the version spec). - """ - - if not isinstance(other, Version): - return 1 # this is also used for equality checks - - for attr in ("major", "minor", "micro", "patch"): - my_version = max(0, self.__dict__[attr]) - other_version = max(0, other.__dict__[attr]) - - if my_version > other_version: return 1 - elif my_version < other_version: return -1 - - my_status = self.status if self.status else "" - other_status = other.status if other.status else "" - - return cmp(my_status, other_status) - -# TODO: version requirements will probably be moved to another module later -REQ_GETINFO_CONFIG_TEXT = Version("0.2.2.7-alpha") -REQ_CONTROL_SOCKET = Version("0.2.0.30") - diff --git a/stem/version.py b/stem/version.py new file mode 100644 index 0000000..d417934 --- /dev/null +++ b/stem/version.py @@ -0,0 +1,156 @@ +""" +Tor versioning information and requirements for its features. These can be +easily parsed and compared, for instance... + +>>> my_version = stem.version.get_system_tor_version() +>>> print my_version +0.2.1.30 +>>> my_version > stem.version.Requirement.CONTROL_SOCKET +True + +get_system_tor_version - gets the version of our system's tor installation +Version - Tor versioning information. + |- __str__ - string representation + +- __cmp__ - compares with another Version + +Requirement - Enumerations for the version requirements of features. + |- GETINFO_CONFIG_TEXT - 'GETINFO config-text' query + +- CONTROL_SOCKET - 'ControlSocket <path>' config option +""" + +import re +import logging + +import stem.util.enum +import stem.util.system + +LOGGER = logging.getLogger("stem") + +# cache for the get_tor_version function +VERSION_CACHE = {} + +def get_system_tor_version(tor_cmd = "tor"): + """ + Queries tor for its version. This is os dependent, only working on linux, + osx, and bsd. + + Arguments: + tor_cmd (str) - command used to run tor + + Returns: + stem.version.Version provided by the tor command + + Raises: + IOError if unable to query or parse the version + """ + + if not tor_cmd in VERSION_CACHE: + try: + version_cmd = "%s --version" % tor_cmd + version_output = stem.util.system.call(version_cmd) + except OSError, exc: + raise IOError(exc) + + if version_output: + # output example: + # Oct 21 07:19:27.438 [notice] Tor v0.2.1.30. This is experimental software. Do not rely on it for strong anonymity. (Running on Linux i686) + # Tor version 0.2.1.30. + + last_line = version_output[-1] + + if last_line.startswith("Tor version ") and last_line.endswith("."): + try: + version_str = last_line[12:-1] + VERSION_CACHE[tor_cmd] = Version(version_str) + except ValueError, exc: + raise IOError(exc) + else: + raise IOError("Unexpected response from '%s': %s" % (version_cmd, last_line)) + else: + raise IOError("'%s' didn't have any output" % version_cmd) + + return VERSION_CACHE[tor_cmd] + +class Version: + """ + Comparable tor version, as per the 'new version' of the version-spec... + https://gitweb.torproject.org/torspec.git/blob/HEAD:/version-spec.txt + + Attributes: + major (int) - major version + minor (int) - minor version + micro (int) - micro version + patch (int) - optional patch level (None if undefined) + status (str) - optional status tag without the preceding dash such as + 'alpha', 'beta-dev', etc (None if undefined) + """ + + def __init__(self, version_str): + """ + Parses a valid tor version string, for instance "0.1.4" or + "0.2.2.23-alpha". + + Raises: + ValueError if input isn't a valid tor version + """ + + m = re.match(r'^([0-9]+).([0-9]+).([0-9]+)(.[0-9]+)?(-\S*)?$', version_str) + + if m: + major, minor, micro, patch, status = m.groups() + + # The patch and status matches are optional (may be None) and have an extra + # proceeding period or dash if they exist. Stripping those off. + + if patch: patch = int(patch[1:]) + if status: status = status[1:] + + self.major = int(major) + self.minor = int(minor) + self.micro = int(micro) + self.patch = patch + self.status = status + else: raise ValueError("'%s' isn't a properly formatted tor version" % version_str) + + def __str__(self): + """ + Provides the normal representation for the version, for instance: + "0.2.2.23-alpha" + """ + + suffix = "" + + if self.patch: + suffix += ".%i" % self.patch + + if self.status: + suffix += "-%s" % self.status + + return "%i.%i.%i%s" % (self.major, self.minor, self.micro, suffix) + + def __cmp__(self, other): + """ + Simple comparison of versions. An undefined patch level is treated as zero + and status tags are compared lexically (as per the version spec). + """ + + if not isinstance(other, Version): + return 1 # this is also used for equality checks + + for attr in ("major", "minor", "micro", "patch"): + my_version = max(0, self.__dict__[attr]) + other_version = max(0, other.__dict__[attr]) + + if my_version > other_version: return 1 + elif my_version < other_version: return -1 + + my_status = self.status if self.status else "" + other_status = other.status if other.status else "" + + return cmp(my_status, other_status) + +Requirement = stem.util.enum.Enum( + ("GETINFO_CONFIG_TEXT", Version("0.2.2.7-alpha")), + ("CONTROL_SOCKET", Version("0.2.0.30")), +) + diff --git a/test/integ/socket/control_message.py b/test/integ/socket/control_message.py index bbcd222..2cdb96c 100644 --- a/test/integ/socket/control_message.py +++ b/test/integ/socket/control_message.py @@ -7,7 +7,7 @@ import socket import unittest
import stem.socket -import stem.types +import stem.version import test.runner
class TestControlMessage(unittest.TestCase): @@ -139,8 +139,9 @@ class TestControlMessage(unittest.TestCase): Parses the 'GETINFO config-text' response. """
- if stem.process.get_tor_version() < stem.types.REQ_GETINFO_CONFIG_TEXT: - self.skipTest("(requires %s)" % stem.types.REQ_GETINFO_CONFIG_TEXT) + req_version = stem.version.Requirement.GETINFO_CONFIG_TEXT + if stem.version.get_system_tor_version() < req_version: + self.skipTest("(requires %s)" % req_version)
# We can't be certain of the order, and there may be extra config-text # entries as per... diff --git a/test/unit/connection/protocolinfo.py b/test/unit/connection/protocolinfo.py index b597981..ecca8b6 100644 --- a/test/unit/connection/protocolinfo.py +++ b/test/unit/connection/protocolinfo.py @@ -4,9 +4,10 @@ Unit tests for the stem.connection.ProtocolInfoResponse class.
import unittest import StringIO + import stem.connection import stem.socket -import stem.types +import stem.version
NO_AUTH = """250-PROTOCOLINFO 1 250-AUTH METHODS=NULL @@ -87,7 +88,7 @@ class TestProtocolInfoResponse(unittest.TestCase): stem.connection.ProtocolInfoResponse.convert(control_message)
self.assertEquals(1, control_message.protocol_version) - self.assertEquals(stem.types.Version("0.2.1.30"), control_message.tor_version) + self.assertEquals(stem.version.Version("0.2.1.30"), control_message.tor_version) self.assertEquals((stem.connection.AuthMethod.NONE, ), control_message.auth_methods) self.assertEquals((), control_message.unknown_auth_methods) self.assertEquals(None, control_message.cookie_path) diff --git a/test/unit/types/__init__.py b/test/unit/types/__init__.py deleted file mode 100644 index 95da593..0000000 --- a/test/unit/types/__init__.py +++ /dev/null @@ -1,6 +0,0 @@ -""" -Unit tests for stem.types. -""" - -__all__ = ["version"] - diff --git a/test/unit/types/version.py b/test/unit/types/version.py deleted file mode 100644 index 067759b..0000000 --- a/test/unit/types/version.py +++ /dev/null @@ -1,133 +0,0 @@ -""" -Unit tests for the stem.types.Version parsing and class. -""" - -import unittest -import stem.types - -class TestVerion(unittest.TestCase): - """ - Tests methods and functions related to 'stem.types.Version'. - """ - - def test_parsing(self): - """ - Tests parsing by the Version class constructor. - """ - - # valid versions with various number of compontents to the version - version = stem.types.Version("0.1.2.3-tag") - self.assert_versions_match(version, 0, 1, 2, 3, "tag") - - version = stem.types.Version("0.1.2.3") - self.assert_versions_match(version, 0, 1, 2, 3, None) - - version = stem.types.Version("0.1.2-tag") - self.assert_versions_match(version, 0, 1, 2, None, "tag") - - version = stem.types.Version("0.1.2") - self.assert_versions_match(version, 0, 1, 2, None, None) - - # checks an empty tag - version = stem.types.Version("0.1.2.3-") - self.assert_versions_match(version, 0, 1, 2, 3, "") - - version = stem.types.Version("0.1.2-") - self.assert_versions_match(version, 0, 1, 2, None, "") - - # checks invalid version strings - self.assertRaises(ValueError, stem.types.Version, "") - self.assertRaises(ValueError, stem.types.Version, "1.2.3.4nodash") - self.assertRaises(ValueError, stem.types.Version, "1.2.3.a") - self.assertRaises(ValueError, stem.types.Version, "1.2.a.4") - self.assertRaises(ValueError, stem.types.Version, "12.3") - self.assertRaises(ValueError, stem.types.Version, "1.-2.3") - - def test_comparison(self): - """ - Tests comparision between Version instances. - """ - - # check for basic incrementing in each portion - self.assert_version_is_greater("1.1.2.3-tag", "0.1.2.3-tag") - self.assert_version_is_greater("0.2.2.3-tag", "0.1.2.3-tag") - self.assert_version_is_greater("0.1.3.3-tag", "0.1.2.3-tag") - self.assert_version_is_greater("0.1.2.4-tag", "0.1.2.3-tag") - self.assert_version_is_greater("0.1.2.3-ugg", "0.1.2.3-tag") - self.assert_version_is_equal("0.1.2.3-tag", "0.1.2.3-tag") - - # checks that a missing patch level equals zero - self.assert_version_is_equal("0.1.2", "0.1.2.0") - self.assert_version_is_equal("0.1.2-tag", "0.1.2.0-tag") - - # checks for missing patch or status - self.assert_version_is_greater("0.1.2.3-tag", "0.1.2.3") - self.assert_version_is_greater("0.1.2.3-tag", "0.1.2-tag") - self.assert_version_is_greater("0.1.2.3-tag", "0.1.2") - - self.assert_version_is_equal("0.1.2.3", "0.1.2.3") - self.assert_version_is_equal("0.1.2", "0.1.2") - - def test_nonversion_comparison(self): - """ - Checks that we can be compared with other types. - """ - - test_version = stem.types.Version("0.1.2.3") - self.assertNotEqual(test_version, None) - self.assertTrue(test_version > None) - - self.assertNotEqual(test_version, 5) - self.assertTrue(test_version > 5) - - def test_string(self): - """ - Tests the Version -> string conversion. - """ - - # checks conversion with various numbers of arguments - - self.assert_string_matches("0.1.2.3-tag") - self.assert_string_matches("0.1.2.3") - self.assert_string_matches("0.1.2") - - def assert_versions_match(self, version, major, minor, micro, patch, status): - """ - Asserts that the values for a types.Version instance match the given - values. - """ - - self.assertEqual(version.major, major) - self.assertEqual(version.minor, minor) - self.assertEqual(version.micro, micro) - self.assertEqual(version.patch, patch) - self.assertEqual(version.status, status) - - def assert_version_is_greater(self, first_version, second_version): - """ - Asserts that the parsed version of the first version is greate than the - second (also checking the inverse). - """ - - version1 = stem.types.Version(first_version) - version2 = stem.types.Version(second_version) - self.assertEqual(version1 > version2, True) - self.assertEqual(version1 < version2, False) - - def assert_version_is_equal(self, first_version, second_version): - """ - Asserts that the parsed version of the first version equals the second. - """ - - version1 = stem.types.Version(first_version) - version2 = stem.types.Version(second_version) - self.assertEqual(version1, version2) - - def assert_string_matches(self, version): - """ - Parses the given version string then checks that its string representation - matches the input. - """ - - self.assertEqual(version, str(stem.types.Version(version))) - diff --git a/test/unit/version.py b/test/unit/version.py new file mode 100644 index 0000000..2845ad3 --- /dev/null +++ b/test/unit/version.py @@ -0,0 +1,133 @@ +""" +Unit tests for the stem.version.Version parsing and class. +""" + +import unittest +import stem.version + +class TestVerion(unittest.TestCase): + """ + Tests methods and functions related to 'stem.version.Version'. + """ + + def test_parsing(self): + """ + Tests parsing by the Version class constructor. + """ + + # valid versions with various number of compontents to the version + version = stem.version.Version("0.1.2.3-tag") + self.assert_versions_match(version, 0, 1, 2, 3, "tag") + + version = stem.version.Version("0.1.2.3") + self.assert_versions_match(version, 0, 1, 2, 3, None) + + version = stem.version.Version("0.1.2-tag") + self.assert_versions_match(version, 0, 1, 2, None, "tag") + + version = stem.version.Version("0.1.2") + self.assert_versions_match(version, 0, 1, 2, None, None) + + # checks an empty tag + version = stem.version.Version("0.1.2.3-") + self.assert_versions_match(version, 0, 1, 2, 3, "") + + version = stem.version.Version("0.1.2-") + self.assert_versions_match(version, 0, 1, 2, None, "") + + # checks invalid version strings + self.assertRaises(ValueError, stem.version.Version, "") + self.assertRaises(ValueError, stem.version.Version, "1.2.3.4nodash") + self.assertRaises(ValueError, stem.version.Version, "1.2.3.a") + self.assertRaises(ValueError, stem.version.Version, "1.2.a.4") + self.assertRaises(ValueError, stem.version.Version, "12.3") + self.assertRaises(ValueError, stem.version.Version, "1.-2.3") + + def test_comparison(self): + """ + Tests comparision between Version instances. + """ + + # check for basic incrementing in each portion + self.assert_version_is_greater("1.1.2.3-tag", "0.1.2.3-tag") + self.assert_version_is_greater("0.2.2.3-tag", "0.1.2.3-tag") + self.assert_version_is_greater("0.1.3.3-tag", "0.1.2.3-tag") + self.assert_version_is_greater("0.1.2.4-tag", "0.1.2.3-tag") + self.assert_version_is_greater("0.1.2.3-ugg", "0.1.2.3-tag") + self.assert_version_is_equal("0.1.2.3-tag", "0.1.2.3-tag") + + # checks that a missing patch level equals zero + self.assert_version_is_equal("0.1.2", "0.1.2.0") + self.assert_version_is_equal("0.1.2-tag", "0.1.2.0-tag") + + # checks for missing patch or status + self.assert_version_is_greater("0.1.2.3-tag", "0.1.2.3") + self.assert_version_is_greater("0.1.2.3-tag", "0.1.2-tag") + self.assert_version_is_greater("0.1.2.3-tag", "0.1.2") + + self.assert_version_is_equal("0.1.2.3", "0.1.2.3") + self.assert_version_is_equal("0.1.2", "0.1.2") + + def test_nonversion_comparison(self): + """ + Checks that we can be compared with other types. + """ + + test_version = stem.version.Version("0.1.2.3") + self.assertNotEqual(test_version, None) + self.assertTrue(test_version > None) + + self.assertNotEqual(test_version, 5) + self.assertTrue(test_version > 5) + + def test_string(self): + """ + Tests the Version -> string conversion. + """ + + # checks conversion with various numbers of arguments + + self.assert_string_matches("0.1.2.3-tag") + self.assert_string_matches("0.1.2.3") + self.assert_string_matches("0.1.2") + + def assert_versions_match(self, version, major, minor, micro, patch, status): + """ + Asserts that the values for a types.Version instance match the given + values. + """ + + self.assertEqual(version.major, major) + self.assertEqual(version.minor, minor) + self.assertEqual(version.micro, micro) + self.assertEqual(version.patch, patch) + self.assertEqual(version.status, status) + + def assert_version_is_greater(self, first_version, second_version): + """ + Asserts that the parsed version of the first version is greate than the + second (also checking the inverse). + """ + + version1 = stem.version.Version(first_version) + version2 = stem.version.Version(second_version) + self.assertEqual(version1 > version2, True) + self.assertEqual(version1 < version2, False) + + def assert_version_is_equal(self, first_version, second_version): + """ + Asserts that the parsed version of the first version equals the second. + """ + + version1 = stem.version.Version(first_version) + version2 = stem.version.Version(second_version) + self.assertEqual(version1, version2) + + def assert_string_matches(self, version): + """ + Parses the given version string then checks that its string representation + matches the input. + """ + + self.assertEqual(version, str(stem.version.Version(version))) +
tor-commits@lists.torproject.org