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 # Copyright (c) 2000-2008 CollabNet. All rights reserved.
11 # This software is licensed as described in the file COPYING, which
12 # you should have received as part of this distribution. The terms
13 # are also available at http://subversion.tigris.org/license-1.html.
14 # If newer versions of this license are posted there, you may use a
15 # newer version instead, at your option.
17 ######################################################################
21 import main
, tree
, wc
# general svntest routines in this module.
22 from svntest
import Failure
24 ######################################################################
27 class SVNUnexpectedOutput(Failure
):
28 """Exception raised if an invocation of svn results in unexpected
29 output of any kind."""
32 class SVNUnexpectedStdout(SVNUnexpectedOutput
):
33 """Exception raised if an invocation of svn results in unexpected
37 class SVNUnexpectedStderr(SVNUnexpectedOutput
):
38 """Exception raised if an invocation of svn results in unexpected
42 class SVNExpectedStdout(SVNUnexpectedOutput
):
43 """Exception raised if an invocation of svn results in no output on
44 STDOUT when output was expected."""
47 class SVNExpectedStderr(SVNUnexpectedOutput
):
48 """Exception raised if an invocation of svn results in no output on
49 STDERR when output was expected."""
52 class SVNUnexpectedExitCode(SVNUnexpectedOutput
):
53 """Exception raised if an invocation of svn exits with a value other
54 than what was expected."""
57 class SVNIncorrectDatatype(SVNUnexpectedOutput
):
58 """Exception raised if invalid input is passed to the
59 run_and_verify_* API"""
63 ######################################################################
64 # Comparison of expected vs. actual output
66 def createExpectedOutput(expected
, match_all
=True):
67 """Return EXPECTED, promoted to an ExpectedOutput instance if not
68 None. Raise SVNIncorrectDatatype if the data type of EXPECTED is
70 if isinstance(expected
, type([])):
71 expected
= ExpectedOutput(expected
)
72 elif isinstance(expected
, type('')):
73 expected
= RegexOutput(expected
, match_all
)
74 elif expected
== AnyOutput
:
75 expected
= AnyOutput()
76 elif expected
is not None and not isinstance(expected
, ExpectedOutput
):
77 raise SVNIncorrectDatatype("Unexpected type for '%s' data" % output_type
)
81 """Contains expected output, and performs comparisions."""
82 def __init__(self
, output
, match_all
=True):
83 """Set SELF.output (which may be None). If MATCH_ALL is True,
84 require that all lines from OUTPUT match when performing
85 comparsisons. If False, allow any lines to match."""
87 self
.match_all
= match_all
88 self
.is_reg_exp
= False
91 return str(self
.output
)
93 def __cmp__(self
, other
):
94 """Return whether SELF.output matches OTHER (which may be a list
95 of newline-terminated lines, or a single string). Either value
97 if self
.output
is None:
100 expected
= self
.output
106 if isinstance(actual
, list):
107 if isinstance(expected
, type('')):
108 expected
= [expected
]
109 is_match
= self
.is_equivalent_list(expected
, actual
)
110 elif isinstance(actual
, type('')):
111 is_match
= self
.is_equivalent_line(expected
, actual
)
112 else: # unhandled type
120 def is_equivalent_list(self
, expected
, actual
):
121 "Return whether EXPECTED and ACTUAL are equivalent."
122 if not self
.is_reg_exp
:
123 if len(expected
) != len(actual
):
125 for i
in range(0, len(actual
)):
126 if not self
.is_equivalent_line(expected
[i
], actual
[i
]):
130 expected_re
= expected
[0]
131 # If we want to check that every line matches the regexp
132 # assume they all match and look for any that don't. If
133 # only one line matching the regexp is enough, assume none
134 # match and look for even one that does.
136 all_lines_match_re
= True
138 all_lines_match_re
= False
140 # If a regex was provided assume that we actually require
141 # some output. Fail if we don't.
145 for i
in range(0, len(actual
)):
147 if not self
.is_equivalent_line(expected_re
, actual
[i
]):
148 all_lines_match_re
= False
151 if self
.is_equivalent_line(expected_re
, actual
[i
]):
153 return all_lines_match_re
155 def is_equivalent_line(self
, expected
, actual
):
156 "Return whether EXPECTED and ACTUAL are equal."
157 return expected
== actual
159 def display_differences(self
, message
, label
, actual
):
160 """Delegate to the display_lines() routine with the appropriate
161 args. MESSAGE is ignored if None."""
162 display_lines(message
, label
, self
.output
, actual
, False, False)
164 class AnyOutput(ExpectedOutput
):
166 ExpectedOutput
.__init
__(self
, None, False)
168 def is_equivalent_list(self
, ignored
, actual
):
170 # Empty text or empty list -- either way, no output!
172 elif isinstance(actual
, list):
174 if self
.is_equivalent_line(None, line
):
180 def is_equivalent_line(self
, ignored
, actual
):
181 return len(actual
) > 0
183 def display_differences(self
, message
, label
, actual
):
187 class RegexOutput(ExpectedOutput
):
188 def __init__(self
, output
, match_all
=True, is_reg_exp
=True):
190 self
.match_all
= match_all
191 self
.is_reg_exp
= is_reg_exp
193 def is_equivalent_line(self
, expected
, actual
):
194 "Return whether the regex EXPECTED matches the ACTUAL text."
195 return re
.match(expected
, actual
) is not None
197 def display_differences(self
, message
, label
, actual
):
198 display_lines(message
, label
, self
.output
, actual
, True, False)
200 class UnorderedOutput(ExpectedOutput
):
201 """Marks unordered output, and performs comparisions."""
203 def __cmp__(self
, other
):
206 return ExpectedOutput
.__cmp
__(self
, other
)
210 def is_equivalent_list(self
, expected
, actual
):
211 "Disregard the order of ACTUAL lines during comparison."
213 if len(expected
) != len(actual
):
215 expected
= list(expected
)
216 for actual_line
in actual
:
218 i
= self
.is_equivalent_line(expected
, actual_line
)
224 for actual_line
in actual
:
226 self
.is_equivalent_line(expected
, actual_line
)
232 def is_equivalent_line(self
, expected
, actual
):
233 """Return the index into the EXPECTED lines of the line ACTUAL.
234 Raise ValueError if not found."""
235 return expected
.index(actual
)
237 def display_differences(self
, message
, label
, actual
):
238 display_lines(message
, label
, self
.output
, actual
, False, True)
240 class UnorderedRegexOutput(UnorderedOutput
, RegexOutput
):
241 def is_equivalent_line(self
, expected
, actual
):
242 for i
in range(0, len(expected
)):
243 if RegexOutput
.is_equivalent_line(self
, expected
[i
], actual
):
246 raise ValueError("'%s' not found" % actual
)
248 def display_differences(self
, message
, label
, actual
):
249 display_lines(message
, label
, self
.output
, actual
, True, True)
252 ######################################################################
253 # Displaying expected and actual output
255 def display_trees(message
, label
, expected
, actual
):
256 'Print two trees, expected and actual.'
257 if message
is not None:
259 if expected
is not None:
260 print('EXPECTED %s:' % label
)
261 tree
.dump_tree(expected
)
262 if actual
is not None:
263 print('ACTUAL %s:' % label
)
264 tree
.dump_tree(actual
)
267 def display_lines(message
, label
, expected
, actual
, expected_is_regexp
=None,
268 expected_is_unordered
=None):
269 """Print MESSAGE, unless it is None, then print EXPECTED (labeled
270 with LABEL) followed by ACTUAL (also labeled with LABEL).
271 Both EXPECTED and ACTUAL may be strings or lists of strings."""
272 if message
is not None:
274 if expected
is not None:
275 output
= 'EXPECTED %s' % label
276 if expected_is_regexp
:
277 output
+= ' (regexp)'
278 if expected_is_unordered
:
279 output
+= ' (unordered)'
284 if expected_is_regexp
:
285 sys
.stdout
.write('\n')
286 if actual
is not None:
287 print('ACTUAL %s:' % label
)
291 def compare_and_display_lines(message
, label
, expected
, actual
,
292 raisable
=main
.SVNLineUnequal
):
293 """Compare two sets of output lines, and print them if they differ,
294 preceded by MESSAGE iff not None. EXPECTED may be an instance of
295 ExpectedOutput (and if not, it is wrapped as such). RAISABLE is an
296 exception class, an instance of which is thrown if ACTUAL doesn't
298 ### It'd be nicer to use createExpectedOutput() here, but its
299 ### semantics don't match all current consumers of this function.
300 if not isinstance(expected
, ExpectedOutput
):
301 expected
= ExpectedOutput(expected
)
303 if expected
!= actual
:
304 expected
.display_differences(message
, label
, actual
)
307 def verify_outputs(message
, actual_stdout
, actual_stderr
,
308 expected_stdout
, expected_stderr
, all_stdout
=True):
309 """Compare and display expected vs. actual stderr and stdout lines:
310 if they don't match, print the difference (preceded by MESSAGE iff
311 not None) and raise an exception.
313 If EXPECTED_STDERR or EXPECTED_STDOUT is a string the string is
314 interpreted as a regular expression. For EXPECTED_STDOUT and
315 ACTUAL_STDOUT to match, every line in ACTUAL_STDOUT must match the
316 EXPECTED_STDOUT regex, unless ALL_STDOUT is false. For
317 EXPECTED_STDERR regexes only one line in ACTUAL_STDERR need match."""
318 expected_stderr
= createExpectedOutput(expected_stderr
, False)
319 expected_stdout
= createExpectedOutput(expected_stdout
, all_stdout
)
321 for (actual
, expected
, label
, raisable
) in (
322 (actual_stderr
, expected_stderr
, 'STDERR', SVNExpectedStderr
),
323 (actual_stdout
, expected_stdout
, 'STDOUT', SVNExpectedStdout
)):
327 expected
= createExpectedOutput(expected
)
328 if isinstance(expected
, RegexOutput
):
329 raisable
= main
.SVNUnmatchedError
330 elif not isinstance(expected
, AnyOutput
):
331 raisable
= main
.SVNLineUnequal
333 compare_and_display_lines(message
, label
, expected
, actual
, raisable
)
335 def verify_exit_code(message
, actual
, expected
,
336 raisable
=SVNUnexpectedExitCode
):
337 """Compare and display expected vs. actual exit codes:
338 if they don't match, print the difference (preceded by MESSAGE iff
339 not None) and raise an exception."""
341 # main.spawn_process() (by virtue of main.wait_on_pipe()) will return an
342 # exit code of None on platforms not supporting Popen3
343 if actual
is not None and expected
!= actual
:
344 display_lines(message
, "Exit Code",
345 str(expected
) + '\n', str(actual
) + '\n')