From 96ee264fa12c3c0ac5037b916daf5e888c646f5d Mon Sep 17 00:00:00 2001 From: Tony Narlock Date: Sun, 13 Oct 2024 04:40:32 -0500 Subject: [PATCH 1/8] pytest plugin(git) Fix git_remote_repo_single_commit_post_init --- src/libvcs/pytest_plugin.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/libvcs/pytest_plugin.py b/src/libvcs/pytest_plugin.py index 0f46ffda..f2382c45 100644 --- a/src/libvcs/pytest_plugin.py +++ b/src/libvcs/pytest_plugin.py @@ -402,10 +402,14 @@ def git_remote_repo_single_commit_post_init( run( ["touch", testfile_filename], cwd=remote_repo_path, - env={"GITCONFIG": str(gitconfig)}, + env=env, + ) + run(["git", "add", testfile_filename], cwd=remote_repo_path, env=env) + run( + ["git", "commit", "-m", "test file for dummyrepo"], + cwd=remote_repo_path, + env=env, ) - run(["git", "add", testfile_filename], cwd=remote_repo_path) - run(["git", "commit", "-m", "test file for dummyrepo"], cwd=remote_repo_path) @pytest.fixture(scope="session") From 9e9712389410b84467a48bd53793e248a6ee9cf1 Mon Sep 17 00:00:00 2001 From: Tony Narlock Date: Sun, 13 Oct 2024 05:24:04 -0500 Subject: [PATCH 2/8] tests(pytest plugin) Rename fixture for `create_git_remote_repo()` --- tests/test_pytest_plugin.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/test_pytest_plugin.py b/tests/test_pytest_plugin.py index 072f677a..19d28be4 100644 --- a/tests/test_pytest_plugin.py +++ b/tests/test_pytest_plugin.py @@ -36,11 +36,11 @@ def test_create_svn_remote_repo( assert svn_remote_1 != svn_remote_2 -def test_plugin( +def test_create_git_remote_repo_and_git_sync( pytester: pytest.Pytester, monkeypatch: pytest.MonkeyPatch, ) -> None: - """Tests for libvcs pytest plugin at large.""" + """Tests pytest plugin integration for create_git_remote_repo() and GitSync().""" # Initialize variables pytester.plugins = ["pytest_plugin"] pytester.makefile( @@ -77,7 +77,7 @@ def setup( from libvcs.sync.git import GitSync from libvcs.pytest_plugin import CreateRepoPytestFixtureFn -def test_repo_git_remote_checkout( +def test_repo_git_remote_repo_and_sync( create_git_remote_repo: CreateRepoPytestFixtureFn, tmp_path: pathlib.Path, projects_path: pathlib.Path, From 9cbcc1f2bce69fd4ac7c345545bb46c49646fcca Mon Sep 17 00:00:00 2001 From: Tony Narlock Date: Sun, 13 Oct 2024 05:30:12 -0500 Subject: [PATCH 3/8] tests(pytest plugin) Test git_remote_repo_single_commit_post_init() --- tests/test_pytest_plugin.py | 97 +++++++++++++++++++++++++++++++++++++ 1 file changed, 97 insertions(+) diff --git a/tests/test_pytest_plugin.py b/tests/test_pytest_plugin.py index 19d28be4..1c025329 100644 --- a/tests/test_pytest_plugin.py +++ b/tests/test_pytest_plugin.py @@ -112,6 +112,103 @@ def test_repo_git_remote_repo_and_sync( result.assert_outcomes(passed=1) +def test_git_remote_repo( + pytester: pytest.Pytester, + monkeypatch: pytest.MonkeyPatch, + tmp_path: pathlib.Path, +) -> None: + """Tests for libvcs pytest plugin git configuration.""" + monkeypatch.setenv("HOME", str(tmp_path)) + + # Initialize variables + pytester.plugins = ["pytest_plugin"] + pytester.makefile( + ".ini", + pytest=textwrap.dedent( + """ +[pytest] +addopts=-vv + """.strip(), + ), + ) + pytester.makeconftest( + textwrap.dedent( + r""" +import pathlib +import pytest + +@pytest.fixture(scope="session") +def vcs_email() -> str: + return "custom_email@testemail.com" + +@pytest.fixture(autouse=True) +def setup( + request: pytest.FixtureRequest, + gitconfig: pathlib.Path, + set_home: pathlib.Path, +) -> None: + pass + """, + ), + ) + tests_path = pytester.path / "tests" + files = { + "example.py": textwrap.dedent( + """ +import pathlib + +from libvcs.sync.git import GitSync +from libvcs.pytest_plugin import ( + CreateRepoPytestFixtureFn, + git_remote_repo_single_commit_post_init +) + +def test_git_bare_repo_sync_and_commit( + create_git_remote_bare_repo: CreateRepoPytestFixtureFn, + projects_path: pathlib.Path, +) -> None: + git_server = create_git_remote_bare_repo() + git_repo_checkout_dir = projects_path / "my_git_checkout" + git_repo = GitSync(path=str(git_repo_checkout_dir), url=f"file://{git_server!s}") + + git_repo.obtain() + git_repo.update_repo() + + assert git_repo.get_revision() == "initial" + + assert git_repo_checkout_dir.exists() + assert pathlib.Path(git_repo_checkout_dir / ".git").exists() + + git_remote_repo_single_commit_post_init( + remote_repo_path=git_repo_checkout_dir + ) + + assert git_repo.get_revision() != "initial" + + last_committer_email = git_repo.cmd.run(["log", "-1", "--pretty=format:%ae"]) + + assert last_committer_email == "custom_email@testemail.com", ( + 'Email should use the override from the "vcs_email" fixture' + ) + """, + ), + } + first_test_key = next(iter(files.keys())) + first_test_filename = str(tests_path / first_test_key) + + tests_path.mkdir() + for file_name, text in files.items(): + test_file = tests_path / file_name + test_file.write_text( + text, + encoding="utf-8", + ) + + # Test + result = pytester.runpytest(str(first_test_filename)) + result.assert_outcomes(passed=1) + + def test_gitconfig( gitconfig: pathlib.Path, set_gitconfig: pathlib.Path, From e8581de315b817edfe59ce526d044990bdb13c7f Mon Sep 17 00:00:00 2001 From: Tony Narlock Date: Sun, 13 Oct 2024 05:04:56 -0500 Subject: [PATCH 4/8] pytest plugin: Add `vcs_name`, `vcs_email`, and `vcs_user` and use them --- src/libvcs/pytest_plugin.py | 82 ++++++++++++++++++++++++++++++------- tests/test_pytest_plugin.py | 3 +- 2 files changed, 70 insertions(+), 15 deletions(-) diff --git a/src/libvcs/pytest_plugin.py b/src/libvcs/pytest_plugin.py index f2382c45..48f2bd7d 100644 --- a/src/libvcs/pytest_plugin.py +++ b/src/libvcs/pytest_plugin.py @@ -46,6 +46,43 @@ def __init__(self, attempts: int, *args: object) -> None: ) +DEFAULT_VCS_NAME = "Test user" +DEFAULT_VCS_EMAIL = "test@example.com" + + +@pytest.fixture(scope="session") +def vcs_name() -> str: + """Return default VCS name.""" + return DEFAULT_VCS_NAME + + +@pytest.fixture(scope="session") +def vcs_email() -> str: + """Return default VCS email.""" + return DEFAULT_VCS_EMAIL + + +@pytest.fixture(scope="session") +def vcs_user(vcs_name: str, vcs_email: str) -> str: + """Return default VCS user.""" + return f"{vcs_name} <{vcs_email}>" + + +@pytest.fixture(scope="session") +def git_commit_envvars(vcs_name: str, vcs_email: str) -> "_ENV": + """Return environment variables for `git commit`. + + For some reason, `GIT_CONFIG` via {func}`set_gitconfig` doesn't work for `git + commit`. + """ + return { + "GIT_AUTHOR_NAME": vcs_name, + "GIT_AUTHOR_EMAIL": vcs_email, + "GIT_COMMITTER_NAME": vcs_name, + "GIT_COMMITTER_EMAIL": vcs_email, + } + + class RandomStrSequence: """Create a random string sequence.""" @@ -110,13 +147,12 @@ def set_home( monkeypatch.setenv("HOME", str(user_path)) -vcs_email = "libvcs@git-pull.com" - - @pytest.fixture(scope="session") @skip_if_git_missing def gitconfig( user_path: pathlib.Path, + vcs_email: str, + vcs_name: str, ) -> pathlib.Path: """Return git configuration, pytest fixture.""" gitconfig = user_path / ".gitconfig" @@ -129,7 +165,7 @@ def gitconfig( f""" [user] email = {vcs_email} - name = {getpass.getuser()} + name = {vcs_name} [color] diff = auto """, @@ -155,6 +191,7 @@ def set_gitconfig( @skip_if_hg_missing def hgconfig( user_path: pathlib.Path, + vcs_user: str, ) -> pathlib.Path: """Return Mercurial configuration.""" hgrc = user_path / ".hgrc" @@ -162,7 +199,7 @@ def hgconfig( textwrap.dedent( f""" [ui] - username = libvcs tests + username = {vcs_user} merge = internal:merge [trusted] @@ -237,7 +274,11 @@ def unique_repo_name(remote_repos_path: pathlib.Path, max_retries: int = 15) -> class CreateRepoPostInitFn(Protocol): """Typing for VCS repo creation callback.""" - def __call__(self, remote_repo_path: pathlib.Path) -> None: + def __call__( + self, + remote_repo_path: pathlib.Path, + env: "_ENV | None" = None, + ) -> None: """Ran after creating a repo from pytest fixture.""" ... @@ -263,6 +304,7 @@ def _create_git_remote_repo( remote_repo_path: pathlib.Path, remote_repo_post_init: Optional[CreateRepoPostInitFn] = None, init_cmd_args: InitCmdArgs = DEFAULT_GIT_REMOTE_REPO_CMD_ARGS, + env: "_ENV | None" = None, ) -> pathlib.Path: if init_cmd_args is None: init_cmd_args = [] @@ -272,7 +314,7 @@ def _create_git_remote_repo( ) if remote_repo_post_init is not None and callable(remote_repo_post_init): - remote_repo_post_init(remote_repo_path=remote_repo_path) + remote_repo_post_init(remote_repo_path=remote_repo_path, env=env) return remote_repo_path @@ -417,15 +459,14 @@ def git_remote_repo_single_commit_post_init( def git_remote_repo( create_git_remote_repo: CreateRepoPytestFixtureFn, gitconfig: pathlib.Path, + git_commit_envvars: "_ENV", ) -> pathlib.Path: """Copy the session-scoped Git repository to a temporary directory.""" # TODO: Cache the effect of of this in a session-based repo repo_path = create_git_remote_repo() git_remote_repo_single_commit_post_init( remote_repo_path=repo_path, - env={ - "GITCONFIG": str(gitconfig), - }, + env=git_commit_envvars, ) return repo_path @@ -600,6 +641,7 @@ def empty_hg_repo( def create_hg_remote_repo( remote_repos_path: pathlib.Path, empty_hg_repo: pathlib.Path, + hgconfig: pathlib.Path, ) -> CreateRepoPytestFixtureFn: """Pre-made hg repo, bare, used as a file:// remote to checkout and commit to.""" @@ -616,7 +658,10 @@ def fn( shutil.copytree(empty_hg_repo, remote_repo_path) if remote_repo_post_init is not None and callable(remote_repo_post_init): - remote_repo_post_init(remote_repo_path=remote_repo_path) + remote_repo_post_init( + remote_repo_path=remote_repo_path, + env={"HGRCPATH": str(hgconfig)}, + ) assert empty_hg_repo.exists() @@ -637,7 +682,8 @@ def hg_remote_repo( """Pre-made, file-based repo for push and pull.""" repo_path = create_hg_remote_repo() hg_remote_repo_single_commit_post_init( - remote_repo_path=repo_path, env={"HGRCPATH": str(hgconfig)} + remote_repo_path=repo_path, + env={"HGRCPATH": str(hgconfig)}, ) return repo_path @@ -735,6 +781,8 @@ def add_doctest_fixtures( doctest_namespace: dict[str, Any], tmp_path: pathlib.Path, set_home: pathlib.Path, + git_commit_envvars: "_ENV", + hgconfig: pathlib.Path, create_git_remote_repo: CreateRepoPytestFixtureFn, create_svn_remote_repo: CreateRepoPytestFixtureFn, create_hg_remote_repo: CreateRepoPytestFixtureFn, @@ -749,7 +797,10 @@ def add_doctest_fixtures( if shutil.which("git"): doctest_namespace["create_git_remote_repo"] = functools.partial( create_git_remote_repo, - remote_repo_post_init=git_remote_repo_single_commit_post_init, + remote_repo_post_init=functools.partial( + git_remote_repo_single_commit_post_init, + env=git_commit_envvars, + ), init_cmd_args=None, ) doctest_namespace["create_git_remote_repo_bare"] = create_git_remote_repo @@ -764,5 +815,8 @@ def add_doctest_fixtures( doctest_namespace["create_hg_remote_repo_bare"] = create_hg_remote_repo doctest_namespace["create_hg_remote_repo"] = functools.partial( create_hg_remote_repo, - remote_repo_post_init=hg_remote_repo_single_commit_post_init, + remote_repo_post_init=functools.partial( + hg_remote_repo_single_commit_post_init, + env={"HGRCPATH": str(hgconfig)}, + ), ) diff --git a/tests/test_pytest_plugin.py b/tests/test_pytest_plugin.py index 1c025329..db807f36 100644 --- a/tests/test_pytest_plugin.py +++ b/tests/test_pytest_plugin.py @@ -7,7 +7,7 @@ import pytest from libvcs._internal.run import run -from libvcs.pytest_plugin import CreateRepoPytestFixtureFn, vcs_email +from libvcs.pytest_plugin import CreateRepoPytestFixtureFn @pytest.mark.skipif(not shutil.which("git"), reason="git is not available") @@ -212,6 +212,7 @@ def test_git_bare_repo_sync_and_commit( def test_gitconfig( gitconfig: pathlib.Path, set_gitconfig: pathlib.Path, + vcs_email: str, ) -> None: """Test gitconfig fixture.""" output = run(["git", "config", "--get", "user.email"]) From a0ba4bc1200de2b35c4f838c085049f92a93be57 Mon Sep 17 00:00:00 2001 From: Tony Narlock Date: Sun, 13 Oct 2024 06:43:44 -0500 Subject: [PATCH 5/8] refactor(test[pytest plugin]) Consolidate `pytester` test --- tests/test_pytest_plugin.py | 87 +++++++------------------------------ 1 file changed, 15 insertions(+), 72 deletions(-) diff --git a/tests/test_pytest_plugin.py b/tests/test_pytest_plugin.py index db807f36..97c99512 100644 --- a/tests/test_pytest_plugin.py +++ b/tests/test_pytest_plugin.py @@ -36,11 +36,14 @@ def test_create_svn_remote_repo( assert svn_remote_1 != svn_remote_2 -def test_create_git_remote_repo_and_git_sync( +def test_git_fixtures( pytester: pytest.Pytester, monkeypatch: pytest.MonkeyPatch, + tmp_path: pathlib.Path, ) -> None: - """Tests pytest plugin integration for create_git_remote_repo() and GitSync().""" + """Tests for libvcs pytest plugin git configuration.""" + monkeypatch.setenv("HOME", str(tmp_path)) + # Initialize variables pytester.plugins = ["pytest_plugin"] pytester.makefile( @@ -58,6 +61,10 @@ def test_create_git_remote_repo_and_git_sync( import pathlib import pytest +@pytest.fixture(scope="session") +def vcs_email() -> str: + return "custom_email@testemail.com" + @pytest.fixture(autouse=True) def setup( request: pytest.FixtureRequest, @@ -75,7 +82,11 @@ def setup( import pathlib from libvcs.sync.git import GitSync -from libvcs.pytest_plugin import CreateRepoPytestFixtureFn +from libvcs.pytest_plugin import ( + CreateRepoPytestFixtureFn, + git_remote_repo_single_commit_post_init +) + def test_repo_git_remote_repo_and_sync( create_git_remote_repo: CreateRepoPytestFixtureFn, @@ -93,75 +104,7 @@ def test_repo_git_remote_repo_and_sync( assert git_repo_checkout_dir.exists() assert pathlib.Path(git_repo_checkout_dir / ".git").exists() - """, - ), - } - first_test_key = next(iter(files.keys())) - first_test_filename = str(tests_path / first_test_key) - - tests_path.mkdir() - for file_name, text in files.items(): - test_file = tests_path / file_name - test_file.write_text( - text, - encoding="utf-8", - ) - - # Test - result = pytester.runpytest(str(first_test_filename)) - result.assert_outcomes(passed=1) - - -def test_git_remote_repo( - pytester: pytest.Pytester, - monkeypatch: pytest.MonkeyPatch, - tmp_path: pathlib.Path, -) -> None: - """Tests for libvcs pytest plugin git configuration.""" - monkeypatch.setenv("HOME", str(tmp_path)) - - # Initialize variables - pytester.plugins = ["pytest_plugin"] - pytester.makefile( - ".ini", - pytest=textwrap.dedent( - """ -[pytest] -addopts=-vv - """.strip(), - ), - ) - pytester.makeconftest( - textwrap.dedent( - r""" -import pathlib -import pytest - -@pytest.fixture(scope="session") -def vcs_email() -> str: - return "custom_email@testemail.com" - -@pytest.fixture(autouse=True) -def setup( - request: pytest.FixtureRequest, - gitconfig: pathlib.Path, - set_home: pathlib.Path, -) -> None: - pass - """, - ), - ) - tests_path = pytester.path / "tests" - files = { - "example.py": textwrap.dedent( - """ -import pathlib -from libvcs.sync.git import GitSync -from libvcs.pytest_plugin import ( - CreateRepoPytestFixtureFn, - git_remote_repo_single_commit_post_init -) def test_git_bare_repo_sync_and_commit( create_git_remote_bare_repo: CreateRepoPytestFixtureFn, @@ -206,7 +149,7 @@ def test_git_bare_repo_sync_and_commit( # Test result = pytester.runpytest(str(first_test_filename)) - result.assert_outcomes(passed=1) + result.assert_outcomes(passed=2) def test_gitconfig( From d35b501891dd456eea28c592c664afaf8c6db8af Mon Sep 17 00:00:00 2001 From: Tony Narlock Date: Sun, 13 Oct 2024 06:45:04 -0500 Subject: [PATCH 6/8] tests(pytest plugin) Move gitconfig test up --- tests/test_pytest_plugin.py | 40 ++++++++++++++++++------------------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/tests/test_pytest_plugin.py b/tests/test_pytest_plugin.py index 97c99512..ee91d742 100644 --- a/tests/test_pytest_plugin.py +++ b/tests/test_pytest_plugin.py @@ -36,6 +36,26 @@ def test_create_svn_remote_repo( assert svn_remote_1 != svn_remote_2 +def test_gitconfig( + gitconfig: pathlib.Path, + set_gitconfig: pathlib.Path, + vcs_email: str, +) -> None: + """Test gitconfig fixture.""" + output = run(["git", "config", "--get", "user.email"]) + used_config_file_output = run( + [ + "git", + "config", + "--show-origin", + "--get", + "user.email", + ], + ) + assert str(gitconfig) in used_config_file_output + assert vcs_email in output, "Should use our fixture config and home directory" + + def test_git_fixtures( pytester: pytest.Pytester, monkeypatch: pytest.MonkeyPatch, @@ -150,23 +170,3 @@ def test_git_bare_repo_sync_and_commit( # Test result = pytester.runpytest(str(first_test_filename)) result.assert_outcomes(passed=2) - - -def test_gitconfig( - gitconfig: pathlib.Path, - set_gitconfig: pathlib.Path, - vcs_email: str, -) -> None: - """Test gitconfig fixture.""" - output = run(["git", "config", "--get", "user.email"]) - used_config_file_output = run( - [ - "git", - "config", - "--show-origin", - "--get", - "user.email", - ], - ) - assert str(gitconfig) in used_config_file_output - assert vcs_email in output, "Should use our fixture config and home directory" From bac8294fbe2b24b8252a5e726ca21224002eb32e Mon Sep 17 00:00:00 2001 From: Tony Narlock Date: Sun, 13 Oct 2024 05:56:10 -0500 Subject: [PATCH 7/8] docs(pytest plugin) Document new fixtures --- docs/pytest-plugin.md | 39 +++++++++++++++++++++++++++++++++++++-- 1 file changed, 37 insertions(+), 2 deletions(-) diff --git a/docs/pytest-plugin.md b/docs/pytest-plugin.md index 64e8e92e..22aeb435 100644 --- a/docs/pytest-plugin.md +++ b/docs/pytest-plugin.md @@ -37,7 +37,7 @@ This pytest plugin works by providing {ref}`pytest fixtures `_): {func}`vcs_user` + - For git only: {func}`git_commit_envvars` These ensure that repositories can be cloned and created without unnecessary warnings. @@ -74,10 +79,19 @@ def setup(set_home: None): pass ``` -### Setting a Default VCS Configuration +### VCS Configuration #### Git +You can override the default author used in {func}`git_remote_repo` and other +fixtures via {func}`vcs_name`, {func}`vcs_email`, and {func}`vcs_user`: + +``` +@pytest.fixture(scope="session") +def vcs_name() -> str: + return "My custom name" +``` + Use the {func}`set_gitconfig` fixture with `autouse=True`: ```python @@ -88,6 +102,27 @@ def setup(set_gitconfig: None): pass ``` +Sometimes, `set_getconfig` via `GIT_CONFIG` doesn't apply as expected. For those +cases, you can use {func}`git_commit_envvars`: + +```python +import pytest + +@pytest.fixture +def my_git_repo( + create_git_remote_repo: CreateRepoPytestFixtureFn, + gitconfig: pathlib.Path, + git_commit_envvars: "_ENV", +) -> pathlib.Path: + """Copy the session-scoped Git repository to a temporary directory.""" + repo_path = create_git_remote_repo() + git_remote_repo_single_commit_post_init( + remote_repo_path=repo_path, + env=git_commit_envvars, + ) + return repo_path +``` + #### Mercurial Use the {func}`set_hgconfig` fixture with `autouse=True`: From 673c1e0982abc59ded900169b984b77a539d441b Mon Sep 17 00:00:00 2001 From: Tony Narlock Date: Sun, 13 Oct 2024 06:51:32 -0500 Subject: [PATCH 8/8] docs(CHANGES) Note pytest plugin fixture changes --- CHANGES | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/CHANGES b/CHANGES index 1c7283f2..b0903eee 100644 --- a/CHANGES +++ b/CHANGES @@ -15,11 +15,25 @@ $ pip install --user --upgrade --pre libvcs +### New features + +#### pytest plugin: Authorship fixtures (#476) + +- New, customizable session-scoped fixtures for default committer on Mercurial and Git: + - Name: {func}`libvcs.pytest_plugin.vcs_name` + - Email: {func}`libvcs.pytest_plugin.vcs_email` + - User (e.g. _`user `_): {func}`libvcs.pytest_plugin.vcs_user` + - For git only: {func}`libvcs.pytest_plugin.git_commit_envvars` + +#### pytest plugins: Default repos use authorship fixtures (#476) + +New repos will automatically apply these session-scoped fixtures. + ## libvcs 0.32.3 (2024-10-13) ### Bug fixes -- Pytest fixtures `hg_remote_repo_single_commit_post_init()` and `git_remote_repo_single_commit_post_init()` now support passing `env` for VCS configuration. +- Pytest fixtures `hg_remote_repo_single_commit_post_init()` and `git_remote_repo_single_commit_post_init()` now support passing `env` for VCS configuration. Both functions accept `hgconfig` and `gitconfig` fixtures, now applied in the `hg_repo` and `git_repo` fixtures.