diff --git a/README.rst b/README.rst index 1e843ce..8db996d 100644 --- a/README.rst +++ b/README.rst @@ -62,6 +62,12 @@ Changelog This project adheres to `Semantic Versioning `_. +1.0.5 - 2015-11-29 +------------------ + +Fixed + * Standard input (piped in data) bug. + 1.0.4 - 2015-11-14 ------------------ diff --git a/appveyor.yml b/appveyor.yml index 904cfe6..a0055c6 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -1,5 +1,5 @@ init: - - set PATH=C:\Python34-x64;C:\Python34-x64\Scripts;%PATH% + - set PATH=C:\Python35-x64;C:\Python35-x64\Scripts;%PATH% artifacts: - path: .coverage diff --git a/flake8_pep257.py b/flake8_pep257.py index 089c5df..f2e47a4 100644 --- a/flake8_pep257.py +++ b/flake8_pep257.py @@ -5,6 +5,7 @@ """ import codecs +import gc import os import pep257 @@ -15,7 +16,14 @@ def load_file(filename): """Read file to memory. - From: https://github.com/public/flake8-import-order/blob/620a376/flake8_import_order/__init__.py#L201 + For stdin sourced files, this function does something super duper incredibly hacky and shameful. So so shameful. I'm + obtaining the original source code of the target module from the only instance of pep8.Checker through the Python + garbage collector. Flake8's API doesn't give me the original source code of the module we are checking. Instead it + has pep8 give me an AST object of the module (already parsed). This unfortunately loses valuable information like + the kind of quotes used for strings (no way to know if a docstring was surrounded by triple double quotes or just + one single quote, thereby rendering pep257's D300 error as unusable). + + This will break one day. I'm sure of it. For now it fixes https://github.com/Robpol86/flake8-pep257/issues/2 :param str filename: File path or 'stdin'. From Main().filename. @@ -23,7 +31,10 @@ def load_file(filename): :rtype: tuple """ if filename in ('stdin', '-', None): - return 'stdin', pep8.stdin_get_value() + instances = [i for i in gc.get_objects() if isinstance(i, pep8.Checker)] + if len(instances) != 1: + raise ValueError('Expected only 1 instance of pep8.Checker, got {0} instead.'.format(len(instances))) + return 'stdin', ''.join(instances[0].lines) with codecs.open(filename, encoding='utf-8') as handle: return filename, handle.read() diff --git a/setup.py b/setup.py index 7c07264..22ec78f 100755 --- a/setup.py +++ b/setup.py @@ -57,6 +57,6 @@ def safe_read(path): name='flake8-pep257', py_modules=['flake8_pep257'], url='https://github.com/Robpol86/flake8-pep257', - version='1.0.4', + version='1.0.5', zip_safe=True, ) diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 0000000..4288706 --- /dev/null +++ b/tests/__init__.py @@ -0,0 +1,11 @@ +"""Non-fixture code shared among test modules.""" + + +try: + from subprocess import check_output, STDOUT +except ImportError: + from subprocess32 import check_output, STDOUT + + +assert check_output +assert STDOUT diff --git a/tests/test_default.py b/tests/test_default.py index d003b03..f5378af 100644 --- a/tests/test_default.py +++ b/tests/test_default.py @@ -1,10 +1,13 @@ """Test against sample modules using the default options.""" import os +from distutils.spawn import find_executable import flake8.main import pytest +from tests import check_output, STDOUT + EXPECTED = """\ ./sample.py:1:1: D100 Missing docstring in public module ./sample.py:5:1: D300 Use \"\"\"triple double quotes\"\"\" (found '''-quotes) @@ -17,7 +20,7 @@ ./sample_unicode.py:15:1: D401 First line should be in imperative mood ('Print', not 'Prints') ./sample_unicode.py:24:1: D203 1 blank line required before class docstring (found 0) ./sample_unicode.py:24:1: D204 1 blank line required after class docstring (found 0) -./sample_unicode.py:24:1: D300 Use \"\"\"triple double quotes\"\"\" (found '''-quotes) +./sample_unicode.py:24:1: D300 Use \"\"\"triple double quotes\"\"\" (found '''-quotes)\ """ @@ -42,11 +45,42 @@ def test_direct(capsys, monkeypatch, tempdir, stdin): out, err = capsys.readouterr() assert not err + # Clean. + if stdin: + expected = '\n'.join('stdin:' + l.split(':', 1)[-1] for l in EXPECTED.splitlines() if stdin in l) + elif os.name == 'nt': + expected = EXPECTED.replace('./sample', r'.\sample') + else: + expected = EXPECTED + out = '\n'.join(l.rstrip() for l in out.splitlines()) + + assert out == expected + + +@pytest.mark.parametrize('stdin', ['', 'sample_unicode.py', 'sample.py']) +def test_subprocess(tempdir, stdin): + """Test by calling flake8 through subprocess using a dedicated python process. + + :param tempdir: conftest fixture. + :param str stdin: Pipe this file to stdin of flake8. + """ + # Prepare. + cwd = str(tempdir.join('empty' if stdin else '')) + stdin_handle = tempdir.join(stdin).open() if stdin else None + + # Execute. + command = [find_executable('flake8'), '--exit-zero', '-' if stdin else '.'] + environ = os.environ.copy() + environ['COV_CORE_DATAFILE'] = '' # Disable pytest-cov's subprocess coverage feature. Doesn't work right now. + out = check_output(command, stderr=STDOUT, cwd=cwd, stdin=stdin_handle, env=environ).decode('utf-8') + + # Clean. if stdin: expected = '\n'.join('stdin:' + l.split(':', 1)[-1] for l in EXPECTED.splitlines() if stdin in l) elif os.name == 'nt': expected = EXPECTED.replace('./sample', r'.\sample') else: expected = EXPECTED + out = '\n'.join(l.rstrip() for l in out.splitlines()) - assert expected.strip() == out.strip() + assert out == expected diff --git a/tests/test_explain.py b/tests/test_explain.py index bef60ab..b620b37 100644 --- a/tests/test_explain.py +++ b/tests/test_explain.py @@ -1,11 +1,13 @@ """Test against sample modules using the explain option in all supported config sources.""" import os -import re +from distutils.spawn import find_executable import flake8.main import pytest +from tests import check_output, STDOUT + EXPECTED = list() EXPECTED.append("""\ ./sample.py:1:1: D100 Missing docstring in public module @@ -61,6 +63,7 @@ Note: Exception to this is made if the docstring contains \"\"\" quotes in its body. + """) EXPECTED.append("""\ ./sample_unicode.py:1:1: D100 Missing docstring in public module @@ -116,6 +119,7 @@ Note: Exception to this is made if the docstring contains \"\"\" quotes in its body. + """) @@ -148,12 +152,50 @@ def test_direct(capsys, monkeypatch, tempdir, stdin, which_cfg): out, err = capsys.readouterr() assert not err + # Clean. + if stdin: + expected = EXPECTED[0 if stdin == 'sample.py' else 1].replace('./{0}:'.format(stdin), 'stdin:') + elif os.name == 'nt': + expected = '\n'.join(EXPECTED).replace('./sample', r'.\sample') + else: + expected = '\n'.join(EXPECTED) + out = '\n'.join(l.rstrip() for l in out.splitlines()) + + assert out == expected + + +@pytest.mark.parametrize('stdin', ['', 'sample_unicode.py', 'sample.py']) +@pytest.mark.parametrize('which_cfg', ['tox.ini', 'tox.ini flake8', 'setup.cfg', '.pep257']) +def test_subprocess(tempdir, stdin, which_cfg): + """Test by calling flake8 through subprocess using a dedicated python process. + + :param tempdir: conftest fixture. + :param str stdin: Pipe this file to stdin of flake8. + :param str which_cfg: Which config file to test with. + """ + # Prepare. + cwd = str(tempdir.join('empty' if stdin else '')) + stdin_handle = tempdir.join(stdin).open() if stdin else None + + # Write configuration. + cfg = which_cfg.split() + section = cfg[1] if len(cfg) > 1 else 'pep257' + key = 'show-pep257' if section == 'flake8' else 'explain' + tempdir.join('empty' if stdin else '', cfg[0]).write('[{0}]\n{1} = True\n'.format(section, key)) + + # Execute. + command = [find_executable('flake8'), '--exit-zero', '-' if stdin else '.'] + environ = os.environ.copy() + environ['COV_CORE_DATAFILE'] = '' # Disable pytest-cov's subprocess coverage feature. Doesn't work right now. + out = check_output(command, stderr=STDOUT, cwd=cwd, stdin=stdin_handle, env=environ).decode('utf-8') + + # Clean. if stdin: expected = EXPECTED[0 if stdin == 'sample.py' else 1].replace('./{0}:'.format(stdin), 'stdin:') elif os.name == 'nt': - expected = '\n\n'.join(EXPECTED).replace('./sample', r'.\sample') + expected = '\n'.join(EXPECTED).replace('./sample', r'.\sample') else: - expected = '\n\n'.join(EXPECTED) - out = re.sub(r'\n[\t ]+\n', r'\n\n', out) + expected = '\n'.join(EXPECTED) + out = '\n'.join(l.rstrip() for l in out.splitlines()) - assert expected.strip() == out.strip() + assert out == expected diff --git a/tests/test_ignore.py b/tests/test_ignore.py index 422e2d5..ea61614 100644 --- a/tests/test_ignore.py +++ b/tests/test_ignore.py @@ -1,10 +1,13 @@ """Test against sample modules using the ignore option in all supported config sources.""" import os +from distutils.spawn import find_executable import flake8.main import pytest +from tests import check_output, STDOUT + EXPECTED = """\ ./sample.py:1:1: D100 Missing docstring in public module ./sample.py:5:1: D300 Use \"\"\"triple double quotes\"\"\" (found '''-quotes) @@ -13,7 +16,7 @@ ./sample_unicode.py:1:1: D100 Missing docstring in public module ./sample_unicode.py:15:1: D300 Use \"\"\"triple double quotes\"\"\" (found '''-quotes) ./sample_unicode.py:15:1: D401 First line should be in imperative mood ('Print', not 'Prints') -./sample_unicode.py:24:1: D300 Use \"\"\"triple double quotes\"\"\" (found '''-quotes) +./sample_unicode.py:24:1: D300 Use \"\"\"triple double quotes\"\"\" (found '''-quotes)\ """ @@ -47,11 +50,51 @@ def test_direct(capsys, monkeypatch, tempdir, ignore, stdin, which_cfg): out, err = capsys.readouterr() assert not err + # Clean. + if stdin: + expected = '\n'.join('stdin:' + l.split(':', 1)[-1] for l in EXPECTED.splitlines() if stdin in l) + elif os.name == 'nt': + expected = EXPECTED.replace('./sample', r'.\sample') + else: + expected = EXPECTED + out = '\n'.join(l.rstrip() for l in out.splitlines()) + + assert out == expected + + +@pytest.mark.parametrize('ignore', ['D203,D204', 'D2']) +@pytest.mark.parametrize('stdin', ['', 'sample_unicode.py', 'sample.py']) +@pytest.mark.parametrize('which_cfg', ['tox.ini', 'tox.ini flake8', 'setup.cfg', '.pep257']) +def test_subprocess(tempdir, ignore, stdin, which_cfg): + """Test by calling flake8 through subprocess using a dedicated python process. + + :param tempdir: conftest fixture. + :param str ignore: Config value for ignore option. + :param str stdin: Pipe this file to stdin of flake8. + :param str which_cfg: Which config file to test with. + """ + # Prepare. + cwd = str(tempdir.join('empty' if stdin else '')) + stdin_handle = tempdir.join(stdin).open() if stdin else None + + # Write configuration. + cfg = which_cfg.split() + section = cfg[1] if len(cfg) > 1 else 'pep257' + tempdir.join('empty' if stdin else '', cfg[0]).write('[{0}]\nignore = {1}\n'.format(section, ignore)) + + # Execute. + command = [find_executable('flake8'), '--exit-zero', '-' if stdin else '.'] + environ = os.environ.copy() + environ['COV_CORE_DATAFILE'] = '' # Disable pytest-cov's subprocess coverage feature. Doesn't work right now. + out = check_output(command, stderr=STDOUT, cwd=cwd, stdin=stdin_handle, env=environ).decode('utf-8') + + # Clean. if stdin: expected = '\n'.join('stdin:' + l.split(':', 1)[-1] for l in EXPECTED.splitlines() if stdin in l) elif os.name == 'nt': expected = EXPECTED.replace('./sample', r'.\sample') else: expected = EXPECTED + out = '\n'.join(l.rstrip() for l in out.splitlines()) - assert expected.strip() == out.strip() + assert out == expected diff --git a/tox.ini b/tox.ini index 9a25469..6c45701 100644 --- a/tox.ini +++ b/tox.ini @@ -7,6 +7,7 @@ commands = {posargs:tests} deps = pytest-cov + py26: subprocess32 usedevelop = True [testenv:py35x64]