[tor-commits] [stem/master] Expanding capabilites of tor version comparison

atagar at torproject.org atagar at torproject.org
Sun Jun 10 01:27:16 UTC 2012


commit 7ecd7c6a63baf08b638a378a7f4c237ec7ceeb84
Author: Damian Johnson <atagar at torproject.org>
Date:   Sat Jun 9 14:20:56 2012 -0700

    Expanding capabilites of tor version comparison
    
    Requirements were simple tor versions which was fine for simple 'greater than'
    comparisons, but broke down for slightly more complicated requirements such
    as 'at least version X in the 0.2.2 series or version Y in the 0.2.3'.
    
    Introducing a 'meets_requirements' that will allow us to compare with arbitrary
    rules. For now we're just comparing with Versions or VersionRequirements, but
    could be expanded later if we need to.
---
 stem/version.py      |   97 +++++++++++++++++++++++++++++++++++++++++++++-
 test/unit/version.py |  103 ++++++++++++++++++++++++++++++++++++++++++++------
 2 files changed, 185 insertions(+), 15 deletions(-)

diff --git a/stem/version.py b/stem/version.py
index 0159512..cfb2e12 100644
--- a/stem/version.py
+++ b/stem/version.py
@@ -7,7 +7,7 @@ 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
+  >>> my_version.meets_requirements(stem.version.Requirement.CONTROL_SOCKET)
   True
 
 **Module Overview:**
@@ -15,13 +15,22 @@ easily parsed and compared, for instance...
 ::
 
   get_system_tor_version - gets the version of our system's tor installation
+  
   Version - Tor versioning information.
+    |- meets_requirements - checks if this version meets the given requirements
     |- __str__ - string representation
     +- __cmp__ - compares with another Version
   
+  VersionRequirements - Series of version requirements
+    |- greater_than - adds rule that matches if we're greater than a version
+    |- less_than    - adds rule that matches if we're less than a version
+    +- in_range     - adds rule that matches if we're within a given version range
+  
   Requirement - Enumerations for the version requirements of features.
-    |- GETINFO_CONFIG_TEXT - 'GETINFO config-text' query
-    +- CONTROL_SOCKET      - 'ControlSocket <path>' config option
+    |- AUTH_SAFECOOKIE      - 'SAFECOOKIE' authentication method
+    |- GETINFO_CONFIG_TEXT  - 'GETINFO config-text' query
+    |- TORRC_CONTROL_SOCKET - 'ControlSocket <path>' config option
+    +- TORRC_DISABLE_DEBUGGER_ATTACHMENT - 'DisableDebuggerAttachment' config option
 """
 
 import re
@@ -109,6 +118,24 @@ class Version:
       self.status = status
     else: raise ValueError("'%s' isn't a properly formatted tor version" % version_str)
   
+  def meets_requirements(self, requirements):
+    """
+    Checks if this version meets the requirements for a given feature.
+    
+    Requirements can be either a :class:`stem.version.Version` or
+    :class:`stem.version.VersionRequirements`.
+    
+    :param requirements: requrirements to be checked for
+    """
+    
+    if isinstance(requirements, Version):
+      return self >= requirements
+    else:
+      for rule in requirements.rules:
+        if rule(self): return True
+      
+      return False
+  
   def __str__(self):
     """
     Provides the string used to construct the Version.
@@ -139,7 +166,71 @@ class Version:
     # 'purely informational'
     return 0
 
+class VersionRequirements:
+  """
+  Series of version constraints that can be compared to. For instance, it
+  allows for comparisons like 'if I'm greater than version X in the 0.2.2
+  series, or greater than version Y in the 0.2.3 series'.
+  
+  This is a logical 'or' of the series of rules.
+  """
+  
+  def __init__(self, rule = None):
+    self.rules = []
+    
+    if rule: self.greater_than(rule)
+  
+  def greater_than(self, version, inclusive = True):
+    """
+    Adds a constraint that we're greater than the given version.
+    
+    :param stem.version.Version verison: version we're checking against
+    :param bool inclusive: if comparison is inclusive or not
+    """
+    
+    if inclusive:
+      self.rules.append(lambda v: version <= v)
+    else:
+      self.rules.append(lambda v: version < v)
+  
+  def less_than(self, version, inclusive = True):
+    """
+    Adds a constraint that we're less than the given version.
+    
+    :param stem.version.Version verison: version we're checking against
+    :param bool inclusive: if comparison is inclusive or not
+    """
+    
+    if inclusive:
+      self.rules.append(lambda v: version >= v)
+    else:
+      self.rules.append(lambda v: version > v)
+  
+  def in_range(self, from_version, to_version, from_inclusive = True, to_inclusive = False):
+    """
+    Adds constraint that we're within the range from one version to another.
+    
+    :param stem.version.Version from_verison: beginning of the comparison range
+    :param stem.version.Version to_verison: end of the comparison range
+    :param bool from_inclusive: if comparison is inclusive with the starting version
+    :param bool to_inclusive: if comparison is inclusive with the ending version
+    """
+    
+    if from_inclusive and to_inclusive:
+      new_rule = lambda v: from_version <= v <= to_version
+    elif from_inclusive:
+      new_rule = lambda v: from_version <= v < to_version
+    else:
+      new_rule = lambda v: from_version < v < to_version
+    
+    self.rules.append(new_rule)
+
+safecookie_req = VersionRequirements()
+safecookie_req.in_range(Version("0.2.2.36"), Version("0.2.3.0"))
+safecookie_req.greater_than(Version("0.2.3.13"))
+
 Requirement = stem.util.enum.Enum(
+  ("AUTH_SAFECOOKIE", safecookie_req),
   ("GETINFO_CONFIG_TEXT", Version("0.2.2.7")),
   ("TORRC_CONTROL_SOCKET", Version("0.2.0.30")),
   ("TORRC_DISABLE_DEBUGGER_ATTACHMENT", Version("0.2.3.9")),
diff --git a/test/unit/version.py b/test/unit/version.py
index 2b5cf96..618b483 100644
--- a/test/unit/version.py
+++ b/test/unit/version.py
@@ -4,6 +4,7 @@ Unit tests for the stem.version.Version parsing and class.
 
 import unittest
 import stem.version
+from stem.version import Version
 import stem.util.system
 
 import test.mocking as mocking
@@ -43,23 +44,23 @@ class TestVersion(unittest.TestCase):
     
     # valid versions with various number of compontents to the version
     
-    version = stem.version.Version("0.1.2.3-tag")
+    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")
+    version = Version("0.1.2.3")
     self.assert_versions_match(version, 0, 1, 2, 3, None)
     
-    version = stem.version.Version("0.1.2-tag")
+    version = Version("0.1.2-tag")
     self.assert_versions_match(version, 0, 1, 2, None, "tag")
     
-    version = stem.version.Version("0.1.2")
+    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-")
+    version = Version("0.1.2.3-")
     self.assert_versions_match(version, 0, 1, 2, 3, "")
     
-    version = stem.version.Version("0.1.2-")
+    version = Version("0.1.2-")
     self.assert_versions_match(version, 0, 1, 2, None, "")
     
     # checks invalid version strings
@@ -101,7 +102,7 @@ class TestVersion(unittest.TestCase):
     Checks that we can be compared with other types.
     """
     
-    test_version = stem.version.Version("0.1.2.3")
+    test_version = Version("0.1.2.3")
     self.assertNotEqual(test_version, None)
     self.assertTrue(test_version > None)
     
@@ -118,6 +119,84 @@ class TestVersion(unittest.TestCase):
     self.assert_string_matches("0.1.2.3")
     self.assert_string_matches("0.1.2")
   
+  def test_requirements_greater_than(self):
+    """
+    Checks a VersionRequirements with a single greater_than rule.
+    """
+    
+    requirements = stem.version.VersionRequirements()
+    requirements.greater_than(Version("0.2.2.36"))
+    
+    self.assertTrue(Version("0.2.2.36").meets_requirements(requirements))
+    self.assertTrue(Version("0.2.2.37").meets_requirements(requirements))
+    self.assertTrue(Version("0.2.3.36").meets_requirements(requirements))
+    self.assertFalse(Version("0.2.2.35").meets_requirements(requirements))
+    self.assertFalse(Version("0.2.1.38").meets_requirements(requirements))
+    
+    requirements = stem.version.VersionRequirements()
+    requirements.greater_than(Version("0.2.2.36"), False)
+    
+    self.assertFalse(Version("0.2.2.35").meets_requirements(requirements))
+    self.assertFalse(Version("0.2.2.36").meets_requirements(requirements))
+    self.assertTrue(Version("0.2.2.37").meets_requirements(requirements))
+  
+  def test_requirements_less_than(self):
+    """
+    Checks a VersionRequirements with a single less_than rule.
+    """
+    
+    requirements = stem.version.VersionRequirements()
+    requirements.less_than(Version("0.2.2.36"))
+    
+    self.assertTrue(Version("0.2.2.36").meets_requirements(requirements))
+    self.assertTrue(Version("0.2.2.35").meets_requirements(requirements))
+    self.assertTrue(Version("0.2.1.38").meets_requirements(requirements))
+    self.assertFalse(Version("0.2.2.37").meets_requirements(requirements))
+    self.assertFalse(Version("0.2.3.36").meets_requirements(requirements))
+    
+    requirements = stem.version.VersionRequirements()
+    requirements.less_than(Version("0.2.2.36"), False)
+    
+    self.assertFalse(Version("0.2.2.37").meets_requirements(requirements))
+    self.assertFalse(Version("0.2.2.36").meets_requirements(requirements))
+    self.assertTrue(Version("0.2.2.35").meets_requirements(requirements))
+  
+  def test_requirements_in_range(self):
+    """
+    Checks a VersionRequirements with a single in_range rule.
+    """
+    
+    requirements = stem.version.VersionRequirements()
+    requirements.in_range(Version("0.2.2.36"), Version("0.2.2.38"))
+    
+    self.assertFalse(Version("0.2.2.35").meets_requirements(requirements))
+    self.assertTrue(Version("0.2.2.36").meets_requirements(requirements))
+    self.assertTrue(Version("0.2.2.37").meets_requirements(requirements))
+    self.assertFalse(Version("0.2.2.38").meets_requirements(requirements))
+    
+    # rule for 'anything in the 0.2.2.x series'
+    requirements = stem.version.VersionRequirements()
+    requirements.in_range(Version("0.2.2.0"), Version("0.2.3.0"))
+    
+    for i in xrange(0, 100):
+      self.assertTrue(Version("0.2.2.%i" % i).meets_requirements(requirements))
+  
+  def test_requirements_multiple_rules(self):
+    """
+    Checks a VersionRequirements is the logical 'or' when it has multiple rules.
+    """
+    
+    # rule to say 'anything but the 0.2.2.x series'
+    requirements = stem.version.VersionRequirements()
+    requirements.greater_than(Version("0.2.3.0"))
+    requirements.less_than(Version("0.2.2.0"), False)
+    
+    self.assertTrue(Version("0.2.3.0").meets_requirements(requirements))
+    self.assertFalse(Version("0.2.2.0").meets_requirements(requirements))
+    
+    for i in xrange(0, 100):
+      self.assertFalse(Version("0.2.2.%i" % i).meets_requirements(requirements))
+  
   def assert_versions_match(self, version, major, minor, micro, patch, status):
     """
     Asserts that the values for a types.Version instance match the given
@@ -136,8 +215,8 @@ class TestVersion(unittest.TestCase):
     second (also checking the inverse).
     """
     
-    version1 = stem.version.Version(first_version)
-    version2 = stem.version.Version(second_version)
+    version1 = Version(first_version)
+    version2 = Version(second_version)
     self.assertEqual(version1 > version2, True)
     self.assertEqual(version1 < version2, False)
   
@@ -146,8 +225,8 @@ class TestVersion(unittest.TestCase):
     Asserts that the parsed version of the first version equals the second.
     """
     
-    version1 = stem.version.Version(first_version)
-    version2 = stem.version.Version(second_version)
+    version1 = Version(first_version)
+    version2 = Version(second_version)
     self.assertEqual(version1, version2)
   
   def assert_string_matches(self, version):
@@ -156,5 +235,5 @@ class TestVersion(unittest.TestCase):
     matches the input.
     """
     
-    self.assertEqual(version, str(stem.version.Version(version)))
+    self.assertEqual(version, str(Version(version)))
 





More information about the tor-commits mailing list