tests/test_with_sudo.py
Rama master · réplica de lectura del repositorio original.
#!/usr/bin/env python3
# SPDX-License-Identifier: GPL-3.0-or-later
# Copyright (c) 2008-2026 Andrew Ziem.
#
# This work is licensed under the terms of the GNU GPL, version 3 or
# later. See the COPYING file in the top-level directory.
"""
Utility script to discover and run tests marked for sudo execution.
Tests are marked with the @also_with_sudo decorator.
Usage:
make tests-with-sudo
"""
import inspect
import os
import pkgutil
import shlex
import shutil
import subprocess
import sys
import unittest
from importlib import import_module
# Add parent directory to path for imports
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
# Import common test utilities
# pylint: disable-next=wrong-import-position
import tests
# pylint: disable-next=wrong-import-position
from tests import common
# pylint: disable-next=wrong-import-position
from bleachbit import IS_POSIX
def discover_sudo_tests():
"""Discover all test methods marked with @also_with_sudo decorator"""
sudo_tests = []
# Scan all test modules for decorated tests, so modules cannot
# silently fall out of coverage when tests are added elsewhere.
test_modules = [
info.name
for info in pkgutil.iter_modules(tests.__path__, 'tests.')
if info.name.startswith('tests.Test')
]
# The nesting mirrors module, class, and method discovery.
# pylint: disable-next=too-many-nested-blocks
for module_name in test_modules:
try:
module = import_module(module_name)
# Find all test classes in the module
for name, obj in inspect.getmembers(module):
if inspect.isclass(obj) and issubclass(obj, unittest.TestCase):
# Find all test methods in the class
for method_name, method in inspect.getmembers(obj):
if (method_name.startswith('test_') and
hasattr(method, '_also_with_sudo')):
test_path = f"{module_name}.{name}.{method_name}"
sudo_tests.append(test_path)
except ImportError as e:
print(f"Warning: Could not import {module_name}: {e}")
return sudo_tests
def _build_test_command(test_list):
"""Return command list to run unittest (optionally under coverage)."""
coverage_runner = os.environ.get('BLEACHBIT_COVERAGE_RUNNER') or ''
coverage_file = os.environ.get('BLEACHBIT_COVERAGE_FILE') or ''
if coverage_runner.strip():
runner_parts = shlex.split(coverage_runner)
if runner_parts and not os.path.isabs(runner_parts[0]):
absolute = shutil.which(runner_parts[0])
if absolute:
runner_parts[0] = absolute
else:
runner_parts = [sys.executable]
return runner_parts + ['-m', 'unittest'] + test_list + ['-v'], coverage_file
def run_sudo_tests(test_list):
"""Run a list of tests with sudo privileges"""
if not test_list:
print("No tests marked with @requires_sudo found")
return True
print(f"Found {len(test_list)} tests marked for sudo execution:")
for test in test_list:
print(f" - {test}")
print(f"\n{'=' * 60}")
print("Running sudo tests...")
print('=' * 60)
base_cmd, coverage_file = _build_test_command(test_list)
if IS_POSIX and os.geteuid() == 0:
sudo_uid = os.getenv('SUDO_UID')
if sudo_uid and sudo_uid != '0':
print("\nDetected EUID=0 with SUDO_UID preserved; skipping nested sudo.")
cmd = base_cmd
else:
print("\nERROR: Do not run tests/test_with_sudo.py with sudo or as root.")
print("Run it unprivileged; it will invoke sudo internally.")
print("Try: make tests-with-sudo or python3 tests/test_with_sudo.py")
return False
else:
cmd = ['sudo', '-E'] + base_cmd
try:
with common.set_temporary_env('COVERAGE_FILE', coverage_file or None):
result = subprocess.run(cmd, check=False)
return result.returncode == 0
except Exception as e:
print(f"Error running sudo tests: {e}")
return False
def main():
"""Discover and run all tests marked with @also_with_sudo"""
sudo_tests = discover_sudo_tests()
if not sudo_tests:
print("No tests marked with @also_with_sudo found")
sys.exit(0)
if run_sudo_tests(sudo_tests):
print("\nAll tests with sudo passed")
sys.exit(0)
else:
print("\nSome tests with sudo failed")
sys.exit(1)
if __name__ == '__main__':
main()