Follow-up to r29036: Now that the "mergeinfo" transaction file is no
[svn.git] / tools / server-side / test_svn_dav_log_parse.py
blobd9364360c2dbcc8714b90de6c5c33c621f525840
1 #!/usr/bin/python
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 # ====================================================================
17 import os
18 import sys
19 import tempfile
20 import unittest
22 import svn.core
24 import svn_dav_log_parse
26 class TestCase(unittest.TestCase):
27 def setUp(self):
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)
34 raise AttributeError
35 self.parse = cls().parse
37 def test_unknown(self):
38 line = 'unknown log line'
39 self.parse(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')
62 def test_lock(self):
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 ...'),
139 ' ...')
140 self.assertEqual(self.result, (['/foo', '/bar'],
141 svn.core.svn_mergeinfo_inherited))
143 def test_log(self):
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 ...'),
154 ' ...')
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) ...'),
158 ' ...')
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 .'),
188 ' .')
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 .'),
230 ' .')
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'
238 ' depth=files'), '')
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,
249 False))
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,
256 False))
257 self.assertEqual(self.parse('update /foo r9 send-copyfrom-args .'),
258 ' .')
259 self.assertEqual(self.result, ('/foo', 9, svn.core.svn_depth_unknown,
260 True))
262 if __name__ == '__main__':
263 if len(sys.argv) == 1:
264 # No arguments so run the unit tests.
265 unittest.main()
266 sys.stderr.write('unittest.main failed to exit\n')
267 sys.exit(2)
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,))
275 sys.exit(2)
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
285 if steal:
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
299 if break_lock:
300 self.action += ' break'
302 # reports
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'
311 % (' '.join(paths),
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),
317 left, right)
318 if limit != 0:
319 self.action += ' limit=%d' % (limit,)
320 if discover_changed_paths:
321 self.action += ' discover-changed-paths'
322 if strict:
323 self.action += ' strict'
324 if include_merged_revisions:
325 self.action += ' include-merged-revisions'
326 if revprops is None:
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)
334 # the update report
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)
349 if ignore_ancestry:
350 self.action += ' ignore-ancestry'
352 def handle_diff_or_merge_2paths(self, from_path, from_rev,
353 to_path, to_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)
358 if ignore_ancestry:
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()
378 try:
379 fp = open(tmp, 'w')
380 parser = Test()
381 parser.linenum = 0
382 for line in open(sys.argv[1]):
383 parser.linenum += 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
388 words = line.split()
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')
395 fp.close()
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))
399 finally:
400 try:
401 os.unlink(tmp)
402 except Exception, e:
403 sys.stderr.write('os.unlink(tmp): %s\n' % (e,))