diff --git a/.coderabbit.yml b/.coderabbit.yml
new file mode 100644
index 0000000000..6a96844e23
--- /dev/null
+++ b/.coderabbit.yml
@@ -0,0 +1,3 @@
+reviews:
+ path_filters:
+ - "!Lib/**"
diff --git a/.cspell.dict/cpython.txt b/.cspell.dict/cpython.txt
index d28a4bb8c5..48059cf4e4 100644
--- a/.cspell.dict/cpython.txt
+++ b/.cspell.dict/cpython.txt
@@ -45,7 +45,9 @@ SA_ONSTACK
stackdepth
stringlib
structseq
+subparams
tok_oldval
+tvars
unaryop
unparse
unparser
diff --git a/.cspell.dict/python-more.txt b/.cspell.dict/python-more.txt
index 0404428324..8e1c012838 100644
--- a/.cspell.dict/python-more.txt
+++ b/.cspell.dict/python-more.txt
@@ -17,6 +17,7 @@ basicsize
bdfl
bigcharset
bignum
+bivariant
breakpointhook
cformat
chunksize
@@ -77,12 +78,14 @@ getfilesystemencodeerrors
getfilesystemencoding
getformat
getframe
+getframemodulename
getnewargs
getpip
getrandom
getrecursionlimit
getrefcount
getsizeof
+getswitchinterval
getweakrefcount
getweakrefs
getwindowsversion
@@ -166,13 +169,17 @@ pycs
pyexpat
PYTHONBREAKPOINT
PYTHONDEBUG
+PYTHONDONTWRITEBYTECODE
PYTHONHASHSEED
PYTHONHOME
PYTHONINSPECT
+PYTHONINTMAXSTRDIGITS
+PYTHONNOUSERSITE
PYTHONOPTIMIZE
PYTHONPATH
PYTHONPATH
PYTHONSAFEPATH
+PYTHONUNBUFFERED
PYTHONVERBOSE
PYTHONWARNDEFAULTENCODING
PYTHONWARNINGS
@@ -205,6 +212,7 @@ seennl
setattro
setcomp
setrecursionlimit
+setswitchinterval
showwarnmsg
signum
slotnames
diff --git a/.cspell.dict/rust-more.txt b/.cspell.dict/rust-more.txt
index 6a98daa9db..6f89fdfafe 100644
--- a/.cspell.dict/rust-more.txt
+++ b/.cspell.dict/rust-more.txt
@@ -3,8 +3,10 @@ arrayvec
bidi
biguint
bindgen
+bitand
bitflags
bitor
+bitxor
bstr
byteorder
byteset
@@ -15,6 +17,7 @@ cranelift
cstring
datelike
deserializer
+deserializers
fdiv
flamescope
flate2
@@ -31,6 +34,7 @@ keccak
lalrpop
lexopt
libc
+libcall
libloading
libz
longlong
diff --git a/.cspell.json b/.cspell.json
index 98a03180fe..9f88a74f96 100644
--- a/.cspell.json
+++ b/.cspell.json
@@ -42,6 +42,7 @@
],
"ignorePaths": [
"**/__pycache__/**",
+ "target/**",
"Lib/**"
],
// words - list of words to be always considered correct
@@ -59,6 +60,7 @@
"dedentations",
"dedents",
"deduped",
+ "downcastable",
"downcasted",
"dumpable",
"emscripten",
@@ -67,6 +69,8 @@
"GetSet",
"groupref",
"internable",
+ "jitted",
+ "jitting",
"lossily",
"makeunicodedata",
"miri",
@@ -85,6 +89,7 @@
"pygetset",
"pyimpl",
"pylib",
+ "pymath",
"pymember",
"PyMethod",
"PyModule",
@@ -130,6 +135,8 @@
// win32
"birthtime",
"IFEXEC",
+ // "stat"
+ "FIRMLINK"
],
// flagWords - list of words to be always considered incorrect
"flagWords": [
diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile
new file mode 100644
index 0000000000..339cdb69bb
--- /dev/null
+++ b/.devcontainer/Dockerfile
@@ -0,0 +1,6 @@
+FROM mcr.microsoft.com/vscode/devcontainers/rust:1-bullseye
+
+# Install clang
+RUN apt-get update \
+ && apt-get install -y clang \
+ && rm -rf /var/lib/apt/lists/*
diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json
index d60eee2130..8838cf6a96 100644
--- a/.devcontainer/devcontainer.json
+++ b/.devcontainer/devcontainer.json
@@ -1,4 +1,25 @@
{
- "image": "mcr.microsoft.com/devcontainers/base:jammy",
- "onCreateCommand": "curl https://sh.rustup.rs -sSf | sh -s -- -y"
-}
\ No newline at end of file
+ "name": "Rust",
+ "build": {
+ "dockerfile": "Dockerfile"
+ },
+ "runArgs": ["--cap-add=SYS_PTRACE", "--security-opt", "seccomp=unconfined"],
+ "customizations": {
+ "vscode": {
+ "settings": {
+ "lldb.executable": "/usr/bin/lldb",
+ // VS Code don't watch files under ./target
+ "files.watcherExclude": {
+ "**/target/**": true
+ },
+ "extensions": [
+ "rust-lang.rust-analyzer",
+ "tamasfe.even-better-toml",
+ "vadimcn.vscode-lldb",
+ "mutantdino.resourcemonitor"
+ ]
+ }
+ }
+ },
+ "remoteUser": "vscode"
+}
diff --git a/.gemini/config.yaml b/.gemini/config.yaml
new file mode 100644
index 0000000000..76afe53388
--- /dev/null
+++ b/.gemini/config.yaml
@@ -0,0 +1,2 @@
+ignore_patterns:
+ - "Lib/**"
diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md
index 2991e3c626..e175cd5184 100644
--- a/.github/copilot-instructions.md
+++ b/.github/copilot-instructions.md
@@ -21,7 +21,7 @@ RustPython is a Python 3 interpreter written in Rust, implementing Python 3.13.0
- `parser/` - Parser for converting Python source to AST
- `core/` - Bytecode representation in Rust structures
- `codegen/` - AST to bytecode compiler
-- `Lib/` - CPython's standard library in Python (copied from CPython)
+- `Lib/` - CPython's standard library in Python (copied from CPython). **IMPORTANT**: Do not edit this directory directly; The only allowed operation is copying files from CPython.
- `derive/` - Rust macros for RustPython
- `common/` - Common utilities
- `extra_tests/` - Integration tests and snippets
@@ -84,7 +84,11 @@ cd extra_tests
pytest -v
# Run the Python test module
-cargo run --release -- -m test
+cargo run --release -- -m test ${TEST_MODULE}
+cargo run --release -- -m test test_unicode # to test test_unicode.py
+
+# Run the Python test module with specific function
+cargo run --release -- -m test test_unicode -k test_unicode_escape
```
### Determining What to Implement
@@ -96,12 +100,13 @@ Run `./whats_left.py` to get a list of unimplemented methods, which is helpful w
### Rust Code
- Follow the default rustfmt code style (`cargo fmt` to format)
-- Use clippy to lint code (`cargo clippy`)
+- **IMPORTANT**: Always run clippy to lint code (`cargo clippy`) before completing tasks. Fix any warnings or lints that are introduced by your changes
- Follow Rust best practices for error handling and memory management
- Use the macro system (`pyclass`, `pymodule`, `pyfunction`, etc.) when implementing Python functionality in Rust
### Python Code
+- **IMPORTANT**: In most cases, Python code should not be edited. Bug fixes should be made through Rust code modifications only
- Follow PEP 8 style for custom Python code
- Use ruff for linting Python code
- Minimize modifications to CPython standard library files
@@ -178,6 +183,27 @@ cargo run --features jit
cargo run --features ssl
```
+## Test Code Modification Rules
+
+**CRITICAL: Test code modification restrictions**
+- NEVER comment out or delete any test code lines except for removing `@unittest.expectedFailure` decorators and upper TODO comments
+- NEVER modify test assertions, test logic, or test data
+- When a test cannot pass due to missing language features, keep it as expectedFailure and document the reason
+- The only acceptable modifications to test files are:
+ 1. Removing `@unittest.expectedFailure` decorators and the upper TODO comments when tests actually pass
+ 2. Adding `@unittest.expectedFailure` decorators when tests cannot be fixed
+
+**Examples of FORBIDDEN modifications:**
+- Commenting out test lines
+- Changing test assertions
+- Modifying test data or expected results
+- Removing test logic
+
+**Correct approach when tests fail due to unsupported syntax:**
+- Keep the test as `@unittest.expectedFailure`
+- Document that it requires PEP 695 support
+- Focus on tests that can be fixed through Rust code changes only
+
## Documentation
- Check the [architecture document](architecture/architecture.md) for a high-level overview
diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml
index e00ad26f2f..89148beea4 100644
--- a/.github/workflows/ci.yaml
+++ b/.github/workflows/ci.yaml
@@ -32,11 +32,6 @@ env:
test_pathlib
test_posixpath
test_venv
- # configparser: https://github.com/RustPython/RustPython/issues/4995#issuecomment-1582397417
- # socketserver: seems related to configparser crash.
- MACOS_SKIPS: >-
- test_configparser
- test_socketserver
# PLATFORM_INDEPENDENT_TESTS are tests that do not depend on the underlying OS. They are currently
# only run on Linux to speed up the CI.
PLATFORM_INDEPENDENT_TESTS: >-
@@ -118,6 +113,7 @@ jobs:
RUST_BACKTRACE: full
name: Run rust tests
runs-on: ${{ matrix.os }}
+ timeout-minutes: ${{ contains(matrix.os, 'windows') && 45 || 30 }}
strategy:
matrix:
os: [macos-latest, ubuntu-latest, windows-latest]
@@ -180,6 +176,7 @@ jobs:
if: ${{ !contains(github.event.pull_request.labels.*.name, 'skip:ci') }}
name: Ensure compilation on various targets
runs-on: ubuntu-latest
+ timeout-minutes: 30
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
@@ -228,13 +225,13 @@ jobs:
- name: Check compilation for freeBSD
run: cargo check --target x86_64-unknown-freebsd
- - name: Prepare repository for redox compilation
- run: bash scripts/redox/uncomment-cargo.sh
- - name: Check compilation for Redox
- uses: coolreader18/redoxer-action@v1
- with:
- command: check
- args: --ignore-rust-version
+ # - name: Prepare repository for redox compilation
+ # run: bash scripts/redox/uncomment-cargo.sh
+ # - name: Check compilation for Redox
+ # uses: coolreader18/redoxer-action@v1
+ # with:
+ # command: check
+ # args: --ignore-rust-version
snippets_cpython:
if: ${{ !contains(github.event.pull_request.labels.*.name, 'skip:ci') }}
@@ -242,6 +239,7 @@ jobs:
RUST_BACKTRACE: full
name: Run snippets and cpython tests
runs-on: ${{ matrix.os }}
+ timeout-minutes: ${{ contains(matrix.os, 'windows') && 45 || 30 }}
strategy:
matrix:
os: [macos-latest, ubuntu-latest, windows-latest]
@@ -284,7 +282,7 @@ jobs:
run: target/release/rustpython -m test -j 1 -u all --slowest --fail-env-changed -v -x ${{ env.PLATFORM_INDEPENDENT_TESTS }}
- if: runner.os == 'macOS'
name: run cpython platform-dependent tests (MacOS)
- run: target/release/rustpython -m test -j 1 --slowest --fail-env-changed -v -x ${{ env.PLATFORM_INDEPENDENT_TESTS }} ${{ env.MACOS_SKIPS }}
+ run: target/release/rustpython -m test -j 1 --slowest --fail-env-changed -v -x ${{ env.PLATFORM_INDEPENDENT_TESTS }}
- if: runner.os == 'Windows'
name: run cpython platform-dependent tests (windows partial - fixme)
run:
@@ -327,8 +325,10 @@ jobs:
run: python -m pip install ruff==0.11.8
- name: Ensure docs generate no warnings
run: cargo doc
- - name: run python lint
- run: ruff check
+ - name: run ruff check
+ run: ruff check --diff
+ - name: run ruff format
+ run: ruff format --check
- name: install prettier
run: yarn global add prettier && echo "$(yarn global bin)" >>$GITHUB_PATH
- name: check wasm code with prettier
@@ -338,7 +338,7 @@ jobs:
- name: install extra dictionaries
run: npm install @cspell/dict-en_us @cspell/dict-cpp @cspell/dict-python @cspell/dict-rust @cspell/dict-win32 @cspell/dict-shell
- name: spell checker
- uses: streetsidesoftware/cspell-action@v6
+ uses: streetsidesoftware/cspell-action@v7
with:
files: '**/*.rs'
incremental_files_only: true
@@ -347,6 +347,7 @@ jobs:
if: ${{ !contains(github.event.pull_request.labels.*.name, 'skip:ci') }}
name: Run tests under miri
runs-on: ubuntu-latest
+ timeout-minutes: 30
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@master
@@ -364,6 +365,7 @@ jobs:
if: ${{ !contains(github.event.pull_request.labels.*.name, 'skip:ci') }}
name: Check the WASM package and demo
runs-on: ubuntu-latest
+ timeout-minutes: 30
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
@@ -424,6 +426,7 @@ jobs:
if: ${{ !contains(github.event.pull_request.labels.*.name, 'skip:ci') }}
name: Run snippets and cpython tests on wasm-wasi
runs-on: ubuntu-latest
+ timeout-minutes: 30
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
diff --git a/.github/workflows/comment-commands.yml b/.github/workflows/comment-commands.yml
new file mode 100644
index 0000000000..d1a457c73e
--- /dev/null
+++ b/.github/workflows/comment-commands.yml
@@ -0,0 +1,21 @@
+name: Comment Commands
+
+on:
+ issue_comment:
+ types: created
+
+jobs:
+ issue_assign:
+ if: (!github.event.issue.pull_request) && github.event.comment.body == 'take'
+ runs-on: ubuntu-latest
+
+ concurrency:
+ group: ${{ github.actor }}-issue-assign
+
+ permissions:
+ issues: write
+
+ steps:
+ # Using REST API and not `gh issue edit`. https://github.com/cli/cli/issues/6235#issuecomment-1243487651
+ - run: |
+ curl -H "Authorization: token ${{ github.token }}" -d '{"assignees": ["${{ github.event.comment.user.login }}"]}' https://api.github.com/repos/${{ github.repository }}/issues/${{ github.event.issue.number }}/assignees
diff --git a/Cargo.lock b/Cargo.lock
index 095a3dca37..50ec28b1ed 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -16,15 +16,15 @@ checksum = "aae1277d39aeec15cb388266ecc24b11c80469deae6067e17a1a7aa9e5c1f234"
[[package]]
name = "ahash"
-version = "0.8.11"
+version = "0.8.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011"
+checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75"
dependencies = [
"cfg-if",
- "getrandom 0.2.15",
+ "getrandom 0.3.2",
"once_cell",
"version_check",
- "zerocopy 0.7.35",
+ "zerocopy",
]
[[package]]
@@ -115,9 +115,9 @@ dependencies = [
[[package]]
name = "anyhow"
-version = "1.0.97"
+version = "1.0.98"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "dcfed56ad506cb2c684a14971b8861fdc3baaaae314b9e5f9bb532cbe3ba7a4f"
+checksum = "e16d2d3311acee920a9eb8d33b8cbc1787ce4a264e85f964c2404b969bdcd487"
[[package]]
name = "approx"
@@ -178,7 +178,7 @@ dependencies = [
"regex",
"rustc-hash",
"shlex",
- "syn 2.0.100",
+ "syn 2.0.101",
]
[[package]]
@@ -213,9 +213,9 @@ dependencies = [
[[package]]
name = "bstr"
-version = "1.11.3"
+version = "1.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "531a9155a481e2ee699d4f98f43c0ca4ff8ee1bfd55c31e9e98fb29d2b176fe0"
+checksum = "234113d19d0d7d613b40e86fb654acf958910802bcceab913a4f9e7cda03b1a4"
dependencies = [
"memchr",
"regex-automata",
@@ -233,9 +233,9 @@ dependencies = [
[[package]]
name = "bytemuck"
-version = "1.22.0"
+version = "1.23.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b6b1fc10dbac614ebc03540c9dbd60e83887fda27794998c6528f1782047d540"
+checksum = "9134a6ef01ce4b366b50689c94f82c14bc72bc5d0386829828a2e2752ef7958c"
[[package]]
name = "bzip2"
@@ -283,9 +283,9 @@ dependencies = [
[[package]]
name = "cc"
-version = "1.2.18"
+version = "1.2.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "525046617d8376e3db1deffb079e91cef90a89fc3ca5c185bbf8c9ecdd15cd5c"
+checksum = "8691782945451c1c383942c4874dbe63814f61cb57ef773cda2972682b7bb3c0"
dependencies = [
"shlex",
]
@@ -313,9 +313,9 @@ checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724"
[[package]]
name = "chrono"
-version = "0.4.40"
+version = "0.4.41"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1a7964611d71df112cb1730f2ee67324fcf4d0fc6606acbbe9bfe06df124637c"
+checksum = "c469d952047f47f91b68d1cba3f10d63c11d73e4636f24f08daf0278abf01c4d"
dependencies = [
"android-tzdata",
"iana-time-zone",
@@ -365,18 +365,18 @@ dependencies = [
[[package]]
name = "clap"
-version = "4.5.36"
+version = "4.5.38"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2df961d8c8a0d08aa9945718ccf584145eee3f3aa06cddbeac12933781102e04"
+checksum = "ed93b9805f8ba930df42c2590f05453d5ec36cbb85d018868a5b24d31f6ac000"
dependencies = [
"clap_builder",
]
[[package]]
name = "clap_builder"
-version = "4.5.36"
+version = "4.5.38"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "132dbda40fb6753878316a489d5a1242a8ef2f0d9e47ba01c951ea8aa7d013a5"
+checksum = "379026ff283facf611b0ea629334361c4211d1b12ee01024eec1591133b04120"
dependencies = [
"anstyle",
"clap_lex",
@@ -472,9 +472,9 @@ dependencies = [
[[package]]
name = "cranelift"
-version = "0.118.0"
+version = "0.119.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e482b051275b415cf7627bb6b26e9902ce6aec058b443266c2a1e7a0de148960"
+checksum = "6d07c374d4da962eca0833c1d14621d5b4e32e68c8ca185b046a3b6b924ad334"
dependencies = [
"cranelift-codegen",
"cranelift-frontend",
@@ -483,39 +483,42 @@ dependencies = [
[[package]]
name = "cranelift-assembler-x64"
-version = "0.118.0"
+version = "0.119.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3e4b56ebe316895d3fa37775d0a87b0c889cc933f5c8b253dbcc7c7bcb7fe7e4"
+checksum = "263cc79b8a23c29720eb596d251698f604546b48c34d0d84f8fd2761e5bf8888"
dependencies = [
"cranelift-assembler-x64-meta",
]
[[package]]
name = "cranelift-assembler-x64-meta"
-version = "0.118.0"
+version = "0.119.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "95cabbc01dfbd7dcd6c329ca44f0212910309c221797ac736a67a5bc8857fe1b"
+checksum = "5b4a113455f8c0e13e3b3222a9c38d6940b958ff22573108be083495c72820e1"
+dependencies = [
+ "cranelift-srcgen",
+]
[[package]]
name = "cranelift-bforest"
-version = "0.118.0"
+version = "0.119.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "76ffe46df300a45f1dc6f609dc808ce963f0e3a2e971682c479a2d13e3b9b8ef"
+checksum = "58f96dca41c5acf5d4312c1d04b3391e21a312f8d64ce31a2723a3bb8edd5d4d"
dependencies = [
"cranelift-entity",
]
[[package]]
name = "cranelift-bitset"
-version = "0.118.0"
+version = "0.119.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b265bed7c51e1921fdae6419791d31af77d33662ee56d7b0fa0704dc8d231cab"
+checksum = "7d821ed698dd83d9c012447eb63a5406c1e9c23732a2f674fb5b5015afd42202"
[[package]]
name = "cranelift-codegen"
-version = "0.118.0"
+version = "0.119.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e606230a7e3a6897d603761baee0d19f88d077f17b996bb5089488a29ae96e41"
+checksum = "06c52fdec4322cb8d5545a648047819aaeaa04e630f88d3a609c0d3c1a00e9a0"
dependencies = [
"bumpalo",
"cranelift-assembler-x64",
@@ -538,43 +541,44 @@ dependencies = [
[[package]]
name = "cranelift-codegen-meta"
-version = "0.118.0"
+version = "0.119.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8a63bffafc23bc60969ad528e138788495999d935f0adcfd6543cb151ca8637d"
+checksum = "af2c215e0c9afa8069aafb71d22aa0e0dde1048d9a5c3c72a83cacf9b61fcf4a"
dependencies = [
- "cranelift-assembler-x64",
+ "cranelift-assembler-x64-meta",
"cranelift-codegen-shared",
+ "cranelift-srcgen",
]
[[package]]
name = "cranelift-codegen-shared"
-version = "0.118.0"
+version = "0.119.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "af50281b67324b58e843170a6a5943cf6d387c06f7eeacc9f5696e4ab7ae7d7e"
+checksum = "97524b2446fc26a78142132d813679dda19f620048ebc9a9fbb0ac9f2d320dcb"
[[package]]
name = "cranelift-control"
-version = "0.118.0"
+version = "0.119.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8c20c1b38d1abfbcebb0032e497e71156c0e3b8dcb3f0a92b9863b7bcaec290c"
+checksum = "8e32e900aee81f9e3cc493405ef667a7812cb5c79b5fc6b669e0a2795bda4b22"
dependencies = [
"arbitrary",
]
[[package]]
name = "cranelift-entity"
-version = "0.118.0"
+version = "0.119.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0c2c67d95507c51b4a1ff3f3555fe4bfec36b9e13c1b684ccc602736f5d5f4a2"
+checksum = "d16a2e28e0fa6b9108d76879d60fe1cc95ba90e1bcf52bac96496371044484ee"
dependencies = [
"cranelift-bitset",
]
[[package]]
name = "cranelift-frontend"
-version = "0.118.0"
+version = "0.119.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4e002691cc69c38b54fc7ec93e5be5b744f627d027031d991cc845d1d512d0ce"
+checksum = "328181a9083d99762d85954a16065d2560394a862b8dc10239f39668df528b95"
dependencies = [
"cranelift-codegen",
"log",
@@ -584,15 +588,15 @@ dependencies = [
[[package]]
name = "cranelift-isle"
-version = "0.118.0"
+version = "0.119.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e93588ed1796cbcb0e2ad160403509e2c5d330d80dd6e0014ac6774c7ebac496"
+checksum = "e916f36f183e377e9a3ed71769f2721df88b72648831e95bb9fa6b0cd9b1c709"
[[package]]
name = "cranelift-jit"
-version = "0.118.0"
+version = "0.119.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "17f6682f0b193d6b7873cc8e7ed67e8776a8a26f50eeabf88534e9be618b9a03"
+checksum = "d6bb584ac927f1076d552504b0075b833b9d61e2e9178ba55df6b2d966b4375d"
dependencies = [
"anyhow",
"cranelift-codegen",
@@ -610,9 +614,9 @@ dependencies = [
[[package]]
name = "cranelift-module"
-version = "0.118.0"
+version = "0.119.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ff19784c6de05116e63e6a34791012bd927b2a4eac56233039c46f1b6a4edac8"
+checksum = "40c18ccb8e4861cf49cec79998af73b772a2b47212d12d3d63bf57cc4293a1e3"
dependencies = [
"anyhow",
"cranelift-codegen",
@@ -621,15 +625,21 @@ dependencies = [
[[package]]
name = "cranelift-native"
-version = "0.118.0"
+version = "0.119.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e5b09bdd6407bf5d89661b80cf926ce731c9e8cc184bf49102267a2369a8358e"
+checksum = "fc852cf04128877047dc2027aa1b85c64f681dc3a6a37ff45dcbfa26e4d52d2f"
dependencies = [
"cranelift-codegen",
"libc",
"target-lexicon",
]
+[[package]]
+name = "cranelift-srcgen"
+version = "0.119.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "47e1a86340a16e74b4285cc86ac69458fa1c8e7aaff313da4a89d10efd3535ee"
+
[[package]]
name = "crc32fast"
version = "1.4.2"
@@ -840,9 +850,9 @@ dependencies = [
[[package]]
name = "error-code"
-version = "3.3.1"
+version = "3.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a5d9305ccc6942a704f4335694ecd3de2ea531b114ac2d51f5f843750787a92f"
+checksum = "dea2df4cf52843e0452895c455a1a2cfbb842a1e7329671acf418fdc53ed4c59"
[[package]]
name = "exitcode"
@@ -953,9 +963,9 @@ dependencies = [
[[package]]
name = "gethostname"
-version = "1.0.1"
+version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ed7131e57abbde63513e0e6636f76668a1ca9798dcae2df4e283cae9ee83859e"
+checksum = "fc257fdb4038301ce4b9cd1b3b51704509692bb3ff716a410cbd07925d9dae55"
dependencies = [
"rustix",
"windows-targets 0.52.6",
@@ -972,15 +982,13 @@ dependencies = [
[[package]]
name = "getrandom"
-version = "0.2.15"
+version = "0.2.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7"
+checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592"
dependencies = [
"cfg-if",
- "js-sys",
"libc",
"wasi 0.11.0+wasi-snapshot-preview1",
- "wasm-bindgen",
]
[[package]]
@@ -1016,9 +1024,9 @@ checksum = "a8d1add55171497b4705a648c6b583acafb01d58050a51727785f0b2c8e0a2b2"
[[package]]
name = "half"
-version = "2.5.0"
+version = "2.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7db2ff139bba50379da6aa0766b52fdcb62cb5b263009b09ed58ba604e14bbd1"
+checksum = "459196ed295495a68f7d7fe1d84f6c4b7ff0e21fe3017b2f283c6fac3ad803c9"
dependencies = [
"cfg-if",
"crunchy",
@@ -1026,9 +1034,9 @@ dependencies = [
[[package]]
name = "hashbrown"
-version = "0.15.2"
+version = "0.15.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289"
+checksum = "84b26c544d002229e640969970a2e74021aadf6e2f96372b9c58eff97de08eb3"
dependencies = [
"foldhash",
]
@@ -1047,9 +1055,9 @@ checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024"
[[package]]
name = "hermit-abi"
-version = "0.5.0"
+version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "fbd780fe5cc30f81464441920d82ac8740e2e46b29a6fad543ddd075229ce37e"
+checksum = "f154ce46856750ed433c8649605bf7ed2de3bc35fd9d2a9f30cddd873c80cb08"
[[package]]
name = "hex"
@@ -1114,14 +1122,12 @@ checksum = "f4c7245a08504955605670dbf141fceab975f15ca21570696aebe9d2e71576bd"
[[package]]
name = "insta"
-version = "1.42.2"
+version = "1.43.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "50259abbaa67d11d2bcafc7ba1d094ed7a0c70e3ce893f0d0997f73558cb3084"
+checksum = "154934ea70c58054b556dd430b99a98c2a7ff5309ac9891597e339b5c28f4371"
dependencies = [
"console",
- "linked-hash-map",
"once_cell",
- "pin-project",
"similar",
]
@@ -1134,7 +1140,7 @@ dependencies = [
"heck",
"proc-macro2",
"quote",
- "syn 2.0.100",
+ "syn 2.0.101",
]
[[package]]
@@ -1143,7 +1149,7 @@ version = "0.4.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e04d7f318608d35d4b61ddd75cbdaee86b023ebe2bd5a66ee0915f0bf93095a9"
dependencies = [
- "hermit-abi 0.5.0",
+ "hermit-abi 0.5.1",
"libc",
"windows-sys 0.59.0",
]
@@ -1189,9 +1195,9 @@ checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c"
[[package]]
name = "jiff"
-version = "0.2.5"
+version = "0.2.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c102670231191d07d37a35af3eb77f1f0dbf7a71be51a962dcd57ea607be7260"
+checksum = "f02000660d30638906021176af16b17498bd0d12813dbfe7b276d8bc7f3c0806"
dependencies = [
"jiff-static",
"log",
@@ -1202,13 +1208,13 @@ dependencies = [
[[package]]
name = "jiff-static"
-version = "0.2.5"
+version = "0.2.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4cdde31a9d349f1b1f51a0b3714a5940ac022976f4b49485fc04be052b183b4c"
+checksum = "f3c30758ddd7188629c6713fc45d1188af4f44c90582311d0c8d8c9907f60c48"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.100",
+ "syn 2.0.101",
]
[[package]]
@@ -1290,15 +1296,15 @@ checksum = "0864a00c8d019e36216b69c2c4ce50b83b7bd966add3cf5ba554ec44f8bebcf5"
[[package]]
name = "libc"
-version = "0.2.171"
+version = "0.2.172"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c19937216e9d3aa9956d9bb8dfc0b0c8beb6058fc4f7a4dc4d850edf86a237d6"
+checksum = "d750af042f7ef4f724306de029d18836c26c1765a54a6a3f094cbd23a7267ffa"
[[package]]
name = "libffi"
-version = "4.0.0"
+version = "4.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4a9434b6fc77375fb624698d5f8c49d7e80b10d59eb1219afda27d1f824d4074"
+checksum = "ebfd30a67b482a08116e753d0656cb626548cf4242543e5cc005be7639d99838"
dependencies = [
"libc",
"libffi-sys",
@@ -1306,9 +1312,9 @@ dependencies = [
[[package]]
name = "libffi-sys"
-version = "3.2.0"
+version = "3.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ead36a2496acfc8edd6cc32352110e9478ac5b9b5f5b9856ebd3d28019addb84"
+checksum = "f003aa318c9f0ee69eb0ada7c78f5c9d2fedd2ceb274173b5c7ff475eee584a3"
dependencies = [
"cc",
]
@@ -1325,9 +1331,9 @@ dependencies = [
[[package]]
name = "libm"
-version = "0.2.11"
+version = "0.2.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8355be11b20d696c8f18f6cc018c4e372165b1fa8126cef092399c9951984ffa"
+checksum = "f9fbbcab51052fe104eb5e5d351cf728d30a5be1fe14d9be8a3b097481fb97de"
[[package]]
name = "libredox"
@@ -1359,17 +1365,11 @@ dependencies = [
"zlib-rs",
]
-[[package]]
-name = "linked-hash-map"
-version = "0.5.6"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f"
-
[[package]]
name = "linux-raw-sys"
-version = "0.9.3"
+version = "0.9.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "fe7db12097d22ec582439daf8618b8fdd1a7bef6270e9af3b1ebcd30893cf413"
+checksum = "cd945864f07fe9f5371a27ad7b52a172b4b499999f1d97574c9fa68373937e12"
[[package]]
name = "lock_api"
@@ -1527,9 +1527,9 @@ checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
[[package]]
name = "miniz_oxide"
-version = "0.8.7"
+version = "0.8.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ff70ce3e48ae43fa075863cef62e8b43b71a4f2382229920e0df362592919430"
+checksum = "3be647b768db090acb35d5ec5db2b0e1f1de11133ca123b9eacf5137868f892a"
dependencies = [
"adler2",
]
@@ -1629,7 +1629,7 @@ checksum = "af1844ef2428cc3e1cb900be36181049ef3d3193c63e43026cfe202983b27a56"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.100",
+ "syn 2.0.101",
]
[[package]]
@@ -1667,7 +1667,7 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.100",
+ "syn 2.0.101",
]
[[package]]
@@ -1678,18 +1678,18 @@ checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e"
[[package]]
name = "openssl-src"
-version = "300.4.2+3.4.1"
+version = "300.5.0+3.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "168ce4e058f975fe43e89d9ccf78ca668601887ae736090aacc23ae353c298e2"
+checksum = "e8ce546f549326b0e6052b649198487d91320875da901e7bd11a06d1ee3f9c2f"
dependencies = [
"cc",
]
[[package]]
name = "openssl-sys"
-version = "0.9.107"
+version = "0.9.108"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8288979acd84749c744a9014b4382d42b8f7b2592847b5afb2ed29e5d16ede07"
+checksum = "e145e1651e858e820e4860f7b9c5e169bc1d8ce1c86043be79fa7b7634821847"
dependencies = [
"cc",
"libc",
@@ -1732,7 +1732,7 @@ checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8"
dependencies = [
"cfg-if",
"libc",
- "redox_syscall 0.5.11",
+ "redox_syscall 0.5.12",
"smallvec",
"windows-targets 0.52.6",
]
@@ -1781,26 +1781,6 @@ dependencies = [
"siphasher",
]
-[[package]]
-name = "pin-project"
-version = "1.1.10"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "677f1add503faace112b9f1373e43e9e054bfdd22ff1a63c1bc485eaec6a6a8a"
-dependencies = [
- "pin-project-internal",
-]
-
-[[package]]
-name = "pin-project-internal"
-version = "1.1.10"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6e918e4ff8c4549eb882f14b3a4bc8c8bc93de829416eacf579f1207a8fbf861"
-dependencies = [
- "proc-macro2",
- "quote",
- "syn 2.0.100",
-]
-
[[package]]
name = "pkg-config"
version = "0.3.32"
@@ -1843,7 +1823,7 @@ checksum = "52a40bc70c2c58040d2d8b167ba9a5ff59fc9dab7ad44771cfde3dcfde7a09c6"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.100",
+ "syn 2.0.101",
]
[[package]]
@@ -1867,7 +1847,7 @@ version = "0.2.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9"
dependencies = [
- "zerocopy 0.8.24",
+ "zerocopy",
]
[[package]]
@@ -1877,14 +1857,14 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "664ec5419c51e34154eec046ebcba56312d5a2fc3b09a06da188e1ad21afadf6"
dependencies = [
"proc-macro2",
- "syn 2.0.100",
+ "syn 2.0.101",
]
[[package]]
name = "proc-macro2"
-version = "1.0.94"
+version = "1.0.95"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a31971752e70b8b2686d7e46ec17fb38dad4051d94024c88df49b667caea9c84"
+checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778"
dependencies = [
"unicode-ident",
]
@@ -1900,9 +1880,9 @@ dependencies = [
[[package]]
name = "pyo3"
-version = "0.24.1"
+version = "0.24.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "17da310086b068fbdcefbba30aeb3721d5bb9af8db4987d6735b2183ca567229"
+checksum = "e5203598f366b11a02b13aa20cab591229ff0a89fd121a308a5df751d5fc9219"
dependencies = [
"cfg-if",
"indoc",
@@ -1918,9 +1898,9 @@ dependencies = [
[[package]]
name = "pyo3-build-config"
-version = "0.24.1"
+version = "0.24.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e27165889bd793000a098bb966adc4300c312497ea25cf7a690a9f0ac5aa5fc1"
+checksum = "99636d423fa2ca130fa5acde3059308006d46f98caac629418e53f7ebb1e9999"
dependencies = [
"once_cell",
"target-lexicon",
@@ -1928,9 +1908,9 @@ dependencies = [
[[package]]
name = "pyo3-ffi"
-version = "0.24.1"
+version = "0.24.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "05280526e1dbf6b420062f3ef228b78c0c54ba94e157f5cb724a609d0f2faabc"
+checksum = "78f9cf92ba9c409279bc3305b5409d90db2d2c22392d443a87df3a1adad59e33"
dependencies = [
"libc",
"pyo3-build-config",
@@ -1938,27 +1918,27 @@ dependencies = [
[[package]]
name = "pyo3-macros"
-version = "0.24.1"
+version = "0.24.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5c3ce5686aa4d3f63359a5100c62a127c9f15e8398e5fdeb5deef1fed5cd5f44"
+checksum = "0b999cb1a6ce21f9a6b147dcf1be9ffedf02e0043aec74dc390f3007047cecd9"
dependencies = [
"proc-macro2",
"pyo3-macros-backend",
"quote",
- "syn 2.0.100",
+ "syn 2.0.101",
]
[[package]]
name = "pyo3-macros-backend"
-version = "0.24.1"
+version = "0.24.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f4cf6faa0cbfb0ed08e89beb8103ae9724eb4750e3a78084ba4017cbe94f3855"
+checksum = "822ece1c7e1012745607d5cf0bcb2874769f0f7cb34c4cde03b9358eb9ef911a"
dependencies = [
"heck",
"proc-macro2",
"pyo3-build-config",
"quote",
- "syn 2.0.100",
+ "syn 2.0.101",
]
[[package]]
@@ -1978,8 +1958,9 @@ checksum = "74765f6d916ee2faa39bc8e68e4f3ed8949b48cccdac59983d287a7cb71ce9c5"
[[package]]
name = "radium"
-version = "1.1.0"
-source = "git+https://github.com/youknowone/ferrilab?branch=fix-nightly#4a301c3a223e096626a2773d1a1eed1fc4e21140"
+version = "1.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1775bc532a9bfde46e26eba441ca1171b91608d14a3bae71fea371f18a00cffe"
dependencies = [
"cfg-if",
]
@@ -2007,13 +1988,12 @@ dependencies = [
[[package]]
name = "rand"
-version = "0.9.0"
+version = "0.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3779b94aeb87e8bd4e834cee3650289ee9e0d5677f976ecdb6d219e5f4f6cd94"
+checksum = "9fbfd9d094a40bf3ae768db9361049ace4c0e04a4fd6b359518bd7b73a73dd97"
dependencies = [
"rand_chacha 0.9.0",
"rand_core 0.9.3",
- "zerocopy 0.8.24",
]
[[package]]
@@ -2042,7 +2022,7 @@ version = "0.6.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
dependencies = [
- "getrandom 0.2.15",
+ "getrandom 0.2.16",
]
[[package]]
@@ -2082,9 +2062,9 @@ checksum = "41cc0f7e4d5d4544e8861606a285bb08d3e70712ccc7d2b84d7c0ccfaf4b05ce"
[[package]]
name = "redox_syscall"
-version = "0.5.11"
+version = "0.5.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d2f103c6d277498fbceb16e84d317e2a400f160f46904d5f5410848c829511a3"
+checksum = "928fca9cf2aa042393a8325b9ead81d2f0df4cb12e1e24cef072922ccd99c5af"
dependencies = [
"bitflags 2.9.0",
]
@@ -2095,7 +2075,7 @@ version = "0.4.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ba009ff324d1fc1b900bd1fdb31564febe58a8ccc8a6fdbb93b543d33b13ca43"
dependencies = [
- "getrandom 0.2.15",
+ "getrandom 0.2.16",
"libredox",
"thiserror 1.0.69",
]
@@ -2173,7 +2153,7 @@ dependencies = [
"pmutil",
"proc-macro2",
"quote",
- "syn 2.0.100",
+ "syn 2.0.101",
]
[[package]]
@@ -2245,9 +2225,9 @@ checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d"
[[package]]
name = "rustix"
-version = "1.0.5"
+version = "1.0.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d97817398dd4bb2e6da002002db259209759911da105da92bec29ccb12cf58bf"
+checksum = "c71e83d6afe7ff64890ec6b71d6a69bb8a610ab78ce364b3352876bb4c801266"
dependencies = [
"bitflags 2.9.0",
"errno",
@@ -2276,6 +2256,7 @@ dependencies = [
"rustpython-stdlib",
"rustpython-vm",
"rustyline",
+ "winresource",
]
[[package]]
@@ -2320,6 +2301,7 @@ dependencies = [
"malachite-bigint",
"malachite-q",
"memchr",
+ "num-complex",
"num-traits",
"once_cell",
"parking_lot",
@@ -2336,7 +2318,7 @@ dependencies = [
name = "rustpython-compiler"
version = "0.4.0"
dependencies = [
- "rand 0.9.0",
+ "rand 0.9.1",
"ruff_python_ast",
"ruff_python_parser",
"ruff_source_file",
@@ -2376,7 +2358,7 @@ dependencies = [
"proc-macro2",
"rustpython-compiler",
"rustpython-derive-impl",
- "syn 2.0.100",
+ "syn 2.0.101",
]
[[package]]
@@ -2389,7 +2371,7 @@ dependencies = [
"quote",
"rustpython-compiler-core",
"rustpython-doc",
- "syn 2.0.100",
+ "syn 2.0.101",
"syn-ext",
"textwrap",
]
@@ -2425,7 +2407,7 @@ dependencies = [
"is-macro",
"lexical-parse-float",
"num-traits",
- "rand 0.9.0",
+ "rand 0.9.1",
"rustpython-wtf8",
"unic-ucd-category",
]
@@ -2517,6 +2499,7 @@ dependencies = [
"unic-ucd-bidi",
"unic-ucd-category",
"unic-ucd-ident",
+ "unicode-bidi-mirroring",
"unicode-casing",
"unicode_names2",
"uuid",
@@ -2621,7 +2604,6 @@ name = "rustpython_wasm"
version = "0.4.0"
dependencies = [
"console_error_panic_hook",
- "getrandom 0.2.15",
"js-sys",
"ruff_python_parser",
"rustpython-common",
@@ -2722,7 +2704,7 @@ checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.100",
+ "syn 2.0.101",
]
[[package]]
@@ -2737,6 +2719,15 @@ dependencies = [
"serde",
]
+[[package]]
+name = "serde_spanned"
+version = "0.6.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "87607cb1398ed59d48732e575a4c28a7a8ebf2454b964fe3f224f2afc07909e1"
+dependencies = [
+ "serde",
+]
+
[[package]]
name = "sha-1"
version = "0.10.1"
@@ -2750,9 +2741,9 @@ dependencies = [
[[package]]
name = "sha2"
-version = "0.10.8"
+version = "0.10.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8"
+checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283"
dependencies = [
"cfg-if",
"cpufeatures",
@@ -2839,7 +2830,7 @@ dependencies = [
"proc-macro2",
"quote",
"rustversion",
- "syn 2.0.100",
+ "syn 2.0.101",
]
[[package]]
@@ -2861,9 +2852,9 @@ dependencies = [
[[package]]
name = "syn"
-version = "2.0.100"
+version = "2.0.101"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b09a44accad81e1ba1cd74a32461ba89dee89095ba17b32f5d03683b1b1fc2a0"
+checksum = "8ce2b7fc941b3a24138a0a7cf8e858bfc6a992e7978a068a5c760deb0ed43caf"
dependencies = [
"proc-macro2",
"quote",
@@ -2878,7 +2869,7 @@ checksum = "b126de4ef6c2a628a68609dd00733766c3b015894698a438ebdf374933fc31d1"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.100",
+ "syn 2.0.101",
]
[[package]]
@@ -2958,7 +2949,7 @@ checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.100",
+ "syn 2.0.101",
]
[[package]]
@@ -2969,7 +2960,7 @@ checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.100",
+ "syn 2.0.101",
]
[[package]]
@@ -3033,6 +3024,47 @@ dependencies = [
"shared-build",
]
+[[package]]
+name = "toml"
+version = "0.8.22"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "05ae329d1f08c4d17a59bed7ff5b5a769d062e64a62d34a3261b219e62cd5aae"
+dependencies = [
+ "serde",
+ "serde_spanned",
+ "toml_datetime",
+ "toml_edit",
+]
+
+[[package]]
+name = "toml_datetime"
+version = "0.6.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3da5db5a963e24bc68be8b17b6fa82814bb22ee8660f192bb182771d498f09a3"
+dependencies = [
+ "serde",
+]
+
+[[package]]
+name = "toml_edit"
+version = "0.22.26"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "310068873db2c5b3e7659d2cc35d21855dbafa50d1ce336397c666e3cb08137e"
+dependencies = [
+ "indexmap",
+ "serde",
+ "serde_spanned",
+ "toml_datetime",
+ "toml_write",
+ "winnow",
+]
+
+[[package]]
+name = "toml_write"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bfb942dfe1d8e29a7ee7fcbde5bd2b9a25fb89aa70caea2eba3bee836ff41076"
+
[[package]]
name = "twox-hash"
version = "1.6.3"
@@ -3169,6 +3201,12 @@ dependencies = [
"unic-common",
]
+[[package]]
+name = "unicode-bidi-mirroring"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "23cb788ffebc92c5948d0e997106233eeb1d8b9512f93f41651f52b6c5f5af86"
+
[[package]]
name = "unicode-casing"
version = "0.1.0"
@@ -3310,7 +3348,7 @@ dependencies = [
"log",
"proc-macro2",
"quote",
- "syn 2.0.100",
+ "syn 2.0.101",
"wasm-bindgen-shared",
]
@@ -3345,7 +3383,7 @@ checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.100",
+ "syn 2.0.101",
"wasm-bindgen-backend",
"wasm-bindgen-shared",
]
@@ -3361,9 +3399,9 @@ dependencies = [
[[package]]
name = "wasmtime-jit-icache-coherence"
-version = "31.0.0"
+version = "32.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a54f6c6c7e9d7eeee32dfcc10db7f29d505ee7dd28d00593ea241d5f70698e64"
+checksum = "eb399eaabd7594f695e1159d236bf40ef55babcb3af97f97c027864ed2104db6"
dependencies = [
"anyhow",
"cfg-if",
@@ -3470,7 +3508,7 @@ checksum = "a47fddd13af08290e67f4acabf4b459f647552718f683a7b415d290ac744a836"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.100",
+ "syn 2.0.101",
]
[[package]]
@@ -3481,7 +3519,7 @@ checksum = "bd9211b69f8dcdfa817bfd14bf1c97c9188afa36f4750130fcdf3f400eca9fa8"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.100",
+ "syn 2.0.101",
]
[[package]]
@@ -3656,6 +3694,15 @@ version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
+[[package]]
+name = "winnow"
+version = "0.7.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c06928c8748d81b05c9be96aad92e1b6ff01833332f281e8cfca3be4b35fc9ec"
+dependencies = [
+ "memchr",
+]
+
[[package]]
name = "winreg"
version = "0.55.0"
@@ -3666,6 +3713,16 @@ dependencies = [
"windows-sys 0.59.0",
]
+[[package]]
+name = "winresource"
+version = "0.1.20"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ba4a67c78ee5782c0c1cb41bebc7e12c6e79644daa1650ebbc1de5d5b08593f7"
+dependencies = [
+ "toml",
+ "version_check",
+]
+
[[package]]
name = "winsafe"
version = "0.0.19"
@@ -3683,9 +3740,9 @@ dependencies = [
[[package]]
name = "xml-rs"
-version = "0.8.25"
+version = "0.8.26"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c5b940ebc25896e71dd073bad2dbaa2abfe97b0a391415e22ad1326d9c54e3c4"
+checksum = "a62ce76d9b56901b19a74f19431b0d8b3bc7ca4ad685a746dfd78ca8f4fc6bda"
[[package]]
name = "xz2"
@@ -3698,42 +3755,22 @@ dependencies = [
[[package]]
name = "zerocopy"
-version = "0.7.35"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0"
-dependencies = [
- "zerocopy-derive 0.7.35",
-]
-
-[[package]]
-name = "zerocopy"
-version = "0.8.24"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2586fea28e186957ef732a5f8b3be2da217d65c5969d4b1e17f973ebbe876879"
-dependencies = [
- "zerocopy-derive 0.8.24",
-]
-
-[[package]]
-name = "zerocopy-derive"
-version = "0.7.35"
+version = "0.8.25"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e"
+checksum = "a1702d9583232ddb9174e01bb7c15a2ab8fb1bc6f227aa1233858c351a3ba0cb"
dependencies = [
- "proc-macro2",
- "quote",
- "syn 2.0.100",
+ "zerocopy-derive",
]
[[package]]
name = "zerocopy-derive"
-version = "0.8.24"
+version = "0.8.25"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a996a8f63c5c4448cd959ac1bab0aaa3306ccfd060472f85943ee0750f0169be"
+checksum = "28a6e20d751156648aa063f3800b706ee209a32c0b4d9f24be3d980b01be55ef"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.100",
+ "syn 2.0.101",
]
[[package]]
diff --git a/Cargo.toml b/Cargo.toml
index 163289e8b2..440855aba5 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -24,6 +24,9 @@ ssl = ["rustpython-stdlib/ssl"]
ssl-vendor = ["ssl", "rustpython-stdlib/ssl-vendor"]
tkinter = ["rustpython-stdlib/tkinter"]
+[build-dependencies]
+winresource = "0.1"
+
[dependencies]
rustpython-compiler = { workspace = true }
rustpython-pylib = { workspace = true, optional = true }
@@ -79,7 +82,6 @@ opt-level = 3
lto = "thin"
[patch.crates-io]
-radium = { version = "1.1.0", git = "https://github.com/youknowone/ferrilab", branch = "fix-nightly" }
# REDOX START, Uncomment when you want to compile/check with redoxer
# REDOX END
@@ -169,7 +171,7 @@ itertools = "0.14.0"
is-macro = "0.3.7"
junction = "1.2.0"
libc = "0.2.169"
-libffi = "4.0"
+libffi = "4.1"
log = "0.4.27"
nix = { version = "0.29", features = ["fs", "user", "process", "term", "time", "signal", "ioctl", "socket", "sched", "zerocopy", "dir", "hostname", "net", "poll"] }
malachite-bigint = "0.6"
@@ -187,7 +189,7 @@ paste = "1.0.15"
proc-macro2 = "1.0.93"
pymath = "0.0.2"
quote = "1.0.38"
-radium = "1.1"
+radium = "1.1.1"
rand = "0.9"
rand_core = { version = "0.9", features = ["os_rng"] }
rustix = { version = "1.0", features = ["event"] }
@@ -208,6 +210,7 @@ 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"
widestring = "1.1.0"
windows-sys = "0.59.0"
wasm-bindgen = "0.2.100"
diff --git a/Lib/_collections_abc.py b/Lib/_collections_abc.py
index 601107d2d8..de624f2e54 100644
--- a/Lib/_collections_abc.py
+++ b/Lib/_collections_abc.py
@@ -85,6 +85,10 @@ def _f(): pass
dict_items = type({}.items())
## misc ##
mappingproxy = type(type.__dict__)
+def _get_framelocalsproxy():
+ return type(sys._getframe().f_locals)
+framelocalsproxy = _get_framelocalsproxy()
+del _get_framelocalsproxy
generator = type((lambda: (yield))())
## coroutine ##
async def _coro(): pass
@@ -508,6 +512,10 @@ def __getitem__(self, item):
new_args = (t_args, t_result)
return _CallableGenericAlias(Callable, tuple(new_args))
+ # TODO: RUSTPYTHON patch for common call
+ def __or__(self, other):
+ super().__or__(other)
+
def _is_param_expr(obj):
"""Checks if obj matches either a list of types, ``...``, ``ParamSpec`` or
``_ConcatenateGenericAlias`` from typing.py
@@ -836,6 +844,7 @@ def __eq__(self, other):
__reversed__ = None
Mapping.register(mappingproxy)
+Mapping.register(framelocalsproxy)
class MappingView(Sized):
@@ -973,7 +982,7 @@ def clear(self):
def update(self, other=(), /, **kwds):
''' D.update([E, ]**F) -> None. Update D from mapping/iterable E and F.
- If E present and has a .keys() method, does: for k in E: D[k] = E[k]
+ If E present and has a .keys() method, does: for k in E.keys(): D[k] = E[k]
If E present and lacks .keys() method, does: for (k, v) in E: D[k] = v
In either case, this is followed by: for k, v in F.items(): D[k] = v
'''
diff --git a/Lib/_colorize.py b/Lib/_colorize.py
index 70acfd4ad0..9eb6f0933b 100644
--- a/Lib/_colorize.py
+++ b/Lib/_colorize.py
@@ -1,21 +1,64 @@
+from __future__ import annotations
import io
import os
import sys
COLORIZE = True
+# types
+if False:
+ from typing import IO
+
class ANSIColors:
- BOLD_GREEN = "\x1b[1;32m"
- BOLD_MAGENTA = "\x1b[1;35m"
- BOLD_RED = "\x1b[1;31m"
+ RESET = "\x1b[0m"
+
+ BLACK = "\x1b[30m"
+ BLUE = "\x1b[34m"
+ CYAN = "\x1b[36m"
GREEN = "\x1b[32m"
- GREY = "\x1b[90m"
MAGENTA = "\x1b[35m"
RED = "\x1b[31m"
- RESET = "\x1b[0m"
+ WHITE = "\x1b[37m" # more like LIGHT GRAY
YELLOW = "\x1b[33m"
+ BOLD_BLACK = "\x1b[1;30m" # DARK GRAY
+ BOLD_BLUE = "\x1b[1;34m"
+ BOLD_CYAN = "\x1b[1;36m"
+ BOLD_GREEN = "\x1b[1;32m"
+ BOLD_MAGENTA = "\x1b[1;35m"
+ BOLD_RED = "\x1b[1;31m"
+ BOLD_WHITE = "\x1b[1;37m" # actual WHITE
+ BOLD_YELLOW = "\x1b[1;33m"
+
+ # intense = like bold but without being bold
+ INTENSE_BLACK = "\x1b[90m"
+ INTENSE_BLUE = "\x1b[94m"
+ INTENSE_CYAN = "\x1b[96m"
+ INTENSE_GREEN = "\x1b[92m"
+ INTENSE_MAGENTA = "\x1b[95m"
+ INTENSE_RED = "\x1b[91m"
+ INTENSE_WHITE = "\x1b[97m"
+ INTENSE_YELLOW = "\x1b[93m"
+
+ BACKGROUND_BLACK = "\x1b[40m"
+ BACKGROUND_BLUE = "\x1b[44m"
+ BACKGROUND_CYAN = "\x1b[46m"
+ BACKGROUND_GREEN = "\x1b[42m"
+ BACKGROUND_MAGENTA = "\x1b[45m"
+ BACKGROUND_RED = "\x1b[41m"
+ BACKGROUND_WHITE = "\x1b[47m"
+ BACKGROUND_YELLOW = "\x1b[43m"
+
+ INTENSE_BACKGROUND_BLACK = "\x1b[100m"
+ INTENSE_BACKGROUND_BLUE = "\x1b[104m"
+ INTENSE_BACKGROUND_CYAN = "\x1b[106m"
+ INTENSE_BACKGROUND_GREEN = "\x1b[102m"
+ INTENSE_BACKGROUND_MAGENTA = "\x1b[105m"
+ INTENSE_BACKGROUND_RED = "\x1b[101m"
+ INTENSE_BACKGROUND_WHITE = "\x1b[107m"
+ INTENSE_BACKGROUND_YELLOW = "\x1b[103m"
+
NoColors = ANSIColors()
@@ -24,14 +67,16 @@ class ANSIColors:
setattr(NoColors, attr, "")
-def get_colors(colorize: bool = False, *, file=None) -> ANSIColors:
+def get_colors(
+ colorize: bool = False, *, file: IO[str] | IO[bytes] | None = None
+) -> ANSIColors:
if colorize or can_colorize(file=file):
return ANSIColors()
else:
return NoColors
-def can_colorize(*, file=None) -> bool:
+def can_colorize(*, file: IO[str] | IO[bytes] | None = None) -> bool:
if file is None:
file = sys.stdout
@@ -64,4 +109,4 @@ def can_colorize(*, file=None) -> bool:
try:
return os.isatty(file.fileno())
except io.UnsupportedOperation:
- return file.isatty()
+ return hasattr(file, "isatty") and file.isatty()
diff --git a/Lib/_py_abc.py b/Lib/_py_abc.py
index 4780f9a619..c870ae9048 100644
--- a/Lib/_py_abc.py
+++ b/Lib/_py_abc.py
@@ -33,8 +33,6 @@ class ABCMeta(type):
_abc_invalidation_counter = 0
def __new__(mcls, name, bases, namespace, /, **kwargs):
- # TODO: RUSTPYTHON remove this line (prevents duplicate bases)
- bases = tuple(dict.fromkeys(bases))
cls = super().__new__(mcls, name, bases, namespace, **kwargs)
# Compute set of abstract method names
abstracts = {name
@@ -100,8 +98,8 @@ def __instancecheck__(cls, instance):
subtype = type(instance)
if subtype is subclass:
if (cls._abc_negative_cache_version ==
- ABCMeta._abc_invalidation_counter and
- subclass in cls._abc_negative_cache):
+ ABCMeta._abc_invalidation_counter and
+ subclass in cls._abc_negative_cache):
return False
# Fall back to the subclass check.
return cls.__subclasscheck__(subclass)
diff --git a/Lib/_threading_local.py b/Lib/_threading_local.py
index e520433998..0b9e5d3bbf 100644
--- a/Lib/_threading_local.py
+++ b/Lib/_threading_local.py
@@ -4,133 +4,6 @@
class. Depending on the version of Python you're using, there may be a
faster one available. You should always import the `local` class from
`threading`.)
-
-Thread-local objects support the management of thread-local data.
-If you have data that you want to be local to a thread, simply create
-a thread-local object and use its attributes:
-
- >>> mydata = local()
- >>> mydata.number = 42
- >>> mydata.number
- 42
-
-You can also access the local-object's dictionary:
-
- >>> mydata.__dict__
- {'number': 42}
- >>> mydata.__dict__.setdefault('widgets', [])
- []
- >>> mydata.widgets
- []
-
-What's important about thread-local objects is that their data are
-local to a thread. If we access the data in a different thread:
-
- >>> log = []
- >>> def f():
- ... items = sorted(mydata.__dict__.items())
- ... log.append(items)
- ... mydata.number = 11
- ... log.append(mydata.number)
-
- >>> import threading
- >>> thread = threading.Thread(target=f)
- >>> thread.start()
- >>> thread.join()
- >>> log
- [[], 11]
-
-we get different data. Furthermore, changes made in the other thread
-don't affect data seen in this thread:
-
- >>> mydata.number
- 42
-
-Of course, values you get from a local object, including a __dict__
-attribute, are for whatever thread was current at the time the
-attribute was read. For that reason, you generally don't want to save
-these values across threads, as they apply only to the thread they
-came from.
-
-You can create custom local objects by subclassing the local class:
-
- >>> class MyLocal(local):
- ... number = 2
- ... initialized = False
- ... def __init__(self, **kw):
- ... if self.initialized:
- ... raise SystemError('__init__ called too many times')
- ... self.initialized = True
- ... self.__dict__.update(kw)
- ... def squared(self):
- ... return self.number ** 2
-
-This can be useful to support default values, methods and
-initialization. Note that if you define an __init__ method, it will be
-called each time the local object is used in a separate thread. This
-is necessary to initialize each thread's dictionary.
-
-Now if we create a local object:
-
- >>> mydata = MyLocal(color='red')
-
-Now we have a default number:
-
- >>> mydata.number
- 2
-
-an initial color:
-
- >>> mydata.color
- 'red'
- >>> del mydata.color
-
-And a method that operates on the data:
-
- >>> mydata.squared()
- 4
-
-As before, we can access the data in a separate thread:
-
- >>> log = []
- >>> thread = threading.Thread(target=f)
- >>> thread.start()
- >>> thread.join()
- >>> log
- [[('color', 'red'), ('initialized', True)], 11]
-
-without affecting this thread's data:
-
- >>> mydata.number
- 2
- >>> mydata.color
- Traceback (most recent call last):
- ...
- AttributeError: 'MyLocal' object has no attribute 'color'
-
-Note that subclasses can define slots, but they are not thread
-local. They are shared across threads:
-
- >>> class MyLocal(local):
- ... __slots__ = 'number'
-
- >>> mydata = MyLocal()
- >>> mydata.number = 42
- >>> mydata.color = 'red'
-
-So, the separate thread:
-
- >>> thread = threading.Thread(target=f)
- >>> thread.start()
- >>> thread.join()
-
-affects what we see:
-
- >>> # TODO: RUSTPYTHON, __slots__
- >>> mydata.number #doctest: +SKIP
- 11
-
->>> del mydata
"""
from weakref import ref
@@ -194,7 +67,6 @@ def thread_deleted(_, idt=idt):
@contextmanager
def _patch(self):
- old = object.__getattribute__(self, '__dict__')
impl = object.__getattribute__(self, '_local__impl')
try:
dct = impl.get_dict()
@@ -205,13 +77,12 @@ def _patch(self):
with impl.locallock:
object.__setattr__(self, '__dict__', dct)
yield
- object.__setattr__(self, '__dict__', old)
class local:
__slots__ = '_local__impl', '__dict__'
- def __new__(cls, *args, **kw):
+ def __new__(cls, /, *args, **kw):
if (args or kw) and (cls.__init__ is object.__init__):
raise TypeError("Initialization arguments are not supported")
self = object.__new__(cls)
diff --git a/Lib/abc.py b/Lib/abc.py
index 1ecff5e214..f8a4e11ce9 100644
--- a/Lib/abc.py
+++ b/Lib/abc.py
@@ -85,10 +85,6 @@ def my_abstract_property(self):
from _abc import (get_cache_token, _abc_init, _abc_register,
_abc_instancecheck, _abc_subclasscheck, _get_dump,
_reset_registry, _reset_caches)
-# TODO: RUSTPYTHON missing _abc module implementation.
-except ModuleNotFoundError:
- from _py_abc import ABCMeta, get_cache_token
- ABCMeta.__module__ = 'abc'
except ImportError:
from _py_abc import ABCMeta, get_cache_token
ABCMeta.__module__ = 'abc'
diff --git a/Lib/argparse.py b/Lib/argparse.py
index 543d9944f9..bd088ea0e6 100644
--- a/Lib/argparse.py
+++ b/Lib/argparse.py
@@ -89,8 +89,6 @@
import re as _re
import sys as _sys
-import warnings
-
from gettext import gettext as _, ngettext
SUPPRESS = '==SUPPRESS=='
@@ -192,6 +190,7 @@ def __init__(self,
# ===============================
# Section and indentation methods
# ===============================
+
def _indent(self):
self._current_indent += self._indent_increment
self._level += 1
@@ -225,7 +224,8 @@ def format_help(self):
# add the heading if the section was non-empty
if self.heading is not SUPPRESS and self.heading is not None:
current_indent = self.formatter._current_indent
- heading = '%*s%s:\n' % (current_indent, '', self.heading)
+ heading_text = _('%(heading)s:') % dict(heading=self.heading)
+ heading = '%*s%s\n' % (current_indent, '', heading_text)
else:
heading = ''
@@ -238,6 +238,7 @@ def _add_item(self, func, args):
# ========================
# Message building methods
# ========================
+
def start_section(self, heading):
self._indent()
section = self._Section(self, self._current_section, heading)
@@ -262,13 +263,12 @@ def add_argument(self, action):
# find all invocations
get_invocation = self._format_action_invocation
- invocations = [get_invocation(action)]
+ invocation_lengths = [len(get_invocation(action)) + self._current_indent]
for subaction in self._iter_indented_subactions(action):
- invocations.append(get_invocation(subaction))
+ invocation_lengths.append(len(get_invocation(subaction)) + self._current_indent)
# update the maximum item length
- invocation_length = max(map(len, invocations))
- action_length = invocation_length + self._current_indent
+ action_length = max(invocation_lengths)
self._action_max_length = max(self._action_max_length,
action_length)
@@ -282,6 +282,7 @@ def add_arguments(self, actions):
# =======================
# Help-formatting methods
# =======================
+
def format_help(self):
help = self._root_section.format_help()
if help:
@@ -329,17 +330,8 @@ def _format_usage(self, usage, actions, groups, prefix):
if len(prefix) + len(usage) > text_width:
# break usage into wrappable parts
- part_regexp = (
- r'\(.*?\)+(?=\s|$)|'
- r'\[.*?\]+(?=\s|$)|'
- r'\S+'
- )
- opt_usage = format(optionals, groups)
- pos_usage = format(positionals, groups)
- opt_parts = _re.findall(part_regexp, opt_usage)
- pos_parts = _re.findall(part_regexp, pos_usage)
- assert ' '.join(opt_parts) == opt_usage
- assert ' '.join(pos_parts) == pos_usage
+ opt_parts = self._get_actions_usage_parts(optionals, groups)
+ pos_parts = self._get_actions_usage_parts(positionals, groups)
# helper for wrapping lines
def get_lines(parts, indent, prefix=None):
@@ -392,6 +384,9 @@ def get_lines(parts, indent, prefix=None):
return '%s%s\n\n' % (prefix, usage)
def _format_actions_usage(self, actions, groups):
+ return ' '.join(self._get_actions_usage_parts(actions, groups))
+
+ def _get_actions_usage_parts(self, actions, groups):
# find group indices and identify actions in groups
group_actions = set()
inserts = {}
@@ -399,56 +394,26 @@ def _format_actions_usage(self, actions, groups):
if not group._group_actions:
raise ValueError(f'empty group {group}')
+ if all(action.help is SUPPRESS for action in group._group_actions):
+ continue
+
try:
start = actions.index(group._group_actions[0])
except ValueError:
continue
else:
- group_action_count = len(group._group_actions)
- end = start + group_action_count
+ end = start + len(group._group_actions)
if actions[start:end] == group._group_actions:
-
- suppressed_actions_count = 0
- for action in group._group_actions:
- group_actions.add(action)
- if action.help is SUPPRESS:
- suppressed_actions_count += 1
-
- exposed_actions_count = group_action_count - suppressed_actions_count
-
- if not group.required:
- if start in inserts:
- inserts[start] += ' ['
- else:
- inserts[start] = '['
- if end in inserts:
- inserts[end] += ']'
- else:
- inserts[end] = ']'
- elif exposed_actions_count > 1:
- if start in inserts:
- inserts[start] += ' ('
- else:
- inserts[start] = '('
- if end in inserts:
- inserts[end] += ')'
- else:
- inserts[end] = ')'
- for i in range(start + 1, end):
- inserts[i] = '|'
+ group_actions.update(group._group_actions)
+ inserts[start, end] = group
# collect all actions format strings
parts = []
- for i, action in enumerate(actions):
+ for action in actions:
# suppressed arguments are marked with None
- # remove | separators for suppressed arguments
if action.help is SUPPRESS:
- parts.append(None)
- if inserts.get(i) == '|':
- inserts.pop(i)
- elif inserts.get(i + 1) == '|':
- inserts.pop(i + 1)
+ part = None
# produce all arg strings
elif not action.option_strings:
@@ -460,9 +425,6 @@ def _format_actions_usage(self, actions, groups):
if part[0] == '[' and part[-1] == ']':
part = part[1:-1]
- # add the action string to the list
- parts.append(part)
-
# produce the first way to invoke the option in brackets
else:
option_string = action.option_strings[0]
@@ -483,26 +445,32 @@ def _format_actions_usage(self, actions, groups):
if not action.required and action not in group_actions:
part = '[%s]' % part
- # add the action string to the list
- parts.append(part)
-
- # insert things at the necessary indices
- for i in sorted(inserts, reverse=True):
- parts[i:i] = [inserts[i]]
-
- # join all the action items with spaces
- text = ' '.join([item for item in parts if item is not None])
+ # add the action string to the list
+ parts.append(part)
- # clean up separators for mutually exclusive groups
- open = r'[\[(]'
- close = r'[\])]'
- text = _re.sub(r'(%s) ' % open, r'\1', text)
- text = _re.sub(r' (%s)' % close, r'\1', text)
- text = _re.sub(r'%s *%s' % (open, close), r'', text)
- text = text.strip()
-
- # return the text
- return text
+ # group mutually exclusive actions
+ inserted_separators_indices = set()
+ for start, end in sorted(inserts, reverse=True):
+ group = inserts[start, end]
+ group_parts = [item for item in parts[start:end] if item is not None]
+ group_size = len(group_parts)
+ if group.required:
+ open, close = "()" if group_size > 1 else ("", "")
+ else:
+ open, close = "[]"
+ group_parts[0] = open + group_parts[0]
+ group_parts[-1] = group_parts[-1] + close
+ for i, part in enumerate(group_parts[:-1], start=start):
+ # insert a separator if not already done in a nested group
+ if i not in inserted_separators_indices:
+ parts[i] = part + ' |'
+ inserted_separators_indices.add(i)
+ parts[start + group_size - 1] = group_parts[-1]
+ for i in range(start + group_size, end):
+ parts[i] = None
+
+ # return the usage parts
+ return [item for item in parts if item is not None]
def _format_text(self, text):
if '%(prog)' in text:
@@ -562,33 +530,27 @@ def _format_action(self, action):
def _format_action_invocation(self, action):
if not action.option_strings:
default = self._get_default_metavar_for_positional(action)
- metavar, = self._metavar_formatter(action, default)(1)
- return metavar
+ return ' '.join(self._metavar_formatter(action, default)(1))
else:
- parts = []
# if the Optional doesn't take a value, format is:
# -s, --long
if action.nargs == 0:
- parts.extend(action.option_strings)
+ return ', '.join(action.option_strings)
# if the Optional takes a value, format is:
- # -s ARGS, --long ARGS
+ # -s, --long ARGS
else:
default = self._get_default_metavar_for_optional(action)
args_string = self._format_args(action, default)
- for option_string in action.option_strings:
- parts.append('%s %s' % (option_string, args_string))
-
- return ', '.join(parts)
+ return ', '.join(action.option_strings) + ' ' + args_string
def _metavar_formatter(self, action, default_metavar):
if action.metavar is not None:
result = action.metavar
elif action.choices is not None:
- choice_strs = [str(choice) for choice in action.choices]
- result = '{%s}' % ','.join(choice_strs)
+ result = '{%s}' % ','.join(map(str, action.choices))
else:
result = default_metavar
@@ -636,8 +598,7 @@ def _expand_help(self, action):
if hasattr(params[name], '__name__'):
params[name] = params[name].__name__
if params.get('choices') is not None:
- choices_str = ', '.join([str(c) for c in params['choices']])
- params['choices'] = choices_str
+ params['choices'] = ', '.join(map(str, params['choices']))
return self._get_help_string(action) % params
def _iter_indented_subactions(self, action):
@@ -704,14 +665,6 @@ class ArgumentDefaultsHelpFormatter(HelpFormatter):
"""
def _get_help_string(self, action):
- """
- Add the default value to the option help message.
-
- ArgumentDefaultsHelpFormatter and BooleanOptionalAction when it isn't
- already present. This code will do that, detecting cornercases to
- prevent duplicates or cases where it wouldn't make sense to the end
- user.
- """
help = action.help
if help is None:
help = ''
@@ -720,7 +673,7 @@ def _get_help_string(self, action):
if action.default is not SUPPRESS:
defaulting_nargs = [OPTIONAL, ZERO_OR_MORE]
if action.option_strings or action.nargs in defaulting_nargs:
- help += ' (default: %(default)s)'
+ help += _(' (default: %(default)s)')
return help
@@ -750,11 +703,19 @@ def _get_action_name(argument):
elif argument.option_strings:
return '/'.join(argument.option_strings)
elif argument.metavar not in (None, SUPPRESS):
- return argument.metavar
+ metavar = argument.metavar
+ if not isinstance(metavar, tuple):
+ return metavar
+ if argument.nargs == ZERO_OR_MORE and len(metavar) == 2:
+ return '%s[, %s]' % metavar
+ elif argument.nargs == ONE_OR_MORE:
+ return '%s[, %s]' % metavar
+ else:
+ return ', '.join(metavar)
elif argument.dest not in (None, SUPPRESS):
return argument.dest
elif argument.choices:
- return '{' + ','.join(argument.choices) + '}'
+ return '{%s}' % ','.join(map(str, argument.choices))
else:
return None
@@ -849,7 +810,8 @@ def __init__(self,
choices=None,
required=False,
help=None,
- metavar=None):
+ metavar=None,
+ deprecated=False):
self.option_strings = option_strings
self.dest = dest
self.nargs = nargs
@@ -860,6 +822,7 @@ def __init__(self,
self.required = required
self.help = help
self.metavar = metavar
+ self.deprecated = deprecated
def _get_kwargs(self):
names = [
@@ -873,6 +836,7 @@ def _get_kwargs(self):
'required',
'help',
'metavar',
+ 'deprecated',
]
return [(name, getattr(self, name)) for name in names]
@@ -895,7 +859,8 @@ def __init__(self,
choices=_deprecated_default,
required=False,
help=None,
- metavar=_deprecated_default):
+ metavar=_deprecated_default,
+ deprecated=False):
_option_strings = []
for option_string in option_strings:
@@ -910,6 +875,7 @@ def __init__(self,
# parser.add_argument('-f', action=BooleanOptionalAction, type=int)
for field_name in ('type', 'choices', 'metavar'):
if locals()[field_name] is not _deprecated_default:
+ import warnings
warnings._deprecated(
field_name,
"{name!r} is deprecated as of Python 3.12 and will be "
@@ -932,7 +898,8 @@ def __init__(self,
choices=choices,
required=required,
help=help,
- metavar=metavar)
+ metavar=metavar,
+ deprecated=deprecated)
def __call__(self, parser, namespace, values, option_string=None):
@@ -955,7 +922,8 @@ def __init__(self,
choices=None,
required=False,
help=None,
- metavar=None):
+ metavar=None,
+ deprecated=False):
if nargs == 0:
raise ValueError('nargs for store actions must be != 0; if you '
'have nothing to store, actions such as store '
@@ -972,7 +940,8 @@ def __init__(self,
choices=choices,
required=required,
help=help,
- metavar=metavar)
+ metavar=metavar,
+ deprecated=deprecated)
def __call__(self, parser, namespace, values, option_string=None):
setattr(namespace, self.dest, values)
@@ -987,7 +956,8 @@ def __init__(self,
default=None,
required=False,
help=None,
- metavar=None):
+ metavar=None,
+ deprecated=False):
super(_StoreConstAction, self).__init__(
option_strings=option_strings,
dest=dest,
@@ -995,7 +965,8 @@ def __init__(self,
const=const,
default=default,
required=required,
- help=help)
+ help=help,
+ deprecated=deprecated)
def __call__(self, parser, namespace, values, option_string=None):
setattr(namespace, self.dest, self.const)
@@ -1008,14 +979,16 @@ def __init__(self,
dest,
default=False,
required=False,
- help=None):
+ help=None,
+ deprecated=False):
super(_StoreTrueAction, self).__init__(
option_strings=option_strings,
dest=dest,
const=True,
- default=default,
+ deprecated=deprecated,
required=required,
- help=help)
+ help=help,
+ default=default)
class _StoreFalseAction(_StoreConstAction):
@@ -1025,14 +998,16 @@ def __init__(self,
dest,
default=True,
required=False,
- help=None):
+ help=None,
+ deprecated=False):
super(_StoreFalseAction, self).__init__(
option_strings=option_strings,
dest=dest,
const=False,
default=default,
required=required,
- help=help)
+ help=help,
+ deprecated=deprecated)
class _AppendAction(Action):
@@ -1047,7 +1022,8 @@ def __init__(self,
choices=None,
required=False,
help=None,
- metavar=None):
+ metavar=None,
+ deprecated=False):
if nargs == 0:
raise ValueError('nargs for append actions must be != 0; if arg '
'strings are not supplying the value to append, '
@@ -1064,7 +1040,8 @@ def __init__(self,
choices=choices,
required=required,
help=help,
- metavar=metavar)
+ metavar=metavar,
+ deprecated=deprecated)
def __call__(self, parser, namespace, values, option_string=None):
items = getattr(namespace, self.dest, None)
@@ -1082,7 +1059,8 @@ def __init__(self,
default=None,
required=False,
help=None,
- metavar=None):
+ metavar=None,
+ deprecated=False):
super(_AppendConstAction, self).__init__(
option_strings=option_strings,
dest=dest,
@@ -1091,7 +1069,8 @@ def __init__(self,
default=default,
required=required,
help=help,
- metavar=metavar)
+ metavar=metavar,
+ deprecated=deprecated)
def __call__(self, parser, namespace, values, option_string=None):
items = getattr(namespace, self.dest, None)
@@ -1107,14 +1086,16 @@ def __init__(self,
dest,
default=None,
required=False,
- help=None):
+ help=None,
+ deprecated=False):
super(_CountAction, self).__init__(
option_strings=option_strings,
dest=dest,
nargs=0,
default=default,
required=required,
- help=help)
+ help=help,
+ deprecated=deprecated)
def __call__(self, parser, namespace, values, option_string=None):
count = getattr(namespace, self.dest, None)
@@ -1129,13 +1110,15 @@ def __init__(self,
option_strings,
dest=SUPPRESS,
default=SUPPRESS,
- help=None):
+ help=None,
+ deprecated=False):
super(_HelpAction, self).__init__(
option_strings=option_strings,
dest=dest,
default=default,
nargs=0,
- help=help)
+ help=help,
+ deprecated=deprecated)
def __call__(self, parser, namespace, values, option_string=None):
parser.print_help()
@@ -1149,7 +1132,10 @@ def __init__(self,
version=None,
dest=SUPPRESS,
default=SUPPRESS,
- help="show program's version number and exit"):
+ help=None,
+ deprecated=False):
+ if help is None:
+ help = _("show program's version number and exit")
super(_VersionAction, self).__init__(
option_strings=option_strings,
dest=dest,
@@ -1193,6 +1179,7 @@ def __init__(self,
self._parser_class = parser_class
self._name_parser_map = {}
self._choices_actions = []
+ self._deprecated = set()
super(_SubParsersAction, self).__init__(
option_strings=option_strings,
@@ -1203,7 +1190,7 @@ def __init__(self,
help=help,
metavar=metavar)
- def add_parser(self, name, **kwargs):
+ def add_parser(self, name, *, deprecated=False, **kwargs):
# set prog from the existing prefix
if kwargs.get('prog') is None:
kwargs['prog'] = '%s %s' % (self._prog_prefix, name)
@@ -1231,6 +1218,10 @@ def add_parser(self, name, **kwargs):
for alias in aliases:
self._name_parser_map[alias] = parser
+ if deprecated:
+ self._deprecated.add(name)
+ self._deprecated.update(aliases)
+
return parser
def _get_subactions(self):
@@ -1246,13 +1237,17 @@ def __call__(self, parser, namespace, values, option_string=None):
# select the parser
try:
- parser = self._name_parser_map[parser_name]
+ subparser = self._name_parser_map[parser_name]
except KeyError:
args = {'parser_name': parser_name,
'choices': ', '.join(self._name_parser_map)}
msg = _('unknown parser %(parser_name)r (choices: %(choices)s)') % args
raise ArgumentError(self, msg)
+ if parser_name in self._deprecated:
+ parser._warning(_("command '%(parser_name)s' is deprecated") %
+ {'parser_name': parser_name})
+
# parse all the remaining options into the namespace
# store any unrecognized options on the object, so that the top
# level parser can decide what to do with them
@@ -1260,12 +1255,13 @@ def __call__(self, parser, namespace, values, option_string=None):
# In case this subparser defines new defaults, we parse them
# in a new namespace object and then update the original
# namespace for the relevant parts.
- subnamespace, arg_strings = parser.parse_known_args(arg_strings, None)
+ subnamespace, arg_strings = subparser.parse_known_args(arg_strings, None)
for key, value in vars(subnamespace).items():
setattr(namespace, key, value)
if arg_strings:
- vars(namespace).setdefault(_UNRECOGNIZED_ARGS_ATTR, [])
+ if not hasattr(namespace, _UNRECOGNIZED_ARGS_ATTR):
+ setattr(namespace, _UNRECOGNIZED_ARGS_ATTR, [])
getattr(namespace, _UNRECOGNIZED_ARGS_ATTR).extend(arg_strings)
class _ExtendAction(_AppendAction):
@@ -1409,6 +1405,7 @@ def __init__(self,
# ====================
# Registration methods
# ====================
+
def register(self, registry_name, value, object):
registry = self._registries.setdefault(registry_name, {})
registry[value] = object
@@ -1419,6 +1416,7 @@ def _registry_get(self, registry_name, value, default=None):
# ==================================
# Namespace default accessor methods
# ==================================
+
def set_defaults(self, **kwargs):
self._defaults.update(kwargs)
@@ -1438,6 +1436,7 @@ def get_default(self, dest):
# =======================
# Adding argument actions
# =======================
+
def add_argument(self, *args, **kwargs):
"""
add_argument(dest, ..., name=value, ...)
@@ -1528,6 +1527,8 @@ def _add_container_actions(self, container):
title_group_map = {}
for group in self._action_groups:
if group.title in title_group_map:
+ # This branch could happen if a derived class added
+ # groups with duplicated titles in __init__
msg = _('cannot merge actions - two groups are named %r')
raise ValueError(msg % (group.title))
title_group_map[group.title] = group
@@ -1552,7 +1553,11 @@ def _add_container_actions(self, container):
# NOTE: if add_mutually_exclusive_group ever gains title= and
# description= then this code will need to be expanded as above
for group in container._mutually_exclusive_groups:
- mutex_group = self.add_mutually_exclusive_group(
+ if group._container is container:
+ cont = self
+ else:
+ cont = title_group_map[group._container.title]
+ mutex_group = cont.add_mutually_exclusive_group(
required=group.required)
# map the actions to their new mutex group
@@ -1571,9 +1576,8 @@ def _get_positional_kwargs(self, dest, **kwargs):
# mark positional arguments as required if at least one is
# always required
- if kwargs.get('nargs') not in [OPTIONAL, ZERO_OR_MORE]:
- kwargs['required'] = True
- if kwargs.get('nargs') == ZERO_OR_MORE and 'default' not in kwargs:
+ nargs = kwargs.get('nargs')
+ if nargs not in [OPTIONAL, ZERO_OR_MORE, REMAINDER, SUPPRESS, 0]:
kwargs['required'] = True
# return the keyword arguments with no option strings
@@ -1698,6 +1702,7 @@ def _remove_action(self, action):
self._group_actions.remove(action)
def add_argument_group(self, *args, **kwargs):
+ import warnings
warnings.warn(
"Nesting argument groups is deprecated.",
category=DeprecationWarning,
@@ -1726,6 +1731,7 @@ def _remove_action(self, action):
self._group_actions.remove(action)
def add_mutually_exclusive_group(self, *args, **kwargs):
+ import warnings
warnings.warn(
"Nesting mutually exclusive groups is deprecated.",
category=DeprecationWarning,
@@ -1811,17 +1817,16 @@ def identity(string):
# add parent arguments and defaults
for parent in parents:
+ if not isinstance(parent, ArgumentParser):
+ raise TypeError('parents must be a list of ArgumentParser')
self._add_container_actions(parent)
- try:
- defaults = parent._defaults
- except AttributeError:
- pass
- else:
- self._defaults.update(defaults)
+ defaults = parent._defaults
+ self._defaults.update(defaults)
# =======================
# Pretty __repr__ methods
# =======================
+
def _get_kwargs(self):
names = [
'prog',
@@ -1836,16 +1841,17 @@ def _get_kwargs(self):
# ==================================
# Optional/Positional adding methods
# ==================================
+
def add_subparsers(self, **kwargs):
if self._subparsers is not None:
- self.error(_('cannot have multiple subparser arguments'))
+ raise ArgumentError(None, _('cannot have multiple subparser arguments'))
# add the parser class to the arguments if it's not present
kwargs.setdefault('parser_class', type(self))
if 'title' in kwargs or 'description' in kwargs:
- title = _(kwargs.pop('title', 'subcommands'))
- description = _(kwargs.pop('description', None))
+ title = kwargs.pop('title', _('subcommands'))
+ description = kwargs.pop('description', None)
self._subparsers = self.add_argument_group(title, description)
else:
self._subparsers = self._positionals
@@ -1887,14 +1893,21 @@ def _get_positional_actions(self):
# =====================================
# Command line argument parsing methods
# =====================================
+
def parse_args(self, args=None, namespace=None):
args, argv = self.parse_known_args(args, namespace)
if argv:
- msg = _('unrecognized arguments: %s')
- self.error(msg % ' '.join(argv))
+ msg = _('unrecognized arguments: %s') % ' '.join(argv)
+ if self.exit_on_error:
+ self.error(msg)
+ else:
+ raise ArgumentError(None, msg)
return args
def parse_known_args(self, args=None, namespace=None):
+ return self._parse_known_args2(args, namespace, intermixed=False)
+
+ def _parse_known_args2(self, args, namespace, intermixed):
if args is None:
# args default to the system args
args = _sys.argv[1:]
@@ -1921,18 +1934,18 @@ def parse_known_args(self, args=None, namespace=None):
# parse the arguments and exit if there are any errors
if self.exit_on_error:
try:
- namespace, args = self._parse_known_args(args, namespace)
+ namespace, args = self._parse_known_args(args, namespace, intermixed)
except ArgumentError as err:
self.error(str(err))
else:
- namespace, args = self._parse_known_args(args, namespace)
+ namespace, args = self._parse_known_args(args, namespace, intermixed)
if hasattr(namespace, _UNRECOGNIZED_ARGS_ATTR):
args.extend(getattr(namespace, _UNRECOGNIZED_ARGS_ATTR))
delattr(namespace, _UNRECOGNIZED_ARGS_ATTR)
return namespace, args
- def _parse_known_args(self, arg_strings, namespace):
+ def _parse_known_args(self, arg_strings, namespace, intermixed):
# replace arg strings that are file references
if self.fromfile_prefix_chars is not None:
arg_strings = self._read_args_from_files(arg_strings)
@@ -1964,11 +1977,11 @@ def _parse_known_args(self, arg_strings, namespace):
# otherwise, add the arg to the arg strings
# and note the index if it was an option
else:
- option_tuple = self._parse_optional(arg_string)
- if option_tuple is None:
+ option_tuples = self._parse_optional(arg_string)
+ if option_tuples is None:
pattern = 'A'
else:
- option_string_indices[i] = option_tuple
+ option_string_indices[i] = option_tuples
pattern = 'O'
arg_string_pattern_parts.append(pattern)
@@ -1978,15 +1991,15 @@ def _parse_known_args(self, arg_strings, namespace):
# converts arg strings to the appropriate and then takes the action
seen_actions = set()
seen_non_default_actions = set()
+ warned = set()
def take_action(action, argument_strings, option_string=None):
seen_actions.add(action)
argument_values = self._get_values(action, argument_strings)
# error if this argument is not allowed with other previously
- # seen arguments, assuming that actions that use the default
- # value don't really count as "present"
- if argument_values is not action.default:
+ # seen arguments
+ if action.option_strings or argument_strings:
seen_non_default_actions.add(action)
for conflict_action in action_conflicts.get(action, []):
if conflict_action in seen_non_default_actions:
@@ -2003,8 +2016,16 @@ def take_action(action, argument_strings, option_string=None):
def consume_optional(start_index):
# get the optional identified at this index
- option_tuple = option_string_indices[start_index]
- action, option_string, explicit_arg = option_tuple
+ option_tuples = option_string_indices[start_index]
+ # if multiple actions match, the option string was ambiguous
+ if len(option_tuples) > 1:
+ options = ', '.join([option_string
+ for action, option_string, sep, explicit_arg in option_tuples])
+ args = {'option': arg_strings[start_index], 'matches': options}
+ msg = _('ambiguous option: %(option)s could match %(matches)s')
+ raise ArgumentError(None, msg % args)
+
+ action, option_string, sep, explicit_arg = option_tuples[0]
# identify additional optionals in the same arg string
# (e.g. -xyz is the same as -x -y -z if no args are required)
@@ -2015,6 +2036,7 @@ def consume_optional(start_index):
# if we found no optional action, skip it
if action is None:
extras.append(arg_strings[start_index])
+ extras_pattern.append('O')
return start_index + 1
# if there is an explicit argument, try to match the
@@ -2031,18 +2053,28 @@ def consume_optional(start_index):
and option_string[1] not in chars
and explicit_arg != ''
):
+ if sep or explicit_arg[0] in chars:
+ msg = _('ignored explicit argument %r')
+ raise ArgumentError(action, msg % explicit_arg)
action_tuples.append((action, [], option_string))
char = option_string[0]
option_string = char + explicit_arg[0]
- new_explicit_arg = explicit_arg[1:] or None
optionals_map = self._option_string_actions
if option_string in optionals_map:
action = optionals_map[option_string]
- explicit_arg = new_explicit_arg
+ explicit_arg = explicit_arg[1:]
+ if not explicit_arg:
+ sep = explicit_arg = None
+ elif explicit_arg[0] == '=':
+ sep = '='
+ explicit_arg = explicit_arg[1:]
+ else:
+ sep = ''
else:
- msg = _('ignored explicit argument %r')
- raise ArgumentError(action, msg % explicit_arg)
-
+ extras.append(char + explicit_arg)
+ extras_pattern.append('O')
+ stop = start_index + 1
+ break
# if the action expect exactly one argument, we've
# successfully matched the option; exit the loop
elif arg_count == 1:
@@ -2073,6 +2105,10 @@ def consume_optional(start_index):
# the Optional's string args stopped
assert action_tuples
for action, args, option_string in action_tuples:
+ if action.deprecated and option_string not in warned:
+ self._warning(_("option '%(option)s' is deprecated") %
+ {'option': option_string})
+ warned.add(option_string)
take_action(action, args, option_string)
return stop
@@ -2091,7 +2127,20 @@ def consume_positionals(start_index):
# and add the Positional and its args to the list
for action, arg_count in zip(positionals, arg_counts):
args = arg_strings[start_index: start_index + arg_count]
+ # Strip out the first '--' if it is not in REMAINDER arg.
+ if action.nargs == PARSER:
+ if arg_strings_pattern[start_index] == '-':
+ assert args[0] == '--'
+ args.remove('--')
+ elif action.nargs != REMAINDER:
+ if (arg_strings_pattern.find('-', start_index,
+ start_index + arg_count) >= 0):
+ args.remove('--')
start_index += arg_count
+ if args and action.deprecated and action.dest not in warned:
+ self._warning(_("argument '%(argument_name)s' is deprecated") %
+ {'argument_name': action.dest})
+ warned.add(action.dest)
take_action(action, args)
# slice off the Positionals that we just parsed and return the
@@ -2102,6 +2151,7 @@ def consume_positionals(start_index):
# consume Positionals and Optionals alternately, until we have
# passed the last option string
extras = []
+ extras_pattern = []
start_index = 0
if option_string_indices:
max_option_string_index = max(option_string_indices)
@@ -2110,11 +2160,12 @@ def consume_positionals(start_index):
while start_index <= max_option_string_index:
# consume any Positionals preceding the next option
- next_option_string_index = min([
- index
- for index in option_string_indices
- if index >= start_index])
- if start_index != next_option_string_index:
+ next_option_string_index = start_index
+ while next_option_string_index <= max_option_string_index:
+ if next_option_string_index in option_string_indices:
+ break
+ next_option_string_index += 1
+ if not intermixed and start_index != next_option_string_index:
positionals_end_index = consume_positionals(start_index)
# only try to parse the next optional if we didn't consume
@@ -2130,16 +2181,35 @@ def consume_positionals(start_index):
if start_index not in option_string_indices:
strings = arg_strings[start_index:next_option_string_index]
extras.extend(strings)
+ extras_pattern.extend(arg_strings_pattern[start_index:next_option_string_index])
start_index = next_option_string_index
# consume the next optional and any arguments for it
start_index = consume_optional(start_index)
- # consume any positionals following the last Optional
- stop_index = consume_positionals(start_index)
+ if not intermixed:
+ # consume any positionals following the last Optional
+ stop_index = consume_positionals(start_index)
- # if we didn't consume all the argument strings, there were extras
- extras.extend(arg_strings[stop_index:])
+ # if we didn't consume all the argument strings, there were extras
+ extras.extend(arg_strings[stop_index:])
+ else:
+ extras.extend(arg_strings[start_index:])
+ extras_pattern.extend(arg_strings_pattern[start_index:])
+ extras_pattern = ''.join(extras_pattern)
+ assert len(extras_pattern) == len(extras)
+ # consume all positionals
+ arg_strings = [s for s, c in zip(extras, extras_pattern) if c != 'O']
+ arg_strings_pattern = extras_pattern.replace('O', '')
+ stop_index = consume_positionals(0)
+ # leave unknown optionals and non-consumed positionals in extras
+ for i, c in enumerate(extras_pattern):
+ if not stop_index:
+ break
+ if c != 'O':
+ stop_index -= 1
+ extras[i] = None
+ extras = [s for s in extras if s is not None]
# make sure all required actions were present and also convert
# action defaults which were not given as arguments
@@ -2161,7 +2231,7 @@ def consume_positionals(start_index):
self._get_value(action, action.default))
if required_actions:
- self.error(_('the following arguments are required: %s') %
+ raise ArgumentError(None, _('the following arguments are required: %s') %
', '.join(required_actions))
# make sure all required groups had one option present
@@ -2177,7 +2247,7 @@ def consume_positionals(start_index):
for action in group._group_actions
if action.help is not SUPPRESS]
msg = _('one of the arguments %s is required')
- self.error(msg % ' '.join(names))
+ raise ArgumentError(None, msg % ' '.join(names))
# return the updated namespace and the extra arguments
return namespace, extras
@@ -2204,7 +2274,7 @@ def _read_args_from_files(self, arg_strings):
arg_strings = self._read_args_from_files(arg_strings)
new_arg_strings.extend(arg_strings)
except OSError as err:
- self.error(str(err))
+ raise ArgumentError(None, str(err))
# return the modified argument list
return new_arg_strings
@@ -2237,18 +2307,19 @@ def _match_argument(self, action, arg_strings_pattern):
def _match_arguments_partial(self, actions, arg_strings_pattern):
# progressively shorten the actions list by slicing off the
# final actions until we find a match
- result = []
for i in range(len(actions), 0, -1):
actions_slice = actions[:i]
pattern = ''.join([self._get_nargs_pattern(action)
for action in actions_slice])
match = _re.match(pattern, arg_strings_pattern)
if match is not None:
- result.extend([len(string) for string in match.groups()])
- break
-
- # return the list of arg string counts
- return result
+ result = [len(string) for string in match.groups()]
+ if (match.end() < len(arg_strings_pattern)
+ and arg_strings_pattern[match.end()] == 'O'):
+ while result and not result[-1]:
+ del result[-1]
+ return result
+ return []
def _parse_optional(self, arg_string):
# if it's an empty string, it was meant to be a positional
@@ -2262,36 +2333,24 @@ def _parse_optional(self, arg_string):
# if the option string is present in the parser, return the action
if arg_string in self._option_string_actions:
action = self._option_string_actions[arg_string]
- return action, arg_string, None
+ return [(action, arg_string, None, None)]
# if it's just a single character, it was meant to be positional
if len(arg_string) == 1:
return None
# if the option string before the "=" is present, return the action
- if '=' in arg_string:
- option_string, explicit_arg = arg_string.split('=', 1)
- if option_string in self._option_string_actions:
- action = self._option_string_actions[option_string]
- return action, option_string, explicit_arg
+ option_string, sep, explicit_arg = arg_string.partition('=')
+ if sep and option_string in self._option_string_actions:
+ action = self._option_string_actions[option_string]
+ return [(action, option_string, sep, explicit_arg)]
# search through all possible prefixes of the option string
# and all actions in the parser for possible interpretations
option_tuples = self._get_option_tuples(arg_string)
- # if multiple actions match, the option string was ambiguous
- if len(option_tuples) > 1:
- options = ', '.join([option_string
- for action, option_string, explicit_arg in option_tuples])
- args = {'option': arg_string, 'matches': options}
- msg = _('ambiguous option: %(option)s could match %(matches)s')
- self.error(msg % args)
-
- # if exactly one action matched, this segmentation is good,
- # so return the parsed action
- elif len(option_tuples) == 1:
- option_tuple, = option_tuples
- return option_tuple
+ if option_tuples:
+ return option_tuples
# if it was not found as an option, but it looks like a negative
# number, it was meant to be positional
@@ -2306,7 +2365,7 @@ def _parse_optional(self, arg_string):
# it was meant to be an optional but there is no such option
# in this parser (though it might be a valid option in a subparser)
- return None, arg_string, None
+ return [(None, arg_string, None, None)]
def _get_option_tuples(self, option_string):
result = []
@@ -2316,39 +2375,38 @@ def _get_option_tuples(self, option_string):
chars = self.prefix_chars
if option_string[0] in chars and option_string[1] in chars:
if self.allow_abbrev:
- if '=' in option_string:
- option_prefix, explicit_arg = option_string.split('=', 1)
- else:
- option_prefix = option_string
- explicit_arg = None
+ option_prefix, sep, explicit_arg = option_string.partition('=')
+ if not sep:
+ sep = explicit_arg = None
for option_string in self._option_string_actions:
if option_string.startswith(option_prefix):
action = self._option_string_actions[option_string]
- tup = action, option_string, explicit_arg
+ tup = action, option_string, sep, explicit_arg
result.append(tup)
# single character options can be concatenated with their arguments
# but multiple character options always have to have their argument
# separate
elif option_string[0] in chars and option_string[1] not in chars:
- option_prefix = option_string
- explicit_arg = None
+ option_prefix, sep, explicit_arg = option_string.partition('=')
+ if not sep:
+ sep = explicit_arg = None
short_option_prefix = option_string[:2]
short_explicit_arg = option_string[2:]
for option_string in self._option_string_actions:
if option_string == short_option_prefix:
action = self._option_string_actions[option_string]
- tup = action, option_string, short_explicit_arg
+ tup = action, option_string, '', short_explicit_arg
result.append(tup)
- elif option_string.startswith(option_prefix):
+ elif self.allow_abbrev and option_string.startswith(option_prefix):
action = self._option_string_actions[option_string]
- tup = action, option_string, explicit_arg
+ tup = action, option_string, sep, explicit_arg
result.append(tup)
# shouldn't ever get here
else:
- self.error(_('unexpected option string: %s') % option_string)
+ raise ArgumentError(None, _('unexpected option string: %s') % option_string)
# return the collected option tuples
return result
@@ -2357,43 +2415,40 @@ def _get_nargs_pattern(self, action):
# in all examples below, we have to allow for '--' args
# which are represented as '-' in the pattern
nargs = action.nargs
+ # if this is an optional action, -- is not allowed
+ option = action.option_strings
# the default (None) is assumed to be a single argument
if nargs is None:
- nargs_pattern = '(-*A-*)'
+ nargs_pattern = '([A])' if option else '(-*A-*)'
# allow zero or one arguments
elif nargs == OPTIONAL:
- nargs_pattern = '(-*A?-*)'
+ nargs_pattern = '(A?)' if option else '(-*A?-*)'
# allow zero or more arguments
elif nargs == ZERO_OR_MORE:
- nargs_pattern = '(-*[A-]*)'
+ nargs_pattern = '(A*)' if option else '(-*[A-]*)'
# allow one or more arguments
elif nargs == ONE_OR_MORE:
- nargs_pattern = '(-*A[A-]*)'
+ nargs_pattern = '(A+)' if option else '(-*A[A-]*)'
# allow any number of options or arguments
elif nargs == REMAINDER:
- nargs_pattern = '([-AO]*)'
+ nargs_pattern = '([AO]*)' if option else '(.*)'
# allow one argument followed by any number of options or arguments
elif nargs == PARSER:
- nargs_pattern = '(-*A[-AO]*)'
+ nargs_pattern = '(A[AO]*)' if option else '(-*A[-AO]*)'
# suppress action, like nargs=0
elif nargs == SUPPRESS:
- nargs_pattern = '(-*-*)'
+ nargs_pattern = '()' if option else '(-*)'
# all others should be integers
else:
- nargs_pattern = '(-*%s-*)' % '-*'.join('A' * nargs)
-
- # if this is an optional action, -- is not allowed
- if action.option_strings:
- nargs_pattern = nargs_pattern.replace('-*', '')
- nargs_pattern = nargs_pattern.replace('-', '')
+ nargs_pattern = '([AO]{%d})' % nargs if option else '((?:-*A){%d}-*)' % nargs
# return the pattern
return nargs_pattern
@@ -2405,8 +2460,11 @@ def _get_nargs_pattern(self, action):
def parse_intermixed_args(self, args=None, namespace=None):
args, argv = self.parse_known_intermixed_args(args, namespace)
if argv:
- msg = _('unrecognized arguments: %s')
- self.error(msg % ' '.join(argv))
+ msg = _('unrecognized arguments: %s') % ' '.join(argv)
+ if self.exit_on_error:
+ self.error(msg)
+ else:
+ raise ArgumentError(None, msg)
return args
def parse_known_intermixed_args(self, args=None, namespace=None):
@@ -2417,10 +2475,6 @@ def parse_known_intermixed_args(self, args=None, namespace=None):
# are then parsed. If the parser definition is incompatible with the
# intermixed assumptions (e.g. use of REMAINDER, subparsers) a
# TypeError is raised.
- #
- # positionals are 'deactivated' by setting nargs and default to
- # SUPPRESS. This blocks the addition of that positional to the
- # namespace
positionals = self._get_positional_actions()
a = [action for action in positionals
@@ -2429,78 +2483,20 @@ def parse_known_intermixed_args(self, args=None, namespace=None):
raise TypeError('parse_intermixed_args: positional arg'
' with nargs=%s'%a[0].nargs)
- if [action.dest for group in self._mutually_exclusive_groups
- for action in group._group_actions if action in positionals]:
- raise TypeError('parse_intermixed_args: positional in'
- ' mutuallyExclusiveGroup')
-
- try:
- save_usage = self.usage
- try:
- if self.usage is None:
- # capture the full usage for use in error messages
- self.usage = self.format_usage()[7:]
- for action in positionals:
- # deactivate positionals
- action.save_nargs = action.nargs
- # action.nargs = 0
- action.nargs = SUPPRESS
- action.save_default = action.default
- action.default = SUPPRESS
- namespace, remaining_args = self.parse_known_args(args,
- namespace)
- for action in positionals:
- # remove the empty positional values from namespace
- if (hasattr(namespace, action.dest)
- and getattr(namespace, action.dest)==[]):
- from warnings import warn
- warn('Do not expect %s in %s' % (action.dest, namespace))
- delattr(namespace, action.dest)
- finally:
- # restore nargs and usage before exiting
- for action in positionals:
- action.nargs = action.save_nargs
- action.default = action.save_default
- optionals = self._get_optional_actions()
- try:
- # parse positionals. optionals aren't normally required, but
- # they could be, so make sure they aren't.
- for action in optionals:
- action.save_required = action.required
- action.required = False
- for group in self._mutually_exclusive_groups:
- group.save_required = group.required
- group.required = False
- namespace, extras = self.parse_known_args(remaining_args,
- namespace)
- finally:
- # restore parser values before exiting
- for action in optionals:
- action.required = action.save_required
- for group in self._mutually_exclusive_groups:
- group.required = group.save_required
- finally:
- self.usage = save_usage
- return namespace, extras
+ return self._parse_known_args2(args, namespace, intermixed=True)
# ========================
# Value conversion methods
# ========================
- def _get_values(self, action, arg_strings):
- # for everything but PARSER, REMAINDER args, strip out first '--'
- if action.nargs not in [PARSER, REMAINDER]:
- try:
- arg_strings.remove('--')
- except ValueError:
- pass
+ def _get_values(self, action, arg_strings):
# optional argument produces a default when not present
if not arg_strings and action.nargs == OPTIONAL:
if action.option_strings:
value = action.const
else:
value = action.default
- if isinstance(value, str):
+ if isinstance(value, str) and value is not SUPPRESS:
value = self._get_value(action, value)
self._check_value(action, value)
@@ -2571,15 +2567,20 @@ def _get_value(self, action, arg_string):
def _check_value(self, action, value):
# converted value must be one of the choices (if specified)
- if action.choices is not None and value not in action.choices:
- args = {'value': value,
- 'choices': ', '.join(map(repr, action.choices))}
- msg = _('invalid choice: %(value)r (choose from %(choices)s)')
- raise ArgumentError(action, msg % args)
+ choices = action.choices
+ if choices is not None:
+ if isinstance(choices, str):
+ choices = iter(choices)
+ if value not in choices:
+ args = {'value': str(value),
+ 'choices': ', '.join(map(str, action.choices))}
+ msg = _('invalid choice: %(value)r (choose from %(choices)s)')
+ raise ArgumentError(action, msg % args)
# =======================
# Help-formatting methods
# =======================
+
def format_usage(self):
formatter = self._get_formatter()
formatter.add_usage(self.usage, self._actions,
@@ -2615,6 +2616,7 @@ def _get_formatter(self):
# =====================
# Help-printing methods
# =====================
+
def print_usage(self, file=None):
if file is None:
file = _sys.stdout
@@ -2636,6 +2638,7 @@ def _print_message(self, message, file=None):
# ===============
# Exiting methods
# ===============
+
def exit(self, status=0, message=None):
if message:
self._print_message(message, _sys.stderr)
@@ -2653,3 +2656,7 @@ def error(self, message):
self.print_usage(_sys.stderr)
args = {'prog': self.prog, 'message': message}
self.exit(2, _('%(prog)s: error: %(message)s\n') % args)
+
+ def _warning(self, message):
+ args = {'prog': self.prog, 'message': message}
+ self._print_message(_('%(prog)s: warning: %(message)s\n') % args, _sys.stderr)
diff --git a/Lib/base64.py b/Lib/base64.py
old mode 100755
new mode 100644
index e233647ee7..5a7e790a19
--- a/Lib/base64.py
+++ b/Lib/base64.py
@@ -18,7 +18,7 @@
'b64encode', 'b64decode', 'b32encode', 'b32decode',
'b32hexencode', 'b32hexdecode', 'b16encode', 'b16decode',
# Base85 and Ascii85 encodings
- 'b85encode', 'b85decode', 'a85encode', 'a85decode',
+ 'b85encode', 'b85decode', 'a85encode', 'a85decode', 'z85encode', 'z85decode',
# Standard Base64 encoding
'standard_b64encode', 'standard_b64decode',
# Some common Base64 alternatives. As referenced by RFC 3458, see thread
@@ -164,7 +164,6 @@ def urlsafe_b64decode(s):
_b32rev = {}
def _b32encode(alphabet, s):
- global _b32tab2
# Delay the initialization of the table to not waste memory
# if the function is never called
if alphabet not in _b32tab2:
@@ -200,7 +199,6 @@ def _b32encode(alphabet, s):
return bytes(encoded)
def _b32decode(alphabet, s, casefold=False, map01=None):
- global _b32rev
# Delay the initialization of the table to not waste memory
# if the function is never called
if alphabet not in _b32rev:
@@ -334,7 +332,7 @@ def a85encode(b, *, foldspaces=False, wrapcol=0, pad=False, adobe=False):
wrapcol controls whether the output should have newline (b'\\n') characters
added to it. If this is non-zero, each output line will be at most this
- many characters long.
+ many characters long, excluding the trailing newline.
pad controls whether the input is padded to a multiple of 4 before
encoding. Note that the btoa implementation always pads.
@@ -499,6 +497,33 @@ def b85decode(b):
result = result[:-padding]
return result
+_z85alphabet = (b'0123456789abcdefghijklmnopqrstuvwxyz'
+ b'ABCDEFGHIJKLMNOPQRSTUVWXYZ.-:+=^!/*?&<>()[]{}@%$#')
+# Translating b85 valid but z85 invalid chars to b'\x00' is required
+# to prevent them from being decoded as b85 valid chars.
+_z85_b85_decode_diff = b';_`|~'
+_z85_decode_translation = bytes.maketrans(
+ _z85alphabet + _z85_b85_decode_diff,
+ _b85alphabet + b'\x00' * len(_z85_b85_decode_diff)
+)
+_z85_encode_translation = bytes.maketrans(_b85alphabet, _z85alphabet)
+
+def z85encode(s):
+ """Encode bytes-like object b in z85 format and return a bytes object."""
+ return b85encode(s).translate(_z85_encode_translation)
+
+def z85decode(s):
+ """Decode the z85-encoded bytes-like object or ASCII string b
+
+ The result is returned as a bytes object.
+ """
+ s = _bytes_from_decode_data(s)
+ s = s.translate(_z85_decode_translation)
+ try:
+ return b85decode(s)
+ except ValueError as e:
+ raise ValueError(e.args[0].replace('base85', 'z85')) from None
+
# Legacy interface. This code could be cleaned up since I don't believe
# binascii has any line length limitations. It just doesn't seem worth it
# though. The files should be opened in binary mode.
diff --git a/Lib/cmd.py b/Lib/cmd.py
index 88ee7d3ddc..a37d16cd7b 100644
--- a/Lib/cmd.py
+++ b/Lib/cmd.py
@@ -42,7 +42,7 @@
functions respectively.
"""
-import string, sys
+import inspect, string, sys
__all__ = ["Cmd"]
@@ -108,7 +108,15 @@ def cmdloop(self, intro=None):
import readline
self.old_completer = readline.get_completer()
readline.set_completer(self.complete)
- readline.parse_and_bind(self.completekey+": complete")
+ if readline.backend == "editline":
+ if self.completekey == 'tab':
+ # libedit uses "^I" instead of "tab"
+ command_string = "bind ^I rl_complete"
+ else:
+ command_string = f"bind {self.completekey} rl_complete"
+ else:
+ command_string = f"{self.completekey}: complete"
+ readline.parse_and_bind(command_string)
except ImportError:
pass
try:
@@ -210,9 +218,8 @@ def onecmd(self, line):
if cmd == '':
return self.default(line)
else:
- try:
- func = getattr(self, 'do_' + cmd)
- except AttributeError:
+ func = getattr(self, 'do_' + cmd, None)
+ if func is None:
return self.default(line)
return func(arg)
@@ -298,6 +305,7 @@ def do_help(self, arg):
except AttributeError:
try:
doc=getattr(self, 'do_' + arg).__doc__
+ doc = inspect.cleandoc(doc)
if doc:
self.stdout.write("%s\n"%str(doc))
return
diff --git a/Lib/codeop.py b/Lib/codeop.py
index 96868047cb..eea6cbc701 100644
--- a/Lib/codeop.py
+++ b/Lib/codeop.py
@@ -65,14 +65,10 @@ def _maybe_compile(compiler, source, filename, symbol):
try:
compiler(source + "\n", filename, symbol)
return None
+ except _IncompleteInputError as e:
+ return None
except SyntaxError as e:
- # XXX: RustPython; support multiline definitions in REPL
- # See also: https://github.com/RustPython/RustPython/pull/5743
- strerr = str(e)
- if source.endswith(":") and "expected an indented block" in strerr:
- return None
- elif "incomplete input" in str(e):
- return None
+ pass
# fallthrough
return compiler(source, filename, symbol, incomplete_input=False)
diff --git a/Lib/compileall.py b/Lib/compileall.py
index a388931fb5..47e2446356 100644
--- a/Lib/compileall.py
+++ b/Lib/compileall.py
@@ -97,9 +97,15 @@ def compile_dir(dir, maxlevels=None, ddir=None, force=False,
files = _walk_dir(dir, quiet=quiet, maxlevels=maxlevels)
success = True
if workers != 1 and ProcessPoolExecutor is not None:
+ import multiprocessing
+ if multiprocessing.get_start_method() == 'fork':
+ mp_context = multiprocessing.get_context('forkserver')
+ else:
+ mp_context = None
# If workers == 0, let ProcessPoolExecutor choose
workers = workers or None
- with ProcessPoolExecutor(max_workers=workers) as executor:
+ with ProcessPoolExecutor(max_workers=workers,
+ mp_context=mp_context) as executor:
results = executor.map(partial(compile_file,
ddir=ddir, force=force,
rx=rx, quiet=quiet,
@@ -110,7 +116,8 @@ def compile_dir(dir, maxlevels=None, ddir=None, force=False,
prependdir=prependdir,
limit_sl_dest=limit_sl_dest,
hardlink_dupes=hardlink_dupes),
- files)
+ files,
+ chunksize=4)
success = min(results, default=True)
else:
for file in files:
@@ -166,13 +173,13 @@ def compile_file(fullname, ddir=None, force=False, rx=None, quiet=0,
if stripdir is not None:
fullname_parts = fullname.split(os.path.sep)
stripdir_parts = stripdir.split(os.path.sep)
- ddir_parts = list(fullname_parts)
-
- for spart, opart in zip(stripdir_parts, fullname_parts):
- if spart == opart:
- ddir_parts.remove(spart)
- dfile = os.path.join(*ddir_parts)
+ if stripdir_parts != fullname_parts[:len(stripdir_parts)]:
+ if quiet < 2:
+ print("The stripdir path {!r} is not a valid prefix for "
+ "source path {!r}; ignoring".format(stripdir, fullname))
+ else:
+ dfile = os.path.join(*fullname_parts[len(stripdir_parts):])
if prependdir is not None:
if dfile is None:
diff --git a/Lib/copy.py b/Lib/copy.py
index da2908ef62..2a4606246a 100644
--- a/Lib/copy.py
+++ b/Lib/copy.py
@@ -4,8 +4,9 @@
import copy
- x = copy.copy(y) # make a shallow copy of y
- x = copy.deepcopy(y) # make a deep copy of y
+ x = copy.copy(y) # make a shallow copy of y
+ x = copy.deepcopy(y) # make a deep copy of y
+ x = copy.replace(y, a=1, b=2) # new object with fields replaced, as defined by `__replace__`
For module specific errors, copy.Error is raised.
@@ -56,7 +57,7 @@ class Error(Exception):
pass
error = Error # backward compatibility
-__all__ = ["Error", "copy", "deepcopy"]
+__all__ = ["Error", "copy", "deepcopy", "replace"]
def copy(x):
"""Shallow copy operation on arbitrary Python objects.
@@ -121,13 +122,13 @@ def deepcopy(x, memo=None, _nil=[]):
See the module's __doc__ string for more info.
"""
+ d = id(x)
if memo is None:
memo = {}
-
- d = id(x)
- y = memo.get(d, _nil)
- if y is not _nil:
- return y
+ else:
+ y = memo.get(d, _nil)
+ if y is not _nil:
+ return y
cls = type(x)
@@ -290,3 +291,16 @@ def _reconstruct(x, memo, func, args,
return y
del types, weakref
+
+
+def replace(obj, /, **changes):
+ """Return a new object replacing specified fields with new values.
+
+ This is especially useful for immutable objects, like named tuples or
+ frozen dataclasses.
+ """
+ cls = obj.__class__
+ func = getattr(cls, '__replace__', None)
+ if func is None:
+ raise TypeError(f"replace() does not support {cls.__name__} objects")
+ return func(obj, **changes)
diff --git a/Lib/gzip.py b/Lib/gzip.py
index 1a3c82ce7e..a550c20a7a 100644
--- a/Lib/gzip.py
+++ b/Lib/gzip.py
@@ -5,11 +5,15 @@
# based on Andrew Kuchling's minigzip.py distributed with the zlib module
-import struct, sys, time, os
-import zlib
+import _compression
import builtins
import io
-import _compression
+import os
+import struct
+import sys
+import time
+import weakref
+import zlib
__all__ = ["BadGzipFile", "GzipFile", "open", "compress", "decompress"]
@@ -125,10 +129,13 @@ class BadGzipFile(OSError):
class _WriteBufferStream(io.RawIOBase):
"""Minimal object to pass WriteBuffer flushes into GzipFile"""
def __init__(self, gzip_file):
- self.gzip_file = gzip_file
+ self.gzip_file = weakref.ref(gzip_file)
def write(self, data):
- return self.gzip_file._write_raw(data)
+ gzip_file = self.gzip_file()
+ if gzip_file is None:
+ raise RuntimeError("lost gzip_file")
+ return gzip_file._write_raw(data)
def seekable(self):
return False
@@ -190,51 +197,58 @@ def __init__(self, filename=None, mode=None,
raise ValueError("Invalid mode: {!r}".format(mode))
if mode and 'b' not in mode:
mode += 'b'
- if fileobj is None:
- fileobj = self.myfileobj = builtins.open(filename, mode or 'rb')
- if filename is None:
- filename = getattr(fileobj, 'name', '')
- if not isinstance(filename, (str, bytes)):
- filename = ''
- else:
- filename = os.fspath(filename)
- origmode = mode
- if mode is None:
- mode = getattr(fileobj, 'mode', 'rb')
-
-
- if mode.startswith('r'):
- self.mode = READ
- raw = _GzipReader(fileobj)
- self._buffer = io.BufferedReader(raw)
- self.name = filename
-
- elif mode.startswith(('w', 'a', 'x')):
- if origmode is None:
- import warnings
- warnings.warn(
- "GzipFile was opened for writing, but this will "
- "change in future Python releases. "
- "Specify the mode argument for opening it for writing.",
- FutureWarning, 2)
- self.mode = WRITE
- self._init_write(filename)
- self.compress = zlib.compressobj(compresslevel,
- zlib.DEFLATED,
- -zlib.MAX_WBITS,
- zlib.DEF_MEM_LEVEL,
- 0)
- self._write_mtime = mtime
- self._buffer_size = _WRITE_BUFFER_SIZE
- self._buffer = io.BufferedWriter(_WriteBufferStream(self),
- buffer_size=self._buffer_size)
- else:
- raise ValueError("Invalid mode: {!r}".format(mode))
- self.fileobj = fileobj
+ try:
+ if fileobj is None:
+ fileobj = self.myfileobj = builtins.open(filename, mode or 'rb')
+ if filename is None:
+ filename = getattr(fileobj, 'name', '')
+ if not isinstance(filename, (str, bytes)):
+ filename = ''
+ else:
+ filename = os.fspath(filename)
+ origmode = mode
+ if mode is None:
+ mode = getattr(fileobj, 'mode', 'rb')
+
+
+ if mode.startswith('r'):
+ self.mode = READ
+ raw = _GzipReader(fileobj)
+ self._buffer = io.BufferedReader(raw)
+ self.name = filename
+
+ elif mode.startswith(('w', 'a', 'x')):
+ if origmode is None:
+ import warnings
+ warnings.warn(
+ "GzipFile was opened for writing, but this will "
+ "change in future Python releases. "
+ "Specify the mode argument for opening it for writing.",
+ FutureWarning, 2)
+ self.mode = WRITE
+ self._init_write(filename)
+ self.compress = zlib.compressobj(compresslevel,
+ zlib.DEFLATED,
+ -zlib.MAX_WBITS,
+ zlib.DEF_MEM_LEVEL,
+ 0)
+ self._write_mtime = mtime
+ self._buffer_size = _WRITE_BUFFER_SIZE
+ self._buffer = io.BufferedWriter(_WriteBufferStream(self),
+ buffer_size=self._buffer_size)
+ else:
+ raise ValueError("Invalid mode: {!r}".format(mode))
- if self.mode == WRITE:
- self._write_gzip_header(compresslevel)
+ self.fileobj = fileobj
+
+ if self.mode == WRITE:
+ self._write_gzip_header(compresslevel)
+ except:
+ # Avoid a ResourceWarning if the write fails,
+ # eg read-only file or KeyboardInterrupt
+ self._close()
+ raise
@property
def mtime(self):
@@ -363,11 +377,14 @@ def close(self):
elif self.mode == READ:
self._buffer.close()
finally:
- self.fileobj = None
- myfileobj = self.myfileobj
- if myfileobj:
- self.myfileobj = None
- myfileobj.close()
+ self._close()
+
+ def _close(self):
+ self.fileobj = None
+ myfileobj = self.myfileobj
+ if myfileobj is not None:
+ self.myfileobj = None
+ myfileobj.close()
def flush(self,zlib_mode=zlib.Z_SYNC_FLUSH):
self._check_not_closed()
@@ -580,12 +597,12 @@ def _rewind(self):
self._new_member = True
-def compress(data, compresslevel=_COMPRESS_LEVEL_BEST, *, mtime=0):
+def compress(data, compresslevel=_COMPRESS_LEVEL_BEST, *, mtime=None):
"""Compress data in one shot and return the compressed string.
compresslevel sets the compression level in range of 0-9.
- mtime can be used to set the modification time.
- The modification time is set to 0 by default, for reproducibility.
+ mtime can be used to set the modification time. The modification time is
+ set to the current time by default.
"""
# Wbits=31 automatically includes a gzip header and trailer.
gzip_data = zlib.compress(data, level=compresslevel, wbits=31)
diff --git a/Lib/ipaddress.py b/Lib/ipaddress.py
index 9ca90fd0f7..67e45450fc 100644
--- a/Lib/ipaddress.py
+++ b/Lib/ipaddress.py
@@ -310,7 +310,7 @@ def collapse_addresses(addresses):
[IPv4Network('192.0.2.0/24')]
Args:
- addresses: An iterator of IPv4Network or IPv6Network objects.
+ addresses: An iterable of IPv4Network or IPv6Network objects.
Returns:
An iterator of the collapsed IPv(4|6)Network objects.
@@ -734,7 +734,7 @@ def __eq__(self, other):
return NotImplemented
def __hash__(self):
- return hash(int(self.network_address) ^ int(self.netmask))
+ return hash((int(self.network_address), int(self.netmask)))
def __contains__(self, other):
# always false if one is v4 and the other is v6.
@@ -1086,7 +1086,11 @@ def is_private(self):
"""
return any(self.network_address in priv_network and
self.broadcast_address in priv_network
- for priv_network in self._constants._private_networks)
+ for priv_network in self._constants._private_networks) and all(
+ self.network_address not in network and
+ self.broadcast_address not in network
+ for network in self._constants._private_networks_exceptions
+ )
@property
def is_global(self):
@@ -1333,18 +1337,41 @@ def is_reserved(self):
@property
@functools.lru_cache()
def is_private(self):
- """Test if this address is allocated for private networks.
+ """``True`` if the address is defined as not globally reachable by
+ iana-ipv4-special-registry_ (for IPv4) or iana-ipv6-special-registry_
+ (for IPv6) with the following exceptions:
- Returns:
- A boolean, True if the address is reserved per
- iana-ipv4-special-registry.
+ * ``is_private`` is ``False`` for ``100.64.0.0/10``
+ * For IPv4-mapped IPv6-addresses the ``is_private`` value is determined by the
+ semantics of the underlying IPv4 addresses and the following condition holds
+ (see :attr:`IPv6Address.ipv4_mapped`)::
+ address.is_private == address.ipv4_mapped.is_private
+
+ ``is_private`` has value opposite to :attr:`is_global`, except for the ``100.64.0.0/10``
+ IPv4 range where they are both ``False``.
"""
- return any(self in net for net in self._constants._private_networks)
+ return (
+ any(self in net for net in self._constants._private_networks)
+ and all(self not in net for net in self._constants._private_networks_exceptions)
+ )
@property
@functools.lru_cache()
def is_global(self):
+ """``True`` if the address is defined as globally reachable by
+ iana-ipv4-special-registry_ (for IPv4) or iana-ipv6-special-registry_
+ (for IPv6) with the following exception:
+
+ For IPv4-mapped IPv6-addresses the ``is_private`` value is determined by the
+ semantics of the underlying IPv4 addresses and the following condition holds
+ (see :attr:`IPv6Address.ipv4_mapped`)::
+
+ address.is_global == address.ipv4_mapped.is_global
+
+ ``is_global`` has value opposite to :attr:`is_private`, except for the ``100.64.0.0/10``
+ IPv4 range where they are both ``False``.
+ """
return self not in self._constants._public_network and not self.is_private
@property
@@ -1389,6 +1416,16 @@ def is_link_local(self):
"""
return self in self._constants._linklocal_network
+ @property
+ def ipv6_mapped(self):
+ """Return the IPv4-mapped IPv6 address.
+
+ Returns:
+ The IPv4-mapped IPv6 address per RFC 4291.
+
+ """
+ return IPv6Address(f'::ffff:{self}')
+
class IPv4Interface(IPv4Address):
@@ -1548,13 +1585,15 @@ class _IPv4Constants:
_public_network = IPv4Network('100.64.0.0/10')
+ # Not globally reachable address blocks listed on
+ # https://www.iana.org/assignments/iana-ipv4-special-registry/iana-ipv4-special-registry.xhtml
_private_networks = [
IPv4Network('0.0.0.0/8'),
IPv4Network('10.0.0.0/8'),
IPv4Network('127.0.0.0/8'),
IPv4Network('169.254.0.0/16'),
IPv4Network('172.16.0.0/12'),
- IPv4Network('192.0.0.0/29'),
+ IPv4Network('192.0.0.0/24'),
IPv4Network('192.0.0.170/31'),
IPv4Network('192.0.2.0/24'),
IPv4Network('192.168.0.0/16'),
@@ -1565,6 +1604,11 @@ class _IPv4Constants:
IPv4Network('255.255.255.255/32'),
]
+ _private_networks_exceptions = [
+ IPv4Network('192.0.0.9/32'),
+ IPv4Network('192.0.0.10/32'),
+ ]
+
_reserved_network = IPv4Network('240.0.0.0/4')
_unspecified_address = IPv4Address('0.0.0.0')
@@ -1630,8 +1674,18 @@ def _ip_int_from_string(cls, ip_str):
"""
if not ip_str:
raise AddressValueError('Address cannot be empty')
-
- parts = ip_str.split(':')
+ if len(ip_str) > 45:
+ shorten = ip_str
+ if len(shorten) > 100:
+ shorten = f'{ip_str[:45]}({len(ip_str)-90} chars elided){ip_str[-45:]}'
+ raise AddressValueError(f"At most 45 characters expected in "
+ f"{shorten!r}")
+
+ # We want to allow more parts than the max to be 'split'
+ # to preserve the correct error message when there are
+ # too many parts combined with '::'
+ _max_parts = cls._HEXTET_COUNT + 1
+ parts = ip_str.split(':', maxsplit=_max_parts)
# An IPv6 address needs at least 2 colons (3 parts).
_min_parts = 3
@@ -1651,7 +1705,6 @@ def _ip_int_from_string(cls, ip_str):
# An IPv6 address can't have more than 8 colons (9 parts).
# The extra colon comes from using the "::" notation for a single
# leading or trailing zero part.
- _max_parts = cls._HEXTET_COUNT + 1
if len(parts) > _max_parts:
msg = "At most %d colons permitted in %r" % (_max_parts-1, ip_str)
raise AddressValueError(msg)
@@ -1923,8 +1976,49 @@ def __init__(self, address):
self._ip = self._ip_int_from_string(addr_str)
+ def _explode_shorthand_ip_string(self):
+ ipv4_mapped = self.ipv4_mapped
+ if ipv4_mapped is None:
+ return super()._explode_shorthand_ip_string()
+ prefix_len = 30
+ raw_exploded_str = super()._explode_shorthand_ip_string()
+ return f"{raw_exploded_str[:prefix_len]}{ipv4_mapped!s}"
+
+ def _reverse_pointer(self):
+ ipv4_mapped = self.ipv4_mapped
+ if ipv4_mapped is None:
+ return super()._reverse_pointer()
+ prefix_len = 30
+ raw_exploded_str = super()._explode_shorthand_ip_string()[:prefix_len]
+ # ipv4 encoded using hexadecimal nibbles instead of decimals
+ ipv4_int = ipv4_mapped._ip
+ reverse_chars = f"{raw_exploded_str}{ipv4_int:008x}"[::-1].replace(':', '')
+ return '.'.join(reverse_chars) + '.ip6.arpa'
+
+ def _ipv4_mapped_ipv6_to_str(self):
+ """Return convenient text representation of IPv4-mapped IPv6 address
+
+ See RFC 4291 2.5.5.2, 2.2 p.3 for details.
+
+ Returns:
+ A string, 'x:x:x:x:x:x:d.d.d.d', where the 'x's are the hexadecimal values of
+ the six high-order 16-bit pieces of the address, and the 'd's are
+ the decimal values of the four low-order 8-bit pieces of the
+ address (standard IPv4 representation) as defined in RFC 4291 2.2 p.3.
+
+ """
+ ipv4_mapped = self.ipv4_mapped
+ if ipv4_mapped is None:
+ raise AddressValueError("Can not apply to non-IPv4-mapped IPv6 address %s" % str(self))
+ high_order_bits = self._ip >> 32
+ return "%s:%s" % (self._string_from_ip_int(high_order_bits), str(ipv4_mapped))
+
def __str__(self):
- ip_str = super().__str__()
+ ipv4_mapped = self.ipv4_mapped
+ if ipv4_mapped is None:
+ ip_str = super().__str__()
+ else:
+ ip_str = self._ipv4_mapped_ipv6_to_str()
return ip_str + '%' + self._scope_id if self._scope_id else ip_str
def __hash__(self):
@@ -1967,6 +2061,9 @@ def is_multicast(self):
See RFC 2373 2.7 for details.
"""
+ ipv4_mapped = self.ipv4_mapped
+ if ipv4_mapped is not None:
+ return ipv4_mapped.is_multicast
return self in self._constants._multicast_network
@property
@@ -1978,6 +2075,9 @@ def is_reserved(self):
reserved IPv6 Network ranges.
"""
+ ipv4_mapped = self.ipv4_mapped
+ if ipv4_mapped is not None:
+ return ipv4_mapped.is_reserved
return any(self in x for x in self._constants._reserved_networks)
@property
@@ -1988,6 +2088,9 @@ def is_link_local(self):
A boolean, True if the address is reserved per RFC 4291.
"""
+ ipv4_mapped = self.ipv4_mapped
+ if ipv4_mapped is not None:
+ return ipv4_mapped.is_link_local
return self in self._constants._linklocal_network
@property
@@ -2007,28 +2110,46 @@ def is_site_local(self):
@property
@functools.lru_cache()
def is_private(self):
- """Test if this address is allocated for private networks.
+ """``True`` if the address is defined as not globally reachable by
+ iana-ipv4-special-registry_ (for IPv4) or iana-ipv6-special-registry_
+ (for IPv6) with the following exceptions:
- Returns:
- A boolean, True if the address is reserved per
- iana-ipv6-special-registry, or is ipv4_mapped and is
- reserved in the iana-ipv4-special-registry.
+ * ``is_private`` is ``False`` for ``100.64.0.0/10``
+ * For IPv4-mapped IPv6-addresses the ``is_private`` value is determined by the
+ semantics of the underlying IPv4 addresses and the following condition holds
+ (see :attr:`IPv6Address.ipv4_mapped`)::
+
+ address.is_private == address.ipv4_mapped.is_private
+ ``is_private`` has value opposite to :attr:`is_global`, except for the ``100.64.0.0/10``
+ IPv4 range where they are both ``False``.
"""
ipv4_mapped = self.ipv4_mapped
if ipv4_mapped is not None:
return ipv4_mapped.is_private
- return any(self in net for net in self._constants._private_networks)
+ return (
+ any(self in net for net in self._constants._private_networks)
+ and all(self not in net for net in self._constants._private_networks_exceptions)
+ )
@property
def is_global(self):
- """Test if this address is allocated for public networks.
+ """``True`` if the address is defined as globally reachable by
+ iana-ipv4-special-registry_ (for IPv4) or iana-ipv6-special-registry_
+ (for IPv6) with the following exception:
- Returns:
- A boolean, true if the address is not reserved per
- iana-ipv6-special-registry.
+ For IPv4-mapped IPv6-addresses the ``is_private`` value is determined by the
+ semantics of the underlying IPv4 addresses and the following condition holds
+ (see :attr:`IPv6Address.ipv4_mapped`)::
+ address.is_global == address.ipv4_mapped.is_global
+
+ ``is_global`` has value opposite to :attr:`is_private`, except for the ``100.64.0.0/10``
+ IPv4 range where they are both ``False``.
"""
+ ipv4_mapped = self.ipv4_mapped
+ if ipv4_mapped is not None:
+ return ipv4_mapped.is_global
return not self.is_private
@property
@@ -2040,6 +2161,9 @@ def is_unspecified(self):
RFC 2373 2.5.2.
"""
+ ipv4_mapped = self.ipv4_mapped
+ if ipv4_mapped is not None:
+ return ipv4_mapped.is_unspecified
return self._ip == 0
@property
@@ -2051,6 +2175,9 @@ def is_loopback(self):
RFC 2373 2.5.3.
"""
+ ipv4_mapped = self.ipv4_mapped
+ if ipv4_mapped is not None:
+ return ipv4_mapped.is_loopback
return self._ip == 1
@property
@@ -2167,7 +2294,7 @@ def is_unspecified(self):
@property
def is_loopback(self):
- return self._ip == 1 and self.network.is_loopback
+ return super().is_loopback and self.network.is_loopback
class IPv6Network(_BaseV6, _BaseNetwork):
@@ -2268,19 +2395,33 @@ class _IPv6Constants:
_multicast_network = IPv6Network('ff00::/8')
+ # Not globally reachable address blocks listed on
+ # https://www.iana.org/assignments/iana-ipv6-special-registry/iana-ipv6-special-registry.xhtml
_private_networks = [
IPv6Network('::1/128'),
IPv6Network('::/128'),
IPv6Network('::ffff:0:0/96'),
+ IPv6Network('64:ff9b:1::/48'),
IPv6Network('100::/64'),
IPv6Network('2001::/23'),
- IPv6Network('2001:2::/48'),
IPv6Network('2001:db8::/32'),
- IPv6Network('2001:10::/28'),
+ # IANA says N/A, let's consider it not globally reachable to be safe
+ IPv6Network('2002::/16'),
+ # RFC 9637: https://www.rfc-editor.org/rfc/rfc9637.html#section-6-2.2
+ IPv6Network('3fff::/20'),
IPv6Network('fc00::/7'),
IPv6Network('fe80::/10'),
]
+ _private_networks_exceptions = [
+ IPv6Network('2001:1::1/128'),
+ IPv6Network('2001:1::2/128'),
+ IPv6Network('2001:3::/32'),
+ IPv6Network('2001:4:112::/48'),
+ IPv6Network('2001:20::/28'),
+ IPv6Network('2001:30::/28'),
+ ]
+
_reserved_networks = [
IPv6Network('::/8'), IPv6Network('100::/8'),
IPv6Network('200::/7'), IPv6Network('400::/6'),
diff --git a/Lib/operator.py b/Lib/operator.py
index 30116c1189..02ccdaa13d 100644
--- a/Lib/operator.py
+++ b/Lib/operator.py
@@ -239,7 +239,7 @@ class attrgetter:
"""
__slots__ = ('_attrs', '_call')
- def __init__(self, attr, *attrs):
+ def __init__(self, attr, /, *attrs):
if not attrs:
if not isinstance(attr, str):
raise TypeError('attribute name must be a string')
@@ -257,7 +257,7 @@ def func(obj):
return tuple(getter(obj) for getter in getters)
self._call = func
- def __call__(self, obj):
+ def __call__(self, obj, /):
return self._call(obj)
def __repr__(self):
@@ -276,7 +276,7 @@ class itemgetter:
"""
__slots__ = ('_items', '_call')
- def __init__(self, item, *items):
+ def __init__(self, item, /, *items):
if not items:
self._items = (item,)
def func(obj):
@@ -288,7 +288,7 @@ def func(obj):
return tuple(obj[i] for i in items)
self._call = func
- def __call__(self, obj):
+ def __call__(self, obj, /):
return self._call(obj)
def __repr__(self):
@@ -315,7 +315,7 @@ def __init__(self, name, /, *args, **kwargs):
self._args = args
self._kwargs = kwargs
- def __call__(self, obj):
+ def __call__(self, obj, /):
return getattr(obj, self._name)(*self._args, **self._kwargs)
def __repr__(self):
diff --git a/Lib/quopri.py b/Lib/quopri.py
index 08899c5cb7..f36cf7b395 100755
--- a/Lib/quopri.py
+++ b/Lib/quopri.py
@@ -67,10 +67,7 @@ def write(s, output=output, lineEnd=b'\n'):
output.write(s + lineEnd)
prevline = None
- while 1:
- line = input.readline()
- if not line:
- break
+ while line := input.readline():
outline = []
# Strip off any readline induced trailing newline
stripped = b''
@@ -126,9 +123,7 @@ def decode(input, output, header=False):
return
new = b''
- while 1:
- line = input.readline()
- if not line: break
+ while line := input.readline():
i, n = 0, len(line)
if n > 0 and line[n-1:n] == b'\n':
partial = 0; n = n-1
diff --git a/Lib/rlcompleter.py b/Lib/rlcompleter.py
index bca4a7bc52..23eb0020f4 100644
--- a/Lib/rlcompleter.py
+++ b/Lib/rlcompleter.py
@@ -31,7 +31,11 @@
import atexit
import builtins
+import inspect
+import keyword
+import re
import __main__
+import warnings
__all__ = ["Completer"]
@@ -85,10 +89,11 @@ def complete(self, text, state):
return None
if state == 0:
- if "." in text:
- self.matches = self.attr_matches(text)
- else:
- self.matches = self.global_matches(text)
+ with warnings.catch_warnings(action="ignore"):
+ if "." in text:
+ self.matches = self.attr_matches(text)
+ else:
+ self.matches = self.global_matches(text)
try:
return self.matches[state]
except IndexError:
@@ -96,7 +101,13 @@ def complete(self, text, state):
def _callable_postfix(self, val, word):
if callable(val):
- word = word + "("
+ word += "("
+ try:
+ if not inspect.signature(val).parameters:
+ word += ")"
+ except ValueError:
+ pass
+
return word
def global_matches(self, text):
@@ -106,18 +117,17 @@ def global_matches(self, text):
defined in self.namespace that match.
"""
- import keyword
matches = []
seen = {"__builtins__"}
n = len(text)
- for word in keyword.kwlist:
+ for word in keyword.kwlist + keyword.softkwlist:
if word[:n] == text:
seen.add(word)
if word in {'finally', 'try'}:
word = word + ':'
elif word not in {'False', 'None', 'True',
'break', 'continue', 'pass',
- 'else'}:
+ 'else', '_'}:
word = word + ' '
matches.append(word)
for nspace in [self.namespace, builtins.__dict__]:
@@ -139,7 +149,6 @@ def attr_matches(self, text):
with a __getattr__ hook is evaluated.
"""
- import re
m = re.match(r"(\w+(\.\w+)*)\.(\w*)", text)
if not m:
return []
@@ -169,13 +178,20 @@ def attr_matches(self, text):
if (word[:n] == attr and
not (noprefix and word[:n+1] == noprefix)):
match = "%s.%s" % (expr, word)
- try:
- val = getattr(thisobject, word)
- except Exception:
- pass # Include even if attribute not set
+ if isinstance(getattr(type(thisobject), word, None),
+ property):
+ # bpo-44752: thisobject.word is a method decorated by
+ # `@property`. What follows applies a postfix if
+ # thisobject.word is callable, but know we know that
+ # this is not callable (because it is a property).
+ # Also, getattr(thisobject, word) will evaluate the
+ # property method, which is not desirable.
+ matches.append(match)
+ continue
+ if (value := getattr(thisobject, word, None)) is not None:
+ matches.append(self._callable_postfix(value, match))
else:
- match = self._callable_postfix(val, match)
- matches.append(match)
+ matches.append(match)
if matches or not noprefix:
break
if noprefix == '_':
diff --git a/Lib/secrets.py b/Lib/secrets.py
index a546efbdd4..566a09b731 100644
--- a/Lib/secrets.py
+++ b/Lib/secrets.py
@@ -2,7 +2,7 @@
managing secrets such as account authentication, tokens, and similar.
See PEP 506 for more information.
-https://www.python.org/dev/peps/pep-0506/
+https://peps.python.org/pep-0506/
"""
@@ -13,7 +13,6 @@
import base64
-import binascii
from hmac import compare_digest
from random import SystemRandom
@@ -56,7 +55,7 @@ def token_hex(nbytes=None):
'f9bf78b9a18ce6d46a0cd2b0b86df9da'
"""
- return binascii.hexlify(token_bytes(nbytes)).decode('ascii')
+ return token_bytes(nbytes).hex()
def token_urlsafe(nbytes=None):
"""Return a random URL-safe text string, in Base64 encoding.
diff --git a/Lib/shlex.py b/Lib/shlex.py
index 4801a6c1d4..f4821616b6 100644
--- a/Lib/shlex.py
+++ b/Lib/shlex.py
@@ -305,9 +305,7 @@ def __next__(self):
def split(s, comments=False, posix=True):
"""Split the string *s* using shell-like syntax."""
if s is None:
- import warnings
- warnings.warn("Passing None for 's' to shlex.split() is deprecated.",
- DeprecationWarning, stacklevel=2)
+ raise ValueError("s argument must not be None")
lex = shlex(s, posix=posix)
lex.whitespace_split = True
if not comments:
@@ -335,10 +333,7 @@ def quote(s):
def _print_tokens(lexer):
- while 1:
- tt = lexer.get_token()
- if not tt:
- break
+ while tt := lexer.get_token():
print("Token: " + repr(tt))
if __name__ == '__main__':
diff --git a/Lib/sqlite3/__main__.py b/Lib/sqlite3/__main__.py
index 1832fc1308..f8a5cca24e 100644
--- a/Lib/sqlite3/__main__.py
+++ b/Lib/sqlite3/__main__.py
@@ -48,30 +48,18 @@ def runsource(self, source, filename="", symbol="single"):
Return True if more input is needed; buffering is done automatically.
Return False is input is a complete statement ready for execution.
"""
- if source == ".version":
- print(f"{sqlite3.sqlite_version}")
- elif source == ".help":
- print("Enter SQL code and press enter.")
- elif source == ".quit":
- sys.exit(0)
- elif not sqlite3.complete_statement(source):
- return True
- else:
- execute(self._cur, source)
- return False
- # TODO: RUSTPYTHON match statement supporting
- # match source:
- # case ".version":
- # print(f"{sqlite3.sqlite_version}")
- # case ".help":
- # print("Enter SQL code and press enter.")
- # case ".quit":
- # sys.exit(0)
- # case _:
- # if not sqlite3.complete_statement(source):
- # return True
- # execute(self._cur, source)
- # return False
+ match source:
+ case ".version":
+ print(f"{sqlite3.sqlite_version}")
+ case ".help":
+ print("Enter SQL code and press enter.")
+ case ".quit":
+ sys.exit(0)
+ case _:
+ if not sqlite3.complete_statement(source):
+ return True
+ execute(self._cur, source)
+ return False
def main():
diff --git a/Lib/test/_typed_dict_helper.py b/Lib/test/_typed_dict_helper.py
deleted file mode 100644
index d333db1931..0000000000
--- a/Lib/test/_typed_dict_helper.py
+++ /dev/null
@@ -1,18 +0,0 @@
-"""Used to test `get_type_hints()` on a cross-module inherited `TypedDict` class
-
-This script uses future annotations to postpone a type that won't be available
-on the module inheriting from to `Foo`. The subclass in the other module should
-look something like this:
-
- class Bar(_typed_dict_helper.Foo, total=False):
- b: int
-"""
-
-from __future__ import annotations
-
-from typing import Optional, TypedDict
-
-OptionalIntType = Optional[int]
-
-class Foo(TypedDict):
- a: OptionalIntType
diff --git a/Lib/test/ann_module.py b/Lib/test/ann_module.py
deleted file mode 100644
index 5081e6b583..0000000000
--- a/Lib/test/ann_module.py
+++ /dev/null
@@ -1,62 +0,0 @@
-
-
-"""
-The module for testing variable annotations.
-Empty lines above are for good reason (testing for correct line numbers)
-"""
-
-from typing import Optional
-from functools import wraps
-
-__annotations__[1] = 2
-
-class C:
-
- x = 5; y: Optional['C'] = None
-
-from typing import Tuple
-x: int = 5; y: str = x; f: Tuple[int, int]
-
-class M(type):
-
- __annotations__['123'] = 123
- o: type = object
-
-(pars): bool = True
-
-class D(C):
- j: str = 'hi'; k: str= 'bye'
-
-from types import new_class
-h_class = new_class('H', (C,))
-j_class = new_class('J')
-
-class F():
- z: int = 5
- def __init__(self, x):
- pass
-
-class Y(F):
- def __init__(self):
- super(F, self).__init__(123)
-
-class Meta(type):
- def __new__(meta, name, bases, namespace):
- return super().__new__(meta, name, bases, namespace)
-
-class S(metaclass = Meta):
- x: str = 'something'
- y: str = 'something else'
-
-def foo(x: int = 10):
- def bar(y: List[str]):
- x: str = 'yes'
- bar()
-
-def dec(func):
- @wraps(func)
- def wrapper(*args, **kwargs):
- return func(*args, **kwargs)
- return wrapper
-
-u: int | float
diff --git a/Lib/test/ann_module2.py b/Lib/test/ann_module2.py
deleted file mode 100644
index 76cf5b3ad9..0000000000
--- a/Lib/test/ann_module2.py
+++ /dev/null
@@ -1,36 +0,0 @@
-"""
-Some correct syntax for variable annotation here.
-More examples are in test_grammar and test_parser.
-"""
-
-from typing import no_type_check, ClassVar
-
-i: int = 1
-j: int
-x: float = i/10
-
-def f():
- class C: ...
- return C()
-
-f().new_attr: object = object()
-
-class C:
- def __init__(self, x: int) -> None:
- self.x = x
-
-c = C(5)
-c.new_attr: int = 10
-
-__annotations__ = {}
-
-
-@no_type_check
-class NTC:
- def meth(self, param: complex) -> None:
- ...
-
-class CV:
- var: ClassVar['CV']
-
-CV.var = CV()
diff --git a/Lib/test/ann_module3.py b/Lib/test/ann_module3.py
deleted file mode 100644
index eccd7be22d..0000000000
--- a/Lib/test/ann_module3.py
+++ /dev/null
@@ -1,18 +0,0 @@
-"""
-Correct syntax for variable annotation that should fail at runtime
-in a certain manner. More examples are in test_grammar and test_parser.
-"""
-
-def f_bad_ann():
- __annotations__[1] = 2
-
-class C_OK:
- def __init__(self, x: int) -> None:
- self.x: no_such_name = x # This one is OK as proposed by Guido
-
-class D_bad_ann:
- def __init__(self, x: int) -> None:
- sfel.y: int = 0
-
-def g_bad_ann():
- no_such_name.attr: int = 0
diff --git a/Lib/test/ann_module4.py b/Lib/test/ann_module4.py
deleted file mode 100644
index 13e9aee54c..0000000000
--- a/Lib/test/ann_module4.py
+++ /dev/null
@@ -1,5 +0,0 @@
-# This ann_module isn't for test_typing,
-# it's for test_module
-
-a:int=3
-b:str=4
diff --git a/Lib/test/ann_module5.py b/Lib/test/ann_module5.py
deleted file mode 100644
index 837041e121..0000000000
--- a/Lib/test/ann_module5.py
+++ /dev/null
@@ -1,10 +0,0 @@
-# Used by test_typing to verify that Final wrapped in ForwardRef works.
-
-from __future__ import annotations
-
-from typing import Final
-
-name: Final[str] = "final"
-
-class MyClass:
- value: Final = 3000
diff --git a/Lib/test/ann_module6.py b/Lib/test/ann_module6.py
deleted file mode 100644
index 679175669b..0000000000
--- a/Lib/test/ann_module6.py
+++ /dev/null
@@ -1,7 +0,0 @@
-# Tests that top-level ClassVar is not allowed
-
-from __future__ import annotations
-
-from typing import ClassVar
-
-wrong: ClassVar[int] = 1
diff --git a/Lib/test/ann_module7.py b/Lib/test/ann_module7.py
deleted file mode 100644
index 8f890cd280..0000000000
--- a/Lib/test/ann_module7.py
+++ /dev/null
@@ -1,11 +0,0 @@
-# Tests class have ``__text_signature__``
-
-from __future__ import annotations
-
-DEFAULT_BUFFER_SIZE = 8192
-
-class BufferedReader(object):
- """BufferedReader(raw, buffer_size=DEFAULT_BUFFER_SIZE)\n--\n\n
- Create a new buffered reader using the given readable raw IO object.
- """
- pass
diff --git a/Lib/test/mod_generics_cache.py b/Lib/test/mod_generics_cache.py
deleted file mode 100644
index 6d35c58396..0000000000
--- a/Lib/test/mod_generics_cache.py
+++ /dev/null
@@ -1,53 +0,0 @@
-"""Module for testing the behavior of generics across different modules."""
-
-import sys
-from textwrap import dedent
-from typing import TypeVar, Generic, Optional
-
-
-if sys.version_info[:2] >= (3, 6):
- exec(dedent("""
- default_a: Optional['A'] = None
- default_b: Optional['B'] = None
-
- T = TypeVar('T')
-
-
- class A(Generic[T]):
- some_b: 'B'
-
-
- class B(Generic[T]):
- class A(Generic[T]):
- pass
-
- my_inner_a1: 'B.A'
- my_inner_a2: A
- my_outer_a: 'A' # unless somebody calls get_type_hints with localns=B.__dict__
- """))
-else: # This should stay in sync with the syntax above.
- __annotations__ = dict(
- default_a=Optional['A'],
- default_b=Optional['B'],
- )
- default_a = None
- default_b = None
-
- T = TypeVar('T')
-
-
- class A(Generic[T]):
- __annotations__ = dict(
- some_b='B'
- )
-
-
- class B(Generic[T]):
- class A(Generic[T]):
- pass
-
- __annotations__ = dict(
- my_inner_a1='B.A',
- my_inner_a2=A,
- my_outer_a='A' # unless somebody calls get_type_hints with localns=B.__dict__
- )
diff --git a/Lib/test/string_tests.py b/Lib/test/string_tests.py
index 6f402513fd..c5831c47fc 100644
--- a/Lib/test/string_tests.py
+++ b/Lib/test/string_tests.py
@@ -8,18 +8,12 @@
from collections import UserList
import random
+
class Sequence:
def __init__(self, seq='wxyz'): self.seq = seq
def __len__(self): return len(self.seq)
def __getitem__(self, i): return self.seq[i]
-class BadSeq1(Sequence):
- def __init__(self): self.seq = [7, 'hello', 123]
- def __str__(self): return '{0} {1} {2}'.format(*self.seq)
-
-class BadSeq2(Sequence):
- def __init__(self): self.seq = ['a', 'b', 'c']
- def __len__(self): return 8
class BaseTest:
# These tests are for buffers of values (bytes) and not
@@ -27,7 +21,7 @@ class BaseTest:
# and various string implementations
# The type to be tested
- # Change in subclasses to change the behaviour of fixtesttype()
+ # Change in subclasses to change the behaviour of fixtype()
type2test = None
# Whether the "contained items" of the container are integers in
@@ -36,7 +30,7 @@ class BaseTest:
contains_bytes = False
# All tests pass their arguments to the testing methods
- # as str objects. fixtesttype() can be used to propagate
+ # as str objects. fixtype() can be used to propagate
# these arguments to the appropriate type
def fixtype(self, obj):
if isinstance(obj, str):
@@ -160,6 +154,12 @@ def test_count(self):
self.assertEqual(rem, 0, '%s != 0 for %s' % (rem, i))
self.assertEqual(r1, r2, '%s != %s for %s' % (r1, r2, i))
+ def test_count_keyword(self):
+ self.assertEqual('aa'.replace('a', 'b', 0), 'aa'.replace('a', 'b', count=0))
+ self.assertEqual('aa'.replace('a', 'b', 1), 'aa'.replace('a', 'b', count=1))
+ self.assertEqual('aa'.replace('a', 'b', 2), 'aa'.replace('a', 'b', count=2))
+ self.assertEqual('aa'.replace('a', 'b', 3), 'aa'.replace('a', 'b', count=3))
+
def test_find(self):
self.checkequal(0, 'abcdefghiabc', 'find', 'abc')
self.checkequal(9, 'abcdefghiabc', 'find', 'abc', 1)
@@ -327,11 +327,12 @@ def reference_find(p, s):
for i in range(len(s)):
if s.startswith(p, i):
return i
+ if p == '' and s == '':
+ return 0
return -1
- rr = random.randrange
- choices = random.choices
- for _ in range(1000):
+ def check_pattern(rr):
+ choices = random.choices
p0 = ''.join(choices('abcde', k=rr(10))) * rr(10, 20)
p = p0[:len(p0) - rr(10)] # pop off some characters
left = ''.join(choices('abcdef', k=rr(2000)))
@@ -341,6 +342,49 @@ def reference_find(p, s):
self.checkequal(reference_find(p, text),
text, 'find', p)
+ rr = random.randrange
+ for _ in range(1000):
+ check_pattern(rr)
+
+ # Test that empty string always work:
+ check_pattern(lambda *args: 0)
+
+ def test_find_many_lengths(self):
+ haystack_repeats = [a * 10**e for e in range(6) for a in (1,2,5)]
+ haystacks = [(n, self.fixtype("abcab"*n + "da")) for n in haystack_repeats]
+
+ needle_repeats = [a * 10**e for e in range(6) for a in (1, 3)]
+ needles = [(m, self.fixtype("abcab"*m + "da")) for m in needle_repeats]
+
+ for n, haystack1 in haystacks:
+ haystack2 = haystack1[:-1]
+ for m, needle in needles:
+ answer1 = 5 * (n - m) if m <= n else -1
+ self.assertEqual(haystack1.find(needle), answer1, msg=(n,m))
+ self.assertEqual(haystack2.find(needle), -1, msg=(n,m))
+
+ def test_adaptive_find(self):
+ # This would be very slow for the naive algorithm,
+ # but str.find() should be O(n + m).
+ for N in 1000, 10_000, 100_000, 1_000_000:
+ A, B = 'a' * N, 'b' * N
+ haystack = A + A + B + A + A
+ needle = A + B + B + A
+ self.checkequal(-1, haystack, 'find', needle)
+ self.checkequal(0, haystack, 'count', needle)
+ self.checkequal(len(haystack), haystack + needle, 'find', needle)
+ self.checkequal(1, haystack + needle, 'count', needle)
+
+ def test_find_with_memory(self):
+ # Test the "Skip with memory" path in the two-way algorithm.
+ for N in 1000, 3000, 10_000, 30_000:
+ needle = 'ab' * N
+ haystack = ('ab'*(N-1) + 'b') * 2
+ self.checkequal(-1, haystack, 'find', needle)
+ self.checkequal(0, haystack, 'count', needle)
+ self.checkequal(len(haystack), haystack + needle, 'find', needle)
+ self.checkequal(1, haystack + needle, 'count', needle)
+
def test_find_shift_table_overflow(self):
"""When the table of 8-bit shifts overflows."""
N = 2**8 + 100
@@ -724,6 +768,18 @@ def test_replace(self):
self.checkraises(TypeError, 'hello', 'replace', 42, 'h')
self.checkraises(TypeError, 'hello', 'replace', 'h', 42)
+ def test_replace_uses_two_way_maxcount(self):
+ # Test that maxcount works in _two_way_count in fastsearch.h
+ A, B = "A"*1000, "B"*1000
+ AABAA = A + A + B + A + A
+ ABBA = A + B + B + A
+ self.checkequal(AABAA + ABBA,
+ AABAA + ABBA, 'replace', ABBA, "ccc", 0)
+ self.checkequal(AABAA + "ccc",
+ AABAA + ABBA, 'replace', ABBA, "ccc", 1)
+ self.checkequal(AABAA + "ccc",
+ AABAA + ABBA, 'replace', ABBA, "ccc", 2)
+
@unittest.skip("TODO: RUSTPYTHON, may only apply to 32-bit platforms")
@unittest.skipIf(sys.maxsize > (1 << 32) or struct.calcsize('P') != 4,
'only applies to 32-bit platforms')
@@ -734,8 +790,6 @@ def test_replace_overflow(self):
self.checkraises(OverflowError, A2_16, "replace", "A", A2_16)
self.checkraises(OverflowError, A2_16, "replace", "AA", A2_16+A2_16)
-
- # Python 3.9
def test_removeprefix(self):
self.checkequal('am', 'spam', 'removeprefix', 'sp')
self.checkequal('spamspam', 'spamspamspam', 'removeprefix', 'spam')
@@ -754,7 +808,6 @@ def test_removeprefix(self):
self.checkraises(TypeError, 'hello', 'removeprefix', 'h', 42)
self.checkraises(TypeError, 'hello', 'removeprefix', ("he", "l"))
- # Python 3.9
def test_removesuffix(self):
self.checkequal('sp', 'spam', 'removesuffix', 'am')
self.checkequal('spamspam', 'spamspamspam', 'removesuffix', 'spam')
@@ -1053,7 +1106,7 @@ def test_splitlines(self):
self.checkraises(TypeError, 'abc', 'splitlines', 42, 42)
-class CommonTest(BaseTest):
+class StringLikeTest(BaseTest):
# This testcase contains tests that can be used in all
# stringlike classes. Currently this is str and UserString.
@@ -1084,11 +1137,6 @@ def test_capitalize_nonascii(self):
self.checkequal('\u019b\u1d00\u1d86\u0221\u1fb7',
'\u019b\u1d00\u1d86\u0221\u1fb7', 'capitalize')
-
-class MixinStrUnicodeUserStringTest:
- # additional tests that only work for
- # stringlike objects, i.e. str, UserString
-
def test_startswith(self):
self.checkequal(True, 'hello', 'startswith', 'he')
self.checkequal(True, 'hello', 'startswith', 'hello')
@@ -1273,8 +1321,11 @@ def test_join(self):
self.checkequal(((('a' * i) + '-') * i)[:-1], '-', 'join',
('a' * i,) * i)
- #self.checkequal(str(BadSeq1()), ' ', 'join', BadSeq1())
- self.checkequal('a b c', ' ', 'join', BadSeq2())
+ class LiesAboutLengthSeq(Sequence):
+ def __init__(self): self.seq = ['a', 'b', 'c']
+ def __len__(self): return 8
+
+ self.checkequal('a b c', ' ', 'join', LiesAboutLengthSeq())
self.checkraises(TypeError, ' ', 'join')
self.checkraises(TypeError, ' ', 'join', None)
@@ -1459,19 +1510,19 @@ def test_find_etc_raise_correct_error_messages(self):
# issue 11828
s = 'hello'
x = 'x'
- self.assertRaisesRegex(TypeError, r'^find\(', s.find,
+ self.assertRaisesRegex(TypeError, r'^find\b', s.find,
x, None, None, None)
- self.assertRaisesRegex(TypeError, r'^rfind\(', s.rfind,
+ self.assertRaisesRegex(TypeError, r'^rfind\b', s.rfind,
x, None, None, None)
- self.assertRaisesRegex(TypeError, r'^index\(', s.index,
+ self.assertRaisesRegex(TypeError, r'^index\b', s.index,
x, None, None, None)
- self.assertRaisesRegex(TypeError, r'^rindex\(', s.rindex,
+ self.assertRaisesRegex(TypeError, r'^rindex\b', s.rindex,
x, None, None, None)
- self.assertRaisesRegex(TypeError, r'^count\(', s.count,
+ self.assertRaisesRegex(TypeError, r'^count\b', s.count,
x, None, None, None)
- self.assertRaisesRegex(TypeError, r'^startswith\(', s.startswith,
+ self.assertRaisesRegex(TypeError, r'^startswith\b', s.startswith,
x, None, None, None)
- self.assertRaisesRegex(TypeError, r'^endswith\(', s.endswith,
+ self.assertRaisesRegex(TypeError, r'^endswith\b', s.endswith,
x, None, None, None)
# issue #15534
diff --git a/Lib/test/support/__init__.py b/Lib/test/support/__init__.py
index 3768a979b2..26a8b16724 100644
--- a/Lib/test/support/__init__.py
+++ b/Lib/test/support/__init__.py
@@ -840,11 +840,27 @@ def python_is_optimized():
return final_opt not in ('', '-O0', '-Og')
-_header = 'nP'
+# From CPython 3.13.5
+Py_GIL_DISABLED = bool(sysconfig.get_config_var('Py_GIL_DISABLED'))
+
+# From CPython 3.13.5
+def requires_gil_enabled(msg="needs the GIL enabled"):
+ """Decorator for skipping tests on the free-threaded build."""
+ return unittest.skipIf(Py_GIL_DISABLED, msg)
+
+# From CPython 3.13.5
+def expected_failure_if_gil_disabled():
+ """Expect test failure if the GIL is disabled."""
+ if Py_GIL_DISABLED:
+ return unittest.expectedFailure
+ return lambda test_case: test_case
+
+# From CPython 3.13.5
+if Py_GIL_DISABLED:
+ _header = 'PHBBInP'
+else:
+ _header = 'nP'
_align = '0n'
-if hasattr(sys, "getobjects"):
- _header = '2P' + _header
- _align = '0P'
_vheader = _header + 'n'
def calcobjsize(fmt):
@@ -1933,8 +1949,7 @@ def _check_tracemalloc():
"if tracemalloc module is tracing "
"memory allocations")
-
-# TODO: RUSTPYTHON (comment out before)
+# TODO: RUSTPYTHON; GC is not supported yet
# def check_free_after_iterating(test, iter, cls, args=()):
# class A(cls):
# def __del__(self):
@@ -2590,6 +2605,22 @@ def adjust_int_max_str_digits(max_digits):
finally:
sys.set_int_max_str_digits(current)
+
+# From CPython 3.13.5
+def get_c_recursion_limit():
+ try:
+ import _testcapi
+ return _testcapi.Py_C_RECURSION_LIMIT
+ except ImportError:
+ raise unittest.SkipTest('requires _testcapi')
+
+
+# From CPython 3.13.5
+def exceeds_recursion_limit():
+ """For recursion tests, easily exceeds default recursion limit."""
+ return get_c_recursion_limit() * 3
+
+
#For recursion tests, easily exceeds default recursion limit
EXCEEDS_RECURSION_LIMIT = 5000
@@ -2602,6 +2633,49 @@ def adjust_int_max_str_digits(max_digits):
'skipped on s390x')
HAVE_ASAN_FORK_BUG = check_sanitizer(address=True)
+# From CPython 3.13.5
+Py_TRACE_REFS = hasattr(sys, 'getobjects')
+
+
+# From Cpython 3.13.5
+@contextlib.contextmanager
+def no_color():
+ import _colorize
+ from .os_helper import EnvironmentVarGuard
+
+ with (
+ swap_attr(_colorize, "can_colorize", lambda file=None: False),
+ EnvironmentVarGuard() as env,
+ ):
+ env.unset("FORCE_COLOR", "NO_COLOR", "PYTHON_COLORS")
+ env.set("NO_COLOR", "1")
+ yield
+
+# From Cpython 3.13.5
+def force_not_colorized(func):
+ """Force the terminal not to be colorized."""
+ @functools.wraps(func)
+ def wrapper(*args, **kwargs):
+ with no_color():
+ return func(*args, **kwargs)
+ return wrapper
+
+
+# From Cpython 3.13.5
+def force_not_colorized_test_class(cls):
+ """Force the terminal not to be colorized for the entire test class."""
+ original_setUpClass = cls.setUpClass
+
+ @classmethod
+ @functools.wraps(cls.setUpClass)
+ def new_setUpClass(cls):
+ cls.enterClassContext(no_color())
+ original_setUpClass()
+
+ cls.setUpClass = new_setUpClass
+ return cls
+
+
# From python 3.12.8
class BrokenIter:
def __init__(self, init_raises=False, next_raises=False, iter_raises=False):
diff --git a/Lib/test/support/hypothesis_helper.py b/Lib/test/support/hypothesis_helper.py
index 40f58a2f59..db93eea5e9 100644
--- a/Lib/test/support/hypothesis_helper.py
+++ b/Lib/test/support/hypothesis_helper.py
@@ -5,13 +5,6 @@
except ImportError:
from . import _hypothesis_stubs as hypothesis
else:
- # Regrtest changes to use a tempdir as the working directory, so we have
- # to tell Hypothesis to use the original in order to persist the database.
- from .os_helper import SAVEDCWD
- from hypothesis.configuration import set_hypothesis_home_dir
-
- set_hypothesis_home_dir(os.path.join(SAVEDCWD, ".hypothesis"))
-
# When using the real Hypothesis, we'll configure it to ignore occasional
# slow tests (avoiding flakiness from random VM slowness in CI).
hypothesis.settings.register_profile(
diff --git a/Lib/test/support/numbers.py b/Lib/test/support/numbers.py
new file mode 100644
index 0000000000..d5dbb41ace
--- /dev/null
+++ b/Lib/test/support/numbers.py
@@ -0,0 +1,80 @@
+# These are shared with test_tokenize and other test modules.
+#
+# Note: since several test cases filter out floats by looking for "e" and ".",
+# don't add hexadecimal literals that contain "e" or "E".
+VALID_UNDERSCORE_LITERALS = [
+ '0_0_0',
+ '4_2',
+ '1_0000_0000',
+ '0b1001_0100',
+ '0xffff_ffff',
+ '0o5_7_7',
+ '1_00_00.5',
+ '1_00_00.5e5',
+ '1_00_00e5_1',
+ '1e1_0',
+ '.1_4',
+ '.1_4e1',
+ '0b_0',
+ '0x_f',
+ '0o_5',
+ '1_00_00j',
+ '1_00_00.5j',
+ '1_00_00e5_1j',
+ '.1_4j',
+ '(1_2.5+3_3j)',
+ '(.5_6j)',
+]
+INVALID_UNDERSCORE_LITERALS = [
+ # Trailing underscores:
+ '0_',
+ '42_',
+ '1.4j_',
+ '0x_',
+ '0b1_',
+ '0xf_',
+ '0o5_',
+ '0 if 1_Else 1',
+ # Underscores in the base selector:
+ '0_b0',
+ '0_xf',
+ '0_o5',
+ # Old-style octal, still disallowed:
+ '0_7',
+ '09_99',
+ # Multiple consecutive underscores:
+ '4_______2',
+ '0.1__4',
+ '0.1__4j',
+ '0b1001__0100',
+ '0xffff__ffff',
+ '0x___',
+ '0o5__77',
+ '1e1__0',
+ '1e1__0j',
+ # Underscore right before a dot:
+ '1_.4',
+ '1_.4j',
+ # Underscore right after a dot:
+ '1._4',
+ '1._4j',
+ '._5',
+ '._5j',
+ # Underscore right after a sign:
+ '1.0e+_1',
+ '1.0e+_1j',
+ # Underscore right before j:
+ '1.4_j',
+ '1.4e5_j',
+ # Underscore right before e:
+ '1_e1',
+ '1.4_e1',
+ '1.4_e1j',
+ # Underscore right after e:
+ '1e_1',
+ '1.4e_1',
+ '1.4e_1j',
+ # Complex cases with parens:
+ '(1+1.5_j_)',
+ '(1+1.5_j)',
+]
diff --git a/Lib/test/support/os_helper.py b/Lib/test/support/os_helper.py
index 821a4b1ffd..70161e9013 100644
--- a/Lib/test/support/os_helper.py
+++ b/Lib/test/support/os_helper.py
@@ -10,6 +10,9 @@
import unittest
import warnings
+# From CPython 3.13.5
+from test import support
+
# Filename used for testing
TESTFN_ASCII = '@test'
@@ -196,6 +199,26 @@ def skip_unless_symlink(test):
return test if ok else unittest.skip(msg)(test)
+# From CPython 3.13.5
+_can_hardlink = None
+
+# From CPython 3.13.5
+def can_hardlink():
+ global _can_hardlink
+ if _can_hardlink is None:
+ # Android blocks hard links using SELinux
+ # (https://stackoverflow.com/q/32365690).
+ _can_hardlink = hasattr(os, "link") and not support.is_android
+ return _can_hardlink
+
+
+# From CPython 3.13.5
+def skip_unless_hardlink(test):
+ ok = can_hardlink()
+ msg = "requires hardlink support"
+ return test if ok else unittest.skip(msg)(test)
+
+
_can_xattr = None
@@ -699,8 +722,11 @@ def __len__(self):
def set(self, envvar, value):
self[envvar] = value
- def unset(self, envvar):
- del self[envvar]
+ # From CPython 3.13.5
+ def unset(self, envvar, /, *envvars):
+ """Unset one or more environment variables."""
+ for ev in (envvar, *envvars):
+ del self[ev]
def copy(self):
# We do what os.environ.copy() does.
diff --git a/Lib/test/support/pty_helper.py b/Lib/test/support/pty_helper.py
new file mode 100644
index 0000000000..6587fd4033
--- /dev/null
+++ b/Lib/test/support/pty_helper.py
@@ -0,0 +1,80 @@
+"""
+Helper to run a script in a pseudo-terminal.
+"""
+import os
+import selectors
+import subprocess
+import sys
+from contextlib import ExitStack
+from errno import EIO
+
+from test.support.import_helper import import_module
+
+def run_pty(script, input=b"dummy input\r", env=None):
+ pty = import_module('pty')
+ output = bytearray()
+ [master, slave] = pty.openpty()
+ args = (sys.executable, '-c', script)
+ proc = subprocess.Popen(args, stdin=slave, stdout=slave, stderr=slave, env=env)
+ os.close(slave)
+ with ExitStack() as cleanup:
+ cleanup.enter_context(proc)
+ def terminate(proc):
+ try:
+ proc.terminate()
+ except ProcessLookupError:
+ # Workaround for Open/Net BSD bug (Issue 16762)
+ pass
+ cleanup.callback(terminate, proc)
+ cleanup.callback(os.close, master)
+ # Avoid using DefaultSelector and PollSelector. Kqueue() does not
+ # work with pseudo-terminals on OS X < 10.9 (Issue 20365) and Open
+ # BSD (Issue 20667). Poll() does not work with OS X 10.6 or 10.4
+ # either (Issue 20472). Hopefully the file descriptor is low enough
+ # to use with select().
+ sel = cleanup.enter_context(selectors.SelectSelector())
+ sel.register(master, selectors.EVENT_READ | selectors.EVENT_WRITE)
+ os.set_blocking(master, False)
+ while True:
+ for [_, events] in sel.select():
+ if events & selectors.EVENT_READ:
+ try:
+ chunk = os.read(master, 0x10000)
+ except OSError as err:
+ # Linux raises EIO when slave is closed (Issue 5380)
+ if err.errno != EIO:
+ raise
+ chunk = b""
+ if not chunk:
+ return output
+ output.extend(chunk)
+ if events & selectors.EVENT_WRITE:
+ try:
+ input = input[os.write(master, input):]
+ except OSError as err:
+ # Apparently EIO means the slave was closed
+ if err.errno != EIO:
+ raise
+ input = b"" # Stop writing
+ if not input:
+ sel.modify(master, selectors.EVENT_READ)
+
+
+######################################################################
+## Fake stdin (for testing interactive debugging)
+######################################################################
+
+class FakeInput:
+ """
+ A fake input stream for pdb's interactive debugger. Whenever a
+ line is read, print it (to simulate the user typing it), and then
+ return it. The set of lines to return is specified in the
+ constructor; they should not have trailing newlines.
+ """
+ def __init__(self, lines):
+ self.lines = lines
+
+ def readline(self):
+ line = self.lines.pop(0)
+ print(line)
+ return line + '\n'
diff --git a/Lib/test/support/smtpd.py b/Lib/test/support/smtpd.py
index ec4e7d2f4c..6052232ec2 100644
--- a/Lib/test/support/smtpd.py
+++ b/Lib/test/support/smtpd.py
@@ -180,122 +180,122 @@ def _set_rset_state(self):
@property
def __server(self):
warn("Access to __server attribute on SMTPChannel is deprecated, "
- "use 'smtp_server' instead", DeprecationWarning, 2)
+ "use 'smtp_server' instead", DeprecationWarning, 2)
return self.smtp_server
@__server.setter
def __server(self, value):
warn("Setting __server attribute on SMTPChannel is deprecated, "
- "set 'smtp_server' instead", DeprecationWarning, 2)
+ "set 'smtp_server' instead", DeprecationWarning, 2)
self.smtp_server = value
@property
def __line(self):
warn("Access to __line attribute on SMTPChannel is deprecated, "
- "use 'received_lines' instead", DeprecationWarning, 2)
+ "use 'received_lines' instead", DeprecationWarning, 2)
return self.received_lines
@__line.setter
def __line(self, value):
warn("Setting __line attribute on SMTPChannel is deprecated, "
- "set 'received_lines' instead", DeprecationWarning, 2)
+ "set 'received_lines' instead", DeprecationWarning, 2)
self.received_lines = value
@property
def __state(self):
warn("Access to __state attribute on SMTPChannel is deprecated, "
- "use 'smtp_state' instead", DeprecationWarning, 2)
+ "use 'smtp_state' instead", DeprecationWarning, 2)
return self.smtp_state
@__state.setter
def __state(self, value):
warn("Setting __state attribute on SMTPChannel is deprecated, "
- "set 'smtp_state' instead", DeprecationWarning, 2)
+ "set 'smtp_state' instead", DeprecationWarning, 2)
self.smtp_state = value
@property
def __greeting(self):
warn("Access to __greeting attribute on SMTPChannel is deprecated, "
- "use 'seen_greeting' instead", DeprecationWarning, 2)
+ "use 'seen_greeting' instead", DeprecationWarning, 2)
return self.seen_greeting
@__greeting.setter
def __greeting(self, value):
warn("Setting __greeting attribute on SMTPChannel is deprecated, "
- "set 'seen_greeting' instead", DeprecationWarning, 2)
+ "set 'seen_greeting' instead", DeprecationWarning, 2)
self.seen_greeting = value
@property
def __mailfrom(self):
warn("Access to __mailfrom attribute on SMTPChannel is deprecated, "
- "use 'mailfrom' instead", DeprecationWarning, 2)
+ "use 'mailfrom' instead", DeprecationWarning, 2)
return self.mailfrom
@__mailfrom.setter
def __mailfrom(self, value):
warn("Setting __mailfrom attribute on SMTPChannel is deprecated, "
- "set 'mailfrom' instead", DeprecationWarning, 2)
+ "set 'mailfrom' instead", DeprecationWarning, 2)
self.mailfrom = value
@property
def __rcpttos(self):
warn("Access to __rcpttos attribute on SMTPChannel is deprecated, "
- "use 'rcpttos' instead", DeprecationWarning, 2)
+ "use 'rcpttos' instead", DeprecationWarning, 2)
return self.rcpttos
@__rcpttos.setter
def __rcpttos(self, value):
warn("Setting __rcpttos attribute on SMTPChannel is deprecated, "
- "set 'rcpttos' instead", DeprecationWarning, 2)
+ "set 'rcpttos' instead", DeprecationWarning, 2)
self.rcpttos = value
@property
def __data(self):
warn("Access to __data attribute on SMTPChannel is deprecated, "
- "use 'received_data' instead", DeprecationWarning, 2)
+ "use 'received_data' instead", DeprecationWarning, 2)
return self.received_data
@__data.setter
def __data(self, value):
warn("Setting __data attribute on SMTPChannel is deprecated, "
- "set 'received_data' instead", DeprecationWarning, 2)
+ "set 'received_data' instead", DeprecationWarning, 2)
self.received_data = value
@property
def __fqdn(self):
warn("Access to __fqdn attribute on SMTPChannel is deprecated, "
- "use 'fqdn' instead", DeprecationWarning, 2)
+ "use 'fqdn' instead", DeprecationWarning, 2)
return self.fqdn
@__fqdn.setter
def __fqdn(self, value):
warn("Setting __fqdn attribute on SMTPChannel is deprecated, "
- "set 'fqdn' instead", DeprecationWarning, 2)
+ "set 'fqdn' instead", DeprecationWarning, 2)
self.fqdn = value
@property
def __peer(self):
warn("Access to __peer attribute on SMTPChannel is deprecated, "
- "use 'peer' instead", DeprecationWarning, 2)
+ "use 'peer' instead", DeprecationWarning, 2)
return self.peer
@__peer.setter
def __peer(self, value):
warn("Setting __peer attribute on SMTPChannel is deprecated, "
- "set 'peer' instead", DeprecationWarning, 2)
+ "set 'peer' instead", DeprecationWarning, 2)
self.peer = value
@property
def __conn(self):
warn("Access to __conn attribute on SMTPChannel is deprecated, "
- "use 'conn' instead", DeprecationWarning, 2)
+ "use 'conn' instead", DeprecationWarning, 2)
return self.conn
@__conn.setter
def __conn(self, value):
warn("Setting __conn attribute on SMTPChannel is deprecated, "
- "set 'conn' instead", DeprecationWarning, 2)
+ "set 'conn' instead", DeprecationWarning, 2)
self.conn = value
@property
def __addr(self):
warn("Access to __addr attribute on SMTPChannel is deprecated, "
- "use 'addr' instead", DeprecationWarning, 2)
+ "use 'addr' instead", DeprecationWarning, 2)
return self.addr
@__addr.setter
def __addr(self, value):
warn("Setting __addr attribute on SMTPChannel is deprecated, "
- "set 'addr' instead", DeprecationWarning, 2)
+ "set 'addr' instead", DeprecationWarning, 2)
self.addr = value
# Overrides base class for convenience.
@@ -339,7 +339,7 @@ def found_terminator(self):
command = line[:i].upper()
arg = line[i+1:].strip()
max_sz = (self.command_size_limits[command]
- if self.extended_smtp else self.command_size_limit)
+ if self.extended_smtp else self.command_size_limit)
if sz > max_sz:
self.push('500 Error: line too long')
return
diff --git a/Lib/test/support/venv.py b/Lib/test/support/venv.py
new file mode 100644
index 0000000000..78e6a51ec1
--- /dev/null
+++ b/Lib/test/support/venv.py
@@ -0,0 +1,70 @@
+import contextlib
+import logging
+import os
+import subprocess
+import shlex
+import sys
+import sysconfig
+import tempfile
+import venv
+
+
+class VirtualEnvironment:
+ def __init__(self, prefix, **venv_create_args):
+ self._logger = logging.getLogger(self.__class__.__name__)
+ venv.create(prefix, **venv_create_args)
+ self._prefix = prefix
+ self._paths = sysconfig.get_paths(
+ scheme='venv',
+ vars={'base': self.prefix},
+ expand=True,
+ )
+
+ @classmethod
+ @contextlib.contextmanager
+ def from_tmpdir(cls, *, prefix=None, dir=None, **venv_create_args):
+ delete = not bool(os.environ.get('PYTHON_TESTS_KEEP_VENV'))
+ with tempfile.TemporaryDirectory(prefix=prefix, dir=dir, delete=delete) as tmpdir:
+ yield cls(tmpdir, **venv_create_args)
+
+ @property
+ def prefix(self):
+ return self._prefix
+
+ @property
+ def paths(self):
+ return self._paths
+
+ @property
+ def interpreter(self):
+ return os.path.join(self.paths['scripts'], os.path.basename(sys.executable))
+
+ def _format_output(self, name, data, indent='\t'):
+ if not data:
+ return indent + f'{name}: (none)'
+ if len(data.splitlines()) == 1:
+ return indent + f'{name}: {data}'
+ else:
+ prefixed_lines = '\n'.join(indent + '> ' + line for line in data.splitlines())
+ return indent + f'{name}:\n' + prefixed_lines
+
+ def run(self, *args, **subprocess_args):
+ if subprocess_args.get('shell'):
+ raise ValueError('Running the subprocess in shell mode is not supported.')
+ default_args = {
+ 'capture_output': True,
+ 'check': True,
+ }
+ try:
+ result = subprocess.run([self.interpreter, *args], **default_args | subprocess_args)
+ except subprocess.CalledProcessError as e:
+ if e.returncode != 0:
+ self._logger.error(
+ f'Interpreter returned non-zero exit status {e.returncode}.\n'
+ + self._format_output('COMMAND', shlex.join(e.cmd)) + '\n'
+ + self._format_output('STDOUT', e.stdout.decode()) + '\n'
+ + self._format_output('STDERR', e.stderr.decode()) + '\n'
+ )
+ raise
+ else:
+ return result
diff --git a/Lib/test/test__colorize.py b/Lib/test/test__colorize.py
index 056a5306ce..b2f0bb1386 100644
--- a/Lib/test/test__colorize.py
+++ b/Lib/test/test__colorize.py
@@ -10,8 +10,7 @@
@contextlib.contextmanager
def clear_env():
with EnvironmentVarGuard() as mock_env:
- for var in "FORCE_COLOR", "NO_COLOR", "PYTHON_COLORS":
- mock_env.unset(var)
+ mock_env.unset("FORCE_COLOR", "NO_COLOR", "PYTHON_COLORS", "TERM")
yield mock_env
diff --git a/Lib/test/test_argparse.py b/Lib/test/test_argparse.py
index 3a62a16cee..dc2df795a7 100644
--- a/Lib/test/test_argparse.py
+++ b/Lib/test/test_argparse.py
@@ -15,7 +15,10 @@
import argparse
import warnings
+from enum import StrEnum
+from test.support import captured_stderr
from test.support import os_helper
+from test.support.i18n_helper import TestTranslationsBase, update_translation_snapshots
from unittest import mock
@@ -280,16 +283,18 @@ def test_failures(self, tester):
parser = self._get_parser(tester)
for args_str in tester.failures:
args = args_str.split()
- with tester.assertRaises(ArgumentParserError, msg=args):
- parser.parse_args(args)
+ with tester.subTest(args=args):
+ with tester.assertRaises(ArgumentParserError, msg=args):
+ parser.parse_args(args)
def test_successes(self, tester):
parser = self._get_parser(tester)
for args, expected_ns in tester.successes:
if isinstance(args, str):
args = args.split()
- result_ns = self._parse_args(parser, args)
- tester.assertEqual(expected_ns, result_ns)
+ with tester.subTest(args=args):
+ result_ns = self._parse_args(parser, args)
+ tester.assertEqual(expected_ns, result_ns)
# add tests for each combination of an optionals adding method
# and an arg parsing method
@@ -378,15 +383,22 @@ class TestOptionalsSingleDashAmbiguous(ParserTestCase):
"""Test Optionals that partially match but are not subsets"""
argument_signatures = [Sig('-foobar'), Sig('-foorab')]
- failures = ['-f', '-f a', '-fa', '-foa', '-foo', '-fo', '-foo b']
+ failures = ['-f', '-f a', '-fa', '-foa', '-foo', '-fo', '-foo b',
+ '-f=a', '-foo=b']
successes = [
('', NS(foobar=None, foorab=None)),
('-foob a', NS(foobar='a', foorab=None)),
+ ('-foob=a', NS(foobar='a', foorab=None)),
('-foor a', NS(foobar=None, foorab='a')),
+ ('-foor=a', NS(foobar=None, foorab='a')),
('-fooba a', NS(foobar='a', foorab=None)),
+ ('-fooba=a', NS(foobar='a', foorab=None)),
('-foora a', NS(foobar=None, foorab='a')),
+ ('-foora=a', NS(foobar=None, foorab='a')),
('-foobar a', NS(foobar='a', foorab=None)),
+ ('-foobar=a', NS(foobar='a', foorab=None)),
('-foorab a', NS(foobar=None, foorab='a')),
+ ('-foorab=a', NS(foobar=None, foorab='a')),
]
@@ -677,7 +689,7 @@ class TestOptionalsChoices(ParserTestCase):
argument_signatures = [
Sig('-f', choices='abc'),
Sig('-g', type=int, choices=range(5))]
- failures = ['a', '-f d', '-fad', '-ga', '-g 6']
+ failures = ['a', '-f d', '-f ab', '-fad', '-ga', '-g 6']
successes = [
('', NS(f=None, g=None)),
('-f a', NS(f='a', g=None)),
@@ -916,7 +928,9 @@ class TestOptionalsAllowLongAbbreviation(ParserTestCase):
successes = [
('', NS(foo=None, foobaz=None, fooble=False)),
('--foo 7', NS(foo='7', foobaz=None, fooble=False)),
+ ('--foo=7', NS(foo='7', foobaz=None, fooble=False)),
('--fooba a', NS(foo=None, foobaz='a', fooble=False)),
+ ('--fooba=a', NS(foo=None, foobaz='a', fooble=False)),
('--foobl --foo g', NS(foo='g', foobaz=None, fooble=True)),
]
@@ -955,6 +969,23 @@ class TestOptionalsDisallowLongAbbreviationPrefixChars(ParserTestCase):
]
+class TestOptionalsDisallowSingleDashLongAbbreviation(ParserTestCase):
+ """Do not allow abbreviations of long options at all"""
+
+ parser_signature = Sig(allow_abbrev=False)
+ argument_signatures = [
+ Sig('-foo'),
+ Sig('-foodle', action='store_true'),
+ Sig('-foonly'),
+ ]
+ failures = ['-foon 3', '-food', '-food -foo 2']
+ successes = [
+ ('', NS(foo=None, foodle=False, foonly=None)),
+ ('-foo 3', NS(foo='3', foodle=False, foonly=None)),
+ ('-foonly 7 -foodle -foo 2', NS(foo='2', foodle=True, foonly='7')),
+ ]
+
+
class TestDisallowLongAbbreviationAllowsShortGrouping(ParserTestCase):
"""Do not allow abbreviations of long options at all"""
@@ -993,6 +1024,34 @@ class TestDisallowLongAbbreviationAllowsShortGroupingPrefix(ParserTestCase):
]
+class TestStrEnumChoices(TestCase):
+ class Color(StrEnum):
+ RED = "red"
+ GREEN = "green"
+ BLUE = "blue"
+
+ def test_parse_enum_value(self):
+ parser = argparse.ArgumentParser()
+ parser.add_argument('--color', choices=self.Color)
+ args = parser.parse_args(['--color', 'red'])
+ self.assertEqual(args.color, self.Color.RED)
+
+ def test_help_message_contains_enum_choices(self):
+ parser = argparse.ArgumentParser()
+ parser.add_argument('--color', choices=self.Color, help='Choose a color')
+ self.assertIn('[--color {red,green,blue}]', parser.format_usage())
+ self.assertIn(' --color {red,green,blue}', parser.format_help())
+
+ def test_invalid_enum_value_raises_error(self):
+ parser = argparse.ArgumentParser(exit_on_error=False)
+ parser.add_argument('--color', choices=self.Color)
+ self.assertRaisesRegex(
+ argparse.ArgumentError,
+ r"invalid choice: 'yellow' \(choose from red, green, blue\)",
+ parser.parse_args,
+ ['--color', 'yellow'],
+ )
+
# ================
# Positional tests
# ================
@@ -1132,57 +1191,87 @@ class TestPositionalsNargs2None(ParserTestCase):
class TestPositionalsNargsNoneZeroOrMore(ParserTestCase):
"""Test a Positional with no nargs followed by one with unlimited"""
- argument_signatures = [Sig('foo'), Sig('bar', nargs='*')]
- failures = ['', '--foo']
+ argument_signatures = [Sig('-x'), Sig('foo'), Sig('bar', nargs='*')]
+ failures = ['', '--foo', 'a b -x X c']
successes = [
- ('a', NS(foo='a', bar=[])),
- ('a b', NS(foo='a', bar=['b'])),
- ('a b c', NS(foo='a', bar=['b', 'c'])),
+ ('a', NS(x=None, foo='a', bar=[])),
+ ('a b', NS(x=None, foo='a', bar=['b'])),
+ ('a b c', NS(x=None, foo='a', bar=['b', 'c'])),
+ ('-x X a', NS(x='X', foo='a', bar=[])),
+ ('a -x X', NS(x='X', foo='a', bar=[])),
+ ('-x X a b', NS(x='X', foo='a', bar=['b'])),
+ ('a -x X b', NS(x='X', foo='a', bar=['b'])),
+ ('a b -x X', NS(x='X', foo='a', bar=['b'])),
+ ('-x X a b c', NS(x='X', foo='a', bar=['b', 'c'])),
+ ('a -x X b c', NS(x='X', foo='a', bar=['b', 'c'])),
+ ('a b c -x X', NS(x='X', foo='a', bar=['b', 'c'])),
]
class TestPositionalsNargsNoneOneOrMore(ParserTestCase):
"""Test a Positional with no nargs followed by one with one or more"""
- argument_signatures = [Sig('foo'), Sig('bar', nargs='+')]
- failures = ['', '--foo', 'a']
+ argument_signatures = [Sig('-x'), Sig('foo'), Sig('bar', nargs='+')]
+ failures = ['', '--foo', 'a', 'a b -x X c']
successes = [
- ('a b', NS(foo='a', bar=['b'])),
- ('a b c', NS(foo='a', bar=['b', 'c'])),
+ ('a b', NS(x=None, foo='a', bar=['b'])),
+ ('a b c', NS(x=None, foo='a', bar=['b', 'c'])),
+ ('-x X a b', NS(x='X', foo='a', bar=['b'])),
+ ('a -x X b', NS(x='X', foo='a', bar=['b'])),
+ ('a b -x X', NS(x='X', foo='a', bar=['b'])),
+ ('-x X a b c', NS(x='X', foo='a', bar=['b', 'c'])),
+ ('a -x X b c', NS(x='X', foo='a', bar=['b', 'c'])),
+ ('a b c -x X', NS(x='X', foo='a', bar=['b', 'c'])),
]
class TestPositionalsNargsNoneOptional(ParserTestCase):
"""Test a Positional with no nargs followed by one with an Optional"""
- argument_signatures = [Sig('foo'), Sig('bar', nargs='?')]
+ argument_signatures = [Sig('-x'), Sig('foo'), Sig('bar', nargs='?')]
failures = ['', '--foo', 'a b c']
successes = [
- ('a', NS(foo='a', bar=None)),
- ('a b', NS(foo='a', bar='b')),
+ ('a', NS(x=None, foo='a', bar=None)),
+ ('a b', NS(x=None, foo='a', bar='b')),
+ ('-x X a', NS(x='X', foo='a', bar=None)),
+ ('a -x X', NS(x='X', foo='a', bar=None)),
+ ('-x X a b', NS(x='X', foo='a', bar='b')),
+ ('a -x X b', NS(x='X', foo='a', bar='b')),
+ ('a b -x X', NS(x='X', foo='a', bar='b')),
]
class TestPositionalsNargsZeroOrMoreNone(ParserTestCase):
"""Test a Positional with unlimited nargs followed by one with none"""
- argument_signatures = [Sig('foo', nargs='*'), Sig('bar')]
- failures = ['', '--foo']
+ argument_signatures = [Sig('-x'), Sig('foo', nargs='*'), Sig('bar')]
+ failures = ['', '--foo', 'a -x X b', 'a -x X b c', 'a b -x X c']
successes = [
- ('a', NS(foo=[], bar='a')),
- ('a b', NS(foo=['a'], bar='b')),
- ('a b c', NS(foo=['a', 'b'], bar='c')),
+ ('a', NS(x=None, foo=[], bar='a')),
+ ('a b', NS(x=None, foo=['a'], bar='b')),
+ ('a b c', NS(x=None, foo=['a', 'b'], bar='c')),
+ ('-x X a', NS(x='X', foo=[], bar='a')),
+ ('a -x X', NS(x='X', foo=[], bar='a')),
+ ('-x X a b', NS(x='X', foo=['a'], bar='b')),
+ ('a b -x X', NS(x='X', foo=['a'], bar='b')),
+ ('-x X a b c', NS(x='X', foo=['a', 'b'], bar='c')),
+ ('a b c -x X', NS(x='X', foo=['a', 'b'], bar='c')),
]
class TestPositionalsNargsOneOrMoreNone(ParserTestCase):
"""Test a Positional with one or more nargs followed by one with none"""
- argument_signatures = [Sig('foo', nargs='+'), Sig('bar')]
- failures = ['', '--foo', 'a']
+ argument_signatures = [Sig('-x'), Sig('foo', nargs='+'), Sig('bar')]
+ failures = ['', '--foo', 'a', 'a -x X b c', 'a b -x X c']
successes = [
- ('a b', NS(foo=['a'], bar='b')),
- ('a b c', NS(foo=['a', 'b'], bar='c')),
+ ('a b', NS(x=None, foo=['a'], bar='b')),
+ ('a b c', NS(x=None, foo=['a', 'b'], bar='c')),
+ ('-x X a b', NS(x='X', foo=['a'], bar='b')),
+ ('a -x X b', NS(x='X', foo=['a'], bar='b')),
+ ('a b -x X', NS(x='X', foo=['a'], bar='b')),
+ ('-x X a b c', NS(x='X', foo=['a', 'b'], bar='c')),
+ ('a b c -x X', NS(x='X', foo=['a', 'b'], bar='c')),
]
@@ -1267,14 +1356,21 @@ class TestPositionalsNargsNoneZeroOrMore1(ParserTestCase):
"""Test three Positionals: no nargs, unlimited nargs and 1 nargs"""
argument_signatures = [
+ Sig('-x'),
Sig('foo'),
Sig('bar', nargs='*'),
Sig('baz', nargs=1),
]
- failures = ['', '--foo', 'a']
+ failures = ['', '--foo', 'a', 'a b -x X c']
successes = [
- ('a b', NS(foo='a', bar=[], baz=['b'])),
- ('a b c', NS(foo='a', bar=['b'], baz=['c'])),
+ ('a b', NS(x=None, foo='a', bar=[], baz=['b'])),
+ ('a b c', NS(x=None, foo='a', bar=['b'], baz=['c'])),
+ ('-x X a b', NS(x='X', foo='a', bar=[], baz=['b'])),
+ ('a -x X b', NS(x='X', foo='a', bar=[], baz=['b'])),
+ ('a b -x X', NS(x='X', foo='a', bar=[], baz=['b'])),
+ ('-x X a b c', NS(x='X', foo='a', bar=['b'], baz=['c'])),
+ ('a -x X b c', NS(x='X', foo='a', bar=['b'], baz=['c'])),
+ ('a b c -x X', NS(x='X', foo='a', bar=['b'], baz=['c'])),
]
@@ -1282,14 +1378,22 @@ class TestPositionalsNargsNoneOneOrMore1(ParserTestCase):
"""Test three Positionals: no nargs, one or more nargs and 1 nargs"""
argument_signatures = [
+ Sig('-x'),
Sig('foo'),
Sig('bar', nargs='+'),
Sig('baz', nargs=1),
]
- failures = ['', '--foo', 'a', 'b']
+ failures = ['', '--foo', 'a', 'b', 'a b -x X c d', 'a b c -x X d']
successes = [
- ('a b c', NS(foo='a', bar=['b'], baz=['c'])),
- ('a b c d', NS(foo='a', bar=['b', 'c'], baz=['d'])),
+ ('a b c', NS(x=None, foo='a', bar=['b'], baz=['c'])),
+ ('a b c d', NS(x=None, foo='a', bar=['b', 'c'], baz=['d'])),
+ ('-x X a b c', NS(x='X', foo='a', bar=['b'], baz=['c'])),
+ ('a -x X b c', NS(x='X', foo='a', bar=['b'], baz=['c'])),
+ ('a b -x X c', NS(x='X', foo='a', bar=['b'], baz=['c'])),
+ ('a b c -x X', NS(x='X', foo='a', bar=['b'], baz=['c'])),
+ ('-x X a b c d', NS(x='X', foo='a', bar=['b', 'c'], baz=['d'])),
+ ('a -x X b c d', NS(x='X', foo='a', bar=['b', 'c'], baz=['d'])),
+ ('a b c d -x X', NS(x='X', foo='a', bar=['b', 'c'], baz=['d'])),
]
@@ -1297,14 +1401,21 @@ class TestPositionalsNargsNoneOptional1(ParserTestCase):
"""Test three Positionals: no nargs, optional narg and 1 nargs"""
argument_signatures = [
+ Sig('-x'),
Sig('foo'),
Sig('bar', nargs='?', default=0.625),
Sig('baz', nargs=1),
]
- failures = ['', '--foo', 'a']
+ failures = ['', '--foo', 'a', 'a b -x X c']
successes = [
- ('a b', NS(foo='a', bar=0.625, baz=['b'])),
- ('a b c', NS(foo='a', bar='b', baz=['c'])),
+ ('a b', NS(x=None, foo='a', bar=0.625, baz=['b'])),
+ ('a b c', NS(x=None, foo='a', bar='b', baz=['c'])),
+ ('-x X a b', NS(x='X', foo='a', bar=0.625, baz=['b'])),
+ ('a -x X b', NS(x='X', foo='a', bar=0.625, baz=['b'])),
+ ('a b -x X', NS(x='X', foo='a', bar=0.625, baz=['b'])),
+ ('-x X a b c', NS(x='X', foo='a', bar='b', baz=['c'])),
+ ('a -x X b c', NS(x='X', foo='a', bar='b', baz=['c'])),
+ ('a b c -x X', NS(x='X', foo='a', bar='b', baz=['c'])),
]
@@ -1382,6 +1493,19 @@ class TestPositionalsActionAppend(ParserTestCase):
('a b c', NS(spam=['a', ['b', 'c']])),
]
+
+class TestPositionalsActionExtend(ParserTestCase):
+ """Test the 'extend' action"""
+
+ argument_signatures = [
+ Sig('spam', action='extend'),
+ Sig('spam', action='extend', nargs=2),
+ ]
+ failures = ['', '--foo', 'a', 'a b', 'a b c d']
+ successes = [
+ ('a b c', NS(spam=['a', 'b', 'c'])),
+ ]
+
# ========================================
# Combined optionals and positionals tests
# ========================================
@@ -1419,6 +1543,32 @@ class TestOptionalsAlmostNumericAndPositionals(ParserTestCase):
]
+class TestOptionalsAndPositionalsAppend(ParserTestCase):
+ argument_signatures = [
+ Sig('foo', nargs='*', action='append'),
+ Sig('--bar'),
+ ]
+ failures = ['-foo']
+ successes = [
+ ('a b', NS(foo=[['a', 'b']], bar=None)),
+ ('--bar a b', NS(foo=[['b']], bar='a')),
+ ('a b --bar c', NS(foo=[['a', 'b']], bar='c')),
+ ]
+
+
+class TestOptionalsAndPositionalsExtend(ParserTestCase):
+ argument_signatures = [
+ Sig('foo', nargs='*', action='extend'),
+ Sig('--bar'),
+ ]
+ failures = ['-foo']
+ successes = [
+ ('a b', NS(foo=['a', 'b'], bar=None)),
+ ('--bar a b', NS(foo=['b'], bar='a')),
+ ('a b --bar c', NS(foo=['a', 'b'], bar='c')),
+ ]
+
+
class TestEmptyAndSpaceContainingArguments(ParserTestCase):
argument_signatures = [
@@ -1481,6 +1631,9 @@ class TestNargsRemainder(ParserTestCase):
successes = [
('X', NS(x='X', y=[], z=None)),
('-z Z X', NS(x='X', y=[], z='Z')),
+ ('-z Z X A B', NS(x='X', y=['A', 'B'], z='Z')),
+ ('X -z Z A B', NS(x='X', y=['-z', 'Z', 'A', 'B'], z=None)),
+ ('X A -z Z B', NS(x='X', y=['A', '-z', 'Z', 'B'], z=None)),
('X A B -z Z', NS(x='X', y=['A', 'B', '-z', 'Z'], z=None)),
('X Y --foo', NS(x='X', y=['Y', '--foo'], z=None)),
]
@@ -1517,18 +1670,24 @@ class TestDefaultSuppress(ParserTestCase):
"""Test actions with suppressed defaults"""
argument_signatures = [
- Sig('foo', nargs='?', default=argparse.SUPPRESS),
- Sig('bar', nargs='*', default=argparse.SUPPRESS),
+ Sig('foo', nargs='?', type=int, default=argparse.SUPPRESS),
+ Sig('bar', nargs='*', type=int, default=argparse.SUPPRESS),
Sig('--baz', action='store_true', default=argparse.SUPPRESS),
+ Sig('--qux', nargs='?', type=int, default=argparse.SUPPRESS),
+ Sig('--quux', nargs='*', type=int, default=argparse.SUPPRESS),
]
- failures = ['-x']
+ failures = ['-x', 'a', '1 a']
successes = [
('', NS()),
- ('a', NS(foo='a')),
- ('a b', NS(foo='a', bar=['b'])),
+ ('1', NS(foo=1)),
+ ('1 2', NS(foo=1, bar=[2])),
('--baz', NS(baz=True)),
- ('a --baz', NS(foo='a', baz=True)),
- ('--baz a b', NS(foo='a', bar=['b'], baz=True)),
+ ('1 --baz', NS(foo=1, baz=True)),
+ ('--baz 1 2', NS(foo=1, bar=[2], baz=True)),
+ ('--qux', NS(qux=None)),
+ ('--qux 1', NS(qux=1)),
+ ('--quux', NS(quux=[])),
+ ('--quux 1 2', NS(quux=[1, 2])),
]
@@ -1899,6 +2058,10 @@ def test_open_args(self):
type('foo')
m.assert_called_with('foo', *args)
+ def test_invalid_file_type(self):
+ with self.assertRaises(ValueError):
+ argparse.FileType('b')('-test')
+
class TestFileTypeMissingInitialization(TestCase):
"""
@@ -2092,6 +2255,27 @@ class TestActionExtend(ParserTestCase):
('--foo f1 --foo f2 f3 f4', NS(foo=['f1', 'f2', 'f3', 'f4'])),
]
+
+class TestInvalidAction(TestCase):
+ """Test invalid user defined Action"""
+
+ class ActionWithoutCall(argparse.Action):
+ pass
+
+ def test_invalid_type(self):
+ parser = argparse.ArgumentParser()
+
+ parser.add_argument('--foo', action=self.ActionWithoutCall)
+ self.assertRaises(NotImplementedError, parser.parse_args, ['--foo', 'bar'])
+
+ def test_modified_invalid_action(self):
+ parser = ErrorRaisingArgumentParser()
+ action = parser.add_argument('--foo')
+ # Someone got crazy and did this
+ action.type = 1
+ self.assertRaises(ArgumentParserError, parser.parse_args, ['--foo', 'bar'])
+
+
# ================
# Subparsers tests
# ================
@@ -2126,7 +2310,9 @@ def _get_parser(self, subparser_help=False, prefix_chars=None,
else:
subparsers_kwargs['help'] = 'command help'
subparsers = parser.add_subparsers(**subparsers_kwargs)
- self.assertArgumentParserError(parser.add_subparsers)
+ self.assertRaisesRegex(argparse.ArgumentError,
+ 'cannot have multiple subparser arguments',
+ parser.add_subparsers)
# add first sub-parser
parser1_kwargs = dict(description='1 description')
@@ -2136,14 +2322,14 @@ def _get_parser(self, subparser_help=False, prefix_chars=None,
parser1_kwargs['aliases'] = ['1alias1', '1alias2']
parser1 = subparsers.add_parser('1', **parser1_kwargs)
parser1.add_argument('-w', type=int, help='w help')
- parser1.add_argument('x', choices='abc', help='x help')
+ parser1.add_argument('x', choices=['a', 'b', 'c'], help='x help')
# add second sub-parser
parser2_kwargs = dict(description='2 description')
if subparser_help:
parser2_kwargs['help'] = '2 help'
parser2 = subparsers.add_parser('2', **parser2_kwargs)
- parser2.add_argument('-y', choices='123', help='y help')
+ parser2.add_argument('-y', choices=['1', '2', '3'], help='y help')
parser2.add_argument('z', type=complex, nargs='*', help='z help')
# add third sub-parser
@@ -2210,6 +2396,68 @@ def test_parse_known_args(self):
(NS(foo=False, bar=0.5, w=7, x='b'), ['-W', '-X', 'Y', 'Z']),
)
+ def test_parse_known_args_to_class_namespace(self):
+ class C:
+ pass
+ self.assertEqual(
+ self.parser.parse_known_args('0.5 1 b -w 7 -p'.split(), namespace=C),
+ (C, ['-p']),
+ )
+ self.assertIs(C.foo, False)
+ self.assertEqual(C.bar, 0.5)
+ self.assertEqual(C.w, 7)
+ self.assertEqual(C.x, 'b')
+
+ def test_abbreviation(self):
+ parser = ErrorRaisingArgumentParser()
+ parser.add_argument('--foodle')
+ parser.add_argument('--foonly')
+ subparsers = parser.add_subparsers()
+ parser1 = subparsers.add_parser('bar')
+ parser1.add_argument('--fo')
+ parser1.add_argument('--foonew')
+
+ self.assertEqual(parser.parse_args(['--food', 'baz', 'bar']),
+ NS(foodle='baz', foonly=None, fo=None, foonew=None))
+ self.assertEqual(parser.parse_args(['--foon', 'baz', 'bar']),
+ NS(foodle=None, foonly='baz', fo=None, foonew=None))
+ self.assertArgumentParserError(parser.parse_args, ['--fo', 'baz', 'bar'])
+ self.assertEqual(parser.parse_args(['bar', '--fo', 'baz']),
+ NS(foodle=None, foonly=None, fo='baz', foonew=None))
+ self.assertEqual(parser.parse_args(['bar', '--foo', 'baz']),
+ NS(foodle=None, foonly=None, fo=None, foonew='baz'))
+ self.assertEqual(parser.parse_args(['bar', '--foon', 'baz']),
+ NS(foodle=None, foonly=None, fo=None, foonew='baz'))
+ self.assertArgumentParserError(parser.parse_args, ['bar', '--food', 'baz'])
+
+ def test_parse_known_args_with_single_dash_option(self):
+ parser = ErrorRaisingArgumentParser()
+ parser.add_argument('-k', '--known', action='count', default=0)
+ parser.add_argument('-n', '--new', action='count', default=0)
+ self.assertEqual(parser.parse_known_args(['-k', '-u']),
+ (NS(known=1, new=0), ['-u']))
+ self.assertEqual(parser.parse_known_args(['-u', '-k']),
+ (NS(known=1, new=0), ['-u']))
+ self.assertEqual(parser.parse_known_args(['-ku']),
+ (NS(known=1, new=0), ['-u']))
+ self.assertArgumentParserError(parser.parse_known_args, ['-k=u'])
+ self.assertEqual(parser.parse_known_args(['-uk']),
+ (NS(known=0, new=0), ['-uk']))
+ self.assertEqual(parser.parse_known_args(['-u=k']),
+ (NS(known=0, new=0), ['-u=k']))
+ self.assertEqual(parser.parse_known_args(['-kunknown']),
+ (NS(known=1, new=0), ['-unknown']))
+ self.assertArgumentParserError(parser.parse_known_args, ['-k=unknown'])
+ self.assertEqual(parser.parse_known_args(['-ku=nknown']),
+ (NS(known=1, new=0), ['-u=nknown']))
+ self.assertEqual(parser.parse_known_args(['-knew']),
+ (NS(known=1, new=1), ['-ew']))
+ self.assertArgumentParserError(parser.parse_known_args, ['-kn=ew'])
+ self.assertArgumentParserError(parser.parse_known_args, ['-k-new'])
+ self.assertArgumentParserError(parser.parse_known_args, ['-kn-ew'])
+ self.assertEqual(parser.parse_known_args(['-kne-w']),
+ (NS(known=1, new=1), ['-e-w']))
+
def test_dest(self):
parser = ErrorRaisingArgumentParser()
parser.add_argument('--foo', action='store_true')
@@ -2269,7 +2517,7 @@ def test_wrong_argument_subparsers_no_destination_error(self):
parser.parse_args(('baz',))
self.assertRegex(
excinfo.exception.stderr,
- r"error: argument {foo,bar}: invalid choice: 'baz' \(choose from 'foo', 'bar'\)\n$"
+ r"error: argument {foo,bar}: invalid choice: 'baz' \(choose from foo, bar\)\n$"
)
def test_optional_subparsers(self):
@@ -2727,6 +2975,38 @@ def test_groups_parents(self):
-x X
'''.format(progname, ' ' if progname else '' )))
+ def test_wrong_type_parents(self):
+ self.assertRaises(TypeError, ErrorRaisingArgumentParser, parents=[1])
+
+ def test_mutex_groups_parents(self):
+ parent = ErrorRaisingArgumentParser(add_help=False)
+ g = parent.add_argument_group(title='g', description='gd')
+ g.add_argument('-w')
+ g.add_argument('-x')
+ m = g.add_mutually_exclusive_group()
+ m.add_argument('-y')
+ m.add_argument('-z')
+ parser = ErrorRaisingArgumentParser(prog='PROG', parents=[parent])
+
+ self.assertRaises(ArgumentParserError, parser.parse_args,
+ ['-y', 'Y', '-z', 'Z'])
+
+ parser_help = parser.format_help()
+ self.assertEqual(parser_help, textwrap.dedent('''\
+ usage: PROG [-h] [-w W] [-x X] [-y Y | -z Z]
+
+ options:
+ -h, --help show this help message and exit
+
+ g:
+ gd
+
+ -w W
+ -x X
+ -y Y
+ -z Z
+ '''))
+
# ==============================
# Mutually exclusive group tests
# ==============================
@@ -2769,6 +3049,27 @@ def test_help(self):
'''
self.assertEqual(parser.format_help(), textwrap.dedent(expected))
+ def test_help_subparser_all_mutually_exclusive_group_members_suppressed(self):
+ self.maxDiff = None
+ parser = ErrorRaisingArgumentParser(prog='PROG')
+ commands = parser.add_subparsers(title="commands", dest="command")
+ cmd_foo = commands.add_parser("foo")
+ group = cmd_foo.add_mutually_exclusive_group()
+ group.add_argument('--verbose', action='store_true', help=argparse.SUPPRESS)
+ group.add_argument('--quiet', action='store_true', help=argparse.SUPPRESS)
+ longopt = '--' + 'long'*32
+ longmeta = 'LONG'*32
+ cmd_foo.add_argument(longopt)
+ expected = f'''\
+ usage: PROG foo [-h]
+ [{longopt} {longmeta}]
+
+ options:
+ -h, --help show this help message and exit
+ {longopt} {longmeta}
+ '''
+ self.assertEqual(cmd_foo.format_help(), textwrap.dedent(expected))
+
def test_empty_group(self):
# See issue 26952
parser = argparse.ArgumentParser()
@@ -2782,26 +3083,30 @@ def test_failures_when_not_required(self):
parse_args = self.get_parser(required=False).parse_args
error = ArgumentParserError
for args_string in self.failures:
- self.assertRaises(error, parse_args, args_string.split())
+ with self.subTest(args=args_string):
+ self.assertRaises(error, parse_args, args_string.split())
def test_failures_when_required(self):
parse_args = self.get_parser(required=True).parse_args
error = ArgumentParserError
for args_string in self.failures + ['']:
- self.assertRaises(error, parse_args, args_string.split())
+ with self.subTest(args=args_string):
+ self.assertRaises(error, parse_args, args_string.split())
def test_successes_when_not_required(self):
parse_args = self.get_parser(required=False).parse_args
successes = self.successes + self.successes_when_not_required
for args_string, expected_ns in successes:
- actual_ns = parse_args(args_string.split())
- self.assertEqual(actual_ns, expected_ns)
+ with self.subTest(args=args_string):
+ actual_ns = parse_args(args_string.split())
+ self.assertEqual(actual_ns, expected_ns)
def test_successes_when_required(self):
parse_args = self.get_parser(required=True).parse_args
for args_string, expected_ns in self.successes:
- actual_ns = parse_args(args_string.split())
- self.assertEqual(actual_ns, expected_ns)
+ with self.subTest(args=args_string):
+ actual_ns = parse_args(args_string.split())
+ self.assertEqual(actual_ns, expected_ns)
def test_usage_when_not_required(self):
format_usage = self.get_parser(required=False).format_usage
@@ -2884,12 +3189,12 @@ def get_parser(self, required=None):
]
usage_when_not_required = '''\
- usage: PROG [-h] [--abcde ABCDE] [--fghij FGHIJ]
- [--klmno KLMNO | --pqrst PQRST]
+ usage: PROG [-h] [--abcde ABCDE] [--fghij FGHIJ] [--klmno KLMNO |
+ --pqrst PQRST]
'''
usage_when_required = '''\
- usage: PROG [-h] [--abcde ABCDE] [--fghij FGHIJ]
- (--klmno KLMNO | --pqrst PQRST)
+ usage: PROG [-h] [--abcde ABCDE] [--fghij FGHIJ] (--klmno KLMNO |
+ --pqrst PQRST)
'''
help = '''\
@@ -2978,7 +3283,7 @@ def get_parser(self, required):
group = parser.add_mutually_exclusive_group(required=required)
group.add_argument('--foo', action='store_true', help='FOO')
group.add_argument('--spam', help='SPAM')
- group.add_argument('badger', nargs='*', default='X', help='BADGER')
+ group.add_argument('badger', nargs='*', help='BADGER')
return parser
failures = [
@@ -2989,13 +3294,13 @@ def get_parser(self, required):
'--foo X Y',
]
successes = [
- ('--foo', NS(foo=True, spam=None, badger='X')),
- ('--spam S', NS(foo=False, spam='S', badger='X')),
+ ('--foo', NS(foo=True, spam=None, badger=[])),
+ ('--spam S', NS(foo=False, spam='S', badger=[])),
('X', NS(foo=False, spam=None, badger=['X'])),
('X Y Z', NS(foo=False, spam=None, badger=['X', 'Y', 'Z'])),
]
successes_when_not_required = [
- ('', NS(foo=False, spam=None, badger='X')),
+ ('', NS(foo=False, spam=None, badger=[])),
]
usage_when_not_required = '''\
@@ -3188,6 +3493,111 @@ def get_parser(self, required):
test_successes_when_not_required = None
test_successes_when_required = None
+
+class TestMutuallyExclusiveOptionalOptional(MEMixin, TestCase):
+ def get_parser(self, required=None):
+ parser = ErrorRaisingArgumentParser(prog='PROG')
+ group = parser.add_mutually_exclusive_group(required=required)
+ group.add_argument('--foo')
+ group.add_argument('--bar', nargs='?')
+ return parser
+
+ failures = [
+ '--foo X --bar Y',
+ '--foo X --bar',
+ ]
+ successes = [
+ ('--foo X', NS(foo='X', bar=None)),
+ ('--bar X', NS(foo=None, bar='X')),
+ ('--bar', NS(foo=None, bar=None)),
+ ]
+ successes_when_not_required = [
+ ('', NS(foo=None, bar=None)),
+ ]
+ usage_when_required = '''\
+ usage: PROG [-h] (--foo FOO | --bar [BAR])
+ '''
+ usage_when_not_required = '''\
+ usage: PROG [-h] [--foo FOO | --bar [BAR]]
+ '''
+ help = '''\
+
+ options:
+ -h, --help show this help message and exit
+ --foo FOO
+ --bar [BAR]
+ '''
+
+
+class TestMutuallyExclusiveOptionalWithDefault(MEMixin, TestCase):
+ def get_parser(self, required=None):
+ parser = ErrorRaisingArgumentParser(prog='PROG')
+ group = parser.add_mutually_exclusive_group(required=required)
+ group.add_argument('--foo')
+ group.add_argument('--bar', type=bool, default=True)
+ return parser
+
+ failures = [
+ '--foo X --bar Y',
+ '--foo X --bar=',
+ ]
+ successes = [
+ ('--foo X', NS(foo='X', bar=True)),
+ ('--bar X', NS(foo=None, bar=True)),
+ ('--bar=', NS(foo=None, bar=False)),
+ ]
+ successes_when_not_required = [
+ ('', NS(foo=None, bar=True)),
+ ]
+ usage_when_required = '''\
+ usage: PROG [-h] (--foo FOO | --bar BAR)
+ '''
+ usage_when_not_required = '''\
+ usage: PROG [-h] [--foo FOO | --bar BAR]
+ '''
+ help = '''\
+
+ options:
+ -h, --help show this help message and exit
+ --foo FOO
+ --bar BAR
+ '''
+
+
+class TestMutuallyExclusivePositionalWithDefault(MEMixin, TestCase):
+ def get_parser(self, required=None):
+ parser = ErrorRaisingArgumentParser(prog='PROG')
+ group = parser.add_mutually_exclusive_group(required=required)
+ group.add_argument('--foo')
+ group.add_argument('bar', nargs='?', type=bool, default=True)
+ return parser
+
+ failures = [
+ '--foo X Y',
+ ]
+ successes = [
+ ('--foo X', NS(foo='X', bar=True)),
+ ('X', NS(foo=None, bar=True)),
+ ]
+ successes_when_not_required = [
+ ('', NS(foo=None, bar=True)),
+ ]
+ usage_when_required = '''\
+ usage: PROG [-h] (--foo FOO | bar)
+ '''
+ usage_when_not_required = '''\
+ usage: PROG [-h] [--foo FOO | bar]
+ '''
+ help = '''\
+
+ positional arguments:
+ bar
+
+ options:
+ -h, --help show this help message and exit
+ --foo FOO
+ '''
+
# =================================================
# Mutually exclusive group in parent parser tests
# =================================================
@@ -3855,7 +4265,7 @@ class TestHelpUsageWithParentheses(HelpTestCase):
options:
-h, --help show this help message and exit
- -p {1 (option A), 2 (option B)}, --optional {1 (option A), 2 (option B)}
+ -p, --optional {1 (option A), 2 (option B)}
'''
version = ''
@@ -4139,6 +4549,158 @@ class TestHelpUsagePositionalsOnlyWrap(HelpTestCase):
version = ''
+class TestHelpUsageMetavarsSpacesParentheses(HelpTestCase):
+ # https://github.com/python/cpython/issues/62549
+ # https://github.com/python/cpython/issues/89743
+ parser_signature = Sig(prog='PROG')
+ argument_signatures = [
+ Sig('-n1', metavar='()', help='n1'),
+ Sig('-o1', metavar='(1, 2)', help='o1'),
+ Sig('-u1', metavar=' (uu) ', help='u1'),
+ Sig('-v1', metavar='( vv )', help='v1'),
+ Sig('-w1', metavar='(w)w', help='w1'),
+ Sig('-x1', metavar='x(x)', help='x1'),
+ Sig('-y1', metavar='yy)', help='y1'),
+ Sig('-z1', metavar='(zz', help='z1'),
+ Sig('-n2', metavar='[]', help='n2'),
+ Sig('-o2', metavar='[1, 2]', help='o2'),
+ Sig('-u2', metavar=' [uu] ', help='u2'),
+ Sig('-v2', metavar='[ vv ]', help='v2'),
+ Sig('-w2', metavar='[w]w', help='w2'),
+ Sig('-x2', metavar='x[x]', help='x2'),
+ Sig('-y2', metavar='yy]', help='y2'),
+ Sig('-z2', metavar='[zz', help='z2'),
+ ]
+
+ usage = '''\
+ usage: PROG [-h] [-n1 ()] [-o1 (1, 2)] [-u1 (uu) ] [-v1 ( vv )] [-w1 (w)w]
+ [-x1 x(x)] [-y1 yy)] [-z1 (zz] [-n2 []] [-o2 [1, 2]] [-u2 [uu] ]
+ [-v2 [ vv ]] [-w2 [w]w] [-x2 x[x]] [-y2 yy]] [-z2 [zz]
+ '''
+ help = usage + '''\
+
+ options:
+ -h, --help show this help message and exit
+ -n1 () n1
+ -o1 (1, 2) o1
+ -u1 (uu) u1
+ -v1 ( vv ) v1
+ -w1 (w)w w1
+ -x1 x(x) x1
+ -y1 yy) y1
+ -z1 (zz z1
+ -n2 [] n2
+ -o2 [1, 2] o2
+ -u2 [uu] u2
+ -v2 [ vv ] v2
+ -w2 [w]w w2
+ -x2 x[x] x2
+ -y2 yy] y2
+ -z2 [zz z2
+ '''
+ version = ''
+
+
+class TestHelpUsageNoWhitespaceCrash(TestCase):
+
+ def test_all_suppressed_mutex_followed_by_long_arg(self):
+ # https://github.com/python/cpython/issues/62090
+ # https://github.com/python/cpython/issues/96310
+ parser = argparse.ArgumentParser(prog='PROG')
+ mutex = parser.add_mutually_exclusive_group()
+ mutex.add_argument('--spam', help=argparse.SUPPRESS)
+ parser.add_argument('--eggs-eggs-eggs-eggs-eggs-eggs')
+ usage = textwrap.dedent('''\
+ usage: PROG [-h]
+ [--eggs-eggs-eggs-eggs-eggs-eggs EGGS_EGGS_EGGS_EGGS_EGGS_EGGS]
+ ''')
+ self.assertEqual(parser.format_usage(), usage)
+
+ def test_newline_in_metavar(self):
+ # https://github.com/python/cpython/issues/77048
+ mapping = ['123456', '12345', '12345', '123']
+ parser = argparse.ArgumentParser('11111111111111')
+ parser.add_argument('-v', '--verbose',
+ help='verbose mode', action='store_true')
+ parser.add_argument('targets',
+ help='installation targets',
+ nargs='+',
+ metavar='\n'.join(mapping))
+ usage = textwrap.dedent('''\
+ usage: 11111111111111 [-h] [-v]
+ 123456
+ 12345
+ 12345
+ 123 [123456
+ 12345
+ 12345
+ 123 ...]
+ ''')
+ self.assertEqual(parser.format_usage(), usage)
+
+ def test_empty_metavar_required_arg(self):
+ # https://github.com/python/cpython/issues/82091
+ parser = argparse.ArgumentParser(prog='PROG')
+ parser.add_argument('--nil', metavar='', required=True)
+ parser.add_argument('--a', metavar='A' * 70)
+ usage = (
+ 'usage: PROG [-h] --nil \n'
+ ' [--a AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA'
+ 'AAAAAAAAAAAAAAAAAAAAAAA]\n'
+ )
+ self.assertEqual(parser.format_usage(), usage)
+
+ def test_all_suppressed_mutex_with_optional_nargs(self):
+ # https://github.com/python/cpython/issues/98666
+ parser = argparse.ArgumentParser(prog='PROG')
+ mutex = parser.add_mutually_exclusive_group()
+ mutex.add_argument(
+ '--param1',
+ nargs='?', const='default', metavar='NAME', help=argparse.SUPPRESS)
+ mutex.add_argument(
+ '--param2',
+ nargs='?', const='default', metavar='NAME', help=argparse.SUPPRESS)
+ usage = 'usage: PROG [-h]\n'
+ self.assertEqual(parser.format_usage(), usage)
+
+ def test_nested_mutex_groups(self):
+ parser = argparse.ArgumentParser(prog='PROG')
+ g = parser.add_mutually_exclusive_group()
+ g.add_argument("--spam")
+ with warnings.catch_warnings():
+ warnings.simplefilter('ignore', DeprecationWarning)
+ gg = g.add_mutually_exclusive_group()
+ gg.add_argument("--hax")
+ gg.add_argument("--hox", help=argparse.SUPPRESS)
+ gg.add_argument("--hex")
+ g.add_argument("--eggs")
+ parser.add_argument("--num")
+
+ usage = textwrap.dedent('''\
+ usage: PROG [-h] [--spam SPAM | [--hax HAX | --hex HEX] | --eggs EGGS]
+ [--num NUM]
+ ''')
+ self.assertEqual(parser.format_usage(), usage)
+
+ def test_long_mutex_groups_wrap(self):
+ parser = argparse.ArgumentParser(prog='PROG')
+ g = parser.add_mutually_exclusive_group()
+ g.add_argument('--op1', metavar='MET', nargs='?')
+ g.add_argument('--op2', metavar=('MET1', 'MET2'), nargs='*')
+ g.add_argument('--op3', nargs='*')
+ g.add_argument('--op4', metavar=('MET1', 'MET2'), nargs='+')
+ g.add_argument('--op5', nargs='+')
+ g.add_argument('--op6', nargs=3)
+ g.add_argument('--op7', metavar=('MET1', 'MET2', 'MET3'), nargs=3)
+
+ usage = textwrap.dedent('''\
+ usage: PROG [-h] [--op1 [MET] | --op2 [MET1 [MET2 ...]] | --op3 [OP3 ...] |
+ --op4 MET1 [MET2 ...] | --op5 OP5 [OP5 ...] | --op6 OP6 OP6 OP6 |
+ --op7 MET1 MET2 MET3]
+ ''')
+ self.assertEqual(parser.format_usage(), usage)
+
+
class TestHelpVariableExpansion(HelpTestCase):
"""Test that variables are expanded properly in help messages"""
@@ -4148,7 +4710,7 @@ class TestHelpVariableExpansion(HelpTestCase):
help='x %(prog)s %(default)s %(type)s %%'),
Sig('-y', action='store_const', default=42, const='XXX',
help='y %(prog)s %(default)s %(const)s'),
- Sig('--foo', choices='abc',
+ Sig('--foo', choices=['a', 'b', 'c'],
help='foo %(prog)s %(default)s %(choices)s'),
Sig('--bar', default='baz', choices=[1, 2], metavar='BBB',
help='bar %(prog)s %(default)s %(dest)s'),
@@ -4338,8 +4900,8 @@ class TestHelpAlternatePrefixChars(HelpTestCase):
help = usage + '''\
options:
- ^^foo foo help
- ;b BAR, ;;bar BAR bar help
+ ^^foo foo help
+ ;b, ;;bar BAR bar help
'''
version = ''
@@ -4391,7 +4953,7 @@ class TestHelpNone(HelpTestCase):
version = ''
-class TestHelpTupleMetavar(HelpTestCase):
+class TestHelpTupleMetavarOptional(HelpTestCase):
"""Test specifying metavar as a tuple"""
parser_signature = Sig(prog='PROG')
@@ -4418,6 +4980,34 @@ class TestHelpTupleMetavar(HelpTestCase):
version = ''
+class TestHelpTupleMetavarPositional(HelpTestCase):
+ """Test specifying metavar on a Positional as a tuple"""
+
+ parser_signature = Sig(prog='PROG')
+ argument_signatures = [
+ Sig('w', help='w help', nargs='+', metavar=('W1', 'W2')),
+ Sig('x', help='x help', nargs='*', metavar=('X1', 'X2')),
+ Sig('y', help='y help', nargs=3, metavar=('Y1', 'Y2', 'Y3')),
+ Sig('z', help='z help', nargs='?', metavar=('Z1',)),
+ ]
+ argument_group_signatures = []
+ usage = '''\
+ usage: PROG [-h] W1 [W2 ...] [X1 [X2 ...]] Y1 Y2 Y3 [Z1]
+ '''
+ help = usage + '''\
+
+ positional arguments:
+ W1 W2 w help
+ X1 X2 x help
+ Y1 Y2 Y3 y help
+ Z1 z help
+
+ options:
+ -h, --help show this help message and exit
+ '''
+ version = ''
+
+
class TestHelpRawText(HelpTestCase):
"""Test the RawTextHelpFormatter"""
@@ -4711,6 +5301,46 @@ def custom_type(string):
version = ''
+class TestHelpUsageLongSubparserCommand(TestCase):
+ """Test that subparser commands are formatted correctly in help"""
+ maxDiff = None
+
+ def test_parent_help(self):
+ def custom_formatter(prog):
+ return argparse.RawTextHelpFormatter(prog, max_help_position=50)
+
+ parent_parser = argparse.ArgumentParser(
+ prog='PROG',
+ formatter_class=custom_formatter
+ )
+
+ cmd_subparsers = parent_parser.add_subparsers(title="commands",
+ metavar='CMD',
+ help='command to use')
+ cmd_subparsers.add_parser("add",
+ help="add something")
+
+ cmd_subparsers.add_parser("remove",
+ help="remove something")
+
+ cmd_subparsers.add_parser("a-very-long-command",
+ help="command that does something")
+
+ parser_help = parent_parser.format_help()
+ self.assertEqual(parser_help, textwrap.dedent('''\
+ usage: PROG [-h] CMD ...
+
+ options:
+ -h, --help show this help message and exit
+
+ commands:
+ CMD command to use
+ add add something
+ remove remove something
+ a-very-long-command command that does something
+ '''))
+
+
# =====================================
# Optional/Positional constructor tests
# =====================================
@@ -4718,15 +5348,15 @@ def custom_type(string):
class TestInvalidArgumentConstructors(TestCase):
"""Test a bunch of invalid Argument constructors"""
- def assertTypeError(self, *args, **kwargs):
+ def assertTypeError(self, *args, errmsg=None, **kwargs):
parser = argparse.ArgumentParser()
- self.assertRaises(TypeError, parser.add_argument,
- *args, **kwargs)
+ self.assertRaisesRegex(TypeError, errmsg, parser.add_argument,
+ *args, **kwargs)
- def assertValueError(self, *args, **kwargs):
+ def assertValueError(self, *args, errmsg=None, **kwargs):
parser = argparse.ArgumentParser()
- self.assertRaises(ValueError, parser.add_argument,
- *args, **kwargs)
+ self.assertRaisesRegex(ValueError, errmsg, parser.add_argument,
+ *args, **kwargs)
def test_invalid_keyword_arguments(self):
self.assertTypeError('-x', bar=None)
@@ -4736,13 +5366,17 @@ def test_invalid_keyword_arguments(self):
def test_missing_destination(self):
self.assertTypeError()
- for action in ['append', 'store']:
- self.assertTypeError(action=action)
+ for action in ['store', 'append', 'extend']:
+ with self.subTest(action=action):
+ self.assertTypeError(action=action)
def test_invalid_option_strings(self):
self.assertValueError('--')
self.assertValueError('---')
+ def test_invalid_prefix(self):
+ self.assertValueError('--foo', '+foo')
+
def test_invalid_type(self):
self.assertValueError('--foo', type='int')
self.assertValueError('--foo', type=(int, float))
@@ -4751,10 +5385,8 @@ def test_invalid_action(self):
self.assertValueError('-x', action='foo')
self.assertValueError('foo', action='baz')
self.assertValueError('--foo', action=('store', 'append'))
- parser = argparse.ArgumentParser()
- with self.assertRaises(ValueError) as cm:
- parser.add_argument("--foo", action="store-true")
- self.assertIn('unknown action', str(cm.exception))
+ self.assertValueError('--foo', action="store-true",
+ errmsg='unknown action')
def test_multiple_dest(self):
parser = argparse.ArgumentParser()
@@ -4767,39 +5399,47 @@ def test_multiple_dest(self):
def test_no_argument_actions(self):
for action in ['store_const', 'store_true', 'store_false',
'append_const', 'count']:
- for attrs in [dict(type=int), dict(nargs='+'),
- dict(choices='ab')]:
- self.assertTypeError('-x', action=action, **attrs)
+ with self.subTest(action=action):
+ for attrs in [dict(type=int), dict(nargs='+'),
+ dict(choices=['a', 'b'])]:
+ with self.subTest(attrs=attrs):
+ self.assertTypeError('-x', action=action, **attrs)
+ self.assertTypeError('x', action=action, **attrs)
+ self.assertTypeError('-x', action=action, nargs=0)
+ self.assertTypeError('x', action=action, nargs=0)
def test_no_argument_no_const_actions(self):
# options with zero arguments
for action in ['store_true', 'store_false', 'count']:
+ with self.subTest(action=action):
+ # const is always disallowed
+ self.assertTypeError('-x', const='foo', action=action)
- # const is always disallowed
- self.assertTypeError('-x', const='foo', action=action)
-
- # nargs is always disallowed
- self.assertTypeError('-x', nargs='*', action=action)
+ # nargs is always disallowed
+ self.assertTypeError('-x', nargs='*', action=action)
def test_more_than_one_argument_actions(self):
- for action in ['store', 'append']:
-
- # nargs=0 is disallowed
- self.assertValueError('-x', nargs=0, action=action)
- self.assertValueError('spam', nargs=0, action=action)
-
- # const is disallowed with non-optional arguments
- for nargs in [1, '*', '+']:
- self.assertValueError('-x', const='foo',
- nargs=nargs, action=action)
- self.assertValueError('spam', const='foo',
- nargs=nargs, action=action)
+ for action in ['store', 'append', 'extend']:
+ with self.subTest(action=action):
+ # nargs=0 is disallowed
+ action_name = 'append' if action == 'extend' else action
+ self.assertValueError('-x', nargs=0, action=action,
+ errmsg=f'nargs for {action_name} actions must be != 0')
+ self.assertValueError('spam', nargs=0, action=action,
+ errmsg=f'nargs for {action_name} actions must be != 0')
+
+ # const is disallowed with non-optional arguments
+ for nargs in [1, '*', '+']:
+ self.assertValueError('-x', const='foo',
+ nargs=nargs, action=action)
+ self.assertValueError('spam', const='foo',
+ nargs=nargs, action=action)
def test_required_const_actions(self):
for action in ['store_const', 'append_const']:
-
- # nargs is always disallowed
- self.assertTypeError('-x', nargs='+', action=action)
+ with self.subTest(action=action):
+ # nargs is always disallowed
+ self.assertTypeError('-x', nargs='+', action=action)
def test_parsers_action_missing_params(self):
self.assertTypeError('command', action='parsers')
@@ -4807,6 +5447,9 @@ def test_parsers_action_missing_params(self):
self.assertTypeError('command', action='parsers',
parser_class=argparse.ArgumentParser)
+ def test_version_missing_params(self):
+ self.assertTypeError('command', action='version')
+
def test_required_positional(self):
self.assertTypeError('foo', required=True)
@@ -5026,7 +5669,8 @@ def test_optional(self):
string = (
"Action(option_strings=['--foo', '-a', '-b'], dest='b', "
"nargs='+', const=None, default=42, type='int', "
- "choices=[1, 2, 3], required=False, help='HELP', metavar='METAVAR')")
+ "choices=[1, 2, 3], required=False, help='HELP', "
+ "metavar='METAVAR', deprecated=False)")
self.assertStringEqual(option, string)
def test_argument(self):
@@ -5043,7 +5687,8 @@ def test_argument(self):
string = (
"Action(option_strings=[], dest='x', nargs='?', "
"const=None, default=2.5, type=%r, choices=[0.5, 1.5, 2.5], "
- "required=True, help='H HH H', metavar='MV MV MV')" % float)
+ "required=True, help='H HH H', metavar='MV MV MV', "
+ "deprecated=False)" % float)
self.assertStringEqual(argument, string)
def test_namespace(self):
@@ -5235,6 +5880,139 @@ def spam(string_to_convert):
args = parser.parse_args('--foo spam!'.split())
self.assertEqual(NS(foo='foo_converted'), args)
+
+# ==============================================
+# Check that deprecated arguments output warning
+# ==============================================
+
+class TestDeprecatedArguments(TestCase):
+
+ def test_deprecated_option(self):
+ parser = argparse.ArgumentParser()
+ parser.add_argument('-f', '--foo', deprecated=True)
+
+ with captured_stderr() as stderr:
+ parser.parse_args(['--foo', 'spam'])
+ stderr = stderr.getvalue()
+ self.assertRegex(stderr, "warning: option '--foo' is deprecated")
+ self.assertEqual(stderr.count('is deprecated'), 1)
+
+ with captured_stderr() as stderr:
+ parser.parse_args(['-f', 'spam'])
+ stderr = stderr.getvalue()
+ self.assertRegex(stderr, "warning: option '-f' is deprecated")
+ self.assertEqual(stderr.count('is deprecated'), 1)
+
+ with captured_stderr() as stderr:
+ parser.parse_args(['--foo', 'spam', '-f', 'ham'])
+ stderr = stderr.getvalue()
+ self.assertRegex(stderr, "warning: option '--foo' is deprecated")
+ self.assertRegex(stderr, "warning: option '-f' is deprecated")
+ self.assertEqual(stderr.count('is deprecated'), 2)
+
+ with captured_stderr() as stderr:
+ parser.parse_args(['--foo', 'spam', '--foo', 'ham'])
+ stderr = stderr.getvalue()
+ self.assertRegex(stderr, "warning: option '--foo' is deprecated")
+ self.assertEqual(stderr.count('is deprecated'), 1)
+
+ def test_deprecated_boolean_option(self):
+ parser = argparse.ArgumentParser()
+ parser.add_argument('-f', '--foo', action=argparse.BooleanOptionalAction, deprecated=True)
+
+ with captured_stderr() as stderr:
+ parser.parse_args(['--foo'])
+ stderr = stderr.getvalue()
+ self.assertRegex(stderr, "warning: option '--foo' is deprecated")
+ self.assertEqual(stderr.count('is deprecated'), 1)
+
+ with captured_stderr() as stderr:
+ parser.parse_args(['-f'])
+ stderr = stderr.getvalue()
+ self.assertRegex(stderr, "warning: option '-f' is deprecated")
+ self.assertEqual(stderr.count('is deprecated'), 1)
+
+ with captured_stderr() as stderr:
+ parser.parse_args(['--no-foo'])
+ stderr = stderr.getvalue()
+ self.assertRegex(stderr, "warning: option '--no-foo' is deprecated")
+ self.assertEqual(stderr.count('is deprecated'), 1)
+
+ with captured_stderr() as stderr:
+ parser.parse_args(['--foo', '--no-foo'])
+ stderr = stderr.getvalue()
+ self.assertRegex(stderr, "warning: option '--foo' is deprecated")
+ self.assertRegex(stderr, "warning: option '--no-foo' is deprecated")
+ self.assertEqual(stderr.count('is deprecated'), 2)
+
+ def test_deprecated_arguments(self):
+ parser = argparse.ArgumentParser()
+ parser.add_argument('foo', nargs='?', deprecated=True)
+ parser.add_argument('bar', nargs='?', deprecated=True)
+
+ with captured_stderr() as stderr:
+ parser.parse_args([])
+ stderr = stderr.getvalue()
+ self.assertEqual(stderr.count('is deprecated'), 0)
+
+ with captured_stderr() as stderr:
+ parser.parse_args(['spam'])
+ stderr = stderr.getvalue()
+ self.assertRegex(stderr, "warning: argument 'foo' is deprecated")
+ self.assertEqual(stderr.count('is deprecated'), 1)
+
+ with captured_stderr() as stderr:
+ parser.parse_args(['spam', 'ham'])
+ stderr = stderr.getvalue()
+ self.assertRegex(stderr, "warning: argument 'foo' is deprecated")
+ self.assertRegex(stderr, "warning: argument 'bar' is deprecated")
+ self.assertEqual(stderr.count('is deprecated'), 2)
+
+ def test_deprecated_varargument(self):
+ parser = argparse.ArgumentParser()
+ parser.add_argument('foo', nargs='*', deprecated=True)
+
+ with captured_stderr() as stderr:
+ parser.parse_args([])
+ stderr = stderr.getvalue()
+ self.assertEqual(stderr.count('is deprecated'), 0)
+
+ with captured_stderr() as stderr:
+ parser.parse_args(['spam'])
+ stderr = stderr.getvalue()
+ self.assertRegex(stderr, "warning: argument 'foo' is deprecated")
+ self.assertEqual(stderr.count('is deprecated'), 1)
+
+ with captured_stderr() as stderr:
+ parser.parse_args(['spam', 'ham'])
+ stderr = stderr.getvalue()
+ self.assertRegex(stderr, "warning: argument 'foo' is deprecated")
+ self.assertEqual(stderr.count('is deprecated'), 1)
+
+ def test_deprecated_subparser(self):
+ parser = argparse.ArgumentParser()
+ subparsers = parser.add_subparsers()
+ subparsers.add_parser('foo', aliases=['baz'], deprecated=True)
+ subparsers.add_parser('bar')
+
+ with captured_stderr() as stderr:
+ parser.parse_args(['bar'])
+ stderr = stderr.getvalue()
+ self.assertEqual(stderr.count('is deprecated'), 0)
+
+ with captured_stderr() as stderr:
+ parser.parse_args(['foo'])
+ stderr = stderr.getvalue()
+ self.assertRegex(stderr, "warning: command 'foo' is deprecated")
+ self.assertEqual(stderr.count('is deprecated'), 1)
+
+ with captured_stderr() as stderr:
+ parser.parse_args(['baz'])
+ stderr = stderr.getvalue()
+ self.assertRegex(stderr, "warning: command 'baz' is deprecated")
+ self.assertEqual(stderr.count('is deprecated'), 1)
+
+
# ==================================================================
# Check semantics regarding the default argument and type conversion
# ==================================================================
@@ -5333,6 +6111,133 @@ def test_zero_or_more_optional(self):
self.assertEqual(NS(x=[]), args)
+class TestDoubleDash(TestCase):
+ def test_single_argument_option(self):
+ parser = argparse.ArgumentParser(exit_on_error=False)
+ parser.add_argument('-f', '--foo')
+ parser.add_argument('bar', nargs='*')
+
+ args = parser.parse_args(['--foo=--'])
+ self.assertEqual(NS(foo='--', bar=[]), args)
+ self.assertRaisesRegex(argparse.ArgumentError,
+ 'argument -f/--foo: expected one argument',
+ parser.parse_args, ['--foo', '--'])
+ args = parser.parse_args(['-f--'])
+ self.assertEqual(NS(foo='--', bar=[]), args)
+ self.assertRaisesRegex(argparse.ArgumentError,
+ 'argument -f/--foo: expected one argument',
+ parser.parse_args, ['-f', '--'])
+ args = parser.parse_args(['--foo', 'a', '--', 'b', 'c'])
+ self.assertEqual(NS(foo='a', bar=['b', 'c']), args)
+ args = parser.parse_args(['a', 'b', '--foo', 'c'])
+ self.assertEqual(NS(foo='c', bar=['a', 'b']), args)
+ args = parser.parse_args(['a', '--', 'b', '--foo', 'c'])
+ self.assertEqual(NS(foo=None, bar=['a', 'b', '--foo', 'c']), args)
+ args = parser.parse_args(['a', '--', 'b', '--', 'c', '--foo', 'd'])
+ self.assertEqual(NS(foo=None, bar=['a', 'b', '--', 'c', '--foo', 'd']), args)
+
+ def test_multiple_argument_option(self):
+ parser = argparse.ArgumentParser(exit_on_error=False)
+ parser.add_argument('-f', '--foo', nargs='*')
+ parser.add_argument('bar', nargs='*')
+
+ args = parser.parse_args(['--foo=--'])
+ self.assertEqual(NS(foo=['--'], bar=[]), args)
+ args = parser.parse_args(['--foo', '--'])
+ self.assertEqual(NS(foo=[], bar=[]), args)
+ args = parser.parse_args(['-f--'])
+ self.assertEqual(NS(foo=['--'], bar=[]), args)
+ args = parser.parse_args(['-f', '--'])
+ self.assertEqual(NS(foo=[], bar=[]), args)
+ args = parser.parse_args(['--foo', 'a', 'b', '--', 'c', 'd'])
+ self.assertEqual(NS(foo=['a', 'b'], bar=['c', 'd']), args)
+ args = parser.parse_args(['a', 'b', '--foo', 'c', 'd'])
+ self.assertEqual(NS(foo=['c', 'd'], bar=['a', 'b']), args)
+ args = parser.parse_args(['a', '--', 'b', '--foo', 'c', 'd'])
+ self.assertEqual(NS(foo=None, bar=['a', 'b', '--foo', 'c', 'd']), args)
+ args, argv = parser.parse_known_args(['a', 'b', '--foo', 'c', '--', 'd'])
+ self.assertEqual(NS(foo=['c'], bar=['a', 'b']), args)
+ self.assertEqual(argv, ['--', 'd'])
+
+ def test_multiple_double_dashes(self):
+ parser = argparse.ArgumentParser(exit_on_error=False)
+ parser.add_argument('foo')
+ parser.add_argument('bar', nargs='*')
+
+ args = parser.parse_args(['--', 'a', 'b', 'c'])
+ self.assertEqual(NS(foo='a', bar=['b', 'c']), args)
+ args = parser.parse_args(['a', '--', 'b', 'c'])
+ self.assertEqual(NS(foo='a', bar=['b', 'c']), args)
+ args = parser.parse_args(['a', 'b', '--', 'c'])
+ self.assertEqual(NS(foo='a', bar=['b', 'c']), args)
+ args = parser.parse_args(['a', '--', 'b', '--', 'c'])
+ self.assertEqual(NS(foo='a', bar=['b', '--', 'c']), args)
+ args = parser.parse_args(['--', '--', 'a', '--', 'b', 'c'])
+ self.assertEqual(NS(foo='--', bar=['a', '--', 'b', 'c']), args)
+
+ def test_remainder(self):
+ parser = argparse.ArgumentParser(exit_on_error=False)
+ parser.add_argument('foo')
+ parser.add_argument('bar', nargs='...')
+
+ args = parser.parse_args(['--', 'a', 'b', 'c'])
+ self.assertEqual(NS(foo='a', bar=['b', 'c']), args)
+ args = parser.parse_args(['a', '--', 'b', 'c'])
+ self.assertEqual(NS(foo='a', bar=['b', 'c']), args)
+ args = parser.parse_args(['a', 'b', '--', 'c'])
+ self.assertEqual(NS(foo='a', bar=['b', '--', 'c']), args)
+ args = parser.parse_args(['a', '--', 'b', '--', 'c'])
+ self.assertEqual(NS(foo='a', bar=['b', '--', 'c']), args)
+
+ parser = argparse.ArgumentParser(exit_on_error=False)
+ parser.add_argument('--foo')
+ parser.add_argument('bar', nargs='...')
+ args = parser.parse_args(['--foo', 'a', '--', 'b', '--', 'c'])
+ self.assertEqual(NS(foo='a', bar=['--', 'b', '--', 'c']), args)
+
+ def test_subparser(self):
+ parser = argparse.ArgumentParser(exit_on_error=False)
+ parser.add_argument('foo')
+ subparsers = parser.add_subparsers()
+ parser1 = subparsers.add_parser('run')
+ parser1.add_argument('-f')
+ parser1.add_argument('bar', nargs='*')
+
+ args = parser.parse_args(['x', 'run', 'a', 'b', '-f', 'c'])
+ self.assertEqual(NS(foo='x', f='c', bar=['a', 'b']), args)
+ args = parser.parse_args(['x', 'run', 'a', 'b', '--', '-f', 'c'])
+ self.assertEqual(NS(foo='x', f=None, bar=['a', 'b', '-f', 'c']), args)
+ args = parser.parse_args(['x', 'run', 'a', '--', 'b', '-f', 'c'])
+ self.assertEqual(NS(foo='x', f=None, bar=['a', 'b', '-f', 'c']), args)
+ args = parser.parse_args(['x', 'run', '--', 'a', 'b', '-f', 'c'])
+ self.assertEqual(NS(foo='x', f=None, bar=['a', 'b', '-f', 'c']), args)
+ args = parser.parse_args(['x', '--', 'run', 'a', 'b', '-f', 'c'])
+ self.assertEqual(NS(foo='x', f='c', bar=['a', 'b']), args)
+ args = parser.parse_args(['--', 'x', 'run', 'a', 'b', '-f', 'c'])
+ self.assertEqual(NS(foo='x', f='c', bar=['a', 'b']), args)
+ args = parser.parse_args(['x', 'run', '--', 'a', '--', 'b'])
+ self.assertEqual(NS(foo='x', f=None, bar=['a', '--', 'b']), args)
+ args = parser.parse_args(['x', '--', 'run', '--', 'a', '--', 'b'])
+ self.assertEqual(NS(foo='x', f=None, bar=['a', '--', 'b']), args)
+ self.assertRaisesRegex(argparse.ArgumentError,
+ "invalid choice: '--'",
+ parser.parse_args, ['--', 'x', '--', 'run', 'a', 'b'])
+
+ def test_subparser_after_multiple_argument_option(self):
+ parser = argparse.ArgumentParser(exit_on_error=False)
+ parser.add_argument('--foo', nargs='*')
+ subparsers = parser.add_subparsers()
+ parser1 = subparsers.add_parser('run')
+ parser1.add_argument('-f')
+ parser1.add_argument('bar', nargs='*')
+
+ args = parser.parse_args(['--foo', 'x', 'y', '--', 'run', 'a', 'b', '-f', 'c'])
+ self.assertEqual(NS(foo=['x', 'y'], f='c', bar=['a', 'b']), args)
+ self.assertRaisesRegex(argparse.ArgumentError,
+ "invalid choice: '--'",
+ parser.parse_args, ['--foo', 'x', '--', '--', 'run', 'a', 'b'])
+
+
# ===========================
# parse_intermixed_args tests
# ===========================
@@ -5352,14 +6257,25 @@ def test_basic(self):
args, extras = parser.parse_known_args(argv)
# cannot parse the '1,2,3'
- self.assertEqual(NS(bar='y', cmd='cmd', foo='x', rest=[]), args)
- self.assertEqual(["1", "2", "3"], extras)
+ self.assertEqual(NS(bar='y', cmd='cmd', foo='x', rest=[1]), args)
+ self.assertEqual(["2", "3"], extras)
+ args, extras = parser.parse_known_intermixed_args(argv)
+ self.assertEqual(NS(bar='y', cmd='cmd', foo='x', rest=[1, 2, 3]), args)
+ self.assertEqual([], extras)
+ # unknown optionals go into extras
+ argv = 'cmd --foo x --error 1 2 --bar y 3'.split()
+ args, extras = parser.parse_known_intermixed_args(argv)
+ self.assertEqual(NS(bar='y', cmd='cmd', foo='x', rest=[1, 2, 3]), args)
+ self.assertEqual(['--error'], extras)
argv = 'cmd --foo x 1 --error 2 --bar y 3'.split()
args, extras = parser.parse_known_intermixed_args(argv)
- # unknown optionals go into extras
- self.assertEqual(NS(bar='y', cmd='cmd', foo='x', rest=[1]), args)
- self.assertEqual(['--error', '2', '3'], extras)
+ self.assertEqual(NS(bar='y', cmd='cmd', foo='x', rest=[1, 2, 3]), args)
+ self.assertEqual(['--error'], extras)
+ argv = 'cmd --foo x 1 2 --error --bar y 3'.split()
+ args, extras = parser.parse_known_intermixed_args(argv)
+ self.assertEqual(NS(bar='y', cmd='cmd', foo='x', rest=[1, 2, 3]), args)
+ self.assertEqual(['--error'], extras)
# restores attributes that were temporarily changed
self.assertIsNone(parser.usage)
@@ -5378,28 +6294,49 @@ def test_remainder(self):
parser.parse_intermixed_args(argv)
self.assertRegex(str(cm.exception), r'\.\.\.')
- def test_exclusive(self):
- # mutually exclusive group; intermixed works fine
- parser = ErrorRaisingArgumentParser(prog='PROG')
+ def test_required_exclusive(self):
+ # required mutually exclusive group; intermixed works fine
+ parser = argparse.ArgumentParser(prog='PROG', exit_on_error=False)
group = parser.add_mutually_exclusive_group(required=True)
group.add_argument('--foo', action='store_true', help='FOO')
group.add_argument('--spam', help='SPAM')
parser.add_argument('badger', nargs='*', default='X', help='BADGER')
+ args = parser.parse_intermixed_args('--foo 1 2'.split())
+ self.assertEqual(NS(badger=['1', '2'], foo=True, spam=None), args)
args = parser.parse_intermixed_args('1 --foo 2'.split())
self.assertEqual(NS(badger=['1', '2'], foo=True, spam=None), args)
- self.assertRaises(ArgumentParserError, parser.parse_intermixed_args, '1 2'.split())
+ self.assertRaisesRegex(argparse.ArgumentError,
+ 'one of the arguments --foo --spam is required',
+ parser.parse_intermixed_args, '1 2'.split())
self.assertEqual(group.required, True)
- def test_exclusive_incompatible(self):
- # mutually exclusive group including positional - fail
- parser = ErrorRaisingArgumentParser(prog='PROG')
+ def test_required_exclusive_with_positional(self):
+ # required mutually exclusive group with positional argument
+ parser = argparse.ArgumentParser(prog='PROG', exit_on_error=False)
group = parser.add_mutually_exclusive_group(required=True)
group.add_argument('--foo', action='store_true', help='FOO')
group.add_argument('--spam', help='SPAM')
group.add_argument('badger', nargs='*', default='X', help='BADGER')
- self.assertRaises(TypeError, parser.parse_intermixed_args, [])
+ args = parser.parse_intermixed_args(['--foo'])
+ self.assertEqual(NS(foo=True, spam=None, badger='X'), args)
+ args = parser.parse_intermixed_args(['a', 'b'])
+ self.assertEqual(NS(foo=False, spam=None, badger=['a', 'b']), args)
+ self.assertRaisesRegex(argparse.ArgumentError,
+ 'one of the arguments --foo --spam badger is required',
+ parser.parse_intermixed_args, [])
+ self.assertRaisesRegex(argparse.ArgumentError,
+ 'argument badger: not allowed with argument --foo',
+ parser.parse_intermixed_args, ['--foo', 'a', 'b'])
+ self.assertRaisesRegex(argparse.ArgumentError,
+ 'argument badger: not allowed with argument --foo',
+ parser.parse_intermixed_args, ['a', '--foo', 'b'])
self.assertEqual(group.required, True)
+ def test_invalid_args(self):
+ parser = ErrorRaisingArgumentParser(prog='PROG')
+ self.assertRaises(ArgumentParserError, parser.parse_intermixed_args, ['a'])
+
+
class TestIntermixedMessageContentError(TestCase):
# case where Intermixed gives different error message
# error is raised by 1st parsing step
@@ -5417,7 +6354,7 @@ def test_missing_argument_name_in_message(self):
with self.assertRaises(ArgumentParserError) as cm:
parser.parse_intermixed_args([])
msg = str(cm.exception)
- self.assertNotRegex(msg, 'req_pos')
+ self.assertRegex(msg, 'req_pos')
self.assertRegex(msg, 'req_opt')
# ==========================
@@ -5667,7 +6604,8 @@ def test_help_with_metavar(self):
class TestExitOnError(TestCase):
def setUp(self):
- self.parser = argparse.ArgumentParser(exit_on_error=False)
+ self.parser = argparse.ArgumentParser(exit_on_error=False,
+ fromfile_prefix_chars='@')
self.parser.add_argument('--integers', metavar='N', type=int)
def test_exit_on_error_with_good_args(self):
@@ -5678,6 +6616,155 @@ def test_exit_on_error_with_bad_args(self):
with self.assertRaises(argparse.ArgumentError):
self.parser.parse_args('--integers a'.split())
+ def test_unrecognized_args(self):
+ self.assertRaisesRegex(argparse.ArgumentError,
+ 'unrecognized arguments: --foo bar',
+ self.parser.parse_args, '--foo bar'.split())
+
+ def test_unrecognized_intermixed_args(self):
+ self.assertRaisesRegex(argparse.ArgumentError,
+ 'unrecognized arguments: --foo bar',
+ self.parser.parse_intermixed_args, '--foo bar'.split())
+
+ def test_required_args(self):
+ self.parser.add_argument('bar')
+ self.parser.add_argument('baz')
+ self.assertRaisesRegex(argparse.ArgumentError,
+ 'the following arguments are required: bar, baz$',
+ self.parser.parse_args, [])
+
+ def test_required_args_with_metavar(self):
+ self.parser.add_argument('bar')
+ self.parser.add_argument('baz', metavar='BaZ')
+ self.assertRaisesRegex(argparse.ArgumentError,
+ 'the following arguments are required: bar, BaZ$',
+ self.parser.parse_args, [])
+
+ def test_required_args_n(self):
+ self.parser.add_argument('bar')
+ self.parser.add_argument('baz', nargs=3)
+ self.assertRaisesRegex(argparse.ArgumentError,
+ 'the following arguments are required: bar, baz$',
+ self.parser.parse_args, [])
+
+ def test_required_args_n_with_metavar(self):
+ self.parser.add_argument('bar')
+ self.parser.add_argument('baz', nargs=3, metavar=('B', 'A', 'Z'))
+ self.assertRaisesRegex(argparse.ArgumentError,
+ 'the following arguments are required: bar, B, A, Z$',
+ self.parser.parse_args, [])
+
+ def test_required_args_optional(self):
+ self.parser.add_argument('bar')
+ self.parser.add_argument('baz', nargs='?')
+ self.assertRaisesRegex(argparse.ArgumentError,
+ 'the following arguments are required: bar$',
+ self.parser.parse_args, [])
+
+ def test_required_args_zero_or_more(self):
+ self.parser.add_argument('bar')
+ self.parser.add_argument('baz', nargs='*')
+ self.assertRaisesRegex(argparse.ArgumentError,
+ 'the following arguments are required: bar$',
+ self.parser.parse_args, [])
+
+ def test_required_args_one_or_more(self):
+ self.parser.add_argument('bar')
+ self.parser.add_argument('baz', nargs='+')
+ self.assertRaisesRegex(argparse.ArgumentError,
+ 'the following arguments are required: bar, baz$',
+ self.parser.parse_args, [])
+
+ def test_required_args_one_or_more_with_metavar(self):
+ self.parser.add_argument('bar')
+ self.parser.add_argument('baz', nargs='+', metavar=('BaZ1', 'BaZ2'))
+ self.assertRaisesRegex(argparse.ArgumentError,
+ r'the following arguments are required: bar, BaZ1\[, BaZ2]$',
+ self.parser.parse_args, [])
+
+ def test_required_args_remainder(self):
+ self.parser.add_argument('bar')
+ self.parser.add_argument('baz', nargs='...')
+ self.assertRaisesRegex(argparse.ArgumentError,
+ 'the following arguments are required: bar$',
+ self.parser.parse_args, [])
+
+ def test_required_mutually_exclusive_args(self):
+ group = self.parser.add_mutually_exclusive_group(required=True)
+ group.add_argument('--bar')
+ group.add_argument('--baz')
+ self.assertRaisesRegex(argparse.ArgumentError,
+ 'one of the arguments --bar --baz is required',
+ self.parser.parse_args, [])
+
+ def test_conflicting_mutually_exclusive_args_optional_with_metavar(self):
+ group = self.parser.add_mutually_exclusive_group()
+ group.add_argument('--bar')
+ group.add_argument('baz', nargs='?', metavar='BaZ')
+ self.assertRaisesRegex(argparse.ArgumentError,
+ 'argument BaZ: not allowed with argument --bar$',
+ self.parser.parse_args, ['--bar', 'a', 'b'])
+ self.assertRaisesRegex(argparse.ArgumentError,
+ 'argument --bar: not allowed with argument BaZ$',
+ self.parser.parse_args, ['a', '--bar', 'b'])
+
+ def test_conflicting_mutually_exclusive_args_zero_or_more_with_metavar1(self):
+ group = self.parser.add_mutually_exclusive_group()
+ group.add_argument('--bar')
+ group.add_argument('baz', nargs='*', metavar=('BAZ1',))
+ self.assertRaisesRegex(argparse.ArgumentError,
+ 'argument BAZ1: not allowed with argument --bar$',
+ self.parser.parse_args, ['--bar', 'a', 'b'])
+ self.assertRaisesRegex(argparse.ArgumentError,
+ 'argument --bar: not allowed with argument BAZ1$',
+ self.parser.parse_args, ['a', '--bar', 'b'])
+
+ def test_conflicting_mutually_exclusive_args_zero_or_more_with_metavar2(self):
+ group = self.parser.add_mutually_exclusive_group()
+ group.add_argument('--bar')
+ group.add_argument('baz', nargs='*', metavar=('BAZ1', 'BAZ2'))
+ self.assertRaisesRegex(argparse.ArgumentError,
+ r'argument BAZ1\[, BAZ2]: not allowed with argument --bar$',
+ self.parser.parse_args, ['--bar', 'a', 'b'])
+ self.assertRaisesRegex(argparse.ArgumentError,
+ r'argument --bar: not allowed with argument BAZ1\[, BAZ2]$',
+ self.parser.parse_args, ['a', '--bar', 'b'])
+
+ def test_ambiguous_option(self):
+ self.parser.add_argument('--foobaz')
+ self.parser.add_argument('--fooble', action='store_true')
+ self.parser.add_argument('--foogle')
+ self.assertRaisesRegex(argparse.ArgumentError,
+ "ambiguous option: --foob could match --foobaz, --fooble",
+ self.parser.parse_args, ['--foob'])
+ self.assertRaisesRegex(argparse.ArgumentError,
+ "ambiguous option: --foob=1 could match --foobaz, --fooble$",
+ self.parser.parse_args, ['--foob=1'])
+ self.assertRaisesRegex(argparse.ArgumentError,
+ "ambiguous option: --foob could match --foobaz, --fooble$",
+ self.parser.parse_args, ['--foob', '1', '--foogle', '2'])
+ self.assertRaisesRegex(argparse.ArgumentError,
+ "ambiguous option: --foob=1 could match --foobaz, --fooble$",
+ self.parser.parse_args, ['--foob=1', '--foogle', '2'])
+
+ # TODO: RUSTPYTHON
+ @unittest.expectedFailure
+ def test_os_error(self):
+ self.parser.add_argument('file')
+ self.assertRaisesRegex(argparse.ArgumentError,
+ "No such file or directory: 'no-such-file'",
+ self.parser.parse_args, ['@no-such-file'])
+
+
+# =================
+# Translation tests
+# =================
+
+class TestTranslations(TestTranslationsBase):
+
+ def test_translations(self):
+ self.assertMsgidsEqual(argparse)
+
def tearDownModule():
# Remove global references to avoid looking like we have refleaks.
@@ -5686,4 +6773,8 @@ def tearDownModule():
if __name__ == '__main__':
+ # To regenerate translation snapshots
+ if len(sys.argv) > 1 and sys.argv[1] == '--snapshot-update':
+ update_translation_snapshots(argparse)
+ sys.exit(0)
unittest.main()
diff --git a/Lib/test/test_array.py b/Lib/test/test_array.py
index c3250ef72e..be89bec522 100644
--- a/Lib/test/test_array.py
+++ b/Lib/test/test_array.py
@@ -176,7 +176,7 @@ def test_numbers(self):
self.assertEqual(a, b,
msg="{0!r} != {1!r}; testcase={2!r}".format(a, b, testcase))
- # TODO: RUSTPYTHON
+ # TODO: RUSTPYTHON - requires UTF-32 encoding support in codecs and proper array reconstructor implementation
@unittest.expectedFailure
def test_unicode(self):
teststr = "Bonne Journ\xe9e \U0002030a\U00020347"
diff --git a/Lib/test/test_ast.py b/Lib/test/test_ast.py
index 8b28686fd6..1cac438250 100644
--- a/Lib/test/test_ast.py
+++ b/Lib/test/test_ast.py
@@ -340,7 +340,8 @@ class X:
support.gc_collect()
self.assertIsNone(ref())
- @unittest.skip("TODO: RUSTPYTHON, thread 'main' panicked at 'not implemented: async for comprehensions'")
+ # TODO: RUSTPYTHON
+ @unittest.expectedFailure
def test_snippets(self):
for input, output, kind in ((exec_tests, exec_results, "exec"),
(single_tests, single_results, "single"),
@@ -353,7 +354,8 @@ def test_snippets(self):
with self.subTest(action="compiling", input=i, kind=kind):
compile(ast_tree, "?", kind)
- @unittest.skip("TODO: RUSTPYTHON, thread 'main' panicked at 'not implemented: async for comprehensions'")
+ # TODO: RUSTPYTHON
+ @unittest.expectedFailure
def test_ast_validation(self):
# compile() is the only function that calls PyAST_Validate
snippets_to_validate = exec_tests + single_tests + eval_tests
@@ -361,7 +363,8 @@ def test_ast_validation(self):
tree = ast.parse(snippet)
compile(tree, '', 'exec')
- @unittest.skip("TODO: RUSTPYTHON, OverflowError: Python int too large to convert to Rust u32")
+ # TODO: RUSTPYTHON
+ @unittest.expectedFailure
def test_invalid_position_information(self):
invalid_linenos = [
(10, 1), (-10, -11), (10, -11), (-5, -2), (-5, 1)
diff --git a/Lib/test/test_base64.py b/Lib/test/test_base64.py
index fa03fa1d61..409c8c109e 100644
--- a/Lib/test/test_base64.py
+++ b/Lib/test/test_base64.py
@@ -545,6 +545,40 @@ def test_b85encode(self):
self.check_other_types(base64.b85encode, b"www.python.org",
b'cXxL#aCvlSZ*DGca%T')
+ def test_z85encode(self):
+ eq = self.assertEqual
+
+ tests = {
+ b'': b'',
+ b'www.python.org': b'CxXl-AcVLsz/dgCA+t',
+ bytes(range(255)): b"""009c61o!#m2NH?C3>iWS5d]J*6CRx17-skh9337x"""
+ b"""ar.{NbQB=+c[cR@eg&FcfFLssg=mfIi5%2YjuU>)kTv.7l}6Nnnj=AD"""
+ b"""oIFnTp/ga?r8($2sxO*itWpVyu$0IOwmYv=xLzi%y&a6dAb/]tBAI+J"""
+ b"""CZjQZE0{D[FpSr8GOteoH(41EJe-&}x#)cTlf[Bu8v].4}L}1:^-"""
+ b"""@qDP""",
+ b"""abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"""
+ b"""0123456789!@#0^&*();:<>,. []{}""":
+ b"""vpA.SwObN*x>?B1zeKohADlbxB-}$ND3R+ylQTvjm[uizoh55PpF:[^"""
+ b"""q=D:$s6eQefFLssg=mfIi5@cEbqrBJdKV-ciY]OSe*aw7DWL""",
+ b'no padding..': b'zF{UpvpS[.zF7NO',
+ b'zero compression\x00\x00\x00\x00': b'Ds.bnay/tbAb]JhB7]Mg00000',
+ b'zero compression\x00\x00\x00': b'Ds.bnay/tbAb]JhB7]Mg0000',
+ b"""Boundary:\x00\x00\x00\x00""": b"""lt}0:wmoI7iSGcW00""",
+ b'Space compr: ': b'q/DePwGUG3ze:IRarR^H',
+ b'\xff': b'@@',
+ b'\xff'*2: b'%nJ',
+ b'\xff'*3: b'%nS9',
+ b'\xff'*4: b'%nSc0',
+ }
+
+ for data, res in tests.items():
+ eq(base64.z85encode(data), res)
+
+ self.check_other_types(base64.z85encode, b"www.python.org",
+ b'CxXl-AcVLsz/dgCA+t')
+
def test_a85decode(self):
eq = self.assertEqual
@@ -586,6 +620,7 @@ def test_a85decode(self):
eq(base64.a85decode(b'y+',
b"www.python.org")
@@ -625,6 +660,41 @@ def test_b85decode(self):
self.check_other_types(base64.b85decode, b'cXxL#aCvlSZ*DGca%T',
b"www.python.org")
+ def test_z85decode(self):
+ eq = self.assertEqual
+
+ tests = {
+ b'': b'',
+ b'CxXl-AcVLsz/dgCA+t': b'www.python.org',
+ b"""009c61o!#m2NH?C3>iWS5d]J*6CRx17-skh9337x"""
+ b"""ar.{NbQB=+c[cR@eg&FcfFLssg=mfIi5%2YjuU>)kTv.7l}6Nnnj=AD"""
+ b"""oIFnTp/ga?r8($2sxO*itWpVyu$0IOwmYv=xLzi%y&a6dAb/]tBAI+J"""
+ b"""CZjQZE0{D[FpSr8GOteoH(41EJe-&}x#)cTlf[Bu8v].4}L}1:^-"""
+ b"""@qDP""": bytes(range(255)),
+ b"""vpA.SwObN*x>?B1zeKohADlbxB-}$ND3R+ylQTvjm[uizoh55PpF:[^"""
+ b"""q=D:$s6eQefFLssg=mfIi5@cEbqrBJdKV-ciY]OSe*aw7DWL""":
+ b"""abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"""
+ b"""0123456789!@#0^&*();:<>,. []{}""",
+ b'zF{UpvpS[.zF7NO': b'no padding..',
+ b'Ds.bnay/tbAb]JhB7]Mg00000': b'zero compression\x00\x00\x00\x00',
+ b'Ds.bnay/tbAb]JhB7]Mg0000': b'zero compression\x00\x00\x00',
+ b"""lt}0:wmoI7iSGcW00""": b"""Boundary:\x00\x00\x00\x00""",
+ b'q/DePwGUG3ze:IRarR^H': b'Space compr: ',
+ b'@@': b'\xff',
+ b'%nJ': b'\xff'*2,
+ b'%nS9': b'\xff'*3,
+ b'%nSc0': b'\xff'*4,
+ }
+
+ for data, res in tests.items():
+ eq(base64.z85decode(data), res)
+ eq(base64.z85decode(data.decode("ascii")), res)
+
+ self.check_other_types(base64.z85decode, b'CxXl-AcVLsz/dgCA+t',
+ b'www.python.org')
+
def test_a85_padding(self):
eq = self.assertEqual
@@ -689,6 +759,8 @@ def test_a85decode_errors(self):
self.assertRaises(ValueError, base64.a85decode, b's8W', adobe=False)
self.assertRaises(ValueError, base64.a85decode, b's8W-', adobe=False)
self.assertRaises(ValueError, base64.a85decode, b's8W-"', adobe=False)
+ self.assertRaises(ValueError, base64.a85decode, b'aaaay',
+ foldspaces=True)
def test_b85decode_errors(self):
illegal = list(range(33)) + \
@@ -704,6 +776,21 @@ def test_b85decode_errors(self):
self.assertRaises(ValueError, base64.b85decode, b'|NsC')
self.assertRaises(ValueError, base64.b85decode, b'|NsC1')
+ def test_z85decode_errors(self):
+ illegal = list(range(33)) + \
+ list(b'"\',;_`|\\~') + \
+ list(range(128, 256))
+ for c in illegal:
+ with self.assertRaises(ValueError, msg=bytes([c])):
+ base64.z85decode(b'0000' + bytes([c]))
+
+ # b'\xff\xff\xff\xff' encodes to b'%nSc0', the following will overflow:
+ self.assertRaises(ValueError, base64.z85decode, b'%')
+ self.assertRaises(ValueError, base64.z85decode, b'%n')
+ self.assertRaises(ValueError, base64.z85decode, b'%nS')
+ self.assertRaises(ValueError, base64.z85decode, b'%nSc')
+ self.assertRaises(ValueError, base64.z85decode, b'%nSc1')
+
def test_decode_nonascii_str(self):
decode_funcs = (base64.b64decode,
base64.standard_b64decode,
@@ -711,7 +798,8 @@ def test_decode_nonascii_str(self):
base64.b32decode,
base64.b16decode,
base64.b85decode,
- base64.a85decode)
+ base64.a85decode,
+ base64.z85decode)
for f in decode_funcs:
self.assertRaises(ValueError, f, 'with non-ascii \xcb')
diff --git a/Lib/test/test_baseexception.py b/Lib/test/test_baseexception.py
index e19162a6ab..63bf538aa5 100644
--- a/Lib/test/test_baseexception.py
+++ b/Lib/test/test_baseexception.py
@@ -83,6 +83,8 @@ def test_inheritance(self):
exc_set = set(e for e in exc_set if not e.startswith('_'))
# RUSTPYTHON specific
exc_set.discard("JitError")
+ # XXX: RUSTPYTHON; IncompleteInputError will be officially introduced in Python 3.15
+ exc_set.discard("IncompleteInputError")
self.assertEqual(len(exc_set), 0, "%s not accounted for" % exc_set)
interface_tests = ("length", "args", "str", "repr")
@@ -119,8 +121,6 @@ def test_interface_no_arg(self):
[repr(exc), exc.__class__.__name__ + '()'])
self.interface_test_driver(results)
- # TODO: RUSTPYTHON
- @unittest.expectedFailure
def test_setstate_refcount_no_crash(self):
# gh-97591: Acquire strong reference before calling tp_hash slot
# in PyObject_SetAttr.
diff --git a/Lib/test/test_binascii.py b/Lib/test/test_binascii.py
index 4ae89837cc..40a2ca9f76 100644
--- a/Lib/test/test_binascii.py
+++ b/Lib/test/test_binascii.py
@@ -258,8 +258,6 @@ def test_hex(self):
self.assertEqual(binascii.hexlify(self.type2test(s)), t)
self.assertEqual(binascii.unhexlify(self.type2test(t)), u)
- # TODO: RUSTPYTHON
- @unittest.expectedFailure
def test_hex_separator(self):
"""Test that hexlify and b2a_hex are binary versions of bytes.hex."""
# Logic of separators is tested in test_bytes.py. This checks that
@@ -388,8 +386,6 @@ def test_empty_string(self):
except Exception as err:
self.fail("{}({!r}) raises {!r}".format(func, empty, err))
- # TODO: RUSTPYTHON
- @unittest.expectedFailure
def test_unicode_b2a(self):
# Unicode strings are not accepted by b2a_* functions.
for func in set(all_functions) - set(a2b_functions):
diff --git a/Lib/test/test_bytes.py b/Lib/test/test_bytes.py
index cc1affc669..e84df546a8 100644
--- a/Lib/test/test_bytes.py
+++ b/Lib/test/test_bytes.py
@@ -10,6 +10,7 @@
import sys
import copy
import functools
+import operator
import pickle
import tempfile
import textwrap
@@ -46,6 +47,10 @@ def __index__(self):
class BaseBytesTest:
+ def assertTypedEqual(self, actual, expected):
+ self.assertIs(type(actual), type(expected))
+ self.assertEqual(actual, expected)
+
def test_basics(self):
b = self.type2test()
self.assertEqual(type(b), self.type2test)
@@ -209,8 +214,6 @@ def test_constructor_overflow(self):
except (OverflowError, MemoryError):
pass
- # TODO: RUSTPYTHON
- @unittest.expectedFailure
def test_constructor_exceptions(self):
# Issue #34974: bytes and bytearray constructors replace unexpected
# exceptions.
@@ -739,6 +742,37 @@ def check(fmt, vals, result):
check(b'%i%b %*.*b', (10, b'3', 5, 3, b'abc',), b'103 abc')
check(b'%c', b'a', b'a')
+ class PseudoFloat:
+ def __init__(self, value):
+ self.value = float(value)
+ def __int__(self):
+ return int(self.value)
+
+ pi = PseudoFloat(3.1415)
+
+ exceptions_params = [
+ ('%x format: an integer is required, not float', b'%x', 3.14),
+ ('%X format: an integer is required, not float', b'%X', 2.11),
+ ('%o format: an integer is required, not float', b'%o', 1.79),
+ ('%x format: an integer is required, not PseudoFloat', b'%x', pi),
+ ('%x format: an integer is required, not complex', b'%x', 3j),
+ ('%X format: an integer is required, not complex', b'%X', 2j),
+ ('%o format: an integer is required, not complex', b'%o', 1j),
+ ('%u format: a real number is required, not complex', b'%u', 3j),
+ # See https://github.com/python/cpython/issues/130928 as for why
+ # the exception message contains '%d' instead of '%i'.
+ ('%d format: a real number is required, not complex', b'%i', 2j),
+ ('%d format: a real number is required, not complex', b'%d', 2j),
+ (
+ r'%c requires an integer in range\(256\) or a single byte',
+ b'%c', pi
+ ),
+ ]
+
+ for msg, format_bytes, value in exceptions_params:
+ with self.assertRaisesRegex(TypeError, msg):
+ operator.mod(format_bytes, value)
+
def test_imod(self):
b = self.type2test(b'hello, %b!')
orig = b
@@ -997,13 +1031,13 @@ def test_translate(self):
self.assertEqual(c, b'hllo')
def test_sq_item(self):
- _testcapi = import_helper.import_module('_testcapi')
+ _testlimitedcapi = import_helper.import_module('_testlimitedcapi')
obj = self.type2test((42,))
with self.assertRaises(IndexError):
- _testcapi.sequence_getitem(obj, -2)
+ _testlimitedcapi.sequence_getitem(obj, -2)
with self.assertRaises(IndexError):
- _testcapi.sequence_getitem(obj, 1)
- self.assertEqual(_testcapi.sequence_getitem(obj, 0), 42)
+ _testlimitedcapi.sequence_getitem(obj, 1)
+ self.assertEqual(_testlimitedcapi.sequence_getitem(obj, 0), 42)
class BytesTest(BaseBytesTest, unittest.TestCase):
@@ -1033,36 +1067,63 @@ def test_buffer_is_readonly(self):
self.assertRaises(TypeError, f.readinto, b"")
def test_custom(self):
- class A:
- def __bytes__(self):
- return b'abc'
- self.assertEqual(bytes(A()), b'abc')
- class A: pass
- self.assertRaises(TypeError, bytes, A())
- class A:
- def __bytes__(self):
- return None
- self.assertRaises(TypeError, bytes, A())
- class A:
+ self.assertEqual(bytes(BytesSubclass(b'abc')), b'abc')
+ self.assertEqual(BytesSubclass(OtherBytesSubclass(b'abc')),
+ BytesSubclass(b'abc'))
+ self.assertEqual(bytes(WithBytes(b'abc')), b'abc')
+ self.assertEqual(BytesSubclass(WithBytes(b'abc')), BytesSubclass(b'abc'))
+
+ class NoBytes: pass
+ self.assertRaises(TypeError, bytes, NoBytes())
+ self.assertRaises(TypeError, bytes, WithBytes('abc'))
+ self.assertRaises(TypeError, bytes, WithBytes(None))
+ class IndexWithBytes:
def __bytes__(self):
return b'a'
def __index__(self):
return 42
- self.assertEqual(bytes(A()), b'a')
+ self.assertEqual(bytes(IndexWithBytes()), b'a')
# Issue #25766
- class A(str):
+ class StrWithBytes(str):
+ def __new__(cls, value):
+ self = str.__new__(cls, '\u20ac')
+ self.value = value
+ return self
def __bytes__(self):
- return b'abc'
- self.assertEqual(bytes(A('\u20ac')), b'abc')
- self.assertEqual(bytes(A('\u20ac'), 'iso8859-15'), b'\xa4')
+ return self.value
+ self.assertEqual(bytes(StrWithBytes(b'abc')), b'abc')
+ self.assertEqual(bytes(StrWithBytes(b'abc'), 'iso8859-15'), b'\xa4')
+ self.assertEqual(bytes(StrWithBytes(BytesSubclass(b'abc'))), b'abc')
+ self.assertEqual(BytesSubclass(StrWithBytes(b'abc')), BytesSubclass(b'abc'))
+ self.assertEqual(BytesSubclass(StrWithBytes(b'abc'), 'iso8859-15'),
+ BytesSubclass(b'\xa4'))
+ self.assertEqual(BytesSubclass(StrWithBytes(BytesSubclass(b'abc'))),
+ BytesSubclass(b'abc'))
+ self.assertEqual(BytesSubclass(StrWithBytes(OtherBytesSubclass(b'abc'))),
+ BytesSubclass(b'abc'))
# Issue #24731
- class A:
+ self.assertTypedEqual(bytes(WithBytes(BytesSubclass(b'abc'))), BytesSubclass(b'abc'))
+ self.assertTypedEqual(BytesSubclass(WithBytes(BytesSubclass(b'abc'))),
+ BytesSubclass(b'abc'))
+ self.assertTypedEqual(BytesSubclass(WithBytes(OtherBytesSubclass(b'abc'))),
+ BytesSubclass(b'abc'))
+
+ class BytesWithBytes(bytes):
+ def __new__(cls, value):
+ self = bytes.__new__(cls, b'\xa4')
+ self.value = value
+ return self
def __bytes__(self):
- return OtherBytesSubclass(b'abc')
- self.assertEqual(bytes(A()), b'abc')
- self.assertIs(type(bytes(A())), OtherBytesSubclass)
- self.assertEqual(BytesSubclass(A()), b'abc')
- self.assertIs(type(BytesSubclass(A())), BytesSubclass)
+ return self.value
+ self.assertTypedEqual(bytes(BytesWithBytes(b'abc')), b'abc')
+ self.assertTypedEqual(BytesSubclass(BytesWithBytes(b'abc')),
+ BytesSubclass(b'abc'))
+ self.assertTypedEqual(bytes(BytesWithBytes(BytesSubclass(b'abc'))),
+ BytesSubclass(b'abc'))
+ self.assertTypedEqual(BytesSubclass(BytesWithBytes(BytesSubclass(b'abc'))),
+ BytesSubclass(b'abc'))
+ self.assertTypedEqual(BytesSubclass(BytesWithBytes(OtherBytesSubclass(b'abc'))),
+ BytesSubclass(b'abc'))
# Test PyBytes_FromFormat()
def test_from_format(self):
@@ -1235,6 +1296,8 @@ class SubBytes(bytes):
class ByteArrayTest(BaseBytesTest, unittest.TestCase):
type2test = bytearray
+ _testlimitedcapi = import_helper.import_module('_testlimitedcapi')
+
def test_getitem_error(self):
b = bytearray(b'python')
msg = "bytearray indices must be integers or slices"
@@ -1327,47 +1390,73 @@ def by(s):
self.assertEqual(re.findall(br"\w+", b), [by("Hello"), by("world")])
def test_setitem(self):
- b = bytearray([1, 2, 3])
- b[1] = 100
- self.assertEqual(b, bytearray([1, 100, 3]))
- b[-1] = 200
- self.assertEqual(b, bytearray([1, 100, 200]))
- b[0] = Indexable(10)
- self.assertEqual(b, bytearray([10, 100, 200]))
- try:
- b[3] = 0
- self.fail("Didn't raise IndexError")
- except IndexError:
- pass
- try:
- b[-10] = 0
- self.fail("Didn't raise IndexError")
- except IndexError:
- pass
- try:
- b[0] = 256
- self.fail("Didn't raise ValueError")
- except ValueError:
- pass
- try:
- b[0] = Indexable(-1)
- self.fail("Didn't raise ValueError")
- except ValueError:
- pass
- try:
- b[0] = None
- self.fail("Didn't raise TypeError")
- except TypeError:
- pass
+ def setitem_as_mapping(b, i, val):
+ b[i] = val
+
+ def setitem_as_sequence(b, i, val):
+ self._testlimitedcapi.sequence_setitem(b, i, val)
+
+ def do_tests(setitem):
+ b = bytearray([1, 2, 3])
+ setitem(b, 1, 100)
+ self.assertEqual(b, bytearray([1, 100, 3]))
+ setitem(b, -1, 200)
+ self.assertEqual(b, bytearray([1, 100, 200]))
+ setitem(b, 0, Indexable(10))
+ self.assertEqual(b, bytearray([10, 100, 200]))
+ try:
+ setitem(b, 3, 0)
+ self.fail("Didn't raise IndexError")
+ except IndexError:
+ pass
+ try:
+ setitem(b, -10, 0)
+ self.fail("Didn't raise IndexError")
+ except IndexError:
+ pass
+ try:
+ setitem(b, 0, 256)
+ self.fail("Didn't raise ValueError")
+ except ValueError:
+ pass
+ try:
+ setitem(b, 0, Indexable(-1))
+ self.fail("Didn't raise ValueError")
+ except ValueError:
+ pass
+ try:
+ setitem(b, 0, object())
+ self.fail("Didn't raise TypeError")
+ except TypeError:
+ pass
+
+ with self.subTest("tp_as_mapping"):
+ do_tests(setitem_as_mapping)
+
+ with self.subTest("tp_as_sequence"):
+ do_tests(setitem_as_sequence)
def test_delitem(self):
- b = bytearray(range(10))
- del b[0]
- self.assertEqual(b, bytearray(range(1, 10)))
- del b[-1]
- self.assertEqual(b, bytearray(range(1, 9)))
- del b[4]
- self.assertEqual(b, bytearray([1, 2, 3, 4, 6, 7, 8]))
+ def del_as_mapping(b, i):
+ del b[i]
+
+ def del_as_sequence(b, i):
+ self._testlimitedcapi.sequence_delitem(b, i)
+
+ def do_tests(delete):
+ b = bytearray(range(10))
+ delete(b, 0)
+ self.assertEqual(b, bytearray(range(1, 10)))
+ delete(b, -1)
+ self.assertEqual(b, bytearray(range(1, 9)))
+ delete(b, 4)
+ self.assertEqual(b, bytearray([1, 2, 3, 4, 6, 7, 8]))
+
+ with self.subTest("tp_as_mapping"):
+ do_tests(del_as_mapping)
+
+ with self.subTest("tp_as_sequence"):
+ do_tests(del_as_sequence)
def test_setslice(self):
b = bytearray(range(10))
@@ -1560,6 +1649,13 @@ def test_extend(self):
a = bytearray(b'')
a.extend([Indexable(ord('a'))])
self.assertEqual(a, b'a')
+ a = bytearray(b'abc')
+ self.assertRaisesRegex(TypeError, # Override for string.
+ "expected iterable of integers; got: 'str'",
+ a.extend, 'def')
+ self.assertRaisesRegex(TypeError, # But not for others.
+ "can't extend bytearray with float",
+ a.extend, 1.0)
def test_remove(self):
b = bytearray(b'hello')
@@ -1749,6 +1845,8 @@ def test_repeat_after_setslice(self):
self.assertEqual(b3, b'xcxcxc')
def test_mutating_index(self):
+ # See gh-91153
+
class Boom:
def __index__(self):
b.clear()
@@ -1760,10 +1858,9 @@ def __index__(self):
b[0] = Boom()
with self.subTest("tp_as_sequence"):
- _testcapi = import_helper.import_module('_testcapi')
b = bytearray(b'Now you see me...')
with self.assertRaises(IndexError):
- _testcapi.sequence_setitem(b, 0, Boom())
+ self._testlimitedcapi.sequence_setitem(b, 0, Boom())
class AssortedBytesTest(unittest.TestCase):
@@ -2062,6 +2159,12 @@ class BytesSubclass(bytes):
class OtherBytesSubclass(bytes):
pass
+class WithBytes:
+ def __init__(self, value):
+ self.value = value
+ def __bytes__(self):
+ return self.value
+
class ByteArraySubclassTest(SubclassTest, unittest.TestCase):
basetype = bytearray
type2test = ByteArraySubclass
diff --git a/Lib/test/test_bz2.py b/Lib/test/test_bz2.py
index b716d6016b..dfc444cbbd 100644
--- a/Lib/test/test_bz2.py
+++ b/Lib/test/test_bz2.py
@@ -676,8 +676,6 @@ def testCompress4G(self, size):
finally:
data = None
- # TODO: RUSTPYTHON
- @unittest.expectedFailure
def testPickle(self):
for proto in range(pickle.HIGHEST_PROTOCOL + 1):
with self.assertRaises(TypeError):
@@ -736,8 +734,6 @@ def testDecompress4G(self, size):
compressed = None
decompressed = None
- # TODO: RUSTPYTHON
- @unittest.expectedFailure
def testPickle(self):
for proto in range(pickle.HIGHEST_PROTOCOL + 1):
with self.assertRaises(TypeError):
diff --git a/Lib/test/test_call.py b/Lib/test/test_call.py
index 8e64ffffd0..3cb9659acb 100644
--- a/Lib/test/test_call.py
+++ b/Lib/test/test_call.py
@@ -13,8 +13,6 @@
class FunctionCalls(unittest.TestCase):
- # TODO: RUSTPYTHON
- @unittest.expectedFailure
def test_kwargs_order(self):
# bpo-34320: **kwargs should preserve order of passed OrderedDict
od = collections.OrderedDict([('a', 1), ('b', 2)])
diff --git a/Lib/test/test_cmd.py b/Lib/test/test_cmd.py
index 319801c71f..46ec82b704 100644
--- a/Lib/test/test_cmd.py
+++ b/Lib/test/test_cmd.py
@@ -9,7 +9,10 @@
import doctest
import unittest
import io
+import textwrap
from test import support
+from test.support.import_helper import import_module
+from test.support.pty_helper import run_pty
class samplecmdclass(cmd.Cmd):
"""
@@ -244,23 +247,55 @@ def test_input_reset_at_EOF(self):
"(Cmd) *** Unknown syntax: EOF\n"))
+class CmdPrintExceptionClass(cmd.Cmd):
+ """
+ GH-80731
+ cmd.Cmd should print the correct exception in default()
+ >>> mycmd = CmdPrintExceptionClass()
+ >>> try:
+ ... raise ValueError("test")
+ ... except ValueError:
+ ... mycmd.onecmd("not important")
+ (, ValueError('test'))
+ """
+
+ def default(self, line):
+ print(sys.exc_info()[:2])
+
+
+@support.requires_subprocess()
+class CmdTestReadline(unittest.TestCase):
+ def setUpClass():
+ # Ensure that the readline module is loaded
+ # If this fails, the test is skipped because SkipTest will be raised
+ readline = import_module('readline')
+
+ def test_basic_completion(self):
+ script = textwrap.dedent("""
+ import cmd
+ class simplecmd(cmd.Cmd):
+ def do_tab_completion_test(self, args):
+ print('tab completion success')
+ return True
+
+ simplecmd().cmdloop()
+ """)
+
+ # 't' and complete 'ab_completion_test' to 'tab_completion_test'
+ input = b"t\t\n"
+
+ output = run_pty(script, input)
+
+ self.assertIn(b'ab_completion_test', output)
+ self.assertIn(b'tab completion success', output)
+
def load_tests(loader, tests, pattern):
tests.addTest(doctest.DocTestSuite())
return tests
-def test_coverage(coverdir):
- trace = support.import_module('trace')
- tracer=trace.Trace(ignoredirs=[sys.base_prefix, sys.base_exec_prefix,],
- trace=0, count=1)
- tracer.run('import importlib; importlib.reload(cmd); test_main()')
- r=tracer.results()
- print("Writing coverage results...")
- r.write_results(show_missing=True, summary=True, coverdir=coverdir)
if __name__ == "__main__":
- if "-c" in sys.argv:
- test_coverage('/tmp/cmd.cover')
- elif "-i" in sys.argv:
+ if "-i" in sys.argv:
samplecmdclass().cmdloop()
else:
unittest.main()
diff --git a/Lib/test/test_cmd_line.py b/Lib/test/test_cmd_line.py
index da53f085a5..a7e4f6dd27 100644
--- a/Lib/test/test_cmd_line.py
+++ b/Lib/test/test_cmd_line.py
@@ -259,7 +259,8 @@ def test_undecodable_code(self):
if not stdout.startswith(pattern):
raise AssertionError("%a doesn't start with %a" % (stdout, pattern))
- @unittest.skip("TODO: RUSTPYTHON, thread 'main' panicked at 'unexpected invalid UTF-8 code point'")
+ # TODO: RUSTPYTHON
+ @unittest.expectedFailure
@unittest.skipIf(sys.platform == 'win32',
'Windows has a native unicode API')
def test_invalid_utf8_arg(self):
diff --git a/Lib/test/test_code.py b/Lib/test/test_code.py
index 1aceff4efc..6b0dc09e28 100644
--- a/Lib/test/test_code.py
+++ b/Lib/test/test_code.py
@@ -249,8 +249,6 @@ def func(): pass
co.co_freevars,
co.co_cellvars)
- # TODO: RUSTPYTHON
- @unittest.expectedFailure
def test_qualname(self):
self.assertEqual(
CodeTest.test_qualname.__code__.co_qualname,
diff --git a/Lib/test/test_collections.py b/Lib/test/test_collections.py
index ecd574ab83..901f596cc3 100644
--- a/Lib/test/test_collections.py
+++ b/Lib/test/test_collections.py
@@ -698,8 +698,6 @@ class NewPoint(tuple):
self.assertEqual(np.x, 1)
self.assertEqual(np.y, 2)
- # TODO: RUSTPYTHON
- @unittest.expectedFailure
def test_new_builtins_issue_43102(self):
obj = namedtuple('C', ())
new_func = obj.__new__
diff --git a/Lib/test/test_compileall.py b/Lib/test/test_compileall.py
new file mode 100644
index 0000000000..a490b8a1d5
--- /dev/null
+++ b/Lib/test/test_compileall.py
@@ -0,0 +1,1177 @@
+import compileall
+import contextlib
+import filecmp
+import importlib.util
+import io
+import os
+import py_compile
+import shutil
+import struct
+import sys
+import tempfile
+import test.test_importlib.util
+import time
+import unittest
+
+from unittest import mock, skipUnless
+try:
+ # compileall relies on ProcessPoolExecutor if ProcessPoolExecutor exists
+ # and it can function.
+ from multiprocessing.util import _cleanup_tests as multiprocessing_cleanup_tests
+ from concurrent.futures import ProcessPoolExecutor
+ from concurrent.futures.process import _check_system_limits
+ _check_system_limits()
+ _have_multiprocessing = True
+except (NotImplementedError, ModuleNotFoundError):
+ _have_multiprocessing = False
+
+from test import support
+from test.support import os_helper
+from test.support import script_helper
+from test.test_py_compile import without_source_date_epoch
+from test.test_py_compile import SourceDateEpochTestMeta
+from test.support.os_helper import FakePath
+
+
+def get_pyc(script, opt):
+ if not opt:
+ # Replace None and 0 with ''
+ opt = ''
+ return importlib.util.cache_from_source(script, optimization=opt)
+
+
+def get_pycs(script):
+ return [get_pyc(script, opt) for opt in (0, 1, 2)]
+
+
+def is_hardlink(filename1, filename2):
+ """Returns True if two files have the same inode (hardlink)"""
+ inode1 = os.stat(filename1).st_ino
+ inode2 = os.stat(filename2).st_ino
+ return inode1 == inode2
+
+
+class CompileallTestsBase:
+
+ def setUp(self):
+ self.directory = tempfile.mkdtemp()
+ self.addCleanup(shutil.rmtree, self.directory)
+
+ self.source_path = os.path.join(self.directory, '_test.py')
+ self.bc_path = importlib.util.cache_from_source(self.source_path)
+ with open(self.source_path, 'w', encoding="utf-8") as file:
+ file.write('x = 123\n')
+ self.source_path2 = os.path.join(self.directory, '_test2.py')
+ self.bc_path2 = importlib.util.cache_from_source(self.source_path2)
+ shutil.copyfile(self.source_path, self.source_path2)
+ self.subdirectory = os.path.join(self.directory, '_subdir')
+ os.mkdir(self.subdirectory)
+ self.source_path3 = os.path.join(self.subdirectory, '_test3.py')
+ shutil.copyfile(self.source_path, self.source_path3)
+
+ def add_bad_source_file(self):
+ self.bad_source_path = os.path.join(self.directory, '_test_bad.py')
+ with open(self.bad_source_path, 'w', encoding="utf-8") as file:
+ file.write('x (\n')
+
+ def timestamp_metadata(self):
+ with open(self.bc_path, 'rb') as file:
+ data = file.read(12)
+ mtime = int(os.stat(self.source_path).st_mtime)
+ compare = struct.pack('<4sLL', importlib.util.MAGIC_NUMBER, 0,
+ mtime & 0xFFFF_FFFF)
+ return data, compare
+
+ def test_year_2038_mtime_compilation(self):
+ # Test to make sure we can handle mtimes larger than what a 32-bit
+ # signed number can hold as part of bpo-34990
+ try:
+ os.utime(self.source_path, (2**32 - 1, 2**32 - 1))
+ except (OverflowError, OSError):
+ self.skipTest("filesystem doesn't support timestamps near 2**32")
+ with contextlib.redirect_stdout(io.StringIO()):
+ self.assertTrue(compileall.compile_file(self.source_path))
+
+ def test_larger_than_32_bit_times(self):
+ # This is similar to the test above but we skip it if the OS doesn't
+ # support modification times larger than 32-bits.
+ try:
+ os.utime(self.source_path, (2**35, 2**35))
+ except (OverflowError, OSError):
+ self.skipTest("filesystem doesn't support large timestamps")
+ with contextlib.redirect_stdout(io.StringIO()):
+ self.assertTrue(compileall.compile_file(self.source_path))
+
+ def recreation_check(self, metadata):
+ """Check that compileall recreates bytecode when the new metadata is
+ used."""
+ if os.environ.get('SOURCE_DATE_EPOCH'):
+ raise unittest.SkipTest('SOURCE_DATE_EPOCH is set')
+ py_compile.compile(self.source_path)
+ self.assertEqual(*self.timestamp_metadata())
+ with open(self.bc_path, 'rb') as file:
+ bc = file.read()[len(metadata):]
+ with open(self.bc_path, 'wb') as file:
+ file.write(metadata)
+ file.write(bc)
+ self.assertNotEqual(*self.timestamp_metadata())
+ compileall.compile_dir(self.directory, force=False, quiet=True)
+ self.assertTrue(*self.timestamp_metadata())
+
+ def test_mtime(self):
+ # Test a change in mtime leads to a new .pyc.
+ self.recreation_check(struct.pack('<4sLL', importlib.util.MAGIC_NUMBER,
+ 0, 1))
+
+ def test_magic_number(self):
+ # Test a change in mtime leads to a new .pyc.
+ self.recreation_check(b'\0\0\0\0')
+
+ def test_compile_files(self):
+ # Test compiling a single file, and complete directory
+ for fn in (self.bc_path, self.bc_path2):
+ try:
+ os.unlink(fn)
+ except:
+ pass
+ self.assertTrue(compileall.compile_file(self.source_path,
+ force=False, quiet=True))
+ self.assertTrue(os.path.isfile(self.bc_path) and
+ not os.path.isfile(self.bc_path2))
+ os.unlink(self.bc_path)
+ self.assertTrue(compileall.compile_dir(self.directory, force=False,
+ quiet=True))
+ self.assertTrue(os.path.isfile(self.bc_path) and
+ os.path.isfile(self.bc_path2))
+ os.unlink(self.bc_path)
+ os.unlink(self.bc_path2)
+ # Test against bad files
+ self.add_bad_source_file()
+ self.assertFalse(compileall.compile_file(self.bad_source_path,
+ force=False, quiet=2))
+ self.assertFalse(compileall.compile_dir(self.directory,
+ force=False, quiet=2))
+
+ def test_compile_file_pathlike(self):
+ self.assertFalse(os.path.isfile(self.bc_path))
+ # we should also test the output
+ with support.captured_stdout() as stdout:
+ self.assertTrue(compileall.compile_file(FakePath(self.source_path)))
+ self.assertRegex(stdout.getvalue(), r'Compiling ([^WindowsPath|PosixPath].*)')
+ self.assertTrue(os.path.isfile(self.bc_path))
+
+ def test_compile_file_pathlike_ddir(self):
+ self.assertFalse(os.path.isfile(self.bc_path))
+ self.assertTrue(compileall.compile_file(FakePath(self.source_path),
+ ddir=FakePath('ddir_path'),
+ quiet=2))
+ self.assertTrue(os.path.isfile(self.bc_path))
+
+ def test_compile_file_pathlike_stripdir(self):
+ self.assertFalse(os.path.isfile(self.bc_path))
+ self.assertTrue(compileall.compile_file(FakePath(self.source_path),
+ stripdir=FakePath('stripdir_path'),
+ quiet=2))
+ self.assertTrue(os.path.isfile(self.bc_path))
+
+ def test_compile_file_pathlike_prependdir(self):
+ self.assertFalse(os.path.isfile(self.bc_path))
+ self.assertTrue(compileall.compile_file(FakePath(self.source_path),
+ prependdir=FakePath('prependdir_path'),
+ quiet=2))
+ self.assertTrue(os.path.isfile(self.bc_path))
+
+ def test_compile_path(self):
+ with test.test_importlib.util.import_state(path=[self.directory]):
+ self.assertTrue(compileall.compile_path(quiet=2))
+
+ with test.test_importlib.util.import_state(path=[self.directory]):
+ self.add_bad_source_file()
+ self.assertFalse(compileall.compile_path(skip_curdir=False,
+ force=True, quiet=2))
+
+ def test_no_pycache_in_non_package(self):
+ # Bug 8563 reported that __pycache__ directories got created by
+ # compile_file() for non-.py files.
+ data_dir = os.path.join(self.directory, 'data')
+ data_file = os.path.join(data_dir, 'file')
+ os.mkdir(data_dir)
+ # touch data/file
+ with open(data_file, 'wb'):
+ pass
+ compileall.compile_file(data_file)
+ self.assertFalse(os.path.exists(os.path.join(data_dir, '__pycache__')))
+
+
+ def test_compile_file_encoding_fallback(self):
+ # Bug 44666 reported that compile_file failed when sys.stdout.encoding is None
+ self.add_bad_source_file()
+ with contextlib.redirect_stdout(io.StringIO()):
+ self.assertFalse(compileall.compile_file(self.bad_source_path))
+
+
+ def test_optimize(self):
+ # make sure compiling with different optimization settings than the
+ # interpreter's creates the correct file names
+ optimize, opt = (1, 1) if __debug__ else (0, '')
+ compileall.compile_dir(self.directory, quiet=True, optimize=optimize)
+ cached = importlib.util.cache_from_source(self.source_path,
+ optimization=opt)
+ self.assertTrue(os.path.isfile(cached))
+ cached2 = importlib.util.cache_from_source(self.source_path2,
+ optimization=opt)
+ self.assertTrue(os.path.isfile(cached2))
+ cached3 = importlib.util.cache_from_source(self.source_path3,
+ optimization=opt)
+ self.assertTrue(os.path.isfile(cached3))
+
+ def test_compile_dir_pathlike(self):
+ self.assertFalse(os.path.isfile(self.bc_path))
+ with support.captured_stdout() as stdout:
+ compileall.compile_dir(FakePath(self.directory))
+ line = stdout.getvalue().splitlines()[0]
+ self.assertRegex(line, r'Listing ([^WindowsPath|PosixPath].*)')
+ self.assertTrue(os.path.isfile(self.bc_path))
+
+ def test_compile_dir_pathlike_stripdir(self):
+ self.assertFalse(os.path.isfile(self.bc_path))
+ self.assertTrue(compileall.compile_dir(FakePath(self.directory),
+ stripdir=FakePath('stripdir_path'),
+ quiet=2))
+ self.assertTrue(os.path.isfile(self.bc_path))
+
+ def test_compile_dir_pathlike_prependdir(self):
+ self.assertFalse(os.path.isfile(self.bc_path))
+ self.assertTrue(compileall.compile_dir(FakePath(self.directory),
+ prependdir=FakePath('prependdir_path'),
+ quiet=2))
+ self.assertTrue(os.path.isfile(self.bc_path))
+
+ @skipUnless(_have_multiprocessing, "requires multiprocessing")
+ @mock.patch('concurrent.futures.ProcessPoolExecutor')
+ def test_compile_pool_called(self, pool_mock):
+ compileall.compile_dir(self.directory, quiet=True, workers=5)
+ self.assertTrue(pool_mock.called)
+
+ def test_compile_workers_non_positive(self):
+ with self.assertRaisesRegex(ValueError,
+ "workers must be greater or equal to 0"):
+ compileall.compile_dir(self.directory, workers=-1)
+
+ @skipUnless(_have_multiprocessing, "requires multiprocessing")
+ @mock.patch('concurrent.futures.ProcessPoolExecutor')
+ def test_compile_workers_cpu_count(self, pool_mock):
+ compileall.compile_dir(self.directory, quiet=True, workers=0)
+ self.assertEqual(pool_mock.call_args[1]['max_workers'], None)
+
+ @skipUnless(_have_multiprocessing, "requires multiprocessing")
+ @mock.patch('concurrent.futures.ProcessPoolExecutor')
+ @mock.patch('compileall.compile_file')
+ def test_compile_one_worker(self, compile_file_mock, pool_mock):
+ compileall.compile_dir(self.directory, quiet=True)
+ self.assertFalse(pool_mock.called)
+ self.assertTrue(compile_file_mock.called)
+
+ @skipUnless(_have_multiprocessing, "requires multiprocessing")
+ @mock.patch('concurrent.futures.ProcessPoolExecutor', new=None)
+ @mock.patch('compileall.compile_file')
+ def test_compile_missing_multiprocessing(self, compile_file_mock):
+ compileall.compile_dir(self.directory, quiet=True, workers=5)
+ self.assertTrue(compile_file_mock.called)
+
+ def test_compile_dir_maxlevels(self):
+ # Test the actual impact of maxlevels parameter
+ depth = 3
+ path = self.directory
+ for i in range(1, depth + 1):
+ path = os.path.join(path, f"dir_{i}")
+ source = os.path.join(path, 'script.py')
+ os.mkdir(path)
+ shutil.copyfile(self.source_path, source)
+ pyc_filename = importlib.util.cache_from_source(source)
+
+ compileall.compile_dir(self.directory, quiet=True, maxlevels=depth - 1)
+ self.assertFalse(os.path.isfile(pyc_filename))
+
+ compileall.compile_dir(self.directory, quiet=True, maxlevels=depth)
+ self.assertTrue(os.path.isfile(pyc_filename))
+
+ def _test_ddir_only(self, *, ddir, parallel=True):
+ """Recursive compile_dir ddir must contain package paths; bpo39769."""
+ fullpath = ["test", "foo"]
+ path = self.directory
+ mods = []
+ for subdir in fullpath:
+ path = os.path.join(path, subdir)
+ os.mkdir(path)
+ script_helper.make_script(path, "__init__", "")
+ mods.append(script_helper.make_script(path, "mod",
+ "def fn(): 1/0\nfn()\n"))
+
+ if parallel:
+ self.addCleanup(multiprocessing_cleanup_tests)
+ compileall.compile_dir(
+ self.directory, quiet=True, ddir=ddir,
+ workers=2 if parallel else 1)
+
+ self.assertTrue(mods)
+ for mod in mods:
+ self.assertTrue(mod.startswith(self.directory), mod)
+ modcode = importlib.util.cache_from_source(mod)
+ modpath = mod[len(self.directory+os.sep):]
+ _, _, err = script_helper.assert_python_failure(modcode)
+ expected_in = os.path.join(ddir, modpath)
+ mod_code_obj = test.test_importlib.util.get_code_from_pyc(modcode)
+ self.assertEqual(mod_code_obj.co_filename, expected_in)
+ self.assertIn(f'"{expected_in}"', os.fsdecode(err))
+
+ # TODO: RUSTPYTHON
+ @unittest.expectedFailure
+ def test_ddir_only_one_worker(self):
+ """Recursive compile_dir ddir= contains package paths; bpo39769."""
+ return self._test_ddir_only(ddir="", parallel=False)
+
+ @skipUnless(_have_multiprocessing, "requires multiprocessing")
+ def test_ddir_multiple_workers(self):
+ """Recursive compile_dir ddir= contains package paths; bpo39769."""
+ return self._test_ddir_only(ddir="", parallel=True)
+
+ # TODO: RUSTPYTHON
+ @unittest.expectedFailure
+ def test_ddir_empty_only_one_worker(self):
+ """Recursive compile_dir ddir='' contains package paths; bpo39769."""
+ return self._test_ddir_only(ddir="", parallel=False)
+
+ @skipUnless(_have_multiprocessing, "requires multiprocessing")
+ def test_ddir_empty_multiple_workers(self):
+ """Recursive compile_dir ddir='' contains package paths; bpo39769."""
+ return self._test_ddir_only(ddir="", parallel=True)
+
+ # TODO: RUSTPYTHON
+ @unittest.expectedFailure
+ def test_strip_only(self):
+ fullpath = ["test", "build", "real", "path"]
+ path = os.path.join(self.directory, *fullpath)
+ os.makedirs(path)
+ script = script_helper.make_script(path, "test", "1 / 0")
+ bc = importlib.util.cache_from_source(script)
+ stripdir = os.path.join(self.directory, *fullpath[:2])
+ compileall.compile_dir(path, quiet=True, stripdir=stripdir)
+ rc, out, err = script_helper.assert_python_failure(bc)
+ expected_in = os.path.join(*fullpath[2:])
+ self.assertIn(
+ expected_in,
+ str(err, encoding=sys.getdefaultencoding())
+ )
+ self.assertNotIn(
+ stripdir,
+ str(err, encoding=sys.getdefaultencoding())
+ )
+
+ def test_strip_only_invalid(self):
+ fullpath = ["test", "build", "real", "path"]
+ path = os.path.join(self.directory, *fullpath)
+ os.makedirs(path)
+ script = script_helper.make_script(path, "test", "1 / 0")
+ bc = importlib.util.cache_from_source(script)
+ stripdir = os.path.join(self.directory, *(fullpath[:2] + ['fake']))
+ with support.captured_stdout() as out:
+ compileall.compile_dir(path, quiet=True, stripdir=stripdir)
+ self.assertIn("not a valid prefix", out.getvalue())
+ rc, out, err = script_helper.assert_python_failure(bc)
+ expected_not_in = os.path.join(self.directory, *fullpath[2:])
+ self.assertIn(
+ path,
+ str(err, encoding=sys.getdefaultencoding())
+ )
+ self.assertNotIn(
+ expected_not_in,
+ str(err, encoding=sys.getdefaultencoding())
+ )
+ self.assertNotIn(
+ stripdir,
+ str(err, encoding=sys.getdefaultencoding())
+ )
+
+ def test_prepend_only(self):
+ fullpath = ["test", "build", "real", "path"]
+ path = os.path.join(self.directory, *fullpath)
+ os.makedirs(path)
+ script = script_helper.make_script(path, "test", "1 / 0")
+ bc = importlib.util.cache_from_source(script)
+ prependdir = "/foo"
+ compileall.compile_dir(path, quiet=True, prependdir=prependdir)
+ rc, out, err = script_helper.assert_python_failure(bc)
+ expected_in = os.path.join(prependdir, self.directory, *fullpath)
+ self.assertIn(
+ expected_in,
+ str(err, encoding=sys.getdefaultencoding())
+ )
+
+ # TODO: RUSTPYTHON
+ @unittest.expectedFailure
+ def test_strip_and_prepend(self):
+ fullpath = ["test", "build", "real", "path"]
+ path = os.path.join(self.directory, *fullpath)
+ os.makedirs(path)
+ script = script_helper.make_script(path, "test", "1 / 0")
+ bc = importlib.util.cache_from_source(script)
+ stripdir = os.path.join(self.directory, *fullpath[:2])
+ prependdir = "/foo"
+ compileall.compile_dir(path, quiet=True,
+ stripdir=stripdir, prependdir=prependdir)
+ rc, out, err = script_helper.assert_python_failure(bc)
+ expected_in = os.path.join(prependdir, *fullpath[2:])
+ self.assertIn(
+ expected_in,
+ str(err, encoding=sys.getdefaultencoding())
+ )
+ self.assertNotIn(
+ stripdir,
+ str(err, encoding=sys.getdefaultencoding())
+ )
+
+ def test_strip_prepend_and_ddir(self):
+ fullpath = ["test", "build", "real", "path", "ddir"]
+ path = os.path.join(self.directory, *fullpath)
+ os.makedirs(path)
+ script_helper.make_script(path, "test", "1 / 0")
+ with self.assertRaises(ValueError):
+ compileall.compile_dir(path, quiet=True, ddir="/bar",
+ stripdir="/foo", prependdir="/bar")
+
+ def test_multiple_optimization_levels(self):
+ script = script_helper.make_script(self.directory,
+ "test_optimization",
+ "a = 0")
+ bc = []
+ for opt_level in "", 1, 2, 3:
+ bc.append(importlib.util.cache_from_source(script,
+ optimization=opt_level))
+ test_combinations = [[0, 1], [1, 2], [0, 2], [0, 1, 2]]
+ for opt_combination in test_combinations:
+ compileall.compile_file(script, quiet=True,
+ optimize=opt_combination)
+ for opt_level in opt_combination:
+ self.assertTrue(os.path.isfile(bc[opt_level]))
+ try:
+ os.unlink(bc[opt_level])
+ except Exception:
+ pass
+
+ @unittest.expectedFailureIfWindows("TODO: RUSTPYTHON")
+ @os_helper.skip_unless_symlink
+ def test_ignore_symlink_destination(self):
+ # Create folders for allowed files, symlinks and prohibited area
+ allowed_path = os.path.join(self.directory, "test", "dir", "allowed")
+ symlinks_path = os.path.join(self.directory, "test", "dir", "symlinks")
+ prohibited_path = os.path.join(self.directory, "test", "dir", "prohibited")
+ os.makedirs(allowed_path)
+ os.makedirs(symlinks_path)
+ os.makedirs(prohibited_path)
+
+ # Create scripts and symlinks and remember their byte-compiled versions
+ allowed_script = script_helper.make_script(allowed_path, "test_allowed", "a = 0")
+ prohibited_script = script_helper.make_script(prohibited_path, "test_prohibited", "a = 0")
+ allowed_symlink = os.path.join(symlinks_path, "test_allowed.py")
+ prohibited_symlink = os.path.join(symlinks_path, "test_prohibited.py")
+ os.symlink(allowed_script, allowed_symlink)
+ os.symlink(prohibited_script, prohibited_symlink)
+ allowed_bc = importlib.util.cache_from_source(allowed_symlink)
+ prohibited_bc = importlib.util.cache_from_source(prohibited_symlink)
+
+ compileall.compile_dir(symlinks_path, quiet=True, limit_sl_dest=allowed_path)
+
+ self.assertTrue(os.path.isfile(allowed_bc))
+ self.assertFalse(os.path.isfile(prohibited_bc))
+
+
+class CompileallTestsWithSourceEpoch(CompileallTestsBase,
+ unittest.TestCase,
+ metaclass=SourceDateEpochTestMeta,
+ source_date_epoch=True):
+ pass
+
+
+class CompileallTestsWithoutSourceEpoch(CompileallTestsBase,
+ unittest.TestCase,
+ metaclass=SourceDateEpochTestMeta,
+ source_date_epoch=False):
+ pass
+
+
+# WASI does not have a temp directory and uses cwd instead. The cwd contains
+# non-ASCII chars, so _walk_dir() fails to encode self.directory.
+@unittest.skipIf(support.is_wasi, "tempdir is not encodable on WASI")
+class EncodingTest(unittest.TestCase):
+ """Issue 6716: compileall should escape source code when printing errors
+ to stdout."""
+
+ def setUp(self):
+ self.directory = tempfile.mkdtemp()
+ self.source_path = os.path.join(self.directory, '_test.py')
+ with open(self.source_path, 'w', encoding='utf-8') as file:
+ # Intentional syntax error: bytes can only contain
+ # ASCII literal characters.
+ file.write('b"\u20ac"')
+
+ def tearDown(self):
+ shutil.rmtree(self.directory)
+
+ def test_error(self):
+ buffer = io.TextIOWrapper(io.BytesIO(), encoding='ascii')
+ with contextlib.redirect_stdout(buffer):
+ compiled = compileall.compile_dir(self.directory)
+ self.assertFalse(compiled) # should not be successful
+ buffer.seek(0)
+ res = buffer.read()
+ self.assertIn(
+ 'SyntaxError: bytes can only contain ASCII literal characters',
+ res,
+ )
+ self.assertNotIn('UnicodeEncodeError', res)
+
+
+class CommandLineTestsBase:
+ """Test compileall's CLI."""
+
+ def setUp(self):
+ self.directory = tempfile.mkdtemp()
+ self.addCleanup(os_helper.rmtree, self.directory)
+ self.pkgdir = os.path.join(self.directory, 'foo')
+ os.mkdir(self.pkgdir)
+ self.pkgdir_cachedir = os.path.join(self.pkgdir, '__pycache__')
+ # Create the __init__.py and a package module.
+ self.initfn = script_helper.make_script(self.pkgdir, '__init__', '')
+ self.barfn = script_helper.make_script(self.pkgdir, 'bar', '')
+
+ @contextlib.contextmanager
+ def temporary_pycache_prefix(self):
+ """Adjust and restore sys.pycache_prefix."""
+ old_prefix = sys.pycache_prefix
+ new_prefix = os.path.join(self.directory, '__testcache__')
+ try:
+ sys.pycache_prefix = new_prefix
+ yield {
+ 'PYTHONPATH': self.directory,
+ 'PYTHONPYCACHEPREFIX': new_prefix,
+ }
+ finally:
+ sys.pycache_prefix = old_prefix
+
+ def _get_run_args(self, args):
+ return [*support.optim_args_from_interpreter_flags(),
+ '-S', '-m', 'compileall',
+ *args]
+
+ def assertRunOK(self, *args, **env_vars):
+ rc, out, err = script_helper.assert_python_ok(
+ *self._get_run_args(args), **env_vars,
+ PYTHONIOENCODING='utf-8')
+ self.assertEqual(b'', err)
+ return out
+
+ def assertRunNotOK(self, *args, **env_vars):
+ rc, out, err = script_helper.assert_python_failure(
+ *self._get_run_args(args), **env_vars,
+ PYTHONIOENCODING='utf-8')
+ return rc, out, err
+
+ def assertCompiled(self, fn):
+ path = importlib.util.cache_from_source(fn)
+ self.assertTrue(os.path.exists(path))
+
+ def assertNotCompiled(self, fn):
+ path = importlib.util.cache_from_source(fn)
+ self.assertFalse(os.path.exists(path))
+
+ # TODO: RUSTPYTHON
+ @unittest.expectedFailure
+ def test_no_args_compiles_path(self):
+ # Note that -l is implied for the no args case.
+ bazfn = script_helper.make_script(self.directory, 'baz', '')
+ with self.temporary_pycache_prefix() as env:
+ self.assertRunOK(**env)
+ self.assertCompiled(bazfn)
+ self.assertNotCompiled(self.initfn)
+ self.assertNotCompiled(self.barfn)
+
+ # TODO: RUSTPYTHON
+ @unittest.expectedFailure
+ @without_source_date_epoch # timestamp invalidation test
+ @support.requires_resource('cpu')
+ def test_no_args_respects_force_flag(self):
+ bazfn = script_helper.make_script(self.directory, 'baz', '')
+ with self.temporary_pycache_prefix() as env:
+ self.assertRunOK(**env)
+ pycpath = importlib.util.cache_from_source(bazfn)
+ # Set atime/mtime backward to avoid file timestamp resolution issues
+ os.utime(pycpath, (time.time()-60,)*2)
+ mtime = os.stat(pycpath).st_mtime
+ # Without force, no recompilation
+ self.assertRunOK(**env)
+ mtime2 = os.stat(pycpath).st_mtime
+ self.assertEqual(mtime, mtime2)
+ # Now force it.
+ self.assertRunOK('-f', **env)
+ mtime2 = os.stat(pycpath).st_mtime
+ self.assertNotEqual(mtime, mtime2)
+
+ @support.requires_resource('cpu')
+ def test_no_args_respects_quiet_flag(self):
+ script_helper.make_script(self.directory, 'baz', '')
+ with self.temporary_pycache_prefix() as env:
+ noisy = self.assertRunOK(**env)
+ self.assertIn(b'Listing ', noisy)
+ quiet = self.assertRunOK('-q', **env)
+ self.assertNotIn(b'Listing ', quiet)
+
+ # Ensure that the default behavior of compileall's CLI is to create
+ # PEP 3147/PEP 488 pyc files.
+ for name, ext, switch in [
+ ('normal', 'pyc', []),
+ ('optimize', 'opt-1.pyc', ['-O']),
+ ('doubleoptimize', 'opt-2.pyc', ['-OO']),
+ ]:
+ def f(self, ext=ext, switch=switch):
+ script_helper.assert_python_ok(*(switch +
+ ['-m', 'compileall', '-q', self.pkgdir]))
+ # Verify the __pycache__ directory contents.
+ self.assertTrue(os.path.exists(self.pkgdir_cachedir))
+ expected = sorted(base.format(sys.implementation.cache_tag, ext)
+ for base in ('__init__.{}.{}', 'bar.{}.{}'))
+ self.assertEqual(sorted(os.listdir(self.pkgdir_cachedir)), expected)
+ # Make sure there are no .pyc files in the source directory.
+ self.assertFalse([fn for fn in os.listdir(self.pkgdir)
+ if fn.endswith(ext)])
+ locals()['test_pep3147_paths_' + name] = f
+
+ def test_legacy_paths(self):
+ # Ensure that with the proper switch, compileall leaves legacy
+ # pyc files, and no __pycache__ directory.
+ self.assertRunOK('-b', '-q', self.pkgdir)
+ # Verify the __pycache__ directory contents.
+ self.assertFalse(os.path.exists(self.pkgdir_cachedir))
+ expected = sorted(['__init__.py', '__init__.pyc', 'bar.py',
+ 'bar.pyc'])
+ self.assertEqual(sorted(os.listdir(self.pkgdir)), expected)
+
+ def test_multiple_runs(self):
+ # Bug 8527 reported that multiple calls produced empty
+ # __pycache__/__pycache__ directories.
+ self.assertRunOK('-q', self.pkgdir)
+ # Verify the __pycache__ directory contents.
+ self.assertTrue(os.path.exists(self.pkgdir_cachedir))
+ cachecachedir = os.path.join(self.pkgdir_cachedir, '__pycache__')
+ self.assertFalse(os.path.exists(cachecachedir))
+ # Call compileall again.
+ self.assertRunOK('-q', self.pkgdir)
+ self.assertTrue(os.path.exists(self.pkgdir_cachedir))
+ self.assertFalse(os.path.exists(cachecachedir))
+
+ @without_source_date_epoch # timestamp invalidation test
+ def test_force(self):
+ self.assertRunOK('-q', self.pkgdir)
+ pycpath = importlib.util.cache_from_source(self.barfn)
+ # set atime/mtime backward to avoid file timestamp resolution issues
+ os.utime(pycpath, (time.time()-60,)*2)
+ mtime = os.stat(pycpath).st_mtime
+ # without force, no recompilation
+ self.assertRunOK('-q', self.pkgdir)
+ mtime2 = os.stat(pycpath).st_mtime
+ self.assertEqual(mtime, mtime2)
+ # now force it.
+ self.assertRunOK('-q', '-f', self.pkgdir)
+ mtime2 = os.stat(pycpath).st_mtime
+ self.assertNotEqual(mtime, mtime2)
+
+ def test_recursion_control(self):
+ subpackage = os.path.join(self.pkgdir, 'spam')
+ os.mkdir(subpackage)
+ subinitfn = script_helper.make_script(subpackage, '__init__', '')
+ hamfn = script_helper.make_script(subpackage, 'ham', '')
+ self.assertRunOK('-q', '-l', self.pkgdir)
+ self.assertNotCompiled(subinitfn)
+ self.assertFalse(os.path.exists(os.path.join(subpackage, '__pycache__')))
+ self.assertRunOK('-q', self.pkgdir)
+ self.assertCompiled(subinitfn)
+ self.assertCompiled(hamfn)
+
+ def test_recursion_limit(self):
+ subpackage = os.path.join(self.pkgdir, 'spam')
+ subpackage2 = os.path.join(subpackage, 'ham')
+ subpackage3 = os.path.join(subpackage2, 'eggs')
+ for pkg in (subpackage, subpackage2, subpackage3):
+ script_helper.make_pkg(pkg)
+
+ subinitfn = os.path.join(subpackage, '__init__.py')
+ hamfn = script_helper.make_script(subpackage, 'ham', '')
+ spamfn = script_helper.make_script(subpackage2, 'spam', '')
+ eggfn = script_helper.make_script(subpackage3, 'egg', '')
+
+ self.assertRunOK('-q', '-r 0', self.pkgdir)
+ self.assertNotCompiled(subinitfn)
+ self.assertFalse(
+ os.path.exists(os.path.join(subpackage, '__pycache__')))
+
+ self.assertRunOK('-q', '-r 1', self.pkgdir)
+ self.assertCompiled(subinitfn)
+ self.assertCompiled(hamfn)
+ self.assertNotCompiled(spamfn)
+
+ self.assertRunOK('-q', '-r 2', self.pkgdir)
+ self.assertCompiled(subinitfn)
+ self.assertCompiled(hamfn)
+ self.assertCompiled(spamfn)
+ self.assertNotCompiled(eggfn)
+
+ self.assertRunOK('-q', '-r 5', self.pkgdir)
+ self.assertCompiled(subinitfn)
+ self.assertCompiled(hamfn)
+ self.assertCompiled(spamfn)
+ self.assertCompiled(eggfn)
+
+ @unittest.skipIf(sys.platform == 'win32', 'TODO: RUSTPYTHON hangs')
+ @os_helper.skip_unless_symlink
+ def test_symlink_loop(self):
+ # Currently, compileall ignores symlinks to directories.
+ # If that limitation is ever lifted, it should protect against
+ # recursion in symlink loops.
+ pkg = os.path.join(self.pkgdir, 'spam')
+ script_helper.make_pkg(pkg)
+ os.symlink('.', os.path.join(pkg, 'evil'))
+ os.symlink('.', os.path.join(pkg, 'evil2'))
+ self.assertRunOK('-q', self.pkgdir)
+ self.assertCompiled(os.path.join(
+ self.pkgdir, 'spam', 'evil', 'evil2', '__init__.py'
+ ))
+
+ def test_quiet(self):
+ noisy = self.assertRunOK(self.pkgdir)
+ quiet = self.assertRunOK('-q', self.pkgdir)
+ self.assertNotEqual(b'', noisy)
+ self.assertEqual(b'', quiet)
+
+ def test_silent(self):
+ script_helper.make_script(self.pkgdir, 'crunchyfrog', 'bad(syntax')
+ _, quiet, _ = self.assertRunNotOK('-q', self.pkgdir)
+ _, silent, _ = self.assertRunNotOK('-qq', self.pkgdir)
+ self.assertNotEqual(b'', quiet)
+ self.assertEqual(b'', silent)
+
+ def test_regexp(self):
+ self.assertRunOK('-q', '-x', r'ba[^\\/]*$', self.pkgdir)
+ self.assertNotCompiled(self.barfn)
+ self.assertCompiled(self.initfn)
+
+ def test_multiple_dirs(self):
+ pkgdir2 = os.path.join(self.directory, 'foo2')
+ os.mkdir(pkgdir2)
+ init2fn = script_helper.make_script(pkgdir2, '__init__', '')
+ bar2fn = script_helper.make_script(pkgdir2, 'bar2', '')
+ self.assertRunOK('-q', self.pkgdir, pkgdir2)
+ self.assertCompiled(self.initfn)
+ self.assertCompiled(self.barfn)
+ self.assertCompiled(init2fn)
+ self.assertCompiled(bar2fn)
+
+ def test_d_compile_error(self):
+ script_helper.make_script(self.pkgdir, 'crunchyfrog', 'bad(syntax')
+ rc, out, err = self.assertRunNotOK('-q', '-d', 'dinsdale', self.pkgdir)
+ self.assertRegex(out, b'File "dinsdale')
+
+ @support.force_not_colorized
+ def test_d_runtime_error(self):
+ bazfn = script_helper.make_script(self.pkgdir, 'baz', 'raise Exception')
+ self.assertRunOK('-q', '-d', 'dinsdale', self.pkgdir)
+ fn = script_helper.make_script(self.pkgdir, 'bing', 'import baz')
+ pyc = importlib.util.cache_from_source(bazfn)
+ os.rename(pyc, os.path.join(self.pkgdir, 'baz.pyc'))
+ os.remove(bazfn)
+ rc, out, err = script_helper.assert_python_failure(fn, __isolated=False)
+ self.assertRegex(err, b'File "dinsdale')
+
+ def test_include_bad_file(self):
+ rc, out, err = self.assertRunNotOK(
+ '-i', os.path.join(self.directory, 'nosuchfile'), self.pkgdir)
+ self.assertRegex(out, b'rror.*nosuchfile')
+ self.assertNotRegex(err, b'Traceback')
+ self.assertFalse(os.path.exists(importlib.util.cache_from_source(
+ self.pkgdir_cachedir)))
+
+ def test_include_file_with_arg(self):
+ f1 = script_helper.make_script(self.pkgdir, 'f1', '')
+ f2 = script_helper.make_script(self.pkgdir, 'f2', '')
+ f3 = script_helper.make_script(self.pkgdir, 'f3', '')
+ f4 = script_helper.make_script(self.pkgdir, 'f4', '')
+ with open(os.path.join(self.directory, 'l1'), 'w', encoding="utf-8") as l1:
+ l1.write(os.path.join(self.pkgdir, 'f1.py')+os.linesep)
+ l1.write(os.path.join(self.pkgdir, 'f2.py')+os.linesep)
+ self.assertRunOK('-i', os.path.join(self.directory, 'l1'), f4)
+ self.assertCompiled(f1)
+ self.assertCompiled(f2)
+ self.assertNotCompiled(f3)
+ self.assertCompiled(f4)
+
+ def test_include_file_no_arg(self):
+ f1 = script_helper.make_script(self.pkgdir, 'f1', '')
+ f2 = script_helper.make_script(self.pkgdir, 'f2', '')
+ f3 = script_helper.make_script(self.pkgdir, 'f3', '')
+ f4 = script_helper.make_script(self.pkgdir, 'f4', '')
+ with open(os.path.join(self.directory, 'l1'), 'w', encoding="utf-8") as l1:
+ l1.write(os.path.join(self.pkgdir, 'f2.py')+os.linesep)
+ self.assertRunOK('-i', os.path.join(self.directory, 'l1'))
+ self.assertNotCompiled(f1)
+ self.assertCompiled(f2)
+ self.assertNotCompiled(f3)
+ self.assertNotCompiled(f4)
+
+ def test_include_on_stdin(self):
+ f1 = script_helper.make_script(self.pkgdir, 'f1', '')
+ f2 = script_helper.make_script(self.pkgdir, 'f2', '')
+ f3 = script_helper.make_script(self.pkgdir, 'f3', '')
+ f4 = script_helper.make_script(self.pkgdir, 'f4', '')
+ p = script_helper.spawn_python(*(self._get_run_args(()) + ['-i', '-']))
+ p.stdin.write((f3+os.linesep).encode('ascii'))
+ script_helper.kill_python(p)
+ self.assertNotCompiled(f1)
+ self.assertNotCompiled(f2)
+ self.assertCompiled(f3)
+ self.assertNotCompiled(f4)
+
+ def test_compiles_as_much_as_possible(self):
+ bingfn = script_helper.make_script(self.pkgdir, 'bing', 'syntax(error')
+ rc, out, err = self.assertRunNotOK('nosuchfile', self.initfn,
+ bingfn, self.barfn)
+ self.assertRegex(out, b'rror')
+ self.assertNotCompiled(bingfn)
+ self.assertCompiled(self.initfn)
+ self.assertCompiled(self.barfn)
+
+ def test_invalid_arg_produces_message(self):
+ out = self.assertRunOK('badfilename')
+ self.assertRegex(out, b"Can't list 'badfilename'")
+
+ def test_pyc_invalidation_mode(self):
+ script_helper.make_script(self.pkgdir, 'f1', '')
+ pyc = importlib.util.cache_from_source(
+ os.path.join(self.pkgdir, 'f1.py'))
+ self.assertRunOK('--invalidation-mode=checked-hash', self.pkgdir)
+ with open(pyc, 'rb') as fp:
+ data = fp.read()
+ self.assertEqual(int.from_bytes(data[4:8], 'little'), 0b11)
+ self.assertRunOK('--invalidation-mode=unchecked-hash', self.pkgdir)
+ with open(pyc, 'rb') as fp:
+ data = fp.read()
+ self.assertEqual(int.from_bytes(data[4:8], 'little'), 0b01)
+
+ @skipUnless(_have_multiprocessing, "requires multiprocessing")
+ def test_workers(self):
+ bar2fn = script_helper.make_script(self.directory, 'bar2', '')
+ files = []
+ for suffix in range(5):
+ pkgdir = os.path.join(self.directory, 'foo{}'.format(suffix))
+ os.mkdir(pkgdir)
+ fn = script_helper.make_script(pkgdir, '__init__', '')
+ files.append(script_helper.make_script(pkgdir, 'bar2', ''))
+
+ self.assertRunOK(self.directory, '-j', '0')
+ self.assertCompiled(bar2fn)
+ for file in files:
+ self.assertCompiled(file)
+
+ @mock.patch('compileall.compile_dir')
+ def test_workers_available_cores(self, compile_dir):
+ with mock.patch("sys.argv",
+ new=[sys.executable, self.directory, "-j0"]):
+ compileall.main()
+ self.assertTrue(compile_dir.called)
+ self.assertEqual(compile_dir.call_args[-1]['workers'], 0)
+
+ # TODO: RUSTPYTHON
+ @unittest.expectedFailure
+ def test_strip_and_prepend(self):
+ fullpath = ["test", "build", "real", "path"]
+ path = os.path.join(self.directory, *fullpath)
+ os.makedirs(path)
+ script = script_helper.make_script(path, "test", "1 / 0")
+ bc = importlib.util.cache_from_source(script)
+ stripdir = os.path.join(self.directory, *fullpath[:2])
+ prependdir = "/foo"
+ self.assertRunOK("-s", stripdir, "-p", prependdir, path)
+ rc, out, err = script_helper.assert_python_failure(bc)
+ expected_in = os.path.join(prependdir, *fullpath[2:])
+ self.assertIn(
+ expected_in,
+ str(err, encoding=sys.getdefaultencoding())
+ )
+ self.assertNotIn(
+ stripdir,
+ str(err, encoding=sys.getdefaultencoding())
+ )
+
+ def test_multiple_optimization_levels(self):
+ path = os.path.join(self.directory, "optimizations")
+ os.makedirs(path)
+ script = script_helper.make_script(path,
+ "test_optimization",
+ "a = 0")
+ bc = []
+ for opt_level in "", 1, 2, 3:
+ bc.append(importlib.util.cache_from_source(script,
+ optimization=opt_level))
+ test_combinations = [["0", "1"],
+ ["1", "2"],
+ ["0", "2"],
+ ["0", "1", "2"]]
+ for opt_combination in test_combinations:
+ self.assertRunOK(path, *("-o" + str(n) for n in opt_combination))
+ for opt_level in opt_combination:
+ self.assertTrue(os.path.isfile(bc[int(opt_level)]))
+ try:
+ os.unlink(bc[opt_level])
+ except Exception:
+ pass
+
+ @unittest.expectedFailureIfWindows("TODO: RUSTPYTHON")
+ @os_helper.skip_unless_symlink
+ def test_ignore_symlink_destination(self):
+ # Create folders for allowed files, symlinks and prohibited area
+ allowed_path = os.path.join(self.directory, "test", "dir", "allowed")
+ symlinks_path = os.path.join(self.directory, "test", "dir", "symlinks")
+ prohibited_path = os.path.join(self.directory, "test", "dir", "prohibited")
+ os.makedirs(allowed_path)
+ os.makedirs(symlinks_path)
+ os.makedirs(prohibited_path)
+
+ # Create scripts and symlinks and remember their byte-compiled versions
+ allowed_script = script_helper.make_script(allowed_path, "test_allowed", "a = 0")
+ prohibited_script = script_helper.make_script(prohibited_path, "test_prohibited", "a = 0")
+ allowed_symlink = os.path.join(symlinks_path, "test_allowed.py")
+ prohibited_symlink = os.path.join(symlinks_path, "test_prohibited.py")
+ os.symlink(allowed_script, allowed_symlink)
+ os.symlink(prohibited_script, prohibited_symlink)
+ allowed_bc = importlib.util.cache_from_source(allowed_symlink)
+ prohibited_bc = importlib.util.cache_from_source(prohibited_symlink)
+
+ self.assertRunOK(symlinks_path, "-e", allowed_path)
+
+ self.assertTrue(os.path.isfile(allowed_bc))
+ self.assertFalse(os.path.isfile(prohibited_bc))
+
+ def test_hardlink_bad_args(self):
+ # Bad arguments combination, hardlink deduplication make sense
+ # only for more than one optimization level
+ self.assertRunNotOK(self.directory, "-o 1", "--hardlink-dupes")
+
+ def test_hardlink(self):
+ # 'a = 0' code produces the same bytecode for the 3 optimization
+ # levels. All three .pyc files must have the same inode (hardlinks).
+ #
+ # If deduplication is disabled, all pyc files must have different
+ # inodes.
+ for dedup in (True, False):
+ with tempfile.TemporaryDirectory() as path:
+ with self.subTest(dedup=dedup):
+ script = script_helper.make_script(path, "script", "a = 0")
+ pycs = get_pycs(script)
+
+ args = ["-q", "-o 0", "-o 1", "-o 2"]
+ if dedup:
+ args.append("--hardlink-dupes")
+ self.assertRunOK(path, *args)
+
+ self.assertEqual(is_hardlink(pycs[0], pycs[1]), dedup)
+ self.assertEqual(is_hardlink(pycs[1], pycs[2]), dedup)
+ self.assertEqual(is_hardlink(pycs[0], pycs[2]), dedup)
+
+
+class CommandLineTestsWithSourceEpoch(CommandLineTestsBase,
+ unittest.TestCase,
+ metaclass=SourceDateEpochTestMeta,
+ source_date_epoch=True):
+ pass
+
+
+class CommandLineTestsNoSourceEpoch(CommandLineTestsBase,
+ unittest.TestCase,
+ metaclass=SourceDateEpochTestMeta,
+ source_date_epoch=False):
+ pass
+
+
+
+@os_helper.skip_unless_hardlink
+class HardlinkDedupTestsBase:
+ # Test hardlink_dupes parameter of compileall.compile_dir()
+
+ def setUp(self):
+ self.path = None
+
+ @contextlib.contextmanager
+ def temporary_directory(self):
+ with tempfile.TemporaryDirectory() as path:
+ self.path = path
+ yield path
+ self.path = None
+
+ def make_script(self, code, name="script"):
+ return script_helper.make_script(self.path, name, code)
+
+ def compile_dir(self, *, dedup=True, optimize=(0, 1, 2), force=False):
+ compileall.compile_dir(self.path, quiet=True, optimize=optimize,
+ hardlink_dupes=dedup, force=force)
+
+ def test_bad_args(self):
+ # Bad arguments combination, hardlink deduplication make sense
+ # only for more than one optimization level
+ with self.temporary_directory():
+ self.make_script("pass")
+ with self.assertRaises(ValueError):
+ compileall.compile_dir(self.path, quiet=True, optimize=0,
+ hardlink_dupes=True)
+ with self.assertRaises(ValueError):
+ # same optimization level specified twice:
+ # compile_dir() removes duplicates
+ compileall.compile_dir(self.path, quiet=True, optimize=[0, 0],
+ hardlink_dupes=True)
+
+ def create_code(self, docstring=False, assertion=False):
+ lines = []
+ if docstring:
+ lines.append("'module docstring'")
+ lines.append('x = 1')
+ if assertion:
+ lines.append("assert x == 1")
+ return '\n'.join(lines)
+
+ def iter_codes(self):
+ for docstring in (False, True):
+ for assertion in (False, True):
+ code = self.create_code(docstring=docstring, assertion=assertion)
+ yield (code, docstring, assertion)
+
+ def test_disabled(self):
+ # Deduplication disabled, no hardlinks
+ for code, docstring, assertion in self.iter_codes():
+ with self.subTest(docstring=docstring, assertion=assertion):
+ with self.temporary_directory():
+ script = self.make_script(code)
+ pycs = get_pycs(script)
+ self.compile_dir(dedup=False)
+ self.assertFalse(is_hardlink(pycs[0], pycs[1]))
+ self.assertFalse(is_hardlink(pycs[0], pycs[2]))
+ self.assertFalse(is_hardlink(pycs[1], pycs[2]))
+
+ def check_hardlinks(self, script, docstring=False, assertion=False):
+ pycs = get_pycs(script)
+ self.assertEqual(is_hardlink(pycs[0], pycs[1]),
+ not assertion)
+ self.assertEqual(is_hardlink(pycs[0], pycs[2]),
+ not assertion and not docstring)
+ self.assertEqual(is_hardlink(pycs[1], pycs[2]),
+ not docstring)
+
+ def test_hardlink(self):
+ # Test deduplication on all combinations
+ for code, docstring, assertion in self.iter_codes():
+ with self.subTest(docstring=docstring, assertion=assertion):
+ with self.temporary_directory():
+ script = self.make_script(code)
+ self.compile_dir()
+ self.check_hardlinks(script, docstring, assertion)
+
+ def test_only_two_levels(self):
+ # Don't build the 3 optimization levels, but only 2
+ for opts in ((0, 1), (1, 2), (0, 2)):
+ with self.subTest(opts=opts):
+ with self.temporary_directory():
+ # code with no dostring and no assertion:
+ # same bytecode for all optimization levels
+ script = self.make_script(self.create_code())
+ self.compile_dir(optimize=opts)
+ pyc1 = get_pyc(script, opts[0])
+ pyc2 = get_pyc(script, opts[1])
+ self.assertTrue(is_hardlink(pyc1, pyc2))
+
+ def test_duplicated_levels(self):
+ # compile_dir() must not fail if optimize contains duplicated
+ # optimization levels and/or if optimization levels are not sorted.
+ with self.temporary_directory():
+ # code with no dostring and no assertion:
+ # same bytecode for all optimization levels
+ script = self.make_script(self.create_code())
+ self.compile_dir(optimize=[1, 0, 1, 0])
+ pyc1 = get_pyc(script, 0)
+ pyc2 = get_pyc(script, 1)
+ self.assertTrue(is_hardlink(pyc1, pyc2))
+
+ def test_recompilation(self):
+ # Test compile_dir() when pyc files already exists and the script
+ # content changed
+ with self.temporary_directory():
+ script = self.make_script("a = 0")
+ self.compile_dir()
+ # All three levels have the same inode
+ self.check_hardlinks(script)
+
+ pycs = get_pycs(script)
+ inode = os.stat(pycs[0]).st_ino
+
+ # Change of the module content
+ script = self.make_script("print(0)")
+
+ # Recompilation without -o 1
+ self.compile_dir(optimize=[0, 2], force=True)
+
+ # opt-1.pyc should have the same inode as before and others should not
+ self.assertEqual(inode, os.stat(pycs[1]).st_ino)
+ self.assertTrue(is_hardlink(pycs[0], pycs[2]))
+ self.assertNotEqual(inode, os.stat(pycs[2]).st_ino)
+ # opt-1.pyc and opt-2.pyc have different content
+ self.assertFalse(filecmp.cmp(pycs[1], pycs[2], shallow=True))
+
+ def test_import(self):
+ # Test that import updates a single pyc file when pyc files already
+ # exists and the script content changed
+ with self.temporary_directory():
+ script = self.make_script(self.create_code(), name="module")
+ self.compile_dir()
+ # All three levels have the same inode
+ self.check_hardlinks(script)
+
+ pycs = get_pycs(script)
+ inode = os.stat(pycs[0]).st_ino
+
+ # Change of the module content
+ script = self.make_script("print(0)", name="module")
+
+ # Import the module in Python with -O (optimization level 1)
+ script_helper.assert_python_ok(
+ "-O", "-c", "import module", __isolated=False, PYTHONPATH=self.path
+ )
+
+ # Only opt-1.pyc is changed
+ self.assertEqual(inode, os.stat(pycs[0]).st_ino)
+ self.assertEqual(inode, os.stat(pycs[2]).st_ino)
+ self.assertFalse(is_hardlink(pycs[1], pycs[2]))
+ # opt-1.pyc and opt-2.pyc have different content
+ self.assertFalse(filecmp.cmp(pycs[1], pycs[2], shallow=True))
+
+
+class HardlinkDedupTestsWithSourceEpoch(HardlinkDedupTestsBase,
+ unittest.TestCase,
+ metaclass=SourceDateEpochTestMeta,
+ source_date_epoch=True):
+ pass
+
+
+class HardlinkDedupTestsNoSourceEpoch(HardlinkDedupTestsBase,
+ unittest.TestCase,
+ metaclass=SourceDateEpochTestMeta,
+ source_date_epoch=False):
+ pass
+
+
+if __name__ == "__main__":
+ unittest.main()
diff --git a/Lib/test/test_complex.py b/Lib/test/test_complex.py
index 106182cab1..86d075de8c 100644
--- a/Lib/test/test_complex.py
+++ b/Lib/test/test_complex.py
@@ -1,15 +1,19 @@
import unittest
import sys
from test import support
-from test.test_grammar import (VALID_UNDERSCORE_LITERALS,
- INVALID_UNDERSCORE_LITERALS)
+from test.support.testcase import ComplexesAreIdenticalMixin
+from test.support.numbers import (
+ VALID_UNDERSCORE_LITERALS,
+ INVALID_UNDERSCORE_LITERALS,
+)
from random import random
-from math import atan2, isnan, copysign
+from math import isnan, copysign
import operator
INF = float("inf")
NAN = float("nan")
+DBL_MAX = sys.float_info.max
# These tests ensure that complex math does the right thing
ZERO_DIVISION = (
@@ -20,7 +24,28 @@
(1, 0+0j),
)
-class ComplexTest(unittest.TestCase):
+class WithIndex:
+ def __init__(self, value):
+ self.value = value
+ def __index__(self):
+ return self.value
+
+class WithFloat:
+ def __init__(self, value):
+ self.value = value
+ def __float__(self):
+ return self.value
+
+class ComplexSubclass(complex):
+ pass
+
+class WithComplex:
+ def __init__(self, value):
+ self.value = value
+ def __complex__(self):
+ return self.value
+
+class ComplexTest(ComplexesAreIdenticalMixin, unittest.TestCase):
def assertAlmostEqual(self, a, b):
if isinstance(a, complex):
@@ -49,29 +74,6 @@ def assertCloseAbs(self, x, y, eps=1e-9):
# check that relative difference < eps
self.assertTrue(abs((x-y)/y) < eps)
- def assertFloatsAreIdentical(self, x, y):
- """assert that floats x and y are identical, in the sense that:
- (1) both x and y are nans, or
- (2) both x and y are infinities, with the same sign, or
- (3) both x and y are zeros, with the same sign, or
- (4) x and y are both finite and nonzero, and x == y
-
- """
- msg = 'floats {!r} and {!r} are not identical'
-
- if isnan(x) or isnan(y):
- if isnan(x) and isnan(y):
- return
- elif x == y:
- if x != 0.0:
- return
- # both zero; check that signs match
- elif copysign(1.0, x) == copysign(1.0, y):
- return
- else:
- msg += ': zeros have different signs'
- self.fail(msg.format(x, y))
-
def assertClose(self, x, y, eps=1e-9):
"""Return true iff complexes x and y "are close"."""
self.assertCloseAbs(x.real, y.real, eps)
@@ -303,6 +305,11 @@ def test_pow(self):
except OverflowError:
pass
+ # gh-113841: possible undefined division by 0 in _Py_c_pow()
+ x, y = 9j, 33j**3
+ with self.assertRaises(OverflowError):
+ x**y
+
def test_pow_with_small_integer_exponents(self):
# Check that small integer exponents are handled identically
# regardless of their type.
@@ -340,138 +347,93 @@ def test_boolcontext(self):
def test_conjugate(self):
self.assertClose(complex(5.3, 9.8).conjugate(), 5.3-9.8j)
+ # TODO: RUSTPYTHON
+ @unittest.expectedFailure
def test_constructor(self):
- class NS:
- def __init__(self, value): self.value = value
- def __complex__(self): return self.value
- self.assertEqual(complex(NS(1+10j)), 1+10j)
- self.assertRaises(TypeError, complex, NS(None))
- self.assertRaises(TypeError, complex, {})
- self.assertRaises(TypeError, complex, NS(1.5))
- self.assertRaises(TypeError, complex, NS(1))
- self.assertRaises(TypeError, complex, object())
- self.assertRaises(TypeError, complex, NS(4.25+0.5j), object())
-
- self.assertAlmostEqual(complex("1+10j"), 1+10j)
- self.assertAlmostEqual(complex(10), 10+0j)
- self.assertAlmostEqual(complex(10.0), 10+0j)
- self.assertAlmostEqual(complex(10), 10+0j)
- self.assertAlmostEqual(complex(10+0j), 10+0j)
- self.assertAlmostEqual(complex(1,10), 1+10j)
- self.assertAlmostEqual(complex(1,10), 1+10j)
- self.assertAlmostEqual(complex(1,10.0), 1+10j)
- self.assertAlmostEqual(complex(1,10), 1+10j)
- self.assertAlmostEqual(complex(1,10), 1+10j)
- self.assertAlmostEqual(complex(1,10.0), 1+10j)
- self.assertAlmostEqual(complex(1.0,10), 1+10j)
- self.assertAlmostEqual(complex(1.0,10), 1+10j)
- self.assertAlmostEqual(complex(1.0,10.0), 1+10j)
- self.assertAlmostEqual(complex(3.14+0j), 3.14+0j)
- self.assertAlmostEqual(complex(3.14), 3.14+0j)
- self.assertAlmostEqual(complex(314), 314.0+0j)
- self.assertAlmostEqual(complex(314), 314.0+0j)
- self.assertAlmostEqual(complex(3.14+0j, 0j), 3.14+0j)
- self.assertAlmostEqual(complex(3.14, 0.0), 3.14+0j)
- self.assertAlmostEqual(complex(314, 0), 314.0+0j)
- self.assertAlmostEqual(complex(314, 0), 314.0+0j)
- self.assertAlmostEqual(complex(0j, 3.14j), -3.14+0j)
- self.assertAlmostEqual(complex(0.0, 3.14j), -3.14+0j)
- self.assertAlmostEqual(complex(0j, 3.14), 3.14j)
- self.assertAlmostEqual(complex(0.0, 3.14), 3.14j)
- self.assertAlmostEqual(complex("1"), 1+0j)
- self.assertAlmostEqual(complex("1j"), 1j)
- self.assertAlmostEqual(complex(), 0)
- self.assertAlmostEqual(complex("-1"), -1)
- self.assertAlmostEqual(complex("+1"), +1)
- self.assertAlmostEqual(complex("(1+2j)"), 1+2j)
- self.assertAlmostEqual(complex("(1.3+2.2j)"), 1.3+2.2j)
- self.assertAlmostEqual(complex("3.14+1J"), 3.14+1j)
- self.assertAlmostEqual(complex(" ( +3.14-6J )"), 3.14-6j)
- self.assertAlmostEqual(complex(" ( +3.14-J )"), 3.14-1j)
- self.assertAlmostEqual(complex(" ( +3.14+j )"), 3.14+1j)
- self.assertAlmostEqual(complex("J"), 1j)
- self.assertAlmostEqual(complex("( j )"), 1j)
- self.assertAlmostEqual(complex("+J"), 1j)
- self.assertAlmostEqual(complex("( -j)"), -1j)
- self.assertAlmostEqual(complex('1e-500'), 0.0 + 0.0j)
- self.assertAlmostEqual(complex('-1e-500j'), 0.0 - 0.0j)
- self.assertAlmostEqual(complex('-1e-500+1e-500j'), -0.0 + 0.0j)
- self.assertEqual(complex('1-1j'), 1.0 - 1j)
- self.assertEqual(complex('1J'), 1j)
-
- class complex2(complex): pass
- self.assertAlmostEqual(complex(complex2(1+1j)), 1+1j)
- self.assertAlmostEqual(complex(real=17, imag=23), 17+23j)
- self.assertAlmostEqual(complex(real=17+23j), 17+23j)
- self.assertAlmostEqual(complex(real=17+23j, imag=23), 17+46j)
- self.assertAlmostEqual(complex(real=1+2j, imag=3+4j), -3+5j)
+ def check(z, x, y):
+ self.assertIs(type(z), complex)
+ self.assertFloatsAreIdentical(z.real, x)
+ self.assertFloatsAreIdentical(z.imag, y)
+
+ check(complex(), 0.0, 0.0)
+ check(complex(10), 10.0, 0.0)
+ check(complex(4.25), 4.25, 0.0)
+ check(complex(4.25+0j), 4.25, 0.0)
+ check(complex(4.25+0.5j), 4.25, 0.5)
+ check(complex(ComplexSubclass(4.25+0.5j)), 4.25, 0.5)
+ check(complex(WithComplex(4.25+0.5j)), 4.25, 0.5)
+
+ check(complex(1, 10), 1.0, 10.0)
+ check(complex(1, 10.0), 1.0, 10.0)
+ check(complex(1, 4.25), 1.0, 4.25)
+ check(complex(1.0, 10), 1.0, 10.0)
+ check(complex(4.25, 10), 4.25, 10.0)
+ check(complex(1.0, 10.0), 1.0, 10.0)
+ check(complex(4.25, 0.5), 4.25, 0.5)
+
+ check(complex(4.25+0j, 0), 4.25, 0.0)
+ check(complex(ComplexSubclass(4.25+0j), 0), 4.25, 0.0)
+ check(complex(WithComplex(4.25+0j), 0), 4.25, 0.0)
+ check(complex(4.25j, 0), 0.0, 4.25)
+ check(complex(0j, 4.25), 0.0, 4.25)
+ check(complex(0, 4.25+0j), 0.0, 4.25)
+ check(complex(0, ComplexSubclass(4.25+0j)), 0.0, 4.25)
+ with self.assertRaisesRegex(TypeError,
+ "second argument must be a number, not 'WithComplex'"):
+ complex(0, WithComplex(4.25+0j))
+ check(complex(0.0, 4.25j), -4.25, 0.0)
+ check(complex(4.25+0j, 0j), 4.25, 0.0)
+ check(complex(4.25j, 0j), 0.0, 4.25)
+ check(complex(0j, 4.25+0j), 0.0, 4.25)
+ check(complex(0j, 4.25j), -4.25, 0.0)
+
+ check(complex(real=4.25), 4.25, 0.0)
+ check(complex(real=4.25+0j), 4.25, 0.0)
+ check(complex(real=4.25+1.5j), 4.25, 1.5)
+ check(complex(imag=1.5), 0.0, 1.5)
+ check(complex(real=4.25, imag=1.5), 4.25, 1.5)
+ check(complex(4.25, imag=1.5), 4.25, 1.5)
# check that the sign of a zero in the real or imaginary part
- # is preserved when constructing from two floats. (These checks
- # are harmless on systems without support for signed zeros.)
- def split_zeros(x):
- """Function that produces different results for 0. and -0."""
- return atan2(x, -1.)
-
- self.assertEqual(split_zeros(complex(1., 0.).imag), split_zeros(0.))
- self.assertEqual(split_zeros(complex(1., -0.).imag), split_zeros(-0.))
- self.assertEqual(split_zeros(complex(0., 1.).real), split_zeros(0.))
- self.assertEqual(split_zeros(complex(-0., 1.).real), split_zeros(-0.))
-
- c = 3.14 + 1j
- self.assertTrue(complex(c) is c)
- del c
-
- self.assertRaises(TypeError, complex, "1", "1")
- self.assertRaises(TypeError, complex, 1, "1")
-
- # SF bug 543840: complex(string) accepts strings with \0
- # Fixed in 2.3.
- self.assertRaises(ValueError, complex, '1+1j\0j')
-
- self.assertRaises(TypeError, int, 5+3j)
- self.assertRaises(TypeError, int, 5+3j)
- self.assertRaises(TypeError, float, 5+3j)
- self.assertRaises(ValueError, complex, "")
- self.assertRaises(TypeError, complex, None)
- self.assertRaisesRegex(TypeError, "not 'NoneType'", complex, None)
- self.assertRaises(ValueError, complex, "\0")
- self.assertRaises(ValueError, complex, "3\09")
- self.assertRaises(TypeError, complex, "1", "2")
- self.assertRaises(TypeError, complex, "1", 42)
- self.assertRaises(TypeError, complex, 1, "2")
- self.assertRaises(ValueError, complex, "1+")
- self.assertRaises(ValueError, complex, "1+1j+1j")
- self.assertRaises(ValueError, complex, "--")
- self.assertRaises(ValueError, complex, "(1+2j")
- self.assertRaises(ValueError, complex, "1+2j)")
- self.assertRaises(ValueError, complex, "1+(2j)")
- self.assertRaises(ValueError, complex, "(1+2j)123")
- self.assertRaises(ValueError, complex, "x")
- self.assertRaises(ValueError, complex, "1j+2")
- self.assertRaises(ValueError, complex, "1e1ej")
- self.assertRaises(ValueError, complex, "1e++1ej")
- self.assertRaises(ValueError, complex, ")1+2j(")
- self.assertRaisesRegex(
- TypeError,
+ # is preserved when constructing from two floats.
+ for x in 1.0, -1.0:
+ for y in 0.0, -0.0:
+ check(complex(x, y), x, y)
+ check(complex(y, x), y, x)
+
+ c = complex(4.25, 1.5)
+ self.assertIs(complex(c), c)
+ c2 = ComplexSubclass(c)
+ self.assertEqual(c2, c)
+ self.assertIs(type(c2), ComplexSubclass)
+ del c, c2
+
+ self.assertRaisesRegex(TypeError,
"first argument must be a string or a number, not 'dict'",
- complex, {1:2}, 1)
- self.assertRaisesRegex(
- TypeError,
+ complex, {})
+ self.assertRaisesRegex(TypeError,
+ "first argument must be a string or a number, not 'NoneType'",
+ complex, None)
+ self.assertRaisesRegex(TypeError,
+ "first argument must be a string or a number, not 'dict'",
+ complex, {1:2}, 0)
+ self.assertRaisesRegex(TypeError,
+ "can't take second arg if first is a string",
+ complex, '1', 0)
+ self.assertRaisesRegex(TypeError,
"second argument must be a number, not 'dict'",
- complex, 1, {1:2})
- # the following three are accepted by Python 2.6
- self.assertRaises(ValueError, complex, "1..1j")
- self.assertRaises(ValueError, complex, "1.11.1j")
- self.assertRaises(ValueError, complex, "1e1.1j")
-
- # check that complex accepts long unicode strings
- self.assertEqual(type(complex("1"*500)), complex)
- # check whitespace processing
- self.assertEqual(complex('\N{EM SPACE}(\N{EN SPACE}1+1j ) '), 1+1j)
- # Invalid unicode string
- # See bpo-34087
- self.assertRaises(ValueError, complex, '\u3053\u3093\u306b\u3061\u306f')
+ complex, 0, {1:2})
+ self.assertRaisesRegex(TypeError,
+ "second arg can't be a string",
+ complex, 0, '1')
+
+ self.assertRaises(TypeError, complex, WithComplex(1.5))
+ self.assertRaises(TypeError, complex, WithComplex(1))
+ self.assertRaises(TypeError, complex, WithComplex(None))
+ self.assertRaises(TypeError, complex, WithComplex(4.25+0j), object())
+ self.assertRaises(TypeError, complex, WithComplex(1.5), object())
+ self.assertRaises(TypeError, complex, WithComplex(1), object())
+ self.assertRaises(TypeError, complex, WithComplex(None), object())
class EvilExc(Exception):
pass
@@ -482,33 +444,33 @@ def __complex__(self):
self.assertRaises(EvilExc, complex, evilcomplex())
- class float2:
- def __init__(self, value):
- self.value = value
- def __float__(self):
- return self.value
-
- self.assertAlmostEqual(complex(float2(42.)), 42)
- self.assertAlmostEqual(complex(real=float2(17.), imag=float2(23.)), 17+23j)
- self.assertRaises(TypeError, complex, float2(None))
-
- class MyIndex:
- def __init__(self, value):
- self.value = value
- def __index__(self):
- return self.value
-
- self.assertAlmostEqual(complex(MyIndex(42)), 42.0+0.0j)
- self.assertAlmostEqual(complex(123, MyIndex(42)), 123.0+42.0j)
- self.assertRaises(OverflowError, complex, MyIndex(2**2000))
- self.assertRaises(OverflowError, complex, 123, MyIndex(2**2000))
+ check(complex(WithFloat(4.25)), 4.25, 0.0)
+ check(complex(WithFloat(4.25), 1.5), 4.25, 1.5)
+ check(complex(1.5, WithFloat(4.25)), 1.5, 4.25)
+ self.assertRaises(TypeError, complex, WithFloat(42))
+ self.assertRaises(TypeError, complex, WithFloat(42), 1.5)
+ self.assertRaises(TypeError, complex, 1.5, WithFloat(42))
+ self.assertRaises(TypeError, complex, WithFloat(None))
+ self.assertRaises(TypeError, complex, WithFloat(None), 1.5)
+ self.assertRaises(TypeError, complex, 1.5, WithFloat(None))
+
+ check(complex(WithIndex(42)), 42.0, 0.0)
+ check(complex(WithIndex(42), 1.5), 42.0, 1.5)
+ check(complex(1.5, WithIndex(42)), 1.5, 42.0)
+ self.assertRaises(OverflowError, complex, WithIndex(2**2000))
+ self.assertRaises(OverflowError, complex, WithIndex(2**2000), 1.5)
+ self.assertRaises(OverflowError, complex, 1.5, WithIndex(2**2000))
+ self.assertRaises(TypeError, complex, WithIndex(None))
+ self.assertRaises(TypeError, complex, WithIndex(None), 1.5)
+ self.assertRaises(TypeError, complex, 1.5, WithIndex(None))
class MyInt:
def __int__(self):
return 42
self.assertRaises(TypeError, complex, MyInt())
- self.assertRaises(TypeError, complex, 123, MyInt())
+ self.assertRaises(TypeError, complex, MyInt(), 1.5)
+ self.assertRaises(TypeError, complex, 1.5, MyInt())
class complex0(complex):
"""Test usage of __complex__() when inheriting from 'complex'"""
@@ -528,9 +490,9 @@ class complex2(complex):
def __complex__(self):
return None
- self.assertEqual(complex(complex0(1j)), 42j)
+ check(complex(complex0(1j)), 0.0, 42.0)
with self.assertWarns(DeprecationWarning):
- self.assertEqual(complex(complex1(1j)), 2j)
+ check(complex(complex1(1j)), 0.0, 2.0)
self.assertRaises(TypeError, complex, complex2(1j))
def test___complex__(self):
@@ -538,36 +500,93 @@ def test___complex__(self):
self.assertEqual(z.__complex__(), z)
self.assertEqual(type(z.__complex__()), complex)
- class complex_subclass(complex):
- pass
-
- z = complex_subclass(3 + 4j)
+ z = ComplexSubclass(3 + 4j)
self.assertEqual(z.__complex__(), 3 + 4j)
self.assertEqual(type(z.__complex__()), complex)
@support.requires_IEEE_754
def test_constructor_special_numbers(self):
- class complex2(complex):
- pass
for x in 0.0, -0.0, INF, -INF, NAN:
for y in 0.0, -0.0, INF, -INF, NAN:
with self.subTest(x=x, y=y):
z = complex(x, y)
self.assertFloatsAreIdentical(z.real, x)
self.assertFloatsAreIdentical(z.imag, y)
- z = complex2(x, y)
- self.assertIs(type(z), complex2)
+ z = ComplexSubclass(x, y)
+ self.assertIs(type(z), ComplexSubclass)
self.assertFloatsAreIdentical(z.real, x)
self.assertFloatsAreIdentical(z.imag, y)
- z = complex(complex2(x, y))
+ z = complex(ComplexSubclass(x, y))
self.assertIs(type(z), complex)
self.assertFloatsAreIdentical(z.real, x)
self.assertFloatsAreIdentical(z.imag, y)
- z = complex2(complex(x, y))
- self.assertIs(type(z), complex2)
+ z = ComplexSubclass(complex(x, y))
+ self.assertIs(type(z), ComplexSubclass)
self.assertFloatsAreIdentical(z.real, x)
self.assertFloatsAreIdentical(z.imag, y)
+ def test_constructor_from_string(self):
+ def check(z, x, y):
+ self.assertIs(type(z), complex)
+ self.assertFloatsAreIdentical(z.real, x)
+ self.assertFloatsAreIdentical(z.imag, y)
+
+ check(complex("1"), 1.0, 0.0)
+ check(complex("1j"), 0.0, 1.0)
+ check(complex("-1"), -1.0, 0.0)
+ check(complex("+1"), 1.0, 0.0)
+ check(complex("1+2j"), 1.0, 2.0)
+ check(complex("(1+2j)"), 1.0, 2.0)
+ check(complex("(1.5+4.25j)"), 1.5, 4.25)
+ check(complex("4.25+1J"), 4.25, 1.0)
+ check(complex(" ( +4.25-6J )"), 4.25, -6.0)
+ check(complex(" ( +4.25-J )"), 4.25, -1.0)
+ check(complex(" ( +4.25+j )"), 4.25, 1.0)
+ check(complex("J"), 0.0, 1.0)
+ check(complex("( j )"), 0.0, 1.0)
+ check(complex("+J"), 0.0, 1.0)
+ check(complex("( -j)"), 0.0, -1.0)
+ check(complex('1-1j'), 1.0, -1.0)
+ check(complex('1J'), 0.0, 1.0)
+
+ check(complex('1e-500'), 0.0, 0.0)
+ check(complex('-1e-500j'), 0.0, -0.0)
+ check(complex('1e-500+1e-500j'), 0.0, 0.0)
+ check(complex('-1e-500+1e-500j'), -0.0, 0.0)
+ check(complex('1e-500-1e-500j'), 0.0, -0.0)
+ check(complex('-1e-500-1e-500j'), -0.0, -0.0)
+
+ # SF bug 543840: complex(string) accepts strings with \0
+ # Fixed in 2.3.
+ self.assertRaises(ValueError, complex, '1+1j\0j')
+ self.assertRaises(ValueError, complex, "")
+ self.assertRaises(ValueError, complex, "\0")
+ self.assertRaises(ValueError, complex, "3\09")
+ self.assertRaises(ValueError, complex, "1+")
+ self.assertRaises(ValueError, complex, "1+1j+1j")
+ self.assertRaises(ValueError, complex, "--")
+ self.assertRaises(ValueError, complex, "(1+2j")
+ self.assertRaises(ValueError, complex, "1+2j)")
+ self.assertRaises(ValueError, complex, "1+(2j)")
+ self.assertRaises(ValueError, complex, "(1+2j)123")
+ self.assertRaises(ValueError, complex, "x")
+ self.assertRaises(ValueError, complex, "1j+2")
+ self.assertRaises(ValueError, complex, "1e1ej")
+ self.assertRaises(ValueError, complex, "1e++1ej")
+ self.assertRaises(ValueError, complex, ")1+2j(")
+ # the following three are accepted by Python 2.6
+ self.assertRaises(ValueError, complex, "1..1j")
+ self.assertRaises(ValueError, complex, "1.11.1j")
+ self.assertRaises(ValueError, complex, "1e1.1j")
+
+ # check that complex accepts long unicode strings
+ self.assertIs(type(complex("1"*500)), complex)
+ # check whitespace processing
+ self.assertEqual(complex('\N{EM SPACE}(\N{EN SPACE}1+1j ) '), 1+1j)
+ # Invalid unicode string
+ # See bpo-34087
+ self.assertRaises(ValueError, complex, '\u3053\u3093\u306b\u3061\u306f')
+
def test_constructor_negative_nans_from_string(self):
self.assertEqual(copysign(1., complex("-nan").real), -1.)
self.assertEqual(copysign(1., complex("-nanj").imag), -1.)
@@ -589,7 +608,7 @@ def test_underscores(self):
def test_hash(self):
for x in range(-30, 30):
self.assertEqual(hash(x), hash(complex(x, 0)))
- x /= 3.0 # now check against floating point
+ x /= 3.0 # now check against floating-point
self.assertEqual(hash(x), hash(complex(x, 0.)))
self.assertNotEqual(hash(2000005 - 1j), -1)
@@ -599,6 +618,8 @@ def test_abs(self):
for num in nums:
self.assertAlmostEqual((num.real**2 + num.imag**2) ** 0.5, abs(num))
+ self.assertRaises(OverflowError, abs, complex(DBL_MAX, DBL_MAX))
+
def test_repr_str(self):
def test(v, expected, test_fn=self.assertEqual):
test_fn(repr(v), expected)
@@ -644,9 +665,6 @@ def test(v, expected, test_fn=self.assertEqual):
test(complex(-0., -0.), "(-0-0j)")
def test_pos(self):
- class ComplexSubclass(complex):
- pass
-
self.assertEqual(+(1+6j), 1+6j)
self.assertEqual(+ComplexSubclass(1, 6), 1+6j)
self.assertIs(type(+ComplexSubclass(1, 6)), complex)
@@ -666,8 +684,8 @@ def test_getnewargs(self):
def test_plus_minus_0j(self):
# test that -0j and 0j literals are not identified
z1, z2 = 0j, -0j
- self.assertEqual(atan2(z1.imag, -1.), atan2(0., -1.))
- self.assertEqual(atan2(z2.imag, -1.), atan2(-0., -1.))
+ self.assertFloatsAreIdentical(z1.imag, 0.0)
+ self.assertFloatsAreIdentical(z2.imag, -0.0)
@support.requires_IEEE_754
def test_negated_imaginary_literal(self):
@@ -702,8 +720,7 @@ def test_repr_roundtrip(self):
for y in vals:
z = complex(x, y)
roundtrip = complex(repr(z))
- self.assertFloatsAreIdentical(z.real, roundtrip.real)
- self.assertFloatsAreIdentical(z.imag, roundtrip.imag)
+ self.assertComplexesAreIdentical(z, roundtrip)
# if we predefine some constants, then eval(repr(z)) should
# also work, except that it might change the sign of zeros
@@ -719,8 +736,6 @@ def test_repr_roundtrip(self):
self.assertFloatsAreIdentical(0.0 + z.imag,
0.0 + roundtrip.imag)
- # TODO: RUSTPYTHON
- @unittest.expectedFailure
def test_format(self):
# empty format string is same as str()
self.assertEqual(format(1+3j, ''), str(1+3j))
diff --git a/Lib/test/test_contains.py b/Lib/test/test_contains.py
index c533311572..471d04a76c 100644
--- a/Lib/test/test_contains.py
+++ b/Lib/test/test_contains.py
@@ -36,7 +36,6 @@ def test_common_tests(self):
self.assertRaises(TypeError, lambda: None in 'abc')
- @unittest.skip("TODO: RUSTPYTHON, hangs")
def test_builtin_sequence_types(self):
# a collection of tests on builtin sequence types
a = range(10)
diff --git a/Lib/test/test_copy.py b/Lib/test/test_copy.py
index cf3dc57930..2f9d8ed9b6 100644
--- a/Lib/test/test_copy.py
+++ b/Lib/test/test_copy.py
@@ -4,7 +4,7 @@
import copyreg
import weakref
import abc
-from operator import le, lt, ge, gt, eq, ne
+from operator import le, lt, ge, gt, eq, ne, attrgetter
import unittest
from test import support
@@ -903,7 +903,89 @@ def m(self):
g.b()
+class TestReplace(unittest.TestCase):
+
+ def test_unsupported(self):
+ self.assertRaises(TypeError, copy.replace, 1)
+ self.assertRaises(TypeError, copy.replace, [])
+ self.assertRaises(TypeError, copy.replace, {})
+ def f(): pass
+ self.assertRaises(TypeError, copy.replace, f)
+ class A: pass
+ self.assertRaises(TypeError, copy.replace, A)
+ self.assertRaises(TypeError, copy.replace, A())
+
+ def test_replace_method(self):
+ class A:
+ def __new__(cls, x, y=0):
+ self = object.__new__(cls)
+ self.x = x
+ self.y = y
+ return self
+
+ def __init__(self, *args, **kwargs):
+ self.z = self.x + self.y
+
+ def __replace__(self, **changes):
+ x = changes.get('x', self.x)
+ y = changes.get('y', self.y)
+ return type(self)(x, y)
+
+ attrs = attrgetter('x', 'y', 'z')
+ a = A(11, 22)
+ self.assertEqual(attrs(copy.replace(a)), (11, 22, 33))
+ self.assertEqual(attrs(copy.replace(a, x=1)), (1, 22, 23))
+ self.assertEqual(attrs(copy.replace(a, y=2)), (11, 2, 13))
+ self.assertEqual(attrs(copy.replace(a, x=1, y=2)), (1, 2, 3))
+
+ # TODO: RUSTPYTHON
+ @unittest.expectedFailure
+ def test_namedtuple(self):
+ from collections import namedtuple
+ from typing import NamedTuple
+ PointFromCall = namedtuple('Point', 'x y', defaults=(0,))
+ class PointFromInheritance(PointFromCall):
+ pass
+ class PointFromClass(NamedTuple):
+ x: int
+ y: int = 0
+ for Point in (PointFromCall, PointFromInheritance, PointFromClass):
+ with self.subTest(Point=Point):
+ p = Point(11, 22)
+ self.assertIsInstance(p, Point)
+ self.assertEqual(copy.replace(p), (11, 22))
+ self.assertIsInstance(copy.replace(p), Point)
+ self.assertEqual(copy.replace(p, x=1), (1, 22))
+ self.assertEqual(copy.replace(p, y=2), (11, 2))
+ self.assertEqual(copy.replace(p, x=1, y=2), (1, 2))
+ with self.assertRaisesRegex(TypeError, 'unexpected field name'):
+ copy.replace(p, x=1, error=2)
+
+ # TODO: RUSTPYTHON
+ @unittest.expectedFailure
+ def test_dataclass(self):
+ from dataclasses import dataclass
+ @dataclass
+ class C:
+ x: int
+ y: int = 0
+
+ attrs = attrgetter('x', 'y')
+ c = C(11, 22)
+ self.assertEqual(attrs(copy.replace(c)), (11, 22))
+ self.assertEqual(attrs(copy.replace(c, x=1)), (1, 22))
+ self.assertEqual(attrs(copy.replace(c, y=2)), (11, 2))
+ self.assertEqual(attrs(copy.replace(c, x=1, y=2)), (1, 2))
+ with self.assertRaisesRegex(TypeError, 'unexpected keyword argument'):
+ copy.replace(c, x=1, error=2)
+
+
+class MiscTestCase(unittest.TestCase):
+ def test__all__(self):
+ support.check__all__(self, copy, not_exported={"dispatch_table", "error"})
+
def global_foo(x, y): return x+y
+
if __name__ == "__main__":
unittest.main()
diff --git a/Lib/test/test_dataclasses.py b/Lib/test/test_dataclasses.py
index 8094962ccf..46430d3231 100644
--- a/Lib/test/test_dataclasses.py
+++ b/Lib/test/test_dataclasses.py
@@ -1906,8 +1906,6 @@ def new_method(self):
c = Alias(10, 1.0)
self.assertEqual(c.new_method(), 1.0)
- # TODO: RUSTPYTHON
- @unittest.expectedFailure
def test_generic_dynamic(self):
T = TypeVar('T')
@@ -2088,8 +2086,6 @@ class C:
self.assertDocStrEqual(C.__doc__, "C(x:List[int]=)")
- # TODO: RUSTPYTHON
- @unittest.expectedFailure
def test_docstring_deque_field(self):
@dataclass
class C:
@@ -2097,8 +2093,6 @@ class C:
self.assertDocStrEqual(C.__doc__, "C(x:collections.deque)")
- # TODO: RUSTPYTHON
- @unittest.expectedFailure
def test_docstring_deque_field_with_default_factory(self):
@dataclass
class C:
@@ -3252,8 +3246,6 @@ def test_classvar_module_level_import(self):
# won't exist on the instance.
self.assertNotIn('not_iv4', c.__dict__)
- # TODO: RUSTPYTHON
- @unittest.expectedFailure
def test_text_annotations(self):
from test import dataclass_textanno
diff --git a/Lib/test/test_datetime.py b/Lib/test/test_datetime.py
index ead211bec3..334e6942d4 100644
--- a/Lib/test/test_datetime.py
+++ b/Lib/test/test_datetime.py
@@ -1,5 +1,6 @@
import unittest
import sys
+import functools
from test.support.import_helper import import_fresh_module
@@ -45,21 +46,26 @@ def load_tests(loader, tests, pattern):
for cls in test_classes:
cls.__name__ += suffix
cls.__qualname__ += suffix
- @classmethod
- def setUpClass(cls_, module=module):
- cls_._save_sys_modules = sys.modules.copy()
- sys.modules[TESTS] = module
- sys.modules['datetime'] = module.datetime_module
- if hasattr(module, '_pydatetime'):
- sys.modules['_pydatetime'] = module._pydatetime
- sys.modules['_strptime'] = module._strptime
- @classmethod
- def tearDownClass(cls_):
- sys.modules.clear()
- sys.modules.update(cls_._save_sys_modules)
- cls.setUpClass = setUpClass
- cls.tearDownClass = tearDownClass
- tests.addTests(loader.loadTestsFromTestCase(cls))
+
+ @functools.wraps(cls, updated=())
+ class Wrapper(cls):
+ @classmethod
+ def setUpClass(cls_, module=module):
+ cls_._save_sys_modules = sys.modules.copy()
+ sys.modules[TESTS] = module
+ sys.modules['datetime'] = module.datetime_module
+ if hasattr(module, '_pydatetime'):
+ sys.modules['_pydatetime'] = module._pydatetime
+ sys.modules['_strptime'] = module._strptime
+ super().setUpClass()
+
+ @classmethod
+ def tearDownClass(cls_):
+ super().tearDownClass()
+ sys.modules.clear()
+ sys.modules.update(cls_._save_sys_modules)
+
+ tests.addTests(loader.loadTestsFromTestCase(Wrapper))
return tests
diff --git a/Lib/test/test_deque.py b/Lib/test/test_deque.py
index 2b0144eb06..9f00e12edd 100644
--- a/Lib/test/test_deque.py
+++ b/Lib/test/test_deque.py
@@ -166,7 +166,7 @@ def test_contains(self):
with self.assertRaises(RuntimeError):
n in d
- def test_contains_count_stop_crashes(self):
+ def test_contains_count_index_stop_crashes(self):
class A:
def __eq__(self, other):
d.clear()
@@ -178,6 +178,10 @@ def __eq__(self, other):
with self.assertRaises(RuntimeError):
_ = d.count(3)
+ d = deque([A()])
+ with self.assertRaises(RuntimeError):
+ d.index(0)
+
def test_extend(self):
d = deque('a')
self.assertRaises(TypeError, d.extend, 1)
diff --git a/Lib/test/test_descr.py b/Lib/test/test_descr.py
index eae8b42fce..7698c340c8 100644
--- a/Lib/test/test_descr.py
+++ b/Lib/test/test_descr.py
@@ -1558,8 +1558,6 @@ class B(A1, A2):
else:
self.fail("finding the most derived metaclass should have failed")
- # TODO: RUSTPYTHON
- @unittest.expectedFailure
def test_classmethods(self):
# Testing class methods...
class C(object):
@@ -1851,8 +1849,6 @@ def __init__(self, foo):
object.__init__(A(3))
self.assertRaises(TypeError, object.__init__, A(3), 5)
- @unittest.expectedFailure
- @unittest.skip("TODO: RUSTPYTHON")
def test_restored_object_new(self):
class A(object):
def __new__(cls, *args, **kwargs):
@@ -2358,8 +2354,6 @@ class D(object):
else:
self.fail("expected ZeroDivisionError from bad property")
- # TODO: RUSTPYTHON
- @unittest.expectedFailure
@unittest.skipIf(sys.flags.optimize >= 2,
"Docstrings are omitted with -O2 and above")
def test_properties_doc_attrib(self):
@@ -2386,8 +2380,6 @@ def test_testcapi_no_segfault(self):
class X(object):
p = property(_testcapi.test_with_docstring)
- # TODO: RUSTPYTHON
- @unittest.expectedFailure
def test_properties_plus(self):
class C(object):
foo = property(doc="hello")
@@ -2534,8 +2526,6 @@ def __iter__(self):
else:
self.fail("no ValueError from dict(%r)" % bad)
- # TODO: RUSTPYTHON
- @unittest.expectedFailure
def test_dir(self):
# Testing dir() ...
junk = 12
@@ -4271,7 +4261,6 @@ class C(object):
C.__name__ = 'D.E'
self.assertEqual((C.__module__, C.__name__), (mod, 'D.E'))
- @unittest.skip("TODO: RUSTPYTHON, rustpython hang")
def test_evil_type_name(self):
# A badly placed Py_DECREF in type_set_name led to arbitrary code
# execution while the type structure was not in a sane state, and a
@@ -4997,8 +4986,6 @@ class Sub(Base):
self.assertIn("__dict__", Base.__dict__)
self.assertNotIn("__dict__", Sub.__dict__)
- # TODO: RUSTPYTHON
- @unittest.expectedFailure
def test_bound_method_repr(self):
class Foo:
def method(self):
diff --git a/Lib/test/test_dict.py b/Lib/test/test_dict.py
index 4aa6f1089a..9598a7ab96 100644
--- a/Lib/test/test_dict.py
+++ b/Lib/test/test_dict.py
@@ -8,7 +8,7 @@
import unittest
import weakref
from test import support
-from test.support import import_helper, C_RECURSION_LIMIT
+from test.support import import_helper, get_c_recursion_limit
class DictTest(unittest.TestCase):
@@ -312,17 +312,34 @@ def __setitem__(self, key, value):
self.assertRaises(Exc, baddict2.fromkeys, [1])
# test fast path for dictionary inputs
+ res = dict(zip(range(6), [0]*6))
d = dict(zip(range(6), range(6)))
- self.assertEqual(dict.fromkeys(d, 0), dict(zip(range(6), [0]*6)))
-
+ self.assertEqual(dict.fromkeys(d, 0), res)
+ # test fast path for set inputs
+ d = set(range(6))
+ self.assertEqual(dict.fromkeys(d, 0), res)
+ # test slow path for other iterable inputs
+ d = list(range(6))
+ self.assertEqual(dict.fromkeys(d, 0), res)
+
+ # test fast path when object's constructor returns large non-empty dict
class baddict3(dict):
def __new__(cls):
return d
- d = {i : i for i in range(10)}
+ d = {i : i for i in range(1000)}
res = d.copy()
res.update(a=None, b=None, c=None)
self.assertEqual(baddict3.fromkeys({"a", "b", "c"}), res)
+ # test slow path when object is a proper subclass of dict
+ class baddict4(dict):
+ def __init__(self):
+ dict.__init__(self, d)
+ d = {i : i for i in range(1000)}
+ res = d.copy()
+ res.update(a=None, b=None, c=None)
+ self.assertEqual(baddict4.fromkeys({"a", "b", "c"}), res)
+
def test_copy(self):
d = {1: 1, 2: 2, 3: 3}
self.assertIsNot(d.copy(), d)
@@ -596,10 +613,9 @@ def __repr__(self):
d = {1: BadRepr()}
self.assertRaises(Exc, repr, d)
- @unittest.skipIf(sys.platform == 'win32', 'TODO: RUSTPYTHON Windows')
def test_repr_deep(self):
d = {}
- for i in range(C_RECURSION_LIMIT + 1):
+ for i in range(get_c_recursion_limit() + 1):
d = {1: d}
self.assertRaises(RecursionError, repr, d)
@@ -994,6 +1010,18 @@ class MyDict(dict):
pass
self._tracked(MyDict())
+ @support.cpython_only
+ def test_track_lazy_instance_dicts(self):
+ class C:
+ pass
+ o = C()
+ d = o.__dict__
+ self._not_tracked(d)
+ o.untracked = 42
+ self._not_tracked(d)
+ o.tracked = []
+ self._tracked(d)
+
def make_shared_key_dict(self, n):
class C:
pass
@@ -1108,10 +1136,8 @@ class C:
a = C()
a.x = 1
d = a.__dict__
- before_resize = sys.getsizeof(d)
d[2] = 2 # split table is resized to a generic combined table
- self.assertGreater(sys.getsizeof(d), before_resize)
self.assertEqual(list(d), ['x', 2])
def test_iterator_pickling(self):
@@ -1485,6 +1511,24 @@ def test_dict_items_result_gc_reversed(self):
gc.collect()
self.assertTrue(gc.is_tracked(next(it)))
+ def test_store_evilattr(self):
+ class EvilAttr:
+ def __init__(self, d):
+ self.d = d
+
+ def __del__(self):
+ if 'attr' in self.d:
+ del self.d['attr']
+ gc.collect()
+
+ class Obj:
+ pass
+
+ obj = Obj()
+ obj.__dict__ = {}
+ for _ in range(10):
+ obj.attr = EvilAttr(obj.__dict__)
+
def test_str_nonstr(self):
# cpython uses a different lookup function if the dict only contains
# `str` keys. Make sure the unoptimized path is used when a non-`str`
@@ -1591,8 +1635,8 @@ class CAPITest(unittest.TestCase):
# Test _PyDict_GetItem_KnownHash()
@support.cpython_only
def test_getitem_knownhash(self):
- _testcapi = import_helper.import_module('_testcapi')
- dict_getitem_knownhash = _testcapi.dict_getitem_knownhash
+ _testinternalcapi = import_helper.import_module('_testinternalcapi')
+ dict_getitem_knownhash = _testinternalcapi.dict_getitem_knownhash
d = {'x': 1, 'y': 2, 'z': 3}
self.assertEqual(dict_getitem_knownhash(d, 'x', hash('x')), 1)
diff --git a/Lib/test/test_exception_group.py b/Lib/test/test_exception_group.py
index 9d156a160c..9d25b4b4d2 100644
--- a/Lib/test/test_exception_group.py
+++ b/Lib/test/test_exception_group.py
@@ -1,7 +1,7 @@
import collections.abc
import types
import unittest
-from test.support import C_RECURSION_LIMIT
+from test.support import get_c_recursion_limit
class TestExceptionGroupTypeHierarchy(unittest.TestCase):
def test_exception_group_types(self):
@@ -15,6 +15,8 @@ def test_exception_is_not_generic_type(self):
with self.assertRaisesRegex(TypeError, 'Exception'):
Exception[OSError]
+ # TODO: RUSTPYTHON
+ @unittest.expectedFailure
def test_exception_group_is_generic_type(self):
E = OSError
self.assertIsInstance(ExceptionGroup[E], types.GenericAlias)
@@ -298,17 +300,33 @@ def assertMatchesTemplate(self, exc, exc_type, template):
self.assertEqual(type(exc), type(template))
self.assertEqual(exc.args, template.args)
+class Predicate:
+ def __init__(self, func):
+ self.func = func
+
+ def __call__(self, e):
+ return self.func(e)
+
+ def method(self, e):
+ return self.func(e)
class ExceptionGroupSubgroupTests(ExceptionGroupTestBase):
def setUp(self):
self.eg = create_simple_eg()
self.eg_template = [ValueError(1), TypeError(int), ValueError(2)]
+ # TODO: RUSTPYTHON
+ @unittest.expectedFailure
def test_basics_subgroup_split__bad_arg_type(self):
+ class C:
+ pass
+
bad_args = ["bad arg",
+ C,
OSError('instance not type'),
[OSError, TypeError],
- (OSError, 42)]
+ (OSError, 42),
+ ]
for arg in bad_args:
with self.assertRaises(TypeError):
self.eg.subgroup(arg)
@@ -340,10 +358,14 @@ def test_basics_subgroup_by_type__match(self):
self.assertMatchesTemplate(subeg, ExceptionGroup, template)
def test_basics_subgroup_by_predicate__passthrough(self):
- self.assertIs(self.eg, self.eg.subgroup(lambda e: True))
+ f = lambda e: True
+ for callable in [f, Predicate(f), Predicate(f).method]:
+ self.assertIs(self.eg, self.eg.subgroup(callable))
def test_basics_subgroup_by_predicate__no_match(self):
- self.assertIsNone(self.eg.subgroup(lambda e: False))
+ f = lambda e: False
+ for callable in [f, Predicate(f), Predicate(f).method]:
+ self.assertIsNone(self.eg.subgroup(callable))
def test_basics_subgroup_by_predicate__match(self):
eg = self.eg
@@ -354,9 +376,12 @@ def test_basics_subgroup_by_predicate__match(self):
((ValueError, TypeError), self.eg_template)]
for match_type, template in testcases:
- subeg = eg.subgroup(lambda e: isinstance(e, match_type))
- self.assertEqual(subeg.message, eg.message)
- self.assertMatchesTemplate(subeg, ExceptionGroup, template)
+ f = lambda e: isinstance(e, match_type)
+ for callable in [f, Predicate(f), Predicate(f).method]:
+ with self.subTest(callable=callable):
+ subeg = eg.subgroup(f)
+ self.assertEqual(subeg.message, eg.message)
+ self.assertMatchesTemplate(subeg, ExceptionGroup, template)
class ExceptionGroupSplitTests(ExceptionGroupTestBase):
@@ -403,14 +428,18 @@ def test_basics_split_by_type__match(self):
self.assertIsNone(rest)
def test_basics_split_by_predicate__passthrough(self):
- match, rest = self.eg.split(lambda e: True)
- self.assertMatchesTemplate(match, ExceptionGroup, self.eg_template)
- self.assertIsNone(rest)
+ f = lambda e: True
+ for callable in [f, Predicate(f), Predicate(f).method]:
+ match, rest = self.eg.split(callable)
+ self.assertMatchesTemplate(match, ExceptionGroup, self.eg_template)
+ self.assertIsNone(rest)
def test_basics_split_by_predicate__no_match(self):
- match, rest = self.eg.split(lambda e: False)
- self.assertIsNone(match)
- self.assertMatchesTemplate(rest, ExceptionGroup, self.eg_template)
+ f = lambda e: False
+ for callable in [f, Predicate(f), Predicate(f).method]:
+ match, rest = self.eg.split(callable)
+ self.assertIsNone(match)
+ self.assertMatchesTemplate(rest, ExceptionGroup, self.eg_template)
def test_basics_split_by_predicate__match(self):
eg = self.eg
@@ -424,20 +453,22 @@ def test_basics_split_by_predicate__match(self):
]
for match_type, match_template, rest_template in testcases:
- match, rest = eg.split(lambda e: isinstance(e, match_type))
- self.assertEqual(match.message, eg.message)
- self.assertMatchesTemplate(
- match, ExceptionGroup, match_template)
- if rest_template is not None:
- self.assertEqual(rest.message, eg.message)
+ f = lambda e: isinstance(e, match_type)
+ for callable in [f, Predicate(f), Predicate(f).method]:
+ match, rest = eg.split(callable)
+ self.assertEqual(match.message, eg.message)
self.assertMatchesTemplate(
- rest, ExceptionGroup, rest_template)
+ match, ExceptionGroup, match_template)
+ if rest_template is not None:
+ self.assertEqual(rest.message, eg.message)
+ self.assertMatchesTemplate(
+ rest, ExceptionGroup, rest_template)
class DeepRecursionInSplitAndSubgroup(unittest.TestCase):
def make_deep_eg(self):
e = TypeError(1)
- for i in range(C_RECURSION_LIMIT + 1):
+ for i in range(get_c_recursion_limit() + 1):
e = ExceptionGroup('eg', [e])
return e
diff --git a/Lib/test/test_exception_hierarchy.py b/Lib/test/test_exception_hierarchy.py
index efee88cd5e..e2f2844512 100644
--- a/Lib/test/test_exception_hierarchy.py
+++ b/Lib/test/test_exception_hierarchy.py
@@ -127,7 +127,6 @@ def test_windows_error(self):
else:
self.assertNotIn('winerror', dir(OSError))
- @unittest.skip("TODO: RUSTPYTHON")
def test_posix_error(self):
e = OSError(EEXIST, "File already exists", "foo.txt")
self.assertEqual(e.errno, EEXIST)
diff --git a/Lib/test/test_exception_variations.py b/Lib/test/test_exception_variations.py
index d874b0e3d1..e103eaf846 100644
--- a/Lib/test/test_exception_variations.py
+++ b/Lib/test/test_exception_variations.py
@@ -1,7 +1,7 @@
import unittest
-class ExceptionTestCase(unittest.TestCase):
+class ExceptTestCases(unittest.TestCase):
def test_try_except_else_finally(self):
hit_except = False
hit_else = False
@@ -172,5 +172,406 @@ def test_nested_else(self):
self.assertTrue(hit_finally)
self.assertTrue(hit_except)
+ def test_nested_exception_in_except(self):
+ hit_else = False
+ hit_finally = False
+ hit_except = False
+ hit_inner_except = False
+ hit_inner_else = False
+
+ try:
+ try:
+ raise Exception('inner exception')
+ except:
+ hit_inner_except = True
+ raise Exception('outer exception')
+ else:
+ hit_inner_else = True
+ except:
+ hit_except = True
+ else:
+ hit_else = True
+ finally:
+ hit_finally = True
+
+ self.assertTrue(hit_inner_except)
+ self.assertFalse(hit_inner_else)
+ self.assertFalse(hit_else)
+ self.assertTrue(hit_finally)
+ self.assertTrue(hit_except)
+
+ def test_nested_exception_in_else(self):
+ hit_else = False
+ hit_finally = False
+ hit_except = False
+ hit_inner_except = False
+ hit_inner_else = False
+
+ try:
+ try:
+ pass
+ except:
+ hit_inner_except = True
+ else:
+ hit_inner_else = True
+ raise Exception('outer exception')
+ except:
+ hit_except = True
+ else:
+ hit_else = True
+ finally:
+ hit_finally = True
+
+ self.assertFalse(hit_inner_except)
+ self.assertTrue(hit_inner_else)
+ self.assertFalse(hit_else)
+ self.assertTrue(hit_finally)
+ self.assertTrue(hit_except)
+
+ def test_nested_exception_in_finally_no_exception(self):
+ hit_else = False
+ hit_finally = False
+ hit_except = False
+ hit_inner_except = False
+ hit_inner_else = False
+ hit_inner_finally = False
+
+ try:
+ try:
+ pass
+ except:
+ hit_inner_except = True
+ else:
+ hit_inner_else = True
+ finally:
+ hit_inner_finally = True
+ raise Exception('outer exception')
+ except:
+ hit_except = True
+ else:
+ hit_else = True
+ finally:
+ hit_finally = True
+
+ self.assertFalse(hit_inner_except)
+ self.assertTrue(hit_inner_else)
+ self.assertTrue(hit_inner_finally)
+ self.assertFalse(hit_else)
+ self.assertTrue(hit_finally)
+ self.assertTrue(hit_except)
+
+ def test_nested_exception_in_finally_with_exception(self):
+ hit_else = False
+ hit_finally = False
+ hit_except = False
+ hit_inner_except = False
+ hit_inner_else = False
+ hit_inner_finally = False
+
+ try:
+ try:
+ raise Exception('inner exception')
+ except:
+ hit_inner_except = True
+ else:
+ hit_inner_else = True
+ finally:
+ hit_inner_finally = True
+ raise Exception('outer exception')
+ except:
+ hit_except = True
+ else:
+ hit_else = True
+ finally:
+ hit_finally = True
+
+
+ self.assertTrue(hit_inner_except)
+ self.assertFalse(hit_inner_else)
+ self.assertTrue(hit_inner_finally)
+ self.assertFalse(hit_else)
+ self.assertTrue(hit_finally)
+ self.assertTrue(hit_except)
+
+
+# TODO: RUSTPYTHON
+'''
+class ExceptStarTestCases(unittest.TestCase):
+ def test_try_except_else_finally(self):
+ hit_except = False
+ hit_else = False
+ hit_finally = False
+
+ try:
+ raise Exception('nyaa!')
+ except* BaseException:
+ hit_except = True
+ else:
+ hit_else = True
+ finally:
+ hit_finally = True
+
+ self.assertTrue(hit_except)
+ self.assertTrue(hit_finally)
+ self.assertFalse(hit_else)
+
+ def test_try_except_else_finally_no_exception(self):
+ hit_except = False
+ hit_else = False
+ hit_finally = False
+
+ try:
+ pass
+ except* BaseException:
+ hit_except = True
+ else:
+ hit_else = True
+ finally:
+ hit_finally = True
+
+ self.assertFalse(hit_except)
+ self.assertTrue(hit_finally)
+ self.assertTrue(hit_else)
+
+ def test_try_except_finally(self):
+ hit_except = False
+ hit_finally = False
+
+ try:
+ raise Exception('yarr!')
+ except* BaseException:
+ hit_except = True
+ finally:
+ hit_finally = True
+
+ self.assertTrue(hit_except)
+ self.assertTrue(hit_finally)
+
+ def test_try_except_finally_no_exception(self):
+ hit_except = False
+ hit_finally = False
+
+ try:
+ pass
+ except* BaseException:
+ hit_except = True
+ finally:
+ hit_finally = True
+
+ self.assertFalse(hit_except)
+ self.assertTrue(hit_finally)
+
+ def test_try_except(self):
+ hit_except = False
+
+ try:
+ raise Exception('ahoy!')
+ except* BaseException:
+ hit_except = True
+
+ self.assertTrue(hit_except)
+
+ def test_try_except_no_exception(self):
+ hit_except = False
+
+ try:
+ pass
+ except* BaseException:
+ hit_except = True
+
+ self.assertFalse(hit_except)
+
+ def test_try_except_else(self):
+ hit_except = False
+ hit_else = False
+
+ try:
+ raise Exception('foo!')
+ except* BaseException:
+ hit_except = True
+ else:
+ hit_else = True
+
+ self.assertFalse(hit_else)
+ self.assertTrue(hit_except)
+
+ def test_try_except_else_no_exception(self):
+ hit_except = False
+ hit_else = False
+
+ try:
+ pass
+ except* BaseException:
+ hit_except = True
+ else:
+ hit_else = True
+
+ self.assertFalse(hit_except)
+ self.assertTrue(hit_else)
+
+ def test_try_finally_no_exception(self):
+ hit_finally = False
+
+ try:
+ pass
+ finally:
+ hit_finally = True
+
+ self.assertTrue(hit_finally)
+
+ def test_nested(self):
+ hit_finally = False
+ hit_inner_except = False
+ hit_inner_finally = False
+
+ try:
+ try:
+ raise Exception('inner exception')
+ except* BaseException:
+ hit_inner_except = True
+ finally:
+ hit_inner_finally = True
+ finally:
+ hit_finally = True
+
+ self.assertTrue(hit_inner_except)
+ self.assertTrue(hit_inner_finally)
+ self.assertTrue(hit_finally)
+
+ def test_nested_else(self):
+ hit_else = False
+ hit_finally = False
+ hit_except = False
+ hit_inner_except = False
+ hit_inner_else = False
+
+ try:
+ try:
+ pass
+ except* BaseException:
+ hit_inner_except = True
+ else:
+ hit_inner_else = True
+
+ raise Exception('outer exception')
+ except* BaseException:
+ hit_except = True
+ else:
+ hit_else = True
+ finally:
+ hit_finally = True
+
+ self.assertFalse(hit_inner_except)
+ self.assertTrue(hit_inner_else)
+ self.assertFalse(hit_else)
+ self.assertTrue(hit_finally)
+ self.assertTrue(hit_except)
+
+ def test_nested_mixed1(self):
+ hit_except = False
+ hit_finally = False
+ hit_inner_except = False
+ hit_inner_finally = False
+
+ try:
+ try:
+ raise Exception('inner exception')
+ except* BaseException:
+ hit_inner_except = True
+ finally:
+ hit_inner_finally = True
+ except:
+ hit_except = True
+ finally:
+ hit_finally = True
+
+ self.assertTrue(hit_inner_except)
+ self.assertTrue(hit_inner_finally)
+ self.assertFalse(hit_except)
+ self.assertTrue(hit_finally)
+
+ def test_nested_mixed2(self):
+ hit_except = False
+ hit_finally = False
+ hit_inner_except = False
+ hit_inner_finally = False
+
+ try:
+ try:
+ raise Exception('inner exception')
+ except:
+ hit_inner_except = True
+ finally:
+ hit_inner_finally = True
+ except* BaseException:
+ hit_except = True
+ finally:
+ hit_finally = True
+
+ self.assertTrue(hit_inner_except)
+ self.assertTrue(hit_inner_finally)
+ self.assertFalse(hit_except)
+ self.assertTrue(hit_finally)
+
+
+ def test_nested_else_mixed1(self):
+ hit_else = False
+ hit_finally = False
+ hit_except = False
+ hit_inner_except = False
+ hit_inner_else = False
+
+ try:
+ try:
+ pass
+ except* BaseException:
+ hit_inner_except = True
+ else:
+ hit_inner_else = True
+
+ raise Exception('outer exception')
+ except:
+ hit_except = True
+ else:
+ hit_else = True
+ finally:
+ hit_finally = True
+
+ self.assertFalse(hit_inner_except)
+ self.assertTrue(hit_inner_else)
+ self.assertFalse(hit_else)
+ self.assertTrue(hit_finally)
+ self.assertTrue(hit_except)
+
+ def test_nested_else_mixed2(self):
+ hit_else = False
+ hit_finally = False
+ hit_except = False
+ hit_inner_except = False
+ hit_inner_else = False
+
+ try:
+ try:
+ pass
+ except:
+ hit_inner_except = True
+ else:
+ hit_inner_else = True
+
+ raise Exception('outer exception')
+ except* BaseException:
+ hit_except = True
+ else:
+ hit_else = True
+ finally:
+ hit_finally = True
+
+ self.assertFalse(hit_inner_except)
+ self.assertTrue(hit_inner_else)
+ self.assertFalse(hit_else)
+ self.assertTrue(hit_finally)
+ self.assertTrue(hit_except)
+'''
+
if __name__ == '__main__':
unittest.main()
diff --git a/Lib/test/test_exceptions.py b/Lib/test/test_exceptions.py
index 8be8122507..57afb6ec6f 100644
--- a/Lib/test/test_exceptions.py
+++ b/Lib/test/test_exceptions.py
@@ -1,24 +1,33 @@
# Python test set -- part 5, built-in exceptions
import copy
-import gc
import os
import sys
import unittest
import pickle
import weakref
import errno
+from codecs import BOM_UTF8
+from itertools import product
from textwrap import dedent
from test.support import (captured_stderr, check_impl_detail,
cpython_only, gc_collect,
no_tracing, script_helper,
- SuppressCrashReport)
+ SuppressCrashReport,
+ force_not_colorized)
from test.support.import_helper import import_module
from test.support.os_helper import TESTFN, unlink
from test.support.warnings_helper import check_warnings
from test import support
+try:
+ import _testcapi
+ from _testcapi import INT_MAX
+except ImportError:
+ _testcapi = None
+ INT_MAX = 2**31 - 1
+
class NaiveException(Exception):
def __init__(self, x):
@@ -35,6 +44,7 @@ def __str__(self):
# XXX This is not really enough, each *operation* should be tested!
+
class ExceptionTests(unittest.TestCase):
def raise_catch(self, exc, excname):
@@ -160,6 +170,7 @@ def ckmsg(src, msg):
ckmsg(s, "'continue' not properly in loop")
ckmsg("continue\n", "'continue' not properly in loop")
+ ckmsg("f'{6 0}'", "invalid syntax. Perhaps you forgot a comma?")
# TODO: RUSTPYTHON
@unittest.expectedFailure
@@ -220,7 +231,7 @@ def check(self, src, lineno, offset, end_lineno=None, end_offset=None, encoding=
src = src.decode(encoding, 'replace')
line = src.split('\n')[lineno-1]
self.assertIn(line, cm.exception.text)
-
+
# TODO: RUSTPYTHON
@unittest.expectedFailure
def test_error_offset_continuation_characters(self):
@@ -238,7 +249,7 @@ def testSyntaxErrorOffset(self):
check('Python = "\u1e54\xfd\u0163\u0125\xf2\xf1" +', 1, 20)
check(b'# -*- coding: cp1251 -*-\nPython = "\xcf\xb3\xf2\xee\xed" +',
2, 19, encoding='cp1251')
- check(b'Python = "\xcf\xb3\xf2\xee\xed" +', 1, 18)
+ check(b'Python = "\xcf\xb3\xf2\xee\xed" +', 1, 10)
check('x = "a', 1, 5)
check('lambda x: x = 2', 1, 1)
check('f{a + b + c}', 1, 2)
@@ -263,7 +274,7 @@ def testSyntaxErrorOffset(self):
check('try:\n pass\nexcept*:\n pass', 3, 8)
check('try:\n pass\nexcept*:\n pass\nexcept* ValueError:\n pass', 3, 8)
- # Errors thrown by tokenizer.c
+ # Errors thrown by the tokenizer
check('(0x+1)', 1, 3)
check('x = 0xI', 1, 6)
check('0010 + 2', 1, 1)
@@ -305,6 +316,7 @@ def baz():
{
6
0="""''', 5, 13)
+ check('b"fooжжж"'.encode(), 1, 1, 1, 10)
# Errors thrown by symtable.c
check('x = [(yield i) for i in range(3)]', 1, 7)
@@ -317,8 +329,8 @@ def baz():
check('def f():\n global x\n nonlocal x', 2, 3)
# Errors thrown by future.c
- check('from __future__ import doesnt_exist', 1, 1)
- check('from __future__ import braces', 1, 1)
+ check('from __future__ import doesnt_exist', 1, 24)
+ check('from __future__ import braces', 1, 24)
check('x=1\nfrom __future__ import division', 2, 1)
check('foo(1=2)', 1, 5)
check('def f():\n x, y: int', 2, 3)
@@ -328,6 +340,14 @@ def baz():
check('(yield i) = 2', 1, 2)
check('def f(*):\n pass', 1, 7)
+ @unittest.skipIf(INT_MAX >= sys.maxsize, "Downcasting to int is safe for col_offset")
+ @support.requires_resource('cpu')
+ @support.bigmemtest(INT_MAX, memuse=2, dry_run=False)
+ def testMemoryErrorBigSource(self, size):
+ src = b"if True:\n%*s" % (size, b"pass")
+ with self.assertRaisesRegex(OverflowError, "Parser column offset overflow"):
+ compile(src, '', 'exec')
+
@cpython_only
def testSettingException(self):
# test that setting an exception at the C level works even if the
@@ -340,24 +360,23 @@ def __init__(self_):
class InvalidException:
pass
+ @unittest.skipIf(_testcapi is None, "requires _testcapi")
def test_capi1():
- import _testcapi
try:
_testcapi.raise_exception(BadException, 1)
except TypeError as err:
- exc, err, tb = sys.exc_info()
- co = tb.tb_frame.f_code
+ co = err.__traceback__.tb_frame.f_code
self.assertEqual(co.co_name, "test_capi1")
self.assertTrue(co.co_filename.endswith('test_exceptions.py'))
else:
self.fail("Expected exception")
+ @unittest.skipIf(_testcapi is None, "requires _testcapi")
def test_capi2():
- import _testcapi
try:
_testcapi.raise_exception(BadException, 0)
except RuntimeError as err:
- exc, err, tb = sys.exc_info()
+ tb = err.__traceback__.tb_next
co = tb.tb_frame.f_code
self.assertEqual(co.co_name, "__init__")
self.assertTrue(co.co_filename.endswith('test_exceptions.py'))
@@ -366,15 +385,14 @@ def test_capi2():
else:
self.fail("Expected exception")
+ @unittest.skipIf(_testcapi is None, "requires _testcapi")
def test_capi3():
- import _testcapi
self.assertRaises(SystemError, _testcapi.raise_exception,
InvalidException, 1)
- if not sys.platform.startswith('java'):
- test_capi1()
- test_capi2()
- test_capi3()
+ test_capi1()
+ test_capi2()
+ test_capi3()
def test_WindowsError(self):
try:
@@ -431,45 +449,45 @@ def testAttributes(self):
# test that exception attributes are happy
exceptionList = [
- (BaseException, (), {'args' : ()}),
- (BaseException, (1, ), {'args' : (1,)}),
- (BaseException, ('foo',),
+ (BaseException, (), {}, {'args' : ()}),
+ (BaseException, (1, ), {}, {'args' : (1,)}),
+ (BaseException, ('foo',), {},
{'args' : ('foo',)}),
- (BaseException, ('foo', 1),
+ (BaseException, ('foo', 1), {},
{'args' : ('foo', 1)}),
- (SystemExit, ('foo',),
+ (SystemExit, ('foo',), {},
{'args' : ('foo',), 'code' : 'foo'}),
- (OSError, ('foo',),
+ (OSError, ('foo',), {},
{'args' : ('foo',), 'filename' : None, 'filename2' : None,
'errno' : None, 'strerror' : None}),
- (OSError, ('foo', 'bar'),
+ (OSError, ('foo', 'bar'), {},
{'args' : ('foo', 'bar'),
'filename' : None, 'filename2' : None,
'errno' : 'foo', 'strerror' : 'bar'}),
- (OSError, ('foo', 'bar', 'baz'),
+ (OSError, ('foo', 'bar', 'baz'), {},
{'args' : ('foo', 'bar'),
'filename' : 'baz', 'filename2' : None,
'errno' : 'foo', 'strerror' : 'bar'}),
- (OSError, ('foo', 'bar', 'baz', None, 'quux'),
+ (OSError, ('foo', 'bar', 'baz', None, 'quux'), {},
{'args' : ('foo', 'bar'), 'filename' : 'baz', 'filename2': 'quux'}),
- (OSError, ('errnoStr', 'strErrorStr', 'filenameStr'),
+ (OSError, ('errnoStr', 'strErrorStr', 'filenameStr'), {},
{'args' : ('errnoStr', 'strErrorStr'),
'strerror' : 'strErrorStr', 'errno' : 'errnoStr',
'filename' : 'filenameStr'}),
- (OSError, (1, 'strErrorStr', 'filenameStr'),
+ (OSError, (1, 'strErrorStr', 'filenameStr'), {},
{'args' : (1, 'strErrorStr'), 'errno' : 1,
'strerror' : 'strErrorStr',
'filename' : 'filenameStr', 'filename2' : None}),
- (SyntaxError, (), {'msg' : None, 'text' : None,
+ (SyntaxError, (), {}, {'msg' : None, 'text' : None,
'filename' : None, 'lineno' : None, 'offset' : None,
'end_offset': None, 'print_file_and_line' : None}),
- (SyntaxError, ('msgStr',),
+ (SyntaxError, ('msgStr',), {},
{'args' : ('msgStr',), 'text' : None,
'print_file_and_line' : None, 'msg' : 'msgStr',
'filename' : None, 'lineno' : None, 'offset' : None,
'end_offset': None}),
(SyntaxError, ('msgStr', ('filenameStr', 'linenoStr', 'offsetStr',
- 'textStr', 'endLinenoStr', 'endOffsetStr')),
+ 'textStr', 'endLinenoStr', 'endOffsetStr')), {},
{'offset' : 'offsetStr', 'text' : 'textStr',
'args' : ('msgStr', ('filenameStr', 'linenoStr',
'offsetStr', 'textStr',
@@ -479,7 +497,7 @@ def testAttributes(self):
'end_lineno': 'endLinenoStr', 'end_offset': 'endOffsetStr'}),
(SyntaxError, ('msgStr', 'filenameStr', 'linenoStr', 'offsetStr',
'textStr', 'endLinenoStr', 'endOffsetStr',
- 'print_file_and_lineStr'),
+ 'print_file_and_lineStr'), {},
{'text' : None,
'args' : ('msgStr', 'filenameStr', 'linenoStr', 'offsetStr',
'textStr', 'endLinenoStr', 'endOffsetStr',
@@ -487,38 +505,40 @@ def testAttributes(self):
'print_file_and_line' : None, 'msg' : 'msgStr',
'filename' : None, 'lineno' : None, 'offset' : None,
'end_lineno': None, 'end_offset': None}),
- (UnicodeError, (), {'args' : (),}),
+ (UnicodeError, (), {}, {'args' : (),}),
(UnicodeEncodeError, ('ascii', 'a', 0, 1,
- 'ordinal not in range'),
+ 'ordinal not in range'), {},
{'args' : ('ascii', 'a', 0, 1,
'ordinal not in range'),
'encoding' : 'ascii', 'object' : 'a',
'start' : 0, 'reason' : 'ordinal not in range'}),
(UnicodeDecodeError, ('ascii', bytearray(b'\xff'), 0, 1,
- 'ordinal not in range'),
+ 'ordinal not in range'), {},
{'args' : ('ascii', bytearray(b'\xff'), 0, 1,
'ordinal not in range'),
'encoding' : 'ascii', 'object' : b'\xff',
'start' : 0, 'reason' : 'ordinal not in range'}),
(UnicodeDecodeError, ('ascii', b'\xff', 0, 1,
- 'ordinal not in range'),
+ 'ordinal not in range'), {},
{'args' : ('ascii', b'\xff', 0, 1,
'ordinal not in range'),
'encoding' : 'ascii', 'object' : b'\xff',
'start' : 0, 'reason' : 'ordinal not in range'}),
- (UnicodeTranslateError, ("\u3042", 0, 1, "ouch"),
+ (UnicodeTranslateError, ("\u3042", 0, 1, "ouch"), {},
{'args' : ('\u3042', 0, 1, 'ouch'),
'object' : '\u3042', 'reason' : 'ouch',
'start' : 0, 'end' : 1}),
- (NaiveException, ('foo',),
+ (NaiveException, ('foo',), {},
{'args': ('foo',), 'x': 'foo'}),
- (SlottedNaiveException, ('foo',),
+ (SlottedNaiveException, ('foo',), {},
{'args': ('foo',), 'x': 'foo'}),
+ (AttributeError, ('foo',), dict(name='name', obj='obj'),
+ dict(args=('foo',), name='name', obj='obj')),
]
try:
# More tests are in test_WindowsError
exceptionList.append(
- (WindowsError, (1, 'strErrorStr', 'filenameStr'),
+ (WindowsError, (1, 'strErrorStr', 'filenameStr'), {},
{'args' : (1, 'strErrorStr'),
'strerror' : 'strErrorStr', 'winerror' : None,
'errno' : 1,
@@ -527,11 +547,11 @@ def testAttributes(self):
except NameError:
pass
- for exc, args, expected in exceptionList:
+ for exc, args, kwargs, expected in exceptionList:
try:
- e = exc(*args)
+ e = exc(*args, **kwargs)
except:
- print("\nexc=%r, args=%r" % (exc, args), file=sys.stderr)
+ print(f"\nexc={exc!r}, args={args!r}", file=sys.stderr)
# raise
else:
# Verify module name
@@ -554,11 +574,39 @@ def testAttributes(self):
new = p.loads(s)
for checkArgName in expected:
got = repr(getattr(new, checkArgName))
- want = repr(expected[checkArgName])
+ if exc == AttributeError and checkArgName == 'obj':
+ # See GH-103352, we're not pickling
+ # obj at this point. So verify it's None.
+ want = repr(None)
+ else:
+ want = repr(expected[checkArgName])
self.assertEqual(got, want,
'pickled "%r", attribute "%s' %
(e, checkArgName))
+ def test_setstate(self):
+ e = Exception(42)
+ e.blah = 53
+ self.assertEqual(e.args, (42,))
+ self.assertEqual(e.blah, 53)
+ self.assertRaises(AttributeError, getattr, e, 'a')
+ self.assertRaises(AttributeError, getattr, e, 'b')
+ e.__setstate__({'a': 1 , 'b': 2})
+ self.assertEqual(e.args, (42,))
+ self.assertEqual(e.blah, 53)
+ self.assertEqual(e.a, 1)
+ self.assertEqual(e.b, 2)
+ e.__setstate__({'a': 11, 'args': (1,2,3), 'blah': 35})
+ self.assertEqual(e.args, (1,2,3))
+ self.assertEqual(e.blah, 35)
+ self.assertEqual(e.a, 11)
+ self.assertEqual(e.b, 2)
+
+ def test_invalid_setstate(self):
+ e = Exception(42)
+ with self.assertRaisesRegex(TypeError, "state is not a dictionary"):
+ e.__setstate__(42)
+
# TODO: RUSTPYTHON
@unittest.expectedFailure
def test_notes(self):
@@ -591,8 +639,8 @@ def test_notes(self):
def testWithTraceback(self):
try:
raise IndexError(4)
- except:
- tb = sys.exc_info()[2]
+ except Exception as e:
+ tb = e.__traceback__
e = BaseException().with_traceback(tb)
self.assertIsInstance(e, BaseException)
@@ -609,8 +657,6 @@ class MyException(Exception):
self.assertIsInstance(e, MyException)
self.assertEqual(e.__traceback__, tb)
- # TODO: RUSTPYTHON
- @unittest.expectedFailure
def testInvalidTraceback(self):
try:
Exception().__traceback__ = 5
@@ -619,17 +665,40 @@ def testInvalidTraceback(self):
else:
self.fail("No exception raised")
- def testInvalidAttrs(self):
- self.assertRaises(TypeError, setattr, Exception(), '__cause__', 1)
- self.assertRaises(TypeError, delattr, Exception(), '__cause__')
- self.assertRaises(TypeError, setattr, Exception(), '__context__', 1)
- self.assertRaises(TypeError, delattr, Exception(), '__context__')
+ # TODO: RUSTPYTHON
+ @unittest.expectedFailure
+ def test_invalid_setattr(self):
+ TE = TypeError
+ exc = Exception()
+ msg = "'int' object is not iterable"
+ self.assertRaisesRegex(TE, msg, setattr, exc, 'args', 1)
+ msg = "__traceback__ must be a traceback or None"
+ self.assertRaisesRegex(TE, msg, setattr, exc, '__traceback__', 1)
+ msg = "exception cause must be None or derive from BaseException"
+ self.assertRaisesRegex(TE, msg, setattr, exc, '__cause__', 1)
+ msg = "exception context must be None or derive from BaseException"
+ self.assertRaisesRegex(TE, msg, setattr, exc, '__context__', 1)
+
+ # TODO: RUSTPYTHON
+ @unittest.expectedFailure
+ def test_invalid_delattr(self):
+ TE = TypeError
+ try:
+ raise IndexError(4)
+ except Exception as e:
+ exc = e
+
+ msg = "may not be deleted"
+ self.assertRaisesRegex(TE, msg, delattr, exc, 'args')
+ self.assertRaisesRegex(TE, msg, delattr, exc, '__traceback__')
+ self.assertRaisesRegex(TE, msg, delattr, exc, '__cause__')
+ self.assertRaisesRegex(TE, msg, delattr, exc, '__context__')
def testNoneClearsTracebackAttr(self):
try:
raise IndexError(4)
- except:
- tb = sys.exc_info()[2]
+ except Exception as e:
+ tb = e.__traceback__
e = Exception()
e.__traceback__ = tb
@@ -703,8 +772,6 @@ def test_str(self):
self.assertTrue(str(Exception('a')))
self.assertTrue(str(Exception('a', 'b')))
- # TODO: RUSTPYTHON
- @unittest.expectedFailure
def test_exception_cleanup_names(self):
# Make sure the local variable bound to the exception instance by
# an "except" statement is only visible inside the except block.
@@ -727,8 +794,6 @@ def test_exception_cleanup_names2(self):
with self.assertRaises(UnboundLocalError):
e
- # TODO: RUSTPYTHON
- @unittest.expectedFailure
def testExceptionCleanupState(self):
# Make sure exception state is cleaned up as soon as the except
# block is left. See #2507
@@ -868,28 +933,28 @@ def yield_raise():
try:
raise KeyError("caught")
except KeyError:
- yield sys.exc_info()[0]
- yield sys.exc_info()[0]
- yield sys.exc_info()[0]
+ yield sys.exception()
+ yield sys.exception()
+ yield sys.exception()
g = yield_raise()
- self.assertEqual(next(g), KeyError)
- self.assertEqual(sys.exc_info()[0], None)
- self.assertEqual(next(g), KeyError)
- self.assertEqual(sys.exc_info()[0], None)
- self.assertEqual(next(g), None)
+ self.assertIsInstance(next(g), KeyError)
+ self.assertIsNone(sys.exception())
+ self.assertIsInstance(next(g), KeyError)
+ self.assertIsNone(sys.exception())
+ self.assertIsNone(next(g))
# Same test, but inside an exception handler
try:
raise TypeError("foo")
except TypeError:
g = yield_raise()
- self.assertEqual(next(g), KeyError)
- self.assertEqual(sys.exc_info()[0], TypeError)
- self.assertEqual(next(g), KeyError)
- self.assertEqual(sys.exc_info()[0], TypeError)
- self.assertEqual(next(g), TypeError)
+ self.assertIsInstance(next(g), KeyError)
+ self.assertIsInstance(sys.exception(), TypeError)
+ self.assertIsInstance(next(g), KeyError)
+ self.assertIsInstance(sys.exception(), TypeError)
+ self.assertIsInstance(next(g), TypeError)
del g
- self.assertEqual(sys.exc_info()[0], TypeError)
+ self.assertIsInstance(sys.exception(), TypeError)
def test_generator_leaking2(self):
# See issue 12475.
@@ -904,7 +969,7 @@ def g():
next(it)
except StopIteration:
pass
- self.assertEqual(sys.exc_info(), (None, None, None))
+ self.assertIsNone(sys.exception())
def test_generator_leaking3(self):
# See issue #23353. When gen.throw() is called, the caller's
@@ -913,17 +978,17 @@ def g():
try:
yield
except ZeroDivisionError:
- yield sys.exc_info()[1]
+ yield sys.exception()
it = g()
next(it)
try:
1/0
except ZeroDivisionError as e:
- self.assertIs(sys.exc_info()[1], e)
+ self.assertIs(sys.exception(), e)
gen_exc = it.throw(e)
- self.assertIs(sys.exc_info()[1], e)
+ self.assertIs(sys.exception(), e)
self.assertIs(gen_exc, e)
- self.assertEqual(sys.exc_info(), (None, None, None))
+ self.assertIsNone(sys.exception())
def test_generator_leaking4(self):
# See issue #23353. When an exception is raised by a generator,
@@ -932,7 +997,7 @@ def g():
try:
1/0
except ZeroDivisionError:
- yield sys.exc_info()[0]
+ yield sys.exception()
raise
it = g()
try:
@@ -940,7 +1005,7 @@ def g():
except TypeError:
# The caller's exception state (TypeError) is temporarily
# saved in the generator.
- tp = next(it)
+ tp = type(next(it))
self.assertIs(tp, ZeroDivisionError)
try:
next(it)
@@ -948,15 +1013,15 @@ def g():
# with an exception, it shouldn't have restored the old
# exception state (TypeError).
except ZeroDivisionError as e:
- self.assertIs(sys.exc_info()[1], e)
+ self.assertIs(sys.exception(), e)
# We used to find TypeError here.
- self.assertEqual(sys.exc_info(), (None, None, None))
+ self.assertIsNone(sys.exception())
def test_generator_doesnt_retain_old_exc(self):
def g():
- self.assertIsInstance(sys.exc_info()[1], RuntimeError)
+ self.assertIsInstance(sys.exception(), RuntimeError)
yield
- self.assertEqual(sys.exc_info(), (None, None, None))
+ self.assertIsNone(sys.exception())
it = g()
try:
raise RuntimeError
@@ -964,7 +1029,7 @@ def g():
next(it)
self.assertRaises(StopIteration, next, it)
- def test_generator_finalizing_and_exc_info(self):
+ def test_generator_finalizing_and_sys_exception(self):
# See #7173
def simple_gen():
yield 1
@@ -976,7 +1041,7 @@ def run_gen():
return next(gen)
run_gen()
gc_collect()
- self.assertEqual(sys.exc_info(), (None, None, None))
+ self.assertIsNone(sys.exception())
def _check_generator_cleanup_exc_state(self, testfunc):
# Issue #12791: exception state is cleaned up as soon as a generator
@@ -1047,14 +1112,14 @@ def test_3114(self):
class MyObject:
def __del__(self):
nonlocal e
- e = sys.exc_info()
+ e = sys.exception()
e = ()
try:
raise Exception(MyObject())
except:
pass
gc_collect() # For PyPy or other GCs.
- self.assertEqual(e, (None, None, None))
+ self.assertIsNone(e)
def test_raise_does_not_create_context_chain_cycle(self):
class A(Exception):
@@ -1096,7 +1161,6 @@ class C(Exception):
self.assertIs(c.__context__, b)
self.assertIsNone(b.__context__)
-
# TODO: RUSTPYTHON
@unittest.skip("Infinite loop")
def test_no_hang_on_context_chain_cycle1(self):
@@ -1118,7 +1182,6 @@ def cycle():
self.assertIsInstance(exc.__context__, ValueError)
self.assertIs(exc.__context__.__context__, exc.__context__)
- @unittest.skip("See issue 44895")
def test_no_hang_on_context_chain_cycle2(self):
# See issue 25782. Cycle at head of context chain.
@@ -1299,6 +1362,31 @@ def test_unicode_errors_no_object(self):
for klass in klasses:
self.assertEqual(str(klass.__new__(klass)), "")
+ # TODO: RUSTPYTHON; OverflowError: Python int too large to convert to Rust usize
+ @unittest.expectedFailure
+ def test_unicode_error_str_does_not_crash(self):
+ # Test that str(UnicodeError(...)) does not crash.
+ # See https://github.com/python/cpython/issues/123378.
+
+ for start, end, objlen in product(
+ range(-5, 5),
+ range(-5, 5),
+ range(7),
+ ):
+ obj = 'a' * objlen
+ with self.subTest('encode', objlen=objlen, start=start, end=end):
+ exc = UnicodeEncodeError('utf-8', obj, start, end, '')
+ self.assertIsInstance(str(exc), str)
+
+ with self.subTest('translate', objlen=objlen, start=start, end=end):
+ exc = UnicodeTranslateError(obj, start, end, '')
+ self.assertIsInstance(str(exc), str)
+
+ encoded = obj.encode()
+ with self.subTest('decode', objlen=objlen, start=start, end=end):
+ exc = UnicodeDecodeError('utf-8', encoded, start, end, '')
+ self.assertIsInstance(str(exc), str)
+
@no_tracing
@unittest.skipIf(sys.platform == 'win32', 'TODO: RUSTPYTHON Windows')
def test_badisinstance(self):
@@ -1325,14 +1413,15 @@ class MyException(Exception, metaclass=Meta):
def g():
try:
return g()
- except RecursionError:
- return sys.exc_info()
- e, v, tb = g()
- self.assertIsInstance(v, RecursionError, type(v))
- self.assertIn("maximum recursion depth exceeded", str(v))
+ except RecursionError as e:
+ return e
+ exc = g()
+ self.assertIsInstance(exc, RecursionError, type(exc))
+ self.assertIn("maximum recursion depth exceeded", str(exc))
@cpython_only
+ @support.requires_resource('cpu')
def test_trashcan_recursion(self):
# See bpo-33930
@@ -1348,6 +1437,7 @@ def foo():
@cpython_only
def test_recursion_normalizing_exception(self):
+ import_module("_testinternalcapi")
# Issue #22898.
# Test that a RecursionError is raised when tstate->recursion_depth is
# equal to recursion_limit in PyErr_NormalizeException() and check
@@ -1360,6 +1450,7 @@ def test_recursion_normalizing_exception(self):
code = """if 1:
import sys
from _testinternalcapi import get_recursion_depth
+ from test import support
class MyException(Exception): pass
@@ -1387,13 +1478,8 @@ def gen():
generator = gen()
next(generator)
recursionlimit = sys.getrecursionlimit()
- depth = get_recursion_depth()
try:
- # Upon the last recursive invocation of recurse(),
- # tstate->recursion_depth is equal to (recursion_limit - 1)
- # and is equal to recursion_limit when _gen_throw() calls
- # PyErr_NormalizeException().
- recurse(setrecursionlimit(depth + 2) - depth)
+ recurse(support.exceeds_recursion_limit())
finally:
sys.setrecursionlimit(recursionlimit)
print('Done.')
@@ -1406,10 +1492,12 @@ def gen():
self.assertIn(b'Done.', out)
@cpython_only
+ @unittest.skipIf(_testcapi is None, "requires _testcapi")
+ @force_not_colorized
def test_recursion_normalizing_infinite_exception(self):
# Issue #30697. Test that a RecursionError is raised when
- # PyErr_NormalizeException() maximum recursion depth has been
- # exceeded.
+ # maximum recursion depth has been exceeded when creating
+ # an exception
code = """if 1:
import _testcapi
try:
@@ -1419,8 +1507,8 @@ def test_recursion_normalizing_infinite_exception(self):
"""
rc, out, err = script_helper.assert_python_failure("-c", code)
self.assertEqual(rc, 1)
- self.assertIn(b'RecursionError: maximum recursion depth exceeded '
- b'while normalizing an exception', err)
+ expected = b'RecursionError: maximum recursion depth exceeded'
+ self.assertTrue(expected in err, msg=f"{expected!r} not found in {err[:3_000]!r}... (truncated)")
self.assertIn(b'Done.', out)
@@ -1472,6 +1560,10 @@ def recurse_in_body_and_except():
@cpython_only
+ # Python built with Py_TRACE_REFS fail with a fatal error in
+ # _PyRefchain_Trace() on memory allocation error.
+ @unittest.skipIf(support.Py_TRACE_REFS, 'cannot test Py_TRACE_REFS build')
+ @unittest.skipIf(_testcapi is None, "requires _testcapi")
def test_recursion_normalizing_with_no_memory(self):
# Issue #30697. Test that in the abort that occurs when there is no
# memory left and the size of the Python frames stack is greater than
@@ -1494,6 +1586,7 @@ def recurse(cnt):
self.assertIn(b'MemoryError', err)
@cpython_only
+ @unittest.skipIf(_testcapi is None, "requires _testcapi")
def test_MemoryError(self):
# PyErr_NoMemory always raises the same exception instance.
# Check that the traceback is not doubled.
@@ -1513,8 +1606,8 @@ def raiseMemError():
self.assertEqual(tb1, tb2)
@cpython_only
+ @unittest.skipIf(_testcapi is None, "requires _testcapi")
def test_exception_with_doc(self):
- import _testcapi
doc2 = "This is a test docstring."
doc4 = "This is another test docstring."
@@ -1553,6 +1646,7 @@ class C(object):
self.assertEqual(error5.__doc__, "")
@cpython_only
+ @unittest.skipIf(_testcapi is None, "requires _testcapi")
def test_memory_error_cleanup(self):
# Issue #5437: preallocated MemoryError instances should not keep
# traceback objects alive.
@@ -1575,10 +1669,8 @@ def inner():
gc_collect() # For PyPy or other GCs.
self.assertEqual(wr(), None)
- # TODO: RUSTPYTHON
- @unittest.expectedFailure
- @no_tracing
@unittest.skipIf(sys.platform == 'win32', 'TODO: RUSTPYTHON Windows')
+ @no_tracing
def test_recursion_error_cleanup(self):
# Same test as above, but with "recursion exceeded" errors
class C:
@@ -1646,6 +1738,10 @@ def test_unhandled(self):
self.assertTrue(report.endswith("\n"))
@cpython_only
+ # Python built with Py_TRACE_REFS fail with a fatal error in
+ # _PyRefchain_Trace() on memory allocation error.
+ @unittest.skipIf(support.Py_TRACE_REFS, 'cannot test Py_TRACE_REFS build')
+ @unittest.skipIf(_testcapi is None, "requires _testcapi")
def test_memory_error_in_PyErr_PrintEx(self):
code = """if 1:
import _testcapi
@@ -1692,7 +1788,7 @@ def g():
raise ValueError
except ValueError:
yield 1
- self.assertEqual(sys.exc_info(), (None, None, None))
+ self.assertIsNone(sys.exception())
yield 2
gen = g()
@@ -1766,7 +1862,21 @@ class TestException(MemoryError):
gc_collect()
-global_for_suggestions = None
+ @unittest.skipIf(_testcapi is None, "requires _testcapi")
+ def test_memory_error_in_subinterp(self):
+ # gh-109894: subinterpreters shouldn't count on last resort memory error
+ # when MemoryError is raised through PyErr_NoMemory() call,
+ # and should preallocate memory errors as does the main interpreter.
+ # interp.static_objects.last_resort_memory_error.args
+ # should be initialized to empty tuple to avoid crash on attempt to print it.
+ code = f"""if 1:
+ import _testcapi
+ _testcapi.run_in_subinterp(\"[0]*{sys.maxsize}\")
+ exit(0)
+ """
+ rc, _, err = script_helper.assert_python_ok("-c", code)
+ self.assertIn(b'MemoryError', err)
+
class NameErrorTests(unittest.TestCase):
def test_name_error_has_name(self):
@@ -1775,272 +1885,6 @@ def test_name_error_has_name(self):
except NameError as exc:
self.assertEqual("bluch", exc.name)
- def test_name_error_suggestions(self):
- def Substitution():
- noise = more_noise = a = bc = None
- blech = None
- print(bluch)
-
- def Elimination():
- noise = more_noise = a = bc = None
- blch = None
- print(bluch)
-
- def Addition():
- noise = more_noise = a = bc = None
- bluchin = None
- print(bluch)
-
- def SubstitutionOverElimination():
- blach = None
- bluc = None
- print(bluch)
-
- def SubstitutionOverAddition():
- blach = None
- bluchi = None
- print(bluch)
-
- def EliminationOverAddition():
- blucha = None
- bluc = None
- print(bluch)
-
- for func, suggestion in [(Substitution, "'blech'?"),
- (Elimination, "'blch'?"),
- (Addition, "'bluchin'?"),
- (EliminationOverAddition, "'blucha'?"),
- (SubstitutionOverElimination, "'blach'?"),
- (SubstitutionOverAddition, "'blach'?")]:
- err = None
- try:
- func()
- except NameError as exc:
- with support.captured_stderr() as err:
- sys.__excepthook__(*sys.exc_info())
- self.assertIn(suggestion, err.getvalue())
-
- def test_name_error_suggestions_from_globals(self):
- def func():
- print(global_for_suggestio)
- try:
- func()
- except NameError as exc:
- with support.captured_stderr() as err:
- sys.__excepthook__(*sys.exc_info())
- self.assertIn("'global_for_suggestions'?", err.getvalue())
-
- def test_name_error_suggestions_from_builtins(self):
- def func():
- print(ZeroDivisionErrrrr)
- try:
- func()
- except NameError as exc:
- with support.captured_stderr() as err:
- sys.__excepthook__(*sys.exc_info())
- self.assertIn("'ZeroDivisionError'?", err.getvalue())
-
- def test_name_error_suggestions_do_not_trigger_for_long_names(self):
- def f():
- somethingverywronghehehehehehe = None
- print(somethingverywronghe)
-
- try:
- f()
- except NameError as exc:
- with support.captured_stderr() as err:
- sys.__excepthook__(*sys.exc_info())
-
- self.assertNotIn("somethingverywronghehe", err.getvalue())
-
- def test_name_error_bad_suggestions_do_not_trigger_for_small_names(self):
- vvv = mom = w = id = pytho = None
-
- with self.subTest(name="b"):
- try:
- b
- except NameError as exc:
- with support.captured_stderr() as err:
- sys.__excepthook__(*sys.exc_info())
- self.assertNotIn("you mean", err.getvalue())
- self.assertNotIn("vvv", err.getvalue())
- self.assertNotIn("mom", err.getvalue())
- self.assertNotIn("'id'", err.getvalue())
- self.assertNotIn("'w'", err.getvalue())
- self.assertNotIn("'pytho'", err.getvalue())
-
- with self.subTest(name="v"):
- try:
- v
- except NameError as exc:
- with support.captured_stderr() as err:
- sys.__excepthook__(*sys.exc_info())
- self.assertNotIn("you mean", err.getvalue())
- self.assertNotIn("vvv", err.getvalue())
- self.assertNotIn("mom", err.getvalue())
- self.assertNotIn("'id'", err.getvalue())
- self.assertNotIn("'w'", err.getvalue())
- self.assertNotIn("'pytho'", err.getvalue())
-
- with self.subTest(name="m"):
- try:
- m
- except NameError as exc:
- with support.captured_stderr() as err:
- sys.__excepthook__(*sys.exc_info())
- self.assertNotIn("you mean", err.getvalue())
- self.assertNotIn("vvv", err.getvalue())
- self.assertNotIn("mom", err.getvalue())
- self.assertNotIn("'id'", err.getvalue())
- self.assertNotIn("'w'", err.getvalue())
- self.assertNotIn("'pytho'", err.getvalue())
-
- with self.subTest(name="py"):
- try:
- py
- except NameError as exc:
- with support.captured_stderr() as err:
- sys.__excepthook__(*sys.exc_info())
- self.assertNotIn("you mean", err.getvalue())
- self.assertNotIn("vvv", err.getvalue())
- self.assertNotIn("mom", err.getvalue())
- self.assertNotIn("'id'", err.getvalue())
- self.assertNotIn("'w'", err.getvalue())
- self.assertNotIn("'pytho'", err.getvalue())
-
- def test_name_error_suggestions_do_not_trigger_for_too_many_locals(self):
- def f():
- # Mutating locals() is unreliable, so we need to do it by hand
- a1 = a2 = a3 = a4 = a5 = a6 = a7 = a8 = a9 = a10 = \
- a11 = a12 = a13 = a14 = a15 = a16 = a17 = a18 = a19 = a20 = \
- a21 = a22 = a23 = a24 = a25 = a26 = a27 = a28 = a29 = a30 = \
- a31 = a32 = a33 = a34 = a35 = a36 = a37 = a38 = a39 = a40 = \
- a41 = a42 = a43 = a44 = a45 = a46 = a47 = a48 = a49 = a50 = \
- a51 = a52 = a53 = a54 = a55 = a56 = a57 = a58 = a59 = a60 = \
- a61 = a62 = a63 = a64 = a65 = a66 = a67 = a68 = a69 = a70 = \
- a71 = a72 = a73 = a74 = a75 = a76 = a77 = a78 = a79 = a80 = \
- a81 = a82 = a83 = a84 = a85 = a86 = a87 = a88 = a89 = a90 = \
- a91 = a92 = a93 = a94 = a95 = a96 = a97 = a98 = a99 = a100 = \
- a101 = a102 = a103 = a104 = a105 = a106 = a107 = a108 = a109 = a110 = \
- a111 = a112 = a113 = a114 = a115 = a116 = a117 = a118 = a119 = a120 = \
- a121 = a122 = a123 = a124 = a125 = a126 = a127 = a128 = a129 = a130 = \
- a131 = a132 = a133 = a134 = a135 = a136 = a137 = a138 = a139 = a140 = \
- a141 = a142 = a143 = a144 = a145 = a146 = a147 = a148 = a149 = a150 = \
- a151 = a152 = a153 = a154 = a155 = a156 = a157 = a158 = a159 = a160 = \
- a161 = a162 = a163 = a164 = a165 = a166 = a167 = a168 = a169 = a170 = \
- a171 = a172 = a173 = a174 = a175 = a176 = a177 = a178 = a179 = a180 = \
- a181 = a182 = a183 = a184 = a185 = a186 = a187 = a188 = a189 = a190 = \
- a191 = a192 = a193 = a194 = a195 = a196 = a197 = a198 = a199 = a200 = \
- a201 = a202 = a203 = a204 = a205 = a206 = a207 = a208 = a209 = a210 = \
- a211 = a212 = a213 = a214 = a215 = a216 = a217 = a218 = a219 = a220 = \
- a221 = a222 = a223 = a224 = a225 = a226 = a227 = a228 = a229 = a230 = \
- a231 = a232 = a233 = a234 = a235 = a236 = a237 = a238 = a239 = a240 = \
- a241 = a242 = a243 = a244 = a245 = a246 = a247 = a248 = a249 = a250 = \
- a251 = a252 = a253 = a254 = a255 = a256 = a257 = a258 = a259 = a260 = \
- a261 = a262 = a263 = a264 = a265 = a266 = a267 = a268 = a269 = a270 = \
- a271 = a272 = a273 = a274 = a275 = a276 = a277 = a278 = a279 = a280 = \
- a281 = a282 = a283 = a284 = a285 = a286 = a287 = a288 = a289 = a290 = \
- a291 = a292 = a293 = a294 = a295 = a296 = a297 = a298 = a299 = a300 = \
- a301 = a302 = a303 = a304 = a305 = a306 = a307 = a308 = a309 = a310 = \
- a311 = a312 = a313 = a314 = a315 = a316 = a317 = a318 = a319 = a320 = \
- a321 = a322 = a323 = a324 = a325 = a326 = a327 = a328 = a329 = a330 = \
- a331 = a332 = a333 = a334 = a335 = a336 = a337 = a338 = a339 = a340 = \
- a341 = a342 = a343 = a344 = a345 = a346 = a347 = a348 = a349 = a350 = \
- a351 = a352 = a353 = a354 = a355 = a356 = a357 = a358 = a359 = a360 = \
- a361 = a362 = a363 = a364 = a365 = a366 = a367 = a368 = a369 = a370 = \
- a371 = a372 = a373 = a374 = a375 = a376 = a377 = a378 = a379 = a380 = \
- a381 = a382 = a383 = a384 = a385 = a386 = a387 = a388 = a389 = a390 = \
- a391 = a392 = a393 = a394 = a395 = a396 = a397 = a398 = a399 = a400 = \
- a401 = a402 = a403 = a404 = a405 = a406 = a407 = a408 = a409 = a410 = \
- a411 = a412 = a413 = a414 = a415 = a416 = a417 = a418 = a419 = a420 = \
- a421 = a422 = a423 = a424 = a425 = a426 = a427 = a428 = a429 = a430 = \
- a431 = a432 = a433 = a434 = a435 = a436 = a437 = a438 = a439 = a440 = \
- a441 = a442 = a443 = a444 = a445 = a446 = a447 = a448 = a449 = a450 = \
- a451 = a452 = a453 = a454 = a455 = a456 = a457 = a458 = a459 = a460 = \
- a461 = a462 = a463 = a464 = a465 = a466 = a467 = a468 = a469 = a470 = \
- a471 = a472 = a473 = a474 = a475 = a476 = a477 = a478 = a479 = a480 = \
- a481 = a482 = a483 = a484 = a485 = a486 = a487 = a488 = a489 = a490 = \
- a491 = a492 = a493 = a494 = a495 = a496 = a497 = a498 = a499 = a500 = \
- a501 = a502 = a503 = a504 = a505 = a506 = a507 = a508 = a509 = a510 = \
- a511 = a512 = a513 = a514 = a515 = a516 = a517 = a518 = a519 = a520 = \
- a521 = a522 = a523 = a524 = a525 = a526 = a527 = a528 = a529 = a530 = \
- a531 = a532 = a533 = a534 = a535 = a536 = a537 = a538 = a539 = a540 = \
- a541 = a542 = a543 = a544 = a545 = a546 = a547 = a548 = a549 = a550 = \
- a551 = a552 = a553 = a554 = a555 = a556 = a557 = a558 = a559 = a560 = \
- a561 = a562 = a563 = a564 = a565 = a566 = a567 = a568 = a569 = a570 = \
- a571 = a572 = a573 = a574 = a575 = a576 = a577 = a578 = a579 = a580 = \
- a581 = a582 = a583 = a584 = a585 = a586 = a587 = a588 = a589 = a590 = \
- a591 = a592 = a593 = a594 = a595 = a596 = a597 = a598 = a599 = a600 = \
- a601 = a602 = a603 = a604 = a605 = a606 = a607 = a608 = a609 = a610 = \
- a611 = a612 = a613 = a614 = a615 = a616 = a617 = a618 = a619 = a620 = \
- a621 = a622 = a623 = a624 = a625 = a626 = a627 = a628 = a629 = a630 = \
- a631 = a632 = a633 = a634 = a635 = a636 = a637 = a638 = a639 = a640 = \
- a641 = a642 = a643 = a644 = a645 = a646 = a647 = a648 = a649 = a650 = \
- a651 = a652 = a653 = a654 = a655 = a656 = a657 = a658 = a659 = a660 = \
- a661 = a662 = a663 = a664 = a665 = a666 = a667 = a668 = a669 = a670 = \
- a671 = a672 = a673 = a674 = a675 = a676 = a677 = a678 = a679 = a680 = \
- a681 = a682 = a683 = a684 = a685 = a686 = a687 = a688 = a689 = a690 = \
- a691 = a692 = a693 = a694 = a695 = a696 = a697 = a698 = a699 = a700 = \
- a701 = a702 = a703 = a704 = a705 = a706 = a707 = a708 = a709 = a710 = \
- a711 = a712 = a713 = a714 = a715 = a716 = a717 = a718 = a719 = a720 = \
- a721 = a722 = a723 = a724 = a725 = a726 = a727 = a728 = a729 = a730 = \
- a731 = a732 = a733 = a734 = a735 = a736 = a737 = a738 = a739 = a740 = \
- a741 = a742 = a743 = a744 = a745 = a746 = a747 = a748 = a749 = a750 = \
- a751 = a752 = a753 = a754 = a755 = a756 = a757 = a758 = a759 = a760 = \
- a761 = a762 = a763 = a764 = a765 = a766 = a767 = a768 = a769 = a770 = \
- a771 = a772 = a773 = a774 = a775 = a776 = a777 = a778 = a779 = a780 = \
- a781 = a782 = a783 = a784 = a785 = a786 = a787 = a788 = a789 = a790 = \
- a791 = a792 = a793 = a794 = a795 = a796 = a797 = a798 = a799 = a800 \
- = None
- print(a0)
-
- try:
- f()
- except NameError as exc:
- with support.captured_stderr() as err:
- sys.__excepthook__(*sys.exc_info())
-
- self.assertNotRegex(err.getvalue(), r"NameError.*a1")
-
- def test_name_error_with_custom_exceptions(self):
- def f():
- blech = None
- raise NameError()
-
- try:
- f()
- except NameError as exc:
- with support.captured_stderr() as err:
- sys.__excepthook__(*sys.exc_info())
-
- self.assertNotIn("blech", err.getvalue())
-
- def f():
- blech = None
- raise NameError
-
- try:
- f()
- except NameError as exc:
- with support.captured_stderr() as err:
- sys.__excepthook__(*sys.exc_info())
-
- self.assertNotIn("blech", err.getvalue())
-
- def test_unbound_local_error_doesn_not_match(self):
- def foo():
- something = 3
- print(somethong)
- somethong = 3
-
- try:
- foo()
- except UnboundLocalError as exc:
- with support.captured_stderr() as err:
- sys.__excepthook__(*sys.exc_info())
-
- self.assertNotIn("something", err.getvalue())
-
def test_issue45826(self):
# regression test for bpo-45826
def f():
@@ -2052,6 +1896,8 @@ def f():
except self.failureException:
with support.captured_stderr() as err:
sys.__excepthook__(*sys.exc_info())
+ else:
+ self.fail("assertRaisesRegex should have failed.")
self.assertIn("aab", err.getvalue())
@@ -2072,10 +1918,17 @@ def f():
self.assertIn("nonsense", err.getvalue())
self.assertIn("ZeroDivisionError", err.getvalue())
+ def test_gh_111654(self):
+ def f():
+ class TestClass:
+ TestClass
+
+ self.assertRaises(NameError, f)
+
+ # Note: name suggestion tests live in `test_traceback`.
+
class AttributeErrorTests(unittest.TestCase):
- # TODO: RUSTPYTHON
- @unittest.expectedFailure
def test_attributes(self):
# Setting 'attr' should not be a problem.
exc = AttributeError('Ouch!')
@@ -2087,8 +1940,6 @@ def test_attributes(self):
self.assertEqual(exc.name, 'carry')
self.assertIs(exc.obj, sentinel)
- # TODO: RUSTPYTHON
- @unittest.expectedFailure
def test_getattr_has_name_and_obj(self):
class A:
blech = None
@@ -2117,244 +1968,11 @@ def blech(self):
self.assertEqual("bluch", exc.name)
self.assertEqual(obj, exc.obj)
- def test_getattr_suggestions(self):
- class Substitution:
- noise = more_noise = a = bc = None
- blech = None
-
- class Elimination:
- noise = more_noise = a = bc = None
- blch = None
-
- class Addition:
- noise = more_noise = a = bc = None
- bluchin = None
-
- class SubstitutionOverElimination:
- blach = None
- bluc = None
-
- class SubstitutionOverAddition:
- blach = None
- bluchi = None
-
- class EliminationOverAddition:
- blucha = None
- bluc = None
-
- for cls, suggestion in [(Substitution, "'blech'?"),
- (Elimination, "'blch'?"),
- (Addition, "'bluchin'?"),
- (EliminationOverAddition, "'bluc'?"),
- (SubstitutionOverElimination, "'blach'?"),
- (SubstitutionOverAddition, "'blach'?")]:
- try:
- cls().bluch
- except AttributeError as exc:
- with support.captured_stderr() as err:
- sys.__excepthook__(*sys.exc_info())
-
- self.assertIn(suggestion, err.getvalue())
-
- def test_getattr_suggestions_do_not_trigger_for_long_attributes(self):
- class A:
- blech = None
-
- try:
- A().somethingverywrong
- except AttributeError as exc:
- with support.captured_stderr() as err:
- sys.__excepthook__(*sys.exc_info())
-
- self.assertNotIn("blech", err.getvalue())
-
- def test_getattr_error_bad_suggestions_do_not_trigger_for_small_names(self):
- class MyClass:
- vvv = mom = w = id = pytho = None
-
- with self.subTest(name="b"):
- try:
- MyClass.b
- except AttributeError as exc:
- with support.captured_stderr() as err:
- sys.__excepthook__(*sys.exc_info())
- self.assertNotIn("you mean", err.getvalue())
- self.assertNotIn("vvv", err.getvalue())
- self.assertNotIn("mom", err.getvalue())
- self.assertNotIn("'id'", err.getvalue())
- self.assertNotIn("'w'", err.getvalue())
- self.assertNotIn("'pytho'", err.getvalue())
-
- with self.subTest(name="v"):
- try:
- MyClass.v
- except AttributeError as exc:
- with support.captured_stderr() as err:
- sys.__excepthook__(*sys.exc_info())
- self.assertNotIn("you mean", err.getvalue())
- self.assertNotIn("vvv", err.getvalue())
- self.assertNotIn("mom", err.getvalue())
- self.assertNotIn("'id'", err.getvalue())
- self.assertNotIn("'w'", err.getvalue())
- self.assertNotIn("'pytho'", err.getvalue())
-
- with self.subTest(name="m"):
- try:
- MyClass.m
- except AttributeError as exc:
- with support.captured_stderr() as err:
- sys.__excepthook__(*sys.exc_info())
- self.assertNotIn("you mean", err.getvalue())
- self.assertNotIn("vvv", err.getvalue())
- self.assertNotIn("mom", err.getvalue())
- self.assertNotIn("'id'", err.getvalue())
- self.assertNotIn("'w'", err.getvalue())
- self.assertNotIn("'pytho'", err.getvalue())
-
- with self.subTest(name="py"):
- try:
- MyClass.py
- except AttributeError as exc:
- with support.captured_stderr() as err:
- sys.__excepthook__(*sys.exc_info())
- self.assertNotIn("you mean", err.getvalue())
- self.assertNotIn("vvv", err.getvalue())
- self.assertNotIn("mom", err.getvalue())
- self.assertNotIn("'id'", err.getvalue())
- self.assertNotIn("'w'", err.getvalue())
- self.assertNotIn("'pytho'", err.getvalue())
-
-
- def test_getattr_suggestions_do_not_trigger_for_big_dicts(self):
- class A:
- blech = None
- # A class with a very big __dict__ will not be consider
- # for suggestions.
- for index in range(2000):
- setattr(A, f"index_{index}", None)
-
- try:
- A().bluch
- except AttributeError as exc:
- with support.captured_stderr() as err:
- sys.__excepthook__(*sys.exc_info())
-
- self.assertNotIn("blech", err.getvalue())
-
- def test_getattr_suggestions_no_args(self):
- class A:
- blech = None
- def __getattr__(self, attr):
- raise AttributeError()
-
- try:
- A().bluch
- except AttributeError as exc:
- with support.captured_stderr() as err:
- sys.__excepthook__(*sys.exc_info())
-
- self.assertIn("blech", err.getvalue())
-
- class A:
- blech = None
- def __getattr__(self, attr):
- raise AttributeError
-
- try:
- A().bluch
- except AttributeError as exc:
- with support.captured_stderr() as err:
- sys.__excepthook__(*sys.exc_info())
-
- self.assertIn("blech", err.getvalue())
-
- def test_getattr_suggestions_invalid_args(self):
- class NonStringifyClass:
- __str__ = None
- __repr__ = None
-
- class A:
- blech = None
- def __getattr__(self, attr):
- raise AttributeError(NonStringifyClass())
-
- class B:
- blech = None
- def __getattr__(self, attr):
- raise AttributeError("Error", 23)
-
- class C:
- blech = None
- def __getattr__(self, attr):
- raise AttributeError(23)
-
- for cls in [A, B, C]:
- try:
- cls().bluch
- except AttributeError as exc:
- with support.captured_stderr() as err:
- sys.__excepthook__(*sys.exc_info())
-
- self.assertIn("blech", err.getvalue())
-
- def test_getattr_suggestions_for_same_name(self):
- class A:
- def __dir__(self):
- return ['blech']
- try:
- A().blech
- except AttributeError as exc:
- with support.captured_stderr() as err:
- sys.__excepthook__(*sys.exc_info())
-
- self.assertNotIn("Did you mean", err.getvalue())
-
- def test_attribute_error_with_failing_dict(self):
- class T:
- bluch = 1
- def __dir__(self):
- raise AttributeError("oh no!")
-
- try:
- T().blich
- except AttributeError as exc:
- with support.captured_stderr() as err:
- sys.__excepthook__(*sys.exc_info())
-
- self.assertNotIn("blech", err.getvalue())
- self.assertNotIn("oh no!", err.getvalue())
-
- def test_attribute_error_with_bad_name(self):
- try:
- raise AttributeError(name=12, obj=23)
- except AttributeError as exc:
- with support.captured_stderr() as err:
- sys.__excepthook__(*sys.exc_info())
-
- self.assertNotIn("?", err.getvalue())
-
- # TODO: RUSTPYTHON
- @unittest.expectedFailure
- def test_attribute_error_inside_nested_getattr(self):
- class A:
- bluch = 1
-
- class B:
- def __getattribute__(self, attr):
- a = A()
- return a.blich
-
- try:
- B().something
- except AttributeError as exc:
- with support.captured_stderr() as err:
- sys.__excepthook__(*sys.exc_info())
-
- self.assertIn("Did you mean", err.getvalue())
- self.assertIn("bluch", err.getvalue())
+ # Note: name suggestion tests live in `test_traceback`.
class ImportErrorTests(unittest.TestCase):
+
# TODO: RUSTPYTHON
@unittest.expectedFailure
def test_attributes(self):
@@ -2375,7 +1993,7 @@ def test_attributes(self):
self.assertEqual(exc.name, 'somename')
self.assertEqual(exc.path, 'somepath')
- msg = "'invalid' is an invalid keyword argument for ImportError"
+ msg = r"ImportError\(\) got an unexpected keyword argument 'invalid'"
with self.assertRaisesRegex(TypeError, msg):
ImportError('test', invalid='keyword')
@@ -2391,8 +2009,6 @@ def test_attributes(self):
with self.assertRaisesRegex(TypeError, msg):
ImportError('test', invalid='keyword', another=True)
- # TODO: RUSTPYTHON
- @unittest.expectedFailure
def test_reset_attributes(self):
exc = ImportError('test', name='name', path='path')
self.assertEqual(exc.args, ('test',))
@@ -2433,9 +2049,162 @@ def test_copy_pickle(self):
self.assertEqual(exc.name, orig.name)
self.assertEqual(exc.path, orig.path)
+
+def run_script(source):
+ if isinstance(source, str):
+ with open(TESTFN, 'w', encoding='utf-8') as testfile:
+ testfile.write(dedent(source))
+ else:
+ with open(TESTFN, 'wb') as testfile:
+ testfile.write(source)
+ _rc, _out, err = script_helper.assert_python_failure('-Wd', '-X', 'utf8', TESTFN)
+ return err.decode('utf-8').splitlines()
+
+class AssertionErrorTests(unittest.TestCase):
+ def tearDown(self):
+ unlink(TESTFN)
+
+ # TODO: RUSTPYTHON
+ @unittest.expectedFailure
+ @force_not_colorized
+ def test_assertion_error_location(self):
+ cases = [
+ ('assert None',
+ [
+ ' assert None',
+ ' ^^^^',
+ 'AssertionError',
+ ],
+ ),
+ ('assert 0',
+ [
+ ' assert 0',
+ ' ^',
+ 'AssertionError',
+ ],
+ ),
+ ('assert 1 > 2',
+ [
+ ' assert 1 > 2',
+ ' ^^^^^',
+ 'AssertionError',
+ ],
+ ),
+ ('assert 1 > 2 and 3 > 2',
+ [
+ ' assert 1 > 2 and 3 > 2',
+ ' ^^^^^^^^^^^^^^^',
+ 'AssertionError',
+ ],
+ ),
+ ('assert 1 > 2, "messäge"',
+ [
+ ' assert 1 > 2, "messäge"',
+ ' ^^^^^',
+ 'AssertionError: messäge',
+ ],
+ ),
+ ('assert 1 > 2, "messäge"'.encode(),
+ [
+ ' assert 1 > 2, "messäge"',
+ ' ^^^^^',
+ 'AssertionError: messäge',
+ ],
+ ),
+ ('# coding: latin1\nassert 1 > 2, "messäge"'.encode('latin1'),
+ [
+ ' assert 1 > 2, "messäge"',
+ ' ^^^^^',
+ 'AssertionError: messäge',
+ ],
+ ),
+ (BOM_UTF8 + 'assert 1 > 2, "messäge"'.encode(),
+ [
+ ' assert 1 > 2, "messäge"',
+ ' ^^^^^',
+ 'AssertionError: messäge',
+ ],
+ ),
+
+ # Multiline:
+ ("""
+ assert (
+ 1 > 2)
+ """,
+ [
+ ' 1 > 2)',
+ ' ^^^^^',
+ 'AssertionError',
+ ],
+ ),
+ ("""
+ assert (
+ 1 > 2), "Message"
+ """,
+ [
+ ' 1 > 2), "Message"',
+ ' ^^^^^',
+ 'AssertionError: Message',
+ ],
+ ),
+ ("""
+ assert (
+ 1 > 2), \\
+ "Message"
+ """,
+ [
+ ' 1 > 2), \\',
+ ' ^^^^^',
+ 'AssertionError: Message',
+ ],
+ ),
+ ]
+ for source, expected in cases:
+ with self.subTest(source=source):
+ result = run_script(source)
+ self.assertEqual(result[-3:], expected)
+
+ # TODO: RUSTPYTHON
+ @unittest.expectedFailure
+ @force_not_colorized
+ def test_multiline_not_highlighted(self):
+ cases = [
+ ("""
+ assert (
+ 1 > 2
+ )
+ """,
+ [
+ ' 1 > 2',
+ 'AssertionError',
+ ],
+ ),
+ ("""
+ assert (
+ 1 < 2 and
+ 3 > 4
+ )
+ """,
+ [
+ ' 1 < 2 and',
+ ' 3 > 4',
+ 'AssertionError',
+ ],
+ ),
+ ]
+ for source, expected in cases:
+ with self.subTest(source=source):
+ result = run_script(source)
+ self.assertEqual(result[-len(expected):], expected)
+
+
+@support.force_not_colorized_test_class
class SyntaxErrorTests(unittest.TestCase):
+ maxDiff = None
+
# TODO: RUSTPYTHON
@unittest.expectedFailure
+ @force_not_colorized
def test_range_of_offsets(self):
cases = [
# Basic range from 2->7
@@ -2526,53 +2295,131 @@ def test_range_of_offsets(self):
self.assertIn(expected, err.getvalue())
the_exception = exc
+ def test_subclass(self):
+ class MySyntaxError(SyntaxError):
+ pass
+
+ try:
+ raise MySyntaxError("bad bad", ("bad.py", 1, 2, "abcdefg", 1, 7))
+ except SyntaxError as exc:
+ with support.captured_stderr() as err:
+ sys.__excepthook__(*sys.exc_info())
+ self.assertIn("""
+ File "bad.py", line 1
+ abcdefg
+ ^^^^^
+""", err.getvalue())
+
# TODO: RUSTPYTHON
@unittest.expectedFailure
def test_encodings(self):
+ self.addCleanup(unlink, TESTFN)
source = (
'# -*- coding: cp437 -*-\n'
'"¢¢¢¢¢¢" + f(4, x for x in range(1))\n'
)
- try:
- with open(TESTFN, 'w', encoding='cp437') as testfile:
- testfile.write(source)
- rc, out, err = script_helper.assert_python_failure('-Wd', '-X', 'utf8', TESTFN)
- err = err.decode('utf-8').splitlines()
-
- self.assertEqual(err[-3], ' "¢¢¢¢¢¢" + f(4, x for x in range(1))')
- self.assertEqual(err[-2], ' ^^^^^^^^^^^^^^^^^^^')
- finally:
- unlink(TESTFN)
+ err = run_script(source.encode('cp437'))
+ self.assertEqual(err[-3], ' "¢¢¢¢¢¢" + f(4, x for x in range(1))')
+ self.assertEqual(err[-2], ' ^^^^^^^^^^^^^^^^^^^')
# Check backwards tokenizer errors
source = '# -*- coding: ascii -*-\n\n(\n'
- try:
- with open(TESTFN, 'w', encoding='ascii') as testfile:
- testfile.write(source)
- rc, out, err = script_helper.assert_python_failure('-Wd', '-X', 'utf8', TESTFN)
- err = err.decode('utf-8').splitlines()
-
- self.assertEqual(err[-3], ' (')
- self.assertEqual(err[-2], ' ^')
- finally:
- unlink(TESTFN)
+ err = run_script(source)
+ self.assertEqual(err[-3], ' (')
+ self.assertEqual(err[-2], ' ^')
# TODO: RUSTPYTHON
@unittest.expectedFailure
def test_non_utf8(self):
# Check non utf-8 characters
- try:
- with open(TESTFN, 'bw') as testfile:
- testfile.write(b"\x89")
- rc, out, err = script_helper.assert_python_failure('-Wd', '-X', 'utf8', TESTFN)
- err = err.decode('utf-8').splitlines()
+ self.addCleanup(unlink, TESTFN)
+ err = run_script(b"\x89")
+ self.assertIn("SyntaxError: Non-UTF-8 code starting with '\\x89' in file", err[-1])
- self.assertIn("SyntaxError: Non-UTF-8 code starting with '\\x89' in file", err[-1])
- finally:
- unlink(TESTFN)
+ # TODO: RUSTPYTHON
+ @unittest.expectedFailure
+ def test_string_source(self):
+ def try_compile(source):
+ with self.assertRaises(SyntaxError) as cm:
+ compile(source, '', 'exec')
+ return cm.exception
+
+ exc = try_compile('return "ä"')
+ self.assertEqual(str(exc), "'return' outside function (, line 1)")
+ self.assertIsNone(exc.text)
+ self.assertEqual(exc.offset, 1)
+ self.assertEqual(exc.end_offset, 12)
+
+ exc = try_compile('return "ä"'.encode())
+ self.assertEqual(str(exc), "'return' outside function (, line 1)")
+ self.assertIsNone(exc.text)
+ self.assertEqual(exc.offset, 1)
+ self.assertEqual(exc.end_offset, 12)
+
+ exc = try_compile(BOM_UTF8 + 'return "ä"'.encode())
+ self.assertEqual(str(exc), "'return' outside function (, line 1)")
+ self.assertIsNone(exc.text)
+ self.assertEqual(exc.offset, 1)
+ self.assertEqual(exc.end_offset, 12)
+
+ exc = try_compile('# coding: latin1\nreturn "ä"'.encode('latin1'))
+ self.assertEqual(str(exc), "'return' outside function (, line 2)")
+ self.assertIsNone(exc.text)
+ self.assertEqual(exc.offset, 1)
+ self.assertEqual(exc.end_offset, 12)
+
+ exc = try_compile('return "ä" #' + 'ä'*1000)
+ self.assertEqual(str(exc), "'return' outside function (, line 1)")
+ self.assertIsNone(exc.text)
+ self.assertEqual(exc.offset, 1)
+ self.assertEqual(exc.end_offset, 12)
+
+ exc = try_compile('return "ä" # ' + 'ä'*1000)
+ self.assertEqual(str(exc), "'return' outside function (, line 1)")
+ self.assertIsNone(exc.text)
+ self.assertEqual(exc.offset, 1)
+ self.assertEqual(exc.end_offset, 12)
# TODO: RUSTPYTHON
@unittest.expectedFailure
+ def test_file_source(self):
+ self.addCleanup(unlink, TESTFN)
+ err = run_script('return "ä"')
+ self.assertEqual(err[-3:], [
+ ' return "ä"',
+ ' ^^^^^^^^^^',
+ "SyntaxError: 'return' outside function"])
+
+ err = run_script('return "ä"'.encode())
+ self.assertEqual(err[-3:], [
+ ' return "ä"',
+ ' ^^^^^^^^^^',
+ "SyntaxError: 'return' outside function"])
+
+ err = run_script(BOM_UTF8 + 'return "ä"'.encode())
+ self.assertEqual(err[-3:], [
+ ' return "ä"',
+ ' ^^^^^^^^^^',
+ "SyntaxError: 'return' outside function"])
+
+ err = run_script('# coding: latin1\nreturn "ä"'.encode('latin1'))
+ self.assertEqual(err[-3:], [
+ ' return "ä"',
+ ' ^^^^^^^^^^',
+ "SyntaxError: 'return' outside function"])
+
+ err = run_script('return "ä" #' + 'ä'*1000)
+ self.assertEqual(err[-2:], [
+ ' ^^^^^^^^^^^',
+ "SyntaxError: 'return' outside function"])
+ self.assertEqual(err[-3][:100], ' return "ä" #' + 'ä'*84)
+
+ err = run_script('return "ä" # ' + 'ä'*1000)
+ self.assertEqual(err[-2:], [
+ ' ^^^^^^^^^^^',
+ "SyntaxError: 'return' outside function"])
+ self.assertEqual(err[-3][:100], ' return "ä" # ' + 'ä'*83)
+
def test_attributes_new_constructor(self):
args = ("bad.py", 1, 2, "abcdefg", 1, 100)
the_exception = SyntaxError("bad bad", args)
@@ -2585,8 +2432,6 @@ def test_attributes_new_constructor(self):
self.assertEqual(error, the_exception.text)
self.assertEqual("bad bad", the_exception.msg)
- # TODO: RUSTPYTHON
- @unittest.expectedFailure
def test_attributes_old_constructor(self):
args = ("bad.py", 1, 2, "abcdefg")
the_exception = SyntaxError("bad bad", args)
diff --git a/Lib/test/test_faulthandler.py b/Lib/test/test_faulthandler.py
index c9838cb714..d7e2c6a1de 100644
--- a/Lib/test/test_faulthandler.py
+++ b/Lib/test/test_faulthandler.py
@@ -7,9 +7,7 @@
import subprocess
import sys
from test import support
-from test.support import os_helper
-from test.support import script_helper, is_android
-from test.support import skip_if_sanitizer
+from test.support import os_helper, script_helper, is_android, MS_WINDOWS, threading_helper
import tempfile
import unittest
from textwrap import dedent
@@ -23,7 +21,6 @@
raise unittest.SkipTest("test module requires subprocess")
TIMEOUT = 0.5
-MS_WINDOWS = (os.name == 'nt')
def expected_traceback(lineno1, lineno2, header, min_count=1):
@@ -36,7 +33,7 @@ def expected_traceback(lineno1, lineno2, header, min_count=1):
return '^' + regex + '$'
def skip_segfault_on_android(test):
- # Issue #32138: Raising SIGSEGV on Android may not cause a crash.
+ # gh-76319: Raising SIGSEGV on Android may not cause a crash.
return unittest.skipIf(is_android,
'raising SIGSEGV on Android is unreliable')(test)
@@ -64,8 +61,16 @@ def get_output(self, code, filename=None, fd=None):
pass_fds = []
if fd is not None:
pass_fds.append(fd)
+ env = dict(os.environ)
+
+ # Sanitizers must not handle SIGSEGV (ex: for test_enable_fd())
+ option = 'handle_segv=0'
+ support.set_sanitizer_env_var(env, option)
+
with support.SuppressCrashReport():
- process = script_helper.spawn_python('-c', code, pass_fds=pass_fds)
+ process = script_helper.spawn_python('-c', code,
+ pass_fds=pass_fds,
+ env=env)
with process:
output, stderr = process.communicate()
exitcode = process.wait()
@@ -243,7 +248,7 @@ def test_sigfpe(self):
faulthandler._sigfpe()
""",
3,
- 'Floating point exception')
+ 'Floating-point exception')
@unittest.skipIf(_testcapi is None, 'need _testcapi')
@unittest.skipUnless(hasattr(signal, 'SIGBUS'), 'need signal.SIGBUS')
@@ -273,6 +278,7 @@ def test_sigill(self):
5,
'Illegal instruction')
+ @unittest.skipIf(_testcapi is None, 'need _testcapi')
def check_fatal_error_func(self, release_gil):
# Test that Py_FatalError() dumps a traceback
with support.SuppressCrashReport():
@@ -282,7 +288,7 @@ def check_fatal_error_func(self, release_gil):
""",
2,
'xyz',
- func='test_fatal_error',
+ func='_testcapi_fatal_error_impl',
py_fatal_error=True)
# TODO: RUSTPYTHON
@@ -324,8 +330,6 @@ def test_gil_released(self):
# TODO: RUSTPYTHON
@unittest.expectedFailure
- @skip_if_sanitizer(memory=True, ub=True, reason="sanitizer "
- "builds change crashing process output.")
@skip_segfault_on_android
def test_enable_file(self):
with temporary_filename() as filename:
@@ -343,8 +347,6 @@ def test_enable_file(self):
@unittest.expectedFailure
@unittest.skipIf(sys.platform == "win32",
"subprocess doesn't support pass_fds on Windows")
- @skip_if_sanitizer(memory=True, ub=True, reason="sanitizer "
- "builds change crashing process output.")
@skip_segfault_on_android
def test_enable_fd(self):
with tempfile.TemporaryFile('wb+') as fp:
@@ -616,10 +618,12 @@ def run(self):
lineno = 8
else:
lineno = 10
+ # When the traceback is dumped, the waiter thread may be in the
+ # `self.running.set()` call or in `self.stop.wait()`.
regex = r"""
^Thread 0x[0-9a-f]+ \(most recent call first\):
(?: File ".*threading.py", line [0-9]+ in [_a-z]+
- ){{1,3}} File "", line 23 in run
+ ){{1,3}} File "", line (?:22|23) in run
File ".*threading.py", line [0-9]+ in _bootstrap_inner
File ".*threading.py", line [0-9]+ in _bootstrap
@@ -735,6 +739,7 @@ def test_dump_traceback_later_fd(self):
# TODO: RUSTPYTHON
@unittest.expectedFailure
+ @support.requires_resource('walltime')
def test_dump_traceback_later_twice(self):
self.check_dump_traceback_later(loops=2)
@@ -974,6 +979,34 @@ def test_cancel_later_without_dump_traceback_later(self):
self.assertEqual(output, [])
self.assertEqual(exitcode, 0)
+ @threading_helper.requires_working_threading()
+ @unittest.skipUnless(support.Py_GIL_DISABLED, "only meaningful if the GIL is disabled")
+ def test_free_threaded_dump_traceback(self):
+ # gh-128400: Other threads need to be paused to invoke faulthandler
+ code = dedent("""
+ import faulthandler
+ from threading import Thread, Event
+
+ class Waiter(Thread):
+ def __init__(self):
+ Thread.__init__(self)
+ self.running = Event()
+ self.stop = Event()
+
+ def run(self):
+ self.running.set()
+ self.stop.wait()
+
+ for _ in range(100):
+ waiter = Waiter()
+ waiter.start()
+ waiter.running.wait()
+ faulthandler.dump_traceback(all_threads=True)
+ waiter.stop.set()
+ waiter.join()
+ """)
+ _, exitcode = self.get_output(code)
+ self.assertEqual(exitcode, 0)
if __name__ == "__main__":
unittest.main()
diff --git a/Lib/test/test_float.py b/Lib/test/test_float.py
index f65eb55ca5..2ccad19e03 100644
--- a/Lib/test/test_float.py
+++ b/Lib/test/test_float.py
@@ -9,8 +9,10 @@
from test import support
from test.support.testcase import FloatsAreIdenticalMixin
-from test.test_grammar import (VALID_UNDERSCORE_LITERALS,
- INVALID_UNDERSCORE_LITERALS)
+from test.support.numbers import (
+ VALID_UNDERSCORE_LITERALS,
+ INVALID_UNDERSCORE_LITERALS,
+)
from math import isinf, isnan, copysign, ldexp
import math
@@ -153,8 +155,6 @@ def check(s):
# non-UTF-8 byte string
check(b'123\xa0')
- # TODO: RUSTPYTHON
- @unittest.skip("RustPython panics on this")
@support.run_with_locale('LC_NUMERIC', 'fr_FR', 'de_DE', '')
def test_float_with_comma(self):
# set locale to something that doesn't use '.' for the decimal point
@@ -1515,4 +1515,4 @@ def __init__(self, value):
if __name__ == '__main__':
- unittest.main()
\ No newline at end of file
+ unittest.main()
diff --git a/Lib/test/test_format.py b/Lib/test/test_format.py
index 187270d5b6..79fbab34c3 100644
--- a/Lib/test/test_format.py
+++ b/Lib/test/test_format.py
@@ -397,8 +397,6 @@ def test_nul(self):
testformat("a%sb", ('c\0d',), 'ac\0db')
testcommon(b"a%sb", (b'c\0d',), b'ac\0db')
- # TODO: RUSTPYTHON
- @unittest.expectedFailure
def test_non_ascii(self):
testformat("\u20ac=%f", (1.0,), "\u20ac=1.000000")
@@ -468,8 +466,6 @@ def test_optimisations(self):
self.assertIs(text % (), text)
self.assertIs(text.format(), text)
- # TODO: RustPython missing complex.__format__ implementation
- @unittest.expectedFailure
def test_precision(self):
f = 1.2
self.assertEqual(format(f, ".0f"), "1")
@@ -506,29 +502,21 @@ def test_g_format_has_no_trailing_zeros(self):
self.assertEqual(format(12300050.0, ".6g"), "1.23e+07")
self.assertEqual(format(12300050.0, "#.6g"), "1.23000e+07")
- # TODO: RUSTPYTHON
- @unittest.expectedFailure
def test_with_two_commas_in_format_specifier(self):
error_msg = re.escape("Cannot specify ',' with ','.")
with self.assertRaisesRegex(ValueError, error_msg):
'{:,,}'.format(1)
- # TODO: RUSTPYTHON
- @unittest.expectedFailure
def test_with_two_underscore_in_format_specifier(self):
error_msg = re.escape("Cannot specify '_' with '_'.")
with self.assertRaisesRegex(ValueError, error_msg):
'{:__}'.format(1)
- # TODO: RUSTPYTHON
- @unittest.expectedFailure
def test_with_a_commas_and_an_underscore_in_format_specifier(self):
error_msg = re.escape("Cannot specify both ',' and '_'.")
with self.assertRaisesRegex(ValueError, error_msg):
'{:,_}'.format(1)
- # TODO: RUSTPYTHON
- @unittest.expectedFailure
def test_with_an_underscore_and_a_comma_in_format_specifier(self):
error_msg = re.escape("Cannot specify both ',' and '_'.")
with self.assertRaisesRegex(ValueError, error_msg):
diff --git a/Lib/test/test_fstring.py b/Lib/test/test_fstring.py
index c727b5b22c..4996eedc1c 100644
--- a/Lib/test/test_fstring.py
+++ b/Lib/test/test_fstring.py
@@ -1838,29 +1838,21 @@ def test_invalid_syntax_error_message(self):
):
compile("f'{a $ b}'", "?", "exec")
- # TODO: RUSTPYTHON
- @unittest.expectedFailure
def test_with_two_commas_in_format_specifier(self):
error_msg = re.escape("Cannot specify ',' with ','.")
with self.assertRaisesRegex(ValueError, error_msg):
f"{1:,,}"
- # TODO: RUSTPYTHON
- @unittest.expectedFailure
def test_with_two_underscore_in_format_specifier(self):
error_msg = re.escape("Cannot specify '_' with '_'.")
with self.assertRaisesRegex(ValueError, error_msg):
f"{1:__}"
- # TODO: RUSTPYTHON
- @unittest.expectedFailure
def test_with_a_commas_and_an_underscore_in_format_specifier(self):
error_msg = re.escape("Cannot specify both ',' and '_'.")
with self.assertRaisesRegex(ValueError, error_msg):
f"{1:,_}"
- # TODO: RUSTPYTHON
- @unittest.expectedFailure
def test_with_an_underscore_and_a_comma_in_format_specifier(self):
error_msg = re.escape("Cannot specify both ',' and '_'.")
with self.assertRaisesRegex(ValueError, error_msg):
diff --git a/Lib/test/test_funcattrs.py b/Lib/test/test_funcattrs.py
index 9080922e5e..3d5378092b 100644
--- a/Lib/test/test_funcattrs.py
+++ b/Lib/test/test_funcattrs.py
@@ -140,8 +140,6 @@ def f(): print(a)
self.fail("shouldn't be able to read an empty cell")
a = 12
- # TODO: RUSTPYTHON
- @unittest.expectedFailure
def test_set_cell(self):
a = 12
def f(): return a
@@ -178,8 +176,6 @@ def test___name__(self):
self.assertEqual(self.fi.a.__name__, 'a')
self.cannot_set_attr(self.fi.a, "__name__", 'a', AttributeError)
- # TODO: RUSTPYTHON
- @unittest.expectedFailure
def test___qualname__(self):
# PEP 3155
self.assertEqual(self.b.__qualname__, 'FuncAttrsTest.setUp..b')
@@ -278,8 +274,6 @@ def test___self__(self):
self.assertEqual(self.fi.a.__self__, self.fi)
self.cannot_set_attr(self.fi.a, "__self__", self.fi, AttributeError)
- # TODO: RUSTPYTHON
- @unittest.expectedFailure
def test___func___non_method(self):
# Behavior should be the same when a method is added via an attr
# assignment
@@ -333,8 +327,6 @@ def test_setting_dict_to_invalid(self):
d = UserDict({'known_attr': 7})
self.cannot_set_attr(self.fi.a.__func__, '__dict__', d, TypeError)
- # TODO: RUSTPYTHON
- @unittest.expectedFailure
def test_setting_dict_to_valid(self):
d = {'known_attr': 7}
self.b.__dict__ = d
@@ -359,8 +351,6 @@ def test_delete___dict__(self):
else:
self.fail("deleting function dictionary should raise TypeError")
- # TODO: RUSTPYTHON
- @unittest.expectedFailure
def test_unassigned_dict(self):
self.assertEqual(self.b.__dict__, {})
@@ -381,8 +371,6 @@ def test_set_docstring_attr(self):
self.assertEqual(self.fi.a.__doc__, docstr)
self.cannot_set_attr(self.fi.a, "__doc__", docstr, AttributeError)
- # TODO: RUSTPYTHON
- @unittest.expectedFailure
def test_delete_docstring(self):
self.b.__doc__ = "The docstring"
del self.b.__doc__
diff --git a/Lib/test/test_functools.py b/Lib/test/test_functools.py
index fb2dcf7a51..32b442b0c0 100644
--- a/Lib/test/test_functools.py
+++ b/Lib/test/test_functools.py
@@ -396,8 +396,6 @@ class TestPartialC(TestPartial, unittest.TestCase):
module = c_functools
partial = c_functools.partial
- # TODO: RUSTPYTHON
- @unittest.expectedFailure
def test_attributes_unwritable(self):
# attributes should not be writable
p = self.partial(capture, 1, 2, a=10, b=20)
@@ -1691,8 +1689,6 @@ def f(zomg: 'zomg_annotation'):
for attr in self.module.WRAPPER_ASSIGNMENTS:
self.assertEqual(getattr(g, attr), getattr(f, attr))
- # TODO: RUSTPYTHON
- @unittest.expectedFailure
@threading_helper.requires_working_threading()
def test_lru_cache_threaded(self):
n, m = 5, 11
@@ -2901,8 +2897,6 @@ def _(arg: int | None):
self.assertEqual(types_union(1), "types.UnionType")
self.assertEqual(types_union(None), "types.UnionType")
- # TODO: RUSTPYTHON
- @unittest.expectedFailure
def test_register_genericalias(self):
@functools.singledispatch
def f(arg):
@@ -2922,8 +2916,6 @@ def f(arg):
self.assertEqual(f(""), "default")
self.assertEqual(f(b""), "default")
- # TODO: RUSTPYTHON
- @unittest.expectedFailure
def test_register_genericalias_decorator(self):
@functools.singledispatch
def f(arg):
@@ -2938,8 +2930,6 @@ def f(arg):
with self.assertRaisesRegex(TypeError, "Invalid first argument to "):
f.register(typing.List[int] | str)
- # TODO: RUSTPYTHON
- @unittest.expectedFailure
def test_register_genericalias_annotation(self):
@functools.singledispatch
def f(arg):
diff --git a/Lib/test/test_genericalias.py b/Lib/test/test_genericalias.py
index 4d630ed166..0daaff099a 100644
--- a/Lib/test/test_genericalias.py
+++ b/Lib/test/test_genericalias.py
@@ -85,8 +85,6 @@ class BaseTest(unittest.TestCase):
if ctypes is not None:
generic_types.extend((ctypes.Array, ctypes.LibraryLoader))
- # TODO: RUSTPYTHON
- @unittest.expectedFailure
def test_subscriptable(self):
for t in self.generic_types:
if t is None:
@@ -173,8 +171,6 @@ def test_exposed_type(self):
self.assertEqual(a.__args__, (int,))
self.assertEqual(a.__parameters__, ())
- # TODO: RUSTPYTHON
- @unittest.expectedFailure
def test_parameters(self):
from typing import List, Dict, Callable
D0 = dict[str, int]
@@ -214,8 +210,6 @@ def test_parameters(self):
self.assertEqual(L5.__args__, (Callable[[K, V], K],))
self.assertEqual(L5.__parameters__, (K, V))
- # TODO: RUSTPYTHON
- @unittest.expectedFailure
def test_parameter_chaining(self):
from typing import List, Dict, Union, Callable
self.assertEqual(list[T][int], list[int])
@@ -275,8 +269,6 @@ class MyType(type):
with self.assertRaises(TypeError):
MyType[int]
- # TODO: RUSTPYTHON
- @unittest.expectedFailure
def test_pickle(self):
alias = GenericAlias(list, T)
for proto in range(pickle.HIGHEST_PROTOCOL + 1):
@@ -286,8 +278,6 @@ def test_pickle(self):
self.assertEqual(loaded.__args__, alias.__args__)
self.assertEqual(loaded.__parameters__, alias.__parameters__)
- # TODO: RUSTPYTHON
- @unittest.expectedFailure
def test_copy(self):
class X(list):
def __copy__(self):
@@ -311,8 +301,6 @@ def test_union(self):
self.assertEqual(a.__args__, (list[int], list[str]))
self.assertEqual(a.__parameters__, ())
- # TODO: RUSTPYTHON
- @unittest.expectedFailure
def test_union_generic(self):
a = typing.Union[list[T], tuple[T, ...]]
self.assertEqual(a.__args__, (list[T], tuple[T, ...]))
@@ -324,8 +312,6 @@ def test_dir(self):
for generic_alias_property in ("__origin__", "__args__", "__parameters__"):
self.assertIn(generic_alias_property, dir_of_gen_alias)
- # TODO: RUSTPYTHON
- @unittest.expectedFailure
def test_weakref(self):
for t in self.generic_types:
if t is None:
diff --git a/Lib/test/test_grammar.py b/Lib/test/test_grammar.py
index d5c9250ab0..e40f569d2c 100644
--- a/Lib/test/test_grammar.py
+++ b/Lib/test/test_grammar.py
@@ -400,7 +400,7 @@ def test_var_annot_module_semantics(self):
def test_var_annot_in_module(self):
# check that functions fail the same way when executed
# outside of module where they were defined
- from test.ann_module3 import f_bad_ann, g_bad_ann, D_bad_ann
+ from test.typinganndata.ann_module3 import f_bad_ann, g_bad_ann, D_bad_ann
with self.assertRaises(NameError):
f_bad_ann()
with self.assertRaises(NameError):
diff --git a/Lib/test/test_gzip.py b/Lib/test/test_gzip.py
index 42bb7d6984..b0d9613cdb 100644
--- a/Lib/test/test_gzip.py
+++ b/Lib/test/test_gzip.py
@@ -3,12 +3,14 @@
import array
import functools
+import gc
import io
import os
import struct
import sys
import unittest
from subprocess import PIPE, Popen
+from test.support import catch_unraisable_exception
from test.support import import_helper
from test.support import os_helper
from test.support import _4G, bigmemtest, requires_subprocess
@@ -713,17 +715,6 @@ def test_compress_mtime(self):
f.read(1) # to set mtime attribute
self.assertEqual(f.mtime, mtime)
- def test_compress_mtime_default(self):
- # test for gh-125260
- datac = gzip.compress(data1, mtime=0)
- datac2 = gzip.compress(data1)
- self.assertEqual(datac, datac2)
- datac3 = gzip.compress(data1, mtime=None)
- self.assertNotEqual(datac, datac3)
- with gzip.GzipFile(fileobj=io.BytesIO(datac3), mode="rb") as f:
- f.read(1) # to set mtime attribute
- self.assertGreater(f.mtime, 1)
-
def test_compress_correct_level(self):
for mtime in (0, 42):
with self.subTest(mtime=mtime):
@@ -859,6 +850,17 @@ def test_write_seek_write(self):
self.assertEqual(gzip.decompress(data), message * 2)
+ def test_refloop_unraisable(self):
+ # Ensure a GzipFile referring to a temporary fileobj deletes cleanly.
+ # Previously an unraisable exception would occur on close because the
+ # fileobj would be closed before the GzipFile as the result of a
+ # reference loop. See issue gh-129726
+ with catch_unraisable_exception() as cm:
+ gzip.GzipFile(fileobj=io.BytesIO(), mode="w")
+ gc.collect()
+ self.assertIsNone(cm.unraisable)
+
+
class TestOpen(BaseTest):
def test_binary_modes(self):
uncompressed = data1 * 50
diff --git a/Lib/test/test_hashlib.py b/Lib/test/test_hashlib.py
index da62486e0b..5055c4c7f7 100644
--- a/Lib/test/test_hashlib.py
+++ b/Lib/test/test_hashlib.py
@@ -456,8 +456,6 @@ def check_blocksize_name(self, name, block_size=0, digest_size=0,
# split for sha3_512 / _sha3.sha3 object
self.assertIn(name.split("_")[0], repr(m))
- # TODO: RUSTPYTHON
- @unittest.expectedFailure
def test_blocksize_name(self):
self.check_blocksize_name('md5', 64, 16)
self.check_blocksize_name('sha1', 64, 20)
@@ -500,8 +498,6 @@ def test_extra_sha3(self):
self.check_sha3('shake_128', 256, 1344, b'\x1f')
self.check_sha3('shake_256', 512, 1088, b'\x1f')
- # TODO: RUSTPYTHON implement all blake2 params
- @unittest.expectedFailure
@requires_blake2
def test_blocksize_name_blake2(self):
self.check_blocksize_name('blake2b', 128, 64)
diff --git a/Lib/test/test_httplib.py b/Lib/test/test_httplib.py
index b73f081bb8..5a59f372ad 100644
--- a/Lib/test/test_httplib.py
+++ b/Lib/test/test_httplib.py
@@ -1,3 +1,4 @@
+import sys
import errno
from http import client, HTTPStatus
import io
@@ -1781,6 +1782,7 @@ def test_networked_bad_cert(self):
# TODO: RUSTPYTHON
@unittest.expectedFailure
+ @unittest.skipIf(sys.platform == 'darwin', 'Occasionally success on macOS')
def test_local_unknown_cert(self):
# The custom cert isn't known to the default trust bundle
import ssl
diff --git a/Lib/test/test_import/__init__.py b/Lib/test/test_import/__init__.py
index 89e5ec1534..44e7da1033 100644
--- a/Lib/test/test_import/__init__.py
+++ b/Lib/test/test_import/__init__.py
@@ -1380,8 +1380,6 @@ def test_crossreference2(self):
self.assertIn('partially initialized module', errmsg)
self.assertIn('circular import', errmsg)
- # TODO: RUSTPYTHON
- @unittest.expectedFailure
def test_circular_from_import(self):
with self.assertRaises(ImportError) as cm:
import test.test_import.data.circular_imports.from_cycle1
diff --git a/Lib/test/test_importlib/source/test_file_loader.py b/Lib/test/test_importlib/source/test_file_loader.py
index 9c85bd234f..9ade197a23 100644
--- a/Lib/test/test_importlib/source/test_file_loader.py
+++ b/Lib/test/test_importlib/source/test_file_loader.py
@@ -672,10 +672,9 @@ def test_read_only_bytecode(self):
os.chmod(bytecode_path, stat.S_IWUSR)
-# TODO: RUSTPYTHON
-# class SourceLoaderBadBytecodeTestPEP451(
-# SourceLoaderBadBytecodeTest, BadBytecodeTestPEP451):
-# pass
+class SourceLoaderBadBytecodeTestPEP451(
+ SourceLoaderBadBytecodeTest, BadBytecodeTestPEP451):
+ pass
# (Frozen_SourceBadBytecodePEP451,
@@ -772,10 +771,9 @@ def test_non_code_marshal(self):
self._test_non_code_marshal(del_source=True)
-# TODO: RUSTPYTHON
-# class SourcelessLoaderBadBytecodeTestPEP451(SourcelessLoaderBadBytecodeTest,
-# BadBytecodeTestPEP451):
-# pass
+class SourcelessLoaderBadBytecodeTestPEP451(SourcelessLoaderBadBytecodeTest,
+ BadBytecodeTestPEP451):
+ pass
# (Frozen_SourcelessBadBytecodePEP451,
diff --git a/Lib/test/test_io.py b/Lib/test/test_io.py
index 9cc22b959d..e91d454b72 100644
--- a/Lib/test/test_io.py
+++ b/Lib/test/test_io.py
@@ -5098,13 +5098,15 @@ def alarm2(sig, frame):
if e.errno != errno.EBADF:
raise
- @unittest.skip("TODO: RUSTPYTHON, thread 'main' panicked at 'already borrowed: BorrowMutError'")
+ # TODO: RUSTPYTHON
+ @unittest.expectedFailure
@requires_alarm
@support.requires_resource('walltime')
def test_interrupted_write_retry_buffered(self):
self.check_interrupted_write_retry(b"x", mode="wb")
- @unittest.skip("TODO: RUSTPYTHON, thread 'main' panicked at 'already borrowed: BorrowMutError'")
+ # TODO: RUSTPYTHON
+ @unittest.expectedFailure
@requires_alarm
@support.requires_resource('walltime')
def test_interrupted_write_retry_text(self):
diff --git a/Lib/test/test_ipaddress.py b/Lib/test/test_ipaddress.py
index fc27628af1..e69e12495a 100644
--- a/Lib/test/test_ipaddress.py
+++ b/Lib/test/test_ipaddress.py
@@ -303,6 +303,14 @@ def test_pickle(self):
def test_weakref(self):
weakref.ref(self.factory('192.0.2.1'))
+ def test_ipv6_mapped(self):
+ self.assertEqual(ipaddress.IPv4Address('192.168.1.1').ipv6_mapped,
+ ipaddress.IPv6Address('::ffff:192.168.1.1'))
+ self.assertEqual(ipaddress.IPv4Address('192.168.1.1').ipv6_mapped,
+ ipaddress.IPv6Address('::ffff:c0a8:101'))
+ self.assertEqual(ipaddress.IPv4Address('192.168.1.1').ipv6_mapped.ipv4_mapped,
+ ipaddress.IPv4Address('192.168.1.1'))
+
class AddressTestCase_v6(BaseTestCase, CommonTestMixin_v6):
factory = ipaddress.IPv6Address
@@ -389,6 +397,19 @@ def assertBadSplit(addr):
# A trailing IPv4 address is two parts
assertBadSplit("10:9:8:7:6:5:4:3:42.42.42.42%scope")
+ def test_bad_address_split_v6_too_long(self):
+ def assertBadSplit(addr):
+ msg = r"At most 45 characters expected in '%s"
+ with self.assertAddressError(msg, re.escape(addr[:45])):
+ ipaddress.IPv6Address(addr)
+
+ # Long IPv6 address
+ long_addr = ("0:" * 10000) + "0"
+ assertBadSplit(long_addr)
+ assertBadSplit(long_addr + "%zoneid")
+ assertBadSplit(long_addr + ":255.255.255.255")
+ assertBadSplit(long_addr + ":ffff:255.255.255.255")
+
def test_bad_address_split_v6_too_many_parts(self):
def assertBadSplit(addr):
msg = "Exactly 8 parts expected without '::' in %r"
@@ -886,8 +907,8 @@ class ComparisonTests(unittest.TestCase):
v6net = ipaddress.IPv6Network(1)
v6intf = ipaddress.IPv6Interface(1)
v6addr_scoped = ipaddress.IPv6Address('::1%scope')
- v6net_scoped= ipaddress.IPv6Network('::1%scope')
- v6intf_scoped= ipaddress.IPv6Interface('::1%scope')
+ v6net_scoped = ipaddress.IPv6Network('::1%scope')
+ v6intf_scoped = ipaddress.IPv6Interface('::1%scope')
v4_addresses = [v4addr, v4intf]
v4_objects = v4_addresses + [v4net]
@@ -1075,6 +1096,7 @@ def setUp(self):
self.ipv6_scoped_interface = ipaddress.IPv6Interface(
'2001:658:22a:cafe:200:0:0:1%scope/64')
self.ipv6_scoped_network = ipaddress.IPv6Network('2001:658:22a:cafe::%scope/64')
+ self.ipv6_with_ipv4_part = ipaddress.IPv6Interface('::1.2.3.4')
def testRepr(self):
self.assertEqual("IPv4Interface('1.2.3.4/32')",
@@ -1328,6 +1350,17 @@ def testGetIp(self):
self.assertEqual(str(self.ipv6_scoped_interface.ip),
'2001:658:22a:cafe:200::1')
+ def testIPv6IPv4MappedStringRepresentation(self):
+ long_prefix = '0000:0000:0000:0000:0000:ffff:'
+ short_prefix = '::ffff:'
+ ipv4 = '1.2.3.4'
+ ipv6_ipv4_str = short_prefix + ipv4
+ ipv6_ipv4_addr = ipaddress.IPv6Address(ipv6_ipv4_str)
+ ipv6_ipv4_iface = ipaddress.IPv6Interface(ipv6_ipv4_str)
+ self.assertEqual(str(ipv6_ipv4_addr), ipv6_ipv4_str)
+ self.assertEqual(ipv6_ipv4_addr.exploded, long_prefix + ipv4)
+ self.assertEqual(str(ipv6_ipv4_iface.ip), ipv6_ipv4_str)
+
def testGetScopeId(self):
self.assertEqual(self.ipv6_address.scope_id,
None)
@@ -1694,6 +1727,8 @@ def testEqual(self):
self.assertTrue(self.ipv6_scoped_interface ==
ipaddress.IPv6Interface('2001:658:22a:cafe:200::1%scope/64'))
+ self.assertTrue(self.ipv6_with_ipv4_part ==
+ ipaddress.IPv6Interface('0000:0000:0000:0000:0000:0000:0102:0304'))
self.assertFalse(self.ipv6_scoped_interface ==
ipaddress.IPv6Interface('2001:658:22a:cafe:200::1%scope/63'))
self.assertFalse(self.ipv6_scoped_interface ==
@@ -2156,6 +2191,11 @@ def testIPv6AddressTooLarge(self):
self.assertEqual(ipaddress.ip_address('FFFF::192.0.2.1'),
ipaddress.ip_address('FFFF::c000:201'))
+ self.assertEqual(ipaddress.ip_address('0000:0000:0000:0000:0000:FFFF:192.168.255.255'),
+ ipaddress.ip_address('::ffff:c0a8:ffff'))
+ self.assertEqual(ipaddress.ip_address('FFFF:0000:0000:0000:0000:0000:192.168.255.255'),
+ ipaddress.ip_address('ffff::c0a8:ffff'))
+
self.assertEqual(ipaddress.ip_address('::FFFF:192.0.2.1%scope'),
ipaddress.ip_address('::FFFF:c000:201%scope'))
self.assertEqual(ipaddress.ip_address('FFFF::192.0.2.1%scope'),
@@ -2168,11 +2208,16 @@ def testIPv6AddressTooLarge(self):
ipaddress.ip_address('::FFFF:c000:201%scope'))
self.assertNotEqual(ipaddress.ip_address('FFFF::192.0.2.1'),
ipaddress.ip_address('FFFF::c000:201%scope'))
+ self.assertEqual(ipaddress.ip_address('0000:0000:0000:0000:0000:FFFF:192.168.255.255%scope'),
+ ipaddress.ip_address('::ffff:c0a8:ffff%scope'))
+ self.assertEqual(ipaddress.ip_address('FFFF:0000:0000:0000:0000:0000:192.168.255.255%scope'),
+ ipaddress.ip_address('ffff::c0a8:ffff%scope'))
def testIPVersion(self):
self.assertEqual(self.ipv4_address.version, 4)
self.assertEqual(self.ipv6_address.version, 6)
self.assertEqual(self.ipv6_scoped_address.version, 6)
+ self.assertEqual(self.ipv6_with_ipv4_part.version, 6)
def testMaxPrefixLength(self):
self.assertEqual(self.ipv4_interface.max_prefixlen, 32)
@@ -2269,6 +2314,10 @@ def testReservedIpv4(self):
self.assertEqual(True, ipaddress.ip_address(
'172.31.255.255').is_private)
self.assertEqual(False, ipaddress.ip_address('172.32.0.0').is_private)
+ self.assertFalse(ipaddress.ip_address('192.0.0.0').is_global)
+ self.assertTrue(ipaddress.ip_address('192.0.0.9').is_global)
+ self.assertTrue(ipaddress.ip_address('192.0.0.10').is_global)
+ self.assertFalse(ipaddress.ip_address('192.0.0.255').is_global)
self.assertEqual(True,
ipaddress.ip_address('169.254.100.200').is_link_local)
@@ -2294,6 +2343,7 @@ def testPrivateNetworks(self):
self.assertEqual(True, ipaddress.ip_network("169.254.0.0/16").is_private)
self.assertEqual(True, ipaddress.ip_network("172.16.0.0/12").is_private)
self.assertEqual(True, ipaddress.ip_network("192.0.0.0/29").is_private)
+ self.assertEqual(False, ipaddress.ip_network("192.0.0.9/32").is_private)
self.assertEqual(True, ipaddress.ip_network("192.0.0.170/31").is_private)
self.assertEqual(True, ipaddress.ip_network("192.0.2.0/24").is_private)
self.assertEqual(True, ipaddress.ip_network("192.168.0.0/16").is_private)
@@ -2310,8 +2360,8 @@ def testPrivateNetworks(self):
self.assertEqual(True, ipaddress.ip_network("::/128").is_private)
self.assertEqual(True, ipaddress.ip_network("::ffff:0:0/96").is_private)
self.assertEqual(True, ipaddress.ip_network("100::/64").is_private)
- self.assertEqual(True, ipaddress.ip_network("2001::/23").is_private)
self.assertEqual(True, ipaddress.ip_network("2001:2::/48").is_private)
+ self.assertEqual(False, ipaddress.ip_network("2001:3::/48").is_private)
self.assertEqual(True, ipaddress.ip_network("2001:db8::/32").is_private)
self.assertEqual(True, ipaddress.ip_network("2001:10::/28").is_private)
self.assertEqual(True, ipaddress.ip_network("fc00::/7").is_private)
@@ -2390,6 +2440,22 @@ def testReservedIpv6(self):
self.assertEqual(True, ipaddress.ip_address('0::0').is_unspecified)
self.assertEqual(False, ipaddress.ip_address('::1').is_unspecified)
+ self.assertFalse(ipaddress.ip_address('64:ff9b:1::').is_global)
+ self.assertFalse(ipaddress.ip_address('2001::').is_global)
+ self.assertTrue(ipaddress.ip_address('2001:1::1').is_global)
+ self.assertTrue(ipaddress.ip_address('2001:1::2').is_global)
+ self.assertFalse(ipaddress.ip_address('2001:2::').is_global)
+ self.assertTrue(ipaddress.ip_address('2001:3::').is_global)
+ self.assertFalse(ipaddress.ip_address('2001:4::').is_global)
+ self.assertTrue(ipaddress.ip_address('2001:4:112::').is_global)
+ self.assertFalse(ipaddress.ip_address('2001:10::').is_global)
+ self.assertTrue(ipaddress.ip_address('2001:20::').is_global)
+ self.assertTrue(ipaddress.ip_address('2001:30::').is_global)
+ self.assertFalse(ipaddress.ip_address('2001:40::').is_global)
+ self.assertFalse(ipaddress.ip_address('2002::').is_global)
+ # gh-124217: conform with RFC 9637
+ self.assertFalse(ipaddress.ip_address('3fff::').is_global)
+
# some generic IETF reserved addresses
self.assertEqual(True, ipaddress.ip_address('100::').is_reserved)
self.assertEqual(True, ipaddress.ip_network('4000::1/128').is_reserved)
@@ -2402,12 +2468,52 @@ def testIpv4Mapped(self):
self.assertEqual(ipaddress.ip_address('::ffff:c0a8:101').ipv4_mapped,
ipaddress.ip_address('192.168.1.1'))
+ def testIpv4MappedProperties(self):
+ # Test that an IPv4 mapped IPv6 address has
+ # the same properties as an IPv4 address.
+ for addr4 in (
+ "178.62.3.251", # global
+ "169.254.169.254", # link local
+ "127.0.0.1", # loopback
+ "224.0.0.1", # multicast
+ "192.168.0.1", # private
+ "0.0.0.0", # unspecified
+ "100.64.0.1", # public and not global
+ ):
+ with self.subTest(addr4):
+ ipv4 = ipaddress.IPv4Address(addr4)
+ ipv6 = ipaddress.IPv6Address(f"::ffff:{addr4}")
+
+ self.assertEqual(ipv4.is_global, ipv6.is_global)
+ self.assertEqual(ipv4.is_private, ipv6.is_private)
+ self.assertEqual(ipv4.is_reserved, ipv6.is_reserved)
+ self.assertEqual(ipv4.is_multicast, ipv6.is_multicast)
+ self.assertEqual(ipv4.is_unspecified, ipv6.is_unspecified)
+ self.assertEqual(ipv4.is_link_local, ipv6.is_link_local)
+ self.assertEqual(ipv4.is_loopback, ipv6.is_loopback)
+
def testIpv4MappedPrivateCheck(self):
self.assertEqual(
True, ipaddress.ip_address('::ffff:192.168.1.1').is_private)
self.assertEqual(
False, ipaddress.ip_address('::ffff:172.32.0.0').is_private)
+ def testIpv4MappedLoopbackCheck(self):
+ # test networks
+ self.assertEqual(True, ipaddress.ip_network(
+ '::ffff:127.100.200.254/128').is_loopback)
+ self.assertEqual(True, ipaddress.ip_network(
+ '::ffff:127.42.0.0/112').is_loopback)
+ self.assertEqual(False, ipaddress.ip_network(
+ '::ffff:128.0.0.0').is_loopback)
+ # test addresses
+ self.assertEqual(True, ipaddress.ip_address(
+ '::ffff:127.100.200.254').is_loopback)
+ self.assertEqual(True, ipaddress.ip_address(
+ '::ffff:127.42.0.0').is_loopback)
+ self.assertEqual(False, ipaddress.ip_address(
+ '::ffff:128.0.0.0').is_loopback)
+
def testAddrExclude(self):
addr1 = ipaddress.ip_network('10.1.1.0/24')
addr2 = ipaddress.ip_network('10.1.1.0/26')
@@ -2509,6 +2615,10 @@ def testCompressIPv6Address(self):
'::7:6:5:4:3:2:0': '0:7:6:5:4:3:2:0/128',
'7:6:5:4:3:2:1::': '7:6:5:4:3:2:1:0/128',
'0:6:5:4:3:2:1::': '0:6:5:4:3:2:1:0/128',
+ '0000:0000:0000:0000:0000:0000:255.255.255.255': '::ffff:ffff/128',
+ '0000:0000:0000:0000:0000:ffff:255.255.255.255': '::ffff:255.255.255.255/128',
+ 'ffff:ffff:ffff:ffff:ffff:ffff:255.255.255.255':
+ 'ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff/128',
}
for uncompressed, compressed in list(test_addresses.items()):
self.assertEqual(compressed, str(ipaddress.IPv6Interface(
@@ -2531,12 +2641,42 @@ def testExplodeShortHandIpStr(self):
self.assertEqual('192.168.178.1', addr4.exploded)
def testReversePointer(self):
- addr1 = ipaddress.IPv4Address('127.0.0.1')
- addr2 = ipaddress.IPv6Address('2001:db8::1')
- self.assertEqual('1.0.0.127.in-addr.arpa', addr1.reverse_pointer)
- self.assertEqual('1.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.8.' +
- 'b.d.0.1.0.0.2.ip6.arpa',
- addr2.reverse_pointer)
+ for addr_v4, expected in [
+ ('127.0.0.1', '1.0.0.127.in-addr.arpa'),
+ # test vector: https://www.rfc-editor.org/rfc/rfc1035, §3.5
+ ('10.2.0.52', '52.0.2.10.in-addr.arpa'),
+ ]:
+ with self.subTest('ipv4_reverse_pointer', addr=addr_v4):
+ addr = ipaddress.IPv4Address(addr_v4)
+ self.assertEqual(addr.reverse_pointer, expected)
+
+ for addr_v6, expected in [
+ (
+ '2001:db8::1', (
+ '1.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.'
+ '0.0.0.0.0.0.0.0.8.b.d.0.1.0.0.2.'
+ 'ip6.arpa'
+ )
+ ),
+ (
+ '::FFFF:192.168.1.35', (
+ '3.2.1.0.8.a.0.c.f.f.f.f.0.0.0.0.'
+ '0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.'
+ 'ip6.arpa'
+ )
+ ),
+ # test vector: https://www.rfc-editor.org/rfc/rfc3596, §2.5
+ (
+ '4321:0:1:2:3:4:567:89ab', (
+ 'b.a.9.8.7.6.5.0.4.0.0.0.3.0.0.0.'
+ '2.0.0.0.1.0.0.0.0.0.0.0.1.2.3.4.'
+ 'ip6.arpa'
+ )
+ )
+ ]:
+ with self.subTest('ipv6_reverse_pointer', addr=addr_v6):
+ addr = ipaddress.IPv6Address(addr_v6)
+ self.assertEqual(addr.reverse_pointer, expected)
def testIntRepresentation(self):
self.assertEqual(16909060, int(self.ipv4_address))
@@ -2642,6 +2782,34 @@ def testV6HashIsNotConstant(self):
ipv6_address2 = ipaddress.IPv6Interface("2001:658:22a:cafe:200:0:0:2")
self.assertNotEqual(ipv6_address1.__hash__(), ipv6_address2.__hash__())
+ # issue 134062 Hash collisions in IPv4Network and IPv6Network
+ def testNetworkV4HashCollisions(self):
+ self.assertNotEqual(
+ ipaddress.IPv4Network("192.168.1.255/32").__hash__(),
+ ipaddress.IPv4Network("192.168.1.0/24").__hash__()
+ )
+ self.assertNotEqual(
+ ipaddress.IPv4Network("172.24.255.0/24").__hash__(),
+ ipaddress.IPv4Network("172.24.0.0/16").__hash__()
+ )
+ self.assertNotEqual(
+ ipaddress.IPv4Network("192.168.1.87/32").__hash__(),
+ ipaddress.IPv4Network("192.168.1.86/31").__hash__()
+ )
+
+ # issue 134062 Hash collisions in IPv4Network and IPv6Network
+ def testNetworkV6HashCollisions(self):
+ self.assertNotEqual(
+ ipaddress.IPv6Network("fe80::/64").__hash__(),
+ ipaddress.IPv6Network("fe80::ffff:ffff:ffff:0/112").__hash__()
+ )
+ self.assertNotEqual(
+ ipaddress.IPv4Network("10.0.0.0/8").__hash__(),
+ ipaddress.IPv6Network(
+ "ffff:ffff:ffff:ffff:ffff:ffff:aff:0/112"
+ ).__hash__()
+ )
+
if __name__ == '__main__':
unittest.main()
diff --git a/Lib/test/test_isinstance.py b/Lib/test/test_isinstance.py
index 9d37cff990..de80e47209 100644
--- a/Lib/test/test_isinstance.py
+++ b/Lib/test/test_isinstance.py
@@ -3,12 +3,11 @@
# testing of error conditions uncovered when using extension types.
import unittest
-import sys
import typing
from test import support
-
+
class TestIsInstanceExceptions(unittest.TestCase):
# Test to make sure that an AttributeError when accessing the instance's
# class's bases is masked. This was actually a bug in Python 2.2 and
@@ -97,7 +96,7 @@ def getclass(self):
class D: pass
self.assertRaises(RuntimeError, isinstance, c, D)
-
+
# These tests are similar to above, but tickle certain code paths in
# issubclass() instead of isinstance() -- really PyObject_IsSubclass()
# vs. PyObject_IsInstance().
@@ -147,7 +146,7 @@ def getbases(self):
self.assertRaises(TypeError, issubclass, B, C())
-
+
# meta classes for creating abstract classes and instances
class AbstractClass(object):
def __init__(self, bases):
@@ -179,7 +178,7 @@ class Super:
class Child(Super):
pass
-
+
class TestIsInstanceIsSubclass(unittest.TestCase):
# Tests to ensure that isinstance and issubclass work on abstract
# classes and instances. Before the 2.2 release, TypeErrors were
@@ -225,7 +224,7 @@ def test_isinstance_with_or_union(self):
with self.assertRaises(TypeError):
isinstance(2, list[int] | int)
with self.assertRaises(TypeError):
- isinstance(2, int | str | list[int] | float)
+ isinstance(2, float | str | list[int] | int)
@@ -311,7 +310,7 @@ class X:
@property
def __bases__(self):
return self.__bases__
- with support.infinite_recursion():
+ with support.infinite_recursion(25):
self.assertRaises(RecursionError, issubclass, X(), int)
self.assertRaises(RecursionError, issubclass, int, X())
self.assertRaises(RecursionError, isinstance, 1, X())
@@ -345,7 +344,7 @@ class B:
pass
A.__getattr__ = B.__getattr__ = X.__getattr__
return (A(), B())
- with support.infinite_recursion():
+ with support.infinite_recursion(25):
self.assertRaises(RecursionError, issubclass, X(), int)
@@ -353,10 +352,12 @@ def blowstack(fxn, arg, compare_to):
# Make sure that calling isinstance with a deeply nested tuple for its
# argument will raise RecursionError eventually.
tuple_arg = (compare_to,)
+ # XXX: RUSTPYTHON; support.exceeds_recursion_limit() is not available yet.
+ import sys
for cnt in range(sys.getrecursionlimit()+5):
tuple_arg = (tuple_arg,)
fxn(arg, tuple_arg)
-
+
if __name__ == '__main__':
unittest.main()
diff --git a/Lib/test/test_itertools.py b/Lib/test/test_itertools.py
index e7b00da71c..072279ea3a 100644
--- a/Lib/test/test_itertools.py
+++ b/Lib/test/test_itertools.py
@@ -1182,8 +1182,6 @@ def test_pairwise(self):
with self.assertRaises(TypeError):
pairwise(None) # non-iterable argument
- # TODO: RUSTPYTHON
- @unittest.skip("TODO: RUSTPYTHON, hangs")
def test_pairwise_reenter(self):
def check(reenter_at, expected):
class I:
@@ -1234,8 +1232,6 @@ def __next__(self):
([5], [6]),
])
- # TODO: RUSTPYTHON
- @unittest.skip("TODO: RUSTPYTHON, hangs")
def test_pairwise_reenter2(self):
def check(maxcount, expected):
class I:
@@ -1765,8 +1761,6 @@ def test_tee_del_backward(self):
del forward, backward
raise
- # TODO: RUSTPYTHON
- @unittest.expectedFailure
def test_tee_reenter(self):
class I:
first = True
diff --git a/Lib/test/test_list.py b/Lib/test/test_list.py
index c82bf5067d..42d8dcbbe1 100644
--- a/Lib/test/test_list.py
+++ b/Lib/test/test_list.py
@@ -1,6 +1,8 @@
import sys
+import textwrap
from test import list_tests
from test.support import cpython_only
+from test.support.script_helper import assert_python_ok
import pickle
import unittest
@@ -98,8 +100,13 @@ def imul(a, b): a *= b
self.assertRaises((MemoryError, OverflowError), mul, lst, n)
self.assertRaises((MemoryError, OverflowError), imul, lst, n)
+ def test_empty_slice(self):
+ x = []
+ x[:] = x
+ self.assertEqual(x, [])
+
# TODO: RUSTPYTHON
- @unittest.skip("Crashes on windows debug build")
+ @unittest.skip("TODO: RUSTPYTHON crash")
def test_list_resize_overflow(self):
# gh-97616: test new_allocated * sizeof(PyObject*) overflow
# check in list_resize()
@@ -113,13 +120,28 @@ def test_list_resize_overflow(self):
with self.assertRaises((MemoryError, OverflowError)):
lst *= size
+ # TODO: RUSTPYTHON
+ @unittest.skip("TODO: RUSTPYTHON hangs")
+ def test_repr_mutate(self):
+ class Obj:
+ @staticmethod
+ def __repr__():
+ try:
+ mylist.pop()
+ except IndexError:
+ pass
+ return 'obj'
+
+ mylist = [Obj() for _ in range(5)]
+ self.assertEqual(repr(mylist), '[obj, obj, obj]')
+
def test_repr_large(self):
# Check the repr of large list objects
def check(n):
l = [0] * n
s = repr(l)
self.assertEqual(s,
- '[' + ', '.join(['0'] * n) + ']')
+ '[' + ', '.join(['0'] * n) + ']')
check(10) # check our checking code
check(1000000)
@@ -302,6 +324,35 @@ def __eq__(self, other):
lst = [X(), X()]
X() in lst
+ def test_tier2_invalidates_iterator(self):
+ # GH-121012
+ for _ in range(100):
+ a = [1, 2, 3]
+ it = iter(a)
+ for _ in it:
+ pass
+ a.append(4)
+ self.assertEqual(list(it), [])
+
+ def test_deopt_from_append_list(self):
+ # gh-132011: it used to crash, because
+ # of `CALL_LIST_APPEND` specialization failure.
+ code = textwrap.dedent("""
+ l = []
+ def lappend(l, x, y):
+ l.append((x, y))
+ for x in range(3):
+ lappend(l, None, None)
+ try:
+ lappend(list, None, None)
+ except TypeError:
+ pass
+ else:
+ raise AssertionError
+ """)
+
+ rc, _, _ = assert_python_ok("-c", code)
+ self.assertEqual(rc, 0)
if __name__ == "__main__":
unittest.main()
diff --git a/Lib/test/test_listcomps.py b/Lib/test/test_listcomps.py
index ad1c5053a3..1380c08d28 100644
--- a/Lib/test/test_listcomps.py
+++ b/Lib/test/test_listcomps.py
@@ -177,7 +177,7 @@ def test_references___class___defined(self):
res = [__class__ for x in [1]]
"""
self._check_in_scopes(
- code, outputs={"res": [2]}, scopes=["module", "function"])
+ code, outputs={"res": [2]}, scopes=["module", "function"])
self._check_in_scopes(code, raises=NameError, scopes=["class"])
def test_references___class___enclosing(self):
@@ -648,11 +648,18 @@ def test_exception_in_post_comp_call(self):
"""
self._check_in_scopes(code, {"value": [1, None]})
+ # TODO: RUSTPYTHON
+ @unittest.expectedFailure
def test_frame_locals(self):
code = """
- val = [sys._getframe().f_locals for a in [0]][0]["a"]
+ val = "a" in [sys._getframe().f_locals for a in [0]][0]
"""
import sys
+ self._check_in_scopes(code, {"val": False}, ns={"sys": sys})
+
+ code = """
+ val = [sys._getframe().f_locals["a"] for a in [0]][0]
+ """
self._check_in_scopes(code, {"val": 0}, ns={"sys": sys})
def _recursive_replace(self, maybe_code):
@@ -736,7 +743,7 @@ def iter_raises():
for func, expected in [(init_raises, "BrokenIter(init_raises=True)"),
(next_raises, "BrokenIter(next_raises=True)"),
(iter_raises, "BrokenIter(iter_raises=True)"),
- ]:
+ ]:
with self.subTest(func):
exc = func()
f = traceback.extract_tb(exc.__traceback__)[0]
diff --git a/Lib/test/test_long.py b/Lib/test/test_long.py
index 6d69232818..2a38b133f1 100644
--- a/Lib/test/test_long.py
+++ b/Lib/test/test_long.py
@@ -625,8 +625,6 @@ def __lt__(self, other):
eq(x > y, Rcmp > 0)
eq(x >= y, Rcmp >= 0)
- # TODO: RUSTPYTHON
- @unittest.expectedFailure
def test__format__(self):
self.assertEqual(format(123456789, 'd'), '123456789')
self.assertEqual(format(123456789, 'd'), '123456789')
diff --git a/Lib/test/test_memoryio.py b/Lib/test/test_memoryio.py
index 5b695f167a..61d9b180e2 100644
--- a/Lib/test/test_memoryio.py
+++ b/Lib/test/test_memoryio.py
@@ -940,8 +940,6 @@ def test_relative_seek(self):
def test_seek(self):
super().test_seek()
- # TODO: RUSTPYTHON
- @unittest.expectedFailure
def test_textio_properties(self):
super().test_textio_properties()
@@ -1046,8 +1044,6 @@ def test_newlines_property(self):
def test_relative_seek(self):
super().test_relative_seek()
- # TODO: RUSTPYTHON
- @unittest.expectedFailure
def test_textio_properties(self):
super().test_textio_properties()
diff --git a/Lib/test/test_module/__init__.py b/Lib/test/test_module/__init__.py
index d8a0ba0803..b599c6d8c8 100644
--- a/Lib/test/test_module/__init__.py
+++ b/Lib/test/test_module/__init__.py
@@ -334,7 +334,7 @@ def test_annotations_getset_raises(self):
del foo.__annotations__
def test_annotations_are_created_correctly(self):
- ann_module4 = import_helper.import_fresh_module('test.ann_module4')
+ ann_module4 = import_helper.import_fresh_module('test.typinganndata.ann_module4')
self.assertTrue("__annotations__" in ann_module4.__dict__)
del ann_module4.__annotations__
self.assertFalse("__annotations__" in ann_module4.__dict__)
diff --git a/Lib/test/test_operator.py b/Lib/test/test_operator.py
index 1db738d228..05b7a7462d 100644
--- a/Lib/test/test_operator.py
+++ b/Lib/test/test_operator.py
@@ -1,6 +1,9 @@
import unittest
+import inspect
import pickle
import sys
+from decimal import Decimal
+from fractions import Fraction
from test import support
from test.support import import_helper
@@ -508,6 +511,44 @@ def __getitem__(self, other): return 5 # so that C is a sequence
self.assertEqual(operator.ixor (c, 5), "ixor")
self.assertEqual(operator.iconcat (c, c), "iadd")
+ def test_iconcat_without_getitem(self):
+ operator = self.module
+
+ msg = "'int' object can't be concatenated"
+ with self.assertRaisesRegex(TypeError, msg):
+ operator.iconcat(1, 0.5)
+
+ def test_index(self):
+ operator = self.module
+ class X:
+ def __index__(self):
+ return 1
+
+ self.assertEqual(operator.index(X()), 1)
+ self.assertEqual(operator.index(0), 0)
+ self.assertEqual(operator.index(1), 1)
+ self.assertEqual(operator.index(2), 2)
+ with self.assertRaises((AttributeError, TypeError)):
+ operator.index(1.5)
+ with self.assertRaises((AttributeError, TypeError)):
+ operator.index(Fraction(3, 7))
+ with self.assertRaises((AttributeError, TypeError)):
+ operator.index(Decimal(1))
+ with self.assertRaises((AttributeError, TypeError)):
+ operator.index(None)
+
+ def test_not_(self):
+ operator = self.module
+ class C:
+ def __bool__(self):
+ raise SyntaxError
+ self.assertRaises(TypeError, operator.not_)
+ self.assertRaises(SyntaxError, operator.not_, C())
+ self.assertFalse(operator.not_(5))
+ self.assertFalse(operator.not_([0]))
+ self.assertTrue(operator.not_(0))
+ self.assertTrue(operator.not_([]))
+
def test_length_hint(self):
operator = self.module
class X(object):
@@ -533,6 +574,13 @@ def __length_hint__(self):
with self.assertRaises(LookupError):
operator.length_hint(X(LookupError))
+ class Y: pass
+
+ msg = "'str' object cannot be interpreted as an integer"
+ with self.assertRaisesRegex(TypeError, msg):
+ operator.length_hint(X(2), "abc")
+ self.assertEqual(operator.length_hint(Y(), 10), 10)
+
def test_call(self):
operator = self.module
@@ -555,6 +603,31 @@ def test_dunder_is_original(self):
if dunder:
self.assertIs(dunder, orig)
+ @support.requires_docstrings
+ def test_attrgetter_signature(self):
+ operator = self.module
+ sig = inspect.signature(operator.attrgetter)
+ self.assertEqual(str(sig), '(attr, /, *attrs)')
+ sig = inspect.signature(operator.attrgetter('x', 'z', 'y'))
+ self.assertEqual(str(sig), '(obj, /)')
+
+ @support.requires_docstrings
+ def test_itemgetter_signature(self):
+ operator = self.module
+ sig = inspect.signature(operator.itemgetter)
+ self.assertEqual(str(sig), '(item, /, *items)')
+ sig = inspect.signature(operator.itemgetter(2, 3, 5))
+ self.assertEqual(str(sig), '(obj, /)')
+
+ @support.requires_docstrings
+ def test_methodcaller_signature(self):
+ operator = self.module
+ sig = inspect.signature(operator.methodcaller)
+ self.assertEqual(str(sig), '(name, /, *args, **kwargs)')
+ sig = inspect.signature(operator.methodcaller('foo', 2, y=3))
+ self.assertEqual(str(sig), '(obj, /)')
+
+
class PyOperatorTestCase(OperatorTestCase, unittest.TestCase):
module = py_operator
@@ -562,6 +635,21 @@ class PyOperatorTestCase(OperatorTestCase, unittest.TestCase):
class COperatorTestCase(OperatorTestCase, unittest.TestCase):
module = c_operator
+ # TODO: RUSTPYTHON
+ @unittest.expectedFailure
+ def test_attrgetter_signature(self):
+ super().test_attrgetter_signature()
+
+ # TODO: RUSTPYTHON
+ @unittest.expectedFailure
+ def test_itemgetter_signature(self):
+ super().test_itemgetter_signature()
+
+ # TODO: RUSTPYTHON
+ @unittest.expectedFailure
+ def test_methodcaller_signature(self):
+ super().test_methodcaller_signature()
+
class OperatorPickleTestCase:
def copy(self, obj, proto):
diff --git a/Lib/test/test_pickle.py b/Lib/test/test_pickle.py
index d61570ce10..e01ddcf0a8 100644
--- a/Lib/test/test_pickle.py
+++ b/Lib/test/test_pickle.py
@@ -664,6 +664,9 @@ def test_exceptions(self):
BaseExceptionGroup,
ExceptionGroup):
continue
+ # TODO: RUSTPYTHON: fix name mapping for _IncompleteInputError
+ if exc is _IncompleteInputError:
+ continue
if exc is not OSError and issubclass(exc, OSError):
self.assertEqual(reverse_mapping('builtins', name),
('exceptions', 'OSError'))
diff --git a/Lib/test/test_posix.py b/Lib/test/test_posix.py
index 7e9e261d78..30d6b6d3c3 100644
--- a/Lib/test/test_posix.py
+++ b/Lib/test/test_posix.py
@@ -1730,8 +1730,6 @@ def test_no_such_executable(self):
self.assertEqual(pid2, pid)
self.assertNotEqual(status, 0)
- # TODO: RUSTPYTHON: TypeError: '_Environ' object is not a mapping
- @unittest.expectedFailure
def test_specify_environment(self):
envfile = os_helper.TESTFN
self.addCleanup(os_helper.unlink, envfile)
@@ -1765,8 +1763,6 @@ def test_empty_file_actions(self):
)
support.wait_process(pid, exitcode=0)
- # TODO: RUSTPYTHON: TypeError: Unexpected keyword argument resetids
- @unittest.expectedFailure
def test_resetids_explicit_default(self):
pid = self.spawn_func(
sys.executable,
@@ -1776,8 +1772,6 @@ def test_resetids_explicit_default(self):
)
support.wait_process(pid, exitcode=0)
- # TODO: RUSTPYTHON: TypeError: Unexpected keyword argument resetids
- @unittest.expectedFailure
def test_resetids(self):
pid = self.spawn_func(
sys.executable,
@@ -1787,8 +1781,6 @@ def test_resetids(self):
)
support.wait_process(pid, exitcode=0)
- # TODO: RUSTPYTHON: TypeError: Unexpected keyword argument setpgroup
- @unittest.expectedFailure
def test_setpgroup(self):
pid = self.spawn_func(
sys.executable,
@@ -1819,8 +1811,6 @@ def test_setsigmask(self):
)
support.wait_process(pid, exitcode=0)
- # TODO: RUSTPYTHON: TypeError: Unexpected keyword argument setsigmask
- @unittest.expectedFailure
def test_setsigmask_wrong_type(self):
with self.assertRaises(TypeError):
self.spawn_func(sys.executable,
@@ -1836,8 +1826,6 @@ def test_setsigmask_wrong_type(self):
os.environ, setsigmask=[signal.NSIG,
signal.NSIG+1])
- # TODO: RUSTPYTHON: TypeError: Unexpected keyword argument setsid
- @unittest.expectedFailure
def test_setsid(self):
rfd, wfd = os.pipe()
self.addCleanup(os.close, rfd)
@@ -1902,7 +1890,6 @@ def test_setsigdef_wrong_type(self):
[sys.executable, "-c", "pass"],
os.environ, setsigdef=[signal.NSIG, signal.NSIG+1])
- # TODO: RUSTPYTHON: TypeError: Unexpected keyword argument scheduler
@unittest.expectedFailure
@requires_sched
@unittest.skipIf(sys.platform.startswith(('freebsd', 'netbsd')),
@@ -1924,7 +1911,6 @@ def test_setscheduler_only_param(self):
)
support.wait_process(pid, exitcode=0)
- # TODO: RUSTPYTHON: TypeError: Unexpected keyword argument scheduler
@unittest.expectedFailure
@requires_sched
@unittest.skipIf(sys.platform.startswith(('freebsd', 'netbsd')),
diff --git a/Lib/test/test_property.py b/Lib/test/test_property.py
index 5312925d93..8411e903b1 100644
--- a/Lib/test/test_property.py
+++ b/Lib/test/test_property.py
@@ -100,32 +100,24 @@ def test_property_decorator_subclass(self):
self.assertRaises(PropertySet, setattr, sub, "spam", None)
self.assertRaises(PropertyDel, delattr, sub, "spam")
- # TODO: RUSTPYTHON
- @unittest.expectedFailure
@unittest.skipIf(sys.flags.optimize >= 2,
"Docstrings are omitted with -O2 and above")
def test_property_decorator_subclass_doc(self):
sub = SubClass()
self.assertEqual(sub.__class__.spam.__doc__, "SubClass.getter")
- # TODO: RUSTPYTHON
- @unittest.expectedFailure
@unittest.skipIf(sys.flags.optimize >= 2,
"Docstrings are omitted with -O2 and above")
def test_property_decorator_baseclass_doc(self):
base = BaseClass()
self.assertEqual(base.__class__.spam.__doc__, "BaseClass.getter")
- # TODO: RUSTPYTHON
- @unittest.expectedFailure
def test_property_decorator_doc(self):
base = PropertyDocBase()
sub = PropertyDocSub()
self.assertEqual(base.__class__.spam.__doc__, "spam spam spam")
self.assertEqual(sub.__class__.spam.__doc__, "spam spam spam")
- # TODO: RUSTPYTHON
- @unittest.expectedFailure
@unittest.skipIf(sys.flags.optimize >= 2,
"Docstrings are omitted with -O2 and above")
def test_property_getter_doc_override(self):
@@ -136,8 +128,6 @@ def test_property_getter_doc_override(self):
self.assertEqual(newgetter.spam, 8)
self.assertEqual(newgetter.__class__.spam.__doc__, "new docstring")
- # TODO: RUSTPYTHON
- @unittest.expectedFailure
def test_property___isabstractmethod__descriptor(self):
for val in (True, False, [], [1], '', '1'):
class C(object):
@@ -169,8 +159,6 @@ def test_property_builtin_doc_writable(self):
p.__doc__ = 'extended'
self.assertEqual(p.__doc__, 'extended')
- # TODO: RUSTPYTHON
- @unittest.expectedFailure
@unittest.skipIf(sys.flags.optimize >= 2,
"Docstrings are omitted with -O2 and above")
def test_property_decorator_doc_writable(self):
@@ -268,8 +256,6 @@ def spam(self):
else:
raise Exception("AttributeError not raised")
- # TODO: RUSTPYTHON
- @unittest.expectedFailure
@unittest.skipIf(sys.flags.optimize >= 2,
"Docstrings are omitted with -O2 and above")
def test_docstring_copy(self):
@@ -282,8 +268,6 @@ def spam(self):
Foo.spam.__doc__,
"spam wrapped in property subclass")
- # TODO: RUSTPYTHON
- @unittest.expectedFailure
@unittest.skipIf(sys.flags.optimize >= 2,
"Docstrings are omitted with -O2 and above")
def test_property_setter_copies_getter_docstring(self):
@@ -317,8 +301,6 @@ def spam(self, value):
FooSub.spam.__doc__,
"spam wrapped in property subclass")
- # TODO: RUSTPYTHON
- @unittest.expectedFailure
@unittest.skipIf(sys.flags.optimize >= 2,
"Docstrings are omitted with -O2 and above")
def test_property_new_getter_new_docstring(self):
@@ -358,20 +340,14 @@ def _format_exc_msg(self, msg):
def setUpClass(cls):
cls.obj = cls.cls()
- # TODO: RUSTPYTHON
- @unittest.expectedFailure
def test_get_property(self):
with self.assertRaisesRegex(AttributeError, self._format_exc_msg("has no getter")):
self.obj.foo
- # TODO: RUSTPYTHON
- @unittest.expectedFailure
def test_set_property(self):
with self.assertRaisesRegex(AttributeError, self._format_exc_msg("has no setter")):
self.obj.foo = None
- # TODO: RUSTPYTHON
- @unittest.expectedFailure
def test_del_property(self):
with self.assertRaisesRegex(AttributeError, self._format_exc_msg("has no deleter")):
del self.obj.foo
diff --git a/Lib/test/test_quopri.py b/Lib/test/test_quopri.py
index 715544c8a9..152d1858dc 100644
--- a/Lib/test/test_quopri.py
+++ b/Lib/test/test_quopri.py
@@ -3,6 +3,7 @@
import sys, io, subprocess
import quopri
+from test import support
ENCSAMPLE = b"""\
@@ -180,6 +181,7 @@ def test_decode_header(self):
for p, e in self.HSTRINGS:
self.assertEqual(quopri.decodestring(e, header=True), p)
+ @support.requires_subprocess()
def test_scriptencode(self):
(p, e) = self.STRINGS[-1]
process = subprocess.Popen([sys.executable, "-mquopri"],
@@ -196,6 +198,7 @@ def test_scriptencode(self):
self.assertEqual(cout[i], e[i])
self.assertEqual(cout, e)
+ @support.requires_subprocess()
def test_scriptdecode(self):
(p, e) = self.STRINGS[-1]
process = subprocess.Popen([sys.executable, "-mquopri", "-d"],
diff --git a/Lib/test/test_raise.py b/Lib/test/test_raise.py
index 94f42c84f1..3ada08f7dc 100644
--- a/Lib/test/test_raise.py
+++ b/Lib/test/test_raise.py
@@ -270,8 +270,6 @@ def test_attrs(self):
tb.tb_next = new_tb
self.assertIs(tb.tb_next, new_tb)
- # TODO: RUSTPYTHON
- @unittest.expectedFailure
def test_constructor(self):
other_tb = get_tb()
frame = sys._getframe()
diff --git a/Lib/test/test_reprlib.py b/Lib/test/test_reprlib.py
index f84dec1ed9..738b48f562 100644
--- a/Lib/test/test_reprlib.py
+++ b/Lib/test/test_reprlib.py
@@ -82,8 +82,6 @@ def test_tuple(self):
expected = repr(t3)[:-2] + "+++)"
eq(r3.repr(t3), expected)
- # TODO: RUSTPYTHON
- @unittest.expectedFailure
def test_container(self):
from array import array
from collections import deque
@@ -178,8 +176,6 @@ def test_instance(self):
self.assertTrue(s.endswith(">"))
self.assertIn(s.find("..."), [12, 13])
- # TODO: RUSTPYTHON
- @unittest.expectedFailure
def test_lambda(self):
r = repr(lambda x: x)
self.assertTrue(r.startswith(".')), '')
+ self.assertTypedEqual(ascii(WithRepr(StrSubclass(''))), StrSubclass(''))
+ self.assertTypedEqual(ascii(WithRepr('<\U0001f40d>')), r'<\U0001f40d>')
+ self.assertTypedEqual(ascii(WithRepr(StrSubclass('<\U0001f40d>'))), r'<\U0001f40d>')
+ self.assertRaises(TypeError, ascii, WithRepr(b'byte-repr'))
def test_repr(self):
# Test basic sanity of repr()
@@ -169,10 +193,13 @@ def test_repr(self):
self.assertEqual(repr("\U00010000" * 39 + "\uffff" * 4096),
repr("\U00010000" * 39 + "\uffff" * 4096))
- class WrongRepr:
- def __repr__(self):
- return b'byte-repr'
- self.assertRaises(TypeError, repr, WrongRepr())
+ self.assertTypedEqual(repr('\U0001f40d'), "'\U0001f40d'")
+ self.assertTypedEqual(repr(StrSubclass('abc')), "'abc'")
+ self.assertTypedEqual(repr(WithRepr('')), '')
+ self.assertTypedEqual(repr(WithRepr(StrSubclass(''))), StrSubclass(''))
+ self.assertTypedEqual(repr(WithRepr('<\U0001f40d>')), '<\U0001f40d>')
+ self.assertTypedEqual(repr(WithRepr(StrSubclass('<\U0001f40d>'))), StrSubclass('<\U0001f40d>'))
+ self.assertRaises(TypeError, repr, WithRepr(b'byte-repr'))
def test_iterators(self):
# Make sure unicode objects have an __iter__ method
@@ -213,7 +240,7 @@ def test_pickle_iterator(self):
self.assertEqual(case, pickled)
def test_count(self):
- string_tests.CommonTest.test_count(self)
+ string_tests.StringLikeTest.test_count(self)
# check mixed argument types
self.checkequalnofix(3, 'aaa', 'count', 'a')
self.checkequalnofix(0, 'aaa', 'count', 'b')
@@ -243,7 +270,7 @@ class MyStr(str):
self.checkequal(3, MyStr('aaa'), 'count', 'a')
def test_find(self):
- string_tests.CommonTest.test_find(self)
+ string_tests.StringLikeTest.test_find(self)
# test implementation details of the memchr fast path
self.checkequal(100, 'a' * 100 + '\u0102', 'find', '\u0102')
self.checkequal(-1, 'a' * 100 + '\u0102', 'find', '\u0201')
@@ -288,7 +315,7 @@ def test_find(self):
self.checkequal(-1, '\u0102' * 100, 'find', '\u0102\U00100304')
def test_rfind(self):
- string_tests.CommonTest.test_rfind(self)
+ string_tests.StringLikeTest.test_rfind(self)
# test implementation details of the memrchr fast path
self.checkequal(0, '\u0102' + 'a' * 100 , 'rfind', '\u0102')
self.checkequal(-1, '\u0102' + 'a' * 100 , 'rfind', '\u0201')
@@ -329,7 +356,7 @@ def test_rfind(self):
self.checkequal(-1, '\u0102' * 100, 'rfind', '\U00100304\u0102')
def test_index(self):
- string_tests.CommonTest.test_index(self)
+ string_tests.StringLikeTest.test_index(self)
self.checkequalnofix(0, 'abcdefghiabc', 'index', '')
self.checkequalnofix(3, 'abcdefghiabc', 'index', 'def')
self.checkequalnofix(0, 'abcdefghiabc', 'index', 'abc')
@@ -353,7 +380,7 @@ def test_index(self):
self.assertRaises(ValueError, ('\u0102' * 100).index, '\u0102\U00100304')
def test_rindex(self):
- string_tests.CommonTest.test_rindex(self)
+ string_tests.StringLikeTest.test_rindex(self)
self.checkequalnofix(12, 'abcdefghiabc', 'rindex', '')
self.checkequalnofix(3, 'abcdefghiabc', 'rindex', 'def')
self.checkequalnofix(9, 'abcdefghiabc', 'rindex', 'abc')
@@ -378,8 +405,6 @@ def test_rindex(self):
self.assertRaises(ValueError, ('a' * 100).rindex, '\U00100304a')
self.assertRaises(ValueError, ('\u0102' * 100).rindex, '\U00100304\u0102')
- # TODO: RUSTPYTHON
- @unittest.expectedFailure
def test_maketrans_translate(self):
# these work with plain translate()
self.checkequalnofix('bbbc', 'abababc', 'translate',
@@ -451,7 +476,7 @@ def test_maketrans_translate(self):
self.assertRaises(TypeError, 'abababc'.translate, 'abc', 'xyz')
def test_split(self):
- string_tests.CommonTest.test_split(self)
+ string_tests.StringLikeTest.test_split(self)
# test mixed kinds
for left, right in ('ba', '\u0101\u0100', '\U00010301\U00010300'):
@@ -468,7 +493,7 @@ def test_split(self):
left + delim * 2 + right, 'split', delim *2)
def test_rsplit(self):
- string_tests.CommonTest.test_rsplit(self)
+ string_tests.StringLikeTest.test_rsplit(self)
# test mixed kinds
for left, right in ('ba', 'юё', '\u0101\u0100', '\U00010301\U00010300'):
left *= 9
@@ -488,7 +513,7 @@ def test_rsplit(self):
left + right, 'rsplit', None)
def test_partition(self):
- string_tests.MixinStrUnicodeUserStringTest.test_partition(self)
+ string_tests.StringLikeTest.test_partition(self)
# test mixed kinds
self.checkequal(('ABCDEFGH', '', ''), 'ABCDEFGH', 'partition', '\u4200')
for left, right in ('ba', '\u0101\u0100', '\U00010301\U00010300'):
@@ -505,7 +530,7 @@ def test_partition(self):
left + delim * 2 + right, 'partition', delim * 2)
def test_rpartition(self):
- string_tests.MixinStrUnicodeUserStringTest.test_rpartition(self)
+ string_tests.StringLikeTest.test_rpartition(self)
# test mixed kinds
self.checkequal(('', '', 'ABCDEFGH'), 'ABCDEFGH', 'rpartition', '\u4200')
for left, right in ('ba', '\u0101\u0100', '\U00010301\U00010300'):
@@ -522,7 +547,7 @@ def test_rpartition(self):
left + delim * 2 + right, 'rpartition', delim * 2)
def test_join(self):
- string_tests.MixinStrUnicodeUserStringTest.test_join(self)
+ string_tests.StringLikeTest.test_join(self)
class MyWrapper:
def __init__(self, sval): self.sval = sval
@@ -550,7 +575,7 @@ def test_join_overflow(self):
self.assertRaises(OverflowError, ''.join, seq)
def test_replace(self):
- string_tests.CommonTest.test_replace(self)
+ string_tests.StringLikeTest.test_replace(self)
# method call forwarded from str implementation because of unicode argument
self.checkequalnofix('one@two!three!', 'one!two!three!', 'replace', '!', '@', 1)
@@ -833,6 +858,15 @@ def test_isprintable(self):
self.assertTrue('\U0001F46F'.isprintable())
self.assertFalse('\U000E0020'.isprintable())
+ @support.requires_resource('cpu')
+ def test_isprintable_invariant(self):
+ for codepoint in range(sys.maxunicode + 1):
+ char = chr(codepoint)
+ category = unicodedata.category(char)
+ self.assertEqual(char.isprintable(),
+ category[0] not in ('C', 'Z')
+ or char == ' ')
+
def test_surrogates(self):
for s in ('a\uD800b\uDFFF', 'a\uDFFFb\uD800',
'a\uD800b\uDFFFa', 'a\uDFFFb\uD800a'):
@@ -861,7 +895,7 @@ def test_surrogates(self):
def test_lower(self):
- string_tests.CommonTest.test_lower(self)
+ string_tests.StringLikeTest.test_lower(self)
self.assertEqual('\U00010427'.lower(), '\U0001044F')
self.assertEqual('\U00010427\U00010427'.lower(),
'\U0001044F\U0001044F')
@@ -892,7 +926,7 @@ def test_casefold(self):
self.assertEqual('\u00b5'.casefold(), '\u03bc')
def test_upper(self):
- string_tests.CommonTest.test_upper(self)
+ string_tests.StringLikeTest.test_upper(self)
self.assertEqual('\U0001044F'.upper(), '\U00010427')
self.assertEqual('\U0001044F\U0001044F'.upper(),
'\U00010427\U00010427')
@@ -911,7 +945,7 @@ def test_upper(self):
# TODO: RUSTPYTHON
@unittest.expectedFailure
def test_capitalize(self):
- string_tests.CommonTest.test_capitalize(self)
+ string_tests.StringLikeTest.test_capitalize(self)
self.assertEqual('\U0001044F'.capitalize(), '\U00010427')
self.assertEqual('\U0001044F\U0001044F'.capitalize(),
'\U00010427\U0001044F')
@@ -949,7 +983,7 @@ def test_title(self):
# TODO: RUSTPYTHON
@unittest.expectedFailure
def test_swapcase(self):
- string_tests.CommonTest.test_swapcase(self)
+ string_tests.StringLikeTest.test_swapcase(self)
self.assertEqual('\U0001044F'.swapcase(), '\U00010427')
self.assertEqual('\U00010427'.swapcase(), '\U0001044F')
self.assertEqual('\U0001044F\U0001044F'.swapcase(),
@@ -975,7 +1009,7 @@ def test_swapcase(self):
self.assertEqual('\u1fd2'.swapcase(), '\u0399\u0308\u0300')
def test_center(self):
- string_tests.CommonTest.test_center(self)
+ string_tests.StringLikeTest.test_center(self)
self.assertEqual('x'.center(2, '\U0010FFFF'),
'x\U0010FFFF')
self.assertEqual('x'.center(3, '\U0010FFFF'),
@@ -1485,7 +1519,7 @@ def __format__(self, spec):
# TODO: RUSTPYTHON
@unittest.expectedFailure
def test_formatting(self):
- string_tests.MixinStrUnicodeUserStringTest.test_formatting(self)
+ string_tests.StringLikeTest.test_formatting(self)
# Testing Unicode formatting strings...
self.assertEqual("%s, %s" % ("abc", "abc"), 'abc, abc')
self.assertEqual("%s, %s, %i, %f, %5.2f" % ("abc", "abc", 1, 2, 3), 'abc, abc, 1, 2.000000, 3.00')
@@ -1661,7 +1695,7 @@ def test_startswith_endswith_errors(self):
self.assertIn('str', exc)
self.assertIn('tuple', exc)
- @support.run_with_locale('LC_ALL', 'de_DE', 'fr_FR')
+ @support.run_with_locale('LC_ALL', 'de_DE', 'fr_FR', '')
def test_format_float(self):
# should not format with a comma, but always with C locale
self.assertEqual('1.0', '%.1f' % 1.0)
@@ -1732,8 +1766,6 @@ def __str__(self):
'character buffers are decoded to unicode'
)
- self.assertRaises(TypeError, str, 42, 42, 42)
-
# TODO: RUSTPYTHON
@unittest.expectedFailure
def test_constructor_keyword_args(self):
@@ -1912,6 +1944,12 @@ def test_utf8_decode_invalid_sequences(self):
self.assertRaises(UnicodeDecodeError,
(b'\xF4'+cb+b'\xBF\xBF').decode, 'utf-8')
+ def test_issue127903(self):
+ # gh-127903: ``_copy_characters`` crashes on DEBUG builds when
+ # there is nothing to copy.
+ d = datetime.datetime(2013, 11, 10, 14, 20, 59)
+ self.assertEqual(d.strftime('%z'), '')
+
def test_issue8271(self):
# Issue #8271: during the decoding of an invalid UTF-8 byte sequence,
# only the start byte and the continuation byte(s) are now considered
@@ -2398,28 +2436,37 @@ def test_ucs4(self):
@unittest.expectedFailure
def test_conversion(self):
# Make sure __str__() works properly
- class ObjectToStr:
- def __str__(self):
- return "foo"
-
- class StrSubclassToStr(str):
- def __str__(self):
- return "foo"
-
- class StrSubclassToStrSubclass(str):
- def __new__(cls, content=""):
- return str.__new__(cls, 2*content)
- def __str__(self):
+ class StrWithStr(str):
+ def __new__(cls, value):
+ self = str.__new__(cls, "")
+ self.value = value
return self
+ def __str__(self):
+ return self.value
- self.assertEqual(str(ObjectToStr()), "foo")
- self.assertEqual(str(StrSubclassToStr("bar")), "foo")
- s = str(StrSubclassToStrSubclass("foo"))
- self.assertEqual(s, "foofoo")
- self.assertIs(type(s), StrSubclassToStrSubclass)
- s = StrSubclass(StrSubclassToStrSubclass("foo"))
- self.assertEqual(s, "foofoo")
- self.assertIs(type(s), StrSubclass)
+ self.assertTypedEqual(str(WithStr('abc')), 'abc')
+ self.assertTypedEqual(str(WithStr(StrSubclass('abc'))), StrSubclass('abc'))
+ self.assertTypedEqual(StrSubclass(WithStr('abc')), StrSubclass('abc'))
+ self.assertTypedEqual(StrSubclass(WithStr(StrSubclass('abc'))),
+ StrSubclass('abc'))
+ self.assertTypedEqual(StrSubclass(WithStr(OtherStrSubclass('abc'))),
+ StrSubclass('abc'))
+
+ self.assertTypedEqual(str(StrWithStr('abc')), 'abc')
+ self.assertTypedEqual(str(StrWithStr(StrSubclass('abc'))), StrSubclass('abc'))
+ self.assertTypedEqual(StrSubclass(StrWithStr('abc')), StrSubclass('abc'))
+ self.assertTypedEqual(StrSubclass(StrWithStr(StrSubclass('abc'))),
+ StrSubclass('abc'))
+ self.assertTypedEqual(StrSubclass(StrWithStr(OtherStrSubclass('abc'))),
+ StrSubclass('abc'))
+
+ self.assertTypedEqual(str(WithRepr('')), '')
+ self.assertTypedEqual(str(WithRepr(StrSubclass(''))), StrSubclass(''))
+ self.assertTypedEqual(StrSubclass(WithRepr('')), StrSubclass(''))
+ self.assertTypedEqual(StrSubclass(WithRepr(StrSubclass(''))),
+ StrSubclass(''))
+ self.assertTypedEqual(StrSubclass(WithRepr(OtherStrSubclass(''))),
+ StrSubclass(''))
def test_unicode_repr(self):
class s1:
@@ -2654,6 +2701,49 @@ def test_check_encoding_errors(self):
proc = assert_python_failure('-X', 'dev', '-c', code)
self.assertEqual(proc.rc, 10, proc)
+ # TODO: RUSTPYTHON
+ @unittest.expectedFailure
+ def test_str_invalid_call(self):
+ # too many args
+ with self.assertRaisesRegex(TypeError, r"str expected at most 3 arguments, got 4"):
+ str("too", "many", "argu", "ments")
+ with self.assertRaisesRegex(TypeError, r"str expected at most 3 arguments, got 4"):
+ str(1, "", "", 1)
+
+ # no such kw arg
+ with self.assertRaisesRegex(TypeError, r"str\(\) got an unexpected keyword argument 'test'"):
+ str(test=1)
+
+ # 'encoding' must be str
+ with self.assertRaisesRegex(TypeError, r"str\(\) argument 'encoding' must be str, not int"):
+ str(1, 1)
+ with self.assertRaisesRegex(TypeError, r"str\(\) argument 'encoding' must be str, not int"):
+ str(1, encoding=1)
+ with self.assertRaisesRegex(TypeError, r"str\(\) argument 'encoding' must be str, not bytes"):
+ str(b"x", b"ascii")
+ with self.assertRaisesRegex(TypeError, r"str\(\) argument 'encoding' must be str, not bytes"):
+ str(b"x", encoding=b"ascii")
+
+ # 'errors' must be str
+ with self.assertRaisesRegex(TypeError, r"str\(\) argument 'encoding' must be str, not int"):
+ str(1, 1, 1)
+ with self.assertRaisesRegex(TypeError, r"str\(\) argument 'errors' must be str, not int"):
+ str(1, errors=1)
+ with self.assertRaisesRegex(TypeError, r"str\(\) argument 'errors' must be str, not int"):
+ str(1, "", errors=1)
+ with self.assertRaisesRegex(TypeError, r"str\(\) argument 'errors' must be str, not bytes"):
+ str(b"x", "ascii", b"strict")
+ with self.assertRaisesRegex(TypeError, r"str\(\) argument 'errors' must be str, not bytes"):
+ str(b"x", "ascii", errors=b"strict")
+
+ # both positional and kwarg
+ with self.assertRaisesRegex(TypeError, r"argument for str\(\) given by name \('encoding'\) and position \(2\)"):
+ str(b"x", "utf-8", encoding="ascii")
+ with self.assertRaisesRegex(TypeError, r"str\(\) takes at most 3 arguments \(4 given\)"):
+ str(b"x", "utf-8", "ignore", encoding="ascii")
+ with self.assertRaisesRegex(TypeError, r"str\(\) takes at most 3 arguments \(4 given\)"):
+ str(b"x", "utf-8", "strict", errors="ignore")
+
class StringModuleTest(unittest.TestCase):
def test_formatter_parser(self):
diff --git a/Lib/test/test_strftime.py b/Lib/test/test_strftime.py
index 08ccebb9ed..f5024d8e6d 100644
--- a/Lib/test/test_strftime.py
+++ b/Lib/test/test_strftime.py
@@ -63,7 +63,6 @@ def setUp(self):
setlocale(LC_TIME, 'C')
self.addCleanup(setlocale, LC_TIME, saved_locale)
- @unittest.skip("TODO: RUSTPYTHON, thread 'main' panicked at 'a Display implementation returned an error unexpectedly: Error'")
def test_strftime(self):
now = time.time()
self._update_variables(now)
diff --git a/Lib/test/test_string_literals.py b/Lib/test/test_string_literals.py
index 537c8fc5c8..098e8d3984 100644
--- a/Lib/test/test_string_literals.py
+++ b/Lib/test/test_string_literals.py
@@ -111,26 +111,92 @@ def test_eval_str_invalid_escape(self):
for b in range(1, 128):
if b in b"""\n\r"'01234567NU\\abfnrtuvx""":
continue
- with self.assertWarns(DeprecationWarning):
+ with self.assertWarns(SyntaxWarning):
self.assertEqual(eval(r"'\%c'" % b), '\\' + chr(b))
with warnings.catch_warnings(record=True) as w:
- warnings.simplefilter('always', category=DeprecationWarning)
+ warnings.simplefilter('always', category=SyntaxWarning)
eval("'''\n\\z'''")
self.assertEqual(len(w), 1)
+ self.assertEqual(str(w[0].message), r"invalid escape sequence '\z'")
self.assertEqual(w[0].filename, '')
- self.assertEqual(w[0].lineno, 1)
+ self.assertEqual(w[0].lineno, 2)
with warnings.catch_warnings(record=True) as w:
- warnings.simplefilter('error', category=DeprecationWarning)
+ warnings.simplefilter('error', category=SyntaxWarning)
with self.assertRaises(SyntaxError) as cm:
eval("'''\n\\z'''")
exc = cm.exception
self.assertEqual(w, [])
+ self.assertEqual(exc.msg, r"invalid escape sequence '\z'")
self.assertEqual(exc.filename, '')
- self.assertEqual(exc.lineno, 1)
+ self.assertEqual(exc.lineno, 2)
self.assertEqual(exc.offset, 1)
+ # Check that the warning is raised only once if there are syntax errors
+
+ with warnings.catch_warnings(record=True) as w:
+ warnings.simplefilter('always', category=SyntaxWarning)
+ with self.assertRaises(SyntaxError) as cm:
+ eval("'\\e' $")
+ exc = cm.exception
+ self.assertEqual(len(w), 1)
+ self.assertEqual(w[0].category, SyntaxWarning)
+ self.assertRegex(str(w[0].message), 'invalid escape sequence')
+ self.assertEqual(w[0].filename, '')
+
+ # TODO: RUSTPYTHON
+ @unittest.expectedFailure
+ def test_eval_str_invalid_octal_escape(self):
+ for i in range(0o400, 0o1000):
+ with self.assertWarns(SyntaxWarning):
+ self.assertEqual(eval(r"'\%o'" % i), chr(i))
+
+ with warnings.catch_warnings(record=True) as w:
+ warnings.simplefilter('always', category=SyntaxWarning)
+ eval("'''\n\\407'''")
+ self.assertEqual(len(w), 1)
+ self.assertEqual(str(w[0].message),
+ r"invalid octal escape sequence '\407'")
+ self.assertEqual(w[0].filename, '')
+ self.assertEqual(w[0].lineno, 2)
+
+ with warnings.catch_warnings(record=True) as w:
+ warnings.simplefilter('error', category=SyntaxWarning)
+ with self.assertRaises(SyntaxError) as cm:
+ eval("'''\n\\407'''")
+ exc = cm.exception
+ self.assertEqual(w, [])
+ self.assertEqual(exc.msg, r"invalid octal escape sequence '\407'")
+ self.assertEqual(exc.filename, '')
+ self.assertEqual(exc.lineno, 2)
+ self.assertEqual(exc.offset, 1)
+
+ # TODO: RUSTPYTHON
+ @unittest.expectedFailure
+ def test_invalid_escape_locations_with_offset(self):
+ with warnings.catch_warnings(record=True) as w:
+ warnings.simplefilter('error', category=SyntaxWarning)
+ with self.assertRaises(SyntaxError) as cm:
+ eval("\"'''''''''''''''''''''invalid\\ Escape\"")
+ exc = cm.exception
+ self.assertEqual(w, [])
+ self.assertEqual(exc.msg, r"invalid escape sequence '\ '")
+ self.assertEqual(exc.filename, '')
+ self.assertEqual(exc.lineno, 1)
+ self.assertEqual(exc.offset, 30)
+
+ with warnings.catch_warnings(record=True) as w:
+ warnings.simplefilter('error', category=SyntaxWarning)
+ with self.assertRaises(SyntaxError) as cm:
+ eval("\"''Incorrect \\ logic?\"")
+ exc = cm.exception
+ self.assertEqual(w, [])
+ self.assertEqual(exc.msg, r"invalid escape sequence '\ '")
+ self.assertEqual(exc.filename, '')
+ self.assertEqual(exc.lineno, 1)
+ self.assertEqual(exc.offset, 14)
+
def test_eval_str_raw(self):
self.assertEqual(eval(""" r'x' """), 'x')
self.assertEqual(eval(r""" r'\x01' """), '\\' + 'x01')
@@ -163,24 +229,52 @@ def test_eval_bytes_invalid_escape(self):
for b in range(1, 128):
if b in b"""\n\r"'01234567\\abfnrtvx""":
continue
- with self.assertWarns(DeprecationWarning):
+ with self.assertWarns(SyntaxWarning):
self.assertEqual(eval(r"b'\%c'" % b), b'\\' + bytes([b]))
with warnings.catch_warnings(record=True) as w:
- warnings.simplefilter('always', category=DeprecationWarning)
+ warnings.simplefilter('always', category=SyntaxWarning)
eval("b'''\n\\z'''")
self.assertEqual(len(w), 1)
+ self.assertEqual(str(w[0].message), r"invalid escape sequence '\z'")
self.assertEqual(w[0].filename, '')
- self.assertEqual(w[0].lineno, 1)
+ self.assertEqual(w[0].lineno, 2)
with warnings.catch_warnings(record=True) as w:
- warnings.simplefilter('error', category=DeprecationWarning)
+ warnings.simplefilter('error', category=SyntaxWarning)
with self.assertRaises(SyntaxError) as cm:
eval("b'''\n\\z'''")
exc = cm.exception
self.assertEqual(w, [])
+ self.assertEqual(exc.msg, r"invalid escape sequence '\z'")
self.assertEqual(exc.filename, '')
- self.assertEqual(exc.lineno, 1)
+ self.assertEqual(exc.lineno, 2)
+
+ # TODO: RUSTPYTHON
+ @unittest.expectedFailure
+ def test_eval_bytes_invalid_octal_escape(self):
+ for i in range(0o400, 0o1000):
+ with self.assertWarns(SyntaxWarning):
+ self.assertEqual(eval(r"b'\%o'" % i), bytes([i & 0o377]))
+
+ with warnings.catch_warnings(record=True) as w:
+ warnings.simplefilter('always', category=SyntaxWarning)
+ eval("b'''\n\\407'''")
+ self.assertEqual(len(w), 1)
+ self.assertEqual(str(w[0].message),
+ r"invalid octal escape sequence '\407'")
+ self.assertEqual(w[0].filename, '')
+ self.assertEqual(w[0].lineno, 2)
+
+ with warnings.catch_warnings(record=True) as w:
+ warnings.simplefilter('error', category=SyntaxWarning)
+ with self.assertRaises(SyntaxError) as cm:
+ eval("b'''\n\\407'''")
+ exc = cm.exception
+ self.assertEqual(w, [])
+ self.assertEqual(exc.msg, r"invalid octal escape sequence '\407'")
+ self.assertEqual(exc.filename, '')
+ self.assertEqual(exc.lineno, 2)
def test_eval_bytes_raw(self):
self.assertEqual(eval(""" br'x' """), b'x')
@@ -217,6 +311,13 @@ def test_eval_str_u(self):
self.assertRaises(SyntaxError, eval, """ bu'' """)
self.assertRaises(SyntaxError, eval, """ ub'' """)
+ def test_uppercase_prefixes(self):
+ self.assertEqual(eval(""" B'x' """), b'x')
+ self.assertEqual(eval(r""" R'\x01' """), r'\x01')
+ self.assertEqual(eval(r""" BR'\x01' """), br'\x01')
+ self.assertEqual(eval(""" F'{1+1}' """), f'{1+1}')
+ self.assertEqual(eval(r""" U'\U0001d120' """), u'\U0001d120')
+
def check_encoding(self, encoding, extra=""):
modname = "xx_" + encoding.replace("-", "_")
fn = os.path.join(self.tmpdir, modname + ".py")
diff --git a/Lib/test/test_struct.py b/Lib/test/test_struct.py
index bc801a08d6..ef5602d083 100644
--- a/Lib/test/test_struct.py
+++ b/Lib/test/test_struct.py
@@ -718,8 +718,6 @@ def test__struct_types_immutable(self):
cls.x = 1
- # TODO: RUSTPYTHON
- @unittest.expectedFailure
def test_issue35714(self):
# Embedded null characters should not be allowed in format strings.
for s in '\0', '2\0i', b'\0':
@@ -790,8 +788,6 @@ def __init__(self):
my_struct = MyStruct()
self.assertEqual(my_struct.pack(12345), b'\x30\x39')
- # TODO: RUSTPYTHON
- @unittest.expectedFailure
def test_repr(self):
s = struct.Struct('=i2H')
self.assertEqual(repr(s), f'Struct({s.format!r})')
@@ -822,8 +818,6 @@ def _check_iterator(it):
with self.assertRaises(struct.error):
s.iter_unpack(b"12")
- # TODO: RUSTPYTHON
- @unittest.expectedFailure
def test_uninstantiable(self):
iter_unpack_type = type(struct.Struct(">ibcp").iter_unpack(b""))
self.assertRaises(TypeError, iter_unpack_type)
diff --git a/Lib/test/test_sys.py b/Lib/test/test_sys.py
index 590d9c8df8..1ce2e9fc0f 100644
--- a/Lib/test/test_sys.py
+++ b/Lib/test/test_sys.py
@@ -167,8 +167,6 @@ def test_excepthook_bytes_filename(self):
self.assertIn(""" text\n""", err)
self.assertTrue(err.endswith("SyntaxError: msg\n"))
- # TODO: RUSTPYTHON, print argument error to stderr in sys.excepthook instead of throwing
- @unittest.expectedFailure
def test_excepthook(self):
with test.support.captured_output("stderr") as stderr:
sys.excepthook(1, '1', 1)
@@ -260,8 +258,6 @@ def test_getdefaultencoding(self):
# testing sys.settrace() is done in test_sys_settrace.py
# testing sys.setprofile() is done in test_sys_setprofile.py
- # TODO: RUSTPYTHON, AttributeError: module 'sys' has no attribute 'setswitchinterval'
- @unittest.expectedFailure
def test_switchinterval(self):
self.assertRaises(TypeError, sys.setswitchinterval)
self.assertRaises(TypeError, sys.setswitchinterval, "a")
diff --git a/Lib/test/test_syslog.py b/Lib/test/test_syslog.py
index 96945bfd8b..b378d62e5c 100644
--- a/Lib/test/test_syslog.py
+++ b/Lib/test/test_syslog.py
@@ -55,8 +55,6 @@ def test_openlog_noargs(self):
syslog.openlog()
syslog.syslog('test message from python test_syslog')
- # TODO: RUSTPYTHON; AttributeError: module 'sys' has no attribute 'getswitchinterval'
- @unittest.expectedFailure
@threading_helper.requires_working_threading()
def test_syslog_threaded(self):
start = threading.Event()
diff --git a/Lib/test/test_threading.py b/Lib/test/test_threading.py
index 92ff3dc380..94f21d1c38 100644
--- a/Lib/test/test_threading.py
+++ b/Lib/test/test_threading.py
@@ -412,8 +412,6 @@ def child():
b"Woke up, sleep function is: ")
self.assertEqual(err, b"")
- # TODO: RUSTPYTHON
- @unittest.expectedFailure
def test_enumerate_after_join(self):
# Try hard to trigger #1703448: a thread is still returned in
# threading.enumerate() after it has been join()ed.
@@ -1745,8 +1743,6 @@ def run_last():
self.assertFalse(err)
self.assertEqual(out.strip(), b'parrot')
- # TODO: RUSTPYTHON
- @unittest.expectedFailure
def test_atexit_called_once(self):
rc, out, err = assert_python_ok("-c", """if True:
import threading
diff --git a/Lib/test/test_time.py b/Lib/test/test_time.py
index 3af18efae2..3886aae934 100644
--- a/Lib/test/test_time.py
+++ b/Lib/test/test_time.py
@@ -236,7 +236,6 @@ def _bounds_checking(self, func):
def test_strftime_bounding_check(self):
self._bounds_checking(lambda tup: time.strftime('', tup))
- @unittest.skip("TODO: RUSTPYTHON, thread 'main' panicked at 'a Display implementation returned an error unexpectedly: Error'")
def test_strftime_format_check(self):
# Test that strftime does not crash on invalid format strings
# that may trigger a buffer overread. When not triggered,
@@ -459,7 +458,6 @@ def test_mktime(self):
# Issue #13309: passing extreme values to mktime() or localtime()
# borks the glibc's internal timezone data.
- @unittest.skip("TODO: RUSTPYTHON, thread 'main' panicked at 'a Display implementation returned an error unexpectedly: Error'")
@unittest.skipUnless(platform.libc_ver()[0] != 'glibc',
"disabled because of a bug in glibc. Issue #13309")
def test_mktime_error(self):
diff --git a/Lib/test/test_tomllib/__main__.py b/Lib/test/test_tomllib/__main__.py
index f309c7ec72..dd06365343 100644
--- a/Lib/test/test_tomllib/__main__.py
+++ b/Lib/test/test_tomllib/__main__.py
@@ -1,6 +1,6 @@
import unittest
-from test.test_tomllib import load_tests
+from . import load_tests
unittest.main()
diff --git a/Lib/test/test_tomllib/test_misc.py b/Lib/test/test_tomllib/test_misc.py
index a477a219fd..9e677a337a 100644
--- a/Lib/test/test_tomllib/test_misc.py
+++ b/Lib/test/test_tomllib/test_misc.py
@@ -9,6 +9,7 @@
import sys
import tempfile
import unittest
+from test import support
from . import tomllib
@@ -92,13 +93,23 @@ def test_deepcopy(self):
self.assertEqual(obj_copy, expected_obj)
def test_inline_array_recursion_limit(self):
- # 465 with default recursion limit
- nest_count = int(sys.getrecursionlimit() * 0.465)
- recursive_array_toml = "arr = " + nest_count * "[" + nest_count * "]"
- tomllib.loads(recursive_array_toml)
+ with support.infinite_recursion(max_depth=100):
+ available = support.get_recursion_available()
+ nest_count = (available // 2) - 2
+ # Add details if the test fails
+ with self.subTest(limit=sys.getrecursionlimit(),
+ available=available,
+ nest_count=nest_count):
+ recursive_array_toml = "arr = " + nest_count * "[" + nest_count * "]"
+ tomllib.loads(recursive_array_toml)
def test_inline_table_recursion_limit(self):
- # 310 with default recursion limit
- nest_count = int(sys.getrecursionlimit() * 0.31)
- recursive_table_toml = nest_count * "key = {" + nest_count * "}"
- tomllib.loads(recursive_table_toml)
+ with support.infinite_recursion(max_depth=100):
+ available = support.get_recursion_available()
+ nest_count = (available // 3) - 1
+ # Add details if the test fails
+ with self.subTest(limit=sys.getrecursionlimit(),
+ available=available,
+ nest_count=nest_count):
+ recursive_table_toml = nest_count * "key = {" + nest_count * "}"
+ tomllib.loads(recursive_table_toml)
diff --git a/Lib/test/test_traceback.py b/Lib/test/test_traceback.py
index 28a8697235..9d95903d52 100644
--- a/Lib/test/test_traceback.py
+++ b/Lib/test/test_traceback.py
@@ -4,26 +4,54 @@
from io import StringIO
import linecache
import sys
+import types
import inspect
+import builtins
import unittest
+import unittest.mock
import re
+import tempfile
+import random
+import string
from test import support
-from test.support import Error, captured_output, cpython_only, ALWAYS_EQ
+import shutil
+from test.support import (Error, captured_output, cpython_only, ALWAYS_EQ,
+ requires_debug_ranges, has_no_debug_ranges,
+ requires_subprocess)
from test.support.os_helper import TESTFN, unlink
-from test.support.script_helper import assert_python_ok
-import textwrap
+from test.support.script_helper import assert_python_ok, assert_python_failure
+from test.support.import_helper import forget
+from test.support import force_not_colorized, force_not_colorized_test_class
+import json
+import textwrap
import traceback
+from functools import partial
+from pathlib import Path
+import _colorize
+MODULE_PREFIX = f'{__name__}.' if __name__ == '__main__' else ''
test_code = namedtuple('code', ['co_filename', 'co_name'])
+test_code.co_positions = lambda _: iter([(6, 6, 0, 0)])
test_frame = namedtuple('frame', ['f_code', 'f_globals', 'f_locals'])
-test_tb = namedtuple('tb', ['tb_frame', 'tb_lineno', 'tb_next'])
+test_tb = namedtuple('tb', ['tb_frame', 'tb_lineno', 'tb_next', 'tb_lasti'])
+
+
+LEVENSHTEIN_DATA_FILE = Path(__file__).parent / 'levenshtein_examples.json'
class TracebackCases(unittest.TestCase):
# For now, a very minimal set of tests. I want to be sure that
# formatting of SyntaxErrors works based on changes for 2.1.
+ def setUp(self):
+ super().setUp()
+ self.colorize = _colorize.COLORIZE
+ _colorize.COLORIZE = False
+
+ def tearDown(self):
+ super().tearDown()
+ _colorize.COLORIZE = self.colorize
def get_exception_format(self, func, exc):
try:
@@ -93,14 +121,81 @@ def test_caret(self):
self.assertEqual(err[1].find("("), err[2].find("^")) # in the right place
self.assertEqual(err[2].count("^"), 1)
- # TODO: RUSTPYTHON
- @unittest.expectedFailure
def test_nocaret(self):
exc = SyntaxError("error", ("x.py", 23, None, "bad syntax"))
err = traceback.format_exception_only(SyntaxError, exc)
self.assertEqual(len(err), 3)
self.assertEqual(err[1].strip(), "bad syntax")
+ @force_not_colorized
+ def test_no_caret_with_no_debug_ranges_flag(self):
+ # Make sure that if `-X no_debug_ranges` is used, there are no carets
+ # in the traceback.
+ try:
+ with open(TESTFN, 'w') as f:
+ f.write("x = 1 / 0\n")
+
+ _, _, stderr = assert_python_failure(
+ '-X', 'no_debug_ranges', TESTFN)
+
+ lines = stderr.splitlines()
+ self.assertEqual(len(lines), 4)
+ self.assertEqual(lines[0], b'Traceback (most recent call last):')
+ self.assertIn(b'line 1, in ', lines[1])
+ self.assertEqual(lines[2], b' x = 1 / 0')
+ self.assertEqual(lines[3], b'ZeroDivisionError: division by zero')
+ finally:
+ unlink(TESTFN)
+
+ def test_no_caret_with_no_debug_ranges_flag_python_traceback(self):
+ code = textwrap.dedent("""
+ import traceback
+ try:
+ x = 1 / 0
+ except ZeroDivisionError:
+ traceback.print_exc()
+ """)
+ try:
+ with open(TESTFN, 'w') as f:
+ f.write(code)
+
+ _, _, stderr = assert_python_ok(
+ '-X', 'no_debug_ranges', TESTFN)
+
+ lines = stderr.splitlines()
+ self.assertEqual(len(lines), 4)
+ self.assertEqual(lines[0], b'Traceback (most recent call last):')
+ self.assertIn(b'line 4, in ', lines[1])
+ self.assertEqual(lines[2], b' x = 1 / 0')
+ self.assertEqual(lines[3], b'ZeroDivisionError: division by zero')
+ finally:
+ unlink(TESTFN)
+
+ def test_recursion_error_during_traceback(self):
+ code = textwrap.dedent("""
+ import sys
+ from weakref import ref
+
+ sys.setrecursionlimit(15)
+
+ def f():
+ ref(lambda: 0, [])
+ f()
+
+ try:
+ f()
+ except RecursionError:
+ pass
+ """)
+ try:
+ with open(TESTFN, 'w') as f:
+ f.write(code)
+
+ rc, _, _ = assert_python_ok(TESTFN)
+ self.assertEqual(rc, 0)
+ finally:
+ unlink(TESTFN)
+
# TODO: RUSTPYTHON
@unittest.expectedFailure
def test_bad_indentation(self):
@@ -123,21 +218,222 @@ def test_base_exception(self):
lst = traceback.format_exception_only(e.__class__, e)
self.assertEqual(lst, ['KeyboardInterrupt\n'])
+ # TODO: RUSTPYTHON
+ @unittest.expectedFailure
def test_format_exception_only_bad__str__(self):
class X(Exception):
def __str__(self):
1/0
err = traceback.format_exception_only(X, X())
self.assertEqual(len(err), 1)
- str_value = '' % X.__name__
+ str_value = ''
if X.__module__ in ('__main__', 'builtins'):
str_name = X.__qualname__
else:
str_name = '.'.join([X.__module__, X.__qualname__])
self.assertEqual(err[0], "%s: %s\n" % (str_name, str_value))
+ def test_format_exception_group_without_show_group(self):
+ eg = ExceptionGroup('A', [ValueError('B')])
+ err = traceback.format_exception_only(eg)
+ self.assertEqual(err, ['ExceptionGroup: A (1 sub-exception)\n'])
+
+ # TODO: RUSTPYTHON
+ @unittest.expectedFailure
+ def test_format_exception_group(self):
+ eg = ExceptionGroup('A', [ValueError('B')])
+ err = traceback.format_exception_only(eg, show_group=True)
+ self.assertEqual(err, [
+ 'ExceptionGroup: A (1 sub-exception)\n',
+ ' ValueError: B\n',
+ ])
+
+ # TODO: RUSTPYTHON
+ @unittest.expectedFailure
+ def test_format_base_exception_group(self):
+ eg = BaseExceptionGroup('A', [BaseException('B')])
+ err = traceback.format_exception_only(eg, show_group=True)
+ self.assertEqual(err, [
+ 'BaseExceptionGroup: A (1 sub-exception)\n',
+ ' BaseException: B\n',
+ ])
+
+ # TODO: RUSTPYTHON
+ @unittest.expectedFailure
+ def test_format_exception_group_with_note(self):
+ exc = ValueError('B')
+ exc.add_note('Note')
+ eg = ExceptionGroup('A', [exc])
+ err = traceback.format_exception_only(eg, show_group=True)
+ self.assertEqual(err, [
+ 'ExceptionGroup: A (1 sub-exception)\n',
+ ' ValueError: B\n',
+ ' Note\n',
+ ])
+
+ # TODO: RUSTPYTHON
+ @unittest.expectedFailure
+ def test_format_exception_group_explicit_class(self):
+ eg = ExceptionGroup('A', [ValueError('B')])
+ err = traceback.format_exception_only(ExceptionGroup, eg, show_group=True)
+ self.assertEqual(err, [
+ 'ExceptionGroup: A (1 sub-exception)\n',
+ ' ValueError: B\n',
+ ])
+
+ # TODO: RUSTPYTHON
+ @unittest.expectedFailure
+ def test_format_exception_group_multiple_exceptions(self):
+ eg = ExceptionGroup('A', [ValueError('B'), TypeError('C')])
+ err = traceback.format_exception_only(eg, show_group=True)
+ self.assertEqual(err, [
+ 'ExceptionGroup: A (2 sub-exceptions)\n',
+ ' ValueError: B\n',
+ ' TypeError: C\n',
+ ])
+
+ # TODO: RUSTPYTHON
+ @unittest.expectedFailure
+ def test_format_exception_group_multiline_messages(self):
+ eg = ExceptionGroup('A\n1', [ValueError('B\n2')])
+ err = traceback.format_exception_only(eg, show_group=True)
+ self.assertEqual(err, [
+ 'ExceptionGroup: A\n1 (1 sub-exception)\n',
+ ' ValueError: B\n',
+ ' 2\n',
+ ])
+
+ # TODO: RUSTPYTHON
+ @unittest.expectedFailure
+ def test_format_exception_group_multiline2_messages(self):
+ exc = ValueError('B\n\n2\n')
+ exc.add_note('\nC\n\n3')
+ eg = ExceptionGroup('A\n\n1\n', [exc, IndexError('D')])
+ err = traceback.format_exception_only(eg, show_group=True)
+ self.assertEqual(err, [
+ 'ExceptionGroup: A\n\n1\n (2 sub-exceptions)\n',
+ ' ValueError: B\n',
+ ' \n',
+ ' 2\n',
+ ' \n',
+ ' \n', # first char of `note`
+ ' C\n',
+ ' \n',
+ ' 3\n', # note ends
+ ' IndexError: D\n',
+ ])
+
+ # TODO: RUSTPYTHON
+ @unittest.expectedFailure
+ def test_format_exception_group_syntax_error(self):
+ exc = SyntaxError("error", ("x.py", 23, None, "bad syntax"))
+ eg = ExceptionGroup('A\n1', [exc])
+ err = traceback.format_exception_only(eg, show_group=True)
+ self.assertEqual(err, [
+ 'ExceptionGroup: A\n1 (1 sub-exception)\n',
+ ' File "x.py", line 23\n',
+ ' bad syntax\n',
+ ' SyntaxError: error\n',
+ ])
+
+ # TODO: RUSTPYTHON
+ @unittest.expectedFailure
+ def test_format_exception_group_nested_with_notes(self):
+ exc = IndexError('D')
+ exc.add_note('Note\nmultiline')
+ eg = ExceptionGroup('A', [
+ ValueError('B'),
+ ExceptionGroup('C', [exc, LookupError('E')]),
+ TypeError('F'),
+ ])
+ err = traceback.format_exception_only(eg, show_group=True)
+ self.assertEqual(err, [
+ 'ExceptionGroup: A (3 sub-exceptions)\n',
+ ' ValueError: B\n',
+ ' ExceptionGroup: C (2 sub-exceptions)\n',
+ ' IndexError: D\n',
+ ' Note\n',
+ ' multiline\n',
+ ' LookupError: E\n',
+ ' TypeError: F\n',
+ ])
+
+ # TODO: RUSTPYTHON
+ @unittest.expectedFailure
+ def test_format_exception_group_with_tracebacks(self):
+ def f():
+ try:
+ 1 / 0
+ except ZeroDivisionError as e:
+ return e
+
+ def g():
+ try:
+ raise TypeError('g')
+ except TypeError as e:
+ return e
+
+ eg = ExceptionGroup('A', [
+ f(),
+ ExceptionGroup('B', [g()]),
+ ])
+ err = traceback.format_exception_only(eg, show_group=True)
+ self.assertEqual(err, [
+ 'ExceptionGroup: A (2 sub-exceptions)\n',
+ ' ZeroDivisionError: division by zero\n',
+ ' ExceptionGroup: B (1 sub-exception)\n',
+ ' TypeError: g\n',
+ ])
+
+ # TODO: RUSTPYTHON
+ @unittest.expectedFailure
+ def test_format_exception_group_with_cause(self):
+ def f():
+ try:
+ try:
+ 1 / 0
+ except ZeroDivisionError:
+ raise ValueError(0)
+ except Exception as e:
+ return e
+
+ eg = ExceptionGroup('A', [f()])
+ err = traceback.format_exception_only(eg, show_group=True)
+ self.assertEqual(err, [
+ 'ExceptionGroup: A (1 sub-exception)\n',
+ ' ValueError: 0\n',
+ ])
+
# TODO: RUSTPYTHON
@unittest.expectedFailure
+ def test_format_exception_group_syntax_error_with_custom_values(self):
+ # See https://github.com/python/cpython/issues/128894
+ for exc in [
+ SyntaxError('error', 'abcd'),
+ SyntaxError('error', [None] * 4),
+ SyntaxError('error', (1, 2, 3, 4)),
+ SyntaxError('error', (1, 2, 3, 4)),
+ SyntaxError('error', (1, 'a', 'b', 2)),
+ # with end_lineno and end_offset:
+ SyntaxError('error', 'abcdef'),
+ SyntaxError('error', [None] * 6),
+ SyntaxError('error', (1, 2, 3, 4, 5, 6)),
+ SyntaxError('error', (1, 'a', 'b', 2, 'c', 'd')),
+ ]:
+ with self.subTest(exc=exc):
+ err = traceback.format_exception_only(exc, show_group=True)
+ # Should not raise an exception:
+ if exc.lineno is not None:
+ self.assertEqual(len(err), 2)
+ self.assertTrue(err[0].startswith(' File'))
+ else:
+ self.assertEqual(len(err), 1)
+ self.assertEqual(err[-1], 'SyntaxError: error\n')
+
+ # TODO: RUSTPYTHON; IndexError: index out of range
+ @unittest.expectedFailure
+ @requires_subprocess()
+ @force_not_colorized
def test_encoded_file(self):
# Test that tracebacks are correctly printed for encoded source files:
# - correct line number (Issue2384)
@@ -185,9 +481,10 @@ def do_test(firstlines, message, charset, lineno):
self.assertTrue(stdout[2].endswith(err_line),
"Invalid traceback line: {0!r} instead of {1!r}".format(
stdout[2], err_line))
- self.assertTrue(stdout[3] == err_msg,
+ actual_err_msg = stdout[3]
+ self.assertTrue(actual_err_msg == err_msg,
"Invalid error message: {0!r} instead of {1!r}".format(
- stdout[3], err_msg))
+ actual_err_msg, err_msg))
do_test("", "foo", "ascii", 3)
for charset in ("ascii", "iso-8859-1", "utf-8", "GBK"):
@@ -219,15 +516,15 @@ class PrintExceptionAtExit(object):
def __init__(self):
try:
x = 1 / 0
- except Exception:
- self.exc_info = sys.exc_info()
- # self.exc_info[1] (traceback) contains frames:
+ except Exception as e:
+ self.exc = e
+ # self.exc.__traceback__ contains frames:
# explicitly clear the reference to self in the current
# frame to break a reference cycle
self = None
def __del__(self):
- traceback.print_exception(*self.exc_info)
+ traceback.print_exception(self.exc)
# Keep a reference in the module namespace to call the destructor
# when the module is unloaded
@@ -236,6 +533,8 @@ def __del__(self):
rc, stdout, stderr = assert_python_ok('-c', code)
expected = [b'Traceback (most recent call last):',
b' File "", line 8, in __init__',
+ b' x = 1 / 0',
+ b' ^^^^^',
b'ZeroDivisionError: division by zero']
self.assertEqual(stderr.splitlines(), expected)
@@ -251,6 +550,16 @@ def test_print_exception_exc(self):
traceback.print_exception(Exception("projector"), file=output)
self.assertEqual(output.getvalue(), "Exception: projector\n")
+ # TODO: RUSTPYTHON
+ @unittest.expectedFailure
+ def test_print_last(self):
+ with support.swap_attr(sys, 'last_exc', ValueError(42)):
+ output = StringIO()
+ traceback.print_last(file=output)
+ self.assertEqual(output.getvalue(), "ValueError: 42\n")
+
+ # TODO: RUSTPYTHON
+ @unittest.expectedFailure
def test_format_exception_exc(self):
e = Exception("projector")
output = traceback.format_exception(e)
@@ -259,7 +568,7 @@ def test_format_exception_exc(self):
traceback.format_exception(e.__class__, e)
with self.assertRaisesRegex(ValueError, 'Both or neither'):
traceback.format_exception(e.__class__, tb=e.__traceback__)
- with self.assertRaisesRegex(TypeError, 'positional-only'):
+ with self.assertRaisesRegex(TypeError, 'required positional argument'):
traceback.format_exception(exc=e)
def test_format_exception_only_exc(self):
@@ -289,193 +598,1501 @@ def test_exception_is_None(self):
self.assertEqual(
traceback.format_exception_only(None, None), [NONE_EXC_STRING])
+ # TODO: RUSTPYTHON
+ @unittest.expectedFailure
def test_signatures(self):
self.assertEqual(
str(inspect.signature(traceback.print_exception)),
('(exc, /, value=, tb=, '
- 'limit=None, file=None, chain=True)'))
+ 'limit=None, file=None, chain=True, **kwargs)'))
self.assertEqual(
str(inspect.signature(traceback.format_exception)),
('(exc, /, value=, tb=, limit=None, '
- 'chain=True)'))
+ 'chain=True, **kwargs)'))
self.assertEqual(
str(inspect.signature(traceback.format_exception_only)),
- '(exc, /, value=)')
-
+ '(exc, /, value=, *, show_group=False, **kwargs)')
-class TracebackFormatTests(unittest.TestCase):
-
- def some_exception(self):
- raise KeyError('blah')
- @cpython_only
- def check_traceback_format(self, cleanup_func=None):
- from _testcapi import traceback_print
+class PurePythonExceptionFormattingMixin:
+ def get_exception(self, callable, slice_start=0, slice_end=-1):
try:
- self.some_exception()
- except KeyError:
- type_, value, tb = sys.exc_info()
- if cleanup_func is not None:
- # Clear the inner frames, not this one
- cleanup_func(tb.tb_next)
- traceback_fmt = 'Traceback (most recent call last):\n' + \
- ''.join(traceback.format_tb(tb))
- file_ = StringIO()
- traceback_print(tb, file_)
- python_fmt = file_.getvalue()
- # Call all _tb and _exc functions
- with captured_output("stderr") as tbstderr:
- traceback.print_tb(tb)
- tbfile = StringIO()
- traceback.print_tb(tb, file=tbfile)
- with captured_output("stderr") as excstderr:
- traceback.print_exc()
- excfmt = traceback.format_exc()
- excfile = StringIO()
- traceback.print_exc(file=excfile)
+ callable()
+ except BaseException:
+ return traceback.format_exc().splitlines()[slice_start:slice_end]
else:
- raise Error("unable to create test traceback string")
+ self.fail("No exception thrown.")
- # Make sure that Python and the traceback module format the same thing
- self.assertEqual(traceback_fmt, python_fmt)
- # Now verify the _tb func output
- self.assertEqual(tbstderr.getvalue(), tbfile.getvalue())
- # Now verify the _exc func output
- self.assertEqual(excstderr.getvalue(), excfile.getvalue())
- self.assertEqual(excfmt, excfile.getvalue())
+ callable_line = get_exception.__code__.co_firstlineno + 2
- # Make sure that the traceback is properly indented.
- tb_lines = python_fmt.splitlines()
- self.assertEqual(len(tb_lines), 5)
- banner = tb_lines[0]
- location, source_line = tb_lines[-2:]
- self.assertTrue(banner.startswith('Traceback'))
- self.assertTrue(location.startswith(' File'))
- self.assertTrue(source_line.startswith(' raise'))
- def test_traceback_format(self):
- self.check_traceback_format()
+class CAPIExceptionFormattingMixin:
+ LEGACY = 0
- def test_traceback_format_with_cleared_frames(self):
- # Check that traceback formatting also works with a clear()ed frame
- def cleanup_tb(tb):
- tb.tb_frame.clear()
- self.check_traceback_format(cleanup_tb)
+ def get_exception(self, callable, slice_start=0, slice_end=-1):
+ from _testcapi import exception_print
+ try:
+ callable()
+ self.fail("No exception thrown.")
+ except Exception as e:
+ with captured_output("stderr") as tbstderr:
+ exception_print(e, self.LEGACY)
+ return tbstderr.getvalue().splitlines()[slice_start:slice_end]
- def test_stack_format(self):
- # Verify _stack functions. Note we have to use _getframe(1) to
- # compare them without this frame appearing in the output
- with captured_output("stderr") as ststderr:
- traceback.print_stack(sys._getframe(1))
- stfile = StringIO()
- traceback.print_stack(sys._getframe(1), file=stfile)
- self.assertEqual(ststderr.getvalue(), stfile.getvalue())
+ callable_line = get_exception.__code__.co_firstlineno + 3
- stfmt = traceback.format_stack(sys._getframe(1))
+class CAPIExceptionFormattingLegacyMixin(CAPIExceptionFormattingMixin):
+ LEGACY = 1
- self.assertEqual(ststderr.getvalue(), "".join(stfmt))
+# @requires_debug_ranges() # XXX: RUSTPYTHON patch
+class TracebackErrorLocationCaretTestBase:
+ """
+ Tests for printing code error expressions as part of PEP 657
+ """
+ # TODO: RUSTPYTHON
+ @unittest.expectedFailure
+ def test_basic_caret(self):
+ # NOTE: In caret tests, "if True:" is used as a way to force indicator
+ # display, since the raising expression spans only part of the line.
+ def f():
+ if True: raise ValueError("basic caret tests")
- def test_print_stack(self):
- def prn():
- traceback.print_stack()
- with captured_output("stderr") as stderr:
- prn()
- lineno = prn.__code__.co_firstlineno
- self.assertEqual(stderr.getvalue().splitlines()[-4:], [
- ' File "%s", line %d, in test_print_stack' % (__file__, lineno+3),
- ' prn()',
- ' File "%s", line %d, in prn' % (__file__, lineno+1),
- ' traceback.print_stack()',
- ])
+ lineno_f = f.__code__.co_firstlineno
+ expected_f = (
+ 'Traceback (most recent call last):\n'
+ f' File "{__file__}", line {self.callable_line}, in get_exception\n'
+ ' callable()\n'
+ ' ~~~~~~~~^^\n'
+ f' File "{__file__}", line {lineno_f+1}, in f\n'
+ ' if True: raise ValueError("basic caret tests")\n'
+ ' ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n'
+ )
+ result_lines = self.get_exception(f)
+ self.assertEqual(result_lines, expected_f.splitlines())
- # issue 26823 - Shrink recursive tracebacks
- def _check_recursive_traceback_display(self, render_exc):
- # Always show full diffs when this test fails
- # Note that rearranging things may require adjusting
- # the relative line numbers in the expected tracebacks
- self.maxDiff = None
+ # TODO: RUSTPYTHON
+ @unittest.expectedFailure
+ def test_line_with_unicode(self):
+ # Make sure that even if a line contains multi-byte unicode characters
+ # the correct carets are printed.
+ def f_with_unicode():
+ if True: raise ValueError("Ĥellö Wörld")
+
+ lineno_f = f_with_unicode.__code__.co_firstlineno
+ expected_f = (
+ 'Traceback (most recent call last):\n'
+ f' File "{__file__}", line {self.callable_line}, in get_exception\n'
+ ' callable()\n'
+ ' ~~~~~~~~^^\n'
+ f' File "{__file__}", line {lineno_f+1}, in f_with_unicode\n'
+ ' if True: raise ValueError("Ĥellö Wörld")\n'
+ ' ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n'
+ )
+ result_lines = self.get_exception(f_with_unicode)
+ self.assertEqual(result_lines, expected_f.splitlines())
- # Check hitting the recursion limit
- def f():
- f()
+ # TODO: RUSTPYTHON
+ @unittest.expectedFailure
+ def test_caret_in_type_annotation(self):
+ def f_with_type():
+ def foo(a: THIS_DOES_NOT_EXIST ) -> int:
+ return 0
- with captured_output("stderr") as stderr_f:
- try:
- f()
- except RecursionError:
- render_exc()
- else:
- self.fail("no recursion occurred")
+ lineno_f = f_with_type.__code__.co_firstlineno
+ expected_f = (
+ 'Traceback (most recent call last):\n'
+ f' File "{__file__}", line {self.callable_line}, in get_exception\n'
+ ' callable()\n'
+ ' ~~~~~~~~^^\n'
+ f' File "{__file__}", line {lineno_f+1}, in f_with_type\n'
+ ' def foo(a: THIS_DOES_NOT_EXIST ) -> int:\n'
+ ' ^^^^^^^^^^^^^^^^^^^\n'
+ )
+ result_lines = self.get_exception(f_with_type)
+ self.assertEqual(result_lines, expected_f.splitlines())
- lineno_f = f.__code__.co_firstlineno
- result_f = (
+ # TODO: RUSTPYTHON
+ @unittest.expectedFailure
+ def test_caret_multiline_expression(self):
+ # Make sure no carets are printed for expressions spanning multiple
+ # lines.
+ def f_with_multiline():
+ if True: raise ValueError(
+ "error over multiple lines"
+ )
+
+ lineno_f = f_with_multiline.__code__.co_firstlineno
+ expected_f = (
'Traceback (most recent call last):\n'
- f' File "{__file__}", line {lineno_f+5}, in _check_recursive_traceback_display\n'
- ' f()\n'
- f' File "{__file__}", line {lineno_f+1}, in f\n'
- ' f()\n'
- f' File "{__file__}", line {lineno_f+1}, in f\n'
- ' f()\n'
- f' File "{__file__}", line {lineno_f+1}, in f\n'
- ' f()\n'
- # XXX: The following line changes depending on whether the tests
- # are run through the interactive interpreter or with -m
- # It also varies depending on the platform (stack size)
- # Fortunately, we don't care about exactness here, so we use regex
- r' \[Previous line repeated (\d+) more times\]' '\n'
- 'RecursionError: maximum recursion depth exceeded\n'
+ f' File "{__file__}", line {self.callable_line}, in get_exception\n'
+ ' callable()\n'
+ ' ~~~~~~~~^^\n'
+ f' File "{__file__}", line {lineno_f+1}, in f_with_multiline\n'
+ ' if True: raise ValueError(\n'
+ ' ^^^^^^^^^^^^^^^^^\n'
+ ' "error over multiple lines"\n'
+ ' ^^^^^^^^^^^^^^^^^^^^^^^^^^^\n'
+ ' )\n'
+ ' ^'
)
+ result_lines = self.get_exception(f_with_multiline)
+ self.assertEqual(result_lines, expected_f.splitlines())
- expected = result_f.splitlines()
- actual = stderr_f.getvalue().splitlines()
+ # TODO: RUSTPYTHON
+ @unittest.expectedFailure
+ def test_caret_multiline_expression_syntax_error(self):
+ # Make sure an expression spanning multiple lines that has
+ # a syntax error is correctly marked with carets.
+ code = textwrap.dedent("""
+ def foo(*args, **kwargs):
+ pass
- # Check the output text matches expectations
- # 2nd last line contains the repetition count
- self.assertEqual(actual[:-2], expected[:-2])
- self.assertRegex(actual[-2], expected[-2])
- # last line can have additional text appended
- self.assertIn(expected[-1], actual[-1])
+ a, b, c = 1, 2, 3
- # Check the recursion count is roughly as expected
- rec_limit = sys.getrecursionlimit()
- self.assertIn(int(re.search(r"\d+", actual[-2]).group()), range(rec_limit-60, rec_limit))
+ foo(a, z
+ for z in
+ range(10), b, c)
+ """)
- # Check a known (limited) number of recursive invocations
- def g(count=10):
- if count:
- return g(count-1)
- raise ValueError
+ def f_with_multiline():
+ # Need to defer the compilation until in self.get_exception(..)
+ return compile(code, "?", "exec")
- with captured_output("stderr") as stderr_g:
- try:
- g()
- except ValueError:
- render_exc()
- else:
- self.fail("no value error was raised")
+ lineno_f = f_with_multiline.__code__.co_firstlineno
- lineno_g = g.__code__.co_firstlineno
- result_g = (
- f' File "{__file__}", line {lineno_g+2}, in g\n'
- ' return g(count-1)\n'
- f' File "{__file__}", line {lineno_g+2}, in g\n'
- ' return g(count-1)\n'
- f' File "{__file__}", line {lineno_g+2}, in g\n'
- ' return g(count-1)\n'
- ' [Previous line repeated 7 more times]\n'
- f' File "{__file__}", line {lineno_g+3}, in g\n'
- ' raise ValueError\n'
- 'ValueError\n'
- )
- tb_line = (
+ expected_f = (
'Traceback (most recent call last):\n'
- f' File "{__file__}", line {lineno_g+7}, in _check_recursive_traceback_display\n'
- ' g()\n'
+ f' File "{__file__}", line {self.callable_line}, in get_exception\n'
+ ' callable()\n'
+ ' ~~~~~~~~^^\n'
+ f' File "{__file__}", line {lineno_f+2}, in f_with_multiline\n'
+ ' return compile(code, "?", "exec")\n'
+ ' File "?", line 7\n'
+ ' foo(a, z\n'
+ ' ^'
+ )
+
+ result_lines = self.get_exception(f_with_multiline)
+ self.assertEqual(result_lines, expected_f.splitlines())
+
+ # Check custom error messages covering multiple lines
+ code = textwrap.dedent("""
+ dummy_call(
+ "dummy value"
+ foo="bar",
)
- expected = (tb_line + result_g).splitlines()
- actual = stderr_g.getvalue().splitlines()
+ """)
+
+ def f_with_multiline():
+ # Need to defer the compilation until in self.get_exception(..)
+ return compile(code, "?", "exec")
+
+ lineno_f = f_with_multiline.__code__.co_firstlineno
+
+ expected_f = (
+ 'Traceback (most recent call last):\n'
+ f' File "{__file__}", line {self.callable_line}, in get_exception\n'
+ ' callable()\n'
+ ' ~~~~~~~~^^\n'
+ f' File "{__file__}", line {lineno_f+2}, in f_with_multiline\n'
+ ' return compile(code, "?", "exec")\n'
+ ' File "?", line 3\n'
+ ' "dummy value"\n'
+ ' ^^^^^^^^^^^^^'
+ )
+
+ result_lines = self.get_exception(f_with_multiline)
+ self.assertEqual(result_lines, expected_f.splitlines())
+
+ # TODO: RUSTPYTHON
+ @unittest.expectedFailure
+ def test_caret_multiline_expression_bin_op(self):
+ # Make sure no carets are printed for expressions spanning multiple
+ # lines.
+ def f_with_multiline():
+ return (
+ 2 + 1 /
+ 0
+ )
+
+ lineno_f = f_with_multiline.__code__.co_firstlineno
+ expected_f = (
+ 'Traceback (most recent call last):\n'
+ f' File "{__file__}", line {self.callable_line}, in get_exception\n'
+ ' callable()\n'
+ ' ~~~~~~~~^^\n'
+ f' File "{__file__}", line {lineno_f+2}, in f_with_multiline\n'
+ ' 2 + 1 /\n'
+ ' ~~^\n'
+ ' 0\n'
+ ' ~'
+ )
+ result_lines = self.get_exception(f_with_multiline)
+ self.assertEqual(result_lines, expected_f.splitlines())
+
+ # TODO: RUSTPYTHON
+ @unittest.expectedFailure
+ def test_caret_for_binary_operators(self):
+ def f_with_binary_operator():
+ divisor = 20
+ return 10 + divisor / 0 + 30
+
+ lineno_f = f_with_binary_operator.__code__.co_firstlineno
+ expected_error = (
+ 'Traceback (most recent call last):\n'
+ f' File "{__file__}", line {self.callable_line}, in get_exception\n'
+ ' callable()\n'
+ ' ~~~~~~~~^^\n'
+ f' File "{__file__}", line {lineno_f+2}, in f_with_binary_operator\n'
+ ' return 10 + divisor / 0 + 30\n'
+ ' ~~~~~~~~^~~\n'
+ )
+ result_lines = self.get_exception(f_with_binary_operator)
+ self.assertEqual(result_lines, expected_error.splitlines())
+
+ # TODO: RUSTPYTHON
+ @unittest.expectedFailure
+ def test_caret_for_binary_operators_with_unicode(self):
+ def f_with_binary_operator():
+ áóí = 20
+ return 10 + áóí / 0 + 30
+
+ lineno_f = f_with_binary_operator.__code__.co_firstlineno
+ expected_error = (
+ 'Traceback (most recent call last):\n'
+ f' File "{__file__}", line {self.callable_line}, in get_exception\n'
+ ' callable()\n'
+ ' ~~~~~~~~^^\n'
+ f' File "{__file__}", line {lineno_f+2}, in f_with_binary_operator\n'
+ ' return 10 + áóí / 0 + 30\n'
+ ' ~~~~^~~\n'
+ )
+ result_lines = self.get_exception(f_with_binary_operator)
+ self.assertEqual(result_lines, expected_error.splitlines())
+
+ # TODO: RUSTPYTHON
+ @unittest.expectedFailure
+ def test_caret_for_binary_operators_two_char(self):
+ def f_with_binary_operator():
+ divisor = 20
+ return 10 + divisor // 0 + 30
+
+ lineno_f = f_with_binary_operator.__code__.co_firstlineno
+ expected_error = (
+ 'Traceback (most recent call last):\n'
+ f' File "{__file__}", line {self.callable_line}, in get_exception\n'
+ ' callable()\n'
+ ' ~~~~~~~~^^\n'
+ f' File "{__file__}", line {lineno_f+2}, in f_with_binary_operator\n'
+ ' return 10 + divisor // 0 + 30\n'
+ ' ~~~~~~~~^^~~\n'
+ )
+ result_lines = self.get_exception(f_with_binary_operator)
+ self.assertEqual(result_lines, expected_error.splitlines())
+
+ # TODO: RUSTPYTHON
+ @unittest.expectedFailure
+ def test_caret_for_binary_operators_with_spaces_and_parenthesis(self):
+ def f_with_binary_operator():
+ a = 1
+ b = c = ""
+ return ( a ) +b + c
+
+ lineno_f = f_with_binary_operator.__code__.co_firstlineno
+ expected_error = (
+ 'Traceback (most recent call last):\n'
+ f' File "{__file__}", line {self.callable_line}, in get_exception\n'
+ ' callable()\n'
+ ' ~~~~~~~~^^\n'
+ f' File "{__file__}", line {lineno_f+3}, in f_with_binary_operator\n'
+ ' return ( a ) +b + c\n'
+ ' ~~~~~~~~~~^~\n'
+ )
+ result_lines = self.get_exception(f_with_binary_operator)
+ self.assertEqual(result_lines, expected_error.splitlines())
+
+ # TODO: RUSTPYTHON
+ @unittest.expectedFailure
+ def test_caret_for_binary_operators_multiline(self):
+ def f_with_binary_operator():
+ b = 1
+ c = ""
+ a = b \
+ +\
+ c # test
+ return a
+
+ lineno_f = f_with_binary_operator.__code__.co_firstlineno
+ expected_error = (
+ 'Traceback (most recent call last):\n'
+ f' File "{__file__}", line {self.callable_line}, in get_exception\n'
+ ' callable()\n'
+ ' ~~~~~~~~^^\n'
+ f' File "{__file__}", line {lineno_f+3}, in f_with_binary_operator\n'
+ ' a = b \\\n'
+ ' ~~~~~~\n'
+ ' +\\\n'
+ ' ^~\n'
+ ' c # test\n'
+ ' ~\n'
+ )
+ result_lines = self.get_exception(f_with_binary_operator)
+ self.assertEqual(result_lines, expected_error.splitlines())
+
+ # TODO: RUSTPYTHON
+ @unittest.expectedFailure
+ def test_caret_for_binary_operators_multiline_two_char(self):
+ def f_with_binary_operator():
+ b = 1
+ c = ""
+ a = (
+ (b # test +
+ ) \
+ # +
+ << (c # test
+ \
+ ) # test
+ )
+ return a
+
+ lineno_f = f_with_binary_operator.__code__.co_firstlineno
+ expected_error = (
+ 'Traceback (most recent call last):\n'
+ f' File "{__file__}", line {self.callable_line}, in get_exception\n'
+ ' callable()\n'
+ ' ~~~~~~~~^^\n'
+ f' File "{__file__}", line {lineno_f+4}, in f_with_binary_operator\n'
+ ' (b # test +\n'
+ ' ~~~~~~~~~~~~\n'
+ ' ) \\\n'
+ ' ~~~~\n'
+ ' # +\n'
+ ' ~~~\n'
+ ' << (c # test\n'
+ ' ^^~~~~~~~~~~~\n'
+ ' \\\n'
+ ' ~\n'
+ ' ) # test\n'
+ ' ~\n'
+ )
+ result_lines = self.get_exception(f_with_binary_operator)
+ self.assertEqual(result_lines, expected_error.splitlines())
+
+ # TODO: RUSTPYTHON
+ @unittest.expectedFailure
+ def test_caret_for_binary_operators_multiline_with_unicode(self):
+ def f_with_binary_operator():
+ b = 1
+ a = ("ááá" +
+ "áá") + b
+ return a
+
+ lineno_f = f_with_binary_operator.__code__.co_firstlineno
+ expected_error = (
+ 'Traceback (most recent call last):\n'
+ f' File "{__file__}", line {self.callable_line}, in get_exception\n'
+ ' callable()\n'
+ ' ~~~~~~~~^^\n'
+ f' File "{__file__}", line {lineno_f+2}, in f_with_binary_operator\n'
+ ' a = ("ááá" +\n'
+ ' ~~~~~~~~\n'
+ ' "áá") + b\n'
+ ' ~~~~~~^~~\n'
+ )
+ result_lines = self.get_exception(f_with_binary_operator)
+ self.assertEqual(result_lines, expected_error.splitlines())
+
+ # TODO: RUSTPYTHON
+ @unittest.expectedFailure
+ def test_caret_for_subscript(self):
+ def f_with_subscript():
+ some_dict = {'x': {'y': None}}
+ return some_dict['x']['y']['z']
+
+ lineno_f = f_with_subscript.__code__.co_firstlineno
+ expected_error = (
+ 'Traceback (most recent call last):\n'
+ f' File "{__file__}", line {self.callable_line}, in get_exception\n'
+ ' callable()\n'
+ ' ~~~~~~~~^^\n'
+ f' File "{__file__}", line {lineno_f+2}, in f_with_subscript\n'
+ " return some_dict['x']['y']['z']\n"
+ ' ~~~~~~~~~~~~~~~~~~~^^^^^\n'
+ )
+ result_lines = self.get_exception(f_with_subscript)
+ self.assertEqual(result_lines, expected_error.splitlines())
+
+ # TODO: RUSTPYTHON
+ @unittest.expectedFailure
+ def test_caret_for_subscript_unicode(self):
+ def f_with_subscript():
+ some_dict = {'ó': {'á': {'í': {'theta': 1}}}}
+ return some_dict['ó']['á']['í']['beta']
+
+ lineno_f = f_with_subscript.__code__.co_firstlineno
+ expected_error = (
+ 'Traceback (most recent call last):\n'
+ f' File "{__file__}", line {self.callable_line}, in get_exception\n'
+ ' callable()\n'
+ ' ~~~~~~~~^^\n'
+ f' File "{__file__}", line {lineno_f+2}, in f_with_subscript\n'
+ " return some_dict['ó']['á']['í']['beta']\n"
+ ' ~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^\n'
+ )
+ result_lines = self.get_exception(f_with_subscript)
+ self.assertEqual(result_lines, expected_error.splitlines())
+
+ # TODO: RUSTPYTHON
+ @unittest.expectedFailure
+ def test_caret_for_subscript_with_spaces_and_parenthesis(self):
+ def f_with_binary_operator():
+ a = []
+ b = c = 1
+ return b [ a ] + c
+
+ lineno_f = f_with_binary_operator.__code__.co_firstlineno
+ expected_error = (
+ 'Traceback (most recent call last):\n'
+ f' File "{__file__}", line {self.callable_line}, in get_exception\n'
+ ' callable()\n'
+ ' ~~~~~~~~^^\n'
+ f' File "{__file__}", line {lineno_f+3}, in f_with_binary_operator\n'
+ ' return b [ a ] + c\n'
+ ' ~~~~~~^^^^^^^^^\n'
+ )
+ result_lines = self.get_exception(f_with_binary_operator)
+ self.assertEqual(result_lines, expected_error.splitlines())
+
+ # TODO: RUSTPYTHON
+ @unittest.expectedFailure
+ def test_caret_for_subscript_multiline(self):
+ def f_with_subscript():
+ bbbbb = {}
+ ccc = 1
+ ddd = 2
+ b = bbbbb \
+ [ ccc # test
+
+ + ddd \
+
+ ] # test
+ return b
+
+ lineno_f = f_with_subscript.__code__.co_firstlineno
+ expected_error = (
+ 'Traceback (most recent call last):\n'
+ f' File "{__file__}", line {self.callable_line}, in get_exception\n'
+ ' callable()\n'
+ ' ~~~~~~~~^^\n'
+ f' File "{__file__}", line {lineno_f+4}, in f_with_subscript\n'
+ ' b = bbbbb \\\n'
+ ' ~~~~~~~\n'
+ ' [ ccc # test\n'
+ ' ^^^^^^^^^^^^^\n'
+ ' \n'
+ ' \n'
+ ' + ddd \\\n'
+ ' ^^^^^^^^\n'
+ ' \n'
+ ' \n'
+ ' ] # test\n'
+ ' ^\n'
+ )
+ result_lines = self.get_exception(f_with_subscript)
+ self.assertEqual(result_lines, expected_error.splitlines())
+
+ # TODO: RUSTPYTHON
+ @unittest.expectedFailure
+ def test_caret_for_call(self):
+ def f_with_call():
+ def f1(a):
+ def f2(b):
+ raise RuntimeError("fail")
+ return f2
+ return f1("x")("y")("z")
+
+ lineno_f = f_with_call.__code__.co_firstlineno
+ expected_error = (
+ 'Traceback (most recent call last):\n'
+ f' File "{__file__}", line {self.callable_line}, in get_exception\n'
+ ' callable()\n'
+ ' ~~~~~~~~^^\n'
+ f' File "{__file__}", line {lineno_f+5}, in f_with_call\n'
+ ' return f1("x")("y")("z")\n'
+ ' ~~~~~~~^^^^^\n'
+ f' File "{__file__}", line {lineno_f+3}, in f2\n'
+ ' raise RuntimeError("fail")\n'
+ )
+ result_lines = self.get_exception(f_with_call)
+ self.assertEqual(result_lines, expected_error.splitlines())
+
+ # TODO: RUSTPYTHON
+ @unittest.expectedFailure
+ def test_caret_for_call_unicode(self):
+ def f_with_call():
+ def f1(a):
+ def f2(b):
+ raise RuntimeError("fail")
+ return f2
+ return f1("ó")("á")
+
+ lineno_f = f_with_call.__code__.co_firstlineno
+ expected_error = (
+ 'Traceback (most recent call last):\n'
+ f' File "{__file__}", line {self.callable_line}, in get_exception\n'
+ ' callable()\n'
+ ' ~~~~~~~~^^\n'
+ f' File "{__file__}", line {lineno_f+5}, in f_with_call\n'
+ ' return f1("ó")("á")\n'
+ ' ~~~~~~~^^^^^\n'
+ f' File "{__file__}", line {lineno_f+3}, in f2\n'
+ ' raise RuntimeError("fail")\n'
+ )
+ result_lines = self.get_exception(f_with_call)
+ self.assertEqual(result_lines, expected_error.splitlines())
+
+ # TODO: RUSTPYTHON
+ @unittest.expectedFailure
+ def test_caret_for_call_with_spaces_and_parenthesis(self):
+ def f_with_binary_operator():
+ def f(a):
+ raise RuntimeError("fail")
+ return f ( "x" ) + 2
+
+ lineno_f = f_with_binary_operator.__code__.co_firstlineno
+ expected_error = (
+ 'Traceback (most recent call last):\n'
+ f' File "{__file__}", line {self.callable_line}, in get_exception\n'
+ ' callable()\n'
+ ' ~~~~~~~~^^\n'
+ f' File "{__file__}", line {lineno_f+3}, in f_with_binary_operator\n'
+ ' return f ( "x" ) + 2\n'
+ ' ~~~~~~^^^^^^^^^^^\n'
+ f' File "{__file__}", line {lineno_f+2}, in f\n'
+ ' raise RuntimeError("fail")\n'
+ )
+ result_lines = self.get_exception(f_with_binary_operator)
+ self.assertEqual(result_lines, expected_error.splitlines())
+
+ # TODO: RUSTPYTHON
+ @unittest.expectedFailure
+ def test_caret_for_call_multiline(self):
+ def f_with_call():
+ class C:
+ def y(self, a):
+ def f(b):
+ raise RuntimeError("fail")
+ return f
+ def g(x):
+ return C()
+ a = (g(1).y)(
+ 2
+ )(3)(4)
+ return a
+
+ lineno_f = f_with_call.__code__.co_firstlineno
+ expected_error = (
+ 'Traceback (most recent call last):\n'
+ f' File "{__file__}", line {self.callable_line}, in get_exception\n'
+ ' callable()\n'
+ ' ~~~~~~~~^^\n'
+ f' File "{__file__}", line {lineno_f+8}, in f_with_call\n'
+ ' a = (g(1).y)(\n'
+ ' ~~~~~~~~~\n'
+ ' 2\n'
+ ' ~\n'
+ ' )(3)(4)\n'
+ ' ~^^^\n'
+ f' File "{__file__}", line {lineno_f+4}, in f\n'
+ ' raise RuntimeError("fail")\n'
+ )
+ result_lines = self.get_exception(f_with_call)
+ self.assertEqual(result_lines, expected_error.splitlines())
+
+ # TODO: RUSTPYTHON
+ @unittest.expectedFailure
+ def test_many_lines(self):
+ def f():
+ x = 1
+ if True: x += (
+ "a" +
+ "a"
+ ) # test
+
+ lineno_f = f.__code__.co_firstlineno
+ expected_error = (
+ 'Traceback (most recent call last):\n'
+ f' File "{__file__}", line {self.callable_line}, in get_exception\n'
+ ' callable()\n'
+ ' ~~~~~~~~^^\n'
+ f' File "{__file__}", line {lineno_f+2}, in f\n'
+ ' if True: x += (\n'
+ ' ^^^^^^\n'
+ ' ...<2 lines>...\n'
+ ' ) # test\n'
+ ' ^\n'
+ )
+ result_lines = self.get_exception(f)
+ self.assertEqual(result_lines, expected_error.splitlines())
+
+ # TODO: RUSTPYTHON
+ @unittest.expectedFailure
+ def test_many_lines_no_caret(self):
+ def f():
+ x = 1
+ x += (
+ "a" +
+ "a"
+ )
+
+ lineno_f = f.__code__.co_firstlineno
+ expected_error = (
+ 'Traceback (most recent call last):\n'
+ f' File "{__file__}", line {self.callable_line}, in get_exception\n'
+ ' callable()\n'
+ ' ~~~~~~~~^^\n'
+ f' File "{__file__}", line {lineno_f+2}, in f\n'
+ ' x += (\n'
+ ' ...<2 lines>...\n'
+ ' )\n'
+ )
+ result_lines = self.get_exception(f)
+ self.assertEqual(result_lines, expected_error.splitlines())
+
+ # TODO: RUSTPYTHON
+ @unittest.expectedFailure
+ def test_many_lines_binary_op(self):
+ def f_with_binary_operator():
+ b = 1
+ c = "a"
+ a = (
+ b +
+ b
+ ) + (
+ c +
+ c +
+ c
+ )
+ return a
+
+ lineno_f = f_with_binary_operator.__code__.co_firstlineno
+ expected_error = (
+ 'Traceback (most recent call last):\n'
+ f' File "{__file__}", line {self.callable_line}, in get_exception\n'
+ ' callable()\n'
+ ' ~~~~~~~~^^\n'
+ f' File "{__file__}", line {lineno_f+3}, in f_with_binary_operator\n'
+ ' a = (\n'
+ ' ~\n'
+ ' b +\n'
+ ' ~~~\n'
+ ' b\n'
+ ' ~\n'
+ ' ) + (\n'
+ ' ~~^~~\n'
+ ' c +\n'
+ ' ~~~\n'
+ ' ...<2 lines>...\n'
+ ' )\n'
+ ' ~\n'
+ )
+ result_lines = self.get_exception(f_with_binary_operator)
+ self.assertEqual(result_lines, expected_error.splitlines())
+
+ # TODO: RUSTPYTHON
+ @unittest.expectedFailure
+ def test_traceback_specialization_with_syntax_error(self):
+ bytecode = compile("1 / 0 / 1 / 2\n", TESTFN, "exec")
+
+ with open(TESTFN, "w") as file:
+ # make the file's contents invalid
+ file.write("1 $ 0 / 1 / 2\n")
+ self.addCleanup(unlink, TESTFN)
+
+ func = partial(exec, bytecode)
+ result_lines = self.get_exception(func)
+
+ lineno_f = bytecode.co_firstlineno
+ expected_error = (
+ 'Traceback (most recent call last):\n'
+ f' File "{__file__}", line {self.callable_line}, in get_exception\n'
+ ' callable()\n'
+ ' ~~~~~~~~^^\n'
+ f' File "{TESTFN}", line {lineno_f}, in \n'
+ " 1 $ 0 / 1 / 2\n"
+ ' ^^^^^\n'
+ )
+ self.assertEqual(result_lines, expected_error.splitlines())
+
+ # TODO: RUSTPYTHON
+ @unittest.expectedFailure
+ def test_traceback_very_long_line(self):
+ source = "if True: " + "a" * 256
+ bytecode = compile(source, TESTFN, "exec")
+
+ with open(TESTFN, "w") as file:
+ file.write(source)
+ self.addCleanup(unlink, TESTFN)
+
+ func = partial(exec, bytecode)
+ result_lines = self.get_exception(func)
+
+ lineno_f = bytecode.co_firstlineno
+ expected_error = (
+ 'Traceback (most recent call last):\n'
+ f' File "{__file__}", line {self.callable_line}, in get_exception\n'
+ ' callable()\n'
+ ' ~~~~~~~~^^\n'
+ f' File "{TESTFN}", line {lineno_f}, in \n'
+ f' {source}\n'
+ f' {" "*len("if True: ") + "^"*256}\n'
+ )
+ self.assertEqual(result_lines, expected_error.splitlines())
+
+ # TODO: RUSTPYTHON
+ @unittest.expectedFailure
+ def test_secondary_caret_not_elided(self):
+ # Always show a line's indicators if they include the secondary character.
+ def f_with_subscript():
+ some_dict = {'x': {'y': None}}
+ some_dict['x']['y']['z']
+
+ lineno_f = f_with_subscript.__code__.co_firstlineno
+ expected_error = (
+ 'Traceback (most recent call last):\n'
+ f' File "{__file__}", line {self.callable_line}, in get_exception\n'
+ ' callable()\n'
+ ' ~~~~~~~~^^\n'
+ f' File "{__file__}", line {lineno_f+2}, in f_with_subscript\n'
+ " some_dict['x']['y']['z']\n"
+ ' ~~~~~~~~~~~~~~~~~~~^^^^^\n'
+ )
+ result_lines = self.get_exception(f_with_subscript)
+ self.assertEqual(result_lines, expected_error.splitlines())
+
+ # TODO: RUSTPYTHON
+ @unittest.expectedFailure
+ def test_caret_exception_group(self):
+ # Notably, this covers whether indicators handle margin strings correctly.
+ # (Exception groups use margin strings to display vertical indicators.)
+ # The implementation must account for both "indent" and "margin" offsets.
+
+ def exc():
+ if True: raise ExceptionGroup("eg", [ValueError(1), TypeError(2)])
+
+ expected_error = (
+ f' + Exception Group Traceback (most recent call last):\n'
+ f' | File "{__file__}", line {self.callable_line}, in get_exception\n'
+ f' | callable()\n'
+ f' | ~~~~~~~~^^\n'
+ f' | File "{__file__}", line {exc.__code__.co_firstlineno + 1}, in exc\n'
+ f' | if True: raise ExceptionGroup("eg", [ValueError(1), TypeError(2)])\n'
+ f' | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n'
+ f' | ExceptionGroup: eg (2 sub-exceptions)\n'
+ f' +-+---------------- 1 ----------------\n'
+ f' | ValueError: 1\n'
+ f' +---------------- 2 ----------------\n'
+ f' | TypeError: 2\n')
+
+ result_lines = self.get_exception(exc)
+ self.assertEqual(result_lines, expected_error.splitlines())
+
+ def assertSpecialized(self, func, expected_specialization):
+ result_lines = self.get_exception(func)
+ specialization_line = result_lines[-1]
+ self.assertEqual(specialization_line.lstrip(), expected_specialization)
+
+ # TODO: RUSTPYTHON
+ @unittest.expectedFailure
+ def test_specialization_variations(self):
+ self.assertSpecialized(lambda: 1/0,
+ "~^~")
+ self.assertSpecialized(lambda: 1/0/3,
+ "~^~")
+ self.assertSpecialized(lambda: 1 / 0,
+ "~~^~~")
+ self.assertSpecialized(lambda: 1 / 0 / 3,
+ "~~^~~")
+ self.assertSpecialized(lambda: 1/ 0,
+ "~^~~")
+ self.assertSpecialized(lambda: 1/ 0/3,
+ "~^~~")
+ self.assertSpecialized(lambda: 1 / 0,
+ "~~~~~^~~~")
+ self.assertSpecialized(lambda: 1 / 0 / 5,
+ "~~~~~^~~~")
+ self.assertSpecialized(lambda: 1 /0,
+ "~~^~")
+ self.assertSpecialized(lambda: 1//0,
+ "~^^~")
+ self.assertSpecialized(lambda: 1//0//4,
+ "~^^~")
+ self.assertSpecialized(lambda: 1 // 0,
+ "~~^^~~")
+ self.assertSpecialized(lambda: 1 // 0 // 4,
+ "~~^^~~")
+ self.assertSpecialized(lambda: 1 //0,
+ "~~^^~")
+ self.assertSpecialized(lambda: 1// 0,
+ "~^^~~")
+
+ # TODO: RUSTPYTHON
+ @unittest.expectedFailure
+ def test_decorator_application_lineno_correct(self):
+ def dec_error(func):
+ raise TypeError
+ def dec_fine(func):
+ return func
+ def applydecs():
+ @dec_error
+ @dec_fine
+ def g(): pass
+ result_lines = self.get_exception(applydecs)
+ lineno_applydescs = applydecs.__code__.co_firstlineno
+ lineno_dec_error = dec_error.__code__.co_firstlineno
+ expected_error = (
+ 'Traceback (most recent call last):\n'
+ f' File "{__file__}", line {self.callable_line}, in get_exception\n'
+ ' callable()\n'
+ ' ~~~~~~~~^^\n'
+ f' File "{__file__}", line {lineno_applydescs + 1}, in applydecs\n'
+ ' @dec_error\n'
+ ' ^^^^^^^^^\n'
+ f' File "{__file__}", line {lineno_dec_error + 1}, in dec_error\n'
+ ' raise TypeError\n'
+ )
+ self.assertEqual(result_lines, expected_error.splitlines())
+
+ def applydecs_class():
+ @dec_error
+ @dec_fine
+ class A: pass
+ result_lines = self.get_exception(applydecs_class)
+ lineno_applydescs_class = applydecs_class.__code__.co_firstlineno
+ expected_error = (
+ 'Traceback (most recent call last):\n'
+ f' File "{__file__}", line {self.callable_line}, in get_exception\n'
+ ' callable()\n'
+ ' ~~~~~~~~^^\n'
+ f' File "{__file__}", line {lineno_applydescs_class + 1}, in applydecs_class\n'
+ ' @dec_error\n'
+ ' ^^^^^^^^^\n'
+ f' File "{__file__}", line {lineno_dec_error + 1}, in dec_error\n'
+ ' raise TypeError\n'
+ )
+ self.assertEqual(result_lines, expected_error.splitlines())
+
+ # TODO: RUSTPYTHON
+ @unittest.expectedFailure
+ def test_multiline_method_call_a(self):
+ def f():
+ (None
+ .method
+ )()
+ actual = self.get_exception(f)
+ expected = [
+ "Traceback (most recent call last):",
+ f" File \"{__file__}\", line {self.callable_line}, in get_exception",
+ " callable()",
+ " ~~~~~~~~^^",
+ f" File \"{__file__}\", line {f.__code__.co_firstlineno + 2}, in f",
+ " .method",
+ " ^^^^^^",
+ ]
+ self.assertEqual(actual, expected)
+
+ # TODO: RUSTPYTHON
+ @unittest.expectedFailure
+ def test_multiline_method_call_b(self):
+ def f():
+ (None.
+ method
+ )()
+ actual = self.get_exception(f)
+ expected = [
+ "Traceback (most recent call last):",
+ f" File \"{__file__}\", line {self.callable_line}, in get_exception",
+ " callable()",
+ " ~~~~~~~~^^",
+ f" File \"{__file__}\", line {f.__code__.co_firstlineno + 2}, in f",
+ " method",
+ ]
+ self.assertEqual(actual, expected)
+
+ # TODO: RUSTPYTHON
+ @unittest.expectedFailure
+ def test_multiline_method_call_c(self):
+ def f():
+ (None
+ . method
+ )()
+ actual = self.get_exception(f)
+ expected = [
+ "Traceback (most recent call last):",
+ f" File \"{__file__}\", line {self.callable_line}, in get_exception",
+ " callable()",
+ " ~~~~~~~~^^",
+ f" File \"{__file__}\", line {f.__code__.co_firstlineno + 2}, in f",
+ " . method",
+ " ^^^^^^",
+ ]
+ self.assertEqual(actual, expected)
+
+ # TODO: RUSTPYTHON
+ @unittest.expectedFailure
+ def test_wide_characters_unicode_with_problematic_byte_offset(self):
+ def f():
+ width
+
+ actual = self.get_exception(f)
+ expected = [
+ "Traceback (most recent call last):",
+ f" File \"{__file__}\", line {self.callable_line}, in get_exception",
+ " callable()",
+ " ~~~~~~~~^^",
+ f" File \"{__file__}\", line {f.__code__.co_firstlineno + 1}, in f",
+ " width",
+ ]
+ self.assertEqual(actual, expected)
+
+
+ # TODO: RUSTPYTHON
+ @unittest.expectedFailure
+ def test_byte_offset_with_wide_characters_middle(self):
+ def f():
+ width = 1
+ raise ValueError(width)
+
+ actual = self.get_exception(f)
+ expected = [
+ "Traceback (most recent call last):",
+ f" File \"{__file__}\", line {self.callable_line}, in get_exception",
+ " callable()",
+ " ~~~~~~~~^^",
+ f" File \"{__file__}\", line {f.__code__.co_firstlineno + 2}, in f",
+ " raise ValueError(width)",
+ ]
+ self.assertEqual(actual, expected)
+
+ # TODO: RUSTPYTHON
+ @unittest.expectedFailure
+ def test_byte_offset_multiline(self):
+ def f():
+ www = 1
+ th = 0
+
+ print(1, www(
+ th))
+
+ actual = self.get_exception(f)
+ expected = [
+ "Traceback (most recent call last):",
+ f" File \"{__file__}\", line {self.callable_line}, in get_exception",
+ " callable()",
+ " ~~~~~~~~^^",
+ f" File \"{__file__}\", line {f.__code__.co_firstlineno + 4}, in f",
+ f" print(1, www(",
+ f" ~~~~~~^",
+ f" th))",
+ f" ^^^^^",
+ ]
+ self.assertEqual(actual, expected)
+
+ # TODO: RUSTPYTHON
+ @unittest.expectedFailure
+ def test_byte_offset_with_wide_characters_term_highlight(self):
+ def f():
+ 说明说明 = 1
+ şçöğıĤellö = 0 # not wide but still non-ascii
+ return 说明说明 / şçöğıĤellö
+
+ actual = self.get_exception(f)
+ expected = [
+ f"Traceback (most recent call last):",
+ f" File \"{__file__}\", line {self.callable_line}, in get_exception",
+ f" callable()",
+ f" ~~~~~~~~^^",
+ f" File \"{__file__}\", line {f.__code__.co_firstlineno + 3}, in f",
+ f" return 说明说明 / şçöğıĤellö",
+ f" ~~~~~~~~~^~~~~~~~~~~~",
+ ]
+ self.assertEqual(actual, expected)
+
+ # TODO: RUSTPYTHON
+ @unittest.expectedFailure
+ def test_byte_offset_with_emojis_term_highlight(self):
+ def f():
+ return "✨🐍" + func_说明说明("📗🚛",
+ "📗🚛") + "🐍"
+
+ actual = self.get_exception(f)
+ expected = [
+ f"Traceback (most recent call last):",
+ f" File \"{__file__}\", line {self.callable_line}, in get_exception",
+ f" callable()",
+ f" ~~~~~~~~^^",
+ f" File \"{__file__}\", line {f.__code__.co_firstlineno + 1}, in f",
+ f' return "✨🐍" + func_说明说明("📗🚛",',
+ f" ^^^^^^^^^^^^^",
+ ]
+ self.assertEqual(actual, expected)
+
+ # TODO: RUSTPYTHON
+ @unittest.expectedFailure
+ def test_byte_offset_wide_chars_subscript(self):
+ def f():
+ my_dct = {
+ "✨🚛✨": {
+ "说明": {
+ "🐍🐍🐍": None
+ }
+ }
+ }
+ return my_dct["✨🚛✨"]["说明"]["🐍"]["说明"]["🐍🐍"]
+
+ actual = self.get_exception(f)
+ expected = [
+ f"Traceback (most recent call last):",
+ f" File \"{__file__}\", line {self.callable_line}, in get_exception",
+ f" callable()",
+ f" ~~~~~~~~^^",
+ f" File \"{__file__}\", line {f.__code__.co_firstlineno + 8}, in f",
+ f' return my_dct["✨🚛✨"]["说明"]["🐍"]["说明"]["🐍🐍"]',
+ f" ~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^",
+ ]
+ self.assertEqual(actual, expected)
+
+ # TODO: RUSTPYTHON
+ @unittest.expectedFailure
+ def test_memory_error(self):
+ def f():
+ raise MemoryError()
+
+ actual = self.get_exception(f)
+ expected = ['Traceback (most recent call last):',
+ f' File "{__file__}", line {self.callable_line}, in get_exception',
+ ' callable()',
+ ' ~~~~~~~~^^',
+ f' File "{__file__}", line {f.__code__.co_firstlineno + 1}, in f',
+ ' raise MemoryError()']
+ self.assertEqual(actual, expected)
+
+ # TODO: RUSTPYTHON
+ @unittest.expectedFailure
+ def test_anchors_for_simple_return_statements_are_elided(self):
+ def g():
+ 1/0
+
+ def f():
+ return g()
+
+ result_lines = self.get_exception(f)
+ expected = ['Traceback (most recent call last):',
+ f" File \"{__file__}\", line {self.callable_line}, in get_exception",
+ " callable()",
+ " ~~~~~~~~^^",
+ f" File \"{__file__}\", line {f.__code__.co_firstlineno + 1}, in f",
+ " return g()",
+ f" File \"{__file__}\", line {g.__code__.co_firstlineno + 1}, in g",
+ " 1/0",
+ " ~^~"
+ ]
+ self.assertEqual(result_lines, expected)
+
+ def g():
+ 1/0
+
+ def f():
+ return g() + 1
+
+ result_lines = self.get_exception(f)
+ expected = ['Traceback (most recent call last):',
+ f" File \"{__file__}\", line {self.callable_line}, in get_exception",
+ " callable()",
+ " ~~~~~~~~^^",
+ f" File \"{__file__}\", line {f.__code__.co_firstlineno + 1}, in f",
+ " return g() + 1",
+ " ~^^",
+ f" File \"{__file__}\", line {g.__code__.co_firstlineno + 1}, in g",
+ " 1/0",
+ " ~^~"
+ ]
+ self.assertEqual(result_lines, expected)
+
+ def g(*args):
+ 1/0
+
+ def f():
+ return g(1,
+ 2, 4,
+ 5)
+
+ result_lines = self.get_exception(f)
+ expected = ['Traceback (most recent call last):',
+ f" File \"{__file__}\", line {self.callable_line}, in get_exception",
+ " callable()",
+ " ~~~~~~~~^^",
+ f" File \"{__file__}\", line {f.__code__.co_firstlineno + 1}, in f",
+ " return g(1,",
+ " 2, 4,",
+ " 5)",
+ f" File \"{__file__}\", line {g.__code__.co_firstlineno + 1}, in g",
+ " 1/0",
+ " ~^~"
+ ]
+ self.assertEqual(result_lines, expected)
+
+ def g(*args):
+ 1/0
+
+ def f():
+ return g(1,
+ 2, 4,
+ 5) + 1
+
+ result_lines = self.get_exception(f)
+ expected = ['Traceback (most recent call last):',
+ f" File \"{__file__}\", line {self.callable_line}, in get_exception",
+ " callable()",
+ " ~~~~~~~~^^",
+ f" File \"{__file__}\", line {f.__code__.co_firstlineno + 1}, in f",
+ " return g(1,",
+ " ~^^^",
+ " 2, 4,",
+ " ^^^^^",
+ " 5) + 1",
+ " ^^",
+ f" File \"{__file__}\", line {g.__code__.co_firstlineno + 1}, in g",
+ " 1/0",
+ " ~^~"
+ ]
+ self.assertEqual(result_lines, expected)
+
+ # TODO: RUSTPYTHON
+ @unittest.expectedFailure
+ def test_anchors_for_simple_assign_statements_are_elided(self):
+ def g():
+ 1/0
+
+ def f():
+ x = g()
+
+ result_lines = self.get_exception(f)
+ expected = ['Traceback (most recent call last):',
+ f" File \"{__file__}\", line {self.callable_line}, in get_exception",
+ " callable()",
+ " ~~~~~~~~^^",
+ f" File \"{__file__}\", line {f.__code__.co_firstlineno + 1}, in f",
+ " x = g()",
+ f" File \"{__file__}\", line {g.__code__.co_firstlineno + 1}, in g",
+ " 1/0",
+ " ~^~"
+ ]
+ self.assertEqual(result_lines, expected)
+
+ def g(*args):
+ 1/0
+
+ def f():
+ x = g(1,
+ 2, 3,
+ 4)
+
+ result_lines = self.get_exception(f)
+ expected = ['Traceback (most recent call last):',
+ f" File \"{__file__}\", line {self.callable_line}, in get_exception",
+ " callable()",
+ " ~~~~~~~~^^",
+ f" File \"{__file__}\", line {f.__code__.co_firstlineno + 1}, in f",
+ " x = g(1,",
+ " 2, 3,",
+ " 4)",
+ f" File \"{__file__}\", line {g.__code__.co_firstlineno + 1}, in g",
+ " 1/0",
+ " ~^~"
+ ]
+ self.assertEqual(result_lines, expected)
+
+ def g():
+ 1/0
+
+ def f():
+ x = y = g()
+
+ result_lines = self.get_exception(f)
+ expected = ['Traceback (most recent call last):',
+ f" File \"{__file__}\", line {self.callable_line}, in get_exception",
+ " callable()",
+ " ~~~~~~~~^^",
+ f" File \"{__file__}\", line {f.__code__.co_firstlineno + 1}, in f",
+ " x = y = g()",
+ " ~^^",
+ f" File \"{__file__}\", line {g.__code__.co_firstlineno + 1}, in g",
+ " 1/0",
+ " ~^~"
+ ]
+ self.assertEqual(result_lines, expected)
+
+ def g(*args):
+ 1/0
+
+ def f():
+ x = y = g(1,
+ 2, 3,
+ 4)
+
+ result_lines = self.get_exception(f)
+ expected = ['Traceback (most recent call last):',
+ f" File \"{__file__}\", line {self.callable_line}, in get_exception",
+ " callable()",
+ " ~~~~~~~~^^",
+ f" File \"{__file__}\", line {f.__code__.co_firstlineno + 1}, in f",
+ " x = y = g(1,",
+ " ~^^^",
+ " 2, 3,",
+ " ^^^^^",
+ " 4)",
+ " ^^",
+ f" File \"{__file__}\", line {g.__code__.co_firstlineno + 1}, in g",
+ " 1/0",
+ " ~^~"
+ ]
+ self.assertEqual(result_lines, expected)
+
+
+# @requires_debug_ranges() # XXX: RUSTPYTHON patch
+@force_not_colorized_test_class
+class PurePythonTracebackErrorCaretTests(
+ PurePythonExceptionFormattingMixin,
+ TracebackErrorLocationCaretTestBase,
+ unittest.TestCase,
+):
+ """
+ Same set of tests as above using the pure Python implementation of
+ traceback printing in traceback.py.
+ """
+
+
+@cpython_only
+# @requires_debug_ranges() # XXX: RUSTPYTHON patch
+@force_not_colorized_test_class
+class CPythonTracebackErrorCaretTests(
+ CAPIExceptionFormattingMixin,
+ TracebackErrorLocationCaretTestBase,
+ unittest.TestCase,
+):
+ """
+ Same set of tests as above but with Python's internal traceback printing.
+ """
+
+@cpython_only
+# @requires_debug_ranges() # XXX: RUSTPYTHON patch
+@force_not_colorized_test_class
+class CPythonTracebackLegacyErrorCaretTests(
+ CAPIExceptionFormattingLegacyMixin,
+ TracebackErrorLocationCaretTestBase,
+ unittest.TestCase,
+):
+ """
+ Same set of tests as above but with Python's legacy internal traceback printing.
+ """
+
+
+class TracebackFormatMixin:
+ DEBUG_RANGES = True
+
+ def some_exception(self):
+ raise KeyError('blah')
+
+ def _filter_debug_ranges(self, expected):
+ return [line for line in expected if not set(line.strip()) <= set("^~")]
+
+ def _maybe_filter_debug_ranges(self, expected):
+ if not self.DEBUG_RANGES:
+ return self._filter_debug_ranges(expected)
+ return expected
+
+ @cpython_only
+ def check_traceback_format(self, cleanup_func=None):
+ from _testcapi import traceback_print
+ try:
+ self.some_exception()
+ except KeyError as e:
+ tb = e.__traceback__
+ if cleanup_func is not None:
+ # Clear the inner frames, not this one
+ cleanup_func(tb.tb_next)
+ traceback_fmt = 'Traceback (most recent call last):\n' + \
+ ''.join(traceback.format_tb(tb))
+ # clear caret lines from traceback_fmt since internal API does
+ # not emit them
+ traceback_fmt = "\n".join(
+ self._filter_debug_ranges(traceback_fmt.splitlines())
+ ) + "\n"
+ file_ = StringIO()
+ traceback_print(tb, file_)
+ python_fmt = file_.getvalue()
+ # Call all _tb and _exc functions
+ with captured_output("stderr") as tbstderr:
+ traceback.print_tb(tb)
+ tbfile = StringIO()
+ traceback.print_tb(tb, file=tbfile)
+ with captured_output("stderr") as excstderr:
+ traceback.print_exc()
+ excfmt = traceback.format_exc()
+ excfile = StringIO()
+ traceback.print_exc(file=excfile)
+ else:
+ raise Error("unable to create test traceback string")
+
+ # Make sure that Python and the traceback module format the same thing
+ self.assertEqual(traceback_fmt, python_fmt)
+ # Now verify the _tb func output
+ self.assertEqual(tbstderr.getvalue(), tbfile.getvalue())
+ # Now verify the _exc func output
+ self.assertEqual(excstderr.getvalue(), excfile.getvalue())
+ self.assertEqual(excfmt, excfile.getvalue())
+
+ # Make sure that the traceback is properly indented.
+ tb_lines = python_fmt.splitlines()
+ banner = tb_lines[0]
+ self.assertEqual(len(tb_lines), 5)
+ location, source_line = tb_lines[-2], tb_lines[-1]
+ self.assertTrue(banner.startswith('Traceback'))
+ self.assertTrue(location.startswith(' File'))
+ self.assertTrue(source_line.startswith(' raise'))
+
+ def test_traceback_format(self):
+ self.check_traceback_format()
+
+ def test_traceback_format_with_cleared_frames(self):
+ # Check that traceback formatting also works with a clear()ed frame
+ def cleanup_tb(tb):
+ tb.tb_frame.clear()
+ self.check_traceback_format(cleanup_tb)
+
+ def test_stack_format(self):
+ # Verify _stack functions. Note we have to use _getframe(1) to
+ # compare them without this frame appearing in the output
+ with captured_output("stderr") as ststderr:
+ traceback.print_stack(sys._getframe(1))
+ stfile = StringIO()
+ traceback.print_stack(sys._getframe(1), file=stfile)
+ self.assertEqual(ststderr.getvalue(), stfile.getvalue())
+
+ stfmt = traceback.format_stack(sys._getframe(1))
+
+ self.assertEqual(ststderr.getvalue(), "".join(stfmt))
+
+ def test_print_stack(self):
+ def prn():
+ traceback.print_stack()
+ with captured_output("stderr") as stderr:
+ prn()
+ lineno = prn.__code__.co_firstlineno
+ self.assertEqual(stderr.getvalue().splitlines()[-4:], [
+ ' File "%s", line %d, in test_print_stack' % (__file__, lineno+3),
+ ' prn()',
+ ' File "%s", line %d, in prn' % (__file__, lineno+1),
+ ' traceback.print_stack()',
+ ])
+
+ # issue 26823 - Shrink recursive tracebacks
+ def _check_recursive_traceback_display(self, render_exc):
+ # Always show full diffs when this test fails
+ # Note that rearranging things may require adjusting
+ # the relative line numbers in the expected tracebacks
+ self.maxDiff = None
+
+ # Check hitting the recursion limit
+ def f():
+ f()
+
+ with captured_output("stderr") as stderr_f:
+ try:
+ f()
+ except RecursionError:
+ render_exc()
+ else:
+ self.fail("no recursion occurred")
+
+ lineno_f = f.__code__.co_firstlineno
+ result_f = (
+ 'Traceback (most recent call last):\n'
+ f' File "{__file__}", line {lineno_f+5}, in _check_recursive_traceback_display\n'
+ ' f()\n'
+ ' ~^^\n'
+ f' File "{__file__}", line {lineno_f+1}, in f\n'
+ ' f()\n'
+ ' ~^^\n'
+ f' File "{__file__}", line {lineno_f+1}, in f\n'
+ ' f()\n'
+ ' ~^^\n'
+ f' File "{__file__}", line {lineno_f+1}, in f\n'
+ ' f()\n'
+ ' ~^^\n'
+ # XXX: The following line changes depending on whether the tests
+ # are run through the interactive interpreter or with -m
+ # It also varies depending on the platform (stack size)
+ # Fortunately, we don't care about exactness here, so we use regex
+ r' \[Previous line repeated (\d+) more times\]' '\n'
+ 'RecursionError: maximum recursion depth exceeded\n'
+ )
+
+ expected = self._maybe_filter_debug_ranges(result_f.splitlines())
+ actual = stderr_f.getvalue().splitlines()
+
+ # Check the output text matches expectations
+ # 2nd last line contains the repetition count
+ self.assertEqual(actual[:-2], expected[:-2])
+ self.assertRegex(actual[-2], expected[-2])
+ # last line can have additional text appended
+ self.assertIn(expected[-1], actual[-1])
+
+ # Check the recursion count is roughly as expected
+ rec_limit = sys.getrecursionlimit()
+ self.assertIn(int(re.search(r"\d+", actual[-2]).group()), range(rec_limit-60, rec_limit))
+
+ # Check a known (limited) number of recursive invocations
+ def g(count=10):
+ if count:
+ return g(count-1) + 1
+ raise ValueError
+
+ with captured_output("stderr") as stderr_g:
+ try:
+ g()
+ except ValueError:
+ render_exc()
+ else:
+ self.fail("no value error was raised")
+
+ lineno_g = g.__code__.co_firstlineno
+ result_g = (
+ f' File "{__file__}", line {lineno_g+2}, in g\n'
+ ' return g(count-1) + 1\n'
+ ' ~^^^^^^^^^\n'
+ f' File "{__file__}", line {lineno_g+2}, in g\n'
+ ' return g(count-1) + 1\n'
+ ' ~^^^^^^^^^\n'
+ f' File "{__file__}", line {lineno_g+2}, in g\n'
+ ' return g(count-1) + 1\n'
+ ' ~^^^^^^^^^\n'
+ ' [Previous line repeated 7 more times]\n'
+ f' File "{__file__}", line {lineno_g+3}, in g\n'
+ ' raise ValueError\n'
+ 'ValueError\n'
+ )
+ tb_line = (
+ 'Traceback (most recent call last):\n'
+ f' File "{__file__}", line {lineno_g+7}, in _check_recursive_traceback_display\n'
+ ' g()\n'
+ ' ~^^\n'
+ )
+ expected = self._maybe_filter_debug_ranges((tb_line + result_g).splitlines())
+ actual = stderr_g.getvalue().splitlines()
self.assertEqual(actual, expected)
# Check 2 different repetitive sections
@@ -497,6 +2114,7 @@ def h(count=10):
'Traceback (most recent call last):\n'
f' File "{__file__}", line {lineno_h+7}, in _check_recursive_traceback_display\n'
' h()\n'
+ ' ~^^\n'
f' File "{__file__}", line {lineno_h+2}, in h\n'
' return h(count-1)\n'
f' File "{__file__}", line {lineno_h+2}, in h\n'
@@ -506,8 +2124,9 @@ def h(count=10):
' [Previous line repeated 7 more times]\n'
f' File "{__file__}", line {lineno_h+3}, in h\n'
' g()\n'
+ ' ~^^\n'
)
- expected = (result_h + result_g).splitlines()
+ expected = self._maybe_filter_debug_ranges((result_h + result_g).splitlines())
actual = stderr_h.getvalue().splitlines()
self.assertEqual(actual, expected)
@@ -521,21 +2140,25 @@ def h(count=10):
self.fail("no error raised")
result_g = (
f' File "{__file__}", line {lineno_g+2}, in g\n'
- ' return g(count-1)\n'
+ ' return g(count-1) + 1\n'
+ ' ~^^^^^^^^^\n'
f' File "{__file__}", line {lineno_g+2}, in g\n'
- ' return g(count-1)\n'
+ ' return g(count-1) + 1\n'
+ ' ~^^^^^^^^^\n'
f' File "{__file__}", line {lineno_g+2}, in g\n'
- ' return g(count-1)\n'
+ ' return g(count-1) + 1\n'
+ ' ~^^^^^^^^^\n'
f' File "{__file__}", line {lineno_g+3}, in g\n'
' raise ValueError\n'
'ValueError\n'
)
tb_line = (
'Traceback (most recent call last):\n'
- f' File "{__file__}", line {lineno_g+71}, in _check_recursive_traceback_display\n'
+ f' File "{__file__}", line {lineno_g+77}, in _check_recursive_traceback_display\n'
' g(traceback._RECURSIVE_CUTOFF)\n'
+ ' ~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n'
)
- expected = (tb_line + result_g).splitlines()
+ expected = self._maybe_filter_debug_ranges((tb_line + result_g).splitlines())
actual = stderr_g.getvalue().splitlines()
self.assertEqual(actual, expected)
@@ -549,11 +2172,14 @@ def h(count=10):
self.fail("no error raised")
result_g = (
f' File "{__file__}", line {lineno_g+2}, in g\n'
- ' return g(count-1)\n'
+ ' return g(count-1) + 1\n'
+ ' ~^^^^^^^^^\n'
f' File "{__file__}", line {lineno_g+2}, in g\n'
- ' return g(count-1)\n'
+ ' return g(count-1) + 1\n'
+ ' ~^^^^^^^^^\n'
f' File "{__file__}", line {lineno_g+2}, in g\n'
- ' return g(count-1)\n'
+ ' return g(count-1) + 1\n'
+ ' ~^^^^^^^^^\n'
' [Previous line repeated 1 more time]\n'
f' File "{__file__}", line {lineno_g+3}, in g\n'
' raise ValueError\n'
@@ -561,23 +2187,25 @@ def h(count=10):
)
tb_line = (
'Traceback (most recent call last):\n'
- f' File "{__file__}", line {lineno_g+99}, in _check_recursive_traceback_display\n'
+ f' File "{__file__}", line {lineno_g+109}, in _check_recursive_traceback_display\n'
' g(traceback._RECURSIVE_CUTOFF + 1)\n'
+ ' ~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n'
)
- expected = (tb_line + result_g).splitlines()
+ expected = self._maybe_filter_debug_ranges((tb_line + result_g).splitlines())
actual = stderr_g.getvalue().splitlines()
self.assertEqual(actual, expected)
- def test_recursive_traceback_python(self):
- self._check_recursive_traceback_display(traceback.print_exc)
-
- @cpython_only
- def test_recursive_traceback_cpython_internal(self):
- from _testcapi import exception_print
- def render_exc():
- exc_type, exc_value, exc_tb = sys.exc_info()
- exception_print(exc_value)
- self._check_recursive_traceback_display(render_exc)
+ # TODO: RUSTPYTHON
+ @unittest.expectedFailure
+ # @requires_debug_ranges() # XXX: RUSTPYTHON patch
+ def test_recursive_traceback(self):
+ if self.DEBUG_RANGES:
+ self._check_recursive_traceback_display(traceback.print_exc)
+ else:
+ from _testcapi import exception_print
+ def render_exc():
+ exception_print(sys.exception())
+ self._check_recursive_traceback_display(render_exc)
def test_format_stack(self):
def fmt():
@@ -606,8 +2234,8 @@ def __eq__(self, other):
except UnhashableException:
try:
raise ex1
- except UnhashableException:
- exc_type, exc_val, exc_tb = sys.exc_info()
+ except UnhashableException as e:
+ exc_val = e
with captured_output("stderr") as stderr_f:
exception_print(exc_val)
@@ -618,6 +2246,53 @@ def __eq__(self, other):
self.assertIn('UnhashableException: ex2', tb[3])
self.assertIn('UnhashableException: ex1', tb[10])
+ def deep_eg(self):
+ e = TypeError(1)
+ for i in range(2000):
+ e = ExceptionGroup('eg', [e])
+ return e
+
+ @cpython_only
+ def test_exception_group_deep_recursion_capi(self):
+ from _testcapi import exception_print
+ LIMIT = 75
+ eg = self.deep_eg()
+ with captured_output("stderr") as stderr_f:
+ with support.infinite_recursion(max_depth=LIMIT):
+ exception_print(eg)
+ output = stderr_f.getvalue()
+ self.assertIn('ExceptionGroup', output)
+ self.assertLessEqual(output.count('ExceptionGroup'), LIMIT)
+
+ def test_exception_group_deep_recursion_traceback(self):
+ LIMIT = 75
+ eg = self.deep_eg()
+ with captured_output("stderr") as stderr_f:
+ with support.infinite_recursion(max_depth=LIMIT):
+ traceback.print_exception(type(eg), eg, eg.__traceback__)
+ output = stderr_f.getvalue()
+ self.assertIn('ExceptionGroup', output)
+ self.assertLessEqual(output.count('ExceptionGroup'), LIMIT)
+
+ @cpython_only
+ def test_print_exception_bad_type_capi(self):
+ from _testcapi import exception_print
+ with captured_output("stderr") as stderr:
+ with support.catch_unraisable_exception():
+ exception_print(42)
+ self.assertEqual(
+ stderr.getvalue(),
+ ('TypeError: print_exception(): '
+ 'Exception expected for value, int found\n')
+ )
+
+ # TODO: RUSTPYTHON
+ @unittest.expectedFailure
+ def test_print_exception_bad_type_python(self):
+ msg = "Exception expected for value, int found"
+ with self.assertRaisesRegex(TypeError, msg):
+ traceback.print_exception(42)
+
cause_message = (
"\nThe above exception was the direct cause "
@@ -630,24 +2305,49 @@ def __eq__(self, other):
boundaries = re.compile(
'(%s|%s)' % (re.escape(cause_message), re.escape(context_message)))
+@force_not_colorized_test_class
+class TestTracebackFormat(unittest.TestCase, TracebackFormatMixin):
+ pass
+
+@cpython_only
+@force_not_colorized_test_class
+class TestFallbackTracebackFormat(unittest.TestCase, TracebackFormatMixin):
+ DEBUG_RANGES = False
+ def setUp(self) -> None:
+ self.original_unraisable_hook = sys.unraisablehook
+ sys.unraisablehook = lambda *args: None
+ self.original_hook = traceback._print_exception_bltin
+ traceback._print_exception_bltin = lambda *args: 1/0
+ return super().setUp()
+
+ def tearDown(self) -> None:
+ traceback._print_exception_bltin = self.original_hook
+ sys.unraisablehook = self.original_unraisable_hook
+ return super().tearDown()
class BaseExceptionReportingTests:
def get_exception(self, exception_or_callable):
- if isinstance(exception_or_callable, Exception):
+ if isinstance(exception_or_callable, BaseException):
return exception_or_callable
try:
exception_or_callable()
except Exception as e:
return e
+ callable_line = get_exception.__code__.co_firstlineno + 4
+
def zero_div(self):
1/0 # In zero_div
def check_zero_div(self, msg):
lines = msg.splitlines()
- self.assertTrue(lines[-3].startswith(' File'))
- self.assertIn('1/0 # In zero_div', lines[-2])
+ if has_no_debug_ranges():
+ self.assertTrue(lines[-3].startswith(' File'))
+ self.assertIn('1/0 # In zero_div', lines[-2])
+ else:
+ self.assertTrue(lines[-4].startswith(' File'))
+ self.assertIn('1/0 # In zero_div', lines[-3])
self.assertTrue(lines[-1].startswith('ZeroDivisionError'), lines[-1])
def test_simple(self):
@@ -656,12 +2356,18 @@ def test_simple(self):
except ZeroDivisionError as _:
e = _
lines = self.get_report(e).splitlines()
- self.assertEqual(len(lines), 4)
+ if has_no_debug_ranges():
+ self.assertEqual(len(lines), 4)
+ self.assertTrue(lines[3].startswith('ZeroDivisionError'))
+ else:
+ self.assertEqual(len(lines), 5)
+ self.assertTrue(lines[4].startswith('ZeroDivisionError'))
self.assertTrue(lines[0].startswith('Traceback'))
self.assertTrue(lines[1].startswith(' File'))
self.assertIn('1/0 # Marker', lines[2])
- self.assertTrue(lines[3].startswith('ZeroDivisionError'))
+ # TODO: RUSTPYTHON
+ @unittest.expectedFailure
def test_cause(self):
def inner_raise():
try:
@@ -694,16 +2400,16 @@ def test_context_suppression(self):
try:
try:
raise Exception
- except:
+ except Exception:
raise ZeroDivisionError from None
except ZeroDivisionError as _:
e = _
lines = self.get_report(e).splitlines()
self.assertEqual(len(lines), 4)
+ self.assertTrue(lines[3].startswith('ZeroDivisionError'))
self.assertTrue(lines[0].startswith('Traceback'))
self.assertTrue(lines[1].startswith(' File'))
self.assertIn('ZeroDivisionError from None', lines[2])
- self.assertTrue(lines[3].startswith('ZeroDivisionError'))
def test_cause_and_context(self):
# When both a cause and a context are set, only the cause should be
@@ -748,8 +2454,6 @@ def outer_raise():
self.assertIn('inner_raise() # Marker', blocks[2])
self.check_zero_div(blocks[2])
- # TODO: RUSTPYTHON
- @unittest.expectedFailure
def test_syntax_error_offset_at_eol(self):
# See #10186.
def e():
@@ -797,52 +2501,661 @@ def test_message_none(self):
err = self.get_report(Exception(''))
self.assertIn('Exception\n', err)
- def test_exception_modulename_not_unicode(self):
- class X(Exception):
- def __str__(self):
- return "I am X"
-
- X.__module__ = 42
-
- err = self.get_report(X())
- exp = f'.{X.__qualname__}: I am X\n'
- self.assertEqual(exp, err)
-
# TODO: RUSTPYTHON
@unittest.expectedFailure
def test_syntax_error_various_offsets(self):
for offset in range(-5, 10):
for add in [0, 2]:
- text = " "*add + "text%d" % offset
+ text = " " * add + "text%d" % offset
expected = [' File "file.py", line 1']
if offset < 1:
expected.append(" %s" % text.lstrip())
elif offset <= 6:
expected.append(" %s" % text.lstrip())
- expected.append(" %s^" % (" "*(offset-1)))
+ # Set the caret length to match the length of the text minus the offset.
+ caret_length = max(1, len(text.lstrip()) - offset + 1)
+ expected.append(" %s%s" % (" " * (offset - 1), "^" * caret_length))
else:
+ caret_length = max(1, len(text.lstrip()) - 4)
expected.append(" %s" % text.lstrip())
- expected.append(" %s^" % (" "*5))
+ expected.append(" %s%s" % (" " * 5, "^" * caret_length))
expected.append("SyntaxError: msg")
expected.append("")
- err = self.get_report(SyntaxError("msg", ("file.py", 1, offset+add, text)))
+ err = self.get_report(SyntaxError("msg", ("file.py", 1, offset + add, text)))
exp = "\n".join(expected)
self.assertEqual(exp, err)
- def test_format_exception_only_qualname(self):
+ # TODO: RUSTPYTHON
+ @unittest.expectedFailure
+ def test_exception_with_note(self):
+ e = ValueError(123)
+ vanilla = self.get_report(e)
+
+ e.add_note('My Note')
+ self.assertEqual(self.get_report(e), vanilla + 'My Note\n')
+
+ del e.__notes__
+ e.add_note('')
+ self.assertEqual(self.get_report(e), vanilla + '\n')
+
+ del e.__notes__
+ e.add_note('Your Note')
+ self.assertEqual(self.get_report(e), vanilla + 'Your Note\n')
+
+ del e.__notes__
+ self.assertEqual(self.get_report(e), vanilla)
+
+ # TODO: RUSTPYTHON
+ @unittest.expectedFailure
+ def test_exception_with_invalid_notes(self):
+ e = ValueError(123)
+ vanilla = self.get_report(e)
+
+ # non-sequence __notes__
+ class BadThing:
+ def __str__(self):
+ return 'bad str'
+
+ def __repr__(self):
+ return 'bad repr'
+
+ # unprintable, non-sequence __notes__
+ class Unprintable:
+ def __repr__(self):
+ raise ValueError('bad value')
+
+ e.__notes__ = BadThing()
+ notes_repr = 'bad repr'
+ self.assertEqual(self.get_report(e), vanilla + notes_repr + '\n')
+
+ e.__notes__ = Unprintable()
+ err_msg = '<__notes__ repr() failed>'
+ self.assertEqual(self.get_report(e), vanilla + err_msg + '\n')
+
+ # non-string item in the __notes__ sequence
+ e.__notes__ = [BadThing(), 'Final Note']
+ bad_note = 'bad str'
+ self.assertEqual(self.get_report(e), vanilla + bad_note + '\nFinal Note\n')
+
+ # unprintable, non-string item in the __notes__ sequence
+ e.__notes__ = [Unprintable(), 'Final Note']
+ err_msg = ''
+ self.assertEqual(self.get_report(e), vanilla + err_msg + '\nFinal Note\n')
+
+ e.__notes__ = "please do not explode me"
+ err_msg = "'please do not explode me'"
+ self.assertEqual(self.get_report(e), vanilla + err_msg + '\n')
+
+ e.__notes__ = b"please do not show me as numbers"
+ err_msg = "b'please do not show me as numbers'"
+ self.assertEqual(self.get_report(e), vanilla + err_msg + '\n')
+
+ # an exception with a broken __getattr__ raising a non expected error
+ class BrokenException(Exception):
+ broken = False
+ def __getattr__(self, name):
+ if self.broken:
+ raise ValueError(f'no {name}')
+
+ e = BrokenException(123)
+ vanilla = self.get_report(e)
+ e.broken = True
+ self.assertEqual(
+ self.get_report(e),
+ vanilla + "Ignored error getting __notes__: ValueError('no __notes__')\n")
+
+ # TODO: RUSTPYTHON
+ @unittest.expectedFailure
+ def test_exception_with_multiple_notes(self):
+ for e in [ValueError(42), SyntaxError('bad syntax')]:
+ with self.subTest(e=e):
+ vanilla = self.get_report(e)
+
+ e.add_note('Note 1')
+ e.add_note('Note 2')
+ e.add_note('Note 3')
+
+ self.assertEqual(
+ self.get_report(e),
+ vanilla + 'Note 1\n' + 'Note 2\n' + 'Note 3\n')
+
+ del e.__notes__
+ e.add_note('Note 4')
+ del e.__notes__
+ e.add_note('Note 5')
+ e.add_note('Note 6')
+
+ self.assertEqual(
+ self.get_report(e),
+ vanilla + 'Note 5\n' + 'Note 6\n')
+
+ def test_exception_qualname(self):
class A:
class B:
class X(Exception):
def __str__(self):
return "I am X"
- pass
+
err = self.get_report(A.B.X())
str_value = 'I am X'
str_name = '.'.join([A.B.X.__module__, A.B.X.__qualname__])
exp = "%s: %s\n" % (str_name, str_value)
+ self.assertEqual(exp, MODULE_PREFIX + err)
+
+ def test_exception_modulename(self):
+ class X(Exception):
+ def __str__(self):
+ return "I am X"
+
+ for modulename in '__main__', 'builtins', 'some_module':
+ X.__module__ = modulename
+ with self.subTest(modulename=modulename):
+ err = self.get_report(X())
+ str_value = 'I am X'
+ if modulename in ['builtins', '__main__']:
+ str_name = X.__qualname__
+ else:
+ str_name = '.'.join([X.__module__, X.__qualname__])
+ exp = "%s: %s\n" % (str_name, str_value)
+ self.assertEqual(exp, err)
+
+ def test_exception_angle_bracketed_filename(self):
+ src = textwrap.dedent("""
+ try:
+ raise ValueError(42)
+ except Exception as e:
+ exc = e
+ """)
+
+ code = compile(src, "", "exec")
+ g, l = {}, {}
+ exec(code, g, l)
+ err = self.get_report(l['exc'])
+ exp = ' File "", line 3, in \nValueError: 42\n'
+ self.assertIn(exp, err)
+
+ def test_exception_modulename_not_unicode(self):
+ class X(Exception):
+ def __str__(self):
+ return "I am X"
+
+ X.__module__ = 42
+
+ err = self.get_report(X())
+ exp = f'.{X.__qualname__}: I am X\n'
self.assertEqual(exp, err)
+ # TODO: RUSTPYTHON
+ @unittest.expectedFailure
+ def test_exception_bad__str__(self):
+ class X(Exception):
+ def __str__(self):
+ 1/0
+ err = self.get_report(X())
+ str_value = ''
+ str_name = '.'.join([X.__module__, X.__qualname__])
+ self.assertEqual(MODULE_PREFIX + err, f"{str_name}: {str_value}\n")
+
+
+ # #### Exception Groups ####
+
+ # TODO: RUSTPYTHON
+ @unittest.expectedFailure
+ def test_exception_group_basic(self):
+ def exc():
+ raise ExceptionGroup("eg", [ValueError(1), TypeError(2)])
+
+ expected = (
+ f' + Exception Group Traceback (most recent call last):\n'
+ f' | File "{__file__}", line {self.callable_line}, in get_exception\n'
+ f' | exception_or_callable()\n'
+ f' | ~~~~~~~~~~~~~~~~~~~~~^^\n'
+ f' | File "{__file__}", line {exc.__code__.co_firstlineno + 1}, in exc\n'
+ f' | raise ExceptionGroup("eg", [ValueError(1), TypeError(2)])\n'
+ f' | ExceptionGroup: eg (2 sub-exceptions)\n'
+ f' +-+---------------- 1 ----------------\n'
+ f' | ValueError: 1\n'
+ f' +---------------- 2 ----------------\n'
+ f' | TypeError: 2\n'
+ f' +------------------------------------\n')
+
+ report = self.get_report(exc)
+ self.assertEqual(report, expected)
+
+ # TODO: RUSTPYTHON
+ @unittest.expectedFailure
+ def test_exception_group_cause(self):
+ def exc():
+ EG = ExceptionGroup
+ try:
+ raise EG("eg1", [ValueError(1), TypeError(2)])
+ except Exception as e:
+ raise EG("eg2", [ValueError(3), TypeError(4)]) from e
+
+ expected = (f' + Exception Group Traceback (most recent call last):\n'
+ f' | File "{__file__}", line {exc.__code__.co_firstlineno + 3}, in exc\n'
+ f' | raise EG("eg1", [ValueError(1), TypeError(2)])\n'
+ f' | ExceptionGroup: eg1 (2 sub-exceptions)\n'
+ f' +-+---------------- 1 ----------------\n'
+ f' | ValueError: 1\n'
+ f' +---------------- 2 ----------------\n'
+ f' | TypeError: 2\n'
+ f' +------------------------------------\n'
+ f'\n'
+ f'The above exception was the direct cause of the following exception:\n'
+ f'\n'
+ f' + Exception Group Traceback (most recent call last):\n'
+ f' | File "{__file__}", line {self.callable_line}, in get_exception\n'
+ f' | exception_or_callable()\n'
+ f' | ~~~~~~~~~~~~~~~~~~~~~^^\n'
+ f' | File "{__file__}", line {exc.__code__.co_firstlineno + 5}, in exc\n'
+ f' | raise EG("eg2", [ValueError(3), TypeError(4)]) from e\n'
+ f' | ExceptionGroup: eg2 (2 sub-exceptions)\n'
+ f' +-+---------------- 1 ----------------\n'
+ f' | ValueError: 3\n'
+ f' +---------------- 2 ----------------\n'
+ f' | TypeError: 4\n'
+ f' +------------------------------------\n')
+
+ report = self.get_report(exc)
+ self.assertEqual(report, expected)
+
+ # TODO: RUSTPYTHON
+ @unittest.expectedFailure
+ def test_exception_group_context_with_context(self):
+ def exc():
+ EG = ExceptionGroup
+ try:
+ try:
+ raise EG("eg1", [ValueError(1), TypeError(2)])
+ except EG:
+ raise EG("eg2", [ValueError(3), TypeError(4)])
+ except EG:
+ raise ImportError(5)
+
+ expected = (
+ f' + Exception Group Traceback (most recent call last):\n'
+ f' | File "{__file__}", line {exc.__code__.co_firstlineno + 4}, in exc\n'
+ f' | raise EG("eg1", [ValueError(1), TypeError(2)])\n'
+ f' | ExceptionGroup: eg1 (2 sub-exceptions)\n'
+ f' +-+---------------- 1 ----------------\n'
+ f' | ValueError: 1\n'
+ f' +---------------- 2 ----------------\n'
+ f' | TypeError: 2\n'
+ f' +------------------------------------\n'
+ f'\n'
+ f'During handling of the above exception, another exception occurred:\n'
+ f'\n'
+ f' + Exception Group Traceback (most recent call last):\n'
+ f' | File "{__file__}", line {exc.__code__.co_firstlineno + 6}, in exc\n'
+ f' | raise EG("eg2", [ValueError(3), TypeError(4)])\n'
+ f' | ExceptionGroup: eg2 (2 sub-exceptions)\n'
+ f' +-+---------------- 1 ----------------\n'
+ f' | ValueError: 3\n'
+ f' +---------------- 2 ----------------\n'
+ f' | TypeError: 4\n'
+ f' +------------------------------------\n'
+ f'\n'
+ f'During handling of the above exception, another exception occurred:\n'
+ f'\n'
+ f'Traceback (most recent call last):\n'
+ f' File "{__file__}", line {self.callable_line}, in get_exception\n'
+ f' exception_or_callable()\n'
+ f' ~~~~~~~~~~~~~~~~~~~~~^^\n'
+ f' File "{__file__}", line {exc.__code__.co_firstlineno + 8}, in exc\n'
+ f' raise ImportError(5)\n'
+ f'ImportError: 5\n')
+
+ report = self.get_report(exc)
+ self.assertEqual(report, expected)
+
+ # TODO: RUSTPYTHON
+ @unittest.expectedFailure
+ def test_exception_group_nested(self):
+ def exc():
+ EG = ExceptionGroup
+ VE = ValueError
+ TE = TypeError
+ try:
+ try:
+ raise EG("nested", [TE(2), TE(3)])
+ except Exception as e:
+ exc = e
+ raise EG("eg", [VE(1), exc, VE(4)])
+ except EG:
+ raise EG("top", [VE(5)])
+
+ expected = (f' + Exception Group Traceback (most recent call last):\n'
+ f' | File "{__file__}", line {exc.__code__.co_firstlineno + 9}, in exc\n'
+ f' | raise EG("eg", [VE(1), exc, VE(4)])\n'
+ f' | ExceptionGroup: eg (3 sub-exceptions)\n'
+ f' +-+---------------- 1 ----------------\n'
+ f' | ValueError: 1\n'
+ f' +---------------- 2 ----------------\n'
+ f' | Exception Group Traceback (most recent call last):\n'
+ f' | File "{__file__}", line {exc.__code__.co_firstlineno + 6}, in exc\n'
+ f' | raise EG("nested", [TE(2), TE(3)])\n'
+ f' | ExceptionGroup: nested (2 sub-exceptions)\n'
+ f' +-+---------------- 1 ----------------\n'
+ f' | TypeError: 2\n'
+ f' +---------------- 2 ----------------\n'
+ f' | TypeError: 3\n'
+ f' +------------------------------------\n'
+ f' +---------------- 3 ----------------\n'
+ f' | ValueError: 4\n'
+ f' +------------------------------------\n'
+ f'\n'
+ f'During handling of the above exception, another exception occurred:\n'
+ f'\n'
+ f' + Exception Group Traceback (most recent call last):\n'
+ f' | File "{__file__}", line {self.callable_line}, in get_exception\n'
+ f' | exception_or_callable()\n'
+ f' | ~~~~~~~~~~~~~~~~~~~~~^^\n'
+ f' | File "{__file__}", line {exc.__code__.co_firstlineno + 11}, in exc\n'
+ f' | raise EG("top", [VE(5)])\n'
+ f' | ExceptionGroup: top (1 sub-exception)\n'
+ f' +-+---------------- 1 ----------------\n'
+ f' | ValueError: 5\n'
+ f' +------------------------------------\n')
+
+ report = self.get_report(exc)
+ self.assertEqual(report, expected)
+
+ # TODO: RUSTPYTHON
+ @unittest.expectedFailure
+ def test_exception_group_width_limit(self):
+ excs = []
+ for i in range(1000):
+ excs.append(ValueError(i))
+ eg = ExceptionGroup('eg', excs)
+
+ expected = (' | ExceptionGroup: eg (1000 sub-exceptions)\n'
+ ' +-+---------------- 1 ----------------\n'
+ ' | ValueError: 0\n'
+ ' +---------------- 2 ----------------\n'
+ ' | ValueError: 1\n'
+ ' +---------------- 3 ----------------\n'
+ ' | ValueError: 2\n'
+ ' +---------------- 4 ----------------\n'
+ ' | ValueError: 3\n'
+ ' +---------------- 5 ----------------\n'
+ ' | ValueError: 4\n'
+ ' +---------------- 6 ----------------\n'
+ ' | ValueError: 5\n'
+ ' +---------------- 7 ----------------\n'
+ ' | ValueError: 6\n'
+ ' +---------------- 8 ----------------\n'
+ ' | ValueError: 7\n'
+ ' +---------------- 9 ----------------\n'
+ ' | ValueError: 8\n'
+ ' +---------------- 10 ----------------\n'
+ ' | ValueError: 9\n'
+ ' +---------------- 11 ----------------\n'
+ ' | ValueError: 10\n'
+ ' +---------------- 12 ----------------\n'
+ ' | ValueError: 11\n'
+ ' +---------------- 13 ----------------\n'
+ ' | ValueError: 12\n'
+ ' +---------------- 14 ----------------\n'
+ ' | ValueError: 13\n'
+ ' +---------------- 15 ----------------\n'
+ ' | ValueError: 14\n'
+ ' +---------------- ... ----------------\n'
+ ' | and 985 more exceptions\n'
+ ' +------------------------------------\n')
+
+ report = self.get_report(eg)
+ self.assertEqual(report, expected)
+
+ # TODO: RUSTPYTHON
+ @unittest.expectedFailure
+ def test_exception_group_depth_limit(self):
+ exc = TypeError('bad type')
+ for i in range(1000):
+ exc = ExceptionGroup(
+ f'eg{i}',
+ [ValueError(i), exc, ValueError(-i)])
+
+ expected = (' | ExceptionGroup: eg999 (3 sub-exceptions)\n'
+ ' +-+---------------- 1 ----------------\n'
+ ' | ValueError: 999\n'
+ ' +---------------- 2 ----------------\n'
+ ' | ExceptionGroup: eg998 (3 sub-exceptions)\n'
+ ' +-+---------------- 1 ----------------\n'
+ ' | ValueError: 998\n'
+ ' +---------------- 2 ----------------\n'
+ ' | ExceptionGroup: eg997 (3 sub-exceptions)\n'
+ ' +-+---------------- 1 ----------------\n'
+ ' | ValueError: 997\n'
+ ' +---------------- 2 ----------------\n'
+ ' | ExceptionGroup: eg996 (3 sub-exceptions)\n'
+ ' +-+---------------- 1 ----------------\n'
+ ' | ValueError: 996\n'
+ ' +---------------- 2 ----------------\n'
+ ' | ExceptionGroup: eg995 (3 sub-exceptions)\n'
+ ' +-+---------------- 1 ----------------\n'
+ ' | ValueError: 995\n'
+ ' +---------------- 2 ----------------\n'
+ ' | ExceptionGroup: eg994 (3 sub-exceptions)\n'
+ ' +-+---------------- 1 ----------------\n'
+ ' | ValueError: 994\n'
+ ' +---------------- 2 ----------------\n'
+ ' | ExceptionGroup: eg993 (3 sub-exceptions)\n'
+ ' +-+---------------- 1 ----------------\n'
+ ' | ValueError: 993\n'
+ ' +---------------- 2 ----------------\n'
+ ' | ExceptionGroup: eg992 (3 sub-exceptions)\n'
+ ' +-+---------------- 1 ----------------\n'
+ ' | ValueError: 992\n'
+ ' +---------------- 2 ----------------\n'
+ ' | ExceptionGroup: eg991 (3 sub-exceptions)\n'
+ ' +-+---------------- 1 ----------------\n'
+ ' | ValueError: 991\n'
+ ' +---------------- 2 ----------------\n'
+ ' | ExceptionGroup: eg990 (3 sub-exceptions)\n'
+ ' +-+---------------- 1 ----------------\n'
+ ' | ValueError: 990\n'
+ ' +---------------- 2 ----------------\n'
+ ' | ... (max_group_depth is 10)\n'
+ ' +---------------- 3 ----------------\n'
+ ' | ValueError: -990\n'
+ ' +------------------------------------\n'
+ ' +---------------- 3 ----------------\n'
+ ' | ValueError: -991\n'
+ ' +------------------------------------\n'
+ ' +---------------- 3 ----------------\n'
+ ' | ValueError: -992\n'
+ ' +------------------------------------\n'
+ ' +---------------- 3 ----------------\n'
+ ' | ValueError: -993\n'
+ ' +------------------------------------\n'
+ ' +---------------- 3 ----------------\n'
+ ' | ValueError: -994\n'
+ ' +------------------------------------\n'
+ ' +---------------- 3 ----------------\n'
+ ' | ValueError: -995\n'
+ ' +------------------------------------\n'
+ ' +---------------- 3 ----------------\n'
+ ' | ValueError: -996\n'
+ ' +------------------------------------\n'
+ ' +---------------- 3 ----------------\n'
+ ' | ValueError: -997\n'
+ ' +------------------------------------\n'
+ ' +---------------- 3 ----------------\n'
+ ' | ValueError: -998\n'
+ ' +------------------------------------\n'
+ ' +---------------- 3 ----------------\n'
+ ' | ValueError: -999\n'
+ ' +------------------------------------\n')
+
+ report = self.get_report(exc)
+ self.assertEqual(report, expected)
+
+ # TODO: RUSTPYTHON
+ @unittest.expectedFailure
+ def test_exception_group_with_notes(self):
+ def exc():
+ try:
+ excs = []
+ for msg in ['bad value', 'terrible value']:
+ try:
+ raise ValueError(msg)
+ except ValueError as e:
+ e.add_note(f'the {msg}')
+ excs.append(e)
+ raise ExceptionGroup("nested", excs)
+ except ExceptionGroup as e:
+ e.add_note(('>> Multi line note\n'
+ '>> Because I am such\n'
+ '>> an important exception.\n'
+ '>> empty lines work too\n'
+ '\n'
+ '(that was an empty line)'))
+ raise
+
+ expected = (f' + Exception Group Traceback (most recent call last):\n'
+ f' | File "{__file__}", line {self.callable_line}, in get_exception\n'
+ f' | exception_or_callable()\n'
+ f' | ~~~~~~~~~~~~~~~~~~~~~^^\n'
+ f' | File "{__file__}", line {exc.__code__.co_firstlineno + 9}, in exc\n'
+ f' | raise ExceptionGroup("nested", excs)\n'
+ f' | ExceptionGroup: nested (2 sub-exceptions)\n'
+ f' | >> Multi line note\n'
+ f' | >> Because I am such\n'
+ f' | >> an important exception.\n'
+ f' | >> empty lines work too\n'
+ f' | \n'
+ f' | (that was an empty line)\n'
+ f' +-+---------------- 1 ----------------\n'
+ f' | Traceback (most recent call last):\n'
+ f' | File "{__file__}", line {exc.__code__.co_firstlineno + 5}, in exc\n'
+ f' | raise ValueError(msg)\n'
+ f' | ValueError: bad value\n'
+ f' | the bad value\n'
+ f' +---------------- 2 ----------------\n'
+ f' | Traceback (most recent call last):\n'
+ f' | File "{__file__}", line {exc.__code__.co_firstlineno + 5}, in exc\n'
+ f' | raise ValueError(msg)\n'
+ f' | ValueError: terrible value\n'
+ f' | the terrible value\n'
+ f' +------------------------------------\n')
+
+ report = self.get_report(exc)
+ self.assertEqual(report, expected)
+
+ # TODO: RUSTPYTHON
+ @unittest.expectedFailure
+ def test_exception_group_with_multiple_notes(self):
+ def exc():
+ try:
+ excs = []
+ for msg in ['bad value', 'terrible value']:
+ try:
+ raise ValueError(msg)
+ except ValueError as e:
+ e.add_note(f'the {msg}')
+ e.add_note(f'Goodbye {msg}')
+ excs.append(e)
+ raise ExceptionGroup("nested", excs)
+ except ExceptionGroup as e:
+ e.add_note(('>> Multi line note\n'
+ '>> Because I am such\n'
+ '>> an important exception.\n'
+ '>> empty lines work too\n'
+ '\n'
+ '(that was an empty line)'))
+ e.add_note('Goodbye!')
+ raise
+
+ expected = (f' + Exception Group Traceback (most recent call last):\n'
+ f' | File "{__file__}", line {self.callable_line}, in get_exception\n'
+ f' | exception_or_callable()\n'
+ f' | ~~~~~~~~~~~~~~~~~~~~~^^\n'
+ f' | File "{__file__}", line {exc.__code__.co_firstlineno + 10}, in exc\n'
+ f' | raise ExceptionGroup("nested", excs)\n'
+ f' | ExceptionGroup: nested (2 sub-exceptions)\n'
+ f' | >> Multi line note\n'
+ f' | >> Because I am such\n'
+ f' | >> an important exception.\n'
+ f' | >> empty lines work too\n'
+ f' | \n'
+ f' | (that was an empty line)\n'
+ f' | Goodbye!\n'
+ f' +-+---------------- 1 ----------------\n'
+ f' | Traceback (most recent call last):\n'
+ f' | File "{__file__}", line {exc.__code__.co_firstlineno + 5}, in exc\n'
+ f' | raise ValueError(msg)\n'
+ f' | ValueError: bad value\n'
+ f' | the bad value\n'
+ f' | Goodbye bad value\n'
+ f' +---------------- 2 ----------------\n'
+ f' | Traceback (most recent call last):\n'
+ f' | File "{__file__}", line {exc.__code__.co_firstlineno + 5}, in exc\n'
+ f' | raise ValueError(msg)\n'
+ f' | ValueError: terrible value\n'
+ f' | the terrible value\n'
+ f' | Goodbye terrible value\n'
+ f' +------------------------------------\n')
+
+ report = self.get_report(exc)
+ self.assertEqual(report, expected)
+
+ # TODO: RUSTPYTHON
+ '''
+ def test_exception_group_wrapped_naked(self):
+ # See gh-128799
+
+ def exc():
+ try:
+ raise Exception(42)
+ except* Exception as e:
+ raise
+ expected = (f' + Exception Group Traceback (most recent call last):\n'
+ f' | File "{__file__}", line {self.callable_line}, in get_exception\n'
+ f' | exception_or_callable()\n'
+ f' | ~~~~~~~~~~~~~~~~~~~~~^^\n'
+ f' | File "{__file__}", line {exc.__code__.co_firstlineno + 3}, in exc\n'
+ f' | except* Exception as e:\n'
+ f' | raise\n'
+ f' | ExceptionGroup: (1 sub-exception)\n'
+ f' +-+---------------- 1 ----------------\n'
+ f' | Traceback (most recent call last):\n'
+ f' | File "{__file__}", line {exc.__code__.co_firstlineno + 2}, in exc\n'
+ f' | raise Exception(42)\n'
+ f' | Exception: 42\n'
+ f' +------------------------------------\n')
+
+ report = self.get_report(exc)
+ self.assertEqual(report, expected)
+
+ def test_KeyboardInterrupt_at_first_line_of_frame(self):
+ # see GH-93249
+ def f():
+ return sys._getframe()
+
+ tb_next = None
+ frame = f()
+ lasti = 0
+ lineno = f.__code__.co_firstlineno
+ tb = types.TracebackType(tb_next, frame, lasti, lineno)
+
+ exc = KeyboardInterrupt()
+ exc.__traceback__ = tb
+
+ expected = (f'Traceback (most recent call last):\n'
+ f' File "{__file__}", line {lineno}, in f\n'
+ f' def f():\n'
+ f'\n'
+ f'KeyboardInterrupt\n')
+
+ report = self.get_report(exc)
+ # remove trailing writespace:
+ report = '\n'.join([l.rstrip() for l in report.split('\n')])
+ self.assertEqual(report, expected)
+ '''
+
+@force_not_colorized_test_class
class PyExcReportingTests(BaseExceptionReportingTests, unittest.TestCase):
#
# This checks reporting through the 'traceback' module, with both
@@ -859,6 +3172,7 @@ def get_report(self, e):
return s
+@force_not_colorized_test_class
class CExcReportingTests(BaseExceptionReportingTests, unittest.TestCase):
#
# This checks built-in reporting by the interpreter.
@@ -941,8 +3255,8 @@ def assertEqualExcept(actual, expected, ignore):
def test_extract_tb(self):
try:
self.last_raises5()
- except Exception:
- exc_type, exc_value, tb = sys.exc_info()
+ except Exception as e:
+ tb = e.__traceback__
def extract(**kwargs):
return traceback.extract_tb(tb, **kwargs)
@@ -968,12 +3282,12 @@ def extract(**kwargs):
def test_format_exception(self):
try:
self.last_raises5()
- except Exception:
- exc_type, exc_value, tb = sys.exc_info()
+ except Exception as e:
+ exc = e
# [1:-1] to exclude "Traceback (...)" header and
# exception type and value
def extract(**kwargs):
- return traceback.format_exception(exc_type, exc_value, tb, **kwargs)[1:-1]
+ return traceback.format_exception(exc, **kwargs)[1:-1]
with support.swap_attr(sys, 'tracebacklimit', 1000):
nolim = extract()
@@ -1013,8 +3327,8 @@ def inner():
try:
outer()
- except:
- type_, value, tb = sys.exc_info()
+ except BaseException as e:
+ tb = e.__traceback__
# Initial assertion: there's one local in the inner frame.
inner_frame = tb.tb_next.tb_next.tb_next.tb_frame
@@ -1057,10 +3371,12 @@ def test_basics(self):
self.assertNotEqual(f, object())
self.assertEqual(f, ALWAYS_EQ)
+ # TODO: RUSTPYTHON
+ @unittest.expectedFailure
def test_lazy_lines(self):
linecache.clearcache()
f = traceback.FrameSummary("f", 1, "dummy", lookup_line=False)
- self.assertEqual(None, f._line)
+ self.assertEqual(None, f._lines)
linecache.lazycache("f", globals())
self.assertEqual(
'"""Test cases for traceback module"""',
@@ -1092,8 +3408,8 @@ def deeper():
def test_walk_tb(self):
try:
1/0
- except Exception:
- _, _, tb = sys.exc_info()
+ except Exception as e:
+ tb = e.__traceback__
s = list(traceback.walk_tb(tb))
self.assertEqual(len(s), 1)
@@ -1177,23 +3493,145 @@ def some_inner(k, v):
' v = 4\n' % (__file__, some_inner.__code__.co_firstlineno + 3)
], s.format())
-class TestTracebackException(unittest.TestCase):
+ # TODO: RUSTPYTHON
+ @unittest.expectedFailure
+ def test_custom_format_frame(self):
+ class CustomStackSummary(traceback.StackSummary):
+ def format_frame_summary(self, frame_summary, colorize=False):
+ return f'{frame_summary.filename}:{frame_summary.lineno}'
- def test_smoke(self):
- try:
+ def some_inner():
+ return CustomStackSummary.extract(
+ traceback.walk_stack(None), limit=1)
+
+ s = some_inner()
+ self.assertEqual(
+ s.format(),
+ [f'{__file__}:{some_inner.__code__.co_firstlineno + 1}'])
+
+ # TODO: RUSTPYTHON
+ @unittest.expectedFailure
+ def test_dropping_frames(self):
+ def f():
1/0
- except Exception:
- exc_info = sys.exc_info()
- exc = traceback.TracebackException(*exc_info)
+
+ def g():
+ try:
+ f()
+ except Exception as e:
+ return e.__traceback__
+
+ tb = g()
+
+ class Skip_G(traceback.StackSummary):
+ def format_frame_summary(self, frame_summary, colorize=False):
+ if frame_summary.name == 'g':
+ return None
+ return super().format_frame_summary(frame_summary)
+
+ stack = Skip_G.extract(
+ traceback.walk_tb(tb)).format()
+
+ self.assertEqual(len(stack), 1)
+ lno = f.__code__.co_firstlineno + 1
+ self.assertEqual(
+ stack[0],
+ f' File "{__file__}", line {lno}, in f\n 1/0\n'
+ )
+
+ # TODO: RUSTPYTHON
+ @unittest.expectedFailure
+ def test_summary_should_show_carets(self):
+ # See: https://github.com/python/cpython/issues/122353
+
+ # statement to execute and to get a ZeroDivisionError for a traceback
+ statement = "abcdef = 1 / 0 and 2.0"
+ colno = statement.index('1 / 0')
+ end_colno = colno + len('1 / 0')
+
+ # Actual line to use when rendering the traceback
+ # and whose AST will be extracted (it will be empty).
+ cached_line = '# this line will be used during rendering'
+ self.addCleanup(unlink, TESTFN)
+ with open(TESTFN, "w") as file:
+ file.write(cached_line)
+ linecache.updatecache(TESTFN, {})
+
+ try:
+ exec(compile(statement, TESTFN, "exec"))
+ except ZeroDivisionError as exc:
+ # This is the simplest way to create a StackSummary
+ # whose FrameSummary items have their column offsets.
+ s = traceback.TracebackException.from_exception(exc).stack
+ self.assertIsInstance(s, traceback.StackSummary)
+ with unittest.mock.patch.object(s, '_should_show_carets',
+ wraps=s._should_show_carets) as ff:
+ self.assertEqual(len(s), 2)
+ self.assertListEqual(
+ s.format_frame_summary(s[1]).splitlines(),
+ [
+ f' File "{TESTFN}", line 1, in ',
+ f' {cached_line}'
+ ]
+ )
+ ff.assert_called_with(colno, end_colno, [cached_line], None)
+
+class Unrepresentable:
+ def __repr__(self) -> str:
+ raise Exception("Unrepresentable")
+
+
+# Used in test_dont_swallow_cause_or_context_of_falsey_exception and
+# test_dont_swallow_subexceptions_of_falsey_exceptiongroup.
+class FalseyException(Exception):
+ def __bool__(self):
+ return False
+
+
+class FalseyExceptionGroup(ExceptionGroup):
+ def __bool__(self):
+ return False
+
+
+class TestTracebackException(unittest.TestCase):
+ def do_test_smoke(self, exc, expected_type_str):
+ try:
+ raise exc
+ except Exception as e:
+ exc_obj = e
+ exc = traceback.TracebackException.from_exception(e)
expected_stack = traceback.StackSummary.extract(
- traceback.walk_tb(exc_info[2]))
+ traceback.walk_tb(e.__traceback__))
self.assertEqual(None, exc.__cause__)
self.assertEqual(None, exc.__context__)
self.assertEqual(False, exc.__suppress_context__)
self.assertEqual(expected_stack, exc.stack)
- self.assertEqual(exc_info[0], exc.exc_type)
- self.assertEqual(str(exc_info[1]), str(exc))
+ with self.assertWarns(DeprecationWarning):
+ self.assertEqual(type(exc_obj), exc.exc_type)
+ self.assertEqual(expected_type_str, exc.exc_type_str)
+ self.assertEqual(str(exc_obj), str(exc))
+
+ # TODO: RUSTPYTHON
+ @unittest.expectedFailure
+ def test_smoke_builtin(self):
+ self.do_test_smoke(ValueError(42), 'ValueError')
+
+ # TODO: RUSTPYTHON
+ @unittest.expectedFailure
+ def test_smoke_user_exception(self):
+ class MyException(Exception):
+ pass
+
+ if __name__ == '__main__':
+ expected = ('TestTracebackException.'
+ 'test_smoke_user_exception..MyException')
+ else:
+ expected = ('test.test_traceback.TestTracebackException.'
+ 'test_smoke_user_exception..MyException')
+ self.do_test_smoke(MyException('bad things happened'), expected)
+ # TODO: RUSTPYTHON
+ @unittest.expectedFailure
def test_from_exception(self):
# Check all the parameters are accepted.
def foo():
@@ -1201,9 +3639,10 @@ def foo():
try:
foo()
except Exception as e:
- exc_info = sys.exc_info()
+ exc_obj = e
+ tb = e.__traceback__
self.expected_stack = traceback.StackSummary.extract(
- traceback.walk_tb(exc_info[2]), limit=1, lookup_lines=False,
+ traceback.walk_tb(tb), limit=1, lookup_lines=False,
capture_locals=True)
self.exc = traceback.TracebackException.from_exception(
e, limit=1, lookup_lines=False, capture_locals=True)
@@ -1213,50 +3652,60 @@ def foo():
self.assertEqual(None, exc.__context__)
self.assertEqual(False, exc.__suppress_context__)
self.assertEqual(expected_stack, exc.stack)
- self.assertEqual(exc_info[0], exc.exc_type)
- self.assertEqual(str(exc_info[1]), str(exc))
+ with self.assertWarns(DeprecationWarning):
+ self.assertEqual(type(exc_obj), exc.exc_type)
+ self.assertEqual(type(exc_obj).__name__, exc.exc_type_str)
+ self.assertEqual(str(exc_obj), str(exc))
+ # TODO: RUSTPYTHON
+ @unittest.expectedFailure
def test_cause(self):
try:
try:
1/0
finally:
- exc_info_context = sys.exc_info()
- exc_context = traceback.TracebackException(*exc_info_context)
+ exc = sys.exception()
+ exc_context = traceback.TracebackException.from_exception(exc)
cause = Exception("cause")
raise Exception("uh oh") from cause
- except Exception:
- exc_info = sys.exc_info()
- exc = traceback.TracebackException(*exc_info)
+ except Exception as e:
+ exc_obj = e
+ exc = traceback.TracebackException.from_exception(e)
expected_stack = traceback.StackSummary.extract(
- traceback.walk_tb(exc_info[2]))
+ traceback.walk_tb(e.__traceback__))
exc_cause = traceback.TracebackException(Exception, cause, None)
self.assertEqual(exc_cause, exc.__cause__)
self.assertEqual(exc_context, exc.__context__)
self.assertEqual(True, exc.__suppress_context__)
self.assertEqual(expected_stack, exc.stack)
- self.assertEqual(exc_info[0], exc.exc_type)
- self.assertEqual(str(exc_info[1]), str(exc))
+ with self.assertWarns(DeprecationWarning):
+ self.assertEqual(type(exc_obj), exc.exc_type)
+ self.assertEqual(type(exc_obj).__name__, exc.exc_type_str)
+ self.assertEqual(str(exc_obj), str(exc))
+ # TODO: RUSTPYTHON
+ @unittest.expectedFailure
def test_context(self):
try:
try:
1/0
finally:
- exc_info_context = sys.exc_info()
- exc_context = traceback.TracebackException(*exc_info_context)
+ exc = sys.exception()
+ exc_context = traceback.TracebackException.from_exception(exc)
raise Exception("uh oh")
- except Exception:
- exc_info = sys.exc_info()
- exc = traceback.TracebackException(*exc_info)
+ except Exception as e:
+ exc_obj = e
+ exc = traceback.TracebackException.from_exception(e)
expected_stack = traceback.StackSummary.extract(
- traceback.walk_tb(exc_info[2]))
+ traceback.walk_tb(e.__traceback__))
self.assertEqual(None, exc.__cause__)
self.assertEqual(exc_context, exc.__context__)
self.assertEqual(False, exc.__suppress_context__)
self.assertEqual(expected_stack, exc.stack)
- self.assertEqual(exc_info[0], exc.exc_type)
- self.assertEqual(str(exc_info[1]), str(exc))
+ with self.assertWarns(DeprecationWarning):
+ self.assertEqual(type(exc_obj), exc.exc_type)
+ self.assertEqual(type(exc_obj).__name__, exc.exc_type_str)
+ self.assertEqual(str(exc_obj), str(exc))
# TODO: RUSTPYTHON
@unittest.expectedFailure
@@ -1264,17 +3713,17 @@ def test_long_context_chain(self):
def f():
try:
1/0
- except:
+ except ZeroDivisionError:
f()
try:
f()
- except RecursionError:
- exc_info = sys.exc_info()
+ except RecursionError as e:
+ exc_obj = e
else:
self.fail("Exception not raised")
- te = traceback.TracebackException(*exc_info)
+ te = traceback.TracebackException.from_exception(exc_obj)
res = list(te.format())
# many ZeroDiv errors followed by the RecursionError
@@ -1285,6 +3734,8 @@ def f():
self.assertIn(
"RecursionError: maximum recursion depth exceeded", res[-1])
+ # TODO: RUSTPYTHON
+ @unittest.expectedFailure
def test_compact_with_cause(self):
try:
try:
@@ -1292,58 +3743,77 @@ def test_compact_with_cause(self):
finally:
cause = Exception("cause")
raise Exception("uh oh") from cause
- except Exception:
- exc_info = sys.exc_info()
- exc = traceback.TracebackException(*exc_info, compact=True)
+ except Exception as e:
+ exc_obj = e
+ exc = traceback.TracebackException.from_exception(exc_obj, compact=True)
expected_stack = traceback.StackSummary.extract(
- traceback.walk_tb(exc_info[2]))
+ traceback.walk_tb(exc_obj.__traceback__))
exc_cause = traceback.TracebackException(Exception, cause, None)
self.assertEqual(exc_cause, exc.__cause__)
self.assertEqual(None, exc.__context__)
self.assertEqual(True, exc.__suppress_context__)
self.assertEqual(expected_stack, exc.stack)
- self.assertEqual(exc_info[0], exc.exc_type)
- self.assertEqual(str(exc_info[1]), str(exc))
+ with self.assertWarns(DeprecationWarning):
+ self.assertEqual(type(exc_obj), exc.exc_type)
+ self.assertEqual(type(exc_obj).__name__, exc.exc_type_str)
+ self.assertEqual(str(exc_obj), str(exc))
+ # TODO: RUSTPYTHON
+ @unittest.expectedFailure
def test_compact_no_cause(self):
try:
try:
1/0
finally:
- exc_info_context = sys.exc_info()
- exc_context = traceback.TracebackException(*exc_info_context)
+ exc = sys.exception()
+ exc_context = traceback.TracebackException.from_exception(exc)
raise Exception("uh oh")
- except Exception:
- exc_info = sys.exc_info()
- exc = traceback.TracebackException(*exc_info, compact=True)
+ except Exception as e:
+ exc_obj = e
+ exc = traceback.TracebackException.from_exception(e, compact=True)
expected_stack = traceback.StackSummary.extract(
- traceback.walk_tb(exc_info[2]))
+ traceback.walk_tb(exc_obj.__traceback__))
self.assertEqual(None, exc.__cause__)
self.assertEqual(exc_context, exc.__context__)
self.assertEqual(False, exc.__suppress_context__)
self.assertEqual(expected_stack, exc.stack)
- self.assertEqual(exc_info[0], exc.exc_type)
- self.assertEqual(str(exc_info[1]), str(exc))
+ with self.assertWarns(DeprecationWarning):
+ self.assertEqual(type(exc_obj), exc.exc_type)
+ self.assertEqual(type(exc_obj).__name__, exc.exc_type_str)
+ self.assertEqual(str(exc_obj), str(exc))
+
+ # TODO: RUSTPYTHON
+ @unittest.expectedFailure
+ def test_no_save_exc_type(self):
+ try:
+ 1/0
+ except Exception as e:
+ exc = e
+
+ te = traceback.TracebackException.from_exception(
+ exc, save_exc_type=False)
+ with self.assertWarns(DeprecationWarning):
+ self.assertIsNone(te.exc_type)
def test_no_refs_to_exception_and_traceback_objects(self):
try:
1/0
- except Exception:
- exc_info = sys.exc_info()
+ except Exception as e:
+ exc_obj = e
- refcnt1 = sys.getrefcount(exc_info[1])
- refcnt2 = sys.getrefcount(exc_info[2])
- exc = traceback.TracebackException(*exc_info)
- self.assertEqual(sys.getrefcount(exc_info[1]), refcnt1)
- self.assertEqual(sys.getrefcount(exc_info[2]), refcnt2)
+ refcnt1 = sys.getrefcount(exc_obj)
+ refcnt2 = sys.getrefcount(exc_obj.__traceback__)
+ exc = traceback.TracebackException.from_exception(exc_obj)
+ self.assertEqual(sys.getrefcount(exc_obj), refcnt1)
+ self.assertEqual(sys.getrefcount(exc_obj.__traceback__), refcnt2)
def test_comparison_basic(self):
try:
1/0
- except Exception:
- exc_info = sys.exc_info()
- exc = traceback.TracebackException(*exc_info)
- exc2 = traceback.TracebackException(*exc_info)
+ except Exception as e:
+ exc_obj = e
+ exc = traceback.TracebackException.from_exception(exc_obj)
+ exc2 = traceback.TracebackException.from_exception(exc_obj)
self.assertIsNot(exc, exc2)
self.assertEqual(exc, exc2)
self.assertNotEqual(exc, object())
@@ -1355,7 +3825,7 @@ def test_comparison_params_variations(self):
def raise_exc():
try:
raise ValueError('bad value')
- except:
+ except ValueError:
raise
def raise_with_locals():
@@ -1364,28 +3834,28 @@ def raise_with_locals():
try:
raise_with_locals()
- except Exception:
- exc_info = sys.exc_info()
+ except Exception as e:
+ exc_obj = e
- exc = traceback.TracebackException(*exc_info)
- exc1 = traceback.TracebackException(*exc_info, limit=10)
- exc2 = traceback.TracebackException(*exc_info, limit=2)
+ exc = traceback.TracebackException.from_exception(exc_obj)
+ exc1 = traceback.TracebackException.from_exception(exc_obj, limit=10)
+ exc2 = traceback.TracebackException.from_exception(exc_obj, limit=2)
self.assertEqual(exc, exc1) # limit=10 gets all frames
self.assertNotEqual(exc, exc2) # limit=2 truncates the output
# locals change the output
- exc3 = traceback.TracebackException(*exc_info, capture_locals=True)
+ exc3 = traceback.TracebackException.from_exception(exc_obj, capture_locals=True)
self.assertNotEqual(exc, exc3)
# there are no locals in the innermost frame
- exc4 = traceback.TracebackException(*exc_info, limit=-1)
- exc5 = traceback.TracebackException(*exc_info, limit=-1, capture_locals=True)
+ exc4 = traceback.TracebackException.from_exception(exc_obj, limit=-1)
+ exc5 = traceback.TracebackException.from_exception(exc_obj, limit=-1, capture_locals=True)
self.assertEqual(exc4, exc5)
# there are locals in the next-to-innermost frame
- exc6 = traceback.TracebackException(*exc_info, limit=-2)
- exc7 = traceback.TracebackException(*exc_info, limit=-2, capture_locals=True)
+ exc6 = traceback.TracebackException.from_exception(exc_obj, limit=-2)
+ exc7 = traceback.TracebackException.from_exception(exc_obj, limit=-2, capture_locals=True)
self.assertNotEqual(exc6, exc7)
def test_comparison_equivalent_exceptions_are_equal(self):
@@ -1393,8 +3863,8 @@ def test_comparison_equivalent_exceptions_are_equal(self):
for _ in range(2):
try:
1/0
- except:
- excs.append(traceback.TracebackException(*sys.exc_info()))
+ except Exception as e:
+ excs.append(traceback.TracebackException.from_exception(e))
self.assertEqual(excs[0], excs[1])
self.assertEqual(list(excs[0].format()), list(excs[1].format()))
@@ -1410,9 +3880,9 @@ def __eq__(self, other):
except UnhashableException:
try:
raise ex1
- except UnhashableException:
- exc_info = sys.exc_info()
- exc = traceback.TracebackException(*exc_info)
+ except UnhashableException as e:
+ exc_obj = e
+ exc = traceback.TracebackException.from_exception(exc_obj)
formatted = list(exc.format())
self.assertIn('UnhashableException: ex2\n', formatted[2])
self.assertIn('UnhashableException: ex1\n', formatted[6])
@@ -1425,11 +3895,10 @@ def recurse(n):
1/0
try:
recurse(10)
- except Exception:
- exc_info = sys.exc_info()
- exc = traceback.TracebackException(*exc_info, limit=5)
+ except Exception as e:
+ exc = traceback.TracebackException.from_exception(e, limit=5)
expected_stack = traceback.StackSummary.extract(
- traceback.walk_tb(exc_info[2]), limit=5)
+ traceback.walk_tb(e.__traceback__), limit=5)
self.assertEqual(expected_stack, exc.stack)
def test_lookup_lines(self):
@@ -1437,29 +3906,32 @@ def test_lookup_lines(self):
e = Exception("uh oh")
c = test_code('/foo.py', 'method')
f = test_frame(c, None, None)
- tb = test_tb(f, 6, None)
+ tb = test_tb(f, 6, None, 0)
exc = traceback.TracebackException(Exception, e, tb, lookup_lines=False)
self.assertEqual(linecache.cache, {})
linecache.updatecache('/foo.py', globals())
self.assertEqual(exc.stack[0].line, "import sys")
+ # TODO: RUSTPYTHON
+ @unittest.expectedFailure
def test_locals(self):
linecache.updatecache('/foo.py', globals())
e = Exception("uh oh")
c = test_code('/foo.py', 'method')
- f = test_frame(c, globals(), {'something': 1, 'other': 'string'})
- tb = test_tb(f, 6, None)
+ f = test_frame(c, globals(), {'something': 1, 'other': 'string', 'unrepresentable': Unrepresentable()})
+ tb = test_tb(f, 6, None, 0)
exc = traceback.TracebackException(
Exception, e, tb, capture_locals=True)
self.assertEqual(
- exc.stack[0].locals, {'something': '1', 'other': "'string'"})
+ exc.stack[0].locals,
+ {'something': '1', 'other': "'string'", 'unrepresentable': ''})
def test_no_locals(self):
linecache.updatecache('/foo.py', globals())
e = Exception("uh oh")
c = test_code('/foo.py', 'method')
f = test_frame(c, globals(), {'something': 1})
- tb = test_tb(f, 6, None)
+ tb = test_tb(f, 6, None, 0)
exc = traceback.TracebackException(Exception, e, tb)
self.assertEqual(exc.stack[0].locals, None)
@@ -1469,6 +3941,949 @@ def test_traceback_header(self):
exc = traceback.TracebackException(Exception, Exception("haven"), None)
self.assertEqual(list(exc.format()), ["Exception: haven\n"])
+ # @requires_debug_ranges() # XXX: RUSTPYTHON patch
+ # TODO: RUSTPYTHON
+ @unittest.expectedFailure
+ def test_print(self):
+ def f():
+ x = 12
+ try:
+ x/0
+ except Exception as e:
+ return e
+ exc = traceback.TracebackException.from_exception(f(), capture_locals=True)
+ output = StringIO()
+ exc.print(file=output)
+ self.assertEqual(
+ output.getvalue().split('\n')[-5:],
+ [' x/0',
+ ' ~^~',
+ ' x = 12',
+ 'ZeroDivisionError: division by zero',
+ ''])
+
+ # TODO: RUSTPYTHON
+ @unittest.expectedFailure
+ def test_dont_swallow_cause_or_context_of_falsey_exception(self):
+ # see gh-132308: Ensure that __cause__ or __context__ attributes of exceptions
+ # that evaluate as falsey are included in the output. For falsey term,
+ # see https://docs.python.org/3/library/stdtypes.html#truth-value-testing.
+
+ try:
+ raise FalseyException from KeyError
+ except FalseyException as e:
+ self.assertIn(cause_message, traceback.format_exception(e))
+
+ try:
+ try:
+ 1/0
+ except ZeroDivisionError:
+ raise FalseyException
+ except FalseyException as e:
+ self.assertIn(context_message, traceback.format_exception(e))
+
+
+class TestTracebackException_ExceptionGroups(unittest.TestCase):
+ def setUp(self):
+ super().setUp()
+ self.eg = self._get_exception_group()
+
+ def _get_exception_group(self):
+ def f():
+ 1/0
+
+ def g(v):
+ raise ValueError(v)
+
+ self.lno_f = f.__code__.co_firstlineno
+ self.lno_g = g.__code__.co_firstlineno
+
+ try:
+ try:
+ try:
+ f()
+ except Exception as e:
+ exc1 = e
+ try:
+ g(42)
+ except Exception as e:
+ exc2 = e
+ raise ExceptionGroup("eg1", [exc1, exc2])
+ except ExceptionGroup as e:
+ exc3 = e
+ try:
+ g(24)
+ except Exception as e:
+ exc4 = e
+ raise ExceptionGroup("eg2", [exc3, exc4])
+ except ExceptionGroup as eg:
+ return eg
+ self.fail('Exception Not Raised')
+
+ def test_exception_group_construction(self):
+ eg = self.eg
+ teg1 = traceback.TracebackException(type(eg), eg, eg.__traceback__)
+ teg2 = traceback.TracebackException.from_exception(eg)
+ self.assertIsNot(teg1, teg2)
+ self.assertEqual(teg1, teg2)
+
+ def test_exception_group_format_exception_only(self):
+ teg = traceback.TracebackException.from_exception(self.eg)
+ formatted = ''.join(teg.format_exception_only()).split('\n')
+ expected = "ExceptionGroup: eg2 (2 sub-exceptions)\n".split('\n')
+
+ self.assertEqual(formatted, expected)
+
+ # TODO: RUSTPYTHON
+ @unittest.expectedFailure
+ def test_exception_group_format_exception_onlyi_recursive(self):
+ teg = traceback.TracebackException.from_exception(self.eg)
+ formatted = ''.join(teg.format_exception_only(show_group=True)).split('\n')
+ expected = [
+ 'ExceptionGroup: eg2 (2 sub-exceptions)',
+ ' ExceptionGroup: eg1 (2 sub-exceptions)',
+ ' ZeroDivisionError: division by zero',
+ ' ValueError: 42',
+ ' ValueError: 24',
+ ''
+ ]
+
+ self.assertEqual(formatted, expected)
+
+ # TODO: RUSTPYTHON
+ @unittest.expectedFailure
+ def test_exception_group_format(self):
+ teg = traceback.TracebackException.from_exception(self.eg)
+
+ formatted = ''.join(teg.format()).split('\n')
+ lno_f = self.lno_f
+ lno_g = self.lno_g
+
+ expected = [
+ f' + Exception Group Traceback (most recent call last):',
+ f' | File "{__file__}", line {lno_g+23}, in _get_exception_group',
+ f' | raise ExceptionGroup("eg2", [exc3, exc4])',
+ f' | ExceptionGroup: eg2 (2 sub-exceptions)',
+ f' +-+---------------- 1 ----------------',
+ f' | Exception Group Traceback (most recent call last):',
+ f' | File "{__file__}", line {lno_g+16}, in _get_exception_group',
+ f' | raise ExceptionGroup("eg1", [exc1, exc2])',
+ f' | ExceptionGroup: eg1 (2 sub-exceptions)',
+ f' +-+---------------- 1 ----------------',
+ f' | Traceback (most recent call last):',
+ f' | File "{__file__}", line {lno_g+9}, in _get_exception_group',
+ f' | f()',
+ f' | ~^^',
+ f' | File "{__file__}", line {lno_f+1}, in f',
+ f' | 1/0',
+ f' | ~^~',
+ f' | ZeroDivisionError: division by zero',
+ f' +---------------- 2 ----------------',
+ f' | Traceback (most recent call last):',
+ f' | File "{__file__}", line {lno_g+13}, in _get_exception_group',
+ f' | g(42)',
+ f' | ~^^^^',
+ f' | File "{__file__}", line {lno_g+1}, in g',
+ f' | raise ValueError(v)',
+ f' | ValueError: 42',
+ f' +------------------------------------',
+ f' +---------------- 2 ----------------',
+ f' | Traceback (most recent call last):',
+ f' | File "{__file__}", line {lno_g+20}, in _get_exception_group',
+ f' | g(24)',
+ f' | ~^^^^',
+ f' | File "{__file__}", line {lno_g+1}, in g',
+ f' | raise ValueError(v)',
+ f' | ValueError: 24',
+ f' +------------------------------------',
+ f'']
+
+ self.assertEqual(formatted, expected)
+
+ # TODO: RUSTPYTHON
+ @unittest.expectedFailure
+ def test_max_group_width(self):
+ excs1 = []
+ excs2 = []
+ for i in range(3):
+ excs1.append(ValueError(i))
+ for i in range(10):
+ excs2.append(TypeError(i))
+
+ EG = ExceptionGroup
+ eg = EG('eg', [EG('eg1', excs1), EG('eg2', excs2)])
+
+ teg = traceback.TracebackException.from_exception(eg, max_group_width=2)
+ formatted = ''.join(teg.format()).split('\n')
+
+ expected = [
+ ' | ExceptionGroup: eg (2 sub-exceptions)',
+ ' +-+---------------- 1 ----------------',
+ ' | ExceptionGroup: eg1 (3 sub-exceptions)',
+ ' +-+---------------- 1 ----------------',
+ ' | ValueError: 0',
+ ' +---------------- 2 ----------------',
+ ' | ValueError: 1',
+ ' +---------------- ... ----------------',
+ ' | and 1 more exception',
+ ' +------------------------------------',
+ ' +---------------- 2 ----------------',
+ ' | ExceptionGroup: eg2 (10 sub-exceptions)',
+ ' +-+---------------- 1 ----------------',
+ ' | TypeError: 0',
+ ' +---------------- 2 ----------------',
+ ' | TypeError: 1',
+ ' +---------------- ... ----------------',
+ ' | and 8 more exceptions',
+ ' +------------------------------------',
+ '']
+
+ self.assertEqual(formatted, expected)
+
+ # TODO: RUSTPYTHON
+ @unittest.expectedFailure
+ def test_max_group_depth(self):
+ exc = TypeError('bad type')
+ for i in range(3):
+ exc = ExceptionGroup('exc', [ValueError(-i), exc, ValueError(i)])
+
+ teg = traceback.TracebackException.from_exception(exc, max_group_depth=2)
+ formatted = ''.join(teg.format()).split('\n')
+
+ expected = [
+ ' | ExceptionGroup: exc (3 sub-exceptions)',
+ ' +-+---------------- 1 ----------------',
+ ' | ValueError: -2',
+ ' +---------------- 2 ----------------',
+ ' | ExceptionGroup: exc (3 sub-exceptions)',
+ ' +-+---------------- 1 ----------------',
+ ' | ValueError: -1',
+ ' +---------------- 2 ----------------',
+ ' | ... (max_group_depth is 2)',
+ ' +---------------- 3 ----------------',
+ ' | ValueError: 1',
+ ' +------------------------------------',
+ ' +---------------- 3 ----------------',
+ ' | ValueError: 2',
+ ' +------------------------------------',
+ '']
+
+ self.assertEqual(formatted, expected)
+
+ def test_comparison(self):
+ try:
+ raise self.eg
+ except ExceptionGroup as e:
+ exc = e
+ for _ in range(5):
+ try:
+ raise exc
+ except Exception as e:
+ exc_obj = e
+ exc = traceback.TracebackException.from_exception(exc_obj)
+ exc2 = traceback.TracebackException.from_exception(exc_obj)
+ exc3 = traceback.TracebackException.from_exception(exc_obj, limit=300)
+ ne = traceback.TracebackException.from_exception(exc_obj, limit=3)
+ self.assertIsNot(exc, exc2)
+ self.assertEqual(exc, exc2)
+ self.assertEqual(exc, exc3)
+ self.assertNotEqual(exc, ne)
+ self.assertNotEqual(exc, object())
+ self.assertEqual(exc, ALWAYS_EQ)
+
+ # TODO: RUSTPYTHON
+ @unittest.expectedFailure
+ def test_dont_swallow_subexceptions_of_falsey_exceptiongroup(self):
+ # see gh-132308: Ensure that subexceptions of exception groups
+ # that evaluate as falsey are displayed in the output. For falsey term,
+ # see https://docs.python.org/3/library/stdtypes.html#truth-value-testing.
+
+ try:
+ raise FalseyExceptionGroup("Gih", (KeyError(), NameError()))
+ except Exception as ee:
+ str_exc = ''.join(traceback.format_exception(ee))
+ self.assertIn('+---------------- 1 ----------------', str_exc)
+ self.assertIn('+---------------- 2 ----------------', str_exc)
+
+ # Test with a falsey exception, in last position, as sub-exceptions.
+ msg = 'bool'
+ try:
+ raise FalseyExceptionGroup("Gah", (KeyError(), FalseyException(msg)))
+ except Exception as ee:
+ str_exc = traceback.format_exception(ee)
+ self.assertIn(f'{FalseyException.__name__}: {msg}', str_exc[-2])
+
+
+global_for_suggestions = None
+
+
+class SuggestionFormattingTestBase:
+ def get_suggestion(self, obj, attr_name=None):
+ if attr_name is not None:
+ def callable():
+ getattr(obj, attr_name)
+ else:
+ callable = obj
+
+ result_lines = self.get_exception(
+ callable, slice_start=-1, slice_end=None
+ )
+ return result_lines[0]
+
+ # TODO: RUSTPYTHON
+ @unittest.expectedFailure
+ def test_getattr_suggestions(self):
+ class Substitution:
+ noise = more_noise = a = bc = None
+ blech = None
+
+ class Elimination:
+ noise = more_noise = a = bc = None
+ blch = None
+
+ class Addition:
+ noise = more_noise = a = bc = None
+ bluchin = None
+
+ class SubstitutionOverElimination:
+ blach = None
+ bluc = None
+
+ class SubstitutionOverAddition:
+ blach = None
+ bluchi = None
+
+ class EliminationOverAddition:
+ blucha = None
+ bluc = None
+
+ class CaseChangeOverSubstitution:
+ Luch = None
+ fluch = None
+ BLuch = None
+
+ for cls, suggestion in [
+ (Addition, "'bluchin'?"),
+ (Substitution, "'blech'?"),
+ (Elimination, "'blch'?"),
+ (Addition, "'bluchin'?"),
+ (SubstitutionOverElimination, "'blach'?"),
+ (SubstitutionOverAddition, "'blach'?"),
+ (EliminationOverAddition, "'bluc'?"),
+ (CaseChangeOverSubstitution, "'BLuch'?"),
+ ]:
+ actual = self.get_suggestion(cls(), 'bluch')
+ self.assertIn(suggestion, actual)
+
+ # TODO: RUSTPYTHON
+ @unittest.expectedFailure
+ def test_getattr_suggestions_underscored(self):
+ class A:
+ bluch = None
+
+ self.assertIn("'bluch'", self.get_suggestion(A(), 'blach'))
+ self.assertIn("'bluch'", self.get_suggestion(A(), '_luch'))
+ self.assertIn("'bluch'", self.get_suggestion(A(), '_bluch'))
+
+ class B:
+ _bluch = None
+ def method(self, name):
+ getattr(self, name)
+
+ self.assertIn("'_bluch'", self.get_suggestion(B(), '_blach'))
+ self.assertIn("'_bluch'", self.get_suggestion(B(), '_luch'))
+ self.assertNotIn("'_bluch'", self.get_suggestion(B(), 'bluch'))
+
+ self.assertIn("'_bluch'", self.get_suggestion(partial(B().method, '_blach')))
+ self.assertIn("'_bluch'", self.get_suggestion(partial(B().method, '_luch')))
+ self.assertIn("'_bluch'", self.get_suggestion(partial(B().method, 'bluch')))
+
+ def test_getattr_suggestions_do_not_trigger_for_long_attributes(self):
+ class A:
+ blech = None
+
+ actual = self.get_suggestion(A(), 'somethingverywrong')
+ self.assertNotIn("blech", actual)
+
+ def test_getattr_error_bad_suggestions_do_not_trigger_for_small_names(self):
+ class MyClass:
+ vvv = mom = w = id = pytho = None
+
+ for name in ("b", "v", "m", "py"):
+ with self.subTest(name=name):
+ actual = self.get_suggestion(MyClass, name)
+ self.assertNotIn("Did you mean", actual)
+ self.assertNotIn("'vvv", actual)
+ self.assertNotIn("'mom'", actual)
+ self.assertNotIn("'id'", actual)
+ self.assertNotIn("'w'", actual)
+ self.assertNotIn("'pytho'", actual)
+
+ def test_getattr_suggestions_do_not_trigger_for_big_dicts(self):
+ class A:
+ blech = None
+ # A class with a very big __dict__ will not be considered
+ # for suggestions.
+ for index in range(2000):
+ setattr(A, f"index_{index}", None)
+
+ actual = self.get_suggestion(A(), 'bluch')
+ self.assertNotIn("blech", actual)
+
+ # TODO: RUSTPYTHON
+ @unittest.expectedFailure
+ def test_getattr_suggestions_no_args(self):
+ class A:
+ blech = None
+ def __getattr__(self, attr):
+ raise AttributeError()
+
+ actual = self.get_suggestion(A(), 'bluch')
+ self.assertIn("blech", actual)
+
+ class A:
+ blech = None
+ def __getattr__(self, attr):
+ raise AttributeError
+
+ actual = self.get_suggestion(A(), 'bluch')
+ self.assertIn("blech", actual)
+
+ # TODO: RUSTPYTHON
+ @unittest.expectedFailure
+ def test_getattr_suggestions_invalid_args(self):
+ class NonStringifyClass:
+ __str__ = None
+ __repr__ = None
+
+ class A:
+ blech = None
+ def __getattr__(self, attr):
+ raise AttributeError(NonStringifyClass())
+
+ class B:
+ blech = None
+ def __getattr__(self, attr):
+ raise AttributeError("Error", 23)
+
+ class C:
+ blech = None
+ def __getattr__(self, attr):
+ raise AttributeError(23)
+
+ for cls in [A, B, C]:
+ actual = self.get_suggestion(cls(), 'bluch')
+ self.assertIn("blech", actual)
+
+ def test_getattr_suggestions_for_same_name(self):
+ class A:
+ def __dir__(self):
+ return ['blech']
+ actual = self.get_suggestion(A(), 'blech')
+ self.assertNotIn("Did you mean", actual)
+
+ def test_attribute_error_with_failing_dict(self):
+ class T:
+ bluch = 1
+ def __dir__(self):
+ raise AttributeError("oh no!")
+
+ actual = self.get_suggestion(T(), 'blich')
+ self.assertNotIn("blech", actual)
+ self.assertNotIn("oh no!", actual)
+
+ # TODO: RUSTPYTHON
+ @unittest.expectedFailure
+ def test_attribute_error_with_non_string_candidates(self):
+ class T:
+ bluch = 1
+
+ instance = T()
+ instance.__dict__[0] = 1
+ actual = self.get_suggestion(instance, 'blich')
+ self.assertIn("bluch", actual)
+
+ def test_attribute_error_with_bad_name(self):
+ def raise_attribute_error_with_bad_name():
+ raise AttributeError(name=12, obj=23)
+
+ result_lines = self.get_exception(
+ raise_attribute_error_with_bad_name, slice_start=-1, slice_end=None
+ )
+ self.assertNotIn("?", result_lines[-1])
+
+ # TODO: RUSTPYTHON
+ @unittest.expectedFailure
+ def test_attribute_error_inside_nested_getattr(self):
+ class A:
+ bluch = 1
+
+ class B:
+ def __getattribute__(self, attr):
+ a = A()
+ return a.blich
+
+ actual = self.get_suggestion(B(), 'something')
+ self.assertIn("Did you mean", actual)
+ self.assertIn("bluch", actual)
+
+ def make_module(self, code):
+ tmpdir = Path(tempfile.mkdtemp())
+ self.addCleanup(shutil.rmtree, tmpdir)
+
+ sys.path.append(str(tmpdir))
+ self.addCleanup(sys.path.pop)
+
+ mod_name = ''.join(random.choices(string.ascii_letters, k=16))
+ module = tmpdir / (mod_name + ".py")
+ module.write_text(code)
+
+ return mod_name
+
+ def get_import_from_suggestion(self, code, name):
+ modname = self.make_module(code)
+
+ def callable():
+ try:
+ exec(f"from {modname} import {name}")
+ except ImportError as e:
+ raise e from None
+ except Exception as e:
+ self.fail(f"Expected ImportError but got {type(e)}")
+ self.addCleanup(forget, modname)
+
+ result_lines = self.get_exception(
+ callable, slice_start=-1, slice_end=None
+ )
+ return result_lines[0]
+
+ # TODO: RUSTPYTHON
+ @unittest.expectedFailure
+ def test_import_from_suggestions(self):
+ substitution = textwrap.dedent("""\
+ noise = more_noise = a = bc = None
+ blech = None
+ """)
+
+ elimination = textwrap.dedent("""
+ noise = more_noise = a = bc = None
+ blch = None
+ """)
+
+ addition = textwrap.dedent("""
+ noise = more_noise = a = bc = None
+ bluchin = None
+ """)
+
+ substitutionOverElimination = textwrap.dedent("""
+ blach = None
+ bluc = None
+ """)
+
+ substitutionOverAddition = textwrap.dedent("""
+ blach = None
+ bluchi = None
+ """)
+
+ eliminationOverAddition = textwrap.dedent("""
+ blucha = None
+ bluc = None
+ """)
+
+ caseChangeOverSubstitution = textwrap.dedent("""
+ Luch = None
+ fluch = None
+ BLuch = None
+ """)
+
+ for code, suggestion in [
+ (addition, "'bluchin'?"),
+ (substitution, "'blech'?"),
+ (elimination, "'blch'?"),
+ (addition, "'bluchin'?"),
+ (substitutionOverElimination, "'blach'?"),
+ (substitutionOverAddition, "'blach'?"),
+ (eliminationOverAddition, "'bluc'?"),
+ (caseChangeOverSubstitution, "'BLuch'?"),
+ ]:
+ actual = self.get_import_from_suggestion(code, 'bluch')
+ self.assertIn(suggestion, actual)
+
+ # TODO: RUSTPYTHON
+ @unittest.expectedFailure
+ def test_import_from_suggestions_underscored(self):
+ code = "bluch = None"
+ self.assertIn("'bluch'", self.get_import_from_suggestion(code, 'blach'))
+ self.assertIn("'bluch'", self.get_import_from_suggestion(code, '_luch'))
+ self.assertIn("'bluch'", self.get_import_from_suggestion(code, '_bluch'))
+
+ code = "_bluch = None"
+ self.assertIn("'_bluch'", self.get_import_from_suggestion(code, '_blach'))
+ self.assertIn("'_bluch'", self.get_import_from_suggestion(code, '_luch'))
+ self.assertNotIn("'_bluch'", self.get_import_from_suggestion(code, 'bluch'))
+
+ # TODO: RUSTPYTHON
+ @unittest.expectedFailure
+ def test_import_from_suggestions_non_string(self):
+ modWithNonStringAttr = textwrap.dedent("""\
+ globals()[0] = 1
+ bluch = 1
+ """)
+ self.assertIn("'bluch'", self.get_import_from_suggestion(modWithNonStringAttr, 'blech'))
+
+ def test_import_from_suggestions_do_not_trigger_for_long_attributes(self):
+ code = "blech = None"
+
+ actual = self.get_suggestion(code, 'somethingverywrong')
+ self.assertNotIn("blech", actual)
+
+ def test_import_from_error_bad_suggestions_do_not_trigger_for_small_names(self):
+ code = "vvv = mom = w = id = pytho = None"
+
+ for name in ("b", "v", "m", "py"):
+ with self.subTest(name=name):
+ actual = self.get_import_from_suggestion(code, name)
+ self.assertNotIn("Did you mean", actual)
+ self.assertNotIn("'vvv'", actual)
+ self.assertNotIn("'mom'", actual)
+ self.assertNotIn("'id'", actual)
+ self.assertNotIn("'w'", actual)
+ self.assertNotIn("'pytho'", actual)
+
+ def test_import_from_suggestions_do_not_trigger_for_big_namespaces(self):
+ # A module with lots of names will not be considered for suggestions.
+ chunks = [f"index_{index} = " for index in range(200)]
+ chunks.append(" None")
+ code = " ".join(chunks)
+ actual = self.get_import_from_suggestion(code, 'bluch')
+ self.assertNotIn("blech", actual)
+
+ def test_import_from_error_with_bad_name(self):
+ def raise_attribute_error_with_bad_name():
+ raise ImportError(name=12, obj=23, name_from=11)
+
+ result_lines = self.get_exception(
+ raise_attribute_error_with_bad_name, slice_start=-1, slice_end=None
+ )
+ self.assertNotIn("?", result_lines[-1])
+
+ # TODO: RUSTPYTHON
+ @unittest.expectedFailure
+ def test_name_error_suggestions(self):
+ def Substitution():
+ noise = more_noise = a = bc = None
+ blech = None
+ print(bluch)
+
+ def Elimination():
+ noise = more_noise = a = bc = None
+ blch = None
+ print(bluch)
+
+ def Addition():
+ noise = more_noise = a = bc = None
+ bluchin = None
+ print(bluch)
+
+ def SubstitutionOverElimination():
+ blach = None
+ bluc = None
+ print(bluch)
+
+ def SubstitutionOverAddition():
+ blach = None
+ bluchi = None
+ print(bluch)
+
+ def EliminationOverAddition():
+ blucha = None
+ bluc = None
+ print(bluch)
+
+ for func, suggestion in [(Substitution, "'blech'?"),
+ (Elimination, "'blch'?"),
+ (Addition, "'bluchin'?"),
+ (EliminationOverAddition, "'blucha'?"),
+ (SubstitutionOverElimination, "'blach'?"),
+ (SubstitutionOverAddition, "'blach'?")]:
+ actual = self.get_suggestion(func)
+ self.assertIn(suggestion, actual)
+
+ # TODO: RUSTPYTHON
+ @unittest.expectedFailure
+ def test_name_error_suggestions_from_globals(self):
+ def func():
+ print(global_for_suggestio)
+ actual = self.get_suggestion(func)
+ self.assertIn("'global_for_suggestions'?", actual)
+
+ # TODO: RUSTPYTHON
+ @unittest.expectedFailure
+ def test_name_error_suggestions_from_builtins(self):
+ def func():
+ print(ZeroDivisionErrrrr)
+ actual = self.get_suggestion(func)
+ self.assertIn("'ZeroDivisionError'?", actual)
+
+ # TODO: RUSTPYTHON
+ @unittest.expectedFailure
+ def test_name_error_suggestions_from_builtins_when_builtins_is_module(self):
+ def func():
+ custom_globals = globals().copy()
+ custom_globals["__builtins__"] = builtins
+ print(eval("ZeroDivisionErrrrr", custom_globals))
+ actual = self.get_suggestion(func)
+ self.assertIn("'ZeroDivisionError'?", actual)
+
+ # TODO: RUSTPYTHON
+ @unittest.expectedFailure
+ def test_name_error_suggestions_with_non_string_candidates(self):
+ def func():
+ abc = 1
+ custom_globals = globals().copy()
+ custom_globals[0] = 1
+ print(eval("abv", custom_globals, locals()))
+ actual = self.get_suggestion(func)
+ self.assertIn("abc", actual)
+
+ def test_name_error_suggestions_do_not_trigger_for_long_names(self):
+ def func():
+ somethingverywronghehehehehehe = None
+ print(somethingverywronghe)
+ actual = self.get_suggestion(func)
+ self.assertNotIn("somethingverywronghehe", actual)
+
+ def test_name_error_bad_suggestions_do_not_trigger_for_small_names(self):
+
+ def f_b():
+ vvv = mom = w = id = pytho = None
+ b
+
+ def f_v():
+ vvv = mom = w = id = pytho = None
+ v
+
+ def f_m():
+ vvv = mom = w = id = pytho = None
+ m
+
+ def f_py():
+ vvv = mom = w = id = pytho = None
+ py
+
+ for name, func in (("b", f_b), ("v", f_v), ("m", f_m), ("py", f_py)):
+ with self.subTest(name=name):
+ actual = self.get_suggestion(func)
+ self.assertNotIn("you mean", actual)
+ self.assertNotIn("vvv", actual)
+ self.assertNotIn("mom", actual)
+ self.assertNotIn("'id'", actual)
+ self.assertNotIn("'w'", actual)
+ self.assertNotIn("'pytho'", actual)
+
+ def test_name_error_suggestions_do_not_trigger_for_too_many_locals(self):
+ def func():
+ # Mutating locals() is unreliable, so we need to do it by hand
+ a1 = a2 = a3 = a4 = a5 = a6 = a7 = a8 = a9 = a10 = \
+ a11 = a12 = a13 = a14 = a15 = a16 = a17 = a18 = a19 = a20 = \
+ a21 = a22 = a23 = a24 = a25 = a26 = a27 = a28 = a29 = a30 = \
+ a31 = a32 = a33 = a34 = a35 = a36 = a37 = a38 = a39 = a40 = \
+ a41 = a42 = a43 = a44 = a45 = a46 = a47 = a48 = a49 = a50 = \
+ a51 = a52 = a53 = a54 = a55 = a56 = a57 = a58 = a59 = a60 = \
+ a61 = a62 = a63 = a64 = a65 = a66 = a67 = a68 = a69 = a70 = \
+ a71 = a72 = a73 = a74 = a75 = a76 = a77 = a78 = a79 = a80 = \
+ a81 = a82 = a83 = a84 = a85 = a86 = a87 = a88 = a89 = a90 = \
+ a91 = a92 = a93 = a94 = a95 = a96 = a97 = a98 = a99 = a100 = \
+ a101 = a102 = a103 = a104 = a105 = a106 = a107 = a108 = a109 = a110 = \
+ a111 = a112 = a113 = a114 = a115 = a116 = a117 = a118 = a119 = a120 = \
+ a121 = a122 = a123 = a124 = a125 = a126 = a127 = a128 = a129 = a130 = \
+ a131 = a132 = a133 = a134 = a135 = a136 = a137 = a138 = a139 = a140 = \
+ a141 = a142 = a143 = a144 = a145 = a146 = a147 = a148 = a149 = a150 = \
+ a151 = a152 = a153 = a154 = a155 = a156 = a157 = a158 = a159 = a160 = \
+ a161 = a162 = a163 = a164 = a165 = a166 = a167 = a168 = a169 = a170 = \
+ a171 = a172 = a173 = a174 = a175 = a176 = a177 = a178 = a179 = a180 = \
+ a181 = a182 = a183 = a184 = a185 = a186 = a187 = a188 = a189 = a190 = \
+ a191 = a192 = a193 = a194 = a195 = a196 = a197 = a198 = a199 = a200 = \
+ a201 = a202 = a203 = a204 = a205 = a206 = a207 = a208 = a209 = a210 = \
+ a211 = a212 = a213 = a214 = a215 = a216 = a217 = a218 = a219 = a220 = \
+ a221 = a222 = a223 = a224 = a225 = a226 = a227 = a228 = a229 = a230 = \
+ a231 = a232 = a233 = a234 = a235 = a236 = a237 = a238 = a239 = a240 = \
+ a241 = a242 = a243 = a244 = a245 = a246 = a247 = a248 = a249 = a250 = \
+ a251 = a252 = a253 = a254 = a255 = a256 = a257 = a258 = a259 = a260 = \
+ a261 = a262 = a263 = a264 = a265 = a266 = a267 = a268 = a269 = a270 = \
+ a271 = a272 = a273 = a274 = a275 = a276 = a277 = a278 = a279 = a280 = \
+ a281 = a282 = a283 = a284 = a285 = a286 = a287 = a288 = a289 = a290 = \
+ a291 = a292 = a293 = a294 = a295 = a296 = a297 = a298 = a299 = a300 = \
+ a301 = a302 = a303 = a304 = a305 = a306 = a307 = a308 = a309 = a310 = \
+ a311 = a312 = a313 = a314 = a315 = a316 = a317 = a318 = a319 = a320 = \
+ a321 = a322 = a323 = a324 = a325 = a326 = a327 = a328 = a329 = a330 = \
+ a331 = a332 = a333 = a334 = a335 = a336 = a337 = a338 = a339 = a340 = \
+ a341 = a342 = a343 = a344 = a345 = a346 = a347 = a348 = a349 = a350 = \
+ a351 = a352 = a353 = a354 = a355 = a356 = a357 = a358 = a359 = a360 = \
+ a361 = a362 = a363 = a364 = a365 = a366 = a367 = a368 = a369 = a370 = \
+ a371 = a372 = a373 = a374 = a375 = a376 = a377 = a378 = a379 = a380 = \
+ a381 = a382 = a383 = a384 = a385 = a386 = a387 = a388 = a389 = a390 = \
+ a391 = a392 = a393 = a394 = a395 = a396 = a397 = a398 = a399 = a400 = \
+ a401 = a402 = a403 = a404 = a405 = a406 = a407 = a408 = a409 = a410 = \
+ a411 = a412 = a413 = a414 = a415 = a416 = a417 = a418 = a419 = a420 = \
+ a421 = a422 = a423 = a424 = a425 = a426 = a427 = a428 = a429 = a430 = \
+ a431 = a432 = a433 = a434 = a435 = a436 = a437 = a438 = a439 = a440 = \
+ a441 = a442 = a443 = a444 = a445 = a446 = a447 = a448 = a449 = a450 = \
+ a451 = a452 = a453 = a454 = a455 = a456 = a457 = a458 = a459 = a460 = \
+ a461 = a462 = a463 = a464 = a465 = a466 = a467 = a468 = a469 = a470 = \
+ a471 = a472 = a473 = a474 = a475 = a476 = a477 = a478 = a479 = a480 = \
+ a481 = a482 = a483 = a484 = a485 = a486 = a487 = a488 = a489 = a490 = \
+ a491 = a492 = a493 = a494 = a495 = a496 = a497 = a498 = a499 = a500 = \
+ a501 = a502 = a503 = a504 = a505 = a506 = a507 = a508 = a509 = a510 = \
+ a511 = a512 = a513 = a514 = a515 = a516 = a517 = a518 = a519 = a520 = \
+ a521 = a522 = a523 = a524 = a525 = a526 = a527 = a528 = a529 = a530 = \
+ a531 = a532 = a533 = a534 = a535 = a536 = a537 = a538 = a539 = a540 = \
+ a541 = a542 = a543 = a544 = a545 = a546 = a547 = a548 = a549 = a550 = \
+ a551 = a552 = a553 = a554 = a555 = a556 = a557 = a558 = a559 = a560 = \
+ a561 = a562 = a563 = a564 = a565 = a566 = a567 = a568 = a569 = a570 = \
+ a571 = a572 = a573 = a574 = a575 = a576 = a577 = a578 = a579 = a580 = \
+ a581 = a582 = a583 = a584 = a585 = a586 = a587 = a588 = a589 = a590 = \
+ a591 = a592 = a593 = a594 = a595 = a596 = a597 = a598 = a599 = a600 = \
+ a601 = a602 = a603 = a604 = a605 = a606 = a607 = a608 = a609 = a610 = \
+ a611 = a612 = a613 = a614 = a615 = a616 = a617 = a618 = a619 = a620 = \
+ a621 = a622 = a623 = a624 = a625 = a626 = a627 = a628 = a629 = a630 = \
+ a631 = a632 = a633 = a634 = a635 = a636 = a637 = a638 = a639 = a640 = \
+ a641 = a642 = a643 = a644 = a645 = a646 = a647 = a648 = a649 = a650 = \
+ a651 = a652 = a653 = a654 = a655 = a656 = a657 = a658 = a659 = a660 = \
+ a661 = a662 = a663 = a664 = a665 = a666 = a667 = a668 = a669 = a670 = \
+ a671 = a672 = a673 = a674 = a675 = a676 = a677 = a678 = a679 = a680 = \
+ a681 = a682 = a683 = a684 = a685 = a686 = a687 = a688 = a689 = a690 = \
+ a691 = a692 = a693 = a694 = a695 = a696 = a697 = a698 = a699 = a700 = \
+ a701 = a702 = a703 = a704 = a705 = a706 = a707 = a708 = a709 = a710 = \
+ a711 = a712 = a713 = a714 = a715 = a716 = a717 = a718 = a719 = a720 = \
+ a721 = a722 = a723 = a724 = a725 = a726 = a727 = a728 = a729 = a730 = \
+ a731 = a732 = a733 = a734 = a735 = a736 = a737 = a738 = a739 = a740 = \
+ a741 = a742 = a743 = a744 = a745 = a746 = a747 = a748 = a749 = a750 = \
+ a751 = a752 = a753 = a754 = a755 = a756 = a757 = a758 = a759 = a760 = \
+ a761 = a762 = a763 = a764 = a765 = a766 = a767 = a768 = a769 = a770 = \
+ a771 = a772 = a773 = a774 = a775 = a776 = a777 = a778 = a779 = a780 = \
+ a781 = a782 = a783 = a784 = a785 = a786 = a787 = a788 = a789 = a790 = \
+ a791 = a792 = a793 = a794 = a795 = a796 = a797 = a798 = a799 = a800 \
+ = None
+ print(a0)
+
+ actual = self.get_suggestion(func)
+ self.assertNotRegex(actual, r"NameError.*a1")
+
+ def test_name_error_with_custom_exceptions(self):
+ def func():
+ blech = None
+ raise NameError()
+
+ actual = self.get_suggestion(func)
+ self.assertNotIn("blech", actual)
+
+ def func():
+ blech = None
+ raise NameError
+
+ actual = self.get_suggestion(func)
+ self.assertNotIn("blech", actual)
+
+ # TODO: RUSTPYTHON
+ @unittest.expectedFailure
+ def test_name_error_with_instance(self):
+ class A:
+ def __init__(self):
+ self.blech = None
+ def foo(self):
+ blich = 1
+ x = blech
+
+ instance = A()
+ actual = self.get_suggestion(instance.foo)
+ self.assertIn("self.blech", actual)
+
+ def test_unbound_local_error_with_instance(self):
+ class A:
+ def __init__(self):
+ self.blech = None
+ def foo(self):
+ blich = 1
+ x = blech
+ blech = 1
+
+ instance = A()
+ actual = self.get_suggestion(instance.foo)
+ self.assertNotIn("self.blech", actual)
+
+ def test_unbound_local_error_with_side_effect(self):
+ # gh-132385
+ class A:
+ def __getattr__(self, key):
+ if key == 'foo':
+ raise AttributeError('foo')
+ if key == 'spam':
+ raise ValueError('spam')
+
+ def bar(self):
+ foo
+ def baz(self):
+ spam
+
+ suggestion = self.get_suggestion(A().bar)
+ self.assertNotIn('self.', suggestion)
+ self.assertIn("'foo'", suggestion)
+
+ suggestion = self.get_suggestion(A().baz)
+ self.assertNotIn('self.', suggestion)
+ self.assertIn("'spam'", suggestion)
+
+ def test_unbound_local_error_does_not_match(self):
+ def func():
+ something = 3
+ print(somethong)
+ somethong = 3
+
+ actual = self.get_suggestion(func)
+ self.assertNotIn("something", actual)
+
+ # TODO: RUSTPYTHON
+ @unittest.expectedFailure
+ def test_name_error_for_stdlib_modules(self):
+ def func():
+ stream = io.StringIO()
+
+ actual = self.get_suggestion(func)
+ self.assertIn("forget to import 'io'", actual)
+
+ # TODO: RUSTPYTHON
+ @unittest.expectedFailure
+ def test_name_error_for_private_stdlib_modules(self):
+ def func():
+ stream = _io.StringIO()
+
+ actual = self.get_suggestion(func)
+ self.assertIn("forget to import '_io'", actual)
+
+
+
+class PurePythonSuggestionFormattingTests(
+ PurePythonExceptionFormattingMixin,
+ SuggestionFormattingTestBase,
+ unittest.TestCase,
+):
+ """
+ Same set of tests as above using the pure Python implementation of
+ traceback printing in traceback.py.
+ """
+
+
+@cpython_only
+class CPythonSuggestionFormattingTests(
+ CAPIExceptionFormattingMixin,
+ SuggestionFormattingTestBase,
+ unittest.TestCase,
+):
+ """
+ Same set of tests as above but with Python's internal traceback printing.
+ """
+
class MiscTest(unittest.TestCase):
@@ -1483,6 +4898,231 @@ def test_all(self):
expected.add(name)
self.assertCountEqual(traceback.__all__, expected)
+ # TODO: RUSTPYTHON
+ @unittest.expectedFailure
+ def test_levenshtein_distance(self):
+ # copied from _testinternalcapi.test_edit_cost
+ # to also exercise the Python implementation
+
+ def CHECK(a, b, expected):
+ actual = traceback._levenshtein_distance(a, b, 4044)
+ self.assertEqual(actual, expected)
+
+ CHECK("", "", 0)
+ CHECK("", "a", 2)
+ CHECK("a", "A", 1)
+ CHECK("Apple", "Aple", 2)
+ CHECK("Banana", "B@n@n@", 6)
+ CHECK("Cherry", "Cherry!", 2)
+ CHECK("---0---", "------", 2)
+ CHECK("abc", "y", 6)
+ CHECK("aa", "bb", 4)
+ CHECK("aaaaa", "AAAAA", 5)
+ CHECK("wxyz", "wXyZ", 2)
+ CHECK("wxyz", "wXyZ123", 8)
+ CHECK("Python", "Java", 12)
+ CHECK("Java", "C#", 8)
+ CHECK("AbstractFoobarManager", "abstract_foobar_manager", 3+2*2)
+ CHECK("CPython", "PyPy", 10)
+ CHECK("CPython", "pypy", 11)
+ CHECK("AttributeError", "AttributeErrop", 2)
+ CHECK("AttributeError", "AttributeErrorTests", 10)
+ CHECK("ABA", "AAB", 4)
+
+ # TODO: RUSTPYTHON
+ @unittest.expectedFailure
+ @support.requires_resource('cpu')
+ def test_levenshtein_distance_short_circuit(self):
+ if not LEVENSHTEIN_DATA_FILE.is_file():
+ self.fail(
+ f"{LEVENSHTEIN_DATA_FILE} is missing."
+ f" Run `make regen-test-levenshtein`"
+ )
+
+ with LEVENSHTEIN_DATA_FILE.open("r") as f:
+ examples = json.load(f)
+ for a, b, expected in examples:
+ res1 = traceback._levenshtein_distance(a, b, 1000)
+ self.assertEqual(res1, expected, msg=(a, b))
+
+ for threshold in [expected, expected + 1, expected + 2]:
+ # big enough thresholds shouldn't change the result
+ res2 = traceback._levenshtein_distance(a, b, threshold)
+ self.assertEqual(res2, expected, msg=(a, b, threshold))
+
+ for threshold in range(expected):
+ # for small thresholds, the only piece of information
+ # we receive is "strings not close enough".
+ res3 = traceback._levenshtein_distance(a, b, threshold)
+ self.assertGreater(res3, threshold, msg=(a, b, threshold))
+
+ @cpython_only
+ def test_suggestions_extension(self):
+ # Check that the C extension is available
+ import _suggestions
+
+ self.assertEqual(
+ _suggestions._generate_suggestions(
+ ["hello", "world"],
+ "hell"
+ ),
+ "hello"
+ )
+ self.assertEqual(
+ _suggestions._generate_suggestions(
+ ["hovercraft"],
+ "eels"
+ ),
+ None
+ )
+
+ # gh-131936: _generate_suggestions() doesn't accept list subclasses
+ class MyList(list):
+ pass
+
+ with self.assertRaises(TypeError):
+ _suggestions._generate_suggestions(MyList(), "")
+
+
+
+
+class TestColorizedTraceback(unittest.TestCase):
+ # TODO: RUSTPYTHON
+ @unittest.expectedFailure
+ def test_colorized_traceback(self):
+ def foo(*args):
+ x = {'a':{'b': None}}
+ y = x['a']['b']['c']
+
+ def baz2(*args):
+ return (lambda *args: foo(*args))(1,2,3,4)
+
+ def baz1(*args):
+ return baz2(1,2,3,4)
+
+ def bar():
+ return baz1(1,
+ 2,3
+ ,4)
+ try:
+ bar()
+ except Exception as e:
+ exc = traceback.TracebackException.from_exception(
+ e, capture_locals=True
+ )
+ lines = "".join(exc.format(colorize=True))
+ red = _colorize.ANSIColors.RED
+ boldr = _colorize.ANSIColors.BOLD_RED
+ reset = _colorize.ANSIColors.RESET
+ self.assertIn("y = " + red + "x['a']['b']" + reset + boldr + "['c']" + reset, lines)
+ self.assertIn("return " + red + "(lambda *args: foo(*args))" + reset + boldr + "(1,2,3,4)" + reset, lines)
+ self.assertIn("return (lambda *args: " + red + "foo" + reset + boldr + "(*args)" + reset + ")(1,2,3,4)", lines)
+ self.assertIn("return baz2(1,2,3,4)", lines)
+ self.assertIn("return baz1(1,\n 2,3\n ,4)", lines)
+ self.assertIn(red + "bar" + reset + boldr + "()" + reset, lines)
+
+ # TODO: RUSTPYTHON
+ @unittest.expectedFailure
+ def test_colorized_syntax_error(self):
+ try:
+ compile("a $ b", "", "exec")
+ except SyntaxError as e:
+ exc = traceback.TracebackException.from_exception(
+ e, capture_locals=True
+ )
+ actual = "".join(exc.format(colorize=True))
+ red = _colorize.ANSIColors.RED
+ magenta = _colorize.ANSIColors.MAGENTA
+ boldm = _colorize.ANSIColors.BOLD_MAGENTA
+ boldr = _colorize.ANSIColors.BOLD_RED
+ reset = _colorize.ANSIColors.RESET
+ expected = "".join([
+ f' File {magenta}""{reset}, line {magenta}1{reset}\n',
+ f' a {boldr}${reset} b\n',
+ f' {boldr}^{reset}\n',
+ f'{boldm}SyntaxError{reset}: {magenta}invalid syntax{reset}\n']
+ )
+ self.assertIn(expected, actual)
+
+ # TODO: RUSTPYTHON
+ @unittest.expectedFailure
+ def test_colorized_traceback_is_the_default(self):
+ def foo():
+ 1/0
+
+ from _testcapi import exception_print
+ try:
+ foo()
+ self.fail("No exception thrown.")
+ except Exception as e:
+ with captured_output("stderr") as tbstderr:
+ with unittest.mock.patch('_colorize.can_colorize', return_value=True):
+ exception_print(e)
+ actual = tbstderr.getvalue().splitlines()
+
+ red = _colorize.ANSIColors.RED
+ boldr = _colorize.ANSIColors.BOLD_RED
+ magenta = _colorize.ANSIColors.MAGENTA
+ boldm = _colorize.ANSIColors.BOLD_MAGENTA
+ reset = _colorize.ANSIColors.RESET
+ lno_foo = foo.__code__.co_firstlineno
+ expected = ['Traceback (most recent call last):',
+ f' File {magenta}"{__file__}"{reset}, '
+ f'line {magenta}{lno_foo+5}{reset}, in {magenta}test_colorized_traceback_is_the_default{reset}',
+ f' {red}foo{reset+boldr}(){reset}',
+ f' {red}~~~{reset+boldr}^^{reset}',
+ f' File {magenta}"{__file__}"{reset}, '
+ f'line {magenta}{lno_foo+1}{reset}, in {magenta}foo{reset}',
+ f' {red}1{reset+boldr}/{reset+red}0{reset}',
+ f' {red}~{reset+boldr}^{reset+red}~{reset}',
+ f'{boldm}ZeroDivisionError{reset}: {magenta}division by zero{reset}']
+ self.assertEqual(actual, expected)
+
+ # TODO: RUSTPYTHON
+ @unittest.expectedFailure
+ def test_colorized_traceback_from_exception_group(self):
+ def foo():
+ exceptions = []
+ try:
+ 1 / 0
+ except ZeroDivisionError as inner_exc:
+ exceptions.append(inner_exc)
+ raise ExceptionGroup("test", exceptions)
+
+ try:
+ foo()
+ except Exception as e:
+ exc = traceback.TracebackException.from_exception(
+ e, capture_locals=True
+ )
+
+ red = _colorize.ANSIColors.RED
+ boldr = _colorize.ANSIColors.BOLD_RED
+ magenta = _colorize.ANSIColors.MAGENTA
+ boldm = _colorize.ANSIColors.BOLD_MAGENTA
+ reset = _colorize.ANSIColors.RESET
+ lno_foo = foo.__code__.co_firstlineno
+ actual = "".join(exc.format(colorize=True)).splitlines()
+ expected = [f" + Exception Group Traceback (most recent call last):",
+ f' | File {magenta}"{__file__}"{reset}, line {magenta}{lno_foo+9}{reset}, in {magenta}test_colorized_traceback_from_exception_group{reset}',
+ f' | {red}foo{reset}{boldr}(){reset}',
+ f' | {red}~~~{reset}{boldr}^^{reset}',
+ f" | e = ExceptionGroup('test', [ZeroDivisionError('division by zero')])",
+ f" | foo = {foo}",
+ f' | self = <{__name__}.TestColorizedTraceback testMethod=test_colorized_traceback_from_exception_group>',
+ f' | File {magenta}"{__file__}"{reset}, line {magenta}{lno_foo+6}{reset}, in {magenta}foo{reset}',
+ f' | raise ExceptionGroup("test", exceptions)',
+ f" | exceptions = [ZeroDivisionError('division by zero')]",
+ f' | {boldm}ExceptionGroup{reset}: {magenta}test (1 sub-exception){reset}',
+ f' +-+---------------- 1 ----------------',
+ f' | Traceback (most recent call last):',
+ f' | File {magenta}"{__file__}"{reset}, line {magenta}{lno_foo+3}{reset}, in {magenta}foo{reset}',
+ f' | {red}1 {reset}{boldr}/{reset}{red} 0{reset}',
+ f' | {red}~~{reset}{boldr}^{reset}{red}~~{reset}',
+ f" | exceptions = [ZeroDivisionError('division by zero')]",
+ f' | {boldm}ZeroDivisionError{reset}: {magenta}division by zero{reset}',
+ f' +------------------------------------']
+ self.assertEqual(actual, expected)
if __name__ == "__main__":
unittest.main()
diff --git a/Lib/test/test_tuple.py b/Lib/test/test_tuple.py
index 26b238e086..153df0e52d 100644
--- a/Lib/test/test_tuple.py
+++ b/Lib/test/test_tuple.py
@@ -42,6 +42,35 @@ def test_keyword_args(self):
with self.assertRaisesRegex(TypeError, 'keyword argument'):
tuple(sequence=())
+ # TODO: RUSTPYTHON
+ @unittest.expectedFailure
+ def test_keywords_in_subclass(self):
+ class subclass(tuple):
+ pass
+ u = subclass([1, 2])
+ self.assertIs(type(u), subclass)
+ self.assertEqual(list(u), [1, 2])
+ with self.assertRaises(TypeError):
+ subclass(sequence=())
+
+ class subclass_with_init(tuple):
+ def __init__(self, arg, newarg=None):
+ self.newarg = newarg
+ u = subclass_with_init([1, 2], newarg=3)
+ self.assertIs(type(u), subclass_with_init)
+ self.assertEqual(list(u), [1, 2])
+ self.assertEqual(u.newarg, 3)
+
+ class subclass_with_new(tuple):
+ def __new__(cls, arg, newarg=None):
+ self = super().__new__(cls, arg)
+ self.newarg = newarg
+ return self
+ u = subclass_with_new([1, 2], newarg=3)
+ self.assertIs(type(u), subclass_with_new)
+ self.assertEqual(list(u), [1, 2])
+ self.assertEqual(u.newarg, 3)
+
def test_truth(self):
super().test_truth()
self.assertTrue(not ())
@@ -77,8 +106,6 @@ def f():
# We expect tuples whose base components have deterministic hashes to
# have deterministic hashes too - and, indeed, the same hashes across
# platforms with hash codes of the same bit width.
- # TODO: RUSTPYTHON
- @unittest.expectedFailure
def test_hash_exact(self):
def check_one_exact(t, e32, e64):
got = hash(t)
diff --git a/Lib/test/test_types.py b/Lib/test/test_types.py
index c62bf61181..59dc9814fb 100644
--- a/Lib/test/test_types.py
+++ b/Lib/test/test_types.py
@@ -742,8 +742,6 @@ def test_instancecheck_and_subclasscheck(self):
self.assertTrue(issubclass(dict, x))
self.assertFalse(issubclass(list, x))
- # TODO: RUSTPYTHON
- @unittest.expectedFailure
def test_instancecheck_and_subclasscheck_order(self):
T = typing.TypeVar('T')
@@ -790,8 +788,6 @@ def __subclasscheck__(cls, sub):
self.assertTrue(issubclass(int, x))
self.assertRaises(ZeroDivisionError, issubclass, list, x)
- # TODO: RUSTPYTHON
- @unittest.expectedFailure
def test_or_type_operator_with_TypeVar(self):
TV = typing.TypeVar('T')
assert TV | str == typing.Union[TV, str]
@@ -799,8 +795,6 @@ def test_or_type_operator_with_TypeVar(self):
self.assertIs((int | TV)[int], int)
self.assertIs((TV | int)[int], int)
- # TODO: RUSTPYTHON
- @unittest.expectedFailure
def test_union_args(self):
def check(arg, expected):
clear_typing_caches()
@@ -831,8 +825,6 @@ def check(arg, expected):
check(x | None, (x, type(None)))
check(None | x, (type(None), x))
- # TODO: RUSTPYTHON
- @unittest.expectedFailure
def test_union_parameter_chaining(self):
T = typing.TypeVar("T")
S = typing.TypeVar("S")
@@ -877,8 +869,6 @@ def eq(actual, expected, typed=True):
eq(x[NT], int | NT | bytes)
eq(x[S], int | S | bytes)
- # TODO: RUSTPYTHON
- @unittest.expectedFailure
def test_union_pickle(self):
orig = list[T] | int
for proto in range(pickle.HIGHEST_PROTOCOL + 1):
@@ -888,8 +878,6 @@ def test_union_pickle(self):
self.assertEqual(loaded.__args__, orig.__args__)
self.assertEqual(loaded.__parameters__, orig.__parameters__)
- # TODO: RUSTPYTHON
- @unittest.expectedFailure
def test_union_copy(self):
orig = list[T] | int
for copied in (copy.copy(orig), copy.deepcopy(orig)):
@@ -897,16 +885,12 @@ def test_union_copy(self):
self.assertEqual(copied.__args__, orig.__args__)
self.assertEqual(copied.__parameters__, orig.__parameters__)
- # TODO: RUSTPYTHON
- @unittest.expectedFailure
def test_union_parameter_substitution_errors(self):
T = typing.TypeVar("T")
x = int | T
with self.assertRaises(TypeError):
x[int, str]
- # TODO: RUSTPYTHON
- @unittest.expectedFailure
def test_or_type_operator_with_forward(self):
T = typing.TypeVar('T')
ForwardAfter = T | 'Forward'
diff --git a/Lib/test/test_typing.py b/Lib/test/test_typing.py
index 70397e2649..229d61ad15 100644
--- a/Lib/test/test_typing.py
+++ b/Lib/test/test_typing.py
@@ -373,8 +373,6 @@ def test_alias(self):
self.assertEqual(get_args(alias_3), (LiteralString,))
class TypeVarTests(BaseTestCase):
- # TODO: RUSTPYTHON
- @unittest.expectedFailure
def test_basic_plain(self):
T = TypeVar('T')
# T equals itself.
@@ -389,8 +387,6 @@ def test_basic_plain(self):
self.assertIs(T.__infer_variance__, False)
self.assertEqual(T.__module__, __name__)
- # TODO: RUSTPYTHON
- @unittest.expectedFailure
def test_basic_with_exec(self):
ns = {}
exec('from typing import TypeVar; T = TypeVar("T", bound=float)', ns, ns)
@@ -404,8 +400,6 @@ def test_basic_with_exec(self):
self.assertIs(T.__infer_variance__, False)
self.assertIs(T.__module__, None)
- # TODO: RUSTPYTHON
- @unittest.expectedFailure
def test_attributes(self):
T_bound = TypeVar('T_bound', bound=int)
self.assertEqual(T_bound.__name__, 'T_bound')
@@ -447,15 +441,11 @@ def test_typevar_subclass_type_error(self):
with self.assertRaises(TypeError):
issubclass(T, int)
- # TODO: RUSTPYTHON
- @unittest.expectedFailure
def test_constrained_error(self):
with self.assertRaises(TypeError):
X = TypeVar('X', int)
X
- # TODO: RUSTPYTHON
- @unittest.expectedFailure
def test_union_unique(self):
X = TypeVar('X')
Y = TypeVar('Y')
@@ -469,8 +459,6 @@ def test_union_unique(self):
self.assertEqual(Union[X, int].__parameters__, (X,))
self.assertIs(Union[X, int].__origin__, Union)
- # TODO: RUSTPYTHON
- @unittest.expectedFailure
def test_or(self):
X = TypeVar('X')
# use a string because str doesn't implement
@@ -485,8 +473,6 @@ def test_union_constrained(self):
A = TypeVar('A', str, bytes)
self.assertNotEqual(Union[A, str], Union[A])
- # TODO: RUSTPYTHON
- @unittest.expectedFailure
def test_repr(self):
self.assertEqual(repr(T), '~T')
self.assertEqual(repr(KT), '~KT')
@@ -501,8 +487,6 @@ def test_no_redefinition(self):
self.assertNotEqual(TypeVar('T'), TypeVar('T'))
self.assertNotEqual(TypeVar('T', int, str), TypeVar('T', int, str))
- # TODO: RUSTPYTHON
- @unittest.expectedFailure
def test_cannot_subclass(self):
with self.assertRaisesRegex(TypeError, NOT_A_BASE_TYPE % 'TypeVar'):
class V(TypeVar): pass
@@ -515,8 +499,6 @@ def test_cannot_instantiate_vars(self):
with self.assertRaises(TypeError):
TypeVar('A')()
- # TODO: RUSTPYTHON
- @unittest.expectedFailure
def test_bound_errors(self):
with self.assertRaises(TypeError):
TypeVar('X', bound=Union)
@@ -533,22 +515,16 @@ def test_missing__name__(self):
)
exec(code, {})
- # TODO: RUSTPYTHON
- @unittest.expectedFailure
def test_no_bivariant(self):
with self.assertRaises(ValueError):
TypeVar('T', covariant=True, contravariant=True)
- # TODO: RUSTPYTHON
- @unittest.expectedFailure
def test_cannot_combine_explicit_and_infer(self):
with self.assertRaises(ValueError):
TypeVar('T', covariant=True, infer_variance=True)
with self.assertRaises(ValueError):
TypeVar('T', contravariant=True, infer_variance=True)
- # TODO: RUSTPYTHON
- @unittest.expectedFailure
def test_var_substitution(self):
T = TypeVar('T')
subst = T.__typing_subst__
@@ -562,8 +538,6 @@ def test_var_substitution(self):
self.assertEqual(subst(int|str), int|str)
self.assertEqual(subst(Union[int, str]), Union[int, str])
- # TODO: RUSTPYTHON
- @unittest.expectedFailure
def test_bad_var_substitution(self):
T = TypeVar('T')
bad_args = (
@@ -590,8 +564,6 @@ def test_many_weakrefs(self):
vals[x] = cls(str(x))
del vals
- # TODO: RUSTPYTHON
- @unittest.expectedFailure
def test_constructor(self):
T = TypeVar(name="T")
self.assertEqual(T.__name__, "T")
@@ -648,8 +620,6 @@ def test_constructor(self):
self.assertIs(T.__infer_variance__, True)
class TypeParameterDefaultsTests(BaseTestCase):
- # TODO: RUSTPYTHON
- @unittest.expectedFailure
def test_typevar(self):
T = TypeVar('T', default=int)
self.assertEqual(T.__default__, int)
@@ -659,8 +629,6 @@ def test_typevar(self):
class A(Generic[T]): ...
Alias = Optional[T]
- # TODO: RUSTPYTHON
- @unittest.expectedFailure
def test_typevar_none(self):
U = TypeVar('U')
U_None = TypeVar('U_None', default=None)
@@ -674,8 +642,6 @@ class X[T]: ...
self.assertIs(T.__default__, NoDefault)
self.assertFalse(T.has_default())
- # TODO: RUSTPYTHON
- @unittest.expectedFailure
def test_paramspec(self):
P = ParamSpec('P', default=(str, int))
self.assertEqual(P.__default__, (str, int))
@@ -688,8 +654,6 @@ class A(Generic[P]): ...
P_default = ParamSpec('P_default', default=...)
self.assertIs(P_default.__default__, ...)
- # TODO: RUSTPYTHON
- @unittest.expectedFailure
def test_paramspec_none(self):
U = ParamSpec('U')
U_None = ParamSpec('U_None', default=None)
@@ -703,8 +667,6 @@ class X[**P]: ...
self.assertIs(P.__default__, NoDefault)
self.assertFalse(P.has_default())
- # TODO: RUSTPYTHON
- @unittest.expectedFailure
def test_typevartuple(self):
Ts = TypeVarTuple('Ts', default=Unpack[Tuple[str, int]])
self.assertEqual(Ts.__default__, Unpack[Tuple[str, int]])
@@ -714,8 +676,6 @@ def test_typevartuple(self):
class A(Generic[Unpack[Ts]]): ...
Alias = Optional[Unpack[Ts]]
- # TODO: RUSTPYTHON
- @unittest.expectedFailure
def test_typevartuple_specialization(self):
T = TypeVar("T")
Ts = TypeVarTuple('Ts', default=Unpack[Tuple[str, int]])
@@ -725,8 +685,6 @@ class A(Generic[T, Unpack[Ts]]): ...
self.assertEqual(A[float, range].__args__, (float, range))
self.assertEqual(A[float, *tuple[int, ...]].__args__, (float, *tuple[int, ...]))
- # TODO: RUSTPYTHON
- @unittest.expectedFailure
def test_typevar_and_typevartuple_specialization(self):
T = TypeVar("T")
U = TypeVar("U", default=float)
@@ -749,8 +707,6 @@ class X(Generic[*Ts, T]): ...
with self.assertRaises(TypeError):
class Y(Generic[*Ts_default, T]): ...
- # TODO: RUSTPYTHON
- @unittest.expectedFailure
def test_allow_default_after_non_default_in_alias(self):
T_default = TypeVar('T_default', default=int)
T = TypeVar('T')
@@ -768,8 +724,6 @@ def test_allow_default_after_non_default_in_alias(self):
a4 = Callable[*Ts, T]
self.assertEqual(a4.__args__, (*Ts, T))
- # TODO: RUSTPYTHON
- @unittest.expectedFailure
def test_paramspec_specialization(self):
T = TypeVar("T")
P = ParamSpec('P', default=[str, int])
@@ -778,8 +732,6 @@ class A(Generic[T, P]): ...
self.assertEqual(A[float].__args__, (float, (str, int)))
self.assertEqual(A[float, [range]].__args__, (float, (range,)))
- # TODO: RUSTPYTHON
- @unittest.expectedFailure
def test_typevar_and_paramspec_specialization(self):
T = TypeVar("T")
U = TypeVar("U", default=float)
@@ -790,8 +742,6 @@ class A(Generic[T, U, P]): ...
self.assertEqual(A[float, int].__args__, (float, int, (str, int)))
self.assertEqual(A[float, int, [range]].__args__, (float, int, (range,)))
- # TODO: RUSTPYTHON
- @unittest.expectedFailure
def test_paramspec_and_typevar_specialization(self):
T = TypeVar("T")
P = ParamSpec('P', default=[str, int])
@@ -802,8 +752,6 @@ class A(Generic[T, P, U]): ...
self.assertEqual(A[float, [range]].__args__, (float, (range,), float))
self.assertEqual(A[float, [range], int].__args__, (float, (range,), int))
- # TODO: RUSTPYTHON
- @unittest.expectedFailure
def test_typevartuple_none(self):
U = TypeVarTuple('U')
U_None = TypeVarTuple('U_None', default=None)
@@ -817,8 +765,6 @@ class X[**Ts]: ...
self.assertIs(Ts.__default__, NoDefault)
self.assertFalse(Ts.has_default())
- # TODO: RUSTPYTHON
- @unittest.expectedFailure
def test_no_default_after_non_default(self):
DefaultStrT = TypeVar('DefaultStrT', default=str)
T = TypeVar('T')
@@ -828,8 +774,6 @@ def test_no_default_after_non_default(self):
):
Test = Generic[DefaultStrT, T]
- # TODO: RUSTPYTHON
- @unittest.expectedFailure
def test_need_more_params(self):
DefaultStrT = TypeVar('DefaultStrT', default=str)
T = TypeVar('T')
@@ -844,8 +788,6 @@ class A(Generic[T, U, DefaultStrT]): ...
):
Test = A[int]
- # TODO: RUSTPYTHON
- @unittest.expectedFailure
def test_pickle(self):
global U, U_co, U_contra, U_default # pickle wants to reference the class by name
U = TypeVar('U')
@@ -974,8 +916,6 @@ class GenericAliasSubstitutionTests(BaseTestCase):
https://github.com/python/cpython/issues/91162.
"""
- # TODO: RUSTPYTHON
- @unittest.expectedFailure
def test_one_parameter(self):
T = TypeVar('T')
Ts = TypeVarTuple('Ts')
@@ -1093,8 +1033,6 @@ class C(Generic[T1, T2]): pass
eval(expected_str)
)
- # TODO: RUSTPYTHON
- @unittest.expectedFailure
def test_three_parameters(self):
T1 = TypeVar('T1')
T2 = TypeVar('T2')
@@ -1270,26 +1208,23 @@ def foo(**kwargs: Unpack[Movie]): ...
self.assertEqual(repr(foo.__annotations__['kwargs']),
f"typing.Unpack[{__name__}.Movie]")
- # TODO: RUSTPYTHON
- @unittest.expectedFailure
def test_builtin_tuple(self):
Ts = TypeVarTuple("Ts")
- # TODO: RUSTPYTHON
- # class Old(Generic[*Ts]): ...
- # class New[*Ts]: ...
+ class Old(Generic[*Ts]): ...
+ class New[*Ts]: ...
PartOld = Old[int, *Ts]
self.assertEqual(PartOld[str].__args__, (int, str))
- # self.assertEqual(PartOld[*tuple[str]].__args__, (int, str))
- # self.assertEqual(PartOld[*Tuple[str]].__args__, (int, str))
+ self.assertEqual(PartOld[*tuple[str]].__args__, (int, str))
+ self.assertEqual(PartOld[*Tuple[str]].__args__, (int, str))
self.assertEqual(PartOld[Unpack[tuple[str]]].__args__, (int, str))
self.assertEqual(PartOld[Unpack[Tuple[str]]].__args__, (int, str))
PartNew = New[int, *Ts]
self.assertEqual(PartNew[str].__args__, (int, str))
- # self.assertEqual(PartNew[*tuple[str]].__args__, (int, str))
- # self.assertEqual(PartNew[*Tuple[str]].__args__, (int, str))
+ self.assertEqual(PartNew[*tuple[str]].__args__, (int, str))
+ self.assertEqual(PartNew[*Tuple[str]].__args__, (int, str))
self.assertEqual(PartNew[Unpack[tuple[str]]].__args__, (int, str))
self.assertEqual(PartNew[Unpack[Tuple[str]]].__args__, (int, str))
@@ -1298,7 +1233,7 @@ def test_builtin_tuple(self):
def test_unpack_wrong_type(self):
Ts = TypeVarTuple("Ts")
class Gen[*Ts]: ...
- # PartGen = Gen[int, *Ts]
+ PartGen = Gen[int, *Ts]
bad_unpack_param = re.escape("Unpack[...] must be used with a tuple type")
with self.assertRaisesRegex(TypeError, bad_unpack_param):
@@ -1308,22 +1243,16 @@ class Gen[*Ts]: ...
class TypeVarTupleTests(BaseTestCase):
- # TODO: RUSTPYTHON
- @unittest.expectedFailure
def test_name(self):
Ts = TypeVarTuple('Ts')
self.assertEqual(Ts.__name__, 'Ts')
Ts2 = TypeVarTuple('Ts2')
self.assertEqual(Ts2.__name__, 'Ts2')
- # TODO: RUSTPYTHON
- @unittest.expectedFailure
def test_module(self):
Ts = TypeVarTuple('Ts')
self.assertEqual(Ts.__module__, __name__)
- # TODO: RUSTPYTHON
- @unittest.expectedFailure
def test_exec(self):
ns = {}
exec('from typing import TypeVarTuple; Ts = TypeVarTuple("Ts")', ns)
@@ -1347,8 +1276,6 @@ def test_cannot_call_instance(self):
with self.assertRaises(TypeError):
Ts()
- # TODO: RUSTPYTHON
- @unittest.expectedFailure
def test_unpacked_typevartuple_is_equal_to_itself(self):
Ts = TypeVarTuple('Ts')
self.assertEqual((*Ts,)[0], (*Ts,)[0])
@@ -1356,11 +1283,9 @@ def test_unpacked_typevartuple_is_equal_to_itself(self):
def test_parameterised_tuple_is_equal_to_itself(self):
Ts = TypeVarTuple('Ts')
- # self.assertEqual(tuple[*Ts], tuple[*Ts])
+ self.assertEqual(tuple[*Ts], tuple[*Ts])
self.assertEqual(Tuple[Unpack[Ts]], Tuple[Unpack[Ts]])
- # TODO: RUSTPYTHON
- @unittest.expectedFailure
def tests_tuple_arg_ordering_matters(self):
Ts1 = TypeVarTuple('Ts1')
Ts2 = TypeVarTuple('Ts2')
@@ -1373,28 +1298,24 @@ def tests_tuple_arg_ordering_matters(self):
Tuple[Unpack[Ts2], Unpack[Ts1]],
)
- # TODO: RUSTPYTHON
- @unittest.expectedFailure
def test_tuple_args_and_parameters_are_correct(self):
Ts = TypeVarTuple('Ts')
- # t1 = tuple[*Ts]
+ t1 = tuple[*Ts]
self.assertEqual(t1.__args__, (*Ts,))
self.assertEqual(t1.__parameters__, (Ts,))
t2 = Tuple[Unpack[Ts]]
self.assertEqual(t2.__args__, (Unpack[Ts],))
self.assertEqual(t2.__parameters__, (Ts,))
- # TODO: RUSTPYTHON
- @unittest.expectedFailure
def test_var_substitution(self):
Ts = TypeVarTuple('Ts')
T = TypeVar('T')
T2 = TypeVar('T2')
- # class G1(Generic[*Ts]): pass
+ class G1(Generic[*Ts]): pass
class G2(Generic[Unpack[Ts]]): pass
for A in G1, G2, Tuple, tuple:
- # B = A[*Ts]
+ B = A[*Ts]
self.assertEqual(B[()], A[()])
self.assertEqual(B[float], A[float])
self.assertEqual(B[float, str], A[float, str])
@@ -1404,7 +1325,7 @@ class G2(Generic[Unpack[Ts]]): pass
self.assertEqual(C[float], A[float])
self.assertEqual(C[float, str], A[float, str])
- # D = list[A[*Ts]]
+ D = list[A[*Ts]]
self.assertEqual(D[()], list[A[()]])
self.assertEqual(D[float], list[A[float]])
self.assertEqual(D[float, str], list[A[float, str]])
@@ -1432,7 +1353,7 @@ class G2(Generic[Unpack[Ts]]): pass
self.assertEqual(G[float, str, int], A[float, str, int])
self.assertEqual(G[float, str, int, bytes], A[float, str, int, bytes])
- # H = tuple[list[T], A[*Ts], list[T2]]
+ H = tuple[list[T], A[*Ts], list[T2]]
with self.assertRaises(TypeError):
H[()]
with self.assertRaises(TypeError):
@@ -1458,13 +1379,11 @@ class G2(Generic[Unpack[Ts]]): pass
self.assertEqual(I[float, str, int, bytes],
Tuple[List[float], A[str, int], List[bytes]])
- # TODO: RUSTPYTHON
- @unittest.expectedFailure
def test_bad_var_substitution(self):
Ts = TypeVarTuple('Ts')
T = TypeVar('T')
T2 = TypeVar('T2')
- # class G1(Generic[*Ts]): pass
+ class G1(Generic[*Ts]): pass
class G2(Generic[Unpack[Ts]]): pass
for A in G1, G2, Tuple, tuple:
@@ -1474,8 +1393,7 @@ class G2(Generic[Unpack[Ts]]): pass
C = A[T, T2]
with self.assertRaises(TypeError):
- # C[*Ts]
- pass
+ C[*Ts]
with self.assertRaises(TypeError):
C[Unpack[Ts]]
@@ -1491,12 +1409,10 @@ class G2(Generic[Unpack[Ts]]): pass
with self.assertRaises(TypeError):
C[int, Unpack[Ts], Unpack[Ts]]
- # TODO: RUSTPYTHON
- @unittest.expectedFailure
def test_repr_is_correct(self):
Ts = TypeVarTuple('Ts')
- # class G1(Generic[*Ts]): pass
+ class G1(Generic[*Ts]): pass
class G2(Generic[Unpack[Ts]]): pass
self.assertEqual(repr(Ts), 'Ts')
@@ -1504,17 +1420,15 @@ class G2(Generic[Unpack[Ts]]): pass
self.assertEqual(repr((*Ts,)[0]), 'typing.Unpack[Ts]')
self.assertEqual(repr(Unpack[Ts]), 'typing.Unpack[Ts]')
- # self.assertEqual(repr(tuple[*Ts]), 'tuple[typing.Unpack[Ts]]')
+ self.assertEqual(repr(tuple[*Ts]), 'tuple[typing.Unpack[Ts]]')
self.assertEqual(repr(Tuple[Unpack[Ts]]), 'typing.Tuple[typing.Unpack[Ts]]')
- # self.assertEqual(repr(*tuple[*Ts]), '*tuple[typing.Unpack[Ts]]')
+ self.assertEqual(repr(*tuple[*Ts]), '*tuple[typing.Unpack[Ts]]')
self.assertEqual(repr(Unpack[Tuple[Unpack[Ts]]]), 'typing.Unpack[typing.Tuple[typing.Unpack[Ts]]]')
- # TODO: RUSTPYTHON
- @unittest.expectedFailure
def test_variadic_class_repr_is_correct(self):
Ts = TypeVarTuple('Ts')
- # class A(Generic[*Ts]): pass
+ class A(Generic[*Ts]): pass
class B(Generic[Unpack[Ts]]): pass
self.assertEndsWith(repr(A[()]), 'A[()]')
@@ -1524,8 +1438,8 @@ class B(Generic[Unpack[Ts]]): pass
self.assertEndsWith(repr(A[float, str]), 'A[float, str]')
self.assertEndsWith(repr(B[float, str]), 'B[float, str]')
- # self.assertEndsWith(repr(A[*tuple[int, ...]]),
- # 'A[*tuple[int, ...]]')
+ self.assertEndsWith(repr(A[*tuple[int, ...]]),
+ 'A[*tuple[int, ...]]')
self.assertEndsWith(repr(B[Unpack[Tuple[int, ...]]]),
'B[typing.Unpack[typing.Tuple[int, ...]]]')
@@ -1544,17 +1458,15 @@ class B(Generic[Unpack[Ts]]): pass
self.assertEndsWith(repr(B[float, Unpack[Tuple[int, ...]], str]),
'B[float, typing.Unpack[typing.Tuple[int, ...]], str]')
- # TODO: RUSTPYTHON
- @unittest.expectedFailure
def test_variadic_class_alias_repr_is_correct(self):
Ts = TypeVarTuple('Ts')
class A(Generic[Unpack[Ts]]): pass
- # B = A[*Ts]
- # self.assertEndsWith(repr(B), 'A[typing.Unpack[Ts]]')
- # self.assertEndsWith(repr(B[()]), 'A[()]')
- # self.assertEndsWith(repr(B[float]), 'A[float]')
- # self.assertEndsWith(repr(B[float, str]), 'A[float, str]')
+ B = A[*Ts]
+ self.assertEndsWith(repr(B), 'A[typing.Unpack[Ts]]')
+ self.assertEndsWith(repr(B[()]), 'A[()]')
+ self.assertEndsWith(repr(B[float]), 'A[float]')
+ self.assertEndsWith(repr(B[float, str]), 'A[float, str]')
C = A[Unpack[Ts]]
self.assertEndsWith(repr(C), 'A[typing.Unpack[Ts]]')
@@ -1610,8 +1522,6 @@ class A(Generic[Unpack[Ts]]): pass
self.assertEndsWith(repr(K[float]), 'A[float, typing.Unpack[typing.Tuple[str, ...]]]')
self.assertEndsWith(repr(K[float, str]), 'A[float, str, typing.Unpack[typing.Tuple[str, ...]]]')
- # TODO: RUSTPYTHON
- @unittest.expectedFailure
def test_cannot_subclass(self):
with self.assertRaisesRegex(TypeError, NOT_A_BASE_TYPE % 'TypeVarTuple'):
class C(TypeVarTuple): pass
@@ -1633,12 +1543,10 @@ class I(*Ts): pass
with self.assertRaisesRegex(TypeError, r'Cannot subclass typing.Unpack\[Ts\]'):
class J(Unpack[Ts]): pass
- # TODO: RUSTPYTHON
- @unittest.expectedFailure
def test_variadic_class_args_are_correct(self):
T = TypeVar('T')
Ts = TypeVarTuple('Ts')
- # class A(Generic[*Ts]): pass
+ class A(Generic[*Ts]): pass
class B(Generic[Unpack[Ts]]): pass
C = A[()]
@@ -1661,10 +1569,10 @@ class B(Generic[Unpack[Ts]]): pass
self.assertEqual(I.__args__, (T,))
self.assertEqual(J.__args__, (T,))
- # K = A[*Ts]
- # L = B[Unpack[Ts]]
- # self.assertEqual(K.__args__, (*Ts,))
- # self.assertEqual(L.__args__, (Unpack[Ts],))
+ K = A[*Ts]
+ L = B[Unpack[Ts]]
+ self.assertEqual(K.__args__, (*Ts,))
+ self.assertEqual(L.__args__, (Unpack[Ts],))
M = A[T, *Ts]
N = B[T, Unpack[Ts]]
@@ -1679,7 +1587,7 @@ class B(Generic[Unpack[Ts]]): pass
def test_variadic_class_origin_is_correct(self):
Ts = TypeVarTuple('Ts')
- # class C(Generic[*Ts]): pass
+ class C(Generic[*Ts]): pass
self.assertIs(C[int].__origin__, C)
self.assertIs(C[T].__origin__, C)
self.assertIs(C[Unpack[Ts]].__origin__, C)
@@ -1692,19 +1600,17 @@ class D(Generic[Unpack[Ts]]): pass
def test_get_type_hints_on_unpack_args(self):
Ts = TypeVarTuple('Ts')
- # def func1(*args: *Ts): pass
- # self.assertEqual(gth(func1), {'args': Unpack[Ts]})
+ def func1(*args: *Ts): pass
+ self.assertEqual(gth(func1), {'args': Unpack[Ts]})
- # def func2(*args: *tuple[int, str]): pass
- # self.assertEqual(gth(func2), {'args': Unpack[tuple[int, str]]})
+ def func2(*args: *tuple[int, str]): pass
+ self.assertEqual(gth(func2), {'args': Unpack[tuple[int, str]]})
- # class CustomVariadic(Generic[*Ts]): pass
+ class CustomVariadic(Generic[*Ts]): pass
- # def func3(*args: *CustomVariadic[int, str]): pass
- # self.assertEqual(gth(func3), {'args': Unpack[CustomVariadic[int, str]]})
+ def func3(*args: *CustomVariadic[int, str]): pass
+ self.assertEqual(gth(func3), {'args': Unpack[CustomVariadic[int, str]]})
- # TODO: RUSTPYTHON
- @unittest.expectedFailure
def test_get_type_hints_on_unpack_args_string(self):
Ts = TypeVarTuple('Ts')
@@ -1715,19 +1621,17 @@ def func1(*args: '*Ts'): pass
def func2(*args: '*tuple[int, str]'): pass
self.assertEqual(gth(func2), {'args': Unpack[tuple[int, str]]})
- # class CustomVariadic(Generic[*Ts]): pass
+ class CustomVariadic(Generic[*Ts]): pass
- # def func3(*args: '*CustomVariadic[int, str]'): pass
- # self.assertEqual(gth(func3, localns={'CustomVariadic': CustomVariadic}),
- # {'args': Unpack[CustomVariadic[int, str]]})
+ def func3(*args: '*CustomVariadic[int, str]'): pass
+ self.assertEqual(gth(func3, localns={'CustomVariadic': CustomVariadic}),
+ {'args': Unpack[CustomVariadic[int, str]]})
- # TODO: RUSTPYTHON
- @unittest.expectedFailure
def test_tuple_args_are_correct(self):
Ts = TypeVarTuple('Ts')
- # self.assertEqual(tuple[*Ts].__args__, (*Ts,))
+ self.assertEqual(tuple[*Ts].__args__, (*Ts,))
self.assertEqual(Tuple[Unpack[Ts]].__args__, (Unpack[Ts],))
self.assertEqual(tuple[*Ts, int].__args__, (*Ts, int))
@@ -1744,8 +1648,6 @@ def test_tuple_args_are_correct(self):
self.assertEqual(tuple[*Ts, int].__args__, (*Ts, int))
self.assertEqual(Tuple[Unpack[Ts]].__args__, (Unpack[Ts],))
- # TODO: RUSTPYTHON
- @unittest.expectedFailure
def test_callable_args_are_correct(self):
Ts = TypeVarTuple('Ts')
Ts1 = TypeVarTuple('Ts1')
@@ -1807,8 +1709,6 @@ def test_callable_args_are_correct(self):
self.assertEqual(s.__args__, (*Ts1, *Ts2))
self.assertEqual(u.__args__, (Unpack[Ts1], Unpack[Ts2]))
- # TODO: RUSTPYTHON
- @unittest.expectedFailure
def test_variadic_class_with_duplicate_typevartuples_fails(self):
Ts1 = TypeVarTuple('Ts1')
Ts2 = TypeVarTuple('Ts2')
@@ -1823,8 +1723,6 @@ class E(Generic[*Ts1, *Ts2, *Ts1]): pass
with self.assertRaises(TypeError):
class F(Generic[Unpack[Ts1], Unpack[Ts2], Unpack[Ts1]]): pass
- # TODO: RUSTPYTHON
- @unittest.expectedFailure
def test_type_concatenation_in_variadic_class_argument_list_succeeds(self):
Ts = TypeVarTuple('Ts')
class C(Generic[Unpack[Ts]]): pass
@@ -1841,8 +1739,6 @@ class C(Generic[Unpack[Ts]]): pass
C[int, bool, *Ts, float, str]
C[int, bool, Unpack[Ts], float, str]
- # TODO: RUSTPYTHON
- @unittest.expectedFailure
def test_type_concatenation_in_tuple_argument_list_succeeds(self):
Ts = TypeVarTuple('Ts')
@@ -1856,15 +1752,11 @@ def test_type_concatenation_in_tuple_argument_list_succeeds(self):
Tuple[int, Unpack[Ts], str]
Tuple[int, bool, Unpack[Ts], float, str]
- # TODO: RUSTPYTHON
- @unittest.expectedFailure
def test_variadic_class_definition_using_packed_typevartuple_fails(self):
Ts = TypeVarTuple('Ts')
with self.assertRaises(TypeError):
class C(Generic[Ts]): pass
- # TODO: RUSTPYTHON
- @unittest.expectedFailure
def test_variadic_class_definition_using_concrete_types_fails(self):
Ts = TypeVarTuple('Ts')
with self.assertRaises(TypeError):
@@ -1872,8 +1764,6 @@ class F(Generic[*Ts, int]): pass
with self.assertRaises(TypeError):
class E(Generic[Unpack[Ts], int]): pass
- # TODO: RUSTPYTHON
- @unittest.expectedFailure
def test_variadic_class_with_2_typevars_accepts_2_or_more_args(self):
Ts = TypeVarTuple('Ts')
T1 = TypeVar('T1')
@@ -1909,20 +1799,19 @@ class F(Generic[Unpack[Ts], T1, T2]): pass
F[int, str, float]
F[int, str, float, bool]
-
def test_variadic_args_annotations_are_correct(self):
Ts = TypeVarTuple('Ts')
def f(*args: Unpack[Ts]): pass
- # def g(*args: *Ts): pass
+ def g(*args: *Ts): pass
self.assertEqual(f.__annotations__, {'args': Unpack[Ts]})
- # self.assertEqual(g.__annotations__, {'args': (*Ts,)[0]})
+ self.assertEqual(g.__annotations__, {'args': (*Ts,)[0]})
def test_variadic_args_with_ellipsis_annotations_are_correct(self):
- # def a(*args: *tuple[int, ...]): pass
- # self.assertEqual(a.__annotations__,
- # {'args': (*tuple[int, ...],)[0]})
+ def a(*args: *tuple[int, ...]): pass
+ self.assertEqual(a.__annotations__,
+ {'args': (*tuple[int, ...],)[0]})
def b(*args: Unpack[Tuple[int, ...]]): pass
self.assertEqual(b.__annotations__,
@@ -1934,29 +1823,29 @@ def test_concatenation_in_variadic_args_annotations_are_correct(self):
# Unpacking using `*`, native `tuple` type
- # def a(*args: *tuple[int, *Ts]): pass
- # self.assertEqual(
- # a.__annotations__,
- # {'args': (*tuple[int, *Ts],)[0]},
- # )
-
- # def b(*args: *tuple[*Ts, int]): pass
- # self.assertEqual(
- # b.__annotations__,
- # {'args': (*tuple[*Ts, int],)[0]},
- # )
-
- # def c(*args: *tuple[str, *Ts, int]): pass
- # self.assertEqual(
- # c.__annotations__,
- # {'args': (*tuple[str, *Ts, int],)[0]},
- # )
-
- # def d(*args: *tuple[int, bool, *Ts, float, str]): pass
- # self.assertEqual(
- # d.__annotations__,
- # {'args': (*tuple[int, bool, *Ts, float, str],)[0]},
- # )
+ def a(*args: *tuple[int, *Ts]): pass
+ self.assertEqual(
+ a.__annotations__,
+ {'args': (*tuple[int, *Ts],)[0]},
+ )
+
+ def b(*args: *tuple[*Ts, int]): pass
+ self.assertEqual(
+ b.__annotations__,
+ {'args': (*tuple[*Ts, int],)[0]},
+ )
+
+ def c(*args: *tuple[str, *Ts, int]): pass
+ self.assertEqual(
+ c.__annotations__,
+ {'args': (*tuple[str, *Ts, int],)[0]},
+ )
+
+ def d(*args: *tuple[int, bool, *Ts, float, str]): pass
+ self.assertEqual(
+ d.__annotations__,
+ {'args': (*tuple[int, bool, *Ts, float, str],)[0]},
+ )
# Unpacking using `Unpack`, `Tuple` type from typing.py
@@ -1984,11 +1873,9 @@ def h(*args: Unpack[Tuple[int, bool, Unpack[Ts], float, str]]): pass
{'args': Unpack[Tuple[int, bool, Unpack[Ts], float, str]]},
)
- # TODO: RUSTPYTHON
- @unittest.expectedFailure
def test_variadic_class_same_args_results_in_equalty(self):
Ts = TypeVarTuple('Ts')
- # class C(Generic[*Ts]): pass
+ class C(Generic[*Ts]): pass
class D(Generic[Unpack[Ts]]): pass
self.assertEqual(C[int], C[int])
@@ -1998,8 +1885,8 @@ class D(Generic[Unpack[Ts]]): pass
Ts2 = TypeVarTuple('Ts2')
self.assertEqual(
- # C[*Ts1],
- # C[*Ts1],
+ C[*Ts1],
+ C[*Ts1],
)
self.assertEqual(
D[Unpack[Ts1]],
@@ -2024,11 +1911,9 @@ class D(Generic[Unpack[Ts]]): pass
D[int, Unpack[Ts1], Unpack[Ts2]],
)
- # TODO: RUSTPYTHON
- @unittest.expectedFailure
def test_variadic_class_arg_ordering_matters(self):
Ts = TypeVarTuple('Ts')
- # class C(Generic[*Ts]): pass
+ class C(Generic[*Ts]): pass
class D(Generic[Unpack[Ts]]): pass
self.assertNotEqual(
@@ -2057,10 +1942,10 @@ def test_variadic_class_arg_typevartuple_identity_matters(self):
Ts1 = TypeVarTuple('Ts1')
Ts2 = TypeVarTuple('Ts2')
- # class C(Generic[*Ts]): pass
+ class C(Generic[*Ts]): pass
class D(Generic[Unpack[Ts]]): pass
- # self.assertNotEqual(C[*Ts1], C[*Ts2])
+ self.assertNotEqual(C[*Ts1], C[*Ts2])
self.assertNotEqual(D[Unpack[Ts1]], D[Unpack[Ts2]])
class TypeVarTuplePicklingTests(BaseTestCase):
@@ -2070,7 +1955,6 @@ class TypeVarTuplePicklingTests(BaseTestCase):
# statements at the start of each test.
# TODO: RUSTPYTHON
- @unittest.expectedFailure
@all_pickle_protocols
def test_pickling_then_unpickling_results_in_same_identity(self, proto):
global global_Ts1 # See explanation at start of class.
@@ -2078,8 +1962,6 @@ def test_pickling_then_unpickling_results_in_same_identity(self, proto):
global_Ts2 = pickle.loads(pickle.dumps(global_Ts1, proto))
self.assertIs(global_Ts1, global_Ts2)
- # TODO: RUSTPYTHON
- @unittest.expectedFailure
@all_pickle_protocols
def test_pickling_then_unpickling_unpacked_results_in_same_identity(self, proto):
global global_Ts # See explanation at start of class.
@@ -2093,8 +1975,6 @@ def test_pickling_then_unpickling_unpacked_results_in_same_identity(self, proto)
unpacked4 = pickle.loads(pickle.dumps(unpacked3, proto))
self.assertIs(unpacked3, unpacked4)
- # TODO: RUSTPYTHON
- @unittest.expectedFailure
@all_pickle_protocols
def test_pickling_then_unpickling_tuple_with_typevartuple_equality(
self, proto
@@ -2104,8 +1984,7 @@ def test_pickling_then_unpickling_tuple_with_typevartuple_equality(
global_Ts = TypeVarTuple('global_Ts')
tuples = [
- # TODO: RUSTPYTHON
- # tuple[*global_Ts],
+ tuple[*global_Ts],
Tuple[Unpack[global_Ts]],
tuple[T, *global_Ts],
@@ -2262,8 +2141,6 @@ class B(metaclass=UnhashableMeta): ...
with self.assertRaises(TypeError):
hash(union3)
- # TODO: RUSTPYTHON
- @unittest.expectedFailure
def test_repr(self):
self.assertEqual(repr(Union), 'typing.Union')
u = Union[Employee, int]
@@ -2431,8 +2308,6 @@ def test_tuple_instance_type_error(self):
isinstance((0, 0), Tuple[int, int])
self.assertIsInstance((0, 0), Tuple)
- # TODO: RUSTPYTHON
- @unittest.expectedFailure
def test_repr(self):
self.assertEqual(repr(Tuple), 'typing.Tuple')
self.assertEqual(repr(Tuple[()]), 'typing.Tuple[()]')
@@ -2514,8 +2389,6 @@ def f():
with self.assertRaises(TypeError):
isinstance(None, Callable[[], Any])
- # TODO: RUSTPYTHON
- @unittest.expectedFailure
def test_repr(self):
Callable = self.Callable
fullname = f'{Callable.__module__}.Callable'
@@ -2528,7 +2401,6 @@ def test_repr(self):
ct3 = Callable[[str, float], list[int]]
self.assertEqual(repr(ct3), f'{fullname}[[str, float], list[int]]')
- @unittest.skip("TODO: RUSTPYTHON")
def test_callable_with_ellipsis(self):
Callable = self.Callable
def foo(a: Callable[..., T]):
@@ -2561,8 +2433,6 @@ def test_weakref(self):
alias = Callable[[int, str], float]
self.assertEqual(weakref.ref(alias)(), alias)
- # TODO: RUSTPYTHON
- @unittest.expectedFailure
def test_pickle(self):
global T_pickle, P_pickle, TS_pickle # needed for pickling
Callable = self.Callable
@@ -2588,8 +2458,6 @@ def test_pickle(self):
del T_pickle, P_pickle, TS_pickle # cleaning up global state
- # TODO: RUSTPYTHON
- @unittest.expectedFailure
def test_var_substitution(self):
Callable = self.Callable
fullname = f"{Callable.__module__}.Callable"
@@ -2614,7 +2482,6 @@ def test_var_substitution(self):
self.assertEqual(C5[int, str, float],
Callable[[typing.List[int], tuple[str, int], float], int])
- @unittest.skip("TODO: RUSTPYTHON")
def test_type_subst_error(self):
Callable = self.Callable
P = ParamSpec('P')
@@ -2634,8 +2501,6 @@ def __call__(self):
self.assertIs(a().__class__, C1)
self.assertEqual(a().__orig_class__, C1[[int], T])
- # TODO: RUSTPYTHON
- @unittest.expectedFailure
def test_paramspec(self):
Callable = self.Callable
fullname = f"{Callable.__module__}.Callable"
@@ -2670,8 +2535,6 @@ def test_paramspec(self):
self.assertEqual(repr(C2), f"{fullname}[~P, int]")
self.assertEqual(repr(C2[int, str]), f"{fullname}[[int, str], int]")
- # TODO: RUSTPYTHON
- @unittest.expectedFailure
def test_concatenate(self):
Callable = self.Callable
fullname = f"{Callable.__module__}.Callable"
@@ -2699,8 +2562,6 @@ def test_concatenate(self):
Callable[Concatenate[int, str, P2], int])
self.assertEqual(C[...], Callable[Concatenate[int, ...], int])
- # TODO: RUSTPYTHON
- @unittest.expectedFailure
def test_nested_paramspec(self):
# Since Callable has some special treatment, we want to be sure
# that substituion works correctly, see gh-103054
@@ -2743,8 +2604,6 @@ class My(Generic[P, T]):
self.assertEqual(C4[bool, bytes, float],
My[[Callable[[int, bool, bytes, str], float], float], float])
- # TODO: RUSTPYTHON
- @unittest.expectedFailure
def test_errors(self):
Callable = self.Callable
alias = Callable[[int, str], float]
@@ -3011,13 +2870,10 @@ def test_runtime_checkable_generic_non_protocol(self):
@runtime_checkable
class Foo[T]: ...
- # TODO: RUSTPYTHON
- @unittest.expectedFailure
def test_runtime_checkable_generic(self):
- # @runtime_checkable
- # class Foo[T](Protocol):
- # def meth(self) -> T: ...
- # pass
+ @runtime_checkable
+ class Foo[T](Protocol):
+ def meth(self) -> T: ...
class Impl:
def meth(self) -> int: ...
@@ -3032,9 +2888,9 @@ def method(self) -> int: ...
# TODO: RUSTPYTHON
@unittest.expectedFailure
def test_pep695_generics_can_be_runtime_checkable(self):
- # @runtime_checkable
- # class HasX(Protocol):
- # x: int
+ @runtime_checkable
+ class HasX(Protocol):
+ x: int
class Bar[T]:
x: T
@@ -3050,8 +2906,6 @@ def __init__(self, y):
self.assertNotIsInstance(Capybara('a'), HasX)
- # TODO: RUSTPYTHON
- @unittest.expectedFailure
def test_everything_implements_empty_protocol(self):
@runtime_checkable
class Empty(Protocol):
@@ -3074,22 +2928,20 @@ def f():
self.assertIsInstance(f, HasCallProtocol)
- # TODO: RUSTPYTHON
- @unittest.expectedFailure
def test_no_inheritance_from_nominal(self):
class C: pass
- # class BP(Protocol): pass
+ class BP(Protocol): pass
- # with self.assertRaises(TypeError):
- # class P(C, Protocol):
- # pass
- # with self.assertRaises(TypeError):
- # class Q(Protocol, C):
- # pass
- # with self.assertRaises(TypeError):
- # class R(BP, C, Protocol):
- # pass
+ with self.assertRaises(TypeError):
+ class P(C, Protocol):
+ pass
+ with self.assertRaises(TypeError):
+ class Q(Protocol, C):
+ pass
+ with self.assertRaises(TypeError):
+ class R(BP, C, Protocol):
+ pass
class D(BP, C): pass
@@ -3101,7 +2953,7 @@ class E(C, BP): pass
# TODO: RUSTPYTHON
@unittest.expectedFailure
def test_no_instantiation(self):
- # class P(Protocol): pass
+ class P(Protocol): pass
with self.assertRaises(TypeError):
P()
@@ -3129,16 +2981,14 @@ class CG(PG[T]): pass
with self.assertRaises(TypeError):
CG[int](42)
- # TODO: RUSTPYTHON
- @unittest.expectedFailure
def test_protocol_defining_init_does_not_get_overridden(self):
# check that P.__init__ doesn't get clobbered
# see https://bugs.python.org/issue44807
- # class P(Protocol):
- # x: int
- # def __init__(self, x: int) -> None:
- # self.x = x
+ class P(Protocol):
+ x: int
+ def __init__(self, x: int) -> None:
+ self.x = x
class C: pass
c = C()
@@ -3243,8 +3093,6 @@ def meth2(self):
self.assertIsInstance(C(), P)
self.assertIsSubclass(C, P)
- # TODO: RUSTPYTHON
- @unittest.expectedFailure
def test_protocols_issubclass(self):
T = TypeVar('T')
@@ -3394,8 +3242,6 @@ class Foo(collections.abc.Mapping, Protocol):
self.assertNotIsInstance([], collections.abc.Mapping)
- # TODO: RUSTPYTHON
- @unittest.expectedFailure
def test_issubclass_and_isinstance_on_Protocol_itself(self):
class C:
def x(self): pass
@@ -3455,8 +3301,6 @@ def x(self): ...
self.assertNotIsSubclass(C, Protocol)
self.assertNotIsInstance(C(), Protocol)
- # TODO: RUSTPYTHON
- @unittest.expectedFailure
def test_protocols_issubclass_non_callable(self):
class C:
x = 1
@@ -3516,8 +3360,6 @@ def __init__(self) -> None:
):
issubclass(Eggs, Spam)
- # TODO: RUSTPYTHON
- @unittest.expectedFailure
def test_no_weird_caching_with_issubclass_after_isinstance_2(self):
@runtime_checkable
class Spam(Protocol):
@@ -3538,8 +3380,6 @@ class Eggs: ...
):
issubclass(Eggs, Spam)
- # TODO: RUSTPYTHON
- @unittest.expectedFailure
def test_no_weird_caching_with_issubclass_after_isinstance_3(self):
@runtime_checkable
class Spam(Protocol):
@@ -3567,9 +3407,9 @@ def __getattr__(self, attr):
# TODO: RUSTPYTHON
@unittest.expectedFailure
def test_no_weird_caching_with_issubclass_after_isinstance_pep695(self):
- # @runtime_checkable
- # class Spam[T](Protocol):
- # x: T
+ @runtime_checkable
+ class Spam[T](Protocol):
+ x: T
class Eggs[T]:
def __init__(self, x: T) -> None:
@@ -3593,8 +3433,6 @@ def __init__(self, x: T) -> None:
class GenericTests(BaseTestCase):
- # TODO: RUSTPYTHON
- @unittest.expectedFailure
def test_basics(self):
X = SimpleMapping[str, Any]
self.assertEqual(X.__parameters__, ())
@@ -3614,8 +3452,6 @@ def test_basics(self):
T = TypeVar("T")
self.assertEqual(List[list[T] | float].__parameters__, (T,))
- # TODO: RUSTPYTHON
- @unittest.expectedFailure
def test_generic_errors(self):
T = TypeVar('T')
S = TypeVar('S')
@@ -3641,8 +3477,6 @@ class D(Generic[T]): pass
with self.assertRaises(TypeError):
D[()]
- # TODO: RUSTPYTHON
- @unittest.expectedFailure
def test_generic_subclass_checks(self):
for typ in [list[int], List[int],
tuple[int, str], Tuple[int, str],
@@ -3659,8 +3493,6 @@ def test_generic_subclass_checks(self):
# but, not when the right arg is also a generic:
self.assertRaises(TypeError, isinstance, typ, typ)
- # TODO: RUSTPYTHON
- @unittest.expectedFailure
def test_init(self):
T = TypeVar('T')
S = TypeVar('S')
@@ -3695,8 +3527,6 @@ def test_repr(self):
self.assertEqual(repr(MySimpleMapping),
f"")
- # TODO: RUSTPYTHON
- @unittest.expectedFailure
def test_chain_repr(self):
T = TypeVar('T')
S = TypeVar('S')
@@ -3721,8 +3551,6 @@ class C(Generic[T]):
self.assertTrue(str(Z).endswith(
'.C[typing.Tuple[str, int]]'))
- # TODO: RUSTPYTHON
- @unittest.expectedFailure
def test_new_repr(self):
T = TypeVar('T')
U = TypeVar('U', covariant=True)
@@ -3734,8 +3562,6 @@ def test_new_repr(self):
self.assertEqual(repr(List[S][T][int]), 'typing.List[int]')
self.assertEqual(repr(List[int]), 'typing.List[int]')
- # TODO: RUSTPYTHON
- @unittest.expectedFailure
def test_new_repr_complex(self):
T = TypeVar('T')
TS = TypeVar('TS')
@@ -3748,8 +3574,6 @@ def test_new_repr_complex(self):
'typing.List[typing.Tuple[typing.List[int], typing.List[int]]]'
)
- # TODO: RUSTPYTHON
- @unittest.expectedFailure
def test_new_repr_bare(self):
T = TypeVar('T')
self.assertEqual(repr(Generic[T]), 'typing.Generic[~T]')
@@ -3775,8 +3599,6 @@ class C(B[int]):
c.bar = 'abc'
self.assertEqual(c.__dict__, {'bar': 'abc'})
- # TODO: RUSTPYTHON
- @unittest.expectedFailure
def test_setattr_exceptions(self):
class Immutable[T]:
def __setattr__(self, key, value):
@@ -3787,8 +3609,6 @@ def __setattr__(self, key, value):
# returned by the `Immutable[int]()` call
self.assertIsInstance(Immutable[int](), Immutable)
- # TODO: RUSTPYTHON
- @unittest.expectedFailure
def test_subscripted_generics_as_proxies(self):
T = TypeVar('T')
class C(Generic[T]):
@@ -3862,8 +3682,6 @@ def test_orig_bases(self):
class C(typing.Dict[str, T]): ...
self.assertEqual(C.__orig_bases__, (typing.Dict[str, T],))
- # TODO: RUSTPYTHON
- @unittest.expectedFailure
def test_naive_runtime_checks(self):
def naive_dict_check(obj, tp):
# Check if a dictionary conforms to Dict type
@@ -3900,8 +3718,6 @@ class C(List[int]): ...
self.assertTrue(naive_list_base_check([1, 2, 3], C))
self.assertFalse(naive_list_base_check(['a', 'b'], C))
- # TODO: RUSTPYTHON
- @unittest.expectedFailure
def test_multi_subscr_base(self):
T = TypeVar('T')
U = TypeVar('U')
@@ -3919,8 +3735,6 @@ class D(C, List[T][U][V]): ...
self.assertEqual(C.__orig_bases__, (List[T][U][V],))
self.assertEqual(D.__orig_bases__, (C, List[T][U][V]))
- # TODO: RUSTPYTHON
- @unittest.expectedFailure
def test_subscript_meta(self):
T = TypeVar('T')
class Meta(type): ...
@@ -3928,8 +3742,6 @@ class Meta(type): ...
self.assertEqual(Union[T, int][Meta], Union[Meta, int])
self.assertEqual(Callable[..., Meta].__args__, (Ellipsis, Meta))
- # TODO: RUSTPYTHON
- @unittest.expectedFailure
def test_generic_hashes(self):
class A(Generic[T]):
...
@@ -3972,8 +3784,6 @@ class A(Generic[T]):
self.assertTrue(repr(Tuple[mod_generics_cache.B.A[str]])
.endswith('mod_generics_cache.B.A[str]]'))
- # TODO: RUSTPYTHON
- @unittest.expectedFailure
def test_extended_generic_rules_eq(self):
T = TypeVar('T')
U = TypeVar('U')
@@ -3990,8 +3800,6 @@ class Derived(Base): ...
self.assertEqual(Callable[[T], T][KT], Callable[[KT], KT])
self.assertEqual(Callable[..., List[T]][int], Callable[..., List[int]])
- # TODO: RUSTPYTHON
- @unittest.expectedFailure
def test_extended_generic_rules_repr(self):
T = TypeVar('T')
self.assertEqual(repr(Union[Tuple, Callable]).replace('typing.', ''),
@@ -4003,8 +3811,6 @@ def test_extended_generic_rules_repr(self):
self.assertEqual(repr(Callable[[], List[T]][int]).replace('typing.', ''),
'Callable[[], List[int]]')
- # TODO: RUSTPYTHON
- @unittest.expectedFailure
def test_generic_forward_ref(self):
def foobar(x: List[List['CC']]): ...
def foobar2(x: list[list[ForwardRef('CC')]]): ...
@@ -4031,8 +3837,6 @@ def barfoo(x: AT): ...
def barfoo2(x: CT): ...
self.assertIs(get_type_hints(barfoo2, globals(), locals())['x'], CT)
- # TODO: RUSTPYTHON
- @unittest.expectedFailure
def test_generic_pep585_forward_ref(self):
# See https://bugs.python.org/issue41370
@@ -4072,8 +3876,6 @@ def f(x: X): ...
{'x': list[list[ForwardRef('X')]]}
)
- # TODO: RUSTPYTHON
- @unittest.expectedFailure
def test_pep695_generic_class_with_future_annotations(self):
original_globals = dict(ann_module695.__dict__)
@@ -4086,14 +3888,10 @@ def test_pep695_generic_class_with_future_annotations(self):
# should not have changed as a result of the get_type_hints() calls!
self.assertEqual(ann_module695.__dict__, original_globals)
- # TODO: RUSTPYTHON
- @unittest.expectedFailure
def test_pep695_generic_class_with_future_annotations_and_local_shadowing(self):
hints_for_B = get_type_hints(ann_module695.B)
self.assertEqual(hints_for_B, {"x": int, "y": str, "z": bytes})
- # TODO: RUSTPYTHON
- @unittest.expectedFailure
def test_pep695_generic_class_with_future_annotations_name_clash_with_global_vars(self):
hints_for_C = get_type_hints(ann_module695.C)
self.assertEqual(
@@ -4101,8 +3899,6 @@ def test_pep695_generic_class_with_future_annotations_name_clash_with_global_var
set(ann_module695.C.__type_params__)
)
- # TODO: RUSTPYTHON
- @unittest.expectedFailure
def test_pep_695_generic_function_with_future_annotations(self):
hints_for_generic_function = get_type_hints(ann_module695.generic_function)
func_t_params = ann_module695.generic_function.__type_params__
@@ -4137,8 +3933,6 @@ def test_pep_695_generic_method_with_future_annotations_name_clash_with_global_v
set(ann_module695.D.generic_method_2.__type_params__)
)
- # TODO: RUSTPYTHON
- @unittest.expectedFailure
def test_pep_695_generics_with_future_annotations_nested_in_function(self):
results = ann_module695.nested()
@@ -4164,8 +3958,6 @@ def test_pep_695_generics_with_future_annotations_nested_in_function(self):
set(results.generic_func.__type_params__)
)
- # TODO: RUSTPYTHON
- @unittest.expectedFailure
def test_extended_generic_rules_subclassing(self):
class T1(Tuple[T, KT]): ...
class T2(Tuple[T, ...]): ...
@@ -4203,8 +3995,6 @@ def test_fail_with_bare_union(self):
with self.assertRaises(TypeError):
List[ClassVar[int]]
- # TODO: RUSTPYTHON
- @unittest.expectedFailure
def test_fail_with_bare_generic(self):
T = TypeVar('T')
with self.assertRaises(TypeError):
@@ -4231,8 +4021,6 @@ class MyChain(typing.ChainMap[str, T]): ...
self.assertIs(MyChain[int]().__class__, MyChain)
self.assertEqual(MyChain[int]().__orig_class__, MyChain[int])
- # TODO: RUSTPYTHON
- @unittest.expectedFailure
def test_all_repr_eq_any(self):
objs = (getattr(typing, el) for el in typing.__all__)
for obj in objs:
@@ -4298,8 +4086,6 @@ class C(B[int]):
)
del PP
- # TODO: RUSTPYTHON
- @unittest.expectedFailure
def test_copy_and_deepcopy(self):
T = TypeVar('T')
class Node(Generic[T]): ...
@@ -4313,8 +4099,6 @@ class Node(Generic[T]): ...
self.assertEqual(t, copy(t))
self.assertEqual(t, deepcopy(t))
- # TODO: RUSTPYTHON
- @unittest.expectedFailure
def test_immutability_by_copy_and_pickle(self):
# Special forms like Union, Any, etc., generic aliases to containers like List,
# Mapping, etc., and type variabcles are considered immutable by copy and pickle.
@@ -4413,8 +4197,6 @@ class D(Generic[T]):
with self.assertRaises(AttributeError):
d_int.foobar = 'no'
- # TODO: RUSTPYTHON
- @unittest.expectedFailure
def test_errors(self):
with self.assertRaises(TypeError):
B = SimpleMapping[XK, Any]
@@ -4440,8 +4222,6 @@ class Y(C[int]):
self.assertEqual(Y.__qualname__,
'GenericTests.test_repr_2..Y')
- # TODO: RUSTPYTHON
- @unittest.expectedFailure
def test_repr_3(self):
T = TypeVar('T')
T1 = TypeVar('T1')
@@ -4505,8 +4285,6 @@ class B(Generic[T]):
self.assertEqual(A[T], A[T])
self.assertNotEqual(A[T], B[T])
- # TODO: RUSTPYTHON
- @unittest.expectedFailure
def test_multiple_inheritance(self):
class A(Generic[T, VT]):
@@ -4572,8 +4350,6 @@ class A(typing.Sized, list[int]): ...
(A, collections.abc.Sized, Generic, list, object),
)
- # TODO: RUSTPYTHON
- @unittest.expectedFailure
def test_multiple_inheritance_with_genericalias_2(self):
T = TypeVar("T")
@@ -4670,8 +4446,6 @@ def foo(x: T):
foo(42)
- # TODO: RUSTPYTHON
- @unittest.expectedFailure
def test_implicit_any(self):
T = TypeVar('T')
@@ -4791,8 +4565,6 @@ class Base(Generic[T_co]):
class Sub(Base, Generic[T]):
...
- # TODO: RUSTPYTHON
- @unittest.expectedFailure
def test_parameter_detection(self):
self.assertEqual(List[T].__parameters__, (T,))
self.assertEqual(List[List[T]].__parameters__, (T,))
@@ -4810,8 +4582,6 @@ class A:
# C version of GenericAlias
self.assertEqual(list[A()].__parameters__, (T,))
- # TODO: RUSTPYTHON
- @unittest.expectedFailure
def test_non_generic_subscript(self):
T = TypeVar('T')
class G(Generic[T]):
@@ -4897,8 +4667,6 @@ def test_basics(self):
with self.assertRaises(TypeError):
Optional[Final[int]]
- # TODO: RUSTPYTHON
- @unittest.expectedFailure
def test_repr(self):
self.assertEqual(repr(Final), 'typing.Final')
cv = Final[int]
@@ -5197,8 +4965,6 @@ class NoTypeCheck_WithFunction:
class ForwardRefTests(BaseTestCase):
- # TODO: RUSTPYTHON
- @unittest.expectedFailure
def test_basics(self):
class Node(Generic[T]):
@@ -5329,8 +5095,6 @@ def test_forward_repr(self):
self.assertEqual(repr(List[ForwardRef('int', module='mod')]),
"typing.List[ForwardRef('int', module='mod')]")
- # TODO: RUSTPYTHON
- @unittest.expectedFailure
def test_union_forward(self):
def foo(a: Union['T']):
@@ -5345,8 +5109,6 @@ def foo(a: tuple[ForwardRef('T')] | int):
self.assertEqual(get_type_hints(foo, globals(), locals()),
{'a': tuple[T] | int})
- # TODO: RUSTPYTHON
- @unittest.expectedFailure
def test_tuple_forward(self):
def foo(a: Tuple['T']):
@@ -5393,8 +5155,6 @@ def cmp(o1, o2):
self.assertIsNot(r1, r2)
self.assertRaises(RecursionError, cmp, r1, r2)
- # TODO: RUSTPYTHON
- @unittest.expectedFailure
def test_union_forward_recursion(self):
ValueList = List['Value']
Value = Union[str, ValueList]
@@ -5443,8 +5203,6 @@ def foo(a: 'Callable[..., T]'):
self.assertEqual(get_type_hints(foo, globals(), locals()),
{'a': Callable[..., T]})
- # TODO: RUSTPYTHON
- @unittest.expectedFailure
def test_special_forms_forward(self):
class C:
@@ -5463,8 +5221,6 @@ class CF:
with self.assertRaises(TypeError):
get_type_hints(CF, globals()),
- # TODO: RUSTPYTHON
- @unittest.expectedFailure
def test_syntax_error(self):
with self.assertRaises(SyntaxError):
@@ -5529,8 +5285,6 @@ def foo(self, x: int): ...
self.assertEqual(get_type_hints(Child.foo), {'x': int})
- # TODO: RUSTPYTHON
- @unittest.expectedFailure
def test_no_type_check_nested_types(self):
# See https://bugs.python.org/issue46571
class Other:
@@ -5599,8 +5353,6 @@ class A:
some.__no_type_check__
self.assertEqual(get_type_hints(some), {'args': int, 'return': int})
- # TODO: RUSTPYTHON
- @unittest.expectedFailure
def test_no_type_check_lambda(self):
@no_type_check
class A:
@@ -5615,8 +5367,6 @@ def test_no_type_check_TypeError(self):
# `TypeError: can't set attributes of built-in/extension type 'dict'`
no_type_check(dict)
- # TODO: RUSTPYTHON
- @unittest.expectedFailure
def test_no_type_check_forward_ref_as_string(self):
class C:
foo: typing.ClassVar[int] = 7
@@ -5671,8 +5421,6 @@ def test_default_globals(self):
hints = get_type_hints(ns['C'].foo)
self.assertEqual(hints, {'a': ns['C'], 'return': ns['D']})
- # TODO: RUSTPYTHON
- @unittest.expectedFailure
def test_final_forward_ref(self):
self.assertEqual(gth(Loop, globals())['attr'], Final[Loop])
self.assertNotEqual(gth(Loop, globals())['attr'], Final[int])
@@ -5686,8 +5434,6 @@ def test_or(self):
class InternalsTests(BaseTestCase):
- # TODO: RUSTPYTHON
- @unittest.expectedFailure
def test_deprecation_for_no_type_params_passed_to__evaluate(self):
with self.assertWarnsRegex(
DeprecationWarning,
@@ -6040,8 +5786,6 @@ def test_get_type_hints_classes(self):
'my_inner_a2': mod_generics_cache.B.A,
'my_outer_a': mod_generics_cache.A})
- # TODO: RUSTPYTHON
- @unittest.expectedFailure
def test_get_type_hints_classes_no_implicit_optional(self):
class WithNoneDefault:
field: int = None # most type-checkers won't be happy with it
@@ -6086,8 +5830,6 @@ class B: ...
b.__annotations__ = {'x': 'A'}
self.assertEqual(gth(b, locals()), {'x': A})
- # TODO: RUSTPYTHON
- @unittest.expectedFailure
def test_get_type_hints_ClassVar(self):
self.assertEqual(gth(ann_module2.CV, ann_module2.__dict__),
{'var': typing.ClassVar[ann_module2.CV]})
@@ -6103,8 +5845,6 @@ def test_get_type_hints_wrapped_decoratored_func(self):
self.assertEqual(gth(ForRefExample.func), expects)
self.assertEqual(gth(ForRefExample.nested), expects)
- # TODO: RUSTPYTHON
- @unittest.expectedFailure
def test_get_type_hints_annotated(self):
def foobar(x: List['X']): ...
X = Annotated[int, (1, 10)]
@@ -6168,8 +5908,6 @@ def barfoo4(x: BA3): ...
{"x": typing.Annotated[int | float, "const"]}
)
- # TODO: RUSTPYTHON
- @unittest.expectedFailure
def test_get_type_hints_annotated_in_union(self): # bpo-46603
def with_union(x: int | list[Annotated[str, 'meta']]): ...
@@ -6179,8 +5917,6 @@ def with_union(x: int | list[Annotated[str, 'meta']]): ...
{'x': int | list[Annotated[str, 'meta']]},
)
- # TODO: RUSTPYTHON
- @unittest.expectedFailure
def test_get_type_hints_annotated_refs(self):
Const = Annotated[T, "Const"]
@@ -6220,8 +5956,6 @@ def annotated_with_none_default(x: Annotated[int, 'data'] = None): ...
{'x': Annotated[int, 'data']},
)
- # TODO: RUSTPYTHON
- @unittest.expectedFailure
def test_get_type_hints_classes_str_annotations(self):
class Foo:
y = str
@@ -6237,8 +5971,6 @@ class BadModule:
self.assertNotIn('bad', sys.modules)
self.assertEqual(get_type_hints(BadModule), {})
- # TODO: RUSTPYTHON
- @unittest.expectedFailure
def test_get_type_hints_annotated_bad_module(self):
# See https://bugs.python.org/issue44468
class BadBase:
@@ -6249,8 +5981,6 @@ class BadType(BadBase):
self.assertNotIn('bad', sys.modules)
self.assertEqual(get_type_hints(BadType), {'foo': tuple, 'bar': list})
- # TODO: RUSTPYTHON
- @unittest.expectedFailure
def test_forward_ref_and_final(self):
# https://bugs.python.org/issue45166
hints = get_type_hints(ann_module5)
@@ -6310,8 +6040,6 @@ def test_get_type_hints_typeddict(self):
"year": NotRequired[Annotated[int, 2000]]
})
- # TODO: RUSTPYTHON
- @unittest.expectedFailure
def test_get_type_hints_collections_abc_callable(self):
# https://github.com/python/cpython/issues/91621
P = ParamSpec('P')
@@ -6326,8 +6054,6 @@ def h(x: collections.abc.Callable[P, int]): ...
class GetUtilitiesTestCase(TestCase):
- # TODO: RUSTPYTHON
- @unittest.expectedFailure
def test_get_origin(self):
T = TypeVar('T')
Ts = TypeVarTuple('Ts')
@@ -6356,11 +6082,9 @@ class C(Generic[T]): pass
self.assertIs(get_origin(NotRequired[int]), NotRequired)
self.assertIs(get_origin((*Ts,)[0]), Unpack)
self.assertIs(get_origin(Unpack[Ts]), Unpack)
- # self.assertIs(get_origin((*tuple[*Ts],)[0]), tuple)
+ self.assertIs(get_origin((*tuple[*Ts],)[0]), tuple)
self.assertIs(get_origin(Unpack[Tuple[Unpack[Ts]]]), Unpack)
- # TODO: RUSTPYTHON
- @unittest.expectedFailure
def test_get_args(self):
T = TypeVar('T')
class C(Generic[T]): pass
@@ -6424,9 +6148,9 @@ class C(Generic[T]): pass
self.assertEqual(get_args(Ts), ())
self.assertEqual(get_args((*Ts,)[0]), (Ts,))
self.assertEqual(get_args(Unpack[Ts]), (Ts,))
- # self.assertEqual(get_args(tuple[*Ts]), (*Ts,))
+ self.assertEqual(get_args(tuple[*Ts]), (*Ts,))
self.assertEqual(get_args(tuple[Unpack[Ts]]), (Unpack[Ts],))
- # self.assertEqual(get_args((*tuple[*Ts],)[0]), (*Ts,))
+ self.assertEqual(get_args((*tuple[*Ts],)[0]), (*Ts,))
self.assertEqual(get_args(Unpack[tuple[Unpack[Ts]]]), (tuple[Unpack[Ts]],))
@@ -6557,8 +6281,6 @@ def test_frozenset(self):
def test_dict(self):
self.assertIsSubclass(dict, typing.Dict)
- # TODO: RUSTPYTHON
- @unittest.expectedFailure
def test_dict_subscribe(self):
K = TypeVar('K')
V = TypeVar('V')
@@ -6763,8 +6485,6 @@ def test_no_async_generator_instantiation(self):
with self.assertRaises(TypeError):
typing.AsyncGenerator[int, int]()
- # TODO: RUSTPYTHON
- @unittest.expectedFailure
def test_subclassing(self):
class MMA(typing.MutableMapping):
@@ -6927,8 +6647,6 @@ def manager():
self.assertIsInstance(cm, typing.ContextManager)
self.assertNotIsInstance(42, typing.ContextManager)
- # TODO: RUSTPYTHON
- @unittest.expectedFailure
def test_contextmanager_type_params(self):
cm1 = typing.ContextManager[int]
self.assertEqual(get_args(cm1), (int, bool | None))
@@ -7183,8 +6901,6 @@ class B(NamedTuple):
class C(NamedTuple, B):
y: str
- # TODO: RUSTPYTHON
- @unittest.expectedFailure
def test_generic(self):
class X(NamedTuple, Generic[T]):
x: T
@@ -7216,8 +6932,6 @@ class Y(Generic[T], NamedTuple):
with self.assertRaises(TypeError):
G[int, str]
- # TODO: RUSTPYTHON
- @unittest.expectedFailure
def test_generic_pep695(self):
class X[T](NamedTuple):
x: T
@@ -7793,8 +7507,6 @@ class ChildWithInlineAndOptional(Untotal, Inline):
class Wrong(*bases):
pass
- # TODO: RUSTPYTHON
- @unittest.expectedFailure
def test_is_typeddict(self):
self.assertIs(is_typeddict(Point2D), True)
self.assertIs(is_typeddict(Union[str, int]), False)
@@ -7844,8 +7556,6 @@ class FooBarGeneric(BarGeneric[int]):
{'a': typing.Optional[T], 'b': int, 'c': str}
)
- # TODO: RUSTPYTHON
- @unittest.expectedFailure
def test_pep695_generic_typeddict(self):
class A[T](TypedDict):
a: T
@@ -7860,8 +7570,6 @@ class A[T](TypedDict):
self.assertEqual(A[str].__parameters__, ())
self.assertEqual(A[str].__args__, (str,))
- # TODO: RUSTPYTHON
- @unittest.expectedFailure
def test_generic_inheritance(self):
class A(TypedDict, Generic[T]):
a: T
@@ -7941,8 +7649,6 @@ class Point3D(Point2DGeneric[T], Generic[T, KT]):
class Point3D(Point2DGeneric[T], Generic[KT]):
c: KT
- # TODO: RUSTPYTHON
- @unittest.expectedFailure
def test_implicit_any_inheritance(self):
class A(TypedDict, Generic[T]):
a: T
@@ -8224,8 +7930,6 @@ def test_no_isinstance(self):
class IOTests(BaseTestCase):
- # TODO: RUSTPYTHON
- @unittest.expectedFailure
def test_io(self):
def stuff(a: IO) -> AnyStr:
@@ -8234,8 +7938,6 @@ def stuff(a: IO) -> AnyStr:
a = stuff.__annotations__['a']
self.assertEqual(a.__parameters__, (AnyStr,))
- # TODO: RUSTPYTHON
- @unittest.expectedFailure
def test_textio(self):
def stuff(a: TextIO) -> str:
@@ -8244,8 +7946,6 @@ def stuff(a: TextIO) -> str:
a = stuff.__annotations__['a']
self.assertEqual(a.__parameters__, ())
- # TODO: RUSTPYTHON
- @unittest.expectedFailure
def test_binaryio(self):
def stuff(a: BinaryIO) -> bytes:
@@ -8419,8 +8119,6 @@ def test_order_in_union(self):
with self.subTest(args=args):
self.assertEqual(expr2, Union[args])
- # TODO: RUSTPYTHON
- @unittest.expectedFailure
def test_specialize(self):
L = Annotated[List[T], "my decoration"]
LI = Annotated[List[int], "my decoration"]
@@ -8471,8 +8169,6 @@ def __eq__(self, other):
self.assertEqual(a.x, c.x)
self.assertEqual(a.classvar, c.classvar)
- # TODO: RUSTPYTHON
- @unittest.expectedFailure
def test_instantiate_generic(self):
MyCount = Annotated[typing.Counter[T], "my decoration"]
self.assertEqual(MyCount([4, 4, 5]), {4: 2, 5: 1})
@@ -8512,8 +8208,6 @@ class C:
A.x = 5
self.assertEqual(C.x, 5)
- # TODO: RUSTPYTHON
- @unittest.expectedFailure
def test_special_form_containment(self):
class C:
classvar: Annotated[ClassVar[int], "a decoration"] = 4
@@ -8522,8 +8216,6 @@ class C:
self.assertEqual(get_type_hints(C, globals())['classvar'], ClassVar[int])
self.assertEqual(get_type_hints(C, globals())['const'], Final[int])
- # TODO: RUSTPYTHON
- @unittest.expectedFailure
def test_special_forms_nesting(self):
# These are uncommon types and are to ensure runtime
# is lax on validation. See gh-89547 for more context.
@@ -8569,8 +8261,6 @@ def test_too_few_type_args(self):
with self.assertRaisesRegex(TypeError, 'at least two arguments'):
Annotated[int]
- # TODO: RUSTPYTHON
- @unittest.expectedFailure
def test_pickle(self):
samples = [typing.Any, typing.Union[int, str],
typing.Optional[str], Tuple[int, ...],
@@ -8601,8 +8291,6 @@ class _Annotated_test_G(Generic[T]):
self.assertEqual(x.bar, 'abc')
self.assertEqual(x.x, 1)
- # TODO: RUSTPYTHON
- @unittest.expectedFailure
def test_subst(self):
dec = "a decoration"
dec2 = "another decoration"
@@ -8632,8 +8320,6 @@ def test_subst(self):
with self.assertRaises(TypeError):
LI[None]
- # TODO: RUSTPYTHON
- @unittest.expectedFailure
def test_typevar_subst(self):
dec = "a decoration"
Ts = TypeVarTuple('Ts')
@@ -8641,7 +8327,7 @@ def test_typevar_subst(self):
T1 = TypeVar('T1')
T2 = TypeVar('T2')
- # A = Annotated[tuple[*Ts], dec]
+ A = Annotated[tuple[*Ts], dec]
self.assertEqual(A[int], Annotated[tuple[int], dec])
self.assertEqual(A[str, int], Annotated[tuple[str, int], dec])
with self.assertRaises(TypeError):
@@ -8748,8 +8434,6 @@ def test_typevar_subst(self):
with self.assertRaises(TypeError):
J[int]
- # TODO: RUSTPYTHON
- @unittest.expectedFailure
def test_annotated_in_other_types(self):
X = List[Annotated[T, 5]]
self.assertEqual(X[int], List[Annotated[int, 5]])
@@ -8809,8 +8493,6 @@ def test_no_isinstance(self):
with self.assertRaises(TypeError):
isinstance(42, TypeAlias)
- # TODO: RUSTPYTHON
- @unittest.expectedFailure
def test_stringized_usage(self):
class A:
a: "TypeAlias"
@@ -8844,8 +8526,6 @@ def test_cannot_subscript(self):
class ParamSpecTests(BaseTestCase):
- # TODO: RUSTPYTHON
- @unittest.expectedFailure
def test_basic_plain(self):
P = ParamSpec('P')
self.assertEqual(P, P)
@@ -8853,8 +8533,6 @@ def test_basic_plain(self):
self.assertEqual(P.__name__, 'P')
self.assertEqual(P.__module__, __name__)
- # TODO: RUSTPYTHON
- @unittest.expectedFailure
def test_basic_with_exec(self):
ns = {}
exec('from typing import ParamSpec; P = ParamSpec("P")', ns, ns)
@@ -8863,8 +8541,6 @@ def test_basic_with_exec(self):
self.assertEqual(P.__name__, 'P')
self.assertIs(P.__module__, None)
- # TODO: RUSTPYTHON
- @unittest.expectedFailure
def test_valid_uses(self):
P = ParamSpec('P')
T = TypeVar('T')
@@ -8904,8 +8580,6 @@ def test_args_kwargs(self):
self.assertEqual(repr(P.kwargs), "P.kwargs")
- # TODO: RUSTPYTHON
- @unittest.expectedFailure
def test_stringized(self):
P = ParamSpec('P')
class C(Generic[P]):
@@ -8918,8 +8592,6 @@ def foo(self, *args: "P.args", **kwargs: "P.kwargs"):
gth(C.foo, globals(), locals()), {"args": P.args, "kwargs": P.kwargs}
)
- # TODO: RUSTPYTHON
- @unittest.expectedFailure
def test_user_generics(self):
T = TypeVar("T")
P = ParamSpec("P")
@@ -8974,8 +8646,6 @@ class Z(Generic[P]):
with self.assertRaisesRegex(TypeError, "many arguments for"):
Z[P_2, bool]
- # TODO: RUSTPYTHON
- @unittest.expectedFailure
def test_multiple_paramspecs_in_user_generics(self):
P = ParamSpec("P")
P2 = ParamSpec("P2")
@@ -8990,15 +8660,13 @@ class X(Generic[P, P2]):
self.assertEqual(G1.__args__, ((int, str), (bytes,)))
self.assertEqual(G2.__args__, ((int,), (str, bytes)))
- # TODO: RUSTPYTHON
- @unittest.expectedFailure
def test_typevartuple_and_paramspecs_in_user_generics(self):
Ts = TypeVarTuple("Ts")
P = ParamSpec("P")
- # class X(Generic[*Ts, P]):
- # f: Callable[P, int]
- # g: Tuple[*Ts]
+ class X(Generic[*Ts, P]):
+ f: Callable[P, int]
+ g: Tuple[*Ts]
G1 = X[int, [bytes]]
self.assertEqual(G1.__args__, (int, (bytes,)))
@@ -9011,9 +8679,9 @@ def test_typevartuple_and_paramspecs_in_user_generics(self):
with self.assertRaises(TypeError):
X[()]
- # class Y(Generic[P, *Ts]):
- # f: Callable[P, int]
- # g: Tuple[*Ts]
+ class Y(Generic[P, *Ts]):
+ f: Callable[P, int]
+ g: Tuple[*Ts]
G1 = Y[[bytes], int]
self.assertEqual(G1.__args__, ((bytes,), int))
@@ -9026,8 +8694,6 @@ def test_typevartuple_and_paramspecs_in_user_generics(self):
with self.assertRaises(TypeError):
Y[()]
- # TODO: RUSTPYTHON
- @unittest.expectedFailure
def test_typevartuple_and_paramspecs_in_generic_aliases(self):
P = ParamSpec('P')
T = TypeVar('T')
@@ -9035,26 +8701,24 @@ def test_typevartuple_and_paramspecs_in_generic_aliases(self):
for C in Callable, collections.abc.Callable:
with self.subTest(generic=C):
- # A = C[P, Tuple[*Ts]]
+ A = C[P, Tuple[*Ts]]
B = A[[int, str], bytes, float]
self.assertEqual(B.__args__, (int, str, Tuple[bytes, float]))
class X(Generic[T, P]):
pass
- # A = X[Tuple[*Ts], P]
+ A = X[Tuple[*Ts], P]
B = A[bytes, float, [int, str]]
self.assertEqual(B.__args__, (Tuple[bytes, float], (int, str,)))
class Y(Generic[P, T]):
pass
- # A = Y[P, Tuple[*Ts]]
+ A = Y[P, Tuple[*Ts]]
B = A[[int, str], bytes, float]
self.assertEqual(B.__args__, ((int, str,), Tuple[bytes, float]))
- # TODO: RUSTPYTHON
- @unittest.expectedFailure
def test_var_substitution(self):
P = ParamSpec("P")
subst = P.__typing_subst__
@@ -9065,8 +8729,6 @@ def test_var_substitution(self):
self.assertIs(subst(P), P)
self.assertEqual(subst(Concatenate[int, P]), Concatenate[int, P])
- # TODO: RUSTPYTHON
- @unittest.expectedFailure
def test_bad_var_substitution(self):
T = TypeVar('T')
P = ParamSpec('P')
@@ -9080,8 +8742,6 @@ def test_bad_var_substitution(self):
with self.assertRaises(TypeError):
collections.abc.Callable[P, T][arg, str]
- # TODO: RUSTPYTHON
- @unittest.expectedFailure
def test_type_var_subst_for_other_type_vars(self):
T = TypeVar('T')
T2 = TypeVar('T2')
@@ -9137,10 +8797,10 @@ class Base(Generic[P]):
self.assertEqual(A8.__args__, ((T, list[T]),))
self.assertEqual(A8[int], Base[[int, list[int]]])
- # A9 = Base[[Tuple[*Ts], *Ts]]
- # self.assertEqual(A9.__parameters__, (Ts,))
- # self.assertEqual(A9.__args__, ((Tuple[*Ts], *Ts),))
- # self.assertEqual(A9[int, str], Base[Tuple[int, str], int, str])
+ A9 = Base[[Tuple[*Ts], *Ts]]
+ self.assertEqual(A9.__parameters__, (Ts,))
+ self.assertEqual(A9.__args__, ((Tuple[*Ts], *Ts),))
+ self.assertEqual(A9[int, str], Base[Tuple[int, str], int, str])
A10 = Base[P2]
self.assertEqual(A10.__parameters__, (P2,))
@@ -9203,8 +8863,6 @@ class PandT(Generic[P, T]):
self.assertEqual(C3.__args__, ((int, *Ts), T))
self.assertEqual(C3[str, bool, bytes], PandT[[int, str, bool], bytes])
- # TODO: RUSTPYTHON
- @unittest.expectedFailure
def test_paramspec_in_nested_generics(self):
# Although ParamSpec should not be found in __parameters__ of most
# generics, they probably should be found when nested in
@@ -9223,8 +8881,6 @@ def test_paramspec_in_nested_generics(self):
self.assertEqual(G2[[int, str], float], list[C])
self.assertEqual(G3[[int, str], float], list[C] | int)
- # TODO: RUSTPYTHON
- @unittest.expectedFailure
def test_paramspec_gets_copied(self):
# bpo-46581
P = ParamSpec('P')
@@ -9246,8 +8902,6 @@ def test_paramspec_gets_copied(self):
self.assertEqual(C2[Concatenate[str, P2]].__parameters__, (P2,))
self.assertEqual(C2[Concatenate[T, P2]].__parameters__, (T, P2))
- # TODO: RUSTPYTHON
- @unittest.expectedFailure
def test_cannot_subclass(self):
with self.assertRaisesRegex(TypeError, NOT_A_BASE_TYPE % 'ParamSpec'):
class C(ParamSpec): pass
@@ -9284,8 +8938,6 @@ def test_dir(self):
with self.subTest(required_item=required_item):
self.assertIn(required_item, dir_items)
- # TODO: RUSTPYTHON
- @unittest.expectedFailure
def test_valid_uses(self):
P = ParamSpec('P')
T = TypeVar('T')
@@ -9316,8 +8968,6 @@ def test_invalid_uses(self):
):
Concatenate[int]
- # TODO: RUSTPYTHON
- @unittest.expectedFailure
def test_var_substitution(self):
T = TypeVar('T')
P = ParamSpec('P')
@@ -9349,8 +8999,6 @@ def foo(arg) -> TypeGuard[int]: ...
with self.assertRaises(TypeError):
TypeGuard[int, str]
- # TODO: RUSTPYTHON
- @unittest.expectedFailure
def test_repr(self):
self.assertEqual(repr(TypeGuard), 'typing.TypeGuard')
cv = TypeGuard[int]
@@ -9401,8 +9049,6 @@ def foo(arg) -> TypeIs[int]: ...
with self.assertRaises(TypeError):
TypeIs[int, str]
- # TODO: RUSTPYTHON
- @unittest.expectedFailure
def test_repr(self):
self.assertEqual(repr(TypeIs), 'typing.TypeIs')
cv = TypeIs[int]
@@ -9448,8 +9094,6 @@ def test_no_isinstance(self):
class SpecialAttrsTests(BaseTestCase):
- # TODO: RUSTPYTHON
- @unittest.expectedFailure
def test_special_attrs(self):
cls_to_check = {
# ABC classes
@@ -9632,8 +9276,6 @@ def test_special_attrs2(self):
loaded = pickle.loads(s)
self.assertIs(SpecialAttrsP, loaded)
- # TODO: RUSTPYTHON
- @unittest.expectedFailure
def test_genericalias_dir(self):
class Foo(Generic[T]):
def bar(self):
@@ -9750,21 +9392,19 @@ class CustomerModel(ModelBase, init=False):
class NoDefaultTests(BaseTestCase):
+ # TODO: RUSTPYTHON
+ @unittest.expectedFailure
def test_pickling(self):
for proto in range(pickle.HIGHEST_PROTOCOL + 1):
s = pickle.dumps(NoDefault, proto)
loaded = pickle.loads(s)
self.assertIs(NoDefault, loaded)
- # TODO: RUSTPYTHON
- @unittest.expectedFailure
def test_constructor(self):
self.assertIs(NoDefault, type(NoDefault)())
with self.assertRaises(TypeError):
type(NoDefault)(1)
- # TODO: RUSTPYTHON
- @unittest.expectedFailure
def test_repr(self):
self.assertEqual(repr(NoDefault), 'typing.NoDefault')
@@ -9775,8 +9415,6 @@ def test_doc(self):
def test_class(self):
self.assertIs(NoDefault.__class__, type(NoDefault))
- # TODO: RUSTPYTHON
- @unittest.expectedFailure
def test_no_call(self):
with self.assertRaises(TypeError):
NoDefault()
@@ -9821,8 +9459,6 @@ def test_all(self):
self.assertIn('SupportsBytes', a)
self.assertIn('SupportsComplex', a)
- # TODO: RUSTPYTHON
- @unittest.expectedFailure
def test_all_exported_names(self):
# ensure all dynamically created objects are actualised
for name in typing.__all__:
diff --git a/Lib/test/test_unicode_file.py b/Lib/test/test_unicode_file.py
index 80c22c6cdd..fe25bfe9f8 100644
--- a/Lib/test/test_unicode_file.py
+++ b/Lib/test/test_unicode_file.py
@@ -110,7 +110,7 @@ def _test_single(self, filename):
os.unlink(filename)
self.assertTrue(not os.path.exists(filename))
# and again with os.open.
- f = os.open(filename, os.O_CREAT)
+ f = os.open(filename, os.O_CREAT | os.O_WRONLY)
os.close(f)
try:
self._do_single(filename)
diff --git a/Lib/test/test_unicode_file_functions.py b/Lib/test/test_unicode_file_functions.py
index 47619c8807..25c16e3a0b 100644
--- a/Lib/test/test_unicode_file_functions.py
+++ b/Lib/test/test_unicode_file_functions.py
@@ -5,7 +5,7 @@
import unittest
import warnings
from unicodedata import normalize
-from test.support import os_helper
+from test.support import is_apple, os_helper
from test import support
@@ -23,13 +23,13 @@
'10_\u1fee\u1ffd',
]
-# Mac OS X decomposes Unicode names, using Normal Form D.
+# Apple platforms decompose Unicode names, using Normal Form D.
# http://developer.apple.com/mac/library/qa/qa2001/qa1173.html
# "However, most volume formats do not follow the exact specification for
# these normal forms. For example, HFS Plus uses a variant of Normal Form D
# in which U+2000 through U+2FFF, U+F900 through U+FAFF, and U+2F800 through
# U+2FAFF are not decomposed."
-if sys.platform != 'darwin':
+if not is_apple:
filenames.extend([
# Specific code points: NFC(fn), NFD(fn), NFKC(fn) and NFKD(fn) all different
'11_\u0385\u03d3\u03d4',
@@ -119,11 +119,11 @@ def test_open(self):
os.stat(name)
self._apply_failure(os.listdir, name, self._listdir_failure)
- # Skip the test on darwin, because darwin does normalize the filename to
+ # Skip the test on Apple platforms, because they don't normalize the filename to
# NFD (a variant of Unicode NFD form). Normalize the filename to NFC, NFKC,
# NFKD in Python is useless, because darwin will normalize it later and so
# open(), os.stat(), etc. don't raise any exception.
- @unittest.skipIf(sys.platform == 'darwin', 'irrelevant test on Mac OS X')
+ @unittest.skipIf(is_apple, 'irrelevant test on Apple platforms')
@unittest.skipIf(
support.is_emscripten or support.is_wasi,
"test fails on Emscripten/WASI when host platform is macOS."
@@ -142,10 +142,10 @@ def test_normalize(self):
self._apply_failure(os.remove, name)
self._apply_failure(os.listdir, name)
- # Skip the test on darwin, because darwin uses a normalization different
+ # Skip the test on Apple platforms, because they use a normalization different
# than Python NFD normalization: filenames are different even if we use
# Python NFD normalization.
- @unittest.skipIf(sys.platform == 'darwin', 'irrelevant test on Mac OS X')
+ @unittest.skipIf(is_apple, 'irrelevant test on Apple platforms')
def test_listdir(self):
sf0 = set(self.files)
with warnings.catch_warnings():
diff --git a/Lib/test/test_unicode_identifiers.py b/Lib/test/test_unicode_identifiers.py
index d7a0ece253..60cfdaabe8 100644
--- a/Lib/test/test_unicode_identifiers.py
+++ b/Lib/test/test_unicode_identifiers.py
@@ -21,7 +21,7 @@ def test_non_bmp_normalized(self):
@unittest.expectedFailure
def test_invalid(self):
try:
- from test import badsyntax_3131
+ from test.tokenizedata import badsyntax_3131
except SyntaxError as err:
self.assertEqual(str(err),
"invalid character '€' (U+20AC) (badsyntax_3131.py, line 2)")
diff --git a/Lib/test/test_unicodedata.py b/Lib/test/test_unicodedata.py
index c9e0b234ef..7f49c1690f 100644
--- a/Lib/test/test_unicodedata.py
+++ b/Lib/test/test_unicodedata.py
@@ -11,15 +11,20 @@
import sys
import unicodedata
import unittest
-from test.support import (open_urlresource, requires_resource, script_helper,
- cpython_only, check_disallow_instantiation,
- ResourceDenied)
+from test.support import (
+ open_urlresource,
+ requires_resource,
+ script_helper,
+ cpython_only,
+ check_disallow_instantiation,
+ force_not_colorized,
+)
class UnicodeMethodsTest(unittest.TestCase):
# update this, if the database changes
- expectedchecksum = '4739770dd4d0e5f1b1677accfc3552ed3c8ef326'
+ expectedchecksum = '63aa77dcb36b0e1df082ee2a6071caeda7f0955e'
# TODO: RUSTPYTHON
@unittest.expectedFailure
@@ -74,7 +79,8 @@ class UnicodeFunctionsTest(UnicodeDatabaseTest):
# Update this if the database changes. Make sure to do a full rebuild
# (e.g. 'make distclean && make') to get the correct checksum.
- expectedchecksum = '98d602e1f69d5c5bb8a5910c40bbbad4e18e8370'
+ expectedchecksum = '232affd2a50ec4bd69d2482aa0291385cbdefaba'
+
# TODO: RUSTPYTHON
@unittest.expectedFailure
@requires_resource('cpu')
@@ -94,6 +100,8 @@ def test_function_checksum(self):
self.db.decomposition(char),
str(self.db.mirrored(char)),
str(self.db.combining(char)),
+ unicodedata.east_asian_width(char),
+ self.db.name(char, ""),
]
h.update(''.join(data).encode("ascii"))
result = h.hexdigest()
@@ -106,6 +114,28 @@ def test_name_inverse_lookup(self):
if looked_name := self.db.name(char, None):
self.assertEqual(self.db.lookup(looked_name), char)
+ def test_no_names_in_pua(self):
+ puas = [*range(0xe000, 0xf8ff),
+ *range(0xf0000, 0xfffff),
+ *range(0x100000, 0x10ffff)]
+ for i in puas:
+ char = chr(i)
+ self.assertRaises(ValueError, self.db.name, char)
+
+ # TODO: RUSTPYTHON; LookupError: undefined character name 'LATIN SMLL LETR A'
+ @unittest.expectedFailure
+ def test_lookup_nonexistant(self):
+ # just make sure that lookup can fail
+ for nonexistant in [
+ "LATIN SMLL LETR A",
+ "OPEN HANDS SIGHS",
+ "DREGS",
+ "HANDBUG",
+ "MODIFIER LETTER CYRILLIC SMALL QUESTION MARK",
+ "???",
+ ]:
+ self.assertRaises(KeyError, self.db.lookup, nonexistant)
+
# TODO: RUSTPYTHON
@unittest.expectedFailure
def test_digit(self):
@@ -179,8 +209,6 @@ def test_decomposition(self):
self.assertRaises(TypeError, self.db.decomposition)
self.assertRaises(TypeError, self.db.decomposition, 'xx')
- # TODO: RUSTPYTHON
- @unittest.expectedFailure
def test_mirrored(self):
self.assertEqual(self.db.mirrored('\uFFFE'), 0)
self.assertEqual(self.db.mirrored('a'), 0)
@@ -247,6 +275,25 @@ def test_east_asian_width(self):
self.assertEqual(eaw('\u2010'), 'A')
self.assertEqual(eaw('\U00020000'), 'W')
+ # TODO: RUSTPYTHON
+ @unittest.expectedFailure
+ def test_east_asian_width_unassigned(self):
+ eaw = self.db.east_asian_width
+ # unassigned
+ for char in '\u0530\u0ecf\u10c6\u20fc\uaaca\U000107bd\U000115f2':
+ self.assertEqual(eaw(char), 'N')
+ self.assertIs(self.db.name(char, None), None)
+
+ # unassigned but reserved for CJK
+ for char in '\uFA6E\uFADA\U0002A6E0\U0002FA20\U0003134B\U0003FFFD':
+ self.assertEqual(eaw(char), 'W')
+ self.assertIs(self.db.name(char, None), None)
+
+ # private use areas
+ for char in '\uE000\uF800\U000F0000\U000FFFEE\U00100000\U0010FFF0':
+ self.assertEqual(eaw(char), 'A')
+ self.assertIs(self.db.name(char, None), None)
+
# TODO: RUSTPYTHON
@unittest.expectedFailure
def test_east_asian_width_9_0_changes(self):
@@ -262,6 +309,7 @@ def test_disallow_instantiation(self):
# TODO: RUSTPYTHON
@unittest.expectedFailure
+ @force_not_colorized
def test_failed_import_during_compiling(self):
# Issue 4367
# Decoding \N escapes requires the unicodedata module. If it can't be
@@ -324,6 +372,7 @@ def test_ucd_510(self):
self.assertTrue("\u1d79".upper()=='\ua77d')
self.assertTrue(".".upper()=='.')
+ @requires_resource('cpu')
def test_bug_5828(self):
self.assertEqual("\u1d79".lower(), "\u1d79")
# Only U+0000 should have U+0000 as its upper/lower/titlecase variant
@@ -366,6 +415,7 @@ def unistr(data):
return "".join([chr(x) for x in data])
@requires_resource('network')
+ @requires_resource('cpu')
def test_normalization(self):
TESTDATAFILE = "NormalizationTest.txt"
TESTDATAURL = f"http://www.pythontest.net/unicode/{unicodedata.unidata_version}/{TESTDATAFILE}"
diff --git a/Lib/test/test_userstring.py b/Lib/test/test_userstring.py
index 51b4f6041e..74df52f541 100644
--- a/Lib/test/test_userstring.py
+++ b/Lib/test/test_userstring.py
@@ -7,8 +7,7 @@
from collections import UserString
class UserStringTest(
- string_tests.CommonTest,
- string_tests.MixinStrUnicodeUserStringTest,
+ string_tests.StringLikeTest,
unittest.TestCase
):
diff --git a/Lib/test/test_uuid.py b/Lib/test/test_uuid.py
index ee6232ed9e..069221ae47 100644
--- a/Lib/test/test_uuid.py
+++ b/Lib/test/test_uuid.py
@@ -4,6 +4,7 @@
import builtins
import contextlib
import copy
+import enum
import io
import os
import pickle
@@ -18,7 +19,7 @@ def importable(name):
try:
__import__(name)
return True
- except:
+ except ModuleNotFoundError:
return False
@@ -31,6 +32,15 @@ def get_command_stdout(command, args):
class BaseTestUUID:
uuid = None
+ # TODO: RUSTPYTHON
+ @unittest.expectedFailure
+ def test_safe_uuid_enum(self):
+ class CheckedSafeUUID(enum.Enum):
+ safe = 0
+ unsafe = -1
+ unknown = None
+ enum._test_simple_enum(CheckedSafeUUID, py_uuid.SafeUUID)
+
def test_UUID(self):
equal = self.assertEqual
ascending = []
@@ -522,7 +532,14 @@ def test_uuid1(self):
@support.requires_mac_ver(10, 5)
@unittest.skipUnless(os.name == 'posix', 'POSIX-only test')
def test_uuid1_safe(self):
- if not self.uuid._has_uuid_generate_time_safe:
+ try:
+ import _uuid
+ except ImportError:
+ has_uuid_generate_time_safe = False
+ else:
+ has_uuid_generate_time_safe = _uuid.has_uuid_generate_time_safe
+
+ if not has_uuid_generate_time_safe or not self.uuid._generate_time_safe:
self.skipTest('requires uuid_generate_time_safe(3)')
u = self.uuid.uuid1()
@@ -538,7 +555,6 @@ def mock_generate_time_safe(self, safe_value):
"""
if os.name != 'posix':
self.skipTest('POSIX-only test')
- self.uuid._load_system_functions()
f = self.uuid._generate_time_safe
if f is None:
self.skipTest('need uuid._generate_time_safe')
@@ -573,8 +589,7 @@ def test_uuid1_bogus_return_value(self):
self.assertEqual(u.is_safe, self.uuid.SafeUUID.unknown)
def test_uuid1_time(self):
- with mock.patch.object(self.uuid, '_has_uuid_generate_time_safe', False), \
- mock.patch.object(self.uuid, '_generate_time_safe', None), \
+ with mock.patch.object(self.uuid, '_generate_time_safe', None), \
mock.patch.object(self.uuid, '_last_timestamp', None), \
mock.patch.object(self.uuid, 'getnode', return_value=93328246233727), \
mock.patch('time.time_ns', return_value=1545052026752910643), \
@@ -582,8 +597,7 @@ def test_uuid1_time(self):
u = self.uuid.uuid1()
self.assertEqual(u, self.uuid.UUID('a7a55b92-01fc-11e9-94c5-54e1acf6da7f'))
- with mock.patch.object(self.uuid, '_has_uuid_generate_time_safe', False), \
- mock.patch.object(self.uuid, '_generate_time_safe', None), \
+ with mock.patch.object(self.uuid, '_generate_time_safe', None), \
mock.patch.object(self.uuid, '_last_timestamp', None), \
mock.patch('time.time_ns', return_value=1545052026752910643):
u = self.uuid.uuid1(node=93328246233727, clock_seq=5317)
@@ -592,7 +606,22 @@ def test_uuid1_time(self):
def test_uuid3(self):
equal = self.assertEqual
- # Test some known version-3 UUIDs.
+ # Test some known version-3 UUIDs with name passed as a byte object
+ for u, v in [(self.uuid.uuid3(self.uuid.NAMESPACE_DNS, b'python.org'),
+ '6fa459ea-ee8a-3ca4-894e-db77e160355e'),
+ (self.uuid.uuid3(self.uuid.NAMESPACE_URL, b'http://python.org/'),
+ '9fe8e8c4-aaa8-32a9-a55c-4535a88b748d'),
+ (self.uuid.uuid3(self.uuid.NAMESPACE_OID, b'1.3.6.1'),
+ 'dd1a1cef-13d5-368a-ad82-eca71acd4cd1'),
+ (self.uuid.uuid3(self.uuid.NAMESPACE_X500, b'c=ca'),
+ '658d3002-db6b-3040-a1d1-8ddd7d189a4d'),
+ ]:
+ equal(u.variant, self.uuid.RFC_4122)
+ equal(u.version, 3)
+ equal(u, self.uuid.UUID(v))
+ equal(str(u), v)
+
+ # Test some known version-3 UUIDs with name passed as a string
for u, v in [(self.uuid.uuid3(self.uuid.NAMESPACE_DNS, 'python.org'),
'6fa459ea-ee8a-3ca4-894e-db77e160355e'),
(self.uuid.uuid3(self.uuid.NAMESPACE_URL, 'http://python.org/'),
@@ -624,7 +653,22 @@ def test_uuid4(self):
def test_uuid5(self):
equal = self.assertEqual
- # Test some known version-5 UUIDs.
+ # Test some known version-5 UUIDs with names given as byte objects
+ for u, v in [(self.uuid.uuid5(self.uuid.NAMESPACE_DNS, b'python.org'),
+ '886313e1-3b8a-5372-9b90-0c9aee199e5d'),
+ (self.uuid.uuid5(self.uuid.NAMESPACE_URL, b'http://python.org/'),
+ '4c565f0d-3f5a-5890-b41b-20cf47701c5e'),
+ (self.uuid.uuid5(self.uuid.NAMESPACE_OID, b'1.3.6.1'),
+ '1447fa61-5277-5fef-a9b3-fbc6e44f4af3'),
+ (self.uuid.uuid5(self.uuid.NAMESPACE_X500, b'c=ca'),
+ 'cc957dd1-a972-5349-98cd-874190002798'),
+ ]:
+ equal(u.variant, self.uuid.RFC_4122)
+ equal(u.version, 5)
+ equal(u, self.uuid.UUID(v))
+ equal(str(u), v)
+
+ # Test some known version-5 UUIDs with names given as strings
for u, v in [(self.uuid.uuid5(self.uuid.NAMESPACE_DNS, 'python.org'),
'886313e1-3b8a-5372-9b90-0c9aee199e5d'),
(self.uuid.uuid5(self.uuid.NAMESPACE_URL, 'http://python.org/'),
@@ -667,6 +711,67 @@ def test_uuid_weakref(self):
weak = weakref.ref(strong)
self.assertIs(strong, weak())
+ @mock.patch.object(sys, "argv", ["", "-u", "uuid3", "-n", "@dns"])
+ @mock.patch('sys.stderr', new_callable=io.StringIO)
+ def test_cli_namespace_required_for_uuid3(self, mock_err):
+ with self.assertRaises(SystemExit) as cm:
+ self.uuid.main()
+
+ # Check that exception code is the same as argparse.ArgumentParser.error
+ self.assertEqual(cm.exception.code, 2)
+ self.assertIn("error: Incorrect number of arguments", mock_err.getvalue())
+
+ @mock.patch.object(sys, "argv", ["", "-u", "uuid3", "-N", "python.org"])
+ @mock.patch('sys.stderr', new_callable=io.StringIO)
+ def test_cli_name_required_for_uuid3(self, mock_err):
+ with self.assertRaises(SystemExit) as cm:
+ self.uuid.main()
+ # Check that exception code is the same as argparse.ArgumentParser.error
+ self.assertEqual(cm.exception.code, 2)
+ self.assertIn("error: Incorrect number of arguments", mock_err.getvalue())
+
+ @mock.patch.object(sys, "argv", [""])
+ def test_cli_uuid4_outputted_with_no_args(self):
+ stdout = io.StringIO()
+ with contextlib.redirect_stdout(stdout):
+ self.uuid.main()
+
+ output = stdout.getvalue().strip()
+ uuid_output = self.uuid.UUID(output)
+
+ # Output uuid should be in the format of uuid4
+ self.assertEqual(output, str(uuid_output))
+ self.assertEqual(uuid_output.version, 4)
+
+ @mock.patch.object(sys, "argv",
+ ["", "-u", "uuid3", "-n", "@dns", "-N", "python.org"])
+ def test_cli_uuid3_ouputted_with_valid_namespace_and_name(self):
+ stdout = io.StringIO()
+ with contextlib.redirect_stdout(stdout):
+ self.uuid.main()
+
+ output = stdout.getvalue().strip()
+ uuid_output = self.uuid.UUID(output)
+
+ # Output should be in the form of uuid5
+ self.assertEqual(output, str(uuid_output))
+ self.assertEqual(uuid_output.version, 3)
+
+ @mock.patch.object(sys, "argv",
+ ["", "-u", "uuid5", "-n", "@dns", "-N", "python.org"])
+ def test_cli_uuid5_ouputted_with_valid_namespace_and_name(self):
+ stdout = io.StringIO()
+ with contextlib.redirect_stdout(stdout):
+ self.uuid.main()
+
+ output = stdout.getvalue().strip()
+ uuid_output = self.uuid.UUID(output)
+
+ # Output should be in the form of uuid5
+ self.assertEqual(output, str(uuid_output))
+ self.assertEqual(uuid_output.version, 5)
+
+
class TestUUIDWithoutExtModule(BaseTestUUID, unittest.TestCase):
uuid = py_uuid
diff --git a/Lib/test/test_weakref.py b/Lib/test/test_weakref.py
index 7d204f3c4c..242c076f9b 100644
--- a/Lib/test/test_weakref.py
+++ b/Lib/test/test_weakref.py
@@ -1,5 +1,6 @@
import gc
import sys
+import doctest
import unittest
import collections
import weakref
@@ -9,10 +10,14 @@
import threading
import time
import random
+import textwrap
from test import support
-from test.support import script_helper, ALWAYS_EQ
+from test.support import script_helper, ALWAYS_EQ, suppress_immortalization
from test.support import gc_collect
+from test.support import import_helper
+from test.support import threading_helper
+from test.support import is_wasi, Py_DEBUG
# Used in ReferencesTestCase.test_ref_created_during_del() .
ref_from_del = None
@@ -77,7 +82,7 @@ def callback(self, ref):
@contextlib.contextmanager
-def collect_in_thread(period=0.0001):
+def collect_in_thread(period=0.005):
"""
Ensure GC collections happen in a different thread, at a high frequency.
"""
@@ -114,6 +119,49 @@ def test_basic_ref(self):
del o
repr(wr)
+ @support.cpython_only
+ def test_ref_repr(self):
+ obj = C()
+ ref = weakref.ref(obj)
+ regex = (
+ rf""
+ )
+ self.assertRegex(repr(ref), regex)
+
+ obj = None
+ gc_collect()
+ self.assertRegex(repr(ref),
+ rf'')
+
+ # test type with __name__
+ class WithName:
+ @property
+ def __name__(self):
+ return "custom_name"
+
+ obj2 = WithName()
+ ref2 = weakref.ref(obj2)
+ regex = (
+ rf""
+ )
+ self.assertRegex(repr(ref2), regex)
+
+ def test_repr_failure_gh99184(self):
+ class MyConfig(dict):
+ def __getattr__(self, x):
+ return self[x]
+
+ obj = MyConfig(offset=5)
+ obj_weakref = weakref.ref(obj)
+
+ self.assertIn('MyConfig', repr(obj_weakref))
+ self.assertIn('MyConfig', str(obj_weakref))
+
def test_basic_callback(self):
self.check_basic_callback(C)
self.check_basic_callback(create_function)
@@ -121,7 +169,7 @@ def test_basic_callback(self):
@support.cpython_only
def test_cfunction(self):
- import _testcapi
+ _testcapi = import_helper.import_module("_testcapi")
create_cfunction = _testcapi.create_cfunction
f = create_cfunction()
wr = weakref.ref(f)
@@ -182,6 +230,22 @@ def check(proxy):
self.assertRaises(ReferenceError, bool, ref3)
self.assertEqual(self.cbcalled, 2)
+ @support.cpython_only
+ def test_proxy_repr(self):
+ obj = C()
+ ref = weakref.proxy(obj, self.callback)
+ regex = (
+ rf""
+ )
+ self.assertRegex(repr(ref), regex)
+
+ obj = None
+ gc_collect()
+ self.assertRegex(repr(ref),
+ rf'')
+
def check_basic_ref(self, factory):
o = factory()
ref = weakref.ref(o)
@@ -613,7 +677,8 @@ class C(object):
# deallocation of c2.
del c2
- def test_callback_in_cycle_1(self):
+ @suppress_immortalization()
+ def test_callback_in_cycle(self):
import gc
class J(object):
@@ -653,40 +718,11 @@ def acallback(self, ignore):
del I, J, II
gc.collect()
- def test_callback_in_cycle_2(self):
+ def test_callback_reachable_one_way(self):
import gc
- # This is just like test_callback_in_cycle_1, except that II is an
- # old-style class. The symptom is different then: an instance of an
- # old-style class looks in its own __dict__ first. 'J' happens to
- # get cleared from I.__dict__ before 'wr', and 'J' was never in II's
- # __dict__, so the attribute isn't found. The difference is that
- # the old-style II doesn't have a NULL __mro__ (it doesn't have any
- # __mro__), so no segfault occurs. Instead it got:
- # test_callback_in_cycle_2 (__main__.ReferencesTestCase) ...
- # Exception exceptions.AttributeError:
- # "II instance has no attribute 'J'" in > ignored
-
- class J(object):
- pass
-
- class II:
- def acallback(self, ignore):
- self.J
-
- I = II()
- I.J = J
- I.wr = weakref.ref(J, I.acallback)
-
- del I, J, II
- gc.collect()
-
- def test_callback_in_cycle_3(self):
- import gc
-
- # This one broke the first patch that fixed the last two. In this
- # case, the objects reachable from the callback aren't also reachable
+ # This one broke the first patch that fixed the previous test. In this case,
+ # the objects reachable from the callback aren't also reachable
# from the object (c1) *triggering* the callback: you can get to
# c1 from c2, but not vice-versa. The result was that c2's __dict__
# got tp_clear'ed by the time the c2.cb callback got invoked.
@@ -706,10 +742,10 @@ def cb(self, ignore):
del c1, c2
gc.collect()
- def test_callback_in_cycle_4(self):
+ def test_callback_different_classes(self):
import gc
- # Like test_callback_in_cycle_3, except c2 and c1 have different
+ # Like test_callback_reachable_one_way, except c2 and c1 have different
# classes. c2's class (C) isn't reachable from c1 then, so protecting
# objects reachable from the dying object (c1) isn't enough to stop
# c2's class (C) from getting tp_clear'ed before c2.cb is invoked.
@@ -736,6 +772,7 @@ class D:
# TODO: RUSTPYTHON
@unittest.expectedFailure
+ @suppress_immortalization()
def test_callback_in_cycle_resurrection(self):
import gc
@@ -879,6 +916,7 @@ def test_init(self):
# No exception should be raised here
gc.collect()
+ @suppress_immortalization()
def test_classes(self):
# Check that classes are weakrefable.
class A(object):
@@ -958,6 +996,7 @@ def test_hashing(self):
self.assertEqual(hash(a), hash(42))
self.assertRaises(TypeError, hash, b)
+ @unittest.skipIf(is_wasi and Py_DEBUG, "requires deep stack")
def test_trashcan_16602(self):
# Issue #16602: when a weakref's target was part of a long
# deallocation chain, the trashcan mechanism could delay clearing
@@ -1015,6 +1054,31 @@ def __del__(self): pass
del x
support.gc_collect()
+ @support.cpython_only
+ def test_no_memory_when_clearing(self):
+ # gh-118331: Make sure we do not raise an exception from the destructor
+ # when clearing weakrefs if allocating the intermediate tuple fails.
+ code = textwrap.dedent("""
+ import _testcapi
+ import weakref
+
+ class TestObj:
+ pass
+
+ def callback(obj):
+ pass
+
+ obj = TestObj()
+ # The choice of 50 is arbitrary, but must be large enough to ensure
+ # the allocation won't be serviced by the free list.
+ wrs = [weakref.ref(obj, callback) for _ in range(50)]
+ _testcapi.set_nomemory(0)
+ del obj
+ """).strip()
+ res, _ = script_helper.run_python_until_end("-c", code)
+ stderr = res.err.decode("ascii", "backslashreplace")
+ self.assertNotRegex(stderr, "_Py_Dealloc: Deallocator of type 'TestObj'")
+
class SubclassableWeakrefTestCase(TestBase):
@@ -1267,6 +1331,12 @@ class MappingTestCase(TestBase):
COUNT = 10
+ if support.check_sanitizer(thread=True) and support.Py_GIL_DISABLED:
+ # Reduce iteration count to get acceptable latency
+ NUM_THREADED_ITERATIONS = 1000
+ else:
+ NUM_THREADED_ITERATIONS = 100000
+
def check_len_cycles(self, dict_type, cons):
N = 20
items = [RefCycle() for i in range(N)]
@@ -1898,34 +1968,56 @@ def test_make_weak_keyed_dict_repr(self):
dict = weakref.WeakKeyDictionary()
self.assertRegex(repr(dict), '')
+ @threading_helper.requires_working_threading()
def test_threaded_weak_valued_setdefault(self):
d = weakref.WeakValueDictionary()
with collect_in_thread():
- for i in range(100000):
+ for i in range(self.NUM_THREADED_ITERATIONS):
x = d.setdefault(10, RefCycle())
self.assertIsNot(x, None) # we never put None in there!
del x
+ @threading_helper.requires_working_threading()
def test_threaded_weak_valued_pop(self):
d = weakref.WeakValueDictionary()
with collect_in_thread():
- for i in range(100000):
+ for i in range(self.NUM_THREADED_ITERATIONS):
d[10] = RefCycle()
x = d.pop(10, 10)
self.assertIsNot(x, None) # we never put None in there!
+ @threading_helper.requires_working_threading()
def test_threaded_weak_valued_consistency(self):
# Issue #28427: old keys should not remove new values from
# WeakValueDictionary when collecting from another thread.
d = weakref.WeakValueDictionary()
with collect_in_thread():
- for i in range(200000):
+ for i in range(2 * self.NUM_THREADED_ITERATIONS):
o = RefCycle()
d[10] = o
# o is still alive, so the dict can't be empty
self.assertEqual(len(d), 1)
o = None # lose ref
+ @support.cpython_only
+ def test_weak_valued_consistency(self):
+ # A single-threaded, deterministic repro for issue #28427: old keys
+ # should not remove new values from WeakValueDictionary. This relies on
+ # an implementation detail of CPython's WeakValueDictionary (its
+ # underlying dictionary of KeyedRefs) to reproduce the issue.
+ d = weakref.WeakValueDictionary()
+ with support.disable_gc():
+ d[10] = RefCycle()
+ # Keep the KeyedRef alive after it's replaced so that GC will invoke
+ # the callback.
+ wr = d.data[10]
+ # Replace the value with something that isn't cyclic garbage
+ o = RefCycle()
+ d[10] = o
+ # Trigger GC, which will invoke the callback for `wr`
+ gc.collect()
+ self.assertEqual(len(d), 1)
+
def check_threaded_weak_dict_copy(self, type_, deepcopy):
# `type_` should be either WeakKeyDictionary or WeakValueDictionary.
# `deepcopy` should be either True or False.
@@ -1987,22 +2079,28 @@ def pop_and_collect(lst):
if exc:
raise exc[0]
+ @threading_helper.requires_working_threading()
def test_threaded_weak_key_dict_copy(self):
# Issue #35615: Weakref keys or values getting GC'ed during dict
# copying should not result in a crash.
self.check_threaded_weak_dict_copy(weakref.WeakKeyDictionary, False)
+ @threading_helper.requires_working_threading()
+ @support.requires_resource('cpu')
def test_threaded_weak_key_dict_deepcopy(self):
# Issue #35615: Weakref keys or values getting GC'ed during dict
# copying should not result in a crash.
self.check_threaded_weak_dict_copy(weakref.WeakKeyDictionary, True)
@unittest.skip("TODO: RUSTPYTHON; occasionally crash (Exit code -6)")
+ @threading_helper.requires_working_threading()
def test_threaded_weak_value_dict_copy(self):
# Issue #35615: Weakref keys or values getting GC'ed during dict
# copying should not result in a crash.
self.check_threaded_weak_dict_copy(weakref.WeakValueDictionary, False)
+ @threading_helper.requires_working_threading()
+ @support.requires_resource('cpu')
def test_threaded_weak_value_dict_deepcopy(self):
# Issue #35615: Weakref keys or values getting GC'ed during dict
# copying should not result in a crash.
@@ -2195,6 +2293,19 @@ def test_atexit(self):
self.assertTrue(b'ZeroDivisionError' in err)
+class ModuleTestCase(unittest.TestCase):
+ # TODO: RUSTPYTHON
+ @unittest.expectedFailure
+ def test_names(self):
+ for name in ('ReferenceType', 'ProxyType', 'CallableProxyType',
+ 'WeakMethod', 'WeakSet', 'WeakKeyDictionary', 'WeakValueDictionary'):
+ obj = getattr(weakref, name)
+ if name != 'WeakSet':
+ self.assertEqual(obj.__module__, 'weakref')
+ self.assertEqual(obj.__name__, name)
+ self.assertEqual(obj.__qualname__, name)
+
+
libreftest = """ Doctest for examples in the library reference: weakref.rst
>>> from test.support import gc_collect
@@ -2283,19 +2394,11 @@ def test_atexit(self):
__test__ = {'libreftest' : libreftest}
-def test_main():
- support.run_unittest(
- ReferencesTestCase,
- WeakMethodTestCase,
- MappingTestCase,
- WeakValueDictionaryTestCase,
- WeakKeyDictionaryTestCase,
- SubclassableWeakrefTestCase,
- FinalizeTestCase,
- )
+def load_tests(loader, tests, pattern):
# TODO: RUSTPYTHON
- # support.run_doctest(sys.modules[__name__])
+ # tests.addTest(doctest.DocTestSuite())
+ return tests
if __name__ == "__main__":
- test_main()
+ unittest.main()
diff --git a/Lib/test/typinganndata/mod_generics_cache.py b/Lib/test/typinganndata/mod_generics_cache.py
index 62deea9859..6c1ee2fec8 100644
--- a/Lib/test/typinganndata/mod_generics_cache.py
+++ b/Lib/test/typinganndata/mod_generics_cache.py
@@ -2,25 +2,23 @@
from typing import TypeVar, Generic, Optional, TypeAliasType
-# TODO: RUSTPYTHON
+default_a: Optional['A'] = None
+default_b: Optional['B'] = None
-# default_a: Optional['A'] = None
-# default_b: Optional['B'] = None
+T = TypeVar('T')
-# T = TypeVar('T')
+class A(Generic[T]):
+ some_b: 'B'
-# class A(Generic[T]):
-# some_b: 'B'
+class B(Generic[T]):
+ class A(Generic[T]):
+ pass
-# class B(Generic[T]):
-# class A(Generic[T]):
-# pass
+ my_inner_a1: 'B.A'
+ my_inner_a2: A
+ my_outer_a: 'A' # unless somebody calls get_type_hints with localns=B.__dict__
-# my_inner_a1: 'B.A'
-# my_inner_a2: A
-# my_outer_a: 'A' # unless somebody calls get_type_hints with localns=B.__dict__
-
-# type Alias = int
-# OldStyle = TypeAliasType("OldStyle", int)
+type Alias = int
+OldStyle = TypeAliasType("OldStyle", int)
diff --git a/Lib/textwrap.py b/Lib/textwrap.py
index 841de9baec..7ca393d1c3 100644
--- a/Lib/textwrap.py
+++ b/Lib/textwrap.py
@@ -63,10 +63,7 @@ class TextWrapper:
Append to the last line of truncated text.
"""
- unicode_whitespace_trans = {}
- uspace = ord(' ')
- for x in _whitespace:
- unicode_whitespace_trans[ord(x)] = uspace
+ unicode_whitespace_trans = dict.fromkeys(map(ord, _whitespace), ord(' '))
# This funky little regex is just the trick for splitting
# text up into word-wrappable chunks. E.g.
@@ -479,13 +476,19 @@ def indent(text, prefix, predicate=None):
consist solely of whitespace characters.
"""
if predicate is None:
- def predicate(line):
- return line.strip()
-
- def prefixed_lines():
- for line in text.splitlines(True):
- yield (prefix + line if predicate(line) else line)
- return ''.join(prefixed_lines())
+ # str.splitlines(True) doesn't produce empty string.
+ # ''.splitlines(True) => []
+ # 'foo\n'.splitlines(True) => ['foo\n']
+ # So we can use just `not s.isspace()` here.
+ predicate = lambda s: not s.isspace()
+
+ prefixed_lines = []
+ for line in text.splitlines(True):
+ if predicate(line):
+ prefixed_lines.append(prefix)
+ prefixed_lines.append(line)
+
+ return ''.join(prefixed_lines)
if __name__ == "__main__":
diff --git a/Lib/tomllib/_parser.py b/Lib/tomllib/_parser.py
index 45ca7a8963..9c80a6a547 100644
--- a/Lib/tomllib/_parser.py
+++ b/Lib/tomllib/_parser.py
@@ -142,7 +142,7 @@ class Flags:
EXPLICIT_NEST = 1
def __init__(self) -> None:
- self._flags: dict[str, dict] = {}
+ self._flags: dict[str, dict[Any, Any]] = {}
self._pending_flags: set[tuple[Key, int]] = set()
def add_pending(self, key: Key, flag: int) -> None:
@@ -200,7 +200,7 @@ def get_or_create_nest(
key: Key,
*,
access_lists: bool = True,
- ) -> dict:
+ ) -> dict[str, Any]:
cont: Any = self.dict
for k in key:
if k not in cont:
@@ -210,7 +210,7 @@ def get_or_create_nest(
cont = cont[-1]
if not isinstance(cont, dict):
raise KeyError("There is no nest behind this key")
- return cont
+ return cont # type: ignore[no-any-return]
def append_nest_to_list(self, key: Key) -> None:
cont = self.get_or_create_nest(key[:-1])
@@ -409,9 +409,9 @@ def parse_one_line_basic_str(src: str, pos: Pos) -> tuple[Pos, str]:
return parse_basic_str(src, pos, multiline=False)
-def parse_array(src: str, pos: Pos, parse_float: ParseFloat) -> tuple[Pos, list]:
+def parse_array(src: str, pos: Pos, parse_float: ParseFloat) -> tuple[Pos, list[Any]]:
pos += 1
- array: list = []
+ array: list[Any] = []
pos = skip_comments_and_array_ws(src, pos)
if src.startswith("]", pos):
@@ -433,7 +433,7 @@ def parse_array(src: str, pos: Pos, parse_float: ParseFloat) -> tuple[Pos, list]
return pos + 1, array
-def parse_inline_table(src: str, pos: Pos, parse_float: ParseFloat) -> tuple[Pos, dict]:
+def parse_inline_table(src: str, pos: Pos, parse_float: ParseFloat) -> tuple[Pos, dict[str, Any]]:
pos += 1
nested_dict = NestedDict()
flags = Flags()
@@ -679,7 +679,7 @@ def make_safe_parse_float(parse_float: ParseFloat) -> ParseFloat:
instead of returning illegal types.
"""
# The default `float` callable never returns illegal types. Optimize it.
- if parse_float is float: # type: ignore[comparison-overlap]
+ if parse_float is float:
return float
def safe_parse_float(float_str: str) -> Any:
diff --git a/Lib/tomllib/_re.py b/Lib/tomllib/_re.py
index 994bb7493f..a97cab2f9d 100644
--- a/Lib/tomllib/_re.py
+++ b/Lib/tomllib/_re.py
@@ -49,7 +49,7 @@
)
-def match_to_datetime(match: re.Match) -> datetime | date:
+def match_to_datetime(match: re.Match[str]) -> datetime | date:
"""Convert a `RE_DATETIME` match to `datetime.datetime` or `datetime.date`.
Raises ValueError if the match does not correspond to a valid date
@@ -95,13 +95,13 @@ def cached_tz(hour_str: str, minute_str: str, sign_str: str) -> timezone:
)
-def match_to_localtime(match: re.Match) -> time:
+def match_to_localtime(match: re.Match[str]) -> time:
hour_str, minute_str, sec_str, micros_str = match.groups()
micros = int(micros_str.ljust(6, "0")) if micros_str else 0
return time(int(hour_str), int(minute_str), int(sec_str), micros)
-def match_to_number(match: re.Match, parse_float: ParseFloat) -> Any:
+def match_to_number(match: re.Match[str], parse_float: ParseFloat) -> Any:
if match.group("floatpart"):
return parse_float(match.group())
return int(match.group(), 0)
diff --git a/Lib/tomllib/mypy.ini b/Lib/tomllib/mypy.ini
new file mode 100644
index 0000000000..1761dce455
--- /dev/null
+++ b/Lib/tomllib/mypy.ini
@@ -0,0 +1,17 @@
+# Config file for running mypy on tomllib.
+# Run mypy by invoking `mypy --config-file Lib/tomllib/mypy.ini`
+# on the command-line from the repo root
+
+[mypy]
+files = Lib/tomllib
+mypy_path = $MYPY_CONFIG_FILE_DIR/../../Misc/mypy
+explicit_package_bases = True
+python_version = 3.12
+pretty = True
+
+# Enable most stricter settings
+enable_error_code = ignore-without-code
+strict = True
+strict_bytes = True
+local_partial_types = True
+warn_unreachable = True
diff --git a/Lib/types.py b/Lib/types.py
index 4dab6ddce0..b036a85068 100644
--- a/Lib/types.py
+++ b/Lib/types.py
@@ -1,6 +1,7 @@
"""
Define names for built-in types that aren't directly accessible as a builtin.
"""
+
import sys
# Iterators in Python aren't a matter of type but of protocol. A large
@@ -52,17 +53,14 @@ def _m(self): pass
try:
raise TypeError
-except TypeError:
- tb = sys.exc_info()[2]
- TracebackType = type(tb)
- FrameType = type(tb.tb_frame)
- tb = None; del tb
+except TypeError as exc:
+ TracebackType = type(exc.__traceback__)
+ FrameType = type(exc.__traceback__.tb_frame)
-# For Jython, the following two types are identical
GetSetDescriptorType = type(FunctionType.__code__)
MemberDescriptorType = type(FunctionType.__globals__)
-del sys, _f, _g, _C, _c, _ag # Not for export
+del sys, _f, _g, _C, _c, _ag, _cell_factory # Not for export
# Provide a PEP 3115 compliant mechanism for class creation
@@ -82,7 +80,7 @@ def resolve_bases(bases):
updated = False
shift = 0
for i, base in enumerate(bases):
- if isinstance(base, type) and not isinstance(base, GenericAlias):
+ if isinstance(base, type):
continue
if not hasattr(base, "__mro_entries__"):
continue
@@ -146,6 +144,35 @@ def _calculate_meta(meta, bases):
"of the metaclasses of all its bases")
return winner
+
+def get_original_bases(cls, /):
+ """Return the class's "original" bases prior to modification by `__mro_entries__`.
+
+ Examples::
+
+ from typing import TypeVar, Generic, NamedTuple, TypedDict
+
+ T = TypeVar("T")
+ class Foo(Generic[T]): ...
+ class Bar(Foo[int], float): ...
+ class Baz(list[str]): ...
+ Eggs = NamedTuple("Eggs", [("a", int), ("b", str)])
+ Spam = TypedDict("Spam", {"a": int, "b": str})
+
+ assert get_original_bases(Bar) == (Foo[int], float)
+ assert get_original_bases(Baz) == (list[str],)
+ assert get_original_bases(Eggs) == (NamedTuple,)
+ assert get_original_bases(Spam) == (TypedDict,)
+ assert get_original_bases(int) == (object,)
+ """
+ try:
+ return cls.__dict__.get("__orig_bases__", cls.__bases__)
+ except AttributeError:
+ raise TypeError(
+ f"Expected an instance of type, not {type(cls).__name__!r}"
+ ) from None
+
+
class DynamicClassAttribute:
"""Route attribute access on a class to __getattr__.
@@ -158,7 +185,7 @@ class DynamicClassAttribute:
attributes on the class with the same name. (Enum used this between Python
versions 3.4 - 3.9 .)
- Subclass from this to use a different method of accessing virtual atributes
+ Subclass from this to use a different method of accessing virtual attributes
and still be treated properly by the inspect module. (Enum uses this since
Python 3.10 .)
@@ -305,4 +332,11 @@ def wrapped(*args, **kwargs):
NoneType = type(None)
NotImplementedType = type(NotImplemented)
+def __getattr__(name):
+ if name == 'CapsuleType':
+ import _socket
+ return type(_socket.CAPI)
+ raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
+
__all__ = [n for n in globals() if n[:1] != '_']
+__all__ += ['CapsuleType']
diff --git a/Lib/typing.py b/Lib/typing.py
index b64a6b6714..a7397356d6 100644
--- a/Lib/typing.py
+++ b/Lib/typing.py
@@ -220,6 +220,8 @@ def _should_unflatten_callable_args(typ, args):
>>> P = ParamSpec('P')
>>> collections.abc.Callable[[int, int], str].__args__ == (int, int, str)
True
+ >>> collections.abc.Callable[P, str].__args__ == (P, str)
+ True
As a result, if we need to reconstruct the Callable from its __args__,
we need to unflatten it.
@@ -263,6 +265,8 @@ def _collect_type_parameters(args, *, enforce_default_ordering: bool = True):
>>> P = ParamSpec('P')
>>> T = TypeVar('T')
+ >>> _collect_type_parameters((T, Callable[P, T]))
+ (~T, ~P)
"""
# required type parameter cannot appear after parameter with default
default_encountered = False
@@ -1983,7 +1987,8 @@ def _allow_reckless_class_checks(depth=2):
The abc and functools modules indiscriminately call isinstance() and
issubclass() on the whole MRO of a user class, which may contain protocols.
"""
- return _caller(depth) in {'abc', 'functools', None}
+ # XXX: RUSTPYTHON; https://github.com/python/cpython/pull/136115
+ return _caller(depth) in {'abc', '_py_abc', 'functools', None}
_PROTO_ALLOWLIST = {
@@ -2090,11 +2095,11 @@ def __subclasscheck__(cls, other):
and cls.__dict__.get("__subclasshook__") is _proto_hook
):
_type_check_issubclass_arg_1(other)
- # non_method_attrs = sorted(cls.__non_callable_proto_members__)
- # raise TypeError(
- # "Protocols with non-method members don't support issubclass()."
- # f" Non-method members: {str(non_method_attrs)[1:-1]}."
- # )
+ non_method_attrs = sorted(cls.__non_callable_proto_members__)
+ raise TypeError(
+ "Protocols with non-method members don't support issubclass()."
+ f" Non-method members: {str(non_method_attrs)[1:-1]}."
+ )
return _abc_subclasscheck(cls, other)
def __instancecheck__(cls, instance):
@@ -2526,6 +2531,18 @@ def get_origin(tp):
This supports generic types, Callable, Tuple, Union, Literal, Final, ClassVar,
Annotated, and others. Return None for unsupported types.
+
+ Examples::
+
+ >>> P = ParamSpec('P')
+ >>> assert get_origin(Literal[42]) is Literal
+ >>> assert get_origin(int) is None
+ >>> assert get_origin(ClassVar[int]) is ClassVar
+ >>> assert get_origin(Generic) is Generic
+ >>> assert get_origin(Generic[T]) is Generic
+ >>> assert get_origin(Union[T, int]) is Union
+ >>> assert get_origin(List[Tuple[T, T]][int]) is list
+ >>> assert get_origin(P.args) is P
"""
if isinstance(tp, _AnnotatedAlias):
return Annotated
@@ -2548,6 +2565,10 @@ def get_args(tp):
>>> T = TypeVar('T')
>>> assert get_args(Dict[str, int]) == (str, int)
+ >>> assert get_args(int) == ()
+ >>> assert get_args(Union[int, Union[T, int], str][int]) == (int, str)
+ >>> assert get_args(Union[int, Tuple[T, int]][str]) == (int, Tuple[str, int])
+ >>> assert get_args(Callable[[], T][int]) == ([], int)
"""
if isinstance(tp, _AnnotatedAlias):
return (tp.__origin__,) + tp.__metadata__
@@ -3225,6 +3246,18 @@ def TypedDict(typename, fields=_sentinel, /, *, total=True):
associated with a value of a consistent type. This expectation
is not checked at runtime.
+ Usage::
+
+ >>> class Point2D(TypedDict):
+ ... x: int
+ ... y: int
+ ... label: str
+ ...
+ >>> a: Point2D = {'x': 1, 'y': 2, 'label': 'good'} # OK
+ >>> b: Point2D = {'z': 3, 'label': 'bad'} # Fails type check
+ >>> Point2D(x=1, y=2, label='first') == dict(x=1, y=2, label='first')
+ True
+
The type info can be accessed via the Point2D.__annotations__ dict, and
the Point2D.__required_keys__ and Point2D.__optional_keys__ frozensets.
TypedDict supports an additional equivalent form::
@@ -3680,44 +3713,43 @@ def decorator(cls_or_fn):
return cls_or_fn
return decorator
-# TODO: RUSTPYTHON
-
-# type _Func = Callable[..., Any]
-
-
-# def override[F: _Func](method: F, /) -> F:
-# """Indicate that a method is intended to override a method in a base class.
-#
-# Usage::
-#
-# class Base:
-# def method(self) -> None:
-# pass
-#
-# class Child(Base):
-# @override
-# def method(self) -> None:
-# super().method()
-#
-# When this decorator is applied to a method, the type checker will
-# validate that it overrides a method or attribute with the same name on a
-# base class. This helps prevent bugs that may occur when a base class is
-# changed without an equivalent change to a child class.
-#
-# There is no runtime checking of this property. The decorator attempts to
-# set the ``__override__`` attribute to ``True`` on the decorated object to
-# allow runtime introspection.
-#
-# See PEP 698 for details.
-# """
-# try:
-# method.__override__ = True
-# except (AttributeError, TypeError):
-# # Skip the attribute silently if it is not writable.
-# # AttributeError happens if the object has __slots__ or a
-# # read-only property, TypeError if it's a builtin class.
-# pass
-# return method
+
+type _Func = Callable[..., Any]
+
+
+def override[F: _Func](method: F, /) -> F:
+ """Indicate that a method is intended to override a method in a base class.
+
+ Usage::
+
+ class Base:
+ def method(self) -> None:
+ pass
+
+ class Child(Base):
+ @override
+ def method(self) -> None:
+ super().method()
+
+ When this decorator is applied to a method, the type checker will
+ validate that it overrides a method or attribute with the same name on a
+ base class. This helps prevent bugs that may occur when a base class is
+ changed without an equivalent change to a child class.
+
+ There is no runtime checking of this property. The decorator attempts to
+ set the ``__override__`` attribute to ``True`` on the decorated object to
+ allow runtime introspection.
+
+ See PEP 698 for details.
+ """
+ try:
+ method.__override__ = True
+ except (AttributeError, TypeError):
+ # Skip the attribute silently if it is not writable.
+ # AttributeError happens if the object has __slots__ or a
+ # read-only property, TypeError if it's a builtin class.
+ pass
+ return method
def is_protocol(tp: type, /) -> bool:
@@ -3740,8 +3772,19 @@ def is_protocol(tp: type, /) -> bool:
and tp != Protocol
)
+
def get_protocol_members(tp: type, /) -> frozenset[str]:
"""Return the set of members defined in a Protocol.
+
+ Example::
+
+ >>> from typing import Protocol, get_protocol_members
+ >>> class P(Protocol):
+ ... def a(self) -> str: ...
+ ... b: int
+ >>> get_protocol_members(P) == frozenset({'a', 'b'})
+ True
+
Raise a TypeError for arguments that are not Protocols.
"""
if not is_protocol(tp):
diff --git a/Lib/uuid.py b/Lib/uuid.py
index e4298253c2..c286eac38e 100644
--- a/Lib/uuid.py
+++ b/Lib/uuid.py
@@ -47,21 +47,22 @@
import os
import sys
-from enum import Enum
+from enum import Enum, _simple_enum
__author__ = 'Ka-Ping Yee '
# The recognized platforms - known behaviors
-if sys.platform in ('win32', 'darwin'):
- _AIX = _LINUX = False
-elif sys.platform in ('emscripten', 'wasi'): # XXX: RUSTPYTHON; patched to support those platforms
+if sys.platform in {'win32', 'darwin', 'emscripten', 'wasi'}:
_AIX = _LINUX = False
+elif sys.platform == 'linux':
+ _LINUX = True
+ _AIX = False
else:
import platform
_platform_system = platform.system()
_AIX = _platform_system == 'AIX'
- _LINUX = _platform_system == 'Linux'
+ _LINUX = _platform_system in ('Linux', 'Android')
_MAC_DELIM = b':'
_MAC_OMITS_LEADING_ZEROES = False
@@ -77,7 +78,8 @@
bytes_ = bytes # The built-in bytes type
-class SafeUUID(Enum):
+@_simple_enum(Enum)
+class SafeUUID:
safe = 0
unsafe = -1
unknown = None
@@ -187,7 +189,7 @@ def __init__(self, hex=None, bytes=None, bytes_le=None, fields=None,
if len(bytes) != 16:
raise ValueError('bytes is not a 16-char string')
assert isinstance(bytes, bytes_), repr(bytes)
- int = int_.from_bytes(bytes, byteorder='big')
+ int = int_.from_bytes(bytes) # big endian
if fields is not None:
if len(fields) != 6:
raise ValueError('fields is not a 6-tuple')
@@ -285,7 +287,7 @@ def __str__(self):
@property
def bytes(self):
- return self.int.to_bytes(16, 'big')
+ return self.int.to_bytes(16) # big endian
@property
def bytes_le(self):
@@ -372,7 +374,12 @@ def _get_command_stdout(command, *args):
# for are actually localized, but in theory some system could do so.)
env = dict(os.environ)
env['LC_ALL'] = 'C'
- proc = subprocess.Popen((executable,) + args,
+ # Empty strings will be quoted by popen so we should just ommit it
+ if args != ('',):
+ command = (executable, *args)
+ else:
+ command = (executable,)
+ proc = subprocess.Popen(command,
stdout=subprocess.PIPE,
stderr=subprocess.DEVNULL,
env=env)
@@ -397,7 +404,7 @@ def _get_command_stdout(command, *args):
# over locally administered ones since the former are globally unique, but
# we'll return the first of the latter found if that's all the machine has.
#
-# See https://en.wikipedia.org/wiki/MAC_address#Universal_vs._local
+# See https://en.wikipedia.org/wiki/MAC_address#Universal_vs._local_(U/L_bit)
def _is_universal(mac):
return not (mac & (1 << 41))
@@ -512,7 +519,7 @@ def _ifconfig_getnode():
mac = _find_mac_near_keyword('ifconfig', args, keywords, lambda i: i+1)
if mac:
return mac
- return None
+ return None
def _ip_getnode():
"""Get the hardware address on Unix by running ip."""
@@ -525,6 +532,8 @@ def _ip_getnode():
def _arp_getnode():
"""Get the hardware address on Unix by running arp."""
import os, socket
+ if not hasattr(socket, "gethostbyname"):
+ return None
try:
ip_addr = socket.gethostbyname(socket.gethostname())
except OSError:
@@ -558,32 +567,16 @@ def _netstat_getnode():
# This works on AIX and might work on Tru64 UNIX.
return _find_mac_under_heading('netstat', '-ian', b'Address')
-def _ipconfig_getnode():
- """[DEPRECATED] Get the hardware address on Windows."""
- # bpo-40501: UuidCreateSequential() is now the only supported approach
- return _windll_getnode()
-
-def _netbios_getnode():
- """[DEPRECATED] Get the hardware address on Windows."""
- # bpo-40501: UuidCreateSequential() is now the only supported approach
- return _windll_getnode()
-
# Import optional C extension at toplevel, to help disabling it when testing
try:
import _uuid
_generate_time_safe = getattr(_uuid, "generate_time_safe", None)
_UuidCreate = getattr(_uuid, "UuidCreate", None)
- _has_uuid_generate_time_safe = _uuid.has_uuid_generate_time_safe
except ImportError:
_uuid = None
_generate_time_safe = None
_UuidCreate = None
- _has_uuid_generate_time_safe = None
-
-
-def _load_system_functions():
- """[DEPRECATED] Platform-specific functions loaded at import time"""
def _unix_getnode():
@@ -609,7 +602,7 @@ def _random_getnode():
# 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/wiki/MAC_address#Unicast_vs._multicast
+ # 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)
@@ -705,9 +698,11 @@ def uuid1(node=None, clock_seq=None):
def uuid3(namespace, name):
"""Generate a UUID from the MD5 hash of a namespace UUID and a name."""
+ if isinstance(name, str):
+ name = bytes(name, "utf-8")
from hashlib import md5
digest = md5(
- namespace.bytes + bytes(name, "utf-8"),
+ namespace.bytes + name,
usedforsecurity=False
).digest()
return UUID(bytes=digest[:16], version=3)
@@ -718,13 +713,68 @@ def uuid4():
def uuid5(namespace, name):
"""Generate a UUID from the SHA-1 hash of a namespace UUID and a name."""
+ if isinstance(name, str):
+ name = bytes(name, "utf-8")
from hashlib import sha1
- hash = sha1(namespace.bytes + bytes(name, "utf-8")).digest()
+ hash = sha1(namespace.bytes + name).digest()
return UUID(bytes=hash[:16], version=5)
+
+def main():
+ """Run the uuid command line interface."""
+ uuid_funcs = {
+ "uuid1": uuid1,
+ "uuid3": uuid3,
+ "uuid4": uuid4,
+ "uuid5": uuid5
+ }
+ uuid_namespace_funcs = ("uuid3", "uuid5")
+ namespaces = {
+ "@dns": NAMESPACE_DNS,
+ "@url": NAMESPACE_URL,
+ "@oid": NAMESPACE_OID,
+ "@x500": NAMESPACE_X500
+ }
+
+ import argparse
+ parser = argparse.ArgumentParser(
+ description="Generates a uuid using the selected uuid function.")
+ parser.add_argument("-u", "--uuid", choices=uuid_funcs.keys(), default="uuid4",
+ help="The function to use to generate the uuid. "
+ "By default uuid4 function is used.")
+ parser.add_argument("-n", "--namespace",
+ help="The namespace is a UUID, or '@ns' where 'ns' is a "
+ "well-known predefined UUID addressed by namespace name. "
+ "Such as @dns, @url, @oid, and @x500. "
+ "Only required for uuid3/uuid5 functions.")
+ parser.add_argument("-N", "--name",
+ help="The name used as part of generating the uuid. "
+ "Only required for uuid3/uuid5 functions.")
+
+ args = parser.parse_args()
+ uuid_func = uuid_funcs[args.uuid]
+ namespace = args.namespace
+ name = args.name
+
+ if args.uuid in uuid_namespace_funcs:
+ if not namespace or not name:
+ parser.error(
+ "Incorrect number of arguments. "
+ f"{args.uuid} requires a namespace and a name. "
+ "Run 'python -m uuid -h' for more information."
+ )
+ namespace = namespaces[namespace] if namespace in namespaces else UUID(namespace)
+ print(uuid_func(namespace, name))
+ else:
+ print(uuid_func())
+
+
# The following standard UUIDs are for use with uuid3() or uuid5().
NAMESPACE_DNS = UUID('6ba7b810-9dad-11d1-80b4-00c04fd430c8')
NAMESPACE_URL = UUID('6ba7b811-9dad-11d1-80b4-00c04fd430c8')
NAMESPACE_OID = UUID('6ba7b812-9dad-11d1-80b4-00c04fd430c8')
NAMESPACE_X500 = UUID('6ba7b814-9dad-11d1-80b4-00c04fd430c8')
+
+if __name__ == "__main__":
+ main()
diff --git a/README.md b/README.md
index 9d0e8dfc84..ce5f02bee2 100644
--- a/README.md
+++ b/README.md
@@ -226,7 +226,7 @@ To enhance CPython compatibility, try to increase unittest coverage by checking
Another approach is to checkout the source code: builtin functions and object
methods are often the simplest and easiest way to contribute.
-You can also simply run `uv run python -I whats_left.py` to assist in finding any unimplemented
+You can also simply run `python -I whats_left.py` to assist in finding any unimplemented
method.
## Compiling to WebAssembly
diff --git a/benches/execution.rs b/benches/execution.rs
index 956975c22f..7a7ba247e5 100644
--- a/benches/execution.rs
+++ b/benches/execution.rs
@@ -71,7 +71,7 @@ pub fn benchmark_file_parsing(group: &mut BenchmarkGroup, name: &str,
pub fn benchmark_pystone(group: &mut BenchmarkGroup, contents: String) {
// Default is 50_000. This takes a while, so reduce it to 30k.
for idx in (10_000..=30_000).step_by(10_000) {
- let code_with_loops = format!("LOOPS = {}\n{}", idx, contents);
+ let code_with_loops = format!("LOOPS = {idx}\n{contents}");
let code_str = code_with_loops.as_str();
group.throughput(Throughput::Elements(idx as u64));
diff --git a/build.rs b/build.rs
new file mode 100644
index 0000000000..adebd659ad
--- /dev/null
+++ b/build.rs
@@ -0,0 +1,17 @@
+fn main() {
+ if std::env::var("CARGO_CFG_TARGET_OS").unwrap() == "windows" {
+ println!("cargo:rerun-if-changed=logo.ico");
+ let mut res = winresource::WindowsResource::new();
+ if std::path::Path::new("logo.ico").exists() {
+ res.set_icon("logo.ico");
+ } else {
+ println!("cargo:warning=logo.ico not found, skipping icon embedding");
+ return;
+ }
+ res.compile()
+ .map_err(|e| {
+ println!("cargo:warning=Failed to compile Windows resources: {e}");
+ })
+ .ok();
+ }
+}
diff --git a/common/Cargo.toml b/common/Cargo.toml
index 4eab8440df..94704d18c9 100644
--- a/common/Cargo.toml
+++ b/common/Cargo.toml
@@ -34,6 +34,7 @@ radium = { workspace = true }
lock_api = "0.4"
siphasher = "1"
+num-complex.workspace = true
[target.'cfg(windows)'.dependencies]
widestring = { workspace = true }
diff --git a/common/src/atomic.rs b/common/src/atomic.rs
index afe4afb444..ef7f41074e 100644
--- a/common/src/atomic.rs
+++ b/common/src/atomic.rs
@@ -65,7 +65,7 @@ impl Default for OncePtr {
impl OncePtr {
#[inline]
pub fn new() -> Self {
- OncePtr {
+ Self {
inner: Radium::new(ptr::null_mut()),
}
}
diff --git a/common/src/borrow.rs b/common/src/borrow.rs
index ce86d71e27..610084006e 100644
--- a/common/src/borrow.rs
+++ b/common/src/borrow.rs
@@ -56,6 +56,7 @@ impl<'a, T: ?Sized> BorrowedValue<'a, T> {
impl Deref for BorrowedValue<'_, T> {
type Target = T;
+
fn deref(&self) -> &T {
match self {
Self::Ref(r) => r,
@@ -81,6 +82,7 @@ pub enum BorrowedValueMut<'a, T: ?Sized> {
WriteLock(PyRwLockWriteGuard<'a, T>),
MappedWriteLock(PyMappedRwLockWriteGuard<'a, T>),
}
+
impl_from!('a, T, BorrowedValueMut<'a, T>,
RefMut(&'a mut T),
MuLock(PyMutexGuard<'a, T>),
@@ -108,6 +110,7 @@ impl<'a, T: ?Sized> BorrowedValueMut<'a, T> {
impl Deref for BorrowedValueMut<'_, T> {
type Target = T;
+
fn deref(&self) -> &T {
match self {
Self::RefMut(r) => r,
diff --git a/common/src/boxvec.rs b/common/src/boxvec.rs
index f5dd622f58..4f3928e56b 100644
--- a/common/src/boxvec.rs
+++ b/common/src/boxvec.rs
@@ -1,4 +1,4 @@
-// cspell:disable
+// spell-checker:disable
//! An unresizable vector backed by a `Box<[T]>`
#![allow(clippy::needless_lifetimes)]
@@ -38,33 +38,33 @@ macro_rules! panic_oob {
}
impl BoxVec {
- pub fn new(n: usize) -> BoxVec {
- BoxVec {
+ pub fn new(n: usize) -> Self {
+ Self {
xs: Box::new_uninit_slice(n),
len: 0,
}
}
#[inline]
- pub fn len(&self) -> usize {
+ pub const fn len(&self) -> usize {
self.len
}
#[inline]
- pub fn is_empty(&self) -> bool {
+ pub const fn is_empty(&self) -> bool {
self.len() == 0
}
#[inline]
- pub fn capacity(&self) -> usize {
+ pub const fn capacity(&self) -> usize {
self.xs.len()
}
- pub fn is_full(&self) -> bool {
+ pub const fn is_full(&self) -> bool {
self.len() == self.capacity()
}
- pub fn remaining_capacity(&self) -> usize {
+ pub const fn remaining_capacity(&self) -> usize {
self.capacity() - self.len()
}
@@ -336,6 +336,7 @@ impl BoxVec {
impl Deref for BoxVec {
type Target = [T];
+
#[inline]
fn deref(&self) -> &[T] {
unsafe { slice::from_raw_parts(self.as_ptr(), self.len()) }
@@ -354,6 +355,7 @@ impl DerefMut for BoxVec {
impl<'a, T> IntoIterator for &'a BoxVec {
type Item = &'a T;
type IntoIter = slice::Iter<'a, T>;
+
fn into_iter(self) -> Self::IntoIter {
self.iter()
}
@@ -363,6 +365,7 @@ impl<'a, T> IntoIterator for &'a BoxVec {
impl<'a, T> IntoIterator for &'a mut BoxVec {
type Item = &'a mut T;
type IntoIter = slice::IterMut<'a, T>;
+
fn into_iter(self) -> Self::IntoIter {
self.iter_mut()
}
@@ -374,6 +377,7 @@ impl<'a, T> IntoIterator for &'a mut BoxVec {
impl IntoIterator for BoxVec {
type Item = T;
type IntoIter = IntoIter;
+
fn into_iter(self) -> IntoIter {
IntoIter { index: 0, v: self }
}
@@ -589,7 +593,7 @@ where
T: Clone,
{
fn clone(&self) -> Self {
- let mut new = BoxVec::new(self.capacity());
+ let mut new = Self::new(self.capacity());
new.extend(self.iter().cloned());
new
}
@@ -672,8 +676,8 @@ pub struct CapacityError {
impl CapacityError {
/// Create a new `CapacityError` from `element`.
- pub fn new(element: T) -> CapacityError {
- CapacityError { element }
+ pub const fn new(element: T) -> Self {
+ Self { element }
}
/// Extract the overflowing element
diff --git a/common/src/cformat.rs b/common/src/cformat.rs
index e62ffca65e..b553f0b6b1 100644
--- a/common/src/cformat.rs
+++ b/common/src/cformat.rs
@@ -76,8 +76,9 @@ pub enum CFloatType {
}
impl CFloatType {
- fn case(self) -> Case {
+ const fn case(self) -> Case {
use CFloatType::*;
+
match self {
ExponentLower | PointDecimalLower | GeneralLower => Case::Lower,
ExponentUpper | PointDecimalUpper | GeneralUpper => Case::Upper,
@@ -100,12 +101,12 @@ pub enum CFormatType {
}
impl CFormatType {
- pub fn to_char(self) -> char {
+ pub const fn to_char(self) -> char {
match self {
- CFormatType::Number(x) => x as u8 as char,
- CFormatType::Float(x) => x as u8 as char,
- CFormatType::Character(x) => x as u8 as char,
- CFormatType::String(x) => x as u8 as char,
+ Self::Number(x) => x as u8 as char,
+ Self::Float(x) => x as u8 as char,
+ Self::Character(x) => x as u8 as char,
+ Self::String(x) => x as u8 as char,
}
}
}
@@ -118,7 +119,7 @@ pub enum CFormatPrecision {
impl From for CFormatPrecision {
fn from(quantity: CFormatQuantity) -> Self {
- CFormatPrecision::Quantity(quantity)
+ Self::Quantity(quantity)
}
}
@@ -135,10 +136,10 @@ bitflags! {
impl CConversionFlags {
#[inline]
- pub fn sign_string(&self) -> &'static str {
- if self.contains(CConversionFlags::SIGN_CHAR) {
+ pub const fn sign_string(&self) -> &'static str {
+ if self.contains(Self::SIGN_CHAR) {
"+"
- } else if self.contains(CConversionFlags::BLANK_SIGN) {
+ } else if self.contains(Self::BLANK_SIGN) {
" "
} else {
""
@@ -171,12 +172,15 @@ pub trait FormatChar: Copy + Into + From {
impl FormatBuf for String {
type Char = char;
+
fn chars(&self) -> impl Iterator- {
(**self).chars()
}
+
fn len(&self) -> usize {
self.len()
}
+
fn concat(mut self, other: Self) -> Self {
self.extend([other]);
self
@@ -187,6 +191,7 @@ impl FormatChar for char {
fn to_char_lossy(self) -> char {
self
}
+
fn eq_char(self, c: char) -> bool {
self == c
}
@@ -194,12 +199,15 @@ impl FormatChar for char {
impl FormatBuf for Wtf8Buf {
type Char = CodePoint;
+
fn chars(&self) -> impl Iterator
- {
self.code_points()
}
+
fn len(&self) -> usize {
(**self).len()
}
+
fn concat(mut self, other: Self) -> Self {
self.extend([other]);
self
@@ -210,6 +218,7 @@ impl FormatChar for CodePoint {
fn to_char_lossy(self) -> char {
self.to_char_lossy()
}
+
fn eq_char(self, c: char) -> bool {
self == c
}
@@ -217,12 +226,15 @@ impl FormatChar for CodePoint {
impl FormatBuf for Vec {
type Char = u8;
+
fn chars(&self) -> impl Iterator
- {
self.iter().copied()
}
+
fn len(&self) -> usize {
self.len()
}
+
fn concat(mut self, other: Self) -> Self {
self.extend(other);
self
@@ -233,6 +245,7 @@ impl FormatChar for u8 {
fn to_char_lossy(self) -> char {
self.into()
}
+
fn eq_char(self, c: char) -> bool {
char::from(self) == c
}
@@ -325,7 +338,7 @@ impl CFormatSpec {
_ => &num_chars,
};
let fill_chars_needed = width.saturating_sub(num_chars);
- let fill_string: T = CFormatSpec::compute_fill_string(fill_char, fill_chars_needed);
+ let fill_string: T = Self::compute_fill_string(fill_char, fill_chars_needed);
if !fill_string.is_empty() {
if self.flags.contains(CConversionFlags::LEFT_ADJUST) {
@@ -348,7 +361,7 @@ impl CFormatSpec {
_ => &num_chars,
};
let fill_chars_needed = width.saturating_sub(num_chars);
- let fill_string: T = CFormatSpec::compute_fill_string(fill_char, fill_chars_needed);
+ let fill_string: T = Self::compute_fill_string(fill_char, fill_chars_needed);
if !fill_string.is_empty() {
// Don't left-adjust if precision-filling: that will always be prepending 0s to %d
@@ -393,6 +406,7 @@ impl CFormatSpec {
Some(&(CFormatQuantity::Amount(1).into())),
)
}
+
pub fn format_bytes(&self, bytes: &[u8]) -> Vec {
let bytes = if let Some(CFormatPrecision::Quantity(CFormatQuantity::Amount(precision))) =
self.precision
@@ -706,14 +720,14 @@ pub enum CFormatPart {
impl CFormatPart {
#[inline]
- pub fn is_specifier(&self) -> bool {
- matches!(self, CFormatPart::Spec { .. })
+ pub const fn is_specifier(&self) -> bool {
+ matches!(self, Self::Spec { .. })
}
#[inline]
- pub fn has_key(&self) -> bool {
+ pub const fn has_key(&self) -> bool {
match self {
- CFormatPart::Spec(s) => s.mapping_key.is_some(),
+ Self::Spec(s) => s.mapping_key.is_some(),
_ => false,
}
}
@@ -803,6 +817,7 @@ impl
CFormatStrOrBytes {
impl IntoIterator for CFormatStrOrBytes {
type Item = (usize, CFormatPart);
type IntoIter = std::vec::IntoIter;
+
fn into_iter(self) -> Self::IntoIter {
self.parts.into_iter()
}
diff --git a/common/src/encodings.rs b/common/src/encodings.rs
index c444e27a5a..39ca266126 100644
--- a/common/src/encodings.rs
+++ b/common/src/encodings.rs
@@ -121,6 +121,7 @@ impl ops::Add for StrSize {
}
}
}
+
impl ops::AddAssign for StrSize {
fn add_assign(&mut self, rhs: Self) {
self.bytes += rhs.bytes;
@@ -133,9 +134,14 @@ struct DecodeError<'a> {
rest: &'a [u8],
err_len: Option,
}
+
/// # Safety
/// `v[..valid_up_to]` must be valid utf8
-unsafe fn make_decode_err(v: &[u8], valid_up_to: usize, err_len: Option) -> DecodeError<'_> {
+const unsafe fn make_decode_err(
+ v: &[u8],
+ valid_up_to: usize,
+ err_len: Option,
+) -> DecodeError<'_> {
let (valid_prefix, rest) = unsafe { v.split_at_unchecked(valid_up_to) };
let valid_prefix = unsafe { core::str::from_utf8_unchecked(valid_prefix) };
DecodeError {
@@ -152,6 +158,7 @@ enum HandleResult<'a> {
reason: &'a str,
},
}
+
fn decode_utf8_compatible(
mut ctx: Ctx,
errors: &E,
diff --git a/common/src/fileutils.rs b/common/src/fileutils.rs
index 5a0d380e20..e9a93947c1 100644
--- a/common/src/fileutils.rs
+++ b/common/src/fileutils.rs
@@ -256,7 +256,7 @@ pub mod windows {
}
}
- fn attributes_to_mode(attr: u32) -> u16 {
+ const fn attributes_to_mode(attr: u32) -> u16 {
let mut m = 0;
if attr & FILE_ATTRIBUTE_DIRECTORY != 0 {
m |= libc::S_IFDIR | 0o111; // IFEXEC for user,group,other
@@ -362,6 +362,7 @@ pub mod windows {
}
}
}
+
pub fn stat_basic_info_to_stat(info: &FILE_STAT_BASIC_INFORMATION) -> StatStruct {
use windows_sys::Win32::Storage::FileSystem;
use windows_sys::Win32::System::Ioctl;
diff --git a/common/src/float_ops.rs b/common/src/float_ops.rs
index b3c90d0ac6..b431e79313 100644
--- a/common/src/float_ops.rs
+++ b/common/src/float_ops.rs
@@ -2,7 +2,7 @@ use malachite_bigint::{BigInt, ToBigInt};
use num_traits::{Float, Signed, ToPrimitive, Zero};
use std::f64;
-pub fn decompose_float(value: f64) -> (f64, i32) {
+pub const fn decompose_float(value: f64) -> (f64, i32) {
if 0.0 == value {
(0.0, 0i32)
} else {
@@ -63,7 +63,7 @@ pub fn gt_int(value: f64, other_int: &BigInt) -> bool {
}
}
-pub fn div(v1: f64, v2: f64) -> Option {
+pub const fn div(v1: f64, v2: f64) -> Option {
if v2 != 0.0 { Some(v1 / v2) } else { None }
}
diff --git a/common/src/format.rs b/common/src/format.rs
index 4c1ce6c5c2..9b2a37d450 100644
--- a/common/src/format.rs
+++ b/common/src/format.rs
@@ -1,6 +1,8 @@
-// cspell:ignore ddfe
+// spell-checker:ignore ddfe
use itertools::{Itertools, PeekingNext};
+use malachite_base::num::basic::floats::PrimitiveFloat;
use malachite_bigint::{BigInt, Sign};
+use num_complex::Complex64;
use num_traits::FromPrimitive;
use num_traits::{Signed, cast::ToPrimitive};
use rustpython_literal::float;
@@ -38,23 +40,23 @@ impl FormatParse for FormatConversion {
}
impl FormatConversion {
- pub fn from_char(c: CodePoint) -> Option {
+ pub fn from_char(c: CodePoint) -> Option {
match c.to_char_lossy() {
- 's' => Some(FormatConversion::Str),
- 'r' => Some(FormatConversion::Repr),
- 'a' => Some(FormatConversion::Ascii),
- 'b' => Some(FormatConversion::Bytes),
+ 's' => Some(Self::Str),
+ 'r' => Some(Self::Repr),
+ 'a' => Some(Self::Ascii),
+ 'b' => Some(Self::Bytes),
_ => None,
}
}
- fn from_string(text: &Wtf8) -> Option {
+ fn from_string(text: &Wtf8) -> Option {
let mut chars = text.code_points();
if chars.next()? != '!' {
return None;
}
- FormatConversion::from_char(chars.next()?)
+ Self::from_char(chars.next()?)
}
}
@@ -67,12 +69,12 @@ pub enum FormatAlign {
}
impl FormatAlign {
- fn from_char(c: CodePoint) -> Option {
+ fn from_char(c: CodePoint) -> Option {
match c.to_char_lossy() {
- '<' => Some(FormatAlign::Left),
- '>' => Some(FormatAlign::Right),
- '=' => Some(FormatAlign::AfterSign),
- '^' => Some(FormatAlign::Center),
+ '<' => Some(Self::Left),
+ '>' => Some(Self::Right),
+ '=' => Some(Self::AfterSign),
+ '^' => Some(Self::Center),
_ => None,
}
}
@@ -125,6 +127,15 @@ impl FormatParse for FormatGrouping {
}
}
+impl From<&FormatGrouping> for char {
+ fn from(fg: &FormatGrouping) -> Self {
+ match fg {
+ FormatGrouping::Comma => ',',
+ FormatGrouping::Underscore => '_',
+ }
+ }
+}
+
#[derive(Debug, PartialEq)]
pub enum FormatType {
String,
@@ -141,7 +152,7 @@ pub enum FormatType {
}
impl From<&FormatType> for char {
- fn from(from: &FormatType) -> char {
+ fn from(from: &FormatType) -> Self {
match from {
FormatType::String => 's',
FormatType::Binary => 'b',
@@ -279,6 +290,7 @@ impl FormatSpec {
pub fn parse(text: impl AsRef) -> Result {
Self::_parse(text.as_ref())
}
+
fn _parse(text: &Wtf8) -> Result {
// get_integer in CPython
let (conversion, text) = FormatConversion::parse(text);
@@ -288,6 +300,9 @@ impl FormatSpec {
let (zero, text) = parse_zero(text);
let (width, text) = parse_number(text)?;
let (grouping_option, text) = FormatGrouping::parse(text);
+ if let Some(grouping) = &grouping_option {
+ Self::validate_separator(grouping, text)?;
+ }
let (precision, text) = parse_precision(text)?;
let (format_type, text) = FormatType::parse(text);
if !text.is_empty() {
@@ -299,7 +314,7 @@ impl FormatSpec {
align = align.or(Some(FormatAlign::AfterSign));
}
- Ok(FormatSpec {
+ Ok(Self {
conversion,
fill,
align,
@@ -312,6 +327,20 @@ impl FormatSpec {
})
}
+ fn validate_separator(grouping: &FormatGrouping, text: &Wtf8) -> Result<(), FormatSpecError> {
+ let mut chars = text.code_points().peekable();
+ match chars.peek().and_then(|cp| CodePoint::to_char(*cp)) {
+ Some(c) if c == ',' || c == '_' => {
+ if c == char::from(grouping) {
+ Err(FormatSpecError::UnspecifiedFormat(c, c))
+ } else {
+ Err(FormatSpecError::ExclusiveFormat(',', '_'))
+ }
+ }
+ _ => Ok(()),
+ }
+ }
+
fn compute_fill_string(fill_char: CodePoint, fill_chars_needed: i32) -> Wtf8Buf {
(0..fill_chars_needed).map(|_| fill_char).collect()
}
@@ -327,7 +356,7 @@ impl FormatSpec {
let magnitude_int_str = parts.next().unwrap().to_string();
let dec_digit_cnt = magnitude_str.len() as i32 - magnitude_int_str.len() as i32;
let int_digit_cnt = disp_digit_cnt - dec_digit_cnt;
- let mut result = FormatSpec::separate_integer(magnitude_int_str, inter, sep, int_digit_cnt);
+ let mut result = Self::separate_integer(magnitude_int_str, inter, sep, int_digit_cnt);
if let Some(part) = parts.next() {
result.push_str(&format!(".{part}"))
}
@@ -350,11 +379,11 @@ impl FormatSpec {
// separate with 0 padding
let padding = "0".repeat(diff as usize);
let padded_num = format!("{padding}{magnitude_str}");
- FormatSpec::insert_separator(padded_num, inter, sep, sep_cnt)
+ Self::insert_separator(padded_num, inter, sep, sep_cnt)
} else {
// separate without padding
let sep_cnt = (magnitude_len - 1) / inter;
- FormatSpec::insert_separator(magnitude_str, inter, sep, sep_cnt)
+ Self::insert_separator(magnitude_str, inter, sep, sep_cnt)
}
}
@@ -392,7 +421,7 @@ impl FormatSpec {
}
}
- fn get_separator_interval(&self) -> usize {
+ const fn get_separator_interval(&self) -> usize {
match self.format_type {
Some(FormatType::Binary | FormatType::Octal | FormatType::Hex(_)) => 4,
Some(FormatType::Decimal | FormatType::Number(_) | FormatType::FixedPoint(_)) => 3,
@@ -404,20 +433,18 @@ impl FormatSpec {
fn add_magnitude_separators(&self, magnitude_str: String, prefix: &str) -> String {
match &self.grouping_option {
Some(fg) => {
- let sep = match fg {
- FormatGrouping::Comma => ',',
- FormatGrouping::Underscore => '_',
- };
+ let sep = char::from(fg);
let inter = self.get_separator_interval().try_into().unwrap();
let magnitude_len = magnitude_str.len();
- let width = self.width.unwrap_or(magnitude_len) as i32 - prefix.len() as i32;
- let disp_digit_cnt = cmp::max(width, magnitude_len as i32);
- FormatSpec::add_magnitude_separators_for_char(
- magnitude_str,
- inter,
- sep,
- disp_digit_cnt,
- )
+ let disp_digit_cnt = if self.fill == Some('0'.into())
+ && self.align == Some(FormatAlign::AfterSign)
+ {
+ let width = self.width.unwrap_or(magnitude_len) as i32 - prefix.len() as i32;
+ cmp::max(width, magnitude_len as i32)
+ } else {
+ magnitude_len as i32
+ };
+ Self::add_magnitude_separators_for_char(magnitude_str, inter, sep, disp_digit_cnt)
}
None => magnitude_str,
}
@@ -617,6 +644,123 @@ impl FormatSpec {
}
}
+ pub fn format_complex(&self, num: &Complex64) -> Result {
+ let (formatted_re, formatted_im) = self.format_complex_re_im(num)?;
+ // Enclose in parentheses if there is no format type and formatted_re is not empty
+ let magnitude_str = if self.format_type.is_none() && !formatted_re.is_empty() {
+ format!("({formatted_re}{formatted_im})")
+ } else {
+ format!("{formatted_re}{formatted_im}")
+ };
+ if let Some(FormatAlign::AfterSign) = &self.align {
+ return Err(FormatSpecError::AlignmentFlag);
+ }
+ match &self.fill.unwrap_or(' '.into()).to_char() {
+ Some('0') => Err(FormatSpecError::ZeroPadding),
+ _ => self.format_sign_and_align(&AsciiStr::new(&magnitude_str), "", FormatAlign::Right),
+ }
+ }
+
+ fn format_complex_re_im(&self, num: &Complex64) -> Result<(String, String), FormatSpecError> {
+ // Format real part
+ let mut formatted_re = String::new();
+ if num.re != 0.0 || num.re.is_negative_zero() || self.format_type.is_some() {
+ let sign_re = if num.re.is_sign_negative() && !num.is_nan() {
+ "-"
+ } else {
+ match self.sign.unwrap_or(FormatSign::Minus) {
+ FormatSign::Plus => "+",
+ FormatSign::Minus => "",
+ FormatSign::MinusOrSpace => " ",
+ }
+ };
+ let re = self.format_complex_float(num.re)?;
+ formatted_re = format!("{sign_re}{re}");
+ }
+ // Format imaginary part
+ let sign_im = if num.im.is_sign_negative() && !num.im.is_nan() {
+ "-"
+ } else if formatted_re.is_empty() {
+ ""
+ } else {
+ "+"
+ };
+ let im = self.format_complex_float(num.im)?;
+ Ok((formatted_re, format!("{sign_im}{im}j")))
+ }
+
+ fn format_complex_float(&self, num: f64) -> Result {
+ self.validate_format(FormatType::FixedPoint(Case::Lower))?;
+ let precision = self.precision.unwrap_or(6);
+ let magnitude = num.abs();
+ let magnitude_str = match &self.format_type {
+ Some(FormatType::Decimal)
+ | Some(FormatType::Binary)
+ | Some(FormatType::Octal)
+ | Some(FormatType::Hex(_))
+ | Some(FormatType::String)
+ | Some(FormatType::Character)
+ | Some(FormatType::Number(Case::Upper))
+ | Some(FormatType::Percentage) => {
+ let ch = char::from(self.format_type.as_ref().unwrap());
+ Err(FormatSpecError::UnknownFormatCode(ch, "complex"))
+ }
+ Some(FormatType::FixedPoint(case)) => Ok(float::format_fixed(
+ precision,
+ magnitude,
+ *case,
+ self.alternate_form,
+ )),
+ Some(FormatType::GeneralFormat(case)) | Some(FormatType::Number(case)) => {
+ let precision = if precision == 0 { 1 } else { precision };
+ Ok(float::format_general(
+ precision,
+ magnitude,
+ *case,
+ self.alternate_form,
+ false,
+ ))
+ }
+ Some(FormatType::Exponent(case)) => Ok(float::format_exponent(
+ precision,
+ magnitude,
+ *case,
+ self.alternate_form,
+ )),
+ None => match magnitude {
+ magnitude if magnitude.is_nan() => Ok("nan".to_owned()),
+ magnitude if magnitude.is_infinite() => Ok("inf".to_owned()),
+ _ => match self.precision {
+ Some(precision) => Ok(float::format_general(
+ precision,
+ magnitude,
+ Case::Lower,
+ self.alternate_form,
+ true,
+ )),
+ None => {
+ if magnitude.fract() == 0.0 {
+ Ok(magnitude.trunc().to_string())
+ } else {
+ Ok(magnitude.to_string())
+ }
+ }
+ },
+ },
+ }?;
+ match &self.grouping_option {
+ Some(fg) => {
+ let sep = char::from(fg);
+ let inter = self.get_separator_interval().try_into().unwrap();
+ let len = magnitude_str.len() as i32;
+ let separated_magnitude =
+ FormatSpec::add_magnitude_separators_for_char(magnitude_str, inter, sep, len);
+ Ok(separated_magnitude)
+ }
+ None => Ok(magnitude_str),
+ }
+ }
+
fn format_sign_and_align(
&self,
magnitude_str: &T,
@@ -640,27 +784,26 @@ impl FormatSpec {
"{}{}{}",
sign_str,
magnitude_str,
- FormatSpec::compute_fill_string(fill_char, fill_chars_needed)
+ Self::compute_fill_string(fill_char, fill_chars_needed)
),
FormatAlign::Right => format!(
"{}{}{}",
- FormatSpec::compute_fill_string(fill_char, fill_chars_needed),
+ Self::compute_fill_string(fill_char, fill_chars_needed),
sign_str,
magnitude_str
),
FormatAlign::AfterSign => format!(
"{}{}{}",
sign_str,
- FormatSpec::compute_fill_string(fill_char, fill_chars_needed),
+ Self::compute_fill_string(fill_char, fill_chars_needed),
magnitude_str
),
FormatAlign::Center => {
let left_fill_chars_needed = fill_chars_needed / 2;
let right_fill_chars_needed = fill_chars_needed - left_fill_chars_needed;
- let left_fill_string =
- FormatSpec::compute_fill_string(fill_char, left_fill_chars_needed);
+ let left_fill_string = Self::compute_fill_string(fill_char, left_fill_chars_needed);
let right_fill_string =
- FormatSpec::compute_fill_string(fill_char, right_fill_chars_needed);
+ Self::compute_fill_string(fill_char, right_fill_chars_needed);
format!("{left_fill_string}{sign_str}{magnitude_str}{right_fill_string}")
}
})
@@ -677,7 +820,7 @@ struct AsciiStr<'a> {
}
impl<'a> AsciiStr<'a> {
- fn new(inner: &'a str) -> Self {
+ const fn new(inner: &'a str) -> Self {
Self { inner }
}
}
@@ -690,6 +833,7 @@ impl CharLen for AsciiStr<'_> {
impl Deref for AsciiStr<'_> {
type Target = str;
+
fn deref(&self) -> &Self::Target {
self.inner
}
@@ -701,11 +845,14 @@ pub enum FormatSpecError {
PrecisionTooBig,
InvalidFormatSpecifier,
UnspecifiedFormat(char, char),
+ ExclusiveFormat(char, char),
UnknownFormatCode(char, &'static str),
PrecisionNotAllowed,
NotAllowed(&'static str),
UnableToConvert,
CodeNotInRange,
+ ZeroPadding,
+ AlignmentFlag,
NotImplemented(char, &'static str),
}
@@ -724,7 +871,7 @@ pub enum FormatParseError {
impl FromStr for FormatSpec {
type Err = FormatSpecError;
fn from_str(s: &str) -> Result {
- FormatSpec::parse(s)
+ Self::parse(s)
}
}
@@ -738,7 +885,7 @@ pub enum FieldNamePart {
impl FieldNamePart {
fn parse_part(
chars: &mut impl PeekingNext- ,
- ) -> Result