93
96
raise SVNIncorrectDatatype("Unexpected type for '%s' data" % output_type)
97
"""Contains expected output, and performs comparisons."""
102
def __init__(self, output, match_all=True):
103
"""Initialize the expected output to OUTPUT which is a string, or a list
104
of strings, or None meaning an empty list. If MATCH_ALL is True, the
105
expected strings will be matched with the actual strings, one-to-one, in
106
the same order. If False, they will be matched with a subset of the
107
actual strings, one-to-one, in the same order, ignoring any other actual
108
strings among the matching ones."""
99
class ExpectedOutput(object):
100
"""Matches an ordered list of lines.
102
If MATCH_ALL is True, the expected lines must match all the actual
103
lines, one-to-one, in the same order. If MATCH_ALL is False, the
104
expected lines must match a subset of the actual lines, one-to-one,
105
in the same order, ignoring any other actual lines among the
109
def __init__(self, expected, match_all=True):
110
"""Initialize the expected output to EXPECTED which is a string, or
113
assert expected is not None
114
self.expected = expected
110
115
self.match_all = match_all
112
117
def __str__(self):
113
return str(self.output)
118
return str(self.expected)
115
120
def __cmp__(self, other):
116
raise Exception('badness')
118
def matches(self, other, except_re=None):
119
"""Return whether SELF.output matches OTHER (which may be a list
120
of newline-terminated lines, or a single string). Either value
122
if self.output is None:
125
expected = self.output
131
if not isinstance(actual, list):
121
raise TypeError("ExpectedOutput does not implement direct comparison; "
122
"see the 'matches()' method")
124
def matches(self, actual):
125
"""Return whether SELF matches ACTUAL (which may be a list
126
of newline-terminated lines, or a single string).
128
assert actual is not None
129
expected = self.expected
133
130
if not isinstance(expected, list):
134
131
expected = [expected]
137
return self.matches_except(expected, actual, except_re)
139
return self.is_equivalent_list(expected, actual)
141
def matches_except(self, expected, actual, except_re):
142
"Return whether EXPECTED and ACTUAL match except for except_re."
143
if not self.is_regex:
146
while i_expected < len(expected) and i_actual < len(actual):
147
if re.match(except_re, actual[i_actual]):
149
elif re.match(except_re, expected[i_expected]):
151
elif expected[i_expected] == actual[i_actual]:
156
if i_expected == len(expected) and i_actual == len(actual):
160
raise Exception("is_regex and except_re are mutually exclusive")
162
def is_equivalent_list(self, expected, actual):
163
"Return whether EXPECTED and ACTUAL are equivalent."
164
if not self.is_regex:
166
# The EXPECTED lines must match the ACTUAL lines, one-to-one, in
168
return expected == actual
170
# The EXPECTED lines must match a subset of the ACTUAL lines,
171
# one-to-one, in the same order, with zero or more other ACTUAL
172
# lines interspersed among the matching ACTUAL lines.
174
for actual_line in actual:
175
if expected[i_expected] == actual_line:
177
if i_expected == len(expected):
181
expected_re = expected[0]
182
# If we want to check that every line matches the regexp
183
# assume they all match and look for any that don't. If
184
# only one line matching the regexp is enough, assume none
185
# match and look for even one that does.
132
if not isinstance(actual, list):
186
135
if self.match_all:
187
all_lines_match_re = True
189
all_lines_match_re = False
191
# If a regex was provided assume that we actually require
192
# some output. Fail if we don't have any.
136
return expected == actual
196
139
for actual_line in actual:
198
if not re.match(expected_re, actual_line):
201
# As soon an actual_line matches something, then we're good.
202
if re.match(expected_re, actual_line):
140
if expected[i_expected] == actual_line:
142
if i_expected == len(expected):
205
return all_lines_match_re
207
146
def display_differences(self, message, label, actual):
208
"""Delegate to the display_lines() routine with the appropriate
209
args. MESSAGE is ignored if None."""
210
display_lines(message, label, self.output, actual,
211
self.is_regex, self.is_unordered)
147
"""Show the differences between the expected and ACTUAL lines. Print
148
MESSAGE unless it is None, the expected lines, the ACTUAL lines,
149
and a diff, all labeled with LABEL.
151
display_lines(message, self.expected, actual, label, label)
152
display_lines_diff(self.expected, actual, label, label)
214
155
class AnyOutput(ExpectedOutput):
156
"""Matches any non-empty output.
215
159
def __init__(self):
216
ExpectedOutput.__init__(self, None, False)
218
def is_equivalent_list(self, ignored, actual):
160
ExpectedOutput.__init__(self, [], False)
162
def matches(self, actual):
163
assert actual is not None
219
165
if len(actual) == 0:
220
166
# No actual output. No match.
231
177
def display_differences(self, message, label, actual):
236
182
class RegexOutput(ExpectedOutput):
183
"""Matches a single regular expression.
185
If MATCH_ALL is true, every actual line must match the RE. If
186
MATCH_ALL is false, at least one actual line must match the RE. In
187
any case, there must be at least one line of actual output.
190
def __init__(self, expected, match_all=True):
191
"EXPECTED is a regular expression string."
192
assert isinstance(expected, str)
193
ExpectedOutput.__init__(self, expected, match_all)
194
self.expected_re = re.compile(expected)
196
def matches(self, actual):
197
assert actual is not None
199
if not isinstance(actual, list):
202
# If a regex was provided assume that we require some actual output.
203
# Fail if we don't have any.
208
return all(self.expected_re.match(line) for line in actual)
210
return any(self.expected_re.match(line) for line in actual)
212
def display_differences(self, message, label, actual):
213
display_lines(message, self.expected, actual, label + ' (regexp)', label)
216
class RegexListOutput(ExpectedOutput):
217
"""Matches an ordered list of regular expressions.
219
If MATCH_ALL is True, the expressions must match all the actual
220
lines, one-to-one, in the same order. If MATCH_ALL is False, the
221
expressions must match a subset of the actual lines, one-to-one, in
222
the same order, ignoring any other actual lines among the matching
225
In any case, there must be at least one line of actual output.
228
def __init__(self, expected, match_all=True):
229
"EXPECTED is a list of regular expression strings."
230
assert isinstance(expected, list) and expected != []
231
ExpectedOutput.__init__(self, expected, match_all)
232
self.expected_res = [re.compile(e) for e in expected]
234
def matches(self, actual):
235
assert actual is not None
236
if not isinstance(actual, list):
240
return (len(self.expected_res) == len(actual) and
241
all(e.match(a) for e, a in zip(self.expected_res, actual)))
244
for actual_line in actual:
245
if self.expected_res[i_expected].match(actual_line):
247
if i_expected == len(self.expected_res):
251
def display_differences(self, message, label, actual):
252
display_lines(message, self.expected, actual, label + ' (regexp)', label)
240
255
class UnorderedOutput(ExpectedOutput):
241
"""Marks unordered output, and performs comparisons."""
245
def __cmp__(self, other):
246
raise Exception('badness')
248
def matches_except(self, expected, actual, except_re):
249
assert type(actual) == type([]) # ### if this trips: fix it!
250
return self.is_equivalent_list([l for l in expected if not except_re.match(l)],
251
[l for l in actual if not except_re.match(l)])
253
def is_equivalent_list(self, expected, actual):
254
"Disregard the order of ACTUAL lines during comparison."
256
e_set = set(expected)
260
if len(e_set) != len(a_set):
256
"""Matches an unordered list of lines.
258
The expected lines must match all the actual lines, one-to-one, in
262
def __init__(self, expected):
263
assert isinstance(expected, list)
264
ExpectedOutput.__init__(self, expected)
266
def matches(self, actual):
267
if not isinstance(actual, list):
270
return sorted(self.expected) == sorted(actual)
272
def display_differences(self, message, label, actual):
273
display_lines(message, self.expected, actual, label + ' (unordered)', label)
274
display_lines_diff(self.expected, actual, label + ' (unordered)', label)
277
class UnorderedRegexListOutput(ExpectedOutput):
278
"""Matches an unordered list of regular expressions.
280
The expressions must match all the actual lines, one-to-one, in any
283
Note: This can give a false negative result (no match) when there is
284
an actual line that matches multiple expressions and a different
285
actual line that matches some but not all of those same
286
expressions. The implementation matches each expression in turn to
287
the first unmatched actual line that it can match, and does not try
288
all the permutations when there are multiple possible matches.
291
def __init__(self, expected):
292
assert isinstance(expected, list)
293
ExpectedOutput.__init__(self, expected)
295
def matches(self, actual):
296
assert actual is not None
297
if not isinstance(actual, list):
300
if len(self.expected) != len(actual):
302
for e in self.expected:
303
expect_re = re.compile(e)
304
for actual_line in actual:
305
if expect_re.match(actual_line):
306
actual.remove(actual_line)
309
# One of the regexes was not found
263
for expect_re in e_set:
264
for actual_line in a_set:
265
if re.match(expect_re, actual_line):
266
a_set.remove(actual_line)
269
# One of the regexes was not found
313
def display_differences(self, message, label, actual):
314
display_lines(message, self.expected, actual,
315
label + ' (regexp) (unordered)', label)
318
class AlternateOutput(ExpectedOutput):
319
"""Matches any one of a list of ExpectedOutput instances.
322
def __init__(self, expected, match_all=True):
323
"EXPECTED is a list of ExpectedOutput instances."
324
assert isinstance(expected, list) and expected != []
325
assert all(isinstance(e, ExpectedOutput) for e in expected)
326
ExpectedOutput.__init__(self, expected)
328
def matches(self, actual):
329
assert actual is not None
330
for e in self.expected:
331
if e.matches(actual):
273
# All expected lines must be in the output.
274
return e_set == a_set
277
# If any of the expected regexes are in the output, then we match.
278
for expect_re in e_set:
279
for actual_line in a_set:
280
if re.match(expect_re, actual_line):
284
# If any of the expected lines are in the output, then we match.
285
return len(e_set.intersection(a_set)) > 0
288
class UnorderedRegexOutput(UnorderedOutput, RegexOutput):
335
def display_differences(self, message, label, actual):
336
# For now, just display differences against the first alternative.
338
e.display_differences(message, label, actual)
293
341
######################################################################
296
344
def display_trees(message, label, expected, actual):
297
345
'Print two trees, expected and actual.'
298
346
if message is not None:
300
348
if expected is not None:
301
print('EXPECTED %s:' % label)
349
logger.warn('EXPECTED %s:', label)
302
350
svntest.tree.dump_tree(expected)
303
351
if actual is not None:
304
print('ACTUAL %s:' % label)
352
logger.warn('ACTUAL %s:', label)
305
353
svntest.tree.dump_tree(actual)
308
def display_lines(message, label, expected, actual, expected_is_regexp=None,
309
expected_is_unordered=None):
356
def display_lines_diff(expected, actual, expected_label, actual_label):
357
"""Print a unified diff between EXPECTED (labeled with EXPECTED_LABEL)
358
and ACTUAL (labeled with ACTUAL_LABEL).
359
Each of EXPECTED and ACTUAL is a string or a list of strings.
361
if not isinstance(expected, list):
362
expected = [expected]
363
if not isinstance(actual, list):
365
logger.warn('DIFF ' + expected_label + ':')
366
for x in unified_diff(expected, actual,
367
fromfile='EXPECTED ' + expected_label,
368
tofile='ACTUAL ' + actual_label):
369
logger.warn('| ' + x.rstrip())
371
def display_lines(message, expected, actual,
372
expected_label, actual_label=None):
310
373
"""Print MESSAGE, unless it is None, then print EXPECTED (labeled
311
with LABEL) followed by ACTUAL (also labeled with LABEL).
312
Both EXPECTED and ACTUAL may be strings or lists of strings."""
374
with EXPECTED_LABEL) followed by ACTUAL (labeled with ACTUAL_LABEL).
375
Each of EXPECTED and ACTUAL is a string or a list of strings.
313
377
if message is not None:
380
if type(expected) is str:
381
expected = [expected]
382
if type(actual) is str:
384
if actual_label is None:
385
actual_label = expected_label
315
386
if expected is not None:
316
output = 'EXPECTED %s' % label
317
if expected_is_regexp:
318
output += ' (regexp)'
319
expected = [expected + '\n']
320
if expected_is_unordered:
321
output += ' (unordered)'
387
logger.warn('EXPECTED %s:', expected_label)
324
388
for x in expected:
389
logger.warn('| ' + x.rstrip())
326
390
if actual is not None:
327
print('ACTUAL %s:' % label)
391
logger.warn('ACTUAL %s:', actual_label)
331
# Additionally print unified diff
332
if not expected_is_regexp:
333
print('DIFF ' + ' '.join(output.split(' ')[1:]))
335
if type(expected) is str:
336
expected = [expected]
338
if type(actual) is str:
341
for x in unified_diff(expected, actual,
342
fromfile="EXPECTED %s" % label,
343
tofile="ACTUAL %s" % label):
393
logger.warn('| ' + x.rstrip())
346
395
def compare_and_display_lines(message, label, expected, actual,
347
raisable=None, except_re=None):
348
397
"""Compare two sets of output lines, and print them if they differ,
349
398
preceded by MESSAGE iff not None. EXPECTED may be an instance of
350
ExpectedOutput (and if not, it is wrapped as such). RAISABLE is an
399
ExpectedOutput (and if not, it is wrapped as such). ACTUAL may be a
400
list of newline-terminated lines, or a single string. RAISABLE is an
351
401
exception class, an instance of which is thrown if ACTUAL doesn't
352
402
match EXPECTED."""
353
403
if raisable is None:
354
404
raisable = svntest.main.SVNLineUnequal
355
405
### It'd be nicer to use createExpectedOutput() here, but its
356
406
### semantics don't match all current consumers of this function.
407
assert expected is not None
408
assert actual is not None
357
409
if not isinstance(expected, ExpectedOutput):
358
410
expected = ExpectedOutput(expected)
360
412
if isinstance(actual, str):
361
413
actual = [actual]
362
actual = [line for line in actual if not line.startswith('DBG:')]
414
actual = svntest.main.filter_dbg(actual)
364
if not expected.matches(actual, except_re):
416
if not expected.matches(actual):
365
417
expected.display_differences(message, label, actual)