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 --- lib/thandy/checkJson.py | 88 ++++++++++++++++++++++++++++++++++++++++++++++++- lib/thandy/tests.py | 69 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 156 insertions(+), 1 deletion(-) (limited to 'lib') 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