From de027162768b095b20c6723bd6a8b1849a0389c0 Mon Sep 17 00:00:00 2001
From: Anton Joubert <ajoubert@ska.ac.za>
Date: Wed, 26 May 2021 13:28:59 +0200
Subject: [PATCH] [SAR-227] Fix intermittent DebugDevice test failures

The debugger-related tests were failing sometimes
with a `[Errno 98] Address already in use`.
This was because debugpy was configured to listen on
the same port, 5678, across multiple tests.  Sometimes
the OS wouldn't have released the port in time for
the next test.

Now use the default port for a single test, and
ephemeral ports for the rest.
---
 src/ska_tango_base/base/base_device.py | 13 ++++++++-----
 tests/test_base_device.py              | 11 ++++++++++-
 2 files changed, 18 insertions(+), 6 deletions(-)

diff --git a/src/ska_tango_base/base/base_device.py b/src/ska_tango_base/base/base_device.py
index 53b07a1b..809e1e90 100644
--- a/src/ska_tango_base/base/base_device.py
+++ b/src/ska_tango_base/base/base_device.py
@@ -326,6 +326,7 @@ class SKABaseDevice(Device):
     """
 
     _global_debugger_listening = False
+    _global_debugger_allocated_port = 0
 
     class InitCommand(ResponseCommand, CompletionCommand):
         """
@@ -1330,8 +1331,9 @@ class SKABaseDevice(Device):
             :rtype: DevUShort
             """
             if not SKABaseDevice._global_debugger_listening:
-                self.start_debugger(_DEBUGGER_PORT)
+                allocated_port = self.start_debugger_and_get_port(_DEBUGGER_PORT)
                 SKABaseDevice._global_debugger_listening = True
+                SKABaseDevice._global_debugger_allocated_port = allocated_port
             device = self.target
             if not device._methods_patched_for_debugger:
                 self.monkey_patch_all_methods_for_debugger()
@@ -1339,14 +1341,15 @@ class SKABaseDevice(Device):
             else:
                 self.logger.warning("Triggering debugger breakpoint...")
                 debugpy.breakpoint()
-            return _DEBUGGER_PORT
+            return SKABaseDevice._global_debugger_allocated_port
 
-        def start_debugger(self, port):
+        def start_debugger_and_get_port(self, port):
             self.logger.warning("Starting debugger...")
-            debugpy.listen(("0.0.0.0", port))
+            interface, allocated_port = debugpy.listen(("0.0.0.0", port))
             self.logger.warning(
-                f"Debugger listening on port {port}. Performance may be degraded."
+                f"Debugger listening on {interface}:{allocated_port}. Performance may be degraded."
             )
+            return allocated_port
 
         def monkey_patch_all_methods_for_debugger(self):
             all_methods = self.get_all_methods()
diff --git a/tests/test_base_device.py b/tests/test_base_device.py
index ef16b9f2..6ea95ef6 100644
--- a/tests/test_base_device.py
+++ b/tests/test_base_device.py
@@ -19,6 +19,9 @@ import tango
 
 from unittest import mock
 from tango import DevFailed, DevState
+
+import ska_tango_base.base.base_device
+
 from ska_tango_base import SKABaseDevice
 from ska_tango_base.base import OpStateModel, ReferenceBaseComponentManager
 from ska_tango_base.base.base_device import (
@@ -607,7 +610,7 @@ class TestSKABaseDevice(object):
             with pytest.raises(ConnectionRefusedError):
                 s.connect(("localhost", _DEBUGGER_PORT))
 
-    def test_DebugDevice_starts_listening(self, tango_context):
+    def test_DebugDevice_starts_listening_on_default_port(self, tango_context):
         port = tango_context.device.DebugDevice()
         assert port == _DEBUGGER_PORT
         assert SKABaseDevice._global_debugger_listening
@@ -616,17 +619,23 @@ class TestSKABaseDevice(object):
         assert tango_context.device.state
 
     def test_DebugDevice_twice_does_not_raise(self, tango_context):
+        patch_debugger_to_start_on_ephermal_port()
         tango_context.device.DebugDevice()
         tango_context.device.DebugDevice()
         assert SKABaseDevice._global_debugger_listening
 
     def test_DebugDevice_does_not_break_a_command(self, tango_context):
+        patch_debugger_to_start_on_ephermal_port()
         tango_context.device.DebugDevice()
         assert tango_context.device.State() == DevState.OFF
         tango_context.device.On()
         assert tango_context.device.State() == DevState.ON
 
 
+def patch_debugger_to_start_on_ephermal_port():
+    ska_tango_base.base.base_device._DEBUGGER_PORT = 0
+
+
 class TestSKABaseDevice_commands:
     """
     This class contains tests of SKABaseDevice commands
-- 
GitLab