From 58cb4f5a839aea03a086a4849dc73d74e53f1026 Mon Sep 17 00:00:00 2001 From: Ben Kurtovic Date: Sun, 14 Aug 2011 23:53:17 -0400 Subject: [PATCH 1/3] UNIT TESTS! --- bot/blowfish.py | 3 +- tests/support.py | 21 ++++++++++++++ tests/test_blowfish.py | 77 ++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 99 insertions(+), 2 deletions(-) create mode 100644 tests/support.py create mode 100644 tests/test_blowfish.py diff --git a/bot/blowfish.py b/bot/blowfish.py index d0862a9..84feab1 100644 --- a/bot/blowfish.py +++ b/bot/blowfish.py @@ -514,8 +514,7 @@ def encrypt(key, plaintext): def decrypt(key, cyphertext): """Decrypt the result of encrypt() using the original key, or raise - IncorrectKeyError(). If the cyphertext is malformed, raise - BadCyphertextError().""" + DecryptionError().""" cypher = Blowfish(key) try: diff --git a/tests/support.py b/tests/support.py new file mode 100644 index 0000000..ac65d9d --- /dev/null +++ b/tests/support.py @@ -0,0 +1,21 @@ +# -*- coding: utf-8 -*- + +""" +EarwigBot's Unit Test Support + +This module provides some support code for unit tests. + +Importing this module will "fix" your path so that EarwigBot code from bot/ +can be imported normally. The run() function runs a given test case. +""" + +from os import path +import sys +import unittest + +root = path.join(path.split(path.dirname(path.abspath(__file__)))[0], "bot") +sys.path.insert(0, root) + +def run(case): + suite = unittest.TestLoader().loadTestsFromTestCase(case) + text_runner = unittest.TextTestRunner(verbosity=2).run(suite) diff --git a/tests/test_blowfish.py b/tests/test_blowfish.py new file mode 100644 index 0000000..3b209de --- /dev/null +++ b/tests/test_blowfish.py @@ -0,0 +1,77 @@ +# -*- coding: utf-8 -*- + +import unittest +import random +import string + +import support +import blowfish + +class TestBlowfish(unittest.TestCase): + + def test_key_sizes(self): + b = blowfish.Blowfish + e = blowfish.KeyLengthError + + self.assertRaisesRegexp(e, "no key given", b, None) + self.assertRaisesRegexp(e, "no key given", b, "") + self.assertRaisesRegexp(e, "at least", b, " " * 3) + self.assertRaisesRegexp(e, "at least", b, "1234567") + self.assertRaisesRegexp(e, "less than", b, " " * 57) + self.assertRaisesRegexp(e, "less than", b, "x" * 60) + self.assertRaisesRegexp(e, "less than", b, "1" * 128) + + b("These keys should be valid!") + b("'!\"#$%&\'()*+,-./:;<=>?@[\\]^_`{|}~'") + b(" " * 8) + b(" " * 56) + + def test_symmetry(self): + def _test_symmetry(): + key_length = random.randint(8, 56) + msg_length = random.randint(0, 4096) + key = "".join([random.choice(chars) for i in xrange(key_length)]) + msg = "".join([random.choice(chars) for i in xrange(msg_length)]) + + enc = blowfish.encrypt(key, msg) + dec = blowfish.decrypt(key, enc) + self.assertEqual(dec, msg) + + chars = string.letters + string.digits + string.punctuation + for i in xrange(8): + _test_symmetry() + + def test_encryption(self): + tests = [ + ("example_key", "Hello, I'm a message!", "8411a21574431176cdff9a549d27962c616014a9fe2a1fe3b0c7a823e8a1e635"), + ("another random key", "Another random message! :(", "2cdcdf4e53145897ed9d4cc2433aa4bf59b087b14d0ac76a13eff12dec00e60c40857109da3c7bc4"), + ("HEY LET'S TRY |°|_J|\|C7|_J/-\\710|\|", "Yes, that was my fail attempt at 1337SP33K >_>", "d4901c7c0956da3b9507cd81cd3c880d7cda25ec6c5336deb9280ce67c099eeddf7c7e052f3a946afbd92c32ae0ab8dbdd875bc5a3f0d686") + ] + + for test in tests: + self.assertEquals(blowfish.encrypt(test[0], test[1]), test[2]) + + def test_decryption(self): + tests = [ + ("blah blah blah", "ab35274c66bb8b3b03c9bd26ab477f3de06857e1d369ad35", "Blah, blah, blah!"), + ("random key", "eb2fe950c5c12bca9534ffdd27631f33d3e4bcae53a634b4aaa09f9fe14c4386", "Random message as well!"), + ("Okay, now I'm just desperate", "0da74e1cec41e8323da93d0c05bcf3919084130cef93021991da174fd97f8e1c9b125ed5263b41a8", "Unit testing is SO FUN ISN'T IT.") + ] + + for test in tests: + self.assertEquals(blowfish.decrypt(test[0], test[1]), test[2]) + + def test_decryption_exceptions(self): + d = blowfish.decrypt + e = blowfish.BlowfishError + + e1 = "could not be decoded" + e2 = "cannot be broken into 8-byte blocks" + e3 = "key is incorrect" + + self.assertRaisesRegexp(e, e1, d, "some_key", "arr!") + self.assertRaisesRegexp(e, e2, d, "some_key", "abcd") + self.assertRaisesRegexp(e, e3, d, "some_key", "abcdabcdabcdabcd") + +if __name__ == "__main__": + support.run(TestBlowfish) From 6ad5a7fae6f8907e2aaa81e1085abcab3c9bc9d6 Mon Sep 17 00:00:00 2001 From: Ben Kurtovic Date: Mon, 15 Aug 2011 19:09:44 -0400 Subject: [PATCH 2/3] more work on tests; added a framework for command testing, now testing !test (ha) --- bot/commands/help.py | 2 +- tests/support.py | 77 ++++++++++++++++++++++++++++++++++++++++++++------ tests/test_blowfish.py | 2 +- tests/test_test.py | 28 ++++++++++++++++++ 4 files changed, 99 insertions(+), 10 deletions(-) create mode 100644 tests/test_test.py diff --git a/bot/commands/help.py b/bot/commands/help.py index 5bb9e3d..b35d205 100644 --- a/bot/commands/help.py +++ b/bot/commands/help.py @@ -29,7 +29,7 @@ class Command(BaseCommand): # Create a dummy message to test which commands pick up the user's # input: - dummy = Data("PRIVMSG #fake-channel :Fake messsage!".split()) + dummy = Data(":foo!bar@example.com PRIVMSG #channel :msg!".split()) dummy.command = command.lower() dummy.is_command = True diff --git a/tests/support.py b/tests/support.py index ac65d9d..cd181d5 100644 --- a/tests/support.py +++ b/tests/support.py @@ -5,17 +5,78 @@ EarwigBot's Unit Test Support This module provides some support code for unit tests. -Importing this module will "fix" your path so that EarwigBot code from bot/ -can be imported normally. The run() function runs a given test case. +Importing this module will "fix" your path so that EarwigBot code from bot/ can +be imported normally. + +CommandTestCase is a subclass of unittest.TestCase that provides setUp() for +creating a fake connection and some other helpful methods. It uses +FakeConnection, a subclass of classes.Connection, but with an internal string +instead of a socket for data. """ from os import path +import re import sys -import unittest +from unittest import TestCase + +root_dir = path.split(path.dirname(path.abspath(__file__)))[0] +code_dir = path.join(root_dir, "bot") +sys.path.insert(0, code_dir) + +from classes import Connection, Data + +class CommandTestCase(TestCase): + re_sender = re.compile(":(.*?)!(.*?)@(.*?)\Z") + + def setUp(self, command): + self.connection = FakeConnection() + self.connection.connect() + self.command = command(self.connection) + + def get_single(self): + data = self.connection.get().split("\n") + line = data.pop(0) + for remaining in data: + self.connection.send(remaining) + return line + + def assertSent(self, msg): + line = self.get_single() + self.assertEqual(line, msg) + + def assertSentIn(self, msgs): + line = self.get_single() + self.assertIn(line, msgs) + + def maker(self, line, chan, msg=None): + data = Data(line) + data.nick, data.ident, data.host = self.re_sender.findall(line[0])[0] + if msg is not None: + data.msg = msg + data.chan = chan + data.parse_args() + return data + + def make_msg(self, command, *args): + line = ":Foo!bar@example.com PRIVMSG #channel :!{0}".format(command) + line = line.strip().split() + line.extend(args) + return self.maker(line, line[2], " ".join(line[3:])[1:]) + + def make_join(self): + line = ":Foo!bar@example.com JOIN :#channel".strip().split() + return self.maker(line, line[2][1:]) + +class FakeConnection(Connection): + def connect(self): + self._buffer = "" + + def close(self): + pass -root = path.join(path.split(path.dirname(path.abspath(__file__)))[0], "bot") -sys.path.insert(0, root) + def get(self, size=4096): + data, self._buffer = self._buffer, "" + return data -def run(case): - suite = unittest.TestLoader().loadTestsFromTestCase(case) - text_runner = unittest.TextTestRunner(verbosity=2).run(suite) + def send(self, msg): + self._buffer += msg + "\n" diff --git a/tests/test_blowfish.py b/tests/test_blowfish.py index 3b209de..9eb6184 100644 --- a/tests/test_blowfish.py +++ b/tests/test_blowfish.py @@ -74,4 +74,4 @@ class TestBlowfish(unittest.TestCase): self.assertRaisesRegexp(e, e3, d, "some_key", "abcdabcdabcdabcd") if __name__ == "__main__": - support.run(TestBlowfish) + unittest.main(verbosity=2) diff --git a/tests/test_test.py b/tests/test_test.py new file mode 100644 index 0000000..0ac0c37 --- /dev/null +++ b/tests/test_test.py @@ -0,0 +1,28 @@ +# -*- coding: utf-8 -*- + +import unittest + +import support +from commands.test import Command + +class TestTest(support.CommandTestCase): + + def setUp(self): + super(TestTest, self).setUp(Command) + + def test_check(self): + self.assertFalse(self.command.check(self.make_msg("bloop"))) + self.assertFalse(self.command.check(self.make_join())) + + self.assertTrue(self.command.check(self.make_msg("test"))) + self.assertTrue(self.command.check(self.make_msg("TEST", "foo"))) + + def test_process(self): + self.command.process(self.make_msg("test")) + + msgs = ["PRIVMSG #channel :Hey \x02Foo\x0F!", + "PRIVMSG #channel :'sup \x02Foo\x0F?"] + self.assertSentIn(msgs) + +if __name__ == "__main__": + unittest.main(verbosity=2) From e461592d71e53968d3290eba8b860375add20022 Mon Sep 17 00:00:00 2001 From: Ben Kurtovic Date: Mon, 15 Aug 2011 19:47:35 -0400 Subject: [PATCH 3/3] adding TestCalc for the IRC command; fixes, additions, cleanup to support etc --- bot/commands/calc.py | 2 +- tests/support.py | 16 +++++++++++++++- tests/test_calc.py | 39 +++++++++++++++++++++++++++++++++++++++ tests/test_test.py | 9 +++++---- 4 files changed, 60 insertions(+), 6 deletions(-) create mode 100644 tests/test_calc.py diff --git a/bot/commands/calc.py b/bot/commands/calc.py index 0fc0fbd..9fcb63a 100644 --- a/bot/commands/calc.py +++ b/bot/commands/calc.py @@ -12,7 +12,7 @@ class Command(BaseCommand): def process(self, data): if not data.args: - self.connection.reply(data, "What do you want me to calculate?") + self.connection.reply(data, "what do you want me to calculate?") return query = ' '.join(data.args) diff --git a/tests/support.py b/tests/support.py index cd181d5..a2dadb5 100644 --- a/tests/support.py +++ b/tests/support.py @@ -36,7 +36,7 @@ class CommandTestCase(TestCase): def get_single(self): data = self.connection.get().split("\n") line = data.pop(0) - for remaining in data: + for remaining in data[1:]: self.connection.send(remaining) return line @@ -48,6 +48,20 @@ class CommandTestCase(TestCase): line = self.get_single() self.assertIn(line, msgs) + def assertSaid(self, msg): + self.assertSent("PRIVMSG #channel :{0}".format(msg)) + + def assertSaidIn(self, msgs): + msgs = ["PRIVMSG #channel :{0}".format(msg) for msg in msgs] + self.assertSentIn(msgs) + + def assertReply(self, msg): + self.assertSaid("\x02Foo\x0F: {0}".format(msg)) + + def assertReplyIn(self, msgs): + msgs = ["\x02Foo\x0F: {0}".format(msg) for msg in msgs] + self.assertSaidIn(msgs) + def maker(self, line, chan, msg=None): data = Data(line) data.nick, data.ident, data.host = self.re_sender.findall(line[0])[0] diff --git a/tests/test_calc.py b/tests/test_calc.py new file mode 100644 index 0000000..981fda3 --- /dev/null +++ b/tests/test_calc.py @@ -0,0 +1,39 @@ +# -*- coding: utf-8 -*- + +import unittest + +import support +from commands.calc import Command + +class TestCalc(support.CommandTestCase): + + def setUp(self): + super(TestCalc, self).setUp(Command) + + def test_check(self): + self.assertFalse(self.command.check(self.make_msg("bloop"))) + self.assertFalse(self.command.check(self.make_join())) + + self.assertTrue(self.command.check(self.make_msg("calc"))) + self.assertTrue(self.command.check(self.make_msg("CALC", "foo"))) + + def test_ignore_empty(self): + self.command.process(self.make_msg("calc")) + self.assertReply("what do you want me to calculate?") + + def test_maths(self): + tests = [ + ("2 + 2", "2 + 2 = 4"), + ("13 * 5", "13 * 5 = 65"), + ("80 / 42", "80 / 42 = 40/21 (approx. 1.9047619047619047)"), + ("2/0", "2/0 = undef"), + ("π", "π = 3.141592653589793238"), + ] + + for test in tests: + q = test[0].strip().split() + self.command.process(self.make_msg("calc", *q)) + self.assertReply(test[1]) + +if __name__ == "__main__": + unittest.main(verbosity=2) diff --git a/tests/test_test.py b/tests/test_test.py index 0ac0c37..540e1eb 100644 --- a/tests/test_test.py +++ b/tests/test_test.py @@ -18,11 +18,12 @@ class TestTest(support.CommandTestCase): self.assertTrue(self.command.check(self.make_msg("TEST", "foo"))) def test_process(self): - self.command.process(self.make_msg("test")) + def _test(): + self.command.process(self.make_msg("test")) + self.assertSaidIn(["Hey \x02Foo\x0F!", "'sup \x02Foo\x0F?"]) - msgs = ["PRIVMSG #channel :Hey \x02Foo\x0F!", - "PRIVMSG #channel :'sup \x02Foo\x0F?"] - self.assertSentIn(msgs) + for i in xrange(64): + _test() if __name__ == "__main__": unittest.main(verbosity=2)