# Richard Darst, 2009 import glob import os import re import shutil import sys import tempfile import unittest import time os.environ['MEETBOT_RUNNING_TESTS'] = '1' import ircmeeting.meeting as meeting import ircmeeting.writers as writers running_tests = True def parse_time(time_): try: return time.strptime(time_, "%H:%M:%S") except ValueError: pass try: return time.strptime(time_, "%H:%M") except ValueError: pass def process_meeting(contents, extraConfig={}, dontSave=True, filename='/dev/null'): """Take a test script, return Meeting object of that meeting. To access the results (a dict keyed by extensions), use M.save(), with M being the return of this function. """ return meeting.process_meeting(contents=contents, channel="#none", filename=filename, dontSave=dontSave, safeMode=False, extraConfig=extraConfig) class MeetBotTest(unittest.TestCase): def test_replay(self): """Replay of a meeting, using 'meeting.py replay'. """ old_argv = sys.argv[:] sys.argv[1:] = ["replay", "test-script-1.log.txt"] sys.path.insert(0, "../ircmeeting") try: gbls = {"__name__":"__main__", "__file__":"../ircmeeting/meeting.py"} execfile("../ircmeeting/meeting.py", gbls) assert "M" in gbls, "M object not in globals: did it run?" finally: del sys.path[0] sys.argv[:] = old_argv def test_supybottests(self): """Test by sending input to supybot, check responses. Uses the external supybot-test command. Unfortunantly, that doesn't have a useful status code, so I need to parse the output. """ os.symlink("../MeetBot", "MeetBot") os.symlink("../ircmeeting", "ircmeeting") sys.path.insert(0, ".") try: output = os.popen("supybot-test ./MeetBot 2>&1").read() assert 'FAILED' not in output, "supybot-based tests failed." assert '\nOK\n' in output, "supybot-based tests failed." finally: os.unlink("MeetBot") os.unlink("ircmeeting") del sys.path[0] trivial_contents = """ 10:10:10 #startmeeting 10:10:10 blah 10:10:10 #endmeeting """ full_writer_map = { '.log.txt': writers.TextLog, '.log.1.html': writers.HTMLlog1, '.log.html': writers.HTMLlog2, '.1.html': writers.HTML1, '.html': writers.HTML2, '.rst': writers.ReST, '.rst.html': writers.HTMLfromReST, '.txt': writers.Text, '.mw': writers.MediaWiki, '.pmw': writers.PmWiki, '.tmp.txt|template=+template.txt': writers.Template, '.tmp.html|template=+template.html': writers.Template, } def M_trivial(self, contents=None, extraConfig={}): """Convenience wrapper to process_meeting. """ if contents is None: contents = self.trivial_contents return process_meeting(contents=contents, extraConfig=extraConfig) def test_script_1(self): """Run test-script-1.log.txt through the processor. - Check all writers - Check actual file writing. """ tmpdir = tempfile.mkdtemp(prefix='test-meetbot') try: process_meeting(contents=file('test-script-1.log.txt').read(), filename=os.path.join(tmpdir, 'meeting'), dontSave=False, extraConfig={'writer_map':self.full_writer_map, }) # Test every extension in the full_writer_map to make sure # it was written. for extension in self.full_writer_map: ext = re.search(r'^\.(.*?)($|\|)', extension).group(1) files = glob.glob(os.path.join(tmpdir, 'meeting.'+ext)) assert len(files) > 0, \ "Extension did not produce output: '%s'"%extension finally: shutil.rmtree(tmpdir) #def test_script_3(self): # process_meeting(contents=file('test-script-3.log.txt').read(), # extraConfig={'writer_map':self.full_writer_map}) all_commands_test_contents = """ 10:10:10 #startmeeting 10:10:10 #topic h6k4orkac 10:10:10 #info blaoulrao 10:10:10 #idea alrkkcao4 10:10:10 #help ntoircoa5 10:10:10 #link http://bnatorkcao.net kroacaonteu 10:10:10 http://jrotjkor.net krotroun 10:10:10 #action xrceoukrc 10:10:10 #nick okbtrokr # Should not appear in non-log output 10:10:10 #idea ckmorkont 10:10:10 #undo # Assert that chairs can change the topic, and non-chairs can't. 10:10:10 #chair y 10:10:10 #topic topic_doeschange 10:10:10 #topic topic_doesntchange 10:10:10 #unchair y 10:10:10 #topic topic_doesnt2change 10:10:10 #endmeeting """ def test_contents_test2(self): """Ensure that certain input lines do appear in the output. This test ensures that the input to certain commands does appear in the output. """ M = process_meeting(contents=self.all_commands_test_contents, extraConfig={'writer_map':self.full_writer_map}) results = M.save() for name, output in results.iteritems(): self.assert_('h6k4orkac' in output, "Topic failed for %s"%name) self.assert_('blaoulrao' in output, "Info failed for %s"%name) self.assert_('alrkkcao4' in output, "Idea failed for %s"%name) self.assert_('ntoircoa5' in output, "Help failed for %s"%name) self.assert_('http://bnatorkcao.net' in output, "Link(1) failed for %s"%name) self.assert_('kroacaonteu' in output, "Link(2) failed for %s"%name) self.assert_('http://jrotjkor.net' in output, "Link detection(1) failed for %s"%name) self.assert_('krotroun' in output, "Link detection(2) failed for %s"%name) self.assert_('xrceoukrc' in output, "Action failed for %s"%name) self.assert_('okbtrokr' in output, "Nick failed for %s"%name) # Things which should only appear or not appear in the # notes (not the logs): if 'log' not in name: self.assert_( 'ckmorkont' not in output, "Undo failed for %s"%name) self.assert_('topic_doeschange' in output, "Chair changing topic failed for %s"%name) self.assert_('topic_doesntchange' not in output, "Non-chair not changing topic failed for %s"%name) self.assert_('topic_doesnt2change' not in output, "Un-chaired was able to chang topic for %s"%name) #def test_contents_test(self): # contents = open('test-script-3.log.txt').read() # M = process_meeting(contents=file('test-script-3.log.txt').read(), # extraConfig={'writer_map':self.full_writer_map}) # results = M.save() # for line in contents.split('\n'): # m = re.search(r'#(\w+)\s+(.*)', line) # if not m: # continue # type_ = m.group(1) # text = m.group(2) # text = re.sub('[^\w]+', '', text).lower() # # m2 = re.search(t2, re.sub(r'[^\w\n]', '', results['.txt'])) # import fitz.interactnow # print m.groups() def test_actionNickMatching(self): """Test properly detect nicknames in lines This checks the 'Action items, per person' list to make sure that the nick matching is limited to full words. For example, the nick 'jon' will no longer be assigned lines containing 'jonathan'. """ script = """ 20:13:50 #startmeeting 20:13:50 20:13:50 #action say somenickLONG 20:13:50 #action say the somenicklong 20:13:50 I should not have an item assisgned to me. 20:13:50 I should have some things assigned to me. 20:13:50 #endmeeting """ M = process_meeting(script) results = M.save()['.html'] # This regular expression is: # \bsomenick\b - the nick in a single word # (?! \() - without " (" following it... to not match # the "People present" section. assert not re.search(r'\bsomenick\b(?! \()', results, re.IGNORECASE), \ "Nick full-word matching failed" def test_urlMatching(self): """Test properly detection of URLs in lines """ script = """ 20:13:50 #startmeeting 20:13:50 #link prefix http://site1.com suffix 20:13:50 http://site2.com suffix 20:13:50 ftp://ftpsite1.com suffix 20:13:50 #link prefix ftp://ftpsite2.com suffix 20:13:50 irc://ircsite1.com suffix 20:13:50 mailto://a@mail.com suffix 20:13:50 #endmeeting """ M = process_meeting(script) results = M.save()['.html'] assert re.search(r'prefix.*href.*http://site1.com.*suffix', results), "URL missing 1" assert re.search(r'href.*http://site2.com.*suffix', results), "URL missing 2" assert re.search(r'href.*ftp://ftpsite1.com.*suffix', results), "URL missing 3" assert re.search(r'prefix.*href.*ftp://ftpsite2.com.*suffix', results), "URL missing 4" assert re.search(r'href.*mailto://a@mail.com.*suffix', results), "URL missing 5" def t_css(self): """Runs all CSS-related tests. """ self.test_css_embed() self.test_css_noembed() self.test_css_file_embed() self.test_css_file() self.test_css_none() def test_css_embed(self): extraConfig={ } results = self.M_trivial(extraConfig={}).save() self.assert_(']+)> *(.*)') loglineAction_re = re.compile(r'\[?([0-9: ]*)\]? *\* *([^ ]+) *(.*)') M = process_meeting('#startmeeting') log = [] M._sendReply = lambda x: log.append(x) M.config.agenda._voters = ['x', 'z'] M.config.agenda._agenda = [['first item', ['opt1', 'opt2']], ['second item', []]] M.config.agenda._votes = { } for i in M.config.agenda._agenda: M.config.agenda._votes[i[0]] = { } M.starttime = time.gmtime(0) contents = """20:13:50 #nextitem 20:13:50 #nextitem 20:13:50 #previtem 20:13:50 #previtem 20:13:50 #startvote 20:13:50 #vote 10 20:13:50 #vote 1 20:13:50 #vote 0 20:13:50 #vote 0 20:13:50 #endvote 20:13:50 #endmeeting""" for line in contents.split('\n'): # match regular spoken lines: m = logline_re.match(line) if m: time_ = parse_time(m.group(1).strip()) nick = m.group(2).strip() line = m.group(3).strip() if M.owner is None: M.owner = nick ; M.chairs = {nick:True} M.addline(nick, line, time_=time_) # match /me lines m = loglineAction_re.match(line) if m: time_ = parse_time(m.group(1).strip()) nick = m.group(2).strip() line = m.group(3).strip() M.addline(nick, "ACTION "+line, time_=time_) self.assert_(M.config.agenda._votes == {'first item': {u'x': 'opt2', u'z': 'opt1'}, 'second item': {}}) answers = ['Current agenda item is second item.', 'Current agenda item is second item.', 'Current agenda item is first item.', 'Current agenda item is first item.', 'Voting started. Your choices are: ', '0. first item', "1. ['opt1', 'opt2']", ' Vote #vote