From 4daeb7a6bb1cb1b3e63a527a8ccf2005400ec6f7 Mon Sep 17 00:00:00 2001 From: Tony Narlock Date: Sun, 1 May 2022 07:16:04 -0500 Subject: [PATCH 1/5] docs(cmd): Controlling invocation --- docs/cmd/index.md | 55 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 55 insertions(+) diff --git a/docs/cmd/index.md b/docs/cmd/index.md index 4e495cb08..32fad657d 100644 --- a/docs/cmd/index.md +++ b/docs/cmd/index.md @@ -20,3 +20,58 @@ git hg svn ``` + +## Controlling commands + +### Override `run()` + +You want to control `stdout`, `stderr`, terminal output, tee'ing or logging, introspect and modify +the commands themselves. libvcs is designed to make this trivial to control. + +- Git -> `Git.` -> `Git.run` -> `run` + +You override `Git.run` method, and all `Git` commands can be intercepted. + +```python + +class MyGit(Git): + def run(self, *args, **kwargs): + return ... +``` + +You can also pass-through using `super()` + +```python + +class MyGit(Git): + def run(self, *args, **kwargs): + return super().run(*args, **kwargs) +``` + +Two possibilities: + +1. Modify args / kwargs before running them +2. Replace `run()` with a different subprocess runner + +### `LazySubprocessMixin` + +```python + +class MyGit(Git, LazySubprocessMixin): + def run(self, *args, **kwargs): + return ... +``` + +You can introspect it here. + +Instead of `git.run(...)` you'd do `git.run(...).run()`. + +Also, you can introspect and modify the output before execution + +```python +>>> mycmd = git.run(...) +>>> mycmd.flags +... +>>> mycmd.flags = '--help' +>>> mycmd.run() +``` From 6411211737aea17afc82cd6afb619e0ef064af75 Mon Sep 17 00:00:00 2001 From: Tony Narlock Date: Sat, 7 May 2022 13:53:16 -0500 Subject: [PATCH 2/5] test(subprocess): SubprocessCommand --- .../subprocess/test_SubprocessCommand.py | 157 ++++++++++++++++++ 1 file changed, 157 insertions(+) diff --git a/tests/_internal/subprocess/test_SubprocessCommand.py b/tests/_internal/subprocess/test_SubprocessCommand.py index 493f67efa..29fd65bea 100644 --- a/tests/_internal/subprocess/test_SubprocessCommand.py +++ b/tests/_internal/subprocess/test_SubprocessCommand.py @@ -1,6 +1,9 @@ import pathlib import subprocess +import sys +import textwrap from typing import Any +from unittest import mock import pytest @@ -137,3 +140,157 @@ def test_run(tmp_path: pathlib.Path, args: list, kwargs: dict, run_kwargs: dict) response = cmd.run(**run_kwargs) assert response.returncode == 0 + + +@pytest.mark.parametrize( + "args,kwargs,run_kwargs", + [ + [ + ["ls"], + {}, + {}, + ], + [[["ls", "-l"]], {}, {}], + [[["ls", "-al"]], {}, {"stdout": subprocess.DEVNULL}], + ], + ids=idfn, +) +@mock.patch("subprocess.Popen") +def test_Popen_mock( + mock_subprocess_popen, + tmp_path: pathlib.Path, + args: list, + kwargs: dict, + run_kwargs: dict, + capsys: pytest.LogCaptureFixture, +): + process_mock = mock.Mock() + attrs = {"communicate.return_value": ("output", "error"), "returncode": 1} + process_mock.configure_mock(**attrs) + mock_subprocess_popen.return_value = process_mock + cmd = SubprocessCommand(*args, cwd=tmp_path, **kwargs) + response = cmd.Popen(**run_kwargs) + + assert response.returncode == 1 + + +@pytest.mark.parametrize( + "args,kwargs,run_kwargs", + [ + [[["git", "pull", "--progress"]], {}, {}], + ], + ids=idfn, +) +@mock.patch("subprocess.Popen") +def test_Popen_git_mock( + mock_subprocess_popen, + tmp_path: pathlib.Path, + args: list, + kwargs: dict, + run_kwargs: dict, + capsys: pytest.LogCaptureFixture, +): + process_mock = mock.Mock() + attrs = {"communicate.return_value": ("output", "error"), "returncode": 1} + process_mock.configure_mock(**attrs) + mock_subprocess_popen.return_value = process_mock + cmd = SubprocessCommand(*args, cwd=tmp_path, **kwargs) + response = cmd.Popen(**run_kwargs) + + stdout, stderr = response.communicate() + + assert response.returncode == 1 + assert stdout == "output" + assert stderr == "error" + + +CODE = ( + textwrap.dedent( + r""" + import sys + import time + size = 10 + for i in range(10): + time.sleep(.01) + sys.stderr.write( + '[' + "#" * i + "." * (size-i) + ']' + f' {i}/{size}' + '\n' + ) + sys.stderr.flush() +""" + ) + .strip("\n") + .lstrip() +) + + +def test_Popen_stderr( + tmp_path: pathlib.Path, + capsys: pytest.LogCaptureFixture, +): + cmd = SubprocessCommand( + [ + sys.executable, + "-c", + CODE, + ], + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + cwd=tmp_path, + ) + response = cmd.Popen() + while response.poll() is None: + stdout, stderr = response.communicate() + + assert stdout != "output" + assert stderr != "1" + assert response.returncode == 0 + + +def test_CaptureStderrMixin( + tmp_path: pathlib.Path, + capsys: pytest.LogCaptureFixture, +): + cmd = SubprocessCommand( + [ + sys.executable, + "-c", + CODE, + ], + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + cwd=tmp_path, + ) + response = cmd.Popen() + while response.poll() is None: + line = response.stderr.readline().decode("utf-8").strip() + if line: + assert line.startswith("[") + assert response.returncode == 0 + + +def test_CaptureStderrMixin_error( + tmp_path: pathlib.Path, + capsys: pytest.LogCaptureFixture, +): + cmd = SubprocessCommand( + [ + sys.executable, + "-c", + CODE + + textwrap.dedent( + """ + sys.exit("FATAL") + """ + ), + ], + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + cwd=tmp_path, + ) + response = cmd.Popen() + while response.poll() is None: + line = response.stderr.readline().decode("utf-8").strip() + if line: + assert line.startswith("[") or line == "FATAL" + + assert response.returncode == 1 From 92d98bd23b5122f0d5de8e96ac07ef42f0472f7e Mon Sep 17 00:00:00 2001 From: Tony Narlock Date: Sat, 21 May 2022 16:25:46 -0500 Subject: [PATCH 3/5] Example of deferred command --- tests/cmd/test_git.py | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/tests/cmd/test_git.py b/tests/cmd/test_git.py index 5eb271399..c522b970c 100644 --- a/tests/cmd/test_git.py +++ b/tests/cmd/test_git.py @@ -3,6 +3,7 @@ import pytest +from libvcs._internal.subprocess import SubprocessCommand from libvcs.cmd import git @@ -11,3 +12,20 @@ def test_run(dir_type: Callable, tmp_path: pathlib.Path): repo = git.Git(dir=dir_type(tmp_path)) assert repo.dir == tmp_path + + +@pytest.mark.parametrize("dir_type", [str, pathlib.Path]) +def test_run_deferred(dir_type: Callable, tmp_path: pathlib.Path): + class GitDeferred(git.Git): + def run(self, args, **kwargs): + return SubprocessCommand(["git", *args], **kwargs) + + g = GitDeferred(dir=dir_type(tmp_path)) + cmd = g.run(["help"]) + + assert g.dir == tmp_path + assert cmd.args == ["git", "help"] + + assert cmd.run(capture_output=True, text=True).stdout.startswith( + "usage: git [--version] [--help] [-C ]" + ) From b37dadebd893637082955214ffaccd15b5328270 Mon Sep 17 00:00:00 2001 From: Tony Narlock Date: Sat, 28 May 2022 11:20:56 -0500 Subject: [PATCH 4/5] !squash projects.git.obtain: Start moving over, needs logging in real time --- libvcs/projects/git.py | 23 +++++++++++++---------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/libvcs/projects/git.py b/libvcs/projects/git.py index 775ed3f86..edf843226 100644 --- a/libvcs/projects/git.py +++ b/libvcs/projects/git.py @@ -22,6 +22,7 @@ from typing import Dict, Literal, Optional, TypedDict, Union from urllib import parse as urlparse +from libvcs.cmd.git import Git from libvcs.types import StrPath from .. import exc @@ -313,24 +314,26 @@ def set_remotes(self, overwrite: bool = False): def obtain(self, *args, **kwargs): """Retrieve the repository, clone if doesn't exist.""" - self.ensure_dir() - - url = self.url + clone_kwargs = {} - cmd = ["clone", "--progress"] if self.git_shallow: - cmd.extend(["--depth", "1"]) + clone_kwargs["depth"] = 1 if self.tls_verify: - cmd.extend(["-c", "http.sslVerify=false"]) - cmd.extend([url, self.dir]) + clone_kwargs["c"] = "http.sslVerify=false" self.log.info("Cloning.") - self.run(cmd, log_in_real_time=True) + + git = Git(dir=self.dir) + + # Needs to log to std out, e.g. log_in_real_time + git.clone(url=self.url, progress=True, make_parents=True, **clone_kwargs) self.log.info("Initializing submodules.") + self.run(["submodule", "init"], log_in_real_time=True) - cmd = ["submodule", "update", "--recursive", "--init"] - self.run(cmd, log_in_real_time=True) + self.run( + ["submodule", "update", "--recursive", "--init"], log_in_real_time=True + ) self.set_remotes(overwrite=True) From 9a8e0f1396bb3708401b20072508d749c321f16e Mon Sep 17 00:00:00 2001 From: Tony Narlock Date: Mon, 30 May 2022 17:09:20 -0500 Subject: [PATCH 5/5] !squash mypy for subcommand --- .../subprocess/test_SubprocessCommand.py | 20 +++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/tests/_internal/subprocess/test_SubprocessCommand.py b/tests/_internal/subprocess/test_SubprocessCommand.py index 29fd65bea..bf2a13483 100644 --- a/tests/_internal/subprocess/test_SubprocessCommand.py +++ b/tests/_internal/subprocess/test_SubprocessCommand.py @@ -168,7 +168,8 @@ def test_Popen_mock( attrs = {"communicate.return_value": ("output", "error"), "returncode": 1} process_mock.configure_mock(**attrs) mock_subprocess_popen.return_value = process_mock - cmd = SubprocessCommand(*args, cwd=tmp_path, **kwargs) + kwargs["cwd"] = tmp_path + cmd = SubprocessCommand(*args, **kwargs) response = cmd.Popen(**run_kwargs) assert response.returncode == 1 @@ -194,7 +195,8 @@ def test_Popen_git_mock( attrs = {"communicate.return_value": ("output", "error"), "returncode": 1} process_mock.configure_mock(**attrs) mock_subprocess_popen.return_value = process_mock - cmd = SubprocessCommand(*args, cwd=tmp_path, **kwargs) + kwargs["cwd"] = tmp_path + cmd = SubprocessCommand(*args, **kwargs) response = cmd.Popen(**run_kwargs) stdout, stderr = response.communicate() @@ -262,9 +264,10 @@ def test_CaptureStderrMixin( ) response = cmd.Popen() while response.poll() is None: - line = response.stderr.readline().decode("utf-8").strip() - if line: - assert line.startswith("[") + if response.stderr is not None: + line = response.stderr.readline().decode("utf-8").strip() + if line: + assert line.startswith("[") assert response.returncode == 0 @@ -289,8 +292,9 @@ def test_CaptureStderrMixin_error( ) response = cmd.Popen() while response.poll() is None: - line = response.stderr.readline().decode("utf-8").strip() - if line: - assert line.startswith("[") or line == "FATAL" + if response.stderr is not None: + line = response.stderr.readline().decode("utf-8").strip() + if line: + assert line.startswith("[") or line == "FATAL" assert response.returncode == 1 pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy