• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

bleachbit / bleachbit / 23171475269

16 Mar 2026 11:50PM UTC coverage: 73.166% (+2.0%) from 71.186%
23171475269

push

github

az0
Gracefully handle missing urllib3

- Do not crash on startup in case of missing urllib3.
- Check for missing packages.
- Notify the user.

Affects BleachBit 5.1.0 and 5.1.1 in Flatpak

Closes https://github.com/bleachbit/bleachbit/issues/2056

20 of 25 new or added lines in 2 files covered. (80.0%)

1105 existing lines in 26 files now uncovered.

7043 of 9626 relevant lines covered (73.17%)

0.73 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

82.68
/bleachbit/SystemInformation.py
1

2
# vim: ts=4:sw=4:expandtab
3
# -*- coding: UTF-8 -*-
4

5
# BleachBit
6
# Copyright (C) 2008-2025 Andrew Ziem
7
# https://www.bleachbit.org
8
#
9
# This program is free software: you can redistribute it and/or modify
10
# it under the terms of the GNU General Public License as published by
11
# the Free Software Foundation, either version 3 of the License, or
12
# (at your option) any later version.
13
#
14
# This program is distributed in the hope that it will be useful,
15
# but WITHOUT ANY WARRANTY; without even the implied warranty of
16
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17
# GNU General Public License for more details.
18
#
19
# You should have received a copy of the GNU General Public License
20
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
21

22

23
"""
24
Show system information
25
"""
26

27
# standard library
28
import logging
1✔
29
import locale
1✔
30
import os
1✔
31
import platform
1✔
32
import re
1✔
33
import sys
1✔
34
from collections import OrderedDict
1✔
35

36
# local
37
import bleachbit
1✔
38
from bleachbit.General import get_executable, get_real_uid
1✔
39

40
logger = logging.getLogger(__name__)
1✔
41

42

43
def get_gtk_info():
1✔
44
    """Get dictionary of information about GTK"""
45
    # pylint: disable=import-outside-toplevel
46
    from bleachbit.GtkShim import gi, Gtk, HAVE_GTK, get_gtk_unavailable_reason
1✔
47

48
    info = {}
1✔
49
    if gi is None:
1✔
UNCOV
50
        logger.debug('gi module not available')
×
UNCOV
51
        return info
×
52

53
    if not hasattr(gi, 'version_info'):
1✔
UNCOV
54
        logger.debug('gi.version_info not available')
×
55
    else:
56
        info['gi.version'] = gi.__version__
1✔
57

58
    if not HAVE_GTK:
1✔
UNCOV
59
        logger.debug('GTK not available: %s', get_gtk_unavailable_reason())
×
UNCOV
60
        return info
×
61

62
    settings = Gtk.Settings.get_default()
1✔
63
    if not settings:
1✔
UNCOV
64
        logger.debug('GTK settings not found')
×
UNCOV
65
        return info
×
66

67
    info['GTK version'] = f"{Gtk.get_major_version()}.{Gtk.get_minor_version()}.{Gtk.get_micro_version()}"
1✔
68
    info['GTK theme'] = settings.get_property('gtk-theme-name')
1✔
69
    info['GTK icon theme'] = settings.get_property('gtk-icon-theme-name')
1✔
70
    info['GTK prefer dark theme'] = settings.get_property(
1✔
71
        'gtk-application-prefer-dark-theme')
72

73
    return info
1✔
74

75

76
def _get_home_dirs_to_anonymize():
1✔
77
    """Return home directories that should be anonymized."""
78
    home_dirs = []
1✔
79
    home_dir = os.path.expanduser('~') or ''
1✔
80
    home_dirs.append(home_dir)
1✔
81

82
    if os.name == 'posix':
1✔
83
        real_home_dir = ''
1✔
84
        try:
1✔
85
            # reminder: pwd is not available on Windows
86
            import pwd  # pylint: disable=import-outside-toplevel
1✔
87
            real_home_dir = pwd.getpwuid(get_real_uid()).pw_dir
1✔
88
        except (ImportError, KeyError, RuntimeError, ValueError):
×
89
            pass
×
90
        home_dirs.append(real_home_dir)
1✔
91

92
    # Filter out root directories and duplicates
93
    filtered_dirs = []
1✔
94
    for d in home_dirs:
1✔
95
        if not d:
1✔
UNCOV
96
            continue
×
97
        if d in ('/', '/root'):
1✔
UNCOV
98
            continue
×
99
        if d not in filtered_dirs:
1✔
100
            filtered_dirs.append(d)
1✔
101

102
    return filtered_dirs
1✔
103

104

105
def anonymize_system_information(text):
1✔
106
    """Anonymize non-generic username in system information text.
107

108
    root is not anonymized.
109
    """
110
    home_dirs = _get_home_dirs_to_anonymize()
1✔
111
    home_token = '~' if os.name == 'posix' else '%userprofile%'
1✔
112

113
    def mask_user_line(line):
1✔
114
        """Mask username for environment variables"""
115
        if not (line.startswith('os.getenv(LOGNAME)') or line.startswith('os.getenv(USER)')):
1✔
116
            return line
1✔
117
        key, _, value = line.partition(' = ')
1✔
118
        if value and value != 'None' and value.strip() != 'root':
1✔
119
            # replace specific non-root username with *non-root*
120
            return f'{key} = *non-root*'
1✔
121
        return line
1✔
122

123
    anonymized_lines = []
1✔
124
    for line in text.split('\n'):
1✔
125
        for home_dir in home_dirs:
1✔
126
            if os.name == 'nt':
1✔
127
                # Windows paths are case-insensitive
128
                line = re.sub(re.escape(home_dir), home_token,
1✔
129
                              line, flags=re.IGNORECASE)
130
            else:
131
                line = line.replace(home_dir, home_token)
1✔
132
        anonymized_lines.append(mask_user_line(line))
1✔
133

134
    return '\n'.join(anonymized_lines)
1✔
135

136

137
def get_version(four_parts=False):
1✔
138
    """Return version information as a string.
139

140
    CI builds will have an integer build number.
141

142
    If four_parts is True, always return a four-part version string.
143
    If False, return three or four parts, depending on available information.
144
    """
145
    build_number_env = os.getenv('APPVEYOR_BUILD_NUMBER')
1✔
146
    build_number_src = None
1✔
147
    try:
1✔
148
        # pylint: disable=import-outside-toplevel
149
        from bleachbit.Revision import build_number as build_number_import
1✔
150
        build_number_src = build_number_import
1✔
UNCOV
151
    except ImportError:
×
UNCOV
152
        pass
×
153

154
    build_number = build_number_src or build_number_env
1✔
155
    if not build_number:
1✔
156
        if not four_parts:
×
157
            return bleachbit.APP_VERSION
×
UNCOV
158
        return f'{bleachbit.APP_VERSION}.0'
×
159
    assert build_number.isdigit()
1✔
160
    return f'{bleachbit.APP_VERSION}.{build_number}'
1✔
161

162

163
def get_system_information():
1✔
164
    """Return system information as a string."""
165
    info = OrderedDict()
1✔
166

167
    # Application and library versions
168
    info['BleachBit version'] = get_version()
1✔
169

170
    try:
1✔
171
        # CI builds and Linux tarball will have a revision.
172
        # pylint: disable=import-outside-toplevel
173
        from bleachbit.Revision import revision
1✔
174
        info['Git revision'] = revision
1✔
175
    except ImportError:
1✔
176
        pass
1✔
177

178
    info.update(get_gtk_info())
1✔
179

180
    # Variables defined in __init__.py
181
    info['local_cleaners_dir'] = bleachbit.local_cleaners_dir
1✔
182
    info['locale_dir'] = bleachbit.locale_dir
1✔
183
    info['options_dir'] = bleachbit.options_dir
1✔
184
    info['personal_cleaners_dir'] = bleachbit.personal_cleaners_dir
1✔
185
    info['system_cleaners_dir'] = bleachbit.system_cleaners_dir
1✔
186

187
    # System environment information
188
    info['locale.getlocale'] = str(locale.getlocale())
1✔
189

190
    # Environment variables
191
    if 'posix' == os.name:
1✔
192
        envs = ('DESKTOP_SESSION', 'LOGNAME', 'USER', 'SUDO_UID')
1✔
193
    elif 'nt' == os.name:
1✔
194
        envs = ('APPDATA', 'cd', 'LocalAppData', 'LocalAppDataLow', 'Music',
1✔
195
                'USERPROFILE', 'ProgramFiles', 'ProgramW6432', 'TMP')
196
    else:
UNCOV
197
        envs = ()
×
198

199
    for env in envs:
1✔
200
        info[f'os.getenv({env})'] = os.getenv(env)
1✔
201

202
    info['os.path.expanduser(~")'] = os.path.expanduser('~')
1✔
203

204
    # Mac Version Name - Dictionary
205
    macosx_dict = {'5': 'Leopard', '6': 'Snow Leopard', '7': 'Lion', '8': 'Mountain Lion',
1✔
206
                   '9': 'Mavericks', '10': 'Yosemite', '11': 'El Capitan', '12': 'Sierra'}
207

208
    if sys.platform == 'linux':
1✔
209
        from bleachbit.Unix import get_distribution_name_version
1✔
210
        info['get_distribution_name_version()'] = get_distribution_name_version()
1✔
211
    elif sys.platform.startswith('darwin'):
1✔
UNCOV
212
        if hasattr(platform, 'mac_ver'):
×
UNCOV
213
            mac_version = platform.mac_ver()[0]
×
UNCOV
214
            version_minor = mac_version.split('.')[1]
×
UNCOV
215
            if version_minor in macosx_dict:
×
UNCOV
216
                info['platform.mac_ver()'] = f'{mac_version} ({macosx_dict[version_minor]})'
×
217
    else:
218
        info['platform.uname().version'] = platform.uname().version
1✔
219

220
    # System information
221
    info['sys.argv'] = sys.argv
1✔
222
    info['sys.executable'] = get_executable()
1✔
223
    info['sys.version'] = sys.version
1✔
224
    if 'nt' == os.name:
1✔
225
        from win32com.shell import shell
1✔
226
        info['IsUserAnAdmin()'] = shell.IsUserAnAdmin()
1✔
227
    info['__file__'] = __file__
1✔
228

229
    # Render the information as a string
230
    return '\n'.join(f'{key} = {value}' for key, value in info.items())
1✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc