7
7
import json
8
8
import os
9
9
from pathlib import Path
10
+ import shutil
11
+ import tempfile
10
12
from typing import Dict
11
13
from typing import final
12
14
from typing import Generator
13
15
from typing import Iterable
14
16
from typing import List
15
17
from typing import Optional
16
18
from typing import Set
19
+ from typing import Tuple
17
20
from typing import Union
18
21
19
22
from .pathlib import resolve_from_str
20
23
from .pathlib import rm_rf
21
24
from .reports import CollectReport
22
25
from _pytest import nodes
23
26
from _pytest ._io import TerminalWriter
27
+ from _pytest .compat import assert_never
24
28
from _pytest .config import Config
25
29
from _pytest .config import ExitCode
26
30
from _pytest .config import hookimpl
@@ -123,6 +127,10 @@ def warn(self, fmt: str, *, _ispytest: bool = False, **args: object) -> None:
123
127
stacklevel = 3 ,
124
128
)
125
129
130
+ def _mkdir (self , path : Path ) -> None :
131
+ self ._ensure_cache_dir_and_supporting_files ()
132
+ path .mkdir (exist_ok = True , parents = True )
133
+
126
134
def mkdir (self , name : str ) -> Path :
127
135
"""Return a directory path object with the given name.
128
136
@@ -141,7 +149,7 @@ def mkdir(self, name: str) -> Path:
141
149
if len (path .parts ) > 1 :
142
150
raise ValueError ("name is not allowed to contain path separators" )
143
151
res = self ._cachedir .joinpath (self ._CACHE_PREFIX_DIRS , path )
144
- res . mkdir ( exist_ok = True , parents = True )
152
+ self . _mkdir ( res )
145
153
return res
146
154
147
155
def _getvaluepath (self , key : str ) -> Path :
@@ -178,19 +186,13 @@ def set(self, key: str, value: object) -> None:
178
186
"""
179
187
path = self ._getvaluepath (key )
180
188
try :
181
- if path .parent .is_dir ():
182
- cache_dir_exists_already = True
183
- else :
184
- cache_dir_exists_already = self ._cachedir .exists ()
185
- path .parent .mkdir (exist_ok = True , parents = True )
189
+ self ._mkdir (path .parent )
186
190
except OSError as exc :
187
191
self .warn (
188
192
f"could not create cache path { path } : { exc } " ,
189
193
_ispytest = True ,
190
194
)
191
195
return
192
- if not cache_dir_exists_already :
193
- self ._ensure_supporting_files ()
194
196
data = json .dumps (value , ensure_ascii = False , indent = 2 )
195
197
try :
196
198
f = path .open ("w" , encoding = "UTF-8" )
@@ -203,17 +205,35 @@ def set(self, key: str, value: object) -> None:
203
205
with f :
204
206
f .write (data )
205
207
206
- def _ensure_supporting_files (self ) -> None :
207
- """Create supporting files in the cache dir that are not really part of the cache ."""
208
- readme_path = self ._cachedir / "README.md"
209
- readme_path . write_text ( README_CONTENT , encoding = "UTF-8" )
208
+ def _ensure_cache_dir_and_supporting_files (self ) -> None :
209
+ """Create the cache dir and its supporting files ."""
210
+ if self ._cachedir . is_dir ():
211
+ return
210
212
211
- gitignore_path = self ._cachedir .joinpath (".gitignore" )
212
- msg = "# Created by pytest automatically.\n *\n "
213
- gitignore_path .write_text (msg , encoding = "UTF-8" )
213
+ files : Iterable [Tuple [str , Union [str , bytes ]]] = (
214
+ ("README.md" , README_CONTENT ),
215
+ (".gitignore" , "# Created by pytest automatically.\n *\n " ),
216
+ ("CACHEDIR.TAG" , CACHEDIR_TAG_CONTENT ),
217
+ )
214
218
215
- cachedir_tag_path = self ._cachedir .joinpath ("CACHEDIR.TAG" )
216
- cachedir_tag_path .write_bytes (CACHEDIR_TAG_CONTENT )
219
+ with tempfile .TemporaryDirectory () as d :
220
+ for file , content in files :
221
+ file = os .path .join (d , file )
222
+ if isinstance (content , str ):
223
+ with open (file , "xt" , encoding = "UTF-8" ) as f :
224
+ f .write (content )
225
+ elif isinstance (content , bytes ):
226
+ with open (file , "xb" ) as f :
227
+ f .write (content )
228
+ else :
229
+ assert_never (content )
230
+ shutil .move (d , self ._cachedir )
231
+ # Create a directory in place of the one we just moved so that `TemporaryDirectory`'s
232
+ # cleanup doesn't complain.
233
+ #
234
+ # TODO: pass ignore_cleanup_errors=True when we no longer support python < 3.10. See
235
+ # https://github.com/python/cpython/issues/74168.
236
+ os .mkdir (d )
217
237
218
238
219
239
class LFPluginCollWrapper :
0 commit comments