From 7fe9be432afdf01c525fb2b24eec67662008f200 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Mon, 16 Nov 2015 23:14:24 +0100 Subject: [PATCH] tests: Make httpbin.Request a proper class. --- tests/integration/features/conftest.py | 4 +- tests/integration/quteprocess.py | 13 +--- tests/integration/test_quteprocess.py | 3 +- tests/integration/test_webserver.py | 16 ++++- tests/integration/webserver.py | 89 ++++++++++++++++++++------ 5 files changed, 90 insertions(+), 35 deletions(-) diff --git a/tests/integration/features/conftest.py b/tests/integration/features/conftest.py index 8e3eb8429..cf96a0c76 100644 --- a/tests/integration/features/conftest.py +++ b/tests/integration/features/conftest.py @@ -99,12 +99,12 @@ def wait_for_message(quteproc, httpbin, category, message): @bdd.then(bdd.parsers.parse("{path} should be loaded")) def path_should_be_loaded(httpbin, path): requests = httpbin.get_requests() - assert requests[-1] == httpbin.Request('GET', '/' + path) + assert requests[-1] == httpbin.ExpectedRequest('GET', '/' + path) @bdd.then(bdd.parsers.parse("The requests should be:\n{pages}")) def list_of_loaded_pages(httpbin, pages): - expected_requests = [httpbin.Request('GET', '/' + path.strip()) + expected_requests = [httpbin.ExpectedRequest('GET', '/' + path.strip()) for path in pages.split('\n')] actual_requests = httpbin.get_requests() assert actual_requests == expected_requests diff --git a/tests/integration/quteprocess.py b/tests/integration/quteprocess.py index ab5c96cd1..d99665e41 100644 --- a/tests/integration/quteprocess.py +++ b/tests/integration/quteprocess.py @@ -47,13 +47,6 @@ def is_ignored_qt_message(message): return False -class NoLineMatch(Exception): - - """Raised by LogLine on unmatched lines.""" - - pass - - class LogLine(testprocess.Line): """A parsed line from the qutebrowser log output. @@ -78,7 +71,7 @@ class LogLine(testprocess.Line): super().__init__(data) match = self.LOG_RE.match(data) if match is None: - raise NoLineMatch(data) + raise testprocess.InvalidLine(data) self.timestamp = datetime.datetime.strptime(match.group('timestamp'), '%H:%M:%S') @@ -138,7 +131,7 @@ class QuteProc(testprocess.Process): def _parse_line(self, line): try: log_line = LogLine(line) - except NoLineMatch: + except testprocess.InvalidLine: if line.startswith(' '): # Multiple lines in some log output... return None @@ -147,7 +140,7 @@ class QuteProc(testprocess.Process): elif is_ignored_qt_message(line): return None else: - raise testprocess.InvalidLine + raise if (log_line.loglevel in ['INFO', 'WARNING', 'ERROR'] or pytest.config.getoption('--verbose')): diff --git a/tests/integration/test_quteprocess.py b/tests/integration/test_quteprocess.py index ad2f3da97..778c062a3 100644 --- a/tests/integration/test_quteprocess.py +++ b/tests/integration/test_quteprocess.py @@ -25,6 +25,7 @@ import datetime import pytest import quteprocess +import testprocess from qutebrowser.utils import log @@ -113,5 +114,5 @@ def test_log_line_parse(data, attrs): def test_log_line_no_match(): - with pytest.raises(quteprocess.NoLineMatch): + with pytest.raises(testprocess.InvalidLine): quteprocess.LogLine("Hello World!") diff --git a/tests/integration/test_webserver.py b/tests/integration/test_webserver.py index 69c0487c8..ae730ad3e 100644 --- a/tests/integration/test_webserver.py +++ b/tests/integration/test_webserver.py @@ -46,5 +46,19 @@ def test_httpbin(httpbin, qtbot, path, content, expected): data = response.read().decode('utf-8') - assert httpbin.get_requests() == [httpbin.Request('GET', path)] + assert httpbin.get_requests() == [httpbin.ExpectedRequest('GET', path)] assert (content in data) == expected + + +@pytest.mark.parametrize('line, verb, path, equal', [ + ('127.0.0.1 - - [01/Jan/1990 00:00:00] "GET / HTTP/1.1" 200 -', + 'GET', '/', True), + ('127.0.0.1 - - [01/Jan/1990 00:00:00] "GET / HTTP/1.1" 200 -', + 'GET', '/foo', False), + ('127.0.0.1 - - [01/Jan/1990 00:00:00] "GET / HTTP/1.1" 200 -', + 'POST', '/foo', False), +]) +def test_expected_request(httpbin, line, verb, path, equal): + expected = httpbin.ExpectedRequest(verb, path) + request = httpbin.Request(line) + assert (expected == request) == equal diff --git a/tests/integration/webserver.py b/tests/integration/webserver.py index 78ec505a0..893c03b2a 100644 --- a/tests/integration/webserver.py +++ b/tests/integration/webserver.py @@ -26,7 +26,7 @@ import re import sys import socket import os.path -import collections +import datetime import pytest from PyQt5.QtCore import pyqtSignal @@ -34,25 +34,17 @@ from PyQt5.QtCore import pyqtSignal import testprocess # pylint: disable=import-error -Request = collections.namedtuple('Request', 'verb, path') +class Request(testprocess.Line): + """A parsed line from the httpbin/flask log output. -class HTTPBin(testprocess.Process): - - """Abstraction over a running HTTPbin server process. - - Reads the log from its stdout and parses it. + Attributes: + timestamp/verb/path/status: Parsed from the log output. Class attributes: LOG_RE: Used to parse the CLF log which httpbin outputs. - - Signals: - new_request: Emitted when there's a new request received. """ - new_request = pyqtSignal(Request) - Request = Request # So it can be used from the fixture easily. - LOG_RE = re.compile(r""" (?P[^ ]*) \ ([^ ]*) # ignored @@ -67,6 +59,68 @@ class HTTPBin(testprocess.Process): \ (?P[^ ]*) """, re.VERBOSE) + def __init__(self, data): + super().__init__(data) + match = self.LOG_RE.match(data) + if match is None: + raise testprocess.InvalidLine(data) + + assert match.group('host') == '127.0.0.1' + assert match.group('user') == '-' + self.timestamp = datetime.datetime.strptime(match.group('date'), + '%d/%b/%Y %H:%M:%S') + self.verb = match.group('verb') + + # FIXME do we need to allow other options? + assert match.group('protocol') == 'HTTP/1.1' + assert self.verb == 'GET' + + self.path = match.group('path') + self.status = int(match.group('status')) + + missing_paths = ['/favicon.ico', '/does-not-exist'] + + if self.path in missing_paths: + assert self.status == 404 + else: + assert self.status < 400 + + assert match.group('size') == '-' + + def __eq__(self, other): + return NotImplemented + + +class ExpectedRequest: + + """Class to compare expected requests easily.""" + + def __init__(self, verb, path): + self.verb = verb + self.path = path + + def __eq__(self, other): + if isinstance(other, (Request, ExpectedRequest)): + return (self.verb == other.verb and + self.path == other.path) + else: + return NotImplemented + + +class HTTPBin(testprocess.Process): + + """Abstraction over a running HTTPbin server process. + + Reads the log from its stdout and parses it. + + Signals: + new_request: Emitted when there's a new request received. + """ + + new_request = pyqtSignal(Request) + Request = Request # So it can be used from the fixture easily. + ExpectedRequest = ExpectedRequest + KEYS = ['verb', 'path'] def __init__(self, parent=None): @@ -93,14 +147,7 @@ class HTTPBin(testprocess.Process): 'quit)'.format(self.port)): self.ready.emit() return None - - match = self.LOG_RE.match(line) - if match is None: - raise testprocess.InvalidLine - # FIXME do we need to allow other options? - assert match.group('protocol') == 'HTTP/1.1' - - return Request(verb=match.group('verb'), path=match.group('path')) + return Request(line) def _executable_args(self): if hasattr(sys, 'frozen'):