commit 69083da95c5ab536adcd23f19a424ed9395015bb Author: Damian Johnson atagar@torproject.org Date: Thu Oct 6 09:52:04 2011 -0700
Class and function for Tor Versions
Starting with a simple (but non-trivial) class that will be needed for handling PROTOCOLINFO responses. This is partly to establish conventions for documentation and unit tests. --- run_tests.py | 13 ++++++ stem/__init__.py | 6 +++ stem/__init__.pyc | Bin 0 -> 228 bytes stem/run_tests.py | 13 ++++++ stem/types.py | 99 +++++++++++++++++++++++++++++++++++++++++++ stem/types.pyc | Bin 0 -> 3406 bytes test/__init__.py | 6 +++ test/__init__.pyc | Bin 0 -> 237 bytes test/version.py | 121 +++++++++++++++++++++++++++++++++++++++++++++++++++++ test/version.pyc | Bin 0 -> 3982 bytes 10 files changed, 258 insertions(+), 0 deletions(-)
diff --git a/run_tests.py b/run_tests.py old mode 100644 new mode 100755 index e69de29..0fe6365 --- a/run_tests.py +++ b/run_tests.py @@ -0,0 +1,13 @@ +#!/usr/bin/env python + +""" +Runs unit and integration tests. +""" + +import unittest +import test.version + +if __name__ == '__main__': + suite = unittest.TestLoader().loadTestsFromTestCase(test.version.TestVerionFunctions) + unittest.TextTestRunner(verbosity=2).run(suite) + diff --git a/stem/__init__.py b/stem/__init__.py new file mode 100644 index 0000000..01c350c --- /dev/null +++ b/stem/__init__.py @@ -0,0 +1,6 @@ +""" +Library for working with the tor process. +""" + +__all__ = ["types"] + diff --git a/stem/__init__.pyc b/stem/__init__.pyc new file mode 100644 index 0000000..43c6f7d Binary files /dev/null and b/stem/__init__.pyc differ diff --git a/stem/run_tests.py b/stem/run_tests.py new file mode 100644 index 0000000..ad52f9c --- /dev/null +++ b/stem/run_tests.py @@ -0,0 +1,13 @@ +#!/usr/bin/env python + +""" +Runs unit and integration tests. +""" + +import unittest +import test.types + +if __name__ == '__main__': + suite = unittest.TestLoader().loadTestsFromTestCase(test.types.TestVerionFunctions) + unittest.TextTestRunner(verbosity=2).run(suite) + diff --git a/stem/types.py b/stem/types.py new file mode 100644 index 0000000..0f7030b --- /dev/null +++ b/stem/types.py @@ -0,0 +1,99 @@ +""" +Classes for miscellaneous tor object. This includes... + +types.Version - Tor versioning information. + * get_version(versionStr) + Converts a version string to a types.Version instance. +""" + +import re + +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, major, minor, micro, patch = None, status = None): + self.major = major + self.minor = minor + self.micro = micro + self.patch = patch + self.status = status + + 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 comparision 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): + raise ValueError("types.Version can only be compared with other Version instances") + + for attr in ("major", "minor", "micro", "patch"): + myVersion = max(0, self.__dict__[attr]) + otherVersion = max(0, other.__dict__[attr]) + + if myVersion > otherVersion: return 1 + elif myVersion < otherVersion: return -1 + + myStatus = self.status if self.status else "" + otherStatus = other.status if other.status else "" + + return cmp(myStatus, otherStatus) + +def get_version(versionStr): + """ + Parses a version string, providing back a types.Version instance. + + Arguments: + versionStr (str) - string representing a tor version (ex. "0.2.2.23-alpha") + + Returns: + types.Version instance + + Throws: + ValueError if input isn't a valid tor version + """ + + if not isinstance(versionStr, str): + raise ValueError("argument is not a string") + + m = re.match(r'^([0-9]+).([0-9]+).([0-9]+)(.[0-9]+)?(-\S*)?$', versionStr) + + 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:] + + return Version(int(major), int(minor), int(micro), patch, status) + else: raise ValueError("'%s' isn't a properly formatted tor version" % versionStr) + diff --git a/stem/types.pyc b/stem/types.pyc new file mode 100644 index 0000000..27a5bb1 Binary files /dev/null and b/stem/types.pyc differ diff --git a/test/__init__.py b/test/__init__.py new file mode 100644 index 0000000..6d651bb --- /dev/null +++ b/test/__init__.py @@ -0,0 +1,6 @@ +""" +Unit and integration tests for the stem library. +""" + +__all__ = ["version"] + diff --git a/test/__init__.pyc b/test/__init__.pyc new file mode 100644 index 0000000..6b41a10 Binary files /dev/null and b/test/__init__.pyc differ diff --git a/test/version.py b/test/version.py new file mode 100644 index 0000000..fd32886 --- /dev/null +++ b/test/version.py @@ -0,0 +1,121 @@ +""" +Unit tests for types functions and classes. +""" + +import unittest +import stem.types + +class TestVerionFunctions(unittest.TestCase): + """ + Tests methods and functions related to 'types.Version'. + """ + + def test_parsing(self): + """ + Tests parsing by the 'get_version' function. + """ + + # valid versions with various number of compontents to the version + version = stem.types.get_version("0.1.2.3-tag") + self.assert_versions_match(version, 0, 1, 2, 3, "tag") + + version = stem.types.get_version("0.1.2.3") + self.assert_versions_match(version, 0, 1, 2, 3, None) + + version = stem.types.get_version("0.1.2-tag") + self.assert_versions_match(version, 0, 1, 2, None, "tag") + + version = stem.types.get_version("0.1.2") + self.assert_versions_match(version, 0, 1, 2, None, None) + + # checks an empty tag + version = stem.types.get_version("0.1.2.3-") + self.assert_versions_match(version, 0, 1, 2, 3, "") + + version = stem.types.get_version("0.1.2-") + self.assert_versions_match(version, 0, 1, 2, None, "") + + # checks invalid version strings + self.assertRaises(ValueError, stem.types.get_version, "") + self.assertRaises(ValueError, stem.types.get_version, "1.2.3.4nodash") + self.assertRaises(ValueError, stem.types.get_version, "1.2.3.a") + self.assertRaises(ValueError, stem.types.get_version, "1.2.a.4") + self.assertRaises(ValueError, stem.types.get_version, "12.3") + self.assertRaises(ValueError, stem.types.get_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_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, firstVersion, secondVersion): + """ + Asserts that the parsed version of the first version is greate than the + second (also checking the inverse). + """ + + version1 = stem.types.get_version(firstVersion) + version2 = stem.types.get_version(secondVersion) + self.assertEqual(version1 > version2, True) + self.assertEqual(version1 < version2, False) + + def assert_version_is_equal(self, firstVersion, secondVersion): + """ + Asserts that the parsed version of the first version equals the second. + """ + + version1 = stem.types.get_version(firstVersion) + version2 = stem.types.get_version(secondVersion) + 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.get_version(version))) + diff --git a/test/version.pyc b/test/version.pyc new file mode 100644 index 0000000..c459fa7 Binary files /dev/null and b/test/version.pyc differ