Repository: amwa-tv/nmos-testing
Issue: Test suite hangs if it can't connect to the specified API

A global fallback timeout for requests should be included in case of this sort of error condition.

Changed files:
IS0401Test.py, IS05Utils.py, Config.py, TestHelper.py, IS0501Test.py

diff --git a/Config.py b/Config.py
index 4f90754..de52fa6 100644
--- a/Config.py
+++ b/Config.py
@@ -31,6 +31,9 @@ QUERY_API_PORT = 80
 # Path to store the specification file cache in. Relative to the base of the testing repository.
 CACHE_PATH = 'cache'
 
+# Timeout for any HTTP requests
+HTTP_TIMEOUT = 1
+
 # Definition of each API specification and its versions.
 SPECIFICATIONS = {
     "is-04": {
diff --git a/IS0401Test.py b/IS0401Test.py
index f84dd2d..3503a49 100644
--- a/IS0401Test.py
+++ b/IS0401Test.py
@@ -14,7 +14,6 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-import requests
 import time
 import socket
 import netifaces
@@ -199,8 +198,8 @@ class IS0401Test(GenericTest):
             url = "http://" + QUERY_API_HOST + ":" + str(QUERY_API_PORT) + "/x-nmos/query/" + \\
                   self.apis[NODE_API_KEY]["version"] + "/" + res_type + "s/" + res_id
             try:
-                r = requests.get(url)
-                if r.status_code == 200:
+                valid, r = self.do_request("GET", url)
+                if valid and r.status_code == 200:
                     found_resource = r.json()
                 else:
                     raise Exception
@@ -223,31 +222,28 @@ class IS0401Test(GenericTest):
             url = "{}self".format(self.node_url)
         else:
             url = "{}{}s".format(self.node_url, res_type)
-        try:
-            # Get data from node itself
-            r = requests.get(url)
-            if r.status_code == 200:
-                try:
-                    node_resources = self.get_node_resources(r.json())
-
-                    if len(node_resources) == 0:
-                        return test.NA("No {} resources were found on the Node.".format(res_type.title()))
-
-                    for res_id in node_resources:
-                        reg_resource = self.get_registry_resource(res_type, res_id)
-                        if not reg_resource:
-                            return test.FAIL("{} {} was not found in the registry.".format(res_type.title(), res_id))
-                        elif reg_resource != node_resources[res_id]:
-                            return test.FAIL("Node API JSON does not match data in registry for "
-                                             "{} {}.".format(res_type.title(), res_id))
-
-                    return test.PASS()
-                except ValueError:
-                    return test.FAIL("Invalid JSON received!")
-            else:
-                return test.FAIL("Could not reach Node!")
-        except requests.ConnectionError:
-            return test.FAIL("Connection error for {}".format(url))
+        # Get data from node itself
+        valid, r = self.do_request("GET", url)
+        if valid and r.status_code == 200:
+            try:
+                node_resources = self.get_node_resources(r.json())
+
+                if len(node_resources) == 0:
+                    return test.NA("No {} resources were found on the Node.".format(res_type.title()))
+
+                for res_id in node_resources:
+                    reg_resource = self.get_registry_resource(res_type, res_id)
+                    if not reg_resource:
+                        return test.FAIL("{} {} was not found in the registry.".format(res_type.title(), res_id))
+                    elif reg_resource != node_resources[res_id]:
+                        return test.FAIL("Node API JSON does not match data in registry for "
+                                         "{} {}.".format(res_type.title(), res_id))
+
+                return test.PASS()
+            except ValueError:
+                return test.FAIL("Invalid JSON received!")
+        else:
+            return test.FAIL("Could not reach Node!")
 
     def test_04(self):
         """Node can register a valid Node resource with the network registration service,
diff --git a/IS0501Test.py b/IS0501Test.py
index b0f9995..415a36a 100644
--- a/IS0501Test.py
+++ b/IS0501Test.py
@@ -15,7 +15,6 @@
 # limitations under the License.
 
 
-import requests
 import uuid
 import os
 from jsonschema import ValidationError, SchemaError, RefResolver, Draft4Validator
@@ -825,15 +824,15 @@ class IS0501Test(GenericTest):
                 data.append(toAdd)
             else:
                 return False, response
-        try:
-            r = requests.post(url, json=data)
+        valid, r = self.do_request("POST", url, data)
+        if valid:
             msg = "Expected a 200 response from {}, got {}".format(url, r.status_code)
             if r.status_code == 200:
                 pass
             else:
                 return False, msg
-        except requests.exceptions.RequestException as e:
-            return False, str(e)
+        else:
+            return False, r
 
         schema = self.get_schema(CONN_API_KEY, "POST", "/bulk/" + port + "s", 200)
         try:
diff --git a/IS05Utils.py b/IS05Utils.py
index 03d3301..b045453 100644
--- a/IS05Utils.py
+++ b/IS05Utils.py
@@ -15,7 +15,6 @@
 # limitations under the License.
 
 import re
-import requests
 import time
 import TestHelper
 
@@ -558,42 +557,38 @@ class IS05Utils(NMOSUtils):
     def get_senders(self):
         """Gets a list of the available senders on the API"""
         toReturn = []
-        try:
-            r = requests.get(self.url + "single/senders/")
+        valid, r = TestHelper.do_request("GET", self.url + "single/senders/")
+        if valid:
             try:
                 for value in r.json():
                     toReturn.append(value[:-1])
             except ValueError:
                 pass
-        except requests.exceptions.RequestException:
-            pass
         return toReturn
 
     def get_receivers(self):
         """Gets a list of the available receivers on the API"""
         toReturn = []
-        try:
-            r = requests.get(self.url + "single/receivers/")
+        valid, r = TestHelper.do_request("GET", self.url + "single/receivers/")
+        if valid:
             try:
                 for value in r.json():
                     toReturn.append(value[:-1])
             except ValueError:
                 pass
-        except requests.exceptions.RequestException:
-            pass
         return toReturn
 
     def get_num_paths(self, port, portType):
         """Returns the number or redundant paths on a port"""
         url = self.url + "single/" + portType + "s/" + port + "/constraints/"
-        try:
-            r = requests.get(url)
+        valid, r = TestHelper.do_request("GET", url)
+        if valid:
             try:
                 rjson = r.json()
                 return len(rjson)
             except ValueError:
                 return 0
-        except requests.exceptions.RequestException:
+        else:
             return 0
 
     def park_resource(self, resource_type, resource_id):
diff --git a/TestHelper.py b/TestHelper.py
index 3ddafcb..01cc3b7 100644
--- a/TestHelper.py
+++ b/TestHelper.py
@@ -15,11 +15,13 @@
 # limitations under the License.
 
 
-import requests
-from copy import copy
 import threading
+from copy import copy
+import requests
 import websocket
 
+from Config import HTTP_TIMEOUT
+
 
 def ordered(obj):
     if isinstance(obj, dict):
@@ -45,7 +47,7 @@ def do_request(method, url, data=None):
         else:
             req = requests.Request(method, url)
         prepped = req.prepare()
-        r = s.send(prepped)
+        r = s.send(prepped, timeout=HTTP_TIMEOUT)
         return True, r
     except requests.exceptions.Timeout:
         return False, "Connection timeout"
@@ -56,6 +58,7 @@ def do_request(method, url, data=None):
     except requests.exceptions.RequestException as e:
         return False, str(e)
 
+
 class WebsocketWorker(threading.Thread):
     """Websocket Client Worker Thread"""