Skip to content

Commit f47247a

Browse files
Refactor Testing Facade out of Controller Tests into utility class (#872)
Factor testing facade interaction out of ControllerTest
1 parent 41e4cbc commit f47247a

File tree

8 files changed

+277
-207
lines changed

8 files changed

+277
-207
lines changed

nmostesting/Config.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -397,7 +397,7 @@
397397
}
398398
}
399399
},
400-
"controller-tests": {
400+
"testing-facade": {
401401
"repo": None,
402402
"versions": ["v1.0"],
403403
"default_version": "v1.0",

nmostesting/ControllerTest.py

Lines changed: 10 additions & 150 deletions
Original file line numberDiff line numberDiff line change
@@ -12,87 +12,41 @@
1212
# See the License for the specific language governing permissions and
1313
# limitations under the License.
1414

15-
import asyncio
1615
import time
1716
import json
1817
import uuid
19-
import inspect
2018
import random
2119
import textwrap
2220
from copy import deepcopy
2321
from urllib.parse import urlparse
2422
from dnslib import QTYPE
25-
from threading import Event
2623

2724
from .GenericTest import GenericTest, NMOSTestException, NMOSInitException
2825
from . import Config as CONFIG
2926
from .TestHelper import get_default_ip, get_mocks_hostname
3027
from .TestResult import Test
3128
from .NMOSUtils import NMOSUtils
29+
from .TestingFacadeUtils import TestingFacadeUtils, TestingFacadeException
3230

33-
from flask import Flask, Blueprint, request
34-
35-
CONTROLLER_TEST_API_KEY = "testquestion"
31+
TESTING_FACADE_API_KEY = "testquestion"
3632
QUERY_API_KEY = "query"
3733
CONN_API_KEY = "connection"
3834
REG_API_KEY = "registration"
3935

40-
CALLBACK_ENDPOINT = "/x-nmos/testanswer/<version>"
41-
42-
# asyncio queue for passing Testing Façade answer responses back to tests
43-
_event_loop = asyncio.new_event_loop()
44-
asyncio.set_event_loop(_event_loop)
45-
_answer_response_queue = asyncio.Queue()
46-
47-
# use exit Event to quit tests early that involve waiting for senders/connections
48-
exitTestEvent = Event()
49-
50-
app = Flask(__name__)
51-
TEST_API = Blueprint('test_api', __name__)
52-
53-
54-
class TestingFacadeException(Exception):
55-
"""Exception thrown due to comms or data errors between NMOS Testing and Testing Façade"""
56-
pass
57-
58-
59-
@TEST_API.route(CALLBACK_ENDPOINT, methods=['POST'])
60-
def receive_answer(version):
61-
62-
if request.method == 'POST':
63-
if 'question_id' not in request.json:
64-
return 'Invalid JSON received', 400
65-
66-
answer_json = request.json
67-
answer_json['time_received'] = time.time()
68-
_event_loop.call_soon_threadsafe(_answer_response_queue.put_nowait, answer_json)
69-
70-
# Interrupt any 'sleeps' that are still active
71-
exitTestEvent.set()
72-
73-
return '', 202
74-
7536

7637
class ControllerTest(GenericTest):
7738
"""
7839
Testing initial set up of new test suite for controller testing
7940
"""
8041
def __init__(self, apis, registries, node, dns_server, auths, disable_auto=True, **kwargs):
81-
# Remove the spec_path as there are no corresponding GitHub repos for Controller Tests
82-
apis[CONTROLLER_TEST_API_KEY].pop("spec_path", None)
42+
# Remove the Testing Facade spec_path as there are no corresponding GitHub repos for the Testing Facade API
43+
apis[TESTING_FACADE_API_KEY].pop("spec_path", None)
8344
# Ensure registration scope is added to JWT to allow authenticated
8445
# registration of mock resources with secure mock registry
8546
if CONFIG.ENABLE_AUTH:
8647
apis[REG_API_KEY] = {}
87-
if CONFIG.ENABLE_HTTPS:
88-
# Comms with Testing Facade are http only
89-
if apis[CONTROLLER_TEST_API_KEY]["base_url"] is not None:
90-
apis[CONTROLLER_TEST_API_KEY]["base_url"] \
91-
= apis[CONTROLLER_TEST_API_KEY]["base_url"].replace("https", "http")
92-
if apis[CONTROLLER_TEST_API_KEY]["url"] is not None:
93-
apis[CONTROLLER_TEST_API_KEY]["url"] \
94-
= apis[CONTROLLER_TEST_API_KEY]["url"].replace("https", "http")
9548
GenericTest.__init__(self, apis, auths=auths, disable_auto=disable_auto, **kwargs)
49+
self.testing_facade_utils = TestingFacadeUtils(apis)
9650
self.primary_registry = registries[1] if registries else None
9751
self.node = node
9852
self.dns_server = dns_server
@@ -109,10 +63,6 @@ def __init__(self, apis, registries, node, dns_server, auths, disable_auto=True,
10963
if QUERY_API_KEY in apis and "version" in self.apis[QUERY_API_KEY] else "v1.3"
11064
self.connection_api_version = self.apis[CONN_API_KEY]["version"] \
11165
if CONN_API_KEY in apis and "version" in self.apis[CONN_API_KEY] else "v1.1"
112-
self.qa_api_version = self.apis[CONTROLLER_TEST_API_KEY]["version"] \
113-
if CONTROLLER_TEST_API_KEY in apis and "version" in self.apis[CONTROLLER_TEST_API_KEY] else "v1.0"
114-
self.answer_uri = "http://" + get_default_ip() + ":" + str(CONFIG.PORT_BASE) + \
115-
CALLBACK_ENDPOINT.replace('<version>', self.qa_api_version)
11666

11767
def set_up_tests(self):
11868
test = Test("Test setup", "set_up_tests")
@@ -150,8 +100,7 @@ def tear_down_tests(self):
150100
if self.primary_registry:
151101
self.primary_registry.disable()
152102

153-
# Reset the state of the Testing Façade
154-
self.do_request("POST", self.apis[CONTROLLER_TEST_API_KEY]["url"], json={"clear": "True"})
103+
self.testing_facade_utils.reset()
155104

156105
if self.dns_server:
157106
self.dns_server.reset()
@@ -174,96 +123,7 @@ def execute_tests(self, test_names):
174123

175124
self.post_tests_message()
176125

177-
async def get_answer_response(self, timeout):
178-
# Add API processing time to specified timeout
179-
# Otherwise, if timeout is None then disable timeout mechanism
180-
timeout = timeout + (2 * CONFIG.API_PROCESSING_TIMEOUT) if timeout is not None else None
181-
182-
return await asyncio.wait_for(_answer_response_queue.get(), timeout=timeout)
183-
184-
def _send_testing_facade_questions(
185-
self,
186-
test_method_name,
187-
question,
188-
answers,
189-
test_type,
190-
multipart_test=None,
191-
metadata=None):
192-
"""
193-
Send question and answers to Testing Façade
194-
question: text to be presented to Test User
195-
answers: list of all possible answers
196-
test_type: "single_choice" - one and only one answer
197-
"multi_choice" - multiple answers
198-
"action" - Test User asked to click button
199-
multipart_test: indicates test uses multiple questions. Default None, should be increasing
200-
integers with each subsequent call within the same test
201-
metadata: Test details to assist fully automated testing
202-
"""
203-
204-
method = getattr(self, test_method_name)
205-
206-
timeout = CONFIG.CONTROLLER_TESTING_TIMEOUT
207-
208-
question_id = test_method_name if not multipart_test else test_method_name + '_' + str(multipart_test)
209-
210-
json_out = {
211-
"test_type": test_type,
212-
"question_id": question_id,
213-
"name": test_method_name,
214-
"description": inspect.getdoc(method),
215-
"question": question,
216-
"answers": answers,
217-
"timeout": timeout,
218-
"answer_uri": self.answer_uri,
219-
"metadata": metadata
220-
}
221-
# Send questions to Testing Façade API endpoint then wait
222-
valid, response = self.do_request("POST", self.apis[CONTROLLER_TEST_API_KEY]["url"], json=json_out)
223-
224-
if not valid or response.status_code != 202:
225-
raise TestingFacadeException("Problem contacting Testing Façade: " + response.text if valid else response)
226-
227-
return json_out
228-
229-
def _wait_for_testing_facade(self, question_id, test_type):
230-
231-
# Wait for answer response or question timeout in seconds. A timeout of None will wait indefinitely
232-
timeout = CONFIG.CONTROLLER_TESTING_TIMEOUT
233-
234-
try:
235-
answer_response = _event_loop.run_until_complete(self.get_answer_response(timeout))
236-
except asyncio.TimeoutError:
237-
raise TestingFacadeException("Test timed out")
238-
239-
# Basic integrity check for response json
240-
if answer_response['question_id'] is None:
241-
raise TestingFacadeException("Integrity check failed: result format error: "
242-
+ json.dump(answer_response))
243-
244-
if answer_response['question_id'] != question_id:
245-
raise TestingFacadeException(
246-
"Integrity check failed: cannot compare result of " + question_id +
247-
" with expected result for " + answer_response['question_id'])
248-
249-
# Multi_choice question submitted without any answers should be an empty list
250-
if test_type == 'multi_choice' and answer_response['answer_response'] is None:
251-
answer_response['answer_response'] = []
252-
253-
return answer_response
254-
255-
def _invoke_testing_facade(self, question, answers, test_type,
256-
multipart_test=None, metadata=None, test_method_name=None):
257-
# Get the name of the calling test method to use as an identifier
258-
test_method_name = test_method_name if test_method_name \
259-
else inspect.currentframe().f_back.f_code.co_name
260-
261-
json_out = self._send_testing_facade_questions(
262-
test_method_name, question, answers, test_type, multipart_test, metadata)
263-
264-
return self._wait_for_testing_facade(json_out['question_id'], test_type)
265-
266-
def _generate_random_indices(self, index_range, min_index_count=2, max_index_count=4):
126+
def generate_random_indices(self, index_range, min_index_count=2, max_index_count=4):
267127
"""
268128
index_range: number of possible indices
269129
min_index_count, max_index_count: Minimum, maximum number of indices to be returned.
@@ -482,7 +342,7 @@ def _register_sender(self, test, sender, codes=[201], fail=Test.FAIL):
482342
sender_data = self._create_sender_json(sender)
483343
self.post_resource(test, "sender", sender_data, codes=codes, fail=fail)
484344

485-
def _delete_sender(self, test, sender):
345+
def delete_sender(self, test, sender):
486346
del_url = self.mock_registry_base_url + 'x-nmos/registration/v1.3/resource/senders/' + sender['id']
487347

488348
valid, r = self.do_request("DELETE", del_url)
@@ -588,7 +448,7 @@ def pre_tests_message(self):
588448
""")
589449

590450
try:
591-
self._invoke_testing_facade(question, [], test_type="action")
451+
self.testing_facade_utils.invoke_testing_facade(question, [], test_type="action")
592452

593453
except TestingFacadeException:
594454
# pre_test_introducton timed out
@@ -607,7 +467,7 @@ def post_tests_message(self):
607467
"""
608468

609469
try:
610-
self._invoke_testing_facade(question, [], test_type="action")
470+
self.testing_facade_utils.invoke_testing_facade(question, [], test_type="action")
611471

612472
except TestingFacadeException:
613473
# post_test_introducton timed out

nmostesting/NMOSTesting.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@
4848
from . import Config as CONFIG
4949
from .DNS import DNS
5050
from .GenericTest import NMOSInitException
51-
from . import ControllerTest
51+
from . import TestingFacadeUtils
5252
from .TestResult import TestStates
5353
from .TestHelper import get_default_ip
5454
from .NMOSUtils import DEFAULT_ARGS
@@ -109,7 +109,7 @@
109109
core_app.config['PORT'] = CONFIG.PORT_BASE
110110
core_app.config['SECURE'] = False
111111
core_app.register_blueprint(NODE_API) # Dependency for IS0401Test
112-
core_app.register_blueprint(ControllerTest.TEST_API)
112+
core_app.register_blueprint(TestingFacadeUtils.TEST_API)
113113
FLASK_APPS.append(core_app)
114114

115115
for instance in range(NUM_REGISTRIES):
@@ -218,7 +218,7 @@
218218
"IS-04-04": {
219219
"name": "IS-04 Controller",
220220
"specs": [{
221-
"spec_key": "controller-tests",
221+
"spec_key": "testing-facade",
222222
"api_key": "testquestion"
223223
}, {
224224
"spec_key": "is-04",
@@ -249,7 +249,7 @@
249249
"IS-05-03": {
250250
"name": "IS-05 Controller",
251251
"specs": [{
252-
"spec_key": "controller-tests",
252+
"spec_key": "testing-facade",
253253
"api_key": "testquestion"
254254
}, {
255255
"spec_key": "is-04",
@@ -373,7 +373,7 @@
373373
"api_key": "controlframework",
374374
"disable_fields": ["host", "port", "urlpath"]
375375
}, {
376-
"spec_key": "controller-tests",
376+
"spec_key": "testing-facade",
377377
"api_key": "testquestion",
378378
"disable_fields": ["urlpath"] if CONFIG.MS05_INTERACTIVE_TESTING else ["host", "port", "urlpath"]
379379
}],
@@ -418,7 +418,7 @@
418418
"BCP-006-01-02": {
419419
"name": "BCP-006-01 Controller",
420420
"specs": [{
421-
"spec_key": "controller-tests",
421+
"spec_key": "testing-facade",
422422
"api_key": "testquestion"
423423
}, {
424424
"spec_key": "is-04",

0 commit comments

Comments
 (0)