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, 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",
 
  48     command: Callable[..., Any], *args: Any, **kwargs: Any
 
  50     old_stdout, sys.stdout = sys.stdout, StringIO()
 
  52         command(*args, **kwargs)
 
  54         yield sys.stdout.read()
 
  56         sys.stdout = old_stdout
 
  60 def event_loop() -> Iterator[None]:
 
  61     policy = asyncio.get_event_loop_policy()
 
  62     loop = policy.new_event_loop()
 
  63     asyncio.set_event_loop(loop)
 
  64     if sys.platform == "win32":
 
  65         asyncio.set_event_loop(asyncio.ProactorEventLoop())
 
  72 async def raise_subprocess_error_1(*args: Any, **kwargs: Any) -> None:
 
  73     raise CalledProcessError(1, ["unittest", "error"], b"", b"")
 
  76 async def raise_subprocess_error_123(*args: Any, **kwargs: Any) -> None:
 
  77     raise CalledProcessError(123, ["unittest", "error"], b"", b"")
 
  80 async def return_false(*args: Any, **kwargs: Any) -> bool:
 
  84 async def return_subproccess_output(*args: Any, **kwargs: Any) -> Tuple[bytes, bytes]:
 
  85     return (b"stdout", b"stderr")
 
  88 async def return_zero(*args: Any, **kwargs: Any) -> int:
 
  92 class PrimerLibTests(unittest.TestCase):
 
  93     def test_analyze_results(self) -> None:
 
  94         fake_results = lib.Results(
 
  98                 "skipped_long_checkout": 0,
 
 102             {"black": CalledProcessError(69, ["black"], b"Black didn't work", b"")},
 
 104         with capture_stdout(lib.analyze_results, 69, fake_results) as analyze_stdout:
 
 105             self.assertEqual(EXPECTED_ANALYSIS_OUTPUT, analyze_stdout)
 
 108     def test_black_run(self) -> None:
 
 109         """Pretend to run Black to ensure we cater for all scenarios"""
 
 110         loop = asyncio.get_event_loop()
 
 111         project_name = "unittest"
 
 112         repo_path = Path(gettempdir())
 
 113         project_config = deepcopy(FAKE_PROJECT_CONFIG)
 
 114         results = lib.Results({"failed": 0, "success": 0}, {})
 
 116         # Test a successful Black run
 
 117         with patch("black_primer.lib._gen_check_output", return_subproccess_output):
 
 118             loop.run_until_complete(
 
 119                 lib.black_run(project_name, repo_path, project_config, results)
 
 121         self.assertEqual(1, results.stats["success"])
 
 122         self.assertFalse(results.failed_projects)
 
 124         # Test a fail based on expecting formatting changes but not getting any
 
 125         project_config["expect_formatting_changes"] = True
 
 126         results = lib.Results({"failed": 0, "success": 0}, {})
 
 127         with patch("black_primer.lib._gen_check_output", return_subproccess_output):
 
 128             loop.run_until_complete(
 
 129                 lib.black_run(project_name, repo_path, project_config, results)
 
 131         self.assertEqual(1, results.stats["failed"])
 
 132         self.assertTrue(results.failed_projects)
 
 134         # Test a fail based on returning 1 and not expecting formatting changes
 
 135         project_config["expect_formatting_changes"] = False
 
 136         results = lib.Results({"failed": 0, "success": 0}, {})
 
 137         with patch("black_primer.lib._gen_check_output", raise_subprocess_error_1):
 
 138             loop.run_until_complete(
 
 139                 lib.black_run(project_name, repo_path, project_config, results)
 
 141         self.assertEqual(1, results.stats["failed"])
 
 142         self.assertTrue(results.failed_projects)
 
 144         # Test a formatting error based on returning 123
 
 145         with patch("black_primer.lib._gen_check_output", raise_subprocess_error_123):
 
 146             loop.run_until_complete(
 
 147                 lib.black_run(project_name, repo_path, project_config, results)
 
 149         self.assertEqual(2, results.stats["failed"])
 
 151     def test_flatten_cli_args(self) -> None:
 
 152         fake_long_args = ["--arg", ["really/", "|long", "|regex", "|splitup"], "--done"]
 
 153         expected = ["--arg", "really/|long|regex|splitup", "--done"]
 
 154         self.assertEqual(expected, lib._flatten_cli_args(fake_long_args))
 
 157     def test_gen_check_output(self) -> None:
 
 158         loop = asyncio.get_event_loop()
 
 159         stdout, stderr = loop.run_until_complete(
 
 160             lib._gen_check_output([lib.BLACK_BINARY, "--help"])
 
 162         self.assertTrue("The uncompromising code formatter" in stdout.decode("utf8"))
 
 163         self.assertEqual(None, stderr)
 
 165         # TODO: Add a test to see failure works on Windows
 
 169         false_bin = "/usr/bin/false" if system() == "Darwin" else "/bin/false"
 
 170         with self.assertRaises(CalledProcessError):
 
 171             loop.run_until_complete(lib._gen_check_output([false_bin]))
 
 173         with self.assertRaises(asyncio.TimeoutError):
 
 174             loop.run_until_complete(
 
 175                 lib._gen_check_output(["/bin/sleep", "2"], timeout=0.1)
 
 179     def test_git_checkout_or_rebase(self) -> None:
 
 180         loop = asyncio.get_event_loop()
 
 181         project_config = deepcopy(FAKE_PROJECT_CONFIG)
 
 182         work_path = Path(gettempdir())
 
 184         expected_repo_path = work_path / "black"
 
 185         with patch("black_primer.lib._gen_check_output", return_subproccess_output):
 
 186             returned_repo_path = loop.run_until_complete(
 
 187                 lib.git_checkout_or_rebase(work_path, project_config)
 
 189         self.assertEqual(expected_repo_path, returned_repo_path)
 
 191     @patch("sys.stdout", new_callable=StringIO)
 
 193     def test_process_queue(self, mock_stdout: Mock) -> None:
 
 194         """Test the process queue on primer itself
 
 195         - If you have non black conforming formatting in primer itself this can fail"""
 
 196         loop = asyncio.get_event_loop()
 
 197         config_path = Path(lib.__file__).parent / "primer.json"
 
 198         with patch("black_primer.lib.git_checkout_or_rebase", return_false):
 
 199             with TemporaryDirectory() as td:
 
 200                 return_val = loop.run_until_complete(
 
 201                     lib.process_queue(str(config_path), Path(td), 2)
 
 203                 self.assertEqual(0, return_val)
 
 206 class PrimerCLITests(unittest.TestCase):
 
 208     def test_async_main(self) -> None:
 
 209         loop = asyncio.get_event_loop()
 
 210         work_dir = Path(gettempdir()) / f"primer_ut_{getpid()}"
 
 215             "long_checkouts": False,
 
 217             "workdir": str(work_dir),
 
 221         with patch("black_primer.cli.lib.process_queue", return_zero):
 
 222             return_val = loop.run_until_complete(cli.async_main(**args))  # type: ignore
 
 223             self.assertEqual(0, return_val)
 
 225     def test_handle_debug(self) -> None:
 
 226         self.assertTrue(cli._handle_debug(None, None, True))
 
 228     def test_help_output(self) -> None:
 
 230         result = runner.invoke(cli.main, ["--help"])
 
 231         self.assertEqual(result.exit_code, 0)
 
 234 if __name__ == "__main__":