Skip to content

pytester: testdir: add makefiles helper #6603

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions changelog/6603.feature.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Add :py:func:`~_pytest.pytester.Testdir.makefiles` helper to :ref:`testdir`, which allows to more easily create files with absolute paths.
27 changes: 27 additions & 0 deletions src/_pytest/pytester.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import re
import subprocess
import sys
import textwrap
import time
import traceback
from fnmatch import fnmatch
Expand Down Expand Up @@ -672,6 +673,32 @@ def maketxtfile(self, *args, **kwargs):
"""Shortcut for .makefile() with a .txt extension."""
return self._makefile(".txt", args, kwargs)

def makefiles(
self, files: Dict[str, str], allow_outside_tmpdir=False
) -> List[Path]:
"""Create the given set of files.

Unlike other helpers like :func:`makepyfile` this allows to specify
absolute paths, which need to be below :attr:`tmpdir` by default
(use `allow_outside_tmpdir` to write arbitrary files).
"""
paths = []
if allow_outside_tmpdir:
validated_files = tuple((Path(k), v) for k, v in files.items())
else:
tmpdir_path = Path(self.tmpdir)
validated_files = tuple(
(Path(k).absolute().relative_to(tmpdir_path), v)
for k, v in files.items()
)

for fpath, content in validated_files:
path = Path(self.tmpdir).joinpath(fpath)
with open(str(path), "w") as fp:
fp.write(textwrap.dedent(content))
paths.append(path)
return paths

def syspathinsert(self, path=None):
"""Prepend a directory to sys.path, defaults to :py:attr:`tmpdir`.

Expand Down
56 changes: 56 additions & 0 deletions testing/test_pytester.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
import builtins
import os
import re
import subprocess
import sys
import time
from collections import OrderedDict
from contextlib import contextmanager
from typing import List

import py.path
Expand All @@ -11,9 +15,11 @@
from _pytest.config import PytestPluginManager
from _pytest.main import ExitCode
from _pytest.outcomes import Failed
from _pytest.pathlib import Path
from _pytest.pytester import CwdSnapshot
from _pytest.pytester import HookRecorder
from _pytest.pytester import LineMatcher
from _pytest.pytester import MonkeyPatch
from _pytest.pytester import SysModulesSnapshot
from _pytest.pytester import SysPathsSnapshot
from _pytest.pytester import Testdir
Expand Down Expand Up @@ -710,3 +716,53 @@ def test_error2(bad_fixture):
result.assert_outcomes(error=2)

assert result.parseoutcomes() == {"error": 2}


def test_testdir_makefiles(testdir: Testdir, monkeypatch: MonkeyPatch) -> None:
tmpdir = testdir.tmpdir

abspath = str(tmpdir / "bar")
created_paths = testdir.makefiles(OrderedDict({"foo": "", abspath: ""}))
p1 = created_paths[0]
assert isinstance(p1, Path)
relpath = tmpdir / "foo"
assert str(p1) == str(relpath)

p2 = created_paths[1]
assert p2.exists()
assert str(p2) == abspath

assert testdir.makefiles({}) == []

# Disallows creation outside of tmpdir by default.
with pytest.raises(
ValueError,
match="'/abspath' does not start with '{}'".format(re.escape(str(tmpdir))),
):
testdir.makefiles({"shouldnotbecreated": "", "/abspath": ""})
# Validation before creating anything.
assert not Path("shouldnotbecreated").exists()

# Support writing arbitrary files on request.
open_calls = []
orig_open = builtins.open

@contextmanager
def mocked_open(*args):
open_calls.append(["__enter__", args])
with orig_open(os.devnull, *args[1:]) as fp:
yield fp

with monkeypatch.context() as mp:
mp.setattr(builtins, "open", mocked_open)
created_paths = testdir.makefiles({"/abspath": ""}, allow_outside_tmpdir=True)
assert created_paths == [Path("/abspath")]
assert open_calls == [["__enter__", ("/abspath", "w")]]

# Duplicated files (absolute and relative).
created_paths = testdir.makefiles(OrderedDict({"bar": "1", abspath: "2"}))
with open("bar", "r") as fp:
assert fp.read() == "2"
created_paths = testdir.makefiles(OrderedDict({abspath: "2", "bar": "1"}))
with open("bar", "r") as fp:
assert fp.read() == "1"