You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

备份并恢复目录的Python上下文管理器导致终端状态异常的问题咨询

备份并恢复目录的Python上下文管理器导致终端状态异常的问题咨询

我写了一个上下文管理器,用于临时备份目录并在退出时恢复它,实现代码如下:

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)

下面是使用这个上下文管理器的测试用例:

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 是我的项目根目录,我从这个目录运行pytest。
  • 测试能通过,但执行后我的终端会进入不一致的状态。比如运行测试后,我遇到了这样的行为:
(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

目录看起来是空的,直到我运行cd .来“刷新”它才恢复正常。


额外观察

  • 这个问题不只是pytest会遇到,比如测试后运行source ~/.config/fish/config.fish也会产生类似错误(getcwd: cannot access parent directories)。
  • $PWD和$VIRTUAL_ENV在测试前后都没有变化,说明虚拟环境本身不是问题所在。

调试尝试

我尝试修改上下文管理器,在删除/恢复原目录前切换工作目录:

@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)

虽然测试还是能通过,但执行后终端状态仍然不一致,我还是必须手动执行cd .来解决问题。


问题

  • 为什么会发生这种情况?这和shell、pytest还是虚拟环境有关?
  • 我该如何修改我的上下文管理器或测试设置,才能确保测试执行后终端状态完全恢复正常?

更新

  • 在删除和替换仓库前切换目录确实有效,如果不这么做,测试后的状态无法正确重置,其他测试会受影响并失败。
  • 我尝试通过bash脚本运行,没用。
  • 我尝试在子shell中运行,没用。
  • 我尝试使用pytest finalizers,也没用。

备注:内容来源于stack exchange,提问作者Michiel Karrenbelt

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.14 15:58:02