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 ######################################################################
32 ######################################################################
35 class SVNUnexpectedOutput(svntest
.Failure
):
36 """Exception raised if an invocation of svn results in unexpected
37 output of any kind."""
40 class SVNUnexpectedStdout(SVNUnexpectedOutput
):
41 """Exception raised if an invocation of svn results in unexpected
45 class SVNUnexpectedStderr(SVNUnexpectedOutput
):
46 """Exception raised if an invocation of svn results in unexpected
50 class SVNExpectedStdout(SVNUnexpectedOutput
):
51 """Exception raised if an invocation of svn results in no output on
52 STDOUT when output was expected."""
55 class SVNExpectedStderr(SVNUnexpectedOutput
):
56 """Exception raised if an invocation of svn results in no output on
57 STDERR when output was expected."""
60 class SVNUnexpectedExitCode(SVNUnexpectedOutput
):
61 """Exception raised if an invocation of svn exits with a value other
62 than what was expected."""
65 class SVNIncorrectDatatype(SVNUnexpectedOutput
):
66 """Exception raised if invalid input is passed to the
67 run_and_verify_* API"""
71 ######################################################################
72 # Comparison of expected vs. actual output
74 def createExpectedOutput(expected
, output_type
, match_all
=True):
75 """Return EXPECTED, promoted to an ExpectedOutput instance if not
76 None. Raise SVNIncorrectDatatype if the data type of EXPECTED is
78 if isinstance(expected
, list):
79 expected
= ExpectedOutput(expected
)
80 elif isinstance(expected
, str):
81 expected
= RegexOutput(expected
, match_all
)
82 elif expected
== AnyOutput
:
83 expected
= AnyOutput()
84 elif expected
is not None and not isinstance(expected
, ExpectedOutput
):
85 raise SVNIncorrectDatatype("Unexpected type for '%s' data" % output_type
)
89 """Contains expected output, and performs comparisons."""
90 def __init__(self
, output
, match_all
=True):
91 """Initialize the expected output to OUTPUT which is a string, or a list
92 of strings, or None meaning an empty list. If MATCH_ALL is True, the
93 expected strings will be matched with the actual strings, one-to-one, in
94 the same order. If False, they will be matched with a subset of the
95 actual strings, one-to-one, in the same order, ignoring any other actual
96 strings among the matching ones."""
98 self
.match_all
= match_all
99 self
.is_reg_exp
= False
102 return str(self
.output
)
104 def __cmp__(self
, other
):
105 """Return whether SELF.output matches OTHER (which may be a list
106 of newline-terminated lines, or a single string). Either value
108 if self
.output
is None:
111 expected
= self
.output
117 if isinstance(actual
, list):
118 if isinstance(expected
, str):
119 expected
= [expected
]
120 is_match
= self
.is_equivalent_list(expected
, actual
)
121 elif isinstance(actual
, str):
122 is_match
= self
.is_equivalent_line(expected
, actual
)
123 else: # unhandled type
131 def is_equivalent_list(self
, expected
, actual
):
132 "Return whether EXPECTED and ACTUAL are equivalent."
133 if not self
.is_reg_exp
:
135 # The EXPECTED lines must match the ACTUAL lines, one-to-one, in
137 if len(expected
) != len(actual
):
139 for i
in range(0, len(actual
)):
140 if not self
.is_equivalent_line(expected
[i
], actual
[i
]):
144 # The EXPECTED lines must match a subset of the ACTUAL lines,
145 # one-to-one, in the same order, with zero or more other ACTUAL
146 # lines interspersed among the matching ACTUAL lines.
148 for actual_line
in actual
:
149 if self
.is_equivalent_line(expected
[i_expected
], actual_line
):
151 if i_expected
== len(expected
):
155 expected_re
= expected
[0]
156 # If we want to check that every line matches the regexp
157 # assume they all match and look for any that don't. If
158 # only one line matching the regexp is enough, assume none
159 # match and look for even one that does.
161 all_lines_match_re
= True
163 all_lines_match_re
= False
165 # If a regex was provided assume that we actually require
166 # some output. Fail if we don't have any.
170 for i
in range(0, len(actual
)):
172 if not self
.is_equivalent_line(expected_re
, actual
[i
]):
173 all_lines_match_re
= False
176 if self
.is_equivalent_line(expected_re
, actual
[i
]):
178 return all_lines_match_re
180 def is_equivalent_line(self
, expected
, actual
):
181 "Return whether EXPECTED and ACTUAL are equal."
182 return expected
== actual
184 def display_differences(self
, message
, label
, actual
):
185 """Delegate to the display_lines() routine with the appropriate
186 args. MESSAGE is ignored if None."""
187 display_lines(message
, label
, self
.output
, actual
, False, False)
189 class AnyOutput(ExpectedOutput
):
191 ExpectedOutput
.__init
__(self
, None, False)
193 def is_equivalent_list(self
, ignored
, actual
):
195 # Empty text or empty list -- either way, no output!
197 elif isinstance(actual
, list):
199 if self
.is_equivalent_line(None, line
):
205 def is_equivalent_line(self
, ignored
, actual
):
206 return len(actual
) > 0
208 def display_differences(self
, message
, label
, actual
):
212 class RegexOutput(ExpectedOutput
):
213 def __init__(self
, output
, match_all
=True, is_reg_exp
=True):
215 self
.match_all
= match_all
216 self
.is_reg_exp
= is_reg_exp
218 def is_equivalent_line(self
, expected
, actual
):
219 "Return whether the regex EXPECTED matches the ACTUAL text."
220 return re
.match(expected
, actual
) is not None
222 def display_differences(self
, message
, label
, actual
):
223 display_lines(message
, label
, self
.output
, actual
, True, False)
225 class UnorderedOutput(ExpectedOutput
):
226 """Marks unordered output, and performs comparisons."""
228 def __cmp__(self
, other
):
231 return ExpectedOutput
.__cmp
__(self
, other
)
235 def is_equivalent_list(self
, expected
, actual
):
236 "Disregard the order of ACTUAL lines during comparison."
238 if len(expected
) != len(actual
):
240 expected
= list(expected
)
241 for actual_line
in actual
:
243 i
= self
.is_equivalent_line(expected
, actual_line
)
249 for actual_line
in actual
:
251 self
.is_equivalent_line(expected
, actual_line
)
257 def is_equivalent_line(self
, expected
, actual
):
258 """Return the index into the EXPECTED lines of the line ACTUAL.
259 Raise ValueError if not found."""
260 return expected
.index(actual
)
262 def display_differences(self
, message
, label
, actual
):
263 display_lines(message
, label
, self
.output
, actual
, False, True)
265 class UnorderedRegexOutput(UnorderedOutput
, RegexOutput
):
266 def is_equivalent_line(self
, expected
, actual
):
267 for i
in range(0, len(expected
)):
268 if RegexOutput
.is_equivalent_line(self
, expected
[i
], actual
):
271 raise ValueError("'%s' not found" % actual
)
273 def display_differences(self
, message
, label
, actual
):
274 display_lines(message
, label
, self
.output
, actual
, True, True)
277 ######################################################################
278 # Displaying expected and actual output
280 def display_trees(message
, label
, expected
, actual
):
281 'Print two trees, expected and actual.'
282 if message
is not None:
284 if expected
is not None:
285 print('EXPECTED %s:' % label
)
286 svntest
.tree
.dump_tree(expected
)
287 if actual
is not None:
288 print('ACTUAL %s:' % label
)
289 svntest
.tree
.dump_tree(actual
)
292 def display_lines(message
, label
, expected
, actual
, expected_is_regexp
=None,
293 expected_is_unordered
=None):
294 """Print MESSAGE, unless it is None, then print EXPECTED (labeled
295 with LABEL) followed by ACTUAL (also labeled with LABEL).
296 Both EXPECTED and ACTUAL may be strings or lists of strings."""
297 if message
is not None:
299 if expected
is not None:
300 output
= 'EXPECTED %s' % label
301 if expected_is_regexp
:
302 output
+= ' (regexp)'
303 if expected_is_unordered
:
304 output
+= ' (unordered)'
309 if expected_is_regexp
:
310 sys
.stdout
.write('\n')
311 if actual
is not None:
312 print('ACTUAL %s:' % label
)
316 def compare_and_display_lines(message
, label
, expected
, actual
,
318 """Compare two sets of output lines, and print them if they differ,
319 preceded by MESSAGE iff not None. EXPECTED may be an instance of
320 ExpectedOutput (and if not, it is wrapped as such). RAISABLE is an
321 exception class, an instance of which is thrown if ACTUAL doesn't
324 raisable
= svntest
.main
.SVNLineUnequal
325 ### It'd be nicer to use createExpectedOutput() here, but its
326 ### semantics don't match all current consumers of this function.
327 if not isinstance(expected
, ExpectedOutput
):
328 expected
= ExpectedOutput(expected
)
330 if isinstance(actual
, str):
332 actual
= [line
for line
in actual
if not line
.startswith('DBG:')]
334 if expected
!= actual
:
335 expected
.display_differences(message
, label
, actual
)
338 def verify_outputs(message
, actual_stdout
, actual_stderr
,
339 expected_stdout
, expected_stderr
, all_stdout
=True):
340 """Compare and display expected vs. actual stderr and stdout lines:
341 if they don't match, print the difference (preceded by MESSAGE iff
342 not None) and raise an exception.
344 If EXPECTED_STDERR or EXPECTED_STDOUT is a string the string is
345 interpreted as a regular expression. For EXPECTED_STDOUT and
346 ACTUAL_STDOUT to match, every line in ACTUAL_STDOUT must match the
347 EXPECTED_STDOUT regex, unless ALL_STDOUT is false. For
348 EXPECTED_STDERR regexes only one line in ACTUAL_STDERR need match."""
349 expected_stderr
= createExpectedOutput(expected_stderr
, 'stderr', False)
350 expected_stdout
= createExpectedOutput(expected_stdout
, 'stdout', all_stdout
)
352 for (actual
, expected
, label
, raisable
) in (
353 (actual_stderr
, expected_stderr
, 'STDERR', SVNExpectedStderr
),
354 (actual_stdout
, expected_stdout
, 'STDOUT', SVNExpectedStdout
)):
358 if isinstance(expected
, RegexOutput
):
359 raisable
= svntest
.main
.SVNUnmatchedError
360 elif not isinstance(expected
, AnyOutput
):
361 raisable
= svntest
.main
.SVNLineUnequal
363 compare_and_display_lines(message
, label
, expected
, actual
, raisable
)
365 def verify_exit_code(message
, actual
, expected
,
366 raisable
=SVNUnexpectedExitCode
):
367 """Compare and display expected vs. actual exit codes:
368 if they don't match, print the difference (preceded by MESSAGE iff
369 not None) and raise an exception."""
371 if expected
!= actual
:
372 display_lines(message
, "Exit Code",
373 str(expected
) + '\n', str(actual
) + '\n')