Skip to content

Commit

Permalink
Merge pull request #152 from jamescooke/context-manager-no-vacuum
Browse files Browse the repository at this point in the history
Make context manager analysis non-greedy
  • Loading branch information
jamescooke committed May 24, 2020
2 parents 3071827 + f4d9da4 commit 879744b
Show file tree
Hide file tree
Showing 45 changed files with 1,064 additions and 372 deletions.
15 changes: 15 additions & 0 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,12 @@ Unreleased_
See also `latest documentation
<https://flake8-aaa.readthedocs.io/en/latest/#__unreleased_marker__>`_.

Added
.....

* Test examples are intended to be real but simple examples. All examples added
or updated are now executed with pytest and no imports and must be green.

Changed
.......

Expand All @@ -22,6 +28,15 @@ Changed
<https://github.com/kylelobo/The-Documentation-Compendium>`_. `#141
<https://github.com/jamescooke/flake8-aaa/issues/141>`_.

* Behaviour of context managers in tests has been changed. Going forwards only
with statements that are used to catch exceptions are considered actions, for
example, ``with pytest.raises(...):``. Otherwise, the with statement is
arrangement or assertion and must be separated from the Act block by a blank
line as usual. `#146 <https://github.com/jamescooke/flake8-aaa/issues/146>`_.

Implementing this feature meant changing the line-by-line analysis that
happens on test function bodies.

0.9.0_ - 2020/03/07
-------------------

Expand Down
5 changes: 4 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,9 @@ lintexamples:
@echo "=== flake8 ==="
flake8 examples | sort > flake8.out
diff examples/bad/flake8_expected.out flake8.out
@echo "=== mypy ==="
mypy examples examples/good --ignore-missing-imports
mypy examples/bad --ignore-missing-imports
@echo "=== black ==="
black --check --diff --verbose examples/good/black

Expand Down Expand Up @@ -92,7 +95,7 @@ cmdbad:

.PHONY: clean
clean:
rm -rf dist build .tox .pytest_cache flake8_aaa.egg-info
rm -rf dist build .tox .pytest_cache src/flake8_aaa.egg-info
find . -name '*.pyc' -delete

.PHONY: sdist
Expand Down
4 changes: 4 additions & 0 deletions examples/bad/bad_expected.out
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
examples/bad/test_aaa01.py:8:1: AAA01 no Act block found in test
examples/bad/test_aaa03_04.py:10:5: AAA04 expected 1 blank line before Assert block, found none
examples/bad/test_aaa03_04.py:3:5: AAA03 expected 1 blank line before Act block, found none
examples/bad/test_aaa03_04.py:4:5: AAA04 expected 1 blank line before Assert block, found none
examples/bad/test_aaa03_04.py:9:9: AAA03 expected 1 blank line before Act block, found none
examples/bad/test_aaa03.py:10:9: AAA03 expected 1 blank line before Act block, found none
examples/bad/test_aaa03.py:3:5: AAA03 expected 1 blank line before Act block, found none
examples/bad/test_aaa04.py:12:5: AAA04 expected 1 blank line before Assert block, found none
examples/bad/test_aaa04.py:5:5: AAA04 expected 1 blank line before Assert block, found none
examples/bad/test_aaa05.py:24:1: AAA05 blank line in block
examples/bad/test_aaa05.py:35:1: AAA05 blank line in block
Expand Down
11 changes: 9 additions & 2 deletions examples/bad/test_aaa03.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
def test():
def test() -> None:
x = 1
result = x**2

assert result == 4
assert result == 1


def test_b(hello_world_path) -> None:
with open(hello_world_path) as f:
result = f.read()

assert result == 'Hello World!\n'
14 changes: 10 additions & 4 deletions examples/bad/test_aaa03_04.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,10 @@
def test():
x = 1
result = x**2
assert result == 4
def test() -> None:
x = 3
result = x**5
assert result == 243


def test_b(hello_world_path) -> None:
with open(hello_world_path) as f:
result = f.read()
assert result == 'Hello World!\n'
15 changes: 11 additions & 4 deletions examples/bad/test_aaa04.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
def test():
x = 1
def test() -> None:
x = 3

result = x**2
assert result == 4
result = x**5
assert result == 243


def test_b(hello_world_path) -> None:
with open(hello_world_path) as f:

result = f.read()
assert result == 'Hello World!\n'
11 changes: 11 additions & 0 deletions examples/conftest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import pathlib

import pytest


@pytest.fixture
def hello_world_path() -> pathlib.Path:
"""
Location of hello_world.txt
"""
return pathlib.Path(__file__).parent / 'data' / 'hello_world.txt'
1 change: 1 addition & 0 deletions examples/data/hello_world.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Hello World!
12 changes: 10 additions & 2 deletions examples/good/README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,16 @@ Arrange Act Assert as checked for by Flake8-AAA.
See `test_example.py <test_example.py>`_ for the simplest example of an
AAA-style test.

Testing
-------
Valid tests
-----------

The goal is that all tests in both good and bad examples will target features
of the Python 3 standard library. They will all be executable with a vanilla
install of ``pytest``. Currently only the ``with`` statement examples support
this.

Testing Flake8-AAA
------------------

To prevent false negatives our test suite runs Flake8-AAA against this
directory and checks no linting errors are raised.
17 changes: 8 additions & 9 deletions examples/good/black/test_context_block_act.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,12 @@
from project.auth import user_perms_changed
# You know it"s black because it does the double quotes :D

from .helpers import catch_signal

def test_simple(hello_world_path) -> None:
"""
`with` statement is part of arrange. Blank lines are maintained around Act.
"""
with open(hello_world_path) as f:

def test(api_client, url):
data = {"user_id": 0, "project_permission": "admin"}
result = f.read()

with catch_signal(user_perms_changed) as callback:
result = api_client.put(url, data=data)

assert result.status_code == 400
assert callback.call_count == 0
assert result == "Hello World!\n"
16 changes: 0 additions & 16 deletions examples/good/test_context_block_act.py

This file was deleted.

4 changes: 2 additions & 2 deletions examples/good/test_django_fakery_factories.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@
from django.db.utils import IntegrityError
from django.test import TestCase

from ..django_fakery_factories import ItemFactory, UserFactory
from ..models import Item
from .django_fakery_factories import ItemFactory, UserFactory
from .models import Item


class TestItemFactory(TestCase):
Expand Down
86 changes: 86 additions & 0 deletions examples/good/test_with_statement.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
import io

import pytest


def test_simple(hello_world_path) -> None:
"""
`with` statement is part of arrange. Blank lines are maintained around Act.
"""
with open(hello_world_path) as f:

result = f.read()

assert result == 'Hello World!\n'


def test_whole(hello_world_path) -> None:
"""
`with` statement wraps whole of test
"""
with open(hello_world_path) as f:

result = f.read()

assert result == 'Hello World!\n'


def test_extra_arrange(hello_world_path) -> None:
"""
Any extra arrangement goes in the `with` block.
"""
with open(hello_world_path) as f:
f.read()

result = f.read()

assert result == ''


def test_assert_in_block(hello_world_path) -> None:
"""
Any assertion that needs the `with` block open, goes after Act and a BL.
"""
with open(hello_world_path) as f:
f.read()

result = f.read()

assert not f.closed
assert f.closed
assert result == ''


def test_pytest_assert_raises_in_block(hello_world_path) -> None:
"""
Checking on a raise in a with block works with Pytest.
"""
with open(hello_world_path) as f:

with pytest.raises(io.UnsupportedOperation):
f.write('hello back')

assert f.read() == 'Hello World!\n'


def test_pytest_assert_raises_on_with(hello_world_path) -> None:
"""
Checking on the raise from a with statement works with Pytest.
"""
with pytest.raises(ValueError) as excinfo:
with open(hello_world_path, 'zz'):
pass

assert 'invalid mode' in str(excinfo.value)


def test_with_in_assert(hello_world_path) -> None:
"""
Using with statement in Assert block is valid
"""
words = ['Hello', 'World!\n']

result = ' '.join(words)

with open(hello_world_path) as f:
assert result == f.read()
30 changes: 30 additions & 0 deletions examples/good/test_with_statement_unittest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import io
import pathlib
import unittest


class Test(unittest.TestCase):

def setUp(self):
self.hello_world_path = pathlib.Path(__file__).parent.parent / 'data' / 'hello_world.txt'

def test_assert_raises_in_block(self):
"""
Checking on a raise in a with block works with unittest.
"""
with open(self.hello_world_path) as f:

with self.assertRaises(io.UnsupportedOperation):
f.write('hello back')

self.assertEqual(f.read(), 'Hello World!\n')

def test_assert_raises_on_with(self):
"""
Checking on the raise from a with statement works with Pytest.
"""
with self.assertRaises(ValueError) as cm:
with open(self.hello_world_path, 'zz'):
pass

self.assertIn('invalid mode', str(cm.exception))
2 changes: 2 additions & 0 deletions requirements/lintexamples.in → requirements/examples.in
Original file line number Diff line number Diff line change
@@ -1,2 +1,4 @@
black
flake8
mypy
pytest
30 changes: 30 additions & 0 deletions requirements/examples.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
#
# This file is autogenerated by pip-compile
# To update, run:
#
# pip-compile --output-file=examples.txt examples.in
#
appdirs==1.4.3 # via black
attrs==19.3.0 # via black, pytest
black==19.10b0 # via -r examples.in
click==7.1.1 # via black
entrypoints==0.3 # via flake8
flake8==3.7.9 # via -r examples.in
mccabe==0.6.1 # via flake8
more-itertools==8.2.0 # via pytest
mypy-extensions==0.4.3 # via mypy
mypy==0.770 # via -r examples.in
packaging==20.3 # via pytest
pathspec==0.7.0 # via black
pluggy==0.13.1 # via pytest
py==1.8.1 # via pytest
pycodestyle==2.5.0 # via flake8
pyflakes==2.1.1 # via flake8
pyparsing==2.4.7 # via packaging
pytest==5.4.1 # via -r examples.in
regex==2020.2.20 # via black
six==1.14.0 # via packaging
toml==0.10.0 # via black
typed-ast==1.4.1 # via black, mypy
typing-extensions==3.7.4.2 # via mypy
wcwidth==0.1.9 # via pytest
19 changes: 0 additions & 19 deletions requirements/lintexamples.txt

This file was deleted.

2 changes: 1 addition & 1 deletion setup.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -16,4 +16,4 @@ column_limit = 120

[tool:pytest]
addopts=--tb=short --color=yes
log_print=False
show_capture = false

0 comments on commit 879744b

Please sign in to comment.