From 3ade0572e2924e57948885b6ad74d19cc7e32795 Mon Sep 17 00:00:00 2001 From: N-Nagorny <15526762+N-Nagorny@users.noreply.github.com> Date: Tue, 14 May 2024 23:26:54 +0400 Subject: [PATCH 1/5] Refactor sections 2.1, 2.2, 2.4 --- nmostesting/IS11Utils.py | 9 +- nmostesting/suites/IS1101Test.py | 1873 +++++++++--------------------- 2 files changed, 558 insertions(+), 1324 deletions(-) diff --git a/nmostesting/IS11Utils.py b/nmostesting/IS11Utils.py index f8b5742d..e4af7fd8 100644 --- a/nmostesting/IS11Utils.py +++ b/nmostesting/IS11Utils.py @@ -97,7 +97,7 @@ def get_transportfile(self, url, sender_id): toReturn = r.text return toReturn - def get_flows(self, url, sender_id): + def get_flow(self, url, sender_id): """Get the flow for a given Sender""" toReturn = None valid, r = TestHelper.do_request("GET", url + "flows/" + sender_id) @@ -105,6 +105,13 @@ def get_flows(self, url, sender_id): toReturn = r.json() return toReturn + def get_source(self, url, id): + toReturn = None + valid, r = TestHelper.do_request("GET", url + "sources/" + id) + if valid and r.status_code == 200: + toReturn = r.json() + return toReturn + def get_receivers_with_or_without_outputs_id(self, receivers, format): self.receivers_with_or_without_outputs = [] for receiver_id in receivers: diff --git a/nmostesting/suites/IS1101Test.py b/nmostesting/suites/IS1101Test.py index 215c200e..ca1c8bb7 100644 --- a/nmostesting/suites/IS1101Test.py +++ b/nmostesting/suites/IS1101Test.py @@ -71,24 +71,11 @@ def __init__(self, apis, **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"] - self.connected_outputs = [] - self.not_edid_connected_outputs = [] - self.edid_connected_outputs = [] self.reference_senders = {} - self.flow = "" self.caps = "" - 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 = {} self.some_input = {} self.input_senders = [] self.not_active_connected_inputs = [] @@ -124,6 +111,9 @@ def set_up_tests(self): self.senders = self.is11_utils.get_senders() self.receivers = self.is11_utils.get_receivers() + self.video_senders = list(filter(self.has_sender_video_flow, self.senders)) + self.audio_senders = list(filter(self.has_sender_audio_flow, self.senders)) + self.receivers_with_outputs = list(filter(self.receiver_has_i_o, self.receivers)) self.receivers_without_outputs = list(set(self.receivers) - set(self.receivers_with_outputs)) @@ -542,1379 +532,314 @@ def get_another_sample_rate(self, sample_rate): return {"numerator": 44100} return "sample_rate not valid" - def test_02_00(self, test): - "Reset active constraints of all senders" - - if len(self.senders) > 0: - for sender_id in self.senders: - valid, response = self.do_request( - "DELETE", - self.build_constraints_active_url(sender_id), - ) - if not valid: - return test.FAIL("Unexpected response from the streamcompatibility API: {}".format(response)) - if response.status_code != 200: - return test.FAIL("The sender {} constraints cannot be deleted".format(sender_id)) - return test.PASS() - return test.UNCLEAR("There are no IS-11 senders") + def test_02_01_01(self, test): + """Senders shown in Stream Compatibility Management API match those shown in Node API""" - def test_02_01(self, test): - "Verify that the device supports the concept of IS-11 Sender" if len(self.senders) == 0: - return test.UNCLEAR("There are no IS-11 senders") - return test.PASS() + return test.UNCLEAR("Not tested. No Senders found via IS-11.") - def test_02_01_01(self, test): - "Verify that IS-11 Senders exist on the Node API as Senders" - if len(self.senders) > 0: - for sender_id in self.senders: - valid, response = self.do_request("GET", self.node_url + "senders/" + sender_id) - if not valid: - return test.FAIL("Unexpected response from the Node API: {}".format(response)) - if response.status_code != 200: - return test.FAIL( - "The sender {} is not available in the Node API response: {}".format(sender_id, response.json()) - ) - sender_node_id = response.json()["id"] - if sender_id != sender_node_id: - return test.FAIL("Senders {} and {} are different".format(sender_id, sender_node_id)) - return test.PASS() - return test.UNCLEAR("There are no IS-11 senders") + is04_senders = self.is04_utils.get_senders() + + if not set(self.senders).issubset(is04_senders): + return test.FAIL("Unable to find all Senders from IS-11 in IS-04") - def test_02_02(self, test): - "Verify senders (generic with/without inputs)" - if len(self.senders) == 0: - return test.UNCLEAR("There are 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) > 0: - for sender_id in self.senders: - valid, response = self.do_request( - "GET", self.build_sender_status_url(sender_id) - ) - if not valid: - return test.FAIL("Unexpected response from the Stream Compatibility Management API: {}" - .format(response)) - if response.status_code != 200: - return test.FAIL( - "The streamcompatibility request for sender {} status has failed: {}" - .format(sender_id, response.json()) - ) - try: - state = response.json()["state"] - except json.JSONDecodeError: - return test.FAIL("Non-JSON response returned from the Stream Compatibility Management API") - except KeyError as e: - return test.FAIL("Unable to find expected key: {}".format(e)) - if state in ["awaiting_essence", "no_essence"]: - for i in range(0, CONFIG.STABLE_STATE_ATTEMPTS): - valid, response = self.do_request( - "GET", self.build_sender_status_url(sender_id) - ) - if not valid: - return test.FAIL("Unexpected response from the streamcompatibility API: {}" - .format(response)) - if response.status_code != 200: - return test.FAIL( - "The streamcompatibility request for sender {} status has failed: {}" - .format(sender_id, response.json()) - ) - try: - state = response.json()["state"] - except json.JSONDecodeError: - return test.FAIL("Non-JSON response returned from the Stream Compatibility Management API") - except KeyError as e: - return test.FAIL("Unable to find expected key: {}".format(e)) + if len(self.senders) == 0: + return test.UNCLEAR("Not tested. No Senders found via IS-11.") - if state in ["awaiting_essence", "no_essence"]: - time.sleep(CONFIG.STABLE_STATE_DELAY) - else: - break - if state != "unconstrained": - return test.FAIL("Expected state of sender {} is \"unconstrained\", got \"{}\"" - .format(sender_id, state)) - return test.PASS() - return test.UNCLEAR("There are no IS-11 senders") + for sender_id in self.senders: + result = self.wait_until_true( + partial(self.is_sender_state_equal_to_expected, test, sender_id, "unconstrained") + ) + if not result: + return test.FAIL("Expected state of sender {} is \"unconstrained\"" + .format(sender_id)) - 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) > 0: - for sender_id in self.senders: - valid, response = self.do_request( - "GET", self.node_url + "senders/" + sender_id - ) - if not valid: - return test.FAIL("Unexpected response from the Node API: {}".format(response)) - if response.status_code != 200: - return test.FAIL( - "The sender {} is not available in the Node API response: {}".format(sender_id, response.json()) - ) - sender_node_id = response.json()["id"] - if sender_id != sender_node_id: - return test.FAIL("Senders {} and {} are different".format(sender_id, sender_node_id)) - 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) - ) - self.flow = self.is11_utils.get_flows(self.node_url, sender_flow_id) - flow_format = self.flow["format"] - self.flow_format[sender_id] = flow_format - if flow_format == "urn:x-nmos:format:video": - self.flow_format_video.append(sender_id) - self.flow_width[sender_id] = self.flow["frame_width"] - self.flow_height[sender_id] = self.flow["frame_height"] - self.flow_grain_rate[sender_id] = self.flow["grain_rate"] - if flow_format == "urn:x-nmos:format:audio": - self.flow_format_audio.append(sender_id) - self.flow_sample_rate[sender_id] = self.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 are no IS-11 senders") + return test.PASS() def test_02_02_03_01(self, test): - "Verify that the video sender supports the minimum set of video constraints" - - sample = "^urn:x-nmos:cap:" + "Video senders support the required parameter constraints" - if len(self.flow_format_video) == 0: - return test.UNCLEAR("There is no video format") - - for sender_id in self.flow_format_video: - valid, response = self.do_request( - "GET", - self.compat_url + "senders/" + sender_id + "/constraints/supported/", - ) - if not valid: - return test.FAIL("Unexpected response from the streamcompatibility API: {}".format(response)) - if response.status_code != 200: - return test.FAIL( - "The streamcompatibility request for sender {} constraints supported has failed: {}" - .format(sender_id, response.json()) - ) - supportedConstraints = response.json()["parameter_constraints"] - for item in supportedConstraints: - if not re.search(sample, 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() + return self.check_param_constraints(test, self.video_senders, REF_SUPPORTED_CONSTRAINTS_VIDEO) def test_02_02_03_02(self, test): - "Verify that the audio sender supports the minimum set of audio constraints" - - sample = "^urn:x-nmos:cap:" - - if len(self.flow_format_audio) == 0: - return test.UNCLEAR("There is no audio format") + "Audio senders support the required parameter constraints" - for sender_id in self.flow_format_audio: - valid, response = self.do_request( - "GET", - self.compat_url + "senders/" + sender_id + "/constraints/supported/", - ) - if not valid: - return test.FAIL("Unexpected response from the streamcompatibility API: {}".format(response)) - if response.status_code != 200: - return test.FAIL( - "The streamcompatibility request for sender {} constraints supported has failed: {}" - .format(sender_id, response.json()) - ) - supportedConstraints = response.json()["parameter_constraints"] - for item in supportedConstraints: - if not re.search(sample, 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() + return self.check_param_constraints(test, self.audio_senders, REF_SUPPORTED_CONSTRAINTS_AUDIO) 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. + IS-11 Video senders increment their version + after changing Active Constraints """ - if len(self.flow_format_video) == 0: - return test.UNCLEAR("There is no video format") - for sender_id in self.flow_format_video: - valid, response = self.do_request( - "GET", self.node_url + "senders/" + sender_id - ) - if not valid: - return test.FAIL("Unexpected response from the Node API: {}".format(response)) - if response.status_code != 200: - return test.FAIL( - "The sender {} is not available in the Node API response: {}".format(sender_id, 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": []} - valid, response = self.do_request( - "PUT", - self.build_constraints_active_url(sender_id), - json=self.grain_rate_constraints[sender_id], - ) - if not valid: - return test.FAIL("Unexpected response from the streamcompatibility API: {}".format(response)) - if response.status_code != 200: - return test.FAIL( - "The sender {} constraints change has failed: {}".format(sender_id, response.json()) - ) - valid, response = self.do_request( - "GET", self.node_url + "senders/" + sender_id - ) - if not valid: - return test.FAIL("Unexpected response from the Node API: {}".format(response)) - if response.status_code != 200: - return test.FAIL( - "The sender {} is not available in the Node API response: {}".format(sender_id, response.json()) - ) - version = response.json()["version"] - if version == self.version[sender_id]: - return test.FAIL("Versions {} and {} are different".format(version, self.version[sender_id])) - self.version[sender_id] = version - valid, response = self.do_request( - "GET", self.build_constraints_active_url(sender_id) - ) - if not valid: - return test.FAIL("Unexpected response from the streamcompatibility API: {}".format(response)) - if response.status_code != 200: - return test.FAIL( - "Contraints active request sender {} has failed: {}".format(sender_id, 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("The sender {} contraints are different".format(sender_id)) - valid, response = self.do_request( - "DELETE", - self.build_constraints_active_url(sender_id), - ) - if not valid: - return test.FAIL("Unexpected response from the streamcompatibility API: {}".format(response)) - if response.status_code != 200: - return test.FAIL( - "The sender {} constraints cannot be deleted".format(sender_id) - ) - valid, response = self.do_request( - "GET", self.node_url + "senders/" + sender_id - ) - if not valid: - return test.FAIL("Unexpected response from the Node API: {}".format(response)) - if response.status_code != 200: - return test.FAIL( - "The sender {} is not available in the Node API response: {}".format(sender_id, response.json()) - ) - version = response.json()["version"] - if version == self.version[sender_id]: - return test.FAIL("Versions {} and {} are different".format(version, self.version[sender_id])) - self.version[sender_id] = version - valid, response = self.do_request( - "GET", self.build_constraints_active_url(sender_id) - ) - if not valid: - return test.FAIL("Unexpected response from the streamcompatibility API: {}".format(response)) - if response.status_code != 200: - return test.FAIL( - "Contraints active request for sender {} has failed: {}".format(sender_id, response.json()) - ) - constraints = response.json() - if constraints != self.empty_constraints[sender_id]: - return test.FAIL("Contraints are different") - return test.PASS() + return self.check_snd_ver_after_active_constraints_put( + test, self.video_senders, "grain_rate", "urn:x-nmos:cap:format:grain_rate" + ) 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: - valid, response = self.do_request( - "GET", self.node_url + "senders/" + sender_id - ) - if not valid: - return test.FAIL("Unexpected response from the Node API: {}".format(response)) - if response.status_code != 200: - return test.FAIL( - "The sender {} is not available in the Node API response: {}".format(sender_id, 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]] - } + + return self.check_snd_ver_after_active_constraints_put( + test, self.audio_senders, "sample_rate", "urn:x-nmos:cap:format:sample_rate" + ) + + def test_02_02_05_01(self, test): + """ + Verify that setting no-op constraints for frame(width,height), + grain_rate doesn't change the flow of a sender(video). + """ + + def make_active_constraints(_, flow): + return { + "constraint_sets": [{ + "urn:x-nmos:cap:format:grain_rate": { + "enum": [flow["grain_rate"]] + }, + "urn:x-nmos:cap:format:frame_width": { + "enum": [flow["frame_width"]] + }, + "urn:x-nmos:cap:format:frame_height": { + "enum": [flow["frame_height"]] } - ] + }] } - self.empty_constraints[sender_id] = {"constraint_sets": []} - valid, response = self.do_request( - "PUT", - self.build_constraints_active_url(sender_id), - json=self.sample_rate_constraints[sender_id], - ) - if not valid: - return test.FAIL("Unexpected response from the streamcompatibility API: {}".format(response)) - if response.status_code != 200: - return test.FAIL( - "The sender {} constraints change has failed: {}".format(sender_id, response.json()) - ) - valid, response = self.do_request( - "GET", self.node_url + "senders/" + sender_id - ) - if not valid: - return test.FAIL("Unexpected response from the Node API: {}".format(response)) - if response.status_code != 200: - return test.FAIL( - "The sender {} is not available in the Node API response: {}".format(sender_id, response.json()) - ) - version = response.json()["version"] - if version == self.version[sender_id]: - return test.FAIL("Versions {} and {} are different".format(version, self.version[sender_id])) - self.version[sender_id] = version - valid, response = self.do_request( - "GET", self.build_constraints_active_url(sender_id) - ) - if not valid: - return test.FAIL("Unexpected response from the streamcompatibility API: {}".format(response)) - if response.status_code != 200: - return test.FAIL( - "Contraints active request for sender {} has failed: {}".format(sender_id, response.json()) - ) - constraints = response.json() + flow_attrs = ["grain_rate", "frame_width", "frame_height"] - if not IS04Utils.compare_constraint_sets( - constraints["constraint_sets"], - self.sample_rate_constraints[sender_id]["constraint_sets"], - ): - return test.FAIL("The constraint applied does not match the active" - "constraint retrieved from the sender {}".format(sender_id)) + return self.apply_nop_active_constraints(test, self.video_senders, make_active_constraints, [], flow_attrs) - valid, response = self.do_request( - "DELETE", - self.build_constraints_active_url(sender_id), - ) - if not valid: - return test.FAIL("Unexpected response from the streamcompatibility API: {}".format(response)) - if response.status_code != 200: - return test.FAIL( - "The sender {} constraints cannot be deleted".format(sender_id) - ) + def test_02_02_05_02(self, test): + """ + Verify that setting no-op constraints for sample_rate doesn't change the flow of a sender(audio). + """ - valid, response = self.do_request( - "GET", self.node_url + "senders/" + sender_id - ) - if not valid: - return test.FAIL("Unexpected response from the Node API: {}".format(response)) - if response.status_code != 200: - return test.FAIL( - "The sender {} is not available in the Node API response: {}".format(sender_id, response.json()) - ) - version = response.json()["version"] - if version == self.version[sender_id]: - return test.FAIL("Versions {} and {} are different".format(version, self.version[sender_id])) - self.version[sender_id] = version + def make_active_constraints(_, flow): + return { + "constraint_sets": [{ + "urn:x-nmos:cap:format:sample_rate": { + "enum": [flow["sample_rate"]] + } + }] + } - valid, response = self.do_request( - "GET", self.build_constraints_active_url(sender_id) - ) - if not valid: - return test.FAIL("Unexpected response from the streamcompatibility API: {}".format(response)) - if response.status_code != 200: - return test.FAIL( - "Contraints active request for sender {} has failed: {}".format(sender_id, response.json()) - ) - constraints = response.json() - if constraints != self.empty_constraints[sender_id]: - return test.FAIL("Contraints are different") - return test.PASS() + flow_attrs = ["sample_rate"] - def test_02_02_05_01(self, test): + return self.apply_nop_active_constraints(test, self.audio_senders, make_active_constraints, [], flow_attrs) + + def test_02_02_06_01(self, test): """ - Verify that setting no-op constraints for frame(width,height), - grain_rate doesn't change the flow of a sender(video). + Verify that setting no-op 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: - valid, response = self.do_request( - "GET", self.build_sender_status_url(sender_id) - ) - if not valid: - return test.FAIL("Unexpected response from the streamcompatibility API: {}".format(response)) - if response.status_code != 200: - return test.FAIL( - "The streamcompatibility request for sender {} status has failed: {}" - .format(sender_id, response.json()) + def make_active_constraints(_, flow): + color_sampling = IS04Utils.make_sampling(flow["components"]) + + if color_sampling is None: + raise NMOSTestException( + test.FAIL("Invalid array of video components") ) - state = response.json()["state"] - if state in ["awaiting_essence", "no_essence"]: - for i in range(0, CONFIG.STABLE_STATE_ATTEMPTS): - valid, response = self.do_request( - "GET", self.build_sender_status_url(sender_id) - ) - if not valid: - return test.FAIL("Unexpected response from the streamcompatibility API: {}".format(response)) - if response.status_code != 200: - return test.FAIL( - "The streamcompatibility request for sender {} status has failed: {}" - .format(sender_id, response.json()) - ) - state = response.json()["state"] - if state in ["awaiting_essence", "no_essence"]: - time.sleep(CONFIG.STABLE_STATE_DELAY) - else: - break - if state != "unconstrained": - return test.FAIL("Expected state of sender {} is \"unconstrained\", got \"{}\"" - .format(sender_id, state)) - self.constraints[sender_id] = { - "constraint_sets": [ - { - "urn:x-nmos:cap:format:grain_rate": { - "enum": [self.flow_grain_rate[sender_id]] - } + return { + "constraint_sets": [{ + "urn:x-nmos:cap:meta:label": "video constraint", + "urn:x-nmos:cap:meta:preference": 0, + "urn:x-nmos:cap:meta:enabled": True, + "urn:x-nmos:cap:format:media_type": { + "enum": [flow["media_type"]] }, - { - "urn:x-nmos:cap:format:frame_width": { - "enum": [self.flow_width[sender_id]] - } + "urn:x-nmos:cap:format:grain_rate": { + "enum": [flow["grain_rate"]] }, - { - "urn:x-nmos:cap:format:frame_height": { - "enum": [self.flow_height[sender_id]] - } + "urn:x-nmos:cap:format:frame_width": { + "enum": [flow["frame_width"]] }, - ] + "urn:x-nmos:cap:format:frame_height": { + "enum": [flow["frame_height"]] + }, + "urn:x-nmos:cap:format:interlace_mode": { + "enum": [flow["interlace_mode"]] + }, + "urn:x-nmos:cap:format:color_sampling": { + "enum": [color_sampling] + }, + "urn:x-nmos:cap:format:component_depth": { + "enum": [flow["components"][0]["bit_depth"]] + } + }] } - valid, response = self.do_request( - "PUT", - self.build_constraints_active_url(sender_id), - json=self.constraints[sender_id], - ) - if not valid: - return test.FAIL("Unexpected response from the streamcompatibility API: {}".format(response)) - if response.status_code != 200: - return test.FAIL( - "The sender {} constraints change has failed: {}" - .format(sender_id, response.json()) - ) - - valid, response = self.do_request( - "GET", self.build_sender_status_url(sender_id) - ) - if not valid: - return test.FAIL("Unexpected response from the streamcompatibility API: {}".format(response)) - if response.status_code != 200: - return test.FAIL( - "The streamcompatibility request for sender {} status has failed: {}" - .format(sender_id, response.json()) - ) - state = response.json()["state"] - - if state in ["awaiting_essence", "no_essence"]: - for i in range(0, CONFIG.STABLE_STATE_ATTEMPTS): - valid, response = self.do_request( - "GET", self.build_sender_status_url(sender_id) - ) - if not valid: - return test.FAIL("Unexpected response from the streamcompatibility API: {}".format(response)) - if response.status_code != 200: - return test.FAIL( - "The streamcompatibility request for sender {} status has failed: {}" - .format(sender_id, response.json()) - ) - state = response.json()["state"] - if state in ["awaiting_essence", "no_essence"]: - time.sleep(CONFIG.STABLE_STATE_DELAY) - else: - break - if state != "constrained": - return test.FAIL("Expected state of sender {} is \"constrained\", got \"{}\"".format(sender_id, state)) - - valid, response = self.do_request( - "GET", self.node_url + "senders/" + sender_id - ) - if not valid: - return test.FAIL("Unexpected response from the Node API: {}".format(response)) - if response.status_code != 200: - return test.FAIL( - "The sender {} is not available in the Node API response: {}".format(sender_id, response.json()) - ) - 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)) - self.flow = self.is11_utils.get_flows(self.node_url, sender_flow_id) - - if ( - self.flow_grain_rate[sender_id] != self.flow["grain_rate"] - or self.flow_width[sender_id] != self.flow["frame_width"] - or self.flow_height[sender_id] != self.flow["frame_height"] - ): - return test.FAIL( - "The constraints on frame_width, frame_height\ - and grain_rate were not expected to change the flow of sender(video) {}" - .format(sender_id) - ) - - valid, response = self.do_request( - "DELETE", - self.build_constraints_active_url(sender_id), - ) - if not valid: - return test.FAIL("Unexpected response from the streamcompatibility API: {}".format(response)) - if response.status_code != 200: - return test.FAIL( - "The sender {} constraints cannot be deleted".format(sender_id) - ) + flow_attrs = [ + "media_type", "grain_rate", + "frame_width", "frame_height", + "interlace_mode", "components" + ] - return test.PASS() + return self.apply_nop_active_constraints(test, self.video_senders, make_active_constraints, [], flow_attrs) - def test_02_02_05_02(self, test): + def test_02_02_06_02(self, test): """ - Verify that setting no-op constraints for sample_rate doesn't change the flow of a sender(audio). + Verify that setting no-op 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: - valid, response = self.do_request( - "GET", self.build_sender_status_url(sender_id) - ) - if not valid: - return test.FAIL("Unexpected response from the streamcompatibility API: {}".format(response)) - if response.status_code != 200: - return test.FAIL( - "The streamcompatibility request for sender {} status has failed: {}" - .format(sender_id, response.json()) - ) - state = response.json()["state"] - - if state in ["awaiting_essence", "no_essence"]: - for i in range(0, CONFIG.STABLE_STATE_ATTEMPTS): - valid, response = self.do_request( - "GET", self.build_sender_status_url(sender_id) - ) - if not valid: - return test.FAIL("Unexpected response from the streamcompatibility API: {}".format(response)) - if response.status_code != 200: - return test.FAIL( - "The streamcompatibility request for sender {} status has failed: {}" - .format(sender_id, response.json()) - ) - state = response.json()["state"] - if state in ["awaiting_essence", "no_essence"]: - time.sleep(CONFIG.STABLE_STATE_DELAY) - else: - break - if state != "unconstrained": - return test.FAIL("Expected state of sender {} is \"unconstrained\", got \"{}\"" - .format(sender_id, state)) - - self.constraints[sender_id] = { - "constraint_sets": [ - { - "urn:x-nmos:cap:format:sample_rate": { - "enum": [self.flow_sample_rate[sender_id]] - } + def make_active_constraints(source, flow): + return { + "constraint_sets": [{ + "urn:x-nmos:cap:meta:label": "audio constraint", + "urn:x-nmos:cap:meta:preference": 0, + "urn:x-nmos:cap:meta:enabled": True, + "urn:x-nmos:cap:format:media_type": { + "enum": [flow["media_type"]] + }, + "urn:x-nmos:cap:format:sample_rate": { + "enum": [flow["sample_rate"]] + }, + "urn:x-nmos:cap:format:channel_count": { + "enum": [len(source["channels"])] + }, + "urn:x-nmos:cap:format:sample_depth": { + "enum": [flow["bit_depth"]] } - ] + }] } - valid, response = self.do_request( - "PUT", - self.build_constraints_active_url(sender_id), - json=self.constraints[sender_id], - ) - if not valid: - return test.FAIL("Unexpected response from the streamcompatibility API: {}".format(response)) - if response.status_code != 200: - return test.FAIL( - "The sender {} constraints change has failed: {}" - .format(sender_id, response.json()) - ) - valid, response = self.do_request( - "GET", self.build_sender_status_url(sender_id) - ) - if not valid: - return test.FAIL("Unexpected response from the streamcompatibility API: {}".format(response)) - if response.status_code != 200: - return test.FAIL( - "The streamcompatibility request for sender {} status has failed: {}" - .format(sender_id, response.json()) - ) - state = response.json()["state"] + source_attrs = ["channels"] + flow_attrs = ["media_type", "sample_rate", "bit_depth"] - if state in ["awaiting_essence", "no_essence"]: - for i in range(0, CONFIG.STABLE_STATE_ATTEMPTS): - valid, response = self.do_request( - "GET", self.build_sender_status_url(sender_id) - ) - if not valid: - return test.FAIL("Unexpected response from the streamcompatibility API: {}".format(response)) - if response.status_code != 200: - return test.FAIL( - "The streamcompatibility request for sender {} status has failed: {}" - .format(sender_id, response.json()) - ) - state = response.json()["state"] - if state in ["awaiting_essence", "no_essence"]: - time.sleep(CONFIG.STABLE_STATE_DELAY) - else: - break - if state != "constrained": - return test.FAIL("Expected state of sender {} is \"constrained\", got \"{}\"".format(sender_id, state)) + return self.apply_nop_active_constraints( + test, self.audio_senders, make_active_constraints, source_attrs, flow_attrs + ) - valid, response = self.do_request( - "GET", self.node_url + "senders/" + sender_id - ) - if not valid: - return test.FAIL("Unexpected response from the Node API: {}".format(response)) - if response.status_code != 200: - return test.FAIL( - "The sender {} is not available in the Node API response: {}" - .format(sender_id, response.json()) + def test_02_02_07_01(self, test): + "Verify that the device adhere to the preference of the constraint_set." + + def make_active_constraints(_, flow): + color_sampling = IS04Utils.make_sampling(flow["components"]) + + if color_sampling is None: + raise NMOSTestException( + test.FAIL("Invalid array of video components") ) - 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)) - self.flow = self.is11_utils.get_flows(self.node_url, sender_flow_id) - flow_sample_rate = self.flow["sample_rate"] - if self.flow_sample_rate[sender_id] != flow_sample_rate: - return test.FAIL("Different sample rate") - valid, response = self.do_request( - "DELETE", - self.build_constraints_active_url(sender_id), - ) - if not valid: - return test.FAIL("Unexpected response from the streamcompatibility API: {}".format(response)) - if response.status_code != 200: - return test.FAIL( - "The sender {} constraints cannot be deleted".format(sender_id) - ) - return test.PASS() - - def test_02_02_06_01(self, test): - """ - Verify that setting no-op 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: - valid, response = self.do_request( - "GET", self.build_sender_status_url(sender_id) - ) - if not valid: - return test.FAIL("Unexpected response from the streamcompatibility API: {}".format(response)) - if response.status_code != 200: - return test.FAIL( - "The streamcompatibility request for sender {} status has failed: {}" - .format(sender_id, response.json()) - ) - state = response.json()["state"] - if state in ["awaiting_essence", "no_essence"]: - for i in range(0, CONFIG.STABLE_STATE_ATTEMPTS): - valid, response = self.do_request( - "GET", self.build_sender_status_url(sender_id) - ) - if not valid: - return test.FAIL("Unexpected response from the streamcompatibility API: {}".format(response)) - if response.status_code != 200: - return test.FAIL( - "The streamcompatibility request for sender {} status has failed: {}" - .format(sender_id, response.json()) - ) - state = response.json()["state"] - if state in ["awaiting_essence", "no_essence"]: - time.sleep(CONFIG.STABLE_STATE_DELAY) - else: - break - if state != "unconstrained": - return test.FAIL("Expected state of sender {} is \"unconstrained\", got \"{}\"" - .format(sender_id, state)) - - valid, response = self.do_request( - "GET", self.node_url + "senders/" + sender_id - ) - if not valid: - return test.FAIL("Unexpected response from the Node API: {}".format(response)) - if response.status_code != 200: - return test.FAIL( - "The sender {} is not available in the Node API response: {}" - .format(sender_id, response.json()) - ) - sender = response.json() - self.flow = self.is11_utils.get_flows(self.node_url, sender["flow_id"]) - color_sampling = IS04Utils.make_sampling(self.flow["components"]) - if color_sampling is None: - return test.FAIL("Invalid array of video components") - constraint_set = {} - - 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": [self.flow["media_type"]] - } - if item == "urn:x-nmos:cap:format:grain_rate": - constraint_set["urn:x-nmos:cap:format:grain_rate"] = { - "enum": [self.flow["grain_rate"]] - } - if item == "urn:x-nmos:cap:format:frame_width": - constraint_set["urn:x-nmos:cap:format:frame_width"] = { - "enum": [self.flow["frame_width"]] - } - if item == "urn:x-nmos:cap:format:frame_height": - constraint_set["urn:x-nmos:cap:format:frame_height"] = { - "enum": [self.flow["frame_height"]] - } - if item == "urn:x-nmos:cap:format:interlace_mode": - constraint_set["urn:x-nmos:cap:format:interlace_mode"] = { - "enum": [self.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": [self.flow["components"][0]["bit_depth"]] - } - except Exception: - pass - - self.constraints[sender_id] = {"constraint_sets": [constraint_set]} - - valid, response = self.do_request( - "PUT", - self.build_constraints_active_url(sender_id), - json=self.constraints[sender_id], - ) - if not valid: - return test.FAIL("Unexpected response from the streamcompatibility API: {}".format(response)) - if response.status_code != 200: - return test.FAIL( - "The sender {} constraints change has failed: {}" - .format(sender_id, response.json()) - ) - new_flow = self.is11_utils.get_flows(self.node_url, sender["flow_id"]) - new_color_sampling = IS04Utils.make_sampling(new_flow["components"]) - if new_color_sampling is None: - return test.FAIL("Invalid array of video components") - - for item in REF_SUPPORTED_CONSTRAINTS_VIDEO: - try: - if item == "urn:x-nmos:cap:format:media_type": - if self.flow["media_type"] != new_flow["media_type"]: - return test.FAIL("Different media_type") - if item == "urn:x-nmos:cap:format:grain_rate": - if self.flow["grain_rate"] != new_flow["grain_rate"]: - return test.FAIL("Different grain_rate") - if item == "urn:x-nmos:cap:format:frame_width": - if self.flow["frame_width"] != new_flow["frame_width"]: - return test.FAIL("Different frame_width") - if item == "urn:x-nmos:cap:format:frame_height": - if self.flow["frame_height"] != new_flow["frame_height"]: - return test.FAIL("Different frame_height") - if item == "urn:x-nmos:cap:format:interlace_mode": - if self.flow["interlace_mode"] != new_flow["interlace_mode"]: - return test.FAIL("Different interlace_mode") - if item == "urn:x-nmos:cap:format:color_sampling": - if color_sampling != new_color_sampling: - return test.FAIL("Different color_sampling") - if item == "urn:x-nmos:cap:format:component_depth": - if ( - self.flow["components"][0]["bit_depth"] - != new_flow["components"][0]["bit_depth"] - ): - return test.FAIL("Different component_depth") - except Exception: - pass - valid, response = self.do_request( - "DELETE", - self.build_constraints_active_url(sender_id), - ) - if not valid: - return test.FAIL("Unexpected response from the streamcompatibility API: {}".format(response)) - if response.status_code != 200: - return test.FAIL( - "The sender {} constraints cannot be deleted".format(sender_id) - ) - return test.PASS() - - def test_02_02_06_02(self, test): - """ - Verify that setting no-op 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: - valid, response = self.do_request( - "GET", self.build_sender_status_url(sender_id) - ) - if not valid: - return test.FAIL("Unexpected response from the streamcompatibility API: {}".format(response)) - if response.status_code != 200: - test.FAIL("The streamcompatibility request for sender {} status has failed: {}" - .format(sender_id, response.json())) - state = response.json()["state"] - if state in ["awaiting_essence", "no_essence"]: - for i in range(0, CONFIG.STABLE_STATE_ATTEMPTS): - valid, response = self.do_request( - "GET", self.build_sender_status_url(sender_id) - ) - if not valid: - return test.FAIL("Unexpected response from the streamcompatibility API: {}".format(response)) - if response.status_code != 200: - return test.FAIL( - "The streamcompatibility request for sender {} status has failed: {}" - .format(sender_id, response.json()) - ) - state = response.json()["state"] - if state in ["awaiting_essence", "no_essence"]: - time.sleep(CONFIG.STABLE_STATE_DELAY) - else: - break - if state != "unconstrained": - return test.FAIL("Expected state of sender {} is \"unconstrained\", got \"{}\"" - .format(sender_id, state)) - valid, response = self.do_request( - "GET", self.node_url + "senders/" + sender_id - ) - if not valid: - return test.FAIL("Unexpected response from the Node API: {}".format(response)) - if response.status_code != 200: - return test.FAIL( - "The sender {} is not available in the Node API response: {}" - .format(sender_id, response.json()) - ) - sender = response.json() - self.flow = self.is11_utils.get_flows(self.node_url, sender["flow_id"]) - constraint_set = {} - - valid, response = self.do_request( - "GET", self.node_url + "sources/" + self.flow["source_id"] - ) - if not valid: - return test.FAIL("Unexpected response from the Node API: {}".format(response)) - if response.status_code != 200: - return test.FAIL( - "The source {} is not available in the Node API: {}" - .format(self.flow["source_id"], 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": [self.flow["media_type"]] - } - if item == "urn:x-nmos:cap:format:sample_rate": - constraint_set["urn:x-nmos:cap:format:sample_rate"] = { - "enum": [self.flow["sample_rate"]] - } - if item == "urn:x-nmos:cap:format:channel_count": - constraint_set["urn:x-nmos:cap:format:channel_count"] = { - "enum": [len(source["channels"])] - } - if item == "urn:x-nmos:cap:format:sample_depth": - constraint_set["urn:x-nmos:cap:format:sample_depth"] = { - "enum": [self.flow["bit_depth"]] - } - except Exception: - pass - self.constraints[sender_id] = {"constraint_sets": [constraint_set]} - valid, response = self.do_request( - "PUT", - self.build_constraints_active_url(sender_id), - json=self.constraints[sender_id], - ) - if not valid: - return test.FAIL("Unexpected response from the streamcompatibility API: {}".format(response)) - if response.status_code != 200: - return test.FAIL( - "The sender {} constraints change has failed: {}" - .format(sender_id, response.json()) - ) - new_flow = self.is11_utils.get_flows(self.node_url, sender["flow_id"]) - - valid, response = self.do_request( - "GET", self.node_url + "sources/" + self.flow["source_id"] - ) - if not valid: - return test.FAIL("Unexpected response from the Node API: {}".format(response)) - if response.status_code != 200: - return test.FAIL( - "The source {} is not available in the Node API: {}" - .format(self.flow["source_id"], response.json()) - ) - new_source = response.json() - - for item in REF_SUPPORTED_CONSTRAINTS_AUDIO: - try: - if item == "urn:x-nmos:cap:format:media_type": - if self.flow["media_type"] != new_flow["media_type"]: - return test.FAIL("Different media_type") - if item == "urn:x-nmos:cap:format:sample_rate": - if self.flow["sample_rate"] != new_flow["sample_rate"]: - return test.FAIL("Different sample_rate") - if item == "urn:x-nmos:cap:format:channel_count": - if len(source["channels"]) != len(new_source["channels"]): - return test.FAIL("Different channel_count") - if item == "urn:x-nmos:cap:format:sample_depth": - if self.flow["bit_depth"] != new_flow["bit_depth"]: - return test.FAIL("Different sample_depth") - except Exception: - pass - valid, response = self.do_request( - "DELETE", - self.build_constraints_active_url(sender_id), - ) - if not valid: - return test.FAIL("Unexpected response from the streamcompatibility API: {}".format(response)) - if response.status_code != 200: - return test.FAIL( - "The sender {} constraints cannot be deleted".format(sender_id) - ) - 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: - valid, response = self.do_request( - "GET", self.build_sender_status_url(sender_id) - ) - if not valid: - return test.FAIL("Unexpected response from the streamcompatibility API: {}".format(response)) - if response.status_code != 200: - return test.FAIL( - "The streamcompatibility request for sender {} status has failed: {}" - .format(sender_id, response.json()) - ) - state = response.json()["state"] - if state in ["awaiting_essence", "no_essence"]: - for i in range(0, CONFIG.STABLE_STATE_ATTEMPTS): - valid, response = self.do_request( - "GET", self.build_sender_status_url(sender_id) - ) - if not valid: - return test.FAIL("Unexpected response from the streamcompatibility API: {}".format(response)) - if response.status_code != 200: - return test.FAIL( - "The streamcompatibility request for sender {} status has failed: {}" - .format(sender_id, response.json()) - ) - state = response.json()["state"] - - if state in ["awaiting_essence", "no_essence"]: - time.sleep(CONFIG.STABLE_STATE_DELAY) - else: - break - if state != "unconstrained": - return test.FAIL("Expected state of sender {} is \"unconstrained\", got \"{}\"" - .format(sender_id, state)) - - valid, response = self.do_request( - "GET", self.node_url + "senders/" + sender_id - ) - if not valid: - return test.FAIL("Unexpected response from the Node API: {}".format(response)) - if response.status_code != 200: - return test.FAIL( - "The sender {} is not available in the Node API response: {}" - .format(sender_id, response.json()) - ) - sender = response.json() - self.flow = self.is11_utils.get_flows(self.node_url, sender["flow_id"]) - color_sampling = IS04Utils.make_sampling(self.flow["components"]) - if color_sampling is None: - return test.FAIL("Invalid array of video components") - constraint_set0 = {} - 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": [self.flow["media_type"]] - } - if item == "urn:x-nmos:cap:format:grain_rate": - constraint_set0["urn:x-nmos:cap:format:grain_rate"] = { - "enum": [self.flow["grain_rate"]] - } - if item == "urn:x-nmos:cap:format:frame_width": - constraint_set0["urn:x-nmos:cap:format:frame_width"] = { - "enum": [self.flow["frame_width"]] - } - if item == "urn:x-nmos:cap:format:frame_height": - constraint_set0["urn:x-nmos:cap:format:frame_height"] = { - "enum": [self.flow["frame_height"]] - } - if item == "urn:x-nmos:cap:format:interlace_mode": - constraint_set0["urn:x-nmos:cap:format:interlace_mode"] = { - "enum": [self.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": [self.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": [self.flow["media_type"]] - } - if item == "urn:x-nmos:cap:format:grain_rate": - constraint_set1["urn:x-nmos:cap:format:grain_rate"] = { - "enum": [self.get_another_grain_rate(self.flow["grain_rate"])] - } - if item == "urn:x-nmos:cap:format:frame_width": - constraint_set1["urn:x-nmos:cap:format:frame_width"] = { - "enum": [self.flow["frame_width"]] - } - if item == "urn:x-nmos:cap:format:frame_height": - constraint_set1["urn:x-nmos:cap:format:frame_height"] = { - "enum": [self.flow["frame_height"]] - } - if item == "urn:x-nmos:cap:format:interlace_mode": - constraint_set1["urn:x-nmos:cap:format:interlace_mode"] = { - "enum": [self.flow["interlace_mode"]] - } - if item == "urn:x-nmos:cap:format:color_sampling": - constraint_set1["urn:x-nmos:cap:format:color_sampling"] = { - "enum": [color_sampling] - } - if item == "urn:x-nmos:cap:format:component_depth": - constraint_set1["urn:x-nmos:cap:format:component_depth"] = { - "enum": [self.flow["components"][0]["bit_depth"]] - } - except Exception: - pass - - self.constraints[sender_id] = { - "constraint_sets": [constraint_set0, constraint_set0] - } - valid, response = self.do_request( - "PUT", - self.build_constraints_active_url(sender_id), - json=self.constraints[sender_id], - ) - if not valid: - return test.FAIL("Unexpected response from the streamcompatibility API: {}".format(response)) - if response.status_code != 200: - return test.FAIL( - "The sender {} constraints change has failed: {}" - .format(sender_id, response.json()) - ) - - new_flow = self.is11_utils.get_flows(self.node_url, sender["flow_id"]) - - new_color_sampling = IS04Utils.make_sampling(new_flow["components"]) - if new_color_sampling is None: - return test.FAIL("invalid array of video components") - - for item in REF_SUPPORTED_CONSTRAINTS_VIDEO: - try: - if item == "urn:x-nmos:cap:format:media_type": - if self.flow["media_type"] != new_flow["media_type"]: - return test.FAIL("Different media_type") - if item == "urn:x-nmos:cap:format:grain_rate": - if self.flow["grain_rate"] != new_flow["grain_rate"]: - return test.FAIL("Different grain_rate") - if item == "urn:x-nmos:cap:format:frame_width": - if self.flow["frame_width"] != new_flow["frame_width"]: - return test.FAIL("Different frame_width") - if item == "urn:x-nmos:cap:format:frame_height": - if self.flow["frame_height"] != new_flow["frame_height"]: - return test.FAIL("Different frame_height") - if item == "urn:x-nmos:cap:format:interlace_mode": - if self.flow["interlace_mode"] != new_flow["interlace_mode"]: - return test.FAIL("Different interlace_mode") - if item == "urn:x-nmos:cap:format:color_sampling": - if color_sampling != new_color_sampling: - return test.FAIL("Different color_sampling") - if item == "urn:x-nmos:cap:format:component_depth": - if ( - self.flow["components"][0]["bit_depth"] - != new_flow["components"][0]["bit_depth"] - ): - return test.FAIL("Different component_depth") - except Exception: - pass - - valid, response = self.do_request( - "DELETE", - self.build_constraints_active_url(sender_id), - ) - if not valid: - return test.FAIL("Unexpected response from the streamcompatibility API: {}".format(response)) - if response.status_code != 200: - return test.FAIL( - "The sender {} constraints cannot be deleted".format(sender_id) - ) - 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: - valid, response = self.do_request( - "GET", self.build_sender_status_url(sender_id) - ) - if not valid: - return test.FAIL("Unexpected response from the streamcompatibility API: {}".format(response)) - if response.status_code != 200: - return test.FAIL("The streamcompatibility request for sender {} status has failed: {}" - .format(sender_id, response.json())) - state = response.json()["state"] - if state in ["awaiting_essence", "no_essence"]: - for i in range(0, CONFIG.STABLE_STATE_ATTEMPTS): - valid, response = self.do_request( - "GET", self.build_sender_status_url(sender_id) - ) - if not valid: - return test.FAIL("Unexpected response from the streamcompatibility API: {}".format(response)) - if response.status_code != 200: - return test.FAIL( - "The streamcompatibility request for sender {} status has failed: {}" - .format(sender_id, response.json()) - ) - state = response.json()["state"] - if state in ["awaiting_essence", "no_essence"]: - time.sleep(CONFIG.STABLE_STATE_DELAY) - else: - break - if state != "unconstrained": - return test.FAIL("Expected state of sender {} is \"unconstrained\", got \"{}\"" - .format(sender_id, state)) - - valid, response = self.do_request( - "GET", self.node_url + "senders/" + sender_id - ) - if not valid: - return test.FAIL("Unexpected response from the Node API: {}".format(response)) - if response.status_code != 200: - return test.FAIL( - "The sender {} is not available in the Node API response: {}" - .format(sender_id, response.json()) - ) - sender = response.json() - self.flow = self.is11_utils.get_flows(self.node_url, sender["flow_id"]) - valid, response = self.do_request( - "GET", self.node_url + "sources/" + self.flow["source_id"] - ) - if not valid: - return test.FAIL("Unexpected response from the Node API: {}".format(response)) - if response.status_code != 200: - return test.FAIL( - "The source {} is not available in the Node API: {}" - .format(self.flow["source_id"], response.json()) - ) - source = response.json() + return { + "constraint_sets": [{ + "urn:x-nmos:cap:meta:label": "video constraint", + "urn:x-nmos:cap:meta:preference": 0, + "urn:x-nmos:cap:meta:enabled": True, + "urn:x-nmos:cap:format:media_type": { + "enum": [flow["media_type"]] + }, + "urn:x-nmos:cap:format:grain_rate": { + "enum": [flow["grain_rate"]] + }, + "urn:x-nmos:cap:format:frame_width": { + "enum": [flow["frame_width"]] + }, + "urn:x-nmos:cap:format:frame_height": { + "enum": [flow["frame_height"]] + }, + "urn:x-nmos:cap:format:interlace_mode": { + "enum": [flow["interlace_mode"]] + }, + "urn:x-nmos:cap:format:color_sampling": { + "enum": [color_sampling] + }, + "urn:x-nmos:cap:format:component_depth": { + "enum": [flow["components"][0]["bit_depth"]] + } + }, { + "urn:x-nmos:cap:meta:label": "video constraint", + "urn:x-nmos:cap:meta:preference": -100, + "urn:x-nmos:cap:meta:enabled": True, + "urn:x-nmos:cap:format:media_type": { + "enum": [flow["media_type"]] + }, + "urn:x-nmos:cap:format:grain_rate": { + "enum": [self.get_another_grain_rate(flow["grain_rate"])] + }, + "urn:x-nmos:cap:format:frame_width": { + "enum": [flow["frame_width"]] + }, + "urn:x-nmos:cap:format:frame_height": { + "enum": [flow["frame_height"]] + }, + "urn:x-nmos:cap:format:interlace_mode": { + "enum": [flow["interlace_mode"]] + }, + "urn:x-nmos:cap:format:color_sampling": { + "enum": [color_sampling] + }, + "urn:x-nmos:cap:format:component_depth": { + "enum": [flow["components"][0]["bit_depth"]] + } + }] + } - constraint_set0 = {} - constraint_set1 = {} + flow_attrs = [ + "media_type", "grain_rate", + "frame_width", "frame_height", + "interlace_mode", "components" + ] - 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": [self.flow["media_type"]] - } - if item == "urn:x-nmos:cap:format:sample_rate": - constraint_set0["urn:x-nmos:cap:format:sample_rate"] = { - "enum": [self.flow["sample_rate"]] - } - if item == "urn:x-nmos:cap:format:channel_count": - constraint_set0["urn:x-nmos:cap:format:channel_count"] = { - "enum": [len(source["channels"])] - } - if item == "urn:x-nmos:cap:format:sample_depth": - constraint_set0["urn:x-nmos:cap:format:sample_depth"] = { - "enum": [self.flow["bit_depth"]] - } - except Exception: - pass + return self.apply_nop_active_constraints(test, self.video_senders, make_active_constraints, [], flow_attrs) - 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": [self.flow["media_type"]] - } - if item == "urn:x-nmos:cap:format:sample_rate": - constraint_set1["urn:x-nmos:cap:format:sample_rate"] = { - "enum": [self.get_another_sample_rate(self.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": [self.flow["bit_depth"]] - } - except Exception: - pass + def test_02_02_07_02(self, test): + "Verify that the device adhere to the preference of the constraint_set." - self.constraints[sender_id] = { - "constraint_sets": [constraint_set0, constraint_set1] + def make_active_constraints(source, flow): + return { + "constraint_sets": [{ + "urn:x-nmos:cap:meta:label": "audio constraint", + "urn:x-nmos:cap:meta:preference": 0, + "urn:x-nmos:cap:meta:enabled": True, + "urn:x-nmos:cap:format:media_type": { + "enum": [flow["media_type"]] + }, + "urn:x-nmos:cap:format:sample_rate": { + "enum": [flow["sample_rate"]] + }, + "urn:x-nmos:cap:format:channel_count": { + "enum": [len(source["channels"])] + }, + "urn:x-nmos:cap:format:sample_depth": { + "enum": [flow["bit_depth"]] + } + }, { + "urn:x-nmos:cap:meta:label": "audio constraint", + "urn:x-nmos:cap:meta:preference": -100, + "urn:x-nmos:cap:meta:enabled": True, + "urn:x-nmos:cap:format:media_type": { + "enum": [flow["media_type"]] + }, + "urn:x-nmos:cap:format:sample_rate": { + "enum": [self.get_another_sample_rate(flow["sample_rate"])] + }, + "urn:x-nmos:cap:format:channel_count": { + "enum": [len(source["channels"])] + }, + "urn:x-nmos:cap:format:sample_depth": { + "enum": [flow["bit_depth"]] + } + }] } - valid, response = self.do_request( - "PUT", - self.build_constraints_active_url(sender_id), - json=self.constraints[sender_id], - ) - if not valid: - return test.FAIL("Unexpected response from the streamcompatibility API: {}".format(response)) - if response.status_code != 200: - return test.FAIL( - "The sender {} constraints change has failed: {}" - .format(sender_id, response.json()) - ) - new_flow = self.is11_utils.get_flows(self.node_url, sender["flow_id"]) - - valid, response = self.do_request( - "GET", self.node_url + "sources/" + self.flow["source_id"] - ) - if not valid: - return test.FAIL("Unexpected response from the Node API: {}".format(response)) - if response.status_code != 200: - return test.FAIL( - "The source {} is not available in the Node API: {}" - .format(self.flow["source_id"], response.json()) - ) - new_source = response.json() + source_attrs = ["channels"] + flow_attrs = ["media_type", "sample_rate", "bit_depth"] - for item in REF_SUPPORTED_CONSTRAINTS_AUDIO: - try: - if item == "urn:x-nmos:cap:format:media_type": - if self.flow["media_type"] != new_flow["media_type"]: - return test.FAIL("Different media_type") - if item == "urn:x-nmos:cap:format:sample_rate": - if self.flow["sample_rate"] != new_flow["sample_rate"]: - return test.FAIL("Different sample_rate") - if item == "urn:x-nmos:cap:format:channel_count": - if len(source["channels"]) != len(new_source["channels"]): - return test.FAIL("Different channel_count") - if item == "urn:x-nmos:cap:format:sample_depth": - if self.flow["bit_depth"] != new_flow["bit_depth"]: - return test.FAIL("Different sample_depth") - except Exception: - pass - valid, response = self.do_request( - "DELETE", - self.build_constraints_active_url(sender_id), - ) - if not valid: - return test.FAIL("Unexpected response from the streamcompatibility API: {}".format(response)) - if response.status_code != 200: - return test.FAIL( - "The sender {} constraints cannot be deleted".format(sender_id) - ) - return test.PASS() + return self.apply_nop_active_constraints( + test, self.audio_senders, make_active_constraints, source_attrs, flow_attrs + ) def test_02_03_00(self, test): """ @@ -2192,10 +1117,10 @@ def test_02_03_05_01(self, test): Verify for inputs supporting EDID that the version and the effective EDID change when applying constraints (video) """ - if len(self.flow_format_video) == 0: + if len(self.video_senders) == 0: return test.UNCLEAR("There is no video format") - for sender_id in self.flow_format_video: + for sender_id in self.video_senders: valid, response = self.do_request( "GET", self.compat_url + "senders/" + sender_id + "/inputs/" ) @@ -2543,10 +1468,10 @@ def test_02_03_05_02(self, test): Verify for inputs supporting EDID that the version and the effective EDID change when applying constraints (audio) """ - if len(self.flow_format_audio) == 0: + if len(self.audio_senders) == 0: return test.UNCLEAR("There is no audio format") - for sender_id in self.flow_format_audio: + for sender_id in self.audio_senders: valid, response = self.do_request( "GET", self.compat_url + "senders/" + sender_id + "/inputs/" ) @@ -3748,6 +2673,7 @@ def delete_base_edid(self): if response.status_code != 204: raise NMOSInitException("The request {} has failed: {}".format(url, response)) + # Returns Input's Senders def get_inputs_senders(self, test, input_id): sender_ids = [] @@ -3772,6 +2698,45 @@ def get_inputs_senders(self, test, input_id): return sender_ids + # Returns Sender's Flow + def get_senders_flow(self, test, sender_id): + valid, response = self.do_request( + "GET", self.node_url + "senders/" + sender_id + ) + if not valid: + raise NMOSTestException( + test.FAIL("Unexpected response from the Node API: {}".format(response)) + ) + if response.status_code != 200: + raise NMOSTestException( + test.FAIL("The sender {} is not available in the Node API response: {}" + .format(sender_id, response.json())) + ) + flow_id = response.json()["flow_id"] + if flow_id is None: + raise NMOSTestException( + test.FAIL("The sender {} must have a flow".format(sender_id)) + ) + + return self.is11_utils.get_flow(self.node_url, flow_id) + + # Returns Flow's Source + def get_flows_source(self, test, flow_id): + valid, response = self.do_request( + "GET", self.node_url + "flows/" + flow_id + ) + if not valid: + raise NMOSTestException( + test.FAIL("Unexpected response from the Node API: {}".format(response)) + ) + if response.status_code != 200: + raise NMOSTestException( + test.FAIL("The Flow {} is not available in the Node API response: {}".format(flow_id, response.json())) + ) + source_id = response.json()["source_id"] + + return self.is11_utils.get_source(self.node_url, source_id) + def get_json(self, test, url): valid, response = self.do_request("GET", url) if not valid or response.status_code != 200: @@ -3817,3 +2782,265 @@ def wait_until_true(self, predicate): return True time.sleep(CONFIG.STABLE_STATE_DELAY) return False + + def has_sender_flow_format(self, sender_id, format): + assert format in ["video", "audio"] + format = "urn:x-nmos:format:" + format + + url = self.node_url + "senders/" + sender_id + valid, response = self.do_request("GET", url) + + if not valid: + raise NMOSInitException( + "Unexpected response from the Node API: {}".format(response) + ) + if response.status_code != 200: + raise NMOSInitException("The request {} has failed: {}".format(url, response)) + + try: + flow_id = response.json()["flow_id"] + if flow_id is None: + return False + + flow = self.is11_utils.get_flow(self.node_url, flow_id) + if flow["format"] == format: + return True + + return False + except json.JSONDecodeError: + raise NMOSInitException( + "Non-JSON response returned from the Node API" + ) + + def has_sender_video_flow(self, id): + return self.has_sender_flow_format(id, "video") + + def has_sender_audio_flow(self, id): + return self.has_sender_flow_format(id, "audio") + + def check_param_constraints(self, test, senders, param_constraints): + sample = "^urn:x-nmos:cap:" + + if len(senders) == 0: + return test.UNCLEAR("Not tested. No appropriate Senders found via IS-11.") + + for sender_id in senders: + valid, response = self.do_request( + "GET", + self.compat_url + "senders/" + sender_id + "/constraints/supported/", + ) + if not valid: + return test.FAIL("Unexpected response from the Stream Compatibility Management API: {}" + .format(response)) + if response.status_code != 200: + return test.FAIL( + "The streamcompatibility request for sender {} constraints supported has failed: {}" + .format(sender_id, response.json()) + ) + supportedConstraints = response.json()["parameter_constraints"] + for item in supportedConstraints: + if not re.search(sample, item): + return test.FAIL("Only x-nmos:cap constraints are allowed") + for item in param_constraints: + if item not in supportedConstraints: + return test.FAIL(item + " is not in supportedConstraints ") + return test.PASS() + + def check_snd_ver_after_active_constraints_put(self, test, senders, flow_key, active_constraints_key): + empty_constraints = {"constraint_sets": []} + + if len(senders) == 0: + return test.UNCLEAR("Not tested. No appropriate Senders found via IS-11.") + + for sender_id in senders: + snd_version_1 = "" + snd_version_2 = "" + snd_version_3 = "" + + valid, response = self.do_request( + "GET", self.node_url + "senders/" + sender_id + ) + if not valid: + return test.FAIL("Unexpected response from the Node API: {}".format(response)) + if response.status_code != 200: + return test.FAIL( + "The sender {} is not available in the Node API response: {}".format(sender_id, response.json()) + ) + snd_version_1 = response.json()["version"] + + flow = self.get_senders_flow(test, sender_id) + + active_constraints_request = { + "constraint_sets": [ + { + active_constraints_key: { + "enum": [flow[flow_key]] + } + } + ] + } + + valid, response = self.do_request( + "PUT", + self.build_constraints_active_url(sender_id), + json=active_constraints_request + ) + if not valid: + return test.FAIL("Unexpected response from the streamcompatibility API: {}".format(response)) + if response.status_code != 200: + return test.FAIL( + "The sender {} constraints change has failed: {}".format(sender_id, response.json()) + ) + + valid, response = self.do_request( + "GET", self.node_url + "senders/" + sender_id + ) + if not valid: + return test.FAIL("Unexpected response from the Node API: {}".format(response)) + if response.status_code != 200: + return test.FAIL( + "The sender {} is not available in the Node API response: {}".format(sender_id, response.json()) + ) + snd_version_2 = response.json()["version"] + if snd_version_2 == snd_version_1: + return test.FAIL("Versions {} and {} are the same".format(snd_version_2, snd_version_1)) + + valid, response = self.do_request( + "GET", self.build_constraints_active_url(sender_id) + ) + if not valid: + return test.FAIL("Unexpected response from the streamcompatibility API: {}".format(response)) + if response.status_code != 200: + return test.FAIL( + "Contraints active request for sender {} has failed: {}".format(sender_id, response.json()) + ) + constraints = response.json() + + if not IS04Utils.compare_constraint_sets( + constraints["constraint_sets"], + active_constraints_request["constraint_sets"], + ): + return test.FAIL("The constraint applied does not match the active" + "constraint retrieved from the sender {}".format(sender_id)) + + valid, response = self.do_request( + "DELETE", + self.build_constraints_active_url(sender_id), + ) + if not valid: + return test.FAIL("Unexpected response from the streamcompatibility API: {}".format(response)) + if response.status_code != 200: + return test.FAIL( + "The sender {} constraints cannot be deleted".format(sender_id) + ) + + valid, response = self.do_request( + "GET", self.node_url + "senders/" + sender_id + ) + if not valid: + return test.FAIL("Unexpected response from the Node API: {}".format(response)) + if response.status_code != 200: + return test.FAIL( + "The sender {} is not available in the Node API response: {}".format(sender_id, response.json()) + ) + snd_version_3 = response.json()["version"] + if snd_version_3 == snd_version_2: + return test.FAIL("Versions {} and {} are the same".format(snd_version_3, snd_version_2)) + + valid, response = self.do_request( + "GET", self.build_constraints_active_url(sender_id) + ) + if not valid: + return test.FAIL("Unexpected response from the streamcompatibility API: {}".format(response)) + if response.status_code != 200: + return test.FAIL( + "Contraints active request for sender {} has failed: {}".format(sender_id, response.json()) + ) + constraints = response.json() + if constraints != empty_constraints: + return test.FAIL("Contraints are different") + return test.PASS() + + def is_sender_state_equal_to_expected(self, test, sender_id, expected): + valid, response = self.do_request( + "GET", self.build_sender_status_url(sender_id) + ) + if not valid: + return test.FAIL("Unexpected response from the streamcompatibility API: {}".format(response)) + if response.status_code != 200: + return test.FAIL( + "The streamcompatibility request for sender {} status has failed: {}" + .format(sender_id, response.json()) + ) + return response.json()["state"] == expected + + def apply_nop_active_constraints(self, test, senders, make_active_constraints, source_attrs, flow_attrs): + if len(senders) == 0: + return test.UNCLEAR("Not tested. No appropriate Senders found via IS-11.") + + for sender_id in senders: + # Verify that the state of the Sender is "unconstrained" + # after "set_up_tests" call + + result = self.wait_until_true( + partial(self.is_sender_state_equal_to_expected, test, sender_id, "unconstrained") + ) + if not result: + return test.FAIL("Expected state of sender {} is \"unconstrained\"" + .format(sender_id)) + + flow = self.get_senders_flow(test, sender_id) + source = self.get_flows_source(test, flow["id"]) + active_constraints_request = make_active_constraints(source, flow) + + valid, response = self.do_request( + "PUT", + self.build_constraints_active_url(sender_id), + json=active_constraints_request + ) + if not valid: + return test.FAIL("Unexpected response from the streamcompatibility API: {}".format(response)) + if response.status_code != 200: + return test.FAIL( + "The sender {} constraints change has failed: {}" + .format(sender_id, response.json()) + ) + + # Verify that the state of the Sender is "constrained" + + result = self.wait_until_true( + partial(self.is_sender_state_equal_to_expected, test, sender_id, "constrained") + ) + if not result: + return test.FAIL("Expected state of sender {} is \"constrained\"" + .format(sender_id)) + + new_flow = self.get_senders_flow(test, sender_id) + new_source = self.get_flows_source(test, new_flow["id"]) + + for flow_attr in flow_attrs: + if (new_flow[flow_attr] != flow[flow_attr]): + return test.FAIL( + "The constraints were not expected to change the flow of sender {}" + .format(sender_id) + ) + + for source_attr in source_attrs: + if (new_source[source_attr] != source[source_attr]): + return test.FAIL( + "The constraints were not expected to change the Source of sender {}" + .format(sender_id) + ) + + valid, response = self.do_request( + "DELETE", + self.build_constraints_active_url(sender_id), + ) + if not valid: + return test.FAIL("Unexpected response from the streamcompatibility API: {}".format(response)) + if response.status_code != 200: + return test.FAIL( + "The sender {} constraints cannot be deleted".format(sender_id) + ) + + return test.PASS() From 2532ffd3e10effe264bf62c8f77ddf24618e8cf5 Mon Sep 17 00:00:00 2001 From: N-Nagorny <15526762+N-Nagorny@users.noreply.github.com> Date: Wed, 15 May 2024 20:54:27 +0400 Subject: [PATCH 2/5] Fix tests 2.3.5.1, 2.3.5.2 --- nmostesting/suites/IS1101Test.py | 59 +++++++++++++++++--------------- 1 file changed, 32 insertions(+), 27 deletions(-) diff --git a/nmostesting/suites/IS1101Test.py b/nmostesting/suites/IS1101Test.py index ca1c8bb7..7ca93d41 100644 --- a/nmostesting/suites/IS1101Test.py +++ b/nmostesting/suites/IS1101Test.py @@ -73,14 +73,10 @@ def __init__(self, apis, **kwargs): self.conn_url = self.apis[CONN_API_KEY]["url"] self.reference_senders = {} self.caps = "" - self.flow_grain_rate = {} - self.flow_sample_rate = {} self.version = {} self.some_input = {} self.input_senders = [] self.not_active_connected_inputs = [] - self.another_grain_rate_constraints = {} - self.another_sample_rate_constraints = {} self.not_input_senders = [] self.is04_utils = IS04Utils(self.node_url) self.is05_utils = IS05Utils(self.conn_url) @@ -1206,14 +1202,15 @@ def test_02_03_05_01(self, test): self.version[sender_id] = version default_edid = self.get_effective_edid(test, input_id) + flow_grain_rate = self.get_senders_flow(test, sender_id)["grain_rate"] - self.another_grain_rate_constraints[sender_id] = { + another_grain_rate_constraints = { "constraint_sets": [ { "urn:x-nmos:cap:format:grain_rate": { "enum": [ self.get_another_grain_rate( - self.flow_grain_rate[sender_id] + flow_grain_rate ) ] } @@ -1223,21 +1220,23 @@ def test_02_03_05_01(self, test): valid, response = self.do_request( "PUT", self.compat_url + "senders/" + sender_id + "/constraints/active/", - json=self.another_grain_rate_constraints[sender_id], + json=another_grain_rate_constraints ) time.sleep(CONFIG.STABLE_STATE_DELAY) if not valid: return test.FAIL( "Unexpected response from the Node API: {}".format(response) ) + + if response.status_code == 422: + return test.UNCLEAR("Device does not accept grain_rate constraint") + if response.status_code != 200: return test.FAIL( - "The sender {} is not available in the Node API request: {}".format( - sender_id, response + "Sender {} failed to apply the Active Constraints: {}".format( + sender_id, response.json() ) ) - if response.status_code == 422: - print("Device does not accept grain_rate constraint") valid, response = self.do_request( "GET", @@ -1256,7 +1255,7 @@ def test_02_03_05_01(self, test): ) ) if response.content == default_edid: - print("Grain rate constraint are not changing effective EDID") + return test.UNCLEAR("Grain rate constraint are not changing effective EDID") valid, response = self.do_request( "GET", self.compat_url + "inputs/" + input_id + "/properties/" @@ -1439,7 +1438,7 @@ def test_02_03_05_01(self, test): return test.FAIL("Unable to find expected key: {}".format(e)) if grain_rate != self.get_another_grain_rate( - self.flow_grain_rate[sender_id] + flow_grain_rate ): return test.FAIL( "The flow_grain_rate does not match the constraint" @@ -1460,8 +1459,7 @@ def test_02_03_05_01(self, test): sender_id, response ) ) - return test.PASS() - return test.UNCLEAR("No resources found to perform this test.") + return test.PASS() def test_02_03_05_02(self, test): """ @@ -1531,6 +1529,7 @@ def test_02_03_05_02(self, test): return test.FAIL("Non-JSON response returned from Node API") except KeyError as e: return test.FAIL("Unable to find expected key: {}".format(e)) + self.version[input_id] = version valid, response = self.do_request( @@ -1543,7 +1542,7 @@ def test_02_03_05_02(self, test): if response.status_code != 200: return test.FAIL( "The sender {} is not available in the Node API request: {}".format( - sender_id, response + sender_id, response.json() ) ) try: @@ -1552,17 +1551,19 @@ def test_02_03_05_02(self, test): return test.FAIL("Non-JSON response returned from Node API") except KeyError as e: return test.FAIL("Unable to find expected key: {}".format(e)) + self.version[sender_id] = version default_edid = self.get_effective_edid(test, input_id) + flow_sample_rate = self.get_senders_flow(test, sender_id)["sample_rate"] - self.another_sample_rate_constraints[sender_id] = { + another_sample_rate_constraints = { "constraint_sets": [ { "urn:x-nmos:cap:format:sample_rate": { "enum": [ self.get_another_sample_rate( - self.flow_sample_rate[sender_id] + flow_sample_rate ) ] } @@ -1572,21 +1573,23 @@ def test_02_03_05_02(self, test): valid, response = self.do_request( "PUT", self.compat_url + "senders/" + sender_id + "/constraints/active/", - json=self.another_sample_rate_constraints[sender_id], + json=another_sample_rate_constraints ) time.sleep(CONFIG.STABLE_STATE_DELAY) if not valid: return test.FAIL( "Unexpected response from the Node API: {}".format(response) ) + + if response.status_code == 422: + return test.UNCLEAR("Device does not accept sample_rate constraint") + if response.status_code != 200: return test.FAIL( - "The sender {} is not available in the Node API request: {}".format( - sender_id, response + "Sender {} failed to apply the Active Constraints: {}".format( + sender_id, response.json() ) ) - if response.status_code == 422: - print("Device does not accept grain_rate constraint") valid, response = self.do_request( "GET", @@ -1605,7 +1608,7 @@ def test_02_03_05_02(self, test): ) ) if response.content == default_edid: - print("Grain rate constraint are not changing effective EDID") + return test.UNCLEAR("Sample rate constraint are not changing effective EDID") valid, response = self.do_request( "GET", self.compat_url + "inputs/" + input_id + "/properties/" @@ -1641,7 +1644,7 @@ def test_02_03_05_02(self, test): if response.status_code != 200: return test.FAIL( "The sender {} is not available in the Node API request: {}".format( - sender_id, response + sender_id, response.json() ) ) try: @@ -1650,6 +1653,7 @@ def test_02_03_05_02(self, test): return test.FAIL("Non-JSON response returned from Node API") except KeyError as e: return test.FAIL("Unable to find expected key: {}".format(e)) + if version == self.version[input_id]: return test.FAIL("Version should change") @@ -1689,6 +1693,7 @@ def test_02_03_05_02(self, test): "GET", self.compat_url + "senders/" + sender_id + "/status/" ) + if not valid: return test.FAIL( "Unexpected response from the Node API: {}".format(response) @@ -1784,10 +1789,10 @@ def test_02_03_05_02(self, test): except KeyError as e: return test.FAIL("Unable to find expected key: {}".format(e)) if sample_rate != self.get_another_sample_rate( - self.flow_sample_rate[sender_id] + flow_sample_rate ): return test.FAIL( - "The flow_grain_rate does not match the constraint" + "The flow_sample_rate does not match the constraint" ) valid, response = self.do_request( "DELETE", From 6e70a77a64154b0a870047b4ac606f6ee334e357 Mon Sep 17 00:00:00 2001 From: N-Nagorny <15526762+N-Nagorny@users.noreply.github.com> Date: Wed, 15 May 2024 20:55:00 +0400 Subject: [PATCH 3/5] Sender tests: remove duplicates --- nmostesting/suites/IS1101Test.py | 376 ++----------------------------- 1 file changed, 25 insertions(+), 351 deletions(-) diff --git a/nmostesting/suites/IS1101Test.py b/nmostesting/suites/IS1101Test.py index 7ca93d41..869cebc6 100644 --- a/nmostesting/suites/IS1101Test.py +++ b/nmostesting/suites/IS1101Test.py @@ -110,6 +110,8 @@ def set_up_tests(self): self.video_senders = list(filter(self.has_sender_video_flow, self.senders)) self.audio_senders = list(filter(self.has_sender_audio_flow, self.senders)) + self.senders_with_inputs = list(filter(self.sender_has_i_o, self.senders)) + self.receivers_with_outputs = list(filter(self.receiver_has_i_o, self.receivers)) self.receivers_without_outputs = list(set(self.receivers) - set(self.receivers_with_outputs)) @@ -837,276 +839,36 @@ def make_active_constraints(source, flow): test, self.audio_senders, make_active_constraints, source_attrs, flow_attrs ) - def test_02_03_00(self, test): + def test_02_03_01(self, test): """ - Verify senders supporting inputs + Verify that the input is valid """ - for input in self.senders: + + if len(self.senders_with_inputs) == 0: + return test.UNCLEAR("Not tested. No Senders with routed Inputs found via IS-11.") + + for sender_id in self.senders_with_inputs: valid, response = self.do_request( - "GET", self.compat_url + "senders/" + input + "/inputs/" - ) + "GET", self.compat_url + "senders/" + sender_id + "/inputs/" + ) if not valid: return test.FAIL("Unexpected response from the streamcompatibility API: {}".format(response)) if response.status_code != 200: - return test.FAIL("The sender's inputs {} streamcompatibility request has failed: {}" - .format(input, response)) + return test.FAIL("The sender {} inputs streamcompatibility request has failed: {}" + .format(sender_id, response)) try: - if len(response.json()) != 0: - self.input_senders.append(input) + inputs = response.json() except json.JSONDecodeError: return test.FAIL("Non-JSON response returned from Node API") except KeyError as e: return test.FAIL("Unable to find expected key: {}".format(e)) - if len(self.input_senders) == 0: - return test.UNCLEAR("No senders supporting inputs") - return test.PASS() - - def test_02_03_01(self, test): - """ - Verify that the input is valid - """ - if len(self.input_senders) != 0: - for sender_id in self.input_senders: - valid, response = self.do_request( - "GET", self.compat_url + "senders/" + sender_id + "/inputs/" - ) - if not valid: - return test.FAIL("Unexpected response from the streamcompatibility API: {}".format(response)) - if response.status_code != 200: - return test.FAIL("The sender {} inputs streamcompatibility request has failed: {}" - .format(sender_id, response)) - try: - inputs = response.json() - except json.JSONDecodeError: - return test.FAIL("Non-JSON response returned from Node API") - except KeyError as e: - return test.FAIL("Unable to find expected key: {}".format(e)) - if len(inputs) == 0: - return test.UNCLEAR("No inputs") - for input_id in inputs: - if input_id not in self.inputs: - return test.FAIL("The input does not exist") - self.some_input[sender_id] = input_id - return test.PASS() - return test.UNCLEAR("No resources found to perform this test") - - def _test_02_03_02(self, test): - """ - Verify that the input passed its test suite - """ - if len(self.input_senders) != 0: - for sender_id in self.input_senders: - valid, response = self.do_request( - "GET", self.compat_url + "senders/" + sender_id + "/inputs/" - ) - if not valid: - return test.FAIL("Unexpected response from the streamcompatibility API: {}".format(response)) - if response.status_code != 200: - return test.FAIL("The sender {} inputs streamcompatibility request has failed: {}" - .format(sender_id, response)) - try: - inputs = response.json() - except json.JSONDecodeError: - return test.FAIL("Non-JSON response returned from Node API") - except KeyError as e: - return test.FAIL("Unable to find expected key: {}".format(e)) - if len(inputs) == 0: - return test.UNCLEAR("No inputs") - for input_id in inputs: - if ( - input_id not in self.edid_connected_inputs - and input_id not in self.not_edid_connected_inputs - ): - print("Input does not exist.") - break - if input_id in self.edid_connected_inputs and not self.test_01_04_00( - test - ): - return test.FAIL("Input supporting EDID failed test suite") - if ( - input_id in self.not_edid_connected_inputs - and not self.test_01_05_00(test) - ): - return test.FAIL("Input not supporting EDID failed test suite") - return test.PASS() - return test.UNCLEAR("No resources found to perform this test") - - def test_02_03_03(self, test): - """ - Verify that the status is "unconstrained" as per our pre-conditions - """ - - if len(self.input_senders) > 0: - for sender_id in self.input_senders: - valid, response = self.do_request( - "GET", self.build_sender_status_url(sender_id) - ) - if not valid: - return test.FAIL("Unexpected response from the Stream Compatibility Management API: {}" - .format(response)) - if response.status_code != 200: - return test.FAIL( - "The streamcompatibility request for sender {} status has failed: {}" - .format(sender_id, response.json()) - ) - try: - state = response.json()["state"] - except json.JSONDecodeError: - return test.FAIL("Non-JSON response returned from the Stream Compatibility Management API") - except KeyError as e: - return test.FAIL("Unable to find expected key: {}".format(e)) - - if state in ["awaiting_essence", "no_essence"]: - for i in range(0, CONFIG.STABLE_STATE_ATTEMPTS): - valid, response = self.do_request( - "GET", self.build_sender_status_url(sender_id) - ) - if not valid: - return test.FAIL("Unexpected response from the streamcompatibility API: {}" - .format(response)) - if response.status_code != 200: - return test.FAIL( - "The streamcompatibility request for sender {} status has failed: {}" - .format(sender_id, response.json()) - ) - try: - state = response.json()["state"] - except json.JSONDecodeError: - return test.FAIL("Non-JSON response returned from the Stream Compatibility Management API") - except KeyError as e: - return test.FAIL("Unable to find expected key: {}".format(e)) - - if state in ["awaiting_essence", "no_essence"]: - time.sleep(CONFIG.STABLE_STATE_DELAY) - else: - break - if state != "unconstrained": - return test.FAIL("Expected state of sender {} is \"unconstrained\", got \"{}\"" - .format(sender_id, state)) - return test.PASS() - return test.UNCLEAR("There are no IS-11 senders with associated Inputs") - - def test_02_03_04(self, test): - """ - Verify for inputs supporting EDID and supporting changing the base EDID - """ - if len(self.input_senders) != 0: - for sender_id in self.input_senders: - valid, response = self.do_request( - "GET", self.compat_url + "senders/" + sender_id + "/inputs/" - ) - if not valid: - return test.FAIL("Unexpected response from the streamcompatibility API: {}" - .format(response)) - if response.status_code != 200: - return test.FAIL("The sender {} inputs streamcompatibility request has failed: {}" - .format(sender_id, response)) - inputs = [] - try: - for input_id in response.json(): - if ( - input_id in self.edid_connected_inputs - and input_id in self.base_edid_inputs - ): - inputs.append(input_id) - else: - print( - "Inputs {} are not connected or does'nt support base Edid".format( - input_id - ) - ) - break - except json.JSONDecodeError: - return test.FAIL("Non-JSON response returned from Node API") - except KeyError as e: - return test.FAIL("Unable to find expected key: {}".format(e)) - if len(inputs) == 0: - return test.UNCLEAR("No input supports changing the base EDID") - for input_id in inputs: - valid, response = self.do_request( - "GET", self.compat_url + "inputs/" + input_id + "/properties/" - ) - if not valid: - return test.FAIL("Unexpected response from the streamcompatibility API: {}".format(response)) - if response.status_code != 200: - return test.FAIL("The input {} properties streamcompatibility request has failed: {}" - .format(input_id, response)) - try: - version = response.json()["version"] - except json.JSONDecodeError: - return test.FAIL("Non-JSON response returned from Node API") - except KeyError as e: - return test.FAIL("Unable to find expected key: {}".format(e)) - self.version[input_id] = version - - valid, response = self.do_request( - "GET", self.node_url + "senders/" + sender_id - ) - if not valid: - return test.FAIL("Unexpected response from the Node API: {}".format(response)) - if response.status_code != 200: - return test.FAIL("The sender {} is not available in the Node API request: {}" - .format(sender_id, response)) - try: - version = response.json()["version"] - except json.JSONDecodeError: - return test.FAIL("Non-JSON response returned from Node API") - except KeyError as e: - return test.FAIL("Unable to find expected key: {}".format(e)) - self.version[sender_id] = version - - valid, response = self.do_request("PUT", - self.compat_url + "inputs/" + input_id + "/edid/base", - headers={"Content-Type": "application/octet-stream"}, - data=self.valid_edid) - if not valid or response.status_code != 204: - return test.FAIL("Unexpected response from the Stream Compatibility Management API: {}" - .format(response)) - time.sleep(CONFIG.STABLE_STATE_DELAY) - - valid, response = self.do_request( - "GET", self.compat_url + "inputs/" + input_id + "/properties/" - ) - if not valid: - return test.FAIL("Unexpected response from the streamcompatibility API: {}".format(response)) - if response.status_code != 200: - return test.FAIL("The input {} properties streamcompatibility request has failed: {}" - .format(input_id, response)) - try: - version = response.json()["version"] - except json.JSONDecodeError: - return test.FAIL("Non-JSON response returned from Node API") - except KeyError as e: - return test.FAIL("Unable to find expected key: {}".format(e)) - if version == self.version[input_id]: - return test.FAIL("Version should change") - - valid, response = self.do_request( - "GET", self.node_url + "senders/" + sender_id - ) - if not valid: - return test.FAIL("Unexpected response from the Node API: {}".format(response)) - if response.status_code != 200: - return test.FAIL("The sender {} is not available in the Node API request: {}" - .format(sender_id, response)) - try: - version = response.json()["version"] - except json.JSONDecodeError: - return test.FAIL("Non-JSON response returned from Node API") - except KeyError as e: - return test.FAIL("Unable to find expected key: {}".format(e)) - if version == self.version[input_id]: - return test.FAIL("Version should change") + if len(inputs) == 0: + return test.UNCLEAR("No inputs") + for input_id in inputs: + if input_id not in self.inputs: + return test.FAIL("The input does not exist") - valid, response = self.do_request( - "DELETE", self.compat_url + "inputs/" + input_id + "/edid/base/" - ) - if not valid: - return test.FAIL("Unexpected response from the streamcompatibility API: {}".format(response)) - if response.status_code != 204: - return test.FAIL("The input {} base edid cannot be deleted".format(input_id)) - return test.PASS() - return test.UNCLEAR("No resources found to perform this test.") + return test.PASS() def test_02_03_05_01(self, test): """ @@ -1813,99 +1575,6 @@ def test_02_03_05_02(self, test): return test.PASS() return test.UNCLEAR("No resources found to perform this test.") - def test_02_04(self, test): - """ - Verify senders not supporting inputs - """ - for input in self.senders: - valid, response = self.do_request( - "GET", self.compat_url + "senders/" + input + "/inputs/" - ) - if not valid: - return test.FAIL( - "Unexpected response from the streamcompatibility API: {}".format( - response - ) - ) - if response.status_code != 200: - return test.FAIL( - "The sender's inputs {} streamcompatibility request has failed: {}".format( - input, response - ) - ) - try: - if len(response.json()) == 0: - self.not_input_senders.append(input) - except json.JSONDecodeError: - return test.FAIL("Non-JSON response returned from Node API") - except KeyError as e: - return test.FAIL("Unable to find expected key: {}".format(e)) - - if len(self.not_input_senders) == 0: - return test.UNCLEAR("All senders support inputs") - return test.PASS() - - def test_02_04_01(self, test): - """ - Verify that the status is "unconstrained" as per our pre-conditions - """ - if len(self.not_input_senders) == 0: - return test.UNCLEAR("All senders support inputs") - for sender_id in self.not_input_senders: - valid, response = self.do_request( - "GET", - self.compat_url + "senders/" + sender_id + "/status/", - ) - if not valid: - return test.FAIL( - "Unexpected response from the Node API: {}".format(response) - ) - if response.status_code != 200: - return test.FAIL( - "The sender {} is not available in the Node API request: {}".format( - sender_id, response - ) - ) - - time.sleep(CONFIG.STABLE_STATE_DELAY) - try: - state = response.json()["state"] - except json.JSONDecodeError: - return test.FAIL("Non-JSON response returned from Node API") - except KeyError as e: - return test.FAIL("Unable to find expected key: {}".format(e)) - if state != "OK": - return test.FAIL("The status is incorrect") - - if state in ["awaiting_essence", "no_essence"]: - for i in range(0, CONFIG.STABLE_STATE_ATTEMPTS): - valid, response = self.do_request( - "GET", self.build_sender_status_url(sender_id) - ) - if not valid: - return test.FAIL("Unexpected response from the streamcompatibility API: {}" - .format(response)) - if response.status_code != 200: - return test.FAIL( - "The streamcompatibility request for sender {} status has failed: {}" - .format(sender_id, response.json()) - ) - try: - state = response.json()["state"] - except json.JSONDecodeError: - return test.FAIL("Non-JSON response returned from the Stream Compatibility Management API") - except KeyError as e: - return test.FAIL("Unable to find expected key: {}".format(e)) - - if state in ["awaiting_essence", "no_essence"]: - time.sleep(CONFIG.STABLE_STATE_DELAY) - else: - break - if state != "unconstrained": - return test.FAIL("Expected state of sender {} is \"unconstrained\", got \"{}\"" - .format(sender_id, state)) - return test.PASS() - # OUTPUTS TESTS def test_03_00(self, test): """Connected Outputs with EDID support return the EDID""" @@ -2597,6 +2266,8 @@ def deactivate_connection_resources(self, port): raise NMOSInitException("Non-JSON response returned from the Connection API") def has_i_o(self, id, type): + assert type in ["sender", "receiver"] + connector = "senders/" if type == "sender" else "receivers/" i_o = "/inputs/" if type == "sender" else "/outputs/" url = self.compat_url + connector + id + i_o @@ -2607,6 +2278,9 @@ def has_i_o(self, id, type): else: raise NMOSInitException("The request {} has failed: {}".format(url, r)) + def sender_has_i_o(self, id): + return self.has_i_o(id, "sender") + def receiver_has_i_o(self, id): return self.has_i_o(id, "receiver") From c5006764a31446a1dd1e7926b0404c7d4f141904 Mon Sep 17 00:00:00 2001 From: N-Nagorny <15526762+N-Nagorny@users.noreply.github.com> Date: Wed, 15 May 2024 22:37:04 +0400 Subject: [PATCH 4/5] Improve tear down --- nmostesting/suites/IS1101Test.py | 52 +++++++++++++++++++++----------- 1 file changed, 34 insertions(+), 18 deletions(-) diff --git a/nmostesting/suites/IS1101Test.py b/nmostesting/suites/IS1101Test.py index 869cebc6..bc6803ae 100644 --- a/nmostesting/suites/IS1101Test.py +++ b/nmostesting/suites/IS1101Test.py @@ -142,16 +142,18 @@ def set_up_tests(self): self.state_no_essence = "no_essence" self.state_awaiting_essence = "awaiting_essence" + self.delete_active_constraints() + self.delete_base_edid() + self.deactivate_connection_resources("sender") self.deactivate_connection_resources("receiver") + def tear_down_tests(self): self.delete_active_constraints() self.delete_base_edid() - def tear_down_tests(self): - for inputId in self.is11_utils.sampled_list(self.base_edid_inputs): - # DELETE the Base EDID of the Input - self.do_request("DELETE", self.compat_url + "inputs/" + inputId + "/edid/base") + self.deactivate_connection_resources("sender") + self.deactivate_connection_resources("receiver") # GENERAL TESTS def test_00_00(self, test): @@ -479,6 +481,17 @@ def is_edid_inequal_to_effective_edid(self, test, inputId, edid): if not result: return test.FAIL("Effective EDID doesn't change when Base EDID changes") + # PUT the same EDID with "adjust_to_caps": "false" + # to reset its value before running other tests + valid, response = self.do_request("PUT", + self.compat_url + "inputs/" + inputId + "/edid/base", + headers={"Content-Type": "application/octet-stream"}, + data=self.valid_edid, + params={"adjust_to_caps": "false"}) + if not valid or response.status_code != 204: + return test.FAIL("Unexpected response from " + "the Stream Compatibility Management API: {}".format(response)) + valid, response = self.do_request("DELETE", self.compat_url + "inputs/" + inputId + "/edid/base") if not valid or response.status_code != 204: return test.FAIL("Unexpected response from " @@ -2697,20 +2710,7 @@ def apply_nop_active_constraints(self, test, senders, make_active_constraints, s new_flow = self.get_senders_flow(test, sender_id) new_source = self.get_flows_source(test, new_flow["id"]) - for flow_attr in flow_attrs: - if (new_flow[flow_attr] != flow[flow_attr]): - return test.FAIL( - "The constraints were not expected to change the flow of sender {}" - .format(sender_id) - ) - - for source_attr in source_attrs: - if (new_source[source_attr] != source[source_attr]): - return test.FAIL( - "The constraints were not expected to change the Source of sender {}" - .format(sender_id) - ) - + # DELETE Active Constraints ASAP to tear down even if the test will fail valid, response = self.do_request( "DELETE", self.build_constraints_active_url(sender_id), @@ -2722,4 +2722,20 @@ def apply_nop_active_constraints(self, test, senders, make_active_constraints, s "The sender {} constraints cannot be deleted".format(sender_id) ) + # Check that Flow didn't change + for flow_attr in flow_attrs: + if (new_flow[flow_attr] != flow[flow_attr]): + return test.FAIL( + "The constraints were not expected to change Flow of Sender {} ({}: {} -> {})" + .format(sender_id, flow_attr, flow[flow_attr], new_flow[flow_attr]) + ) + + # Check that Source didn't change + for source_attr in source_attrs: + if (new_source[source_attr] != source[source_attr]): + return test.FAIL( + "The constraints were not expected to change Source of Sender {} ({}: {} -> {})" + .format(sender_id, source_attr, source[source_attr], new_source[source_attr]) + ) + return test.PASS() From b30005a899e7975f215199e3783e6c5e2613382e Mon Sep 17 00:00:00 2001 From: N-Nagorny <15526762+N-Nagorny@users.noreply.github.com> Date: Wed, 15 May 2024 22:37:26 +0400 Subject: [PATCH 5/5] Increase error verbosity --- nmostesting/suites/IS1101Test.py | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/nmostesting/suites/IS1101Test.py b/nmostesting/suites/IS1101Test.py index bc6803ae..26108d10 100644 --- a/nmostesting/suites/IS1101Test.py +++ b/nmostesting/suites/IS1101Test.py @@ -225,7 +225,7 @@ def test_01_01(self, test): def test_01_02(self, test): """Inputs with Base EDID support handle PUTting and DELETing the Base EDID""" def is_edid_equal_to_effective_edid(self, test, inputId, edid): - return self.get_effective_edid(test, inputId) == edid + return self.get_effective_edid(test, inputId) == edid, "" if len(self.base_edid_inputs) == 0: return test.UNCLEAR("Not tested. No inputs with Base EDID support found.") @@ -454,10 +454,10 @@ def test_01_06(self, test): """Effective EDID updates if Base EDID changes with 'adjust_to_caps'""" def is_edid_equal_to_effective_edid(self, test, inputId, edid): - return self.get_effective_edid(test, inputId) == edid + return self.get_effective_edid(test, inputId) == edid, "" def is_edid_inequal_to_effective_edid(self, test, inputId, edid): - return self.get_effective_edid(test, inputId) != edid + return self.get_effective_edid(test, inputId) != edid, "" if len(self.adjust_to_caps_inputs) == 0: return test.UNCLEAR("Not tested. No inputs with 'adjust_to_caps' support found.") @@ -2469,11 +2469,13 @@ def get_outputs_edid(self, test, output_id): return response.content def wait_until_true(self, predicate): + err = "" for i in range(0, CONFIG.STABLE_STATE_ATTEMPTS): - if predicate(): + result, err = predicate() + if result: return True time.sleep(CONFIG.STABLE_STATE_DELAY) - return False + return False, err def has_sender_flow_format(self, sender_id, format): assert format in ["video", "audio"] @@ -2664,7 +2666,8 @@ def is_sender_state_equal_to_expected(self, test, sender_id, expected): "The streamcompatibility request for sender {} status has failed: {}" .format(sender_id, response.json()) ) - return response.json()["state"] == expected + actual_state = response.json()["state"] + return actual_state == expected, actual_state def apply_nop_active_constraints(self, test, senders, make_active_constraints, source_attrs, flow_attrs): if len(senders) == 0: