Skip to content
Merged
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