Sun Apr 25 13:11:21 PDT 2010 kevan@isnotajoke.com * Add tests for 'tahoe censor' Sun Apr 25 13:11:50 PDT 2010 kevan@isnotajoke.com * Make it so that CLI tests work without actually making a node directory It is not necessary to have a node directory for 'tahoe censor', because its operations are all local -- it processes a log file. So I made its CensorOptions class subclass something other than VDriveOptions. The result of that is that CensorOptions doesn't know how to process a node-directory parameter, which this code would send. So, now it looks for a 'no_node' kwarg; if this is present and set to True, the node-directory option is not sent, and everything works. New patches: [Add tests for 'tahoe censor' kevan@isnotajoke.com**20100425201121 Ignore-this: 9fe8849052cc261e5d4c40051686bd9b ] { hunk ./src/allmydata/test/test_cli.py 9 import urllib import re import simplejson +import pickle +import platform +import stat +import bz2 from allmydata.util import fileutil, hashutil, base32 from allmydata import uri hunk ./src/allmydata/test/test_cli.py 453 help = str(cli.AddAliasOptions()) self.failUnless("add-alias ALIAS DIRCAP" in help, help) + def test_censor(self): + help = str(cli.CensorOptions()) + self.failUnless("censor SOURCE-LOG DEST-LOG" in help, help) + + class CLITestMixin: def do_cli(self, verb, *args, **kwargs): nodeargs = [ hunk ./src/allmydata/test/test_cli.py 2228 self.failUnlessIn("error:", err) d.addCallback(_check) return d + + +class Censor(GridTestMixin, CLITestMixin, unittest.TestCase): + def write_test_log_to_file(self, f): + e1 = {} + e1['d'] = {"message": "Tub location set to 62.220.0.0:49628,127.0.0.1:49628"} + ips_to_look_for = ["62.220.0.0", "127.0.0.1"] + pickle.dump(e1, f) + e2 = {} + e2['d'] = {"message": "connectTCP to ('134.71.255.255', 44785)"} + ips_to_look_for.append("134.71.255.255") + pickle.dump(e2, f) + e3 = {} + e3['d'] = {"message": "pb://todjw7qkb4dgq4fkeo7cqydcu5vneioh@tahoecs2.allmydata.com:52106/introducer"} + furls_to_look_for = ["pb://todjw7qkb4dgq4fkeo7cqydcu5vneioh@tahoecs2.allmydata.com:52106/introducer"] + pickle.dump(e3, f) + e4 = {} + e4['d'] = {"message": "dkalsdkjaslkjd"} + sis_to_look_for = ["dkalsdkjaslkjd"] + pickle.dump(e4, f) + e5 = {} + e5['d'] = {"message": "dasdasdsadsads"} + sis_to_look_for.append("dasdasdsadsads") + pickle.dump(e5, f) + # ([ips], [furls], [sis], # of log messages) + return (ips_to_look_for, furls_to_look_for, sis_to_look_for, 5) + + + def test_censor_with_nonexistent_source(self): + # When asked to censor a file that doesn't exist, 'tahoe censor' + # should print something useful as an error message. + self.basedir = os.path.join("cli", + "Censor", + "test_censor_with_nonexistent_source") + self.set_up_grid() + d = self.do_cli("censor", "does_not_exist", no_node=True) + def _check((rc, out, err)): + self.failUnlessEqual(rc, 1) + self.failUnlessIn("Error", err) + self.failUnlessIn("doesn't exist", err) + d.addCallback(_check) + return d + + + def test_censor_with_nonsensical_source(self): + # 'tahoe censor' works on logs that are actually pickled + # dictionaries, as output by foolscap. If asked to censor + # something else, it should print something useful as an error + # message. + self.basedir = os.path.join("cli", + "Censor", + "test_censor_with_nonsensical_source") + self.set_up_grid() + test_file = os.path.join(self.basedir, "input") + f = open(test_file, "wb") + f.write("blahblahblah") + f.close() + d = self.do_cli("censor", test_file, no_node=True) + def _check((rc, out, err)): + self.failUnlessEqual(rc, 1) + self.failUnlessIn("Error", err) + self.failUnlessIn("invalid format", err) + d.addCallback(_check) + def _then(ign): + self.test_file = os.path.join(self.basedir, "input2") + f = open(self.test_file, "wb") + f.write("For some reason, the file contents above result in " + "an IndexError, while these result in an EOFError. In " + "either case, the program should output something useful") + f.close() + d.addCallback(_then) + d.addCallback(lambda ign: self.do_cli("censor", self.test_file, + no_node=True)) + d.addCallback(_check) + return d + + + def test_censor_with_empty_source(self): + # 'tahoe censor' should complain when presented with an empty + # log file to censor. + self.basedir = os.path.join("cli", + "Censor", + "test_censor_with_empty_source") + self.set_up_grid() + test_file = os.path.join(self.basedir, "input") + f = open(test_file, "wb").close() + d = self.do_cli("censor", test_file, no_node=True) + def _check((rc, out, err)): + self.failUnlessEqual(rc, 1) + self.failUnlessIn("Error", err) + self.failUnlessIn("empty", err) + d.addCallback(_check) + return d + + + def test_censor_with_unreadable_source(self): + # 'tahoe censor' should complain when presented with a file that + # OS-level access controls prevent it from reading. + if platform.system() == "Windows": + raise unittest.SkipTest("os.chmod() can't make a file that this " + "test can't read on Windows.") + self.basedir = os.path.join("cli", + "Censor", + "test_censor_with_unreadable_source") + self.set_up_grid() + test_file = os.path.join(self.basedir, "input") + f = open(test_file, "wb") + self.write_test_log_to_file(f) + f.close() + os.chmod(test_file, stat.S_IWRITE) + d = self.do_cli("censor", test_file, no_node=True) + def _check((rc, out, err)): + self.failUnlessEqual(rc, 1) + self.failUnlessIn("Error", err) + self.failUnlessIn("read", err) + d.addCallback(_check) + return d + + + def test_censor_with_unwritable_destination(self): + # 'tahoe censor' should complain when presented with a + # destination file that it can't write to. + self.basedir = os.path.join("cli", + "Censor", + "test_censor_with_unwritable_destination") + self.set_up_grid() + test_file = os.path.join(self.basedir, "input") + f = open(test_file, "wb") + self.write_test_log_to_file(f) + f.close() + test_out = os.path.join(self.basedir, "output") + open(test_out, "wb").close() + # should make test_out readonly on both Windows and *nixes. + os.chmod(test_out, stat.S_IREAD) + d = self.do_cli("censor", test_file, test_out, no_node=True) + def _check((rc, out, err)): + self.failUnlessEqual(rc, 1) + self.failUnlessIn("Error:", err) + self.failUnlessIn("writable", err) + d.addCallback(_check) + # In the case where the source file is also the destination file + # (i.e.: we're censoring in-place), 'tahoe censor' should also + # complain if it can't write to the source file. + def _then(ign): + self.second_test_file = os.path.join(self.basedir, "input2") + f = open(self.second_test_file, "wb") + self.write_test_log_to_file(f) + f.close() + os.chmod(self.second_test_file, stat.S_IREAD) + d.addCallback(_then) + d.addCallback(lambda ign: self.do_cli("censor", self.second_test_file, + no_node=True)) + d.addCallback(_check) + return d + + + def test_censor_should_censor_IP_addresses(self): + # 'tahoe censor' should successfully remove IP addresses from + # valid log files. + self.basedir = os.path.join("cli", + "Censor", + "test_censor_should_censor_IP_addresses") + self.set_up_grid() + test_file = os.path.join(self.basedir, "input") + f = open(test_file, "wb") + ips, furls, sis, total = self.write_test_log_to_file(f) + f.close() + d = self.do_cli("censor", test_file, no_node=True) + def _check((rc, out, err)): + self.failUnlessEqual(rc, 0) + f = open(test_file, "rb") + while True: + try: + e = pickle.load(f) + for ip in ips: + self.failIfIn(ip, e['d']["message"]) + except EOFError: + break + f.close() + d.addCallback(_check) + return d + + + def test_censor_should_censor_storage_indices(self): + # 'tahoe censor' should successfully remove storage indices + # from valid log files if they are of the form SI or + # SI + self.basedir = os.path.join("cli", + "Censor", + "test_censor_should_censor_storage_indices") + self.set_up_grid() + test_file = os.path.join(self.basedir, "input") + f = open(test_file, "wb") + ips, furls, sis, total = self.write_test_log_to_file(f) + f.close() + d = self.do_cli("censor", test_file, no_node=True) + def _check((rc, out, err)): + self.failUnlessEqual(rc, 0) + f = open(test_file, "rb") + while True: + try: + e = pickle.load(f) + for si in sis: + self.failIfIn(si, e['d']["message"]) + except EOFError: + break + f.close() + d.addCallback(_check) + return d + + + def test_censor_should_censor_furls(self): + # 'tahoe censor' should successfully remove furls from valid log + # files. + self.basedir = os.path.join("cli", + "Censor", + "test_censor_should_censor_furls") + self.set_up_grid() + test_file = os.path.join(self.basedir, "input") + f = open(test_file, "wb") + ips, furls, sis, total = self.write_test_log_to_file(f) + f.close() + d = self.do_cli("censor", test_file, no_node=True) + def _check((rc, out, err)): + self.failUnlessEqual(rc, 0) + f = open(test_file, "rb") + while True: + try: + e = pickle.load(f) + for furl in furls: + self.failIfIn(furl, e['d']["message"]) + except EOFError: + break + f.close() + d.addCallback(_check) + return d + + + def test_censor_verbose_mode(self): + # When run in verbose mode (with the -v or --verbose flags), + # 'tahoe censor' should output messages telling the user what it + # is doing. + self.basedir = os.path.join("cli", + "Censor", + "test_censor_verbose_mode") + self.set_up_grid() + test_file = os.path.join(self.basedir, "input") + f = open(test_file, "wb") + ips, furls, sis, total = self.write_test_log_to_file(f) + f.close() + d = self.do_cli("censor", "-v", test_file, no_node=True) + def _check((rc, out, err)): + self.failUnlessEqual(rc, 0) + for item in ips + furls + sis: + self.failUnlessIn(item, out) + d.addCallback(_check) + return d + + + def test_censor_quiet_mode(self): + # When run in quiet mode (with the -q or --quiet flags), 'tahoe + # censor' should not output anything other than error messages. + self.basedir = os.path.join("cli", + "Censor", + "test_censor_quiet_mode") + self.set_up_grid() + test_file = os.path.join(self.basedir, "input") + f = open(test_file, "wb") + self.write_test_log_to_file(f) + f.close() + d = self.do_cli("censor", "-q", test_file, no_node=True) + def _check((rc, out, err)): + self.failUnlessEqual(rc, 0) + self.failUnlessEqual(out, "") + d.addCallback(_check) + def _then(ign): + self.second_test_file = os.path.join(self.basedir, "input2") + f = open(self.second_test_file, "wb") + self.write_test_log_to_file(f) + f.close() + os.chmod(self.second_test_file, stat.S_IREAD) + d.addCallback(_then) + d.addCallback(lambda ign: self.do_cli("censor", + "-q", + self.second_test_file, + no_node=True)) + def _check2((rc, out, err)): + self.failUnlessEqual(rc, 1) + self.failUnlessEqual(out, "") + self.failUnlessIn("Error:", err) + self.failUnlessIn("write", err) + d.addCallback(_check2) + return d + + + def test_censor_bz2(self): + # 'tahoe censor' should be capable of censoring both + # uncompressed log files and bzipped log files. + self.basedir = os.path.join("cli", + "Censor", + "test_censor_bz2") + self.set_up_grid() + test_file = os.path.join(self.basedir, "input.bz2") + f = bz2.BZ2File(test_file, "wb") + ips, furls, sis, total = self.write_test_log_to_file(f) + self.items = ips + furls + sis + f.close() + # First, check to see that we can read from a bz2 file and + # write to a bz2 file. + d = self.do_cli("censor", test_file, no_node=True) + def _check((rc, out, err)): + self.failUnlessEqual(rc, 0) + f = bz2.BZ2File(test_file, "rb") + while True: + try: + e = pickle.load(f) + for item in self.items: + self.failIfIn(item, e['d']["message"]) + except EOFError: + break + f.close() + d.addCallback(_check) + # Now, check to see that we can write to a bz2 logfile + # from a plain logfile + def _then(ign): + self.second_source = os.path.join(self.basedir, "input2") + self.second_dest = os.path.join(self.basedir, "output2.bz2") + f = open(self.second_source, "wb") + self.write_test_log_to_file(f) + f.close() + d.addCallback(_then) + d.addCallback(lambda ign: self.do_cli("censor", + self.second_source, + self.second_dest, + no_node=True)) + def _check2((rc, out, err)): + self.failUnlessEqual(rc, 0) + f = bz2.BZ2File(self.second_dest, "rb") + while True: + try: + e = pickle.load(f) + for item in self.items: + self.failIfIn(item, e['d']["message"]) + except EOFError: + break + f.close() + d.addCallback(_check2) + # Finally, check to see that we can write from a bz2 logfile + # to a plain logfile. + def _later(ign): + self.third_source = os.path.join(self.basedir, "input3.bz2") + self.third_dest = os.path.join(self.basedir, "output3") + f = bz2.BZ2File(self.third_source, "wb") + self.write_test_log_to_file(f) + f.close() + d.addCallback(_later) + d.addCallback(lambda ign: self.do_cli("censor", + self.third_source, + self.third_dest, + no_node=True)) + def _check3((rc, out, err)): + self.failUnlessEqual(rc, 0) + f = open(self.third_dest, "rb") + while True: + try: + e = pickle.load(f) + for item in self.items: + self.failIfIn(item, e['d']["message"]) + except EOFError: + break + f.close() + d.addCallback(_check3) + return d + + + def test_censor_log_counting(self): + # When not run in quiet mode, 'tahoe censor' should output a + # useful concluding message, including: + # - The total number of logs processed + # - The total number of SIs censored + # - The total number of IP addresses censored. + # - The total number of furls censored. + self.basedir = os.path.join("cli", + "Censor", + "test_censor_log_counting") + self.set_up_grid() + test_file = os.path.join(self.basedir, "input") + f = open(test_file, "wb") + ips, furls, sis, total = self.write_test_log_to_file(f) + f.close() + d = self.do_cli("censor", test_file, no_node=True) + def _check((rc, out, err)): + self.failUnlessEqual(rc, 0) + self.failUnlessIn("total of %d" % total, out) + self.failUnlessIn("Storage Indices: %d" % len(sis), out) + self.failUnlessIn("IP Addresses: %d" % len(ips), out) + self.failUnlessIn("Node URLs: %d" % len(furls), out) + d.addCallback(_check) + return d } [Make it so that CLI tests work without actually making a node directory kevan@isnotajoke.com**20100425201150 Ignore-this: ca5467176c9ce15b17dfa27cf6b2885d It is not necessary to have a node directory for 'tahoe censor', because its operations are all local -- it processes a log file. So I made its CensorOptions class subclass something other than VDriveOptions. The result of that is that CensorOptions doesn't know how to process a node-directory parameter, which this code would send. So, now it looks for a 'no_node' kwarg; if this is present and set to True, the node-directory option is not sent, and everything works. ] hunk ./src/allmydata/test/test_cli.py 460 class CLITestMixin: def do_cli(self, verb, *args, **kwargs): - nodeargs = [ - "--node-directory", self.get_clientdir(), - ] + if "no_node" not in kwargs: + nodeargs = [ + "--node-directory", self.get_clientdir(), + ] + else: + del(kwargs['no_node']) + nodeargs = [] argv = [verb] + nodeargs + list(args) stdin = kwargs.get("stdin", "") stdout, stderr = StringIO(), StringIO() Context: [setup: add licensing declaration for setuptools (noticed by the FSF compliance folks) zooko@zooko.com**20100309184415 Ignore-this: 2dfa7d812d65fec7c72ddbf0de609ccb ] [setup: fix error in licensing declaration from Shawn Willden, as noted by the FSF compliance division zooko@zooko.com**20100309163736 Ignore-this: c0623d27e469799d86cabf67921a13f8 ] [CREDITS to Jacob Appelbaum zooko@zooko.com**20100304015616 Ignore-this: 70db493abbc23968fcc8db93f386ea54 ] [desert-island-build-with-proper-versions jacob@appelbaum.net**20100304013858] [docs: a few small edits to try to guide newcomers through the docs zooko@zooko.com**20100303231902 Ignore-this: a6aab44f5bf5ad97ea73e6976bc4042d 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. ] [TAG allmydata-tahoe-1.6.1 david-sarah@jacaranda.org**20100228062314 Ignore-this: eb5f03ada8ea953ee7780e7fe068539 ] Patch bundle hash: 7e2f8933bb23af7dc504dab74325f01cfbbea2ec