blob: 9e180504c475ee4b312d1bf7cd81e54a4ceed154 [file] [log] [blame]
# Copyright 2023 The ChromiumOS Authors
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
"""Tests for os_util.py."""
import os
from pathlib import Path
from unittest import mock
import pytest
from chromite.utils import os_util
@pytest.fixture(name="as_root_user")
def _as_root_user(monkeypatch):
"""Monkeypatch the euid as 0."""
monkeypatch.setattr(os, "geteuid", lambda: 0)
yield
@pytest.fixture(name="as_non_root_user")
def _as_non_root_user(monkeypatch):
"""Monkeypatch the euid as non-0."""
monkeypatch.setattr(os, "geteuid", lambda: 1)
yield
# pylint: disable=unused-argument
def test_root_user_checks_as_root_user(as_root_user) -> None:
"""Test is_[non_]root_user as the root user."""
assert os_util.is_root_user()
os_util.assert_root_user()
assert not os_util.is_non_root_user()
with pytest.raises(AssertionError):
os_util.assert_non_root_user()
def test_root_user_checks_as_non_root_user(as_non_root_user) -> None:
"""Test is_[non_]root_user as a non-root user."""
assert os_util.is_non_root_user()
os_util.assert_non_root_user()
assert not os_util.is_root_user()
with pytest.raises(AssertionError):
os_util.assert_root_user()
def test_root_user_decorator_as_root(as_root_user) -> None:
"""Success case for require root user decorator."""
@os_util.require_root_user("Passes")
def passes() -> None:
pass
passes()
def test_root_user_decorator_as_non_root(as_non_root_user) -> None:
"""Failure case for require root user decorator."""
@os_util.require_root_user("Fails")
def fails() -> None:
pytest.fail("Allowed to execute as wrong user.")
with pytest.raises(AssertionError):
fails()
def test_non_root_user_decorator_as_root(as_root_user) -> None:
"""Failure case for require non-root user decorator."""
@os_util.require_non_root_user("Fails")
def fails() -> None:
pytest.fail("Allowed to execute as wrong user.")
with pytest.raises(AssertionError):
fails()
def test_non_root_user_decorator_as_non_root(as_non_root_user) -> None:
"""Success case for require non-root user decorator."""
@os_util.require_non_root_user("Passes")
def passes() -> None:
pass
passes()
@pytest.fixture(name="switch_to_sudo_user_mock")
def _switch_to_sudo_user_mock():
"""Mock out privileged APIs."""
with mock.patch.multiple(
os,
initgroups=mock.DEFAULT,
setresgid=mock.DEFAULT,
setresuid=mock.DEFAULT,
):
yield
def test_switch_to_sudo_user_saved(
as_root_user, switch_to_sudo_user_mock
) -> None:
"""Verify we switch state properly."""
os.environ.update(
{
"SUDO_GID": "123",
"SUDO_UID": "456",
"SUDO_USER": "testuser",
"USER": "root",
}
)
os_util.switch_to_sudo_user()
assert "SUDO_GID" not in os.environ
assert "SUDO_UID" not in os.environ
assert "SUDO_USER" not in os.environ
assert os.environ["USER"] == "testuser"
os.initgroups.assert_called_once_with("testuser", 123)
os.setresgid.assert_called_once_with(123, 123, -1)
os.setresuid.assert_called_once_with(456, 456, -1)
def test_switch_to_sudo_user_cleared(
as_root_user, switch_to_sudo_user_mock
) -> None:
"""Verify we switch state properly."""
os.environ.update(
{
"SUDO_GID": "123",
"SUDO_UID": "456",
"SUDO_USER": "testuser",
"USER": "root",
}
)
os_util.switch_to_sudo_user(clear_saved_id=True)
assert "SUDO_GID" not in os.environ
assert "SUDO_UID" not in os.environ
assert "SUDO_USER" not in os.environ
assert os.environ["USER"] == "testuser"
os.initgroups.assert_called_once_with("testuser", 123)
os.setresgid.assert_called_once_with(123, 123, 123)
os.setresuid.assert_called_once_with(456, 456, 456)
def test_non_root_user_home_as_root(
as_root_user, monkeypatch, tmp_path: Path
) -> None:
"""Test non-root-user-home as root user."""
user = "user"
user_home = tmp_path / "home" / user
user_home.mkdir(parents=True)
def expanduser(self, *_args, **_kwargs):
"""expanduser patch."""
assert str(self) == f"~{user}"
return user_home
monkeypatch.setattr(Path, "expanduser", expanduser)
monkeypatch.setenv("PORTAGE_USERNAME", user)
assert user_home == os_util.non_root_home()
def test_non_root_user_home_as_root_not_found(
as_root_user, monkeypatch
) -> None:
"""Test non-root-user-home as root user when no user found."""
env = os.environ.copy()
env.pop("PORTAGE_USERNAME", None)
env.pop("SUDO_USER", None)
monkeypatch.setattr(os, "environ", env)
with pytest.raises(os_util.UnknownNonRootUserError):
os_util.non_root_home()
def test_non_root_user_home_as_root_pwd_error(
as_root_user, monkeypatch
) -> None:
def expanduser(self, *_args, **_kwargs) -> None:
"""expanduser patch."""
raise RuntimeError("Error")
monkeypatch.setattr(Path, "expanduser", expanduser)
monkeypatch.setenv("PORTAGE_USERNAME", "user")
with pytest.raises(os_util.UnknownHomeDirectoryError):
os_util.non_root_home()
def test_get_non_root_user_portage_username(as_root_user, monkeypatch) -> None:
"""Test get_non_root_user from PORTAGE_USERNAME."""
user = "portage_username"
monkeypatch.setenv("PORTAGE_USERNAME", user)
assert user == os_util.get_non_root_user()
def test_get_non_root_user_sudo_user(as_root_user, monkeypatch) -> None:
"""Test get_non_root_user from SUDO_USER."""
user = "user"
env = os.environ.copy()
env.pop("PORTAGE_USERNAME", None)
env["SUDO_USER"] = user
monkeypatch.setattr(os, "environ", env)
assert user == os_util.get_non_root_user()
def test_get_non_root_user_no_user(as_root_user, monkeypatch) -> None:
"""Test get_non_root_user with no user."""
env = os.environ.copy()
env.pop("PORTAGE_USERNAME", None)
env.pop("SUDO_USER", None)
monkeypatch.setattr(os, "environ", env)
assert not os_util.get_non_root_user()
# pylint: enable=unused-argument