Reputation: 143
I have a context manager that temporarily backs up a directory and restores it upon exit. Here's the implementation:
import shutil
import tempfile
from pathlib import Path
from contextlib import contextmanager
@contextmanager
def on_exit(directory: Path):
backup = Path(tempfile.mkdtemp(prefix="backup_")) / directory.name
shutil.copytree(directory, backup, symlinks=True)
try:
yield
finally:
shutil.rmtree(directory)
shutil.copytree(backup, directory, symlinks=True)
shutil.rmtree(backup)
Here's a test that uses the context manager:
from pathlib import Path
from src.utils import rollback
def get_repo_root() -> Path:
current_dir = Path(__file__).resolve()
while current_dir != current_dir.parent:
if (current_dir / ".git").exists():
return current_dir
current_dir = current_dir.parent
raise RuntimeError("No repository root found.")
def test_rollback_on_exit():
with rollback.on_exit(get_repo_root()):
...
directory
is the root directory of my project, and I run pytest from this directory.(dummy-py3.12) user@laptop ~/p/dummy (feat/rollback)> echo $PWD
/home/user/projects/dummy
(dummy-py3.12) user@laptop ~/p/dummy (feat/rollback)> echo $VIRTUAL_ENV
/home/user/.cache/pypoetry/virtualenvs/dummy-83ZLFGNe-py3.12
(dummy-py3.12) user@laptop ~/p/dummy (feat/rollback)> ls -la | wc -l
17
(dummy-py3.12) user@laptop ~/p/dummy (feat/rollback)> pytest tests/test_rollback.py
============================================= test session starts ==============================================
platform linux -- Python 3.12.3, pytest-7.2.1, pluggy-1.5.0
rootdir: /home/user/projects/dummy
plugins: cov-6.0.0, asyncio-0.21.1, typeguard-4.4.1, hypothesis-6.119.3
asyncio: mode=Mode.STRICT
collected 1 item
tests/test_rollback.py . [100%]
=============================================== warnings summary ===============================================
tests/test_rollback.py: 20 warnings
/home/user/.cache/pypoetry/virtualenvs/dummy-83ZLFGNe-py3.12/lib/python3.12/site-packages/typer/core.py:300: DeprecationWarning: 'autocompletion' is renamed to 'shell_complete'. The old name is deprecated and will be removed in Click 8.1. See the docs about 'Parameter' for information about new behavior.
_typer_param_setup_autocompletion_compat(self, autocompletion=autocompletion)
-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
======================================== 1 passed, 20 warnings in 0.74s ========================================
(dummy-py3.12) user@laptop ~/p/dummy (feat/rollback)> echo $PWD
/home/user/projects/dummy
(dummy-py3.12) user@laptop ~/p/dummy (feat/rollback)> echo $VIRTUAL_ENV
/home/user/.cache/pypoetry/virtualenvs/dummy-83ZLFGNe-py3.12
(dummy-py3.12) user@laptop ~/p/dummy> ls -la | wc -l
1
(dummy-py3.12) user@laptop ~/p/dummy> cd .
(dummy-py3.12) user@laptop ~/p/dummy (feat/rollback)> ls -la | wc -l
17
The directory appears empty until I run cd . to "refresh" it.
source ~/.config/fish/config.fish
after the test produces similar errors (getcwd: cannot access parent directories
).$PWD
and $VIRTUAL_ENV
remain unchanged before and after the test, suggesting the virtual environment itself is not the issue.I tried modifying the context manager to change the working directory before removing/restoring the original directory:
@contextmanager
def change_dir(target_path):
original_path = Path.cwd()
try:
os.chdir(target_path)
yield
finally:
os.chdir(original_path)
@contextmanager
def on_exit(directory: Path):
backup = Path(tempfile.mkdtemp(prefix="backup_")) / directory.name
shutil.copytree(directory, backup, symlinks=True)
try:
yield
finally:
with change_dir("/"):
shutil.rmtree(directory)
shutil.copytree(backup, directory, symlinks=True)
shutil.rmtree(backup)
While this also passes the test, the terminal state is still inconsistent after execution. I must manually refresh it (cd .
) to resolve the issue just the same.
change_dir
before removing and replacing the repository does work, without it the state after running tests is not properly reset and other tests are affected and fail.Upvotes: 1
Views: 55
Reputation: 54767
If you are removing and replacing the "current directory" (that is, "."), then this is expected. The shell knows your "current directory" by its inode number, not by its path. When you remove that directory, the inode points into empty space. Even though you create a new ".", that "." has a new inode number. The shell has cached the old one. The "cd ." fixes this.
Upvotes: 1