Add a helper function for tests to wait for event

A pattern used by many tests was to wait for a certain event happening
in the container and to stay in a time limit. This was implemented in
each test case using this pattern.

This change adds a helper function available to all tests, that takes
a function and a time limit. The function can return some output, which
is forwarded to the test and has to return a boolean, indicating whether
the event, for which the function tests has happened. The helper
function will execute the given function as long as this function returns
False or until the time limit is reached and will then return the
functions output and a boolean indicating, whether the function returned
True within the time limit.

Change-Id: I3505e0f25028a81f84dc7e5b8f006e0d6a904ab4
diff --git a/setup.cfg b/setup.cfg
new file mode 100644
index 0000000..1f82615
--- /dev/null
+++ b/setup.cfg
@@ -0,0 +1,2 @@
+[tool:pytest]
+norecursedirs=tests/helpers
diff --git a/tests/conftest.py b/tests/conftest.py
index 073414b..b6d4ff9 100644
--- a/tests/conftest.py
+++ b/tests/conftest.py
@@ -15,11 +15,14 @@
 # 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-master/test_container_integration_gerrit_master.py b/tests/container-images/gerrit-master/test_container_integration_gerrit_master.py
index 8ea25e0..7c0ea8c 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
@@ -1,4 +1,4 @@
-# pylint: disable=W0613
+# pylint: disable=W0613, E1101
 
 # Copyright (C) 2018 The Android Open Source Project
 #
@@ -16,11 +16,12 @@
 
 import os.path
 import re
-import time
 
 import pytest
 import requests
 
+import utils
+
 CONFIG_FILES = ["gerrit.config", "secure.config", "replication.config"]
 
 @pytest.fixture(scope="module")
@@ -78,13 +79,12 @@
 @pytest.mark.incremental
 class TestGerritMasterStartScript(object):
   def test_gerrit_master_gerrit_starts_up(self, container_run):
-    timeout = time.time() + 60
-    while time.time() < timeout:
-      last_log_line = container_run.logs().decode("utf-8")
-      if re.search(r"Gerrit Code Review .+ ready", last_log_line):
-        break
-      time.sleep(2)
-    assert timeout > time.time()
+    def wait_for_gerrit_start():
+      log = container_run.logs().decode("utf-8")
+      return log, 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
 
   def test_gerrit_master_custom_gerrit_config_available(
       self, container_run, config_file_to_test):
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 972205e..386b190 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
@@ -1,4 +1,4 @@
-# pylint: disable=W0613
+# pylint: disable=W0613, E1101
 
 # Copyright (C) 2018 The Android Open Source Project
 #
@@ -19,12 +19,13 @@
 import os
 import os.path
 import re
-import time
 
 import git
 import pytest
 import requests
 
+import utils
+
 CONFIG_FILES = ["gerrit.config", "secure.config"]
 
 class GerritSlaveTestSetup():
@@ -148,13 +149,12 @@
     return request.param
 
   def test_gerrit_slave_gerrit_starts_up(self, container_run_h2):
-    timeout = time.time() + 60
-    while time.time() < timeout:
-      last_log_line = container_run_h2.logs().decode("utf-8")
-      if re.search(r"Gerrit Code Review .+ ready", last_log_line):
-        break
-      time.sleep(2)
-    assert timeout > time.time()
+    def wait_for_gerrit_start():
+      log = container_run_h2.logs().decode("utf-8")
+      return log, 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
 
   def test_gerrit_slave_custom_gerrit_config_available(
       self, container_run_h2, config_file_to_test):
@@ -183,15 +183,16 @@
 
 @pytest.mark.slow
 def test_gerrit_slave_downloads_mysql_driver(container_run_mysql, tmp_dir):
-  timeout = time.time() + 20
-  while time.time() < timeout:
+
+  def wait_for_mysql_driver_download():
     _, output = container_run_mysql.exec_run(
       "find /var/gerrit/lib -name 'mysql-connector-java-*.jar'")
     output = output.decode("utf-8").strip()
-    if re.match(r"/var/gerrit/lib/mysql-connector-java-.*\.jar", output):
-      break
+    return output, re.match(r"/var/gerrit/lib/mysql-connector-java-.*\.jar", output)
 
-  assert timeout > time.time()
+  finished_in_time, _ = utils.exec_fn_with_timeout(
+    wait_for_mysql_driver_download, 20)
+  assert finished_in_time
 
   driver_path_pattern = os.path.join(tmp_dir, "lib", "mysql-connector-java-*.jar")
   lib_files = [f for f in glob(driver_path_pattern) if os.path.isfile(f)]
diff --git a/tests/helpers/utils.py b/tests/helpers/utils.py
new file mode 100644
index 0000000..20cdd52
--- /dev/null
+++ b/tests/helpers/utils.py
@@ -0,0 +1,40 @@
+# 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