2 # verify.py: routines that handle comparison and display of expected
5 # Subversion is a tool for revision control.
6 # See http://subversion.tigris.org for more information.
8 # ====================================================================
9 # Licensed to the Apache Software Foundation (ASF) under one
10 # or more contributor license agreements. See the NOTICE file
11 # distributed with this work for additional information
12 # regarding copyright ownership. The ASF licenses this file
13 # to you under the Apache License, Version 2.0 (the
14 # "License"); you may not use this file except in compliance
15 # with the License. You may obtain a copy of the License at
17 # http://www.apache.org/licenses/LICENSE-2.0
19 # Unless required by applicable law or agreed to in writing,
20 # software distributed under the License is distributed on an
21 # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
22 # KIND, either express or implied. See the License for the
23 # specific language governing permissions and limitations
25 ######################################################################
28 from difflib
import unified_diff
33 ######################################################################
36 class SVNUnexpectedOutput(svntest
.Failure
):
37 """Exception raised if an invocation of svn results in unexpected
38 output of any kind."""
41 class SVNUnexpectedStdout(SVNUnexpectedOutput
):
42 """Exception raised if an invocation of svn results in unexpected
46 class SVNUnexpectedStderr(SVNUnexpectedOutput
):
47 """Exception raised if an invocation of svn results in unexpected
51 class SVNExpectedStdout(SVNUnexpectedOutput
):
52 """Exception raised if an invocation of svn results in no output on
53 STDOUT when output was expected."""
56 class SVNExpectedStderr(SVNUnexpectedOutput
):
57 """Exception raised if an invocation of svn results in no output on
58 STDERR when output was expected."""
61 class SVNUnexpectedExitCode(SVNUnexpectedOutput
):
62 """Exception raised if an invocation of svn exits with a value other
63 than what was expected."""
66 class SVNIncorrectDatatype(SVNUnexpectedOutput
):
67 """Exception raised if invalid input is passed to the
68 run_and_verify_* API"""
72 ######################################################################
73 # Comparison of expected vs. actual output
75 def createExpectedOutput(expected
, output_type
, match_all
=True):
76 """Return EXPECTED, promoted to an ExpectedOutput instance if not
77 None. Raise SVNIncorrectDatatype if the data type of EXPECTED is
79 if isinstance(expected
, list):
80 expected
= ExpectedOutput(expected
)
81 elif isinstance(expected
, str):
82 expected
= RegexOutput(expected
, match_all
)
83 elif expected
is AnyOutput
:
84 expected
= AnyOutput()
85 elif expected
is not None and not isinstance(expected
, ExpectedOutput
):
86 raise SVNIncorrectDatatype("Unexpected type for '%s' data" % output_type
)
90 """Contains expected output, and performs comparisons."""
95 def __init__(self
, output
, match_all
=True):
96 """Initialize the expected output to OUTPUT which is a string, or a list
97 of strings, or None meaning an empty list. If MATCH_ALL is True, the
98 expected strings will be matched with the actual strings, one-to-one, in
99 the same order. If False, they will be matched with a subset of the
100 actual strings, one-to-one, in the same order, ignoring any other actual
101 strings among the matching ones."""
103 self
.match_all
= match_all
106 return str(self
.output
)
108 def __cmp__(self
, other
):
111 def matches(self
, other
, except_re
=None):
112 """Return whether SELF.output matches OTHER (which may be a list
113 of newline-terminated lines, or a single string). Either value
115 if self
.output
is None:
118 expected
= self
.output
124 if not isinstance(actual
, list):
126 if not isinstance(expected
, list):
127 expected
= [expected
]
130 return self
.matches_except(expected
, actual
, except_re
)
132 return self
.is_equivalent_list(expected
, actual
)
134 def matches_except(self
, expected
, actual
, except_re
):
135 "Return whether EXPECTED and ACTUAL match except for except_re."
136 if not self
.is_regex
:
139 while i_expected
< len(expected
) and i_actual
< len(actual
):
140 if re
.match(except_re
, actual
[i_actual
]):
142 elif re
.match(except_re
, expected
[i_expected
]):
144 elif expected
[i_expected
] == actual
[i_actual
]:
149 if i_expected
== len(expected
) and i_actual
== len(actual
):
153 raise Exception("is_regex and except_re are mutually exclusive")
155 def is_equivalent_list(self
, expected
, actual
):
156 "Return whether EXPECTED and ACTUAL are equivalent."
157 if not self
.is_regex
:
159 # The EXPECTED lines must match the ACTUAL lines, one-to-one, in
161 return expected
== actual
163 # The EXPECTED lines must match a subset of the ACTUAL lines,
164 # one-to-one, in the same order, with zero or more other ACTUAL
165 # lines interspersed among the matching ACTUAL lines.
167 for actual_line
in actual
:
168 if expected
[i_expected
] == actual_line
:
170 if i_expected
== len(expected
):
174 expected_re
= expected
[0]
175 # If we want to check that every line matches the regexp
176 # assume they all match and look for any that don't. If
177 # only one line matching the regexp is enough, assume none
178 # match and look for even one that does.
180 all_lines_match_re
= True
182 all_lines_match_re
= False
184 # If a regex was provided assume that we actually require
185 # some output. Fail if we don't have any.
189 for actual_line
in actual
:
191 if not re
.match(expected_re
, actual_line
):
194 # As soon an actual_line matches something, then we're good.
195 if re
.match(expected_re
, actual_line
):
198 return all_lines_match_re
200 def display_differences(self
, message
, label
, actual
):
201 """Delegate to the display_lines() routine with the appropriate
202 args. MESSAGE is ignored if None."""
203 display_lines(message
, label
, self
.output
, actual
,
204 self
.is_regex
, self
.is_unordered
)
207 class AnyOutput(ExpectedOutput
):
209 ExpectedOutput
.__init
__(self
, None, False)
211 def is_equivalent_list(self
, ignored
, actual
):
213 # No actual output. No match.
217 # If any line has some text, then there is output, so we match.
221 # We did not find a line with text. No match.
224 def display_differences(self
, message
, label
, actual
):
229 class RegexOutput(ExpectedOutput
):
233 class UnorderedOutput(ExpectedOutput
):
234 """Marks unordered output, and performs comparisons."""
238 def __cmp__(self
, other
):
241 def is_equivalent_list(self
, expected
, actual
):
242 "Disregard the order of ACTUAL lines during comparison."
244 e_set
= set(expected
)
248 if len(e_set
) != len(a_set
):
251 for expect_re
in e_set
:
252 for actual_line
in a_set
:
253 if re
.match(expect_re
, actual_line
):
254 a_set
.remove(actual_line
)
257 # One of the regexes was not found
261 # All expected lines must be in the output.
262 return e_set
== a_set
265 # If any of the expected regexes are in the output, then we match.
266 for expect_re
in e_set
:
267 for actual_line
in a_set
:
268 if re
.match(expect_re
, actual_line
):
272 # If any of the expected lines are in the output, then we match.
273 return len(e_set
.intersection(a_set
)) > 0
276 class UnorderedRegexOutput(UnorderedOutput
, RegexOutput
):
281 ######################################################################
282 # Displaying expected and actual output
284 def display_trees(message
, label
, expected
, actual
):
285 'Print two trees, expected and actual.'
286 if message
is not None:
288 if expected
is not None:
289 print('EXPECTED %s:' % label
)
290 svntest
.tree
.dump_tree(expected
)
291 if actual
is not None:
292 print('ACTUAL %s:' % label
)
293 svntest
.tree
.dump_tree(actual
)
296 def display_lines(message
, label
, expected
, actual
, expected_is_regexp
=None,
297 expected_is_unordered
=None):
298 """Print MESSAGE, unless it is None, then print EXPECTED (labeled
299 with LABEL) followed by ACTUAL (also labeled with LABEL).
300 Both EXPECTED and ACTUAL may be strings or lists of strings."""
301 if message
is not None:
303 if expected
is not None:
304 output
= 'EXPECTED %s' % label
305 if expected_is_regexp
:
306 output
+= ' (regexp)'
307 expected
= [expected
+ '\n']
308 if expected_is_unordered
:
309 output
+= ' (unordered)'
314 if actual
is not None:
315 print('ACTUAL %s:' % label
)
319 # Additionally print unified diff
320 if not expected_is_regexp
:
321 print('DIFF ' + ' '.join(output
.split(' ')[1:]))
323 if type(expected
) is str:
324 expected
= [expected
]
326 if type(actual
) is str:
329 for x
in unified_diff(expected
, actual
,
330 fromfile
="EXPECTED %s" % label
,
331 tofile
="ACTUAL %s" % label
):
334 def compare_and_display_lines(message
, label
, expected
, actual
,
335 raisable
=None, except_re
=None):
336 """Compare two sets of output lines, and print them if they differ,
337 preceded by MESSAGE iff not None. EXPECTED may be an instance of
338 ExpectedOutput (and if not, it is wrapped as such). RAISABLE is an
339 exception class, an instance of which is thrown if ACTUAL doesn't
342 raisable
= svntest
.main
.SVNLineUnequal
343 ### It'd be nicer to use createExpectedOutput() here, but its
344 ### semantics don't match all current consumers of this function.
345 if not isinstance(expected
, ExpectedOutput
):
346 expected
= ExpectedOutput(expected
)
348 if isinstance(actual
, str):
350 actual
= [line
for line
in actual
if not line
.startswith('DBG:')]
352 if not expected
.matches(actual
, except_re
):
353 expected
.display_differences(message
, label
, actual
)
356 def verify_outputs(message
, actual_stdout
, actual_stderr
,
357 expected_stdout
, expected_stderr
, all_stdout
=True):
358 """Compare and display expected vs. actual stderr and stdout lines:
359 if they don't match, print the difference (preceded by MESSAGE iff
360 not None) and raise an exception.
362 If EXPECTED_STDERR or EXPECTED_STDOUT is a string the string is
363 interpreted as a regular expression. For EXPECTED_STDOUT and
364 ACTUAL_STDOUT to match, every line in ACTUAL_STDOUT must match the
365 EXPECTED_STDOUT regex, unless ALL_STDOUT is false. For
366 EXPECTED_STDERR regexes only one line in ACTUAL_STDERR need match."""
367 expected_stderr
= createExpectedOutput(expected_stderr
, 'stderr', False)
368 expected_stdout
= createExpectedOutput(expected_stdout
, 'stdout', all_stdout
)
370 for (actual
, expected
, label
, raisable
) in (
371 (actual_stderr
, expected_stderr
, 'STDERR', SVNExpectedStderr
),
372 (actual_stdout
, expected_stdout
, 'STDOUT', SVNExpectedStdout
)):
376 if isinstance(expected
, RegexOutput
):
377 raisable
= svntest
.main
.SVNUnmatchedError
378 elif not isinstance(expected
, AnyOutput
):
379 raisable
= svntest
.main
.SVNLineUnequal
381 compare_and_display_lines(message
, label
, expected
, actual
, raisable
)
383 def verify_exit_code(message
, actual
, expected
,
384 raisable
=SVNUnexpectedExitCode
):
385 """Compare and display expected vs. actual exit codes:
386 if they don't match, print the difference (preceded by MESSAGE iff
387 not None) and raise an exception."""
389 if expected
!= actual
:
390 display_lines(message
, "Exit Code",
391 str(expected
) + '\n', str(actual
) + '\n')