Skip to content

Commit 80a4844

Browse files
committed
pytester: testdir: add makefiles helper
This is a sane method to create a set of files, allowing for absolute paths. Ref: pytest-dev#6578 Ref: pytest-dev#6579
1 parent a7dfc6f commit 80a4844

File tree

3 files changed

+83
-0
lines changed

3 files changed

+83
-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
@@ -8,6 +8,7 @@
88
import shlex
99
import subprocess
1010
import sys
11+
import textwrap
1112
import time
1213
import traceback
1314
from fnmatch import fnmatch
@@ -741,6 +742,32 @@ def maketxtfile(self, *args, **kwargs):
741742
"""Shortcut for .makefile() with a .txt extension."""
742743
return self._makefile(".txt", args, kwargs)
743744

745+
def makefiles(
746+
self, files: Dict[str, str], allow_outside_tmpdir=False
747+
) -> List[Path]:
748+
"""Create the given set of files.
749+
750+
Unlike other helpers like :func:`makepyfile` this allows to specify
751+
absolute paths, which need to be below :attr:`tmpdir` by default
752+
(use `allow_outside_tmpdir` to write arbitrary files).
753+
"""
754+
paths = []
755+
if allow_outside_tmpdir:
756+
validated_files = tuple((Path(k), v) for k, v in files.items())
757+
else:
758+
tmpdir_path = Path(self.tmpdir)
759+
validated_files = tuple(
760+
(Path(k).absolute().relative_to(tmpdir_path), v)
761+
for k, v in files.items()
762+
)
763+
764+
for fpath, content in validated_files:
765+
path = Path(self.tmpdir).joinpath(fpath)
766+
with open(str(path), "w") as fp:
767+
fp.write(textwrap.dedent(content))
768+
paths.append(path)
769+
return paths
770+
744771
def syspathinsert(self, path=None):
745772
"""Prepend a directory to sys.path, defaults to :py:attr:`tmpdir`.
746773

testing/test_pytester.py

Lines changed: 55 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
@@ -10,6 +14,7 @@
1014
import pytest
1115
from _pytest.config import ExitCode
1216
from _pytest.config import PytestPluginManager
17+
from _pytest.pathlib import Path
1318
from _pytest.pytester import CwdSnapshot
1419
from _pytest.pytester import HookRecorder
1520
from _pytest.pytester import LineMatcher
@@ -1215,3 +1220,53 @@ def pytest_collect_file(path, parent):
12151220
"=* 1 failed in *",
12161221
]
12171222
)
1223+
1224+
1225+
def test_testdir_makefiles(testdir: Testdir, monkeypatch: MonkeyPatch) -> None:
1226+
tmpdir = testdir.tmpdir
1227+
1228+
abspath = str(tmpdir / "bar")
1229+
created_paths = testdir.makefiles(OrderedDict({"foo": "", abspath: ""}))
1230+
p1 = created_paths[0]
1231+
assert isinstance(p1, Path)
1232+
relpath = tmpdir / "foo"
1233+
assert str(p1) == str(relpath)
1234+
1235+
p2 = created_paths[1]
1236+
assert p2.exists()
1237+
assert str(p2) == abspath
1238+
1239+
assert testdir.makefiles({}) == []
1240+
1241+
# Disallows creation outside of tmpdir by default.
1242+
with pytest.raises(
1243+
ValueError,
1244+
match="'/abspath' does not start with '{}'".format(re.escape(str(tmpdir))),
1245+
):
1246+
testdir.makefiles({"shouldnotbecreated": "", "/abspath": ""})
1247+
# Validation before creating anything.
1248+
assert not Path("shouldnotbecreated").exists()
1249+
1250+
# Support writing arbitrary files on request.
1251+
open_calls = []
1252+
orig_open = builtins.open
1253+
1254+
@contextmanager
1255+
def mocked_open(*args):
1256+
open_calls.append(["__enter__", args])
1257+
with orig_open(os.devnull, *args[1:]) as fp:
1258+
yield fp
1259+
1260+
with monkeypatch.context() as mp:
1261+
mp.setattr(builtins, "open", mocked_open)
1262+
created_paths = testdir.makefiles({"/abspath": ""}, allow_outside_tmpdir=True)
1263+
assert created_paths == [Path("/abspath")]
1264+
assert open_calls == [["__enter__", ("/abspath", "w")]]
1265+
1266+
# Duplicated files (absolute and relative).
1267+
created_paths = testdir.makefiles(OrderedDict({"bar": "1", abspath: "2"}))
1268+
with open("bar", "r") as fp:
1269+
assert fp.read() == "2"
1270+
created_paths = testdir.makefiles(OrderedDict({abspath: "2", "bar": "1"}))
1271+
with open("bar", "r") as fp:
1272+
assert fp.read() == "1"

0 commit comments

Comments
 (0)