summaryrefslogtreecommitdiff
path: root/tests/end2end/features/test_editor_bdd.py
blob: 40f77a0f71d469a0d1bb78fabf542a5dce427a77 (plain)
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
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:

# Copyright 2016-2021 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
#
# This file is part of qutebrowser.
#
# qutebrowser 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.
#
# qutebrowser 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 qutebrowser.  If not, see <https://www.gnu.org/licenses/>.

import sys
import json
import textwrap
import os
import signal
import time

import pytest
import pytest_bdd as bdd
from PyQt5.QtCore import pyqtSignal, pyqtSlot, QObject, QFileSystemWatcher
bdd.scenarios('editor.feature')

from qutebrowser.utils import utils


@bdd.when(bdd.parsers.parse('I setup a fake editor replacing "{text}" by '
                            '"{replacement}"'))
def set_up_editor_replacement(quteproc, server, tmpdir, text, replacement):
    """Set up editor.command to a small python script doing a replacement."""
    text = text.replace('(port)', str(server.port))
    script = tmpdir / 'script.py'
    script.write(textwrap.dedent("""
        import sys

        with open(sys.argv[1], encoding='utf-8') as f:
            data = f.read()

        data = data.replace("{text}", "{replacement}")

        with open(sys.argv[1], 'w', encoding='utf-8') as f:
            f.write(data)
    """.format(text=text, replacement=replacement)))
    editor = json.dumps([sys.executable, str(script), '{}'])
    quteproc.set_setting('editor.command', editor)


@bdd.when(bdd.parsers.parse('I setup a fake editor returning "{text}"'))
def set_up_editor(quteproc, tmpdir, text):
    """Set up editor.command to a small python script inserting a text."""
    script = tmpdir / 'script.py'
    script.write(textwrap.dedent("""
        import sys

        with open(sys.argv[1], 'w', encoding='utf-8') as f:
            f.write({text!r})
    """.format(text=text)))
    editor = json.dumps([sys.executable, str(script), '{}'])
    quteproc.set_setting('editor.command', editor)


@bdd.when(bdd.parsers.parse('I setup a fake editor returning empty text'))
def set_up_editor_empty(quteproc, tmpdir):
    """Set up editor.command to a small python script inserting empty text."""
    set_up_editor(quteproc, tmpdir, "")


class EditorPidWatcher(QObject):

    appeared = pyqtSignal()

    def __init__(self, directory, parent=None):
        super().__init__(parent)
        self._pidfile = directory / 'editor_pid'
        self._watcher = QFileSystemWatcher(self)
        self._watcher.addPath(str(directory))
        self._watcher.directoryChanged.connect(self._check_update)
        self.has_pidfile = False
        self._check_update()

    @pyqtSlot()
    def _check_update(self):
        if self.has_pidfile:
            return

        if self._pidfile.check():
            if self._pidfile.read():
                self.has_pidfile = True
                self.appeared.emit()
            else:
                self._watcher.addPath(str(self._pidfile))

    def manual_check(self):
        return self._pidfile.check()


@pytest.fixture
def editor_pid_watcher(tmpdir):
    return EditorPidWatcher(tmpdir)


@bdd.when(bdd.parsers.parse('I setup a fake editor that writes "{text}" on '
                            'save'))
def set_up_editor_wait(quteproc, tmpdir, text, editor_pid_watcher):
    """Set up editor.command to a small python script inserting a text."""
    assert not utils.is_windows
    pidfile = tmpdir / 'editor_pid'
    script = tmpdir / 'script.py'
    script.write(textwrap.dedent("""
        import os
        import sys
        import time
        import signal

        def handle(sig, _frame):
            filename = sys.argv[1]
            old_mtime = new_mtime = os.stat(filename).st_mtime
            while old_mtime == new_mtime:
                time.sleep(0.1)
                with open(filename, 'w', encoding='utf-8') as f:
                    f.write({text!r})
                new_mtime = os.stat(filename).st_mtime
            if sig == signal.SIGUSR1:
                sys.exit(0)

        signal.signal(signal.SIGUSR1, handle)
        signal.signal(signal.SIGUSR2, handle)

        with open(r'{pidfile}', 'w') as f:
            f.write(str(os.getpid()))

        time.sleep(100)
    """.format(pidfile=pidfile, text=text)))
    editor = json.dumps([sys.executable, str(script), '{}'])
    quteproc.set_setting('editor.command', editor)


@bdd.when("I wait until the editor has started")
def wait_editor(qtbot, editor_pid_watcher):
    if not editor_pid_watcher.has_pidfile:
        with qtbot.wait_signal(editor_pid_watcher.appeared, raising=False):
            pass

    if not editor_pid_watcher.manual_check():
        pytest.fail("Editor pidfile failed to appear!")


@bdd.when(bdd.parsers.parse('I kill the waiting editor'))
def kill_editor_wait(tmpdir):
    """Kill the waiting editor."""
    pidfile = tmpdir / 'editor_pid'
    pid = int(pidfile.read())
    # windows has no SIGUSR1, but we don't run this on windows anyways
    # for posix, there IS a member so we need to ignore useless-suppression
    # pylint: disable=no-member,useless-suppression
    os.kill(pid, signal.SIGUSR1)


@bdd.when(bdd.parsers.parse('I save without exiting the editor'))
def save_editor_wait(tmpdir):
    """Trigger the waiting editor to write without exiting."""
    pidfile = tmpdir / 'editor_pid'
    # give the "editor" process time to write its pid
    for _ in range(10):
        if pidfile.check():
            break
        time.sleep(1)
    pid = int(pidfile.read())
    # windows has no SIGUSR2, but we don't run this on windows anyways
    # for posix, there IS a member so we need to ignore useless-suppression
    # pylint: disable=no-member,useless-suppression
    os.kill(pid, signal.SIGUSR2)


@bdd.when(bdd.parsers.parse('I setup a fake {kind} fileselector '
                            'selecting "{files}" and writes to {output_type}'))
def set_up_fileselector(quteproc, py_proc, kind, files, output_type):
    """Set up fileselect.xxx.command to select the file(s)."""
    cmd, args = py_proc(r"""
        import os
        import sys
        tmp_file = None
        for i, arg in enumerate(sys.argv):
            if arg.startswith('--file='):
                tmp_file = arg[len('--file='):]
                sys.argv.pop(i)
                break
        selected_files = sys.argv[1:]
        if tmp_file is None:
            for selected_file in selected_files:
                print(os.path.abspath(selected_file))
        else:
            with open(tmp_file, 'w') as f:
                for selected_file in selected_files:
                    f.write(os.path.abspath(selected_file) + '\n')
    """)
    args += files.split(' ')
    if output_type == "a temporary file":
        args += ['--file={}']
    fileselect_cmd = json.dumps([cmd, *args])
    quteproc.set_setting('fileselect.handler', 'external')
    quteproc.set_setting(f'fileselect.{kind}.command', fileselect_cmd)