All patches and comments are welcome. Please squash your changes to logical
commits before using git-format-patch and git-send-email to
patches@git.madduck.net.
If you'd read over the Git project's submission guidelines and adhered to them,
I'd be especially grateful.
6 from contextlib import contextmanager
7 from copy import deepcopy
8 from io import StringIO
10 from pathlib import Path
11 from platform import system
12 from subprocess import CalledProcessError
13 from tempfile import TemporaryDirectory, gettempdir
14 from typing import Any, Callable, Generator, Iterator, Tuple
15 from unittest.mock import Mock, patch
17 from click.testing import CliRunner
19 from black_primer import cli, lib
22 EXPECTED_ANALYSIS_OUTPUT = """\
23 -- primer results 📊 --
25 68 / 69 succeeded (98.55%) ✅
26 1 / 69 FAILED (1.45%) 💩
27 - 0 projects disabled by config
28 - 0 projects skipped due to Python version
29 - 0 skipped due to long checkout
39 FAKE_PROJECT_CONFIG = {
40 "cli_arguments": ["--unittest"],
41 "expect_formatting_changes": False,
42 "git_clone_url": "https://github.com/psf/black.git",
47 def capture_stdout(command: Callable, *args: Any, **kwargs: Any) -> Generator:
48 old_stdout, sys.stdout = sys.stdout, StringIO()
50 command(*args, **kwargs)
52 yield sys.stdout.read()
54 sys.stdout = old_stdout
58 def event_loop() -> Iterator[None]:
59 policy = asyncio.get_event_loop_policy()
60 loop = policy.new_event_loop()
61 asyncio.set_event_loop(loop)
62 if sys.platform == "win32":
63 asyncio.set_event_loop(asyncio.ProactorEventLoop())
70 async def raise_subprocess_error_1(*args: Any, **kwargs: Any) -> None:
71 raise CalledProcessError(1, ["unittest", "error"], b"", b"")
74 async def raise_subprocess_error_123(*args: Any, **kwargs: Any) -> None:
75 raise CalledProcessError(123, ["unittest", "error"], b"", b"")
78 async def return_false(*args: Any, **kwargs: Any) -> bool:
82 async def return_subproccess_output(*args: Any, **kwargs: Any) -> Tuple[bytes, bytes]:
83 return (b"stdout", b"stderr")
86 async def return_zero(*args: Any, **kwargs: Any) -> int:
90 class PrimerLibTests(unittest.TestCase):
91 def test_analyze_results(self) -> None:
92 fake_results = lib.Results(
96 "skipped_long_checkout": 0,
100 {"black": CalledProcessError(69, ["black"], b"Black didn't work", b"")},
102 with capture_stdout(lib.analyze_results, 69, fake_results) as analyze_stdout:
103 self.assertEqual(EXPECTED_ANALYSIS_OUTPUT, analyze_stdout)
106 def test_black_run(self) -> None:
107 """Pretend to run Black to ensure we cater for all scenarios"""
108 loop = asyncio.get_event_loop()
109 project_name = "unittest"
110 repo_path = Path(gettempdir())
111 project_config = deepcopy(FAKE_PROJECT_CONFIG)
112 results = lib.Results({"failed": 0, "success": 0}, {})
114 # Test a successful Black run
115 with patch("black_primer.lib._gen_check_output", return_subproccess_output):
116 loop.run_until_complete(
117 lib.black_run(project_name, repo_path, project_config, results)
119 self.assertEqual(1, results.stats["success"])
120 self.assertFalse(results.failed_projects)
122 # Test a fail based on expecting formatting changes but not getting any
123 project_config["expect_formatting_changes"] = True
124 results = lib.Results({"failed": 0, "success": 0}, {})
125 with patch("black_primer.lib._gen_check_output", return_subproccess_output):
126 loop.run_until_complete(
127 lib.black_run(project_name, repo_path, project_config, results)
129 self.assertEqual(1, results.stats["failed"])
130 self.assertTrue(results.failed_projects)
132 # Test a fail based on returning 1 and not expecting formatting changes
133 project_config["expect_formatting_changes"] = False
134 results = lib.Results({"failed": 0, "success": 0}, {})
135 with patch("black_primer.lib._gen_check_output", raise_subprocess_error_1):
136 loop.run_until_complete(
137 lib.black_run(project_name, repo_path, project_config, results)
139 self.assertEqual(1, results.stats["failed"])
140 self.assertTrue(results.failed_projects)
142 # Test a formatting error based on returning 123
143 with patch("black_primer.lib._gen_check_output", raise_subprocess_error_123):
144 loop.run_until_complete(
145 lib.black_run(project_name, repo_path, project_config, results)
147 self.assertEqual(2, results.stats["failed"])
150 def test_gen_check_output(self) -> None:
151 loop = asyncio.get_event_loop()
152 stdout, stderr = loop.run_until_complete(
153 lib._gen_check_output([lib.BLACK_BINARY, "--help"])
155 self.assertTrue("The uncompromising code formatter" in stdout.decode("utf8"))
156 self.assertEqual(None, stderr)
158 # TODO: Add a test to see failure works on Windows
162 false_bin = "/usr/bin/false" if system() == "Darwin" else "/bin/false"
163 with self.assertRaises(CalledProcessError):
164 loop.run_until_complete(lib._gen_check_output([false_bin]))
166 with self.assertRaises(asyncio.TimeoutError):
167 loop.run_until_complete(
168 lib._gen_check_output(["/bin/sleep", "2"], timeout=0.1)
172 def test_git_checkout_or_rebase(self) -> None:
173 loop = asyncio.get_event_loop()
174 project_config = deepcopy(FAKE_PROJECT_CONFIG)
175 work_path = Path(gettempdir())
177 expected_repo_path = work_path / "black"
178 with patch("black_primer.lib._gen_check_output", return_subproccess_output):
179 returned_repo_path = loop.run_until_complete(
180 lib.git_checkout_or_rebase(work_path, project_config)
182 self.assertEqual(expected_repo_path, returned_repo_path)
184 @patch("sys.stdout", new_callable=StringIO)
186 def test_process_queue(self, mock_stdout: Mock) -> None:
187 loop = asyncio.get_event_loop()
188 config_path = Path(lib.__file__).parent / "primer.json"
189 with patch("black_primer.lib.git_checkout_or_rebase", return_false):
190 with TemporaryDirectory() as td:
191 return_val = loop.run_until_complete(
192 lib.process_queue(str(config_path), td, 2)
194 self.assertEqual(0, return_val)
197 class PrimerCLITests(unittest.TestCase):
199 def test_async_main(self) -> None:
200 loop = asyncio.get_event_loop()
201 work_dir = Path(gettempdir()) / f"primer_ut_{getpid()}"
206 "long_checkouts": False,
208 "workdir": str(work_dir),
212 with patch("black_primer.cli.lib.process_queue", return_zero):
213 return_val = loop.run_until_complete(cli.async_main(**args))
214 self.assertEqual(0, return_val)
216 def test_handle_debug(self) -> None:
217 self.assertTrue(cli._handle_debug(None, None, True))
219 def test_help_output(self) -> None:
221 result = runner.invoke(cli.main, ["--help"])
222 self.assertEqual(result.exit_code, 0)
225 if __name__ == "__main__":