From 6c78fd8ff6b3d39fbae85103bda0651ca2f764d9 Mon Sep 17 00:00:00 2001 From: Nick Mathewson Date: Sat, 22 Nov 2008 17:39:57 +0000 Subject: more docs and tests git-svn-id: file:///home/or/svnrepo/updater/trunk@17364 55e972cd-5a19-0410-ae62-a4d7a52db4cd --- TODO | 33 +++++++++++-------- doc/HOWTO | 3 ++ lib/thandy/checkJson.py | 88 ++++++++++++++++++++++++++++++++++++++++++++++++- lib/thandy/tests.py | 69 ++++++++++++++++++++++++++++++++++++++ 4 files changed, 179 insertions(+), 14 deletions(-) diff --git a/TODO b/TODO index 758361f..53e8e88 100644 --- a/TODO +++ b/TODO @@ -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 @@ line 343, in decryptSecret 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@moria:~$ thandy-server timestamp Traceback (most recent call last): @@ -90,3 +96,4 @@ __getattr__ return getattr(self.key, attr) AttributeError: rsaKey instance has no attribute 'd' + [ Gives a more descriptive error now. ] diff --git a/doc/HOWTO b/doc/HOWTO index 572b821..71601b1 100644 --- a/doc/HOWTO +++ b/doc/HOWTO @@ -99,6 +99,9 @@ or something), run: 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. diff --git a/lib/thandy/checkJson.py b/lib/thandy/checkJson.py index b5fef3d..84ea9f2 100644 --- a/lib/thandy/checkJson.py +++ b/lib/thandy/checkJson.py @@ -6,7 +6,10 @@ import sys 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 @@ class Schema: 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 Any(Schema): 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 @@ class RE(Schema): >>> 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 RE(Schema): class Str(Schema): """ + Matches a particular string. + >>> s = Str("Hi") >>> s.matches("Hi") True @@ -68,6 +89,8 @@ class Str(Schema): class AnyStr(Schema): """ + Matches any string, but no non-string object. + >>> s = AnyStr() >>> s.matches("") True @@ -88,8 +111,36 @@ class AnyStr(Schema): 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 @@ class ListOf(Schema): 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 ListOf(Schema): 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 @@ class Struct(Schema): 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 Struct(Schema): 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 Opt: 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 Obj(Schema): class Int(Schema): """ + Matches an integer. + >>> s = Int() >>> s.matches(99) True @@ -262,6 +346,8 @@ class Int(Schema): class Bool(Schema): """ + Matches a boolean. + >>> s = Bool() >>> s.matches(True) and s.matches(False) True diff --git a/lib/thandy/tests.py b/lib/thandy/tests.py index e2fb767..924fc82 100644 --- a/lib/thandy/tests.py +++ b/lib/thandy/tests.py @@ -9,9 +9,25 @@ import thandy.keys 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 @@ class CryptoTests(unittest.TestCase): 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() -- cgit v1.2.3