import os import sys import pytest from _pytest import monkeypatch from flask import Flask from flask.globals import app_ctx as _app_ctx @pytest.fixture(scope="session", autouse=True) def _standard_os_environ(): """Set up ``os.environ`` at the start of the test session to have standard values. Returns a list of operations that is used by :func:`._reset_os_environ` after each test. """ mp = monkeypatch.MonkeyPatch() out = ( (os.environ, "FLASK_ENV_FILE", monkeypatch.notset), (os.environ, "FLASK_APP", monkeypatch.notset), (os.environ, "FLASK_DEBUG", monkeypatch.notset), (os.environ, "FLASK_RUN_FROM_CLI", monkeypatch.notset), (os.environ, "WERKZEUG_RUN_MAIN", monkeypatch.notset), ) for _, key, value in out: if value is monkeypatch.notset: mp.delenv(key, False) else: mp.setenv(key, value) yield out mp.undo() @pytest.fixture(autouse=True) def _reset_os_environ(monkeypatch, _standard_os_environ): """Reset ``os.environ`` to the standard environ after each test, in case a test changed something without cleaning up. """ monkeypatch._setitem.extend(_standard_os_environ) @pytest.fixture def app(): app = Flask("flask_test", root_path=os.path.dirname(__file__)) app.config.update( TESTING=True, SECRET_KEY="test key", ) return app @pytest.fixture def app_ctx(app): with app.app_context() as ctx: yield ctx @pytest.fixture def req_ctx(app): with app.test_request_context() as ctx: yield ctx @pytest.fixture def client(app): return app.test_client() @pytest.fixture def test_apps(monkeypatch): monkeypatch.syspath_prepend(os.path.join(os.path.dirname(__file__), "test_apps")) original_modules = set(sys.modules.keys()) yield # Remove any imports cached during the test. Otherwise "import app" # will work in the next test even though it's no longer on the path. for key in sys.modules.keys() - original_modules: sys.modules.pop(key) @pytest.fixture(autouse=True) def leak_detector(): """Fails if any app contexts are still pushed when a test ends. Pops all contexts so subsequent tests are not affected. """ yield leaks = [] while _app_ctx: leaks.append(_app_ctx._get_current_object()) _app_ctx.pop() assert not leaks @pytest.fixture def modules_tmp_path(tmp_path, monkeypatch): """A temporary directory added to sys.path.""" rv = tmp_path / "modules_tmp" rv.mkdir() monkeypatch.syspath_prepend(os.fspath(rv)) return rv @pytest.fixture def modules_tmp_path_prefix(modules_tmp_path, monkeypatch): monkeypatch.setattr(sys, "prefix", os.fspath(modules_tmp_path)) return modules_tmp_path @pytest.fixture def site_packages(modules_tmp_path, monkeypatch): """Create a fake site-packages.""" py_dir = f"python{sys.version_info.major}.{sys.version_info.minor}" rv = modules_tmp_path / "lib" / py_dir / "site-packages" rv.mkdir(parents=True) monkeypatch.syspath_prepend(os.fspath(rv)) return rv @pytest.fixture def purge_module(request): def inner(name): request.addfinalizer(lambda: sys.modules.pop(name, None)) return inner