mirror of
https://github.com/RustPython/RustPython.git
synced 2026-06-02 19:39:49 +09:00
Compare commits
1 Commits
main
...
dependabot
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2c97de2528 |
@@ -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`."
|
||||
10
.github/PULL_REQUEST_TEMPLATE.md
vendored
10
.github/PULL_REQUEST_TEMPLATE.md
vendored
@@ -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? -->
|
||||
|
||||
17
.github/workflows/ci.yaml
vendored
17
.github/workflows/ci.yaml
vendored
@@ -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
|
||||
|
||||
5
.github/workflows/cron-ci.yaml
vendored
5
.github/workflows/cron-ci.yaml
vendored
@@ -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
76
Cargo.lock
generated
@@ -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"
|
||||
|
||||
15
Cargo.toml
15
Cargo.toml
@@ -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"
|
||||
|
||||
@@ -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
|
||||
30
Lib/asyncio/__main__.py
vendored
30
Lib/asyncio/__main__.py
vendored
@@ -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)
|
||||
|
||||
11
Lib/asyncio/base_events.py
vendored
11
Lib/asyncio/base_events.py
vendored
@@ -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)
|
||||
|
||||
4
Lib/asyncio/base_subprocess.py
vendored
4
Lib/asyncio/base_subprocess.py
vendored
@@ -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
|
||||
|
||||
4
Lib/asyncio/futures.py
vendored
4
Lib/asyncio/futures.py
vendored
@@ -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():
|
||||
|
||||
2
Lib/asyncio/queues.py
vendored
2
Lib/asyncio/queues.py
vendored
@@ -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.
|
||||
"""
|
||||
|
||||
26
Lib/asyncio/windows_utils.py
vendored
26
Lib/asyncio/windows_utils.py
vendored
@@ -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:
|
||||
|
||||
8
Lib/importlib/_bootstrap.py
vendored
8
Lib/importlib/_bootstrap.py
vendored
@@ -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'
|
||||
|
||||
2
Lib/importlib/_bootstrap_external.py
vendored
2
Lib/importlib/_bootstrap_external.py
vendored
@@ -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:
|
||||
|
||||
118
Lib/multiprocessing/connection.py
vendored
118
Lib/multiprocessing/connection.py
vendored
@@ -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
|
||||
|
||||
36
Lib/multiprocessing/context.py
vendored
36
Lib/multiprocessing/context.py
vendored
@@ -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'
|
||||
|
||||
2
Lib/multiprocessing/dummy/__init__.py
vendored
2
Lib/multiprocessing/dummy/__init__.py
vendored
@@ -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()
|
||||
|
||||
96
Lib/multiprocessing/forkserver.py
vendored
96
Lib/multiprocessing/forkserver.py
vendored
@@ -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):
|
||||
|
||||
59
Lib/multiprocessing/managers.py
vendored
59
Lib/multiprocessing/managers.py
vendored
@@ -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)
|
||||
|
||||
15
Lib/multiprocessing/popen_fork.py
vendored
15
Lib/multiprocessing/popen_fork.py
vendored
@@ -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()
|
||||
|
||||
11
Lib/multiprocessing/process.py
vendored
11
Lib/multiprocessing/process.py
vendored
@@ -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()
|
||||
|
||||
2
Lib/multiprocessing/queues.py
vendored
2
Lib/multiprocessing/queues.py
vendored
@@ -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()
|
||||
|
||||
12
Lib/multiprocessing/reduction.py
vendored
12
Lib/multiprocessing/reduction.py
vendored
@@ -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))
|
||||
|
||||
97
Lib/multiprocessing/resource_tracker.py
vendored
97
Lib/multiprocessing/resource_tracker.py
vendored
@@ -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'{'):
|
||||
|
||||
2
Lib/multiprocessing/shared_memory.py
vendored
2
Lib/multiprocessing/shared_memory.py
vendored
@@ -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)
|
||||
|
||||
2
Lib/multiprocessing/spawn.py
vendored
2
Lib/multiprocessing/spawn.py
vendored
@@ -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
|
||||
|
||||
31
Lib/multiprocessing/synchronize.py
vendored
31
Lib/multiprocessing/synchronize.py
vendored
@@ -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)
|
||||
|
||||
14
Lib/multiprocessing/util.py
vendored
14
Lib/multiprocessing/util.py
vendored
@@ -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
615
Lib/profile.py
vendored
@@ -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
777
Lib/pstats.py
vendored
@@ -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.
|
||||
62
Lib/test/_test_embed_structseq.py
vendored
62
Lib/test/_test_embed_structseq.py
vendored
@@ -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")
|
||||
809
Lib/test/_test_multiprocessing.py
vendored
809
Lib/test/_test_multiprocessing.py
vendored
File diff suppressed because it is too large
Load Diff
30
Lib/test/mp_preload_large_sysargv.py
vendored
30
Lib/test/mp_preload_large_sysargv.py
vendored
@@ -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]}")
|
||||
22
Lib/test/mp_preload_sysargv.py
vendored
22
Lib/test/mp_preload_sysargv.py
vendored
@@ -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
BIN
Lib/test/pstats.pck
vendored
Binary file not shown.
114
Lib/test/test_android.py
vendored
114
Lib/test/test_android.py
vendored
@@ -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"])
|
||||
|
||||
25
Lib/test/test_asyncio/test_sock_lowlevel.py
vendored
25
Lib/test/test_asyncio/test_sock_lowlevel.py
vendored
@@ -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
|
||||
|
||||
|
||||
40
Lib/test/test_asyncio/test_ssl.py
vendored
40
Lib/test/test_asyncio/test_ssl.py
vendored
@@ -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')
|
||||
|
||||
42
Lib/test/test_asyncio/test_streams.py
vendored
42
Lib/test/test_asyncio/test_streams.py
vendored
@@ -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()
|
||||
|
||||
31
Lib/test/test_asyncio/test_subprocess.py
vendored
31
Lib/test/test_asyncio/test_subprocess.py
vendored
@@ -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:
|
||||
|
||||
|
||||
49
Lib/test/test_asyncio/test_tasks.py
vendored
49
Lib/test/test_asyncio/test_tasks.py
vendored
@@ -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):
|
||||
|
||||
44
Lib/test/test_asyncio/test_windows_utils.py
vendored
44
Lib/test/test_asyncio/test_windows_utils.py
vendored
@@ -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()
|
||||
|
||||
9
Lib/test/test_binascii.py
vendored
9
Lib/test/test_binascii.py
vendored
@@ -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:
|
||||
|
||||
27
Lib/test/test_builtin.py
vendored
27
Lib/test/test_builtin.py
vendored
@@ -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")
|
||||
|
||||
473
Lib/test/test_cmd_line.py
vendored
473
Lib/test/test_cmd_line.py
vendored
@@ -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'")
|
||||
|
||||
|
||||
92
Lib/test/test_cmd_line_script.py
vendored
92
Lib/test/test_cmd_line_script.py
vendored
@@ -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
|
||||
|
||||
234
Lib/test/test_cprofile.py
vendored
234
Lib/test/test_cprofile.py
vendored
@@ -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()
|
||||
15
Lib/test/test_defaultdict.py
vendored
15
Lib/test/test_defaultdict.py
vendored
@@ -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
2032
Lib/test/test_embed.py
vendored
File diff suppressed because it is too large
Load Diff
9
Lib/test/test_file_eintr.py
vendored
9
Lib/test/test_file_eintr.py
vendored
@@ -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
177
Lib/test/test_gc.py
vendored
@@ -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()
|
||||
|
||||
|
||||
2
Lib/test/test_generators.py
vendored
2
Lib/test/test_generators.py
vendored
@@ -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
|
||||
|
||||
2
Lib/test/test_genexps.py
vendored
2
Lib/test/test_genexps.py
vendored
@@ -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
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
|
||||
|
||||
1
Lib/test/test_import/data/unwritable/x.py
vendored
1
Lib/test/test_import/data/unwritable/x.py
vendored
@@ -0,0 +1 @@
|
||||
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import os
|
||||
import unittest
|
||||
|
||||
from . import util
|
||||
|
||||
4
Lib/test/test_importlib/test_locks.py
vendored
4
Lib/test/test_importlib/test_locks.py
vendored
@@ -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()
|
||||
|
||||
66
Lib/test/test_importlib/test_threaded_import.py
vendored
66
Lib/test/test_importlib/test_threaded_import.py
vendored
@@ -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()
|
||||
|
||||
6
Lib/test/test_importlib/util.py
vendored
6
Lib/test/test_importlib/util.py
vendored
@@ -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
385
Lib/test/test_mmap.py
vendored
@@ -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')
|
||||
|
||||
415
Lib/test/test_multibytecodec.py
vendored
415
Lib/test/test_multibytecodec.py
vendored
@@ -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()
|
||||
@@ -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()
|
||||
|
||||
1
Lib/test/test_named_expressions.py
vendored
1
Lib/test/test_named_expressions.py
vendored
@@ -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)"""
|
||||
|
||||
|
||||
20
Lib/test/test_posix.py
vendored
20
Lib/test/test_posix.py
vendored
@@ -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')),
|
||||
|
||||
223
Lib/test/test_profile.py
vendored
223
Lib/test/test_profile.py
vendored
@@ -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()
|
||||
164
Lib/test/test_pstats.py
vendored
164
Lib/test/test_pstats.py
vendored
@@ -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()
|
||||
6
Lib/test/test_script_helper.py
vendored
6
Lib/test/test_script_helper.py
vendored
@@ -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
|
||||
|
||||
|
||||
94
Lib/test/test_super.py
vendored
94
Lib/test/test_super.py
vendored
@@ -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()
|
||||
|
||||
86
Lib/test/test_syntax.py
vendored
86
Lib/test/test_syntax.py
vendored
@@ -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
|
||||
|
||||
|
||||
1151
Lib/test/test_tracemalloc.py
vendored
1151
Lib/test/test_tracemalloc.py
vendored
File diff suppressed because it is too large
Load Diff
39
Lib/test/test_ucn.py
vendored
39
Lib/test/test_ucn.py
vendored
@@ -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):
|
||||
|
||||
4
Lib/test/test_unicode_file_functions.py
vendored
4
Lib/test/test_unicode_file_functions.py
vendored
@@ -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)
|
||||
|
||||
5
Lib/test/test_unicode_identifiers.py
vendored
5
Lib/test/test_unicode_identifiers.py
vendored
@@ -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
84
Lib/test/test_uuid.py
vendored
@@ -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
13
Lib/test/test_venv.py
vendored
@@ -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
560
Lib/tracemalloc.py
vendored
@@ -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
10
Lib/uuid.py
vendored
@@ -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:
|
||||
|
||||
24
README.md
24
README.md
@@ -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
|
||||
|
||||
|
||||
@@ -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"] }
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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()));
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -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());
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -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>> {
|
||||
|
||||
@@ -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);
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -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)));
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -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 {
|
||||
|
||||
@@ -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"]
|
||||
|
||||
|
||||
@@ -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")
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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),
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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>,
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
Reference in New Issue
Block a user