Merge remote-tracking branch 'upstream/master' into mixed-line-ending

This commit is contained in:
Morgan Courbet 2017-07-18 19:40:39 +02:00
commit 55658c4bbc
No known key found for this signature in database
GPG key ID: 467299D324A21B24
47 changed files with 923 additions and 302 deletions

View file

@ -23,6 +23,6 @@ def test_main_failing(tmpdir, input_src, expected_ret, output_src):
def test_respects_config_file(tmpdir):
with tmpdir.as_cwd():
tmpdir.join('setup.cfg').write('[pep8]\nignore=E221')
tmpdir.join('setup.cfg').write('[pycodestyle]\nignore=E221')
tmpdir.join('test.py').write('print(1 + 2)\n')
assert main(['test.py', '-i', '-v']) == 0

View file

@ -81,12 +81,7 @@ def test_allows_gitlfs(temp_git_dir): # pragma: no cover
with temp_git_dir.as_cwd():
# Work around https://github.com/github/git-lfs/issues/913
cmd_output(
'git',
'commit',
'--no-gpg-sign',
'--allow-empty',
'-m',
'foo',
'git', 'commit', '--no-gpg-sign', '--allow-empty', '-m', 'foo',
)
cmd_output('git', 'lfs', 'install')
temp_git_dir.join('f.py').write('a' * 10000)

View file

@ -19,7 +19,7 @@ TESTS = (
'"foo"\n',
1,
'{filename}:2 Module docstring appears after code '
'(code seen on line 1).\n'
'(code seen on line 1).\n',
),
# Test double docstring
(
@ -28,7 +28,7 @@ TESTS = (
'"fake docstring"\n',
1,
'{filename}:3 Multiple module docstrings '
'(first docstring on line 1).\n'
'(first docstring on line 1).\n',
),
# Test multiple lines of code above
(

View file

@ -0,0 +1,39 @@
# -*- coding: utf-8 -*-
from __future__ import absolute_import
from __future__ import unicode_literals
import pytest
from pre_commit_hooks.check_executables_have_shebangs import main
@pytest.mark.parametrize(
'content', (
b'#!/bin/bash\nhello world\n',
b'#!/usr/bin/env python3.6',
b'#!python',
'#!☃'.encode('UTF-8'),
),
)
def test_has_shebang(content, tmpdir):
path = tmpdir.join('path')
path.write(content, 'wb')
assert main((path.strpath,)) == 0
@pytest.mark.parametrize(
'content', (
b'',
b' #!python\n',
b'\n#!python\n',
b'python\n',
''.encode('UTF-8'),
),
)
def test_bad_shebang(content, tmpdir, capsys):
path = tmpdir.join('path')
path.write(content, 'wb')
assert main((path.strpath,)) == 1
_, stderr = capsys.readouterr()
assert stderr.startswith('{}: marked executable but'.format(path.strpath))

View file

@ -4,11 +4,13 @@ from pre_commit_hooks.check_json import check_json
from testing.util import get_resource_path
@pytest.mark.parametrize(('filename', 'expected_retval'), (
('bad_json.notjson', 1),
('bad_json_latin1.nonjson', 1),
('ok_json.json', 0),
))
@pytest.mark.parametrize(
('filename', 'expected_retval'), (
('bad_json.notjson', 1),
('bad_json_latin1.nonjson', 1),
('ok_json.json', 0),
),
)
def test_check_json(capsys, filename, expected_retval):
ret = check_json([get_resource_path(filename)])
assert ret == expected_retval

View file

@ -45,7 +45,7 @@ def f1_is_a_conflict_file(tmpdir):
'child\n'
'=======\n'
'parent\n'
'>>>>>>>'
'>>>>>>>',
) or f1.startswith(
'<<<<<<< HEAD\n'
'child\n'
@ -53,7 +53,15 @@ def f1_is_a_conflict_file(tmpdir):
'||||||| merged common ancestors\n'
'=======\n'
'parent\n'
'>>>>>>>'
'>>>>>>>',
) or f1.startswith(
# .gitconfig with [pull] rebase = preserve causes a rebase which
# flips parent / child
'<<<<<<< HEAD\n'
'parent\n'
'=======\n'
'child\n'
'>>>>>>>',
)
assert os.path.exists(os.path.join('.git', 'MERGE_MSG'))
yield
@ -85,7 +93,7 @@ def repository_is_pending_merge(tmpdir):
repo2_f2.write('child\n')
cmd_output('git', 'add', '--', repo2_f2.strpath)
cmd_output('git', 'commit', '--no-gpg-sign', '-m', 'clone commit2')
cmd_output('git', 'pull', '--no-commit')
cmd_output('git', 'pull', '--no-commit', '--no-rebase')
# We should end up in a pending merge
assert repo2_f1.read() == 'parent\n'
assert repo2_f2.read() == 'child\n'

View file

@ -7,10 +7,12 @@ from testing.util import get_resource_path
@pytest.mark.xfail(os.name == 'nt', reason='No symlink support on windows')
@pytest.mark.parametrize(('filename', 'expected_retval'), (
('broken_symlink', 1),
('working_symlink', 0),
))
@pytest.mark.parametrize(
('filename', 'expected_retval'), (
('broken_symlink', 1),
('working_symlink', 0),
),
)
def test_check_symlinks(filename, expected_retval):
ret = check_symlinks([get_resource_path(filename)])
assert ret == expected_retval

View file

@ -4,10 +4,12 @@ from pre_commit_hooks.check_xml import check_xml
from testing.util import get_resource_path
@pytest.mark.parametrize(('filename', 'expected_retval'), (
('bad_xml.notxml', 1),
('ok_xml.xml', 0),
))
@pytest.mark.parametrize(
('filename', 'expected_retval'), (
('bad_xml.notxml', 1),
('ok_xml.xml', 0),
),
)
def test_check_xml(filename, expected_retval):
ret = check_xml([get_resource_path(filename)])
assert ret == expected_retval

View file

@ -4,10 +4,12 @@ from pre_commit_hooks.check_yaml import check_yaml
from testing.util import get_resource_path
@pytest.mark.parametrize(('filename', 'expected_retval'), (
('bad_yaml.notyaml', 1),
('ok_yaml.yaml', 0),
))
@pytest.mark.parametrize(
('filename', 'expected_retval'), (
('bad_yaml.notyaml', 1),
('ok_yaml.yaml', 0),
),
)
def test_check_yaml(filename, expected_retval):
ret = check_yaml([get_resource_path(filename)])
assert ret == expected_retval

View file

@ -10,30 +10,36 @@ from testing.util import get_resource_path
@pytest.fixture
def ast_with_no_debug_imports():
return ast.parse("""
return ast.parse(
"""
import foo
import bar
import baz
from foo import bar
""")
""",
)
@pytest.fixture
def ast_with_debug_import_form_1():
return ast.parse("""
return ast.parse(
"""
import ipdb; ipdb.set_trace()
""")
""",
)
@pytest.fixture
def ast_with_debug_import_form_2():
return ast.parse("""
return ast.parse(
"""
from pudb import set_trace; set_trace()
""")
""",
)
def test_returns_no_debug_statements(ast_with_no_debug_imports):
@ -46,7 +52,7 @@ def test_returns_one_form_1(ast_with_debug_import_form_1):
visitor = ImportStatementParser()
visitor.visit(ast_with_debug_import_form_1)
assert visitor.debug_import_statements == [
DebugStatement('ipdb', 3, 0)
DebugStatement('ipdb', 3, 0),
]
@ -54,7 +60,7 @@ def test_returns_one_form_2(ast_with_debug_import_form_2):
visitor = ImportStatementParser()
visitor.visit(ast_with_debug_import_form_2)
assert visitor.debug_import_statements == [
DebugStatement('pudb', 3, 0)
DebugStatement('pudb', 3, 0),
]

View file

@ -21,16 +21,16 @@ from testing.util import get_resource_path
(
{
'AWS_DUMMY_KEY': '/foo', 'AWS_CONFIG_FILE': '/bar',
'AWS_CREDENTIAL_FILE': '/baz'
'AWS_CREDENTIAL_FILE': '/baz',
},
{'/bar', '/baz'}
{'/bar', '/baz'},
),
(
{
'AWS_CONFIG_FILE': '/foo', 'AWS_CREDENTIAL_FILE': '/bar',
'AWS_SHARED_CREDENTIALS_FILE': '/baz'
'AWS_SHARED_CREDENTIALS_FILE': '/baz',
},
{'/foo', '/bar', '/baz'}
{'/foo', '/bar', '/baz'},
),
),
)
@ -51,7 +51,7 @@ def test_get_aws_credentials_file_from_env(env_vars, values):
({'AWS_DUMMY_KEY': 'foo', 'AWS_SECRET_ACCESS_KEY': 'bar'}, {'bar'}),
(
{'AWS_SECRET_ACCESS_KEY': 'foo', 'AWS_SECURITY_TOKEN': 'bar'},
{'foo', 'bar'}
{'foo', 'bar'},
),
),
)
@ -66,19 +66,21 @@ def test_get_aws_secrets_from_env(env_vars, values):
(
(
'aws_config_with_secret.ini',
{'z2rpgs5uit782eapz5l1z0y2lurtsyyk6hcfozlb'}
{'z2rpgs5uit782eapz5l1z0y2lurtsyyk6hcfozlb'},
),
('aws_config_with_session_token.ini', {'foo'}),
('aws_config_with_secret_and_session_token.ini',
{'z2rpgs5uit782eapz5l1z0y2lurtsyyk6hcfozlb', 'foo'}),
(
'aws_config_with_secret_and_session_token.ini',
{'z2rpgs5uit782eapz5l1z0y2lurtsyyk6hcfozlb', 'foo'},
),
(
'aws_config_with_multiple_sections.ini',
{
'7xebzorgm5143ouge9gvepxb2z70bsb2rtrh099e',
'z2rpgs5uit782eapz5l1z0y2lurtsyyk6hcfozlb',
'ixswosj8gz3wuik405jl9k3vdajsnxfhnpui38ez',
'foo'
}
'foo',
},
),
('aws_config_without_secrets.ini', set()),
('nonsense.txt', set()),
@ -121,7 +123,7 @@ def test_non_existent_credentials(mock_secrets_env, mock_secrets_file, capsys):
mock_secrets_file.return_value = set()
ret = main((
get_resource_path('aws_config_without_secrets.ini'),
"--credentials-file=testing/resources/credentailsfilethatdoesntexist"
"--credentials-file=testing/resources/credentailsfilethatdoesntexist",
))
assert ret == 2
out, _ = capsys.readouterr()
@ -141,6 +143,6 @@ def test_non_existent_credentials_with_allow_flag(mock_secrets_env, mock_secrets
ret = main((
get_resource_path('aws_config_without_secrets.ini'),
"--credentials-file=testing/resources/credentailsfilethatdoesntexist",
"--allow-missing-credentials"
"--allow-missing-credentials",
))
assert ret == 0

View file

@ -0,0 +1,33 @@
import pytest
from pre_commit_hooks.file_contents_sorter import FAIL
from pre_commit_hooks.file_contents_sorter import main
from pre_commit_hooks.file_contents_sorter import PASS
@pytest.mark.parametrize(
('input_s', 'expected_retval', 'output'),
(
(b'', FAIL, b'\n'),
(b'lonesome\n', PASS, b'lonesome\n'),
(b'missing_newline', FAIL, b'missing_newline\n'),
(b'newline\nmissing', FAIL, b'missing\nnewline\n'),
(b'missing\nnewline', FAIL, b'missing\nnewline\n'),
(b'alpha\nbeta\n', PASS, b'alpha\nbeta\n'),
(b'beta\nalpha\n', FAIL, b'alpha\nbeta\n'),
(b'C\nc\n', PASS, b'C\nc\n'),
(b'c\nC\n', FAIL, b'C\nc\n'),
(b'mag ical \n tre vor\n', FAIL, b' tre vor\nmag ical \n'),
(b'@\n-\n_\n#\n', FAIL, b'#\n-\n@\n_\n'),
(b'extra\n\n\nwhitespace\n', FAIL, b'extra\nwhitespace\n'),
(b'whitespace\n\n\nextra\n', FAIL, b'extra\nwhitespace\n'),
),
)
def test_integration(input_s, expected_retval, output, tmpdir):
path = tmpdir.join('file.txt')
path.write_binary(input_s)
output_retval = main([path.strpath])
assert path.read_binary() == output
assert output_retval == expected_retval

View file

@ -56,7 +56,7 @@ def test_integration_remove_ok(tmpdir):
b'# -*- coding: utf-8 -*-\n'
b'foo = "bar"\n'
),
)
),
)
def test_ok_inputs(input_str):
bytesio = io.BytesIO(input_str)
@ -100,7 +100,7 @@ def test_ok_inputs(input_str):
(b'#!/usr/bin/env python\n', b''),
(b'#!/usr/bin/env python\n#coding: utf8\n', b''),
(b'#!/usr/bin/env python\n# -*- coding: utf-8 -*-\n', b''),
)
),
)
def test_not_ok_inputs(input_str, output):
bytesio = io.BytesIO(input_str)

View file

@ -10,10 +10,14 @@ from pre_commit_hooks.forbid_new_submodules import main
def git_dir_with_git_dir(tmpdir):
with tmpdir.as_cwd():
cmd_output('git', 'init', '.')
cmd_output('git', 'commit', '-m', 'init', '--allow-empty')
cmd_output(
'git', 'commit', '-m', 'init', '--allow-empty', '--no-gpg-sign',
)
cmd_output('git', 'init', 'foo')
with tmpdir.join('foo').as_cwd():
cmd_output('git', 'commit', '-m', 'init', '--allow-empty')
cmd_output(
'git', 'commit', '-m', 'init', '--allow-empty', '--no-gpg-sign',
cwd=tmpdir.join('foo').strpath,
)
yield

View file

@ -1,7 +1,43 @@
from __future__ import absolute_import
from __future__ import unicode_literals
import io
import yaml
def test_hooks_yaml_same_contents():
legacy_contents = io.open('hooks.yaml').read()
contents = io.open('.pre-commit-hooks.yaml').read()
assert legacy_contents == contents
def _assert_parseable_in_old_pre_commit(hooks):
for hook in hooks:
assert {'id', 'name', 'entry', 'files', 'language'} <= set(hook)
def test_legacy_hooks():
with io.open('hooks.yaml') as legacy_file:
legacy = yaml.load(legacy_file.read())
with io.open('.pre-commit-hooks.yaml') as hooks_file:
hooks = yaml.load(hooks_file.read())
# The same set of hooks should be defined in both files
new_hook_ids = {hook['id'] for hook in hooks}
legacy_hook_ids = {hook['id'] for hook in legacy}
assert new_hook_ids == legacy_hook_ids
# Both files should be parseable by pre-commit<0.15.0
_assert_parseable_in_old_pre_commit(legacy)
_assert_parseable_in_old_pre_commit(hooks)
# The legacy file should force upgrading
for hook in legacy:
del hook['id']
assert hook == {
'language': 'system',
'name': 'upgrade-your-pre-commit-version',
'entry': 'upgrade-your-pre-commit-version',
'files': '',
'minimum_pre_commit_version': '0.15.0',
}
# Each hook should require a new version if it uses types
for hook in hooks:
if 'types' in hook:
assert hook['minimum_pre_commit_version'] == '0.15.0'

View file

@ -17,35 +17,41 @@ def test_parse_indent():
parse_indent('-2')
@pytest.mark.parametrize(('filename', 'expected_retval'), (
('not_pretty_formatted_json.json', 1),
('unsorted_pretty_formatted_json.json', 1),
('non_ascii_pretty_formatted_json.json', 1),
('pretty_formatted_json.json', 0),
))
@pytest.mark.parametrize(
('filename', 'expected_retval'), (
('not_pretty_formatted_json.json', 1),
('unsorted_pretty_formatted_json.json', 1),
('non_ascii_pretty_formatted_json.json', 1),
('pretty_formatted_json.json', 0),
),
)
def test_pretty_format_json(filename, expected_retval):
ret = pretty_format_json([get_resource_path(filename)])
assert ret == expected_retval
@pytest.mark.parametrize(('filename', 'expected_retval'), (
('not_pretty_formatted_json.json', 1),
('unsorted_pretty_formatted_json.json', 0),
('non_ascii_pretty_formatted_json.json', 1),
('pretty_formatted_json.json', 0),
))
@pytest.mark.parametrize(
('filename', 'expected_retval'), (
('not_pretty_formatted_json.json', 1),
('unsorted_pretty_formatted_json.json', 0),
('non_ascii_pretty_formatted_json.json', 1),
('pretty_formatted_json.json', 0),
),
)
def test_unsorted_pretty_format_json(filename, expected_retval):
ret = pretty_format_json(['--no-sort-keys', get_resource_path(filename)])
assert ret == expected_retval
@pytest.mark.parametrize(('filename', 'expected_retval'), (
('not_pretty_formatted_json.json', 1),
('unsorted_pretty_formatted_json.json', 1),
('non_ascii_pretty_formatted_json.json', 1),
('pretty_formatted_json.json', 1),
('tab_pretty_formatted_json.json', 0),
))
@pytest.mark.parametrize(
('filename', 'expected_retval'), (
('not_pretty_formatted_json.json', 1),
('unsorted_pretty_formatted_json.json', 1),
('non_ascii_pretty_formatted_json.json', 1),
('pretty_formatted_json.json', 1),
('tab_pretty_formatted_json.json', 0),
),
)
def test_tab_pretty_format_json(filename, expected_retval):
ret = pretty_format_json(['--indent', '\t', get_resource_path(filename)])
assert ret == expected_retval

View file

@ -1,31 +1,41 @@
import pytest
from pre_commit_hooks.requirements_txt_fixer import FAIL
from pre_commit_hooks.requirements_txt_fixer import fix_requirements_txt
from pre_commit_hooks.requirements_txt_fixer import PASS
from pre_commit_hooks.requirements_txt_fixer import Requirement
# Input, expected return value, expected output
TESTS = (
(b'foo\nbar\n', 1, b'bar\nfoo\n'),
(b'bar\nfoo\n', 0, b'bar\nfoo\n'),
(b'#comment1\nfoo\n#comment2\nbar\n', 1, b'#comment2\nbar\n#comment1\nfoo\n'),
(b'#comment1\nbar\n#comment2\nfoo\n', 0, b'#comment1\nbar\n#comment2\nfoo\n'),
(b'#comment\n\nfoo\nbar\n', 1, b'#comment\n\nbar\nfoo\n'),
(b'#comment\n\nbar\nfoo\n', 0, b'#comment\n\nbar\nfoo\n'),
(b'\nfoo\nbar\n', 1, b'bar\n\nfoo\n'),
(b'\nbar\nfoo\n', 0, b'\nbar\nfoo\n'),
(b'pyramid==1\npyramid-foo==2\n', 0, b'pyramid==1\npyramid-foo==2\n'),
(b'ocflib\nDjango\nPyMySQL\n', 1, b'Django\nocflib\nPyMySQL\n'),
(b'-e git+ssh://git_url@tag#egg=ocflib\nDjango\nPyMySQL\n', 1, b'Django\n-e git+ssh://git_url@tag#egg=ocflib\nPyMySQL\n'),
@pytest.mark.parametrize(
('input_s', 'expected_retval', 'output'),
(
(b'', PASS, b''),
(b'\n', PASS, b'\n'),
(b'foo\nbar\n', FAIL, b'bar\nfoo\n'),
(b'bar\nfoo\n', PASS, b'bar\nfoo\n'),
(b'#comment1\nfoo\n#comment2\nbar\n', FAIL, b'#comment2\nbar\n#comment1\nfoo\n'),
(b'#comment1\nbar\n#comment2\nfoo\n', PASS, b'#comment1\nbar\n#comment2\nfoo\n'),
(b'#comment\n\nfoo\nbar\n', FAIL, b'#comment\n\nbar\nfoo\n'),
(b'#comment\n\nbar\nfoo\n', PASS, b'#comment\n\nbar\nfoo\n'),
(b'\nfoo\nbar\n', FAIL, b'bar\n\nfoo\n'),
(b'\nbar\nfoo\n', PASS, b'\nbar\nfoo\n'),
(b'pyramid==1\npyramid-foo==2\n', PASS, b'pyramid==1\npyramid-foo==2\n'),
(b'ocflib\nDjango\nPyMySQL\n', FAIL, b'Django\nocflib\nPyMySQL\n'),
(
b'-e git+ssh://git_url@tag#egg=ocflib\nDjango\nPyMySQL\n',
FAIL,
b'Django\n-e git+ssh://git_url@tag#egg=ocflib\nPyMySQL\n',
),
),
)
@pytest.mark.parametrize(('input_s', 'expected_retval', 'output'), TESTS)
def test_integration(input_s, expected_retval, output, tmpdir):
path = tmpdir.join('file.txt')
path.write_binary(input_s)
assert fix_requirements_txt([path.strpath]) == expected_retval
output_retval = fix_requirements_txt([path.strpath])
assert path.read_binary() == output
assert output_retval == expected_retval
def test_requirement_object():

View file

@ -0,0 +1,120 @@
from __future__ import absolute_import
from __future__ import unicode_literals
import os
import pytest
from pre_commit_hooks.sort_simple_yaml import first_key
from pre_commit_hooks.sort_simple_yaml import main
from pre_commit_hooks.sort_simple_yaml import parse_block
from pre_commit_hooks.sort_simple_yaml import parse_blocks
from pre_commit_hooks.sort_simple_yaml import sort
RETVAL_GOOD = 0
RETVAL_BAD = 1
TEST_SORTS = [
(
['c: true', '', 'b: 42', 'a: 19'],
['b: 42', 'a: 19', '', 'c: true'],
RETVAL_BAD,
),
(
['# i am', '# a header', '', 'c: true', '', 'b: 42', 'a: 19'],
['# i am', '# a header', '', 'b: 42', 'a: 19', '', 'c: true'],
RETVAL_BAD,
),
(
['# i am', '# a header', '', 'already: sorted', '', 'yup: i am'],
['# i am', '# a header', '', 'already: sorted', '', 'yup: i am'],
RETVAL_GOOD,
),
(
['# i am', '# a header'],
['# i am', '# a header'],
RETVAL_GOOD,
),
]
@pytest.mark.parametrize('bad_lines,good_lines,retval', TEST_SORTS)
def test_integration_good_bad_lines(tmpdir, bad_lines, good_lines, retval):
file_path = os.path.join(tmpdir.strpath, 'foo.yaml')
with open(file_path, 'w') as f:
f.write("\n".join(bad_lines) + "\n")
assert main([file_path]) == retval
with open(file_path, 'r') as f:
assert [line.rstrip() for line in f.readlines()] == good_lines
def test_parse_header():
lines = ['# some header', '# is here', '', 'this is not a header']
assert parse_block(lines, header=True) == ['# some header', '# is here']
assert lines == ['', 'this is not a header']
lines = ['this is not a header']
assert parse_block(lines, header=True) == []
assert lines == ['this is not a header']
def test_parse_block():
# a normal block
lines = ['a: 42', 'b: 17', '', 'c: 19']
assert parse_block(lines) == ['a: 42', 'b: 17']
assert lines == ['', 'c: 19']
# a block at the end
lines = ['c: 19']
assert parse_block(lines) == ['c: 19']
assert lines == []
# no block
lines = []
assert parse_block(lines) == []
assert lines == []
def test_parse_blocks():
# normal blocks
lines = ['a: 42', 'b: 17', '', 'c: 19']
assert parse_blocks(lines) == [['a: 42', 'b: 17'], ['c: 19']]
assert lines == []
# a single block
lines = ['a: 42', 'b: 17']
assert parse_blocks(lines) == [['a: 42', 'b: 17']]
assert lines == []
# no blocks
lines = []
assert parse_blocks(lines) == []
assert lines == []
def test_first_key():
# first line
lines = ['a: 42', 'b: 17', '', 'c: 19']
assert first_key(lines) == 'a: 42'
# second line
lines = ['# some comment', 'a: 42', 'b: 17', '', 'c: 19']
assert first_key(lines) == 'a: 42'
# second line with quotes
lines = ['# some comment', '"a": 42', 'b: 17', '', 'c: 19']
assert first_key(lines) == 'a": 42'
# no lines
lines = []
assert first_key(lines) is None
@pytest.mark.parametrize('bad_lines,good_lines,_', TEST_SORTS)
def test_sort(bad_lines, good_lines, _):
assert sort(bad_lines) == good_lines

View file

@ -22,16 +22,20 @@ TESTS = (
# Docstring
('""" Foo """', '""" Foo """', 0),
(
textwrap.dedent("""
textwrap.dedent(
"""
x = " \\
foo \\
"\n
"""),
textwrap.dedent("""
""",
),
textwrap.dedent(
"""
x = ' \\
foo \\
'\n
"""),
""",
),
1,
),
('"foo""bar"', "'foo''bar'", 1),

View file

@ -69,7 +69,7 @@ def test_markdown_linebreak_ext_opt(filename, input_s, output, tmpdir):
path = tmpdir.join(filename)
path.write(input_s)
ret = fix_trailing_whitespace((
'--markdown-linebreak-ext=TxT', path.strpath
'--markdown-linebreak-ext=TxT', path.strpath,
))
assert ret == 1
assert path.read() == output