See More

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