Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions src/dotenv/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -244,6 +244,9 @@ def run_command(command: List[str], env: Dict[str, str]) -> None:
except FileNotFoundError:
print(f"Command not found: {command[0]}", file=sys.stderr)
sys.exit(1)
except PermissionError:
print(f"Permission denied: {command[0]}", file=sys.stderr)
sys.exit(1)

_, _ = p.communicate()

Expand All @@ -254,3 +257,6 @@ def run_command(command: List[str], env: Dict[str, str]) -> None:
except FileNotFoundError:
print(f"Command not found: {command[0]}", file=sys.stderr)
sys.exit(1)
except PermissionError:
print(f"Permission denied: {command[0]}", file=sys.stderr)
sys.exit(1)
45 changes: 45 additions & 0 deletions tests/test_cli_permission.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
"""Command-launch permission failures should be CLI diagnostics, not tracebacks."""

import os
import sys

import pytest

from dotenv.cli import run_command
from tests.test_lib import run_dotenv


@pytest.mark.parametrize("platform", ["linux", "win32"])
def test_permission_error_is_reported_on_both_launch_paths(
monkeypatch, capsys, platform
):
import dotenv.cli as module

def denied(*args, **kwargs):
raise PermissionError(13, "Permission denied")

monkeypatch.setattr(module.sys, "platform", platform)
monkeypatch.setattr(module.os, "execvpe", denied)
monkeypatch.setattr(module, "Popen", denied, raising=False)
with pytest.raises(SystemExit) as caught:
run_command(["no-execute"], {"PRIVATE_TOKEN": "must-not-appear"})
assert caught.value.code == 1
output = capsys.readouterr().err
assert "Permission denied: no-execute" in output
assert "must-not-appear" not in output


@pytest.mark.skipif(sys.platform == "win32", reason="POSIX execute bits")
@pytest.mark.parametrize("target_kind", ["file", "directory"])
def test_run_nonexecutable_target_has_no_traceback(tmp_path, target_kind):
(tmp_path / ".env").write_text("A=b\n")
target = tmp_path / "not-executable"
if target_kind == "file":
target.write_text("#!/bin/sh\nexit 0\n")
target.chmod(0o600)
else:
target.mkdir()
result = run_dotenv(["run", os.fspath(target)], cwd=tmp_path)
assert result.returncode == 1
assert "Permission denied:" in result.stderr
assert "Traceback" not in result.stderr
Loading