test_controller.py 6.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203
  1. # Copyright (c) 2017–2018 crocoite contributors
  2. #
  3. # Permission is hereby granted, free of charge, to any person obtaining a copy
  4. # of this software and associated documentation files (the "Software"), to deal
  5. # in the Software without restriction, including without limitation the rights
  6. # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  7. # copies of the Software, and to permit persons to whom the Software is
  8. # furnished to do so, subject to the following conditions:
  9. #
  10. # The above copyright notice and this permission notice shall be included in
  11. # all copies or substantial portions of the Software.
  12. #
  13. # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  14. # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  15. # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  16. # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  17. # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  18. # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  19. # THE SOFTWARE.
  20. import asyncio
  21. from yarl import URL
  22. from aiohttp import web
  23. import pytest
  24. from .logger import Logger
  25. from .controller import ControllerSettings, SinglePageController, SetEntry, \
  26. IdleStateTracker
  27. from .browser import PageIdle
  28. from .devtools import Process
  29. from .test_browser import loader
  30. @pytest.mark.asyncio
  31. async def test_controller_timeout ():
  32. """ Make sure the controller terminates, even if the site keeps reloading/fetching stuff """
  33. async def f (req):
  34. return web.Response (body="""<html>
  35. <body>
  36. <p>hello</p>
  37. <script>
  38. window.setTimeout (function () { window.location = '/' }, 250);
  39. window.setInterval (function () { fetch('/').then (function (e) { console.log (e) }) }, 150);
  40. </script>
  41. </body>
  42. </html>""", status=200, content_type='text/html', charset='utf-8')
  43. url = URL.build (scheme='http', host='localhost', port=8080)
  44. app = web.Application ()
  45. app.router.add_route ('GET', '/', f)
  46. runner = web.AppRunner(app)
  47. await runner.setup()
  48. site = web.TCPSite(runner, url.host, url.port)
  49. await site.start()
  50. loop = asyncio.get_event_loop ()
  51. try:
  52. logger = Logger ()
  53. settings = ControllerSettings (idleTimeout=1, timeout=5)
  54. controller = SinglePageController (url=url, logger=logger,
  55. service=Process (), behavior=[], settings=settings)
  56. # give the controller a little more time to finish, since there are
  57. # hard-coded asyncio.sleep calls in there right now.
  58. # XXX fix this
  59. before = loop.time ()
  60. await asyncio.wait_for (controller.run (), timeout=settings.timeout*2)
  61. after = loop.time ()
  62. assert after-before >= settings.timeout, (settings.timeout*2, after-before)
  63. finally:
  64. # give the browser some time to close before interrupting the
  65. # connection by destroying the HTTP server
  66. await asyncio.sleep (1)
  67. await runner.cleanup ()
  68. @pytest.mark.asyncio
  69. async def test_controller_idle_timeout ():
  70. """ Make sure the controller terminates, even if the site keeps reloading/fetching stuff """
  71. async def f (req):
  72. return web.Response (body="""<html>
  73. <body>
  74. <p>hello</p>
  75. <script>
  76. window.setInterval (function () { fetch('/').then (function (e) { console.log (e) }) }, 2000);
  77. </script>
  78. </body>
  79. </html>""", status=200, content_type='text/html', charset='utf-8')
  80. url = URL.build (scheme='http', host='localhost', port=8080)
  81. app = web.Application ()
  82. app.router.add_route ('GET', '/', f)
  83. runner = web.AppRunner(app)
  84. await runner.setup()
  85. site = web.TCPSite(runner, url.host, url.port)
  86. await site.start()
  87. loop = asyncio.get_event_loop ()
  88. try:
  89. logger = Logger ()
  90. settings = ControllerSettings (idleTimeout=1, timeout=60)
  91. controller = SinglePageController (url=url, logger=logger,
  92. service=Process (), behavior=[], settings=settings)
  93. before = loop.time ()
  94. await asyncio.wait_for (controller.run (), settings.timeout*2)
  95. after = loop.time ()
  96. assert settings.idleTimeout <= after-before <= settings.idleTimeout*2+3
  97. finally:
  98. await runner.cleanup ()
  99. def test_set_entry ():
  100. a = SetEntry (1, a=2, b=3)
  101. assert a == a
  102. assert hash (a) == hash (a)
  103. b = SetEntry (1, a=2, b=4)
  104. assert a == b
  105. assert hash (a) == hash (b)
  106. c = SetEntry (2, a=2, b=3)
  107. assert a != c
  108. assert hash (a) != hash (c)
  109. @pytest.mark.asyncio
  110. async def test_idle_state_tracker ():
  111. # default is idle
  112. loop = asyncio.get_event_loop ()
  113. idle = IdleStateTracker (loop)
  114. assert idle._idle
  115. # idle change
  116. await idle.push (PageIdle (False))
  117. assert not idle._idle
  118. # nothing happens for other objects
  119. await idle.push ({})
  120. assert not idle._idle
  121. # no state change -> wait does not return
  122. with pytest.raises (asyncio.TimeoutError):
  123. await asyncio.wait_for (idle.wait (0.1), timeout=1)
  124. # wait at least timeout
  125. delta = 0.2
  126. timeout = 1
  127. await idle.push (PageIdle (True))
  128. assert idle._idle
  129. start = loop.time ()
  130. await idle.wait (timeout)
  131. end = loop.time ()
  132. assert (timeout-delta) < (end-start) < (timeout+delta)
  133. @pytest.fixture
  134. async def recordingServer ():
  135. """ Simple HTTP server that records raw requests """
  136. url = URL ('http://localhost:8080')
  137. reqs = []
  138. async def record (request):
  139. reqs.append (request)
  140. return web.Response(text='ok', content_type='text/plain')
  141. app = web.Application()
  142. app.add_routes([web.get(url.path, record)])
  143. runner = web.AppRunner(app)
  144. await runner.setup()
  145. site = web.TCPSite (runner, url.host, url.port)
  146. await site.start()
  147. yield url, reqs
  148. await runner.cleanup ()
  149. from .test_devtools import tab, browser
  150. from http.cookies import Morsel, SimpleCookie
  151. @pytest.mark.asyncio
  152. async def test_set_cookies (tab, recordingServer):
  153. """ Make sure cookies are set properly and only affect the domain they were
  154. set for """
  155. logger = Logger ()
  156. url, reqs = recordingServer
  157. cookies = []
  158. c = Morsel ()
  159. c.set ('foo', 'bar', '')
  160. c['domain'] = 'localhost'
  161. cookies.append (c)
  162. c = Morsel ()
  163. c.set ('buz', 'beef', '')
  164. c['domain'] = 'nonexistent.example'
  165. settings = ControllerSettings (idleTimeout=1, timeout=60, cookies=cookies)
  166. controller = SinglePageController (url=url, logger=logger,
  167. service=Process (), behavior=[], settings=settings)
  168. await asyncio.wait_for (controller.run (), settings.timeout*2)
  169. assert len (reqs) == 1
  170. req = reqs[0]
  171. reqCookies = SimpleCookie (req.headers['cookie'])
  172. assert len (reqCookies) == 1
  173. c = next (iter (reqCookies.values ()))
  174. assert c.key == cookies[0].key
  175. assert c.value == cookies[0].value