tests/TestCommon.py
Rama master · réplica de lectura del repositorio original.
# 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.
"""
Test case for Common
"""
# standard imports
import os
import subprocess
import tempfile
# first party imports
from tests import common
class CommonTestCase(common.BleachbitTestCase):
"""Test case for Common."""
_valid_language_codes = [
'be@latin',
'C.UTF-8',
'C.utf8',
'C',
'de_DE.iso88591',
'de-CH', # seen in Fedora in Docker
'en_US',
'en-US',
'en',
'es_419',
'fr_FR.utf8',
'ja_JP.SJIS',
'ko_KR.eucKR',
'nb_NO.ISO-8859-1',
'POSIX',
'ru_RU.KOI8-R',
'zh_Hant',
'be_BY.utf8@latin',
]
_invalid_language_codes = ['e', 'en_', 'english', 'en_US_', '123',
'en_us', 'en_US.',
'en_us.utf8',
'en_us.UTF-8',
'utf8',
'UTF-8',
'.utf8',
'.UTF-8',
'en@boldquot.header',
'tor@default.service',
'',
[],
0,
None]
def test_assertIsLanguageCode_hardcoded_valid(self):
"""Test assertIsLanguageCode() accepts hard-coded valid codes"""
for code in self._valid_language_codes:
with self.subTest(code=code):
self.assertIsLanguageCode(code)
def test_assertIsLanguageCode_hardcoded_invalid(self):
"""Test assertIsLanguageCode() rejects hard-coded invalid codes"""
invalid_codes = list(self._invalid_language_codes)
invalid_codes.extend(
[code + ' ' for code in self._valid_language_codes])
invalid_codes.extend(
[' ' + code for code in self._valid_language_codes])
for code in invalid_codes:
with self.subTest(code=code):
with self.assertRaises(AssertionError, msg=f'Expected exception for {code}'):
self.assertIsLanguageCode(code)
def test_assertIsLanguageCode_live(self):
"""Test assertIsLanguageCode() using live data"""
from bleachbit import locale_dir
locale_dirs = list(set([locale_dir, '/usr/share/locale']))
lang_codes = []
# Skip directories that are not valid language codes
# 'UTF-8' is a macOS-specific LC_CTYPE directory, not a locale.
skip_dirs = {'l10n', 'UTF-8'}
for this_dir in locale_dirs:
if not os.path.isdir(this_dir):
continue
for lang_code in os.listdir(this_dir):
if not os.path.isdir(os.path.join(this_dir, lang_code)):
continue
if lang_code in skip_dirs:
continue
lang_codes.append(lang_code)
if os.path.exists('/etc/locale.alias'):
with open('/etc/locale.alias', 'r', encoding='utf-8') as f:
for line in f:
line = line.strip()
if not line.startswith('#'):
parts = line.split()
if len(parts) > 1:
lang_codes.append(parts[1])
for lang_code in lang_codes:
if lang_code in common.SKIP_ALIAS_CODES:
continue
self.assertIsLanguageCode(lang_code)
def test_environment(self):
"""Test for important environment variables"""
# useful for researching
# grep -Poh "([\\$%]\w+)" cleaners/*xml | cut -b2- | sort | uniq -i
# bleachbit/init.py sets these variables on posix, if they do not exist.
# Docker does not set USER
envs = {'posix': ['HOME', 'XDG_CACHE_HOME', 'XDG_CONFIG_HOME', 'XDG_DATA_HOME'],
'nt': ['AppData', 'CommonAppData', 'Documents', 'ProgramFiles', 'UserProfile', 'WinDir']}
for env in envs[os.name]:
e = os.getenv(env)
self.assertIsNotNone(e, f"Environment variable {env} is not set")
self.assertNotEqual(
e.strip(), "", f"Environment variable {env} is empty")
def test_xdist_tempdir_isolation(self):
"""Use a private temporary directory in each xdist worker"""
worker_id = os.environ.get('PYTEST_XDIST_WORKER')
if not worker_id:
self.skipTest('requires pytest-xdist worker')
options_dir = os.environ.get('BLEACHBIT_TEST_OPTIONS_DIR')
self.assertIsNotNone(
options_dir, 'conftest must set BLEACHBIT_TEST_OPTIONS_DIR')
# The per-worker keying is the isolation mechanism: without it every
# worker would share one directory and the environment checks below
# would still pass, so also require the worker id in the name.
self.assertIn(worker_id, os.path.basename(options_dir))
for env_var in ('TEMP', 'TMP', 'TMPDIR'):
env_dir = os.environ.get(env_var)
self.assertIsNotNone(env_dir, f'conftest must set {env_var}')
self.assertExists(env_dir)
self.assertTrue(
os.path.samefile(options_dir, env_dir),
f'{env_var} must point to the worker temp directory')
temp_dir = tempfile.gettempdir()
self.assertExists(temp_dir)
self.assertTrue(os.path.samefile(options_dir, temp_dir))
# setUpClass creates self.tempdir via tempfile.mkdtemp(); with
# tempfile.tempdir pointed at the worker dir it must live inside it.
# samefile (not string equality) is used so path case or symlinks
# do not cause spurious failures.
self.assertExists(self.tempdir)
self.assertTrue(os.path.samefile(
options_dir, os.path.commonpath((options_dir, self.tempdir))))
def test_testall_import_leaves_bleachbit_unloaded(self):
"""TestAll sets the options dir before bleachbit is imported"""
import sys
# An early import would pin the real config dir before main() sets it
code = 'import sys, tests.TestAll; print("bleachbit" in sys.modules)'
root = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
proc = subprocess.run([sys.executable, '-c', code],
capture_output=True, text=True, timeout=60,
check=False, cwd=root)
self.assertEqual(proc.returncode, 0, proc.stderr)
self.assertEqual(proc.stdout.strip(), 'False', proc.stderr)
def _run_conftest(self, options_dir, worker=None):
"""Run conftest.py in a child process and return its options dir"""
import sys
env = dict(os.environ, BLEACHBIT_TEST_OPTIONS_DIR=options_dir)
env.pop('PYTEST_XDIST_WORKER', None)
if worker:
env['PYTEST_XDIST_WORKER'] = worker
code = ('import os, runpy, sys; runpy.run_path(sys.argv[1]); '
'print(os.environ["BLEACHBIT_TEST_OPTIONS_DIR"])')
conftest = os.path.join(os.path.dirname(
os.path.abspath(__file__)), 'conftest.py')
proc = subprocess.run([sys.executable, '-c', code, conftest],
env=env, capture_output=True, text=True,
check=False, timeout=60)
self.assertEqual(proc.returncode, 0, proc.stderr)
return proc.stdout.strip()
def test_conftest_worker_skips_controller_dir(self):
"""An xdist worker does not adopt the controller's options dir"""
# The worker id can turn up in the controller's random suffix
controller_dir = os.path.join(
self.tempdir, 'bleachbit-test-master-k0gw1zq_')
other_worker_dir = os.path.join(
controller_dir, 'bleachbit-test-gw0-abcd1234')
os.makedirs(other_worker_dir)
worker_dir = self._run_conftest(controller_dir, 'gw1')
self.assertNotEqual(controller_dir, worker_dir)
self.assertExists(other_worker_dir)
def test_conftest_keeps_preset_options_dir(self):
"""conftest does not remove an options dir the caller set"""
preset_dir = os.path.join(self.tempdir, 'preset')
preset_file = os.path.join(preset_dir, 'bleachbit.ini')
common.touch_file(preset_file)
self.assertEqual(preset_dir, self._run_conftest(preset_dir))
self.assertExists(preset_file)
@common.skipUnlessWindows
def test_have_root_windows(self):
"""Windows does not have POSIX root privileges"""
self.assertFalse(common.have_root())
def test_get_put_env(self):
"""Unit test for get_env() and put_env()"""
self.assertIsNone(common.get_env('PUTENV_TEST'))
common.put_env('PUTENV_TEST', '1')
self.assertEqual('1', common.get_env('PUTENV_TEST'))
common.put_env('PUTENV_TEST', None)
self.assertIsNone(common.get_env('PUTENV_TEST'))
@common.skipUnlessWindows
def test_get_opened_windows_titles(self):
"""Unit test for get_opened_windows_titles()"""
titles = common.get_opened_windows_titles()
self.assertIsInstance(titles, list, "Should return a list")
self.assertGreater(len(titles), 0,
f"Should have at least one window title. Got: {titles}")
# Verify all items are strings and none are empty
for title in titles:
self.assertIsInstance(
title, str, f"All titles should be strings, got {type(title)}")
self.assertGreater(len(title.strip()), 0,
f"Window title should not be empty or whitespace-only: '{title}'")
def test_mock_missing_package(self):
"""Test mock_missing_package context manager"""
# The imports are what this test exercises, so each one lives in its
# own function and is returned, rather than left as an unused name
def import_logging():
import logging
return logging
def import_cli():
import bleachbit.CLI
return bleachbit.CLI
def import_sys():
import sys
return sys
# Inside the context, importing logging should raise an errorl.
with common.mock_missing_package('logging', clear_prefixes=('bleachbit.CLI',)):
self.assertRaises(ImportError, import_logging)
self.assertRaises(ImportError, import_cli)
# Importing sys should be unaffected.
self.assertIsNotNone(import_sys())
# Outside the context, importing should work.
self.assertIsNotNone(import_logging())
self.assertIsNotNone(import_cli())
def test_set_temporary_env(self):
"""Test set_temporary_env context manager"""
# Test with an environment variable that was not set before.
self.assertIsNone(os.environ.get('TEST_VAR_NEW'))
with common.set_temporary_env('TEST_VAR_NEW', 'test_value'):
self.assertEqual('test_value', os.environ.get('TEST_VAR_NEW'))
# Outside the context, the environment variable should be unset.
self.assertIsNone(os.environ.get('TEST_VAR_NEW'))
# Test with an environment variable that was set before.
os.environ['TEST_VAR_EXISTING'] = 'original_value'
with common.set_temporary_env('TEST_VAR_EXISTING', 'override_value'):
self.assertEqual('override_value',
os.environ.get('TEST_VAR_EXISTING'))
# Outside the context, the environment variable should be restored.
self.assertEqual('original_value', os.environ.get('TEST_VAR_EXISTING'))
del os.environ['TEST_VAR_EXISTING']
def test_touch_file(self):
"""Unit test for touch_file"""
fn = os.path.join(self.tempdir, 'test_touch_file')
self.assertNotExists(fn)
# Create empty file.
common.touch_file(fn)
from bleachbit.FileUtilities import getsize
self.assertExists(fn)
self.assertEqual(0, getsize(fn))
# Increase size of file.
fsize = 2**13
with open(fn, "w", encoding="utf-8") as f:
f.write(' ' * fsize)
self.assertEqual(fsize, getsize(fn))
# Do not truncate.
common.touch_file(fn)
self.assertExists(fn)
self.assertEqual(fsize, getsize(fn))
def test_assertExists_relative_path(self):
"""Unit test for assertExists with relative path"""
# Create a file in the current directory
os.chdir(self.tempdir)
relative_fn = 'test_relative_file.txt'
common.touch_file(relative_fn)
self.assertExists(relative_fn)
os.remove(relative_fn)
self.assertNotExists(relative_fn)
self.assertNotExists('this-does-not-exist')
class ResolveLocaleDirTestCase(common.BleachbitTestCase):
"""Test case for bleachbit._resolve_locale_dir()"""
def test_macos_app_bundle(self):
"""On macOS, prefer Contents/locale (one level above
Contents/Resources) over the generic /usr/share/locale/
fallback, when it exists."""
from bleachbit import _resolve_locale_dir
exe_path = '/Applications/BleachBit.app/Contents/Resources'
bundle_dir = os.path.normpath(os.path.join(exe_path, '..', 'locale'))
result = _resolve_locale_dir(
exe_path, is_linux=False, is_mac=True, is_windows=False,
is_netbsd=False, is_bsd=False,
path_exists=lambda p: p == bundle_dir)
self.assertEqual(result, bundle_dir)
def test_macos_no_bundle_falls_back(self):
"""On macOS, when neither the local, AppImage-style, nor
bundle-relative locale directory exists, fall back to the
generic system path."""
from bleachbit import _resolve_locale_dir
result = _resolve_locale_dir(
'/some/dev/path', is_linux=False, is_mac=True,
is_windows=False, is_netbsd=False, is_bsd=False,
path_exists=lambda p: False)
self.assertEqual(result, '/usr/share/locale/')
def test_current_directory_ignored(self):
"""A locale/ directory in the current directory is not used"""
from bleachbit import _resolve_locale_dir
exe_path = r'C:\Program Files\BleachBit'
result = _resolve_locale_dir(
exe_path, is_linux=False, is_mac=False, is_windows=True,
is_netbsd=False, is_bsd=False,
path_exists=lambda p: p in ('./locale/', 'locale'))
self.assertEqual(result, os.path.join(exe_path, 'share\\locale\\'))
def test_non_mac_unaffected(self):
"""On Linux, the macOS bundle-relative check must never be
consulted, even if such a path happened to exist."""
from bleachbit import _resolve_locale_dir
result = _resolve_locale_dir(
'/usr/bin', is_linux=True, is_mac=False, is_windows=False,
is_netbsd=False, is_bsd=False,
path_exists=lambda p: False)
self.assertEqual(result, '/usr/share/locale/')