Ctrl K
ComunidadGuías…

tests/TestCommon.py

Rama master · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
# 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/')