2 from zope.interface import implements
3 from twisted.trial import unittest
4 from twisted.internet import defer
5 from allmydata.interfaces import IDownloader, IUploader
6 #from allmydata.filetree.directory import (ImmutableDirectorySubTree,
9 #from allmydata.filetree.specification import (CHKFileSpecification,
10 # CHKDirectorySpecification)
11 from allmydata import workqueue
12 from cStringIO import StringIO
14 class FakeMesh(object):
15 implements(IDownloader, IUploader)
18 class FakeOpener(object):
20 def __init__(self, objects={}):
21 self.objects = objects
22 def open(self, subtree_specification, parent_is_mutable):
23 #print "open", subtree_specification, subtree_specification.serialize(), parent_is_mutable
24 return defer.succeed(self.objects[subtree_specification.serialize()])
27 class FakeWorkQueue(object):
28 implements(workqueue.IWorkQueue)
30 self.first_commands = []
31 self.last_commands = []
32 self.tempfile_number = 0
33 self.boxname_number = 0
34 def dump_commands(self):
35 return self.first_commands + self.last_commands
36 def clear_commands(self):
37 self.first_commands = []
38 self.last_commands = []
40 def create_tempfile(self, suffix=""):
41 self.tempfile_number += 1
42 self.first_commands.append("create_tempfile-%d" % self.tempfile_number)
43 return (StringIO(), "dummy_filename-%d" % self.tempfile_number)
44 def create_boxname(self):
45 self.boxname_number += 1
46 self.first_commands.append("create_boxname-%d" % self.boxname_number)
47 return "dummy_boxname-%d" % self.boxname_number
48 def add_upload_chk(self, source_filename, stash_uri_in_boxname):
49 self.first_commands.append(("upload_chk", source_filename,
50 stash_uri_in_boxname))
51 def add_upload_ssk(self, source_filename, write_capability,
53 self.first_commands.append(("upload_ssk", source_filename,
54 write_capability, previous_version))
55 def add_retain_ssk(self, read_capability):
56 self.last_commands.append(("retain_ssk", read_capability))
57 def add_unlink_ssk(self, write_capability):
58 self.last_commands.append(("unlink_ssk", write_capability))
59 def add_retain_uri_from_box(self, boxname):
60 self.last_commands.append(("retain_uri_from_box", boxname))
61 def add_addpath(self, boxname, path):
62 self.first_commands.append(("addpath", boxname, path))
63 def add_unlink_uri(self, uri):
64 self.last_commands.append(("unlink_uri", uri))
65 def add_delete_tempfile(self, filename):
66 self.first_commands.append(("delete_tempfile", filename))
67 def add_delete_box(self, boxname):
68 self.last_commands.append(("delete_box", boxname))
72 class OneSubTree(unittest.TestCase):
73 def test_create_empty_immutable(self):
74 st = ImmutableDirectorySubTree()
76 self.failIf(st.is_mutable())
77 d = st.get([], FakeOpener())
79 self.failUnless(IDirectoryNode.providedBy(root))
80 self.failUnlessEqual(root.list(), [])
81 d.addCallback(_got_root)
84 def test_immutable_1(self):
85 st = ImmutableDirectorySubTree()
87 # now populate it (by modifying the internal data structures) with
88 # some internal directories
91 three = SubTreeNode(st)
92 st.root.node_children["one"] = one
93 st.root.node_children["two"] = two
94 two.node_children["three"] = three
97 self.failIf(st.is_mutable())
101 self.failUnless(IDirectoryNode.providedBy(root))
102 self.failUnlessEqual(root.list(), ["one", "two"])
103 d.addCallback(_got_root)
104 d.addCallback(lambda res: st.get(["one"], o))
106 self.failUnlessIdentical(one, _one)
107 self.failUnless(IDirectoryNode.providedBy(_one))
108 self.failUnlessEqual(_one.list(), [])
109 d.addCallback(_got_one)
110 d.addCallback(lambda res: st.get(["two"], o))
112 self.failUnlessIdentical(two, _two)
113 self.failUnless(IDirectoryNode.providedBy(_two))
114 self.failUnlessEqual(_two.list(), ["three"])
115 d.addCallback(_got_two)
116 d.addCallback(lambda res: st.get(["two", "three"], o))
117 def _got_three(_three):
118 self.failUnlessIdentical(three, _three)
119 self.failUnless(IDirectoryNode.providedBy(_three))
120 self.failUnlessEqual(_three.list(), [])
121 d.addCallback(_got_three)
122 d.addCallback(lambda res: st.get(["missing"], o))
123 d.addCallback(self.failUnlessEqual, None)
126 def test_mutable_1(self):
129 st = MutableCHKDirectorySubTree()
132 self.failUnless(st.is_mutable())
135 self.failUnless(IDirectoryNode.providedBy(root))
136 self.failUnlessEqual(root.list(), [])
137 d.addCallback(_got_root)
138 file_three = CHKFileSpecification()
139 file_three.set_uri("file_three_uri")
140 d.addCallback(lambda res: st.add(["one", "two", "three"], file_three,
142 d.addCallback(lambda res: st.get(["one"], o))
144 self.failUnless(IDirectoryNode.providedBy(one))
145 self.failUnlessEqual(one.list(), ["two"])
146 d.addCallback(_got_one)
147 d.addCallback(lambda res: st.get(["one", "two"], o))
149 self.failUnless(IDirectoryNode.providedBy(two))
150 self.failUnlessEqual(two.list(), ["three"])
151 self.failUnlessIdentical(two.child_specifications["three"],
153 d.addCallback(_got_two)
156 def test_addpath(self):
159 st = MutableCHKDirectorySubTree()
162 file_three = CHKFileSpecification()
163 file_three.set_uri("file_three_uri")
164 d = st.add(["one", "two", "three"], file_three, o, wq)
169 ('upload_chk', 'dummy_filename-1', 'dummy_boxname-1'),
170 ('delete_tempfile', 'dummy_filename-1'),
171 ('addpath', 'dummy_boxname-1', []),
172 ('retain_uri_from_box', 'dummy_boxname-1'),
173 ('delete_box', 'dummy_boxname-1'),
174 ('unlink_uri', None),
176 self.failUnlessEqual(wq.dump_commands(), expected)
178 #for c in wq.dump_commands():
183 def test_serialize(self):
184 st = ImmutableDirectorySubTree()
186 one = SubTreeNode(st)
187 two = SubTreeNode(st)
188 three = SubTreeNode(st)
189 st.root.node_children["one"] = one
190 st.root.node_children["two"] = two
191 two.node_children["three"] = three
192 file_four = CHKFileSpecification()
193 file_four.set_uri("file_four_uri")
194 two.child_specifications["four"] = file_four
195 data = st.serialize()
196 st_new = ImmutableDirectorySubTree()
197 st_new.unserialize(data)
199 st_four = ImmutableDirectorySubTree()
201 st_four.root.node_children["five"] = SubTreeNode(st_four)
203 o = FakeOpener({("CHK-File", "file_four_uri"): st_four})
206 self.failUnless(IDirectoryNode.providedBy(root))
207 self.failUnlessEqual(root.list(), ["one", "two"])
208 d.addCallback(_got_root)
209 d.addCallback(lambda res: st.get(["two"], o))
211 self.failUnless(IDirectoryNode.providedBy(_two))
212 self.failUnlessEqual(_two.list(), ["four", "three"])
213 d.addCallback(_got_two)
215 d.addCallback(lambda res: st.get(["two", "four"], o))
216 def _got_four(_four):
217 self.failUnless(IDirectoryNode.providedBy(_four))
218 self.failUnlessEqual(_four.list(), ["five"])
219 d.addCallback(_got_four)
221 class MultipleSubTrees(unittest.TestCase):
224 st = ImmutableDirectorySubTree()
226 # populate it with some internal directories and child links and see
227 # if we can follow them
228 one = SubTreeNode(st)
229 two = SubTreeNode(st)
230 three = SubTreeNode(st)
231 st.root.node_children["one"] = one
232 st.root.node_children["two"] = two
233 two.node_children["three"] = three
235 def test_addpath(self):
237 st1 = MutableCHKDirectorySubTree()
240 one = SubTreeNode(st1)
241 two = SubTreeNode(st1)
242 st1.root.node_children["one"] = one
243 one.node_children["two"] = two
244 three = CHKDirectorySpecification()
245 three.set_uri("dir_three_uri")
246 two.child_specifications["three"] = three
248 st2 = MutableCHKDirectorySubTree()
251 four = SubTreeNode(st2)
252 five = SubTreeNode(st2)
253 st2.root.node_children["four"] = four
254 four.node_children["five"] = five
256 file_six = CHKFileSpecification()
257 file_six.set_uri("file_six_uri")
259 o = FakeOpener({("CHK-Directory", "dir_three_uri"): st2})
261 d = defer.succeed(None)
262 d.addCallback(lambda res:
263 st1.get(["one", "two", "three", "four", "five"], o))
265 self.failUnless(IDirectoryNode.providedBy(res))
266 self.failUnlessIdentical(res, five)
267 d.addCallback(_got_five)
269 d.addCallback(lambda res:
270 st1.add(["one", "two", "six"],
276 ('upload_chk', 'dummy_filename-1', 'dummy_boxname-1'),
277 ('delete_tempfile', 'dummy_filename-1'),
278 # one/two/six only modifies the top-most CHKDirectory, so
279 # the addpath that gets scheduled is targeted at the root
280 ('addpath', 'dummy_boxname-1', []),
281 ('retain_uri_from_box', 'dummy_boxname-1'),
282 ('delete_box', 'dummy_boxname-1'),
283 ('unlink_uri', None),
285 self.failUnlessEqual(wq.dump_commands(), expected)
289 d.addCallback(lambda res:
290 st1.add(["one", "two", "three", "four", "six"],
296 ('upload_chk', 'dummy_filename-2', 'dummy_boxname-2'),
297 ('delete_tempfile', 'dummy_filename-2'),
298 # one/two/three/four/six modifies the lower CHKDirectory, so
299 # we schedule an addpath of the link that points from the
300 # upper CHKDirectory to the lower one (at one/two/three).
301 ('addpath', 'dummy_boxname-2', ["one", "two", "three"]),
302 ('retain_uri_from_box', 'dummy_boxname-2'),
303 ('delete_box', 'dummy_boxname-2'),
304 ('unlink_uri', None),
306 self.failUnlessEqual(wq.dump_commands(), expected)
307 d.addCallback(_done2)
315 class Redirect(unittest.TestCase):
320 from twisted.python.failure import Failure
321 from allmydata.filetree import directory, redirect, vdrive
322 from allmydata.filetree.interfaces import (ISubTree, INode, IDirectoryNode,
323 IFileNode, NoSuchDirectoryError,
325 from allmydata.filetree.file import CHKFileNode
326 from allmydata import upload
327 from allmydata.interfaces import IDownloader
328 from allmydata.util import bencode
330 class Utils(unittest.TestCase):
331 def test_in_pairs(self):
333 pairs = list(directory.in_pairs(l))
334 self.failUnlessEqual(pairs, [(0,1), (2,3), (4,5), (6,7)])
336 class FakeMesh(object):
337 implements(IDownloader, IUploader)
343 def upload(self, uploadable):
344 uri = "stub-uri-%d" % len(self.files)
346 print "FakeMesh.upload -> %s" % uri
347 assert upload.IUploadable.providedBy(uploadable)
348 f = uploadable.get_filehandle()
350 uploadable.close_filehandle(f)
351 self.files[uri] = data
352 return defer.succeed(uri)
354 def upload_filename(self, filename):
356 print "FakeMesh.upload_filename(%s)" % filename
357 return self.upload(upload.FileName(filename))
359 def upload_data(self, data):
361 print "FakeMesh.upload_data(%s)" % data
362 return self.upload(upload.Data(data))
364 def download(self, uri, target):
366 print "FakeMesh.download(%s)" % uri
368 target.write(self.files[uri])
370 return defer.maybeDeferred(target.finish)
373 class VDrive(unittest.TestCase):
375 def makeVirtualDrive(self, basedir, root_node=None, mesh=None):
376 wq = workqueue.WorkQueue(os.path.join("test_filetree",
378 basedir, "1.workqueue"))
380 assert IUploader.providedBy(mesh)
381 assert IDownloader.providedBy(mesh)
386 root_node = directory.LocalFileSubTreeNode()
387 root_node.new("rootdirtree.save")
388 v = vdrive.VirtualDrive(wq, dl, ul, root_node)
391 def makeLocalTree(self, basename):
392 # create a LocalFileRedirection pointing at a LocalFileSubTree.
393 # Returns a VirtualDrive instance.
394 topdir = directory.LocalFileSubTree().new("%s-dirtree.save" % basename)
395 topdir.update_now(None)
396 root = redirect.LocalFileRedirection().new("%s-root" % basename,
397 topdir.create_node_now())
398 root.update_now(None)
399 v = self.makeVirtualDrive("%s-vdrive" % basename,
400 root.create_node_now())
403 def makeCHKTree(self, basename):
404 # create a LocalFileRedirection pointing at a CHKDirectorySubTree.
405 # Returns a VirtualDrive instance.
407 topdir = directory.CHKDirectorySubTree().new()
408 d = topdir.update_now(mesh)
409 def _updated(topnode):
410 root = redirect.LocalFileRedirection()
411 root.new("%s-root" % basename, topnode)
412 return root.update_now(mesh)
413 d.addCallback(_updated)
414 d.addCallback(lambda rootnode:
415 self.makeVirtualDrive("%s-vdrive" % basename,
419 def failUnlessListsAreEqual(self, list1, list2):
420 self.failUnlessEqual(sorted(list1), sorted(list2))
422 def failUnlessContentsAreEqual(self, c1, c2):
423 c1a = dict([(k,v.serialize_node()) for k,v in c1.items()])
424 c2a = dict([(k,v.serialize_node()) for k,v in c2.items()])
425 self.failUnlessEqual(c1a, c2a)
427 def testDirectory(self):
428 stm = vdrive.SubTreeMaker(FakeMesh())
430 # create an empty directory (stored locally)
431 subtree = directory.LocalFileSubTree()
432 subtree.new("dirtree.save")
433 self.failUnless(ISubTree.providedBy(subtree))
435 # get the root IDirectoryNode (which is still empty) and examine it
436 (found_path, root, remaining_path) = subtree.get_node_for_path([])
437 self.failUnlessEqual(found_path, [])
438 self.failUnlessEqual(remaining_path, [])
439 self.failUnless(INode.providedBy(root))
440 self.failUnless(IDirectoryNode.providedBy(root))
441 self.failUnlessListsAreEqual(root.list().keys(), [])
442 self.failUnlessIdentical(root.get_subtree(), subtree)
444 # now add some children to it
445 subdir1 = root.add_subdir("subdir1")
446 file1 = CHKFileNode()
448 root.add("foo.txt", file1)
449 self.failUnlessListsAreEqual(root.list().keys(),
450 ["foo.txt", "subdir1"])
451 self.failUnlessIdentical(root.get("foo.txt"), file1)
452 subdir1a = root.get("subdir1")
453 self.failUnlessIdentical(subdir1, subdir1a)
455 self.failUnless(IDirectoryNode.providedBy(subdir1))
456 self.failUnlessListsAreEqual(subdir1.list().keys(), [])
457 self.failUnlessIdentical(subdir1.get_subtree(), subtree)
459 subdir2 = subdir1.add_subdir("subdir2")
460 subdir3 = subdir2.add_subdir("subdir3")
461 subdir4 = subdir2.add_subdir("subdir4")
463 subdir2.delete("subdir4")
464 self.failUnlessListsAreEqual(subdir2.list().keys(), ["subdir3"])
466 del root, subdir1, subdir2, subdir3, subdir4
467 # leaving file1 for later use
469 # now serialize it and examine the results
471 subtree.serialize_subtree_to_file(f)
474 unpacked = bencode.bdecode(data)
476 del f, data, unpacked
478 node = subtree.create_node_now()
479 self.failUnless(isinstance(node, directory.LocalFileSubTreeNode))
480 node_s = node.serialize_node()
481 self.failUnless(isinstance(node_s, str))
482 self.failUnless(node_s.startswith("LocalFileDirectory:"))
483 self.failUnless("dirtree.save" in node_s)
486 d = defer.maybeDeferred(subtree.update_now, None)
489 return stm.make_subtree_from_node(node, False)
490 d.addCallback(_updated)
492 def _opened(new_subtree):
493 res = new_subtree.get_node_for_path([])
494 (found_path, root, remaining_path) = res
495 self.failUnlessEqual(found_path, [])
496 self.failUnlessEqual(remaining_path, [])
497 self.failUnless(INode.providedBy(root))
498 self.failUnless(IDirectoryNode.providedBy(root))
499 self.failUnlessListsAreEqual(root.list().keys(),
500 ["foo.txt", "subdir1"])
501 file1a = root.get("foo.txt")
502 self.failUnless(INode(file1a))
503 self.failUnless(isinstance(file1a, CHKFileNode))
504 self.failUnless(IFileNode(file1a))
505 self.failUnlessEqual(file1a.get_uri(), "uri1")
506 subdir1 = root.get("subdir1")
507 subdir2 = subdir1.get("subdir2")
508 self.failUnlessListsAreEqual(subdir2.list().keys(), ["subdir3"])
509 subdir2.delete("subdir3")
510 self.failUnlessListsAreEqual(subdir2.list().keys(), [])
511 d.addCallback(_opened)
514 def shouldFail(self, res, expected_failure, which):
515 if isinstance(res, Failure):
516 res.trap(expected_failure)
518 self.fail("%s was supposed to raise %s, not get '%s'" %
519 (which, expected_failure, res))
521 def testVdrive(self):
522 v = self.makeLocalTree("vdrive")
525 def _listed(contents):
526 self.failUnlessEqual(contents, {})
527 d.addCallback(_listed)
529 child1 = CHKFileNode().new("uri1")
530 d.addCallback(lambda res: v.add_node(["a"], child1))
531 d.addCallback(lambda res: v.workqueue.flush())
532 d.addCallback(lambda res: v.list([]))
533 def _listed2(contents):
534 self.failUnlessListsAreEqual(contents.keys(), ["a"])
535 self.failUnlessContentsAreEqual(contents, {"a": child1})
536 d.addCallback(_listed2)
537 child2 = CHKFileNode().new("uri2")
538 child3 = CHKFileNode().new("uri3")
539 d.addCallback(lambda res: v.add_node(["b","c"], child2))
540 d.addCallback(lambda res: v.add_node(["b","d"], child3))
541 d.addCallback(lambda res: v.workqueue.flush())
542 d.addCallback(lambda res: v.list([]))
543 def _listed3(contents):
544 self.failUnlessListsAreEqual(contents.keys(), ["a","b"])
545 d.addCallback(_listed3)
546 d.addCallback(lambda res: v.list(["b"]))
547 def _listed4(contents):
548 self.failUnlessListsAreEqual(contents.keys(), ["c","d"])
549 self.failUnlessContentsAreEqual(contents,
550 {"c": child2, "d": child3})
551 d.addCallback(_listed4)
553 d.addCallback(lambda res: v._get_file_uri(["b","c"]))
554 d.addCallback(self.failUnlessEqual, "uri2")
556 d.addCallback(lambda res: v.list(["bogus"]))
557 d.addBoth(self.shouldFail, NoSuchDirectoryError, "list(bogus)")
559 d.addCallback(lambda res: v._get_file_uri(["b", "bogus"]))
560 d.addBoth(self.shouldFail, NoSuchChildError, "_get_file_uri(b/bogus)")
564 def testUpload(self):
565 v = self.makeLocalTree("upload")
567 DATA = "here is some data\n"
568 f = open(filename, "wb")
572 rc = v.upload(["a","b","upload1"], filename)
573 self.failUnlessIdentical(rc, None)
575 d = v.workqueue.flush()
577 d.addCallback(lambda res: v.list([]))
578 d.addCallback(lambda contents:
579 self.failUnlessListsAreEqual(contents.keys(), ["a"]))
580 d.addCallback(lambda res: v.list(["a"]))
581 d.addCallback(lambda contents:
582 self.failUnlessListsAreEqual(contents.keys(), ["b"]))
583 d.addCallback(lambda res: v.list(["a","b"]))
584 d.addCallback(lambda contents:
585 self.failUnlessListsAreEqual(contents.keys(),
587 d.addCallback(lambda res: v.download_as_data(["a","b","upload1"]))
588 d.addCallback(self.failUnlessEqual, DATA)
592 def testCHKDirUpload(self):
593 DATA = "here is some data\n"
595 f = open(filename, "wb")
599 d = defer.maybeDeferred(self.makeCHKTree, "chk-upload")
603 rc = v.upload(["a","b","upload1"], filename)
604 self.failUnlessIdentical(rc, None)
606 return v.workqueue.flush()
609 d.addCallback(lambda res: self.v.list([]))
610 d.addCallback(lambda contents:
611 self.failUnlessListsAreEqual(contents.keys(), ["a"]))
612 d.addCallback(lambda res: self.v.list(["a"]))
613 d.addCallback(lambda contents:
614 self.failUnlessListsAreEqual(contents.keys(), ["b"]))
615 d.addCallback(lambda res: self.v.list(["a","b"]))
616 d.addCallback(lambda contents:
617 self.failUnlessListsAreEqual(contents.keys(),
619 d.addCallback(lambda res: self.v.download_as_data(["a","b","upload1"]))
620 d.addCallback(self.failUnlessEqual, DATA)
624 def testCHKDirDelete(self):
625 DATA = "here is some data\n"
627 f = open(filename, "wb")
631 d = defer.maybeDeferred(self.makeCHKTree, "chk-delete")
636 d.addCallback(lambda r:
637 self.v.upload(["a","b","upload1"], filename))
638 d.addCallback(lambda r:
639 self.v.upload_data(["a","b","upload2"], DATA))
640 d.addCallback(lambda r:
641 self.v.upload(["a","c","upload3"], filename))
642 d.addCallback(lambda r:
643 self.v.workqueue.flush())
645 d.addCallback(lambda r: self.v.list([]))
646 d.addCallback(lambda contents:
647 self.failUnlessListsAreEqual(contents.keys(), ["a"]))
648 d.addCallback(lambda r: self.v.list(["a"]))
649 d.addCallback(lambda contents:
650 self.failUnlessListsAreEqual(contents.keys(), ["b","c"]))
651 d.addCallback(lambda r: self.v.list(["a","b"]))
652 d.addCallback(lambda contents:
653 self.failUnlessListsAreEqual(contents.keys(),
654 ["upload1", "upload2"]))
655 #d.addCallback(lambda r: self.v.download_as_data(["a","b","upload1"]))
656 #d.addCallback(self.failUnlessEqual, DATA)
659 d.addCallback(lambda r: self.v.delete(["a","b","upload2"]))
660 d.addCallback(lambda r: self.v.workqueue.flush())
661 d.addCallback(lambda r: self.v.list(["a","b"]))
662 d.addCallback(lambda contents:
663 self.failUnlessListsAreEqual(contents.keys(),