eric7/DebugClients/Python/coverage/control.py

branch
eric7
changeset 8775
0802ae193343
parent 8527
2bd1325d727e
child 8929
fcca2fa618bf
equal deleted inserted replaced
8774:d728227e8ebb 8775:0802ae193343
9 import os 9 import os
10 import os.path 10 import os.path
11 import platform 11 import platform
12 import sys 12 import sys
13 import time 13 import time
14 import warnings
14 15
15 from coverage import env 16 from coverage import env
16 from coverage.annotate import AnnotateReporter 17 from coverage.annotate import AnnotateReporter
17 from coverage.backward import string_class, iitems
18 from coverage.collector import Collector, CTracer 18 from coverage.collector import Collector, CTracer
19 from coverage.config import read_coverage_config 19 from coverage.config import read_coverage_config
20 from coverage.context import should_start_context_test_function, combine_context_switchers 20 from coverage.context import should_start_context_test_function, combine_context_switchers
21 from coverage.data import CoverageData, combine_parallel_data 21 from coverage.data import CoverageData, combine_parallel_data
22 from coverage.debug import DebugControl, short_stack, write_formatted_info 22 from coverage.debug import DebugControl, short_stack, write_formatted_info
23 from coverage.disposition import disposition_debug_msg 23 from coverage.disposition import disposition_debug_msg
24 from coverage.exceptions import CoverageException, CoverageWarning
24 from coverage.files import PathAliases, abs_file, relative_filename, set_relative_directory 25 from coverage.files import PathAliases, abs_file, relative_filename, set_relative_directory
25 from coverage.html import HtmlReporter 26 from coverage.html import HtmlReporter
26 from coverage.inorout import InOrOut 27 from coverage.inorout import InOrOut
27 from coverage.jsonreport import JsonReporter 28 from coverage.jsonreport import JsonReporter
28 from coverage.misc import CoverageException, bool_or_none, join_regex 29 from coverage.misc import bool_or_none, join_regex, human_sorted, human_sorted_items
29 from coverage.misc import DefaultValue, ensure_dir_for_file, isolate_module 30 from coverage.misc import DefaultValue, ensure_dir_for_file, isolate_module
30 from coverage.plugin import FileReporter 31 from coverage.plugin import FileReporter
31 from coverage.plugin_support import Plugins 32 from coverage.plugin_support import Plugins
32 from coverage.python import PythonFileReporter 33 from coverage.python import PythonFileReporter
33 from coverage.report import render_report 34 from coverage.report import render_report
34 from coverage.results import Analysis, Numbers 35 from coverage.results import Analysis
35 from coverage.summary import SummaryReporter 36 from coverage.summary import SummaryReporter
36 from coverage.xmlreport import XmlReporter 37 from coverage.xmlreport import XmlReporter
37 38
38 try: 39 try:
39 from coverage.multiproc import patch_multiprocessing 40 from coverage.multiproc import patch_multiprocessing
59 cov.config = original_config 60 cov.config = original_config
60 61
61 62
62 _DEFAULT_DATAFILE = DefaultValue("MISSING") 63 _DEFAULT_DATAFILE = DefaultValue("MISSING")
63 64
64 class Coverage(object): 65 class Coverage:
65 """Programmatic access to coverage.py. 66 """Programmatic access to coverage.py.
66 67
67 To use:: 68 To use::
68 69
69 from coverage import Coverage 70 from coverage import Coverage
100 def __init__( 101 def __init__(
101 self, data_file=_DEFAULT_DATAFILE, data_suffix=None, cover_pylib=None, 102 self, data_file=_DEFAULT_DATAFILE, data_suffix=None, cover_pylib=None,
102 auto_data=False, timid=None, branch=None, config_file=True, 103 auto_data=False, timid=None, branch=None, config_file=True,
103 source=None, source_pkgs=None, omit=None, include=None, debug=None, 104 source=None, source_pkgs=None, omit=None, include=None, debug=None,
104 concurrency=None, check_preimported=False, context=None, 105 concurrency=None, check_preimported=False, context=None,
106 messages=False,
105 ): # pylint: disable=too-many-arguments 107 ): # pylint: disable=too-many-arguments
106 """ 108 """
107 Many of these arguments duplicate and override values that can be 109 Many of these arguments duplicate and override values that can be
108 provided in a configuration file. Parameters that are missing here 110 provided in a configuration file. Parameters that are missing here
109 will use values from the config file. 111 will use values from the config file.
170 started can mean that code is missed. 172 started can mean that code is missed.
171 173
172 `context` is a string to use as the :ref:`static context 174 `context` is a string to use as the :ref:`static context
173 <static_contexts>` label for collected data. 175 <static_contexts>` label for collected data.
174 176
177 If `messages` is true, some messages will be printed to stdout
178 indicating what is happening.
179
175 .. versionadded:: 4.0 180 .. versionadded:: 4.0
176 The `concurrency` parameter. 181 The `concurrency` parameter.
177 182
178 .. versionadded:: 4.2 183 .. versionadded:: 4.2
179 The `concurrency` parameter can now be a list of strings. 184 The `concurrency` parameter can now be a list of strings.
181 .. versionadded:: 5.0 186 .. versionadded:: 5.0
182 The `check_preimported` and `context` parameters. 187 The `check_preimported` and `context` parameters.
183 188
184 .. versionadded:: 5.3 189 .. versionadded:: 5.3
185 The `source_pkgs` parameter. 190 The `source_pkgs` parameter.
191
192 .. versionadded:: 6.0
193 The `messages` parameter.
186 194
187 """ 195 """
188 # data_file=None means no disk file at all. data_file missing means 196 # data_file=None means no disk file at all. data_file missing means
189 # use the value from the config file. 197 # use the value from the config file.
190 self._no_disk = data_file is None 198 self._no_disk = data_file is None
191 if data_file is _DEFAULT_DATAFILE: 199 if data_file is _DEFAULT_DATAFILE:
192 data_file = None 200 data_file = None
193 201
194 # Build our configuration from a number of sources. 202 self.config = None
195 self.config = read_coverage_config(
196 config_file=config_file,
197 data_file=data_file, cover_pylib=cover_pylib, timid=timid,
198 branch=branch, parallel=bool_or_none(data_suffix),
199 source=source, source_pkgs=source_pkgs, run_omit=omit, run_include=include, debug=debug,
200 report_omit=omit, report_include=include,
201 concurrency=concurrency, context=context,
202 )
203 203
204 # This is injectable by tests. 204 # This is injectable by tests.
205 self._debug_file = None 205 self._debug_file = None
206 206
207 self._auto_load = self._auto_save = auto_data 207 self._auto_load = self._auto_save = auto_data
210 # Is it ok for no data to be collected? 210 # Is it ok for no data to be collected?
211 self._warn_no_data = True 211 self._warn_no_data = True
212 self._warn_unimported_source = True 212 self._warn_unimported_source = True
213 self._warn_preimported_source = check_preimported 213 self._warn_preimported_source = check_preimported
214 self._no_warn_slugs = None 214 self._no_warn_slugs = None
215 self._messages = messages
215 216
216 # A record of all the warnings that have been issued. 217 # A record of all the warnings that have been issued.
217 self._warnings = [] 218 self._warnings = []
218 219
219 # Other instance attributes, set later. 220 # Other instance attributes, set later.
232 # Have we started collecting and not stopped it? 233 # Have we started collecting and not stopped it?
233 self._started = False 234 self._started = False
234 # Should we write the debug output? 235 # Should we write the debug output?
235 self._should_write_debug = True 236 self._should_write_debug = True
236 237
238 # Build our configuration from a number of sources.
239 self.config = read_coverage_config(
240 config_file=config_file, warn=self._warn,
241 data_file=data_file, cover_pylib=cover_pylib, timid=timid,
242 branch=branch, parallel=bool_or_none(data_suffix),
243 source=source, source_pkgs=source_pkgs, run_omit=omit, run_include=include, debug=debug,
244 report_omit=omit, report_include=include,
245 concurrency=concurrency, context=context,
246 )
247
237 # If we have sub-process measurement happening automatically, then we 248 # If we have sub-process measurement happening automatically, then we
238 # want any explicit creation of a Coverage object to mean, this process 249 # want any explicit creation of a Coverage object to mean, this process
239 # is already coverage-aware, so don't auto-measure it. By now, the 250 # is already coverage-aware, so don't auto-measure it. By now, the
240 # auto-creation of a Coverage object has already happened. But we can 251 # auto-creation of a Coverage object has already happened. But we can
241 # find it and tell it not to save its data. 252 # find it and tell it not to save its data.
289 self._write_startup_debug() 300 self._write_startup_debug()
290 301
291 # '[run] _crash' will raise an exception if the value is close by in 302 # '[run] _crash' will raise an exception if the value is close by in
292 # the call stack, for testing error handling. 303 # the call stack, for testing error handling.
293 if self.config._crash and self.config._crash in short_stack(limit=4): 304 if self.config._crash and self.config._crash in short_stack(limit=4):
294 raise Exception("Crashing because called by {}".format(self.config._crash)) 305 raise Exception(f"Crashing because called by {self.config._crash}")
295 306
296 def _write_startup_debug(self): 307 def _write_startup_debug(self):
297 """Write out debug info at startup if needed.""" 308 """Write out debug info at startup if needed."""
298 wrote_any = False 309 wrote_any = False
299 with self._debug.without_callers(): 310 with self._debug.without_callers():
300 if self._debug.should('config'): 311 if self._debug.should('config'):
301 config_info = sorted(self.config.__dict__.items()) 312 config_info = human_sorted_items(self.config.__dict__.items())
302 config_info = [(k, v) for k, v in config_info if not k.startswith('_')] 313 config_info = [(k, v) for k, v in config_info if not k.startswith('_')]
303 write_formatted_info(self._debug, "config", config_info) 314 write_formatted_info(self._debug, "config", config_info)
304 wrote_any = True 315 wrote_any = True
305 316
306 if self._debug.should('sys'): 317 if self._debug.should('sys'):
332 343
333 """ 344 """
334 reason = self._inorout.check_include_omit_etc(filename, frame) 345 reason = self._inorout.check_include_omit_etc(filename, frame)
335 if self._debug.should('trace'): 346 if self._debug.should('trace'):
336 if not reason: 347 if not reason:
337 msg = "Including %r" % (filename,) 348 msg = f"Including {filename!r}"
338 else: 349 else:
339 msg = "Not including %r: %s" % (filename, reason) 350 msg = f"Not including {filename!r}: {reason}"
340 self._debug.write(msg) 351 self._debug.write(msg)
341 352
342 return not reason 353 return not reason
343 354
344 def _warn(self, msg, slug=None, once=False): 355 def _warn(self, msg, slug=None, once=False):
349 If `once` is true, only show this warning once (determined by the 360 If `once` is true, only show this warning once (determined by the
350 slug.) 361 slug.)
351 362
352 """ 363 """
353 if self._no_warn_slugs is None: 364 if self._no_warn_slugs is None:
354 self._no_warn_slugs = list(self.config.disable_warnings) 365 if self.config is not None:
355 366 self._no_warn_slugs = list(self.config.disable_warnings)
356 if slug in self._no_warn_slugs: 367
357 # Don't issue the warning 368 if self._no_warn_slugs is not None:
358 return 369 if slug in self._no_warn_slugs:
370 # Don't issue the warning
371 return
359 372
360 self._warnings.append(msg) 373 self._warnings.append(msg)
361 if slug: 374 if slug:
362 msg = "%s (%s)" % (msg, slug) 375 msg = f"{msg} ({slug})"
363 if self._debug.should('pid'): 376 if self._debug is not None and self._debug.should('pid'):
364 msg = "[%d] %s" % (os.getpid(), msg) 377 msg = f"[{os.getpid()}] {msg}"
365 sys.stderr.write("Coverage.py warning: %s\n" % msg) 378 warnings.warn(msg, category=CoverageWarning, stacklevel=2)
366 379
367 if once: 380 if once:
368 self._no_warn_slugs.append(slug) 381 self._no_warn_slugs.append(slug)
382
383 def _message(self, msg):
384 """Write a message to the user, if configured to do so."""
385 if self._messages:
386 print(msg)
369 387
370 def get_option(self, option_name): 388 def get_option(self, option_name):
371 """Get an option from the configuration. 389 """Get an option from the configuration.
372 390
373 `option_name` is a colon-separated string indicating the section and 391 `option_name` is a colon-separated string indicating the section and
440 if not dycon or dycon == "none": 458 if not dycon or dycon == "none":
441 context_switchers = [] 459 context_switchers = []
442 elif dycon == "test_function": 460 elif dycon == "test_function":
443 context_switchers = [should_start_context_test_function] 461 context_switchers = [should_start_context_test_function]
444 else: 462 else:
445 raise CoverageException( 463 raise CoverageException(f"Don't understand dynamic_context setting: {dycon!r}")
446 "Don't understand dynamic_context setting: {!r}".format(dycon)
447 )
448 464
449 context_switchers.extend( 465 context_switchers.extend(
450 plugin.dynamic_context for plugin in self._plugins.context_switchers 466 plugin.dynamic_context for plugin in self._plugins.context_switchers
451 ) 467 )
452 468
463 concurrency=concurrency, 479 concurrency=concurrency,
464 ) 480 )
465 481
466 suffix = self._data_suffix_specified 482 suffix = self._data_suffix_specified
467 if suffix or self.config.parallel: 483 if suffix or self.config.parallel:
468 if not isinstance(suffix, string_class): 484 if not isinstance(suffix, str):
469 # if data_suffix=True, use .machinename.pid.random 485 # if data_suffix=True, use .machinename.pid.random
470 suffix = True 486 suffix = True
471 else: 487 else:
472 suffix = None 488 suffix = None
473 489
476 self._collector.use_data(self._data, self.config.context) 492 self._collector.use_data(self._data, self.config.context)
477 493
478 # Early warning if we aren't going to be able to support plugins. 494 # Early warning if we aren't going to be able to support plugins.
479 if self._plugins.file_tracers and not self._collector.supports_plugins: 495 if self._plugins.file_tracers and not self._collector.supports_plugins:
480 self._warn( 496 self._warn(
481 "Plugin file tracers (%s) aren't supported with %s" % ( 497 "Plugin file tracers ({}) aren't supported with {}".format(
482 ", ".join( 498 ", ".join(
483 plugin._coverage_plugin_name 499 plugin._coverage_plugin_name
484 for plugin in self._plugins.file_tracers 500 for plugin in self._plugins.file_tracers
485 ), 501 ),
486 self._collector.tracer_name(), 502 self._collector.tracer_name(),
560 self._started = False 576 self._started = False
561 577
562 def _atexit(self): 578 def _atexit(self):
563 """Clean up on process shutdown.""" 579 """Clean up on process shutdown."""
564 if self._debug.should("process"): 580 if self._debug.should("process"):
565 self._debug.write("atexit: pid: {}, instance: {!r}".format(os.getpid(), self)) 581 self._debug.write(f"atexit: pid: {os.getpid()}, instance: {self!r}")
566 if self._started: 582 if self._started:
567 self.stop() 583 self.stop()
568 if self._auto_save: 584 if self._auto_save:
569 self.save() 585 self.save()
570 586
596 612
597 .. versionadded:: 5.0 613 .. versionadded:: 5.0
598 614
599 """ 615 """
600 if not self._started: # pragma: part started 616 if not self._started: # pragma: part started
601 raise CoverageException( 617 raise CoverageException("Cannot switch context, coverage is not started")
602 "Cannot switch context, coverage is not started"
603 )
604 618
605 if self._collector.should_start_context: 619 if self._collector.should_start_context:
606 self._warn("Conflicting dynamic contexts", slug="dynamic-conflict", once=True) 620 self._warn("Conflicting dynamic contexts", slug="dynamic-conflict", once=True)
607 621
608 self._collector.switch_context(new_context) 622 self._collector.switch_context(new_context)
690 self._post_init() 704 self._post_init()
691 self.get_data() 705 self.get_data()
692 706
693 aliases = None 707 aliases = None
694 if self.config.paths: 708 if self.config.paths:
695 aliases = PathAliases() 709 aliases = PathAliases(relative=self.config.relative_files)
696 for paths in self.config.paths.values(): 710 for paths in self.config.paths.values():
697 result = paths[0] 711 result = paths[0]
698 for pattern in paths[1:]: 712 for pattern in paths[1:]:
699 aliases.add(pattern, result) 713 aliases.add(pattern, result)
700 714
702 self._data, 716 self._data,
703 aliases=aliases, 717 aliases=aliases,
704 data_paths=data_paths, 718 data_paths=data_paths,
705 strict=strict, 719 strict=strict,
706 keep=keep, 720 keep=keep,
721 message=self._message,
707 ) 722 )
708 723
709 def get_data(self): 724 def get_data(self):
710 """Get the collected data. 725 """Get the collected data.
711 726
796 Returns an `Analysis` object. 811 Returns an `Analysis` object.
797 812
798 """ 813 """
799 # All reporting comes through here, so do reporting initialization. 814 # All reporting comes through here, so do reporting initialization.
800 self._init() 815 self._init()
801 Numbers.set_precision(self.config.precision)
802 self._post_init() 816 self._post_init()
803 817
804 data = self.get_data() 818 data = self.get_data()
805 if not isinstance(it, FileReporter): 819 if not isinstance(it, FileReporter):
806 it = self._get_file_reporter(it) 820 it = self._get_file_reporter(it)
807 821
808 return Analysis(data, it, self._file_mapper) 822 return Analysis(data, self.config.precision, it, self._file_mapper)
809 823
810 def _get_file_reporter(self, morf): 824 def _get_file_reporter(self, morf):
811 """Get a FileReporter for a module or file name.""" 825 """Get a FileReporter for a module or file name."""
812 plugin = None 826 plugin = None
813 file_reporter = "python" 827 file_reporter = "python"
814 828
815 if isinstance(morf, string_class): 829 if isinstance(morf, str):
816 mapped_morf = self._file_mapper(morf) 830 mapped_morf = self._file_mapper(morf)
817 plugin_name = self._data.file_tracer(mapped_morf) 831 plugin_name = self._data.file_tracer(mapped_morf)
818 if plugin_name: 832 if plugin_name:
819 plugin = self._plugins.get(plugin_name) 833 plugin = self._plugins.get(plugin_name)
820 834
821 if plugin: 835 if plugin:
822 file_reporter = plugin.file_reporter(mapped_morf) 836 file_reporter = plugin.file_reporter(mapped_morf)
823 if file_reporter is None: 837 if file_reporter is None:
824 raise CoverageException( 838 raise CoverageException(
825 "Plugin %r did not provide a file reporter for %r." % ( 839 "Plugin {!r} did not provide a file reporter for {!r}.".format(
826 plugin._coverage_plugin_name, morf 840 plugin._coverage_plugin_name, morf
827 ) 841 )
828 ) 842 )
829 843
830 if file_reporter == "python": 844 if file_reporter == "python":
916 self, morfs=None, directory=None, ignore_errors=None, 930 self, morfs=None, directory=None, ignore_errors=None,
917 omit=None, include=None, contexts=None, 931 omit=None, include=None, contexts=None,
918 ): 932 ):
919 """Annotate a list of modules. 933 """Annotate a list of modules.
920 934
935 .. note::
936 This method has been obsoleted by more modern reporting tools,
937 including the :meth:`html_report` method. It will be removed in a
938 future version.
939
921 Each module in `morfs` is annotated. The source is written to a new 940 Each module in `morfs` is annotated. The source is written to a new
922 file, named with a ",cover" suffix, with each line prefixed with a 941 file, named with a ",cover" suffix, with each line prefixed with a
923 marker to indicate the coverage of the line. Covered lines have ">", 942 marker to indicate the coverage of the line. Covered lines have ">",
924 excluded lines have "-", and missing lines have "!". 943 excluded lines have "-", and missing lines have "!".
925 944
926 See :meth:`report` for other arguments. 945 See :meth:`report` for other arguments.
927 946
928 """ 947 """
948 print("The annotate command will be removed in a future version.")
949 print("Get in touch if you still use it: ned@nedbatchelder.com")
950
929 with override_config(self, 951 with override_config(self,
930 ignore_errors=ignore_errors, report_omit=omit, 952 ignore_errors=ignore_errors, report_omit=omit,
931 report_include=include, report_contexts=contexts, 953 report_include=include, report_contexts=contexts,
932 ): 954 ):
933 reporter = AnnotateReporter(self) 955 reporter = AnnotateReporter(self)
967 html_dir=directory, extra_css=extra_css, html_title=title, 989 html_dir=directory, extra_css=extra_css, html_title=title,
968 html_skip_covered=skip_covered, show_contexts=show_contexts, report_contexts=contexts, 990 html_skip_covered=skip_covered, show_contexts=show_contexts, report_contexts=contexts,
969 html_skip_empty=skip_empty, precision=precision, 991 html_skip_empty=skip_empty, precision=precision,
970 ): 992 ):
971 reporter = HtmlReporter(self) 993 reporter = HtmlReporter(self)
972 return reporter.report(morfs) 994 ret = reporter.report(morfs)
995 return ret
973 996
974 def xml_report( 997 def xml_report(
975 self, morfs=None, outfile=None, ignore_errors=None, 998 self, morfs=None, outfile=None, ignore_errors=None,
976 omit=None, include=None, contexts=None, skip_empty=None, 999 omit=None, include=None, contexts=None, skip_empty=None,
977 ): 1000 ):
989 """ 1012 """
990 with override_config(self, 1013 with override_config(self,
991 ignore_errors=ignore_errors, report_omit=omit, report_include=include, 1014 ignore_errors=ignore_errors, report_omit=omit, report_include=include,
992 xml_output=outfile, report_contexts=contexts, skip_empty=skip_empty, 1015 xml_output=outfile, report_contexts=contexts, skip_empty=skip_empty,
993 ): 1016 ):
994 return render_report(self.config.xml_output, XmlReporter(self), morfs) 1017 return render_report(self.config.xml_output, XmlReporter(self), morfs, self._message)
995 1018
996 def json_report( 1019 def json_report(
997 self, morfs=None, outfile=None, ignore_errors=None, 1020 self, morfs=None, outfile=None, ignore_errors=None,
998 omit=None, include=None, contexts=None, pretty_print=None, 1021 omit=None, include=None, contexts=None, pretty_print=None,
999 show_contexts=None 1022 show_contexts=None
1013 with override_config(self, 1036 with override_config(self,
1014 ignore_errors=ignore_errors, report_omit=omit, report_include=include, 1037 ignore_errors=ignore_errors, report_omit=omit, report_include=include,
1015 json_output=outfile, report_contexts=contexts, json_pretty_print=pretty_print, 1038 json_output=outfile, report_contexts=contexts, json_pretty_print=pretty_print,
1016 json_show_contexts=show_contexts 1039 json_show_contexts=show_contexts
1017 ): 1040 ):
1018 return render_report(self.config.json_output, JsonReporter(self), morfs) 1041 return render_report(self.config.json_output, JsonReporter(self), morfs, self._message)
1019 1042
1020 def sys_info(self): 1043 def sys_info(self):
1021 """Return a list of (key, value) pairs showing internal information.""" 1044 """Return a list of (key, value) pairs showing internal information."""
1022 1045
1023 import coverage as covmod 1046 import coverage as covmod
1034 entry += " (disabled)" 1057 entry += " (disabled)"
1035 entries.append(entry) 1058 entries.append(entry)
1036 return entries 1059 return entries
1037 1060
1038 info = [ 1061 info = [
1039 ('version', covmod.__version__), 1062 ('coverage_version', covmod.__version__),
1040 ('coverage', covmod.__file__), 1063 ('coverage_module', covmod.__file__),
1041 ('tracer', self._collector.tracer_name() if self._collector else "-none-"), 1064 ('tracer', self._collector.tracer_name() if self._collector else "-none-"),
1042 ('CTracer', 'available' if CTracer else "unavailable"), 1065 ('CTracer', 'available' if CTracer else "unavailable"),
1043 ('plugins.file_tracers', plugin_info(self._plugins.file_tracers)), 1066 ('plugins.file_tracers', plugin_info(self._plugins.file_tracers)),
1044 ('plugins.configurers', plugin_info(self._plugins.configurers)), 1067 ('plugins.configurers', plugin_info(self._plugins.configurers)),
1045 ('plugins.context_switchers', plugin_info(self._plugins.context_switchers)), 1068 ('plugins.context_switchers', plugin_info(self._plugins.context_switchers)),
1059 ('def_encoding', sys.getdefaultencoding()), 1082 ('def_encoding', sys.getdefaultencoding()),
1060 ('fs_encoding', sys.getfilesystemencoding()), 1083 ('fs_encoding', sys.getfilesystemencoding()),
1061 ('pid', os.getpid()), 1084 ('pid', os.getpid()),
1062 ('cwd', os.getcwd()), 1085 ('cwd', os.getcwd()),
1063 ('path', sys.path), 1086 ('path', sys.path),
1064 ('environment', sorted( 1087 ('environment', human_sorted(
1065 ("%s = %s" % (k, v)) 1088 f"{k} = {v}"
1066 for k, v in iitems(os.environ) 1089 for k, v in os.environ.items()
1067 if any(slug in k for slug in ("COV", "PY")) 1090 if (
1091 any(slug in k for slug in ("COV", "PY")) or
1092 (k in ("HOME", "TEMP", "TMP"))
1093 )
1068 )), 1094 )),
1069 ('command_line', " ".join(getattr(sys, 'argv', ['-none-']))), 1095 ('command_line', " ".join(getattr(sys, 'argv', ['-none-']))),
1070 ] 1096 ]
1071 1097
1072 if self._inorout: 1098 if self._inorout:

eric ide

mercurial