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 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260
|
from unittest import mock
import pytest
from briefcase.commands.run import LogFilter
from briefcase.integrations.subprocess import StopStreaming
def test_default_filter():
"""A default logfilter echoes content verbatim."""
popen = mock.MagicMock()
log_filter = LogFilter(
popen,
clean_filter=None,
clean_output=True,
exit_filter=None,
)
for i in range(0, 10):
line = f"this is line {i}"
# Every line is returned verbatim
assert [line] == list(log_filter(line))
# no return code was detected
assert log_filter.returncode is None
def test_clean_filter():
"""A cleaning filter can be used to strip content."""
# Define a cleaning filter that strips the first 5 characters,
# and identifies all content as Python
def clean_filter(line):
return line[5:], True
popen = mock.MagicMock()
log_filter = LogFilter(
popen,
clean_filter=clean_filter,
clean_output=True,
exit_filter=None,
)
for i in range(0, 10):
line = f"this is line {i}"
# The line has the preamble stripped
assert [line[5:]] == list(log_filter(line))
# no return code was detected
assert log_filter.returncode is None
def test_clean_filter_unclean_output():
"""A cleaning filter can be used to strip content, but doesn't have to alter
output."""
# Define a cleaning filter that strips the first 5 characters,
# and identifies all content as Python
def clean_filter(line):
return line[5:], True
popen = mock.MagicMock()
log_filter = LogFilter(
popen,
clean_filter=clean_filter,
clean_output=False,
exit_filter=None,
)
for i in range(0, 10):
line = f"this is line {i}"
# Every line is returned verbatim
assert [line] == list(log_filter(line))
# no return code was detected
assert log_filter.returncode is None
@pytest.mark.parametrize(
"raw, expected_output, use_content_filter, clean_output, returncode",
[
# Without cleaning, simple content is passed through as is.
(
["1: line 1", "2: line 2", "3: line 3", "4: line 4", "5: line 5"],
["1: line 1", "2: line 2", "3: line 3", "4: line 4", "5: line 5"],
False, # don't use content filter
False, # don't use clean output
None, # no return code
),
# Cleaning can be turned on without altering output
(
["1: line 1", "2: line 2", "3: line 3", "4: line 4", "5: line 5"],
["1: line 1", "2: line 2", "3: line 3", "4: line 4", "5: line 5"],
True, # use content filter
False, # don't use clean output
None, # no return code
),
# Dumped content is ignored, even if output isn't being cleaned
(
["1: line 1", "2: line 2", "DUMP: garbage", "4: line 4", "5: line 5"],
["1: line 1", "2: line 2", "4: line 4", "5: line 5"],
True, # use content filter
False, # don't use clean output
None, # no return code
),
# Output can be cleaned
(
["1: line 1", "2: line 2", "3: line 3", "4: line 4", "5: line 5"],
["line 1", "line 2", "line 3", "line 4", "line 5"],
True, # use content filter
True, # use clean output
None, # no return code
),
# Dumped content won't appear in cleaned output
(
["1: line 1", "2: line 2", "DUMP: garbage", "4: line 4", "5: line 5"],
["line 1", "line 2", "line 4", "line 5"],
True, # use content filter
True, # use clean output
None, # no return code
),
# Without cleaning, exit status won't be found
(
["1: line 1", "2: line 2", "3: -----", "4: ", "5: EXIT 42", "post"],
["1: line 1", "2: line 2", "3: -----", "4: ", "5: EXIT 42", "post"],
False, # don't use content filter
False, # don't use clean output
None, # no return code due to line prefixes
),
# If test output is clean without filtering, exit status can be determined
(
["line 1", "line 2", "-----", "", "EXIT 42", "post"],
["line 1", "line 2", "-----", ""],
False, # don't use content filter
False, # don't use clean output
42, # exit status from raw output
),
# Exit status can be found without altering output
# Line prefixes are all even to ensure they aren't ignored
(
["2: line 1", "4: line 2", "6: -----", "8: ", "10: EXIT 42", "post"],
["2: line 1", "4: line 2", "6: -----", "8: "],
True, # use content filter
False, # don't use clean output
42, # exit status
),
# Exit status won't consider ignored content
# Dumped content won't be considered
(
["2: line 1", "4: -----", "DUMP: garbage", "6: ", "8: EXIT 42", "post"],
["2: line 1", "4: -----", "6: "],
True, # use content filter
False, # don't use clean output
42, # exit status
),
# Exit status won't consider ignored content
# Line 5 will be ignored, but not dumped
(
["2: line 1", "4: -----", "5: Ignore this", "6: ", "8: EXIT 42", "post"],
["2: line 1", "4: -----", "5: Ignore this", "6: "],
True, # use content filter
False, # don't use clean output
42, # exit status
),
# Exit status can be found with cleaned output
# Line prefixes are all even to ensure they aren't ignored; output is clean
(
["2: line 1", "4: line 2", "6: -----", "8: ", "10: EXIT 42", "post"],
["line 1", "line 2", "-----", ""],
True, # use content filter
True, # use clean output
42, # Exit status
),
# Exit status won't consider ignored content
# Dumped content won't be considered; output is clean
(
["2: line 1", "4: -----", "DUMP: garbage", "6: ", "8: EXIT 42", "post"],
["line 1", "-----", ""],
True, # use content filter
True, # use clean output
42, # exit status
),
# Exit status won't consider ignored content
# Line 5 will be ignored, but not dumped; output is clean
(
["2: line 1", "4: -----", "5: Ignore this", "6: ", "8: EXIT 42", "post"],
["line 1", "-----", "Ignore this", ""],
True, # use content filter
True, # use clean output
42, # exit status
),
# Exit status won't be found if it occurs on ignored lines
# Line 5 is the right pattern, but it will be marked as ignored by the cleaner
(
["2: line 1", "4: line 2", "5: -----", "6: ", "8: EXIT 42", "post"],
["2: line 1", "4: line 2", "5: -----", "6: ", "8: EXIT 42", "post"],
True, # use content filter
False, # don't use clean output
None, # no return code due to line 5 not matching
),
],
)
def test_log_filter(
raw,
expected_output,
use_content_filter,
clean_output,
returncode,
):
"""The log filter behaves as expected."""
# Define a clean filter that removes an index at the start of the line, only
# analyses content with an even prefix, and dumps content that starts "DUMP:"
def clean_filter(line):
if line.startswith("DUMP: "):
return None
parts = line.split(":", 1)
try:
line = int(parts[0])
return " ".join(parts[1:]).strip(), line % 2 == 0
except ValueError:
return line, False
# Define a custom filter that looks for specific multiline output
exit_filter = LogFilter.test_filter(r"^-----\n\nEXIT (?P<returncode>\d+)$")
# Set up a log stream
popen = mock.MagicMock()
log_filter = LogFilter(
popen,
clean_filter=clean_filter if use_content_filter else None,
clean_output=clean_output,
exit_filter=exit_filter,
)
# Pipe the raw output through the log filter, and capture the output
output = []
terminated = False
for raw_line in raw:
try:
for line in log_filter(raw_line):
output.append(line)
except StopStreaming:
terminated = True
break
# Actual output is as expected
assert output == expected_output
if returncode is None:
# No success/failure condition was set; no termination condition was processed
assert log_filter.returncode is None
assert not terminated
else:
# The success/failure condition was detected, and the termination condition was processed
assert log_filter.returncode == returncode
assert terminated
|