Compare commits

...

17 Commits

Author SHA1 Message Date
Shahar Naveh
3a6fda4daf Update opcode from 3.13.7 (#6156)
* Update `opcode` from 3.13.7

* Base `_opcode`

* Add `test__opcode.py` from 3.13.7

* Impl `has_*` methods

* Add more methods

* Update `dis.py` from 3.13.7

* Update `support/bytecode_helper.py` from 3.13.7

* correct is_valid

* Patch failing tests

* Unpatch `support/__init__.py`

* clippy

* Make comments to doc

* impl `_varname_from_oparg` for code

* Unmark passing tests

* Revert changes to `dis`

* Mark failing tests
2025-10-05 11:14:33 +09:00
dependabot[bot]
1aea1467da Bump on-headers, serve and compression in /wasm/demo (#6168)
Bumps [on-headers](https://github.com/jshttp/on-headers) to 1.1.0 and updates ancestor dependencies [on-headers](https://github.com/jshttp/on-headers), [serve](https://github.com/vercel/serve) and [compression](https://github.com/expressjs/compression). These dependencies need to be updated together.


Updates `on-headers` from 1.0.2 to 1.1.0
- [Release notes](https://github.com/jshttp/on-headers/releases)
- [Changelog](https://github.com/jshttp/on-headers/blob/master/HISTORY.md)
- [Commits](https://github.com/jshttp/on-headers/compare/v1.0.2...v1.1.0)

Updates `serve` from 14.2.4 to 14.2.5
- [Release notes](https://github.com/vercel/serve/releases)
- [Commits](https://github.com/vercel/serve/compare/14.2.4...v14.2.5)

Updates `compression` from 1.7.4 to 1.8.1
- [Release notes](https://github.com/expressjs/compression/releases)
- [Changelog](https://github.com/expressjs/compression/blob/master/HISTORY.md)
- [Commits](https://github.com/expressjs/compression/compare/1.7.4...v1.8.1)

---
updated-dependencies:
- dependency-name: on-headers
  dependency-version: 1.1.0
  dependency-type: indirect
- dependency-name: serve
  dependency-version: 14.2.5
  dependency-type: direct:development
- dependency-name: compression
  dependency-version: 1.8.1
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-09-21 19:34:03 +09:00
Shahar Naveh
3c01be29c4 Update some tests to 3.13.7 (#6171)
* Update `test_call.py` from 3.13.7

* Update `test_yield_from.py` from 3.13.7

* Update more tests to 3.13.7

* More tests to 3.13.7

* Remove patch from passing test
2025-09-21 19:33:41 +09:00
Shahar Naveh
24f4fbad82 Run scheduled CI jobs only on upstream repo (#6157)
* Run scheduled CI jobs only on upstream repo

* Only disable if scheduling on forks
2025-09-21 02:05:32 +09:00
Shahar Naveh
30cbc41298 Update github actions in CI (#6169)
* Update `setup-python` to v6

* Update `checkout` to v5
2025-09-21 01:53:00 +09:00
dependabot[bot]
150e8ef43d Bump actions/download-artifact from 4 to 5 (#6162)
Bumps [actions/download-artifact](https://github.com/actions/download-artifact) from 4 to 5.
- [Release notes](https://github.com/actions/download-artifact/releases)
- [Commits](https://github.com/actions/download-artifact/compare/v4...v5)

---
updated-dependencies:
- dependency-name: actions/download-artifact
  dependency-version: '5'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-09-21 00:01:06 +09:00
dependabot[bot]
4b91e985ac Bump ruff_python_ast from 0.11.0 to 0.13.1 (#6166)
Bumps [ruff_python_ast](https://github.com/astral-sh/ruff) from 0.11.0 to 0.13.1.
- [Release notes](https://github.com/astral-sh/ruff/releases)
- [Changelog](https://github.com/astral-sh/ruff/blob/main/CHANGELOG.md)
- [Commits](2cd25ef641...706be0a6e7)

---
updated-dependencies:
- dependency-name: ruff_python_ast
  dependency-version: 706be0a6e7e09936511198f2ff8982915520d138
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-09-20 23:42:02 +09:00
dependabot[bot]
fdae128cec Bump actions/setup-node from 4 to 5 (#6160)
Bumps [actions/setup-node](https://github.com/actions/setup-node) from 4 to 5.
- [Release notes](https://github.com/actions/setup-node/releases)
- [Commits](https://github.com/actions/setup-node/compare/v4...v5)

---
updated-dependencies:
- dependency-name: actions/setup-node
  dependency-version: '5'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-09-20 23:41:28 +09:00
Shahar Naveh
11e1330758 Reconfigure dependabot (#6158)
* Add `cargo` to dependabot

* Remove noisy comments

* Don't group updates
2025-09-20 22:46:02 +09:00
Jeong, YunWon
aa0eb4bedf rustpython-common wasm_js feature (#6116) 2025-09-17 09:11:13 +09:00
Shahar Naveh
141ed72693 Dependencies cleanup (#6151)
* Update deps

* Remove some unused deps

* Update lockfile
2025-09-17 09:10:35 +09:00
Shahar Naveh
62067aefd3 Update uuid from 3.13.7 (#6155) 2025-09-16 21:58:36 +09:00
Shahar Naveh
b7d9d7d9ae Update test/test_fstring.py from 3.13.7 (#6154)
* Update `test_fstring.py` from 3.13.7

* Patch failing tests
2025-09-16 21:57:36 +09:00
Shahar Naveh
67958ec791 Update {io,encodings} from 3.13.7 (#6153)
* Update `io` from 3.13.7

* Patch test & upsate `encodings` from 3.13.7

* Unmark passing tests
2025-09-16 21:53:25 +09:00
Jeong, YunWon
b666c52df9 code object linetable (#6150)
* Code.replace

* implement linetable
2025-09-16 21:49:54 +09:00
Shahar Naveh
6ead82154e Update glob from 3.13.7 (#6152) 2025-09-16 09:35:08 +09:00
Shahar Naveh
ca95366219 Update fnmatch from 3.13.7 (#6149) 2025-09-15 21:36:11 +09:00
59 changed files with 5242 additions and 2427 deletions

View File

@@ -3,3 +3,6 @@ rustflags = "-C link-arg=/STACK:8000000"
[target.'cfg(all(target_os = "windows", not(target_env = "msvc")))']
rustflags = "-C link-args=-Wl,--stack,8000000"
[target.wasm32-unknown-unknown]
rustflags = ["--cfg=getrandom_backend=\"wasm_js\""]

View File

@@ -1,13 +1,10 @@
# Keep GitHub Actions up to date with GitHub's Dependabot...
# https://docs.github.com/en/code-security/dependabot/working-with-dependabot/keeping-your-actions-up-to-date-with-dependabot
# https://docs.github.com/en/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file#package-ecosystem
version: 2
updates:
- package-ecosystem: github-actions
- package-ecosystem: cargo
directory: /
schedule:
interval: weekly
- package-ecosystem: github-actions
directory: /
groups:
github-actions:
patterns:
- "*" # Group all Actions updates into a single larger pull request
schedule:
interval: weekly

View File

@@ -122,7 +122,7 @@ jobs:
- windows-2025 # TODO: Switch to `windows-latest` on 2025/09/30
fail-fast: false
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v5
- uses: dtolnay/rust-toolchain@stable
with:
components: clippy
@@ -181,7 +181,7 @@ jobs:
runs-on: ubuntu-latest
timeout-minutes: 30
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v5
- uses: dtolnay/rust-toolchain@stable
with:
target: i686-unknown-linux-gnu
@@ -251,10 +251,10 @@ jobs:
- windows-2025 # TODO: Switch to `windows-latest` on 2025/09/30
fail-fast: false
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v5
- uses: dtolnay/rust-toolchain@stable
- uses: Swatinem/rust-cache@v2
- uses: actions/setup-python@v5
- uses: actions/setup-python@v6
with:
python-version: ${{ env.PYTHON_VERSION }}
- name: Set up the Windows environment
@@ -273,7 +273,7 @@ jobs:
- name: build rustpython
run: cargo build --release --verbose --features=threading ${{ env.CARGO_ARGS }},jit
if: runner.os != 'macOS'
- uses: actions/setup-python@v5
- uses: actions/setup-python@v6
with:
python-version: ${{ env.PYTHON_VERSION }}
- name: run snippets
@@ -316,7 +316,7 @@ jobs:
name: Check Rust code with rustfmt and clippy
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v5
- uses: dtolnay/rust-toolchain@stable
with:
components: rustfmt, clippy
@@ -324,7 +324,7 @@ jobs:
run: cargo fmt --check
- name: run clippy on wasm
run: cargo clippy --manifest-path=wasm/lib/Cargo.toml -- -Dwarnings
- uses: actions/setup-python@v5
- uses: actions/setup-python@v6
with:
python-version: ${{ env.PYTHON_VERSION }}
- name: install ruff
@@ -357,7 +357,7 @@ jobs:
env:
NIGHTLY_CHANNEL: nightly
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v5
- uses: dtolnay/rust-toolchain@master
with:
@@ -379,7 +379,7 @@ jobs:
runs-on: ubuntu-latest
timeout-minutes: 30
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v5
- uses: dtolnay/rust-toolchain@stable
- uses: Swatinem/rust-cache@v2
@@ -390,12 +390,12 @@ jobs:
wget https://github.com/mozilla/geckodriver/releases/download/v0.36.0/geckodriver-v0.36.0-linux64.tar.gz
mkdir geckodriver
tar -xzf geckodriver-v0.36.0-linux64.tar.gz -C geckodriver
- uses: actions/setup-python@v5
- uses: actions/setup-python@v6
with:
python-version: ${{ env.PYTHON_VERSION }}
- run: python -m pip install -r requirements.txt
working-directory: ./wasm/tests
- uses: actions/setup-node@v4
- uses: actions/setup-node@v5
with:
cache: "npm"
cache-dependency-path: "wasm/demo/package-lock.json"
@@ -440,7 +440,7 @@ jobs:
runs-on: ubuntu-latest
timeout-minutes: 30
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v5
- uses: dtolnay/rust-toolchain@stable
with:
target: wasm32-wasip1

View File

@@ -18,6 +18,8 @@ jobs:
codecov:
name: Collect code coverage data
runs-on: ubuntu-latest
# Disable this scheduled job when running on a fork.
if: ${{ github.repository == 'RustPython/RustPython' || github.event_name != 'schedule' }}
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
@@ -44,6 +46,8 @@ jobs:
testdata:
name: Collect regression test data
runs-on: ubuntu-latest
# Disable this scheduled job when running on a fork.
if: ${{ github.repository == 'RustPython/RustPython' || github.event_name != 'schedule' }}
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
@@ -73,6 +77,8 @@ jobs:
whatsleft:
name: Collect what is left data
runs-on: ubuntu-latest
# Disable this scheduled job when running on a fork.
if: ${{ github.repository == 'RustPython/RustPython' || github.event_name != 'schedule' }}
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
@@ -111,6 +117,8 @@ jobs:
benchmark:
name: Collect benchmark data
runs-on: ubuntu-latest
# Disable this scheduled job when running on a fork.
if: ${{ github.repository == 'RustPython/RustPython' || github.event_name != 'schedule' }}
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable

View File

@@ -21,6 +21,8 @@ env:
jobs:
build:
runs-on: ${{ matrix.platform.runner }}
# Disable this scheduled job when running on a fork.
if: ${{ github.repository == 'RustPython/RustPython' || github.event_name != 'schedule' }}
strategy:
matrix:
platform:
@@ -88,6 +90,8 @@ jobs:
build-wasm:
runs-on: ubuntu-latest
# Disable this scheduled job when running on a fork.
if: ${{ github.repository == 'RustPython/RustPython' || github.event_name != 'schedule' }}
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
@@ -108,7 +112,7 @@ jobs:
- name: install wasm-pack
run: curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh
- uses: actions/setup-node@v4
- uses: actions/setup-node@v5
- uses: mwilliamson/setup-wabt-action@v3
with: { wabt-version: "1.0.30" }
- name: build demo
@@ -136,10 +140,12 @@ jobs:
release:
runs-on: ubuntu-latest
# Disable this scheduled job when running on a fork.
if: ${{ github.repository == 'RustPython/RustPython' || github.event_name != 'schedule' }}
needs: [build, build-wasm]
steps:
- name: Download Binary Artifacts
uses: actions/download-artifact@v4
uses: actions/download-artifact@v5
with:
path: bin
pattern: rustpython-*

538
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -51,7 +51,7 @@ rustyline = { workspace = true }
[dev-dependencies]
criterion = { workspace = true }
pyo3 = { version = "0.24", features = ["auto-initialize"] }
pyo3 = { version = "0.26", features = ["auto-initialize"] }
[[bench]]
name = "execution"
@@ -163,27 +163,27 @@ ruff_python_ast = { git = "https://github.com/astral-sh/ruff.git", tag = "0.11.0
ruff_text_size = { git = "https://github.com/astral-sh/ruff.git", tag = "0.11.0" }
ruff_source_file = { git = "https://github.com/astral-sh/ruff.git", tag = "0.11.0" }
ahash = "0.8.11"
ahash = "0.8.12"
ascii = "1.1"
bitflags = "2.9.1"
bitflags = "2.9.4"
bstr = "1"
cfg-if = "1.0"
chrono = "0.4.39"
chrono = "0.4.42"
constant_time_eq = "0.4"
criterion = { version = "0.5", features = ["html_reports"] }
criterion = { version = "0.7", features = ["html_reports"] }
crossbeam-utils = "0.8.21"
flame = "0.2.2"
getrandom = { version = "0.3", features = ["std"] }
glob = "0.3"
hex = "0.4.3"
indexmap = { version = "2.10.0", features = ["std"] }
indexmap = { version = "2.11.3", features = ["std"] }
insta = "1.42"
itertools = "0.14.0"
is-macro = "0.3.7"
junction = "1.2.0"
junction = "1.3.0"
libc = "0.2.169"
libffi = "4.1"
log = "0.4.27"
log = "0.4.28"
nix = { version = "0.30", features = ["fs", "user", "process", "term", "time", "signal", "ioctl", "socket", "sched", "zerocopy", "dir", "hostname", "net", "poll"] }
malachite-bigint = "0.6"
malachite-q = "0.6"
@@ -204,9 +204,9 @@ radium = "1.1.1"
rand = "0.9"
rand_core = { version = "0.9", features = ["os_rng"] }
rustix = { version = "1.0", features = ["event"] }
rustyline = "17.0.0"
serde = { version = "1.0.133", default-features = false }
schannel = "0.1.27"
rustyline = "17.0.1"
serde = { version = "1.0.225", default-features = false }
schannel = "0.1.28"
scoped-tls = "1"
scopeguard = "1"
static_assertions = "1.1"
@@ -214,16 +214,16 @@ strum = "0.27"
strum_macros = "0.27"
syn = "2"
thiserror = "2.0"
thread_local = "1.1.8"
unicode-casing = "0.1.0"
thread_local = "1.1.9"
unicode-casing = "0.1.1"
unic-char-property = "0.9.0"
unic-normal = "0.9.0"
unic-ucd-age = "0.9.0"
unic-ucd-bidi = "0.9.0"
unic-ucd-category = "0.9.0"
unic-ucd-ident = "0.9.0"
unicode_names2 = "1.3.0"
unicode-bidi-mirroring = "0.2"
unicode_names2 = "2.0.0"
unicode-bidi-mirroring = "0.4"
widestring = "1.2.0"
windows-sys = "0.59.0"
wasm-bindgen = "0.2.100"

343
Lib/_opcode_metadata.py vendored Normal file
View File

@@ -0,0 +1,343 @@
# This file is generated by Tools/cases_generator/py_metadata_generator.py
# from:
# Python/bytecodes.c
# Do not edit!
_specializations = {
"RESUME": [
"RESUME_CHECK",
],
"TO_BOOL": [
"TO_BOOL_ALWAYS_TRUE",
"TO_BOOL_BOOL",
"TO_BOOL_INT",
"TO_BOOL_LIST",
"TO_BOOL_NONE",
"TO_BOOL_STR",
],
"BINARY_OP": [
"BINARY_OP_MULTIPLY_INT",
"BINARY_OP_ADD_INT",
"BINARY_OP_SUBTRACT_INT",
"BINARY_OP_MULTIPLY_FLOAT",
"BINARY_OP_ADD_FLOAT",
"BINARY_OP_SUBTRACT_FLOAT",
"BINARY_OP_ADD_UNICODE",
"BINARY_OP_INPLACE_ADD_UNICODE",
],
"BINARY_SUBSCR": [
"BINARY_SUBSCR_DICT",
"BINARY_SUBSCR_GETITEM",
"BINARY_SUBSCR_LIST_INT",
"BINARY_SUBSCR_STR_INT",
"BINARY_SUBSCR_TUPLE_INT",
],
"STORE_SUBSCR": [
"STORE_SUBSCR_DICT",
"STORE_SUBSCR_LIST_INT",
],
"SEND": [
"SEND_GEN",
],
"UNPACK_SEQUENCE": [
"UNPACK_SEQUENCE_TWO_TUPLE",
"UNPACK_SEQUENCE_TUPLE",
"UNPACK_SEQUENCE_LIST",
],
"STORE_ATTR": [
"STORE_ATTR_INSTANCE_VALUE",
"STORE_ATTR_SLOT",
"STORE_ATTR_WITH_HINT",
],
"LOAD_GLOBAL": [
"LOAD_GLOBAL_MODULE",
"LOAD_GLOBAL_BUILTIN",
],
"LOAD_SUPER_ATTR": [
"LOAD_SUPER_ATTR_ATTR",
"LOAD_SUPER_ATTR_METHOD",
],
"LOAD_ATTR": [
"LOAD_ATTR_INSTANCE_VALUE",
"LOAD_ATTR_MODULE",
"LOAD_ATTR_WITH_HINT",
"LOAD_ATTR_SLOT",
"LOAD_ATTR_CLASS",
"LOAD_ATTR_PROPERTY",
"LOAD_ATTR_GETATTRIBUTE_OVERRIDDEN",
"LOAD_ATTR_METHOD_WITH_VALUES",
"LOAD_ATTR_METHOD_NO_DICT",
"LOAD_ATTR_METHOD_LAZY_DICT",
"LOAD_ATTR_NONDESCRIPTOR_WITH_VALUES",
"LOAD_ATTR_NONDESCRIPTOR_NO_DICT",
],
"COMPARE_OP": [
"COMPARE_OP_FLOAT",
"COMPARE_OP_INT",
"COMPARE_OP_STR",
],
"CONTAINS_OP": [
"CONTAINS_OP_SET",
"CONTAINS_OP_DICT",
],
"FOR_ITER": [
"FOR_ITER_LIST",
"FOR_ITER_TUPLE",
"FOR_ITER_RANGE",
"FOR_ITER_GEN",
],
"CALL": [
"CALL_BOUND_METHOD_EXACT_ARGS",
"CALL_PY_EXACT_ARGS",
"CALL_TYPE_1",
"CALL_STR_1",
"CALL_TUPLE_1",
"CALL_BUILTIN_CLASS",
"CALL_BUILTIN_O",
"CALL_BUILTIN_FAST",
"CALL_BUILTIN_FAST_WITH_KEYWORDS",
"CALL_LEN",
"CALL_ISINSTANCE",
"CALL_LIST_APPEND",
"CALL_METHOD_DESCRIPTOR_O",
"CALL_METHOD_DESCRIPTOR_FAST_WITH_KEYWORDS",
"CALL_METHOD_DESCRIPTOR_NOARGS",
"CALL_METHOD_DESCRIPTOR_FAST",
"CALL_ALLOC_AND_ENTER_INIT",
"CALL_PY_GENERAL",
"CALL_BOUND_METHOD_GENERAL",
"CALL_NON_PY_GENERAL",
],
}
_specialized_opmap = {
'BINARY_OP_ADD_FLOAT': 150,
'BINARY_OP_ADD_INT': 151,
'BINARY_OP_ADD_UNICODE': 152,
'BINARY_OP_INPLACE_ADD_UNICODE': 3,
'BINARY_OP_MULTIPLY_FLOAT': 153,
'BINARY_OP_MULTIPLY_INT': 154,
'BINARY_OP_SUBTRACT_FLOAT': 155,
'BINARY_OP_SUBTRACT_INT': 156,
'BINARY_SUBSCR_DICT': 157,
'BINARY_SUBSCR_GETITEM': 158,
'BINARY_SUBSCR_LIST_INT': 159,
'BINARY_SUBSCR_STR_INT': 160,
'BINARY_SUBSCR_TUPLE_INT': 161,
'CALL_ALLOC_AND_ENTER_INIT': 162,
'CALL_BOUND_METHOD_EXACT_ARGS': 163,
'CALL_BOUND_METHOD_GENERAL': 164,
'CALL_BUILTIN_CLASS': 165,
'CALL_BUILTIN_FAST': 166,
'CALL_BUILTIN_FAST_WITH_KEYWORDS': 167,
'CALL_BUILTIN_O': 168,
'CALL_ISINSTANCE': 169,
'CALL_LEN': 170,
'CALL_LIST_APPEND': 171,
'CALL_METHOD_DESCRIPTOR_FAST': 172,
'CALL_METHOD_DESCRIPTOR_FAST_WITH_KEYWORDS': 173,
'CALL_METHOD_DESCRIPTOR_NOARGS': 174,
'CALL_METHOD_DESCRIPTOR_O': 175,
'CALL_NON_PY_GENERAL': 176,
'CALL_PY_EXACT_ARGS': 177,
'CALL_PY_GENERAL': 178,
'CALL_STR_1': 179,
'CALL_TUPLE_1': 180,
'CALL_TYPE_1': 181,
'COMPARE_OP_FLOAT': 182,
'COMPARE_OP_INT': 183,
'COMPARE_OP_STR': 184,
'CONTAINS_OP_DICT': 185,
'CONTAINS_OP_SET': 186,
'FOR_ITER_GEN': 187,
'FOR_ITER_LIST': 188,
'FOR_ITER_RANGE': 189,
'FOR_ITER_TUPLE': 190,
'LOAD_ATTR_CLASS': 191,
'LOAD_ATTR_GETATTRIBUTE_OVERRIDDEN': 192,
'LOAD_ATTR_INSTANCE_VALUE': 193,
'LOAD_ATTR_METHOD_LAZY_DICT': 194,
'LOAD_ATTR_METHOD_NO_DICT': 195,
'LOAD_ATTR_METHOD_WITH_VALUES': 196,
'LOAD_ATTR_MODULE': 197,
'LOAD_ATTR_NONDESCRIPTOR_NO_DICT': 198,
'LOAD_ATTR_NONDESCRIPTOR_WITH_VALUES': 199,
'LOAD_ATTR_PROPERTY': 200,
'LOAD_ATTR_SLOT': 201,
'LOAD_ATTR_WITH_HINT': 202,
'LOAD_GLOBAL_BUILTIN': 203,
'LOAD_GLOBAL_MODULE': 204,
'LOAD_SUPER_ATTR_ATTR': 205,
'LOAD_SUPER_ATTR_METHOD': 206,
'RESUME_CHECK': 207,
'SEND_GEN': 208,
'STORE_ATTR_INSTANCE_VALUE': 209,
'STORE_ATTR_SLOT': 210,
'STORE_ATTR_WITH_HINT': 211,
'STORE_SUBSCR_DICT': 212,
'STORE_SUBSCR_LIST_INT': 213,
'TO_BOOL_ALWAYS_TRUE': 214,
'TO_BOOL_BOOL': 215,
'TO_BOOL_INT': 216,
'TO_BOOL_LIST': 217,
'TO_BOOL_NONE': 218,
'TO_BOOL_STR': 219,
'UNPACK_SEQUENCE_LIST': 220,
'UNPACK_SEQUENCE_TUPLE': 221,
'UNPACK_SEQUENCE_TWO_TUPLE': 222,
}
opmap = {
'CACHE': 0,
'RESERVED': 17,
'RESUME': 149,
'INSTRUMENTED_LINE': 254,
'BEFORE_ASYNC_WITH': 1,
'BEFORE_WITH': 2,
'BINARY_SLICE': 4,
'BINARY_SUBSCR': 5,
'CHECK_EG_MATCH': 6,
'CHECK_EXC_MATCH': 7,
'CLEANUP_THROW': 8,
'DELETE_SUBSCR': 9,
'END_ASYNC_FOR': 10,
'END_FOR': 11,
'END_SEND': 12,
'EXIT_INIT_CHECK': 13,
'FORMAT_SIMPLE': 14,
'FORMAT_WITH_SPEC': 15,
'GET_AITER': 16,
'GET_ANEXT': 18,
'GET_ITER': 19,
'GET_LEN': 20,
'GET_YIELD_FROM_ITER': 21,
'INTERPRETER_EXIT': 22,
'LOAD_ASSERTION_ERROR': 23,
'LOAD_BUILD_CLASS': 24,
'LOAD_LOCALS': 25,
'MAKE_FUNCTION': 26,
'MATCH_KEYS': 27,
'MATCH_MAPPING': 28,
'MATCH_SEQUENCE': 29,
'NOP': 30,
'POP_EXCEPT': 31,
'POP_TOP': 32,
'PUSH_EXC_INFO': 33,
'PUSH_NULL': 34,
'RETURN_GENERATOR': 35,
'RETURN_VALUE': 36,
'SETUP_ANNOTATIONS': 37,
'STORE_SLICE': 38,
'STORE_SUBSCR': 39,
'TO_BOOL': 40,
'UNARY_INVERT': 41,
'UNARY_NEGATIVE': 42,
'UNARY_NOT': 43,
'WITH_EXCEPT_START': 44,
'BINARY_OP': 45,
'BUILD_CONST_KEY_MAP': 46,
'BUILD_LIST': 47,
'BUILD_MAP': 48,
'BUILD_SET': 49,
'BUILD_SLICE': 50,
'BUILD_STRING': 51,
'BUILD_TUPLE': 52,
'CALL': 53,
'CALL_FUNCTION_EX': 54,
'CALL_INTRINSIC_1': 55,
'CALL_INTRINSIC_2': 56,
'CALL_KW': 57,
'COMPARE_OP': 58,
'CONTAINS_OP': 59,
'CONVERT_VALUE': 60,
'COPY': 61,
'COPY_FREE_VARS': 62,
'DELETE_ATTR': 63,
'DELETE_DEREF': 64,
'DELETE_FAST': 65,
'DELETE_GLOBAL': 66,
'DELETE_NAME': 67,
'DICT_MERGE': 68,
'DICT_UPDATE': 69,
'ENTER_EXECUTOR': 70,
'EXTENDED_ARG': 71,
'FOR_ITER': 72,
'GET_AWAITABLE': 73,
'IMPORT_FROM': 74,
'IMPORT_NAME': 75,
'IS_OP': 76,
'JUMP_BACKWARD': 77,
'JUMP_BACKWARD_NO_INTERRUPT': 78,
'JUMP_FORWARD': 79,
'LIST_APPEND': 80,
'LIST_EXTEND': 81,
'LOAD_ATTR': 82,
'LOAD_CONST': 83,
'LOAD_DEREF': 84,
'LOAD_FAST': 85,
'LOAD_FAST_AND_CLEAR': 86,
'LOAD_FAST_CHECK': 87,
'LOAD_FAST_LOAD_FAST': 88,
'LOAD_FROM_DICT_OR_DEREF': 89,
'LOAD_FROM_DICT_OR_GLOBALS': 90,
'LOAD_GLOBAL': 91,
'LOAD_NAME': 92,
'LOAD_SUPER_ATTR': 93,
'MAKE_CELL': 94,
'MAP_ADD': 95,
'MATCH_CLASS': 96,
'POP_JUMP_IF_FALSE': 97,
'POP_JUMP_IF_NONE': 98,
'POP_JUMP_IF_NOT_NONE': 99,
'POP_JUMP_IF_TRUE': 100,
'RAISE_VARARGS': 101,
'RERAISE': 102,
'RETURN_CONST': 103,
'SEND': 104,
'SET_ADD': 105,
'SET_FUNCTION_ATTRIBUTE': 106,
'SET_UPDATE': 107,
'STORE_ATTR': 108,
'STORE_DEREF': 109,
'STORE_FAST': 110,
'STORE_FAST_LOAD_FAST': 111,
'STORE_FAST_STORE_FAST': 112,
'STORE_GLOBAL': 113,
'STORE_NAME': 114,
'SWAP': 115,
'UNPACK_EX': 116,
'UNPACK_SEQUENCE': 117,
'YIELD_VALUE': 118,
'INSTRUMENTED_RESUME': 236,
'INSTRUMENTED_END_FOR': 237,
'INSTRUMENTED_END_SEND': 238,
'INSTRUMENTED_RETURN_VALUE': 239,
'INSTRUMENTED_RETURN_CONST': 240,
'INSTRUMENTED_YIELD_VALUE': 241,
'INSTRUMENTED_LOAD_SUPER_ATTR': 242,
'INSTRUMENTED_FOR_ITER': 243,
'INSTRUMENTED_CALL': 244,
'INSTRUMENTED_CALL_KW': 245,
'INSTRUMENTED_CALL_FUNCTION_EX': 246,
'INSTRUMENTED_INSTRUCTION': 247,
'INSTRUMENTED_JUMP_FORWARD': 248,
'INSTRUMENTED_JUMP_BACKWARD': 249,
'INSTRUMENTED_POP_JUMP_IF_TRUE': 250,
'INSTRUMENTED_POP_JUMP_IF_FALSE': 251,
'INSTRUMENTED_POP_JUMP_IF_NONE': 252,
'INSTRUMENTED_POP_JUMP_IF_NOT_NONE': 253,
'JUMP': 256,
'JUMP_NO_INTERRUPT': 257,
'LOAD_CLOSURE': 258,
'LOAD_METHOD': 259,
'LOAD_SUPER_METHOD': 260,
'LOAD_ZERO_SUPER_ATTR': 261,
'LOAD_ZERO_SUPER_METHOD': 262,
'POP_BLOCK': 263,
'SETUP_CLEANUP': 264,
'SETUP_FINALLY': 265,
'SETUP_WITH': 266,
'STORE_FAST_MAYBE_NULL': 267,
}
HAVE_ARGUMENT = 44
MIN_INSTRUMENTED_OPCODE = 236

View File

@@ -209,6 +209,7 @@ aliases = {
'ms932' : 'cp932',
'mskanji' : 'cp932',
'ms_kanji' : 'cp932',
'windows_31j' : 'cp932',
# cp949 codec
'949' : 'cp949',

166
Lib/encodings/idna.py vendored
View File

@@ -11,7 +11,7 @@ ace_prefix = b"xn--"
sace_prefix = "xn--"
# This assumes query strings, so AllowUnassigned is true
def nameprep(label):
def nameprep(label): # type: (str) -> str
# Map
newlabel = []
for c in label:
@@ -25,7 +25,7 @@ def nameprep(label):
label = unicodedata.normalize("NFKC", label)
# Prohibit
for c in label:
for i, c in enumerate(label):
if stringprep.in_table_c12(c) or \
stringprep.in_table_c22(c) or \
stringprep.in_table_c3(c) or \
@@ -35,7 +35,7 @@ def nameprep(label):
stringprep.in_table_c7(c) or \
stringprep.in_table_c8(c) or \
stringprep.in_table_c9(c):
raise UnicodeError("Invalid character %r" % c)
raise UnicodeEncodeError("idna", label, i, i+1, f"Invalid character {c!r}")
# Check bidi
RandAL = [stringprep.in_table_d1(x) for x in label]
@@ -46,29 +46,38 @@ def nameprep(label):
# This is table C.8, which was already checked
# 2) If a string contains any RandALCat character, the string
# MUST NOT contain any LCat character.
if any(stringprep.in_table_d2(x) for x in label):
raise UnicodeError("Violation of BIDI requirement 2")
for i, x in enumerate(label):
if stringprep.in_table_d2(x):
raise UnicodeEncodeError("idna", label, i, i+1,
"Violation of BIDI requirement 2")
# 3) If a string contains any RandALCat character, a
# RandALCat character MUST be the first character of the
# string, and a RandALCat character MUST be the last
# character of the string.
if not RandAL[0] or not RandAL[-1]:
raise UnicodeError("Violation of BIDI requirement 3")
if not RandAL[0]:
raise UnicodeEncodeError("idna", label, 0, 1,
"Violation of BIDI requirement 3")
if not RandAL[-1]:
raise UnicodeEncodeError("idna", label, len(label)-1, len(label),
"Violation of BIDI requirement 3")
return label
def ToASCII(label):
def ToASCII(label): # type: (str) -> bytes
try:
# Step 1: try ASCII
label = label.encode("ascii")
except UnicodeError:
label_ascii = label.encode("ascii")
except UnicodeEncodeError:
pass
else:
# Skip to step 3: UseSTD3ASCIIRules is false, so
# Skip to step 8.
if 0 < len(label) < 64:
return label
raise UnicodeError("label empty or too long")
if 0 < len(label_ascii) < 64:
return label_ascii
if len(label) == 0:
raise UnicodeEncodeError("idna", label, 0, 1, "label empty")
else:
raise UnicodeEncodeError("idna", label, 0, len(label), "label too long")
# Step 2: nameprep
label = nameprep(label)
@@ -76,29 +85,34 @@ def ToASCII(label):
# Step 3: UseSTD3ASCIIRules is false
# Step 4: try ASCII
try:
label = label.encode("ascii")
except UnicodeError:
label_ascii = label.encode("ascii")
except UnicodeEncodeError:
pass
else:
# Skip to step 8.
if 0 < len(label) < 64:
return label
raise UnicodeError("label empty or too long")
return label_ascii
if len(label) == 0:
raise UnicodeEncodeError("idna", label, 0, 1, "label empty")
else:
raise UnicodeEncodeError("idna", label, 0, len(label), "label too long")
# Step 5: Check ACE prefix
if label.startswith(sace_prefix):
raise UnicodeError("Label starts with ACE prefix")
if label.lower().startswith(sace_prefix):
raise UnicodeEncodeError(
"idna", label, 0, len(sace_prefix), "Label starts with ACE prefix")
# Step 6: Encode with PUNYCODE
label = label.encode("punycode")
label_ascii = label.encode("punycode")
# Step 7: Prepend ACE prefix
label = ace_prefix + label
label_ascii = ace_prefix + label_ascii
# Step 8: Check size
if 0 < len(label) < 64:
return label
raise UnicodeError("label empty or too long")
# do not check for empty as we prepend ace_prefix.
if len(label_ascii) < 64:
return label_ascii
raise UnicodeEncodeError("idna", label, 0, len(label), "label too long")
def ToUnicode(label):
if len(label) > 1024:
@@ -110,7 +124,9 @@ def ToUnicode(label):
# per https://www.rfc-editor.org/rfc/rfc3454#section-3.1 while still
# preventing us from wasting time decoding a big thing that'll just
# hit the actual <= 63 length limit in Step 6.
raise UnicodeError("label way too long")
if isinstance(label, str):
label = label.encode("utf-8", errors="backslashreplace")
raise UnicodeDecodeError("idna", label, 0, len(label), "label way too long")
# Step 1: Check for ASCII
if isinstance(label, bytes):
pure_ascii = True
@@ -118,25 +134,32 @@ def ToUnicode(label):
try:
label = label.encode("ascii")
pure_ascii = True
except UnicodeError:
except UnicodeEncodeError:
pure_ascii = False
if not pure_ascii:
assert isinstance(label, str)
# Step 2: Perform nameprep
label = nameprep(label)
# It doesn't say this, but apparently, it should be ASCII now
try:
label = label.encode("ascii")
except UnicodeError:
raise UnicodeError("Invalid character in IDN label")
except UnicodeEncodeError as exc:
raise UnicodeEncodeError("idna", label, exc.start, exc.end,
"Invalid character in IDN label")
# Step 3: Check for ACE prefix
if not label.startswith(ace_prefix):
assert isinstance(label, bytes)
if not label.lower().startswith(ace_prefix):
return str(label, "ascii")
# Step 4: Remove ACE prefix
label1 = label[len(ace_prefix):]
# Step 5: Decode using PUNYCODE
result = label1.decode("punycode")
try:
result = label1.decode("punycode")
except UnicodeDecodeError as exc:
offset = len(ace_prefix)
raise UnicodeDecodeError("idna", label, offset+exc.start, offset+exc.end, exc.reason)
# Step 6: Apply ToASCII
label2 = ToASCII(result)
@@ -144,7 +167,8 @@ def ToUnicode(label):
# Step 7: Compare the result of step 6 with the one of step 3
# label2 will already be in lower case.
if str(label, "ascii").lower() != str(label2, "ascii"):
raise UnicodeError("IDNA does not round-trip", label, label2)
raise UnicodeDecodeError("idna", label, 0, len(label),
f"IDNA does not round-trip, '{label!r}' != '{label2!r}'")
# Step 8: return the result of step 5
return result
@@ -156,7 +180,7 @@ class Codec(codecs.Codec):
if errors != 'strict':
# IDNA is quite clear that implementations must be strict
raise UnicodeError("unsupported error handling "+errors)
raise UnicodeError(f"Unsupported error handling: {errors}")
if not input:
return b'', 0
@@ -168,11 +192,16 @@ class Codec(codecs.Codec):
else:
# ASCII name: fast path
labels = result.split(b'.')
for label in labels[:-1]:
if not (0 < len(label) < 64):
raise UnicodeError("label empty or too long")
if len(labels[-1]) >= 64:
raise UnicodeError("label too long")
for i, label in enumerate(labels[:-1]):
if len(label) == 0:
offset = sum(len(l) for l in labels[:i]) + i
raise UnicodeEncodeError("idna", input, offset, offset+1,
"label empty")
for i, label in enumerate(labels):
if len(label) >= 64:
offset = sum(len(l) for l in labels[:i]) + i
raise UnicodeEncodeError("idna", input, offset, offset+len(label),
"label too long")
return result, len(input)
result = bytearray()
@@ -182,17 +211,27 @@ class Codec(codecs.Codec):
del labels[-1]
else:
trailing_dot = b''
for label in labels:
for i, label in enumerate(labels):
if result:
# Join with U+002E
result.extend(b'.')
result.extend(ToASCII(label))
try:
result.extend(ToASCII(label))
except (UnicodeEncodeError, UnicodeDecodeError) as exc:
offset = sum(len(l) for l in labels[:i]) + i
raise UnicodeEncodeError(
"idna",
input,
offset + exc.start,
offset + exc.end,
exc.reason,
)
return bytes(result+trailing_dot), len(input)
def decode(self, input, errors='strict'):
if errors != 'strict':
raise UnicodeError("Unsupported error handling "+errors)
raise UnicodeError(f"Unsupported error handling: {errors}")
if not input:
return "", 0
@@ -202,7 +241,7 @@ class Codec(codecs.Codec):
# XXX obviously wrong, see #3232
input = bytes(input)
if ace_prefix not in input:
if ace_prefix not in input.lower():
# Fast path
try:
return input.decode('ascii'), len(input)
@@ -218,8 +257,15 @@ class Codec(codecs.Codec):
trailing_dot = ''
result = []
for label in labels:
result.append(ToUnicode(label))
for i, label in enumerate(labels):
try:
u_label = ToUnicode(label)
except (UnicodeEncodeError, UnicodeDecodeError) as exc:
offset = sum(len(x) for x in labels[:i]) + len(labels[:i])
raise UnicodeDecodeError(
"idna", input, offset+exc.start, offset+exc.end, exc.reason)
else:
result.append(u_label)
return ".".join(result)+trailing_dot, len(input)
@@ -227,7 +273,7 @@ class IncrementalEncoder(codecs.BufferedIncrementalEncoder):
def _buffer_encode(self, input, errors, final):
if errors != 'strict':
# IDNA is quite clear that implementations must be strict
raise UnicodeError("unsupported error handling "+errors)
raise UnicodeError(f"Unsupported error handling: {errors}")
if not input:
return (b'', 0)
@@ -251,7 +297,16 @@ class IncrementalEncoder(codecs.BufferedIncrementalEncoder):
# Join with U+002E
result.extend(b'.')
size += 1
result.extend(ToASCII(label))
try:
result.extend(ToASCII(label))
except (UnicodeEncodeError, UnicodeDecodeError) as exc:
raise UnicodeEncodeError(
"idna",
input,
size + exc.start,
size + exc.end,
exc.reason,
)
size += len(label)
result += trailing_dot
@@ -261,7 +316,7 @@ class IncrementalEncoder(codecs.BufferedIncrementalEncoder):
class IncrementalDecoder(codecs.BufferedIncrementalDecoder):
def _buffer_decode(self, input, errors, final):
if errors != 'strict':
raise UnicodeError("Unsupported error handling "+errors)
raise UnicodeError(f"Unsupported error handling: {errors}")
if not input:
return ("", 0)
@@ -271,7 +326,11 @@ class IncrementalDecoder(codecs.BufferedIncrementalDecoder):
labels = dots.split(input)
else:
# Must be ASCII string
input = str(input, "ascii")
try:
input = str(input, "ascii")
except (UnicodeEncodeError, UnicodeDecodeError) as exc:
raise UnicodeDecodeError("idna", input,
exc.start, exc.end, exc.reason)
labels = input.split(".")
trailing_dot = ''
@@ -288,7 +347,18 @@ class IncrementalDecoder(codecs.BufferedIncrementalDecoder):
result = []
size = 0
for label in labels:
result.append(ToUnicode(label))
try:
u_label = ToUnicode(label)
except (UnicodeEncodeError, UnicodeDecodeError) as exc:
raise UnicodeDecodeError(
"idna",
input.encode("ascii", errors="backslashreplace"),
size + exc.start,
size + exc.end,
exc.reason,
)
else:
result.append(u_label)
if size:
size += 1
size += len(label)

View File

@@ -201,7 +201,7 @@ decoding_table = (
'\u02dc' # 0x98 -> SMALL TILDE
'\u2122' # 0x99 -> TRADE MARK SIGN
'\u0161' # 0x9A -> LATIN SMALL LETTER S WITH CARON
'\x9b' # 0x9B -> <control>
'\u203a' # 0x9B -> SINGLE RIGHT-POINTING ANGLE QUOTATION MARK
'\u0153' # 0x9C -> LATIN SMALL LIGATURE OE
'\x9d' # 0x9D -> <control>
'\x9e' # 0x9E -> <control>

View File

@@ -1,4 +1,4 @@
""" Codec for the Punicode encoding, as specified in RFC 3492
""" Codec for the Punycode encoding, as specified in RFC 3492
Written by Martin v. Löwis.
"""
@@ -131,10 +131,11 @@ def decode_generalized_number(extended, extpos, bias, errors):
j = 0
while 1:
try:
char = ord(extended[extpos])
char = extended[extpos]
except IndexError:
if errors == "strict":
raise UnicodeError("incomplete punicode string")
raise UnicodeDecodeError("punycode", extended, extpos, extpos+1,
"incomplete punycode string")
return extpos + 1, None
extpos += 1
if 0x41 <= char <= 0x5A: # A-Z
@@ -142,8 +143,8 @@ def decode_generalized_number(extended, extpos, bias, errors):
elif 0x30 <= char <= 0x39:
digit = char - 22 # 0x30-26
elif errors == "strict":
raise UnicodeError("Invalid extended code point '%s'"
% extended[extpos-1])
raise UnicodeDecodeError("punycode", extended, extpos-1, extpos,
f"Invalid extended code point '{extended[extpos-1]}'")
else:
return extpos, None
t = T(j, bias)
@@ -155,11 +156,14 @@ def decode_generalized_number(extended, extpos, bias, errors):
def insertion_sort(base, extended, errors):
"""3.2 Insertion unsort coding"""
"""3.2 Insertion sort coding"""
# This function raises UnicodeDecodeError with position in the extended.
# Caller should add the offset.
char = 0x80
pos = -1
bias = 72
extpos = 0
while extpos < len(extended):
newpos, delta = decode_generalized_number(extended, extpos,
bias, errors)
@@ -171,7 +175,9 @@ def insertion_sort(base, extended, errors):
char += pos // (len(base) + 1)
if char > 0x10FFFF:
if errors == "strict":
raise UnicodeError("Invalid character U+%x" % char)
raise UnicodeDecodeError(
"punycode", extended, pos-1, pos,
f"Invalid character U+{char:x}")
char = ord('?')
pos = pos % (len(base) + 1)
base = base[:pos] + chr(char) + base[pos:]
@@ -187,11 +193,21 @@ def punycode_decode(text, errors):
pos = text.rfind(b"-")
if pos == -1:
base = ""
extended = str(text, "ascii").upper()
extended = text.upper()
else:
base = str(text[:pos], "ascii", errors)
extended = str(text[pos+1:], "ascii").upper()
return insertion_sort(base, extended, errors)
try:
base = str(text[:pos], "ascii", errors)
except UnicodeDecodeError as exc:
raise UnicodeDecodeError("ascii", text, exc.start, exc.end,
exc.reason) from None
extended = text[pos+1:].upper()
try:
return insertion_sort(base, extended, errors)
except UnicodeDecodeError as exc:
offset = pos + 1
raise UnicodeDecodeError("punycode", text,
offset+exc.start, offset+exc.end,
exc.reason) from None
### Codec APIs
@@ -203,7 +219,7 @@ class Codec(codecs.Codec):
def decode(self, input, errors='strict'):
if errors not in ('strict', 'replace', 'ignore'):
raise UnicodeError("Unsupported error handling "+errors)
raise UnicodeError(f"Unsupported error handling: {errors}")
res = punycode_decode(input, errors)
return res, len(input)
@@ -214,7 +230,7 @@ class IncrementalEncoder(codecs.IncrementalEncoder):
class IncrementalDecoder(codecs.IncrementalDecoder):
def decode(self, input, final=False):
if self.errors not in ('strict', 'replace', 'ignore'):
raise UnicodeError("Unsupported error handling "+self.errors)
raise UnicodeError(f"Unsupported error handling: {self.errors}")
return punycode_decode(input, self.errors)
class StreamWriter(Codec,codecs.StreamWriter):

View File

@@ -1,6 +1,6 @@
""" Python 'undefined' Codec
This codec will always raise a ValueError exception when being
This codec will always raise a UnicodeError exception when being
used. It is intended for use by the site.py file to switch off
automatic string to Unicode coercion.

View File

@@ -64,7 +64,7 @@ class IncrementalDecoder(codecs.BufferedIncrementalDecoder):
elif byteorder == 1:
self.decoder = codecs.utf_16_be_decode
elif consumed >= 2:
raise UnicodeError("UTF-16 stream does not start with BOM")
raise UnicodeDecodeError("utf-16", input, 0, 2, "Stream does not start with BOM")
return (output, consumed)
return self.decoder(input, self.errors, final)
@@ -138,7 +138,7 @@ class StreamReader(codecs.StreamReader):
elif byteorder == 1:
self.decode = codecs.utf_16_be_decode
elif consumed>=2:
raise UnicodeError("UTF-16 stream does not start with BOM")
raise UnicodeDecodeError("utf-16", input, 0, 2, "Stream does not start with BOM")
return (object, consumed)
### encodings module API

View File

@@ -59,7 +59,7 @@ class IncrementalDecoder(codecs.BufferedIncrementalDecoder):
elif byteorder == 1:
self.decoder = codecs.utf_32_be_decode
elif consumed >= 4:
raise UnicodeError("UTF-32 stream does not start with BOM")
raise UnicodeDecodeError("utf-32", input, 0, 4, "Stream does not start with BOM")
return (output, consumed)
return self.decoder(input, self.errors, final)
@@ -132,8 +132,8 @@ class StreamReader(codecs.StreamReader):
self.decode = codecs.utf_32_le_decode
elif byteorder == 1:
self.decode = codecs.utf_32_be_decode
elif consumed>=4:
raise UnicodeError("UTF-32 stream does not start with BOM")
elif consumed >= 4:
raise UnicodeDecodeError("utf-32", input, 0, 4, "Stream does not start with BOM")
return (object, consumed)
### encodings module API

37
Lib/fnmatch.py vendored
View File

@@ -16,12 +16,6 @@ import functools
__all__ = ["filter", "fnmatch", "fnmatchcase", "translate"]
# Build a thread-safe incrementing counter to help create unique regexp group
# names across calls.
from itertools import count
_nextgroupnum = count().__next__
del count
def fnmatch(name, pat):
"""Test whether FILENAME matches PATTERN.
@@ -41,7 +35,7 @@ def fnmatch(name, pat):
pat = os.path.normcase(pat)
return fnmatchcase(name, pat)
@functools.lru_cache(maxsize=256, typed=True)
@functools.lru_cache(maxsize=32768, typed=True)
def _compile_pattern(pat):
if isinstance(pat, bytes):
pat_str = str(pat, 'ISO-8859-1')
@@ -84,6 +78,11 @@ def translate(pat):
"""
STAR = object()
parts = _translate(pat, STAR, '.')
return _join_translated_parts(parts, STAR)
def _translate(pat, STAR, QUESTION_MARK):
res = []
add = res.append
i, n = 0, len(pat)
@@ -95,7 +94,7 @@ def translate(pat):
if (not res) or res[-1] is not STAR:
add(STAR)
elif c == '?':
add('.')
add(QUESTION_MARK)
elif c == '[':
j = i
if j < n and pat[j] == '!':
@@ -152,9 +151,11 @@ def translate(pat):
else:
add(re.escape(c))
assert i == n
return res
def _join_translated_parts(inp, STAR):
# Deal with STARs.
inp = res
res = []
add = res.append
i, n = 0, len(inp)
@@ -165,17 +166,10 @@ def translate(pat):
# Now deal with STAR fixed STAR fixed ...
# For an interior `STAR fixed` pairing, we want to do a minimal
# .*? match followed by `fixed`, with no possibility of backtracking.
# We can't spell that directly, but can trick it into working by matching
# .*?fixed
# in a lookahead assertion, save the matched part in a group, then
# consume that group via a backreference. If the overall match fails,
# the lookahead assertion won't try alternatives. So the translation is:
# (?=(?P<name>.*?fixed))(?P=name)
# Group names are created as needed: g0, g1, g2, ...
# The numbers are obtained from _nextgroupnum() to ensure they're unique
# across calls and across threads. This is because people rely on the
# undocumented ability to join multiple translate() results together via
# "|" to build large regexps matching "one of many" shell patterns.
# Atomic groups ("(?>...)") allow us to spell that directly.
# Note: people rely on the undocumented ability to join multiple
# translate() results together via "|" to build large regexps matching
# "one of many" shell patterns.
while i < n:
assert inp[i] is STAR
i += 1
@@ -192,8 +186,7 @@ def translate(pat):
add(".*")
add(fixed)
else:
groupnum = _nextgroupnum()
add(f"(?=(?P<g{groupnum}>.*?{fixed}))(?P=g{groupnum})")
add(f"(?>.*?{fixed})")
assert i == n
res = "".join(res)
return fr'(?s:{res})\Z'

301
Lib/glob.py vendored
View File

@@ -4,11 +4,14 @@ import contextlib
import os
import re
import fnmatch
import functools
import itertools
import operator
import stat
import sys
__all__ = ["glob", "iglob", "escape"]
__all__ = ["glob", "iglob", "escape", "translate"]
def glob(pathname, *, root_dir=None, dir_fd=None, recursive=False,
include_hidden=False):
@@ -104,8 +107,8 @@ def _iglob(pathname, root_dir, dir_fd, recursive, dironly,
def _glob1(dirname, pattern, dir_fd, dironly, include_hidden=False):
names = _listdir(dirname, dir_fd, dironly)
if include_hidden or not _ishidden(pattern):
names = (x for x in names if include_hidden or not _ishidden(x))
if not (include_hidden or _ishidden(pattern)):
names = (x for x in names if not _ishidden(x))
return fnmatch.filter(names, pattern)
def _glob0(dirname, basename, dir_fd, dironly, include_hidden=False):
@@ -119,12 +122,19 @@ def _glob0(dirname, basename, dir_fd, dironly, include_hidden=False):
return [basename]
return []
# Following functions are not public but can be used by third-party code.
_deprecated_function_message = (
"{name} is deprecated and will be removed in Python {remove}. Use "
"glob.glob and pass a directory to its root_dir argument instead."
)
def glob0(dirname, pattern):
import warnings
warnings._deprecated("glob.glob0", _deprecated_function_message, remove=(3, 15))
return _glob0(dirname, pattern, None, False)
def glob1(dirname, pattern):
import warnings
warnings._deprecated("glob.glob1", _deprecated_function_message, remove=(3, 15))
return _glob1(dirname, pattern, None, False)
# This helper function recursively yields relative pathnames inside a literal
@@ -249,4 +259,287 @@ def escape(pathname):
return drive + pathname
_special_parts = ('', '.', '..')
_dir_open_flags = os.O_RDONLY | getattr(os, 'O_DIRECTORY', 0)
_no_recurse_symlinks = object()
def translate(pat, *, recursive=False, include_hidden=False, seps=None):
"""Translate a pathname with shell wildcards to a regular expression.
If `recursive` is true, the pattern segment '**' will match any number of
path segments.
If `include_hidden` is true, wildcards can match path segments beginning
with a dot ('.').
If a sequence of separator characters is given to `seps`, they will be
used to split the pattern into segments and match path separators. If not
given, os.path.sep and os.path.altsep (where available) are used.
"""
if not seps:
if os.path.altsep:
seps = (os.path.sep, os.path.altsep)
else:
seps = os.path.sep
escaped_seps = ''.join(map(re.escape, seps))
any_sep = f'[{escaped_seps}]' if len(seps) > 1 else escaped_seps
not_sep = f'[^{escaped_seps}]'
if include_hidden:
one_last_segment = f'{not_sep}+'
one_segment = f'{one_last_segment}{any_sep}'
any_segments = f'(?:.+{any_sep})?'
any_last_segments = '.*'
else:
one_last_segment = f'[^{escaped_seps}.]{not_sep}*'
one_segment = f'{one_last_segment}{any_sep}'
any_segments = f'(?:{one_segment})*'
any_last_segments = f'{any_segments}(?:{one_last_segment})?'
results = []
parts = re.split(any_sep, pat)
last_part_idx = len(parts) - 1
for idx, part in enumerate(parts):
if part == '*':
results.append(one_segment if idx < last_part_idx else one_last_segment)
elif recursive and part == '**':
if idx < last_part_idx:
if parts[idx + 1] != '**':
results.append(any_segments)
else:
results.append(any_last_segments)
else:
if part:
if not include_hidden and part[0] in '*?':
results.append(r'(?!\.)')
results.extend(fnmatch._translate(part, f'{not_sep}*', not_sep))
if idx < last_part_idx:
results.append(any_sep)
res = ''.join(results)
return fr'(?s:{res})\Z'
@functools.lru_cache(maxsize=512)
def _compile_pattern(pat, sep, case_sensitive, recursive=True):
"""Compile given glob pattern to a re.Pattern object (observing case
sensitivity)."""
flags = re.NOFLAG if case_sensitive else re.IGNORECASE
regex = translate(pat, recursive=recursive, include_hidden=True, seps=sep)
return re.compile(regex, flags=flags).match
class _Globber:
"""Class providing shell-style pattern matching and globbing.
"""
def __init__(self, sep, case_sensitive, case_pedantic=False, recursive=False):
self.sep = sep
self.case_sensitive = case_sensitive
self.case_pedantic = case_pedantic
self.recursive = recursive
# Low-level methods
lstat = operator.methodcaller('lstat')
add_slash = operator.methodcaller('joinpath', '')
@staticmethod
def scandir(path):
"""Emulates os.scandir(), which returns an object that can be used as
a context manager. This method is called by walk() and glob().
"""
return contextlib.nullcontext(path.iterdir())
@staticmethod
def concat_path(path, text):
"""Appends text to the given path.
"""
return path.with_segments(path._raw_path + text)
@staticmethod
def parse_entry(entry):
"""Returns the path of an entry yielded from scandir().
"""
return entry
# High-level methods
def compile(self, pat):
return _compile_pattern(pat, self.sep, self.case_sensitive, self.recursive)
def selector(self, parts):
"""Returns a function that selects from a given path, walking and
filtering according to the glob-style pattern parts in *parts*.
"""
if not parts:
return self.select_exists
part = parts.pop()
if self.recursive and part == '**':
selector = self.recursive_selector
elif part in _special_parts:
selector = self.special_selector
elif not self.case_pedantic and magic_check.search(part) is None:
selector = self.literal_selector
else:
selector = self.wildcard_selector
return selector(part, parts)
def special_selector(self, part, parts):
"""Returns a function that selects special children of the given path.
"""
select_next = self.selector(parts)
def select_special(path, exists=False):
path = self.concat_path(self.add_slash(path), part)
return select_next(path, exists)
return select_special
def literal_selector(self, part, parts):
"""Returns a function that selects a literal descendant of a path.
"""
# Optimization: consume and join any subsequent literal parts here,
# rather than leaving them for the next selector. This reduces the
# number of string concatenation operations and calls to add_slash().
while parts and magic_check.search(parts[-1]) is None:
part += self.sep + parts.pop()
select_next = self.selector(parts)
def select_literal(path, exists=False):
path = self.concat_path(self.add_slash(path), part)
return select_next(path, exists=False)
return select_literal
def wildcard_selector(self, part, parts):
"""Returns a function that selects direct children of a given path,
filtering by pattern.
"""
match = None if part == '*' else self.compile(part)
dir_only = bool(parts)
if dir_only:
select_next = self.selector(parts)
def select_wildcard(path, exists=False):
try:
# We must close the scandir() object before proceeding to
# avoid exhausting file descriptors when globbing deep trees.
with self.scandir(path) as scandir_it:
entries = list(scandir_it)
except OSError:
pass
else:
for entry in entries:
if match is None or match(entry.name):
if dir_only:
try:
if not entry.is_dir():
continue
except OSError:
continue
entry_path = self.parse_entry(entry)
if dir_only:
yield from select_next(entry_path, exists=True)
else:
yield entry_path
return select_wildcard
def recursive_selector(self, part, parts):
"""Returns a function that selects a given path and all its children,
recursively, filtering by pattern.
"""
# Optimization: consume following '**' parts, which have no effect.
while parts and parts[-1] == '**':
parts.pop()
# Optimization: consume and join any following non-special parts here,
# rather than leaving them for the next selector. They're used to
# build a regular expression, which we use to filter the results of
# the recursive walk. As a result, non-special pattern segments
# following a '**' wildcard don't require additional filesystem access
# to expand.
follow_symlinks = self.recursive is not _no_recurse_symlinks
if follow_symlinks:
while parts and parts[-1] not in _special_parts:
part += self.sep + parts.pop()
match = None if part == '**' else self.compile(part)
dir_only = bool(parts)
select_next = self.selector(parts)
def select_recursive(path, exists=False):
path = self.add_slash(path)
match_pos = len(str(path))
if match is None or match(str(path), match_pos):
yield from select_next(path, exists)
stack = [path]
while stack:
yield from select_recursive_step(stack, match_pos)
def select_recursive_step(stack, match_pos):
path = stack.pop()
try:
# We must close the scandir() object before proceeding to
# avoid exhausting file descriptors when globbing deep trees.
with self.scandir(path) as scandir_it:
entries = list(scandir_it)
except OSError:
pass
else:
for entry in entries:
is_dir = False
try:
if entry.is_dir(follow_symlinks=follow_symlinks):
is_dir = True
except OSError:
pass
if is_dir or not dir_only:
entry_path = self.parse_entry(entry)
if match is None or match(str(entry_path), match_pos):
if dir_only:
yield from select_next(entry_path, exists=True)
else:
# Optimization: directly yield the path if this is
# last pattern part.
yield entry_path
if is_dir:
stack.append(entry_path)
return select_recursive
def select_exists(self, path, exists=False):
"""Yields the given path, if it exists.
"""
if exists:
# Optimization: this path is already known to exist, e.g. because
# it was returned from os.scandir(), so we skip calling lstat().
yield path
else:
try:
self.lstat(path)
yield path
except OSError:
pass
class _StringGlobber(_Globber):
lstat = staticmethod(os.lstat)
scandir = staticmethod(os.scandir)
parse_entry = operator.attrgetter('path')
concat_path = operator.add
if os.name == 'nt':
@staticmethod
def add_slash(pathname):
tail = os.path.splitroot(pathname)[2]
if not tail or tail[-1] in '\\/':
return pathname
return f'{pathname}\\'
else:
@staticmethod
def add_slash(pathname):
if not pathname or pathname[-1] == '/':
return pathname
return f'{pathname}/'

15
Lib/io.py vendored
View File

@@ -46,23 +46,17 @@ __all__ = ["BlockingIOError", "open", "open_code", "IOBase", "RawIOBase",
"BufferedReader", "BufferedWriter", "BufferedRWPair",
"BufferedRandom", "TextIOBase", "TextIOWrapper",
"UnsupportedOperation", "SEEK_SET", "SEEK_CUR", "SEEK_END",
"DEFAULT_BUFFER_SIZE", "text_encoding",
"IncrementalNewlineDecoder"
]
"DEFAULT_BUFFER_SIZE", "text_encoding", "IncrementalNewlineDecoder"]
import _io
import abc
from _io import (DEFAULT_BUFFER_SIZE, BlockingIOError, UnsupportedOperation,
open, open_code, BytesIO, StringIO, BufferedReader,
open, open_code, FileIO, BytesIO, StringIO, BufferedReader,
BufferedWriter, BufferedRWPair, BufferedRandom,
IncrementalNewlineDecoder, text_encoding, TextIOWrapper)
try:
from _io import FileIO
except ImportError:
pass
# Pretend this exception was created here.
UnsupportedOperation.__module__ = "io"
@@ -87,10 +81,7 @@ class BufferedIOBase(_io._BufferedIOBase, IOBase):
class TextIOBase(_io._TextIOBase, IOBase):
__doc__ = _io._TextIOBase.__doc__
try:
RawIOBase.register(FileIO)
except NameError:
pass
RawIOBase.register(FileIO)
for klass in (BytesIO, BufferedReader, BufferedWriter, BufferedRandom,
BufferedRWPair):

449
Lib/opcode.py vendored
View File

@@ -4,404 +4,47 @@ opcode module - potentially shared between dis and other modules which
operate on bytecodes (e.g. peephole optimizers).
"""
__all__ = ["cmp_op", "hasarg", "hasconst", "hasname", "hasjrel", "hasjabs",
"haslocal", "hascompare", "hasfree", "hasexc", "opname", "opmap",
"HAVE_ARGUMENT", "EXTENDED_ARG"]
# It's a chicken-and-egg I'm afraid:
# We're imported before _opcode's made.
# With exception unheeded
# (stack_effect is not needed)
# Both our chickens and eggs are allayed.
# --Larry Hastings, 2013/11/23
__all__ = ["cmp_op", "stack_effect", "hascompare", "opname", "opmap",
"HAVE_ARGUMENT", "EXTENDED_ARG", "hasarg", "hasconst", "hasname",
"hasjump", "hasjrel", "hasjabs", "hasfree", "haslocal", "hasexc"]
try:
from _opcode import stack_effect
__all__.append('stack_effect')
except ImportError:
pass
import _opcode
from _opcode import stack_effect
cmp_op = ('<', '<=', '==', '!=', '>', '>=')
from _opcode_metadata import (_specializations, _specialized_opmap, opmap,
HAVE_ARGUMENT, MIN_INSTRUMENTED_OPCODE)
EXTENDED_ARG = opmap['EXTENDED_ARG']
hasarg = []
hasconst = []
hasname = []
hasjrel = []
hasjabs = []
haslocal = []
hascompare = []
hasfree = []
hasexc = []
def is_pseudo(op):
return op >= MIN_PSEUDO_OPCODE and op <= MAX_PSEUDO_OPCODE
oplists = [hasarg, hasconst, hasname, hasjrel, hasjabs,
haslocal, hascompare, hasfree, hasexc]
opmap = {}
## pseudo opcodes (used in the compiler) mapped to the values
## they can become in the actual code.
_pseudo_ops = {}
def def_op(name, op):
opmap[name] = op
def name_op(name, op):
def_op(name, op)
hasname.append(op)
def jrel_op(name, op):
def_op(name, op)
hasjrel.append(op)
def jabs_op(name, op):
def_op(name, op)
hasjabs.append(op)
def pseudo_op(name, op, real_ops):
def_op(name, op)
_pseudo_ops[name] = real_ops
# add the pseudo opcode to the lists its targets are in
for oplist in oplists:
res = [opmap[rop] in oplist for rop in real_ops]
if any(res):
assert all(res)
oplist.append(op)
# Instruction opcodes for compiled code
# Blank lines correspond to available opcodes
def_op('CACHE', 0)
def_op('POP_TOP', 1)
def_op('PUSH_NULL', 2)
def_op('NOP', 9)
def_op('UNARY_POSITIVE', 10)
def_op('UNARY_NEGATIVE', 11)
def_op('UNARY_NOT', 12)
def_op('UNARY_INVERT', 15)
def_op('BINARY_SUBSCR', 25)
def_op('BINARY_SLICE', 26)
def_op('STORE_SLICE', 27)
def_op('GET_LEN', 30)
def_op('MATCH_MAPPING', 31)
def_op('MATCH_SEQUENCE', 32)
def_op('MATCH_KEYS', 33)
def_op('PUSH_EXC_INFO', 35)
def_op('CHECK_EXC_MATCH', 36)
def_op('CHECK_EG_MATCH', 37)
def_op('WITH_EXCEPT_START', 49)
def_op('GET_AITER', 50)
def_op('GET_ANEXT', 51)
def_op('BEFORE_ASYNC_WITH', 52)
def_op('BEFORE_WITH', 53)
def_op('END_ASYNC_FOR', 54)
def_op('CLEANUP_THROW', 55)
def_op('STORE_SUBSCR', 60)
def_op('DELETE_SUBSCR', 61)
# TODO: RUSTPYTHON
# Delete below def_op after updating coroutines.py
def_op('YIELD_FROM', 72)
def_op('GET_ITER', 68)
def_op('GET_YIELD_FROM_ITER', 69)
def_op('PRINT_EXPR', 70)
def_op('LOAD_BUILD_CLASS', 71)
def_op('LOAD_ASSERTION_ERROR', 74)
def_op('RETURN_GENERATOR', 75)
def_op('LIST_TO_TUPLE', 82)
def_op('RETURN_VALUE', 83)
def_op('IMPORT_STAR', 84)
def_op('SETUP_ANNOTATIONS', 85)
def_op('ASYNC_GEN_WRAP', 87)
def_op('PREP_RERAISE_STAR', 88)
def_op('POP_EXCEPT', 89)
HAVE_ARGUMENT = 90 # real opcodes from here have an argument:
name_op('STORE_NAME', 90) # Index in name list
name_op('DELETE_NAME', 91) # ""
def_op('UNPACK_SEQUENCE', 92) # Number of tuple items
jrel_op('FOR_ITER', 93)
def_op('UNPACK_EX', 94)
name_op('STORE_ATTR', 95) # Index in name list
name_op('DELETE_ATTR', 96) # ""
name_op('STORE_GLOBAL', 97) # ""
name_op('DELETE_GLOBAL', 98) # ""
def_op('SWAP', 99)
def_op('LOAD_CONST', 100) # Index in const list
hasconst.append(100)
name_op('LOAD_NAME', 101) # Index in name list
def_op('BUILD_TUPLE', 102) # Number of tuple items
def_op('BUILD_LIST', 103) # Number of list items
def_op('BUILD_SET', 104) # Number of set items
def_op('BUILD_MAP', 105) # Number of dict entries
name_op('LOAD_ATTR', 106) # Index in name list
def_op('COMPARE_OP', 107) # Comparison operator
hascompare.append(107)
name_op('IMPORT_NAME', 108) # Index in name list
name_op('IMPORT_FROM', 109) # Index in name list
jrel_op('JUMP_FORWARD', 110) # Number of words to skip
jrel_op('JUMP_IF_FALSE_OR_POP', 111) # Number of words to skip
jrel_op('JUMP_IF_TRUE_OR_POP', 112) # ""
jrel_op('POP_JUMP_IF_FALSE', 114)
jrel_op('POP_JUMP_IF_TRUE', 115)
name_op('LOAD_GLOBAL', 116) # Index in name list
def_op('IS_OP', 117)
def_op('CONTAINS_OP', 118)
def_op('RERAISE', 119)
def_op('COPY', 120)
def_op('BINARY_OP', 122)
jrel_op('SEND', 123) # Number of bytes to skip
def_op('LOAD_FAST', 124) # Local variable number, no null check
haslocal.append(124)
def_op('STORE_FAST', 125) # Local variable number
haslocal.append(125)
def_op('DELETE_FAST', 126) # Local variable number
haslocal.append(126)
def_op('LOAD_FAST_CHECK', 127) # Local variable number
haslocal.append(127)
jrel_op('POP_JUMP_IF_NOT_NONE', 128)
jrel_op('POP_JUMP_IF_NONE', 129)
def_op('RAISE_VARARGS', 130) # Number of raise arguments (1, 2, or 3)
def_op('GET_AWAITABLE', 131)
def_op('MAKE_FUNCTION', 132) # Flags
def_op('BUILD_SLICE', 133) # Number of items
jrel_op('JUMP_BACKWARD_NO_INTERRUPT', 134) # Number of words to skip (backwards)
def_op('MAKE_CELL', 135)
hasfree.append(135)
def_op('LOAD_CLOSURE', 136)
hasfree.append(136)
def_op('LOAD_DEREF', 137)
hasfree.append(137)
def_op('STORE_DEREF', 138)
hasfree.append(138)
def_op('DELETE_DEREF', 139)
hasfree.append(139)
jrel_op('JUMP_BACKWARD', 140) # Number of words to skip (backwards)
def_op('CALL_FUNCTION_EX', 142) # Flags
def_op('EXTENDED_ARG', 144)
EXTENDED_ARG = 144
def_op('LIST_APPEND', 145)
def_op('SET_ADD', 146)
def_op('MAP_ADD', 147)
def_op('LOAD_CLASSDEREF', 148)
hasfree.append(148)
def_op('COPY_FREE_VARS', 149)
def_op('YIELD_VALUE', 150)
def_op('RESUME', 151) # This must be kept in sync with deepfreeze.py
def_op('MATCH_CLASS', 152)
def_op('FORMAT_VALUE', 155)
def_op('BUILD_CONST_KEY_MAP', 156)
def_op('BUILD_STRING', 157)
def_op('LIST_EXTEND', 162)
def_op('SET_UPDATE', 163)
def_op('DICT_MERGE', 164)
def_op('DICT_UPDATE', 165)
def_op('CALL', 171)
def_op('KW_NAMES', 172)
hasconst.append(172)
hasarg.extend([op for op in opmap.values() if op >= HAVE_ARGUMENT])
MIN_PSEUDO_OPCODE = 256
pseudo_op('SETUP_FINALLY', 256, ['NOP'])
hasexc.append(256)
pseudo_op('SETUP_CLEANUP', 257, ['NOP'])
hasexc.append(257)
pseudo_op('SETUP_WITH', 258, ['NOP'])
hasexc.append(258)
pseudo_op('POP_BLOCK', 259, ['NOP'])
pseudo_op('JUMP', 260, ['JUMP_FORWARD', 'JUMP_BACKWARD'])
pseudo_op('JUMP_NO_INTERRUPT', 261, ['JUMP_FORWARD', 'JUMP_BACKWARD_NO_INTERRUPT'])
pseudo_op('LOAD_METHOD', 262, ['LOAD_ATTR'])
MAX_PSEUDO_OPCODE = MIN_PSEUDO_OPCODE + len(_pseudo_ops) - 1
del def_op, name_op, jrel_op, jabs_op, pseudo_op
opname = ['<%r>' % (op,) for op in range(MAX_PSEUDO_OPCODE + 1)]
opname = ['<%r>' % (op,) for op in range(max(opmap.values()) + 1)]
for op, i in opmap.items():
opname[i] = op
cmp_op = ('<', '<=', '==', '!=', '>', '>=')
_nb_ops = [
("NB_ADD", "+"),
("NB_AND", "&"),
("NB_FLOOR_DIVIDE", "//"),
("NB_LSHIFT", "<<"),
("NB_MATRIX_MULTIPLY", "@"),
("NB_MULTIPLY", "*"),
("NB_REMAINDER", "%"),
("NB_OR", "|"),
("NB_POWER", "**"),
("NB_RSHIFT", ">>"),
("NB_SUBTRACT", "-"),
("NB_TRUE_DIVIDE", "/"),
("NB_XOR", "^"),
("NB_INPLACE_ADD", "+="),
("NB_INPLACE_AND", "&="),
("NB_INPLACE_FLOOR_DIVIDE", "//="),
("NB_INPLACE_LSHIFT", "<<="),
("NB_INPLACE_MATRIX_MULTIPLY", "@="),
("NB_INPLACE_MULTIPLY", "*="),
("NB_INPLACE_REMAINDER", "%="),
("NB_INPLACE_OR", "|="),
("NB_INPLACE_POWER", "**="),
("NB_INPLACE_RSHIFT", ">>="),
("NB_INPLACE_SUBTRACT", "-="),
("NB_INPLACE_TRUE_DIVIDE", "/="),
("NB_INPLACE_XOR", "^="),
]
# These lists are documented as part of the dis module's API
hasarg = [op for op in opmap.values() if _opcode.has_arg(op)]
hasconst = [op for op in opmap.values() if _opcode.has_const(op)]
hasname = [op for op in opmap.values() if _opcode.has_name(op)]
hasjump = [op for op in opmap.values() if _opcode.has_jump(op)]
hasjrel = hasjump # for backward compatibility
hasjabs = []
hasfree = [op for op in opmap.values() if _opcode.has_free(op)]
haslocal = [op for op in opmap.values() if _opcode.has_local(op)]
hasexc = [op for op in opmap.values() if _opcode.has_exc(op)]
_specializations = {
"BINARY_OP": [
"BINARY_OP_ADAPTIVE",
"BINARY_OP_ADD_FLOAT",
"BINARY_OP_ADD_INT",
"BINARY_OP_ADD_UNICODE",
"BINARY_OP_INPLACE_ADD_UNICODE",
"BINARY_OP_MULTIPLY_FLOAT",
"BINARY_OP_MULTIPLY_INT",
"BINARY_OP_SUBTRACT_FLOAT",
"BINARY_OP_SUBTRACT_INT",
],
"BINARY_SUBSCR": [
"BINARY_SUBSCR_ADAPTIVE",
"BINARY_SUBSCR_DICT",
"BINARY_SUBSCR_GETITEM",
"BINARY_SUBSCR_LIST_INT",
"BINARY_SUBSCR_TUPLE_INT",
],
"CALL": [
"CALL_ADAPTIVE",
"CALL_PY_EXACT_ARGS",
"CALL_PY_WITH_DEFAULTS",
"CALL_BOUND_METHOD_EXACT_ARGS",
"CALL_BUILTIN_CLASS",
"CALL_BUILTIN_FAST_WITH_KEYWORDS",
"CALL_METHOD_DESCRIPTOR_FAST_WITH_KEYWORDS",
"CALL_NO_KW_BUILTIN_FAST",
"CALL_NO_KW_BUILTIN_O",
"CALL_NO_KW_ISINSTANCE",
"CALL_NO_KW_LEN",
"CALL_NO_KW_LIST_APPEND",
"CALL_NO_KW_METHOD_DESCRIPTOR_FAST",
"CALL_NO_KW_METHOD_DESCRIPTOR_NOARGS",
"CALL_NO_KW_METHOD_DESCRIPTOR_O",
"CALL_NO_KW_STR_1",
"CALL_NO_KW_TUPLE_1",
"CALL_NO_KW_TYPE_1",
],
"COMPARE_OP": [
"COMPARE_OP_ADAPTIVE",
"COMPARE_OP_FLOAT_JUMP",
"COMPARE_OP_INT_JUMP",
"COMPARE_OP_STR_JUMP",
],
"EXTENDED_ARG": [
"EXTENDED_ARG_QUICK",
],
"FOR_ITER": [
"FOR_ITER_ADAPTIVE",
"FOR_ITER_LIST",
"FOR_ITER_RANGE",
],
"JUMP_BACKWARD": [
"JUMP_BACKWARD_QUICK",
],
"LOAD_ATTR": [
"LOAD_ATTR_ADAPTIVE",
# These potentially push [NULL, bound method] onto the stack.
"LOAD_ATTR_CLASS",
"LOAD_ATTR_GETATTRIBUTE_OVERRIDDEN",
"LOAD_ATTR_INSTANCE_VALUE",
"LOAD_ATTR_MODULE",
"LOAD_ATTR_PROPERTY",
"LOAD_ATTR_SLOT",
"LOAD_ATTR_WITH_HINT",
# These will always push [unbound method, self] onto the stack.
"LOAD_ATTR_METHOD_LAZY_DICT",
"LOAD_ATTR_METHOD_NO_DICT",
"LOAD_ATTR_METHOD_WITH_DICT",
"LOAD_ATTR_METHOD_WITH_VALUES",
],
"LOAD_CONST": [
"LOAD_CONST__LOAD_FAST",
],
"LOAD_FAST": [
"LOAD_FAST__LOAD_CONST",
"LOAD_FAST__LOAD_FAST",
],
"LOAD_GLOBAL": [
"LOAD_GLOBAL_ADAPTIVE",
"LOAD_GLOBAL_BUILTIN",
"LOAD_GLOBAL_MODULE",
],
"RESUME": [
"RESUME_QUICK",
],
"STORE_ATTR": [
"STORE_ATTR_ADAPTIVE",
"STORE_ATTR_INSTANCE_VALUE",
"STORE_ATTR_SLOT",
"STORE_ATTR_WITH_HINT",
],
"STORE_FAST": [
"STORE_FAST__LOAD_FAST",
"STORE_FAST__STORE_FAST",
],
"STORE_SUBSCR": [
"STORE_SUBSCR_ADAPTIVE",
"STORE_SUBSCR_DICT",
"STORE_SUBSCR_LIST_INT",
],
"UNPACK_SEQUENCE": [
"UNPACK_SEQUENCE_ADAPTIVE",
"UNPACK_SEQUENCE_LIST",
"UNPACK_SEQUENCE_TUPLE",
"UNPACK_SEQUENCE_TWO_TUPLE",
],
}
_specialized_instructions = [
opcode for family in _specializations.values() for opcode in family
]
_specialization_stats = [
"success",
"failure",
"hit",
"deferred",
"miss",
"deopt",
]
_intrinsic_1_descs = _opcode.get_intrinsic1_descs()
_intrinsic_2_descs = _opcode.get_intrinsic2_descs()
_nb_ops = _opcode.get_nb_ops()
hascompare = [opmap["COMPARE_OP"]]
_cache_format = {
"LOAD_GLOBAL": {
"counter": 1,
"index": 1,
"module_keys_version": 2,
"module_keys_version": 1,
"builtin_keys_version": 1,
},
"BINARY_OP": {
@@ -412,16 +55,19 @@ _cache_format = {
},
"COMPARE_OP": {
"counter": 1,
"mask": 1,
},
"CONTAINS_OP": {
"counter": 1,
},
"BINARY_SUBSCR": {
"counter": 1,
"type_version": 2,
"func_version": 1,
},
"FOR_ITER": {
"counter": 1,
},
"LOAD_SUPER_ATTR": {
"counter": 1,
},
"LOAD_ATTR": {
"counter": 1,
"version": 2,
@@ -436,13 +82,34 @@ _cache_format = {
"CALL": {
"counter": 1,
"func_version": 2,
"min_args": 1,
},
"STORE_SUBSCR": {
"counter": 1,
},
"SEND": {
"counter": 1,
},
"JUMP_BACKWARD": {
"counter": 1,
},
"TO_BOOL": {
"counter": 1,
"version": 2,
},
"POP_JUMP_IF_TRUE": {
"counter": 1,
},
"POP_JUMP_IF_FALSE": {
"counter": 1,
},
"POP_JUMP_IF_NONE": {
"counter": 1,
},
"POP_JUMP_IF_NOT_NONE": {
"counter": 1,
},
}
_inline_cache_entries = [
sum(_cache_format.get(opname[opcode], {}).values()) for opcode in range(256)
]
_inline_cache_entries = {
name : sum(value.values()) for (name, value) in _cache_format.items()
}

View File

@@ -7,7 +7,7 @@ import contextlib
import dataclasses
import functools
import logging
# import _opcode # TODO: RUSTPYTHON
import _opcode
import os
import re
import stat

View File

@@ -3,10 +3,26 @@
import unittest
import dis
import io
from _testinternalcapi import compiler_codegen, optimize_cfg, assemble_code_object
import opcode
try:
import _testinternalcapi
except ImportError:
_testinternalcapi = None
_UNSPECIFIED = object()
def instructions_with_positions(instrs, co_positions):
# Return (instr, positions) pairs from the instrs list and co_positions
# iterator. The latter contains items for cache lines and the former
# doesn't, so those need to be skipped.
co_positions = co_positions or iter(())
for instr in instrs:
yield instr, next(co_positions, ())
for _, size, _ in (instr.cache_info or ()):
for i in range(size):
next(co_positions, ())
class BytecodeTestCase(unittest.TestCase):
"""Custom assertion methods for inspecting bytecode."""
@@ -53,16 +69,14 @@ class CompilationStepTestCase(unittest.TestCase):
class Label:
pass
def assertInstructionsMatch(self, actual_, expected_):
# get two lists where each entry is a label or
# an instruction tuple. Normalize the labels to the
# instruction count of the target, and compare the lists.
def assertInstructionsMatch(self, actual_seq, expected):
# get an InstructionSequence and an expected list, where each
# entry is a label or an instruction tuple. Construct an expcted
# instruction sequence and compare with the one given.
self.assertIsInstance(actual_, list)
self.assertIsInstance(expected_, list)
actual = self.normalize_insts(actual_)
expected = self.normalize_insts(expected_)
self.assertIsInstance(expected, list)
actual = actual_seq.get_instructions()
expected = self.seq_from_insts(expected).get_instructions()
self.assertEqual(len(actual), len(expected))
# compare instructions
@@ -72,10 +86,8 @@ class CompilationStepTestCase(unittest.TestCase):
continue
self.assertIsInstance(exp, tuple)
self.assertIsInstance(act, tuple)
# crop comparison to the provided expected values
if len(act) > len(exp):
act = act[:len(exp)]
self.assertEqual(exp, act)
idx = max([p[0] for p in enumerate(exp) if p[1] != -1])
self.assertEqual(exp[:idx], act[:idx])
def resolveAndRemoveLabels(self, insts):
idx = 0
@@ -90,54 +102,57 @@ class CompilationStepTestCase(unittest.TestCase):
return res
def normalize_insts(self, insts):
""" Map labels to instruction index.
Map opcodes to opnames.
"""
insts = self.resolveAndRemoveLabels(insts)
res = []
def seq_from_insts(self, insts):
labels = {item for item in insts if isinstance(item, self.Label)}
for i, lbl in enumerate(labels):
lbl.value = i
seq = _testinternalcapi.new_instruction_sequence()
for item in insts:
assert isinstance(item, tuple)
opcode, oparg, *loc = item
opcode = dis.opmap.get(opcode, opcode)
if isinstance(oparg, self.Label):
arg = oparg.value
if isinstance(item, self.Label):
seq.use_label(item.value)
else:
arg = oparg if opcode in self.HAS_ARG else None
opcode = dis.opname[opcode]
res.append((opcode, arg, *loc))
return res
op = item[0]
if isinstance(op, str):
op = opcode.opmap[op]
arg, *loc = item[1:]
if isinstance(arg, self.Label):
arg = arg.value
loc = loc + [-1] * (4 - len(loc))
seq.addop(op, arg or 0, *loc)
return seq
def complete_insts_info(self, insts):
# fill in omitted fields in location, and oparg 0 for ops with no arg.
res = []
for item in insts:
assert isinstance(item, tuple)
inst = list(item)
opcode = dis.opmap[inst[0]]
oparg = inst[1]
loc = inst[2:] + [-1] * (6 - len(inst))
res.append((opcode, oparg, *loc))
return res
def check_instructions(self, insts):
for inst in insts:
if isinstance(inst, self.Label):
continue
op, arg, *loc = inst
if isinstance(op, str):
op = opcode.opmap[op]
self.assertEqual(op in opcode.hasarg,
arg is not None,
f"{opcode.opname[op]=} {arg=}")
self.assertTrue(all(isinstance(l, int) for l in loc))
@unittest.skipIf(_testinternalcapi is None, "requires _testinternalcapi")
class CodegenTestCase(CompilationStepTestCase):
def generate_code(self, ast):
insts, _ = compiler_codegen(ast, "my_file.py", 0)
insts, _ = _testinternalcapi.compiler_codegen(ast, "my_file.py", 0)
return insts
@unittest.skipIf(_testinternalcapi is None, "requires _testinternalcapi")
class CfgOptimizationTestCase(CompilationStepTestCase):
def get_optimized(self, insts, consts, nlocals=0):
insts = self.normalize_insts(insts)
insts = self.complete_insts_info(insts)
insts = optimize_cfg(insts, consts, nlocals)
def get_optimized(self, seq, consts, nlocals=0):
insts = _testinternalcapi.optimize_cfg(seq, consts, nlocals)
return insts, consts
@unittest.skipIf(_testinternalcapi is None, "requires _testinternalcapi")
class AssemblerTestCase(CompilationStepTestCase):
def get_code_object(self, filename, insts, metadata):
co = assemble_code_object(filename, insts, metadata)
co = _testinternalcapi.assemble_code_object(filename, insts, metadata)
return co

143
Lib/test/test__opcode.py vendored Normal file
View File

@@ -0,0 +1,143 @@
import dis
from test.support.import_helper import import_module
import unittest
import opcode
_opcode = import_module("_opcode")
from _opcode import stack_effect
class OpListTests(unittest.TestCase):
def check_bool_function_result(self, func, ops, expected):
for op in ops:
if isinstance(op, str):
op = dis.opmap[op]
with self.subTest(opcode=op, func=func):
self.assertIsInstance(func(op), bool)
self.assertEqual(func(op), expected)
def test_invalid_opcodes(self):
invalid = [-100, -1, 255, 512, 513, 1000]
self.check_bool_function_result(_opcode.is_valid, invalid, False)
self.check_bool_function_result(_opcode.has_arg, invalid, False)
self.check_bool_function_result(_opcode.has_const, invalid, False)
self.check_bool_function_result(_opcode.has_name, invalid, False)
self.check_bool_function_result(_opcode.has_jump, invalid, False)
self.check_bool_function_result(_opcode.has_free, invalid, False)
self.check_bool_function_result(_opcode.has_local, invalid, False)
self.check_bool_function_result(_opcode.has_exc, invalid, False)
@unittest.expectedFailure # TODO: RUSTPYTHON; AttributeError: module 'dis' has no attribute 'opmap'
def test_is_valid(self):
names = [
'CACHE',
'POP_TOP',
'IMPORT_NAME',
'JUMP',
'INSTRUMENTED_RETURN_VALUE',
]
opcodes = [dis.opmap[opname] for opname in names]
self.check_bool_function_result(_opcode.is_valid, opcodes, True)
@unittest.expectedFailure # TODO: RUSTPYTHON; AttributeError: module 'dis' has no attribute 'hasarg'
def test_oplists(self):
def check_function(self, func, expected):
for op in [-10, 520]:
with self.subTest(opcode=op, func=func):
res = func(op)
self.assertIsInstance(res, bool)
self.assertEqual(res, op in expected)
check_function(self, _opcode.has_arg, dis.hasarg)
check_function(self, _opcode.has_const, dis.hasconst)
check_function(self, _opcode.has_name, dis.hasname)
check_function(self, _opcode.has_jump, dis.hasjump)
check_function(self, _opcode.has_free, dis.hasfree)
check_function(self, _opcode.has_local, dis.haslocal)
check_function(self, _opcode.has_exc, dis.hasexc)
class StackEffectTests(unittest.TestCase):
@unittest.expectedFailure # TODO: RUSTPYTHON
def test_stack_effect(self):
self.assertEqual(stack_effect(dis.opmap['POP_TOP']), -1)
self.assertEqual(stack_effect(dis.opmap['BUILD_SLICE'], 0), -1)
self.assertEqual(stack_effect(dis.opmap['BUILD_SLICE'], 1), -1)
self.assertEqual(stack_effect(dis.opmap['BUILD_SLICE'], 3), -2)
self.assertRaises(ValueError, stack_effect, 30000)
# All defined opcodes
has_arg = dis.hasarg
for name, code in filter(lambda item: item[0] not in dis.deoptmap, dis.opmap.items()):
if code >= opcode.MIN_INSTRUMENTED_OPCODE:
continue
with self.subTest(opname=name):
stack_effect(code)
stack_effect(code, 0)
# All not defined opcodes
for code in set(range(256)) - set(dis.opmap.values()):
with self.subTest(opcode=code):
self.assertRaises(ValueError, stack_effect, code)
self.assertRaises(ValueError, stack_effect, code, 0)
@unittest.expectedFailure # TODO: RUSTPYTHON
def test_stack_effect_jump(self):
FOR_ITER = dis.opmap['FOR_ITER']
self.assertEqual(stack_effect(FOR_ITER, 0), 1)
self.assertEqual(stack_effect(FOR_ITER, 0, jump=True), 1)
self.assertEqual(stack_effect(FOR_ITER, 0, jump=False), 1)
JUMP_FORWARD = dis.opmap['JUMP_FORWARD']
self.assertEqual(stack_effect(JUMP_FORWARD, 0), 0)
self.assertEqual(stack_effect(JUMP_FORWARD, 0, jump=True), 0)
self.assertEqual(stack_effect(JUMP_FORWARD, 0, jump=False), 0)
# All defined opcodes
has_arg = dis.hasarg
has_exc = dis.hasexc
has_jump = dis.hasjabs + dis.hasjrel
for name, code in filter(lambda item: item[0] not in dis.deoptmap, dis.opmap.items()):
if code >= opcode.MIN_INSTRUMENTED_OPCODE:
continue
with self.subTest(opname=name):
if code not in has_arg:
common = stack_effect(code)
jump = stack_effect(code, jump=True)
nojump = stack_effect(code, jump=False)
else:
common = stack_effect(code, 0)
jump = stack_effect(code, 0, jump=True)
nojump = stack_effect(code, 0, jump=False)
if code in has_jump or code in has_exc:
self.assertEqual(common, max(jump, nojump))
else:
self.assertEqual(jump, common)
self.assertEqual(nojump, common)
class SpecializationStatsTests(unittest.TestCase):
def test_specialization_stats(self):
stat_names = ["success", "failure", "hit", "deferred", "miss", "deopt"]
specialized_opcodes = [
op.lower()
for op in opcode._specializations
if opcode._inline_cache_entries.get(op, 0)
]
self.assertIn('load_attr', specialized_opcodes)
self.assertIn('binary_subscr', specialized_opcodes)
stats = _opcode.get_specialization_stats()
if stats is not None:
self.assertIsInstance(stats, dict)
self.assertCountEqual(stats.keys(), specialized_opcodes)
self.assertCountEqual(
stats['load_attr'].keys(),
stat_names + ['failure_kinds'])
for sn in stat_names:
self.assertIsInstance(stats['load_attr'][sn], int)
self.assertIsInstance(
stats['load_attr']['failure_kinds'],
tuple)
for v in stats['load_attr']['failure_kinds']:
self.assertIsInstance(v, int)
if __name__ == "__main__":
unittest.main()

882
Lib/test/test_call.py vendored

File diff suppressed because it is too large Load Diff

12
Lib/test/test_code.py vendored
View File

@@ -347,8 +347,6 @@ class CodeTest(unittest.TestCase):
newcode = code.replace(co_name="func") # Should not raise SystemError
self.assertEqual(code, newcode)
# TODO: RUSTPYTHON
@unittest.expectedFailure
def test_empty_linetable(self):
def func():
pass
@@ -468,8 +466,6 @@ class CodeTest(unittest.TestCase):
# co_positions behavior when info is missing.
# TODO: RUSTPYTHON
@unittest.expectedFailure
# @requires_debug_ranges()
def test_co_positions_empty_linetable(self):
def func():
@@ -480,8 +476,6 @@ class CodeTest(unittest.TestCase):
self.assertIsNone(line)
self.assertEqual(end_line, new_code.co_firstlineno + 1)
# TODO: RUSTPYTHON
@unittest.expectedFailure
def test_code_equality(self):
def f():
try:
@@ -522,8 +516,6 @@ class CodeTest(unittest.TestCase):
self.assertNotEqual(c, swapped)
self.assertNotEqual(hash(c), hash(swapped))
# TODO: RUSTPYTHON
@unittest.expectedFailure
def test_code_hash_uses_bytecode(self):
c = (lambda x, y: x + y).__code__
d = (lambda x, y: x * y).__code__
@@ -735,8 +727,6 @@ class CodeLocationTest(unittest.TestCase):
self.assertEqual(l1, l2)
self.assertEqual(len(pos1), len(pos2))
# TODO: RUSTPYTHON
@unittest.expectedFailure
def test_positions(self):
self.check_positions(parse_location_table)
self.check_positions(misshappen)
@@ -751,8 +741,6 @@ class CodeLocationTest(unittest.TestCase):
self.assertEqual(l1, l2)
self.assertEqual(len(lines1), len(lines2))
# TODO: RUSTPYTHON
@unittest.expectedFailure
def test_lines(self):
self.check_lines(parse_location_table)
self.check_lines(misshappen)

View File

@@ -762,7 +762,6 @@ class UTF16Test(ReadTest, unittest.TestCase):
f = reader(s)
self.assertEqual(f.read(), "spamspam")
@unittest.expectedFailure # TODO: RUSTPYTHON;; UTF-16 stream does not start with BOM
def test_badbom(self):
s = io.BytesIO(b"\xff\xff")
f = codecs.getreader(self.encoding)(s)
@@ -1509,7 +1508,6 @@ class PunycodeTest(unittest.TestCase):
puny = puny.decode("ascii").encode("ascii")
self.assertEqual(uni, puny.decode("punycode"))
@unittest.expectedFailure # TODO: RUSTPYTHON; b'Pro\xffprostnemluvesky' != b'Pro\xffprostnemluvesky-uyb24dma41a'
def test_decode_invalid(self):
testcases = [
(b"xn--w&", "strict", UnicodeDecodeError("punycode", b"", 5, 6, "")),
@@ -1694,7 +1692,6 @@ nameprep_tests = [
class NameprepTest(unittest.TestCase):
@unittest.expectedFailure # TODO: RUSTPYTHON; UnicodeError: Invalid character '\u1680'
def test_nameprep(self):
from encodings.idna import nameprep
for pos, (orig, prepped) in enumerate(nameprep_tests):
@@ -1732,7 +1729,6 @@ class IDNACodecTest(unittest.TestCase):
("あさ.\u034f", UnicodeEncodeError("idna", "あさ.\u034f", 3, 4, "")),
]
@unittest.expectedFailure # TODO: RUSTPYTHON; 'XN--pythn-mua.org.' != 'pythön.org.'
def test_builtin_decode(self):
self.assertEqual(str(b"python.org", "idna"), "python.org")
self.assertEqual(str(b"python.org.", "idna"), "python.org.")
@@ -1746,7 +1742,6 @@ class IDNACodecTest(unittest.TestCase):
self.assertEqual(str(b"bugs.XN--pythn-mua.org.", "idna"),
"bugs.pyth\xf6n.org.")
@unittest.expectedFailure # TODO: RUSTPYTHON; 'ascii' != 'idna'
def test_builtin_decode_invalid(self):
for case, expected in self.invalid_decode_testcases:
with self.subTest(case=case, expected=expected):
@@ -1764,7 +1759,6 @@ class IDNACodecTest(unittest.TestCase):
self.assertEqual("pyth\xf6n.org".encode("idna"), b"xn--pythn-mua.org")
self.assertEqual("pyth\xf6n.org.".encode("idna"), b"xn--pythn-mua.org.")
@unittest.expectedFailure # TODO: RUSTPYTHON; UnicodeError: label empty or too long
def test_builtin_encode_invalid(self):
for case, expected in self.invalid_encode_testcases:
with self.subTest(case=case, expected=expected):
@@ -1776,7 +1770,6 @@ class IDNACodecTest(unittest.TestCase):
self.assertEqual(exc.start, expected.start)
self.assertEqual(exc.end, expected.end)
@unittest.expectedFailure # TODO: RUSTPYTHON; UnicodeError: label empty or too long
def test_builtin_decode_length_limit(self):
with self.assertRaisesRegex(UnicodeDecodeError, "way too long"):
(b"xn--016c"+b"a"*1100).decode("idna")
@@ -1818,7 +1811,6 @@ class IDNACodecTest(unittest.TestCase):
self.assertEqual(decoder.decode(b"rg."), "org.")
self.assertEqual(decoder.decode(b"", True), "")
@unittest.expectedFailure # TODO: RUSTPYTHON; 'ascii' != 'idna'
def test_incremental_decode_invalid(self):
iterdecode_testcases = [
(b"\xFFpython.org", UnicodeDecodeError("idna", b"\xFF", 0, 1, "")),
@@ -1880,7 +1872,6 @@ class IDNACodecTest(unittest.TestCase):
self.assertEqual(encoder.encode("ample.org."), b"xn--xample-9ta.org.")
self.assertEqual(encoder.encode("", True), b"")
@unittest.expectedFailure # TODO: RUSTPYTHON; UnicodeError: label empty or too long
def test_incremental_encode_invalid(self):
iterencode_testcases = [
(f"foo.{'\xff'*60}", UnicodeEncodeError("idna", f"{'\xff'*60}", 0, 60, "")),

View File

@@ -885,8 +885,6 @@ if 1:
self.assertIn('LOAD_ATTR', instructions)
self.assertIn('PRECALL', instructions)
# TODO: RUSTPYTHON
@unittest.expectedFailure
def test_lineno_procedure_call(self):
def call():
(

View File

@@ -344,10 +344,9 @@ class OtherFileTests:
class COtherFileTests(OtherFileTests, unittest.TestCase):
open = io.open
# TODO: RUSTPYTHON
@unittest.expectedFailure
@unittest.expectedFailure # TODO: RUSTPYTHON
def testSetBufferSize(self):
super().testSetBufferSize()
return super().testSetBufferSize()
class PyOtherFileTests(OtherFileTests, unittest.TestCase):
open = staticmethod(pyio.open)

View File

@@ -71,7 +71,7 @@ class FnmatchTestCase(unittest.TestCase):
check('usr/bin', 'usr\\bin', False, fnmatchcase)
check('usr\\bin', 'usr\\bin', True, fnmatchcase)
@unittest.expectedFailureIfWindows("TODO: RUSTPYTHON")
@unittest.expectedFailureIfWindows('TODO: RUSTPYTHON')
def test_bytes(self):
self.check_match(b'test', b'te*')
self.check_match(b'test\xff', b'te*\xff')
@@ -239,17 +239,9 @@ class TranslateTestCase(unittest.TestCase):
self.assertEqual(translate('A*********?[?]?'), r'(?s:A.*.[?].)\Z')
# fancy translation to prevent exponential-time match failure
t = translate('**a*a****a')
digits = re.findall(r'\d+', t)
self.assertEqual(len(digits), 4)
self.assertEqual(digits[0], digits[1])
self.assertEqual(digits[2], digits[3])
g1 = f"g{digits[0]}" # e.g., group name "g4"
g2 = f"g{digits[2]}" # e.g., group name "g5"
self.assertEqual(t,
fr'(?s:(?=(?P<{g1}>.*?a))(?P={g1})(?=(?P<{g2}>.*?a))(?P={g2}).*a)\Z')
self.assertEqual(t, r'(?s:(?>.*?a)(?>.*?a).*a)\Z')
# and try pasting multiple translate results - it's an undocumented
# feature that this works; all the pain of generating unique group
# names across calls exists to support this
# feature that this works
r1 = translate('**a**a**a*')
r2 = translate('**b**b**b*')
r3 = translate('*c*c*c*')

1833
Lib/test/test_fstring.py vendored

File diff suppressed because it is too large Load Diff

194
Lib/test/test_glob.py vendored
View File

@@ -1,9 +1,12 @@
import glob
import os
import re
import shutil
import sys
import unittest
import warnings
from test.support import is_wasi, Py_DEBUG
from test.support.os_helper import (TESTFN, skip_unless_symlink,
can_symlink, create_empty_file, change_cwd)
@@ -167,37 +170,45 @@ class GlobTests(unittest.TestCase):
self.norm('aab', 'F')])
def test_glob_directory_with_trailing_slash(self):
# Patterns ending with a slash shouldn't match non-dirs
res = glob.glob(self.norm('Z*Z') + os.sep)
self.assertEqual(res, [])
res = glob.glob(self.norm('ZZZ') + os.sep)
self.assertEqual(res, [])
# When there is a wildcard pattern which ends with os.sep, glob()
# doesn't blow up.
res = glob.glob(self.norm('aa*') + os.sep)
self.assertEqual(len(res), 2)
# either of these results is reasonable
self.assertIn(set(res), [
{self.norm('aaa'), self.norm('aab')},
{self.norm('aaa') + os.sep, self.norm('aab') + os.sep},
])
seps = (os.sep, os.altsep) if os.altsep else (os.sep,)
for sep in seps:
# Patterns ending with a slash shouldn't match non-dirs
self.assertEqual(glob.glob(self.norm('Z*Z') + sep), [])
self.assertEqual(glob.glob(self.norm('ZZZ') + sep), [])
self.assertEqual(glob.glob(self.norm('aaa') + sep),
[self.norm('aaa') + sep])
# Preserving the redundant separators is an implementation detail.
self.assertEqual(glob.glob(self.norm('aaa') + sep*2),
[self.norm('aaa') + sep*2])
# When there is a wildcard pattern which ends with a pathname
# separator, glob() doesn't blow.
# The result should end with the pathname separator.
# Normalizing the trailing separator is an implementation detail.
eq = self.assertSequencesEqual_noorder
eq(glob.glob(self.norm('aa*') + sep),
[self.norm('aaa') + os.sep, self.norm('aab') + os.sep])
# Stripping the redundant separators is an implementation detail.
eq(glob.glob(self.norm('aa*') + sep*2),
[self.norm('aaa') + os.sep, self.norm('aab') + os.sep])
def test_glob_bytes_directory_with_trailing_slash(self):
# Same as test_glob_directory_with_trailing_slash, but with a
# bytes argument.
res = glob.glob(os.fsencode(self.norm('Z*Z') + os.sep))
self.assertEqual(res, [])
res = glob.glob(os.fsencode(self.norm('ZZZ') + os.sep))
self.assertEqual(res, [])
res = glob.glob(os.fsencode(self.norm('aa*') + os.sep))
self.assertEqual(len(res), 2)
# either of these results is reasonable
self.assertIn(set(res), [
{os.fsencode(self.norm('aaa')),
os.fsencode(self.norm('aab'))},
{os.fsencode(self.norm('aaa') + os.sep),
os.fsencode(self.norm('aab') + os.sep)},
])
seps = (os.sep, os.altsep) if os.altsep else (os.sep,)
for sep in seps:
self.assertEqual(glob.glob(os.fsencode(self.norm('Z*Z') + sep)), [])
self.assertEqual(glob.glob(os.fsencode(self.norm('ZZZ') + sep)), [])
self.assertEqual(glob.glob(os.fsencode(self.norm('aaa') + sep)),
[os.fsencode(self.norm('aaa') + sep)])
self.assertEqual(glob.glob(os.fsencode(self.norm('aaa') + sep*2)),
[os.fsencode(self.norm('aaa') + sep*2)])
eq = self.assertSequencesEqual_noorder
eq(glob.glob(os.fsencode(self.norm('aa*') + sep)),
[os.fsencode(self.norm('aaa') + os.sep),
os.fsencode(self.norm('aab') + os.sep)])
eq(glob.glob(os.fsencode(self.norm('aa*') + sep*2)),
[os.fsencode(self.norm('aaa') + os.sep),
os.fsencode(self.norm('aab') + os.sep)])
@skip_unless_symlink
def test_glob_symlinks(self):
@@ -205,8 +216,7 @@ class GlobTests(unittest.TestCase):
eq(self.glob('sym3'), [self.norm('sym3')])
eq(self.glob('sym3', '*'), [self.norm('sym3', 'EF'),
self.norm('sym3', 'efg')])
self.assertIn(self.glob('sym3' + os.sep),
[[self.norm('sym3')], [self.norm('sym3') + os.sep]])
eq(self.glob('sym3' + os.sep), [self.norm('sym3') + os.sep])
eq(self.glob('*', '*F'),
[self.norm('aaa', 'zzzF'),
self.norm('aab', 'F'), self.norm('sym3', 'EF')])
@@ -364,6 +374,8 @@ class GlobTests(unittest.TestCase):
self.assertEqual(self.rglob('mypipe', 'sub'), [])
self.assertEqual(self.rglob('mypipe', '*'), [])
@unittest.skipIf(is_wasi and Py_DEBUG, "requires too much stack")
def test_glob_many_open_files(self):
depth = 30
base = os.path.join(self.tempdir, 'deep')
@@ -381,10 +393,134 @@ class GlobTests(unittest.TestCase):
for it in iters:
self.assertEqual(next(it), p)
def test_glob0(self):
with self.assertWarns(DeprecationWarning):
glob.glob0(self.tempdir, 'a')
with warnings.catch_warnings():
warnings.simplefilter('ignore')
eq = self.assertSequencesEqual_noorder
eq(glob.glob0(self.tempdir, 'a'), ['a'])
eq(glob.glob0(self.tempdir, '.bb'), ['.bb'])
eq(glob.glob0(self.tempdir, '.b*'), [])
eq(glob.glob0(self.tempdir, 'b'), [])
eq(glob.glob0(self.tempdir, '?'), [])
eq(glob.glob0(self.tempdir, '*a'), [])
eq(glob.glob0(self.tempdir, 'a*'), [])
def test_glob1(self):
with self.assertWarns(DeprecationWarning):
glob.glob1(self.tempdir, 'a')
with warnings.catch_warnings():
warnings.simplefilter('ignore')
eq = self.assertSequencesEqual_noorder
eq(glob.glob1(self.tempdir, 'a'), ['a'])
eq(glob.glob1(self.tempdir, '.bb'), ['.bb'])
eq(glob.glob1(self.tempdir, '.b*'), ['.bb'])
eq(glob.glob1(self.tempdir, 'b'), [])
eq(glob.glob1(self.tempdir, '?'), ['a'])
eq(glob.glob1(self.tempdir, '*a'), ['a', 'aaa'])
eq(glob.glob1(self.tempdir, 'a*'), ['a', 'aaa', 'aab'])
def test_translate_matching(self):
match = re.compile(glob.translate('*')).match
self.assertIsNotNone(match('foo'))
self.assertIsNotNone(match('foo.bar'))
self.assertIsNone(match('.foo'))
match = re.compile(glob.translate('.*')).match
self.assertIsNotNone(match('.foo'))
match = re.compile(glob.translate('**', recursive=True)).match
self.assertIsNotNone(match('foo'))
self.assertIsNone(match('.foo'))
self.assertIsNotNone(match(os.path.join('foo', 'bar')))
self.assertIsNone(match(os.path.join('foo', '.bar')))
self.assertIsNone(match(os.path.join('.foo', 'bar')))
self.assertIsNone(match(os.path.join('.foo', '.bar')))
match = re.compile(glob.translate('**/*', recursive=True)).match
self.assertIsNotNone(match(os.path.join('foo', 'bar')))
self.assertIsNone(match(os.path.join('foo', '.bar')))
self.assertIsNone(match(os.path.join('.foo', 'bar')))
self.assertIsNone(match(os.path.join('.foo', '.bar')))
match = re.compile(glob.translate('*/**', recursive=True)).match
self.assertIsNotNone(match(os.path.join('foo', 'bar')))
self.assertIsNone(match(os.path.join('foo', '.bar')))
self.assertIsNone(match(os.path.join('.foo', 'bar')))
self.assertIsNone(match(os.path.join('.foo', '.bar')))
match = re.compile(glob.translate('**/.bar', recursive=True)).match
self.assertIsNotNone(match(os.path.join('foo', '.bar')))
self.assertIsNone(match(os.path.join('.foo', '.bar')))
match = re.compile(glob.translate('**/*.*', recursive=True)).match
self.assertIsNone(match(os.path.join('foo', 'bar')))
self.assertIsNone(match(os.path.join('foo', '.bar')))
self.assertIsNotNone(match(os.path.join('foo', 'bar.txt')))
self.assertIsNone(match(os.path.join('foo', '.bar.txt')))
def test_translate(self):
def fn(pat):
return glob.translate(pat, seps='/')
self.assertEqual(fn('foo'), r'(?s:foo)\Z')
self.assertEqual(fn('foo/bar'), r'(?s:foo/bar)\Z')
self.assertEqual(fn('*'), r'(?s:[^/.][^/]*)\Z')
self.assertEqual(fn('?'), r'(?s:(?!\.)[^/])\Z')
self.assertEqual(fn('a*'), r'(?s:a[^/]*)\Z')
self.assertEqual(fn('*a'), r'(?s:(?!\.)[^/]*a)\Z')
self.assertEqual(fn('.*'), r'(?s:\.[^/]*)\Z')
self.assertEqual(fn('?aa'), r'(?s:(?!\.)[^/]aa)\Z')
self.assertEqual(fn('aa?'), r'(?s:aa[^/])\Z')
self.assertEqual(fn('aa[ab]'), r'(?s:aa[ab])\Z')
self.assertEqual(fn('**'), r'(?s:(?!\.)[^/]*)\Z')
self.assertEqual(fn('***'), r'(?s:(?!\.)[^/]*)\Z')
self.assertEqual(fn('a**'), r'(?s:a[^/]*)\Z')
self.assertEqual(fn('**b'), r'(?s:(?!\.)[^/]*b)\Z')
self.assertEqual(fn('/**/*/*.*/**'),
r'(?s:/(?!\.)[^/]*/[^/.][^/]*/(?!\.)[^/]*\.[^/]*/(?!\.)[^/]*)\Z')
def test_translate_include_hidden(self):
def fn(pat):
return glob.translate(pat, include_hidden=True, seps='/')
self.assertEqual(fn('foo'), r'(?s:foo)\Z')
self.assertEqual(fn('foo/bar'), r'(?s:foo/bar)\Z')
self.assertEqual(fn('*'), r'(?s:[^/]+)\Z')
self.assertEqual(fn('?'), r'(?s:[^/])\Z')
self.assertEqual(fn('a*'), r'(?s:a[^/]*)\Z')
self.assertEqual(fn('*a'), r'(?s:[^/]*a)\Z')
self.assertEqual(fn('.*'), r'(?s:\.[^/]*)\Z')
self.assertEqual(fn('?aa'), r'(?s:[^/]aa)\Z')
self.assertEqual(fn('aa?'), r'(?s:aa[^/])\Z')
self.assertEqual(fn('aa[ab]'), r'(?s:aa[ab])\Z')
self.assertEqual(fn('**'), r'(?s:[^/]*)\Z')
self.assertEqual(fn('***'), r'(?s:[^/]*)\Z')
self.assertEqual(fn('a**'), r'(?s:a[^/]*)\Z')
self.assertEqual(fn('**b'), r'(?s:[^/]*b)\Z')
self.assertEqual(fn('/**/*/*.*/**'), r'(?s:/[^/]*/[^/]+/[^/]*\.[^/]*/[^/]*)\Z')
def test_translate_recursive(self):
def fn(pat):
return glob.translate(pat, recursive=True, include_hidden=True, seps='/')
self.assertEqual(fn('*'), r'(?s:[^/]+)\Z')
self.assertEqual(fn('?'), r'(?s:[^/])\Z')
self.assertEqual(fn('**'), r'(?s:.*)\Z')
self.assertEqual(fn('**/**'), r'(?s:.*)\Z')
self.assertEqual(fn('***'), r'(?s:[^/]*)\Z')
self.assertEqual(fn('a**'), r'(?s:a[^/]*)\Z')
self.assertEqual(fn('**b'), r'(?s:[^/]*b)\Z')
self.assertEqual(fn('/**/*/*.*/**'), r'(?s:/(?:.+/)?[^/]+/[^/]*\.[^/]*/.*)\Z')
def test_translate_seps(self):
def fn(pat):
return glob.translate(pat, recursive=True, include_hidden=True, seps=['/', '\\'])
self.assertEqual(fn('foo/bar\\baz'), r'(?s:foo[/\\]bar[/\\]baz)\Z')
self.assertEqual(fn('**/*'), r'(?s:(?:.+[/\\])?[^/\\]+)\Z')
@skip_unless_symlink
class SymlinkLoopGlobTests(unittest.TestCase):
# gh-109959: On Linux, glob._isdir() and glob._lexists() can return False
# randomly when checking the "link/" symbolic link.
# https://github.com/python/cpython/issues/109959#issuecomment-2577550700
@unittest.skip("flaky test")
def test_selflink(self):
tempdir = TESTFN + "_dir"
os.makedirs(tempdir)

577
Lib/test/test_io.py vendored
View File

@@ -39,11 +39,9 @@ from itertools import cycle, count
from test import support
from test.support.script_helper import (
assert_python_ok, assert_python_failure, run_python_until_end)
from test.support import import_helper
from test.support import os_helper
from test.support import threading_helper
from test.support import warnings_helper
from test.support import skip_if_sanitizer
from test.support import (
import_helper, is_apple, os_helper, threading_helper, warnings_helper,
)
from test.support.os_helper import FakePath
import codecs
@@ -66,10 +64,6 @@ else:
class EmptyStruct(ctypes.Structure):
pass
# Does io.IOBase finalizer log the exception if the close() method fails?
# The exception is ignored silently by default in release build.
IOBASE_EMITS_UNRAISABLE = (support.Py_DEBUG or sys.flags.dev_mode)
def _default_chunk_size():
"""Get the default TextIOWrapper chunk size"""
@@ -631,10 +625,10 @@ class IOTest(unittest.TestCase):
self.read_ops(f, True)
def test_large_file_ops(self):
# On Windows and Mac OSX this test consumes large resources; It takes
# a long time to build the >2 GiB file and takes >2 GiB of disk space
# therefore the resource must be enabled to run this test.
if sys.platform[:3] == 'win' or sys.platform == 'darwin':
# On Windows and Apple platforms this test consumes large resources; It
# takes a long time to build the >2 GiB file and takes >2 GiB of disk
# space therefore the resource must be enabled to run this test.
if sys.platform[:3] == 'win' or is_apple:
support.requires(
'largefile',
'test requires %s bytes and a long time to run' % self.LARGE)
@@ -645,11 +639,9 @@ class IOTest(unittest.TestCase):
def test_with_open(self):
for bufsize in (0, 100):
f = None
with self.open(os_helper.TESTFN, "wb", bufsize) as f:
f.write(b"xxx")
self.assertEqual(f.closed, True)
f = None
try:
with self.open(os_helper.TESTFN, "wb", bufsize) as f:
1/0
@@ -788,8 +780,7 @@ class IOTest(unittest.TestCase):
file = self.open(f.fileno(), "r", encoding="utf-8", closefd=False)
self.assertEqual(file.buffer.raw.closefd, False)
# TODO: RUSTPYTHON
@unittest.expectedFailure
@unittest.expectedFailure # TODO: RUSTPYTHON
def test_garbage_collection(self):
# FileIO objects are collected, and collecting them flushes
# all data to disk.
@@ -904,7 +895,7 @@ class IOTest(unittest.TestCase):
def badopener(fname, flags):
return -1
with self.assertRaises(ValueError) as cm:
open('non-existent', 'r', opener=badopener)
self.open('non-existent', 'r', opener=badopener)
self.assertEqual(str(cm.exception), 'opener returned -1')
def test_bad_opener_other_negative(self):
@@ -912,7 +903,7 @@ class IOTest(unittest.TestCase):
def badopener(fname, flags):
return -2
with self.assertRaises(ValueError) as cm:
open('non-existent', 'r', opener=badopener)
self.open('non-existent', 'r', opener=badopener)
self.assertEqual(str(cm.exception), 'opener returned -2')
def test_opener_invalid_fd(self):
@@ -1048,11 +1039,41 @@ class IOTest(unittest.TestCase):
# Silence destructor error
R.flush = lambda self: None
@threading_helper.requires_working_threading()
def test_write_readline_races(self):
# gh-134908: Concurrent iteration over a file caused races
thread_count = 2
write_count = 100
read_count = 100
def writer(file, barrier):
barrier.wait()
for _ in range(write_count):
file.write("x")
def reader(file, barrier):
barrier.wait()
for _ in range(read_count):
for line in file:
self.assertEqual(line, "")
with self.open(os_helper.TESTFN, "w+") as f:
barrier = threading.Barrier(thread_count + 1)
reader = threading.Thread(target=reader, args=(f, barrier))
writers = [threading.Thread(target=writer, args=(f, barrier))
for _ in range(thread_count)]
with threading_helper.catch_threading_exception() as cm:
with threading_helper.start_threads(writers + [reader]):
pass
self.assertIsNone(cm.exc_type)
self.assertEqual(os.stat(os_helper.TESTFN).st_size,
write_count * thread_count)
class CIOTest(IOTest):
# TODO: RUSTPYTHON, cyclic gc
@unittest.expectedFailure
@unittest.expectedFailure # TODO: RUSTPYTHON; cyclic gc
def test_IOBase_finalize(self):
# Issue #12149: segmentation fault on _PyIOBase_finalize when both a
# class which inherits IOBase and an object of this class are caught
@@ -1071,10 +1092,9 @@ class CIOTest(IOTest):
support.gc_collect()
self.assertIsNone(wr(), wr)
# TODO: RUSTPYTHON, AssertionError: filter ('', ResourceWarning) did not catch any warning
@unittest.expectedFailure
@unittest.expectedFailure # TODO: RUSTPYTHON; AssertionError: filter ('', ResourceWarning) did not catch any warning
def test_destructor(self):
super().test_destructor(self)
return super().test_destructor()
@support.cpython_only
class TestIOCTypes(unittest.TestCase):
@@ -1165,9 +1185,32 @@ class TestIOCTypes(unittest.TestCase):
_io = self._io
support.check_disallow_instantiation(self, _io._BytesIOBuffer)
def test_stringio_setstate(self):
# gh-127182: Calling __setstate__() with invalid arguments must not crash
obj = self._io.StringIO()
with self.assertRaisesRegex(
TypeError,
'initial_value must be str or None, not int',
):
obj.__setstate__((1, '', 0, {}))
obj.__setstate__((None, '', 0, {})) # should not crash
self.assertEqual(obj.getvalue(), '')
obj.__setstate__(('', '', 0, {}))
self.assertEqual(obj.getvalue(), '')
class PyIOTest(IOTest):
pass
@unittest.expectedFailure # TODO: RUSTPYTHON; OSError: Negative file descriptor
def test_bad_opener_negative_1():
return super().test_bad_opener_negative_1()
@unittest.expectedFailure # TODO: RUSTPYTHON; OSError: Negative file descriptor
def test_bad_opener_other_negative():
return super().test_bad_opener_other_negative()
@support.cpython_only
class APIMismatchTest(unittest.TestCase):
@@ -1175,7 +1218,7 @@ class APIMismatchTest(unittest.TestCase):
def test_RawIOBase_io_in_pyio_match(self):
"""Test that pyio RawIOBase class has all c RawIOBase methods"""
mismatch = support.detect_api_mismatch(pyio.RawIOBase, io.RawIOBase,
ignore=('__weakref__',))
ignore=('__weakref__', '__static_attributes__'))
self.assertEqual(mismatch, set(), msg='Python RawIOBase does not have all C RawIOBase methods')
def test_RawIOBase_pyio_in_io_match(self):
@@ -1244,6 +1287,7 @@ class CommonBufferedTests:
# a ValueError.
self.assertRaises(ValueError, _with)
@unittest.expectedFailure # TODO: RUSTPYTHON
def test_error_through_destructor(self):
# Test that the exception state is not modified by a destructor,
# even if close() fails.
@@ -1252,10 +1296,7 @@ class CommonBufferedTests:
with self.assertRaises(AttributeError):
self.tp(rawio).xyzzy
if not IOBASE_EMITS_UNRAISABLE:
self.assertIsNone(cm.unraisable)
elif cm.unraisable is not None:
self.assertEqual(cm.unraisable.exc_type, OSError)
self.assertEqual(cm.unraisable.exc_type, OSError)
def test_repr(self):
raw = self.MockRawIO()
@@ -1271,11 +1312,9 @@ class CommonBufferedTests:
# Issue #25455
raw = self.MockRawIO()
b = self.tp(raw)
with support.swap_attr(raw, 'name', b):
try:
with support.swap_attr(raw, 'name', b), support.infinite_recursion(25):
with self.assertRaises(RuntimeError):
repr(b) # Should not crash
except RuntimeError:
pass
def test_flush_error_on_close(self):
# Test that buffered file is closed despite failed flush
@@ -1356,6 +1395,28 @@ class CommonBufferedTests:
with self.assertRaises(AttributeError):
buf.raw = x
def test_pickling_subclass(self):
global MyBufferedIO
class MyBufferedIO(self.tp):
def __init__(self, raw, tag):
super().__init__(raw)
self.tag = tag
def __getstate__(self):
return self.tag, self.raw.getvalue()
def __setstate__(slf, state):
tag, value = state
slf.__init__(self.BytesIO(value), tag)
raw = self.BytesIO(b'data')
buf = MyBufferedIO(raw, tag='ham')
for proto in range(pickle.HIGHEST_PROTOCOL + 1):
with self.subTest(protocol=proto):
pickled = pickle.dumps(buf, proto)
newbuf = pickle.loads(pickled)
self.assertEqual(newbuf.raw.getvalue(), b'data')
self.assertEqual(newbuf.tag, 'ham')
del MyBufferedIO
class SizeofTest:
@@ -1717,20 +1778,6 @@ class BufferedReaderTest(unittest.TestCase, CommonBufferedTests):
class CBufferedReaderTest(BufferedReaderTest, SizeofTest):
tp = io.BufferedReader
@unittest.skip("TODO: RUSTPYTHON, fallible allocation")
@skip_if_sanitizer(memory=True, address=True, thread=True,
reason="sanitizer defaults to crashing "
"instead of returning NULL for malloc failure.")
def test_constructor(self):
BufferedReaderTest.test_constructor(self)
# The allocation can succeed on 32-bit builds, e.g. with more
# than 2 GiB RAM and a 64-bit kernel.
if sys.maxsize > 0x7FFFFFFF:
rawio = self.MockRawIO()
bufio = self.tp(rawio)
self.assertRaises((OverflowError, MemoryError, ValueError),
bufio.__init__, rawio, sys.maxsize)
def test_initialization(self):
rawio = self.MockRawIO([b"abc"])
bufio = self.tp(rawio)
@@ -1748,8 +1795,7 @@ class CBufferedReaderTest(BufferedReaderTest, SizeofTest):
# checking this is not so easy.
self.assertRaises(OSError, bufio.read, 10)
# TODO: RUSTPYTHON
@unittest.expectedFailure
@unittest.expectedFailure # TODO: RUSTPYTHON
def test_garbage_collection(self):
# C BufferedReader objects are collected.
# The Python version has __del__, so it ends into gc.garbage instead
@@ -1766,40 +1812,44 @@ class CBufferedReaderTest(BufferedReaderTest, SizeofTest):
def test_args_error(self):
# Issue #17275
with self.assertRaisesRegex(TypeError, "BufferedReader"):
self.tp(io.BytesIO(), 1024, 1024, 1024)
self.tp(self.BytesIO(), 1024, 1024, 1024)
def test_bad_readinto_value(self):
rawio = io.BufferedReader(io.BytesIO(b"12"))
rawio = self.tp(self.BytesIO(b"12"))
rawio.readinto = lambda buf: -1
bufio = self.tp(rawio)
with self.assertRaises(OSError) as cm:
bufio.readline()
self.assertIsNone(cm.exception.__cause__)
# TODO: RUSTPYTHON, TypeError: 'bytes' object cannot be interpreted as an integer")
@unittest.expectedFailure
@unittest.expectedFailure # TODO: RUSTPYTHON; TypeError: 'bytes' object cannot be interpreted as an integer")
def test_bad_readinto_type(self):
rawio = io.BufferedReader(io.BytesIO(b"12"))
rawio = self.tp(self.BytesIO(b"12"))
rawio.readinto = lambda buf: b''
bufio = self.tp(rawio)
with self.assertRaises(OSError) as cm:
bufio.readline()
self.assertIsInstance(cm.exception.__cause__, TypeError)
# TODO: RUSTPYTHON
@unittest.expectedFailure
@unittest.expectedFailure # TODO: RUSTPYTHON
def test_flush_error_on_close(self):
super().test_flush_error_on_close()
return super().test_flush_error_on_close()
# TODO: RUSTPYTHON, AssertionError: UnsupportedOperation not raised by truncate
@unittest.expectedFailure
def test_truncate_on_read_only(self): # TODO: RUSTPYTHON, remove when this passes
super().test_truncate_on_read_only() # TODO: RUSTPYTHON, remove when this passes
# TODO: RUSTPYTHON
@unittest.expectedFailure
@unittest.expectedFailure # TODO: RUSTPYTHON
def test_seek_character_device_file(self):
super().test_seek_character_device_file()
return super().test_seek_character_device_file()
@unittest.expectedFailure # TODO: RUSTPYTHON; AssertionError: UnsupportedOperation not raised by truncate
def test_truncate_on_read_only(self):
return super().test_truncate_on_read_only()
@unittest.skip('TODO: RUSTPYTHON; fallible allocation')
def test_constructor(self):
return super().test_constructor()
@unittest.expectedFailure # TODO: RUSTPYTHON
def test_pickling_subclass(self):
return super().test_pickling_subclass()
class PyBufferedReaderTest(BufferedReaderTest):
@@ -1909,8 +1959,7 @@ class BufferedWriterTest(unittest.TestCase, CommonBufferedTests):
def test_writes_and_truncates(self):
self.check_writes(lambda bufio: bufio.truncate(bufio.tell()))
# TODO: RUSTPYTHON
@unittest.expectedFailure
@unittest.expectedFailure # TODO: RUSTPYTHON
def test_write_non_blocking(self):
raw = self.MockNonBlockWriterIO()
bufio = self.tp(raw, 8)
@@ -2107,20 +2156,6 @@ class BufferedWriterTest(unittest.TestCase, CommonBufferedTests):
class CBufferedWriterTest(BufferedWriterTest, SizeofTest):
tp = io.BufferedWriter
@unittest.skip("TODO: RUSTPYTHON, fallible allocation")
@skip_if_sanitizer(memory=True, address=True, thread=True,
reason="sanitizer defaults to crashing "
"instead of returning NULL for malloc failure.")
def test_constructor(self):
BufferedWriterTest.test_constructor(self)
# The allocation can succeed on 32-bit builds, e.g. with more
# than 2 GiB RAM and a 64-bit kernel.
if sys.maxsize > 0x7FFFFFFF:
rawio = self.MockRawIO()
bufio = self.tp(rawio)
self.assertRaises((OverflowError, MemoryError, ValueError),
bufio.__init__, rawio, sys.maxsize)
def test_initialization(self):
rawio = self.MockRawIO()
bufio = self.tp(rawio)
@@ -2131,8 +2166,7 @@ class CBufferedWriterTest(BufferedWriterTest, SizeofTest):
self.assertRaises(ValueError, bufio.__init__, rawio, buffer_size=-1)
self.assertRaises(ValueError, bufio.write, b"def")
# TODO: RUSTPYTHON
@unittest.expectedFailure
@unittest.expectedFailure # TODO: RUSTPYTHON
def test_garbage_collection(self):
# C BufferedWriter objects are collected, and collecting them flushes
# all data to disk.
@@ -2155,11 +2189,17 @@ class CBufferedWriterTest(BufferedWriterTest, SizeofTest):
with self.assertRaisesRegex(TypeError, "BufferedWriter"):
self.tp(self.BytesIO(), 1024, 1024, 1024)
# TODO: RUSTPYTHON
@unittest.expectedFailure
@unittest.expectedFailure # TODO: RUSTPYTHON
def test_flush_error_on_close(self):
super().test_flush_error_on_close()
return super().test_flush_error_on_close()
@unittest.skip('TODO: RUSTPYTHON; fallible allocation')
def test_constructor(self):
return super().test_constructor()
@unittest.expectedFailure # TODO: RUSTPYTHON
def test_pickling_subclass(self):
return super().test_pickling_subclass()
class PyBufferedWriterTest(BufferedWriterTest):
tp = pyio.BufferedWriter
@@ -2637,22 +2677,7 @@ class BufferedRandomTest(BufferedReaderTest, BufferedWriterTest):
class CBufferedRandomTest(BufferedRandomTest, SizeofTest):
tp = io.BufferedRandom
@unittest.skip("TODO: RUSTPYTHON, fallible allocation")
@skip_if_sanitizer(memory=True, address=True, thread=True,
reason="sanitizer defaults to crashing "
"instead of returning NULL for malloc failure.")
def test_constructor(self):
BufferedRandomTest.test_constructor(self)
# The allocation can succeed on 32-bit builds, e.g. with more
# than 2 GiB RAM and a 64-bit kernel.
if sys.maxsize > 0x7FFFFFFF:
rawio = self.MockRawIO()
bufio = self.tp(rawio)
self.assertRaises((OverflowError, MemoryError, ValueError),
bufio.__init__, rawio, sys.maxsize)
# TODO: RUSTPYTHON
@unittest.expectedFailure
@unittest.expectedFailure # TODO: RUSTPYTHON
def test_garbage_collection(self):
CBufferedReaderTest.test_garbage_collection(self)
CBufferedWriterTest.test_garbage_collection(self)
@@ -2662,20 +2687,25 @@ class CBufferedRandomTest(BufferedRandomTest, SizeofTest):
with self.assertRaisesRegex(TypeError, "BufferedRandom"):
self.tp(self.BytesIO(), 1024, 1024, 1024)
# TODO: RUSTPYTHON
@unittest.expectedFailure
@unittest.expectedFailure # TODO: RUSTPYTHON
def test_flush_error_on_close(self):
super().test_flush_error_on_close()
return super().test_flush_error_on_close()
# TODO: RUSTPYTHON
@unittest.expectedFailure
@unittest.expectedFailure # TODO: RUSTPYTHON
def test_seek_character_device_file(self):
super().test_seek_character_device_file()
return super().test_seek_character_device_file()
# TODO: RUSTPYTHON; f.read1(1) returns b'a'
@unittest.expectedFailure
@unittest.expectedFailure # TODO: RUSTPYTHON; f.read1(1) returns b'a'
def test_read1_after_write(self):
super().test_read1_after_write()
return super().test_read1_after_write()
@unittest.skip('TODO: RUSTPYTHON; fallible allocation')
def test_constructor(self):
return super().test_constructor()
@unittest.expectedFailure # TODO: RUSTPYTHON
def test_pickling_subclass(self):
return super().test_pickling_subclass()
class PyBufferedRandomTest(BufferedRandomTest):
@@ -2935,11 +2965,16 @@ class TextIOWrapperTest(unittest.TestCase):
# Issue #25455
raw = self.BytesIO()
t = self.TextIOWrapper(raw, encoding="utf-8")
with support.swap_attr(raw, 'name', t):
try:
with support.swap_attr(raw, 'name', t), support.infinite_recursion(25):
with self.assertRaises(RuntimeError):
repr(t) # Should not crash
except RuntimeError:
pass
def test_subclass_repr(self):
class TestSubclass(self.TextIOWrapper):
pass
f = TestSubclass(self.StringIO())
self.assertIn(TestSubclass.__name__, repr(f))
def test_line_buffering(self):
r = self.BytesIO()
@@ -3166,6 +3201,7 @@ class TextIOWrapperTest(unittest.TestCase):
support.gc_collect()
self.assertEqual(record, [1, 2, 3])
@unittest.expectedFailure # TODO: RUSTPYTHON
def test_error_through_destructor(self):
# Test that the exception state is not modified by a destructor,
# even if close() fails.
@@ -3174,10 +3210,7 @@ class TextIOWrapperTest(unittest.TestCase):
with self.assertRaises(AttributeError):
self.TextIOWrapper(rawio, encoding="utf-8").xyzzy
if not IOBASE_EMITS_UNRAISABLE:
self.assertIsNone(cm.unraisable)
elif cm.unraisable is not None:
self.assertEqual(cm.unraisable.exc_type, OSError)
self.assertEqual(cm.unraisable.exc_type, OSError)
# Systematic tests of the text I/O API
@@ -3327,8 +3360,7 @@ class TextIOWrapperTest(unittest.TestCase):
finally:
StatefulIncrementalDecoder.codecEnabled = 0
# TODO: RUSTPYTHON
@unittest.expectedFailure
@unittest.expectedFailure # TODO: RUSTPYTHON
def test_multibyte_seek_and_tell(self):
f = self.open(os_helper.TESTFN, "w", encoding="euc_jp")
f.write("AB\n\u3046\u3048\n")
@@ -3344,8 +3376,7 @@ class TextIOWrapperTest(unittest.TestCase):
self.assertEqual(f.tell(), p1)
f.close()
# TODO: RUSTPYTHON
@unittest.expectedFailure
@unittest.expectedFailure # TODO: RUSTPYTHON
def test_seek_with_encoder_state(self):
f = self.open(os_helper.TESTFN, "w", encoding="euc_jis_2004")
f.write("\u00e6\u0300")
@@ -3359,8 +3390,7 @@ class TextIOWrapperTest(unittest.TestCase):
self.assertEqual(f.readline(), "\u00e6\u0300\u0300")
f.close()
# TODO: RUSTPYTHON
@unittest.expectedFailure
@unittest.expectedFailure # TODO: RUSTPYTHON
def test_encoded_writes(self):
data = "1234567890"
tests = ("utf-16",
@@ -3499,8 +3529,7 @@ class TextIOWrapperTest(unittest.TestCase):
self.assertEqual(buffer.seekable(), txt.seekable())
# TODO: RUSTPYTHON
@unittest.expectedFailure
@unittest.expectedFailure # TODO: RUSTPYTHON
def test_append_bom(self):
# The BOM is not written again when appending to a non-empty file
filename = os_helper.TESTFN
@@ -3516,8 +3545,7 @@ class TextIOWrapperTest(unittest.TestCase):
with self.open(filename, 'rb') as f:
self.assertEqual(f.read(), 'aaaxxx'.encode(charset))
# TODO: RUSTPYTHON
@unittest.expectedFailure
@unittest.expectedFailure # TODO: RUSTPYTHON
def test_seek_bom(self):
# Same test, but when seeking manually
filename = os_helper.TESTFN
@@ -3533,8 +3561,7 @@ class TextIOWrapperTest(unittest.TestCase):
with self.open(filename, 'rb') as f:
self.assertEqual(f.read(), 'bbbzzz'.encode(charset))
# TODO: RUSTPYTHON
@unittest.expectedFailure
@unittest.expectedFailure # TODO: RUSTPYTHON
def test_seek_append_bom(self):
# Same test, but first seek to the start and then to the end
filename = os_helper.TESTFN
@@ -3795,17 +3822,14 @@ class TextIOWrapperTest(unittest.TestCase):
codecs.lookup('utf-8')
class C:
def __init__(self):
self.buf = io.BytesIO()
def __del__(self):
io.TextIOWrapper(self.buf, **{kwargs})
io.TextIOWrapper(io.BytesIO(), **{kwargs})
print("ok")
c = C()
""".format(iomod=iomod, kwargs=kwargs)
return assert_python_ok("-c", code)
# TODO: RUSTPYTHON
@unittest.expectedFailure
@unittest.expectedFailure # TODO: RUSTPYTHON
def test_create_at_shutdown_without_encoding(self):
rc, out, err = self._check_create_at_shutdown()
if err:
@@ -3815,8 +3839,7 @@ class TextIOWrapperTest(unittest.TestCase):
else:
self.assertEqual("ok", out.decode().strip())
# TODO: RUSTPYTHON
@unittest.expectedFailure
@unittest.expectedFailure # TODO: RUSTPYTHON
def test_create_at_shutdown_with_encoding(self):
rc, out, err = self._check_create_at_shutdown(encoding='utf-8',
errors='strict')
@@ -4042,6 +4065,28 @@ class TextIOWrapperTest(unittest.TestCase):
f.write(res)
self.assertEqual(res + f.readline(), 'foo\nbar\n')
def test_pickling_subclass(self):
global MyTextIO
class MyTextIO(self.TextIOWrapper):
def __init__(self, raw, tag):
super().__init__(raw)
self.tag = tag
def __getstate__(self):
return self.tag, self.buffer.getvalue()
def __setstate__(slf, state):
tag, value = state
slf.__init__(self.BytesIO(value), tag)
raw = self.BytesIO(b'data')
txt = MyTextIO(raw, 'ham')
for proto in range(pickle.HIGHEST_PROTOCOL + 1):
with self.subTest(protocol=proto):
pickled = pickle.dumps(txt, proto)
newtxt = pickle.loads(pickled)
self.assertEqual(newtxt.buffer.getvalue(), b'data')
self.assertEqual(newtxt.tag, 'ham')
del MyTextIO
class MemviewBytesIO(io.BytesIO):
'''A BytesIO object whose read method returns memoryviews
@@ -4066,98 +4111,7 @@ class CTextIOWrapperTest(TextIOWrapperTest):
io = io
shutdown_error = "LookupError: unknown encoding: ascii"
# TODO: RUSTPYTHON
@unittest.expectedFailure
def test_constructor(self):
super().test_constructor()
# TODO: RUSTPYTHON
@unittest.expectedFailure
def test_detach(self):
super().test_detach()
# TODO: RUSTPYTHON
@unittest.expectedFailure
def test_reconfigure_encoding_read(self):
super().test_reconfigure_encoding_read()
# TODO: RUSTPYTHON
@unittest.expectedFailure
def test_reconfigure_line_buffering(self):
super().test_reconfigure_line_buffering()
# TODO: RUSTPYTHON
@unittest.expectedFailure
def test_basic_io(self):
super().test_basic_io()
# TODO: RUSTPYTHON
@unittest.expectedFailure
def test_telling(self):
super().test_telling()
# TODO: RUSTPYTHON
@unittest.expectedFailure
def test_uninitialized(self):
super().test_uninitialized()
# TODO: RUSTPYTHON
@unittest.expectedFailure
def test_non_text_encoding_codecs_are_rejected(self):
super().test_non_text_encoding_codecs_are_rejected()
# TODO: RUSTPYTHON
@unittest.expectedFailure
def test_repr(self):
super().test_repr()
# TODO: RUSTPYTHON
@unittest.expectedFailure
def test_newlines(self):
super().test_newlines()
# TODO: RUSTPYTHON
@unittest.expectedFailure
def test_newlines_input(self):
super().test_newlines_input()
# TODO: RUSTPYTHON
@unittest.expectedFailure
def test_reconfigure_write_through(self):
super().test_reconfigure_write_through()
# TODO: RUSTPYTHON
@unittest.expectedFailure
def test_reconfigure_write_fromascii(self):
super().test_reconfigure_write_fromascii()
# TODO: RUSTPYTHON
@unittest.expectedFailure
def test_reconfigure_write(self):
super().test_reconfigure_write()
# TODO: RUSTPYTHON
@unittest.expectedFailure
def test_reconfigure_defaults(self):
super().test_reconfigure_defaults()
# TODO: RUSTPYTHON
@unittest.expectedFailure
def test_reconfigure_newline(self):
super().test_reconfigure_newline()
# TODO: RUSTPYTHON
@unittest.expectedFailure
def test_reconfigure_errors(self):
super().test_reconfigure_errors()
# TODO: RUSTPYTHON
@unittest.expectedFailure
def test_reconfigure_locale(self):
super().test_reconfigure_locale()
# TODO: RUSTPYTHON
@unittest.expectedFailure
@unittest.expectedFailure # TODO: RUSTPYTHON
def test_initialization(self):
r = self.BytesIO(b"\xc3\xa9\n\n")
b = self.BufferedReader(r, 1000)
@@ -4168,8 +4122,7 @@ class CTextIOWrapperTest(TextIOWrapperTest):
t = self.TextIOWrapper.__new__(self.TextIOWrapper)
self.assertRaises(Exception, repr, t)
# TODO: RUSTPYTHON
@unittest.expectedFailure
@unittest.expectedFailure # TODO: RUSTPYTHON
def test_garbage_collection(self):
# C TextIOWrapper objects are collected, and collecting them flushes
# all data to disk.
@@ -4233,20 +4186,121 @@ class CTextIOWrapperTest(TextIOWrapperTest):
t.write("x"*chunk_size)
self.assertEqual([b"abcdef", b"ghi", b"x"*chunk_size], buf._write_stack)
@unittest.expectedFailure # TODO: RUSTPYTHON
def test_issue119506(self):
chunk_size = 8192
class MockIO(self.MockRawIO):
written = False
def write(self, data):
if not self.written:
self.written = True
t.write("middle")
return super().write(data)
buf = MockIO()
t = self.TextIOWrapper(buf)
t.write("abc")
t.write("def")
# writing data which size >= chunk_size cause flushing buffer before write.
t.write("g" * chunk_size)
t.flush()
self.assertEqual([b"abcdef", b"middle", b"g"*chunk_size],
buf._write_stack)
@unittest.expectedFailure # TODO: RUSTPYTHON
def test_basic_io(self):
return super().test_basic_io()
@unittest.expectedFailure # TODO: RUSTPYTHON
def test_constructor(self):
return super().test_constructor()
@unittest.expectedFailure # TODO: RUSTPYTHON
def test_detach(self):
return super().test_detach()
@unittest.expectedFailure # TODO: RUSTPYTHON
def test_newlines(self):
return super().test_newlines()
@unittest.expectedFailure # TODO: RUSTPYTHON
def test_newlines_input(self):
return super().test_newlines_input()
@unittest.expectedFailure # TODO: RUSTPYTHON
def test_non_text_encoding_codecs_are_rejected(self):
return super().test_non_text_encoding_codecs_are_rejected()
@unittest.expectedFailure # TODO: RUSTPYTHON
def test_reconfigure_defaults(self):
return super().test_reconfigure_defaults()
@unittest.expectedFailure # TODO: RUSTPYTHON
def test_reconfigure_encoding_read(self):
return super().test_reconfigure_encoding_read()
@unittest.expectedFailure # TODO: RUSTPYTHON
def test_reconfigure_errors(self):
return super().test_reconfigure_errors()
@unittest.expectedFailure # TODO: RUSTPYTHON
def test_reconfigure_line_buffering(self):
return super().test_reconfigure_line_buffering()
@unittest.expectedFailure # TODO: RUSTPYTHON
def test_reconfigure_locale(self):
return super().test_reconfigure_locale()
@unittest.expectedFailure # TODO: RUSTPYTHON
def test_reconfigure_newline(self):
return super().test_reconfigure_newline()
@unittest.expectedFailure # TODO: RUSTPYTHON
def test_reconfigure_write(self):
return super().test_reconfigure_write()
@unittest.expectedFailure # TODO: RUSTPYTHON
def test_reconfigure_write_fromascii(self):
return super().test_reconfigure_write_fromascii()
@unittest.expectedFailure # TODO: RUSTPYTHON
def test_reconfigure_write_through(self):
return super().test_reconfigure_write_through()
@unittest.expectedFailure # TODO: RUSTPYTHON
def test_repr(self):
return super().test_repr()
@unittest.expectedFailure # TODO: RUSTPYTHON
def test_telling(self):
return super().test_telling()
@unittest.expectedFailure # TODO: RUSTPYTHON
def test_uninitialized(self):
return super().test_uninitialized()
@unittest.expectedFailure # TODO: RUSTPYTHON
def test_recursive_repr(self):
return super().test_recursive_repr()
@unittest.expectedFailure # TODO: RUSTPYTHON
def test_pickling_subclass(self):
return super().test_pickling_subclass()
class PyTextIOWrapperTest(TextIOWrapperTest):
io = pyio
shutdown_error = "LookupError: unknown encoding: ascii"
# TODO: RUSTPYTHON
@unittest.expectedFailure
@unittest.expectedFailure # TODO: RUSTPYTHON; AssertionError: ValueError not raised
def test_constructor(self):
super().test_constructor()
return super().test_constructor()
# TODO: RUSTPYTHON
@unittest.expectedFailure
@unittest.expectedFailure # TODO: RUSTPYTHON
def test_newlines(self):
super().test_newlines()
return super().test_newlines()
class IncrementalNewlineDecoderTest(unittest.TestCase):
@@ -4326,8 +4380,7 @@ class IncrementalNewlineDecoderTest(unittest.TestCase):
self.assertEqual(decoder.decode(input), "abc")
self.assertEqual(decoder.newlines, None)
# TODO: RUSTPYTHON
@unittest.expectedFailure
@unittest.expectedFailure # TODO: RUSTPYTHON
def test_newline_decoder(self):
encodings = (
# None meaning the IncrementalNewlineDecoder takes unicode input
@@ -4489,8 +4542,7 @@ class MiscIOTest(unittest.TestCase):
self.assertRaises(ValueError, f.writelines, [])
self.assertRaises(ValueError, next, f)
# TODO: RUSTPYTHON, cyclic gc
@unittest.expectedFailure
@unittest.expectedFailure # TODO: RUSTPYTHON; cyclic gc
def test_blockingioerror(self):
# Various BlockingIOError issues
class C(str):
@@ -4538,15 +4590,14 @@ class MiscIOTest(unittest.TestCase):
self._check_abc_inheritance(io)
def _check_warn_on_dealloc(self, *args, **kwargs):
f = open(*args, **kwargs)
f = self.open(*args, **kwargs)
r = repr(f)
with self.assertWarns(ResourceWarning) as cm:
f = None
support.gc_collect()
self.assertIn(r, str(cm.warning.args[0]))
# TODO: RUSTPYTHON
@unittest.expectedFailure
@unittest.expectedFailure # TODO: RUSTPYTHON
def test_warn_on_dealloc(self):
self._check_warn_on_dealloc(os_helper.TESTFN, "wb", buffering=0)
self._check_warn_on_dealloc(os_helper.TESTFN, "wb")
@@ -4569,10 +4620,9 @@ class MiscIOTest(unittest.TestCase):
r, w = os.pipe()
fds += r, w
with warnings_helper.check_no_resource_warning(self):
open(r, *args, closefd=False, **kwargs)
self.open(r, *args, closefd=False, **kwargs)
# TODO: RUSTPYTHON
@unittest.expectedFailure
@unittest.expectedFailure # TODO: RUSTPYTHON
@unittest.skipUnless(hasattr(os, "pipe"), "requires os.pipe()")
def test_warn_on_dealloc_fd(self):
self._check_warn_on_dealloc_fd("rb", buffering=0)
@@ -4602,16 +4652,14 @@ class MiscIOTest(unittest.TestCase):
with self.assertRaisesRegex(TypeError, msg):
pickle.dumps(f, protocol)
# TODO: RUSTPYTHON
@unittest.expectedFailure
@unittest.expectedFailure # TODO: RUSTPYTHON
@unittest.skipIf(
support.is_emscripten, "fstat() of a pipe fd is not supported"
)
def test_nonblock_pipe_write_bigbuf(self):
self._test_nonblock_pipe_write(16*1024)
# TODO: RUSTPYTHON
@unittest.expectedFailure
@unittest.expectedFailure # TODO: RUSTPYTHON
@unittest.skipIf(
support.is_emscripten, "fstat() of a pipe fd is not supported"
)
@@ -4733,8 +4781,7 @@ class MiscIOTest(unittest.TestCase):
proc = assert_python_failure('-X', 'dev', '-c', code)
self.assertEqual(proc.rc, 10, proc)
# TODO: RUSTPYTHON, AssertionError: 0 != 2
@unittest.expectedFailure
@unittest.expectedFailure # TODO: RUSTPYTHON; AssertionError: 0 != 2
def test_check_encoding_warning(self):
# PEP 597: Raise warning when encoding is not specified
# and sys.flags.warn_default_encoding is set.
@@ -4758,8 +4805,7 @@ class MiscIOTest(unittest.TestCase):
self.assertTrue(
warnings[1].startswith(b"<string>:8: EncodingWarning: "))
# TODO: RUSTPYTHON
@unittest.expectedFailure
@unittest.expectedFailure # TODO: RUSTPYTHON
def test_text_encoding(self):
# PEP 597, bpo-47000. io.text_encoding() returns "locale" or "utf-8"
# based on sys.flags.utf8_mode
@@ -4837,10 +4883,9 @@ class CMiscIOTest(MiscIOTest):
def test_daemon_threads_shutdown_stderr_deadlock(self):
self.check_daemon_threads_shutdown_deadlock('stderr')
# TODO: RUSTPYTHON, AssertionError: 22 != 10 : _PythonRunResult(rc=22, out=b'', err=b'')
@unittest.expectedFailure
def test_check_encoding_errors(self): # TODO: RUSTPYTHON, remove when this passes
super().test_check_encoding_errors() # TODO: RUSTPYTHON, remove when this passes
@unittest.expectedFailure # TODO: RUSTPYTHON; AssertionError: 22 != 10 : _PythonRunResult(rc=22, out=b'', err=b'')
def test_check_encoding_errors(self):
return super().test_check_encoding_errors()
class PyMiscIOTest(MiscIOTest):
@@ -5014,16 +5059,14 @@ class SignalsTest(unittest.TestCase):
os.close(w)
os.close(r)
# TODO: RUSTPYTHON
@unittest.expectedFailure
@unittest.expectedFailure # TODO: RUSTPYTHON
@requires_alarm
@support.requires_resource('walltime')
def test_interrupted_read_retry_buffered(self):
self.check_interrupted_read_retry(lambda x: x.decode('latin1'),
mode="rb")
# TODO: RUSTPYTHON
@unittest.expectedFailure
@unittest.expectedFailure # TODO: RUSTPYTHON
@requires_alarm
@support.requires_resource('walltime')
def test_interrupted_read_retry_text(self):
@@ -5098,15 +5141,13 @@ class SignalsTest(unittest.TestCase):
if e.errno != errno.EBADF:
raise
# TODO: RUSTPYTHON
@unittest.expectedFailure
@unittest.expectedFailure # TODO: RUSTPYTHON
@requires_alarm
@support.requires_resource('walltime')
def test_interrupted_write_retry_buffered(self):
self.check_interrupted_write_retry(b"x", mode="wb")
# TODO: RUSTPYTHON
@unittest.expectedFailure
@unittest.expectedFailure # TODO: RUSTPYTHON
@requires_alarm
@support.requires_resource('walltime')
def test_interrupted_write_retry_text(self):

View File

@@ -185,6 +185,21 @@ class TestCause(unittest.TestCase):
else:
self.fail("No exception raised")
@unittest.expectedFailure # TODO: RUSTPYTHON; TypeError: 'classmethod' object is not callable
def test_class_cause_nonexception_result(self):
class ConstructsNone(BaseException):
@classmethod
def __new__(*args, **kwargs):
return None
try:
raise IndexError from ConstructsNone
except TypeError as e:
self.assertIn("should have returned an instance of BaseException", str(e))
except IndexError:
self.fail("Wrong kind of exception raised")
else:
self.fail("No exception raised")
def test_instance_cause(self):
cause = KeyError()
try:
@@ -233,8 +248,7 @@ class TestTracebackType(unittest.TestCase):
def raiser(self):
raise ValueError
# TODO: RUSTPYTHON
@unittest.expectedFailure
@unittest.expectedFailure # TODO: RUSTPYTHON
def test_attrs(self):
try:
self.raiser()

View File

@@ -177,6 +177,57 @@ class ScopeTests(unittest.TestCase):
self.assertEqual(foo(a=42), 50)
self.assertEqual(foo(), 25)
def testCellIsArgAndEscapes(self):
# We need to be sure that a cell passed in as an arg still
# gets wrapped in a new cell if the arg escapes into an
# inner function (closure).
def external():
value = 42
def inner():
return value
cell, = inner.__closure__
return cell
cell_ext = external()
def spam(arg):
def eggs():
return arg
return eggs
eggs = spam(cell_ext)
cell_closure, = eggs.__closure__
cell_eggs = eggs()
self.assertIs(cell_eggs, cell_ext)
self.assertIsNot(cell_eggs, cell_closure)
def testCellIsLocalAndEscapes(self):
# We need to be sure that a cell bound to a local still
# gets wrapped in a new cell if the local escapes into an
# inner function (closure).
def external():
value = 42
def inner():
return value
cell, = inner.__closure__
return cell
cell_ext = external()
def spam(arg):
cell = arg
def eggs():
return cell
return eggs
eggs = spam(cell_ext)
cell_closure, = eggs.__closure__
cell_eggs = eggs()
self.assertIs(cell_eggs, cell_ext)
self.assertIsNot(cell_eggs, cell_closure)
def testRecursion(self):
def f(x):
@@ -641,10 +692,7 @@ class ScopeTests(unittest.TestCase):
self.assertEqual(c.dec(), 1)
self.assertEqual(c.dec(), 0)
# TODO: RUSTPYTHON, figure out how to communicate that `y = 9` should be
# stored as a global rather than a STORE_NAME, even when
# the `global y` is in a nested subscope
@unittest.expectedFailure
@unittest.expectedFailure # TODO: RUSTPYTHON; figure out how to communicate that `y = 9` should be stored as a global rather than a STORE_NAME, even when the `global y` is in a nested subscope
def testGlobalInParallelNestedFunctions(self):
# A symbol table bug leaked the global statement from one
# function to other nested functions in the same block.
@@ -763,6 +811,30 @@ class ScopeTests(unittest.TestCase):
gc_collect() # For PyPy or other GCs.
self.assertIsNone(ref())
def test_multiple_nesting(self):
# Regression test for https://github.com/python/cpython/issues/121863
class MultiplyNested:
def f1(self):
__arg = 1
class D:
def g(self, __arg):
return __arg
return D().g(_MultiplyNested__arg=2)
def f2(self):
__arg = 1
class D:
def g(self, __arg):
return __arg
return D().g
inst = MultiplyNested()
with self.assertRaises(TypeError):
inst.f1()
closure = inst.f2()
with self.assertRaises(TypeError):
closure(_MultiplyNested__arg=2)
if __name__ == '__main__':
unittest.main()

View File

@@ -196,6 +196,7 @@ class TestRmTree(BaseTest, unittest.TestCase):
self.assertIsInstance(victim, bytes)
shutil.rmtree(victim)
@unittest.skipIf(sys.platform == 'win32', 'TODO: RUSTPYTHON; flaky')
@os_helper.skip_unless_symlink
def test_rmtree_fails_on_symlink_onerror(self):
tmp = self.mkdtemp()
@@ -1477,7 +1478,7 @@ class TestCopy(BaseTest, unittest.TestCase):
finally:
shutil.rmtree(TESTFN, ignore_errors=True)
@unittest.expectedFailureIfWindows('TODO: RUSTPYTHON')
@unittest.expectedFailureIfWindows('TODO: RUSTPYTHON; AssertionError: SameFileError not raised for copyfile')
@os_helper.skip_unless_symlink
def test_dont_copy_file_onto_symlink_to_itself(self):
# bug 851123.

View File

@@ -286,8 +286,7 @@ class SliceTest(unittest.TestCase):
self.assertIsNot(s.stop, c.stop)
self.assertIsNot(s.step, c.step)
# TODO: RUSTPYTHON
@unittest.expectedFailure
@unittest.expectedFailure # TODO: RUSTPYTHON
def test_cycle(self):
class myobj(): pass
o = myobj()

View File

@@ -105,8 +105,7 @@ class TestLiterals(unittest.TestCase):
self.assertRaises(SyntaxError, eval, r""" '\U000000' """)
self.assertRaises(SyntaxError, eval, r""" '\U0000000' """)
# TODO: RUSTPYTHON
@unittest.expectedFailure
@unittest.expectedFailure # TODO: RUSTPYTHON
def test_eval_str_invalid_escape(self):
for b in range(1, 128):
if b in b"""\n\r"'01234567NU\\abfnrtuvx""":
@@ -145,8 +144,7 @@ class TestLiterals(unittest.TestCase):
self.assertRegex(str(w[0].message), 'invalid escape sequence')
self.assertEqual(w[0].filename, '<string>')
# TODO: RUSTPYTHON
@unittest.expectedFailure
@unittest.expectedFailure # TODO: RUSTPYTHON
def test_eval_str_invalid_octal_escape(self):
for i in range(0o400, 0o1000):
with self.assertWarns(SyntaxWarning):
@@ -172,8 +170,7 @@ class TestLiterals(unittest.TestCase):
self.assertEqual(exc.lineno, 2)
self.assertEqual(exc.offset, 1)
# TODO: RUSTPYTHON
@unittest.expectedFailure
@unittest.expectedFailure # TODO: RUSTPYTHON
def test_invalid_escape_locations_with_offset(self):
with warnings.catch_warnings(record=True) as w:
warnings.simplefilter('error', category=SyntaxWarning)
@@ -223,8 +220,7 @@ class TestLiterals(unittest.TestCase):
self.assertRaises(SyntaxError, eval, r""" b'\x' """)
self.assertRaises(SyntaxError, eval, r""" b'\x0' """)
# TODO: RUSTPYTHON
@unittest.expectedFailure
@unittest.expectedFailure # TODO: RUSTPYTHON
def test_eval_bytes_invalid_escape(self):
for b in range(1, 128):
if b in b"""\n\r"'01234567\\abfnrtvx""":
@@ -250,8 +246,7 @@ class TestLiterals(unittest.TestCase):
self.assertEqual(exc.filename, '<string>')
self.assertEqual(exc.lineno, 2)
# TODO: RUSTPYTHON
@unittest.expectedFailure
@unittest.expectedFailure # TODO: RUSTPYTHON
def test_eval_bytes_invalid_octal_escape(self):
for i in range(0o400, 0o1000):
with self.assertWarns(SyntaxWarning):

View File

@@ -146,7 +146,7 @@ class StrtodTests(unittest.TestCase):
digits *= 5
exponent -= 1
@unittest.skip("TODO: RUSTPYTHON, fails on debug mode, flaky in release mode")
@unittest.skip('TODO: RUSTPYTHON; fails on debug mode, flaky in release mode')
def test_halfway_cases(self):
# test halfway cases for the round-half-to-even rule
for i in range(100 * TEST_SIZE):
@@ -173,8 +173,7 @@ class StrtodTests(unittest.TestCase):
s = '{}e{}'.format(digits, exponent)
self.check_strtod(s)
# TODO: RUSTPYTHON
@unittest.expectedFailure
@unittest.expectedFailure # TODO: RUSTPYTHON
def test_boundaries(self):
# boundaries expressed as triples (n, e, u), where
# n*10**e is an approximation to the boundary value and
@@ -195,8 +194,7 @@ class StrtodTests(unittest.TestCase):
u *= 10
e -= 1
# TODO: RUSTPYTHON
@unittest.expectedFailure
@unittest.expectedFailure # TODO: RUSTPYTHON
def test_underflow_boundary(self):
# test values close to 2**-1075, the underflow boundary; similar
# to boundary_tests, except that the random error doesn't scale
@@ -208,8 +206,7 @@ class StrtodTests(unittest.TestCase):
s = '{}e{}'.format(digits, exponent)
self.check_strtod(s)
# TODO: RUSTPYTHON
@unittest.expectedFailure
@unittest.expectedFailure # TODO: RUSTPYTHON
def test_bigcomp(self):
for ndigs in 5, 10, 14, 15, 16, 17, 18, 19, 20, 40, 41, 50:
dig10 = 10**ndigs
@@ -219,8 +216,7 @@ class StrtodTests(unittest.TestCase):
s = '{}e{}'.format(digits, exponent)
self.check_strtod(s)
# TODO: RUSTPYTHON, Incorrectly rounded str->float conversion for -07e-321
@unittest.skip("TODO: RUSTPYTHON; flaky test")
@unittest.skip('TODO: RUSTPYTHON; flaky test')
def test_parsing(self):
# make '0' more likely to be chosen than other digits
digits = '000000123456789'
@@ -288,8 +284,7 @@ class StrtodTests(unittest.TestCase):
self.assertEqual(float(negative_exp(20000)), 1.0)
self.assertEqual(float(negative_exp(30000)), 1.0)
# TODO: RUSTPYTHON
@unittest.expectedFailure
@unittest.expectedFailure # TODO: RUSTPYTHON
def test_particular(self):
# inputs that produced crashes or incorrectly rounded results with
# previous versions of dtoa.c, for various reasons

View File

@@ -1,14 +1,11 @@
"""Do a minimal test of all the modules that aren't otherwise tested."""
import importlib
import platform
import sys
from test import support
from test.support import import_helper
from test.support import warnings_helper
import unittest
class TestUntestedModules(unittest.TestCase):
@unittest.expectedFailureIfWindows("TODO: RUSTPYTHON")
def test_untested_modules_can_be_imported(self):
untested = ('encodings',)
with warnings_helper.check_warnings(quiet=True):
@@ -21,31 +18,6 @@ class TestUntestedModules(unittest.TestCase):
self.fail('{} has tests even though test_sundry claims '
'otherwise'.format(name))
import distutils.bcppcompiler
import distutils.ccompiler
import distutils.cygwinccompiler
import distutils.filelist
import distutils.text_file
import distutils.unixccompiler
import distutils.command.bdist_dumb
if sys.platform.startswith('win') and not platform.win32_is_iot():
import distutils.command.bdist_msi
import distutils.command.bdist
import distutils.command.bdist_rpm
import distutils.command.build_clib
import distutils.command.build_ext
import distutils.command.build
import distutils.command.clean
import distutils.command.config
import distutils.command.install_data
import distutils.command.install_egg_info
import distutils.command.install_headers
import distutils.command.install_lib
import distutils.command.register
import distutils.command.sdist
import distutils.command.upload
import html.entities
try:
@@ -54,5 +26,6 @@ class TestUntestedModules(unittest.TestCase):
if support.verbose:
print("skipping tty")
if __name__ == "__main__":
unittest.main()

31
Lib/test/test_uuid.py vendored
View File

@@ -1,6 +1,7 @@
import unittest
from test import support
from test.support import import_helper
from test.support.script_helper import assert_python_ok
import builtins
import contextlib
import copy
@@ -32,8 +33,7 @@ def mock_get_command_stdout(data):
class BaseTestUUID:
uuid = None
# TODO: RUSTPYTHON
@unittest.expectedFailure
@unittest.expectedFailure # TODO: RUSTPYTHON
def test_safe_uuid_enum(self):
class CheckedSafeUUID(enum.Enum):
safe = 0
@@ -775,10 +775,37 @@ class BaseTestUUID:
class TestUUIDWithoutExtModule(BaseTestUUID, unittest.TestCase):
uuid = py_uuid
@unittest.skipUnless(c_uuid, 'requires the C _uuid module')
class TestUUIDWithExtModule(BaseTestUUID, unittest.TestCase):
uuid = c_uuid
def check_has_stable_libuuid_extractable_node(self):
if not self.uuid._has_stable_extractable_node:
self.skipTest("libuuid cannot deduce MAC address")
@unittest.skipUnless(os.name == 'posix', 'POSIX only')
def test_unix_getnode_from_libuuid(self):
self.check_has_stable_libuuid_extractable_node()
script = 'import uuid; print(uuid._unix_getnode())'
_, n_a, _ = assert_python_ok('-c', script)
_, n_b, _ = assert_python_ok('-c', script)
n_a, n_b = n_a.decode().strip(), n_b.decode().strip()
self.assertTrue(n_a.isdigit())
self.assertTrue(n_b.isdigit())
self.assertEqual(n_a, n_b)
@unittest.skipUnless(os.name == 'nt', 'Windows only')
def test_windows_getnode_from_libuuid(self):
self.check_has_stable_libuuid_extractable_node()
script = 'import uuid; print(uuid._windll_getnode())'
_, n_a, _ = assert_python_ok('-c', script)
_, n_b, _ = assert_python_ok('-c', script)
n_a, n_b = n_a.decode().strip(), n_b.decode().strip()
self.assertTrue(n_a.isdigit())
self.assertTrue(n_b.isdigit())
self.assertEqual(n_a, n_b)
class BaseTestInternals:
_uuid = py_uuid

View File

@@ -538,8 +538,7 @@ class TestPEP380Operation(unittest.TestCase):
"finishing g",
])
# TODO: RUSTPYTHON
@unittest.expectedFailure
@unittest.expectedFailure # TODO: RUSTPYTHON
def test_broken_getattr_handling(self):
"""
Test subiterator with a broken getattr implementation
@@ -787,8 +786,7 @@ class TestPEP380Operation(unittest.TestCase):
repr(value),
])
# TODO: RUSTPYTHON
@unittest.expectedFailure
@unittest.expectedFailure # TODO: RUSTPYTHON
def test_throwing_GeneratorExit_into_subgen_that_returns(self):
"""
Test throwing GeneratorExit into a subgenerator that
@@ -819,8 +817,7 @@ class TestPEP380Operation(unittest.TestCase):
"Enter f",
])
# TODO: RUSTPYTHON
@unittest.expectedFailure
@unittest.expectedFailure # TODO: RUSTPYTHON
def test_throwing_GeneratorExit_into_subgenerator_that_yields(self):
"""
Test throwing GeneratorExit into a subgenerator that
@@ -887,8 +884,7 @@ class TestPEP380Operation(unittest.TestCase):
yield from ()
self.assertRaises(StopIteration, next, g())
# TODO: RUSTPYTHON
@unittest.expectedFailure
@unittest.expectedFailure # TODO: RUSTPYTHON
def test_delegating_generators_claim_to_be_running(self):
# Check with basic iteration
def one():
@@ -904,6 +900,7 @@ class TestPEP380Operation(unittest.TestCase):
yield 2
g1 = one()
self.assertEqual(list(g1), [0, 1, 2, 3])
# Check with send
g1 = one()
res = [next(g1)]
@@ -913,6 +910,9 @@ class TestPEP380Operation(unittest.TestCase):
except StopIteration:
pass
self.assertEqual(res, [0, 1, 2, 3])
@unittest.expectedFailure # TODO: RUSTPYTHON; AssertionError: Lists differ: [0, 1, 2] != [0, 1, 2, 3]
def test_delegating_generators_claim_to_be_running_with_throw(self):
# Check with throw
class MyErr(Exception):
pass
@@ -949,8 +949,10 @@ class TestPEP380Operation(unittest.TestCase):
except:
self.assertEqual(res, [0, 1, 2, 3])
raise
def test_delegating_generators_claim_to_be_running_with_close(self):
# Check with close
class MyIt(object):
class MyIt:
def __iter__(self):
return self
def __next__(self):
@@ -1057,6 +1059,538 @@ class TestPEP380Operation(unittest.TestCase):
g.send((1, 2, 3, 4))
self.assertEqual(v, (1, 2, 3, 4))
class TestInterestingEdgeCases(unittest.TestCase):
def assert_stop_iteration(self, iterator):
with self.assertRaises(StopIteration) as caught:
next(iterator)
self.assertIsNone(caught.exception.value)
self.assertIsNone(caught.exception.__context__)
def assert_generator_raised_stop_iteration(self):
return self.assertRaisesRegex(RuntimeError, r"^generator raised StopIteration$")
def assert_generator_ignored_generator_exit(self):
return self.assertRaisesRegex(RuntimeError, r"^generator ignored GeneratorExit$")
@unittest.expectedFailure # TODO: RUSTPYTHON
def test_close_and_throw_work(self):
yielded_first = object()
yielded_second = object()
returned = object()
def inner():
yield yielded_first
yield yielded_second
return returned
def outer():
return (yield from inner())
with self.subTest("close"):
g = outer()
self.assertIs(next(g), yielded_first)
g.close()
self.assert_stop_iteration(g)
with self.subTest("throw GeneratorExit"):
g = outer()
self.assertIs(next(g), yielded_first)
thrown = GeneratorExit()
with self.assertRaises(GeneratorExit) as caught:
g.throw(thrown)
self.assertIs(caught.exception, thrown)
self.assertIsNone(caught.exception.__context__)
self.assert_stop_iteration(g)
with self.subTest("throw StopIteration"):
g = outer()
self.assertIs(next(g), yielded_first)
thrown = StopIteration()
# PEP 479:
with self.assert_generator_raised_stop_iteration() as caught:
g.throw(thrown)
self.assertIs(caught.exception.__context__, thrown)
self.assertIsNone(caught.exception.__context__.__context__)
self.assert_stop_iteration(g)
with self.subTest("throw BaseException"):
g = outer()
self.assertIs(next(g), yielded_first)
thrown = BaseException()
with self.assertRaises(BaseException) as caught:
g.throw(thrown)
self.assertIs(caught.exception, thrown)
self.assertIsNone(caught.exception.__context__)
self.assert_stop_iteration(g)
with self.subTest("throw Exception"):
g = outer()
self.assertIs(next(g), yielded_first)
thrown = Exception()
with self.assertRaises(Exception) as caught:
g.throw(thrown)
self.assertIs(caught.exception, thrown)
self.assertIsNone(caught.exception.__context__)
self.assert_stop_iteration(g)
@unittest.expectedFailure # TODO: RUSTPYTHON; AssertionError: GeneratorExit() is not GeneratorExit()
def test_close_and_throw_raise_generator_exit(self):
yielded_first = object()
yielded_second = object()
returned = object()
def inner():
try:
yield yielded_first
yield yielded_second
return returned
finally:
raise raised
def outer():
return (yield from inner())
with self.subTest("close"):
g = outer()
self.assertIs(next(g), yielded_first)
raised = GeneratorExit()
# GeneratorExit is suppressed. This is consistent with PEP 342:
# https://peps.python.org/pep-0342/#new-generator-method-close
g.close()
self.assert_stop_iteration(g)
with self.subTest("throw GeneratorExit"):
g = outer()
self.assertIs(next(g), yielded_first)
raised = GeneratorExit()
thrown = GeneratorExit()
with self.assertRaises(GeneratorExit) as caught:
g.throw(thrown)
# The raised GeneratorExit is suppressed, but the thrown one
# propagates. This is consistent with PEP 380:
# https://peps.python.org/pep-0380/#proposal
self.assertIs(caught.exception, thrown)
self.assertIsNone(caught.exception.__context__)
self.assert_stop_iteration(g)
with self.subTest("throw StopIteration"):
g = outer()
self.assertIs(next(g), yielded_first)
raised = GeneratorExit()
thrown = StopIteration()
with self.assertRaises(GeneratorExit) as caught:
g.throw(thrown)
self.assertIs(caught.exception, raised)
self.assertIs(caught.exception.__context__, thrown)
self.assertIsNone(caught.exception.__context__.__context__)
self.assert_stop_iteration(g)
with self.subTest("throw BaseException"):
g = outer()
self.assertIs(next(g), yielded_first)
raised = GeneratorExit()
thrown = BaseException()
with self.assertRaises(GeneratorExit) as caught:
g.throw(thrown)
self.assertIs(caught.exception, raised)
self.assertIs(caught.exception.__context__, thrown)
self.assertIsNone(caught.exception.__context__.__context__)
self.assert_stop_iteration(g)
with self.subTest("throw Exception"):
g = outer()
self.assertIs(next(g), yielded_first)
raised = GeneratorExit()
thrown = Exception()
with self.assertRaises(GeneratorExit) as caught:
g.throw(thrown)
self.assertIs(caught.exception, raised)
self.assertIs(caught.exception.__context__, thrown)
self.assertIsNone(caught.exception.__context__.__context__)
self.assert_stop_iteration(g)
@unittest.expectedFailure # TODO: RUSTPYTHON; AssertionError: RuntimeError not raised
def test_close_and_throw_raise_stop_iteration(self):
yielded_first = object()
yielded_second = object()
returned = object()
def inner():
try:
yield yielded_first
yield yielded_second
return returned
finally:
raise raised
def outer():
return (yield from inner())
with self.subTest("close"):
g = outer()
self.assertIs(next(g), yielded_first)
raised = StopIteration()
# PEP 479:
with self.assert_generator_raised_stop_iteration() as caught:
g.close()
self.assertIs(caught.exception.__context__, raised)
self.assertIsInstance(caught.exception.__context__.__context__, GeneratorExit)
self.assertIsNone(caught.exception.__context__.__context__.__context__)
self.assert_stop_iteration(g)
with self.subTest("throw GeneratorExit"):
g = outer()
self.assertIs(next(g), yielded_first)
raised = StopIteration()
thrown = GeneratorExit()
# PEP 479:
with self.assert_generator_raised_stop_iteration() as caught:
g.throw(thrown)
self.assertIs(caught.exception.__context__, raised)
# This isn't the same GeneratorExit as thrown! It's the one created
# by calling inner.close():
self.assertIsInstance(caught.exception.__context__.__context__, GeneratorExit)
self.assertIsNone(caught.exception.__context__.__context__.__context__)
self.assert_stop_iteration(g)
with self.subTest("throw StopIteration"):
g = outer()
self.assertIs(next(g), yielded_first)
raised = StopIteration()
thrown = StopIteration()
# PEP 479:
with self.assert_generator_raised_stop_iteration() as caught:
g.throw(thrown)
self.assertIs(caught.exception.__context__, raised)
self.assertIs(caught.exception.__context__.__context__, thrown)
self.assertIsNone(caught.exception.__context__.__context__.__context__)
self.assert_stop_iteration(g)
with self.subTest("throw BaseException"):
g = outer()
self.assertIs(next(g), yielded_first)
raised = StopIteration()
thrown = BaseException()
# PEP 479:
with self.assert_generator_raised_stop_iteration() as caught:
g.throw(thrown)
self.assertIs(caught.exception.__context__, raised)
self.assertIs(caught.exception.__context__.__context__, thrown)
self.assertIsNone(caught.exception.__context__.__context__.__context__)
self.assert_stop_iteration(g)
with self.subTest("throw Exception"):
g = outer()
self.assertIs(next(g), yielded_first)
raised = StopIteration()
thrown = Exception()
# PEP 479:
with self.assert_generator_raised_stop_iteration() as caught:
g.throw(thrown)
self.assertIs(caught.exception.__context__, raised)
self.assertIs(caught.exception.__context__.__context__, thrown)
self.assertIsNone(caught.exception.__context__.__context__.__context__)
self.assert_stop_iteration(g)
def test_close_and_throw_raise_base_exception(self):
yielded_first = object()
yielded_second = object()
returned = object()
def inner():
try:
yield yielded_first
yield yielded_second
return returned
finally:
raise raised
def outer():
return (yield from inner())
with self.subTest("close"):
g = outer()
self.assertIs(next(g), yielded_first)
raised = BaseException()
with self.assertRaises(BaseException) as caught:
g.close()
self.assertIs(caught.exception, raised)
self.assertIsInstance(caught.exception.__context__, GeneratorExit)
self.assertIsNone(caught.exception.__context__.__context__)
self.assert_stop_iteration(g)
with self.subTest("throw GeneratorExit"):
g = outer()
self.assertIs(next(g), yielded_first)
raised = BaseException()
thrown = GeneratorExit()
with self.assertRaises(BaseException) as caught:
g.throw(thrown)
self.assertIs(caught.exception, raised)
# This isn't the same GeneratorExit as thrown! It's the one created
# by calling inner.close():
self.assertIsInstance(caught.exception.__context__, GeneratorExit)
self.assertIsNone(caught.exception.__context__.__context__)
self.assert_stop_iteration(g)
with self.subTest("throw StopIteration"):
g = outer()
self.assertIs(next(g), yielded_first)
raised = BaseException()
thrown = StopIteration()
with self.assertRaises(BaseException) as caught:
g.throw(thrown)
self.assertIs(caught.exception, raised)
self.assertIs(caught.exception.__context__, thrown)
self.assertIsNone(caught.exception.__context__.__context__)
self.assert_stop_iteration(g)
with self.subTest("throw BaseException"):
g = outer()
self.assertIs(next(g), yielded_first)
raised = BaseException()
thrown = BaseException()
with self.assertRaises(BaseException) as caught:
g.throw(thrown)
self.assertIs(caught.exception, raised)
self.assertIs(caught.exception.__context__, thrown)
self.assertIsNone(caught.exception.__context__.__context__)
self.assert_stop_iteration(g)
with self.subTest("throw Exception"):
g = outer()
self.assertIs(next(g), yielded_first)
raised = BaseException()
thrown = Exception()
with self.assertRaises(BaseException) as caught:
g.throw(thrown)
self.assertIs(caught.exception, raised)
self.assertIs(caught.exception.__context__, thrown)
self.assertIsNone(caught.exception.__context__.__context__)
self.assert_stop_iteration(g)
def test_close_and_throw_raise_exception(self):
yielded_first = object()
yielded_second = object()
returned = object()
def inner():
try:
yield yielded_first
yield yielded_second
return returned
finally:
raise raised
def outer():
return (yield from inner())
with self.subTest("close"):
g = outer()
self.assertIs(next(g), yielded_first)
raised = Exception()
with self.assertRaises(Exception) as caught:
g.close()
self.assertIs(caught.exception, raised)
self.assertIsInstance(caught.exception.__context__, GeneratorExit)
self.assertIsNone(caught.exception.__context__.__context__)
self.assert_stop_iteration(g)
with self.subTest("throw GeneratorExit"):
g = outer()
self.assertIs(next(g), yielded_first)
raised = Exception()
thrown = GeneratorExit()
with self.assertRaises(Exception) as caught:
g.throw(thrown)
self.assertIs(caught.exception, raised)
# This isn't the same GeneratorExit as thrown! It's the one created
# by calling inner.close():
self.assertIsInstance(caught.exception.__context__, GeneratorExit)
self.assertIsNone(caught.exception.__context__.__context__)
self.assert_stop_iteration(g)
with self.subTest("throw StopIteration"):
g = outer()
self.assertIs(next(g), yielded_first)
raised = Exception()
thrown = StopIteration()
with self.assertRaises(Exception) as caught:
g.throw(thrown)
self.assertIs(caught.exception, raised)
self.assertIs(caught.exception.__context__, thrown)
self.assertIsNone(caught.exception.__context__.__context__)
self.assert_stop_iteration(g)
with self.subTest("throw BaseException"):
g = outer()
self.assertIs(next(g), yielded_first)
raised = Exception()
thrown = BaseException()
with self.assertRaises(Exception) as caught:
g.throw(thrown)
self.assertIs(caught.exception, raised)
self.assertIs(caught.exception.__context__, thrown)
self.assertIsNone(caught.exception.__context__.__context__)
self.assert_stop_iteration(g)
with self.subTest("throw Exception"):
g = outer()
self.assertIs(next(g), yielded_first)
raised = Exception()
thrown = Exception()
with self.assertRaises(Exception) as caught:
g.throw(thrown)
self.assertIs(caught.exception, raised)
self.assertIs(caught.exception.__context__, thrown)
self.assertIsNone(caught.exception.__context__.__context__)
self.assert_stop_iteration(g)
@unittest.expectedFailure # TODO: RUSTPYTHON; AssertionError: None is not StopIteration()
def test_close_and_throw_yield(self):
yielded_first = object()
yielded_second = object()
returned = object()
def inner():
try:
yield yielded_first
finally:
yield yielded_second
return returned
def outer():
return (yield from inner())
with self.subTest("close"):
g = outer()
self.assertIs(next(g), yielded_first)
# No chaining happens. This is consistent with PEP 342:
# https://peps.python.org/pep-0342/#new-generator-method-close
with self.assert_generator_ignored_generator_exit() as caught:
g.close()
self.assertIsNone(caught.exception.__context__)
self.assert_stop_iteration(g)
with self.subTest("throw GeneratorExit"):
g = outer()
self.assertIs(next(g), yielded_first)
thrown = GeneratorExit()
# No chaining happens. This is consistent with PEP 342:
# https://peps.python.org/pep-0342/#new-generator-method-close
with self.assert_generator_ignored_generator_exit() as caught:
g.throw(thrown)
self.assertIsNone(caught.exception.__context__)
self.assert_stop_iteration(g)
with self.subTest("throw StopIteration"):
g = outer()
self.assertIs(next(g), yielded_first)
thrown = StopIteration()
self.assertEqual(g.throw(thrown), yielded_second)
# PEP 479:
with self.assert_generator_raised_stop_iteration() as caught:
next(g)
self.assertIs(caught.exception.__context__, thrown)
self.assertIsNone(caught.exception.__context__.__context__)
self.assert_stop_iteration(g)
with self.subTest("throw BaseException"):
g = outer()
self.assertIs(next(g), yielded_first)
thrown = BaseException()
self.assertEqual(g.throw(thrown), yielded_second)
with self.assertRaises(BaseException) as caught:
next(g)
self.assertIs(caught.exception, thrown)
self.assertIsNone(caught.exception.__context__)
self.assert_stop_iteration(g)
with self.subTest("throw Exception"):
g = outer()
self.assertIs(next(g), yielded_first)
thrown = Exception()
self.assertEqual(g.throw(thrown), yielded_second)
with self.assertRaises(Exception) as caught:
next(g)
self.assertIs(caught.exception, thrown)
self.assertIsNone(caught.exception.__context__)
self.assert_stop_iteration(g)
@unittest.expectedFailure # TODO: RUSTPYTHON
def test_close_and_throw_return(self):
yielded_first = object()
yielded_second = object()
returned = object()
def inner():
try:
yield yielded_first
yield yielded_second
finally:
return returned
def outer():
return (yield from inner())
with self.subTest("close"):
g = outer()
self.assertIs(next(g), yielded_first)
# StopIteration is suppressed. This is consistent with PEP 342:
# https://peps.python.org/pep-0342/#new-generator-method-close
g.close()
self.assert_stop_iteration(g)
with self.subTest("throw GeneratorExit"):
g = outer()
self.assertIs(next(g), yielded_first)
thrown = GeneratorExit()
# StopIteration is suppressed. This is consistent with PEP 342:
# https://peps.python.org/pep-0342/#new-generator-method-close
with self.assertRaises(GeneratorExit) as caught:
g.throw(thrown)
self.assertIs(caught.exception, thrown)
self.assertIsNone(caught.exception.__context__)
self.assert_stop_iteration(g)
with self.subTest("throw StopIteration"):
g = outer()
self.assertIs(next(g), yielded_first)
thrown = StopIteration()
with self.assertRaises(StopIteration) as caught:
g.throw(thrown)
self.assertIs(caught.exception.value, returned)
self.assertIsNone(caught.exception.__context__)
self.assert_stop_iteration(g)
with self.subTest("throw BaseException"):
g = outer()
self.assertIs(next(g), yielded_first)
thrown = BaseException()
with self.assertRaises(StopIteration) as caught:
g.throw(thrown)
self.assertIs(caught.exception.value, returned)
self.assertIsNone(caught.exception.__context__)
self.assert_stop_iteration(g)
with self.subTest("throw Exception"):
g = outer()
self.assertIs(next(g), yielded_first)
thrown = Exception()
with self.assertRaises(StopIteration) as caught:
g.throw(thrown)
self.assertIs(caught.exception.value, returned)
self.assertIsNone(caught.exception.__context__)
self.assert_stop_iteration(g)
if __name__ == '__main__':
unittest.main()

20
Lib/uuid.py vendored
View File

@@ -572,39 +572,43 @@ def _netstat_getnode():
try:
import _uuid
_generate_time_safe = getattr(_uuid, "generate_time_safe", None)
_has_stable_extractable_node = getattr(_uuid, "has_stable_extractable_node", False)
_UuidCreate = getattr(_uuid, "UuidCreate", None)
except ImportError:
_uuid = None
_generate_time_safe = None
_has_stable_extractable_node = False
_UuidCreate = None
def _unix_getnode():
"""Get the hardware address on Unix using the _uuid extension module."""
if _generate_time_safe:
if _generate_time_safe and _has_stable_extractable_node:
uuid_time, _ = _generate_time_safe()
return UUID(bytes=uuid_time).node
def _windll_getnode():
"""Get the hardware address on Windows using the _uuid extension module."""
if _UuidCreate:
if _UuidCreate and _has_stable_extractable_node:
uuid_bytes = _UuidCreate()
return UUID(bytes_le=uuid_bytes).node
def _random_getnode():
"""Get a random node ID."""
# RFC 4122, $4.1.6 says "For systems with no IEEE address, a randomly or
# pseudo-randomly generated value may be used; see Section 4.5. The
# multicast bit must be set in such addresses, in order that they will
# never conflict with addresses obtained from network cards."
# RFC 9562, §6.10-3 says that
#
# Implementations MAY elect to obtain a 48-bit cryptographic-quality
# random number as per Section 6.9 to use as the Node ID. [...] [and]
# implementations MUST set the least significant bit of the first octet
# of the Node ID to 1. This bit is the unicast or multicast bit, which
# will never be set in IEEE 802 addresses obtained from network cards.
#
# The "multicast bit" of a MAC address is defined to be "the least
# significant bit of the first octet". This works out to be the 41st bit
# counting from 1 being the least significant bit, or 1<<40.
#
# See https://en.wikipedia.org/w/index.php?title=MAC_address&oldid=1128764812#Universal_vs._local_(U/L_bit)
import random
return random.getrandbits(48) | (1 << 40)
return int.from_bytes(os.urandom(6)) | (1 << 40)
# _OS_GETTERS, when known, are targeted for a specific OS or platform.

View File

@@ -1,17 +1,15 @@
use criterion::measurement::WallTime;
use criterion::{
Bencher, BenchmarkGroup, BenchmarkId, Criterion, Throughput, black_box, criterion_group,
criterion_main,
Bencher, BenchmarkGroup, BenchmarkId, Criterion, Throughput, criterion_group, criterion_main,
measurement::WallTime,
};
use rustpython_compiler::Mode;
use rustpython_vm::{Interpreter, PyResult, Settings};
use std::collections::HashMap;
use std::path::Path;
use std::{collections::HashMap, hint::black_box, path::Path};
fn bench_cpython_code(b: &mut Bencher, source: &str) {
let c_str_source_head = std::ffi::CString::new(source).unwrap();
let c_str_source = c_str_source_head.as_c_str();
pyo3::Python::with_gil(|py| {
pyo3::Python::attach(|py| {
b.iter(|| {
let module = pyo3::types::PyModule::from_code(py, c_str_source, c"", c"")
.expect("Error running source");
@@ -54,7 +52,7 @@ pub fn benchmark_file_parsing(group: &mut BenchmarkGroup<WallTime>, name: &str,
});
group.bench_function(BenchmarkId::new("cpython", name), |b| {
use pyo3::types::PyAnyMethods;
pyo3::Python::with_gil(|py| {
pyo3::Python::attach(|py| {
let builtins =
pyo3::types::PyModule::import(py, "builtins").expect("Failed to import builtins");
let compile = builtins.getattr("compile").expect("no compile in builtins");

View File

@@ -37,7 +37,7 @@ pub struct MicroBenchmark {
}
fn bench_cpython_code(group: &mut BenchmarkGroup<WallTime>, bench: &MicroBenchmark) {
pyo3::Python::with_gil(|py| {
pyo3::Python::attach(|py| {
let setup_name = format!("{}_setup", bench.name);
let setup_code = cpy_compile_code(py, &bench.setup, &setup_name).unwrap();

View File

@@ -10,6 +10,7 @@ license.workspace = true
[features]
threading = ["parking_lot"]
wasm_js = ["getrandom/wasm_js"]
[dependencies]
rustpython-literal = { workspace = true }
@@ -17,7 +18,6 @@ rustpython-wtf8 = { workspace = true }
ascii = { workspace = true }
bitflags = { workspace = true }
bstr = { workspace = true }
cfg-if = { workspace = true }
getrandom = { workspace = true }
itertools = { workspace = true }
@@ -25,7 +25,6 @@ libc = { workspace = true }
malachite-bigint = { workspace = true }
malachite-q = { workspace = true }
malachite-base = { workspace = true }
memchr = { workspace = true }
num-traits = { workspace = true }
once_cell = { workspace = true }
parking_lot = { workspace = true, optional = true }

View File

@@ -18,7 +18,6 @@ ruff_text_size = { workspace = true }
thiserror = { workspace = true }
[dev-dependencies]
rand = { workspace = true }
[lints]
workspace = true

View File

@@ -5,7 +5,7 @@ use rustpython_compiler_core::{
OneIndexed, SourceLocation,
bytecode::{
CodeFlags, CodeObject, CodeUnit, ConstantData, InstrDisplayContext, Instruction, Label,
OpArg,
OpArg, PyCodeLocationInfoKind,
},
};
@@ -72,6 +72,7 @@ pub struct InstructionInfo {
pub target: BlockIdx,
// pub range: TextRange,
pub location: SourceLocation,
// TODO: end_location for debug ranges
}
// spell-checker:ignore petgraph
@@ -199,6 +200,9 @@ impl CodeInfo {
locations.clear()
}
// Generate linetable from locations
let linetable = generate_linetable(&locations, first_line_number.get() as i32);
Ok(CodeObject {
flags,
posonlyarg_count,
@@ -218,6 +222,8 @@ impl CodeInfo {
cellvars: cellvar_cache.into_iter().collect(),
freevars: freevar_cache.into_iter().collect(),
cell2arg,
linetable,
exceptiontable: Box::new([]), // TODO: Generate actual exception table
})
}
@@ -388,3 +394,134 @@ fn iter_blocks(blocks: &[Block]) -> impl Iterator<Item = (BlockIdx, &Block)> + '
Some((idx, b))
})
}
/// Generate CPython 3.11+ format linetable from source locations
fn generate_linetable(locations: &[SourceLocation], first_line: i32) -> Box<[u8]> {
if locations.is_empty() {
return Box::new([]);
}
let mut linetable = Vec::new();
// Initialize prev_line to first_line
// The first entry's delta is relative to co_firstlineno
let mut prev_line = first_line;
let mut i = 0;
while i < locations.len() {
let loc = &locations[i];
// Count consecutive instructions with the same location
let mut length = 1;
while i + length < locations.len() && locations[i + length] == locations[i] {
length += 1;
}
// Process in chunks of up to 8 instructions
while length > 0 {
let entry_length = length.min(8);
// Get line and column information
// SourceLocation always has row and column (both are OneIndexed)
let line = loc.row.get() as i32;
let col = (loc.column.get() as i32) - 1; // Convert 1-based to 0-based
let line_delta = line - prev_line;
// Choose the appropriate encoding based on line delta and column info
// Note: SourceLocation always has valid column, so we never get NO_COLUMNS case
if line_delta == 0 {
let end_col = col; // Use same column for end (no range info available)
if col < 80 && end_col - col < 16 && end_col >= col {
// Short form (codes 0-9) for common cases
let code = (col / 8).min(9) as u8; // Short0 to Short9
linetable.push(0x80 | (code << 3) | ((entry_length - 1) as u8));
let col_byte = (((col % 8) as u8) << 4) | ((end_col - col) as u8 & 0xf);
linetable.push(col_byte);
} else if col < 128 && end_col < 128 {
// One-line form (code 10) for same line
linetable.push(
0x80 | ((PyCodeLocationInfoKind::OneLine0 as u8) << 3)
| ((entry_length - 1) as u8),
);
linetable.push(col as u8);
linetable.push(end_col as u8);
} else {
// Long form for columns >= 128
linetable.push(
0x80 | ((PyCodeLocationInfoKind::Long as u8) << 3)
| ((entry_length - 1) as u8),
);
write_signed_varint(&mut linetable, 0); // line_delta = 0
write_varint(&mut linetable, 0); // end_line delta = 0
write_varint(&mut linetable, (col as u32) + 1); // column + 1 for encoding
write_varint(&mut linetable, (end_col as u32) + 1); // end_col + 1
}
} else if line_delta > 0 && line_delta < 3
/* && column.is_some() */
{
// One-line form (codes 11-12) for line deltas 1-2
let end_col = col; // Use same column for end
if col < 128 && end_col < 128 {
let code = (PyCodeLocationInfoKind::OneLine0 as u8) + (line_delta as u8); // 11 for delta=1, 12 for delta=2
linetable.push(0x80 | (code << 3) | ((entry_length - 1) as u8));
linetable.push(col as u8);
linetable.push(end_col as u8);
} else {
// Long form for columns >= 128 or negative line delta
linetable.push(
0x80 | ((PyCodeLocationInfoKind::Long as u8) << 3)
| ((entry_length - 1) as u8),
);
write_signed_varint(&mut linetable, line_delta);
write_varint(&mut linetable, 0); // end_line delta = 0
write_varint(&mut linetable, (col as u32) + 1); // column + 1 for encoding
write_varint(&mut linetable, (end_col as u32) + 1); // end_col + 1
}
} else {
// Long form (code 14) for all other cases
// This handles: line_delta < 0, line_delta >= 3, or columns >= 128
let end_col = col; // Use same column for end
linetable.push(
0x80 | ((PyCodeLocationInfoKind::Long as u8) << 3) | ((entry_length - 1) as u8),
);
write_signed_varint(&mut linetable, line_delta);
write_varint(&mut linetable, 0); // end_line delta = 0
write_varint(&mut linetable, (col as u32) + 1); // column + 1 for encoding
write_varint(&mut linetable, (end_col as u32) + 1); // end_col + 1
}
prev_line = line;
length -= entry_length;
i += entry_length;
}
}
linetable.into_boxed_slice()
}
/// Write a variable-length unsigned integer (6-bit chunks)
/// Returns the number of bytes written
fn write_varint(buf: &mut Vec<u8>, mut val: u32) -> usize {
let start_len = buf.len();
while val >= 64 {
buf.push(0x40 | (val & 0x3f) as u8);
val >>= 6;
}
buf.push(val as u8);
buf.len() - start_len
}
/// Write a variable-length signed integer
/// Returns the number of bytes written
fn write_signed_varint(buf: &mut Vec<u8>, val: i32) -> usize {
let uval = if val < 0 {
// (unsigned int)(-val) has an undefined behavior for INT_MIN
// So we use (0 - val as u32) to handle it correctly
((0u32.wrapping_sub(val as u32)) << 1) | 1
} else {
(val as u32) << 1
};
write_varint(buf, uval)
}

View File

@@ -17,9 +17,8 @@ bitflags = { workspace = true }
itertools = { workspace = true }
malachite-bigint = { workspace = true }
num-complex = { workspace = true }
serde = { workspace = true, optional = true, default-features = false, features = ["derive"] }
lz4_flex = "0.11"
[lints]
workspace = true
workspace = true

View File

@@ -33,6 +33,75 @@ pub enum ResumeType {
AfterAwait = 3,
}
/// CPython 3.11+ linetable location info codes
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
#[repr(u8)]
pub enum PyCodeLocationInfoKind {
// Short forms are 0 to 9
Short0 = 0,
Short1 = 1,
Short2 = 2,
Short3 = 3,
Short4 = 4,
Short5 = 5,
Short6 = 6,
Short7 = 7,
Short8 = 8,
Short9 = 9,
// One line forms are 10 to 12
OneLine0 = 10,
OneLine1 = 11,
OneLine2 = 12,
NoColumns = 13,
Long = 14,
None = 15,
}
impl PyCodeLocationInfoKind {
pub fn from_code(code: u8) -> Option<Self> {
match code {
0 => Some(Self::Short0),
1 => Some(Self::Short1),
2 => Some(Self::Short2),
3 => Some(Self::Short3),
4 => Some(Self::Short4),
5 => Some(Self::Short5),
6 => Some(Self::Short6),
7 => Some(Self::Short7),
8 => Some(Self::Short8),
9 => Some(Self::Short9),
10 => Some(Self::OneLine0),
11 => Some(Self::OneLine1),
12 => Some(Self::OneLine2),
13 => Some(Self::NoColumns),
14 => Some(Self::Long),
15 => Some(Self::None),
_ => Option::None,
}
}
pub fn is_short(&self) -> bool {
(*self as u8) <= 9
}
pub fn short_column_group(&self) -> Option<u8> {
if self.is_short() {
Some(*self as u8)
} else {
Option::None
}
}
pub fn one_line_delta(&self) -> Option<i32> {
match self {
Self::OneLine0 => Some(0),
Self::OneLine1 => Some(1),
Self::OneLine2 => Some(2),
_ => Option::None,
}
}
}
pub trait Constant: Sized {
type Name: AsRef<str>;
@@ -129,23 +198,27 @@ pub struct CodeObject<C: Constant = ConstantData> {
pub instructions: Box<[CodeUnit]>,
pub locations: Box<[SourceLocation]>,
pub flags: CodeFlags,
/// Number of positional-only arguments
pub posonlyarg_count: u32,
// Number of positional-only arguments
pub arg_count: u32,
pub kwonlyarg_count: u32,
pub source_path: C::Name,
pub first_line_number: Option<OneIndexed>,
pub max_stackdepth: u32,
/// Name of the object that created this code object
pub obj_name: C::Name,
// Name of the object that created this code object
/// Qualified name of the object (like CPython's co_qualname)
pub qualname: C::Name,
// Qualified name of the object (like CPython's co_qualname)
pub cell2arg: Option<Box<[i32]>>,
pub constants: Box<[C]>,
pub names: Box<[C::Name]>,
pub varnames: Box<[C::Name]>,
pub cellvars: Box<[C::Name]>,
pub freevars: Box<[C::Name]>,
/// Line number table (CPython 3.11+ format)
pub linetable: Box<[u8]>,
/// Exception handling table
pub exceptiontable: Box<[u8]>,
}
bitflags! {
@@ -221,6 +294,12 @@ impl OpArg {
}
}
impl From<u32> for OpArg {
fn from(raw: u32) -> Self {
Self(raw)
}
}
#[derive(Default, Copy, Clone)]
#[repr(transparent)]
pub struct OpArgState {
@@ -1202,6 +1281,8 @@ impl<C: Constant> CodeObject<C> {
first_line_number: self.first_line_number,
max_stackdepth: self.max_stackdepth,
cell2arg: self.cell2arg,
linetable: self.linetable,
exceptiontable: self.exceptiontable,
}
}
@@ -1232,6 +1313,8 @@ impl<C: Constant> CodeObject<C> {
first_line_number: self.first_line_number,
max_stackdepth: self.max_stackdepth,
cell2arg: self.cell2arg.clone(),
linetable: self.linetable.clone(),
exceptiontable: self.exceptiontable.clone(),
}
}
}

View File

@@ -251,6 +251,16 @@ pub fn deserialize_code<R: Read, Bag: ConstantBag>(
let cellvars = read_names()?;
let freevars = read_names()?;
// Read linetable and exceptiontable
let linetable_len = rdr.read_u32()?;
let linetable = rdr.read_slice(linetable_len)?.to_vec().into_boxed_slice();
let exceptiontable_len = rdr.read_u32()?;
let exceptiontable = rdr
.read_slice(exceptiontable_len)?
.to_vec()
.into_boxed_slice();
Ok(CodeObject {
instructions,
locations,
@@ -269,6 +279,8 @@ pub fn deserialize_code<R: Read, Bag: ConstantBag>(
varnames,
cellvars,
freevars,
linetable,
exceptiontable,
})
}
@@ -684,4 +696,8 @@ pub fn serialize_code<W: Write, C: Constant>(buf: &mut W, code: &CodeObject<C>)
write_names(&code.varnames);
write_names(&code.cellvars);
write_names(&code.freevars);
// Serialize linetable and exceptiontable
write_vec(buf, &code.linetable);
write_vec(buf, &code.exceptiontable);
}

View File

@@ -14,8 +14,7 @@ proc-macro = true
[dependencies]
rustpython-compiler = { workspace = true }
rustpython-derive-impl = { workspace = true }
proc-macro2 = { workspace = true }
syn = { workspace = true }
[lints]
workspace = true
workspace = true

View File

@@ -22,7 +22,7 @@ tkinter = ["dep:tk-sys", "dep:tcl-sys"]
[dependencies]
# rustpython crates
rustpython-derive = { workspace = true }
rustpython-vm = { workspace = true, default-features = false }
rustpython-vm = { workspace = true, default-features = false, features = ["compiler"]}
rustpython-common = { workspace = true }
ahash = { workspace = true }
@@ -117,7 +117,6 @@ lzma-sys = "0.1"
xz2 = "0.1"
[target.'cfg(windows)'.dependencies]
junction = { workspace = true }
paste = { workspace = true }
schannel = { workspace = true }
widestring = { workspace = true }

View File

@@ -38,6 +38,7 @@ mod locale;
mod math;
#[cfg(unix)]
mod mmap;
mod opcode;
mod pyexpat;
mod pystruct;
mod random;
@@ -135,6 +136,7 @@ pub fn get_module_inits() -> impl Iterator<Item = (Cow<'static, str>, StdlibInit
"_json" => json::make_module,
"math" => math::make_module,
"pyexpat" => pyexpat::make_module,
"_opcode" => opcode::make_module,
"_random" => random::make_module,
"_statistics" => statistics::make_module,
"_struct" => pystruct::make_module,

282
stdlib/src/opcode.rs Normal file
View File

@@ -0,0 +1,282 @@
pub(crate) use opcode::make_module;
#[pymodule]
mod opcode {
use crate::vm::{
AsObject, PyObjectRef, PyResult, VirtualMachine,
builtins::{PyBool, PyInt, PyIntRef, PyNone},
bytecode::Instruction,
match_class,
};
use std::ops::Deref;
struct Opcode(Instruction);
impl Deref for Opcode {
type Target = Instruction;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl Opcode {
// https://github.com/python/cpython/blob/bcee1c322115c581da27600f2ae55e5439c027eb/Include/opcode_ids.h#L238
const HAVE_ARGUMENT: i32 = 44;
pub fn try_from_pyint(raw: PyIntRef, vm: &VirtualMachine) -> PyResult<Self> {
let instruction = raw
.try_to_primitive::<u8>(vm)
.and_then(|v| {
Instruction::try_from(v).map_err(|_| {
vm.new_exception_empty(vm.ctx.exceptions.value_error.to_owned())
})
})
.map_err(|_| vm.new_value_error("invalid opcode or oparg"))?;
Ok(Self(instruction))
}
/// https://github.com/python/cpython/blob/bcee1c322115c581da27600f2ae55e5439c027eb/Include/internal/pycore_opcode_metadata.h#L914-L916
#[must_use]
pub const fn is_valid(opcode: i32) -> bool {
opcode >= 0 && opcode < 268 && opcode != 255
}
// All `has_*` methods below mimics
// https://github.com/python/cpython/blob/bcee1c322115c581da27600f2ae55e5439c027eb/Include/internal/pycore_opcode_metadata.h#L966-L1190
#[must_use]
pub const fn has_arg(opcode: i32) -> bool {
Self::is_valid(opcode) && opcode > Self::HAVE_ARGUMENT
}
#[must_use]
pub const fn has_const(opcode: i32) -> bool {
Self::is_valid(opcode) && matches!(opcode, 83 | 103 | 240)
}
#[must_use]
pub const fn has_name(opcode: i32) -> bool {
Self::is_valid(opcode)
&& matches!(
opcode,
63 | 66
| 67
| 74
| 75
| 82
| 90
| 91
| 92
| 93
| 108
| 113
| 114
| 259
| 260
| 261
| 262
)
}
#[must_use]
pub const fn has_jump(opcode: i32) -> bool {
Self::is_valid(opcode)
&& matches!(
opcode,
72 | 77 | 78 | 79 | 97 | 98 | 99 | 100 | 104 | 256 | 257
)
}
#[must_use]
pub const fn has_free(opcode: i32) -> bool {
Self::is_valid(opcode) && matches!(opcode, 64 | 84 | 89 | 94 | 109)
}
#[must_use]
pub const fn has_local(opcode: i32) -> bool {
Self::is_valid(opcode)
&& matches!(opcode, 65 | 85 | 86 | 87 | 88 | 110 | 111 | 112 | 258 | 267)
}
#[must_use]
pub const fn has_exc(opcode: i32) -> bool {
Self::is_valid(opcode) && matches!(opcode, 264..=266)
}
}
#[pyattr]
const ENABLE_SPECIALIZATION: i8 = 1;
#[derive(FromArgs)]
struct StackEffectArgs {
#[pyarg(positional)]
opcode: PyIntRef,
#[pyarg(positional, optional)]
oparg: Option<PyObjectRef>,
#[pyarg(named, optional)]
jump: Option<PyObjectRef>,
}
#[pyfunction]
fn stack_effect(args: StackEffectArgs, vm: &VirtualMachine) -> PyResult<i32> {
let oparg = args
.oparg
.map(|v| {
if !v.fast_isinstance(vm.ctx.types.int_type) {
return Err(vm.new_type_error(format!(
"'{}' object cannot be interpreted as an integer",
v.class().name()
)));
}
v.downcast_ref::<PyInt>()
.ok_or_else(|| vm.new_type_error(""))?
.try_to_primitive::<u32>(vm)
})
.unwrap_or(Ok(0))?;
let jump = args
.jump
.map(|v| {
match_class!(match v {
b @ PyBool => Ok(b.is(&vm.ctx.true_value)),
_n @ PyNone => Ok(false),
_ => {
Err(vm.new_value_error("stack_effect: jump must be False, True or None"))
}
})
})
.unwrap_or(Ok(false))?;
let opcode = Opcode::try_from_pyint(args.opcode, vm)?;
Ok(opcode.stack_effect(oparg.into(), jump))
}
#[pyfunction]
fn is_valid(opcode: i32) -> bool {
Opcode::is_valid(opcode)
}
#[pyfunction]
fn has_arg(opcode: i32) -> bool {
Opcode::has_arg(opcode)
}
#[pyfunction]
fn has_const(opcode: i32) -> bool {
Opcode::has_const(opcode)
}
#[pyfunction]
fn has_name(opcode: i32) -> bool {
Opcode::has_name(opcode)
}
#[pyfunction]
fn has_jump(opcode: i32) -> bool {
Opcode::has_jump(opcode)
}
#[pyfunction]
fn has_free(opcode: i32) -> bool {
Opcode::has_free(opcode)
}
#[pyfunction]
fn has_local(opcode: i32) -> bool {
Opcode::has_local(opcode)
}
#[pyfunction]
fn has_exc(opcode: i32) -> bool {
Opcode::has_exc(opcode)
}
#[pyfunction]
fn get_intrinsic1_descs(vm: &VirtualMachine) -> Vec<PyObjectRef> {
[
"INTRINSIC_1_INVALID",
"INTRINSIC_PRINT",
"INTRINSIC_IMPORT_STAR",
"INTRINSIC_STOPITERATION_ERROR",
"INTRINSIC_ASYNC_GEN_WRAP",
"INTRINSIC_UNARY_POSITIVE",
"INTRINSIC_LIST_TO_TUPLE",
"INTRINSIC_TYPEVAR",
"INTRINSIC_PARAMSPEC",
"INTRINSIC_TYPEVARTUPLE",
"INTRINSIC_SUBSCRIPT_GENERIC",
"INTRINSIC_TYPEALIAS",
]
.into_iter()
.map(|x| vm.ctx.new_str(x).into())
.collect()
}
#[pyfunction]
fn get_intrinsic2_descs(vm: &VirtualMachine) -> Vec<PyObjectRef> {
[
"INTRINSIC_2_INVALID",
"INTRINSIC_PREP_RERAISE_STAR",
"INTRINSIC_TYPEVAR_WITH_BOUND",
"INTRINSIC_TYPEVAR_WITH_CONSTRAINTS",
"INTRINSIC_SET_FUNCTION_TYPE_PARAMS",
"INTRINSIC_SET_TYPEPARAM_DEFAULT",
]
.into_iter()
.map(|x| vm.ctx.new_str(x).into())
.collect()
}
#[pyfunction]
fn get_nb_ops(vm: &VirtualMachine) -> Vec<PyObjectRef> {
[
("NB_ADD", "+"),
("NB_AND", "&"),
("NB_FLOOR_DIVIDE", "//"),
("NB_LSHIFT", "<<"),
("NB_MATRIX_MULTIPLY", "@"),
("NB_MULTIPLY", "*"),
("NB_REMAINDER", "%"),
("NB_OR", "|"),
("NB_POWER", "**"),
("NB_RSHIFT", ">>"),
("NB_SUBTRACT", "-"),
("NB_TRUE_DIVIDE", "/"),
("NB_XOR", "^"),
("NB_INPLACE_ADD", "+="),
("NB_INPLACE_AND", "&="),
("NB_INPLACE_FLOOR_DIVIDE", "//="),
("NB_INPLACE_LSHIFT", "<<="),
("NB_INPLACE_MATRIX_MULTIPLY", "@="),
("NB_INPLACE_MULTIPLY", "*="),
("NB_INPLACE_REMAINDER", "%="),
("NB_INPLACE_OR", "|="),
("NB_INPLACE_POWER", "**="),
("NB_INPLACE_RSHIFT", ">>="),
("NB_INPLACE_SUBTRACT", "-="),
("NB_INPLACE_TRUE_DIVIDE", "/="),
("NB_INPLACE_XOR", "^="),
]
.into_iter()
.map(|(a, b)| {
vm.ctx
.new_tuple(vec![vm.ctx.new_str(a).into(), vm.ctx.new_str(b).into()])
.into()
})
.collect()
}
#[pyfunction]
fn get_executor(_code: PyObjectRef, vm: &VirtualMachine) -> PyResult<PyObjectRef> {
// TODO
Ok(vm.ctx.none())
}
#[pyfunction]
fn get_specialization_stats(vm: &VirtualMachine) -> PyObjectRef {
vm.ctx.none()
}
}

View File

@@ -30,4 +30,7 @@ mod _uuid {
fn has_uuid_generate_time_safe(_vm: &VirtualMachine) -> u32 {
0
}
#[pyattr(name = "has_stable_extractable_node")]
const HAS_STABLE_EXTRACTABLE_NODE: bool = false;
}

View File

@@ -24,7 +24,7 @@ ast = ["ruff_python_ast", "ruff_text_size"]
codegen = ["rustpython-codegen", "ast"]
parser = ["ast"]
serde = ["dep:serde"]
wasmbind = ["chrono/wasmbind", "getrandom/wasm_js", "wasm-bindgen"]
wasmbind = ["rustpython-common/wasm_js", "chrono/wasmbind", "wasm-bindgen"]
[dependencies]
rustpython-compiler = { workspace = true, optional = true }
@@ -56,7 +56,6 @@ itertools = { workspace = true }
is-macro = { workspace = true }
libc = { workspace = true }
log = { workspace = true }
nix = { workspace = true }
malachite-bigint = { workspace = true }
num-complex = { workspace = true }
num-integer = { workspace = true }
@@ -76,15 +75,13 @@ thread_local = { workspace = true }
memchr = { workspace = true }
caseless = "0.2.2"
flamer = { version = "0.4", optional = true }
flamer = { version = "0.5", optional = true }
half = "2"
memoffset = "0.9.1"
optional = { workspace = true }
result-like = "0.5.0"
timsort = "0.1.2"
## unicode stuff
unicode_names2 = { workspace = true }
# TODO: use unic for this; needed for title case:
# https://github.com/RustPython/RustPython/pull/832#discussion_r275428939
unicode-casing = { workspace = true }
@@ -95,6 +92,7 @@ unic-ucd-ident = { workspace = true }
[target.'cfg(unix)'.dependencies]
rustix = { workspace = true }
nix = { workspace = true }
exitcode = "1.1.2"
uname = "0.1.1"
@@ -113,7 +111,6 @@ num_cpus = "1.17.0"
[target.'cfg(windows)'.dependencies]
junction = { workspace = true }
schannel = { workspace = true }
winreg = "0.55"
[target.'cfg(windows)'.dependencies.windows]

View File

@@ -16,8 +16,125 @@ use crate::{
use malachite_bigint::BigInt;
use num_traits::Zero;
use rustpython_compiler_core::OneIndexed;
use rustpython_compiler_core::bytecode::PyCodeLocationInfoKind;
use std::{borrow::Borrow, fmt, ops::Deref};
/// State for iterating through code address ranges
struct PyCodeAddressRange<'a> {
ar_start: i32,
ar_end: i32,
ar_line: i32,
computed_line: i32,
reader: LineTableReader<'a>,
}
impl<'a> PyCodeAddressRange<'a> {
fn new(linetable: &'a [u8], first_line: i32) -> Self {
PyCodeAddressRange {
ar_start: 0,
ar_end: 0,
ar_line: -1,
computed_line: first_line,
reader: LineTableReader::new(linetable),
}
}
/// Check if this is a NO_LINE marker (code 15)
fn is_no_line_marker(byte: u8) -> bool {
(byte >> 3) == 0x1f
}
/// Advance to next address range
fn advance(&mut self) -> bool {
if self.reader.at_end() {
return false;
}
let first_byte = match self.reader.read_byte() {
Some(b) => b,
None => return false,
};
if (first_byte & 0x80) == 0 {
return false; // Invalid linetable
}
let code = (first_byte >> 3) & 0x0f;
let length = ((first_byte & 0x07) + 1) as i32;
// Get line delta for this entry
let line_delta = self.get_line_delta(code);
// Update computed line
self.computed_line += line_delta;
// Check for NO_LINE marker
if Self::is_no_line_marker(first_byte) {
self.ar_line = -1;
} else {
self.ar_line = self.computed_line;
}
// Update address range
self.ar_start = self.ar_end;
self.ar_end += length * 2; // sizeof(_Py_CODEUNIT) = 2
// Skip remaining bytes for this entry
while !self.reader.at_end() {
if let Some(b) = self.reader.peek_byte() {
if (b & 0x80) != 0 {
break;
}
self.reader.read_byte();
} else {
break;
}
}
true
}
fn get_line_delta(&mut self, code: u8) -> i32 {
let kind = match PyCodeLocationInfoKind::from_code(code) {
Some(k) => k,
None => return 0,
};
match kind {
PyCodeLocationInfoKind::None => 0, // NO_LINE marker
PyCodeLocationInfoKind::Long => {
let delta = self.reader.read_signed_varint();
// Skip end_line, col, end_col
self.reader.read_varint();
self.reader.read_varint();
self.reader.read_varint();
delta
}
PyCodeLocationInfoKind::NoColumns => self.reader.read_signed_varint(),
PyCodeLocationInfoKind::OneLine0 => {
self.reader.read_byte(); // Skip column
self.reader.read_byte(); // Skip end column
0
}
PyCodeLocationInfoKind::OneLine1 => {
self.reader.read_byte(); // Skip column
self.reader.read_byte(); // Skip end column
1
}
PyCodeLocationInfoKind::OneLine2 => {
self.reader.read_byte(); // Skip column
self.reader.read_byte(); // Skip end column
2
}
_ if kind.is_short() => {
self.reader.read_byte(); // Skip column byte
0
}
_ => 0,
}
}
}
#[derive(FromArgs)]
pub struct ReplaceArgs {
#[pyarg(named, optional)]
@@ -40,6 +157,22 @@ pub struct ReplaceArgs {
co_flags: OptionalArg<u16>,
#[pyarg(named, optional)]
co_varnames: OptionalArg<Vec<PyObjectRef>>,
#[pyarg(named, optional)]
co_nlocals: OptionalArg<u32>,
#[pyarg(named, optional)]
co_stacksize: OptionalArg<u32>,
#[pyarg(named, optional)]
co_code: OptionalArg<crate::builtins::PyBytesRef>,
#[pyarg(named, optional)]
co_linetable: OptionalArg<crate::builtins::PyBytesRef>,
#[pyarg(named, optional)]
co_exceptiontable: OptionalArg<crate::builtins::PyBytesRef>,
#[pyarg(named, optional)]
co_freevars: OptionalArg<Vec<PyObjectRef>>,
#[pyarg(named, optional)]
co_cellvars: OptionalArg<Vec<PyObjectRef>>,
#[pyarg(named, optional)]
co_qualname: OptionalArg<PyStrRef>,
}
#[derive(Clone)]
@@ -350,6 +483,211 @@ impl PyCode {
vm.ctx.new_tuple(names)
}
#[pygetset]
pub fn co_linetable(&self, vm: &VirtualMachine) -> crate::builtins::PyBytesRef {
// Return the actual linetable from the code object
vm.ctx.new_bytes(self.code.linetable.to_vec())
}
#[pygetset]
pub fn co_exceptiontable(&self, vm: &VirtualMachine) -> crate::builtins::PyBytesRef {
// Return the actual exception table from the code object
vm.ctx.new_bytes(self.code.exceptiontable.to_vec())
}
#[pymethod]
pub fn co_lines(&self, vm: &VirtualMachine) -> PyResult<PyObjectRef> {
// TODO: Implement lazy iterator (lineiterator) like CPython for better performance
// Currently returns eager list for simplicity
// Return an iterator over (start_offset, end_offset, lineno) tuples
let linetable = self.code.linetable.as_ref();
let mut lines = Vec::new();
if !linetable.is_empty() {
let first_line = self.code.first_line_number.map_or(0, |n| n.get() as i32);
let mut range = PyCodeAddressRange::new(linetable, first_line);
// Process all address ranges and merge consecutive entries with same line
let mut pending_entry: Option<(i32, i32, i32)> = None;
while range.advance() {
let start = range.ar_start;
let end = range.ar_end;
let line = range.ar_line;
if let Some((prev_start, _, prev_line)) = pending_entry {
if prev_line == line {
// Same line, extend the range
pending_entry = Some((prev_start, end, prev_line));
} else {
// Different line, emit the previous entry
let tuple = if prev_line == -1 {
vm.ctx.new_tuple(vec![
vm.ctx.new_int(prev_start).into(),
vm.ctx.new_int(start).into(),
vm.ctx.none(),
])
} else {
vm.ctx.new_tuple(vec![
vm.ctx.new_int(prev_start).into(),
vm.ctx.new_int(start).into(),
vm.ctx.new_int(prev_line).into(),
])
};
lines.push(tuple.into());
pending_entry = Some((start, end, line));
}
} else {
// First entry
pending_entry = Some((start, end, line));
}
}
// Emit the last pending entry
if let Some((start, end, line)) = pending_entry {
let tuple = if line == -1 {
vm.ctx.new_tuple(vec![
vm.ctx.new_int(start).into(),
vm.ctx.new_int(end).into(),
vm.ctx.none(),
])
} else {
vm.ctx.new_tuple(vec![
vm.ctx.new_int(start).into(),
vm.ctx.new_int(end).into(),
vm.ctx.new_int(line).into(),
])
};
lines.push(tuple.into());
}
}
let list = vm.ctx.new_list(lines);
vm.call_method(list.as_object(), "__iter__", ())
}
#[pymethod]
pub fn co_positions(&self, vm: &VirtualMachine) -> PyResult<PyObjectRef> {
// Return an iterator over (line, end_line, column, end_column) tuples for each instruction
let linetable = self.code.linetable.as_ref();
let mut positions = Vec::new();
if !linetable.is_empty() {
let mut reader = LineTableReader::new(linetable);
let mut line = self.code.first_line_number.map_or(0, |n| n.get() as i32);
while !reader.at_end() {
let first_byte = match reader.read_byte() {
Some(b) => b,
None => break,
};
if (first_byte & 0x80) == 0 {
break; // Invalid linetable
}
let code = (first_byte >> 3) & 0x0f;
let length = ((first_byte & 0x07) + 1) as i32;
let kind = match PyCodeLocationInfoKind::from_code(code) {
Some(k) => k,
None => break, // Invalid code
};
let (line_delta, end_line_delta, column, end_column): (
i32,
i32,
Option<i32>,
Option<i32>,
) = match kind {
PyCodeLocationInfoKind::None => {
// No location - all values are None
(0, 0, None, None)
}
PyCodeLocationInfoKind::Long => {
// Long form
let delta = reader.read_signed_varint();
let end_line_delta = reader.read_varint() as i32;
let col = reader.read_varint();
let column = if col == 0 {
None
} else {
Some((col - 1) as i32)
};
let end_col = reader.read_varint();
let end_column = if end_col == 0 {
None
} else {
Some((end_col - 1) as i32)
};
// endline = line + end_line_delta (will be computed after line update)
(delta, end_line_delta, column, end_column)
}
PyCodeLocationInfoKind::NoColumns => {
// No column form
let delta = reader.read_signed_varint();
(delta, 0, None, None) // endline will be same as line (delta = 0)
}
PyCodeLocationInfoKind::OneLine0
| PyCodeLocationInfoKind::OneLine1
| PyCodeLocationInfoKind::OneLine2 => {
// One-line form - endline = line
let col = reader.read_byte().unwrap_or(0) as i32;
let end_col = reader.read_byte().unwrap_or(0) as i32;
let delta = kind.one_line_delta().unwrap_or(0);
(delta, 0, Some(col), Some(end_col)) // endline = line (delta = 0)
}
_ if kind.is_short() => {
// Short form - endline = line
let col_data = reader.read_byte().unwrap_or(0);
let col_group = kind.short_column_group().unwrap_or(0);
let col = ((col_group as i32) << 3) | ((col_data >> 4) as i32);
let end_col = col + (col_data & 0x0f) as i32;
(0, 0, Some(col), Some(end_col)) // endline = line (delta = 0)
}
_ => (0, 0, None, None),
};
// Update line number
line += line_delta;
// Generate position tuples for each instruction covered by this entry
for _ in 0..length {
// Handle special case for no location (code 15)
let final_line = if kind == PyCodeLocationInfoKind::None {
None
} else {
Some(line)
};
let final_endline = if kind == PyCodeLocationInfoKind::None {
None
} else {
Some(line + end_line_delta)
};
// Convert Option to PyObject (None or int)
let line_obj = final_line.to_pyobject(vm);
let end_line_obj = final_endline.to_pyobject(vm);
let column_obj = column.to_pyobject(vm);
let end_column_obj = end_column.to_pyobject(vm);
let tuple =
vm.ctx
.new_tuple(vec![line_obj, end_line_obj, column_obj, end_column_obj]);
positions.push(tuple.into());
}
}
}
let list = vm.ctx.new_list(positions);
vm.call_method(list.as_object(), "__iter__", ())
}
#[pymethod]
pub fn replace(&self, args: ReplaceArgs, vm: &VirtualMachine) -> PyResult<Self> {
let posonlyarg_count = match args.co_posonlyargcount {
@@ -408,6 +746,66 @@ impl PyCode {
OptionalArg::Missing => self.code.varnames.iter().map(|s| s.to_object()).collect(),
};
let qualname = match args.co_qualname {
OptionalArg::Present(qualname) => qualname,
OptionalArg::Missing => self.code.qualname.to_owned(),
};
let max_stackdepth = match args.co_stacksize {
OptionalArg::Present(stacksize) => stacksize,
OptionalArg::Missing => self.code.max_stackdepth,
};
let instructions = match args.co_code {
OptionalArg::Present(_code_bytes) => {
// Convert bytes back to instructions
// For now, keep the original instructions
// TODO: Properly parse bytecode from bytes
self.code.instructions.clone()
}
OptionalArg::Missing => self.code.instructions.clone(),
};
let cellvars = match args.co_cellvars {
OptionalArg::Present(cellvars) => cellvars
.into_iter()
.map(|o| o.as_interned_str(vm).unwrap())
.collect(),
OptionalArg::Missing => self.code.cellvars.clone(),
};
let freevars = match args.co_freevars {
OptionalArg::Present(freevars) => freevars
.into_iter()
.map(|o| o.as_interned_str(vm).unwrap())
.collect(),
OptionalArg::Missing => self.code.freevars.clone(),
};
// Validate co_nlocals if provided
if let OptionalArg::Present(nlocals) = args.co_nlocals
&& nlocals as usize != varnames.len()
{
return Err(vm.new_value_error(format!(
"co_nlocals ({}) != len(co_varnames) ({})",
nlocals,
varnames.len()
)));
}
// Handle linetable and exceptiontable
let linetable = match args.co_linetable {
OptionalArg::Present(linetable) => linetable.as_bytes().to_vec().into_boxed_slice(),
OptionalArg::Missing => self.code.linetable.clone(),
};
let exceptiontable = match args.co_exceptiontable {
OptionalArg::Present(exceptiontable) => {
exceptiontable.as_bytes().to_vec().into_boxed_slice()
}
OptionalArg::Missing => self.code.exceptiontable.clone(),
};
Ok(Self {
code: CodeObject {
flags: CodeFlags::from_bits_truncate(flags),
@@ -417,10 +815,10 @@ impl PyCode {
source_path: source_path.as_object().as_interned_str(vm).unwrap(),
first_line_number,
obj_name: obj_name.as_object().as_interned_str(vm).unwrap(),
qualname: self.code.qualname,
qualname: qualname.as_object().as_interned_str(vm).unwrap(),
max_stackdepth: self.code.max_stackdepth,
instructions: self.code.instructions.clone(),
max_stackdepth,
instructions,
locations: self.code.locations.clone(),
constants: constants.into_iter().map(Literal).collect(),
names: names
@@ -431,12 +829,23 @@ impl PyCode {
.into_iter()
.map(|o| o.as_interned_str(vm).unwrap())
.collect(),
cellvars: self.code.cellvars.clone(),
freevars: self.code.freevars.clone(),
cellvars,
freevars,
cell2arg: self.code.cell2arg.clone(),
linetable,
exceptiontable,
},
})
}
#[pymethod]
fn _varname_from_oparg(&self, opcode: i32, vm: &VirtualMachine) -> PyResult<PyObjectRef> {
let idx_err = |vm: &VirtualMachine| vm.new_index_error("tuple index out of range");
let idx = usize::try_from(opcode).map_err(|_| idx_err(vm))?;
let name = self.code.varnames.get(idx).ok_or_else(|| idx_err(vm))?;
Ok(name.to_object())
}
}
impl fmt::Display for PyCode {
@@ -457,6 +866,69 @@ impl ToPyObject for bytecode::CodeObject {
}
}
// Helper struct for reading linetable
struct LineTableReader<'a> {
data: &'a [u8],
pos: usize,
}
impl<'a> LineTableReader<'a> {
fn new(data: &'a [u8]) -> Self {
Self { data, pos: 0 }
}
fn read_byte(&mut self) -> Option<u8> {
if self.pos < self.data.len() {
let byte = self.data[self.pos];
self.pos += 1;
Some(byte)
} else {
None
}
}
fn peek_byte(&self) -> Option<u8> {
if self.pos < self.data.len() {
Some(self.data[self.pos])
} else {
None
}
}
fn read_varint(&mut self) -> u32 {
if let Some(first) = self.read_byte() {
let mut val = (first & 0x3f) as u32;
let mut shift = 0;
let mut byte = first;
while (byte & 0x40) != 0 {
if let Some(next) = self.read_byte() {
shift += 6;
val |= ((next & 0x3f) as u32) << shift;
byte = next;
} else {
break;
}
}
val
} else {
0
}
}
fn read_signed_varint(&mut self) -> i32 {
let uval = self.read_varint();
if uval & 1 != 0 {
-((uval >> 1) as i32)
} else {
(uval >> 1) as i32
}
}
fn at_end(&self) -> bool {
self.pos >= self.data.len()
}
}
pub fn init(ctx: &Context) {
PyCode::extend_class(ctx, ctx.types.code_type);
}

View File

@@ -21,7 +21,7 @@
"css-loader": "^7.1.2",
"html-webpack-plugin": "^5.6.3",
"mini-css-extract-plugin": "^2.9.2",
"serve": "^14.2.4",
"serve": "^14.2.5",
"webpack": "^5.97.1",
"webpack-cli": "^6.0.1",
"webpack-dev-server": "^5.2.1"
@@ -1542,24 +1542,65 @@
}
},
"node_modules/compression": {
"version": "1.7.4",
"resolved": "https://registry.npmjs.org/compression/-/compression-1.7.4.tgz",
"integrity": "sha512-jaSIDzP9pZVS4ZfQ+TzvtiWhdpFhE2RDHz8QJkpX9SIpLq88VueF5jJw6t+6CUQcAoA6t+x89MLrWAqpfDE8iQ==",
"version": "1.8.1",
"resolved": "https://registry.npmjs.org/compression/-/compression-1.8.1.tgz",
"integrity": "sha512-9mAqGPHLakhCLeNyxPkK4xVo746zQ/czLH1Ky+vkitMnWfWZps8r0qXuwhwizagCRttsL4lfG4pIOvaWLpAP0w==",
"dev": true,
"license": "MIT",
"dependencies": {
"accepts": "~1.3.5",
"bytes": "3.0.0",
"compressible": "~2.0.16",
"bytes": "3.1.2",
"compressible": "~2.0.18",
"debug": "2.6.9",
"on-headers": "~1.0.2",
"safe-buffer": "5.1.2",
"negotiator": "~0.6.4",
"on-headers": "~1.1.0",
"safe-buffer": "5.2.1",
"vary": "~1.1.2"
},
"engines": {
"node": ">= 0.8.0"
}
},
"node_modules/compression/node_modules/bytes": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz",
"integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">= 0.8"
}
},
"node_modules/compression/node_modules/negotiator": {
"version": "0.6.4",
"resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.4.tgz",
"integrity": "sha512-myRT3DiWPHqho5PrJaIRyaMv2kgYf0mUVgBNOYMuCH5Ki1yEiQaf/ZJuQ62nvpc44wL5WDbTX7yGJi1Neevw8w==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">= 0.6"
}
},
"node_modules/compression/node_modules/safe-buffer": {
"version": "5.2.1",
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
"integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
"dev": true,
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/feross"
},
{
"type": "patreon",
"url": "https://www.patreon.com/feross"
},
{
"type": "consulting",
"url": "https://feross.org/support"
}
],
"license": "MIT"
},
"node_modules/concat-map": {
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
@@ -3540,9 +3581,9 @@
}
},
"node_modules/on-headers": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.2.tgz",
"integrity": "sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==",
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.1.0.tgz",
"integrity": "sha512-737ZY3yNnXy37FHkQxPzt4UZ2UWPWiCZWLvFZ4fu5cueciegX0zGPnrlY6bwRg4FdQOe9YU8MkmJwGhoMybl8A==",
"dev": true,
"license": "MIT",
"engines": {
@@ -4310,9 +4351,9 @@
}
},
"node_modules/serve": {
"version": "14.2.4",
"resolved": "https://registry.npmjs.org/serve/-/serve-14.2.4.tgz",
"integrity": "sha512-qy1S34PJ/fcY8gjVGszDB3EXiPSk5FKhUa7tQe0UPRddxRidc2V6cNHPNewbE1D7MAkgLuWEt3Vw56vYy73tzQ==",
"version": "14.2.5",
"resolved": "https://registry.npmjs.org/serve/-/serve-14.2.5.tgz",
"integrity": "sha512-Qn/qMkzCcMFVPb60E/hQy+iRLpiU8PamOfOSYoAHmmF+fFFmpPpqa6Oci2iWYpTdOUM3VF+TINud7CfbQnsZbA==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -4323,7 +4364,7 @@
"chalk": "5.0.1",
"chalk-template": "0.4.0",
"clipboardy": "3.0.0",
"compression": "1.7.4",
"compression": "1.8.1",
"is-port-reachable": "4.0.0",
"serve-handler": "6.1.6",
"update-check": "1.5.4"

View File

@@ -16,7 +16,7 @@
"css-loader": "^7.1.2",
"html-webpack-plugin": "^5.6.3",
"mini-css-extract-plugin": "^2.9.2",
"serve": "^14.2.4",
"serve": "^14.2.5",
"webpack": "^5.97.1",
"webpack-cli": "^6.0.1",
"webpack-dev-server": "^5.2.1"

View File

@@ -1,5 +1,2 @@
[build]
target = "wasm32-unknown-unknown"
[target.wasm32-unknown-unknown]
rustflags = ["--cfg=getrandom_backend=\"wasm_js\""]