[ Web Proxy ]
URL:
Viewing: https://raw.githubusercontent.com/python/pymanager/main/tests/test_scriptutils.py [Back]  [Original]

import base64
import pytest
import subprocess
import sys
import textwrap

from pathlib import PurePath

import manage.scriptutils as SU
from manage.scriptutils import (
    find_install_from_script,
    _find_shebang_command,
    _parse_shebang,
    _read_script,
    _replace_templates,
    NewEncoding,
    _maybe_quote,
    quote_args,
    split_args
)

def _fake_install(v, **kwargs):
    try:
        kwargs["run-for"] = kwargs.pop("run_for")
    except LookupError:
        pass
    return {
        "company": kwargs.get("company", "Test"),
        "id": f"test-{v}",
        "tag": str(v),
        "version": str(v),
        "prefix": PurePath(f"./pkgs/test-{v}"),
        "executable": PurePath(f"./pkgs/test-{v}/test-binary-{v}.exe"),
        **kwargs
    }

INSTALLS = [
    _fake_install("1.0",
                  run_for=[dict(tag="1.0", target="./test-binary-1.0.exe"),
                           dict(tag="1.0", target="./test-binary-1.0-win.exe", windowed=1)],
                  alias=[dict(name="test1.0.exe", target="./test-binary-1.0.exe"),
                         dict(name="testw1.0.exe", target="./test-binary-w-1.0.exe", windowed=1)],
    ),
    _fake_install("1.1",
                  default=1,
                  run_for=[dict(tag="1.1", target="./test-binary-1.1.exe"),
                           dict(tag="1.1", target="./test-binary-1.1-win.exe", windowed=1)],
                  alias=[dict(name="test1.1.exe", target="./test-binary-1.1.exe"),
                         dict(name="testw1.1.exe", target="./test-binary-w-1.1.exe", windowed=1)],
    ),
    _fake_install("1.3.1", company="PythonCore"),
    _fake_install("1.3.2", company="PythonOther"),
    _fake_install("2.0", alias=[{"name": "test2.0.exe", "target": "./test-binary-2.0.exe"}]),
]

@pytest.mark.parametrize("script, expect", [
    ("", None),
    ("#! /usr/bin/test1.0\n#! /usr/bin/test2.0\n", "1.0"),
    ("#! /usr/bin/test2.0\n#! /usr/bin/test1.0\n", "2.0"),
    ("#! /usr/bin/test1.0.exe\n#! /usr/bin/test2.0\n", "1.0"),
    ("#!test1.0.exe\n", "1.0"),
    ("#!test1.1.exe\n", "1.1"),
    ("#!test1.2.exe\n", None),
    ("#!test-binary-1.1.exe\n", "1.1"),
    ("#!.\\pkgs\\test-1.1\\test-binary-1.1.exe\n", "1.1"),
    ("#!.\\pkgs\\test-1.0\\test-binary-1.1.exe\n", None),
    ("#! /usr/bin/env test1.0\n", "1.0"),
    ("#! /usr/bin/env test2.0\n", "2.0"),
    ("#! /usr/bin/env -S test2.0\n", "2.0"),
    # Legacy handling specifically for "python"
    ("#! /usr/bin/python1.3.1", "1.3.1"),
    ("#! /usr/bin/env python1.3.1", "1.3.1"),
    ("#! /usr/bin/python1.3.2", None),
])
def test_read_shebang(fake_config, tmp_path, script, expect):
    fake_config.installs.extend(INSTALLS)
    if expect:
        expect = [i for i in INSTALLS if i["tag"] == expect][0]

    script_py = tmp_path / "test-script.py"
    if isinstance(script, str):
        script = script.encode()
    script_py.write_bytes(script)
    try:
        actual = find_install_from_script(fake_config, script_py, windowed=False)
        assert expect == actual
    except LookupError:
        assert not expect


@pytest.mark.parametrize("script, expect, windowed", [
    # Non-windowed alias from non-windowed launcher uses default 'executable'
    ("#! /usr/bin/test1.0\n", "test-binary-1.0.exe", False),
    # Non-windowed alias from windowed launcher uses first windowed 'run-for'
    ("#! /usr/bin/test1.0\n", "test-binary-1.0-win.exe", True),
    # Windowed alias from either launcher uses the discovered alias
    ("#! /usr/bin/testw1.0\n", "test-binary-w-1.0.exe", False),
    ("#! /usr/bin/testw1.0\n", "test-binary-w-1.0.exe", True),

    # No windowed option for 2.0, so picks the regular executable
    ("#! /usr/bin/test2.0\n", "test-binary-2.0.exe", False),
    ("#! /usr/bin/test2.0\n", "test-binary-2.0.exe", True),
    ("#! /usr/bin/testw2.0\n", None, False),
    ("#! /usr/bin/testw2.0\n", None, True),
    ("#!test1.0.exe\n", "test-binary-1.0.exe", False),
    ("#!test1.0.exe\n", "test-binary-1.0-win.exe", True),
    ("#!testw1.0.exe\n", "test-binary-w-1.0.exe", False),
    ("#!testw1.0.exe\n", "test-binary-w-1.0.exe", True),
    ("#!test1.1.exe\n", "test-binary-1.1.exe", False),
    ("#!test1.1.exe\n", "test-binary-1.1-win.exe", True),
    ("#!testw1.1.exe\n", "test-binary-w-1.1.exe", False),
    ("#!testw1.1.exe\n", "test-binary-w-1.1.exe", True),

    # Matching executable name won't be overridden by windowed setting
    ("#!test-binary-1.1.exe\n", "test-binary-1.1.exe", False),
    ("#!test-binary-1.1.exe\n", "test-binary-1.1.exe", True),
    ("#! /usr/bin/env test1.0\n", "test-binary-1.0.exe", False),
    ("#! /usr/bin/env test1.0\n", "test-binary-1.0-win.exe", True),
    ("#! /usr/bin/env testw1.0\n", "test-binary-w-1.0.exe", False),
    ("#! /usr/bin/env testw1.0\n", "test-binary-w-1.0.exe", True),

    # Default name will use default 'executable' or first windowed 'run-for'
    ("#! /usr/bin/python\n", "test-binary-1.1.exe", False),
    ("#! /usr/bin/python\n", "test-binary-1.1-win.exe", True),
    ("#! /usr/bin/pythonw\n", "test-binary-1.1-win.exe", False),
    ("#! /usr/bin/pythonw\n", "test-binary-1.1-win.exe", True),
])
def test_read_shebang_windowed(fake_config, tmp_path, script, expect, windowed):
    fake_config.installs.extend(INSTALLS)

    script_py = tmp_path / "test-script.py"
    if isinstance(script, str):
        script = script.encode()
    script_py.write_bytes(script)
    try:
        actual = find_install_from_script(fake_config, script_py, windowed=windowed)
        assert actual["executable"].match(expect)
    except LookupError:
        assert not expect


def test_default_py_shebang(fake_config, tmp_path):
    inst = _fake_install("1.0", company="PythonCore", prefix=PurePath("C:\\TestRoot"), default=True)
    inst["run-for"] = [
        dict(name="othername.exe", target=".\\test-binary-1.0.exe"),
        dict(name="othernamew.exe", target=".\\test-binary-1.0-w.exe", windowed=1),
    ]
    fake_config.installs[:] = [inst]

    def t(n):
        return _find_shebang_command(fake_config, n, windowed=False)

    # Finds the install's default executable
    assert t("python")["executable"].match("test-binary-1.0.exe")
    assert t("py")["executable"].match("test-binary-1.0.exe")
    assert t("python3")["executable"].match("test-binary-1.0.exe")
    assert t("python1.0")["executable"].match("test-binary-1.0.exe")
    # Finds the install's run-for executable with windowed=1
    assert t("pythonw")["executable"].match("test-binary-1.0-w.exe")
    assert t("pyw")["executable"].match("test-binary-1.0-w.exe")
    assert t("pythonw3")["executable"].match("test-binary-1.0-w.exe")
    assert t("pythonw1.0")["executable"].match("test-binary-1.0-w.exe")


def test_unmanaged_py_shebang(fake_config, tmp_path):
    inst = _fake_install("1.0", company="PythonCore", prefix=PurePath("C:\\TestRoot"))
    inst["unmanaged"] = 1
    inst["run-for"] = [
        dict(name="python.exe", target=".\\python.exe"),
        dict(name="pythonw.exe", target=".\\pythonw.exe", windowed=1),
    ]
    fake_config.installs[:] = [inst]

    def t(n):
        return _find_shebang_command(fake_config, n, windowed=False)

    # Finds the install's default executable
    assert t("python")["executable"].match("test-binary-1.0.exe")
    assert t("python1.0")["executable"].match("test-binary-1.0.exe")
    # Finds the install's run-for executable with windowed=1
    assert t("pythonw")["executable"].match("pythonw.exe")
    assert t("pythonw1.0")["executable"].match("pythonw.exe")


@pytest.mark.parametrize("script, expect", [
    ("# not a coding comment", None),
    ("# coding: utf-8-sig", None),
    ("# coding: utf-8", "utf-8"),
    ("# coding: ascii", "ascii"),
    ("# actually a coding: comment", "comment"),
    ("#~=~=~=coding:ascii=~=~=~=~", "ascii"),
    ("#! /usr/bin/env python\n# coding: ascii", None),
])
def test_read_coding_comment(fake_config, tmp_path, script, expect):
    script_py = tmp_path / "test-script.py"
    if isinstance(script, str):
        script = script.encode()
    script_py.write_bytes(script)
    try:
        _read_script(fake_config, script_py, "utf-8-sig", windowed=False)
    except NewEncoding as enc:
        assert enc.args[0] == expect
    except LookupError:
        assert not expect
    else:
        assert not expect


@pytest.mark.parametrize("arg, expect", [pytest.param(*a, id=a[0]) for a in [
    ('abc', 'abc'),
    ('a b c', '"a b c"'),
    ('abc ', '"abc "'),
    (' abc', '" abc"'),
    ('a1\\b\\c', 'a1\\b\\c'),
    ('a2\\ b', '"a2\\ b"'),
    ('a3\\b\\', 'a3\\b\\'),
    ('a4 b\\', '"a4 b\\\\"'),
    ('a5 b\\\\', '"a5 b\\\\\\\\"'),
    ('a1"b', 'a1\\"b'),
    ('a2\\"b', 'a2\\\\\\"b'),
    ('a3\\\\"b', 'a3\\\\\\\\\\"b'),
    ('a4\\\\\\"b', 'a4\\\\\\\\\\\\\\"b'),
    ('a5 "b', '"a5 \\"b"'),
    ('a6\\ "b', '"a6\\ \\"b"'),
    ('a7 \\"b', '"a7 \\\\\\"b"'),
]])
def test_quote_one_arg(arg, expect):
    # Test our expected result by passing it to Python and checking what it sees
    test_cmd = (
        'python -c "import base64, sys; '
        'expect = base64.b64decode(\'{}\').decode(); '
        'print(\'Expect:\', repr(expect), \' Actual:\', repr(sys.argv[1])); '
        'sys.exit(0 if expect == sys.argv[1] else 1)" {} END_OF_ARGS'
    ).format(base64.b64encode(arg.encode()).decode("ascii"), expect)
    subprocess.check_call(test_cmd, executable=sys.executable)
    # Test that our quote function produces the expected result
    assert expect == _maybe_quote(arg)


@pytest.mark.parametrize("arg, expect, expect_call", [pytest.param(*a, id=a[0]) for a in [
    ('"a1 b"', '"a1 b"', 'a1 b'),
    ('"a2" b"', '"a2\\" b"', 'a2" b'),
]])
def test_quote_one_quoted_arg(arg, expect, expect_call):
    # Test our expected result by passing it to Python and checking what it sees
    test_cmd = (
        'python -c "import base64, sys; '
        'expect = base64.b64decode(\'{}\').decode(); '
        'print(\'Expect:\', repr(expect), \' Actual:\', repr(sys.argv[1])); '
        'sys.exit(0 if expect == sys.argv[1] else 1)" {} END_OF_ARGS'
    ).format(base64.b64encode(expect_call.encode()).decode("ascii"), expect)
    subprocess.check_call(test_cmd, executable=sys.executable)
    # Test that our quote function produces the expected result
    assert expect == _maybe_quote(arg)


# We're not going to try too hard here - most of the tricky logic is covered
# by the previous couple of tests.
@pytest.mark.parametrize("args, expect", [pytest.param(*a, id=a[1]) for a in [
    (["a1", "b", "c"], 'a1 b c'),
    (["a2 b", "c d"], '"a2 b" "c d"'),
    (['a3"b', 'c"d', 'e f'], 'a3\\"b c\\"d "e f"'),
    (['a4"b c"d', 'e f'], '"a4\\"b c\\"d" "e f"'),
    (['a5\\b\\', 'c\\d'], 'a5\\b\\ c\\d'),
    (['a6\\b\\ c\\', 'd\\e'], '"a6\\b\\ c\\\\" d\\e'),
]])
def test_quote_args(args, expect):
    # Test our expected result by passing it to Python and checking what it sees
    test_cmd = (
        'python -c "import base64, sys; '
        'expect = base64.b64decode(\'{}\').decode().split(\'\\0\'); '
        'print(\'Expect:\', repr(expect), \' Actual:\', repr(sys.argv)); '
        'sys.exit(0 if expect == sys.argv[1:-1] else 1)" {} END_OF_ARGS'
    ).format(base64.b64encode('\0'.join(args).encode()).decode("ascii"), expect)
    subprocess.check_call(test_cmd, executable=sys.executable)
    # Test that our quote function produces the expected result
    assert expect == quote_args(args)
    # Test that our split function produces the same result
    assert args == split_args(expect), expect


@pytest.mark.parametrize("line, expect_id, expect_line", [pytest.param(*a, id=a[0]) for a in [
    ("#!/usr/bin/python", "Test1", None),
    ("#!/usr/bin/python -Es", "Test1", None),
    ("#! /usr/bin/pythonw", "Test1", None),
    ("#!  /usr/bin/python2", "Test2", None),
    ("#!   /usr/bin/pythonw2", "Test2", None),
    ("#!  /usr/bin/python3", "PythonCore3", None),
    ("#!   /usr/bin/pythonw3", "PythonCore3", None),
    ("#!  custom", None, "#!CUSTOM"),
    ("#!  CUSTOM", None, "#!CUSTOM"),
    ("#! full line custom", None, "#!CUSTOM2"),
    ("#! FULL LINE CUSTOM", None, "#!CUSTOM2"),
    ("#!full line custom with extra", None, None),
    ("custom", None, None),
    ("full line custom", None, None),
]])
def test_shebang_templates(fake_config, line, expect_id, expect_line):
    fake_config.installs = [
        dict(id="Test1", company="Test", tag="1", default=True),
        dict(id="Test2", company="Test", tag="2"),
        dict(id="Test3", company="Test", tag="3.2"),
        dict(id="PythonCore3", company="PythonCore", tag="3.2"),
    ]
    fake_config.shebang_templates = {
        "/usr/bin/python": "py",
        "/usr/bin/pythonw": "pyw",
        "/usr/bin/python2": "py -V:Test/2",
        "/usr/bin/pythonw2": "pyw -V:Test/2",
        "/usr/bin/python3": "py -3.2",
        "/usr/bin/pythonw3": "pyw -3.2",
        "custom": "CUSTOM",
        "full line custom": "CUSTOM2",
    }
    actual, actual_line = _replace_templates(fake_config, line, False)
    if expect_id:
        assert actual
        assert expect_id == actual["id"]
    elif expect_line:
        assert expect_line == actual_line
    else:
        assert not actual
        assert not actual_line


def test_parse_shebang_templates(monkeypatch):
    class Cmd:
        shebang_templates = True

    expect = {"an": "install"}
    monkeypatch.setattr(SU, "_replace_templates", lambda *a: (expect, None))
    actual = _parse_shebang(Cmd, "Anything at all")
    assert expect == actual

    expect = {"id": "COMMAND"}
    monkeypatch.setattr(SU, "_replace_templates", lambda *a: (None, "#!COMMAND"))
    monkeypatch.setattr(SU, "_find_shebang_command", lambda cmd, full_cmd, **kw: {"id": full_cmd})
    actual = _parse_shebang(Cmd, "Anything at all", windowed=False)
    assert expect == actual

Web Proxy Viewer  |  New URL  |  Original Page