update
This commit is contained in:
451
.CondaPkg/env/Lib/test/support/__init__.py
vendored
451
.CondaPkg/env/Lib/test/support/__init__.py
vendored
@@ -6,7 +6,6 @@ if __name__ != 'test.support':
|
||||
import contextlib
|
||||
import dataclasses
|
||||
import functools
|
||||
import getpass
|
||||
import opcode
|
||||
import os
|
||||
import re
|
||||
@@ -19,8 +18,6 @@ import types
|
||||
import unittest
|
||||
import warnings
|
||||
|
||||
from .testresult import get_test_runner
|
||||
|
||||
|
||||
__all__ = [
|
||||
# globals
|
||||
@@ -34,7 +31,6 @@ __all__ = [
|
||||
"is_resource_enabled", "requires", "requires_freebsd_version",
|
||||
"requires_linux_version", "requires_mac_ver",
|
||||
"check_syntax_error",
|
||||
"run_unittest", "run_doctest",
|
||||
"requires_gzip", "requires_bz2", "requires_lzma",
|
||||
"bigmemtest", "bigaddrspacetest", "cpython_only", "get_attribute",
|
||||
"requires_IEEE_754", "requires_zlib",
|
||||
@@ -46,7 +42,7 @@ __all__ = [
|
||||
"check_disallow_instantiation", "check_sanitizer", "skip_if_sanitizer",
|
||||
"requires_limited_api", "requires_specialization",
|
||||
# sys
|
||||
"is_jython", "is_android", "is_emscripten", "is_wasi",
|
||||
"MS_WINDOWS", "is_jython", "is_android", "is_emscripten", "is_wasi",
|
||||
"check_impl_detail", "unix_shell", "setswitchinterval",
|
||||
# os
|
||||
"get_pagesize",
|
||||
@@ -62,6 +58,7 @@ __all__ = [
|
||||
"LOOPBACK_TIMEOUT", "INTERNET_TIMEOUT", "SHORT_TIMEOUT", "LONG_TIMEOUT",
|
||||
"Py_DEBUG", "EXCEEDS_RECURSION_LIMIT", "C_RECURSION_LIMIT",
|
||||
"skip_on_s390x",
|
||||
"BrokenIter",
|
||||
]
|
||||
|
||||
|
||||
@@ -74,13 +71,7 @@ __all__ = [
|
||||
#
|
||||
# The timeout should be long enough for connect(), recv() and send() methods
|
||||
# of socket.socket.
|
||||
LOOPBACK_TIMEOUT = 5.0
|
||||
if sys.platform == 'win32' and ' 32 bit (ARM)' in sys.version:
|
||||
# bpo-37553: test_socket.SendfileUsingSendTest is taking longer than 2
|
||||
# seconds on Windows ARM32 buildbot
|
||||
LOOPBACK_TIMEOUT = 10
|
||||
elif sys.platform == 'vxworks':
|
||||
LOOPBACK_TIMEOUT = 10
|
||||
LOOPBACK_TIMEOUT = 10.0
|
||||
|
||||
# Timeout in seconds for network requests going to the internet. The timeout is
|
||||
# short enough to prevent a test to wait for too long if the internet request
|
||||
@@ -391,49 +382,67 @@ def requires_mac_ver(*min_version):
|
||||
|
||||
def skip_if_buildbot(reason=None):
|
||||
"""Decorator raising SkipTest if running on a buildbot."""
|
||||
import getpass
|
||||
if not reason:
|
||||
reason = 'not suitable for buildbots'
|
||||
try:
|
||||
isbuildbot = getpass.getuser().lower() == 'buildbot'
|
||||
except (KeyError, EnvironmentError) as err:
|
||||
except (KeyError, OSError) as err:
|
||||
warnings.warn(f'getpass.getuser() failed {err}.', RuntimeWarning)
|
||||
isbuildbot = False
|
||||
return unittest.skipIf(isbuildbot, reason)
|
||||
|
||||
def check_sanitizer(*, address=False, memory=False, ub=False):
|
||||
def check_sanitizer(*, address=False, memory=False, ub=False, thread=False):
|
||||
"""Returns True if Python is compiled with sanitizer support"""
|
||||
if not (address or memory or ub):
|
||||
raise ValueError('At least one of address, memory, or ub must be True')
|
||||
if not (address or memory or ub or thread):
|
||||
raise ValueError('At least one of address, memory, ub or thread must be True')
|
||||
|
||||
|
||||
_cflags = sysconfig.get_config_var('CFLAGS') or ''
|
||||
_config_args = sysconfig.get_config_var('CONFIG_ARGS') or ''
|
||||
cflags = sysconfig.get_config_var('CFLAGS') or ''
|
||||
config_args = sysconfig.get_config_var('CONFIG_ARGS') or ''
|
||||
memory_sanitizer = (
|
||||
'-fsanitize=memory' in _cflags or
|
||||
'--with-memory-sanitizer' in _config_args
|
||||
'-fsanitize=memory' in cflags or
|
||||
'--with-memory-sanitizer' in config_args
|
||||
)
|
||||
address_sanitizer = (
|
||||
'-fsanitize=address' in _cflags or
|
||||
'--with-address-sanitizer' in _config_args
|
||||
'-fsanitize=address' in cflags or
|
||||
'--with-address-sanitizer' in config_args
|
||||
)
|
||||
ub_sanitizer = (
|
||||
'-fsanitize=undefined' in _cflags or
|
||||
'--with-undefined-behavior-sanitizer' in _config_args
|
||||
'-fsanitize=undefined' in cflags or
|
||||
'--with-undefined-behavior-sanitizer' in config_args
|
||||
)
|
||||
thread_sanitizer = (
|
||||
'-fsanitize=thread' in cflags or
|
||||
'--with-thread-sanitizer' in config_args
|
||||
)
|
||||
return (
|
||||
(memory and memory_sanitizer) or
|
||||
(address and address_sanitizer) or
|
||||
(ub and ub_sanitizer)
|
||||
(ub and ub_sanitizer) or
|
||||
(thread and thread_sanitizer)
|
||||
)
|
||||
|
||||
|
||||
def skip_if_sanitizer(reason=None, *, address=False, memory=False, ub=False):
|
||||
def skip_if_sanitizer(reason=None, *, address=False, memory=False, ub=False, thread=False):
|
||||
"""Decorator raising SkipTest if running with a sanitizer active."""
|
||||
if not reason:
|
||||
reason = 'not working with sanitizers active'
|
||||
skip = check_sanitizer(address=address, memory=memory, ub=ub)
|
||||
skip = check_sanitizer(address=address, memory=memory, ub=ub, thread=thread)
|
||||
return unittest.skipIf(skip, reason)
|
||||
|
||||
# gh-89363: True if fork() can hang if Python is built with Address Sanitizer
|
||||
# (ASAN): libasan race condition, dead lock in pthread_create().
|
||||
HAVE_ASAN_FORK_BUG = check_sanitizer(address=True)
|
||||
|
||||
|
||||
def set_sanitizer_env_var(env, option):
|
||||
for name in ('ASAN_OPTIONS', 'MSAN_OPTIONS', 'UBSAN_OPTIONS', 'TSAN_OPTIONS'):
|
||||
if name in env:
|
||||
env[name] += f':{option}'
|
||||
else:
|
||||
env[name] = option
|
||||
|
||||
|
||||
def system_must_validate_cert(f):
|
||||
"""Skip the test on TLS certificate validation failures."""
|
||||
@@ -515,6 +524,8 @@ def requires_legacy_unicode_capi():
|
||||
return unittest.skipUnless(unicode_legacy_string,
|
||||
'requires legacy Unicode C API')
|
||||
|
||||
MS_WINDOWS = (sys.platform == 'win32')
|
||||
|
||||
# Is not actually used in tests, but is kept for compatibility.
|
||||
is_jython = sys.platform.startswith('java')
|
||||
|
||||
@@ -781,6 +792,24 @@ def python_is_optimized():
|
||||
return final_opt not in ('', '-O0', '-Og')
|
||||
|
||||
|
||||
def check_cflags_pgo():
|
||||
# Check if Python was built with ./configure --enable-optimizations:
|
||||
# with Profile Guided Optimization (PGO).
|
||||
cflags_nodist = sysconfig.get_config_var('PY_CFLAGS_NODIST') or ''
|
||||
pgo_options = [
|
||||
# GCC
|
||||
'-fprofile-use',
|
||||
# clang: -fprofile-instr-use=code.profclangd
|
||||
'-fprofile-instr-use',
|
||||
# ICC
|
||||
"-prof-use",
|
||||
]
|
||||
PGO_PROF_USE_FLAG = sysconfig.get_config_var('PGO_PROF_USE_FLAG')
|
||||
if PGO_PROF_USE_FLAG:
|
||||
pgo_options.append(PGO_PROF_USE_FLAG)
|
||||
return any(option in cflags_nodist for option in pgo_options)
|
||||
|
||||
|
||||
_header = 'nP'
|
||||
_align = '0n'
|
||||
if hasattr(sys, "getobjects"):
|
||||
@@ -788,10 +817,20 @@ if hasattr(sys, "getobjects"):
|
||||
_align = '0P'
|
||||
_vheader = _header + 'n'
|
||||
|
||||
def check_bolt_optimized():
|
||||
# Always return false, if the platform is WASI,
|
||||
# because BOLT optimization does not support WASM binary.
|
||||
if is_wasi:
|
||||
return False
|
||||
config_args = sysconfig.get_config_var('CONFIG_ARGS') or ''
|
||||
return '--enable-bolt' in config_args
|
||||
|
||||
|
||||
def calcobjsize(fmt):
|
||||
import struct
|
||||
return struct.calcsize(_header + fmt + _align)
|
||||
|
||||
|
||||
def calcvobjsize(fmt):
|
||||
import struct
|
||||
return struct.calcsize(_vheader + fmt + _align)
|
||||
@@ -890,27 +929,31 @@ _4G = 4 * _1G
|
||||
|
||||
MAX_Py_ssize_t = sys.maxsize
|
||||
|
||||
def set_memlimit(limit):
|
||||
global max_memuse
|
||||
global real_max_memuse
|
||||
def _parse_memlimit(limit: str) -> int:
|
||||
sizes = {
|
||||
'k': 1024,
|
||||
'm': _1M,
|
||||
'g': _1G,
|
||||
't': 1024*_1G,
|
||||
}
|
||||
m = re.match(r'(\d+(\.\d+)?) (K|M|G|T)b?$', limit,
|
||||
m = re.match(r'(\d+(?:\.\d+)?) (K|M|G|T)b?$', limit,
|
||||
re.IGNORECASE | re.VERBOSE)
|
||||
if m is None:
|
||||
raise ValueError('Invalid memory limit %r' % (limit,))
|
||||
memlimit = int(float(m.group(1)) * sizes[m.group(3).lower()])
|
||||
real_max_memuse = memlimit
|
||||
if memlimit > MAX_Py_ssize_t:
|
||||
memlimit = MAX_Py_ssize_t
|
||||
raise ValueError(f'Invalid memory limit: {limit!r}')
|
||||
return int(float(m.group(1)) * sizes[m.group(2).lower()])
|
||||
|
||||
def set_memlimit(limit: str) -> None:
|
||||
global max_memuse
|
||||
global real_max_memuse
|
||||
memlimit = _parse_memlimit(limit)
|
||||
if memlimit < _2G - 1:
|
||||
raise ValueError('Memory limit %r too low to be useful' % (limit,))
|
||||
raise ValueError('Memory limit {limit!r} too low to be useful')
|
||||
|
||||
real_max_memuse = memlimit
|
||||
memlimit = min(memlimit, MAX_Py_ssize_t)
|
||||
max_memuse = memlimit
|
||||
|
||||
|
||||
class _MemoryWatchdog:
|
||||
"""An object which periodically watches the process' memory consumption
|
||||
and prints it out.
|
||||
@@ -1100,175 +1143,6 @@ def requires_specialization(test):
|
||||
return unittest.skipUnless(
|
||||
opcode.ENABLE_SPECIALIZATION, "requires specialization")(test)
|
||||
|
||||
def _filter_suite(suite, pred):
|
||||
"""Recursively filter test cases in a suite based on a predicate."""
|
||||
newtests = []
|
||||
for test in suite._tests:
|
||||
if isinstance(test, unittest.TestSuite):
|
||||
_filter_suite(test, pred)
|
||||
newtests.append(test)
|
||||
else:
|
||||
if pred(test):
|
||||
newtests.append(test)
|
||||
suite._tests = newtests
|
||||
|
||||
@dataclasses.dataclass(slots=True)
|
||||
class TestStats:
|
||||
tests_run: int = 0
|
||||
failures: int = 0
|
||||
skipped: int = 0
|
||||
|
||||
@staticmethod
|
||||
def from_unittest(result):
|
||||
return TestStats(result.testsRun,
|
||||
len(result.failures),
|
||||
len(result.skipped))
|
||||
|
||||
@staticmethod
|
||||
def from_doctest(results):
|
||||
return TestStats(results.attempted,
|
||||
results.failed)
|
||||
|
||||
def accumulate(self, stats):
|
||||
self.tests_run += stats.tests_run
|
||||
self.failures += stats.failures
|
||||
self.skipped += stats.skipped
|
||||
|
||||
|
||||
def _run_suite(suite):
|
||||
"""Run tests from a unittest.TestSuite-derived class."""
|
||||
runner = get_test_runner(sys.stdout,
|
||||
verbosity=verbose,
|
||||
capture_output=(junit_xml_list is not None))
|
||||
|
||||
result = runner.run(suite)
|
||||
|
||||
if junit_xml_list is not None:
|
||||
junit_xml_list.append(result.get_xml_element())
|
||||
|
||||
if not result.testsRun and not result.skipped and not result.errors:
|
||||
raise TestDidNotRun
|
||||
if not result.wasSuccessful():
|
||||
stats = TestStats.from_unittest(result)
|
||||
if len(result.errors) == 1 and not result.failures:
|
||||
err = result.errors[0][1]
|
||||
elif len(result.failures) == 1 and not result.errors:
|
||||
err = result.failures[0][1]
|
||||
else:
|
||||
err = "multiple errors occurred"
|
||||
if not verbose: err += "; run in verbose mode for details"
|
||||
errors = [(str(tc), exc_str) for tc, exc_str in result.errors]
|
||||
failures = [(str(tc), exc_str) for tc, exc_str in result.failures]
|
||||
raise TestFailedWithDetails(err, errors, failures, stats=stats)
|
||||
return result
|
||||
|
||||
|
||||
# By default, don't filter tests
|
||||
_match_test_func = None
|
||||
|
||||
_accept_test_patterns = None
|
||||
_ignore_test_patterns = None
|
||||
|
||||
|
||||
def match_test(test):
|
||||
# Function used by support.run_unittest() and regrtest --list-cases
|
||||
if _match_test_func is None:
|
||||
return True
|
||||
else:
|
||||
return _match_test_func(test.id())
|
||||
|
||||
|
||||
def _is_full_match_test(pattern):
|
||||
# If a pattern contains at least one dot, it's considered
|
||||
# as a full test identifier.
|
||||
# Example: 'test.test_os.FileTests.test_access'.
|
||||
#
|
||||
# ignore patterns which contain fnmatch patterns: '*', '?', '[...]'
|
||||
# or '[!...]'. For example, ignore 'test_access*'.
|
||||
return ('.' in pattern) and (not re.search(r'[?*\[\]]', pattern))
|
||||
|
||||
|
||||
def set_match_tests(accept_patterns=None, ignore_patterns=None):
|
||||
global _match_test_func, _accept_test_patterns, _ignore_test_patterns
|
||||
|
||||
if accept_patterns is None:
|
||||
accept_patterns = ()
|
||||
if ignore_patterns is None:
|
||||
ignore_patterns = ()
|
||||
|
||||
accept_func = ignore_func = None
|
||||
|
||||
if accept_patterns != _accept_test_patterns:
|
||||
accept_patterns, accept_func = _compile_match_function(accept_patterns)
|
||||
if ignore_patterns != _ignore_test_patterns:
|
||||
ignore_patterns, ignore_func = _compile_match_function(ignore_patterns)
|
||||
|
||||
# Create a copy since patterns can be mutable and so modified later
|
||||
_accept_test_patterns = tuple(accept_patterns)
|
||||
_ignore_test_patterns = tuple(ignore_patterns)
|
||||
|
||||
if accept_func is not None or ignore_func is not None:
|
||||
def match_function(test_id):
|
||||
accept = True
|
||||
ignore = False
|
||||
if accept_func:
|
||||
accept = accept_func(test_id)
|
||||
if ignore_func:
|
||||
ignore = ignore_func(test_id)
|
||||
return accept and not ignore
|
||||
|
||||
_match_test_func = match_function
|
||||
|
||||
|
||||
def _compile_match_function(patterns):
|
||||
if not patterns:
|
||||
func = None
|
||||
# set_match_tests(None) behaves as set_match_tests(())
|
||||
patterns = ()
|
||||
elif all(map(_is_full_match_test, patterns)):
|
||||
# Simple case: all patterns are full test identifier.
|
||||
# The test.bisect_cmd utility only uses such full test identifiers.
|
||||
func = set(patterns).__contains__
|
||||
else:
|
||||
import fnmatch
|
||||
regex = '|'.join(map(fnmatch.translate, patterns))
|
||||
# The search *is* case sensitive on purpose:
|
||||
# don't use flags=re.IGNORECASE
|
||||
regex_match = re.compile(regex).match
|
||||
|
||||
def match_test_regex(test_id):
|
||||
if regex_match(test_id):
|
||||
# The regex matches the whole identifier, for example
|
||||
# 'test.test_os.FileTests.test_access'.
|
||||
return True
|
||||
else:
|
||||
# Try to match parts of the test identifier.
|
||||
# For example, split 'test.test_os.FileTests.test_access'
|
||||
# into: 'test', 'test_os', 'FileTests' and 'test_access'.
|
||||
return any(map(regex_match, test_id.split(".")))
|
||||
|
||||
func = match_test_regex
|
||||
|
||||
return patterns, func
|
||||
|
||||
|
||||
def run_unittest(*classes):
|
||||
"""Run tests from unittest.TestCase-derived classes."""
|
||||
valid_types = (unittest.TestSuite, unittest.TestCase)
|
||||
loader = unittest.TestLoader()
|
||||
suite = unittest.TestSuite()
|
||||
for cls in classes:
|
||||
if isinstance(cls, str):
|
||||
if cls in sys.modules:
|
||||
suite.addTest(loader.loadTestsFromModule(sys.modules[cls]))
|
||||
else:
|
||||
raise ValueError("str arguments must be keys in sys.modules")
|
||||
elif isinstance(cls, valid_types):
|
||||
suite.addTest(cls)
|
||||
else:
|
||||
suite.addTest(loader.loadTestsFromTestCase(cls))
|
||||
_filter_suite(suite, match_test)
|
||||
return _run_suite(suite)
|
||||
|
||||
#=======================================================================
|
||||
# Check for the presence of docstrings.
|
||||
@@ -1290,38 +1164,6 @@ requires_docstrings = unittest.skipUnless(HAVE_DOCSTRINGS,
|
||||
"test requires docstrings")
|
||||
|
||||
|
||||
#=======================================================================
|
||||
# doctest driver.
|
||||
|
||||
def run_doctest(module, verbosity=None, optionflags=0):
|
||||
"""Run doctest on the given module. Return (#failures, #tests).
|
||||
|
||||
If optional argument verbosity is not specified (or is None), pass
|
||||
support's belief about verbosity on to doctest. Else doctest's
|
||||
usual behavior is used (it searches sys.argv for -v).
|
||||
"""
|
||||
|
||||
import doctest
|
||||
|
||||
if verbosity is None:
|
||||
verbosity = verbose
|
||||
else:
|
||||
verbosity = None
|
||||
|
||||
results = doctest.testmod(module,
|
||||
verbose=verbosity,
|
||||
optionflags=optionflags)
|
||||
if results.failed:
|
||||
stats = TestStats.from_doctest(results)
|
||||
raise TestFailed(f"{results.failed} of {results.attempted} "
|
||||
f"doctests failed",
|
||||
stats=stats)
|
||||
if verbose:
|
||||
print('doctest (%s) ... %d tests with zero failures' %
|
||||
(module.__name__, results.attempted))
|
||||
return results
|
||||
|
||||
|
||||
#=======================================================================
|
||||
# Support for saving and restoring the imported modules.
|
||||
|
||||
@@ -2286,13 +2128,13 @@ def set_recursion_limit(limit):
|
||||
finally:
|
||||
sys.setrecursionlimit(original_limit)
|
||||
|
||||
def infinite_recursion(max_depth=100):
|
||||
"""Set a lower limit for tests that interact with infinite recursions
|
||||
(e.g test_ast.ASTHelpers_Test.test_recursion_direct) since on some
|
||||
debug windows builds, due to not enough functions being inlined the
|
||||
stack size might not handle the default recursion limit (1000). See
|
||||
bpo-11105 for details."""
|
||||
if max_depth < 3:
|
||||
def infinite_recursion(max_depth=None):
|
||||
if max_depth is None:
|
||||
# Pick a number large enough to cause problems
|
||||
# but not take too long for code that can handle
|
||||
# very deep recursion.
|
||||
max_depth = 20_000
|
||||
elif max_depth < 3:
|
||||
raise ValueError("max_depth must be at least 3, got {max_depth}")
|
||||
depth = get_recursion_depth()
|
||||
depth = max(depth - 1, 1) # Ignore infinite_recursion() frame.
|
||||
@@ -2353,7 +2195,9 @@ def _findwheel(pkgname):
|
||||
If set, the wheels are searched for in WHEEL_PKG_DIR (see ensurepip).
|
||||
Otherwise, they are searched for in the test directory.
|
||||
"""
|
||||
wheel_dir = sysconfig.get_config_var('WHEEL_PKG_DIR') or TEST_HOME_DIR
|
||||
wheel_dir = sysconfig.get_config_var('WHEEL_PKG_DIR') or os.path.join(
|
||||
TEST_HOME_DIR, 'wheeldata',
|
||||
)
|
||||
filenames = os.listdir(wheel_dir)
|
||||
filenames = sorted(filenames, reverse=True) # approximate "newest" first
|
||||
for filename in filenames:
|
||||
@@ -2534,8 +2378,121 @@ def adjust_int_max_str_digits(max_digits):
|
||||
EXCEEDS_RECURSION_LIMIT = 5000
|
||||
|
||||
# The default C recursion limit (from Include/cpython/pystate.h).
|
||||
C_RECURSION_LIMIT = 1500
|
||||
if Py_DEBUG:
|
||||
if is_wasi:
|
||||
C_RECURSION_LIMIT = 150
|
||||
else:
|
||||
C_RECURSION_LIMIT = 500
|
||||
else:
|
||||
if is_wasi:
|
||||
C_RECURSION_LIMIT = 500
|
||||
elif hasattr(os, 'uname') and os.uname().machine == 's390x':
|
||||
C_RECURSION_LIMIT = 800
|
||||
elif sys.platform.startswith('win'):
|
||||
C_RECURSION_LIMIT = 3000
|
||||
elif check_sanitizer(address=True):
|
||||
C_RECURSION_LIMIT = 4000
|
||||
else:
|
||||
C_RECURSION_LIMIT = 10000
|
||||
|
||||
#Windows doesn't have os.uname() but it doesn't support s390x.
|
||||
skip_on_s390x = unittest.skipIf(hasattr(os, 'uname') and os.uname().machine == 's390x',
|
||||
'skipped on s390x')
|
||||
|
||||
_BASE_COPY_SRC_DIR_IGNORED_NAMES = frozenset({
|
||||
# SRC_DIR/.git
|
||||
'.git',
|
||||
# ignore all __pycache__/ sub-directories
|
||||
'__pycache__',
|
||||
})
|
||||
|
||||
# Ignore function for shutil.copytree() to copy the Python source code.
|
||||
def copy_python_src_ignore(path, names):
|
||||
ignored = _BASE_COPY_SRC_DIR_IGNORED_NAMES
|
||||
if os.path.basename(path) == 'Doc':
|
||||
ignored |= {
|
||||
# SRC_DIR/Doc/build/
|
||||
'build',
|
||||
# SRC_DIR/Doc/venv/
|
||||
'venv',
|
||||
}
|
||||
|
||||
# check if we are at the root of the source code
|
||||
elif 'Modules' in names:
|
||||
ignored |= {
|
||||
# SRC_DIR/build/
|
||||
'build',
|
||||
}
|
||||
return ignored
|
||||
|
||||
|
||||
def iter_builtin_types():
|
||||
for obj in __builtins__.values():
|
||||
if not isinstance(obj, type):
|
||||
continue
|
||||
cls = obj
|
||||
if cls.__module__ != 'builtins':
|
||||
continue
|
||||
yield cls
|
||||
|
||||
|
||||
def iter_slot_wrappers(cls):
|
||||
assert cls.__module__ == 'builtins', cls
|
||||
|
||||
def is_slot_wrapper(name, value):
|
||||
if not isinstance(value, types.WrapperDescriptorType):
|
||||
assert not repr(value).startswith('<slot wrapper '), (cls, name, value)
|
||||
return False
|
||||
assert repr(value).startswith('<slot wrapper '), (cls, name, value)
|
||||
assert callable(value), (cls, name, value)
|
||||
assert name.startswith('__') and name.endswith('__'), (cls, name, value)
|
||||
return True
|
||||
|
||||
ns = vars(cls)
|
||||
unused = set(ns)
|
||||
for name in dir(cls):
|
||||
if name in ns:
|
||||
unused.remove(name)
|
||||
|
||||
try:
|
||||
value = getattr(cls, name)
|
||||
except AttributeError:
|
||||
# It's as though it weren't in __dir__.
|
||||
assert name in ('__annotate__', '__annotations__', '__abstractmethods__'), (cls, name)
|
||||
if name in ns and is_slot_wrapper(name, ns[name]):
|
||||
unused.add(name)
|
||||
continue
|
||||
|
||||
if not name.startswith('__') or not name.endswith('__'):
|
||||
assert not is_slot_wrapper(name, value), (cls, name, value)
|
||||
if not is_slot_wrapper(name, value):
|
||||
if name in ns:
|
||||
assert not is_slot_wrapper(name, ns[name]), (cls, name, value, ns[name])
|
||||
else:
|
||||
if name in ns:
|
||||
assert ns[name] is value, (cls, name, value, ns[name])
|
||||
yield name, True
|
||||
else:
|
||||
yield name, False
|
||||
|
||||
for name in unused:
|
||||
value = ns[name]
|
||||
if is_slot_wrapper(cls, name, value):
|
||||
yield name, True
|
||||
|
||||
|
||||
class BrokenIter:
|
||||
def __init__(self, init_raises=False, next_raises=False, iter_raises=False):
|
||||
if init_raises:
|
||||
1/0
|
||||
self.next_raises = next_raises
|
||||
self.iter_raises = iter_raises
|
||||
|
||||
def __next__(self):
|
||||
if self.next_raises:
|
||||
1/0
|
||||
|
||||
def __iter__(self):
|
||||
if self.iter_raises:
|
||||
1/0
|
||||
return self
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
.CondaPkg/env/Lib/test/support/__pycache__/pty_helper.cpython-312.pyc
vendored
Normal file
BIN
.CondaPkg/env/Lib/test/support/__pycache__/pty_helper.cpython-312.pyc
vendored
Normal file
Binary file not shown.
Binary file not shown.
BIN
.CondaPkg/env/Lib/test/support/__pycache__/smtpd.cpython-312.pyc
vendored
Normal file
BIN
.CondaPkg/env/Lib/test/support/__pycache__/smtpd.cpython-312.pyc
vendored
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
37
.CondaPkg/env/Lib/test/support/import_helper.py
vendored
37
.CondaPkg/env/Lib/test/support/import_helper.py
vendored
@@ -8,7 +8,7 @@ import sys
|
||||
import unittest
|
||||
import warnings
|
||||
|
||||
from .os_helper import unlink
|
||||
from .os_helper import unlink, temp_dir
|
||||
|
||||
|
||||
@contextlib.contextmanager
|
||||
@@ -268,9 +268,44 @@ def modules_cleanup(oldmodules):
|
||||
sys.modules.update(oldmodules)
|
||||
|
||||
|
||||
@contextlib.contextmanager
|
||||
def isolated_modules():
|
||||
"""
|
||||
Save modules on entry and cleanup on exit.
|
||||
"""
|
||||
(saved,) = modules_setup()
|
||||
try:
|
||||
yield
|
||||
finally:
|
||||
modules_cleanup(saved)
|
||||
|
||||
|
||||
def mock_register_at_fork(func):
|
||||
# bpo-30599: Mock os.register_at_fork() when importing the random module,
|
||||
# since this function doesn't allow to unregister callbacks and would leak
|
||||
# memory.
|
||||
from unittest import mock
|
||||
return mock.patch('os.register_at_fork', create=True)(func)
|
||||
|
||||
|
||||
@contextlib.contextmanager
|
||||
def ready_to_import(name=None, source=""):
|
||||
from test.support import script_helper
|
||||
|
||||
# 1. Sets up a temporary directory and removes it afterwards
|
||||
# 2. Creates the module file
|
||||
# 3. Temporarily clears the module from sys.modules (if any)
|
||||
# 4. Reverts or removes the module when cleaning up
|
||||
name = name or "spam"
|
||||
with temp_dir() as tempdir:
|
||||
path = script_helper.make_script(tempdir, name, source)
|
||||
old_module = sys.modules.pop(name, None)
|
||||
try:
|
||||
sys.path.insert(0, tempdir)
|
||||
yield name, path
|
||||
sys.path.remove(tempdir)
|
||||
finally:
|
||||
if old_module is not None:
|
||||
sys.modules[name] = old_module
|
||||
else:
|
||||
sys.modules.pop(name, None)
|
||||
|
||||
38
.CondaPkg/env/Lib/test/support/os_helper.py
vendored
38
.CondaPkg/env/Lib/test/support/os_helper.py
vendored
@@ -10,6 +10,8 @@ import time
|
||||
import unittest
|
||||
import warnings
|
||||
|
||||
from test import support
|
||||
|
||||
|
||||
# Filename used for testing
|
||||
TESTFN_ASCII = '@test'
|
||||
@@ -245,15 +247,15 @@ def can_chmod():
|
||||
global _can_chmod
|
||||
if _can_chmod is not None:
|
||||
return _can_chmod
|
||||
if not hasattr(os, "chown"):
|
||||
if not hasattr(os, "chmod"):
|
||||
_can_chmod = False
|
||||
return _can_chmod
|
||||
try:
|
||||
with open(TESTFN, "wb") as f:
|
||||
try:
|
||||
os.chmod(TESTFN, 0o777)
|
||||
os.chmod(TESTFN, 0o555)
|
||||
mode1 = os.stat(TESTFN).st_mode
|
||||
os.chmod(TESTFN, 0o666)
|
||||
os.chmod(TESTFN, 0o777)
|
||||
mode2 = os.stat(TESTFN).st_mode
|
||||
except OSError as e:
|
||||
can = False
|
||||
@@ -300,6 +302,10 @@ def can_dac_override():
|
||||
else:
|
||||
_can_dac_override = True
|
||||
finally:
|
||||
try:
|
||||
os.chmod(TESTFN, 0o700)
|
||||
except OSError:
|
||||
pass
|
||||
unlink(TESTFN)
|
||||
|
||||
return _can_dac_override
|
||||
@@ -590,10 +596,17 @@ def fd_count():
|
||||
"""Count the number of open file descriptors.
|
||||
"""
|
||||
if sys.platform.startswith(('linux', 'freebsd', 'emscripten')):
|
||||
fd_path = "/proc/self/fd"
|
||||
elif sys.platform == "darwin":
|
||||
fd_path = "/dev/fd"
|
||||
else:
|
||||
fd_path = None
|
||||
|
||||
if fd_path is not None:
|
||||
try:
|
||||
names = os.listdir("/proc/self/fd")
|
||||
names = os.listdir(fd_path)
|
||||
# Subtract one because listdir() internally opens a file
|
||||
# descriptor to list the content of the /proc/self/fd/ directory.
|
||||
# descriptor to list the content of the directory.
|
||||
return len(names) - 1
|
||||
except FileNotFoundError:
|
||||
pass
|
||||
@@ -720,13 +733,16 @@ class EnvironmentVarGuard(collections.abc.MutableMapping):
|
||||
|
||||
|
||||
try:
|
||||
import ctypes
|
||||
kernel32 = ctypes.WinDLL('kernel32', use_last_error=True)
|
||||
if support.MS_WINDOWS:
|
||||
import ctypes
|
||||
kernel32 = ctypes.WinDLL('kernel32', use_last_error=True)
|
||||
|
||||
ERROR_FILE_NOT_FOUND = 2
|
||||
DDD_REMOVE_DEFINITION = 2
|
||||
DDD_EXACT_MATCH_ON_REMOVE = 4
|
||||
DDD_NO_BROADCAST_SYSTEM = 8
|
||||
ERROR_FILE_NOT_FOUND = 2
|
||||
DDD_REMOVE_DEFINITION = 2
|
||||
DDD_EXACT_MATCH_ON_REMOVE = 4
|
||||
DDD_NO_BROADCAST_SYSTEM = 8
|
||||
else:
|
||||
raise AttributeError
|
||||
except (ImportError, AttributeError):
|
||||
def subst_drive(path):
|
||||
raise unittest.SkipTest('ctypes or kernel32 is not available')
|
||||
|
||||
80
.CondaPkg/env/Lib/test/support/pty_helper.py
vendored
Normal file
80
.CondaPkg/env/Lib/test/support/pty_helper.py
vendored
Normal file
@@ -0,0 +1,80 @@
|
||||
"""
|
||||
Helper to run a script in a pseudo-terminal.
|
||||
"""
|
||||
import os
|
||||
import selectors
|
||||
import subprocess
|
||||
import sys
|
||||
from contextlib import ExitStack
|
||||
from errno import EIO
|
||||
|
||||
from test.support.import_helper import import_module
|
||||
|
||||
def run_pty(script, input=b"dummy input\r", env=None):
|
||||
pty = import_module('pty')
|
||||
output = bytearray()
|
||||
[master, slave] = pty.openpty()
|
||||
args = (sys.executable, '-c', script)
|
||||
proc = subprocess.Popen(args, stdin=slave, stdout=slave, stderr=slave, env=env)
|
||||
os.close(slave)
|
||||
with ExitStack() as cleanup:
|
||||
cleanup.enter_context(proc)
|
||||
def terminate(proc):
|
||||
try:
|
||||
proc.terminate()
|
||||
except ProcessLookupError:
|
||||
# Workaround for Open/Net BSD bug (Issue 16762)
|
||||
pass
|
||||
cleanup.callback(terminate, proc)
|
||||
cleanup.callback(os.close, master)
|
||||
# Avoid using DefaultSelector and PollSelector. Kqueue() does not
|
||||
# work with pseudo-terminals on OS X < 10.9 (Issue 20365) and Open
|
||||
# BSD (Issue 20667). Poll() does not work with OS X 10.6 or 10.4
|
||||
# either (Issue 20472). Hopefully the file descriptor is low enough
|
||||
# to use with select().
|
||||
sel = cleanup.enter_context(selectors.SelectSelector())
|
||||
sel.register(master, selectors.EVENT_READ | selectors.EVENT_WRITE)
|
||||
os.set_blocking(master, False)
|
||||
while True:
|
||||
for [_, events] in sel.select():
|
||||
if events & selectors.EVENT_READ:
|
||||
try:
|
||||
chunk = os.read(master, 0x10000)
|
||||
except OSError as err:
|
||||
# Linux raises EIO when slave is closed (Issue 5380)
|
||||
if err.errno != EIO:
|
||||
raise
|
||||
chunk = b""
|
||||
if not chunk:
|
||||
return output
|
||||
output.extend(chunk)
|
||||
if events & selectors.EVENT_WRITE:
|
||||
try:
|
||||
input = input[os.write(master, input):]
|
||||
except OSError as err:
|
||||
# Apparently EIO means the slave was closed
|
||||
if err.errno != EIO:
|
||||
raise
|
||||
input = b"" # Stop writing
|
||||
if not input:
|
||||
sel.modify(master, selectors.EVENT_READ)
|
||||
|
||||
|
||||
######################################################################
|
||||
## Fake stdin (for testing interactive debugging)
|
||||
######################################################################
|
||||
|
||||
class FakeInput:
|
||||
"""
|
||||
A fake input stream for pdb's interactive debugger. Whenever a
|
||||
line is read, print it (to simulate the user typing it), and then
|
||||
return it. The set of lines to return is specified in the
|
||||
constructor; they should not have trailing newlines.
|
||||
"""
|
||||
def __init__(self, lines):
|
||||
self.lines = lines
|
||||
|
||||
def readline(self):
|
||||
line = self.lines.pop(0)
|
||||
print(line)
|
||||
return line + '\n'
|
||||
14
.CondaPkg/env/Lib/test/support/script_helper.py
vendored
14
.CondaPkg/env/Lib/test/support/script_helper.py
vendored
@@ -64,8 +64,8 @@ class _PythonRunResult(collections.namedtuple("_PythonRunResult",
|
||||
"""Helper for reporting Python subprocess run results"""
|
||||
def fail(self, cmd_line):
|
||||
"""Provide helpful details about failed subcommand runs"""
|
||||
# Limit to 80 lines to ASCII characters
|
||||
maxlen = 80 * 100
|
||||
# Limit to 300 lines of ASCII characters
|
||||
maxlen = 300 * 100
|
||||
out, err = self.out, self.err
|
||||
if len(out) > maxlen:
|
||||
out = b'(... truncated stdout ...)' + out[-maxlen:]
|
||||
@@ -218,9 +218,13 @@ def make_script(script_dir, script_basename, source, omit_suffix=False):
|
||||
if not omit_suffix:
|
||||
script_filename += os.extsep + 'py'
|
||||
script_name = os.path.join(script_dir, script_filename)
|
||||
# The script should be encoded to UTF-8, the default string encoding
|
||||
with open(script_name, 'w', encoding='utf-8') as script_file:
|
||||
script_file.write(source)
|
||||
if isinstance(source, str):
|
||||
# The script should be encoded to UTF-8, the default string encoding
|
||||
with open(script_name, 'w', encoding='utf-8') as script_file:
|
||||
script_file.write(source)
|
||||
else:
|
||||
with open(script_name, 'wb') as script_file:
|
||||
script_file.write(source)
|
||||
importlib.invalidate_caches()
|
||||
return script_name
|
||||
|
||||
|
||||
873
.CondaPkg/env/Lib/test/support/smtpd.py
vendored
Normal file
873
.CondaPkg/env/Lib/test/support/smtpd.py
vendored
Normal file
@@ -0,0 +1,873 @@
|
||||
#! /usr/bin/env python3
|
||||
"""An RFC 5321 smtp proxy with optional RFC 1870 and RFC 6531 extensions.
|
||||
|
||||
Usage: %(program)s [options] [localhost:localport [remotehost:remoteport]]
|
||||
|
||||
Options:
|
||||
|
||||
--nosetuid
|
||||
-n
|
||||
This program generally tries to setuid `nobody', unless this flag is
|
||||
set. The setuid call will fail if this program is not run as root (in
|
||||
which case, use this flag).
|
||||
|
||||
--version
|
||||
-V
|
||||
Print the version number and exit.
|
||||
|
||||
--class classname
|
||||
-c classname
|
||||
Use `classname' as the concrete SMTP proxy class. Uses `PureProxy' by
|
||||
default.
|
||||
|
||||
--size limit
|
||||
-s limit
|
||||
Restrict the total size of the incoming message to "limit" number of
|
||||
bytes via the RFC 1870 SIZE extension. Defaults to 33554432 bytes.
|
||||
|
||||
--smtputf8
|
||||
-u
|
||||
Enable the SMTPUTF8 extension and behave as an RFC 6531 smtp proxy.
|
||||
|
||||
--debug
|
||||
-d
|
||||
Turn on debugging prints.
|
||||
|
||||
--help
|
||||
-h
|
||||
Print this message and exit.
|
||||
|
||||
Version: %(__version__)s
|
||||
|
||||
If localhost is not given then `localhost' is used, and if localport is not
|
||||
given then 8025 is used. If remotehost is not given then `localhost' is used,
|
||||
and if remoteport is not given, then 25 is used.
|
||||
"""
|
||||
|
||||
# Overview:
|
||||
#
|
||||
# This file implements the minimal SMTP protocol as defined in RFC 5321. It
|
||||
# has a hierarchy of classes which implement the backend functionality for the
|
||||
# smtpd. A number of classes are provided:
|
||||
#
|
||||
# SMTPServer - the base class for the backend. Raises NotImplementedError
|
||||
# if you try to use it.
|
||||
#
|
||||
# DebuggingServer - simply prints each message it receives on stdout.
|
||||
#
|
||||
# PureProxy - Proxies all messages to a real smtpd which does final
|
||||
# delivery. One known problem with this class is that it doesn't handle
|
||||
# SMTP errors from the backend server at all. This should be fixed
|
||||
# (contributions are welcome!).
|
||||
#
|
||||
#
|
||||
# Author: Barry Warsaw <barry@python.org>
|
||||
#
|
||||
# TODO:
|
||||
#
|
||||
# - support mailbox delivery
|
||||
# - alias files
|
||||
# - Handle more ESMTP extensions
|
||||
# - handle error codes from the backend smtpd
|
||||
|
||||
import sys
|
||||
import os
|
||||
import errno
|
||||
import getopt
|
||||
import time
|
||||
import socket
|
||||
import collections
|
||||
from test.support import asyncore, asynchat
|
||||
from warnings import warn
|
||||
from email._header_value_parser import get_addr_spec, get_angle_addr
|
||||
|
||||
__all__ = [
|
||||
"SMTPChannel", "SMTPServer", "DebuggingServer", "PureProxy",
|
||||
]
|
||||
|
||||
program = sys.argv[0]
|
||||
__version__ = 'Python SMTP proxy version 0.3'
|
||||
|
||||
|
||||
class Devnull:
|
||||
def write(self, msg): pass
|
||||
def flush(self): pass
|
||||
|
||||
|
||||
DEBUGSTREAM = Devnull()
|
||||
NEWLINE = '\n'
|
||||
COMMASPACE = ', '
|
||||
DATA_SIZE_DEFAULT = 33554432
|
||||
|
||||
|
||||
def usage(code, msg=''):
|
||||
print(__doc__ % globals(), file=sys.stderr)
|
||||
if msg:
|
||||
print(msg, file=sys.stderr)
|
||||
sys.exit(code)
|
||||
|
||||
|
||||
class SMTPChannel(asynchat.async_chat):
|
||||
COMMAND = 0
|
||||
DATA = 1
|
||||
|
||||
command_size_limit = 512
|
||||
command_size_limits = collections.defaultdict(lambda x=command_size_limit: x)
|
||||
|
||||
@property
|
||||
def max_command_size_limit(self):
|
||||
try:
|
||||
return max(self.command_size_limits.values())
|
||||
except ValueError:
|
||||
return self.command_size_limit
|
||||
|
||||
def __init__(self, server, conn, addr, data_size_limit=DATA_SIZE_DEFAULT,
|
||||
map=None, enable_SMTPUTF8=False, decode_data=False):
|
||||
asynchat.async_chat.__init__(self, conn, map=map)
|
||||
self.smtp_server = server
|
||||
self.conn = conn
|
||||
self.addr = addr
|
||||
self.data_size_limit = data_size_limit
|
||||
self.enable_SMTPUTF8 = enable_SMTPUTF8
|
||||
self._decode_data = decode_data
|
||||
if enable_SMTPUTF8 and decode_data:
|
||||
raise ValueError("decode_data and enable_SMTPUTF8 cannot"
|
||||
" be set to True at the same time")
|
||||
if decode_data:
|
||||
self._emptystring = ''
|
||||
self._linesep = '\r\n'
|
||||
self._dotsep = '.'
|
||||
self._newline = NEWLINE
|
||||
else:
|
||||
self._emptystring = b''
|
||||
self._linesep = b'\r\n'
|
||||
self._dotsep = ord(b'.')
|
||||
self._newline = b'\n'
|
||||
self._set_rset_state()
|
||||
self.seen_greeting = ''
|
||||
self.extended_smtp = False
|
||||
self.command_size_limits.clear()
|
||||
self.fqdn = socket.getfqdn()
|
||||
try:
|
||||
self.peer = conn.getpeername()
|
||||
except OSError as err:
|
||||
# a race condition may occur if the other end is closing
|
||||
# before we can get the peername
|
||||
self.close()
|
||||
if err.errno != errno.ENOTCONN:
|
||||
raise
|
||||
return
|
||||
print('Peer:', repr(self.peer), file=DEBUGSTREAM)
|
||||
self.push('220 %s %s' % (self.fqdn, __version__))
|
||||
|
||||
def _set_post_data_state(self):
|
||||
"""Reset state variables to their post-DATA state."""
|
||||
self.smtp_state = self.COMMAND
|
||||
self.mailfrom = None
|
||||
self.rcpttos = []
|
||||
self.require_SMTPUTF8 = False
|
||||
self.num_bytes = 0
|
||||
self.set_terminator(b'\r\n')
|
||||
|
||||
def _set_rset_state(self):
|
||||
"""Reset all state variables except the greeting."""
|
||||
self._set_post_data_state()
|
||||
self.received_data = ''
|
||||
self.received_lines = []
|
||||
|
||||
|
||||
# properties for backwards-compatibility
|
||||
@property
|
||||
def __server(self):
|
||||
warn("Access to __server attribute on SMTPChannel is deprecated, "
|
||||
"use 'smtp_server' instead", DeprecationWarning, 2)
|
||||
return self.smtp_server
|
||||
@__server.setter
|
||||
def __server(self, value):
|
||||
warn("Setting __server attribute on SMTPChannel is deprecated, "
|
||||
"set 'smtp_server' instead", DeprecationWarning, 2)
|
||||
self.smtp_server = value
|
||||
|
||||
@property
|
||||
def __line(self):
|
||||
warn("Access to __line attribute on SMTPChannel is deprecated, "
|
||||
"use 'received_lines' instead", DeprecationWarning, 2)
|
||||
return self.received_lines
|
||||
@__line.setter
|
||||
def __line(self, value):
|
||||
warn("Setting __line attribute on SMTPChannel is deprecated, "
|
||||
"set 'received_lines' instead", DeprecationWarning, 2)
|
||||
self.received_lines = value
|
||||
|
||||
@property
|
||||
def __state(self):
|
||||
warn("Access to __state attribute on SMTPChannel is deprecated, "
|
||||
"use 'smtp_state' instead", DeprecationWarning, 2)
|
||||
return self.smtp_state
|
||||
@__state.setter
|
||||
def __state(self, value):
|
||||
warn("Setting __state attribute on SMTPChannel is deprecated, "
|
||||
"set 'smtp_state' instead", DeprecationWarning, 2)
|
||||
self.smtp_state = value
|
||||
|
||||
@property
|
||||
def __greeting(self):
|
||||
warn("Access to __greeting attribute on SMTPChannel is deprecated, "
|
||||
"use 'seen_greeting' instead", DeprecationWarning, 2)
|
||||
return self.seen_greeting
|
||||
@__greeting.setter
|
||||
def __greeting(self, value):
|
||||
warn("Setting __greeting attribute on SMTPChannel is deprecated, "
|
||||
"set 'seen_greeting' instead", DeprecationWarning, 2)
|
||||
self.seen_greeting = value
|
||||
|
||||
@property
|
||||
def __mailfrom(self):
|
||||
warn("Access to __mailfrom attribute on SMTPChannel is deprecated, "
|
||||
"use 'mailfrom' instead", DeprecationWarning, 2)
|
||||
return self.mailfrom
|
||||
@__mailfrom.setter
|
||||
def __mailfrom(self, value):
|
||||
warn("Setting __mailfrom attribute on SMTPChannel is deprecated, "
|
||||
"set 'mailfrom' instead", DeprecationWarning, 2)
|
||||
self.mailfrom = value
|
||||
|
||||
@property
|
||||
def __rcpttos(self):
|
||||
warn("Access to __rcpttos attribute on SMTPChannel is deprecated, "
|
||||
"use 'rcpttos' instead", DeprecationWarning, 2)
|
||||
return self.rcpttos
|
||||
@__rcpttos.setter
|
||||
def __rcpttos(self, value):
|
||||
warn("Setting __rcpttos attribute on SMTPChannel is deprecated, "
|
||||
"set 'rcpttos' instead", DeprecationWarning, 2)
|
||||
self.rcpttos = value
|
||||
|
||||
@property
|
||||
def __data(self):
|
||||
warn("Access to __data attribute on SMTPChannel is deprecated, "
|
||||
"use 'received_data' instead", DeprecationWarning, 2)
|
||||
return self.received_data
|
||||
@__data.setter
|
||||
def __data(self, value):
|
||||
warn("Setting __data attribute on SMTPChannel is deprecated, "
|
||||
"set 'received_data' instead", DeprecationWarning, 2)
|
||||
self.received_data = value
|
||||
|
||||
@property
|
||||
def __fqdn(self):
|
||||
warn("Access to __fqdn attribute on SMTPChannel is deprecated, "
|
||||
"use 'fqdn' instead", DeprecationWarning, 2)
|
||||
return self.fqdn
|
||||
@__fqdn.setter
|
||||
def __fqdn(self, value):
|
||||
warn("Setting __fqdn attribute on SMTPChannel is deprecated, "
|
||||
"set 'fqdn' instead", DeprecationWarning, 2)
|
||||
self.fqdn = value
|
||||
|
||||
@property
|
||||
def __peer(self):
|
||||
warn("Access to __peer attribute on SMTPChannel is deprecated, "
|
||||
"use 'peer' instead", DeprecationWarning, 2)
|
||||
return self.peer
|
||||
@__peer.setter
|
||||
def __peer(self, value):
|
||||
warn("Setting __peer attribute on SMTPChannel is deprecated, "
|
||||
"set 'peer' instead", DeprecationWarning, 2)
|
||||
self.peer = value
|
||||
|
||||
@property
|
||||
def __conn(self):
|
||||
warn("Access to __conn attribute on SMTPChannel is deprecated, "
|
||||
"use 'conn' instead", DeprecationWarning, 2)
|
||||
return self.conn
|
||||
@__conn.setter
|
||||
def __conn(self, value):
|
||||
warn("Setting __conn attribute on SMTPChannel is deprecated, "
|
||||
"set 'conn' instead", DeprecationWarning, 2)
|
||||
self.conn = value
|
||||
|
||||
@property
|
||||
def __addr(self):
|
||||
warn("Access to __addr attribute on SMTPChannel is deprecated, "
|
||||
"use 'addr' instead", DeprecationWarning, 2)
|
||||
return self.addr
|
||||
@__addr.setter
|
||||
def __addr(self, value):
|
||||
warn("Setting __addr attribute on SMTPChannel is deprecated, "
|
||||
"set 'addr' instead", DeprecationWarning, 2)
|
||||
self.addr = value
|
||||
|
||||
# Overrides base class for convenience.
|
||||
def push(self, msg):
|
||||
asynchat.async_chat.push(self, bytes(
|
||||
msg + '\r\n', 'utf-8' if self.require_SMTPUTF8 else 'ascii'))
|
||||
|
||||
# Implementation of base class abstract method
|
||||
def collect_incoming_data(self, data):
|
||||
limit = None
|
||||
if self.smtp_state == self.COMMAND:
|
||||
limit = self.max_command_size_limit
|
||||
elif self.smtp_state == self.DATA:
|
||||
limit = self.data_size_limit
|
||||
if limit and self.num_bytes > limit:
|
||||
return
|
||||
elif limit:
|
||||
self.num_bytes += len(data)
|
||||
if self._decode_data:
|
||||
self.received_lines.append(str(data, 'utf-8'))
|
||||
else:
|
||||
self.received_lines.append(data)
|
||||
|
||||
# Implementation of base class abstract method
|
||||
def found_terminator(self):
|
||||
line = self._emptystring.join(self.received_lines)
|
||||
print('Data:', repr(line), file=DEBUGSTREAM)
|
||||
self.received_lines = []
|
||||
if self.smtp_state == self.COMMAND:
|
||||
sz, self.num_bytes = self.num_bytes, 0
|
||||
if not line:
|
||||
self.push('500 Error: bad syntax')
|
||||
return
|
||||
if not self._decode_data:
|
||||
line = str(line, 'utf-8')
|
||||
i = line.find(' ')
|
||||
if i < 0:
|
||||
command = line.upper()
|
||||
arg = None
|
||||
else:
|
||||
command = line[:i].upper()
|
||||
arg = line[i+1:].strip()
|
||||
max_sz = (self.command_size_limits[command]
|
||||
if self.extended_smtp else self.command_size_limit)
|
||||
if sz > max_sz:
|
||||
self.push('500 Error: line too long')
|
||||
return
|
||||
method = getattr(self, 'smtp_' + command, None)
|
||||
if not method:
|
||||
self.push('500 Error: command "%s" not recognized' % command)
|
||||
return
|
||||
method(arg)
|
||||
return
|
||||
else:
|
||||
if self.smtp_state != self.DATA:
|
||||
self.push('451 Internal confusion')
|
||||
self.num_bytes = 0
|
||||
return
|
||||
if self.data_size_limit and self.num_bytes > self.data_size_limit:
|
||||
self.push('552 Error: Too much mail data')
|
||||
self.num_bytes = 0
|
||||
return
|
||||
# Remove extraneous carriage returns and de-transparency according
|
||||
# to RFC 5321, Section 4.5.2.
|
||||
data = []
|
||||
for text in line.split(self._linesep):
|
||||
if text and text[0] == self._dotsep:
|
||||
data.append(text[1:])
|
||||
else:
|
||||
data.append(text)
|
||||
self.received_data = self._newline.join(data)
|
||||
args = (self.peer, self.mailfrom, self.rcpttos, self.received_data)
|
||||
kwargs = {}
|
||||
if not self._decode_data:
|
||||
kwargs = {
|
||||
'mail_options': self.mail_options,
|
||||
'rcpt_options': self.rcpt_options,
|
||||
}
|
||||
status = self.smtp_server.process_message(*args, **kwargs)
|
||||
self._set_post_data_state()
|
||||
if not status:
|
||||
self.push('250 OK')
|
||||
else:
|
||||
self.push(status)
|
||||
|
||||
# SMTP and ESMTP commands
|
||||
def smtp_HELO(self, arg):
|
||||
if not arg:
|
||||
self.push('501 Syntax: HELO hostname')
|
||||
return
|
||||
# See issue #21783 for a discussion of this behavior.
|
||||
if self.seen_greeting:
|
||||
self.push('503 Duplicate HELO/EHLO')
|
||||
return
|
||||
self._set_rset_state()
|
||||
self.seen_greeting = arg
|
||||
self.push('250 %s' % self.fqdn)
|
||||
|
||||
def smtp_EHLO(self, arg):
|
||||
if not arg:
|
||||
self.push('501 Syntax: EHLO hostname')
|
||||
return
|
||||
# See issue #21783 for a discussion of this behavior.
|
||||
if self.seen_greeting:
|
||||
self.push('503 Duplicate HELO/EHLO')
|
||||
return
|
||||
self._set_rset_state()
|
||||
self.seen_greeting = arg
|
||||
self.extended_smtp = True
|
||||
self.push('250-%s' % self.fqdn)
|
||||
if self.data_size_limit:
|
||||
self.push('250-SIZE %s' % self.data_size_limit)
|
||||
self.command_size_limits['MAIL'] += 26
|
||||
if not self._decode_data:
|
||||
self.push('250-8BITMIME')
|
||||
if self.enable_SMTPUTF8:
|
||||
self.push('250-SMTPUTF8')
|
||||
self.command_size_limits['MAIL'] += 10
|
||||
self.push('250 HELP')
|
||||
|
||||
def smtp_NOOP(self, arg):
|
||||
if arg:
|
||||
self.push('501 Syntax: NOOP')
|
||||
else:
|
||||
self.push('250 OK')
|
||||
|
||||
def smtp_QUIT(self, arg):
|
||||
# args is ignored
|
||||
self.push('221 Bye')
|
||||
self.close_when_done()
|
||||
|
||||
def _strip_command_keyword(self, keyword, arg):
|
||||
keylen = len(keyword)
|
||||
if arg[:keylen].upper() == keyword:
|
||||
return arg[keylen:].strip()
|
||||
return ''
|
||||
|
||||
def _getaddr(self, arg):
|
||||
if not arg:
|
||||
return '', ''
|
||||
if arg.lstrip().startswith('<'):
|
||||
address, rest = get_angle_addr(arg)
|
||||
else:
|
||||
address, rest = get_addr_spec(arg)
|
||||
if not address:
|
||||
return address, rest
|
||||
return address.addr_spec, rest
|
||||
|
||||
def _getparams(self, params):
|
||||
# Return params as dictionary. Return None if not all parameters
|
||||
# appear to be syntactically valid according to RFC 1869.
|
||||
result = {}
|
||||
for param in params:
|
||||
param, eq, value = param.partition('=')
|
||||
if not param.isalnum() or eq and not value:
|
||||
return None
|
||||
result[param] = value if eq else True
|
||||
return result
|
||||
|
||||
def smtp_HELP(self, arg):
|
||||
if arg:
|
||||
extended = ' [SP <mail-parameters>]'
|
||||
lc_arg = arg.upper()
|
||||
if lc_arg == 'EHLO':
|
||||
self.push('250 Syntax: EHLO hostname')
|
||||
elif lc_arg == 'HELO':
|
||||
self.push('250 Syntax: HELO hostname')
|
||||
elif lc_arg == 'MAIL':
|
||||
msg = '250 Syntax: MAIL FROM: <address>'
|
||||
if self.extended_smtp:
|
||||
msg += extended
|
||||
self.push(msg)
|
||||
elif lc_arg == 'RCPT':
|
||||
msg = '250 Syntax: RCPT TO: <address>'
|
||||
if self.extended_smtp:
|
||||
msg += extended
|
||||
self.push(msg)
|
||||
elif lc_arg == 'DATA':
|
||||
self.push('250 Syntax: DATA')
|
||||
elif lc_arg == 'RSET':
|
||||
self.push('250 Syntax: RSET')
|
||||
elif lc_arg == 'NOOP':
|
||||
self.push('250 Syntax: NOOP')
|
||||
elif lc_arg == 'QUIT':
|
||||
self.push('250 Syntax: QUIT')
|
||||
elif lc_arg == 'VRFY':
|
||||
self.push('250 Syntax: VRFY <address>')
|
||||
else:
|
||||
self.push('501 Supported commands: EHLO HELO MAIL RCPT '
|
||||
'DATA RSET NOOP QUIT VRFY')
|
||||
else:
|
||||
self.push('250 Supported commands: EHLO HELO MAIL RCPT DATA '
|
||||
'RSET NOOP QUIT VRFY')
|
||||
|
||||
def smtp_VRFY(self, arg):
|
||||
if arg:
|
||||
address, params = self._getaddr(arg)
|
||||
if address:
|
||||
self.push('252 Cannot VRFY user, but will accept message '
|
||||
'and attempt delivery')
|
||||
else:
|
||||
self.push('502 Could not VRFY %s' % arg)
|
||||
else:
|
||||
self.push('501 Syntax: VRFY <address>')
|
||||
|
||||
def smtp_MAIL(self, arg):
|
||||
if not self.seen_greeting:
|
||||
self.push('503 Error: send HELO first')
|
||||
return
|
||||
print('===> MAIL', arg, file=DEBUGSTREAM)
|
||||
syntaxerr = '501 Syntax: MAIL FROM: <address>'
|
||||
if self.extended_smtp:
|
||||
syntaxerr += ' [SP <mail-parameters>]'
|
||||
if arg is None:
|
||||
self.push(syntaxerr)
|
||||
return
|
||||
arg = self._strip_command_keyword('FROM:', arg)
|
||||
address, params = self._getaddr(arg)
|
||||
if not address:
|
||||
self.push(syntaxerr)
|
||||
return
|
||||
if not self.extended_smtp and params:
|
||||
self.push(syntaxerr)
|
||||
return
|
||||
if self.mailfrom:
|
||||
self.push('503 Error: nested MAIL command')
|
||||
return
|
||||
self.mail_options = params.upper().split()
|
||||
params = self._getparams(self.mail_options)
|
||||
if params is None:
|
||||
self.push(syntaxerr)
|
||||
return
|
||||
if not self._decode_data:
|
||||
body = params.pop('BODY', '7BIT')
|
||||
if body not in ['7BIT', '8BITMIME']:
|
||||
self.push('501 Error: BODY can only be one of 7BIT, 8BITMIME')
|
||||
return
|
||||
if self.enable_SMTPUTF8:
|
||||
smtputf8 = params.pop('SMTPUTF8', False)
|
||||
if smtputf8 is True:
|
||||
self.require_SMTPUTF8 = True
|
||||
elif smtputf8 is not False:
|
||||
self.push('501 Error: SMTPUTF8 takes no arguments')
|
||||
return
|
||||
size = params.pop('SIZE', None)
|
||||
if size:
|
||||
if not size.isdigit():
|
||||
self.push(syntaxerr)
|
||||
return
|
||||
elif self.data_size_limit and int(size) > self.data_size_limit:
|
||||
self.push('552 Error: message size exceeds fixed maximum message size')
|
||||
return
|
||||
if len(params.keys()) > 0:
|
||||
self.push('555 MAIL FROM parameters not recognized or not implemented')
|
||||
return
|
||||
self.mailfrom = address
|
||||
print('sender:', self.mailfrom, file=DEBUGSTREAM)
|
||||
self.push('250 OK')
|
||||
|
||||
def smtp_RCPT(self, arg):
|
||||
if not self.seen_greeting:
|
||||
self.push('503 Error: send HELO first');
|
||||
return
|
||||
print('===> RCPT', arg, file=DEBUGSTREAM)
|
||||
if not self.mailfrom:
|
||||
self.push('503 Error: need MAIL command')
|
||||
return
|
||||
syntaxerr = '501 Syntax: RCPT TO: <address>'
|
||||
if self.extended_smtp:
|
||||
syntaxerr += ' [SP <mail-parameters>]'
|
||||
if arg is None:
|
||||
self.push(syntaxerr)
|
||||
return
|
||||
arg = self._strip_command_keyword('TO:', arg)
|
||||
address, params = self._getaddr(arg)
|
||||
if not address:
|
||||
self.push(syntaxerr)
|
||||
return
|
||||
if not self.extended_smtp and params:
|
||||
self.push(syntaxerr)
|
||||
return
|
||||
self.rcpt_options = params.upper().split()
|
||||
params = self._getparams(self.rcpt_options)
|
||||
if params is None:
|
||||
self.push(syntaxerr)
|
||||
return
|
||||
# XXX currently there are no options we recognize.
|
||||
if len(params.keys()) > 0:
|
||||
self.push('555 RCPT TO parameters not recognized or not implemented')
|
||||
return
|
||||
self.rcpttos.append(address)
|
||||
print('recips:', self.rcpttos, file=DEBUGSTREAM)
|
||||
self.push('250 OK')
|
||||
|
||||
def smtp_RSET(self, arg):
|
||||
if arg:
|
||||
self.push('501 Syntax: RSET')
|
||||
return
|
||||
self._set_rset_state()
|
||||
self.push('250 OK')
|
||||
|
||||
def smtp_DATA(self, arg):
|
||||
if not self.seen_greeting:
|
||||
self.push('503 Error: send HELO first');
|
||||
return
|
||||
if not self.rcpttos:
|
||||
self.push('503 Error: need RCPT command')
|
||||
return
|
||||
if arg:
|
||||
self.push('501 Syntax: DATA')
|
||||
return
|
||||
self.smtp_state = self.DATA
|
||||
self.set_terminator(b'\r\n.\r\n')
|
||||
self.push('354 End data with <CR><LF>.<CR><LF>')
|
||||
|
||||
# Commands that have not been implemented
|
||||
def smtp_EXPN(self, arg):
|
||||
self.push('502 EXPN not implemented')
|
||||
|
||||
|
||||
class SMTPServer(asyncore.dispatcher):
|
||||
# SMTPChannel class to use for managing client connections
|
||||
channel_class = SMTPChannel
|
||||
|
||||
def __init__(self, localaddr, remoteaddr,
|
||||
data_size_limit=DATA_SIZE_DEFAULT, map=None,
|
||||
enable_SMTPUTF8=False, decode_data=False):
|
||||
self._localaddr = localaddr
|
||||
self._remoteaddr = remoteaddr
|
||||
self.data_size_limit = data_size_limit
|
||||
self.enable_SMTPUTF8 = enable_SMTPUTF8
|
||||
self._decode_data = decode_data
|
||||
if enable_SMTPUTF8 and decode_data:
|
||||
raise ValueError("decode_data and enable_SMTPUTF8 cannot"
|
||||
" be set to True at the same time")
|
||||
asyncore.dispatcher.__init__(self, map=map)
|
||||
try:
|
||||
gai_results = socket.getaddrinfo(*localaddr,
|
||||
type=socket.SOCK_STREAM)
|
||||
self.create_socket(gai_results[0][0], gai_results[0][1])
|
||||
# try to re-use a server port if possible
|
||||
self.set_reuse_addr()
|
||||
self.bind(localaddr)
|
||||
self.listen(5)
|
||||
except:
|
||||
self.close()
|
||||
raise
|
||||
else:
|
||||
print('%s started at %s\n\tLocal addr: %s\n\tRemote addr:%s' % (
|
||||
self.__class__.__name__, time.ctime(time.time()),
|
||||
localaddr, remoteaddr), file=DEBUGSTREAM)
|
||||
|
||||
def handle_accepted(self, conn, addr):
|
||||
print('Incoming connection from %s' % repr(addr), file=DEBUGSTREAM)
|
||||
channel = self.channel_class(self,
|
||||
conn,
|
||||
addr,
|
||||
self.data_size_limit,
|
||||
self._map,
|
||||
self.enable_SMTPUTF8,
|
||||
self._decode_data)
|
||||
|
||||
# API for "doing something useful with the message"
|
||||
def process_message(self, peer, mailfrom, rcpttos, data, **kwargs):
|
||||
"""Override this abstract method to handle messages from the client.
|
||||
|
||||
peer is a tuple containing (ipaddr, port) of the client that made the
|
||||
socket connection to our smtp port.
|
||||
|
||||
mailfrom is the raw address the client claims the message is coming
|
||||
from.
|
||||
|
||||
rcpttos is a list of raw addresses the client wishes to deliver the
|
||||
message to.
|
||||
|
||||
data is a string containing the entire full text of the message,
|
||||
headers (if supplied) and all. It has been `de-transparencied'
|
||||
according to RFC 821, Section 4.5.2. In other words, a line
|
||||
containing a `.' followed by other text has had the leading dot
|
||||
removed.
|
||||
|
||||
kwargs is a dictionary containing additional information. It is
|
||||
empty if decode_data=True was given as init parameter, otherwise
|
||||
it will contain the following keys:
|
||||
'mail_options': list of parameters to the mail command. All
|
||||
elements are uppercase strings. Example:
|
||||
['BODY=8BITMIME', 'SMTPUTF8'].
|
||||
'rcpt_options': same, for the rcpt command.
|
||||
|
||||
This function should return None for a normal `250 Ok' response;
|
||||
otherwise, it should return the desired response string in RFC 821
|
||||
format.
|
||||
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
class DebuggingServer(SMTPServer):
|
||||
|
||||
def _print_message_content(self, peer, data):
|
||||
inheaders = 1
|
||||
lines = data.splitlines()
|
||||
for line in lines:
|
||||
# headers first
|
||||
if inheaders and not line:
|
||||
peerheader = 'X-Peer: ' + peer[0]
|
||||
if not isinstance(data, str):
|
||||
# decoded_data=false; make header match other binary output
|
||||
peerheader = repr(peerheader.encode('utf-8'))
|
||||
print(peerheader)
|
||||
inheaders = 0
|
||||
if not isinstance(data, str):
|
||||
# Avoid spurious 'str on bytes instance' warning.
|
||||
line = repr(line)
|
||||
print(line)
|
||||
|
||||
def process_message(self, peer, mailfrom, rcpttos, data, **kwargs):
|
||||
print('---------- MESSAGE FOLLOWS ----------')
|
||||
if kwargs:
|
||||
if kwargs.get('mail_options'):
|
||||
print('mail options: %s' % kwargs['mail_options'])
|
||||
if kwargs.get('rcpt_options'):
|
||||
print('rcpt options: %s\n' % kwargs['rcpt_options'])
|
||||
self._print_message_content(peer, data)
|
||||
print('------------ END MESSAGE ------------')
|
||||
|
||||
|
||||
class PureProxy(SMTPServer):
|
||||
def __init__(self, *args, **kwargs):
|
||||
if 'enable_SMTPUTF8' in kwargs and kwargs['enable_SMTPUTF8']:
|
||||
raise ValueError("PureProxy does not support SMTPUTF8.")
|
||||
super(PureProxy, self).__init__(*args, **kwargs)
|
||||
|
||||
def process_message(self, peer, mailfrom, rcpttos, data):
|
||||
lines = data.split('\n')
|
||||
# Look for the last header
|
||||
i = 0
|
||||
for line in lines:
|
||||
if not line:
|
||||
break
|
||||
i += 1
|
||||
lines.insert(i, 'X-Peer: %s' % peer[0])
|
||||
data = NEWLINE.join(lines)
|
||||
refused = self._deliver(mailfrom, rcpttos, data)
|
||||
# TBD: what to do with refused addresses?
|
||||
print('we got some refusals:', refused, file=DEBUGSTREAM)
|
||||
|
||||
def _deliver(self, mailfrom, rcpttos, data):
|
||||
import smtplib
|
||||
refused = {}
|
||||
try:
|
||||
s = smtplib.SMTP()
|
||||
s.connect(self._remoteaddr[0], self._remoteaddr[1])
|
||||
try:
|
||||
refused = s.sendmail(mailfrom, rcpttos, data)
|
||||
finally:
|
||||
s.quit()
|
||||
except smtplib.SMTPRecipientsRefused as e:
|
||||
print('got SMTPRecipientsRefused', file=DEBUGSTREAM)
|
||||
refused = e.recipients
|
||||
except (OSError, smtplib.SMTPException) as e:
|
||||
print('got', e.__class__, file=DEBUGSTREAM)
|
||||
# All recipients were refused. If the exception had an associated
|
||||
# error code, use it. Otherwise,fake it with a non-triggering
|
||||
# exception code.
|
||||
errcode = getattr(e, 'smtp_code', -1)
|
||||
errmsg = getattr(e, 'smtp_error', 'ignore')
|
||||
for r in rcpttos:
|
||||
refused[r] = (errcode, errmsg)
|
||||
return refused
|
||||
|
||||
|
||||
class Options:
|
||||
setuid = True
|
||||
classname = 'PureProxy'
|
||||
size_limit = None
|
||||
enable_SMTPUTF8 = False
|
||||
|
||||
|
||||
def parseargs():
|
||||
global DEBUGSTREAM
|
||||
try:
|
||||
opts, args = getopt.getopt(
|
||||
sys.argv[1:], 'nVhc:s:du',
|
||||
['class=', 'nosetuid', 'version', 'help', 'size=', 'debug',
|
||||
'smtputf8'])
|
||||
except getopt.error as e:
|
||||
usage(1, e)
|
||||
|
||||
options = Options()
|
||||
for opt, arg in opts:
|
||||
if opt in ('-h', '--help'):
|
||||
usage(0)
|
||||
elif opt in ('-V', '--version'):
|
||||
print(__version__)
|
||||
sys.exit(0)
|
||||
elif opt in ('-n', '--nosetuid'):
|
||||
options.setuid = False
|
||||
elif opt in ('-c', '--class'):
|
||||
options.classname = arg
|
||||
elif opt in ('-d', '--debug'):
|
||||
DEBUGSTREAM = sys.stderr
|
||||
elif opt in ('-u', '--smtputf8'):
|
||||
options.enable_SMTPUTF8 = True
|
||||
elif opt in ('-s', '--size'):
|
||||
try:
|
||||
int_size = int(arg)
|
||||
options.size_limit = int_size
|
||||
except:
|
||||
print('Invalid size: ' + arg, file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
# parse the rest of the arguments
|
||||
if len(args) < 1:
|
||||
localspec = 'localhost:8025'
|
||||
remotespec = 'localhost:25'
|
||||
elif len(args) < 2:
|
||||
localspec = args[0]
|
||||
remotespec = 'localhost:25'
|
||||
elif len(args) < 3:
|
||||
localspec = args[0]
|
||||
remotespec = args[1]
|
||||
else:
|
||||
usage(1, 'Invalid arguments: %s' % COMMASPACE.join(args))
|
||||
|
||||
# split into host/port pairs
|
||||
i = localspec.find(':')
|
||||
if i < 0:
|
||||
usage(1, 'Bad local spec: %s' % localspec)
|
||||
options.localhost = localspec[:i]
|
||||
try:
|
||||
options.localport = int(localspec[i+1:])
|
||||
except ValueError:
|
||||
usage(1, 'Bad local port: %s' % localspec)
|
||||
i = remotespec.find(':')
|
||||
if i < 0:
|
||||
usage(1, 'Bad remote spec: %s' % remotespec)
|
||||
options.remotehost = remotespec[:i]
|
||||
try:
|
||||
options.remoteport = int(remotespec[i+1:])
|
||||
except ValueError:
|
||||
usage(1, 'Bad remote port: %s' % remotespec)
|
||||
return options
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
options = parseargs()
|
||||
# Become nobody
|
||||
classname = options.classname
|
||||
if "." in classname:
|
||||
lastdot = classname.rfind(".")
|
||||
mod = __import__(classname[:lastdot], globals(), locals(), [""])
|
||||
classname = classname[lastdot+1:]
|
||||
else:
|
||||
import __main__ as mod
|
||||
class_ = getattr(mod, classname)
|
||||
proxy = class_((options.localhost, options.localport),
|
||||
(options.remotehost, options.remoteport),
|
||||
options.size_limit, enable_SMTPUTF8=options.enable_SMTPUTF8)
|
||||
if options.setuid:
|
||||
try:
|
||||
import pwd
|
||||
except ImportError:
|
||||
print('Cannot import module "pwd"; try running with -n option.', file=sys.stderr)
|
||||
sys.exit(1)
|
||||
nobody = pwd.getpwnam('nobody')[2]
|
||||
try:
|
||||
os.setuid(nobody)
|
||||
except PermissionError:
|
||||
print('Cannot setuid "nobody"; try running with -n option.', file=sys.stderr)
|
||||
sys.exit(1)
|
||||
try:
|
||||
asyncore.loop()
|
||||
except KeyboardInterrupt:
|
||||
pass
|
||||
40
.CondaPkg/env/Lib/test/support/testcase.py
vendored
40
.CondaPkg/env/Lib/test/support/testcase.py
vendored
@@ -1,3 +1,6 @@
|
||||
from math import copysign, isnan
|
||||
|
||||
|
||||
class ExceptionIsLikeMixin:
|
||||
def assertExceptionIsLike(self, exc, template):
|
||||
"""
|
||||
@@ -23,3 +26,40 @@ class ExceptionIsLikeMixin:
|
||||
self.assertEqual(len(exc.exceptions), len(template.exceptions))
|
||||
for e, t in zip(exc.exceptions, template.exceptions):
|
||||
self.assertExceptionIsLike(e, t)
|
||||
|
||||
|
||||
class FloatsAreIdenticalMixin:
|
||||
def assertFloatsAreIdentical(self, x, y):
|
||||
"""Fail unless floats x and y are identical, in the sense that:
|
||||
(1) both x and y are nans, or
|
||||
(2) both x and y are infinities, with the same sign, or
|
||||
(3) both x and y are zeros, with the same sign, or
|
||||
(4) x and y are both finite and nonzero, and x == y
|
||||
|
||||
"""
|
||||
msg = 'floats {!r} and {!r} are not identical'
|
||||
|
||||
if isnan(x) or isnan(y):
|
||||
if isnan(x) and isnan(y):
|
||||
return
|
||||
elif x == y:
|
||||
if x != 0.0:
|
||||
return
|
||||
# both zero; check that signs match
|
||||
elif copysign(1.0, x) == copysign(1.0, y):
|
||||
return
|
||||
else:
|
||||
msg += ': zeros have different signs'
|
||||
self.fail(msg.format(x, y))
|
||||
|
||||
|
||||
class ComplexesAreIdenticalMixin(FloatsAreIdenticalMixin):
|
||||
def assertComplexesAreIdentical(self, x, y):
|
||||
"""Fail unless complex numbers x and y have equal values and signs.
|
||||
|
||||
In particular, if x and y both have real (or imaginary) part
|
||||
zero, but the zeros have different signs, this test will fail.
|
||||
|
||||
"""
|
||||
self.assertFloatsAreIdentical(x.real, y.real)
|
||||
self.assertFloatsAreIdentical(x.imag, y.imag)
|
||||
|
||||
191
.CondaPkg/env/Lib/test/support/testresult.py
vendored
191
.CondaPkg/env/Lib/test/support/testresult.py
vendored
@@ -1,191 +0,0 @@
|
||||
'''Test runner and result class for the regression test suite.
|
||||
|
||||
'''
|
||||
|
||||
import functools
|
||||
import io
|
||||
import sys
|
||||
import time
|
||||
import traceback
|
||||
import unittest
|
||||
from test import support
|
||||
|
||||
class RegressionTestResult(unittest.TextTestResult):
|
||||
USE_XML = False
|
||||
|
||||
def __init__(self, stream, descriptions, verbosity):
|
||||
super().__init__(stream=stream, descriptions=descriptions,
|
||||
verbosity=2 if verbosity else 0)
|
||||
self.buffer = True
|
||||
if self.USE_XML:
|
||||
from xml.etree import ElementTree as ET
|
||||
from datetime import datetime, UTC
|
||||
self.__ET = ET
|
||||
self.__suite = ET.Element('testsuite')
|
||||
self.__suite.set('start',
|
||||
datetime.now(UTC)
|
||||
.replace(tzinfo=None)
|
||||
.isoformat(' '))
|
||||
self.__e = None
|
||||
self.__start_time = None
|
||||
|
||||
@classmethod
|
||||
def __getId(cls, test):
|
||||
try:
|
||||
test_id = test.id
|
||||
except AttributeError:
|
||||
return str(test)
|
||||
try:
|
||||
return test_id()
|
||||
except TypeError:
|
||||
return str(test_id)
|
||||
return repr(test)
|
||||
|
||||
def startTest(self, test):
|
||||
super().startTest(test)
|
||||
if self.USE_XML:
|
||||
self.__e = e = self.__ET.SubElement(self.__suite, 'testcase')
|
||||
self.__start_time = time.perf_counter()
|
||||
|
||||
def _add_result(self, test, capture=False, **args):
|
||||
if not self.USE_XML:
|
||||
return
|
||||
e = self.__e
|
||||
self.__e = None
|
||||
if e is None:
|
||||
return
|
||||
ET = self.__ET
|
||||
|
||||
e.set('name', args.pop('name', self.__getId(test)))
|
||||
e.set('status', args.pop('status', 'run'))
|
||||
e.set('result', args.pop('result', 'completed'))
|
||||
if self.__start_time:
|
||||
e.set('time', f'{time.perf_counter() - self.__start_time:0.6f}')
|
||||
|
||||
if capture:
|
||||
if self._stdout_buffer is not None:
|
||||
stdout = self._stdout_buffer.getvalue().rstrip()
|
||||
ET.SubElement(e, 'system-out').text = stdout
|
||||
if self._stderr_buffer is not None:
|
||||
stderr = self._stderr_buffer.getvalue().rstrip()
|
||||
ET.SubElement(e, 'system-err').text = stderr
|
||||
|
||||
for k, v in args.items():
|
||||
if not k or not v:
|
||||
continue
|
||||
e2 = ET.SubElement(e, k)
|
||||
if hasattr(v, 'items'):
|
||||
for k2, v2 in v.items():
|
||||
if k2:
|
||||
e2.set(k2, str(v2))
|
||||
else:
|
||||
e2.text = str(v2)
|
||||
else:
|
||||
e2.text = str(v)
|
||||
|
||||
@classmethod
|
||||
def __makeErrorDict(cls, err_type, err_value, err_tb):
|
||||
if isinstance(err_type, type):
|
||||
if err_type.__module__ == 'builtins':
|
||||
typename = err_type.__name__
|
||||
else:
|
||||
typename = f'{err_type.__module__}.{err_type.__name__}'
|
||||
else:
|
||||
typename = repr(err_type)
|
||||
|
||||
msg = traceback.format_exception(err_type, err_value, None)
|
||||
tb = traceback.format_exception(err_type, err_value, err_tb)
|
||||
|
||||
return {
|
||||
'type': typename,
|
||||
'message': ''.join(msg),
|
||||
'': ''.join(tb),
|
||||
}
|
||||
|
||||
def addError(self, test, err):
|
||||
self._add_result(test, True, error=self.__makeErrorDict(*err))
|
||||
super().addError(test, err)
|
||||
|
||||
def addExpectedFailure(self, test, err):
|
||||
self._add_result(test, True, output=self.__makeErrorDict(*err))
|
||||
super().addExpectedFailure(test, err)
|
||||
|
||||
def addFailure(self, test, err):
|
||||
self._add_result(test, True, failure=self.__makeErrorDict(*err))
|
||||
super().addFailure(test, err)
|
||||
if support.failfast:
|
||||
self.stop()
|
||||
|
||||
def addSkip(self, test, reason):
|
||||
self._add_result(test, skipped=reason)
|
||||
super().addSkip(test, reason)
|
||||
|
||||
def addSuccess(self, test):
|
||||
self._add_result(test)
|
||||
super().addSuccess(test)
|
||||
|
||||
def addUnexpectedSuccess(self, test):
|
||||
self._add_result(test, outcome='UNEXPECTED_SUCCESS')
|
||||
super().addUnexpectedSuccess(test)
|
||||
|
||||
def get_xml_element(self):
|
||||
if not self.USE_XML:
|
||||
raise ValueError("USE_XML is false")
|
||||
e = self.__suite
|
||||
e.set('tests', str(self.testsRun))
|
||||
e.set('errors', str(len(self.errors)))
|
||||
e.set('failures', str(len(self.failures)))
|
||||
return e
|
||||
|
||||
class QuietRegressionTestRunner:
|
||||
def __init__(self, stream, buffer=False):
|
||||
self.result = RegressionTestResult(stream, None, 0)
|
||||
self.result.buffer = buffer
|
||||
|
||||
def run(self, test):
|
||||
test(self.result)
|
||||
return self.result
|
||||
|
||||
def get_test_runner_class(verbosity, buffer=False):
|
||||
if verbosity:
|
||||
return functools.partial(unittest.TextTestRunner,
|
||||
resultclass=RegressionTestResult,
|
||||
buffer=buffer,
|
||||
verbosity=verbosity)
|
||||
return functools.partial(QuietRegressionTestRunner, buffer=buffer)
|
||||
|
||||
def get_test_runner(stream, verbosity, capture_output=False):
|
||||
return get_test_runner_class(verbosity, capture_output)(stream)
|
||||
|
||||
if __name__ == '__main__':
|
||||
import xml.etree.ElementTree as ET
|
||||
RegressionTestResult.USE_XML = True
|
||||
|
||||
class TestTests(unittest.TestCase):
|
||||
def test_pass(self):
|
||||
pass
|
||||
|
||||
def test_pass_slow(self):
|
||||
time.sleep(1.0)
|
||||
|
||||
def test_fail(self):
|
||||
print('stdout', file=sys.stdout)
|
||||
print('stderr', file=sys.stderr)
|
||||
self.fail('failure message')
|
||||
|
||||
def test_error(self):
|
||||
print('stdout', file=sys.stdout)
|
||||
print('stderr', file=sys.stderr)
|
||||
raise RuntimeError('error message')
|
||||
|
||||
suite = unittest.TestSuite()
|
||||
suite.addTest(unittest.TestLoader().loadTestsFromTestCase(TestTests))
|
||||
stream = io.StringIO()
|
||||
runner_cls = get_test_runner_class(sum(a == '-v' for a in sys.argv))
|
||||
runner = runner_cls(sys.stdout)
|
||||
result = runner.run(suite)
|
||||
print('Output:', stream.getvalue())
|
||||
print('XML: ', end='')
|
||||
for s in ET.tostringlist(result.get_xml_element()):
|
||||
print(s.decode(), end='')
|
||||
print()
|
||||
@@ -22,34 +22,37 @@ from test import support
|
||||
|
||||
|
||||
def threading_setup():
|
||||
return _thread._count(), threading._dangling.copy()
|
||||
return _thread._count(), len(threading._dangling)
|
||||
|
||||
|
||||
def threading_cleanup(*original_values):
|
||||
_MAX_COUNT = 100
|
||||
orig_count, orig_ndangling = original_values
|
||||
|
||||
for count in range(_MAX_COUNT):
|
||||
values = _thread._count(), threading._dangling
|
||||
if values == original_values:
|
||||
break
|
||||
timeout = 1.0
|
||||
for _ in support.sleeping_retry(timeout, error=False):
|
||||
# Copy the thread list to get a consistent output. threading._dangling
|
||||
# is a WeakSet, its value changes when it's read.
|
||||
dangling_threads = list(threading._dangling)
|
||||
count = _thread._count()
|
||||
|
||||
if not count:
|
||||
# Display a warning at the first iteration
|
||||
support.environment_altered = True
|
||||
dangling_threads = values[1]
|
||||
support.print_warning(f"threading_cleanup() failed to cleanup "
|
||||
f"{values[0] - original_values[0]} threads "
|
||||
f"(count: {values[0]}, "
|
||||
f"dangling: {len(dangling_threads)})")
|
||||
for thread in dangling_threads:
|
||||
support.print_warning(f"Dangling thread: {thread!r}")
|
||||
if count <= orig_count:
|
||||
return
|
||||
|
||||
# Don't hold references to threads
|
||||
dangling_threads = None
|
||||
values = None
|
||||
# Timeout!
|
||||
support.environment_altered = True
|
||||
support.print_warning(
|
||||
f"threading_cleanup() failed to clean up threads "
|
||||
f"in {timeout:.1f} seconds\n"
|
||||
f" before: thread count={orig_count}, dangling={orig_ndangling}\n"
|
||||
f" after: thread count={count}, dangling={len(dangling_threads)}")
|
||||
for thread in dangling_threads:
|
||||
support.print_warning(f"Dangling thread: {thread!r}")
|
||||
|
||||
time.sleep(0.01)
|
||||
support.gc_collect()
|
||||
# The warning happens when a test spawns threads and some of these threads
|
||||
# are still running after the test completes. To fix this warning, join
|
||||
# threads explicitly to wait until they complete.
|
||||
#
|
||||
# To make the warning more likely, reduce the timeout.
|
||||
|
||||
|
||||
def reap_threads(func):
|
||||
|
||||
Reference in New Issue
Block a user