File: test_recursion_limit.py

package info (click to toggle)
pyinstaller 6.18.0%2Bds-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 11,820 kB
  • sloc: python: 41,828; ansic: 12,123; makefile: 171; sh: 131; xml: 19
file content (85 lines) | stat: -rw-r--r-- 3,347 bytes parent folder | download | duplicates (2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
#-----------------------------------------------------------------------------
# Copyright (c) 2005-2023, PyInstaller Development Team.
#
# Distributed under the terms of the GNU General Public License (version 2
# or later) with exception for distributing the bootloader.
#
# The full license is in the file COPYING.txt, distributed with this software.
#
# SPDX-License-Identifier: (GPL-2.0-or-later WITH Bootloader-exception)
#-----------------------------------------------------------------------------

import pytest

from PyInstaller.lib.modulegraph import modulegraph
from PyInstaller import configure
from PyInstaller import __main__ as pyi_main


@pytest.fixture
def large_import_chain(tmp_path):
    # Create package directory
    pkg = tmp_path / 'pkg'
    pkg.mkdir()
    # Create pkg/__init__py that imports first sub-package (a)
    (pkg / '__init__.py').write_text("from . import a", encoding='utf-8')
    # Create sub-packages
    mod = None
    for alpha in "abcdefg":
        # Overwrite last module of previous sub-package to import this sub-package.
        if mod:
            mod.write_text(f"import pkg.{alpha}", encoding='utf-8')
        # Create sub-package
        subpkg = pkg / alpha
        subpkg.mkdir()
        # Create sub-package's __init__.py, which imports first module.
        (subpkg / '__init__.py').write_text(f"from . import {alpha}000", encoding='utf-8')
        # Create modules; each module imports its next sibling (except the very last one, which we overwrite at the
        # start of next loop iteration).
        for num in range(250):
            mod = subpkg / f"{alpha}{num:03}.py"
            mod.write_text(f"from . import {alpha}{num + 1:03}", encoding='utf-8')

    script = tmp_path / 'script.py'
    script.write_text('import pkg', encoding='utf-8')

    return [str(tmp_path)], str(script)


def test_recursion_too_deep(large_import_chain):
    """
    modulegraph is recursive and triggers RecursionError if nesting of imported modules is too deep.
    This can be worked around by increasing recursion limit.

    With the default recursion limit (1000), the recursion error occurs at about 115 modules, with limit 2000
    (as tested below) at about 240 modules, and with limit 5000 at about 660 modules.
    """
    path, script = large_import_chain
    mg = modulegraph.ModuleGraph(path)
    # Increase recursion limit to 5 times of the default. Given the module import chain created above
    # this still should fail.
    with pytest.raises(RecursionError):
        mg.add_script(script)


def test_RecursionError_prints_message(tmp_path, large_import_chain, monkeypatch):
    """
    modulegraph is recursive and triggers RecursionError if nesting of imported modules is too deep.
    Ensure an informative message is printed if RecursionError occurs.
    """
    path, script = large_import_chain

    default_args = [
        '--specpath', str(tmp_path),
        '--distpath', str(tmp_path / 'dist'),
        '--workpath', str(tmp_path / 'build'),
        '--path', str(tmp_path),
    ]  # yapf: disable

    pyi_args = [script, *default_args]
    PYI_CONFIG = configure.get_config()
    PYI_CONFIG['cachedir'] = str(tmp_path)

    with pytest.raises(SystemExit) as execinfo:
        pyi_main.run(pyi_args, PYI_CONFIG)
    assert "sys.setrecursionlimit" in str(execinfo.value)