Repository: psf/black

Issue:
Add STDIN primer test
Test feeding a file in vía STDIN to primer

Expected usage:
```
cat awesome.py | black -
```

primer will do with with asyncio.subprocess


Discussion:

Resolving patch:
diff --git a/src/black_primer/lib.py b/src/black_primer/lib.py
--- a/src/black_primer/lib.py
+++ b/src/black_primer/lib.py
@@ -42,16 +42,18 @@ async def _gen_check_output(
     timeout: float = 600,
     env: Optional[Dict[str, str]] = None,
     cwd: Optional[Path] = None,
+    stdin: Optional[bytes] = None,
 ) -> Tuple[bytes, bytes]:
     process = await asyncio.create_subprocess_exec(
         *cmd,
+        stdin=asyncio.subprocess.PIPE,
         stdout=asyncio.subprocess.PIPE,
         stderr=asyncio.subprocess.STDOUT,
         env=env,
         cwd=cwd,
     )
     try:
-        (stdout, stderr) = await asyncio.wait_for(process.communicate(), timeout)
+        (stdout, stderr) = await asyncio.wait_for(process.communicate(stdin), timeout)
     except asyncio.TimeoutError:
         process.kill()
         await process.wait()
@@ -112,20 +114,35 @@ def analyze_results(project_count: int, results: Results) -> int:
 
 
 async def black_run(
-    repo_path: Path,
+    project_name: str,
+    repo_path: Optional[Path],
     project_config: Dict[str, Any],
     results: Results,
     no_diff: bool = False,
 ) -> None:
     """Run Black and record failures"""
+    if not repo_path:
+        results.stats["failed"] += 1
+        results.failed_projects[project_name] = CalledProcessError(
+            69, [], f"{project_name} has no repo_path: {repo_path}".encode(), b""
+        )
+        return
+
+    stdin_test = project_name.upper() == "STDIN"
     cmd = [str(which(BLACK_BINARY))]
     if "cli_arguments" in project_config and project_config["cli_arguments"]:
         cmd.extend(project_config["cli_arguments"])
     cmd.append("--check")
-    if no_diff:
-        cmd.append(".")
+    if not no_diff:
+        cmd.append("--diff")
+
+    # Workout if we should read in a python file or search from cwd
+    stdin = None
+    if stdin_test:
+        cmd.append("-")
+        stdin = repo_path.read_bytes()
     else:
-        cmd.extend(["--diff", "."])
+        cmd.append(".")
 
     with TemporaryDirectory() as tmp_path:
         # Prevent reading top-level user configs by manipulating environment variables
@@ -135,8 +152,11 @@ async def black_run(
             "USERPROFILE": tmp_path,  # Windows (changes `Path.home()` output)
         }
 
+        cwd_path = repo_path.parent if stdin_test else repo_path
         try:
-            _stdout, _stderr = await _gen_check_output(cmd, cwd=repo_path, env=env)
+            _stdout, _stderr = await _gen_check_output(
+                cmd, cwd=cwd_path, env=env, stdin=stdin
+            )
         except asyncio.TimeoutError:
             results.stats["failed"] += 1
             LOG.error(f"Running black for {repo_path} timed out ({cmd})")
@@ -289,12 +309,15 @@ async def project_runner(
             LOG.debug(f"Skipping {project_name} as it's configured as a long checkout")
             continue
 
-        repo_path = await git_checkout_or_rebase(work_path, project_config, rebase)
-        if not repo_path:
-            continue
-        await black_run(repo_path, project_config, results, no_diff)
+        repo_path: Optional[Path] = Path(__file__)
+        stdin_project = project_name.upper() == "STDIN"
+        if not stdin_project:
+            repo_path = await git_checkout_or_rebase(work_path, project_config, rebase)
+            if not repo_path:
+                continue
+        await black_run(project_name, repo_path, project_config, results, no_diff)
 
-        if not keep:
+        if not keep and not stdin_project:
             LOG.debug(f"Removing {repo_path}")
             rmtree_partial = partial(
                 rmtree, path=repo_path, onerror=handle_PermissionError


Test patch:
diff --git a/tests/test_primer.py b/tests/test_primer.py
--- a/tests/test_primer.py
+++ b/tests/test_primer.py
@@ -106,13 +106,16 @@ def test_analyze_results(self) -> None:
     def test_black_run(self) -> None:
         """Pretend to run Black to ensure we cater for all scenarios"""
         loop = asyncio.get_event_loop()
+        project_name = "unittest"
         repo_path = Path(gettempdir())
         project_config = deepcopy(FAKE_PROJECT_CONFIG)
         results = lib.Results({"failed": 0, "success": 0}, {})
 
         # Test a successful Black run
         with patch("black_primer.lib._gen_check_output", return_subproccess_output):
-            loop.run_until_complete(lib.black_run(repo_path, project_config, results))
+            loop.run_until_complete(
+                lib.black_run(project_name, repo_path, project_config, results)
+            )
         self.assertEqual(1, results.stats["success"])
         self.assertFalse(results.failed_projects)
 
@@ -120,7 +123,9 @@ def test_black_run(self) -> None:
         project_config["expect_formatting_changes"] = True
         results = lib.Results({"failed": 0, "success": 0}, {})
         with patch("black_primer.lib._gen_check_output", return_subproccess_output):
-            loop.run_until_complete(lib.black_run(repo_path, project_config, results))
+            loop.run_until_complete(
+                lib.black_run(project_name, repo_path, project_config, results)
+            )
         self.assertEqual(1, results.stats["failed"])
         self.assertTrue(results.failed_projects)
 
@@ -128,13 +133,17 @@ def test_black_run(self) -> None:
         project_config["expect_formatting_changes"] = False
         results = lib.Results({"failed": 0, "success": 0}, {})
         with patch("black_primer.lib._gen_check_output", raise_subprocess_error_1):
-            loop.run_until_complete(lib.black_run(repo_path, project_config, results))
+            loop.run_until_complete(
+                lib.black_run(project_name, repo_path, project_config, results)
+            )
         self.assertEqual(1, results.stats["failed"])
         self.assertTrue(results.failed_projects)
 
         # Test a formatting error based on returning 123
         with patch("black_primer.lib._gen_check_output", raise_subprocess_error_123):
-            loop.run_until_complete(lib.black_run(repo_path, project_config, results))
+            loop.run_until_complete(
+                lib.black_run(project_name, repo_path, project_config, results)
+            )
         self.assertEqual(2, results.stats["failed"])
 
     @event_loop()