1 | Sun Apr 25 13:11:21 PDT 2010 kevan@isnotajoke.com |
---|
2 | * Add tests for 'tahoe censor' |
---|
3 | |
---|
4 | Sun Apr 25 13:11:50 PDT 2010 kevan@isnotajoke.com |
---|
5 | * Make it so that CLI tests work without actually making a node directory |
---|
6 | |
---|
7 | It is not necessary to have a node directory for 'tahoe censor', because |
---|
8 | its operations are all local -- it processes a log file. So I made its |
---|
9 | CensorOptions class subclass something other than VDriveOptions. The |
---|
10 | result of that is that CensorOptions doesn't know how to process a |
---|
11 | node-directory parameter, which this code would send. So, now it looks |
---|
12 | for a 'no_node' kwarg; if this is present and set to True, the |
---|
13 | node-directory option is not sent, and everything works. |
---|
14 | |
---|
15 | New patches: |
---|
16 | |
---|
17 | [Add tests for 'tahoe censor' |
---|
18 | kevan@isnotajoke.com**20100425201121 |
---|
19 | Ignore-this: 9fe8849052cc261e5d4c40051686bd9b |
---|
20 | ] { |
---|
21 | hunk ./src/allmydata/test/test_cli.py 9 |
---|
22 | import urllib |
---|
23 | import re |
---|
24 | import simplejson |
---|
25 | +import pickle |
---|
26 | +import platform |
---|
27 | +import stat |
---|
28 | +import bz2 |
---|
29 | |
---|
30 | from allmydata.util import fileutil, hashutil, base32 |
---|
31 | from allmydata import uri |
---|
32 | hunk ./src/allmydata/test/test_cli.py 453 |
---|
33 | help = str(cli.AddAliasOptions()) |
---|
34 | self.failUnless("add-alias ALIAS DIRCAP" in help, help) |
---|
35 | |
---|
36 | + def test_censor(self): |
---|
37 | + help = str(cli.CensorOptions()) |
---|
38 | + self.failUnless("censor SOURCE-LOG DEST-LOG" in help, help) |
---|
39 | + |
---|
40 | + |
---|
41 | class CLITestMixin: |
---|
42 | def do_cli(self, verb, *args, **kwargs): |
---|
43 | nodeargs = [ |
---|
44 | hunk ./src/allmydata/test/test_cli.py 2228 |
---|
45 | self.failUnlessIn("error:", err) |
---|
46 | d.addCallback(_check) |
---|
47 | return d |
---|
48 | + |
---|
49 | + |
---|
50 | +class Censor(GridTestMixin, CLITestMixin, unittest.TestCase): |
---|
51 | + def write_test_log_to_file(self, f): |
---|
52 | + e1 = {} |
---|
53 | + e1['d'] = {"message": "Tub location set to 62.220.0.0:49628,127.0.0.1:49628"} |
---|
54 | + ips_to_look_for = ["62.220.0.0", "127.0.0.1"] |
---|
55 | + pickle.dump(e1, f) |
---|
56 | + e2 = {} |
---|
57 | + e2['d'] = {"message": "connectTCP to ('134.71.255.255', 44785)"} |
---|
58 | + ips_to_look_for.append("134.71.255.255") |
---|
59 | + pickle.dump(e2, f) |
---|
60 | + e3 = {} |
---|
61 | + e3['d'] = {"message": "pb://todjw7qkb4dgq4fkeo7cqydcu5vneioh@tahoecs2.allmydata.com:52106/introducer"} |
---|
62 | + furls_to_look_for = ["pb://todjw7qkb4dgq4fkeo7cqydcu5vneioh@tahoecs2.allmydata.com:52106/introducer"] |
---|
63 | + pickle.dump(e3, f) |
---|
64 | + e4 = {} |
---|
65 | + e4['d'] = {"message": "<si>dkalsdkjaslkjd</si>"} |
---|
66 | + sis_to_look_for = ["dkalsdkjaslkjd"] |
---|
67 | + pickle.dump(e4, f) |
---|
68 | + e5 = {} |
---|
69 | + e5['d'] = {"message": "<SI>dasdasdsadsads</SI>"} |
---|
70 | + sis_to_look_for.append("dasdasdsadsads") |
---|
71 | + pickle.dump(e5, f) |
---|
72 | + # ([ips], [furls], [sis], # of log messages) |
---|
73 | + return (ips_to_look_for, furls_to_look_for, sis_to_look_for, 5) |
---|
74 | + |
---|
75 | + |
---|
76 | + def test_censor_with_nonexistent_source(self): |
---|
77 | + # When asked to censor a file that doesn't exist, 'tahoe censor' |
---|
78 | + # should print something useful as an error message. |
---|
79 | + self.basedir = os.path.join("cli", |
---|
80 | + "Censor", |
---|
81 | + "test_censor_with_nonexistent_source") |
---|
82 | + self.set_up_grid() |
---|
83 | + d = self.do_cli("censor", "does_not_exist", no_node=True) |
---|
84 | + def _check((rc, out, err)): |
---|
85 | + self.failUnlessEqual(rc, 1) |
---|
86 | + self.failUnlessIn("Error", err) |
---|
87 | + self.failUnlessIn("doesn't exist", err) |
---|
88 | + d.addCallback(_check) |
---|
89 | + return d |
---|
90 | + |
---|
91 | + |
---|
92 | + def test_censor_with_nonsensical_source(self): |
---|
93 | + # 'tahoe censor' works on logs that are actually pickled |
---|
94 | + # dictionaries, as output by foolscap. If asked to censor |
---|
95 | + # something else, it should print something useful as an error |
---|
96 | + # message. |
---|
97 | + self.basedir = os.path.join("cli", |
---|
98 | + "Censor", |
---|
99 | + "test_censor_with_nonsensical_source") |
---|
100 | + self.set_up_grid() |
---|
101 | + test_file = os.path.join(self.basedir, "input") |
---|
102 | + f = open(test_file, "wb") |
---|
103 | + f.write("blahblahblah") |
---|
104 | + f.close() |
---|
105 | + d = self.do_cli("censor", test_file, no_node=True) |
---|
106 | + def _check((rc, out, err)): |
---|
107 | + self.failUnlessEqual(rc, 1) |
---|
108 | + self.failUnlessIn("Error", err) |
---|
109 | + self.failUnlessIn("invalid format", err) |
---|
110 | + d.addCallback(_check) |
---|
111 | + def _then(ign): |
---|
112 | + self.test_file = os.path.join(self.basedir, "input2") |
---|
113 | + f = open(self.test_file, "wb") |
---|
114 | + f.write("For some reason, the file contents above result in " |
---|
115 | + "an IndexError, while these result in an EOFError. In " |
---|
116 | + "either case, the program should output something useful") |
---|
117 | + f.close() |
---|
118 | + d.addCallback(_then) |
---|
119 | + d.addCallback(lambda ign: self.do_cli("censor", self.test_file, |
---|
120 | + no_node=True)) |
---|
121 | + d.addCallback(_check) |
---|
122 | + return d |
---|
123 | + |
---|
124 | + |
---|
125 | + def test_censor_with_empty_source(self): |
---|
126 | + # 'tahoe censor' should complain when presented with an empty |
---|
127 | + # log file to censor. |
---|
128 | + self.basedir = os.path.join("cli", |
---|
129 | + "Censor", |
---|
130 | + "test_censor_with_empty_source") |
---|
131 | + self.set_up_grid() |
---|
132 | + test_file = os.path.join(self.basedir, "input") |
---|
133 | + f = open(test_file, "wb").close() |
---|
134 | + d = self.do_cli("censor", test_file, no_node=True) |
---|
135 | + def _check((rc, out, err)): |
---|
136 | + self.failUnlessEqual(rc, 1) |
---|
137 | + self.failUnlessIn("Error", err) |
---|
138 | + self.failUnlessIn("empty", err) |
---|
139 | + d.addCallback(_check) |
---|
140 | + return d |
---|
141 | + |
---|
142 | + |
---|
143 | + def test_censor_with_unreadable_source(self): |
---|
144 | + # 'tahoe censor' should complain when presented with a file that |
---|
145 | + # OS-level access controls prevent it from reading. |
---|
146 | + if platform.system() == "Windows": |
---|
147 | + raise unittest.SkipTest("os.chmod() can't make a file that this " |
---|
148 | + "test can't read on Windows.") |
---|
149 | + self.basedir = os.path.join("cli", |
---|
150 | + "Censor", |
---|
151 | + "test_censor_with_unreadable_source") |
---|
152 | + self.set_up_grid() |
---|
153 | + test_file = os.path.join(self.basedir, "input") |
---|
154 | + f = open(test_file, "wb") |
---|
155 | + self.write_test_log_to_file(f) |
---|
156 | + f.close() |
---|
157 | + os.chmod(test_file, stat.S_IWRITE) |
---|
158 | + d = self.do_cli("censor", test_file, no_node=True) |
---|
159 | + def _check((rc, out, err)): |
---|
160 | + self.failUnlessEqual(rc, 1) |
---|
161 | + self.failUnlessIn("Error", err) |
---|
162 | + self.failUnlessIn("read", err) |
---|
163 | + d.addCallback(_check) |
---|
164 | + return d |
---|
165 | + |
---|
166 | + |
---|
167 | + def test_censor_with_unwritable_destination(self): |
---|
168 | + # 'tahoe censor' should complain when presented with a |
---|
169 | + # destination file that it can't write to. |
---|
170 | + self.basedir = os.path.join("cli", |
---|
171 | + "Censor", |
---|
172 | + "test_censor_with_unwritable_destination") |
---|
173 | + self.set_up_grid() |
---|
174 | + test_file = os.path.join(self.basedir, "input") |
---|
175 | + f = open(test_file, "wb") |
---|
176 | + self.write_test_log_to_file(f) |
---|
177 | + f.close() |
---|
178 | + test_out = os.path.join(self.basedir, "output") |
---|
179 | + open(test_out, "wb").close() |
---|
180 | + # should make test_out readonly on both Windows and *nixes. |
---|
181 | + os.chmod(test_out, stat.S_IREAD) |
---|
182 | + d = self.do_cli("censor", test_file, test_out, no_node=True) |
---|
183 | + def _check((rc, out, err)): |
---|
184 | + self.failUnlessEqual(rc, 1) |
---|
185 | + self.failUnlessIn("Error:", err) |
---|
186 | + self.failUnlessIn("writable", err) |
---|
187 | + d.addCallback(_check) |
---|
188 | + # In the case where the source file is also the destination file |
---|
189 | + # (i.e.: we're censoring in-place), 'tahoe censor' should also |
---|
190 | + # complain if it can't write to the source file. |
---|
191 | + def _then(ign): |
---|
192 | + self.second_test_file = os.path.join(self.basedir, "input2") |
---|
193 | + f = open(self.second_test_file, "wb") |
---|
194 | + self.write_test_log_to_file(f) |
---|
195 | + f.close() |
---|
196 | + os.chmod(self.second_test_file, stat.S_IREAD) |
---|
197 | + d.addCallback(_then) |
---|
198 | + d.addCallback(lambda ign: self.do_cli("censor", self.second_test_file, |
---|
199 | + no_node=True)) |
---|
200 | + d.addCallback(_check) |
---|
201 | + return d |
---|
202 | + |
---|
203 | + |
---|
204 | + def test_censor_should_censor_IP_addresses(self): |
---|
205 | + # 'tahoe censor' should successfully remove IP addresses from |
---|
206 | + # valid log files. |
---|
207 | + self.basedir = os.path.join("cli", |
---|
208 | + "Censor", |
---|
209 | + "test_censor_should_censor_IP_addresses") |
---|
210 | + self.set_up_grid() |
---|
211 | + test_file = os.path.join(self.basedir, "input") |
---|
212 | + f = open(test_file, "wb") |
---|
213 | + ips, furls, sis, total = self.write_test_log_to_file(f) |
---|
214 | + f.close() |
---|
215 | + d = self.do_cli("censor", test_file, no_node=True) |
---|
216 | + def _check((rc, out, err)): |
---|
217 | + self.failUnlessEqual(rc, 0) |
---|
218 | + f = open(test_file, "rb") |
---|
219 | + while True: |
---|
220 | + try: |
---|
221 | + e = pickle.load(f) |
---|
222 | + for ip in ips: |
---|
223 | + self.failIfIn(ip, e['d']["message"]) |
---|
224 | + except EOFError: |
---|
225 | + break |
---|
226 | + f.close() |
---|
227 | + d.addCallback(_check) |
---|
228 | + return d |
---|
229 | + |
---|
230 | + |
---|
231 | + def test_censor_should_censor_storage_indices(self): |
---|
232 | + # 'tahoe censor' should successfully remove storage indices |
---|
233 | + # from valid log files if they are of the form <si>SI</si> or |
---|
234 | + # <SI>SI</SI> |
---|
235 | + self.basedir = os.path.join("cli", |
---|
236 | + "Censor", |
---|
237 | + "test_censor_should_censor_storage_indices") |
---|
238 | + self.set_up_grid() |
---|
239 | + test_file = os.path.join(self.basedir, "input") |
---|
240 | + f = open(test_file, "wb") |
---|
241 | + ips, furls, sis, total = self.write_test_log_to_file(f) |
---|
242 | + f.close() |
---|
243 | + d = self.do_cli("censor", test_file, no_node=True) |
---|
244 | + def _check((rc, out, err)): |
---|
245 | + self.failUnlessEqual(rc, 0) |
---|
246 | + f = open(test_file, "rb") |
---|
247 | + while True: |
---|
248 | + try: |
---|
249 | + e = pickle.load(f) |
---|
250 | + for si in sis: |
---|
251 | + self.failIfIn(si, e['d']["message"]) |
---|
252 | + except EOFError: |
---|
253 | + break |
---|
254 | + f.close() |
---|
255 | + d.addCallback(_check) |
---|
256 | + return d |
---|
257 | + |
---|
258 | + |
---|
259 | + def test_censor_should_censor_furls(self): |
---|
260 | + # 'tahoe censor' should successfully remove furls from valid log |
---|
261 | + # files. |
---|
262 | + self.basedir = os.path.join("cli", |
---|
263 | + "Censor", |
---|
264 | + "test_censor_should_censor_furls") |
---|
265 | + self.set_up_grid() |
---|
266 | + test_file = os.path.join(self.basedir, "input") |
---|
267 | + f = open(test_file, "wb") |
---|
268 | + ips, furls, sis, total = self.write_test_log_to_file(f) |
---|
269 | + f.close() |
---|
270 | + d = self.do_cli("censor", test_file, no_node=True) |
---|
271 | + def _check((rc, out, err)): |
---|
272 | + self.failUnlessEqual(rc, 0) |
---|
273 | + f = open(test_file, "rb") |
---|
274 | + while True: |
---|
275 | + try: |
---|
276 | + e = pickle.load(f) |
---|
277 | + for furl in furls: |
---|
278 | + self.failIfIn(furl, e['d']["message"]) |
---|
279 | + except EOFError: |
---|
280 | + break |
---|
281 | + f.close() |
---|
282 | + d.addCallback(_check) |
---|
283 | + return d |
---|
284 | + |
---|
285 | + |
---|
286 | + def test_censor_verbose_mode(self): |
---|
287 | + # When run in verbose mode (with the -v or --verbose flags), |
---|
288 | + # 'tahoe censor' should output messages telling the user what it |
---|
289 | + # is doing. |
---|
290 | + self.basedir = os.path.join("cli", |
---|
291 | + "Censor", |
---|
292 | + "test_censor_verbose_mode") |
---|
293 | + self.set_up_grid() |
---|
294 | + test_file = os.path.join(self.basedir, "input") |
---|
295 | + f = open(test_file, "wb") |
---|
296 | + ips, furls, sis, total = self.write_test_log_to_file(f) |
---|
297 | + f.close() |
---|
298 | + d = self.do_cli("censor", "-v", test_file, no_node=True) |
---|
299 | + def _check((rc, out, err)): |
---|
300 | + self.failUnlessEqual(rc, 0) |
---|
301 | + for item in ips + furls + sis: |
---|
302 | + self.failUnlessIn(item, out) |
---|
303 | + d.addCallback(_check) |
---|
304 | + return d |
---|
305 | + |
---|
306 | + |
---|
307 | + def test_censor_quiet_mode(self): |
---|
308 | + # When run in quiet mode (with the -q or --quiet flags), 'tahoe |
---|
309 | + # censor' should not output anything other than error messages. |
---|
310 | + self.basedir = os.path.join("cli", |
---|
311 | + "Censor", |
---|
312 | + "test_censor_quiet_mode") |
---|
313 | + self.set_up_grid() |
---|
314 | + test_file = os.path.join(self.basedir, "input") |
---|
315 | + f = open(test_file, "wb") |
---|
316 | + self.write_test_log_to_file(f) |
---|
317 | + f.close() |
---|
318 | + d = self.do_cli("censor", "-q", test_file, no_node=True) |
---|
319 | + def _check((rc, out, err)): |
---|
320 | + self.failUnlessEqual(rc, 0) |
---|
321 | + self.failUnlessEqual(out, "") |
---|
322 | + d.addCallback(_check) |
---|
323 | + def _then(ign): |
---|
324 | + self.second_test_file = os.path.join(self.basedir, "input2") |
---|
325 | + f = open(self.second_test_file, "wb") |
---|
326 | + self.write_test_log_to_file(f) |
---|
327 | + f.close() |
---|
328 | + os.chmod(self.second_test_file, stat.S_IREAD) |
---|
329 | + d.addCallback(_then) |
---|
330 | + d.addCallback(lambda ign: self.do_cli("censor", |
---|
331 | + "-q", |
---|
332 | + self.second_test_file, |
---|
333 | + no_node=True)) |
---|
334 | + def _check2((rc, out, err)): |
---|
335 | + self.failUnlessEqual(rc, 1) |
---|
336 | + self.failUnlessEqual(out, "") |
---|
337 | + self.failUnlessIn("Error:", err) |
---|
338 | + self.failUnlessIn("write", err) |
---|
339 | + d.addCallback(_check2) |
---|
340 | + return d |
---|
341 | + |
---|
342 | + |
---|
343 | + def test_censor_bz2(self): |
---|
344 | + # 'tahoe censor' should be capable of censoring both |
---|
345 | + # uncompressed log files and bzipped log files. |
---|
346 | + self.basedir = os.path.join("cli", |
---|
347 | + "Censor", |
---|
348 | + "test_censor_bz2") |
---|
349 | + self.set_up_grid() |
---|
350 | + test_file = os.path.join(self.basedir, "input.bz2") |
---|
351 | + f = bz2.BZ2File(test_file, "wb") |
---|
352 | + ips, furls, sis, total = self.write_test_log_to_file(f) |
---|
353 | + self.items = ips + furls + sis |
---|
354 | + f.close() |
---|
355 | + # First, check to see that we can read from a bz2 file and |
---|
356 | + # write to a bz2 file. |
---|
357 | + d = self.do_cli("censor", test_file, no_node=True) |
---|
358 | + def _check((rc, out, err)): |
---|
359 | + self.failUnlessEqual(rc, 0) |
---|
360 | + f = bz2.BZ2File(test_file, "rb") |
---|
361 | + while True: |
---|
362 | + try: |
---|
363 | + e = pickle.load(f) |
---|
364 | + for item in self.items: |
---|
365 | + self.failIfIn(item, e['d']["message"]) |
---|
366 | + except EOFError: |
---|
367 | + break |
---|
368 | + f.close() |
---|
369 | + d.addCallback(_check) |
---|
370 | + # Now, check to see that we can write to a bz2 logfile |
---|
371 | + # from a plain logfile |
---|
372 | + def _then(ign): |
---|
373 | + self.second_source = os.path.join(self.basedir, "input2") |
---|
374 | + self.second_dest = os.path.join(self.basedir, "output2.bz2") |
---|
375 | + f = open(self.second_source, "wb") |
---|
376 | + self.write_test_log_to_file(f) |
---|
377 | + f.close() |
---|
378 | + d.addCallback(_then) |
---|
379 | + d.addCallback(lambda ign: self.do_cli("censor", |
---|
380 | + self.second_source, |
---|
381 | + self.second_dest, |
---|
382 | + no_node=True)) |
---|
383 | + def _check2((rc, out, err)): |
---|
384 | + self.failUnlessEqual(rc, 0) |
---|
385 | + f = bz2.BZ2File(self.second_dest, "rb") |
---|
386 | + while True: |
---|
387 | + try: |
---|
388 | + e = pickle.load(f) |
---|
389 | + for item in self.items: |
---|
390 | + self.failIfIn(item, e['d']["message"]) |
---|
391 | + except EOFError: |
---|
392 | + break |
---|
393 | + f.close() |
---|
394 | + d.addCallback(_check2) |
---|
395 | + # Finally, check to see that we can write from a bz2 logfile |
---|
396 | + # to a plain logfile. |
---|
397 | + def _later(ign): |
---|
398 | + self.third_source = os.path.join(self.basedir, "input3.bz2") |
---|
399 | + self.third_dest = os.path.join(self.basedir, "output3") |
---|
400 | + f = bz2.BZ2File(self.third_source, "wb") |
---|
401 | + self.write_test_log_to_file(f) |
---|
402 | + f.close() |
---|
403 | + d.addCallback(_later) |
---|
404 | + d.addCallback(lambda ign: self.do_cli("censor", |
---|
405 | + self.third_source, |
---|
406 | + self.third_dest, |
---|
407 | + no_node=True)) |
---|
408 | + def _check3((rc, out, err)): |
---|
409 | + self.failUnlessEqual(rc, 0) |
---|
410 | + f = open(self.third_dest, "rb") |
---|
411 | + while True: |
---|
412 | + try: |
---|
413 | + e = pickle.load(f) |
---|
414 | + for item in self.items: |
---|
415 | + self.failIfIn(item, e['d']["message"]) |
---|
416 | + except EOFError: |
---|
417 | + break |
---|
418 | + f.close() |
---|
419 | + d.addCallback(_check3) |
---|
420 | + return d |
---|
421 | + |
---|
422 | + |
---|
423 | + def test_censor_log_counting(self): |
---|
424 | + # When not run in quiet mode, 'tahoe censor' should output a |
---|
425 | + # useful concluding message, including: |
---|
426 | + # - The total number of logs processed |
---|
427 | + # - The total number of SIs censored |
---|
428 | + # - The total number of IP addresses censored. |
---|
429 | + # - The total number of furls censored. |
---|
430 | + self.basedir = os.path.join("cli", |
---|
431 | + "Censor", |
---|
432 | + "test_censor_log_counting") |
---|
433 | + self.set_up_grid() |
---|
434 | + test_file = os.path.join(self.basedir, "input") |
---|
435 | + f = open(test_file, "wb") |
---|
436 | + ips, furls, sis, total = self.write_test_log_to_file(f) |
---|
437 | + f.close() |
---|
438 | + d = self.do_cli("censor", test_file, no_node=True) |
---|
439 | + def _check((rc, out, err)): |
---|
440 | + self.failUnlessEqual(rc, 0) |
---|
441 | + self.failUnlessIn("total of %d" % total, out) |
---|
442 | + self.failUnlessIn("Storage Indices: %d" % len(sis), out) |
---|
443 | + self.failUnlessIn("IP Addresses: %d" % len(ips), out) |
---|
444 | + self.failUnlessIn("Node URLs: %d" % len(furls), out) |
---|
445 | + d.addCallback(_check) |
---|
446 | + return d |
---|
447 | } |
---|
448 | [Make it so that CLI tests work without actually making a node directory |
---|
449 | kevan@isnotajoke.com**20100425201150 |
---|
450 | Ignore-this: ca5467176c9ce15b17dfa27cf6b2885d |
---|
451 | |
---|
452 | It is not necessary to have a node directory for 'tahoe censor', because |
---|
453 | its operations are all local -- it processes a log file. So I made its |
---|
454 | CensorOptions class subclass something other than VDriveOptions. The |
---|
455 | result of that is that CensorOptions doesn't know how to process a |
---|
456 | node-directory parameter, which this code would send. So, now it looks |
---|
457 | for a 'no_node' kwarg; if this is present and set to True, the |
---|
458 | node-directory option is not sent, and everything works. |
---|
459 | ] hunk ./src/allmydata/test/test_cli.py 460 |
---|
460 | |
---|
461 | class CLITestMixin: |
---|
462 | def do_cli(self, verb, *args, **kwargs): |
---|
463 | - nodeargs = [ |
---|
464 | - "--node-directory", self.get_clientdir(), |
---|
465 | - ] |
---|
466 | + if "no_node" not in kwargs: |
---|
467 | + nodeargs = [ |
---|
468 | + "--node-directory", self.get_clientdir(), |
---|
469 | + ] |
---|
470 | + else: |
---|
471 | + del(kwargs['no_node']) |
---|
472 | + nodeargs = [] |
---|
473 | argv = [verb] + nodeargs + list(args) |
---|
474 | stdin = kwargs.get("stdin", "") |
---|
475 | stdout, stderr = StringIO(), StringIO() |
---|
476 | |
---|
477 | Context: |
---|
478 | |
---|
479 | [setup: add licensing declaration for setuptools (noticed by the FSF compliance folks) |
---|
480 | zooko@zooko.com**20100309184415 |
---|
481 | Ignore-this: 2dfa7d812d65fec7c72ddbf0de609ccb |
---|
482 | ] |
---|
483 | [setup: fix error in licensing declaration from Shawn Willden, as noted by the FSF compliance division |
---|
484 | zooko@zooko.com**20100309163736 |
---|
485 | Ignore-this: c0623d27e469799d86cabf67921a13f8 |
---|
486 | ] |
---|
487 | [CREDITS to Jacob Appelbaum |
---|
488 | zooko@zooko.com**20100304015616 |
---|
489 | Ignore-this: 70db493abbc23968fcc8db93f386ea54 |
---|
490 | ] |
---|
491 | [desert-island-build-with-proper-versions |
---|
492 | jacob@appelbaum.net**20100304013858] |
---|
493 | [docs: a few small edits to try to guide newcomers through the docs |
---|
494 | zooko@zooko.com**20100303231902 |
---|
495 | Ignore-this: a6aab44f5bf5ad97ea73e6976bc4042d |
---|
496 | These edits were suggested by my watching over Jake Appelbaum's shoulder as he completely ignored/skipped/missed install.html and also as he decided that debian.txt wouldn't help him with basic installation. Then I threw in a few docs edits that have been sitting around in my sandbox asking to be committed for months. |
---|
497 | ] |
---|
498 | [TAG allmydata-tahoe-1.6.1 |
---|
499 | david-sarah@jacaranda.org**20100228062314 |
---|
500 | Ignore-this: eb5f03ada8ea953ee7780e7fe068539 |
---|
501 | ] |
---|
502 | Patch bundle hash: |
---|
503 | 7e2f8933bb23af7dc504dab74325f01cfbbea2ec |
---|