Compare commits

..

1 Commits

Author SHA1 Message Date
dependabot[bot]
2c97de2528 Bump the crypto group across 1 directory with 6 updates
Bumps the crypto group with 6 updates in the / directory:

| Package | From | To |
| --- | --- | --- |
| [digest](https://github.com/RustCrypto/traits) | `0.10.7` | `0.11.3` |
| [hmac](https://github.com/RustCrypto/MACs) | `0.12.1` | `0.13.0` |
| [md-5](https://github.com/RustCrypto/hashes) | `0.10.6` | `0.11.0` |
| [pbkdf2](https://github.com/RustCrypto/password-hashes) | `0.12.2` | `0.13.0` |
| [sha2](https://github.com/RustCrypto/hashes) | `0.10.9` | `0.11.0` |
| [sha3](https://github.com/RustCrypto/hashes) | `0.10.9` | `0.12.0` |



Updates `digest` from 0.10.7 to 0.11.3
- [Commits](https://github.com/RustCrypto/traits/compare/digest-v0.10.7...digest-v0.11.3)

Updates `hmac` from 0.12.1 to 0.13.0
- [Commits](https://github.com/RustCrypto/MACs/compare/hmac-v0.12.1...hmac-v0.13.0)

Updates `md-5` from 0.10.6 to 0.11.0
- [Commits](https://github.com/RustCrypto/hashes/compare/md-5-v0.10.6...md2-v0.11.0)

Updates `pbkdf2` from 0.12.2 to 0.13.0
- [Commits](https://github.com/RustCrypto/password-hashes/compare/pbkdf2-v0.12.2...pbkdf2-v0.13.0)

Updates `sha2` from 0.10.9 to 0.11.0
- [Commits](https://github.com/RustCrypto/hashes/compare/sha2-v0.10.9...sha2-v0.11.0)

Updates `sha3` from 0.10.9 to 0.12.0
- [Commits](https://github.com/RustCrypto/hashes/compare/sha3-v0.10.9...sha3-v0.12.0)

---
updated-dependencies:
- dependency-name: digest
  dependency-version: 0.11.3
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: crypto
- dependency-name: hmac
  dependency-version: 0.13.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: crypto
- dependency-name: md-5
  dependency-version: 0.11.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: crypto
- dependency-name: pbkdf2
  dependency-version: 0.13.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: crypto
- dependency-name: sha2
  dependency-version: 0.11.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: crypto
- dependency-name: sha3
  dependency-version: 0.12.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: crypto
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-05-28 02:11:44 +00:00
101 changed files with 813 additions and 10341 deletions

View File

@@ -1,60 +0,0 @@
---
name: rustpython-capi-expansion
description: Implement missing RustPython C-API functions in crates/capi using the pyo3-ffi header split mapping (`pyo3-ffi/src/*.rs`, mirroring CPython C API headers). Use this whenever the user asks to add or port C-API functions (for example from setobject.h, dictobject.h, unicodeobject.h) or add capi tests.
---
# RustPython C-API Expansion
Use this workflow for adding missing C-API functions to RustPython.
## Source of truth for target files
- Use this mapping source: `pyo3-ffi/src/*.rs`, which mirrors the CPython header split used by the C API.
- Map requested header APIs to `crates/capi/src/<header_basename>.rs` using that split. Examples:
- `setobject.h` -> `crates/capi/src/setobject.rs`
- `dictobject.h` -> `crates/capi/src/dictobject.rs`
- `unicodeobject.h` -> `crates/capi/src/unicodeobject.rs`
- Do not invent alternate target modules when the header split implies a direct target.
- If the target file is not present yet, create it and wire it in `crates/capi/src/lib.rs`.
## Implementation workflow
1. Identify requested missing APIs from the user request and their originating C API header.
2. Open nearby capi modules in `crates/capi/src/` and follow existing style and patterns.
3. Implement only the requested functions in the mapped target file.
4. Keep behavior aligned with CPython C-API contracts.
5. Prefer using existing `rustpython-vm` functionality as much as possible instead of re-implementing behavior in capi.
6. If a needed `rustpython-vm` helper exists but is private, make it public with a minimal, focused visibility change.
7. Prefer direct contract assumptions over defensive null checks unless required by the established local style.
8. Add basic tests only; do not overfit with very specific edge-case clutter.
9. In tests, use `pyo3` only as a safe wrapper over the API. Avoid raw pointer-heavy direct FFI-style tests.
10. Run tests from `crates/capi`.
## Testing rules
- Run test commands with working directory set to `crates/capi`.
- Prefer targeted tests first (module/function filter), then broader capi tests if needed.
- Keep test names concise (no required `test_` prefix).
## Style rules
- Follow existing RustPython capi coding style in neighboring files.
- Reuse `rustpython-vm` methods and types first; avoid duplicating VM logic in capi wrappers.
- When exposing previously private VM helpers, keep the API surface minimal and avoid unrelated refactors.
- Only expose and implement ABI-stable C-API surface needed for `abi3` / `abi3t`.
- Add comments only when they explain non-obvious behavior.
- Keep edits minimal and focused on requested API expansion.
## Completion checklist
- [ ] All requested functions implemented in mapped target file.
- [ ] New module exported in `crates/capi/src/lib.rs` when applicable.
- [ ] Basic safe-wrapper `pyo3` tests added/updated.
- [ ] Tests executed from `crates/capi` and passing for changed area.
- [ ] Final response includes changed file paths and test command summary.
## Example prompts this skill should handle
- "Implement these missing functions from `dictobject.h`."
- "Add `setobject.h` C-API functions in RustPython and include basic tests."
- "Port the listed `unicodeobject.h` APIs in capi, follow existing style, and run tests from `crates/capi`."

View File

@@ -1,10 +0,0 @@
<!--
Thanks for your contribution!
-->
- [ ] Closes #xxxx <!-- Replace xxxx with the GitHub issue number -->
- [ ] This PR follows our [AI policy](https://github.com/RustPython/.github/blob/main/AI_POLICY.md)
## Summary
<!-- What's the purpose of the change? What does it do, and why? -->

View File

@@ -33,6 +33,7 @@ env:
CARGO_PROFILE_DEV_DEBUG: 0
CARGO_PROFILE_RELEASE_DEBUG: 0
CARGO_TERM_COLOR: always
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true # TODO: Remove on 2026/06/02
CI: true
jobs:
@@ -139,10 +140,6 @@ jobs:
run: cargo build --no-default-features --features ssl-openssl
if: runner.os == 'Linux'
- name: Test vendored OpenSSL build
run: cargo build --no-default-features --features ssl-openssl-vendor
if: runner.os == 'Linux'
# - name: Install tk-dev for tkinter build
# run: sudo apt-get update && sudo apt-get install -y tk-dev
# if: runner.os == 'Linux'
@@ -309,7 +306,11 @@ jobs:
extra_test_args: [] # TODO: Enable '-u all'
env_polluting_tests:
- test_set
skips: []
skips:
- test_rlcompleter
- test_pathlib # panic by surrogate chars
- test_posixpath # OSError: (22, 'The filename, directory name, or volume label syntax is incorrect. (os error 123)')
- test_venv # couple of failing tests
timeout: 50
fail-fast: false
steps:
@@ -540,6 +541,12 @@ jobs:
key: prek-${{ hashFiles('.pre-commit-config.yaml') }}
path: ~/.cache/prek
# TODO: Remove on 2026/06/02 when node24 is the default
- uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
with:
package-manager-cache: false
node-version: "24"
- name: install prek
id: prek
uses: j178/prek-action@bdca6f102f98e2b4c7029491a53dfd366469e33d # v2.0.4

View File

@@ -15,6 +15,7 @@ on:
env:
CARGO_ARGS: --no-default-features --features stdlib,importlib,stdio,encodings,ssl-rustls-aws-lc,jit,host_env
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: 'true' # TODO: Remove on 2026/06/02
jobs:
# codecov collects code coverage data from the rust tests, python snippets and python test suite.
@@ -24,8 +25,6 @@ jobs:
runs-on: ubuntu-latest
# Disable this scheduled job when running on a fork.
if: ${{ github.repository == 'RustPython/RustPython' || github.event_name != 'schedule' }}
env:
INSTA_WORKSPACE_ROOT: ${{ github.workspace }}
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
@@ -57,7 +56,7 @@ jobs:
- name: Upload to Codecov
if: ${{ github.event_name != 'pull_request' }}
uses: codecov/codecov-action@e79a6962e0d4c0c17b229090214935d2e33f8354 # v6.0.1
uses: codecov/codecov-action@57e3a136b779b570ffcdbf80b3bdc90e7fab3de2 # v6.0.0
with:
files: ./codecov.lcov

76
Cargo.lock generated
View File

@@ -1111,6 +1111,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f1dd6dbb5841937940781866fa1281a1ff7bd3bf827091440879f9994983d5c2"
dependencies = [
"block-buffer 0.12.0",
"const-oid 0.10.2",
"crypto-common 0.2.2",
"ctutils",
]
@@ -1582,15 +1583,6 @@ version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dfa686283ad6dd069f105e5ab091b04c62850d3e4cf5d67debad1933f55023df"
[[package]]
name = "hmac"
version = "0.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e"
dependencies = [
"digest 0.10.7",
]
[[package]]
name = "hmac"
version = "0.13.0"
@@ -1969,11 +1961,12 @@ dependencies = [
[[package]]
name = "keccak"
version = "0.1.6"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cb26cec98cce3a3d96cbb7bced3c4b16e3d13f27ec56dbd62cbc8f39cfb9d653"
checksum = "9e24a010dd405bd7ed803e5253182815b41bf2e6a80cc3bfc066658e03a198aa"
dependencies = [
"cpufeatures 0.2.17",
"cfg-if",
"cpufeatures 0.3.0",
]
[[package]]
@@ -2323,12 +2316,12 @@ dependencies = [
[[package]]
name = "md-5"
version = "0.10.6"
version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d89e7ee0cfbedfc4da3340218492196241d89eefb6dab27de5df917a6d2e78cf"
checksum = "69b6441f590336821bb897fb28fc622898ccceb1d6cea3fde5ea86b090c4de98"
dependencies = [
"cfg-if",
"digest 0.10.7",
"digest 0.11.3",
]
[[package]]
@@ -2653,16 +2646,6 @@ version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2ee67f1008b1ba2321834326597b8e186293b049a023cdef258527550b9935b4"
[[package]]
name = "pbkdf2"
version = "0.12.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f8ed6a7761f76e3b9f92dfb0a60a6a6477c61024b775147ff0973a02653abaf2"
dependencies = [
"digest 0.10.7",
"hmac 0.12.1",
]
[[package]]
name = "pbkdf2"
version = "0.13.0"
@@ -2670,7 +2653,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "112d82ceb8c5bf524d9af484d4e4970c9fd5a0cc15ba14ad93dccd28873b0629"
dependencies = [
"digest 0.11.3",
"hmac 0.13.0",
"hmac",
]
[[package]]
@@ -2787,10 +2770,10 @@ dependencies = [
"aes",
"cbc",
"der 0.8.0",
"pbkdf2 0.13.0",
"pbkdf2",
"rand_core 0.10.1",
"scrypt",
"sha2 0.11.0",
"sha2",
"spki 0.8.0",
]
@@ -3479,7 +3462,6 @@ name = "rustpython-capi"
version = "0.5.0"
dependencies = [
"bitflags 2.11.1",
"itertools 0.14.0",
"num-complex",
"pyo3",
"rustpython-stdlib",
@@ -3757,7 +3739,7 @@ dependencies = [
"crossbeam-utils",
"csv-core",
"der 0.8.0",
"digest 0.10.7",
"digest 0.11.3",
"dns-lookup",
"dyn-clone",
"flame",
@@ -3765,7 +3747,7 @@ dependencies = [
"foreign-types-shared",
"gethostname",
"hex",
"hmac 0.12.1",
"hmac",
"icu_normalizer",
"icu_properties",
"indexmap",
@@ -3790,7 +3772,7 @@ dependencies = [
"openssl-sys",
"parking_lot",
"paste",
"pbkdf2 0.12.2",
"pbkdf2",
"pem-rfc7468 1.0.0",
"phf 0.13.1",
"pkcs8",
@@ -3811,7 +3793,7 @@ dependencies = [
"rustpython-ruff_text_size",
"rustpython-vm",
"sha-1",
"sha2 0.10.9",
"sha2",
"sha3",
"socket2",
"system-configuration",
@@ -4004,9 +3986,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d87af57419b594aa23fa95f09f0e06d80d84ba01c26148c43844cad6ff4485f0"
dependencies = [
"cfg-if",
"pbkdf2 0.13.0",
"pbkdf2",
"salsa20",
"sha2 0.11.0",
"sha2",
]
[[package]]
@@ -4123,17 +4105,6 @@ dependencies = [
"digest 0.10.7",
]
[[package]]
name = "sha2"
version = "0.10.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283"
dependencies = [
"cfg-if",
"cpufeatures 0.2.17",
"digest 0.10.7",
]
[[package]]
name = "sha2"
version = "0.11.0"
@@ -4147,12 +4118,13 @@ dependencies = [
[[package]]
name = "sha3"
version = "0.10.9"
version = "0.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "77fd7028345d415a4034cf8777cd4f8ab1851274233b45f84e3d955502d93874"
checksum = "bc9bad02c26382724b2d2692c6f179285e4b54eeecd7968f52a50059c3c11759"
dependencies = [
"digest 0.10.7",
"digest 0.11.3",
"keccak",
"sponge-cursor",
]
[[package]]
@@ -4254,6 +4226,12 @@ dependencies = [
"der 0.8.0",
]
[[package]]
name = "sponge-cursor"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3a0219bd7d979d58245a4f41f695e1ac9f8befdffadd7f61f1bae9e39abc6620"
[[package]]
name = "stable_deref_trait"
version = "1.2.1"

View File

@@ -27,7 +27,7 @@ ssl-rustls = ["ssl", "rustpython-stdlib/ssl-rustls"]
ssl-rustls-aws-lc = ["ssl-rustls", "dep:rustls", "rustls/aws_lc_rs"]
ssl-rustls-aws-lc-fips = ["ssl-rustls-aws-lc", "rustls/fips"]
ssl-openssl = ["ssl", "rustpython-stdlib/ssl-openssl"]
ssl-openssl-vendor = ["ssl-openssl", "rustpython-stdlib/ssl-openssl-vendor"]
ssl-vendor = ["ssl-openssl", "rustpython-stdlib/ssl-vendor"]
tkinter = ["rustpython-stdlib/tkinter"]
[build-dependencies]
@@ -210,14 +210,13 @@ crc32fast = "1.3.2"
criterion = { version = "0.8", features = ["html_reports"] }
crossbeam-utils = "0.8.21"
csv-core = "0.1.11"
digest = "0.10.7"
digest = "0.11.3"
dns-lookup = "3.0"
dyn-clone = "1.0.10"
exitcode = "1.1.2"
flame = "0.2.2"
flamer = "0.5"
flate2 = { version = "1.1.9", default-features = false }
# Bump only when the openssl crate bumps it
foreign-types-shared = "0.1"
gethostname = "1.0.2"
getrandom = { version = "0.3", features = ["std"] }
@@ -225,7 +224,7 @@ glob = "0.3"
half = "2"
hex = "0.4.3"
hexf-parse = "0.2.1"
hmac = "0.12"
hmac = "0.13"
indexmap = { version = "2.14.0", features = ["std"] }
insta = "1.47"
itertools = "0.14.0"
@@ -248,7 +247,7 @@ mac_address = "1.1.3"
malachite-bigint = "0.9.1"
malachite-q = "0.9.1"
malachite-base = "0.9.1"
md-5 = "0.10.1"
md-5 = "0.11.0"
memchr = "2.8.0"
memmap2 = "0.9.10"
mt19937 = "<=3.2" # upgrade it once rand is upgraded
@@ -264,7 +263,7 @@ openssl-probe = "0.2.1"
optional = "0.5"
parking_lot = "0.12.3"
paste = "1.0.15"
pbkdf2 = "0.12"
pbkdf2 = "0.13"
pem-rfc7468 = "1.0"
pkcs8 = "0.11"
proc-macro2 = "1.0.105"
@@ -289,8 +288,8 @@ schannel = "0.1.29"
scopeguard = "1"
serde-wasm-bindgen = "0.6.5"
sha-1 = "0.10.0"
sha2 = "0.10.2"
sha3 = "0.10.1"
sha2 = "0.11.0"
sha3 = "0.12.0"
siphasher = "1"
socket2 = "0.6.3"
static_assertions = "1.1"

View File

@@ -1,28 +1,4 @@
# Contributing to RustPython
Contributions are more than welcome, and in many cases we are happy to guide
contributors through PRs or on [**Discord**](https://discord.gg/vru8NypEhv).
## Finding ways to help
We label issues that would be good for a first time contributor as [`good first issue`](https://github.com/RustPython/RustPython/issues?q=label%3A%22good+first+issue%22+is%3Aissue+is%3Aopen+).
Also checkout the [issue tracker](https://github.com/RustPython/RustPython/issues) for all open issues.
You can enhance CPython compatibility by increasing our unittest coverage, you can see [This pinned issue](https://github.com/RustPython/RustPython/issues/6839) to see which libs and tests need be updated to our current supported python version.
Another approach is to checkout the source code: builtin functions and object
methods are often the simplest and easiest way to contribute.
You can also simply run `python -I scripts/whats_left.py` to assist in finding any unimplemented method.
## Use of AI
We **require all use of AI in contributions to follow our
[AI Policy](https://github.com/RustPython/.github/blob/main/AI_POLICY.md)**.
If your contribution does not follow the policy, it will be closed.
## RustPython Development Guide and Tips
# RustPython Development Guide and Tips
RustPython attracts developers with interest and experience in Rust, Python,
or WebAssembly. Whether you are familiar with Rust, Python, or

View File

@@ -86,27 +86,22 @@ class REPLThread(threading.Thread):
global return_code
try:
if not sys.flags.quiet:
banner = (
f'asyncio REPL {sys.version} on {sys.platform}\n'
f'Use "await" directly instead of "asyncio.run()".\n'
f'Type "help", "copyright", "credits" or "license" '
f'for more information.\n'
)
banner = (
f'asyncio REPL {sys.version} on {sys.platform}\n'
f'Use "await" directly instead of "asyncio.run()".\n'
f'Type "help", "copyright", "credits" or "license" '
f'for more information.\n'
)
console.write(banner)
console.write(banner)
if not sys.flags.isolated and (startup_path := os.getenv("PYTHONSTARTUP")):
if startup_path := os.getenv("PYTHONSTARTUP"):
sys.audit("cpython.run_startup", startup_path)
try:
import tokenize
with tokenize.open(startup_path) as f:
startup_code = compile(f.read(), startup_path, "exec")
import tokenize
with tokenize.open(startup_path) as f:
startup_code = compile(f.read(), startup_path, "exec")
exec(startup_code, console.locals)
except SystemExit:
raise
except BaseException:
console.showtraceback()
ps1 = getattr(sys, "ps1", ">>> ")
if CAN_USE_PYREPL:
@@ -241,5 +236,4 @@ if __name__ == '__main__':
break
console.write('exiting asyncio REPL...\n')
loop.close()
sys.exit(return_code)

View File

@@ -1345,17 +1345,6 @@ class BaseEventLoop(events.AbstractEventLoop):
# have a chance to get called before "ssl_protocol.connection_made()".
transport.pause_reading()
# gh-142352: move buffered StreamReader data to SSLProtocol
if server_side:
from .streams import StreamReaderProtocol
if isinstance(protocol, StreamReaderProtocol):
stream_reader = getattr(protocol, '_stream_reader', None)
if stream_reader is not None:
buffer = stream_reader._buffer
if buffer:
ssl_protocol._incoming.write(buffer)
buffer.clear()
transport.set_protocol(ssl_protocol)
conmade_cb = self.call_soon(ssl_protocol.connection_made, transport)
resume_cb = self.call_soon(transport.resume_reading)

View File

@@ -265,7 +265,7 @@ class BaseSubprocessTransport(transports.SubprocessTransport):
# to avoid hanging forever in self._wait as otherwise _exit_waiters
# would never be woken up, we wake them up here.
for waiter in self._exit_waiters:
if not waiter.done():
if not waiter.cancelled():
waiter.set_result(self._returncode)
if all(p is not None and p.disconnected
for p in self._pipes.values()):
@@ -278,7 +278,7 @@ class BaseSubprocessTransport(transports.SubprocessTransport):
finally:
# wake up futures waiting for wait()
for waiter in self._exit_waiters:
if not waiter.done():
if not waiter.cancelled():
waiter.set_result(self._returncode)
self._exit_waiters = None
self._loop = None

View File

@@ -392,7 +392,7 @@ def _chain_future(source, destination):
def _call_check_cancel(destination):
if destination.cancelled():
if source_loop is None or source_loop is events._get_running_loop():
if source_loop is None or source_loop is dest_loop:
source.cancel()
else:
source_loop.call_soon_threadsafe(source.cancel)
@@ -401,7 +401,7 @@ def _chain_future(source, destination):
if (destination.cancelled() and
dest_loop is not None and dest_loop.is_closed()):
return
if dest_loop is None or dest_loop is events._get_running_loop():
if dest_loop is None or dest_loop is source_loop:
_set_state(destination, source)
else:
if dest_loop.is_closed():

View File

@@ -37,7 +37,7 @@ class Queue(mixins._LoopBoundMixin):
is an integer greater than 0, then "await put()" will block when the
queue reaches maxsize, until an item is removed by get().
Unlike queue.Queue, you can reliably know this Queue's size
Unlike the standard library Queue, you can reliably know this Queue's size
with qsize(), since your single-threaded asyncio application won't be
interrupted between calling qsize() and doing an operation on the Queue.
"""

View File

@@ -10,6 +10,7 @@ import itertools
import msvcrt
import os
import subprocess
import tempfile
import warnings
@@ -23,7 +24,6 @@ BUFSIZE = 8192
PIPE = subprocess.PIPE
STDOUT = subprocess.STDOUT
_mmap_counter = itertools.count()
_MAX_PIPE_ATTEMPTS = 20
# Replacement for os.pipe() using handles instead of fds
@@ -31,6 +31,10 @@ _MAX_PIPE_ATTEMPTS = 20
def pipe(*, duplex=False, overlapped=(True, True), bufsize=BUFSIZE):
"""Like os.pipe() but with overlapped support and using handles not fds."""
address = tempfile.mktemp(
prefix=r'\\.\pipe\python-pipe-{:d}-{:d}-'.format(
os.getpid(), next(_mmap_counter)))
if duplex:
openmode = _winapi.PIPE_ACCESS_DUPLEX
access = _winapi.GENERIC_READ | _winapi.GENERIC_WRITE
@@ -52,20 +56,9 @@ def pipe(*, duplex=False, overlapped=(True, True), bufsize=BUFSIZE):
h1 = h2 = None
try:
for attempts in itertools.count():
address = r'\\.\pipe\python-pipe-{:d}-{:d}-{}'.format(
os.getpid(), next(_mmap_counter), os.urandom(8).hex())
try:
h1 = _winapi.CreateNamedPipe(
address, openmode, _winapi.PIPE_WAIT,
1, obsize, ibsize, _winapi.NMPWAIT_WAIT_FOREVER, _winapi.NULL)
break
except OSError as e:
if attempts >= _MAX_PIPE_ATTEMPTS:
raise
if e.winerror not in (_winapi.ERROR_PIPE_BUSY,
_winapi.ERROR_ACCESS_DENIED):
raise
h1 = _winapi.CreateNamedPipe(
address, openmode, _winapi.PIPE_WAIT,
1, obsize, ibsize, _winapi.NMPWAIT_WAIT_FOREVER, _winapi.NULL)
h2 = _winapi.CreateFile(
address, access, 0, _winapi.NULL, _winapi.OPEN_EXISTING,
@@ -111,9 +104,8 @@ class PipeHandle:
def close(self, *, CloseHandle=_winapi.CloseHandle):
if self._handle is not None:
handle = self._handle
CloseHandle(self._handle)
self._handle = None
CloseHandle(handle)
def __del__(self, _warn=warnings.warn):
if self._handle is not None:

View File

@@ -1375,14 +1375,6 @@ def _find_and_load(name, import_):
# NOTE: because of this, initializing must be set *before*
# putting the new module in sys.modules.
_lock_unlock_module(name)
else:
# Verify the module is still in sys.modules. Another thread may have
# removed it (due to import failure) between our sys.modules.get()
# above and the _initializing check. If removed, we retry the import
# to preserve normal semantics: the caller gets the exception from
# the actual import failure rather than a synthetic error.
if sys.modules.get(name) is not module:
return _find_and_load(name, import_)
if module is None:
message = f'import of {name} halted; None in sys.modules'

View File

@@ -946,7 +946,7 @@ class FileLoader:
def get_data(self, path):
"""Return the data from path as raw bytes."""
if isinstance(self, (SourceLoader, SourcelessFileLoader, ExtensionFileLoader)):
if isinstance(self, (SourceLoader, ExtensionFileLoader)):
with _io.open_code(str(path)) as file:
return file.read()
else:

View File

@@ -11,12 +11,13 @@ __all__ = [ 'Client', 'Listener', 'Pipe', 'wait' ]
import errno
import io
import itertools
import os
import sys
import socket
import struct
import time
import tempfile
import itertools
from . import util
@@ -38,14 +39,11 @@ except ImportError:
#
#
# 64 KiB is the default PIPE buffer size of most POSIX platforms.
BUFSIZE = 64 * 1024
BUFSIZE = 8192
# A very generous timeout when it comes to local connections...
CONNECTION_TIMEOUT = 20.
_mmap_counter = itertools.count()
_MAX_PIPE_ATTEMPTS = 100
default_family = 'AF_INET'
families = ['AF_INET']
@@ -76,14 +74,10 @@ def arbitrary_address(family):
if family == 'AF_INET':
return ('localhost', 0)
elif family == 'AF_UNIX':
# NOTE: util.get_temp_dir() is a 0o700 per-process directory. A
# mktemp-style ToC vs ToU concern is not important; bind() surfaces
# the extremely unlikely collision as EADDRINUSE.
return os.path.join(util.get_temp_dir(),
f'sock-{os.urandom(6).hex()}')
return tempfile.mktemp(prefix='sock-', dir=util.get_temp_dir())
elif family == 'AF_PIPE':
return (r'\\.\pipe\pyc-%d-%d-%s' %
(os.getpid(), next(_mmap_counter), os.urandom(8).hex()))
return tempfile.mktemp(prefix=r'\\.\pipe\pyc-%d-%d-' %
(os.getpid(), next(_mmap_counter)), dir="")
else:
raise ValueError('unrecognized family')
@@ -185,10 +179,6 @@ class _ConnectionBase:
finally:
self._handle = None
def _detach(self):
"""Stop managing the underlying file descriptor or handle."""
self._handle = None
def send_bytes(self, buf, offset=0, size=None):
"""Send the bytes data from a bytes-like object"""
self._check_closed()
@@ -326,32 +316,22 @@ if _winapi:
try:
ov, err = _winapi.ReadFile(self._handle, bsize,
overlapped=True)
sentinel = object()
return_value = sentinel
try:
try:
if err == _winapi.ERROR_IO_PENDING:
waitres = _winapi.WaitForMultipleObjects(
[ov.event], False, INFINITE)
assert waitres == WAIT_OBJECT_0
except:
ov.cancel()
raise
finally:
nread, err = ov.GetOverlappedResult(True)
if err == 0:
f = io.BytesIO()
f.write(ov.getbuffer())
return_value = f
elif err == _winapi.ERROR_MORE_DATA:
return_value = self._get_more_data(ov, maxsize)
if err == _winapi.ERROR_IO_PENDING:
waitres = _winapi.WaitForMultipleObjects(
[ov.event], False, INFINITE)
assert waitres == WAIT_OBJECT_0
except:
if return_value is sentinel:
raise
if return_value is not sentinel:
return return_value
ov.cancel()
raise
finally:
nread, err = ov.GetOverlappedResult(True)
if err == 0:
f = io.BytesIO()
f.write(ov.getbuffer())
return f
elif err == _winapi.ERROR_MORE_DATA:
return self._get_more_data(ov, maxsize)
except OSError as e:
if e.winerror == _winapi.ERROR_BROKEN_PIPE:
raise EOFError
@@ -412,8 +392,7 @@ class Connection(_ConnectionBase):
handle = self._handle
remaining = size
while remaining > 0:
to_read = min(BUFSIZE, remaining)
chunk = read(handle, to_read)
chunk = read(handle, remaining)
n = len(chunk)
if n == 0:
if remaining == size:
@@ -476,29 +455,17 @@ class Listener(object):
def __init__(self, address=None, family=None, backlog=1, authkey=None):
family = family or (address and address_type(address)) \
or default_family
address = address or arbitrary_address(family)
_validate_family(family)
if family == 'AF_PIPE':
self._listener = PipeListener(address, backlog)
else:
self._listener = SocketListener(address, family, backlog)
if authkey is not None and not isinstance(authkey, bytes):
raise TypeError('authkey should be a byte string')
if family == 'AF_PIPE':
if address:
self._listener = PipeListener(address, backlog)
else:
for attempts in itertools.count():
address = arbitrary_address(family)
try:
self._listener = PipeListener(address, backlog)
break
except OSError as e:
if attempts >= _MAX_PIPE_ATTEMPTS:
raise
if e.winerror not in (_winapi.ERROR_PIPE_BUSY,
_winapi.ERROR_ACCESS_DENIED):
raise
else:
address = address or arbitrary_address(family)
self._listener = SocketListener(address, family, backlog)
self._authkey = authkey
def accept(self):
@@ -586,6 +553,7 @@ else:
'''
Returns pair of connection objects at either end of a pipe
'''
address = arbitrary_address('AF_PIPE')
if duplex:
openmode = _winapi.PIPE_ACCESS_DUPLEX
access = _winapi.GENERIC_READ | _winapi.GENERIC_WRITE
@@ -595,25 +563,15 @@ else:
access = _winapi.GENERIC_WRITE
obsize, ibsize = 0, BUFSIZE
for attempts in itertools.count():
address = arbitrary_address('AF_PIPE')
try:
h1 = _winapi.CreateNamedPipe(
address, openmode | _winapi.FILE_FLAG_OVERLAPPED |
_winapi.FILE_FLAG_FIRST_PIPE_INSTANCE,
_winapi.PIPE_TYPE_MESSAGE | _winapi.PIPE_READMODE_MESSAGE |
_winapi.PIPE_WAIT,
1, obsize, ibsize, _winapi.NMPWAIT_WAIT_FOREVER,
# default security descriptor: the handle cannot be inherited
_winapi.NULL
)
break
except OSError as e:
if attempts >= _MAX_PIPE_ATTEMPTS:
raise
if e.winerror not in (_winapi.ERROR_PIPE_BUSY,
_winapi.ERROR_ACCESS_DENIED):
raise
h1 = _winapi.CreateNamedPipe(
address, openmode | _winapi.FILE_FLAG_OVERLAPPED |
_winapi.FILE_FLAG_FIRST_PIPE_INSTANCE,
_winapi.PIPE_TYPE_MESSAGE | _winapi.PIPE_READMODE_MESSAGE |
_winapi.PIPE_WAIT,
1, obsize, ibsize, _winapi.NMPWAIT_WAIT_FOREVER,
# default security descriptor: the handle cannot be inherited
_winapi.NULL
)
h2 = _winapi.CreateFile(
address, access, 0, _winapi.NULL, _winapi.OPEN_EXISTING,
_winapi.FILE_FLAG_OVERLAPPED, _winapi.NULL

View File

@@ -145,13 +145,7 @@ class BaseContext(object):
'''Check whether this is a fake forked process in a frozen executable.
If so then run code specified by commandline and exit.
'''
# gh-140814: allow_none=True avoids locking in the default start
# method, which would cause a later set_start_method() to fail.
# None is safe to pass through: spawn.freeze_support()
# independently detects whether this process is a spawned
# child, so the start method check here is only an optimization.
if (getattr(sys, 'frozen', False)
and self.get_start_method(allow_none=True) in ('spawn', None)):
if self.get_start_method() == 'spawn' and getattr(sys, 'frozen', False):
from .spawn import freeze_support
freeze_support()
@@ -173,7 +167,7 @@ class BaseContext(object):
'''
# This is undocumented. In previous versions of multiprocessing
# its only effect was to make socket objects inheritable on Windows.
from . import connection # noqa: F401
from . import connection
def set_executable(self, executable):
'''Sets the path to a python.exe or pythonw.exe binary used to run
@@ -265,12 +259,13 @@ class DefaultContext(BaseContext):
def get_all_start_methods(self):
"""Returns a list of the supported start methods, default first."""
default = self._default_context.get_start_method()
start_method_names = [default]
start_method_names.extend(
name for name in _concrete_contexts if name != default
)
return start_method_names
if sys.platform == 'win32':
return ['spawn']
else:
methods = ['spawn', 'fork'] if sys.platform == 'darwin' else ['fork', 'spawn']
if reduction.HAVE_SEND_HANDLE:
methods.append('forkserver')
return methods
#
@@ -325,15 +320,14 @@ if sys.platform != 'win32':
'spawn': SpawnContext(),
'forkserver': ForkServerContext(),
}
# bpo-33725: running arbitrary code after fork() is no longer reliable
# on macOS since macOS 10.14 (Mojave). Use spawn by default instead.
# gh-84559: We changed everyones default to a thread safeish one in 3.14.
if reduction.HAVE_SEND_HANDLE and sys.platform != 'darwin':
_default_context = DefaultContext(_concrete_contexts['forkserver'])
else:
if sys.platform == 'darwin':
# bpo-33725: running arbitrary code after fork() is no longer reliable
# on macOS since macOS 10.14 (Mojave). Use spawn by default instead.
_default_context = DefaultContext(_concrete_contexts['spawn'])
else:
_default_context = DefaultContext(_concrete_contexts['fork'])
else: # Windows
else:
class SpawnProcess(process.BaseProcess):
_start_method = 'spawn'

View File

@@ -33,7 +33,7 @@ from queue import Queue
class DummyProcess(threading.Thread):
def __init__(self, group=None, target=None, name=None, args=(), kwargs=None):
def __init__(self, group=None, target=None, name=None, args=(), kwargs={}):
threading.Thread.__init__(self, group, target, name, args, kwargs)
self._pid = None
self._children = weakref.WeakKeyDictionary()

View File

@@ -9,7 +9,6 @@ import sys
import threading
import warnings
from . import AuthenticationError
from . import connection
from . import process
from .context import reduction
@@ -26,7 +25,6 @@ __all__ = ['ensure_running', 'get_inherited_fds', 'connect_to_new_process',
MAXFDS_TO_SEND = 256
SIGNED_STRUCT = struct.Struct('q') # large enough for pid_t
_AUTHKEY_LEN = 32 # <= PIPEBUF so it fits a single write to an empty pipe.
#
# Forkserver class
@@ -35,7 +33,6 @@ _AUTHKEY_LEN = 32 # <= PIPEBUF so it fits a single write to an empty pipe.
class ForkServer(object):
def __init__(self):
self._forkserver_authkey = None
self._forkserver_address = None
self._forkserver_alive_fd = None
self._forkserver_pid = None
@@ -62,7 +59,6 @@ class ForkServer(object):
if not util.is_abstract_socket_namespace(self._forkserver_address):
os.unlink(self._forkserver_address)
self._forkserver_address = None
self._forkserver_authkey = None
def set_forkserver_preload(self, modules_names):
'''Set list of module names to try to load in forkserver process.'''
@@ -87,7 +83,6 @@ class ForkServer(object):
process data.
'''
self.ensure_running()
assert self._forkserver_authkey
if len(fds) + 4 >= MAXFDS_TO_SEND:
raise ValueError('too many fds')
with socket.socket(socket.AF_UNIX) as client:
@@ -98,18 +93,6 @@ class ForkServer(object):
resource_tracker.getfd()]
allfds += fds
try:
client.setblocking(True)
wrapped_client = connection.Connection(client.fileno())
# The other side of this exchange happens in the child as
# implemented in main().
try:
connection.answer_challenge(
wrapped_client, self._forkserver_authkey)
connection.deliver_challenge(
wrapped_client, self._forkserver_authkey)
finally:
wrapped_client._detach()
del wrapped_client
reduction.sendfds(client, allfds)
return parent_r, parent_w
except:
@@ -137,30 +120,20 @@ class ForkServer(object):
return
# dead, launch it again
os.close(self._forkserver_alive_fd)
self._forkserver_authkey = None
self._forkserver_address = None
self._forkserver_alive_fd = None
self._forkserver_pid = None
# gh-144503: sys_argv is passed as real argv elements after the
# ``-c cmd`` rather than repr'd into main_kws so that a large
# parent sys.argv cannot push the single ``-c`` command string
# over the OS per-argument length limit (MAX_ARG_STRLEN on Linux).
# The child sees them as sys.argv[1:].
cmd = ('import sys; '
'from multiprocessing.forkserver import main; '
'main(%d, %d, %r, sys_argv=sys.argv[1:], **%r)')
cmd = ('from multiprocessing.forkserver import main; ' +
'main(%d, %d, %r, **%r)')
main_kws = {}
sys_argv = None
if self._preload_modules:
data = spawn.get_preparation_data('ignore')
if 'sys_path' in data:
main_kws['sys_path'] = data['sys_path']
if 'init_main_from_path' in data:
main_kws['main_path'] = data['init_main_from_path']
if 'sys_argv' in data:
sys_argv = data['sys_argv']
with socket.socket(socket.AF_UNIX) as listener:
address = connection.arbitrary_address('AF_UNIX')
@@ -172,33 +145,19 @@ class ForkServer(object):
# all client processes own the write end of the "alive" pipe;
# when they all terminate the read end becomes ready.
alive_r, alive_w = os.pipe()
# A short lived pipe to initialize the forkserver authkey.
authkey_r, authkey_w = os.pipe()
try:
fds_to_pass = [listener.fileno(), alive_r, authkey_r]
main_kws['authkey_r'] = authkey_r
fds_to_pass = [listener.fileno(), alive_r]
cmd %= (listener.fileno(), alive_r, self._preload_modules,
main_kws)
exe = spawn.get_executable()
args = [exe] + util._args_from_interpreter_flags()
args += ['-c', cmd]
if sys_argv is not None:
args += sys_argv
pid = util.spawnv_passfds(exe, args, fds_to_pass)
except:
os.close(alive_w)
os.close(authkey_w)
raise
finally:
os.close(alive_r)
os.close(authkey_r)
# Authenticate our control socket to prevent access from
# processes we have not shared this key with.
try:
self._forkserver_authkey = os.urandom(_AUTHKEY_LEN)
os.write(authkey_w, self._forkserver_authkey)
finally:
os.close(authkey_w)
self._forkserver_address = address
self._forkserver_alive_fd = alive_w
self._forkserver_pid = pid
@@ -207,21 +166,9 @@ class ForkServer(object):
#
#
def main(listener_fd, alive_r, preload, main_path=None, sys_path=None,
*, sys_argv=None, authkey_r=None):
"""Run forkserver."""
if authkey_r is not None:
try:
authkey = os.read(authkey_r, _AUTHKEY_LEN)
assert len(authkey) == _AUTHKEY_LEN, f'{len(authkey)} < {_AUTHKEY_LEN}'
finally:
os.close(authkey_r)
else:
authkey = b''
def main(listener_fd, alive_r, preload, main_path=None, sys_path=None):
'''Run forkserver.'''
if preload:
if sys_argv is not None:
sys.argv[:] = sys_argv
if sys_path is not None:
sys.path[:] = sys_path
if '__main__' in preload and main_path is not None:
@@ -315,24 +262,8 @@ def main(listener_fd, alive_r, preload, main_path=None, sys_path=None,
if listener in rfds:
# Incoming fork request
with listener.accept()[0] as s:
try:
if authkey:
wrapped_s = connection.Connection(s.fileno())
# The other side of this exchange happens in
# in connect_to_new_process().
try:
connection.deliver_challenge(
wrapped_s, authkey)
connection.answer_challenge(
wrapped_s, authkey)
finally:
wrapped_s._detach()
del wrapped_s
# Receive fds from client
fds = reduction.recvfds(s, MAXFDS_TO_SEND + 1)
except (EOFError, BrokenPipeError, AuthenticationError):
s.close()
continue
# Receive fds from client
fds = reduction.recvfds(s, MAXFDS_TO_SEND + 1)
if len(fds) > MAXFDS_TO_SEND:
raise RuntimeError(
"Too many ({0:n}) fds to send".format(
@@ -400,14 +331,13 @@ def _serve_one(child_r, fds, unused_fds, handlers):
#
def read_signed(fd):
data = bytearray(SIGNED_STRUCT.size)
unread = memoryview(data)
while unread:
count = os.readinto(fd, unread)
if count == 0:
data = b''
length = SIGNED_STRUCT.size
while len(data) < length:
s = os.read(fd, length - len(data))
if not s:
raise EOFError('unexpected EOF')
unread = unread[count:]
data += s
return SIGNED_STRUCT.unpack(data)[0]
def write_signed(fd, n):

View File

@@ -18,7 +18,6 @@ import sys
import threading
import signal
import array
import collections.abc
import queue
import time
import types
@@ -1059,14 +1058,12 @@ class IteratorProxy(BaseProxy):
class AcquirerProxy(BaseProxy):
_exposed_ = ('acquire', 'release', 'locked')
_exposed_ = ('acquire', 'release')
def acquire(self, blocking=True, timeout=None):
args = (blocking,) if timeout is None else (blocking, timeout)
return self._callmethod('acquire', args)
def release(self):
return self._callmethod('release')
def locked(self):
return self._callmethod('locked')
def __enter__(self):
return self._callmethod('acquire')
def __exit__(self, exc_type, exc_val, exc_tb):
@@ -1074,7 +1071,7 @@ class AcquirerProxy(BaseProxy):
class ConditionProxy(AcquirerProxy):
_exposed_ = ('acquire', 'release', 'locked', 'wait', 'notify', 'notify_all')
_exposed_ = ('acquire', 'release', 'wait', 'notify', 'notify_all')
def wait(self, timeout=None):
return self._callmethod('wait', (timeout,))
def notify(self, n=1):
@@ -1162,10 +1159,10 @@ class ValueProxy(BaseProxy):
BaseListProxy = MakeProxyType('BaseListProxy', (
'__add__', '__contains__', '__delitem__', '__getitem__', '__imul__',
'__len__', '__mul__', '__reversed__', '__rmul__', '__setitem__',
'append', 'clear', 'copy', 'count', 'extend', 'index', 'insert', 'pop',
'remove', 'reverse', 'sort',
'__add__', '__contains__', '__delitem__', '__getitem__', '__len__',
'__mul__', '__reversed__', '__rmul__', '__setitem__',
'append', 'count', 'extend', 'index', 'insert', 'pop', 'remove',
'reverse', 'sort', '__imul__'
))
class ListProxy(BaseListProxy):
def __iadd__(self, value):
@@ -1177,55 +1174,18 @@ class ListProxy(BaseListProxy):
__class_getitem__ = classmethod(types.GenericAlias)
collections.abc.MutableSequence.register(BaseListProxy)
_BaseDictProxy = MakeProxyType('_BaseDictProxy', (
'__contains__', '__delitem__', '__getitem__', '__ior__', '__iter__',
'__len__', '__or__', '__reversed__', '__ror__',
'__setitem__', 'clear', 'copy', 'fromkeys', 'get', 'items',
_BaseDictProxy = MakeProxyType('DictProxy', (
'__contains__', '__delitem__', '__getitem__', '__iter__', '__len__',
'__setitem__', 'clear', 'copy', 'get', 'items',
'keys', 'pop', 'popitem', 'setdefault', 'update', 'values'
))
_BaseDictProxy._method_to_typeid_ = {
'__iter__': 'Iterator',
}
class DictProxy(_BaseDictProxy):
def __ior__(self, value):
self._callmethod('__ior__', (value,))
return self
__class_getitem__ = classmethod(types.GenericAlias)
collections.abc.MutableMapping.register(_BaseDictProxy)
_BaseSetProxy = MakeProxyType("_BaseSetProxy", (
'__and__', '__class_getitem__', '__contains__', '__iand__', '__ior__',
'__isub__', '__iter__', '__ixor__', '__len__', '__or__', '__rand__',
'__ror__', '__rsub__', '__rxor__', '__sub__', '__xor__',
'__ge__', '__gt__', '__le__', '__lt__',
'add', 'clear', 'copy', 'difference', 'difference_update', 'discard',
'intersection', 'intersection_update', 'isdisjoint', 'issubset',
'issuperset', 'pop', 'remove', 'symmetric_difference',
'symmetric_difference_update', 'union', 'update',
))
class SetProxy(_BaseSetProxy):
def __ior__(self, value):
self._callmethod('__ior__', (value,))
return self
def __iand__(self, value):
self._callmethod('__iand__', (value,))
return self
def __ixor__(self, value):
self._callmethod('__ixor__', (value,))
return self
def __isub__(self, value):
self._callmethod('__isub__', (value,))
return self
__class_getitem__ = classmethod(types.GenericAlias)
collections.abc.MutableMapping.register(_BaseSetProxy)
ArrayProxy = MakeProxyType('ArrayProxy', (
'__len__', '__getitem__', '__setitem__'
@@ -1277,7 +1237,6 @@ SyncManager.register('Barrier', threading.Barrier, BarrierProxy)
SyncManager.register('Pool', pool.Pool, PoolProxy)
SyncManager.register('list', list, ListProxy)
SyncManager.register('dict', dict, DictProxy)
SyncManager.register('set', set, SetProxy)
SyncManager.register('Value', Value, ValueProxy)
SyncManager.register('Array', Array, ArrayProxy)
SyncManager.register('Namespace', Namespace, NamespaceProxy)

View File

@@ -54,9 +54,6 @@ class Popen(object):
if self.wait(timeout=0.1) is None:
raise
def interrupt(self):
self._send_signal(signal.SIGINT)
def terminate(self):
self._send_signal(signal.SIGTERM)
@@ -67,17 +64,7 @@ class Popen(object):
code = 1
parent_r, child_w = os.pipe()
child_r, parent_w = os.pipe()
# gh-146313: Tell the resource tracker's at-fork handler to keep
# the inherited pipe fd so this child reuses the parent's tracker
# (gh-80849) rather than closing it and launching its own.
from .resource_tracker import _fork_intent
_fork_intent.preserve_fd = True
try:
self.pid = os.fork()
finally:
# Reset in both parent and child so the flag does not leak
# into a subsequent raw os.fork() or nested Process launch.
_fork_intent.preserve_fd = False
self.pid = os.fork()
if self.pid == 0:
try:
atexit._clear()

View File

@@ -77,7 +77,7 @@ class BaseProcess(object):
def _Popen(self):
raise NotImplementedError
def __init__(self, group=None, target=None, name=None, args=(), kwargs=None,
def __init__(self, group=None, target=None, name=None, args=(), kwargs={},
*, daemon=None):
assert group is None, 'group argument must be None for now'
count = next(_process_counter)
@@ -89,7 +89,7 @@ class BaseProcess(object):
self._closed = False
self._target = target
self._args = tuple(args)
self._kwargs = dict(kwargs) if kwargs else {}
self._kwargs = dict(kwargs)
self._name = name or type(self).__name__ + '-' + \
':'.join(str(i) for i in self._identity)
if daemon is not None:
@@ -125,13 +125,6 @@ class BaseProcess(object):
del self._target, self._args, self._kwargs
_children.add(self)
def interrupt(self):
'''
Terminate process; sends SIGINT signal
'''
self._check_closed()
self._popen.interrupt()
def terminate(self):
'''
Terminate process; sends SIGTERM signal or uses TerminateProcess()

View File

@@ -121,7 +121,7 @@ class Queue(object):
def qsize(self):
# Raises NotImplementedError on Mac OSX because of broken sem_getvalue()
return self._maxsize - self._sem.get_value()
return self._maxsize - self._sem._semlock._get_value()
def empty(self):
return not self._poll()

View File

@@ -139,12 +139,15 @@ else:
__all__ += ['DupFd', 'sendfds', 'recvfds']
import array
# On MacOSX we should acknowledge receipt of fds -- see Issue14669
ACKNOWLEDGE = sys.platform == 'darwin'
def sendfds(sock, fds):
'''Send an array of fds over an AF_UNIX socket.'''
fds = array.array('i', fds)
msg = bytes([len(fds) % 256])
sock.sendmsg([msg], [(socket.SOL_SOCKET, socket.SCM_RIGHTS, fds)])
if sock.recv(1) != b'A':
if ACKNOWLEDGE and sock.recv(1) != b'A':
raise RuntimeError('did not receive acknowledgement of fd')
def recvfds(sock, size):
@@ -155,11 +158,8 @@ else:
if not msg and not ancdata:
raise EOFError
try:
# We send/recv an Ack byte after the fds to work around an old
# macOS bug; it isn't clear if this is still required but it
# makes unit testing fd sending easier.
# See: https://github.com/python/cpython/issues/58874
sock.send(b'A') # Acknowledge
if ACKNOWLEDGE:
sock.send(b'A')
if len(ancdata) != 1:
raise RuntimeError('received %d items of ancdata' %
len(ancdata))

View File

@@ -20,7 +20,6 @@ import os
import signal
import sys
import threading
import time
import warnings
from collections import deque
@@ -52,8 +51,12 @@ if os.name == 'posix':
# absence of POSIX named semaphores. In that case, no named semaphores were
# ever opened, so no cleanup would be necessary.
if hasattr(_multiprocessing, 'sem_unlink'):
_CLEANUP_FUNCS['semaphore'] = _multiprocessing.sem_unlink
_CLEANUP_FUNCS['shared_memory'] = _posixshmem.shm_unlink
_CLEANUP_FUNCS.update({
'semaphore': _multiprocessing.sem_unlink,
})
_CLEANUP_FUNCS.update({
'shared_memory': _posixshmem.shm_unlink,
})
class ReentrantCallError(RuntimeError):
@@ -76,10 +79,6 @@ class ResourceTracker(object):
# The reader should understand all formats.
self._use_simple_format = True
# Set to True by _stop_locked() if the waitpid polling loop ran to
# its timeout without reaping the tracker. Exposed for tests.
self._waitpid_timed_out = False
def _reentrant_call_error(self):
# gh-109629: this happens if an explicit call to the ResourceTracker
# gets interrupted by a garbage collection, invoking a finalizer (*)
@@ -92,51 +91,16 @@ class ResourceTracker(object):
# making sure child processess are cleaned before ResourceTracker
# gets destructed.
# see https://github.com/python/cpython/issues/88887
# gh-146313: use a timeout to avoid deadlocking if a forked child
# still holds the pipe's write end open.
self._stop(use_blocking_lock=False, wait_timeout=1.0)
self._stop(use_blocking_lock=False)
def _after_fork_in_child(self):
# gh-146313: Called in the child right after os.fork().
#
# The tracker process is a child of the *parent*, not of us, so we
# could never waitpid() it anyway. Clearing _pid means our __del__
# becomes a no-op (the early return for _pid is None).
#
# Whether we keep the inherited _fd depends on who forked us:
#
# - multiprocessing.Process with the 'fork' start method sets
# _fork_intent.preserve_fd before forking. The child keeps the
# fd and reuses the parent's tracker (gh-80849). This is safe
# because multiprocessing's atexit handler joins all children
# before the parent's __del__ runs, so by then the fd copies
# are gone and the parent can reap the tracker promptly.
#
# - A raw os.fork() leaves the flag unset. We close the fd in the child after forking so
# the parent's __del__ can reap the tracker without waiting
# for the child to exit. If we later need a tracker, ensure_running()
# will launch a fresh one.
self._lock._at_fork_reinit()
self._reentrant_messages.clear()
self._pid = None
self._exitcode = None
if (self._fd is not None and
not getattr(_fork_intent, 'preserve_fd', False)):
fd = self._fd
self._fd = None
try:
os.close(fd)
except OSError:
pass
def _stop(self, use_blocking_lock=True, wait_timeout=None):
def _stop(self, use_blocking_lock=True):
if use_blocking_lock:
with self._lock:
self._stop_locked(wait_timeout=wait_timeout)
self._stop_locked()
else:
acquired = self._lock.acquire(blocking=False)
try:
self._stop_locked(wait_timeout=wait_timeout)
self._stop_locked()
finally:
if acquired:
self._lock.release()
@@ -146,10 +110,6 @@ class ResourceTracker(object):
close=os.close,
waitpid=os.waitpid,
waitstatus_to_exitcode=os.waitstatus_to_exitcode,
monotonic=time.monotonic,
sleep=time.sleep,
WNOHANG=getattr(os, 'WNOHANG', None),
wait_timeout=None,
):
# This shouldn't happen (it might when called by a finalizer)
# so we check for it anyway.
@@ -166,30 +126,7 @@ class ResourceTracker(object):
self._fd = None
try:
if wait_timeout is None:
_, status = waitpid(self._pid, 0)
else:
# gh-146313: A forked child may still hold the pipe's write
# end open, preventing the tracker from seeing EOF and
# exiting. Poll with WNOHANG to avoid blocking forever.
deadline = monotonic() + wait_timeout
delay = 0.001
while True:
result_pid, status = waitpid(self._pid, WNOHANG)
if result_pid != 0:
break
remaining = deadline - monotonic()
if remaining <= 0:
# The tracker is still running; it will be
# reparented to PID 1 (or the nearest subreaper)
# when we exit, and reaped there once all pipe
# holders release their fd.
self._pid = None
self._exitcode = None
self._waitpid_timed_out = True
return
delay = min(delay * 2, remaining, 0.1)
sleep(delay)
_, status = waitpid(self._pid, 0)
except ChildProcessError:
self._pid = None
self._exitcode = None
@@ -375,24 +312,12 @@ class ResourceTracker(object):
self._ensure_running_and_write(msg)
# gh-146313: Per-thread flag set by .popen_fork.Popen._launch() just before
# os.fork(), telling _after_fork_in_child() to keep the inherited pipe fd so
# the child can reuse this tracker (gh-80849). Unset for raw os.fork() calls,
# where the child instead closes the fd so the parent's __del__ can reap the
# tracker. Using threading.local() keeps multiple threads calling
# popen_fork.Popen._launch() at once from clobbering eachothers intent.
_fork_intent = threading.local()
_resource_tracker = ResourceTracker()
ensure_running = _resource_tracker.ensure_running
register = _resource_tracker.register
unregister = _resource_tracker.unregister
getfd = _resource_tracker.getfd
# gh-146313: See _after_fork_in_child docstring.
if hasattr(os, 'register_at_fork'):
os.register_at_fork(after_in_child=_resource_tracker._after_fork_in_child)
def _decode_message(line):
if line.startswith(b'{'):

View File

@@ -539,6 +539,6 @@ class ShareableList:
if value == entry:
return position
else:
raise ValueError("ShareableList.index(x): x not in list")
raise ValueError(f"{value!r} not in this container")
__class_getitem__ = classmethod(types.GenericAlias)

View File

@@ -184,7 +184,7 @@ def get_preparation_data(name):
sys_argv=sys.argv,
orig_dir=process.ORIGINAL_DIR,
dir=os.getcwd(),
start_method=get_start_method(allow_none=True),
start_method=get_start_method(),
)
# Figure out whether to initialise main in the subprocess as a module

View File

@@ -21,21 +21,22 @@ from . import context
from . import process
from . import util
# TODO: Do any platforms still lack a functioning sem_open?
# Try to import the mp.synchronize module cleanly, if it fails
# raise ImportError for platforms lacking a working sem_open implementation.
# See issue 3770
try:
from _multiprocessing import SemLock, sem_unlink
except ImportError:
except (ImportError):
raise ImportError("This platform lacks a functioning sem_open" +
" implementation. https://github.com/python/cpython/issues/48020.")
" implementation, therefore, the required" +
" synchronization primitives needed will not" +
" function, see issue 3770.")
#
# Constants
#
# These match the enum in Modules/_multiprocessing/semaphore.c
RECURSIVE_MUTEX = 0
SEMAPHORE = 1
RECURSIVE_MUTEX, SEMAPHORE = list(range(2))
SEM_VALUE_MAX = _multiprocessing.SemLock.SEM_VALUE_MAX
#
@@ -90,9 +91,6 @@ class SemLock(object):
self.acquire = self._semlock.acquire
self.release = self._semlock.release
def locked(self):
return self._semlock._is_zero()
def __enter__(self):
return self._semlock.__enter__()
@@ -135,16 +133,11 @@ class Semaphore(SemLock):
SemLock.__init__(self, SEMAPHORE, value, SEM_VALUE_MAX, ctx=ctx)
def get_value(self):
'''Returns current value of Semaphore.
Raises NotImplementedError on Mac OSX
because of broken sem_getvalue().
'''
return self._semlock._get_value()
def __repr__(self):
try:
value = self.get_value()
value = self._semlock._get_value()
except Exception:
value = 'unknown'
return '<%s(value=%s)>' % (self.__class__.__name__, value)
@@ -160,7 +153,7 @@ class BoundedSemaphore(Semaphore):
def __repr__(self):
try:
value = self.get_value()
value = self._semlock._get_value()
except Exception:
value = 'unknown'
return '<%s(value=%s, maxvalue=%s)>' % \
@@ -252,8 +245,8 @@ class Condition(object):
def __repr__(self):
try:
num_waiters = (self._sleeping_count.get_value() -
self._woken_count.get_value())
num_waiters = (self._sleeping_count._semlock._get_value() -
self._woken_count._semlock._get_value())
except Exception:
num_waiters = 'unknown'
return '<%s(%s, %s)>' % (self.__class__.__name__, self._lock, num_waiters)

View File

@@ -14,12 +14,12 @@ import weakref
import atexit
import threading # we want threading to install it's
# cleanup function before multiprocessing does
from subprocess import _args_from_interpreter_flags # noqa: F401
from subprocess import _args_from_interpreter_flags
from . import process
__all__ = [
'sub_debug', 'debug', 'info', 'sub_warning', 'warn', 'get_logger',
'sub_debug', 'debug', 'info', 'sub_warning', 'get_logger',
'log_to_stderr', 'get_temp_dir', 'register_after_fork',
'is_exiting', 'Finalize', 'ForkAwareThreadLock', 'ForkAwareLocal',
'close_all_fds_except', 'SUBDEBUG', 'SUBWARNING',
@@ -54,7 +54,7 @@ def info(msg, *args):
if _logger:
_logger.log(INFO, msg, *args, stacklevel=2)
def warn(msg, *args):
def _warn(msg, *args):
if _logger:
_logger.log(WARNING, msg, *args, stacklevel=2)
@@ -196,14 +196,14 @@ def _get_base_temp_dir(tempfile):
try:
base_system_tempdir = tempfile._get_default_tempdir(dirlist)
except FileNotFoundError:
warn("Process-wide temporary directory %s will not be usable for "
"creating socket files and no usable system-wide temporary "
"directory was found in %s", base_tempdir, dirlist)
_warn("Process-wide temporary directory %s will not be usable for "
"creating socket files and no usable system-wide temporary "
"directory was found in %s", base_tempdir, dirlist)
# At this point, the system-wide temporary directory is not usable
# but we may assume that the user-defined one is, even if we will
# not be able to write socket files out there.
return base_tempdir
warn("Ignoring user-defined temporary directory: %s", base_tempdir)
_warn("Ignoring user-defined temporary directory: %s", base_tempdir)
# at most max(map(len, dirlist)) + 14 + 14 = 36 characters
assert len(base_system_tempdir) + 14 + 14 < _SUN_PATH_MAX
return base_system_tempdir

615
Lib/profile.py vendored
View File

@@ -1,615 +0,0 @@
#
# Class for profiling python code. rev 1.0 6/2/94
#
# Written by James Roskind
# Based on prior profile module by Sjoerd Mullender...
# which was hacked somewhat by: Guido van Rossum
"""Class for profiling Python code."""
# Copyright Disney Enterprises, Inc. All Rights Reserved.
# Licensed to PSF under a Contributor Agreement
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
# either express or implied. See the License for the specific language
# governing permissions and limitations under the License.
import importlib.machinery
import io
import sys
import time
import marshal
__all__ = ["run", "runctx", "Profile"]
# Sample timer for use with
#i_count = 0
#def integer_timer():
# global i_count
# i_count = i_count + 1
# return i_count
#itimes = integer_timer # replace with C coded timer returning integers
class _Utils:
"""Support class for utility functions which are shared by
profile.py and cProfile.py modules.
Not supposed to be used directly.
"""
def __init__(self, profiler):
self.profiler = profiler
def run(self, statement, filename, sort):
prof = self.profiler()
try:
prof.run(statement)
except SystemExit:
pass
finally:
self._show(prof, filename, sort)
def runctx(self, statement, globals, locals, filename, sort):
prof = self.profiler()
try:
prof.runctx(statement, globals, locals)
except SystemExit:
pass
finally:
self._show(prof, filename, sort)
def _show(self, prof, filename, sort):
if filename is not None:
prof.dump_stats(filename)
else:
prof.print_stats(sort)
#**************************************************************************
# The following are the static member functions for the profiler class
# Note that an instance of Profile() is *not* needed to call them.
#**************************************************************************
def run(statement, filename=None, sort=-1):
"""Run statement under profiler optionally saving results in filename
This function takes a single argument that can be passed to the
"exec" statement, and an optional file name. In all cases this
routine attempts to "exec" its first argument and gather profiling
statistics from the execution. If no file name is present, then this
function automatically prints a simple profiling report, sorted by the
standard name string (file/line/function-name) that is presented in
each line.
"""
return _Utils(Profile).run(statement, filename, sort)
def runctx(statement, globals, locals, filename=None, sort=-1):
"""Run statement under profiler, supplying your own globals and locals,
optionally saving results in filename.
statement and filename have the same semantics as profile.run
"""
return _Utils(Profile).runctx(statement, globals, locals, filename, sort)
class Profile:
"""Profiler class.
self.cur is always a tuple. Each such tuple corresponds to a stack
frame that is currently active (self.cur[-2]). The following are the
definitions of its members. We use this external "parallel stack" to
avoid contaminating the program that we are profiling. (old profiler
used to write into the frames local dictionary!!) Derived classes
can change the definition of some entries, as long as they leave
[-2:] intact (frame and previous tuple). In case an internal error is
detected, the -3 element is used as the function name.
[ 0] = Time that needs to be charged to the parent frame's function.
It is used so that a function call will not have to access the
timing data for the parent frame.
[ 1] = Total time spent in this frame's function, excluding time in
subfunctions (this latter is tallied in cur[2]).
[ 2] = Total time spent in subfunctions, excluding time executing the
frame's function (this latter is tallied in cur[1]).
[-3] = Name of the function that corresponds to this frame.
[-2] = Actual frame that we correspond to (used to sync exception handling).
[-1] = Our parent 6-tuple (corresponds to frame.f_back).
Timing data for each function is stored as a 5-tuple in the dictionary
self.timings[]. The index is always the name stored in self.cur[-3].
The following are the definitions of the members:
[0] = The number of times this function was called, not counting direct
or indirect recursion,
[1] = Number of times this function appears on the stack, minus one
[2] = Total time spent internal to this function
[3] = Cumulative time that this function was present on the stack. In
non-recursive functions, this is the total execution time from start
to finish of each invocation of a function, including time spent in
all subfunctions.
[4] = A dictionary indicating for each function name, the number of times
it was called by us.
"""
bias = 0 # calibration constant
def __init__(self, timer=None, bias=None):
self.timings = {}
self.cur = None
self.cmd = ""
self.c_func_name = ""
if bias is None:
bias = self.bias
self.bias = bias # Materialize in local dict for lookup speed.
if not timer:
self.timer = self.get_time = time.process_time
self.dispatcher = self.trace_dispatch_i
else:
self.timer = timer
t = self.timer() # test out timer function
try:
length = len(t)
except TypeError:
self.get_time = timer
self.dispatcher = self.trace_dispatch_i
else:
if length == 2:
self.dispatcher = self.trace_dispatch
else:
self.dispatcher = self.trace_dispatch_l
# This get_time() implementation needs to be defined
# here to capture the passed-in timer in the parameter
# list (for performance). Note that we can't assume
# the timer() result contains two values in all
# cases.
def get_time_timer(timer=timer, sum=sum):
return sum(timer())
self.get_time = get_time_timer
self.t = self.get_time()
self.simulate_call('profiler')
# Heavily optimized dispatch routine for time.process_time() timer
def trace_dispatch(self, frame, event, arg):
timer = self.timer
t = timer()
t = t[0] + t[1] - self.t - self.bias
if event == "c_call":
self.c_func_name = arg.__name__
if self.dispatch[event](self, frame,t):
t = timer()
self.t = t[0] + t[1]
else:
r = timer()
self.t = r[0] + r[1] - t # put back unrecorded delta
# Dispatch routine for best timer program (return = scalar, fastest if
# an integer but float works too -- and time.process_time() relies on that).
def trace_dispatch_i(self, frame, event, arg):
timer = self.timer
t = timer() - self.t - self.bias
if event == "c_call":
self.c_func_name = arg.__name__
if self.dispatch[event](self, frame, t):
self.t = timer()
else:
self.t = timer() - t # put back unrecorded delta
# Dispatch routine for macintosh (timer returns time in ticks of
# 1/60th second)
def trace_dispatch_mac(self, frame, event, arg):
timer = self.timer
t = timer()/60.0 - self.t - self.bias
if event == "c_call":
self.c_func_name = arg.__name__
if self.dispatch[event](self, frame, t):
self.t = timer()/60.0
else:
self.t = timer()/60.0 - t # put back unrecorded delta
# SLOW generic dispatch routine for timer returning lists of numbers
def trace_dispatch_l(self, frame, event, arg):
get_time = self.get_time
t = get_time() - self.t - self.bias
if event == "c_call":
self.c_func_name = arg.__name__
if self.dispatch[event](self, frame, t):
self.t = get_time()
else:
self.t = get_time() - t # put back unrecorded delta
# In the event handlers, the first 3 elements of self.cur are unpacked
# into vrbls w/ 3-letter names. The last two characters are meant to be
# mnemonic:
# _pt self.cur[0] "parent time" time to be charged to parent frame
# _it self.cur[1] "internal time" time spent directly in the function
# _et self.cur[2] "external time" time spent in subfunctions
def trace_dispatch_exception(self, frame, t):
rpt, rit, ret, rfn, rframe, rcur = self.cur
if (rframe is not frame) and rcur:
return self.trace_dispatch_return(rframe, t)
self.cur = rpt, rit+t, ret, rfn, rframe, rcur
return 1
def trace_dispatch_call(self, frame, t):
if self.cur and frame.f_back is not self.cur[-2]:
rpt, rit, ret, rfn, rframe, rcur = self.cur
if not isinstance(rframe, Profile.fake_frame):
assert rframe.f_back is frame.f_back, ("Bad call", rfn,
rframe, rframe.f_back,
frame, frame.f_back)
self.trace_dispatch_return(rframe, 0)
assert (self.cur is None or \
frame.f_back is self.cur[-2]), ("Bad call",
self.cur[-3])
fcode = frame.f_code
fn = (fcode.co_filename, fcode.co_firstlineno, fcode.co_name)
self.cur = (t, 0, 0, fn, frame, self.cur)
timings = self.timings
if fn in timings:
cc, ns, tt, ct, callers = timings[fn]
timings[fn] = cc, ns + 1, tt, ct, callers
else:
timings[fn] = 0, 0, 0, 0, {}
return 1
def trace_dispatch_c_call (self, frame, t):
fn = ("", 0, self.c_func_name)
self.cur = (t, 0, 0, fn, frame, self.cur)
timings = self.timings
if fn in timings:
cc, ns, tt, ct, callers = timings[fn]
timings[fn] = cc, ns+1, tt, ct, callers
else:
timings[fn] = 0, 0, 0, 0, {}
return 1
def trace_dispatch_return(self, frame, t):
if frame is not self.cur[-2]:
assert frame is self.cur[-2].f_back, ("Bad return", self.cur[-3])
self.trace_dispatch_return(self.cur[-2], 0)
# Prefix "r" means part of the Returning or exiting frame.
# Prefix "p" means part of the Previous or Parent or older frame.
rpt, rit, ret, rfn, frame, rcur = self.cur
rit = rit + t
frame_total = rit + ret
ppt, pit, pet, pfn, pframe, pcur = rcur
self.cur = ppt, pit + rpt, pet + frame_total, pfn, pframe, pcur
timings = self.timings
cc, ns, tt, ct, callers = timings[rfn]
if not ns:
# This is the only occurrence of the function on the stack.
# Else this is a (directly or indirectly) recursive call, and
# its cumulative time will get updated when the topmost call to
# it returns.
ct = ct + frame_total
cc = cc + 1
if pfn in callers:
callers[pfn] = callers[pfn] + 1 # hack: gather more
# stats such as the amount of time added to ct courtesy
# of this specific call, and the contribution to cc
# courtesy of this call.
else:
callers[pfn] = 1
timings[rfn] = cc, ns - 1, tt + rit, ct, callers
return 1
dispatch = {
"call": trace_dispatch_call,
"exception": trace_dispatch_exception,
"return": trace_dispatch_return,
"c_call": trace_dispatch_c_call,
"c_exception": trace_dispatch_return, # the C function returned
"c_return": trace_dispatch_return,
}
# The next few functions play with self.cmd. By carefully preloading
# our parallel stack, we can force the profiled result to include
# an arbitrary string as the name of the calling function.
# We use self.cmd as that string, and the resulting stats look
# very nice :-).
def set_cmd(self, cmd):
if self.cur[-1]: return # already set
self.cmd = cmd
self.simulate_call(cmd)
class fake_code:
def __init__(self, filename, line, name):
self.co_filename = filename
self.co_line = line
self.co_name = name
self.co_firstlineno = 0
def __repr__(self):
return repr((self.co_filename, self.co_line, self.co_name))
class fake_frame:
def __init__(self, code, prior):
self.f_code = code
self.f_back = prior
def simulate_call(self, name):
code = self.fake_code('profile', 0, name)
if self.cur:
pframe = self.cur[-2]
else:
pframe = None
frame = self.fake_frame(code, pframe)
self.dispatch['call'](self, frame, 0)
# collect stats from pending stack, including getting final
# timings for self.cmd frame.
def simulate_cmd_complete(self):
get_time = self.get_time
t = get_time() - self.t
while self.cur[-1]:
# We *can* cause assertion errors here if
# dispatch_trace_return checks for a frame match!
self.dispatch['return'](self, self.cur[-2], t)
t = 0
self.t = get_time() - t
def print_stats(self, sort=-1):
import pstats
if not isinstance(sort, tuple):
sort = (sort,)
pstats.Stats(self).strip_dirs().sort_stats(*sort).print_stats()
def dump_stats(self, file):
with open(file, 'wb') as f:
self.create_stats()
marshal.dump(self.stats, f)
def create_stats(self):
self.simulate_cmd_complete()
self.snapshot_stats()
def snapshot_stats(self):
self.stats = {}
for func, (cc, ns, tt, ct, callers) in self.timings.items():
callers = callers.copy()
nc = 0
for callcnt in callers.values():
nc += callcnt
self.stats[func] = cc, nc, tt, ct, callers
# The following two methods can be called by clients to use
# a profiler to profile a statement, given as a string.
def run(self, cmd):
import __main__
dict = __main__.__dict__
return self.runctx(cmd, dict, dict)
def runctx(self, cmd, globals, locals):
self.set_cmd(cmd)
sys.setprofile(self.dispatcher)
try:
exec(cmd, globals, locals)
finally:
sys.setprofile(None)
return self
# This method is more useful to profile a single function call.
def runcall(self, func, /, *args, **kw):
self.set_cmd(repr(func))
sys.setprofile(self.dispatcher)
try:
return func(*args, **kw)
finally:
sys.setprofile(None)
#******************************************************************
# The following calculates the overhead for using a profiler. The
# problem is that it takes a fair amount of time for the profiler
# to stop the stopwatch (from the time it receives an event).
# Similarly, there is a delay from the time that the profiler
# re-starts the stopwatch before the user's code really gets to
# continue. The following code tries to measure the difference on
# a per-event basis.
#
# Note that this difference is only significant if there are a lot of
# events, and relatively little user code per event. For example,
# code with small functions will typically benefit from having the
# profiler calibrated for the current platform. This *could* be
# done on the fly during init() time, but it is not worth the
# effort. Also note that if too large a value specified, then
# execution time on some functions will actually appear as a
# negative number. It is *normal* for some functions (with very
# low call counts) to have such negative stats, even if the
# calibration figure is "correct."
#
# One alternative to profile-time calibration adjustments (i.e.,
# adding in the magic little delta during each event) is to track
# more carefully the number of events (and cumulatively, the number
# of events during sub functions) that are seen. If this were
# done, then the arithmetic could be done after the fact (i.e., at
# display time). Currently, we track only call/return events.
# These values can be deduced by examining the callees and callers
# vectors for each functions. Hence we *can* almost correct the
# internal time figure at print time (note that we currently don't
# track exception event processing counts). Unfortunately, there
# is currently no similar information for cumulative sub-function
# time. It would not be hard to "get all this info" at profiler
# time. Specifically, we would have to extend the tuples to keep
# counts of this in each frame, and then extend the defs of timing
# tuples to include the significant two figures. I'm a bit fearful
# that this additional feature will slow the heavily optimized
# event/time ratio (i.e., the profiler would run slower, fur a very
# low "value added" feature.)
#**************************************************************
def calibrate(self, m, verbose=0):
if self.__class__ is not Profile:
raise TypeError("Subclasses must override .calibrate().")
saved_bias = self.bias
self.bias = 0
try:
return self._calibrate_inner(m, verbose)
finally:
self.bias = saved_bias
def _calibrate_inner(self, m, verbose):
get_time = self.get_time
# Set up a test case to be run with and without profiling. Include
# lots of calls, because we're trying to quantify stopwatch overhead.
# Do not raise any exceptions, though, because we want to know
# exactly how many profile events are generated (one call event, +
# one return event, per Python-level call).
def f1(n):
for i in range(n):
x = 1
def f(m, f1=f1):
for i in range(m):
f1(100)
f(m) # warm up the cache
# elapsed_noprofile <- time f(m) takes without profiling.
t0 = get_time()
f(m)
t1 = get_time()
elapsed_noprofile = t1 - t0
if verbose:
print("elapsed time without profiling =", elapsed_noprofile)
# elapsed_profile <- time f(m) takes with profiling. The difference
# is profiling overhead, only some of which the profiler subtracts
# out on its own.
p = Profile()
t0 = get_time()
p.runctx('f(m)', globals(), locals())
t1 = get_time()
elapsed_profile = t1 - t0
if verbose:
print("elapsed time with profiling =", elapsed_profile)
# reported_time <- "CPU seconds" the profiler charged to f and f1.
total_calls = 0.0
reported_time = 0.0
for (filename, line, funcname), (cc, ns, tt, ct, callers) in \
p.timings.items():
if funcname in ("f", "f1"):
total_calls += cc
reported_time += tt
if verbose:
print("'CPU seconds' profiler reported =", reported_time)
print("total # calls =", total_calls)
if total_calls != m + 1:
raise ValueError("internal error: total calls = %d" % total_calls)
# reported_time - elapsed_noprofile = overhead the profiler wasn't
# able to measure. Divide by twice the number of calls (since there
# are two profiler events per call in this test) to get the hidden
# overhead per event.
mean = (reported_time - elapsed_noprofile) / 2.0 / total_calls
if verbose:
print("mean stopwatch overhead per profile event =", mean)
return mean
#****************************************************************************
def main():
import os
from optparse import OptionParser
usage = "profile.py [-o output_file_path] [-s sort] [-m module | scriptfile] [arg] ..."
parser = OptionParser(usage=usage)
parser.allow_interspersed_args = False
parser.add_option('-o', '--outfile', dest="outfile",
help="Save stats to <outfile>", default=None)
parser.add_option('-m', dest="module", action="store_true",
help="Profile a library module.", default=False)
parser.add_option('-s', '--sort', dest="sort",
help="Sort order when printing to stdout, based on pstats.Stats class",
default=-1)
if not sys.argv[1:]:
parser.print_usage()
sys.exit(2)
(options, args) = parser.parse_args()
sys.argv[:] = args
# The script that we're profiling may chdir, so capture the absolute path
# to the output file at startup.
if options.outfile is not None:
options.outfile = os.path.abspath(options.outfile)
if len(args) > 0:
if options.module:
import runpy
code = "run_module(modname, run_name='__main__')"
globs = {
'run_module': runpy.run_module,
'modname': args[0]
}
else:
progname = args[0]
sys.path.insert(0, os.path.dirname(progname))
with io.open_code(progname) as fp:
code = compile(fp.read(), progname, 'exec')
spec = importlib.machinery.ModuleSpec(name='__main__', loader=None,
origin=progname)
globs = {
'__spec__': spec,
'__file__': spec.origin,
'__name__': spec.name,
'__package__': None,
'__cached__': None,
}
try:
runctx(code, globs, None, options.outfile, options.sort)
except BrokenPipeError as exc:
# Prevent "Exception ignored" during interpreter shutdown.
sys.stdout = None
sys.exit(exc.errno)
else:
parser.print_usage()
return parser
# When invoked as main program, invoke the profiler on a script
if __name__ == '__main__':
main()

777
Lib/pstats.py vendored
View File

@@ -1,777 +0,0 @@
"""Class for printing reports on profiled python code."""
# Written by James Roskind
# Based on prior profile module by Sjoerd Mullender...
# which was hacked somewhat by: Guido van Rossum
# Copyright Disney Enterprises, Inc. All Rights Reserved.
# Licensed to PSF under a Contributor Agreement
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
# either express or implied. See the License for the specific language
# governing permissions and limitations under the License.
import sys
import os
import time
import marshal
import re
from enum import StrEnum, _simple_enum
from functools import cmp_to_key
from dataclasses import dataclass
__all__ = ["Stats", "SortKey", "FunctionProfile", "StatsProfile"]
@_simple_enum(StrEnum)
class SortKey:
CALLS = 'calls', 'ncalls'
CUMULATIVE = 'cumulative', 'cumtime'
FILENAME = 'filename', 'module'
LINE = 'line'
NAME = 'name'
NFL = 'nfl'
PCALLS = 'pcalls'
STDNAME = 'stdname'
TIME = 'time', 'tottime'
def __new__(cls, *values):
value = values[0]
obj = str.__new__(cls, value)
obj._value_ = value
for other_value in values[1:]:
cls._value2member_map_[other_value] = obj
obj._all_values = values
return obj
@dataclass(unsafe_hash=True)
class FunctionProfile:
ncalls: str
tottime: float
percall_tottime: float
cumtime: float
percall_cumtime: float
file_name: str
line_number: int
@dataclass(unsafe_hash=True)
class StatsProfile:
'''Class for keeping track of an item in inventory.'''
total_tt: float
func_profiles: dict[str, FunctionProfile]
class Stats:
"""This class is used for creating reports from data generated by the
Profile class. It is a "friend" of that class, and imports data either
by direct access to members of Profile class, or by reading in a dictionary
that was emitted (via marshal) from the Profile class.
The big change from the previous Profiler (in terms of raw functionality)
is that an "add()" method has been provided to combine Stats from
several distinct profile runs. Both the constructor and the add()
method now take arbitrarily many file names as arguments.
All the print methods now take an argument that indicates how many lines
to print. If the arg is a floating-point number between 0 and 1.0, then
it is taken as a decimal percentage of the available lines to be printed
(e.g., .1 means print 10% of all available lines). If it is an integer,
it is taken to mean the number of lines of data that you wish to have
printed.
The sort_stats() method now processes some additional options (i.e., in
addition to the old -1, 0, 1, or 2 that are respectively interpreted as
'stdname', 'calls', 'time', and 'cumulative'). It takes either an
arbitrary number of quoted strings or SortKey enum to select the sort
order.
For example sort_stats('time', 'name') or sort_stats(SortKey.TIME,
SortKey.NAME) sorts on the major key of 'internal function time', and on
the minor key of 'the name of the function'. Look at the two tables in
sort_stats() and get_sort_arg_defs(self) for more examples.
All methods return self, so you can string together commands like:
Stats('foo', 'goo').strip_dirs().sort_stats('calls').\
print_stats(5).print_callers(5)
"""
def __init__(self, *args, stream=None):
self.stream = stream or sys.stdout
if not len(args):
arg = None
else:
arg = args[0]
args = args[1:]
self.init(arg)
self.add(*args)
def init(self, arg):
self.all_callees = None # calc only if needed
self.files = []
self.fcn_list = None
self.total_tt = 0
self.total_calls = 0
self.prim_calls = 0
self.max_name_len = 0
self.top_level = set()
self.stats = {}
self.sort_arg_dict = {}
self.load_stats(arg)
try:
self.get_top_level_stats()
except Exception:
print("Invalid timing data %s" %
(self.files[-1] if self.files else ''), file=self.stream)
raise
def load_stats(self, arg):
if arg is None:
self.stats = {}
return
elif isinstance(arg, str):
with open(arg, 'rb') as f:
self.stats = marshal.load(f)
try:
file_stats = os.stat(arg)
arg = time.ctime(file_stats.st_mtime) + " " + arg
except: # in case this is not unix
pass
self.files = [arg]
elif hasattr(arg, 'create_stats'):
arg.create_stats()
self.stats = arg.stats
arg.stats = {}
if not self.stats:
raise TypeError("Cannot create or construct a %r object from %r"
% (self.__class__, arg))
return
def get_top_level_stats(self):
for func, (cc, nc, tt, ct, callers) in self.stats.items():
self.total_calls += nc
self.prim_calls += cc
self.total_tt += tt
if ("jprofile", 0, "profiler") in callers:
self.top_level.add(func)
if len(func_std_string(func)) > self.max_name_len:
self.max_name_len = len(func_std_string(func))
def add(self, *arg_list):
if not arg_list:
return self
for item in reversed(arg_list):
if type(self) != type(item):
item = Stats(item)
self.files += item.files
self.total_calls += item.total_calls
self.prim_calls += item.prim_calls
self.total_tt += item.total_tt
for func in item.top_level:
self.top_level.add(func)
if self.max_name_len < item.max_name_len:
self.max_name_len = item.max_name_len
self.fcn_list = None
for func, stat in item.stats.items():
if func in self.stats:
old_func_stat = self.stats[func]
else:
old_func_stat = (0, 0, 0, 0, {},)
self.stats[func] = add_func_stats(old_func_stat, stat)
return self
def dump_stats(self, filename):
"""Write the profile data to a file we know how to load back."""
with open(filename, 'wb') as f:
marshal.dump(self.stats, f)
# list the tuple indices and directions for sorting,
# along with some printable description
sort_arg_dict_default = {
"calls" : (((1,-1), ), "call count"),
"ncalls" : (((1,-1), ), "call count"),
"cumtime" : (((3,-1), ), "cumulative time"),
"cumulative": (((3,-1), ), "cumulative time"),
"filename" : (((4, 1), ), "file name"),
"line" : (((5, 1), ), "line number"),
"module" : (((4, 1), ), "file name"),
"name" : (((6, 1), ), "function name"),
"nfl" : (((6, 1),(4, 1),(5, 1),), "name/file/line"),
"pcalls" : (((0,-1), ), "primitive call count"),
"stdname" : (((7, 1), ), "standard name"),
"time" : (((2,-1), ), "internal time"),
"tottime" : (((2,-1), ), "internal time"),
}
def get_sort_arg_defs(self):
"""Expand all abbreviations that are unique."""
if not self.sort_arg_dict:
self.sort_arg_dict = dict = {}
bad_list = {}
for word, tup in self.sort_arg_dict_default.items():
fragment = word
while fragment:
if fragment in dict:
bad_list[fragment] = 0
break
dict[fragment] = tup
fragment = fragment[:-1]
for word in bad_list:
del dict[word]
return self.sort_arg_dict
def sort_stats(self, *field):
if not field:
self.fcn_list = 0
return self
if len(field) == 1 and isinstance(field[0], int):
# Be compatible with old profiler
field = [ {-1: "stdname",
0: "calls",
1: "time",
2: "cumulative"}[field[0]] ]
elif len(field) >= 2:
for arg in field[1:]:
if type(arg) != type(field[0]):
raise TypeError("Can't have mixed argument type")
sort_arg_defs = self.get_sort_arg_defs()
sort_tuple = ()
self.sort_type = ""
connector = ""
for word in field:
if isinstance(word, SortKey):
word = word.value
sort_tuple = sort_tuple + sort_arg_defs[word][0]
self.sort_type += connector + sort_arg_defs[word][1]
connector = ", "
stats_list = []
for func, (cc, nc, tt, ct, callers) in self.stats.items():
stats_list.append((cc, nc, tt, ct) + func +
(func_std_string(func), func))
stats_list.sort(key=cmp_to_key(TupleComp(sort_tuple).compare))
self.fcn_list = fcn_list = []
for tuple in stats_list:
fcn_list.append(tuple[-1])
return self
def reverse_order(self):
if self.fcn_list:
self.fcn_list.reverse()
return self
def strip_dirs(self):
oldstats = self.stats
self.stats = newstats = {}
max_name_len = 0
for func, (cc, nc, tt, ct, callers) in oldstats.items():
newfunc = func_strip_path(func)
if len(func_std_string(newfunc)) > max_name_len:
max_name_len = len(func_std_string(newfunc))
newcallers = {}
for func2, caller in callers.items():
newcallers[func_strip_path(func2)] = caller
if newfunc in newstats:
newstats[newfunc] = add_func_stats(
newstats[newfunc],
(cc, nc, tt, ct, newcallers))
else:
newstats[newfunc] = (cc, nc, tt, ct, newcallers)
old_top = self.top_level
self.top_level = new_top = set()
for func in old_top:
new_top.add(func_strip_path(func))
self.max_name_len = max_name_len
self.fcn_list = None
self.all_callees = None
return self
def calc_callees(self):
if self.all_callees:
return
self.all_callees = all_callees = {}
for func, (cc, nc, tt, ct, callers) in self.stats.items():
if not func in all_callees:
all_callees[func] = {}
for func2, caller in callers.items():
if not func2 in all_callees:
all_callees[func2] = {}
all_callees[func2][func] = caller
return
#******************************************************************
# The following functions support actual printing of reports
#******************************************************************
# Optional "amount" is either a line count, or a percentage of lines.
def eval_print_amount(self, sel, list, msg):
new_list = list
if isinstance(sel, str):
try:
rex = re.compile(sel)
except re.PatternError:
msg += " <Invalid regular expression %r>\n" % sel
return new_list, msg
new_list = []
for func in list:
if rex.search(func_std_string(func)):
new_list.append(func)
else:
count = len(list)
if isinstance(sel, float) and 0.0 <= sel < 1.0:
count = int(count * sel + .5)
new_list = list[:count]
elif isinstance(sel, int) and 0 <= sel < count:
count = sel
new_list = list[:count]
if len(list) != len(new_list):
msg += " List reduced from %r to %r due to restriction <%r>\n" % (
len(list), len(new_list), sel)
return new_list, msg
def get_stats_profile(self):
"""This method returns an instance of StatsProfile, which contains a mapping
of function names to instances of FunctionProfile. Each FunctionProfile
instance holds information related to the function's profile such as how
long the function took to run, how many times it was called, etc...
"""
func_list = self.fcn_list[:] if self.fcn_list else list(self.stats.keys())
if not func_list:
return StatsProfile(0, {})
total_tt = float(f8(self.total_tt))
func_profiles = {}
stats_profile = StatsProfile(total_tt, func_profiles)
for func in func_list:
cc, nc, tt, ct, callers = self.stats[func]
file_name, line_number, func_name = func
ncalls = str(nc) if nc == cc else (str(nc) + '/' + str(cc))
tottime = float(f8(tt))
percall_tottime = -1 if nc == 0 else float(f8(tt/nc))
cumtime = float(f8(ct))
percall_cumtime = -1 if cc == 0 else float(f8(ct/cc))
func_profile = FunctionProfile(
ncalls,
tottime, # time spent in this function alone
percall_tottime,
cumtime, # time spent in the function plus all functions that this function called,
percall_cumtime,
file_name,
line_number
)
func_profiles[func_name] = func_profile
return stats_profile
def get_print_list(self, sel_list):
width = self.max_name_len
if self.fcn_list:
stat_list = self.fcn_list[:]
msg = " Ordered by: " + self.sort_type + '\n'
else:
stat_list = list(self.stats.keys())
msg = " Random listing order was used\n"
for selection in sel_list:
stat_list, msg = self.eval_print_amount(selection, stat_list, msg)
count = len(stat_list)
if not stat_list:
return 0, stat_list
print(msg, file=self.stream)
if count < len(self.stats):
width = 0
for func in stat_list:
if len(func_std_string(func)) > width:
width = len(func_std_string(func))
return width+2, stat_list
def print_stats(self, *amount):
for filename in self.files:
print(filename, file=self.stream)
if self.files:
print(file=self.stream)
indent = ' ' * 8
for func in self.top_level:
print(indent, func_get_function_name(func), file=self.stream)
print(indent, self.total_calls, "function calls", end=' ', file=self.stream)
if self.total_calls != self.prim_calls:
print("(%d primitive calls)" % self.prim_calls, end=' ', file=self.stream)
print("in %.3f seconds" % self.total_tt, file=self.stream)
print(file=self.stream)
width, list = self.get_print_list(amount)
if list:
self.print_title()
for func in list:
self.print_line(func)
print(file=self.stream)
print(file=self.stream)
return self
def print_callees(self, *amount):
width, list = self.get_print_list(amount)
if list:
self.calc_callees()
self.print_call_heading(width, "called...")
for func in list:
if func in self.all_callees:
self.print_call_line(width, func, self.all_callees[func])
else:
self.print_call_line(width, func, {})
print(file=self.stream)
print(file=self.stream)
return self
def print_callers(self, *amount):
width, list = self.get_print_list(amount)
if list:
self.print_call_heading(width, "was called by...")
for func in list:
cc, nc, tt, ct, callers = self.stats[func]
self.print_call_line(width, func, callers, "<-")
print(file=self.stream)
print(file=self.stream)
return self
def print_call_heading(self, name_size, column_title):
print("Function ".ljust(name_size) + column_title, file=self.stream)
# print sub-header only if we have new-style callers
subheader = False
for cc, nc, tt, ct, callers in self.stats.values():
if callers:
value = next(iter(callers.values()))
subheader = isinstance(value, tuple)
break
if subheader:
print(" "*name_size + " ncalls tottime cumtime", file=self.stream)
def print_call_line(self, name_size, source, call_dict, arrow="->"):
print(func_std_string(source).ljust(name_size) + arrow, end=' ', file=self.stream)
if not call_dict:
print(file=self.stream)
return
clist = sorted(call_dict.keys())
indent = ""
for func in clist:
name = func_std_string(func)
value = call_dict[func]
if isinstance(value, tuple):
nc, cc, tt, ct = value
if nc != cc:
substats = '%d/%d' % (nc, cc)
else:
substats = '%d' % (nc,)
substats = '%s %s %s %s' % (substats.rjust(7+2*len(indent)),
f8(tt), f8(ct), name)
left_width = name_size + 1
else:
substats = '%s(%r) %s' % (name, value, f8(self.stats[func][3]))
left_width = name_size + 3
print(indent*left_width + substats, file=self.stream)
indent = " "
def print_title(self):
print(' ncalls tottime percall cumtime percall', end=' ', file=self.stream)
print('filename:lineno(function)', file=self.stream)
def print_line(self, func): # hack: should print percentages
cc, nc, tt, ct, callers = self.stats[func]
c = str(nc)
if nc != cc:
c = c + '/' + str(cc)
print(c.rjust(9), end=' ', file=self.stream)
print(f8(tt), end=' ', file=self.stream)
if nc == 0:
print(' '*8, end=' ', file=self.stream)
else:
print(f8(tt/nc), end=' ', file=self.stream)
print(f8(ct), end=' ', file=self.stream)
if cc == 0:
print(' '*8, end=' ', file=self.stream)
else:
print(f8(ct/cc), end=' ', file=self.stream)
print(func_std_string(func), file=self.stream)
class TupleComp:
"""This class provides a generic function for comparing any two tuples.
Each instance records a list of tuple-indices (from most significant
to least significant), and sort direction (ascending or descending) for
each tuple-index. The compare functions can then be used as the function
argument to the system sort() function when a list of tuples need to be
sorted in the instances order."""
def __init__(self, comp_select_list):
self.comp_select_list = comp_select_list
def compare (self, left, right):
for index, direction in self.comp_select_list:
l = left[index]
r = right[index]
if l < r:
return -direction
if l > r:
return direction
return 0
#**************************************************************************
# func_name is a triple (file:string, line:int, name:string)
def func_strip_path(func_name):
filename, line, name = func_name
return os.path.basename(filename), line, name
def func_get_function_name(func):
return func[2]
def func_std_string(func_name): # match what old profile produced
if func_name[:2] == ('~', 0):
# special case for built-in functions
name = func_name[2]
if name.startswith('<') and name.endswith('>'):
return '{%s}' % name[1:-1]
else:
return name
else:
return "%s:%d(%s)" % func_name
#**************************************************************************
# The following functions combine statistics for pairs functions.
# The bulk of the processing involves correctly handling "call" lists,
# such as callers and callees.
#**************************************************************************
def add_func_stats(target, source):
"""Add together all the stats for two profile entries."""
cc, nc, tt, ct, callers = source
t_cc, t_nc, t_tt, t_ct, t_callers = target
return (cc+t_cc, nc+t_nc, tt+t_tt, ct+t_ct,
add_callers(t_callers, callers))
def add_callers(target, source):
"""Combine two caller lists in a single list."""
new_callers = {}
for func, caller in target.items():
new_callers[func] = caller
for func, caller in source.items():
if func in new_callers:
if isinstance(caller, tuple):
# format used by cProfile
new_callers[func] = tuple(i + j for i, j in zip(caller, new_callers[func]))
else:
# format used by profile
new_callers[func] += caller
else:
new_callers[func] = caller
return new_callers
def count_calls(callers):
"""Sum the caller statistics to get total number of calls received."""
nc = 0
for calls in callers.values():
nc += calls
return nc
#**************************************************************************
# The following functions support printing of reports
#**************************************************************************
def f8(x):
return "%8.3f" % x
#**************************************************************************
# Statistics browser added by ESR, April 2001
#**************************************************************************
if __name__ == '__main__':
import cmd
try:
import readline # noqa: F401
except ImportError:
pass
class ProfileBrowser(cmd.Cmd):
def __init__(self, profile=None):
cmd.Cmd.__init__(self)
self.prompt = "% "
self.stats = None
self.stream = sys.stdout
if profile is not None:
self.do_read(profile)
def generic(self, fn, line):
args = line.split()
processed = []
for term in args:
try:
processed.append(int(term))
continue
except ValueError:
pass
try:
frac = float(term)
if frac > 1 or frac < 0:
print("Fraction argument must be in [0, 1]", file=self.stream)
continue
processed.append(frac)
continue
except ValueError:
pass
processed.append(term)
if self.stats:
getattr(self.stats, fn)(*processed)
else:
print("No statistics object is loaded.", file=self.stream)
return 0
def generic_help(self):
print("Arguments may be:", file=self.stream)
print("* An integer maximum number of entries to print.", file=self.stream)
print("* A decimal fractional number between 0 and 1, controlling", file=self.stream)
print(" what fraction of selected entries to print.", file=self.stream)
print("* A regular expression; only entries with function names", file=self.stream)
print(" that match it are printed.", file=self.stream)
def do_add(self, line):
if self.stats:
try:
self.stats.add(line)
except OSError as e:
print("Failed to load statistics for %s: %s" % (line, e), file=self.stream)
else:
print("No statistics object is loaded.", file=self.stream)
return 0
def help_add(self):
print("Add profile info from given file to current statistics object.", file=self.stream)
def do_callees(self, line):
return self.generic('print_callees', line)
def help_callees(self):
print("Print callees statistics from the current stat object.", file=self.stream)
self.generic_help()
def do_callers(self, line):
return self.generic('print_callers', line)
def help_callers(self):
print("Print callers statistics from the current stat object.", file=self.stream)
self.generic_help()
def do_EOF(self, line):
print("", file=self.stream)
return 1
def help_EOF(self):
print("Leave the profile browser.", file=self.stream)
def do_quit(self, line):
return 1
def help_quit(self):
print("Leave the profile browser.", file=self.stream)
def do_read(self, line):
if line:
try:
self.stats = Stats(line)
except OSError as err:
print(err.args[1], file=self.stream)
return
except Exception as err:
print(err.__class__.__name__ + ':', err, file=self.stream)
return
self.prompt = line + "% "
elif len(self.prompt) > 2:
line = self.prompt[:-2]
self.do_read(line)
else:
print("No statistics object is current -- cannot reload.", file=self.stream)
return 0
def help_read(self):
print("Read in profile data from a specified file.", file=self.stream)
print("Without argument, reload the current file.", file=self.stream)
def do_reverse(self, line):
if self.stats:
self.stats.reverse_order()
else:
print("No statistics object is loaded.", file=self.stream)
return 0
def help_reverse(self):
print("Reverse the sort order of the profiling report.", file=self.stream)
def do_sort(self, line):
if not self.stats:
print("No statistics object is loaded.", file=self.stream)
return
abbrevs = self.stats.get_sort_arg_defs()
if line and all((x in abbrevs) for x in line.split()):
self.stats.sort_stats(*line.split())
else:
print("Valid sort keys (unique prefixes are accepted):", file=self.stream)
for (key, value) in Stats.sort_arg_dict_default.items():
print("%s -- %s" % (key, value[1]), file=self.stream)
return 0
def help_sort(self):
print("Sort profile data according to specified keys.", file=self.stream)
print("(Typing `sort' without arguments lists valid keys.)", file=self.stream)
def complete_sort(self, text, *args):
return [a for a in Stats.sort_arg_dict_default if a.startswith(text)]
def do_stats(self, line):
return self.generic('print_stats', line)
def help_stats(self):
print("Print statistics from the current stat object.", file=self.stream)
self.generic_help()
def do_strip(self, line):
if self.stats:
self.stats.strip_dirs()
else:
print("No statistics object is loaded.", file=self.stream)
def help_strip(self):
print("Strip leading path information from filenames in the report.", file=self.stream)
def help_help(self):
print("Show help for a given command.", file=self.stream)
def postcmd(self, stop, line):
if stop:
return stop
return None
if len(sys.argv) > 1:
initprofile = sys.argv[1]
else:
initprofile = None
try:
browser = ProfileBrowser(initprofile)
for profile in sys.argv[2:]:
browser.do_add(profile)
print("Welcome to the profile statistics browser.", file=browser.stream)
browser.cmdloop()
print("Goodbye.", file=browser.stream)
except KeyboardInterrupt:
pass
# That's all, folks.

View File

@@ -1,62 +0,0 @@
import sys
import types
import unittest
# bpo-46417: Test that structseq types used by the sys module are still
# valid when Py_Finalize()/Py_Initialize() are called multiple times.
class TestStructSeq(unittest.TestCase):
# test PyTypeObject members
def check_structseq(self, obj_type):
# ob_refcnt
self.assertGreaterEqual(sys.getrefcount(obj_type), 1)
# tp_base
self.assertIsSubclass(obj_type, tuple)
# tp_bases
self.assertEqual(obj_type.__bases__, (tuple,))
# tp_dict
self.assertIsInstance(obj_type.__dict__, types.MappingProxyType)
# tp_mro
self.assertEqual(obj_type.__mro__, (obj_type, tuple, object))
# tp_name
self.assertIsInstance(type.__name__, str)
# tp_subclasses
self.assertEqual(obj_type.__subclasses__(), [])
def test_sys_attrs(self):
for attr_name in (
'flags', # FlagsType
'float_info', # FloatInfoType
'hash_info', # Hash_InfoType
'int_info', # Int_InfoType
'thread_info', # ThreadInfoType
'version_info', # VersionInfoType
):
with self.subTest(attr=attr_name):
attr = getattr(sys, attr_name)
self.check_structseq(type(attr))
def test_sys_funcs(self):
func_names = ['get_asyncgen_hooks'] # AsyncGenHooksType
if hasattr(sys, 'getwindowsversion'):
func_names.append('getwindowsversion') # WindowsVersionType
for func_name in func_names:
with self.subTest(func=func_name):
func = getattr(sys, func_name)
obj = func()
self.check_structseq(type(obj))
try:
unittest.main(
module=(
'__main__'
if __name__ == '__main__'
# Avoiding a circular import:
else sys.modules['test._test_embed_structseq']
)
)
except SystemExit as exc:
if exc.args[0] != 0:
raise
print("Tests passed")

File diff suppressed because it is too large Load Diff

View File

@@ -1,30 +0,0 @@
# gh-144503: Test that the forkserver can start when the parent process has
# a very large sys.argv. Prior to the fix, sys.argv was repr'd into the
# forkserver ``-c`` command string which could exceed the OS limit on the
# length of a single argv element (MAX_ARG_STRLEN on Linux, ~128 KiB),
# causing posix_spawn to fail and the parent to see a BrokenPipeError.
import multiprocessing
import sys
EXPECTED_LEN = 5002 # argv[0] + 5000 padding entries + sentinel
def fun():
print(f"worker:{len(sys.argv)}:{sys.argv[-1]}")
if __name__ == "__main__":
# Inflate sys.argv well past 128 KiB before the forkserver is started.
sys.argv[1:] = ["x" * 50] * 5000 + ["sentinel"]
assert len(sys.argv) == EXPECTED_LEN
ctx = multiprocessing.get_context("forkserver")
p = ctx.Process(target=fun)
p.start()
p.join()
sys.exit(p.exitcode)
else:
# This branch runs when the forkserver preloads this module as
# __mp_main__; confirm the large argv was propagated intact.
print(f"preload:{len(sys.argv)}:{sys.argv[-1]}")

View File

@@ -1,22 +0,0 @@
# gh-143706: Test that sys.argv is correctly set during main module import
# when using forkserver with __main__ preloading.
import multiprocessing
import sys
# This will be printed during module import - sys.argv should be correct here
print(f"module:{sys.argv[1:]}")
def fun():
# This will be printed when the function is called
print(f"fun:{sys.argv[1:]}")
if __name__ == "__main__":
ctx = multiprocessing.get_context("forkserver")
ctx.set_forkserver_preload(['__main__'])
fun()
p = ctx.Process(target=fun)
p.start()
p.join()

BIN
Lib/test/pstats.pck vendored

Binary file not shown.

View File

@@ -1,4 +1,5 @@
import io
import platform
import queue
import re
import subprocess
@@ -16,11 +17,17 @@ from unittest.mock import patch
if sys.platform != "android":
raise unittest.SkipTest("Android-specific")
api_level = platform.android_ver().api_level
# (name, level, fileno)
STREAM_INFO = [("stdout", "I", 1), ("stderr", "W", 2)]
# Test redirection of stdout and stderr to the Android log.
@unittest.skipIf(
api_level < 23 and platform.machine() == "aarch64",
"SELinux blocks reading logs on older ARM64 emulators"
)
class TestAndroidOutput(unittest.TestCase):
maxDiff = None
@@ -35,41 +42,31 @@ class TestAndroidOutput(unittest.TestCase):
for line in self.logcat_process.stdout:
self.logcat_queue.put(line.rstrip("\n"))
self.logcat_process.stdout.close()
self.logcat_thread = Thread(target=logcat_thread)
self.logcat_thread.start()
try:
from ctypes import CDLL, c_char_p, c_int
android_log_write = getattr(CDLL("liblog.so"), "__android_log_write")
android_log_write.argtypes = (c_int, c_char_p, c_char_p)
ANDROID_LOG_INFO = 4
from ctypes import CDLL, c_char_p, c_int
android_log_write = getattr(CDLL("liblog.so"), "__android_log_write")
android_log_write.argtypes = (c_int, c_char_p, c_char_p)
ANDROID_LOG_INFO = 4
# Separate tests using a marker line with a different tag.
tag, message = "python.test", f"{self.id()} {time()}"
android_log_write(
ANDROID_LOG_INFO, tag.encode("UTF-8"), message.encode("UTF-8"))
self.assert_log("I", tag, message, skip=True)
except:
# If setUp throws an exception, tearDown is not automatically
# called. Avoid leaving a dangling thread which would keep the
# Python process alive indefinitely.
self.tearDown()
raise
# Separate tests using a marker line with a different tag.
tag, message = "python.test", f"{self.id()} {time()}"
android_log_write(
ANDROID_LOG_INFO, tag.encode("UTF-8"), message.encode("UTF-8"))
self.assert_log("I", tag, message, skip=True, timeout=5)
def assert_logs(self, level, tag, expected, **kwargs):
for line in expected:
self.assert_log(level, tag, line, **kwargs)
def assert_log(self, level, tag, expected, *, skip=False):
deadline = time() + LOOPBACK_TIMEOUT
def assert_log(self, level, tag, expected, *, skip=False, timeout=0.5):
deadline = time() + timeout
while True:
try:
line = self.logcat_queue.get(timeout=(deadline - time()))
except queue.Empty:
raise self.failureException(
f"line not found: {expected!r}"
) from None
self.fail(f"line not found: {expected!r}")
if match := re.fullmatch(fr"(.)/{tag}: (.*)", line):
try:
self.assertEqual(level, match[1])
@@ -84,42 +81,35 @@ class TestAndroidOutput(unittest.TestCase):
self.logcat_process.wait(LOOPBACK_TIMEOUT)
self.logcat_thread.join(LOOPBACK_TIMEOUT)
# Avoid an irrelevant warning about threading._dangling.
self.logcat_thread = None
@contextmanager
def reconfigure(self, stream, **settings):
original_settings = {key: getattr(stream, key, None) for key in settings.keys()}
stream.reconfigure(**settings)
def unbuffered(self, stream):
stream.reconfigure(write_through=True)
try:
yield
finally:
stream.reconfigure(**original_settings)
stream.reconfigure(write_through=False)
# In --verbose3 mode, sys.stdout and sys.stderr are captured, so we can't
# test them directly. Detect this mode and use some temporary streams with
# the same properties.
def stream_context(self, stream_name, level):
# https://developer.android.com/ndk/reference/group/logging
prio = {"I": 4, "W": 5}[level]
stack = ExitStack()
stack.enter_context(self.subTest(stream_name))
# In --verbose3 mode, sys.stdout and sys.stderr are captured, so we can't
# test them directly. Detect this mode and use some temporary streams with
# the same properties.
stream = getattr(sys, stream_name)
native_stream = getattr(sys, f"__{stream_name}__")
if isinstance(stream, io.StringIO):
# https://developer.android.com/ndk/reference/group/logging
prio = {"I": 4, "W": 5}[level]
stack.enter_context(
patch(
f"sys.{stream_name}",
stream := TextLogStream(
prio, f"python.{stream_name}", native_stream,
TextLogStream(
prio, f"python.{stream_name}", native_stream.fileno(),
errors="backslashreplace"
),
)
)
# The tests assume the stream is initially buffered.
stack.enter_context(self.reconfigure(stream, write_through=False))
return stack
def test_str(self):
@@ -146,7 +136,7 @@ class TestAndroidOutput(unittest.TestCase):
self.assert_logs(level, tag, lines)
# Single-line messages,
with self.reconfigure(stream, write_through=True):
with self.unbuffered(stream):
write("", [])
write("a")
@@ -176,18 +166,14 @@ class TestAndroidOutput(unittest.TestCase):
# Multi-line messages. Avoid identical consecutive lines, as
# they may activate "chatty" filtering and break the tests.
#
# Additional spaces will appear in the output where necessary to
# protect leading newlines.
write("\nx", [" "])
write("\nx", [""])
write("\na\n", ["x", "a"])
write("\n", [" "])
write("\n\n", [" ", " "])
write("\n", [""])
write("b\n", ["b"])
write("c\n\n", ["c", " "])
write("c\n\n", ["c", ""])
write("d\ne", ["d"])
write("xx", [])
write("f\n\ng", ["exxf", " "])
write("f\n\ng", ["exxf", ""])
write("\n", ["g"])
# Since this is a line-based logging system, line buffering
@@ -197,17 +183,16 @@ class TestAndroidOutput(unittest.TestCase):
# However, buffering can be turned off completely if you want a
# flush after every write.
with self.reconfigure(stream, write_through=True):
write("\nx", [" ", "x"])
write("\na\n", [" ", "a"])
write("\n", [" "])
write("\n\n", [" ", " "])
with self.unbuffered(stream):
write("\nx", ["", "x"])
write("\na\n", ["", "a"])
write("\n", [""])
write("b\n", ["b"])
write("c\n\n", ["c", " "])
write("c\n\n", ["c", ""])
write("d\ne", ["d", "e"])
write("xx", ["xx"])
write("f\n\ng", ["f", " ", "g"])
write("\n", [" "])
write("f\n\ng", ["f", "", "g"])
write("\n", [""])
# "\r\n" should be translated into "\n".
write("hello\r\n", ["hello"])
@@ -327,16 +312,19 @@ class TestAndroidOutput(unittest.TestCase):
# currently use `logcat -v tag`, which shows each line as if it
# was a separate log entry, but strips a single trailing
# newline.
write(b"\nx", [" ", "x"])
write(b"\na\n", [" ", "a"])
write(b"\n", [" "])
write(b"\n\n", [" ", ""])
#
# On newer versions of Android, all three of the above tools (or
# maybe Logcat itself) will also strip any number of leading
# newlines.
write(b"\nx", ["", "x"] if api_level < 30 else ["x"])
write(b"\na\n", ["", "a"] if api_level < 30 else ["a"])
write(b"\n", [""])
write(b"b\n", ["b"])
write(b"c\n\n", ["c", ""])
write(b"d\ne", ["d", "e"])
write(b"xx", ["xx"])
write(b"f\n\ng", ["f", "", "g"])
write(b"\n", [" "])
write(b"\n", [""])
# "\r\n" should be translated into "\n".
write(b"hello\r\n", ["hello"])

View File

@@ -427,27 +427,6 @@ class BaseSockTestsMixin:
self.loop.run_until_complete(
self._basetest_datagram_recvfrom_into(server_address))
async def _basetest_datagram_recvfrom_into_wrong_size(self, server_address):
# Call sock_sendto() with a size larger than the buffer
with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as sock:
sock.setblocking(False)
buf = bytearray(5000)
data = b'\x01' * 4096
wrong_size = len(buf) + 1
await self.loop.sock_sendto(sock, data, server_address)
with self.assertRaises(ValueError):
await self.loop.sock_recvfrom_into(
sock, buf, wrong_size)
size, addr = await self.loop.sock_recvfrom_into(sock, buf)
self.assertEqual(buf[:size], data)
def test_recvfrom_into_wrong_size(self):
with test_utils.run_udp_echo_server() as server_address:
self.loop.run_until_complete(
self._basetest_datagram_recvfrom_into_wrong_size(server_address))
async def _basetest_datagram_sendto_blocking(self, server_address):
# Sad path, sock.sendto() raises BlockingIOError
# This involves patching sock.sendto() to raise BlockingIOError but
@@ -663,10 +642,6 @@ if sys.platform == 'win32':
self._basetest_datagram_send_to_non_listening_address(
recvfrom_into))
@unittest.expectedFailureIfWindows("TODO: RUSTPYTHON; AssertionError: ValueError not raised")
def test_recvfrom_into_wrong_size(self):
return super().test_recvfrom_into_wrong_size()
else:
import selectors

View File

@@ -27,7 +27,6 @@ from test.test_asyncio import utils as test_utils
MACOS = (sys.platform == 'darwin')
BUF_MULTIPLIER = 1024 if not MACOS else 64
HANDSHAKE_TIMEOUT = support.LONG_TIMEOUT
def tearDownModule():
@@ -258,12 +257,15 @@ class TestSSL(test_utils.TestCase):
await fut
async def start_server():
extras = {}
extras = dict(ssl_handshake_timeout=support.SHORT_TIMEOUT)
srv = await asyncio.start_server(
handle_client,
'127.0.0.1', 0,
family=socket.AF_INET,
ssl=sslctx,
ssl_handshake_timeout=HANDSHAKE_TIMEOUT)
**extras)
try:
srv_socks = srv.sockets
@@ -320,11 +322,14 @@ class TestSSL(test_utils.TestCase):
sock.close()
async def client(addr):
extras = {}
extras = dict(ssl_handshake_timeout=support.SHORT_TIMEOUT)
reader, writer = await asyncio.open_connection(
*addr,
ssl=client_sslctx,
server_hostname='',
ssl_handshake_timeout=HANDSHAKE_TIMEOUT)
**extras)
writer.write(A_DATA)
self.assertEqual(await reader.readexactly(2), b'OK')
@@ -344,8 +349,7 @@ class TestSSL(test_utils.TestCase):
reader, writer = await asyncio.open_connection(
sock=sock,
ssl=client_sslctx,
server_hostname='',
ssl_handshake_timeout=HANDSHAKE_TIMEOUT)
server_hostname='')
writer.write(A_DATA)
self.assertEqual(await reader.readexactly(2), b'OK')
@@ -444,7 +448,7 @@ class TestSSL(test_utils.TestCase):
*addr,
ssl=client_sslctx,
server_hostname='',
ssl_handshake_timeout=HANDSHAKE_TIMEOUT)
ssl_handshake_timeout=support.SHORT_TIMEOUT)
writer.close()
await self.wait_closed(writer)
@@ -606,7 +610,7 @@ class TestSSL(test_utils.TestCase):
extras = {}
if server_ssl:
extras = dict(ssl_handshake_timeout=HANDSHAKE_TIMEOUT)
extras = dict(ssl_handshake_timeout=support.SHORT_TIMEOUT)
f = loop.create_task(
loop.connect_accepted_socket(
@@ -655,8 +659,7 @@ class TestSSL(test_utils.TestCase):
reader, writer = await asyncio.open_connection(
*addr,
ssl=client_sslctx,
server_hostname='',
ssl_handshake_timeout=HANDSHAKE_TIMEOUT)
server_hostname='')
self.assertEqual(await reader.readline(), b'A\n')
writer.write(b'B')
@@ -1150,11 +1153,14 @@ class TestSSL(test_utils.TestCase):
await fut
async def start_server():
extras = {}
srv = await self.loop.create_server(
server_protocol_factory,
'127.0.0.1', 0,
family=socket.AF_INET,
ssl=sslctx_1)
ssl=sslctx_1,
**extras)
try:
srv_socks = srv.sockets
@@ -1204,11 +1210,14 @@ class TestSSL(test_utils.TestCase):
sock.close()
async def client(addr):
extras = {}
extras = dict(ssl_handshake_timeout=support.SHORT_TIMEOUT)
reader, writer = await asyncio.open_connection(
*addr,
ssl=client_sslctx,
server_hostname='',
ssl_handshake_timeout=HANDSHAKE_TIMEOUT)
**extras)
writer.write(A_DATA)
self.assertEqual(await reader.readexactly(2), b'OK')
@@ -1278,8 +1287,7 @@ class TestSSL(test_utils.TestCase):
reader, writer = await asyncio.open_connection(
*addr,
ssl=client_sslctx,
server_hostname='',
ssl_handshake_timeout=HANDSHAKE_TIMEOUT)
server_hostname='')
sslprotocol = writer.transport._ssl_protocol
writer.write(b'ping')
data = await reader.readexactly(4)
@@ -1391,8 +1399,7 @@ class TestSSL(test_utils.TestCase):
reader, writer = await asyncio.open_connection(
*addr,
ssl=client_sslctx,
server_hostname='',
ssl_handshake_timeout=HANDSHAKE_TIMEOUT)
server_hostname='')
writer.write(b'ping')
data = await reader.readexactly(4)
self.assertEqual(data, b'pong')
@@ -1523,8 +1530,7 @@ class TestSSL(test_utils.TestCase):
reader, writer = await asyncio.open_connection(
*addr,
ssl=client_sslctx,
server_hostname='',
ssl_handshake_timeout=HANDSHAKE_TIMEOUT)
server_hostname='')
writer.write(b'ping')
data = await reader.readexactly(4)
self.assertEqual(data, b'pong')

View File

@@ -819,48 +819,6 @@ class StreamTests(test_utils.TestCase):
self.assertEqual(msg1, b"hello world 1!\n")
self.assertEqual(msg2, b"hello world 2!\n")
@unittest.skipIf(ssl is None, 'No ssl module')
def test_start_tls_buffered_data(self):
# gh-142352: test start_tls() with buffered data
async def server_handler(client_reader, client_writer):
# Wait for TLS ClientHello to be buffered before start_tls().
await client_reader._wait_for_data('test_start_tls_buffered_data'),
self.assertTrue(client_reader._buffer)
await client_writer.start_tls(test_utils.simple_server_sslcontext())
line = await client_reader.readline()
self.assertEqual(line, b"ping\n")
client_writer.write(b"pong\n")
await client_writer.drain()
client_writer.close()
await client_writer.wait_closed()
async def client(addr):
reader, writer = await asyncio.open_connection(*addr)
await writer.start_tls(test_utils.simple_client_sslcontext())
writer.write(b"ping\n")
await writer.drain()
line = await reader.readline()
self.assertEqual(line, b"pong\n")
writer.close()
await writer.wait_closed()
async def run_test():
server = await asyncio.start_server(
server_handler, socket_helper.HOSTv4, 0)
server_addr = server.sockets[0].getsockname()
await client(server_addr)
server.close()
await server.wait_closed()
messages = []
self.loop.set_exception_handler(lambda loop, ctx: messages.append(ctx))
self.loop.run_until_complete(run_test())
self.assertEqual(messages, [])
def test_streamreader_constructor_without_loop(self):
with self.assertRaisesRegex(RuntimeError, 'no current event loop'):
asyncio.StreamReader()

View File

@@ -111,37 +111,6 @@ class SubprocessTransportTests(test_utils.TestCase):
)
transport.close()
def test_proc_exited_no_invalid_state_error_on_exit_waiters(self):
# gh-145541: when _connect_pipes hasn't completed (so
# _pipes_connected is False) and the process exits, _try_finish()
# sets the result on exit waiters. Then _call_connection_lost() must
# not call set_result() again on the same waiters.
self.loop.set_exception_handler(
lambda loop, context: self.fail(
f"unexpected exception: {context}")
)
waiter = self.loop.create_future()
transport, protocol = self.create_transport(waiter)
# Simulate a waiter registered via _wait() before the process exits.
exit_waiter = self.loop.create_future()
transport._exit_waiters.append(exit_waiter)
# _connect_pipes hasn't completed, so _pipes_connected is False.
self.assertFalse(transport._pipes_connected)
# Simulate process exit. _try_finish() will set the result on
# exit_waiter because _pipes_connected is False, and then schedule
# _call_connection_lost() because _pipes is empty (vacuously all
# disconnected). _call_connection_lost() must skip exit_waiter
# because it's already done.
transport._process_exited(6)
self.loop.run_until_complete(waiter)
self.assertEqual(exit_waiter.result(), 6)
transport.close()
class SubprocessMixin:

View File

@@ -2947,6 +2947,7 @@ class CTask_CFuture_Tests(BaseTaskTests, SetMethodsTest,
return super().test_log_destroyed_pending_task()
@unittest.skipUnless(hasattr(futures, '_CFuture') and
hasattr(tasks, '_CTask'),
'requires the C _asyncio module')
@@ -3029,6 +3030,7 @@ class PyTask_PyFuture_SubclassTests(BaseTaskTests, test_utils.TestCase):
all_tasks = staticmethod(tasks._py_all_tasks)
current_task = staticmethod(tasks._py_current_task)
@unittest.skipUnless(hasattr(tasks, '_CTask'),
'requires the C _asyncio module')
class CTask_Future_Tests(test_utils.TestCase):
@@ -3061,26 +3063,6 @@ class BaseTaskIntrospectionTests:
_enter_task = None
_leave_task = None
all_tasks = None
Task = None
def test_register_task_resurrection(self):
register_task = self._register_task
class EvilLoop:
def get_debug(self):
return False
def call_exception_handler(self, context):
register_task(context["task"])
async def coro_fn ():
pass
coro = coro_fn()
self.addCleanup(coro.close)
loop = EvilLoop()
with self.assertRaises(AttributeError):
self.Task(coro, loop=loop)
def test__register_task_1(self):
class TaskLike:
@@ -3211,7 +3193,6 @@ class PyIntrospectionTests(test_utils.TestCase, BaseTaskIntrospectionTests):
_leave_task = staticmethod(tasks._py_leave_task)
all_tasks = staticmethod(tasks._py_all_tasks)
current_task = staticmethod(tasks._py_current_task)
Task = tasks._PyTask
@unittest.skipUnless(hasattr(tasks, '_c_register_task'),
@@ -3224,12 +3205,10 @@ class CIntrospectionTests(test_utils.TestCase, BaseTaskIntrospectionTests):
_leave_task = staticmethod(tasks._c_leave_task)
all_tasks = staticmethod(tasks._c_all_tasks)
current_task = staticmethod(tasks._c_current_task)
Task = tasks._CTask
else:
_register_task = _unregister_task = _enter_task = _leave_task = None
class BaseCurrentLoopTests:
current_task = None
@@ -3719,30 +3698,6 @@ class RunCoroutineThreadsafeTests(test_utils.TestCase):
(loop, context), kwargs = callback.call_args
self.assertEqual(context['exception'], exc_context.exception)
def test_run_coroutine_threadsafe_and_cancel(self):
task = None
thread_future = None
# Use a custom task factory to capture the created Task
def task_factory(loop, coro):
nonlocal task
task = asyncio.Task(coro, loop=loop)
return task
self.addCleanup(self.loop.set_task_factory,
self.loop.get_task_factory())
async def target():
nonlocal thread_future
self.loop.set_task_factory(task_factory)
thread_future = asyncio.run_coroutine_threadsafe(asyncio.sleep(10), self.loop)
await asyncio.sleep(0)
thread_future.cancel()
self.loop.run_until_complete(target())
self.assertTrue(task.cancelled())
self.assertTrue(thread_future.cancelled())
class SleepTests(test_utils.TestCase):
def setUp(self):

View File

@@ -77,30 +77,6 @@ class PipeTests(unittest.TestCase):
else:
raise RuntimeError('expected ERROR_INVALID_HANDLE')
def test_pipe_handle_close_after_external_close(self):
# gh-149388: PipeHandle.close() must clear ``_handle`` before calling
# CloseHandle so that if CloseHandle raises on a stale handle the
# PipeHandle is still marked closed and __del__ / subsequent close()
# calls are silent no-ops.
h1, h2 = windows_utils.pipe(overlapped=(False, False))
try:
p = windows_utils.PipeHandle(h1)
# Simulate an external close of the underlying handle (e.g.
# a finalizer race or a concurrent close on the same object).
_winapi.CloseHandle(p.handle)
# First close() still propagates the OSError from CloseHandle,
# but must clear ``_handle`` first.
with self.assertRaises(OSError):
p.close()
self.assertIsNone(p.handle)
# Second close() is a no-op.
p.close()
# __del__ through GC is also a silent no-op — no unraisable.
del p
support.gc_collect()
finally:
_winapi.CloseHandle(h2)
class PopenTests(unittest.TestCase):
@@ -153,25 +129,5 @@ class PopenTests(unittest.TestCase):
pass
class OverlappedRefleakTests(unittest.TestCase):
def test_wsasendto_failure(self):
ov = _overlapped.Overlapped()
buf = bytearray(4096)
with self.assertRaises(OSError):
ov.WSASendTo(0x1234, buf, 0, ("127.0.0.1", 1))
def test_wsarecvfrom_failure(self):
ov = _overlapped.Overlapped()
with self.assertRaises(OSError):
ov.WSARecvFrom(0x1234, 1024, 0)
def test_wsarecvfrominto_failure(self):
ov = _overlapped.Overlapped()
buf = bytearray(4096)
with self.assertRaises(OSError):
ov.WSARecvFromInto(0x1234, buf, len(buf), 0)
if __name__ == '__main__':
unittest.main()

View File

@@ -222,7 +222,6 @@ class BinASCIITest(unittest.TestCase):
assertInvalidLength(b'a' * (4 * 87 + 1))
assertInvalidLength(b'A\tB\nC ??DE') # only 5 valid characters
@unittest.expectedFailure # TODO: RUSTPYTHON; AssertionError: Error not raised by a2b_uu
def test_uu(self):
MAX_UU = 45
for backtick in (True, False):
@@ -243,10 +242,6 @@ class BinASCIITest(unittest.TestCase):
self.assertEqual(binascii.a2b_uu(b"\xff"), b"\x00"*31)
self.assertRaises(binascii.Error, binascii.a2b_uu, b"\xff\x00")
self.assertRaises(binascii.Error, binascii.a2b_uu, b"!!!!")
self.assertRaises(binascii.Error, binascii.a2b_uu,
self.type2test(b""))
self.assertRaises(binascii.Error, binascii.a2b_uu,
self.type2test(b"#86)C")[:0])
self.assertRaises(binascii.Error, binascii.b2a_uu, 46*b"!")
# Issue #7701 (crash on a pydebug build)
@@ -445,7 +440,6 @@ class BinASCIITest(unittest.TestCase):
self.assertConversion(binary, converted, restored,
quotetabs=quotetabs, istext=istext, header=header)
@unittest.expectedFailure # TODO: RUSTPYTHON; AssertionError: Error not raised by a2b_uu
def test_empty_string(self):
# A test for SF bug #1022953. Make sure SystemError is not raised.
empty = self.type2test(b'')
@@ -455,9 +449,6 @@ class BinASCIITest(unittest.TestCase):
binascii.crc_hqx(empty, 0)
continue
f = getattr(binascii, func)
if func == 'a2b_uu':
self.assertRaises(binascii.Error, f, empty)
continue
try:
f(empty)
except Exception as err:

View File

@@ -293,27 +293,6 @@ class BuiltinTest(ComplexesAreIdenticalMixin, unittest.TestCase):
self.assertEqual(overridden_outputs, ['all', 'any', 'tuple'])
def test_builtin_call_async_genexpr_no_crash(self):
async def f_all():
return all(await 2 for _ in [])
async def f_any():
return any(await 2 for _ in [])
async def f_tuple():
return tuple(await 2 for _ in [])
async def f_list():
return list(await 2 for _ in [])
async def f_set():
return set(await 2 for _ in [])
for f in (f_all, f_any, f_tuple, f_list, f_set):
with self.subTest(func=f.__name__):
with self.assertRaises(TypeError):
run_yielding_async_fn(f)
def test_ascii(self):
self.assertEqual(ascii(''), '\'\'')
self.assertEqual(ascii(0), '0')
@@ -627,7 +606,7 @@ class BuiltinTest(ComplexesAreIdenticalMixin, unittest.TestCase):
exec(co, glob)
self.assertEqual(type(glob['ticker']()), AsyncGeneratorType)
@unittest.expectedFailure # TODO: RUSTPYTHON; AssertionError: <_ast.Name object at 0xb40000731e3d1360> is not an instance of <class '_ast.Constant'>
@unittest.expectedFailure # TODO: RUSTPYTHON; AssertionError: <_ast.Name object at 0xb40000731e3d1360> is not an instance of <class '_ast.Constant'>
def test_compile_ast(self):
args = ("a*__debug__", "f.py", "exec")
raw = compile(*args, flags = ast.PyCF_ONLY_AST).body[0]
@@ -991,7 +970,7 @@ class BuiltinTest(ComplexesAreIdenticalMixin, unittest.TestCase):
self.assertRaisesRegex(NameError, "name 'superglobal' is not defined",
eval, code, ns)
@unittest.expectedFailure # TODO: RUSTPYTHON; wrong error message
@unittest.expectedFailure # TODO: RUSTPYTHON; wrong error message
def test_exec_builtins_mapping_import(self):
code = compile("import foo.bar", "test", "exec")
ns = {'__builtins__': types.MappingProxyType({})}
@@ -1000,7 +979,7 @@ class BuiltinTest(ComplexesAreIdenticalMixin, unittest.TestCase):
exec(code, ns)
self.assertEqual(ns['foo'], ('foo.bar', ns, ns, None, 0))
@unittest.expectedFailure # TODO: RUSTPYTHON; AssertionError: AttributeError not raised by eval
@unittest.expectedFailure # TODO: RUSTPYTHON; AssertionError: AttributeError not raised by eval
def test_eval_builtins_mapping_reduce(self):
# list_iterator.__reduce__() calls _PyEval_GetBuiltin("iter")
code = compile("x.__reduce__()", "test", "eval")

View File

@@ -3,23 +3,17 @@
# See test_cmd_line_script.py for testing of script execution
import os
import re
import subprocess
import sys
import sysconfig
import tempfile
import textwrap
import unittest
from test import support
from test.support import os_helper
from test.support import force_not_colorized
from test.support import threading_helper
from test.support.script_helper import (
spawn_python, kill_python, assert_python_ok, assert_python_failure,
interpreter_requires_environment
)
from textwrap import dedent
if not support.has_subprocess_support:
raise unittest.SkipTest("test module requires subprocess")
@@ -39,13 +33,11 @@ class CmdLineTest(unittest.TestCase):
def verify_valid_flag(self, cmd_line):
rc, out, err = assert_python_ok(cmd_line)
if out != b'':
self.assertEndsWith(out, b'\n')
self.assertTrue(out == b'' or out.endswith(b'\n'))
self.assertNotIn(b'Traceback', out)
self.assertNotIn(b'Traceback', err)
return out
@support.cpython_only
def test_help(self):
self.verify_valid_flag('-h')
self.verify_valid_flag('-?')
@@ -56,28 +48,20 @@ class CmdLineTest(unittest.TestCase):
self.assertNotIn(b'-X dev', out)
self.assertLess(len(lines), 50)
@support.cpython_only
# TODO: RUSTPYTHON
@unittest.expectedFailure
def test_help_env(self):
out = self.verify_valid_flag('--help-env')
self.assertIn(b'PYTHONHOME', out)
# Env vars in each section should be sorted alphabetically
# (ignoring underscores so PYTHON_FOO and PYTHONFOO intermix naturally)
sort_key = lambda name: name.replace(b'_', b'').lower()
sections = out.split(b'These variables have equivalent')
for section in sections:
envvars = re.findall(rb'^(PYTHON\w+)', section, re.MULTILINE)
self.assertEqual(envvars, sorted(envvars, key=sort_key),
"env vars should be sorted alphabetically")
@support.cpython_only
# TODO: RUSTPYTHON
@unittest.expectedFailure
def test_help_xoptions(self):
out = self.verify_valid_flag('--help-xoptions')
self.assertIn(b'-X dev', out)
options = re.findall(rb'^-X (\w+)', out, re.MULTILINE)
self.assertEqual(options, sorted(options),
"options should be sorted alphabetically")
@support.cpython_only
# TODO: RUSTPYTHON
@unittest.expectedFailure
def test_help_all(self):
out = self.verify_valid_flag('--help-all')
lines = out.splitlines()
@@ -96,13 +80,12 @@ class CmdLineTest(unittest.TestCase):
def test_site_flag(self):
self.verify_valid_flag('-S')
@support.cpython_only
def test_version(self):
version = ('Python %d.%d' % sys.version_info[:2]).encode("ascii")
for switch in '-V', '--version', '-VV':
rc, out, err = assert_python_ok(switch)
self.assertNotStartsWith(err, version)
self.assertStartsWith(out, version)
self.assertFalse(err.startswith(version))
self.assertTrue(out.startswith(version))
def test_verbose(self):
# -v causes imports to write to stderr. If the write to
@@ -162,7 +145,8 @@ class CmdLineTest(unittest.TestCase):
else:
self.assertEqual(err, b'')
@support.cpython_only
# TODO: RUSTPYTHON
@unittest.expectedFailure
def test_xoption_frozen_modules(self):
tests = {
('=on', 'FrozenImporter'),
@@ -177,18 +161,6 @@ class CmdLineTest(unittest.TestCase):
res = assert_python_ok(*cmd)
self.assertRegex(res.out.decode('utf-8'), expected)
@support.cpython_only
def test_env_var_frozen_modules(self):
tests = {
('on', 'FrozenImporter'),
('off', 'SourceFileLoader'),
}
for raw, expected in tests:
cmd = ['-c', 'import os; print(os.__spec__.loader, end="")']
with self.subTest(raw):
res = assert_python_ok(*cmd, PYTHON_FROZEN_MODULES=raw)
self.assertRegex(res.out.decode('utf-8'), expected)
def test_run_module(self):
# Test expected operation of the '-m' switch
# Switch needs an argument
@@ -201,7 +173,6 @@ class CmdLineTest(unittest.TestCase):
# All good if module is located and run successfully
assert_python_ok('-m', 'timeit', '-n', '1')
@unittest.expectedFailureIf(support.is_android, "TODO: RUSTPYTHON")
def test_run_module_bug1764407(self):
# -m and -i need to play well together
# Runs the timeit module and checks the __main__
@@ -213,14 +184,6 @@ class CmdLineTest(unittest.TestCase):
self.assertTrue(data.find(b'1 loop') != -1)
self.assertTrue(data.find(b'__main__.Timer') != -1)
@support.cpython_only
def test_null_byte_in_interactive_mode(self):
# gh-140594: Fix an out of bounds read when a single NUL character
# is read from the standard input in interactive mode.
proc = spawn_python('-i')
proc.communicate(b'\x00', timeout=support.SHORT_TIMEOUT)
self.assertEqual(proc.returncode, 0)
def test_relativedir_bug46421(self):
# Test `python -m unittest` with a relative directory beginning with ./
# Note: We have to switch to the project's top module's directory, as per
@@ -259,7 +222,8 @@ class CmdLineTest(unittest.TestCase):
# command line, but how subprocess does decode bytes to unicode. Python
# doesn't decode the command line because Windows provides directly the
# arguments as unicode (using wmain() instead of main()).
@unittest.expectedFailure # TODO: RUSTPYTHON
# TODO: RUSTPYTHON
@unittest.expectedFailure
@unittest.skipIf(sys.platform == 'win32',
'Windows has a native unicode API')
def test_undecodable_code(self):
@@ -295,7 +259,8 @@ class CmdLineTest(unittest.TestCase):
if not stdout.startswith(pattern):
raise AssertionError("%a doesn't start with %a" % (stdout, pattern))
@unittest.expectedFailure # TODO: RUSTPYTHON
# TODO: RUSTPYTHON
@unittest.expectedFailure
@unittest.skipIf(sys.platform == 'win32',
'Windows has a native unicode API')
def test_invalid_utf8_arg(self):
@@ -307,10 +272,12 @@ class CmdLineTest(unittest.TestCase):
# Test with default config, in the C locale, in the Python UTF-8 Mode.
code = 'import sys, os; s=os.fsencode(sys.argv[1]); print(ascii(s))'
# TODO: RUSTPYTHON
def run_default(arg):
cmd = [sys.executable, '-c', code, arg]
return subprocess.run(cmd, stdout=subprocess.PIPE, text=True)
# TODO: RUSTPYTHON
def run_c_locale(arg):
cmd = [sys.executable, '-c', code, arg]
env = dict(os.environ)
@@ -318,6 +285,7 @@ class CmdLineTest(unittest.TestCase):
return subprocess.run(cmd, stdout=subprocess.PIPE,
text=True, env=env)
# TODO: RUSTPYTHON
def run_utf8_mode(arg):
cmd = [sys.executable, '-X', 'utf8', '-c', code, arg]
return subprocess.run(cmd, stdout=subprocess.PIPE, text=True)
@@ -362,8 +330,6 @@ class CmdLineTest(unittest.TestCase):
self.assertEqual(stdout, expected)
self.assertEqual(p.returncode, 0)
@unittest.skipIf(os.environ.get("PYTHONUNBUFFERED", "0") != "0",
"Python stdio buffering is disabled.")
def test_non_interactive_output_buffering(self):
code = textwrap.dedent("""
import sys
@@ -403,7 +369,7 @@ class CmdLineTest(unittest.TestCase):
p.stdin.flush()
data, rc = _kill_python_and_exit_code(p)
self.assertEqual(rc, 0)
self.assertStartsWith(data, b'x')
self.assertTrue(data.startswith(b'x'), data)
def test_large_PYTHONPATH(self):
path1 = "ABCDE" * 100
@@ -440,7 +406,8 @@ class CmdLineTest(unittest.TestCase):
# for empty and unset PYTHONPATH
self.assertEqual(out1, out2)
@unittest.expectedFailure # TODO: RUSTPYTHON
# TODO: RUSTPYTHON
@unittest.expectedFailure
def test_displayhook_unencodable(self):
for encoding in ('ascii', 'latin-1', 'utf-8'):
env = os.environ.copy()
@@ -508,7 +475,6 @@ class CmdLineTest(unittest.TestCase):
self.assertRegex(err.decode('ascii', 'ignore'), 'SyntaxError')
self.assertEqual(b'', out)
@unittest.expectedFailure # TODO: RUSTPYTHON
def test_stdout_flush_at_shutdown(self):
# Issue #5319: if stdout.flush() fails at shutdown, an error should
# be printed out.
@@ -520,9 +486,8 @@ class CmdLineTest(unittest.TestCase):
rc, out, err = assert_python_failure('-c', code)
self.assertEqual(b'', out)
self.assertEqual(120, rc)
self.assertIn(b'Exception ignored while flushing sys.stdout:\n'
b'OSError: '.replace(b'\n', os.linesep.encode()),
err)
self.assertRegex(err.decode('ascii', 'ignore'),
'Exception ignored in.*\nOSError: .*')
def test_closed_stdout(self):
# Issue #13444: if stdout has been explicitly closed, we should
@@ -560,19 +525,23 @@ class CmdLineTest(unittest.TestCase):
self.assertEqual(err, b'')
self.assertEqual(p.returncode, 42)
@unittest.expectedFailure # TODO: RUSTPYTHON
# TODO: RUSTPYTHON
@unittest.expectedFailure
def test_no_stdin(self):
self._test_no_stdio(['stdin'])
@unittest.expectedFailure # TODO: RUSTPYTHON
# TODO: RUSTPYTHON
@unittest.expectedFailure
def test_no_stdout(self):
self._test_no_stdio(['stdout'])
@unittest.expectedFailure # TODO: RUSTPYTHON
# TODO: RUSTPYTHON
@unittest.expectedFailure
def test_no_stderr(self):
self._test_no_stdio(['stderr'])
@unittest.expectedFailure # TODO: RUSTPYTHON
# TODO: RUSTPYTHON
@unittest.expectedFailure
def test_no_std_streams(self):
self._test_no_stdio(['stdin', 'stdout', 'stderr'])
@@ -624,7 +593,8 @@ class CmdLineTest(unittest.TestCase):
print("del sys.modules['__main__']", file=script)
assert_python_ok(filename)
@support.cpython_only
# TODO: RUSTPYTHON
@unittest.expectedFailure
def test_unknown_options(self):
rc, out, err = assert_python_failure('-E', '-z')
self.assertIn(b'Unknown option: -z', err)
@@ -671,7 +641,6 @@ class CmdLineTest(unittest.TestCase):
cwd=tmpdir)
self.assertEqual(out.strip(), b"ok")
@unittest.expectedFailure # TODO: RUSTPYTHON
def test_sys_flags_set(self):
# Issue 31845: a startup refactoring broke reading flags from env vars
for value, expected in (("", 0), ("1", 1), ("text", 1), ("2", 2)):
@@ -681,19 +650,22 @@ class CmdLineTest(unittest.TestCase):
PYTHONDONTWRITEBYTECODE=value,
PYTHONVERBOSE=value,
)
expected_bool = int(bool(value))
dont_write_bytecode = int(bool(value))
code = (
"import sys; "
"sys.stderr.write(str(sys.flags)); "
f"""sys.exit(not (
sys.flags.optimize == sys.flags.verbose == {expected}
and sys.flags.debug == sys.flags.dont_write_bytecode == {expected_bool}
sys.flags.debug == sys.flags.optimize ==
sys.flags.verbose ==
{expected}
and sys.flags.dont_write_bytecode == {dont_write_bytecode}
))"""
)
with self.subTest(envar_value=value):
assert_python_ok('-c', code, **env_vars)
@unittest.expectedFailure # TODO: RUSTPYTHON
# TODO: RUSTPYTHON
@unittest.expectedFailure
def test_set_pycache_prefix(self):
# sys.pycache_prefix can be set from either -X pycache_prefix or
# PYTHONPYCACHEPREFIX env var, with the former taking precedence.
@@ -739,7 +711,6 @@ class CmdLineTest(unittest.TestCase):
self.assertEqual(proc.returncode, 0, proc)
return proc.stdout.rstrip()
@support.cpython_only
def test_xdev(self):
# sys.flags.dev_mode
code = "import sys; print(sys.flags.dev_mode)"
@@ -776,24 +747,22 @@ class CmdLineTest(unittest.TestCase):
# Memory allocator debug hooks
try:
import _testinternalcapi # noqa: F401
import _testcapi
except ImportError:
pass
else:
code = "import _testinternalcapi; print(_testinternalcapi.pymem_getallocatorsname())"
code = "import _testcapi; print(_testcapi.pymem_getallocatorsname())"
with support.SuppressCrashReport():
out = self.run_xdev("-c", code, check_exitcode=False)
if support.with_pymalloc():
alloc_name = "pymalloc_debug"
elif support.Py_GIL_DISABLED:
alloc_name = "mimalloc_debug"
else:
alloc_name = "malloc_debug"
self.assertEqual(out, alloc_name)
# Faulthandler
try:
import faulthandler # noqa: F401
import faulthandler
except ImportError:
pass
else:
@@ -822,7 +791,8 @@ class CmdLineTest(unittest.TestCase):
self.assertEqual(proc.returncode, 0, proc)
return proc.stdout.rstrip()
@unittest.expectedFailure # TODO: RUSTPYTHON
# TODO: RUSTPYTHON
@unittest.expectedFailure
def test_warnings_filter_precedence(self):
expected_filters = ("error::BytesWarning "
"once::UserWarning "
@@ -845,7 +815,7 @@ class CmdLineTest(unittest.TestCase):
self.assertEqual(out, expected_filters)
def check_pythonmalloc(self, env_var, name):
code = 'import _testinternalcapi; print(_testinternalcapi.pymem_getallocatorsname())'
code = 'import _testcapi; print(_testcapi.pymem_getallocatorsname())'
env = dict(os.environ)
env.pop('PYTHONDEVMODE', None)
if env_var is not None:
@@ -861,16 +831,12 @@ class CmdLineTest(unittest.TestCase):
self.assertEqual(proc.stdout.rstrip(), name)
self.assertEqual(proc.returncode, 0)
@support.cpython_only
# TODO: RUSTPYTHON
@unittest.expectedFailure
def test_pythonmalloc(self):
# Test the PYTHONMALLOC environment variable
malloc = not support.Py_GIL_DISABLED
pymalloc = support.with_pymalloc()
mimalloc = support.with_mimalloc()
if support.Py_GIL_DISABLED:
default_name = 'mimalloc_debug' if support.Py_DEBUG else 'mimalloc'
default_name_debug = 'mimalloc_debug'
elif pymalloc:
if pymalloc:
default_name = 'pymalloc_debug' if support.Py_DEBUG else 'pymalloc'
default_name_debug = 'pymalloc_debug'
else:
@@ -880,28 +846,21 @@ class CmdLineTest(unittest.TestCase):
tests = [
(None, default_name),
('debug', default_name_debug),
('malloc', 'malloc'),
('malloc_debug', 'malloc_debug'),
]
if malloc:
tests.extend([
('malloc', 'malloc'),
('malloc_debug', 'malloc_debug'),
])
if pymalloc:
tests.extend((
('pymalloc', 'pymalloc'),
('pymalloc_debug', 'pymalloc_debug'),
))
if mimalloc:
tests.extend((
('mimalloc', 'mimalloc'),
('mimalloc_debug', 'mimalloc_debug'),
))
for env_var, name in tests:
with self.subTest(env_var=env_var, name=name):
self.check_pythonmalloc(env_var, name)
@unittest.expectedFailure # TODO: RUSTPYTHON
# TODO: RUSTPYTHON
@unittest.expectedFailure
def test_pythondevmode_env(self):
# Test the PYTHONDEVMODE environment variable
code = "import sys; print(sys.flags.dev_mode)"
@@ -920,138 +879,6 @@ class CmdLineTest(unittest.TestCase):
self.assertEqual(proc.stdout.rstrip(), 'True')
self.assertEqual(proc.returncode, 0, proc)
@unittest.expectedFailure # TODO: RUSTPYTHON
def test_python_gil(self):
cases = [
# (env, opt, expected, msg)
('1', None, '1', "PYTHON_GIL=1"),
(None, '1', '1', "-X gil=1"),
]
if support.Py_GIL_DISABLED:
cases.extend(
[
(None, None, 'None', "no options set"),
('0', None, '0', "PYTHON_GIL=0"),
('1', '0', '0', "-X gil=0 overrides PYTHON_GIL=1"),
(None, '0', '0', "-X gil=0"),
]
)
else:
cases.extend(
[
(None, None, '1', '-X gil=0 (unsupported by this build)'),
('1', None, '1', 'PYTHON_GIL=0 (unsupported by this build)'),
]
)
code = "import sys; print(sys.flags.gil)"
environ = dict(os.environ)
for env, opt, expected, msg in cases:
with self.subTest(msg, env=env, opt=opt):
environ.pop('PYTHON_GIL', None)
if env is not None:
environ['PYTHON_GIL'] = env
extra_args = []
if opt is not None:
extra_args = ['-X', f'gil={opt}']
proc = subprocess.run([sys.executable, *extra_args, '-c', code],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
text=True, env=environ)
self.assertEqual(proc.returncode, 0, proc)
self.assertEqual(proc.stdout.rstrip(), expected)
self.assertEqual(proc.stderr, '')
def test_python_asyncio_debug(self):
code = "import asyncio; print(asyncio.new_event_loop().get_debug())"
rc, out, err = assert_python_ok('-c', code, PYTHONASYNCIODEBUG='1')
self.assertIn(b'True', out)
@unittest.skipUnless(sysconfig.get_config_var('Py_TRACE_REFS'), "Requires --with-trace-refs build option")
def test_python_dump_refs(self):
code = 'import sys; sys._clear_internal_caches()'
rc, out, err = assert_python_ok('-c', code, PYTHONDUMPREFS='1')
self.assertEqual(rc, 0)
@unittest.skipUnless(sysconfig.get_config_var('Py_TRACE_REFS'), "Requires --with-trace-refs build option")
def test_python_dump_refs_file(self):
with tempfile.NamedTemporaryFile() as dump_file:
code = 'import sys; sys._clear_internal_caches()'
rc, out, err = assert_python_ok('-c', code, PYTHONDUMPREFSFILE=dump_file.name)
self.assertEqual(rc, 0)
with open(dump_file.name, 'r') as file:
contents = file.read()
self.assertIn('Remaining objects', contents)
@unittest.expectedFailureIf(sys.platform == "darwin", "TODO: RUSTPYTHON")
@unittest.skipUnless(sys.platform == 'darwin', 'PYTHONEXECUTABLE only works on macOS')
def test_python_executable(self):
code = 'import sys; print(sys.executable)'
expected = "/busr/bbin/bpython"
rc, out, err = assert_python_ok('-c', code, PYTHONEXECUTABLE=expected)
self.assertIn(expected.encode(), out)
@unittest.expectedFailureIf(support.MS_WINDOWS, "TODO: RUSTPYTHON")
@unittest.skipUnless(support.MS_WINDOWS, 'Test only applicable on Windows')
def test_python_legacy_windows_fs_encoding(self):
code = "import sys; print(sys.getfilesystemencoding())"
expected = 'mbcs'
rc, out, err = assert_python_ok('-c', code, PYTHONLEGACYWINDOWSFSENCODING='1')
self.assertIn(expected.encode(), out)
@unittest.expectedFailureIf(support.MS_WINDOWS, "TODO: RUSTPYTHON")
@unittest.skipUnless(support.MS_WINDOWS, 'Test only applicable on Windows')
def test_python_legacy_windows_stdio(self):
# Test that _WindowsConsoleIO is used when PYTHONLEGACYWINDOWSSTDIO
# is not set.
# We cannot use PIPE becase it prevents creating new console.
# So we use exit code.
code = "import sys; sys.exit(type(sys.stdout.buffer.raw).__name__ != '_WindowsConsoleIO')"
env = os.environ.copy()
env["PYTHONLEGACYWINDOWSSTDIO"] = ""
p = subprocess.run([sys.executable, "-c", code],
creationflags=subprocess.CREATE_NEW_CONSOLE,
env=env)
self.assertEqual(p.returncode, 0)
# Then test that FIleIO is used when PYTHONLEGACYWINDOWSSTDIO is set.
code = "import sys; sys.exit(type(sys.stdout.buffer.raw).__name__ != 'FileIO')"
env["PYTHONLEGACYWINDOWSSTDIO"] = "1"
p = subprocess.run([sys.executable, "-c", code],
creationflags=subprocess.CREATE_NEW_CONSOLE,
env=env)
self.assertEqual(p.returncode, 0)
@unittest.expectedFailure # TODO: RUSTPYTHON
@unittest.skipIf("-fsanitize" in sysconfig.get_config_vars().get('PY_CFLAGS', ()),
"PYTHONMALLOCSTATS doesn't work with ASAN")
def test_python_malloc_stats(self):
code = "pass"
rc, out, err = assert_python_ok('-c', code, PYTHONMALLOCSTATS='1')
self.assertIn(b'Small block threshold', err)
def test_python_user_base(self):
code = "import site; print(site.USER_BASE)"
expected = "/custom/userbase"
rc, out, err = assert_python_ok('-c', code, PYTHONUSERBASE=expected)
self.assertIn(expected.encode(), out)
def test_python_basic_repl(self):
# Currently this only tests that the env var is set. See test_pyrepl.test_python_basic_repl.
code = "import os; print('PYTHON_BASIC_REPL' in os.environ)"
expected = "True"
rc, out, err = assert_python_ok('-c', code, PYTHON_BASIC_REPL='1')
self.assertIn(expected.encode(), out)
@unittest.skipUnless(sysconfig.get_config_var('HAVE_PERF_TRAMPOLINE'), "Requires HAVE_PERF_TRAMPOLINE support")
def test_python_perf_jit_support(self):
code = "import sys; print(sys.is_stack_trampoline_active())"
expected = "True"
rc, out, err = assert_python_ok('-c', code, PYTHON_PERF_JIT_SUPPORT='1')
self.assertIn(expected.encode(), out)
@unittest.skipUnless(sys.platform == 'win32',
'bpo-32457 only applies on Windows')
def test_argv0_normalization(self):
@@ -1064,15 +891,16 @@ class CmdLineTest(unittest.TestCase):
self.assertEqual(proc.returncode, 0, proc)
self.assertEqual(proc.stdout.strip(), b'0')
@support.cpython_only
# TODO: RUSTPYTHON
@unittest.expectedFailure
def test_parsing_error(self):
args = [sys.executable, '-I', '--unknown-option']
proc = subprocess.run(args,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
text=True)
err_msg = "Unknown option: --unknown-option\nusage: "
self.assertStartsWith(proc.stderr, err_msg)
err_msg = "unknown option --unknown-option\nusage: "
self.assertTrue(proc.stderr.startswith(err_msg), proc.stderr)
self.assertNotEqual(proc.returncode, 0)
def test_int_max_str_digits(self):
@@ -1087,8 +915,11 @@ class CmdLineTest(unittest.TestCase):
assert_python_failure('-c', code, PYTHONINTMAXSTRDIGITS='foo')
assert_python_failure('-c', code, PYTHONINTMAXSTRDIGITS='100')
def res2int(res):
out = res.out.strip().decode("utf-8")
return tuple(int(i) for i in out.split())
res = assert_python_ok('-c', code)
res2int = self.res2int
current_max = sys.get_int_max_str_digits()
self.assertEqual(res2int(res), (current_max, current_max))
res = assert_python_ok('-X', 'int_max_str_digits=0', '-c', code)
@@ -1108,181 +939,6 @@ class CmdLineTest(unittest.TestCase):
)
self.assertEqual(res2int(res), (6000, 6000))
@unittest.expectedFailure # TODO: RUSTPYTHON
def test_cmd_dedent(self):
# test that -c auto-dedents its arguments
test_cases = [
(
"""
print('space-auto-dedent')
""",
"space-auto-dedent",
),
(
dedent(
"""
^^^print('tab-auto-dedent')
"""
).replace("^", "\t"),
"tab-auto-dedent",
),
(
dedent(
"""
^^if 1:
^^^^print('mixed-auto-dedent-1')
^^print('mixed-auto-dedent-2')
"""
).replace("^", "\t \t"),
"mixed-auto-dedent-1\nmixed-auto-dedent-2",
),
(
'''
data = """$
this data has an empty newline above and a newline with spaces below $
$
"""$
if 1: $
print(repr(data))$
'''.replace(
"$", ""
),
# Note: entirely blank lines are normalized to \n, even if they
# are part of a data string. This is consistent with
# textwrap.dedent behavior, but might not be intuitive.
"'\\n\\nthis data has an empty newline above and a newline with spaces below \\n\\n'",
),
(
'',
'',
),
(
' \t\n\t\n \t\t\t \t\t \t\n\t\t \n\n\n\t\t\t ',
'',
),
]
for code, expected in test_cases:
# Run the auto-dedent case
args1 = sys.executable, '-c', code
proc1 = subprocess.run(args1, stdout=subprocess.PIPE)
self.assertEqual(proc1.returncode, 0, proc1)
output1 = proc1.stdout.strip().decode(encoding='utf-8')
# Manually dedent beforehand, check the result is the same.
args2 = sys.executable, '-c', dedent(code)
proc2 = subprocess.run(args2, stdout=subprocess.PIPE)
self.assertEqual(proc2.returncode, 0, proc2)
output2 = proc2.stdout.strip().decode(encoding='utf-8')
self.assertEqual(output1, output2)
self.assertEqual(output1.replace('\r\n', '\n'), expected)
def test_cmd_dedent_failcase(self):
# Mixing tabs and spaces is not allowed
from textwrap import dedent
template = dedent(
'''
-+if 1:
+-++ print('will fail')
''')
code = template.replace('-', ' ').replace('+', '\t')
assert_python_failure('-c', code)
code = template.replace('-', '\t').replace('+', ' ')
assert_python_failure('-c', code)
@unittest.expectedFailure # TODO: RUSTPYTHON
def test_cpu_count(self):
code = "import os; print(os.cpu_count(), os.process_cpu_count())"
res = assert_python_ok('-X', 'cpu_count=4321', '-c', code)
self.assertEqual(self.res2int(res), (4321, 4321))
res = assert_python_ok('-c', code, PYTHON_CPU_COUNT='1234')
self.assertEqual(self.res2int(res), (1234, 1234))
def test_cpu_count_default(self):
code = "import os; print(os.cpu_count(), os.process_cpu_count())"
res = assert_python_ok('-X', 'cpu_count=default', '-c', code)
self.assertEqual(self.res2int(res), (os.cpu_count(), os.process_cpu_count()))
res = assert_python_ok('-X', 'cpu_count=default', '-c', code, PYTHON_CPU_COUNT='1234')
self.assertEqual(self.res2int(res), (os.cpu_count(), os.process_cpu_count()))
res = assert_python_ok('-c', code, PYTHON_CPU_COUNT='default')
self.assertEqual(self.res2int(res), (os.cpu_count(), os.process_cpu_count()))
@unittest.expectedFailure # TODO: RUSTPYTHON
def test_import_time(self):
# os is not imported at startup
code = 'import os; import os'
for case in 'importtime', 'importtime=1', 'importtime=true':
res = assert_python_ok('-X', case, '-c', code)
res_err = res.err.decode('utf-8')
self.assertRegex(res_err, r'import time: \s*\d+ \| \s*\d+ \| \s*os')
self.assertNotRegex(res_err, r'import time: cached\s* \| cached\s* \| os')
res = assert_python_ok('-X', 'importtime=2', '-c', code)
res_err = res.err.decode('utf-8')
self.assertRegex(res_err, r'import time: \s*\d+ \| \s*\d+ \| \s*os')
self.assertRegex(res_err, r'import time: cached\s* \| cached\s* \| os')
assert_python_failure('-X', 'importtime=-1', '-c', code)
assert_python_failure('-X', 'importtime=3', '-c', code)
def res2int(self, res):
out = res.out.strip().decode("utf-8")
return tuple(int(i) for i in out.split())
@unittest.skipUnless(support.Py_GIL_DISABLED,
"PYTHON_TLBC and -X tlbc"
" only supported in Py_GIL_DISABLED builds")
@threading_helper.requires_working_threading()
def test_disable_thread_local_bytecode(self):
code = """if 1:
import threading
def test(x, y):
return x + y
t = threading.Thread(target=test, args=(1,2))
t.start()
t.join()"""
assert_python_ok("-W", "always", "-X", "tlbc=0", "-c", code)
assert_python_ok("-W", "always", "-c", code, PYTHON_TLBC="0")
@unittest.skipUnless(support.Py_GIL_DISABLED,
"PYTHON_TLBC and -X tlbc"
" only supported in Py_GIL_DISABLED builds")
@threading_helper.requires_working_threading()
def test_enable_thread_local_bytecode(self):
code = """if 1:
import threading
def test(x, y):
return x + y
t = threading.Thread(target=test, args=(1,2))
t.start()
t.join()"""
# The functionality of thread-local bytecode is tested more extensively
# in test_thread_local_bytecode
assert_python_ok("-W", "always", "-X", "tlbc=1", "-c", code)
assert_python_ok("-W", "always", "-c", code, PYTHON_TLBC="1")
@unittest.expectedFailure # TODO: RUSTPYTHON
@unittest.skipUnless(support.Py_GIL_DISABLED,
"PYTHON_TLBC and -X tlbc"
" only supported in Py_GIL_DISABLED builds")
def test_invalid_thread_local_bytecode(self):
rc, out, err = assert_python_failure("-X", "tlbc")
self.assertIn(b"tlbc=n: n is missing or invalid", err)
rc, out, err = assert_python_failure("-X", "tlbc=foo")
self.assertIn(b"tlbc=n: n is missing or invalid", err)
rc, out, err = assert_python_failure("-X", "tlbc=-1")
self.assertIn(b"tlbc=n: n is missing or invalid", err)
rc, out, err = assert_python_failure("-X", "tlbc=2")
self.assertIn(b"tlbc=n: n is missing or invalid", err)
rc, out, err = assert_python_failure(PYTHON_TLBC="foo")
self.assertIn(b"PYTHON_TLBC=N: N is missing or invalid", err)
rc, out, err = assert_python_failure(PYTHON_TLBC="-1")
self.assertIn(b"PYTHON_TLBC=N: N is missing or invalid", err)
rc, out, err = assert_python_failure(PYTHON_TLBC="2")
self.assertIn(b"PYTHON_TLBC=N: N is missing or invalid", err)
@unittest.skipIf(interpreter_requires_environment(),
'Cannot run -I tests when PYTHON env vars are required.')
@@ -1323,7 +979,6 @@ class IgnoreEnvironmentTest(unittest.TestCase):
class SyntaxErrorTests(unittest.TestCase):
@force_not_colorized
def check_string(self, code):
proc = subprocess.run([sys.executable, "-"], input=code,
stdout=subprocess.PIPE, stderr=subprocess.PIPE)
@@ -1331,11 +986,13 @@ class SyntaxErrorTests(unittest.TestCase):
self.assertNotEqual(proc.stderr, None)
self.assertIn(b"\nSyntaxError", proc.stderr)
@unittest.expectedFailureIf(not support.is_android, "TODO: RUSTPYTHON")
# TODO: RUSTPYTHON
@unittest.expectedFailure
def test_tokenizer_error_with_stdin(self):
self.check_string(b"(1+2+3")
@unittest.expectedFailureIf(not support.is_android, "TODO: RUSTPYTHON")
# TODO: RUSTPYTHON
@unittest.expectedFailure
def test_decoding_error_at_the_end_of_the_line(self):
self.check_string(br"'\u1f'")

View File

@@ -14,7 +14,8 @@ import io
import textwrap
from test import support
from test.support import import_helper, is_apple, os_helper
from test.support import import_helper
from test.support import os_helper
from test.support.script_helper import (
make_pkg, make_script, make_zip_pkg, make_zip_script,
assert_python_ok, assert_python_failure, spawn_python, kill_python)
@@ -88,8 +89,6 @@ def _make_test_zip_pkg(zip_dir, zip_basename, pkg_name, script_basename,
importlib.invalidate_caches()
return to_return
@support.force_not_colorized_test_class
class CmdLineTest(unittest.TestCase):
def _check_output(self, script_name, exit_code, data,
expected_file, expected_argv0,
@@ -153,13 +152,15 @@ class CmdLineTest(unittest.TestCase):
print('Expected output: %r' % expected_msg)
self.assertIn(expected_msg.encode('utf-8'), err)
@unittest.expectedFailure # TODO: RUSTPYTHON
# TODO: RUSTPYTHON
@unittest.expectedFailure
def test_dash_c_loader(self):
rc, out, err = assert_python_ok("-c", "print(__loader__)")
expected = repr(importlib.machinery.BuiltinImporter).encode("utf-8")
self.assertIn(expected, out)
@unittest.expectedFailure # TODO: RUSTPYTHON
# TODO: RUSTPYTHON
@unittest.expectedFailure
def test_stdin_loader(self):
# Unfortunately, there's no way to automatically test the fully
# interactive REPL, since that code path only gets executed when
@@ -206,23 +207,21 @@ class CmdLineTest(unittest.TestCase):
stderr = p.stderr if separate_stderr else p.stdout
self.assertIn(b'Traceback ', stderr.readline())
self.assertIn(b'File "<stdin>"', stderr.readline())
self.assertIn(b'1/0', stderr.readline())
self.assertIn(b' ~^~', stderr.readline())
self.assertIn(b'ZeroDivisionError', stderr.readline())
@unittest.skip("TODO: RUSTPYTHON; test hang in middle")
@unittest.skip("TODO: RUSTPYTHON, test hang in middle")
def test_repl_stdout_flush(self):
self.check_repl_stdout_flush()
@unittest.skip("TODO: RUSTPYTHON; test hang in middle")
@unittest.skip("TODO: RUSTPYTHON, test hang in middle")
def test_repl_stdout_flush_separate_stderr(self):
self.check_repl_stdout_flush(True)
@unittest.skip("TODO: RUSTPYTHON; test hang in middle")
@unittest.skip("TODO: RUSTPYTHON, test hang in middle")
def test_repl_stderr_flush(self):
self.check_repl_stderr_flush()
@unittest.skip("TODO: RUSTPYTHON; test hang in middle")
@unittest.skip("TODO: RUSTPYTHON, test hang in middle")
def test_repl_stderr_flush_separate_stderr(self):
self.check_repl_stderr_flush(True)
@@ -234,7 +233,8 @@ class CmdLineTest(unittest.TestCase):
importlib.machinery.SourceFileLoader,
expected_cwd=script_dir)
@unittest.expectedFailure # TODO: RUSTPYTHON
# TODO: RUSTPYTHON
@unittest.expectedFailure
def test_script_abspath(self):
# pass the script using the relative path, expect the absolute path
# in __file__
@@ -390,7 +390,8 @@ class CmdLineTest(unittest.TestCase):
"be directly executed")
self._check_import_error(["-m", "test_pkg"], msg, cwd=script_dir)
@unittest.expectedFailure # TODO: RUSTPYTHON
# TODO: RUSTPYTHON
@unittest.expectedFailure
def test_issue8202(self):
# Make sure package __init__ modules see "-m" in sys.argv0 while
# searching for the module to execute
@@ -553,7 +554,7 @@ class CmdLineTest(unittest.TestCase):
script = textwrap.dedent("""\
try:
raise ValueError
except ValueError:
except:
raise NameError from None
""")
with os_helper.temp_dir() as script_dir:
@@ -561,23 +562,18 @@ class CmdLineTest(unittest.TestCase):
exitcode, stdout, stderr = assert_python_failure(script_name)
text = stderr.decode('ascii').split('\n')
self.assertEqual(len(text), 5)
self.assertStartsWith(text[0], 'Traceback')
self.assertStartsWith(text[1], ' File ')
self.assertStartsWith(text[3], 'NameError')
self.assertTrue(text[0].startswith('Traceback'))
self.assertTrue(text[1].startswith(' File '))
self.assertTrue(text[3].startswith('NameError'))
@unittest.expectedFailureIf(sys.platform in ("android", "linux"), "TODO: RUSTPYTHON")
@unittest.expectedFailureIf(sys.platform == "linux", "TODO: RUSTPYTHON")
def test_non_ascii(self):
# Apple platforms deny the creation of a file with an invalid UTF-8 name.
# Mac OS X denies the creation of a file with an invalid UTF-8 name.
# Windows allows creating a name with an arbitrary bytes name, but
# Python cannot a undecodable bytes argument to a subprocess.
# Emscripten/WASI does not permit invalid UTF-8 names.
if (
os_helper.TESTFN_UNDECODABLE
and sys.platform not in {
"win32", "emscripten", "wasi"
}
and not is_apple
):
# WASI does not permit invalid UTF-8 names.
if (os_helper.TESTFN_UNDECODABLE
and sys.platform not in ('win32', 'darwin', 'emscripten', 'wasi')):
name = os.fsdecode(os_helper.TESTFN_UNDECODABLE)
elif os_helper.TESTFN_NONASCII:
name = os_helper.TESTFN_NONASCII
@@ -645,7 +641,8 @@ class CmdLineTest(unittest.TestCase):
self.assertNotIn("\f", text)
self.assertIn("\n 1 + 1 = 2\n ^^^^^\n", text)
@unittest.expectedFailure # TODO: RUSTPYTHON
# TODO: RUSTPYTHON
@unittest.expectedFailure
def test_syntaxerror_multi_line_fstring(self):
script = 'foo = f"""{}\nfoo"""\n'
with os_helper.temp_dir() as script_dir:
@@ -660,7 +657,8 @@ class CmdLineTest(unittest.TestCase):
],
)
@unittest.expectedFailure # TODO: RUSTPYTHON
# TODO: RUSTPYTHON
@unittest.expectedFailure
def test_syntaxerror_invalid_escape_sequence_multi_line(self):
script = 'foo = """\\q"""\n'
with os_helper.temp_dir() as script_dir:
@@ -671,13 +669,13 @@ class CmdLineTest(unittest.TestCase):
self.assertEqual(
stderr.splitlines()[-3:],
[ b' foo = """\\q"""',
b' ^^',
b'SyntaxError: "\\q" is an invalid escape sequence. '
b'Did you mean "\\\\q"? A raw string is also an option.'
b' ^^^^^^^^',
b'SyntaxError: invalid escape sequence \'\\q\''
],
)
@unittest.expectedFailure # TODO: RUSTPYTHON
# TODO: RUSTPYTHON
@unittest.expectedFailure
def test_syntaxerror_null_bytes(self):
script = "x = '\0' nothing to see here\n';import os;os.system('echo pwnd')\n"
with os_helper.temp_dir() as script_dir:
@@ -690,7 +688,8 @@ class CmdLineTest(unittest.TestCase):
],
)
@unittest.expectedFailure # TODO: RUSTPYTHON
# TODO: RUSTPYTHON
@unittest.expectedFailure
def test_syntaxerror_null_bytes_in_multiline_string(self):
scripts = ["\n'''\nmultilinestring\0\n'''", "\nf'''\nmultilinestring\0\n'''"] # Both normal and f-strings
with os_helper.temp_dir() as script_dir:
@@ -704,26 +703,6 @@ class CmdLineTest(unittest.TestCase):
]
)
def test_source_lines_are_shown_when_running_source(self):
_, _, stderr = assert_python_failure("-c", "1/0")
expected_lines = [
b'Traceback (most recent call last):',
b' File "<string>", line 1, in <module>',
b' 1/0',
b' ~^~',
b'ZeroDivisionError: division by zero']
self.assertEqual(stderr.splitlines(), expected_lines)
def test_syntaxerror_does_not_crash(self):
script = "nonlocal x\n"
with os_helper.temp_dir() as script_dir:
script_name = _make_test_script(script_dir, 'script', script)
exitcode, stdout, stderr = assert_python_failure(script_name)
text = io.TextIOWrapper(io.BytesIO(stderr), 'ascii').read()
# It used to crash in https://github.com/python/cpython/issues/111132
self.assertEndsWith(text,
'SyntaxError: nonlocal declaration not allowed at module level\n')
def test_consistent_sys_path_for_direct_execution(self):
# This test case ensures that the following all give the same
# sys.path configuration:
@@ -792,7 +771,8 @@ class CmdLineTest(unittest.TestCase):
traceback_lines = stderr.decode().splitlines()
self.assertIn("No module named script_pkg", traceback_lines[-1])
@unittest.expectedFailure # TODO: RUSTPYTHON
# TODO: RUSTPYTHON
@unittest.expectedFailure
def test_nonexisting_script(self):
# bpo-34783: "./python script.py" must not crash
# if the script file doesn't exist.
@@ -808,11 +788,11 @@ class CmdLineTest(unittest.TestCase):
self.assertIn(": can't open file ", err)
self.assertNotEqual(proc.returncode, 0)
@unittest.skipIf(sys.platform.startswith("darwin"), "TODO: RUSTPYTHON; Problems with Mac os descriptor")
@unittest.skipUnless(os.path.exists('/dev/fd/0'), 'requires /dev/fd platform')
@unittest.skipIf(sys.platform.startswith("freebsd") and
os.stat("/dev").st_dev == os.stat("/dev/fd").st_dev,
"Requires fdescfs mounted on /dev/fd on FreeBSD")
@unittest.skipIf(sys.platform.startswith("darwin"), "TODO: RUSTPYTHON Problems with Mac os descriptor")
def test_script_as_dev_fd(self):
# GH-87235: On macOS passing a non-trivial script to /dev/fd/N can cause
# problems because all open /dev/fd/N file descriptors share the same

View File

@@ -1,234 +0,0 @@
"""Test suite for the cProfile module."""
import sys
import unittest
# rip off all interesting stuff from test_profile
try:
import cProfile
except ImportError:
# TODO: RUSTPYTHON; _lsprof not implemented
raise unittest.SkipTest('cProfile requires _lsprof')
import tempfile
import textwrap
from test.test_profile import ProfileTest, regenerate_expected_output
from test.support.script_helper import assert_python_failure, assert_python_ok
from test import support
class CProfileTest(ProfileTest):
profilerclass = cProfile.Profile
profilermodule = cProfile
expected_max_output = "{built-in method builtins.max}"
def get_expected_output(self):
return _ProfileOutput
def test_bad_counter_during_dealloc(self):
# bpo-3895
import _lsprof
with support.catch_unraisable_exception() as cm:
obj = _lsprof.Profiler(lambda: int)
obj.enable()
obj.disable()
obj.clear()
self.assertEqual(cm.unraisable.exc_type, TypeError)
def test_crash_with_not_enough_args(self):
# gh-126220
import _lsprof
for profile in [_lsprof.Profiler(), cProfile.Profile()]:
for method in [
"_pystart_callback",
"_pyreturn_callback",
"_ccall_callback",
"_creturn_callback",
]:
with self.subTest(profile=profile, method=method):
method_obj = getattr(profile, method)
with self.assertRaises(TypeError):
method_obj() # should not crash
def test_evil_external_timer(self):
# gh-120289
# Disabling profiler in external timer should not crash
import _lsprof
class EvilTimer():
def __init__(self, disable_count):
self.count = 0
self.disable_count = disable_count
def __call__(self):
self.count += 1
if self.count == self.disable_count:
profiler_with_evil_timer.disable()
return self.count
# this will trigger external timer to disable profiler at
# call event - in initContext in _lsprof.c
with support.catch_unraisable_exception() as cm:
profiler_with_evil_timer = _lsprof.Profiler(EvilTimer(1))
profiler_with_evil_timer.enable()
# Make a call to trigger timer
(lambda: None)()
profiler_with_evil_timer.disable()
profiler_with_evil_timer.clear()
self.assertEqual(cm.unraisable.exc_type, RuntimeError)
# this will trigger external timer to disable profiler at
# return event - in Stop in _lsprof.c
with support.catch_unraisable_exception() as cm:
profiler_with_evil_timer = _lsprof.Profiler(EvilTimer(2))
profiler_with_evil_timer.enable()
# Make a call to trigger timer
(lambda: None)()
profiler_with_evil_timer.disable()
profiler_with_evil_timer.clear()
self.assertEqual(cm.unraisable.exc_type, RuntimeError)
def test_profile_enable_disable(self):
prof = self.profilerclass()
# Make sure we clean ourselves up if the test fails for some reason.
self.addCleanup(prof.disable)
prof.enable()
self.assertEqual(
sys.monitoring.get_tool(sys.monitoring.PROFILER_ID), "cProfile")
prof.disable()
self.assertIs(sys.monitoring.get_tool(sys.monitoring.PROFILER_ID), None)
def test_profile_as_context_manager(self):
prof = self.profilerclass()
# Make sure we clean ourselves up if the test fails for some reason.
self.addCleanup(prof.disable)
with prof as __enter__return_value:
# profile.__enter__ should return itself.
self.assertIs(prof, __enter__return_value)
# profile should be set as the global profiler inside the
# with-block
self.assertEqual(
sys.monitoring.get_tool(sys.monitoring.PROFILER_ID), "cProfile")
# profile shouldn't be set once we leave the with-block.
self.assertIs(sys.monitoring.get_tool(sys.monitoring.PROFILER_ID), None)
def test_second_profiler(self):
pr = self.profilerclass()
pr2 = self.profilerclass()
pr.enable()
self.assertRaises(ValueError, pr2.enable)
pr.disable()
def test_throw(self):
"""
gh-106152
generator.throw() should trigger a call in cProfile
"""
def gen():
yield
pr = self.profilerclass()
pr.enable()
g = gen()
try:
g.throw(SyntaxError)
except SyntaxError:
pass
pr.disable()
pr.create_stats()
self.assertTrue(any("throw" in func[2] for func in pr.stats.keys())),
def test_bad_descriptor(self):
# gh-132250
# cProfile should not crash when the profiler callback fails to locate
# the actual function of a method.
with self.profilerclass() as prof:
with self.assertRaises(TypeError):
bytes.find(str())
class TestCommandLine(unittest.TestCase):
def test_sort(self):
rc, out, err = assert_python_failure('-m', 'cProfile', '-s', 'demo')
self.assertGreater(rc, 0)
self.assertIn(b"option -s: invalid choice: 'demo'", err)
def test_profile_script_importing_main(self):
"""Check that scripts that reference __main__ see their own namespace
when being profiled."""
with tempfile.NamedTemporaryFile("w+", delete_on_close=False) as f:
f.write(textwrap.dedent("""\
class Foo:
pass
import __main__
assert Foo == __main__.Foo
"""))
f.close()
assert_python_ok('-m', "cProfile", f.name)
def main():
if '-r' not in sys.argv:
unittest.main()
else:
regenerate_expected_output(__file__, CProfileTest)
# Don't remove this comment. Everything below it is auto-generated.
#--cut--------------------------------------------------------------------------
_ProfileOutput = {}
_ProfileOutput['print_stats'] = """\
28 0.028 0.001 0.028 0.001 profilee.py:110(__getattr__)
1 0.270 0.270 1.000 1.000 profilee.py:25(testfunc)
23/3 0.150 0.007 0.170 0.057 profilee.py:35(factorial)
20 0.020 0.001 0.020 0.001 profilee.py:48(mul)
2 0.040 0.020 0.600 0.300 profilee.py:55(helper)
4 0.116 0.029 0.120 0.030 profilee.py:73(helper1)
2 0.000 0.000 0.140 0.070 profilee.py:84(helper2_indirect)
8 0.312 0.039 0.400 0.050 profilee.py:88(helper2)
8 0.064 0.008 0.080 0.010 profilee.py:98(subhelper)"""
_ProfileOutput['print_callers'] = """\
profilee.py:110(__getattr__) <- 16 0.016 0.016 profilee.py:98(subhelper)
profilee.py:25(testfunc) <- 1 0.270 1.000 <string>:1(<module>)
profilee.py:35(factorial) <- 1 0.014 0.130 profilee.py:25(testfunc)
20/3 0.130 0.147 profilee.py:35(factorial)
2 0.006 0.040 profilee.py:84(helper2_indirect)
profilee.py:48(mul) <- 20 0.020 0.020 profilee.py:35(factorial)
profilee.py:55(helper) <- 2 0.040 0.600 profilee.py:25(testfunc)
profilee.py:73(helper1) <- 4 0.116 0.120 profilee.py:55(helper)
profilee.py:84(helper2_indirect) <- 2 0.000 0.140 profilee.py:55(helper)
profilee.py:88(helper2) <- 6 0.234 0.300 profilee.py:55(helper)
2 0.078 0.100 profilee.py:84(helper2_indirect)
profilee.py:98(subhelper) <- 8 0.064 0.080 profilee.py:88(helper2)
{built-in method builtins.hasattr} <- 4 0.000 0.004 profilee.py:73(helper1)
8 0.000 0.008 profilee.py:88(helper2)
{built-in method sys.exception} <- 4 0.000 0.000 profilee.py:73(helper1)
{method 'append' of 'list' objects} <- 4 0.000 0.000 profilee.py:73(helper1)"""
_ProfileOutput['print_callees'] = """\
<string>:1(<module>) -> 1 0.270 1.000 profilee.py:25(testfunc)
profilee.py:110(__getattr__) ->
profilee.py:25(testfunc) -> 1 0.014 0.130 profilee.py:35(factorial)
2 0.040 0.600 profilee.py:55(helper)
profilee.py:35(factorial) -> 20/3 0.130 0.147 profilee.py:35(factorial)
20 0.020 0.020 profilee.py:48(mul)
profilee.py:48(mul) ->
profilee.py:55(helper) -> 4 0.116 0.120 profilee.py:73(helper1)
2 0.000 0.140 profilee.py:84(helper2_indirect)
6 0.234 0.300 profilee.py:88(helper2)
profilee.py:73(helper1) -> 4 0.000 0.004 {built-in method builtins.hasattr}
profilee.py:84(helper2_indirect) -> 2 0.006 0.040 profilee.py:35(factorial)
2 0.078 0.100 profilee.py:88(helper2)
profilee.py:88(helper2) -> 8 0.064 0.080 profilee.py:98(subhelper)
profilee.py:98(subhelper) -> 16 0.016 0.016 profilee.py:110(__getattr__)
{built-in method builtins.hasattr} -> 12 0.012 0.012 profilee.py:110(__getattr__)"""
if __name__ == "__main__":
main()

View File

@@ -204,20 +204,5 @@ class TestDefaultDict(unittest.TestCase):
self.assertEqual(test_dict[key], 2)
self.assertEqual(count, 2)
def test_repr_recursive_factory(self):
# gh-145492: defaultdict.__repr__ should not cause infinite recursion
# when the factory's __repr__ calls repr() on the defaultdict.
class ProblematicFactory:
def __call__(self):
return {}
def __repr__(self):
repr(dd)
return f"ProblematicFactory for {dd}"
dd = defaultdict(ProblematicFactory())
# Should not raise RecursionError
r = repr(dd)
self.assertIn("ProblematicFactory for", r)
if __name__ == "__main__":
unittest.main()

2032
Lib/test/test_embed.py vendored

File diff suppressed because it is too large Load Diff

View File

@@ -21,8 +21,8 @@ if not support.has_subprocess_support:
raise unittest.SkipTest("test module requires subprocess")
# Test import all of the things we're about to try testing up front.
import _io # noqa: F401
import _pyio # noqa: F401
import _io
import _pyio
@unittest.skipUnless(os.name == 'posix', 'tests requires a posix system.')
class TestFileIOSignalInterrupt:
@@ -186,9 +186,10 @@ class TestFileIOSignalInterrupt:
class CTestFileIOSignalInterrupt(TestFileIOSignalInterrupt, unittest.TestCase):
modname = '_io'
@unittest.expectedFailure # TODO: RUSTPYTHON; - _io.FileIO.readall uses read_to_end which differs from _pyio.FileIO.readall
# TODO: RUSTPYTHON - _io.FileIO.readall uses read_to_end which differs from _pyio.FileIO.readall
@unittest.expectedFailure
def test_readall(self):
return super().test_readall()
super().test_readall()
class PyTestFileIOSignalInterrupt(TestFileIOSignalInterrupt, unittest.TestCase):
modname = '_pyio'

177
Lib/test/test_gc.py vendored
View File

@@ -7,7 +7,7 @@ from test.support import (verbose, refcount_test,
Py_GIL_DISABLED)
from test.support.import_helper import import_module
from test.support.os_helper import temp_dir, TESTFN, unlink
from test.support.script_helper import assert_python_ok, make_script
from test.support.script_helper import assert_python_ok, make_script, run_test_script
from test.support import threading_helper, gc_threshold
import gc
@@ -236,7 +236,8 @@ class GCTests(unittest.TestCase):
# is 3 because it includes f's code object.
self.assertIn(gc.collect(), (2, 3))
@unittest.expectedFailure # TODO: RUSTPYTHON; - weakref clear ordering differs from 3.15+
# TODO: RUSTPYTHON - weakref clear ordering differs from 3.15+
@unittest.expectedFailure
def test_function_tp_clear_leaves_consistent_state(self):
# https://github.com/python/cpython/issues/91636
code = """if 1:
@@ -310,6 +311,28 @@ class GCTests(unittest.TestCase):
self.assertRegex(stdout, rb"""\A\s*func=None""")
self.assertFalse(stderr)
# TODO: RUSTPYTHON - _datetime module not available
@unittest.expectedFailure
def test_datetime_weakref_cycle(self):
# https://github.com/python/cpython/issues/132413
# If the weakref used by the datetime extension gets cleared by the GC (due to being
# in an unreachable cycle) then datetime functions would crash (get_module_state()
# was returning a NULL pointer). This bug is fixed by clearing weakrefs without
# callbacks *after* running finalizers.
code = """if 1:
import _datetime
class C:
def __del__(self):
print('__del__ called')
_datetime.timedelta(days=1) # crash?
l = [C()]
l.append(l)
"""
rc, stdout, stderr = assert_python_ok("-c", code)
self.assertEqual(rc, 0)
self.assertEqual(stdout.strip(), b'__del__ called')
@refcount_test
def test_frame(self):
def f():
@@ -400,19 +423,11 @@ class GCTests(unittest.TestCase):
# each call to collect(N)
x = []
gc.collect(0)
# x is now in gen 1
# x is now in the old gen
a, b, c = gc.get_count()
gc.collect(1)
# x is now in gen 2
d, e, f = gc.get_count()
gc.collect(2)
# x is now in gen 3
g, h, i = gc.get_count()
# We don't check a, d, g since their exact values depends on
# We don't check a since its exact values depends on
# internal implementation details of the interpreter.
self.assertEqual((b, c), (1, 0))
self.assertEqual((e, f), (0, 1))
self.assertEqual((h, i), (0, 0))
def test_trashcan(self):
class Ouch:
@@ -667,9 +682,8 @@ class GCTests(unittest.TestCase):
gc.collect()
self.assertEqual(len(ouch), 2) # else the callbacks didn't run
for x in ouch:
# If the callback resurrected one of these guys, the instance
# would be damaged, with an empty __dict__.
self.assertEqual(x, None)
# The weakref should be cleared before executing the callback.
self.assertIsNone(x)
def test_bug21435(self):
# This is a poor test - its only virtue is that it happened to
@@ -831,17 +845,20 @@ class GCTests(unittest.TestCase):
rc, out, err = assert_python_ok(TESTFN)
self.assertEqual(out.strip(), b'__del__ called')
@unittest.expectedFailure # TODO: RUSTPYTHON
def test_get_stats(self):
stats = gc.get_stats()
self.assertEqual(len(stats), 3)
for st in stats:
self.assertIsInstance(st, dict)
self.assertEqual(set(st),
{"collected", "collections", "uncollectable"})
self.assertEqual(
set(st),
{"collected", "collections", "uncollectable", "candidates", "duration"}
)
self.assertGreaterEqual(st["collected"], 0)
self.assertGreaterEqual(st["collections"], 0)
self.assertGreaterEqual(st["uncollectable"], 0)
self.assertGreaterEqual(st["candidates"], 0)
self.assertGreaterEqual(st["duration"], 0)
# Check that collection counts are incremented correctly
if gc.isenabled():
self.addCleanup(gc.enable)
@@ -852,11 +869,25 @@ class GCTests(unittest.TestCase):
self.assertEqual(new[0]["collections"], old[0]["collections"] + 1)
self.assertEqual(new[1]["collections"], old[1]["collections"])
self.assertEqual(new[2]["collections"], old[2]["collections"])
self.assertGreater(new[0]["duration"], old[0]["duration"])
self.assertEqual(new[1]["duration"], old[1]["duration"])
self.assertEqual(new[2]["duration"], old[2]["duration"])
for stat in ["collected", "uncollectable", "candidates"]:
self.assertGreaterEqual(new[0][stat], old[0][stat])
self.assertEqual(new[1][stat], old[1][stat])
self.assertEqual(new[2][stat], old[2][stat])
gc.collect(2)
new = gc.get_stats()
self.assertEqual(new[0]["collections"], old[0]["collections"] + 1)
old, new = new, gc.get_stats()
self.assertEqual(new[0]["collections"], old[0]["collections"])
self.assertEqual(new[1]["collections"], old[1]["collections"])
self.assertEqual(new[2]["collections"], old[2]["collections"] + 1)
self.assertEqual(new[0]["duration"], old[0]["duration"])
self.assertEqual(new[1]["duration"], old[1]["duration"])
self.assertGreater(new[2]["duration"], old[2]["duration"])
for stat in ["collected", "uncollectable", "candidates"]:
self.assertEqual(new[0][stat], old[0][stat])
self.assertEqual(new[1][stat], old[1][stat])
self.assertGreaterEqual(new[2][stat], old[2][stat])
def test_freeze(self):
gc.freeze()
@@ -880,42 +911,10 @@ class GCTests(unittest.TestCase):
self.assertTrue(
any(l is element for element in gc.get_objects(generation=0))
)
self.assertFalse(
any(l is element for element in gc.get_objects(generation=1))
)
self.assertFalse(
any(l is element for element in gc.get_objects(generation=2))
)
gc.collect(generation=0)
gc.collect()
self.assertFalse(
any(l is element for element in gc.get_objects(generation=0))
)
self.assertTrue(
any(l is element for element in gc.get_objects(generation=1))
)
self.assertFalse(
any(l is element for element in gc.get_objects(generation=2))
)
gc.collect(generation=1)
self.assertFalse(
any(l is element for element in gc.get_objects(generation=0))
)
self.assertFalse(
any(l is element for element in gc.get_objects(generation=1))
)
self.assertTrue(
any(l is element for element in gc.get_objects(generation=2))
)
gc.collect(generation=2)
self.assertFalse(
any(l is element for element in gc.get_objects(generation=0))
)
self.assertFalse(
any(l is element for element in gc.get_objects(generation=1))
)
self.assertTrue(
any(l is element for element in gc.get_objects(generation=2))
)
del l
gc.collect()
@@ -1204,6 +1203,37 @@ class GCTests(unittest.TestCase):
""")
assert_python_ok("-c", source)
def test_do_not_cleanup_type_subclasses_before_finalization(self):
# See https://github.com/python/cpython/issues/135552
# If we cleanup weakrefs for tp_subclasses before calling
# the finalizer (__del__) then the line `fail = BaseNode.next.next`
# should fail because we are trying to access a subclass
# attribute. But subclass type cache was not properly invalidated.
code = """
class BaseNode:
def __del__(self):
BaseNode.next = BaseNode.next.next
fail = BaseNode.next.next
class Node(BaseNode):
pass
BaseNode.next = Node()
BaseNode.next.next = Node()
"""
# this test checks garbage collection while interp
# finalization
assert_python_ok("-c", textwrap.dedent(code))
code_inside_function = textwrap.dedent(F"""
def test():
{textwrap.indent(code, ' ')}
test()
""")
# this test checks regular garbage collection
assert_python_ok("-c", code_inside_function)
@unittest.skipUnless(Py_GIL_DISABLED, "requires free-threaded GC")
@unittest.skipIf(_testinternalcapi is None, "requires _testinternalcapi")
@@ -1222,14 +1252,16 @@ class GCTests(unittest.TestCase):
# Use n // 2 just in case some other objects were collected.
self.assertTrue(new_count - count > (n // 2))
@requires_gil_enabled('need generational GC')
class IncrementalGCTests(unittest.TestCase):
@unittest.skipIf(_testinternalcapi is None, "requires _testinternalcapi")
def test_heap_size(self):
count = _testinternalcapi.get_tracked_heap_size()
l = []
self.assertEqual(count + 1, _testinternalcapi.get_tracked_heap_size())
del l
self.assertEqual(count, _testinternalcapi.get_tracked_heap_size())
@requires_gil_enabled("Free threading does not support incremental GC")
def test_incremental_gc_handles_fast_cycle_creation(self):
# Run this test in a fresh process. The number of alive objects (which can
# be from unit tests run before this one) can influence how quickly cyclic
# garbage is found.
script = support.findfile("_test_gc_fast_cycles.py")
run_test_script(script)
class GCCallbackTests(unittest.TestCase):
@@ -1306,9 +1338,11 @@ class GCCallbackTests(unittest.TestCase):
# Check that we got the right info dict for all callbacks
for v in self.visit:
info = v[2]
self.assertTrue("generation" in info)
self.assertTrue("collected" in info)
self.assertTrue("uncollectable" in info)
self.assertIn("generation", info)
self.assertIn("collected", info)
self.assertIn("uncollectable", info)
self.assertIn("candidates", info)
self.assertIn("duration", info)
def test_collect_generation(self):
self.preclean()
@@ -1484,8 +1518,8 @@ class GCTogglingTests(unittest.TestCase):
assert not detector.gc_happened
while not detector.gc_happened:
i += 1
if i > 10000:
self.fail("gc didn't happen after 10000 iterations")
if i > 100000:
self.fail("gc didn't happen after 100000 iterations")
self.assertEqual(len(ouch), 0)
junk.append([]) # this will eventually trigger gc
@@ -1496,6 +1530,7 @@ class GCTogglingTests(unittest.TestCase):
self.assertEqual(x, None)
@gc_threshold(1000, 0, 0)
@unittest.skipIf(Py_GIL_DISABLED, "requires GC generations or increments")
def test_bug1055820d(self):
# Corresponds to temp2d.py in the bug report. This is very much like
# test_bug1055820c, but uses a __del__ method instead of a weakref
@@ -1556,8 +1591,8 @@ class GCTogglingTests(unittest.TestCase):
gc.collect()
while not detector.gc_happened:
i += 1
if i > 10000:
self.fail("gc didn't happen after 10000 iterations")
if i > 50000:
self.fail("gc didn't happen after 50000 iterations")
self.assertEqual(len(ouch), 0)
junk.append([]) # this will eventually trigger gc
@@ -1574,8 +1609,8 @@ class GCTogglingTests(unittest.TestCase):
detector = GC_Detector()
while not detector.gc_happened:
i += 1
if i > 10000:
self.fail("gc didn't happen after 10000 iterations")
if i > 100000:
self.fail("gc didn't happen after 100000 iterations")
junk.append([]) # this will eventually trigger gc
try:
@@ -1585,11 +1620,11 @@ class GCTogglingTests(unittest.TestCase):
detector = GC_Detector()
while not detector.gc_happened:
i += 1
if i > 10000:
if i > 100000:
break
junk.append([]) # this may eventually trigger gc (if it is enabled)
self.assertEqual(i, 10001)
self.assertEqual(i, 100001)
finally:
gc.enable()

View File

@@ -2525,7 +2525,7 @@ Traceback (most recent call last):
...
SyntaxError: 'yield from' outside function
>>> def f(): x = yield = y
>>> def f(): x = yield = y # TODO: RUSTPYTHON # doctest: +EXPECTED_FAILURE
Traceback (most recent call last):
...
SyntaxError: assignment to yield expression not possible

View File

@@ -154,7 +154,7 @@ Verify re-use of tuples (a side benefit of using genexps over listcomps)
Verify that syntax error's are raised for genexps used as lvalues
>>> (y for y in (1,2)) = 10
>>> (y for y in (1,2)) = 10 # TODO: RUSTPYTHON # doctest: +EXPECTED_FAILURE
Traceback (most recent call last):
...
SyntaxError: cannot assign to generator expression

View File

@@ -0,0 +1 @@

View File

@@ -0,0 +1 @@

View File

@@ -0,0 +1 @@

View File

@@ -5,6 +5,7 @@ machinery = util.import_importlib('importlib.machinery')
from test.support import captured_stdout, import_helper, STDLIB_DIR
import contextlib
import os.path
import sys
import types
import unittest
import warnings
@@ -75,7 +76,7 @@ class ExecModuleTests(abc.LoaderTests):
self.assertHasAttr(module, '__spec__')
self.assertEqual(module.__spec__.loader_state.origname, name)
@unittest.skipIf(__import__("sys").platform == "win32", "TODO: RUSTPYTHON")
@unittest.skipIf(sys.platform == "win32", "TODO: RUSTPYTHON")
def test_package(self):
name = '__phello__'
module, output = self.exec_module(name)
@@ -146,7 +147,7 @@ class InspectLoaderTests:
result = self.machinery.FrozenImporter.get_source('__hello__')
self.assertIsNone(result)
@unittest.skipIf(__import__("sys").platform == "win32", "TODO: RUSTPYTHON")
@unittest.skipIf(sys.platform == "win32", "TODO: RUSTPYTHON")
def test_is_package(self):
# Should be able to tell what is a package.
test_for = (('__hello__', False), ('__phello__', True),

View File

@@ -1,3 +1,4 @@
import os
import unittest
from . import util

View File

@@ -155,8 +155,8 @@ class LifetimeTests:
# TODO: RUSTPYTHON; dead weakref module locks not cleaned up in frozen bootstrap
Frozen_LifetimeTests.test_all_locks = unittest.skip("TODO: RUSTPYTHON")(
Frozen_LifetimeTests.test_all_locks
)
Frozen_LifetimeTests.test_all_locks)
def setUpModule():
thread_info = threading_helper.threading_setup()

View File

@@ -263,72 +263,6 @@ class ThreadedImportTests(unittest.TestCase):
'partial', 'pool_in_threads.py')
script_helper.assert_python_ok(fn)
@unittest.expectedFailure # TODO: RUSTPYTHON
def test_import_failure_race_condition(self):
# Regression test for race condition where a thread could receive
# a partially-initialized module when another thread's import fails.
# The race occurs when:
# 1. Thread 1 starts importing, adds module to sys.modules
# 2. Thread 2 sees the module in sys.modules
# 3. Thread 1's import fails, removes module from sys.modules
# 4. Thread 2 should NOT return the stale module reference
os.mkdir(TESTFN)
self.addCleanup(shutil.rmtree, TESTFN)
sys.path.insert(0, TESTFN)
self.addCleanup(sys.path.remove, TESTFN)
# Create a module that partially initializes then fails
modname = 'failing_import_module'
with open(os.path.join(TESTFN, modname + '.py'), 'w') as f:
f.write('''
import time
PARTIAL_ATTR = 'initialized'
time.sleep(0.05) # Widen race window
raise RuntimeError("Intentional import failure")
''')
self.addCleanup(forget, modname)
importlib.invalidate_caches()
errors = []
results = []
def do_import(delay=0):
time.sleep(delay)
try:
mod = __import__(modname)
# If we got a module, verify it's in sys.modules
if modname not in sys.modules:
errors.append(
f"Got module {mod!r} but {modname!r} not in sys.modules"
)
elif sys.modules[modname] is not mod:
errors.append(
f"Got different module than sys.modules[{modname!r}]"
)
else:
results.append(('success', mod))
except RuntimeError:
results.append(('RuntimeError',))
except Exception as e:
errors.append(f"Unexpected exception: {e}")
# Run multiple iterations to increase chance of hitting the race
for _ in range(10):
errors.clear()
results.clear()
if modname in sys.modules:
del sys.modules[modname]
t1 = threading.Thread(target=do_import, args=(0,))
t2 = threading.Thread(target=do_import, args=(0.01,))
t1.start()
t2.start()
t1.join()
t2.join()
# Neither thread should have errors about stale modules
self.assertEqual(errors, [], f"Race condition detected: {errors}")
def setUpModule():
thread_info = threading_helper.threading_setup()

View File

@@ -15,10 +15,10 @@ import sys
import tempfile
import types
try: # TODO: RUSTPYTHON
import_helper.import_module("_testmultiphase")
try:
_testsinglephase = import_helper.import_module("_testsinglephase")
except unittest.SkipTest:
_testmultiphase = None
_testsinglephase = None # TODO: RUSTPYTHON
BUILTINS = types.SimpleNamespace()

385
Lib/test/test_mmap.py vendored
View File

@@ -1,12 +1,9 @@
from test.support import (
requires, _2G, _4G, gc_collect, cpython_only, is_emscripten, is_apple,
in_systemd_nspawn_sync_suppressed,
requires, _2G, _4G, gc_collect, cpython_only, is_emscripten
)
from test.support.import_helper import import_module
from test.support.os_helper import TESTFN, unlink
from test.support.script_helper import assert_python_ok
import unittest
import errno
import os
import re
import itertools
@@ -14,7 +11,6 @@ import random
import socket
import string
import sys
import textwrap
import weakref
# Skip test if we can't import mmap.
@@ -36,10 +32,6 @@ if is_emscripten:
class MmapTests(unittest.TestCase):
def setUp(self):
# TODO: RUSTPYTHON; Remove this once windows doesn't get errored on setup:/
if os.name == "nt":
raise unittest.SkipTest("TODO: RUSTPYTHON; Error during class setUp")
if os.path.exists(TESTFN):
os.unlink(TESTFN)
@@ -49,7 +41,6 @@ class MmapTests(unittest.TestCase):
except OSError:
pass
@unittest.expectedFailure # TODO: RUSTPYTHON; AttributeError: 'mmap' object has no attribute 'seekable'
def test_basic(self):
# Test mmap module on Unix systems and Windows
@@ -102,12 +93,11 @@ class MmapTests(unittest.TestCase):
self.assertEqual(end, PAGESIZE + 6)
# test seeking around (try to overflow the seek implementation)
self.assertTrue(m.seekable())
self.assertEqual(m.seek(0, 0), 0)
m.seek(0,0)
self.assertEqual(m.tell(), 0)
self.assertEqual(m.seek(42, 1), 42)
m.seek(42,1)
self.assertEqual(m.tell(), 42)
self.assertEqual(m.seek(0, 2), len(m))
m.seek(0,2)
self.assertEqual(m.tell(), len(m))
# Try to seek to negative position...
@@ -172,7 +162,7 @@ class MmapTests(unittest.TestCase):
# Ensuring that readonly mmap can't be write() to
try:
m.seek(0, 0)
m.seek(0,0)
m.write(b'abc')
except TypeError:
pass
@@ -181,7 +171,7 @@ class MmapTests(unittest.TestCase):
# Ensuring that readonly mmap can't be write_byte() to
try:
m.seek(0, 0)
m.seek(0,0)
m.write_byte(b'd')
except TypeError:
pass
@@ -265,79 +255,15 @@ class MmapTests(unittest.TestCase):
# Try writing with PROT_EXEC and without PROT_WRITE
prot = mmap.PROT_READ | getattr(mmap, 'PROT_EXEC', 0)
with open(TESTFN, "r+b") as f:
try:
m = mmap.mmap(f.fileno(), mapsize, prot=prot)
except PermissionError:
# on macOS 14, PROT_READ | PROT_EXEC is not allowed
pass
else:
self.assertRaises(TypeError, m.write, b"abcdef")
self.assertRaises(TypeError, m.write_byte, 0)
m.close()
@unittest.expectedFailure # TODO: RUSTPYTHON
@unittest.skipIf(os.name == 'nt', 'trackfd not present on Windows')
def test_trackfd_parameter(self):
size = 64
with open(TESTFN, "wb") as f:
f.write(b"a"*size)
for close_original_fd in True, False:
with self.subTest(close_original_fd=close_original_fd):
with open(TESTFN, "r+b") as f:
with mmap.mmap(f.fileno(), size, trackfd=False) as m:
if close_original_fd:
f.close()
self.assertEqual(len(m), size)
with self.assertRaises(OSError) as err_cm:
m.size()
self.assertEqual(err_cm.exception.errno, errno.EBADF)
with self.assertRaises(ValueError):
m.resize(size * 2)
with self.assertRaises(ValueError):
m.resize(size // 2)
self.assertEqual(m.closed, False)
# Smoke-test other API
m.write_byte(ord('X'))
m[2] = ord('Y')
m.flush()
with open(TESTFN, "rb") as f:
self.assertEqual(f.read(4), b'XaYa')
self.assertEqual(m.tell(), 1)
m.seek(0)
self.assertEqual(m.tell(), 0)
self.assertEqual(m.read_byte(), ord('X'))
self.assertEqual(m.closed, True)
self.assertEqual(os.stat(TESTFN).st_size, size)
@unittest.expectedFailure # TODO: RUSTPYTHON
@unittest.skipIf(os.name == 'nt', 'trackfd not present on Windows')
def test_trackfd_neg1(self):
size = 64
with mmap.mmap(-1, size, trackfd=False) as m:
with self.assertRaises(OSError):
m.size()
with self.assertRaises(ValueError):
m.resize(size // 2)
self.assertEqual(len(m), size)
m[0] = ord('a')
assert m[0] == ord('a')
@unittest.skipIf(os.name != 'nt', 'trackfd only fails on Windows')
def test_no_trackfd_parameter_on_windows(self):
# 'trackffd' is an invalid keyword argument for this function
size = 64
with self.assertRaises(TypeError):
mmap.mmap(-1, size, trackfd=True)
with self.assertRaises(TypeError):
mmap.mmap(-1, size, trackfd=False)
m = mmap.mmap(f.fileno(), mapsize, prot=prot)
self.assertRaises(TypeError, m.write, b"abcdef")
self.assertRaises(TypeError, m.write_byte, 0)
m.close()
def test_bad_file_desc(self):
# Try opening a bad file descriptor...
self.assertRaises(OSError, mmap.mmap, -2, 4096)
@unittest.expectedFailureIfWindows("TODO: RUSTPYTHON")
def test_tougher_find(self):
# Do a tougher .find() test. SF bug 515943 pointed out that, in 2.2,
# searching for data with embedded \0 bytes didn't work.
@@ -356,7 +282,6 @@ class MmapTests(unittest.TestCase):
self.assertEqual(m.find(slice + b'x'), -1)
m.close()
@unittest.skip("TODO: RUSTPYTHON; panic")
def test_find_end(self):
# test the new 'end' parameter works as expected
with open(TESTFN, 'wb+') as f:
@@ -374,29 +299,7 @@ class MmapTests(unittest.TestCase):
self.assertEqual(m.find(b'one', 1, -2), -1)
self.assertEqual(m.find(bytearray(b'one')), 0)
for i in range(-n-1, n+1):
for j in range(-n-1, n+1):
for p in [b"o", b"on", b"two", b"ones", b"s"]:
expected = data.find(p, i, j)
self.assertEqual(m.find(p, i, j), expected, (p, i, j))
def test_find_does_not_access_beyond_buffer(self):
try:
flags = mmap.MAP_PRIVATE | mmap.MAP_ANONYMOUS
PAGESIZE = mmap.PAGESIZE
PROT_NONE = 0
PROT_READ = mmap.PROT_READ
except AttributeError as e:
raise unittest.SkipTest("mmap flags unavailable") from e
for i in range(0, 2049):
with mmap.mmap(-1, PAGESIZE * (i + 1),
flags=flags, prot=PROT_NONE) as guard:
with mmap.mmap(-1, PAGESIZE * (i + 2048),
flags=flags, prot=PROT_READ) as fm:
fm.find(b"fo", -2)
@unittest.expectedFailureIfWindows("TODO: RUSTPYTHON")
def test_rfind(self):
# test the new 'end' parameter works as expected
with open(TESTFN, 'wb+') as f:
@@ -457,7 +360,6 @@ class MmapTests(unittest.TestCase):
self.assertRaises(ValueError, mmap.mmap, f.fileno(), 0,
offset=2147418112)
@unittest.expectedFailureIfWindows("TODO: RUSTPYTHON")
def test_move(self):
# make move works everywhere (64-bit format problem earlier)
with open(TESTFN, 'wb+') as f:
@@ -505,6 +407,7 @@ class MmapTests(unittest.TestCase):
m.move(0, 0, 1)
m.move(0, 0, 0)
def test_anonymous(self):
# anonymous mmap.mmap(-1, PAGE)
m = mmap.mmap(-1, PAGESIZE)
@@ -517,7 +420,6 @@ class MmapTests(unittest.TestCase):
m[x] = b
self.assertEqual(m[x], b)
@unittest.expectedFailureIfWindows("TODO: RUSTPYTHON")
def test_read_all(self):
m = mmap.mmap(-1, 16)
self.addCleanup(m.close)
@@ -539,7 +441,6 @@ class MmapTests(unittest.TestCase):
m.seek(9)
self.assertEqual(m.read(-42), bytes(range(9, 16)))
@unittest.expectedFailureIfWindows("TODO: RUSTPYTHON")
def test_read_invalid_arg(self):
m = mmap.mmap(-1, 16)
self.addCleanup(m.close)
@@ -656,7 +557,6 @@ class MmapTests(unittest.TestCase):
except OSError:
pass
@unittest.expectedFailureIfWindows("TODO: RUSTPYTHON")
def test_subclass(self):
class anon_mmap(mmap.mmap):
def __new__(klass, *args, **kwargs):
@@ -709,7 +609,6 @@ class MmapTests(unittest.TestCase):
self.assertEqual(m[:], b"012barbaz9")
self.assertRaises(ValueError, m.write, b"ba")
@unittest.expectedFailureIfWindows("TODO: RUSTPYTHON")
def test_non_ascii_byte(self):
for b in (129, 200, 255): # > 128
m = mmap.mmap(-1, 1)
@@ -719,7 +618,6 @@ class MmapTests(unittest.TestCase):
self.assertEqual(m.read_byte(), b)
m.close()
@unittest.expectedFailureIfWindows("TODO: RUSTPYTHON")
@unittest.skipUnless(os.name == 'nt', 'requires Windows')
def test_tagname(self):
data1 = b"0123456789"
@@ -748,16 +646,14 @@ class MmapTests(unittest.TestCase):
m2.close()
m1.close()
with self.assertRaisesRegex(TypeError, 'tagname'):
mmap.mmap(-1, 8, tagname=1)
@cpython_only
@unittest.skipUnless(os.name == 'nt', 'requires Windows')
def test_sizeof(self):
m1 = mmap.mmap(-1, 100)
tagname = random_tagname()
m2 = mmap.mmap(-1, 100, tagname=tagname)
self.assertGreater(sys.getsizeof(m2), sys.getsizeof(m1))
self.assertEqual(sys.getsizeof(m2),
sys.getsizeof(m1) + len(tagname) + 1)
@unittest.skipUnless(os.name == 'nt', 'requires Windows')
def test_crasher_on_windows(self):
@@ -812,7 +708,6 @@ class MmapTests(unittest.TestCase):
"wrong exception raised in context manager")
self.assertTrue(m.closed, "context manager failed")
@unittest.expectedFailureIfWindows("TODO: RUSTPYTHON")
def test_weakref(self):
# Check mmap objects are weakrefable
mm = mmap.mmap(-1, 16)
@@ -822,7 +717,6 @@ class MmapTests(unittest.TestCase):
gc_collect()
self.assertIs(wr(), None)
@unittest.expectedFailureIfWindows("TODO: RUSTPYTHON")
def test_write_returning_the_number_of_bytes_written(self):
mm = mmap.mmap(-1, 16)
self.assertEqual(mm.write(b""), 0)
@@ -830,7 +724,6 @@ class MmapTests(unittest.TestCase):
self.assertEqual(mm.write(b"yz"), 2)
self.assertEqual(mm.write(b"python"), 6)
@unittest.expectedFailureIfWindows("TODO: RUSTPYTHON")
def test_resize_past_pos(self):
m = mmap.mmap(-1, 8192)
self.addCleanup(m.close)
@@ -851,7 +744,7 @@ class MmapTests(unittest.TestCase):
with self.assertRaises(TypeError):
m * 2
@unittest.skipIf(sys.platform.startswith("linux"), "TODO: RUSTPYTHON; memmap2 doesn't throw OSError when offset is not a multiple of mmap.PAGESIZE on Linux")
@unittest.skipIf(sys.platform.startswith("linux"), "TODO: RUSTPYTHON, memmap2 doesn't throw OSError when offset is not a multiple of mmap.PAGESIZE on Linux")
def test_flush_return_value(self):
# mm.flush() should return None on success, raise an
# exception on error under all platforms.
@@ -860,13 +753,11 @@ class MmapTests(unittest.TestCase):
mm.write(b'python')
result = mm.flush()
self.assertIsNone(result)
if (sys.platform.startswith(('linux', 'android'))
and not in_systemd_nspawn_sync_suppressed()):
if sys.platform.startswith('linux'):
# 'offset' must be a multiple of mmap.PAGESIZE on Linux.
# See bpo-34754 for details.
self.assertRaises(OSError, mm.flush, 1, len(b'python'))
@unittest.expectedFailureIfWindows("TODO: RUSTPYTHON")
def test_repr(self):
open_mmap_repr_pat = re.compile(
r"<mmap.mmap closed=False, "
@@ -903,16 +794,11 @@ class MmapTests(unittest.TestCase):
match = closed_mmap_repr_pat.match(repr(mm))
self.assertIsNotNone(match)
@unittest.expectedFailure # TODO: RUSTPYTHON
@unittest.skipUnless(hasattr(mmap.mmap, 'madvise'), 'needs madvise')
def test_madvise(self):
size = 2 * PAGESIZE
m = mmap.mmap(-1, size)
class Number:
def __index__(self):
return 2
with self.assertRaisesRegex(ValueError, "madvise start out of bounds"):
m.madvise(mmap.MADV_NORMAL, size)
with self.assertRaisesRegex(ValueError, "madvise start out of bounds"):
@@ -921,84 +807,42 @@ class MmapTests(unittest.TestCase):
m.madvise(mmap.MADV_NORMAL, 0, -1)
with self.assertRaisesRegex(OverflowError, "madvise length too large"):
m.madvise(mmap.MADV_NORMAL, PAGESIZE, sys.maxsize)
with self.assertRaisesRegex(
TypeError, "'str' object cannot be interpreted as an integer"):
m.madvise(mmap.MADV_NORMAL, PAGESIZE, "Not a Number")
self.assertEqual(m.madvise(mmap.MADV_NORMAL), None)
self.assertEqual(m.madvise(mmap.MADV_NORMAL, PAGESIZE), None)
self.assertEqual(m.madvise(mmap.MADV_NORMAL, PAGESIZE, size), None)
self.assertEqual(m.madvise(mmap.MADV_NORMAL, 0, 2), None)
self.assertEqual(m.madvise(mmap.MADV_NORMAL, 0, Number()), None)
self.assertEqual(m.madvise(mmap.MADV_NORMAL, 0, size), None)
@unittest.expectedFailureIf(sys.platform in ("linux", "win32"), "TODO: RUSTPYTHON")
def test_resize_up_anonymous_mapping(self):
@unittest.skipUnless(os.name == 'nt', 'requires Windows')
def test_resize_up_when_mapped_to_pagefile(self):
"""If the mmap is backed by the pagefile ensure a resize up can happen
and that the original data is still in place
"""
start_size = PAGESIZE
new_size = 2 * start_size
data = random.randbytes(start_size)
data = bytes(random.getrandbits(8) for _ in range(start_size))
with mmap.mmap(-1, start_size) as m:
m[:] = data
if sys.platform.startswith(('linux', 'android')):
# Can't expand a shared anonymous mapping on Linux.
# See https://bugzilla.kernel.org/show_bug.cgi?id=8691
with self.assertRaises(ValueError):
m.resize(new_size)
else:
try:
m.resize(new_size)
except SystemError:
pass
else:
self.assertEqual(len(m), new_size)
self.assertEqual(m[:start_size], data)
self.assertEqual(m[start_size:], b'\0' * (new_size - start_size))
m = mmap.mmap(-1, start_size)
m[:] = data
m.resize(new_size)
self.assertEqual(len(m), new_size)
self.assertEqual(m[:start_size], data[:start_size])
@unittest.expectedFailureIfWindows("TODO: RUSTPYTHON")
@unittest.skipUnless(os.name == 'posix', 'requires Posix')
def test_resize_up_private_anonymous_mapping(self):
start_size = PAGESIZE
new_size = 2 * start_size
data = random.randbytes(start_size)
with mmap.mmap(-1, start_size, flags=mmap.MAP_PRIVATE) as m:
m[:] = data
try:
m.resize(new_size)
except SystemError:
pass
else:
self.assertEqual(len(m), new_size)
self.assertEqual(m[:start_size], data)
self.assertEqual(m[start_size:], b'\0' * (new_size - start_size))
@unittest.expectedFailureIfWindows("TODO: RUSTPYTHON")
def test_resize_down_anonymous_mapping(self):
@unittest.skipUnless(os.name == 'nt', 'requires Windows')
def test_resize_down_when_mapped_to_pagefile(self):
"""If the mmap is backed by the pagefile ensure a resize down up can happen
and that a truncated form of the original data is still in place
"""
start_size = 2 * PAGESIZE
start_size = PAGESIZE
new_size = start_size // 2
data = random.randbytes(start_size)
data = bytes(random.getrandbits(8) for _ in range(start_size))
with mmap.mmap(-1, start_size) as m:
m[:] = data
try:
m.resize(new_size)
except SystemError:
pass
else:
self.assertEqual(len(m), new_size)
self.assertEqual(m[:], data[:new_size])
if sys.platform.startswith(('linux', 'android')):
# Can't expand to its original size.
with self.assertRaises(ValueError):
m.resize(start_size)
m = mmap.mmap(-1, start_size)
m[:] = data
m.resize(new_size)
self.assertEqual(len(m), new_size)
self.assertEqual(m[:new_size], data[:new_size])
@unittest.expectedFailureIfWindows("TODO: RUSTPYTHON")
@unittest.skipUnless(os.name == 'nt', 'requires Windows')
def test_resize_fails_if_mapping_held_elsewhere(self):
"""If more than one mapping is held against a named file on Windows, neither
@@ -1023,7 +867,6 @@ class MmapTests(unittest.TestCase):
finally:
f.close()
@unittest.expectedFailureIfWindows("TODO: RUSTPYTHON")
@unittest.skipUnless(os.name == 'nt', 'requires Windows')
def test_resize_succeeds_with_error_for_second_named_mapping(self):
"""If a more than one mapping exists of the same name, none of them can
@@ -1045,168 +888,6 @@ class MmapTests(unittest.TestCase):
self.assertEqual(m1[:data_length], data)
self.assertEqual(m2[:data_length], data)
@unittest.expectedFailure # TODO: RUSTPYTHON
def test_mmap_closed_by_int_scenarios(self):
"""
gh-103987: Test that mmap objects raise ValueError
for closed mmap files
"""
class MmapClosedByIntContext:
def __init__(self, access) -> None:
self.access = access
def __enter__(self):
self.f = open(TESTFN, "w+b")
self.f.write(random.randbytes(100))
self.f.flush()
m = mmap.mmap(self.f.fileno(), 100, access=self.access)
class X:
def __index__(self):
m.close()
return 10
return (m, X)
def __exit__(self, exc_type, exc_value, traceback):
self.f.close()
read_access_modes = [
mmap.ACCESS_READ,
mmap.ACCESS_WRITE,
mmap.ACCESS_COPY,
mmap.ACCESS_DEFAULT,
]
write_access_modes = [
mmap.ACCESS_WRITE,
mmap.ACCESS_COPY,
mmap.ACCESS_DEFAULT,
]
for access in read_access_modes:
with MmapClosedByIntContext(access) as (m, X):
with self.assertRaisesRegex(ValueError, "mmap closed or invalid"):
m[X()]
with MmapClosedByIntContext(access) as (m, X):
with self.assertRaisesRegex(ValueError, "mmap closed or invalid"):
m[X() : 20]
with MmapClosedByIntContext(access) as (m, X):
with self.assertRaisesRegex(ValueError, "mmap closed or invalid"):
m[X() : 20 : 2]
with MmapClosedByIntContext(access) as (m, X):
with self.assertRaisesRegex(ValueError, "mmap closed or invalid"):
m[20 : X() : -2]
with MmapClosedByIntContext(access) as (m, X):
with self.assertRaisesRegex(ValueError, "mmap closed or invalid"):
m.read(X())
with MmapClosedByIntContext(access) as (m, X):
with self.assertRaisesRegex(ValueError, "mmap closed or invalid"):
m.find(b"1", 1, X())
for access in write_access_modes:
with MmapClosedByIntContext(access) as (m, X):
with self.assertRaisesRegex(ValueError, "mmap closed or invalid"):
m[X() : 20] = b"1" * 10
with MmapClosedByIntContext(access) as (m, X):
with self.assertRaisesRegex(ValueError, "mmap closed or invalid"):
m[X() : 20 : 2] = b"1" * 5
with MmapClosedByIntContext(access) as (m, X):
with self.assertRaisesRegex(ValueError, "mmap closed or invalid"):
m[20 : X() : -2] = b"1" * 5
with MmapClosedByIntContext(access) as (m, X):
with self.assertRaisesRegex(ValueError, "mmap closed or invalid"):
m.move(1, 2, X())
with MmapClosedByIntContext(access) as (m, X):
with self.assertRaisesRegex(ValueError, "mmap closed or invalid"):
m.write_byte(X())
@unittest.skipUnless(os.name == 'nt', 'requires Windows')
@unittest.skipUnless(hasattr(mmap.mmap, '_protect'), 'test needs debug build')
def test_access_violations(self):
from test.support.os_helper import TESTFN
code = textwrap.dedent("""
import faulthandler
import mmap
import os
import sys
from contextlib import suppress
# Prevent logging access violations to stderr.
faulthandler.disable()
PAGESIZE = mmap.PAGESIZE
PAGE_NOACCESS = 0x01
with open(sys.argv[1], 'bw+') as f:
f.write(b'A'* PAGESIZE)
f.flush()
m = mmap.mmap(f.fileno(), PAGESIZE)
m._protect(PAGE_NOACCESS, 0, PAGESIZE)
with suppress(OSError):
m.read(PAGESIZE)
assert False, 'mmap.read() did not raise'
with suppress(OSError):
m.read_byte()
assert False, 'mmap.read_byte() did not raise'
with suppress(OSError):
m.readline()
assert False, 'mmap.readline() did not raise'
with suppress(OSError):
m.write(b'A'* PAGESIZE)
assert False, 'mmap.write() did not raise'
with suppress(OSError):
m.write_byte(0)
assert False, 'mmap.write_byte() did not raise'
with suppress(OSError):
m[0] # test mmap_subscript
assert False, 'mmap.__getitem__() did not raise'
with suppress(OSError):
m[0:10] # test mmap_subscript
assert False, 'mmap.__getitem__() did not raise'
with suppress(OSError):
m[0:10:2] # test mmap_subscript
assert False, 'mmap.__getitem__() did not raise'
with suppress(OSError):
m[0] = 1
assert False, 'mmap.__setitem__() did not raise'
with suppress(OSError):
m[0:10] = b'A'* 10
assert False, 'mmap.__setitem__() did not raise'
with suppress(OSError):
m[0:10:2] = b'A'* 5
assert False, 'mmap.__setitem__() did not raise'
with suppress(OSError):
m.move(0, 10, 1)
assert False, 'mmap.move() did not raise'
with suppress(OSError):
list(m) # test mmap_item
assert False, 'mmap.__getitem__() did not raise'
with suppress(OSError):
m.find(b'A')
assert False, 'mmap.find() did not raise'
with suppress(OSError):
m.rfind(b'A')
assert False, 'mmap.rfind() did not raise'
""")
rt, stdout, stderr = assert_python_ok("-c", code, TESTFN)
self.assertEqual(stdout.strip(), b'')
self.assertEqual(stderr.strip(), b'')
class LargeMmapTests(unittest.TestCase):
def setUp(self):
@@ -1216,7 +897,7 @@ class LargeMmapTests(unittest.TestCase):
unlink(TESTFN)
def _make_test_file(self, num_zeroes, tail):
if sys.platform[:3] == 'win' or is_apple:
if sys.platform[:3] == 'win' or sys.platform == 'darwin':
requires('largefile',
'test requires %s bytes and a long time to run' % str(0x180000000))
f = open(TESTFN, 'w+b')

View File

@@ -1,415 +0,0 @@
#
# test_multibytecodec.py
# Unit test for multibytecodec itself
#
import codecs
import io
import sys
import textwrap
import unittest
try:
import _multibytecodec
except ImportError:
# TODO: RUSTPYTHON; _multibytecodec not implemented
raise unittest.SkipTest('_multibytecodec not available')
from test import support
from test.support import os_helper
from test.support.os_helper import TESTFN
from test.support.import_helper import import_module
ALL_CJKENCODINGS = [
# _codecs_cn
'gb2312', 'gbk', 'gb18030', 'hz',
# _codecs_hk
'big5hkscs',
# _codecs_jp
'cp932', 'shift_jis', 'euc_jp', 'euc_jisx0213', 'shift_jisx0213',
'euc_jis_2004', 'shift_jis_2004',
# _codecs_kr
'cp949', 'euc_kr', 'johab',
# _codecs_tw
'big5', 'cp950',
# _codecs_iso2022
'iso2022_jp', 'iso2022_jp_1', 'iso2022_jp_2', 'iso2022_jp_2004',
'iso2022_jp_3', 'iso2022_jp_ext', 'iso2022_kr',
]
class Test_MultibyteCodec(unittest.TestCase):
def test_nullcoding(self):
for enc in ALL_CJKENCODINGS:
self.assertEqual(b''.decode(enc), '')
self.assertEqual(str(b'', enc), '')
self.assertEqual(''.encode(enc), b'')
def test_str_decode(self):
for enc in ALL_CJKENCODINGS:
self.assertEqual('abcd'.encode(enc), b'abcd')
def test_errorcallback_longindex(self):
dec = codecs.getdecoder('euc-kr')
myreplace = lambda exc: ('', sys.maxsize+1)
codecs.register_error('test.cjktest', myreplace)
self.assertRaises(IndexError, dec,
b'apple\x92ham\x93spam', 'test.cjktest')
def test_errorcallback_custom_ignore(self):
# Issue #23215: MemoryError with custom error handlers and multibyte codecs
data = 100 * "\udc00"
codecs.register_error("test.ignore", codecs.ignore_errors)
for enc in ALL_CJKENCODINGS:
self.assertEqual(data.encode(enc, "test.ignore"), b'')
def test_codingspec(self):
try:
for enc in ALL_CJKENCODINGS:
code = '# coding: {}\n'.format(enc)
exec(code)
finally:
os_helper.unlink(TESTFN)
def test_init_segfault(self):
# bug #3305: this used to segfault
self.assertRaises(AttributeError,
_multibytecodec.MultibyteStreamReader, None)
self.assertRaises(AttributeError,
_multibytecodec.MultibyteStreamWriter, None)
def test_decode_unicode(self):
# Trying to decode a unicode string should raise a TypeError
for enc in ALL_CJKENCODINGS:
self.assertRaises(TypeError, codecs.getdecoder(enc), "")
class Test_IncrementalEncoder(unittest.TestCase):
def test_stateless(self):
# cp949 encoder isn't stateful at all.
encoder = codecs.getincrementalencoder('cp949')()
self.assertEqual(encoder.encode('\ud30c\uc774\uc36c \ub9c8\uc744'),
b'\xc6\xc4\xc0\xcc\xbd\xe3 \xb8\xb6\xc0\xbb')
self.assertEqual(encoder.reset(), None)
self.assertEqual(encoder.encode('\u2606\u223c\u2606', True),
b'\xa1\xd9\xa1\xad\xa1\xd9')
self.assertEqual(encoder.reset(), None)
self.assertEqual(encoder.encode('', True), b'')
self.assertEqual(encoder.encode('', False), b'')
self.assertEqual(encoder.reset(), None)
def test_stateful(self):
# jisx0213 encoder is stateful for a few code points. eg)
# U+00E6 => A9DC
# U+00E6 U+0300 => ABC4
# U+0300 => ABDC
encoder = codecs.getincrementalencoder('jisx0213')()
self.assertEqual(encoder.encode('\u00e6\u0300'), b'\xab\xc4')
self.assertEqual(encoder.encode('\u00e6'), b'')
self.assertEqual(encoder.encode('\u0300'), b'\xab\xc4')
self.assertEqual(encoder.encode('\u00e6', True), b'\xa9\xdc')
self.assertEqual(encoder.reset(), None)
self.assertEqual(encoder.encode('\u0300'), b'\xab\xdc')
self.assertEqual(encoder.encode('\u00e6'), b'')
self.assertEqual(encoder.encode('', True), b'\xa9\xdc')
self.assertEqual(encoder.encode('', True), b'')
def test_stateful_keep_buffer(self):
encoder = codecs.getincrementalencoder('jisx0213')()
self.assertEqual(encoder.encode('\u00e6'), b'')
self.assertRaises(UnicodeEncodeError, encoder.encode, '\u0123')
self.assertEqual(encoder.encode('\u0300\u00e6'), b'\xab\xc4')
self.assertRaises(UnicodeEncodeError, encoder.encode, '\u0123')
self.assertEqual(encoder.reset(), None)
self.assertEqual(encoder.encode('\u0300'), b'\xab\xdc')
self.assertEqual(encoder.encode('\u00e6'), b'')
self.assertRaises(UnicodeEncodeError, encoder.encode, '\u0123')
self.assertEqual(encoder.encode('', True), b'\xa9\xdc')
def test_state_methods_with_buffer_state(self):
# euc_jis_2004 stores state as a buffer of pending bytes
encoder = codecs.getincrementalencoder('euc_jis_2004')()
initial_state = encoder.getstate()
self.assertEqual(encoder.encode('\u00e6\u0300'), b'\xab\xc4')
encoder.setstate(initial_state)
self.assertEqual(encoder.encode('\u00e6\u0300'), b'\xab\xc4')
self.assertEqual(encoder.encode('\u00e6'), b'')
partial_state = encoder.getstate()
self.assertEqual(encoder.encode('\u0300'), b'\xab\xc4')
encoder.setstate(partial_state)
self.assertEqual(encoder.encode('\u0300'), b'\xab\xc4')
def test_state_methods_with_non_buffer_state(self):
# iso2022_jp stores state without using a buffer
encoder = codecs.getincrementalencoder('iso2022_jp')()
self.assertEqual(encoder.encode('z'), b'z')
en_state = encoder.getstate()
self.assertEqual(encoder.encode('\u3042'), b'\x1b\x24\x42\x24\x22')
jp_state = encoder.getstate()
self.assertEqual(encoder.encode('z'), b'\x1b\x28\x42z')
encoder.setstate(jp_state)
self.assertEqual(encoder.encode('\u3042'), b'\x24\x22')
encoder.setstate(en_state)
self.assertEqual(encoder.encode('z'), b'z')
def test_getstate_returns_expected_value(self):
# Note: getstate is implemented such that these state values
# are expected to be the same across all builds of Python,
# regardless of x32/64 bit, endianness and compiler.
# euc_jis_2004 stores state as a buffer of pending bytes
buffer_state_encoder = codecs.getincrementalencoder('euc_jis_2004')()
self.assertEqual(buffer_state_encoder.getstate(), 0)
buffer_state_encoder.encode('\u00e6')
self.assertEqual(buffer_state_encoder.getstate(),
int.from_bytes(
b"\x02"
b"\xc3\xa6"
b"\x00\x00\x00\x00\x00\x00\x00\x00",
'little'))
buffer_state_encoder.encode('\u0300')
self.assertEqual(buffer_state_encoder.getstate(), 0)
# iso2022_jp stores state without using a buffer
non_buffer_state_encoder = codecs.getincrementalencoder('iso2022_jp')()
self.assertEqual(non_buffer_state_encoder.getstate(),
int.from_bytes(
b"\x00"
b"\x42\x42\x00\x00\x00\x00\x00\x00",
'little'))
non_buffer_state_encoder.encode('\u3042')
self.assertEqual(non_buffer_state_encoder.getstate(),
int.from_bytes(
b"\x00"
b"\xc2\x42\x00\x00\x00\x00\x00\x00",
'little'))
def test_setstate_validates_input_size(self):
encoder = codecs.getincrementalencoder('euc_jp')()
pending_size_nine = int.from_bytes(
b"\x09"
b"\x00\x00\x00\x00\x00\x00\x00\x00"
b"\x00\x00\x00\x00\x00\x00\x00\x00",
'little')
self.assertRaises(UnicodeError, encoder.setstate, pending_size_nine)
def test_setstate_validates_input_bytes(self):
encoder = codecs.getincrementalencoder('euc_jp')()
invalid_utf8 = int.from_bytes(
b"\x01"
b"\xff"
b"\x00\x00\x00\x00\x00\x00\x00\x00",
'little')
self.assertRaises(UnicodeDecodeError, encoder.setstate, invalid_utf8)
def test_issue5640(self):
encoder = codecs.getincrementalencoder('shift-jis')('backslashreplace')
self.assertEqual(encoder.encode('\xff'), b'\\xff')
self.assertEqual(encoder.encode('\n'), b'\n')
@support.cpython_only
def test_subinterp(self):
# bpo-42846: Test a CJK codec in a subinterpreter
_testcapi = import_module("_testcapi")
encoding = 'cp932'
text = "Python の開発は、1990 年ごろから開始されています。"
code = textwrap.dedent("""
import codecs
encoding = %r
text = %r
encoder = codecs.getincrementalencoder(encoding)()
text2 = encoder.encode(text).decode(encoding)
if text2 != text:
raise ValueError(f"encoding issue: {text2!a} != {text!a}")
""") % (encoding, text)
res = _testcapi.run_in_subinterp(code)
self.assertEqual(res, 0)
class Test_IncrementalDecoder(unittest.TestCase):
def test_dbcs(self):
# cp949 decoder is simple with only 1 or 2 bytes sequences.
decoder = codecs.getincrementaldecoder('cp949')()
self.assertEqual(decoder.decode(b'\xc6\xc4\xc0\xcc\xbd'),
'\ud30c\uc774')
self.assertEqual(decoder.decode(b'\xe3 \xb8\xb6\xc0\xbb'),
'\uc36c \ub9c8\uc744')
self.assertEqual(decoder.decode(b''), '')
def test_dbcs_keep_buffer(self):
decoder = codecs.getincrementaldecoder('cp949')()
self.assertEqual(decoder.decode(b'\xc6\xc4\xc0'), '\ud30c')
self.assertRaises(UnicodeDecodeError, decoder.decode, b'', True)
self.assertEqual(decoder.decode(b'\xcc'), '\uc774')
self.assertEqual(decoder.decode(b'\xc6\xc4\xc0'), '\ud30c')
self.assertRaises(UnicodeDecodeError, decoder.decode,
b'\xcc\xbd', True)
self.assertEqual(decoder.decode(b'\xcc'), '\uc774')
def test_iso2022(self):
decoder = codecs.getincrementaldecoder('iso2022-jp')()
ESC = b'\x1b'
self.assertEqual(decoder.decode(ESC + b'('), '')
self.assertEqual(decoder.decode(b'B', True), '')
self.assertEqual(decoder.decode(ESC + b'$'), '')
self.assertEqual(decoder.decode(b'B@$'), '\u4e16')
self.assertEqual(decoder.decode(b'@$@'), '\u4e16')
self.assertEqual(decoder.decode(b'$', True), '\u4e16')
self.assertEqual(decoder.reset(), None)
self.assertEqual(decoder.decode(b'@$'), '@$')
self.assertEqual(decoder.decode(ESC + b'$'), '')
self.assertRaises(UnicodeDecodeError, decoder.decode, b'', True)
self.assertEqual(decoder.decode(b'B@$'), '\u4e16')
def test_decode_unicode(self):
# Trying to decode a unicode string should raise a TypeError
for enc in ALL_CJKENCODINGS:
decoder = codecs.getincrementaldecoder(enc)()
self.assertRaises(TypeError, decoder.decode, "")
def test_state_methods(self):
decoder = codecs.getincrementaldecoder('euc_jp')()
# Decode a complete input sequence
self.assertEqual(decoder.decode(b'\xa4\xa6'), '\u3046')
pending1, _ = decoder.getstate()
self.assertEqual(pending1, b'')
# Decode first half of a partial input sequence
self.assertEqual(decoder.decode(b'\xa4'), '')
pending2, flags2 = decoder.getstate()
self.assertEqual(pending2, b'\xa4')
# Decode second half of a partial input sequence
self.assertEqual(decoder.decode(b'\xa6'), '\u3046')
pending3, _ = decoder.getstate()
self.assertEqual(pending3, b'')
# Jump back and decode second half of partial input sequence again
decoder.setstate((pending2, flags2))
self.assertEqual(decoder.decode(b'\xa6'), '\u3046')
pending4, _ = decoder.getstate()
self.assertEqual(pending4, b'')
# Ensure state values are preserved correctly
decoder.setstate((b'abc', 123456789))
self.assertEqual(decoder.getstate(), (b'abc', 123456789))
def test_setstate_validates_input(self):
decoder = codecs.getincrementaldecoder('euc_jp')()
self.assertRaises(TypeError, decoder.setstate, 123)
self.assertRaises(TypeError, decoder.setstate, ("invalid", 0))
self.assertRaises(TypeError, decoder.setstate, (b"1234", "invalid"))
self.assertRaises(UnicodeDecodeError, decoder.setstate, (b"123456789", 0))
class Test_StreamReader(unittest.TestCase):
def test_bug1728403(self):
try:
f = open(TESTFN, 'wb')
try:
f.write(b'\xa1')
finally:
f.close()
with self.assertWarns(DeprecationWarning):
f = codecs.open(TESTFN, encoding='cp949')
try:
self.assertRaises(UnicodeDecodeError, f.read, 2)
finally:
f.close()
finally:
os_helper.unlink(TESTFN)
class Test_StreamWriter(unittest.TestCase):
def test_gb18030(self):
s= io.BytesIO()
c = codecs.getwriter('gb18030')(s)
c.write('123')
self.assertEqual(s.getvalue(), b'123')
c.write('\U00012345')
self.assertEqual(s.getvalue(), b'123\x907\x959')
c.write('\uac00\u00ac')
self.assertEqual(s.getvalue(),
b'123\x907\x959\x827\xcf5\x810\x851')
def test_utf_8(self):
s= io.BytesIO()
c = codecs.getwriter('utf-8')(s)
c.write('123')
self.assertEqual(s.getvalue(), b'123')
c.write('\U00012345')
self.assertEqual(s.getvalue(), b'123\xf0\x92\x8d\x85')
c.write('\uac00\u00ac')
self.assertEqual(s.getvalue(),
b'123\xf0\x92\x8d\x85'
b'\xea\xb0\x80\xc2\xac')
def test_streamwriter_strwrite(self):
s = io.BytesIO()
wr = codecs.getwriter('gb18030')(s)
wr.write('abcd')
self.assertEqual(s.getvalue(), b'abcd')
class Test_ISO2022(unittest.TestCase):
def test_g2(self):
iso2022jp2 = b'\x1b(B:hu4:unit\x1b.A\x1bNi de famille'
uni = ':hu4:unit\xe9 de famille'
self.assertEqual(iso2022jp2.decode('iso2022-jp-2'), uni)
def test_iso2022_jp_g0(self):
self.assertNotIn(b'\x0e', '\N{SOFT HYPHEN}'.encode('iso-2022-jp-2'))
for encoding in ('iso-2022-jp-2004', 'iso-2022-jp-3'):
e = '\u3406'.encode(encoding)
self.assertFalse(any(x > 0x80 for x in e))
@support.requires_resource('cpu')
def test_bug1572832(self):
for x in range(0x10000, 0x110000):
# Any ISO 2022 codec will cause the segfault
chr(x).encode('iso_2022_jp', 'ignore')
class TestStateful(unittest.TestCase):
text = '\u4E16\u4E16'
encoding = 'iso-2022-jp'
expected = b'\x1b$B@$@$'
reset = b'\x1b(B'
expected_reset = expected + reset
def test_encode(self):
self.assertEqual(self.text.encode(self.encoding), self.expected_reset)
def test_incrementalencoder(self):
encoder = codecs.getincrementalencoder(self.encoding)()
output = b''.join(
encoder.encode(char)
for char in self.text)
self.assertEqual(output, self.expected)
self.assertEqual(encoder.encode('', final=True), self.reset)
self.assertEqual(encoder.encode('', final=True), b'')
def test_incrementalencoder_final(self):
encoder = codecs.getincrementalencoder(self.encoding)()
last_index = len(self.text) - 1
output = b''.join(
encoder.encode(char, index == last_index)
for index, char in enumerate(self.text))
self.assertEqual(output, self.expected_reset)
self.assertEqual(encoder.encode('', final=True), b'')
class TestHZStateful(TestStateful):
text = '\u804a\u804a'
encoding = 'hz'
expected = b'~{ADAD'
reset = b'~}'
expected_reset = expected + reset
if __name__ == "__main__":
unittest.main()

View File

@@ -3,5 +3,6 @@ from test._test_multiprocessing import install_tests_in_module_dict
install_tests_in_module_dict(globals(), 'fork', only_type="processes")
if __name__ == '__main__':
unittest.main()

View File

@@ -90,6 +90,7 @@ class NamedExpressionInvalidTest(unittest.TestCase):
with self.assertRaisesRegex(SyntaxError, "invalid syntax"):
exec(code, {}, {})
@unittest.expectedFailure # TODO: RUSTPYTHON; wrong error message
def test_named_expression_invalid_15(self):
code = """(lambda: x := 1)"""

View File

@@ -1934,11 +1934,6 @@ class _PosixSpawnMixin:
)
support.wait_process(pid, exitcode=0)
def test_setpgroup_allow_none(self):
path, args = self.NOOP_PROGRAM[0], self.NOOP_PROGRAM
pid = self.spawn_func(path, args, os.environ, setpgroup=None)
support.wait_process(pid, exitcode=0)
def test_setpgroup_wrong_type(self):
with self.assertRaises(TypeError):
self.spawn_func(sys.executable,
@@ -2039,21 +2034,6 @@ class _PosixSpawnMixin:
[sys.executable, "-c", "pass"],
os.environ, setsigdef=[signal.NSIG, signal.NSIG+1])
def test_scheduler_allow_none(self):
path, args = self.NOOP_PROGRAM[0], self.NOOP_PROGRAM
pid = self.spawn_func(path, args, os.environ, scheduler=None)
support.wait_process(pid, exitcode=0)
@unittest.expectedFailure # TODO: RUSTPYTHON; Wrong error message
@support.subTests("scheduler", [object(), 1, [1, 2]])
def test_scheduler_wrong_type(self, scheduler):
path, args = self.NOOP_PROGRAM[0], self.NOOP_PROGRAM
with self.assertRaisesRegex(
TypeError,
"scheduler must be a tuple or None",
):
self.spawn_func(path, args, os.environ, scheduler=scheduler)
@unittest.expectedFailureIf(sys.platform in ("darwin", "linux"), "TODO: RUSTPYTHON; NotImplementedError: scheduler parameter is not yet implemented")
@requires_sched
@unittest.skipIf(sys.platform.startswith(('freebsd', 'netbsd')),

View File

@@ -1,223 +0,0 @@
"""Test suite for the profile module."""
import sys
import pstats
import unittest
import os
from difflib import unified_diff
from io import StringIO
from test.support.os_helper import TESTFN, unlink, temp_dir, change_cwd
from contextlib import contextmanager, redirect_stdout
import profile
from test.profilee import testfunc, timer
from test.support.script_helper import assert_python_failure, assert_python_ok
class ProfileTest(unittest.TestCase):
profilerclass = profile.Profile
profilermodule = profile
methodnames = ['print_stats', 'print_callers', 'print_callees']
expected_max_output = ':0(max)'
def tearDown(self):
unlink(TESTFN)
def get_expected_output(self):
return _ProfileOutput
@classmethod
def do_profiling(cls):
results = []
prof = cls.profilerclass(timer, 0.001)
start_timer = timer()
prof.runctx("testfunc()", globals(), locals())
results.append(timer() - start_timer)
for methodname in cls.methodnames:
s = StringIO()
stats = pstats.Stats(prof, stream=s)
stats.strip_dirs().sort_stats("stdname")
getattr(stats, methodname)()
output = s.getvalue().splitlines()
mod_name = testfunc.__module__.rsplit('.', 1)[1]
# Only compare against stats originating from the test file.
# Prevents outside code (e.g., the io module) from causing
# unexpected output.
output = [line.rstrip() for line in output if mod_name in line]
results.append('\n'.join(output))
return results
@unittest.expectedFailure # TODO: RUSTPYTHON; print_callees output differs from CPython
def test_cprofile(self):
results = self.do_profiling()
expected = self.get_expected_output()
self.assertEqual(results[0], 1000)
fail = []
for i, method in enumerate(self.methodnames):
a = expected[method]
b = results[i+1]
if a != b:
fail.append(f"\nStats.{method} output for "
f"{self.profilerclass.__name__} "
"does not fit expectation:")
fail.extend(unified_diff(a.split('\n'), b.split('\n'),
lineterm=""))
if fail:
self.fail("\n".join(fail))
def test_calling_conventions(self):
# Issue #5330: profile and cProfile wouldn't report C functions called
# with keyword arguments. We test all calling conventions.
stmts = [
"max([0])",
"max([0], key=int)",
"max([0], **dict(key=int))",
"max(*([0],))",
"max(*([0],), key=int)",
"max(*([0],), **dict(key=int))",
]
for stmt in stmts:
s = StringIO()
prof = self.profilerclass(timer, 0.001)
prof.runctx(stmt, globals(), locals())
stats = pstats.Stats(prof, stream=s)
stats.print_stats()
res = s.getvalue()
self.assertIn(self.expected_max_output, res,
"Profiling {0!r} didn't report max:\n{1}".format(stmt, res))
def test_run(self):
with silent():
self.profilermodule.run("int('1')")
self.profilermodule.run("int('1')", filename=TESTFN)
self.assertTrue(os.path.exists(TESTFN))
def test_run_with_sort_by_values(self):
with redirect_stdout(StringIO()) as f:
self.profilermodule.run("int('1')", sort=('tottime', 'stdname'))
self.assertIn("Ordered by: internal time, standard name", f.getvalue())
def test_runctx(self):
with silent():
self.profilermodule.runctx("testfunc()", globals(), locals())
self.profilermodule.runctx("testfunc()", globals(), locals(),
filename=TESTFN)
self.assertTrue(os.path.exists(TESTFN))
def test_run_profile_as_module(self):
# Test that -m switch needs an argument
assert_python_failure('-m', self.profilermodule.__name__, '-m')
# Test failure for not-existent module
assert_python_failure('-m', self.profilermodule.__name__,
'-m', 'random_module_xyz')
# Test successful run
assert_python_ok('-m', self.profilermodule.__name__,
'-m', 'timeit', '-n', '1')
def test_output_file_when_changing_directory(self):
with temp_dir() as tmpdir, change_cwd(tmpdir):
os.mkdir('dest')
with open('demo.py', 'w', encoding="utf-8") as f:
f.write('import os; os.chdir("dest")')
assert_python_ok(
'-m', self.profilermodule.__name__,
'-o', 'out.pstats',
'demo.py',
)
self.assertTrue(os.path.exists('out.pstats'))
def regenerate_expected_output(filename, cls):
filename = filename.rstrip('co')
print('Regenerating %s...' % filename)
results = cls.do_profiling()
newfile = []
with open(filename, 'r') as f:
for line in f:
newfile.append(line)
if line.startswith('#--cut'):
break
with open(filename, 'w') as f:
f.writelines(newfile)
f.write("_ProfileOutput = {}\n")
for i, method in enumerate(cls.methodnames):
f.write('_ProfileOutput[%r] = """\\\n%s"""\n' % (
method, results[i+1]))
f.write('\nif __name__ == "__main__":\n main()\n')
@contextmanager
def silent():
stdout = sys.stdout
try:
sys.stdout = StringIO()
yield
finally:
sys.stdout = stdout
def main():
if '-r' not in sys.argv:
unittest.main()
else:
regenerate_expected_output(__file__, ProfileTest)
# Don't remove this comment. Everything below it is auto-generated.
#--cut--------------------------------------------------------------------------
_ProfileOutput = {}
_ProfileOutput['print_stats'] = """\
28 27.972 0.999 27.972 0.999 profilee.py:110(__getattr__)
1 269.996 269.996 999.769 999.769 profilee.py:25(testfunc)
23/3 149.937 6.519 169.917 56.639 profilee.py:35(factorial)
20 19.980 0.999 19.980 0.999 profilee.py:48(mul)
2 39.986 19.993 599.830 299.915 profilee.py:55(helper)
4 115.984 28.996 119.964 29.991 profilee.py:73(helper1)
2 -0.006 -0.003 139.946 69.973 profilee.py:84(helper2_indirect)
8 311.976 38.997 399.912 49.989 profilee.py:88(helper2)
8 63.976 7.997 79.960 9.995 profilee.py:98(subhelper)"""
_ProfileOutput['print_callers'] = """\
:0(append) <- profilee.py:73(helper1)(4) 119.964
:0(exception) <- profilee.py:73(helper1)(4) 119.964
:0(hasattr) <- profilee.py:73(helper1)(4) 119.964
profilee.py:88(helper2)(8) 399.912
profilee.py:110(__getattr__) <- :0(hasattr)(12) 11.964
profilee.py:98(subhelper)(16) 79.960
profilee.py:25(testfunc) <- <string>:1(<module>)(1) 999.767
profilee.py:35(factorial) <- profilee.py:25(testfunc)(1) 999.769
profilee.py:35(factorial)(20) 169.917
profilee.py:84(helper2_indirect)(2) 139.946
profilee.py:48(mul) <- profilee.py:35(factorial)(20) 169.917
profilee.py:55(helper) <- profilee.py:25(testfunc)(2) 999.769
profilee.py:73(helper1) <- profilee.py:55(helper)(4) 599.830
profilee.py:84(helper2_indirect) <- profilee.py:55(helper)(2) 599.830
profilee.py:88(helper2) <- profilee.py:55(helper)(6) 599.830
profilee.py:84(helper2_indirect)(2) 139.946
profilee.py:98(subhelper) <- profilee.py:88(helper2)(8) 399.912"""
_ProfileOutput['print_callees'] = """\
:0(hasattr) -> profilee.py:110(__getattr__)(12) 27.972
<string>:1(<module>) -> profilee.py:25(testfunc)(1) 999.769
profilee.py:110(__getattr__) ->
profilee.py:25(testfunc) -> profilee.py:35(factorial)(1) 169.917
profilee.py:55(helper)(2) 599.830
profilee.py:35(factorial) -> profilee.py:35(factorial)(20) 169.917
profilee.py:48(mul)(20) 19.980
profilee.py:48(mul) ->
profilee.py:55(helper) -> profilee.py:73(helper1)(4) 119.964
profilee.py:84(helper2_indirect)(2) 139.946
profilee.py:88(helper2)(6) 399.912
profilee.py:73(helper1) -> :0(append)(4) -0.004
profilee.py:84(helper2_indirect) -> profilee.py:35(factorial)(2) 169.917
profilee.py:88(helper2)(2) 399.912
profilee.py:88(helper2) -> :0(hasattr)(8) 11.964
profilee.py:98(subhelper)(8) 79.960
profilee.py:98(subhelper) -> profilee.py:110(__getattr__)(16) 27.972"""
if __name__ == "__main__":
main()

View File

@@ -1,164 +0,0 @@
import unittest
from test import support
from test.support.import_helper import ensure_lazy_imports
from io import StringIO
from pstats import SortKey
from enum import StrEnum, _test_simple_enum
import os
import pstats
import tempfile
try:
import cProfile # XXX: RUSTPYTHON
except ImportError:
cProfile = None
class LazyImportTest(unittest.TestCase):
@support.cpython_only
def test_lazy_import(self):
ensure_lazy_imports("pstats", {"typing"})
class AddCallersTestCase(unittest.TestCase):
"""Tests for pstats.add_callers helper."""
def test_combine_results(self):
# pstats.add_callers should combine the call results of both target
# and source by adding the call time. See issue1269.
# new format: used by the cProfile module
target = {"a": (1, 2, 3, 4)}
source = {"a": (1, 2, 3, 4), "b": (5, 6, 7, 8)}
new_callers = pstats.add_callers(target, source)
self.assertEqual(new_callers, {'a': (2, 4, 6, 8), 'b': (5, 6, 7, 8)})
# old format: used by the profile module
target = {"a": 1}
source = {"a": 1, "b": 5}
new_callers = pstats.add_callers(target, source)
self.assertEqual(new_callers, {'a': 2, 'b': 5})
class StatsTestCase(unittest.TestCase):
def setUp(self):
stats_file = support.findfile('pstats.pck')
self.stats = pstats.Stats(stats_file)
def test_add(self):
stream = StringIO()
stats = pstats.Stats(stream=stream)
stats.add(self.stats, self.stats)
def test_dump_and_load_works_correctly(self):
temp_storage_new = tempfile.NamedTemporaryFile(delete=False)
try:
self.stats.dump_stats(filename=temp_storage_new.name)
tmp_stats = pstats.Stats(temp_storage_new.name)
self.assertEqual(self.stats.stats, tmp_stats.stats)
finally:
temp_storage_new.close()
os.remove(temp_storage_new.name)
@unittest.skipUnless(cProfile, 'TODO: RUSTPYTHON; _lsprof not implemented')
def test_load_equivalent_to_init(self):
stats = pstats.Stats()
self.temp_storage = tempfile.NamedTemporaryFile(delete=False)
try:
cProfile.run('import os', filename=self.temp_storage.name)
stats.load_stats(self.temp_storage.name)
created = pstats.Stats(self.temp_storage.name)
self.assertEqual(stats.stats, created.stats)
finally:
self.temp_storage.close()
os.remove(self.temp_storage.name)
def test_loading_wrong_types(self):
stats = pstats.Stats()
with self.assertRaises(TypeError):
stats.load_stats(42)
def test_sort_stats_int(self):
valid_args = {-1: 'stdname',
0: 'calls',
1: 'time',
2: 'cumulative'}
for arg_int, arg_str in valid_args.items():
self.stats.sort_stats(arg_int)
self.assertEqual(self.stats.sort_type,
self.stats.sort_arg_dict_default[arg_str][-1])
def test_sort_stats_string(self):
for sort_name in ['calls', 'ncalls', 'cumtime', 'cumulative',
'filename', 'line', 'module', 'name', 'nfl', 'pcalls',
'stdname', 'time', 'tottime']:
self.stats.sort_stats(sort_name)
self.assertEqual(self.stats.sort_type,
self.stats.sort_arg_dict_default[sort_name][-1])
def test_sort_stats_partial(self):
sortkey = 'filename'
for sort_name in ['f', 'fi', 'fil', 'file', 'filen', 'filena',
'filenam', 'filename']:
self.stats.sort_stats(sort_name)
self.assertEqual(self.stats.sort_type,
self.stats.sort_arg_dict_default[sortkey][-1])
def test_sort_stats_enum(self):
for member in SortKey:
self.stats.sort_stats(member)
self.assertEqual(
self.stats.sort_type,
self.stats.sort_arg_dict_default[member.value][-1])
class CheckedSortKey(StrEnum):
CALLS = 'calls', 'ncalls'
CUMULATIVE = 'cumulative', 'cumtime'
FILENAME = 'filename', 'module'
LINE = 'line'
NAME = 'name'
NFL = 'nfl'
PCALLS = 'pcalls'
STDNAME = 'stdname'
TIME = 'time', 'tottime'
def __new__(cls, *values):
value = values[0]
obj = str.__new__(cls, value)
obj._value_ = value
for other_value in values[1:]:
cls._value2member_map_[other_value] = obj
obj._all_values = values
return obj
_test_simple_enum(CheckedSortKey, SortKey)
def test_sort_starts_mix(self):
self.assertRaises(TypeError, self.stats.sort_stats,
'calls',
SortKey.TIME)
self.assertRaises(TypeError, self.stats.sort_stats,
SortKey.TIME,
'calls')
@unittest.skipUnless(cProfile, 'TODO: RUSTPYTHON; _lsprof not implemented')
def test_get_stats_profile(self):
def pass1(): pass
def pass2(): pass
def pass3(): pass
pr = cProfile.Profile()
pr.enable()
pass1()
pass2()
pass3()
pr.create_stats()
ps = pstats.Stats(pr)
stats_profile = ps.get_stats_profile()
funcs_called = set(stats_profile.func_profiles.keys())
self.assertIn('pass1', funcs_called)
self.assertIn('pass2', funcs_called)
self.assertIn('pass3', funcs_called)
def test_SortKey_enum(self):
self.assertEqual(SortKey.FILENAME, 'filename')
self.assertNotEqual(SortKey.FILENAME, SortKey.CALLS)
if __name__ == "__main__":
unittest.main()

View File

@@ -3,7 +3,7 @@
import subprocess
import sys
import os
from test.support import script_helper, requires_subprocess
from test.support import script_helper
import unittest
from unittest import mock
@@ -69,12 +69,12 @@ class TestScriptHelper(unittest.TestCase):
self.assertNotIn('-E', popen_command)
@requires_subprocess()
class TestScriptHelperEnvironment(unittest.TestCase):
"""Code coverage for interpreter_requires_environment()."""
def setUp(self):
self.assertHasAttr(script_helper, '__cached_interp_requires_environment')
self.assertTrue(
hasattr(script_helper, '__cached_interp_requires_environment'))
# Reset the private cached state.
script_helper.__dict__['__cached_interp_requires_environment'] = None

View File

@@ -1,7 +1,5 @@
"""Unit tests for zero-argument super() & related machinery."""
import copy
import pickle
import textwrap
import threading
import unittest
@@ -9,6 +7,9 @@ from unittest.mock import patch
from test.support import import_helper, threading_helper
ADAPTIVE_WARMUP_DELAY = 2
class A:
def f(self):
return 'A'
@@ -90,7 +91,8 @@ class TestSuper(unittest.TestCase):
self.assertEqual(E().f(), 'AE')
@unittest.expectedFailure # TODO: RUSTPYTHON
# TODO: RUSTPYTHON; SyntaxError: name '__class__' is assigned to before global declaration
'''
def test_various___class___pathologies(self):
# See issue #12370
class X(A):
@@ -112,7 +114,7 @@ class TestSuper(unittest.TestCase):
__class__""", globals(), {})
self.assertIs(type(e.exception), NameError) # Not UnboundLocalError
class X:
# global __class__ # TODO: RUSTPYTHON; SyntaxError: name '__class__' is assigned to before global declaration
global __class__
__class__ = 42
def f():
__class__
@@ -120,11 +122,12 @@ class TestSuper(unittest.TestCase):
del globals()["__class__"]
self.assertNotIn("__class__", X.__dict__)
class X:
# nonlocal __class__ # TODO: RUSTPYTHON; SyntaxError: name '__class__' is assigned to before nonlocal declaration
nonlocal __class__
__class__ = 42
def f():
__class__
self.assertEqual(__class__, 42)
'''
def test___class___instancemethod(self):
# See issue #14857
@@ -188,7 +191,7 @@ class TestSuper(unittest.TestCase):
B = type("B", (), test_namespace)
self.assertIs(B.f(), B)
@unittest.expectedFailure # TODO: RUSTPYTHON
@unittest.expectedFailure # TODO: RUSTPYTHON
def test___class___mro(self):
# See issue #23722
test_class = None
@@ -446,7 +449,7 @@ class TestSuper(unittest.TestCase):
self.assertEqual(C().method(), super)
@unittest.expectedFailure # TODO: RUSTPYTHON; TypeError: type 'super' is not an acceptable base type
@unittest.expectedFailure # TODO: RUSTPYTHON; TypeError: type 'super' is not an acceptable base type
def test_super_subclass___class__(self):
class mysuper(super):
pass
@@ -466,8 +469,7 @@ class TestSuper(unittest.TestCase):
super(MyType, type(mytype)).__setattr__(mytype, "bar", 1)
self.assertEqual(mytype.bar, 1)
_testinternalcapi = import_helper.import_module("_testinternalcapi")
for _ in range(_testinternalcapi.SPECIALIZATION_THRESHOLD):
for _ in range(ADAPTIVE_WARMUP_DELAY):
test("foo1")
def test_reassigned_new(self):
@@ -486,8 +488,7 @@ class TestSuper(unittest.TestCase):
def __new__(cls):
return super().__new__(cls)
_testinternalcapi = import_helper.import_module("_testinternalcapi")
for _ in range(_testinternalcapi.SPECIALIZATION_THRESHOLD):
for _ in range(ADAPTIVE_WARMUP_DELAY):
C()
def test_mixed_staticmethod_hierarchy(self):
@@ -507,8 +508,7 @@ class TestSuper(unittest.TestCase):
def some(cls):
return super().some(cls)
_testinternalcapi = import_helper.import_module("_testinternalcapi")
for _ in range(_testinternalcapi.SPECIALIZATION_THRESHOLD):
for _ in range(ADAPTIVE_WARMUP_DELAY):
C.some(C)
@threading_helper.requires_working_threading()
@@ -544,74 +544,6 @@ class TestSuper(unittest.TestCase):
for thread in threads:
thread.join()
def test_special_methods(self):
for e in E(), E:
s = super(C, e)
self.assertEqual(s.__reduce__, e.__reduce__)
self.assertEqual(s.__reduce_ex__, e.__reduce_ex__)
self.assertEqual(s.__getstate__, e.__getstate__)
self.assertNotHasAttr(s, '__getnewargs__')
self.assertNotHasAttr(s, '__getnewargs_ex__')
self.assertNotHasAttr(s, '__setstate__')
self.assertNotHasAttr(s, '__copy__')
self.assertNotHasAttr(s, '__deepcopy__')
def test_pickling(self):
e = E()
e.x = 1
s = super(C, e)
for proto in range(pickle.HIGHEST_PROTOCOL + 1):
with self.subTest(proto=proto):
u = pickle.loads(pickle.dumps(s, proto))
self.assertEqual(u.f(), s.f())
self.assertIs(type(u), type(s))
self.assertIs(type(u.__self__), E)
self.assertEqual(u.__self__.x, 1)
self.assertIs(u.__thisclass__, C)
self.assertIs(u.__self_class__, E)
s = super(C, E)
for proto in range(pickle.HIGHEST_PROTOCOL + 1):
with self.subTest(proto=proto):
u = pickle.loads(pickle.dumps(s, proto))
self.assertEqual(u.cm(), s.cm())
self.assertEqual(u.f, s.f)
self.assertIs(type(u), type(s))
self.assertIs(u.__self__, E)
self.assertIs(u.__thisclass__, C)
self.assertIs(u.__self_class__, E)
def test_shallow_copying(self):
s = super(C, E())
self.assertIs(copy.copy(s), s)
s = super(C, E)
self.assertIs(copy.copy(s), s)
def test_deep_copying(self):
e = E()
e.x = [1]
s = super(C, e)
u = copy.deepcopy(s)
self.assertEqual(u.f(), s.f())
self.assertIs(type(u), type(s))
self.assertIsNot(u, s)
self.assertIs(type(u.__self__), E)
self.assertIsNot(u.__self__, e)
self.assertIsNot(u.__self__.x, e.x)
self.assertEqual(u.__self__.x, [1])
self.assertIs(u.__thisclass__, C)
self.assertIs(u.__self_class__, E)
s = super(C, E)
u = copy.deepcopy(s)
self.assertEqual(u.cm(), s.cm())
self.assertEqual(u.f, s.f)
self.assertIsNot(u, s)
self.assertIs(type(u), type(s))
self.assertIs(u.__self__, E)
self.assertIs(u.__thisclass__, C)
self.assertIs(u.__self_class__, E)
if __name__ == "__main__":
unittest.main()

View File

@@ -31,7 +31,7 @@ Errors from set_context():
Traceback (most recent call last):
SyntaxError: invalid syntax
>>> None = 1
>>> None = 1 # TODO: RUSTPYTHON; Wrong error message # doctest: +EXPECTED_FAILURE
Traceback (most recent call last):
SyntaxError: cannot assign to None
@@ -39,11 +39,11 @@ SyntaxError: cannot assign to None
Traceback (most recent call last):
SyntaxError: invalid syntax
>>> True = 1
>>> True = 1 # TODO: RUSTPYTHON; Wrong error message # doctest: +EXPECTED_FAILURE
Traceback (most recent call last):
SyntaxError: cannot assign to True
>>> (True := 1)
>>> (True := 1) # TODO: RUSTPYTHON; Wrong error message # doctest: +EXPECTED_FAILURE
Traceback (most recent call last):
SyntaxError: cannot use assignment expressions with True
@@ -79,7 +79,7 @@ SyntaxError: cannot delete __debug__
Traceback (most recent call last):
SyntaxError: cannot assign to function call here. Maybe you meant '==' instead of '='?
>>> yield = 1
>>> yield = 1 # TODO: RUSTPYTHON; Wrong error message # doctest: +EXPECTED_FAILURE
Traceback (most recent call last):
SyntaxError: assignment to yield expression not possible
@@ -91,23 +91,23 @@ SyntaxError: cannot delete function call
Traceback (most recent call last):
SyntaxError: cannot assign to expression here. Maybe you meant '==' instead of '='?
>>> (x for x in x) = 1
>>> (x for x in x) = 1 # TODO: RUSTPYTHON; Wrong error message # doctest: +EXPECTED_FAILURE
Traceback (most recent call last):
SyntaxError: cannot assign to generator expression
>>> 1 = 1
>>> 1 = 1 # TODO: RUSTPYTHON; Wrong error message # doctest: +EXPECTED_FAILURE
Traceback (most recent call last):
SyntaxError: cannot assign to literal here. Maybe you meant '==' instead of '='?
>>> "abc" = 1
>>> "abc" = 1 # TODO: RUSTPYTHON; Wrong error message # doctest: +EXPECTED_FAILURE
Traceback (most recent call last):
SyntaxError: cannot assign to literal here. Maybe you meant '==' instead of '='?
>>> b"" = 1
>>> b"" = 1 # TODO: RUSTPYTHON; Wrong error message # doctest: +EXPECTED_FAILURE
Traceback (most recent call last):
SyntaxError: cannot assign to literal here. Maybe you meant '==' instead of '='?
>>> ... = 1
>>> ... = 1 # TODO: RUSTPYTHON; Wrong error message # doctest: +EXPECTED_FAILURE
Traceback (most recent call last):
SyntaxError: cannot assign to ellipsis here. Maybe you meant '==' instead of '='?
@@ -124,7 +124,7 @@ them.
Traceback (most recent call last):
SyntaxError: cannot assign to literal
>>> (a, True, c) = (1, 2, 3)
>>> (a, True, c) = (1, 2, 3) # TODO: RUSTPYTHON; Wrong error message # doctest: +EXPECTED_FAILURE
Traceback (most recent call last):
SyntaxError: cannot assign to True
@@ -132,7 +132,7 @@ SyntaxError: cannot assign to True
Traceback (most recent call last):
SyntaxError: cannot assign to __debug__
>>> (a, *True, c) = (1, 2, 3)
>>> (a, *True, c) = (1, 2, 3) # TODO: RUSTPYTHON; Wrong error message # doctest: +EXPECTED_FAILURE
Traceback (most recent call last):
SyntaxError: cannot assign to True
@@ -140,19 +140,19 @@ SyntaxError: cannot assign to True
Traceback (most recent call last):
SyntaxError: cannot assign to __debug__
>>> [a, b, c + 1] = [1, 2, 3]
>>> [a, b, c + 1] = [1, 2, 3] # TODO: RUSTPYTHON; Wrong error message # doctest: +EXPECTED_FAILURE
Traceback (most recent call last):
SyntaxError: cannot assign to expression
>>> [a, b[1], c + 1] = [1, 2, 3]
>>> [a, b[1], c + 1] = [1, 2, 3] # TODO: RUSTPYTHON; Wrong error message # doctest: +EXPECTED_FAILURE
Traceback (most recent call last):
SyntaxError: cannot assign to expression
>>> [a, b.c.d, c + 1] = [1, 2, 3]
>>> [a, b.c.d, c + 1] = [1, 2, 3] # TODO: RUSTPYTHON; Wrong error message # doctest: +EXPECTED_FAILURE
Traceback (most recent call last):
SyntaxError: cannot assign to expression
>>> a if 1 else b = 1
>>> a if 1 else b = 1 # TODO: RUSTPYTHON; Wrong error message # doctest: +EXPECTED_FAILURE
Traceback (most recent call last):
SyntaxError: cannot assign to conditional expression
@@ -188,15 +188,15 @@ SyntaxError: expected expression before 'if', but statement is given
Traceback (most recent call last):
SyntaxError: invalid syntax
>>> True = True = 3
>>> True = True = 3 # TODO: RUSTPYTHON; Wrong error message # doctest: +EXPECTED_FAILURE
Traceback (most recent call last):
SyntaxError: cannot assign to True
>>> x = y = True = z = 3
>>> x = y = True = z = 3 # TODO: RUSTPYTHON; Wrong error message # doctest: +EXPECTED_FAILURE
Traceback (most recent call last):
SyntaxError: cannot assign to True
>>> x = y = yield = 1
>>> x = y = yield = 1 # TODO: RUSTPYTHON; Wrong error message # doctest: +EXPECTED_FAILURE
Traceback (most recent call last):
SyntaxError: assignment to yield expression not possible
@@ -215,31 +215,31 @@ SyntaxError: 'list' is an illegal expression for augmented assignment
Invalid targets in `for` loops and `with` statements should also
produce a specialized error message
>>> for a() in b: pass
>>> for a() in b: pass # TODO: RUSTPYTHON; Wrong error message # doctest: +EXPECTED_FAILURE
Traceback (most recent call last):
SyntaxError: cannot assign to function call
>>> for (a, b()) in b: pass
>>> for (a, b()) in b: pass # TODO: RUSTPYTHON; Wrong error message # doctest: +EXPECTED_FAILURE
Traceback (most recent call last):
SyntaxError: cannot assign to function call
>>> for [a, b()] in b: pass
>>> for [a, b()] in b: pass # TODO: RUSTPYTHON; Wrong error message # doctest: +EXPECTED_FAILURE
Traceback (most recent call last):
SyntaxError: cannot assign to function call
>>> for (*a, b, c+1) in b: pass
>>> for (*a, b, c+1) in b: pass # TODO: RUSTPYTHON; Wrong error message # doctest: +EXPECTED_FAILURE
Traceback (most recent call last):
SyntaxError: cannot assign to expression
>>> for (x, *(y, z.d())) in b: pass
>>> for (x, *(y, z.d())) in b: pass # TODO: RUSTPYTHON; Wrong error message # doctest: +EXPECTED_FAILURE
Traceback (most recent call last):
SyntaxError: cannot assign to function call
>>> for a, b() in c: pass
>>> for a, b() in c: pass # TODO: RUSTPYTHON; Wrong error message # doctest: +EXPECTED_FAILURE
Traceback (most recent call last):
SyntaxError: cannot assign to function call
>>> for a, b, (c + 1, d()): pass
>>> for a, b, (c + 1, d()): pass # TODO: RUSTPYTHON; Wrong error message # doctest: +EXPECTED_FAILURE
Traceback (most recent call last):
SyntaxError: cannot assign to expression
@@ -251,27 +251,27 @@ SyntaxError: invalid syntax
Traceback (most recent call last):
SyntaxError: invalid syntax
>>> with a as b(): pass
>>> with a as b(): pass # TODO: RUSTPYTHON; Wrong error message # doctest: +EXPECTED_FAILURE
Traceback (most recent call last):
SyntaxError: cannot assign to function call
>>> with a as (b, c()): pass
>>> with a as (b, c()): pass # TODO: RUSTPYTHON; Wrong error message # doctest: +EXPECTED_FAILURE
Traceback (most recent call last):
SyntaxError: cannot assign to function call
>>> with a as [b, c()]: pass
>>> with a as [b, c()]: pass # TODO: RUSTPYTHON; Wrong error message # doctest: +EXPECTED_FAILURE
Traceback (most recent call last):
SyntaxError: cannot assign to function call
>>> with a as (*b, c, d+1): pass
>>> with a as (*b, c, d+1): pass # TODO: RUSTPYTHON; Wrong error message # doctest: +EXPECTED_FAILURE
Traceback (most recent call last):
SyntaxError: cannot assign to expression
>>> with a as (x, *(y, z.d())): pass
>>> with a as (x, *(y, z.d())): pass # TODO: RUSTPYTHON; Wrong error message # doctest: +EXPECTED_FAILURE
Traceback (most recent call last):
SyntaxError: cannot assign to function call
>>> with a as b, c as d(): pass
>>> with a as b, c as d(): pass # TODO: RUSTPYTHON; Wrong error message # doctest: +EXPECTED_FAILURE
Traceback (most recent call last):
SyntaxError: cannot assign to function call
@@ -293,11 +293,11 @@ SyntaxError: 'in' expected after for-loop variables
Traceback (most recent call last):
SyntaxError: 'in' expected after for-loop variables
>>> [x for x() in a]
>>> [x for x() in a] # TODO: RUSTPYTHON; Wrong error message # doctest: +EXPECTED_FAILURE
Traceback (most recent call last):
SyntaxError: cannot assign to function call
>>> [x for a, b, (c + 1, d()) in y]
>>> [x for a, b, (c + 1, d()) in y] # TODO: RUSTPYTHON; Wrong error message # doctest: +EXPECTED_FAILURE
Traceback (most recent call last):
SyntaxError: cannot assign to expression
@@ -305,11 +305,11 @@ SyntaxError: cannot assign to expression
Traceback (most recent call last):
SyntaxError: 'in' expected after for-loop variables
>>> [x for x+1 in y]
>>> [x for x+1 in y] # TODO: RUSTPYTHON; Wrong error message # doctest: +EXPECTED_FAILURE
Traceback (most recent call last):
SyntaxError: cannot assign to expression
>>> [x for x+1, x() in y]
>>> [x for x+1, x() in y] # TODO: RUSTPYTHON; Wrong error message # doctest: +EXPECTED_FAILURE
Traceback (most recent call last):
SyntaxError: cannot assign to expression
@@ -334,19 +334,19 @@ SyntaxError: invalid syntax. Is this intended to be part of the string?
# produce special error messages regarding missing
# parentheses, but about missing commas instead
>>> [1, 2 3]
>>> [1, 2 3] # TODO: RUSTPYTHON; Wrong error message # doctest: +EXPECTED_FAILURE
Traceback (most recent call last):
SyntaxError: invalid syntax. Perhaps you forgot a comma?
>>> {1, 2 3}
>>> {1, 2 3} # TODO: RUSTPYTHON; Wrong error message # doctest: +EXPECTED_FAILURE
Traceback (most recent call last):
SyntaxError: invalid syntax. Perhaps you forgot a comma?
>>> {1:2, 2:5 3:12}
>>> {1:2, 2:5 3:12} # TODO: RUSTPYTHON; Wrong error message # doctest: +EXPECTED_FAILURE
Traceback (most recent call last):
SyntaxError: invalid syntax. Perhaps you forgot a comma?
>>> (1, 2 3)
>>> (1, 2 3) # TODO: RUSTPYTHON; Wrong error message # doctest: +EXPECTED_FAILURE
Traceback (most recent call last):
SyntaxError: invalid syntax. Perhaps you forgot a comma?
@@ -2114,19 +2114,19 @@ SyntaxError: cannot use subscript as import target
# Check that we don't raise a "cannot use name as import target" error
# if there is an error in an unrelated statement after ';'
>>> import a as b; None = 1
>>> import a as b; None = 1 # TODO: RUSTPYTHON; Wrong error message # doctest: +EXPECTED_FAILURE
Traceback (most recent call last):
SyntaxError: cannot assign to None
>>> import a, b as c; d = 1; None = 1
>>> import a, b as c; d = 1; None = 1 # TODO: RUSTPYTHON; Wrong error message # doctest: +EXPECTED_FAILURE
Traceback (most recent call last):
SyntaxError: cannot assign to None
>>> from a import b as c; None = 1
>>> from a import b as c; None = 1 # TODO: RUSTPYTHON; Wrong error message # doctest: +EXPECTED_FAILURE
Traceback (most recent call last):
SyntaxError: cannot assign to None
>>> from a import b, c as d; e = 1; None = 1
>>> from a import b, c as d; e = 1; None = 1 # TODO: RUSTPYTHON; Wrong error message # doctest: +EXPECTED_FAILURE
Traceback (most recent call last):
SyntaxError: cannot assign to None

File diff suppressed because it is too large Load Diff

39
Lib/test/test_ucn.py vendored
View File

@@ -88,9 +88,6 @@ class UnicodeNamesTest(unittest.TestCase):
self.checkletter("HANGUL SYLLABLE HWEOK", "\ud6f8")
self.checkletter("HANGUL SYLLABLE HIH", "\ud7a3")
self.checkletter("haNGul SYllABle WAe", '\uc65c')
self.checkletter("HAngUL syLLabLE waE", '\uc65c')
self.assertRaises(ValueError, unicodedata.name, "\ud7a4")
def test_cjk_unified_ideographs(self):
@@ -106,36 +103,6 @@ class UnicodeNamesTest(unittest.TestCase):
self.checkletter("CJK UNIFIED IDEOGRAPH-2B81D", "\U0002B81D")
self.checkletter("CJK UNIFIED IDEOGRAPH-3134A", "\U0003134A")
self.checkletter("cjK UniFIeD idEogRAph-3aBc", "\u3abc")
self.checkletter("CJk uNIfiEd IDeOGraPH-3AbC", "\u3abc")
self.checkletter("cjK UniFIeD idEogRAph-2aBcD", "\U0002abcd")
self.checkletter("CJk uNIfiEd IDeOGraPH-2AbCd", "\U0002abcd")
@unittest.expectedFailure # TODO: RUSTPYTHON; SyntaxError: got unexpected unicode
def test_tangut_ideographs(self):
self.checkletter("TANGUT IDEOGRAPH-17000", "\U00017000")
self.checkletter("TANGUT IDEOGRAPH-187F7", "\U000187f7")
self.checkletter("TANGUT IDEOGRAPH-18D00", "\U00018D00")
self.checkletter("TANGUT IDEOGRAPH-18D08", "\U00018d08")
self.checkletter("tangut ideograph-18d08", "\U00018d08")
def test_egyptian_hieroglyphs(self):
self.checkletter("EGYPTIAN HIEROGLYPH-13460", "\U00013460")
self.checkletter("EGYPTIAN HIEROGLYPH-143FA", "\U000143fa")
self.checkletter("egyptian hieroglyph-143fa", "\U000143fa")
def test_khitan_small_script_characters(self):
self.checkletter("KHITAN SMALL SCRIPT CHARACTER-18B00", "\U00018b00")
self.checkletter("KHITAN SMALL SCRIPT CHARACTER-18CD5", "\U00018cd5")
self.checkletter("KHITAN SMALL SCRIPT CHARACTER-18CFF", "\U00018cff")
self.checkletter("KHITAN SMALL SCRIPT CHARACTER-18CFF", "\U00018cff")
self.checkletter("khitan small script character-18cff", "\U00018cff")
def test_nushu_characters(self):
self.checkletter("NUSHU CHARACTER-1B170", "\U0001b170")
self.checkletter("NUSHU CHARACTER-1B2FB", "\U0001b2fb")
self.checkletter("nushu character-1b2fb", "\U0001b2fb")
def test_bmp_characters(self):
for code in range(0x10000):
char = chr(code)
@@ -149,7 +116,7 @@ class UnicodeNamesTest(unittest.TestCase):
self.checkletter("HALFWIDTH KATAKANA SEMI-VOICED SOUND MARK", "\uFF9F")
self.checkletter("FULLWIDTH LATIN SMALL LETTER A", "\uFF41")
@unittest.expectedFailure # TODO: RUSTPYTHON
@unittest.expectedFailure # TODO: RUSTPYTHON
def test_aliases(self):
# Check that the aliases defined in the NameAliases.txt file work.
# This should be updated when new aliases are added or the file
@@ -190,7 +157,7 @@ class UnicodeNamesTest(unittest.TestCase):
unicodedata.name(chr(cp))
self.assertEqual(str(cm.exception), 'no such name')
@unittest.expectedFailure # TODO: RUSTPYTHON
@unittest.expectedFailure # TODO: RUSTPYTHON
def test_named_sequences_sample(self):
# Check a few named sequences. See #12753.
sequences = [
@@ -207,7 +174,7 @@ class UnicodeNamesTest(unittest.TestCase):
with self.assertRaises(KeyError):
unicodedata.ucd_3_2_0.lookup(seqname)
@unittest.expectedFailure # TODO: RUSTPYTHON
@unittest.expectedFailure # TODO: RUSTPYTHON
def test_named_sequences_full(self):
# Check all the named sequences
def check_version(testfile):

View File

@@ -125,8 +125,8 @@ class UnicodeFileTests(unittest.TestCase):
# open(), os.stat(), etc. don't raise any exception.
@unittest.skipIf(is_apple, 'irrelevant test on Apple platforms')
@unittest.skipIf(
support.is_wasi,
"test fails on WASI when host platform is macOS."
support.is_emscripten or support.is_wasi,
"test fails on Emscripten/WASI when host platform is macOS."
)
def test_normalize(self):
files = set(self.files)

View File

@@ -17,10 +17,11 @@ class PEP3131Test(unittest.TestCase):
𝔘𝔫𝔦𝔠𝔬𝔡𝔢 = 1
self.assertIn("Unicode", dir())
@unittest.expectedFailure # TODO: RUSTPYTHON
# TODO: RUSTPYTHON
@unittest.expectedFailure
def test_invalid(self):
try:
from test.tokenizedata import badsyntax_3131 # noqa: F401
from test.tokenizedata import badsyntax_3131
except SyntaxError as err:
self.assertEqual(str(err),
"invalid character '' (U+20AC) (badsyntax_3131.py, line 2)")

84
Lib/test/test_uuid.py vendored
View File

@@ -1176,47 +1176,6 @@ class CommandLineTestCases:
self.assertEqual(cm.exception.code, 2)
self.assertIn("error: Incorrect number of arguments", mock_err.getvalue())
@mock.patch.object(sys, "argv",
["", "-u", "uuid3", "-n", "@dns", "-N", "python.org"])
def test_cli_uuid3_outputted_with_valid_namespace_and_name(self):
stdout = io.StringIO()
with contextlib.redirect_stdout(stdout):
self.uuid.main()
output = stdout.getvalue().strip()
uuid_output = self.uuid.UUID(output)
# Output should be in the form of uuid3
self.assertEqual(output, str(uuid_output))
self.assertEqual(uuid_output.version, 3)
@mock.patch.object(sys, "argv",
["", "-u", "uuid3", "-n",
"0d6a16cc-34a7-47d8-b660-214d0ae184d2",
"-N", "some.user"])
def test_cli_uuid3_outputted_with_custom_namespace_and_name(self):
stdout = io.StringIO()
with contextlib.redirect_stdout(stdout):
self.uuid.main()
output = stdout.getvalue().strip()
uuid_output = self.uuid.UUID(output)
# Output should be in the form of uuid3
self.assertEqual(output, str(uuid_output))
self.assertEqual(uuid_output.version, 3)
@mock.patch.object(sys, "argv",
["", "-u", "uuid3", "-n", "any UUID", "-N", "python.org"])
@mock.patch('sys.stderr', new_callable=io.StringIO)
def test_cli_uuid3_with_invalid_namespace(self, mock_err):
with self.assertRaises(SystemExit) as cm:
self.uuid.main()
# Check that exception code is the same as argparse.ArgumentParser.error
self.assertEqual(cm.exception.code, 2)
self.assertIn("error: badly formed hexadecimal UUID string",
mock_err.getvalue())
@mock.patch.object(sys, "argv", [""])
def test_cli_uuid4_outputted_with_no_args(self):
stdout = io.StringIO()
@@ -1244,9 +1203,23 @@ class CommandLineTestCases:
uuid_output = self.uuid.UUID(o)
self.assertEqual(uuid_output.version, 4)
@mock.patch.object(sys, "argv",
["", "-u", "uuid3", "-n", "@dns", "-N", "python.org"])
def test_cli_uuid3_ouputted_with_valid_namespace_and_name(self):
stdout = io.StringIO()
with contextlib.redirect_stdout(stdout):
self.uuid.main()
output = stdout.getvalue().strip()
uuid_output = self.uuid.UUID(output)
# Output should be in the form of uuid5
self.assertEqual(output, str(uuid_output))
self.assertEqual(uuid_output.version, 3)
@mock.patch.object(sys, "argv",
["", "-u", "uuid5", "-n", "@dns", "-N", "python.org"])
def test_cli_uuid5_outputted_with_valid_namespace_and_name(self):
def test_cli_uuid5_ouputted_with_valid_namespace_and_name(self):
stdout = io.StringIO()
with contextlib.redirect_stdout(stdout):
self.uuid.main()
@@ -1258,33 +1231,6 @@ class CommandLineTestCases:
self.assertEqual(output, str(uuid_output))
self.assertEqual(uuid_output.version, 5)
@mock.patch.object(sys, "argv",
["", "-u", "uuid5", "-n",
"0d6a16cc-34a7-47d8-b660-214d0ae184d2",
"-N", "some.user"])
def test_cli_uuid5_ouputted_with_custom_namespace_and_name(self):
stdout = io.StringIO()
with contextlib.redirect_stdout(stdout):
self.uuid.main()
output = stdout.getvalue().strip()
uuid_output = self.uuid.UUID(output)
# Output should be in the form of uuid5
self.assertEqual(output, str(uuid_output))
self.assertEqual(uuid_output.version, 5)
@mock.patch.object(sys, "argv",
["", "-u", "uuid5", "-n", "any UUID", "-N", "python.org"])
@mock.patch('sys.stderr', new_callable=io.StringIO)
def test_cli_uuid5_with_invalid_namespace(self, mock_err):
with self.assertRaises(SystemExit) as cm:
self.uuid.main()
# Check that exception code is the same as argparse.ArgumentParser.error
self.assertEqual(cm.exception.code, 2)
self.assertIn("error: badly formed hexadecimal UUID string",
mock_err.getvalue())
@mock.patch.object(sys, "argv", ["", "-u", "uuid6"])
def test_cli_uuid6(self):
self.do_test_standalone_uuid(6)

13
Lib/test/test_venv.py vendored
View File

@@ -266,7 +266,6 @@ class BasicTest(BaseTest):
with patch('venv.subprocess.check_output', pip_cmd_checker):
builder.upgrade_dependencies(fake_context)
@unittest.expectedFailureIfWindows("TODO: RUSTPYTHON")
@requireVenvCreate
def test_prefixes(self):
"""
@@ -286,7 +285,6 @@ class BasicTest(BaseTest):
self.assertEqual(pathlib.Path(out.strip().decode()),
pathlib.Path(expected), prefix)
@unittest.expectedFailureIfWindows("TODO: RUSTPYTHON")
@requireVenvCreate
def test_sysconfig(self):
"""
@@ -320,7 +318,6 @@ class BasicTest(BaseTest):
out, err = check_output(cmd, encoding='utf-8')
self.assertEqual(out.strip(), expected, err)
@unittest.expectedFailureIfWindows("TODO: RUSTPYTHON")
@requireVenvCreate
@unittest.skipUnless(can_symlink(), 'Needs symlinks')
def test_sysconfig_symlinks(self):
@@ -461,7 +458,6 @@ class BasicTest(BaseTest):
data = self.get_text_file_contents('pyvenv.cfg')
self.assertIn('include-system-site-packages = %s\n' % s, data)
@unittest.expectedFailureIfWindows("TODO: RUSTPYTHON")
@unittest.skipUnless(can_symlink(), 'Needs symlinks')
def test_symlinking(self):
"""
@@ -486,7 +482,6 @@ class BasicTest(BaseTest):
# run the test, the pyvenv.cfg in the venv created in the test will
# point to the venv being used to run the test, and we lose the link
# to the source build - so Python can't initialise properly.
@unittest.expectedFailureIfWindows("TODO: RUSTPYTHON")
@requireVenvCreate
def test_executable(self):
"""
@@ -499,7 +494,6 @@ class BasicTest(BaseTest):
'import sys; print(sys.executable)'])
self.assertEqual(out.strip(), envpy.encode())
@unittest.expectedFailureIfWindows("TODO: RUSTPYTHON")
@unittest.skipUnless(can_symlink(), 'Needs symlinks')
def test_executable_symlinks(self):
"""
@@ -568,7 +562,6 @@ class BasicTest(BaseTest):
self.assertEndsWith(lines[1], env_name.encode())
# gh-124651: test quoted strings on Windows
@unittest.expectedFailureIfWindows("TODO: RUSTPYTHON")
@unittest.skipUnless(os.name == 'nt', 'only relevant on Windows')
def test_special_chars_windows(self):
"""
@@ -592,7 +585,6 @@ class BasicTest(BaseTest):
self.assertTrue(env_name.encode() in lines[0])
self.assertEndsWith(lines[1], env_name.encode())
@unittest.expectedFailureIfWindows("TODO: RUSTPYTHON")
@unittest.skipUnless(os.name == 'nt', 'only relevant on Windows')
def test_unicode_in_batch_file(self):
"""
@@ -624,7 +616,6 @@ class BasicTest(BaseTest):
filepath_regex = r"'[A-Z]:\\\\(?:[^\\\\]+\\\\)*[^\\\\]+'"
self.assertRegex(err, rf"Unable to symlink {filepath_regex} to {filepath_regex}")
@unittest.expectedFailureIfWindows("TODO: RUSTPYTHON")
@requireVenvCreate
def test_multiprocessing(self):
"""
@@ -644,7 +635,6 @@ class BasicTest(BaseTest):
'pool.terminate()'])
self.assertEqual(out.strip(), "python".encode())
@unittest.expectedFailureIfWindows("TODO: RUSTPYTHON")
@requireVenvCreate
def test_multiprocessing_recursion(self):
"""
@@ -902,7 +892,6 @@ class BasicTest(BaseTest):
self.assertFalse(same_path(path1, path2))
# gh-126084: venvwlauncher should run pythonw, not python
@unittest.expectedFailureIfWindows("TODO: RUSTPYTHON")
@requireVenvCreate
@unittest.skipUnless(os.name == 'nt', 'only relevant on Windows')
def test_venvwlauncher(self):
@@ -937,13 +926,11 @@ class EnsurePipTest(BaseTest):
self.assertEqual(out.strip(), "OK")
@unittest.expectedFailureIfWindows("TODO: RUSTPYTHON")
def test_no_pip_by_default(self):
rmtree(self.env_dir)
self.run_with_capture(venv.create, self.env_dir)
self.assert_pip_not_installed()
@unittest.expectedFailureIfWindows("TODO: RUSTPYTHON")
def test_explicit_no_pip(self):
rmtree(self.env_dir)
self.run_with_capture(venv.create, self.env_dir, with_pip=False)

560
Lib/tracemalloc.py vendored
View File

@@ -1,560 +0,0 @@
from collections.abc import Sequence, Iterable
from functools import total_ordering
import fnmatch
import linecache
import os.path
import pickle
# Import types and functions implemented in C
from _tracemalloc import *
from _tracemalloc import _get_object_traceback, _get_traces
def _format_size(size, sign):
for unit in ('B', 'KiB', 'MiB', 'GiB', 'TiB'):
if abs(size) < 100 and unit != 'B':
# 3 digits (xx.x UNIT)
if sign:
return "%+.1f %s" % (size, unit)
else:
return "%.1f %s" % (size, unit)
if abs(size) < 10 * 1024 or unit == 'TiB':
# 4 or 5 digits (xxxx UNIT)
if sign:
return "%+.0f %s" % (size, unit)
else:
return "%.0f %s" % (size, unit)
size /= 1024
class Statistic:
"""
Statistic difference on memory allocations between two Snapshot instance.
"""
__slots__ = ('traceback', 'size', 'count')
def __init__(self, traceback, size, count):
self.traceback = traceback
self.size = size
self.count = count
def __hash__(self):
return hash((self.traceback, self.size, self.count))
def __eq__(self, other):
if not isinstance(other, Statistic):
return NotImplemented
return (self.traceback == other.traceback
and self.size == other.size
and self.count == other.count)
def __str__(self):
text = ("%s: size=%s, count=%i"
% (self.traceback,
_format_size(self.size, False),
self.count))
if self.count:
average = self.size / self.count
text += ", average=%s" % _format_size(average, False)
return text
def __repr__(self):
return ('<Statistic traceback=%r size=%i count=%i>'
% (self.traceback, self.size, self.count))
def _sort_key(self):
return (self.size, self.count, self.traceback)
class StatisticDiff:
"""
Statistic difference on memory allocations between an old and a new
Snapshot instance.
"""
__slots__ = ('traceback', 'size', 'size_diff', 'count', 'count_diff')
def __init__(self, traceback, size, size_diff, count, count_diff):
self.traceback = traceback
self.size = size
self.size_diff = size_diff
self.count = count
self.count_diff = count_diff
def __hash__(self):
return hash((self.traceback, self.size, self.size_diff,
self.count, self.count_diff))
def __eq__(self, other):
if not isinstance(other, StatisticDiff):
return NotImplemented
return (self.traceback == other.traceback
and self.size == other.size
and self.size_diff == other.size_diff
and self.count == other.count
and self.count_diff == other.count_diff)
def __str__(self):
text = ("%s: size=%s (%s), count=%i (%+i)"
% (self.traceback,
_format_size(self.size, False),
_format_size(self.size_diff, True),
self.count,
self.count_diff))
if self.count:
average = self.size / self.count
text += ", average=%s" % _format_size(average, False)
return text
def __repr__(self):
return ('<StatisticDiff traceback=%r size=%i (%+i) count=%i (%+i)>'
% (self.traceback, self.size, self.size_diff,
self.count, self.count_diff))
def _sort_key(self):
return (abs(self.size_diff), self.size,
abs(self.count_diff), self.count,
self.traceback)
def _compare_grouped_stats(old_group, new_group):
statistics = []
for traceback, stat in new_group.items():
previous = old_group.pop(traceback, None)
if previous is not None:
stat = StatisticDiff(traceback,
stat.size, stat.size - previous.size,
stat.count, stat.count - previous.count)
else:
stat = StatisticDiff(traceback,
stat.size, stat.size,
stat.count, stat.count)
statistics.append(stat)
for traceback, stat in old_group.items():
stat = StatisticDiff(traceback, 0, -stat.size, 0, -stat.count)
statistics.append(stat)
return statistics
@total_ordering
class Frame:
"""
Frame of a traceback.
"""
__slots__ = ("_frame",)
def __init__(self, frame):
# frame is a tuple: (filename: str, lineno: int)
self._frame = frame
@property
def filename(self):
return self._frame[0]
@property
def lineno(self):
return self._frame[1]
def __eq__(self, other):
if not isinstance(other, Frame):
return NotImplemented
return (self._frame == other._frame)
def __lt__(self, other):
if not isinstance(other, Frame):
return NotImplemented
return (self._frame < other._frame)
def __hash__(self):
return hash(self._frame)
def __str__(self):
return "%s:%s" % (self.filename, self.lineno)
def __repr__(self):
return "<Frame filename=%r lineno=%r>" % (self.filename, self.lineno)
@total_ordering
class Traceback(Sequence):
"""
Sequence of Frame instances sorted from the oldest frame
to the most recent frame.
"""
__slots__ = ("_frames", '_total_nframe')
def __init__(self, frames, total_nframe=None):
Sequence.__init__(self)
# frames is a tuple of frame tuples: see Frame constructor for the
# format of a frame tuple; it is reversed, because _tracemalloc
# returns frames sorted from most recent to oldest, but the
# Python API expects oldest to most recent
self._frames = tuple(reversed(frames))
self._total_nframe = total_nframe
@property
def total_nframe(self):
return self._total_nframe
def __len__(self):
return len(self._frames)
def __getitem__(self, index):
if isinstance(index, slice):
return tuple(Frame(trace) for trace in self._frames[index])
else:
return Frame(self._frames[index])
def __contains__(self, frame):
return frame._frame in self._frames
def __hash__(self):
return hash(self._frames)
def __eq__(self, other):
if not isinstance(other, Traceback):
return NotImplemented
return (self._frames == other._frames)
def __lt__(self, other):
if not isinstance(other, Traceback):
return NotImplemented
return (self._frames < other._frames)
def __str__(self):
return str(self[0])
def __repr__(self):
s = f"<Traceback {tuple(self)}"
if self._total_nframe is None:
s += ">"
else:
s += f" total_nframe={self.total_nframe}>"
return s
def format(self, limit=None, most_recent_first=False):
lines = []
if limit is not None:
if limit > 0:
frame_slice = self[-limit:]
else:
frame_slice = self[:limit]
else:
frame_slice = self
if most_recent_first:
frame_slice = reversed(frame_slice)
for frame in frame_slice:
lines.append(' File "%s", line %s'
% (frame.filename, frame.lineno))
line = linecache.getline(frame.filename, frame.lineno).strip()
if line:
lines.append(' %s' % line)
return lines
def get_object_traceback(obj):
"""
Get the traceback where the Python object *obj* was allocated.
Return a Traceback instance.
Return None if the tracemalloc module is not tracing memory allocations or
did not trace the allocation of the object.
"""
frames = _get_object_traceback(obj)
if frames is not None:
return Traceback(frames)
else:
return None
class Trace:
"""
Trace of a memory block.
"""
__slots__ = ("_trace",)
def __init__(self, trace):
# trace is a tuple: (domain: int, size: int, traceback: tuple).
# See Traceback constructor for the format of the traceback tuple.
self._trace = trace
@property
def domain(self):
return self._trace[0]
@property
def size(self):
return self._trace[1]
@property
def traceback(self):
return Traceback(*self._trace[2:])
def __eq__(self, other):
if not isinstance(other, Trace):
return NotImplemented
return (self._trace == other._trace)
def __hash__(self):
return hash(self._trace)
def __str__(self):
return "%s: %s" % (self.traceback, _format_size(self.size, False))
def __repr__(self):
return ("<Trace domain=%s size=%s, traceback=%r>"
% (self.domain, _format_size(self.size, False), self.traceback))
class _Traces(Sequence):
def __init__(self, traces):
Sequence.__init__(self)
# traces is a tuple of trace tuples: see Trace constructor
self._traces = traces
def __len__(self):
return len(self._traces)
def __getitem__(self, index):
if isinstance(index, slice):
return tuple(Trace(trace) for trace in self._traces[index])
else:
return Trace(self._traces[index])
def __contains__(self, trace):
return trace._trace in self._traces
def __eq__(self, other):
if not isinstance(other, _Traces):
return NotImplemented
return (self._traces == other._traces)
def __repr__(self):
return "<Traces len=%s>" % len(self)
def _normalize_filename(filename):
filename = os.path.normcase(filename)
if filename.endswith('.pyc'):
filename = filename[:-1]
return filename
class BaseFilter:
def __init__(self, inclusive):
self.inclusive = inclusive
def _match(self, trace):
raise NotImplementedError
class Filter(BaseFilter):
def __init__(self, inclusive, filename_pattern,
lineno=None, all_frames=False, domain=None):
super().__init__(inclusive)
self.inclusive = inclusive
self._filename_pattern = _normalize_filename(filename_pattern)
self.lineno = lineno
self.all_frames = all_frames
self.domain = domain
@property
def filename_pattern(self):
return self._filename_pattern
def _match_frame_impl(self, filename, lineno):
filename = _normalize_filename(filename)
if not fnmatch.fnmatch(filename, self._filename_pattern):
return False
if self.lineno is None:
return True
else:
return (lineno == self.lineno)
def _match_frame(self, filename, lineno):
return self._match_frame_impl(filename, lineno) ^ (not self.inclusive)
def _match_traceback(self, traceback):
if self.all_frames:
if any(self._match_frame_impl(filename, lineno)
for filename, lineno in traceback):
return self.inclusive
else:
return (not self.inclusive)
else:
filename, lineno = traceback[0]
return self._match_frame(filename, lineno)
def _match(self, trace):
domain, size, traceback, total_nframe = trace
res = self._match_traceback(traceback)
if self.domain is not None:
if self.inclusive:
return res and (domain == self.domain)
else:
return res or (domain != self.domain)
return res
class DomainFilter(BaseFilter):
def __init__(self, inclusive, domain):
super().__init__(inclusive)
self._domain = domain
@property
def domain(self):
return self._domain
def _match(self, trace):
domain, size, traceback, total_nframe = trace
return (domain == self.domain) ^ (not self.inclusive)
class Snapshot:
"""
Snapshot of traces of memory blocks allocated by Python.
"""
def __init__(self, traces, traceback_limit):
# traces is a tuple of trace tuples: see _Traces constructor for
# the exact format
self.traces = _Traces(traces)
self.traceback_limit = traceback_limit
def dump(self, filename):
"""
Write the snapshot into a file.
"""
with open(filename, "wb") as fp:
pickle.dump(self, fp, pickle.HIGHEST_PROTOCOL)
@staticmethod
def load(filename):
"""
Load a snapshot from a file.
"""
with open(filename, "rb") as fp:
return pickle.load(fp)
def _filter_trace(self, include_filters, exclude_filters, trace):
if include_filters:
if not any(trace_filter._match(trace)
for trace_filter in include_filters):
return False
if exclude_filters:
if any(not trace_filter._match(trace)
for trace_filter in exclude_filters):
return False
return True
def filter_traces(self, filters):
"""
Create a new Snapshot instance with a filtered traces sequence, filters
is a list of Filter or DomainFilter instances. If filters is an empty
list, return a new Snapshot instance with a copy of the traces.
"""
if not isinstance(filters, Iterable):
raise TypeError("filters must be a list of filters, not %s"
% type(filters).__name__)
if filters:
include_filters = []
exclude_filters = []
for trace_filter in filters:
if trace_filter.inclusive:
include_filters.append(trace_filter)
else:
exclude_filters.append(trace_filter)
new_traces = [trace for trace in self.traces._traces
if self._filter_trace(include_filters,
exclude_filters,
trace)]
else:
new_traces = self.traces._traces.copy()
return Snapshot(new_traces, self.traceback_limit)
def _group_by(self, key_type, cumulative):
if key_type not in ('traceback', 'filename', 'lineno'):
raise ValueError("unknown key_type: %r" % (key_type,))
if cumulative and key_type not in ('lineno', 'filename'):
raise ValueError("cumulative mode cannot by used "
"with key type %r" % key_type)
stats = {}
tracebacks = {}
if not cumulative:
for trace in self.traces._traces:
domain, size, trace_traceback, total_nframe = trace
try:
traceback = tracebacks[trace_traceback]
except KeyError:
if key_type == 'traceback':
frames = trace_traceback
elif key_type == 'lineno':
frames = trace_traceback[:1]
else: # key_type == 'filename':
frames = ((trace_traceback[0][0], 0),)
traceback = Traceback(frames)
tracebacks[trace_traceback] = traceback
try:
stat = stats[traceback]
stat.size += size
stat.count += 1
except KeyError:
stats[traceback] = Statistic(traceback, size, 1)
else:
# cumulative statistics
for trace in self.traces._traces:
domain, size, trace_traceback, total_nframe = trace
for frame in trace_traceback:
try:
traceback = tracebacks[frame]
except KeyError:
if key_type == 'lineno':
frames = (frame,)
else: # key_type == 'filename':
frames = ((frame[0], 0),)
traceback = Traceback(frames)
tracebacks[frame] = traceback
try:
stat = stats[traceback]
stat.size += size
stat.count += 1
except KeyError:
stats[traceback] = Statistic(traceback, size, 1)
return stats
def statistics(self, key_type, cumulative=False):
"""
Group statistics by key_type. Return a sorted list of Statistic
instances.
"""
grouped = self._group_by(key_type, cumulative)
statistics = list(grouped.values())
statistics.sort(reverse=True, key=Statistic._sort_key)
return statistics
def compare_to(self, old_snapshot, key_type, cumulative=False):
"""
Compute the differences with an old snapshot old_snapshot. Get
statistics as a sorted list of StatisticDiff instances, grouped by
group_by.
"""
new_group = self._group_by(key_type, cumulative)
old_group = old_snapshot._group_by(key_type, cumulative)
statistics = _compare_grouped_stats(old_group, new_group)
statistics.sort(reverse=True, key=StatisticDiff._sort_key)
return statistics
def take_snapshot():
"""
Take a snapshot of traces of memory blocks allocated by Python.
"""
if not is_tracing():
raise RuntimeError("the tracemalloc module must be tracing memory "
"allocations to take a snapshot")
traces = _get_traces()
traceback_limit = get_traceback_limit()
return Snapshot(traces, traceback_limit)

10
Lib/uuid.py vendored
View File

@@ -961,7 +961,7 @@ def main():
default="uuid4",
help="function to generate the UUID")
parser.add_argument("-n", "--namespace",
metavar=f"{{any UUID,{','.join(namespaces)}}}",
choices=["any UUID", *namespaces.keys()],
help="uuid3/uuid5 only: "
"a UUID, or a well-known predefined UUID addressed "
"by namespace name")
@@ -983,13 +983,7 @@ def main():
f"{args.uuid} requires a namespace and a name. "
"Run 'python -m uuid -h' for more information."
)
if namespace in namespaces:
namespace = namespaces[namespace]
else:
try:
namespace = UUID(namespace)
except ValueError as exc:
parser.error(f"{exc}: {args.namespace!r}")
namespace = namespaces[namespace] if namespace in namespaces else UUID(namespace)
for _ in range(args.count):
print(uuid_func(namespace, name))
else:

View File

@@ -80,7 +80,7 @@ $ python # now `python` is the alias of the RustPython for the new env
If you'd like to make https requests, you can enable the `ssl` feature, which
also lets you install the `pip` package manager. Note that on Windows, you may
need to install OpenSSL, or you can enable the `ssl-openssl-vendor` feature instead,
need to install OpenSSL, or you can enable the `ssl-vendor` feature instead,
which compiles OpenSSL for you but requires a C compiler, perl, and `make`.
OpenSSL version 3 is expected and tested in CI. Older versions may not work.
@@ -103,7 +103,7 @@ rustpython
### SSL provider
For HTTPS requests, `ssl-rustls-aws-lc` is enabled by default for the RustPython binary. Embedders can use `rustpython-stdlib`'s provider-agnostic `ssl-rustls` feature and install their own rustls crypto provider, or replace rustls with `ssl-openssl` if their environment requires OpenSSL.
Note that to use OpenSSL on Windows, you may need to install OpenSSL, or you can enable the `ssl-openssl-vendor` feature instead,
Note that to use OpenSSL on Windows, you may need to install OpenSSL, or you can enable the `ssl-vendor` feature instead,
which compiles OpenSSL for you but requires a C compiler, perl, and `make`.
OpenSSL version 3 is expected and tested in CI. Older versions may not work.
@@ -229,10 +229,24 @@ For a high level overview of the components, see the [architecture](architecture
## Contributing
Contributions are welcome and highly appreciated. To get started, check out the
[**contributing guidelines**](CONTRIBUTING.md).
Contributions are more than welcome, and in many cases we are happy to guide
contributors through PRs or on Discord. Please refer to the
[development guide](DEVELOPMENT.md) as well for tips on developments.
You can also join us on [**Discord**](https://discord.gg/vru8NypEhv).
With that in mind, please note this project is maintained by volunteers, some of
the best ways to get started are below:
Most tasks are listed in the
[issue tracker](https://github.com/RustPython/RustPython/issues). Check issues
labeled with [good first issue](https://github.com/RustPython/RustPython/issues?q=label%3A%22good+first+issue%22+is%3Aissue+is%3Aopen+) if you wish to start coding.
To enhance CPython compatibility, try to increase unittest coverage by checking this article: [How to contribute to RustPython by CPython unittest](https://rustpython.github.io/guideline/2020/04/04/how-to-contribute-by-cpython-unittest.html)
Another approach is to checkout the source code: builtin functions and object
methods are often the simplest and easiest way to contribute.
You can also simply run `python -I scripts/whats_left.py` to assist in finding any unimplemented
method.
## Compiling to WebAssembly

View File

@@ -13,7 +13,6 @@ crate-type = ["cdylib", "rlib"]
[dependencies]
bitflags = { workspace = true }
itertools = { workspace = true }
num-complex = { workspace = true }
rustpython-vm = { workspace = true, features = ["threading", "compiler"] }
rustpython-stdlib = {workspace = true, features = ["threading"] }

View File

@@ -1,14 +1,9 @@
use crate::{PyObject, pystate::with_vm};
use alloc::slice;
use core::ffi::c_int;
pub use mapping::*;
use rustpython_vm::builtins::{PyDict, PyStr, PyTuple};
use rustpython_vm::function::{FuncArgs, KwArgs, PosArgs};
use rustpython_vm::{AsObject, Py, PyObjectRef, PyResult, VirtualMachine};
pub use sequence::*;
mod mapping;
mod sequence;
const PY_VECTORCALL_ARGUMENTS_OFFSET: usize = 1usize << (usize::BITS as usize - 1);

View File

@@ -1,66 +0,0 @@
use crate::{PyObject, pystate::with_vm};
#[unsafe(no_mangle)]
pub unsafe extern "C" fn PyMapping_Size(obj: *mut PyObject) -> isize {
with_vm(|vm| {
let obj = unsafe { &*obj };
obj.try_mapping(vm)?.length(vm)
})
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn PyMapping_Keys(obj: *mut PyObject) -> *mut PyObject {
with_vm(|vm| {
let obj = unsafe { &*obj };
let keys = obj.try_mapping(vm)?.keys(vm)?;
let iter = keys.get_iter(vm)?;
Ok(vm.ctx.new_list(iter.try_to_value(vm)?))
})
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn PyMapping_Values(obj: *mut PyObject) -> *mut PyObject {
with_vm(|vm| {
let obj = unsafe { &*obj };
let values = obj.try_mapping(vm)?.values(vm)?;
let iter = values.get_iter(vm)?;
Ok(vm.ctx.new_list(iter.try_to_value(vm)?))
})
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn PyMapping_Items(obj: *mut PyObject) -> *mut PyObject {
with_vm(|vm| {
let obj = unsafe { &*obj };
let items = obj.try_mapping(vm)?.items(vm)?;
let iter = items.get_iter(vm)?;
Ok(vm.ctx.new_list(iter.try_to_value(vm)?))
})
}
#[cfg(false)]
mod tests {
use pyo3::prelude::*;
use pyo3::types::{PyDict, PyMapping, PyMappingMethods, PyTuple};
#[test]
fn size_keys_values_items() {
Python::attach(|py| {
let dict = PyDict::new(py);
dict.set_item("a", 1).unwrap();
dict.set_item("b", 2).unwrap();
let mapping = dict.cast_into::<PyMapping>().unwrap();
assert_eq!(mapping.len().unwrap(), 2);
let keys = mapping.keys().unwrap();
assert_eq!(keys.len(), 2);
let values = mapping.values().unwrap();
assert_eq!(values.len(), 2);
let items = mapping.items().unwrap();
assert_eq!(items.len(), 2);
assert!(items.iter().all(|item| item.cast_into::<PyTuple>().is_ok()));
})
}
}

View File

@@ -1,286 +0,0 @@
use crate::{PyObject, pystate::with_vm};
use core::ffi::c_int;
#[unsafe(no_mangle)]
pub unsafe extern "C" fn PySequence_Check(obj: *mut PyObject) -> c_int {
with_vm(|_vm| {
let obj = unsafe { &*obj };
Ok(obj.sequence_unchecked().check())
})
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn PySequence_Concat(
obj1: *mut PyObject,
obj2: *mut PyObject,
) -> *mut PyObject {
with_vm(|vm| {
let obj1 = unsafe { &*obj1 };
let obj2 = unsafe { &*obj2 };
obj1.try_sequence(vm)?.concat(obj2, vm)
})
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn PySequence_Count(obj: *mut PyObject, value: *mut PyObject) -> isize {
with_vm(|vm| {
let obj = unsafe { &*obj };
let value = unsafe { &*value };
obj.try_sequence(vm)?.count(value, vm)
})
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn PySequence_DelItem(obj: *mut PyObject, index: isize) -> c_int {
with_vm(|vm| {
let obj = unsafe { &*obj };
obj.try_sequence(vm)?.del_item(index, vm)
})
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn PySequence_DelSlice(obj: *mut PyObject, low: isize, high: isize) -> c_int {
with_vm(|vm| {
let obj = unsafe { &*obj };
obj.try_sequence(vm)?.del_slice(low, high, vm)
})
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn PySequence_GetItem(obj: *mut PyObject, index: isize) -> *mut PyObject {
with_vm(|vm| {
let obj = unsafe { &*obj };
obj.try_sequence(vm)?.get_item(index, vm)
})
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn PySequence_GetSlice(
obj: *mut PyObject,
low: isize,
high: isize,
) -> *mut PyObject {
with_vm(|vm| {
let obj = unsafe { &*obj };
obj.try_sequence(vm)?.get_slice(low, high, vm)
})
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn PySequence_InPlaceConcat(
obj1: *mut PyObject,
obj2: *mut PyObject,
) -> *mut PyObject {
with_vm(|vm| {
let obj1 = unsafe { &*obj1 };
let obj2 = unsafe { &*obj2 };
obj1.try_sequence(vm)?.inplace_concat(obj2, vm)
})
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn PySequence_InPlaceRepeat(
obj: *mut PyObject,
count: isize,
) -> *mut PyObject {
with_vm(|vm| {
let obj = unsafe { &*obj };
obj.try_sequence(vm)?.inplace_repeat(count, vm)
})
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn PySequence_Index(obj: *mut PyObject, value: *mut PyObject) -> isize {
with_vm(|vm| {
let obj = unsafe { &*obj };
let value = unsafe { &*value };
obj.try_sequence(vm)?.index(value, vm)
})
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn PySequence_List(obj: *mut PyObject) -> *mut PyObject {
with_vm(|vm| {
let obj = unsafe { &*obj };
Ok(obj.try_sequence(vm)?.list(vm))
})
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn PySequence_Repeat(obj: *mut PyObject, count: isize) -> *mut PyObject {
with_vm(|vm| {
let obj = unsafe { &*obj };
obj.try_sequence(vm)?.repeat(count, vm)
})
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn PySequence_SetItem(
obj: *mut PyObject,
index: isize,
value: *mut PyObject,
) -> c_int {
with_vm(|vm| {
let obj = unsafe { &*obj };
let value = unsafe { &*value };
obj.try_sequence(vm)?.set_item(index, value.to_owned(), vm)
})
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn PySequence_SetSlice(
obj: *mut PyObject,
low: isize,
high: isize,
value: *mut PyObject,
) -> c_int {
with_vm(|vm| {
let obj = unsafe { &*obj };
let value = unsafe { &*value };
obj.try_sequence(vm)?
.set_slice(low, high, value.to_owned(), vm)
})
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn PySequence_Size(obj: *mut PyObject) -> isize {
with_vm(|vm| {
let obj = unsafe { &*obj };
obj.try_sequence(vm)?.length(vm)
})
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn PySequence_Length(obj: *mut PyObject) -> isize {
unsafe { PySequence_Size(obj) }
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn PySequence_Tuple(obj: *mut PyObject) -> *mut PyObject {
with_vm(|vm| {
let obj = unsafe { &*obj };
Ok(obj.try_sequence(vm)?.tuple(vm))
})
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn PySequence_Contains(obj: *mut PyObject, value: *mut PyObject) -> c_int {
with_vm(|vm| {
let obj = unsafe { &*obj };
let value = unsafe { &*value };
obj.sequence_unchecked().contains(value, vm)
})
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn PySequence_In(obj: *mut PyObject, value: *mut PyObject) -> c_int {
unsafe { PySequence_Contains(obj, value) }
}
#[cfg(false)]
mod tests {
use pyo3::prelude::*;
use pyo3::types::{PyAnyMethods, PyDict, PyList, PySequence, PySequenceMethods, PyTuple};
#[test]
fn item_and_size_ops() {
Python::attach(|py| {
let list = PyList::new(py, [1, 2, 3]).unwrap();
let seq = list.cast_into::<PySequence>().unwrap();
assert_eq!(seq.len().unwrap(), 3);
assert_eq!(seq.get_item(1).unwrap().extract::<i32>().unwrap(), 2);
seq.set_item(1, 4).unwrap();
assert_eq!(seq.get_item(1).unwrap().extract::<i32>().unwrap(), 4);
seq.del_item(1).unwrap();
assert_eq!(seq.get_item(1).unwrap().extract::<i32>().unwrap(), 3);
});
}
#[test]
fn slice_ops() {
Python::attach(|py| {
let list = PyList::new(py, [1, 2, 3, 4]).unwrap();
let seq = list.cast_into::<PySequence>().unwrap();
let sub = seq.get_slice(1, 3).unwrap();
assert_eq!(sub.get_item(0).unwrap().extract::<i32>().unwrap(), 2);
assert_eq!(sub.get_item(1).unwrap().extract::<i32>().unwrap(), 3);
let repl = PyList::new(py, [8, 9]).unwrap();
seq.set_slice(1, 3, &repl).unwrap();
assert_eq!(seq.get_item(1).unwrap().extract::<i32>().unwrap(), 8);
assert_eq!(seq.get_item(2).unwrap().extract::<i32>().unwrap(), 9);
seq.del_slice(1, 3).unwrap();
assert_eq!(seq.get_item(0).unwrap().extract::<i32>().unwrap(), 1);
assert_eq!(seq.get_item(1).unwrap().extract::<i32>().unwrap(), 4);
});
}
#[test]
fn concat_repeat_and_inplace_ops() {
Python::attach(|py| {
let list = PyList::new(py, [1, 2]).unwrap();
let seq = list.cast_into::<PySequence>().unwrap();
let rhs = PyList::new(py, [3])
.unwrap()
.cast_into::<PySequence>()
.unwrap();
let concat = seq.concat(&rhs).unwrap();
assert_eq!(concat.get_item(2).unwrap().extract::<i32>().unwrap(), 3);
let repeat = seq.repeat(2).unwrap();
assert_eq!(repeat.get_item(2).unwrap().extract::<i32>().unwrap(), 1);
let iadd_rhs = PyList::new(py, [4])
.unwrap()
.cast_into::<PySequence>()
.unwrap();
seq.in_place_concat(&iadd_rhs).unwrap();
assert_eq!(seq.get_item(2).unwrap().extract::<i32>().unwrap(), 4);
seq.in_place_repeat(2).unwrap();
assert_eq!(seq.get_item(5).unwrap().extract::<i32>().unwrap(), 4);
});
}
#[test]
fn count_index_contains_and_convert_ops() {
Python::attach(|py| {
let tuple = PyTuple::new(py, [1, 2, 1])
.unwrap()
.cast_into::<PySequence>()
.unwrap();
assert_eq!(tuple.count(1).unwrap(), 2);
assert_eq!(tuple.index(2).unwrap(), 1);
assert!(tuple.contains(1).unwrap());
let as_list = tuple.to_list().unwrap().cast_into::<PySequence>().unwrap();
assert_eq!(as_list.get_item(0).unwrap().extract::<i32>().unwrap(), 1);
let as_tuple = as_list
.to_tuple()
.unwrap()
.cast_into::<PySequence>()
.unwrap();
assert_eq!(as_tuple.get_item(2).unwrap().extract::<i32>().unwrap(), 1);
});
}
#[test]
fn contains_works_for_dict() {
Python::attach(|py| {
let dict = PyDict::new(py);
dict.set_item("k", 1).unwrap();
let any = dict.into_any();
assert!(any.contains("k").unwrap());
assert!(!any.contains("missing").unwrap());
});
}
}

View File

@@ -25,12 +25,10 @@ pub mod pyerrors;
pub mod pylifecycle;
pub mod pystate;
pub mod refcount;
pub mod setobject;
pub mod traceback;
pub mod tupleobject;
pub mod unicodeobject;
mod util;
pub mod weakrefobject;
/// Get main interpreter of this process. Will be None if it has not been initialized yet.
pub fn get_main_interpreter() -> MutexGuard<'static, Option<Interpreter>> {

View File

@@ -1,162 +0,0 @@
use crate::PyObject;
use crate::object::define_py_check;
use crate::pystate::with_vm;
use core::ffi::c_int;
use itertools::process_results;
use rustpython_vm::AsObject;
use rustpython_vm::PyPayload;
use rustpython_vm::TryFromObject;
use rustpython_vm::builtins::{PyFrozenSet, PySet};
use rustpython_vm::function::ArgIterable;
define_py_check!(fn PySet_Check, types.set_type);
define_py_check!(fn PyFrozenSet_Check, types.frozenset_type);
#[unsafe(no_mangle)]
pub unsafe extern "C" fn PySet_New(iterable: *mut PyObject) -> *mut PyObject {
with_vm(|vm| {
if iterable.is_null() {
return Ok(PySet::default().into_ref(&vm.ctx));
}
let iterable = ArgIterable::try_from_object(vm, unsafe { &*iterable }.to_owned())?;
let set = PySet::default().into_ref(&vm.ctx);
for item in iterable.iter(vm)? {
set.add(item?, vm)?;
}
Ok(set)
})
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn PyFrozenSet_New(iterable: *mut PyObject) -> *mut PyObject {
with_vm(|vm| {
if iterable.is_null() {
return Ok(vm.ctx.empty_frozenset.to_owned());
}
let iterable = ArgIterable::try_from_object(vm, unsafe { &*iterable }.to_owned())?;
let set = process_results(iterable.iter(vm)?, |it| PyFrozenSet::from_iter(vm, it))??;
Ok(set.into_ref(&vm.ctx))
})
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn PySet_Add(set: *mut PyObject, key: *mut PyObject) -> c_int {
with_vm(|vm| {
let set = unsafe { &*set }.try_downcast_ref::<PySet>(vm)?;
let key = unsafe { &*key }.to_owned();
set.add(key, vm)
})
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn PySet_Clear(set: *mut PyObject) -> c_int {
with_vm(|vm| {
let set = unsafe { &*set }.try_downcast_ref::<PySet>(vm)?;
set.clear();
Ok(())
})
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn PySet_Contains(anyset: *mut PyObject, key: *mut PyObject) -> c_int {
with_vm(|vm| {
let anyset = unsafe { &*anyset };
let key = unsafe { &*key };
if let Some(set) = anyset.downcast_ref::<PySet>() {
set.__contains__(key, vm)
} else if let Some(frozenset) = anyset.downcast_ref::<PyFrozenSet>() {
frozenset.__contains__(key, vm)
} else {
Err(vm.new_type_error(format!(
"expected set or frozenset, got '{}'",
anyset.class().name()
)))
}
})
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn PySet_Discard(set: *mut PyObject, key: *mut PyObject) -> c_int {
with_vm(|vm| {
let set = unsafe { &*set }.try_downcast_ref::<PySet>(vm)?;
let key = unsafe { &*key };
let had_item = set.__contains__(key, vm)?;
if had_item {
set.discard(key.to_owned(), vm)?;
}
Ok(had_item)
})
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn PySet_Pop(set: *mut PyObject) -> *mut PyObject {
with_vm(|vm| {
let set = unsafe { &*set }.try_downcast_ref::<PySet>(vm)?;
set.pop(vm)
})
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn PySet_Size(anyset: *mut PyObject) -> isize {
with_vm(|vm| {
let anyset = unsafe { &*anyset };
if let Some(set) = anyset.downcast_ref::<PySet>() {
set.as_object().length(vm)
} else if let Some(frozenset) = anyset.downcast_ref::<PyFrozenSet>() {
frozenset.as_object().length(vm)
} else {
Err(vm.new_type_error(format!(
"expected set or frozenset, got '{}'",
anyset.class().name()
)))
}
})
}
#[cfg(false)]
mod tests {
use pyo3::prelude::*;
use pyo3::types::{PyFrozenSet, PyInt, PySet};
#[test]
fn new_and_size() {
Python::attach(|py| {
let set = PySet::empty(py).unwrap();
assert!(set.is_instance_of::<PySet>());
assert_eq!(set.len(), 0);
let frozen = PyFrozenSet::empty(py).unwrap();
assert!(frozen.is_instance_of::<PyFrozenSet>());
assert_eq!(frozen.len(), 0);
})
}
#[test]
fn add_contains_discard() {
Python::attach(|py| {
let set = PySet::empty(py).unwrap();
let item = PyInt::new(py, 42);
set.add(&item).unwrap();
assert!(set.contains(&item).unwrap());
set.discard(&item).unwrap();
assert!(!set.contains(&item).unwrap());
})
}
#[test]
fn pop_reduces_size() {
Python::attach(|py| {
let set = PySet::empty(py).unwrap();
set.add(7).unwrap();
assert_eq!(set.len(), 1);
let popped = set.pop().unwrap();
assert_eq!(popped.extract::<i32>().unwrap(), 7);
assert_eq!(set.len(), 0);
})
}
}

View File

@@ -1,106 +0,0 @@
use crate::PyObject;
use crate::object::define_py_check;
use crate::pystate::with_vm;
use core::ffi::c_int;
use rustpython_vm::builtins::{PyWeak, PyWeakProxy};
define_py_check!(fn PyWeakref_CheckProxy, types.weakproxy_type);
define_py_check!(fn PyWeakref_CheckRef, types.weakref_type);
#[unsafe(no_mangle)]
pub unsafe extern "C" fn PyWeakref_GetRef(
reference: *mut PyObject,
result: *mut *mut PyObject,
) -> c_int {
with_vm(|vm| {
unsafe {
*result = core::ptr::null_mut();
}
let reference = unsafe { &*reference };
let upgraded = if let Some(weak) = reference.downcast_ref::<PyWeak>() {
weak.upgrade()
} else if let Some(proxy) = reference.downcast_ref::<PyWeakProxy>() {
proxy.get_weak().upgrade()
} else {
return Err(vm.new_type_error("expected a weakref"));
};
if let Some(obj) = upgraded {
unsafe {
*result = obj.into_raw().as_ptr();
}
Ok(true)
} else {
Ok(false)
}
})
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn PyWeakref_NewProxy(
ob: *mut PyObject,
callback: *mut PyObject,
) -> *mut PyObject {
with_vm(|vm| {
let ob = unsafe { &*ob };
let callback = unsafe { callback.as_ref() }
.filter(|callback| !vm.is_none(callback))
.map(ToOwned::to_owned);
PyWeakProxy::new_weakproxy(ob, callback, vm)
})
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn PyWeakref_NewRef(
ob: *mut PyObject,
callback: *mut PyObject,
) -> *mut PyObject {
with_vm(|vm| {
let ob = unsafe { &*ob };
let callback = unsafe { callback.as_ref() }
.filter(|callback| !vm.is_none(callback))
.map(ToOwned::to_owned);
ob.downgrade(callback, vm)
})
}
#[cfg(false)]
mod tests {
use pyo3::prelude::*;
use pyo3::types::PyAnyMethods;
use pyo3::types::{PyInt, PyWeakrefMethods, PyWeakrefProxy, PyWeakrefReference};
#[test]
fn check_ref_and_proxy() {
Python::attach(|py| {
let object_ty = py.get_type::<PyInt>();
let weak_ref = PyWeakrefReference::new(&object_ty).unwrap();
let weak_proxy = PyWeakrefProxy::new(&object_ty).unwrap();
assert!(weak_ref.is_instance_of::<PyWeakrefReference>());
assert!(weak_proxy.is_instance_of::<PyWeakrefProxy>());
});
}
#[test]
fn new_ref_and_get_ref() {
Python::attach(|py| {
let object_ty = py.get_type::<PyInt>();
let weak_ref = PyWeakrefReference::new(&object_ty).unwrap();
assert!(weak_ref.upgrade().is_some_and(|obj| obj.is(&object_ty)));
});
}
#[test]
fn new_proxy() {
Python::attach(|py| {
let object_ty = py.get_type::<PyInt>();
let weak_proxy = PyWeakrefProxy::new(&object_ty).unwrap();
assert!(weak_proxy.upgrade().is_some_and(|obj| obj.is(&object_ty)));
});
}
}

View File

@@ -1,9 +1,4 @@
pub use ruff_python_ast::token::TokenKind;
use ruff_python_parser::{LexicalErrorType, ParseErrorType};
use ruff_source_file::{PositionEncoding, SourceFile, SourceFileBuilder, SourceLocation};
use ruff_text_size::TextSlice;
use thiserror::Error;
use rustpython_codegen::{compile, symboltable};
pub use rustpython_codegen::compile::CompileOpts;
@@ -14,19 +9,20 @@ pub use ruff_python_ast as ast;
pub use ruff_python_parser as parser;
pub use rustpython_codegen as codegen;
pub use rustpython_compiler_core as core;
use thiserror::Error;
#[derive(Error, Debug)]
pub enum CompileErrorType {
#[error(transparent)]
Codegen(#[from] codegen::error::CodegenErrorType),
#[error(transparent)]
Parse(#[from] ParseErrorType),
Parse(#[from] parser::ParseErrorType),
}
#[derive(Error, Debug)]
pub struct ParseError {
#[source]
pub error: ParseErrorType,
pub error: parser::ParseErrorType,
pub raw_location: ruff_text_size::TextRange,
pub location: SourceLocation,
pub end_location: SourceLocation,
@@ -58,140 +54,57 @@ impl CompileError {
// For EOF errors (unclosed brackets), find the unclosed bracket position
// and adjust both the error location and message
let mut is_unclosed_bracket = false;
let (error_type, location, end_location) = match &error.error {
ParseErrorType::Lexical(LexicalErrorType::Eof) => {
if let Some((bracket_char, bracket_offset)) = find_unclosed_bracket(source_text) {
let bracket_text_size = ruff_text_size::TextSize::new(bracket_offset as u32);
let loc =
source_code.source_location(bracket_text_size, PositionEncoding::Utf8);
let end_loc = SourceLocation {
line: loc.line,
character_offset: loc.character_offset.saturating_add(1),
};
let msg = format!("'{bracket_char}' was never closed");
is_unclosed_bracket = true;
(ParseErrorType::OtherError(msg), loc, end_loc)
} else {
let loc =
source_code.source_location(error.location.start(), PositionEncoding::Utf8);
let end_loc =
source_code.source_location(error.location.end(), PositionEncoding::Utf8);
(error.error, loc, end_loc)
}
}
ParseErrorType::Lexical(LexicalErrorType::IndentationError) => {
// For IndentationError, point the offset to the end of the line content
// instead of the beginning
let loc =
source_code.source_location(error.location.start(), PositionEncoding::Utf8);
let line_idx = loc.line.to_zero_indexed();
let line = source_text.split('\n').nth(line_idx).unwrap_or("");
let line_end_col = line.chars().count() + 1; // 1-indexed, past last char
let (error_type, location, end_location) = if matches!(
&error.error,
parser::ParseErrorType::Lexical(parser::LexicalErrorType::Eof)
) {
if let Some((bracket_char, bracket_offset)) = find_unclosed_bracket(source_text) {
let bracket_text_size = ruff_text_size::TextSize::new(bracket_offset as u32);
let loc = source_code.source_location(bracket_text_size, PositionEncoding::Utf8);
let end_loc = SourceLocation {
line: loc.line,
character_offset: ruff_source_file::OneIndexed::new(line_end_col)
.unwrap_or(loc.character_offset),
character_offset: loc.character_offset.saturating_add(1),
};
(error.error, end_loc, end_loc)
}
ParseErrorType::ExpectedToken { expected, found }
if matches!((expected, found), (TokenKind::Comma, TokenKind::Int)) =>
{
let msg = format!("'{bracket_char}' was never closed");
is_unclosed_bracket = true;
(parser::ParseErrorType::OtherError(msg), loc, end_loc)
} else {
let loc =
source_code.source_location(error.location.start(), PositionEncoding::Utf8);
let mut end_loc =
let end_loc =
source_code.source_location(error.location.end(), PositionEncoding::Utf8);
// If the error range ends at the start of a new line (column 1),
// adjust it to the end of the previous line
if end_loc.character_offset.get() == 1 && end_loc.line > loc.line {
let prev_line_end = error.location.end() - ruff_text_size::TextSize::from(1);
end_loc = source_code.source_location(prev_line_end, PositionEncoding::Utf8);
end_loc.character_offset = end_loc.character_offset.saturating_add(1);
}
let msg = "invalid syntax. Perhaps you forgot a comma?".into();
(ParseErrorType::OtherError(msg), loc, end_loc)
}
ParseErrorType::InvalidAssignmentTarget => {
let loc =
source_code.source_location(error.location.start(), PositionEncoding::Utf8);
let mut end_loc =
source_code.source_location(error.location.end(), PositionEncoding::Utf8);
// If the error range ends at the start of a new line (column 1),
// adjust it to the end of the previous line
if end_loc.character_offset.get() == 1 && end_loc.line > loc.line {
let prev_line_end = error.location.end() - ruff_text_size::TextSize::from(1);
end_loc = source_code.source_location(prev_line_end, PositionEncoding::Utf8);
end_loc.character_offset = end_loc.character_offset.saturating_add(1);
}
let expr_str = source_file.source_text().slice(error.location);
let msg = parser::parse_expression(expr_str).map_or_else(
|_| match expr_str {
"yield" => "assignment to yield expression not possible".into(),
_ => format!("cannot assign to {expr_str}"),
},
|parsed| match *parsed.syntax().body {
ast::Expr::Call(_) => "cannot assign to function call".into(),
ast::Expr::BinOp(_) => "cannot assign to expression".into(),
ast::Expr::If(_) => "cannot assign to conditional expression".into(),
ast::Expr::Generator(_) => "cannot assign to generator expression".into(),
ast::Expr::StringLiteral(_)
| ast::Expr::BytesLiteral(_)
| ast::Expr::NumberLiteral(_) => {
"cannot assign to literal here. Maybe you meant '==' instead of '='?"
.into()
}
ast::Expr::EllipsisLiteral(_) => {
"cannot assign to ellipsis here. Maybe you meant '==' instead of '='?"
.into()
}
_ => format!("cannot assign to {expr_str}"),
},
);
(ParseErrorType::OtherError(msg), loc, end_loc)
}
ParseErrorType::InvalidNamedAssignmentTarget => {
let loc =
source_code.source_location(error.location.start(), PositionEncoding::Utf8);
let mut end_loc =
source_code.source_location(error.location.end(), PositionEncoding::Utf8);
// If the error range ends at the start of a new line (column 1),
// adjust it to the end of the previous line
if end_loc.character_offset.get() == 1 && end_loc.line > loc.line {
let prev_line_end = error.location.end() - ruff_text_size::TextSize::from(1);
end_loc = source_code.source_location(prev_line_end, PositionEncoding::Utf8);
end_loc.character_offset = end_loc.character_offset.saturating_add(1);
}
let target = source_file.source_text().slice(error.location);
let msg = format!("cannot use assignment expressions with {target}");
(ParseErrorType::OtherError(msg), loc, end_loc)
}
_ => {
let loc =
source_code.source_location(error.location.start(), PositionEncoding::Utf8);
let mut end_loc =
source_code.source_location(error.location.end(), PositionEncoding::Utf8);
// If the error range ends at the start of a new line (column 1),
// adjust it to the end of the previous line
if end_loc.character_offset.get() == 1 && end_loc.line > loc.line {
let prev_line_end = error.location.end() - ruff_text_size::TextSize::from(1);
end_loc = source_code.source_location(prev_line_end, PositionEncoding::Utf8);
end_loc.character_offset = end_loc.character_offset.saturating_add(1);
}
(error.error, loc, end_loc)
}
} else if matches!(
&error.error,
parser::ParseErrorType::Lexical(parser::LexicalErrorType::IndentationError)
) {
// For IndentationError, point the offset to the end of the line content
// instead of the beginning
let loc = source_code.source_location(error.location.start(), PositionEncoding::Utf8);
let line_idx = loc.line.to_zero_indexed();
let line = source_text.split('\n').nth(line_idx).unwrap_or("");
let line_end_col = line.chars().count() + 1; // 1-indexed, past last char
let end_loc = SourceLocation {
line: loc.line,
character_offset: ruff_source_file::OneIndexed::new(line_end_col)
.unwrap_or(loc.character_offset),
};
(error.error, end_loc, end_loc)
} else {
let loc = source_code.source_location(error.location.start(), PositionEncoding::Utf8);
let mut end_loc =
source_code.source_location(error.location.end(), PositionEncoding::Utf8);
// If the error range ends at the start of a new line (column 1),
// adjust it to the end of the previous line
if end_loc.character_offset.get() == 1 && end_loc.line > loc.line {
let prev_line_end = error.location.end() - ruff_text_size::TextSize::from(1);
end_loc = source_code.source_location(prev_line_end, PositionEncoding::Utf8);
end_loc.character_offset = end_loc.character_offset.saturating_add(1);
}
(error.error, loc, end_loc)
};
Self::Parse(ParseError {

View File

@@ -20,7 +20,7 @@ sqlite = ["dep:libsqlite3-sys"]
ssl = ["host_env"]
ssl-rustls = ["__ssl-rustls", "rustls/custom-provider"]
ssl-openssl = ["ssl", "openssl", "openssl-sys", "foreign-types-shared", "openssl-probe"]
ssl-openssl-vendor = ["ssl-openssl", "openssl/vendored"]
ssl-vendor = ["ssl-openssl", "openssl/vendored"]
tkinter = ["dep:tk-sys", "dep:tcl-sys", "dep:widestring"]
flame-it = ["flame"]

View File

@@ -1,309 +0,0 @@
pub(crate) use _queue::module_def;
#[pymodule]
mod _queue {
use alloc::collections::VecDeque;
use core::time::Duration;
use std::time::Instant;
use crate::vm::{
AsObject, Py, PyObject, PyObjectRef, PyPayload, PyRef, PyResult, VirtualMachine,
builtins::{PyBaseExceptionRef, PyException, PyGenericAlias, PyStr, PyType, PyTypeRef},
function::{PyComparisonValue, TimeoutSeconds},
protocol::PyNumberMethods,
types::{AsNumber, Comparable, Constructor, PyComparisonOp, Representable},
};
type BufInner = VecDeque<PyObjectRef>;
cfg_select! {
feature = "threading" => {
use parking_lot::{Condvar, Mutex, MutexGuard};
type Buf = Mutex<BufInner>;
},
_ => {
use crate::common::lock::PyMutex;
type Buf = PyMutex<BufInner>;
}
}
const INITIAL_RING_BUF_CAPACITY: usize = 8;
#[pyattr]
#[pyclass(module = "_queue", name = "Empty", base = PyException)]
#[repr(transparent)]
pub(crate) struct PyEmptyError(PyException);
#[pyclass(flags(HAS_WEAKREF))]
impl PyEmptyError {}
/// ## See Also
///
/// [`empty_error`](https://github.com/python/cpython/blob/v3.14.5/Modules/_queuemodule.c#L347-L355).
fn empty_error(vm: &VirtualMachine) -> PyBaseExceptionRef {
vm.new_exception_empty(PyEmptyError::class(&vm.ctx).to_owned())
}
#[cfg(feature = "threading")]
#[derive(Debug)]
struct Semaphore {
mutex: Mutex<usize>,
cond: Condvar,
}
#[cfg(feature = "threading")]
impl Semaphore {
#[must_use]
fn new() -> Self {
Self {
mutex: Mutex::new(0),
cond: Condvar::new(),
}
}
fn release(&self) {
{
let mut count = self.mutex.lock();
*count += 1;
} // lock dropped. now we can notify a waiting thread
self.cond.notify_one();
}
/// Returns `true` if the semaphore was acquired, `false` on timeout.
#[must_use]
fn acquire(&self, block: bool, deadline: Option<Instant>, vm: &VirtualMachine) -> bool {
let mut count = self.mutex.lock();
loop {
if *count > 0 {
*count -= 1;
return true;
}
if !block {
return false;
}
match deadline {
Some(dl) => {
let result = vm.allow_threads(|| self.cond.wait_until(&mut count, dl));
if result.timed_out() && *count == 0 {
return false;
}
}
None => {
vm.allow_threads(|| self.cond.wait(&mut count));
}
}
}
}
}
#[pyattr]
#[pyclass(module = "_queue", name = "SimpleQueue", unhashable = true)]
#[derive(Debug, PyPayload)]
struct PySimpleQueue {
buf: Buf,
#[cfg(feature = "threading")]
sem: Semaphore,
}
impl Default for PySimpleQueue {
fn default() -> Self {
Self {
buf: Buf::new(VecDeque::with_capacity(INITIAL_RING_BUF_CAPACITY)),
#[cfg(feature = "threading")]
sem: Semaphore::new(),
}
}
}
impl PySimpleQueue {
fn push(&self, item: PyObjectRef) {
self.buf.lock().push_back(item);
#[cfg(feature = "threading")]
self.sem.release();
}
/// Returns a strong reference from the head of the buffer.
///
/// ## See Also
///
/// [`RingBuf_Get`](https://github.com/python/cpython/blob/v3.14.5/Modules/_queuemodule.c#L133-L154).
fn get_inner(
#[cfg(feature = "threading")] buf: &mut MutexGuard<'_, BufInner>,
#[cfg(not(feature = "threading"))] buf: &mut BufInner,
) -> Option<PyObjectRef> {
let cap = buf.capacity();
if buf.len() < (cap / 4) {
// Items is less than 25% occupied, shrink it by 50%. This allows for
// growth without immediately needing to resize the underlying items array
buf.shrink_to(cap / 2)
}
buf.pop_front()
}
}
#[derive(FromArgs)]
struct PutArgs {
#[pyarg(positional)]
item: PyObjectRef,
#[expect(
dead_code,
reason = "Intentional. Provide compatibility with the Queue class"
)]
#[pyarg(any, optional, default = true)]
block: bool,
#[expect(
dead_code,
reason = "Intentional. Provide compatibility with the Queue class"
)]
#[pyarg(any, optional)]
timeout: Option<PyObjectRef>,
}
#[derive(FromArgs)]
struct GetArgs {
#[pyarg(any, optional, default = true)]
block: bool,
#[pyarg(any, optional)]
timeout: Option<TimeoutSeconds>,
}
#[pyclass(
with(Constructor, Comparable, Representable),
flags(BASETYPE, HAS_WEAKREF, IMMUTABLETYPE)
)]
impl PySimpleQueue {
#[pymethod]
fn empty(&self) -> bool {
self.buf.lock().is_empty()
}
#[pymethod]
fn qsize(&self) -> usize {
self.buf.lock().len()
}
#[pymethod]
fn put(&self, args: PutArgs) {
let PutArgs { item, .. } = args;
self.push(item);
}
#[pymethod]
fn put_nowait(&self, item: PyObjectRef) {
self.push(item);
}
#[pymethod]
fn get(&self, args: GetArgs, vm: &VirtualMachine) -> PyResult<PyObjectRef> {
let GetArgs { block, timeout } = args;
// Non-blocking: just try once
if !block {
return Self::get_inner(&mut self.buf.lock()).ok_or_else(|| empty_error(vm));
}
#[cfg_attr(
not(feature = "threading"),
expect(
unused_variables,
reason = "We are still validating the 'timeout' arg even if we don't have threading"
)
)]
let deadline = match timeout.map(|v| v.to_secs_f64()) {
Some(v) if v < 0.0 => {
return Err(vm.new_value_error("'timeout' must be a non-negative number"));
}
Some(v) => Some(Instant::now() + Duration::from_secs_f64(v)),
None => None,
};
#[cfg(feature = "threading")]
{
if !self.sem.acquire(block, deadline, vm) {
return Err(empty_error(vm));
}
}
Self::get_inner(&mut self.buf.lock()).ok_or_else(|| empty_error(vm))
}
#[pymethod]
fn get_nowait(&self, vm: &VirtualMachine) -> PyResult<PyObjectRef> {
#[cfg(feature = "threading")]
{
if !self.sem.acquire(false, None, vm) {
return Err(empty_error(vm));
}
}
Self::get_inner(&mut self.buf.lock()).ok_or_else(|| empty_error(vm))
}
#[pyclassmethod]
fn __class_getitem__(
cls: PyTypeRef,
args: PyObjectRef,
vm: &VirtualMachine,
) -> PyGenericAlias {
PyGenericAlias::from_args(cls, args, vm)
}
}
impl Constructor for PySimpleQueue {
type Args = ();
fn py_new(_cls: &Py<PyType>, _args: Self::Args, _vm: &VirtualMachine) -> PyResult<Self> {
Ok(Self::default())
}
}
impl AsNumber for PySimpleQueue {
fn as_number() -> &'static PyNumberMethods {
static AS_NUMBER: PyNumberMethods = PyNumberMethods {
boolean: Some(|number, _vm| {
let zelf = number.obj.downcast_ref::<PySimpleQueue>().unwrap();
Ok(!zelf.buf.lock().is_empty())
}),
..PyNumberMethods::NOT_IMPLEMENTED
};
&AS_NUMBER
}
}
impl Comparable for PySimpleQueue {
fn cmp(
zelf: &Py<Self>,
other: &PyObject,
op: PyComparisonOp,
_vm: &VirtualMachine,
) -> PyResult<PyComparisonValue> {
Ok(if let Some(res) = op.identical_optimization(zelf, other) {
res.into()
} else {
PyComparisonValue::NotImplemented
})
}
}
impl Representable for PySimpleQueue {
fn repr(zelf: &Py<Self>, vm: &VirtualMachine) -> PyResult<PyRef<PyStr>> {
Ok(vm.ctx.new_str(format!(
"<{} at {:#x}>",
Self::class(&vm.ctx).slot_name(),
zelf.get_id()
)))
}
fn repr_str(_zelf: &Py<Self>, _vm: &VirtualMachine) -> PyResult<String> {
unreachable!("repr() is overridden directly")
}
}
}

View File

@@ -53,7 +53,6 @@ mod math;
#[cfg(all(feature = "host_env", any(unix, windows)))]
mod mmap;
mod _queue;
mod pyexpat;
mod pystruct;
mod random;
@@ -226,7 +225,6 @@ pub fn stdlib_module_defs(ctx: &Context) -> Vec<&'static builtins::PyModuleDef>
posixshmem::module_def(ctx),
pyexpat::module_def(ctx),
pystruct::module_def(ctx),
_queue::module_def(ctx),
random::module_def(ctx),
#[cfg(all(feature = "host_env", unix, not(target_os = "redox")))]
resource::module_def(ctx),

View File

@@ -65,7 +65,7 @@ mod _ssl {
LazyLock, PyMappedRwLockReadGuard, PyMutex, PyRwLock, PyRwLockReadGuard,
PyRwLockWriteGuard,
},
socket::{self, PySocket, SockWaitKind, sock_wait},
socket::{self, PySocket},
vm::{
AsObject, Py, PyObject, PyObjectRef, PyPayload, PyRef, PyResult, VirtualMachine,
builtins::{
@@ -2297,31 +2297,34 @@ mod _ssl {
needs: SslNeeds,
deadline: &SocketDeadline,
vm: &VirtualMachine,
) -> PyResult<SelectRet> {
let Some(sock) = self.0.sock_opt() else {
return Ok(SelectRet::Closed);
) -> SelectRet {
let sock = match self.0.sock_opt() {
Some(s) => s,
None => return SelectRet::Closed,
};
// For blocking sockets without timeout, call sock_select with None timeout
// For blocking sockets without timeout, call sock_wait with None timeout
// to actually block waiting for data instead of busy-looping
let timeout = match &deadline {
Ok(deadline) => match deadline.checked_duration_since(Instant::now()) {
Some(d) => Some(d),
None => return Ok(SelectRet::TimedOut),
None => return SelectRet::TimedOut,
},
Err(true) => None, // Blocking: no timeout, wait indefinitely
Err(false) => return Ok(SelectRet::Nonblocking),
Err(false) => return SelectRet::Nonblocking,
};
let wait_kind = match needs {
SslNeeds::Read => SockWaitKind::Read,
SslNeeds::Write => SockWaitKind::Write,
};
sock_wait(&*sock, wait_kind, timeout, vm).map(|timed_out| {
if timed_out {
SelectRet::TimedOut
} else {
SelectRet::Ok
}
})
let res = socket::sock_wait(
&sock,
match needs {
SslNeeds::Read => socket::SockWaitKind::Read,
SslNeeds::Write => socket::SockWaitKind::Write,
},
timeout,
vm,
);
match res {
Ok(true) => SelectRet::TimedOut,
_ => SelectRet::Ok,
}
}
fn socket_needs(
@@ -2329,15 +2332,14 @@ mod _ssl {
err: &ssl::Error,
deadline: &SocketDeadline,
vm: &VirtualMachine,
) -> PyResult<(Option<SslNeeds>, SelectRet)> {
) -> (Option<SslNeeds>, SelectRet) {
let needs = match err.code() {
ssl::ErrorCode::WANT_READ => Some(SslNeeds::Read),
ssl::ErrorCode::WANT_WRITE => Some(SslNeeds::Write),
_ => None,
};
let state =
needs.map_or(Ok(SelectRet::Ok), |needs| self.select(needs, deadline, vm))?;
Ok((needs, state))
let state = needs.map_or(SelectRet::Ok, |needs| self.select(needs, deadline, vm));
(needs, state)
}
}
@@ -2855,7 +2857,7 @@ mod _ssl {
break;
}
// Wait briefly for peer's close_notify before retrying
match socket_stream.select(SslNeeds::Read, &deadline, vm)? {
match socket_stream.select(SslNeeds::Read, &deadline, vm) {
SelectRet::TimedOut => {
return Err(socket::timeout_error_msg(
vm,
@@ -2893,7 +2895,7 @@ mod _ssl {
};
// Wait on the socket
match socket_stream.select(needs, &deadline, vm)? {
match socket_stream.select(needs, &deadline, vm) {
SelectRet::TimedOut => {
let msg = if err == sys::SSL_ERROR_WANT_READ {
"The read operation timed out"
@@ -2989,7 +2991,7 @@ mod _ssl {
let (needs, state) = stream
.get_ref()
.expect("handshake called in bio mode; should only be called in socket mode")
.socket_needs(&err, &timeout, vm)?;
.socket_needs(&err, &timeout, vm);
match state {
SelectRet::TimedOut => {
// Clean up SNI ex_data before returning error
@@ -3043,7 +3045,7 @@ mod _ssl {
.get_ref()
.expect("write called in bio mode; should only be called in socket mode");
let timeout = socket_ref.timeout_deadline();
let state = socket_ref.select(SslNeeds::Write, &timeout, vm)?;
let state = socket_ref.select(SslNeeds::Write, &timeout, vm);
match state {
SelectRet::TimedOut => {
return Err(socket::timeout_error_msg(
@@ -3063,7 +3065,7 @@ mod _ssl {
let (needs, state) = stream
.get_ref()
.expect("write called in bio mode; should only be called in socket mode")
.socket_needs(&err, &timeout, vm)?;
.socket_needs(&err, &timeout, vm);
match state {
SelectRet::TimedOut => {
return Err(socket::timeout_error_msg(
@@ -3234,7 +3236,7 @@ mod _ssl {
let (needs, state) = stream
.get_ref()
.expect("read called in bio mode; should only be called in socket mode")
.socket_needs(&err, &timeout, vm)?;
.socket_needs(&err, &timeout, vm);
match state {
SelectRet::TimedOut => {
return Err(socket::timeout_error_msg(
@@ -4147,7 +4149,7 @@ mod bio {
use openssl_sys as sys;
use std::marker::PhantomData;
pub(super) struct MemBioSlice<'a>(*mut sys::BIO, PhantomData<&'a [u8]>);
pub struct MemBioSlice<'a>(*mut sys::BIO, PhantomData<&'a [u8]>);
impl Drop for MemBioSlice<'_> {
fn drop(&mut self) {
@@ -4158,7 +4160,7 @@ mod bio {
}
impl<'a> MemBioSlice<'a> {
pub(super) fn new(buf: &'a [u8]) -> Result<MemBioSlice<'a>, ErrorStack> {
pub fn new(buf: &'a [u8]) -> Result<MemBioSlice<'a>, ErrorStack> {
openssl::init();
assert!(buf.len() <= c_int::MAX as usize);
@@ -4170,7 +4172,7 @@ mod bio {
Ok(MemBioSlice(bio, PhantomData))
}
pub(super) fn as_ptr(&self) -> *mut sys::BIO {
pub fn as_ptr(&self) -> *mut sys::BIO {
self.0
}
}

View File

@@ -125,10 +125,6 @@ pub(crate) mod ssl_error {
)
}
#[cfg_attr(
all(feature = "ssl-openssl", not(feature = "ssl-rustls")),
expect(dead_code)
)]
pub(crate) fn create_ssl_zero_return_error(vm: &VirtualMachine) -> PyRef<PyOSError> {
vm.new_os_subtype_error(
PySSLZeroReturnError::class(&vm.ctx).to_owned(),
@@ -137,10 +133,6 @@ pub(crate) mod ssl_error {
)
}
#[cfg_attr(
all(feature = "ssl-openssl", not(feature = "ssl-rustls")),
expect(dead_code)
)]
pub(crate) fn create_ssl_syscall_error(
vm: &VirtualMachine,
msg: impl Into<String>,

View File

@@ -537,7 +537,7 @@ impl PySet {
self.inner.len()
}
pub fn __contains__(&self, needle: &PyObject, vm: &VirtualMachine) -> PyResult<bool> {
fn __contains__(&self, needle: &PyObject, vm: &VirtualMachine) -> PyResult<bool> {
self.inner.contains(needle, vm)
}
@@ -679,17 +679,17 @@ impl PySet {
}
#[pymethod]
pub fn discard(&self, item: PyObjectRef, vm: &VirtualMachine) -> PyResult<()> {
fn discard(&self, item: PyObjectRef, vm: &VirtualMachine) -> PyResult<()> {
self.inner.discard(&item, vm).map(|_| ())
}
#[pymethod]
pub fn clear(&self) {
fn clear(&self) {
self.inner.clear()
}
#[pymethod]
pub fn pop(&self, vm: &VirtualMachine) -> PyResult {
fn pop(&self, vm: &VirtualMachine) -> PyResult {
self.inner.pop(vm)
}
@@ -995,7 +995,7 @@ impl PyFrozenSet {
self.inner.len()
}
pub fn __contains__(&self, needle: &PyObject, vm: &VirtualMachine) -> PyResult<bool> {
fn __contains__(&self, needle: &PyObject, vm: &VirtualMachine) -> PyResult<bool> {
self.inner.contains(needle, vm)
}

View File

@@ -42,22 +42,6 @@ impl Constructor for PyWeakProxy {
Self::Args { referent, callback }: Self::Args,
vm: &VirtualMachine,
) -> PyResult<Self> {
let weak = Self::new_weak(referent.as_ref(), callback.into_option(), vm)?;
// TODO: PyWeakProxy should use the same payload as PyWeak
Ok(Self { weak })
}
}
crate::common::static_cell! {
static WEAK_SUBCLASS: PyTypeRef;
}
impl PyWeakProxy {
fn new_weak(
referent: &PyObject,
callback: Option<PyObjectRef>,
vm: &VirtualMachine,
) -> PyResult<PyRef<PyWeak>> {
// using an internal subclass as the class prevents us from getting the generic weakref,
// which would mess up the weakref count
let weak_cls = WEAK_SUBCLASS.get_or_init(|| {
@@ -68,22 +52,15 @@ impl PyWeakProxy {
super::PyWeak::make_slots(),
)
});
referent.downgrade_with_typ(callback, weak_cls.clone(), vm)
// TODO: PyWeakProxy should use the same payload as PyWeak
Ok(Self {
weak: referent.downgrade_with_typ(callback.into_option(), weak_cls.clone(), vm)?,
})
}
}
pub fn new_weakproxy(
referent: &PyObject,
callback: Option<PyObjectRef>,
vm: &VirtualMachine,
) -> PyResult<PyRef<Self>> {
let weak = Self::new_weak(referent, callback, vm)?;
Ok(Self { weak }.into_ref(&vm.ctx))
}
#[must_use]
pub fn get_weak(&self) -> &PyRef<PyWeak> {
&self.weak
}
crate::common::static_cell! {
static WEAK_SUBCLASS: PyTypeRef;
}
#[pyclass(with(

View File

@@ -131,7 +131,7 @@ impl Comparable for PyWeak {
) -> PyResult<PyComparisonValue> {
op.eq_only(|| {
let other = class_or_notimplemented!(Self, other);
let both = zelf.upgrade().zip(other.upgrade());
let both = zelf.upgrade().and_then(|s| other.upgrade().map(|o| (s, o)));
match both {
// CPython parity (Objects/weakref.c::weakref_richcompare): use
// PyObject_RichCompare on the referents, not the bool variant,

View File

@@ -1006,6 +1006,10 @@ mod builtins {
exp: y,
modulus,
} = args;
#[expect(
clippy::unnecessary_option_map_or_else,
reason = "changing this won't compile"
)]
let modulus = modulus
.as_ref()
.map_or_else(|| vm.ctx.none.as_object(), |m| m);

View File

@@ -733,18 +733,6 @@ const ERROR_CODES: &[(&str, i32)] = &[
ERPCMISMATCH
),
e!(cfg(target_vendor = "apple"), ESHLIBVERS),
e!(
cfg(all(
unix,
any(target_os = "linux", target_os = "fuchsia"),
not(any(
target_os = "freebsd",
target_os = "android",
target_vendor = "apple",
))
)),
EHWPOISON
),
];
#[cfg(not(any(unix, windows, target_os = "wasi")))]

Some files were not shown because too many files have changed in this diff Show More