diff --git a/.github/workflows/linters.yaml b/.github/workflows/linters.yaml new file mode 100644 index 0000000..060d9c2 --- /dev/null +++ b/.github/workflows/linters.yaml @@ -0,0 +1,23 @@ +name: Enforce Conventions + +on: + push: + +jobs: + linters-and-formatters: + runs-on: ubuntu-latest + environment: prod + steps: + - name: Checkout code + uses: actions/checkout@v3 + + - name: Set up Python 3.12 + uses: actions/setup-python@v4 + with: + python-version: '3.12' + + - name: Enforce Conventions + uses: pre-commit/action@v3.0.1 + with: + extra_args: + --all-files diff --git a/.github/workflows/publish.yaml b/.github/workflows/publish.yaml index 81cb63d..7da6736 100644 --- a/.github/workflows/publish.yaml +++ b/.github/workflows/publish.yaml @@ -1,7 +1,7 @@ name: Publish on: workflow_dispatch: -jobs: +jobs: publish: runs-on: ubuntu-20.04 steps: diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000..4cba894 --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,39 @@ +# See https://pre-commit.com for more information +# See https://pre-commit.com/hooks.html for more hooks +repos: + - repo: https://github.com/pre-commit/pre-commit-hooks + rev: v3.2.0 + hooks: + - id: trailing-whitespace + - id: end-of-file-fixer + - id: check-yaml + exclude: template.yml + - repo: https://github.com/psf/black + rev: 23.12.1 + hooks: + - id: black + args: + - --line-length=150 + - --target-version=py311 + - --skip-string-normalization + - repo: https://github.com/PyCQA/flake8 + rev: 7.0.0 + hooks: + - id: flake8 + args: + - --ignore=E203,E501,E266,F541,W503,F405 + - "--exclude=docs/*" + - repo: https://github.com/python-jsonschema/check-jsonschema + rev: 0.28.3 + hooks: + - id: check-github-workflows + args: [ "--verbose" ] + - repo: https://github.com/PracticeFoxyCode/practice + rev: releases/1.0.0 + hooks: + - id: foxylint-imports + args: + - "--accept=/from testix.*import/" + - "--accept=/from examples import/" + - "--accept=/from unittest.*import/" + - "--exclude=./testix/__init__.py" diff --git a/chatbot/src/chatbot/chatbot.py b/chatbot/src/chatbot/chatbot.py index bc435a3..92654a7 100644 --- a/chatbot/src/chatbot/chatbot.py +++ b/chatbot/src/chatbot/chatbot.py @@ -1,8 +1,9 @@ import socket -from . import responder +from . import responder + class Chatbot: - def __init__( self, peer ): + def __init__(self, peer): self._peer = peer self._responder = responder.Responder() diff --git a/chatbot/test/test_chatbot.py b/chatbot/test/test_chatbot.py index 77ab5f4..ee78952 100644 --- a/chatbot/test/test_chatbot.py +++ b/chatbot/test/test_chatbot.py @@ -1,18 +1,19 @@ import pytest import socket -from testix.frequentlyused import * -from testix import patch_module -from chatbot import chatbot +from testix.frequentlyused import * # noqa: F403 +from testix import patch_module # noqa: F401 +from chatbot import chatbot # foxylint-imports:ignore + class TestChatbot: @pytest.fixture(autouse=True) - def globals_patch(self, patch_module): - patch_module( chatbot, 'responder' ) + def globals_patch(self, patch_module): # noqa: F811 + patch_module(chatbot, 'responder') def construct(self): with Scenario() as s: - s.responder.Responder() >> Fake( 'responder_' ) - self.tested = chatbot.Chatbot( Fake( 'sock' ) ) + s.responder.Responder() >> Fake('responder_') + self.tested = chatbot.Chatbot(Fake('sock')) def test_construction(self): self.construct() @@ -21,8 +22,8 @@ def test_request_response_loop(self): self.construct() with Scenario() as s: for i in range(10): - s.sock.recv(4096) >> f'request {i}' - s.responder_.process(f'request {i}') >> f'response {i}' + s.sock.recv(4096) >> f'request {i}' + s.responder_.process(f'request {i}') >> f'response {i}' s.sock.send(f'response {i}') s.sock.recv(4096) >> Throwing(TestixLoopBreaker) @@ -33,15 +34,15 @@ def test_request_response_loop_survives_a_recv_exception(self): self.construct() with Scenario() as s: for i in range(10): - s.sock.recv(4096) >> f'request {i}' - s.responder_.process(f'request {i}') >> f'response {i}' + s.sock.recv(4096) >> f'request {i}' + s.responder_.process(f'request {i}') >> f'response {i}' s.sock.send(f'response {i}') s.sock.recv(4096) >> Throwing(socket.error) for i in range(10): - s.sock.recv(4096) >> f'request {i}' - s.responder_.process(f'request {i}') >> f'response {i}' + s.sock.recv(4096) >> f'request {i}' + s.responder_.process(f'request {i}') >> f'response {i}' s.sock.send(f'response {i}') s.sock.recv(4096) >> Throwing(TestixLoopBreaker) diff --git a/chatbot/test/test_chatbot_with_unittest_mock.py b/chatbot/test/test_chatbot_with_unittest_mock.py index 159f0a0..4581b69 100644 --- a/chatbot/test/test_chatbot_with_unittest_mock.py +++ b/chatbot/test/test_chatbot_with_unittest_mock.py @@ -1,36 +1,38 @@ import pytest -from unittest.mock import patch +import unittest.mock from unittest.mock import Mock, call -import socket import chatbot.chatbot import chatbot.responder + class TestChatbot: def construct(self, sock, Responder): - self.tested = chatbot.chatbot.Chatbot( sock ) + self.tested = chatbot.chatbot.Chatbot(sock) Responder.assert_called_once_with() - @patch('chatbot.responder.Responder') + @unittest.mock.patch('chatbot.responder.Responder') def test_construction(self, Responder): sock = Mock() self.construct(sock, Responder) - @patch('chatbot.responder.Responder') + @unittest.mock.patch('chatbot.responder.Responder') def test_request_response_loop(self, Responder): sock = Mock() responder = Mock() - Responder.side_effect = [ responder ] + Responder.side_effect = [responder] self.construct(sock, Responder) - class EndTestException(Exception): pass + + class EndTestException(Exception): + pass REQUESTS = [f'request {i}' for i in range(10)] RESPONSES = [f'response {i}' for i in range(10)] responder.process.side_effect = RESPONSES sock.recv.side_effect = REQUESTS + [EndTestException] - + with pytest.raises(EndTestException): self.tested.go() - sock.recv.assert_has_calls( [ call(4096) ] * 10 ) - responder.process.assert_has_calls( [ call(request) for request in REQUESTS ] ) - sock.send.assert_has_calls( [ call( response ) for response in RESPONSES ] ) + sock.recv.assert_has_calls([call(4096)] * 10) + responder.process.assert_has_calls([call(request) for request in REQUESTS]) + sock.send.assert_has_calls([call(response) for response in RESPONSES]) diff --git a/docs/chatapp/tests/e2e/pseudo/test_send_and_receive_messages_1.py b/docs/chatapp/tests/e2e/pseudo/test_send_and_receive_messages_1.py index d5e3ad0..7bdd84f 100644 --- a/docs/chatapp/tests/e2e/pseudo/test_send_and_receive_messages_1.py +++ b/docs/chatapp/tests/e2e/pseudo/test_send_and_receive_messages_1.py @@ -12,5 +12,3 @@ def test_send_and_receive_messages(): assert alice_messages == ['hi Alice'] assert bob_messages == ['hi Bob'] - - diff --git a/docs/chatapp/tests/e2e/pseudo/test_send_and_receive_messages_2.py b/docs/chatapp/tests/e2e/pseudo/test_send_and_receive_messages_2.py index d00fa0a..8e8ded9 100644 --- a/docs/chatapp/tests/e2e/pseudo/test_send_and_receive_messages_2.py +++ b/docs/chatapp/tests/e2e/pseudo/test_send_and_receive_messages_2.py @@ -1,5 +1,6 @@ import chatapp.client + class OnMessage: def __init__(self): self.messages = [] @@ -7,6 +8,7 @@ def __init__(self): def __call__(self, client, message, peer): self.messages.append({'message': message, 'peer': peer}) + def test_send_and_receive_messages(): alice_callback = OnMessage() bob_callback = OnMessage() @@ -17,4 +19,4 @@ def test_send_and_receive_messages(): bob.send('hi Alice', to='Alice') assert alice_callback.messages == [{'message': 'hi Alice', 'peer': 'Bob'}] - assert bob_callback.messages == [{'message': 'hi Bob', 'peer': 'Alice'}] + assert bob_callback.messages == [{'message': 'hi Bob', 'peer': 'Alice'}] diff --git a/docs/chatapp/tests/e2e/pseudo/test_send_and_receive_messages_3.py b/docs/chatapp/tests/e2e/pseudo/test_send_and_receive_messages_3.py index 7169221..634b890 100644 --- a/docs/chatapp/tests/e2e/pseudo/test_send_and_receive_messages_3.py +++ b/docs/chatapp/tests/e2e/pseudo/test_send_and_receive_messages_3.py @@ -1,6 +1,7 @@ import chatapp.client import time + class OnMessage: def __init__(self): self.messages = [] @@ -8,6 +9,7 @@ def __init__(self): def __call__(self, client, message, peer): self.messages.append({'message': message, 'peer': peer}) + def test_send_and_receive_messages(): alice_callback = OnMessage() bob_callback = OnMessage() @@ -21,4 +23,4 @@ def test_send_and_receive_messages(): time.sleep(LET_SERVER_RELAY_MESSAGES) assert alice_callback.messages == [{'message': 'hi Alice', 'peer': 'Bob'}] - assert bob_callback.messages == [{'message': 'hi Bob', 'peer': 'Alice'}] + assert bob_callback.messages == [{'message': 'hi Bob', 'peer': 'Alice'}] diff --git a/docs/chatapp/tests/e2e/pseudo/test_send_and_receive_messages_4.py b/docs/chatapp/tests/e2e/pseudo/test_send_and_receive_messages_4.py index 83fa096..6086cae 100644 --- a/docs/chatapp/tests/e2e/pseudo/test_send_and_receive_messages_4.py +++ b/docs/chatapp/tests/e2e/pseudo/test_send_and_receive_messages_4.py @@ -3,6 +3,7 @@ import chatapp.server import time + class OnMessage: def __init__(self): self.messages = [] @@ -10,6 +11,7 @@ def __init__(self): def __call__(self, client, message, peer): self.messages.append({'message': message, 'peer': peer}) + @pytest.fixture def chat_app_server(): server = chatapp.server.Server(bind_to=('', 3333)) @@ -17,6 +19,7 @@ def chat_app_server(): yield 'http://localhost:3333' server.stop() + def test_send_and_receive_messages(chat_app_server): alice_callback = OnMessage() bob_callback = OnMessage() @@ -30,4 +33,4 @@ def test_send_and_receive_messages(chat_app_server): time.sleep(LET_SERVER_RELAY_MESSAGES) assert alice_callback.messages == [{'message': 'hi Alice', 'peer': 'Bob'}] - assert bob_callback.messages == [{'message': 'hi Bob', 'peer': 'Alice'}] + assert bob_callback.messages == [{'message': 'hi Bob', 'peer': 'Alice'}] diff --git a/docs/common.rst b/docs/common.rst index b11e479..a349721 100644 --- a/docs/common.rst +++ b/docs/common.rst @@ -6,4 +6,3 @@ .. |RED| replace:: :boldred:`RED` .. |GREEN| replace:: :boldgreen:`GREEN` .. |REFACTOR| replace:: :boldblue:`REFACTOR` - diff --git a/docs/conf.py b/docs/conf.py index 22e1e91..46894a0 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -36,6 +36,7 @@ # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. import sphinx_theme + html_theme = 'stanford_theme' html_theme_path = [sphinx_theme.get_html_theme_path('stanford-theme')] # diff --git a/docs/index.rst b/docs/index.rst index eb92b9d..ec8857c 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -17,7 +17,7 @@ The Test-First Mocking Framework |testix| is special because it allows you to specify what your mock objects do, and it then enforces your specifications automatically. It also reduces (albeit -not entirely) mock setup. +not entirely) mock setup. Other frameworks usually have a flow like this: @@ -29,7 +29,7 @@ Other frameworks usually have a flow like this: #. setup mock objects #. specify *exactly* what should happen to them using a Scenario context - + Quick Example ------------- @@ -86,10 +86,10 @@ Advantages #. Readability - the expectations are very similar to the actual code that they test (compare ``s.sock.recv(4096)`` with the standard ``sock.recv.assert_called_once_with(4096)`` #. Test Driven Development friendliness: if you use ``sock.recv.assert_called_once_with(4096)``, you must - use it after the code has run. With |testix|, you specify what you *expect*, and the asserting + use it after the code has run. With |testix|, you specify what you *expect*, and the asserting is done for you by magic. -What are you waiting for? +What are you waiting for? Go to the :doc:`reference` or read the :doc:`Tutorial` diff --git a/docs/line_monitor/source/12/line_monitor.py b/docs/line_monitor/source/12/line_monitor.py index 791c408..d8653ed 100644 --- a/docs/line_monitor/source/12/line_monitor.py +++ b/docs/line_monitor/source/12/line_monitor.py @@ -2,6 +2,7 @@ import pty import select + class LineMonitor: def __init__(self): self._callback = None diff --git a/docs/line_monitor/source/14/line_monitor.py b/docs/line_monitor/source/14/line_monitor.py index b967709..8226f4e 100644 --- a/docs/line_monitor/source/14/line_monitor.py +++ b/docs/line_monitor/source/14/line_monitor.py @@ -2,6 +2,7 @@ import pty import select + class LineMonitor: def __init__(self): self._callback = None diff --git a/docs/line_monitor/source/16/line_monitor.py b/docs/line_monitor/source/16/line_monitor.py index 824994c..1a3f304 100644 --- a/docs/line_monitor/source/16/line_monitor.py +++ b/docs/line_monitor/source/16/line_monitor.py @@ -2,6 +2,7 @@ import pty import select + class LineMonitor: def __init__(self): self._callback = None diff --git a/docs/line_monitor/source/17/line_monitor.py b/docs/line_monitor/source/17/line_monitor.py index d1facbb..8436a16 100644 --- a/docs/line_monitor/source/17/line_monitor.py +++ b/docs/line_monitor/source/17/line_monitor.py @@ -2,6 +2,7 @@ import pty import select + class LineMonitor: def __init__(self): self._callback = None diff --git a/docs/line_monitor/source/19/line_monitor.py b/docs/line_monitor/source/19/line_monitor.py index 343532c..3374fb1 100644 --- a/docs/line_monitor/source/19/line_monitor.py +++ b/docs/line_monitor/source/19/line_monitor.py @@ -2,6 +2,7 @@ import pty import select + class LineMonitor: def __init__(self): self._callback = None diff --git a/docs/line_monitor/source/2/line_monitor.py b/docs/line_monitor/source/2/line_monitor.py index 451992c..46196ae 100644 --- a/docs/line_monitor/source/2/line_monitor.py +++ b/docs/line_monitor/source/2/line_monitor.py @@ -1,6 +1,7 @@ import subprocess import pty + class LineMonitor: def register_callback(self, callback): pass diff --git a/docs/line_monitor/source/21/line_monitor.py b/docs/line_monitor/source/21/line_monitor.py index 44d86d1..63fb2a6 100644 --- a/docs/line_monitor/source/21/line_monitor.py +++ b/docs/line_monitor/source/21/line_monitor.py @@ -2,6 +2,7 @@ import pty import select + class LineMonitor: def __init__(self): self._callback = None diff --git a/docs/line_monitor/source/23/line_monitor.py b/docs/line_monitor/source/23/line_monitor.py index 75d7814..8345b6f 100644 --- a/docs/line_monitor/source/23/line_monitor.py +++ b/docs/line_monitor/source/23/line_monitor.py @@ -2,6 +2,7 @@ import pty import select + class LineMonitor: def __init__(self): self._callback = None diff --git a/docs/line_monitor/source/25/line_monitor.py b/docs/line_monitor/source/25/line_monitor.py index 82603cd..e3127d1 100644 --- a/docs/line_monitor/source/25/line_monitor.py +++ b/docs/line_monitor/source/25/line_monitor.py @@ -2,6 +2,7 @@ import pty import select + class LineMonitor: def __init__(self): self._callback = None diff --git a/docs/line_monitor/source/26/line_monitor.py b/docs/line_monitor/source/26/line_monitor.py index bad193e..6171210 100644 --- a/docs/line_monitor/source/26/line_monitor.py +++ b/docs/line_monitor/source/26/line_monitor.py @@ -2,6 +2,7 @@ import pty import select + class LineMonitor: def __init__(self): self._callback = None diff --git a/docs/line_monitor/source/5/line_monitor.py b/docs/line_monitor/source/5/line_monitor.py index 6a31e04..768dc4b 100644 --- a/docs/line_monitor/source/5/line_monitor.py +++ b/docs/line_monitor/source/5/line_monitor.py @@ -1,6 +1,7 @@ import subprocess import pty + class LineMonitor: def __init__(self): self._callback = None diff --git a/docs/line_monitor/source/7/line_monitor.py b/docs/line_monitor/source/7/line_monitor.py index b788ede..94c9db2 100644 --- a/docs/line_monitor/source/7/line_monitor.py +++ b/docs/line_monitor/source/7/line_monitor.py @@ -1,6 +1,7 @@ import subprocess import pty + class LineMonitor: def __init__(self): self._callback = None diff --git a/docs/line_monitor/source/9/line_monitor.py b/docs/line_monitor/source/9/line_monitor.py index dfb9f09..42a396e 100644 --- a/docs/line_monitor/source/9/line_monitor.py +++ b/docs/line_monitor/source/9/line_monitor.py @@ -1,6 +1,7 @@ import subprocess import pty + class LineMonitor: def __init__(self): self._callback = None diff --git a/docs/line_monitor/tests/e2e/test_line_monitor.py b/docs/line_monitor/tests/e2e/test_line_monitor.py index d0e6ea7..a06f629 100644 --- a/docs/line_monitor/tests/e2e/test_line_monitor.py +++ b/docs/line_monitor/tests/e2e/test_line_monitor.py @@ -1,5 +1,6 @@ import line_monitor + def test_line_monitor(): captured_lines = [] tested = line_monitor.LineMonitor() diff --git a/docs/line_monitor/tests/unit/1/test_line_monitor.py b/docs/line_monitor/tests/unit/1/test_line_monitor.py index df9ab29..7b13ed5 100644 --- a/docs/line_monitor/tests/unit/1/test_line_monitor.py +++ b/docs/line_monitor/tests/unit/1/test_line_monitor.py @@ -2,10 +2,12 @@ import pytest import line_monitor + @pytest.fixture def override_imports(patch_module): - patch_module(line_monitor, 'subprocess') # this replaces the subprocess object inside line_monitor with a Fake("subprocess") object - patch_module(line_monitor, 'pty') # does the same for the pty module + patch_module(line_monitor, 'subprocess') # this replaces the subprocess object inside line_monitor with a Fake("subprocess") object + patch_module(line_monitor, 'pty') # does the same for the pty module + def test_lauch_subprocess_with_pseudoterminal(override_imports): tested = line_monitor.LineMonitor() diff --git a/docs/line_monitor/tests/unit/10/test_line_monitor.py b/docs/line_monitor/tests/unit/10/test_line_monitor.py index 510b722..dd4c1e9 100644 --- a/docs/line_monitor/tests/unit/10/test_line_monitor.py +++ b/docs/line_monitor/tests/unit/10/test_line_monitor.py @@ -2,23 +2,27 @@ import pytest import line_monitor + @pytest.fixture def override_imports(patch_module): patch_module(line_monitor, 'subprocess') patch_module(line_monitor, 'pty') patch_module(line_monitor, 'open') + def launch_scenario(s): s.pty.openpty() >> ('write_to_fd', 'read_from_fd') s.open('read_from_fd', encoding='latin-1') >> Fake('reader') s.subprocess.Popen(['my', 'command', 'line'], stdout='write_to_fd', close_fds=True) + def test_lauch_subprocess_with_pseudoterminal(override_imports): tested = line_monitor.LineMonitor() with Scenario() as s: launch_scenario(s) tested.launch_subprocess(['my', 'command', 'line']) + def test_receive_output_lines_via_callback(override_imports): tested = line_monitor.LineMonitor() with Scenario() as s: @@ -37,6 +41,7 @@ def test_receive_output_lines_via_callback(override_imports): with pytest.raises(loop_breaker.LoopBreaker): tested.monitor() + def test_monitoring_with_no_callback(override_imports): tested = line_monitor.LineMonitor() with Scenario() as s: @@ -51,6 +56,7 @@ def test_monitoring_with_no_callback(override_imports): with pytest.raises(loop_breaker.LoopBreaker): tested.monitor() + def test_callback_registered_mid_monitoring(override_imports): tested = line_monitor.LineMonitor() with Scenario() as s: @@ -60,8 +66,8 @@ def test_callback_registered_mid_monitoring(override_imports): s.reader.readline() >> 'line 1' s.reader.readline() >> 'line 2' s.reader.readline() >> 'line 3' - s << Hook(tested.register_callback, Fake('my_callback')) # the hook will execute right after the 'line 3' readline finishes - s.my_callback('line 3') # callback is now registered, so it should be called + s << Hook(tested.register_callback, Fake('my_callback')) # the hook will execute right after the 'line 3' readline finishes + s.my_callback('line 3') # callback is now registered, so it should be called s.reader.readline() >> Throwing(loop_breaker.LoopBreaker) with pytest.raises(loop_breaker.LoopBreaker): diff --git a/docs/line_monitor/tests/unit/11/test_line_monitor.py b/docs/line_monitor/tests/unit/11/test_line_monitor.py index 20f4164..4d674d1 100644 --- a/docs/line_monitor/tests/unit/11/test_line_monitor.py +++ b/docs/line_monitor/tests/unit/11/test_line_monitor.py @@ -3,6 +3,7 @@ import line_monitor import select + @pytest.fixture def override_imports(patch_module): patch_module(line_monitor, 'subprocess') @@ -11,6 +12,7 @@ def override_imports(patch_module): patch_module(line_monitor, 'select') Fake('select').POLLIN = select.POLLIN + def launch_scenario(s): s.pty.openpty() >> ('write_to_fd', 'read_from_fd') s.open('read_from_fd', encoding='latin-1') >> Fake('reader') @@ -19,12 +21,14 @@ def launch_scenario(s): s.poller.register('reader_descriptor', select.POLLIN) s.subprocess.Popen(['my', 'command', 'line'], stdout='write_to_fd', close_fds=True) >> Fake('the_process') + def test_lauch_subprocess_with_pseudoterminal(override_imports): tested = line_monitor.LineMonitor() with Scenario() as s: launch_scenario(s) tested.launch_subprocess(['my', 'command', 'line']) + def test_receive_output_lines_via_callback(override_imports): tested = line_monitor.LineMonitor() with Scenario() as s: @@ -43,6 +47,7 @@ def test_receive_output_lines_via_callback(override_imports): with pytest.raises(loop_breaker.LoopBreaker): tested.monitor() + def test_monitoring_with_no_callback(override_imports): tested = line_monitor.LineMonitor() with Scenario() as s: @@ -57,6 +62,7 @@ def test_monitoring_with_no_callback(override_imports): with pytest.raises(loop_breaker.LoopBreaker): tested.monitor() + def test_callback_registered_mid_monitoring(override_imports): tested = line_monitor.LineMonitor() with Scenario() as s: @@ -66,8 +72,8 @@ def test_callback_registered_mid_monitoring(override_imports): s.reader.readline() >> 'line 1' s.reader.readline() >> 'line 2' s.reader.readline() >> 'line 3' - s << Hook(tested.register_callback, Fake('my_callback')) # the hook will execute right after the 'line 3' readline finishes - s.my_callback('line 3') # callback is now registered, so it should be called + s << Hook(tested.register_callback, Fake('my_callback')) # the hook will execute right after the 'line 3' readline finishes + s.my_callback('line 3') # callback is now registered, so it should be called s.reader.readline() >> Throwing(loop_breaker.LoopBreaker) with pytest.raises(loop_breaker.LoopBreaker): diff --git a/docs/line_monitor/tests/unit/13/test_line_monitor.py b/docs/line_monitor/tests/unit/13/test_line_monitor.py index e981353..3dd965e 100644 --- a/docs/line_monitor/tests/unit/13/test_line_monitor.py +++ b/docs/line_monitor/tests/unit/13/test_line_monitor.py @@ -3,6 +3,7 @@ import line_monitor import select + @pytest.fixture def override_imports(patch_module): patch_module(line_monitor, 'subprocess') @@ -11,6 +12,7 @@ def override_imports(patch_module): patch_module(line_monitor, 'select') Fake('select').POLLIN = select.POLLIN + def launch_scenario(s): s.pty.openpty() >> ('write_to_fd', 'read_from_fd') s.open('read_from_fd', encoding='latin-1') >> Fake('reader') @@ -19,19 +21,23 @@ def launch_scenario(s): s.poller.register('reader_descriptor', select.POLLIN) s.subprocess.Popen(['my', 'command', 'line'], stdout='write_to_fd', close_fds=True) >> Fake('the_process') + def test_lauch_subprocess_with_pseudoterminal(override_imports): tested = line_monitor.LineMonitor() with Scenario() as s: launch_scenario(s) tested.launch_subprocess(['my', 'command', 'line']) + def read_line_scenario(s, line): s.poller.poll() >> [('reader_descriptor', select.POLLIN)] s.reader.readline() >> line + def end_test_scenario(s): s.poller.poll() >> Throwing(loop_breaker.LoopBreaker) + def test_receive_output_lines_via_callback(override_imports): tested = line_monitor.LineMonitor() with Scenario() as s: @@ -50,6 +56,7 @@ def test_receive_output_lines_via_callback(override_imports): with pytest.raises(loop_breaker.LoopBreaker): tested.monitor() + def test_monitoring_with_no_callback(override_imports): tested = line_monitor.LineMonitor() with Scenario() as s: @@ -64,6 +71,7 @@ def test_monitoring_with_no_callback(override_imports): with pytest.raises(loop_breaker.LoopBreaker): tested.monitor() + def test_callback_registered_mid_monitoring(override_imports): tested = line_monitor.LineMonitor() with Scenario() as s: @@ -73,8 +81,8 @@ def test_callback_registered_mid_monitoring(override_imports): read_line_scenario(s, 'line 1') read_line_scenario(s, 'line 2') read_line_scenario(s, 'line 3') - s << Hook(tested.register_callback, Fake('my_callback')) # the hook will execute right after the 'line 3' readline finishes - s.my_callback('line 3') # callback is now registered, so it should be called + s << Hook(tested.register_callback, Fake('my_callback')) # the hook will execute right after the 'line 3' readline finishes + s.my_callback('line 3') # callback is now registered, so it should be called end_test_scenario(s) with pytest.raises(loop_breaker.LoopBreaker): diff --git a/docs/line_monitor/tests/unit/15/test_line_monitor.py b/docs/line_monitor/tests/unit/15/test_line_monitor.py index 188e6ce..02b9ad5 100644 --- a/docs/line_monitor/tests/unit/15/test_line_monitor.py +++ b/docs/line_monitor/tests/unit/15/test_line_monitor.py @@ -3,6 +3,7 @@ import line_monitor import select + @pytest.fixture def override_imports(patch_module): patch_module(line_monitor, 'subprocess') @@ -11,6 +12,7 @@ def override_imports(patch_module): patch_module(line_monitor, 'select') Fake('select').POLLIN = select.POLLIN + def launch_scenario(s): s.pty.openpty() >> ('write_to_fd', 'read_from_fd') s.open('read_from_fd', encoding='latin-1') >> Fake('reader') @@ -19,22 +21,27 @@ def launch_scenario(s): s.poller.register('reader_descriptor', select.POLLIN) s.subprocess.Popen(['my', 'command', 'line'], stdout='write_to_fd', close_fds=True) >> Fake('the_process') + def test_lauch_subprocess_with_pseudoterminal(override_imports): tested = line_monitor.LineMonitor() with Scenario() as s: launch_scenario(s) tested.launch_subprocess(['my', 'command', 'line']) + def read_line_scenario(s, line): s.poller.poll() >> [('reader_descriptor', select.POLLIN)] s.reader.readline() >> line + def skip_line_scenario(s): s.poller.poll() >> [('reader_descriptor', 0)] + def end_test_scenario(s): s.poller.poll() >> Throwing(loop_breaker.LoopBreaker) + def test_receive_output_lines_via_callback(override_imports): tested = line_monitor.LineMonitor() with Scenario() as s: @@ -57,6 +64,7 @@ def test_receive_output_lines_via_callback(override_imports): with pytest.raises(loop_breaker.LoopBreaker): tested.monitor() + def test_monitoring_with_no_callback(override_imports): tested = line_monitor.LineMonitor() with Scenario() as s: @@ -73,6 +81,7 @@ def test_monitoring_with_no_callback(override_imports): with pytest.raises(loop_breaker.LoopBreaker): tested.monitor() + def test_callback_registered_mid_monitoring(override_imports): tested = line_monitor.LineMonitor() with Scenario() as s: @@ -83,8 +92,8 @@ def test_callback_registered_mid_monitoring(override_imports): skip_line_scenario(s) read_line_scenario(s, 'line 2') read_line_scenario(s, 'line 3') - s << Hook(tested.register_callback, Fake('my_callback')) # the hook will execute right after the 'line 3' readline finishes - s.my_callback('line 3') # callback is now registered, so it should be called + s << Hook(tested.register_callback, Fake('my_callback')) # the hook will execute right after the 'line 3' readline finishes + s.my_callback('line 3') # callback is now registered, so it should be called end_test_scenario(s) with pytest.raises(loop_breaker.LoopBreaker): diff --git a/docs/line_monitor/tests/unit/18/test_line_monitor.py b/docs/line_monitor/tests/unit/18/test_line_monitor.py index 37e14b3..5dd4f60 100644 --- a/docs/line_monitor/tests/unit/18/test_line_monitor.py +++ b/docs/line_monitor/tests/unit/18/test_line_monitor.py @@ -3,6 +3,7 @@ import line_monitor import select + @pytest.fixture def override_imports(patch_module): patch_module(line_monitor, 'subprocess') @@ -11,6 +12,7 @@ def override_imports(patch_module): patch_module(line_monitor, 'select') Fake('select').POLLIN = select.POLLIN + def launch_scenario(s): s.pty.openpty() >> ('write_to_fd', 'read_from_fd') s.open('read_from_fd', encoding='latin-1') >> Fake('reader') @@ -19,22 +21,27 @@ def launch_scenario(s): s.poller.register('reader_descriptor', select.POLLIN) s.subprocess.Popen(['my', 'command', 'line'], stdout='write_to_fd', close_fds=True) >> Fake('the_process') + def test_lauch_subprocess_with_pseudoterminal(override_imports): tested = line_monitor.LineMonitor() with Scenario() as s: launch_scenario(s) tested.launch_subprocess(['my', 'command', 'line']) + def read_line_scenario(s, line): s.poller.poll(IgnoreArgument()) >> [('reader_descriptor', select.POLLIN)] s.reader.readline() >> line + def skip_line_scenario(s): s.poller.poll(IgnoreArgument()) >> [('reader_descriptor', 0)] + def end_test_scenario(s): s.poller.poll(IgnoreArgument()) >> Throwing(loop_breaker.LoopBreaker) + def test_receive_output_lines_via_callback(override_imports): tested = line_monitor.LineMonitor() with Scenario() as s: @@ -57,6 +64,7 @@ def test_receive_output_lines_via_callback(override_imports): with pytest.raises(loop_breaker.LoopBreaker): tested.monitor() + def test_monitoring_with_no_callback(override_imports): tested = line_monitor.LineMonitor() with Scenario() as s: @@ -73,6 +81,7 @@ def test_monitoring_with_no_callback(override_imports): with pytest.raises(loop_breaker.LoopBreaker): tested.monitor() + def test_callback_registered_mid_monitoring(override_imports): tested = line_monitor.LineMonitor() with Scenario() as s: @@ -83,8 +92,8 @@ def test_callback_registered_mid_monitoring(override_imports): skip_line_scenario(s) read_line_scenario(s, 'line 2') read_line_scenario(s, 'line 3') - s << Hook(tested.register_callback, Fake('my_callback')) # the hook will execute right after the 'line 3' readline finishes - s.my_callback('line 3') # callback is now registered, so it should be called + s << Hook(tested.register_callback, Fake('my_callback')) # the hook will execute right after the 'line 3' readline finishes + s.my_callback('line 3') # callback is now registered, so it should be called end_test_scenario(s) with pytest.raises(loop_breaker.LoopBreaker): diff --git a/docs/line_monitor/tests/unit/20/test_line_monitor.py b/docs/line_monitor/tests/unit/20/test_line_monitor.py index a7e0dd5..46f3e81 100644 --- a/docs/line_monitor/tests/unit/20/test_line_monitor.py +++ b/docs/line_monitor/tests/unit/20/test_line_monitor.py @@ -3,6 +3,7 @@ import line_monitor import select + @pytest.fixture def override_imports(patch_module): patch_module(line_monitor, 'subprocess') @@ -11,6 +12,7 @@ def override_imports(patch_module): patch_module(line_monitor, 'select') Fake('select').POLLIN = select.POLLIN + def launch_scenario(s): s.pty.openpty() >> ('write_to_fd', 'read_from_fd') s.open('read_from_fd', encoding='latin-1') >> Fake('reader') @@ -19,25 +21,31 @@ def launch_scenario(s): s.poller.register('reader_descriptor', select.POLLIN) s.subprocess.Popen(['my', 'command', 'line'], stdout='write_to_fd', close_fds=True) >> Fake('the_process') + def test_lauch_subprocess_with_pseudoterminal(override_imports): tested = line_monitor.LineMonitor() with Scenario() as s: launch_scenario(s) tested.launch_subprocess(['my', 'command', 'line']) + def read_line_scenario(s, line): s.poller.poll(IgnoreArgument()) >> [('reader_descriptor', select.POLLIN)] s.reader.readline() >> line + def skip_line_scenario(s): s.poller.poll(IgnoreArgument()) >> [('reader_descriptor', 0)] + def skip_line_on_empty_poll_scenario(s): s.poller.poll(IgnoreArgument()) >> [] + def end_test_scenario(s): s.poller.poll(IgnoreArgument()) >> Throwing(loop_breaker.LoopBreaker) + def test_receive_output_lines_via_callback(override_imports): tested = line_monitor.LineMonitor() with Scenario() as s: @@ -61,6 +69,7 @@ def test_receive_output_lines_via_callback(override_imports): with pytest.raises(loop_breaker.LoopBreaker): tested.monitor() + def test_monitoring_with_no_callback(override_imports): tested = line_monitor.LineMonitor() with Scenario() as s: @@ -79,6 +88,7 @@ def test_monitoring_with_no_callback(override_imports): with pytest.raises(loop_breaker.LoopBreaker): tested.monitor() + def test_callback_registered_mid_monitoring(override_imports): tested = line_monitor.LineMonitor() with Scenario() as s: @@ -90,8 +100,8 @@ def test_callback_registered_mid_monitoring(override_imports): read_line_scenario(s, 'line 2') skip_line_on_empty_poll_scenario(s) read_line_scenario(s, 'line 3') - s << Hook(tested.register_callback, Fake('my_callback')) # the hook will execute right after the 'line 3' readline finishes - s.my_callback('line 3') # callback is now registered, so it should be called + s << Hook(tested.register_callback, Fake('my_callback')) # the hook will execute right after the 'line 3' readline finishes + s.my_callback('line 3') # callback is now registered, so it should be called end_test_scenario(s) with pytest.raises(loop_breaker.LoopBreaker): diff --git a/docs/line_monitor/tests/unit/22/test_line_monitor.py b/docs/line_monitor/tests/unit/22/test_line_monitor.py index df2bdcd..323d93c 100644 --- a/docs/line_monitor/tests/unit/22/test_line_monitor.py +++ b/docs/line_monitor/tests/unit/22/test_line_monitor.py @@ -3,6 +3,7 @@ import line_monitor import select + @pytest.fixture def override_imports(patch_module): patch_module(line_monitor, 'subprocess') @@ -11,6 +12,7 @@ def override_imports(patch_module): patch_module(line_monitor, 'select') Fake('select').POLLIN = select.POLLIN + def launch_scenario(s): s.pty.openpty() >> ('write_to_fd', 'read_from_fd') s.open('read_from_fd', encoding='latin-1') >> Fake('reader') @@ -19,28 +21,35 @@ def launch_scenario(s): s.poller.register('reader_descriptor', select.POLLIN) s.subprocess.Popen(['my', 'command', 'line'], stdout='write_to_fd', close_fds=True) >> Fake('the_process') + def test_lauch_subprocess_with_pseudoterminal(override_imports): tested = line_monitor.LineMonitor() with Scenario() as s: launch_scenario(s) tested.launch_subprocess(['my', 'command', 'line']) + def read_line_scenario(s, line): s.poller.poll(IgnoreArgument()) >> [('reader_descriptor', select.POLLIN)] s.reader.readline() >> line + def skip_line_scenario(s): s.poller.poll(IgnoreArgument()) >> [('reader_descriptor', 0)] + def skip_line_on_empty_poll_scenario(s): s.poller.poll(IgnoreArgument()) >> [] + def process_lives_scenario(s): s.the_process.poll() >> None + def end_test_scenario(s): s.poller.poll(IgnoreArgument()) >> Throwing(loop_breaker.LoopBreaker) + def test_receive_output_lines_via_callback(override_imports): tested = line_monitor.LineMonitor() with Scenario() as s: @@ -67,6 +76,7 @@ def test_receive_output_lines_via_callback(override_imports): with pytest.raises(loop_breaker.LoopBreaker): tested.monitor() + def test_monitoring_with_no_callback(override_imports): tested = line_monitor.LineMonitor() with Scenario() as s: @@ -88,6 +98,7 @@ def test_monitoring_with_no_callback(override_imports): with pytest.raises(loop_breaker.LoopBreaker): tested.monitor() + def test_callback_registered_mid_monitoring(override_imports): tested = line_monitor.LineMonitor() with Scenario() as s: @@ -101,8 +112,8 @@ def test_callback_registered_mid_monitoring(override_imports): skip_line_on_empty_poll_scenario(s) process_lives_scenario(s) read_line_scenario(s, 'line 3') - s << Hook(tested.register_callback, Fake('my_callback')) # the hook will execute right after the 'line 3' readline finishes - s.my_callback('line 3') # callback is now registered, so it should be called + s << Hook(tested.register_callback, Fake('my_callback')) # the hook will execute right after the 'line 3' readline finishes + s.my_callback('line 3') # callback is now registered, so it should be called end_test_scenario(s) with pytest.raises(loop_breaker.LoopBreaker): diff --git a/docs/line_monitor/tests/unit/24/test_line_monitor.py b/docs/line_monitor/tests/unit/24/test_line_monitor.py index 1395320..7d204bc 100644 --- a/docs/line_monitor/tests/unit/24/test_line_monitor.py +++ b/docs/line_monitor/tests/unit/24/test_line_monitor.py @@ -3,6 +3,7 @@ import line_monitor import select + @pytest.fixture def override_imports(patch_module): patch_module(line_monitor, 'subprocess') @@ -11,6 +12,7 @@ def override_imports(patch_module): patch_module(line_monitor, 'select') Fake('select').POLLIN = select.POLLIN + def launch_scenario(s): s.pty.openpty() >> ('write_to_fd', 'read_from_fd') s.open('read_from_fd', encoding='latin-1') >> Fake('reader') @@ -19,32 +21,40 @@ def launch_scenario(s): s.poller.register('reader_descriptor', select.POLLIN) s.subprocess.Popen(['my', 'command', 'line'], stdout='write_to_fd', close_fds=True) >> Fake('the_process') + def test_lauch_subprocess_with_pseudoterminal(override_imports): tested = line_monitor.LineMonitor() with Scenario() as s: launch_scenario(s) tested.launch_subprocess(['my', 'command', 'line']) + def read_line_scenario(s, line): s.poller.poll(IgnoreArgument()) >> [('reader_descriptor', select.POLLIN)] s.reader.readline() >> line + def skip_line_scenario(s): s.poller.poll(IgnoreArgument()) >> [('reader_descriptor', 0)] + def skip_line_on_empty_poll_scenario(s): s.poller.poll(IgnoreArgument()) >> [] + def process_lives_scenario(s): s.the_process.poll() >> None + def process_died_scenario(s): s.the_process.poll() >> 'some_exit_code' s.reader.close() + def end_test_scenario(s): s.poller.poll(IgnoreArgument()) >> Throwing(loop_breaker.LoopBreaker) + def test_receive_output_lines_via_callback(override_imports): tested = line_monitor.LineMonitor() with Scenario() as s: @@ -71,6 +81,7 @@ def test_receive_output_lines_via_callback(override_imports): with pytest.raises(loop_breaker.LoopBreaker): tested.monitor() + def test_monitoring_with_no_callback(override_imports): tested = line_monitor.LineMonitor() with Scenario() as s: @@ -92,6 +103,7 @@ def test_monitoring_with_no_callback(override_imports): with pytest.raises(loop_breaker.LoopBreaker): tested.monitor() + def test_callback_registered_mid_monitoring(override_imports): tested = line_monitor.LineMonitor() with Scenario() as s: @@ -105,8 +117,8 @@ def test_callback_registered_mid_monitoring(override_imports): skip_line_on_empty_poll_scenario(s) process_lives_scenario(s) read_line_scenario(s, 'line 3') - s << Hook(tested.register_callback, Fake('my_callback')) # the hook will execute right after the 'line 3' readline finishes - s.my_callback('line 3') # callback is now registered, so it should be called + s << Hook(tested.register_callback, Fake('my_callback')) # the hook will execute right after the 'line 3' readline finishes + s.my_callback('line 3') # callback is now registered, so it should be called end_test_scenario(s) with pytest.raises(loop_breaker.LoopBreaker): diff --git a/docs/line_monitor/tests/unit/3/test_line_monitor.py b/docs/line_monitor/tests/unit/3/test_line_monitor.py index 2939f0b..165aa5c 100644 --- a/docs/line_monitor/tests/unit/3/test_line_monitor.py +++ b/docs/line_monitor/tests/unit/3/test_line_monitor.py @@ -2,12 +2,14 @@ import pytest import line_monitor + @pytest.fixture def override_imports(patch_module): - patch_module(line_monitor, 'subprocess') # this replaces the subprocess object inside line_monitor with a Fake("subprocess") object - patch_module(line_monitor, 'pty') # does the same for the pty module + patch_module(line_monitor, 'subprocess') # this replaces the subprocess object inside line_monitor with a Fake("subprocess") object + patch_module(line_monitor, 'pty') # does the same for the pty module patch_module(line_monitor, 'open') + def test_lauch_subprocess_with_pseudoterminal(override_imports): tested = line_monitor.LineMonitor() with Scenario() as s: @@ -16,11 +18,14 @@ def test_lauch_subprocess_with_pseudoterminal(override_imports): tested.launch_subprocess(['my', 'command', 'line']) + def test_receive_output_lines_via_callback(override_imports): tested = line_monitor.LineMonitor() with Scenario() as s: s.pty.openpty() >> ('write_to_fd', 'read_from_fd') - s.open('read_from_fd', encoding='latin-1') >> Fake('reader') # wrapping a binary file descriptor with a text-oriented stream requires an encoding + s.open('read_from_fd', encoding='latin-1') >> Fake( + 'reader' + ) # wrapping a binary file descriptor with a text-oriented stream requires an encoding s.subprocess.Popen(['my', 'command', 'line'], stdout='write_to_fd', close_fds=True) tested.launch_subprocess(['my', 'command', 'line']) diff --git a/docs/line_monitor/tests/unit/4/test_line_monitor.py b/docs/line_monitor/tests/unit/4/test_line_monitor.py index e885ade..a122365 100644 --- a/docs/line_monitor/tests/unit/4/test_line_monitor.py +++ b/docs/line_monitor/tests/unit/4/test_line_monitor.py @@ -2,23 +2,27 @@ import pytest import line_monitor + @pytest.fixture def override_imports(patch_module): patch_module(line_monitor, 'subprocess') patch_module(line_monitor, 'pty') patch_module(line_monitor, 'open') + def launch_scenario(s): s.pty.openpty() >> ('write_to_fd', 'read_from_fd') s.open('read_from_fd', encoding='latin-1') >> Fake('reader') s.subprocess.Popen(['my', 'command', 'line'], stdout='write_to_fd', close_fds=True) + def test_lauch_subprocess_with_pseudoterminal(override_imports): tested = line_monitor.LineMonitor() with Scenario() as s: launch_scenario(s) tested.launch_subprocess(['my', 'command', 'line']) + def test_receive_output_lines_via_callback(override_imports): tested = line_monitor.LineMonitor() with Scenario() as s: diff --git a/docs/line_monitor/tests/unit/6/test_line_monitor.py b/docs/line_monitor/tests/unit/6/test_line_monitor.py index 0dd8e12..b991cbf 100644 --- a/docs/line_monitor/tests/unit/6/test_line_monitor.py +++ b/docs/line_monitor/tests/unit/6/test_line_monitor.py @@ -2,23 +2,27 @@ import pytest import line_monitor + @pytest.fixture def override_imports(patch_module): patch_module(line_monitor, 'subprocess') patch_module(line_monitor, 'pty') patch_module(line_monitor, 'open') + def launch_scenario(s): s.pty.openpty() >> ('write_to_fd', 'read_from_fd') s.open('read_from_fd', encoding='latin-1') >> Fake('reader') s.subprocess.Popen(['my', 'command', 'line'], stdout='write_to_fd', close_fds=True) + def test_lauch_subprocess_with_pseudoterminal(override_imports): tested = line_monitor.LineMonitor() with Scenario() as s: launch_scenario(s) tested.launch_subprocess(['my', 'command', 'line']) + def test_receive_output_lines_via_callback(override_imports): tested = line_monitor.LineMonitor() with Scenario() as s: @@ -31,7 +35,7 @@ def test_receive_output_lines_via_callback(override_imports): s.my_callback('line 2') s.reader.readline() >> 'line 3' s.my_callback('line 3') - s.reader.readline() >> Throwing(loop_breaker.LoopBreaker) # this tells the Fake('reader') to raise an instance of TestixLoopBreaker() + s.reader.readline() >> Throwing(loop_breaker.LoopBreaker) # this tells the Fake('reader') to raise an instance of TestixLoopBreaker() tested.register_callback(Fake('my_callback')) with pytest.raises(loop_breaker.LoopBreaker): diff --git a/docs/line_monitor/tests/unit/8/test_line_monitor.py b/docs/line_monitor/tests/unit/8/test_line_monitor.py index de5ddcd..0cdaca1 100644 --- a/docs/line_monitor/tests/unit/8/test_line_monitor.py +++ b/docs/line_monitor/tests/unit/8/test_line_monitor.py @@ -2,23 +2,27 @@ import pytest import line_monitor + @pytest.fixture def override_imports(patch_module): patch_module(line_monitor, 'subprocess') patch_module(line_monitor, 'pty') patch_module(line_monitor, 'open') + def launch_scenario(s): s.pty.openpty() >> ('write_to_fd', 'read_from_fd') s.open('read_from_fd', encoding='latin-1') >> Fake('reader') s.subprocess.Popen(['my', 'command', 'line'], stdout='write_to_fd', close_fds=True) + def test_lauch_subprocess_with_pseudoterminal(override_imports): tested = line_monitor.LineMonitor() with Scenario() as s: launch_scenario(s) tested.launch_subprocess(['my', 'command', 'line']) + def test_receive_output_lines_via_callback(override_imports): tested = line_monitor.LineMonitor() with Scenario() as s: @@ -31,12 +35,13 @@ def test_receive_output_lines_via_callback(override_imports): s.my_callback('line 2') s.reader.readline() >> 'line 3' s.my_callback('line 3') - s.reader.readline() >> Throwing(loop_breaker.LoopBreaker) # this tells the Fake('reader') to raise an instance of TestixLoopBreaker() + s.reader.readline() >> Throwing(loop_breaker.LoopBreaker) # this tells the Fake('reader') to raise an instance of TestixLoopBreaker() tested.register_callback(Fake('my_callback')) with pytest.raises(loop_breaker.LoopBreaker): tested.monitor() + def test_monitoring_with_no_callback(override_imports): tested = line_monitor.LineMonitor() with Scenario() as s: @@ -46,7 +51,7 @@ def test_monitoring_with_no_callback(override_imports): s.reader.readline() >> 'line 1' s.reader.readline() >> 'line 2' s.reader.readline() >> 'line 3' - s.reader.readline() >> Throwing(loop_breaker.LoopBreaker) # this tells the Fake('reader') to raise an instance of TestixLoopBreaker() + s.reader.readline() >> Throwing(loop_breaker.LoopBreaker) # this tells the Fake('reader') to raise an instance of TestixLoopBreaker() with pytest.raises(loop_breaker.LoopBreaker): tested.monitor() diff --git a/docs/reference/1/test_reader.py b/docs/reference/1/test_reader.py index d14e3d8..41029d4 100644 --- a/docs/reference/1/test_reader.py +++ b/docs/reference/1/test_reader.py @@ -2,6 +2,7 @@ import reader + def test_read_all_from_socket(): with Scenario() as s: s.sock.recv(4096) >> b'data1' diff --git a/docs/reference/2/test_reader.py b/docs/reference/2/test_reader.py index e23a1a5..072a036 100644 --- a/docs/reference/2/test_reader.py +++ b/docs/reference/2/test_reader.py @@ -2,6 +2,7 @@ import reader + def test_read_all_from_socket(): with Scenario() as s: s.sock.recv(IgnoreArgument()) >> b'data1' diff --git a/docs/reference/3/test_unordered.py b/docs/reference/3/test_unordered.py index c38e6f2..56bc6c6 100644 --- a/docs/reference/3/test_unordered.py +++ b/docs/reference/3/test_unordered.py @@ -1,5 +1,6 @@ from testix import * + def test_unordered_expecation(): with Scenario() as s: s.some_object('a') @@ -9,6 +10,7 @@ def test_unordered_expecation(): my_fake = Fake('some_object') my_code(my_fake) + def my_code(x): x('a') x('c') diff --git a/docs/reference/4/test_locker_context_manager.py b/docs/reference/4/test_locker_context_manager.py index 5270fb4..2bb6860 100644 --- a/docs/reference/4/test_locker_context_manager.py +++ b/docs/reference/4/test_locker_context_manager.py @@ -1,5 +1,6 @@ from testix import * + def test_fake_context(): locker_mock = Fake('locker') with Scenario() as s: diff --git a/docs/reference/advanced_argument_expectations.rst b/docs/reference/advanced_argument_expectations.rst index 38851c5..21a8044 100644 --- a/docs/reference/advanced_argument_expectations.rst +++ b/docs/reference/advanced_argument_expectations.rst @@ -59,7 +59,7 @@ As you can see, the ``.connect()`` method is called three times, but with differ Testing for Object Identity --------------------------- -Sometimes we want to ensure a method is called with a specific object. We are not satisfied with it being +Sometimes we want to ensure a method is called with a specific object. We are not satisfied with it being called with an *equal* object, we want the *same actual object*. That is, we are interested in testing ``actual is expected`` and not ``actual == expected``. We can do this with ``ArgumentIs``. Here is an example: @@ -107,7 +107,7 @@ Either way, you can make a ``Fake`` raise an exception using the ``Throwing`` mo This test will enforce *both* that ``Fake('spin_verifier')`` is called, and that an exception is raised. -NOTE: if you don't care about some details, e.g. the string inside ``Exception`` here, +NOTE: if you don't care about some details, e.g. the string inside ``Exception`` here, you can just use ``Throwing(Exception)``. The ``Throwing`` modifier calls its argument, and raises the resulting object, so in this case it will just raise a plain ``Exception()``. @@ -120,9 +120,9 @@ Capturing Arguments ------------------- Sometimes we don't want to *demand* anything about a method's arguments, but we do want to *capture* them. -This is useful for when we want to simulate the triggering of an internal callback. +This is useful for when we want to simulate the triggering of an internal callback. -For example, suppose we have a class which implements some logic when the process ends via an `atexit _` handler. +For example, suppose we have a class which implements some logic when the process ends via an `atexit _` handler. Testing this might seem hard, since we don't want to actually make the process (which is running our test) exit. Here's how to do it using |testix|'s ``SaveArgument`` feature. @@ -134,7 +134,7 @@ Here's how to do it using |testix|'s ``SaveArgument`` feature. We use ``saveargument.SaveArgument()`` to capture the argument passed to ``atexit.register()``, and name this captured argument ``the_handler``. -We later retrieve the captured callback via the ``saveargument.saved()`` dictionary. +We later retrieve the captured callback via the ``saveargument.saved()`` dictionary. This enables us to trigger the callback ourselves by calling ``handler()`` - which satisfies our demand ``s.cleanup_logic(1, 2, 3)``. @@ -143,7 +143,7 @@ This enables us to trigger the callback ourselves by calling ``handler()`` - whi Implementing Arbitrary Argument Matching ---------------------------------------- -Sometimes you need some complicated logic that |testix| doesn't support +Sometimes you need some complicated logic that |testix| doesn't support out of the box. You can define your own argument expectation classes with some arbitrary logic, and use them in your tests, by implementing classes derived from the ``ArgumentExpectation`` base class, which is essentially an interface: diff --git a/docs/reference/argument_expectations/classroom.py b/docs/reference/argument_expectations/classroom.py index 675a30f..e5bab25 100644 --- a/docs/reference/argument_expectations/classroom.py +++ b/docs/reference/argument_expectations/classroom.py @@ -5,6 +5,7 @@ def __init__(self, name): def __eq__(self, other): return self.name == other.name + class Classroom: def __init__(self, people: list): self._people = people diff --git a/docs/reference/argument_expectations/test_arbitrary_argument_expectation.py b/docs/reference/argument_expectations/test_arbitrary_argument_expectation.py index c8c3b16..80d6791 100644 --- a/docs/reference/argument_expectations/test_arbitrary_argument_expectation.py +++ b/docs/reference/argument_expectations/test_arbitrary_argument_expectation.py @@ -3,14 +3,17 @@ import temporary_storage + @pytest.fixture(autouse=True) def mock_builtin(patch_module): patch_module(temporary_storage, 'open') + class StartsWith(ArgumentExpectation): def ok(self, value): return value.startswith(self.expectedValue) + def test_person_connects_somehow(): with Scenario() as s: s.open(StartsWith('/tmp/'), 'w') >> Fake('the_file') diff --git a/docs/reference/argument_expectations/test_atexit_handler.py b/docs/reference/argument_expectations/test_atexit_handler.py index 5ed9c4a..82dd2ae 100644 --- a/docs/reference/argument_expectations/test_atexit_handler.py +++ b/docs/reference/argument_expectations/test_atexit_handler.py @@ -2,9 +2,11 @@ import pytest import robot + @pytest.fixture def mock_imports(patch_module): - patch_module(robot, 'atexit') # mock atexit module + patch_module(robot, 'atexit') # mock atexit module + def test_atexit_handler(mock_imports): with Scenario() as s: diff --git a/docs/reference/argument_expectations/test_ignore_details.py b/docs/reference/argument_expectations/test_ignore_details.py index d17305e..2277246 100644 --- a/docs/reference/argument_expectations/test_ignore_details.py +++ b/docs/reference/argument_expectations/test_ignore_details.py @@ -2,6 +2,7 @@ import server + def test_person_connects_somehow(): with Scenario() as s: s.database.connect(IgnoreCallDetails()) diff --git a/docs/reference/argument_expectations/test_object_identity.py b/docs/reference/argument_expectations/test_object_identity.py index c674630..899c019 100644 --- a/docs/reference/argument_expectations/test_object_identity.py +++ b/docs/reference/argument_expectations/test_object_identity.py @@ -3,6 +3,7 @@ import classroom + def test_this_will_pass(): joe = classroom.Person('Joe') with Scenario() as s: @@ -11,6 +12,7 @@ def test_this_will_pass(): tested = classroom.Classroom(Fake('mylist')) tested.enter_original(joe) + def test_this_will_fail(): joe = classroom.Person('Joe') with Scenario() as s: diff --git a/docs/reference/argument_expectations/test_particle_classifier.py b/docs/reference/argument_expectations/test_particle_classifier.py index 6a41922..caad452 100644 --- a/docs/reference/argument_expectations/test_particle_classifier.py +++ b/docs/reference/argument_expectations/test_particle_classifier.py @@ -3,9 +3,11 @@ import particle_classifier + def my_exception_factory(): return Exception('bad spin value') + def test_allow_spin_verifier_to_raise_exceptions(): with Scenario() as s: s.spin_verifier('some spin value') >> Throwing(my_exception_factory) diff --git a/docs/reference/async_tests/async_read.py b/docs/reference/async_tests/async_read.py index 634ed92..aa660da 100644 --- a/docs/reference/async_tests/async_read.py +++ b/docs/reference/async_tests/async_read.py @@ -1,5 +1,6 @@ import aiofiles + async def go(filename): async with aiofiles.open(filename) as f: return await f.read() diff --git a/docs/reference/async_tests/test_1.py b/docs/reference/async_tests/test_1.py index f2d6c66..0984840 100644 --- a/docs/reference/async_tests/test_1.py +++ b/docs/reference/async_tests/test_1.py @@ -1,6 +1,7 @@ from testix import * import pytest + @pytest.mark.asyncio async def test_async_expectations(): with scenario.Scenario('awaitable test') as s: @@ -11,8 +12,9 @@ async def test_async_expectations(): assert await my_code(Fake('my_fake')) == 'sync value' + async def my_code(thing): - another = await thing('some data') - yet_another = await another() - last_one = await yet_another() - return last_one.sync_func(1, 2, 3) + another = await thing('some data') + yet_another = await another() + last_one = await yet_another() + return last_one.sync_func(1, 2, 3) diff --git a/docs/reference/async_tests/test_async_context_manager.py b/docs/reference/async_tests/test_async_context_manager.py index 7c5ea42..d7e626a 100644 --- a/docs/reference/async_tests/test_async_context_manager.py +++ b/docs/reference/async_tests/test_async_context_manager.py @@ -3,10 +3,12 @@ import async_read + @pytest.fixture(autouse=True) def override_import(patch_module): patch_module(async_read, 'aiofiles') + @pytest.mark.asyncio async def test_read_write_from_async_file(): with scenario.Scenario() as s: diff --git a/docs/reference/asyncio_support.rst b/docs/reference/asyncio_support.rst index f05e00d..528e14a 100644 --- a/docs/reference/asyncio_support.rst +++ b/docs/reference/asyncio_support.rst @@ -23,7 +23,7 @@ Note that the test function itself is `async` and that you have to use the ``pytest.mark.asyncio`` decorator on the test - this decorator makes sure the test runs inside an ``asyncio`` event loop. -Note that the ``__await_on__`` changes the expectation ``.my_fake('some data')`` into *two* expectations - the function call, and the use of ``await``-ing. You can see this, if, e.g., you cut ``my_code(thing)`` short by replacing its first line with ``return 'sync value'``. This is the correct value, so the ``assert`` statement passes, however the ``Scenario`` context will inform you that +Note that the ``__await_on__`` changes the expectation ``.my_fake('some data')`` into *two* expectations - the function call, and the use of ``await``-ing. You can see this, if, e.g., you cut ``my_code(thing)`` short by replacing its first line with ``return 'sync value'``. This is the correct value, so the ``assert`` statement passes, however the ``Scenario`` context will inform you that .. code:: console diff --git a/docs/reference/hooks.rst b/docs/reference/hooks.rst index 4c3723b..c05eb2e 100644 --- a/docs/reference/hooks.rst +++ b/docs/reference/hooks.rst @@ -20,7 +20,7 @@ after the callback is registered to be passed on to it. with Scenario() as s: s.input_stream.readline() >> 'line 1' s.input_stream.readline() >> 'line 2' - s << Hook(tested.register_callback, Fake('my_callback')) + s << Hook(tested.register_callback, Fake('my_callback')) # the hook will execute right after the 'line 2' readline finishes # we therefore expect that after reading the next line, the callback will be called s.input_stream.readline() >> 'line 3' diff --git a/docs/reference/index.rst b/docs/reference/index.rst index 4ae551a..7fe2c8c 100644 --- a/docs/reference/index.rst +++ b/docs/reference/index.rst @@ -6,4 +6,3 @@ Reference advanced_argument_expectations hooks asyncio_support - diff --git a/docs/reference/override_import/my_server.py b/docs/reference/override_import/my_server.py index 3d5bfa1..57ad715 100644 --- a/docs/reference/override_import/my_server.py +++ b/docs/reference/override_import/my_server.py @@ -1,4 +1,5 @@ -import socket # when the test runs, this is actually Fake('socket') +import socket # when the test runs, this is actually Fake('socket') + class MyServer: def __init__(self): diff --git a/docs/reference/override_import/test_override_socket.py b/docs/reference/override_import/test_override_socket.py index 05a9080..d292d7b 100644 --- a/docs/reference/override_import/test_override_socket.py +++ b/docs/reference/override_import/test_override_socket.py @@ -2,9 +2,11 @@ import pytest import my_server -@pytest.fixture(autouse=True) # if autouse is not used here, you will have to specify override_imports as an argument to test_my_server() below + +@pytest.fixture(autouse=True) # if autouse is not used here, you will have to specify override_imports as an argument to test_my_server() below def override_imports(patch_module): - patch_module(my_server, 'socket') # replace socket with Fake('socket') + patch_module(my_server, 'socket') # replace socket with Fake('socket') + def test_my_server(): with Scenario() as s: @@ -17,4 +19,3 @@ def test_my_server(): tested = my_server.MyServer() tested.serve_request() - diff --git a/docs/reference/scenarios_and_fake_objects.rst b/docs/reference/scenarios_and_fake_objects.rst index aa0ea43..7c121e3 100644 --- a/docs/reference/scenarios_and_fake_objects.rst +++ b/docs/reference/scenarios_and_fake_objects.rst @@ -16,7 +16,7 @@ Here's a test that expects the tested code to repeatedly call ``.recv(4096)`` on Scenarios track fake objects - instances of ``Fake``. Fake objects have a name, e.g. ``Fake('sock')`` - and you can demand various method calls on a fake object, e.g. - + .. code:: python s.sock.recv(4096) >> b'data1' @@ -62,7 +62,7 @@ This will ensure that the ``.func1()`` method is called on the ``Fake("alpha")`` a.func1(1, 2, a=1, b='hi there') With this definition, ``my_code(Fake("alpha"))`` will pass the test. The value returned from ``.func1()`` in this case will be ``None``. If you want to specify a return value, use ``>>`` as before - + .. code:: python s.alpha.func1(1, 2, a=1, b='hi there') @@ -191,7 +191,7 @@ Unordered Expectations ---------------------- Most of the time, in my experience, it's a good idea that expectations -are met in the exact order that they were specified. +are met in the exact order that they were specified. .. code:: python @@ -216,10 +216,10 @@ Of course you can still use the ``>>`` operator to specify return values for you Everlasting Expectations ------------------------ -Sometimes we don't want to test for a specific number of calls, but +Sometimes we don't want to test for a specific number of calls, but we do want to make sure that a function call is of a particular form. -We can modify an ``.unordered()`` expectation with ``.everlasting()`` and +We can modify an ``.unordered()`` expectation with ``.everlasting()`` and this essentially modifies the expectation to mean "this call is expected zero or more times, in any order". Here's a small example diff --git a/docs/reference/test_everlasting.py b/docs/reference/test_everlasting.py index c754780..547a5bc 100644 --- a/docs/reference/test_everlasting.py +++ b/docs/reference/test_everlasting.py @@ -1,5 +1,6 @@ from testix import * + def test_unordered_expecation(): with Scenario() as s: s.some_object('a') @@ -9,6 +10,7 @@ def test_unordered_expecation(): my_fake = Fake('some_object') my_code(my_fake) + def my_code(x): x('c') x('c') diff --git a/docs/requirements.txt b/docs/requirements.txt index 6aa697f..0e3dff6 100644 --- a/docs/requirements.txt +++ b/docs/requirements.txt @@ -1,4 +1,4 @@ -#sphinx_bernard_theme +#sphinx_bernard_theme #renku-sphinx-theme #insegel #cloud-sptheme diff --git a/docs/tutorial/basics/exact_enforcement.rst b/docs/tutorial/basics/exact_enforcement.rst index d9d996d..0a9a5a5 100644 --- a/docs/tutorial/basics/exact_enforcement.rst +++ b/docs/tutorial/basics/exact_enforcement.rst @@ -16,11 +16,11 @@ Wrong Arguments So, e.g. if we have a test like this: - + .. literalinclude:: other_tests/more_advanced/3/test_exact_enforcement.py :linenos: -The code *must* be some variation of +The code *must* be some variation of .. code:: python @@ -28,7 +28,7 @@ The code *must* be some variation of names = name_source.get_names('all', order='lexicographic', ascending=True) # and at some point later... name_destination.put_names(names) - + any of these will cause a failure: .. code:: python @@ -63,7 +63,7 @@ and |testix| will report That is, the ``Scenario``'s various expectations were met, but then the code "surprised" |testix| with another call on a ``Fake`` object. -As we said before, the right way to specify |testix| Scenarios as to +As we said before, the right way to specify |testix| Scenarios as to specify what you want *exactly* - *no more, no less*. Ways Around Exactness diff --git a/docs/tutorial/basics/index.rst b/docs/tutorial/basics/index.rst index 4e61f27..33e2106 100644 --- a/docs/tutorial/basics/index.rst +++ b/docs/tutorial/basics/index.rst @@ -4,7 +4,7 @@ |testix| Basics =============== -It's time to start writing unit tests using |testix|. +It's time to start writing unit tests using |testix|. In this section we'll cover all the basics, then move on to our more complete ``LineMonitor`` example. @@ -14,4 +14,3 @@ to our more complete ``LineMonitor`` example. more_advance_tests exact_enforcement recap_1 - diff --git a/docs/tutorial/basics/more_advance_tests.rst b/docs/tutorial/basics/more_advance_tests.rst index 0452b3b..40bdd97 100644 --- a/docs/tutorial/basics/more_advance_tests.rst +++ b/docs/tutorial/basics/more_advance_tests.rst @@ -11,29 +11,29 @@ Specifying Return Values how a ``Fake`` object named ``"sock"`` should be used by our code. When we say ``s.sock.send(b'the data')`` we express the expectation that the code -under test will call the ``.send()`` method with exactly one argument, whose value +under test will call the ``.send()`` method with exactly one argument, whose value should equal exactly ``b'the data'``. -When the code does this with ``"sock"``'s ``.send()`` method, however, what value +When the code does this with ``"sock"``'s ``.send()`` method, however, what value is returned by method call? -The answer in this case is ``None`` - but |testix| also allows us to define -this return value. This is useful when you want to test thing related to what +The answer in this case is ``None`` - but |testix| also allows us to define +this return value. This is useful when you want to test thing related to what function calls on ``Fake`` objects return, e.g. thing about testing -some code that receives data on one socket, and sends the length of said data -to another socket. +some code that receives data on one socket, and sends the length of said data +to another socket. We therefore expect that there will be a ``.recv()`` call on one socket which -returns some data, this data in turn is converted to a number (its length), +returns some data, this data in turn is converted to a number (its length), which is then encoded and sent on the outgoing socket. Here's how to test this in |testix| .. literalinclude:: other_tests/more_advanced/1/test_forward_lengths.py :linenos: - :emphasize-lines: 10 + :emphasize-lines: 10 -We see here a pattern which is common with |testix| - specifying an +We see here a pattern which is common with |testix| - specifying an entire scenario of what should happen, then making it happen by calling the code under test. @@ -92,4 +92,3 @@ This makes |testix| very conducive to Test Driven Development - if you change th When approaching adding new features - start with defining a test for them. We'll discuss exactness some more next. - diff --git a/docs/tutorial/basics/other_tests/data_sender_example/1/data_sender.py b/docs/tutorial/basics/other_tests/data_sender_example/1/data_sender.py index 633a32b..5e0cb23 100644 --- a/docs/tutorial/basics/other_tests/data_sender_example/1/data_sender.py +++ b/docs/tutorial/basics/other_tests/data_sender_example/1/data_sender.py @@ -1,3 +1,2 @@ def send_some_data(socket, data): pass - diff --git a/docs/tutorial/basics/other_tests/data_sender_example/1/test_sending_data.py b/docs/tutorial/basics/other_tests/data_sender_example/1/test_sending_data.py index 7fa51ac..e7b22c8 100644 --- a/docs/tutorial/basics/other_tests/data_sender_example/1/test_sending_data.py +++ b/docs/tutorial/basics/other_tests/data_sender_example/1/test_sending_data.py @@ -2,6 +2,7 @@ import data_sender + def test_sending_data(): fake_socket = Fake('sock') with Scenario() as s: diff --git a/docs/tutorial/basics/other_tests/data_sender_example/prefix_0/test_sending_data.py b/docs/tutorial/basics/other_tests/data_sender_example/prefix_0/test_sending_data.py index 003bdc6..5d75dee 100644 --- a/docs/tutorial/basics/other_tests/data_sender_example/prefix_0/test_sending_data.py +++ b/docs/tutorial/basics/other_tests/data_sender_example/prefix_0/test_sending_data.py @@ -2,6 +2,7 @@ import data_sender + def test_sending_data(): fake_socket = Fake('sock') with Scenario() as s: diff --git a/docs/tutorial/basics/other_tests/data_sender_example/prefix_1/data_sender.py b/docs/tutorial/basics/other_tests/data_sender_example/prefix_1/data_sender.py index f5cdabf..829567b 100644 --- a/docs/tutorial/basics/other_tests/data_sender_example/prefix_1/data_sender.py +++ b/docs/tutorial/basics/other_tests/data_sender_example/prefix_1/data_sender.py @@ -1,4 +1,3 @@ - def send_some_data(socket, data): length = len(data) header = b'SIZE:' + bytes(str(length), encoding='latin-1') diff --git a/docs/tutorial/basics/other_tests/data_sender_example/prefix_2/data_sender.py b/docs/tutorial/basics/other_tests/data_sender_example/prefix_2/data_sender.py index 24a9794..91980fa 100644 --- a/docs/tutorial/basics/other_tests/data_sender_example/prefix_2/data_sender.py +++ b/docs/tutorial/basics/other_tests/data_sender_example/prefix_2/data_sender.py @@ -1,5 +1,3 @@ - - def send_some_data(socket, data): length = len(data) header = b'SIZE:' + bytes(str(length), encoding='latin-1') + b' ' diff --git a/docs/tutorial/basics/other_tests/more_advanced/1/test_forward_lengths.py b/docs/tutorial/basics/other_tests/more_advanced/1/test_forward_lengths.py index 983a1f1..8a3731a 100644 --- a/docs/tutorial/basics/other_tests/more_advanced/1/test_forward_lengths.py +++ b/docs/tutorial/basics/other_tests/more_advanced/1/test_forward_lengths.py @@ -1,17 +1,18 @@ from testix import * import forwarder + def test_forward_data_lengths(): tested = forwarder.Forwarder() incoming = Fake('incoming_socket') outgoing = Fake('outgoing_socket') with Scenario() as s: # we'll require that the length of the data is sent, along with a ' ' separator - s.incoming_socket.recv(4096) >> b'some data' + s.incoming_socket.recv(4096) >> b'some data' s.outgoing_socket.send(b'9 ') - s.incoming_socket.recv(4096) >> b'other data' + s.incoming_socket.recv(4096) >> b'other data' s.outgoing_socket.send(b'10 ') - s.incoming_socket.recv(4096) >> b'even more data' + s.incoming_socket.recv(4096) >> b'even more data' s.outgoing_socket.send(b'14 ') tested.forward_once(incoming, outgoing) diff --git a/docs/tutorial/basics/other_tests/more_advanced/1/test_forward_lengths_2.py b/docs/tutorial/basics/other_tests/more_advanced/1/test_forward_lengths_2.py index c4e145e..fbafe39 100644 --- a/docs/tutorial/basics/other_tests/more_advanced/1/test_forward_lengths_2.py +++ b/docs/tutorial/basics/other_tests/more_advanced/1/test_forward_lengths_2.py @@ -1,20 +1,21 @@ from testix import * import forwarder + def test_forward_data_lengths(): tested = forwarder.Forwarder() incoming = Fake('incoming_socket') outgoing = Fake('outgoing_socket') with Scenario() as s: # we'll require that the length of the data is sent, along with a ' ' separator - s.incoming_socket.recv(4096) >> b'some data' + s.incoming_socket.recv(4096) >> b'some data' s.outgoing_socket.send(b'9 ') tested.forward_once(incoming, outgoing) - s.incoming_socket.recv(4096) >> b'other data' + s.incoming_socket.recv(4096) >> b'other data' s.outgoing_socket.send(b'10 ') tested.forward_once(incoming, outgoing) - s.incoming_socket.recv(4096) >> b'even more data' + s.incoming_socket.recv(4096) >> b'even more data' s.outgoing_socket.send(b'14 ') tested.forward_once(incoming, outgoing) diff --git a/docs/tutorial/basics/other_tests/more_advanced/3/test_exact_enforcement.py b/docs/tutorial/basics/other_tests/more_advanced/3/test_exact_enforcement.py index e2aa7b3..d661653 100644 --- a/docs/tutorial/basics/other_tests/more_advanced/3/test_exact_enforcement.py +++ b/docs/tutorial/basics/other_tests/more_advanced/3/test_exact_enforcement.py @@ -1,10 +1,10 @@ from testix import * import my_code + def test_my_code(): with Scenario() as s: s.source.get_names('all', order='lexicographic', ascending=True) >> ['some', 'names'] s.destination.put_names(['some', 'names']) my_code.go(Fake('source'), Fake('destination')) - diff --git a/docs/tutorial/basics/other_tests/more_advanced/3/test_exact_enforcement_ways_to_fail.py b/docs/tutorial/basics/other_tests/more_advanced/3/test_exact_enforcement_ways_to_fail.py index 7ae4655..0b32f35 100644 --- a/docs/tutorial/basics/other_tests/more_advanced/3/test_exact_enforcement_ways_to_fail.py +++ b/docs/tutorial/basics/other_tests/more_advanced/3/test_exact_enforcement_ways_to_fail.py @@ -1,27 +1,33 @@ import pytest from testix import * + def go1(source, destination): names = source.get_names(spec='all', order='lexicographic', ascending=True) destination.put_names(names) + def go2(source, destination): names = source.get_names('all', True, order='lexicographic') destination.put_names(names) + def go3(source, destination): names = source.get_names('all', 'lexicographic', True) destination.put_names(names) + def go4(source, destination): names = source.get_names('all', order='lexicographic', ascending=True) destination.put_names(names) destination.something_else() + @pytest.fixture(params=[go1, go2, go3, go4]) def go(request): return request.param + def test_my_code(go): with Scenario() as s: s.source.get_names('all', order='lexicographic', ascending=True) >> ['some', 'names'] diff --git a/docs/tutorial/basics/recap_1.rst b/docs/tutorial/basics/recap_1.rst index f972ca2..08d7c99 100644 --- a/docs/tutorial/basics/recap_1.rst +++ b/docs/tutorial/basics/recap_1.rst @@ -17,5 +17,5 @@ We can also now recognize some major advantages over the mock objects from the P #. |testix| lends itself naturally to the Test Driven approach to development (TDD) through its ``Scenario`` concept and the "no less - no more" approach that makes it harder to change the functionality of code without changing the test first. #. test syntax is more visually similar to the code under test, e.g. the ``s.sock.send(b'the data')`` is visually similar to the same as the actual code ``socket.send(data)``. This makes tests more readable and easy to understand. -#. Whatever expectations you define for you mock objects - they will be +#. Whatever expectations you define for you mock objects - they will be *exactly enforced* - defining expectations and enforcing them *is one and the same*. diff --git a/docs/tutorial/basics/working_with_scenarios_and_fake_objects.rst b/docs/tutorial/basics/working_with_scenarios_and_fake_objects.rst index 4cfad30..8ab4312 100644 --- a/docs/tutorial/basics/working_with_scenarios_and_fake_objects.rst +++ b/docs/tutorial/basics/working_with_scenarios_and_fake_objects.rst @@ -23,14 +23,14 @@ to interact with, that we want to test carefully. As an example - suppose our co When testing we #. don't *really* want to send data over a *real* socket -#. do want to verify that the ``send_some_data`` function called ``sock.send(b'the data')``. +#. do want to verify that the ``send_some_data`` function called ``sock.send(b'the data')``. The solution is to pass ``send_some_data`` an object that implements a ``.send`` method, but which is not an actual socket. Instead this object will just record that ``.send`` was called, and we'll be able to query it to see that it was called with ``b'the data'``. The idea here is that there's no point testing sockets - we know that those work. The point here is to test that *our code does the right thing with the socket*. The Standard Library Way - `unittest.mock` ------------------------------------------ -The approach taken by the standard `unittest.mock `_ module from the Python standard library, +The approach taken by the standard `unittest.mock `_ module from the Python standard library, is to provide us with a generic ``Mock`` class which records every call that is made to it, and has some helper functions to help as `assret` that some things happened. @@ -65,7 +65,7 @@ Note that first we need to fail the test - so ``send_some_data`` here is only a :caption: data_sender.py skeleton implementation -What's going on here? First, we create a `Fake` object ``sock`` - this is |testix| generic mock object - note that we +What's going on here? First, we create a `Fake` object ``sock`` - this is |testix| generic mock object - note that we define a name for it explicitly - ``'sock'``. We then start a ``Scenario()`` context manager in the ``with Scenario() as s`` statement. A Scenario is a way to specify the required behaviour - what do we demand happen to our fake objects? In this case, @@ -200,4 +200,3 @@ Now the test passes. $ python -m pytest -v docs/tutorial/other_tests/data_sender_example/prefix_2 docs/tutorial/other_tests/data_sender_example/prefix_2/test_sending_data.py::test_sending_data PASSED - diff --git a/docs/tutorial/conclusion.rst b/docs/tutorial/conclusion.rst index e64ba5c..6d88fc6 100644 --- a/docs/tutorial/conclusion.rst +++ b/docs/tutorial/conclusion.rst @@ -23,7 +23,7 @@ We can finally run our end-to-end test: -We have plenty of tests, and 100% code coverage. +We have plenty of tests, and 100% code coverage. .. code:: console @@ -38,7 +38,7 @@ We have plenty of tests, and 100% code coverage. --------------------------------------------------------------------------- docs/line_monitor/source/26/line_monitor.py 38 0 100% -Since we practiced Test Driven Development: +Since we practiced Test Driven Development: #. *Every single line* of our code is justified. #. Every edge case we thought about is documented - via our tests. While tests written in Python are less readable than actual documentation written in English - they are much, much more reliable. If we have a good CI system to run our tests - this form of documentation does not get outdated. @@ -60,6 +60,6 @@ In one word - we were *logical* about it. This is the proper way to write code. When done properly, it increases both development speed and the quality of the product delivered. -Now you know. +Now you know. **Use this knowledge. Write good code.** diff --git a/docs/tutorial/design.rst b/docs/tutorial/design.rst index 234129e..ac9aa73 100644 --- a/docs/tutorial/design.rst +++ b/docs/tutorial/design.rst @@ -53,7 +53,7 @@ In our case, since the project is quite small, the integration test will actually test the scope of the entire project. and so it is more appropriately called an End-to-End (E2E) Test. -In real projects, E2E tests usually include +In real projects, E2E tests usually include #. an actual deployment, which is as similar as possible to real life deployments. #. various UI testing techniques, e.g. launching a web-browser to use some webapp diff --git a/docs/tutorial/e2e_test.rst b/docs/tutorial/e2e_test.rst index f75be7b..b86d3a3 100644 --- a/docs/tutorial/e2e_test.rst +++ b/docs/tutorial/e2e_test.rst @@ -7,11 +7,11 @@ End-to-End Test ================ -When we say "Test First" - this means that we go +When we say "Test First" - this means that we go about thinking about our code by thinking about how to test that it works. -When we say "Test Driven" - this means that we +When we say "Test Driven" - this means that we let our thinking about tests *define* how the code will work. In this way, the tests *drive* our development. @@ -31,7 +31,7 @@ So we want our users to do something like this monitor.monitor() # monitor process until it ends for line in captured_lines: print(f'saw this: {line}') - + Now that we have a rough idea, let's write a test which will make this precise. The code below is not the final test, and will not really work, but it's a sketch: @@ -49,4 +49,4 @@ Note that in the process of developing the *test*, we chose the names of various This is what we mean when we say that tests *drive* development. -However, to truly work Test Driven - we need to make this test *fail properly*. +However, to truly work Test Driven - we need to make this test *fail properly*. diff --git a/docs/tutorial/fail_properly.rst b/docs/tutorial/fail_properly.rst index fb4f789..68d5bc8 100644 --- a/docs/tutorial/fail_properly.rst +++ b/docs/tutorial/fail_properly.rst @@ -23,14 +23,14 @@ Results ultimately in E ModuleNotFoundError: No module named 'line_monitor' -That is because none of the code for ``line_monitor`` exits yet. This is a sort of failure, but it's not very interesting. +That is because none of the code for ``line_monitor`` exits yet. This is a sort of failure, but it's not very interesting. -What we want is for the test to fail *properly* - we want it to fail *not* because our system doesn't exist - we want it to fail because our system does not implement the correct behaviour yet. +What we want is for the test to fail *properly* - we want it to fail *not* because our system doesn't exist - we want it to fail because our system does not implement the correct behaviour yet. In concrete terms, we want it to fail because a subprocess has seemingly been launched, but its output has not been captured by our monitor. In short, we want it to fail on our ``assert`` statements, not due to some technicalities -So, let's write some basic code that achieves just that. We create a ``line_monitor.py`` file +So, let's write some basic code that achieves just that. We create a ``line_monitor.py`` file within our ``import`` path with skeleton code: .. _skeleton_line_monitor: @@ -75,7 +75,7 @@ This is admittedly rare, but I've seen it happen. The more common scenario however is that we wrote the test, wrote the skeleton code, ran the test - and it failed, but *not in the way we planned*. This means -that the test does not, in fact, test what we want. +that the test does not, in fact, test what we want. If we write our test *after* we've developed our code, how will we ever know that the test actually tests what we think it is testing? You'd be amazed at diff --git a/docs/tutorial/index.rst b/docs/tutorial/index.rst index 865e465..b8e4218 100644 --- a/docs/tutorial/index.rst +++ b/docs/tutorial/index.rst @@ -25,5 +25,3 @@ Let's call this library :code:`LineMonitor`. line_monitor_unit_tests/index.rst conclusion.rst more_about_readability.rst - - diff --git a/docs/tutorial/line_monitor_unit_tests/launching_the_subprocess.rst b/docs/tutorial/line_monitor_unit_tests/launching_the_subprocess.rst index f716ee4..d1c1ff5 100644 --- a/docs/tutorial/line_monitor_unit_tests/launching_the_subprocess.rst +++ b/docs/tutorial/line_monitor_unit_tests/launching_the_subprocess.rst @@ -10,7 +10,7 @@ High Level Design We will implement ``LineMonitor`` as follows: #. a ``LineMonitor`` sill launch the subprocess using the `subprocess `_ Python standard library. -#. It will attach a |pseudoterminal| to said subprocess (using `pty `_). If you don't know too much about what a |pseudoterminal| is - don't worry about it, I don't either. +#. It will attach a |pseudoterminal| to said subprocess (using `pty `_). If you don't know too much about what a |pseudoterminal| is - don't worry about it, I don't either. Essentially it's attaching the subprocess's input and output streams to the father process. Another way of doing this is using pipes, but there are some technical advantages to using a |pseudoterminal|. @@ -28,7 +28,7 @@ We want to enforce, using |testix|, that ``subprocess.Popen()`` is called with a If the following paragraph is confusing, don't worry - things will become clearer after you see it all working. -Since |testix|'s ``Scenario`` object only tracks |testix| `Fake` objects, we must somehow fool the ``LineMonitor`` to +Since |testix|'s ``Scenario`` object only tracks |testix| `Fake` objects, we must somehow fool the ``LineMonitor`` to use a ``Fake('subprocess')`` object instead of the actual ``subprocess`` module. We need to do the same for the ``pty`` module. @@ -47,15 +47,15 @@ What's going on here? * That our code calls ``pty.openpty()`` to create a |pseudoterminal| and obtain its two file descriptors. * That our code then launch a subprocess and point its ``stdout`` to the write file-descriptor of the |pseudoterminal| (we also demand ``close_fds=True`` wince we want to fully specify our subprocess's inputs and outputs). -#. Finally, we call our ``.launch_subprocess()`` method to actually do the work - we can't hope that our code meet our expectations if we never actually call it, right? +#. Finally, we call our ``.launch_subprocess()`` method to actually do the work - we can't hope that our code meet our expectations if we never actually call it, right? A few points on this: -#. See how we *first* write our expectations and only *then* call the code to deliver on these expectations. This is one way |testix| pushes you into a Test Driven mindset. -#. In real life, ``pty.openpty()`` returns two file descriptors - which are *integers*. In our test, we made this call return two *strings*. +#. See how we *first* write our expectations and only *then* call the code to deliver on these expectations. This is one way |testix| pushes you into a Test Driven mindset. +#. In real life, ``pty.openpty()`` returns two file descriptors - which are *integers*. In our test, we made this call return two *strings*. We could have, e.g. define two constants equal to some integers, e.g. ``WRITE_FD=20`` and ``READ_FD=30`` and used those - but it wouldn't really matter and would make the test more cluttered. - Technically, what's important is that ``openpty()`` returns a tuple and we demand that the first item in this tuple is passed over to the right place in the call to ``Popen()``. + Technically, what's important is that ``openpty()`` returns a tuple and we demand that the first item in this tuple is passed over to the right place in the call to ``Popen()``. Some people find fault with this style. Personally I think passing strings around (recall that in Python strings are immutable) where all you're testing is moving around objects - is a good way to make a readable test. Failing the Test @@ -64,7 +64,7 @@ Failing the Test Remember, when practicing TDD you should always fail your tests first, and make sure they :doc:`fail properly <../fail_properly>`. -So let's see some failures! Let's see some |RED|! +So let's see some failures! Let's see some |RED|! Running this test with the :ref:`skeleton implementation ` we have for ``LineMonitor`` results in: @@ -90,16 +90,16 @@ Let's write some code that makes the test pass: :linenos: :emphasize-lines: 9-12 -Running our test with this code produces +Running our test with this code produces .. code-block:: console - test_line_monitor.py::test_lauch_subprocess_with_pseudoterminal PASSED + test_line_monitor.py::test_lauch_subprocess_with_pseudoterminal PASSED Finally, we see some |GREEN|! Usually we will now take the time to |REFACTOR| our code, but we have so little code at this time that we'll skip it for now. -OK, we have our basic subprocess with a |pseudoterminal| - now's +OK, we have our basic subprocess with a |pseudoterminal| - now's the time to test for and implement actually monitoring the output. diff --git a/docs/tutorial/line_monitor_unit_tests/monitoring_the_output.rst b/docs/tutorial/line_monitor_unit_tests/monitoring_the_output.rst index 8a3c07a..4bc5150 100644 --- a/docs/tutorial/line_monitor_unit_tests/monitoring_the_output.rst +++ b/docs/tutorial/line_monitor_unit_tests/monitoring_the_output.rst @@ -6,7 +6,7 @@ Monitoring The Output Next we want to test the following behaviour: we register a callback with our ``LineMonitor`` object using its ``.register_callback()`` method, -and it calls our callback with each line of output +and it calls our callback with each line of output it reads from the |pseudoterminal|. Python streams have a useful ``.readline()`` method, so let's wrap the read file-descriptor of the |pseudoterminal| with a stream. It turns out @@ -40,7 +40,7 @@ We must also remember to mock the built-in `open`: :lines: 5-9 :emphasize-lines: 5 -We can already see a problem: the scenario is actually built out of two parts - +We can already see a problem: the scenario is actually built out of two parts - the part which tests ``.launch_subprocess()``, and the part which tests ``.monitor()``. Furthermore, since we have our previous test in ``test_lauch_subprocess_with_pseudoterminal``, which doesn't expect the call to ``open()``, @@ -66,7 +66,7 @@ OK this seems reasonable, let's get some |RED|! Running this both our tests fail E expected: open('read_from_fd', encoding = 'latin-1') E actual : subprocess.Popen(['my', 'command', 'line'], stdout = 'write_to_fd', close_fds = True) -We changed our expectations from ``.launch_subprocess()`` to call ``open()``, but we did not change the implementation yet, so |testix| is surprised to find that we actually call ``subprocess.Popen`` - and makes our test fail. +We changed our expectations from ``.launch_subprocess()`` to call ``open()``, but we did not change the implementation yet, so |testix| is surprised to find that we actually call ``subprocess.Popen`` - and makes our test fail. Good, let's fix it and get to |GREEN|. We introduce the following to our code: @@ -191,6 +191,6 @@ check that it failed properly. Happily, this is the case for this particular tes Let's Recap ----------- -We now have our first implementation of the ``LineMonitor``. It essentially works, +We now have our first implementation of the ``LineMonitor``. It essentially works, but it's still has its problems. We'll tackle these problems later in this tutorial, but first, let's do a short recap. diff --git a/docs/tutorial/line_monitor_unit_tests/recap_1.rst b/docs/tutorial/line_monitor_unit_tests/recap_1.rst index 5290243..5f2b3d5 100644 --- a/docs/tutorial/line_monitor_unit_tests/recap_1.rst +++ b/docs/tutorial/line_monitor_unit_tests/recap_1.rst @@ -8,13 +8,13 @@ Let's recap a bit on what we've been doing in this tutorial. We started with a test for the basic subprocess-launch behaviour, got to |RED|, implemented the code, and got to |GREEN|. -Next, when moving to implement the actual output monitoring behaviour, we kept the first test, +Next, when moving to implement the actual output monitoring behaviour, we kept the first test, and added a new one. This is very important in TDD - the old test keeps the old behaviour intact - if, when implementing the new behaviour we break the old one - we will know. -When working with |testix|, you are encourage to track all your mocks (``Fake`` objects) very +When working with |testix|, you are encourage to track all your mocks (``Fake`` objects) very precisely. This effectively made us refactor the launch-process test scenario into a ``launch_scenario()`` -helper function, since you must launch a subprocess before monitoring it. +helper function, since you must launch a subprocess before monitoring it. We also saw that adding a call to ``open`` made the original launch-process test fail as well as the new monitor test. This makes sense, since the launching behaviour @@ -23,7 +23,7 @@ so the test fails. Another thing we ran into is that sometimes we get |GREEN| even when we wanted |RED|. This should make you uneasy - it usually means that the test is not really testing what -you think it is. In our case, however, it was just because an edge case which we +you think it is. In our case, however, it was just because an edge case which we added a test for was already covered by our existing code. When that happens, strict TDD isn't really possible - and you need to revert to making sure that if you break the code on purpose, it breaks the test in the proper manner. @@ -47,13 +47,13 @@ Why didn't we save the subprocess in an instance variable? Working TDD makes us want to get to |GREEN| - no more, no less. Since we don't need to store the subprocess to pass the test, we don't do it. -Let me repeat that for you: **if we don't need it to pass the test, we don't do it**. +Let me repeat that for you: **if we don't need it to pass the test, we don't do it**. You might say "but we need to hold on to the subprocess to control it, see if it's still alive, or kill it". Well, maybe we do. If that's what we really think, we should express this need in a test - make sure it's |RED|, and -then write the code to make it |GREEN|. +then write the code to make it |GREEN|. This is one particular way of implementing the `YAGNI `_ principle - if you're not familiar with it, you should take the time to read about it. diff --git a/docs/tutorial/line_monitor_unit_tests/watching_the_subprocess.rst b/docs/tutorial/line_monitor_unit_tests/watching_the_subprocess.rst index 9977926..d7a2369 100644 --- a/docs/tutorial/line_monitor_unit_tests/watching_the_subprocess.rst +++ b/docs/tutorial/line_monitor_unit_tests/watching_the_subprocess.rst @@ -20,7 +20,7 @@ We'll start by checking for available data before we try to read it. Polling the Read File Descriptor -------------------------------- -We want to create a `polling object `_, and +We want to create a `polling object `_, and register the reader's file descriptor using its `.register` method. Let's test for it. We have to mock the `select` module, of course, and also change our `launch_scenario()`. @@ -30,7 +30,7 @@ Let's test for it. We have to mock the `select` module, of course, and also chan :lines: 6-20 :emphasize-lines: 6-7,12-14 -There is a quick here - after running ``patch_module(line_monitor, 'select')``, the ``select`` object +There is a quick here - after running ``patch_module(line_monitor, 'select')``, the ``select`` object inside the tested ``line_monitor`` module is replace by a ``Fake('select')`` fake object. Later, we want to demand that ``poller.register()`` be called with the ``select.POLLIN`` constant. As things are, this would technically also be the fake object @@ -44,14 +44,14 @@ While it is possible to demand s.poller.register('reader_descriptor', Fake('select').POLLIN) -And it will work just fine, I find it less readable. Therefore I'd rather "rescue" the ``POLLIN`` -object from the real ``select`` and assign it to the fake ``select``. +And it will work just fine, I find it less readable. Therefore I'd rather "rescue" the ``POLLIN`` +object from the real ``select`` and assign it to the fake ``select``. You may notice another quirk - the function ``.fileno()`` returns a file descriptor, which is an integer. However, in our test we make it return a string value, ``'reader_descriptor'``, and later test that this value is transmitted to the ``.register()`` call on the polling object. -Of course it is possible to write something like +Of course it is possible to write something like .. code:: python @@ -89,8 +89,8 @@ Let's get to |GREEN| with this and then continue with testing the actual polling :linenos: :emphasize-lines: 3,17-18 -Now our tests pass once again. We have |GREEN|, but we haven't really added the actual -feature we want to develop. We want the monitor to stop monitoring once the underlying +Now our tests pass once again. We have |GREEN|, but we haven't really added the actual +feature we want to develop. We want the monitor to stop monitoring once the underlying subprocess is dead, and not get blocked trying to read a line that will never come. This will involve using the poll object to poll the read descriptor to see @@ -143,7 +143,7 @@ Here is our ``test_receive_output_lines_via_callback``, adapted to the new situa E actual : reader.readline() -Very good. Now let's fix our code to pass the tests. Note that we did not yet add a test for +Very good. Now let's fix our code to pass the tests. Note that we did not yet add a test for the case where the file descriptor does not have any data to read - that come later. Always proceed in small, baby steps - and you'll be fine. Try to do it all at once, and you'll crash and burn. @@ -155,7 +155,7 @@ Getting to |GREEN| is super easy, we add just this one line of code: :emphasize-lines: 3 -Well, this is |GREEN|, but adds little value. It's time for a serious test +Well, this is |GREEN|, but adds little value. It's time for a serious test that makes sure that ``.readline()`` is called *if and only if* ``POLLIN`` is present. Let's get to |RED|. @@ -201,7 +201,7 @@ Solving the Blocking Problem ---------------------------- We are now in a position not to block forever when data does not arrive. -To do that, we need to add a timeout on the ``.poll`` call - since as it +To do that, we need to add a timeout on the ``.poll`` call - since as it is now, it may still block forever waiting for some event on the file. Getting to |RED| is simple in principle, e.g. if we want a 10 seconds timeout, @@ -218,11 +218,11 @@ just change demands of our various scenarios, e.g. If we do this, however - and later on discover that a 60 second timeout is more reasonable, we will have to Test Drive the change from 10 to 60. This seems more annoying that it is helpful. -Sometimes, tests can be *too* specific. +Sometimes, tests can be *too* specific. |testix| has a way to specifically ignore the values of specific arguments - you specify the special value ``IgnoreArgument()`` instead of the overly -specific ``10``. +specific ``10``. Here's how to use it in this case: @@ -289,7 +289,7 @@ and if ``.poll()`` returns a non-None value, stop the monitor. If you think about it, we can poll the subprocess only when there's no data available. It may be that there is data to read and process has died, -but in that case, we'll just discover it is dead when the data runs out. This +but in that case, we'll just discover it is dead when the data runs out. This way we make sure we read all the data out of the pipe when the process has died, even if it takes more than one read. @@ -301,7 +301,7 @@ in continuing to monitor the pipe for more data, and we should close the reader, :lines: 38-40,44-68 :emphasize-lines: 17,19,21 -Note that we demand process polling only after no data was ready to read, +Note that we demand process polling only after no data was ready to read, here it only comes after some ``skip_line*scenario`` function. This brings us into |RED| territory. Current we have not taken the case that the process dies into account, @@ -313,7 +313,7 @@ but as usual, we're taking things slowly. Let's get to |GREEN|. :emphasize-lines: 8,13 Note that this is the first time we bothered to save the subprocess ``Popen`` object! -This is another example of how TDD helps us. If the test passes without us making some move - then +This is another example of how TDD helps us. If the test passes without us making some move - then we simply don't make it. This helps us write minimalistic code. Remember, code that doesn't exist - has no bugs. diff --git a/examples/async_read.py b/examples/async_read.py index 634ed92..aa660da 100644 --- a/examples/async_read.py +++ b/examples/async_read.py @@ -1,5 +1,6 @@ import aiofiles + async def go(filename): async with aiofiles.open(filename) as f: return await f.read() diff --git a/examples/calculator.py b/examples/calculator.py index 01dbb03..9dde729 100644 --- a/examples/calculator.py +++ b/examples/calculator.py @@ -1,14 +1,15 @@ from . import multiplier -class Calculator( object ): - def __init__( self, initialValue ): + +class Calculator(object): + def __init__(self, initialValue): self._value = initialValue - def add( self, addend ): + def add(self, addend): self._value += addend - def result( self ): + def result(self): return self._value - def multiply( self, factor ): - self._value = multiplier.multiply( first = self._value, second = factor ) + def multiply(self, factor): + self._value = multiplier.multiply(first=self._value, second=factor) diff --git a/examples/daylight.py b/examples/daylight.py index fe21474..b6d50f2 100644 --- a/examples/daylight.py +++ b/examples/daylight.py @@ -1,13 +1,15 @@ import datetime -def _beforeDawn( hour ): + +def _beforeDawn(hour): EARLY = 5 return hour < EARLY + def nextDaylightDate(): today = datetime.date.today() hour = datetime.datetime.today().hour - if _beforeDawn( hour ): + if _beforeDawn(hour): return today else: - return today + datetime.timedelta( 1 ) + return today + datetime.timedelta(1) diff --git a/examples/tests/test_calculator.py b/examples/tests/test_calculator.py index b203f6c..1af6928 100644 --- a/examples/tests/test_calculator.py +++ b/examples/tests/test_calculator.py @@ -1,29 +1,30 @@ import pytest -from testix.frequentlyused import * -from testix import patch_module -from examples import calculator +from testix.frequentlyused import * # noqa: F403 +from testix import patch_module # noqa: F401 +from examples import calculator # foxylint-imports:ignore + class Test_Calculator: - def construct( self, value ): - self.tested = calculator.Calculator( value ) + def construct(self, value): + self.tested = calculator.Calculator(value) @pytest.fixture - def module_patch(self, patch_module): + def module_patch(self, patch_module): # noqa: F811 patch_module(calculator, 'multiplier') - def test_Addition( self ): - self.construct( 5 ) - self.tested.add( 7 ) + def test_Addition(self): + self.construct(5) + self.tested.add(7) assert self.tested.result() == 12 - self.tested.add( 1.5 ) + self.tested.add(1.5) assert self.tested.result() == 13.5 - def test_MultiplicationUsesMultiplier( self, module_patch ): - self.construct( 5 ) + def test_MultiplicationUsesMultiplier(self, module_patch): + self.construct(5) with Scenario() as s: - s.multiplier.multiply( first = 5, second = 7 ) >> 35 - s.multiplier.multiply( first = 35, second = 10 ) >> 350 - self.tested.multiply( 7 ) + s.multiplier.multiply(first=5, second=7) >> 35 + s.multiplier.multiply(first=35, second=10) >> 350 + self.tested.multiply(7) assert self.tested.result() == 35 - self.tested.multiply( 10 ) + self.tested.multiply(10) assert self.tested.result() == 350 diff --git a/examples/tests/test_daylight.py b/examples/tests/test_daylight.py index 1015d66..b2d7d4b 100644 --- a/examples/tests/test_daylight.py +++ b/examples/tests/test_daylight.py @@ -1,28 +1,31 @@ import pytest -from testix.frequentlyused import * -from testix import patch_module +from testix.frequentlyused import * # noqa: F403 +from testix import patch_module # noqa: F401 from examples import daylight -class FakeDay( object ): - def __add__( self, other ): - return other + + +class FakeDay(object): + def __add__(self, other): + return other + class Test_Daylight: @pytest.fixture - def module_patch( self, patch_module ): - patch_module( daylight, 'datetime' ) + def module_patch(self, patch_module): # noqa: F811 + patch_module(daylight, 'datetime') - def test_Main( self, module_patch ): + def test_Main(self, module_patch): with Scenario() as s: fakeDay = FakeDay() fakeDay.hour = 12 - s.datetime.date.today().returns( fakeDay ) - s.datetime.datetime.today().returns( fakeDay ) - s.datetime.timedelta( IgnoreArgument() ).returns( FakeDay() ) + s.datetime.date.today().returns(fakeDay) + s.datetime.datetime.today().returns(fakeDay) + s.datetime.timedelta(IgnoreArgument()).returns(FakeDay()) nextDay = daylight.nextDaylightDate() assert nextDay is not fakeDay - def test_EarlyInTheMorningUsesSameDate( self, module_patch ): + def test_EarlyInTheMorningUsesSameDate(self, module_patch): with Scenario() as s: fakeDay = FakeDay() fakeDay.hour = 2 diff --git a/examples/tests/test_daylight_with_starndard_mock.py b/examples/tests/test_daylight_with_starndard_mock.py index c068e95..4b2c2a1 100644 --- a/examples/tests/test_daylight_with_starndard_mock.py +++ b/examples/tests/test_daylight_with_starndard_mock.py @@ -1,18 +1,20 @@ -import pytest -from unittest.mock import patch from unittest.mock import Mock +import unittest.mock from examples import daylight -class FakeDay( object ): - def __add__( self, other ): - return other + + +class FakeDay(object): + def __add__(self, other): + return other + class Test_Daylight: - def module_patch( self, patch_module ): - patch_module( daylight, 'datetime' ) + def module_patch(self, patch_module): + patch_module(daylight, 'datetime') - @patch('examples.daylight.datetime') - def test_Main( self, datetime ): + @unittest.mock.patch('examples.daylight.datetime') + def test_Main(self, datetime): fakeDay = FakeDay() fakeDay.hour = 12 datetime.date.today = Mock(side_effect=[fakeDay]) @@ -24,8 +26,8 @@ def test_Main( self, datetime ): datetime.datetime.today.assert_called_once_with() datetime.timedelta.assert_called_once() - @patch('examples.daylight.datetime') - def test_EarlyInTheMorningUsesSameDate( self, datetime ): + @unittest.mock.patch('examples.daylight.datetime') + def test_EarlyInTheMorningUsesSameDate(self, datetime): fakeDay = FakeDay() fakeDay.hour = 2 datetime.date.today = Mock(side_effect=[fakeDay]) diff --git a/examples/tests/test_using_aiofiles.py b/examples/tests/test_using_aiofiles.py index 0311ed3..0c4fcef 100644 --- a/examples/tests/test_using_aiofiles.py +++ b/examples/tests/test_using_aiofiles.py @@ -1,15 +1,17 @@ -from testix import * +from testix import * # noqa: F403 import pytest from examples import async_read + @pytest.fixture(autouse=True) def override_import(patch_module): patch_module(async_read, 'aiofiles') + @pytest.mark.asyncio async def test_read_write_from_async_file(): - with scenario.Scenario() as s: + with Scenario() as s: s.__async_with__.aiofiles.open('file_name.txt') >> Fake('the_file') s.__await_on__.the_file.read() >> 'the text' diff --git a/makestage.py b/makestage.py index 95f4fe6..4b1c48c 100644 --- a/makestage.py +++ b/makestage.py @@ -4,20 +4,25 @@ import pathlib import os + def test_folder(stage): return pathlib.Path(f'docs/line_monitor/tests/unit/{stage}') + def source_folder(stage): return pathlib.Path(f'docs/line_monitor/source/{stage}') + def copy(source, destination): logging.info(f'cp {source} {destination}') shutil.copy(source, destination) + def link(source, destination): logging.info(f'ln -s {source} {destination}') os.symlink(source, destination) + parser = argparse.ArgumentParser() parser.add_argument('stage', type=int) parser.add_argument('source_mode', choices=['link', 'copy']) diff --git a/pyproject.toml b/pyproject.toml index 4ae7df5..1471a43 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,7 +1,7 @@ [tool.poetry] name = "testix" readme = "README.md" -version = "12.0.0" +version = "12.0.1" description = "Mocking framework Python with *exact* Scenarios" authors = ["Yoav Kleinberger "] license = "MIT" diff --git a/stage_unit.sh b/stage_unit.sh index 1fd5729..0256066 100755 --- a/stage_unit.sh +++ b/stage_unit.sh @@ -1,5 +1,5 @@ #!/bin/bash set -x STAGE=$1 -export PYTHONPATH=docs/line_monitor/source/$STAGE +export PYTHONPATH=docs/line_monitor/source/$STAGE python -m pytest --cov=docs/line_monitor/source --cov-report=term-missing -sv docs/line_monitor/tests/unit/$STAGE/ diff --git a/test/conftest.py b/test/conftest.py index f6477ae..26b9691 100644 --- a/test/conftest.py +++ b/test/conftest.py @@ -1,3 +1,3 @@ from testix import failhooks -failhooks.setMode( 'raise' ) +failhooks.setMode('raise') diff --git a/test/helper_module.py b/test/helper_module.py index 00d4061..b713454 100644 --- a/test/helper_module.py +++ b/test/helper_module.py @@ -1 +1 @@ -import socket +import socket # noqa: F401 diff --git a/test/some_module.py b/test/some_module.py index 8c39cfc..69597d1 100644 --- a/test/some_module.py +++ b/test/some_module.py @@ -1,2 +1,2 @@ -import socket -import helper_module +import socket # noqa: F401 +import helper_module # noqa: F401 diff --git a/test/test_argumentexpectations.py b/test/test_argumentexpectations.py index eaa48e4..b6f975d 100644 --- a/test/test_argumentexpectations.py +++ b/test/test_argumentexpectations.py @@ -6,57 +6,58 @@ from testix import testixexception from testix import argumentexpectations + class TestArgumentExpectations: - @hypothesis.given(A=strategies.integers(),B=strategies.integers()) + @hypothesis.given(A=strategies.integers(), B=strategies.integers()) def test_argument_equals_raises_when_called_with_wrong_arguments(self, A, B): - hypothesis.assume( A != B ) + hypothesis.assume(A != B) fakeObject = fake.Fake('some_object') with scenario.Scenario() as s: - s.some_object( A ) >> 'first' - s.some_object( B ) >> 'second' - assert fakeObject( A ) == 'first' - with pytest.raises( testixexception.ExpectationException ): - fakeObject( A ) + s.some_object(A) >> 'first' + s.some_object(B) >> 'second' + assert fakeObject(A) == 'first' + with pytest.raises(testixexception.ExpectationException): + fakeObject(A) - def test_argument_is_fake_object_with_path( self ): + def test_argument_is_fake_object_with_path(self): fakeObject = fake.Fake('some_object') with scenario.Scenario() as s: - s.some_object( argumentexpectations.ArgumentIsFakeObjectWithPath( 'another_fake_object' ) ) >> 'the result' - s.some_object( argumentexpectations.ArgumentIsFakeObjectWithPath( 'yet_another' ) ) >> 'another result' + s.some_object(argumentexpectations.ArgumentIsFakeObjectWithPath('another_fake_object')) >> 'the result' + s.some_object(argumentexpectations.ArgumentIsFakeObjectWithPath('yet_another')) >> 'another result' assert fakeObject(fake.Fake('another_fake_object')) == 'the result' assert fakeObject(fake.Fake('yet_another')) == 'another result' - def test_FakeObjectExpectation( self ): + def test_FakeObjectExpectation(self): fakeObject = fake.Fake('some_object') fakeArgument = fake.Fake('fake_argument') with scenario.Scenario() as s: s.some_object(fake.Fake('fake_argument')) - fakeObject( fakeArgument ) + fakeObject(fakeArgument) - def test_IgnoreArgument( self ): + def test_IgnoreArgument(self): fakeObject = fake.Fake('some_object') with scenario.Scenario() as s: - s.some_object( 10 ) >> 'first' - s.some_object( argumentexpectations.IgnoreArgument() ) >> 'second' - assert fakeObject( 10 ) == 'first' - assert fakeObject( "this doens't matter" ) == 'second' + s.some_object(10) >> 'first' + s.some_object(argumentexpectations.IgnoreArgument()) >> 'second' + assert fakeObject(10) == 'first' + assert fakeObject("this doens't matter") == 'second' def test_IgnoreCallDetails(self): fakeObject = fake.Fake('some_object') with scenario.Scenario() as s: - s.some_object( 10 ) >> 'first' - s.some_object( argumentexpectations.IgnoreCallDetails() ) >> 'second' + s.some_object(10) >> 'first' + s.some_object(argumentexpectations.IgnoreCallDetails()) >> 'second' s.another_object(argumentexpectations.IgnoreCallDetails()) - assert fakeObject( 10 ) == 'first' - assert fakeObject( "this doens't matter", "this doens'nt either", this='does not matter also', that='neither' ) == 'second' - with pytest.raises( testixexception.ExpectationException ): + assert fakeObject(10) == 'first' + assert fakeObject("this doens't matter", "this doens'nt either", this='does not matter also', that='neither') == 'second' + with pytest.raises(testixexception.ExpectationException): fakeObject("this is an unexpected call: verify that IgnoreCallDetails() still leaves the Fake object's path verification intact") - def test_KeywordArguments( self ): + def test_KeywordArguments(self): fakeObject = fake.Fake('some_object') with scenario.Scenario() as s: - s.some_object( 10, name = 'Lancelot' ).returns( 'first' ) - s.some_object( 11, name = 'Galahad' ).returns( 'second' ) - assert fakeObject( 10, name = 'Lancelot' ) == 'first' - with pytest.raises( testixexception.ExpectationException ): - fakeObject( 11, name = 'not Galahad' ) + s.some_object(10, name='Lancelot').returns('first') + s.some_object(11, name='Galahad').returns('second') + assert fakeObject(10, name='Lancelot') == 'first' + with pytest.raises(testixexception.ExpectationException): + fakeObject(11, name='not Galahad') diff --git a/test/test_fakeobject.py b/test/test_fakeobject.py index 072cacf..00066d6 100644 --- a/test/test_fakeobject.py +++ b/test/test_fakeobject.py @@ -4,20 +4,21 @@ from testix import fake from testix import testixexception + class TestFakeObject: @hypothesis.given(text=strategies.text()) def test_CallingFakeObject_WhileNoScenario_MustThrow(self, text): fakeObject = fake.Fake('hi_there') - with pytest.raises( testixexception.TestixError ): + with pytest.raises(testixexception.TestixError): fakeObject(text) - def test_FakeObjectImplicitCreation_OnlyOnce( self ): + def test_FakeObjectImplicitCreation_OnlyOnce(self): fakeObject = fake.Fake('hi_there') b1 = fakeObject.b b2 = fakeObject.b assert b1 is b2 - def test_FakeObjectCreation_OnlyOnce( self ): + def test_FakeObjectCreation_OnlyOnce(self): fakeObject1 = fake.Fake('hi_there') fakeObject2 = fake.Fake('hi_there') assert fakeObject1 is fakeObject2 diff --git a/test/test_patcher.py b/test/test_patcher.py index a620e05..29937c1 100644 --- a/test/test_patcher.py +++ b/test/test_patcher.py @@ -3,9 +3,11 @@ from testix.patch_module import Patcher import some_module + class AnObject: pass + def test_override_object_attribute_with_fake(): patcher = Patcher() thing = AnObject() @@ -13,16 +15,18 @@ def test_override_object_attribute_with_fake(): patcher(thing, 'name') assert thing.name is Fake('name') patcher.undo() - assert thing.name is 'original' + assert thing.name == 'original' + def test_override_object_attribute_with_arbitrary_object(): patcher = Patcher() thing = AnObject() thing.name = 'original' patcher(thing, 'name', 'arbitrary object') - assert thing.name is 'arbitrary object' + assert thing.name == 'arbitrary object' patcher.undo() - assert thing.name is 'original' + assert thing.name == 'original' + def test_override_non_existing_attribute(): patcher = Patcher() @@ -33,6 +37,7 @@ def test_override_non_existing_attribute(): patcher.undo() assert not hasattr(thing, 'made_up_attribute') + def test_scenario_should_not_reset_fake_modules(): patcher = Patcher() thing = AnObject() @@ -40,19 +45,20 @@ def test_scenario_should_not_reset_fake_modules(): patcher(thing, 'name') assert thing.name is Fake('name') thing.name.length = 111 - with Scenario() as s: + with Scenario(): assert thing.name.length == 111 patcher.undo() assert type(Fake('name').length) is Fake - assert thing.name is 'original' - + assert thing.name == 'original' Fake('name').length = 222 - with Scenario() as s: + with Scenario(): assert type(Fake('name').length) is Fake + def test_bugfix_mocking_same_module_twice_raises_exception_issue_106(): import socket + patcher = Patcher() patcher(some_module, 'socket') patcher(some_module.helper_module, 'socket') @@ -62,15 +68,17 @@ def test_bugfix_mocking_same_module_twice_raises_exception_issue_106(): assert some_module.helper_module.socket is socket assert some_module.socket is socket + def test_bugfix_mocking_same_module_twice_raises_exception_issue_106__complicate_with_attributes(): import socket + patcher = Patcher() patcher(some_module, 'socket') some_module.socket.hi = 'there' patcher(some_module.helper_module, 'socket') assert some_module.socket is Fake('socket') assert some_module.helper_module.socket is Fake('socket') - with Scenario() as s: + with Scenario(): assert some_module.helper_module.socket.hi == 'there' patcher.undo() assert some_module.helper_module.socket is socket diff --git a/test/test_scenario.py b/test/test_scenario.py index 84e1863..433e913 100644 --- a/test/test_scenario.py +++ b/test/test_scenario.py @@ -8,219 +8,225 @@ from testix import fake from testix import DSL + class TestScenario: - def test_EmptyScenario( self ): - with scenario.Scenario() as s: + def test_EmptyScenario(self): + with scenario.Scenario(): pass - def test_OnlyOneScenarioMayExistAtAnyOneTime( self ): - with scenario.Scenario() as s: - with pytest.raises( testixexception.TestixError ): + def test_OnlyOneScenarioMayExistAtAnyOneTime(self): + with scenario.Scenario(): + with pytest.raises(testixexception.TestixError): scenario.Scenario() - def test_TwoScenariosOneAfterTheOther( self ): - with scenario.Scenario() as s: + def test_TwoScenariosOneAfterTheOther(self): + with scenario.Scenario(): pass - with scenario.Scenario() as s: + with scenario.Scenario(): pass - @hypothesis.given(A=strategies.integers(),B=strategies.integers()) + @hypothesis.given(A=strategies.integers(), B=strategies.integers()) def test_CallExpectationReturnsFakeValue(self, A, B): with scenario.Scenario() as s: s.some_object(A).returns(B) fakeObject = fake.Fake('some_object') - assert fakeObject( A ) == B + assert fakeObject(A) == B - @hypothesis.given(A=strategies.integers(),B=strategies.integers(),C=strategies.integers(),D=strategies.integers(),E=strategies.integers()) + @hypothesis.given(A=strategies.integers(), B=strategies.integers(), C=strategies.integers(), D=strategies.integers(), E=strategies.integers()) def test_TwoFakeCallsGetCorrectValues(self, A, B, C, D, E): with scenario.Scenario() as s: - s.some_object( A ) >> B - s.another_object( C, D ) >> E + s.some_object(A) >> B + s.another_object(C, D) >> E some_object = fake.Fake('some_object') another_object = fake.Fake('another_object') - assert some_object( A ) == B - assert another_object( C, D ) == E + assert some_object(A) == B + assert another_object(C, D) == E + + def test_TwoFakeCalls_MustBeInOrder(self): + class PreventScenarioEndVerifications(Exception): + pass - def test_TwoFakeCalls_MustBeInOrder( self ): - class PreventScenarioEndVerifications(Exception): pass with pytest.raises(PreventScenarioEndVerifications): with scenario.Scenario() as s: - s.some_object( 10 ).returns( 15 ) - s.another_object( 20, 50 ).returns( 30 ) - some_object = fake.Fake('some_object') + s.some_object(10).returns(15) + s.another_object(20, 50).returns(30) + some_object = fake.Fake('some_object') # noqa: F841 another_object = fake.Fake('another_object') - with pytest.raises( testixexception.ExpectationException ): - another_object( 20, 50 ) + with pytest.raises(testixexception.ExpectationException): + another_object(20, 50) raise PreventScenarioEndVerifications() - def test_Four_FakeCalls_MustBeInOrder( self ): + def test_Four_FakeCalls_MustBeInOrder(self): with scenario.Scenario() as s: - s.some_object( 10 ) >> 15 - s.another_object( 20, 50 ).returns( 30 ) - s.some_object( 'x' ).returns( 'y' ) - s.another_object( 'X', 'Y' ) >> 'Z' + s.some_object(10) >> 15 + s.another_object(20, 50).returns(30) + s.some_object('x').returns('y') + s.another_object('X', 'Y') >> 'Z' some_object = fake.Fake('some_object') another_object = fake.Fake('another_object') - assert some_object( 10 ) == 15 - assert another_object( 20, 50 ) == 30 - assert some_object( 'x' ) == 'y' - assert another_object( 'X', 'Y' ) == 'Z' + assert some_object(10) == 15 + assert another_object(20, 50) == 30 + assert some_object('x') == 'y' + assert another_object('X', 'Y') == 'Z' - def test_ScenarioEndsPrematurely( self ): - with pytest.raises( testixexception.ScenarioException ): + def test_ScenarioEndsPrematurely(self): + with pytest.raises(testixexception.ScenarioException): with scenario.Scenario() as s: - s.some_object( 10 ).returns( 15 ) - s.another_object( 20, 50 ).returns( 30 ) + s.some_object(10).returns(15) + s.another_object(20, 50).returns(30) some_object = fake.Fake('some_object') - another_object = fake.Fake('another_object') - assert some_object( 10 ) == 15 + another_object = fake.Fake('another_object') # noqa: F841 + assert some_object(10) == 15 - def test_bugfix_ScenarioEndsPrematurely_With_UnorderedCalls( self ): - with pytest.raises( testixexception.ScenarioException ): + def test_bugfix_ScenarioEndsPrematurely_With_UnorderedCalls(self): + with pytest.raises(testixexception.ScenarioException): with scenario.Scenario() as s: - s.some_object( 10 ) - s.another_object( 20, 50 ).unordered() + s.some_object(10) + s.another_object(20, 50).unordered() some_object = fake.Fake('some_object') - another_object = fake.Fake('another_object') - some_object( 10 ) + another_object = fake.Fake('another_object') # noqa: F841 + some_object(10) - def test_CallParametersDontMatch( self ): + def test_CallParametersDontMatch(self): with scenario.Scenario() as s: - s.some_object( 10 ).returns( 15 ) + s.some_object(10).returns(15) some_object = fake.Fake('some_object') - with pytest.raises( testixexception.ExpectationException ): - some_object( 1024 ) + with pytest.raises(testixexception.ExpectationException): + some_object(1024) - def test_ShiftLeftOperator( self ): + def test_ShiftLeftOperator(self): with scenario.Scenario() as s: s.some_object(10).returns(15) s.some_object(15).returns(30) some_object = fake.Fake('some_object') - assert some_object( 10 ) == 15 - assert some_object( 15 ) == 30 + assert some_object(10) == 15 + assert some_object(15) == 30 - def test_ThrowingCallExpectation( self ): - class MyException( Exception ): pass + def test_ThrowingCallExpectation(self): + class MyException(Exception): + pass with scenario.Scenario() as s: - s.some_object( 10 ).throwing( MyException ) + s.some_object(10).throwing(MyException) some_object = fake.Fake('some_object') - with pytest.raises( MyException ): - some_object( 10 ) + with pytest.raises(MyException): + some_object(10) - def test_ThrowingCallExpectation_AlternateSyntax( self ): - class MyException( Exception ): pass + def test_ThrowingCallExpectation_AlternateSyntax(self): + class MyException(Exception): + pass with scenario.Scenario() as s: - s.some_object( 10 ) >> DSL.Throwing( MyException ) + s.some_object(10) >> DSL.Throwing(MyException) some_object = fake.Fake('some_object') - with pytest.raises( MyException ): - some_object( 10 ) + with pytest.raises(MyException): + some_object(10) - @hypothesis.given( values=strategies.permutations( [ 10, 11, 12 ] ) ) - def test_UnorderedExpectation( self, values ): + @hypothesis.given(values=strategies.permutations([10, 11, 12])) + def test_UnorderedExpectation(self, values): with scenario.Scenario() as s: - s.some_object( 10 ).unordered() - s.some_object( 11 ).unordered() - s.some_object( 12 ).unordered() + s.some_object(10).unordered() + s.some_object(11).unordered() + s.some_object(12).unordered() some_object = fake.Fake('some_object') - some_object( values[ 0 ] ) - some_object( values[ 1 ] ) - some_object( values[ 2 ] ) + some_object(values[0]) + some_object(values[1]) + some_object(values[2]) - def test_UnorderedExpectationsRunOut( self ): + def test_UnorderedExpectationsRunOut(self): with scenario.Scenario() as s: - s.some_object( 10 ).unordered() - s.some_object( 11 ).unordered() + s.some_object(10).unordered() + s.some_object(11).unordered() some_object = fake.Fake('some_object') - some_object( 11 ) - some_object( 10 ) - with pytest.raises( testixexception.ExpectationException ): - some_object( 11 ) + some_object(11) + some_object(10) + with pytest.raises(testixexception.ExpectationException): + some_object(11) - def test_EverlastingCall( self ): + def test_EverlastingCall(self): with scenario.Scenario() as s: - s.some_object( 'everlasting means zero or more times - in this case, zero' ).unordered().everlasting() - s.some_object( 10 ).unordered().everlasting() - s.some_object( 11 ).unordered().everlasting() + s.some_object('everlasting means zero or more times - in this case, zero').unordered().everlasting() + s.some_object(10).unordered().everlasting() + s.some_object(11).unordered().everlasting() some_object = fake.Fake('some_object') - some_object( 10 ) - some_object( 10 ) - some_object( 10 ) - some_object( 10 ) - some_object( 11 ) - some_object( 11 ) - - def test_Everlasting_Unorderd_and_Regular_Calls( self ): + some_object(10) + some_object(10) + some_object(10) + some_object(10) + some_object(11) + some_object(11) + + def test_Everlasting_Unorderd_and_Regular_Calls(self): with scenario.Scenario() as s: - s.everlasting( 10 ).unordered().everlasting() >> 'ten' - s.everlasting( 11 ).unordered().everlasting() >> 'eleven' - s.unordered( 20 ).unordered() >> 'twenty' - s.unordered( 19 ).unordered() >> 'nineteen' - s.ordered( 1 ).returns( 'one' ) - s.ordered( 2 ).returns( 'two' ) - s.ordered( 3 ).returns( 'three' ) + s.everlasting(10).unordered().everlasting() >> 'ten' + s.everlasting(11).unordered().everlasting() >> 'eleven' + s.unordered(20).unordered() >> 'twenty' + s.unordered(19).unordered() >> 'nineteen' + s.ordered(1).returns('one') + s.ordered(2).returns('two') + s.ordered(3).returns('three') ordered = fake.Fake('ordered') everlasting = fake.Fake('everlasting') unordered = fake.Fake('unordered') - assert everlasting( 10 ) == 'ten' - assert ordered( 1 ) == 'one' - assert ordered( 2 ) == 'two' - assert everlasting( 10 ) == 'ten' - assert everlasting( 11 ) == 'eleven' - assert everlasting( 11 ) == 'eleven' - assert everlasting( 10 ) == 'ten' - assert ordered( 3 ) == 'three' - assert everlasting( 11 ) == 'eleven' - assert everlasting( 10 ) == 'ten' - assert unordered( 19 ) == 'nineteen' - assert unordered( 20 ) == 'twenty' - assert everlasting( 10 ) == 'ten' + assert everlasting(10) == 'ten' + assert ordered(1) == 'one' + assert ordered(2) == 'two' + assert everlasting(10) == 'ten' + assert everlasting(11) == 'eleven' + assert everlasting(11) == 'eleven' + assert everlasting(10) == 'ten' + assert ordered(3) == 'three' + assert everlasting(11) == 'eleven' + assert everlasting(10) == 'ten' + assert unordered(19) == 'nineteen' + assert unordered(20) == 'twenty' + assert everlasting(10) == 'ten' with pytest.raises(testixexception.TestixException): - unordered( 20 ) + unordered(20) with pytest.raises(testixexception.TestixException): - ordered( 1 ) + ordered(1) with pytest.raises(testixexception.TestixException): - everlasting( 'wtf' ) + everlasting('wtf') - def test_Everlasting_Calls_Have_ArgumentExpectations( self ): + def test_Everlasting_Calls_Have_ArgumentExpectations(self): with scenario.Scenario() as s: - s.some_object( 10 ).returns( 'ten' ).unordered().everlasting() + s.some_object(10).returns('ten').unordered().everlasting() some_object = fake.Fake('some_object') - assert some_object( 10 ) == 'ten' - with pytest.raises( testixexception.ExpectationException ): - some_object( 11 ) + assert some_object(10) == 'ten' + with pytest.raises(testixexception.ExpectationException): + some_object(11) - def test_Hooks( self ): + def test_Hooks(self): func1Calls = [] - def func1( * a, **k ): - func1Calls.append( ( a, k ) ) + + def func1(*a, **k): + func1Calls.append((a, k)) with scenario.Scenario() as s: - s.some_object( 10 ) - s << hook.Hook( func1, 10, 20, name = 'Moshe' ) - s << hook.Hook( func1, 70, 80, name = 'Avraham' ) - s.some_object( 11 ) - s << hook.Hook( func1, 11, 21, name = 'Haim' ) + s.some_object(10) + s << hook.Hook(func1, 10, 20, name='Moshe') + s << hook.Hook(func1, 70, 80, name='Avraham') + s.some_object(11) + s << hook.Hook(func1, 11, 21, name='Haim') some_object = fake.Fake('some_object') - some_object( 10 ) - assert len( func1Calls ) == 2 - assert func1Calls[ 0 ] == ( ( 10, 20 ), { 'name': 'Moshe' } ) - assert func1Calls[ 1 ] == ( ( 70, 80 ), { 'name': 'Avraham' } ) - some_object( 11 ) - assert len( func1Calls ) == 3 - func1Calls[ 2 ] == ( 11, 21 ), { 'name': 'Haim' } - + some_object(10) + assert len(func1Calls) == 2 + assert func1Calls[0] == ((10, 20), {'name': 'Moshe'}) + assert func1Calls[1] == ((70, 80), {'name': 'Avraham'}) + some_object(11) + assert len(func1Calls) == 3 + func1Calls[2] == (11, 21), {'name': 'Haim'} + def test_fake_context(self): locker_mock = fake.Fake('locker') with scenario.Scenario() as s: @@ -279,20 +285,20 @@ def test_fix_issue_27__path_attribute_is_possible(self): assert fakeObject.path == '/path/to/nowhere' def test_scenario_resets_attributes_on_fakes(self): - with scenario.Scenario() as s: + with scenario.Scenario(): fakeObject = fake.Fake('some_object') fake.Fake('some_object').name = 'haim' fake.Fake('some_object').age = 40 assert fakeObject.age == 40 assert fakeObject.name == 'haim' - with scenario.Scenario() as s: + with scenario.Scenario(): fake.Fake('some_object').height = 180 assert fake.Fake('some_object').height == 180 assert type(fakeObject.age) is fake.Fake assert type(fakeObject.haim) is fake.Fake - with scenario.Scenario() as s: + with scenario.Scenario(): fakeObject = fake.Fake('some_object') assert type(fakeObject.height) is fake.Fake assert type(fakeObject.age) is fake.Fake @@ -318,10 +324,13 @@ async def test_async_expectations(self): assert await fakeObject.some_attribute.another_method('wtf') == 777 with scenario.Scenario('awaitable throws') as s: - class MyException( Exception ): pass - s.__await_on__.some_object( 10 ) >> DSL.Throwing( MyException ) - with pytest.raises( MyException ): - await fakeObject( 10 ) + + class MyException(Exception): + pass + + s.__await_on__.some_object(10) >> DSL.Throwing(MyException) + with pytest.raises(MyException): + await fakeObject(10) @pytest.mark.asyncio async def test_async_context_managers(self): diff --git a/testix/__init__.py b/testix/__init__.py index d4af873..4eeecd7 100644 --- a/testix/__init__.py +++ b/testix/__init__.py @@ -1,3 +1,3 @@ -from .patch_module import patch_module -from .frequentlyused import * -from . import loop_breaker +from .patch_module import patch_module # noqa: F401 +from .frequentlyused import * # noqa: F401,F403 +from . import loop_breaker # noqa: F401 diff --git a/testix/argumentexpectations.py b/testix/argumentexpectations.py index 1a278fb..82985c1 100644 --- a/testix/argumentexpectations.py +++ b/testix/argumentexpectations.py @@ -1,54 +1,60 @@ from testix import fake import pprint + class ArgumentExpectation: - def __init__(self, value): - self.expectedValue = value + def __init__(self, value): + self.expectedValue = value + + def ok(self, value): + raise Exception("must override this") + + def __repr__(self): + WORKAROUND_PFORMAT_BREAKS_LONG_STRINGS = 100000 + return pprint.pformat(self.expectedValue, width=WORKAROUND_PFORMAT_BREAKS_LONG_STRINGS) + + +class ArgumentEquals(ArgumentExpectation): + def ok(self, value): + return self.expectedValue == value + - def ok(self, value): - raise Exception("must override this") +class ArgumentIsFakeObjectWithPath(ArgumentExpectation): + def ok(self, value): + if not isinstance(value, fake.Fake): + return False + expectedPath = self.expectedValue + return value is fake.Fake(expectedPath) - def __repr__( self ): - WORKAROUND_PFORMAT_BREAKS_LONG_STRINGS = 100000 - return pprint.pformat( self.expectedValue, width=WORKAROUND_PFORMAT_BREAKS_LONG_STRINGS ) + def __repr__(self): + return "|%s|" % self.expectedValue -class ArgumentEquals( ArgumentExpectation ): - def ok( self, value ): - return self.expectedValue == value -class ArgumentIsFakeObjectWithPath( ArgumentExpectation ): - def ok( self, value ): - if not isinstance(value, fake.Fake): - return False - expectedPath = self.expectedValue - return value is fake.Fake(expectedPath) +class IgnoreArgument(ArgumentExpectation): + def __init__(self): + ArgumentExpectation.__init__(self, 0) - def __repr__( self ): - return "|%s|" % self.expectedValue + def ok(self, value): + return True -class IgnoreArgument( ArgumentExpectation ): - def __init__( self ): - ArgumentExpectation.__init__( self, 0 ) + def __repr__(self): + return '|IGNORED|' - def ok( self, value ): - return True - def __repr__( self ): - return '|IGNORED|' +class IgnoreCallDetails(ArgumentExpectation): + def __init__(self): + ArgumentExpectation.__init__(self, 0) -class IgnoreCallDetails( ArgumentExpectation ): - def __init__( self ): - ArgumentExpectation.__init__( self, 0 ) + def ok(self, value): + return True - def ok( self, value ): - return True + def __repr__(self): + return '|ALL_DETAILS_IGNORED|' - def __repr__( self ): - return '|ALL_DETAILS_IGNORED|' -class ArgumentIs( ArgumentExpectation ): - def ok( self, value ): - return value is self.expectedValue +class ArgumentIs(ArgumentExpectation): + def ok(self, value): + return value is self.expectedValue - def __repr__( self ): - return '|IS %s|' % self.expectedValue + def __repr__(self): + return '|IS %s|' % self.expectedValue diff --git a/testix/call_formatter.py b/testix/call_formatter.py index 0304660..f05a6fa 100644 --- a/testix/call_formatter.py +++ b/testix/call_formatter.py @@ -1,11 +1,12 @@ import traceback -def format( name, args, kwargs ): - if len( args ) > 0: - argsString = ', '.join( [ repr( arg ) for arg in args ] ) + +def format(name, args, kwargs): + if len(args) > 0: + argsString = ', '.join([repr(arg) for arg in args]) else: argsString = None - if len( kwargs ) > 0: + if len(kwargs) > 0: kwargsString = ', '.join('{key} = {value}'.format(key=key, value=repr(value)) for (key, value) in kwargs.items()) else: kwargsString = None @@ -21,11 +22,12 @@ def format( name, args, kwargs ): return '{name}({argsString}, {kwargsString})'.format(name=name, argsString=argsString, kwargsString=kwargsString) + def caller_context(): stack = traceback.extract_stack() for index, frame_summary in enumerate(stack): if 'return self.__returnResultFromScenario' in frame_summary.line: break - frame_summary = stack[index-1] + frame_summary = stack[index - 1] return '{line} ({filename}:{lineno})'.format(line=frame_summary.line, filename=frame_summary.filename, lineno=frame_summary.lineno) diff --git a/testix/call_modifiers/asynchronous.py b/testix/call_modifiers/asynchronous.py index d962124..178c44f 100644 --- a/testix/call_modifiers/asynchronous.py +++ b/testix/call_modifiers/asynchronous.py @@ -3,6 +3,7 @@ import uuid from . import base + class Asynchronous(base.Base): def __init__(self, call): self.__result = None diff --git a/testix/call_modifiers/awaitable.py b/testix/call_modifiers/awaitable.py index 18454d4..5281018 100644 --- a/testix/call_modifiers/awaitable.py +++ b/testix/call_modifiers/awaitable.py @@ -3,6 +3,7 @@ from testix import fake_privacy_violator from . import base + class Awaitable(base.Base): def __init__(self, call): self.__result = None diff --git a/testix/call_modifiers/base.py b/testix/call_modifiers/base.py index 9bbe941..4cae6dd 100644 --- a/testix/call_modifiers/base.py +++ b/testix/call_modifiers/base.py @@ -1,5 +1,6 @@ import abc + class Base(abc.ABC): @abc.abstractmethod def __init__(self, call): diff --git a/testix/call_modifiers/synchronous.py b/testix/call_modifiers/synchronous.py index 1ad7a36..9e4eae3 100644 --- a/testix/call_modifiers/synchronous.py +++ b/testix/call_modifiers/synchronous.py @@ -3,6 +3,7 @@ import uuid from . import base + class Synchronous(base.Base): def __init__(self, call): self.__result = None diff --git a/testix/call_modifiers/trivial.py b/testix/call_modifiers/trivial.py index 08015de..7524bad 100644 --- a/testix/call_modifiers/trivial.py +++ b/testix/call_modifiers/trivial.py @@ -1,5 +1,6 @@ from . import base + class Trivial(base.Base): def __init__(self, call): self.__result = None diff --git a/testix/expectationmaker.py b/testix/expectationmaker.py index 9509c22..38e1e81 100644 --- a/testix/expectationmaker.py +++ b/testix/expectationmaker.py @@ -1,5 +1,4 @@ from . import call_character -from testix import testixexception import testix.expectations.call from testix import expectations import testix.call_modifiers.synchronous @@ -7,6 +6,7 @@ import testix.call_modifiers.awaitable import testix.call_modifiers.trivial + class ExpectationMaker: def __init__(self, scenario, scenarioMocks, path, character: call_character.CallCharacter): self.__scenario = scenario diff --git a/testix/expectations/call.py b/testix/expectations/call.py index f196817..cf528b1 100644 --- a/testix/expectations/call.py +++ b/testix/expectations/call.py @@ -4,11 +4,12 @@ from testix import call_formatter from testix import DSL + class Call: - def __init__( self, fakeObjectPath, modifier, * arguments, ** kwargExpectations ): + def __init__(self, fakeObjectPath, modifier, *arguments, **kwargExpectations): self.__fakeObjectPath = fakeObjectPath - self.__argumentExpectations = [ self.__expectation( arg ) for arg in arguments ] - self.__kwargExpectations = { name: self.__expectation( kwargExpectations[ name ] ) for name in kwargExpectations } + self.__argumentExpectations = [self.__expectation(arg) for arg in arguments] + self.__kwargExpectations = {name: self.__expectation(kwargExpectations[name]) for name in kwargExpectations} self.__unordered = False self.__everlasting = False self.__modifier = modifier(self) @@ -21,84 +22,84 @@ def returns(self, result): self.__modifier.set_result(result) return self - def __rshift__( self, result ): + def __rshift__(self, result): if type(result) is DSL.Throwing: self.throwing(result.exceptionFactory) else: - self.returns( result ) + self.returns(result) - def throwing( self, exceptionFactory ): + def throwing(self, exceptionFactory): self.__modifier.throwing(exceptionFactory) return self - def unordered( self ): - scenario.current().unordered( self ) + def unordered(self): + scenario.current().unordered(self) self.__unordered = True return self - def everlasting( self ): + def everlasting(self): self.__everlasting = True if not self.__unordered: raise testixexception.TestixError("call cannot be everlasting and not unordered") return self - def __expectation( self, arg ): - if isinstance( arg, argumentexpectations.ArgumentExpectation ): + def __expectation(self, arg): + if isinstance(arg, argumentexpectations.ArgumentExpectation): return arg defaultExpectation = argumentexpectations.ArgumentEquals - return defaultExpectation( arg ) + return defaultExpectation(arg) - def result( self ): + def result(self): return self.__modifier.result() - def __repr__( self ): - return call_formatter.format( self.__fakeObjectPath, self.__argumentExpectations, self.__kwargExpectations ) + def __repr__(self): + return call_formatter.format(self.__fakeObjectPath, self.__argumentExpectations, self.__kwargExpectations) - def fits( self, fakeObjectPath, args, kwargs ): + def fits(self, fakeObjectPath, args, kwargs): if fakeObjectPath != self.__fakeObjectPath: return False if self.__ignoreCallDetails(): return True - if not self.__verifyArguments( args ): + if not self.__verifyArguments(args): return False - if not self.__verifyKeywordArguments( kwargs ): + if not self.__verifyKeywordArguments(kwargs): return False return True def __ignoreCallDetails(self): - argumentExpectations = list( self.__argumentExpectations ) + argumentExpectations = list(self.__argumentExpectations) if len(argumentExpectations) == 0: return False first = argumentExpectations[0] return type(first) is argumentexpectations.IgnoreCallDetails - def __verifyArguments( self, args ): - args = list( args ) - argumentExpectations = list( self.__argumentExpectations ) - if len( argumentExpectations ) != len( args ): + def __verifyArguments(self, args): + args = list(args) + argumentExpectations = list(self.__argumentExpectations) + if len(argumentExpectations) != len(args): return False - while len( argumentExpectations ) > 0: - argumentExpectation = argumentExpectations.pop( 0 ) - actualArgument = args.pop( 0 ) - if not argumentExpectation.ok( actualArgument ): + while len(argumentExpectations) > 0: + argumentExpectation = argumentExpectations.pop(0) + actualArgument = args.pop(0) + if not argumentExpectation.ok(actualArgument): return False return True - def __verifyKeywordArguments( self, kwargs ): + def __verifyKeywordArguments(self, kwargs): for name, argumentExpectation in self.__kwargExpectations.items(): if name not in kwargs: return False - actualArgument = kwargs[ name ] - if not argumentExpectation.ok( actualArgument ): + actualArgument = kwargs[name] + if not argumentExpectation.ok(actualArgument): return False - if self.__unexpectedKeyworkArgument( kwargs ): + if self.__unexpectedKeyworkArgument(kwargs): return False return True - def __unexpectedKeyworkArgument( self, kwargs ): + def __unexpectedKeyworkArgument(self, kwargs): for name in kwargs: if name not in self.__kwargExpectations: return True - def everlasting_( self ): + def everlasting_(self): return self.__everlasting diff --git a/testix/failhooks.py b/testix/failhooks.py index 14ab8bd..8a8ea12 100644 --- a/testix/failhooks.py +++ b/testix/failhooks.py @@ -5,11 +5,13 @@ except ImportError: pytest = None -def _fail_py_test( exceptionFactory, message ): - return pytest.fail( message ) -def _fail_raise( exceptionFactory, message ): - raise exceptionFactory( message ) +def _fail_py_test(exceptionFactory, message): + return pytest.fail(message) + + +def _fail_raise(exceptionFactory, message): + raise exceptionFactory(message) if pytest is None: @@ -17,16 +19,16 @@ def _fail_raise( exceptionFactory, message ): else: _fail = _fail_py_test -def setMode( mode ): - FAILS = { 'pytest': _fail_py_test, - 'raise': _fail_raise } + +def setMode(mode): + FAILS = {'pytest': _fail_py_test, 'raise': _fail_raise} global _fail - _fail = FAILS[ mode ] + _fail = FAILS[mode] + + +def error(message): + raise testixexception.TestixError(message) -def error( message ): - raise testixexception.TestixError( message ) -def fail( exceptionFactory, message ): - return _fail( exceptionFactory, '\ntestix: {}\n'.format(exceptionFactory.__name__) + - 'testix details:\n' + - '{}'.format(message) ) +def fail(exceptionFactory, message): + return _fail(exceptionFactory, '\ntestix: {}\n'.format(exceptionFactory.__name__) + 'testix details:\n' + '{}'.format(message)) diff --git a/testix/fake.py b/testix/fake.py index dd2e3ed..60f9bbe 100644 --- a/testix/fake.py +++ b/testix/fake.py @@ -6,7 +6,7 @@ class Fake: __registry = {} __fake_module = set() - def __new__( cls, path_a62df12dd67848be82c505d63b928725, **attributes ): + def __new__(cls, path_a62df12dd67848be82c505d63b928725, **attributes): if path_a62df12dd67848be82c505d63b928725 in Fake.__registry: return Fake.__registry[path_a62df12dd67848be82c505d63b928725] instance = super(Fake, cls).__new__(cls) @@ -40,7 +40,7 @@ def clear_attributes(instance): scenario.Scenario.init_hook = clear_all_attributes - def __init__( self, path_a62df12dd67848be82c505d63b928725, **attributes ): + def __init__(self, path_a62df12dd67848be82c505d63b928725, **attributes): self.__path = path_a62df12dd67848be82c505d63b928725 self.__set_attributes(attributes) @@ -56,20 +56,20 @@ def __set_attributes(self, attributes): for key, value in attributes.items(): setattr(self, key, value) - def __call__( self, * args, ** kwargs ): - return self.__returnResultFromScenario( * args, ** kwargs ) + def __call__(self, *args, **kwargs): + return self.__returnResultFromScenario(*args, **kwargs) - def __returnResultFromScenario( self, * args, ** kwargs ): + def __returnResultFromScenario(self, *args, **kwargs): if scenario.current() is None: - failhooks.error( "can not find a scenario object" ) - return scenario.current().resultFor( self.__path, * args, ** kwargs ) + failhooks.error("can not find a scenario object") + return scenario.current().resultFor(self.__path, *args, **kwargs) - def __str__( self ): - return 'FakeObject( "%s", %s )' % ( self.__path, id( self ) ) + def __str__(self): + return 'FakeObject( "%s", %s )' % (self.__path, id(self)) - def __repr__( self ): - return str( self ) + def __repr__(self): + return str(self) - def __getattr__( self, name ): - childsName = '%s.%s' % ( self.__path, name ) + def __getattr__(self, name): + childsName = '%s.%s' % (self.__path, name) return Fake(childsName) diff --git a/testix/fakefile.py b/testix/fakefile.py index 9c6b10f..5810e18 100644 --- a/testix/fakefile.py +++ b/testix/fakefile.py @@ -2,8 +2,8 @@ class FakeFile(fake.Fake): - def __enter__( self ): + def __enter__(self): return self - def __exit__( self, * args ): + def __exit__(self, *args): self.close() diff --git a/testix/frequentlyused.py b/testix/frequentlyused.py index cb668b3..fedb4fc 100644 --- a/testix/frequentlyused.py +++ b/testix/frequentlyused.py @@ -1,7 +1,7 @@ -from testix.fake import Fake -from testix.fakeobject import FakeObject -from testix.argumentexpectations import * -from testix.scenario import * -from testix.hook import Hook -from testix import saveargument -from testix.DSL import * +from testix.fake import Fake # noqa: F401 +from testix.fakeobject import FakeObject # noqa: F401 +from testix.argumentexpectations import * # noqa: F401,F403 +from testix.scenario import * # noqa: F401,F403 +from testix.hook import Hook # noqa: F401 +from testix import saveargument # noqa: F401 +from testix.DSL import * # noqa: F401,F403 diff --git a/testix/hook.py b/testix/hook.py index 31da57b..e2124ae 100644 --- a/testix/hook.py +++ b/testix/hook.py @@ -1,8 +1,8 @@ class Hook: - def __init__(self, callable, * args, ** kwargs): + def __init__(self, callable, *args, **kwargs): self.__callable = callable self.__args = args self.__kwargs = kwargs - def execute( self ): + def execute(self): self.__callable(*self.__args, **self.__kwargs) diff --git a/testix/patch_module.py b/testix/patch_module.py index 76ba977..b9fe606 100644 --- a/testix/patch_module.py +++ b/testix/patch_module.py @@ -3,17 +3,18 @@ _SENTINEL = 'testix-sentinel-a72004be-7a66-42f5-bdcf-7d71eb7283e3' + class Patcher: def __init__(self): self.__stack = [] - def __call__(self, module, attribute, mock = None): + def __call__(self, module, attribute, mock=None): original = self.__save_original_value(module, attribute) if mock is None: mock = fake.Fake(attribute) fake.Fake.exempt_from_attribute_sweep(mock.path_a62df12dd67848be82c505d63b928725) - setattr( module, attribute, mock ) - self.__stack.append( ( module, attribute, original, mock ) ) + setattr(module, attribute, mock) + self.__stack.append((module, attribute, original, mock)) return mock def __save_original_value(self, module, attribute): @@ -32,6 +33,7 @@ def undo(self): else: setattr(module, attribute, original) + @pytest.fixture def patch_module(): patcher = Patcher() diff --git a/testix/saveargument.py b/testix/saveargument.py index 75bb730..8d0702e 100644 --- a/testix/saveargument.py +++ b/testix/saveargument.py @@ -2,8 +2,9 @@ _saved = {} + class SaveArgument(argumentexpectations.ArgumentExpectation): - def __init__( self, saveTo ): + def __init__(self, saveTo): self.__saveTo = saveTo argumentexpectations.ArgumentExpectation.__init__(self, None) @@ -11,9 +12,10 @@ def ok(self, value): _saved[self.__saveTo] = value return True - def __repr__( self ): + def __repr__(self): return '|SAVE|' + def saved(): global _saved return _saved diff --git a/testix/scenario.py b/testix/scenario.py index 2485386..fa438b1 100644 --- a/testix/scenario.py +++ b/testix/scenario.py @@ -3,18 +3,17 @@ from testix import scenario_mocks from testix import failhooks from testix import call_formatter -import traceback -import pprint import sys -class Scenario( object ): + +class Scenario(object): _current = None - init_hook = lambda: None + init_hook = lambda: None # noqa: E731 - def __init__( self, title='', *, verbose = False ): + def __init__(self, title='', *, verbose=False): Scenario.init_hook() if Scenario._current is not None: - failhooks.error( "New scenario started before previous one ended" ) + failhooks.error("New scenario started before previous one ended") self.__title = title self.__verbose = verbose self.__expected = [] @@ -22,60 +21,60 @@ def __init__( self, title='', *, verbose = False ): self.__endingVerifications = True Scenario._current = self - def __enter__( self ): + def __enter__(self): return scenario_mocks.ScenarioMocks(self) - def __exit__( self, exceptionType, exception, traceback ): + def __exit__(self, exceptionType, exception, traceback): if exceptionType is not None: self.__endingVerifications = False self.__end() - def __debug( self, message ): + def __debug(self, message): if not self.__verbose: - return - sys.stderr.write( 'SCENARIO: {}\n'.format(message) ) + return + sys.stderr.write('SCENARIO: {}\n'.format(message)) - def addEvent( self, event ): - if isinstance( event, hook.Hook ): - self.__expected.append( event ) - return + def addEvent(self, event): + if isinstance(event, hook.Hook): + self.__expected.append(event) + return call = event - self.__expected.append( call ) + self.__expected.append(call) - def resultFor( self, fakeObjectPath, * args, ** kwargs ): - self.__debug( 'resultFor: {fakeObjectPath}, {args}, {kwargs}'.format(fakeObjectPath=fakeObjectPath, args=args, kwargs=kwargs) ) - unorderedCall = self.__findUnorderedCall( fakeObjectPath, args, kwargs ) + def resultFor(self, fakeObjectPath, *args, **kwargs): + self.__debug('resultFor: {fakeObjectPath}, {args}, {kwargs}'.format(fakeObjectPath=fakeObjectPath, args=args, kwargs=kwargs)) + unorderedCall = self.__findUnorderedCall(fakeObjectPath, args, kwargs) if unorderedCall is not None: - self.__debug( 'found unordered call: {}'.format(unorderedCall) ) + self.__debug('found unordered call: {}'.format(unorderedCall)) return unorderedCall.result() - return self.__resultForOrderedCall( fakeObjectPath, args, kwargs ) + return self.__resultForOrderedCall(fakeObjectPath, args, kwargs) - def __resultForOrderedCall( self, fakeObjectPath, args, kwargs ): - self.__debug( '_resultForOrderedCall: {fakeObjectPath}, {args}, {kwargs}'.format(fakeObjectPath=fakeObjectPath, args=args, kwargs=kwargs) ) - if len( self.__expected ) == 0: + def __resultForOrderedCall(self, fakeObjectPath, args, kwargs): + self.__debug('_resultForOrderedCall: {fakeObjectPath}, {args}, {kwargs}'.format(fakeObjectPath=fakeObjectPath, args=args, kwargs=kwargs)) + if len(self.__expected) == 0: message = self.__effective_title() - message += "unexpected call: {}\n".format(call_formatter.format( fakeObjectPath, args, kwargs )) - message += "Expected nothing" - failhooks.fail( testixexception.ExpectationException, message ) - expected = self.__expected.pop( 0 ) - self.__verifyCallExpected( expected, fakeObjectPath, args, kwargs ) + message += "unexpected call: {}\n".format(call_formatter.format(fakeObjectPath, args, kwargs)) + message += "Expected nothing" + failhooks.fail(testixexception.ExpectationException, message) + expected = self.__expected.pop(0) + self.__verifyCallExpected(expected, fakeObjectPath, args, kwargs) result = expected.result() self.__executeHooks() self.__debug('scenario: {expected} >> {result}'.format(expected=expected, result=result)) return result - def __executeHooks( self ): - while len( self.__expected ) > 0 and isinstance( self.__expected[ 0 ], hook.Hook ): - currentHook = self.__expected.pop( 0 ) - currentHook.execute() + def __executeHooks(self): + while len(self.__expected) > 0 and isinstance(self.__expected[0], hook.Hook): + currentHook = self.__expected.pop(0) + currentHook.execute() - def __findUnorderedCall( self, fakeObjectPath, args, kwargs ): + def __findUnorderedCall(self, fakeObjectPath, args, kwargs): for call in self.__unorderedExpectations: - if call.fits( fakeObjectPath, args, kwargs ): - if not call.everlasting_(): - self.__unorderedExpectations.remove( call ) - return call + if call.fits(fakeObjectPath, args, kwargs): + if not call.everlasting_(): + self.__unorderedExpectations.remove(call) + return call return None def __effective_title(self): @@ -84,12 +83,16 @@ def __effective_title(self): else: return '=== Scenario (no title) ===\n' - def __verifyCallExpected( self, expected, fakeObjectPath, args, kwargs ): - self.__debug( '_verifyCallExpected: {expected}. actual={fakeObjectPath} args={args}, kwargs={kwargs}'.format(expected=expected, fakeObjectPath=fakeObjectPath, args=args, kwargs=kwargs) ) - if not expected.fits( fakeObjectPath, args, kwargs ): + def __verifyCallExpected(self, expected, fakeObjectPath, args, kwargs): + self.__debug( + '_verifyCallExpected: {expected}. actual={fakeObjectPath} args={args}, kwargs={kwargs}'.format( + expected=expected, fakeObjectPath=fakeObjectPath, args=args, kwargs=kwargs + ) + ) + if not expected.fits(fakeObjectPath, args, kwargs): message = self.__effective_title() message += " expected: {}\n".format(expected) - message += " actual : {}\n".format(call_formatter.format( fakeObjectPath, args, kwargs )) + message += " actual : {}\n".format(call_formatter.format(fakeObjectPath, args, kwargs)) message += '=== OFFENDING LINE ===\n' message += " " + call_formatter.caller_context() + '\n' message += '=== FURTHER EXPECTATIONS (showing at most 10 out of {}) ===\n'.format(len(self.__expected)) @@ -97,31 +100,38 @@ def __verifyCallExpected( self, expected, fakeObjectPath, args, kwargs ): message += ' {}\n'.format(expectation) message += '=== END ===\n' - - failhooks.fail( testixexception.ExpectationException, message ) + failhooks.fail(testixexception.ExpectationException, message) @staticmethod def current(): return Scenario._current - def __performEndVerifications( self ): - if len( self.__expected ) > 0: - failhooks.fail( testixexception.ScenarioException, "Scenario ended, but not all expectations were met. Pending expectations (ordered): {}".format(self.__expected) ) - mortalUnordered = [ expectation for expectation in self.__unorderedExpectations if not expectation.everlasting_() ] - if len( mortalUnordered ) > 0: - failhooks.fail( testixexception.ScenarioException, "Scenario ended, but not all expectations were met. Pending expectations (unordered): {}".format(mortalUnordered) ) - - def __end( self ): + def __performEndVerifications(self): + if len(self.__expected) > 0: + failhooks.fail( + testixexception.ScenarioException, + "Scenario ended, but not all expectations were met. Pending expectations (ordered): {}".format(self.__expected), + ) + mortalUnordered = [expectation for expectation in self.__unorderedExpectations if not expectation.everlasting_()] + if len(mortalUnordered) > 0: + failhooks.fail( + testixexception.ScenarioException, + "Scenario ended, but not all expectations were met. Pending expectations (unordered): {}".format(mortalUnordered), + ) + + def __end(self): Scenario._current = None if self.__endingVerifications: self.__performEndVerifications() - def unordered( self, call ): - self.__expected.remove( call ) - self.__unorderedExpectations.append( call ) + def unordered(self, call): + self.__expected.remove(call) + self.__unorderedExpectations.append(call) + def clearAllScenarios(): Scenario._current = None + def current(): return Scenario.current() diff --git a/testix/scenario_mocks.py b/testix/scenario_mocks.py index 741bb93..17bc64d 100644 --- a/testix/scenario_mocks.py +++ b/testix/scenario_mocks.py @@ -3,8 +3,9 @@ from . import call_character import copy + class ScenarioMocks: - def __init__( self, scenario ): + def __init__(self, scenario): self.__awaitable = False self.__scenario = scenario self.__character = call_character.CallCharacter() @@ -16,7 +17,7 @@ def __from_fake__(self, fake): path = fake_privacy_violator.path(fake) return getattr(self, path) - def __getattr__( self, name ): + def __getattr__(self, name): modifiers_ = copy.copy(self.__character) self.__reset_modifiers() return expectationmaker.ExpectationMaker(self.__scenario, self, name, modifiers_) @@ -24,7 +25,7 @@ def __getattr__( self, name ): def __reset_modifiers(self): self.__character = call_character.CallCharacter() - def __lshift__( self, expectation ): + def __lshift__(self, expectation): self.__scenario.addEvent(expectation) return self diff --git a/testix/testixexception.py b/testix/testixexception.py index a0fda72..0ddc298 100644 --- a/testix/testixexception.py +++ b/testix/testixexception.py @@ -1,6 +1,17 @@ import unittest -class TestixError(Exception): pass -class TestixException(unittest.TestCase.failureException): pass -class ScenarioException(TestixException): pass -class ExpectationException(TestixException): pass + +class TestixError(Exception): + pass + + +class TestixException(unittest.TestCase.failureException): + pass + + +class ScenarioException(TestixException): + pass + + +class ExpectationException(TestixException): + pass diff --git a/tools/docs_new_stage.py b/tools/docs_new_stage.py index 4827b50..f4f08f9 100644 --- a/tools/docs_new_stage.py +++ b/tools/docs_new_stage.py @@ -4,6 +4,7 @@ import shutil import box + class Folders: def __init__(self, number): self.number = number @@ -18,6 +19,7 @@ def __init__(self, number): self.previous.test = root / f'tests/unit/{number - 1}' self.previous.source = root / f'source/{number - 1}' + def go_test(folders): previous = folders.number - 1 os.chdir(folders.source) @@ -25,6 +27,7 @@ def go_test(folders): os.chdir(folders.test) shutil.copy(f'../{previous}/test_line_monitor.py', '.') + def go_source(folders): previous = folders.number - 1 os.chdir(folders.test) @@ -32,6 +35,7 @@ def go_source(folders): os.chdir(folders.source) shutil.copy(f'../{previous}/line_monitor.py', '.') + def main(): parser = argparse.ArgumentParser() parser.add_argument('number', type=int) diff --git a/tools/full_testix_importer.py b/tools/full_testix_importer.py index 1871b89..f800a92 100644 --- a/tools/full_testix_importer.py +++ b/tools/full_testix_importer.py @@ -1,10 +1,11 @@ -import argparse import pathlib import importlib import logging + def main(): import testix + root = pathlib.Path(testix.__file__).parent.parent logging.basicConfig(level=logging.INFO) for file in root.glob('testix/**/*.py'): @@ -19,5 +20,6 @@ def main(): logging.info('all imports OK') + if __name__ == '__main__': main() diff --git a/vim/plugin/bufexplorer.vim b/vim/plugin/bufexplorer.vim index 3514a7d..845da15 100644 --- a/vim/plugin/bufexplorer.vim +++ b/vim/plugin/bufexplorer.vim @@ -142,20 +142,20 @@ function! s:BEAddBuffer() if !exists('s:raw_buffer_listing') || empty(s:raw_buffer_listing) silent let s:raw_buffer_listing = s:BEGetBufferInfo(0) else - " We cannot use :buffers! or :ls! to gather information + " We cannot use :buffers! or :ls! to gather information " about this buffer since it was only just added. - " Any changes to the buffer (setlocal buftype, ...) + " Any changes to the buffer (setlocal buftype, ...) " happens after this event fires. " " So we will indicate the :buffers! command must be re-run. " This should help with the performance of the plugin. - " There are some checks which can be performed + " There are some checks which can be performed " before deciding to refresh the buffer list. let bufnr = expand('') + 0 if s:BEIgnoreBuffer(bufnr) == 1 - return + return else let s:refreshBufferList = 1 endif @@ -174,7 +174,7 @@ function! s:BEActivateBuffer() endif if !empty(l) && l[0] == '-1' - " The first time we add a tab Vim uses the current + " The first time we add a tab Vim uses the current " buffer as it's starting page, even though we are about " to edit a new page (BufEnter triggers after), so " remove the -1 entry indicating we have covered this case. @@ -214,7 +214,7 @@ function! s:BEActivateBuffer() let shortlist = filter(copy(s:raw_buffer_listing), "v:val.attributes =~ '".'^\s*'.b.'u\>'."'") if !empty(shortlist) - " If it is unlisted (ie deleted), but now we editing it again + " If it is unlisted (ie deleted), but now we editing it again " rebuild the buffer list. let s:refreshBufferList = 1 endif @@ -279,7 +279,7 @@ function! s:BEInitialize() let s:running = 1 endfunction -" BEIgnoreBuffer +" BEIgnoreBuffer function! s:BEIgnoreBuffer(buf) " Check to see if this buffer should be ignore by BufExplorer. @@ -307,7 +307,7 @@ function! s:BEIgnoreBuffer(buf) return 1 end - return 0 + return 0 endfunction " BECleanup {{{1 @@ -358,7 +358,7 @@ function! StartBufExplorer(open) let s:originBuffer = bufnr("%") + 0 " Create or rebuild the raw buffer list if necessary. - if !exists('s:raw_buffer_listing') || + if !exists('s:raw_buffer_listing') || \ empty(s:raw_buffer_listing) || \ s:refreshBufferList == 1 silent let s:raw_buffer_listing = s:BEGetBufferInfo(0) @@ -396,8 +396,8 @@ endfunction " BEDisplayBufferList {{{1 function! s:BEDisplayBufferList() - " Do not set bufhidden since it wipes out - " the data if we switch away from the buffer + " Do not set bufhidden since it wipes out + " the data if we switch away from the buffer " using CTRL-^ setlocal buftype=nofile setlocal modifiable @@ -584,7 +584,7 @@ function! s:BEGetBufferInfo(bufnr) redir END if (a:bufnr > 0) - " Since we are only interested in this specified buffer + " Since we are only interested in this specified buffer " remove the other buffers listed let bufoutput = substitute(bufoutput."\n", '^.*\n\(\s*'.a:bufnr.'\>.\{-}\)\n.*', '\1', '') endif @@ -653,7 +653,7 @@ function! s:BEBuildBufferList() endif endfor - if show_buffer == 0 + if show_buffer == 0 continue endif endif @@ -710,7 +710,7 @@ function! s:BESelectBuffer(...) else call WinManagerFileEdit(bufname, 0) endif - + return endif @@ -819,7 +819,7 @@ function! s:BERemoveBuffer(type, mode) range if bufNbr !~ '^\d\+$' || getbufvar(bufNbr+0, '&modified') != 0 call s:BEError("Sorry, no write since last change for buffer ".bufNbr.", unable to delete") else - let _bufNbrs = _bufNbrs . (_bufNbrs==''?'':' '). bufNbr + let _bufNbrs = _bufNbrs . (_bufNbrs==''?'':' '). bufNbr endif endif endfor diff --git a/vim/plugin/conque_term.vim b/vim/plugin/conque_term.vim index 2ed9e73..891252b 100644 --- a/vim/plugin/conque_term.vim +++ b/vim/plugin/conque_term.vim @@ -5,20 +5,20 @@ " VERSION: 2.0, for Vim 7.0 " LICENSE: " Conque - Vim terminal/console emulator -" Copyright (C) 2009-2010 Nico Raffo +" Copyright (C) 2009-2010 Nico Raffo " " MIT License -" +" " Permission is hereby granted, free of charge, to any person obtaining a copy " of this software and associated documentation files (the "Software"), to deal " in the Software without restriction, including without limitation the rights " to use, copy, modify, merge, publish, distribute, sublicense, and/or sell " copies of the Software, and to permit persons to whom the Software is " furnished to do so, subject to the following conditions: -" +" " The above copyright notice and this permission notice shall be included in " all copies or substantial portions of the Software. -" +" " THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR " IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, " FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE diff --git a/vim/plugin/mru.vim b/vim/plugin/mru.vim index 029b849..1aa35a1 100644 --- a/vim/plugin/mru.vim +++ b/vim/plugin/mru.vim @@ -148,7 +148,7 @@ " " let MRU_Exclude_Files = '^/tmp/.*\|^/var/tmp/.*' " For Unix " let MRU_Exclude_Files = '^c:\\temp\\.*' " For MS-Windows -" +" " The specified pattern should be a Vim regular expression pattern. " " If you want to add only file names matching a set of patterns to the MRU @@ -816,7 +816,7 @@ function! s:MRU_add_files_to_menu(prefix, file_list) " the beginning and end. if len > 30 let dir_name = strpart(dir_name, 0, 10) . - \ '...' . + \ '...' . \ strpart(dir_name, len - 20) endif let esc_dir_name = escape(dir_name, ".\\" . s:esc_filename_chars)