1  import math
       2  import os.path
       3  import sys
       4  import sysconfig
       5  import textwrap
       6  from test import support
       7  
       8  
       9  def format_duration(seconds):
      10      ms = math.ceil(seconds * 1e3)
      11      seconds, ms = divmod(ms, 1000)
      12      minutes, seconds = divmod(seconds, 60)
      13      hours, minutes = divmod(minutes, 60)
      14  
      15      parts = []
      16      if hours:
      17          parts.append('%s hour' % hours)
      18      if minutes:
      19          parts.append('%s min' % minutes)
      20      if seconds:
      21          if parts:
      22              # 2 min 1 sec
      23              parts.append('%s sec' % seconds)
      24          else:
      25              # 1.0 sec
      26              parts.append('%.1f sec' % (seconds + ms / 1000))
      27      if not parts:
      28          return '%s ms' % ms
      29  
      30      parts = parts[:2]
      31      return ' '.join(parts)
      32  
      33  
      34  def strip_py_suffix(names: list[str]):
      35      if not names:
      36          return
      37      for idx, name in enumerate(names):
      38          basename, ext = os.path.splitext(name)
      39          if ext == '.py':
      40              names[idx] = basename
      41  
      42  
      43  def count(n, word):
      44      if n == 1:
      45          return "%d %s" % (n, word)
      46      else:
      47          return "%d %ss" % (n, word)
      48  
      49  
      50  def printlist(x, width=70, indent=4, file=None):
      51      """Print the elements of iterable x to stdout.
      52  
      53      Optional arg width (default 70) is the maximum line length.
      54      Optional arg indent (default 4) is the number of blanks with which to
      55      begin each line.
      56      """
      57  
      58      blanks = ' ' * indent
      59      # Print the sorted list: 'x' may be a '--random' list or a set()
      60      print(textwrap.fill(' '.join(str(elt) for elt in sorted(x)), width,
      61                          initial_indent=blanks, subsequent_indent=blanks),
      62            file=file)
      63  
      64  
      65  def print_warning(msg):
      66      support.print_warning(msg)
      67  
      68  
      69  orig_unraisablehook = None
      70  
      71  
      72  def regrtest_unraisable_hook(unraisable):
      73      global orig_unraisablehook
      74      support.environment_altered = True
      75      support.print_warning("Unraisable exception")
      76      old_stderr = sys.stderr
      77      try:
      78          support.flush_std_streams()
      79          sys.stderr = support.print_warning.orig_stderr
      80          orig_unraisablehook(unraisable)
      81          sys.stderr.flush()
      82      finally:
      83          sys.stderr = old_stderr
      84  
      85  
      86  def setup_unraisable_hook():
      87      global orig_unraisablehook
      88      orig_unraisablehook = sys.unraisablehook
      89      sys.unraisablehook = regrtest_unraisable_hook
      90  
      91  
      92  orig_threading_excepthook = None
      93  
      94  
      95  def regrtest_threading_excepthook(args):
      96      global orig_threading_excepthook
      97      support.environment_altered = True
      98      support.print_warning(f"Uncaught thread exception: {args.exc_type.__name__}")
      99      old_stderr = sys.stderr
     100      try:
     101          support.flush_std_streams()
     102          sys.stderr = support.print_warning.orig_stderr
     103          orig_threading_excepthook(args)
     104          sys.stderr.flush()
     105      finally:
     106          sys.stderr = old_stderr
     107  
     108  
     109  def setup_threading_excepthook():
     110      global orig_threading_excepthook
     111      import threading
     112      orig_threading_excepthook = threading.excepthook
     113      threading.excepthook = regrtest_threading_excepthook
     114  
     115  
     116  def clear_caches():
     117      # Clear the warnings registry, so they can be displayed again
     118      for mod in sys.modules.values():
     119          if hasattr(mod, '__warningregistry__'):
     120              del mod.__warningregistry__
     121  
     122      # Flush standard output, so that buffered data is sent to the OS and
     123      # associated Python objects are reclaimed.
     124      for stream in (sys.stdout, sys.stderr, sys.__stdout__, sys.__stderr__):
     125          if stream is not None:
     126              stream.flush()
     127  
     128      try:
     129          re = sys.modules['re']
     130      except KeyError:
     131          pass
     132      else:
     133          re.purge()
     134  
     135      try:
     136          _strptime = sys.modules['_strptime']
     137      except KeyError:
     138          pass
     139      else:
     140          _strptime._regex_cache.clear()
     141  
     142      try:
     143          urllib_parse = sys.modules['urllib.parse']
     144      except KeyError:
     145          pass
     146      else:
     147          urllib_parse.clear_cache()
     148  
     149      try:
     150          urllib_request = sys.modules['urllib.request']
     151      except KeyError:
     152          pass
     153      else:
     154          urllib_request.urlcleanup()
     155  
     156      try:
     157          linecache = sys.modules['linecache']
     158      except KeyError:
     159          pass
     160      else:
     161          linecache.clearcache()
     162  
     163      try:
     164          mimetypes = sys.modules['mimetypes']
     165      except KeyError:
     166          pass
     167      else:
     168          mimetypes._default_mime_types()
     169  
     170      try:
     171          filecmp = sys.modules['filecmp']
     172      except KeyError:
     173          pass
     174      else:
     175          filecmp._cache.clear()
     176  
     177      try:
     178          struct = sys.modules['struct']
     179      except KeyError:
     180          pass
     181      else:
     182          struct._clearcache()
     183  
     184      try:
     185          doctest = sys.modules['doctest']
     186      except KeyError:
     187          pass
     188      else:
     189          doctest.master = None
     190  
     191      try:
     192          ctypes = sys.modules['ctypes']
     193      except KeyError:
     194          pass
     195      else:
     196          ctypes._reset_cache()
     197  
     198      try:
     199          typing = sys.modules['typing']
     200      except KeyError:
     201          pass
     202      else:
     203          for f in typing._cleanups:
     204              f()
     205  
     206      try:
     207          fractions = sys.modules['fractions']
     208      except KeyError:
     209          pass
     210      else:
     211          fractions._hash_algorithm.cache_clear()
     212  
     213      try:
     214          inspect = sys.modules['inspect']
     215      except KeyError:
     216          pass
     217      else:
     218          inspect._shadowed_dict_from_mro_tuple.cache_clear()
     219  
     220  
     221  def get_build_info():
     222      # Get most important configure and build options as a list of strings.
     223      # Example: ['debug', 'ASAN+MSAN'] or ['release', 'LTO+PGO'].
     224  
     225      config_args = sysconfig.get_config_var('CONFIG_ARGS') or ''
     226      cflags = sysconfig.get_config_var('PY_CFLAGS') or ''
     227      cflags_nodist = sysconfig.get_config_var('PY_CFLAGS_NODIST') or ''
     228      ldflags_nodist = sysconfig.get_config_var('PY_LDFLAGS_NODIST') or ''
     229  
     230      build = []
     231      if hasattr(sys, 'gettotalrefcount'):
     232          # --with-pydebug
     233          build.append('debug')
     234  
     235          if '-DNDEBUG' in (cflags + cflags_nodist):
     236              build.append('without_assert')
     237      else:
     238          build.append('release')
     239  
     240          if '--with-assertions' in config_args:
     241              build.append('with_assert')
     242          elif '-DNDEBUG' not in (cflags + cflags_nodist):
     243              build.append('with_assert')
     244  
     245      # --enable-framework=name
     246      framework = sysconfig.get_config_var('PYTHONFRAMEWORK')
     247      if framework:
     248          build.append(f'framework={framework}')
     249  
     250      # --enable-shared
     251      shared = int(sysconfig.get_config_var('PY_ENABLE_SHARED') or '0')
     252      if shared:
     253          build.append('shared')
     254  
     255      # --with-lto
     256      optimizations = []
     257      if '-flto=thin' in ldflags_nodist:
     258          optimizations.append('ThinLTO')
     259      elif '-flto' in ldflags_nodist:
     260          optimizations.append('LTO')
     261  
     262      # --enable-optimizations
     263      pgo_options = (
     264          # GCC
     265          '-fprofile-use',
     266          # clang: -fprofile-instr-use=code.profclangd
     267          '-fprofile-instr-use',
     268          # ICC
     269          "-prof-use",
     270      )
     271      if any(option in cflags_nodist for option in pgo_options):
     272          optimizations.append('PGO')
     273      if optimizations:
     274          build.append('+'.join(optimizations))
     275  
     276      # --with-address-sanitizer
     277      sanitizers = []
     278      if support.check_sanitizer(address=True):
     279          sanitizers.append("ASAN")
     280      # --with-memory-sanitizer
     281      if support.check_sanitizer(memory=True):
     282          sanitizers.append("MSAN")
     283      # --with-undefined-behavior-sanitizer
     284      if support.check_sanitizer(ub=True):
     285          sanitizers.append("UBSAN")
     286      if sanitizers:
     287          build.append('+'.join(sanitizers))
     288  
     289      # --with-trace-refs
     290      if hasattr(sys, 'getobjects'):
     291          build.append("TraceRefs")
     292      # --enable-pystats
     293      if hasattr(sys, '_stats_on'):
     294          build.append("pystats")
     295      # --with-valgrind
     296      if sysconfig.get_config_var('WITH_VALGRIND'):
     297          build.append("valgrind")
     298      # --with-dtrace
     299      if sysconfig.get_config_var('WITH_DTRACE'):
     300          build.append("dtrace")
     301  
     302      return build