Merge lp:~eric97/bzr/dump-btree-api into lp:bzr

Proposed by Eric Siegerman
Status: Work in progress
Proposed branch: lp:~eric97/bzr/dump-btree-api
Merge into: lp:bzr
Diff against target: 410 lines (+202/-139)
4 files modified
bzrlib/btree_index.py (+50/-0)
bzrlib/builtins.py (+4/-56)
bzrlib/tests/blackbox/test_dump_btree.py (+35/-83)
bzrlib/tests/test_btree_index.py (+113/-0)
To merge this branch: bzr merge lp:~eric97/bzr/dump-btree-api
Reviewer Review Type Date Requested Status
Jelmer Vernooij (community) code Approve
Vincent Ladeuil Approve
Review via email: mp+50107@code.launchpad.net

Commit message

Create a public API for generating (quasi-)text dumps of B-Tree index files, by turning two private methods in builtins.py into public methods on bzrlib.btree_index.BTreeGraphIndex.

Description of the change

A review comment for another MP of mine (https://code.launchpad.net/~eric97/bzr/regenerate-pack-names) asked that my tests use API calls instead of run_bzr('dump-btree') to dump B-Tree index files. The problem is, there is no API for that; the implementation of "bzr dump-btree" lives directly in builtins, as private methods of cmd_dump_btree.

This MP moves those methods to bzrlib.btree_index.BTreeGraphIndex, and makes them public. It is a pure refactoring; it is intended that there be no change in functionality.

To post a comment you must log in.
Revision history for this message
Vincent Ladeuil (vila) wrote :

Nicely done !
Just one tweak from me but good to land otherwise:

151 + test_raw_output_3nodes = (
152 + 'Root node:\n'
153 + 'B+Tree Graph Index 2\n'

You can just use:

test_raw_output_3nodes = '''Root node:
B+Tree Graph Index 2
...
'''

Or even:

test_raw_output_3nodes = '''\
Root node:
B+Tree Graph Index 2
...
'''

Easier to copy/paste (all rules have exceptions, copy/paste is bad, but copying an expected result from a failed test is way faster than typing it from scratch).

Nice to see tests going from blackbox to unit !

I let our PP do the second review ;)

review: Approve
Revision history for this message
Eric Siegerman (eric97) wrote :

On Thu, 2011-02-17 at 09:46 +0000, Vincent Ladeuil wrote:
> Review: Approve
> Nicely done !

Thanks.

> You can just use:
>
> test_raw_output_3nodes = '''Root node:
> B+Tree Graph Index 2
> ...
> '''

That won't match, so it requires a change -- either:
    test_raw_output_0nodes = re.sub("(?m)^ *", "",
        '''Root node:
        B+Tree Graph Index 2
        ...
        ''')
or:
    test_raw_output_0nodes = \
'''Root node:
B+Tree Graph Index 2
...
'''

Is it still an improvement? If so, which is preferable?

> (all rules have exceptions, copy/paste is bad, but copying an expected
result from a failed test is way faster than typing it from scratch).

I don't understand: why is one easier to copy than the other?

As for whether copying is bad, well, those data definitions, and most of
the method that reads them, are already duplicated between the two test
files. I didn't like doing that, but liked even less to reach across
from one to the other:
    self.assertEquals(some_other_module.SomeClass.expected_value,
actual_value)
Plus, for the module that does that, it would put the test data far from
the tests themselves, thus awkward to look up.

  - Eric

lp:~eric97/bzr/dump-btree-api updated
5672. By Eric Siegerman <email address hidden>

Remove another blackbox test that's now redundant.

Revision history for this message
Vincent Ladeuil (vila) wrote :

> On Thu, 2011-02-17 at 09:46 +0000, Vincent Ladeuil wrote:
> > Review: Approve
> > Nicely done !
>
> Thanks.
>
> > You can just use:
> >
> > test_raw_output_3nodes = '''Root node:
> > B+Tree Graph Index 2
> > ...
> > '''
>
> That won't match, so it requires a change -- either:
> test_raw_output_0nodes = re.sub("(?m)^ *", "",
> '''Root node:
> B+Tree Graph Index 2
> ...
> ''')

I meant: use a triple quoting string and don't indent the lines in it so the string matches the output of the command without adding all the quotes and the '\n's.

> or:
> test_raw_output_0nodes = \
> '''Root node:
> B+Tree Graph Index 2
> ...
> '''
>

Same as my second example, don't worry, it's just a matter of taste, *I* dislike the '= \' and prefer '="""\" instead, no big deal, anyway I misread it as being defined at the top level where no leading spaces won't break the reading. poolie disliked embedding such strings in classes anyway, so forget about it.

> I don't understand: why is one easier to copy than the other?

Both of my examples are as easy to copy (the second one slightly more) and your second example is easy as well. The point is to avoid the additional quotes and '\n's.

>
> As for whether copying is bad, well, those data definitions, and most of
> the method that reads them, are already duplicated between the two test
> files. I didn't like doing that, but liked even less to reach across
> from one to the other:
> self.assertEquals(some_other_module.SomeClass.expected_value,
> actual_value)
> Plus, for the module that does that, it would put the test data far from
> the tests themselves, thus awkward to look up.

Right, the counter-argument is that you'll need to update both files if the output format change. There is no perfect answer to this problem when the tests can't be in the same file. In the end, it's a matter of taste and pragmatism.

Revision history for this message
Eric Siegerman (eric97) wrote :

On Thu, 2011-02-17 at 15:47 +0000, Vincent Ladeuil wrote:
> [...] I misread it as being defined at the top level where no
> leading spaces won't break the reading.

Then I'd prefer to leave it as is, if it's OK with you. That's
how it was in the original, anyway...

  - Eric

Revision history for this message
Jelmer Vernooij (jelmer) wrote :

Seems reasonable to me, submitting to PQM...

review: Approve (code)
Revision history for this message
Jelmer Vernooij (jelmer) wrote :

sent to pqm by email

Revision history for this message
Eric Siegerman (eric97) wrote :

Jelmer:

Did this go to PQM yet? If not, good! Please don't (re)send it yet; I've found a bug. It won't break anything existing, but still, it'd be nice to clean it up before the MP lands.

Thanks much,
  - Eric

Revision history for this message
Jelmer Vernooij (jelmer) wrote :

Hi Eric,

On Fri, 2011-02-18 at 17:58 +0000, Eric Siegerman wrote:
> Jelmer:
>
> Did this go to PQM yet? If not, good! Please don't (re)send it yet; I've found a bug. It won't break anything existing, but still, it'd be nice to clean it up before the MP lands.
It's been sent off to PQM but failed (don't recall the reason but I can
look it up).

I'll set the MP back to Work In Progress for the moment so we don't
accidentally submit it again. Just let us know when it's ready again. :)

Cheers,

Jelmer

Revision history for this message
Eric Siegerman (eric97) wrote :

On Fri, 2011-02-18 at 18:25 +0000, Jelmer Vernooij wrote:
> It's been sent off to PQM but failed (don't recall the reason but I
can
> look it up).

That'd be helpful.

Is the status of PQM jobs available to non-core folks so we don't
have to pester you guys? I found the queue page at
http://pqm.bazaar-vcs.org/, but it doesn't list completed jobs,
nor offer a link to a page that does. All I knew from Launchpad
and email was that it had been sent, and then no further status,
just silence. Or have I been looking in the wrong place?

> I'll set the MP back to Work In Progress for the moment so we don't
> accidentally submit it again. Just let us know when it's ready
again. :)

OK; will do.

Thanks,
  - Eric

Revision history for this message
Jelmer Vernooij (jelmer) wrote :

On Fri, 2011-02-18 at 19:02 +0000, Eric Siegerman wrote:
> On Fri, 2011-02-18 at 18:25 +0000, Jelmer Vernooij wrote:
> > It's been sent off to PQM but failed (don't recall the reason but I
> can
> > look it up).
> That'd be helpful.
>
> Is the status of PQM jobs available to non-core folks so we don't
> have to pester you guys? I found the queue page at
> http://pqm.bazaar-vcs.org/, but it doesn't list completed jobs,
> nor offer a link to a page that does. All I knew from Launchpad
> and email was that it had been sent, and then no further status,
> just silence. Or have I been looking in the wrong place?
It's not available in a private place either :) PQM sends the output of
each job to the submitter of that job, in this case myself. I'll
forward that email to you.

> > I'll set the MP back to Work In Progress for the moment so we don't
> > accidentally submit it again. Just let us know when it's ready
> again. :)
> OK; will do.
Thanks!

Cheers,

Jelmer

Revision history for this message
Vincent Ladeuil (vila) wrote :

<snip/>
    > Is the status of PQM jobs available to non-core folks so we don't
    > have to pester you guys? I found the queue page at
    > http://pqm.bazaar-vcs.org/, but it doesn't list completed jobs,

That's because you don't refresh it often enough[1], if you are very very
lucky you may able to catch the number of failures and if you are even
more lucky you can get the name of the failing test(s).

    > nor offer a link to a page that does.

None exists.

    > All I knew from Launchpad and email was that it had been sent, and
    > then no further status,

...

    > just silence.

Yeah, the painful silence usually means something went wrong :-(

This is certainly one of the worst feedback you could get for a test
failure...

First steps of defense:

- run the full test suite locally,

- run make check | subunit2pyunit locally,

- run make check with python2.4 locally

If they all pass, bangs your head on the desktop (find a soft desktop if
you repeat the later too often) and nag the patch pilot :-/

If you've reached a high enough pain threshold, you can file a bug.

[1]: That's a painful joke if you didn't notice :)

Unmerged revisions

5672. By Eric Siegerman <email address hidden>

Remove another blackbox test that's now redundant.

5671. By Eric Siegerman <email address hidden>

Factor test data out of the blackbox tests too.

5670. By Eric Siegerman <email address hidden>

Don't need empty-btree blackbox tests, now that we have them as unit tests.

5669. By Eric Siegerman <email address hidden>

A little code-formatting cleanup.

5668. By Eric Siegerman <email address hidden>

Factor out the test data.

5667. By Eric Siegerman <email address hidden>

Adapt the new test_btree_index btree-dumping tests to use the new API
instead of calling run_bzr().

5666. By Eric Siegerman <email address hidden>

Delete a redundant test.

There's no need to test over multiple transports that the methods can
cope with a no-leaf-nodes B-Tree; local transport will suffice.

5665. By Eric Siegerman <email address hidden>

Add docstrings.

5664. By Eric Siegerman <email address hidden>

Move dump_raw_bytes() and dump_entries() from cmd_dump_btree to btree_index,
and adapt them to their new location.

5663. By Eric Siegerman <email address hidden>

Copy all tests verbatim from blackbox/test_dump_btree.py to test_btree_index.py.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'bzrlib/btree_index.py'
2--- bzrlib/btree_index.py 2010-08-06 15:38:24 +0000
3+++ bzrlib/btree_index.py 2011-02-17 14:57:46 +0000
4@@ -1584,6 +1584,56 @@
5 for node in self._read_nodes(range(start_node, node_end)):
6 pass
7
8+ def dump_raw_bytes(self, out_file):
9+ """Produce a raw dump of this B-Tree.
10+
11+ Dump the header and the pages. The pages are decompressed, but
12+ otherwise uninterpreted.
13+
14+ :param out_file: File-like object to write the dump to.
15+ """
16+ # We need to parse at least the root node.
17+ # This is because the first page of every row starts with an
18+ # uncompressed header.
19+ bytes = self._transport.get_bytes(self._name)
20+ for page_idx, page_start in enumerate(xrange(0, len(bytes),
21+ _PAGE_SIZE)):
22+ page_end = min(page_start + _PAGE_SIZE, len(bytes))
23+ page_bytes = bytes[page_start:page_end]
24+ if page_idx == 0:
25+ out_file.write('Root node:\n')
26+ header_end, data = self._parse_header_from_bytes(page_bytes)
27+ out_file.write(page_bytes[:header_end])
28+ page_bytes = data
29+ out_file.write('\nPage %d\n' % (page_idx,))
30+ if len(page_bytes) == 0:
31+ out_file.write('(empty)\n');
32+ else:
33+ decomp_bytes = zlib.decompress(page_bytes)
34+ out_file.write(decomp_bytes)
35+ out_file.write('\n')
36+
37+ def dump_entries(self, out_file):
38+ """Produce a formatted dump of this B-Tree.
39+
40+ Dump the leaf nodes, formatted as nested tuples. The header is
41+ not dumped, so if the tree contains no leaf nodes, this method
42+ produces no output.
43+
44+ :param out_file: File-like object to write the dump to.
45+ """
46+ for node in self.iter_all_entries():
47+ # Node is made up of:
48+ # (index, key, value, [references])
49+ try:
50+ refs = node[3]
51+ except IndexError:
52+ refs_as_tuples = None
53+ else:
54+ refs_as_tuples = static_tuple.as_tuples(refs)
55+ as_tuple = (tuple(node[1]), node[2], refs_as_tuples)
56+ out_file.write('%s\n' % (as_tuple,))
57+
58
59 _gcchk_factory = _LeafNode
60
61
62=== modified file 'bzrlib/builtins.py'
63--- bzrlib/builtins.py 2011-02-09 17:10:05 +0000
64+++ bzrlib/builtins.py 2011-02-17 14:57:46 +0000
65@@ -383,63 +383,11 @@
66 def run(self, path, raw=False):
67 dirname, basename = osutils.split(path)
68 t = transport.get_transport(dirname)
69+ btree = btree_index.BTreeGraphIndex(t, basename, None)
70 if raw:
71- self._dump_raw_bytes(t, basename)
72- else:
73- self._dump_entries(t, basename)
74-
75- def _get_index_and_bytes(self, trans, basename):
76- """Create a BTreeGraphIndex and raw bytes."""
77- bt = btree_index.BTreeGraphIndex(trans, basename, None)
78- bytes = trans.get_bytes(basename)
79- bt._file = cStringIO.StringIO(bytes)
80- bt._size = len(bytes)
81- return bt, bytes
82-
83- def _dump_raw_bytes(self, trans, basename):
84- import zlib
85-
86- # We need to parse at least the root node.
87- # This is because the first page of every row starts with an
88- # uncompressed header.
89- bt, bytes = self._get_index_and_bytes(trans, basename)
90- for page_idx, page_start in enumerate(xrange(0, len(bytes),
91- btree_index._PAGE_SIZE)):
92- page_end = min(page_start + btree_index._PAGE_SIZE, len(bytes))
93- page_bytes = bytes[page_start:page_end]
94- if page_idx == 0:
95- self.outf.write('Root node:\n')
96- header_end, data = bt._parse_header_from_bytes(page_bytes)
97- self.outf.write(page_bytes[:header_end])
98- page_bytes = data
99- self.outf.write('\nPage %d\n' % (page_idx,))
100- if len(page_bytes) == 0:
101- self.outf.write('(empty)\n');
102- else:
103- decomp_bytes = zlib.decompress(page_bytes)
104- self.outf.write(decomp_bytes)
105- self.outf.write('\n')
106-
107- def _dump_entries(self, trans, basename):
108- try:
109- st = trans.stat(basename)
110- except errors.TransportNotPossible:
111- # We can't stat, so we'll fake it because we have to do the 'get()'
112- # anyway.
113- bt, _ = self._get_index_and_bytes(trans, basename)
114- else:
115- bt = btree_index.BTreeGraphIndex(trans, basename, st.st_size)
116- for node in bt.iter_all_entries():
117- # Node is made up of:
118- # (index, key, value, [references])
119- try:
120- refs = node[3]
121- except IndexError:
122- refs_as_tuples = None
123- else:
124- refs_as_tuples = static_tuple.as_tuples(refs)
125- as_tuple = (tuple(node[1]), node[2], refs_as_tuples)
126- self.outf.write('%s\n' % (as_tuple,))
127+ btree.dump_raw_bytes(self.outf)
128+ else:
129+ btree.dump_entries(self.outf)
130
131
132 class cmd_remove_tree(Command):
133
134=== modified file 'bzrlib/tests/blackbox/test_dump_btree.py'
135--- bzrlib/tests/blackbox/test_dump_btree.py 2011-02-08 23:06:34 +0000
136+++ bzrlib/tests/blackbox/test_dump_btree.py 2011-02-17 14:57:46 +0000
137@@ -28,12 +28,36 @@
138
139 class TestDumpBtree(tests.TestCaseWithTransport):
140
141- def create_sample_btree_index(self):
142+ # Each node is represented as the *string value* that
143+ # BTreeGraphIndex.dump_entries() should produce for it.
144+ test_data_3nodes = (
145+ "(('test', 'key1'), 'value', ((('ref', 'entry'),),))\n",
146+ "(('test', 'key2'), 'value2', ((('ref', 'entry2'),),))\n",
147+ "(('test2', 'key3'), 'value3', ((('ref', 'entry3'),),))\n",
148+ )
149+ test_input_3nodes = [eval(node) for node in test_data_3nodes]
150+ test_output_3nodes = "".join(test_data_3nodes)
151+ test_raw_output_3nodes = (
152+ 'Root node:\n'
153+ 'B+Tree Graph Index 2\n'
154+ 'node_ref_lists=1\n'
155+ 'key_elements=2\n'
156+ 'len=3\n'
157+ 'row_lengths=1\n'
158+ '\n'
159+ 'Page 0\n'
160+ 'type=leaf\n'
161+ 'test\0key1\0ref\0entry\0value\n'
162+ 'test\0key2\0ref\0entry2\0value2\n'
163+ 'test2\0key3\0ref\0entry3\0value3\n'
164+ '\n'
165+ )
166+
167+ def create_sample_btree_index(self, node_data):
168 builder = btree_index.BTreeBuilder(
169 reference_lists=1, key_elements=2)
170- builder.add_node(('test', 'key1'), 'value', ((('ref', 'entry'),),))
171- builder.add_node(('test', 'key2'), 'value2', ((('ref', 'entry2'),),))
172- builder.add_node(('test2', 'key3'), 'value3', ((('ref', 'entry3'),),))
173+ for node in node_data:
174+ builder.add_node(*node)
175 out_f = builder.finish()
176 try:
177 self.build_tree_contents([('test.btree', out_f.read())])
178@@ -41,90 +65,18 @@
179 out_f.close()
180
181 def test_dump_btree_smoke(self):
182- self.create_sample_btree_index()
183+ self.create_sample_btree_index(self.test_input_3nodes)
184 out, err = self.run_bzr('dump-btree test.btree')
185- self.assertEqualDiff(
186- "(('test', 'key1'), 'value', ((('ref', 'entry'),),))\n"
187- "(('test', 'key2'), 'value2', ((('ref', 'entry2'),),))\n"
188- "(('test2', 'key3'), 'value3', ((('ref', 'entry3'),),))\n",
189- out)
190+ self.assertEqualDiff(self.test_output_3nodes, out)
191
192 def test_dump_btree_http_smoke(self):
193 self.transport_readonly_server = http_server.HttpServer
194- self.create_sample_btree_index()
195+ self.create_sample_btree_index(self.test_input_3nodes)
196 url = self.get_readonly_url('test.btree')
197 out, err = self.run_bzr(['dump-btree', url])
198- self.assertEqualDiff(
199- "(('test', 'key1'), 'value', ((('ref', 'entry'),),))\n"
200- "(('test', 'key2'), 'value2', ((('ref', 'entry2'),),))\n"
201- "(('test2', 'key3'), 'value3', ((('ref', 'entry3'),),))\n",
202- out)
203+ self.assertEqualDiff(self.test_output_3nodes, out)
204
205 def test_dump_btree_raw_smoke(self):
206- self.create_sample_btree_index()
207- out, err = self.run_bzr('dump-btree test.btree --raw')
208- self.assertEqualDiff(
209- 'Root node:\n'
210- 'B+Tree Graph Index 2\n'
211- 'node_ref_lists=1\n'
212- 'key_elements=2\n'
213- 'len=3\n'
214- 'row_lengths=1\n'
215- '\n'
216- 'Page 0\n'
217- 'type=leaf\n'
218- 'test\0key1\0ref\0entry\0value\n'
219- 'test\0key2\0ref\0entry2\0value2\n'
220- 'test2\0key3\0ref\0entry3\0value3\n'
221- '\n',
222- out)
223-
224- def test_dump_btree_no_refs_smoke(self):
225- # A BTree index with no ref lists (such as *.cix) can be dumped without
226- # errors.
227- builder = btree_index.BTreeBuilder(
228- reference_lists=0, key_elements=2)
229- builder.add_node(('test', 'key1'), 'value')
230- out_f = builder.finish()
231- try:
232- self.build_tree_contents([('test.btree', out_f.read())])
233- finally:
234- out_f.close()
235- out, err = self.run_bzr('dump-btree test.btree')
236-
237- def create_sample_empty_btree_index(self):
238- builder = btree_index.BTreeBuilder(
239- reference_lists=1, key_elements=2)
240- out_f = builder.finish()
241- try:
242- self.build_tree_contents([('test.btree', out_f.read())])
243- finally:
244- out_f.close()
245-
246- def test_dump_empty_btree_smoke(self):
247- self.create_sample_empty_btree_index()
248- out, err = self.run_bzr('dump-btree test.btree')
249- self.assertEqualDiff("", out)
250-
251- def test_dump_empty_btree_http_smoke(self):
252- self.transport_readonly_server = http_server.HttpServer
253- self.create_sample_empty_btree_index()
254- url = self.get_readonly_url('test.btree')
255- out, err = self.run_bzr(['dump-btree', url])
256- self.assertEqualDiff("", out)
257-
258- def test_dump_empty_btree_raw_smoke(self):
259- self.create_sample_empty_btree_index()
260- out, err = self.run_bzr('dump-btree test.btree --raw')
261- self.assertEqualDiff(
262- 'Root node:\n'
263- 'B+Tree Graph Index 2\n'
264- 'node_ref_lists=1\n'
265- 'key_elements=2\n'
266- 'len=0\n'
267- 'row_lengths=\n'
268- '\n'
269- 'Page 0\n'
270- '(empty)\n',
271- out)
272-
273+ self.create_sample_btree_index(self.test_input_3nodes)
274+ out, err = self.run_bzr('dump-btree test.btree --raw')
275+ self.assertEqualDiff(self.test_raw_output_3nodes, out)
276
277=== modified file 'bzrlib/tests/test_btree_index.py'
278--- bzrlib/tests/test_btree_index.py 2011-01-27 14:27:18 +0000
279+++ bzrlib/tests/test_btree_index.py 2011-02-17 14:57:46 +0000
280@@ -17,6 +17,7 @@
281
282 """Tests for btree indices."""
283
284+import cStringIO
285 import pprint
286 import zlib
287
288@@ -31,6 +32,7 @@
289 )
290 from bzrlib.tests import (
291 TestCaseWithTransport,
292+ http_server,
293 scenarios,
294 )
295
296@@ -1522,3 +1524,114 @@
297 self.set_cached_offsets(index, [0, 1, 2, 3, 4, 5, 6, 7, 100])
298 self.assertExpandOffsets([102, 103, 104, 105, 106, 107, 108], index,
299 [105])
300+
301+
302+class TestDumpBtree(tests.TestCaseWithTransport):
303+
304+ # Each node is represented as the *string value* that
305+ # BTreeGraphIndex.dump_entries() should produce for it.
306+ test_data_3nodes = (
307+ "(('test', 'key1'), 'value', ((('ref', 'entry'),),))\n",
308+ "(('test', 'key2'), 'value2', ((('ref', 'entry2'),),))\n",
309+ "(('test2', 'key3'), 'value3', ((('ref', 'entry3'),),))\n",
310+ )
311+ test_input_3nodes = [eval(node) for node in test_data_3nodes]
312+ test_output_3nodes = "".join(test_data_3nodes)
313+ test_raw_output_3nodes = (
314+ 'Root node:\n'
315+ 'B+Tree Graph Index 2\n'
316+ 'node_ref_lists=1\n'
317+ 'key_elements=2\n'
318+ 'len=3\n'
319+ 'row_lengths=1\n'
320+ '\n'
321+ 'Page 0\n'
322+ 'type=leaf\n'
323+ 'test\0key1\0ref\0entry\0value\n'
324+ 'test\0key2\0ref\0entry2\0value2\n'
325+ 'test2\0key3\0ref\0entry3\0value3\n'
326+ '\n'
327+ )
328+
329+ def create_sample_btree_and_outf(self, node_data):
330+ """Create a simple B-Tree with the given content.
331+
332+ As a convenience, also creates an empty StringIO, which the caller can
333+ pass to the method under test as the destination for the dump.
334+
335+ :param node_data: An iterable of 3-tuples (key, value, refs), one tuple
336+ per leaf node in the desired tree,
337+ :return: (btree, outf_for_dump)
338+ """
339+ builder = btree_index.BTreeBuilder(
340+ reference_lists=1, key_elements=2)
341+ for node in node_data:
342+ builder.add_node(*node)
343+ out_f = builder.finish()
344+ try:
345+ self.build_tree_contents([('test.btree', out_f.read())])
346+ finally:
347+ out_f.close()
348+ bt = btree_index.BTreeGraphIndex(self.get_transport(''),
349+ 'test.btree', None)
350+ outf_for_dump = cStringIO.StringIO()
351+ return bt, outf_for_dump
352+
353+ def test_dump_btree_smoke(self):
354+ bt, outf = self.create_sample_btree_and_outf(self.test_input_3nodes)
355+ bt.dump_entries(outf)
356+ self.assertEqualDiff(self.test_output_3nodes, outf.getvalue())
357+
358+ def test_dump_btree_http_smoke(self):
359+ self.transport_readonly_server = http_server.HttpServer
360+ _, outf = self.create_sample_btree_and_outf(self.test_input_3nodes)
361+ url = self.get_readonly_url()
362+ t = transport.get_transport(url)
363+ bt = btree_index.BTreeGraphIndex(t, 'test.btree', None)
364+ bt.dump_entries(outf)
365+ self.assertEqualDiff(self.test_output_3nodes, outf.getvalue())
366+
367+ def test_dump_btree_raw_smoke(self):
368+ bt, outf = self.create_sample_btree_and_outf(self.test_input_3nodes)
369+ bt.dump_raw_bytes(outf)
370+ self.assertEqualDiff(self.test_raw_output_3nodes, outf.getvalue())
371+
372+ def test_dump_btree_no_refs_smoke(self):
373+ # A BTree index with no ref lists (such as *.cix) can be dumped without
374+ # errors.
375+ builder = btree_index.BTreeBuilder(reference_lists=0, key_elements=2)
376+ builder.add_node(('test', 'key1'), 'value')
377+ out_f = builder.finish()
378+ try:
379+ self.build_tree_contents([('test.btree', out_f.read())])
380+ finally:
381+ out_f.close()
382+ bt = btree_index.BTreeGraphIndex(self.get_transport(''),
383+ 'test.btree', None)
384+ outf = cStringIO.StringIO()
385+ bt.dump_entries(outf)
386+
387+ test_input_0nodes = ()
388+ test_output_0nodes = ""
389+ test_raw_output_0nodes = (
390+ 'Root node:\n'
391+ 'B+Tree Graph Index 2\n'
392+ 'node_ref_lists=1\n'
393+ 'key_elements=2\n'
394+ 'len=0\n'
395+ 'row_lengths=\n'
396+ '\n'
397+ 'Page 0\n'
398+ '(empty)\n'
399+ )
400+
401+ def test_dump_empty_btree_smoke(self):
402+ bt, outf = self.create_sample_btree_and_outf(self.test_input_0nodes)
403+ bt.dump_entries(outf)
404+ self.assertEqualDiff(self.test_output_0nodes, outf.getvalue())
405+
406+ def test_dump_empty_btree_raw_smoke(self):
407+ bt, outf = self.create_sample_btree_and_outf(self.test_input_0nodes)
408+ bt.dump_raw_bytes(outf)
409+ self.assertEqualDiff(self.test_raw_output_0nodes, outf.getvalue())
410+