# Richard Darst, 2009 import glob import os import re import shutil import sys import tempfile import unittest os.environ['MEETBOT_RUNNING_TESTS'] = '1' import ircmeeting.meeting as meeting import ircmeeting.writers as writers import test_meeting running_tests = True 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_(' #startmeeting", "Meeting started .*\nUseful Commands: #action #agreed #help #info #idea #link #topic.\n") test.M.config.manage_agenda = True return(test) def test_agenda_item_changing(self): test = self.get_simple_agenda_test() # Test changing item before vote test.answer_should_match('20:13:50 #nextitem', 'Current agenda item is second item.') test.answer_should_match('20:13:50 #nextitem', 'Current agenda item is second item.') test.answer_should_match('20:13:50 #previtem', 'Current agenda item is first item.') test.answer_should_match('20:13:50 #previtem', 'Current agenda item is first item.') # Test changing item during vote test.process('20:13:50 #startvote') test.answer_should_match('20:13:50 #nextitem', 'Voting is currently ' +\ 'open so I didn\'t change item. Please #endvote first') test.answer_should_match('20:13:50 #previtem', 'Voting is currently ' +\ 'open so I didn\'t change item. Please #endvote first') # Test changing item after vote test.process('20:13:50 #endvote') test.answer_should_match('20:13:50 #nextitem', 'Current agenda item is second item.') test.answer_should_match('20:13:50 #nextitem', 'Current agenda item is second item.') test.answer_should_match('20:13:50 #previtem', 'Current agenda item is first item.') test.answer_should_match('20:13:50 #previtem', 'Current agenda item is first item.') def test_agenda_option_listing(self): test = self.get_simple_agenda_test() test.answer_should_match('20:13:50 #option list', 'Available voting options ' +\ 'are:\n0. opt1\n1. opt2\n') test.process('20:13:50 #nextitem') test.answer_should_match('20:13:50 #option list', 'No voting options available.') test.process('20:13:50 #previtem') test.answer_should_match('20:13:50 #option list', 'Available voting options ' +\ 'are:\n0. opt1\n1. opt2\n') def test_agenda_option_adding(self): test = self.get_simple_agenda_test() test.process('20:13:50 #nextitem') test.answer_should_match('20:13:50 #option add first option', 'You can not vote or change agenda. Only x, z can.') test.answer_should_match('20:13:50 #option add first option', 'You added new voting option: first option') test.answer_should_match('20:13:50 #option list', 'Available voting options ' +\ 'are:\n0. first option') def test_agenda_option_removing(self): test = self.get_simple_agenda_test() test.answer_should_match('20:13:50 #option remove 1', 'You can not vote or change agenda. Only x, z can.') test.answer_should_match('20:13:50 #option remove 1', 'You removed voting option 1: opt2') test.answer_should_match('20:13:50 #option list', 'Available voting options ' +\ 'are:\n0. opt1') def test_agenda_voting(self): test = self.get_simple_agenda_test() test.M.config.agenda._voters.append('t') test.answer_should_match('20:13:50 #startvote', 'Voting started\. ' +\ 'Available voting options are:\n0. opt1\n1. opt2\nVote ' +\ '#vote