@@ -39,7 +39,12 @@ class DebugControl:
39
39
40
40
show_repr_attr = False # For AutoReprMixin
41
41
42
- def __init__ (self , options : Iterable [str ], output : Optional [IO [str ]]) -> None :
42
+ def __init__ (
43
+ self ,
44
+ options : Iterable [str ],
45
+ output : Optional [IO [str ]],
46
+ file_name : Optional [str ] = None ,
47
+ ) -> None :
43
48
"""Configure the options and output file for debugging."""
44
49
self .options = list (options ) + FORCED_DEBUG
45
50
self .suppress_callers = False
@@ -49,6 +54,7 @@ def __init__(self, options: Iterable[str], output: Optional[IO[str]]) -> None:
49
54
filters .append (add_pid_and_tid )
50
55
self .output = DebugOutputFile .get_one (
51
56
output ,
57
+ file_name = file_name ,
52
58
show_process = self .should ('process' ),
53
59
filters = filters ,
54
60
)
@@ -306,13 +312,11 @@ def __init__(
306
312
if hasattr (os , 'getppid' ):
307
313
self .write (f"New process: pid: { os .getpid ()!r} , parent pid: { os .getppid ()!r} \n " )
308
314
309
- SYS_MOD_NAME = '$coverage.debug.DebugOutputFile.the_one'
310
- SINGLETON_ATTR = 'the_one_and_is_interim'
311
-
312
315
@classmethod
313
316
def get_one (
314
317
cls ,
315
318
fileobj : Optional [IO [str ]] = None ,
319
+ file_name : Optional [str ] = None ,
316
320
show_process : bool = True ,
317
321
filters : Iterable [Callable [[str ], str ]] = (),
318
322
interim : bool = False ,
@@ -321,9 +325,9 @@ def get_one(
321
325
322
326
If `fileobj` is provided, then a new DebugOutputFile is made with it.
323
327
324
- If `fileobj` isn't provided, then a file is chosen
325
- ( COVERAGE_DEBUG_FILE, or stderr), and a process-wide singleton
326
- DebugOutputFile is made.
328
+ If `fileobj` isn't provided, then a file is chosen (`file_name` if
329
+ provided, or COVERAGE_DEBUG_FILE, or stderr), and a process-wide
330
+ singleton DebugOutputFile is made.
327
331
328
332
`show_process` controls whether the debug file adds process-level
329
333
information, and filters is a list of other message filters to apply.
@@ -338,27 +342,49 @@ def get_one(
338
342
# Make DebugOutputFile around the fileobj passed.
339
343
return cls (fileobj , show_process , filters )
340
344
341
- # Because of the way igor.py deletes and re-imports modules,
342
- # this class can be defined more than once. But we really want
343
- # a process-wide singleton. So stash it in sys.modules instead of
344
- # on a class attribute. Yes, this is aggressively gross.
345
- singleton_module = sys .modules .get (cls .SYS_MOD_NAME )
346
- the_one , is_interim = getattr (singleton_module , cls .SINGLETON_ATTR , (None , True ))
345
+ the_one , is_interim = cls ._get_singleton_data ()
347
346
if the_one is None or is_interim :
348
- if fileobj is None :
349
- debug_file_name = os .environ .get ("COVERAGE_DEBUG_FILE" , FORCED_DEBUG_FILE )
350
- if debug_file_name in ("stdout" , "stderr" ):
351
- fileobj = getattr (sys , debug_file_name )
352
- elif debug_file_name :
353
- fileobj = open (debug_file_name , "a" )
347
+ if file_name is not None :
348
+ fileobj = open (file_name , "a" , encoding = "utf-8" )
349
+ else :
350
+ file_name = os .environ .get ("COVERAGE_DEBUG_FILE" , FORCED_DEBUG_FILE )
351
+ if file_name in ("stdout" , "stderr" ):
352
+ fileobj = getattr (sys , file_name )
353
+ elif file_name :
354
+ fileobj = open (file_name , "a" , encoding = "utf-8" )
354
355
else :
355
356
fileobj = sys .stderr
356
357
the_one = cls (fileobj , show_process , filters )
357
- singleton_module = types .ModuleType (cls .SYS_MOD_NAME )
358
- setattr (singleton_module , cls .SINGLETON_ATTR , (the_one , interim ))
359
- sys .modules [cls .SYS_MOD_NAME ] = singleton_module
358
+ cls ._set_singleton_data (the_one , interim )
360
359
return the_one
361
360
361
+ # Because of the way igor.py deletes and re-imports modules,
362
+ # this class can be defined more than once. But we really want
363
+ # a process-wide singleton. So stash it in sys.modules instead of
364
+ # on a class attribute. Yes, this is aggressively gross.
365
+
366
+ SYS_MOD_NAME = '$coverage.debug.DebugOutputFile.the_one'
367
+ SINGLETON_ATTR = 'the_one_and_is_interim'
368
+
369
+ @classmethod
370
+ def _set_singleton_data (cls , the_one : DebugOutputFile , interim : bool ) -> None :
371
+ """Set the one DebugOutputFile to rule them all."""
372
+ singleton_module = types .ModuleType (cls .SYS_MOD_NAME )
373
+ setattr (singleton_module , cls .SINGLETON_ATTR , (the_one , interim ))
374
+ sys .modules [cls .SYS_MOD_NAME ] = singleton_module
375
+
376
+ @classmethod
377
+ def _get_singleton_data (cls ) -> Tuple [Optional [DebugOutputFile ], bool ]:
378
+ """Get the one DebugOutputFile."""
379
+ singleton_module = sys .modules .get (cls .SYS_MOD_NAME )
380
+ return getattr (singleton_module , cls .SINGLETON_ATTR , (None , True ))
381
+
382
+ @classmethod
383
+ def _del_singleton_data (cls ) -> None :
384
+ """Delete the one DebugOutputFile, just for tests to use."""
385
+ if cls .SYS_MOD_NAME in sys .modules :
386
+ del sys .modules [cls .SYS_MOD_NAME ]
387
+
362
388
def write (self , text : str ) -> None :
363
389
"""Just like file.write, but filter through all our filters."""
364
390
assert self .outfile is not None
0 commit comments