Repository: opsdroid/opsdroid

Issue:
Add a SIGHUP handler to reload opsdroid
Many linux daemons respond to the [`SIGHUP`](https://en.wikipedia.org/wiki/Signal_(IPC)#SIGHUP) signal by reloading their configurations. Combined with opsdroid's `no_cache: true` this could be used to reload a skill (or other plugin) while developing it.

This should be added in the same manner as the [existing handlers](https://github.com/opsdroid/opsdroid/blob/master/opsdroid/core.py#L56-L60).


Add a SIGHUP handler to reload opsdroid
Many linux daemons respond to the [`SIGHUP`](https://en.wikipedia.org/wiki/Signal_(IPC)#SIGHUP) signal by reloading their configurations. Combined with opsdroid's `no_cache: true` this could be used to reload a skill (or other plugin) while developing it.

This should be added in the same manner as the [existing handlers](https://github.com/opsdroid/opsdroid/blob/master/opsdroid/core.py#L56-L60).




Discussion:
Hey @Cadair, I would like to work on this issue and fix it. Any starting steps you'd like to suggest? Thanks.
Hey @Cadair, I would like to work on this issue and fix it. Any starting steps you'd like to suggest? Thanks.

Resolving patch:
diff --git a/opsdroid/core.py b/opsdroid/core.py
--- a/opsdroid/core.py
+++ b/opsdroid/core.py
@@ -58,6 +58,9 @@ def __init__(self, config=None, config_path=None):
                 self.eventloop.add_signal_handler(
                     sig, lambda: asyncio.ensure_future(self.handle_signal())
                 )
+            self.eventloop.add_signal_handler(
+                signal.SIGHUP, lambda: asyncio.ensure_future(self.reload())
+            )
         self.eventloop.set_exception_handler(self.handle_async_exception)
         self.skills = []
         self.memory = Memory()


Test patch:
diff --git a/tests/test_core.py b/tests/test_core.py
--- a/tests/test_core.py
+++ b/tests/test_core.py
@@ -7,6 +7,8 @@
 import asynctest
 import asynctest.mock as amock
 import importlib
+import signal
+import threading
 import time
 
 from opsdroid.cli.start import configure_lang
@@ -82,6 +84,28 @@ def test_run(self):
             self.assertTrue(opsdroid.eventloop.run_until_complete.called)
             self.assertTrue(mock_sysexit.called)
 
+    def test_signals(self):
+        if os.name == "nt":
+            pytest.skip("SIGHUP unsupported on windows")
+        loop = asyncio.get_event_loop()
+
+        def real_test_signals(opsdroid):
+            asyncio.set_event_loop(loop)
+            opsdroid.load = amock.CoroutineMock()
+            opsdroid.unload = amock.CoroutineMock()
+            opsdroid.reload = amock.CoroutineMock()
+            threading.Timer(2, lambda: os.kill(os.getpid(), signal.SIGHUP)).start()
+            threading.Timer(3, lambda: os.kill(os.getpid(), signal.SIGINT)).start()
+            with mock.patch("sys.exit") as mock_sysexit:
+                opsdroid.run()
+            self.assertTrue(opsdroid.reload.called)
+            self.assertTrue(mock_sysexit.called)
+
+        with OpsDroid() as opsdroid:
+            thread = threading.Thread(target=real_test_signals, args=(opsdroid,))
+            thread.start()
+            thread.join()
+
     def test_run_cancelled(self):
         with OpsDroid() as opsdroid:
             opsdroid.is_running = amock.Mock(side_effect=[False, True, False])