Repository: exasol/integration-test-docker-environment (language: python)

Issue:
Bucket- and Database-forward might be the same
## Background

When assigning both ports. we call 2x find_free_port() [here](https://github.com/exasol/integration-test-docker-environment/blob/926079ec32fb8721073e0a33ede3bf57554ee55d/exasol_integration_test_docker_environment/testing/exaslct_test_environment.py#L120). It can happen that find_free_port() returns 2x the same port.

## Acceptance Criteria

Change find_free_port() to:
```
def find_free_port(num_ports:int)  -> List[int]
```



Discussion / hints:

Fix patch:
diff --git a/doc/changes/changes_0.8.0.md b/doc/changes/changes_0.8.0.md
index b94a913c5..8aa018859 100644
--- a/doc/changes/changes_0.8.0.md
+++ b/doc/changes/changes_0.8.0.md
@@ -22,6 +22,7 @@ If you need further versions, please open an issue.
 ## Bug Fixes:
 
  - #148: Fix broken Github actions
+ - #152: Fix bug where Bucket- and Database-forward might be the same 
 
 ## Features / Enhancements:
 


Test patch:
diff --git a/exasol_integration_test_docker_environment/test/test_find_free_port.py b/exasol_integration_test_docker_environment/test/test_find_free_port.py
new file mode 100644
index 000000000..4b5f6671a
--- /dev/null
+++ b/exasol_integration_test_docker_environment/test/test_find_free_port.py
@@ -0,0 +1,23 @@
+import unittest
+
+from exasol_integration_test_docker_environment.testing.utils import find_free_ports
+
+
+class FindFreePortTest(unittest.TestCase):
+
+    def run_it(self, num_ports: int):
+        ports = find_free_ports(num_ports)
+        self.assertNotIn(0, ports)
+        self.assertEqual(len(ports), num_ports)
+        #Check that there are no duplicates!
+        ports_set = set(ports)
+        self.assertEqual(len(ports), len(ports_set))
+
+    def test_find_ports(self):
+        num_ports = [1, 2, 100, 1000]
+        for num_port in num_ports:
+            self.run_it(num_port)
+
+
+if __name__ == '__main__':
+    unittest.main()
diff --git a/exasol_integration_test_docker_environment/testing/exaslct_test_environment.py b/exasol_integration_test_docker_environment/testing/exaslct_test_environment.py
index 18cf4b2ef..ddcbb3b38 100644
--- a/exasol_integration_test_docker_environment/testing/exaslct_test_environment.py
+++ b/exasol_integration_test_docker_environment/testing/exaslct_test_environment.py
@@ -13,7 +13,7 @@
 from exasol_integration_test_docker_environment.testing.exaslct_docker_test_environment import \
     ExaslctDockerTestEnvironment
 from exasol_integration_test_docker_environment.testing.spawned_test_environments import SpawnedTestEnvironments
-from exasol_integration_test_docker_environment.testing.utils import find_free_port, check_db_version_from_env
+from exasol_integration_test_docker_environment.testing.utils import find_free_ports, check_db_version_from_env
 
 
 class ExaslctTestEnvironment:
@@ -110,6 +110,7 @@ def close(self):
 
     def spawn_docker_test_environments(self, name: str, additional_parameter: List[str] = None) \
             -> SpawnedTestEnvironments:
+        database_port, bucketfs_port = find_free_ports(2)
         on_host_parameter = ExaslctDockerTestEnvironment(
             name=self.name + "_" + name,
             database_host="localhost",
@@ -117,8 +118,8 @@ def spawn_docker_test_environments(self, name: str, additional_parameter: List[s
             db_password="exasol",
             bucketfs_username="w",
             bucketfs_password="write",
-            database_port=find_free_port(),
-            bucketfs_port=find_free_port())
+            database_port=database_port,
+            bucketfs_port=bucketfs_port)
         docker_db_version_parameter = ""
         db_version_from_env = check_db_version_from_env()
         if db_version_from_env is not None:
@@ -165,7 +166,7 @@ def spawn_docker_test_environments(self, name: str, additional_parameter: List[s
             return SpawnedTestEnvironments(on_host_parameter, None)
 
     def create_registry(self):
-        registry_port = find_free_port()
+        registry_port = find_free_ports(1)[0]
         registry_container_name = self.name.replace("/", "_") + "_registry"
         with ContextDockerClient() as docker_client:
             print("Start pull of registry:2")
diff --git a/exasol_integration_test_docker_environment/testing/utils.py b/exasol_integration_test_docker_environment/testing/utils.py
index f517f6daa..c1fe7f617 100644
--- a/exasol_integration_test_docker_environment/testing/utils.py
+++ b/exasol_integration_test_docker_environment/testing/utils.py
@@ -1,8 +1,8 @@
 import json
 import os
 import socket
-from contextlib import closing
-from typing import Optional
+from contextlib import ExitStack
+from typing import Optional, List
 
 import requests
 
@@ -11,15 +11,24 @@
 INTEGRATION_TEST_DOCKER_ENVIRONMENT_DEFAULT_BIN = "./start-test-env-without-poetry"
 
 
-def find_free_port():
-    with closing(socket.socket(socket.AF_INET, socket.SOCK_STREAM)) as s:
-        s.bind(('', 0))
-        s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
-        port = s.getsockname()[1]
-    with closing(socket.socket(socket.AF_INET, socket.SOCK_STREAM)) as s:
-        s.bind(('', port))
-        s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
-    return port
+def find_free_ports(num_ports: int) -> List[int]:
+
+    ret_val = list()
+    with ExitStack() as stack:
+        sockets = [stack.enter_context(socket.socket(socket.AF_INET, socket.SOCK_STREAM)) for dummy in range(num_ports)]
+        for s in sockets:
+            s.bind(('', 0))
+            s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
+            ret_val.append(s.getsockname()[1])
+    with ExitStack() as stack:
+        # Create an array of tuples of new socket + port to use
+        sockets = [(stack.enter_context(socket.socket(socket.AF_INET, socket.SOCK_STREAM)), port) for port in ret_val]
+        for socket_port in sockets:
+            s = socket_port[0]
+            port = socket_port[1]
+            s.bind(('', port))
+            s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
+    return ret_val
 
 
 def remove_docker_container(containers):