In the command-line client, forbid
[svn.git] / subversion / tests / cmdline / svntest / verify.py
blob0b0fbab4b5a0c313e515f18db4ab5bb99321bc85
2 # verify.py: routines that handle comparison and display of expected
3 # vs. actual output
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 ######################################################################
19 import re, sys
21 import main, tree, wc # general svntest routines in this module.
22 from svntest import Failure
24 ######################################################################
25 # Exception types
27 class SVNUnexpectedOutput(Failure):
28 """Exception raised if an invocation of svn results in unexpected
29 output of any kind."""
30 pass
32 class SVNUnexpectedStdout(SVNUnexpectedOutput):
33 """Exception raised if an invocation of svn results in unexpected
34 output on STDOUT."""
35 pass
37 class SVNUnexpectedStderr(SVNUnexpectedOutput):
38 """Exception raised if an invocation of svn results in unexpected
39 output on STDERR."""
40 pass
42 class SVNExpectedStdout(SVNUnexpectedOutput):
43 """Exception raised if an invocation of svn results in no output on
44 STDOUT when output was expected."""
45 pass
47 class SVNExpectedStderr(SVNUnexpectedOutput):
48 """Exception raised if an invocation of svn results in no output on
49 STDERR when output was expected."""
50 pass
52 class SVNIncorrectDatatype(SVNUnexpectedOutput):
53 """Exception raised if invalid input is passed to the
54 run_and_verify_* API"""
55 pass
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
64 not handled."""
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)
73 return expected
75 class ExpectedOutput:
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."""
81 self.output = output
82 self.match_all = match_all
83 self.is_reg_exp = False
85 def __str__(self):
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
91 may be None."""
92 if self.output is None:
93 expected = []
94 else:
95 expected = self.output
96 if other is None:
97 actual = []
98 else:
99 actual = other
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
108 is_match = False
110 if is_match:
111 return 0
112 else:
113 return 1
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):
119 return False
120 for i in range(0, len(actual)):
121 if not self.is_equivalent_line(expected[i], actual[i]):
122 return False
123 return True
124 else:
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.
130 if self.match_all:
131 all_lines_match_re = True
132 else:
133 all_lines_match_re = False
134 for i in range(0, len(actual)):
135 if self.match_all:
136 if not self.is_equivalent_line(expected_re, actual[i]):
137 all_lines_match_re = False
138 break
139 else:
140 if self.is_equivalent_line(expected_re, actual[i]):
141 return True
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):
154 def __init__(self):
155 ExpectedOutput.__init__(self, None, False)
157 def is_equivalent_list(self, ignored, actual):
158 if len(actual) == 0:
159 # Empty text or empty list -- either way, no output!
160 return False
161 elif isinstance(actual, list):
162 for line in actual:
163 if self.is_equivalent_line(None, line):
164 return True
165 return False
166 else:
167 return True
169 def is_equivalent_line(self, ignored, actual):
170 return len(actual) > 0
172 def display_differences(self, message, label, actual):
173 if message:
174 print message
176 class RegexOutput(ExpectedOutput):
177 def __init__(self, output, match_all=True, is_reg_exp=True):
178 self.output = output
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):
193 "Handle ValueError."
194 try:
195 return ExpectedOutput.__cmp__(self, other)
196 except ValueError:
197 return 1
199 def is_equivalent_list(self, expected, actual):
200 "Disregard the order of ACTUAL lines during comparison."
201 if self.match_all:
202 if len(expected) != len(actual):
203 return False
204 expected = list(expected)
205 for actual_line in actual:
206 try:
207 i = self.is_equivalent_line(expected, actual_line)
208 expected.pop(i)
209 except ValueError:
210 return False
211 return True
212 else:
213 for actual_line in actual:
214 try:
215 self.is_equivalent_line(expected, actual_line)
216 return True
217 except ValueError:
218 pass
219 return False
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):
233 return i
234 else:
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:
247 print message
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:
262 print message
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)'
269 output += ':'
270 print output
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
284 match EXPECTED."""
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)
292 raise raisable
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)):
308 if expected is None:
309 continue
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)