[or-cvs] r17364: {updater} more docs and tests (in updater/trunk: . doc lib/thandy)

nickm at seul.org nickm at seul.org
Sat Nov 22 17:39:57 UTC 2008


Author: nickm
Date: 2008-11-22 12:39:57 -0500 (Sat, 22 Nov 2008)
New Revision: 17364

Modified:
   updater/trunk/TODO
   updater/trunk/doc/HOWTO
   updater/trunk/lib/thandy/checkJson.py
   updater/trunk/lib/thandy/tests.py
Log:
more docs and tests

Modified: updater/trunk/TODO
===================================================================
--- updater/trunk/TODO	2008-11-22 17:37:56 UTC (rev 17363)
+++ updater/trunk/TODO	2008-11-22 17:39:57 UTC (rev 17364)
@@ -1,18 +1,19 @@
-03:07 < nickm> ok.  tomorrow morning I try to get messaging sorted, and try to
-               write up a registry-based exe version checker.
+- get messaging sorted
+o try to write up a registry-based exe version checker.,
+- Decouple install from check: they are not necessarily related.
 
-- Download improvements.
-  - Back off on download failure.
-  - Handle full stalled file in download.
+. Download improvements.
+  o Back off on download failure.
+  o Handle full stalled file in download.
   - Use if-modified-since on timestamp
 
-- Write client-side code
-  . Decide early if a python implementation will do for v1.
+o Write client-side code
+  o Decide early if a python implementation will do for v1.
      o Adjust httplib, urllib2 to use socks4a.
        o Check SOCKS package for suitability as basis for socks4a support?
-     - Look into best packageing practices
+     o Look into best packaging practices
 
-  . Write code to run, telling another process about status,
+  o Write code to run, telling another process about status,
     eventually coming up with a list of packages to install or an
     "A-OK" signal.
 
@@ -21,7 +22,7 @@
   o DL-via-Tor
   o Install-when-done
   o Verbose output
-  - quiet output.
+  o quiet output.
   X Rendezvous-back with Tor when done.
 
 - Better configurability: let users override mirrors, keys, etc.
@@ -41,9 +42,10 @@
 - Testing
   - Much bigger unit tests.
 
-- Bugs Roger keeps noticing
-  - you can add the same role to a key twice.
-  - wishlist item: thandy-pk dumpkey-all
+o Bugs Roger keeps noticing
+  o you can add the same role to a key twice.
+  o wishlist item: thandy-pk dumpkey-all
+    o Already there: Run "thandy-pk dumpkey" with no arguments
 
 - Confusing tracebacks that could use a check and error message:
 
@@ -66,6 +68,10 @@
     raise thandy.BadPassword()
 thandy.BadPassword
 
+   [ I maintain that BadPassword _is_ an error message.  I'll make it say
+     "Password Incorrect", though, so it looks less like a bug.]
+
+
 when your ~/.thandy/timestamp_key didn't get the secret key dumped too,
 thandy at moria:~$ thandy-server timestamp
 Traceback (most recent call last):
@@ -90,3 +96,4 @@
     return getattr(self.key, attr)
 AttributeError: rsaKey instance has no attribute 'd'
 
+  [ Gives a more descriptive error now. ]

Modified: updater/trunk/doc/HOWTO
===================================================================
--- updater/trunk/doc/HOWTO	2008-11-22 17:37:56 UTC (rev 17363)
+++ updater/trunk/doc/HOWTO	2008-11-22 17:39:57 UTC (rev 17364)
@@ -99,6 +99,9 @@
 
     thandy-pk dumpkey --include-secret {KEYID}
 
+You can list as many or as few keys as you want.  To dump _all_ keys, just
+omit the KEYID.
+
 Generally speaking, you want to figure out what keys get what roles _before_
 you start making the first keylist; otherwise you'll probably need to
 re-export them.

Modified: updater/trunk/lib/thandy/checkJson.py
===================================================================
--- updater/trunk/lib/thandy/checkJson.py	2008-11-22 17:37:56 UTC (rev 17363)
+++ updater/trunk/lib/thandy/checkJson.py	2008-11-22 17:39:57 UTC (rev 17364)
@@ -6,7 +6,10 @@
 import thandy
 
 class Schema:
+    """A schema matches a set of possible Python objects, of types
+       that are encodable in JSON."""
     def matches(self, obj):
+        """Return True if 'obj' matches this schema, False if it doesn't."""
         try:
             self.checkMatch(obj)
         except thandy.FormatException:
@@ -15,10 +18,14 @@
             return True
 
     def checkMatch(self, obj):
+        """Raise thandy.FormatException if 'obj' does not match this schema.
+           Abstract method."""
         raise NotImplemented()
 
 class Any(Schema):
     """
+       Matches any single object.
+
        >>> s = Any()
        >>> s.matches("A String")
        True
@@ -30,6 +37,8 @@
 
 class RE(Schema):
     """
+       Matches any string that matches a given regular expression.
+
        >>> s = RE("h.*d")
        >>> s.matches("hello world")
        True
@@ -40,12 +49,22 @@
        >>> s.matches([33, "Hello"])
        False
     """
-    def __init__(self, pat=None, modifiers=0, reObj=None, reName="pattern"):
+    def __init__(self, pat=None, modifiers=0, reObj=None, reName=None):
+        """Make a new RE schema
+             pat -- The pattern to match, or None if reObj is provided.
+             modifiers -- Flags to use when compiling the pattern.
+             reObj -- A compiled regular expression object.
+        """
         if not reObj:
             if not pat.endswith("$"):
                 pat += "$"
             reObj = re.compile(pat, modifiers)
         self._re = reObj
+        if reName == None:
+            if pat != None:
+                reName = "pattern /%s/"%pat
+            else:
+                reName = "pattern"
         self._reName = reName
     def checkMatch(self, obj):
         if not isinstance(obj, basestring) or not self._re.match(obj):
@@ -54,6 +73,8 @@
 
 class Str(Schema):
     """
+       Matches a particular string.
+
        >>> s = Str("Hi")
        >>> s.matches("Hi")
        True
@@ -68,6 +89,8 @@
 
 class AnyStr(Schema):
     """
+       Matches any string, but no non-string object.
+
        >>> s = AnyStr()
        >>> s.matches("")
        True
@@ -88,8 +111,36 @@
         if not isinstance(obj, basestring):
             raise thandy.FormatException("Expected a string; got %r"%obj)
 
+class OneOf(Schema):
+    """
+       Matches an object that matches any one of several schemas.
+
+       >>> s = OneOf([ListOf(Int()), Str("Hello"), Str("bye")])
+       >>> s.matches(3)
+       False
+       >>> s.matches("bye")
+       True
+       >>> s.matches([])
+       True
+       >>> s.matches([1,2])
+       True
+       >>> s.matches(["Hi"])
+       False
+    """
+    def __init__(self, alternatives):
+        self._subschemas = alternatives
+
+    def checkMatch(self, obj):
+        for m in self._subschemas:
+            if m.matches(obj):
+                return
+
+        raise thandy.FormatException("Object matched no recognized alternative")
+
 class ListOf(Schema):
     """
+       Matches a homogenous list of some subschema.
+
        >>> s = ListOf(RE("(?:..)*"))
        >>> s.matches("hi")
        False
@@ -101,6 +152,16 @@
        True
        >>> s.matches(["This", "one", "is not"])
        False
+
+       >>> s = ListOf(Int(), minCount=3, maxCount=10)
+       >>> s.matches([3]*2)
+       False
+       >>> s.matches([3]*3)
+       True
+       >>> s.matches([3]*10)
+       True
+       >>> s.matches([3]*11)
+       False
     """
     def __init__(self, schema, minCount=0, maxCount=sys.maxint,listName="list"):
         self._schema = schema
@@ -123,6 +184,8 @@
 
 class Struct(Schema):
     """
+       Matches a non-homogenous list of items.
+
        >>> s = Struct([ListOf(AnyStr()), AnyStr(), Str("X")])
        >>> s.matches(False)
        False
@@ -136,6 +199,18 @@
        False
        >>> s.matches([[], "Q", "X", "Y"])
        False
+
+       >>> s = Struct([Str("X")], allowMore=True)
+       >>> s.matches([])
+       False
+       >>> s.matches(["X"])
+       True
+       >>> s.matches(["X", "Y"])
+       True
+       >>> s.matches(["X", ["Y", "Z"]])
+       True
+       >>> s.matches([["X"]])
+       False
     """
     def __init__(self, subschemas, allowMore=False, structName="list"):
         self._subschemas = subschemas[:]
@@ -156,6 +231,10 @@
 
 class DictOf(Schema):
     """
+       Matches a mapping from items matching a particular key-schema
+       to items matching a value-schema.  Note that in JSON, keys must
+       be strings.
+
        >>> s = DictOf(RE(r'[aeiou]+'), Struct([AnyStr(), AnyStr()]))
        >>> s.matches("")
        False
@@ -199,6 +278,9 @@
 
 class Obj(Schema):
     """
+       Matches a dict from specified keys to key-specific types.  Unrecognized
+       keys are allowed.
+
        >>> s = Obj(a=AnyStr(), bc=Struct([Int(), Int()]))
        >>> s.matches({'a':"ZYYY", 'bc':[5,9]})
        True
@@ -234,6 +316,8 @@
 
 class Int(Schema):
     """
+       Matches an integer.
+
        >>> s = Int()
        >>> s.matches(99)
        True
@@ -262,6 +346,8 @@
 
 class Bool(Schema):
     """
+       Matches a boolean.
+
        >>> s = Bool()
        >>> s.matches(True) and s.matches(False)
        True

Modified: updater/trunk/lib/thandy/tests.py
===================================================================
--- updater/trunk/lib/thandy/tests.py	2008-11-22 17:37:56 UTC (rev 17363)
+++ updater/trunk/lib/thandy/tests.py	2008-11-22 17:39:57 UTC (rev 17364)
@@ -9,9 +9,25 @@
 import thandy.formats
 import thandy.repository
 import thandy.checkJson
+import thandy.util
 
 import thandy.tests
 
+def deltree(top):
+    for dirpath, dirnames, filenames in os.walk(top, topdown=False):
+        for f in filenames:
+            os.unlink(os.path.join(dirpath, f))
+        for d in dirnames:
+            os.rmdir(os.path.join(dirpath, d))
+    os.rmdir(top)
+
+def contents(fn, mode='rb'):
+    f = open(fn, mode)
+    try:
+        return f.read()
+    finally:
+        f.close()
+
 class CanonicalEncodingTest(unittest.TestCase):
     def test_encode(self):
         enc = thandy.formats.encodeCanonical
@@ -47,6 +63,59 @@
         ks2.load(passwd)
         self.assertEquals(key1.key.n, ks2.getKey(key1.getKeyID()).key.n)
 
+class UtilTests(unittest.TestCase):
+    def setUp(self):
+        self._dir = tempfile.mkdtemp()
+    def tearDown(self):
+        deltree(self._dir)
+
+    def test_replaceFile(self):
+        fn1 = os.path.join(self._dir, "File1")
+        S1="Why do you curtsey, commoner? I presumed this would be anonymous."
+        S2="I am simply guaranteeing your commitment to my anonymity."
+        # -- WIGU adventures, 24 March 2005.
+        thandy.util.replaceFile(fn1, S1)
+        self.assertEquals(contents(fn1), S1)
+        thandy.util.replaceFile(fn1, S2)
+        self.assertEquals(contents(fn1), S2)
+
+        self.assertEquals(os.listdir(self._dir), [ "File1" ])
+
+    def test_moveFile(self):
+        d = self._dir
+        os.mkdir(os.path.join(d, "subdir"))
+        fn1 = os.path.join(d, "f1")
+        fn2 = os.path.join(d, "f2")
+        fn3 = os.path.join(d, "subdir", "f3")
+        S1="""We monitor all citizens constantly to detect insider baddies!
+              Isn't it wondersome?!"""
+        S2="""Wondersome yes... But could such a tactic instill a sense of
+              distrust and fear in a populace that is overwhelmingly true and
+              pious?"""
+        S3="""I think the fact that we are not currently under siege by
+              unscrupulous minions speaks for itself."""
+        # -- WIGU adventures, 22 January 2004
+
+        thandy.util.replaceFile(fn1, S1)
+        thandy.util.replaceFile(fn2, S2)
+        thandy.util.replaceFile(fn3, S3)
+
+        self.assertEquals(contents(fn1), S1)
+        self.assertTrue(os.path.exists(fn2))
+        self.assertTrue(os.path.exists(fn3))
+
+        thandy.util.moveFile(fn2, fn1)
+        self.assertEquals(contents(fn1), S2)
+        self.assertFalse(os.path.exists(fn2))
+
+        thandy.util.moveFile(fn1, fn3)
+        self.assertEquals(contents(fn3), S2)
+        self.assertFalse(os.path.exists(fn1))
+
+        self.assertEquals(os.listdir(d), ["subdir"])
+        self.assertEquals(os.listdir(os.path.join(d, "subdir")), ["f3"])
+
+
 def suite():
     suite = unittest.TestSuite()
 



More information about the tor-commits mailing list