File: TestExternalCommand.py

package info (click to toggle)
bleachbit 5.0.2-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 6,916 kB
  • sloc: python: 15,623; xml: 3,218; makefile: 232; sh: 9
file content (182 lines) | stat: -rw-r--r-- 7,633 bytes parent folder | download
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)