X-Git-Url: https://git.madduck.net/etc/vim.git/blobdiff_plain/66aa676278948368dff251dffd58c850cb8b889e..d00eac9944acdafe2fb61a61c7e763c6502e89d5:/tests/test_black.py?ds=inline

diff --git a/tests/test_black.py b/tests/test_black.py
index 92031ca..db46499 100644
--- a/tests/test_black.py
+++ b/tests/test_black.py
@@ -1,5 +1,6 @@
 #!/usr/bin/env python3
 import asyncio
+import logging
 from concurrent.futures import ThreadPoolExecutor
 from contextlib import contextmanager, redirect_stderr
 from functools import partial, wraps
@@ -27,6 +28,7 @@ from click import unstyle
 from click.testing import CliRunner
 
 import black
+from black import Feature
 
 try:
     import blackd
@@ -36,13 +38,14 @@ except ImportError:
 else:
     has_blackd_deps = True
 
-
-ll = 88
-ff = partial(black.format_file_in_place, line_length=ll, fast=True)
-fs = partial(black.format_str, line_length=ll)
+ff = partial(black.format_file_in_place, mode=black.FileMode(), fast=True)
+fs = partial(black.format_str, mode=black.FileMode())
 THIS_FILE = Path(__file__)
 THIS_DIR = THIS_FILE.parent
 EMPTY_LINE = "# EMPTY LINE WITH WHITESPACE" + " (this comment will be removed)"
+PY36_ARGS = [
+    f"--target-version={version.name.lower()}" for version in black.PY36_VERSIONS
+]
 T = TypeVar("T")
 R = TypeVar("R")
 
@@ -155,13 +158,22 @@ class BlackTestCase(unittest.TestCase):
                 black.err(str(ve))
         self.assertEqual(expected, actual)
 
+    def invokeBlack(
+        self, args: List[str], exit_code: int = 0, ignore_config: bool = True
+    ) -> None:
+        runner = BlackRunner()
+        if ignore_config:
+            args = ["--config", str(THIS_DIR / "empty.toml"), *args]
+        result = runner.invoke(black.main, args)
+        self.assertEqual(result.exit_code, exit_code, msg=runner.stderr_bytes.decode())
+
     @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)
+        black.assert_stable(source, actual, black.FileMode())
 
     def test_empty_ff(self) -> None:
         expected = ""
@@ -180,7 +192,7 @@ class BlackTestCase(unittest.TestCase):
         actual = fs(source)
         self.assertFormatEqual(expected, actual)
         black.assert_equivalent(source, actual)
-        black.assert_stable(source, actual, line_length=ll)
+        black.assert_stable(source, actual, black.FileMode())
         self.assertFalse(ff(THIS_FILE))
 
     @patch("black.dump_to_file", dump_to_stderr)
@@ -189,20 +201,20 @@ class BlackTestCase(unittest.TestCase):
         actual = fs(source)
         self.assertFormatEqual(expected, actual)
         black.assert_equivalent(source, actual)
-        black.assert_stable(source, actual, line_length=ll)
+        black.assert_stable(source, actual, black.FileMode())
         self.assertFalse(ff(THIS_DIR / ".." / "black.py"))
 
     def test_piping(self) -> None:
         source, expected = read_data("../black", data=False)
         result = BlackRunner().invoke(
             black.main,
-            ["-", "--fast", f"--line-length={ll}"],
+            ["-", "--fast", f"--line-length={black.DEFAULT_LINE_LENGTH}"],
             input=BytesIO(source.encode("utf8")),
         )
         self.assertEqual(result.exit_code, 0)
         self.assertFormatEqual(expected, result.output)
         black.assert_equivalent(source, result.output)
-        black.assert_stable(source, result.output, line_length=ll)
+        black.assert_stable(source, result.output, black.FileMode())
 
     def test_piping_diff(self) -> None:
         diff_header = re.compile(
@@ -212,7 +224,13 @@ class BlackTestCase(unittest.TestCase):
         source, _ = read_data("expression.py")
         expected, _ = read_data("expression.diff")
         config = THIS_DIR / "data" / "empty_pyproject.toml"
-        args = ["-", "--fast", f"--line-length={ll}", "--diff", f"--config={config}"]
+        args = [
+            "-",
+            "--fast",
+            f"--line-length={black.DEFAULT_LINE_LENGTH}",
+            "--diff",
+            f"--config={config}",
+        ]
         result = BlackRunner().invoke(
             black.main, args, input=BytesIO(source.encode("utf8"))
         )
@@ -227,7 +245,7 @@ class BlackTestCase(unittest.TestCase):
         actual = fs(source)
         self.assertFormatEqual(expected, actual)
         black.assert_equivalent(source, actual)
-        black.assert_stable(source, actual, line_length=ll)
+        black.assert_stable(source, actual, black.FileMode())
         self.assertFalse(ff(THIS_DIR / ".." / "setup.py"))
 
     @patch("black.dump_to_file", dump_to_stderr)
@@ -236,7 +254,7 @@ class BlackTestCase(unittest.TestCase):
         actual = fs(source)
         self.assertFormatEqual(expected, actual)
         black.assert_equivalent(source, actual)
-        black.assert_stable(source, actual, line_length=ll)
+        black.assert_stable(source, actual, black.FileMode())
 
     @patch("black.dump_to_file", dump_to_stderr)
     def test_function2(self) -> None:
@@ -244,7 +262,7 @@ class BlackTestCase(unittest.TestCase):
         actual = fs(source)
         self.assertFormatEqual(expected, actual)
         black.assert_equivalent(source, actual)
-        black.assert_stable(source, actual, line_length=ll)
+        black.assert_stable(source, actual, black.FileMode())
 
     @patch("black.dump_to_file", dump_to_stderr)
     def test_expression(self) -> None:
@@ -252,7 +270,7 @@ class BlackTestCase(unittest.TestCase):
         actual = fs(source)
         self.assertFormatEqual(expected, actual)
         black.assert_equivalent(source, actual)
-        black.assert_stable(source, actual, line_length=ll)
+        black.assert_stable(source, actual, black.FileMode())
 
     def test_expression_ff(self) -> None:
         source, expected = read_data("expression")
@@ -266,7 +284,7 @@ class BlackTestCase(unittest.TestCase):
         self.assertFormatEqual(expected, actual)
         with patch("black.dump_to_file", dump_to_stderr):
             black.assert_equivalent(source, actual)
-            black.assert_stable(source, actual, line_length=ll)
+            black.assert_stable(source, actual, black.FileMode())
 
     def test_expression_diff(self) -> None:
         source, _ = read_data("expression.py")
@@ -299,7 +317,7 @@ class BlackTestCase(unittest.TestCase):
         actual = fs(source)
         self.assertFormatEqual(expected, actual)
         black.assert_equivalent(source, actual)
-        black.assert_stable(source, actual, line_length=ll)
+        black.assert_stable(source, actual, black.FileMode())
 
     @patch("black.dump_to_file", dump_to_stderr)
     def test_string_quotes(self) -> None:
@@ -307,12 +325,12 @@ class BlackTestCase(unittest.TestCase):
         actual = fs(source)
         self.assertFormatEqual(expected, actual)
         black.assert_equivalent(source, actual)
-        black.assert_stable(source, actual, line_length=ll)
-        mode = black.FileMode.NO_STRING_NORMALIZATION
+        black.assert_stable(source, actual, black.FileMode())
+        mode = black.FileMode(string_normalization=False)
         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)
+        black.assert_stable(source, not_normalized, mode=mode)
 
     @patch("black.dump_to_file", dump_to_stderr)
     def test_slices(self) -> None:
@@ -320,7 +338,7 @@ class BlackTestCase(unittest.TestCase):
         actual = fs(source)
         self.assertFormatEqual(expected, actual)
         black.assert_equivalent(source, actual)
-        black.assert_stable(source, actual, line_length=ll)
+        black.assert_stable(source, actual, black.FileMode())
 
     @patch("black.dump_to_file", dump_to_stderr)
     def test_comments(self) -> None:
@@ -328,7 +346,7 @@ class BlackTestCase(unittest.TestCase):
         actual = fs(source)
         self.assertFormatEqual(expected, actual)
         black.assert_equivalent(source, actual)
-        black.assert_stable(source, actual, line_length=ll)
+        black.assert_stable(source, actual, black.FileMode())
 
     @patch("black.dump_to_file", dump_to_stderr)
     def test_comments2(self) -> None:
@@ -336,7 +354,7 @@ class BlackTestCase(unittest.TestCase):
         actual = fs(source)
         self.assertFormatEqual(expected, actual)
         black.assert_equivalent(source, actual)
-        black.assert_stable(source, actual, line_length=ll)
+        black.assert_stable(source, actual, black.FileMode())
 
     @patch("black.dump_to_file", dump_to_stderr)
     def test_comments3(self) -> None:
@@ -344,7 +362,7 @@ class BlackTestCase(unittest.TestCase):
         actual = fs(source)
         self.assertFormatEqual(expected, actual)
         black.assert_equivalent(source, actual)
-        black.assert_stable(source, actual, line_length=ll)
+        black.assert_stable(source, actual, black.FileMode())
 
     @patch("black.dump_to_file", dump_to_stderr)
     def test_comments4(self) -> None:
@@ -352,7 +370,7 @@ class BlackTestCase(unittest.TestCase):
         actual = fs(source)
         self.assertFormatEqual(expected, actual)
         black.assert_equivalent(source, actual)
-        black.assert_stable(source, actual, line_length=ll)
+        black.assert_stable(source, actual, black.FileMode())
 
     @patch("black.dump_to_file", dump_to_stderr)
     def test_comments5(self) -> None:
@@ -360,7 +378,7 @@ class BlackTestCase(unittest.TestCase):
         actual = fs(source)
         self.assertFormatEqual(expected, actual)
         black.assert_equivalent(source, actual)
-        black.assert_stable(source, actual, line_length=ll)
+        black.assert_stable(source, actual, black.FileMode())
 
     @patch("black.dump_to_file", dump_to_stderr)
     def test_comments6(self) -> None:
@@ -368,7 +386,7 @@ class BlackTestCase(unittest.TestCase):
         actual = fs(source)
         self.assertFormatEqual(expected, actual)
         black.assert_equivalent(source, actual)
-        black.assert_stable(source, actual, line_length=ll)
+        black.assert_stable(source, actual, black.FileMode())
 
     @patch("black.dump_to_file", dump_to_stderr)
     def test_cantfit(self) -> None:
@@ -376,7 +394,7 @@ class BlackTestCase(unittest.TestCase):
         actual = fs(source)
         self.assertFormatEqual(expected, actual)
         black.assert_equivalent(source, actual)
-        black.assert_stable(source, actual, line_length=ll)
+        black.assert_stable(source, actual, black.FileMode())
 
     @patch("black.dump_to_file", dump_to_stderr)
     def test_import_spacing(self) -> None:
@@ -384,7 +402,7 @@ class BlackTestCase(unittest.TestCase):
         actual = fs(source)
         self.assertFormatEqual(expected, actual)
         black.assert_equivalent(source, actual)
-        black.assert_stable(source, actual, line_length=ll)
+        black.assert_stable(source, actual, black.FileMode())
 
     @patch("black.dump_to_file", dump_to_stderr)
     def test_composition(self) -> None:
@@ -392,7 +410,7 @@ class BlackTestCase(unittest.TestCase):
         actual = fs(source)
         self.assertFormatEqual(expected, actual)
         black.assert_equivalent(source, actual)
-        black.assert_stable(source, actual, line_length=ll)
+        black.assert_stable(source, actual, black.FileMode())
 
     @patch("black.dump_to_file", dump_to_stderr)
     def test_empty_lines(self) -> None:
@@ -400,7 +418,7 @@ class BlackTestCase(unittest.TestCase):
         actual = fs(source)
         self.assertFormatEqual(expected, actual)
         black.assert_equivalent(source, actual)
-        black.assert_stable(source, actual, line_length=ll)
+        black.assert_stable(source, actual, black.FileMode())
 
     @patch("black.dump_to_file", dump_to_stderr)
     def test_string_prefixes(self) -> None:
@@ -408,33 +426,32 @@ class BlackTestCase(unittest.TestCase):
         actual = fs(source)
         self.assertFormatEqual(expected, actual)
         black.assert_equivalent(source, actual)
-        black.assert_stable(source, actual, line_length=ll)
+        black.assert_stable(source, actual, black.FileMode())
 
     @patch("black.dump_to_file", dump_to_stderr)
     def test_numeric_literals(self) -> None:
         source, expected = read_data("numeric_literals")
-        actual = fs(source, mode=black.FileMode.PYTHON36)
+        mode = black.FileMode(target_versions=black.PY36_VERSIONS)
+        actual = fs(source, mode=mode)
         self.assertFormatEqual(expected, actual)
         black.assert_equivalent(source, actual)
-        black.assert_stable(source, actual, line_length=ll)
+        black.assert_stable(source, actual, mode)
 
     @patch("black.dump_to_file", dump_to_stderr)
     def test_numeric_literals_ignoring_underscores(self) -> None:
         source, expected = read_data("numeric_literals_skip_underscores")
-        mode = (
-            black.FileMode.PYTHON36 | black.FileMode.NO_NUMERIC_UNDERSCORE_NORMALIZATION
-        )
+        mode = black.FileMode(target_versions=black.PY36_VERSIONS)
         actual = fs(source, mode=mode)
         self.assertFormatEqual(expected, actual)
         black.assert_equivalent(source, actual)
-        black.assert_stable(source, actual, line_length=ll, mode=mode)
+        black.assert_stable(source, actual, mode)
 
     @patch("black.dump_to_file", dump_to_stderr)
     def test_numeric_literals_py2(self) -> None:
         source, expected = read_data("numeric_literals_py2")
         actual = fs(source)
         self.assertFormatEqual(expected, actual)
-        black.assert_stable(source, actual, line_length=ll)
+        black.assert_stable(source, actual, black.FileMode())
 
     @patch("black.dump_to_file", dump_to_stderr)
     def test_python2(self) -> None:
@@ -442,22 +459,22 @@ class BlackTestCase(unittest.TestCase):
         actual = fs(source)
         self.assertFormatEqual(expected, actual)
         # black.assert_equivalent(source, actual)
-        black.assert_stable(source, actual, line_length=ll)
+        black.assert_stable(source, actual, black.FileMode())
 
     @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)
+        black.assert_stable(source, actual, black.FileMode())
 
     @patch("black.dump_to_file", dump_to_stderr)
     def test_stub(self) -> None:
-        mode = black.FileMode.PYI
+        mode = black.FileMode(is_pyi=True)
         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)
+        black.assert_stable(source, actual, mode)
 
     @patch("black.dump_to_file", dump_to_stderr)
     def test_python37(self) -> None:
@@ -467,7 +484,7 @@ class BlackTestCase(unittest.TestCase):
         major, minor = sys.version_info[:2]
         if major > 3 or (major == 3 and minor >= 7):
             black.assert_equivalent(source, actual)
-        black.assert_stable(source, actual, line_length=ll)
+        black.assert_stable(source, actual, black.FileMode())
 
     @patch("black.dump_to_file", dump_to_stderr)
     def test_fmtonoff(self) -> None:
@@ -475,7 +492,7 @@ class BlackTestCase(unittest.TestCase):
         actual = fs(source)
         self.assertFormatEqual(expected, actual)
         black.assert_equivalent(source, actual)
-        black.assert_stable(source, actual, line_length=ll)
+        black.assert_stable(source, actual, black.FileMode())
 
     @patch("black.dump_to_file", dump_to_stderr)
     def test_fmtonoff2(self) -> None:
@@ -483,7 +500,7 @@ class BlackTestCase(unittest.TestCase):
         actual = fs(source)
         self.assertFormatEqual(expected, actual)
         black.assert_equivalent(source, actual)
-        black.assert_stable(source, actual, line_length=ll)
+        black.assert_stable(source, actual, black.FileMode())
 
     @patch("black.dump_to_file", dump_to_stderr)
     def test_remove_empty_parentheses_after_class(self) -> None:
@@ -491,7 +508,7 @@ class BlackTestCase(unittest.TestCase):
         actual = fs(source)
         self.assertFormatEqual(expected, actual)
         black.assert_equivalent(source, actual)
-        black.assert_stable(source, actual, line_length=ll)
+        black.assert_stable(source, actual, black.FileMode())
 
     @patch("black.dump_to_file", dump_to_stderr)
     def test_new_line_between_class_and_code(self) -> None:
@@ -499,7 +516,7 @@ class BlackTestCase(unittest.TestCase):
         actual = fs(source)
         self.assertFormatEqual(expected, actual)
         black.assert_equivalent(source, actual)
-        black.assert_stable(source, actual, line_length=ll)
+        black.assert_stable(source, actual, black.FileMode())
 
     @patch("black.dump_to_file", dump_to_stderr)
     def test_bracket_match(self) -> None:
@@ -507,7 +524,7 @@ class BlackTestCase(unittest.TestCase):
         actual = fs(source)
         self.assertFormatEqual(expected, actual)
         black.assert_equivalent(source, actual)
-        black.assert_stable(source, actual, line_length=ll)
+        black.assert_stable(source, actual, black.FileMode())
 
     def test_comment_indentation(self) -> None:
         contents_tab = "if 1:\n\tif 2:\n\t\tpass\n\t# comment\n\tpass\n"
@@ -794,27 +811,31 @@ class BlackTestCase(unittest.TestCase):
                 "2 files would fail to reformat.",
             )
 
-    def test_is_python36(self) -> None:
+    def test_get_features_used(self) -> None:
         node = black.lib2to3_parse("def f(*, arg): ...\n")
-        self.assertFalse(black.is_python36(node))
+        self.assertEqual(black.get_features_used(node), set())
         node = black.lib2to3_parse("def f(*, arg,): ...\n")
-        self.assertTrue(black.is_python36(node))
+        self.assertEqual(black.get_features_used(node), {Feature.TRAILING_COMMA})
         node = black.lib2to3_parse("def f(*, arg): f'string'\n")
-        self.assertTrue(black.is_python36(node))
+        self.assertEqual(black.get_features_used(node), {Feature.F_STRINGS})
         node = black.lib2to3_parse("123_456\n")
-        self.assertTrue(black.is_python36(node))
+        self.assertEqual(black.get_features_used(node), {Feature.NUMERIC_UNDERSCORES})
         node = black.lib2to3_parse("123456\n")
-        self.assertFalse(black.is_python36(node))
+        self.assertEqual(black.get_features_used(node), set())
         source, expected = read_data("function")
         node = black.lib2to3_parse(source)
-        self.assertTrue(black.is_python36(node))
+        self.assertEqual(
+            black.get_features_used(node), {Feature.TRAILING_COMMA, Feature.F_STRINGS}
+        )
         node = black.lib2to3_parse(expected)
-        self.assertTrue(black.is_python36(node))
+        self.assertEqual(
+            black.get_features_used(node), {Feature.TRAILING_COMMA, Feature.F_STRINGS}
+        )
         source, expected = read_data("expression")
         node = black.lib2to3_parse(source)
-        self.assertFalse(black.is_python36(node))
+        self.assertEqual(black.get_features_used(node), set())
         node = black.lib2to3_parse(expected)
-        self.assertFalse(black.is_python36(node))
+        self.assertEqual(black.get_features_used(node), set())
 
     def test_get_future_imports(self) -> None:
         node = black.lib2to3_parse("\n")
@@ -872,21 +893,22 @@ class BlackTestCase(unittest.TestCase):
 
     def test_format_file_contents(self) -> None:
         empty = ""
+        mode = black.FileMode()
         with self.assertRaises(black.NothingChanged):
-            black.format_file_contents(empty, line_length=ll, fast=False)
+            black.format_file_contents(empty, mode=mode, fast=False)
         just_nl = "\n"
         with self.assertRaises(black.NothingChanged):
-            black.format_file_contents(just_nl, line_length=ll, fast=False)
+            black.format_file_contents(just_nl, mode=mode, fast=False)
         same = "l = [1, 2, 3]\n"
         with self.assertRaises(black.NothingChanged):
-            black.format_file_contents(same, line_length=ll, fast=False)
+            black.format_file_contents(same, mode=mode, fast=False)
         different = "l = [1,2,3]"
         expected = same
-        actual = black.format_file_contents(different, line_length=ll, fast=False)
+        actual = black.format_file_contents(different, mode=mode, fast=False)
         self.assertEqual(expected, actual)
         invalid = "return if you can"
         with self.assertRaises(black.InvalidInput) as e:
-            black.format_file_contents(invalid, line_length=ll, fast=False)
+            black.format_file_contents(invalid, mode=mode, fast=False)
         self.assertEqual(str(e.exception), "Cannot parse: 1:7: return if you can")
 
     def test_endmarker(self) -> None:
@@ -916,35 +938,33 @@ class BlackTestCase(unittest.TestCase):
         self.assertEqual("".join(err_lines), "")
 
     def test_cache_broken_file(self) -> None:
-        mode = black.FileMode.AUTO_DETECT
+        mode = black.FileMode()
         with cache_dir() as workspace:
-            cache_file = black.get_cache_file(black.DEFAULT_LINE_LENGTH, mode)
+            cache_file = black.get_cache_file(mode)
             with cache_file.open("w") as fobj:
                 fobj.write("this is not a pickle")
-            self.assertEqual(black.read_cache(black.DEFAULT_LINE_LENGTH, mode), {})
+            self.assertEqual(black.read_cache(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(black.DEFAULT_LINE_LENGTH, mode)
+            self.invokeBlack([str(src)])
+            cache = black.read_cache(mode)
             self.assertIn(src, cache)
 
     def test_cache_single_file_already_cached(self) -> None:
-        mode = black.FileMode.AUTO_DETECT
+        mode = black.FileMode()
         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.DEFAULT_LINE_LENGTH, mode)
-            result = CliRunner().invoke(black.main, [str(src)])
-            self.assertEqual(result.exit_code, 0)
+            black.write_cache({}, [src], mode)
+            self.invokeBlack([str(src)])
             with src.open("r") as fobj:
                 self.assertEqual(fobj.read(), "print('hello')")
 
     @event_loop(close=False)
     def test_cache_multiple_files(self) -> None:
-        mode = black.FileMode.AUTO_DETECT
+        mode = black.FileMode()
         with cache_dir() as workspace, patch(
             "black.ProcessPoolExecutor", new=ThreadPoolExecutor
         ):
@@ -954,50 +974,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.DEFAULT_LINE_LENGTH, mode)
-            result = CliRunner().invoke(black.main, [str(workspace)])
-            self.assertEqual(result.exit_code, 0)
+            black.write_cache({}, [one], mode)
+            self.invokeBlack([str(workspace)])
             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(black.DEFAULT_LINE_LENGTH, mode)
+            cache = black.read_cache(mode)
             self.assertIn(one, cache)
             self.assertIn(two, cache)
 
     def test_no_cache_when_writeback_diff(self) -> None:
-        mode = black.FileMode.AUTO_DETECT
+        mode = black.FileMode()
         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)
-            cache_file = black.get_cache_file(black.DEFAULT_LINE_LENGTH, mode)
+            self.invokeBlack([str(src), "--diff"])
+            cache_file = black.get_cache_file(mode)
             self.assertFalse(cache_file.exists())
 
     def test_no_cache_when_stdin(self) -> None:
-        mode = black.FileMode.AUTO_DETECT
+        mode = black.FileMode()
         with cache_dir():
             result = CliRunner().invoke(
                 black.main, ["-"], input=BytesIO(b"print('hello')")
             )
             self.assertEqual(result.exit_code, 0)
-            cache_file = black.get_cache_file(black.DEFAULT_LINE_LENGTH, mode)
+            cache_file = black.get_cache_file(mode)
             self.assertFalse(cache_file.exists())
 
     def test_read_cache_no_cachefile(self) -> None:
-        mode = black.FileMode.AUTO_DETECT
+        mode = black.FileMode()
         with cache_dir():
-            self.assertEqual(black.read_cache(black.DEFAULT_LINE_LENGTH, mode), {})
+            self.assertEqual(black.read_cache(mode), {})
 
     def test_write_cache_read_cache(self) -> None:
-        mode = black.FileMode.AUTO_DETECT
+        mode = black.FileMode()
         with cache_dir() as workspace:
             src = (workspace / "test.py").resolve()
             src.touch()
-            black.write_cache({}, [src], black.DEFAULT_LINE_LENGTH, mode)
-            cache = black.read_cache(black.DEFAULT_LINE_LENGTH, mode)
+            black.write_cache({}, [src], mode)
+            cache = black.read_cache(mode)
             self.assertIn(src, cache)
             self.assertEqual(cache[src], black.get_cache_info(src))
 
@@ -1018,15 +1036,15 @@ class BlackTestCase(unittest.TestCase):
             self.assertEqual(done, {cached})
 
     def test_write_cache_creates_directory_if_needed(self) -> None:
-        mode = black.FileMode.AUTO_DETECT
+        mode = black.FileMode()
         with cache_dir(exists=False) as workspace:
             self.assertFalse(workspace.exists())
-            black.write_cache({}, [], black.DEFAULT_LINE_LENGTH, mode)
+            black.write_cache({}, [], mode)
             self.assertTrue(workspace.exists())
 
     @event_loop(close=False)
     def test_failed_formatting_does_not_get_cached(self) -> None:
-        mode = black.FileMode.AUTO_DETECT
+        mode = black.FileMode()
         with cache_dir() as workspace, patch(
             "black.ProcessPoolExecutor", new=ThreadPoolExecutor
         ):
@@ -1036,40 +1054,33 @@ class BlackTestCase(unittest.TestCase):
             clean = (workspace / "clean.py").resolve()
             with clean.open("w") as fobj:
                 fobj.write('print("hello")\n')
-            result = CliRunner().invoke(black.main, [str(workspace)])
-            self.assertEqual(result.exit_code, 123)
-            cache = black.read_cache(black.DEFAULT_LINE_LENGTH, mode)
+            self.invokeBlack([str(workspace)], exit_code=123)
+            cache = black.read_cache(mode)
             self.assertNotIn(failing, cache)
             self.assertIn(clean, cache)
 
     def test_write_cache_write_fail(self) -> None:
-        mode = black.FileMode.AUTO_DETECT
+        mode = black.FileMode()
         with cache_dir(), patch.object(Path, "open") as mock:
             mock.side_effect = OSError
-            black.write_cache({}, [], black.DEFAULT_LINE_LENGTH, mode)
+            black.write_cache({}, [], mode)
 
     @event_loop(close=False)
     def test_check_diff_use_together(self) -> None:
         with cache_dir():
             # Files which will be reformatted.
             src1 = (THIS_DIR / "data" / "string_quotes.py").resolve()
-            result = CliRunner().invoke(black.main, [str(src1), "--diff", "--check"])
-            self.assertEqual(result.exit_code, 1, result.output)
+            self.invokeBlack([str(src1), "--diff", "--check"], exit_code=1)
             # Files which will not be reformatted.
             src2 = (THIS_DIR / "data" / "composition.py").resolve()
-            result = CliRunner().invoke(black.main, [str(src2), "--diff", "--check"])
-            self.assertEqual(result.exit_code, 0, result.output)
+            self.invokeBlack([str(src2), "--diff", "--check"])
             # Multi file command.
-            result = CliRunner().invoke(
-                black.main, [str(src1), str(src2), "--diff", "--check"]
-            )
-            self.assertEqual(result.exit_code, 1, result.output)
+            self.invokeBlack([str(src1), str(src2), "--diff", "--check"], exit_code=1)
 
     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)
+            self.invokeBlack([])
 
     def test_broken_symlink(self) -> None:
         with cache_dir() as workspace:
@@ -1078,43 +1089,42 @@ class BlackTestCase(unittest.TestCase):
                 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)
+            self.invokeBlack([str(workspace.resolve())])
 
     def test_read_cache_line_lengths(self) -> None:
-        mode = black.FileMode.AUTO_DETECT
+        mode = black.FileMode()
+        short_mode = black.FileMode(line_length=1)
         with cache_dir() as workspace:
             path = (workspace / "file.py").resolve()
             path.touch()
-            black.write_cache({}, [path], 1, mode)
-            one = black.read_cache(1, mode)
+            black.write_cache({}, [path], mode)
+            one = black.read_cache(mode)
             self.assertIn(path, one)
-            two = black.read_cache(2, mode)
+            two = black.read_cache(short_mode)
             self.assertNotIn(path, two)
 
     def test_single_file_force_pyi(self) -> None:
-        reg_mode = black.FileMode.AUTO_DETECT
-        pyi_mode = black.FileMode.PYI
+        reg_mode = black.FileMode()
+        pyi_mode = black.FileMode(is_pyi=True)
         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)
+            self.invokeBlack([str(path), "--pyi"])
             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)
+            pyi_cache = black.read_cache(pyi_mode)
             self.assertIn(path, pyi_cache)
-            normal_cache = black.read_cache(black.DEFAULT_LINE_LENGTH, reg_mode)
+            normal_cache = black.read_cache(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
+        reg_mode = black.FileMode()
+        pyi_mode = black.FileMode(is_pyi=True)
         contents, expected = read_data("force_pyi")
         with cache_dir() as workspace:
             paths = [
@@ -1124,15 +1134,14 @@ class BlackTestCase(unittest.TestCase):
             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)
+            self.invokeBlack([str(p) for p in paths] + ["--pyi"])
             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)
+            pyi_cache = black.read_cache(pyi_mode)
+            normal_cache = black.read_cache(reg_mode)
             for path in paths:
                 self.assertIn(path, pyi_cache)
                 self.assertNotIn(path, normal_cache)
@@ -1147,28 +1156,27 @@ class BlackTestCase(unittest.TestCase):
         self.assertFormatEqual(actual, expected)
 
     def test_single_file_force_py36(self) -> None:
-        reg_mode = black.FileMode.AUTO_DETECT
-        py36_mode = black.FileMode.PYTHON36
+        reg_mode = black.FileMode()
+        py36_mode = black.FileMode(target_versions=black.PY36_VERSIONS)
         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)
+            self.invokeBlack([str(path), *PY36_ARGS])
             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)
+            # verify cache with --target-version is separate
+            py36_cache = black.read_cache(py36_mode)
             self.assertIn(path, py36_cache)
-            normal_cache = black.read_cache(black.DEFAULT_LINE_LENGTH, reg_mode)
+            normal_cache = black.read_cache(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
+        reg_mode = black.FileMode()
+        py36_mode = black.FileMode(target_versions=black.PY36_VERSIONS)
         source, expected = read_data("force_py36")
         with cache_dir() as workspace:
             paths = [
@@ -1178,17 +1186,14 @@ class BlackTestCase(unittest.TestCase):
             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)
+            self.invokeBlack([str(p) for p in paths] + PY36_ARGS)
             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)
+            # verify cache with --target-version is separate
+            pyi_cache = black.read_cache(py36_mode)
+            normal_cache = black.read_cache(reg_mode)
             for path in paths:
                 self.assertIn(path, pyi_cache)
                 self.assertNotIn(path, normal_cache)
@@ -1196,7 +1201,9 @@ class BlackTestCase(unittest.TestCase):
     def test_pipe_force_py36(self) -> None:
         source, expected = read_data("force_py36")
         result = CliRunner().invoke(
-            black.main, ["-", "-q", "--py36"], input=BytesIO(source.encode("utf8"))
+            black.main,
+            ["-", "-q", "--target-version=py36"],
+            input=BytesIO(source.encode("utf8")),
         )
         self.assertEqual(result.exit_code, 0)
         actual = result.output
@@ -1265,8 +1272,7 @@ class BlackTestCase(unittest.TestCase):
 
     def test_invalid_include_exclude(self) -> None:
         for option in ["--include", "--exclude"]:
-            result = CliRunner().invoke(black.main, ["-", option, "**()(!!*)"])
-            self.assertEqual(result.exit_code, 2)
+            self.invokeBlack(["-", option, "**()(!!*)"], exit_code=2)
 
     def test_preserves_line_endings(self) -> None:
         with TemporaryDirectory() as workspace:
@@ -1350,6 +1356,21 @@ class BlackTestCase(unittest.TestCase):
             except RuntimeError as re:
                 self.fail(f"`patch_click()` failed, exception still raised: {re}")
 
+    def test_root_logger_not_used_directly(self) -> None:
+        def fail(*args: Any, **kwargs: Any) -> None:
+            self.fail("Record created with root logger")
+
+        with patch.multiple(
+            logging.root,
+            debug=fail,
+            info=fail,
+            warning=fail,
+            error=fail,
+            critical=fail,
+            log=fail,
+        ):
+            ff(THIS_FILE)
+
     @unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed")
     @async_test
     async def test_blackd_request_needs_formatting(self) -> None:
@@ -1407,10 +1428,24 @@ class BlackTestCase(unittest.TestCase):
     async def test_blackd_invalid_python_variant(self) -> None:
         app = blackd.make_app()
         async with TestClient(TestServer(app)) as client:
-            response = await client.post(
-                "/", data=b"what", headers={blackd.PYTHON_VARIANT_HEADER: "lol"}
-            )
-            self.assertEqual(response.status, 400)
+
+            async def check(header_value: str, expected_status: int = 400) -> None:
+                response = await client.post(
+                    "/",
+                    data=b"what",
+                    headers={blackd.PYTHON_VARIANT_HEADER: header_value},
+                )
+                self.assertEqual(response.status, expected_status)
+
+            await check("lol")
+            await check("ruby3.5")
+            await check("pyi3.6")
+            await check("py1.5")
+            await check("2.8")
+            await check("py2.8")
+            await check("3.0")
+            await check("pypy3.0")
+            await check("jython3.4")
 
     @unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed")
     @async_test
@@ -1426,51 +1461,35 @@ class BlackTestCase(unittest.TestCase):
 
     @unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed")
     @async_test
-    async def test_blackd_py36(self) -> None:
+    async def test_blackd_python_variant(self) -> None:
         app = blackd.make_app()
+        code = (
+            "def f(\n"
+            "    and_has_a_bunch_of,\n"
+            "    very_long_arguments_too,\n"
+            "    and_lots_of_them_as_well_lol,\n"
+            "    **and_very_long_keyword_arguments\n"
+            "):\n"
+            "    pass\n"
+        )
         async with TestClient(TestServer(app)) as client:
-            response = await client.post(
-                "/",
-                data=(
-                    "def f(\n"
-                    "    and_has_a_bunch_of,\n"
-                    "    very_long_arguments_too,\n"
-                    "    and_lots_of_them_as_well_lol,\n"
-                    "    **and_very_long_keyword_arguments\n"
-                    "):\n"
-                    "    pass\n"
-                ),
-                headers={blackd.PYTHON_VARIANT_HEADER: "3.6"},
-            )
-            self.assertEqual(response.status, 200)
-            response = await client.post(
-                "/",
-                data=(
-                    "def f(\n"
-                    "    and_has_a_bunch_of,\n"
-                    "    very_long_arguments_too,\n"
-                    "    and_lots_of_them_as_well_lol,\n"
-                    "    **and_very_long_keyword_arguments\n"
-                    "):\n"
-                    "    pass\n"
-                ),
-                headers={blackd.PYTHON_VARIANT_HEADER: "3.5"},
-            )
-            self.assertEqual(response.status, 204)
-            response = await client.post(
-                "/",
-                data=(
-                    "def f(\n"
-                    "    and_has_a_bunch_of,\n"
-                    "    very_long_arguments_too,\n"
-                    "    and_lots_of_them_as_well_lol,\n"
-                    "    **and_very_long_keyword_arguments\n"
-                    "):\n"
-                    "    pass\n"
-                ),
-                headers={blackd.PYTHON_VARIANT_HEADER: "2"},
-            )
-            self.assertEqual(response.status, 204)
+
+            async def check(header_value: str, expected_status: int) -> None:
+                response = await client.post(
+                    "/", data=code, headers={blackd.PYTHON_VARIANT_HEADER: header_value}
+                )
+                self.assertEqual(response.status, expected_status)
+
+            await check("3.6", 200)
+            await check("py3.6", 200)
+            await check("3.5,3.7", 200)
+            await check("3.5,py3.7", 200)
+
+            await check("2", 204)
+            await check("2.7", 204)
+            await check("py2.7", 204)
+            await check("3.4", 204)
+            await check("py3.4", 204)
 
     @unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed")
     @async_test