55import os
66import sys
77import threading
8+ from concurrent .futures import ThreadPoolExecutor
89from contextlib import contextmanager
910from errno import ENOSYS
1011from inspect import getframeinfo , stack
1112from pathlib import Path , PurePath
1213from stat import S_IWGRP , S_IWOTH , S_IWUSR , filemode
1314from types import TracebackType
1415from typing import Callable , Iterator , Tuple , Type , Union
16+ from uuid import uuid4
1517
1618import pytest
1719from _pytest .logging import LogCaptureFixture
@@ -81,6 +83,10 @@ def tmp_path_ro(tmp_path: Path) -> Iterator[Path]:
8183
8284@pytest .mark .parametrize ("lock_type" , [FileLock , SoftFileLock ])
8385@pytest .mark .skipif (sys .platform == "win32" , reason = "Windows does not have read only folders" )
86+ @pytest .mark .skipif (
87+ sys .platform != "win32" and os .geteuid () == 0 , # noqa: SC200
88+ reason = "Cannot make a read only file (that the current user: root can't read)" ,
89+ )
8490def test_ro_folder (lock_type : type [BaseFileLock ], tmp_path_ro : Path ) -> None :
8591 lock = lock_type (str (tmp_path_ro / "a" ))
8692 with pytest .raises (PermissionError , match = "Permission denied" ):
@@ -96,6 +102,10 @@ def tmp_file_ro(tmp_path: Path) -> Iterator[Path]:
96102
97103
98104@pytest .mark .parametrize ("lock_type" , [FileLock , SoftFileLock ])
105+ @pytest .mark .skipif (
106+ sys .platform != "win32" and os .geteuid () == 0 , # noqa: SC200
107+ reason = "Cannot make a read only file (that the current user: root can't read)" ,
108+ )
99109def test_ro_file (lock_type : type [BaseFileLock ], tmp_file_ro : Path ) -> None :
100110 lock = lock_type (str (tmp_file_ro ))
101111 with pytest .raises (PermissionError , match = "Permission denied" ):
@@ -509,3 +519,60 @@ def test_soft_errors(tmp_path: Path, mocker: MockerFixture) -> None:
509519 mocker .patch ("os.open" , side_effect = OSError (ENOSYS , "mock error" ))
510520 with pytest .raises (OSError , match = "mock error" ):
511521 SoftFileLock (tmp_path / "a.lock" ).acquire ()
522+
523+
524+ def _check_file_read_write (txt_file : Path ) -> None :
525+ for _ in range (3 ):
526+ uuid = str (uuid4 ())
527+ txt_file .write_text (uuid )
528+ assert txt_file .read_text () == uuid
529+
530+
531+ @pytest .mark .parametrize ("lock_type" , [FileLock , SoftFileLock ])
532+ def test_thrashing_with_thread_pool_passing_lock_to_threads (tmp_path : Path , lock_type : type [BaseFileLock ]) -> None :
533+ def mess_with_file (lock_ : BaseFileLock ) -> None :
534+ with lock_ :
535+ _check_file_read_write (txt_file )
536+
537+ lock_file , txt_file = tmp_path / "test.txt.lock" , tmp_path / "test.txt"
538+ lock = lock_type (lock_file )
539+ results = []
540+ with ThreadPoolExecutor () as executor :
541+ for _ in range (100 ):
542+ results .append (executor .submit (mess_with_file , lock ))
543+
544+ assert all (r .result () is None for r in results )
545+
546+
547+ @pytest .mark .parametrize ("lock_type" , [FileLock , SoftFileLock ])
548+ def test_thrashing_with_thread_pool_global_lock (tmp_path : Path , lock_type : type [BaseFileLock ]) -> None :
549+ def mess_with_file () -> None :
550+ with lock :
551+ _check_file_read_write (txt_file )
552+
553+ lock_file , txt_file = tmp_path / "test.txt.lock" , tmp_path / "test.txt"
554+ lock = lock_type (lock_file )
555+ results = []
556+ with ThreadPoolExecutor () as executor :
557+ for _ in range (100 ):
558+ results .append (executor .submit (mess_with_file ))
559+
560+ assert all (r .result () is None for r in results )
561+
562+
563+ @pytest .mark .parametrize ("lock_type" , [FileLock , SoftFileLock ])
564+ def test_thrashing_with_thread_pool_lock_recreated_in_each_thread (
565+ tmp_path : Path ,
566+ lock_type : type [BaseFileLock ],
567+ ) -> None :
568+ def mess_with_file () -> None :
569+ with lock_type (lock_file ):
570+ _check_file_read_write (txt_file )
571+
572+ lock_file , txt_file = tmp_path / "test.txt.lock" , tmp_path / "test.txt"
573+ results = []
574+ with ThreadPoolExecutor () as executor :
575+ for _ in range (100 ):
576+ results .append (executor .submit (mess_with_file ))
577+
578+ assert all (r .result () is None for r in results )
0 commit comments