Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 7 additions & 3 deletions .github/CODEOWNERS
Original file line number Diff line number Diff line change
Expand Up @@ -290,9 +290,10 @@ Tools/jit/ @brandtbucher @savannahostrowski @diegorusso
InternalDocs/jit.md @brandtbucher @savannahostrowski @diegorusso @AA-Turner

# Lazy imports (PEP 810)
Objects/lazyimportobject.c @yhg1s @DinoV @pablogsal
Include/internal/pycore_lazyimportobject.h @yhg1s @DinoV @pablogsal
Lib/test/test_lazy_import @yhg1s @DinoV @pablogsal
.github/workflows/reusable-test-lazy-imports-all.yml @yhg1s @DinoV @pablogsal
Objects/lazyimportobject.c @yhg1s @DinoV @pablogsal
Include/internal/pycore_lazyimportobject.h @yhg1s @DinoV @pablogsal
Lib/test/test_lazy_import @yhg1s @DinoV @pablogsal

# Micro-op / μop / Tier 2 Optimiser
Python/optimizer.c @markshannon @Fidget-Spinner
Expand Down Expand Up @@ -655,5 +656,8 @@ Objects/**/clinic/
PC/**/clinic/
Python/**/clinic/

# Exclude Lazy Imports=all CI carve out file
Lib/test/lazy_imports_all_exclude.txt

# Exclude HTML IDs list
Doc/tools/removed-ids.txt
8 changes: 8 additions & 0 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -476,6 +476,12 @@ jobs:
name: hypothesis-example-db
path: ${{ env.CPYTHON_BUILDDIR }}/.hypothesis/examples/

test-lazy-imports-all:
name: 'Lazy imports enabled'
needs: build-context
if: fromJSON(needs.build-context.outputs.run-tests)
uses: ./.github/workflows/reusable-test-lazy-imports-all.yml

build-asan:
name: 'Address sanitizer'
runs-on: ${{ matrix.os }}
Expand Down Expand Up @@ -648,6 +654,7 @@ jobs:
- build-emscripten
- build-wasi
- test-hypothesis
- test-lazy-imports-all
- build-asan
- build-san
- cross-build-linux
Expand Down Expand Up @@ -705,4 +712,5 @@ jobs:
${{ !fromJSON(needs.build-context.outputs.run-ios) && 'build-ios,' || '' }}
${{ !fromJSON(needs.build-context.outputs.run-emscripten) && 'build-emscripten,' || '' }}
${{ !fromJSON(needs.build-context.outputs.run-wasi) && 'build-wasi,' || '' }}
${{ !fromJSON(needs.build-context.outputs.run-tests) && 'test-lazy-imports-all,' || '' }}
jobs: ${{ toJSON(needs) }}
79 changes: 79 additions & 0 deletions .github/workflows/reusable-test-lazy-imports-all.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
name: Reusable Lazy Imports Tests

# Run the CPython test suite with global lazy imports forced on
# (``-X lazy_imports=all``).
#
# Modules that are known to fail under lazy imports are listed in
# Lib/test/lazy_imports_all_exclude.txt and skipped here. Remove entries from
# that file as the modules are fixed so this workflow starts guarding them
# against regressions. Excluded modules are also checked separately so the
# workflow fails when one starts passing and its exclusion should be removed.

on:
workflow_call:

permissions:
contents: read

env:
FORCE_COLOR: 1

jobs:
test-lazy-imports-all:
name: 'Run Tests with lazy_imports=all'
runs-on: ubuntu-26.04
timeout-minutes: 60
env:
EXCLUDE_FILE: Lib/test/lazy_imports_all_exclude.txt
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
persist-credentials: false
- name: Register gcc problem matcher
run: echo "::add-matcher::.github/problem-matchers/gcc.json"
- name: Install dependencies
run: sudo ./.github/workflows/posix-deps-apt.sh
- name: Configure CPython
run: ./configure --config-cache --with-pydebug
- name: Build CPython
run: make -j4
- name: Display build info
run: make pythoninfo
- name: Verify lazy imports are fully enabled
run: ./python -X lazy_imports=all -c "import sys; assert sys.flags.lazy_imports == 1, sys.flags.lazy_imports; print('lazy imports all enabled')"
- name: Build test list (all tests minus the known-failing exclusions)
run: |
set -euo pipefail
./python -m test --list-tests > all_tests.txt
# Strip comments/blank lines from the exclusion file, then drop those
# exact test names (whole-line, fixed-string match) from the run list.
grep -vE '^\s*(#.*)?$' "$EXCLUDE_FILE" > exclude_tests.txt || true
grep -vxF -f exclude_tests.txt all_tests.txt > run_tests.txt
# Fail loudly if any exclusion entry matched nothing: a stale or
# mistyped name (or a change in `--list-tests` output) would otherwise
# silently stop excluding a module and let it fail the run.
stale=$(comm -23 <(sort -u exclude_tests.txt) <(sort -u all_tests.txt))
if [ -n "$stale" ]; then
echo "::error::Stale entries in $EXCLUDE_FILE (no longer match 'python -m test --list-tests'); remove or fix them:"
echo "$stale"
exit 1
fi
echo "Excluding $(wc -l < exclude_tests.txt) module(s); running $(wc -l < run_tests.txt) of $(wc -l < all_tests.txt)."
- name: Run tests with lazy imports
run: xvfb-run xargs -a run_tests.txt ./python -X lazy_imports=all -m test --fast-ci --timeout=900 < /dev/null
- name: Verify excluded tests still need exclusion
run: |
set -euo pipefail
unexpected_passes=()
while IFS= read -r test_name; do
[ -n "$test_name" ] || continue
echo "Checking excluded test: $test_name"
if xvfb-run ./python -X lazy_imports=all -m test --fast-ci --timeout=900 "$test_name"; then
unexpected_passes+=("$test_name")
fi
done < exclude_tests.txt
if [ "${#unexpected_passes[@]}" -ne 0 ]; then
echo "::error::These tests still appear in $EXCLUDE_FILE but now pass with -X lazy_imports=all. Remove them from the exclude file:"
printf '%s\n' "${unexpected_passes[@]}"
exit 1
fi
40 changes: 40 additions & 0 deletions Lib/test/lazy_imports_all_exclude.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
# Test modules that currently FAIL under global lazy imports
# (``-X lazy_imports=all`` / ``PYTHON_LAZY_IMPORTS=all``).
#
# The "Lazy Imports All" CI workflow
# (.github/workflows/reusable-test-lazy-imports-all.yml) runs the whole test
# suite with lazy_imports=all, skipping every module listed here. Exclusion is
# whole-module: a listed module is skipped entirely, so any passing tests it
# contains are not covered until its line is removed. As each module is fixed,
# delete its line so the workflow starts guarding it against regressions. The
# workflow also checks listed modules separately and fails if one now passes,
# so accidental fixes prompt cleanup of this file.
#
# Format: one test name per line, exactly as printed by
# ``python -m test --list-tests``. Lines starting with ``#`` and blank lines
# are ignored. Note that split test packages use a dotted path
# (e.g. test.test_future_stmt.test_future) while ordinary modules use the bare
# name (e.g. test_builtin).

test.test_inspect.test_inspect
test___all__
test__interpreters
test_builtin
test_clinic
test_crossinterp
test_datetime
test_generated_cases
test_heapq
test_import
test_importlib
test_json
test_pkg
test_profile
test_profiling
test_pyrepl
test_subprocess
test_symtable
test_tools
test_trace
test_type_annotations
test_unittest
13 changes: 13 additions & 0 deletions Lib/test/test_array.py
Original file line number Diff line number Diff line change
Expand Up @@ -1607,10 +1607,19 @@ class HalfFloatTest(FPTest, unittest.TestCase):
typecode = 'e'
minitemsize = 2

def test_overflows(self):
# Overflows half-float type:
self.assertRaises(OverflowError, array.array, self.typecode, [123456])
# Overflows also float type:
self.assertRaises(OverflowError, array.array, self.typecode, [1e300])

class FloatTest(FPTest, unittest.TestCase):
typecode = 'f'
minitemsize = 4

def test_overflows(self):
self.assertRaises(OverflowError, array.array, self.typecode, [1e300])

class DoubleTest(FPTest, unittest.TestCase):
typecode = 'd'
minitemsize = 8
Expand All @@ -1637,6 +1646,10 @@ class ComplexFloatTest(CFPTest, unittest.TestCase):
typecode = 'Zf'
minitemsize = 8

def test_overflows(self):
self.assertRaises(OverflowError, array.array, self.typecode, [1e300])
self.assertRaises(OverflowError, array.array, self.typecode, [1e300j])

class ComplexDoubleTest(CFPTest, unittest.TestCase):
typecode = 'Zd'
minitemsize = 16
Expand Down
4 changes: 2 additions & 2 deletions Lib/test/test_capi/test_marshal.py
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,7 @@ def test_write_object_to_file(self):
with self.assertRaises(SystemError):
write_object_to_file(NULL, filename, version)

with self.assertRaisesRegex(ValueError, 'unmarshallable object'):
with self.assertRaisesRegex(ValueError, 'cannot marshal object objects'):
write_object_to_file(UNMARSHALLABLE, filename, version)

def test_read_short_from_file(self):
Expand Down Expand Up @@ -225,7 +225,7 @@ def test_pymarshal_writeobjecttostring(self):
obj2 = marshal.loads(data)
self.check_object(obj2, obj)

with self.assertRaisesRegex(ValueError, 'unmarshallable object'):
with self.assertRaisesRegex(ValueError, 'cannot marshal object objects'):
writeobjecttostring(UNMARSHALLABLE, version)

with self.assertRaises(SystemError):
Expand Down
9 changes: 5 additions & 4 deletions Lib/test/test_cmd_line.py
Original file line number Diff line number Diff line change
Expand Up @@ -1069,8 +1069,9 @@ def test_python_legacy_windows_stdio(self):

@unittest.skipUnless(support.MS_WINDOWS, 'Test only applicable on Windows')
def test_python_legacy_windows_stdio_encoding(self):
# gh-86427: In the legacy mode the encoding of the standard streams
# is the encoding of the console.
# gh-86427: In the legacy mode the encoding of a standard stream is
# the encoding of the console it is connected to, which can differ
# for input and output.
import ctypes
kernel32 = ctypes.WinDLL('kernel32', use_last_error=True)
try:
Expand All @@ -1080,7 +1081,7 @@ def test_python_legacy_windows_stdio_encoding(self):
# We cannot use PIPE, because the standard streams should be
# connected to the console. So we use the exit code.
code = ("import sys; sys.exit(sys.stdin.encoding != 'cp850' or "
"sys.stdout.encoding != 'cp850')")
"sys.stdout.encoding != 'cp437')")
env = os.environ.copy()
env['PYTHONLEGACYWINDOWSSTDIO'] = '1'
env['PYTHONUTF8'] = '0'
Expand All @@ -1091,7 +1092,7 @@ def test_python_legacy_windows_stdio_encoding(self):
try:
if not kernel32.SetConsoleCP(850):
self.skipTest('cannot set the console input code page')
if not kernel32.SetConsoleOutputCP(850):
if not kernel32.SetConsoleOutputCP(437):
self.skipTest('cannot set the console output code page')
proc = subprocess.run([sys.executable, '-c', code], env=env,
stdin=fin, stdout=fout,
Expand Down
Loading
Loading