Skip to content

Commit 67b1161

Browse files
committed
pytester: testdir: add makefiles helper
This is a sane method to create a set of files, allowing for absolute paths. Ref: #6578 Ref: #6579
1 parent d0cb160 commit 67b1161

File tree

3 files changed

+84
-0
lines changed

3 files changed

+84
-0
lines changed

changelog/6603.feature.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Add :py:func:`~_pytest.pytester.Testdir.makefiles` helper to :ref:`testdir`, which allows to more easily create files with absolute paths.

src/_pytest/pytester.py

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
import re
88
import subprocess
99
import sys
10+
import textwrap
1011
import time
1112
import traceback
1213
from fnmatch import fnmatch
@@ -672,6 +673,32 @@ def maketxtfile(self, *args, **kwargs):
672673
"""Shortcut for .makefile() with a .txt extension."""
673674
return self._makefile(".txt", args, kwargs)
674675

676+
def makefiles(
677+
self, files: Dict[str, str], allow_outside_tmpdir=False
678+
) -> List[Path]:
679+
"""Create the given set of files.
680+
681+
Unlike other helpers like :func:`makepyfile` this allows to specify
682+
absolute paths, which need to be below :attr:`tmpdir` by default
683+
(use `allow_outside_tmpdir` to write arbitrary files).
684+
"""
685+
paths = []
686+
if allow_outside_tmpdir:
687+
validated_files = tuple((Path(k), v) for k, v in files.items())
688+
else:
689+
tmpdir_path = Path(self.tmpdir)
690+
validated_files = tuple(
691+
(Path(k).absolute().relative_to(tmpdir_path), v)
692+
for k, v in files.items()
693+
)
694+
695+
for fpath, content in validated_files:
696+
path = Path(self.tmpdir).joinpath(fpath)
697+
with open(str(path), "w") as fp:
698+
fp.write(textwrap.dedent(content))
699+
paths.append(path)
700+
return paths
701+
675702
def syspathinsert(self, path=None):
676703
"""Prepend a directory to sys.path, defaults to :py:attr:`tmpdir`.
677704

testing/test_pytester.py

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,11 @@
1+
import builtins
12
import os
3+
import re
24
import subprocess
35
import sys
46
import time
7+
from collections import OrderedDict
8+
from contextlib import contextmanager
59
from typing import List
610

711
import py.path
@@ -11,9 +15,11 @@
1115
from _pytest.config import PytestPluginManager
1216
from _pytest.main import ExitCode
1317
from _pytest.outcomes import Failed
18+
from _pytest.pathlib import Path
1419
from _pytest.pytester import CwdSnapshot
1520
from _pytest.pytester import HookRecorder
1621
from _pytest.pytester import LineMatcher
22+
from _pytest.pytester import MonkeyPatch
1723
from _pytest.pytester import SysModulesSnapshot
1824
from _pytest.pytester import SysPathsSnapshot
1925
from _pytest.pytester import Testdir
@@ -710,3 +716,53 @@ def test_error2(bad_fixture):
710716
result.assert_outcomes(error=2)
711717

712718
assert result.parseoutcomes() == {"error": 2}
719+
720+
721+
def test_testdir_makefiles(testdir: Testdir, monkeypatch: MonkeyPatch) -> None:
722+
tmpdir = testdir.tmpdir
723+
724+
abspath = str(tmpdir / "bar")
725+
created_paths = testdir.makefiles(OrderedDict({"foo": "", abspath: ""}))
726+
p1 = created_paths[0]
727+
assert isinstance(p1, Path)
728+
relpath = tmpdir / "foo"
729+
assert str(p1) == str(relpath)
730+
731+
p2 = created_paths[1]
732+
assert p2.exists()
733+
assert str(p2) == abspath
734+
735+
assert testdir.makefiles({}) == []
736+
737+
# Disallows creation outside of tmpdir by default.
738+
with pytest.raises(
739+
ValueError,
740+
match="'/abspath' does not start with '{}'".format(re.escape(str(tmpdir))),
741+
):
742+
testdir.makefiles({"shouldnotbecreated": "", "/abspath": ""})
743+
# Validation before creating anything.
744+
assert not Path("shouldnotbecreated").exists()
745+
746+
# Support writing arbitrary files on request.
747+
open_calls = []
748+
orig_open = builtins.open
749+
750+
@contextmanager
751+
def mocked_open(*args):
752+
open_calls.append(["__enter__", args])
753+
with orig_open(os.devnull, *args[1:]) as fp:
754+
yield fp
755+
756+
with monkeypatch.context() as mp:
757+
mp.setattr(builtins, "open", mocked_open)
758+
created_paths = testdir.makefiles({"/abspath": ""}, allow_outside_tmpdir=True)
759+
assert created_paths == [Path("/abspath")]
760+
assert open_calls == [["__enter__", ("/abspath", "w")]]
761+
762+
# Duplicated files (absolute and relative).
763+
created_paths = testdir.makefiles(OrderedDict({"bar": "1", abspath: "2"}))
764+
with open("bar", "r") as fp:
765+
assert fp.read() == "2"
766+
created_paths = testdir.makefiles(OrderedDict({abspath: "2", "bar": "1"}))
767+
with open("bar", "r") as fp:
768+
assert fp.read() == "1"

0 commit comments

Comments
 (0)