3 # ====================================================================
4 # Copyright (c) 2008 CollabNet. All rights reserved.
6 # This software is licensed as described in the file COPYING, which
7 # you should have received as part of this distribution. The terms
8 # are also available at http://subversion.tigris.org/license-1.html.
9 # If newer versions of this license are posted there, you may use a
10 # newer version instead, at your option.
12 # This software consists of voluntary contributions made by many
13 # individuals. For exact contribution history, see the revision
14 # history and logs, available at http://subversion.tigris.org/.
15 # ====================================================================
24 import svn_dav_log_parse
26 class TestCase(unittest
.TestCase
):
28 # Define a class to stuff everything passed to any handle_
29 # method into self.result.
30 class cls(svn_dav_log_parse
.Parser
):
31 def __getattr__(cls_self
, attr
):
32 if attr
.startswith('handle_'):
33 return lambda *a
: setattr(self
, 'result', a
)
35 self
.parse
= cls().parse
37 def test_unknown(self
):
38 line
= 'unknown log line'
40 self
.assertEqual(self
.result
, (line
,))
42 def test_commit(self
):
43 self
.assertRaises(svn_dav_log_parse
.Error
, self
.parse
, 'commit')
44 self
.assertRaises(svn_dav_log_parse
.Error
, self
.parse
, 'commit 3')
45 self
.assertEqual(self
.parse('commit r3'), '')
46 self
.assertEqual(self
.result
, (3,))
47 self
.assertEqual(self
.parse('commit r3 leftover'), ' leftover')
48 self
.assertEqual(self
.result
, (3,))
50 def test_list_dir(self
):
51 self
.assertRaises(svn_dav_log_parse
.Error
, self
.parse
, 'list-dir')
52 self
.assertRaises(svn_dav_log_parse
.Error
, self
.parse
, 'list-dir foo')
53 self
.assertRaises(svn_dav_log_parse
.Error
, self
.parse
, 'list-dir foo 3')
54 self
.assertEqual(self
.parse('list-dir /a/b/c r3 ...'), ' ...')
55 self
.assertEqual(self
.result
, ('/a/b/c', 3))
56 self
.assertEqual(self
.parse('list-dir / r3'), '')
57 self
.assertEqual(self
.result
, ('/', 3))
58 # path must be absolute
59 self
.assertRaises(svn_dav_log_parse
.Error
,
60 self
.parse
, 'list-dir a/b/c r3')
63 self
.assertRaises(svn_dav_log_parse
.Error
, self
.parse
, 'lock')
64 self
.parse('lock /foo')
65 self
.assertEqual(self
.result
, ('/foo', False))
66 self
.assertEqual(self
.parse('lock /foo steal ...'), ' ...')
67 self
.assertEqual(self
.result
, ('/foo', True))
68 self
.assertEqual(self
.parse('lock /foo stear'), ' stear')
70 def test_prop_list(self
):
71 self
.assertRaises(svn_dav_log_parse
.Error
,
72 self
.parse
, 'prop-list')
73 self
.assertRaises(svn_dav_log_parse
.Error
,
74 self
.parse
, 'prop-list /foo')
75 self
.assertRaises(svn_dav_log_parse
.Error
,
76 self
.parse
, 'prop-list /foo@bar')
77 self
.assertEqual(self
.parse('prop-list /foo@3 ...'), ' ...')
78 self
.assertEqual(self
.result
, ('/foo', 3))
80 def test_revprop_change(self
):
81 self
.assertRaises(svn_dav_log_parse
.Error
,
82 self
.parse
, 'revprop-change r3')
83 self
.assertRaises(svn_dav_log_parse
.Error
,
84 self
.parse
, 'revprop-change r svn:log')
85 self
.assertRaises(svn_dav_log_parse
.Error
,
86 self
.parse
, 'revprop-change rX svn:log')
87 self
.assertEqual(self
.parse('revprop-change r3 svn:log ...'), ' ...')
88 self
.assertEqual(self
.result
, (3, 'svn:log'))
90 def test_revprop_list(self
):
91 self
.assertRaises(svn_dav_log_parse
.Error
,
92 self
.parse
, 'revprop-list')
93 self
.assertRaises(svn_dav_log_parse
.Error
,
94 self
.parse
, 'revprop-list r')
95 self
.assertRaises(svn_dav_log_parse
.Error
,
96 self
.parse
, 'revprop-list rX')
97 self
.assertEqual(self
.parse('revprop-list r3 ...'), ' ...')
98 self
.assertEqual(self
.result
, (3,))
100 def test_unlock(self
):
101 self
.assertRaises(svn_dav_log_parse
.Error
, self
.parse
, 'unlock')
102 self
.parse('unlock /foo')
103 self
.assertEqual(self
.result
, ('/foo', False))
104 self
.assertEqual(self
.parse('unlock /foo break ...'), ' ...')
105 self
.assertEqual(self
.result
, ('/foo', True))
106 self
.assertEqual(self
.parse('unlock /foo bear'), ' bear')
108 def test_blame(self
):
109 self
.assertRaises(svn_dav_log_parse
.Error
, self
.parse
, 'blame')
110 self
.assertRaises(svn_dav_log_parse
.Error
,
111 self
.parse
, 'blame /foo 3')
112 self
.assertRaises(svn_dav_log_parse
.Error
,
113 self
.parse
, 'blame /foo 3:a')
114 self
.assertRaises(svn_dav_log_parse
.Error
,
115 self
.parse
, 'blame /foo r3:a')
116 self
.assertEqual(self
.parse('blame /foo r3:4 ...'), ' ...')
117 self
.assertEqual(self
.result
, ('/foo', 3, 4, False))
118 self
.assertEqual(self
.parse('blame /foo r3:4'
119 ' include-merged-revisions ...'), ' ...')
120 self
.assertEqual(self
.result
, ('/foo', 3, 4, True))
122 def test_get_mergeinfo(self
):
123 self
.assertRaises(svn_dav_log_parse
.Error
,
124 self
.parse
, 'get-mergeinfo')
125 self
.assertRaises(svn_dav_log_parse
.Error
,
126 self
.parse
, 'get-mergeinfo /foo')
127 self
.assertRaises(svn_dav_log_parse
.Error
,
128 self
.parse
, 'get-mergeinfo (/foo')
129 self
.assertRaises(svn_dav_log_parse
.Error
,
130 self
.parse
, 'get-mergeinfo (/foo /bar')
131 self
.assertRaises(svn_dav_log_parse
.Error
,
132 self
.parse
, 'get-mergeinfo (/foo)')
133 self
.assertRaises(svn_dav_log_parse
.BadMergeinfoInheritanceError
,
134 self
.parse
, 'get-mergeinfo (/foo) bork')
135 self
.assertEqual(self
.parse('get-mergeinfo (/foo) explicit'), '')
136 self
.assertEqual(self
.result
, (['/foo'],
137 svn
.core
.svn_mergeinfo_explicit
))
138 self
.assertEqual(self
.parse('get-mergeinfo (/foo /bar) inherited ...'),
140 self
.assertEqual(self
.result
, (['/foo', '/bar'],
141 svn
.core
.svn_mergeinfo_inherited
))
144 self
.assertRaises(svn_dav_log_parse
.Error
, self
.parse
, 'log')
145 self
.assertRaises(svn_dav_log_parse
.Error
,
146 self
.parse
, 'log /foo')
147 self
.assertRaises(svn_dav_log_parse
.Error
,
148 self
.parse
, 'log (/foo)')
149 self
.assertEqual(self
.parse('log (/foo) r3:4'
150 ' include-merged-revisions'), '')
151 self
.assertEqual(self
.result
,
152 (['/foo'], 3, 4, 0, False, False, True, []))
153 self
.assertEqual(self
.parse('log (/foo /bar) r3:4 revprops=all ...'),
155 self
.assertEqual(self
.result
,
156 (['/foo', '/bar'], 3, 4, 0, False, False, False, None))
157 self
.assertEqual(self
.parse('log (/foo) r3:4 revprops=(a b) ...'),
159 self
.assertEqual(self
.result
,
160 (['/foo'], 3, 4, 0, False, False, False, ['a', 'b']))
161 self
.assertEqual(self
.parse('log (/foo) r8:1 limit=3'), '')
162 self
.assertEqual(self
.result
,
163 (['/foo'], 8, 1, 3, False, False, False, []))
165 def test_replay(self
):
166 self
.assertRaises(svn_dav_log_parse
.Error
, self
.parse
, 'replay')
167 self
.assertRaises(svn_dav_log_parse
.Error
,
168 self
.parse
, 'replay /foo')
169 self
.assertRaises(svn_dav_log_parse
.Error
,
170 self
.parse
, 'replay (/foo) r9')
171 self
.assertRaises(svn_dav_log_parse
.Error
,
172 self
.parse
, 'replay (/foo) r9:10')
173 self
.assertEqual(self
.parse('replay /foo r9'), '')
174 self
.assertEqual(self
.result
, ('/foo', 9))
176 def test_checkout_or_export(self
):
177 self
.assertRaises(svn_dav_log_parse
.Error
,
178 self
.parse
, 'checkout-or-export')
179 self
.assertRaises(svn_dav_log_parse
.Error
,
180 self
.parse
, 'checkout-or-export /foo')
181 self
.assertEqual(self
.parse('checkout-or-export /foo r9'), '')
182 self
.assertEqual(self
.result
, ('/foo', 9, svn
.core
.svn_depth_unknown
))
183 self
.assertRaises(svn_dav_log_parse
.BadDepthError
, self
.parse
,
184 'checkout-or-export /foo r9 depth=INVALID-DEPTH')
185 self
.assertRaises(svn_dav_log_parse
.BadDepthError
, self
.parse
,
186 'checkout-or-export /foo r9 depth=bork')
187 self
.assertEqual(self
.parse('checkout-or-export /foo r9 depth=files .'),
189 self
.assertEqual(self
.result
, ('/foo', 9, svn
.core
.svn_depth_files
))
191 def test_diff_or_merge_1path(self
):
192 self
.assertRaises(svn_dav_log_parse
.Error
,
193 self
.parse
, 'diff-or-merge')
194 self
.assertEqual(self
.parse('diff-or-merge /foo r9:10'), '')
195 self
.assertEqual(self
.result
, ('/foo', 9, 10,
196 svn
.core
.svn_depth_unknown
, False))
197 self
.assertEqual(self
.parse('diff-or-merge /foo r9:10'
198 ' ignore-ancestry ...'), ' ...')
199 self
.assertEqual(self
.result
, ('/foo', 9, 10,
200 svn
.core
.svn_depth_unknown
, True))
201 self
.assertEqual(self
.parse('diff-or-merge /foo r9:10 depth=files'), '')
202 self
.assertEqual(self
.result
, ('/foo', 9, 10,
203 svn
.core
.svn_depth_files
, False))
205 def test_diff_or_merge_2paths(self
):
206 self
.assertEqual(self
.parse('diff-or-merge /foo@9 /bar@10'), '')
207 self
.assertEqual(self
.result
, ('/foo', 9, '/bar', 10,
208 svn
.core
.svn_depth_unknown
, False))
209 self
.assertEqual(self
.parse('diff-or-merge /foo@9 /bar@10'
210 ' ignore-ancestry ...'), ' ...')
211 self
.assertEqual(self
.result
, ('/foo', 9, '/bar', 10,
212 svn
.core
.svn_depth_unknown
, True))
213 self
.assertEqual(self
.parse('diff-or-merge /foo@9 /bar@10'
214 ' depth=files ignore-ancestry'), '')
215 self
.assertEqual(self
.result
, ('/foo', 9, '/bar', 10,
216 svn
.core
.svn_depth_files
, True))
218 def test_remote_status(self
):
219 self
.assertRaises(svn_dav_log_parse
.Error
,
220 self
.parse
, 'remote-status')
221 self
.assertRaises(svn_dav_log_parse
.Error
,
222 self
.parse
, 'remote-status /foo')
223 self
.assertEqual(self
.parse('remote-status /foo r9'), '')
224 self
.assertEqual(self
.result
, ('/foo', 9, svn
.core
.svn_depth_unknown
))
225 self
.assertRaises(svn_dav_log_parse
.BadDepthError
, self
.parse
,
226 'remote-status /foo r9 depth=INVALID-DEPTH')
227 self
.assertRaises(svn_dav_log_parse
.BadDepthError
, self
.parse
,
228 'remote-status /foo r9 depth=bork')
229 self
.assertEqual(self
.parse('remote-status /foo r9 depth=files .'),
231 self
.assertEqual(self
.result
, ('/foo', 9, svn
.core
.svn_depth_files
))
233 def test_switch(self
):
234 self
.assertEqual(self
.parse('switch /foo@9 /bar@10 ...'), ' ...')
235 self
.assertEqual(self
.result
, ('/foo', 9, '/bar', 10,
236 svn
.core
.svn_depth_unknown
))
237 self
.assertEqual(self
.parse('switch /foo@9 /bar@10'
239 self
.assertEqual(self
.result
, ('/foo', 9, '/bar', 10,
240 svn
.core
.svn_depth_files
))
242 def test_update(self
):
243 self
.assertRaises(svn_dav_log_parse
.Error
,
244 self
.parse
, 'update')
245 self
.assertRaises(svn_dav_log_parse
.Error
,
246 self
.parse
, 'update /foo')
247 self
.assertEqual(self
.parse('update /foo r9'), '')
248 self
.assertEqual(self
.result
, ('/foo', 9, svn
.core
.svn_depth_unknown
,
250 self
.assertRaises(svn_dav_log_parse
.BadDepthError
, self
.parse
,
251 'update /foo r9 depth=INVALID-DEPTH')
252 self
.assertRaises(svn_dav_log_parse
.BadDepthError
, self
.parse
,
253 'update /foo r9 depth=bork')
254 self
.assertEqual(self
.parse('update /foo r9 depth=files .'), ' .')
255 self
.assertEqual(self
.result
, ('/foo', 9, svn
.core
.svn_depth_files
,
257 self
.assertEqual(self
.parse('update /foo r9 send-copyfrom-args .'),
259 self
.assertEqual(self
.result
, ('/foo', 9, svn
.core
.svn_depth_unknown
,
262 if __name__
== '__main__':
263 if len(sys
.argv
) == 1:
264 # No arguments so run the unit tests.
266 sys
.stderr
.write('unittest.main failed to exit\n')
269 # Use the argument as the path to a log file to test against.
271 # Define a class to reconstruct the SVN-ACTION string.
272 class Test(svn_dav_log_parse
.Parser
):
273 def handle_unknown(self
, line
):
274 sys
.stderr
.write('unknown log line at %d\n' % (self
.linenum
,))
277 def handle_commit(self
, revision
):
278 self
.action
= 'commit r%d' % (revision
,)
280 def handle_list_dir(self
, path
, revision
):
281 self
.action
= 'list-dir %s r%d' % (path
, revision
)
283 def handle_lock(self
, path
, steal
):
284 self
.action
= 'lock ' + path
286 self
.action
+= ' steal'
288 def handle_prop_list(self
, path
, revision
):
289 self
.action
= 'prop-list %s@%d' % (path
, revision
)
291 def handle_revprop_change(self
, revision
, revprop
):
292 self
.action
= 'revprop-change r%d %s' % (revision
, revprop
)
294 def handle_revprop_list(self
, revision
):
295 self
.action
= 'revprop-list r%d' % (revision
,)
297 def handle_unlock(self
, path
, break_lock
):
298 self
.action
= 'unlock ' + path
300 self
.action
+= ' break'
304 def handle_blame(self
, path
, left
, right
, include_merged_revisions
):
305 self
.action
= 'blame %s r%d:%d' % (path
, left
, right
)
306 if include_merged_revisions
:
307 self
.action
+= ' include-merged-revisions'
309 def handle_get_mergeinfo(self
, paths
, inheritance
):
310 self
.action
= ('get-mergeinfo (%s) %s'
312 svn
.core
.svn_inheritance_to_word(inheritance
)))
314 def handle_log(self
, paths
, left
, right
, limit
, discover_changed_paths
,
315 strict
, include_merged_revisions
, revprops
):
316 self
.action
= 'log (%s) r%d:%d' % (' '.join(paths
),
319 self
.action
+= ' limit=%d' % (limit
,)
320 if discover_changed_paths
:
321 self
.action
+= ' discover-changed-paths'
323 self
.action
+= ' strict'
324 if include_merged_revisions
:
325 self
.action
+= ' include-merged-revisions'
327 self
.action
+= ' revprops=all'
328 elif len(revprops
) > 0:
329 self
.action
+= ' revprops=(%s)' % (' '.join(revprops
),)
331 def handle_replay(self
, path
, revision
):
332 self
.action
= 'replay %s r%d' % (path
, revision
)
336 def maybe_depth(self
, depth
):
337 if depth
!= svn
.core
.svn_depth_unknown
:
338 self
.action
+= ' depth=%s' % (
339 svn
.core
.svn_depth_to_word(depth
),)
341 def handle_checkout_or_export(self
, path
, revision
, depth
):
342 self
.action
= 'checkout-or-export %s r%d' % (path
, revision
)
343 self
.maybe_depth(depth
)
345 def handle_diff_or_merge_1path(self
, path
, left
, right
,
346 depth
, ignore_ancestry
):
347 self
.action
= 'diff-or-merge %s r%d:%d' % (path
, left
, right
)
348 self
.maybe_depth(depth
)
350 self
.action
+= ' ignore-ancestry'
352 def handle_diff_or_merge_2paths(self
, from_path
, from_rev
,
354 depth
, ignore_ancestry
):
355 self
.action
= ('diff-or-merge %s@%d %s@%d'
356 % (from_path
, from_rev
, to_path
, to_rev
))
357 self
.maybe_depth(depth
)
359 self
.action
+= ' ignore-ancestry'
361 def handle_remote_status(self
, path
, revision
, depth
):
362 self
.action
= 'remote-status %s r%d' % (path
, revision
)
363 self
.maybe_depth(depth
)
365 def handle_switch(self
, from_path
, from_rev
,
366 to_path
, to_rev
, depth
):
367 self
.action
= ('switch %s@%d %s@%d'
368 % (from_path
, from_rev
, to_path
, to_rev
))
369 self
.maybe_depth(depth
)
371 def handle_update(self
, path
, revision
, depth
, send_copyfrom_args
):
372 self
.action
= 'update %s r%d' % (path
, revision
)
373 self
.maybe_depth(depth
)
374 if send_copyfrom_args
:
375 self
.action
+= ' send-copyfrom-args'
377 tmp
= tempfile
.mktemp()
382 for line
in open(sys
.argv
[1]):
384 # Find the SVN-ACTION string from the CustomLog format
385 # davautocheck.sh uses. If that changes, this will need
386 # to as well. Currently it's
387 # %t %u %{SVN-REPOS-NAME}e %{SVN-ACTION}e
389 leading
= ' '.join(words
[:4])
390 action
= ' '.join(words
[4:])
391 # Parse the action and write the reconstructed action to
392 # the temporary file.
393 trailing
= parser
.parse(action
)
394 fp
.write(leading
+ ' ' + parser
.action
+ trailing
+ '\n')
396 # Check differences between original and reconstructed files
397 # (should be identical).
398 sys
.exit(os
.spawnlp(os
.P_WAIT
, 'diff', 'diff', '-u', sys
.argv
[1], tmp
))
403 sys
.stderr
.write('os.unlink(tmp): %s\n' % (e
,))