diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml
index cb2bd3e946..2acc7f5d0d 100644
--- a/.github/workflows/ci.yaml
+++ b/.github/workflows/ci.yaml
@@ -342,6 +342,10 @@ jobs:
- name: check wasm code with prettier
# prettier doesn't handle ignore files very well: https://github.com/prettier/prettier/issues/8506
run: cd wasm && git ls-files -z | xargs -0 prettier --check -u
+ - name: Check update_asdl.sh consistency
+ run: bash scripts/update_asdl.sh && git diff --exit-code
+ - name: Check whats_left is not broken
+ run: python -I whats_left.py
miri:
name: Run tests under miri
diff --git a/Lib/cgitb.py b/Lib/cgitb.py
index 449bb1d43e..4f81271be3 100644
--- a/Lib/cgitb.py
+++ b/Lib/cgitb.py
@@ -1,321 +1,321 @@
-"""More comprehensive traceback formatting for Python scripts.
-
-To enable this module, do:
-
- import cgitb; cgitb.enable()
-
-at the top of your script. The optional arguments to enable() are:
-
- display - if true, tracebacks are displayed in the web browser
- logdir - if set, tracebacks are written to files in this directory
- context - number of lines of source code to show for each stack frame
- format - 'text' or 'html' controls the output format
-
-By default, tracebacks are displayed but not saved, the context is 5 lines
-and the output format is 'html' (for backwards compatibility with the
-original use of this module)
-
-Alternatively, if you have caught an exception and want cgitb to display it
-for you, call cgitb.handler(). The optional argument to handler() is a
-3-item tuple (etype, evalue, etb) just like the value of sys.exc_info().
-The default handler displays output as HTML.
-
-"""
-import inspect
-import keyword
-import linecache
-import os
-import pydoc
-import sys
-import tempfile
-import time
-import tokenize
-import traceback
-
-def reset():
- """Return a string that resets the CGI and browser to a known state."""
- return '''
-
A problem occurred in a Python script. Here is the sequence of
-function calls leading up to the error, in the order they occurred.
'''
-
- indent = '":
- call += inspect.formatargvalues(args, varargs, varkw, locals,
- formatvalue=lambda value: '=' + pydoc.html.repr(value))
-
- highlight = {}
- def reader(lnum=[lnum]):
- highlight[lnum[0]] = 1
- try: return linecache.getline(file, lnum[0])
- finally: lnum[0] += 1
- vars = scanvars(reader, frame, locals)
-
- rows = ['%s%s %s |
' %
- (' ', link, call)]
- if index is not None:
- i = lnum - index
- for line in lines:
- num = small(' ' * (5-len(str(i))) + str(i)) + ' '
- if i in highlight:
- line = '=>%s%s' % (num, pydoc.html.preformat(line))
- rows.append('%s |
' % line)
- else:
- line = ' %s%s' % (num, pydoc.html.preformat(line))
- rows.append('%s |
' % grey(line))
- i += 1
-
- done, dump = {}, []
- for name, where, value in vars:
- if name in done: continue
- done[name] = 1
- if value is not __UNDEF__:
- if where in ('global', 'builtin'):
- name = ('%s ' % where) + strong(name)
- elif where == 'local':
- name = strong(name)
- else:
- name = where + strong(name.split('.')[-1])
- dump.append('%s = %s' % (name, pydoc.html.repr(value)))
- else:
- dump.append(name + ' undefined')
-
- rows.append('%s |
' % small(grey(', '.join(dump))))
- frames.append('''
-''' % '\n'.join(rows))
-
- exception = ['%s: %s' % (strong(pydoc.html.escape(str(etype))),
- pydoc.html.escape(str(evalue)))]
- for name in dir(evalue):
- if name[:1] == '_': continue
- value = pydoc.html.repr(getattr(evalue, name))
- exception.append('\n
%s%s =\n%s' % (indent, name, value))
-
- return head + ''.join(frames) + ''.join(exception) + '''
-
-
-
-''' % pydoc.html.escape(
- ''.join(traceback.format_exception(etype, evalue, etb)))
-
-def text(einfo, context=5):
- """Return a plain text document describing a given traceback."""
- etype, evalue, etb = einfo
- if isinstance(etype, type):
- etype = etype.__name__
- pyver = 'Python ' + sys.version.split()[0] + ': ' + sys.executable
- date = time.ctime(time.time())
- head = "%s\n%s\n%s\n" % (str(etype), pyver, date) + '''
-A problem occurred in a Python script. Here is the sequence of
-function calls leading up to the error, in the order they occurred.
-'''
-
- frames = []
- records = inspect.getinnerframes(etb, context)
- for frame, file, lnum, func, lines, index in records:
- file = file and os.path.abspath(file) or '?'
- args, varargs, varkw, locals = inspect.getargvalues(frame)
- call = ''
- if func != '?':
- call = 'in ' + func
- if func != "":
- call += inspect.formatargvalues(args, varargs, varkw, locals,
- formatvalue=lambda value: '=' + pydoc.text.repr(value))
-
- highlight = {}
- def reader(lnum=[lnum]):
- highlight[lnum[0]] = 1
- try: return linecache.getline(file, lnum[0])
- finally: lnum[0] += 1
- vars = scanvars(reader, frame, locals)
-
- rows = [' %s %s' % (file, call)]
- if index is not None:
- i = lnum - index
- for line in lines:
- num = '%5d ' % i
- rows.append(num+line.rstrip())
- i += 1
-
- done, dump = {}, []
- for name, where, value in vars:
- if name in done: continue
- done[name] = 1
- if value is not __UNDEF__:
- if where == 'global': name = 'global ' + name
- elif where != 'local': name = where + name.split('.')[-1]
- dump.append('%s = %s' % (name, pydoc.text.repr(value)))
- else:
- dump.append(name + ' undefined')
-
- rows.append('\n'.join(dump))
- frames.append('\n%s\n' % '\n'.join(rows))
-
- exception = ['%s: %s' % (str(etype), str(evalue))]
- for name in dir(evalue):
- value = pydoc.text.repr(getattr(evalue, name))
- exception.append('\n%s%s = %s' % (" "*4, name, value))
-
- return head + ''.join(frames) + ''.join(exception) + '''
-
-The above is a description of an error in a Python program. Here is
-the original traceback:
-
-%s
-''' % ''.join(traceback.format_exception(etype, evalue, etb))
-
-class Hook:
- """A hook to replace sys.excepthook that shows tracebacks in HTML."""
-
- def __init__(self, display=1, logdir=None, context=5, file=None,
- format="html"):
- self.display = display # send tracebacks to browser if true
- self.logdir = logdir # log tracebacks to files if not None
- self.context = context # number of source code lines per frame
- self.file = file or sys.stdout # place to send the output
- self.format = format
-
- def __call__(self, etype, evalue, etb):
- self.handle((etype, evalue, etb))
-
- def handle(self, info=None):
- info = info or sys.exc_info()
- if self.format == "html":
- self.file.write(reset())
-
- formatter = (self.format=="html") and html or text
- plain = False
- try:
- doc = formatter(info, self.context)
- except: # just in case something goes wrong
- doc = ''.join(traceback.format_exception(*info))
- plain = True
-
- if self.display:
- if plain:
- doc = pydoc.html.escape(doc)
- self.file.write('' + doc + '
\n')
- else:
- self.file.write(doc + '\n')
- else:
- self.file.write('A problem occurred in a Python script.\n')
-
- if self.logdir is not None:
- suffix = ['.txt', '.html'][self.format=="html"]
- (fd, path) = tempfile.mkstemp(suffix=suffix, dir=self.logdir)
-
- try:
- with os.fdopen(fd, 'w') as file:
- file.write(doc)
- msg = '%s contains the description of this error.' % path
- except:
- msg = 'Tried to save traceback to %s, but failed.' % path
-
- if self.format == 'html':
- self.file.write('
%s
\n' % msg)
- else:
- self.file.write(msg + '\n')
- try:
- self.file.flush()
- except: pass
-
-handler = Hook().handle
-def enable(display=1, logdir=None, context=5, format="html"):
- """Install an exception handler that formats tracebacks as HTML.
-
- The optional argument 'display' can be set to 0 to suppress sending the
- traceback to the browser, and 'logdir' can be set to a directory to cause
- tracebacks to be written to files there."""
- sys.excepthook = Hook(display=display, logdir=logdir,
- context=context, format=format)
+"""More comprehensive traceback formatting for Python scripts.
+
+To enable this module, do:
+
+ import cgitb; cgitb.enable()
+
+at the top of your script. The optional arguments to enable() are:
+
+ display - if true, tracebacks are displayed in the web browser
+ logdir - if set, tracebacks are written to files in this directory
+ context - number of lines of source code to show for each stack frame
+ format - 'text' or 'html' controls the output format
+
+By default, tracebacks are displayed but not saved, the context is 5 lines
+and the output format is 'html' (for backwards compatibility with the
+original use of this module)
+
+Alternatively, if you have caught an exception and want cgitb to display it
+for you, call cgitb.handler(). The optional argument to handler() is a
+3-item tuple (etype, evalue, etb) just like the value of sys.exc_info().
+The default handler displays output as HTML.
+
+"""
+import inspect
+import keyword
+import linecache
+import os
+import pydoc
+import sys
+import tempfile
+import time
+import tokenize
+import traceback
+
+def reset():
+ """Return a string that resets the CGI and browser to a known state."""
+ return '''
+ --> -->
+
+ '''
+
+__UNDEF__ = [] # a special sentinel object
+def small(text):
+ if text:
+ return '' + text + ''
+ else:
+ return ''
+
+def strong(text):
+ if text:
+ return '' + text + ''
+ else:
+ return ''
+
+def grey(text):
+ if text:
+ return '' + text + ''
+ else:
+ return ''
+
+def lookup(name, frame, locals):
+ """Find the value for a given name in the given environment."""
+ if name in locals:
+ return 'local', locals[name]
+ if name in frame.f_globals:
+ return 'global', frame.f_globals[name]
+ if '__builtins__' in frame.f_globals:
+ builtins = frame.f_globals['__builtins__']
+ if type(builtins) is type({}):
+ if name in builtins:
+ return 'builtin', builtins[name]
+ else:
+ if hasattr(builtins, name):
+ return 'builtin', getattr(builtins, name)
+ return None, __UNDEF__
+
+def scanvars(reader, frame, locals):
+ """Scan one logical line of Python and look up values of variables used."""
+ vars, lasttoken, parent, prefix, value = [], None, None, '', __UNDEF__
+ for ttype, token, start, end, line in tokenize.generate_tokens(reader):
+ if ttype == tokenize.NEWLINE: break
+ if ttype == tokenize.NAME and token not in keyword.kwlist:
+ if lasttoken == '.':
+ if parent is not __UNDEF__:
+ value = getattr(parent, token, __UNDEF__)
+ vars.append((prefix + token, prefix, value))
+ else:
+ where, value = lookup(token, frame, locals)
+ vars.append((token, where, value))
+ elif token == '.':
+ prefix += lasttoken + '.'
+ parent = value
+ else:
+ parent, prefix = None, ''
+ lasttoken = token
+ return vars
+
+def html(einfo, context=5):
+ """Return a nice HTML document describing a given traceback."""
+ etype, evalue, etb = einfo
+ if isinstance(etype, type):
+ etype = etype.__name__
+ pyver = 'Python ' + sys.version.split()[0] + ': ' + sys.executable
+ date = time.ctime(time.time())
+ head = '' + pydoc.html.heading(
+ '%s' %
+ strong(pydoc.html.escape(str(etype))),
+ '#ffffff', '#6622aa', pyver + '
' + date) + '''
+A problem occurred in a Python script. Here is the sequence of
+function calls leading up to the error, in the order they occurred.
'''
+
+ indent = '' + small(' ' * 5) + ' '
+ frames = []
+ records = inspect.getinnerframes(etb, context)
+ for frame, file, lnum, func, lines, index in records:
+ if file:
+ file = os.path.abspath(file)
+ link = '%s' % (file, pydoc.html.escape(file))
+ else:
+ file = link = '?'
+ args, varargs, varkw, locals = inspect.getargvalues(frame)
+ call = ''
+ if func != '?':
+ call = 'in ' + strong(pydoc.html.escape(func))
+ if func != "":
+ call += inspect.formatargvalues(args, varargs, varkw, locals,
+ formatvalue=lambda value: '=' + pydoc.html.repr(value))
+
+ highlight = {}
+ def reader(lnum=[lnum]):
+ highlight[lnum[0]] = 1
+ try: return linecache.getline(file, lnum[0])
+ finally: lnum[0] += 1
+ vars = scanvars(reader, frame, locals)
+
+ rows = ['%s%s %s |
' %
+ (' ', link, call)]
+ if index is not None:
+ i = lnum - index
+ for line in lines:
+ num = small(' ' * (5-len(str(i))) + str(i)) + ' '
+ if i in highlight:
+ line = '=>%s%s' % (num, pydoc.html.preformat(line))
+ rows.append('%s |
' % line)
+ else:
+ line = ' %s%s' % (num, pydoc.html.preformat(line))
+ rows.append('%s |
' % grey(line))
+ i += 1
+
+ done, dump = {}, []
+ for name, where, value in vars:
+ if name in done: continue
+ done[name] = 1
+ if value is not __UNDEF__:
+ if where in ('global', 'builtin'):
+ name = ('%s ' % where) + strong(name)
+ elif where == 'local':
+ name = strong(name)
+ else:
+ name = where + strong(name.split('.')[-1])
+ dump.append('%s = %s' % (name, pydoc.html.repr(value)))
+ else:
+ dump.append(name + ' undefined')
+
+ rows.append('%s |
' % small(grey(', '.join(dump))))
+ frames.append('''
+''' % '\n'.join(rows))
+
+ exception = ['%s: %s' % (strong(pydoc.html.escape(str(etype))),
+ pydoc.html.escape(str(evalue)))]
+ for name in dir(evalue):
+ if name[:1] == '_': continue
+ value = pydoc.html.repr(getattr(evalue, name))
+ exception.append('\n
%s%s =\n%s' % (indent, name, value))
+
+ return head + ''.join(frames) + ''.join(exception) + '''
+
+
+
+''' % pydoc.html.escape(
+ ''.join(traceback.format_exception(etype, evalue, etb)))
+
+def text(einfo, context=5):
+ """Return a plain text document describing a given traceback."""
+ etype, evalue, etb = einfo
+ if isinstance(etype, type):
+ etype = etype.__name__
+ pyver = 'Python ' + sys.version.split()[0] + ': ' + sys.executable
+ date = time.ctime(time.time())
+ head = "%s\n%s\n%s\n" % (str(etype), pyver, date) + '''
+A problem occurred in a Python script. Here is the sequence of
+function calls leading up to the error, in the order they occurred.
+'''
+
+ frames = []
+ records = inspect.getinnerframes(etb, context)
+ for frame, file, lnum, func, lines, index in records:
+ file = file and os.path.abspath(file) or '?'
+ args, varargs, varkw, locals = inspect.getargvalues(frame)
+ call = ''
+ if func != '?':
+ call = 'in ' + func
+ if func != "":
+ call += inspect.formatargvalues(args, varargs, varkw, locals,
+ formatvalue=lambda value: '=' + pydoc.text.repr(value))
+
+ highlight = {}
+ def reader(lnum=[lnum]):
+ highlight[lnum[0]] = 1
+ try: return linecache.getline(file, lnum[0])
+ finally: lnum[0] += 1
+ vars = scanvars(reader, frame, locals)
+
+ rows = [' %s %s' % (file, call)]
+ if index is not None:
+ i = lnum - index
+ for line in lines:
+ num = '%5d ' % i
+ rows.append(num+line.rstrip())
+ i += 1
+
+ done, dump = {}, []
+ for name, where, value in vars:
+ if name in done: continue
+ done[name] = 1
+ if value is not __UNDEF__:
+ if where == 'global': name = 'global ' + name
+ elif where != 'local': name = where + name.split('.')[-1]
+ dump.append('%s = %s' % (name, pydoc.text.repr(value)))
+ else:
+ dump.append(name + ' undefined')
+
+ rows.append('\n'.join(dump))
+ frames.append('\n%s\n' % '\n'.join(rows))
+
+ exception = ['%s: %s' % (str(etype), str(evalue))]
+ for name in dir(evalue):
+ value = pydoc.text.repr(getattr(evalue, name))
+ exception.append('\n%s%s = %s' % (" "*4, name, value))
+
+ return head + ''.join(frames) + ''.join(exception) + '''
+
+The above is a description of an error in a Python program. Here is
+the original traceback:
+
+%s
+''' % ''.join(traceback.format_exception(etype, evalue, etb))
+
+class Hook:
+ """A hook to replace sys.excepthook that shows tracebacks in HTML."""
+
+ def __init__(self, display=1, logdir=None, context=5, file=None,
+ format="html"):
+ self.display = display # send tracebacks to browser if true
+ self.logdir = logdir # log tracebacks to files if not None
+ self.context = context # number of source code lines per frame
+ self.file = file or sys.stdout # place to send the output
+ self.format = format
+
+ def __call__(self, etype, evalue, etb):
+ self.handle((etype, evalue, etb))
+
+ def handle(self, info=None):
+ info = info or sys.exc_info()
+ if self.format == "html":
+ self.file.write(reset())
+
+ formatter = (self.format=="html") and html or text
+ plain = False
+ try:
+ doc = formatter(info, self.context)
+ except: # just in case something goes wrong
+ doc = ''.join(traceback.format_exception(*info))
+ plain = True
+
+ if self.display:
+ if plain:
+ doc = pydoc.html.escape(doc)
+ self.file.write('' + doc + '
\n')
+ else:
+ self.file.write(doc + '\n')
+ else:
+ self.file.write('A problem occurred in a Python script.\n')
+
+ if self.logdir is not None:
+ suffix = ['.txt', '.html'][self.format=="html"]
+ (fd, path) = tempfile.mkstemp(suffix=suffix, dir=self.logdir)
+
+ try:
+ with os.fdopen(fd, 'w') as file:
+ file.write(doc)
+ msg = '%s contains the description of this error.' % path
+ except:
+ msg = 'Tried to save traceback to %s, but failed.' % path
+
+ if self.format == 'html':
+ self.file.write('
%s
\n' % msg)
+ else:
+ self.file.write(msg + '\n')
+ try:
+ self.file.flush()
+ except: pass
+
+handler = Hook().handle
+def enable(display=1, logdir=None, context=5, format="html"):
+ """Install an exception handler that formats tracebacks as HTML.
+
+ The optional argument 'display' can be set to 0 to suppress sending the
+ traceback to the browser, and 'logdir' can be set to a directory to cause
+ tracebacks to be written to files there."""
+ sys.excepthook = Hook(display=display, logdir=logdir,
+ context=context, format=format)
diff --git a/Lib/keyword.py b/Lib/keyword.py
index eab8204c91..cc2b46b722 100644
--- a/Lib/keyword.py
+++ b/Lib/keyword.py
@@ -1,63 +1,63 @@
-"""Keywords (from "Grammar/python.gram")
-
-This file is automatically generated; please don't muck it up!
-
-To update the symbols in this file, 'cd' to the top directory of
-the python source tree and run:
-
- PYTHONPATH=Tools/peg_generator python3 -m pegen.keywordgen \
- Grammar/python.gram \
- Grammar/Tokens \
- Lib/keyword.py
-
-Alternatively, you can run 'make regen-keyword'.
-"""
-
-__all__ = ["iskeyword", "issoftkeyword", "kwlist", "softkwlist"]
-
-kwlist = [
- 'False',
- 'None',
- 'True',
- 'and',
- 'as',
- 'assert',
- 'async',
- 'await',
- 'break',
- 'class',
- 'continue',
- 'def',
- 'del',
- 'elif',
- 'else',
- 'except',
- 'finally',
- 'for',
- 'from',
- 'global',
- 'if',
- 'import',
- 'in',
- 'is',
- 'lambda',
- 'nonlocal',
- 'not',
- 'or',
- 'pass',
- 'raise',
- 'return',
- 'try',
- 'while',
- 'with',
- 'yield'
-]
-
-softkwlist = [
- '_',
- 'case',
- 'match'
-]
-
-iskeyword = frozenset(kwlist).__contains__
-issoftkeyword = frozenset(softkwlist).__contains__
+"""Keywords (from "Grammar/python.gram")
+
+This file is automatically generated; please don't muck it up!
+
+To update the symbols in this file, 'cd' to the top directory of
+the python source tree and run:
+
+ PYTHONPATH=Tools/peg_generator python3 -m pegen.keywordgen \
+ Grammar/python.gram \
+ Grammar/Tokens \
+ Lib/keyword.py
+
+Alternatively, you can run 'make regen-keyword'.
+"""
+
+__all__ = ["iskeyword", "issoftkeyword", "kwlist", "softkwlist"]
+
+kwlist = [
+ 'False',
+ 'None',
+ 'True',
+ 'and',
+ 'as',
+ 'assert',
+ 'async',
+ 'await',
+ 'break',
+ 'class',
+ 'continue',
+ 'def',
+ 'del',
+ 'elif',
+ 'else',
+ 'except',
+ 'finally',
+ 'for',
+ 'from',
+ 'global',
+ 'if',
+ 'import',
+ 'in',
+ 'is',
+ 'lambda',
+ 'nonlocal',
+ 'not',
+ 'or',
+ 'pass',
+ 'raise',
+ 'return',
+ 'try',
+ 'while',
+ 'with',
+ 'yield'
+]
+
+softkwlist = [
+ '_',
+ 'case',
+ 'match'
+]
+
+iskeyword = frozenset(kwlist).__contains__
+issoftkeyword = frozenset(softkwlist).__contains__
diff --git a/Lib/platform.py b/Lib/platform.py
index e32f9c11cd..fe88fa9d52 100755
--- a/Lib/platform.py
+++ b/Lib/platform.py
@@ -1065,7 +1065,13 @@ def _sys_version(sys_version=None):
repr(sys_version))
version, buildno, builddate, buildtime, compiler = \
match.groups()
- name = 'CPython'
+
+ # XXX: RUSTPYTHON support
+ if "rustc" in sys_version:
+ name = "RustPython"
+ else:
+ name = 'CPython'
+
if builddate is None:
builddate = ''
elif buildtime:
diff --git a/Lib/trace.py b/Lib/trace.py
index 85f8471b6d..89f17d485f 100644
--- a/Lib/trace.py
+++ b/Lib/trace.py
@@ -1,756 +1,756 @@
-#!/usr/bin/env python3
-
-# portions copyright 2001, Autonomous Zones Industries, Inc., all rights...
-# err... reserved and offered to the public under the terms of the
-# Python 2.2 license.
-# Author: Zooko O'Whielacronx
-# http://zooko.com/
-# mailto:zooko@zooko.com
-#
-# Copyright 2000, Mojam Media, Inc., all rights reserved.
-# Author: Skip Montanaro
-#
-# Copyright 1999, Bioreason, Inc., all rights reserved.
-# Author: Andrew Dalke
-#
-# Copyright 1995-1997, Automatrix, Inc., all rights reserved.
-# Author: Skip Montanaro
-#
-# Copyright 1991-1995, Stichting Mathematisch Centrum, all rights reserved.
-#
-#
-# Permission to use, copy, modify, and distribute this Python software and
-# its associated documentation for any purpose without fee is hereby
-# granted, provided that the above copyright notice appears in all copies,
-# and that both that copyright notice and this permission notice appear in
-# supporting documentation, and that the name of neither Automatrix,
-# Bioreason or Mojam Media be used in advertising or publicity pertaining to
-# distribution of the software without specific, written prior permission.
-#
-"""program/module to trace Python program or function execution
-
-Sample use, command line:
- trace.py -c -f counts --ignore-dir '$prefix' spam.py eggs
- trace.py -t --ignore-dir '$prefix' spam.py eggs
- trace.py --trackcalls spam.py eggs
-
-Sample use, programmatically
- import sys
-
- # create a Trace object, telling it what to ignore, and whether to
- # do tracing or line-counting or both.
- tracer = trace.Trace(ignoredirs=[sys.base_prefix, sys.base_exec_prefix,],
- trace=0, count=1)
- # run the new command using the given tracer
- tracer.run('main()')
- # make a report, placing output in /tmp
- r = tracer.results()
- r.write_results(show_missing=True, coverdir="/tmp")
-"""
-__all__ = ['Trace', 'CoverageResults']
-
-import linecache
-import os
-import sys
-import sysconfig
-import token
-import tokenize
-import inspect
-import gc
-import dis
-import pickle
-from time import monotonic as _time
-
-import threading
-
-PRAGMA_NOCOVER = "#pragma NO COVER"
-
-class _Ignore:
- def __init__(self, modules=None, dirs=None):
- self._mods = set() if not modules else set(modules)
- self._dirs = [] if not dirs else [os.path.normpath(d)
- for d in dirs]
- self._ignore = { '': 1 }
-
- def names(self, filename, modulename):
- if modulename in self._ignore:
- return self._ignore[modulename]
-
- # haven't seen this one before, so see if the module name is
- # on the ignore list.
- if modulename in self._mods: # Identical names, so ignore
- self._ignore[modulename] = 1
- return 1
-
- # check if the module is a proper submodule of something on
- # the ignore list
- for mod in self._mods:
- # Need to take some care since ignoring
- # "cmp" mustn't mean ignoring "cmpcache" but ignoring
- # "Spam" must also mean ignoring "Spam.Eggs".
- if modulename.startswith(mod + '.'):
- self._ignore[modulename] = 1
- return 1
-
- # Now check that filename isn't in one of the directories
- if filename is None:
- # must be a built-in, so we must ignore
- self._ignore[modulename] = 1
- return 1
-
- # Ignore a file when it contains one of the ignorable paths
- for d in self._dirs:
- # The '+ os.sep' is to ensure that d is a parent directory,
- # as compared to cases like:
- # d = "/usr/local"
- # filename = "/usr/local.py"
- # or
- # d = "/usr/local.py"
- # filename = "/usr/local.py"
- if filename.startswith(d + os.sep):
- self._ignore[modulename] = 1
- return 1
-
- # Tried the different ways, so we don't ignore this module
- self._ignore[modulename] = 0
- return 0
-
-def _modname(path):
- """Return a plausible module name for the patch."""
-
- base = os.path.basename(path)
- filename, ext = os.path.splitext(base)
- return filename
-
-def _fullmodname(path):
- """Return a plausible module name for the path."""
-
- # If the file 'path' is part of a package, then the filename isn't
- # enough to uniquely identify it. Try to do the right thing by
- # looking in sys.path for the longest matching prefix. We'll
- # assume that the rest is the package name.
-
- comparepath = os.path.normcase(path)
- longest = ""
- for dir in sys.path:
- dir = os.path.normcase(dir)
- if comparepath.startswith(dir) and comparepath[len(dir)] == os.sep:
- if len(dir) > len(longest):
- longest = dir
-
- if longest:
- base = path[len(longest) + 1:]
- else:
- base = path
- # the drive letter is never part of the module name
- drive, base = os.path.splitdrive(base)
- base = base.replace(os.sep, ".")
- if os.altsep:
- base = base.replace(os.altsep, ".")
- filename, ext = os.path.splitext(base)
- return filename.lstrip(".")
-
-class CoverageResults:
- def __init__(self, counts=None, calledfuncs=None, infile=None,
- callers=None, outfile=None):
- self.counts = counts
- if self.counts is None:
- self.counts = {}
- self.counter = self.counts.copy() # map (filename, lineno) to count
- self.calledfuncs = calledfuncs
- if self.calledfuncs is None:
- self.calledfuncs = {}
- self.calledfuncs = self.calledfuncs.copy()
- self.callers = callers
- if self.callers is None:
- self.callers = {}
- self.callers = self.callers.copy()
- self.infile = infile
- self.outfile = outfile
- if self.infile:
- # Try to merge existing counts file.
- try:
- with open(self.infile, 'rb') as f:
- counts, calledfuncs, callers = pickle.load(f)
- self.update(self.__class__(counts, calledfuncs, callers))
- except (OSError, EOFError, ValueError) as err:
- print(("Skipping counts file %r: %s"
- % (self.infile, err)), file=sys.stderr)
-
- def is_ignored_filename(self, filename):
- """Return True if the filename does not refer to a file
- we want to have reported.
- """
- return filename.startswith('<') and filename.endswith('>')
-
- def update(self, other):
- """Merge in the data from another CoverageResults"""
- counts = self.counts
- calledfuncs = self.calledfuncs
- callers = self.callers
- other_counts = other.counts
- other_calledfuncs = other.calledfuncs
- other_callers = other.callers
-
- for key in other_counts:
- counts[key] = counts.get(key, 0) + other_counts[key]
-
- for key in other_calledfuncs:
- calledfuncs[key] = 1
-
- for key in other_callers:
- callers[key] = 1
-
- def write_results(self, show_missing=True, summary=False, coverdir=None):
- """
- Write the coverage results.
-
- :param show_missing: Show lines that had no hits.
- :param summary: Include coverage summary per module.
- :param coverdir: If None, the results of each module are placed in its
- directory, otherwise it is included in the directory
- specified.
- """
- if self.calledfuncs:
- print()
- print("functions called:")
- calls = self.calledfuncs
- for filename, modulename, funcname in sorted(calls):
- print(("filename: %s, modulename: %s, funcname: %s"
- % (filename, modulename, funcname)))
-
- if self.callers:
- print()
- print("calling relationships:")
- lastfile = lastcfile = ""
- for ((pfile, pmod, pfunc), (cfile, cmod, cfunc)) \
- in sorted(self.callers):
- if pfile != lastfile:
- print()
- print("***", pfile, "***")
- lastfile = pfile
- lastcfile = ""
- if cfile != pfile and lastcfile != cfile:
- print(" -->", cfile)
- lastcfile = cfile
- print(" %s.%s -> %s.%s" % (pmod, pfunc, cmod, cfunc))
-
- # turn the counts data ("(filename, lineno) = count") into something
- # accessible on a per-file basis
- per_file = {}
- for filename, lineno in self.counts:
- lines_hit = per_file[filename] = per_file.get(filename, {})
- lines_hit[lineno] = self.counts[(filename, lineno)]
-
- # accumulate summary info, if needed
- sums = {}
-
- for filename, count in per_file.items():
- if self.is_ignored_filename(filename):
- continue
-
- if filename.endswith(".pyc"):
- filename = filename[:-1]
-
- if coverdir is None:
- dir = os.path.dirname(os.path.abspath(filename))
- modulename = _modname(filename)
- else:
- dir = coverdir
- if not os.path.exists(dir):
- os.makedirs(dir)
- modulename = _fullmodname(filename)
-
- # If desired, get a list of the line numbers which represent
- # executable content (returned as a dict for better lookup speed)
- if show_missing:
- lnotab = _find_executable_linenos(filename)
- else:
- lnotab = {}
- source = linecache.getlines(filename)
- coverpath = os.path.join(dir, modulename + ".cover")
- with open(filename, 'rb') as fp:
- encoding, _ = tokenize.detect_encoding(fp.readline)
- n_hits, n_lines = self.write_results_file(coverpath, source,
- lnotab, count, encoding)
- if summary and n_lines:
- percent = int(100 * n_hits / n_lines)
- sums[modulename] = n_lines, percent, modulename, filename
-
-
- if summary and sums:
- print("lines cov% module (path)")
- for m in sorted(sums):
- n_lines, percent, modulename, filename = sums[m]
- print("%5d %3d%% %s (%s)" % sums[m])
-
- if self.outfile:
- # try and store counts and module info into self.outfile
- try:
- with open(self.outfile, 'wb') as f:
- pickle.dump((self.counts, self.calledfuncs, self.callers),
- f, 1)
- except OSError as err:
- print("Can't save counts files because %s" % err, file=sys.stderr)
-
- def write_results_file(self, path, lines, lnotab, lines_hit, encoding=None):
- """Return a coverage results file in path."""
- # ``lnotab`` is a dict of executable lines, or a line number "table"
-
- try:
- outfile = open(path, "w", encoding=encoding)
- except OSError as err:
- print(("trace: Could not open %r for writing: %s "
- "- skipping" % (path, err)), file=sys.stderr)
- return 0, 0
-
- n_lines = 0
- n_hits = 0
- with outfile:
- for lineno, line in enumerate(lines, 1):
- # do the blank/comment match to try to mark more lines
- # (help the reader find stuff that hasn't been covered)
- if lineno in lines_hit:
- outfile.write("%5d: " % lines_hit[lineno])
- n_hits += 1
- n_lines += 1
- elif lineno in lnotab and not PRAGMA_NOCOVER in line:
- # Highlight never-executed lines, unless the line contains
- # #pragma: NO COVER
- outfile.write(">>>>>> ")
- n_lines += 1
- else:
- outfile.write(" ")
- outfile.write(line.expandtabs(8))
-
- return n_hits, n_lines
-
-def _find_lines_from_code(code, strs):
- """Return dict where keys are lines in the line number table."""
- linenos = {}
-
- for _, lineno in dis.findlinestarts(code):
- if lineno not in strs:
- linenos[lineno] = 1
-
- return linenos
-
-def _find_lines(code, strs):
- """Return lineno dict for all code objects reachable from code."""
- # get all of the lineno information from the code of this scope level
- linenos = _find_lines_from_code(code, strs)
-
- # and check the constants for references to other code objects
- for c in code.co_consts:
- if inspect.iscode(c):
- # find another code object, so recurse into it
- linenos.update(_find_lines(c, strs))
- return linenos
-
-def _find_strings(filename, encoding=None):
- """Return a dict of possible docstring positions.
-
- The dict maps line numbers to strings. There is an entry for
- line that contains only a string or a part of a triple-quoted
- string.
- """
- d = {}
- # If the first token is a string, then it's the module docstring.
- # Add this special case so that the test in the loop passes.
- prev_ttype = token.INDENT
- with open(filename, encoding=encoding) as f:
- tok = tokenize.generate_tokens(f.readline)
- for ttype, tstr, start, end, line in tok:
- if ttype == token.STRING:
- if prev_ttype == token.INDENT:
- sline, scol = start
- eline, ecol = end
- for i in range(sline, eline + 1):
- d[i] = 1
- prev_ttype = ttype
- return d
-
-def _find_executable_linenos(filename):
- """Return dict where keys are line numbers in the line number table."""
- try:
- with tokenize.open(filename) as f:
- prog = f.read()
- encoding = f.encoding
- except OSError as err:
- print(("Not printing coverage data for %r: %s"
- % (filename, err)), file=sys.stderr)
- return {}
- code = compile(prog, filename, "exec")
- strs = _find_strings(filename, encoding)
- return _find_lines(code, strs)
-
-class Trace:
- def __init__(self, count=1, trace=1, countfuncs=0, countcallers=0,
- ignoremods=(), ignoredirs=(), infile=None, outfile=None,
- timing=False):
- """
- @param count true iff it should count number of times each
- line is executed
- @param trace true iff it should print out each line that is
- being counted
- @param countfuncs true iff it should just output a list of
- (filename, modulename, funcname,) for functions
- that were called at least once; This overrides
- `count' and `trace'
- @param ignoremods a list of the names of modules to ignore
- @param ignoredirs a list of the names of directories to ignore
- all of the (recursive) contents of
- @param infile file from which to read stored counts to be
- added into the results
- @param outfile file in which to write the results
- @param timing true iff timing information be displayed
- """
- self.infile = infile
- self.outfile = outfile
- self.ignore = _Ignore(ignoremods, ignoredirs)
- self.counts = {} # keys are (filename, linenumber)
- self.pathtobasename = {} # for memoizing os.path.basename
- self.donothing = 0
- self.trace = trace
- self._calledfuncs = {}
- self._callers = {}
- self._caller_cache = {}
- self.start_time = None
- if timing:
- self.start_time = _time()
- if countcallers:
- self.globaltrace = self.globaltrace_trackcallers
- elif countfuncs:
- self.globaltrace = self.globaltrace_countfuncs
- elif trace and count:
- self.globaltrace = self.globaltrace_lt
- self.localtrace = self.localtrace_trace_and_count
- elif trace:
- self.globaltrace = self.globaltrace_lt
- self.localtrace = self.localtrace_trace
- elif count:
- self.globaltrace = self.globaltrace_lt
- self.localtrace = self.localtrace_count
- else:
- # Ahem -- do nothing? Okay.
- self.donothing = 1
-
- def run(self, cmd):
- import __main__
- dict = __main__.__dict__
- self.runctx(cmd, dict, dict)
-
- def runctx(self, cmd, globals=None, locals=None):
- if globals is None: globals = {}
- if locals is None: locals = {}
- if not self.donothing:
- threading.settrace(self.globaltrace)
- sys.settrace(self.globaltrace)
- try:
- exec(cmd, globals, locals)
- finally:
- if not self.donothing:
- sys.settrace(None)
- threading.settrace(None)
-
- def runfunc(*args, **kw):
- if len(args) >= 2:
- self, func, *args = args
- elif not args:
- raise TypeError("descriptor 'runfunc' of 'Trace' object "
- "needs an argument")
- elif 'func' in kw:
- func = kw.pop('func')
- self, *args = args
- import warnings
- warnings.warn("Passing 'func' as keyword argument is deprecated",
- DeprecationWarning, stacklevel=2)
- else:
- raise TypeError('runfunc expected at least 1 positional argument, '
- 'got %d' % (len(args)-1))
-
- result = None
- if not self.donothing:
- sys.settrace(self.globaltrace)
- try:
- result = func(*args, **kw)
- finally:
- if not self.donothing:
- sys.settrace(None)
- return result
- runfunc.__text_signature__ = '($self, func, /, *args, **kw)'
-
- def file_module_function_of(self, frame):
- code = frame.f_code
- filename = code.co_filename
- if filename:
- modulename = _modname(filename)
- else:
- modulename = None
-
- funcname = code.co_name
- clsname = None
- if code in self._caller_cache:
- if self._caller_cache[code] is not None:
- clsname = self._caller_cache[code]
- else:
- self._caller_cache[code] = None
- ## use of gc.get_referrers() was suggested by Michael Hudson
- # all functions which refer to this code object
- funcs = [f for f in gc.get_referrers(code)
- if inspect.isfunction(f)]
- # require len(func) == 1 to avoid ambiguity caused by calls to
- # new.function(): "In the face of ambiguity, refuse the
- # temptation to guess."
- if len(funcs) == 1:
- dicts = [d for d in gc.get_referrers(funcs[0])
- if isinstance(d, dict)]
- if len(dicts) == 1:
- classes = [c for c in gc.get_referrers(dicts[0])
- if hasattr(c, "__bases__")]
- if len(classes) == 1:
- # ditto for new.classobj()
- clsname = classes[0].__name__
- # cache the result - assumption is that new.* is
- # not called later to disturb this relationship
- # _caller_cache could be flushed if functions in
- # the new module get called.
- self._caller_cache[code] = clsname
- if clsname is not None:
- funcname = "%s.%s" % (clsname, funcname)
-
- return filename, modulename, funcname
-
- def globaltrace_trackcallers(self, frame, why, arg):
- """Handler for call events.
-
- Adds information about who called who to the self._callers dict.
- """
- if why == 'call':
- # XXX Should do a better job of identifying methods
- this_func = self.file_module_function_of(frame)
- parent_func = self.file_module_function_of(frame.f_back)
- self._callers[(parent_func, this_func)] = 1
-
- def globaltrace_countfuncs(self, frame, why, arg):
- """Handler for call events.
-
- Adds (filename, modulename, funcname) to the self._calledfuncs dict.
- """
- if why == 'call':
- this_func = self.file_module_function_of(frame)
- self._calledfuncs[this_func] = 1
-
- def globaltrace_lt(self, frame, why, arg):
- """Handler for call events.
-
- If the code block being entered is to be ignored, returns `None',
- else returns self.localtrace.
- """
- if why == 'call':
- code = frame.f_code
- filename = frame.f_globals.get('__file__', None)
- if filename:
- # XXX _modname() doesn't work right for packages, so
- # the ignore support won't work right for packages
- modulename = _modname(filename)
- if modulename is not None:
- ignore_it = self.ignore.names(filename, modulename)
- if not ignore_it:
- if self.trace:
- print((" --- modulename: %s, funcname: %s"
- % (modulename, code.co_name)))
- return self.localtrace
- else:
- return None
-
- def localtrace_trace_and_count(self, frame, why, arg):
- if why == "line":
- # record the file name and line number of every trace
- filename = frame.f_code.co_filename
- lineno = frame.f_lineno
- key = filename, lineno
- self.counts[key] = self.counts.get(key, 0) + 1
-
- if self.start_time:
- print('%.2f' % (_time() - self.start_time), end=' ')
- bname = os.path.basename(filename)
- print("%s(%d): %s" % (bname, lineno,
- linecache.getline(filename, lineno)), end='')
- return self.localtrace
-
- def localtrace_trace(self, frame, why, arg):
- if why == "line":
- # record the file name and line number of every trace
- filename = frame.f_code.co_filename
- lineno = frame.f_lineno
-
- if self.start_time:
- print('%.2f' % (_time() - self.start_time), end=' ')
- bname = os.path.basename(filename)
- print("%s(%d): %s" % (bname, lineno,
- linecache.getline(filename, lineno)), end='')
- return self.localtrace
-
- def localtrace_count(self, frame, why, arg):
- if why == "line":
- filename = frame.f_code.co_filename
- lineno = frame.f_lineno
- key = filename, lineno
- self.counts[key] = self.counts.get(key, 0) + 1
- return self.localtrace
-
- def results(self):
- return CoverageResults(self.counts, infile=self.infile,
- outfile=self.outfile,
- calledfuncs=self._calledfuncs,
- callers=self._callers)
-
-def main():
- import argparse
-
- parser = argparse.ArgumentParser()
- parser.add_argument('--version', action='version', version='trace 2.0')
-
- grp = parser.add_argument_group('Main options',
- 'One of these (or --report) must be given')
-
- grp.add_argument('-c', '--count', action='store_true',
- help='Count the number of times each line is executed and write '
- 'the counts to .cover for each module executed, in '
- 'the module\'s directory. See also --coverdir, --file, '
- '--no-report below.')
- grp.add_argument('-t', '--trace', action='store_true',
- help='Print each line to sys.stdout before it is executed')
- grp.add_argument('-l', '--listfuncs', action='store_true',
- help='Keep track of which functions are executed at least once '
- 'and write the results to sys.stdout after the program exits. '
- 'Cannot be specified alongside --trace or --count.')
- grp.add_argument('-T', '--trackcalls', action='store_true',
- help='Keep track of caller/called pairs and write the results to '
- 'sys.stdout after the program exits.')
-
- grp = parser.add_argument_group('Modifiers')
-
- _grp = grp.add_mutually_exclusive_group()
- _grp.add_argument('-r', '--report', action='store_true',
- help='Generate a report from a counts file; does not execute any '
- 'code. --file must specify the results file to read, which '
- 'must have been created in a previous run with --count '
- '--file=FILE')
- _grp.add_argument('-R', '--no-report', action='store_true',
- help='Do not generate the coverage report files. '
- 'Useful if you want to accumulate over several runs.')
-
- grp.add_argument('-f', '--file',
- help='File to accumulate counts over several runs')
- grp.add_argument('-C', '--coverdir',
- help='Directory where the report files go. The coverage report '
- 'for . will be written to file '
- '//.cover')
- grp.add_argument('-m', '--missing', action='store_true',
- help='Annotate executable lines that were not executed with '
- '">>>>>> "')
- grp.add_argument('-s', '--summary', action='store_true',
- help='Write a brief summary for each file to sys.stdout. '
- 'Can only be used with --count or --report')
- grp.add_argument('-g', '--timing', action='store_true',
- help='Prefix each line with the time since the program started. '
- 'Only used while tracing')
-
- grp = parser.add_argument_group('Filters',
- 'Can be specified multiple times')
- grp.add_argument('--ignore-module', action='append', default=[],
- help='Ignore the given module(s) and its submodules '
- '(if it is a package). Accepts comma separated list of '
- 'module names.')
- grp.add_argument('--ignore-dir', action='append', default=[],
- help='Ignore files in the given directory '
- '(multiple directories can be joined by os.pathsep).')
-
- parser.add_argument('--module', action='store_true', default=False,
- help='Trace a module. ')
- parser.add_argument('progname', nargs='?',
- help='file to run as main program')
- parser.add_argument('arguments', nargs=argparse.REMAINDER,
- help='arguments to the program')
-
- opts = parser.parse_args()
-
- if opts.ignore_dir:
- _prefix = sysconfig.get_path("stdlib")
- _exec_prefix = sysconfig.get_path("platstdlib")
-
- def parse_ignore_dir(s):
- s = os.path.expanduser(os.path.expandvars(s))
- s = s.replace('$prefix', _prefix).replace('$exec_prefix', _exec_prefix)
- return os.path.normpath(s)
-
- opts.ignore_module = [mod.strip()
- for i in opts.ignore_module for mod in i.split(',')]
- opts.ignore_dir = [parse_ignore_dir(s)
- for i in opts.ignore_dir for s in i.split(os.pathsep)]
-
- if opts.report:
- if not opts.file:
- parser.error('-r/--report requires -f/--file')
- results = CoverageResults(infile=opts.file, outfile=opts.file)
- return results.write_results(opts.missing, opts.summary, opts.coverdir)
-
- if not any([opts.trace, opts.count, opts.listfuncs, opts.trackcalls]):
- parser.error('must specify one of --trace, --count, --report, '
- '--listfuncs, or --trackcalls')
-
- if opts.listfuncs and (opts.count or opts.trace):
- parser.error('cannot specify both --listfuncs and (--trace or --count)')
-
- if opts.summary and not opts.count:
- parser.error('--summary can only be used with --count or --report')
-
- if opts.progname is None:
- parser.error('progname is missing: required with the main options')
-
- t = Trace(opts.count, opts.trace, countfuncs=opts.listfuncs,
- countcallers=opts.trackcalls, ignoremods=opts.ignore_module,
- ignoredirs=opts.ignore_dir, infile=opts.file,
- outfile=opts.file, timing=opts.timing)
- try:
- if opts.module:
- import runpy
- module_name = opts.progname
- mod_name, mod_spec, code = runpy._get_module_details(module_name)
- sys.argv = [code.co_filename, *opts.arguments]
- globs = {
- '__name__': '__main__',
- '__file__': code.co_filename,
- '__package__': mod_spec.parent,
- '__loader__': mod_spec.loader,
- '__spec__': mod_spec,
- '__cached__': None,
- }
- else:
- sys.argv = [opts.progname, *opts.arguments]
- sys.path[0] = os.path.dirname(opts.progname)
-
- with open(opts.progname, 'rb') as fp:
- code = compile(fp.read(), opts.progname, 'exec')
- # try to emulate __main__ namespace as much as possible
- globs = {
- '__file__': opts.progname,
- '__name__': '__main__',
- '__package__': None,
- '__cached__': None,
- }
- t.runctx(code, globs, globs)
- except OSError as err:
- sys.exit("Cannot run file %r because: %s" % (sys.argv[0], err))
- except SystemExit:
- pass
-
- results = t.results()
-
- if not opts.no_report:
- results.write_results(opts.missing, opts.summary, opts.coverdir)
-
-if __name__=='__main__':
- main()
+#!/usr/bin/env python3
+
+# portions copyright 2001, Autonomous Zones Industries, Inc., all rights...
+# err... reserved and offered to the public under the terms of the
+# Python 2.2 license.
+# Author: Zooko O'Whielacronx
+# http://zooko.com/
+# mailto:zooko@zooko.com
+#
+# Copyright 2000, Mojam Media, Inc., all rights reserved.
+# Author: Skip Montanaro
+#
+# Copyright 1999, Bioreason, Inc., all rights reserved.
+# Author: Andrew Dalke
+#
+# Copyright 1995-1997, Automatrix, Inc., all rights reserved.
+# Author: Skip Montanaro
+#
+# Copyright 1991-1995, Stichting Mathematisch Centrum, all rights reserved.
+#
+#
+# Permission to use, copy, modify, and distribute this Python software and
+# its associated documentation for any purpose without fee is hereby
+# granted, provided that the above copyright notice appears in all copies,
+# and that both that copyright notice and this permission notice appear in
+# supporting documentation, and that the name of neither Automatrix,
+# Bioreason or Mojam Media be used in advertising or publicity pertaining to
+# distribution of the software without specific, written prior permission.
+#
+"""program/module to trace Python program or function execution
+
+Sample use, command line:
+ trace.py -c -f counts --ignore-dir '$prefix' spam.py eggs
+ trace.py -t --ignore-dir '$prefix' spam.py eggs
+ trace.py --trackcalls spam.py eggs
+
+Sample use, programmatically
+ import sys
+
+ # create a Trace object, telling it what to ignore, and whether to
+ # do tracing or line-counting or both.
+ tracer = trace.Trace(ignoredirs=[sys.base_prefix, sys.base_exec_prefix,],
+ trace=0, count=1)
+ # run the new command using the given tracer
+ tracer.run('main()')
+ # make a report, placing output in /tmp
+ r = tracer.results()
+ r.write_results(show_missing=True, coverdir="/tmp")
+"""
+__all__ = ['Trace', 'CoverageResults']
+
+import linecache
+import os
+import sys
+import sysconfig
+import token
+import tokenize
+import inspect
+import gc
+import dis
+import pickle
+from time import monotonic as _time
+
+import threading
+
+PRAGMA_NOCOVER = "#pragma NO COVER"
+
+class _Ignore:
+ def __init__(self, modules=None, dirs=None):
+ self._mods = set() if not modules else set(modules)
+ self._dirs = [] if not dirs else [os.path.normpath(d)
+ for d in dirs]
+ self._ignore = { '': 1 }
+
+ def names(self, filename, modulename):
+ if modulename in self._ignore:
+ return self._ignore[modulename]
+
+ # haven't seen this one before, so see if the module name is
+ # on the ignore list.
+ if modulename in self._mods: # Identical names, so ignore
+ self._ignore[modulename] = 1
+ return 1
+
+ # check if the module is a proper submodule of something on
+ # the ignore list
+ for mod in self._mods:
+ # Need to take some care since ignoring
+ # "cmp" mustn't mean ignoring "cmpcache" but ignoring
+ # "Spam" must also mean ignoring "Spam.Eggs".
+ if modulename.startswith(mod + '.'):
+ self._ignore[modulename] = 1
+ return 1
+
+ # Now check that filename isn't in one of the directories
+ if filename is None:
+ # must be a built-in, so we must ignore
+ self._ignore[modulename] = 1
+ return 1
+
+ # Ignore a file when it contains one of the ignorable paths
+ for d in self._dirs:
+ # The '+ os.sep' is to ensure that d is a parent directory,
+ # as compared to cases like:
+ # d = "/usr/local"
+ # filename = "/usr/local.py"
+ # or
+ # d = "/usr/local.py"
+ # filename = "/usr/local.py"
+ if filename.startswith(d + os.sep):
+ self._ignore[modulename] = 1
+ return 1
+
+ # Tried the different ways, so we don't ignore this module
+ self._ignore[modulename] = 0
+ return 0
+
+def _modname(path):
+ """Return a plausible module name for the patch."""
+
+ base = os.path.basename(path)
+ filename, ext = os.path.splitext(base)
+ return filename
+
+def _fullmodname(path):
+ """Return a plausible module name for the path."""
+
+ # If the file 'path' is part of a package, then the filename isn't
+ # enough to uniquely identify it. Try to do the right thing by
+ # looking in sys.path for the longest matching prefix. We'll
+ # assume that the rest is the package name.
+
+ comparepath = os.path.normcase(path)
+ longest = ""
+ for dir in sys.path:
+ dir = os.path.normcase(dir)
+ if comparepath.startswith(dir) and comparepath[len(dir)] == os.sep:
+ if len(dir) > len(longest):
+ longest = dir
+
+ if longest:
+ base = path[len(longest) + 1:]
+ else:
+ base = path
+ # the drive letter is never part of the module name
+ drive, base = os.path.splitdrive(base)
+ base = base.replace(os.sep, ".")
+ if os.altsep:
+ base = base.replace(os.altsep, ".")
+ filename, ext = os.path.splitext(base)
+ return filename.lstrip(".")
+
+class CoverageResults:
+ def __init__(self, counts=None, calledfuncs=None, infile=None,
+ callers=None, outfile=None):
+ self.counts = counts
+ if self.counts is None:
+ self.counts = {}
+ self.counter = self.counts.copy() # map (filename, lineno) to count
+ self.calledfuncs = calledfuncs
+ if self.calledfuncs is None:
+ self.calledfuncs = {}
+ self.calledfuncs = self.calledfuncs.copy()
+ self.callers = callers
+ if self.callers is None:
+ self.callers = {}
+ self.callers = self.callers.copy()
+ self.infile = infile
+ self.outfile = outfile
+ if self.infile:
+ # Try to merge existing counts file.
+ try:
+ with open(self.infile, 'rb') as f:
+ counts, calledfuncs, callers = pickle.load(f)
+ self.update(self.__class__(counts, calledfuncs, callers))
+ except (OSError, EOFError, ValueError) as err:
+ print(("Skipping counts file %r: %s"
+ % (self.infile, err)), file=sys.stderr)
+
+ def is_ignored_filename(self, filename):
+ """Return True if the filename does not refer to a file
+ we want to have reported.
+ """
+ return filename.startswith('<') and filename.endswith('>')
+
+ def update(self, other):
+ """Merge in the data from another CoverageResults"""
+ counts = self.counts
+ calledfuncs = self.calledfuncs
+ callers = self.callers
+ other_counts = other.counts
+ other_calledfuncs = other.calledfuncs
+ other_callers = other.callers
+
+ for key in other_counts:
+ counts[key] = counts.get(key, 0) + other_counts[key]
+
+ for key in other_calledfuncs:
+ calledfuncs[key] = 1
+
+ for key in other_callers:
+ callers[key] = 1
+
+ def write_results(self, show_missing=True, summary=False, coverdir=None):
+ """
+ Write the coverage results.
+
+ :param show_missing: Show lines that had no hits.
+ :param summary: Include coverage summary per module.
+ :param coverdir: If None, the results of each module are placed in its
+ directory, otherwise it is included in the directory
+ specified.
+ """
+ if self.calledfuncs:
+ print()
+ print("functions called:")
+ calls = self.calledfuncs
+ for filename, modulename, funcname in sorted(calls):
+ print(("filename: %s, modulename: %s, funcname: %s"
+ % (filename, modulename, funcname)))
+
+ if self.callers:
+ print()
+ print("calling relationships:")
+ lastfile = lastcfile = ""
+ for ((pfile, pmod, pfunc), (cfile, cmod, cfunc)) \
+ in sorted(self.callers):
+ if pfile != lastfile:
+ print()
+ print("***", pfile, "***")
+ lastfile = pfile
+ lastcfile = ""
+ if cfile != pfile and lastcfile != cfile:
+ print(" -->", cfile)
+ lastcfile = cfile
+ print(" %s.%s -> %s.%s" % (pmod, pfunc, cmod, cfunc))
+
+ # turn the counts data ("(filename, lineno) = count") into something
+ # accessible on a per-file basis
+ per_file = {}
+ for filename, lineno in self.counts:
+ lines_hit = per_file[filename] = per_file.get(filename, {})
+ lines_hit[lineno] = self.counts[(filename, lineno)]
+
+ # accumulate summary info, if needed
+ sums = {}
+
+ for filename, count in per_file.items():
+ if self.is_ignored_filename(filename):
+ continue
+
+ if filename.endswith(".pyc"):
+ filename = filename[:-1]
+
+ if coverdir is None:
+ dir = os.path.dirname(os.path.abspath(filename))
+ modulename = _modname(filename)
+ else:
+ dir = coverdir
+ if not os.path.exists(dir):
+ os.makedirs(dir)
+ modulename = _fullmodname(filename)
+
+ # If desired, get a list of the line numbers which represent
+ # executable content (returned as a dict for better lookup speed)
+ if show_missing:
+ lnotab = _find_executable_linenos(filename)
+ else:
+ lnotab = {}
+ source = linecache.getlines(filename)
+ coverpath = os.path.join(dir, modulename + ".cover")
+ with open(filename, 'rb') as fp:
+ encoding, _ = tokenize.detect_encoding(fp.readline)
+ n_hits, n_lines = self.write_results_file(coverpath, source,
+ lnotab, count, encoding)
+ if summary and n_lines:
+ percent = int(100 * n_hits / n_lines)
+ sums[modulename] = n_lines, percent, modulename, filename
+
+
+ if summary and sums:
+ print("lines cov% module (path)")
+ for m in sorted(sums):
+ n_lines, percent, modulename, filename = sums[m]
+ print("%5d %3d%% %s (%s)" % sums[m])
+
+ if self.outfile:
+ # try and store counts and module info into self.outfile
+ try:
+ with open(self.outfile, 'wb') as f:
+ pickle.dump((self.counts, self.calledfuncs, self.callers),
+ f, 1)
+ except OSError as err:
+ print("Can't save counts files because %s" % err, file=sys.stderr)
+
+ def write_results_file(self, path, lines, lnotab, lines_hit, encoding=None):
+ """Return a coverage results file in path."""
+ # ``lnotab`` is a dict of executable lines, or a line number "table"
+
+ try:
+ outfile = open(path, "w", encoding=encoding)
+ except OSError as err:
+ print(("trace: Could not open %r for writing: %s "
+ "- skipping" % (path, err)), file=sys.stderr)
+ return 0, 0
+
+ n_lines = 0
+ n_hits = 0
+ with outfile:
+ for lineno, line in enumerate(lines, 1):
+ # do the blank/comment match to try to mark more lines
+ # (help the reader find stuff that hasn't been covered)
+ if lineno in lines_hit:
+ outfile.write("%5d: " % lines_hit[lineno])
+ n_hits += 1
+ n_lines += 1
+ elif lineno in lnotab and not PRAGMA_NOCOVER in line:
+ # Highlight never-executed lines, unless the line contains
+ # #pragma: NO COVER
+ outfile.write(">>>>>> ")
+ n_lines += 1
+ else:
+ outfile.write(" ")
+ outfile.write(line.expandtabs(8))
+
+ return n_hits, n_lines
+
+def _find_lines_from_code(code, strs):
+ """Return dict where keys are lines in the line number table."""
+ linenos = {}
+
+ for _, lineno in dis.findlinestarts(code):
+ if lineno not in strs:
+ linenos[lineno] = 1
+
+ return linenos
+
+def _find_lines(code, strs):
+ """Return lineno dict for all code objects reachable from code."""
+ # get all of the lineno information from the code of this scope level
+ linenos = _find_lines_from_code(code, strs)
+
+ # and check the constants for references to other code objects
+ for c in code.co_consts:
+ if inspect.iscode(c):
+ # find another code object, so recurse into it
+ linenos.update(_find_lines(c, strs))
+ return linenos
+
+def _find_strings(filename, encoding=None):
+ """Return a dict of possible docstring positions.
+
+ The dict maps line numbers to strings. There is an entry for
+ line that contains only a string or a part of a triple-quoted
+ string.
+ """
+ d = {}
+ # If the first token is a string, then it's the module docstring.
+ # Add this special case so that the test in the loop passes.
+ prev_ttype = token.INDENT
+ with open(filename, encoding=encoding) as f:
+ tok = tokenize.generate_tokens(f.readline)
+ for ttype, tstr, start, end, line in tok:
+ if ttype == token.STRING:
+ if prev_ttype == token.INDENT:
+ sline, scol = start
+ eline, ecol = end
+ for i in range(sline, eline + 1):
+ d[i] = 1
+ prev_ttype = ttype
+ return d
+
+def _find_executable_linenos(filename):
+ """Return dict where keys are line numbers in the line number table."""
+ try:
+ with tokenize.open(filename) as f:
+ prog = f.read()
+ encoding = f.encoding
+ except OSError as err:
+ print(("Not printing coverage data for %r: %s"
+ % (filename, err)), file=sys.stderr)
+ return {}
+ code = compile(prog, filename, "exec")
+ strs = _find_strings(filename, encoding)
+ return _find_lines(code, strs)
+
+class Trace:
+ def __init__(self, count=1, trace=1, countfuncs=0, countcallers=0,
+ ignoremods=(), ignoredirs=(), infile=None, outfile=None,
+ timing=False):
+ """
+ @param count true iff it should count number of times each
+ line is executed
+ @param trace true iff it should print out each line that is
+ being counted
+ @param countfuncs true iff it should just output a list of
+ (filename, modulename, funcname,) for functions
+ that were called at least once; This overrides
+ `count' and `trace'
+ @param ignoremods a list of the names of modules to ignore
+ @param ignoredirs a list of the names of directories to ignore
+ all of the (recursive) contents of
+ @param infile file from which to read stored counts to be
+ added into the results
+ @param outfile file in which to write the results
+ @param timing true iff timing information be displayed
+ """
+ self.infile = infile
+ self.outfile = outfile
+ self.ignore = _Ignore(ignoremods, ignoredirs)
+ self.counts = {} # keys are (filename, linenumber)
+ self.pathtobasename = {} # for memoizing os.path.basename
+ self.donothing = 0
+ self.trace = trace
+ self._calledfuncs = {}
+ self._callers = {}
+ self._caller_cache = {}
+ self.start_time = None
+ if timing:
+ self.start_time = _time()
+ if countcallers:
+ self.globaltrace = self.globaltrace_trackcallers
+ elif countfuncs:
+ self.globaltrace = self.globaltrace_countfuncs
+ elif trace and count:
+ self.globaltrace = self.globaltrace_lt
+ self.localtrace = self.localtrace_trace_and_count
+ elif trace:
+ self.globaltrace = self.globaltrace_lt
+ self.localtrace = self.localtrace_trace
+ elif count:
+ self.globaltrace = self.globaltrace_lt
+ self.localtrace = self.localtrace_count
+ else:
+ # Ahem -- do nothing? Okay.
+ self.donothing = 1
+
+ def run(self, cmd):
+ import __main__
+ dict = __main__.__dict__
+ self.runctx(cmd, dict, dict)
+
+ def runctx(self, cmd, globals=None, locals=None):
+ if globals is None: globals = {}
+ if locals is None: locals = {}
+ if not self.donothing:
+ threading.settrace(self.globaltrace)
+ sys.settrace(self.globaltrace)
+ try:
+ exec(cmd, globals, locals)
+ finally:
+ if not self.donothing:
+ sys.settrace(None)
+ threading.settrace(None)
+
+ def runfunc(*args, **kw):
+ if len(args) >= 2:
+ self, func, *args = args
+ elif not args:
+ raise TypeError("descriptor 'runfunc' of 'Trace' object "
+ "needs an argument")
+ elif 'func' in kw:
+ func = kw.pop('func')
+ self, *args = args
+ import warnings
+ warnings.warn("Passing 'func' as keyword argument is deprecated",
+ DeprecationWarning, stacklevel=2)
+ else:
+ raise TypeError('runfunc expected at least 1 positional argument, '
+ 'got %d' % (len(args)-1))
+
+ result = None
+ if not self.donothing:
+ sys.settrace(self.globaltrace)
+ try:
+ result = func(*args, **kw)
+ finally:
+ if not self.donothing:
+ sys.settrace(None)
+ return result
+ runfunc.__text_signature__ = '($self, func, /, *args, **kw)'
+
+ def file_module_function_of(self, frame):
+ code = frame.f_code
+ filename = code.co_filename
+ if filename:
+ modulename = _modname(filename)
+ else:
+ modulename = None
+
+ funcname = code.co_name
+ clsname = None
+ if code in self._caller_cache:
+ if self._caller_cache[code] is not None:
+ clsname = self._caller_cache[code]
+ else:
+ self._caller_cache[code] = None
+ ## use of gc.get_referrers() was suggested by Michael Hudson
+ # all functions which refer to this code object
+ funcs = [f for f in gc.get_referrers(code)
+ if inspect.isfunction(f)]
+ # require len(func) == 1 to avoid ambiguity caused by calls to
+ # new.function(): "In the face of ambiguity, refuse the
+ # temptation to guess."
+ if len(funcs) == 1:
+ dicts = [d for d in gc.get_referrers(funcs[0])
+ if isinstance(d, dict)]
+ if len(dicts) == 1:
+ classes = [c for c in gc.get_referrers(dicts[0])
+ if hasattr(c, "__bases__")]
+ if len(classes) == 1:
+ # ditto for new.classobj()
+ clsname = classes[0].__name__
+ # cache the result - assumption is that new.* is
+ # not called later to disturb this relationship
+ # _caller_cache could be flushed if functions in
+ # the new module get called.
+ self._caller_cache[code] = clsname
+ if clsname is not None:
+ funcname = "%s.%s" % (clsname, funcname)
+
+ return filename, modulename, funcname
+
+ def globaltrace_trackcallers(self, frame, why, arg):
+ """Handler for call events.
+
+ Adds information about who called who to the self._callers dict.
+ """
+ if why == 'call':
+ # XXX Should do a better job of identifying methods
+ this_func = self.file_module_function_of(frame)
+ parent_func = self.file_module_function_of(frame.f_back)
+ self._callers[(parent_func, this_func)] = 1
+
+ def globaltrace_countfuncs(self, frame, why, arg):
+ """Handler for call events.
+
+ Adds (filename, modulename, funcname) to the self._calledfuncs dict.
+ """
+ if why == 'call':
+ this_func = self.file_module_function_of(frame)
+ self._calledfuncs[this_func] = 1
+
+ def globaltrace_lt(self, frame, why, arg):
+ """Handler for call events.
+
+ If the code block being entered is to be ignored, returns `None',
+ else returns self.localtrace.
+ """
+ if why == 'call':
+ code = frame.f_code
+ filename = frame.f_globals.get('__file__', None)
+ if filename:
+ # XXX _modname() doesn't work right for packages, so
+ # the ignore support won't work right for packages
+ modulename = _modname(filename)
+ if modulename is not None:
+ ignore_it = self.ignore.names(filename, modulename)
+ if not ignore_it:
+ if self.trace:
+ print((" --- modulename: %s, funcname: %s"
+ % (modulename, code.co_name)))
+ return self.localtrace
+ else:
+ return None
+
+ def localtrace_trace_and_count(self, frame, why, arg):
+ if why == "line":
+ # record the file name and line number of every trace
+ filename = frame.f_code.co_filename
+ lineno = frame.f_lineno
+ key = filename, lineno
+ self.counts[key] = self.counts.get(key, 0) + 1
+
+ if self.start_time:
+ print('%.2f' % (_time() - self.start_time), end=' ')
+ bname = os.path.basename(filename)
+ print("%s(%d): %s" % (bname, lineno,
+ linecache.getline(filename, lineno)), end='')
+ return self.localtrace
+
+ def localtrace_trace(self, frame, why, arg):
+ if why == "line":
+ # record the file name and line number of every trace
+ filename = frame.f_code.co_filename
+ lineno = frame.f_lineno
+
+ if self.start_time:
+ print('%.2f' % (_time() - self.start_time), end=' ')
+ bname = os.path.basename(filename)
+ print("%s(%d): %s" % (bname, lineno,
+ linecache.getline(filename, lineno)), end='')
+ return self.localtrace
+
+ def localtrace_count(self, frame, why, arg):
+ if why == "line":
+ filename = frame.f_code.co_filename
+ lineno = frame.f_lineno
+ key = filename, lineno
+ self.counts[key] = self.counts.get(key, 0) + 1
+ return self.localtrace
+
+ def results(self):
+ return CoverageResults(self.counts, infile=self.infile,
+ outfile=self.outfile,
+ calledfuncs=self._calledfuncs,
+ callers=self._callers)
+
+def main():
+ import argparse
+
+ parser = argparse.ArgumentParser()
+ parser.add_argument('--version', action='version', version='trace 2.0')
+
+ grp = parser.add_argument_group('Main options',
+ 'One of these (or --report) must be given')
+
+ grp.add_argument('-c', '--count', action='store_true',
+ help='Count the number of times each line is executed and write '
+ 'the counts to .cover for each module executed, in '
+ 'the module\'s directory. See also --coverdir, --file, '
+ '--no-report below.')
+ grp.add_argument('-t', '--trace', action='store_true',
+ help='Print each line to sys.stdout before it is executed')
+ grp.add_argument('-l', '--listfuncs', action='store_true',
+ help='Keep track of which functions are executed at least once '
+ 'and write the results to sys.stdout after the program exits. '
+ 'Cannot be specified alongside --trace or --count.')
+ grp.add_argument('-T', '--trackcalls', action='store_true',
+ help='Keep track of caller/called pairs and write the results to '
+ 'sys.stdout after the program exits.')
+
+ grp = parser.add_argument_group('Modifiers')
+
+ _grp = grp.add_mutually_exclusive_group()
+ _grp.add_argument('-r', '--report', action='store_true',
+ help='Generate a report from a counts file; does not execute any '
+ 'code. --file must specify the results file to read, which '
+ 'must have been created in a previous run with --count '
+ '--file=FILE')
+ _grp.add_argument('-R', '--no-report', action='store_true',
+ help='Do not generate the coverage report files. '
+ 'Useful if you want to accumulate over several runs.')
+
+ grp.add_argument('-f', '--file',
+ help='File to accumulate counts over several runs')
+ grp.add_argument('-C', '--coverdir',
+ help='Directory where the report files go. The coverage report '
+ 'for . will be written to file '
+ '//.cover')
+ grp.add_argument('-m', '--missing', action='store_true',
+ help='Annotate executable lines that were not executed with '
+ '">>>>>> "')
+ grp.add_argument('-s', '--summary', action='store_true',
+ help='Write a brief summary for each file to sys.stdout. '
+ 'Can only be used with --count or --report')
+ grp.add_argument('-g', '--timing', action='store_true',
+ help='Prefix each line with the time since the program started. '
+ 'Only used while tracing')
+
+ grp = parser.add_argument_group('Filters',
+ 'Can be specified multiple times')
+ grp.add_argument('--ignore-module', action='append', default=[],
+ help='Ignore the given module(s) and its submodules '
+ '(if it is a package). Accepts comma separated list of '
+ 'module names.')
+ grp.add_argument('--ignore-dir', action='append', default=[],
+ help='Ignore files in the given directory '
+ '(multiple directories can be joined by os.pathsep).')
+
+ parser.add_argument('--module', action='store_true', default=False,
+ help='Trace a module. ')
+ parser.add_argument('progname', nargs='?',
+ help='file to run as main program')
+ parser.add_argument('arguments', nargs=argparse.REMAINDER,
+ help='arguments to the program')
+
+ opts = parser.parse_args()
+
+ if opts.ignore_dir:
+ _prefix = sysconfig.get_path("stdlib")
+ _exec_prefix = sysconfig.get_path("platstdlib")
+
+ def parse_ignore_dir(s):
+ s = os.path.expanduser(os.path.expandvars(s))
+ s = s.replace('$prefix', _prefix).replace('$exec_prefix', _exec_prefix)
+ return os.path.normpath(s)
+
+ opts.ignore_module = [mod.strip()
+ for i in opts.ignore_module for mod in i.split(',')]
+ opts.ignore_dir = [parse_ignore_dir(s)
+ for i in opts.ignore_dir for s in i.split(os.pathsep)]
+
+ if opts.report:
+ if not opts.file:
+ parser.error('-r/--report requires -f/--file')
+ results = CoverageResults(infile=opts.file, outfile=opts.file)
+ return results.write_results(opts.missing, opts.summary, opts.coverdir)
+
+ if not any([opts.trace, opts.count, opts.listfuncs, opts.trackcalls]):
+ parser.error('must specify one of --trace, --count, --report, '
+ '--listfuncs, or --trackcalls')
+
+ if opts.listfuncs and (opts.count or opts.trace):
+ parser.error('cannot specify both --listfuncs and (--trace or --count)')
+
+ if opts.summary and not opts.count:
+ parser.error('--summary can only be used with --count or --report')
+
+ if opts.progname is None:
+ parser.error('progname is missing: required with the main options')
+
+ t = Trace(opts.count, opts.trace, countfuncs=opts.listfuncs,
+ countcallers=opts.trackcalls, ignoremods=opts.ignore_module,
+ ignoredirs=opts.ignore_dir, infile=opts.file,
+ outfile=opts.file, timing=opts.timing)
+ try:
+ if opts.module:
+ import runpy
+ module_name = opts.progname
+ mod_name, mod_spec, code = runpy._get_module_details(module_name)
+ sys.argv = [code.co_filename, *opts.arguments]
+ globs = {
+ '__name__': '__main__',
+ '__file__': code.co_filename,
+ '__package__': mod_spec.parent,
+ '__loader__': mod_spec.loader,
+ '__spec__': mod_spec,
+ '__cached__': None,
+ }
+ else:
+ sys.argv = [opts.progname, *opts.arguments]
+ sys.path[0] = os.path.dirname(opts.progname)
+
+ with open(opts.progname, 'rb') as fp:
+ code = compile(fp.read(), opts.progname, 'exec')
+ # try to emulate __main__ namespace as much as possible
+ globs = {
+ '__file__': opts.progname,
+ '__name__': '__main__',
+ '__package__': None,
+ '__cached__': None,
+ }
+ t.runctx(code, globs, globs)
+ except OSError as err:
+ sys.exit("Cannot run file %r because: %s" % (sys.argv[0], err))
+ except SystemExit:
+ pass
+
+ results = t.results()
+
+ if not opts.no_report:
+ results.write_results(opts.missing, opts.summary, opts.coverdir)
+
+if __name__=='__main__':
+ main()
diff --git a/vm/src/version.rs b/vm/src/version.rs
index b98f8c28cd..452f659efc 100644
--- a/vm/src/version.rs
+++ b/vm/src/version.rs
@@ -1,8 +1,7 @@
/* Several function to retrieve version information.
*/
-use chrono::prelude::DateTime;
-use chrono::Local;
+use chrono::{prelude::DateTime, Local};
use std::time::{Duration, UNIX_EPOCH};
// = 3.10.0alpha
@@ -18,7 +17,7 @@ pub const VERSION_HEX: usize =
pub fn get_version() -> String {
format!(
- "{:.80} ({:.80}) \n[{:.80}]",
+ "{:.80} ({:.80}) \n[{:.80}]", // \n is PyPy convention
get_version_number(),
get_build_info(),
get_compiler()