From 2e1c6619e9a286462da9e5aeb5b4297ec78e93df Mon Sep 17 00:00:00 2001 From: ggeorgea Date: Sun, 19 Feb 2023 21:05:13 -0500 Subject: [PATCH 01/11] Initial submit for tests general (0), Senders (2), Outputs (3), Receivers (4) Based on IS-11 Test Plan v0.6 Senders contains tests 2.0, 2.1 and 2.2 Receivers contains tests 4.1 and 4.2 Signed-off-by: ggeorgea --- nmostesting/suites/IS1101Test.py | 332 ++++++- nmostesting/suites/IS1102Test.py | 1392 +++++++++++++++++++++++++++++- 2 files changed, 1720 insertions(+), 4 deletions(-) diff --git a/nmostesting/suites/IS1101Test.py b/nmostesting/suites/IS1101Test.py index 14277a0c..637d3149 100644 --- a/nmostesting/suites/IS1101Test.py +++ b/nmostesting/suites/IS1101Test.py @@ -13,14 +13,344 @@ # limitations under the License. from ..GenericTest import GenericTest +from .. import TestHelper COMPAT_API_KEY = "streamcompatibility" +CONTROLS = "controls" class IS1101Test(GenericTest): """ Runs Node Tests covering IS-11 """ + def __init__(self, apis): - GenericTest.__init__(self, apis) + # Don't auto-test paths responding with an EDID binary as they don't have a JSON Schema + omit_paths = [ + "/inputs/{inputId}/edid", + "/inputs/{inputId}/edid/base", + "/inputs/{inputId}/edid/effective", + "/outputs/{outputId}/edid", + ] + GenericTest.__init__(self, apis, omit_paths) self.compat_url = self.apis[COMPAT_API_KEY]["url"] + self.base_url = self.apis[COMPAT_API_KEY]["base_url"] + self.connected_outputs = [] + self.edid_connected_outputs = [] + self.not_edid_connected_outputs = [] + self.outputs = [] + self.active_connected_outputs = [] + self.receivers = "" + self.receivers_outputs = "" + self.caps = "" + + # GENERAL TESTS + def test_00_01(self, test): + """Verify that IS-11 is exposed in the Node API as \ + urn:x-nmos:control:stream-compat/v1.0 at url /x-nmos/streamcompatibility/v1.0/ + """ + valid_res, response = TestHelper.do_request( + "GET", self.base_url + "/x-nmos/node/v1.3/devices/" + ) + if valid_res: + response_json = response.json() + controls = response_json[0][CONTROLS] + control_href = "" + for control in controls: + if control["type"] == "urn:x-nmos:control:stream-compat/v1.0": + control_href = control["href"] + break + if len(control) == 0: + return test.WARNING("IS-11 API is not available") + if not control_href.endswith(self.compat_url): + return test.FAIL("IS-11 URL is invalid") + return test.PASS() + return test.FAIL(response) + + def test_00_02(self, test): + "Put all senders into inactive state" + senders_url = self.base_url + "/x-nmos/connection/v1.1/single/senders/" + _, response = TestHelper.do_request("GET", senders_url) + if response.status_code != 200: + return test.FAIL(response.json()) + senders = response.json() + if len(senders) > 0: + for sender in senders: + url = senders_url + sender + "staged/" + deactivate_json = { + "master_enable": False, + "activation": {"mode": "activate_immediate"}, + } + + _, response = TestHelper.do_request("PATCH", url, json=deactivate_json) + if ( + response.status_code != 200 + or response.json()["master_enable"] + or response.json()["activation"]["mode"] != "activate_immediate" + ): + return test.FAIL(response.json()) + return test.PASS() + return test.UNCLEAR("Could not find any IS-04 senders to test") + + def test_00_03(self, test): + "Put all the receivers into inactive state" + receivers_url = self.base_url + "/x-nmos/connection/v1.1/single/receivers/" + _, response = TestHelper.do_request("GET", receivers_url) + if response.status_code != 200: + return test.FAIL(response.json()) + receivers = response.json() + if len(receivers) > 0: + for receiver in receivers: + url = receivers_url + receiver + "staged/" + deactivate_json = { + "master_enable": False, + "activation": {"mode": "activate_immediate"}, + } + _, response = TestHelper.do_request("PATCH", url, json=deactivate_json) + if ( + response.status_code != 200 + or response.json()["master_enable"] + or response.json()["activation"]["mode"] != "activate_immediate" + ): + return test.FAIL(response.json()) + + return test.PASS() + + return test.UNCLEAR("Could not find any IS-04 receivers to test") + + # OUTPUTS TESTS + def test_03_01(self, test): + """ + Verify that the device supports the concept of Output. + """ + _, response = TestHelper.do_request("GET", self.compat_url + "outputs/") + + if response.status_code == 200: + if len(response.json()) != 0: + self.outputs.append(response.json()[0]) + if len(self.outputs) == 0: + return test.UNCLEAR("No outputs") + return test.PASS() + return test.FAIL(response.json()) + + def test_03_02(self, test): + """ + Verify that some of the outputs of the device are connected. + """ + if len(self.outputs) == 0: + return test.UNCLEAR("No IS11 receivers outputs") + for output in self.outputs: + _, response = TestHelper.do_request( + "GET", self.compat_url + "outputs/" + output + "properties/" + ) + if response.status_code == 200: + outputs_properties_json = [] + outputs_properties_json.append(response.json()) + for output in outputs_properties_json: + if output["connected"]: + self.connected_outputs.append(output["id"]) + else: + return test.FAIL(response.json()) + if len(self.connected_outputs) == 0: + return test.UNCLEAR("None Outputs support edid.") + return test.PASS() + + def test_03_03(self, test): + """ + Verify that all connected outputs do not have + a signal as test 0 put all of the receivers inactive. + """ + if len(self.connected_outputs) == 0: + return test.UNCLEAR("no connected outputs") + for output_id in self.connected_outputs: + _, response = TestHelper.do_request( + "GET", self.compat_url + "outputs/" + output_id + "/properties/" + ) + if response.status_code == 200: + if response.json()["status"]["state"] == "signal_present": + self.active_connected_outputs.append(response.json()) + else: + return test.FAIL(response.json()) + if len(self.active_connected_outputs) != 0: + return test.UNCLEAR( + "Connected output have a signal while all receivers are inactive" + ) + return test.PASS() + + def test_03_04(self, test): + """ + Verify that connected outputs supporting EDID behave according to the RAML file. + """ + if len(self.connected_outputs) == 0: + return test.UNCLEAR("no connected outputs") + for output_id in self.connected_outputs: + _, response = TestHelper.do_request( + "GET", self.compat_url + "outputs/" + output_id + "/properties/" + ) + if response.status_code == 200: + if response.json()["edid_support"]: + self.edid_connected_outputs.append(response.json()["id"]) + else: + return test.FAIL(response.json()) + if self.edid_connected_outputs == 0: + return test.UNCLEAR("Outputs not supporting edid") + return test.PASS() + + def test_03_04_01(self, test): + """ + Verify that an output indicating EDID support behaves according to the RAML file. + """ + if len(self.edid_connected_outputs) == 0: + return test.UNCLEAR("no edid connected outputs") + for output_id in self.edid_connected_outputs: + _, response = TestHelper.do_request( + "GET", self.compat_url + "outputs/" + output_id + ) + if response.status_code != 200: + return test.FAIL(response.json()) + return test.PASS() + + def test_03_04_02(self, test): + """ + Verify that a valid EDID can be retrieved from the device; + this EDID represents the default EDID of the device. + """ + is_valid_response = True + if len(self.edid_connected_outputs) == 0: + return test.UNCLEAR("no edid connected outputs") + for output_id in self.edid_connected_outputs: + _, response = TestHelper.do_request( + "GET", self.compat_url + "outputs/" + output_id + "/edid/" + ) + if ( + response.status_code != 200 + and response.headers["Content-Type"] != "application/octet-stream" + ): + is_valid_response = False + break + if is_valid_response: + return test.PASS() + return test.FAIL(response.json()) + + def test_03_05(self, test): + """ + Verify that connected outputs not supporting EDID behave according to the RAML file. + """ + if len(self.connected_outputs) == 0: + return test.UNCLEAR("no connected outputs") + for output_id in self.connected_outputs: + _, response = TestHelper.do_request( + "GET", self.compat_url + "outputs/" + output_id + "/properties/" + ) + if response.status_code == 200: + if not response.json()["edid_support"]: + self.not_edid_connected_outputs.append(response.json()["id"]) + else: + return test.FAIL(response.json()) + if len(self.not_edid_connected_outputs) == 0: + return test.UNCLEAR("Outputs supporting edid") + return test.PASS() + + def test_03_05_01(self, test): + """ + Verify that there is no EDID support. + """ + if len(self.not_edid_connected_outputs) == 0: + return test.UNCLEAR("none of not edid connected outputs") + for output_id in self.not_edid_connected_outputs: + _, response = TestHelper.do_request( + "GET", self.compat_url + "outputs/" + output_id + "/edid/" + ) + if response.status_code != 204: + return test.UNCLEAR("status code should be 204") + return test.PASS() + + # RECEIVERS TESTS + def test_04_01(self, test): + """ + Verify that the device supports the concept of IS-11 Receiver. + """ + _, response = TestHelper.do_request("GET", self.compat_url + "receivers/") + + if response.status_code != 200: + return test.FAIL(response.json()) + self.receivers = response.json() + return ( + test.PASS() + if len(self.receivers) != 0 + else test.UNCLEAR("No IS_11 receivers") + ) + + def test_04_01_01(self, test): + """ + Verify that IS-11 Receivers exist on the Node API as Receivers. + """ + for receiver_id in self.receivers: + _, response = TestHelper.do_request( + "GET", self.base_url + "/x-nmos/node/v1.3/receivers/" + receiver_id + ) + if response.status_code != 200: + return test.FAIL(response.json()) + if response.json()["id"] != receiver_id[:-1]: + return test.UNCLEAR( + "The IS-11 Receiver doesn't exist on the Node API as receiver" + ) + return ( + test.PASS() + if len(self.receivers) != 0 + else test.UNCLEAR("No IS_11 receivers") + ) + + def test_04_02(self, test): + """ + Verify receivers (generic with/without outputs) + """ + _, response = TestHelper.do_request("GET", self.compat_url + "receivers/") + if response.status_code != 200: + return test.FAIL(response.json()) + self.receivers = response.json() + return ( + test.PASS() + if len(self.receivers) != 0 + else test.UNCLEAR("No IS_11 receivers") + ) + + def test_04_02_01(self, test): + """ + Verify that the status is "unknown" or "non_compliant_stream" + as per our pre-conditions of not being master_enabled. + """ + for receiver_id in self.receivers: + _, response = TestHelper.do_request( + "GET", self.compat_url + "receivers/" + receiver_id + "status/" + ) + if response.status_code != 200: + return test.FAIL(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.PASS() + if len(self.receivers) != 0 + else test.UNCLEAR("No IS_11 receivers") + ) + + def test_04_02_02(self, test): + """ + Verify that the Receiver supports Receiver Capabilities. + """ + for receiver_id in self.receivers: + _, response = TestHelper.do_request( + "GET", self.base_url + "/x-nmos/node/v1.3/receivers/" + receiver_id + ) + if response.status_code != 200: + return test.FAIL(response.json()) + self.caps = response.json()["caps"] + 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.UNCLEAR(" The receiver does not support BCP-004-01") + return ( + test.PASS() + if len(self.receivers) != 0 + else test.UNCLEAR("No IS_11 receivers") + ) diff --git a/nmostesting/suites/IS1102Test.py b/nmostesting/suites/IS1102Test.py index e0919fe8..e51a0fb7 100644 --- a/nmostesting/suites/IS1102Test.py +++ b/nmostesting/suites/IS1102Test.py @@ -13,16 +13,1402 @@ # limitations under the License. from ..GenericTest import GenericTest +from .. import TestHelper +import time +import re -NODE_API_KEY = "node" COMPAT_API_KEY = "streamcompatibility" +NODE_API_KEY = "node" +CONTROLS = "controls" + +REF_SUPPORTED_CONSTRAINTS_VIDEO = [ + "urn:x-nmos:cap:meta:label", + "urn:x-nmos:cap:meta:preference", + "urn:x-nmos:cap:meta:enabled", + "urn:x-nmos:cap:format:media_type", + "urn:x-nmos:cap:format:grain_rate", + "urn:x-nmos:cap:format:frame_width", + "urn:x-nmos:cap:format:frame_height", + "urn:x-nmos:cap:format:interlace_mode", + "urn:x-nmos:cap:format:color_sampling", + "urn:x-nmos:cap:format:component_depth", +] +REF_SUPPORTED_CONSTRAINTS_AUDIO = [ + "urn:x-nmos:cap:meta:label", + "urn:x-nmos:cap:meta:preference", + "urn:x-nmos:cap:meta:enabled", + "urn:x-nmos:cap:format:media_type", + "urn:x-nmos:cap:format:channel_count", + "urn:x-nmos:cap:format:sample_rate", + "urn:x-nmos:cap:format:sample_depth", +] class IS1102Test(GenericTest): """ - Runs Node Tests covering both IS-04 and IS-11 + Runs Node Tests covering IS-11 """ def __init__(self, apis): - GenericTest.__init__(self, apis,) + # Don't auto-test paths responding with an EDID binary as they don't have a JSON Schema + omit_paths = [ + "/inputs/{inputId}/edid", + "/inputs/{inputId}/edid/base", + "/inputs/{inputId}/edid/effective", + "/outputs/{outputId}/edid" + ] + GenericTest.__init__(self, apis, omit_paths) self.node_url = self.apis[NODE_API_KEY]["url"] self.compat_url = self.apis[COMPAT_API_KEY]["url"] + self.base_url = self.apis[COMPAT_API_KEY]["base_url"] + self.senders = "" + self.senders_2 = "" + self.flow_format = {} + self.flow_format_audio = [] + self.flow_format_video = [] + self.flow_width = {} + self.flow_height = {} + self.flow_grain_rate = {} + self.flow_sample_rate = {} + self.version = {} + self.grain_rate_constraints = {} + self.empty_constraints = {} + self.sample_rate_constraints = {} + self.constraints = {} + + # SENDERS TESTS + """ + Runs Node Tests covering IS-11 for Senders + """ + + def compare_complex(self, response_constraints, sample_rate_constraints): + + response_constraints_enum = response_constraints["constraint_sets"][0][ + "urn:x-nmos:cap:format:sample_rate" + ]["enum"] + sample_rate_constraints_enum = sample_rate_constraints["constraint_sets"][0][ + "urn:x-nmos:cap:format:sample_rate" + ]["enum"] + + if len(response_constraints_enum) > 0 and len(sample_rate_constraints_enum) > 0: + if ( + "numerator" in response_constraints_enum[0] + and "numerator" in sample_rate_constraints_enum[0] + and "denominator" in response_constraints_enum[0] + and "denominator" in sample_rate_constraints_enum[0] + ): + return ( + response_constraints_enum[0]["numerator"] + == sample_rate_constraints_enum[0]["numerator"] + and response_constraints_enum[0]["denominator"] + == sample_rate_constraints_enum[0]["denominator"] + ) + + if ( + "numerator" in response_constraints_enum[0] + and "numerator" in sample_rate_constraints_enum[0] + and "denominator" in response_constraints_enum[0] + and "denominator" not in sample_rate_constraints_enum[0] + ): + return ( + response_constraints_enum[0]["numerator"] + == sample_rate_constraints_enum[0]["numerator"] + and response_constraints_enum[0]["denominator"] == 1 + ) + + if ( + "numerator" in response_constraints_enum[0] + and "numerator" in sample_rate_constraints_enum[0] + and "denominator" not in response_constraints_enum[0] + and "denominator" in sample_rate_constraints_enum[0] + ): + return ( + response_constraints_enum[0]["numerator"] + == sample_rate_constraints_enum[0]["numerator"] + and 1 == sample_rate_constraints_enum[0]["denominator"] + ) + return False + + def getSdpColorSampling(self, flow_components): + """ + getColorSamplingFromComponents supports RGB, + YCbCr-4:4:4, YCbCr-4:2:2, YCbCr-4:2:0 and assumes + that the bit-depth is compliant without verifying it. + """ + names = [] + widths = [] + heights = [] + + if len(flow_components) != 3: + return "invalid array of video components" + + for i in range(0, 3): + if "name" in flow_components[i]: + names.append( + {"name" + str(i): flow_components[i]["name"], "err" + str(i): None} + ) + else: + names.append({"name" + str(i): None, "err" + str(i): "not defined"}) + + if ( + names[0]["err0"] is None + and names[0]["name0"] == "R" + and names[1]["err1"] is None + and names[1]["name1"] == "G" + and names[2]["err2"] is None + and names[2]["name2"] == "B" + ): + for i in range(0, 3): + if "width" in flow_components[i]: + widths.append( + { + "width" + str(i): flow_components[i]["width"], + "err" + str(i): None, + } + ) + else: + widths.append( + {"width" + str(i): None, "err" + str(i): "not defined"} + ) + + if ( + widths[0]["err0"] is not None + or widths[1]["err1"] is not None + or widths[2]["err2"] is not None + ): + return "invalid array of video components" + + for i in range(0, 3): + if "height" in flow_components[i]: + heights.append( + { + "height" + str(i): flow_components[i]["height"], + "err" + str(i): None, + } + ) + else: + heights.append( + {"height" + str(i): None, "err" + str(i): "not defined"} + ) + + if ( + heights[0]["err0"] is not None + or heights[1]["err1"] is not None + or heights[2]["err2"] is not None + ): + return "invalid array of video components" + + if ( + widths[0]["width0"] == widths[1]["width1"] + and widths[0]["width0"] == widths[2]["width2"] + and heights[0]["height0"] == heights[1]["height1"] + and heights[0]["height0"] == heights[2]["height2"] + ): + return "RGB" + + if ( + names[0]["err0"] is None + and names[0]["name0"] == "Y" + and names[1]["err1"] is None + and names[1]["name1"] == "Cb" + and names[2]["err2"] is None + and names[2]["name2"] == "Cr" + ): + + for i in range(0, 3): + if "width" in flow_components[i]: + widths.append( + { + "width" + str(i): flow_components[i]["width"], + "err" + str(i): None, + } + ) + else: + widths.append( + {"width" + str(i): None, "err" + str(i): "not defined"} + ) + + if ( + widths[0]["err0"] is not None + or widths[1]["err1"] is not None + or widths[2]["err2"] is not None + ): + return "invalid array of video components" + + for i in range(0, 3): + if "height" in flow_components[i]: + heights.append( + { + "height" + str(i): flow_components[i]["height"], + "err" + str(i): None, + } + ) + else: + heights.append( + {"height" + str(i): None, "err" + str(i): "not defined"} + ) + + if ( + heights[0]["err0"] is not None + or heights[1]["err1"] is not None + or heights[2]["err2"] is not None + ): + return "invalid array of video components" + + if ( + widths[0]["width0"] == widths[1]["width1"] + and widths[0]["width0"] == widths[2]["width2"] + and heights[0]["height0"] == heights[1]["height1"] + and heights[0]["height0"] == heights[2]["height2"] + ): + return "YCbCr-4:4:4" + + if ( + widths[0]["width0"] == 2 * widths[1]["width1"] + and widths[0]["width0"] == 2 * widths[2]["width2"] + and heights[0]["height0"] == heights[1]["height1"] + and heights[0]["height0"] == heights[2]["height2"] + ): + return "YCbCr-4:2:2" + + if ( + widths[0]["width0"] == 2 * widths[1]["width1"] + and widths[0]["width0"] == 2 * widths[2]["width2"] + and heights[0]["height0"] == 2 * heights[1]["height1"] + and heights[0]["height0"] == 2 * heights[2]["height2"] + ): + return "YCbCr-4:2:0" + + return "invalid array of video components" + + def get_another_grain_rate(self, grain_rate): + numerator = grain_rate["numerator"] + denominator = grain_rate["denominator"] + if (numerator == 30 or numerator == 25) and denominator == 1: + return {"numerator": numerator * 2, "denominator": 1} + if (numerator == 60 or numerator == 50) and denominator == 1: + return {"numerator": numerator / 2, "denominator": 1} + if numerator == 24 and denominator == 1: + return {"numerator": 30, "denominator": 1} + if (numerator == 30000 or numerator == 25000) and denominator == 1001: + return {"numerator": numerator * 2, "denominator": 1001} + if (numerator == 60000 or numerator == 50000) and denominator == 1001: + return {"numerator": numerator / 2, "denominator": 1001} + return "grain_rate not valid" + + def get_another_sample_rate(self, sample_rate): + numerator = sample_rate["numerator"] + if numerator == 48000: + return {"numerator": 44100} + if numerator == 44100: + return {"numerator": 48000} + if numerator == 96000: + return {"numerator": 4800} + if numerator == 88200: + return {"numerator": 44100} + return "sample_rate not valid" + + def test_02_00(self, test): + "Reset active constraints of all senders" + _, response = TestHelper.do_request("GET", self.compat_url + "senders/") + if response.status_code == 200: + self.senders = response.json() + for sender in self.senders: + _, response = TestHelper.do_request( + "DELETE", + self.compat_url + "senders/" + sender + "constraints/active/", + ) + if response.status_code != 200: + return test.FAIL("senders constraints cannot be deleted") + return test.PASS() + + return test.FAIL(response.json()) + + def test_02_01(self, test): + "Verify that the device supports the concept of IS-11 Sender" + _, response = TestHelper.do_request("GET", self.compat_url + "senders/") + if response.status_code != 200: + return test.FAIL(response.json) + self.senders = response.json() + if len(self.senders) == 0: + return test.UNCLEAR("there is no IS-11 senders") + return test.PASS() + + def test_02_01_01(self, test): + "Verify that the device supports the concept of IS-11 Sender" + if len(self.senders) != 0: + for sender_id in self.senders: + _, response = TestHelper.do_request( + "GET", self.node_url + "senders/" + sender_id + ) + if response.status_code != 200: + return test.FAIL() + sender_node = response.json()["id"] + if sender_id[:-1] != sender_node: + return test.FAIL("") + return test.PASS() + return test.UNCLEAR("there is no IS-11 senders") + + def test_02_02(self, test): + "Verify senders (generic with/without inputs)" + _, response = TestHelper.do_request("GET", self.compat_url + "senders/") + if response.status_code != 200: + return test.FAIL(response.json()) + self.senders_2 = response.json() + return test.PASS() + + def test_02_02_01(self, test): + "Verify that the status is unconstrained as per our pre-conditions" + if len(self.senders_2) != 0: + for sender_id in self.senders_2: + _, response = TestHelper.do_request( + "GET", self.compat_url + "senders/" + sender_id + "status/" + ) + if response.status_code != 200: + return test.FAIL(response.json()) + state = response.json()["state"] + if state in ["awating_essence", "no_essence"]: + for i in range(0, 5): + _, response = TestHelper.do_request( + "GET", self.compat_url + "senders/" + sender_id + "status/" + ) + state = response.json()["status"]["state"] + if state in ["awating_essence", "no_essence"]: + time.sleep(3000) + else: + break + if state != "unconstrained": + return test.FAIL("inputs are unstable.") + return test.PASS() + return test.UNCLEAR("there is no IS-11 senders") + + def test_02_02_03(self, test): + """ + Verify that the sender is available in the node API, + has an associated flow and is inactive + """ + if len(self.senders_2) != 0: + for sender_id in self.senders_2: + _, response = TestHelper.do_request( + "GET", self.node_url + "senders/" + sender_id + ) + if response.status_code != 200: + return test.FAIL(response.json) + sender_node = response.json()["id"] + if sender_id[:-1] != sender_node: + return test.FAIL("") + sender_flow_id = response.json()["flow_id"] + if sender_flow_id is None: + return test.FAIL("the sender must have a flow") + sender_subscription_active = response.json()["subscription"]["active"] + if sender_subscription_active: + return test.FAIL("the sender must be inactive") + _, response = TestHelper.do_request( + "GET", self.node_url + "flows/" + sender_flow_id + ) + if response.status_code != 200: + return test.FAIL(response.json()) + flow_format = response.json()["format"] + self.flow_format[sender_id] = flow_format + if flow_format == "urn:x-nmos:format:video": + self.flow_format_video.append(sender_id) + flow_frame_width = response.json()["frame_width"] + self.flow_width[sender_id] = flow_frame_width + flow_frame_height = response.json()["frame_height"] + self.flow_height[sender_id] = flow_frame_height + flow_grain_rate = response.json()["grain_rate"] + self.flow_grain_rate[sender_id] = flow_grain_rate + if flow_format == "urn:x-nmos:format:audio": + self.flow_format_audio.append(sender_id) + flow_sample_rate = response.json()["sample_rate"] + self.flow_sample_rate[sender_id] = flow_sample_rate + if ( + flow_format != "urn:x-nmos:format:video" + and flow_format != "urn:x-nmos:format:audio" + ): + print("only audio and video senders are tested at this time.") + return test.PASS() + return test.UNCLEAR("there is no IS-11 senders") + + def test_02_02_03_01(self, test): + "Verify that the video sender supports the minimum set of video constraints" + + pattern = "^urn:x-nmos:cap:" + + if len(self.flow_format_video) == 0: + return test.UNCLEAR("There is no video format") + + for sender_id in self.flow_format_video: + _, response = TestHelper.do_request( + "GET", + self.compat_url + "senders/" + sender_id + "constraints/supported/ ", + ) + if response.status_code != 200: + return test.FAIL(response.json()) + supportedConstraints = response.json()["parameter_constraints"] + for item in supportedConstraints: + if not re.search(pattern, item): + return test.FAIL("only x-nmos:cap constraints are allowed") + for item in REF_SUPPORTED_CONSTRAINTS_VIDEO: + if item not in supportedConstraints: + return test.FAIL(item + " is not in supportedConstraints ") + return test.PASS() + + def test_02_02_03_02(self, test): + "Verify that the video sender supports the minimum set of video constraints" + + pattern = "^urn:x-nmos:cap:" + + if len(self.flow_format_audio) == 0: + return test.UNCLEAR("There is no video format") + + for sender_id in self.flow_format_audio: + _, response = TestHelper.do_request( + "GET", + self.compat_url + "senders/" + sender_id + "constraints/supported/ ", + ) + if response.status_code != 200: + return test.FAIL(response.json()) + supportedConstraints = response.json()["parameter_constraints"] + for item in supportedConstraints: + if not re.search(pattern, item): + return test.FAIL("only x-nmos:cap constraints are allowed") + for item in REF_SUPPORTED_CONSTRAINTS_AUDIO: + if item not in supportedConstraints: + return test.FAIL(item + "is not in supportedConstraints") + return test.PASS() + + def test_02_02_04_01(self, test): + """ + Verify that changing the constraints of an + IS-11 sender(video) changes the version of + the associated IS-04 sender. + """ + if len(self.flow_format_video) == 0: + return test.UNCLEAR("There is no video format") + + for sender_id in self.flow_format_video: + _, response = TestHelper.do_request( + "GET", self.node_url + "senders/" + sender_id + ) + if response.status_code != 200: + return test.FAIL(response.json()) + version = response.json()["version"] + self.version[sender_id] = version + self.grain_rate_constraints[sender_id] = { + "constraint_sets": [ + { + "urn:x-nmos:cap:format:grain_rate": { + "enum": [self.flow_grain_rate[sender_id]] + } + } + ] + } + self.empty_constraints[sender_id] = {"constraint_sets": []} + _, response = TestHelper.do_request( + "PUT", + self.compat_url + "senders/" + sender_id + "constraints/active/", + json=self.grain_rate_constraints[sender_id], + ) + if response.status_code != 200: + return test.FAIL(response.json()) + _, response = TestHelper.do_request( + "GET", self.node_url + "senders/" + sender_id + ) + if response.status_code != 200: + return test.FAIL(response.json()) + version = response.json()["version"] + if version == self.version[sender_id]: + return test.FAIL() + self.version[sender_id] = version + _, response = TestHelper.do_request( + "GET", self.compat_url + "senders/" + sender_id + "constraints/active/ " + ) + if response.status_code != 200: + return test.FAIL(response.json()) + constraints = response.json() + if constraints != self.grain_rate_constraints[sender_id]: + return test.FAIL() + _, response = TestHelper.do_request( + "DELETE", + self.compat_url + "senders/" + sender_id + "constraints/active/", + ) + if response.status_code != 200: + return test.FAIL(response.json()) + _, response = TestHelper.do_request( + "GET", self.node_url + "senders/" + sender_id + ) + if response.status_code != 200: + return test.FAIL(response.json()) + version = response.json()["version"] + if version == self.version[sender_id]: + return test.FAIL() + self.version[sender_id] = version + _, response = TestHelper.do_request( + "GET", self.compat_url + "senders/" + sender_id + "constraints/active/" + ) + if response.status_code != 200: + return test.FAIL(response.json()) + constraints = response.json() + if constraints != self.empty_constraints[sender_id]: + return test.FAIL("Constraints doesn't match") + return test.PASS() + + def test_02_02_04_02(self, test): + """ + Verify that changing the constraints of an IS-11 + sender(audio) changes the version of the associated IS-04 sender. + """ + if len(self.flow_format_audio) == 0: + return test.UNCLEAR("There is no audio format") + for sender_id in self.flow_format_audio: + _, response = TestHelper.do_request( + "GET", self.node_url + "senders/" + sender_id + ) + if response.status_code != 200: + return test.FAIL(response.json()) + version = response.json()["version"] + self.version[sender_id] = version + self.sample_rate_constraints[sender_id] = { + "constraint_sets": [ + { + "urn:x-nmos:cap:format:sample_rate": { + "enum": [self.flow_sample_rate[sender_id]] + } + } + ] + } + self.empty_constraints[sender_id] = {"constraint_sets": []} + _, response = TestHelper.do_request( + "PUT", + self.compat_url + "senders/" + sender_id + "constraints/active/", + json=self.sample_rate_constraints[sender_id], + ) + if response.status_code != 200: + return test.FAIL(response.json()) + _, response = TestHelper.do_request( + "GET", self.node_url + "senders/" + sender_id + ) + if response.status_code != 200: + return test.FAIL(response.json()) + version = response.json()["version"] + if version == self.version[sender_id]: + return test.FAIL() + self.version[sender_id] = version + + _, response = TestHelper.do_request( + "GET", self.compat_url + "senders/" + sender_id + "constraints/active/" + ) + if response.status_code != 200: + return test.FAIL(response.json()) + constraints = response.json() + + if not self.compare_complex( + constraints, self.sample_rate_constraints[sender_id] + ): + return test.FAIL( + "constraints and SampleRateConstraints[" + + sender_id + + "] are different " + ) + + _, response = TestHelper.do_request( + "DELETE", + self.compat_url + "senders/" + sender_id + "constraints/active/", + ) + if response.status_code != 200: + return test.FAIL(response.json()) + + _, response = TestHelper.do_request( + "GET", self.node_url + "senders/" + sender_id + ) + if response.status_code != 200: + return test.FAIL(response.json()) + version = response.json()["version"] + if version == self.version[sender_id]: + return test.FAIL() + self.version[sender_id] = version + + _, response = TestHelper.do_request( + "GET", self.compat_url + "senders/" + sender_id + "constraints/active/ " + ) + if response.status_code != 200: + return test.FAIL(response.json()) + constraints = response.json() + if constraints != self.empty_constraints[sender_id]: + return test.FAIL("Constraints doesn't match") + return test.PASS() + + def test_02_02_05_01(self, test): + """Verify that setting NOP constraints for frame_width, + frame_height and grain_rate does not change the flow of + a sender (video) and that the state goes from \"unconstrained\" + to \"constrained\" + """ + if len(self.flow_format_video) == 0: + return test.UNCLEAR("There is no audio format ") + + for sender_id in self.flow_format_video: + _, response = TestHelper.do_request( + "GET", self.compat_url + "senders/" + sender_id + "status/" + ) + if response.status_code != 200: + return test.FAIL(response.json()) + state = response.json()["state"] + if state in ["awating_essence", "no_essence"]: + for i in range(0, 5): + _, response = TestHelper.do_request( + "GET", self.compat_url + "senders/" + sender_id + "status/" + ) + state = response.json()["status"]["state"] + if state in ["awating_essence", "no_essence"]: + time.sleep(3000) + else: + break + if state != "unconstrained": + return test.FAIL("inputs are unstable.") + + self.constraints[sender_id] = { + "constraint_sets": [ + { + "urn:x-nmos:cap:format:grain_rate": { + "enum": [self.flow_grain_rate[sender_id]] + } + }, + { + "urn:x-nmos:cap:format:frame_width": { + "enum": [self.flow_width[sender_id]] + } + }, + { + "urn:x-nmos:cap:format:frame_height": { + "enum": [self.flow_height[sender_id]] + } + }, + ] + } + + _, response = TestHelper.do_request( + "PUT", + self.compat_url + "senders/" + sender_id + "constraints/active/", + json=self.constraints[sender_id], + ) + if response.status_code != 200: + return test.FAIL(response.json()) + + _, response = TestHelper.do_request( + "GET", self.compat_url + "senders/" + sender_id + "status/" + ) + if response.status_code != 200: + return test.FAIL(response.json()) + state = response.json()["state"] + + if state in ["awating_essence", "no_essence"]: + for i in range(0, 5): + _, response = TestHelper.do_request( + "GET", self.compat_url + "senders/" + sender_id + "status/" + ) + state = response.json()["status"]["state"] + if state in ["awating_essence", "no_essence"]: + time.sleep(3000) + else: + break + if state != "constrained": + return test.FAIL("inputs are unstable.") + + _, response = TestHelper.do_request( + "GET", self.node_url + "senders/" + sender_id + ) + if response.status_code != 200: + return test.FAIL(response.json) + sender_flow_id = response.json()["flow_id"] + if sender_flow_id is None: + return test.FAIL("the sender must have a flow") + + _, response = TestHelper.do_request( + "GET", self.node_url + "flows/" + sender_flow_id + ) + if response.status_code != 200: + return test.FAIL(response.json()) + + 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"] + ): + return test.FAIL("different argument") + + _, response = TestHelper.do_request( + "DELETE", + self.compat_url + "senders/" + sender_id + "constraints/active/", + ) + if response.status_code != 200: + return test.FAIL(response.json()) + + return test.PASS() + + def test_02_02_05_02(self, test): + """Verify that setting NOP constraints for sample_rate does not change the flow of a sender (audio) and \ + that the state goes from \"unconstrained\" to \"constrained\"""" + + if len(self.flow_format_audio) == 0: + return test.UNCLEAR("There is no audio format ") + + for sender_id in self.flow_format_audio: + _, response = TestHelper.do_request( + "GET", self.compat_url + "senders/" + sender_id + "status/" + ) + if response.status_code != 200: + return test.FAIL(response.json()) + state = response.json()["state"] + + if state in ["awating_essence", "no_essence"]: + for i in range(0, 5): + _, response = TestHelper.do_request( + "GET", self.compat_url + "senders/" + sender_id + "status/" + ) + state = response.json()["status"]["state"] + if state in ["awating_essence", "no_essence"]: + time.sleep(3000) + else: + break + if state != "unconstrained": + return test.FAIL("inputs are unstable.") + + self.constraints[sender_id] = { + "constraint_sets": [ + { + "urn:x-nmos:cap:format:sample_rate": { + "enum": [self.flow_sample_rate[sender_id]] + } + } + ] + } + _, response = TestHelper.do_request( + "PUT", + self.compat_url + "senders/" + sender_id + "constraints/active/", + json=self.constraints[sender_id], + ) + if response.status_code != 200: + return test.FAIL(response.json()) + + _, response = TestHelper.do_request( + "GET", self.compat_url + "senders/" + sender_id + "status/" + ) + if response.status_code != 200: + return test.FAIL(response.json()) + state = response.json()["state"] + + if state in ["awating_essence", "no_essence"]: + for i in range(0, 5): + _, response = TestHelper.do_request( + "GET", self.compat_url + "senders/" + sender_id + "status/" + ) + state = response.json()["status"]["state"] + if state in ["awating_essence", "no_essence"]: + time.sleep(3000) + else: + break + if state != "constrained": + return test.FAIL("inputs are unstable.") + + _, response = TestHelper.do_request( + "GET", self.node_url + "senders/" + sender_id + ) + if response.status_code != 200: + return test.FAIL(response.json) + sender_flow_id = response.json()["flow_id"] + if sender_flow_id is None: + return test.FAIL("the sender must have a flow") + + _, response = TestHelper.do_request( + "GET", self.node_url + "flows/" + sender_flow_id + ) + if response.status_code != 200: + return test.FAIL(response.json()) + flow_sample_rate = response.json()["sample_rate"] + if self.flow_sample_rate[sender_id] != flow_sample_rate: + return test.FAIL("Different sample rate") + + _, response = TestHelper.do_request( + "DELETE", + self.compat_url + "senders/" + sender_id + "constraints/active/", + ) + if response.status_code != 200: + return test.FAIL(response.json()) + return test.PASS() + + def test_02_02_06_01(self, test): + """Verify that setting NOP constraints for supported constraints does not change the flow of a sender (video) \ + and that the state goes from \"unconstrained\" to \"constrained\"""" + if len(self.flow_format_video) == 0: + return test.UNCLEAR("There is no audio format ") + + for sender_id in self.flow_format_video: + _, response = TestHelper.do_request( + "GET", self.compat_url + "senders/" + sender_id + "status/" + ) + if response.status_code != 200: + return test.FAIL(response.json()) + state = response.json()["state"] + if state in ["awating_essence", "no_essence"]: + for i in range(0, 5): + _, response = TestHelper.do_request( + "GET", self.compat_url + "senders/" + sender_id + "status/" + ) + state = response.json()["status"]["state"] + if state in ["awating_essence", "no_essence"]: + time.sleep(3000) + else: + break + if state != "unconstrained": + return test.FAIL("inputs are unstable.") + + _, response = TestHelper.do_request( + "GET", self.node_url + "senders/" + sender_id + ) + if response.status_code != 200: + return test.FAIL(response.json) + sender = response.json() + + _, response = TestHelper.do_request( + "GET", self.node_url + "flows/" + sender["flow_id"] + ) + if response.status_code != 200: + return test.FAIL(response.json()) + flow = response.json() + color_sampling = self.getSdpColorSampling(flow["components"]) + if color_sampling == "invalid array of video components": + return test.FAIL("invalid array of video components") + constraint_set = {} + + for item in REF_SUPPORTED_CONSTRAINTS_VIDEO: + + if item == "urn:x-nmos:cap:meta:label": + constraint_set["urn:x-nmos:cap:meta:label"] = "video constraint" + if item == "urn:x-nmos:cap:meta:preference": + constraint_set["urn:x-nmos:cap:meta:preference"] = 0 + if item == "urn:x-nmos:cap:meta:enabled": + 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"]] + } + if item == "urn:x-nmos:cap:format:grain_rate": + constraint_set["urn:x-nmos:cap:format:grain_rate"] = { + "enum": [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"]] + } + if item == "urn:x-nmos:cap:format:frame_height": + constraint_set["urn:x-nmos:cap:format:frame_height"] = { + "enum": [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"]] + } + if item == "urn:x-nmos:cap:format:color_sampling": + constraint_set["urn:x-nmos:cap:format:color_sampling"] = { + "enum": [color_sampling] + } + if item == "urn:x-nmos:cap:format:component_depth": + constraint_set["urn:x-nmos:cap:format:component_depth"] = { + "enum": [flow["components"][0]["bit_depth"]] + } + self.constraints[sender_id] = {"constraint_sets": [constraint_set]} + + _, response = TestHelper.do_request( + "PUT", + self.compat_url + "senders/" + sender_id + "constraints/active/", + json=self.constraints[sender_id], + ) + if response.status_code != 200: + return test.FAIL(response.json()) + + _, response = TestHelper.do_request( + "GET", self.node_url + "flows/" + sender["flow_id"] + ) + if response.status_code != 200: + return test.FAIL(response.json()) + new_flow = response.json() + + new_color_sampling = self.getSdpColorSampling(new_flow["components"]) + if new_color_sampling == "invalid array of video components": + return test.FAIL("invalid array of video components") + + for item in REF_SUPPORTED_CONSTRAINTS_VIDEO: + + if item == "urn:x-nmos:cap:format:media_type": + if 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"]: + return test.FAIL("different grain_rate") + if item == "urn:x-nmos:cap:format:frame_width": + if 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"]: + return test.FAIL("different frame_height") + if item == "urn:x-nmos:cap:format:interlace_mode": + if 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"] + != new_flow["components"][0]["bit_depth"] + ): + return test.FAIL("different component_depth") + + _, response = TestHelper.do_request( + "DELETE", + self.compat_url + "senders/" + sender_id + "constraints/active/", + ) + if response.status_code != 200: + return test.FAIL(response.json()) + return test.PASS() + + def test_02_02_06_02(self, test): + """Verify that setting NOP constraints for supported + constraints does not change the flow of a sender (audio) + and that the state goes from \"unconstrained\" to \"constrained\" + """ + if len(self.flow_format_audio) == 0: + return test.UNCLEAR("There is no audio format") + for sender_id in self.flow_format_audio: + _, response = TestHelper.do_request( + "GET", self.compat_url + "senders/" + sender_id + "status/" + ) + if response.status_code != 200: + test.FAIL(response.json()) + state = response.json()["state"] + if state in ["awating_essence", "no_essence"]: + for i in range(0, 5): + _, response = TestHelper.do_request( + "GET", self.compat_url + "senders/" + sender_id + "status/" + ) + state = response.json()["status"]["state"] + if state in ["awating_essence", "no_essence"]: + time.sleep(3000) + else: + break + if state != "unconstrained": + return test.FAIL("inputs are unstable.") + _, response = TestHelper.do_request( + "GET", self.node_url + "senders/" + sender_id + ) + if response.status_code != 200: + return test.FAIL(response.json) + sender = response.json() + + _, response = TestHelper.do_request( + "GET", self.node_url + "flows/" + sender["flow_id"] + ) + if response.status_code != 200: + return test.FAIL(response.json()) + flow = response.json() + constraint_set = {} + + _, response = TestHelper.do_request( + "GET", self.node_url + "sources/" + flow["source_id"] + ) + if response.status_code != 200: + return test.FAIL(response.json()) + source = response.json() + + for item in REF_SUPPORTED_CONSTRAINTS_AUDIO: + + if item == "urn:x-nmos:cap:meta:label": + constraint_set["urn:x-nmos:cap:meta:label"] = "video constraint" + if item == "urn:x-nmos:cap:meta:preference": + constraint_set["urn:x-nmos:cap:meta:preference"] = 0 + if item == "urn:x-nmos:cap:meta:enabled": + 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"]] + } + if item == "urn:x-nmos:cap:format:sample_rate": + constraint_set["urn:x-nmos:cap:format:sample_rate"] = { + "enum": [flow["sample_rate"]] + } + if item == "urn:x-nmos:cap:format:channel_count": + constraint_set["urn:x-nmos:cap:format:channel_count"] = { + "enum": [len(source["channels"])] + } + if item == "urn:x-nmos:cap:format:sample_depth": + constraint_set["urn:x-nmos:cap:format:sample_depth"] = { + "enum": [flow["bit_depth"]] + } + + self.constraints[sender_id] = {"constraint_sets": [constraint_set]} + _, response = TestHelper.do_request( + "PUT", + self.compat_url + "senders/" + sender_id + "constraints/active/", + json=self.constraints[sender_id], + ) + if response.status_code != 200: + return test.FAIL(response.json()) + + _, response = TestHelper.do_request( + "GET", self.node_url + "flows/" + sender["flow_id"] + ) + if response.status_code != 200: + return test.FAIL(response.json()) + new_flow = response.json() + + _, response = TestHelper.do_request( + "GET", self.node_url + "sources/" + flow["source_id"] + ) + if response.status_code != 200: + return test.FAIL(response.json()) + new_source = response.json() + + for item in REF_SUPPORTED_CONSTRAINTS_AUDIO: + + if item == "urn:x-nmos:cap:format:media_type": + if 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"]: + 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"]: + return test.FAIL("different sample_depth") + + _, response = TestHelper.do_request( + "DELETE", + self.compat_url + "senders/" + sender_id + "constraints/active/", + ) + if response.status_code != 200: + return test.FAIL(response.json()) + return test.PASS() + + def test_02_02_07_01(self, test): + "Verify that the device adhere to the preference of the constraint_set." + if len(self.flow_format_video) == 0: + return test.UNCLEAR("There is no audio format ") + + for sender_id in self.flow_format_video: + _, response = TestHelper.do_request( + "GET", self.compat_url + "senders/" + sender_id + "status/" + ) + if response.status_code != 200: + return test.FAIL(response.json()) + state = response.json()["state"] + if state in ["awating_essence", "no_essence"]: + for i in range(0, 5): + _, response = TestHelper.do_request( + "GET", self.compat_url + "senders/" + sender_id + "status/" + ) + state = response.json()["status"]["state"] + if state in ["awating_essence", "no_essence"]: + time.sleep(3000) + else: + break + if state != "unconstrained": + return test.FAIL("inputs are unstable.") + + _, response = TestHelper.do_request( + "GET", self.node_url + "senders/" + sender_id + ) + if response.status_code != 200: + return test.FAIL(response.json) + sender = response.json() + + _, response = TestHelper.do_request( + "GET", self.node_url + "flows/" + sender["flow_id"] + ) + if response.status_code != 200: + return test.FAIL(response.json()) + flow = response.json() + color_sampling = self.getSdpColorSampling(flow["components"]) + if color_sampling == "invalid array of video components": + return test.FAIL("invalid array of video components") + constraint_set0 = {} + constraint_set1 = {} + + for item in REF_SUPPORTED_CONSTRAINTS_VIDEO: + + if item == "urn:x-nmos:cap:meta:label": + constraint_set0["urn:x-nmos:cap:meta:label"] = "video constraint" + if item == "urn:x-nmos:cap:meta:preference": + constraint_set0["urn:x-nmos:cap:meta:preference"] = 0 + if item == "urn:x-nmos:cap:meta:enabled": + 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"]] + } + if item == "urn:x-nmos:cap:format:grain_rate": + constraint_set0["urn:x-nmos:cap:format:grain_rate"] = { + "enum": [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"]] + } + if item == "urn:x-nmos:cap:format:frame_height": + constraint_set0["urn:x-nmos:cap:format:frame_height"] = { + "enum": [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"]] + } + if item == "urn:x-nmos:cap:format:color_sampling": + constraint_set0["urn:x-nmos:cap:format:color_sampling"] = { + "enum": [color_sampling] + } + if item == "urn:x-nmos:cap:format:component_depth": + constraint_set0["urn:x-nmos:cap:format:component_depth"] = { + "enum": [flow["components"][0]["bit_depth"]] + } + + for item in REF_SUPPORTED_CONSTRAINTS_VIDEO: + + if item == "urn:x-nmos:cap:meta:label": + constraint_set1["urn:x-nmos:cap:meta:label"] = "video constraint" + if item == "urn:x-nmos:cap:meta:preference": + constraint_set1["urn:x-nmos:cap:meta:preference"] = -100 + if item == "urn:x-nmos:cap:meta:enabled": + 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"]] + } + 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"])] + } + if item == "urn:x-nmos:cap:format:frame_width": + constraint_set1["urn:x-nmos:cap:format:frame_width"] = { + "enum": [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"]] + } + if item == "urn:x-nmos:cap:format:interlace_mode": + constraint_set1["urn:x-nmos:cap:format:interlace_mode"] = { + "enum": [flow["interlace_mode"]] + } + if item == "urn:x-nmos:cap:format:color_sampling": + constraint_set1["urn:x-nmos:cap:format:color_sampling"] = { + "enum": [color_sampling] + } + if item == "urn:x-nmos:cap:format:component_depth": + constraint_set1["urn:x-nmos:cap:format:component_depth"] = { + "enum": [flow["components"][0]["bit_depth"]] + } + + self.constraints[sender_id] = { + "constraint_sets": [constraint_set0, constraint_set0] + } + _, response = TestHelper.do_request( + "PUT", + self.compat_url + "senders/" + sender_id + "constraints/active/", + json=self.constraints[sender_id], + ) + if response.status_code != 200: + return test.FAIL(response.json()) + + _, response = TestHelper.do_request( + "GET", self.node_url + "flows/" + sender["flow_id"] + ) + if response.status_code != 200: + return test.FAIL(response.json()) + new_flow = response.json() + + new_color_sampling = self.getSdpColorSampling(new_flow["components"]) + if new_color_sampling == "invalid array of video components": + return test.FAIL("invalid array of video components") + + for item in REF_SUPPORTED_CONSTRAINTS_VIDEO: + + if item == "urn:x-nmos:cap:format:media_type": + if 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"]: + return test.FAIL("different grain_rate") + if item == "urn:x-nmos:cap:format:frame_width": + if 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"]: + return test.FAIL("different frame_height") + if item == "urn:x-nmos:cap:format:interlace_mode": + if 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"] + != new_flow["components"][0]["bit_depth"] + ): + return test.FAIL("different component_depth") + + _, response = TestHelper.do_request( + "DELETE", + self.compat_url + "senders/" + sender_id + "constraints/active/", + ) + if response.status_code != 200: + return test.FAIL(response.json()) + return test.PASS() + + def test_02_02_07_02(self, test): + "Verify that the device adhere to the preference of the constraint_set." + if len(self.flow_format_audio) == 0: + return test.UNCLEAR("There is no audio format") + + for sender_id in self.flow_format_audio: + _, response = TestHelper.do_request( + "GET", self.compat_url + "senders/" + sender_id + "status/" + ) + if response.status_code != 200: + test.FAIL(response.json()) + state = response.json()["state"] + if state in ["awating_essence", "no_essence"]: + for i in range(0, 5): + _, response = TestHelper.do_request( + "GET", self.compat_url + "senders/" + sender_id + "status/" + ) + state = response.json()["status"]["state"] + if state in ["awating_essence", "no_essence"]: + time.sleep(3000) + else: + break + if state != "unconstrained": + return test.FAIL("inputs are unstable.") + + _, response = TestHelper.do_request( + "GET", self.node_url + "senders/" + sender_id + ) + if response.status_code != 200: + return test.FAIL(response.json) + sender = response.json() + + _, response = TestHelper.do_request( + "GET", self.node_url + "flows/" + sender["flow_id"] + ) + if response.status_code != 200: + return test.FAIL(response.json()) + flow = response.json() + _, response = TestHelper.do_request( + "GET", self.node_url + "sources/" + flow["source_id"] + ) + if response.status_code != 200: + return test.FAIL(response.json()) + source = response.json() + + constraint_set0 = {} + constraint_set1 = {} + + for item in REF_SUPPORTED_CONSTRAINTS_AUDIO: + + if item == "urn:x-nmos:cap:meta:label": + constraint_set0["urn:x-nmos:cap:meta:label"] = "video constraint" + if item == "urn:x-nmos:cap:meta:preference": + constraint_set0["urn:x-nmos:cap:meta:preference"] = 0 + if item == "urn:x-nmos:cap:meta:enabled": + 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"]] + } + if item == "urn:x-nmos:cap:format:sample_rate": + constraint_set0["urn:x-nmos:cap:format:sample_rate"] = { + "enum": [flow["sample_rate"]] + } + if item == "urn:x-nmos:cap:format:channel_count": + constraint_set0["urn:x-nmos:cap:format:channel_count"] = { + "enum": [len(source["channels"])] + } + if item == "urn:x-nmos:cap:format:sample_depth": + constraint_set0["urn:x-nmos:cap:format:sample_depth"] = { + "enum": [flow["bit_depth"]] + } + + for item in REF_SUPPORTED_CONSTRAINTS_AUDIO: + + if item == "urn:x-nmos:cap:meta:label": + constraint_set1["urn:x-nmos:cap:meta:label"] = "video constraint" + if item == "urn:x-nmos:cap:meta:preference": + constraint_set1["urn:x-nmos:cap:meta:preference"] = -100 + if item == "urn:x-nmos:cap:meta:enabled": + 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"]] + } + 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"])] + } + if item == "urn:x-nmos:cap:format:channel_count": + constraint_set1["urn:x-nmos:cap:format:channel_count"] = { + "enum": [len(source["channels"])] + } + if item == "urn:x-nmos:cap:format:sample_depth": + constraint_set1["urn:x-nmos:cap:format:sample_depth"] = { + "enum": [flow["bit_depth"]] + } + + self.constraints[sender_id] = { + "constraint_sets": [constraint_set0, constraint_set1] + } + + _, response = TestHelper.do_request( + "PUT", + self.compat_url + "senders/" + sender_id + "constraints/active/", + json=self.constraints[sender_id], + ) + if response.status_code != 200: + return test.FAIL(response.json()) + _, response = TestHelper.do_request( + "GET", self.node_url + "flows/" + sender["flow_id"] + ) + if response.status_code != 200: + return test.FAIL(response.json()) + new_flow = response.json() + + _, response = TestHelper.do_request( + "GET", self.node_url + "sources/" + flow["source_id"] + ) + if response.status_code != 200: + return test.FAIL(response.json()) + new_source = response.json() + + for item in REF_SUPPORTED_CONSTRAINTS_AUDIO: + + if item == "urn:x-nmos:cap:format:media_type": + if 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"]: + 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"]: + return test.FAIL("different sample_depth") + + _, response = TestHelper.do_request( + "DELETE", + self.compat_url + "senders/" + sender_id + "constraints/active/", + ) + if response.status_code != 200: + return test.FAIL(response.json()) + return test.PASS() From 0e2bde7cea4c3acc6c5b9fdc45603780a8f25cac Mon Sep 17 00:00:00 2001 From: ggeorgea Date: Wed, 8 Mar 2023 11:13:25 -0500 Subject: [PATCH 02/11] Add Node and Connection Add version API as options Fixed failure comments and standardize message formating Created helper class for Complex x-nmos:cap:format comparisons Signed-off-by: ggeorgea --- nmostesting/NMOSTesting.py | 6 + nmostesting/nmosTestingComplexCompare.py | 262 ++++++++ nmostesting/suites/IS1101Test.py | 150 ++--- nmostesting/suites/IS1102Test.py | 789 +++++++++++------------ 4 files changed, 728 insertions(+), 479 deletions(-) create mode 100644 nmostesting/nmosTestingComplexCompare.py diff --git a/nmostesting/NMOSTesting.py b/nmostesting/NMOSTesting.py index d5916106..f57fb818 100644 --- a/nmostesting/NMOSTesting.py +++ b/nmostesting/NMOSTesting.py @@ -342,6 +342,12 @@ "specs": [{ "spec_key": "is-11", "api_key": "streamcompatibility" + }, { + "spec_key": "is-04", + "api_key": "node" + }, { + "spec_key": "is-05", + "api_key": "connection" }], "class": IS1101Test.IS1101Test }, diff --git a/nmostesting/nmosTestingComplexCompare.py b/nmostesting/nmosTestingComplexCompare.py new file mode 100644 index 00000000..42b46df3 --- /dev/null +++ b/nmostesting/nmosTestingComplexCompare.py @@ -0,0 +1,262 @@ +# constraint_set schema +# +# { +# "$schema": "http://json-schema.org/draft-04/schema#", +# "description": "Describes a Constraint Set", +# "title": "Constraint Set", +# "type": "object", +# "minProperties": 1, +# "properties": { +# "urn:x-nmos:cap:meta:label": { +# "description": "Freeform string label for the Constraint Set", +# "type": "string" +# }, +# "urn:x-nmos:cap:meta:preference": { +# "description": "This value expresses the relative 'weight' that the Receiver assigns to +# its preference for the streams satisfied by the associated Constraint Set. +# The weight is an integer in the range -100 through 100, +# where -100 is least preferred and 100 is most preferred. +# When the attribute is omitted, the effective value for the associated Constraint Set is 0.", +# "type": "integer", +# "default": 0, +# "maximum": 100, +# "minimum": -100 +# }, +# "urn:x-nmos:cap:meta:enabled": { +# "description": "This value indicates whether a Constraint Set is available to use immediately (true) +# or whether this is an offline capability which can be activated via +# some unspecified configuration mechanism (false). +# When the attribute is omitted its value is assumed to be true.", +# "type": "boolean", +# "default": true +# } +# }, +# "patternProperties": { +# "^urn:x-nmos:cap:(?!meta:)": { +# "$ref": "param_constraint.json" +# } +# } +# } +# +# We want to compare that two constraint sets are equal based on the properties of teh schema that allow default +# values for properties not defined. For example the "preference" property defined to true or undefined is the +# same such that comparing an object A heving the property set to true and an object B not having the property +# defined will indicate equality because the schema defines a default value. +# +# This function verifies two constraint sets where at most one constraint set is expected and each constraint +# set must have at most one "sample_rate" paremeter constraint. We then compare the two objects based on their +# respective schemas: constraint_set and rational. We expect the constraint to be defined using the "enum" +# keyword with a single array entry. +# +# return true if equal, false otherwise + + +def compare_complex_sample_rate_constraint( + response_constraints, sample_rate_constraints +): + + # NOTE: We already know that both response_constraints, sample_rate_constraints are valid + # and have been each independently been validated against the schemas. We only check equality. + + # Each constraint_sets array must have a single entry + if len(response_constraints) != 1 or len(sample_rate_constraints) != 1: + return False + + # If the sample_rate property is not defined, objects are not equivalent + try: + response_constraints_enum = response_constraints["constraint_sets"][0][ + "urn:x-nmos:cap:format:sample_rate" + ]["enum"] + except Exception: + return False + + # If the sample_rate property is not defined, objects are not equivalent + try: + sample_rate_constraints_enum = sample_rate_constraints["constraint_sets"][0][ + "urn:x-nmos:cap:format:sample_rate" + ]["enum"] + except Exception: + return False + + # There must be a single entry in the enum array + if len(response_constraints_enum) != 1 or len(sample_rate_constraints_enum) != 1: + return False + + try: + response_numerator = response_constraints_enum[0]["numerator"] + response_denominator = 1 + + if "denominator" in response_constraints_enum[0]: + response_denominator = response_constraints_enum[0]["denominator"] + + sample_rate_numerator = sample_rate_constraints_enum[0]["numerator"] + sample_rate_denominator = 1 + + if "denominator" in sample_rate_constraints_enum[0]: + sample_rate_denominator = sample_rate_constraints_enum[0]["denominator"] + + if ( + response_numerator != sample_rate_numerator + or response_denominator != sample_rate_denominator + ): + return False + except Exception: + return False + + # There must be no other patternProperties + for prop in sample_rate_constraints["constraint_sets"][0]: + if ( + prop != "urn:x-nmos:cap:format:sample_rate" + and prop != "urn:x-nmos:cap:meta:enabled" + and prop != "urn:x-nmos:cap:meta:preference" + ): + return False + + for prop in response_constraints["constraint_sets"][0]: + if ( + prop != "urn:x-nmos:cap:format:sample_rate" + and prop != "urn:x-nmos:cap:meta:enabled" + and prop != "urn:x-nmos:cap:meta:preference" + ): + return False + + # Check meta:enabled considering default values + response_enabled = True + if "urn:x-nmos:cap:meta:enabled" in response_constraints["constraint_sets"][0]: + response_enabled = response_constraints["constraint_sets"][0][ + "urn:x-nmos:cap:meta:enabled" + ] + + sample_rate_enabled = True + if "urn:x-nmos:cap:meta:enabled" in sample_rate_constraints["constraint_sets"][0]: + sample_rate_enabled = sample_rate_constraints["constraint_sets"][0][ + "urn:x-nmos:cap:meta:enabled" + ] + + if response_enabled != sample_rate_enabled: + return False + + # Check meta:preference considering default values + response_preference = 0 + if "urn:x-nmos:cap:meta:preference" in response_constraints["constraint_sets"][0]: + response_preference = response_constraints["constraint_sets"][0][ + "urn:x-nmos:cap:meta:preference" + ] + + sample_rate_preference = 0 + if ( + "urn:x-nmos:cap:meta:preference" + in sample_rate_constraints["constraint_sets"][0] + ): + sample_rate_preference = sample_rate_constraints["constraint_sets"][0][ + "urn:x-nmos:cap:meta:preference" + ] + + if response_preference != sample_rate_preference: + return False + + # If we get here it is because the two objects are equal + return True + + +def compare_complex_grain_rate_constraint(response_constraints, grain_rate_constraints): + + # NOTE: We already know that both response_constraints, grain_rate_constraints are valid + # and have been each independently been validated against the schemas. We only check equality. + + # Each constraint_sets array must have a single entry + if len(response_constraints) != 1 or len(grain_rate_constraints) != 1: + return False + + # If the grain_rate property is not defined, objects are not equivalent + try: + response_constraints_enum = response_constraints["constraint_sets"][0][ + "urn:x-nmos:cap:format:grain_rate" + ]["enum"] + except Exception: + return False + + # If the grain_rate property is not defined, objects are not equivalent + try: + grain_rate_constraints_enum = grain_rate_constraints["constraint_sets"][0][ + "urn:x-nmos:cap:format:grain_rate" + ]["enum"] + except Exception: + return False + + # There must be a single entry in the enum array + if len(response_constraints_enum) != 1 or len(grain_rate_constraints_enum) != 1: + return False + + try: + response_numerator = response_constraints_enum[0]["numerator"] + response_denominator = 1 + + if "denominator" in response_constraints_enum[0]: + response_denominator = response_constraints_enum[0]["denominator"] + + grain_rate_numerator = grain_rate_constraints_enum[0]["numerator"] + grain_rate_denominator = 1 + + if "denominator" in grain_rate_constraints_enum[0]: + grain_rate_denominator = grain_rate_constraints_enum[0]["denominator"] + + if ( + response_numerator != grain_rate_numerator + or response_denominator != grain_rate_denominator + ): + return False + except Exception: + return False + + # There must be no other patternProperties + for prop in grain_rate_constraints["constraint_sets"][0]: + if ( + prop != "urn:x-nmos:cap:format:grain_rate" + and prop != "urn:x-nmos:cap:meta:enabled" + and prop != "urn:x-nmos:cap:meta:preference" + ): + return False + + for prop in response_constraints["constraint_sets"][0]: + if ( + prop != "urn:x-nmos:cap:format:grain_rate" + and prop != "urn:x-nmos:cap:meta:enabled" + and prop != "urn:x-nmos:cap:meta:preference" + ): + return False + + # Check meta:enabled considering default values + response_enabled = True + if "urn:x-nmos:cap:meta:enabled" in response_constraints["constraint_sets"][0]: + response_enabled = response_constraints["constraint_sets"][0][ + "urn:x-nmos:cap:meta:enabled" + ] + + grain_rate_enabled = True + if "urn:x-nmos:cap:meta:enabled" in grain_rate_constraints["constraint_sets"][0]: + grain_rate_enabled = grain_rate_constraints["constraint_sets"][0][ + "urn:x-nmos:cap:meta:enabled" + ] + + if response_enabled != grain_rate_enabled: + return False + + # Check meta:preference considering default values + response_preference = 0 + if "urn:x-nmos:cap:meta:preference" in response_constraints["constraint_sets"][0]: + response_preference = response_constraints["constraint_sets"][0][ + "urn:x-nmos:cap:meta:preference" + ] + + grain_rate_preference = 0 + if "urn:x-nmos:cap:meta:preference" in grain_rate_constraints["constraint_sets"][0]: + grain_rate_preference = grain_rate_constraints["constraint_sets"][0][ + "urn:x-nmos:cap:meta:preference" + ] + + if response_preference != grain_rate_preference: + return False + + # If we get here it is because the two objects are equal + return True diff --git a/nmostesting/suites/IS1101Test.py b/nmostesting/suites/IS1101Test.py index 637d3149..f3077ec6 100644 --- a/nmostesting/suites/IS1101Test.py +++ b/nmostesting/suites/IS1101Test.py @@ -17,6 +17,8 @@ COMPAT_API_KEY = "streamcompatibility" CONTROLS = "controls" +NODE_API_KEY = "node" +CONN_API_KEY = "connection" class IS1101Test(GenericTest): @@ -30,11 +32,12 @@ def __init__(self, apis): "/inputs/{inputId}/edid", "/inputs/{inputId}/edid/base", "/inputs/{inputId}/edid/effective", - "/outputs/{outputId}/edid", + "/outputs/{outputId}/edid" ] GenericTest.__init__(self, apis, omit_paths) self.compat_url = self.apis[COMPAT_API_KEY]["url"] - self.base_url = self.apis[COMPAT_API_KEY]["base_url"] + 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 = [] @@ -45,34 +48,33 @@ def __init__(self, apis): self.caps = "" # GENERAL TESTS - def test_00_01(self, test): + def test_01(self, test): """Verify that IS-11 is exposed in the Node API as \ urn:x-nmos:control:stream-compat/v1.0 at url /x-nmos/streamcompatibility/v1.0/ """ - valid_res, response = TestHelper.do_request( - "GET", self.base_url + "/x-nmos/node/v1.3/devices/" + _, response = TestHelper.do_request( + "GET", self.node_url + "devices/" ) - if valid_res: - response_json = response.json() - controls = response_json[0][CONTROLS] - control_href = "" + if response.status_code != 200: + return test.FAIL("The request has not succeeded.", response) + controls = response.json()[0][CONTROLS] + control_href = "" + if len(controls) > 0: for control in controls: - if control["type"] == "urn:x-nmos:control:stream-compat/v1.0": + if control["type"] == "urn:x-nmos:control:stream-compat/" + self.apis[COMPAT_API_KEY]["version"]: control_href = control["href"] break - if len(control) == 0: - return test.WARNING("IS-11 API is not available") if not control_href.endswith(self.compat_url): return test.FAIL("IS-11 URL is invalid") return test.PASS() - return test.FAIL(response) + return test.FAIL("IS-11 API is not available") - def test_00_02(self, test): + def test_02(self, test): "Put all senders into inactive state" - senders_url = self.base_url + "/x-nmos/connection/v1.1/single/senders/" + senders_url = self.conn_url + "single/senders/" _, response = TestHelper.do_request("GET", senders_url) if response.status_code != 200: - return test.FAIL(response.json()) + return test.FAIL("The request has not succeeded.", response.json()) senders = response.json() if len(senders) > 0: for sender in senders: @@ -88,16 +90,16 @@ def test_00_02(self, test): or response.json()["master_enable"] or response.json()["activation"]["mode"] != "activate_immediate" ): - return test.FAIL(response.json()) + return test.FAIL("The request has not succeeded.", response.json()) return test.PASS() - return test.UNCLEAR("Could not find any IS-04 senders to test") + return test.UNCLEAR("Could not find any senders to test") - def test_00_03(self, test): + def test_03(self, test): "Put all the receivers into inactive state" - receivers_url = self.base_url + "/x-nmos/connection/v1.1/single/receivers/" + receivers_url = self.conn_url + "single/receivers/" _, response = TestHelper.do_request("GET", receivers_url) if response.status_code != 200: - return test.FAIL(response.json()) + return test.FAIL("The request has not succeeded.", response.json()) receivers = response.json() if len(receivers) > 0: for receiver in receivers: @@ -112,11 +114,11 @@ def test_00_03(self, test): or response.json()["master_enable"] or response.json()["activation"]["mode"] != "activate_immediate" ): - return test.FAIL(response.json()) + return test.FAIL("The request has not succeeded.", response.json()) return test.PASS() - return test.UNCLEAR("Could not find any IS-04 receivers to test") + return test.UNCLEAR("Could not find any receivers to test") # OUTPUTS TESTS def test_03_01(self, test): @@ -131,14 +133,14 @@ def test_03_01(self, test): if len(self.outputs) == 0: return test.UNCLEAR("No outputs") return test.PASS() - return test.FAIL(response.json()) + return test.FAIL("The request has not succeeded.", response.json()) def test_03_02(self, test): """ Verify that some of the outputs of the device are connected. """ if len(self.outputs) == 0: - return test.UNCLEAR("No IS11 receivers outputs") + return test.UNCLEAR("No IS11 outputs") for output in self.outputs: _, response = TestHelper.do_request( "GET", self.compat_url + "outputs/" + output + "properties/" @@ -150,9 +152,9 @@ def test_03_02(self, test): if output["connected"]: self.connected_outputs.append(output["id"]) else: - return test.FAIL(response.json()) + return test.FAIL("The request has not succeeded.", response.json()) if len(self.connected_outputs) == 0: - return test.UNCLEAR("None Outputs support edid.") + return test.UNCLEAR("No connected outputs.") return test.PASS() def test_03_03(self, test): @@ -161,7 +163,7 @@ def test_03_03(self, test): a signal as test 0 put all of the receivers inactive. """ if len(self.connected_outputs) == 0: - return test.UNCLEAR("no connected outputs") + return test.UNCLEAR("No connected outputs.") for output_id in self.connected_outputs: _, response = TestHelper.do_request( "GET", self.compat_url + "outputs/" + output_id + "/properties/" @@ -170,10 +172,10 @@ def test_03_03(self, test): if response.json()["status"]["state"] == "signal_present": self.active_connected_outputs.append(response.json()) else: - return test.FAIL(response.json()) + return test.FAIL("The request has not succeeded.", response.json()) if len(self.active_connected_outputs) != 0: return test.UNCLEAR( - "Connected output have a signal while all receivers are inactive" + "Connected output have a signal while all receivers are inactive." ) return test.PASS() @@ -182,7 +184,7 @@ def test_03_04(self, test): Verify that connected outputs supporting EDID behave according to the RAML file. """ if len(self.connected_outputs) == 0: - return test.UNCLEAR("no connected outputs") + return test.UNCLEAR("No connected outputs.") for output_id in self.connected_outputs: _, response = TestHelper.do_request( "GET", self.compat_url + "outputs/" + output_id + "/properties/" @@ -191,9 +193,9 @@ def test_03_04(self, test): if response.json()["edid_support"]: self.edid_connected_outputs.append(response.json()["id"]) else: - return test.FAIL(response.json()) + return test.FAIL("The request has not succeeded.", response.json()) if self.edid_connected_outputs == 0: - return test.UNCLEAR("Outputs not supporting edid") + return test.UNCLEAR("Outputs not supporting edid.") return test.PASS() def test_03_04_01(self, test): @@ -201,13 +203,13 @@ def test_03_04_01(self, test): Verify that an output indicating EDID support behaves according to the RAML file. """ if len(self.edid_connected_outputs) == 0: - return test.UNCLEAR("no edid connected outputs") + return test.UNCLEAR("No edid connected outputs.") for output_id in self.edid_connected_outputs: _, response = TestHelper.do_request( "GET", self.compat_url + "outputs/" + output_id ) if response.status_code != 200: - return test.FAIL(response.json()) + return test.FAIL("The request has not succeeded.", response.json()) return test.PASS() def test_03_04_02(self, test): @@ -217,27 +219,27 @@ def test_03_04_02(self, test): """ is_valid_response = True if len(self.edid_connected_outputs) == 0: - return test.UNCLEAR("no edid connected outputs") + return test.UNCLEAR("No edid connected outputs.") for output_id in self.edid_connected_outputs: _, response = TestHelper.do_request( "GET", self.compat_url + "outputs/" + output_id + "/edid/" ) if ( response.status_code != 200 - and response.headers["Content-Type"] != "application/octet-stream" + or response.headers["Content-Type"] != "application/octet-stream" ): is_valid_response = False break if is_valid_response: return test.PASS() - return test.FAIL(response.json()) + return test.FAIL("The request has not succeeded.", response.json()) def test_03_05(self, test): """ Verify that connected outputs not supporting EDID behave according to the RAML file. """ if len(self.connected_outputs) == 0: - return test.UNCLEAR("no connected outputs") + return test.UNCLEAR("No connected outputs.") for output_id in self.connected_outputs: _, response = TestHelper.do_request( "GET", self.compat_url + "outputs/" + output_id + "/properties/" @@ -246,9 +248,9 @@ def test_03_05(self, test): if not response.json()["edid_support"]: self.not_edid_connected_outputs.append(response.json()["id"]) else: - return test.FAIL(response.json()) + return test.FAIL("The request has not succeeded.", response.json()) if len(self.not_edid_connected_outputs) == 0: - return test.UNCLEAR("Outputs supporting edid") + return test.UNCLEAR("Outputs supporting edid.") return test.PASS() def test_03_05_01(self, test): @@ -256,13 +258,13 @@ def test_03_05_01(self, test): Verify that there is no EDID support. """ if len(self.not_edid_connected_outputs) == 0: - return test.UNCLEAR("none of not edid connected outputs") + return test.UNCLEAR("None of not edid connected outputs.") for output_id in self.not_edid_connected_outputs: _, response = TestHelper.do_request( "GET", self.compat_url + "outputs/" + output_id + "/edid/" ) if response.status_code != 204: - return test.UNCLEAR("status code should be 204") + return test.UNCLEAR("Status code should be 204.") return test.PASS() # RECEIVERS TESTS @@ -273,33 +275,29 @@ def test_04_01(self, test): _, response = TestHelper.do_request("GET", self.compat_url + "receivers/") if response.status_code != 200: - return test.FAIL(response.json()) + return test.FAIL("The request has not succeeded.", response.json()) self.receivers = response.json() - return ( - test.PASS() - if len(self.receivers) != 0 - else test.UNCLEAR("No IS_11 receivers") - ) + if len(self.receivers) == 0: + return test.UNCLEAR("No IS_11 receivers.") + return test.PASS() def test_04_01_01(self, test): """ Verify that IS-11 Receivers exist on the Node API as Receivers. """ + if len(self.receivers) == 0: + return test.UNCLEAR("No IS_11 receivers.") for receiver_id in self.receivers: _, response = TestHelper.do_request( - "GET", self.base_url + "/x-nmos/node/v1.3/receivers/" + receiver_id + "GET", self.node_url + "receivers/" + receiver_id ) if response.status_code != 200: - return test.FAIL(response.json()) + return test.FAIL("The request has not succeeded.", response.json()) if response.json()["id"] != receiver_id[:-1]: return test.UNCLEAR( - "The IS-11 Receiver doesn't exist on the Node API as receiver" + "The IS-11 Receiver doesn't exist on the Node API as receiver." ) - return ( - test.PASS() - if len(self.receivers) != 0 - else test.UNCLEAR("No IS_11 receivers") - ) + return test.PASS() def test_04_02(self, test): """ @@ -307,50 +305,44 @@ def test_04_02(self, test): """ _, response = TestHelper.do_request("GET", self.compat_url + "receivers/") if response.status_code != 200: - return test.FAIL(response.json()) + return test.FAIL("The request has not succeeded.", response.json()) self.receivers = response.json() - return ( - test.PASS() - if len(self.receivers) != 0 - else test.UNCLEAR("No IS_11 receivers") - ) + if len(self.receivers) == 0: + return test.UNCLEAR("No IS_11 receivers.") + return test.PASS() def test_04_02_01(self, test): """ Verify that the status is "unknown" or "non_compliant_stream" as per our pre-conditions of not being master_enabled. """ + if len(self.receivers) == 0: + return test.UNCLEAR("No IS_11 receivers.") for receiver_id in self.receivers: _, response = TestHelper.do_request( "GET", self.compat_url + "receivers/" + receiver_id + "status/" ) if response.status_code != 200: - return test.FAIL(response.json()) + return test.FAIL("The request has not succeeded.", 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.PASS() - if len(self.receivers) != 0 - else test.UNCLEAR("No IS_11 receivers") - ) + return test.FAIL("The state is not unknown or non compliant stream.") + return test.PASS() def test_04_02_02(self, test): """ Verify that the Receiver supports Receiver Capabilities. """ + if len(self.receivers) == 0: + return test.UNCLEAR("No IS_11 receivers.") for receiver_id in self.receivers: _, response = TestHelper.do_request( - "GET", self.base_url + "/x-nmos/node/v1.3/receivers/" + receiver_id + "GET", self.node_url + "receivers/" + receiver_id ) if response.status_code != 200: - return test.FAIL(response.json()) + return test.FAIL("The request has not succeeded.", response.json()) self.caps = response.json()["caps"] if "constraint_sets" not in self.caps: - return test.UNCLEAR(" The receiver does not have constraint_sets in caps") + return test.UNCLEAR("The receiver does not have constraint_sets in caps.") if len(self.caps["constraint_sets"]) == 0: - return test.UNCLEAR(" The receiver does not support BCP-004-01") - return ( - test.PASS() - if len(self.receivers) != 0 - else test.UNCLEAR("No IS_11 receivers") - ) + return test.UNCLEAR("The receiver does not support BCP-004-01.") + return test.PASS() diff --git a/nmostesting/suites/IS1102Test.py b/nmostesting/suites/IS1102Test.py index e51a0fb7..d5da7726 100644 --- a/nmostesting/suites/IS1102Test.py +++ b/nmostesting/suites/IS1102Test.py @@ -16,6 +16,7 @@ from .. import TestHelper import time import re +from .. import nmosTestingComplexCompare COMPAT_API_KEY = "streamcompatibility" NODE_API_KEY = "node" @@ -48,6 +49,7 @@ class IS1102Test(GenericTest): """ Runs Node Tests covering IS-11 """ + def __init__(self, apis): # Don't auto-test paths responding with an EDID binary as they don't have a JSON Schema omit_paths = [ @@ -59,7 +61,6 @@ def __init__(self, apis): GenericTest.__init__(self, apis, omit_paths) self.node_url = self.apis[NODE_API_KEY]["url"] self.compat_url = self.apis[COMPAT_API_KEY]["url"] - self.base_url = self.apis[COMPAT_API_KEY]["base_url"] self.senders = "" self.senders_2 = "" self.flow_format = {} @@ -80,54 +81,6 @@ def __init__(self, apis): Runs Node Tests covering IS-11 for Senders """ - def compare_complex(self, response_constraints, sample_rate_constraints): - - response_constraints_enum = response_constraints["constraint_sets"][0][ - "urn:x-nmos:cap:format:sample_rate" - ]["enum"] - sample_rate_constraints_enum = sample_rate_constraints["constraint_sets"][0][ - "urn:x-nmos:cap:format:sample_rate" - ]["enum"] - - if len(response_constraints_enum) > 0 and len(sample_rate_constraints_enum) > 0: - if ( - "numerator" in response_constraints_enum[0] - and "numerator" in sample_rate_constraints_enum[0] - and "denominator" in response_constraints_enum[0] - and "denominator" in sample_rate_constraints_enum[0] - ): - return ( - response_constraints_enum[0]["numerator"] - == sample_rate_constraints_enum[0]["numerator"] - and response_constraints_enum[0]["denominator"] - == sample_rate_constraints_enum[0]["denominator"] - ) - - if ( - "numerator" in response_constraints_enum[0] - and "numerator" in sample_rate_constraints_enum[0] - and "denominator" in response_constraints_enum[0] - and "denominator" not in sample_rate_constraints_enum[0] - ): - return ( - response_constraints_enum[0]["numerator"] - == sample_rate_constraints_enum[0]["numerator"] - and response_constraints_enum[0]["denominator"] == 1 - ) - - if ( - "numerator" in response_constraints_enum[0] - and "numerator" in sample_rate_constraints_enum[0] - and "denominator" not in response_constraints_enum[0] - and "denominator" in sample_rate_constraints_enum[0] - ): - return ( - response_constraints_enum[0]["numerator"] - == sample_rate_constraints_enum[0]["numerator"] - and 1 == sample_rate_constraints_enum[0]["denominator"] - ) - return False - def getSdpColorSampling(self, flow_components): """ getColorSamplingFromComponents supports RGB, @@ -297,6 +250,8 @@ def get_another_grain_rate(self, grain_rate): def get_another_sample_rate(self, sample_rate): numerator = sample_rate["numerator"] + if numerator == 0: + return {"numerator": 48000} if numerator == 48000: return {"numerator": 44100} if numerator == 44100: @@ -310,8 +265,10 @@ def get_another_sample_rate(self, sample_rate): def test_02_00(self, test): "Reset active constraints of all senders" _, response = TestHelper.do_request("GET", self.compat_url + "senders/") - if response.status_code == 200: - self.senders = response.json() + if response.status_code != 200: + return test.FAIL("The request has not succeeded.", response.json()) + self.senders = response.json() + if len(self.senders) != 0: for sender in self.senders: _, response = TestHelper.do_request( "DELETE", @@ -320,8 +277,7 @@ def test_02_00(self, test): if response.status_code != 200: return test.FAIL("senders constraints cannot be deleted") return test.PASS() - - return test.FAIL(response.json()) + return test.UNCLEAR("There is no IS-11 senders.") def test_02_01(self, test): "Verify that the device supports the concept of IS-11 Sender" @@ -330,7 +286,7 @@ def test_02_01(self, test): return test.FAIL(response.json) self.senders = response.json() if len(self.senders) == 0: - return test.UNCLEAR("there is no IS-11 senders") + return test.UNCLEAR("There is no IS-11 senders.") return test.PASS() def test_02_01_01(self, test): @@ -341,19 +297,21 @@ def test_02_01_01(self, test): "GET", self.node_url + "senders/" + sender_id ) if response.status_code != 200: - return test.FAIL() + return test.FAIL("The request has not succeeded.", response.json()) sender_node = response.json()["id"] if sender_id[:-1] != sender_node: - return test.FAIL("") + return test.FAIL("Senders are different") return test.PASS() - return test.UNCLEAR("there is no IS-11 senders") + return test.UNCLEAR("There is no IS-11 senders.") def test_02_02(self, test): "Verify senders (generic with/without inputs)" _, response = TestHelper.do_request("GET", self.compat_url + "senders/") if response.status_code != 200: - return test.FAIL(response.json()) + return test.FAIL("The request has not succeeded.", response.json()) self.senders_2 = response.json() + if len(self.senders_2) == 0: + return test.UNCLEAR("There is no IS-11 senders.") return test.PASS() def test_02_02_01(self, test): @@ -364,7 +322,7 @@ def test_02_02_01(self, test): "GET", self.compat_url + "senders/" + sender_id + "status/" ) if response.status_code != 200: - return test.FAIL(response.json()) + return test.FAIL("The request has not succeeded.", response.json()) state = response.json()["state"] if state in ["awating_essence", "no_essence"]: for i in range(0, 5): @@ -379,7 +337,7 @@ def test_02_02_01(self, test): if state != "unconstrained": return test.FAIL("inputs are unstable.") return test.PASS() - return test.UNCLEAR("there is no IS-11 senders") + return test.UNCLEAR("There is no IS-11 senders.") def test_02_02_03(self, test): """ @@ -395,7 +353,7 @@ def test_02_02_03(self, test): return test.FAIL(response.json) sender_node = response.json()["id"] if sender_id[:-1] != sender_node: - return test.FAIL("") + return test.FAIL("Senders are different") sender_flow_id = response.json()["flow_id"] if sender_flow_id is None: return test.FAIL("the sender must have a flow") @@ -406,7 +364,7 @@ def test_02_02_03(self, test): "GET", self.node_url + "flows/" + sender_flow_id ) if response.status_code != 200: - return test.FAIL(response.json()) + return test.FAIL("The request has not succeeded.", response.json()) flow_format = response.json()["format"] self.flow_format[sender_id] = flow_format if flow_format == "urn:x-nmos:format:video": @@ -427,7 +385,7 @@ def test_02_02_03(self, test): ): print("only audio and video senders are tested at this time.") return test.PASS() - return test.UNCLEAR("there is no IS-11 senders") + return test.UNCLEAR("There is no IS-11 senders.") def test_02_02_03_01(self, test): "Verify that the video sender supports the minimum set of video constraints" @@ -435,15 +393,15 @@ def test_02_02_03_01(self, test): pattern = "^urn:x-nmos:cap:" if len(self.flow_format_video) == 0: - return test.UNCLEAR("There is no video format") + return test.UNCLEAR("There is no video format.") for sender_id in self.flow_format_video: _, response = TestHelper.do_request( "GET", - self.compat_url + "senders/" + sender_id + "constraints/supported/ ", + self.compat_url + "senders/" + sender_id + "constraints/supported/", ) if response.status_code != 200: - return test.FAIL(response.json()) + return test.FAIL("The request has not succeeded.", response.json()) supportedConstraints = response.json()["parameter_constraints"] for item in supportedConstraints: if not re.search(pattern, item): @@ -459,15 +417,15 @@ def test_02_02_03_02(self, test): pattern = "^urn:x-nmos:cap:" if len(self.flow_format_audio) == 0: - return test.UNCLEAR("There is no video format") + return test.UNCLEAR("There is no video format.") for sender_id in self.flow_format_audio: _, response = TestHelper.do_request( "GET", - self.compat_url + "senders/" + sender_id + "constraints/supported/ ", + self.compat_url + "senders/" + sender_id + "constraints/supported/", ) if response.status_code != 200: - return test.FAIL(response.json()) + return test.FAIL("The request has not succeeded.", response.json()) supportedConstraints = response.json()["parameter_constraints"] for item in supportedConstraints: if not re.search(pattern, item): @@ -484,14 +442,14 @@ def test_02_02_04_01(self, test): the associated IS-04 sender. """ if len(self.flow_format_video) == 0: - return test.UNCLEAR("There is no video format") + return test.UNCLEAR("There is no video format.") for sender_id in self.flow_format_video: _, response = TestHelper.do_request( "GET", self.node_url + "senders/" + sender_id ) if response.status_code != 200: - return test.FAIL(response.json()) + return test.FAIL("The request has not succeeded.", response.json()) version = response.json()["version"] self.version[sender_id] = version self.grain_rate_constraints[sender_id] = { @@ -510,47 +468,49 @@ def test_02_02_04_01(self, test): json=self.grain_rate_constraints[sender_id], ) if response.status_code != 200: - return test.FAIL(response.json()) + return test.FAIL("The request has not succeeded.", response.json()) _, response = TestHelper.do_request( "GET", self.node_url + "senders/" + sender_id ) if response.status_code != 200: - return test.FAIL(response.json()) + return test.FAIL("The request has not succeeded.", response.json()) version = response.json()["version"] if version == self.version[sender_id]: - return test.FAIL() + return test.FAIL("Version are different") self.version[sender_id] = version _, response = TestHelper.do_request( - "GET", self.compat_url + "senders/" + sender_id + "constraints/active/ " + "GET", self.compat_url + "senders/" + sender_id + "constraints/active/" ) if response.status_code != 200: - return test.FAIL(response.json()) + return test.FAIL("The request has not succeeded.", response.json()) constraints = response.json() - if constraints != self.grain_rate_constraints[sender_id]: - return test.FAIL() + if not nmosTestingComplexCompare.compare_complex_grain_rate_constraint( + constraints, self.grain_rate_constraints[sender_id] + ): + return test.FAIL("Contraints are different") _, response = TestHelper.do_request( "DELETE", self.compat_url + "senders/" + sender_id + "constraints/active/", ) if response.status_code != 200: - return test.FAIL(response.json()) + return test.FAIL("The request has not succeeded.", response.json()) _, response = TestHelper.do_request( "GET", self.node_url + "senders/" + sender_id ) if response.status_code != 200: - return test.FAIL(response.json()) + return test.FAIL("The request has not succeeded.", response.json()) version = response.json()["version"] if version == self.version[sender_id]: - return test.FAIL() + return test.FAIL("Version are different") self.version[sender_id] = version _, response = TestHelper.do_request( "GET", self.compat_url + "senders/" + sender_id + "constraints/active/" ) if response.status_code != 200: - return test.FAIL(response.json()) + return test.FAIL("The request has not succeeded.", response.json()) constraints = response.json() if constraints != self.empty_constraints[sender_id]: - return test.FAIL("Constraints doesn't match") + return test.FAIL("Contraints are different") return test.PASS() def test_02_02_04_02(self, test): @@ -559,13 +519,13 @@ def test_02_02_04_02(self, test): sender(audio) changes the version of the associated IS-04 sender. """ if len(self.flow_format_audio) == 0: - return test.UNCLEAR("There is no audio format") + return test.UNCLEAR("There is no audio format.") for sender_id in self.flow_format_audio: _, response = TestHelper.do_request( "GET", self.node_url + "senders/" + sender_id ) if response.status_code != 200: - return test.FAIL(response.json()) + return test.FAIL("The request has not succeeded.", response.json()) version = response.json()["version"] self.version[sender_id] = version self.sample_rate_constraints[sender_id] = { @@ -584,25 +544,25 @@ def test_02_02_04_02(self, test): json=self.sample_rate_constraints[sender_id], ) if response.status_code != 200: - return test.FAIL(response.json()) + return test.FAIL("The request has not succeeded.", response.json()) _, response = TestHelper.do_request( "GET", self.node_url + "senders/" + sender_id ) if response.status_code != 200: - return test.FAIL(response.json()) + return test.FAIL("The request has not succeeded.", response.json()) version = response.json()["version"] if version == self.version[sender_id]: - return test.FAIL() + return test.FAIL("Version are different") self.version[sender_id] = version _, response = TestHelper.do_request( "GET", self.compat_url + "senders/" + sender_id + "constraints/active/" ) if response.status_code != 200: - return test.FAIL(response.json()) + return test.FAIL("The request has not succeeded.", response.json()) constraints = response.json() - if not self.compare_complex( + if not nmosTestingComplexCompare.compare_complex_sample_rate_constraint( constraints, self.sample_rate_constraints[sender_id] ): return test.FAIL( @@ -616,43 +576,42 @@ def test_02_02_04_02(self, test): self.compat_url + "senders/" + sender_id + "constraints/active/", ) if response.status_code != 200: - return test.FAIL(response.json()) + return test.FAIL("The request has not succeeded.", response.json()) _, response = TestHelper.do_request( "GET", self.node_url + "senders/" + sender_id ) if response.status_code != 200: - return test.FAIL(response.json()) + return test.FAIL("The request has not succeeded.", response.json()) version = response.json()["version"] if version == self.version[sender_id]: - return test.FAIL() + return test.FAIL("Version are different") self.version[sender_id] = version _, response = TestHelper.do_request( - "GET", self.compat_url + "senders/" + sender_id + "constraints/active/ " + "GET", self.compat_url + "senders/" + sender_id + "constraints/active/" ) if response.status_code != 200: - return test.FAIL(response.json()) + return test.FAIL("The request has not succeeded.", response.json()) constraints = response.json() if constraints != self.empty_constraints[sender_id]: - return test.FAIL("Constraints doesn't match") + return test.FAIL("Contraints are different") return test.PASS() def test_02_02_05_01(self, test): - """Verify that setting NOP constraints for frame_width, - frame_height and grain_rate does not change the flow of - a sender (video) and that the state goes from \"unconstrained\" - to \"constrained\" + """ + Verify that setting NOP constraints for frame(width,height), + grain_rate doesn't change the flow of a sender(video). """ if len(self.flow_format_video) == 0: - return test.UNCLEAR("There is no audio format ") + return test.UNCLEAR("There is no video format.") for sender_id in self.flow_format_video: _, response = TestHelper.do_request( "GET", self.compat_url + "senders/" + sender_id + "status/" ) if response.status_code != 200: - return test.FAIL(response.json()) + return test.FAIL("The request has not succeeded.", response.json()) state = response.json()["state"] if state in ["awating_essence", "no_essence"]: for i in range(0, 5): @@ -693,13 +652,13 @@ def test_02_02_05_01(self, test): json=self.constraints[sender_id], ) if response.status_code != 200: - return test.FAIL(response.json()) + return test.FAIL("The request has not succeeded.", response.json()) _, response = TestHelper.do_request( "GET", self.compat_url + "senders/" + sender_id + "status/" ) if response.status_code != 200: - return test.FAIL(response.json()) + return test.FAIL("The request has not succeeded.", response.json()) state = response.json()["state"] if state in ["awating_essence", "no_essence"]: @@ -728,7 +687,7 @@ def test_02_02_05_01(self, test): "GET", self.node_url + "flows/" + sender_flow_id ) if response.status_code != 200: - return test.FAIL(response.json()) + return test.FAIL("The request has not succeeded.", response.json()) if ( self.flow_grain_rate[sender_id] != response.json()["grain_rate"] @@ -742,23 +701,24 @@ def test_02_02_05_01(self, test): self.compat_url + "senders/" + sender_id + "constraints/active/", ) if response.status_code != 200: - return test.FAIL(response.json()) + return test.FAIL("The request has not succeeded.", response.json()) return test.PASS() def test_02_02_05_02(self, test): - """Verify that setting NOP constraints for sample_rate does not change the flow of a sender (audio) and \ - that the state goes from \"unconstrained\" to \"constrained\"""" + """ + Verify that setting NOP constraints for sample_rate doesn't change the flow of a sender(audio). + """ if len(self.flow_format_audio) == 0: - return test.UNCLEAR("There is no audio format ") + return test.UNCLEAR("There is no audio format.") for sender_id in self.flow_format_audio: _, response = TestHelper.do_request( "GET", self.compat_url + "senders/" + sender_id + "status/" ) if response.status_code != 200: - return test.FAIL(response.json()) + return test.FAIL("The request has not succeeded.", response.json()) state = response.json()["state"] if state in ["awating_essence", "no_essence"]: @@ -789,13 +749,13 @@ def test_02_02_05_02(self, test): json=self.constraints[sender_id], ) if response.status_code != 200: - return test.FAIL(response.json()) + return test.FAIL("The request has not succeeded.", response.json()) _, response = TestHelper.do_request( "GET", self.compat_url + "senders/" + sender_id + "status/" ) if response.status_code != 200: - return test.FAIL(response.json()) + return test.FAIL("The request has not succeeded.", response.json()) state = response.json()["state"] if state in ["awating_essence", "no_essence"]: @@ -824,7 +784,7 @@ def test_02_02_05_02(self, test): "GET", self.node_url + "flows/" + sender_flow_id ) if response.status_code != 200: - return test.FAIL(response.json()) + return test.FAIL("The request has not succeeded.", response.json()) flow_sample_rate = response.json()["sample_rate"] if self.flow_sample_rate[sender_id] != flow_sample_rate: return test.FAIL("Different sample rate") @@ -834,21 +794,23 @@ def test_02_02_05_02(self, test): self.compat_url + "senders/" + sender_id + "constraints/active/", ) if response.status_code != 200: - return test.FAIL(response.json()) + return test.FAIL("The request has not succeeded.", response.json()) return test.PASS() def test_02_02_06_01(self, test): - """Verify that setting NOP constraints for supported constraints does not change the flow of a sender (video) \ - and that the state goes from \"unconstrained\" to \"constrained\"""" + """ + Verify that setting NOP constraints for supported constraints + doesn't change the flow of a sender(video). + """ if len(self.flow_format_video) == 0: - return test.UNCLEAR("There is no audio format ") + return test.UNCLEAR("There is no video format.") for sender_id in self.flow_format_video: _, response = TestHelper.do_request( "GET", self.compat_url + "senders/" + sender_id + "status/" ) if response.status_code != 200: - return test.FAIL(response.json()) + return test.FAIL("The request has not succeeded.", response.json()) state = response.json()["state"] if state in ["awating_essence", "no_essence"]: for i in range(0, 5): @@ -874,7 +836,7 @@ def test_02_02_06_01(self, test): "GET", self.node_url + "flows/" + sender["flow_id"] ) if response.status_code != 200: - return test.FAIL(response.json()) + return test.FAIL("The request has not succeeded.", response.json()) flow = response.json() color_sampling = self.getSdpColorSampling(flow["components"]) if color_sampling == "invalid array of video components": @@ -882,41 +844,44 @@ def test_02_02_06_01(self, test): constraint_set = {} for item in REF_SUPPORTED_CONSTRAINTS_VIDEO: + try: + if item == "urn:x-nmos:cap:meta:label": + constraint_set["urn:x-nmos:cap:meta:label"] = "video constraint" + if item == "urn:x-nmos:cap:meta:preference": + constraint_set["urn:x-nmos:cap:meta:preference"] = 0 + if item == "urn:x-nmos:cap:meta:enabled": + 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"]] + } + if item == "urn:x-nmos:cap:format:grain_rate": + constraint_set["urn:x-nmos:cap:format:grain_rate"] = { + "enum": [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"]] + } + if item == "urn:x-nmos:cap:format:frame_height": + constraint_set["urn:x-nmos:cap:format:frame_height"] = { + "enum": [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"]] + } + if item == "urn:x-nmos:cap:format:color_sampling": + constraint_set["urn:x-nmos:cap:format:color_sampling"] = { + "enum": [color_sampling] + } + if item == "urn:x-nmos:cap:format:component_depth": + constraint_set["urn:x-nmos:cap:format:component_depth"] = { + "enum": [flow["components"][0]["bit_depth"]] + } + except Exception: + pass - if item == "urn:x-nmos:cap:meta:label": - constraint_set["urn:x-nmos:cap:meta:label"] = "video constraint" - if item == "urn:x-nmos:cap:meta:preference": - constraint_set["urn:x-nmos:cap:meta:preference"] = 0 - if item == "urn:x-nmos:cap:meta:enabled": - 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"]] - } - if item == "urn:x-nmos:cap:format:grain_rate": - constraint_set["urn:x-nmos:cap:format:grain_rate"] = { - "enum": [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"]] - } - if item == "urn:x-nmos:cap:format:frame_height": - constraint_set["urn:x-nmos:cap:format:frame_height"] = { - "enum": [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"]] - } - if item == "urn:x-nmos:cap:format:color_sampling": - constraint_set["urn:x-nmos:cap:format:color_sampling"] = { - "enum": [color_sampling] - } - if item == "urn:x-nmos:cap:format:component_depth": - constraint_set["urn:x-nmos:cap:format:component_depth"] = { - "enum": [flow["components"][0]["bit_depth"]] - } self.constraints[sender_id] = {"constraint_sets": [constraint_set]} _, response = TestHelper.do_request( @@ -925,13 +890,13 @@ def test_02_02_06_01(self, test): json=self.constraints[sender_id], ) if response.status_code != 200: - return test.FAIL(response.json()) + return test.FAIL("The request has not succeeded.", response.json()) _, response = TestHelper.do_request( "GET", self.node_url + "flows/" + sender["flow_id"] ) if response.status_code != 200: - return test.FAIL(response.json()) + return test.FAIL("The request has not succeeded.", response.json()) new_flow = response.json() new_color_sampling = self.getSdpColorSampling(new_flow["components"]) @@ -939,53 +904,54 @@ def test_02_02_06_01(self, test): return test.FAIL("invalid array of video components") for item in REF_SUPPORTED_CONSTRAINTS_VIDEO: - - if item == "urn:x-nmos:cap:format:media_type": - if 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"]: - return test.FAIL("different grain_rate") - if item == "urn:x-nmos:cap:format:frame_width": - if 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"]: - return test.FAIL("different frame_height") - if item == "urn:x-nmos:cap:format:interlace_mode": - if 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"] - != new_flow["components"][0]["bit_depth"] - ): - return test.FAIL("different component_depth") - + try: + if item == "urn:x-nmos:cap:format:media_type": + if 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"]: + return test.FAIL("different grain_rate") + if item == "urn:x-nmos:cap:format:frame_width": + if 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"]: + return test.FAIL("different frame_height") + if item == "urn:x-nmos:cap:format:interlace_mode": + if 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"] + != new_flow["components"][0]["bit_depth"] + ): + return test.FAIL("different component_depth") + except Exception: + pass _, response = TestHelper.do_request( "DELETE", self.compat_url + "senders/" + sender_id + "constraints/active/", ) if response.status_code != 200: - return test.FAIL(response.json()) + return test.FAIL("The request has not succeeded.", response.json()) return test.PASS() def test_02_02_06_02(self, test): - """Verify that setting NOP constraints for supported - constraints does not change the flow of a sender (audio) - and that the state goes from \"unconstrained\" to \"constrained\" + """ + Verify that setting NOP constraints for supported + constraints doesn't change the flow of a sender(audio). """ if len(self.flow_format_audio) == 0: - return test.UNCLEAR("There is no audio format") + return test.UNCLEAR("There is no audio format.") for sender_id in self.flow_format_audio: _, response = TestHelper.do_request( "GET", self.compat_url + "senders/" + sender_id + "status/" ) if response.status_code != 200: - test.FAIL(response.json()) + test.FAIL("The request has not succeeded.", response.json()) state = response.json()["state"] if state in ["awating_essence", "no_essence"]: for i in range(0, 5): @@ -1010,7 +976,7 @@ def test_02_02_06_02(self, test): "GET", self.node_url + "flows/" + sender["flow_id"] ) if response.status_code != 200: - return test.FAIL(response.json()) + return test.FAIL("The request has not succeeded.", response.json()) flow = response.json() constraint_set = {} @@ -1018,34 +984,36 @@ def test_02_02_06_02(self, test): "GET", self.node_url + "sources/" + flow["source_id"] ) if response.status_code != 200: - return test.FAIL(response.json()) + return test.FAIL("The request has not succeeded.", response.json()) source = response.json() for item in REF_SUPPORTED_CONSTRAINTS_AUDIO: - - if item == "urn:x-nmos:cap:meta:label": - constraint_set["urn:x-nmos:cap:meta:label"] = "video constraint" - if item == "urn:x-nmos:cap:meta:preference": - constraint_set["urn:x-nmos:cap:meta:preference"] = 0 - if item == "urn:x-nmos:cap:meta:enabled": - 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"]] - } - if item == "urn:x-nmos:cap:format:sample_rate": - constraint_set["urn:x-nmos:cap:format:sample_rate"] = { - "enum": [flow["sample_rate"]] - } - if item == "urn:x-nmos:cap:format:channel_count": - constraint_set["urn:x-nmos:cap:format:channel_count"] = { - "enum": [len(source["channels"])] - } - if item == "urn:x-nmos:cap:format:sample_depth": - constraint_set["urn:x-nmos:cap:format:sample_depth"] = { - "enum": [flow["bit_depth"]] - } - + try: + + if item == "urn:x-nmos:cap:meta:label": + constraint_set["urn:x-nmos:cap:meta:label"] = "audio constraint" + if item == "urn:x-nmos:cap:meta:preference": + constraint_set["urn:x-nmos:cap:meta:preference"] = 0 + if item == "urn:x-nmos:cap:meta:enabled": + 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"]] + } + if item == "urn:x-nmos:cap:format:sample_rate": + constraint_set["urn:x-nmos:cap:format:sample_rate"] = { + "enum": [flow["sample_rate"]] + } + if item == "urn:x-nmos:cap:format:channel_count": + constraint_set["urn:x-nmos:cap:format:channel_count"] = { + "enum": [len(source["channels"])] + } + if item == "urn:x-nmos:cap:format:sample_depth": + constraint_set["urn:x-nmos:cap:format:sample_depth"] = { + "enum": [flow["bit_depth"]] + } + except Exception: + pass self.constraints[sender_id] = {"constraint_sets": [constraint_set]} _, response = TestHelper.do_request( "PUT", @@ -1053,56 +1021,57 @@ def test_02_02_06_02(self, test): json=self.constraints[sender_id], ) if response.status_code != 200: - return test.FAIL(response.json()) + return test.FAIL("The request has not succeeded.", response.json()) _, response = TestHelper.do_request( "GET", self.node_url + "flows/" + sender["flow_id"] ) if response.status_code != 200: - return test.FAIL(response.json()) + return test.FAIL("The request has not succeeded.", response.json()) new_flow = response.json() _, response = TestHelper.do_request( "GET", self.node_url + "sources/" + flow["source_id"] ) if response.status_code != 200: - return test.FAIL(response.json()) + return test.FAIL("The request has not succeeded.", response.json()) new_source = response.json() for item in REF_SUPPORTED_CONSTRAINTS_AUDIO: - - if item == "urn:x-nmos:cap:format:media_type": - if 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"]: - 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"]: - return test.FAIL("different sample_depth") - + try: + if item == "urn:x-nmos:cap:format:media_type": + if 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"]: + 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"]: + return test.FAIL("different sample_depth") + except Exception: + pass _, response = TestHelper.do_request( "DELETE", self.compat_url + "senders/" + sender_id + "constraints/active/", ) if response.status_code != 200: - return test.FAIL(response.json()) + return test.FAIL("The request has not succeeded.", response.json()) return test.PASS() def test_02_02_07_01(self, test): "Verify that the device adhere to the preference of the constraint_set." if len(self.flow_format_video) == 0: - return test.UNCLEAR("There is no audio format ") + return test.UNCLEAR("There is no video format.") for sender_id in self.flow_format_video: _, response = TestHelper.do_request( "GET", self.compat_url + "senders/" + sender_id + "status/" ) if response.status_code != 200: - return test.FAIL(response.json()) + return test.FAIL("The request has not succeeded.", response.json()) state = response.json()["state"] if state in ["awating_essence", "no_essence"]: for i in range(0, 5): @@ -1128,7 +1097,7 @@ def test_02_02_07_01(self, test): "GET", self.node_url + "flows/" + sender["flow_id"] ) if response.status_code != 200: - return test.FAIL(response.json()) + return test.FAIL("The request has not succeeded.", response.json()) flow = response.json() color_sampling = self.getSdpColorSampling(flow["components"]) if color_sampling == "invalid array of video components": @@ -1137,78 +1106,87 @@ def test_02_02_07_01(self, test): constraint_set1 = {} for item in REF_SUPPORTED_CONSTRAINTS_VIDEO: - - if item == "urn:x-nmos:cap:meta:label": - constraint_set0["urn:x-nmos:cap:meta:label"] = "video constraint" - if item == "urn:x-nmos:cap:meta:preference": - constraint_set0["urn:x-nmos:cap:meta:preference"] = 0 - if item == "urn:x-nmos:cap:meta:enabled": - 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"]] - } - if item == "urn:x-nmos:cap:format:grain_rate": - constraint_set0["urn:x-nmos:cap:format:grain_rate"] = { - "enum": [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"]] - } - if item == "urn:x-nmos:cap:format:frame_height": - constraint_set0["urn:x-nmos:cap:format:frame_height"] = { - "enum": [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"]] - } - if item == "urn:x-nmos:cap:format:color_sampling": - constraint_set0["urn:x-nmos:cap:format:color_sampling"] = { - "enum": [color_sampling] - } - if item == "urn:x-nmos:cap:format:component_depth": - constraint_set0["urn:x-nmos:cap:format:component_depth"] = { - "enum": [flow["components"][0]["bit_depth"]] - } + try: + + if item == "urn:x-nmos:cap:meta:label": + constraint_set0[ + "urn:x-nmos:cap:meta:label" + ] = "video constraint" + if item == "urn:x-nmos:cap:meta:preference": + constraint_set0["urn:x-nmos:cap:meta:preference"] = 0 + if item == "urn:x-nmos:cap:meta:enabled": + 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"]] + } + if item == "urn:x-nmos:cap:format:grain_rate": + constraint_set0["urn:x-nmos:cap:format:grain_rate"] = { + "enum": [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"]] + } + if item == "urn:x-nmos:cap:format:frame_height": + constraint_set0["urn:x-nmos:cap:format:frame_height"] = { + "enum": [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"]] + } + if item == "urn:x-nmos:cap:format:color_sampling": + constraint_set0["urn:x-nmos:cap:format:color_sampling"] = { + "enum": [color_sampling] + } + if item == "urn:x-nmos:cap:format:component_depth": + constraint_set0["urn:x-nmos:cap:format:component_depth"] = { + "enum": [flow["components"][0]["bit_depth"]] + } + except Exception: + pass for item in REF_SUPPORTED_CONSTRAINTS_VIDEO: - - if item == "urn:x-nmos:cap:meta:label": - constraint_set1["urn:x-nmos:cap:meta:label"] = "video constraint" - if item == "urn:x-nmos:cap:meta:preference": - constraint_set1["urn:x-nmos:cap:meta:preference"] = -100 - if item == "urn:x-nmos:cap:meta:enabled": - 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"]] - } - 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"])] - } - if item == "urn:x-nmos:cap:format:frame_width": - constraint_set1["urn:x-nmos:cap:format:frame_width"] = { - "enum": [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"]] - } - if item == "urn:x-nmos:cap:format:interlace_mode": - constraint_set1["urn:x-nmos:cap:format:interlace_mode"] = { - "enum": [flow["interlace_mode"]] - } - if item == "urn:x-nmos:cap:format:color_sampling": - constraint_set1["urn:x-nmos:cap:format:color_sampling"] = { - "enum": [color_sampling] - } - if item == "urn:x-nmos:cap:format:component_depth": - constraint_set1["urn:x-nmos:cap:format:component_depth"] = { - "enum": [flow["components"][0]["bit_depth"]] - } + try: + if item == "urn:x-nmos:cap:meta:label": + constraint_set1[ + "urn:x-nmos:cap:meta:label" + ] = "video constraint" + if item == "urn:x-nmos:cap:meta:preference": + constraint_set1["urn:x-nmos:cap:meta:preference"] = -100 + if item == "urn:x-nmos:cap:meta:enabled": + 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"]] + } + 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"])] + } + if item == "urn:x-nmos:cap:format:frame_width": + constraint_set1["urn:x-nmos:cap:format:frame_width"] = { + "enum": [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"]] + } + if item == "urn:x-nmos:cap:format:interlace_mode": + constraint_set1["urn:x-nmos:cap:format:interlace_mode"] = { + "enum": [flow["interlace_mode"]] + } + if item == "urn:x-nmos:cap:format:color_sampling": + constraint_set1["urn:x-nmos:cap:format:color_sampling"] = { + "enum": [color_sampling] + } + if item == "urn:x-nmos:cap:format:component_depth": + constraint_set1["urn:x-nmos:cap:format:component_depth"] = { + "enum": [flow["components"][0]["bit_depth"]] + } + except Exception: + pass self.constraints[sender_id] = { "constraint_sets": [constraint_set0, constraint_set0] @@ -1219,13 +1197,13 @@ def test_02_02_07_01(self, test): json=self.constraints[sender_id], ) if response.status_code != 200: - return test.FAIL(response.json()) + return test.FAIL("The request has not succeeded.", response.json()) _, response = TestHelper.do_request( "GET", self.node_url + "flows/" + sender["flow_id"] ) if response.status_code != 200: - return test.FAIL(response.json()) + return test.FAIL("The request has not succeeded.", response.json()) new_flow = response.json() new_color_sampling = self.getSdpColorSampling(new_flow["components"]) @@ -1233,51 +1211,53 @@ def test_02_02_07_01(self, test): return test.FAIL("invalid array of video components") for item in REF_SUPPORTED_CONSTRAINTS_VIDEO: - - if item == "urn:x-nmos:cap:format:media_type": - if 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"]: - return test.FAIL("different grain_rate") - if item == "urn:x-nmos:cap:format:frame_width": - if 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"]: - return test.FAIL("different frame_height") - if item == "urn:x-nmos:cap:format:interlace_mode": - if 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"] - != new_flow["components"][0]["bit_depth"] - ): - return test.FAIL("different component_depth") + try: + if item == "urn:x-nmos:cap:format:media_type": + if 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"]: + return test.FAIL("different grain_rate") + if item == "urn:x-nmos:cap:format:frame_width": + if 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"]: + return test.FAIL("different frame_height") + if item == "urn:x-nmos:cap:format:interlace_mode": + if 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"] + != new_flow["components"][0]["bit_depth"] + ): + return test.FAIL("different component_depth") + except Exception: + pass _, response = TestHelper.do_request( "DELETE", self.compat_url + "senders/" + sender_id + "constraints/active/", ) if response.status_code != 200: - return test.FAIL(response.json()) + return test.FAIL("The request has not succeeded.", response.json()) return test.PASS() def test_02_02_07_02(self, test): "Verify that the device adhere to the preference of the constraint_set." if len(self.flow_format_audio) == 0: - return test.UNCLEAR("There is no audio format") + return test.UNCLEAR("There is no audio format.") for sender_id in self.flow_format_audio: _, response = TestHelper.do_request( "GET", self.compat_url + "senders/" + sender_id + "status/" ) if response.status_code != 200: - test.FAIL(response.json()) + test.FAIL("The request has not succeeded.", response.json()) state = response.json()["state"] if state in ["awating_essence", "no_essence"]: for i in range(0, 5): @@ -1303,67 +1283,75 @@ def test_02_02_07_02(self, test): "GET", self.node_url + "flows/" + sender["flow_id"] ) if response.status_code != 200: - return test.FAIL(response.json()) + return test.FAIL("The request has not succeeded.", response.json()) flow = response.json() _, response = TestHelper.do_request( "GET", self.node_url + "sources/" + flow["source_id"] ) if response.status_code != 200: - return test.FAIL(response.json()) + return test.FAIL("The request has not succeeded.", response.json()) source = response.json() constraint_set0 = {} constraint_set1 = {} for item in REF_SUPPORTED_CONSTRAINTS_AUDIO: - - if item == "urn:x-nmos:cap:meta:label": - constraint_set0["urn:x-nmos:cap:meta:label"] = "video constraint" - if item == "urn:x-nmos:cap:meta:preference": - constraint_set0["urn:x-nmos:cap:meta:preference"] = 0 - if item == "urn:x-nmos:cap:meta:enabled": - 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"]] - } - if item == "urn:x-nmos:cap:format:sample_rate": - constraint_set0["urn:x-nmos:cap:format:sample_rate"] = { - "enum": [flow["sample_rate"]] - } - if item == "urn:x-nmos:cap:format:channel_count": - constraint_set0["urn:x-nmos:cap:format:channel_count"] = { - "enum": [len(source["channels"])] - } - if item == "urn:x-nmos:cap:format:sample_depth": - constraint_set0["urn:x-nmos:cap:format:sample_depth"] = { - "enum": [flow["bit_depth"]] - } + try: + if item == "urn:x-nmos:cap:meta:label": + constraint_set0[ + "urn:x-nmos:cap:meta:label" + ] = "audio constraint" + if item == "urn:x-nmos:cap:meta:preference": + constraint_set0["urn:x-nmos:cap:meta:preference"] = 0 + if item == "urn:x-nmos:cap:meta:enabled": + 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"]] + } + if item == "urn:x-nmos:cap:format:sample_rate": + constraint_set0["urn:x-nmos:cap:format:sample_rate"] = { + "enum": [flow["sample_rate"]] + } + if item == "urn:x-nmos:cap:format:channel_count": + constraint_set0["urn:x-nmos:cap:format:channel_count"] = { + "enum": [len(source["channels"])] + } + if item == "urn:x-nmos:cap:format:sample_depth": + constraint_set0["urn:x-nmos:cap:format:sample_depth"] = { + "enum": [flow["bit_depth"]] + } + except Exception: + pass for item in REF_SUPPORTED_CONSTRAINTS_AUDIO: - - if item == "urn:x-nmos:cap:meta:label": - constraint_set1["urn:x-nmos:cap:meta:label"] = "video constraint" - if item == "urn:x-nmos:cap:meta:preference": - constraint_set1["urn:x-nmos:cap:meta:preference"] = -100 - if item == "urn:x-nmos:cap:meta:enabled": - 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"]] - } - 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"])] - } - if item == "urn:x-nmos:cap:format:channel_count": - constraint_set1["urn:x-nmos:cap:format:channel_count"] = { - "enum": [len(source["channels"])] - } - if item == "urn:x-nmos:cap:format:sample_depth": - constraint_set1["urn:x-nmos:cap:format:sample_depth"] = { - "enum": [flow["bit_depth"]] - } + try: + if item == "urn:x-nmos:cap:meta:label": + constraint_set1[ + "urn:x-nmos:cap:meta:label" + ] = "video constraint" + if item == "urn:x-nmos:cap:meta:preference": + constraint_set1["urn:x-nmos:cap:meta:preference"] = -100 + if item == "urn:x-nmos:cap:meta:enabled": + 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"]] + } + 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"])] + } + if item == "urn:x-nmos:cap:format:channel_count": + constraint_set1["urn:x-nmos:cap:format:channel_count"] = { + "enum": [len(source["channels"])] + } + if item == "urn:x-nmos:cap:format:sample_depth": + constraint_set1["urn:x-nmos:cap:format:sample_depth"] = { + "enum": [flow["bit_depth"]] + } + except Exception: + pass self.constraints[sender_id] = { "constraint_sets": [constraint_set0, constraint_set1] @@ -1375,40 +1363,41 @@ def test_02_02_07_02(self, test): json=self.constraints[sender_id], ) if response.status_code != 200: - return test.FAIL(response.json()) + return test.FAIL("The request has not succeeded.", response.json()) _, response = TestHelper.do_request( "GET", self.node_url + "flows/" + sender["flow_id"] ) if response.status_code != 200: - return test.FAIL(response.json()) + return test.FAIL("The request has not succeeded.", response.json()) new_flow = response.json() _, response = TestHelper.do_request( "GET", self.node_url + "sources/" + flow["source_id"] ) if response.status_code != 200: - return test.FAIL(response.json()) + return test.FAIL("The request has not succeeded.", response.json()) new_source = response.json() for item in REF_SUPPORTED_CONSTRAINTS_AUDIO: - - if item == "urn:x-nmos:cap:format:media_type": - if 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"]: - 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"]: - return test.FAIL("different sample_depth") - + try: + if item == "urn:x-nmos:cap:format:media_type": + if 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"]: + 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"]: + return test.FAIL("different sample_depth") + except Exception: + pass _, response = TestHelper.do_request( "DELETE", self.compat_url + "senders/" + sender_id + "constraints/active/", ) if response.status_code != 200: - return test.FAIL(response.json()) + return test.FAIL("The request has not succeeded.", response.json()) return test.PASS() From 0961c09eb55eb8b6d1adf3ef20e9d0ec506287c1 Mon Sep 17 00:00:00 2001 From: ggeorgea Date: Wed, 12 Apr 2023 09:41:28 -0400 Subject: [PATCH 03/11] New IS-11 PR after IS-11 branch merged with master. Code is limited to PR-761 comments fixes only Signed-off-by: ggeorgea --- nmostesting/Config.py | 1 - nmostesting/GenericTest.py | 18 +- nmostesting/NMOSTesting.py | 6 + nmostesting/nmosTestingComplexCompare.py | 262 +++++ nmostesting/suites/IS1101Test.py | 315 +++++ nmostesting/suites/IS1102Test.py | 1371 +++++++++++++++++++++- 6 files changed, 1960 insertions(+), 13 deletions(-) create mode 100644 nmostesting/nmosTestingComplexCompare.py diff --git a/nmostesting/Config.py b/nmostesting/Config.py index ab040d44..fcf898e9 100644 --- a/nmostesting/Config.py +++ b/nmostesting/Config.py @@ -186,7 +186,6 @@ # Bash shell to use for running testssl.sh TEST_SSL_BASH = "bash" - # Definition of each API specification and its versions. SPECIFICATIONS = { "is-04": { diff --git a/nmostesting/GenericTest.py b/nmostesting/GenericTest.py index c9627748..728cda70 100644 --- a/nmostesting/GenericTest.py +++ b/nmostesting/GenericTest.py @@ -341,13 +341,13 @@ def check_response(self, schema, method, response): cors_valid, cors_message = self.check_CORS(method, response.headers) if not cors_valid: return False, cors_message - - try: - self.validate_schema(response.json(), schema) - except jsonschema.ValidationError: - return False, "Response schema validation error" - except json.JSONDecodeError: - return False, "Invalid JSON received" + if response.headers["Content-Type"] == "application/json": + try: + self.validate_schema(response.json(), schema) + except jsonschema.ValidationError: + return False, "Response schema validation error" + except json.JSONDecodeError: + return False, "Invalid JSON received" return True, ctype_message @@ -663,11 +663,9 @@ def check_api_resource(self, test, resource, response_code, api, path): elif resource[1]['method'].upper() in ["HEAD", "OPTIONS"]: # For methods which don't return a payload, return immediately after the CORS header check return True, "" - # For all other methods proceed to check the response against the schema schema = self.get_schema(api, resource[1]["method"], resource[0], response.status_code) - - if not schema: + if not schema and response.headers["Content-Type"] == 'application/json': raise NMOSTestException(test.MANUAL("Test suite unable to locate schema")) return self.check_response(schema, resource[1]["method"], response) diff --git a/nmostesting/NMOSTesting.py b/nmostesting/NMOSTesting.py index d5916106..f57fb818 100644 --- a/nmostesting/NMOSTesting.py +++ b/nmostesting/NMOSTesting.py @@ -342,6 +342,12 @@ "specs": [{ "spec_key": "is-11", "api_key": "streamcompatibility" + }, { + "spec_key": "is-04", + "api_key": "node" + }, { + "spec_key": "is-05", + "api_key": "connection" }], "class": IS1101Test.IS1101Test }, diff --git a/nmostesting/nmosTestingComplexCompare.py b/nmostesting/nmosTestingComplexCompare.py new file mode 100644 index 00000000..42b46df3 --- /dev/null +++ b/nmostesting/nmosTestingComplexCompare.py @@ -0,0 +1,262 @@ +# constraint_set schema +# +# { +# "$schema": "http://json-schema.org/draft-04/schema#", +# "description": "Describes a Constraint Set", +# "title": "Constraint Set", +# "type": "object", +# "minProperties": 1, +# "properties": { +# "urn:x-nmos:cap:meta:label": { +# "description": "Freeform string label for the Constraint Set", +# "type": "string" +# }, +# "urn:x-nmos:cap:meta:preference": { +# "description": "This value expresses the relative 'weight' that the Receiver assigns to +# its preference for the streams satisfied by the associated Constraint Set. +# The weight is an integer in the range -100 through 100, +# where -100 is least preferred and 100 is most preferred. +# When the attribute is omitted, the effective value for the associated Constraint Set is 0.", +# "type": "integer", +# "default": 0, +# "maximum": 100, +# "minimum": -100 +# }, +# "urn:x-nmos:cap:meta:enabled": { +# "description": "This value indicates whether a Constraint Set is available to use immediately (true) +# or whether this is an offline capability which can be activated via +# some unspecified configuration mechanism (false). +# When the attribute is omitted its value is assumed to be true.", +# "type": "boolean", +# "default": true +# } +# }, +# "patternProperties": { +# "^urn:x-nmos:cap:(?!meta:)": { +# "$ref": "param_constraint.json" +# } +# } +# } +# +# We want to compare that two constraint sets are equal based on the properties of teh schema that allow default +# values for properties not defined. For example the "preference" property defined to true or undefined is the +# same such that comparing an object A heving the property set to true and an object B not having the property +# defined will indicate equality because the schema defines a default value. +# +# This function verifies two constraint sets where at most one constraint set is expected and each constraint +# set must have at most one "sample_rate" paremeter constraint. We then compare the two objects based on their +# respective schemas: constraint_set and rational. We expect the constraint to be defined using the "enum" +# keyword with a single array entry. +# +# return true if equal, false otherwise + + +def compare_complex_sample_rate_constraint( + response_constraints, sample_rate_constraints +): + + # NOTE: We already know that both response_constraints, sample_rate_constraints are valid + # and have been each independently been validated against the schemas. We only check equality. + + # Each constraint_sets array must have a single entry + if len(response_constraints) != 1 or len(sample_rate_constraints) != 1: + return False + + # If the sample_rate property is not defined, objects are not equivalent + try: + response_constraints_enum = response_constraints["constraint_sets"][0][ + "urn:x-nmos:cap:format:sample_rate" + ]["enum"] + except Exception: + return False + + # If the sample_rate property is not defined, objects are not equivalent + try: + sample_rate_constraints_enum = sample_rate_constraints["constraint_sets"][0][ + "urn:x-nmos:cap:format:sample_rate" + ]["enum"] + except Exception: + return False + + # There must be a single entry in the enum array + if len(response_constraints_enum) != 1 or len(sample_rate_constraints_enum) != 1: + return False + + try: + response_numerator = response_constraints_enum[0]["numerator"] + response_denominator = 1 + + if "denominator" in response_constraints_enum[0]: + response_denominator = response_constraints_enum[0]["denominator"] + + sample_rate_numerator = sample_rate_constraints_enum[0]["numerator"] + sample_rate_denominator = 1 + + if "denominator" in sample_rate_constraints_enum[0]: + sample_rate_denominator = sample_rate_constraints_enum[0]["denominator"] + + if ( + response_numerator != sample_rate_numerator + or response_denominator != sample_rate_denominator + ): + return False + except Exception: + return False + + # There must be no other patternProperties + for prop in sample_rate_constraints["constraint_sets"][0]: + if ( + prop != "urn:x-nmos:cap:format:sample_rate" + and prop != "urn:x-nmos:cap:meta:enabled" + and prop != "urn:x-nmos:cap:meta:preference" + ): + return False + + for prop in response_constraints["constraint_sets"][0]: + if ( + prop != "urn:x-nmos:cap:format:sample_rate" + and prop != "urn:x-nmos:cap:meta:enabled" + and prop != "urn:x-nmos:cap:meta:preference" + ): + return False + + # Check meta:enabled considering default values + response_enabled = True + if "urn:x-nmos:cap:meta:enabled" in response_constraints["constraint_sets"][0]: + response_enabled = response_constraints["constraint_sets"][0][ + "urn:x-nmos:cap:meta:enabled" + ] + + sample_rate_enabled = True + if "urn:x-nmos:cap:meta:enabled" in sample_rate_constraints["constraint_sets"][0]: + sample_rate_enabled = sample_rate_constraints["constraint_sets"][0][ + "urn:x-nmos:cap:meta:enabled" + ] + + if response_enabled != sample_rate_enabled: + return False + + # Check meta:preference considering default values + response_preference = 0 + if "urn:x-nmos:cap:meta:preference" in response_constraints["constraint_sets"][0]: + response_preference = response_constraints["constraint_sets"][0][ + "urn:x-nmos:cap:meta:preference" + ] + + sample_rate_preference = 0 + if ( + "urn:x-nmos:cap:meta:preference" + in sample_rate_constraints["constraint_sets"][0] + ): + sample_rate_preference = sample_rate_constraints["constraint_sets"][0][ + "urn:x-nmos:cap:meta:preference" + ] + + if response_preference != sample_rate_preference: + return False + + # If we get here it is because the two objects are equal + return True + + +def compare_complex_grain_rate_constraint(response_constraints, grain_rate_constraints): + + # NOTE: We already know that both response_constraints, grain_rate_constraints are valid + # and have been each independently been validated against the schemas. We only check equality. + + # Each constraint_sets array must have a single entry + if len(response_constraints) != 1 or len(grain_rate_constraints) != 1: + return False + + # If the grain_rate property is not defined, objects are not equivalent + try: + response_constraints_enum = response_constraints["constraint_sets"][0][ + "urn:x-nmos:cap:format:grain_rate" + ]["enum"] + except Exception: + return False + + # If the grain_rate property is not defined, objects are not equivalent + try: + grain_rate_constraints_enum = grain_rate_constraints["constraint_sets"][0][ + "urn:x-nmos:cap:format:grain_rate" + ]["enum"] + except Exception: + return False + + # There must be a single entry in the enum array + if len(response_constraints_enum) != 1 or len(grain_rate_constraints_enum) != 1: + return False + + try: + response_numerator = response_constraints_enum[0]["numerator"] + response_denominator = 1 + + if "denominator" in response_constraints_enum[0]: + response_denominator = response_constraints_enum[0]["denominator"] + + grain_rate_numerator = grain_rate_constraints_enum[0]["numerator"] + grain_rate_denominator = 1 + + if "denominator" in grain_rate_constraints_enum[0]: + grain_rate_denominator = grain_rate_constraints_enum[0]["denominator"] + + if ( + response_numerator != grain_rate_numerator + or response_denominator != grain_rate_denominator + ): + return False + except Exception: + return False + + # There must be no other patternProperties + for prop in grain_rate_constraints["constraint_sets"][0]: + if ( + prop != "urn:x-nmos:cap:format:grain_rate" + and prop != "urn:x-nmos:cap:meta:enabled" + and prop != "urn:x-nmos:cap:meta:preference" + ): + return False + + for prop in response_constraints["constraint_sets"][0]: + if ( + prop != "urn:x-nmos:cap:format:grain_rate" + and prop != "urn:x-nmos:cap:meta:enabled" + and prop != "urn:x-nmos:cap:meta:preference" + ): + return False + + # Check meta:enabled considering default values + response_enabled = True + if "urn:x-nmos:cap:meta:enabled" in response_constraints["constraint_sets"][0]: + response_enabled = response_constraints["constraint_sets"][0][ + "urn:x-nmos:cap:meta:enabled" + ] + + grain_rate_enabled = True + if "urn:x-nmos:cap:meta:enabled" in grain_rate_constraints["constraint_sets"][0]: + grain_rate_enabled = grain_rate_constraints["constraint_sets"][0][ + "urn:x-nmos:cap:meta:enabled" + ] + + if response_enabled != grain_rate_enabled: + return False + + # Check meta:preference considering default values + response_preference = 0 + if "urn:x-nmos:cap:meta:preference" in response_constraints["constraint_sets"][0]: + response_preference = response_constraints["constraint_sets"][0][ + "urn:x-nmos:cap:meta:preference" + ] + + grain_rate_preference = 0 + if "urn:x-nmos:cap:meta:preference" in grain_rate_constraints["constraint_sets"][0]: + grain_rate_preference = grain_rate_constraints["constraint_sets"][0][ + "urn:x-nmos:cap:meta:preference" + ] + + if response_preference != grain_rate_preference: + return False + + # If we get here it is because the two objects are equal + return True diff --git a/nmostesting/suites/IS1101Test.py b/nmostesting/suites/IS1101Test.py index 14277a0c..757f34ff 100644 --- a/nmostesting/suites/IS1101Test.py +++ b/nmostesting/suites/IS1101Test.py @@ -12,9 +12,14 @@ # See the License for the specific language governing permissions and # limitations under the License. +from ..NMOSUtils import NMOSUtils from ..GenericTest import GenericTest +from .. import TestHelper COMPAT_API_KEY = "streamcompatibility" +CONTROLS = "controls" +NODE_API_KEY = "node" +CONN_API_KEY = "connection" class IS1101Test(GenericTest): @@ -24,3 +29,313 @@ class IS1101Test(GenericTest): def __init__(self, apis): GenericTest.__init__(self, apis) self.compat_url = self.apis[COMPAT_API_KEY]["url"] + 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.outputs = [] + self.active_connected_outputs = [] + self.receivers = "" + self.receivers_outputs = "" + self.caps = "" + + # GENERAL TESTS + def test_01(self, test): + """Verify that IS-11 is exposed in the Node API as \ + urn:x-nmos:control:stream-compat/v1.0 at url /x-nmos/streamcompatibility/v1.0/ + """ + _, response = TestHelper.do_request( + "GET", self.node_url + "devices/" + ) + if response.status_code != 200: + return test.FAIL("The request has not succeeded: {}".format(response.json())) + controls = response.json()[0][CONTROLS] + control_href = "" + if len(controls) > 0: + for control in controls: + if control["type"] == "urn:x-nmos:control:stream-compat/" + self.apis[COMPAT_API_KEY]["version"]: + control_href = control["href"] + break + if not NMOSUtils.compare_urls(control_href, self.compat_url): + return test.FAIL("IS-11 URL is invalid") + return test.PASS() + return test.FAIL("IS-11 API is not available") + + def test_02(self, test): + "Put all senders into inactive state" + senders_url = self.conn_url + "single/senders/" + _, response = TestHelper.do_request("GET", senders_url) + if response.status_code != 200: + return test.FAIL("The request has not succeeded: {}".format(response.json())) + senders = response.json() + if len(senders) > 0: + for sender in senders: + url = senders_url + sender + "staged/" + deactivate_json = { + "master_enable": False, + "activation": {"mode": "activate_immediate"}, + } + + _, response = TestHelper.do_request("PATCH", url, json=deactivate_json) + if ( + response.status_code != 200 + or response.json()["master_enable"] + or response.json()["activation"]["mode"] != "activate_immediate" + ): + return test.FAIL("The request has not succeeded: {}".format(response.json())) + return test.PASS() + return test.UNCLEAR("Could not find any senders to test") + + def test_03(self, test): + "Put all the receivers into inactive state" + receivers_url = self.conn_url + "single/receivers/" + _, response = TestHelper.do_request("GET", receivers_url) + if response.status_code != 200: + return test.FAIL("The request has not succeeded: {}".format(response.json())) + receivers = response.json() + if len(receivers) > 0: + for receiver in receivers: + url = receivers_url + receiver + "staged/" + deactivate_json = { + "master_enable": False, + "activation": {"mode": "activate_immediate"}, + } + _, response = TestHelper.do_request("PATCH", url, json=deactivate_json) + if ( + response.status_code != 200 + or response.json()["master_enable"] + or response.json()["activation"]["mode"] != "activate_immediate" + ): + return test.FAIL("The request has not succeeded: {}".format(response.json())) + + return test.PASS() + + return test.UNCLEAR("Could not find any receivers to test") + + # OUTPUTS TESTS + def test_03_01(self, test): + """ + Verify that the device supports the concept of Output. + """ + _, response = TestHelper.do_request("GET", self.compat_url + "outputs/") + + if response.status_code == 200: + if len(response.json()) != 0: + self.outputs.append(response.json()[0]) + if len(self.outputs) == 0: + return test.UNCLEAR("No outputs") + return test.PASS() + return test.FAIL("The request has not succeeded: {}".format(response.json())) + + def test_03_02(self, test): + """ + Verify that some of the outputs of the device are connected. + """ + if len(self.outputs) == 0: + return test.UNCLEAR("No IS-11 outputs") + for output in self.outputs: + _, response = TestHelper.do_request( + "GET", self.compat_url + "outputs/" + output + "properties/" + ) + if response.status_code == 200: + outputs_properties_json = [] + outputs_properties_json.append(response.json()) + for output in outputs_properties_json: + if output["connected"]: + self.connected_outputs.append(output["id"]) + else: + return test.FAIL("The request has not succeeded: {}".format(response.json())) + if len(self.connected_outputs) == 0: + return test.UNCLEAR("No connected outputs.") + return test.PASS() + + def test_03_03(self, test): + """ + Verify that all connected outputs do not have + a signal as test 0 put all of the receivers inactive. + """ + if len(self.connected_outputs) == 0: + return test.UNCLEAR("No connected outputs.") + for output_id in self.connected_outputs: + _, response = TestHelper.do_request( + "GET", self.compat_url + "outputs/" + output_id + "/properties/" + ) + if response.status_code == 200: + if response.json()["status"]["state"] == "signal_present": + self.active_connected_outputs.append(response.json()) + else: + return test.FAIL("The request has not succeeded: {}".format(response.json())) + if len(self.active_connected_outputs) != 0: + return test.UNCLEAR( + "Connected output have a signal while all receivers are inactive." + ) + return test.PASS() + + def test_03_04(self, test): + """ + Verify that connected outputs supporting EDID behave according to the RAML file. + """ + if len(self.connected_outputs) == 0: + return test.UNCLEAR("No connected outputs.") + for output_id in self.connected_outputs: + _, response = TestHelper.do_request( + "GET", self.compat_url + "outputs/" + output_id + "/properties/" + ) + if response.status_code == 200: + if response.json()["edid_support"]: + self.edid_connected_outputs.append(response.json()["id"]) + else: + return test.FAIL("The request has not succeeded: {}".format(response.json())) + if self.edid_connected_outputs == 0: + return test.UNCLEAR("Outputs not supporting edid.") + return test.PASS() + + def test_03_04_01(self, test): + """ + Verify that an output indicating EDID support behaves according to the RAML file. + """ + if len(self.edid_connected_outputs) == 0: + return test.UNCLEAR("No edid connected outputs.") + for output_id in self.edid_connected_outputs: + _, response = TestHelper.do_request( + "GET", self.compat_url + "outputs/" + output_id + ) + if response.status_code != 200: + return test.FAIL("The request has not succeeded: {}".format(response.json())) + return test.PASS() + + def test_03_04_02(self, test): + """ + Verify that a valid EDID can be retrieved from the device; + this EDID represents the default EDID of the device. + """ + is_valid_response = True + if len(self.edid_connected_outputs) == 0: + return test.UNCLEAR("No edid connected outputs.") + for output_id in self.edid_connected_outputs: + _, response = TestHelper.do_request( + "GET", self.compat_url + "outputs/" + output_id + "/edid/" + ) + if ( + response.status_code != 200 + or response.headers["Content-Type"] != "application/octet-stream" + ): + is_valid_response = False + break + if is_valid_response: + return test.PASS() + return test.FAIL("The request has not succeeded: {}".format(response.json())) + + def test_03_05(self, test): + """ + Verify that connected outputs not supporting EDID behave according to the RAML file. + """ + if len(self.connected_outputs) == 0: + return test.UNCLEAR("No connected outputs.") + for output_id in self.connected_outputs: + _, response = TestHelper.do_request( + "GET", self.compat_url + "outputs/" + output_id + "/properties/" + ) + if response.status_code == 200: + if not response.json()["edid_support"]: + self.not_edid_connected_outputs.append(response.json()["id"]) + else: + return test.FAIL("The request has not succeeded: {}".format(response.json())) + if len(self.not_edid_connected_outputs) == 0: + return test.UNCLEAR("Outputs supporting edid.") + return test.PASS() + + def test_03_05_01(self, test): + """ + Verify that there is no EDID support. + """ + if len(self.not_edid_connected_outputs) == 0: + return test.UNCLEAR("None of not edid connected outputs.") + for output_id in self.not_edid_connected_outputs: + _, response = TestHelper.do_request( + "GET", self.compat_url + "outputs/" + output_id + "/edid/" + ) + if response.status_code != 204: + return test.FAIL("Status code should be 204.") + return test.PASS() + + # RECEIVERS TESTS + def test_04_01(self, test): + """ + Verify that the device supports the concept of IS-11 Receiver. + """ + _, response = TestHelper.do_request("GET", self.compat_url + "receivers/") + + if response.status_code != 200: + return test.FAIL("The request has not succeeded: {}".format(response.json())) + self.receivers = response.json() + if len(self.receivers) == 0: + return test.UNCLEAR("No IS-11 receivers.") + return test.PASS() + + def test_04_01_01(self, test): + """ + Verify that IS-11 Receivers exist on the Node API as Receivers. + """ + if len(self.receivers) == 0: + return test.UNCLEAR("No IS-11 receivers.") + for receiver_id in self.receivers: + _, response = TestHelper.do_request( + "GET", self.node_url + "receivers/" + receiver_id + ) + if response.status_code != 200: + return test.FAIL("The request has not succeeded: {}".format(response.json())) + if response.json()["id"] != receiver_id[:-1]: + return test.UNCLEAR( + "The IS-11 Receiver doesn't exist on the Node API as receiver." + ) + return test.PASS() + + def test_04_02(self, test): + """ + Verify receivers (generic with/without outputs) + """ + _, response = TestHelper.do_request("GET", self.compat_url + "receivers/") + if response.status_code != 200: + return test.FAIL("The request has not succeeded: {}".format(response.json())) + self.receivers = response.json() + if len(self.receivers) == 0: + return test.UNCLEAR("No IS-11 receivers.") + return test.PASS() + + def test_04_02_01(self, test): + """ + Verify that the status is "unknown" or "non_compliant_stream" + as per our pre-conditions of not being master_enabled. + """ + if len(self.receivers) == 0: + return test.UNCLEAR("No IS-11 receivers.") + for receiver_id in self.receivers: + _, response = TestHelper.do_request( + "GET", self.compat_url + "receivers/" + receiver_id + "status/" + ) + if response.status_code != 200: + return test.FAIL("The request has not succeeded: {}".format(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.PASS() + + def test_04_02_02(self, test): + """ + Verify that the Receiver supports Receiver Capabilities. + """ + if len(self.receivers) == 0: + return test.UNCLEAR("No IS-11 receivers.") + for receiver_id in self.receivers: + _, response = TestHelper.do_request( + "GET", self.node_url + "receivers/" + receiver_id + ) + if response.status_code != 200: + return test.FAIL("The request has not succeeded: {}".format(response.json())) + self.caps = response.json()["caps"] + 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.UNCLEAR("The receiver does not support BCP-004-01.") + return test.PASS() diff --git a/nmostesting/suites/IS1102Test.py b/nmostesting/suites/IS1102Test.py index e0919fe8..48e9dd65 100644 --- a/nmostesting/suites/IS1102Test.py +++ b/nmostesting/suites/IS1102Test.py @@ -13,16 +13,1383 @@ # limitations under the License. from ..GenericTest import GenericTest +from .. import TestHelper +import time +import re +from .. import nmosTestingComplexCompare -NODE_API_KEY = "node" COMPAT_API_KEY = "streamcompatibility" +NODE_API_KEY = "node" +CONTROLS = "controls" + +REF_SUPPORTED_CONSTRAINTS_VIDEO = [ + "urn:x-nmos:cap:meta:label", + "urn:x-nmos:cap:meta:preference", + "urn:x-nmos:cap:meta:enabled", + "urn:x-nmos:cap:format:media_type", + "urn:x-nmos:cap:format:grain_rate", + "urn:x-nmos:cap:format:frame_width", + "urn:x-nmos:cap:format:frame_height", + "urn:x-nmos:cap:format:interlace_mode", + "urn:x-nmos:cap:format:color_sampling", + "urn:x-nmos:cap:format:component_depth", +] +REF_SUPPORTED_CONSTRAINTS_AUDIO = [ + "urn:x-nmos:cap:meta:label", + "urn:x-nmos:cap:meta:preference", + "urn:x-nmos:cap:meta:enabled", + "urn:x-nmos:cap:format:media_type", + "urn:x-nmos:cap:format:channel_count", + "urn:x-nmos:cap:format:sample_rate", + "urn:x-nmos:cap:format:sample_depth", +] class IS1102Test(GenericTest): """ - Runs Node Tests covering both IS-04 and IS-11 + Runs Node Tests covering IS-11 """ def __init__(self, apis): GenericTest.__init__(self, apis,) self.node_url = self.apis[NODE_API_KEY]["url"] self.compat_url = self.apis[COMPAT_API_KEY]["url"] + self.senders = "" + self.senders_2 = "" + self.flow_format = {} + self.flow_format_audio = [] + self.flow_format_video = [] + self.flow_width = {} + self.flow_height = {} + self.flow_grain_rate = {} + self.flow_sample_rate = {} + self.version = {} + self.grain_rate_constraints = {} + self.empty_constraints = {} + self.sample_rate_constraints = {} + self.constraints = {} + + # SENDERS TESTS + """ + Runs Node Tests covering IS-11 for Senders + """ + + def getSdpColorSampling(self, flow_components): + """ + getColorSamplingFromComponents supports RGB, + YCbCr-4:4:4, YCbCr-4:2:2, YCbCr-4:2:0 and assumes + that the bit-depth is compliant without verifying it. + """ + names = [] + widths = [] + heights = [] + + if len(flow_components) != 3: + return "invalid array of video components" + + for i in range(0, 3): + if "name" in flow_components[i]: + names.append( + {"name" + str(i): flow_components[i]["name"], "err" + str(i): None} + ) + else: + names.append({"name" + str(i): None, "err" + str(i): "not defined"}) + + if ( + names[0]["err0"] is None + and names[0]["name0"] == "R" + and names[1]["err1"] is None + and names[1]["name1"] == "G" + and names[2]["err2"] is None + and names[2]["name2"] == "B" + ): + for i in range(0, 3): + if "width" in flow_components[i]: + widths.append( + { + "width" + str(i): flow_components[i]["width"], + "err" + str(i): None, + } + ) + else: + widths.append( + {"width" + str(i): None, "err" + str(i): "not defined"} + ) + + if ( + widths[0]["err0"] is not None + or widths[1]["err1"] is not None + or widths[2]["err2"] is not None + ): + return "invalid array of video components" + + for i in range(0, 3): + if "height" in flow_components[i]: + heights.append( + { + "height" + str(i): flow_components[i]["height"], + "err" + str(i): None, + } + ) + else: + heights.append( + {"height" + str(i): None, "err" + str(i): "not defined"} + ) + + if ( + heights[0]["err0"] is not None + or heights[1]["err1"] is not None + or heights[2]["err2"] is not None + ): + return "invalid array of video components" + + if ( + widths[0]["width0"] == widths[1]["width1"] + and widths[0]["width0"] == widths[2]["width2"] + and heights[0]["height0"] == heights[1]["height1"] + and heights[0]["height0"] == heights[2]["height2"] + ): + return "RGB" + + if ( + names[0]["err0"] is None + and names[0]["name0"] == "Y" + and names[1]["err1"] is None + and names[1]["name1"] == "Cb" + and names[2]["err2"] is None + and names[2]["name2"] == "Cr" + ): + + for i in range(0, 3): + if "width" in flow_components[i]: + widths.append( + { + "width" + str(i): flow_components[i]["width"], + "err" + str(i): None, + } + ) + else: + widths.append( + {"width" + str(i): None, "err" + str(i): "not defined"} + ) + + if ( + widths[0]["err0"] is not None + or widths[1]["err1"] is not None + or widths[2]["err2"] is not None + ): + return "invalid array of video components" + + for i in range(0, 3): + if "height" in flow_components[i]: + heights.append( + { + "height" + str(i): flow_components[i]["height"], + "err" + str(i): None, + } + ) + else: + heights.append( + {"height" + str(i): None, "err" + str(i): "not defined"} + ) + + if ( + heights[0]["err0"] is not None + or heights[1]["err1"] is not None + or heights[2]["err2"] is not None + ): + return "invalid array of video components" + + if ( + widths[0]["width0"] == widths[1]["width1"] + and widths[0]["width0"] == widths[2]["width2"] + and heights[0]["height0"] == heights[1]["height1"] + and heights[0]["height0"] == heights[2]["height2"] + ): + return "YCbCr-4:4:4" + + if ( + widths[0]["width0"] == 2 * widths[1]["width1"] + and widths[0]["width0"] == 2 * widths[2]["width2"] + and heights[0]["height0"] == heights[1]["height1"] + and heights[0]["height0"] == heights[2]["height2"] + ): + return "YCbCr-4:2:2" + + if ( + widths[0]["width0"] == 2 * widths[1]["width1"] + and widths[0]["width0"] == 2 * widths[2]["width2"] + and heights[0]["height0"] == 2 * heights[1]["height1"] + and heights[0]["height0"] == 2 * heights[2]["height2"] + ): + return "YCbCr-4:2:0" + + return "invalid array of video components" + + def get_another_grain_rate(self, grain_rate): + numerator = grain_rate["numerator"] + denominator = grain_rate["denominator"] + if (numerator == 30 or numerator == 25) and denominator == 1: + return {"numerator": numerator * 2, "denominator": 1} + if (numerator == 60 or numerator == 50) and denominator == 1: + return {"numerator": numerator / 2, "denominator": 1} + if numerator == 24 and denominator == 1: + return {"numerator": 30, "denominator": 1} + if (numerator == 30000 or numerator == 25000) and denominator == 1001: + return {"numerator": numerator * 2, "denominator": 1001} + if (numerator == 60000 or numerator == 50000) and denominator == 1001: + return {"numerator": numerator / 2, "denominator": 1001} + return "grain_rate not valid" + + def get_another_sample_rate(self, sample_rate): + numerator = sample_rate["numerator"] + if numerator == 0: + return {"numerator": 48000} + if numerator == 48000: + return {"numerator": 44100} + if numerator == 44100: + return {"numerator": 48000} + if numerator == 96000: + return {"numerator": 4800} + if numerator == 88200: + return {"numerator": 44100} + return "sample_rate not valid" + + def test_02_00(self, test): + "Reset active constraints of all senders" + _, response = TestHelper.do_request("GET", self.compat_url + "senders/") + if response.status_code != 200: + return test.FAIL("The request has not succeeded: {}".format(response.json())) + self.senders = response.json() + if len(self.senders) != 0: + for sender in self.senders: + _, response = TestHelper.do_request( + "DELETE", + self.compat_url + "senders/" + sender + "constraints/active/", + ) + if response.status_code != 200: + return test.FAIL("senders constraints cannot be deleted") + return test.PASS() + return test.UNCLEAR("There is no IS-11 senders.") + + def test_02_01(self, test): + "Verify that the device supports the concept of IS-11 Sender" + _, response = TestHelper.do_request("GET", self.compat_url + "senders/") + if response.status_code != 200: + return test.FAIL(response.json) + self.senders = response.json() + if len(self.senders) == 0: + return test.UNCLEAR("There is no IS-11 senders.") + return test.PASS() + + def test_02_01_01(self, test): + "Verify that the device supports the concept of IS-11 Sender" + if len(self.senders) != 0: + for sender_id in self.senders: + _, response = TestHelper.do_request( + "GET", self.node_url + "senders/" + sender_id + ) + if response.status_code != 200: + return test.FAIL("The request has not succeeded: {}".format(response.json())) + sender_node = response.json()["id"] + if sender_id[:-1] != sender_node: + return test.FAIL("Senders are different") + return test.PASS() + return test.UNCLEAR("There is no IS-11 senders.") + + def test_02_02(self, test): + "Verify senders (generic with/without inputs)" + _, response = TestHelper.do_request("GET", self.compat_url + "senders/") + if response.status_code != 200: + return test.FAIL("The request has not succeeded: {}".format(response.json())) + self.senders_2 = response.json() + if len(self.senders_2) == 0: + return test.UNCLEAR("There is no IS-11 senders.") + return test.PASS() + + def test_02_02_01(self, test): + "Verify that the status is unconstrained as per our pre-conditions" + if len(self.senders_2) != 0: + for sender_id in self.senders_2: + _, response = TestHelper.do_request( + "GET", self.compat_url + "senders/" + sender_id + "status/" + ) + if response.status_code != 200: + return test.FAIL("The request has not succeeded: {}".format(response.json())) + state = response.json()["state"] + if state in ["awating_essence", "no_essence"]: + for i in range(0, 5): + _, response = TestHelper.do_request( + "GET", self.compat_url + "senders/" + sender_id + "status/" + ) + state = response.json()["status"]["state"] + if state in ["awating_essence", "no_essence"]: + time.sleep(3000) + else: + break + if state != "unconstrained": + return test.FAIL("inputs are unstable.") + return test.PASS() + return test.UNCLEAR("There is no IS-11 senders.") + + def test_02_02_03(self, test): + """ + Verify that the sender is available in the node API, + has an associated flow and is inactive + """ + if len(self.senders_2) != 0: + for sender_id in self.senders_2: + _, response = TestHelper.do_request( + "GET", self.node_url + "senders/" + sender_id + ) + if response.status_code != 200: + return test.FAIL(response.json) + sender_node = response.json()["id"] + if sender_id[:-1] != sender_node: + return test.FAIL("Senders are different") + sender_flow_id = response.json()["flow_id"] + if sender_flow_id is None: + return test.FAIL("the sender must have a flow") + sender_subscription_active = response.json()["subscription"]["active"] + if sender_subscription_active: + return test.FAIL("the sender {} must be inactive ".format(sender_id)) + _, response = TestHelper.do_request( + "GET", self.node_url + "flows/" + sender_flow_id + ) + if response.status_code != 200: + return test.FAIL("The request has not succeeded: {}".format(response.json())) + flow_format = response.json()["format"] + self.flow_format[sender_id] = flow_format + if flow_format == "urn:x-nmos:format:video": + self.flow_format_video.append(sender_id) + flow_frame_width = response.json()["frame_width"] + self.flow_width[sender_id] = flow_frame_width + flow_frame_height = response.json()["frame_height"] + self.flow_height[sender_id] = flow_frame_height + flow_grain_rate = response.json()["grain_rate"] + self.flow_grain_rate[sender_id] = flow_grain_rate + if flow_format == "urn:x-nmos:format:audio": + self.flow_format_audio.append(sender_id) + flow_sample_rate = response.json()["sample_rate"] + self.flow_sample_rate[sender_id] = flow_sample_rate + if ( + flow_format != "urn:x-nmos:format:video" + and flow_format != "urn:x-nmos:format:audio" + ): + print("only audio and video senders are tested at this time.") + return test.PASS() + return test.UNCLEAR("There is no IS-11 senders.") + + def test_02_02_03_01(self, test): + "Verify that the video sender supports the minimum set of video constraints" + + pattern = "^urn:x-nmos:cap:" + + if len(self.flow_format_video) == 0: + return test.UNCLEAR("There is no video format.") + + for sender_id in self.flow_format_video: + _, response = TestHelper.do_request( + "GET", + self.compat_url + "senders/" + sender_id + "constraints/supported/", + ) + if response.status_code != 200: + return test.FAIL("The request has not succeeded: {}".format(response.json())) + supportedConstraints = response.json()["parameter_constraints"] + for item in supportedConstraints: + if not re.search(pattern, item): + return test.FAIL("only x-nmos:cap constraints are allowed") + for item in REF_SUPPORTED_CONSTRAINTS_VIDEO: + if item not in supportedConstraints: + return test.FAIL(item + " is not in supportedConstraints ") + return test.PASS() + + def test_02_02_03_02(self, test): + "Verify that the video sender supports the minimum set of video constraints" + + pattern = "^urn:x-nmos:cap:" + + if len(self.flow_format_audio) == 0: + return test.UNCLEAR("There is no video format.") + + for sender_id in self.flow_format_audio: + _, response = TestHelper.do_request( + "GET", + self.compat_url + "senders/" + sender_id + "constraints/supported/", + ) + if response.status_code != 200: + return test.FAIL("The request has not succeeded: {}".format(response.json())) + supportedConstraints = response.json()["parameter_constraints"] + for item in supportedConstraints: + if not re.search(pattern, item): + return test.FAIL("only x-nmos:cap constraints are allowed") + for item in REF_SUPPORTED_CONSTRAINTS_AUDIO: + if item not in supportedConstraints: + return test.FAIL(item + "is not in supportedConstraints") + return test.PASS() + + def test_02_02_04_01(self, test): + """ + Verify that changing the constraints of an + IS-11 sender(video) changes the version of + the associated IS-04 sender. + """ + if len(self.flow_format_video) == 0: + return test.UNCLEAR("There is no video format.") + + for sender_id in self.flow_format_video: + _, response = TestHelper.do_request( + "GET", self.node_url + "senders/" + sender_id + ) + if response.status_code != 200: + return test.FAIL("The request has not succeeded: {}".format(response.json())) + version = response.json()["version"] + self.version[sender_id] = version + self.grain_rate_constraints[sender_id] = { + "constraint_sets": [ + { + "urn:x-nmos:cap:format:grain_rate": { + "enum": [self.flow_grain_rate[sender_id]] + } + } + ] + } + self.empty_constraints[sender_id] = {"constraint_sets": []} + _, response = TestHelper.do_request( + "PUT", + self.compat_url + "senders/" + sender_id + "constraints/active/", + json=self.grain_rate_constraints[sender_id], + ) + if response.status_code != 200: + return test.FAIL("The request has not succeeded: {}".format(response.json())) + _, response = TestHelper.do_request( + "GET", self.node_url + "senders/" + sender_id + ) + if response.status_code != 200: + return test.FAIL("The request has not succeeded: {}".format(response.json())) + version = response.json()["version"] + if version == self.version[sender_id]: + return test.FAIL("Version are different") + self.version[sender_id] = version + _, response = TestHelper.do_request( + "GET", self.compat_url + "senders/" + sender_id + "constraints/active/" + ) + if response.status_code != 200: + return test.FAIL("The request has not succeeded: {}".format(response.json())) + constraints = response.json() + if not nmosTestingComplexCompare.compare_complex_grain_rate_constraint( + constraints, self.grain_rate_constraints[sender_id] + ): + return test.FAIL("Contraints are different") + _, response = TestHelper.do_request( + "DELETE", + self.compat_url + "senders/" + sender_id + "constraints/active/", + ) + if response.status_code != 200: + return test.FAIL("The request has not succeeded: {}".format(response.json())) + _, response = TestHelper.do_request( + "GET", self.node_url + "senders/" + sender_id + ) + if response.status_code != 200: + return test.FAIL("The request has not succeeded: {}".format(response.json())) + version = response.json()["version"] + if version == self.version[sender_id]: + return test.FAIL("Version are different") + self.version[sender_id] = version + _, response = TestHelper.do_request( + "GET", self.compat_url + "senders/" + sender_id + "constraints/active/" + ) + if response.status_code != 200: + return test.FAIL("The request has not succeeded: {}".format(response.json())) + constraints = response.json() + if constraints != self.empty_constraints[sender_id]: + return test.FAIL("Contraints are different") + return test.PASS() + + def test_02_02_04_02(self, test): + """ + Verify that changing the constraints of an IS-11 + sender(audio) changes the version of the associated IS-04 sender. + """ + if len(self.flow_format_audio) == 0: + return test.UNCLEAR("There is no audio format.") + for sender_id in self.flow_format_audio: + _, response = TestHelper.do_request( + "GET", self.node_url + "senders/" + sender_id + ) + if response.status_code != 200: + return test.FAIL("The request has not succeeded: {}".format(response.json())) + version = response.json()["version"] + self.version[sender_id] = version + self.sample_rate_constraints[sender_id] = { + "constraint_sets": [ + { + "urn:x-nmos:cap:format:sample_rate": { + "enum": [self.flow_sample_rate[sender_id]] + } + } + ] + } + self.empty_constraints[sender_id] = {"constraint_sets": []} + _, response = TestHelper.do_request( + "PUT", + self.compat_url + "senders/" + sender_id + "constraints/active/", + json=self.sample_rate_constraints[sender_id], + ) + if response.status_code != 200: + return test.FAIL("The request has not succeeded: {}".format(response.json())) + _, response = TestHelper.do_request( + "GET", self.node_url + "senders/" + sender_id + ) + if response.status_code != 200: + return test.FAIL("The request has not succeeded: {}".format(response.json())) + version = response.json()["version"] + if version == self.version[sender_id]: + return test.FAIL("Version are different") + self.version[sender_id] = version + + _, response = TestHelper.do_request( + "GET", self.compat_url + "senders/" + sender_id + "constraints/active/" + ) + if response.status_code != 200: + return test.FAIL("The request has not succeeded: {}".format(response.json())) + constraints = response.json() + + if not nmosTestingComplexCompare.compare_complex_sample_rate_constraint( + constraints, self.sample_rate_constraints[sender_id] + ): + return test.FAIL( + "constraints and SampleRateConstraints[" + + sender_id + + "] are different " + ) + + _, response = TestHelper.do_request( + "DELETE", + self.compat_url + "senders/" + sender_id + "constraints/active/", + ) + if response.status_code != 200: + return test.FAIL("The request has not succeeded: {}".format(response.json())) + + _, response = TestHelper.do_request( + "GET", self.node_url + "senders/" + sender_id + ) + if response.status_code != 200: + return test.FAIL("The request has not succeeded: {}".format(response.json())) + version = response.json()["version"] + if version == self.version[sender_id]: + return test.FAIL("Version are different") + self.version[sender_id] = version + + _, response = TestHelper.do_request( + "GET", self.compat_url + "senders/" + sender_id + "constraints/active/" + ) + if response.status_code != 200: + return test.FAIL("The request has not succeeded: {}".format(response.json())) + constraints = response.json() + if constraints != self.empty_constraints[sender_id]: + return test.FAIL("Contraints are different") + return test.PASS() + + def test_02_02_05_01(self, test): + """ + Verify that setting NOP constraints for frame(width,height), + grain_rate doesn't change the flow of a sender(video). + """ + if len(self.flow_format_video) == 0: + return test.UNCLEAR("There is no video format.") + + for sender_id in self.flow_format_video: + _, response = TestHelper.do_request( + "GET", self.compat_url + "senders/" + sender_id + "status/" + ) + if response.status_code != 200: + return test.FAIL("The request has not succeeded: {}".format(response.json())) + state = response.json()["state"] + if state in ["awating_essence", "no_essence"]: + for i in range(0, 5): + _, response = TestHelper.do_request( + "GET", self.compat_url + "senders/" + sender_id + "status/" + ) + state = response.json()["status"]["state"] + if state in ["awating_essence", "no_essence"]: + time.sleep(3000) + else: + break + if state != "unconstrained": + return test.FAIL("inputs are unstable.") + + self.constraints[sender_id] = { + "constraint_sets": [ + { + "urn:x-nmos:cap:format:grain_rate": { + "enum": [self.flow_grain_rate[sender_id]] + } + }, + { + "urn:x-nmos:cap:format:frame_width": { + "enum": [self.flow_width[sender_id]] + } + }, + { + "urn:x-nmos:cap:format:frame_height": { + "enum": [self.flow_height[sender_id]] + } + }, + ] + } + + _, response = TestHelper.do_request( + "PUT", + self.compat_url + "senders/" + sender_id + "constraints/active/", + json=self.constraints[sender_id], + ) + if response.status_code != 200: + return test.FAIL("The request has not succeeded: {}".format(response.json())) + + _, response = TestHelper.do_request( + "GET", self.compat_url + "senders/" + sender_id + "status/" + ) + if response.status_code != 200: + return test.FAIL("The request has not succeeded: {}".format(response.json())) + state = response.json()["state"] + + if state in ["awating_essence", "no_essence"]: + for i in range(0, 5): + _, response = TestHelper.do_request( + "GET", self.compat_url + "senders/" + sender_id + "status/" + ) + state = response.json()["status"]["state"] + if state in ["awating_essence", "no_essence"]: + time.sleep(3000) + else: + break + if state != "constrained": + return test.FAIL("inputs are unstable.") + + _, response = TestHelper.do_request( + "GET", self.node_url + "senders/" + sender_id + ) + if response.status_code != 200: + return test.FAIL(response.json) + sender_flow_id = response.json()["flow_id"] + if sender_flow_id is None: + return test.FAIL("the sender must have a flow") + + _, response = TestHelper.do_request( + "GET", self.node_url + "flows/" + sender_flow_id + ) + if response.status_code != 200: + return test.FAIL("The request has not succeeded: {}".format(response.json())) + + 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"] + ): + return test.FAIL("different argument") + + _, response = TestHelper.do_request( + "DELETE", + self.compat_url + "senders/" + sender_id + "constraints/active/", + ) + if response.status_code != 200: + return test.FAIL("The request has not succeeded: {}".format(response.json())) + + return test.PASS() + + def test_02_02_05_02(self, test): + """ + Verify that setting NOP constraints for sample_rate doesn't change the flow of a sender(audio). + """ + + if len(self.flow_format_audio) == 0: + return test.UNCLEAR("There is no audio format.") + + for sender_id in self.flow_format_audio: + _, response = TestHelper.do_request( + "GET", self.compat_url + "senders/" + sender_id + "status/" + ) + if response.status_code != 200: + return test.FAIL("The request has not succeeded: {}".format(response.json())) + state = response.json()["state"] + + if state in ["awating_essence", "no_essence"]: + for i in range(0, 5): + _, response = TestHelper.do_request( + "GET", self.compat_url + "senders/" + sender_id + "status/" + ) + state = response.json()["status"]["state"] + if state in ["awating_essence", "no_essence"]: + time.sleep(3000) + else: + break + if state != "unconstrained": + return test.FAIL("inputs are unstable.") + + self.constraints[sender_id] = { + "constraint_sets": [ + { + "urn:x-nmos:cap:format:sample_rate": { + "enum": [self.flow_sample_rate[sender_id]] + } + } + ] + } + _, response = TestHelper.do_request( + "PUT", + self.compat_url + "senders/" + sender_id + "constraints/active/", + json=self.constraints[sender_id], + ) + if response.status_code != 200: + return test.FAIL("The request has not succeeded: {}".format(response.json())) + + _, response = TestHelper.do_request( + "GET", self.compat_url + "senders/" + sender_id + "status/" + ) + if response.status_code != 200: + return test.FAIL("The request has not succeeded: {}".format(response.json())) + state = response.json()["state"] + + if state in ["awating_essence", "no_essence"]: + for i in range(0, 5): + _, response = TestHelper.do_request( + "GET", self.compat_url + "senders/" + sender_id + "status/" + ) + state = response.json()["status"]["state"] + if state in ["awating_essence", "no_essence"]: + time.sleep(3000) + else: + break + if state != "constrained": + return test.FAIL("inputs are unstable.") + + _, response = TestHelper.do_request( + "GET", self.node_url + "senders/" + sender_id + ) + if response.status_code != 200: + return test.FAIL(response.json) + sender_flow_id = response.json()["flow_id"] + if sender_flow_id is None: + return test.FAIL("the sender must have a flow") + + _, response = TestHelper.do_request( + "GET", self.node_url + "flows/" + sender_flow_id + ) + if response.status_code != 200: + return test.FAIL("The request has not succeeded: {}".format(response.json())) + flow_sample_rate = response.json()["sample_rate"] + if self.flow_sample_rate[sender_id] != flow_sample_rate: + return test.FAIL("Different sample rate") + + _, response = TestHelper.do_request( + "DELETE", + self.compat_url + "senders/" + sender_id + "constraints/active/", + ) + if response.status_code != 200: + return test.FAIL("The request has not succeeded: {}".format(response.json())) + return test.PASS() + + def test_02_02_06_01(self, test): + """ + Verify that setting NOP constraints for supported constraints + doesn't change the flow of a sender(video). + """ + if len(self.flow_format_video) == 0: + return test.UNCLEAR("There is no video format.") + + for sender_id in self.flow_format_video: + _, response = TestHelper.do_request( + "GET", self.compat_url + "senders/" + sender_id + "status/" + ) + if response.status_code != 200: + return test.FAIL("The request has not succeeded: {}".format(response.json())) + state = response.json()["state"] + if state in ["awating_essence", "no_essence"]: + for i in range(0, 5): + _, response = TestHelper.do_request( + "GET", self.compat_url + "senders/" + sender_id + "status/" + ) + state = response.json()["status"]["state"] + if state in ["awating_essence", "no_essence"]: + time.sleep(3000) + else: + break + if state != "unconstrained": + return test.FAIL("inputs are unstable.") + + _, response = TestHelper.do_request( + "GET", self.node_url + "senders/" + sender_id + ) + if response.status_code != 200: + return test.FAIL(response.json) + sender = response.json() + + _, response = TestHelper.do_request( + "GET", self.node_url + "flows/" + sender["flow_id"] + ) + if response.status_code != 200: + return test.FAIL("The request has not succeeded: {}".format(response.json())) + flow = response.json() + color_sampling = self.getSdpColorSampling(flow["components"]) + if color_sampling == "invalid array of video components": + return test.FAIL("invalid array of video components") + constraint_set = {} + + for item in REF_SUPPORTED_CONSTRAINTS_VIDEO: + try: + if item == "urn:x-nmos:cap:meta:label": + constraint_set["urn:x-nmos:cap:meta:label"] = "video constraint" + if item == "urn:x-nmos:cap:meta:preference": + constraint_set["urn:x-nmos:cap:meta:preference"] = 0 + if item == "urn:x-nmos:cap:meta:enabled": + 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"]] + } + if item == "urn:x-nmos:cap:format:grain_rate": + constraint_set["urn:x-nmos:cap:format:grain_rate"] = { + "enum": [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"]] + } + if item == "urn:x-nmos:cap:format:frame_height": + constraint_set["urn:x-nmos:cap:format:frame_height"] = { + "enum": [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"]] + } + if item == "urn:x-nmos:cap:format:color_sampling": + constraint_set["urn:x-nmos:cap:format:color_sampling"] = { + "enum": [color_sampling] + } + if item == "urn:x-nmos:cap:format:component_depth": + constraint_set["urn:x-nmos:cap:format:component_depth"] = { + "enum": [flow["components"][0]["bit_depth"]] + } + except Exception: + pass + + self.constraints[sender_id] = {"constraint_sets": [constraint_set]} + + _, response = TestHelper.do_request( + "PUT", + self.compat_url + "senders/" + sender_id + "constraints/active/", + json=self.constraints[sender_id], + ) + if response.status_code != 200: + return test.FAIL("The request has not succeeded: {}".format(response.json())) + + _, response = TestHelper.do_request( + "GET", self.node_url + "flows/" + sender["flow_id"] + ) + if response.status_code != 200: + return test.FAIL("The request has not succeeded: {}".format(response.json())) + new_flow = response.json() + + new_color_sampling = self.getSdpColorSampling(new_flow["components"]) + if new_color_sampling == "invalid array of video components": + return test.FAIL("invalid array of video components") + + 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"]: + return test.FAIL("different media_type") + if item == "urn:x-nmos:cap:format:grain_rate": + if 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"]: + return test.FAIL("different frame_width") + if item == "urn:x-nmos:cap:format:frame_height": + if 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"]: + 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"] + != new_flow["components"][0]["bit_depth"] + ): + return test.FAIL("different component_depth") + except Exception: + pass + _, response = TestHelper.do_request( + "DELETE", + self.compat_url + "senders/" + sender_id + "constraints/active/", + ) + if response.status_code != 200: + return test.FAIL("The request has not succeeded: {}".format(response.json())) + return test.PASS() + + def test_02_02_06_02(self, test): + """ + Verify that setting NOP constraints for supported + constraints doesn't change the flow of a sender(audio). + """ + if len(self.flow_format_audio) == 0: + return test.UNCLEAR("There is no audio format.") + for sender_id in self.flow_format_audio: + _, response = TestHelper.do_request( + "GET", self.compat_url + "senders/" + sender_id + "status/" + ) + if response.status_code != 200: + test.FAIL("The request has not succeeded: {}".format(response.json())) + state = response.json()["state"] + if state in ["awating_essence", "no_essence"]: + for i in range(0, 5): + _, response = TestHelper.do_request( + "GET", self.compat_url + "senders/" + sender_id + "status/" + ) + state = response.json()["status"]["state"] + if state in ["awating_essence", "no_essence"]: + time.sleep(3000) + else: + break + if state != "unconstrained": + return test.FAIL("inputs are unstable.") + _, response = TestHelper.do_request( + "GET", self.node_url + "senders/" + sender_id + ) + if response.status_code != 200: + return test.FAIL(response.json) + sender = response.json() + + _, response = TestHelper.do_request( + "GET", self.node_url + "flows/" + sender["flow_id"] + ) + if response.status_code != 200: + return test.FAIL("The request has not succeeded: {}".format(response.json())) + flow = response.json() + constraint_set = {} + + _, response = TestHelper.do_request( + "GET", self.node_url + "sources/" + flow["source_id"] + ) + if response.status_code != 200: + return test.FAIL("The request has not succeeded: {}".format(response.json())) + source = response.json() + + for item in REF_SUPPORTED_CONSTRAINTS_AUDIO: + try: + + if item == "urn:x-nmos:cap:meta:label": + constraint_set["urn:x-nmos:cap:meta:label"] = "audio constraint" + if item == "urn:x-nmos:cap:meta:preference": + constraint_set["urn:x-nmos:cap:meta:preference"] = 0 + if item == "urn:x-nmos:cap:meta:enabled": + 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"]] + } + if item == "urn:x-nmos:cap:format:sample_rate": + constraint_set["urn:x-nmos:cap:format:sample_rate"] = { + "enum": [flow["sample_rate"]] + } + if item == "urn:x-nmos:cap:format:channel_count": + constraint_set["urn:x-nmos:cap:format:channel_count"] = { + "enum": [len(source["channels"])] + } + if item == "urn:x-nmos:cap:format:sample_depth": + constraint_set["urn:x-nmos:cap:format:sample_depth"] = { + "enum": [flow["bit_depth"]] + } + except Exception: + pass + self.constraints[sender_id] = {"constraint_sets": [constraint_set]} + _, response = TestHelper.do_request( + "PUT", + self.compat_url + "senders/" + sender_id + "constraints/active/", + json=self.constraints[sender_id], + ) + if response.status_code != 200: + return test.FAIL("The request has not succeeded: {}".format(response.json())) + + _, response = TestHelper.do_request( + "GET", self.node_url + "flows/" + sender["flow_id"] + ) + if response.status_code != 200: + return test.FAIL("The request has not succeeded: {}".format(response.json())) + new_flow = response.json() + + _, response = TestHelper.do_request( + "GET", self.node_url + "sources/" + flow["source_id"] + ) + if response.status_code != 200: + return test.FAIL("The request has not succeeded: {}".format(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"]: + return test.FAIL("different media_type") + if item == "urn:x-nmos:cap:format:sample_rate": + if 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"]: + return test.FAIL("different sample_depth") + except Exception: + pass + _, response = TestHelper.do_request( + "DELETE", + self.compat_url + "senders/" + sender_id + "constraints/active/", + ) + if response.status_code != 200: + return test.FAIL("The request has not succeeded: {}".format(response.json())) + return test.PASS() + + def test_02_02_07_01(self, test): + "Verify that the device adhere to the preference of the constraint_set." + if len(self.flow_format_video) == 0: + return test.UNCLEAR("There is no video format.") + + for sender_id in self.flow_format_video: + _, response = TestHelper.do_request( + "GET", self.compat_url + "senders/" + sender_id + "status/" + ) + if response.status_code != 200: + return test.FAIL("The request has not succeeded: {}".format(response.json())) + state = response.json()["state"] + if state in ["awating_essence", "no_essence"]: + for i in range(0, 5): + _, response = TestHelper.do_request( + "GET", self.compat_url + "senders/" + sender_id + "status/" + ) + state = response.json()["status"]["state"] + if state in ["awating_essence", "no_essence"]: + time.sleep(3000) + else: + break + if state != "unconstrained": + return test.FAIL("inputs are unstable.") + + _, response = TestHelper.do_request( + "GET", self.node_url + "senders/" + sender_id + ) + if response.status_code != 200: + return test.FAIL(response.json) + sender = response.json() + + _, response = TestHelper.do_request( + "GET", self.node_url + "flows/" + sender["flow_id"] + ) + if response.status_code != 200: + return test.FAIL("The request has not succeeded: {}".format(response.json())) + flow = response.json() + color_sampling = self.getSdpColorSampling(flow["components"]) + if color_sampling == "invalid array of video components": + return test.FAIL("invalid array of video components") + constraint_set0 = {} + constraint_set1 = {} + + for item in REF_SUPPORTED_CONSTRAINTS_VIDEO: + try: + + if item == "urn:x-nmos:cap:meta:label": + constraint_set0[ + "urn:x-nmos:cap:meta:label" + ] = "video constraint" + if item == "urn:x-nmos:cap:meta:preference": + constraint_set0["urn:x-nmos:cap:meta:preference"] = 0 + if item == "urn:x-nmos:cap:meta:enabled": + 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"]] + } + if item == "urn:x-nmos:cap:format:grain_rate": + constraint_set0["urn:x-nmos:cap:format:grain_rate"] = { + "enum": [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"]] + } + if item == "urn:x-nmos:cap:format:frame_height": + constraint_set0["urn:x-nmos:cap:format:frame_height"] = { + "enum": [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"]] + } + if item == "urn:x-nmos:cap:format:color_sampling": + constraint_set0["urn:x-nmos:cap:format:color_sampling"] = { + "enum": [color_sampling] + } + if item == "urn:x-nmos:cap:format:component_depth": + constraint_set0["urn:x-nmos:cap:format:component_depth"] = { + "enum": [flow["components"][0]["bit_depth"]] + } + except Exception: + pass + + for item in REF_SUPPORTED_CONSTRAINTS_VIDEO: + try: + if item == "urn:x-nmos:cap:meta:label": + constraint_set1[ + "urn:x-nmos:cap:meta:label" + ] = "video constraint" + if item == "urn:x-nmos:cap:meta:preference": + constraint_set1["urn:x-nmos:cap:meta:preference"] = -100 + if item == "urn:x-nmos:cap:meta:enabled": + 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"]] + } + 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"])] + } + if item == "urn:x-nmos:cap:format:frame_width": + constraint_set1["urn:x-nmos:cap:format:frame_width"] = { + "enum": [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"]] + } + if item == "urn:x-nmos:cap:format:interlace_mode": + constraint_set1["urn:x-nmos:cap:format:interlace_mode"] = { + "enum": [flow["interlace_mode"]] + } + if item == "urn:x-nmos:cap:format:color_sampling": + constraint_set1["urn:x-nmos:cap:format:color_sampling"] = { + "enum": [color_sampling] + } + if item == "urn:x-nmos:cap:format:component_depth": + constraint_set1["urn:x-nmos:cap:format:component_depth"] = { + "enum": [flow["components"][0]["bit_depth"]] + } + except Exception: + pass + + self.constraints[sender_id] = { + "constraint_sets": [constraint_set0, constraint_set0] + } + _, response = TestHelper.do_request( + "PUT", + self.compat_url + "senders/" + sender_id + "constraints/active/", + json=self.constraints[sender_id], + ) + if response.status_code != 200: + return test.FAIL("The request has not succeeded: {}".format(response.json())) + + _, response = TestHelper.do_request( + "GET", self.node_url + "flows/" + sender["flow_id"] + ) + if response.status_code != 200: + return test.FAIL("The request has not succeeded: {}".format(response.json())) + new_flow = response.json() + + new_color_sampling = self.getSdpColorSampling(new_flow["components"]) + if new_color_sampling == "invalid array of video components": + return test.FAIL("invalid array of video components") + + 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"]: + return test.FAIL("different media_type") + if item == "urn:x-nmos:cap:format:grain_rate": + if 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"]: + return test.FAIL("different frame_width") + if item == "urn:x-nmos:cap:format:frame_height": + if 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"]: + 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"] + != new_flow["components"][0]["bit_depth"] + ): + return test.FAIL("different component_depth") + except Exception: + pass + + _, response = TestHelper.do_request( + "DELETE", + self.compat_url + "senders/" + sender_id + "constraints/active/", + ) + if response.status_code != 200: + return test.FAIL("The request has not succeeded: {}".format(response.json())) + return test.PASS() + + def test_02_02_07_02(self, test): + "Verify that the device adhere to the preference of the constraint_set." + if len(self.flow_format_audio) == 0: + return test.UNCLEAR("There is no audio format.") + + for sender_id in self.flow_format_audio: + _, response = TestHelper.do_request( + "GET", self.compat_url + "senders/" + sender_id + "status/" + ) + if response.status_code != 200: + test.FAIL("The request has not succeeded: {}".format(response.json())) + state = response.json()["state"] + if state in ["awating_essence", "no_essence"]: + for i in range(0, 5): + _, response = TestHelper.do_request( + "GET", self.compat_url + "senders/" + sender_id + "status/" + ) + state = response.json()["status"]["state"] + if state in ["awating_essence", "no_essence"]: + time.sleep(3000) + else: + break + if state != "unconstrained": + return test.FAIL("inputs are unstable.") + + _, response = TestHelper.do_request( + "GET", self.node_url + "senders/" + sender_id + ) + if response.status_code != 200: + return test.FAIL(response.json) + sender = response.json() + + _, response = TestHelper.do_request( + "GET", self.node_url + "flows/" + sender["flow_id"] + ) + if response.status_code != 200: + return test.FAIL("The request has not succeeded: {}".format(response.json())) + flow = response.json() + _, response = TestHelper.do_request( + "GET", self.node_url + "sources/" + flow["source_id"] + ) + if response.status_code != 200: + return test.FAIL("The request has not succeeded: {}".format(response.json())) + source = response.json() + + constraint_set0 = {} + constraint_set1 = {} + + for item in REF_SUPPORTED_CONSTRAINTS_AUDIO: + try: + if item == "urn:x-nmos:cap:meta:label": + constraint_set0[ + "urn:x-nmos:cap:meta:label" + ] = "audio constraint" + if item == "urn:x-nmos:cap:meta:preference": + constraint_set0["urn:x-nmos:cap:meta:preference"] = 0 + if item == "urn:x-nmos:cap:meta:enabled": + 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"]] + } + if item == "urn:x-nmos:cap:format:sample_rate": + constraint_set0["urn:x-nmos:cap:format:sample_rate"] = { + "enum": [flow["sample_rate"]] + } + if item == "urn:x-nmos:cap:format:channel_count": + constraint_set0["urn:x-nmos:cap:format:channel_count"] = { + "enum": [len(source["channels"])] + } + if item == "urn:x-nmos:cap:format:sample_depth": + constraint_set0["urn:x-nmos:cap:format:sample_depth"] = { + "enum": [flow["bit_depth"]] + } + except Exception: + pass + + for item in REF_SUPPORTED_CONSTRAINTS_AUDIO: + try: + if item == "urn:x-nmos:cap:meta:label": + constraint_set1[ + "urn:x-nmos:cap:meta:label" + ] = "video constraint" + if item == "urn:x-nmos:cap:meta:preference": + constraint_set1["urn:x-nmos:cap:meta:preference"] = -100 + if item == "urn:x-nmos:cap:meta:enabled": + 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"]] + } + 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"])] + } + if item == "urn:x-nmos:cap:format:channel_count": + constraint_set1["urn:x-nmos:cap:format:channel_count"] = { + "enum": [len(source["channels"])] + } + if item == "urn:x-nmos:cap:format:sample_depth": + constraint_set1["urn:x-nmos:cap:format:sample_depth"] = { + "enum": [flow["bit_depth"]] + } + except Exception: + pass + + self.constraints[sender_id] = { + "constraint_sets": [constraint_set0, constraint_set1] + } + + _, response = TestHelper.do_request( + "PUT", + self.compat_url + "senders/" + sender_id + "constraints/active/", + json=self.constraints[sender_id], + ) + if response.status_code != 200: + return test.FAIL("The request has not succeeded: {}".format(response.json())) + _, response = TestHelper.do_request( + "GET", self.node_url + "flows/" + sender["flow_id"] + ) + if response.status_code != 200: + return test.FAIL("The request has not succeeded: {}".format(response.json())) + new_flow = response.json() + + _, response = TestHelper.do_request( + "GET", self.node_url + "sources/" + flow["source_id"] + ) + if response.status_code != 200: + return test.FAIL("The request has not succeeded: {}".format(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"]: + return test.FAIL("different media_type") + if item == "urn:x-nmos:cap:format:sample_rate": + if 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"]: + return test.FAIL("different sample_depth") + except Exception: + pass + _, response = TestHelper.do_request( + "DELETE", + self.compat_url + "senders/" + sender_id + "constraints/active/", + ) + if response.status_code != 200: + return test.FAIL("The request has not succeeded: {}".format(response.json())) + return test.PASS() From c11f72671cb5d34cffcdf25a58c7547411838e97 Mon Sep 17 00:00:00 2001 From: ggeorgea Date: Wed, 12 Apr 2023 10:28:24 -0400 Subject: [PATCH 04/11] Removing mistakenly commited files --- 0001-Add-Node-and-Connection.patch | 1987 ----------------- ...or-tests-general-0-Senders-2-Outputs.patch | 1777 --------------- nmostesting/suites/BCP0050101Test.py | 26 - 3 files changed, 3790 deletions(-) delete mode 100644 0001-Add-Node-and-Connection.patch delete mode 100644 0001-Initial-submit-for-tests-general-0-Senders-2-Outputs.patch delete mode 100644 nmostesting/suites/BCP0050101Test.py diff --git a/0001-Add-Node-and-Connection.patch b/0001-Add-Node-and-Connection.patch deleted file mode 100644 index 61b776a8..00000000 --- a/0001-Add-Node-and-Connection.patch +++ /dev/null @@ -1,1987 +0,0 @@ -From 1c40a84f376016a2a4ab0abe3fa3eea238bcee83 Mon Sep 17 00:00:00 2001 -From: ggeorgea -Date: Wed, 8 Mar 2023 11:13:25 -0500 -Subject: [PATCH] Add Node and Connection - -Add version API as options -Fixed failure comments and standardize message formating -Created helper class for Complex x-nmos:cap:format comparisons ---- - nmostesting/NMOSTesting.py | 6 + - nmostesting/nmosTestingComplexCompare.py | 262 ++++++++ - nmostesting/suites/IS1101Test.py | 150 ++--- - nmostesting/suites/IS1102Test.py | 789 +++++++++++------------ - 4 files changed, 728 insertions(+), 479 deletions(-) - create mode 100644 nmostesting/nmosTestingComplexCompare.py - -diff --git a/nmostesting/NMOSTesting.py b/nmostesting/NMOSTesting.py -index 77f363b..93e1508 100644 ---- a/nmostesting/NMOSTesting.py -+++ b/nmostesting/NMOSTesting.py -@@ -324,6 +324,12 @@ TEST_DEFINITIONS = { - "specs": [{ - "spec_key": "is-11", - "api_key": "streamcompatibility" -+ }, { -+ "spec_key": "is-04", -+ "api_key": "node" -+ }, { -+ "spec_key": "is-05", -+ "api_key": "connection" - }], - "class": IS1101Test.IS1101Test - }, -diff --git a/nmostesting/nmosTestingComplexCompare.py b/nmostesting/nmosTestingComplexCompare.py -new file mode 100644 -index 0000000..42b46df ---- /dev/null -+++ b/nmostesting/nmosTestingComplexCompare.py -@@ -0,0 +1,262 @@ -+# constraint_set schema -+# -+# { -+# "$schema": "http://json-schema.org/draft-04/schema#", -+# "description": "Describes a Constraint Set", -+# "title": "Constraint Set", -+# "type": "object", -+# "minProperties": 1, -+# "properties": { -+# "urn:x-nmos:cap:meta:label": { -+# "description": "Freeform string label for the Constraint Set", -+# "type": "string" -+# }, -+# "urn:x-nmos:cap:meta:preference": { -+# "description": "This value expresses the relative 'weight' that the Receiver assigns to -+# its preference for the streams satisfied by the associated Constraint Set. -+# The weight is an integer in the range -100 through 100, -+# where -100 is least preferred and 100 is most preferred. -+# When the attribute is omitted, the effective value for the associated Constraint Set is 0.", -+# "type": "integer", -+# "default": 0, -+# "maximum": 100, -+# "minimum": -100 -+# }, -+# "urn:x-nmos:cap:meta:enabled": { -+# "description": "This value indicates whether a Constraint Set is available to use immediately (true) -+# or whether this is an offline capability which can be activated via -+# some unspecified configuration mechanism (false). -+# When the attribute is omitted its value is assumed to be true.", -+# "type": "boolean", -+# "default": true -+# } -+# }, -+# "patternProperties": { -+# "^urn:x-nmos:cap:(?!meta:)": { -+# "$ref": "param_constraint.json" -+# } -+# } -+# } -+# -+# We want to compare that two constraint sets are equal based on the properties of teh schema that allow default -+# values for properties not defined. For example the "preference" property defined to true or undefined is the -+# same such that comparing an object A heving the property set to true and an object B not having the property -+# defined will indicate equality because the schema defines a default value. -+# -+# This function verifies two constraint sets where at most one constraint set is expected and each constraint -+# set must have at most one "sample_rate" paremeter constraint. We then compare the two objects based on their -+# respective schemas: constraint_set and rational. We expect the constraint to be defined using the "enum" -+# keyword with a single array entry. -+# -+# return true if equal, false otherwise -+ -+ -+def compare_complex_sample_rate_constraint( -+ response_constraints, sample_rate_constraints -+): -+ -+ # NOTE: We already know that both response_constraints, sample_rate_constraints are valid -+ # and have been each independently been validated against the schemas. We only check equality. -+ -+ # Each constraint_sets array must have a single entry -+ if len(response_constraints) != 1 or len(sample_rate_constraints) != 1: -+ return False -+ -+ # If the sample_rate property is not defined, objects are not equivalent -+ try: -+ response_constraints_enum = response_constraints["constraint_sets"][0][ -+ "urn:x-nmos:cap:format:sample_rate" -+ ]["enum"] -+ except Exception: -+ return False -+ -+ # If the sample_rate property is not defined, objects are not equivalent -+ try: -+ sample_rate_constraints_enum = sample_rate_constraints["constraint_sets"][0][ -+ "urn:x-nmos:cap:format:sample_rate" -+ ]["enum"] -+ except Exception: -+ return False -+ -+ # There must be a single entry in the enum array -+ if len(response_constraints_enum) != 1 or len(sample_rate_constraints_enum) != 1: -+ return False -+ -+ try: -+ response_numerator = response_constraints_enum[0]["numerator"] -+ response_denominator = 1 -+ -+ if "denominator" in response_constraints_enum[0]: -+ response_denominator = response_constraints_enum[0]["denominator"] -+ -+ sample_rate_numerator = sample_rate_constraints_enum[0]["numerator"] -+ sample_rate_denominator = 1 -+ -+ if "denominator" in sample_rate_constraints_enum[0]: -+ sample_rate_denominator = sample_rate_constraints_enum[0]["denominator"] -+ -+ if ( -+ response_numerator != sample_rate_numerator -+ or response_denominator != sample_rate_denominator -+ ): -+ return False -+ except Exception: -+ return False -+ -+ # There must be no other patternProperties -+ for prop in sample_rate_constraints["constraint_sets"][0]: -+ if ( -+ prop != "urn:x-nmos:cap:format:sample_rate" -+ and prop != "urn:x-nmos:cap:meta:enabled" -+ and prop != "urn:x-nmos:cap:meta:preference" -+ ): -+ return False -+ -+ for prop in response_constraints["constraint_sets"][0]: -+ if ( -+ prop != "urn:x-nmos:cap:format:sample_rate" -+ and prop != "urn:x-nmos:cap:meta:enabled" -+ and prop != "urn:x-nmos:cap:meta:preference" -+ ): -+ return False -+ -+ # Check meta:enabled considering default values -+ response_enabled = True -+ if "urn:x-nmos:cap:meta:enabled" in response_constraints["constraint_sets"][0]: -+ response_enabled = response_constraints["constraint_sets"][0][ -+ "urn:x-nmos:cap:meta:enabled" -+ ] -+ -+ sample_rate_enabled = True -+ if "urn:x-nmos:cap:meta:enabled" in sample_rate_constraints["constraint_sets"][0]: -+ sample_rate_enabled = sample_rate_constraints["constraint_sets"][0][ -+ "urn:x-nmos:cap:meta:enabled" -+ ] -+ -+ if response_enabled != sample_rate_enabled: -+ return False -+ -+ # Check meta:preference considering default values -+ response_preference = 0 -+ if "urn:x-nmos:cap:meta:preference" in response_constraints["constraint_sets"][0]: -+ response_preference = response_constraints["constraint_sets"][0][ -+ "urn:x-nmos:cap:meta:preference" -+ ] -+ -+ sample_rate_preference = 0 -+ if ( -+ "urn:x-nmos:cap:meta:preference" -+ in sample_rate_constraints["constraint_sets"][0] -+ ): -+ sample_rate_preference = sample_rate_constraints["constraint_sets"][0][ -+ "urn:x-nmos:cap:meta:preference" -+ ] -+ -+ if response_preference != sample_rate_preference: -+ return False -+ -+ # If we get here it is because the two objects are equal -+ return True -+ -+ -+def compare_complex_grain_rate_constraint(response_constraints, grain_rate_constraints): -+ -+ # NOTE: We already know that both response_constraints, grain_rate_constraints are valid -+ # and have been each independently been validated against the schemas. We only check equality. -+ -+ # Each constraint_sets array must have a single entry -+ if len(response_constraints) != 1 or len(grain_rate_constraints) != 1: -+ return False -+ -+ # If the grain_rate property is not defined, objects are not equivalent -+ try: -+ response_constraints_enum = response_constraints["constraint_sets"][0][ -+ "urn:x-nmos:cap:format:grain_rate" -+ ]["enum"] -+ except Exception: -+ return False -+ -+ # If the grain_rate property is not defined, objects are not equivalent -+ try: -+ grain_rate_constraints_enum = grain_rate_constraints["constraint_sets"][0][ -+ "urn:x-nmos:cap:format:grain_rate" -+ ]["enum"] -+ except Exception: -+ return False -+ -+ # There must be a single entry in the enum array -+ if len(response_constraints_enum) != 1 or len(grain_rate_constraints_enum) != 1: -+ return False -+ -+ try: -+ response_numerator = response_constraints_enum[0]["numerator"] -+ response_denominator = 1 -+ -+ if "denominator" in response_constraints_enum[0]: -+ response_denominator = response_constraints_enum[0]["denominator"] -+ -+ grain_rate_numerator = grain_rate_constraints_enum[0]["numerator"] -+ grain_rate_denominator = 1 -+ -+ if "denominator" in grain_rate_constraints_enum[0]: -+ grain_rate_denominator = grain_rate_constraints_enum[0]["denominator"] -+ -+ if ( -+ response_numerator != grain_rate_numerator -+ or response_denominator != grain_rate_denominator -+ ): -+ return False -+ except Exception: -+ return False -+ -+ # There must be no other patternProperties -+ for prop in grain_rate_constraints["constraint_sets"][0]: -+ if ( -+ prop != "urn:x-nmos:cap:format:grain_rate" -+ and prop != "urn:x-nmos:cap:meta:enabled" -+ and prop != "urn:x-nmos:cap:meta:preference" -+ ): -+ return False -+ -+ for prop in response_constraints["constraint_sets"][0]: -+ if ( -+ prop != "urn:x-nmos:cap:format:grain_rate" -+ and prop != "urn:x-nmos:cap:meta:enabled" -+ and prop != "urn:x-nmos:cap:meta:preference" -+ ): -+ return False -+ -+ # Check meta:enabled considering default values -+ response_enabled = True -+ if "urn:x-nmos:cap:meta:enabled" in response_constraints["constraint_sets"][0]: -+ response_enabled = response_constraints["constraint_sets"][0][ -+ "urn:x-nmos:cap:meta:enabled" -+ ] -+ -+ grain_rate_enabled = True -+ if "urn:x-nmos:cap:meta:enabled" in grain_rate_constraints["constraint_sets"][0]: -+ grain_rate_enabled = grain_rate_constraints["constraint_sets"][0][ -+ "urn:x-nmos:cap:meta:enabled" -+ ] -+ -+ if response_enabled != grain_rate_enabled: -+ return False -+ -+ # Check meta:preference considering default values -+ response_preference = 0 -+ if "urn:x-nmos:cap:meta:preference" in response_constraints["constraint_sets"][0]: -+ response_preference = response_constraints["constraint_sets"][0][ -+ "urn:x-nmos:cap:meta:preference" -+ ] -+ -+ grain_rate_preference = 0 -+ if "urn:x-nmos:cap:meta:preference" in grain_rate_constraints["constraint_sets"][0]: -+ grain_rate_preference = grain_rate_constraints["constraint_sets"][0][ -+ "urn:x-nmos:cap:meta:preference" -+ ] -+ -+ if response_preference != grain_rate_preference: -+ return False -+ -+ # If we get here it is because the two objects are equal -+ return True -diff --git a/nmostesting/suites/IS1101Test.py b/nmostesting/suites/IS1101Test.py -index 637d314..f3077ec 100644 ---- a/nmostesting/suites/IS1101Test.py -+++ b/nmostesting/suites/IS1101Test.py -@@ -17,6 +17,8 @@ from .. import TestHelper - - COMPAT_API_KEY = "streamcompatibility" - CONTROLS = "controls" -+NODE_API_KEY = "node" -+CONN_API_KEY = "connection" - - - class IS1101Test(GenericTest): -@@ -30,11 +32,12 @@ class IS1101Test(GenericTest): - "/inputs/{inputId}/edid", - "/inputs/{inputId}/edid/base", - "/inputs/{inputId}/edid/effective", -- "/outputs/{outputId}/edid", -+ "/outputs/{outputId}/edid" - ] - GenericTest.__init__(self, apis, omit_paths) - self.compat_url = self.apis[COMPAT_API_KEY]["url"] -- self.base_url = self.apis[COMPAT_API_KEY]["base_url"] -+ 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 = [] -@@ -45,34 +48,33 @@ class IS1101Test(GenericTest): - self.caps = "" - - # GENERAL TESTS -- def test_00_01(self, test): -+ def test_01(self, test): - """Verify that IS-11 is exposed in the Node API as \ - urn:x-nmos:control:stream-compat/v1.0 at url /x-nmos/streamcompatibility/v1.0/ - """ -- valid_res, response = TestHelper.do_request( -- "GET", self.base_url + "/x-nmos/node/v1.3/devices/" -+ _, response = TestHelper.do_request( -+ "GET", self.node_url + "devices/" - ) -- if valid_res: -- response_json = response.json() -- controls = response_json[0][CONTROLS] -- control_href = "" -+ if response.status_code != 200: -+ return test.FAIL("The request has not succeeded.", response) -+ controls = response.json()[0][CONTROLS] -+ control_href = "" -+ if len(controls) > 0: - for control in controls: -- if control["type"] == "urn:x-nmos:control:stream-compat/v1.0": -+ if control["type"] == "urn:x-nmos:control:stream-compat/" + self.apis[COMPAT_API_KEY]["version"]: - control_href = control["href"] - break -- if len(control) == 0: -- return test.WARNING("IS-11 API is not available") - if not control_href.endswith(self.compat_url): - return test.FAIL("IS-11 URL is invalid") - return test.PASS() -- return test.FAIL(response) -+ return test.FAIL("IS-11 API is not available") - -- def test_00_02(self, test): -+ def test_02(self, test): - "Put all senders into inactive state" -- senders_url = self.base_url + "/x-nmos/connection/v1.1/single/senders/" -+ senders_url = self.conn_url + "single/senders/" - _, response = TestHelper.do_request("GET", senders_url) - if response.status_code != 200: -- return test.FAIL(response.json()) -+ return test.FAIL("The request has not succeeded.", response.json()) - senders = response.json() - if len(senders) > 0: - for sender in senders: -@@ -88,16 +90,16 @@ class IS1101Test(GenericTest): - or response.json()["master_enable"] - or response.json()["activation"]["mode"] != "activate_immediate" - ): -- return test.FAIL(response.json()) -+ return test.FAIL("The request has not succeeded.", response.json()) - return test.PASS() -- return test.UNCLEAR("Could not find any IS-04 senders to test") -+ return test.UNCLEAR("Could not find any senders to test") - -- def test_00_03(self, test): -+ def test_03(self, test): - "Put all the receivers into inactive state" -- receivers_url = self.base_url + "/x-nmos/connection/v1.1/single/receivers/" -+ receivers_url = self.conn_url + "single/receivers/" - _, response = TestHelper.do_request("GET", receivers_url) - if response.status_code != 200: -- return test.FAIL(response.json()) -+ return test.FAIL("The request has not succeeded.", response.json()) - receivers = response.json() - if len(receivers) > 0: - for receiver in receivers: -@@ -112,11 +114,11 @@ class IS1101Test(GenericTest): - or response.json()["master_enable"] - or response.json()["activation"]["mode"] != "activate_immediate" - ): -- return test.FAIL(response.json()) -+ return test.FAIL("The request has not succeeded.", response.json()) - - return test.PASS() - -- return test.UNCLEAR("Could not find any IS-04 receivers to test") -+ return test.UNCLEAR("Could not find any receivers to test") - - # OUTPUTS TESTS - def test_03_01(self, test): -@@ -131,14 +133,14 @@ class IS1101Test(GenericTest): - if len(self.outputs) == 0: - return test.UNCLEAR("No outputs") - return test.PASS() -- return test.FAIL(response.json()) -+ return test.FAIL("The request has not succeeded.", response.json()) - - def test_03_02(self, test): - """ - Verify that some of the outputs of the device are connected. - """ - if len(self.outputs) == 0: -- return test.UNCLEAR("No IS11 receivers outputs") -+ return test.UNCLEAR("No IS11 outputs") - for output in self.outputs: - _, response = TestHelper.do_request( - "GET", self.compat_url + "outputs/" + output + "properties/" -@@ -150,9 +152,9 @@ class IS1101Test(GenericTest): - if output["connected"]: - self.connected_outputs.append(output["id"]) - else: -- return test.FAIL(response.json()) -+ return test.FAIL("The request has not succeeded.", response.json()) - if len(self.connected_outputs) == 0: -- return test.UNCLEAR("None Outputs support edid.") -+ return test.UNCLEAR("No connected outputs.") - return test.PASS() - - def test_03_03(self, test): -@@ -161,7 +163,7 @@ class IS1101Test(GenericTest): - a signal as test 0 put all of the receivers inactive. - """ - if len(self.connected_outputs) == 0: -- return test.UNCLEAR("no connected outputs") -+ return test.UNCLEAR("No connected outputs.") - for output_id in self.connected_outputs: - _, response = TestHelper.do_request( - "GET", self.compat_url + "outputs/" + output_id + "/properties/" -@@ -170,10 +172,10 @@ class IS1101Test(GenericTest): - if response.json()["status"]["state"] == "signal_present": - self.active_connected_outputs.append(response.json()) - else: -- return test.FAIL(response.json()) -+ return test.FAIL("The request has not succeeded.", response.json()) - if len(self.active_connected_outputs) != 0: - return test.UNCLEAR( -- "Connected output have a signal while all receivers are inactive" -+ "Connected output have a signal while all receivers are inactive." - ) - return test.PASS() - -@@ -182,7 +184,7 @@ class IS1101Test(GenericTest): - Verify that connected outputs supporting EDID behave according to the RAML file. - """ - if len(self.connected_outputs) == 0: -- return test.UNCLEAR("no connected outputs") -+ return test.UNCLEAR("No connected outputs.") - for output_id in self.connected_outputs: - _, response = TestHelper.do_request( - "GET", self.compat_url + "outputs/" + output_id + "/properties/" -@@ -191,9 +193,9 @@ class IS1101Test(GenericTest): - if response.json()["edid_support"]: - self.edid_connected_outputs.append(response.json()["id"]) - else: -- return test.FAIL(response.json()) -+ return test.FAIL("The request has not succeeded.", response.json()) - if self.edid_connected_outputs == 0: -- return test.UNCLEAR("Outputs not supporting edid") -+ return test.UNCLEAR("Outputs not supporting edid.") - return test.PASS() - - def test_03_04_01(self, test): -@@ -201,13 +203,13 @@ class IS1101Test(GenericTest): - Verify that an output indicating EDID support behaves according to the RAML file. - """ - if len(self.edid_connected_outputs) == 0: -- return test.UNCLEAR("no edid connected outputs") -+ return test.UNCLEAR("No edid connected outputs.") - for output_id in self.edid_connected_outputs: - _, response = TestHelper.do_request( - "GET", self.compat_url + "outputs/" + output_id - ) - if response.status_code != 200: -- return test.FAIL(response.json()) -+ return test.FAIL("The request has not succeeded.", response.json()) - return test.PASS() - - def test_03_04_02(self, test): -@@ -217,27 +219,27 @@ class IS1101Test(GenericTest): - """ - is_valid_response = True - if len(self.edid_connected_outputs) == 0: -- return test.UNCLEAR("no edid connected outputs") -+ return test.UNCLEAR("No edid connected outputs.") - for output_id in self.edid_connected_outputs: - _, response = TestHelper.do_request( - "GET", self.compat_url + "outputs/" + output_id + "/edid/" - ) - if ( - response.status_code != 200 -- and response.headers["Content-Type"] != "application/octet-stream" -+ or response.headers["Content-Type"] != "application/octet-stream" - ): - is_valid_response = False - break - if is_valid_response: - return test.PASS() -- return test.FAIL(response.json()) -+ return test.FAIL("The request has not succeeded.", response.json()) - - def test_03_05(self, test): - """ - Verify that connected outputs not supporting EDID behave according to the RAML file. - """ - if len(self.connected_outputs) == 0: -- return test.UNCLEAR("no connected outputs") -+ return test.UNCLEAR("No connected outputs.") - for output_id in self.connected_outputs: - _, response = TestHelper.do_request( - "GET", self.compat_url + "outputs/" + output_id + "/properties/" -@@ -246,9 +248,9 @@ class IS1101Test(GenericTest): - if not response.json()["edid_support"]: - self.not_edid_connected_outputs.append(response.json()["id"]) - else: -- return test.FAIL(response.json()) -+ return test.FAIL("The request has not succeeded.", response.json()) - if len(self.not_edid_connected_outputs) == 0: -- return test.UNCLEAR("Outputs supporting edid") -+ return test.UNCLEAR("Outputs supporting edid.") - return test.PASS() - - def test_03_05_01(self, test): -@@ -256,13 +258,13 @@ class IS1101Test(GenericTest): - Verify that there is no EDID support. - """ - if len(self.not_edid_connected_outputs) == 0: -- return test.UNCLEAR("none of not edid connected outputs") -+ return test.UNCLEAR("None of not edid connected outputs.") - for output_id in self.not_edid_connected_outputs: - _, response = TestHelper.do_request( - "GET", self.compat_url + "outputs/" + output_id + "/edid/" - ) - if response.status_code != 204: -- return test.UNCLEAR("status code should be 204") -+ return test.UNCLEAR("Status code should be 204.") - return test.PASS() - - # RECEIVERS TESTS -@@ -273,33 +275,29 @@ class IS1101Test(GenericTest): - _, response = TestHelper.do_request("GET", self.compat_url + "receivers/") - - if response.status_code != 200: -- return test.FAIL(response.json()) -+ return test.FAIL("The request has not succeeded.", response.json()) - self.receivers = response.json() -- return ( -- test.PASS() -- if len(self.receivers) != 0 -- else test.UNCLEAR("No IS_11 receivers") -- ) -+ if len(self.receivers) == 0: -+ return test.UNCLEAR("No IS_11 receivers.") -+ return test.PASS() - - def test_04_01_01(self, test): - """ - Verify that IS-11 Receivers exist on the Node API as Receivers. - """ -+ if len(self.receivers) == 0: -+ return test.UNCLEAR("No IS_11 receivers.") - for receiver_id in self.receivers: - _, response = TestHelper.do_request( -- "GET", self.base_url + "/x-nmos/node/v1.3/receivers/" + receiver_id -+ "GET", self.node_url + "receivers/" + receiver_id - ) - if response.status_code != 200: -- return test.FAIL(response.json()) -+ return test.FAIL("The request has not succeeded.", response.json()) - if response.json()["id"] != receiver_id[:-1]: - return test.UNCLEAR( -- "The IS-11 Receiver doesn't exist on the Node API as receiver" -+ "The IS-11 Receiver doesn't exist on the Node API as receiver." - ) -- return ( -- test.PASS() -- if len(self.receivers) != 0 -- else test.UNCLEAR("No IS_11 receivers") -- ) -+ return test.PASS() - - def test_04_02(self, test): - """ -@@ -307,50 +305,44 @@ class IS1101Test(GenericTest): - """ - _, response = TestHelper.do_request("GET", self.compat_url + "receivers/") - if response.status_code != 200: -- return test.FAIL(response.json()) -+ return test.FAIL("The request has not succeeded.", response.json()) - self.receivers = response.json() -- return ( -- test.PASS() -- if len(self.receivers) != 0 -- else test.UNCLEAR("No IS_11 receivers") -- ) -+ if len(self.receivers) == 0: -+ return test.UNCLEAR("No IS_11 receivers.") -+ return test.PASS() - - def test_04_02_01(self, test): - """ - Verify that the status is "unknown" or "non_compliant_stream" - as per our pre-conditions of not being master_enabled. - """ -+ if len(self.receivers) == 0: -+ return test.UNCLEAR("No IS_11 receivers.") - for receiver_id in self.receivers: - _, response = TestHelper.do_request( - "GET", self.compat_url + "receivers/" + receiver_id + "status/" - ) - if response.status_code != 200: -- return test.FAIL(response.json()) -+ return test.FAIL("The request has not succeeded.", 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.PASS() -- if len(self.receivers) != 0 -- else test.UNCLEAR("No IS_11 receivers") -- ) -+ return test.FAIL("The state is not unknown or non compliant stream.") -+ return test.PASS() - - def test_04_02_02(self, test): - """ - Verify that the Receiver supports Receiver Capabilities. - """ -+ if len(self.receivers) == 0: -+ return test.UNCLEAR("No IS_11 receivers.") - for receiver_id in self.receivers: - _, response = TestHelper.do_request( -- "GET", self.base_url + "/x-nmos/node/v1.3/receivers/" + receiver_id -+ "GET", self.node_url + "receivers/" + receiver_id - ) - if response.status_code != 200: -- return test.FAIL(response.json()) -+ return test.FAIL("The request has not succeeded.", response.json()) - self.caps = response.json()["caps"] - if "constraint_sets" not in self.caps: -- return test.UNCLEAR(" The receiver does not have constraint_sets in caps") -+ return test.UNCLEAR("The receiver does not have constraint_sets in caps.") - if len(self.caps["constraint_sets"]) == 0: -- return test.UNCLEAR(" The receiver does not support BCP-004-01") -- return ( -- test.PASS() -- if len(self.receivers) != 0 -- else test.UNCLEAR("No IS_11 receivers") -- ) -+ return test.UNCLEAR("The receiver does not support BCP-004-01.") -+ return test.PASS() -diff --git a/nmostesting/suites/IS1102Test.py b/nmostesting/suites/IS1102Test.py -index e51a0fb..d5da772 100644 ---- a/nmostesting/suites/IS1102Test.py -+++ b/nmostesting/suites/IS1102Test.py -@@ -16,6 +16,7 @@ from ..GenericTest import GenericTest - from .. import TestHelper - import time - import re -+from .. import nmosTestingComplexCompare - - COMPAT_API_KEY = "streamcompatibility" - NODE_API_KEY = "node" -@@ -48,6 +49,7 @@ class IS1102Test(GenericTest): - """ - Runs Node Tests covering IS-11 - """ -+ - def __init__(self, apis): - # Don't auto-test paths responding with an EDID binary as they don't have a JSON Schema - omit_paths = [ -@@ -59,7 +61,6 @@ class IS1102Test(GenericTest): - GenericTest.__init__(self, apis, omit_paths) - self.node_url = self.apis[NODE_API_KEY]["url"] - self.compat_url = self.apis[COMPAT_API_KEY]["url"] -- self.base_url = self.apis[COMPAT_API_KEY]["base_url"] - self.senders = "" - self.senders_2 = "" - self.flow_format = {} -@@ -80,54 +81,6 @@ class IS1102Test(GenericTest): - Runs Node Tests covering IS-11 for Senders - """ - -- def compare_complex(self, response_constraints, sample_rate_constraints): -- -- response_constraints_enum = response_constraints["constraint_sets"][0][ -- "urn:x-nmos:cap:format:sample_rate" -- ]["enum"] -- sample_rate_constraints_enum = sample_rate_constraints["constraint_sets"][0][ -- "urn:x-nmos:cap:format:sample_rate" -- ]["enum"] -- -- if len(response_constraints_enum) > 0 and len(sample_rate_constraints_enum) > 0: -- if ( -- "numerator" in response_constraints_enum[0] -- and "numerator" in sample_rate_constraints_enum[0] -- and "denominator" in response_constraints_enum[0] -- and "denominator" in sample_rate_constraints_enum[0] -- ): -- return ( -- response_constraints_enum[0]["numerator"] -- == sample_rate_constraints_enum[0]["numerator"] -- and response_constraints_enum[0]["denominator"] -- == sample_rate_constraints_enum[0]["denominator"] -- ) -- -- if ( -- "numerator" in response_constraints_enum[0] -- and "numerator" in sample_rate_constraints_enum[0] -- and "denominator" in response_constraints_enum[0] -- and "denominator" not in sample_rate_constraints_enum[0] -- ): -- return ( -- response_constraints_enum[0]["numerator"] -- == sample_rate_constraints_enum[0]["numerator"] -- and response_constraints_enum[0]["denominator"] == 1 -- ) -- -- if ( -- "numerator" in response_constraints_enum[0] -- and "numerator" in sample_rate_constraints_enum[0] -- and "denominator" not in response_constraints_enum[0] -- and "denominator" in sample_rate_constraints_enum[0] -- ): -- return ( -- response_constraints_enum[0]["numerator"] -- == sample_rate_constraints_enum[0]["numerator"] -- and 1 == sample_rate_constraints_enum[0]["denominator"] -- ) -- return False -- - def getSdpColorSampling(self, flow_components): - """ - getColorSamplingFromComponents supports RGB, -@@ -297,6 +250,8 @@ class IS1102Test(GenericTest): - - def get_another_sample_rate(self, sample_rate): - numerator = sample_rate["numerator"] -+ if numerator == 0: -+ return {"numerator": 48000} - if numerator == 48000: - return {"numerator": 44100} - if numerator == 44100: -@@ -310,8 +265,10 @@ class IS1102Test(GenericTest): - def test_02_00(self, test): - "Reset active constraints of all senders" - _, response = TestHelper.do_request("GET", self.compat_url + "senders/") -- if response.status_code == 200: -- self.senders = response.json() -+ if response.status_code != 200: -+ return test.FAIL("The request has not succeeded.", response.json()) -+ self.senders = response.json() -+ if len(self.senders) != 0: - for sender in self.senders: - _, response = TestHelper.do_request( - "DELETE", -@@ -320,8 +277,7 @@ class IS1102Test(GenericTest): - if response.status_code != 200: - return test.FAIL("senders constraints cannot be deleted") - return test.PASS() -- -- return test.FAIL(response.json()) -+ return test.UNCLEAR("There is no IS-11 senders.") - - def test_02_01(self, test): - "Verify that the device supports the concept of IS-11 Sender" -@@ -330,7 +286,7 @@ class IS1102Test(GenericTest): - return test.FAIL(response.json) - self.senders = response.json() - if len(self.senders) == 0: -- return test.UNCLEAR("there is no IS-11 senders") -+ return test.UNCLEAR("There is no IS-11 senders.") - return test.PASS() - - def test_02_01_01(self, test): -@@ -341,19 +297,21 @@ class IS1102Test(GenericTest): - "GET", self.node_url + "senders/" + sender_id - ) - if response.status_code != 200: -- return test.FAIL() -+ return test.FAIL("The request has not succeeded.", response.json()) - sender_node = response.json()["id"] - if sender_id[:-1] != sender_node: -- return test.FAIL("") -+ return test.FAIL("Senders are different") - return test.PASS() -- return test.UNCLEAR("there is no IS-11 senders") -+ return test.UNCLEAR("There is no IS-11 senders.") - - def test_02_02(self, test): - "Verify senders (generic with/without inputs)" - _, response = TestHelper.do_request("GET", self.compat_url + "senders/") - if response.status_code != 200: -- return test.FAIL(response.json()) -+ return test.FAIL("The request has not succeeded.", response.json()) - self.senders_2 = response.json() -+ if len(self.senders_2) == 0: -+ return test.UNCLEAR("There is no IS-11 senders.") - return test.PASS() - - def test_02_02_01(self, test): -@@ -364,7 +322,7 @@ class IS1102Test(GenericTest): - "GET", self.compat_url + "senders/" + sender_id + "status/" - ) - if response.status_code != 200: -- return test.FAIL(response.json()) -+ return test.FAIL("The request has not succeeded.", response.json()) - state = response.json()["state"] - if state in ["awating_essence", "no_essence"]: - for i in range(0, 5): -@@ -379,7 +337,7 @@ class IS1102Test(GenericTest): - if state != "unconstrained": - return test.FAIL("inputs are unstable.") - return test.PASS() -- return test.UNCLEAR("there is no IS-11 senders") -+ return test.UNCLEAR("There is no IS-11 senders.") - - def test_02_02_03(self, test): - """ -@@ -395,7 +353,7 @@ class IS1102Test(GenericTest): - return test.FAIL(response.json) - sender_node = response.json()["id"] - if sender_id[:-1] != sender_node: -- return test.FAIL("") -+ return test.FAIL("Senders are different") - sender_flow_id = response.json()["flow_id"] - if sender_flow_id is None: - return test.FAIL("the sender must have a flow") -@@ -406,7 +364,7 @@ class IS1102Test(GenericTest): - "GET", self.node_url + "flows/" + sender_flow_id - ) - if response.status_code != 200: -- return test.FAIL(response.json()) -+ return test.FAIL("The request has not succeeded.", response.json()) - flow_format = response.json()["format"] - self.flow_format[sender_id] = flow_format - if flow_format == "urn:x-nmos:format:video": -@@ -427,7 +385,7 @@ class IS1102Test(GenericTest): - ): - print("only audio and video senders are tested at this time.") - return test.PASS() -- return test.UNCLEAR("there is no IS-11 senders") -+ return test.UNCLEAR("There is no IS-11 senders.") - - def test_02_02_03_01(self, test): - "Verify that the video sender supports the minimum set of video constraints" -@@ -435,15 +393,15 @@ class IS1102Test(GenericTest): - pattern = "^urn:x-nmos:cap:" - - if len(self.flow_format_video) == 0: -- return test.UNCLEAR("There is no video format") -+ return test.UNCLEAR("There is no video format.") - - for sender_id in self.flow_format_video: - _, response = TestHelper.do_request( - "GET", -- self.compat_url + "senders/" + sender_id + "constraints/supported/ ", -+ self.compat_url + "senders/" + sender_id + "constraints/supported/", - ) - if response.status_code != 200: -- return test.FAIL(response.json()) -+ return test.FAIL("The request has not succeeded.", response.json()) - supportedConstraints = response.json()["parameter_constraints"] - for item in supportedConstraints: - if not re.search(pattern, item): -@@ -459,15 +417,15 @@ class IS1102Test(GenericTest): - pattern = "^urn:x-nmos:cap:" - - if len(self.flow_format_audio) == 0: -- return test.UNCLEAR("There is no video format") -+ return test.UNCLEAR("There is no video format.") - - for sender_id in self.flow_format_audio: - _, response = TestHelper.do_request( - "GET", -- self.compat_url + "senders/" + sender_id + "constraints/supported/ ", -+ self.compat_url + "senders/" + sender_id + "constraints/supported/", - ) - if response.status_code != 200: -- return test.FAIL(response.json()) -+ return test.FAIL("The request has not succeeded.", response.json()) - supportedConstraints = response.json()["parameter_constraints"] - for item in supportedConstraints: - if not re.search(pattern, item): -@@ -484,14 +442,14 @@ class IS1102Test(GenericTest): - the associated IS-04 sender. - """ - if len(self.flow_format_video) == 0: -- return test.UNCLEAR("There is no video format") -+ return test.UNCLEAR("There is no video format.") - - for sender_id in self.flow_format_video: - _, response = TestHelper.do_request( - "GET", self.node_url + "senders/" + sender_id - ) - if response.status_code != 200: -- return test.FAIL(response.json()) -+ return test.FAIL("The request has not succeeded.", response.json()) - version = response.json()["version"] - self.version[sender_id] = version - self.grain_rate_constraints[sender_id] = { -@@ -510,47 +468,49 @@ class IS1102Test(GenericTest): - json=self.grain_rate_constraints[sender_id], - ) - if response.status_code != 200: -- return test.FAIL(response.json()) -+ return test.FAIL("The request has not succeeded.", response.json()) - _, response = TestHelper.do_request( - "GET", self.node_url + "senders/" + sender_id - ) - if response.status_code != 200: -- return test.FAIL(response.json()) -+ return test.FAIL("The request has not succeeded.", response.json()) - version = response.json()["version"] - if version == self.version[sender_id]: -- return test.FAIL() -+ return test.FAIL("Version are different") - self.version[sender_id] = version - _, response = TestHelper.do_request( -- "GET", self.compat_url + "senders/" + sender_id + "constraints/active/ " -+ "GET", self.compat_url + "senders/" + sender_id + "constraints/active/" - ) - if response.status_code != 200: -- return test.FAIL(response.json()) -+ return test.FAIL("The request has not succeeded.", response.json()) - constraints = response.json() -- if constraints != self.grain_rate_constraints[sender_id]: -- return test.FAIL() -+ if not nmosTestingComplexCompare.compare_complex_grain_rate_constraint( -+ constraints, self.grain_rate_constraints[sender_id] -+ ): -+ return test.FAIL("Contraints are different") - _, response = TestHelper.do_request( - "DELETE", - self.compat_url + "senders/" + sender_id + "constraints/active/", - ) - if response.status_code != 200: -- return test.FAIL(response.json()) -+ return test.FAIL("The request has not succeeded.", response.json()) - _, response = TestHelper.do_request( - "GET", self.node_url + "senders/" + sender_id - ) - if response.status_code != 200: -- return test.FAIL(response.json()) -+ return test.FAIL("The request has not succeeded.", response.json()) - version = response.json()["version"] - if version == self.version[sender_id]: -- return test.FAIL() -+ return test.FAIL("Version are different") - self.version[sender_id] = version - _, response = TestHelper.do_request( - "GET", self.compat_url + "senders/" + sender_id + "constraints/active/" - ) - if response.status_code != 200: -- return test.FAIL(response.json()) -+ return test.FAIL("The request has not succeeded.", response.json()) - constraints = response.json() - if constraints != self.empty_constraints[sender_id]: -- return test.FAIL("Constraints doesn't match") -+ return test.FAIL("Contraints are different") - return test.PASS() - - def test_02_02_04_02(self, test): -@@ -559,13 +519,13 @@ class IS1102Test(GenericTest): - sender(audio) changes the version of the associated IS-04 sender. - """ - if len(self.flow_format_audio) == 0: -- return test.UNCLEAR("There is no audio format") -+ return test.UNCLEAR("There is no audio format.") - for sender_id in self.flow_format_audio: - _, response = TestHelper.do_request( - "GET", self.node_url + "senders/" + sender_id - ) - if response.status_code != 200: -- return test.FAIL(response.json()) -+ return test.FAIL("The request has not succeeded.", response.json()) - version = response.json()["version"] - self.version[sender_id] = version - self.sample_rate_constraints[sender_id] = { -@@ -584,25 +544,25 @@ class IS1102Test(GenericTest): - json=self.sample_rate_constraints[sender_id], - ) - if response.status_code != 200: -- return test.FAIL(response.json()) -+ return test.FAIL("The request has not succeeded.", response.json()) - _, response = TestHelper.do_request( - "GET", self.node_url + "senders/" + sender_id - ) - if response.status_code != 200: -- return test.FAIL(response.json()) -+ return test.FAIL("The request has not succeeded.", response.json()) - version = response.json()["version"] - if version == self.version[sender_id]: -- return test.FAIL() -+ return test.FAIL("Version are different") - self.version[sender_id] = version - - _, response = TestHelper.do_request( - "GET", self.compat_url + "senders/" + sender_id + "constraints/active/" - ) - if response.status_code != 200: -- return test.FAIL(response.json()) -+ return test.FAIL("The request has not succeeded.", response.json()) - constraints = response.json() - -- if not self.compare_complex( -+ if not nmosTestingComplexCompare.compare_complex_sample_rate_constraint( - constraints, self.sample_rate_constraints[sender_id] - ): - return test.FAIL( -@@ -616,43 +576,42 @@ class IS1102Test(GenericTest): - self.compat_url + "senders/" + sender_id + "constraints/active/", - ) - if response.status_code != 200: -- return test.FAIL(response.json()) -+ return test.FAIL("The request has not succeeded.", response.json()) - - _, response = TestHelper.do_request( - "GET", self.node_url + "senders/" + sender_id - ) - if response.status_code != 200: -- return test.FAIL(response.json()) -+ return test.FAIL("The request has not succeeded.", response.json()) - version = response.json()["version"] - if version == self.version[sender_id]: -- return test.FAIL() -+ return test.FAIL("Version are different") - self.version[sender_id] = version - - _, response = TestHelper.do_request( -- "GET", self.compat_url + "senders/" + sender_id + "constraints/active/ " -+ "GET", self.compat_url + "senders/" + sender_id + "constraints/active/" - ) - if response.status_code != 200: -- return test.FAIL(response.json()) -+ return test.FAIL("The request has not succeeded.", response.json()) - constraints = response.json() - if constraints != self.empty_constraints[sender_id]: -- return test.FAIL("Constraints doesn't match") -+ return test.FAIL("Contraints are different") - return test.PASS() - - def test_02_02_05_01(self, test): -- """Verify that setting NOP constraints for frame_width, -- frame_height and grain_rate does not change the flow of -- a sender (video) and that the state goes from \"unconstrained\" -- to \"constrained\" -+ """ -+ Verify that setting NOP constraints for frame(width,height), -+ grain_rate doesn't change the flow of a sender(video). - """ - if len(self.flow_format_video) == 0: -- return test.UNCLEAR("There is no audio format ") -+ return test.UNCLEAR("There is no video format.") - - for sender_id in self.flow_format_video: - _, response = TestHelper.do_request( - "GET", self.compat_url + "senders/" + sender_id + "status/" - ) - if response.status_code != 200: -- return test.FAIL(response.json()) -+ return test.FAIL("The request has not succeeded.", response.json()) - state = response.json()["state"] - if state in ["awating_essence", "no_essence"]: - for i in range(0, 5): -@@ -693,13 +652,13 @@ class IS1102Test(GenericTest): - json=self.constraints[sender_id], - ) - if response.status_code != 200: -- return test.FAIL(response.json()) -+ return test.FAIL("The request has not succeeded.", response.json()) - - _, response = TestHelper.do_request( - "GET", self.compat_url + "senders/" + sender_id + "status/" - ) - if response.status_code != 200: -- return test.FAIL(response.json()) -+ return test.FAIL("The request has not succeeded.", response.json()) - state = response.json()["state"] - - if state in ["awating_essence", "no_essence"]: -@@ -728,7 +687,7 @@ class IS1102Test(GenericTest): - "GET", self.node_url + "flows/" + sender_flow_id - ) - if response.status_code != 200: -- return test.FAIL(response.json()) -+ return test.FAIL("The request has not succeeded.", response.json()) - - if ( - self.flow_grain_rate[sender_id] != response.json()["grain_rate"] -@@ -742,23 +701,24 @@ class IS1102Test(GenericTest): - self.compat_url + "senders/" + sender_id + "constraints/active/", - ) - if response.status_code != 200: -- return test.FAIL(response.json()) -+ return test.FAIL("The request has not succeeded.", response.json()) - - return test.PASS() - - def test_02_02_05_02(self, test): -- """Verify that setting NOP constraints for sample_rate does not change the flow of a sender (audio) and \ -- that the state goes from \"unconstrained\" to \"constrained\"""" -+ """ -+ Verify that setting NOP constraints for sample_rate doesn't change the flow of a sender(audio). -+ """ - - if len(self.flow_format_audio) == 0: -- return test.UNCLEAR("There is no audio format ") -+ return test.UNCLEAR("There is no audio format.") - - for sender_id in self.flow_format_audio: - _, response = TestHelper.do_request( - "GET", self.compat_url + "senders/" + sender_id + "status/" - ) - if response.status_code != 200: -- return test.FAIL(response.json()) -+ return test.FAIL("The request has not succeeded.", response.json()) - state = response.json()["state"] - - if state in ["awating_essence", "no_essence"]: -@@ -789,13 +749,13 @@ class IS1102Test(GenericTest): - json=self.constraints[sender_id], - ) - if response.status_code != 200: -- return test.FAIL(response.json()) -+ return test.FAIL("The request has not succeeded.", response.json()) - - _, response = TestHelper.do_request( - "GET", self.compat_url + "senders/" + sender_id + "status/" - ) - if response.status_code != 200: -- return test.FAIL(response.json()) -+ return test.FAIL("The request has not succeeded.", response.json()) - state = response.json()["state"] - - if state in ["awating_essence", "no_essence"]: -@@ -824,7 +784,7 @@ class IS1102Test(GenericTest): - "GET", self.node_url + "flows/" + sender_flow_id - ) - if response.status_code != 200: -- return test.FAIL(response.json()) -+ return test.FAIL("The request has not succeeded.", response.json()) - flow_sample_rate = response.json()["sample_rate"] - if self.flow_sample_rate[sender_id] != flow_sample_rate: - return test.FAIL("Different sample rate") -@@ -834,21 +794,23 @@ class IS1102Test(GenericTest): - self.compat_url + "senders/" + sender_id + "constraints/active/", - ) - if response.status_code != 200: -- return test.FAIL(response.json()) -+ return test.FAIL("The request has not succeeded.", response.json()) - return test.PASS() - - def test_02_02_06_01(self, test): -- """Verify that setting NOP constraints for supported constraints does not change the flow of a sender (video) \ -- and that the state goes from \"unconstrained\" to \"constrained\"""" -+ """ -+ Verify that setting NOP constraints for supported constraints -+ doesn't change the flow of a sender(video). -+ """ - if len(self.flow_format_video) == 0: -- return test.UNCLEAR("There is no audio format ") -+ return test.UNCLEAR("There is no video format.") - - for sender_id in self.flow_format_video: - _, response = TestHelper.do_request( - "GET", self.compat_url + "senders/" + sender_id + "status/" - ) - if response.status_code != 200: -- return test.FAIL(response.json()) -+ return test.FAIL("The request has not succeeded.", response.json()) - state = response.json()["state"] - if state in ["awating_essence", "no_essence"]: - for i in range(0, 5): -@@ -874,7 +836,7 @@ class IS1102Test(GenericTest): - "GET", self.node_url + "flows/" + sender["flow_id"] - ) - if response.status_code != 200: -- return test.FAIL(response.json()) -+ return test.FAIL("The request has not succeeded.", response.json()) - flow = response.json() - color_sampling = self.getSdpColorSampling(flow["components"]) - if color_sampling == "invalid array of video components": -@@ -882,41 +844,44 @@ class IS1102Test(GenericTest): - constraint_set = {} - - for item in REF_SUPPORTED_CONSTRAINTS_VIDEO: -+ try: -+ if item == "urn:x-nmos:cap:meta:label": -+ constraint_set["urn:x-nmos:cap:meta:label"] = "video constraint" -+ if item == "urn:x-nmos:cap:meta:preference": -+ constraint_set["urn:x-nmos:cap:meta:preference"] = 0 -+ if item == "urn:x-nmos:cap:meta:enabled": -+ 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"]] -+ } -+ if item == "urn:x-nmos:cap:format:grain_rate": -+ constraint_set["urn:x-nmos:cap:format:grain_rate"] = { -+ "enum": [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"]] -+ } -+ if item == "urn:x-nmos:cap:format:frame_height": -+ constraint_set["urn:x-nmos:cap:format:frame_height"] = { -+ "enum": [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"]] -+ } -+ if item == "urn:x-nmos:cap:format:color_sampling": -+ constraint_set["urn:x-nmos:cap:format:color_sampling"] = { -+ "enum": [color_sampling] -+ } -+ if item == "urn:x-nmos:cap:format:component_depth": -+ constraint_set["urn:x-nmos:cap:format:component_depth"] = { -+ "enum": [flow["components"][0]["bit_depth"]] -+ } -+ except Exception: -+ pass - -- if item == "urn:x-nmos:cap:meta:label": -- constraint_set["urn:x-nmos:cap:meta:label"] = "video constraint" -- if item == "urn:x-nmos:cap:meta:preference": -- constraint_set["urn:x-nmos:cap:meta:preference"] = 0 -- if item == "urn:x-nmos:cap:meta:enabled": -- 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"]] -- } -- if item == "urn:x-nmos:cap:format:grain_rate": -- constraint_set["urn:x-nmos:cap:format:grain_rate"] = { -- "enum": [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"]] -- } -- if item == "urn:x-nmos:cap:format:frame_height": -- constraint_set["urn:x-nmos:cap:format:frame_height"] = { -- "enum": [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"]] -- } -- if item == "urn:x-nmos:cap:format:color_sampling": -- constraint_set["urn:x-nmos:cap:format:color_sampling"] = { -- "enum": [color_sampling] -- } -- if item == "urn:x-nmos:cap:format:component_depth": -- constraint_set["urn:x-nmos:cap:format:component_depth"] = { -- "enum": [flow["components"][0]["bit_depth"]] -- } - self.constraints[sender_id] = {"constraint_sets": [constraint_set]} - - _, response = TestHelper.do_request( -@@ -925,13 +890,13 @@ class IS1102Test(GenericTest): - json=self.constraints[sender_id], - ) - if response.status_code != 200: -- return test.FAIL(response.json()) -+ return test.FAIL("The request has not succeeded.", response.json()) - - _, response = TestHelper.do_request( - "GET", self.node_url + "flows/" + sender["flow_id"] - ) - if response.status_code != 200: -- return test.FAIL(response.json()) -+ return test.FAIL("The request has not succeeded.", response.json()) - new_flow = response.json() - - new_color_sampling = self.getSdpColorSampling(new_flow["components"]) -@@ -939,53 +904,54 @@ class IS1102Test(GenericTest): - return test.FAIL("invalid array of video components") - - for item in REF_SUPPORTED_CONSTRAINTS_VIDEO: -- -- if item == "urn:x-nmos:cap:format:media_type": -- if 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"]: -- return test.FAIL("different grain_rate") -- if item == "urn:x-nmos:cap:format:frame_width": -- if 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"]: -- return test.FAIL("different frame_height") -- if item == "urn:x-nmos:cap:format:interlace_mode": -- if 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"] -- != new_flow["components"][0]["bit_depth"] -- ): -- return test.FAIL("different component_depth") -- -+ try: -+ if item == "urn:x-nmos:cap:format:media_type": -+ if 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"]: -+ return test.FAIL("different grain_rate") -+ if item == "urn:x-nmos:cap:format:frame_width": -+ if 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"]: -+ return test.FAIL("different frame_height") -+ if item == "urn:x-nmos:cap:format:interlace_mode": -+ if 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"] -+ != new_flow["components"][0]["bit_depth"] -+ ): -+ return test.FAIL("different component_depth") -+ except Exception: -+ pass - _, response = TestHelper.do_request( - "DELETE", - self.compat_url + "senders/" + sender_id + "constraints/active/", - ) - if response.status_code != 200: -- return test.FAIL(response.json()) -+ return test.FAIL("The request has not succeeded.", response.json()) - return test.PASS() - - def test_02_02_06_02(self, test): -- """Verify that setting NOP constraints for supported -- constraints does not change the flow of a sender (audio) -- and that the state goes from \"unconstrained\" to \"constrained\" -+ """ -+ Verify that setting NOP constraints for supported -+ constraints doesn't change the flow of a sender(audio). - """ - if len(self.flow_format_audio) == 0: -- return test.UNCLEAR("There is no audio format") -+ return test.UNCLEAR("There is no audio format.") - for sender_id in self.flow_format_audio: - _, response = TestHelper.do_request( - "GET", self.compat_url + "senders/" + sender_id + "status/" - ) - if response.status_code != 200: -- test.FAIL(response.json()) -+ test.FAIL("The request has not succeeded.", response.json()) - state = response.json()["state"] - if state in ["awating_essence", "no_essence"]: - for i in range(0, 5): -@@ -1010,7 +976,7 @@ class IS1102Test(GenericTest): - "GET", self.node_url + "flows/" + sender["flow_id"] - ) - if response.status_code != 200: -- return test.FAIL(response.json()) -+ return test.FAIL("The request has not succeeded.", response.json()) - flow = response.json() - constraint_set = {} - -@@ -1018,34 +984,36 @@ class IS1102Test(GenericTest): - "GET", self.node_url + "sources/" + flow["source_id"] - ) - if response.status_code != 200: -- return test.FAIL(response.json()) -+ return test.FAIL("The request has not succeeded.", response.json()) - source = response.json() - - for item in REF_SUPPORTED_CONSTRAINTS_AUDIO: -- -- if item == "urn:x-nmos:cap:meta:label": -- constraint_set["urn:x-nmos:cap:meta:label"] = "video constraint" -- if item == "urn:x-nmos:cap:meta:preference": -- constraint_set["urn:x-nmos:cap:meta:preference"] = 0 -- if item == "urn:x-nmos:cap:meta:enabled": -- 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"]] -- } -- if item == "urn:x-nmos:cap:format:sample_rate": -- constraint_set["urn:x-nmos:cap:format:sample_rate"] = { -- "enum": [flow["sample_rate"]] -- } -- if item == "urn:x-nmos:cap:format:channel_count": -- constraint_set["urn:x-nmos:cap:format:channel_count"] = { -- "enum": [len(source["channels"])] -- } -- if item == "urn:x-nmos:cap:format:sample_depth": -- constraint_set["urn:x-nmos:cap:format:sample_depth"] = { -- "enum": [flow["bit_depth"]] -- } -- -+ try: -+ -+ if item == "urn:x-nmos:cap:meta:label": -+ constraint_set["urn:x-nmos:cap:meta:label"] = "audio constraint" -+ if item == "urn:x-nmos:cap:meta:preference": -+ constraint_set["urn:x-nmos:cap:meta:preference"] = 0 -+ if item == "urn:x-nmos:cap:meta:enabled": -+ 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"]] -+ } -+ if item == "urn:x-nmos:cap:format:sample_rate": -+ constraint_set["urn:x-nmos:cap:format:sample_rate"] = { -+ "enum": [flow["sample_rate"]] -+ } -+ if item == "urn:x-nmos:cap:format:channel_count": -+ constraint_set["urn:x-nmos:cap:format:channel_count"] = { -+ "enum": [len(source["channels"])] -+ } -+ if item == "urn:x-nmos:cap:format:sample_depth": -+ constraint_set["urn:x-nmos:cap:format:sample_depth"] = { -+ "enum": [flow["bit_depth"]] -+ } -+ except Exception: -+ pass - self.constraints[sender_id] = {"constraint_sets": [constraint_set]} - _, response = TestHelper.do_request( - "PUT", -@@ -1053,56 +1021,57 @@ class IS1102Test(GenericTest): - json=self.constraints[sender_id], - ) - if response.status_code != 200: -- return test.FAIL(response.json()) -+ return test.FAIL("The request has not succeeded.", response.json()) - - _, response = TestHelper.do_request( - "GET", self.node_url + "flows/" + sender["flow_id"] - ) - if response.status_code != 200: -- return test.FAIL(response.json()) -+ return test.FAIL("The request has not succeeded.", response.json()) - new_flow = response.json() - - _, response = TestHelper.do_request( - "GET", self.node_url + "sources/" + flow["source_id"] - ) - if response.status_code != 200: -- return test.FAIL(response.json()) -+ return test.FAIL("The request has not succeeded.", response.json()) - new_source = response.json() - - for item in REF_SUPPORTED_CONSTRAINTS_AUDIO: -- -- if item == "urn:x-nmos:cap:format:media_type": -- if 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"]: -- 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"]: -- return test.FAIL("different sample_depth") -- -+ try: -+ if item == "urn:x-nmos:cap:format:media_type": -+ if 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"]: -+ 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"]: -+ return test.FAIL("different sample_depth") -+ except Exception: -+ pass - _, response = TestHelper.do_request( - "DELETE", - self.compat_url + "senders/" + sender_id + "constraints/active/", - ) - if response.status_code != 200: -- return test.FAIL(response.json()) -+ return test.FAIL("The request has not succeeded.", response.json()) - return test.PASS() - - def test_02_02_07_01(self, test): - "Verify that the device adhere to the preference of the constraint_set." - if len(self.flow_format_video) == 0: -- return test.UNCLEAR("There is no audio format ") -+ return test.UNCLEAR("There is no video format.") - - for sender_id in self.flow_format_video: - _, response = TestHelper.do_request( - "GET", self.compat_url + "senders/" + sender_id + "status/" - ) - if response.status_code != 200: -- return test.FAIL(response.json()) -+ return test.FAIL("The request has not succeeded.", response.json()) - state = response.json()["state"] - if state in ["awating_essence", "no_essence"]: - for i in range(0, 5): -@@ -1128,7 +1097,7 @@ class IS1102Test(GenericTest): - "GET", self.node_url + "flows/" + sender["flow_id"] - ) - if response.status_code != 200: -- return test.FAIL(response.json()) -+ return test.FAIL("The request has not succeeded.", response.json()) - flow = response.json() - color_sampling = self.getSdpColorSampling(flow["components"]) - if color_sampling == "invalid array of video components": -@@ -1137,78 +1106,87 @@ class IS1102Test(GenericTest): - constraint_set1 = {} - - for item in REF_SUPPORTED_CONSTRAINTS_VIDEO: -- -- if item == "urn:x-nmos:cap:meta:label": -- constraint_set0["urn:x-nmos:cap:meta:label"] = "video constraint" -- if item == "urn:x-nmos:cap:meta:preference": -- constraint_set0["urn:x-nmos:cap:meta:preference"] = 0 -- if item == "urn:x-nmos:cap:meta:enabled": -- 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"]] -- } -- if item == "urn:x-nmos:cap:format:grain_rate": -- constraint_set0["urn:x-nmos:cap:format:grain_rate"] = { -- "enum": [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"]] -- } -- if item == "urn:x-nmos:cap:format:frame_height": -- constraint_set0["urn:x-nmos:cap:format:frame_height"] = { -- "enum": [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"]] -- } -- if item == "urn:x-nmos:cap:format:color_sampling": -- constraint_set0["urn:x-nmos:cap:format:color_sampling"] = { -- "enum": [color_sampling] -- } -- if item == "urn:x-nmos:cap:format:component_depth": -- constraint_set0["urn:x-nmos:cap:format:component_depth"] = { -- "enum": [flow["components"][0]["bit_depth"]] -- } -+ try: -+ -+ if item == "urn:x-nmos:cap:meta:label": -+ constraint_set0[ -+ "urn:x-nmos:cap:meta:label" -+ ] = "video constraint" -+ if item == "urn:x-nmos:cap:meta:preference": -+ constraint_set0["urn:x-nmos:cap:meta:preference"] = 0 -+ if item == "urn:x-nmos:cap:meta:enabled": -+ 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"]] -+ } -+ if item == "urn:x-nmos:cap:format:grain_rate": -+ constraint_set0["urn:x-nmos:cap:format:grain_rate"] = { -+ "enum": [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"]] -+ } -+ if item == "urn:x-nmos:cap:format:frame_height": -+ constraint_set0["urn:x-nmos:cap:format:frame_height"] = { -+ "enum": [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"]] -+ } -+ if item == "urn:x-nmos:cap:format:color_sampling": -+ constraint_set0["urn:x-nmos:cap:format:color_sampling"] = { -+ "enum": [color_sampling] -+ } -+ if item == "urn:x-nmos:cap:format:component_depth": -+ constraint_set0["urn:x-nmos:cap:format:component_depth"] = { -+ "enum": [flow["components"][0]["bit_depth"]] -+ } -+ except Exception: -+ pass - - for item in REF_SUPPORTED_CONSTRAINTS_VIDEO: -- -- if item == "urn:x-nmos:cap:meta:label": -- constraint_set1["urn:x-nmos:cap:meta:label"] = "video constraint" -- if item == "urn:x-nmos:cap:meta:preference": -- constraint_set1["urn:x-nmos:cap:meta:preference"] = -100 -- if item == "urn:x-nmos:cap:meta:enabled": -- 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"]] -- } -- 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"])] -- } -- if item == "urn:x-nmos:cap:format:frame_width": -- constraint_set1["urn:x-nmos:cap:format:frame_width"] = { -- "enum": [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"]] -- } -- if item == "urn:x-nmos:cap:format:interlace_mode": -- constraint_set1["urn:x-nmos:cap:format:interlace_mode"] = { -- "enum": [flow["interlace_mode"]] -- } -- if item == "urn:x-nmos:cap:format:color_sampling": -- constraint_set1["urn:x-nmos:cap:format:color_sampling"] = { -- "enum": [color_sampling] -- } -- if item == "urn:x-nmos:cap:format:component_depth": -- constraint_set1["urn:x-nmos:cap:format:component_depth"] = { -- "enum": [flow["components"][0]["bit_depth"]] -- } -+ try: -+ if item == "urn:x-nmos:cap:meta:label": -+ constraint_set1[ -+ "urn:x-nmos:cap:meta:label" -+ ] = "video constraint" -+ if item == "urn:x-nmos:cap:meta:preference": -+ constraint_set1["urn:x-nmos:cap:meta:preference"] = -100 -+ if item == "urn:x-nmos:cap:meta:enabled": -+ 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"]] -+ } -+ 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"])] -+ } -+ if item == "urn:x-nmos:cap:format:frame_width": -+ constraint_set1["urn:x-nmos:cap:format:frame_width"] = { -+ "enum": [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"]] -+ } -+ if item == "urn:x-nmos:cap:format:interlace_mode": -+ constraint_set1["urn:x-nmos:cap:format:interlace_mode"] = { -+ "enum": [flow["interlace_mode"]] -+ } -+ if item == "urn:x-nmos:cap:format:color_sampling": -+ constraint_set1["urn:x-nmos:cap:format:color_sampling"] = { -+ "enum": [color_sampling] -+ } -+ if item == "urn:x-nmos:cap:format:component_depth": -+ constraint_set1["urn:x-nmos:cap:format:component_depth"] = { -+ "enum": [flow["components"][0]["bit_depth"]] -+ } -+ except Exception: -+ pass - - self.constraints[sender_id] = { - "constraint_sets": [constraint_set0, constraint_set0] -@@ -1219,13 +1197,13 @@ class IS1102Test(GenericTest): - json=self.constraints[sender_id], - ) - if response.status_code != 200: -- return test.FAIL(response.json()) -+ return test.FAIL("The request has not succeeded.", response.json()) - - _, response = TestHelper.do_request( - "GET", self.node_url + "flows/" + sender["flow_id"] - ) - if response.status_code != 200: -- return test.FAIL(response.json()) -+ return test.FAIL("The request has not succeeded.", response.json()) - new_flow = response.json() - - new_color_sampling = self.getSdpColorSampling(new_flow["components"]) -@@ -1233,51 +1211,53 @@ class IS1102Test(GenericTest): - return test.FAIL("invalid array of video components") - - for item in REF_SUPPORTED_CONSTRAINTS_VIDEO: -- -- if item == "urn:x-nmos:cap:format:media_type": -- if 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"]: -- return test.FAIL("different grain_rate") -- if item == "urn:x-nmos:cap:format:frame_width": -- if 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"]: -- return test.FAIL("different frame_height") -- if item == "urn:x-nmos:cap:format:interlace_mode": -- if 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"] -- != new_flow["components"][0]["bit_depth"] -- ): -- return test.FAIL("different component_depth") -+ try: -+ if item == "urn:x-nmos:cap:format:media_type": -+ if 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"]: -+ return test.FAIL("different grain_rate") -+ if item == "urn:x-nmos:cap:format:frame_width": -+ if 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"]: -+ return test.FAIL("different frame_height") -+ if item == "urn:x-nmos:cap:format:interlace_mode": -+ if 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"] -+ != new_flow["components"][0]["bit_depth"] -+ ): -+ return test.FAIL("different component_depth") -+ except Exception: -+ pass - - _, response = TestHelper.do_request( - "DELETE", - self.compat_url + "senders/" + sender_id + "constraints/active/", - ) - if response.status_code != 200: -- return test.FAIL(response.json()) -+ return test.FAIL("The request has not succeeded.", response.json()) - return test.PASS() - - def test_02_02_07_02(self, test): - "Verify that the device adhere to the preference of the constraint_set." - if len(self.flow_format_audio) == 0: -- return test.UNCLEAR("There is no audio format") -+ return test.UNCLEAR("There is no audio format.") - - for sender_id in self.flow_format_audio: - _, response = TestHelper.do_request( - "GET", self.compat_url + "senders/" + sender_id + "status/" - ) - if response.status_code != 200: -- test.FAIL(response.json()) -+ test.FAIL("The request has not succeeded.", response.json()) - state = response.json()["state"] - if state in ["awating_essence", "no_essence"]: - for i in range(0, 5): -@@ -1303,67 +1283,75 @@ class IS1102Test(GenericTest): - "GET", self.node_url + "flows/" + sender["flow_id"] - ) - if response.status_code != 200: -- return test.FAIL(response.json()) -+ return test.FAIL("The request has not succeeded.", response.json()) - flow = response.json() - _, response = TestHelper.do_request( - "GET", self.node_url + "sources/" + flow["source_id"] - ) - if response.status_code != 200: -- return test.FAIL(response.json()) -+ return test.FAIL("The request has not succeeded.", response.json()) - source = response.json() - - constraint_set0 = {} - constraint_set1 = {} - - for item in REF_SUPPORTED_CONSTRAINTS_AUDIO: -- -- if item == "urn:x-nmos:cap:meta:label": -- constraint_set0["urn:x-nmos:cap:meta:label"] = "video constraint" -- if item == "urn:x-nmos:cap:meta:preference": -- constraint_set0["urn:x-nmos:cap:meta:preference"] = 0 -- if item == "urn:x-nmos:cap:meta:enabled": -- 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"]] -- } -- if item == "urn:x-nmos:cap:format:sample_rate": -- constraint_set0["urn:x-nmos:cap:format:sample_rate"] = { -- "enum": [flow["sample_rate"]] -- } -- if item == "urn:x-nmos:cap:format:channel_count": -- constraint_set0["urn:x-nmos:cap:format:channel_count"] = { -- "enum": [len(source["channels"])] -- } -- if item == "urn:x-nmos:cap:format:sample_depth": -- constraint_set0["urn:x-nmos:cap:format:sample_depth"] = { -- "enum": [flow["bit_depth"]] -- } -+ try: -+ if item == "urn:x-nmos:cap:meta:label": -+ constraint_set0[ -+ "urn:x-nmos:cap:meta:label" -+ ] = "audio constraint" -+ if item == "urn:x-nmos:cap:meta:preference": -+ constraint_set0["urn:x-nmos:cap:meta:preference"] = 0 -+ if item == "urn:x-nmos:cap:meta:enabled": -+ 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"]] -+ } -+ if item == "urn:x-nmos:cap:format:sample_rate": -+ constraint_set0["urn:x-nmos:cap:format:sample_rate"] = { -+ "enum": [flow["sample_rate"]] -+ } -+ if item == "urn:x-nmos:cap:format:channel_count": -+ constraint_set0["urn:x-nmos:cap:format:channel_count"] = { -+ "enum": [len(source["channels"])] -+ } -+ if item == "urn:x-nmos:cap:format:sample_depth": -+ constraint_set0["urn:x-nmos:cap:format:sample_depth"] = { -+ "enum": [flow["bit_depth"]] -+ } -+ except Exception: -+ pass - - for item in REF_SUPPORTED_CONSTRAINTS_AUDIO: -- -- if item == "urn:x-nmos:cap:meta:label": -- constraint_set1["urn:x-nmos:cap:meta:label"] = "video constraint" -- if item == "urn:x-nmos:cap:meta:preference": -- constraint_set1["urn:x-nmos:cap:meta:preference"] = -100 -- if item == "urn:x-nmos:cap:meta:enabled": -- 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"]] -- } -- 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"])] -- } -- if item == "urn:x-nmos:cap:format:channel_count": -- constraint_set1["urn:x-nmos:cap:format:channel_count"] = { -- "enum": [len(source["channels"])] -- } -- if item == "urn:x-nmos:cap:format:sample_depth": -- constraint_set1["urn:x-nmos:cap:format:sample_depth"] = { -- "enum": [flow["bit_depth"]] -- } -+ try: -+ if item == "urn:x-nmos:cap:meta:label": -+ constraint_set1[ -+ "urn:x-nmos:cap:meta:label" -+ ] = "video constraint" -+ if item == "urn:x-nmos:cap:meta:preference": -+ constraint_set1["urn:x-nmos:cap:meta:preference"] = -100 -+ if item == "urn:x-nmos:cap:meta:enabled": -+ 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"]] -+ } -+ 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"])] -+ } -+ if item == "urn:x-nmos:cap:format:channel_count": -+ constraint_set1["urn:x-nmos:cap:format:channel_count"] = { -+ "enum": [len(source["channels"])] -+ } -+ if item == "urn:x-nmos:cap:format:sample_depth": -+ constraint_set1["urn:x-nmos:cap:format:sample_depth"] = { -+ "enum": [flow["bit_depth"]] -+ } -+ except Exception: -+ pass - - self.constraints[sender_id] = { - "constraint_sets": [constraint_set0, constraint_set1] -@@ -1375,40 +1363,41 @@ class IS1102Test(GenericTest): - json=self.constraints[sender_id], - ) - if response.status_code != 200: -- return test.FAIL(response.json()) -+ return test.FAIL("The request has not succeeded.", response.json()) - _, response = TestHelper.do_request( - "GET", self.node_url + "flows/" + sender["flow_id"] - ) - if response.status_code != 200: -- return test.FAIL(response.json()) -+ return test.FAIL("The request has not succeeded.", response.json()) - new_flow = response.json() - - _, response = TestHelper.do_request( - "GET", self.node_url + "sources/" + flow["source_id"] - ) - if response.status_code != 200: -- return test.FAIL(response.json()) -+ return test.FAIL("The request has not succeeded.", response.json()) - new_source = response.json() - - for item in REF_SUPPORTED_CONSTRAINTS_AUDIO: -- -- if item == "urn:x-nmos:cap:format:media_type": -- if 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"]: -- 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"]: -- return test.FAIL("different sample_depth") -- -+ try: -+ if item == "urn:x-nmos:cap:format:media_type": -+ if 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"]: -+ 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"]: -+ return test.FAIL("different sample_depth") -+ except Exception: -+ pass - _, response = TestHelper.do_request( - "DELETE", - self.compat_url + "senders/" + sender_id + "constraints/active/", - ) - if response.status_code != 200: -- return test.FAIL(response.json()) -+ return test.FAIL("The request has not succeeded.", response.json()) - return test.PASS() --- -2.39.1.windows.1 - diff --git a/0001-Initial-submit-for-tests-general-0-Senders-2-Outputs.patch b/0001-Initial-submit-for-tests-general-0-Senders-2-Outputs.patch deleted file mode 100644 index 9b838dc8..00000000 --- a/0001-Initial-submit-for-tests-general-0-Senders-2-Outputs.patch +++ /dev/null @@ -1,1777 +0,0 @@ -From 036890ddfffa20727a541f0b0ff36754f066c456 Mon Sep 17 00:00:00 2001 -From: ggeorgea -Date: Sun, 19 Feb 2023 21:05:13 -0500 -Subject: [PATCH] Initial submit for tests general (0), Senders (2), Outputs - (3), Receivers (4) - -Based on IS-11 Test Plan v0.6 -Senders contains tests 2.0, 2.1 and 2.2 -Receivers contains tests 4.1 and 4.2 ---- - nmostesting/suites/IS1101Test.py | 332 ++++++- - nmostesting/suites/IS1102Test.py | 1392 +++++++++++++++++++++++++++++- - 2 files changed, 1720 insertions(+), 4 deletions(-) - -diff --git a/nmostesting/suites/IS1101Test.py b/nmostesting/suites/IS1101Test.py -index 14277a0..637d314 100644 ---- a/nmostesting/suites/IS1101Test.py -+++ b/nmostesting/suites/IS1101Test.py -@@ -13,14 +13,344 @@ - # limitations under the License. - - from ..GenericTest import GenericTest -+from .. import TestHelper - - COMPAT_API_KEY = "streamcompatibility" -+CONTROLS = "controls" - - - class IS1101Test(GenericTest): - """ - Runs Node Tests covering IS-11 - """ -+ - def __init__(self, apis): -- GenericTest.__init__(self, apis) -+ # Don't auto-test paths responding with an EDID binary as they don't have a JSON Schema -+ omit_paths = [ -+ "/inputs/{inputId}/edid", -+ "/inputs/{inputId}/edid/base", -+ "/inputs/{inputId}/edid/effective", -+ "/outputs/{outputId}/edid", -+ ] -+ GenericTest.__init__(self, apis, omit_paths) - self.compat_url = self.apis[COMPAT_API_KEY]["url"] -+ self.base_url = self.apis[COMPAT_API_KEY]["base_url"] -+ self.connected_outputs = [] -+ self.edid_connected_outputs = [] -+ self.not_edid_connected_outputs = [] -+ self.outputs = [] -+ self.active_connected_outputs = [] -+ self.receivers = "" -+ self.receivers_outputs = "" -+ self.caps = "" -+ -+ # GENERAL TESTS -+ def test_00_01(self, test): -+ """Verify that IS-11 is exposed in the Node API as \ -+ urn:x-nmos:control:stream-compat/v1.0 at url /x-nmos/streamcompatibility/v1.0/ -+ """ -+ valid_res, response = TestHelper.do_request( -+ "GET", self.base_url + "/x-nmos/node/v1.3/devices/" -+ ) -+ if valid_res: -+ response_json = response.json() -+ controls = response_json[0][CONTROLS] -+ control_href = "" -+ for control in controls: -+ if control["type"] == "urn:x-nmos:control:stream-compat/v1.0": -+ control_href = control["href"] -+ break -+ if len(control) == 0: -+ return test.WARNING("IS-11 API is not available") -+ if not control_href.endswith(self.compat_url): -+ return test.FAIL("IS-11 URL is invalid") -+ return test.PASS() -+ return test.FAIL(response) -+ -+ def test_00_02(self, test): -+ "Put all senders into inactive state" -+ senders_url = self.base_url + "/x-nmos/connection/v1.1/single/senders/" -+ _, response = TestHelper.do_request("GET", senders_url) -+ if response.status_code != 200: -+ return test.FAIL(response.json()) -+ senders = response.json() -+ if len(senders) > 0: -+ for sender in senders: -+ url = senders_url + sender + "staged/" -+ deactivate_json = { -+ "master_enable": False, -+ "activation": {"mode": "activate_immediate"}, -+ } -+ -+ _, response = TestHelper.do_request("PATCH", url, json=deactivate_json) -+ if ( -+ response.status_code != 200 -+ or response.json()["master_enable"] -+ or response.json()["activation"]["mode"] != "activate_immediate" -+ ): -+ return test.FAIL(response.json()) -+ return test.PASS() -+ return test.UNCLEAR("Could not find any IS-04 senders to test") -+ -+ def test_00_03(self, test): -+ "Put all the receivers into inactive state" -+ receivers_url = self.base_url + "/x-nmos/connection/v1.1/single/receivers/" -+ _, response = TestHelper.do_request("GET", receivers_url) -+ if response.status_code != 200: -+ return test.FAIL(response.json()) -+ receivers = response.json() -+ if len(receivers) > 0: -+ for receiver in receivers: -+ url = receivers_url + receiver + "staged/" -+ deactivate_json = { -+ "master_enable": False, -+ "activation": {"mode": "activate_immediate"}, -+ } -+ _, response = TestHelper.do_request("PATCH", url, json=deactivate_json) -+ if ( -+ response.status_code != 200 -+ or response.json()["master_enable"] -+ or response.json()["activation"]["mode"] != "activate_immediate" -+ ): -+ return test.FAIL(response.json()) -+ -+ return test.PASS() -+ -+ return test.UNCLEAR("Could not find any IS-04 receivers to test") -+ -+ # OUTPUTS TESTS -+ def test_03_01(self, test): -+ """ -+ Verify that the device supports the concept of Output. -+ """ -+ _, response = TestHelper.do_request("GET", self.compat_url + "outputs/") -+ -+ if response.status_code == 200: -+ if len(response.json()) != 0: -+ self.outputs.append(response.json()[0]) -+ if len(self.outputs) == 0: -+ return test.UNCLEAR("No outputs") -+ return test.PASS() -+ return test.FAIL(response.json()) -+ -+ def test_03_02(self, test): -+ """ -+ Verify that some of the outputs of the device are connected. -+ """ -+ if len(self.outputs) == 0: -+ return test.UNCLEAR("No IS11 receivers outputs") -+ for output in self.outputs: -+ _, response = TestHelper.do_request( -+ "GET", self.compat_url + "outputs/" + output + "properties/" -+ ) -+ if response.status_code == 200: -+ outputs_properties_json = [] -+ outputs_properties_json.append(response.json()) -+ for output in outputs_properties_json: -+ if output["connected"]: -+ self.connected_outputs.append(output["id"]) -+ else: -+ return test.FAIL(response.json()) -+ if len(self.connected_outputs) == 0: -+ return test.UNCLEAR("None Outputs support edid.") -+ return test.PASS() -+ -+ def test_03_03(self, test): -+ """ -+ Verify that all connected outputs do not have -+ a signal as test 0 put all of the receivers inactive. -+ """ -+ if len(self.connected_outputs) == 0: -+ return test.UNCLEAR("no connected outputs") -+ for output_id in self.connected_outputs: -+ _, response = TestHelper.do_request( -+ "GET", self.compat_url + "outputs/" + output_id + "/properties/" -+ ) -+ if response.status_code == 200: -+ if response.json()["status"]["state"] == "signal_present": -+ self.active_connected_outputs.append(response.json()) -+ else: -+ return test.FAIL(response.json()) -+ if len(self.active_connected_outputs) != 0: -+ return test.UNCLEAR( -+ "Connected output have a signal while all receivers are inactive" -+ ) -+ return test.PASS() -+ -+ def test_03_04(self, test): -+ """ -+ Verify that connected outputs supporting EDID behave according to the RAML file. -+ """ -+ if len(self.connected_outputs) == 0: -+ return test.UNCLEAR("no connected outputs") -+ for output_id in self.connected_outputs: -+ _, response = TestHelper.do_request( -+ "GET", self.compat_url + "outputs/" + output_id + "/properties/" -+ ) -+ if response.status_code == 200: -+ if response.json()["edid_support"]: -+ self.edid_connected_outputs.append(response.json()["id"]) -+ else: -+ return test.FAIL(response.json()) -+ if self.edid_connected_outputs == 0: -+ return test.UNCLEAR("Outputs not supporting edid") -+ return test.PASS() -+ -+ def test_03_04_01(self, test): -+ """ -+ Verify that an output indicating EDID support behaves according to the RAML file. -+ """ -+ if len(self.edid_connected_outputs) == 0: -+ return test.UNCLEAR("no edid connected outputs") -+ for output_id in self.edid_connected_outputs: -+ _, response = TestHelper.do_request( -+ "GET", self.compat_url + "outputs/" + output_id -+ ) -+ if response.status_code != 200: -+ return test.FAIL(response.json()) -+ return test.PASS() -+ -+ def test_03_04_02(self, test): -+ """ -+ Verify that a valid EDID can be retrieved from the device; -+ this EDID represents the default EDID of the device. -+ """ -+ is_valid_response = True -+ if len(self.edid_connected_outputs) == 0: -+ return test.UNCLEAR("no edid connected outputs") -+ for output_id in self.edid_connected_outputs: -+ _, response = TestHelper.do_request( -+ "GET", self.compat_url + "outputs/" + output_id + "/edid/" -+ ) -+ if ( -+ response.status_code != 200 -+ and response.headers["Content-Type"] != "application/octet-stream" -+ ): -+ is_valid_response = False -+ break -+ if is_valid_response: -+ return test.PASS() -+ return test.FAIL(response.json()) -+ -+ def test_03_05(self, test): -+ """ -+ Verify that connected outputs not supporting EDID behave according to the RAML file. -+ """ -+ if len(self.connected_outputs) == 0: -+ return test.UNCLEAR("no connected outputs") -+ for output_id in self.connected_outputs: -+ _, response = TestHelper.do_request( -+ "GET", self.compat_url + "outputs/" + output_id + "/properties/" -+ ) -+ if response.status_code == 200: -+ if not response.json()["edid_support"]: -+ self.not_edid_connected_outputs.append(response.json()["id"]) -+ else: -+ return test.FAIL(response.json()) -+ if len(self.not_edid_connected_outputs) == 0: -+ return test.UNCLEAR("Outputs supporting edid") -+ return test.PASS() -+ -+ def test_03_05_01(self, test): -+ """ -+ Verify that there is no EDID support. -+ """ -+ if len(self.not_edid_connected_outputs) == 0: -+ return test.UNCLEAR("none of not edid connected outputs") -+ for output_id in self.not_edid_connected_outputs: -+ _, response = TestHelper.do_request( -+ "GET", self.compat_url + "outputs/" + output_id + "/edid/" -+ ) -+ if response.status_code != 204: -+ return test.UNCLEAR("status code should be 204") -+ return test.PASS() -+ -+ # RECEIVERS TESTS -+ def test_04_01(self, test): -+ """ -+ Verify that the device supports the concept of IS-11 Receiver. -+ """ -+ _, response = TestHelper.do_request("GET", self.compat_url + "receivers/") -+ -+ if response.status_code != 200: -+ return test.FAIL(response.json()) -+ self.receivers = response.json() -+ return ( -+ test.PASS() -+ if len(self.receivers) != 0 -+ else test.UNCLEAR("No IS_11 receivers") -+ ) -+ -+ def test_04_01_01(self, test): -+ """ -+ Verify that IS-11 Receivers exist on the Node API as Receivers. -+ """ -+ for receiver_id in self.receivers: -+ _, response = TestHelper.do_request( -+ "GET", self.base_url + "/x-nmos/node/v1.3/receivers/" + receiver_id -+ ) -+ if response.status_code != 200: -+ return test.FAIL(response.json()) -+ if response.json()["id"] != receiver_id[:-1]: -+ return test.UNCLEAR( -+ "The IS-11 Receiver doesn't exist on the Node API as receiver" -+ ) -+ return ( -+ test.PASS() -+ if len(self.receivers) != 0 -+ else test.UNCLEAR("No IS_11 receivers") -+ ) -+ -+ def test_04_02(self, test): -+ """ -+ Verify receivers (generic with/without outputs) -+ """ -+ _, response = TestHelper.do_request("GET", self.compat_url + "receivers/") -+ if response.status_code != 200: -+ return test.FAIL(response.json()) -+ self.receivers = response.json() -+ return ( -+ test.PASS() -+ if len(self.receivers) != 0 -+ else test.UNCLEAR("No IS_11 receivers") -+ ) -+ -+ def test_04_02_01(self, test): -+ """ -+ Verify that the status is "unknown" or "non_compliant_stream" -+ as per our pre-conditions of not being master_enabled. -+ """ -+ for receiver_id in self.receivers: -+ _, response = TestHelper.do_request( -+ "GET", self.compat_url + "receivers/" + receiver_id + "status/" -+ ) -+ if response.status_code != 200: -+ return test.FAIL(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.PASS() -+ if len(self.receivers) != 0 -+ else test.UNCLEAR("No IS_11 receivers") -+ ) -+ -+ def test_04_02_02(self, test): -+ """ -+ Verify that the Receiver supports Receiver Capabilities. -+ """ -+ for receiver_id in self.receivers: -+ _, response = TestHelper.do_request( -+ "GET", self.base_url + "/x-nmos/node/v1.3/receivers/" + receiver_id -+ ) -+ if response.status_code != 200: -+ return test.FAIL(response.json()) -+ self.caps = response.json()["caps"] -+ 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.UNCLEAR(" The receiver does not support BCP-004-01") -+ return ( -+ test.PASS() -+ if len(self.receivers) != 0 -+ else test.UNCLEAR("No IS_11 receivers") -+ ) -diff --git a/nmostesting/suites/IS1102Test.py b/nmostesting/suites/IS1102Test.py -index e0919fe..e51a0fb 100644 ---- a/nmostesting/suites/IS1102Test.py -+++ b/nmostesting/suites/IS1102Test.py -@@ -13,16 +13,1402 @@ - # limitations under the License. - - from ..GenericTest import GenericTest -+from .. import TestHelper -+import time -+import re - --NODE_API_KEY = "node" - COMPAT_API_KEY = "streamcompatibility" -+NODE_API_KEY = "node" -+CONTROLS = "controls" -+ -+REF_SUPPORTED_CONSTRAINTS_VIDEO = [ -+ "urn:x-nmos:cap:meta:label", -+ "urn:x-nmos:cap:meta:preference", -+ "urn:x-nmos:cap:meta:enabled", -+ "urn:x-nmos:cap:format:media_type", -+ "urn:x-nmos:cap:format:grain_rate", -+ "urn:x-nmos:cap:format:frame_width", -+ "urn:x-nmos:cap:format:frame_height", -+ "urn:x-nmos:cap:format:interlace_mode", -+ "urn:x-nmos:cap:format:color_sampling", -+ "urn:x-nmos:cap:format:component_depth", -+] -+REF_SUPPORTED_CONSTRAINTS_AUDIO = [ -+ "urn:x-nmos:cap:meta:label", -+ "urn:x-nmos:cap:meta:preference", -+ "urn:x-nmos:cap:meta:enabled", -+ "urn:x-nmos:cap:format:media_type", -+ "urn:x-nmos:cap:format:channel_count", -+ "urn:x-nmos:cap:format:sample_rate", -+ "urn:x-nmos:cap:format:sample_depth", -+] - - - class IS1102Test(GenericTest): - """ -- Runs Node Tests covering both IS-04 and IS-11 -+ Runs Node Tests covering IS-11 - """ - def __init__(self, apis): -- GenericTest.__init__(self, apis,) -+ # Don't auto-test paths responding with an EDID binary as they don't have a JSON Schema -+ omit_paths = [ -+ "/inputs/{inputId}/edid", -+ "/inputs/{inputId}/edid/base", -+ "/inputs/{inputId}/edid/effective", -+ "/outputs/{outputId}/edid" -+ ] -+ GenericTest.__init__(self, apis, omit_paths) - self.node_url = self.apis[NODE_API_KEY]["url"] - self.compat_url = self.apis[COMPAT_API_KEY]["url"] -+ self.base_url = self.apis[COMPAT_API_KEY]["base_url"] -+ self.senders = "" -+ self.senders_2 = "" -+ self.flow_format = {} -+ self.flow_format_audio = [] -+ self.flow_format_video = [] -+ self.flow_width = {} -+ self.flow_height = {} -+ self.flow_grain_rate = {} -+ self.flow_sample_rate = {} -+ self.version = {} -+ self.grain_rate_constraints = {} -+ self.empty_constraints = {} -+ self.sample_rate_constraints = {} -+ self.constraints = {} -+ -+ # SENDERS TESTS -+ """ -+ Runs Node Tests covering IS-11 for Senders -+ """ -+ -+ def compare_complex(self, response_constraints, sample_rate_constraints): -+ -+ response_constraints_enum = response_constraints["constraint_sets"][0][ -+ "urn:x-nmos:cap:format:sample_rate" -+ ]["enum"] -+ sample_rate_constraints_enum = sample_rate_constraints["constraint_sets"][0][ -+ "urn:x-nmos:cap:format:sample_rate" -+ ]["enum"] -+ -+ if len(response_constraints_enum) > 0 and len(sample_rate_constraints_enum) > 0: -+ if ( -+ "numerator" in response_constraints_enum[0] -+ and "numerator" in sample_rate_constraints_enum[0] -+ and "denominator" in response_constraints_enum[0] -+ and "denominator" in sample_rate_constraints_enum[0] -+ ): -+ return ( -+ response_constraints_enum[0]["numerator"] -+ == sample_rate_constraints_enum[0]["numerator"] -+ and response_constraints_enum[0]["denominator"] -+ == sample_rate_constraints_enum[0]["denominator"] -+ ) -+ -+ if ( -+ "numerator" in response_constraints_enum[0] -+ and "numerator" in sample_rate_constraints_enum[0] -+ and "denominator" in response_constraints_enum[0] -+ and "denominator" not in sample_rate_constraints_enum[0] -+ ): -+ return ( -+ response_constraints_enum[0]["numerator"] -+ == sample_rate_constraints_enum[0]["numerator"] -+ and response_constraints_enum[0]["denominator"] == 1 -+ ) -+ -+ if ( -+ "numerator" in response_constraints_enum[0] -+ and "numerator" in sample_rate_constraints_enum[0] -+ and "denominator" not in response_constraints_enum[0] -+ and "denominator" in sample_rate_constraints_enum[0] -+ ): -+ return ( -+ response_constraints_enum[0]["numerator"] -+ == sample_rate_constraints_enum[0]["numerator"] -+ and 1 == sample_rate_constraints_enum[0]["denominator"] -+ ) -+ return False -+ -+ def getSdpColorSampling(self, flow_components): -+ """ -+ getColorSamplingFromComponents supports RGB, -+ YCbCr-4:4:4, YCbCr-4:2:2, YCbCr-4:2:0 and assumes -+ that the bit-depth is compliant without verifying it. -+ """ -+ names = [] -+ widths = [] -+ heights = [] -+ -+ if len(flow_components) != 3: -+ return "invalid array of video components" -+ -+ for i in range(0, 3): -+ if "name" in flow_components[i]: -+ names.append( -+ {"name" + str(i): flow_components[i]["name"], "err" + str(i): None} -+ ) -+ else: -+ names.append({"name" + str(i): None, "err" + str(i): "not defined"}) -+ -+ if ( -+ names[0]["err0"] is None -+ and names[0]["name0"] == "R" -+ and names[1]["err1"] is None -+ and names[1]["name1"] == "G" -+ and names[2]["err2"] is None -+ and names[2]["name2"] == "B" -+ ): -+ for i in range(0, 3): -+ if "width" in flow_components[i]: -+ widths.append( -+ { -+ "width" + str(i): flow_components[i]["width"], -+ "err" + str(i): None, -+ } -+ ) -+ else: -+ widths.append( -+ {"width" + str(i): None, "err" + str(i): "not defined"} -+ ) -+ -+ if ( -+ widths[0]["err0"] is not None -+ or widths[1]["err1"] is not None -+ or widths[2]["err2"] is not None -+ ): -+ return "invalid array of video components" -+ -+ for i in range(0, 3): -+ if "height" in flow_components[i]: -+ heights.append( -+ { -+ "height" + str(i): flow_components[i]["height"], -+ "err" + str(i): None, -+ } -+ ) -+ else: -+ heights.append( -+ {"height" + str(i): None, "err" + str(i): "not defined"} -+ ) -+ -+ if ( -+ heights[0]["err0"] is not None -+ or heights[1]["err1"] is not None -+ or heights[2]["err2"] is not None -+ ): -+ return "invalid array of video components" -+ -+ if ( -+ widths[0]["width0"] == widths[1]["width1"] -+ and widths[0]["width0"] == widths[2]["width2"] -+ and heights[0]["height0"] == heights[1]["height1"] -+ and heights[0]["height0"] == heights[2]["height2"] -+ ): -+ return "RGB" -+ -+ if ( -+ names[0]["err0"] is None -+ and names[0]["name0"] == "Y" -+ and names[1]["err1"] is None -+ and names[1]["name1"] == "Cb" -+ and names[2]["err2"] is None -+ and names[2]["name2"] == "Cr" -+ ): -+ -+ for i in range(0, 3): -+ if "width" in flow_components[i]: -+ widths.append( -+ { -+ "width" + str(i): flow_components[i]["width"], -+ "err" + str(i): None, -+ } -+ ) -+ else: -+ widths.append( -+ {"width" + str(i): None, "err" + str(i): "not defined"} -+ ) -+ -+ if ( -+ widths[0]["err0"] is not None -+ or widths[1]["err1"] is not None -+ or widths[2]["err2"] is not None -+ ): -+ return "invalid array of video components" -+ -+ for i in range(0, 3): -+ if "height" in flow_components[i]: -+ heights.append( -+ { -+ "height" + str(i): flow_components[i]["height"], -+ "err" + str(i): None, -+ } -+ ) -+ else: -+ heights.append( -+ {"height" + str(i): None, "err" + str(i): "not defined"} -+ ) -+ -+ if ( -+ heights[0]["err0"] is not None -+ or heights[1]["err1"] is not None -+ or heights[2]["err2"] is not None -+ ): -+ return "invalid array of video components" -+ -+ if ( -+ widths[0]["width0"] == widths[1]["width1"] -+ and widths[0]["width0"] == widths[2]["width2"] -+ and heights[0]["height0"] == heights[1]["height1"] -+ and heights[0]["height0"] == heights[2]["height2"] -+ ): -+ return "YCbCr-4:4:4" -+ -+ if ( -+ widths[0]["width0"] == 2 * widths[1]["width1"] -+ and widths[0]["width0"] == 2 * widths[2]["width2"] -+ and heights[0]["height0"] == heights[1]["height1"] -+ and heights[0]["height0"] == heights[2]["height2"] -+ ): -+ return "YCbCr-4:2:2" -+ -+ if ( -+ widths[0]["width0"] == 2 * widths[1]["width1"] -+ and widths[0]["width0"] == 2 * widths[2]["width2"] -+ and heights[0]["height0"] == 2 * heights[1]["height1"] -+ and heights[0]["height0"] == 2 * heights[2]["height2"] -+ ): -+ return "YCbCr-4:2:0" -+ -+ return "invalid array of video components" -+ -+ def get_another_grain_rate(self, grain_rate): -+ numerator = grain_rate["numerator"] -+ denominator = grain_rate["denominator"] -+ if (numerator == 30 or numerator == 25) and denominator == 1: -+ return {"numerator": numerator * 2, "denominator": 1} -+ if (numerator == 60 or numerator == 50) and denominator == 1: -+ return {"numerator": numerator / 2, "denominator": 1} -+ if numerator == 24 and denominator == 1: -+ return {"numerator": 30, "denominator": 1} -+ if (numerator == 30000 or numerator == 25000) and denominator == 1001: -+ return {"numerator": numerator * 2, "denominator": 1001} -+ if (numerator == 60000 or numerator == 50000) and denominator == 1001: -+ return {"numerator": numerator / 2, "denominator": 1001} -+ return "grain_rate not valid" -+ -+ def get_another_sample_rate(self, sample_rate): -+ numerator = sample_rate["numerator"] -+ if numerator == 48000: -+ return {"numerator": 44100} -+ if numerator == 44100: -+ return {"numerator": 48000} -+ if numerator == 96000: -+ return {"numerator": 4800} -+ if numerator == 88200: -+ return {"numerator": 44100} -+ return "sample_rate not valid" -+ -+ def test_02_00(self, test): -+ "Reset active constraints of all senders" -+ _, response = TestHelper.do_request("GET", self.compat_url + "senders/") -+ if response.status_code == 200: -+ self.senders = response.json() -+ for sender in self.senders: -+ _, response = TestHelper.do_request( -+ "DELETE", -+ self.compat_url + "senders/" + sender + "constraints/active/", -+ ) -+ if response.status_code != 200: -+ return test.FAIL("senders constraints cannot be deleted") -+ return test.PASS() -+ -+ return test.FAIL(response.json()) -+ -+ def test_02_01(self, test): -+ "Verify that the device supports the concept of IS-11 Sender" -+ _, response = TestHelper.do_request("GET", self.compat_url + "senders/") -+ if response.status_code != 200: -+ return test.FAIL(response.json) -+ self.senders = response.json() -+ if len(self.senders) == 0: -+ return test.UNCLEAR("there is no IS-11 senders") -+ return test.PASS() -+ -+ def test_02_01_01(self, test): -+ "Verify that the device supports the concept of IS-11 Sender" -+ if len(self.senders) != 0: -+ for sender_id in self.senders: -+ _, response = TestHelper.do_request( -+ "GET", self.node_url + "senders/" + sender_id -+ ) -+ if response.status_code != 200: -+ return test.FAIL() -+ sender_node = response.json()["id"] -+ if sender_id[:-1] != sender_node: -+ return test.FAIL("") -+ return test.PASS() -+ return test.UNCLEAR("there is no IS-11 senders") -+ -+ def test_02_02(self, test): -+ "Verify senders (generic with/without inputs)" -+ _, response = TestHelper.do_request("GET", self.compat_url + "senders/") -+ if response.status_code != 200: -+ return test.FAIL(response.json()) -+ self.senders_2 = response.json() -+ return test.PASS() -+ -+ def test_02_02_01(self, test): -+ "Verify that the status is unconstrained as per our pre-conditions" -+ if len(self.senders_2) != 0: -+ for sender_id in self.senders_2: -+ _, response = TestHelper.do_request( -+ "GET", self.compat_url + "senders/" + sender_id + "status/" -+ ) -+ if response.status_code != 200: -+ return test.FAIL(response.json()) -+ state = response.json()["state"] -+ if state in ["awating_essence", "no_essence"]: -+ for i in range(0, 5): -+ _, response = TestHelper.do_request( -+ "GET", self.compat_url + "senders/" + sender_id + "status/" -+ ) -+ state = response.json()["status"]["state"] -+ if state in ["awating_essence", "no_essence"]: -+ time.sleep(3000) -+ else: -+ break -+ if state != "unconstrained": -+ return test.FAIL("inputs are unstable.") -+ return test.PASS() -+ return test.UNCLEAR("there is no IS-11 senders") -+ -+ def test_02_02_03(self, test): -+ """ -+ Verify that the sender is available in the node API, -+ has an associated flow and is inactive -+ """ -+ if len(self.senders_2) != 0: -+ for sender_id in self.senders_2: -+ _, response = TestHelper.do_request( -+ "GET", self.node_url + "senders/" + sender_id -+ ) -+ if response.status_code != 200: -+ return test.FAIL(response.json) -+ sender_node = response.json()["id"] -+ if sender_id[:-1] != sender_node: -+ return test.FAIL("") -+ sender_flow_id = response.json()["flow_id"] -+ if sender_flow_id is None: -+ return test.FAIL("the sender must have a flow") -+ sender_subscription_active = response.json()["subscription"]["active"] -+ if sender_subscription_active: -+ return test.FAIL("the sender must be inactive") -+ _, response = TestHelper.do_request( -+ "GET", self.node_url + "flows/" + sender_flow_id -+ ) -+ if response.status_code != 200: -+ return test.FAIL(response.json()) -+ flow_format = response.json()["format"] -+ self.flow_format[sender_id] = flow_format -+ if flow_format == "urn:x-nmos:format:video": -+ self.flow_format_video.append(sender_id) -+ flow_frame_width = response.json()["frame_width"] -+ self.flow_width[sender_id] = flow_frame_width -+ flow_frame_height = response.json()["frame_height"] -+ self.flow_height[sender_id] = flow_frame_height -+ flow_grain_rate = response.json()["grain_rate"] -+ self.flow_grain_rate[sender_id] = flow_grain_rate -+ if flow_format == "urn:x-nmos:format:audio": -+ self.flow_format_audio.append(sender_id) -+ flow_sample_rate = response.json()["sample_rate"] -+ self.flow_sample_rate[sender_id] = flow_sample_rate -+ if ( -+ flow_format != "urn:x-nmos:format:video" -+ and flow_format != "urn:x-nmos:format:audio" -+ ): -+ print("only audio and video senders are tested at this time.") -+ return test.PASS() -+ return test.UNCLEAR("there is no IS-11 senders") -+ -+ def test_02_02_03_01(self, test): -+ "Verify that the video sender supports the minimum set of video constraints" -+ -+ pattern = "^urn:x-nmos:cap:" -+ -+ if len(self.flow_format_video) == 0: -+ return test.UNCLEAR("There is no video format") -+ -+ for sender_id in self.flow_format_video: -+ _, response = TestHelper.do_request( -+ "GET", -+ self.compat_url + "senders/" + sender_id + "constraints/supported/ ", -+ ) -+ if response.status_code != 200: -+ return test.FAIL(response.json()) -+ supportedConstraints = response.json()["parameter_constraints"] -+ for item in supportedConstraints: -+ if not re.search(pattern, item): -+ return test.FAIL("only x-nmos:cap constraints are allowed") -+ for item in REF_SUPPORTED_CONSTRAINTS_VIDEO: -+ if item not in supportedConstraints: -+ return test.FAIL(item + " is not in supportedConstraints ") -+ return test.PASS() -+ -+ def test_02_02_03_02(self, test): -+ "Verify that the video sender supports the minimum set of video constraints" -+ -+ pattern = "^urn:x-nmos:cap:" -+ -+ if len(self.flow_format_audio) == 0: -+ return test.UNCLEAR("There is no video format") -+ -+ for sender_id in self.flow_format_audio: -+ _, response = TestHelper.do_request( -+ "GET", -+ self.compat_url + "senders/" + sender_id + "constraints/supported/ ", -+ ) -+ if response.status_code != 200: -+ return test.FAIL(response.json()) -+ supportedConstraints = response.json()["parameter_constraints"] -+ for item in supportedConstraints: -+ if not re.search(pattern, item): -+ return test.FAIL("only x-nmos:cap constraints are allowed") -+ for item in REF_SUPPORTED_CONSTRAINTS_AUDIO: -+ if item not in supportedConstraints: -+ return test.FAIL(item + "is not in supportedConstraints") -+ return test.PASS() -+ -+ def test_02_02_04_01(self, test): -+ """ -+ Verify that changing the constraints of an -+ IS-11 sender(video) changes the version of -+ the associated IS-04 sender. -+ """ -+ if len(self.flow_format_video) == 0: -+ return test.UNCLEAR("There is no video format") -+ -+ for sender_id in self.flow_format_video: -+ _, response = TestHelper.do_request( -+ "GET", self.node_url + "senders/" + sender_id -+ ) -+ if response.status_code != 200: -+ return test.FAIL(response.json()) -+ version = response.json()["version"] -+ self.version[sender_id] = version -+ self.grain_rate_constraints[sender_id] = { -+ "constraint_sets": [ -+ { -+ "urn:x-nmos:cap:format:grain_rate": { -+ "enum": [self.flow_grain_rate[sender_id]] -+ } -+ } -+ ] -+ } -+ self.empty_constraints[sender_id] = {"constraint_sets": []} -+ _, response = TestHelper.do_request( -+ "PUT", -+ self.compat_url + "senders/" + sender_id + "constraints/active/", -+ json=self.grain_rate_constraints[sender_id], -+ ) -+ if response.status_code != 200: -+ return test.FAIL(response.json()) -+ _, response = TestHelper.do_request( -+ "GET", self.node_url + "senders/" + sender_id -+ ) -+ if response.status_code != 200: -+ return test.FAIL(response.json()) -+ version = response.json()["version"] -+ if version == self.version[sender_id]: -+ return test.FAIL() -+ self.version[sender_id] = version -+ _, response = TestHelper.do_request( -+ "GET", self.compat_url + "senders/" + sender_id + "constraints/active/ " -+ ) -+ if response.status_code != 200: -+ return test.FAIL(response.json()) -+ constraints = response.json() -+ if constraints != self.grain_rate_constraints[sender_id]: -+ return test.FAIL() -+ _, response = TestHelper.do_request( -+ "DELETE", -+ self.compat_url + "senders/" + sender_id + "constraints/active/", -+ ) -+ if response.status_code != 200: -+ return test.FAIL(response.json()) -+ _, response = TestHelper.do_request( -+ "GET", self.node_url + "senders/" + sender_id -+ ) -+ if response.status_code != 200: -+ return test.FAIL(response.json()) -+ version = response.json()["version"] -+ if version == self.version[sender_id]: -+ return test.FAIL() -+ self.version[sender_id] = version -+ _, response = TestHelper.do_request( -+ "GET", self.compat_url + "senders/" + sender_id + "constraints/active/" -+ ) -+ if response.status_code != 200: -+ return test.FAIL(response.json()) -+ constraints = response.json() -+ if constraints != self.empty_constraints[sender_id]: -+ return test.FAIL("Constraints doesn't match") -+ return test.PASS() -+ -+ def test_02_02_04_02(self, test): -+ """ -+ Verify that changing the constraints of an IS-11 -+ sender(audio) changes the version of the associated IS-04 sender. -+ """ -+ if len(self.flow_format_audio) == 0: -+ return test.UNCLEAR("There is no audio format") -+ for sender_id in self.flow_format_audio: -+ _, response = TestHelper.do_request( -+ "GET", self.node_url + "senders/" + sender_id -+ ) -+ if response.status_code != 200: -+ return test.FAIL(response.json()) -+ version = response.json()["version"] -+ self.version[sender_id] = version -+ self.sample_rate_constraints[sender_id] = { -+ "constraint_sets": [ -+ { -+ "urn:x-nmos:cap:format:sample_rate": { -+ "enum": [self.flow_sample_rate[sender_id]] -+ } -+ } -+ ] -+ } -+ self.empty_constraints[sender_id] = {"constraint_sets": []} -+ _, response = TestHelper.do_request( -+ "PUT", -+ self.compat_url + "senders/" + sender_id + "constraints/active/", -+ json=self.sample_rate_constraints[sender_id], -+ ) -+ if response.status_code != 200: -+ return test.FAIL(response.json()) -+ _, response = TestHelper.do_request( -+ "GET", self.node_url + "senders/" + sender_id -+ ) -+ if response.status_code != 200: -+ return test.FAIL(response.json()) -+ version = response.json()["version"] -+ if version == self.version[sender_id]: -+ return test.FAIL() -+ self.version[sender_id] = version -+ -+ _, response = TestHelper.do_request( -+ "GET", self.compat_url + "senders/" + sender_id + "constraints/active/" -+ ) -+ if response.status_code != 200: -+ return test.FAIL(response.json()) -+ constraints = response.json() -+ -+ if not self.compare_complex( -+ constraints, self.sample_rate_constraints[sender_id] -+ ): -+ return test.FAIL( -+ "constraints and SampleRateConstraints[" -+ + sender_id -+ + "] are different " -+ ) -+ -+ _, response = TestHelper.do_request( -+ "DELETE", -+ self.compat_url + "senders/" + sender_id + "constraints/active/", -+ ) -+ if response.status_code != 200: -+ return test.FAIL(response.json()) -+ -+ _, response = TestHelper.do_request( -+ "GET", self.node_url + "senders/" + sender_id -+ ) -+ if response.status_code != 200: -+ return test.FAIL(response.json()) -+ version = response.json()["version"] -+ if version == self.version[sender_id]: -+ return test.FAIL() -+ self.version[sender_id] = version -+ -+ _, response = TestHelper.do_request( -+ "GET", self.compat_url + "senders/" + sender_id + "constraints/active/ " -+ ) -+ if response.status_code != 200: -+ return test.FAIL(response.json()) -+ constraints = response.json() -+ if constraints != self.empty_constraints[sender_id]: -+ return test.FAIL("Constraints doesn't match") -+ return test.PASS() -+ -+ def test_02_02_05_01(self, test): -+ """Verify that setting NOP constraints for frame_width, -+ frame_height and grain_rate does not change the flow of -+ a sender (video) and that the state goes from \"unconstrained\" -+ to \"constrained\" -+ """ -+ if len(self.flow_format_video) == 0: -+ return test.UNCLEAR("There is no audio format ") -+ -+ for sender_id in self.flow_format_video: -+ _, response = TestHelper.do_request( -+ "GET", self.compat_url + "senders/" + sender_id + "status/" -+ ) -+ if response.status_code != 200: -+ return test.FAIL(response.json()) -+ state = response.json()["state"] -+ if state in ["awating_essence", "no_essence"]: -+ for i in range(0, 5): -+ _, response = TestHelper.do_request( -+ "GET", self.compat_url + "senders/" + sender_id + "status/" -+ ) -+ state = response.json()["status"]["state"] -+ if state in ["awating_essence", "no_essence"]: -+ time.sleep(3000) -+ else: -+ break -+ if state != "unconstrained": -+ return test.FAIL("inputs are unstable.") -+ -+ self.constraints[sender_id] = { -+ "constraint_sets": [ -+ { -+ "urn:x-nmos:cap:format:grain_rate": { -+ "enum": [self.flow_grain_rate[sender_id]] -+ } -+ }, -+ { -+ "urn:x-nmos:cap:format:frame_width": { -+ "enum": [self.flow_width[sender_id]] -+ } -+ }, -+ { -+ "urn:x-nmos:cap:format:frame_height": { -+ "enum": [self.flow_height[sender_id]] -+ } -+ }, -+ ] -+ } -+ -+ _, response = TestHelper.do_request( -+ "PUT", -+ self.compat_url + "senders/" + sender_id + "constraints/active/", -+ json=self.constraints[sender_id], -+ ) -+ if response.status_code != 200: -+ return test.FAIL(response.json()) -+ -+ _, response = TestHelper.do_request( -+ "GET", self.compat_url + "senders/" + sender_id + "status/" -+ ) -+ if response.status_code != 200: -+ return test.FAIL(response.json()) -+ state = response.json()["state"] -+ -+ if state in ["awating_essence", "no_essence"]: -+ for i in range(0, 5): -+ _, response = TestHelper.do_request( -+ "GET", self.compat_url + "senders/" + sender_id + "status/" -+ ) -+ state = response.json()["status"]["state"] -+ if state in ["awating_essence", "no_essence"]: -+ time.sleep(3000) -+ else: -+ break -+ if state != "constrained": -+ return test.FAIL("inputs are unstable.") -+ -+ _, response = TestHelper.do_request( -+ "GET", self.node_url + "senders/" + sender_id -+ ) -+ if response.status_code != 200: -+ return test.FAIL(response.json) -+ sender_flow_id = response.json()["flow_id"] -+ if sender_flow_id is None: -+ return test.FAIL("the sender must have a flow") -+ -+ _, response = TestHelper.do_request( -+ "GET", self.node_url + "flows/" + sender_flow_id -+ ) -+ if response.status_code != 200: -+ return test.FAIL(response.json()) -+ -+ 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"] -+ ): -+ return test.FAIL("different argument") -+ -+ _, response = TestHelper.do_request( -+ "DELETE", -+ self.compat_url + "senders/" + sender_id + "constraints/active/", -+ ) -+ if response.status_code != 200: -+ return test.FAIL(response.json()) -+ -+ return test.PASS() -+ -+ def test_02_02_05_02(self, test): -+ """Verify that setting NOP constraints for sample_rate does not change the flow of a sender (audio) and \ -+ that the state goes from \"unconstrained\" to \"constrained\"""" -+ -+ if len(self.flow_format_audio) == 0: -+ return test.UNCLEAR("There is no audio format ") -+ -+ for sender_id in self.flow_format_audio: -+ _, response = TestHelper.do_request( -+ "GET", self.compat_url + "senders/" + sender_id + "status/" -+ ) -+ if response.status_code != 200: -+ return test.FAIL(response.json()) -+ state = response.json()["state"] -+ -+ if state in ["awating_essence", "no_essence"]: -+ for i in range(0, 5): -+ _, response = TestHelper.do_request( -+ "GET", self.compat_url + "senders/" + sender_id + "status/" -+ ) -+ state = response.json()["status"]["state"] -+ if state in ["awating_essence", "no_essence"]: -+ time.sleep(3000) -+ else: -+ break -+ if state != "unconstrained": -+ return test.FAIL("inputs are unstable.") -+ -+ self.constraints[sender_id] = { -+ "constraint_sets": [ -+ { -+ "urn:x-nmos:cap:format:sample_rate": { -+ "enum": [self.flow_sample_rate[sender_id]] -+ } -+ } -+ ] -+ } -+ _, response = TestHelper.do_request( -+ "PUT", -+ self.compat_url + "senders/" + sender_id + "constraints/active/", -+ json=self.constraints[sender_id], -+ ) -+ if response.status_code != 200: -+ return test.FAIL(response.json()) -+ -+ _, response = TestHelper.do_request( -+ "GET", self.compat_url + "senders/" + sender_id + "status/" -+ ) -+ if response.status_code != 200: -+ return test.FAIL(response.json()) -+ state = response.json()["state"] -+ -+ if state in ["awating_essence", "no_essence"]: -+ for i in range(0, 5): -+ _, response = TestHelper.do_request( -+ "GET", self.compat_url + "senders/" + sender_id + "status/" -+ ) -+ state = response.json()["status"]["state"] -+ if state in ["awating_essence", "no_essence"]: -+ time.sleep(3000) -+ else: -+ break -+ if state != "constrained": -+ return test.FAIL("inputs are unstable.") -+ -+ _, response = TestHelper.do_request( -+ "GET", self.node_url + "senders/" + sender_id -+ ) -+ if response.status_code != 200: -+ return test.FAIL(response.json) -+ sender_flow_id = response.json()["flow_id"] -+ if sender_flow_id is None: -+ return test.FAIL("the sender must have a flow") -+ -+ _, response = TestHelper.do_request( -+ "GET", self.node_url + "flows/" + sender_flow_id -+ ) -+ if response.status_code != 200: -+ return test.FAIL(response.json()) -+ flow_sample_rate = response.json()["sample_rate"] -+ if self.flow_sample_rate[sender_id] != flow_sample_rate: -+ return test.FAIL("Different sample rate") -+ -+ _, response = TestHelper.do_request( -+ "DELETE", -+ self.compat_url + "senders/" + sender_id + "constraints/active/", -+ ) -+ if response.status_code != 200: -+ return test.FAIL(response.json()) -+ return test.PASS() -+ -+ def test_02_02_06_01(self, test): -+ """Verify that setting NOP constraints for supported constraints does not change the flow of a sender (video) \ -+ and that the state goes from \"unconstrained\" to \"constrained\"""" -+ if len(self.flow_format_video) == 0: -+ return test.UNCLEAR("There is no audio format ") -+ -+ for sender_id in self.flow_format_video: -+ _, response = TestHelper.do_request( -+ "GET", self.compat_url + "senders/" + sender_id + "status/" -+ ) -+ if response.status_code != 200: -+ return test.FAIL(response.json()) -+ state = response.json()["state"] -+ if state in ["awating_essence", "no_essence"]: -+ for i in range(0, 5): -+ _, response = TestHelper.do_request( -+ "GET", self.compat_url + "senders/" + sender_id + "status/" -+ ) -+ state = response.json()["status"]["state"] -+ if state in ["awating_essence", "no_essence"]: -+ time.sleep(3000) -+ else: -+ break -+ if state != "unconstrained": -+ return test.FAIL("inputs are unstable.") -+ -+ _, response = TestHelper.do_request( -+ "GET", self.node_url + "senders/" + sender_id -+ ) -+ if response.status_code != 200: -+ return test.FAIL(response.json) -+ sender = response.json() -+ -+ _, response = TestHelper.do_request( -+ "GET", self.node_url + "flows/" + sender["flow_id"] -+ ) -+ if response.status_code != 200: -+ return test.FAIL(response.json()) -+ flow = response.json() -+ color_sampling = self.getSdpColorSampling(flow["components"]) -+ if color_sampling == "invalid array of video components": -+ return test.FAIL("invalid array of video components") -+ constraint_set = {} -+ -+ for item in REF_SUPPORTED_CONSTRAINTS_VIDEO: -+ -+ if item == "urn:x-nmos:cap:meta:label": -+ constraint_set["urn:x-nmos:cap:meta:label"] = "video constraint" -+ if item == "urn:x-nmos:cap:meta:preference": -+ constraint_set["urn:x-nmos:cap:meta:preference"] = 0 -+ if item == "urn:x-nmos:cap:meta:enabled": -+ 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"]] -+ } -+ if item == "urn:x-nmos:cap:format:grain_rate": -+ constraint_set["urn:x-nmos:cap:format:grain_rate"] = { -+ "enum": [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"]] -+ } -+ if item == "urn:x-nmos:cap:format:frame_height": -+ constraint_set["urn:x-nmos:cap:format:frame_height"] = { -+ "enum": [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"]] -+ } -+ if item == "urn:x-nmos:cap:format:color_sampling": -+ constraint_set["urn:x-nmos:cap:format:color_sampling"] = { -+ "enum": [color_sampling] -+ } -+ if item == "urn:x-nmos:cap:format:component_depth": -+ constraint_set["urn:x-nmos:cap:format:component_depth"] = { -+ "enum": [flow["components"][0]["bit_depth"]] -+ } -+ self.constraints[sender_id] = {"constraint_sets": [constraint_set]} -+ -+ _, response = TestHelper.do_request( -+ "PUT", -+ self.compat_url + "senders/" + sender_id + "constraints/active/", -+ json=self.constraints[sender_id], -+ ) -+ if response.status_code != 200: -+ return test.FAIL(response.json()) -+ -+ _, response = TestHelper.do_request( -+ "GET", self.node_url + "flows/" + sender["flow_id"] -+ ) -+ if response.status_code != 200: -+ return test.FAIL(response.json()) -+ new_flow = response.json() -+ -+ new_color_sampling = self.getSdpColorSampling(new_flow["components"]) -+ if new_color_sampling == "invalid array of video components": -+ return test.FAIL("invalid array of video components") -+ -+ for item in REF_SUPPORTED_CONSTRAINTS_VIDEO: -+ -+ if item == "urn:x-nmos:cap:format:media_type": -+ if 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"]: -+ return test.FAIL("different grain_rate") -+ if item == "urn:x-nmos:cap:format:frame_width": -+ if 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"]: -+ return test.FAIL("different frame_height") -+ if item == "urn:x-nmos:cap:format:interlace_mode": -+ if 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"] -+ != new_flow["components"][0]["bit_depth"] -+ ): -+ return test.FAIL("different component_depth") -+ -+ _, response = TestHelper.do_request( -+ "DELETE", -+ self.compat_url + "senders/" + sender_id + "constraints/active/", -+ ) -+ if response.status_code != 200: -+ return test.FAIL(response.json()) -+ return test.PASS() -+ -+ def test_02_02_06_02(self, test): -+ """Verify that setting NOP constraints for supported -+ constraints does not change the flow of a sender (audio) -+ and that the state goes from \"unconstrained\" to \"constrained\" -+ """ -+ if len(self.flow_format_audio) == 0: -+ return test.UNCLEAR("There is no audio format") -+ for sender_id in self.flow_format_audio: -+ _, response = TestHelper.do_request( -+ "GET", self.compat_url + "senders/" + sender_id + "status/" -+ ) -+ if response.status_code != 200: -+ test.FAIL(response.json()) -+ state = response.json()["state"] -+ if state in ["awating_essence", "no_essence"]: -+ for i in range(0, 5): -+ _, response = TestHelper.do_request( -+ "GET", self.compat_url + "senders/" + sender_id + "status/" -+ ) -+ state = response.json()["status"]["state"] -+ if state in ["awating_essence", "no_essence"]: -+ time.sleep(3000) -+ else: -+ break -+ if state != "unconstrained": -+ return test.FAIL("inputs are unstable.") -+ _, response = TestHelper.do_request( -+ "GET", self.node_url + "senders/" + sender_id -+ ) -+ if response.status_code != 200: -+ return test.FAIL(response.json) -+ sender = response.json() -+ -+ _, response = TestHelper.do_request( -+ "GET", self.node_url + "flows/" + sender["flow_id"] -+ ) -+ if response.status_code != 200: -+ return test.FAIL(response.json()) -+ flow = response.json() -+ constraint_set = {} -+ -+ _, response = TestHelper.do_request( -+ "GET", self.node_url + "sources/" + flow["source_id"] -+ ) -+ if response.status_code != 200: -+ return test.FAIL(response.json()) -+ source = response.json() -+ -+ for item in REF_SUPPORTED_CONSTRAINTS_AUDIO: -+ -+ if item == "urn:x-nmos:cap:meta:label": -+ constraint_set["urn:x-nmos:cap:meta:label"] = "video constraint" -+ if item == "urn:x-nmos:cap:meta:preference": -+ constraint_set["urn:x-nmos:cap:meta:preference"] = 0 -+ if item == "urn:x-nmos:cap:meta:enabled": -+ 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"]] -+ } -+ if item == "urn:x-nmos:cap:format:sample_rate": -+ constraint_set["urn:x-nmos:cap:format:sample_rate"] = { -+ "enum": [flow["sample_rate"]] -+ } -+ if item == "urn:x-nmos:cap:format:channel_count": -+ constraint_set["urn:x-nmos:cap:format:channel_count"] = { -+ "enum": [len(source["channels"])] -+ } -+ if item == "urn:x-nmos:cap:format:sample_depth": -+ constraint_set["urn:x-nmos:cap:format:sample_depth"] = { -+ "enum": [flow["bit_depth"]] -+ } -+ -+ self.constraints[sender_id] = {"constraint_sets": [constraint_set]} -+ _, response = TestHelper.do_request( -+ "PUT", -+ self.compat_url + "senders/" + sender_id + "constraints/active/", -+ json=self.constraints[sender_id], -+ ) -+ if response.status_code != 200: -+ return test.FAIL(response.json()) -+ -+ _, response = TestHelper.do_request( -+ "GET", self.node_url + "flows/" + sender["flow_id"] -+ ) -+ if response.status_code != 200: -+ return test.FAIL(response.json()) -+ new_flow = response.json() -+ -+ _, response = TestHelper.do_request( -+ "GET", self.node_url + "sources/" + flow["source_id"] -+ ) -+ if response.status_code != 200: -+ return test.FAIL(response.json()) -+ new_source = response.json() -+ -+ for item in REF_SUPPORTED_CONSTRAINTS_AUDIO: -+ -+ if item == "urn:x-nmos:cap:format:media_type": -+ if 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"]: -+ 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"]: -+ return test.FAIL("different sample_depth") -+ -+ _, response = TestHelper.do_request( -+ "DELETE", -+ self.compat_url + "senders/" + sender_id + "constraints/active/", -+ ) -+ if response.status_code != 200: -+ return test.FAIL(response.json()) -+ return test.PASS() -+ -+ def test_02_02_07_01(self, test): -+ "Verify that the device adhere to the preference of the constraint_set." -+ if len(self.flow_format_video) == 0: -+ return test.UNCLEAR("There is no audio format ") -+ -+ for sender_id in self.flow_format_video: -+ _, response = TestHelper.do_request( -+ "GET", self.compat_url + "senders/" + sender_id + "status/" -+ ) -+ if response.status_code != 200: -+ return test.FAIL(response.json()) -+ state = response.json()["state"] -+ if state in ["awating_essence", "no_essence"]: -+ for i in range(0, 5): -+ _, response = TestHelper.do_request( -+ "GET", self.compat_url + "senders/" + sender_id + "status/" -+ ) -+ state = response.json()["status"]["state"] -+ if state in ["awating_essence", "no_essence"]: -+ time.sleep(3000) -+ else: -+ break -+ if state != "unconstrained": -+ return test.FAIL("inputs are unstable.") -+ -+ _, response = TestHelper.do_request( -+ "GET", self.node_url + "senders/" + sender_id -+ ) -+ if response.status_code != 200: -+ return test.FAIL(response.json) -+ sender = response.json() -+ -+ _, response = TestHelper.do_request( -+ "GET", self.node_url + "flows/" + sender["flow_id"] -+ ) -+ if response.status_code != 200: -+ return test.FAIL(response.json()) -+ flow = response.json() -+ color_sampling = self.getSdpColorSampling(flow["components"]) -+ if color_sampling == "invalid array of video components": -+ return test.FAIL("invalid array of video components") -+ constraint_set0 = {} -+ constraint_set1 = {} -+ -+ for item in REF_SUPPORTED_CONSTRAINTS_VIDEO: -+ -+ if item == "urn:x-nmos:cap:meta:label": -+ constraint_set0["urn:x-nmos:cap:meta:label"] = "video constraint" -+ if item == "urn:x-nmos:cap:meta:preference": -+ constraint_set0["urn:x-nmos:cap:meta:preference"] = 0 -+ if item == "urn:x-nmos:cap:meta:enabled": -+ 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"]] -+ } -+ if item == "urn:x-nmos:cap:format:grain_rate": -+ constraint_set0["urn:x-nmos:cap:format:grain_rate"] = { -+ "enum": [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"]] -+ } -+ if item == "urn:x-nmos:cap:format:frame_height": -+ constraint_set0["urn:x-nmos:cap:format:frame_height"] = { -+ "enum": [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"]] -+ } -+ if item == "urn:x-nmos:cap:format:color_sampling": -+ constraint_set0["urn:x-nmos:cap:format:color_sampling"] = { -+ "enum": [color_sampling] -+ } -+ if item == "urn:x-nmos:cap:format:component_depth": -+ constraint_set0["urn:x-nmos:cap:format:component_depth"] = { -+ "enum": [flow["components"][0]["bit_depth"]] -+ } -+ -+ for item in REF_SUPPORTED_CONSTRAINTS_VIDEO: -+ -+ if item == "urn:x-nmos:cap:meta:label": -+ constraint_set1["urn:x-nmos:cap:meta:label"] = "video constraint" -+ if item == "urn:x-nmos:cap:meta:preference": -+ constraint_set1["urn:x-nmos:cap:meta:preference"] = -100 -+ if item == "urn:x-nmos:cap:meta:enabled": -+ 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"]] -+ } -+ 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"])] -+ } -+ if item == "urn:x-nmos:cap:format:frame_width": -+ constraint_set1["urn:x-nmos:cap:format:frame_width"] = { -+ "enum": [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"]] -+ } -+ if item == "urn:x-nmos:cap:format:interlace_mode": -+ constraint_set1["urn:x-nmos:cap:format:interlace_mode"] = { -+ "enum": [flow["interlace_mode"]] -+ } -+ if item == "urn:x-nmos:cap:format:color_sampling": -+ constraint_set1["urn:x-nmos:cap:format:color_sampling"] = { -+ "enum": [color_sampling] -+ } -+ if item == "urn:x-nmos:cap:format:component_depth": -+ constraint_set1["urn:x-nmos:cap:format:component_depth"] = { -+ "enum": [flow["components"][0]["bit_depth"]] -+ } -+ -+ self.constraints[sender_id] = { -+ "constraint_sets": [constraint_set0, constraint_set0] -+ } -+ _, response = TestHelper.do_request( -+ "PUT", -+ self.compat_url + "senders/" + sender_id + "constraints/active/", -+ json=self.constraints[sender_id], -+ ) -+ if response.status_code != 200: -+ return test.FAIL(response.json()) -+ -+ _, response = TestHelper.do_request( -+ "GET", self.node_url + "flows/" + sender["flow_id"] -+ ) -+ if response.status_code != 200: -+ return test.FAIL(response.json()) -+ new_flow = response.json() -+ -+ new_color_sampling = self.getSdpColorSampling(new_flow["components"]) -+ if new_color_sampling == "invalid array of video components": -+ return test.FAIL("invalid array of video components") -+ -+ for item in REF_SUPPORTED_CONSTRAINTS_VIDEO: -+ -+ if item == "urn:x-nmos:cap:format:media_type": -+ if 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"]: -+ return test.FAIL("different grain_rate") -+ if item == "urn:x-nmos:cap:format:frame_width": -+ if 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"]: -+ return test.FAIL("different frame_height") -+ if item == "urn:x-nmos:cap:format:interlace_mode": -+ if 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"] -+ != new_flow["components"][0]["bit_depth"] -+ ): -+ return test.FAIL("different component_depth") -+ -+ _, response = TestHelper.do_request( -+ "DELETE", -+ self.compat_url + "senders/" + sender_id + "constraints/active/", -+ ) -+ if response.status_code != 200: -+ return test.FAIL(response.json()) -+ return test.PASS() -+ -+ def test_02_02_07_02(self, test): -+ "Verify that the device adhere to the preference of the constraint_set." -+ if len(self.flow_format_audio) == 0: -+ return test.UNCLEAR("There is no audio format") -+ -+ for sender_id in self.flow_format_audio: -+ _, response = TestHelper.do_request( -+ "GET", self.compat_url + "senders/" + sender_id + "status/" -+ ) -+ if response.status_code != 200: -+ test.FAIL(response.json()) -+ state = response.json()["state"] -+ if state in ["awating_essence", "no_essence"]: -+ for i in range(0, 5): -+ _, response = TestHelper.do_request( -+ "GET", self.compat_url + "senders/" + sender_id + "status/" -+ ) -+ state = response.json()["status"]["state"] -+ if state in ["awating_essence", "no_essence"]: -+ time.sleep(3000) -+ else: -+ break -+ if state != "unconstrained": -+ return test.FAIL("inputs are unstable.") -+ -+ _, response = TestHelper.do_request( -+ "GET", self.node_url + "senders/" + sender_id -+ ) -+ if response.status_code != 200: -+ return test.FAIL(response.json) -+ sender = response.json() -+ -+ _, response = TestHelper.do_request( -+ "GET", self.node_url + "flows/" + sender["flow_id"] -+ ) -+ if response.status_code != 200: -+ return test.FAIL(response.json()) -+ flow = response.json() -+ _, response = TestHelper.do_request( -+ "GET", self.node_url + "sources/" + flow["source_id"] -+ ) -+ if response.status_code != 200: -+ return test.FAIL(response.json()) -+ source = response.json() -+ -+ constraint_set0 = {} -+ constraint_set1 = {} -+ -+ for item in REF_SUPPORTED_CONSTRAINTS_AUDIO: -+ -+ if item == "urn:x-nmos:cap:meta:label": -+ constraint_set0["urn:x-nmos:cap:meta:label"] = "video constraint" -+ if item == "urn:x-nmos:cap:meta:preference": -+ constraint_set0["urn:x-nmos:cap:meta:preference"] = 0 -+ if item == "urn:x-nmos:cap:meta:enabled": -+ 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"]] -+ } -+ if item == "urn:x-nmos:cap:format:sample_rate": -+ constraint_set0["urn:x-nmos:cap:format:sample_rate"] = { -+ "enum": [flow["sample_rate"]] -+ } -+ if item == "urn:x-nmos:cap:format:channel_count": -+ constraint_set0["urn:x-nmos:cap:format:channel_count"] = { -+ "enum": [len(source["channels"])] -+ } -+ if item == "urn:x-nmos:cap:format:sample_depth": -+ constraint_set0["urn:x-nmos:cap:format:sample_depth"] = { -+ "enum": [flow["bit_depth"]] -+ } -+ -+ for item in REF_SUPPORTED_CONSTRAINTS_AUDIO: -+ -+ if item == "urn:x-nmos:cap:meta:label": -+ constraint_set1["urn:x-nmos:cap:meta:label"] = "video constraint" -+ if item == "urn:x-nmos:cap:meta:preference": -+ constraint_set1["urn:x-nmos:cap:meta:preference"] = -100 -+ if item == "urn:x-nmos:cap:meta:enabled": -+ 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"]] -+ } -+ 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"])] -+ } -+ if item == "urn:x-nmos:cap:format:channel_count": -+ constraint_set1["urn:x-nmos:cap:format:channel_count"] = { -+ "enum": [len(source["channels"])] -+ } -+ if item == "urn:x-nmos:cap:format:sample_depth": -+ constraint_set1["urn:x-nmos:cap:format:sample_depth"] = { -+ "enum": [flow["bit_depth"]] -+ } -+ -+ self.constraints[sender_id] = { -+ "constraint_sets": [constraint_set0, constraint_set1] -+ } -+ -+ _, response = TestHelper.do_request( -+ "PUT", -+ self.compat_url + "senders/" + sender_id + "constraints/active/", -+ json=self.constraints[sender_id], -+ ) -+ if response.status_code != 200: -+ return test.FAIL(response.json()) -+ _, response = TestHelper.do_request( -+ "GET", self.node_url + "flows/" + sender["flow_id"] -+ ) -+ if response.status_code != 200: -+ return test.FAIL(response.json()) -+ new_flow = response.json() -+ -+ _, response = TestHelper.do_request( -+ "GET", self.node_url + "sources/" + flow["source_id"] -+ ) -+ if response.status_code != 200: -+ return test.FAIL(response.json()) -+ new_source = response.json() -+ -+ for item in REF_SUPPORTED_CONSTRAINTS_AUDIO: -+ -+ if item == "urn:x-nmos:cap:format:media_type": -+ if 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"]: -+ 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"]: -+ return test.FAIL("different sample_depth") -+ -+ _, response = TestHelper.do_request( -+ "DELETE", -+ self.compat_url + "senders/" + sender_id + "constraints/active/", -+ ) -+ if response.status_code != 200: -+ return test.FAIL(response.json()) -+ return test.PASS() --- -2.39.1.windows.1 - diff --git a/nmostesting/suites/BCP0050101Test.py b/nmostesting/suites/BCP0050101Test.py deleted file mode 100644 index 505f4a08..00000000 --- a/nmostesting/suites/BCP0050101Test.py +++ /dev/null @@ -1,26 +0,0 @@ -# Copyright (C) 2022 Advanced Media Workflow Association -# -# 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. - -from ..GenericTest import GenericTest - -NODE_API_KEY = "node" - - -class BCP0050101Test(GenericTest): - """ - Runs Node Tests covering BCP-005-01 - """ - def __init__(self, apis): - GenericTest.__init__(self, apis) - self.node_url = self.apis[NODE_API_KEY]["url"] From f451531fe19291ce639f33c2b06a9e6717313a81 Mon Sep 17 00:00:00 2001 From: ggeorgea Date: Mon, 17 Apr 2023 09:41:45 -0400 Subject: [PATCH 05/11] - Merge of IS1102Test.py and IS1101Test.py - Use of generic IS04Utils.py, consequently deletion of new class nmosTestingComplexCompare.py --- nmostesting/IS04Utils.py | 156 ++- nmostesting/NMOSTesting.py | 24 +- nmostesting/TestHelper.py | 7 +- nmostesting/nmosTestingComplexCompare.py | 262 ---- nmostesting/suites/IS0401Test.py | 2 +- nmostesting/suites/IS0501Test.py | 2 +- nmostesting/suites/IS1101Test.py | 1393 ++++++++++++++++++++- nmostesting/suites/IS1102Test.py | 1395 ---------------------- 8 files changed, 1514 insertions(+), 1727 deletions(-) delete mode 100644 nmostesting/nmosTestingComplexCompare.py delete mode 100644 nmostesting/suites/IS1102Test.py diff --git a/nmostesting/IS04Utils.py b/nmostesting/IS04Utils.py index 29c97a37..6d8ff2aa 100644 --- a/nmostesting/IS04Utils.py +++ b/nmostesting/IS04Utils.py @@ -11,11 +11,11 @@ # 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 re +from copy import deepcopy +from fractions import Fraction from . import TestHelper - from .NMOSUtils import NMOSUtils -from copy import deepcopy class IS04Utils(NMOSUtils): @@ -71,9 +71,106 @@ def get_resources(self, resource, url=None): return toReturn @staticmethod + def comparable_parameter_constraint_value(value): + if isinstance(value, dict): + return Fraction(value["numerator"], value.get("denominator", 1)) + return value + + @staticmethod + def comparable_parameter_constraint(param_constraint): + result = deepcopy(param_constraint) + if "minimum" in result: + result["minimum"] = IS04Utils.comparable_parameter_constraint_value( + result["minimum"] + ) + if "maximum" in result: + result["maximum"] = IS04Utils.comparable_parameter_constraint_value( + result["maximum"] + ) + if "enum" in result: + for i, value in enumerate(result["enum"]): + result["enum"][i] = IS04Utils.comparable_parameter_constraint_value( + value + ) + return result + + @staticmethod + def comparable_constraint_set(constraint_set): + result = { + k: IS04Utils.comparable_parameter_constraint(v) + if re.match("^urn:x-nmos:cap:(?!meta:)", k) + else v + for k, v in constraint_set.items() + } + # could also remove urn:x-nmos:cap:meta:label? + if "urn:x-nmos:cap:meta:preference" not in result: + result["urn:x-nmos:cap:meta:preference"] = 0 + if "urn:x-nmos:cap:meta:enabled" not in result: + result["urn:x-nmos:cap:meta:enabled"] = True + return result + + @staticmethod + def comparable_constraint_sets(constraint_sets): + return [IS04Utils.comparable_constraint_set(_) for _ in constraint_sets] + + @staticmethod + def compare_constraint_sets(lhs, rhs): + """Check that two Constraint Sets arrays are closely equivalent""" + return TestHelper.compare_json( + IS04Utils.comparable_constraint_sets(lhs), + IS04Utils.comparable_constraint_sets(rhs), + ) + + @staticmethod + def make_sampling(flow_components): + samplers = { + # Red-Green-Blue-Alpha + "RGBA": {"R": (1, 1), "G": (1, 1), "B": (1, 1), "A": (1, 1)}, + # Red-Green-Blue + "RGB": {"R": (1, 1), "G": (1, 1), "B": (1, 1)}, + # Non-constant luminance YCbCr + "YCbCr-4:4:4": {"Y": (1, 1), "Cb": (1, 1), "Cr": (1, 1)}, + "YCbCr-4:2:2": {"Y": (1, 1), "Cb": (2, 1), "Cr": (2, 1)}, + "YCbCr-4:2:0": {"Y": (1, 1), "Cb": (2, 2), "Cr": (2, 2)}, + "YCbCr-4:1:1": {"Y": (1, 1), "Cb": (4, 1), "Cr": (4, 1)}, + # Constant luminance YCbCr + "CLYCbCr-4:4:4": {"Yc": (1, 1), "Cbc": (1, 1), "Crc": (1, 1)}, + "CLYCbCr-4:2:2": {"Yc": (1, 1), "Cbc": (2, 1), "Crc": (2, 1)}, + "CLYCbCr-4:2:0": {"Yc": (1, 1), "Cbc": (2, 2), "Crc": (2, 2)}, + # Constant intensity ICtCp + "ICtCp-4:4:4": {"I": (1, 1), "Ct": (1, 1), "Cp": (1, 1)}, + "ICtCp-4:2:2": {"I": (1, 1), "Ct": (2, 1), "Cp": (2, 1)}, + "ICtCp-4:2:0": {"I": (1, 1), "Ct": (2, 2), "Cp": (2, 2)}, + # XYZ + "XYZ": {"X": (1, 1), "Y": (1, 1), "Z": (1, 1)}, + # Key signal represented as a single component + "KEY": {"Key": (1, 1)}, + # Sampling signaled by the payload + "UNSPECIFIED": {}, + } + + max_w, max_h = 0, 0 + for component in flow_components: + w, h = component["width"], component["height"] + if w > max_w: + max_w = w + if h > max_h: + max_h = h + + components_sampler = {} + for component in flow_components: + w, h = component["width"], component["height"] + components_sampler[component["name"]] = (max_w / w, max_h / h) + + for sampling, sampler in samplers.items(): + if sampler == components_sampler: + return sampling + def downgrade_resource(resource_type, resource_data, requested_version): """Downgrades given resource data to requested version""" - version_major, version_minor = [int(x) for x in requested_version[1:].split(".")] + version_major, version_minor = [ + int(x) for x in requested_version[1:].split(".") + ] data = deepcopy(resource_data) @@ -94,19 +191,12 @@ def downgrade_resource(resource_type, resource_data, requested_version): if key in endpoint: del endpoint[key] if version_minor <= 1: - keys_to_remove = [ - "interfaces" - ] + keys_to_remove = ["interfaces"] for key in keys_to_remove: if key in data: del data[key] if version_minor == 0: - keys_to_remove = [ - "api", - "clocks", - "description", - "tags" - ] + keys_to_remove = ["api", "clocks", "description", "tags"] for key in keys_to_remove: if key in data: del data[key] @@ -122,11 +212,7 @@ def downgrade_resource(resource_type, resource_data, requested_version): if version_minor <= 1: pass if version_minor == 0: - keys_to_remove = [ - "controls", - "description", - "tags" - ] + keys_to_remove = ["controls", "description", "tags"] for key in keys_to_remove: if key in data: del data[key] @@ -136,11 +222,7 @@ def downgrade_resource(resource_type, resource_data, requested_version): if version_minor <= 2: pass if version_minor <= 1: - keys_to_remove = [ - "caps", - "interface_bindings", - "subscription" - ] + keys_to_remove = ["caps", "interface_bindings", "subscription"] for key in keys_to_remove: if key in data: del data[key] @@ -152,9 +234,7 @@ def downgrade_resource(resource_type, resource_data, requested_version): if version_minor <= 2: pass if version_minor <= 1: - keys_to_remove = [ - "interface_bindings" - ] + keys_to_remove = ["interface_bindings"] for key in keys_to_remove: if key in data: del data[key] @@ -166,20 +246,14 @@ def downgrade_resource(resource_type, resource_data, requested_version): elif resource_type == "source": if version_minor <= 2: - keys_to_remove = [ - "event_type" - ] + keys_to_remove = ["event_type"] for key in keys_to_remove: if key in data: del data[key] if version_minor <= 1: pass if version_minor == 0: - keys_to_remove = [ - "channels", - "clock_name", - "grain_rate" - ] + keys_to_remove = ["channels", "clock_name", "grain_rate"] for key in keys_to_remove: if key in data: del data[key] @@ -187,9 +261,7 @@ def downgrade_resource(resource_type, resource_data, requested_version): elif resource_type == "flow": if version_minor <= 2: - keys_to_remove = [ - "event_type" - ] + keys_to_remove = ["event_type"] for key in keys_to_remove: if key in data: del data[key] @@ -208,7 +280,7 @@ def downgrade_resource(resource_type, resource_data, requested_version): "interlace_mode", "media_type", "sample_rate", - "transfer_characteristic" + "transfer_characteristic", ] for key in keys_to_remove: if key in data: @@ -217,18 +289,14 @@ def downgrade_resource(resource_type, resource_data, requested_version): elif resource_type == "subscription": if version_minor <= 2: - keys_to_remove = [ - "authorization" - ] + keys_to_remove = ["authorization"] for key in keys_to_remove: if key in data: del data[key] if version_minor <= 1: pass if version_minor == 0: - keys_to_remove = [ - "secure" - ] + keys_to_remove = ["secure"] for key in keys_to_remove: if key in data: del data[key] diff --git a/nmostesting/NMOSTesting.py b/nmostesting/NMOSTesting.py index f57fb818..da589f37 100644 --- a/nmostesting/NMOSTesting.py +++ b/nmostesting/NMOSTesting.py @@ -83,7 +83,6 @@ from .suites import IS0902Test # from .suites import IS1001Test from .suites import IS1101Test -from .suites import IS1102Test from .suites import BCP00301Test from .suites import BCP0050101Test from .suites import BCP0060101Test @@ -342,25 +341,14 @@ "specs": [{ "spec_key": "is-11", "api_key": "streamcompatibility" - }, { - "spec_key": "is-04", - "api_key": "node" - }, { - "spec_key": "is-05", - "api_key": "connection" - }], - "class": IS1101Test.IS1101Test - }, - "IS-11-02": { - "name": "IS-11 Interaction with IS-04", - "specs": [{ + }, { "spec_key": "is-04", "api_key": "node" }, { - "spec_key": "is-11", - "api_key": "streamcompatibility" + "spec_key": "is-05", + "api_key": "connection" }], - "class": IS1102Test.IS1102Test + "class": IS1101Test.IS1101Test }, "BCP-003-01": { "name": "BCP-003-01 Secure Communication", @@ -380,6 +368,10 @@ }, "BCP-006-01-01": { "name": "BCP-006-01 NMOS With JPEG XS", + "specs": [{ + "spec_key": "is-04", + "api_key": "node" + }], "extra_specs": [{ "spec_key": "nmos-parameter-registers", "api_key": "flow-register" diff --git a/nmostesting/TestHelper.py b/nmostesting/TestHelper.py index 588d9c3f..db8de1e2 100644 --- a/nmostesting/TestHelper.py +++ b/nmostesting/TestHelper.py @@ -264,17 +264,18 @@ def loader(uri): return schema -def check_content_type(headers, expected_type="application/json"): +def check_content_type(headers, expected_type=["application/json", "application/sdp", "application/octet-stream"]): """Check the Content-Type header of an API request or response""" if "Content-Type" not in headers: return False, "API failed to signal a Content-Type." else: ctype = headers["Content-Type"] ctype_params = ctype.split(";") - if ctype_params[0] != expected_type: + is_expected_type = ctype_params[0] in expected_type + if not is_expected_type: return False, "API signalled a Content-Type of {} rather than {}." \ .format(ctype, expected_type) - elif ctype_params[0] in ["application/json", "application/sdp"]: + elif is_expected_type: if len(ctype_params) == 2 and ctype_params[1].strip().lower() == "charset=utf-8": return True, "API signalled an unnecessary 'charset' in its Content-Type: {}" \ .format(ctype) diff --git a/nmostesting/nmosTestingComplexCompare.py b/nmostesting/nmosTestingComplexCompare.py deleted file mode 100644 index 42b46df3..00000000 --- a/nmostesting/nmosTestingComplexCompare.py +++ /dev/null @@ -1,262 +0,0 @@ -# constraint_set schema -# -# { -# "$schema": "http://json-schema.org/draft-04/schema#", -# "description": "Describes a Constraint Set", -# "title": "Constraint Set", -# "type": "object", -# "minProperties": 1, -# "properties": { -# "urn:x-nmos:cap:meta:label": { -# "description": "Freeform string label for the Constraint Set", -# "type": "string" -# }, -# "urn:x-nmos:cap:meta:preference": { -# "description": "This value expresses the relative 'weight' that the Receiver assigns to -# its preference for the streams satisfied by the associated Constraint Set. -# The weight is an integer in the range -100 through 100, -# where -100 is least preferred and 100 is most preferred. -# When the attribute is omitted, the effective value for the associated Constraint Set is 0.", -# "type": "integer", -# "default": 0, -# "maximum": 100, -# "minimum": -100 -# }, -# "urn:x-nmos:cap:meta:enabled": { -# "description": "This value indicates whether a Constraint Set is available to use immediately (true) -# or whether this is an offline capability which can be activated via -# some unspecified configuration mechanism (false). -# When the attribute is omitted its value is assumed to be true.", -# "type": "boolean", -# "default": true -# } -# }, -# "patternProperties": { -# "^urn:x-nmos:cap:(?!meta:)": { -# "$ref": "param_constraint.json" -# } -# } -# } -# -# We want to compare that two constraint sets are equal based on the properties of teh schema that allow default -# values for properties not defined. For example the "preference" property defined to true or undefined is the -# same such that comparing an object A heving the property set to true and an object B not having the property -# defined will indicate equality because the schema defines a default value. -# -# This function verifies two constraint sets where at most one constraint set is expected and each constraint -# set must have at most one "sample_rate" paremeter constraint. We then compare the two objects based on their -# respective schemas: constraint_set and rational. We expect the constraint to be defined using the "enum" -# keyword with a single array entry. -# -# return true if equal, false otherwise - - -def compare_complex_sample_rate_constraint( - response_constraints, sample_rate_constraints -): - - # NOTE: We already know that both response_constraints, sample_rate_constraints are valid - # and have been each independently been validated against the schemas. We only check equality. - - # Each constraint_sets array must have a single entry - if len(response_constraints) != 1 or len(sample_rate_constraints) != 1: - return False - - # If the sample_rate property is not defined, objects are not equivalent - try: - response_constraints_enum = response_constraints["constraint_sets"][0][ - "urn:x-nmos:cap:format:sample_rate" - ]["enum"] - except Exception: - return False - - # If the sample_rate property is not defined, objects are not equivalent - try: - sample_rate_constraints_enum = sample_rate_constraints["constraint_sets"][0][ - "urn:x-nmos:cap:format:sample_rate" - ]["enum"] - except Exception: - return False - - # There must be a single entry in the enum array - if len(response_constraints_enum) != 1 or len(sample_rate_constraints_enum) != 1: - return False - - try: - response_numerator = response_constraints_enum[0]["numerator"] - response_denominator = 1 - - if "denominator" in response_constraints_enum[0]: - response_denominator = response_constraints_enum[0]["denominator"] - - sample_rate_numerator = sample_rate_constraints_enum[0]["numerator"] - sample_rate_denominator = 1 - - if "denominator" in sample_rate_constraints_enum[0]: - sample_rate_denominator = sample_rate_constraints_enum[0]["denominator"] - - if ( - response_numerator != sample_rate_numerator - or response_denominator != sample_rate_denominator - ): - return False - except Exception: - return False - - # There must be no other patternProperties - for prop in sample_rate_constraints["constraint_sets"][0]: - if ( - prop != "urn:x-nmos:cap:format:sample_rate" - and prop != "urn:x-nmos:cap:meta:enabled" - and prop != "urn:x-nmos:cap:meta:preference" - ): - return False - - for prop in response_constraints["constraint_sets"][0]: - if ( - prop != "urn:x-nmos:cap:format:sample_rate" - and prop != "urn:x-nmos:cap:meta:enabled" - and prop != "urn:x-nmos:cap:meta:preference" - ): - return False - - # Check meta:enabled considering default values - response_enabled = True - if "urn:x-nmos:cap:meta:enabled" in response_constraints["constraint_sets"][0]: - response_enabled = response_constraints["constraint_sets"][0][ - "urn:x-nmos:cap:meta:enabled" - ] - - sample_rate_enabled = True - if "urn:x-nmos:cap:meta:enabled" in sample_rate_constraints["constraint_sets"][0]: - sample_rate_enabled = sample_rate_constraints["constraint_sets"][0][ - "urn:x-nmos:cap:meta:enabled" - ] - - if response_enabled != sample_rate_enabled: - return False - - # Check meta:preference considering default values - response_preference = 0 - if "urn:x-nmos:cap:meta:preference" in response_constraints["constraint_sets"][0]: - response_preference = response_constraints["constraint_sets"][0][ - "urn:x-nmos:cap:meta:preference" - ] - - sample_rate_preference = 0 - if ( - "urn:x-nmos:cap:meta:preference" - in sample_rate_constraints["constraint_sets"][0] - ): - sample_rate_preference = sample_rate_constraints["constraint_sets"][0][ - "urn:x-nmos:cap:meta:preference" - ] - - if response_preference != sample_rate_preference: - return False - - # If we get here it is because the two objects are equal - return True - - -def compare_complex_grain_rate_constraint(response_constraints, grain_rate_constraints): - - # NOTE: We already know that both response_constraints, grain_rate_constraints are valid - # and have been each independently been validated against the schemas. We only check equality. - - # Each constraint_sets array must have a single entry - if len(response_constraints) != 1 or len(grain_rate_constraints) != 1: - return False - - # If the grain_rate property is not defined, objects are not equivalent - try: - response_constraints_enum = response_constraints["constraint_sets"][0][ - "urn:x-nmos:cap:format:grain_rate" - ]["enum"] - except Exception: - return False - - # If the grain_rate property is not defined, objects are not equivalent - try: - grain_rate_constraints_enum = grain_rate_constraints["constraint_sets"][0][ - "urn:x-nmos:cap:format:grain_rate" - ]["enum"] - except Exception: - return False - - # There must be a single entry in the enum array - if len(response_constraints_enum) != 1 or len(grain_rate_constraints_enum) != 1: - return False - - try: - response_numerator = response_constraints_enum[0]["numerator"] - response_denominator = 1 - - if "denominator" in response_constraints_enum[0]: - response_denominator = response_constraints_enum[0]["denominator"] - - grain_rate_numerator = grain_rate_constraints_enum[0]["numerator"] - grain_rate_denominator = 1 - - if "denominator" in grain_rate_constraints_enum[0]: - grain_rate_denominator = grain_rate_constraints_enum[0]["denominator"] - - if ( - response_numerator != grain_rate_numerator - or response_denominator != grain_rate_denominator - ): - return False - except Exception: - return False - - # There must be no other patternProperties - for prop in grain_rate_constraints["constraint_sets"][0]: - if ( - prop != "urn:x-nmos:cap:format:grain_rate" - and prop != "urn:x-nmos:cap:meta:enabled" - and prop != "urn:x-nmos:cap:meta:preference" - ): - return False - - for prop in response_constraints["constraint_sets"][0]: - if ( - prop != "urn:x-nmos:cap:format:grain_rate" - and prop != "urn:x-nmos:cap:meta:enabled" - and prop != "urn:x-nmos:cap:meta:preference" - ): - return False - - # Check meta:enabled considering default values - response_enabled = True - if "urn:x-nmos:cap:meta:enabled" in response_constraints["constraint_sets"][0]: - response_enabled = response_constraints["constraint_sets"][0][ - "urn:x-nmos:cap:meta:enabled" - ] - - grain_rate_enabled = True - if "urn:x-nmos:cap:meta:enabled" in grain_rate_constraints["constraint_sets"][0]: - grain_rate_enabled = grain_rate_constraints["constraint_sets"][0][ - "urn:x-nmos:cap:meta:enabled" - ] - - if response_enabled != grain_rate_enabled: - return False - - # Check meta:preference considering default values - response_preference = 0 - if "urn:x-nmos:cap:meta:preference" in response_constraints["constraint_sets"][0]: - response_preference = response_constraints["constraint_sets"][0][ - "urn:x-nmos:cap:meta:preference" - ] - - grain_rate_preference = 0 - if "urn:x-nmos:cap:meta:preference" in grain_rate_constraints["constraint_sets"][0]: - grain_rate_preference = grain_rate_constraints["constraint_sets"][0][ - "urn:x-nmos:cap:meta:preference" - ] - - if response_preference != grain_rate_preference: - return False - - # If we get here it is because the two objects are equal - return True diff --git a/nmostesting/suites/IS0401Test.py b/nmostesting/suites/IS0401Test.py index 0a0a3fa9..30c27c46 100644 --- a/nmostesting/suites/IS0401Test.py +++ b/nmostesting/suites/IS0401Test.py @@ -1410,7 +1410,7 @@ def test_20_01(self, test): headers = None valid, response = self.do_request("GET", href, headers=headers) if valid and response.status_code == 200: - valid, message = check_content_type(response.headers, "application/sdp") + valid, message = check_content_type(response.headers, ["application/sdp"]) if not content_type_warn and (not valid or message != ""): content_type_warn = message elif valid and response.status_code == 404: diff --git a/nmostesting/suites/IS0501Test.py b/nmostesting/suites/IS0501Test.py index 81f413c9..6348ea63 100644 --- a/nmostesting/suites/IS0501Test.py +++ b/nmostesting/suites/IS0501Test.py @@ -1026,7 +1026,7 @@ def test_42(self, test): url = self.url + "single/senders/{}/transportfile".format(sender) valid, response = self.do_request("GET", url) if valid and response.status_code == 200: - valid, message = check_content_type(response.headers, "application/sdp") + valid, message = check_content_type(response.headers, ["application/sdp"]) if valid and message != "": return test.FAIL(message) elif not valid: diff --git a/nmostesting/suites/IS1101Test.py b/nmostesting/suites/IS1101Test.py index 757f34ff..ab4041ab 100644 --- a/nmostesting/suites/IS1101Test.py +++ b/nmostesting/suites/IS1101Test.py @@ -15,19 +15,51 @@ from ..NMOSUtils import NMOSUtils from ..GenericTest import GenericTest from .. import TestHelper +import time +import re +from ..IS04Utils import IS04Utils COMPAT_API_KEY = "streamcompatibility" CONTROLS = "controls" NODE_API_KEY = "node" CONN_API_KEY = "connection" +REF_SUPPORTED_CONSTRAINTS_VIDEO = [ + "urn:x-nmos:cap:meta:label", + "urn:x-nmos:cap:meta:preference", + "urn:x-nmos:cap:meta:enabled", + "urn:x-nmos:cap:format:media_type", + "urn:x-nmos:cap:format:grain_rate", + "urn:x-nmos:cap:format:frame_width", + "urn:x-nmos:cap:format:frame_height", + "urn:x-nmos:cap:format:interlace_mode", + "urn:x-nmos:cap:format:color_sampling", + "urn:x-nmos:cap:format:component_depth", +] +REF_SUPPORTED_CONSTRAINTS_AUDIO = [ + "urn:x-nmos:cap:meta:label", + "urn:x-nmos:cap:meta:preference", + "urn:x-nmos:cap:meta:enabled", + "urn:x-nmos:cap:format:media_type", + "urn:x-nmos:cap:format:channel_count", + "urn:x-nmos:cap:format:sample_rate", + "urn:x-nmos:cap:format:sample_depth", +] + class IS1101Test(GenericTest): """ Runs Node Tests covering IS-11 """ - def __init__(self, apis): - GenericTest.__init__(self, apis) + def __init__(self, apis, **kwargs): + # Don't auto-test paths responding with an EDID binary as they don't have a JSON Schema + omit_paths = [ + "/inputs/{inputId}/edid", + "/inputs/{inputId}/edid/base", + "/inputs/{inputId}/edid/effective", + "/outputs/{outputId}/edid" + ] + GenericTest.__init__(self, apis, omit_paths, **kwargs) self.compat_url = self.apis[COMPAT_API_KEY]["url"] self.node_url = self.apis[NODE_API_KEY]["url"] self.conn_url = self.apis[CONN_API_KEY]["url"] @@ -39,9 +71,25 @@ def __init__(self, apis): self.receivers = "" self.receivers_outputs = "" self.caps = "" + self.node_url = self.apis[NODE_API_KEY]["url"] + self.compat_url = self.apis[COMPAT_API_KEY]["url"] + self.senders = "" + self.senders_2 = "" + self.flow_format = {} + self.flow_format_audio = [] + self.flow_format_video = [] + self.flow_width = {} + self.flow_height = {} + self.flow_grain_rate = {} + self.flow_sample_rate = {} + self.version = {} + self.grain_rate_constraints = {} + self.empty_constraints = {} + self.sample_rate_constraints = {} + self.constraints = {} # GENERAL TESTS - def test_01(self, test): + def test_00_01(self, test): """Verify that IS-11 is exposed in the Node API as \ urn:x-nmos:control:stream-compat/v1.0 at url /x-nmos/streamcompatibility/v1.0/ """ @@ -62,7 +110,7 @@ def test_01(self, test): return test.PASS() return test.FAIL("IS-11 API is not available") - def test_02(self, test): + def test_00_02(self, test): "Put all senders into inactive state" senders_url = self.conn_url + "single/senders/" _, response = TestHelper.do_request("GET", senders_url) @@ -87,7 +135,7 @@ def test_02(self, test): return test.PASS() return test.UNCLEAR("Could not find any senders to test") - def test_03(self, test): + def test_00_03(self, test): "Put all the receivers into inactive state" receivers_url = self.conn_url + "single/receivers/" _, response = TestHelper.do_request("GET", receivers_url) @@ -113,6 +161,1341 @@ def test_03(self, test): return test.UNCLEAR("Could not find any receivers to test") + # SENDERS TESTS + """ + Runs Node Tests covering IS-11 for Senders + """ + + def get_another_grain_rate(self, grain_rate): + numerator = grain_rate["numerator"] + denominator = grain_rate["denominator"] + if (numerator == 30 or numerator == 25) and denominator == 1: + return {"numerator": numerator * 2, "denominator": 1} + if (numerator == 60 or numerator == 50) and denominator == 1: + return {"numerator": numerator / 2, "denominator": 1} + if numerator == 24 and denominator == 1: + return {"numerator": 30, "denominator": 1} + if (numerator == 30000 or numerator == 25000) and denominator == 1001: + return {"numerator": numerator * 2, "denominator": 1001} + if (numerator == 60000 or numerator == 50000) and denominator == 1001: + return {"numerator": numerator / 2, "denominator": 1001} + return "grain_rate not valid" + + def get_another_sample_rate(self, sample_rate): + numerator = sample_rate["numerator"] + if numerator == 0: + return {"numerator": 48000} + if numerator == 48000: + return {"numerator": 44100} + if numerator == 44100: + return {"numerator": 48000} + if numerator == 96000: + return {"numerator": 4800} + if numerator == 88200: + return {"numerator": 44100} + return "sample_rate not valid" + + def test_02_00(self, test): + "Reset active constraints of all senders" + _, response = TestHelper.do_request("GET", self.compat_url + "senders/") + if response.status_code != 200: + return test.FAIL( + "The request has not succeeded: {}".format(response.json()) + ) + self.senders = response.json() + if len(self.senders) != 0: + for sender in self.senders: + _, response = TestHelper.do_request( + "DELETE", + self.compat_url + "senders/" + sender + "constraints/active/", + ) + if response.status_code != 200: + return test.FAIL("senders constraints cannot be deleted") + return test.PASS() + return test.UNCLEAR("There is no IS-11 senders.") + + def test_02_01(self, test): + "Verify that the device supports the concept of IS-11 Sender" + _, response = TestHelper.do_request("GET", self.compat_url + "senders/") + if response.status_code != 200: + return test.FAIL( + "The request has not succeeded: {}".format(response.json()) + ) + self.senders = response.json() + if len(self.senders) == 0: + return test.UNCLEAR("There is no IS-11 senders.") + return test.PASS() + + def test_02_01_01(self, test): + "Verify that the device supports the concept of IS-11 Sender" + if len(self.senders) != 0: + for sender_id in self.senders: + _, response = TestHelper.do_request( + "GET", self.node_url + "senders/" + sender_id + ) + if response.status_code != 200: + return test.FAIL( + "The request has not succeeded: {}".format(response.json()) + ) + sender_node = response.json()["id"] + if sender_id[:-1] != sender_node: + return test.FAIL("Senders are different") + return test.PASS() + return test.UNCLEAR("There is no IS-11 senders.") + + def test_02_02(self, test): + "Verify senders (generic with/without inputs)" + _, response = TestHelper.do_request("GET", self.compat_url + "senders/") + if response.status_code != 200: + return test.FAIL( + "The request has not succeeded: {}".format(response.json()) + ) + self.senders_2 = response.json() + if len(self.senders_2) == 0: + return test.UNCLEAR("There is no IS-11 senders.") + return test.PASS() + + def test_02_02_01(self, test): + "Verify that the status is unconstrained as per our pre-conditions" + if len(self.senders_2) != 0: + for sender_id in self.senders_2: + _, response = TestHelper.do_request( + "GET", self.compat_url + "senders/" + sender_id + "status/" + ) + if response.status_code != 200: + return test.FAIL( + "The request has not succeeded: {}".format(response.json()) + ) + state = response.json()["state"] + if state in ["awating_essence", "no_essence"]: + for i in range(0, 5): + _, response = TestHelper.do_request( + "GET", self.compat_url + "senders/" + sender_id + "status/" + ) + if response.status_code != 200: + return test.FAIL( + "The request has not succeeded: {}".format( + response.json() + ) + ) + state = response.json()["state"] + if state in ["awating_essence", "no_essence"]: + time.sleep(3) + else: + break + if state != "unconstrained": + return test.FAIL("inputs are unstable.") + return test.PASS() + return test.UNCLEAR("There is no IS-11 senders.") + + def test_02_02_03(self, test): + """ + Verify that the sender is available in the node API, + has an associated flow and is inactive + """ + if len(self.senders_2) != 0: + for sender_id in self.senders_2: + _, response = TestHelper.do_request( + "GET", self.node_url + "senders/" + sender_id + ) + if response.status_code != 200: + return test.FAIL( + "The request has not succeeded: {}".format(response.json()) + ) + sender_node = response.json()["id"] + if sender_id[:-1] != sender_node: + return test.FAIL("Senders are different") + 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)) + sender_subscription_active = response.json()["subscription"]["active"] + if sender_subscription_active: + return test.FAIL( + "the sender {} must be inactive ".format(sender_id) + ) + _, response = TestHelper.do_request( + "GET", self.node_url + "flows/" + sender_flow_id + ) + if response.status_code != 200: + return test.FAIL( + "The request has not succeeded: {}".format(response.json()) + ) + flow_format = response.json()["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"] + 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"] + if ( + flow_format != "urn:x-nmos:format:video" + and flow_format != "urn:x-nmos:format:audio" + ): + print("only audio and video senders are tested at this time.") + return test.PASS() + return test.UNCLEAR("There is no IS-11 senders.") + + def test_02_02_03_01(self, test): + "Verify that the video sender supports the minimum set of video constraints" + + pattern = "^urn:x-nmos:cap:" + + if len(self.flow_format_video) == 0: + return test.UNCLEAR("There is no video format.") + + for sender_id in self.flow_format_video: + _, response = TestHelper.do_request( + "GET", + self.compat_url + "senders/" + sender_id + "constraints/supported/", + ) + if response.status_code != 200: + return test.FAIL( + "The request has not succeeded: {}".format(response.json()) + ) + supportedConstraints = response.json()["parameter_constraints"] + for item in supportedConstraints: + if not re.search(pattern, item): + return test.FAIL("only x-nmos:cap constraints are allowed") + for item in REF_SUPPORTED_CONSTRAINTS_VIDEO: + if item not in supportedConstraints: + return test.FAIL(item + " is not in supportedConstraints ") + return test.PASS() + + def test_02_02_03_02(self, test): + "Verify that the video sender supports the minimum set of video constraints" + + pattern = "^urn:x-nmos:cap:" + + if len(self.flow_format_audio) == 0: + return test.UNCLEAR("There is no audio format.") + + for sender_id in self.flow_format_audio: + _, response = TestHelper.do_request( + "GET", + self.compat_url + "senders/" + sender_id + "constraints/supported/", + ) + if response.status_code != 200: + return test.FAIL( + "The request has not succeeded: {}".format(response.json()) + ) + supportedConstraints = response.json()["parameter_constraints"] + for item in supportedConstraints: + if not re.search(pattern, item): + return test.FAIL("only x-nmos:cap constraints are allowed") + for item in REF_SUPPORTED_CONSTRAINTS_AUDIO: + if item not in supportedConstraints: + return test.FAIL(item + "is not in supportedConstraints") + return test.PASS() + + def test_02_02_04_01(self, test): + """ + Verify that changing the constraints of an + IS-11 sender(video) changes the version of + the associated IS-04 sender. + """ + if len(self.flow_format_video) == 0: + return test.UNCLEAR("There is no video format.") + + for sender_id in self.flow_format_video: + _, response = TestHelper.do_request( + "GET", self.node_url + "senders/" + sender_id + ) + if response.status_code != 200: + return test.FAIL( + "The request has not succeeded: {}".format(response.json()) + ) + version = response.json()["version"] + self.version[sender_id] = version + self.grain_rate_constraints[sender_id] = { + "constraint_sets": [ + { + "urn:x-nmos:cap:format:grain_rate": { + "enum": [self.flow_grain_rate[sender_id]] + } + } + ] + } + self.empty_constraints[sender_id] = {"constraint_sets": []} + _, response = TestHelper.do_request( + "PUT", + self.compat_url + "senders/" + sender_id + "constraints/active/", + json=self.grain_rate_constraints[sender_id], + ) + if response.status_code != 200: + return test.FAIL( + "The request has not succeeded: {}".format(response.json()) + ) + _, response = TestHelper.do_request( + "GET", self.node_url + "senders/" + sender_id + ) + if response.status_code != 200: + return test.FAIL( + "The request has not succeeded: {}".format(response.json()) + ) + version = response.json()["version"] + if version == self.version[sender_id]: + return test.FAIL("Version are different") + self.version[sender_id] = version + _, response = TestHelper.do_request( + "GET", self.compat_url + "senders/" + sender_id + "constraints/active/" + ) + if response.status_code != 200: + return test.FAIL( + "The request has not succeeded: {}".format(response.json()) + ) + constraints = response.json() + if not IS04Utils.compare_constraint_sets( + constraints["constraint_sets"], + self.grain_rate_constraints[sender_id]["constraint_sets"], + ): + return test.FAIL("Contraints are different") + _, response = TestHelper.do_request( + "DELETE", + self.compat_url + "senders/" + sender_id + "constraints/active/", + ) + if response.status_code != 200: + return test.FAIL( + "The request has not succeeded: {}".format(response.json()) + ) + _, response = TestHelper.do_request( + "GET", self.node_url + "senders/" + sender_id + ) + if response.status_code != 200: + return test.FAIL( + "The request has not succeeded: {}".format(response.json()) + ) + version = response.json()["version"] + if version == self.version[sender_id]: + return test.FAIL("Version are different") + self.version[sender_id] = version + _, response = TestHelper.do_request( + "GET", self.compat_url + "senders/" + sender_id + "constraints/active/" + ) + if response.status_code != 200: + return test.FAIL( + "The request has not succeeded: {}".format(response.json()) + ) + constraints = response.json() + if constraints != self.empty_constraints[sender_id]: + return test.FAIL("Contraints are different") + return test.PASS() + + def test_02_02_04_02(self, test): + """ + Verify that changing the constraints of an IS-11 + sender(audio) changes the version of the associated IS-04 sender. + """ + if len(self.flow_format_audio) == 0: + return test.UNCLEAR("There is no audio format.") + for sender_id in self.flow_format_audio: + _, response = TestHelper.do_request( + "GET", self.node_url + "senders/" + sender_id + ) + if response.status_code != 200: + return test.FAIL( + "The request has not succeeded: {}".format(response.json()) + ) + version = response.json()["version"] + self.version[sender_id] = version + self.sample_rate_constraints[sender_id] = { + "constraint_sets": [ + { + "urn:x-nmos:cap:format:sample_rate": { + "enum": [self.flow_sample_rate[sender_id]] + } + } + ] + } + self.empty_constraints[sender_id] = {"constraint_sets": []} + _, response = TestHelper.do_request( + "PUT", + self.compat_url + "senders/" + sender_id + "constraints/active/", + json=self.sample_rate_constraints[sender_id], + ) + if response.status_code != 200: + return test.FAIL( + "The request has not succeeded: {}".format(response.json()) + ) + _, response = TestHelper.do_request( + "GET", self.node_url + "senders/" + sender_id + ) + if response.status_code != 200: + return test.FAIL( + "The request has not succeeded: {}".format(response.json()) + ) + version = response.json()["version"] + if version == self.version[sender_id]: + return test.FAIL("Version are different") + self.version[sender_id] = version + + _, response = TestHelper.do_request( + "GET", self.compat_url + "senders/" + sender_id + "constraints/active/" + ) + if response.status_code != 200: + return test.FAIL( + "The request has not succeeded: {}".format(response.json()) + ) + constraints = response.json() + + if not IS04Utils.compare_constraint_sets( + constraints["constraint_sets"], + self.sample_rate_constraints[sender_id]["constraint_sets"], + ): + return test.FAIL( + "constraints and SampleRateConstraints[" + + sender_id + + "] are different " + ) + + _, response = TestHelper.do_request( + "DELETE", + self.compat_url + "senders/" + sender_id + "constraints/active/", + ) + if response.status_code != 200: + return test.FAIL( + "The request has not succeeded: {}".format(response.json()) + ) + + _, response = TestHelper.do_request( + "GET", self.node_url + "senders/" + sender_id + ) + if response.status_code != 200: + return test.FAIL( + "The request has not succeeded: {}".format(response.json()) + ) + version = response.json()["version"] + if version == self.version[sender_id]: + return test.FAIL("Version are different") + self.version[sender_id] = version + + _, response = TestHelper.do_request( + "GET", self.compat_url + "senders/" + sender_id + "constraints/active/" + ) + if response.status_code != 200: + return test.FAIL( + "The request has not succeeded: {}".format(response.json()) + ) + constraints = response.json() + if constraints != self.empty_constraints[sender_id]: + return test.FAIL("Contraints are different") + return test.PASS() + + def test_02_02_05_01(self, test): + """ + Verify that setting NOP constraints for frame(width,height), + grain_rate doesn't change the flow of a sender(video). + """ + if len(self.flow_format_video) == 0: + return test.UNCLEAR("There is no video format.") + + for sender_id in self.flow_format_video: + _, response = TestHelper.do_request( + "GET", self.compat_url + "senders/" + sender_id + "status/" + ) + if response.status_code != 200: + return test.FAIL( + "The request has not succeeded: {}".format(response.json()) + ) + state = response.json()["state"] + if state in ["awating_essence", "no_essence"]: + for i in range(0, 5): + _, response = TestHelper.do_request( + "GET", self.compat_url + "senders/" + sender_id + "status/" + ) + if response.status_code != 200: + return test.FAIL( + "The request has not succeeded: {}".format(response.json()) + ) + state = response.json()["state"] + if state in ["awating_essence", "no_essence"]: + time.sleep(3) + else: + break + if state != "unconstrained": + return test.FAIL("inputs are unstable.") + + self.constraints[sender_id] = { + "constraint_sets": [ + { + "urn:x-nmos:cap:format:grain_rate": { + "enum": [self.flow_grain_rate[sender_id]] + } + }, + { + "urn:x-nmos:cap:format:frame_width": { + "enum": [self.flow_width[sender_id]] + } + }, + { + "urn:x-nmos:cap:format:frame_height": { + "enum": [self.flow_height[sender_id]] + } + }, + ] + } + + _, response = TestHelper.do_request( + "PUT", + self.compat_url + "senders/" + sender_id + "constraints/active/", + json=self.constraints[sender_id], + ) + if response.status_code != 200: + return test.FAIL( + "The request has not succeeded: {}".format(response.json()) + ) + + _, response = TestHelper.do_request( + "GET", self.compat_url + "senders/" + sender_id + "status/" + ) + if response.status_code != 200: + return test.FAIL( + "The request has not succeeded: {}".format(response.json()) + ) + state = response.json()["state"] + + if state in ["awating_essence", "no_essence"]: + for i in range(0, 5): + _, response = TestHelper.do_request( + "GET", self.compat_url + "senders/" + sender_id + "status/" + ) + if response.status_code != 200: + return test.FAIL( + "The request has not succeeded: {}".format(response.json()) + ) + state = response.json()["state"] + if state in ["awating_essence", "no_essence"]: + time.sleep(3) + else: + break + if state != "constrained": + return test.FAIL("inputs are unstable.") + + _, response = TestHelper.do_request( + "GET", self.node_url + "senders/" + sender_id + ) + if response.status_code != 200: + return test.FAIL( + "The request has not succeeded: {}".format(response.json()) + ) + sender_flow_id = response.json()["flow_id"] + if sender_flow_id is None: + return test.FAIL("the sender must have a flow") + + _, response = TestHelper.do_request( + "GET", self.node_url + "flows/" + sender_flow_id + ) + if response.status_code != 200: + return test.FAIL( + "The request has not succeeded: {}".format(response.json()) + ) + + 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"] + ): + return test.FAIL("different argument") + + _, response = TestHelper.do_request( + "DELETE", + self.compat_url + "senders/" + sender_id + "constraints/active/", + ) + if response.status_code != 200: + return test.FAIL( + "The request has not succeeded: {}".format(response.json()) + ) + + return test.PASS() + + def test_02_02_05_02(self, test): + """ + Verify that setting NOP constraints for sample_rate doesn't change the flow of a sender(audio). + """ + + if len(self.flow_format_audio) == 0: + return test.UNCLEAR("There is no audio format.") + + for sender_id in self.flow_format_audio: + _, response = TestHelper.do_request( + "GET", self.compat_url + "senders/" + sender_id + "status/" + ) + if response.status_code != 200: + return test.FAIL( + "The request has not succeeded: {}".format(response.json()) + ) + state = response.json()["state"] + + if state in ["awating_essence", "no_essence"]: + for i in range(0, 5): + _, response = TestHelper.do_request( + "GET", self.compat_url + "senders/" + sender_id + "status/" + ) + if response.status_code != 200: + return test.FAIL( + "The request has not succeeded: {}".format(response.json()) + ) + state = response.json()["state"] + if state in ["awating_essence", "no_essence"]: + time.sleep(3) + else: + break + if state != "unconstrained": + return test.FAIL("inputs are unstable.") + + self.constraints[sender_id] = { + "constraint_sets": [ + { + "urn:x-nmos:cap:format:sample_rate": { + "enum": [self.flow_sample_rate[sender_id]] + } + } + ] + } + _, response = TestHelper.do_request( + "PUT", + self.compat_url + "senders/" + sender_id + "constraints/active/", + json=self.constraints[sender_id], + ) + if response.status_code != 200: + return test.FAIL( + "The request has not succeeded: {}".format(response.json()) + ) + + _, response = TestHelper.do_request( + "GET", self.compat_url + "senders/" + sender_id + "status/" + ) + if response.status_code != 200: + return test.FAIL( + "The request has not succeeded: {}".format(response.json()) + ) + state = response.json()["state"] + + if state in ["awating_essence", "no_essence"]: + for i in range(0, 5): + _, response = TestHelper.do_request( + "GET", self.compat_url + "senders/" + sender_id + "status/" + ) + if response.status_code != 200: + return test.FAIL( + "The request has not succeeded: {}".format(response.json()) + ) + state = response.json()["state"] + if state in ["awating_essence", "no_essence"]: + time.sleep(3) + else: + break + if state != "constrained": + return test.FAIL("inputs are unstable.") + + _, response = TestHelper.do_request( + "GET", self.node_url + "senders/" + sender_id + ) + if response.status_code != 200: + return test.FAIL( + "The request has not succeeded: {}".format(response.json()) + ) + sender_flow_id = response.json()["flow_id"] + if sender_flow_id is None: + return test.FAIL("the sender must have a flow") + + _, response = TestHelper.do_request( + "GET", self.node_url + "flows/" + sender_flow_id + ) + if response.status_code != 200: + return test.FAIL( + "The request has not succeeded: {}".format(response.json()) + ) + flow_sample_rate = response.json()["sample_rate"] + if self.flow_sample_rate[sender_id] != flow_sample_rate: + return test.FAIL("Different sample rate") + + _, response = TestHelper.do_request( + "DELETE", + self.compat_url + "senders/" + sender_id + "constraints/active/", + ) + if response.status_code != 200: + return test.FAIL( + "The request has not succeeded: {}".format(response.json()) + ) + return test.PASS() + + def test_02_02_06_01(self, test): + """ + Verify that setting NOP constraints for supported constraints + doesn't change the flow of a sender(video). + """ + if len(self.flow_format_video) == 0: + return test.UNCLEAR("There is no video format.") + + for sender_id in self.flow_format_video: + _, response = TestHelper.do_request( + "GET", self.compat_url + "senders/" + sender_id + "status/" + ) + if response.status_code != 200: + return test.FAIL( + "The request has not succeeded: {}".format(response.json()) + ) + state = response.json()["state"] + if state in ["awating_essence", "no_essence"]: + for i in range(0, 5): + _, response = TestHelper.do_request( + "GET", self.compat_url + "senders/" + sender_id + "status/" + ) + if response.status_code != 200: + return test.FAIL( + "The request has not succeeded: {}".format(response.json()) + ) + state = response.json()["state"] + if state in ["awating_essence", "no_essence"]: + time.sleep(3) + else: + break + if state != "unconstrained": + return test.FAIL("inputs are unstable.") + + _, response = TestHelper.do_request( + "GET", self.node_url + "senders/" + sender_id + ) + if response.status_code != 200: + return test.FAIL( + "The request has not succeeded: {}".format(response.json()) + ) + sender = response.json() + + _, response = TestHelper.do_request( + "GET", self.node_url + "flows/" + sender["flow_id"] + ) + if response.status_code != 200: + return test.FAIL( + "The request has not succeeded: {}".format(response.json()) + ) + flow = response.json() + color_sampling = IS04Utils.make_sampling(flow["components"]) + if color_sampling is None: + return test.FAIL("invalid array of video components") + constraint_set = {} + + for item in REF_SUPPORTED_CONSTRAINTS_VIDEO: + try: + if item == "urn:x-nmos:cap:meta:label": + constraint_set["urn:x-nmos:cap:meta:label"] = "video constraint" + if item == "urn:x-nmos:cap:meta:preference": + constraint_set["urn:x-nmos:cap:meta:preference"] = 0 + if item == "urn:x-nmos:cap:meta:enabled": + 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"]] + } + if item == "urn:x-nmos:cap:format:grain_rate": + constraint_set["urn:x-nmos:cap:format:grain_rate"] = { + "enum": [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"]] + } + if item == "urn:x-nmos:cap:format:frame_height": + constraint_set["urn:x-nmos:cap:format:frame_height"] = { + "enum": [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"]] + } + if item == "urn:x-nmos:cap:format:color_sampling": + constraint_set["urn:x-nmos:cap:format:color_sampling"] = { + "enum": [color_sampling] + } + if item == "urn:x-nmos:cap:format:component_depth": + constraint_set["urn:x-nmos:cap:format:component_depth"] = { + "enum": [flow["components"][0]["bit_depth"]] + } + except Exception: + pass + + self.constraints[sender_id] = {"constraint_sets": [constraint_set]} + + _, response = TestHelper.do_request( + "PUT", + self.compat_url + "senders/" + sender_id + "constraints/active/", + json=self.constraints[sender_id], + ) + if response.status_code != 200: + return test.FAIL( + "The request has not succeeded: {}".format(response.json()) + ) + + _, response = TestHelper.do_request( + "GET", self.node_url + "flows/" + sender["flow_id"] + ) + if response.status_code != 200: + return test.FAIL( + "The request has not succeeded: {}".format(response.json()) + ) + new_flow = response.json() + + new_color_sampling = IS04Utils.make_sampling(new_flow["components"]) + if new_color_sampling is None: + return test.FAIL("invalid array of video components") + + 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"]: + return test.FAIL("different media_type") + if item == "urn:x-nmos:cap:format:grain_rate": + if 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"]: + return test.FAIL("different frame_width") + if item == "urn:x-nmos:cap:format:frame_height": + if 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"]: + 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"] + != new_flow["components"][0]["bit_depth"] + ): + return test.FAIL("different component_depth") + except Exception: + pass + _, response = TestHelper.do_request( + "DELETE", + self.compat_url + "senders/" + sender_id + "constraints/active/", + ) + if response.status_code != 200: + return test.FAIL( + "The request has not succeeded: {}".format(response.json()) + ) + return test.PASS() + + def test_02_02_06_02(self, test): + """ + Verify that setting NOP constraints for supported + constraints doesn't change the flow of a sender(audio). + """ + if len(self.flow_format_audio) == 0: + return test.UNCLEAR("There is no audio format.") + for sender_id in self.flow_format_audio: + _, response = TestHelper.do_request( + "GET", self.compat_url + "senders/" + sender_id + "status/" + ) + if response.status_code != 200: + test.FAIL("The request has not succeeded: {}".format(response.json())) + state = response.json()["state"] + if state in ["awating_essence", "no_essence"]: + for i in range(0, 5): + _, response = TestHelper.do_request( + "GET", self.compat_url + "senders/" + sender_id + "status/" + ) + if response.status_code != 200: + return test.FAIL( + "The request has not succeeded: {}".format(response.json()) + ) + state = response.json()["state"] + if state in ["awating_essence", "no_essence"]: + time.sleep(3) + else: + break + if state != "unconstrained": + return test.FAIL("inputs are unstable.") + _, response = TestHelper.do_request( + "GET", self.node_url + "senders/" + sender_id + ) + if response.status_code != 200: + return test.FAIL( + "The request has not succeeded: {}".format(response.json()) + ) + sender = response.json() + + _, response = TestHelper.do_request( + "GET", self.node_url + "flows/" + sender["flow_id"] + ) + if response.status_code != 200: + return test.FAIL( + "The request has not succeeded: {}".format(response.json()) + ) + flow = response.json() + constraint_set = {} + + _, response = TestHelper.do_request( + "GET", self.node_url + "sources/" + flow["source_id"] + ) + if response.status_code != 200: + return test.FAIL( + "The request has not succeeded: {}".format(response.json()) + ) + source = response.json() + + for item in REF_SUPPORTED_CONSTRAINTS_AUDIO: + try: + + if item == "urn:x-nmos:cap:meta:label": + constraint_set["urn:x-nmos:cap:meta:label"] = "audio constraint" + if item == "urn:x-nmos:cap:meta:preference": + constraint_set["urn:x-nmos:cap:meta:preference"] = 0 + if item == "urn:x-nmos:cap:meta:enabled": + 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"]] + } + if item == "urn:x-nmos:cap:format:sample_rate": + constraint_set["urn:x-nmos:cap:format:sample_rate"] = { + "enum": [flow["sample_rate"]] + } + if item == "urn:x-nmos:cap:format:channel_count": + constraint_set["urn:x-nmos:cap:format:channel_count"] = { + "enum": [len(source["channels"])] + } + if item == "urn:x-nmos:cap:format:sample_depth": + constraint_set["urn:x-nmos:cap:format:sample_depth"] = { + "enum": [flow["bit_depth"]] + } + except Exception: + pass + self.constraints[sender_id] = {"constraint_sets": [constraint_set]} + _, response = TestHelper.do_request( + "PUT", + self.compat_url + "senders/" + sender_id + "constraints/active/", + json=self.constraints[sender_id], + ) + if response.status_code != 200: + return test.FAIL( + "The request has not succeeded: {}".format(response.json()) + ) + + _, response = TestHelper.do_request( + "GET", self.node_url + "flows/" + sender["flow_id"] + ) + if response.status_code != 200: + return test.FAIL( + "The request has not succeeded: {}".format(response.json()) + ) + new_flow = response.json() + + _, response = TestHelper.do_request( + "GET", self.node_url + "sources/" + flow["source_id"] + ) + if response.status_code != 200: + return test.FAIL( + "The request has not succeeded: {}".format(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"]: + return test.FAIL("different media_type") + if item == "urn:x-nmos:cap:format:sample_rate": + if 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"]: + return test.FAIL("different sample_depth") + except Exception: + pass + _, response = TestHelper.do_request( + "DELETE", + self.compat_url + "senders/" + sender_id + "constraints/active/", + ) + if response.status_code != 200: + return test.FAIL( + "The request has not succeeded: {}".format(response.json()) + ) + return test.PASS() + + def test_02_02_07_01(self, test): + "Verify that the device adhere to the preference of the constraint_set." + if len(self.flow_format_video) == 0: + return test.UNCLEAR("There is no video format.") + + for sender_id in self.flow_format_video: + _, response = TestHelper.do_request( + "GET", self.compat_url + "senders/" + sender_id + "status/" + ) + if response.status_code != 200: + return test.FAIL( + "The request has not succeeded: {}".format(response.json()) + ) + state = response.json()["state"] + if state in ["awating_essence", "no_essence"]: + for i in range(0, 5): + _, response = TestHelper.do_request( + "GET", self.compat_url + "senders/" + sender_id + "status/" + ) + if response.status_code != 200: + return test.FAIL( + "The request has not succeeded: {}".format(response.json()) + ) + state = response.json()["state"] + + if state in ["awating_essence", "no_essence"]: + time.sleep(3) + else: + break + if state != "unconstrained": + return test.FAIL("inputs are unstable.") + + _, response = TestHelper.do_request( + "GET", self.node_url + "senders/" + sender_id + ) + if response.status_code != 200: + return test.FAIL( + "The request has not succeeded: {}".format(response.json()) + ) + sender = response.json() + + _, response = TestHelper.do_request( + "GET", self.node_url + "flows/" + sender["flow_id"] + ) + if response.status_code != 200: + return test.FAIL( + "The request has not succeeded: {}".format(response.json()) + ) + flow = response.json() + color_sampling = IS04Utils.make_sampling(flow["components"]) + if color_sampling is None: + return test.FAIL("invalid array of video components") + constraint_set0 = {} + constraint_set1 = {} + + for item in REF_SUPPORTED_CONSTRAINTS_VIDEO: + try: + + if item == "urn:x-nmos:cap:meta:label": + constraint_set0[ + "urn:x-nmos:cap:meta:label" + ] = "video constraint" + if item == "urn:x-nmos:cap:meta:preference": + constraint_set0["urn:x-nmos:cap:meta:preference"] = 0 + if item == "urn:x-nmos:cap:meta:enabled": + 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"]] + } + if item == "urn:x-nmos:cap:format:grain_rate": + constraint_set0["urn:x-nmos:cap:format:grain_rate"] = { + "enum": [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"]] + } + if item == "urn:x-nmos:cap:format:frame_height": + constraint_set0["urn:x-nmos:cap:format:frame_height"] = { + "enum": [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"]] + } + if item == "urn:x-nmos:cap:format:color_sampling": + constraint_set0["urn:x-nmos:cap:format:color_sampling"] = { + "enum": [color_sampling] + } + if item == "urn:x-nmos:cap:format:component_depth": + constraint_set0["urn:x-nmos:cap:format:component_depth"] = { + "enum": [flow["components"][0]["bit_depth"]] + } + except Exception: + pass + + for item in REF_SUPPORTED_CONSTRAINTS_VIDEO: + try: + if item == "urn:x-nmos:cap:meta:label": + constraint_set1[ + "urn:x-nmos:cap:meta:label" + ] = "video constraint" + if item == "urn:x-nmos:cap:meta:preference": + constraint_set1["urn:x-nmos:cap:meta:preference"] = -100 + if item == "urn:x-nmos:cap:meta:enabled": + 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"]] + } + 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"])] + } + if item == "urn:x-nmos:cap:format:frame_width": + constraint_set1["urn:x-nmos:cap:format:frame_width"] = { + "enum": [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"]] + } + if item == "urn:x-nmos:cap:format:interlace_mode": + constraint_set1["urn:x-nmos:cap:format:interlace_mode"] = { + "enum": [flow["interlace_mode"]] + } + if item == "urn:x-nmos:cap:format:color_sampling": + constraint_set1["urn:x-nmos:cap:format:color_sampling"] = { + "enum": [color_sampling] + } + if item == "urn:x-nmos:cap:format:component_depth": + constraint_set1["urn:x-nmos:cap:format:component_depth"] = { + "enum": [flow["components"][0]["bit_depth"]] + } + except Exception: + pass + + self.constraints[sender_id] = { + "constraint_sets": [constraint_set0, constraint_set0] + } + _, response = TestHelper.do_request( + "PUT", + self.compat_url + "senders/" + sender_id + "constraints/active/", + json=self.constraints[sender_id], + ) + if response.status_code != 200: + return test.FAIL( + "The request has not succeeded: {}".format(response.json()) + ) + + _, response = TestHelper.do_request( + "GET", self.node_url + "flows/" + sender["flow_id"] + ) + if response.status_code != 200: + return test.FAIL( + "The request has not succeeded: {}".format(response.json()) + ) + new_flow = response.json() + + new_color_sampling = IS04Utils.make_sampling(new_flow["components"]) + if new_color_sampling is None: + return test.FAIL("invalid array of video components") + + 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"]: + return test.FAIL("different media_type") + if item == "urn:x-nmos:cap:format:grain_rate": + if 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"]: + return test.FAIL("different frame_width") + if item == "urn:x-nmos:cap:format:frame_height": + if 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"]: + 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"] + != new_flow["components"][0]["bit_depth"] + ): + return test.FAIL("different component_depth") + except Exception: + pass + + _, response = TestHelper.do_request( + "DELETE", + self.compat_url + "senders/" + sender_id + "constraints/active/", + ) + if response.status_code != 200: + return test.FAIL( + "The request has not succeeded: {}".format(response.json()) + ) + return test.PASS() + + def test_02_02_07_02(self, test): + "Verify that the device adhere to the preference of the constraint_set." + if len(self.flow_format_audio) == 0: + return test.UNCLEAR("There is no audio format.") + + for sender_id in self.flow_format_audio: + _, response = TestHelper.do_request( + "GET", self.compat_url + "senders/" + sender_id + "status/" + ) + if response.status_code != 200: + test.FAIL("The request has not succeeded: {}".format(response.json())) + state = response.json()["state"] + if state in ["awating_essence", "no_essence"]: + for i in range(0, 5): + _, response = TestHelper.do_request( + "GET", self.compat_url + "senders/" + sender_id + "status/" + ) + if response.status_code != 200: + return test.FAIL( + "The request has not succeeded: {}".format(response.json()) + ) + state = response.json()["state"] + if state in ["awating_essence", "no_essence"]: + time.sleep(3) + else: + break + if state != "unconstrained": + return test.FAIL("inputs are unstable.") + + _, response = TestHelper.do_request( + "GET", self.node_url + "senders/" + sender_id + ) + if response.status_code != 200: + return test.FAIL( + "The request has not succeeded: {}".format(response.json()) + ) + sender = response.json() + + _, response = TestHelper.do_request( + "GET", self.node_url + "flows/" + sender["flow_id"] + ) + if response.status_code != 200: + return test.FAIL( + "The request has not succeeded: {}".format(response.json()) + ) + flow = response.json() + _, response = TestHelper.do_request( + "GET", self.node_url + "sources/" + flow["source_id"] + ) + if response.status_code != 200: + return test.FAIL( + "The request has not succeeded: {}".format(response.json()) + ) + source = response.json() + + constraint_set0 = {} + constraint_set1 = {} + + for item in REF_SUPPORTED_CONSTRAINTS_AUDIO: + try: + if item == "urn:x-nmos:cap:meta:label": + constraint_set0[ + "urn:x-nmos:cap:meta:label" + ] = "audio constraint" + if item == "urn:x-nmos:cap:meta:preference": + constraint_set0["urn:x-nmos:cap:meta:preference"] = 0 + if item == "urn:x-nmos:cap:meta:enabled": + 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"]] + } + if item == "urn:x-nmos:cap:format:sample_rate": + constraint_set0["urn:x-nmos:cap:format:sample_rate"] = { + "enum": [flow["sample_rate"]] + } + if item == "urn:x-nmos:cap:format:channel_count": + constraint_set0["urn:x-nmos:cap:format:channel_count"] = { + "enum": [len(source["channels"])] + } + if item == "urn:x-nmos:cap:format:sample_depth": + constraint_set0["urn:x-nmos:cap:format:sample_depth"] = { + "enum": [flow["bit_depth"]] + } + except Exception: + pass + + for item in REF_SUPPORTED_CONSTRAINTS_AUDIO: + try: + if item == "urn:x-nmos:cap:meta:label": + constraint_set1[ + "urn:x-nmos:cap:meta:label" + ] = "video constraint" + if item == "urn:x-nmos:cap:meta:preference": + constraint_set1["urn:x-nmos:cap:meta:preference"] = -100 + if item == "urn:x-nmos:cap:meta:enabled": + 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"]] + } + 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"])] + } + if item == "urn:x-nmos:cap:format:channel_count": + constraint_set1["urn:x-nmos:cap:format:channel_count"] = { + "enum": [len(source["channels"])] + } + if item == "urn:x-nmos:cap:format:sample_depth": + constraint_set1["urn:x-nmos:cap:format:sample_depth"] = { + "enum": [flow["bit_depth"]] + } + except Exception: + pass + + self.constraints[sender_id] = { + "constraint_sets": [constraint_set0, constraint_set1] + } + + _, response = TestHelper.do_request( + "PUT", + self.compat_url + "senders/" + sender_id + "constraints/active/", + json=self.constraints[sender_id], + ) + if response.status_code != 200: + return test.FAIL( + "The request has not succeeded: {}".format(response.json()) + ) + _, response = TestHelper.do_request( + "GET", self.node_url + "flows/" + sender["flow_id"] + ) + if response.status_code != 200: + return test.FAIL( + "The request has not succeeded: {}".format(response.json()) + ) + new_flow = response.json() + + _, response = TestHelper.do_request( + "GET", self.node_url + "sources/" + flow["source_id"] + ) + if response.status_code != 200: + return test.FAIL( + "The request has not succeeded: {}".format(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"]: + return test.FAIL("different media_type") + if item == "urn:x-nmos:cap:format:sample_rate": + if 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"]: + return test.FAIL("different sample_depth") + except Exception: + pass + _, response = TestHelper.do_request( + "DELETE", + self.compat_url + "senders/" + sender_id + "constraints/active/", + ) + if response.status_code != 200: + return test.FAIL( + "The request has not succeeded: {}".format(response.json()) + ) + return test.PASS() + # OUTPUTS TESTS def test_03_01(self, test): """ diff --git a/nmostesting/suites/IS1102Test.py b/nmostesting/suites/IS1102Test.py deleted file mode 100644 index 48e9dd65..00000000 --- a/nmostesting/suites/IS1102Test.py +++ /dev/null @@ -1,1395 +0,0 @@ -# Copyright (C) 2022 Advanced Media Workflow Association -# -# 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. - -from ..GenericTest import GenericTest -from .. import TestHelper -import time -import re -from .. import nmosTestingComplexCompare - -COMPAT_API_KEY = "streamcompatibility" -NODE_API_KEY = "node" -CONTROLS = "controls" - -REF_SUPPORTED_CONSTRAINTS_VIDEO = [ - "urn:x-nmos:cap:meta:label", - "urn:x-nmos:cap:meta:preference", - "urn:x-nmos:cap:meta:enabled", - "urn:x-nmos:cap:format:media_type", - "urn:x-nmos:cap:format:grain_rate", - "urn:x-nmos:cap:format:frame_width", - "urn:x-nmos:cap:format:frame_height", - "urn:x-nmos:cap:format:interlace_mode", - "urn:x-nmos:cap:format:color_sampling", - "urn:x-nmos:cap:format:component_depth", -] -REF_SUPPORTED_CONSTRAINTS_AUDIO = [ - "urn:x-nmos:cap:meta:label", - "urn:x-nmos:cap:meta:preference", - "urn:x-nmos:cap:meta:enabled", - "urn:x-nmos:cap:format:media_type", - "urn:x-nmos:cap:format:channel_count", - "urn:x-nmos:cap:format:sample_rate", - "urn:x-nmos:cap:format:sample_depth", -] - - -class IS1102Test(GenericTest): - """ - Runs Node Tests covering IS-11 - """ - def __init__(self, apis): - GenericTest.__init__(self, apis,) - self.node_url = self.apis[NODE_API_KEY]["url"] - self.compat_url = self.apis[COMPAT_API_KEY]["url"] - self.senders = "" - self.senders_2 = "" - self.flow_format = {} - self.flow_format_audio = [] - self.flow_format_video = [] - self.flow_width = {} - self.flow_height = {} - self.flow_grain_rate = {} - self.flow_sample_rate = {} - self.version = {} - self.grain_rate_constraints = {} - self.empty_constraints = {} - self.sample_rate_constraints = {} - self.constraints = {} - - # SENDERS TESTS - """ - Runs Node Tests covering IS-11 for Senders - """ - - def getSdpColorSampling(self, flow_components): - """ - getColorSamplingFromComponents supports RGB, - YCbCr-4:4:4, YCbCr-4:2:2, YCbCr-4:2:0 and assumes - that the bit-depth is compliant without verifying it. - """ - names = [] - widths = [] - heights = [] - - if len(flow_components) != 3: - return "invalid array of video components" - - for i in range(0, 3): - if "name" in flow_components[i]: - names.append( - {"name" + str(i): flow_components[i]["name"], "err" + str(i): None} - ) - else: - names.append({"name" + str(i): None, "err" + str(i): "not defined"}) - - if ( - names[0]["err0"] is None - and names[0]["name0"] == "R" - and names[1]["err1"] is None - and names[1]["name1"] == "G" - and names[2]["err2"] is None - and names[2]["name2"] == "B" - ): - for i in range(0, 3): - if "width" in flow_components[i]: - widths.append( - { - "width" + str(i): flow_components[i]["width"], - "err" + str(i): None, - } - ) - else: - widths.append( - {"width" + str(i): None, "err" + str(i): "not defined"} - ) - - if ( - widths[0]["err0"] is not None - or widths[1]["err1"] is not None - or widths[2]["err2"] is not None - ): - return "invalid array of video components" - - for i in range(0, 3): - if "height" in flow_components[i]: - heights.append( - { - "height" + str(i): flow_components[i]["height"], - "err" + str(i): None, - } - ) - else: - heights.append( - {"height" + str(i): None, "err" + str(i): "not defined"} - ) - - if ( - heights[0]["err0"] is not None - or heights[1]["err1"] is not None - or heights[2]["err2"] is not None - ): - return "invalid array of video components" - - if ( - widths[0]["width0"] == widths[1]["width1"] - and widths[0]["width0"] == widths[2]["width2"] - and heights[0]["height0"] == heights[1]["height1"] - and heights[0]["height0"] == heights[2]["height2"] - ): - return "RGB" - - if ( - names[0]["err0"] is None - and names[0]["name0"] == "Y" - and names[1]["err1"] is None - and names[1]["name1"] == "Cb" - and names[2]["err2"] is None - and names[2]["name2"] == "Cr" - ): - - for i in range(0, 3): - if "width" in flow_components[i]: - widths.append( - { - "width" + str(i): flow_components[i]["width"], - "err" + str(i): None, - } - ) - else: - widths.append( - {"width" + str(i): None, "err" + str(i): "not defined"} - ) - - if ( - widths[0]["err0"] is not None - or widths[1]["err1"] is not None - or widths[2]["err2"] is not None - ): - return "invalid array of video components" - - for i in range(0, 3): - if "height" in flow_components[i]: - heights.append( - { - "height" + str(i): flow_components[i]["height"], - "err" + str(i): None, - } - ) - else: - heights.append( - {"height" + str(i): None, "err" + str(i): "not defined"} - ) - - if ( - heights[0]["err0"] is not None - or heights[1]["err1"] is not None - or heights[2]["err2"] is not None - ): - return "invalid array of video components" - - if ( - widths[0]["width0"] == widths[1]["width1"] - and widths[0]["width0"] == widths[2]["width2"] - and heights[0]["height0"] == heights[1]["height1"] - and heights[0]["height0"] == heights[2]["height2"] - ): - return "YCbCr-4:4:4" - - if ( - widths[0]["width0"] == 2 * widths[1]["width1"] - and widths[0]["width0"] == 2 * widths[2]["width2"] - and heights[0]["height0"] == heights[1]["height1"] - and heights[0]["height0"] == heights[2]["height2"] - ): - return "YCbCr-4:2:2" - - if ( - widths[0]["width0"] == 2 * widths[1]["width1"] - and widths[0]["width0"] == 2 * widths[2]["width2"] - and heights[0]["height0"] == 2 * heights[1]["height1"] - and heights[0]["height0"] == 2 * heights[2]["height2"] - ): - return "YCbCr-4:2:0" - - return "invalid array of video components" - - def get_another_grain_rate(self, grain_rate): - numerator = grain_rate["numerator"] - denominator = grain_rate["denominator"] - if (numerator == 30 or numerator == 25) and denominator == 1: - return {"numerator": numerator * 2, "denominator": 1} - if (numerator == 60 or numerator == 50) and denominator == 1: - return {"numerator": numerator / 2, "denominator": 1} - if numerator == 24 and denominator == 1: - return {"numerator": 30, "denominator": 1} - if (numerator == 30000 or numerator == 25000) and denominator == 1001: - return {"numerator": numerator * 2, "denominator": 1001} - if (numerator == 60000 or numerator == 50000) and denominator == 1001: - return {"numerator": numerator / 2, "denominator": 1001} - return "grain_rate not valid" - - def get_another_sample_rate(self, sample_rate): - numerator = sample_rate["numerator"] - if numerator == 0: - return {"numerator": 48000} - if numerator == 48000: - return {"numerator": 44100} - if numerator == 44100: - return {"numerator": 48000} - if numerator == 96000: - return {"numerator": 4800} - if numerator == 88200: - return {"numerator": 44100} - return "sample_rate not valid" - - def test_02_00(self, test): - "Reset active constraints of all senders" - _, response = TestHelper.do_request("GET", self.compat_url + "senders/") - if response.status_code != 200: - return test.FAIL("The request has not succeeded: {}".format(response.json())) - self.senders = response.json() - if len(self.senders) != 0: - for sender in self.senders: - _, response = TestHelper.do_request( - "DELETE", - self.compat_url + "senders/" + sender + "constraints/active/", - ) - if response.status_code != 200: - return test.FAIL("senders constraints cannot be deleted") - return test.PASS() - return test.UNCLEAR("There is no IS-11 senders.") - - def test_02_01(self, test): - "Verify that the device supports the concept of IS-11 Sender" - _, response = TestHelper.do_request("GET", self.compat_url + "senders/") - if response.status_code != 200: - return test.FAIL(response.json) - self.senders = response.json() - if len(self.senders) == 0: - return test.UNCLEAR("There is no IS-11 senders.") - return test.PASS() - - def test_02_01_01(self, test): - "Verify that the device supports the concept of IS-11 Sender" - if len(self.senders) != 0: - for sender_id in self.senders: - _, response = TestHelper.do_request( - "GET", self.node_url + "senders/" + sender_id - ) - if response.status_code != 200: - return test.FAIL("The request has not succeeded: {}".format(response.json())) - sender_node = response.json()["id"] - if sender_id[:-1] != sender_node: - return test.FAIL("Senders are different") - return test.PASS() - return test.UNCLEAR("There is no IS-11 senders.") - - def test_02_02(self, test): - "Verify senders (generic with/without inputs)" - _, response = TestHelper.do_request("GET", self.compat_url + "senders/") - if response.status_code != 200: - return test.FAIL("The request has not succeeded: {}".format(response.json())) - self.senders_2 = response.json() - if len(self.senders_2) == 0: - return test.UNCLEAR("There is no IS-11 senders.") - return test.PASS() - - def test_02_02_01(self, test): - "Verify that the status is unconstrained as per our pre-conditions" - if len(self.senders_2) != 0: - for sender_id in self.senders_2: - _, response = TestHelper.do_request( - "GET", self.compat_url + "senders/" + sender_id + "status/" - ) - if response.status_code != 200: - return test.FAIL("The request has not succeeded: {}".format(response.json())) - state = response.json()["state"] - if state in ["awating_essence", "no_essence"]: - for i in range(0, 5): - _, response = TestHelper.do_request( - "GET", self.compat_url + "senders/" + sender_id + "status/" - ) - state = response.json()["status"]["state"] - if state in ["awating_essence", "no_essence"]: - time.sleep(3000) - else: - break - if state != "unconstrained": - return test.FAIL("inputs are unstable.") - return test.PASS() - return test.UNCLEAR("There is no IS-11 senders.") - - def test_02_02_03(self, test): - """ - Verify that the sender is available in the node API, - has an associated flow and is inactive - """ - if len(self.senders_2) != 0: - for sender_id in self.senders_2: - _, response = TestHelper.do_request( - "GET", self.node_url + "senders/" + sender_id - ) - if response.status_code != 200: - return test.FAIL(response.json) - sender_node = response.json()["id"] - if sender_id[:-1] != sender_node: - return test.FAIL("Senders are different") - sender_flow_id = response.json()["flow_id"] - if sender_flow_id is None: - return test.FAIL("the sender must have a flow") - sender_subscription_active = response.json()["subscription"]["active"] - if sender_subscription_active: - return test.FAIL("the sender {} must be inactive ".format(sender_id)) - _, response = TestHelper.do_request( - "GET", self.node_url + "flows/" + sender_flow_id - ) - if response.status_code != 200: - return test.FAIL("The request has not succeeded: {}".format(response.json())) - flow_format = response.json()["format"] - self.flow_format[sender_id] = flow_format - if flow_format == "urn:x-nmos:format:video": - self.flow_format_video.append(sender_id) - flow_frame_width = response.json()["frame_width"] - self.flow_width[sender_id] = flow_frame_width - flow_frame_height = response.json()["frame_height"] - self.flow_height[sender_id] = flow_frame_height - flow_grain_rate = response.json()["grain_rate"] - self.flow_grain_rate[sender_id] = flow_grain_rate - if flow_format == "urn:x-nmos:format:audio": - self.flow_format_audio.append(sender_id) - flow_sample_rate = response.json()["sample_rate"] - self.flow_sample_rate[sender_id] = flow_sample_rate - if ( - flow_format != "urn:x-nmos:format:video" - and flow_format != "urn:x-nmos:format:audio" - ): - print("only audio and video senders are tested at this time.") - return test.PASS() - return test.UNCLEAR("There is no IS-11 senders.") - - def test_02_02_03_01(self, test): - "Verify that the video sender supports the minimum set of video constraints" - - pattern = "^urn:x-nmos:cap:" - - if len(self.flow_format_video) == 0: - return test.UNCLEAR("There is no video format.") - - for sender_id in self.flow_format_video: - _, response = TestHelper.do_request( - "GET", - self.compat_url + "senders/" + sender_id + "constraints/supported/", - ) - if response.status_code != 200: - return test.FAIL("The request has not succeeded: {}".format(response.json())) - supportedConstraints = response.json()["parameter_constraints"] - for item in supportedConstraints: - if not re.search(pattern, item): - return test.FAIL("only x-nmos:cap constraints are allowed") - for item in REF_SUPPORTED_CONSTRAINTS_VIDEO: - if item not in supportedConstraints: - return test.FAIL(item + " is not in supportedConstraints ") - return test.PASS() - - def test_02_02_03_02(self, test): - "Verify that the video sender supports the minimum set of video constraints" - - pattern = "^urn:x-nmos:cap:" - - if len(self.flow_format_audio) == 0: - return test.UNCLEAR("There is no video format.") - - for sender_id in self.flow_format_audio: - _, response = TestHelper.do_request( - "GET", - self.compat_url + "senders/" + sender_id + "constraints/supported/", - ) - if response.status_code != 200: - return test.FAIL("The request has not succeeded: {}".format(response.json())) - supportedConstraints = response.json()["parameter_constraints"] - for item in supportedConstraints: - if not re.search(pattern, item): - return test.FAIL("only x-nmos:cap constraints are allowed") - for item in REF_SUPPORTED_CONSTRAINTS_AUDIO: - if item not in supportedConstraints: - return test.FAIL(item + "is not in supportedConstraints") - return test.PASS() - - def test_02_02_04_01(self, test): - """ - Verify that changing the constraints of an - IS-11 sender(video) changes the version of - the associated IS-04 sender. - """ - if len(self.flow_format_video) == 0: - return test.UNCLEAR("There is no video format.") - - for sender_id in self.flow_format_video: - _, response = TestHelper.do_request( - "GET", self.node_url + "senders/" + sender_id - ) - if response.status_code != 200: - return test.FAIL("The request has not succeeded: {}".format(response.json())) - version = response.json()["version"] - self.version[sender_id] = version - self.grain_rate_constraints[sender_id] = { - "constraint_sets": [ - { - "urn:x-nmos:cap:format:grain_rate": { - "enum": [self.flow_grain_rate[sender_id]] - } - } - ] - } - self.empty_constraints[sender_id] = {"constraint_sets": []} - _, response = TestHelper.do_request( - "PUT", - self.compat_url + "senders/" + sender_id + "constraints/active/", - json=self.grain_rate_constraints[sender_id], - ) - if response.status_code != 200: - return test.FAIL("The request has not succeeded: {}".format(response.json())) - _, response = TestHelper.do_request( - "GET", self.node_url + "senders/" + sender_id - ) - if response.status_code != 200: - return test.FAIL("The request has not succeeded: {}".format(response.json())) - version = response.json()["version"] - if version == self.version[sender_id]: - return test.FAIL("Version are different") - self.version[sender_id] = version - _, response = TestHelper.do_request( - "GET", self.compat_url + "senders/" + sender_id + "constraints/active/" - ) - if response.status_code != 200: - return test.FAIL("The request has not succeeded: {}".format(response.json())) - constraints = response.json() - if not nmosTestingComplexCompare.compare_complex_grain_rate_constraint( - constraints, self.grain_rate_constraints[sender_id] - ): - return test.FAIL("Contraints are different") - _, response = TestHelper.do_request( - "DELETE", - self.compat_url + "senders/" + sender_id + "constraints/active/", - ) - if response.status_code != 200: - return test.FAIL("The request has not succeeded: {}".format(response.json())) - _, response = TestHelper.do_request( - "GET", self.node_url + "senders/" + sender_id - ) - if response.status_code != 200: - return test.FAIL("The request has not succeeded: {}".format(response.json())) - version = response.json()["version"] - if version == self.version[sender_id]: - return test.FAIL("Version are different") - self.version[sender_id] = version - _, response = TestHelper.do_request( - "GET", self.compat_url + "senders/" + sender_id + "constraints/active/" - ) - if response.status_code != 200: - return test.FAIL("The request has not succeeded: {}".format(response.json())) - constraints = response.json() - if constraints != self.empty_constraints[sender_id]: - return test.FAIL("Contraints are different") - return test.PASS() - - def test_02_02_04_02(self, test): - """ - Verify that changing the constraints of an IS-11 - sender(audio) changes the version of the associated IS-04 sender. - """ - if len(self.flow_format_audio) == 0: - return test.UNCLEAR("There is no audio format.") - for sender_id in self.flow_format_audio: - _, response = TestHelper.do_request( - "GET", self.node_url + "senders/" + sender_id - ) - if response.status_code != 200: - return test.FAIL("The request has not succeeded: {}".format(response.json())) - version = response.json()["version"] - self.version[sender_id] = version - self.sample_rate_constraints[sender_id] = { - "constraint_sets": [ - { - "urn:x-nmos:cap:format:sample_rate": { - "enum": [self.flow_sample_rate[sender_id]] - } - } - ] - } - self.empty_constraints[sender_id] = {"constraint_sets": []} - _, response = TestHelper.do_request( - "PUT", - self.compat_url + "senders/" + sender_id + "constraints/active/", - json=self.sample_rate_constraints[sender_id], - ) - if response.status_code != 200: - return test.FAIL("The request has not succeeded: {}".format(response.json())) - _, response = TestHelper.do_request( - "GET", self.node_url + "senders/" + sender_id - ) - if response.status_code != 200: - return test.FAIL("The request has not succeeded: {}".format(response.json())) - version = response.json()["version"] - if version == self.version[sender_id]: - return test.FAIL("Version are different") - self.version[sender_id] = version - - _, response = TestHelper.do_request( - "GET", self.compat_url + "senders/" + sender_id + "constraints/active/" - ) - if response.status_code != 200: - return test.FAIL("The request has not succeeded: {}".format(response.json())) - constraints = response.json() - - if not nmosTestingComplexCompare.compare_complex_sample_rate_constraint( - constraints, self.sample_rate_constraints[sender_id] - ): - return test.FAIL( - "constraints and SampleRateConstraints[" - + sender_id - + "] are different " - ) - - _, response = TestHelper.do_request( - "DELETE", - self.compat_url + "senders/" + sender_id + "constraints/active/", - ) - if response.status_code != 200: - return test.FAIL("The request has not succeeded: {}".format(response.json())) - - _, response = TestHelper.do_request( - "GET", self.node_url + "senders/" + sender_id - ) - if response.status_code != 200: - return test.FAIL("The request has not succeeded: {}".format(response.json())) - version = response.json()["version"] - if version == self.version[sender_id]: - return test.FAIL("Version are different") - self.version[sender_id] = version - - _, response = TestHelper.do_request( - "GET", self.compat_url + "senders/" + sender_id + "constraints/active/" - ) - if response.status_code != 200: - return test.FAIL("The request has not succeeded: {}".format(response.json())) - constraints = response.json() - if constraints != self.empty_constraints[sender_id]: - return test.FAIL("Contraints are different") - return test.PASS() - - def test_02_02_05_01(self, test): - """ - Verify that setting NOP constraints for frame(width,height), - grain_rate doesn't change the flow of a sender(video). - """ - if len(self.flow_format_video) == 0: - return test.UNCLEAR("There is no video format.") - - for sender_id in self.flow_format_video: - _, response = TestHelper.do_request( - "GET", self.compat_url + "senders/" + sender_id + "status/" - ) - if response.status_code != 200: - return test.FAIL("The request has not succeeded: {}".format(response.json())) - state = response.json()["state"] - if state in ["awating_essence", "no_essence"]: - for i in range(0, 5): - _, response = TestHelper.do_request( - "GET", self.compat_url + "senders/" + sender_id + "status/" - ) - state = response.json()["status"]["state"] - if state in ["awating_essence", "no_essence"]: - time.sleep(3000) - else: - break - if state != "unconstrained": - return test.FAIL("inputs are unstable.") - - self.constraints[sender_id] = { - "constraint_sets": [ - { - "urn:x-nmos:cap:format:grain_rate": { - "enum": [self.flow_grain_rate[sender_id]] - } - }, - { - "urn:x-nmos:cap:format:frame_width": { - "enum": [self.flow_width[sender_id]] - } - }, - { - "urn:x-nmos:cap:format:frame_height": { - "enum": [self.flow_height[sender_id]] - } - }, - ] - } - - _, response = TestHelper.do_request( - "PUT", - self.compat_url + "senders/" + sender_id + "constraints/active/", - json=self.constraints[sender_id], - ) - if response.status_code != 200: - return test.FAIL("The request has not succeeded: {}".format(response.json())) - - _, response = TestHelper.do_request( - "GET", self.compat_url + "senders/" + sender_id + "status/" - ) - if response.status_code != 200: - return test.FAIL("The request has not succeeded: {}".format(response.json())) - state = response.json()["state"] - - if state in ["awating_essence", "no_essence"]: - for i in range(0, 5): - _, response = TestHelper.do_request( - "GET", self.compat_url + "senders/" + sender_id + "status/" - ) - state = response.json()["status"]["state"] - if state in ["awating_essence", "no_essence"]: - time.sleep(3000) - else: - break - if state != "constrained": - return test.FAIL("inputs are unstable.") - - _, response = TestHelper.do_request( - "GET", self.node_url + "senders/" + sender_id - ) - if response.status_code != 200: - return test.FAIL(response.json) - sender_flow_id = response.json()["flow_id"] - if sender_flow_id is None: - return test.FAIL("the sender must have a flow") - - _, response = TestHelper.do_request( - "GET", self.node_url + "flows/" + sender_flow_id - ) - if response.status_code != 200: - return test.FAIL("The request has not succeeded: {}".format(response.json())) - - 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"] - ): - return test.FAIL("different argument") - - _, response = TestHelper.do_request( - "DELETE", - self.compat_url + "senders/" + sender_id + "constraints/active/", - ) - if response.status_code != 200: - return test.FAIL("The request has not succeeded: {}".format(response.json())) - - return test.PASS() - - def test_02_02_05_02(self, test): - """ - Verify that setting NOP constraints for sample_rate doesn't change the flow of a sender(audio). - """ - - if len(self.flow_format_audio) == 0: - return test.UNCLEAR("There is no audio format.") - - for sender_id in self.flow_format_audio: - _, response = TestHelper.do_request( - "GET", self.compat_url + "senders/" + sender_id + "status/" - ) - if response.status_code != 200: - return test.FAIL("The request has not succeeded: {}".format(response.json())) - state = response.json()["state"] - - if state in ["awating_essence", "no_essence"]: - for i in range(0, 5): - _, response = TestHelper.do_request( - "GET", self.compat_url + "senders/" + sender_id + "status/" - ) - state = response.json()["status"]["state"] - if state in ["awating_essence", "no_essence"]: - time.sleep(3000) - else: - break - if state != "unconstrained": - return test.FAIL("inputs are unstable.") - - self.constraints[sender_id] = { - "constraint_sets": [ - { - "urn:x-nmos:cap:format:sample_rate": { - "enum": [self.flow_sample_rate[sender_id]] - } - } - ] - } - _, response = TestHelper.do_request( - "PUT", - self.compat_url + "senders/" + sender_id + "constraints/active/", - json=self.constraints[sender_id], - ) - if response.status_code != 200: - return test.FAIL("The request has not succeeded: {}".format(response.json())) - - _, response = TestHelper.do_request( - "GET", self.compat_url + "senders/" + sender_id + "status/" - ) - if response.status_code != 200: - return test.FAIL("The request has not succeeded: {}".format(response.json())) - state = response.json()["state"] - - if state in ["awating_essence", "no_essence"]: - for i in range(0, 5): - _, response = TestHelper.do_request( - "GET", self.compat_url + "senders/" + sender_id + "status/" - ) - state = response.json()["status"]["state"] - if state in ["awating_essence", "no_essence"]: - time.sleep(3000) - else: - break - if state != "constrained": - return test.FAIL("inputs are unstable.") - - _, response = TestHelper.do_request( - "GET", self.node_url + "senders/" + sender_id - ) - if response.status_code != 200: - return test.FAIL(response.json) - sender_flow_id = response.json()["flow_id"] - if sender_flow_id is None: - return test.FAIL("the sender must have a flow") - - _, response = TestHelper.do_request( - "GET", self.node_url + "flows/" + sender_flow_id - ) - if response.status_code != 200: - return test.FAIL("The request has not succeeded: {}".format(response.json())) - flow_sample_rate = response.json()["sample_rate"] - if self.flow_sample_rate[sender_id] != flow_sample_rate: - return test.FAIL("Different sample rate") - - _, response = TestHelper.do_request( - "DELETE", - self.compat_url + "senders/" + sender_id + "constraints/active/", - ) - if response.status_code != 200: - return test.FAIL("The request has not succeeded: {}".format(response.json())) - return test.PASS() - - def test_02_02_06_01(self, test): - """ - Verify that setting NOP constraints for supported constraints - doesn't change the flow of a sender(video). - """ - if len(self.flow_format_video) == 0: - return test.UNCLEAR("There is no video format.") - - for sender_id in self.flow_format_video: - _, response = TestHelper.do_request( - "GET", self.compat_url + "senders/" + sender_id + "status/" - ) - if response.status_code != 200: - return test.FAIL("The request has not succeeded: {}".format(response.json())) - state = response.json()["state"] - if state in ["awating_essence", "no_essence"]: - for i in range(0, 5): - _, response = TestHelper.do_request( - "GET", self.compat_url + "senders/" + sender_id + "status/" - ) - state = response.json()["status"]["state"] - if state in ["awating_essence", "no_essence"]: - time.sleep(3000) - else: - break - if state != "unconstrained": - return test.FAIL("inputs are unstable.") - - _, response = TestHelper.do_request( - "GET", self.node_url + "senders/" + sender_id - ) - if response.status_code != 200: - return test.FAIL(response.json) - sender = response.json() - - _, response = TestHelper.do_request( - "GET", self.node_url + "flows/" + sender["flow_id"] - ) - if response.status_code != 200: - return test.FAIL("The request has not succeeded: {}".format(response.json())) - flow = response.json() - color_sampling = self.getSdpColorSampling(flow["components"]) - if color_sampling == "invalid array of video components": - return test.FAIL("invalid array of video components") - constraint_set = {} - - for item in REF_SUPPORTED_CONSTRAINTS_VIDEO: - try: - if item == "urn:x-nmos:cap:meta:label": - constraint_set["urn:x-nmos:cap:meta:label"] = "video constraint" - if item == "urn:x-nmos:cap:meta:preference": - constraint_set["urn:x-nmos:cap:meta:preference"] = 0 - if item == "urn:x-nmos:cap:meta:enabled": - 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"]] - } - if item == "urn:x-nmos:cap:format:grain_rate": - constraint_set["urn:x-nmos:cap:format:grain_rate"] = { - "enum": [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"]] - } - if item == "urn:x-nmos:cap:format:frame_height": - constraint_set["urn:x-nmos:cap:format:frame_height"] = { - "enum": [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"]] - } - if item == "urn:x-nmos:cap:format:color_sampling": - constraint_set["urn:x-nmos:cap:format:color_sampling"] = { - "enum": [color_sampling] - } - if item == "urn:x-nmos:cap:format:component_depth": - constraint_set["urn:x-nmos:cap:format:component_depth"] = { - "enum": [flow["components"][0]["bit_depth"]] - } - except Exception: - pass - - self.constraints[sender_id] = {"constraint_sets": [constraint_set]} - - _, response = TestHelper.do_request( - "PUT", - self.compat_url + "senders/" + sender_id + "constraints/active/", - json=self.constraints[sender_id], - ) - if response.status_code != 200: - return test.FAIL("The request has not succeeded: {}".format(response.json())) - - _, response = TestHelper.do_request( - "GET", self.node_url + "flows/" + sender["flow_id"] - ) - if response.status_code != 200: - return test.FAIL("The request has not succeeded: {}".format(response.json())) - new_flow = response.json() - - new_color_sampling = self.getSdpColorSampling(new_flow["components"]) - if new_color_sampling == "invalid array of video components": - return test.FAIL("invalid array of video components") - - 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"]: - return test.FAIL("different media_type") - if item == "urn:x-nmos:cap:format:grain_rate": - if 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"]: - return test.FAIL("different frame_width") - if item == "urn:x-nmos:cap:format:frame_height": - if 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"]: - 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"] - != new_flow["components"][0]["bit_depth"] - ): - return test.FAIL("different component_depth") - except Exception: - pass - _, response = TestHelper.do_request( - "DELETE", - self.compat_url + "senders/" + sender_id + "constraints/active/", - ) - if response.status_code != 200: - return test.FAIL("The request has not succeeded: {}".format(response.json())) - return test.PASS() - - def test_02_02_06_02(self, test): - """ - Verify that setting NOP constraints for supported - constraints doesn't change the flow of a sender(audio). - """ - if len(self.flow_format_audio) == 0: - return test.UNCLEAR("There is no audio format.") - for sender_id in self.flow_format_audio: - _, response = TestHelper.do_request( - "GET", self.compat_url + "senders/" + sender_id + "status/" - ) - if response.status_code != 200: - test.FAIL("The request has not succeeded: {}".format(response.json())) - state = response.json()["state"] - if state in ["awating_essence", "no_essence"]: - for i in range(0, 5): - _, response = TestHelper.do_request( - "GET", self.compat_url + "senders/" + sender_id + "status/" - ) - state = response.json()["status"]["state"] - if state in ["awating_essence", "no_essence"]: - time.sleep(3000) - else: - break - if state != "unconstrained": - return test.FAIL("inputs are unstable.") - _, response = TestHelper.do_request( - "GET", self.node_url + "senders/" + sender_id - ) - if response.status_code != 200: - return test.FAIL(response.json) - sender = response.json() - - _, response = TestHelper.do_request( - "GET", self.node_url + "flows/" + sender["flow_id"] - ) - if response.status_code != 200: - return test.FAIL("The request has not succeeded: {}".format(response.json())) - flow = response.json() - constraint_set = {} - - _, response = TestHelper.do_request( - "GET", self.node_url + "sources/" + flow["source_id"] - ) - if response.status_code != 200: - return test.FAIL("The request has not succeeded: {}".format(response.json())) - source = response.json() - - for item in REF_SUPPORTED_CONSTRAINTS_AUDIO: - try: - - if item == "urn:x-nmos:cap:meta:label": - constraint_set["urn:x-nmos:cap:meta:label"] = "audio constraint" - if item == "urn:x-nmos:cap:meta:preference": - constraint_set["urn:x-nmos:cap:meta:preference"] = 0 - if item == "urn:x-nmos:cap:meta:enabled": - 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"]] - } - if item == "urn:x-nmos:cap:format:sample_rate": - constraint_set["urn:x-nmos:cap:format:sample_rate"] = { - "enum": [flow["sample_rate"]] - } - if item == "urn:x-nmos:cap:format:channel_count": - constraint_set["urn:x-nmos:cap:format:channel_count"] = { - "enum": [len(source["channels"])] - } - if item == "urn:x-nmos:cap:format:sample_depth": - constraint_set["urn:x-nmos:cap:format:sample_depth"] = { - "enum": [flow["bit_depth"]] - } - except Exception: - pass - self.constraints[sender_id] = {"constraint_sets": [constraint_set]} - _, response = TestHelper.do_request( - "PUT", - self.compat_url + "senders/" + sender_id + "constraints/active/", - json=self.constraints[sender_id], - ) - if response.status_code != 200: - return test.FAIL("The request has not succeeded: {}".format(response.json())) - - _, response = TestHelper.do_request( - "GET", self.node_url + "flows/" + sender["flow_id"] - ) - if response.status_code != 200: - return test.FAIL("The request has not succeeded: {}".format(response.json())) - new_flow = response.json() - - _, response = TestHelper.do_request( - "GET", self.node_url + "sources/" + flow["source_id"] - ) - if response.status_code != 200: - return test.FAIL("The request has not succeeded: {}".format(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"]: - return test.FAIL("different media_type") - if item == "urn:x-nmos:cap:format:sample_rate": - if 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"]: - return test.FAIL("different sample_depth") - except Exception: - pass - _, response = TestHelper.do_request( - "DELETE", - self.compat_url + "senders/" + sender_id + "constraints/active/", - ) - if response.status_code != 200: - return test.FAIL("The request has not succeeded: {}".format(response.json())) - return test.PASS() - - def test_02_02_07_01(self, test): - "Verify that the device adhere to the preference of the constraint_set." - if len(self.flow_format_video) == 0: - return test.UNCLEAR("There is no video format.") - - for sender_id in self.flow_format_video: - _, response = TestHelper.do_request( - "GET", self.compat_url + "senders/" + sender_id + "status/" - ) - if response.status_code != 200: - return test.FAIL("The request has not succeeded: {}".format(response.json())) - state = response.json()["state"] - if state in ["awating_essence", "no_essence"]: - for i in range(0, 5): - _, response = TestHelper.do_request( - "GET", self.compat_url + "senders/" + sender_id + "status/" - ) - state = response.json()["status"]["state"] - if state in ["awating_essence", "no_essence"]: - time.sleep(3000) - else: - break - if state != "unconstrained": - return test.FAIL("inputs are unstable.") - - _, response = TestHelper.do_request( - "GET", self.node_url + "senders/" + sender_id - ) - if response.status_code != 200: - return test.FAIL(response.json) - sender = response.json() - - _, response = TestHelper.do_request( - "GET", self.node_url + "flows/" + sender["flow_id"] - ) - if response.status_code != 200: - return test.FAIL("The request has not succeeded: {}".format(response.json())) - flow = response.json() - color_sampling = self.getSdpColorSampling(flow["components"]) - if color_sampling == "invalid array of video components": - return test.FAIL("invalid array of video components") - constraint_set0 = {} - constraint_set1 = {} - - for item in REF_SUPPORTED_CONSTRAINTS_VIDEO: - try: - - if item == "urn:x-nmos:cap:meta:label": - constraint_set0[ - "urn:x-nmos:cap:meta:label" - ] = "video constraint" - if item == "urn:x-nmos:cap:meta:preference": - constraint_set0["urn:x-nmos:cap:meta:preference"] = 0 - if item == "urn:x-nmos:cap:meta:enabled": - 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"]] - } - if item == "urn:x-nmos:cap:format:grain_rate": - constraint_set0["urn:x-nmos:cap:format:grain_rate"] = { - "enum": [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"]] - } - if item == "urn:x-nmos:cap:format:frame_height": - constraint_set0["urn:x-nmos:cap:format:frame_height"] = { - "enum": [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"]] - } - if item == "urn:x-nmos:cap:format:color_sampling": - constraint_set0["urn:x-nmos:cap:format:color_sampling"] = { - "enum": [color_sampling] - } - if item == "urn:x-nmos:cap:format:component_depth": - constraint_set0["urn:x-nmos:cap:format:component_depth"] = { - "enum": [flow["components"][0]["bit_depth"]] - } - except Exception: - pass - - for item in REF_SUPPORTED_CONSTRAINTS_VIDEO: - try: - if item == "urn:x-nmos:cap:meta:label": - constraint_set1[ - "urn:x-nmos:cap:meta:label" - ] = "video constraint" - if item == "urn:x-nmos:cap:meta:preference": - constraint_set1["urn:x-nmos:cap:meta:preference"] = -100 - if item == "urn:x-nmos:cap:meta:enabled": - 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"]] - } - 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"])] - } - if item == "urn:x-nmos:cap:format:frame_width": - constraint_set1["urn:x-nmos:cap:format:frame_width"] = { - "enum": [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"]] - } - if item == "urn:x-nmos:cap:format:interlace_mode": - constraint_set1["urn:x-nmos:cap:format:interlace_mode"] = { - "enum": [flow["interlace_mode"]] - } - if item == "urn:x-nmos:cap:format:color_sampling": - constraint_set1["urn:x-nmos:cap:format:color_sampling"] = { - "enum": [color_sampling] - } - if item == "urn:x-nmos:cap:format:component_depth": - constraint_set1["urn:x-nmos:cap:format:component_depth"] = { - "enum": [flow["components"][0]["bit_depth"]] - } - except Exception: - pass - - self.constraints[sender_id] = { - "constraint_sets": [constraint_set0, constraint_set0] - } - _, response = TestHelper.do_request( - "PUT", - self.compat_url + "senders/" + sender_id + "constraints/active/", - json=self.constraints[sender_id], - ) - if response.status_code != 200: - return test.FAIL("The request has not succeeded: {}".format(response.json())) - - _, response = TestHelper.do_request( - "GET", self.node_url + "flows/" + sender["flow_id"] - ) - if response.status_code != 200: - return test.FAIL("The request has not succeeded: {}".format(response.json())) - new_flow = response.json() - - new_color_sampling = self.getSdpColorSampling(new_flow["components"]) - if new_color_sampling == "invalid array of video components": - return test.FAIL("invalid array of video components") - - 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"]: - return test.FAIL("different media_type") - if item == "urn:x-nmos:cap:format:grain_rate": - if 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"]: - return test.FAIL("different frame_width") - if item == "urn:x-nmos:cap:format:frame_height": - if 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"]: - 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"] - != new_flow["components"][0]["bit_depth"] - ): - return test.FAIL("different component_depth") - except Exception: - pass - - _, response = TestHelper.do_request( - "DELETE", - self.compat_url + "senders/" + sender_id + "constraints/active/", - ) - if response.status_code != 200: - return test.FAIL("The request has not succeeded: {}".format(response.json())) - return test.PASS() - - def test_02_02_07_02(self, test): - "Verify that the device adhere to the preference of the constraint_set." - if len(self.flow_format_audio) == 0: - return test.UNCLEAR("There is no audio format.") - - for sender_id in self.flow_format_audio: - _, response = TestHelper.do_request( - "GET", self.compat_url + "senders/" + sender_id + "status/" - ) - if response.status_code != 200: - test.FAIL("The request has not succeeded: {}".format(response.json())) - state = response.json()["state"] - if state in ["awating_essence", "no_essence"]: - for i in range(0, 5): - _, response = TestHelper.do_request( - "GET", self.compat_url + "senders/" + sender_id + "status/" - ) - state = response.json()["status"]["state"] - if state in ["awating_essence", "no_essence"]: - time.sleep(3000) - else: - break - if state != "unconstrained": - return test.FAIL("inputs are unstable.") - - _, response = TestHelper.do_request( - "GET", self.node_url + "senders/" + sender_id - ) - if response.status_code != 200: - return test.FAIL(response.json) - sender = response.json() - - _, response = TestHelper.do_request( - "GET", self.node_url + "flows/" + sender["flow_id"] - ) - if response.status_code != 200: - return test.FAIL("The request has not succeeded: {}".format(response.json())) - flow = response.json() - _, response = TestHelper.do_request( - "GET", self.node_url + "sources/" + flow["source_id"] - ) - if response.status_code != 200: - return test.FAIL("The request has not succeeded: {}".format(response.json())) - source = response.json() - - constraint_set0 = {} - constraint_set1 = {} - - for item in REF_SUPPORTED_CONSTRAINTS_AUDIO: - try: - if item == "urn:x-nmos:cap:meta:label": - constraint_set0[ - "urn:x-nmos:cap:meta:label" - ] = "audio constraint" - if item == "urn:x-nmos:cap:meta:preference": - constraint_set0["urn:x-nmos:cap:meta:preference"] = 0 - if item == "urn:x-nmos:cap:meta:enabled": - 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"]] - } - if item == "urn:x-nmos:cap:format:sample_rate": - constraint_set0["urn:x-nmos:cap:format:sample_rate"] = { - "enum": [flow["sample_rate"]] - } - if item == "urn:x-nmos:cap:format:channel_count": - constraint_set0["urn:x-nmos:cap:format:channel_count"] = { - "enum": [len(source["channels"])] - } - if item == "urn:x-nmos:cap:format:sample_depth": - constraint_set0["urn:x-nmos:cap:format:sample_depth"] = { - "enum": [flow["bit_depth"]] - } - except Exception: - pass - - for item in REF_SUPPORTED_CONSTRAINTS_AUDIO: - try: - if item == "urn:x-nmos:cap:meta:label": - constraint_set1[ - "urn:x-nmos:cap:meta:label" - ] = "video constraint" - if item == "urn:x-nmos:cap:meta:preference": - constraint_set1["urn:x-nmos:cap:meta:preference"] = -100 - if item == "urn:x-nmos:cap:meta:enabled": - 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"]] - } - 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"])] - } - if item == "urn:x-nmos:cap:format:channel_count": - constraint_set1["urn:x-nmos:cap:format:channel_count"] = { - "enum": [len(source["channels"])] - } - if item == "urn:x-nmos:cap:format:sample_depth": - constraint_set1["urn:x-nmos:cap:format:sample_depth"] = { - "enum": [flow["bit_depth"]] - } - except Exception: - pass - - self.constraints[sender_id] = { - "constraint_sets": [constraint_set0, constraint_set1] - } - - _, response = TestHelper.do_request( - "PUT", - self.compat_url + "senders/" + sender_id + "constraints/active/", - json=self.constraints[sender_id], - ) - if response.status_code != 200: - return test.FAIL("The request has not succeeded: {}".format(response.json())) - _, response = TestHelper.do_request( - "GET", self.node_url + "flows/" + sender["flow_id"] - ) - if response.status_code != 200: - return test.FAIL("The request has not succeeded: {}".format(response.json())) - new_flow = response.json() - - _, response = TestHelper.do_request( - "GET", self.node_url + "sources/" + flow["source_id"] - ) - if response.status_code != 200: - return test.FAIL("The request has not succeeded: {}".format(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"]: - return test.FAIL("different media_type") - if item == "urn:x-nmos:cap:format:sample_rate": - if 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"]: - return test.FAIL("different sample_depth") - except Exception: - pass - _, response = TestHelper.do_request( - "DELETE", - self.compat_url + "senders/" + sender_id + "constraints/active/", - ) - if response.status_code != 200: - return test.FAIL("The request has not succeeded: {}".format(response.json())) - return test.PASS() From 89416693427a61823bef1f11ce53dfeb31d39fce Mon Sep 17 00:00:00 2001 From: ggeorgea Date: Mon, 17 Apr 2023 09:53:44 -0400 Subject: [PATCH 06/11] Fix flake8 error --- nmostesting/suites/IS1101Test.py | 1 + 1 file changed, 1 insertion(+) diff --git a/nmostesting/suites/IS1101Test.py b/nmostesting/suites/IS1101Test.py index d3d45330..ab4041ab 100644 --- a/nmostesting/suites/IS1101Test.py +++ b/nmostesting/suites/IS1101Test.py @@ -46,6 +46,7 @@ "urn:x-nmos:cap:format:sample_depth", ] + class IS1101Test(GenericTest): """ Runs Node Tests covering IS-11 From 23c6274fce3c9a587c8b383cd612ad8bc783e817 Mon Sep 17 00:00:00 2001 From: ggeorgea Date: Tue, 18 Apr 2023 11:36:15 -0400 Subject: [PATCH 07/11] - Removing IS1102Test.py that was reintroduced by GitHub fixing merge conflicts. - Fix 4800 typo changing it to 48000 --- nmostesting/suites/IS1101Test.py | 2 +- nmostesting/suites/IS1102Test.py | 34 -------------------------------- 2 files changed, 1 insertion(+), 35 deletions(-) delete mode 100644 nmostesting/suites/IS1102Test.py diff --git a/nmostesting/suites/IS1101Test.py b/nmostesting/suites/IS1101Test.py index ab4041ab..61b7f1c6 100644 --- a/nmostesting/suites/IS1101Test.py +++ b/nmostesting/suites/IS1101Test.py @@ -190,7 +190,7 @@ def get_another_sample_rate(self, sample_rate): if numerator == 44100: return {"numerator": 48000} if numerator == 96000: - return {"numerator": 4800} + return {"numerator": 48000} if numerator == 88200: return {"numerator": 44100} return "sample_rate not valid" diff --git a/nmostesting/suites/IS1102Test.py b/nmostesting/suites/IS1102Test.py deleted file mode 100644 index 171983ab..00000000 --- a/nmostesting/suites/IS1102Test.py +++ /dev/null @@ -1,34 +0,0 @@ -# Copyright (C) 2022 Advanced Media Workflow Association -# -# 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. - -from ..GenericTest import GenericTest - -NODE_API_KEY = "node" -COMPAT_API_KEY = "streamcompatibility" - - -class IS1102Test(GenericTest): - """ - Runs Node Tests covering both IS-04 and IS-11 - """ - def __init__(self, apis, **kwargs): - # Don't auto-test paths responding with an EDID binary as they don't have a JSON Schema - omit_paths = [ - "/inputs/{inputId}/edid/base", - "/inputs/{inputId}/edid/effective", - "/outputs/{outputId}/edid" - ] - GenericTest.__init__(self, apis, omit_paths, **kwargs) - self.node_url = self.apis[NODE_API_KEY]["url"] - self.compat_url = self.apis[COMPAT_API_KEY]["url"] From 9e87e7862ff72e3ce4460f6e1e986e6c59456636 Mon Sep 17 00:00:00 2001 From: ggeorgea Date: Wed, 26 Apr 2023 17:51:17 -0400 Subject: [PATCH 08/11] - Removing non-json fix, replacing it with missing omit path for SDP files - Fix Test status with regards to IS-11 API not being present --- nmostesting/GenericTest.py | 16 ++++++++-------- nmostesting/TestHelper.py | 7 +++---- nmostesting/suites/IS0401Test.py | 2 +- nmostesting/suites/IS0501Test.py | 2 +- nmostesting/suites/IS1101Test.py | 4 ++-- 5 files changed, 15 insertions(+), 16 deletions(-) diff --git a/nmostesting/GenericTest.py b/nmostesting/GenericTest.py index 3c7ee32e..cb870f2d 100644 --- a/nmostesting/GenericTest.py +++ b/nmostesting/GenericTest.py @@ -341,13 +341,13 @@ def check_response(self, schema, method, response): cors_valid, cors_message = self.check_CORS(method, response.headers) if not cors_valid: return False, cors_message - if response.headers["Content-Type"] == "application/json": - try: - self.validate_schema(response.json(), schema) - except jsonschema.ValidationError: - return False, "Response schema validation error" - except json.JSONDecodeError: - return False, "Invalid JSON received" + + try: + self.validate_schema(response.json(), schema) + except jsonschema.ValidationError: + return False, "Response schema validation error" + except json.JSONDecodeError: + return False, "Invalid JSON received" return True, ctype_message @@ -668,7 +668,7 @@ def check_api_resource(self, test, resource, response_code, api, path): return True, "" # For all other methods proceed to check the response against the schema schema = self.get_schema(api, resource[1]["method"], resource[0], response.status_code) - if not schema and response.headers["Content-Type"] == 'application/json': + if not schema: raise NMOSTestException(test.MANUAL("Test suite unable to locate schema")) return self.check_response(schema, resource[1]["method"], response) diff --git a/nmostesting/TestHelper.py b/nmostesting/TestHelper.py index db8de1e2..50cc3fb3 100644 --- a/nmostesting/TestHelper.py +++ b/nmostesting/TestHelper.py @@ -264,18 +264,17 @@ def loader(uri): return schema -def check_content_type(headers, expected_type=["application/json", "application/sdp", "application/octet-stream"]): +def check_content_type(headers, expected_type="application/json"): """Check the Content-Type header of an API request or response""" if "Content-Type" not in headers: return False, "API failed to signal a Content-Type." else: ctype = headers["Content-Type"] ctype_params = ctype.split(";") - is_expected_type = ctype_params[0] in expected_type - if not is_expected_type: + if ctype_params[0] != expected_type: return False, "API signalled a Content-Type of {} rather than {}." \ .format(ctype, expected_type) - elif is_expected_type: + elif ctype_params[0] in ["application/json", "application/sdp"]: if len(ctype_params) == 2 and ctype_params[1].strip().lower() == "charset=utf-8": return True, "API signalled an unnecessary 'charset' in its Content-Type: {}" \ .format(ctype) diff --git a/nmostesting/suites/IS0401Test.py b/nmostesting/suites/IS0401Test.py index 30c27c46..e7482821 100644 --- a/nmostesting/suites/IS0401Test.py +++ b/nmostesting/suites/IS0401Test.py @@ -1410,7 +1410,7 @@ def test_20_01(self, test): headers = None valid, response = self.do_request("GET", href, headers=headers) if valid and response.status_code == 200: - valid, message = check_content_type(response.headers, ["application/sdp"]) + valid, message = check_content_type(response.headers, "application/sdp") if not content_type_warn and (not valid or message != ""): content_type_warn = message elif valid and response.status_code == 404: diff --git a/nmostesting/suites/IS0501Test.py b/nmostesting/suites/IS0501Test.py index 6348ea63..b5a00de9 100644 --- a/nmostesting/suites/IS0501Test.py +++ b/nmostesting/suites/IS0501Test.py @@ -1026,7 +1026,7 @@ def test_42(self, test): url = self.url + "single/senders/{}/transportfile".format(sender) valid, response = self.do_request("GET", url) if valid and response.status_code == 200: - valid, message = check_content_type(response.headers, ["application/sdp"]) + valid, message = check_content_type(response.headers, "application/sdp") if valid and message != "": return test.FAIL(message) elif not valid: diff --git a/nmostesting/suites/IS1101Test.py b/nmostesting/suites/IS1101Test.py index 61b7f1c6..f430bc84 100644 --- a/nmostesting/suites/IS1101Test.py +++ b/nmostesting/suites/IS1101Test.py @@ -108,7 +108,7 @@ def test_00_01(self, test): if not NMOSUtils.compare_urls(control_href, self.compat_url): return test.FAIL("IS-11 URL is invalid") return test.PASS() - return test.FAIL("IS-11 API is not available") + return test.WARNING("IS-11 API is not available") def test_00_02(self, test): "Put all senders into inactive state" @@ -1720,5 +1720,5 @@ 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.UNCLEAR("The receiver does not support BCP-004-01.") + return test.WARNING("The receiver does not support BCP-004-01.") return test.PASS() From 2e9a8efb0d1ca6e114b985a396f22f6f43d6f27c Mon Sep 17 00:00:00 2001 From: ggeorgea Date: Wed, 26 Apr 2023 17:58:59 -0400 Subject: [PATCH 09/11] - Adding missing omit path "/single/senders/{senderId}/transportfile", --- nmostesting/suites/IS1101Test.py | 1 + 1 file changed, 1 insertion(+) diff --git a/nmostesting/suites/IS1101Test.py b/nmostesting/suites/IS1101Test.py index f430bc84..6ea7ae30 100644 --- a/nmostesting/suites/IS1101Test.py +++ b/nmostesting/suites/IS1101Test.py @@ -54,6 +54,7 @@ class IS1101Test(GenericTest): def __init__(self, apis, **kwargs): # Don't auto-test paths responding with an EDID binary as they don't have a JSON Schema omit_paths = [ + "/single/senders/{senderId}/transportfile", "/inputs/{inputId}/edid", "/inputs/{inputId}/edid/base", "/inputs/{inputId}/edid/effective", From 68e203d3aef54093ac47f4ec11418515a4a9e037 Mon Sep 17 00:00:00 2001 From: ggeorgea Date: Thu, 27 Apr 2023 09:50:01 -0400 Subject: [PATCH 10/11] Fix gareth final checks --- nmostesting/Config.py | 1 + nmostesting/GenericTest.py | 2 ++ nmostesting/IS04Utils.py | 56 ++++++++++++++++++++++++++++---------- nmostesting/TestHelper.py | 6 ++-- 4 files changed, 48 insertions(+), 17 deletions(-) diff --git a/nmostesting/Config.py b/nmostesting/Config.py index fcf898e9..ab040d44 100644 --- a/nmostesting/Config.py +++ b/nmostesting/Config.py @@ -186,6 +186,7 @@ # Bash shell to use for running testssl.sh TEST_SSL_BASH = "bash" + # Definition of each API specification and its versions. SPECIFICATIONS = { "is-04": { diff --git a/nmostesting/GenericTest.py b/nmostesting/GenericTest.py index cb870f2d..951eb1fa 100644 --- a/nmostesting/GenericTest.py +++ b/nmostesting/GenericTest.py @@ -666,8 +666,10 @@ def check_api_resource(self, test, resource, response_code, api, path): elif resource[1]['method'].upper() in ["HEAD", "OPTIONS"]: # For methods which don't return a payload, return immediately after the CORS header check return True, "" + # For all other methods proceed to check the response against the schema schema = self.get_schema(api, resource[1]["method"], resource[0], response.status_code) + if not schema: raise NMOSTestException(test.MANUAL("Test suite unable to locate schema")) diff --git a/nmostesting/IS04Utils.py b/nmostesting/IS04Utils.py index 6d8ff2aa..840e17c5 100644 --- a/nmostesting/IS04Utils.py +++ b/nmostesting/IS04Utils.py @@ -11,6 +11,7 @@ # 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 re from copy import deepcopy from fractions import Fraction @@ -168,9 +169,7 @@ def make_sampling(flow_components): def downgrade_resource(resource_type, resource_data, requested_version): """Downgrades given resource data to requested version""" - version_major, version_minor = [ - int(x) for x in requested_version[1:].split(".") - ] + version_major, version_minor = [int(x) for x in requested_version[1:].split(".")] data = deepcopy(resource_data) @@ -191,12 +190,19 @@ def downgrade_resource(resource_type, resource_data, requested_version): if key in endpoint: del endpoint[key] if version_minor <= 1: - keys_to_remove = ["interfaces"] + keys_to_remove = [ + "interfaces" + ] for key in keys_to_remove: if key in data: del data[key] if version_minor == 0: - keys_to_remove = ["api", "clocks", "description", "tags"] + keys_to_remove = [ + "api", + "clocks", + "description", + "tags" + ] for key in keys_to_remove: if key in data: del data[key] @@ -212,7 +218,11 @@ def downgrade_resource(resource_type, resource_data, requested_version): if version_minor <= 1: pass if version_minor == 0: - keys_to_remove = ["controls", "description", "tags"] + keys_to_remove = [ + "controls", + "description", + "tags" + ] for key in keys_to_remove: if key in data: del data[key] @@ -222,7 +232,11 @@ def downgrade_resource(resource_type, resource_data, requested_version): if version_minor <= 2: pass if version_minor <= 1: - keys_to_remove = ["caps", "interface_bindings", "subscription"] + keys_to_remove = [ + "caps", + "interface_bindings", + "subscription" + ] for key in keys_to_remove: if key in data: del data[key] @@ -234,7 +248,9 @@ def downgrade_resource(resource_type, resource_data, requested_version): if version_minor <= 2: pass if version_minor <= 1: - keys_to_remove = ["interface_bindings"] + keys_to_remove = [ + "interface_bindings" + ] for key in keys_to_remove: if key in data: del data[key] @@ -246,14 +262,20 @@ def downgrade_resource(resource_type, resource_data, requested_version): elif resource_type == "source": if version_minor <= 2: - keys_to_remove = ["event_type"] + keys_to_remove = [ + "event_type" + ] for key in keys_to_remove: if key in data: del data[key] if version_minor <= 1: pass if version_minor == 0: - keys_to_remove = ["channels", "clock_name", "grain_rate"] + keys_to_remove = [ + "channels", + "clock_name", + "grain_rate" + ] for key in keys_to_remove: if key in data: del data[key] @@ -261,7 +283,9 @@ def downgrade_resource(resource_type, resource_data, requested_version): elif resource_type == "flow": if version_minor <= 2: - keys_to_remove = ["event_type"] + keys_to_remove = [ + "event_type" + ] for key in keys_to_remove: if key in data: del data[key] @@ -280,7 +304,7 @@ def downgrade_resource(resource_type, resource_data, requested_version): "interlace_mode", "media_type", "sample_rate", - "transfer_characteristic", + "transfer_characteristic" ] for key in keys_to_remove: if key in data: @@ -289,14 +313,18 @@ def downgrade_resource(resource_type, resource_data, requested_version): elif resource_type == "subscription": if version_minor <= 2: - keys_to_remove = ["authorization"] + keys_to_remove = [ + "authorization" + ] for key in keys_to_remove: if key in data: del data[key] if version_minor <= 1: pass if version_minor == 0: - keys_to_remove = ["secure"] + keys_to_remove = [ + "secure" + ] for key in keys_to_remove: if key in data: del data[key] diff --git a/nmostesting/TestHelper.py b/nmostesting/TestHelper.py index 50cc3fb3..588d9c3f 100644 --- a/nmostesting/TestHelper.py +++ b/nmostesting/TestHelper.py @@ -264,17 +264,17 @@ def loader(uri): return schema -def check_content_type(headers, expected_type="application/json"): +def check_content_type(headers, expected_type="application/json"): """Check the Content-Type header of an API request or response""" if "Content-Type" not in headers: return False, "API failed to signal a Content-Type." else: ctype = headers["Content-Type"] ctype_params = ctype.split(";") - if ctype_params[0] != expected_type: + if ctype_params[0] != expected_type: return False, "API signalled a Content-Type of {} rather than {}." \ .format(ctype, expected_type) - elif ctype_params[0] in ["application/json", "application/sdp"]: + elif ctype_params[0] in ["application/json", "application/sdp"]: if len(ctype_params) == 2 and ctype_params[1].strip().lower() == "charset=utf-8": return True, "API signalled an unnecessary 'charset' in its Content-Type: {}" \ .format(ctype) From c7b3680f01d1b4f99909f9573dcbabcf83176075 Mon Sep 17 00:00:00 2001 From: Gareth Sylvester-Bradley Date: Thu, 27 Apr 2023 15:51:44 +0100 Subject: [PATCH 11/11] Fix mixed EOL --- nmostesting/suites/IS0401Test.py | 2 +- nmostesting/suites/IS0501Test.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/nmostesting/suites/IS0401Test.py b/nmostesting/suites/IS0401Test.py index e7482821..0a0a3fa9 100644 --- a/nmostesting/suites/IS0401Test.py +++ b/nmostesting/suites/IS0401Test.py @@ -1410,7 +1410,7 @@ def test_20_01(self, test): headers = None valid, response = self.do_request("GET", href, headers=headers) if valid and response.status_code == 200: - valid, message = check_content_type(response.headers, "application/sdp") + valid, message = check_content_type(response.headers, "application/sdp") if not content_type_warn and (not valid or message != ""): content_type_warn = message elif valid and response.status_code == 404: diff --git a/nmostesting/suites/IS0501Test.py b/nmostesting/suites/IS0501Test.py index b5a00de9..81f413c9 100644 --- a/nmostesting/suites/IS0501Test.py +++ b/nmostesting/suites/IS0501Test.py @@ -1026,7 +1026,7 @@ def test_42(self, test): url = self.url + "single/senders/{}/transportfile".format(sender) valid, response = self.do_request("GET", url) if valid and response.status_code == 200: - valid, message = check_content_type(response.headers, "application/sdp") + valid, message = check_content_type(response.headers, "application/sdp") if valid and message != "": return test.FAIL(message) elif not valid: