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-2007 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 SVNIncorrectDatatype(SVNUnexpectedOutput
):
53 """Exception raised if invalid input is passed to the
54 run_and_verify_* API"""
58 ######################################################################
59 # Comparison of expected vs. actual output
61 def createExpectedOutput(expected
, match_all
=True):
62 """Return EXPECTED, promoted to an ExpectedOutput instance if not
63 None. Raise SVNIncorrectDatatype if the data type of EXPECTED is
65 if isinstance(expected
, type([])):
66 expected
= ExpectedOutput(expected
)
67 elif isinstance(expected
, type('')):
68 expected
= RegexOutput(expected
, match_all
)
69 elif expected
== AnyOutput
:
70 expected
= AnyOutput()
71 elif expected
is not None and not isinstance(expected
, ExpectedOutput
):
72 raise SVNIncorrectDatatype("Unexpected type for '%s' data" % output_type
)
76 """Contains expected output, and performs comparisions."""
77 def __init__(self
, output
, match_all
=True):
78 """Set SELF.output (which may be None). If MATCH_ALL is True,
79 require that all lines from OUTPUT match when performing
80 comparsisons. If False, allow any lines to match."""
82 self
.match_all
= match_all
83 self
.is_reg_exp
= False
86 return str(self
.output
)
88 def __cmp__(self
, other
):
89 """Return whether SELF.output matches OTHER (which may be a list
90 of newline-terminated lines, or a single string). Either value
92 if self
.output
is None:
95 expected
= self
.output
101 if isinstance(actual
, list):
102 if isinstance(expected
, type('')):
103 expected
= [expected
]
104 is_match
= self
.is_equivalent_list(expected
, actual
)
105 elif isinstance(actual
, type('')):
106 is_match
= self
.is_equivalent_line(expected
, actual
)
107 else: # unhandled type
115 def is_equivalent_list(self
, expected
, actual
):
116 "Return whether EXPECTED and ACTUAL are equivalent."
117 if not self
.is_reg_exp
:
118 if len(expected
) != len(actual
):
120 for i
in range(0, len(actual
)):
121 if not self
.is_equivalent_line(expected
[i
], actual
[i
]):
125 expected_re
= expected
[0]
126 # If we want to check that every line matches the regexp
127 # assume they all match and look for any that don't. If
128 # only one line matching the regexp is enough, assume none
129 # match and look for even one that does.
131 all_lines_match_re
= True
133 all_lines_match_re
= False
134 for i
in range(0, len(actual
)):
136 if not self
.is_equivalent_line(expected_re
, actual
[i
]):
137 all_lines_match_re
= False
140 if self
.is_equivalent_line(expected_re
, actual
[i
]):
142 return all_lines_match_re
144 def is_equivalent_line(self
, expected
, actual
):
145 "Return whether EXPECTED and ACTUAL are equal."
146 return expected
== actual
148 def display_differences(self
, message
, label
, actual
):
149 """Delegate to the display_lines() routine with the appropriate
150 args. MESSAGE is ignored if None."""
151 display_lines(message
, label
, self
.output
, actual
, False, False)
153 class AnyOutput(ExpectedOutput
):
155 ExpectedOutput
.__init
__(self
, None, False)
157 def is_equivalent_list(self
, ignored
, actual
):
159 # Empty text or empty list -- either way, no output!
161 elif isinstance(actual
, list):
163 if self
.is_equivalent_line(None, line
):
169 def is_equivalent_line(self
, ignored
, actual
):
170 return len(actual
) > 0
172 def display_differences(self
, message
, label
, actual
):
176 class RegexOutput(ExpectedOutput
):
177 def __init__(self
, output
, match_all
=True, is_reg_exp
=True):
179 self
.match_all
= match_all
180 self
.is_reg_exp
= is_reg_exp
182 def is_equivalent_line(self
, expected
, actual
):
183 "Return whether the regex EXPECTED matches the ACTUAL text."
184 return re
.match(expected
, actual
) is not None
186 def display_differences(self
, message
, label
, actual
):
187 display_lines(message
, label
, self
.output
, actual
, True, False)
189 class UnorderedOutput(ExpectedOutput
):
190 """Marks unordered output, and performs comparisions."""
192 def __cmp__(self
, other
):
195 return ExpectedOutput
.__cmp
__(self
, other
)
199 def is_equivalent_list(self
, expected
, actual
):
200 "Disregard the order of ACTUAL lines during comparison."
202 if len(expected
) != len(actual
):
204 expected
= list(expected
)
205 for actual_line
in actual
:
207 i
= self
.is_equivalent_line(expected
, actual_line
)
213 for actual_line
in actual
:
215 self
.is_equivalent_line(expected
, actual_line
)
221 def is_equivalent_line(self
, expected
, actual
):
222 """Return the index into the EXPECTED lines of the line ACTUAL.
223 Raise ValueError if not found."""
224 return expected
.index(actual
)
226 def display_differences(self
, message
, label
, actual
):
227 display_lines(message
, label
, self
.output
, actual
, False, True)
229 class UnorderedRegexOutput(UnorderedOutput
, RegexOutput
):
230 def is_equivalent_line(self
, expected
, actual
):
231 for i
in range(0, len(expected
)):
232 if RegexOutput
.is_equivalent_line(self
, expected
[i
], actual
):
235 raise ValueError("'%s' not found" % actual
)
237 def display_differences(self
, message
, label
, actual
):
238 display_lines(message
, label
, self
.output
, actual
, True, True)
241 ######################################################################
242 # Displaying expected and actual output
244 def display_trees(message
, label
, expected
, actual
):
245 'Print two trees, expected and actual.'
246 if message
is not None:
248 if expected
is not None:
249 print 'EXPECTED', label
+ ':'
250 tree
.dump_tree(expected
)
251 if actual
is not None:
252 print 'ACTUAL', label
+ ':'
253 tree
.dump_tree(actual
)
256 def display_lines(message
, label
, expected
, actual
, expected_is_regexp
=None,
257 expected_is_unordered
=None):
258 """Print MESSAGE, unless it is None, then print EXPECTED (labeled
259 with LABEL) followed by ACTUAL (also labeled with LABEL).
260 Both EXPECTED and ACTUAL may be strings or lists of strings."""
261 if message
is not None:
263 if expected
is not None:
264 output
= 'EXPECTED %s' % label
265 if expected_is_regexp
:
266 output
+= ' (regexp)'
267 if expected_is_unordered
:
268 output
+= ' (unordered)'
271 map(sys
.stdout
.write
, expected
)
272 if expected_is_regexp
:
273 map(sys
.stdout
.write
, '\n')
274 if actual
is not None:
275 print 'ACTUAL %s:' % label
276 map(sys
.stdout
.write
, actual
)
278 def compare_and_display_lines(message
, label
, expected
, actual
,
279 raisable
=main
.SVNLineUnequal
):
280 """Compare two sets of output lines, and print them if they differ.
281 MESSAGE is ignored if None. EXPECTED may be an instance of
282 ExpectedOutput (and if not, it is wrapped as such). RAISABLE is an
283 exception class, an instance of which is thrown if ACTUAL doesn't
285 ### It'd be nicer to use createExpectedOutput() here, but its
286 ### semantics don't match all current consumers of this function.
287 if not isinstance(expected
, ExpectedOutput
):
288 expected
= ExpectedOutput(expected
)
290 if expected
!= actual
:
291 expected
.display_differences(message
, label
, actual
)
294 def verify_outputs(message
, actual_stdout
, actual_stderr
,
295 expected_stdout
, expected_stderr
):
296 """Compare and display expected vs. actual stderr and stdout lines,
297 raising an exception if outputs don't match. If EXPECTED_STDERR or
298 EXPECTED_STDOUT is a string the string is interpreted as a regular
299 expression. For EXPECTED_STDOUT and ACTUAL_STDOUT to match, every
300 line in ACTUAL_STDOUT must match the EXPECTED_STDOUT regex. For
301 EXPECTED_STDERR regexes only one line in ACTUAL_STDERR need match."""
302 expected_stderr
= createExpectedOutput(expected_stderr
, False)
303 expected_stdout
= createExpectedOutput(expected_stdout
, True)
305 for (actual
, expected
, label
, raisable
) in (
306 (actual_stderr
, expected_stderr
, 'STDERR', SVNExpectedStderr
),
307 (actual_stdout
, expected_stdout
, 'STDOUT', SVNExpectedStdout
)):
311 expected
= createExpectedOutput(expected
)
312 if isinstance(expected
, RegexOutput
):
313 raisable
= main
.SVNUnmatchedError
314 elif not isinstance(expected
, AnyOutput
):
315 raisable
= main
.SVNLineUnequal
317 compare_and_display_lines(message
, label
, expected
, actual
, raisable
)