Skip to content

pytest fixtures: hg / git configurations to session scope #475

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 10 commits into from
Oct 12, 2024
10 changes: 10 additions & 0 deletions CHANGES
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,16 @@ $ pip install --user --upgrade --pre libvcs

<!-- Maintainers, insert changes / features for the next release here -->

## Breaking changes

### pytest fixtures: Session-scoped `hgconfig` and `gitconfig` (#475)

These are now set by `set_hgconfig` and `set_gitconfig`, which set `HGRCPATH` and `GIT_CONFIG`, instead of overriding `HOME`.

## Documentation

- Updates for pytest plugin documentation.

## libvcs 0.31.0 (2024-10-12)

### Breaking changes
Expand Down
90 changes: 57 additions & 33 deletions docs/pytest-plugin.md
Original file line number Diff line number Diff line change
@@ -1,16 +1,14 @@
(pytest_plugin)=

# `pytest` plugin
# `pytest` Plugin

Create git, svn, and hg repos on the fly in [pytest].
With libvcs's pytest plugin for [pytest], you can easily create Git, SVN, and Mercurial repositories on the fly.

```{seealso} Using libvcs?
```{seealso} Are you using libvcs?

Do you want more flexibility? Correctness? Power? Defaults changed? [Connect with us] on the tracker, we want to know
your case, we won't stabilize APIs until we're sure everything is by the book.
Looking for more flexibility, correctness, or power? Need different defaults? [Connect with us] on GitHub. We'd love to hear about your use case—APIs won't be stabilized until we're confident everything meets expectations.

[connect with us]: https://github.com/vcs-python/libvcs/discussions

```

```{module} libvcs.pytest_plugin
Expand All @@ -21,66 +19,92 @@ your case, we won't stabilize APIs until we're sure everything is by the book.

## Usage

Install `libvcs` via the python package manager of your choosing, e.g.
Install `libvcs` using your preferred Python package manager:

```console
$ pip install libvcs
```

The pytest plugin will automatically be detected via pytest, and the fixtures will be added.
Pytest will automatically detect the plugin, and its fixtures will be available.

## Fixtures

`pytest-vcs` works through providing {ref}`pytest fixtures <pytest:fixtures-api>` - so read up on
those!
This pytest plugin works by providing {ref}`pytest fixtures <pytest:fixtures-api>`. The plugin's fixtures ensure that a fresh Git, Subversion, or Mercurial repository is available for each test. It utilizes [session-scoped fixtures] to cache initial repositories, improving performance across tests.

The plugin's fixtures guarantee a fresh git repository every test.
[session-scoped fixtures]: https://docs.pytest.org/en/8.3.x/how-to/fixtures.html#fixture-scopes

(recommended-fixtures)=

## Recommended fixtures
## Recommended Fixtures

These fixtures are automatically used when the plugin is enabled and `pytest` is run.
When the plugin is enabled and `pytest` is run, these fixtures are automatically used:

- Creating temporary, test directories for:
- Create temporary test directories for:
- `/home/` ({func}`home_path`)
- `/home/${user}` ({func}`user_path`)
- Setting your home directory
- Patch `$HOME` to point to {func}`user_path` ({func}`set_home`)
- Set default configuration
- Set the home directory:
- Patch `$HOME` to point to {func}`user_path` using ({func}`set_home`)
- Create configuration files:
- `.gitconfig` via {func}`gitconfig`
- `.hgrc` via {func}`hgconfig`
- Set default VCS configurations:
- Use {func}`hgconfig` for [`HGRCPATH`] via {func}`set_hgconfig`
- Use {func}`gitconfig` for [`GIT_CONFIG`] via {func}`set_gitconfig`

These ensure that repositories can be cloned and created without unnecessary warnings.

[`HGRCPATH`]: https://www.mercurial-scm.org/doc/hg.1.html#:~:text=UNIX%2Dlike%20environments.-,HGRCPATH,-If%20not%20set
[`GIT_CONFIG`]: https://git-scm.com/docs/git-config#Documentation/git-config.txt-GITCONFIG

## Bootstrapping pytest in `conftest.py`

To configure the above fixtures with `autouse=True`, add them to your `conftest.py` file or test file, depending on the desired scope.

_Why aren't these fixtures added automatically by the plugin?_ This design choice promotes explicitness, adhering to best practices for pytest plugins and Python packages.

### Setting a Temporary Home Directory

To set a temporary home directory, use the {func}`set_home` fixture with `autouse=True`:

```python
import pytest

@pytest.fixture(autouse=True)
def setup(set_home: None):
pass
```

- `.gitconfig`, via {func}`gitconfig`:
- `.hgrc`, via {func}`hgconfig`:
### Setting a Default VCS Configuration

These are set to ensure you can correctly clone and create repositories without without extra
warnings.
#### Git

## Bootstrapping pytest in your `conftest.py`
Use the {func}`set_gitconfig` fixture with `autouse=True`:

The most common scenario is you will want to configure the above fixtures with `autouse`.
```python
import pytest

_Why doesn't the plugin automatically add them?_ It's part of being a decent pytest plugin and
python package: explicitness.
@pytest.fixture(autouse=True)
def setup(set_gitconfig: None):
pass
```

(set_home)=
#### Mercurial

### Setting a temporary home directory
Use the {func}`set_hgconfig` fixture with `autouse=True`:

```python
import pytest

@pytest.fixture(autouse=True)
def setup(
set_home: None,
):
def setup(set_hgconfig: None):
pass
```

## See examples
## Examples

View libvcs's own [tests/](https://github.com/vcs-python/libvcs/tree/master/tests)
For usage examples, refer to libvcs's own [tests/](https://github.com/vcs-python/libvcs/tree/master/tests).

## API reference
## API Reference

```{eval-rst}
.. automodule:: libvcs.pytest_plugin
Expand Down
64 changes: 39 additions & 25 deletions src/libvcs/pytest_plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -108,17 +108,25 @@ def set_home(
monkeypatch.setenv("HOME", str(user_path))


@pytest.fixture
vcs_email = "[email protected]"


@pytest.fixture(scope="session")
@skip_if_git_missing
def gitconfig(user_path: pathlib.Path, set_home: pathlib.Path) -> pathlib.Path:
def gitconfig(
user_path: pathlib.Path,
) -> pathlib.Path:
"""Return git configuration, pytest fixture."""
gitconfig = user_path / ".gitconfig"
user_email = "[email protected]"

if gitconfig.exists():
return gitconfig

gitconfig.write_text(
textwrap.dedent(
f"""
[user]
email = {user_email}
email = {vcs_email}
name = {getpass.getuser()}
[color]
diff = auto
Expand All @@ -127,26 +135,26 @@ def gitconfig(user_path: pathlib.Path, set_home: pathlib.Path) -> pathlib.Path:
encoding="utf-8",
)

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 user_email in output, "Should use our fixture config and home directory"

return gitconfig


@pytest.fixture
@skip_if_git_missing
def set_gitconfig(
monkeypatch: pytest.MonkeyPatch,
gitconfig: pathlib.Path,
) -> pathlib.Path:
"""Set git configuration."""
monkeypatch.setenv("GIT_CONFIG", str(gitconfig))
return gitconfig


@pytest.fixture(scope="session")
@skip_if_hg_missing
def hgconfig(user_path: pathlib.Path, set_home: pathlib.Path) -> pathlib.Path:
"""Return Mercurial configuration, pytest fixture."""
def hgconfig(
user_path: pathlib.Path,
) -> pathlib.Path:
"""Return Mercurial configuration."""
hgrc = user_path / ".hgrc"
hgrc.write_text(
textwrap.dedent(
Expand All @@ -164,6 +172,17 @@ def hgconfig(user_path: pathlib.Path, set_home: pathlib.Path) -> pathlib.Path:
return hgrc


@pytest.fixture
@skip_if_hg_missing
def set_hgconfig(
monkeypatch: pytest.MonkeyPatch,
hgconfig: pathlib.Path,
) -> pathlib.Path:
"""Set Mercurial configuration."""
monkeypatch.setenv("HGRCPATH", str(hgconfig))
return hgconfig


@pytest.fixture
def projects_path(
user_path: pathlib.Path,
Expand Down Expand Up @@ -490,8 +509,7 @@ def svn_remote_repo(
create_svn_remote_repo: CreateRepoPytestFixtureFn,
) -> pathlib.Path:
"""Pre-made. Local file:// based SVN server."""
repo_path = create_svn_remote_repo()
return repo_path
return create_svn_remote_repo()


@pytest.fixture(scope="session")
Expand Down Expand Up @@ -690,8 +708,6 @@ def add_doctest_fixtures(
doctest_namespace: dict[str, Any],
tmp_path: pathlib.Path,
set_home: pathlib.Path,
gitconfig: pathlib.Path,
hgconfig: pathlib.Path,
create_git_remote_repo: CreateRepoPytestFixtureFn,
create_svn_remote_repo: CreateRepoPytestFixtureFn,
create_hg_remote_repo: CreateRepoPytestFixtureFn,
Expand All @@ -704,7 +720,6 @@ def add_doctest_fixtures(
return
doctest_namespace["tmp_path"] = tmp_path
if shutil.which("git"):
doctest_namespace["gitconfig"] = gitconfig
doctest_namespace["create_git_remote_repo"] = functools.partial(
create_git_remote_repo,
remote_repo_post_init=git_remote_repo_single_commit_post_init,
Expand All @@ -719,7 +734,6 @@ def add_doctest_fixtures(
remote_repo_post_init=svn_remote_repo_single_commit_post_init,
)
if shutil.which("hg"):
doctest_namespace["hgconfig"] = hgconfig
doctest_namespace["create_hg_remote_repo_bare"] = create_hg_remote_repo
doctest_namespace["create_hg_remote_repo"] = functools.partial(
create_hg_remote_repo,
Expand Down
14 changes: 5 additions & 9 deletions tests/sync/test_hg.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,24 +8,20 @@
from libvcs import exc
from libvcs._internal.run import run
from libvcs._internal.shortcuts import create_project
from libvcs.pytest_plugin import CreateRepoPytestFixtureFn
from libvcs.sync.hg import HgSync

if not shutil.which("hg"):
pytestmark = pytest.mark.skip(reason="hg is not available")


@pytest.fixture
def hg_remote_repo(
set_home: pathlib.Path,
hgconfig: pathlib.Path,
create_hg_remote_repo: CreateRepoPytestFixtureFn,
@pytest.fixture(autouse=True)
def set_hgconfig(
set_hgconfig: pathlib.Path,
) -> pathlib.Path:
"""Create a remote hg repository."""
return create_hg_remote_repo()
"""Set mercurial configuration."""
return set_hgconfig


@pytest.mark.usefixtures("set_home", "hgconfig")
def test_hg_sync(
tmp_path: pathlib.Path,
projects_path: pathlib.Path,
Expand Down
3 changes: 2 additions & 1 deletion tests/sync/test_svn.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,8 @@ def test_svn_sync(tmp_path: pathlib.Path, svn_remote_repo: pathlib.Path) -> None


def test_svn_sync_with_files(
tmp_path: pathlib.Path, svn_remote_repo_with_files: pathlib.Path
tmp_path: pathlib.Path,
svn_remote_repo_with_files: pathlib.Path,
) -> None:
"""Tests for SvnSync."""
repo_name = "my_svn_project"
Expand Down
22 changes: 21 additions & 1 deletion tests/test_pytest_plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@

import pytest

from libvcs.pytest_plugin import CreateRepoPytestFixtureFn
from libvcs._internal.run import run
from libvcs.pytest_plugin import CreateRepoPytestFixtureFn, vcs_email


@pytest.mark.skipif(not shutil.which("git"), reason="git is not available")
Expand Down Expand Up @@ -109,3 +110,22 @@ def test_repo_git_remote_checkout(
# Test
result = pytester.runpytest(str(first_test_filename))
result.assert_outcomes(passed=1)


def test_gitconfig(
gitconfig: pathlib.Path,
set_gitconfig: pathlib.Path,
) -> 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"
Loading