diff --git a/nmostesting/Config.py b/nmostesting/Config.py index 78704aec..3f5e3fed 100644 --- a/nmostesting/Config.py +++ b/nmostesting/Config.py @@ -190,8 +190,18 @@ # Bash shell to use for running testssl.sh TEST_SSL_BASH = "bash" +# IP and port of IS-11 REFERENCE SENDER to test IS-11 Receivers under live streaming conditions. +# The URL of a device API is required to access the IS-04 Node (http://{device}/x-nmos/node/v1.2), +# IS-05 Connection (http://{device}/x-nmos/connection/v1.1) and +# IS-11 Stream Compatibility (http://{device}/x-nmos/streamcompatibility/v1.0) +IS11_REFERENCE_SENDER_CONNECTION_API = "" +IS11_REFERENCE_SENDER_CONNECTION_API_PORT = "" +IS11_REFERENCE_SENDER_NODE_API = "" +IS11_REFERENCE_SENDER_NODE_API_PORT = "" + # Stability delay for any request on IS-11 STABLE_STATE_DELAY = 3 + # Definition of each API specification and its versions. SPECIFICATIONS = { "is-04": { diff --git a/nmostesting/IS11Utils.py b/nmostesting/IS11Utils.py index 7fec2e61..5c6807c8 100644 --- a/nmostesting/IS11Utils.py +++ b/nmostesting/IS11Utils.py @@ -12,36 +12,60 @@ # See the License for the specific language governing permissions and # limitations under the License. +from enum import Enum + from . import TestHelper from .NMOSUtils import NMOSUtils +SND_RCV_SUBSET = Enum('SndRcvSubset', ['ALL', 'WITH_I_O', 'WITHOUT_I_O']) + class IS11Utils(NMOSUtils): def __init__(self, url): NMOSUtils.__init__(self, url) # TODO: Remove the duplication (IS05Utils) - def get_senders(self): + def get_senders(self, filter=SND_RCV_SUBSET.ALL): """Gets a list of the available senders on the API""" toReturn = [] valid, r = TestHelper.do_request("GET", self.url + "senders/") if valid and r.status_code == 200: try: for value in r.json(): - toReturn.append(value[:-1]) + if filter == SND_RCV_SUBSET.ALL: + toReturn.append(value[:-1]) + else: + valid_io, r_io = TestHelper.do_request("GET", self.url + "senders/" + value + "inputs/") + if valid_io and r_io.status_code == 200: + try: + if len(r_io.json()) > 0 and filter == SND_RCV_SUBSET.WITH_I_O or \ + len(r_io.json()) == 0 and filter == SND_RCV_SUBSET.WITHOUT_I_O: + toReturn.append(value[:-1]) + except ValueError: + pass except ValueError: pass return toReturn # TODO: Remove the duplication (IS05Utils) - def get_receivers(self): + def get_receivers(self, filter=SND_RCV_SUBSET.ALL): """Gets a list of the available receivers on the API""" toReturn = [] valid, r = TestHelper.do_request("GET", self.url + "receivers/") if valid and r.status_code == 200: try: for value in r.json(): - toReturn.append(value[:-1]) + if filter == SND_RCV_SUBSET.ALL: + toReturn.append(value[:-1]) + else: + valid_io, r_io = TestHelper.do_request("GET", self.url + "receivers/" + value + "outputs/") + if valid_io and r_io.status_code == 200: + try: + if len(r_io.json()) > 0 and filter == SND_RCV_SUBSET.WITH_I_O or \ + len(r_io.json()) == 0 and filter == SND_RCV_SUBSET.WITHOUT_I_O: + toReturn.append(value[:-1]) + except ValueError: + pass except ValueError: pass return toReturn @@ -96,3 +120,19 @@ def checkCleanRequestJSON(self, method, dest, data=None, code=200): return False, "Invalid JSON received" else: return valid, response + + def get_transportfile(self, url, sender_id): + """Get the transport file for a given Sender""" + toReturn = None + valid, r = TestHelper.do_request("GET", url + "single/senders/" + sender_id + "/transportfile/") + if valid and r.status_code == 200: + toReturn = r.text + return toReturn + + def get_flows(self, url, sender_id): + """Get the flow for a given Sender""" + toReturn = None + valid, r = TestHelper.do_request("GET", url + "flows/" + sender_id) + if valid and r.status_code == 200: + toReturn = r.json() + return toReturn diff --git a/nmostesting/suites/IS1101Test.py b/nmostesting/suites/IS1101Test.py index 478a78fa..1bbc0ced 100644 --- a/nmostesting/suites/IS1101Test.py +++ b/nmostesting/suites/IS1101Test.py @@ -21,7 +21,8 @@ from .. import TestHelper from .. import Config as CONFIG from ..IS04Utils import IS04Utils -from ..IS11Utils import IS11Utils +from ..IS11Utils import IS11Utils, SND_RCV_SUBSET +from urllib.parse import urlparse COMPAT_API_KEY = "streamcompatibility" CONTROLS = "controls" @@ -69,8 +70,13 @@ def __init__(self, apis, **kwargs): self.node_url = self.apis[NODE_API_KEY]["url"] self.conn_url = self.apis[CONN_API_KEY]["url"] self.connected_outputs = [] - self.edid_connected_outputs = [] self.not_edid_connected_outputs = [] + self.edid_connected_outputs = [] + self.reference_senders = [] + self.reference_sender_id = "" + self.receivers_with_outputs = [] + self.receivers_without_outputs = [] + self.flow = "" self.caps = "" self.flow_format = {} self.flow_format_audio = [] @@ -86,6 +92,14 @@ def __init__(self, apis, **kwargs): self.constraints = {} self.is11_utils = IS11Utils(self.compat_url) + def remove_last_slash(self, id): + """ + Check if the id comes with a slash at the end or not. If yes, the slash will be remove. + """ + if (id[-1] == '/'): + return id[:-1] + return id + def build_constraints_active_url(self, sender_id): return self.compat_url + "senders/" + sender_id + "/constraints/active/" @@ -95,6 +109,8 @@ def build_sender_status_url(self, sender_id): def set_up_tests(self): self.senders = self.is11_utils.get_senders() self.receivers = self.is11_utils.get_receivers() + self.receivers_with_outputs = self.is11_utils.get_receivers(SND_RCV_SUBSET.WITH_I_O) + self.receivers_without_outputs = self.is11_utils.get_receivers(SND_RCV_SUBSET.WITHOUT_I_O) self.inputs = self.is11_utils.get_inputs() self.outputs = self.is11_utils.get_outputs() @@ -317,26 +333,17 @@ def test_02_02_03(self, test): return test.FAIL( "The sender {} must be inactive ".format(sender_id) ) - valid, response = TestHelper.do_request( - "GET", self.node_url + "flows/" + sender_flow_id - ) - if not valid: - return test.FAIL("Unexpected response from the Node API: {}".format(response)) - if response.status_code != 200: - return test.FAIL( - "The sender {} is not available in the Node API has an associated flow: {}" - .format(sender_flow_id, response.json()) - ) - flow_format = response.json()["format"] + self.flow = self.is11_utils.get_flows(self.node_url, sender_flow_id) + flow_format = self.flow["format"] self.flow_format[sender_id] = flow_format if flow_format == "urn:x-nmos:format:video": self.flow_format_video.append(sender_id) - self.flow_width[sender_id] = response.json()["frame_width"] - self.flow_height[sender_id] = response.json()["frame_height"] - self.flow_grain_rate[sender_id] = response.json()["grain_rate"] + self.flow_width[sender_id] = self.flow["frame_width"] + self.flow_height[sender_id] = self.flow["frame_height"] + self.flow_grain_rate[sender_id] = self.flow["grain_rate"] if flow_format == "urn:x-nmos:format:audio": self.flow_format_audio.append(sender_id) - self.flow_sample_rate[sender_id] = response.json()["sample_rate"] + self.flow_sample_rate[sender_id] = self.flow["sample_rate"] if ( flow_format != "urn:x-nmos:format:video" and flow_format != "urn:x-nmos:format:audio" @@ -738,22 +745,12 @@ def test_02_02_05_01(self, test): sender_flow_id = response.json()["flow_id"] if sender_flow_id is None: return test.FAIL("The sender {} must have a flow".format(sender_id)) - - valid, response = TestHelper.do_request( - "GET", self.node_url + "flows/" + sender_flow_id - ) - if not valid: - return test.FAIL("Unexpected response from the Node API: {}".format(response)) - if response.status_code != 200: - return test.FAIL( - "The sender {} is not available in the Node API has an associated flow: {}" - .format(sender_flow_id, response.json()) - ) + self.flow = self.is11_utils.get_flows(self.node_url, sender_flow_id) if ( - self.flow_grain_rate[sender_id] != response.json()["grain_rate"] - or self.flow_width[sender_id] != response.json()["frame_width"] - or self.flow_height[sender_id] != response.json()["frame_height"] + self.flow_grain_rate[sender_id] != self.flow["grain_rate"] + or self.flow_width[sender_id] != self.flow["frame_width"] + or self.flow_height[sender_id] != self.flow["frame_height"] ): return test.FAIL( "The constraints on frame_width, frame_height\ @@ -882,18 +879,8 @@ def test_02_02_05_02(self, test): sender_flow_id = response.json()["flow_id"] if sender_flow_id is None: return test.FAIL("The sender {} must have a flow".format(sender_id)) - - valid, response = TestHelper.do_request( - "GET", self.node_url + "flows/" + sender_flow_id - ) - if not valid: - return test.FAIL("Unexpected response from the Node API: {}".format(response)) - if response.status_code != 200: - return test.FAIL( - "The sender {} is not available in the Node API has an associated flow: {}" - .format(sender_flow_id, response.json()) - ) - flow_sample_rate = response.json()["sample_rate"] + self.flow = self.is11_utils.get_flows(self.node_url, sender_flow_id) + flow_sample_rate = self.flow["sample_rate"] if self.flow_sample_rate[sender_id] != flow_sample_rate: return test.FAIL("Different sample rate") @@ -960,19 +947,8 @@ def test_02_02_06_01(self, test): .format(sender_id, response.json()) ) sender = response.json() - - valid, response = TestHelper.do_request( - "GET", self.node_url + "flows/" + sender["flow_id"] - ) - if not valid: - return test.FAIL("Unexpected response from the valid API: {}".format(response)) - if response.status_code != 200: - return test.FAIL( - "The sender {} is not available in the Node API has an associated flow: {}" - .format(sender["flow_id"], response.json()) - ) - flow = response.json() - color_sampling = IS04Utils.make_sampling(flow["components"]) + self.flow = self.is11_utils.get_flows(self.node_url, sender["flow_id"]) + color_sampling = IS04Utils.make_sampling(self.flow["components"]) if color_sampling is None: return test.FAIL("Invalid array of video components") constraint_set = {} @@ -987,23 +963,23 @@ def test_02_02_06_01(self, test): constraint_set["urn:x-nmos:cap:meta:enabled"] = True if item == "urn:x-nmos:cap:format:media_type": constraint_set["urn:x-nmos:cap:format:media_type"] = { - "enum": [flow["media_type"]] + "enum": [self.flow["media_type"]] } if item == "urn:x-nmos:cap:format:grain_rate": constraint_set["urn:x-nmos:cap:format:grain_rate"] = { - "enum": [flow["grain_rate"]] + "enum": [self.flow["grain_rate"]] } if item == "urn:x-nmos:cap:format:frame_width": constraint_set["urn:x-nmos:cap:format:frame_width"] = { - "enum": [flow["frame_width"]] + "enum": [self.flow["frame_width"]] } if item == "urn:x-nmos:cap:format:frame_height": constraint_set["urn:x-nmos:cap:format:frame_height"] = { - "enum": [flow["frame_height"]] + "enum": [self.flow["frame_height"]] } if item == "urn:x-nmos:cap:format:interlace_mode": constraint_set["urn:x-nmos:cap:format:interlace_mode"] = { - "enum": [flow["interlace_mode"]] + "enum": [self.flow["interlace_mode"]] } if item == "urn:x-nmos:cap:format:color_sampling": constraint_set["urn:x-nmos:cap:format:color_sampling"] = { @@ -1011,7 +987,7 @@ def test_02_02_06_01(self, test): } if item == "urn:x-nmos:cap:format:component_depth": constraint_set["urn:x-nmos:cap:format:component_depth"] = { - "enum": [flow["components"][0]["bit_depth"]] + "enum": [self.flow["components"][0]["bit_depth"]] } except Exception: pass @@ -1030,19 +1006,7 @@ def test_02_02_06_01(self, test): "The sender {} constraints change has failed: {}" .format(sender_id, response.json()) ) - - valid, response = TestHelper.do_request( - "GET", self.node_url + "flows/" + sender["flow_id"] - ) - if not valid: - return test.FAIL("Unexpected response from the Node API: {}".format(response)) - if response.status_code != 200: - return test.FAIL( - "The sender {} is not available in the Node API has an associated flow: {}" - .format(sender["flow_id"], response.json()) - ) - new_flow = response.json() - + new_flow = self.is11_utils.get_flows(self.node_url, sender["flow_id"]) new_color_sampling = IS04Utils.make_sampling(new_flow["components"]) if new_color_sampling is None: return test.FAIL("Invalid array of video components") @@ -1050,26 +1014,26 @@ def test_02_02_06_01(self, test): for item in REF_SUPPORTED_CONSTRAINTS_VIDEO: try: if item == "urn:x-nmos:cap:format:media_type": - if flow["media_type"] != new_flow["media_type"]: + if self.flow["media_type"] != new_flow["media_type"]: return test.FAIL("Different media_type") if item == "urn:x-nmos:cap:format:grain_rate": - if flow["grain_rate"] != new_flow["grain_rate"]: + if self.flow["grain_rate"] != new_flow["grain_rate"]: return test.FAIL("Different grain_rate") if item == "urn:x-nmos:cap:format:frame_width": - if flow["frame_width"] != new_flow["frame_width"]: + if self.flow["frame_width"] != new_flow["frame_width"]: return test.FAIL("Different frame_width") if item == "urn:x-nmos:cap:format:frame_height": - if flow["frame_height"] != new_flow["frame_height"]: + if self.flow["frame_height"] != new_flow["frame_height"]: return test.FAIL("Different frame_height") if item == "urn:x-nmos:cap:format:interlace_mode": - if flow["interlace_mode"] != new_flow["interlace_mode"]: + if self.flow["interlace_mode"] != new_flow["interlace_mode"]: return test.FAIL("Different interlace_mode") if item == "urn:x-nmos:cap:format:color_sampling": if color_sampling != new_color_sampling: return test.FAIL("Different color_sampling") if item == "urn:x-nmos:cap:format:component_depth": if ( - flow["components"][0]["bit_depth"] + self.flow["components"][0]["bit_depth"] != new_flow["components"][0]["bit_depth"] ): return test.FAIL("Different component_depth") @@ -1134,29 +1098,18 @@ def test_02_02_06_02(self, test): .format(sender_id, response.json()) ) sender = response.json() - - valid, response = TestHelper.do_request( - "GET", self.node_url + "flows/" + sender["flow_id"] - ) - if not valid: - return test.FAIL("Unexpected response from the Node API: {}".format(response)) - if response.status_code != 200: - return test.FAIL( - "The sender {} is not available in the Node API has an associated flow: {}" - .format(sender["flow_id"], response.json()) - ) - flow = response.json() + self.flow = self.is11_utils.get_flows(self.node_url, sender["flow_id"]) constraint_set = {} valid, response = TestHelper.do_request( - "GET", self.node_url + "sources/" + flow["source_id"] + "GET", self.node_url + "sources/" + self.flow["source_id"] ) if not valid: return test.FAIL("Unexpected response from the Node API: {}".format(response)) if response.status_code != 200: return test.FAIL( "The source {} is not available in the Node API: {}" - .format(flow["source_id"], response.json()) + .format(self.flow["source_id"], response.json()) ) source = response.json() @@ -1171,11 +1124,11 @@ def test_02_02_06_02(self, test): constraint_set["urn:x-nmos:cap:meta:enabled"] = True if item == "urn:x-nmos:cap:format:media_type": constraint_set["urn:x-nmos:cap:format:media_type"] = { - "enum": [flow["media_type"]] + "enum": [self.flow["media_type"]] } if item == "urn:x-nmos:cap:format:sample_rate": constraint_set["urn:x-nmos:cap:format:sample_rate"] = { - "enum": [flow["sample_rate"]] + "enum": [self.flow["sample_rate"]] } if item == "urn:x-nmos:cap:format:channel_count": constraint_set["urn:x-nmos:cap:format:channel_count"] = { @@ -1183,7 +1136,7 @@ def test_02_02_06_02(self, test): } if item == "urn:x-nmos:cap:format:sample_depth": constraint_set["urn:x-nmos:cap:format:sample_depth"] = { - "enum": [flow["bit_depth"]] + "enum": [self.flow["bit_depth"]] } except Exception: pass @@ -1200,44 +1153,33 @@ def test_02_02_06_02(self, test): "The sender {} constraints change has failed: {}" .format(sender_id, response.json()) ) + new_flow = self.is11_utils.get_flows(self.node_url, sender["flow_id"]) valid, response = TestHelper.do_request( - "GET", self.node_url + "flows/" + sender["flow_id"] - ) - if not valid: - return test.FAIL("Unexpected response from the Node API: {}".format(response)) - if response.status_code != 200: - return test.FAIL( - "The sender {} is not available in the Node API has an associated flow: {}" - .format(sender["flow_id"], response.json()) - ) - new_flow = response.json() - - valid, response = TestHelper.do_request( - "GET", self.node_url + "sources/" + flow["source_id"] + "GET", self.node_url + "sources/" + self.flow["source_id"] ) if not valid: return test.FAIL("Unexpected response from the Node API: {}".format(response)) if response.status_code != 200: return test.FAIL( "The source {} is not available in the Node API: {}" - .format(flow["source_id"], response.json()) + .format(self.flow["source_id"], response.json()) ) new_source = response.json() for item in REF_SUPPORTED_CONSTRAINTS_AUDIO: try: if item == "urn:x-nmos:cap:format:media_type": - if flow["media_type"] != new_flow["media_type"]: + if self.flow["media_type"] != new_flow["media_type"]: return test.FAIL("Different media_type") if item == "urn:x-nmos:cap:format:sample_rate": - if flow["sample_rate"] != new_flow["sample_rate"]: + if self.flow["sample_rate"] != new_flow["sample_rate"]: return test.FAIL("Different sample_rate") if item == "urn:x-nmos:cap:format:channel_count": if len(source["channels"]) != len(new_source["channels"]): return test.FAIL("Different channel_count") if item == "urn:x-nmos:cap:format:sample_depth": - if flow["bit_depth"] != new_flow["bit_depth"]: + if self.flow["bit_depth"] != new_flow["bit_depth"]: return test.FAIL("Different sample_depth") except Exception: pass @@ -1302,19 +1244,8 @@ def test_02_02_07_01(self, test): .format(sender_id, response.json()) ) sender = response.json() - - valid, response = TestHelper.do_request( - "GET", self.node_url + "flows/" + sender["flow_id"] - ) - if not valid: - return test.FAIL("Unexpected response from the Node API: {}".format(response)) - if response.status_code != 200: - return test.FAIL( - "The sender {} is not available in the Node API has an associated flow: {}" - .format(sender["flow_id"], response.json()) - ) - flow = response.json() - color_sampling = IS04Utils.make_sampling(flow["components"]) + self.flow = self.is11_utils.get_flows(self.node_url, sender["flow_id"]) + color_sampling = IS04Utils.make_sampling(self.flow["components"]) if color_sampling is None: return test.FAIL("Invalid array of video components") constraint_set0 = {} @@ -1333,23 +1264,23 @@ def test_02_02_07_01(self, test): constraint_set0["urn:x-nmos:cap:meta:enabled"] = True if item == "urn:x-nmos:cap:format:media_type": constraint_set0["urn:x-nmos:cap:format:media_type"] = { - "enum": [flow["media_type"]] + "enum": [self.flow["media_type"]] } if item == "urn:x-nmos:cap:format:grain_rate": constraint_set0["urn:x-nmos:cap:format:grain_rate"] = { - "enum": [flow["grain_rate"]] + "enum": [self.flow["grain_rate"]] } if item == "urn:x-nmos:cap:format:frame_width": constraint_set0["urn:x-nmos:cap:format:frame_width"] = { - "enum": [flow["frame_width"]] + "enum": [self.flow["frame_width"]] } if item == "urn:x-nmos:cap:format:frame_height": constraint_set0["urn:x-nmos:cap:format:frame_height"] = { - "enum": [flow["frame_height"]] + "enum": [self.flow["frame_height"]] } if item == "urn:x-nmos:cap:format:interlace_mode": constraint_set0["urn:x-nmos:cap:format:interlace_mode"] = { - "enum": [flow["interlace_mode"]] + "enum": [self.flow["interlace_mode"]] } if item == "urn:x-nmos:cap:format:color_sampling": constraint_set0["urn:x-nmos:cap:format:color_sampling"] = { @@ -1357,7 +1288,7 @@ def test_02_02_07_01(self, test): } if item == "urn:x-nmos:cap:format:component_depth": constraint_set0["urn:x-nmos:cap:format:component_depth"] = { - "enum": [flow["components"][0]["bit_depth"]] + "enum": [self.flow["components"][0]["bit_depth"]] } except Exception: pass @@ -1374,23 +1305,23 @@ def test_02_02_07_01(self, test): constraint_set1["urn:x-nmos:cap:meta:enabled"] = True if item == "urn:x-nmos:cap:format:media_type": constraint_set1["urn:x-nmos:cap:format:media_type"] = { - "enum": [flow["media_type"]] + "enum": [self.flow["media_type"]] } if item == "urn:x-nmos:cap:format:grain_rate": constraint_set1["urn:x-nmos:cap:format:grain_rate"] = { - "enum": [self.get_another_grain_rate(flow["grain_rate"])] + "enum": [self.get_another_grain_rate(self.flow["grain_rate"])] } if item == "urn:x-nmos:cap:format:frame_width": constraint_set1["urn:x-nmos:cap:format:frame_width"] = { - "enum": [flow["frame_width"]] + "enum": [self.flow["frame_width"]] } if item == "urn:x-nmos:cap:format:frame_height": constraint_set1["urn:x-nmos:cap:format:frame_height"] = { - "enum": [flow["frame_height"]] + "enum": [self.flow["frame_height"]] } if item == "urn:x-nmos:cap:format:interlace_mode": constraint_set1["urn:x-nmos:cap:format:interlace_mode"] = { - "enum": [flow["interlace_mode"]] + "enum": [self.flow["interlace_mode"]] } if item == "urn:x-nmos:cap:format:color_sampling": constraint_set1["urn:x-nmos:cap:format:color_sampling"] = { @@ -1398,7 +1329,7 @@ def test_02_02_07_01(self, test): } if item == "urn:x-nmos:cap:format:component_depth": constraint_set1["urn:x-nmos:cap:format:component_depth"] = { - "enum": [flow["components"][0]["bit_depth"]] + "enum": [self.flow["components"][0]["bit_depth"]] } except Exception: pass @@ -1419,17 +1350,7 @@ def test_02_02_07_01(self, test): .format(sender_id, response.json()) ) - valid, response = TestHelper.do_request( - "GET", self.node_url + "flows/" + sender["flow_id"] - ) - if not valid: - return test.FAIL("Unexpected response from the Node API: {}".format(response)) - if response.status_code != 200: - return test.FAIL( - "The sender {} is not available in the Node API has an associated flow: {}" - .format(sender["flow_id"], response.json()) - ) - new_flow = response.json() + new_flow = self.is11_utils.get_flows(self.node_url, sender["flow_id"]) new_color_sampling = IS04Utils.make_sampling(new_flow["components"]) if new_color_sampling is None: @@ -1438,26 +1359,26 @@ def test_02_02_07_01(self, test): for item in REF_SUPPORTED_CONSTRAINTS_VIDEO: try: if item == "urn:x-nmos:cap:format:media_type": - if flow["media_type"] != new_flow["media_type"]: + if self.flow["media_type"] != new_flow["media_type"]: return test.FAIL("Different media_type") if item == "urn:x-nmos:cap:format:grain_rate": - if flow["grain_rate"] != new_flow["grain_rate"]: + if self.flow["grain_rate"] != new_flow["grain_rate"]: return test.FAIL("Different grain_rate") if item == "urn:x-nmos:cap:format:frame_width": - if flow["frame_width"] != new_flow["frame_width"]: + if self.flow["frame_width"] != new_flow["frame_width"]: return test.FAIL("Different frame_width") if item == "urn:x-nmos:cap:format:frame_height": - if flow["frame_height"] != new_flow["frame_height"]: + if self.flow["frame_height"] != new_flow["frame_height"]: return test.FAIL("Different frame_height") if item == "urn:x-nmos:cap:format:interlace_mode": - if flow["interlace_mode"] != new_flow["interlace_mode"]: + if self.flow["interlace_mode"] != new_flow["interlace_mode"]: return test.FAIL("Different interlace_mode") if item == "urn:x-nmos:cap:format:color_sampling": if color_sampling != new_color_sampling: return test.FAIL("Different color_sampling") if item == "urn:x-nmos:cap:format:component_depth": if ( - flow["components"][0]["bit_depth"] + self.flow["components"][0]["bit_depth"] != new_flow["components"][0]["bit_depth"] ): return test.FAIL("Different component_depth") @@ -1522,27 +1443,16 @@ def test_02_02_07_02(self, test): .format(sender_id, response.json()) ) sender = response.json() - - valid, response = TestHelper.do_request( - "GET", self.node_url + "flows/" + sender["flow_id"] - ) - if not valid: - return test.FAIL("Unexpected response from the Node API: {}".format(response)) - if response.status_code != 200: - return test.FAIL( - "The sender {} is not available in the Node API has an associated flow: {}" - .format(sender["flow_id"], response.json()) - ) - flow = response.json() + self.flow = self.is11_utils.get_flows(self.node_url, sender["flow_id"]) valid, response = TestHelper.do_request( - "GET", self.node_url + "sources/" + flow["source_id"] + "GET", self.node_url + "sources/" + self.flow["source_id"] ) if not valid: return test.FAIL("Unexpected response from the Node API: {}".format(response)) if response.status_code != 200: return test.FAIL( "The source {} is not available in the Node API: {}" - .format(flow["source_id"], response.json()) + .format(self.flow["source_id"], response.json()) ) source = response.json() @@ -1561,11 +1471,11 @@ def test_02_02_07_02(self, test): constraint_set0["urn:x-nmos:cap:meta:enabled"] = True if item == "urn:x-nmos:cap:format:media_type": constraint_set0["urn:x-nmos:cap:format:media_type"] = { - "enum": [flow["media_type"]] + "enum": [self.flow["media_type"]] } if item == "urn:x-nmos:cap:format:sample_rate": constraint_set0["urn:x-nmos:cap:format:sample_rate"] = { - "enum": [flow["sample_rate"]] + "enum": [self.flow["sample_rate"]] } if item == "urn:x-nmos:cap:format:channel_count": constraint_set0["urn:x-nmos:cap:format:channel_count"] = { @@ -1573,7 +1483,7 @@ def test_02_02_07_02(self, test): } if item == "urn:x-nmos:cap:format:sample_depth": constraint_set0["urn:x-nmos:cap:format:sample_depth"] = { - "enum": [flow["bit_depth"]] + "enum": [self.flow["bit_depth"]] } except Exception: pass @@ -1590,11 +1500,11 @@ def test_02_02_07_02(self, test): constraint_set1["urn:x-nmos:cap:meta:enabled"] = True if item == "urn:x-nmos:cap:format:media_type": constraint_set1["urn:x-nmos:cap:format:media_type"] = { - "enum": [flow["media_type"]] + "enum": [self.flow["media_type"]] } if item == "urn:x-nmos:cap:format:sample_rate": constraint_set1["urn:x-nmos:cap:format:sample_rate"] = { - "enum": [self.get_another_sample_rate(flow["sample_rate"])] + "enum": [self.get_another_sample_rate(self.flow["sample_rate"])] } if item == "urn:x-nmos:cap:format:channel_count": constraint_set1["urn:x-nmos:cap:format:channel_count"] = { @@ -1602,7 +1512,7 @@ def test_02_02_07_02(self, test): } if item == "urn:x-nmos:cap:format:sample_depth": constraint_set1["urn:x-nmos:cap:format:sample_depth"] = { - "enum": [flow["bit_depth"]] + "enum": [self.flow["bit_depth"]] } except Exception: pass @@ -1623,43 +1533,33 @@ def test_02_02_07_02(self, test): "The sender {} constraints change has failed: {}" .format(sender_id, response.json()) ) - valid, response = TestHelper.do_request( - "GET", self.node_url + "flows/" + sender["flow_id"] - ) - if not valid: - return test.FAIL("Unexpected response from the Node API: {}".format(response)) - if response.status_code != 200: - return test.FAIL( - "The sender {} is not available in the Node API has an associated flow: {}" - .format(sender["flow_id"], response.json()) - ) - new_flow = response.json() + new_flow = self.is11_utils.get_flows(self.node_url, sender["flow_id"]) valid, response = TestHelper.do_request( - "GET", self.node_url + "sources/" + flow["source_id"] + "GET", self.node_url + "sources/" + self.flow["source_id"] ) if not valid: return test.FAIL("Unexpected response from the Node API: {}".format(response)) if response.status_code != 200: return test.FAIL( "The source {} is not available in the Node API: {}" - .format(flow["source_id"], response.json()) + .format(self.flow["source_id"], response.json()) ) new_source = response.json() for item in REF_SUPPORTED_CONSTRAINTS_AUDIO: try: if item == "urn:x-nmos:cap:format:media_type": - if flow["media_type"] != new_flow["media_type"]: + if self.flow["media_type"] != new_flow["media_type"]: return test.FAIL("Different media_type") if item == "urn:x-nmos:cap:format:sample_rate": - if flow["sample_rate"] != new_flow["sample_rate"]: + if self.flow["sample_rate"] != new_flow["sample_rate"]: return test.FAIL("Different sample_rate") if item == "urn:x-nmos:cap:format:channel_count": if len(source["channels"]) != len(new_source["channels"]): return test.FAIL("Different channel_count") if item == "urn:x-nmos:cap:format:sample_depth": - if flow["bit_depth"] != new_flow["bit_depth"]: + if self.flow["bit_depth"] != new_flow["bit_depth"]: return test.FAIL("Different sample_depth") except Exception: pass @@ -1863,7 +1763,7 @@ def test_04_01_01(self, test): if response.status_code != 200: return test.FAIL("The Node API request for receiver {} has failed: {}" .format(receiver_id, response.json())) - if response.json()["id"] != receiver_id[:-1]: + if response.json()["id"] != receiver_id: return test.UNCLEAR( "The IS-11 Receiver doesn't exist on the Node API as receiver." ) @@ -1894,7 +1794,8 @@ def test_04_02_01(self, test): return test.FAIL("The streamcompatibility request for receiver {} has failed: {}" .format(receiver_id, response.json())) if response.json()["state"] not in ["unknown", "non_compliant_stream"]: - return test.FAIL("The state is not unknown or non_compliant_stream") + return test.FAIL("Receiver {}: expected states: \"unknown\" and \"non_compliant_stream\", got {}" + .format(receiver_id, response.json()["state"])) return test.PASS() def test_04_02_02(self, test): @@ -1916,7 +1817,538 @@ def test_04_02_02(self, test): if "constraint_sets" not in self.caps: return test.UNCLEAR("The receiver does not have constraint_sets in caps") if len(self.caps["constraint_sets"]) == 0: - return test.WARNING("The receiver does not support BCP-004-01.") + return test.WARNING("The receiver does not support BCP-004-01") + return test.PASS() + + def test_04_03(self, test): + """ + Verify receivers supporting outputs + """ + if len(self.receivers) == 0: + return test.UNCLEAR("No IS-11 receivers") + """ + The test requires streaming from a Sender in order to + verify the state of the Receiver and the associated outputs. + if IS11_REFERENCE_SENDER_NODE_API is not configured fail 4.3 with log indicating + that IS11_REFERENCE_SENDER_NODE_API is not configured + """ + if not (CONFIG.IS11_REFERENCE_SENDER_NODE_API_PORT and CONFIG.IS11_REFERENCE_SENDER_NODE_API): + return test.DISABLED("Please configure IS11_REFERENCE_SENDER_NODE_API" + " and IS11_REFERENCE_SENDER_NODE_API_PORT in Config.py") + if len(self.receivers_with_outputs) == 0: + return test.UNCLEAR("No IS-11 receivers support outputs") + + for receiver_id in self.receivers_with_outputs: + valid, response = TestHelper.do_request('GET', self.node_url + "receivers/" + receiver_id) + if not valid: + return test.FAIL("Unexpected response from the Node API: {}".format(response)) + if response.status_code != 200: + return test.FAIL("The Node API request for receiver {} has failed: {}" + .format(receiver_id, response.json())) + receiver_format = response.json()["format"] + receiver_transport_file = response.json()["transport"] + if (receiver_format == "urn:x-nmos:format:video"): + """ + using IS11_REFERENCE_SENDER_NODE_API GET sender_id from Node API/senders/*/ where + .format is receiver.format and .transport is receiver.transport + """ + new_netloc = "{}:{}".format(CONFIG.IS11_REFERENCE_SENDER_NODE_API, + CONFIG.IS11_REFERENCE_SENDER_NODE_API_PORT) + url = self.node_url.replace(urlparse(self.node_url).netloc, new_netloc) + valid, response = TestHelper.do_request('GET', url + "senders/") + if not valid: + return test.FAIL("Unexpected response from the Node API: {}".format(response)) + if response.status_code != 200: + return test.FAIL("The sender's request {} has failed: {}" + .format(url + "senders/", response.json())) + self.reference_senders = response.json() + if len(self.reference_senders) == 0: + return test.DISABLED("No IS-11 reference sender") + + for sender in self.reference_senders: + if (sender["flow_id"]): + self.flow = self.is11_utils.get_flows(url, sender["flow_id"]) + sender_format = self.flow["format"] + if ((sender_format == receiver_format) and + (sender["transport"] == receiver_transport_file)): + self.reference_sender_id = sender["id"] + if not (self.reference_sender_id): + return test.UNCLEAR("The format and transport file of the " + "IS11_REFERENCE_SENDER_NODE_API do not match with the receiver") + return test.PASS() + + def test_04_03_01(self, test): + """ + Verify the status of the Receiver and the associated outputs using + the reference Sender to produce the video stream consumed by the Receiver + """ + if len(self.receivers) == 0: + return test.UNCLEAR("No IS-11 receivers") + if not (CONFIG.IS11_REFERENCE_SENDER_CONNECTION_API and CONFIG.IS11_REFERENCE_SENDER_NODE_API): + return test.DISABLED("Please configure IS11_REFERENCE_SENDER_NODE_API" + " and IS11_REFERENCE_SENDER_CONNECTION_API in Config.py") + if not (CONFIG.IS11_REFERENCE_SENDER_CONNECTION_API_PORT and CONFIG.IS11_REFERENCE_SENDER_NODE_API_PORT): + return test.DISABLED("Please configure IS11_REFERENCE_SENDER_NODE_API_PORT" + " and IS11_REFERENCE_SENDER_CONNECTION_API_PORT in Config.py") + if len(self.reference_senders) == 0: + return test.DISABLED("No IS-11 reference sender") + self.is_compatible_media_type = False + for sender in self.reference_senders: + new_netloc = "{}:{}".format(CONFIG.IS11_REFERENCE_SENDER_NODE_API, + CONFIG.IS11_REFERENCE_SENDER_NODE_API_PORT) + url_node = self.node_url.replace(urlparse(self.node_url).netloc, new_netloc) + valid, response = TestHelper.do_request('GET', url_node + "senders/" + sender["id"]) + if not valid: + return test.FAIL("Unexpected response from the Node API: {}".format(response)) + if response.status_code != 200: + return test.FAIL("The Node API request for sender {} has failed: {}" + .format(sender["id"], response.json())) + sender_flow_id = response.json()["flow_id"] + if not sender_flow_id: + return test.UNCLEAR("There are no Flow id") + self.flow = self.is11_utils.get_flows(url_node, sender_flow_id) + sender_media_type = self.flow["media_type"] + new_netloc = "{}:{}".format(CONFIG.IS11_REFERENCE_SENDER_CONNECTION_API, + CONFIG.IS11_REFERENCE_SENDER_CONNECTION_API_PORT) + url = self.conn_url.replace(urlparse(self.conn_url).netloc, new_netloc) + json_data = { + "master_enable": True, + "activation": {"mode": "activate_immediate"} + } + headers = {"Content-Type": "application/json"} + valid_patch, response = TestHelper.do_request('PATCH', url + "single/senders/" + + sender["id"] + "/staged/", json=json_data, headers=headers) + if not valid_patch: + return test.FAIL("Unexpected response from the Connection API: {}".format(response)) + if response.status_code != 200: + return test.FAIL("The patch request to {} has failed: {}" + .format(url + "single/senders/" + sender["id"] + "/staged/", response.json())) + if ("audio" in sender_media_type or "video" in sender_media_type): + self.is_compatible_media_type = True + sdp_transport_file = self.is11_utils.get_transportfile(url, sender["id"]) + for receiver_id in self.receivers_with_outputs: + valid, response = TestHelper.do_request('GET', self.node_url + "receivers/" + receiver_id) + if not valid: + return test.FAIL("Unexpected response from the Node API: {}".format(response)) + if response.status_code != 200: + return test.FAIL("The Node API request for receiver {} has failed: {}" + .format(receiver_id, response.json())) + receiver_media_type = response.json()["caps"]["media_types"] + if (sender_media_type in receiver_media_type and len(sdp_transport_file.strip()) != 0): + patchload = { + "sender_id": sender["id"], + "master_enable": True, + "activation": {"mode": "activate_immediate"}, + "transport_file": {"type": "application/sdp", + "data": "{}".format(sdp_transport_file)} + } + valid_patch, response = TestHelper.do_request('PATCH', self.conn_url + "single/receivers/" + + receiver_id + "/staged/", + json=patchload, headers=headers) + if not valid_patch: + return test.FAIL("Unexpected response from the Connection API: {}".format(response)) + if response.status_code != 200: + return test.FAIL("The patch request to {} has failed: {}" + .format(self.conn_url + "single/receivers/" + + receiver_id + "/staged/", response.json())) + valid, response = TestHelper.do_request('GET', self.conn_url + "single/receivers/" + + receiver_id + "/active/") + if not valid: + return test.FAIL("Unexpected response from the Connection API: {}".format(response)) + if response.status_code != 200: + return test.FAIL("The connection request for receiver {} has failed: {}" + .format(receiver_id, response.json())) + + if (response.json()["master_enable"] is not True): + time.sleep(CONFIG.STABLE_STATE_DELAY) + valid, response = TestHelper.do_request('GET', self.conn_url + "single/receivers/" + + receiver_id + "/active/") + if not valid: + return test.FAIL("Unexpected response from the Connection API: {}".format(response)) + if response.status_code != 200: + return test.FAIL("The connection request for receiver {} has failed: {}" + .format(receiver_id, response.json())) + if (response.json()["master_enable"] is not True): + return test.FAIL("The master_enable still False") + valid, response = TestHelper.do_request('GET', self.compat_url + + "receivers/" + receiver_id + "/status/") + if not valid: + return test.FAIL("Unexpected response from the streamcompatibility API: {}" + .format(response)) + if response.status_code != 200: + return test.FAIL("The streamcompatibility request for receiver {} status has failed: {}" + .format(receiver_id, response.json())) + if (response.json()["state"] != "compliant_stream"): + return test.FAIL("The state should be compliant_stream") + + valid, response = TestHelper.do_request('GET', self.compat_url + + "receivers/" + receiver_id + "/outputs/") + if not valid: + return test.FAIL("Unexpected response from the streamcompatibility API: {}" + .format(response)) + if response.status_code != 200: + return test.FAIL("The streamcompatibility request for receiver {} outputs has failed: {}" + .format(receiver_id, response.json())) + self.outputs = response.json() + if not (self.is_compatible_media_type): + return test.WARNING("Please use a sender with a video or" + " an audio format as a reference in the config file.") + return test.PASS() + + def test_04_03_01_01(self, test): + """ + Verify that the status indicates that there is a signal. + """ + time.sleep(CONFIG.STABLE_STATE_DELAY) + if len(self.receivers) == 0: + return test.UNCLEAR("No IS-11 receivers") + if not (CONFIG.IS11_REFERENCE_SENDER_CONNECTION_API and CONFIG.IS11_REFERENCE_SENDER_NODE_API): + return test.DISABLED("Please configure IS11_REFERENCE_SENDER_NODE_API" + " and IS11_REFERENCE_SENDER_CONNECTION_API in Config.py") + if not (CONFIG.IS11_REFERENCE_SENDER_CONNECTION_API_PORT and CONFIG.IS11_REFERENCE_SENDER_NODE_API_PORT): + return test.DISABLED("Please configure IS11_REFERENCE_SENDER_NODE_API_PORT" + " and IS11_REFERENCE_SENDER_CONNECTION_API_PORT in Config.py") + if len(self.outputs) == 0: + return test.DISABLED("No IS-11 receiver outputs") + for output_id in self.outputs: + output_id = self.remove_last_slash(output_id) + valid, response = TestHelper.do_request('GET', self.compat_url + "outputs/" + output_id + "/properties/") + if not valid: + return test.FAIL("Unexpected response from the streamcompatibility API: {}".format(response)) + if response.status_code != 200: + return test.FAIL("The streamcompatibility request for output {} properties has failed: {}" + .format(output_id, response.json())) + if (response.json()["status"]["state"] != "signal_present"): + return test.FAIL("The state: {} should be signal_present".format(response.json()["status"]["state"])) + if len(self.reference_senders) == 0: + return test.DISABLED("No IS-11 reference sender") + + for sender in self.reference_senders: + new_netloc = "{}:{}".format(CONFIG.IS11_REFERENCE_SENDER_CONNECTION_API, + CONFIG.IS11_REFERENCE_SENDER_CONNECTION_API_PORT) + url = self.conn_url.replace(urlparse(self.conn_url).netloc, new_netloc) + json_data = { + "master_enable": False, + "activation": {"mode": "activate_immediate"} + } + headers = {"Content-Type": "application/json"} + valid_patch, response = TestHelper.do_request('PATCH', url + "single/senders/" + + sender["id"] + "/staged/", + json=json_data, headers=headers) + if not valid_patch: + return test.FAIL("Unexpected response from the Connection API: {}".format(response)) + if response.status_code != 200: + return test.FAIL("The patch request to {} has failed: {}" + .format(url + "single/senders/" + sender["id"] + "/staged/", response.json())) + if len(self.receivers_with_outputs) == 0: + return test.UNCLEAR("No IS-11 receivers support outputs") + for receiver_id in self.receivers_with_outputs: + valid, response = TestHelper.do_request('GET', self.node_url + "receivers/" + receiver_id) + if not valid: + return test.FAIL("Unexpected response from the Node API: {}".format(response)) + if response.status_code != 200: + return test.FAIL("The Node API request for receiver {} has failed: {}" + .format(receiver_id, response.json())) + receiver_format = response.json()["format"] + receiver_transport_file = response.json()["transport"] + if (receiver_format == "urn:x-nmos:format:audio"): + """ + using IS11_REFERENCE_SENDER_NODE_API GET sender_id from Node API/senders/*/ where + .format is receiver.format and .transport is receiver.transport + """ + new_netloc = "{}:{}".format(CONFIG.IS11_REFERENCE_SENDER_NODE_API, + CONFIG.IS11_REFERENCE_SENDER_NODE_API_PORT) + url = self.node_url.replace(urlparse(self.node_url).netloc, new_netloc) + valid, response = TestHelper.do_request('GET', url + "senders/") + if not valid: + return test.FAIL("Unexpected response from the Node API: {}".format(response)) + if response.status_code != 200: + return test.FAIL("The request for {} has failed: {}" + .format(url + "senders/", response.json())) + self.reference_senders = response.json() + if len(self.reference_senders) == 0: + return test.DISABLED("No IS-11 reference sender") + for sender in self.reference_senders: + if (sender["flow_id"]): + self.flow = self.is11_utils.get_flows(url, sender["flow_id"]) + sender_format = self.flow["format"] + if (sender_format == receiver_format and + sender["transport"] == receiver_transport_file): + self.reference_sender_id = sender["id"] + if len(self.reference_sender_id) == 0: + return test.UNCLEAR("The format and transport file of the " + "IS-11 REFERENCE SENDER do not match with the receiver") + return test.PASS() + + def test_04_03_02(self, test): + """ + Verify the status of the Receiver and the + associated outputs using the reference Sender to + produce the audio stream consumed by the Receiver. + """ + if len(self.receivers) == 0: + return test.UNCLEAR("No IS-11 receivers") + if len(self.reference_senders) == 0: + return test.DISABLED("No IS-11 reference sender") + if not (CONFIG.IS11_REFERENCE_SENDER_CONNECTION_API and CONFIG.IS11_REFERENCE_SENDER_CONNECTION_API_PORT): + return test.DISABLED("Please configure IS11_REFERENCE_SENDER_CONNECTION_API" + " and IS11_REFERENCE_SENDER_CONNECTION_API_PORT in Config.py") + self.is_compatible_media_type = False + for sender in self.reference_senders: + new_netloc = "{}:{}".format(CONFIG.IS11_REFERENCE_SENDER_CONNECTION_API, + CONFIG.IS11_REFERENCE_SENDER_CONNECTION_API_PORT) + new_netloc_node = "{}:{}".format(CONFIG.IS11_REFERENCE_SENDER_NODE_API, + CONFIG.IS11_REFERENCE_SENDER_NODE_API_PORT) + url = self.conn_url.replace(urlparse(self.conn_url).netloc, new_netloc) + url_node = self.node_url.replace(urlparse(self.node_url).netloc, new_netloc_node) + valid, response = TestHelper.do_request('GET', url_node + "senders/" + sender["id"]) + if not valid: + return test.FAIL("Unexpected response from the Node API: {}".format(response)) + if response.status_code != 200: + return test.FAIL("The Node API request for sender {} has failed: {}" + .format(sender["id"], response.json())) + sender_flow_id = response.json()["flow_id"] + if not sender_flow_id: + return test.UNCLEAR("There are no Flow id") + self.flow = self.is11_utils.get_flows(url_node, sender_flow_id) + sender_media_type = self.flow["media_type"] + if ("audio" in sender_media_type or "video" in sender_media_type): + self.is_compatible_media_type = True + json_data = { + "master_enable": True, + "activation": {"mode": "activate_immediate"} + } + headers = {"Content-Type": "application/json"} + valid_patch, response = TestHelper.do_request('PATCH', url + "single/senders/" + + sender["id"] + "/staged/", + json=json_data, headers=headers) + if not valid_patch: + return test.FAIL("Unexpected response from the Connection API: {}".format(response)) + if response.status_code != 200: + return test.FAIL("The patch request to {} has failed: {}" + .format(url + "single/senders/" + sender["id"] + "/staged/", response.json())) + + sdp_transport_file = self.is11_utils.get_transportfile(url, sender["id"]) + if len(sdp_transport_file.strip()) == 0: + return test.FAIL("The IS-11 reference sender {} transport file is empty".format(sender["id"])) + for receiver_id in self.receivers_with_outputs: + valid, response = TestHelper.do_request('GET', self.node_url + "receivers/" + receiver_id) + if not valid: + return test.FAIL("Unexpected response from the Node API: {}".format(response)) + if response.status_code != 200: + return test.FAIL("The Node API request for receiver {} has failed: {}" + .format(receiver_id, response.json())) + receiver_media_type = response.json()["caps"]["media_types"] + if (sender_media_type in receiver_media_type and len(sdp_transport_file.strip()) != 0): + patchload = { + "sender_id": sender["id"], + "master_enable": True, + "activation": {"mode": "activate_immediate"}, + "transport_file": {"type": "application/sdp", + "data": "{}".format(sdp_transport_file.strip())} + } + valid_patch, response = TestHelper.do_request('PATCH', self.conn_url + "single/receivers/" + + receiver_id + "/staged/", + json=patchload, headers=headers) + if not valid_patch: + return test.FAIL("Unexpected response from the Connection API: {}".format(response)) + if response.status_code != 200: + return test.FAIL("The patch request to {} has failed: {}" + .format(self.conn_url + "single/receivers/" + + receiver_id + "/staged/", response.json())) + + valid, response = TestHelper.do_request('GET', self.conn_url + "single/receivers/" + + receiver_id + "/active/") + if not valid: + return test.FAIL("Unexpected response from the Connection API: {}".format(response)) + if response.status_code != 200: + return test.FAIL("The connection request for receiver {} has failed: {}" + .format(receiver_id, response.json())) + + if (response.json()["master_enable"] is not True): + time.sleep(CONFIG.STABLE_STATE_DELAY) + valid, response = TestHelper.do_request('GET', self.conn_url + "single/receivers/" + + receiver_id + "/active/") + if not valid: + return test.FAIL("Unexpected response from the Connection API: {}".format(response)) + if response.status_code != 200: + return test.FAIL("The connection request for receiver {} has failed: {}" + .format(receiver_id, response.json())) + + valid, response = TestHelper.do_request('GET', self.compat_url + "receivers/" + + receiver_id + "/status/") + if not valid: + return test.FAIL("Unexpected response from the streamcompatibility API: {}" + .format(response)) + if response.status_code != 200: + return test.FAIL("The streamcompatibility request for receiver {} status has failed: {}" + .format(receiver_id, response.json())) + if (response.json()["state"] != "compliant_stream"): + return test.FAIL("The state should be compliant_stream") + + valid, response = TestHelper.do_request('GET', self.compat_url + "receivers/" + + receiver_id + "/outputs/") + if not valid: + return test.FAIL("Unexpected response from the streamcompatibility API: {}" + .format(response)) + if response.status_code != 200: + return test.FAIL("The streamcompatibility request for receiver {} outputs has failed: {}" + .format(receiver_id, response.json())) + self.outputs = response.json() + if not (self.is_compatible_media_type): + return test.WARNING("Please use a sender with a video or " + "an audio format as a reference in the config file.") + return test.PASS() + + def test_04_03_02_01(self, test): + """ + Verify that the status indicates that there is a signal. + """ + time.sleep(CONFIG.STABLE_STATE_DELAY) + if len(self.outputs) == 0: + return test.DISABLED("No IS-11 receiver outputs") + for output_id in self.outputs: + output_id = self.remove_last_slash(output_id) + valid, response = TestHelper.do_request('GET', self.compat_url + "outputs/" + output_id + "/properties/") + if not valid: + return test.FAIL("Unexpected response from the streamcompatibility API: {}".format(response)) + if response.status_code != 200: + return test.FAIL("The streamcompatibility request for output {} properties has failed: {}" + .format(output_id, response.json())) + if not (CONFIG.IS11_REFERENCE_SENDER_CONNECTION_API): + return test.DISABLED("Please configure IS11_REFERENCE_SENDER_CONNECTION_API in Config.py") + if (response.json()["status"]["state"] != "signal_present"): + return test.FAIL("The state: {} should be signal_present".format(response.json()["status"]["state"])) + if len(self.reference_senders) == 0: + return test.DISABLED("No IS-11 reference sender") + for sender in self.reference_senders: + new_netloc = "{}:{}".format(CONFIG.IS11_REFERENCE_SENDER_CONNECTION_API, + CONFIG.IS11_REFERENCE_SENDER_CONNECTION_API_PORT) + url = self.conn_url.replace(urlparse(self.conn_url).netloc, new_netloc) + json_data = { + "master_enable": False, + "activation": {"mode": "activate_immediate"} + } + headers = {"Content-Type": "application/json"} + valid_patch, response = TestHelper.do_request('PATCH', url + "single/senders/" + + sender["id"] + "/staged/", + json=json_data, headers=headers) + if not valid_patch: + return test.FAIL("Unexpected response from the Connection API: {}".format(response)) + if response.status_code != 200: + return test.FAIL("The patch request to {} has failed: {}" + .format(url + "single/senders/" + sender["id"] + "/staged/", response.json())) + time.sleep(CONFIG.STABLE_STATE_DELAY) + return test.PASS() + + def test_04_04(self, test): + """ + Verify receivers not supporting outputs + """ + if len(self.receivers) == 0: + return test.UNCLEAR("No IS-11 receivers") + if len(self.receivers_without_outputs) == 0: + return test.UNCLEAR("All IS-11 receivers support outputs") + return test.PASS() + + def test_04_04_01(self, test): + """ + Verify the status of the Receiver. + The test requires streaming from a Sender + in order to verify the state of the Receiver. + """ + if len(self.receivers) == 0: + return test.UNCLEAR("No IS-11 receivers") + if not (CONFIG.IS11_REFERENCE_SENDER_CONNECTION_API): + return test.DISABLED("Please configure IS11_REFERENCE_SENDER_CONNECTION_API in Config.py") + if len(self.reference_senders) == 0: + return test.DISABLED("No IS-11 reference sender") + for sender in self.reference_senders: + new_netloc = "{}:{}".format(CONFIG.IS11_REFERENCE_SENDER_CONNECTION_API, + CONFIG.IS11_REFERENCE_SENDER_CONNECTION_API_PORT) + url = self.conn_url.replace(urlparse(self.conn_url).netloc, new_netloc) + + sdp_transport_file = self.is11_utils.get_transportfile(url, sender["id"]) + if sdp_transport_file is None: + continue + + json_data = { + "master_enable": True, + "activation": {"mode": "activate_immediate"} + } + headers = {"Content-Type": "application/json"} + valid_patch, response = TestHelper.do_request('PATCH', url + "single/senders/" + + sender["id"] + "/staged/", json=json_data, headers=headers) + if not valid_patch: + return test.FAIL("Unexpected response from the connection API: {}".format(response)) + if response.status_code != 200: + return test.FAIL("The patch request to {} has failed: {}" + .format(url + "single/senders/" + sender["id"] + "/staged/", response.json())) + time.sleep(CONFIG.STABLE_STATE_DELAY) + sdp_transport_file = self.is11_utils.get_transportfile(url, sender["id"]) + time.sleep(CONFIG.STABLE_STATE_DELAY) + if len(sdp_transport_file.strip()) == 0: + return test.FAIL("The IS-11 reference sender {} transport file is empty".format(sender["id"])) + if len(self.receivers_without_outputs) == 0: + return test.UNCLEAR("All IS-11 receivers support outputs") + for receiver_id in self.receivers_without_outputs: + patchload = { + "sender_id": sender["id"], + "master_enable": True, + "activation": {"mode": "activate_immediate"}, + "transport_file": {"type": "application/sdp", + "data": "{}".format(sdp_transport_file)} + } + + valid_patch, response = TestHelper.do_request('PATCH', self.conn_url + "single/receivers/" + + receiver_id + "staged/", json=patchload, headers=headers) + if not valid_patch: + return test.FAIL("Unexpected response from the Connection API: {}".format(response)) + if response.status_code != 200: + return test.FAIL("The patch request{} to has failed: {}" + .format(self.conn_url + "single/receivers/" + + receiver_id + "staged/", response.json())) + time.sleep(CONFIG.STABLE_STATE_DELAY) + valid, response = TestHelper.do_request('GET', self.conn_url + "single/receivers/" + + receiver_id + "active/") + if not valid: + return test.FAIL("Unexpected response from the Connection API: {}".format(response)) + if response.status_code != 200: + return test.FAIL("The connection request for receiver {} has failed: {}" + .format(receiver_id, response.json())) + + if (response.json()["master_enable"] is not True): + time.sleep(CONFIG.STABLE_STATE_DELAY) + valid, response = TestHelper.do_request('GET', self.conn_url + "single/receivers/" + + receiver_id + "active/") + if not valid: + return test.FAIL("Unexpected response from the Connnection API: {}".format(response)) + if response.status_code != 200: + return test.FAIL("The connection request for receiver {} has failed: {}" + .format(receiver_id, response.json())) + + valid, response = TestHelper.do_request('GET', self.compat_url + "receivers/" + receiver_id + "status/") + if not valid: + return test.FAIL("Unexpected response from the streamcompatibility API: {}".format(response)) + if response.status_code != 200: + return test.FAIL("The streamcompatibility request for receiver {} status has failed: {}" + .format(receiver_id, response.json())) + if (response.json()["state"] != "compliant_stream"): + return test.FAIL("The state should be compliant_stream") + + json_data = { + "master_enable": False, + "activation": {"mode": "activate_immediate"} + } + headers = {"Content-Type": "application/json"} + valid_patch, response = TestHelper.do_request('PATCH', url + "single/senders/" + + sender["id"] + "/staged/", json=json_data, headers=headers) + if not valid_patch: + return test.FAIL("Unexpected response from the Connection API: {}".format(response)) + if response.status_code != 200: + return test.FAIL("The patch to {} has failed: {}" + .format(url + "single/senders/" + sender["id"] + "/staged/", response.json())) + time.sleep(CONFIG.STABLE_STATE_DELAY) return test.PASS() def test_06_01(self, test):