File: test_cli.py

package info (click to toggle)
qtsass 0.4.0-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 344 kB
  • sloc: python: 1,337; sh: 7; makefile: 2
file content (230 lines) | stat: -rw-r--r-- 6,424 bytes parent folder | download | duplicates (2)
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
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
# -*- coding: utf-8 -*-
# -----------------------------------------------------------------------------
# Copyright (c) 2015 Yann Lanthony
# Copyright (c) 2017-2018 Spyder Project Contributors
#
# Licensed under the terms of the MIT License
# (See LICENSE.txt for details)
# -----------------------------------------------------------------------------
"""Test qtsass cli."""

from __future__ import absolute_import

# Standard library imports
from collections import namedtuple
from os.path import basename, exists
from subprocess import PIPE, Popen
import time

# Local imports
from . import PROJECT_DIR, await_condition, example, touch


SLEEP_INTERVAL = 1
Result = namedtuple('Result', "code stdout stderr")


def indent(text, prefix='    '):
    """Like textwrap.indent"""

    return ''.join([prefix + line for line in text.splitlines(True)])


def invoke(args):
    """Invoke qtsass cli with specified args"""

    kwargs = dict(
        stdout=PIPE,
        stderr=PIPE,
        cwd=PROJECT_DIR
    )
    proc = Popen(['python3', '-m', 'qtsass'] + args, **kwargs)
    return proc


def invoke_with_result(args):
    """Invoke qtsass cli and return a Result obj"""

    proc = invoke(args)
    out, err = proc.communicate()
    out = out.decode('ascii', errors="ignore")
    err = err.decode('ascii', errors="ignore")
    return Result(proc.returncode, out, err)


def kill(proc, timeout=1):
    """Kill a subprocess and return a Result obj"""

    proc.kill()
    out, err = proc.communicate()
    out = out.decode('ascii', errors="ignore")
    err = err.decode('ascii', errors="ignore")
    return Result(proc.returncode, out, err)


def format_result(result):
    """Format a subprocess Result obj"""

    out = [
        'Subprocess Report...',
        'Exit code: %s' % result.code,
    ]
    if result.stdout:
        out.append('stdout:')
        out.append(indent(result.stdout, '    '))
    if result.stderr:
        out.append('stderr:')
        out.append(indent(result.stderr, '    '))
    return '\n'.join(out)


def test_compile_dummy_to_stdout():
    """CLI compile dummy example to stdout."""

    args = [example('dummy.scss')]
    result = invoke_with_result(args)

    assert result.code == 0
    assert result.stdout


def test_compile_dummy_to_file(tmpdir):
    """CLI compile dummy example to file."""

    input = example('dummy.scss')
    output = tmpdir.join('dummy.css')
    args = [input, '-o', output.strpath]
    result = invoke_with_result(args)

    assert result.code == 0
    assert exists(output.strpath)


def test_watch_dummy(tmpdir):
    """CLI watch dummy example."""

    input = example('dummy.scss')
    output = tmpdir.join('dummy.css')
    args = [input, '-o', output.strpath, '-w']
    proc = invoke(args)

    # Wait for initial compile
    output_exists = lambda: exists(output.strpath)
    if not await_condition(output_exists):
        result = kill(proc)
        report = format_result(result)
        err = "Failed to compile dummy.scss\n"
        err += report
        assert False, report

    # Ensure subprocess is still alive
    assert proc.poll() is None

    # Touch input file, triggering a recompile
    created = output.mtime()
    file_modified = lambda: output.mtime() > created
    time.sleep(SLEEP_INTERVAL)
    touch(input)

    if not await_condition(file_modified):
        result = kill(proc)
        report = format_result(result)
        err = 'Modifying %s did not trigger recompile.\n' % basename(input)
        err += report
        assert False, err

    kill(proc)


def test_compile_complex(tmpdir):
    """CLI compile complex example."""

    input = example('complex')
    output = tmpdir.mkdir('output')
    args = [input, '-o', output.strpath]
    result = invoke_with_result(args)

    assert result.code == 0

    expected_files = [output.join('light.css'), output.join('dark.css')]
    for file in expected_files:
        assert exists(file.strpath)


def test_watch_complex(tmpdir):
    """CLI watch complex example."""

    input = example('complex')
    output = tmpdir.mkdir('output')
    args = [input, '-o', output.strpath, '-w']
    proc = invoke(args)

    expected_files = [output.join('light.css'), output.join('dark.css')]

    # Wait for initial compile
    files_created = lambda: all([exists(f.strpath) for f in expected_files])
    if not await_condition(files_created):
        result = kill(proc)
        report = format_result(result)
        err = 'All expected files have not been created...'
        err += report
        assert False, err

    # Ensure subprocess is still alive
    assert proc.poll() is None

    # Input files to touch
    input_full = example('complex', 'light.scss')
    input_partial = example('complex', '_base.scss')
    input_nested = example('complex', 'widgets', '_qwidget.scss')

    def touch_and_wait(input_file, timeout=2000):
        """Touch a file, triggering a recompile"""

        filename = basename(input_file)
        old_mtimes = [f.mtime() for f in expected_files]
        files_modified = lambda: all(
            [f.mtime() > old_mtimes[i] for i, f in enumerate(expected_files)]
        )
        time.sleep(SLEEP_INTERVAL)
        touch(input_file)

        if not await_condition(files_modified, timeout):
            result = kill(proc)
            report = format_result(result)
            err = 'Modifying %s did not trigger recompile.\n' % filename
            err += report
            for i, f in enumerate(expected_files):
                err += str(f) + '\n'
                err += str(old_mtimes[i]) + '\n'
                err += str(f.mtime()) + '\n'
                err += str(bool(f.mtime() > old_mtimes[i])) + '\n'
            assert False, err

        return True

    assert touch_and_wait(input_full)
    assert touch_and_wait(input_partial)
    assert touch_and_wait(input_nested)

    kill(proc)


def test_invalid_input():
    """CLI input is not a file or dir."""

    proc = invoke_with_result(['file_does_not_exist.scss'])
    assert proc.code == 1
    assert 'Error: input must be' in proc.stdout

    proc = invoke_with_result(['./dir/does/not/exist'])
    assert proc.code == 1
    assert 'Error: input must be' in proc.stdout


def test_dir_missing_output():
    """CLI dir missing output option"""

    proc = invoke_with_result([example('complex')])
    assert proc.code == 1
    assert 'Error: missing required option' in proc.stdout