test_dirnode.py: obtain full coverage of dirnode.py

This commit is contained in:
Brian Warner
2007-12-04 14:32:04 -07:00
parent cca166a4f5
commit 0f5ef5184d
5 changed files with 398 additions and 142 deletions

View File

@ -6,8 +6,7 @@ from twisted.internet import defer
import simplejson import simplejson
from allmydata.mutable import NotMutableError from allmydata.mutable import NotMutableError
from allmydata.interfaces import IMutableFileNode, IDirectoryNode,\ from allmydata.interfaces import IMutableFileNode, IDirectoryNode,\
IURI, IFileNode, \ IURI, IFileNode, IMutableFileURI, IVerifierURI
IVerifierURI
from allmydata.util import hashutil from allmydata.util import hashutil
from allmydata.util.hashutil import netstring from allmydata.util.hashutil import netstring
from allmydata.uri import NewDirectoryURI from allmydata.uri import NewDirectoryURI
@ -67,7 +66,7 @@ class NewDirectoryNode:
d.addCallback(self._filenode_created) d.addCallback(self._filenode_created)
return d return d
def _filenode_created(self, res): def _filenode_created(self, res):
self._uri = NewDirectoryURI(self._node._uri) self._uri = NewDirectoryURI(IMutableFileURI(self._node.get_uri()))
return self return self
def _read(self): def _read(self):
@ -159,7 +158,7 @@ class NewDirectoryNode:
def check(self): def check(self):
"""Perform a file check. See IChecker.check for details.""" """Perform a file check. See IChecker.check for details."""
pass # TODO return defer.succeed(None) # TODO
def list(self): def list(self):
"""I return a Deferred that fires with a dictionary mapping child """I return a Deferred that fires with a dictionary mapping child

View File

@ -1135,6 +1135,33 @@ class IChecker(Interface):
might need to back away from this in the future. might need to back away from this in the future.
""" """
class IClient(Interface):
def upload(uploadable, wait_for_numpeers):
"""Upload some data into a CHK, get back the URI string for it.
@param uploadable: something that implements IUploadable
@param wait_for_numpeers: don't upload anything until we have at least
this many peers connected
@return: a Deferred that fires with the (string) URI for this file.
"""
def create_empty_dirnode(wait_for_numpeers):
"""Create a new dirnode, empty and unattached.
@param wait_for_numpeers: don't create anything until we have at least
this many peers connected.
@return: a Deferred that fires with the new IDirectoryNode instance.
"""
def create_node_from_uri(uri):
"""Create a new IFilesystemNode instance from the uri, synchronously.
@param uri: a string or IURI-providing instance. This could be for a
LiteralFileNode, a CHK file node, a mutable file node, or
a directory node
@return: an instance that provides IFilesystemNode (or more usefully one
of its subclasses). File-specifying URIs will result in
IFileNode or IMutableFileNode -providing instances, like
FileNode, LiteralFileNode, or MutableFileNode.
Directory-specifying URIs will result in
IDirectoryNode-providing instances, like NewDirectoryNode.
"""
class NotCapableError(Exception): class NotCapableError(Exception):
"""You have tried to write to a read-only node.""" """You have tried to write to a read-only node."""

View File

@ -0,0 +1,337 @@
import os
from zope.interface import implements
from twisted.trial import unittest
from twisted.internet import defer
from allmydata import uri, dirnode, upload
from allmydata.interfaces import IURI, IClient, IMutableFileNode, \
INewDirectoryURI, IReadonlyNewDirectoryURI, IFileNode
from allmydata.util import hashutil, testutil
# to test dirnode.py, we want to construct a tree of real DirectoryNodes that
# contain pointers to fake files. We start with a fake MutableFileNode that
# stores all of its data in a static table.
def make_chk_file_uri(size):
return uri.CHKFileURI(key=os.urandom(16),
uri_extension_hash=os.urandom(32),
needed_shares=3,
total_shares=10,
size=size)
def make_mutable_file_uri():
return uri.WriteableSSKFileURI(writekey=os.urandom(16),
fingerprint=os.urandom(32))
def make_verifier_uri():
return uri.SSKVerifierURI(storage_index=os.urandom(16),
fingerprint=os.urandom(32))
class FakeMutableFileNode:
implements(IMutableFileNode)
all_contents = {}
def __init__(self, client):
self.client = client
self.my_uri = make_mutable_file_uri()
self.storage_index = self.my_uri.storage_index
def create(self, initial_contents, wait_for_numpeers=None):
self.all_contents[self.storage_index] = initial_contents
return defer.succeed(self)
def init_from_uri(self, myuri):
self.my_uri = IURI(myuri)
self.storage_index = self.my_uri.storage_index
return self
def get_uri(self):
return self.my_uri
def is_readonly(self):
return self.my_uri.is_readonly()
def is_mutable(self):
return self.my_uri.is_mutable()
def download_to_data(self):
return defer.succeed(self.all_contents[self.storage_index])
def get_writekey(self):
return "\x00"*16
def replace(self, new_contents, wait_for_numpeers=None):
self.all_contents[self.storage_index] = new_contents
return defer.succeed(None)
class MyDirectoryNode(dirnode.NewDirectoryNode):
filenode_class = FakeMutableFileNode
class Marker:
implements(IFileNode, IMutableFileNode) # sure, why not
def __init__(self, nodeuri):
if not isinstance(nodeuri, str):
nodeuri = nodeuri.to_string()
self.nodeuri = nodeuri
si = hashutil.tagged_hash("tag1", nodeuri)
fp = hashutil.tagged_hash("tag2", nodeuri)
self.verifieruri = uri.SSKVerifierURI(storage_index=si,
fingerprint=fp).to_string()
def get_uri(self):
return self.nodeuri
def get_readonly_uri(self):
return self.nodeuri
def get_verifier(self):
return self.verifieruri
# dirnode requires three methods from the client: upload(),
# create_node_from_uri(), and create_empty_dirnode(). Of these, upload() is
# only used by the convenience composite method add_file().
class FakeClient:
implements(IClient)
chk_contents = {}
def upload(self, uploadable, wait_for_numpeers):
d = uploadable.get_size()
d.addCallback(lambda size: uploadable.read(size))
def _got_data(datav):
data = "".join(datav)
u = make_chk_file_uri(len(data))
self.chk_contents[u] = data
return u
d.addCallback(_got_data)
return d
def create_node_from_uri(self, u):
u = IURI(u)
if (INewDirectoryURI.providedBy(u)
or IReadonlyNewDirectoryURI.providedBy(u)):
return MyDirectoryNode(self).init_from_uri(u)
return Marker(u.to_string())
def create_empty_dirnode(self, wait_for_numpeers):
n = MyDirectoryNode(self)
d = n.create(wait_for_numpeers)
d.addCallback(lambda res: n)
return d
class Dirnode(unittest.TestCase, testutil.ShouldFailMixin):
def setUp(self):
self.client = FakeClient()
def test_basic(self):
d = self.client.create_empty_dirnode(0)
def _done(res):
self.failUnless(isinstance(res, MyDirectoryNode))
rep = str(res)
self.failUnless("RW" in rep)
d.addCallback(_done)
return d
def test_corrupt(self):
d = self.client.create_empty_dirnode(0)
def _created(dn):
u = make_mutable_file_uri()
d = dn.set_uri("child", u)
d.addCallback(lambda res: dn.list())
def _check1(children):
self.failUnless("child" in children)
d.addCallback(_check1)
d.addCallback(lambda res:
self.shouldFail(KeyError, "get bogus", None,
dn.get, "bogus"))
def _corrupt(res):
filenode = dn._node
si = IURI(filenode.get_uri()).storage_index
old_contents = filenode.all_contents[si]
# we happen to know that the writecap is encrypted near the
# end of the string. Flip one of its bits and make sure we
# detect the corruption.
new_contents = testutil.flip_bit(old_contents, -10)
filenode.all_contents[si] = new_contents
d.addCallback(_corrupt)
def _check2(res):
self.shouldFail(hashutil.IntegrityCheckError, "corrupt",
"HMAC does not match, crypttext is corrupted",
dn.list)
d.addCallback(_check2)
return d
d.addCallback(_created)
return d
def test_check(self):
d = self.client.create_empty_dirnode(0)
d.addCallback(lambda dn: dn.check())
def _done(res):
pass
d.addCallback(_done)
return d
def test_readonly(self):
fileuri = make_chk_file_uri(1234)
filenode = self.client.create_node_from_uri(fileuri)
uploadable = upload.Data("some data")
d = self.client.create_empty_dirnode(0)
def _created(rw_dn):
d2 = rw_dn.set_uri("child", fileuri)
d2.addCallback(lambda res: rw_dn)
return d2
d.addCallback(_created)
def _ready(rw_dn):
ro_uri = rw_dn.get_readonly_uri()
ro_dn = self.client.create_node_from_uri(ro_uri)
self.failUnless(ro_dn.is_readonly())
self.failUnless(ro_dn.is_mutable())
self.shouldFail(dirnode.NotMutableError, "set_uri ro", None,
ro_dn.set_uri, "newchild", fileuri)
self.shouldFail(dirnode.NotMutableError, "set_uri ro", None,
ro_dn.set_node, "newchild", filenode)
self.shouldFail(dirnode.NotMutableError, "set_uri ro", None,
ro_dn.add_file, "newchild", uploadable)
self.shouldFail(dirnode.NotMutableError, "set_uri ro", None,
ro_dn.delete, "child")
self.shouldFail(dirnode.NotMutableError, "set_uri ro", None,
ro_dn.create_empty_directory, "newchild")
self.shouldFail(dirnode.NotMutableError, "set_uri ro", None,
ro_dn.move_child_to, "child", rw_dn)
self.shouldFail(dirnode.NotMutableError, "set_uri ro", None,
rw_dn.move_child_to, "child", ro_dn)
return ro_dn.list()
d.addCallback(_ready)
def _listed(children):
self.failUnless("child" in children)
d.addCallback(_listed)
return d
def test_create(self):
self.expected_manifest = []
d = self.client.create_empty_dirnode(wait_for_numpeers=1)
def _then(n):
self.failUnless(n.is_mutable())
u = n.get_uri()
self.failUnless(u)
self.failUnless(u.startswith("URI:DIR2:"), u)
u_ro = n.get_readonly_uri()
self.failUnless(u_ro.startswith("URI:DIR2-RO:"), u_ro)
u_v = n.get_verifier()
self.failUnless(u_v.startswith("URI:DIR2-Verifier:"), u_v)
self.expected_manifest.append(u_v)
d = n.list()
d.addCallback(lambda res: self.failUnlessEqual(res, {}))
d.addCallback(lambda res: n.has_child("missing"))
d.addCallback(lambda res: self.failIf(res))
fake_file_uri = make_mutable_file_uri()
m = Marker(fake_file_uri)
ffu_v = m.get_verifier()
assert isinstance(ffu_v, str)
self.expected_manifest.append(ffu_v)
d.addCallback(lambda res: n.set_uri("child", fake_file_uri))
d.addCallback(lambda res: n.create_empty_directory("subdir", wait_for_numpeers=1))
def _created(subdir):
self.failUnless(isinstance(subdir, MyDirectoryNode))
self.subdir = subdir
new_v = subdir.get_verifier()
assert isinstance(new_v, str)
self.expected_manifest.append(new_v)
d.addCallback(_created)
d.addCallback(lambda res: n.list())
d.addCallback(lambda children:
self.failUnlessEqual(sorted(children.keys()),
sorted(["child", "subdir"])))
d.addCallback(lambda res: n.build_manifest())
def _check_manifest(manifest):
self.failUnlessEqual(sorted(manifest),
sorted(self.expected_manifest))
d.addCallback(_check_manifest)
def _add_subsubdir(res):
return self.subdir.create_empty_directory("subsubdir", wait_for_numpeers=1)
d.addCallback(_add_subsubdir)
d.addCallback(lambda res: n.get_child_at_path("subdir/subsubdir"))
d.addCallback(lambda subsubdir:
self.failUnless(isinstance(subsubdir,
MyDirectoryNode)))
d.addCallback(lambda res: n.get_child_at_path(""))
d.addCallback(lambda res: self.failUnlessEqual(res.get_uri(),
n.get_uri()))
d.addCallback(lambda res: n.get_metadata_for("child"))
d.addCallback(lambda metadata: self.failUnlessEqual(metadata, {}))
d.addCallback(lambda res: n.delete("subdir"))
d.addCallback(lambda old_child:
self.failUnlessEqual(old_child.get_uri(),
self.subdir.get_uri()))
d.addCallback(lambda res: n.list())
d.addCallback(lambda children:
self.failUnlessEqual(sorted(children.keys()),
sorted(["child"])))
uploadable = upload.Data("some data")
d.addCallback(lambda res: n.add_file("newfile", uploadable))
d.addCallback(lambda newnode:
self.failUnless(IFileNode.providedBy(newnode)))
d.addCallback(lambda res: n.list())
d.addCallback(lambda children:
self.failUnlessEqual(sorted(children.keys()),
sorted(["child", "newfile"])))
d.addCallback(lambda res: n.create_empty_directory("subdir2"))
def _created2(subdir2):
self.subdir2 = subdir2
d.addCallback(_created2)
d.addCallback(lambda res:
n.move_child_to("child", self.subdir2))
d.addCallback(lambda res: n.list())
d.addCallback(lambda children:
self.failUnlessEqual(sorted(children.keys()),
sorted(["newfile", "subdir2"])))
d.addCallback(lambda res: self.subdir2.list())
d.addCallback(lambda children:
self.failUnlessEqual(sorted(children.keys()),
sorted(["child"])))
return d
d.addCallback(_then)
return d
netstring = hashutil.netstring
split_netstring = dirnode.split_netstring
class Netstring(unittest.TestCase):
def test_split(self):
a = netstring("hello") + netstring("world")
self.failUnlessEqual(split_netstring(a, 2), ("hello", "world"))
self.failUnlessEqual(split_netstring(a, 2, False), ("hello", "world"))
self.failUnlessEqual(split_netstring(a, 2, True),
("hello", "world", ""))
self.failUnlessRaises(ValueError, split_netstring, a, 3)
self.failUnlessRaises(ValueError, split_netstring, a+" extra", 2)
self.failUnlessRaises(ValueError, split_netstring, a+" extra", 2, False)
def test_extra(self):
a = netstring("hello")
self.failUnlessEqual(split_netstring(a, 1, True), ("hello", ""))
b = netstring("hello") + "extra stuff"
self.failUnlessEqual(split_netstring(b, 1, True),
("hello", "extra stuff"))
def test_nested(self):
a = netstring("hello") + netstring("world") + "extra stuff"
b = netstring("a") + netstring("is") + netstring(a) + netstring(".")
top = split_netstring(b, 4)
self.failUnlessEqual(len(top), 4)
self.failUnlessEqual(top[0], "a")
self.failUnlessEqual(top[1], "is")
self.failUnlessEqual(top[2], a)
self.failUnlessEqual(top[3], ".")
self.failUnlessRaises(ValueError, split_netstring, a, 2)
self.failUnlessRaises(ValueError, split_netstring, a, 2, False)
bottom = split_netstring(a, 2, True)
self.failUnlessEqual(bottom, ("hello", "world", "extra stuff"))

View File

@ -3,46 +3,14 @@ import itertools, struct
from twisted.trial import unittest from twisted.trial import unittest
from twisted.internet import defer from twisted.internet import defer
from twisted.python import failure, log from twisted.python import failure, log
from allmydata import mutable, uri, dirnode, upload from allmydata import mutable, uri, dirnode
from allmydata.dirnode import split_netstring from allmydata.util.hashutil import tagged_hash
from allmydata.util.hashutil import netstring, tagged_hash
from allmydata.encode import NotEnoughPeersError from allmydata.encode import NotEnoughPeersError
from allmydata.interfaces import IURI, INewDirectoryURI, \ from allmydata.interfaces import IURI, INewDirectoryURI, \
IMutableFileURI, IFileNode, IUploadable, IFileURI IMutableFileURI, IUploadable, IFileURI
from allmydata.filenode import LiteralFileNode from allmydata.filenode import LiteralFileNode
import sha import sha
class Netstring(unittest.TestCase):
def test_split(self):
a = netstring("hello") + netstring("world")
self.failUnlessEqual(split_netstring(a, 2), ("hello", "world"))
self.failUnlessEqual(split_netstring(a, 2, False), ("hello", "world"))
self.failUnlessEqual(split_netstring(a, 2, True),
("hello", "world", ""))
self.failUnlessRaises(ValueError, split_netstring, a+" extra", 2)
self.failUnlessRaises(ValueError, split_netstring, a+" extra", 2, False)
def test_extra(self):
a = netstring("hello")
self.failUnlessEqual(split_netstring(a, 1, True), ("hello", ""))
b = netstring("hello") + "extra stuff"
self.failUnlessEqual(split_netstring(b, 1, True),
("hello", "extra stuff"))
def test_nested(self):
a = netstring("hello") + netstring("world") + "extra stuff"
b = netstring("a") + netstring("is") + netstring(a) + netstring(".")
top = split_netstring(b, 4)
self.failUnlessEqual(len(top), 4)
self.failUnlessEqual(top[0], "a")
self.failUnlessEqual(top[1], "is")
self.failUnlessEqual(top[2], a)
self.failUnlessEqual(top[3], ".")
self.failUnlessRaises(ValueError, split_netstring, a, 2)
self.failUnlessRaises(ValueError, split_netstring, a, 2, False)
bottom = split_netstring(a, 2, True)
self.failUnlessEqual(bottom, ("hello", "world", "extra stuff"))
class FakeFilenode(mutable.MutableFileNode): class FakeFilenode(mutable.MutableFileNode):
counter = itertools.count(1) counter = itertools.count(1)
all_contents = {} all_contents = {}
@ -441,106 +409,3 @@ class FakePrivKey:
return "PRIVKEY-%d" % self.count return "PRIVKEY-%d" % self.count
def sign(self, data): def sign(self, data):
return "SIGN(%s)" % data return "SIGN(%s)" % data
class Dirnode(unittest.TestCase):
def setUp(self):
self.client = FakeClient()
def test_create(self):
self.expected_manifest = []
d = self.client.create_empty_dirnode(wait_for_numpeers=1)
def _then(n):
self.failUnless(n.is_mutable())
u = n.get_uri()
self.failUnless(u)
self.failUnless(u.startswith("URI:DIR2:"), u)
u_ro = n.get_readonly_uri()
self.failUnless(u_ro.startswith("URI:DIR2-RO:"), u_ro)
u_v = n.get_verifier()
self.failUnless(u_v.startswith("URI:DIR2-Verifier:"), u_v)
self.expected_manifest.append(u_v)
d = n.list()
d.addCallback(lambda res: self.failUnlessEqual(res, {}))
d.addCallback(lambda res: n.has_child("missing"))
d.addCallback(lambda res: self.failIf(res))
fake_file_uri = uri.WriteableSSKFileURI("a"*16,"b"*32)
ffu_v = fake_file_uri.get_verifier().to_string()
self.expected_manifest.append(ffu_v)
d.addCallback(lambda res: n.set_uri("child", fake_file_uri))
d.addCallback(lambda res: n.create_empty_directory("subdir", wait_for_numpeers=1))
def _created(subdir):
self.failUnless(isinstance(subdir, FakeNewDirectoryNode))
self.subdir = subdir
new_v = subdir.get_verifier()
self.expected_manifest.append(new_v)
d.addCallback(_created)
d.addCallback(lambda res: n.list())
d.addCallback(lambda children:
self.failUnlessEqual(sorted(children.keys()),
sorted(["child", "subdir"])))
d.addCallback(lambda res: n.build_manifest())
def _check_manifest(manifest):
self.failUnlessEqual(sorted(manifest),
sorted(self.expected_manifest))
d.addCallback(_check_manifest)
def _add_subsubdir(res):
return self.subdir.create_empty_directory("subsubdir", wait_for_numpeers=1)
d.addCallback(_add_subsubdir)
d.addCallback(lambda res: n.get_child_at_path("subdir/subsubdir"))
d.addCallback(lambda subsubdir:
self.failUnless(isinstance(subsubdir,
FakeNewDirectoryNode)))
d.addCallback(lambda res: n.get_child_at_path(""))
d.addCallback(lambda res: self.failUnlessEqual(res.get_uri(),
n.get_uri()))
d.addCallback(lambda res: n.get_metadata_for("child"))
d.addCallback(lambda metadata: self.failUnlessEqual(metadata, {}))
d.addCallback(lambda res: n.delete("subdir"))
d.addCallback(lambda old_child:
self.failUnlessEqual(old_child.get_uri(),
self.subdir.get_uri()))
d.addCallback(lambda res: n.list())
d.addCallback(lambda children:
self.failUnlessEqual(sorted(children.keys()),
sorted(["child"])))
uploadable = upload.Data("some data")
d.addCallback(lambda res: n.add_file("newfile", uploadable))
d.addCallback(lambda newnode:
self.failUnless(IFileNode.providedBy(newnode)))
d.addCallback(lambda res: n.list())
d.addCallback(lambda children:
self.failUnlessEqual(sorted(children.keys()),
sorted(["child", "newfile"])))
d.addCallback(lambda res: n.create_empty_directory("subdir2"))
def _created2(subdir2):
self.subdir2 = subdir2
d.addCallback(_created2)
d.addCallback(lambda res:
n.move_child_to("child", self.subdir2))
d.addCallback(lambda res: n.list())
d.addCallback(lambda children:
self.failUnlessEqual(sorted(children.keys()),
sorted(["newfile", "subdir2"])))
d.addCallback(lambda res: self.subdir2.list())
d.addCallback(lambda children:
self.failUnlessEqual(sorted(children.keys()),
sorted(["child"])))
return d
d.addCallback(_then)
return d

View File

@ -1,6 +1,16 @@
import os, signal, time import os, signal, time
from twisted.internet import reactor, defer from twisted.internet import reactor, defer
from twisted.python import failure
def flip_bit(good, which):
# flip the low-order bit of good[which]
if which == -1:
pieces = good[:which], good[-1:], ""
else:
pieces = good[:which], good[which:which+1], good[which+1:]
return pieces[0] + chr(ord(pieces[1]) ^ 0x01) + pieces[2]
class SignalMixin: class SignalMixin:
# This class is necessary for any code which wants to use Processes # This class is necessary for any code which wants to use Processes
@ -37,6 +47,24 @@ class PollMixin:
reactor.callLater(pollinterval, d.callback, None) reactor.callLater(pollinterval, d.callback, None)
return d return d
class ShouldFailMixin:
def shouldFail(self, expected_failure, which, substring, callable, *args, **kwargs):
assert substring is None or isinstance(substring, str)
d = defer.maybeDeferred(callable, *args, **kwargs)
def done(res):
if isinstance(res, failure.Failure):
res.trap(expected_failure)
if substring:
self.failUnless(substring in str(res),
"substring '%s' not in '%s'"
% (substring, str(res)))
else:
self.fail("%s was supposed to raise %s, not get '%s'" %
(which, expected_failure, res))
d.addBoth(done)
return d
class TestMixin(SignalMixin): class TestMixin(SignalMixin):
def setUp(self, repeatable=False): def setUp(self, repeatable=False):