Commits

chmullig committed 91cd183

* Added unit tests for specific functions (still a long ways from complete though)
* Fixed the google calculator for regular expressions
* Made the bot.start() command optional in run, for happier testing

Comments (0)

Files changed (9)

 @command("googlecalc", aliases=('gc',), doc="Calculate something using google")
 def googlecalc(client, event, channel, nick, rest):
 	query = rest
-	gcre = re.compile('<h2 class=r style="font-size:138%"><b>(.+?)</b>')
 	html = get_html('http://www.google.com/search?%s' % urllib.urlencode({'q' : query}))
-	return plaintext(gcre.search(html).group(1))
+	try:
+		gcre = re.compile('<h2 class=r style="font-size:138%"><b>(.+?)</b>')
+		res = plaintext(gcre.search(html).group(1))
+	except AttributeError:
+		gcre = re.compile('<h3 class=r><b>(.+?)</b>')
+		res = plaintext(gcre.search(html).group(1))
+	return res
+		
 
 @command("time", doc="What time is it in.... Similar to !weather")
 def googletime(client, event, channel, nick, rest):
 
 global config
 
-def run(configFile=None, configDict=None):
+def run(configFile=None, configDict=None, start=True):
 	global config
 	import sys, yaml
 	class O(object): 
 	bot = LoggingCommandBot(config.database_dir, config.server_host, config.server_port, 
 		config.bot_nickname, config.log_channels, config.other_channels,
 		config.feed_interval*60, config.feeds)
-	bot.start()
+	if start:
+		bot.start()
 	def karmaChange(self, thing, change):
 		thing = thing.strip().lower()
 		value = int(self.karmaLookup(thing)) + int(change)
-		log_id, log_message = self.db.execute('SELECT id, message FROM LOGS order by datetime desc limit 1').fetchone()
 		UPDATE_SQL = 'UPDATE karma_values SET karmavalue = ? where karmaid = (select karmaid from karma_keys where karmakey = ?)'
 		res = self.db.execute(UPDATE_SQL, (value, thing))
 		if res.rowcount == 0:
 			INSERT_KEY_SQL = 'INSERT INTO karma_keys (karmakey, karmaid) VALUES (?, ?)'
 			ins = self.db.execute(INSERT_VALUE_SQL, [value])
 			self.db.execute(INSERT_KEY_SQL, (thing, ins.lastrowid))
-		if thing in log_message.lower():
-			self.db.execute('INSERT INTO karma_log (karmakey, logid, change) VALUES (?, ?, ?)', (thing, log_id, change))
 		self.db.commit()
 
 	def karmaList(self, select=0):

tests/functional/__init__.py

 		
 	def send_message(self, channel, message):
 	   	self.c.privmsg(channel, message)
-		time.sleep(0.1)
+		time.sleep(0.05)
 
 class PmxbotHarness(object):
 	@classmethod
 		cls.db = sqlite3.connect(cls.dbfile)
 		
 		serverargs = shlex.split('/usr/bin/tclsh tclird/ircd.tcl')
-		cls.server = subprocess.Popen(['tclsh', os.path.join(path, 'tclircd/ircd.tcl')])
-		time.sleep(1)
+		cls.server = subprocess.Popen(['tclsh', os.path.join(path, 'tclircd/ircd.tcl')], stdout=open('/dev/null', 'w'), stderr=open('/dev/null', 'w'))
+		time.sleep(0.5)
 		cls.bot = subprocess.Popen(['pmxbot', configfile])
-		time.sleep(1)
+		time.sleep(0.5)
 		
 		cls.client = TestingClient('localhost', 6668, 'testingbot')
 
 	def check_logs(cls, channel='', nick='', message=''):
 		if channel.startswith('#'):
 			channel = channel[1:]
-		time.sleep(0.25)
+		time.sleep(0.1)
 		cursor = cls.db.cursor()
 		query = "select * from logs where 1=1"
 		if channel:
 	
 	@classmethod
 	def teardown_class(cls):
-		time.sleep(1)
 		cls.bot.terminate()
 		cls.server.terminate()
 		cls.db.rollback()

tests/unit/__init__.py

Empty file added.

tests/unit/__init__.pyc

Binary file added.

tests/unit/pmxbot.sqlite

Binary file added.

tests/unit/test_commands.py

+import pmxbot.pmxbot as pmxbot
+import re
+import os
+import uuid
+import datetime
+
+class Empty(object):
+	"""
+	Passed in to the individual commands instead of a client/event because
+	we don't normally care about them
+	"""
+	pass
+
+c = Empty()
+e = Empty()
+
+
+def logical_xor(a, b):
+	return bool(a) ^ bool(b)
+
+def onetrue(*args):
+	truthiness = filter(bool, args)
+	if len(truthiness) == 1:
+		return True
+	else:
+		return False
+
+class TestCommands(object):
+	@classmethod
+	def setup_class(self):
+		path = os.path.dirname(os.path.abspath(__file__))
+		configfile = os.path.join(path, 'testconf.yaml')
+		pmxbot.run(configFile=configfile, start=False)
+		pmxbot.botbase.logger.message("logged", "testrunner", "some text")
+		
+	def test_google(self):
+		"""
+		Basic google search for "pmxbot". Result must contain a link.
+		"""
+		res = pmxbot.google(c, e, "#test", "testrunner", "pmxbot")
+		assert "http" in res 
+
+	def test_googlecalc_simple(self):
+		"""
+		Basic google calculator command - 1+1 must include 2 in results
+		"""
+		res = pmxbot.googlecalc(c, e, "#test", "testrunner", "1+1")
+		assert "2" in res
+
+	def test_googlecalc_complicated(self):
+		"""
+		More complicated google calculator command - 40 gallons in liters must
+		include 151.4 in results
+		"""
+		res = pmxbot.googlecalc(c, e, "#test", "testrunner", "40 gallons in liters")
+		assert "151.4" in res
+
+	def test_googlecalc_currency(self):
+		"""
+		Test that google calculator for a currency conversion: 1 USD in GBP
+		"""
+		res = pmxbot.googlecalc(c, e, "#test", "testrunner", "1 USD in GBP")
+		print res
+		assert re.match(r"""1 US dollars? = \d\.\d+ British pounds? sterling""", res) 
+		
+	def test_time_one(self):
+		"""
+		Check the time in Washington, DC. Must include something that looks like a time XX:XX(AM/PM)
+		"""
+		res = pmxbot.googletime(c, e, "#test", "testrunner", "Washington, DC")
+		for line in res:
+			assert re.match(r"""^[0-9]{1,2}:[0-9]{2}(?:am|pm) """, line)
+
+	def test_time_three(self):
+		"""
+		Check the time in three cities. Must include something that looks like
+		a time XX:XX(AM/PM) on each line
+		"""
+		res = pmxbot.googletime(c, e, "#test", "testrunner", "Washington, DC | Palo Alto, CA | London")
+		for line in res:
+			assert re.match(r"""^[0-9]{1,2}:[0-9]{2}(?:am|pm) """, line)
+	
+	def test_time_all(self):
+		"""
+		Check the time in "all" cities. Must include something that looks like
+		a time XX:XX(AM/PM) on each line
+		"""
+		res = pmxbot.googletime(c, e, "#test", "testrunner", "all")
+		for line in res:
+			assert re.match(r"""^[0-9]{1,2}:[0-9]{2}(?:am|pm) """, line)
+			
+	def test_weather_one(self):
+		"""
+		Check the weather in Washington, DC. Must include something that looks like a weather XX:XX(AM/PM)
+		"""
+		res = pmxbot.weather(c, e, "#test", "testrunner", "Washington, DC")
+		for line in res:
+			assert re.match(r""".+\. Currently: (?:-)?[0-9]{1,3}F/(?:-)?[0-9]{1,2}C, .+\.\W+[A-z]{3}: (?:-)?[0-9]{1,3}F/(?:-)?[0-9]{1,2}C, """, line)
+
+	def test_weather_three(self):
+		"""
+		Check the weather in three cities. Must include something that looks like
+		a weather XX:XX(AM/PM) on each line
+		"""
+		res = pmxbot.weather(c, e, "#test", "testrunner", "Washington, DC | Palo Alto, CA | London")
+		for line in res:
+			assert re.match(r""".+\. Currently: (?:-)?[0-9]{1,3}F/(?:-)?[0-9]{1,2}C, .+\.\W+[A-z]{3}: (?:-)?[0-9]{1,3}F/(?:-)?[0-9]{1,2}C, """, line)
+
+	def test_weather_all(self):
+		"""
+		Check the weather in "all" cities. Must include something that looks like
+		a weather XX:XX(AM/PM) on each line
+		"""
+		res = pmxbot.weather(c, e, "#test", "testrunner", "all")
+		for line in res:
+			assert re.match(r""".+\. Currently: (?:-)?[0-9]{1,3}F/(?:-)?[0-9]{1,2}C, .+\.\W+[A-z]{3}: (?:-)?[0-9]{1,3}F/(?:-)?[0-9]{1,2}C, """, line)
+			
+	def test_boo(self):
+		"""
+		Test "boo foo"
+		"""
+		subject = "foo"
+		pre = pmxbot.karmaLookup(pmxbot.botbase.logger.db, subject)
+		res = pmxbot.boo(c, e, "#test", "testrunner", subject)
+		assert res == "/me BOOO %s!!! BOOO!!!" % subject
+		post = pmxbot.karmaLookup(pmxbot.botbase.logger.db, subject)
+		assert post == pre - 1
+
+	def test_troutslap(self):
+		"""
+		Test "troutslap foo"
+		"""
+		subject = "foo"
+		pre = pmxbot.karmaLookup(pmxbot.botbase.logger.db, subject)
+		res = pmxbot.troutslap(c, e, "#test", "testrunner", subject)
+		assert res == "/me slaps %s around a bit with a large trout" % subject
+		post = pmxbot.karmaLookup(pmxbot.botbase.logger.db, subject)
+		assert post == pre - 1
+		
+	def test_keelhaul(self):
+		"""
+		Test "keelhaul foo"
+		"""
+		subject = "foo"
+		pre = pmxbot.karmaLookup(pmxbot.botbase.logger.db, subject)
+		res = pmxbot.keelhaul(c, e, "#test", "testrunner", subject)
+		assert res == "/me straps %s to a dirty rope, tosses 'em overboard and pulls with great speed. Yarrr!" % subject
+		post = pmxbot.karmaLookup(pmxbot.botbase.logger.db, subject)
+		assert post == pre - 1
+		
+	def test_motivate(self):
+		"""
+		Test that motivate actually works.
+		"""
+		subject = "foo"
+		pre = pmxbot.karmaLookup(pmxbot.botbase.logger.db, subject)
+		res = pmxbot.motivate(c, e, "#test", "testrunner", subject)
+		assert res == "you're doing good work, %s!" % subject
+		post = pmxbot.karmaLookup(pmxbot.botbase.logger.db, subject)
+		assert post == pre + 1
+		
+		
+	def test_motivate_with_spaces(self):
+		"""
+		Test that motivate strips beginning and ending whitespace
+		"""
+		subject = "foo"
+		pre = pmxbot.karmaLookup(pmxbot.botbase.logger.db, subject)
+		res = pmxbot.motivate(c, e, "#test", "testrunner", "   %s 	  " % subject)
+		assert res == "you're doing good work, %s!" % subject
+		post = pmxbot.karmaLookup(pmxbot.botbase.logger.db, subject)
+		assert post == pre + 1
+
+	def test_demotivate(self):
+		"""
+		Test that demotivate actually works.
+		"""
+		subject = "foo"
+		pre = pmxbot.karmaLookup(pmxbot.botbase.logger.db, subject)
+		res = pmxbot.demotivate(c, e, "#test", "testrunner", subject)
+		assert res == "you're doing horrible work, %s!" % subject
+		post = pmxbot.karmaLookup(pmxbot.botbase.logger.db, subject)
+		assert post == pre - 1
+
+	def test_imotivate(self):
+		"""
+		Test that ironic/sarcastic motivate actually works.
+		"""
+		subject = "foo"
+		pre = pmxbot.karmaLookup(pmxbot.botbase.logger.db, subject)
+		res = pmxbot.imotivate(c, e, "#test", "testrunner", subject)
+		assert res == """you're "doing" "good" "work", %s!""" % subject
+		post = pmxbot.karmaLookup(pmxbot.botbase.logger.db, subject)
+		assert post == pre - 1
+		
+	def test_add_quote(self):
+		"""
+		Try adding a quote
+		"""
+		quote = "And then she said %s" % str(uuid.uuid4())
+		res = pmxbot.quote(c, e, "#test", "testrunner", "add %s" % quote)
+		assert res == "Quote added!"
+		cursor = pmxbot.botbase.logger.db.cursor()
+		cursor.execute("select count(*) from quotes where library = 'pmx' and quote = ?", (quote,))
+		numquotes = cursor.fetchone()[0]
+		assert numquotes == 1
+
+	def test_add_and_retreive_quote(self):
+		"""
+		Try adding a quote, then retrieving it
+		"""
+		id = str(uuid.uuid4())
+		quote = "So I says to Mabel, I says, %s" % id
+		res = pmxbot.quote(c, e, "#test", "testrunner", "add %s" % quote)
+		assert res == "Quote added!"
+		cursor = pmxbot.botbase.logger.db.cursor()
+		cursor.execute("select count(*) from quotes where library = 'pmx' and quote = ?", (quote,))
+		numquotes = cursor.fetchone()[0]
+		assert numquotes == 1
+		
+		res = pmxbot.quote(c, e, "#test", "testrunner", id)
+		assert res == "(1/1): %s" % quote
+		
+	def test_roll(self):
+		"""
+		Roll a die, both with no arguments and with some numbers
+		"""
+		res = int(pmxbot.roll(c, e, "#test", "testrunner", "").split()[-1])
+		assert res >= 0 and res <= 100
+		n = 6668
+		
+		res = int(pmxbot.roll(c, e, "#test", "testrunner", "%s" % n).split()[-1])
+		assert res >= 0 and res <= n
+		
+	def test_ticker_goog(self):
+		"""
+		Get the current stock price of Google.
+		
+		GOOG at 4:00pm (ET): 484.81 (1.5%)
+		"""
+		res = pmxbot.ticker(c, e, "#test", "testrunner", "goog")
+		assert re.match(r"""^GOOG at \d{1,2}:\d{2}(?:am|pm) \([A-z]{1,3}\): \d{2,4}.\d{2} \(\d{1,3}.\d%\)$""", res)
+		
+	def test_ticker_yougov(self):
+		"""
+		Get the current stock price of YouGov.
+		
+		YOU.L at 10:37am (ET): 39.40 (0.4%)
+		"""
+		res = pmxbot.ticker(c, e, "#test", "testrunner", "you.l")
+		assert re.match(r"""^YOU.L at \d{1,2}:\d{2}(?:am|pm) \([A-z]{1,3}\): \d{1,4}.\d{2} \(\d{1,3}.\d%\)$""", res)
+		
+	def test_ticker_dow(self):
+		"""
+		Get the current stock price of the dow jones.
+
+		^DJI at 10:37am (ET): 39.40 (0.4%)
+		"""
+		res = pmxbot.ticker(c, e, "#test", "testrunner", "^dji")
+		assert re.match(r"""^\^DJI at \d{1,2}:\d{2}(?:am|pm) \([A-z]{1,3}\): \d{4,5}.\d{2} \(\d{1,3}.\d%\)$""", res)
+		
+	def test_pick_or(self):
+		"""
+		Test the pick command with a simple or expression
+		"""
+		res = pmxbot.pick(c, e, "#test", "testrunner", "fire or acid")
+		assert logical_xor("fire" in res, "acid" in res)
+		assert " or " not in res
+		
+	def test_pick_or_intro(self):
+		"""
+		Test the pick command with an intro and a simple "or" expression
+		"""
+		res = pmxbot.pick(c, e, "#test", "testrunner", "how would you like to die, pmxbot: fire or acid")
+		assert logical_xor("fire" in res, "acid" in res)
+		assert "die" not in res and "pmxbot" not in res and " or " not in res
+
+	def test_pick_comma(self):
+		"""
+		Test the pick command with two options separated by commas
+		"""
+		res = pmxbot.pick(c, e, "#test", "testrunner", "fire, acid")
+		assert logical_xor("fire" in res, "acid" in res)
+		
+	def test_pick_comma_intro(self):
+		"""
+		Test the pick command with an intro followed by two options separted by commas
+		"""
+		res = pmxbot.pick(c, e, "#test", "testrunner", "how would you like to die, pmxbot: fire, acid")
+		assert logical_xor("fire" in res, "acid" in res)
+		assert "die" not in res and "pmxbot" not in res
+		
+	def test_pick_comma_or_intro(self):
+		"""
+		Test the pick command with an intro followed by options with commands and ors
+		"""
+		res = pmxbot.pick(c, e, "#test", "testrunner", "how would you like to die, pmxbot: gun, fire, acid or defenestration")
+		assert onetrue("gun" in res, "fire" in res, "acid" in res, "defenestration" in res)
+		assert "die" not in res and "pmxbot" not in res and " or " not in res
+
+	def test_lunch(self):
+		"""
+		Test that the lunch command selects one of the list options
+		"""
+		res = pmxbot.lunch(c, e, "#test", "testrunner", "PA")
+		assert res in ["Pasta?", "Thaiphoon", "Pluto's", "Penninsula Creamery", "Kan Zeman"]
+	
+	def test_karma_check_self_blank(self):
+		"""
+		Determine your own, blank, karma.
+		"""
+		id = str(uuid.uuid4())[:15]
+		res = pmxbot.karma(c, e, "#test", id, "")
+		assert re.match(r"^%s has 0 karmas$" % id, res)
+	
+	def test_karma_check_other_blank(self):
+		"""
+		Determine some else's blank/new karma.
+		"""
+		id = str(uuid.uuid4())
+		res = pmxbot.karma(c, e, "#test", "testrunner", id)
+		assert re.match("^%s has 0 karmas$" % id, res)
+	
+	def test_karma_set_and_check(self):
+		"""
+		Take a new entity, give it some karma, check that it has more
+		"""
+		id = str(uuid.uuid4())
+		res = pmxbot.karma(c, e, "#test", "testrunner", id)
+		assert re.match("^%s has 0 karmas$" % id, res)
+		res = pmxbot.karma(c, e, "#test", "testrunner", "%s++" %id)
+		res = pmxbot.karma(c, e, "#test", "testrunner", "%s++" %id)
+		res = pmxbot.karma(c, e, "#test", "testrunner", "%s++" %id)
+		res = pmxbot.karma(c, e, "#test", "testrunner", "%s--" %id)
+		res = pmxbot.karma(c, e, "#test", "testrunner", id)
+		assert re.match(r"^%s has 2 karmas$" % id, res)
+		
+		
+	def test_karma_set_and_check_with_space(self):
+		"""
+		Take a new entity that has a space in it's name, give it some karma, check that it has more
+		"""
+		id = str(uuid.uuid4()).replace("-", " ")
+		res = pmxbot.karma(c, e, "#test", "testrunner", id)
+		assert re.match("^%s has 0 karmas$" % id, res)
+		res = pmxbot.karma(c, e, "#test", "testrunner", "%s++" %id)
+		res = pmxbot.karma(c, e, "#test", "testrunner", "%s++" %id)
+		res = pmxbot.karma(c, e, "#test", "testrunner", "%s++" %id)
+		res = pmxbot.karma(c, e, "#test", "testrunner", "%s--" %id)
+		res = pmxbot.karma(c, e, "#test", "testrunner", id)
+		assert re.match(r"^%s has 2 karmas$" % id, res)
+	
+	def test_karma_randomchange(self):
+		"""
+		Take a new entity that has a space in it's name, give it some karma, check that it has more
+		"""
+		id = str(uuid.uuid4())
+		flags = {}
+		i = 0
+		karmafetch = re.compile(r"^%s has (\-?\d+) karmas$" % id)
+		while len(flags) < 3 and i <= 30:
+			res = pmxbot.karma(c, e, "#test", "testrunner", id)
+			prekarma = int(karmafetch.findall(res)[0])
+			change = pmxbot.karma(c, e, "#test", "testrunner", "%s~~" % id)
+			assert change in ["%s karma++" % id, "%s karma--" % id, "%s karma shall remain the same" % id]
+			if change.endswith('karma++'):
+				flags['++'] = True
+				res = pmxbot.karma(c, e, "#test", "testrunner", id)
+				postkarma = int(karmafetch.findall(res)[0])
+				assert postkarma == prekarma + 1
+			elif change.endswith('karma--'):
+				flags['--'] = True
+				res = pmxbot.karma(c, e, "#test", "testrunner", id)
+				postkarma = int(karmafetch.findall(res)[0])
+				assert postkarma == prekarma - 1
+			elif change.endswith('karma shall remain the same'):
+				flags['same'] = True
+				res = pmxbot.karma(c, e, "#test", "testrunner", id)
+				postkarma = int(karmafetch.findall(res)[0])
+				assert postkarma == prekarma
+			i+=1
+		assert len(flags) == 3
+		assert i < 30
+
+
+#	def test_yahoolunch_zip(self):
+#		"""
+#		Test that the lunch function returns something that looks right when asked with a zip.
+#		
+#		Bob's Ranch House @ 585 Collier Way - http://local.yahoo.com/info-21835324-bob-s-ranch-house-etna
+#		The Indian Experience @ 1708 L St Nw - http://local.yahoo.com/info-34655142-the-indian-experience-washington
+#		"""
+#		res = pmxbot.lunch(c, e, "#test", "testrunner", "20009")
+#		print res
+#		assert re.match(r"""^.+? @ .+? - http://local.yahoo.com/info-\d+-.+$""", res, re.DOTALL)
+#		
+#	def test_yahoolunch_zip(self):
+#		"""
+#		Test that the lunch function returns something that looks right when asked with an address.
+#
+#		Bob's Ranch House @ 585 Collier Way - http://local.yahoo.com/info-21835324-bob-s-ranch-house-etna
+#		The Indian Experience @ 1708 L St Nw - http://local.yahoo.com/info-34655142-the-indian-experience-washington
+#		"""
+#		res = pmxbot.lunch(c, e, "#test", "testrunner", "1600 Pennsylvania Ave, Washington, DC")
+#		print res
+#		assert re.match(r"""^.+? @ .+? - http://local.yahoo.com/info-\d+-.+$""", res, re.DOTALL)
+#
+#	def test_yahoolunch_zip_radius(self):
+#		"""
+#		Test that the lunch function returns something when asked with a radius
+#
+#		Bob's Ranch House @ 585 Collier Way - http://local.yahoo.com/info-21835324-bob-s-ranch-house-etna
+#		The Indian Experience @ 1708 L St Nw - http://local.yahoo.com/info-34655142-the-indian-experience-washington
+#		"""
+#		res = pmxbot.lunch(c, e, "#test", "testrunner", "20009 4mi")
+#		print res
+#		assert re.match(r"""^.+? @ .+? - http://local.yahoo.com/info-\d+-.+$""", res, re.DOTALL)

tests/unit/test_commands.pyc

Binary file added.

tests/unit/testconf.yaml

+server_host: "localhost"
+server_port: 6668
+bot_nickname: integrationbot
+log_channels: 
+    - "#logged"
+other_channels:
+    - "#inane"
+    - "#test"
+inane_channel: "#inane"
+database_dir: "tests/unit"
+places: ["Palo Alto, CA", "Canton, OH", "Washington, DC", "London, England"]
+lunch_choices:
+    PA: ["Pasta?", "Thaiphoon", "Pluto's", "Penninsula Creamery", "Kan Zeman"]
+feed_interval: 15 #15 minutes
+feeds:
+    - name    : "pmxbot bitbucket"
+      channel : "#inane"
+      linkurl : "http://bitbucket.org/yougov/pmxbot"
+      url     : "http://bitbucket.org/yougov/pmxbot"
+local_extensions: []