1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182
|
# vim: ts=4:sw=4:expandtab
# -*- coding: UTF-8 -*-
# BleachBit
# Copyright (C) 2008-2025 Andrew Ziem
# https://www.bleachbit.org
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
"""
Test case for application level functions
"""
import os
import sys
import subprocess
from unittest import mock
try:
import gi
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk # pylint: disable=unused-import
from bleachbit.GUI import Bleachbit
HAVE_GTK = True
except ImportError:
HAVE_GTK = False
import bleachbit
from bleachbit.Options import options
from tests import common
@common.skipUnlessWindows
class ExternalCommandTestCase(common.BleachbitTestCase):
"""Test case for application level functions"""
@classmethod
def setUpClass(cls):
bleachbit.online_update_notification_enabled = False
cls.old_language = common.get_env('LANGUAGE')
common.put_env('LANGUAGE', 'en')
super(ExternalCommandTestCase, ExternalCommandTestCase).setUpClass()
options.set('first_start', False)
options.set('check_online_updates', False) # avoid pop-up window
@classmethod
def tearDownClass(cls):
super(ExternalCommandTestCase, ExternalCommandTestCase).tearDownClass()
common.put_env('LANGUAGE', cls.old_language)
def _context_helper(self, fn_prefix, allow_opened_window=False):
"""Unit test for 'Shred with BleachBit' Windows Explorer context menu command"""
# This test is more likely an integration test as it imitates user behavior.
# It could be interpreted as TestCLI->test_gui_no-uac_shred_exit
# but it is more explicit to have it here as a separate case.
# It covers a single case where one file is shred without delete confirmation dialog.
def set_curdir_to_bleachbit():
os.curdir = os.path.split(__file__)[0]
os.curdir = os.path.split(os.curdir)[0]
file_to_shred = self.mkstemp(prefix=fn_prefix)
print('file_to_shred = {}'.format(file_to_shred))
self.assertExists(file_to_shred)
set_curdir_to_bleachbit()
shred_command_string = self._get_shred_command_string(file_to_shred)
self._run_shred_command(shred_command_string)
self.assertNotExists(file_to_shred)
if not allow_opened_window:
opened_windows_titles = common.get_opened_windows_titles()
# Assert that the Bleachbit window has been closed after the context menu operation had finished.
# If any windows is titled BleachBit the test will fail.
# This could happen with Windows Explorer opened with folder BleachBit for example.
self.assertFalse(
any(['BleachBit' == window_title for window_title in opened_windows_titles]))
def _get_shred_command_string(self, file_to_shred):
shred_command_string = r'{} bleachbit.py --context-menu "{}"'.format(sys.executable,
file_to_shred)
return shred_command_string
def _run_shred_command(self, shred_command_string):
environment_changed = False
if 'BLEACHBIT_TEST_OPTIONS_DIR' not in os.environ:
# We need to set env var because of the new process that executes the context menu command string
common.put_env('BLEACHBIT_TEST_OPTIONS_DIR', self.tempdir)
environment_changed = True
options.set('delete_confirmation', False)
os.system(shred_command_string)
if environment_changed:
# We don't want to affect other tests, executed after this one.
common.put_env('BLEACHBIT_TEST_OPTIONS_DIR', None)
def test_windows_explorer_context_menu_command(self):
for fn_prefix in ('file_to_shred_with_context_menu_command', 'embedded space'):
self._context_helper(fn_prefix)
self._test_as_non_admin_with_context_menu_path(fn_prefix)
def test_context_menu_command_while_the_app_is_running(self):
"""Test the context menu while the application is running"""
p = subprocess.Popen([sys.executable, 'bleachbit.py'], shell=False)
try:
self._context_helper('while_app_is_running',
allow_opened_window=True)
finally:
# Ensure the process is terminated and reaped to prevent ResourceWarning
try:
p.kill()
except Exception:
pass
try:
p.wait(timeout=15)
except Exception:
pass
def _test_as_non_admin_with_context_menu_path(self, fn_prefix):
"""
This tests covers elevate_privileges in the case where we pretend that we are not admin.
"""
from bleachbit.Options import options
file_to_shred = self.mkstemp(prefix=fn_prefix)
self.assertExists(file_to_shred)
original = bleachbit.Windows.shell.ShellExecuteEx
def shell_execute_synchronous(lpVerb='', lpFile='', lpParameters='', nShow=''):
"""
We need a synchronous call the ShellExecuteEx so we can assert after it finishes.
"""
from win32com.shell import shell, shellcon
import win32event
bleachbit.Windows.shell.ShellExecuteEx = original
rc = shell.ShellExecuteEx(lpVerb=lpVerb,
lpFile=lpFile,
lpParameters=lpParameters,
nShow=nShow,
fMask=shellcon.SEE_MASK_NOCLOSEPROCESS,
)
hproc = rc['hProcess']
win32event.WaitForSingleObject(hproc, win32event.INFINITE)
return rc
environment_changed = False
if 'BLEACHBIT_TEST_OPTIONS_DIR' not in os.environ:
# We need to set env var because of the new process that executes the context menu command string
common.put_env('BLEACHBIT_TEST_OPTIONS_DIR', self.tempdir)
environment_changed = True
options.set('delete_confirmation', False)
# We redirect the ShellExecuteEx call like this because if we do it in a mock we enter recursion
# because we call ShellExecuteEx in shell_execute_synchronous
bleachbit.Windows.shell.ShellExecuteEx = shell_execute_synchronous
with mock.patch('bleachbit.Windows.shell.IsUserAnAdmin', return_value=False):
with mock.patch('bleachbit.GUI.sys.exit'):
with mock.patch('bleachbit.Windows.sys.argv', ['dummy-arg', '--context-menu', file_to_shred]):
Bleachbit(auto_exit=True, shred_paths=[
file_to_shred], uac=True)
self.assertNotExists(file_to_shred)
if environment_changed:
# We don't want to affect other tests, executed after this one.
common.put_env('BLEACHBIT_TEST_OPTIONS_DIR', None)
|