Option C Tools
Loading...
Searching...
No Matches
test_scaffold_venv.py
Go to the documentation of this file.
1#!/usr/bin/env python3
2# -*- coding: utf-8 -*-
3# tests/tests_scaffold/test_scaffold_venv.py
4
5"""
6Purpose
7-------
8FS-503 / FS-504 regression coverage — the ``include_venv`` flag on
9:func:`oct.scaffold.run_scaffold` must:
10
11* create ``.venv/`` via :mod:`venv` when True (the default),
12* populate :attr:`ScaffoldResult.venv_path`,
13* leave ``venv_path`` as ``None`` when False,
14* skip (not overwrite) an existing ``.venv/``,
15* downgrade :class:`OSError` from the builder to a warning message,
16* make the venv discoverable to :func:`check_venv_health` — i.e. the
17 scaffolded project is then "venv-found" even before activation.
18
19The tests stub :class:`venv.EnvBuilder` so the suite never shells out to
20``ensurepip`` — we are testing the *contract*, not the stdlib.
21
22Diagnostics
23-----------
24Domain: SCAFFOLD-TESTS
25Levels:
26 L2 — test lifecycle
27
28Contracts
29---------
30- ``include_venv=True`` → ``.venv/`` created; ``venv_path`` set.
31- ``include_venv=False`` → no ``.venv/``; ``venv_path is None``.
32- Pre-existing ``.venv/`` is recorded in ``skipped`` with a message.
33"""
34
35from __future__ import annotations
36
37from pathlib import Path
38
39import pytest
40
41from oct.scaffold import ScaffoldResult, run_scaffold
42from oct.scaffold import oct_scaffold as _scaffold_mod
43
44
46 """Drop-in replacement for :class:`venv.EnvBuilder` used in tests.
47
48 Creates the target directory (mirroring the stdlib side-effect) without
49 installing pip — cheap, deterministic, safe on Windows.
50 """
51
52 instances: list["_FakeEnvBuilder"] = []
53
54 def __init__(self, *, with_pip: bool = True, clear: bool = False,
55 upgrade_deps: bool = False) -> None:
56 self.with_pip = with_pip
57 self.clear = clear
58 self.upgrade_deps = upgrade_deps
59 self.created: list[str] = []
60 _FakeEnvBuilder.instances.append(self)
61
62 def create(self, env_dir: str) -> None:
63 Path(env_dir).mkdir(parents=True, exist_ok=True)
64 (Path(env_dir) / "pyvenv.cfg").write_text("home = /fake\n", encoding="utf-8")
65 self.created.append(env_dir)
66
67
68@pytest.fixture(autouse=True)
69def _stub_envbuilder(monkeypatch: pytest.MonkeyPatch):
70 """Swap in the fake EnvBuilder for every test in this module."""
71 _FakeEnvBuilder.instances = []
72 monkeypatch.setattr(_scaffold_mod.venv, "EnvBuilder", _FakeEnvBuilder)
73 yield
74
75
77 """FS-503: default include_venv=True materialises .venv/ at the project root."""
78 result = run_scaffold("demo", tmp_path, include_venv=True)
79 project = tmp_path / "demo"
80 venv_dir = project / ".venv"
81 assert venv_dir.is_dir()
82 assert (venv_dir / "pyvenv.cfg").is_file()
83 assert result.venv_path == venv_dir
84 assert venv_dir in result.created
85 assert len(_FakeEnvBuilder.instances) == 1
86
87
89 """FS-503: include_venv=False produces no .venv/ and venv_path stays None."""
90 result = run_scaffold("demo", tmp_path, include_venv=False)
91 project = tmp_path / "demo"
92 assert not (project / ".venv").exists()
93 assert result.venv_path is None
94 assert _FakeEnvBuilder.instances == []
95
96
98 """FS-503: include_venv=True combined with dry_run=True must not touch disk."""
99 result = run_scaffold("demo", tmp_path, dry_run=True, include_venv=True)
100 project = tmp_path / "demo"
101 assert not project.exists()
102 assert result.venv_path is None
103 assert _FakeEnvBuilder.instances == []
104 assert any(".venv" in m and "DRY RUN" in m for m in result.messages)
105
106
108 """FS-503: a second run over an existing tree records .venv/ in skipped."""
109 first = run_scaffold("demo", tmp_path, include_venv=True)
110 assert first.venv_path is not None # sanity
111 second = run_scaffold("demo", tmp_path, include_venv=True)
112 project = tmp_path / "demo"
113 assert (project / ".venv") in second.skipped
114 # The fake builder was only triggered on the first run.
115 assert len(_FakeEnvBuilder.instances) == 1
116 assert any("already exists" in m for m in second.messages)
117
118
120 tmp_path: Path, monkeypatch: pytest.MonkeyPatch,
121):
122 """FS-503: OSError from EnvBuilder must not abort scaffolding."""
123
124 class _ExplodingBuilder:
125 def __init__(self, **_: object) -> None:
126 pass
127
128 def create(self, env_dir: str) -> None: # noqa: ARG002
129 raise OSError("disk full")
130
131 monkeypatch.setattr(_scaffold_mod.venv, "EnvBuilder", _ExplodingBuilder)
132 result = run_scaffold("demo", tmp_path, include_venv=True)
133 project = tmp_path / "demo"
134 # Project is still scaffolded; only venv missing.
135 assert (project / ".option_c" / "octrc.json").is_file()
136 assert result.venv_path is None
137 assert any("venv creation failed" in m for m in result.messages)
138
139
141 """Smoke: include_venv flag preserves the ScaffoldResult contract."""
142 result = run_scaffold("demo", tmp_path, include_venv=True)
143 assert isinstance(result, ScaffoldResult)
144 assert isinstance(result.created, list)
145 assert isinstance(result.skipped, list)
146 assert isinstance(result.messages, list)
None __init__(self, *, bool with_pip=True, bool clear=False, bool upgrade_deps=False)
test_venv_creation_failure_downgrades_to_warning(Path tmp_path, pytest.MonkeyPatch monkeypatch)
_stub_envbuilder(pytest.MonkeyPatch monkeypatch)