5
5
import os
6
6
import sys
7
7
import threading
8
+ from concurrent .futures import ThreadPoolExecutor
8
9
from contextlib import contextmanager
9
10
from errno import ENOSYS
10
11
from inspect import getframeinfo , stack
11
12
from pathlib import Path , PurePath
12
13
from stat import S_IWGRP , S_IWOTH , S_IWUSR , filemode
13
14
from types import TracebackType
14
15
from typing import Callable , Iterator , Tuple , Type , Union
16
+ from uuid import uuid4
15
17
16
18
import pytest
17
19
from _pytest .logging import LogCaptureFixture
@@ -81,6 +83,10 @@ def tmp_path_ro(tmp_path: Path) -> Iterator[Path]:
81
83
82
84
@pytest .mark .parametrize ("lock_type" , [FileLock , SoftFileLock ])
83
85
@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
+ )
84
90
def test_ro_folder (lock_type : type [BaseFileLock ], tmp_path_ro : Path ) -> None :
85
91
lock = lock_type (str (tmp_path_ro / "a" ))
86
92
with pytest .raises (PermissionError , match = "Permission denied" ):
@@ -96,6 +102,10 @@ def tmp_file_ro(tmp_path: Path) -> Iterator[Path]:
96
102
97
103
98
104
@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
+ )
99
109
def test_ro_file (lock_type : type [BaseFileLock ], tmp_file_ro : Path ) -> None :
100
110
lock = lock_type (str (tmp_file_ro ))
101
111
with pytest .raises (PermissionError , match = "Permission denied" ):
@@ -509,3 +519,60 @@ def test_soft_errors(tmp_path: Path, mocker: MockerFixture) -> None:
509
519
mocker .patch ("os.open" , side_effect = OSError (ENOSYS , "mock error" ))
510
520
with pytest .raises (OSError , match = "mock error" ):
511
521
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