ddns_test.py 6.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195
  1. # Copyright (C) 2011 Internet Systems Consortium.
  2. #
  3. # Permission to use, copy, modify, and distribute this software for any
  4. # purpose with or without fee is hereby granted, provided that the above
  5. # copyright notice and this permission notice appear in all copies.
  6. #
  7. # THE SOFTWARE IS PROVIDED "AS IS" AND INTERNET SYSTEMS CONSORTIUM
  8. # DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL
  9. # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL
  10. # INTERNET SYSTEMS CONSORTIUM BE LIABLE FOR ANY SPECIAL, DIRECT,
  11. # INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING
  12. # FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
  13. # NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
  14. # WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
  15. '''Tests for the DDNS module'''
  16. import unittest
  17. import isc
  18. import ddns
  19. import isc.config
  20. import select
  21. class FakeSocket:
  22. """
  23. A fake socket. It only provides a file number.
  24. """
  25. def __init__(self, fileno):
  26. self.__fileno = fileno
  27. def fileno(self):
  28. return self.__fileno
  29. class MyCCSession(isc.config.ConfigData):
  30. '''Fake session with minimal interface compliance'''
  31. def __init__(self):
  32. module_spec = isc.config.module_spec_from_file(
  33. ddns.SPECFILE_LOCATION)
  34. isc.config.ConfigData.__init__(self, module_spec)
  35. self._started = False
  36. def start(self):
  37. '''Called by DDNSServer initialization, but not used in tests'''
  38. self._started = True
  39. def get_socket(self):
  40. """
  41. Used to get the file number for select.
  42. """
  43. return FakeSocket(1)
  44. class MyDDNSServer():
  45. '''Fake DDNS server used to test the main() function'''
  46. def __init__(self):
  47. self.reset()
  48. def run(self):
  49. '''
  50. Fake the run() method of the DDNS server. This will set
  51. self._run_called to True.
  52. If self._exception is not None, this is raised as an exception
  53. '''
  54. self.run_called = True
  55. if self._exception is not None:
  56. self.exception_raised = True
  57. raise self._exception
  58. def set_exception(self, exception):
  59. '''Set an exception to be raised when run() is called'''
  60. self._exception = exception
  61. def reset(self):
  62. '''(Re)set to initial values'''
  63. self.run_called = False
  64. self.exception_raised = False
  65. self._exception = None
  66. class TestDDNSServer(unittest.TestCase):
  67. def setUp(self):
  68. cc_session = MyCCSession()
  69. self.assertFalse(cc_session._started)
  70. self.ddns_server = ddns.DDNSServer(cc_session)
  71. self.assertTrue(cc_session._started)
  72. self.__select_expected = None
  73. self.__select_answer = None
  74. self.__hook_called = False
  75. def test_config_handler(self):
  76. # Config handler does not do anything yet, but should at least
  77. # return 'ok' for now.
  78. new_config = {}
  79. answer = self.ddns_server.config_handler(new_config)
  80. self.assertEqual((0, None), isc.config.parse_answer(answer))
  81. def test_shutdown_command(self):
  82. '''Test whether the shutdown command works'''
  83. self.assertFalse(self.ddns_server._shutdown)
  84. answer = self.ddns_server.command_handler('shutdown', None)
  85. self.assertEqual((0, None), isc.config.parse_answer(answer))
  86. self.assertTrue(self.ddns_server._shutdown)
  87. def test_command_handler(self):
  88. '''Test some commands.'''
  89. # this command should not exist
  90. answer = self.ddns_server.command_handler('bad_command', None)
  91. self.assertEqual((1, 'Unknown command: bad_command'),
  92. isc.config.parse_answer(answer))
  93. def test_signal_handler(self):
  94. '''Test whether signal_handler calls shutdown()'''
  95. signal_handler = ddns.create_signal_handler(self.ddns_server)
  96. self.assertFalse(self.ddns_server._shutdown)
  97. signal_handler(None, None)
  98. self.assertTrue(self.ddns_server._shutdown)
  99. def __select(self, reads, writes, exceptions, timeout=None):
  100. """
  101. A fake select. It checks it was called with the correct parameters and
  102. returns a preset answer.
  103. """
  104. self.assertEqual(self.__select_expected, (reads, writes, exceptions,
  105. timeout))
  106. answer = self.__select_answer
  107. self.__select_answer = None
  108. self.ddns_server._shutdown = True
  109. return answer
  110. def __hook(self):
  111. """
  112. A hook that can be installed to any unary function and see if it was
  113. really called.
  114. """
  115. self.__hook_called = True
  116. def test_accept_called(self):
  117. """
  118. Test we call the accept function when a new connection comes.
  119. """
  120. self.ddns_server._listen_socket = FakeSocket(2)
  121. ddns.select.select = self.__select
  122. self.ddns_server.accept = self.__hook
  123. self.__select_expected = ([1, 2], [], [], None)
  124. self.__select_answer = ([2], [], [])
  125. self.ddns_server.run()
  126. self.assertTrue(self.ddns_server._shutdown)
  127. # The answer got used
  128. self.assertIsNone(self.__select_answer)
  129. self.assertTrue(self.__hook_called)
  130. ddns.select.select = select.select
  131. class TestMain(unittest.TestCase):
  132. def setUp(self):
  133. self._server = MyDDNSServer()
  134. def test_main(self):
  135. self.assertFalse(self._server.run_called)
  136. ddns.main(self._server)
  137. self.assertTrue(self._server.run_called)
  138. def check_exception(self, ex):
  139. '''Common test sequence to see if the given exception is caused.
  140. '''
  141. # Should technically not be necessary, but reset server to be sure
  142. self._server.reset()
  143. self.assertFalse(self._server.exception_raised)
  144. self._server.set_exception(ex)
  145. ddns.main(self._server)
  146. self.assertTrue(self._server.exception_raised)
  147. def test_exceptions(self):
  148. '''
  149. Test whether exceptions are caught in main()
  150. These exceptions should not bubble up.
  151. '''
  152. self._server.set_exception(KeyboardInterrupt())
  153. self.assertFalse(self._server.exception_raised)
  154. ddns.main(self._server)
  155. self.assertTrue(self._server.exception_raised)
  156. self.check_exception(isc.cc.SessionError("error"))
  157. self.check_exception(isc.config.ModuleCCSessionError("error"))
  158. self.check_exception(ddns.DDNSConfigError("error"))
  159. self.check_exception(isc.cc.SessionTimeout("error"))
  160. self.check_exception(Exception("error"))
  161. # Add one that is not a subclass of Exception, and hence not
  162. # caught. Misuse BaseException for that.
  163. self._server.reset()
  164. self.assertFalse(self._server.exception_raised)
  165. self._server.set_exception(BaseException("error"))
  166. self.assertRaises(BaseException, ddns.main, self._server)
  167. self.assertTrue(self._server.exception_raised)
  168. if __name__== "__main__":
  169. isc.log.resetUnitTestRootLogger()
  170. unittest.main()