Skip to content

Commit a960eb6

Browse files
[PR #3957/79fe2045 backport][3.10] Improve test suite handling of paths, temp files (#8083)
**This is a backport of PR #3957 as merged into master (79fe204).** * Improve test suite handling of paths, temp files This updates most uses of `os.path` to instead use `pathlib.Path`. Relatedly, and following up from #3955 (which replaced pytest's `tmpdir` fixture with `tmp_path`), this removes most ad-hoc tempfile creation in favor of the `tmp_path` fixture. Following conversion, unnecessary `os` and `tempfile` imports were removed. Most pathlib changes involve straightforward changes from `os` functions such as `os.mkdir` or `os.path.abspath` to their equivalent methods in `pathlib.Path`. Changing ad-hoc temporary path to `tmp_path` involved removing the `tmp_dir_path` fixture and replacing its functionality with `tmp_path` in `test_save_load` and `test_guess_filename_with_tempfile`. On `test_static_route_user_home` function: * I think that the intention of this test is to ensure that aiohttp correctly expands the home path if passed in a string. I refactored it to `pathlib.Path` and cut out duplication of `relative_to()` calls. But if it's not doing anything but expanding `~`, then it's testing the functionality of `pathlib.Path`, not aiohttp. On `unix_sockname` fixture: This fixture uses `tempfile.TemporaryDirectory`. Because it's a somewhat complicated fixture used across multiple test modules, I left it as-is for now. On `str(tmp_path)` and even `pathlib.Path(str(tmp_path))`: pytest uses `pathlib2` to provide `tmp_path` for Python 3.5 (only). This is mostly fine but it fails on a couple of corner cases, such as `os.symlink()` which blocks all but `str` and `PurePath` via isinstance type checking. In several cases, this requires conversion to string or conversion to string and then into `pathlib.Path` to maintain code compatibility. See: pytest-dev/pytest/issues/5017 * Correct test_guess_filename to use file object * Update symlink in tests; more guess_filename tests (cherry picked from commit 79fe204) <!-- Thank you for your contribution! --> ## What do these changes do? This updates most uses of `os.path` to instead use `pathlib.Path`. Relatedly, and following up from #3955 (which replaced pytest's `tmpdir` fixture with `tmp_path`), this removes most ad-hoc tempfile creation in favor of the `tmp_path` fixture. Following conversion, unnecessary `os` and `tempfile` imports were removed. Most pathlib changes involve straightforward changes from `os` functions such as `os.mkdir` or `os.path.abspath` to their equivalent methods in `pathlib.Path`. Changing ad-hoc temporary path to `tmp_path` involved removing the `tmp_dir_path` fixture and replacing its functionality with `tmp_path` in `test_save_load` and `test_guess_filename_with_tempfile`. On `test_static_route_user_home` function: * I think that the intention of this test is to ensure that aiohttp correctly expands the home path if passed in a string. I refactored it to `pathlib.Path` and cut out duplication of `relative_to()` calls. But if it's not doing anything but expanding `~`, then it's testing the functionality of `pathlib.Path`, not aiohttp. On `unix_sockname` fixture: This fixture uses `tempfile.TemporaryDirectory`. Because it's a somewhat complicated fixture used across multiple test modules, I left it as-is for now. On `str(tmp_path)` and even `pathlib.Path(str(tmp_path))`: pytest uses `pathlib2` to provide `tmp_path` for Python 3.5 (only). This is mostly fine but it fails on a couple of corner cases, such as `os.symlink()` which blocks all but `str` and `PurePath` via isinstance type checking. In several cases, this requires conversion to string or conversion to string and then into `pathlib.Path` to maintain code compatibility. See: pytest-dev/pytest/issues/5017 ## Are there changes in behavior for the user? These changes only affect the test suite and have no impact on the end user. ## Related issue number This is intended to address discussion following the simplistic changes from tmpdir to tmp_path of #3955. ## Checklist - [X] I think the code is well written - [X] Unit tests for the changes exist - [X] Documentation reflects the changes - [X] If you provide code modification, please add yourself to `CONTRIBUTORS.txt` * The format is &lt;Name&gt; &lt;Surname&gt;. * Please keep alphabetical order, the file is sorted by names. - [X] Add a new news fragment into the `CHANGES` folder * name it `<issue_id>.<type>` for example (588.bugfix) * if you don't have an `issue_id` change it to the pr id after creating the pr * ensure type is one of the following: * `.feature`: Signifying a new feature. * `.bugfix`: Signifying a bug fix. * `.doc`: Signifying a documentation improvement. * `.removal`: Signifying a deprecation or removal of public API. * `.misc`: A ticket has been closed, but it is not of interest to users. * Make sure to use full sentences with correct case and punctuation, for example: "Fix issue with non-ascii contents in doctest text files." Co-authored-by: Matt VanEseltine <[email protected]>
1 parent 6018c7f commit a960eb6

10 files changed

+130
-149
lines changed

CHANGES/3957.misc

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Improve test suite handling of paths and temp files to consistently use pathlib and pytest fixtures.

tests/test_client_request.py

+9-12
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import asyncio
22
import hashlib
33
import io
4-
import os.path
4+
import pathlib
55
import urllib.parse
66
import zlib
77
from http.cookies import BaseCookie, Morsel, SimpleCookie
@@ -921,12 +921,11 @@ async def test_chunked_transfer_encoding(loop, conn) -> None:
921921

922922

923923
async def test_file_upload_not_chunked(loop) -> None:
924-
here = os.path.dirname(__file__)
925-
fname = os.path.join(here, "aiohttp.png")
926-
with open(fname, "rb") as f:
924+
file_path = pathlib.Path(__file__).parent / "aiohttp.png"
925+
with file_path.open("rb") as f:
927926
req = ClientRequest("post", URL("http://python.org/"), data=f, loop=loop)
928927
assert not req.chunked
929-
assert req.headers["CONTENT-LENGTH"] == str(os.path.getsize(fname))
928+
assert req.headers["CONTENT-LENGTH"] == str(file_path.stat().st_size)
930929
await req.close()
931930

932931

@@ -947,19 +946,17 @@ async def test_precompressed_data_stays_intact(loop) -> None:
947946

948947

949948
async def test_file_upload_not_chunked_seek(loop) -> None:
950-
here = os.path.dirname(__file__)
951-
fname = os.path.join(here, "aiohttp.png")
952-
with open(fname, "rb") as f:
949+
file_path = pathlib.Path(__file__).parent / "aiohttp.png"
950+
with file_path.open("rb") as f:
953951
f.seek(100)
954952
req = ClientRequest("post", URL("http://python.org/"), data=f, loop=loop)
955-
assert req.headers["CONTENT-LENGTH"] == str(os.path.getsize(fname) - 100)
953+
assert req.headers["CONTENT-LENGTH"] == str(file_path.stat().st_size - 100)
956954
await req.close()
957955

958956

959957
async def test_file_upload_force_chunked(loop) -> None:
960-
here = os.path.dirname(__file__)
961-
fname = os.path.join(here, "aiohttp.png")
962-
with open(fname, "rb") as f:
958+
file_path = pathlib.Path(__file__).parent / "aiohttp.png"
959+
with file_path.open("rb") as f:
963960
req = ClientRequest(
964961
"post", URL("http://python.org/"), data=f, chunked=True, loop=loop
965962
)

tests/test_cookiejar.py

+3-5
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,8 @@
11
import asyncio
22
import datetime
33
import itertools
4-
import os
4+
import pathlib
55
import pickle
6-
import tempfile
76
import unittest
87
from http.cookies import BaseCookie, Morsel, SimpleCookie
98
from unittest import mock
@@ -178,8 +177,8 @@ async def test_constructor_with_expired(
178177
assert jar._loop is loop
179178

180179

181-
async def test_save_load(loop, cookies_to_send, cookies_to_receive) -> None:
182-
file_path = tempfile.mkdtemp() + "/aiohttp.test.cookie"
180+
async def test_save_load(tmp_path, loop, cookies_to_send, cookies_to_receive) -> None:
181+
file_path = pathlib.Path(str(tmp_path)) / "aiohttp.test.cookie"
183182

184183
# export cookie jar
185184
jar_save = CookieJar(loop=loop)
@@ -193,7 +192,6 @@ async def test_save_load(loop, cookies_to_send, cookies_to_receive) -> None:
193192
for cookie in jar_load:
194193
jar_test[cookie.key] = cookie
195194

196-
os.unlink(file_path)
197195
assert jar_test == cookies_to_receive
198196

199197

tests/test_helpers.py

+12-3
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44
import gc
55
import platform
66
import sys
7-
import tempfile
87
import weakref
98
from math import ceil, modf
109
from pathlib import Path
@@ -73,11 +72,21 @@ def test_parse_mimetype(mimetype, expected) -> None:
7372
# ------------------- guess_filename ----------------------------------
7473

7574

76-
def test_guess_filename_with_tempfile() -> None:
77-
with tempfile.TemporaryFile() as fp:
75+
def test_guess_filename_with_file_object(tmp_path) -> None:
76+
file_path = tmp_path / "test_guess_filename"
77+
with file_path.open("w+b") as fp:
7878
assert helpers.guess_filename(fp, "no-throw") is not None
7979

8080

81+
def test_guess_filename_with_path(tmp_path) -> None:
82+
file_path = tmp_path / "test_guess_filename"
83+
assert helpers.guess_filename(file_path, "no-throw") is not None
84+
85+
86+
def test_guess_filename_with_default() -> None:
87+
assert helpers.guess_filename(None, "no-throw") == "no-throw"
88+
89+
8190
# ------------------- BasicAuth -----------------------------------
8291

8392

tests/test_multipart.py

+4-3
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import asyncio
22
import io
33
import json
4+
import pathlib
45
import zlib
56
from unittest import mock
67

@@ -1270,7 +1271,7 @@ async def test_write_preserves_content_disposition(self, buf, stream) -> None:
12701271

12711272
async def test_preserve_content_disposition_header(self, buf, stream):
12721273
# https://github.com/aio-libs/aiohttp/pull/3475#issuecomment-451072381
1273-
with open(__file__, "rb") as fobj:
1274+
with pathlib.Path(__file__).open("rb") as fobj:
12741275
with aiohttp.MultipartWriter("form-data", boundary=":") as writer:
12751276
part = writer.append(
12761277
fobj,
@@ -1297,7 +1298,7 @@ async def test_preserve_content_disposition_header(self, buf, stream):
12971298

12981299
async def test_set_content_disposition_override(self, buf, stream):
12991300
# https://github.com/aio-libs/aiohttp/pull/3475#issuecomment-451072381
1300-
with open(__file__, "rb") as fobj:
1301+
with pathlib.Path(__file__).open("rb") as fobj:
13011302
with aiohttp.MultipartWriter("form-data", boundary=":") as writer:
13021303
part = writer.append(
13031304
fobj,
@@ -1324,7 +1325,7 @@ async def test_set_content_disposition_override(self, buf, stream):
13241325

13251326
async def test_reset_content_disposition_header(self, buf, stream):
13261327
# https://github.com/aio-libs/aiohttp/pull/3475#issuecomment-451072381
1327-
with open(__file__, "rb") as fobj:
1328+
with pathlib.Path(__file__).open("rb") as fobj:
13281329
with aiohttp.MultipartWriter("form-data", boundary=":") as writer:
13291330
part = writer.append(
13301331
fobj,

tests/test_proxy_functional.py

+3-3
Original file line numberDiff line numberDiff line change
@@ -731,7 +731,7 @@ async def test_proxy_from_env_http_with_auth_from_netrc(
731731
auth.login,
732732
auth.password,
733733
)
734-
with open(str(netrc_file), "w") as f:
734+
with netrc_file.open("w") as f:
735735
f.write(netrc_file_data)
736736
mocker.patch.dict(
737737
os.environ, {"http_proxy": str(proxy.url), "NETRC": str(netrc_file)}
@@ -757,7 +757,7 @@ async def test_proxy_from_env_http_without_auth_from_netrc(
757757
auth.login,
758758
auth.password,
759759
)
760-
with open(str(netrc_file), "w") as f:
760+
with netrc_file.open("w") as f:
761761
f.write(netrc_file_data)
762762
mocker.patch.dict(
763763
os.environ, {"http_proxy": str(proxy.url), "NETRC": str(netrc_file)}
@@ -780,7 +780,7 @@ async def test_proxy_from_env_http_without_auth_from_wrong_netrc(
780780
auth = aiohttp.BasicAuth("user", "pass")
781781
netrc_file = tmp_path / "test_netrc"
782782
invalid_data = f"machine 127.0.0.1 {auth.login} pass {auth.password}"
783-
with open(str(netrc_file), "w") as f:
783+
with netrc_file.open("w") as f:
784784
f.write(invalid_data)
785785

786786
mocker.patch.dict(

tests/test_urldispatch.py

+34-36
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import os
21
import pathlib
32
import re
43
from collections.abc import Container, Iterable, Mapping, MutableMapping, Sized
@@ -49,7 +48,7 @@ def fill_routes(router):
4948
def go():
5049
route1 = router.add_route("GET", "/plain", make_handler())
5150
route2 = router.add_route("GET", "/variable/{name}", make_handler())
52-
resource = router.add_static("/static", os.path.dirname(aiohttp.__file__))
51+
resource = router.add_static("/static", pathlib.Path(aiohttp.__file__).parent)
5352
return [route1, route2] + list(resource)
5453

5554
return go
@@ -342,7 +341,7 @@ def test_route_dynamic(router) -> None:
342341

343342
def test_add_static(router) -> None:
344343
resource = router.add_static(
345-
"/st", os.path.dirname(aiohttp.__file__), name="static"
344+
"/st", pathlib.Path(aiohttp.__file__).parent, name="static"
346345
)
347346
assert router["static"] is resource
348347
url = resource.url_for(filename="/dir/a.txt")
@@ -351,7 +350,7 @@ def test_add_static(router) -> None:
351350

352351

353352
def test_add_static_append_version(router) -> None:
354-
resource = router.add_static("/st", os.path.dirname(__file__), name="static")
353+
resource = router.add_static("/st", pathlib.Path(__file__).parent, name="static")
355354
url = resource.url_for(filename="/data.unknown_mime_type", append_version=True)
356355
expect_url = (
357356
"/st/data.unknown_mime_type?" "v=aUsn8CHEhhszc81d28QmlcBW0KQpfS2F4trgQKhOYd8%3D"
@@ -361,7 +360,7 @@ def test_add_static_append_version(router) -> None:
361360

362361
def test_add_static_append_version_set_from_constructor(router) -> None:
363362
resource = router.add_static(
364-
"/st", os.path.dirname(__file__), append_version=True, name="static"
363+
"/st", pathlib.Path(__file__).parent, append_version=True, name="static"
365364
)
366365
url = resource.url_for(filename="/data.unknown_mime_type")
367366
expect_url = (
@@ -372,15 +371,15 @@ def test_add_static_append_version_set_from_constructor(router) -> None:
372371

373372
def test_add_static_append_version_override_constructor(router) -> None:
374373
resource = router.add_static(
375-
"/st", os.path.dirname(__file__), append_version=True, name="static"
374+
"/st", pathlib.Path(__file__).parent, append_version=True, name="static"
376375
)
377376
url = resource.url_for(filename="/data.unknown_mime_type", append_version=False)
378377
expect_url = "/st/data.unknown_mime_type"
379378
assert expect_url == str(url)
380379

381380

382381
def test_add_static_append_version_filename_without_slash(router) -> None:
383-
resource = router.add_static("/st", os.path.dirname(__file__), name="static")
382+
resource = router.add_static("/st", pathlib.Path(__file__).parent, name="static")
384383
url = resource.url_for(filename="data.unknown_mime_type", append_version=True)
385384
expect_url = (
386385
"/st/data.unknown_mime_type?" "v=aUsn8CHEhhszc81d28QmlcBW0KQpfS2F4trgQKhOYd8%3D"
@@ -389,27 +388,26 @@ def test_add_static_append_version_filename_without_slash(router) -> None:
389388

390389

391390
def test_add_static_append_version_non_exists_file(router) -> None:
392-
resource = router.add_static("/st", os.path.dirname(__file__), name="static")
391+
resource = router.add_static("/st", pathlib.Path(__file__).parent, name="static")
393392
url = resource.url_for(filename="/non_exists_file", append_version=True)
394393
assert "/st/non_exists_file" == str(url)
395394

396395

397396
def test_add_static_append_version_non_exists_file_without_slash(router) -> None:
398-
resource = router.add_static("/st", os.path.dirname(__file__), name="static")
397+
resource = router.add_static("/st", pathlib.Path(__file__).parent, name="static")
399398
url = resource.url_for(filename="non_exists_file", append_version=True)
400399
assert "/st/non_exists_file" == str(url)
401400

402401

403-
def test_add_static_append_version_follow_symlink(router, tmpdir) -> None:
402+
def test_add_static_append_version_follow_symlink(router, tmp_path) -> None:
404403
# Tests the access to a symlink, in static folder with apeend_version
405-
tmp_dir_path = str(tmpdir)
406-
symlink_path = os.path.join(tmp_dir_path, "append_version_symlink")
407-
symlink_target_path = os.path.dirname(__file__)
408-
os.symlink(symlink_target_path, symlink_path, True)
404+
symlink_path = tmp_path / "append_version_symlink"
405+
symlink_target_path = pathlib.Path(__file__).parent
406+
pathlib.Path(str(symlink_path)).symlink_to(str(symlink_target_path), True)
409407

410408
# Register global static route:
411409
resource = router.add_static(
412-
"/st", tmp_dir_path, follow_symlinks=True, append_version=True
410+
"/st", str(tmp_path), follow_symlinks=True, append_version=True
413411
)
414412

415413
url = resource.url_for(filename="/append_version_symlink/data.unknown_mime_type")
@@ -421,16 +419,16 @@ def test_add_static_append_version_follow_symlink(router, tmpdir) -> None:
421419
assert expect_url == str(url)
422420

423421

424-
def test_add_static_append_version_not_follow_symlink(router, tmpdir) -> None:
422+
def test_add_static_append_version_not_follow_symlink(router, tmp_path) -> None:
425423
# Tests the access to a symlink, in static folder with apeend_version
426-
tmp_dir_path = str(tmpdir)
427-
symlink_path = os.path.join(tmp_dir_path, "append_version_symlink")
428-
symlink_target_path = os.path.dirname(__file__)
429-
os.symlink(symlink_target_path, symlink_path, True)
424+
symlink_path = tmp_path / "append_version_symlink"
425+
symlink_target_path = pathlib.Path(__file__).parent
426+
427+
pathlib.Path(str(symlink_path)).symlink_to(str(symlink_target_path), True)
430428

431429
# Register global static route:
432430
resource = router.add_static(
433-
"/st", tmp_dir_path, follow_symlinks=False, append_version=True
431+
"/st", str(tmp_path), follow_symlinks=False, append_version=True
434432
)
435433

436434
filename = "/append_version_symlink/data.unknown_mime_type"
@@ -467,7 +465,7 @@ def test_dynamic_not_match(router) -> None:
467465

468466

469467
async def test_static_not_match(router) -> None:
470-
router.add_static("/pre", os.path.dirname(aiohttp.__file__), name="name")
468+
router.add_static("/pre", pathlib.Path(aiohttp.__file__).parent, name="name")
471469
resource = router["name"]
472470
ret = await resource.resolve(make_mocked_request("GET", "/another/path"))
473471
assert (None, set()) == ret
@@ -503,17 +501,17 @@ def test_contains(router) -> None:
503501

504502

505503
def test_static_repr(router) -> None:
506-
router.add_static("/get", os.path.dirname(aiohttp.__file__), name="name")
504+
router.add_static("/get", pathlib.Path(aiohttp.__file__).parent, name="name")
507505
assert Matches(r"<StaticResource 'name' /get") == repr(router["name"])
508506

509507

510508
def test_static_adds_slash(router) -> None:
511-
route = router.add_static("/prefix", os.path.dirname(aiohttp.__file__))
509+
route = router.add_static("/prefix", pathlib.Path(aiohttp.__file__).parent)
512510
assert "/prefix" == route._prefix
513511

514512

515513
def test_static_remove_trailing_slash(router) -> None:
516-
route = router.add_static("/prefix/", os.path.dirname(aiohttp.__file__))
514+
route = router.add_static("/prefix/", pathlib.Path(aiohttp.__file__).parent)
517515
assert "/prefix" == route._prefix
518516

519517

@@ -778,7 +776,7 @@ def test_named_resources(router) -> None:
778776
route1 = router.add_route("GET", "/plain", make_handler(), name="route1")
779777
route2 = router.add_route("GET", "/variable/{name}", make_handler(), name="route2")
780778
route3 = router.add_static(
781-
"/static", os.path.dirname(aiohttp.__file__), name="route3"
779+
"/static", pathlib.Path(aiohttp.__file__).parent, name="route3"
782780
)
783781
names = {route1.name, route2.name, route3.name}
784782

@@ -943,11 +941,11 @@ def test_resources_abc(router) -> None:
943941

944942
def test_static_route_user_home(router) -> None:
945943
here = pathlib.Path(aiohttp.__file__).parent
946-
home = pathlib.Path(os.path.expanduser("~"))
947-
if not str(here).startswith(str(home)): # pragma: no cover
944+
try:
945+
static_dir = pathlib.Path("~") / here.relative_to(pathlib.Path.home())
946+
except ValueError: # pragma: no cover
948947
pytest.skip("aiohttp folder is not placed in user's HOME")
949-
static_dir = "~/" + str(here.relative_to(home))
950-
route = router.add_static("/st", static_dir)
948+
route = router.add_static("/st", str(static_dir))
951949
assert here == route.get_info()["directory"]
952950

953951

@@ -958,13 +956,13 @@ def test_static_route_points_to_file(router) -> None:
958956

959957

960958
async def test_404_for_static_resource(router) -> None:
961-
resource = router.add_static("/st", os.path.dirname(aiohttp.__file__))
959+
resource = router.add_static("/st", pathlib.Path(aiohttp.__file__).parent)
962960
ret = await resource.resolve(make_mocked_request("GET", "/unknown/path"))
963961
assert (None, set()) == ret
964962

965963

966964
async def test_405_for_resource_adapter(router) -> None:
967-
resource = router.add_static("/st", os.path.dirname(aiohttp.__file__))
965+
resource = router.add_static("/st", pathlib.Path(aiohttp.__file__).parent)
968966
ret = await resource.resolve(make_mocked_request("POST", "/st/abc.py"))
969967
assert (None, {"HEAD", "GET"}) == ret
970968

@@ -979,12 +977,12 @@ async def test_check_allowed_method_for_found_resource(router) -> None:
979977

980978

981979
def test_url_for_in_static_resource(router) -> None:
982-
resource = router.add_static("/static", os.path.dirname(aiohttp.__file__))
980+
resource = router.add_static("/static", pathlib.Path(aiohttp.__file__).parent)
983981
assert URL("/static/file.txt") == resource.url_for(filename="file.txt")
984982

985983

986984
def test_url_for_in_static_resource_pathlib(router) -> None:
987-
resource = router.add_static("/static", os.path.dirname(aiohttp.__file__))
985+
resource = router.add_static("/static", pathlib.Path(aiohttp.__file__).parent)
988986
assert URL("/static/file.txt") == resource.url_for(
989987
filename=pathlib.Path("file.txt")
990988
)
@@ -1163,7 +1161,7 @@ def test_frozen_app_on_subapp(app) -> None:
11631161

11641162

11651163
def test_set_options_route(router) -> None:
1166-
resource = router.add_static("/static", os.path.dirname(aiohttp.__file__))
1164+
resource = router.add_static("/static", pathlib.Path(aiohttp.__file__).parent)
11671165
options = None
11681166
for route in resource:
11691167
if route.method == "OPTIONS":
@@ -1233,7 +1231,7 @@ def test_dynamic_resource_canonical() -> None:
12331231

12341232
def test_static_resource_canonical() -> None:
12351233
prefix = "/prefix"
1236-
directory = str(os.path.dirname(aiohttp.__file__))
1234+
directory = str(pathlib.Path(aiohttp.__file__).parent)
12371235
canonical = prefix
12381236
res = StaticResource(prefix=prefix, directory=directory)
12391237
assert res.canonical == canonical

0 commit comments

Comments
 (0)