Use pytest-timeout plugin for tests waiting for an event

So far a custom helper function was used to test, whether an event
happened within a time limit.

Now the pytest-timeout plugin is used to limit the time a test can run
before failing. This makes it easier to implement a time limit.

Change-Id: I189c8f32296cf293617e5f1b99dd7cc3329e294b
diff --git a/Pipfile b/Pipfile
index 646dc0c..ce95123 100644
--- a/Pipfile
+++ b/Pipfile
@@ -14,6 +14,7 @@
 passlib = "~=1.7.1"
 pyopenssl = "~=18.0.0"
 requests = "~=2.21.0"
+pytest-timeout = "~=1.3.3"
 
 [requires]
 python_version = "3.7"
diff --git a/Pipfile.lock b/Pipfile.lock
index 355fea9..9d7a564 100644
--- a/Pipfile.lock
+++ b/Pipfile.lock
@@ -1,7 +1,7 @@
 {
     "_meta": {
         "hash": {
-            "sha256": "abb246e4d04aaa73d1ac30c1d15db235accf031381d8439b4edaefa74e15bb52"
+            "sha256": "5b2cf04682c1f1aba0992c3674b336ae743a64ea96ef515a4661db5d182e8cf5"
         },
         "pipfile-spec": 6,
         "requires": {
@@ -129,11 +129,11 @@
         },
         "gitpython": {
             "hashes": [
-                "sha256:c15c55ff890cd3a6a8330059e80885410a328f645551b55a91d858bfb3eb2573",
-                "sha256:df752b6b6f06f11213e91c4925aea7eaf9e37e88fb71c8a7a1aa0a5c10852120"
+                "sha256:17815b908454e49604e86ffb0e4d981c463d009b54ab30ead7f6ad8ad3a8cffb",
+                "sha256:392f31eaadc19db35a54e3ab7285577fb4a86d96ecee08cf22a573f06633baab"
             ],
             "index": "pypi",
-            "version": "==2.1.13"
+            "version": "==2.1.14"
         },
         "idna": {
             "hashes": [
@@ -144,10 +144,10 @@
         },
         "importlib-metadata": {
             "hashes": [
-                "sha256:23d3d873e008a513952355379d93cbcab874c58f4f034ff657c7a87422fa64e8",
-                "sha256:80d2de76188eabfbfcf27e6a37342c2827801e59c4cc14b0371c56fed43820e3"
+                "sha256:9ff1b1c5a354142de080b8a4e9803e5d0d59283c93aed808617c787d16768375",
+                "sha256:b7143592e374e50584564794fcb8aaf00a23025f9db866627f89a21491847a8d"
             ],
-            "version": "==0.19"
+            "version": "==0.20"
         },
         "more-itertools": {
             "hashes": [
@@ -159,10 +159,10 @@
         },
         "packaging": {
             "hashes": [
-                "sha256:0c98a5d0be38ed775798ece1b9727178c4469d9c3b4ada66e8e6b7849f8732af",
-                "sha256:9e1cbf8c12b1f1ce0bb5344b8d7ecf66a6f8a6e91bcb0c84593ed6d3ab5c4ab3"
+                "sha256:a7ac867b97fdc07ee80a8058fe4435ccd274ecc3b0ed61d852d7d53055528cf9",
+                "sha256:c491ca87294da7cc01902edbe30a5bc6c4c28172b5138ab4e4aa1b9d7bfaeafe"
             ],
-            "version": "==19.0"
+            "version": "==19.1"
         },
         "passlib": {
             "hashes": [
@@ -202,18 +202,26 @@
         },
         "pyparsing": {
             "hashes": [
-                "sha256:09b60ec311af02f93c66ab2cb65e3ca92c5cb5de5445894982ce9f0928b116f5",
-                "sha256:38181feb3a1278f464b7e1d0d647760f7ab08d93f654f886ff528946450adbab"
+                "sha256:6f98a7b9397e206d78cc01df10131398f1c8b8510a2f4d97d9abd82e1aacdd80",
+                "sha256:d9338df12903bbf5d65a0e4e87c2161968b10d2e489652bb47001d82a9b028b4"
             ],
-            "version": "==2.4.2a1"
+            "version": "==2.4.2"
         },
         "pytest": {
             "hashes": [
-                "sha256:6aa9bc2f6f6504d7949e9df2a756739ca06e58ffda19b5e53c725f7b03fb4aae",
-                "sha256:b77ae6f2d1a760760902a7676887b665c086f71e3461c64ed2a312afcedc00d6"
+                "sha256:8fc39199bdda3d9d025d3b1f4eb99a192c20828030ea7c9a0d2840721de7d347",
+                "sha256:d100a02770f665f5dcf7e3f08202db29857fee6d15f34c942be0a511f39814f0"
             ],
             "index": "pypi",
-            "version": "==4.6.4"
+            "version": "==4.6.5"
+        },
+        "pytest-timeout": {
+            "hashes": [
+                "sha256:4a30ba76837a32c7b7cd5c84ee9933fde4b9022b0cd20ea7d4a577c2a1649fb1",
+                "sha256:d49f618c6448c14168773b6cdda022764c63ea80d42274e3156787e8088d04c6"
+            ],
+            "index": "pypi",
+            "version": "==1.3.3"
         },
         "requests": {
             "hashes": [
@@ -260,10 +268,10 @@
         },
         "zipp": {
             "hashes": [
-                "sha256:4970c3758f4e89a7857a973b1e2a5d75bcdc47794442f2e2dd4fe8e0466e809a",
-                "sha256:8a5712cfd3bb4248015eb3b0b3c54a5f6ee3f2425963ef2a0125b8bc40aafaec"
+                "sha256:3718b1cbcd963c7d4c5511a8240812904164b7f381b647143a89d3b98f9bcd8e",
+                "sha256:f06903e9f1f43b12d371004b4ac7b06ab39a44adc747266928ae6debfa7b3335"
             ],
-            "version": "==0.5.2"
+            "version": "==0.6.0"
         }
     },
     "develop": {
@@ -312,26 +320,26 @@
         },
         "lazy-object-proxy": {
             "hashes": [
-                "sha256:159a745e61422217881c4de71f9eafd9d703b93af95618635849fe469a283661",
-                "sha256:23f63c0821cc96a23332e45dfaa83266feff8adc72b9bcaef86c202af765244f",
-                "sha256:3b11be575475db2e8a6e11215f5aa95b9ec14de658628776e10d96fa0b4dac13",
-                "sha256:3f447aff8bc61ca8b42b73304f6a44fa0d915487de144652816f950a3f1ab821",
-                "sha256:4ba73f6089cd9b9478bc0a4fa807b47dbdb8fad1d8f31a0f0a5dbf26a4527a71",
-                "sha256:4f53eadd9932055eac465bd3ca1bd610e4d7141e1278012bd1f28646aebc1d0e",
-                "sha256:64483bd7154580158ea90de5b8e5e6fc29a16a9b4db24f10193f0c1ae3f9d1ea",
-                "sha256:6f72d42b0d04bfee2397aa1862262654b56922c20a9bb66bb76b6f0e5e4f9229",
-                "sha256:7c7f1ec07b227bdc561299fa2328e85000f90179a2f44ea30579d38e037cb3d4",
-                "sha256:7c8b1ba1e15c10b13cad4171cfa77f5bb5ec2580abc5a353907780805ebe158e",
-                "sha256:8559b94b823f85342e10d3d9ca4ba5478168e1ac5658a8a2f18c991ba9c52c20",
-                "sha256:a262c7dfb046f00e12a2bdd1bafaed2408114a89ac414b0af8755c696eb3fc16",
-                "sha256:acce4e3267610c4fdb6632b3886fe3f2f7dd641158a843cf6b6a68e4ce81477b",
-                "sha256:be089bb6b83fac7f29d357b2dc4cf2b8eb8d98fe9d9ff89f9ea6012970a853c7",
-                "sha256:bfab710d859c779f273cc48fb86af38d6e9210f38287df0069a63e40b45a2f5c",
-                "sha256:c10d29019927301d524a22ced72706380de7cfc50f767217485a912b4c8bd82a",
-                "sha256:dd6e2b598849b3d7aee2295ac765a578879830fb8966f70be8cd472e6069932e",
-                "sha256:e408f1eacc0a68fed0c08da45f31d0ebb38079f043328dce69ff133b95c29dc1"
+                "sha256:02b260c8deb80db09325b99edf62ae344ce9bc64d68b7a634410b8e9a568edbf",
+                "sha256:18f9c401083a4ba6e162355873f906315332ea7035803d0fd8166051e3d402e3",
+                "sha256:1f2c6209a8917c525c1e2b55a716135ca4658a3042b5122d4e3413a4030c26ce",
+                "sha256:2f06d97f0ca0f414f6b707c974aaf8829c2292c1c497642f63824119d770226f",
+                "sha256:616c94f8176808f4018b39f9638080ed86f96b55370b5a9463b2ee5c926f6c5f",
+                "sha256:63b91e30ef47ef68a30f0c3c278fbfe9822319c15f34b7538a829515b84ca2a0",
+                "sha256:77b454f03860b844f758c5d5c6e5f18d27de899a3db367f4af06bec2e6013a8e",
+                "sha256:83fe27ba321e4cfac466178606147d3c0aa18e8087507caec78ed5a966a64905",
+                "sha256:84742532d39f72df959d237912344d8a1764c2d03fe58beba96a87bfa11a76d8",
+                "sha256:874ebf3caaf55a020aeb08acead813baf5a305927a71ce88c9377970fe7ad3c2",
+                "sha256:9f5caf2c7436d44f3cec97c2fa7791f8a675170badbfa86e1992ca1b84c37009",
+                "sha256:a0c8758d01fcdfe7ae8e4b4017b13552efa7f1197dd7358dc9da0576f9d0328a",
+                "sha256:a4def978d9d28cda2d960c279318d46b327632686d82b4917516c36d4c274512",
+                "sha256:ad4f4be843dace866af5fc142509e9b9817ca0c59342fdb176ab6ad552c927f5",
+                "sha256:ae33dd198f772f714420c5ab698ff05ff900150486c648d29951e9c70694338e",
+                "sha256:b4a2b782b8a8c5522ad35c93e04d60e2ba7f7dcb9271ec8e8c3e08239be6c7b4",
+                "sha256:c462eb33f6abca3b34cdedbe84d761f31a60b814e173b98ede3c81bb48967c4f",
+                "sha256:fd135b8d35dfdcdb984828c84d695937e58cc5f49e1c854eb311c4d6aa03f4f1"
             ],
-            "version": "==1.4.1"
+            "version": "==1.4.2"
         },
         "mccabe": {
             "hashes": [
diff --git a/tests/conftest.py b/tests/conftest.py
index 44de092..e0f9363 100644
--- a/tests/conftest.py
+++ b/tests/conftest.py
@@ -15,14 +15,11 @@
 # limitations under the License.
 
 import os
-import sys
 
 import docker
 import git
 import pytest
 
-sys.path.append(os.path.join(os.path.dirname(__file__), "helpers"))
-
 # Base images that are not published and thus only tagged with "latest"
 BASE_IMGS = ["base", "gerrit-base"]
 DOCKER_ORG = "k8sgerrit"
diff --git a/tests/container-images/gerrit-init/test_container_integration_gerrit_init.py b/tests/container-images/gerrit-init/test_container_integration_gerrit_init.py
index 370ed49..6118c23 100644
--- a/tests/container-images/gerrit-init/test_container_integration_gerrit_init.py
+++ b/tests/container-images/gerrit-init/test_container_integration_gerrit_init.py
@@ -20,8 +20,6 @@
 
 import pytest
 
-import utils
-
 
 @pytest.fixture(scope="class")
 def container_run_default(request, docker_client, gerrit_init_image, tmp_path_factory):
@@ -64,24 +62,27 @@
 
 @pytest.mark.incremental
 class TestGerritInitEmptySite:
+    @pytest.mark.timeout(60)
     def test_gerrit_init_gerrit_is_initialized(self, container_run_default):
         def wait_for_init_success_message():
             log = container_run_default.logs().decode("utf-8")
             return log, re.search(r"Initialized /var/gerrit", log)
 
-        finished_in_time, _ = utils.exec_fn_with_timeout(wait_for_init_success_message)
-        assert finished_in_time
+        while not wait_for_init_success_message():
+            continue
 
+    @pytest.mark.timeout(60)
     def test_gerrit_init_exits_after_init(self, container_run_default):
         def wait_for_container_exit():
             try:
                 container_run_default.reload()
-                return None, False
+                return False
             except NotFound:
-                return None, True
+                return True
 
-        finished_in_time, _ = utils.exec_fn_with_timeout(wait_for_container_exit)
-        assert finished_in_time
+        while not wait_for_container_exit():
+            continue
+
         assert container_run_default.attrs["State"]["ExitCode"] == 0
 
 
diff --git a/tests/container-images/gerrit-master/test_container_integration_gerrit_master.py b/tests/container-images/gerrit-master/test_container_integration_gerrit_master.py
index 61791db..30d965b 100644
--- a/tests/container-images/gerrit-master/test_container_integration_gerrit_master.py
+++ b/tests/container-images/gerrit-master/test_container_integration_gerrit_master.py
@@ -16,12 +16,11 @@
 
 import os.path
 import re
+import time
 
 import pytest
 import requests
 
-import utils
-
 CONFIG_FILES = ["gerrit.config", "secure.config", "replication.config"]
 
 
@@ -79,13 +78,14 @@
 @pytest.mark.slow
 @pytest.mark.incremental
 class TestGerritMasterStartScript:
+    @pytest.mark.timeout(60)
     def test_gerrit_master_gerrit_starts_up(self, container_run):
         def wait_for_gerrit_start():
             log = container_run.logs().decode("utf-8")
-            return log, re.search(r"Gerrit Code Review .+ ready", log)
+            return re.search(r"Gerrit Code Review .+ ready", log)
 
-        finished_in_time, _ = utils.exec_fn_with_timeout(wait_for_gerrit_start, 60)
-        assert finished_in_time
+        while not wait_for_gerrit_start:
+            continue
 
     def test_gerrit_master_custom_gerrit_config_available(
         self, container_run, config_file_to_test
@@ -98,7 +98,15 @@
         assert exit_code == 0
         assert output == "True"
 
+    @pytest.mark.timeout(60)
     def test_gerrit_master_httpd_is_responding(self, container_run):
-        response = requests.get("http://localhost:8081")
+        status = None
+        while not status == 200:
+            try:
+                response = requests.get("http://localhost:8081")
+                status = response.status_code
+            except requests.exceptions.ConnectionError:
+                time.sleep(1)
+
         assert response.status_code == 200
         assert re.search(r'content="Gerrit Code Review"', response.text)
diff --git a/tests/container-images/gerrit-slave/test_container_integration_gerrit_slave.py b/tests/container-images/gerrit-slave/test_container_integration_gerrit_slave.py
index 86132d1..ff6ac92 100644
--- a/tests/container-images/gerrit-slave/test_container_integration_gerrit_slave.py
+++ b/tests/container-images/gerrit-slave/test_container_integration_gerrit_slave.py
@@ -22,8 +22,6 @@
 import pytest
 import requests
 
-import utils
-
 CONFIG_FILES = ["gerrit.config", "secure.config"]
 
 
@@ -82,13 +80,14 @@
     def expected_repository(self, request):
         return request.param
 
+    @pytest.mark.timeout(60)
     def test_gerrit_slave_gerrit_starts_up(self, container_run):
         def wait_for_gerrit_start():
             log = container_run.logs().decode("utf-8")
-            return log, re.search(r"Gerrit Code Review .+ ready", log)
+            return re.search(r"Gerrit Code Review .+ ready", log)
 
-        finished_in_time, _ = utils.exec_fn_with_timeout(wait_for_gerrit_start, 60)
-        assert finished_in_time
+        while not wait_for_gerrit_start():
+            continue
 
     def test_gerrit_slave_custom_gerrit_config_available(
         self, container_run, config_file_to_test
diff --git a/tests/helpers/utils.py b/tests/helpers/utils.py
deleted file mode 100644
index 0adbe9e..0000000
--- a/tests/helpers/utils.py
+++ /dev/null
@@ -1,41 +0,0 @@
-# Copyright (C) 2018 The Android Open Source Project
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-import time
-
-
-def exec_fn_with_timeout(func, limit=60):
-    """Helper function that executes a given function until it returns True or a
-     given time limit is reached.
-
-  Arguments:
-    func {function} -- Function to execute. The function can return some output
-                    (or None) and as a second return value a boolean indicating,
-                    whether the event the function was waiting for has happened.
-
-  Keyword Arguments:
-    limit {int} -- Maximum time in seconds to wait for a positive response of
-                   the function (default: {60})
-
-  Returns:
-    boolean -- False, if the timeout was reached
-    any -- Last output of fn
-  """
-
-    timeout = time.time() + limit
-    while time.time() < timeout:
-        output, is_finished = func()
-        if is_finished:
-            return True, output
-    return False, output