X-Git-Url: https://git.madduck.net/etc/vim.git/blobdiff_plain/06e95b1e9bcd43c4574840f8174ba4b2c5d281bd..f3ea58cbc7750edd154bf4f97272c6ecf8f77d13:/tests/test_black.py?ds=sidebyside diff --git a/tests/test_black.py b/tests/test_black.py index 6f0ffa3..e98f019 100644 --- a/tests/test_black.py +++ b/tests/test_black.py @@ -3,9 +3,10 @@ import asyncio from concurrent.futures import ThreadPoolExecutor from contextlib import contextmanager from functools import partial -from io import StringIO +from io import BytesIO, TextIOWrapper import os from pathlib import Path +import re import sys from tempfile import TemporaryDirectory from typing import Any, List, Tuple, Iterator @@ -17,6 +18,7 @@ from click.testing import CliRunner import black + ll = 88 ff = partial(black.format_file_in_place, line_length=ll, fast=True) fs = partial(black.format_str, line_length=ll) @@ -31,7 +33,7 @@ def dump_to_stderr(*output: str) -> str: def read_data(name: str) -> Tuple[str, str]: """read_data('test_name') -> 'input', 'output'""" - if not name.endswith((".py", ".out", ".diff")): + if not name.endswith((".py", ".pyi", ".out", ".diff")): name += ".py" _input: List[str] = [] _output: List[str] = [] @@ -57,8 +59,7 @@ def cache_dir(exists: bool = True) -> Iterator[Path]: cache_dir = Path(workspace) if not exists: cache_dir = cache_dir / "new" - cache_file = cache_dir / "cache.pkl" - with patch("black.CACHE_DIR", cache_dir), patch("black.CACHE_FILE", cache_file): + with patch("black.CACHE_DIR", cache_dir): yield cache_dir @@ -99,6 +100,25 @@ class BlackTestCase(unittest.TestCase): black.err(str(ve)) self.assertEqual(expected, actual) + @patch("black.dump_to_file", dump_to_stderr) + def test_empty(self) -> None: + source = expected = "" + actual = fs(source) + self.assertFormatEqual(expected, actual) + black.assert_equivalent(source, actual) + black.assert_stable(source, actual, line_length=ll) + + def test_empty_ff(self) -> None: + expected = "" + tmp_file = Path(black.dump_to_file()) + try: + self.assertFalse(ff(tmp_file, write_back=black.WriteBack.YES)) + with open(tmp_file, encoding="utf8") as f: + actual = f.read() + finally: + os.unlink(tmp_file) + self.assertFormatEqual(expected, actual) + @patch("black.dump_to_file", dump_to_stderr) def test_self(self) -> None: source, expected = read_data("test_black") @@ -121,8 +141,9 @@ class BlackTestCase(unittest.TestCase): source, expected = read_data("../black") hold_stdin, hold_stdout = sys.stdin, sys.stdout try: - sys.stdin, sys.stdout = StringIO(source), StringIO() - sys.stdin.name = "" + sys.stdin = TextIOWrapper(BytesIO(source.encode("utf8")), encoding="utf8") + sys.stdout = TextIOWrapper(BytesIO(), encoding="utf8") + sys.stdin.buffer.name = "" # type: ignore black.format_stdin_to_stdout( line_length=ll, fast=True, write_back=black.WriteBack.YES ) @@ -135,17 +156,22 @@ class BlackTestCase(unittest.TestCase): black.assert_stable(source, actual, line_length=ll) def test_piping_diff(self) -> None: + diff_header = re.compile( + rf"(STDIN|STDOUT)\t\d\d\d\d-\d\d-\d\d " + rf"\d\d:\d\d:\d\d\.\d\d\d\d\d\d \+\d\d\d\d" + ) source, _ = read_data("expression.py") expected, _ = read_data("expression.diff") hold_stdin, hold_stdout = sys.stdin, sys.stdout try: - sys.stdin, sys.stdout = StringIO(source), StringIO() - sys.stdin.name = "" + sys.stdin = TextIOWrapper(BytesIO(source.encode("utf8")), encoding="utf8") + sys.stdout = TextIOWrapper(BytesIO(), encoding="utf8") black.format_stdin_to_stdout( line_length=ll, fast=True, write_back=black.WriteBack.DIFF ) sys.stdout.seek(0) actual = sys.stdout.read() + actual = diff_header.sub("[Deterministic header]", actual) finally: sys.stdin, sys.stdout = hold_stdin, hold_stdout actual = actual.rstrip() + "\n" # the diff output has a trailing space @@ -168,6 +194,14 @@ class BlackTestCase(unittest.TestCase): black.assert_equivalent(source, actual) black.assert_stable(source, actual, line_length=ll) + @patch("black.dump_to_file", dump_to_stderr) + def test_function2(self) -> None: + source, expected = read_data("function2") + actual = fs(source) + self.assertFormatEqual(expected, actual) + black.assert_equivalent(source, actual) + black.assert_stable(source, actual, line_length=ll) + @patch("black.dump_to_file", dump_to_stderr) def test_expression(self) -> None: source, expected = read_data("expression") @@ -194,13 +228,17 @@ class BlackTestCase(unittest.TestCase): source, _ = read_data("expression.py") expected, _ = read_data("expression.diff") tmp_file = Path(black.dump_to_file(source)) + diff_header = re.compile( + rf"{re.escape(str(tmp_file))}\t\d\d\d\d-\d\d-\d\d " + rf"\d\d:\d\d:\d\d\.\d\d\d\d\d\d \+\d\d\d\d" + ) hold_stdout = sys.stdout try: - sys.stdout = StringIO() + sys.stdout = TextIOWrapper(BytesIO(), encoding="utf8") self.assertTrue(ff(tmp_file, write_back=black.WriteBack.DIFF)) sys.stdout.seek(0) actual = sys.stdout.read() - actual = actual.replace(str(tmp_file), "") + actual = diff_header.sub("[Deterministic header]", actual) finally: sys.stdout = hold_stdout os.unlink(tmp_file) @@ -229,6 +267,19 @@ class BlackTestCase(unittest.TestCase): self.assertFormatEqual(expected, actual) black.assert_equivalent(source, actual) black.assert_stable(source, actual, line_length=ll) + mode = black.FileMode.NO_STRING_NORMALIZATION + not_normalized = fs(source, mode=mode) + self.assertFormatEqual(source, not_normalized) + black.assert_equivalent(source, not_normalized) + black.assert_stable(source, not_normalized, line_length=ll, mode=mode) + + @patch("black.dump_to_file", dump_to_stderr) + def test_slices(self) -> None: + source, expected = read_data("slices") + actual = fs(source) + self.assertFormatEqual(expected, actual) + black.assert_equivalent(source, actual) + black.assert_stable(source, actual, line_length=ll) @patch("black.dump_to_file", dump_to_stderr) def test_comments(self) -> None: @@ -262,6 +313,14 @@ class BlackTestCase(unittest.TestCase): black.assert_equivalent(source, actual) black.assert_stable(source, actual, line_length=ll) + @patch("black.dump_to_file", dump_to_stderr) + def test_comments5(self) -> None: + source, expected = read_data("comments5") + actual = fs(source) + self.assertFormatEqual(expected, actual) + black.assert_equivalent(source, actual) + black.assert_stable(source, actual, line_length=ll) + @patch("black.dump_to_file", dump_to_stderr) def test_cantfit(self) -> None: source, expected = read_data("cantfit") @@ -294,6 +353,14 @@ class BlackTestCase(unittest.TestCase): black.assert_equivalent(source, actual) black.assert_stable(source, actual, line_length=ll) + @patch("black.dump_to_file", dump_to_stderr) + def test_string_prefixes(self) -> None: + source, expected = read_data("string_prefixes") + actual = fs(source) + self.assertFormatEqual(expected, actual) + black.assert_equivalent(source, actual) + black.assert_stable(source, actual, line_length=ll) + @patch("black.dump_to_file", dump_to_stderr) def test_python2(self) -> None: source, expected = read_data("python2") @@ -302,6 +369,21 @@ class BlackTestCase(unittest.TestCase): # black.assert_equivalent(source, actual) black.assert_stable(source, actual, line_length=ll) + @patch("black.dump_to_file", dump_to_stderr) + def test_python2_unicode_literals(self) -> None: + source, expected = read_data("python2_unicode_literals") + actual = fs(source) + self.assertFormatEqual(expected, actual) + black.assert_stable(source, actual, line_length=ll) + + @patch("black.dump_to_file", dump_to_stderr) + def test_stub(self) -> None: + mode = black.FileMode.PYI + source, expected = read_data("stub.pyi") + actual = fs(source, mode=mode) + self.assertFormatEqual(expected, actual) + black.assert_stable(source, actual, line_length=ll, mode=mode) + @patch("black.dump_to_file", dump_to_stderr) def test_fmtonoff(self) -> None: source, expected = read_data("fmtonoff") @@ -310,8 +392,24 @@ class BlackTestCase(unittest.TestCase): black.assert_equivalent(source, actual) black.assert_stable(source, actual, line_length=ll) - def test_report(self) -> None: - report = black.Report() + @patch("black.dump_to_file", dump_to_stderr) + def test_remove_empty_parentheses_after_class(self) -> None: + source, expected = read_data("class_blank_parentheses") + actual = fs(source) + self.assertFormatEqual(expected, actual) + black.assert_equivalent(source, actual) + black.assert_stable(source, actual, line_length=ll) + + @patch("black.dump_to_file", dump_to_stderr) + def test_new_line_between_class_and_code(self) -> None: + source, expected = read_data("class_methods_new_line") + actual = fs(source) + self.assertFormatEqual(expected, actual) + black.assert_equivalent(source, actual) + black.assert_stable(source, actual, line_length=ll) + + def test_report_verbose(self) -> None: + report = black.Report(verbose=True) out_lines = [] err_lines = [] @@ -378,9 +476,19 @@ class BlackTestCase(unittest.TestCase): "2 files failed to reformat.", ) self.assertEqual(report.return_code, 123) - report.done(Path("f4"), black.Changed.NO) + report.path_ignored(Path("wat"), "no match") self.assertEqual(len(out_lines), 5) self.assertEqual(len(err_lines), 2) + self.assertEqual(out_lines[-1], "wat ignored: no match") + self.assertEqual( + unstyle(str(report)), + "2 files reformatted, 2 files left unchanged, " + "2 files failed to reformat.", + ) + self.assertEqual(report.return_code, 123) + report.done(Path("f4"), black.Changed.NO) + self.assertEqual(len(out_lines), 6) + self.assertEqual(len(err_lines), 2) self.assertEqual(out_lines[-1], "f4 already well formatted, good job.") self.assertEqual( unstyle(str(report)), @@ -395,6 +503,183 @@ class BlackTestCase(unittest.TestCase): "2 files would fail to reformat.", ) + def test_report_quiet(self) -> None: + report = black.Report(quiet=True) + out_lines = [] + err_lines = [] + + def out(msg: str, **kwargs: Any) -> None: + out_lines.append(msg) + + def err(msg: str, **kwargs: Any) -> None: + err_lines.append(msg) + + with patch("black.out", out), patch("black.err", err): + report.done(Path("f1"), black.Changed.NO) + self.assertEqual(len(out_lines), 0) + self.assertEqual(len(err_lines), 0) + self.assertEqual(unstyle(str(report)), "1 file left unchanged.") + self.assertEqual(report.return_code, 0) + report.done(Path("f2"), black.Changed.YES) + self.assertEqual(len(out_lines), 0) + self.assertEqual(len(err_lines), 0) + self.assertEqual( + unstyle(str(report)), "1 file reformatted, 1 file left unchanged." + ) + report.done(Path("f3"), black.Changed.CACHED) + self.assertEqual(len(out_lines), 0) + self.assertEqual(len(err_lines), 0) + self.assertEqual( + unstyle(str(report)), "1 file reformatted, 2 files left unchanged." + ) + self.assertEqual(report.return_code, 0) + report.check = True + self.assertEqual(report.return_code, 1) + report.check = False + report.failed(Path("e1"), "boom") + self.assertEqual(len(out_lines), 0) + self.assertEqual(len(err_lines), 1) + self.assertEqual(err_lines[-1], "error: cannot format e1: boom") + self.assertEqual( + unstyle(str(report)), + "1 file reformatted, 2 files left unchanged, " + "1 file failed to reformat.", + ) + self.assertEqual(report.return_code, 123) + report.done(Path("f3"), black.Changed.YES) + self.assertEqual(len(out_lines), 0) + self.assertEqual(len(err_lines), 1) + self.assertEqual( + unstyle(str(report)), + "2 files reformatted, 2 files left unchanged, " + "1 file failed to reformat.", + ) + self.assertEqual(report.return_code, 123) + report.failed(Path("e2"), "boom") + self.assertEqual(len(out_lines), 0) + self.assertEqual(len(err_lines), 2) + self.assertEqual(err_lines[-1], "error: cannot format e2: boom") + self.assertEqual( + unstyle(str(report)), + "2 files reformatted, 2 files left unchanged, " + "2 files failed to reformat.", + ) + self.assertEqual(report.return_code, 123) + report.path_ignored(Path("wat"), "no match") + self.assertEqual(len(out_lines), 0) + self.assertEqual(len(err_lines), 2) + self.assertEqual( + unstyle(str(report)), + "2 files reformatted, 2 files left unchanged, " + "2 files failed to reformat.", + ) + self.assertEqual(report.return_code, 123) + report.done(Path("f4"), black.Changed.NO) + self.assertEqual(len(out_lines), 0) + self.assertEqual(len(err_lines), 2) + self.assertEqual( + unstyle(str(report)), + "2 files reformatted, 3 files left unchanged, " + "2 files failed to reformat.", + ) + self.assertEqual(report.return_code, 123) + report.check = True + self.assertEqual( + unstyle(str(report)), + "2 files would be reformatted, 3 files would be left unchanged, " + "2 files would fail to reformat.", + ) + + def test_report_normal(self) -> None: + report = black.Report() + out_lines = [] + err_lines = [] + + def out(msg: str, **kwargs: Any) -> None: + out_lines.append(msg) + + def err(msg: str, **kwargs: Any) -> None: + err_lines.append(msg) + + with patch("black.out", out), patch("black.err", err): + report.done(Path("f1"), black.Changed.NO) + self.assertEqual(len(out_lines), 0) + self.assertEqual(len(err_lines), 0) + self.assertEqual(unstyle(str(report)), "1 file left unchanged.") + self.assertEqual(report.return_code, 0) + report.done(Path("f2"), black.Changed.YES) + self.assertEqual(len(out_lines), 1) + self.assertEqual(len(err_lines), 0) + self.assertEqual(out_lines[-1], "reformatted f2") + self.assertEqual( + unstyle(str(report)), "1 file reformatted, 1 file left unchanged." + ) + report.done(Path("f3"), black.Changed.CACHED) + self.assertEqual(len(out_lines), 1) + self.assertEqual(len(err_lines), 0) + self.assertEqual(out_lines[-1], "reformatted f2") + self.assertEqual( + unstyle(str(report)), "1 file reformatted, 2 files left unchanged." + ) + self.assertEqual(report.return_code, 0) + report.check = True + self.assertEqual(report.return_code, 1) + report.check = False + report.failed(Path("e1"), "boom") + self.assertEqual(len(out_lines), 1) + self.assertEqual(len(err_lines), 1) + self.assertEqual(err_lines[-1], "error: cannot format e1: boom") + self.assertEqual( + unstyle(str(report)), + "1 file reformatted, 2 files left unchanged, " + "1 file failed to reformat.", + ) + self.assertEqual(report.return_code, 123) + report.done(Path("f3"), black.Changed.YES) + self.assertEqual(len(out_lines), 2) + self.assertEqual(len(err_lines), 1) + self.assertEqual(out_lines[-1], "reformatted f3") + self.assertEqual( + unstyle(str(report)), + "2 files reformatted, 2 files left unchanged, " + "1 file failed to reformat.", + ) + self.assertEqual(report.return_code, 123) + report.failed(Path("e2"), "boom") + self.assertEqual(len(out_lines), 2) + self.assertEqual(len(err_lines), 2) + self.assertEqual(err_lines[-1], "error: cannot format e2: boom") + self.assertEqual( + unstyle(str(report)), + "2 files reformatted, 2 files left unchanged, " + "2 files failed to reformat.", + ) + self.assertEqual(report.return_code, 123) + report.path_ignored(Path("wat"), "no match") + self.assertEqual(len(out_lines), 2) + self.assertEqual(len(err_lines), 2) + self.assertEqual( + unstyle(str(report)), + "2 files reformatted, 2 files left unchanged, " + "2 files failed to reformat.", + ) + self.assertEqual(report.return_code, 123) + report.done(Path("f4"), black.Changed.NO) + self.assertEqual(len(out_lines), 2) + self.assertEqual(len(err_lines), 2) + self.assertEqual( + unstyle(str(report)), + "2 files reformatted, 3 files left unchanged, " + "2 files failed to reformat.", + ) + self.assertEqual(report.return_code, 123) + report.check = True + self.assertEqual( + unstyle(str(report)), + "2 files would be reformatted, 3 files would be left unchanged, " + "2 files would fail to reformat.", + ) + def test_is_python36(self) -> None: node = black.lib2to3_parse("def f(*, arg): ...\n") self.assertFalse(black.is_python36(node)) @@ -413,6 +698,28 @@ class BlackTestCase(unittest.TestCase): node = black.lib2to3_parse(expected) self.assertFalse(black.is_python36(node)) + def test_get_future_imports(self) -> None: + node = black.lib2to3_parse("\n") + self.assertEqual(set(), black.get_future_imports(node)) + node = black.lib2to3_parse("from __future__ import black\n") + self.assertEqual({"black"}, black.get_future_imports(node)) + node = black.lib2to3_parse("from __future__ import multiple, imports\n") + self.assertEqual({"multiple", "imports"}, black.get_future_imports(node)) + node = black.lib2to3_parse("from __future__ import (parenthesized, imports)\n") + self.assertEqual({"parenthesized", "imports"}, black.get_future_imports(node)) + node = black.lib2to3_parse( + "from __future__ import multiple\nfrom __future__ import imports\n" + ) + self.assertEqual({"multiple", "imports"}, black.get_future_imports(node)) + node = black.lib2to3_parse("# comment\nfrom __future__ import black\n") + self.assertEqual({"black"}, black.get_future_imports(node)) + node = black.lib2to3_parse('"""docstring"""\nfrom __future__ import black\n') + self.assertEqual({"black"}, black.get_future_imports(node)) + node = black.lib2to3_parse("some(other, code)\nfrom __future__ import black\n") + self.assertEqual(set(), black.get_future_imports(node)) + node = black.lib2to3_parse("from some.module import black\n") + self.assertEqual(set(), black.get_future_imports(node)) + def test_debug_visitor(self) -> None: source, _ = read_data("debug_visitor.py") expected, _ = read_data("debug_visitor.out") @@ -483,24 +790,27 @@ class BlackTestCase(unittest.TestCase): self.assertEqual("".join(err_lines), "") def test_cache_broken_file(self) -> None: + mode = black.FileMode.AUTO_DETECT with cache_dir() as workspace: - with black.CACHE_FILE.open("w") as fobj: + cache_file = black.get_cache_file(black.DEFAULT_LINE_LENGTH, mode) + with cache_file.open("w") as fobj: fobj.write("this is not a pickle") - self.assertEqual(black.read_cache(), {}) + self.assertEqual(black.read_cache(black.DEFAULT_LINE_LENGTH, mode), {}) src = (workspace / "test.py").resolve() with src.open("w") as fobj: fobj.write("print('hello')") result = CliRunner().invoke(black.main, [str(src)]) self.assertEqual(result.exit_code, 0) - cache = black.read_cache() + cache = black.read_cache(black.DEFAULT_LINE_LENGTH, mode) self.assertIn(src, cache) def test_cache_single_file_already_cached(self) -> None: + mode = black.FileMode.AUTO_DETECT with cache_dir() as workspace: src = (workspace / "test.py").resolve() with src.open("w") as fobj: fobj.write("print('hello')") - black.write_cache({}, [src]) + black.write_cache({}, [src], black.DEFAULT_LINE_LENGTH, mode) result = CliRunner().invoke(black.main, [str(src)]) self.assertEqual(result.exit_code, 0) with src.open("r") as fobj: @@ -508,6 +818,7 @@ class BlackTestCase(unittest.TestCase): @event_loop(close=False) def test_cache_multiple_files(self) -> None: + mode = black.FileMode.AUTO_DETECT with cache_dir() as workspace, patch( "black.ProcessPoolExecutor", new=ThreadPoolExecutor ): @@ -517,42 +828,48 @@ class BlackTestCase(unittest.TestCase): two = (workspace / "two.py").resolve() with two.open("w") as fobj: fobj.write("print('hello')") - black.write_cache({}, [one]) + black.write_cache({}, [one], black.DEFAULT_LINE_LENGTH, mode) result = CliRunner().invoke(black.main, [str(workspace)]) self.assertEqual(result.exit_code, 0) with one.open("r") as fobj: self.assertEqual(fobj.read(), "print('hello')") with two.open("r") as fobj: self.assertEqual(fobj.read(), 'print("hello")\n') - cache = black.read_cache() + cache = black.read_cache(black.DEFAULT_LINE_LENGTH, mode) self.assertIn(one, cache) self.assertIn(two, cache) def test_no_cache_when_writeback_diff(self) -> None: + mode = black.FileMode.AUTO_DETECT with cache_dir() as workspace: src = (workspace / "test.py").resolve() with src.open("w") as fobj: fobj.write("print('hello')") result = CliRunner().invoke(black.main, [str(src), "--diff"]) self.assertEqual(result.exit_code, 0) - self.assertFalse(black.CACHE_FILE.exists()) + cache_file = black.get_cache_file(black.DEFAULT_LINE_LENGTH, mode) + self.assertFalse(cache_file.exists()) def test_no_cache_when_stdin(self) -> None: + mode = black.FileMode.AUTO_DETECT with cache_dir(): result = CliRunner().invoke(black.main, ["-"], input="print('hello')") self.assertEqual(result.exit_code, 0) - self.assertFalse(black.CACHE_FILE.exists()) + cache_file = black.get_cache_file(black.DEFAULT_LINE_LENGTH, mode) + self.assertFalse(cache_file.exists()) def test_read_cache_no_cachefile(self) -> None: + mode = black.FileMode.AUTO_DETECT with cache_dir(): - self.assertEqual(black.read_cache(), {}) + self.assertEqual(black.read_cache(black.DEFAULT_LINE_LENGTH, mode), {}) def test_write_cache_read_cache(self) -> None: + mode = black.FileMode.AUTO_DETECT with cache_dir() as workspace: src = (workspace / "test.py").resolve() src.touch() - black.write_cache({}, [src]) - cache = black.read_cache() + black.write_cache({}, [src], black.DEFAULT_LINE_LENGTH, mode) + cache = black.read_cache(black.DEFAULT_LINE_LENGTH, mode) self.assertIn(src, cache) self.assertEqual(cache[src], black.get_cache_info(src)) @@ -567,19 +884,21 @@ class BlackTestCase(unittest.TestCase): cached_but_changed.touch() cache = {cached: black.get_cache_info(cached), cached_but_changed: (0.0, 0)} todo, done = black.filter_cached( - cache, [uncached, cached, cached_but_changed] + cache, {uncached, cached, cached_but_changed} ) - self.assertEqual(todo, [uncached, cached_but_changed]) - self.assertEqual(done, [cached]) + self.assertEqual(todo, {uncached, cached_but_changed}) + self.assertEqual(done, {cached}) def test_write_cache_creates_directory_if_needed(self) -> None: + mode = black.FileMode.AUTO_DETECT with cache_dir(exists=False) as workspace: self.assertFalse(workspace.exists()) - black.write_cache({}, []) + black.write_cache({}, [], black.DEFAULT_LINE_LENGTH, mode) self.assertTrue(workspace.exists()) @event_loop(close=False) def test_failed_formatting_does_not_get_cached(self) -> None: + mode = black.FileMode.AUTO_DETECT with cache_dir() as workspace, patch( "black.ProcessPoolExecutor", new=ThreadPoolExecutor ): @@ -591,15 +910,17 @@ class BlackTestCase(unittest.TestCase): fobj.write('print("hello")\n') result = CliRunner().invoke(black.main, [str(workspace)]) self.assertEqual(result.exit_code, 123) - cache = black.read_cache() + cache = black.read_cache(black.DEFAULT_LINE_LENGTH, mode) self.assertNotIn(failing, cache) self.assertIn(clean, cache) def test_write_cache_write_fail(self) -> None: + mode = black.FileMode.AUTO_DETECT with cache_dir(), patch.object(Path, "open") as mock: mock.side_effect = OSError - black.write_cache({}, []) + black.write_cache({}, [], black.DEFAULT_LINE_LENGTH, mode) + @event_loop(close=False) def test_check_diff_use_together(self) -> None: with cache_dir(): # Files which will be reformatted. @@ -616,7 +937,218 @@ class BlackTestCase(unittest.TestCase): result = CliRunner().invoke( black.main, [str(src1), str(src2), "--diff", "--check"] ) - self.assertEqual(result.exit_code, 1) + self.assertEqual(result.exit_code, 1, result.output) + + def test_no_files(self) -> None: + with cache_dir(): + # Without an argument, black exits with error code 0. + result = CliRunner().invoke(black.main, []) + self.assertEqual(result.exit_code, 0) + + def test_broken_symlink(self) -> None: + with cache_dir() as workspace: + symlink = workspace / "broken_link.py" + try: + symlink.symlink_to("nonexistent.py") + except OSError as e: + self.skipTest(f"Can't create symlinks: {e}") + result = CliRunner().invoke(black.main, [str(workspace.resolve())]) + self.assertEqual(result.exit_code, 0) + + def test_read_cache_line_lengths(self) -> None: + mode = black.FileMode.AUTO_DETECT + with cache_dir() as workspace: + path = (workspace / "file.py").resolve() + path.touch() + black.write_cache({}, [path], 1, mode) + one = black.read_cache(1, mode) + self.assertIn(path, one) + two = black.read_cache(2, mode) + self.assertNotIn(path, two) + + def test_single_file_force_pyi(self) -> None: + reg_mode = black.FileMode.AUTO_DETECT + pyi_mode = black.FileMode.PYI + contents, expected = read_data("force_pyi") + with cache_dir() as workspace: + path = (workspace / "file.py").resolve() + with open(path, "w") as fh: + fh.write(contents) + result = CliRunner().invoke(black.main, [str(path), "--pyi"]) + self.assertEqual(result.exit_code, 0) + with open(path, "r") as fh: + actual = fh.read() + # verify cache with --pyi is separate + pyi_cache = black.read_cache(black.DEFAULT_LINE_LENGTH, pyi_mode) + self.assertIn(path, pyi_cache) + normal_cache = black.read_cache(black.DEFAULT_LINE_LENGTH, reg_mode) + self.assertNotIn(path, normal_cache) + self.assertEqual(actual, expected) + + @event_loop(close=False) + def test_multi_file_force_pyi(self) -> None: + reg_mode = black.FileMode.AUTO_DETECT + pyi_mode = black.FileMode.PYI + contents, expected = read_data("force_pyi") + with cache_dir() as workspace: + paths = [ + (workspace / "file1.py").resolve(), + (workspace / "file2.py").resolve(), + ] + for path in paths: + with open(path, "w") as fh: + fh.write(contents) + result = CliRunner().invoke(black.main, [str(p) for p in paths] + ["--pyi"]) + self.assertEqual(result.exit_code, 0) + for path in paths: + with open(path, "r") as fh: + actual = fh.read() + self.assertEqual(actual, expected) + # verify cache with --pyi is separate + pyi_cache = black.read_cache(black.DEFAULT_LINE_LENGTH, pyi_mode) + normal_cache = black.read_cache(black.DEFAULT_LINE_LENGTH, reg_mode) + for path in paths: + self.assertIn(path, pyi_cache) + self.assertNotIn(path, normal_cache) + + def test_pipe_force_pyi(self) -> None: + source, expected = read_data("force_pyi") + result = CliRunner().invoke(black.main, ["-", "-q", "--pyi"], input=source) + self.assertEqual(result.exit_code, 0) + actual = result.output + self.assertFormatEqual(actual, expected) + + def test_single_file_force_py36(self) -> None: + reg_mode = black.FileMode.AUTO_DETECT + py36_mode = black.FileMode.PYTHON36 + source, expected = read_data("force_py36") + with cache_dir() as workspace: + path = (workspace / "file.py").resolve() + with open(path, "w") as fh: + fh.write(source) + result = CliRunner().invoke(black.main, [str(path), "--py36"]) + self.assertEqual(result.exit_code, 0) + with open(path, "r") as fh: + actual = fh.read() + # verify cache with --py36 is separate + py36_cache = black.read_cache(black.DEFAULT_LINE_LENGTH, py36_mode) + self.assertIn(path, py36_cache) + normal_cache = black.read_cache(black.DEFAULT_LINE_LENGTH, reg_mode) + self.assertNotIn(path, normal_cache) + self.assertEqual(actual, expected) + + @event_loop(close=False) + def test_multi_file_force_py36(self) -> None: + reg_mode = black.FileMode.AUTO_DETECT + py36_mode = black.FileMode.PYTHON36 + source, expected = read_data("force_py36") + with cache_dir() as workspace: + paths = [ + (workspace / "file1.py").resolve(), + (workspace / "file2.py").resolve(), + ] + for path in paths: + with open(path, "w") as fh: + fh.write(source) + result = CliRunner().invoke( + black.main, [str(p) for p in paths] + ["--py36"] + ) + self.assertEqual(result.exit_code, 0) + for path in paths: + with open(path, "r") as fh: + actual = fh.read() + self.assertEqual(actual, expected) + # verify cache with --py36 is separate + pyi_cache = black.read_cache(black.DEFAULT_LINE_LENGTH, py36_mode) + normal_cache = black.read_cache(black.DEFAULT_LINE_LENGTH, reg_mode) + for path in paths: + self.assertIn(path, pyi_cache) + self.assertNotIn(path, normal_cache) + + def test_pipe_force_py36(self) -> None: + source, expected = read_data("force_py36") + result = CliRunner().invoke(black.main, ["-", "-q", "--py36"], input=source) + self.assertEqual(result.exit_code, 0) + actual = result.output + self.assertFormatEqual(actual, expected) + + def test_include_exclude(self) -> None: + path = THIS_DIR / "include_exclude_tests" + include = re.compile(r"\.pyi?$") + exclude = re.compile(r"/exclude/|/\.definitely_exclude/") + report = black.Report() + sources: List[Path] = [] + expected = [ + Path(THIS_DIR / "include_exclude_tests/b/dont_exclude/a.py"), + Path(THIS_DIR / "include_exclude_tests/b/dont_exclude/a.pyi"), + ] + this_abs = THIS_DIR.resolve() + sources.extend( + black.gen_python_files_in_dir(path, this_abs, include, exclude, report) + ) + self.assertEqual(sorted(expected), sorted(sources)) + + def test_empty_include(self) -> None: + path = THIS_DIR / "include_exclude_tests" + report = black.Report() + empty = re.compile(r"") + sources: List[Path] = [] + expected = [ + Path(path / "b/exclude/a.pie"), + Path(path / "b/exclude/a.py"), + Path(path / "b/exclude/a.pyi"), + Path(path / "b/dont_exclude/a.pie"), + Path(path / "b/dont_exclude/a.py"), + Path(path / "b/dont_exclude/a.pyi"), + Path(path / "b/.definitely_exclude/a.pie"), + Path(path / "b/.definitely_exclude/a.py"), + Path(path / "b/.definitely_exclude/a.pyi"), + ] + this_abs = THIS_DIR.resolve() + sources.extend( + black.gen_python_files_in_dir( + path, this_abs, empty, re.compile(black.DEFAULT_EXCLUDES), report + ) + ) + self.assertEqual(sorted(expected), sorted(sources)) + + def test_empty_exclude(self) -> None: + path = THIS_DIR / "include_exclude_tests" + report = black.Report() + empty = re.compile(r"") + sources: List[Path] = [] + expected = [ + Path(path / "b/dont_exclude/a.py"), + Path(path / "b/dont_exclude/a.pyi"), + Path(path / "b/exclude/a.py"), + Path(path / "b/exclude/a.pyi"), + Path(path / "b/.definitely_exclude/a.py"), + Path(path / "b/.definitely_exclude/a.pyi"), + ] + this_abs = THIS_DIR.resolve() + sources.extend( + black.gen_python_files_in_dir( + path, this_abs, re.compile(black.DEFAULT_INCLUDES), empty, report + ) + ) + self.assertEqual(sorted(expected), sorted(sources)) + + def test_invalid_include_exclude(self) -> None: + for option in ["--include", "--exclude"]: + result = CliRunner().invoke(black.main, ["-", option, "**()(!!*)"]) + self.assertEqual(result.exit_code, 2) + + def test_preserves_line_endings(self) -> None: + with TemporaryDirectory() as workspace: + test_file = Path(workspace) / "test.py" + for nl in ["\n", "\r\n"]: + contents = nl.join(["def f( ):", " pass"]) + test_file.write_bytes(contents.encode()) + ff(test_file, write_back=black.WriteBack.YES) + updated_contents: bytes = test_file.read_bytes() + self.assertIn(nl.encode(), updated_contents) # type: ignore + if nl == "\n": + self.assertNotIn(b"\r\n", updated_contents) # type: ignore if __name__ == "__main__":