1 2""" 3This module tests the code listings used in the documentation. 4""" 5 6import sys 7 8from twisted.python.filepath import FilePath 9from twisted.trial.unittest import SkipTest, TestCase 10 11from nevow._widget_plugin import ElementRenderingLivePage 12from nevow.testutil import renderLivePage, JavaScriptTestCase 13from nevow.athena import jsDeps, expose 14 15from nevow import plugins 16 17 18class ExampleTestBase(object): 19 """ 20 This is a mixin which adds an example to the path, tests it, and then 21 removes it from the path and unimports the modules which the test loaded. 22 Test cases which test example code and documentation listings should use 23 this. 24 25 This is done this way so that examples can live in isolated path entries, 26 next to the documentation, replete with their own plugin packages and 27 whatever other metadata they need. Also, example code is a rare instance 28 of it being valid to have multiple versions of the same code in the 29 repository at once, rather than relying on version control, because 30 documentation will often show the progression of a single piece of code as 31 features are added to it, and we want to test each one. 32 """ 33 34 examplePath = None 35 36 def setUp(self): 37 """ 38 Add our example directory to the path and record which modules are 39 currently loaded. 40 """ 41 self.originalPath = sys.path[:] 42 self.originalModules = sys.modules.copy() 43 here = FilePath(__file__).parent().parent().parent() 44 for childName in self.examplePath: 45 here = here.child(childName) 46 if not here.exists(): 47 raise SkipTest("Examples (%s) not found - cannot test" % (here.path,)) 48 sys.path.append(here.path) 49 # This stuff is pretty horrible; we need to turn off JS caching for the 50 # duration of this test (this flag will be set back to True by the 51 # plugin-loading itself, so no need to clean it up) because the 52 # previously-loaded list of plugins is going to be invalid. 53 jsDeps._loadPlugins = True 54 # Even more horrible! nevow.plugins.__path__ needs to be recomputed 55 # each time for the new value of sys.path. 56 reload(plugins) 57 58 59 def tearDown(self): 60 """ 61 Remove the example directory from the path and remove all modules loaded by 62 the test from sys.modules. 63 """ 64 sys.modules.clear() 65 sys.modules.update(self.originalModules) 66 sys.path[:] = self.originalPath 67 reload(plugins) 68 69 70 71class ExampleJavaScriptTestCase(JavaScriptTestCase): 72 """ 73 Since JavaScriptTestCase does not use setUp and tearDown, we can't use 74 simple inheritance to invoke the functionality in ExampleTestBase; so, 75 instead, this class composes JavaScriptTestCase with a ExampleTestBase. 76 """ 77 def run(self, result): 78 """ 79 Wrap L{JavaScriptTestCase.run} to change and restore the plugin environment 80 on each test run. 81 """ 82 base = ExampleTestBase() 83 base.examplePath = self.examplePath 84 try: 85 base.setUp() 86 except SkipTest, e: 87 result.startTest(self) 88 result.addSkip(self, str(e)) 89 result.stopTest(self) 90 else: 91 try: 92 return JavaScriptTestCase.run(self, result) 93 finally: 94 base.tearDown() 95 96 97def chatListing(partname): 98 """ 99 Return a list of strings that represents a path from the root of the Nevow 100 source package which contains a potential PYTHONPATH entry with some 101 listing code in it, based on which part of the chat tutorial it came from. 102 """ 103 return ['doc', 'howto', 'chattutorial', 'part'+partname, 'listings'] 104 105 106 107class Part00Tests(ExampleJavaScriptTestCase): 108 """ 109 Refer to JavaScript unit tests for section 01 of the tutorial. 110 """ 111 112 examplePath = chatListing('00') 113 114 def test_part00(self): 115 """ 116 Test method pointing to part 00 of the tutorial. 117 """ 118 return 'Nevow.Test.TestHowtoListing00' 119 120 121 122class Echo00(ExampleTestBase, TestCase): 123 """ 124 These tests will make sure that the very basic 'echobox' portion of the 125 tutorial works. 126 """ 127 examplePath = chatListing('00') 128 129 def test_renderEcho(self): 130 """ 131 Rendering the echo example element should produce a very simple text area. 132 """ 133 from echothing.echobox import EchoElement 134 TEXT = 'Echo Element' 135 eb = EchoElement() 136 erlp = ElementRenderingLivePage(eb) 137 def checkContent(result): 138 # The "liveElement" renderer inserts this, let's look for it to 139 # make sure it rendered live: 140 self.assertIn('id="athena:'+str(eb._athenaID)+'"', result) 141 self.assertIn('athena:class="EchoThing.EchoWidget"', result) 142 self.assertIn(TEXT, result) 143 return renderLivePage(erlp).addCallback(checkContent) 144 145 146 def test_echoTellsClient(self): 147 """ 148 When 'hear' is called on a ChatterElement, it should inform its client of what 149 was said and by whom. 150 """ 151 from echothing.echobox import EchoElement 152 eb = EchoElement() 153 echoed = [] 154 eb.callRemote = lambda method, message: echoed.append((method, message)) 155 eb.say(u'HELLO... Hello... hello...') 156 self.assertEquals(echoed, [('addText', u'HELLO... Hello... hello...')]) 157 158 159 160class Part01Tests(ExampleJavaScriptTestCase): 161 """ 162 Refer to JavaScript unit tests for section 01 of the tutorial. 163 """ 164 165 examplePath = chatListing('01') 166 167 def test_part01(self): 168 """ 169 Test method pointing to part 01 of the tutorial. 170 """ 171 return 'Nevow.Test.TestHowtoListing01' 172 173 174 175class RenderAndChat01(ExampleTestBase, TestCase): 176 """ 177 These tests make sure that the listing for part 01 of the chatterbox 178 tutorial will import, render, and allow users to chat with each other. 179 """ 180 181 examplePath = chatListing('01') 182 183 def test_basicRendering(self): 184 """ 185 Rendering the example element should produce a chat area. 186 """ 187 from chatthing.chatterbox import ChatterElement, ChatRoom 188 PROMPT = 'Choose your username: ' 189 cb = ChatterElement(ChatRoom()) 190 erlp = ElementRenderingLivePage(cb) 191 def checkContent(result): 192 # The "liveElement" renderer inserts this, let's look for it to 193 # make sure it rendered live: 194 self.assertIn('id="athena:'+str(cb._athenaID)+'"', result) 195 self.assertIn('athena:class="ChatThing.ChatterWidget"', result) 196 self.assertIn(PROMPT, result) 197 return renderLivePage(erlp).addCallback(checkContent) 198 199 200 def test_username(self): 201 """ 202 When a user sets their username in the chat room, it should set an 203 attribute on their ChatterElement instance. 204 """ 205 from chatthing.chatterbox import ChatterElement, ChatRoom 206 cb = ChatterElement(ChatRoom()) 207 setUsername = expose.get(cb, 'setUsername') 208 setUsername(u'jethro') 209 self.assertIdentical(u'jethro', cb.username) 210 211 212 def test_loginThenWall(self): 213 """ 214 When a user logs in the 'wall' method on their ChatterElement gets called, 215 notifiying everyone in the room that they have entered. 216 """ 217 from chatthing.chatterbox import ChatRoom 218 jethroHeard = [] 219 cletusHeard = [] 220 cr = ChatRoom() 221 user1 = cr.makeChatter() 222 user1.wall = lambda msg: jethroHeard.append(msg) 223 user1.setUsername(u'jethro') 224 user2 = cr.makeChatter() 225 user2.wall = lambda msg: cletusHeard.append(msg) 226 user2.setUsername(u'cletus') 227 self.assertEquals(jethroHeard, 228 [u' * user jethro has joined the room', 229 u' * user cletus has joined the room']) 230 self.assertEquals(cletusHeard, [u' * user cletus has joined the room']) 231 232 233 def test_sayThenHear(self): 234 """ 235 When a user calls the 'say' method on their ChatterElement, everyone (including 236 them) should 'hear' it. 237 """ 238 from chatthing.chatterbox import ChatRoom 239 cr = ChatRoom() 240 user1 = cr.makeChatter() 241 user1.wall = lambda msg: msg 242 user1.setUsername(u'jethro') 243 user2 = cr.makeChatter() 244 user2.wall = lambda msg: msg 245 user2.setUsername(u'cletus') 246 jethroHeard = [] 247 cletusHeard = [] 248 user1.hear = lambda who, what: jethroHeard.append((who,what)) 249 user2.hear = lambda who, what: cletusHeard.append((who,what)) 250 say = expose.get(user1, 'say') 251 say(u'Hey, Cletus!') 252 self.assertEquals(jethroHeard, cletusHeard) 253 self.assertEquals(cletusHeard, [(u'jethro', u'Hey, Cletus!')]) 254 255 256 def test_wallTellsClient(self): 257 """ 258 When 'wall' is called on a ChatterElement, it should inform its client of 259 the message. 260 """ 261 from chatthing.chatterbox import ChatRoom 262 cb = ChatRoom().makeChatter() 263 heard = [] 264 cb.callRemote = lambda method, msg: heard.append((method, msg)) 265 cb.wall(u'Message for everyone...') 266 self.assertEquals(heard, [('displayMessage', u'Message for everyone...')]) 267 268 def test_hearTellsClient(self): 269 """ 270 When 'hear' is called on a ChatterElement, it should inform its client of what 271 was said and by whom. 272 """ 273 from chatthing.chatterbox import ChatRoom 274 cb = ChatRoom().makeChatter() 275 heard = [] 276 cb.callRemote = lambda method, who, what: heard.append((method, who, what)) 277 cb.hear(u'Hello', u'Chat') 278 self.assertEquals(heard, [('displayUserMessage', u'Hello', u'Chat')]) 279 280