Merge the svnserve-logging branch, in its entirety, to trunk, using the
[svn.git] / tools / server-side / test_svn_server_log_parse.py
blobc634fda8cfc7f763b277ab8bd5e67e87c4334be5
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 # Run this without arguments to run unit tests.
18 # Run with a path to a davautocheck ops log to test that it can parse that.
20 import os
21 import re
22 import sys
23 import tempfile
24 import unittest
26 import svn.core
28 import svn_server_log_parse
30 class TestCase(unittest.TestCase):
31 def setUp(self):
32 # Define a class to stuff everything passed to any handle_
33 # method into self.result.
34 class cls(svn_server_log_parse.Parser):
35 def __getattr__(cls_self, attr):
36 if attr.startswith('handle_'):
37 return lambda *a: setattr(self, 'result', a)
38 raise AttributeError
39 self.parse = cls().parse
41 def test_unknown(self):
42 line = 'unknown log line'
43 self.parse(line)
44 self.assertEqual(self.result, (line,))
46 def test_reparent(self):
47 self.assertRaises(svn_server_log_parse.Error, self.parse, 'reparent')
48 self.assertEqual(self.parse('reparent /'), '')
49 self.assertEqual(self.result, ('/',))
51 def test_get_latest_rev(self):
52 self.assertEqual(self.parse('get-latest-rev'), '')
53 self.assertEqual(self.result, ())
54 self.assertEqual(self.parse('get-latest-rev r3'), 'r3')
55 self.assertEqual(self.result, ())
57 def test_get_dated_rev(self):
58 self.assertRaises(svn_server_log_parse.Error, self.parse,
59 'get-dated-rev')
60 self.assertEqual(self.parse('get-dated-rev 2008-04-15T20:41:24.000000Z'), '')
61 self.assertEqual(self.result, ('2008-04-15T20:41:24.000000Z',))
63 def test_commit(self):
64 self.assertRaises(svn_server_log_parse.Error, self.parse, 'commit')
65 self.assertRaises(svn_server_log_parse.Error, self.parse, 'commit 3')
66 self.assertEqual(self.parse('commit r3'), '')
67 self.assertEqual(self.result, (3,))
68 self.assertEqual(self.parse('commit r3 leftover'), ' leftover')
69 self.assertEqual(self.result, (3,))
71 def test_get_dir(self):
72 self.get_dir_or_file('get-dir')
74 def test_get_file(self):
75 self.get_dir_or_file('get-file')
77 def get_dir_or_file(self, c):
78 self.assertRaises(svn_server_log_parse.Error, self.parse, c)
79 self.assertRaises(svn_server_log_parse.Error, self.parse, c + ' foo')
80 self.assertRaises(svn_server_log_parse.Error, self.parse, c + ' foo 3')
81 self.assertEqual(self.parse(c + ' /a/b/c r3 ...'), ' ...')
82 self.assertEqual(self.result, ('/a/b/c', 3, False, False))
83 self.assertEqual(self.parse(c + ' / r3'), '')
84 self.assertEqual(self.result, ('/', 3, False, False))
85 # path must be absolute
86 self.assertRaises(svn_server_log_parse.Error,
87 self.parse, c + ' a/b/c r3')
88 self.assertEqual(self.parse(c + ' /k r27 text'), '')
89 self.assertEqual(self.result, ('/k', 27, True, False))
90 self.assertEqual(self.parse(c + ' /k r27 props'), '')
91 self.assertEqual(self.result, ('/k', 27, False, True))
92 self.assertEqual(self.parse(c + ' /k r27 text props'), '')
93 self.assertEqual(self.result, ('/k', 27, True, True))
94 # out of order not accepted
95 self.assertEqual(self.parse(c + ' /k r27 props text'), ' text')
96 self.assertEqual(self.result, ('/k', 27, False, True))
98 def test_lock(self):
99 self.assertRaises(svn_server_log_parse.Error, self.parse, 'lock')
100 self.parse('lock (/foo)')
101 self.assertEqual(self.result, (['/foo'], False))
102 self.assertEqual(self.parse('lock (/foo) steal ...'), ' ...')
103 self.assertEqual(self.result, (['/foo'], True))
104 self.assertEqual(self.parse('lock (/foo) stear'), ' stear')
106 def test_change_rev_prop(self):
107 self.assertRaises(svn_server_log_parse.Error,
108 self.parse, 'change-rev-prop r3')
109 self.assertRaises(svn_server_log_parse.Error,
110 self.parse, 'change-rev-prop r svn:log')
111 self.assertRaises(svn_server_log_parse.Error,
112 self.parse, 'change-rev-prop rX svn:log')
113 self.assertEqual(self.parse('change-rev-prop r3 svn:log ...'), ' ...')
114 self.assertEqual(self.result, (3, 'svn:log'))
116 def test_rev_proplist(self):
117 self.assertRaises(svn_server_log_parse.Error,
118 self.parse, 'rev-proplist')
119 self.assertRaises(svn_server_log_parse.Error,
120 self.parse, 'rev-proplist r')
121 self.assertRaises(svn_server_log_parse.Error,
122 self.parse, 'rev-proplist rX')
123 self.assertEqual(self.parse('rev-proplist r3 ...'), ' ...')
124 self.assertEqual(self.result, (3,))
126 def test_rev_prop(self):
127 self.assertRaises(svn_server_log_parse.Error, self.parse, 'rev-prop')
128 self.assertRaises(svn_server_log_parse.Error, self.parse, 'rev-prop r')
129 self.assertRaises(svn_server_log_parse.Error, self.parse, 'rev-prop rX')
130 self.assertEqual(self.parse('rev-prop r3 foo ...'), ' ...')
131 self.assertEqual(self.result, (3, 'foo'))
133 def test_unlock(self):
134 self.assertRaises(svn_server_log_parse.Error, self.parse, 'unlock')
135 self.parse('unlock (/foo)')
136 self.assertEqual(self.result, (['/foo'], False))
137 self.assertEqual(self.parse('unlock (/foo) break ...'), ' ...')
138 self.assertEqual(self.result, (['/foo'], True))
139 self.assertEqual(self.parse('unlock (/foo) bear'), ' bear')
141 def test_get_lock(self):
142 self.assertRaises(svn_server_log_parse.Error, self.parse, 'get-lock')
143 self.parse('get-lock /foo')
144 self.assertEqual(self.result, ('/foo',))
146 def test_get_locks(self):
147 self.assertRaises(svn_server_log_parse.Error, self.parse, 'get-locks')
148 self.parse('get-locks /foo')
149 self.assertEqual(self.result, ('/foo',))
151 def test_get_locations(self):
152 self.assertRaises(svn_server_log_parse.Error, self.parse,
153 'get-locations')
154 self.assertRaises(svn_server_log_parse.Error,
155 self.parse, 'get-locations /foo 3')
156 self.assertEqual(self.parse('get-locations /foo (3 4) ...'), ' ...')
157 self.assertEqual(self.result, ('/foo', [3, 4]))
158 self.assertEqual(self.parse('get-locations /foo (3)'), '')
159 self.assertEqual(self.result, ('/foo', [3]))
161 def test_get_location_segments(self):
162 self.assertRaises(svn_server_log_parse.Error, self.parse,
163 'get-location-segments')
164 self.assertRaises(svn_server_log_parse.Error,
165 self.parse, 'get-location-segments /foo 3')
166 self.assertEqual(self.parse('get-location-segments /foo@2 r3:4'), '')
167 self.assertEqual(self.result, ('/foo', 2, 3, 4))
169 def test_get_file_revs(self):
170 self.assertRaises(svn_server_log_parse.Error, self.parse, 'get-file-revs')
171 self.assertRaises(svn_server_log_parse.Error,
172 self.parse, 'get-file-revs /foo 3')
173 self.assertRaises(svn_server_log_parse.Error,
174 self.parse, 'get-file-revs /foo 3:a')
175 self.assertRaises(svn_server_log_parse.Error,
176 self.parse, 'get-file-revs /foo r3:a')
177 self.assertEqual(self.parse('get-file-revs /foo r3:4 ...'), ' ...')
178 self.assertEqual(self.result, ('/foo', 3, 4, False))
179 self.assertEqual(self.parse('get-file-revs /foo r3:4'
180 ' include-merged-revisions ...'), ' ...')
181 self.assertEqual(self.result, ('/foo', 3, 4, True))
183 def test_get_mergeinfo(self):
184 self.assertRaises(svn_server_log_parse.Error,
185 self.parse, 'get-mergeinfo')
186 self.assertRaises(svn_server_log_parse.Error,
187 self.parse, 'get-mergeinfo /foo')
188 self.assertRaises(svn_server_log_parse.Error,
189 self.parse, 'get-mergeinfo (/foo')
190 self.assertRaises(svn_server_log_parse.Error,
191 self.parse, 'get-mergeinfo (/foo /bar')
192 self.assertRaises(svn_server_log_parse.Error,
193 self.parse, 'get-mergeinfo (/foo)')
194 self.assertRaises(svn_server_log_parse.BadMergeinfoInheritanceError,
195 self.parse, 'get-mergeinfo (/foo) bork')
196 self.assertEqual(self.parse('get-mergeinfo (/foo) explicit'), '')
197 self.assertEqual(self.result, (['/foo'],
198 svn.core.svn_mergeinfo_explicit, False))
199 self.assertEqual(self.parse('get-mergeinfo (/foo /bar) inherited ...'),
200 ' ...')
201 self.assertEqual(self.result, (['/foo', '/bar'],
202 svn.core.svn_mergeinfo_inherited, False))
203 self.assertEqual(self.result, (['/foo', '/bar'],
204 svn.core.svn_mergeinfo_inherited, False))
206 def test_log(self):
207 self.assertRaises(svn_server_log_parse.Error, self.parse, 'log')
208 self.assertRaises(svn_server_log_parse.Error,
209 self.parse, 'log /foo')
210 self.assertRaises(svn_server_log_parse.Error,
211 self.parse, 'log (/foo)')
212 self.assertEqual(self.parse('log (/foo) r3:4'
213 ' include-merged-revisions'), '')
214 self.assertEqual(self.result,
215 (['/foo'], 3, 4, 0, False, False, True, []))
216 self.assertEqual(self.parse('log (/foo /bar) r3:4 revprops=all ...'),
217 ' ...')
218 self.assertEqual(self.result,
219 (['/foo', '/bar'], 3, 4, 0, False, False, False, None))
220 self.assertEqual(self.parse('log (/foo) r3:4 revprops=(a b) ...'),
221 ' ...')
222 self.assertEqual(self.result,
223 (['/foo'], 3, 4, 0, False, False, False, ['a', 'b']))
224 self.assertEqual(self.parse('log (/foo) r8:1 limit=3'), '')
225 self.assertEqual(self.result,
226 (['/foo'], 8, 1, 3, False, False, False, []))
228 def test_check_path(self):
229 self.assertRaises(svn_server_log_parse.Error, self.parse, 'check-path')
230 self.assertEqual(self.parse('check-path /foo@9'), '')
231 self.assertEqual(self.result, ('/foo', 9))
233 def test_stat(self):
234 self.assertRaises(svn_server_log_parse.Error, self.parse, 'stat')
235 self.assertEqual(self.parse('stat /foo@9'), '')
236 self.assertEqual(self.result, ('/foo', 9))
238 def test_replay(self):
239 self.assertRaises(svn_server_log_parse.Error, self.parse, 'replay')
240 self.assertRaises(svn_server_log_parse.Error,
241 self.parse, 'replay /foo')
242 self.assertRaises(svn_server_log_parse.Error,
243 self.parse, 'replay (/foo) r9')
244 self.assertRaises(svn_server_log_parse.Error,
245 self.parse, 'replay (/foo) r9:10')
246 self.assertEqual(self.parse('replay /foo r9'), '')
247 self.assertEqual(self.result, ('/foo', 9))
249 def test_checkout_or_export(self):
250 self.assertRaises(svn_server_log_parse.Error,
251 self.parse, 'checkout-or-export')
252 self.assertRaises(svn_server_log_parse.Error,
253 self.parse, 'checkout-or-export /foo')
254 self.assertEqual(self.parse('checkout-or-export /foo r9'), '')
255 self.assertEqual(self.result, ('/foo', 9, svn.core.svn_depth_unknown))
256 self.assertRaises(svn_server_log_parse.BadDepthError, self.parse,
257 'checkout-or-export /foo r9 depth=INVALID-DEPTH')
258 self.assertRaises(svn_server_log_parse.BadDepthError, self.parse,
259 'checkout-or-export /foo r9 depth=bork')
260 self.assertEqual(self.parse('checkout-or-export /foo r9 depth=files .'),
261 ' .')
262 self.assertEqual(self.result, ('/foo', 9, svn.core.svn_depth_files))
264 def test_diff_1path(self):
265 self.assertRaises(svn_server_log_parse.Error,
266 self.parse, 'diff')
267 self.assertEqual(self.parse('diff /foo r9:10'), '')
268 self.assertEqual(self.result, ('/foo', 9, 10,
269 svn.core.svn_depth_unknown, False))
270 self.assertEqual(self.parse('diff /foo r9:10'
271 ' ignore-ancestry ...'), ' ...')
272 self.assertEqual(self.result, ('/foo', 9, 10,
273 svn.core.svn_depth_unknown, True))
274 self.assertEqual(self.parse('diff /foo r9:10 depth=files'), '')
275 self.assertEqual(self.result, ('/foo', 9, 10,
276 svn.core.svn_depth_files, False))
278 def test_diff_2paths(self):
279 self.assertEqual(self.parse('diff /foo@9 /bar@10'), '')
280 self.assertEqual(self.result, ('/foo', 9, '/bar', 10,
281 svn.core.svn_depth_unknown, False))
282 self.assertEqual(self.parse('diff /foo@9 /bar@10'
283 ' ignore-ancestry ...'), ' ...')
284 self.assertEqual(self.result, ('/foo', 9, '/bar', 10,
285 svn.core.svn_depth_unknown, True))
286 self.assertEqual(self.parse('diff /foo@9 /bar@10'
287 ' depth=files ignore-ancestry'), '')
288 self.assertEqual(self.result, ('/foo', 9, '/bar', 10,
289 svn.core.svn_depth_files, True))
291 def test_status(self):
292 self.assertRaises(svn_server_log_parse.Error,
293 self.parse, 'status')
294 self.assertRaises(svn_server_log_parse.Error,
295 self.parse, 'status /foo')
296 self.assertEqual(self.parse('status /foo r9'), '')
297 self.assertEqual(self.result, ('/foo', 9, svn.core.svn_depth_unknown))
298 self.assertRaises(svn_server_log_parse.BadDepthError, self.parse,
299 'status /foo r9 depth=INVALID-DEPTH')
300 self.assertRaises(svn_server_log_parse.BadDepthError, self.parse,
301 'status /foo r9 depth=bork')
302 self.assertEqual(self.parse('status /foo r9 depth=files .'),
303 ' .')
304 self.assertEqual(self.result, ('/foo', 9, svn.core.svn_depth_files))
306 def test_switch(self):
307 self.assertEqual(self.parse('switch /foo /bar@10 ...'), ' ...')
308 self.assertEqual(self.result, ('/foo', '/bar', 10,
309 svn.core.svn_depth_unknown))
310 self.assertEqual(self.parse('switch /foo /bar@10'
311 ' depth=files'), '')
312 self.assertEqual(self.result, ('/foo', '/bar', 10,
313 svn.core.svn_depth_files))
315 def test_update(self):
316 self.assertRaises(svn_server_log_parse.Error,
317 self.parse, 'update')
318 self.assertRaises(svn_server_log_parse.Error,
319 self.parse, 'update /foo')
320 self.assertEqual(self.parse('update /foo r9'), '')
321 self.assertEqual(self.result, ('/foo', 9, svn.core.svn_depth_unknown,
322 False))
323 self.assertRaises(svn_server_log_parse.BadDepthError, self.parse,
324 'update /foo r9 depth=INVALID-DEPTH')
325 self.assertRaises(svn_server_log_parse.BadDepthError, self.parse,
326 'update /foo r9 depth=bork')
327 self.assertEqual(self.parse('update /foo r9 depth=files .'), ' .')
328 self.assertEqual(self.result, ('/foo', 9, svn.core.svn_depth_files,
329 False))
330 self.assertEqual(self.parse('update /foo r9 send-copyfrom-args .'),
331 ' .')
332 self.assertEqual(self.result, ('/foo', 9, svn.core.svn_depth_unknown,
333 True))
335 if __name__ == '__main__':
336 if len(sys.argv) == 1:
337 # No arguments so run the unit tests.
338 unittest.main()
339 sys.stderr.write('unittest.main failed to exit\n')
340 sys.exit(2)
342 # Use the argument as the path to a log file to test against.
344 # Define a class to reconstruct the SVN-ACTION string.
345 class Test(svn_server_log_parse.Parser):
346 def handle_unknown(self, line):
347 sys.stderr.write('unknown log line at %d:\n%s\n' % (self.linenum,
348 line))
349 sys.exit(2)
351 def handle_reparent(self, path):
352 self.action = 'reparent ' + path
354 def handle_get_latest_rev(self):
355 self.action = 'get-latest-rev'
357 def handle_get_dated_rev(self, date):
358 self.action = 'get-dated-rev ' + date
360 def handle_commit(self, revision):
361 self.action = 'commit r%d' % (revision,)
363 def handle_get_dir(self, path, revision, text, props):
364 self.action = 'get-dir %s r%d' % (path, revision)
365 if text:
366 self.action += ' text'
367 if props:
368 self.action += ' props'
370 def handle_get_file(self, path, revision, text, props):
371 self.action = 'get-file %s r%d' % (path, revision)
372 if text:
373 self.action += ' text'
374 if props:
375 self.action += ' props'
377 def handle_lock(self, paths, steal):
378 self.action = 'lock (%s)' % (' '.join(paths),)
379 if steal:
380 self.action += ' steal'
382 def handle_change_rev_prop(self, revision, revprop):
383 self.action = 'change-rev-prop r%d %s' % (revision, revprop)
385 def handle_rev_prop(self, revision, revprop):
386 self.action = 'rev-prop r%d %s' % (revision, revprop)
388 def handle_rev_proplist(self, revision):
389 self.action = 'rev-proplist r%d' % (revision,)
391 def handle_unlock(self, paths, break_lock):
392 self.action = 'unlock (%s)' % (' '.join(paths),)
393 if break_lock:
394 self.action += ' break'
396 def handle_get_lock(self, path):
397 self.action = 'get-lock ' + path
399 def handle_get_locks(self, path):
400 self.action = 'get-locks ' + path
402 def handle_get_locations(self, path, revisions):
403 self.action = ('get-locations %s (%s)'
404 % (path, ' '.join([str(x) for x in revisions])))
406 def handle_get_location_segments(self, path, peg, left, right):
407 self.action = 'get-location-segments %s@%d r%d:%d' % (path, peg,
408 left, right)
410 def handle_get_file_revs(self, path, left, right, include_merged_revisions):
411 self.action = 'get-file-revs %s r%d:%d' % (path, left, right)
412 if include_merged_revisions:
413 self.action += ' include-merged-revisions'
415 def handle_get_mergeinfo(self, paths, inheritance, include_descendants):
416 self.action = ('get-mergeinfo (%s) %s'
417 % (' '.join(paths),
418 svn.core.svn_inheritance_to_word(inheritance)))
419 if include_descendants:
420 self.action += ' include-descendants'
422 def handle_log(self, paths, left, right, limit, discover_changed_paths,
423 strict, include_merged_revisions, revprops):
424 self.action = 'log (%s) r%d:%d' % (' '.join(paths),
425 left, right)
426 if limit != 0:
427 self.action += ' limit=%d' % (limit,)
428 if discover_changed_paths:
429 self.action += ' discover-changed-paths'
430 if strict:
431 self.action += ' strict'
432 if include_merged_revisions:
433 self.action += ' include-merged-revisions'
434 if revprops is None:
435 self.action += ' revprops=all'
436 elif len(revprops) > 0:
437 self.action += ' revprops=(%s)' % (' '.join(revprops),)
439 def handle_check_path(self, path, revision):
440 self.action = 'check-path %s@%d' % (path, revision)
442 def handle_stat(self, path, revision):
443 self.action = 'stat %s@%d' % (path, revision)
445 def handle_replay(self, path, revision):
446 self.action = 'replay %s r%d' % (path, revision)
448 def maybe_depth(self, depth):
449 if depth != svn.core.svn_depth_unknown:
450 self.action += ' depth=%s' % (
451 svn.core.svn_depth_to_word(depth),)
453 def handle_checkout_or_export(self, path, revision, depth):
454 self.action = 'checkout-or-export %s r%d' % (path, revision)
455 self.maybe_depth(depth)
457 def handle_diff_1path(self, path, left, right,
458 depth, ignore_ancestry):
459 self.action = 'diff %s r%d:%d' % (path, left, right)
460 self.maybe_depth(depth)
461 if ignore_ancestry:
462 self.action += ' ignore-ancestry'
464 def handle_diff_2paths(self, from_path, from_rev,
465 to_path, to_rev,
466 depth, ignore_ancestry):
467 self.action = ('diff %s@%d %s@%d'
468 % (from_path, from_rev, to_path, to_rev))
469 self.maybe_depth(depth)
470 if ignore_ancestry:
471 self.action += ' ignore-ancestry'
473 def handle_status(self, path, revision, depth):
474 self.action = 'status %s r%d' % (path, revision)
475 self.maybe_depth(depth)
477 def handle_switch(self, from_path, to_path, to_rev, depth):
478 self.action = ('switch %s %s@%d'
479 % (from_path, to_path, to_rev))
480 self.maybe_depth(depth)
482 def handle_update(self, path, revision, depth, send_copyfrom_args):
483 self.action = 'update %s r%d' % (path, revision)
484 self.maybe_depth(depth)
485 if send_copyfrom_args:
486 self.action += ' send-copyfrom-args'
488 tmp = tempfile.mktemp()
489 try:
490 fp = open(tmp, 'w')
491 parser = Test()
492 parser.linenum = 0
493 log_file = sys.argv[1]
494 log_type = None
495 for line in open(log_file):
496 if log_type is None:
497 # Figure out which log type we have.
498 if re.match(r'\d+ \d\d\d\d-', line):
499 log_type = 'svnserve'
500 elif re.match(r'\[\d\d/', line):
501 log_type = 'mod_dav_svn'
502 else:
503 sys.stderr.write("unknown log format in '%s'"
504 % (log_file,))
505 sys.exit(3)
506 sys.stderr.write('parsing %s log...\n' % (log_type,))
507 sys.stderr.flush()
509 words = line.split()
510 if log_type == 'svnserve':
511 # Skip over PID, date, client address, username, and repos.
512 if words[5].startswith('ERR'):
513 # Skip error lines.
514 fp.write(line)
515 continue
516 leading = ' '.join(words[:5])
517 action = ' '.join(words[5:])
518 else:
519 # Find the SVN-ACTION string from the CustomLog format
520 # davautocheck.sh uses. If that changes, this will need
521 # to as well. Currently it's
522 # %t %u %{SVN-REPOS-NAME}e %{SVN-ACTION}e
523 leading = ' '.join(words[:4])
524 action = ' '.join(words[4:])
526 # Parse the action and write the reconstructed action to
527 # the temporary file. Ignore the returned trailing text,
528 # as we have none in the davautocheck ops log.
529 parser.linenum += 1
530 try:
531 parser.parse(action)
532 except svn_server_log_parse.Error:
533 sys.stderr.write('error at line %d: %s\n'
534 % (parser.linenum, action))
535 raise
536 fp.write(leading + ' ' + parser.action + '\n')
537 fp.close()
538 # Check differences between original and reconstructed files
539 # (should be identical).
540 result = os.spawnlp(os.P_WAIT, 'diff', 'diff', '-u', log_file, tmp)
541 if result == 0:
542 sys.stderr.write('OK\n')
543 sys.exit(result)
544 finally:
545 try:
546 os.unlink(tmp)
547 except Exception, e:
548 sys.stderr.write('os.unlink(tmp): %s\n' % (e,))