File: test_shebangs.py

package info (click to toggle)
pdm 2.26.6-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 5,512 kB
  • sloc: python: 26,467; sh: 314; javascript: 34; makefile: 26
file content (119 lines) | stat: -rw-r--r-- 4,119 bytes parent folder | download
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
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
import io
import os
import shlex
import zipfile
from pathlib import Path

import pytest

from pdm.environments.local import PythonLocalEnvironment, _get_shebang_path, _is_console_script, _replace_shebang


@pytest.mark.parametrize(
    "executable,is_launcher,expected",
    [
        ("/usr/bin/python", True, b"/usr/bin/python"),
        ("/usr/bin/python", False, b"/usr/bin/python"),
        ("/a path/with space/python", False, shlex.quote("/a path/with space/python").encode("utf-8")),
    ],
)
def test_get_shebang_path(executable, is_launcher, expected):
    assert _get_shebang_path(executable, is_launcher) == expected


def test_is_console_script_unix_and_binary():
    # Non-Windows path: startswith #! => True
    assert _is_console_script(b"#!/usr/bin/env python\nprint('x')\n") is True
    # Undecodable bytes => False
    assert _is_console_script(b"\xff\xfe\xfd") is False


@pytest.mark.skipif(os.name == "nt", reason="Regex branch differs on Windows")
@pytest.mark.parametrize(
    "content,new_exec,expected_prefix",
    [
        (b"#!/usr/bin/python\nprint('hi')\n", b"/new/python", b"#!/new/python\n"),
        (
            b"#!/bin/sh\n'''exec' '/old path/python' \"$0\" \"$@\"\n' '''\nprint('x')\n",
            b"/new/python",
            b"#!/bin/sh\n'''exec' /new/python \"$0\"",
        ),
    ],
)
def test_replace_shebang_unix(tmp_path: Path, content: bytes, new_exec: bytes, expected_prefix: bytes):
    f = tmp_path / "script"
    f.write_bytes(content)
    _replace_shebang(f, new_exec)
    data = f.read_bytes()
    assert data.startswith(expected_prefix)


def test_update_shebangs_changes_scripts_header(project):
    env = PythonLocalEnvironment(project)
    # Create a fake script under the environment's scripts dir
    scripts = Path(env.get_paths()["scripts"])  # ensure exists
    script = scripts / "demo"
    script.write_text("#!/usr/bin/python\nprint('ok')\n", encoding="utf-8")

    new_path = "/opt/python/bin/python"
    # Exercise update_shebangs
    env.update_shebangs(new_path)

    text = script.read_text(encoding="utf-8")
    assert text.splitlines()[0] == f"#!{new_path}"


def test_update_shebangs_ignores_non_target_files_and_dirs(project):
    env = PythonLocalEnvironment(project)
    scripts = Path(env.get_paths()["scripts"])  # ensure exists
    # A non-target extension file
    other = scripts / "notascript.sh"
    other.write_text("#!/usr/bin/python\nprint('no change')\n", encoding="utf-8")
    # A directory
    d = scripts / "adir"
    d.mkdir(exist_ok=True)

    before_other = other.read_text(encoding="utf-8")
    env.update_shebangs("/opt/python/bin/python")
    after_other = other.read_text(encoding="utf-8")
    # Should be unchanged
    assert before_other == after_other


def test_replace_shebang_early_return_when_not_console(tmp_path: Path):
    f = tmp_path / "no_shebang"
    original = b"print('hello')\n"
    f.write_bytes(original)
    _replace_shebang(f, b"/new/python")
    assert f.read_bytes() == original


def _zip_with_main_bytes() -> bytes:
    buf = io.BytesIO()
    with zipfile.ZipFile(buf, mode="w") as zf:
        zf.writestr("__main__.py", "print('ok')\n")
    return buf.getvalue()


def test_is_console_script_windows_zip(monkeypatch):
    monkeypatch.setattr(os, "name", "nt")
    data = _zip_with_main_bytes()
    assert _is_console_script(data) is True
    # Not a valid zip => False (falls back to text detection which returns False)
    assert _is_console_script(b"not a zip") is False


def test_is_console_script_windows_text_shebang(monkeypatch):
    # On Windows, non-zip scripts with shebang should still be recognized
    monkeypatch.setattr(os, "name", "nt")
    assert _is_console_script(b"#!/usr/bin/env python\r\nprint('x')\r\n") is True


def test_replace_shebang_windows_zip_no_change(monkeypatch, tmp_path: Path):
    monkeypatch.setattr(os, "name", "nt")
    f = tmp_path / "script.exe"
    data = _zip_with_main_bytes()
    f.write_bytes(data)
    _replace_shebang(f, b"C:/Python/python.exe")
    # Should remain unchanged since regex won't match zip content
    assert f.read_bytes() == data