From e94824991c1f956149ec6a68c617e707d432a9d3 Mon Sep 17 00:00:00 2001 From: Max Leske Date: Tue, 9 Nov 2021 07:41:02 +0100 Subject: [PATCH 01/19] Improved performance improvements by removing arbitrary timeouts --- ftw/http.py | 107 +++++++++++++++++++++++++++++----------------------- 1 file changed, 59 insertions(+), 48 deletions(-) diff --git a/ftw/http.py b/ftw/http.py index 58e3366..1e9aebb 100644 --- a/ftw/http.py +++ b/ftw/http.py @@ -11,6 +11,7 @@ import sys import time import zlib +import select import brotli from IPy import IP @@ -267,7 +268,6 @@ def __init__(self): 'ADH-AES256-SHA:ECDHE-ECDSA-AES128-GCM-SHA256:' \ 'ECDHE-RSA-AES128-GCM-SHA256:AES128-GCM-SHA256:AES128-SHA256:HIGH:' self.CRLF = '\r\n' - self.HTTP_TIMEOUT = .3 self.RECEIVE_BYTES = 8192 self.SOCKET_TIMEOUT = 5 @@ -466,38 +466,71 @@ def get_response(self): """ Get the response from the socket """ - self.sock.setblocking(0) our_data = [] - # Beginning time - begin = time.time() + self.sock.setblocking(False) + try: + our_data = self.read_response_from_socket() + finally: + try: + self.sock.shutdown(socket.SHUT_WR) + self.sock.close() + except OSError as err: + raise errors.TestError( + 'We were unable to close the socket as expected.', + { + 'msg': err, + 'function': 'http.HttpUA.get_response' + }) + else: + self.response_object = HttpResponse(b''.join(our_data), self) + finally: + if not b''.join(our_data): + raise errors.TestError( + 'No response from server. Request likely timed out.', + { + 'host': self.request_object.dest_addr, + 'port': self.request_object.port, + 'proto': self.request_object.protocol, + 'msg': 'Please send the request and check Wireshark', + 'function': 'http.HttpUA.get_response' + }) + + def read_response_from_socket(self): + # wait for socket to become ready + ready_sock, _, _ = select.select([self.sock], [], [self.sock], self.SOCKET_TIMEOUT) + if not ready_sock: + raise errors.TestError( + f'No response from server within {self.SOCKET_TIMEOUT} seconds', + { + 'host': self.request_object.dest_addr, + 'port': self.request_object.port, + 'proto': self.request_object.protocol, + 'msg': 'Please send the request and check Wireshark', + 'function': 'http.HttpUA.get_response' + }) + + our_data = [] while True: - # If we have data then if we're passed the timeout break - if our_data and time.time() - begin > self.HTTP_TIMEOUT: - break - # If we're dataless wait just a bit - elif time.time() - begin > self.HTTP_TIMEOUT * 2: - break - # Recv data try: data = self.sock.recv(self.RECEIVE_BYTES) - if data: - our_data.append(util.ensure_binary(data)) - begin = time.time() - else: - # Sleep for sometime to indicate a gap - time.sleep(self.HTTP_TIMEOUT) - except socket.error as err: - # Check if we got a timeout - if err.errno == errno.EAGAIN: - pass + if len(data) == 0: + # we're done + break + our_data.append(util.ensure_binary(data)) + except BlockingIOError as e: + if e.errno == socket.EAGAIN: + # we're done + break + # something else happened + pass + except OSError as err: # SSL will return SSLWantRead instead of EAGAIN - elif sys.platform == 'win32' and \ - err.errno == errno.WSAEWOULDBLOCK: + if sys.platform == 'win32' and err.errno == errno.WSAEWOULDBLOCK: pass elif (self.request_object.protocol == 'https' and - err.args[0] == ssl.SSL_ERROR_WANT_READ): + err.args[0] == ssl.SSL_ERROR_WANT_READ): continue - # If we didn't it's an error + # It's an error else: raise errors.TestError( 'Failed to connect to server', @@ -508,26 +541,4 @@ def get_response(self): 'message': err, 'function': 'http.HttpUA.get_response' }) - try: - self.sock.shutdown(socket.SHUT_WR) - self.sock.close() - except socket.error as err: - raise errors.TestError( - 'We were unable to close the socket as expected.', - { - 'msg': err, - 'function': 'http.HttpUA.get_response' - }) - else: - self.response_object = HttpResponse(b''.join(our_data), self) - finally: - if not b''.join(our_data): - raise errors.TestError( - 'No response from server. Request likely timed out.', - { - 'host': self.request_object.dest_addr, - 'port': self.request_object.port, - 'proto': self.request_object.protocol, - 'msg': 'Please send the request and check Wireshark', - 'function': 'http.HttpUA.get_response' - }) + return our_data \ No newline at end of file From 69f2902b5191f908c5bd2783ce4ac7c64411a072 Mon Sep 17 00:00:00 2001 From: Max Leske Date: Tue, 9 Nov 2021 07:42:29 +0100 Subject: [PATCH 02/19] Added marker methods to log checker The marker methods are called by the test runner and enable the log checker to perform setup actions before the request is sent and after the response has been received --- ftw/logchecker.py | 12 ++++++++++++ ftw/testrunner.py | 12 +++++++++--- 2 files changed, 21 insertions(+), 3 deletions(-) diff --git a/ftw/logchecker.py b/ftw/logchecker.py index 3f3d557..a4cdfe2 100644 --- a/ftw/logchecker.py +++ b/ftw/logchecker.py @@ -15,6 +15,18 @@ def set_times(self, start, end): self.start = start self.end = end + def mark_start(selfj): + """ + May be implemented to set up the log checker before the request is being sent + """ + pass + + def mark_end(self): + """ + May be implemented to tell the log checker that the response has been received + """ + pass + @abstractmethod def get_logs(self): """ diff --git a/ftw/testrunner.py b/ftw/testrunner.py index 5f1c907..1654aec 100644 --- a/ftw/testrunner.py +++ b/ftw/testrunner.py @@ -187,11 +187,17 @@ def run_stage(self, stage, logger_obj=None, http_ua=None): else: if not http_ua: http_ua = http.HttpUA() - start = datetime.datetime.utcnow() + if (stage.output.log_contains_str or + stage.output.no_log_contains_str) and logger_obj is not None: + logger_obj.mark_start() + start = datetime.datetime.utcnow() http_ua.send_request(stage.input) - end = datetime.datetime.utcnow() + if (stage.output.log_contains_str or + stage.output.no_log_contains_str) and logger_obj is not None: + logger_obj.mark_end() + end = datetime.datetime.utcnow() if (stage.output.log_contains_str or - stage.output.no_log_contains_str) and logger_obj is not None: + stage.output.no_log_contains_str) and logger_obj is not None: logger_obj.set_times(start, end) lines = logger_obj.get_logs() if stage.output.log_contains_str: From 481dc3655757b0cb917db13a4c4deb0b16ad8a58 Mon Sep 17 00:00:00 2001 From: Max Leske Date: Tue, 9 Nov 2021 07:53:01 +0100 Subject: [PATCH 03/19] Fixed line lengths --- ftw/http.py | 31 +++++++++++++++++-------------- ftw/logchecker.py | 6 ++++-- ftw/testrunner.py | 15 +++++++++------ 3 files changed, 30 insertions(+), 22 deletions(-) diff --git a/ftw/http.py b/ftw/http.py index 1e9aebb..35289c3 100644 --- a/ftw/http.py +++ b/ftw/http.py @@ -484,23 +484,25 @@ def get_response(self): else: self.response_object = HttpResponse(b''.join(our_data), self) finally: - if not b''.join(our_data): - raise errors.TestError( - 'No response from server. Request likely timed out.', - { - 'host': self.request_object.dest_addr, - 'port': self.request_object.port, - 'proto': self.request_object.protocol, - 'msg': 'Please send the request and check Wireshark', - 'function': 'http.HttpUA.get_response' - }) + if b''.join(our_data): + return + raise errors.TestError( + 'No response from server. Request likely timed out.', + { + 'host': self.request_object.dest_addr, + 'port': self.request_object.port, + 'proto': self.request_object.protocol, + 'msg': 'Please send the request and check Wireshark', + 'function': 'http.HttpUA.get_response' + }) def read_response_from_socket(self): # wait for socket to become ready - ready_sock, _, _ = select.select([self.sock], [], [self.sock], self.SOCKET_TIMEOUT) + ready_sock, _, _ = select.select( + [self.sock], [], [self.sock], self.SOCKET_TIMEOUT) if not ready_sock: raise errors.TestError( - f'No response from server within {self.SOCKET_TIMEOUT} seconds', + f'No response from server within {self.SOCKET_TIMEOUT}s', { 'host': self.request_object.dest_addr, 'port': self.request_object.port, @@ -518,14 +520,15 @@ def read_response_from_socket(self): break our_data.append(util.ensure_binary(data)) except BlockingIOError as e: - if e.errno == socket.EAGAIN: + if e.errno == socket.EAGAIN or e.errno == socket.EWOULDBLOCK: # we're done break # something else happened pass except OSError as err: # SSL will return SSLWantRead instead of EAGAIN - if sys.platform == 'win32' and err.errno == errno.WSAEWOULDBLOCK: + if (sys.platform == 'win32' and + err.errno == errno.WSAEWOULDBLOCK): pass elif (self.request_object.protocol == 'https' and err.args[0] == ssl.SSL_ERROR_WANT_READ): diff --git a/ftw/logchecker.py b/ftw/logchecker.py index a4cdfe2..4b09a2e 100644 --- a/ftw/logchecker.py +++ b/ftw/logchecker.py @@ -17,13 +17,15 @@ def set_times(self, start, end): def mark_start(selfj): """ - May be implemented to set up the log checker before the request is being sent + May be implemented to set up the log checker before + the request is being sent """ pass def mark_end(self): """ - May be implemented to tell the log checker that the response has been received + May be implemented to tell the log checker that + the response has been received """ pass diff --git a/ftw/testrunner.py b/ftw/testrunner.py index 1654aec..32f0384 100644 --- a/ftw/testrunner.py +++ b/ftw/testrunner.py @@ -187,17 +187,20 @@ def run_stage(self, stage, logger_obj=None, http_ua=None): else: if not http_ua: http_ua = http.HttpUA() - if (stage.output.log_contains_str or - stage.output.no_log_contains_str) and logger_obj is not None: + if ((stage.output.log_contains_str or + stage.output.no_log_contains_str) and + logger_obj is not None): logger_obj.mark_start() start = datetime.datetime.utcnow() http_ua.send_request(stage.input) - if (stage.output.log_contains_str or - stage.output.no_log_contains_str) and logger_obj is not None: + if ((stage.output.log_contains_str or + stage.output.no_log_contains_str) and + logger_obj is not None): logger_obj.mark_end() end = datetime.datetime.utcnow() - if (stage.output.log_contains_str or - stage.output.no_log_contains_str) and logger_obj is not None: + if ((stage.output.log_contains_str or + stage.output.no_log_contains_str) and + logger_obj is not None): logger_obj.set_times(start, end) lines = logger_obj.get_logs() if stage.output.log_contains_str: From 2b232f8c719e9c3e9445884e82abb4a97186d891 Mon Sep 17 00:00:00 2001 From: Max Leske Date: Tue, 9 Nov 2021 07:53:25 +0100 Subject: [PATCH 04/19] Updated all requirements to latest versions --- requirements.txt | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/requirements.txt b/requirements.txt index 1bf435b..b46f7dc 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,5 +1,5 @@ -Brotli==1.0.7 -IPy==0.83 -PyYAML==5.4 -pytest==4.6 -python-dateutil==2.6.0 +Brotli==1.0.9 +IPy==1.01 +PyYAML==6.0 +pytest==6.2.5 +python-dateutil==2.8.2 From 25dde71d10a8f30a8a284a2116edee74263e7170 Mon Sep 17 00:00:00 2001 From: Max Leske Date: Tue, 9 Nov 2021 07:54:12 +0100 Subject: [PATCH 05/19] Added Python 3.10 to the test matrix --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 5ede2e0..7602717 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -9,7 +9,7 @@ jobs: strategy: matrix: - python-version: [ '3.6', '3.7', '3.8', '3.9' ] + python-version: [ '3.6', '3.7', '3.8', '3.9', '3.10' ] steps: - name: Checkout repo From d0916adc6547dc03b310dde028ddbf278016f006 Mon Sep 17 00:00:00 2001 From: Max Leske Date: Tue, 9 Nov 2021 07:56:41 +0100 Subject: [PATCH 06/19] Fixed visual indentation --- ftw/testrunner.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/ftw/testrunner.py b/ftw/testrunner.py index 32f0384..ebda45c 100644 --- a/ftw/testrunner.py +++ b/ftw/testrunner.py @@ -189,18 +189,18 @@ def run_stage(self, stage, logger_obj=None, http_ua=None): http_ua = http.HttpUA() if ((stage.output.log_contains_str or stage.output.no_log_contains_str) and - logger_obj is not None): + logger_obj is not None): logger_obj.mark_start() start = datetime.datetime.utcnow() http_ua.send_request(stage.input) if ((stage.output.log_contains_str or stage.output.no_log_contains_str) and - logger_obj is not None): + logger_obj is not None): logger_obj.mark_end() end = datetime.datetime.utcnow() if ((stage.output.log_contains_str or stage.output.no_log_contains_str) and - logger_obj is not None): + logger_obj is not None): logger_obj.set_times(start, end) lines = logger_obj.get_logs() if stage.output.log_contains_str: From 75f57090f286214baca1009eaf7a5ff3af4cfa9f Mon Sep 17 00:00:00 2001 From: Max Leske Date: Tue, 9 Nov 2021 07:58:20 +0100 Subject: [PATCH 07/19] Added missing new line --- ftw/http.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ftw/http.py b/ftw/http.py index 35289c3..0504f68 100644 --- a/ftw/http.py +++ b/ftw/http.py @@ -544,4 +544,4 @@ def read_response_from_socket(self): 'message': err, 'function': 'http.HttpUA.get_response' }) - return our_data \ No newline at end of file + return our_data From 69467d634d520f0f9939c389a485ac917059df0f Mon Sep 17 00:00:00 2001 From: Max Leske Date: Tue, 9 Nov 2021 07:59:56 +0100 Subject: [PATCH 08/19] Removed unused import --- ftw/http.py | 1 - 1 file changed, 1 deletion(-) diff --git a/ftw/http.py b/ftw/http.py index 0504f68..56666de 100644 --- a/ftw/http.py +++ b/ftw/http.py @@ -9,7 +9,6 @@ import socket import ssl import sys -import time import zlib import select From f32c5c8f385e13babe622bdc236def551b163c44 Mon Sep 17 00:00:00 2001 From: Max Leske Date: Tue, 9 Nov 2021 21:06:13 +0100 Subject: [PATCH 09/19] Use Python 3.10 to run file checks --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 7602717..be7cfb3 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -23,7 +23,7 @@ jobs: python -m pip install -r requirements.txt python setup.py install - name: Check source files - if: matrix.python-version == '3.9' + if: matrix.python-version == '3.10' run: | python -m pip install pytest-pycodestyle python -m pip install pytest-flakes From bd24e1e6f7a316b2047bb395c6983e91e009e315 Mon Sep 17 00:00:00 2001 From: Max Leske Date: Tue, 9 Nov 2021 21:06:55 +0100 Subject: [PATCH 10/19] Replaced deprecated keys in setup.cfg --- setup.cfg | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.cfg b/setup.cfg index cc752ab..8b88600 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,5 +1,5 @@ [metadata] -description-file = README.md +description_file = README.md [tool:pytest] addopts = -s -v From 08687678b40b908885685b2009b81b65ce0890cc Mon Sep 17 00:00:00 2001 From: Max Leske Date: Tue, 9 Nov 2021 21:07:35 +0100 Subject: [PATCH 11/19] Made SSL work (not fast but at least it works) --- ftw/http.py | 29 +++++++++++++++++------------ 1 file changed, 17 insertions(+), 12 deletions(-) diff --git a/ftw/http.py b/ftw/http.py index 56666de..ca09d8c 100644 --- a/ftw/http.py +++ b/ftw/http.py @@ -483,17 +483,17 @@ def get_response(self): else: self.response_object = HttpResponse(b''.join(our_data), self) finally: - if b''.join(our_data): - return - raise errors.TestError( - 'No response from server. Request likely timed out.', - { - 'host': self.request_object.dest_addr, - 'port': self.request_object.port, - 'proto': self.request_object.protocol, - 'msg': 'Please send the request and check Wireshark', - 'function': 'http.HttpUA.get_response' - }) + if not b''.join(our_data): + raise errors.TestError( + 'No response from server.' \ + + ' Request likely timed out.', + { + 'host': self.request_object.dest_addr, + 'port': self.request_object.port, + 'proto': self.request_object.protocol, + 'msg': 'Please send the request and check Wireshark', + 'function': 'http.HttpUA.get_response' + }) def read_response_from_socket(self): # wait for socket to become ready @@ -531,7 +531,12 @@ def read_response_from_socket(self): pass elif (self.request_object.protocol == 'https' and err.args[0] == ssl.SSL_ERROR_WANT_READ): - continue + ready_sock, _, _ = select.select( + [self.sock], [], [self.sock], .3) + if not ready_sock: + break + else: + continue # It's an error else: raise errors.TestError( From 52e2ff038582c25d53749f5367a030502fd5daec Mon Sep 17 00:00:00 2001 From: Max Leske Date: Tue, 9 Nov 2021 21:12:49 +0100 Subject: [PATCH 12/19] Fixed formatting --- ftw/http.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/ftw/http.py b/ftw/http.py index ca09d8c..82b37b8 100644 --- a/ftw/http.py +++ b/ftw/http.py @@ -485,13 +485,14 @@ def get_response(self): finally: if not b''.join(our_data): raise errors.TestError( - 'No response from server.' \ - + ' Request likely timed out.', + 'No response from server.' + + ' Request likely timed out.', { 'host': self.request_object.dest_addr, 'port': self.request_object.port, 'proto': self.request_object.protocol, - 'msg': 'Please send the request and check Wireshark', + 'msg': 'Please send the request and check' + + ' Wireshark', 'function': 'http.HttpUA.get_response' }) From eaecc0f252e3749fde53b156e3d33978e83ebff1 Mon Sep 17 00:00:00 2001 From: Max Leske Date: Sun, 23 Jan 2022 13:01:22 +0100 Subject: [PATCH 13/19] Removed two test skips, those tests actually work --- test/integration/test_http.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/test/integration/test_http.py b/test/integration/test_http.py index d2bd8d1..9a87dc9 100644 --- a/test/integration/test_http.py +++ b/test/integration/test_http.py @@ -3,7 +3,6 @@ import pytest -@pytest.mark.skip(reason='Integration failure, @chaimsanders for more info') def test_cookies1(): """Tests accessing a site that sets a cookie and then wants to resend the cookie""" @@ -80,7 +79,6 @@ def test_raw1(): assert http_ua.response_object.status == 200 -@pytest.mark.skip(reason='Integration failure, @chaimsanders for more info') def test_raw2(): """Test to make sure a raw request will work with actual seperators""" x = ruleset.Input(dest_addr='example.com', raw_request='''GET / HTTP/1.1 From 801c835f3de69fae35de08a5362ba850985a4c62 Mon Sep 17 00:00:00 2001 From: Max Leske Date: Sun, 30 Jan 2022 11:41:31 +0100 Subject: [PATCH 14/19] Extended the pytest plugin and test setup to pass along additional data The additional data can be used to generate unique ID's for individual tests --- ftw/pytest_plugin.py | 21 +++++++++++++++------ ftw/ruleset.py | 20 ++++++++++++++------ ftw/testrunner.py | 5 ++--- 3 files changed, 31 insertions(+), 15 deletions(-) diff --git a/ftw/pytest_plugin.py b/ftw/pytest_plugin.py index d6ebf16..87d17f1 100644 --- a/ftw/pytest_plugin.py +++ b/ftw/pytest_plugin.py @@ -7,7 +7,7 @@ from .ruleset import Test -def get_testdata(rulesets): +def get_testdata(rulesets, use_rulesets): """ In order to do test-level parametrization (is this a word?), we have to bundle the test data from rulesets into tuples so py.test can understand @@ -17,7 +17,10 @@ def get_testdata(rulesets): for ruleset in rulesets: for test in ruleset.tests: if test.enabled: - testdata.append((ruleset, test)) + args = [test] + if use_rulesets: + args = [rulesets] + args + testdata.append(args) return testdata @@ -127,7 +130,13 @@ def pytest_generate_tests(metafunc): metafunc.config.option.ruledir_recurse, True) if metafunc.config.option.rule: rulesets = util.get_rulesets(metafunc.config.option.rule, False) - if 'ruleset' in metafunc.fixturenames and \ - 'test' in metafunc.fixturenames: - metafunc.parametrize('ruleset, test', get_testdata(rulesets), - ids=test_id) + if 'test' in metafunc.fixturenames: + use_rulesets = False + arg_names = ['test'] + if 'ruleset' in metafunc.fixturenames: + use_rulesets = True + arg_names = ['rulesets'] + arg_names + metafunc.parametrize( + arg_names, + get_testdata(rulesets, use_rulesets), + ids=test_id) diff --git a/ftw/ruleset.py b/ftw/ruleset.py index 04f951c..13f06af 100644 --- a/ftw/ruleset.py +++ b/ftw/ruleset.py @@ -137,18 +137,26 @@ class Stage(object): This class holds information about 1 stage in a test, which contains 1 input and 1 output """ - def __init__(self, stage_dict): + def __init__(self, stage_dict, stage_index, test): self.stage_dict = stage_dict + self.stage_index = stage_index + self.test = test self.input = Input(**stage_dict['input']) self.output = Output(stage_dict['output']) + self.id = self.build_id() + + def build_id(self): + rule_name = self.test.ruleset_meta["name"].split('.')[0] + return f'{rule_name}-{self.test.test_index}-{self.stage_index}' class Test(object): """ This class holds information for 1 test and potentially many stages """ - def __init__(self, test_dict, ruleset_meta): + def __init__(self, test_dict, test_index, ruleset_meta): self.test_dict = test_dict + self.test_index = test_index self.ruleset_meta = ruleset_meta self.test_title = self.test_dict['test_title'] self.stages = self.build_stages() @@ -160,8 +168,8 @@ def build_stages(self): """ Processes and loads an array of stages from the test dictionary """ - return [Stage(stage_dict['stage']) - for stage_dict in self.test_dict['stages']] + return [Stage(stage_dict['stage'], index, self) + for index, stage_dict in enumerate(self.test_dict['stages'])] class Ruleset(object): @@ -183,8 +191,8 @@ def extract_tests(self): creates test objects based on input """ try: - return [Test(test_dict, self.meta) - for test_dict in self.yaml_file['tests']] + return [Test(test_dict, index, self.meta) + for index, test_dict in enumerate(self.yaml_file['tests'])] except errors.TestError as e: e.args[1]['meta'] = self.meta raise e diff --git a/ftw/testrunner.py b/ftw/testrunner.py index ebda45c..21f6ade 100644 --- a/ftw/testrunner.py +++ b/ftw/testrunner.py @@ -174,7 +174,6 @@ def run_stage(self, stage, logger_obj=None, http_ua=None): input, waits for output then compares expected vs actual output http_ua can be passed in to persist cookies """ - # Send our request (exceptions caught as needed) if stage.output.expect_error: with pytest.raises(errors.TestError) as excinfo: @@ -190,13 +189,13 @@ def run_stage(self, stage, logger_obj=None, http_ua=None): if ((stage.output.log_contains_str or stage.output.no_log_contains_str) and logger_obj is not None): - logger_obj.mark_start() + logger_obj.mark_start(stage.id) start = datetime.datetime.utcnow() http_ua.send_request(stage.input) if ((stage.output.log_contains_str or stage.output.no_log_contains_str) and logger_obj is not None): - logger_obj.mark_end() + logger_obj.mark_end(stage.id) end = datetime.datetime.utcnow() if ((stage.output.log_contains_str or stage.output.no_log_contains_str) and From b69159c4bf20c4a5e50e312c0cf46ccd3a9d48ed Mon Sep 17 00:00:00 2001 From: Max Leske Date: Sun, 30 Jan 2022 11:48:44 +0100 Subject: [PATCH 15/19] Fixed unit test --- test/unit/test_ruleset.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/test/unit/test_ruleset.py b/test/unit/test_ruleset.py index ac15c97..763773a 100644 --- a/test/unit/test_ruleset.py +++ b/test/unit/test_ruleset.py @@ -33,11 +33,12 @@ def test_input(): def test_testobj(): with pytest.raises(KeyError) as excinfo: - ruleset.Test({}, {}) + ruleset.Test({}, {}, {}) assert 'test_title' in str(excinfo.value) + ruleset_meta = {'name': 'test-name.yaml'} stages_dict = {'test_title': 1, 'stages': [{'stage': {'output': {'log_contains': 'foo'}, 'input': {}}}]} - ruleset.Test(stages_dict, {}) + ruleset.Test(stages_dict, {}, ruleset_meta) def test_ruleset(): From c1a089b84c8ea77415a547af89c06bade273f1bf Mon Sep 17 00:00:00 2001 From: Max Leske Date: Sun, 30 Jan 2022 13:00:55 +0100 Subject: [PATCH 16/19] Added missing parameters Fixed fixture name in pytest_generate_tests --- ftw/logchecker.py | 4 ++-- ftw/pytest_plugin.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/ftw/logchecker.py b/ftw/logchecker.py index 4b09a2e..22e0fa5 100644 --- a/ftw/logchecker.py +++ b/ftw/logchecker.py @@ -15,14 +15,14 @@ def set_times(self, start, end): self.start = start self.end = end - def mark_start(selfj): + def mark_start(self, stage_id): """ May be implemented to set up the log checker before the request is being sent """ pass - def mark_end(self): + def mark_end(self, stage_id): """ May be implemented to tell the log checker that the response has been received diff --git a/ftw/pytest_plugin.py b/ftw/pytest_plugin.py index 87d17f1..63d288c 100644 --- a/ftw/pytest_plugin.py +++ b/ftw/pytest_plugin.py @@ -135,7 +135,7 @@ def pytest_generate_tests(metafunc): arg_names = ['test'] if 'ruleset' in metafunc.fixturenames: use_rulesets = True - arg_names = ['rulesets'] + arg_names + arg_names = ['ruleset'] + arg_names metafunc.parametrize( arg_names, get_testdata(rulesets, use_rulesets), From 1beb83e737c370029cf5edf102f8732ca1ed7e50 Mon Sep 17 00:00:00 2001 From: Max Leske Date: Sun, 30 Jan 2022 13:02:59 +0100 Subject: [PATCH 17/19] Updated dependencies in setup.py --- setup.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/setup.py b/setup.py index 5964e47..59309d0 100644 --- a/setup.py +++ b/setup.py @@ -35,10 +35,10 @@ use_scm_version=True, setup_requires=['setuptools_scm'], install_requires=[ - 'Brotli==1.0.7', - 'IPy==0.83', - 'PyYAML==5.4', - 'pytest==4.6', - 'python-dateutil==2.6.0' + 'Brotli==1.0.9', + 'IPy==1.01', + 'PyYAML==6.0', + 'pytest==6.2.5', + 'python-dateutil==2.8.2' ], ) From b85029da2475c8f4cc248a55050c214e812c9985 Mon Sep 17 00:00:00 2001 From: Max Leske Date: Sun, 30 Jan 2022 13:03:15 +0100 Subject: [PATCH 18/19] Removed superfluous fixture name from tests --- test/integration/test_logcontains.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/integration/test_logcontains.py b/test/integration/test_logcontains.py index b76f77b..4549396 100644 --- a/test/integration/test_logcontains.py +++ b/test/integration/test_logcontains.py @@ -27,13 +27,13 @@ def logchecker_obj(): return LoggerTestObj() -def test_logcontains_withlog(logchecker_obj, ruleset, test): +def test_logcontains_withlog(logchecker_obj, test): runner = testrunner.TestRunner() for stage in test.stages: runner.run_stage(stage, logchecker_obj) -def test_logcontains_nolog(logchecker_obj, ruleset, test): +def test_logcontains_nolog(logchecker_obj, test): logchecker_obj.do_nothing = True runner = testrunner.TestRunner() with(pytest.raises(AssertionError)): From 6f850542eec55816a1323e0c6a54ef95480c4eb8 Mon Sep 17 00:00:00 2001 From: Max Leske Date: Fri, 4 Mar 2022 08:07:38 +0100 Subject: [PATCH 19/19] Removed superfluous code --- ftw/http.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/ftw/http.py b/ftw/http.py index 82b37b8..c3e2892 100644 --- a/ftw/http.py +++ b/ftw/http.py @@ -520,11 +520,10 @@ def read_response_from_socket(self): break our_data.append(util.ensure_binary(data)) except BlockingIOError as e: + # If we can't handle the error here, pass it on if e.errno == socket.EAGAIN or e.errno == socket.EWOULDBLOCK: # we're done break - # something else happened - pass except OSError as err: # SSL will return SSLWantRead instead of EAGAIN if (sys.platform == 'win32' and @@ -536,8 +535,6 @@ def read_response_from_socket(self): [self.sock], [], [self.sock], .3) if not ready_sock: break - else: - continue # It's an error else: raise errors.TestError(