wire_shell_path's existing-setup regex required a character before PATH=, so it missed bare assignments such as Fedora's ~/.bashrc ( PATH="$HOME/.local/bin:$HOME/bin:$PATH") and Debian's ~/.profile. The installer then appended its own line to .bashrc, .profile and .bash_profile, and because Fedora's .bash_profile sources .bashrc, login shells got ~/.local/bin on PATH several times. Match bare assignments too, and make the appended line a no-op when PATH already contains ~/.local/bin.
90 lines
3.1 KiB
Python
90 lines
3.1 KiB
Python
"""install.sh wires ~/.local/bin into shell startup files without duplicating it (#123424).
|
|
|
|
Distro skeletons put ~/.local/bin on PATH with a bare assignment
|
|
(`PATH="$HOME/.local/bin:$PATH"`), not `export PATH=...`; that must count as
|
|
existing setup. The appended line must also be a no-op when PATH already has the
|
|
entry, because Fedora's ~/.bash_profile sources ~/.bashrc in every login shell.
|
|
"""
|
|
import os
|
|
from pathlib import Path
|
|
import shlex
|
|
import subprocess
|
|
|
|
import pytest
|
|
|
|
pytestmark = pytest.mark.platforms("posix")
|
|
INSTALL_SH = Path(__file__).resolve().parents[3] / "scripts" / "install.sh"
|
|
MARKER = "# Hermes Agent command"
|
|
|
|
# Fedora /etc/skel (bash-5.2): ~/.bashrc guards and prepends with a bare assignment.
|
|
FEDORA_BASHRC = """\
|
|
# .bashrc
|
|
|
|
# Source global definitions
|
|
if [ -f /etc/bashrc ]; then
|
|
. /etc/bashrc
|
|
fi
|
|
|
|
# User specific environment
|
|
if ! [[ "$PATH" =~ "$HOME/.local/bin:$HOME/bin:" ]]; then
|
|
PATH="$HOME/.local/bin:$HOME/bin:$PATH"
|
|
fi
|
|
export PATH
|
|
"""
|
|
FEDORA_BASH_PROFILE = """\
|
|
# .bash_profile
|
|
|
|
# Get the aliases and functions
|
|
if [ -f ~/.bashrc ]; then
|
|
. ~/.bashrc
|
|
fi
|
|
|
|
# User specific environment and startup programs
|
|
"""
|
|
# Debian /etc/skel/.profile tail.
|
|
DEBIAN_PROFILE = """\
|
|
# set PATH so it includes user's private bin if it exists
|
|
if [ -d "$HOME/.local/bin" ] ; then
|
|
PATH="$HOME/.local/bin:$PATH"
|
|
fi
|
|
"""
|
|
|
|
|
|
def _wire(home: Path, runs: int = 1) -> None:
|
|
env = {k: v for k, v in os.environ.items() if k not in ("CI", "GITHUB_ACTIONS")}
|
|
env.update(HOME=str(home), HERMES_HOME=str(home / "hermes"), SHELL="/bin/bash",
|
|
NO_COLOR="1", TERM="dumb", CI="true")
|
|
script = f"source {shlex.quote(INSTALL_SH.as_posix())} --manifest\n" + "wire_shell_path\n" * runs
|
|
result = subprocess.run(["bash", "-c", script], env=env, stdin=subprocess.DEVNULL,
|
|
capture_output=True, text=True, timeout=30)
|
|
assert result.returncode == 0, result.stdout + result.stderr
|
|
|
|
|
|
def _login_path(home: Path) -> list[str]:
|
|
result = subprocess.run(["bash", "-lc", 'printf %s "$PATH"'],
|
|
env={"HOME": str(home), "PATH": "/usr/local/bin:/usr/bin:/bin"},
|
|
stdin=subprocess.DEVNULL, capture_output=True, text=True, timeout=30)
|
|
assert result.returncode == 0, result.stderr
|
|
return result.stdout.split(":")
|
|
|
|
|
|
def _home(tmp_path: Path, **files: str) -> Path:
|
|
(tmp_path / ".local" / "bin").mkdir(parents=True)
|
|
for name, text in files.items():
|
|
(tmp_path / name).write_text(text, encoding="utf-8")
|
|
return tmp_path
|
|
|
|
|
|
@pytest.mark.parametrize("name, text", [(".bashrc", FEDORA_BASHRC), (".profile", DEBIAN_PROFILE)])
|
|
def test_bare_assignment_counts_as_existing_setup(tmp_path, name, text):
|
|
home = _home(tmp_path, **{name: text})
|
|
_wire(home)
|
|
assert (home / name).read_text(encoding="utf-8-sig") == text
|
|
|
|
|
|
def test_fedora_login_shell_has_local_bin_once(tmp_path):
|
|
home = _home(tmp_path, **{".bashrc": FEDORA_BASHRC, ".bash_profile": FEDORA_BASH_PROFILE})
|
|
_wire(home, runs=2)
|
|
assert (home / ".bash_profile").read_text(encoding="utf-8-sig").count(MARKER) == 1
|
|
assert _login_path(home).count(str(home / ".local" / "bin")) == 1
|