1#!/usr/bin/env python 2# 3# svnadmin_tests.py: testing the 'svnadmin' tool. 4# 5# Subversion is a tool for revision control. 6# See http://subversion.apache.org for more information. 7# 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 16# 17# http://www.apache.org/licenses/LICENSE-2.0 18# 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 24# under the License. 25###################################################################### 26 27# General modules 28import os 29import logging 30import re 31import shutil 32import sys 33import threading 34import time 35import gzip 36 37logger = logging.getLogger() 38 39# Our testing module 40import svntest 41from svntest.verify import SVNExpectedStdout, SVNExpectedStderr 42from svntest.verify import SVNUnexpectedStderr 43from svntest.verify import UnorderedOutput 44from svntest.main import SVN_PROP_MERGEINFO 45 46# (abbreviation) 47Skip = svntest.testcase.Skip_deco 48SkipUnless = svntest.testcase.SkipUnless_deco 49XFail = svntest.testcase.XFail_deco 50Issues = svntest.testcase.Issues_deco 51Issue = svntest.testcase.Issue_deco 52Wimp = svntest.testcase.Wimp_deco 53SkipDumpLoadCrossCheck = svntest.testcase.SkipDumpLoadCrossCheck_deco 54Item = svntest.wc.StateItem 55 56def read_rep_cache(repo_dir): 57 """Return the rep-cache contents as a dict {hash: (rev, index, ...)}. 58 """ 59 db_path = os.path.join(repo_dir, 'db', 'rep-cache.db') 60 db1 = svntest.sqlite3.connect(db_path) 61 schema1 = db1.execute("pragma user_version").fetchone()[0] 62 # Can't test newer rep-cache schemas with an old built-in SQLite; see the 63 # documentation of STMT_CREATE_SCHEMA_V2 in ../../libsvn_fs_fs/rep-cache-db.sql 64 if schema1 >= 2 and svntest.sqlite3.sqlite_version_info < (3, 8, 2): 65 raise svntest.Failure("Can't read rep-cache schema %d using old " 66 "Python-SQLite version %s < (3,8,2)" % 67 (schema1, 68 svntest.sqlite3.sqlite_version_info)) 69 70 content = { row[0]: row[1:] for row in 71 db1.execute("select * from rep_cache") } 72 return content 73 74def check_hotcopy_bdb(src, dst): 75 "Verify that the SRC BDB repository has been correctly copied to DST." 76 ### TODO: This function should be extended to verify all hotcopied files, 77 ### not just compare the output of 'svnadmin dump'. See check_hotcopy_fsfs(). 78 exit_code, origout, origerr = svntest.main.run_svnadmin("dump", src, 79 '--quiet') 80 exit_code, backout, backerr = svntest.main.run_svnadmin("dump", dst, 81 '--quiet') 82 if origerr or backerr or origout != backout: 83 raise svntest.Failure 84 85def check_hotcopy_fsfs_fsx(src, dst): 86 # Walk the source and compare all files to the destination 87 for src_dirpath, src_dirs, src_files in os.walk(src): 88 # Verify that the current directory exists in the destination 89 dst_dirpath = src_dirpath.replace(src, dst) 90 if not os.path.isdir(dst_dirpath): 91 raise svntest.Failure("%s does not exist in hotcopy " 92 "destination" % dst_dirpath) 93 # Verify that all dirents in the current directory also exist in source 94 for dst_dirent in os.listdir(dst_dirpath): 95 # Ignore auto-created empty lock files as they may or may not 96 # be present and are neither required by nor do they harm to 97 # the destination repository. 98 if dst_dirent == 'pack-lock': 99 continue 100 if dst_dirent == 'write-lock': 101 continue 102 103 # Ignore auto-created rep-cache.db-journal file 104 if dst_dirent == 'rep-cache.db-journal': 105 continue 106 107 src_dirent = os.path.join(src_dirpath, dst_dirent) 108 if not os.path.exists(src_dirent): 109 raise svntest.Failure("%s does not exist in hotcopy " 110 "source" % src_dirent) 111 # Compare all files in this directory 112 for src_file in src_files: 113 # Ignore auto-created empty lock files as they may or may not 114 # be present and are neither required by nor do they harm to 115 # the destination repository. 116 if src_file == 'pack-lock': 117 continue 118 if src_file == 'write-lock': 119 continue 120 121 # Ignore auto-created rep-cache.db-journal file 122 if src_file == 'rep-cache.db-journal': 123 continue 124 125 src_path = os.path.join(src_dirpath, src_file) 126 dst_path = os.path.join(dst_dirpath, src_file) 127 if not os.path.isfile(dst_path): 128 raise svntest.Failure("%s does not exist in hotcopy " 129 "destination" % dst_path) 130 131 # Special case for db/uuid: Only the UUID in the first line needs 132 # to match. Source and target must have the same number of lines 133 # (due to having the same format). 134 if src_path == os.path.join(src, 'db', 'uuid'): 135 lines1 = open(src_path, 'rb').read().split(b"\n") 136 lines2 = open(dst_path, 'rb').read().split(b"\n") 137 if len(lines1) != len(lines2): 138 raise svntest.Failure("%s differs in number of lines" 139 % dst_path) 140 if lines1[0] != lines2[0]: 141 raise svntest.Failure("%s contains different uuid: '%s' vs. '%s'" 142 % (dst_path, lines1[0], lines2[0])) 143 continue 144 145 # Special case for rep-cache: It will always differ in a byte-by-byte 146 # comparison, so compare db tables instead. 147 if src_file == 'rep-cache.db': 148 db1 = svntest.sqlite3.connect(src_path) 149 db2 = svntest.sqlite3.connect(dst_path) 150 schema1 = db1.execute("pragma user_version").fetchone()[0] 151 schema2 = db2.execute("pragma user_version").fetchone()[0] 152 if schema1 != schema2: 153 raise svntest.Failure("rep-cache schema differs: '%s' vs. '%s'" 154 % (schema1, schema2)) 155 # Can't test newer rep-cache schemas with an old built-in SQLite. 156 if schema1 >= 2 and svntest.sqlite3.sqlite_version_info < (3, 8, 2): 157 continue 158 159 rows1 = [] 160 rows2 = [] 161 for row in db1.execute("select * from rep_cache order by hash"): 162 rows1.append(row) 163 for row in db2.execute("select * from rep_cache order by hash"): 164 rows2.append(row) 165 if len(rows1) != len(rows2): 166 raise svntest.Failure("number of rows in rep-cache differs") 167 for i in range(len(rows1)): 168 if rows1[i] != rows2[i]: 169 raise svntest.Failure("rep-cache row %i differs: '%s' vs. '%s'" 170 % (i, rows1[i], rows2[i])) 171 continue 172 173 # Special case for revprop-generation: It will always be zero in 174 # the hotcopy destination (i.e. a fresh cache generation) 175 if src_file == 'revprop-generation': 176 f2 = open(dst_path, 'r') 177 revprop_gen = int(f2.read().strip()) 178 if revprop_gen != 0: 179 raise svntest.Failure("Hotcopy destination has non-zero " + 180 "revprop generation") 181 continue 182 183 f1 = open(src_path, 'rb') 184 f2 = open(dst_path, 'rb') 185 while True: 186 offset = 0 187 BUFSIZE = 1024 188 buf1 = f1.read(BUFSIZE) 189 buf2 = f2.read(BUFSIZE) 190 if not buf1 or not buf2: 191 if not buf1 and not buf2: 192 # both at EOF 193 break 194 elif buf1: 195 raise svntest.Failure("%s differs at offset %i" % 196 (dst_path, offset)) 197 elif buf2: 198 raise svntest.Failure("%s differs at offset %i" % 199 (dst_path, offset)) 200 if len(buf1) != len(buf2): 201 raise svntest.Failure("%s differs in length" % dst_path) 202 for i in range(len(buf1)): 203 if buf1[i] != buf2[i]: 204 raise svntest.Failure("%s differs at offset %i" 205 % (dst_path, offset)) 206 offset += 1 207 f1.close() 208 f2.close() 209 210def check_hotcopy_fsfs(src, dst): 211 "Verify that the SRC FSFS repository has been correctly copied to DST." 212 check_hotcopy_fsfs_fsx(src, dst) 213 214def check_hotcopy_fsx(src, dst): 215 "Verify that the SRC FSX repository has been correctly copied to DST." 216 check_hotcopy_fsfs_fsx(src, dst) 217 218#---------------------------------------------------------------------- 219 220# How we currently test 'svnadmin' -- 221# 222# 'svnadmin create': Create an empty repository, test that the 223# root node has a proper created-revision, 224# because there was once a bug where it 225# didn't. 226# 227# Note also that "svnadmin create" is tested 228# implicitly every time we run a python test 229# script. (An empty repository is always 230# created and then imported into; if this 231# subcommand failed catastrophically, every 232# test would fail and we would know instantly.) 233# 234# 'svnadmin createtxn' 235# 'svnadmin rmtxn': See below. 236# 237# 'svnadmin lstxns': We don't care about the contents of transactions; 238# we only care that they exist or not. 239# Therefore, we can simply parse transaction headers. 240# 241# 'svnadmin dump': A couple regression tests that ensure dump doesn't 242# error out, and one to check that the --quiet option 243# really does what it's meant to do. The actual 244# contents of the dump aren't verified at all. 245# 246# ### TODO: someday maybe we could parse the contents of trees too. 247# 248###################################################################### 249# Helper routines 250 251 252def get_txns(repo_dir): 253 "Get the txn names using 'svnadmin lstxns'." 254 255 exit_code, output_lines, error_lines = svntest.main.run_svnadmin('lstxns', 256 repo_dir) 257 txns = sorted([output_lines.strip(x) for x in output_lines]) 258 259 return txns 260 261def patch_format(repo_dir, shard_size): 262 """Rewrite the format of the FSFS or FSX repository REPO_DIR so 263 that it would use sharding with SHARDS revisions per shard.""" 264 265 format_path = os.path.join(repo_dir, "db", "format") 266 contents = open(format_path, 'rb').read() 267 processed_lines = [] 268 269 for line in contents.split(b"\n"): 270 if line.startswith(b"layout "): 271 processed_lines.append(("layout sharded %d" % shard_size).encode()) 272 else: 273 processed_lines.append(line) 274 275 new_contents = b"\n".join(processed_lines) 276 os.chmod(format_path, svntest.main.S_ALL_RW) 277 with open(format_path, 'wb') as f: 278 f.write(new_contents) 279 280def is_sharded(repo_dir): 281 """Return whether the FSFS repository REPO_DIR is sharded.""" 282 283 format_path = os.path.join(repo_dir, "db", "format") 284 contents = open(format_path, 'rb').read() 285 286 for line in contents.split(b"\n"): 287 if line.startswith(b"layout sharded"): 288 return True 289 290 return False 291 292def load_and_verify_dumpstream(sbox, expected_stdout, expected_stderr, 293 revs, check_props, dump, *varargs): 294 """Load the array of lines passed in DUMP into the current tests' 295 repository and verify the repository content using the array of 296 wc.States passed in REVS. If CHECK_PROPS is True, check properties 297 of each rev's items. VARARGS are optional arguments passed to the 298 'load' command.""" 299 300 dump = svntest.main.ensure_list(dump) 301 302 exit_code, output, errput = svntest.main.run_command_stdin( 303 svntest.main.svnadmin_binary, expected_stderr, 0, True, dump, 304 'load', '--quiet', sbox.repo_dir, *varargs) 305 306 if expected_stdout: 307 if expected_stdout is svntest.verify.AnyOutput: 308 if len(output) == 0: 309 raise SVNExpectedStdout 310 else: 311 svntest.verify.compare_and_display_lines( 312 "Standard output", "STDOUT:", expected_stdout, output) 313 314 if expected_stderr: 315 if expected_stderr is svntest.verify.AnyOutput: 316 if len(errput) == 0: 317 raise SVNExpectedStderr 318 else: 319 svntest.verify.compare_and_display_lines( 320 "Standard error output", "STDERR:", expected_stderr, errput) 321 # The expected error occurred, so don't try to verify the result 322 return 323 324 if revs: 325 # verify revs as wc states 326 for rev in range(len(revs)): 327 svntest.actions.run_and_verify_svn(svntest.verify.AnyOutput, [], 328 "update", "-r%s" % (rev+1), 329 sbox.wc_dir) 330 331 rev_tree = revs[rev] 332 svntest.actions.verify_disk(sbox.wc_dir, rev_tree, check_props) 333 334def load_dumpstream(sbox, dump, *varargs): 335 "Load dump text without verification." 336 return load_and_verify_dumpstream(sbox, None, None, None, False, dump, 337 *varargs) 338 339class FSFS_Index: 340 """Manages indexes of a rev file in a FSFS format 7 repository. 341 The interface returns P2L information and allows for item offsets 342 and lengths to be modified. """ 343 344 def __init__(self, sbox, revision): 345 self.by_item = { } 346 self.revision = revision 347 self.repo_dir = sbox.repo_dir 348 349 self._read() 350 351 def _read(self): 352 """ Read P2L index using svnfsfs. """ 353 exit_code, output, errput = svntest.main.run_svnfsfs('dump-index', 354 '-r' + str(self.revision), 355 self.repo_dir) 356 svntest.verify.verify_outputs("Error while dumping index", 357 [], errput, [], []) 358 svntest.verify.verify_exit_code(None, exit_code, 0) 359 360 self.by_item.clear() 361 for line in output: 362 values = line.split() 363 if len(values) >= 4 and values[0] != 'Start': 364 item = int(values[4]) 365 self.by_item[item] = values 366 367 def _write(self): 368 """ Rewrite indexes using svnfsfs. """ 369 by_offset = {} 370 for key in self.by_item: 371 values = self.by_item[key] 372 by_offset[int(values[0], 16)] = values 373 374 lines = [] 375 for (offset, values) in sorted(by_offset.items()): 376 values = by_offset[offset] 377 line = values[0] + ' ' + values[1] + ' ' + values[2] + ' ' + \ 378 values[3] + ' ' + values[4] + '\n'; 379 lines.append(line.encode()) 380 381 exit_code, output, errput = svntest.main.run_command_stdin( 382 svntest.main.svnfsfs_binary, 0, 0, False, lines, 383 'load-index', self.repo_dir) 384 385 svntest.verify.verify_outputs("Error while rewriting index", 386 output, errput, [], []) 387 svntest.verify.verify_exit_code(None, exit_code, 0) 388 389 def get_item(self, item): 390 """ Return offset, length and type of ITEM. """ 391 values = self.by_item[item] 392 393 offset = int(values[0], 16) 394 len = int(values[1], 16) 395 type = values[2] 396 397 return (offset, len, type) 398 399 def modify_item(self, item, offset, len): 400 """ Modify offset and length of ITEM. """ 401 values = self.by_item[item] 402 403 values[0] = '%x' % offset 404 values[1] = '%x' % len 405 406 self._write() 407 408def repo_format(sbox): 409 """ Return the repository format number for SBOX.""" 410 411 format_file = open(os.path.join(sbox.repo_dir, "db", "format")) 412 format = int(format_file.read()[:1]) 413 format_file.close() 414 415 return format 416 417def set_changed_path_list(sbox, revision, changes): 418 """ Replace the changed paths list in the revision file REVISION in SBOX 419 with the text CHANGES.""" 420 421 idx = None 422 423 # read full file 424 fp = open(fsfs_file(sbox.repo_dir, 'revs', str(revision)), 'r+b') 425 contents = fp.read() 426 length = len(contents) 427 428 if repo_format(sbox) < 7: 429 # replace the changed paths list 430 header = contents[contents.rfind(b'\n', length - 64, length - 1):] 431 body_len = int(header.split(b' ')[1]) 432 433 else: 434 # read & parse revision file footer 435 footer_length = contents[length-1]; 436 if isinstance(footer_length, str): 437 footer_length = ord(footer_length) 438 439 footer = contents[length - footer_length - 1:length-1] 440 l2p_offset = int(footer.split(b' ')[0]) 441 l2p_checksum = footer.split(b' ')[1] 442 p2l_offset = int(footer.split(b' ')[2]) 443 p2l_checksum = footer.split(b' ')[3] 444 445 idx = FSFS_Index(sbox, revision) 446 (offset, item_len, item_type) = idx.get_item(1) 447 448 # split file contents 449 body_len = offset 450 indexes = contents[l2p_offset:length - footer_length - 1] 451 452 # construct new footer, include indexes as are 453 file_len = body_len + len(changes) + 1 454 p2l_offset += file_len - l2p_offset 455 456 header = str(file_len).encode() + b' ' + l2p_checksum + b' ' \ 457 + str(p2l_offset).encode() + b' ' + p2l_checksum 458 header += bytes([len(header)]) 459 header = b'\n' + indexes + header 460 461 contents = contents[:body_len] + changes + header 462 463 # set new contents 464 fp.seek(0) 465 fp.write(contents) 466 fp.truncate() 467 fp.close() 468 469 if repo_format(sbox) >= 7: 470 idx.modify_item(1, offset, len(changes) + 1) 471 472###################################################################### 473# Tests 474 475 476#---------------------------------------------------------------------- 477 478# dump stream tests need a dump file 479 480def clean_dumpfile(): 481 return \ 482 [ b"SVN-fs-dump-format-version: 2\n\n", 483 b"UUID: 668cc64a-31ed-0310-8ccb-b75d75bb44e3\n\n", 484 b"Revision-number: 0\n", 485 b"Prop-content-length: 56\n", 486 b"Content-length: 56\n\n", 487 b"K 8\nsvn:date\nV 27\n2005-01-08T21:48:13.838745Z\nPROPS-END\n\n\n", 488 b"Revision-number: 1\n", 489 b"Prop-content-length: 98\n", 490 b"Content-length: 98\n\n", 491 b"K 7\nsvn:log\nV 0\n\nK 10\nsvn:author\nV 4\nerik\n", 492 b"K 8\nsvn:date\nV 27\n2005-01-08T21:51:16.313791Z\nPROPS-END\n\n\n", 493 b"Node-path: A\n", 494 b"Node-kind: file\n", 495 b"Node-action: add\n", 496 b"Prop-content-length: 35\n", 497 b"Text-content-length: 5\n", 498 b"Text-content-md5: e1cbb0c3879af8347246f12c559a86b5\n", 499 b"Content-length: 40\n\n", 500 b"K 12\nsvn:keywords\nV 2\nId\nPROPS-END\ntext\n\n\n"] 501 502dumpfile_revisions = \ 503 [ svntest.wc.State('', { 'A' : svntest.wc.StateItem(contents="text\n") }) ] 504 505#---------------------------------------------------------------------- 506def extra_headers(sbox): 507 "loading of dumpstream with extra headers" 508 509 sbox.build(empty=True) 510 511 dumpfile = clean_dumpfile() 512 513 dumpfile[3:3] = \ 514 [ b"X-Comment-Header: Ignored header normally not in dump stream\n" ] 515 516 load_and_verify_dumpstream(sbox,[],[], dumpfile_revisions, False, dumpfile, 517 '--ignore-uuid') 518 519#---------------------------------------------------------------------- 520# Ensure loading continues after skipping a bit of unknown extra content. 521def extra_blockcontent(sbox): 522 "load success on oversized Content-length" 523 524 sbox.build(empty=True) 525 526 dumpfile = clean_dumpfile() 527 528 # Replace "Content-length" line with two lines 529 dumpfile[8:9] = \ 530 [ b"Extra-content-length: 10\n", 531 b"Content-length: 108\n\n" ] 532 # Insert the extra content after "PROPS-END\n" 533 dumpfile[11] = dumpfile[11][:-2] + b"extra text\n\n\n" 534 535 load_and_verify_dumpstream(sbox,[],[], dumpfile_revisions, False, dumpfile, 536 '--ignore-uuid') 537 538#---------------------------------------------------------------------- 539def inconsistent_headers(sbox): 540 "load failure on undersized Content-length" 541 542 sbox.build(empty=True) 543 544 dumpfile = clean_dumpfile() 545 546 dumpfile[-2] = b"Content-length: 30\n\n" 547 548 load_and_verify_dumpstream(sbox, [], svntest.verify.AnyOutput, 549 dumpfile_revisions, False, dumpfile) 550 551#---------------------------------------------------------------------- 552# Test for issue #2729: Datestamp-less revisions in dump streams do 553# not remain so after load 554@Issue(2729) 555def empty_date(sbox): 556 "preserve date-less revisions in load" 557 558 sbox.build(empty=True) 559 560 dumpfile = clean_dumpfile() 561 562 # Replace portions of the revision data to drop the svn:date revprop. 563 dumpfile[7:11] = \ 564 [ b"Prop-content-length: 52\n", 565 b"Content-length: 52\n\n", 566 b"K 7\nsvn:log\nV 0\n\nK 10\nsvn:author\nV 4\nerik\nPROPS-END\n\n\n" 567 ] 568 569 load_and_verify_dumpstream(sbox,[],[], dumpfile_revisions, False, dumpfile, 570 '--ignore-uuid') 571 572 # Verify that the revision still lacks the svn:date property. 573 svntest.actions.run_and_verify_svn([], '.*(E195011|E200017).*svn:date', 574 "propget", "--revprop", "-r1", "svn:date", 575 sbox.wc_dir) 576 577#---------------------------------------------------------------------- 578 579def dump_copied_dir(sbox): 580 "'svnadmin dump' on copied directory" 581 582 sbox.build() 583 wc_dir = sbox.wc_dir 584 repo_dir = sbox.repo_dir 585 586 old_C_path = os.path.join(wc_dir, 'A', 'C') 587 new_C_path = os.path.join(wc_dir, 'A', 'B', 'C') 588 svntest.main.run_svn(None, 'cp', old_C_path, new_C_path) 589 sbox.simple_commit(message='log msg') 590 591 exit_code, output, errput = svntest.main.run_svnadmin("dump", repo_dir) 592 if svntest.verify.compare_and_display_lines( 593 "Output of 'svnadmin dump' is unexpected.", 594 'STDERR', ["* Dumped revision 0.\n", 595 "* Dumped revision 1.\n", 596 "* Dumped revision 2.\n"], errput): 597 raise svntest.Failure 598 599#---------------------------------------------------------------------- 600 601def dump_move_dir_modify_child(sbox): 602 "'svnadmin dump' on modified child of copied dir" 603 604 sbox.build() 605 wc_dir = sbox.wc_dir 606 repo_dir = sbox.repo_dir 607 608 B_path = os.path.join(wc_dir, 'A', 'B') 609 Q_path = os.path.join(wc_dir, 'A', 'Q') 610 svntest.main.run_svn(None, 'cp', B_path, Q_path) 611 svntest.main.file_append(os.path.join(Q_path, 'lambda'), 'hello') 612 sbox.simple_commit(message='log msg') 613 exit_code, output, errput = svntest.main.run_svnadmin("dump", repo_dir) 614 svntest.verify.compare_and_display_lines( 615 "Output of 'svnadmin dump' is unexpected.", 616 'STDERR', ["* Dumped revision 0.\n", 617 "* Dumped revision 1.\n", 618 "* Dumped revision 2.\n"], errput) 619 620 exit_code, output, errput = svntest.main.run_svnadmin("dump", "-r", 621 "0:HEAD", repo_dir) 622 svntest.verify.compare_and_display_lines( 623 "Output of 'svnadmin dump' is unexpected.", 624 'STDERR', ["* Dumped revision 0.\n", 625 "* Dumped revision 1.\n", 626 "* Dumped revision 2.\n"], errput) 627 628#---------------------------------------------------------------------- 629 630def dump_quiet(sbox): 631 "'svnadmin dump --quiet'" 632 633 sbox.build(create_wc = False) 634 635 exit_code, dump, errput = svntest.main.run_svnadmin("dump", sbox.repo_dir, 636 '--quiet') 637 svntest.verify.compare_and_display_lines( 638 "Output of 'svnadmin dump --quiet' is unexpected.", 639 'STDERR', [], errput) 640 641#---------------------------------------------------------------------- 642 643def hotcopy_dot(sbox): 644 "'svnadmin hotcopy PATH .'" 645 sbox.build() 646 647 backup_dir, backup_url = sbox.add_repo_path('backup') 648 os.mkdir(backup_dir) 649 cwd = os.getcwd() 650 651 os.chdir(backup_dir) 652 svntest.actions.run_and_verify_svnadmin( 653 None, [], 654 "hotcopy", os.path.join(cwd, sbox.repo_dir), '.') 655 656 os.chdir(cwd) 657 658 if svntest.main.is_fs_type_fsfs(): 659 check_hotcopy_fsfs(sbox.repo_dir, backup_dir) 660 if svntest.main.is_fs_type_bdb(): 661 check_hotcopy_bdb(sbox.repo_dir, backup_dir) 662 if svntest.main.is_fs_type_fsx(): 663 check_hotcopy_fsx(sbox.repo_dir, backup_dir) 664 665#---------------------------------------------------------------------- 666 667# This test is redundant for FSFS. The hotcopy_dot and hotcopy_incremental 668# tests cover this check for FSFS already. 669@SkipUnless(svntest.main.is_fs_type_bdb) 670def hotcopy_format(sbox): 671 "'svnadmin hotcopy' checking db/format file" 672 sbox.build() 673 674 backup_dir, backup_url = sbox.add_repo_path('backup') 675 exit_code, output, errput = svntest.main.run_svnadmin("hotcopy", 676 sbox.repo_dir, 677 backup_dir) 678 if errput: 679 logger.warn("Error: hotcopy failed") 680 raise svntest.Failure 681 682 # verify that the db/format files are the same 683 fp = open(os.path.join(sbox.repo_dir, "db", "format")) 684 contents1 = fp.read() 685 fp.close() 686 687 fp2 = open(os.path.join(backup_dir, "db", "format")) 688 contents2 = fp2.read() 689 fp2.close() 690 691 if contents1 != contents2: 692 logger.warn("Error: db/format file contents do not match after hotcopy") 693 raise svntest.Failure 694 695#---------------------------------------------------------------------- 696 697def setrevprop(sbox): 698 "setlog, setrevprop, delrevprop; bypass hooks" 699 sbox.build() 700 701 # Try a simple log property modification. 702 iota_path = os.path.join(sbox.wc_dir, "iota") 703 mu_path = sbox.ospath('A/mu') 704 svntest.actions.run_and_verify_svnadmin([], [], 705 "setlog", sbox.repo_dir, "-r0", 706 "--bypass-hooks", 707 iota_path) 708 709 # Make sure it fails without --bypass-hooks. (We haven't called 710 # svntest.actions.enable_revprop_changes().) 711 # 712 # Note that we attempt to set the log message to a different value than the 713 # successful call. 714 svntest.actions.run_and_verify_svnadmin([], svntest.verify.AnyOutput, 715 "setlog", sbox.repo_dir, "-r0", 716 mu_path) 717 718 # Verify that the revprop value matches what we set when retrieved 719 # through the client. 720 svntest.actions.run_and_verify_svn([ "This is the file 'iota'.\n", "\n" ], 721 [], "propget", "--revprop", "-r0", 722 "svn:log", sbox.wc_dir) 723 724 # Try an author property modification. 725 foo_path = os.path.join(sbox.wc_dir, "foo") 726 svntest.main.file_write(foo_path, "foo") 727 728 exit_code, output, errput = svntest.main.run_svnadmin("setrevprop", 729 sbox.repo_dir, 730 "-r0", "svn:author", 731 foo_path) 732 if errput: 733 logger.warn("Error: 'setrevprop' failed") 734 raise svntest.Failure 735 736 # Verify that the revprop value matches what we set when retrieved 737 # through the client. 738 svntest.actions.run_and_verify_svn([ "foo\n" ], [], "propget", 739 "--revprop", "-r0", "svn:author", 740 sbox.wc_dir) 741 742 # Delete the property. 743 svntest.actions.run_and_verify_svnadmin([], [], 744 "delrevprop", "-r0", sbox.repo_dir, 745 "svn:author") 746 svntest.actions.run_and_verify_svnlook([], ".*E200017.*svn:author.*", 747 "propget", "--revprop", "-r0", 748 sbox.repo_dir, "svn:author") 749 750def verify_windows_paths_in_repos(sbox): 751 "verify a repository containing paths like 'c:hi'" 752 753 # setup a repo with a directory 'c:hi' 754 sbox.build(create_wc = False) 755 repo_url = sbox.repo_url 756 chi_url = sbox.repo_url + '/c:hi' 757 758 svntest.actions.run_and_verify_svn(None, [], 759 'mkdir', '-m', 'log_msg', 760 chi_url) 761 762 exit_code, output, errput = svntest.main.run_svnadmin("verify", 763 sbox.repo_dir) 764 if errput: 765 raise SVNUnexpectedStderr(errput) 766 767 # unfortunately, some backends needs to do more checks than other 768 # resulting in different progress output 769 if svntest.main.is_fs_log_addressing(): 770 svntest.verify.compare_and_display_lines( 771 "Error while running 'svnadmin verify'.", 772 'STDOUT', ["* Verifying metadata at revision 0 ...\n", 773 "* Verifying repository metadata ...\n", 774 "* Verified revision 0.\n", 775 "* Verified revision 1.\n", 776 "* Verified revision 2.\n"], output) 777 elif svntest.main.fs_has_rep_sharing() and not svntest.main.is_fs_type_bdb(): 778 svntest.verify.compare_and_display_lines( 779 "Error while running 'svnadmin verify'.", 780 'STDOUT', ["* Verifying repository metadata ...\n", 781 "* Verified revision 0.\n", 782 "* Verified revision 1.\n", 783 "* Verified revision 2.\n"], output) 784 else: 785 svntest.verify.compare_and_display_lines( 786 "Error while running 'svnadmin verify'.", 787 'STDOUT', ["* Verified revision 0.\n", 788 "* Verified revision 1.\n", 789 "* Verified revision 2.\n"], output) 790 791#---------------------------------------------------------------------- 792 793# Returns the filename of the rev or revprop file (according to KIND) 794# numbered REV in REPO_DIR, which must be in the first shard if we're 795# using a sharded repository. 796def fsfs_file(repo_dir, kind, rev): 797 if svntest.main.options.server_minor_version >= 5: 798 if svntest.main.options.fsfs_sharding is None: 799 if svntest.main.is_fs_type_fsx(): 800 rev = 'r' + rev 801 return os.path.join(repo_dir, 'db', kind, '0', rev) 802 else: 803 shard = int(rev) // svntest.main.options.fsfs_sharding 804 if svntest.main.is_fs_type_fsx(): 805 rev = 'r' + rev 806 path = os.path.join(repo_dir, 'db', kind, str(shard), rev) 807 808 if svntest.main.options.fsfs_packing is None or kind == 'revprops': 809 # we don't pack revprops 810 return path 811 elif os.path.exists(path): 812 # rev exists outside a pack file. 813 return path 814 else: 815 # didn't find the plain file; assume it's in a pack file 816 return os.path.join(repo_dir, 'db', kind, ('%d.pack' % shard), 'pack') 817 else: 818 return os.path.join(repo_dir, 'db', kind, rev) 819 820 821@SkipUnless(svntest.main.is_fs_type_fsfs) 822def verify_incremental_fsfs(sbox): 823 """svnadmin verify detects corruption dump can't""" 824 825 if svntest.main.options.fsfs_version is not None and \ 826 svntest.main.options.fsfs_version not in [4, 6]: 827 raise svntest.Skip("Unsupported prepackaged repository version") 828 829 # setup a repo with a directory 'c:hi' 830 # use physical addressing as this is hard to provoke with logical addressing 831 sbox.build(create_wc = False, 832 minor_version = min(svntest.main.options.server_minor_version,8)) 833 repo_url = sbox.repo_url 834 E_url = sbox.repo_url + '/A/B/E' 835 836 # Create A/B/E/bravo in r2. 837 svntest.actions.run_and_verify_svn(None, [], 838 'mkdir', '-m', 'log_msg', 839 E_url + '/bravo') 840 # Corrupt r2's reference to A/C by replacing "dir 7-1.0.r1/1568" with 841 # "dir 7-1.0.r1/1569" (increment offset) and updating the checksum for 842 # this directory listing to "c9b5a2d26473a4e28088673dda9df804" so that 843 # the listing itself is valid. 844 r2 = fsfs_file(sbox.repo_dir, 'revs', '2') 845 if r2.endswith('pack'): 846 raise svntest.Skip("Test doesn't handle packed revisions") 847 848 fp = open(r2, 'wb') 849 fp.write(b"""id: 0-2.0.r2/0 850type: dir 851count: 0 852cpath: /A/B/E/bravo 853copyroot: 0 / 854 855PLAIN 856K 5 857alpha 858V 17 859file 3-1.0.r1/719 860K 4 861beta 862V 17 863file 4-1.0.r1/840 864K 5 865bravo 866V 14 867dir 0-2.0.r2/0 868END 869ENDREP 870id: 2-1.0.r2/181 871type: dir 872pred: 2-1.0.r1/1043 873count: 1 874text: 2 69 99 99 f63001f7fddd1842d8891474d0982111 875cpath: /A/B/E 876copyroot: 0 / 877 878PLAIN 879K 1 880E 881V 16 882dir 2-1.0.r2/181 883K 1 884F 885V 17 886dir 5-1.0.r1/1160 887K 6 888lambda 889V 17 890file 6-1.0.r1/597 891END 892ENDREP 893id: 1-1.0.r2/424 894type: dir 895pred: 1-1.0.r1/1335 896count: 1 897text: 2 316 95 95 bccb66379b4f825dac12b50d80211bae 898cpath: /A/B 899copyroot: 0 / 900 901PLAIN 902K 1 903B 904V 16 905dir 1-1.0.r2/424 906K 1 907C 908V 17 909dir 7-1.0.r1/1569 910K 1 911D 912V 17 913dir 8-1.0.r1/3061 914K 2 915mu 916V 18 917file i-1.0.r1/1451 918END 919ENDREP 920id: 0-1.0.r2/692 921type: dir 922pred: 0-1.0.r1/3312 923count: 1 924text: 2 558 121 121 c9b5a2d26473a4e28088673dda9df804 925cpath: /A 926copyroot: 0 / 927 928PLAIN 929K 1 930A 931V 16 932dir 0-1.0.r2/692 933K 4 934iota 935V 18 936file j-1.0.r1/3428 937END 938ENDREP 939id: 0.0.r2/904 940type: dir 941pred: 0.0.r1/3624 942count: 2 943text: 2 826 65 65 e44e4151d0d124533338619f082c8c9a 944cpath: / 945copyroot: 0 / 946 947_0.0.t1-1 add false false /A/B/E/bravo 948 949 950904 1031 951""") 952 fp.close() 953 954 exit_code, output, errput = svntest.main.run_svnadmin("verify", "-r2", 955 sbox.repo_dir) 956 svntest.verify.verify_outputs( 957 message=None, actual_stdout=output, actual_stderr=errput, 958 expected_stdout=None, 959 expected_stderr=".*Found malformed header '[^']*' in revision file" 960 "|.*Missing id field in node-rev.*") 961 962#---------------------------------------------------------------------- 963 964# Helper for two test functions. 965def corrupt_and_recover_db_current(sbox, minor_version=None): 966 """Build up a MINOR_VERSION sandbox and test different recovery scenarios 967 with missing, out-of-date or even corrupt db/current files. Recovery should 968 behave the same way with all values of MINOR_VERSION, hence this helper 969 containing the common code that allows us to check it.""" 970 971 sbox.build(minor_version=minor_version) 972 current_path = os.path.join(sbox.repo_dir, 'db', 'current') 973 974 # Commit up to r3, so we can test various recovery scenarios. 975 svntest.main.file_append(os.path.join(sbox.wc_dir, 'iota'), 'newer line\n') 976 sbox.simple_commit(message='log msg') 977 978 svntest.main.file_append(os.path.join(sbox.wc_dir, 'iota'), 'newest line\n') 979 sbox.simple_commit(message='log msg') 980 981 # Remember the contents of the db/current file. 982 expected_current_contents = open(current_path).read() 983 984 # Move aside the current file for r3. 985 os.rename(os.path.join(sbox.repo_dir, 'db','current'), 986 os.path.join(sbox.repo_dir, 'db','was_current')) 987 988 # Run 'svnadmin recover' and check that the current file is recreated. 989 exit_code, output, errput = svntest.main.run_svnadmin("recover", 990 sbox.repo_dir) 991 if errput: 992 raise SVNUnexpectedStderr(errput) 993 994 actual_current_contents = open(current_path).read() 995 svntest.verify.compare_and_display_lines( 996 "Contents of db/current is unexpected.", 997 'db/current', expected_current_contents, actual_current_contents) 998 999 # Now try writing db/current to be one rev lower than it should be. 1000 svntest.main.file_write(current_path, '2\n') 1001 1002 # Run 'svnadmin recover' and check that the current file is fixed. 1003 exit_code, output, errput = svntest.main.run_svnadmin("recover", 1004 sbox.repo_dir) 1005 if errput: 1006 raise SVNUnexpectedStderr(errput) 1007 1008 actual_current_contents = open(current_path).read() 1009 svntest.verify.compare_and_display_lines( 1010 "Contents of db/current is unexpected.", 1011 'db/current', expected_current_contents, actual_current_contents) 1012 1013 # Now try writing db/current to be *two* revs lower than it should be. 1014 svntest.main.file_write(current_path, '1\n') 1015 1016 # Run 'svnadmin recover' and check that the current file is fixed. 1017 exit_code, output, errput = svntest.main.run_svnadmin("recover", 1018 sbox.repo_dir) 1019 if errput: 1020 raise SVNUnexpectedStderr(errput) 1021 1022 actual_current_contents = open(current_path).read() 1023 svntest.verify.compare_and_display_lines( 1024 "Contents of db/current is unexpected.", 1025 'db/current', expected_current_contents, actual_current_contents) 1026 1027 # Now try writing db/current to be fish revs lower than it should be. 1028 # 1029 # Note: I'm not actually sure it's wise to recover from this, but 1030 # detecting it would require rewriting fs_fs.c:get_youngest() to 1031 # check the actual contents of its buffer, since atol() will happily 1032 # convert "fish" to 0. 1033 svntest.main.file_write(current_path, 'fish\n') 1034 1035 # Run 'svnadmin recover' and check that the current file is fixed. 1036 exit_code, output, errput = svntest.main.run_svnadmin("recover", 1037 sbox.repo_dir) 1038 if errput: 1039 raise SVNUnexpectedStderr(errput) 1040 1041 actual_current_contents = open(current_path).read() 1042 svntest.verify.compare_and_display_lines( 1043 "Contents of db/current is unexpected.", 1044 'db/current', expected_current_contents, actual_current_contents) 1045 1046 1047@SkipUnless(svntest.main.is_fs_type_fsfs) 1048def fsfs_recover_db_current(sbox): 1049 "fsfs recover db/current" 1050 corrupt_and_recover_db_current(sbox) 1051 1052 1053@SkipUnless(svntest.main.is_fs_type_fsfs) 1054def fsfs_recover_old_db_current(sbox): 1055 "fsfs recover db/current --compatible-version=1.3" 1056 1057 # Around trunk@1573728, 'svnadmin recover' wrongly errored out 1058 # for the --compatible-version=1.3 repositories with missing or 1059 # invalid db/current file: 1060 # svnadmin: E160006: No such revision 1 1061 1062 corrupt_and_recover_db_current(sbox, minor_version=3) 1063 1064#---------------------------------------------------------------------- 1065@Issue(2983) 1066def load_with_parent_dir(sbox): 1067 "'svnadmin load --parent-dir' reparents mergeinfo" 1068 1069 ## See https://issues.apache.org/jira/browse/SVN-2983. ## 1070 sbox.build(empty=True) 1071 1072 dumpfile_location = os.path.join(os.path.dirname(sys.argv[0]), 1073 'svnadmin_tests_data', 1074 'mergeinfo_included.dump') 1075 dumpfile = svntest.actions.load_dumpfile(dumpfile_location) 1076 1077 # Create 'sample' dir in sbox.repo_url, and load the dump stream there. 1078 svntest.actions.run_and_verify_svn(['Committing transaction...\n', 1079 'Committed revision 1.\n'], 1080 [], "mkdir", sbox.repo_url + "/sample", 1081 "-m", "Create sample dir") 1082 load_dumpstream(sbox, dumpfile, '--parent-dir', '/sample') 1083 1084 # Verify the svn:mergeinfo properties for '--parent-dir' 1085 svntest.actions.run_and_verify_svn([sbox.repo_url + 1086 "/sample/branch - /sample/trunk:5-7\n"], 1087 [], 'propget', 'svn:mergeinfo', '-R', 1088 sbox.repo_url + '/sample/branch') 1089 svntest.actions.run_and_verify_svn([sbox.repo_url + 1090 "/sample/branch1 - " + 1091 "/sample/branch:6-9\n"], 1092 [], 'propget', 'svn:mergeinfo', '-R', 1093 sbox.repo_url + '/sample/branch1') 1094 1095 # Create 'sample-2' dir in sbox.repo_url, and load the dump stream again. 1096 # This time, don't include a leading slash on the --parent-dir argument. 1097 # See issue #3547. 1098 svntest.actions.run_and_verify_svn(['Committing transaction...\n', 1099 'Committed revision 11.\n'], 1100 [], "mkdir", sbox.repo_url + "/sample-2", 1101 "-m", "Create sample-2 dir") 1102 load_dumpstream(sbox, dumpfile, '--parent-dir', 'sample-2') 1103 1104 # Verify the svn:mergeinfo properties for '--parent-dir'. 1105 svntest.actions.run_and_verify_svn([sbox.repo_url + 1106 "/sample-2/branch - " + 1107 "/sample-2/trunk:15-17\n"], 1108 [], 'propget', 'svn:mergeinfo', '-R', 1109 sbox.repo_url + '/sample-2/branch') 1110 svntest.actions.run_and_verify_svn([sbox.repo_url + 1111 "/sample-2/branch1 - " + 1112 "/sample-2/branch:16-19\n"], 1113 [], 'propget', 'svn:mergeinfo', '-R', 1114 sbox.repo_url + '/sample-2/branch1') 1115 1116#---------------------------------------------------------------------- 1117 1118def set_uuid(sbox): 1119 "test 'svnadmin setuuid'" 1120 1121 sbox.build(create_wc=False) 1122 1123 # Squirrel away the original repository UUID. 1124 exit_code, output, errput = svntest.main.run_svnlook('uuid', sbox.repo_dir) 1125 if errput: 1126 raise SVNUnexpectedStderr(errput) 1127 orig_uuid = output[0].rstrip() 1128 1129 # Try setting a new, bogus UUID. 1130 svntest.actions.run_and_verify_svnadmin(None, '^.*Malformed UUID.*$', 1131 'setuuid', sbox.repo_dir, 'abcdef') 1132 1133 # Try generating a brand new UUID. 1134 svntest.actions.run_and_verify_svnadmin([], None, 1135 'setuuid', sbox.repo_dir) 1136 exit_code, output, errput = svntest.main.run_svnlook('uuid', sbox.repo_dir) 1137 if errput: 1138 raise SVNUnexpectedStderr(errput) 1139 new_uuid = output[0].rstrip() 1140 if new_uuid == orig_uuid: 1141 logger.warn("Error: new UUID matches the original one") 1142 raise svntest.Failure 1143 1144 # Now, try setting the UUID back to the original value. 1145 svntest.actions.run_and_verify_svnadmin([], None, 1146 'setuuid', sbox.repo_dir, orig_uuid) 1147 exit_code, output, errput = svntest.main.run_svnlook('uuid', sbox.repo_dir) 1148 if errput: 1149 raise SVNUnexpectedStderr(errput) 1150 new_uuid = output[0].rstrip() 1151 if new_uuid != orig_uuid: 1152 logger.warn("Error: new UUID doesn't match the original one") 1153 raise svntest.Failure 1154 1155#---------------------------------------------------------------------- 1156@Issue(3020) 1157def reflect_dropped_renumbered_revs(sbox): 1158 "reflect dropped renumbered revs in svn:mergeinfo" 1159 1160 ## See https://issues.apache.org/jira/browse/SVN-3020. ## 1161 1162 sbox.build(empty=True) 1163 1164 dumpfile_location = os.path.join(os.path.dirname(sys.argv[0]), 1165 'svndumpfilter_tests_data', 1166 'with_merges.dump') 1167 dumpfile = svntest.actions.load_dumpfile(dumpfile_location) 1168 1169 # Create 'toplevel' dir in sbox.repo_url 1170 svntest.actions.run_and_verify_svn(['Committing transaction...\n', 1171 'Committed revision 1.\n'], 1172 [], "mkdir", sbox.repo_url + "/toplevel", 1173 "-m", "Create toplevel dir") 1174 1175 # Load the dump stream in sbox.repo_url 1176 load_dumpstream(sbox, dumpfile) 1177 1178 # Load the dump stream in toplevel dir 1179 load_dumpstream(sbox, dumpfile, '--parent-dir', '/toplevel') 1180 1181 # Verify the svn:mergeinfo properties 1182 url = sbox.repo_url 1183 expected_output = svntest.verify.UnorderedOutput([ 1184 url + "/trunk - /branch1:5-9\n", 1185 url + "/toplevel/trunk - /toplevel/branch1:14-18\n", 1186 ]) 1187 svntest.actions.run_and_verify_svn(expected_output, [], 1188 'propget', 'svn:mergeinfo', '-R', 1189 sbox.repo_url) 1190 1191#---------------------------------------------------------------------- 1192 1193@SkipUnless(svntest.main.is_fs_type_fsfs) 1194@Issue(2992) 1195def fsfs_recover_handle_missing_revs_or_revprops_file(sbox): 1196 """fsfs recovery checks missing revs / revprops files""" 1197 # Set up a repository containing the greek tree. 1198 sbox.build() 1199 1200 # Commit up to r3, so we can test various recovery scenarios. 1201 svntest.main.file_append(os.path.join(sbox.wc_dir, 'iota'), 'newer line\n') 1202 sbox.simple_commit(message='log msg') 1203 1204 svntest.main.file_append(os.path.join(sbox.wc_dir, 'iota'), 'newest line\n') 1205 sbox.simple_commit(message='log msg') 1206 1207 rev_3 = fsfs_file(sbox.repo_dir, 'revs', '3') 1208 rev_was_3 = rev_3 + '.was' 1209 1210 # Move aside the revs file for r3. 1211 os.rename(rev_3, rev_was_3) 1212 1213 # Verify 'svnadmin recover' fails when youngest has a revprops 1214 # file but no revs file. 1215 exit_code, output, errput = svntest.main.run_svnadmin("recover", 1216 sbox.repo_dir) 1217 1218 if svntest.verify.verify_outputs( 1219 "Output of 'svnadmin recover' is unexpected.", None, errput, None, 1220 ".*Expected current rev to be <= %s but found 3" 1221 # For example, if svntest.main.fsfs_sharding == 2, then rev_3 would 1222 # be the pack file for r2:r3, and the error message would report "<= 1". 1223 % (rev_3.endswith('pack') and '[012]' or '2')): 1224 raise svntest.Failure 1225 1226 # Restore the r3 revs file, thus repairing the repository. 1227 os.rename(rev_was_3, rev_3) 1228 1229 revprop_3 = fsfs_file(sbox.repo_dir, 'revprops', '3') 1230 revprop_was_3 = revprop_3 + '.was' 1231 1232 # Move aside the revprops file for r3. 1233 os.rename(revprop_3, revprop_was_3) 1234 1235 # Verify 'svnadmin recover' fails when youngest has a revs file 1236 # but no revprops file (issue #2992). 1237 exit_code, output, errput = svntest.main.run_svnadmin("recover", 1238 sbox.repo_dir) 1239 1240 if svntest.verify.verify_outputs( 1241 "Output of 'svnadmin recover' is unexpected.", None, errput, None, 1242 ".*Revision 3 has a revs file but no revprops file"): 1243 raise svntest.Failure 1244 1245 # Restore the r3 revprops file, thus repairing the repository. 1246 os.rename(revprop_was_3, revprop_3) 1247 1248 # Change revprops file to a directory for revision 3 1249 os.rename(revprop_3, revprop_was_3) 1250 os.mkdir(revprop_3) 1251 1252 # Verify 'svnadmin recover' fails when youngest has a revs file 1253 # but revprops file is not a file (another aspect of issue #2992). 1254 exit_code, output, errput = svntest.main.run_svnadmin("recover", 1255 sbox.repo_dir) 1256 1257 if svntest.verify.verify_outputs( 1258 "Output of 'svnadmin recover' is unexpected.", None, errput, None, 1259 ".*Revision 3 has a non-file where its revprops file should be.*"): 1260 raise svntest.Failure 1261 1262 # Restore the r3 revprops file, thus repairing the repository. 1263 os.rmdir(revprop_3) 1264 os.rename(revprop_was_3, revprop_3) 1265 1266 1267#---------------------------------------------------------------------- 1268 1269@Skip(svntest.main.tests_use_prepackaged_repository) 1270def create_in_repo_subdir(sbox): 1271 "'svnadmin create /path/to/repo/subdir'" 1272 1273 sbox.build(create_wc=False, empty=True) 1274 repo_dir = sbox.repo_dir 1275 1276 success = False 1277 try: 1278 # This should fail 1279 subdir = os.path.join(repo_dir, 'Z') 1280 svntest.main.create_repos(subdir) 1281 except svntest.main.SVNRepositoryCreateFailure: 1282 success = True 1283 if not success: 1284 raise svntest.Failure 1285 1286 cwd = os.getcwd() 1287 success = False 1288 try: 1289 # This should fail, too 1290 subdir = os.path.join(repo_dir, 'conf') 1291 os.chdir(subdir) 1292 svntest.main.create_repos('Z') 1293 os.chdir(cwd) 1294 except svntest.main.SVNRepositoryCreateFailure: 1295 success = True 1296 os.chdir(cwd) 1297 if not success: 1298 raise svntest.Failure 1299 1300 1301@SkipUnless(svntest.main.is_fs_type_fsfs) 1302@SkipDumpLoadCrossCheck() 1303def verify_with_invalid_revprops(sbox): 1304 "svnadmin verify detects invalid revprops file" 1305 1306 sbox.build(create_wc=False, empty=True) 1307 repo_dir = sbox.repo_dir 1308 1309 # Run a test verify 1310 exit_code, output, errput = svntest.main.run_svnadmin("verify", 1311 sbox.repo_dir) 1312 1313 if errput: 1314 raise SVNUnexpectedStderr(errput) 1315 if svntest.verify.verify_outputs( 1316 "Output of 'svnadmin verify' is unexpected.", None, output, None, 1317 ".*Verified revision 0*"): 1318 raise svntest.Failure 1319 1320 # Empty the revprops file 1321 rp_file = open(os.path.join(repo_dir, 'db', 'revprops', '0', '0'), 'w') 1322 1323 rp_file.write('') 1324 rp_file.close() 1325 1326 exit_code, output, errput = svntest.main.run_svnadmin("verify", 1327 sbox.repo_dir) 1328 1329 if svntest.verify.verify_outputs( 1330 "Output of 'svnadmin verify' is unexpected.", None, errput, None, 1331 ".*svnadmin: E200002:.*"): 1332 raise svntest.Failure 1333 1334#---------------------------------------------------------------------- 1335# Even *more* testing for issue #3020 'Reflect dropped/renumbered 1336# revisions in svn:mergeinfo data during svnadmin load' 1337# 1338# Full or incremental dump-load cycles should result in the same 1339# mergeinfo in the loaded repository. 1340# 1341# Given a repository 'SOURCE-REPOS' with mergeinfo, and a repository 1342# 'TARGET-REPOS' (which may or may not be empty), either of the following 1343# methods to move 'SOURCE-REPOS' to 'TARGET-REPOS' should result in 1344# the same mergeinfo on 'TARGET-REPOS': 1345# 1346# 1) Dump -r1:HEAD from 'SOURCE-REPOS' and load it in one shot to 1347# 'TARGET-REPOS'. 1348# 1349# 2) Dump 'SOURCE-REPOS' in a series of incremental dumps and load 1350# each of them to 'TARGET-REPOS'. 1351# 1352# See https://issues.apache.org/jira/browse/SVN-3020#desc13 1353@Issue(3020) 1354def dont_drop_valid_mergeinfo_during_incremental_loads(sbox): 1355 "don't filter mergeinfo revs from incremental dump" 1356 1357 # Create an empty repos. 1358 sbox.build(empty=True) 1359 1360 # PART 1: Load a full dump to an empty repository. 1361 # 1362 # The test repository used here, 'mergeinfo_included_full.dump', is 1363 # this repos: 1364 # __________________________________________ 1365 # | | 1366 # | ____________________________|_____ 1367 # | | | | 1368 # trunk---r2---r3-----r5---r6-------r8---r9---------------> | | 1369 # r1 | | | | | | 1370 # initial | | | |______ | | 1371 # import copy | copy | merge merge 1372 # | | | merge (r5) (r8) 1373 # | | | (r9) | | 1374 # | | | | | | 1375 # | | V V | | 1376 # | | branches/B2-------r11---r12----> | | 1377 # | | r7 |____| | | 1378 # | | | | | 1379 # | merge |___ | | 1380 # | (r6) | | | 1381 # | |_________________ | | | 1382 # | | merge | | 1383 # | | (r11-12) | | 1384 # | | | | | 1385 # V V V | | 1386 # branches/B1-------------------r10--------r13--> | | 1387 # r4 | | 1388 # | V V 1389 # branches/B1/B/E------------------------------r14---r15-> 1390 # 1391 # 1392 # The mergeinfo on this repos@15 is: 1393 # 1394 # Properties on 'branches/B1': 1395 # svn:mergeinfo 1396 # /branches/B2:11-12 1397 # /trunk:6,9 1398 # Properties on 'branches/B1/B/E': 1399 # svn:mergeinfo 1400 # /branches/B2/B/E:11-12 1401 # /trunk/B/E:5-6,8-9 1402 # Properties on 'branches/B2': 1403 # svn:mergeinfo 1404 # /trunk:9 1405 dumpfile_location = os.path.join(os.path.dirname(sys.argv[0]), 1406 'svnadmin_tests_data', 1407 'mergeinfo_included_full.dump') 1408 dumpfile_full = svntest.actions.load_dumpfile(dumpfile_location) 1409 load_dumpstream(sbox, dumpfile_full, '--ignore-uuid') 1410 1411 # Check that the mergeinfo is as expected. 1412 url = sbox.repo_url + '/branches/' 1413 expected_output = svntest.verify.UnorderedOutput([ 1414 url + "B1 - /branches/B2:11-12\n", 1415 "/trunk:6,9\n", 1416 url + "B2 - /trunk:9\n", 1417 url + "B1/B/E - /branches/B2/B/E:11-12\n", 1418 "/trunk/B/E:5-6,8-9\n"]) 1419 svntest.actions.run_and_verify_svn(expected_output, [], 1420 'propget', 'svn:mergeinfo', '-R', 1421 sbox.repo_url) 1422 1423 # PART 2: Load a a series of incremental dumps to an empty repository. 1424 # 1425 # Incrementally dump the repository into three dump files: 1426 dump_file_r1_10 = sbox.get_tempname("r1-10-dump") 1427 exit_code, output, errput = svntest.main.run_svnadmin( 1428 'dump', sbox.repo_dir, '-r1:10') 1429 dump_fp = open(dump_file_r1_10, 'wb') 1430 dump_fp.writelines(output) 1431 dump_fp.close() 1432 1433 dump_file_r11_13 = sbox.get_tempname("r11-13-dump") 1434 exit_code, output, errput = svntest.main.run_svnadmin( 1435 'dump', sbox.repo_dir, '--incremental', '-r11:13') 1436 dump_fp = open(dump_file_r11_13, 'wb') 1437 dump_fp.writelines(output) 1438 dump_fp.close() 1439 1440 dump_file_r14_15 = sbox.get_tempname("r14-15-dump") 1441 exit_code, output, errput = svntest.main.run_svnadmin( 1442 'dump', sbox.repo_dir, '--incremental', '-r14:15') 1443 dump_fp = open(dump_file_r14_15, 'wb') 1444 dump_fp.writelines(output) 1445 dump_fp.close() 1446 1447 # Blow away the current repos and create an empty one in its place. 1448 sbox.build(empty=True) 1449 1450 # Load the three incremental dump files in sequence. 1451 load_dumpstream(sbox, svntest.actions.load_dumpfile(dump_file_r1_10), 1452 '--ignore-uuid') 1453 load_dumpstream(sbox, svntest.actions.load_dumpfile(dump_file_r11_13), 1454 '--ignore-uuid') 1455 load_dumpstream(sbox, svntest.actions.load_dumpfile(dump_file_r14_15), 1456 '--ignore-uuid') 1457 1458 # Check the mergeinfo, we use the same expected output as before, 1459 # as it (duh!) should be exactly the same as when we loaded the 1460 # repos in one shot. 1461 svntest.actions.run_and_verify_svn(expected_output, [], 1462 'propget', 'svn:mergeinfo', '-R', 1463 sbox.repo_url) 1464 1465 # Now repeat the above two scenarios, but with an initially non-empty target 1466 # repository. First, try the full dump-load in one shot. 1467 # 1468 # PART 3: Load a full dump to an non-empty repository. 1469 # 1470 # Reset our sandbox. 1471 sbox.build(empty=True) 1472 1473 # Load this skeleton repos into the empty target: 1474 # 1475 # Projects/ (Added r1) 1476 # README (Added r2) 1477 # Project-X (Added r3) 1478 # Project-Y (Added r4) 1479 # Project-Z (Added r5) 1480 # docs/ (Added r6) 1481 # README (Added r6) 1482 dumpfile_location = os.path.join(os.path.dirname(sys.argv[0]), 1483 'svnadmin_tests_data', 1484 'skeleton_repos.dump') 1485 dumpfile_skeleton = svntest.actions.load_dumpfile(dumpfile_location) 1486 load_dumpstream(sbox, dumpfile_skeleton, '--ignore-uuid') 1487 1488 # Load 'svnadmin_tests_data/mergeinfo_included_full.dump' in one shot: 1489 load_dumpstream(sbox, dumpfile_full, '--parent-dir', 'Projects/Project-X', 1490 '--ignore-uuid') 1491 1492 # Check that the mergeinfo is as expected. This is exactly the 1493 # same expected mergeinfo we previously checked, except that the 1494 # revisions are all offset +6 to reflect the revions already in 1495 # the skeleton target before we began loading and the leading source 1496 # paths are adjusted by the --parent-dir: 1497 # 1498 # Properties on 'branches/B1': 1499 # svn:mergeinfo 1500 # /Projects/Project-X/branches/B2:17-18 1501 # /Projects/Project-X/trunk:12,15 1502 # Properties on 'branches/B1/B/E': 1503 # svn:mergeinfo 1504 # /Projects/Project-X/branches/B2/B/E:17-18 1505 # /Projects/Project-X/trunk/B/E:11-12,14-15 1506 # Properties on 'branches/B2': 1507 # svn:mergeinfo 1508 # /Projects/Project-X/trunk:15 1509 url = sbox.repo_url + '/Projects/Project-X/branches/' 1510 expected_output = svntest.verify.UnorderedOutput([ 1511 url + "B1 - /Projects/Project-X/branches/B2:17-18\n", 1512 "/Projects/Project-X/trunk:12,15\n", 1513 url + "B2 - /Projects/Project-X/trunk:15\n", 1514 url + "B1/B/E - /Projects/Project-X/branches/B2/B/E:17-18\n", 1515 "/Projects/Project-X/trunk/B/E:11-12,14-15\n"]) 1516 svntest.actions.run_and_verify_svn(expected_output, [], 1517 'propget', 'svn:mergeinfo', '-R', 1518 sbox.repo_url) 1519 1520 # PART 4: Load a a series of incremental dumps to an non-empty repository. 1521 # 1522 # Reset our sandbox. 1523 sbox.build(empty=True) 1524 1525 # Load this skeleton repos into the empty target: 1526 load_dumpstream(sbox, dumpfile_skeleton, '--ignore-uuid') 1527 1528 # Load the three incremental dump files in sequence. 1529 load_dumpstream(sbox, svntest.actions.load_dumpfile(dump_file_r1_10), 1530 '--parent-dir', 'Projects/Project-X', '--ignore-uuid') 1531 load_dumpstream(sbox, svntest.actions.load_dumpfile(dump_file_r11_13), 1532 '--parent-dir', 'Projects/Project-X', '--ignore-uuid') 1533 load_dumpstream(sbox, svntest.actions.load_dumpfile(dump_file_r14_15), 1534 '--parent-dir', 'Projects/Project-X', '--ignore-uuid') 1535 1536 # Check the resulting mergeinfo. We expect the exact same results 1537 # as Part 3. 1538 # See https://issues.apache.org/jira/browse/SVN-3020#desc16. 1539 svntest.actions.run_and_verify_svn(expected_output, [], 1540 'propget', 'svn:mergeinfo', '-R', 1541 sbox.repo_url) 1542 1543 1544@SkipUnless(svntest.main.is_posix_os) 1545@Issue(2591) 1546def hotcopy_symlink(sbox): 1547 "'svnadmin hotcopy' replicates symlink" 1548 1549 ## See https://issues.apache.org/jira/browse/SVN-2591. ## 1550 1551 # Create a repository. 1552 sbox.build(create_wc=False, empty=True) 1553 original_repo = sbox.repo_dir 1554 1555 hotcopy_repo, hotcopy_url = sbox.add_repo_path('hotcopy') 1556 1557 # Create a file, a dir and a missing path outside the repoitory. 1558 svntest.main.safe_rmtree(sbox.wc_dir, 1) 1559 os.mkdir(sbox.wc_dir) 1560 external_file_path = os.path.join(sbox.wc_dir, "file") 1561 svntest.main.file_write(external_file_path, "An existing file") 1562 external_dir_path = os.path.join(sbox.wc_dir, "dir") 1563 os.mkdir(external_dir_path) 1564 external_missing_path = os.path.join(sbox.wc_dir, "missing") 1565 1566 # Symlink definitions: base name -> target relpath. 1567 # Check both existing and nonexistent targets. 1568 # Check targets both within and outside the source repository. 1569 symlinks = [ 1570 ('in_repos_file', 'format'), 1571 ('in_repos_dir', 'conf'), 1572 ('in_repos_missing', 'missing'), 1573 ('external_file', os.path.join('..', '..', '..', external_file_path)), 1574 ('external_dir', os.path.join('..', '..', '..', external_dir_path)), 1575 ('external_missing', os.path.join('..', '..', '..', external_missing_path)), 1576 ] 1577 1578 # Create symlinks within the repository directory. 1579 for name, target_relpath in symlinks: 1580 target_path = os.path.join(original_repo, target_relpath) 1581 target_abspath = os.path.abspath(target_path) 1582 1583 # Create two symlinks to each target - one relative, one absolute. 1584 symlink_path = os.path.join(original_repo, name) 1585 os.symlink(target_relpath, symlink_path + '_rel') 1586 os.symlink(target_abspath, symlink_path + '_abs') 1587 1588 svntest.actions.run_and_verify_svnadmin( 1589 None, [], 1590 "hotcopy", original_repo, hotcopy_repo) 1591 1592 # Check if the symlinks were copied correctly. 1593 for name, target_relpath in symlinks: 1594 target_path = os.path.join(original_repo, target_relpath) 1595 target_abspath = os.path.abspath(target_path) 1596 1597 # Check two symlinks to each target - one relative, one absolute. 1598 symlink_path = os.path.join(hotcopy_repo, name) 1599 if os.readlink(symlink_path + '_rel') != target_relpath: 1600 raise svntest.Failure 1601 if os.readlink(symlink_path + '_abs') != target_abspath: 1602 raise svntest.Failure 1603 1604def load_bad_props(sbox): 1605 "svnadmin load with invalid svn: props" 1606 1607 dump_str = b"""SVN-fs-dump-format-version: 2 1608 1609UUID: dc40867b-38f6-0310-9f5f-f81aa277e06f 1610 1611Revision-number: 0 1612Prop-content-length: 56 1613Content-length: 56 1614 1615K 8 1616svn:date 1617V 27 16182005-05-03T19:09:41.129900Z 1619PROPS-END 1620 1621Revision-number: 1 1622Prop-content-length: 99 1623Content-length: 99 1624 1625K 7 1626svn:log 1627V 3 1628\n\r\n 1629K 10 1630svn:author 1631V 2 1632pl 1633K 8 1634svn:date 1635V 27 16362005-05-03T19:10:19.975578Z 1637PROPS-END 1638 1639Node-path: file 1640Node-kind: file 1641Node-action: add 1642Prop-content-length: 10 1643Text-content-length: 5 1644Text-content-md5: e1cbb0c3879af8347246f12c559a86b5 1645Content-length: 15 1646 1647PROPS-END 1648text 1649 1650 1651""" 1652 sbox.build(empty=True) 1653 1654 # Try to load the dumpstream, expecting a failure (because of mixed EOLs). 1655 exp_err = svntest.verify.RegexListOutput(['svnadmin: E125005:.*', 1656 'svnadmin: E125005:.*', 1657 'svnadmin: E125017:.*'], 1658 match_all=False) 1659 load_and_verify_dumpstream(sbox, [], exp_err, dumpfile_revisions, 1660 False, dump_str, '--ignore-uuid') 1661 1662 # Now try it again bypassing prop validation. (This interface takes 1663 # care of the removal and recreation of the original repository.) 1664 svntest.actions.load_repo(sbox, dump_str=dump_str, 1665 bypass_prop_validation=True) 1666 # Getting the property should fail. 1667 svntest.actions.run_and_verify_svn(None, 'svn: E135000: ', 1668 'pg', 'svn:log', '--revprop', '-r1', 1669 sbox.repo_url) 1670 1671 # Now try it again with prop normalization. 1672 svntest.actions.load_repo(sbox, dump_str=dump_str, 1673 bypass_prop_validation=False, 1674 normalize_props=True) 1675 # We should get the expected property value. 1676 exit_code, output, _ = svntest.main.run_svn(None, 'pg', 'svn:log', 1677 '--revprop', '-r1', 1678 '--no-newline', 1679 sbox.repo_url) 1680 svntest.verify.verify_exit_code(None, exit_code, 0) 1681 if output != ['\n', '\n']: 1682 raise svntest.Failure("Unexpected property value %s" % output) 1683 1684# This test intentionally corrupts a revision and assumes an FSFS 1685# repository. If you can make it work with BDB please do so. 1686# However, the verification triggered by this test is in the repos layer 1687# so it will trigger with either backend anyway. 1688@SkipUnless(svntest.main.is_fs_type_fsfs) 1689@SkipUnless(svntest.main.server_enforces_UTF8_fspaths_in_verify) 1690def verify_non_utf8_paths(sbox): 1691 "svnadmin verify with non-UTF-8 paths" 1692 1693 if svntest.main.options.fsfs_version is not None and \ 1694 svntest.main.options.fsfs_version not in [4, 6]: 1695 raise svntest.Skip("Unsupported prepackaged repository version") 1696 1697 dumpfile = clean_dumpfile() 1698 1699 # Corruption only possible in physically addressed revisions created 1700 # with pre-1.6 servers. 1701 sbox.build(empty=True, 1702 minor_version=min(svntest.main.options.server_minor_version,8)) 1703 1704 # Load the dumpstream 1705 load_and_verify_dumpstream(sbox, [], [], dumpfile_revisions, False, 1706 dumpfile, '--ignore-uuid') 1707 1708 # Replace the path 'A' in revision 1 with a non-UTF-8 sequence. 1709 # This has been observed in repositories in the wild, though Subversion 1710 # 1.6 and greater should prevent such filenames from entering the repository. 1711 path1 = os.path.join(sbox.repo_dir, "db", "revs", "0", "1") 1712 path_new = os.path.join(sbox.repo_dir, "db", "revs", "0", "1.new") 1713 fp1 = open(path1, 'rb') 1714 fp_new = open(path_new, 'wb') 1715 for line in fp1.readlines(): 1716 if line == b"A\n": 1717 # replace 'A' with a latin1 character -- the new path is not valid UTF-8 1718 fp_new.write(b"\xE6\n") 1719 elif line == b"text: 1 340 32 32 a6be7b4cf075fd39e6a99eb69a31232b\n": 1720 # phys, PLAIN directories: fix up the representation checksum 1721 fp_new.write(b"text: 1 340 32 32 f2e93e73272cac0f18fccf16f224eb93\n") 1722 elif line == b"text: 1 340 44 32 a6be7b4cf075fd39e6a99eb69a31232b\n": 1723 # phys, deltified directories: fix up the representation checksum 1724 fp_new.write(b"text: 1 340 44 32 f2e93e73272cac0f18fccf16f224eb93\n") 1725 elif line == b"cpath: /A\n": 1726 # also fix up the 'created path' field 1727 fp_new.write(b"cpath: /\xE6\n") 1728 elif line == b"_0.0.t0-0 add-file true true /A\n": 1729 # and another occurrance 1730 fp_new.write(b"_0.0.t0-0 add-file true true /\xE6\n") 1731 else: 1732 fp_new.write(line) 1733 fp1.close() 1734 fp_new.close() 1735 os.remove(path1) 1736 os.rename(path_new, path1) 1737 1738 # Verify the repository, expecting failure 1739 exit_code, output, errput = svntest.main.run_svnadmin("verify", 1740 sbox.repo_dir) 1741 svntest.verify.verify_outputs( 1742 "Unexpected error while running 'svnadmin verify'.", 1743 [], errput, None, ".*Path '.*' is not in UTF-8.*") 1744 1745 # Make sure the repository can still be dumped so that the 1746 # encoding problem can be fixed in a dump/edit/load cycle. 1747 expected_stderr = [ 1748 "* Dumped revision 0.\n", 1749 "WARNING 0x0002: E160005: " 1750 "While validating fspath '?\\E6': " 1751 "Path '?\\E6' is not in UTF-8" 1752 "\n", 1753 "* Dumped revision 1.\n", 1754 ] 1755 exit_code, output, errput = svntest.main.run_svnadmin("dump", sbox.repo_dir) 1756 if svntest.verify.compare_and_display_lines( 1757 "Output of 'svnadmin dump' is unexpected.", 1758 'STDERR', expected_stderr, errput): 1759 raise svntest.Failure 1760 1761def test_lslocks_and_rmlocks(sbox): 1762 "test 'svnadmin lslocks' and 'svnadmin rmlocks'" 1763 1764 sbox.build(create_wc=False) 1765 iota_url = sbox.repo_url + '/iota' 1766 lambda_url = sbox.repo_url + '/A/B/lambda' 1767 1768 exit_code, output, errput = svntest.main.run_svnadmin("lslocks", 1769 sbox.repo_dir) 1770 1771 if exit_code or errput or output: 1772 raise svntest.Failure("Error: 'lslocks' failed") 1773 1774 expected_output = svntest.verify.UnorderedRegexListOutput( 1775 ["'.*lambda' locked by user 'jrandom'.\n", 1776 "'.*iota' locked by user 'jrandom'.\n"]) 1777 1778 # Lock iota and A/B/lambda using svn client 1779 svntest.actions.run_and_verify_svn(expected_output, 1780 [], "lock", "-m", "Locking files", 1781 iota_url, lambda_url) 1782 1783 def expected_output_list(path): 1784 return [ 1785 "Path: " + path, 1786 "UUID Token: opaquelocktoken:.*", 1787 "Owner: jrandom", 1788 "Created:.*", 1789 "Expires:.*", 1790 "Comment \(1 line\):", 1791 "Locking files", 1792 "\n", # empty line 1793 ] 1794 1795 # List all locks 1796 exit_code, output, errput = svntest.main.run_svnadmin("lslocks", 1797 sbox.repo_dir) 1798 if errput: 1799 raise SVNUnexpectedStderr(errput) 1800 svntest.verify.verify_exit_code(None, exit_code, 0) 1801 1802 expected_output = svntest.verify.UnorderedRegexListOutput( 1803 expected_output_list('/A/B/lambda') + 1804 expected_output_list('/iota')) 1805 svntest.verify.compare_and_display_lines('lslocks output mismatch', 1806 'output', 1807 expected_output, output) 1808 1809 # List lock in path /A 1810 exit_code, output, errput = svntest.main.run_svnadmin("lslocks", 1811 sbox.repo_dir, 1812 "A") 1813 if errput: 1814 raise SVNUnexpectedStderr(errput) 1815 1816 expected_output = svntest.verify.RegexListOutput( 1817 expected_output_list('/A/B/lambda')) 1818 svntest.verify.compare_and_display_lines('lslocks output mismatch', 1819 'output', 1820 expected_output, output) 1821 svntest.verify.verify_exit_code(None, exit_code, 0) 1822 1823 # Remove locks 1824 exit_code, output, errput = svntest.main.run_svnadmin("rmlocks", 1825 sbox.repo_dir, 1826 "iota", 1827 "A/B/lambda") 1828 expected_output = UnorderedOutput(["Removed lock on '/iota'.\n", 1829 "Removed lock on '/A/B/lambda'.\n"]) 1830 1831 svntest.verify.verify_outputs( 1832 "Unexpected output while running 'svnadmin rmlocks'.", 1833 output, [], expected_output, None) 1834 1835#---------------------------------------------------------------------- 1836@Issue(3734) 1837def load_ranges(sbox): 1838 "'svnadmin load --revision X:Y'" 1839 1840 ## See https://issues.apache.org/jira/browse/SVN-3734. ## 1841 sbox.build(empty=True) 1842 1843 dumpfile_location = os.path.join(os.path.dirname(sys.argv[0]), 1844 'svnadmin_tests_data', 1845 'skeleton_repos.dump') 1846 dumplines = svntest.actions.load_dumpfile(dumpfile_location) 1847 1848 # Load our dumpfile, 2 revisions at a time, verifying that we have 1849 # the correct youngest revision after each load. 1850 load_dumpstream(sbox, dumplines, '-r0:2') 1851 svntest.actions.run_and_verify_svnlook(['2\n'], 1852 None, 'youngest', sbox.repo_dir) 1853 load_dumpstream(sbox, dumplines, '-r3:4') 1854 svntest.actions.run_and_verify_svnlook(['4\n'], 1855 None, 'youngest', sbox.repo_dir) 1856 load_dumpstream(sbox, dumplines, '-r5:6') 1857 svntest.actions.run_and_verify_svnlook(['6\n'], 1858 None, 'youngest', sbox.repo_dir) 1859 1860 # There are ordering differences in the property blocks. 1861 if (svntest.main.options.server_minor_version < 6): 1862 temp = [] 1863 1864 for line in dumplines: 1865 if not "Text-content-sha1:" in line: 1866 temp.append(line) 1867 1868 expected_dump = UnorderedOutput(temp) 1869 else: 1870 expected_dump = UnorderedOutput(dumplines) 1871 1872 new_dumpdata = svntest.actions.run_and_verify_dump(sbox.repo_dir) 1873 svntest.verify.compare_and_display_lines("Dump files", "DUMP", 1874 expected_dump, new_dumpdata) 1875 1876@SkipUnless(svntest.main.is_fs_type_fsfs) 1877def hotcopy_incremental(sbox): 1878 "'svnadmin hotcopy --incremental PATH .'" 1879 sbox.build() 1880 1881 backup_dir, backup_url = sbox.add_repo_path('backup') 1882 os.mkdir(backup_dir) 1883 cwd = os.getcwd() 1884 1885 for i in [1, 2, 3]: 1886 os.chdir(backup_dir) 1887 svntest.actions.run_and_verify_svnadmin( 1888 None, [], 1889 "hotcopy", "--incremental", os.path.join(cwd, sbox.repo_dir), '.') 1890 1891 os.chdir(cwd) 1892 1893 check_hotcopy_fsfs(sbox.repo_dir, backup_dir) 1894 1895 if i < 3: 1896 sbox.simple_mkdir("newdir-%i" % i) 1897 sbox.simple_commit() 1898 1899@SkipUnless(svntest.main.is_fs_type_fsfs) 1900@SkipUnless(svntest.main.fs_has_pack) 1901def hotcopy_incremental_packed(sbox): 1902 "'svnadmin hotcopy --incremental' with packing" 1903 1904 # Configure two files per shard to trigger packing. 1905 sbox.build() 1906 patch_format(sbox.repo_dir, shard_size=2) 1907 1908 backup_dir, backup_url = sbox.add_repo_path('backup') 1909 os.mkdir(backup_dir) 1910 cwd = os.getcwd() 1911 1912 # Pack revisions 0 and 1 if not already packed. 1913 if not (svntest.main.is_fs_type_fsfs and svntest.main.options.fsfs_packing 1914 and svntest.main.options.fsfs_sharding == 2): 1915 svntest.actions.run_and_verify_svnadmin( 1916 ['Packing revisions in shard 0...done.\n'], [], "pack", 1917 os.path.join(cwd, sbox.repo_dir)) 1918 1919 # Commit 5 more revs, hotcopy and pack after each commit. 1920 for i in [1, 2, 3, 4, 5]: 1921 os.chdir(backup_dir) 1922 svntest.actions.run_and_verify_svnadmin( 1923 None, [], 1924 "hotcopy", "--incremental", os.path.join(cwd, sbox.repo_dir), '.') 1925 1926 os.chdir(cwd) 1927 1928 check_hotcopy_fsfs(sbox.repo_dir, backup_dir) 1929 1930 if i < 5: 1931 sbox.simple_mkdir("newdir-%i" % i) 1932 sbox.simple_commit() 1933 if (svntest.main.is_fs_type_fsfs and not svntest.main.options.fsfs_packing 1934 and not i % 2): 1935 expected_output = ['Packing revisions in shard %d...done.\n' % (i/2)] 1936 else: 1937 expected_output = [] 1938 svntest.actions.run_and_verify_svnadmin( 1939 expected_output, [], "pack", os.path.join(cwd, sbox.repo_dir)) 1940 1941 1942def locking(sbox): 1943 "svnadmin lock tests" 1944 sbox.build(create_wc=False) 1945 1946 comment_path = os.path.join(svntest.main.temp_dir, "comment") 1947 svntest.main.file_write(comment_path, "dummy comment") 1948 1949 invalid_comment_path = os.path.join(svntest.main.temp_dir, "invalid_comment") 1950 svntest.main.file_write(invalid_comment_path, "character is invalid") 1951 1952 # Test illegal character in comment file. 1953 expected_error = ".*svnadmin: E130004:.*" 1954 svntest.actions.run_and_verify_svnadmin(None, 1955 expected_error, "lock", 1956 sbox.repo_dir, 1957 "iota", "jrandom", 1958 invalid_comment_path) 1959 1960 # Test locking path with --bypass-hooks 1961 expected_output = "'/iota' locked by user 'jrandom'." 1962 svntest.actions.run_and_verify_svnadmin(expected_output, 1963 None, "lock", 1964 sbox.repo_dir, 1965 "iota", "jrandom", 1966 comment_path, 1967 "--bypass-hooks") 1968 1969 # Remove lock 1970 svntest.actions.run_and_verify_svnadmin(None, 1971 None, "rmlocks", 1972 sbox.repo_dir, "iota") 1973 1974 # Test locking path without --bypass-hooks 1975 expected_output = "'/iota' locked by user 'jrandom'." 1976 svntest.actions.run_and_verify_svnadmin(expected_output, 1977 None, "lock", 1978 sbox.repo_dir, 1979 "iota", "jrandom", 1980 comment_path) 1981 1982 # Test locking already locked path. 1983 expected_error = ".*svnadmin: E160035:.*" 1984 svntest.actions.run_and_verify_svnadmin(None, 1985 expected_error, "lock", 1986 sbox.repo_dir, 1987 "iota", "jrandom", 1988 comment_path) 1989 1990 # Test locking non-existent path. 1991 expected_error = ".*svnadmin: E160013:.*" 1992 svntest.actions.run_and_verify_svnadmin(None, 1993 expected_error, "lock", 1994 sbox.repo_dir, 1995 "non-existent", "jrandom", 1996 comment_path) 1997 1998 # Test locking a path while specifying a lock token. 1999 expected_output = "'/A/D/G/rho' locked by user 'jrandom'." 2000 lock_token = "opaquelocktoken:01234567-89ab-cdef-89ab-cdef01234567" 2001 svntest.actions.run_and_verify_svnadmin(expected_output, 2002 None, "lock", 2003 sbox.repo_dir, 2004 "A/D/G/rho", "jrandom", 2005 comment_path, lock_token) 2006 2007 # Test unlocking a path, but provide the wrong lock token. 2008 expected_error = ".*svnadmin: E160040:.*" 2009 wrong_lock_token = "opaquelocktoken:12345670-9ab8-defc-9ab8-def01234567c" 2010 svntest.actions.run_and_verify_svnadmin(None, 2011 expected_error, "unlock", 2012 sbox.repo_dir, 2013 "A/D/G/rho", "jrandom", 2014 wrong_lock_token) 2015 2016 # Test unlocking the path again, but this time provide the correct 2017 # lock token. 2018 expected_output = "'/A/D/G/rho' unlocked by user 'jrandom'." 2019 svntest.actions.run_and_verify_svnadmin(expected_output, 2020 None, "unlock", 2021 sbox.repo_dir, 2022 "A/D/G/rho", "jrandom", 2023 lock_token) 2024 2025 # Install lock/unlock prevention hooks. 2026 hook_path = svntest.main.get_pre_lock_hook_path(sbox.repo_dir) 2027 svntest.main.create_python_hook_script(hook_path, 'import sys; sys.exit(1)') 2028 hook_path = svntest.main.get_pre_unlock_hook_path(sbox.repo_dir) 2029 svntest.main.create_python_hook_script(hook_path, 'import sys; sys.exit(1)') 2030 2031 # Test locking a path. Don't use --bypass-hooks, though, as we wish 2032 # to verify that hook script is really getting executed. 2033 expected_error = ".*svnadmin: E165001:.*" 2034 svntest.actions.run_and_verify_svnadmin(None, 2035 expected_error, "lock", 2036 sbox.repo_dir, 2037 "iota", "jrandom", 2038 comment_path) 2039 2040 # Fetch the lock token for our remaining locked path. (We didn't 2041 # explicitly set it, so it will vary from test run to test run.) 2042 exit_code, output, errput = svntest.main.run_svnadmin("lslocks", 2043 sbox.repo_dir, 2044 "iota") 2045 iota_token = None 2046 for line in output: 2047 if line.startswith("UUID Token: opaquelocktoken:"): 2048 iota_token = line[12:].rstrip() 2049 break 2050 if iota_token is None: 2051 raise svntest.Failure("Unable to lookup lock token for 'iota'") 2052 2053 # Try to unlock a path while providing the correct lock token but 2054 # with a preventative hook in place. 2055 expected_error = ".*svnadmin: E165001:.*" 2056 svntest.actions.run_and_verify_svnadmin(None, 2057 expected_error, "unlock", 2058 sbox.repo_dir, 2059 "iota", "jrandom", 2060 iota_token) 2061 2062 # Finally, use --bypass-hooks to unlock the path (again using the 2063 # correct lock token). 2064 expected_output = "'/iota' unlocked by user 'jrandom'." 2065 svntest.actions.run_and_verify_svnadmin(expected_output, 2066 None, "unlock", 2067 "--bypass-hooks", 2068 sbox.repo_dir, 2069 "iota", "jrandom", 2070 iota_token) 2071 2072 2073@SkipUnless(svntest.main.is_threaded_python) 2074@Issue(4129) 2075def mergeinfo_race(sbox): 2076 "concurrent mergeinfo commits invalidate pred-count" 2077 sbox.build() 2078 2079 # This test exercises two commit-time race condition bugs: 2080 # 2081 # (a) metadata corruption when concurrent commits change svn:mergeinfo (issue #4129) 2082 # (b) false positive SVN_ERR_FS_CONFLICT error with httpv1 commits 2083 # https://mail-archives.apache.org/mod_mbox/subversion-dev/201507.mbox/%3C20150731234536.GA5395@tarsus.local2%3E 2084 # 2085 # Both bugs are timing-dependent and might not reproduce 100% of the time. 2086 2087 wc_dir = sbox.wc_dir 2088 wc2_dir = sbox.add_wc_path('2') 2089 2090 ## Create wc2. 2091 svntest.main.run_svn(None, 'checkout', '-q', sbox.repo_url, wc2_dir) 2092 2093 ## Some random edits. 2094 svntest.main.run_svn(None, 'mkdir', sbox.ospath('d1', wc_dir)) 2095 svntest.main.run_svn(None, 'mkdir', sbox.ospath('d2', wc2_dir)) 2096 2097 ## Set random mergeinfo properties. 2098 svntest.main.run_svn(None, 'ps', 'svn:mergeinfo', '/P:42', sbox.ospath('A', wc_dir)) 2099 svntest.main.run_svn(None, 'ps', 'svn:mergeinfo', '/Q:42', sbox.ospath('iota', wc2_dir)) 2100 2101 def makethread(some_wc_dir): 2102 def worker(): 2103 svntest.main.run_svn(None, 'commit', '-mm', some_wc_dir) 2104 return worker 2105 2106 t1 = threading.Thread(None, makethread(wc_dir)) 2107 t2 = threading.Thread(None, makethread(wc2_dir)) 2108 2109 # t2 will trigger the issue #4129 sanity check in fs_fs.c 2110 t1.start(); t2.start() 2111 2112 t1.join(); t2.join() 2113 2114 # Crude attempt to make sure everything worked. 2115 # TODO: better way to catch exceptions in the thread 2116 if svntest.actions.run_and_parse_info(sbox.repo_url)[0]['Revision'] != '3': 2117 raise svntest.Failure("one or both commits failed") 2118 2119 2120@Issue(4213) 2121@Skip(svntest.main.is_fs_type_fsx) 2122def recover_old_empty(sbox): 2123 "recover empty --compatible-version=1.3" 2124 sbox.build(create_wc=False, empty=True, minor_version=3) 2125 svntest.actions.run_and_verify_svnadmin(None, [], 2126 "recover", sbox.repo_dir) 2127 2128 2129@SkipUnless(svntest.main.is_fs_type_fsfs) 2130def verify_keep_going(sbox): 2131 "svnadmin verify --keep-going test" 2132 2133 # No support for modifying pack files 2134 if svntest.main.options.fsfs_packing: 2135 raise svntest.Skip('fsfs packing set') 2136 2137 sbox.build(create_wc = False) 2138 repo_url = sbox.repo_url 2139 B_url = sbox.repo_url + '/B' 2140 C_url = sbox.repo_url + '/C' 2141 2142 # Create A/B/E/bravo in r2. 2143 svntest.actions.run_and_verify_svn(None, [], 2144 'mkdir', '-m', 'log_msg', 2145 B_url) 2146 2147 svntest.actions.run_and_verify_svn(None, [], 2148 'mkdir', '-m', 'log_msg', 2149 C_url) 2150 2151 r2 = fsfs_file(sbox.repo_dir, 'revs', '2') 2152 fp = open(r2, 'r+b') 2153 fp.write(b"inserting junk to corrupt the rev") 2154 fp.close() 2155 exit_code, output, errput = svntest.main.run_svnadmin("verify", 2156 "--keep-going", 2157 sbox.repo_dir) 2158 2159 exp_out = svntest.verify.RegexListOutput([".*Verified revision 0.", 2160 ".*Verified revision 1.", 2161 ".*", 2162 ".*Summary.*", 2163 ".*r2: E160004:.*", 2164 ".*r2: E160004:.*", 2165 ".*r3: E160004:.*", 2166 ".*r3: E160004:.*"]) 2167 2168 if (svntest.main.fs_has_rep_sharing()): 2169 exp_out.insert(0, ".*Verifying.*metadata.*") 2170 2171 exp_err = svntest.verify.RegexListOutput([".*Error verifying revision 2.", 2172 "svnadmin: E160004:.*", 2173 "svnadmin: E160004:.*", 2174 ".*Error verifying revision 3.", 2175 "svnadmin: E160004:.*", 2176 "svnadmin: E160004:.*", 2177 "svnadmin: E205012:.*"], False) 2178 2179 if (svntest.main.is_fs_log_addressing()): 2180 exp_err.insert(0, ".*Error verifying repository metadata.") 2181 exp_err.insert(1, "svnadmin: E160004:.*") 2182 2183 if svntest.verify.verify_outputs("Unexpected error while running 'svnadmin verify'.", 2184 output, errput, exp_out, exp_err): 2185 raise svntest.Failure 2186 2187 exit_code, output, errput = svntest.main.run_svnadmin("verify", 2188 sbox.repo_dir) 2189 2190 if (svntest.main.is_fs_log_addressing()): 2191 exp_out = svntest.verify.RegexListOutput([".*Verifying metadata at revision 0.*"]) 2192 else: 2193 exp_out = svntest.verify.RegexListOutput([".*Verified revision 0.", 2194 ".*Verified revision 1."]) 2195 if (svntest.main.fs_has_rep_sharing()): 2196 exp_out.insert(0, ".*Verifying repository metadata.*") 2197 2198 if (svntest.main.is_fs_log_addressing()): 2199 exp_err = svntest.verify.RegexListOutput([ 2200 ".*Error verifying repository metadata.", 2201 "svnadmin: E160004:.*"], False) 2202 else: 2203 exp_err = svntest.verify.RegexListOutput([".*Error verifying revision 2.", 2204 "svnadmin: E160004:.*", 2205 "svnadmin: E160004:.*"], False) 2206 2207 if svntest.verify.verify_outputs("Unexpected error while running 'svnadmin verify'.", 2208 output, errput, exp_out, exp_err): 2209 raise svntest.Failure 2210 2211 2212 exit_code, output, errput = svntest.main.run_svnadmin("verify", 2213 "--quiet", 2214 sbox.repo_dir) 2215 2216 if (svntest.main.is_fs_log_addressing()): 2217 exp_err = svntest.verify.RegexListOutput([ 2218 ".*Error verifying repository metadata.", 2219 "svnadmin: E160004:.*"], False) 2220 else: 2221 exp_err = svntest.verify.RegexListOutput([".*Error verifying revision 2.", 2222 "svnadmin: E160004:.*", 2223 "svnadmin: E160004:.*"], False) 2224 2225 if svntest.verify.verify_outputs("Output of 'svnadmin verify' is unexpected.", 2226 None, errput, None, exp_err): 2227 raise svntest.Failure 2228 2229 # Don't leave a corrupt repository 2230 svntest.main.safe_rmtree(sbox.repo_dir, True) 2231 2232 2233@SkipUnless(svntest.main.is_fs_type_fsfs) 2234def verify_keep_going_quiet(sbox): 2235 "svnadmin verify --keep-going --quiet test" 2236 2237 # No support for modifying pack files 2238 if svntest.main.options.fsfs_packing: 2239 raise svntest.Skip('fsfs packing set') 2240 2241 sbox.build(create_wc = False) 2242 repo_url = sbox.repo_url 2243 B_url = sbox.repo_url + '/B' 2244 C_url = sbox.repo_url + '/C' 2245 2246 # Create A/B/E/bravo in r2. 2247 svntest.actions.run_and_verify_svn(None, [], 2248 'mkdir', '-m', 'log_msg', 2249 B_url) 2250 2251 svntest.actions.run_and_verify_svn(None, [], 2252 'mkdir', '-m', 'log_msg', 2253 C_url) 2254 2255 r2 = fsfs_file(sbox.repo_dir, 'revs', '2') 2256 fp = open(r2, 'r+b') 2257 fp.write(b"inserting junk to corrupt the rev") 2258 fp.close() 2259 2260 exit_code, output, errput = svntest.main.run_svnadmin("verify", 2261 "--keep-going", 2262 "--quiet", 2263 sbox.repo_dir) 2264 2265 exp_err = svntest.verify.RegexListOutput([".*Error verifying revision 2.", 2266 "svnadmin: E160004:.*", 2267 "svnadmin: E160004:.*", 2268 ".*Error verifying revision 3.", 2269 "svnadmin: E160004:.*", 2270 "svnadmin: E160004:.*", 2271 "svnadmin: E205012:.*"], False) 2272 2273 # Insert another expected error from checksum verification 2274 if (svntest.main.is_fs_log_addressing()): 2275 exp_err.insert(0, ".*Error verifying repository metadata.") 2276 exp_err.insert(1, "svnadmin: E160004:.*") 2277 2278 if svntest.verify.verify_outputs( 2279 "Unexpected error while running 'svnadmin verify'.", 2280 output, errput, None, exp_err): 2281 raise svntest.Failure 2282 2283 # Don't leave a corrupt repository 2284 svntest.main.safe_rmtree(sbox.repo_dir, True) 2285 2286 2287@SkipUnless(svntest.main.is_fs_type_fsfs) 2288def verify_invalid_path_changes(sbox): 2289 "detect invalid changed path list entries" 2290 2291 # No support for modifying pack files 2292 if svntest.main.options.fsfs_packing: 2293 raise svntest.Skip('fsfs packing set') 2294 2295 sbox.build(create_wc = False) 2296 repo_url = sbox.repo_url 2297 2298 # Create a number of revisions each adding a single path 2299 for r in range(2,20): 2300 svntest.actions.run_and_verify_svn(None, [], 2301 'mkdir', '-m', 'log_msg', 2302 sbox.repo_url + '/B' + str(r)) 2303 2304 # modify every other revision to make sure that errors are not simply 2305 # "carried over" but that all corrupts we get detected independently 2306 2307 # add existing node 2308 set_changed_path_list(sbox, 2, 2309 b"_0.0.t1-1 add-dir false false /A\n\n") 2310 2311 # add into non-existent parent 2312 set_changed_path_list(sbox, 4, 2313 b"_0.0.t3-2 add-dir false false /C/X\n\n") 2314 2315 # del non-existent node 2316 set_changed_path_list(sbox, 6, 2317 b"_0.0.t5-2 delete-dir false false /C\n\n") 2318 2319 # del existent node of the wrong kind 2320 # 2321 # THIS WILL NOT BE DETECTED 2322 # since dump mechanism and file don't care about the types of deleted nodes 2323 set_changed_path_list(sbox, 8, 2324 b"_0.0.t7-2 delete-file false false /B3\n\n") 2325 2326 # copy from non-existent node 2327 set_changed_path_list(sbox, 10, 2328 b"_0.0.t9-2 add-dir false false /B10\n6 /B8\n") 2329 2330 # copy from existing node of the wrong kind 2331 set_changed_path_list(sbox, 12, 2332 b"_0.0.t11-2 add-file false false /B12\n9 /B8\n") 2333 2334 # modify non-existent node 2335 set_changed_path_list(sbox, 14, 2336 b"_0.0.t13-2 modify-file false false /A/D/H/foo\n\n") 2337 2338 # modify existent node of the wrong kind 2339 set_changed_path_list(sbox, 16, 2340 b"_0.0.t15-2 modify-file false false /B12\n\n") 2341 2342 # replace non-existent node 2343 set_changed_path_list(sbox, 18, 2344 b"_0.0.t17-2 replace-file false false /A/D/H/foo\n\n") 2345 2346 # find corruptions 2347 exit_code, output, errput = svntest.main.run_svnadmin("verify", 2348 "--keep-going", 2349 sbox.repo_dir) 2350 2351 # Errors generated by FSFS when CHANGED_PATHS is not forced into emulation 2352 exp_out1 = svntest.verify.RegexListOutput([".*Verified revision 0.", 2353 ".*Verified revision 1.", 2354 ".*Verified revision 3.", 2355 ".*Verified revision 5.", 2356 ".*Verified revision 7.", 2357 ".*Verified revision 8.", 2358 ".*Verified revision 9.", 2359 ".*Verified revision 11.", 2360 ".*Verified revision 13.", 2361 ".*Verified revision 15.", 2362 ".*Verified revision 17.", 2363 ".*Verified revision 19.", 2364 ".*", 2365 ".*Summary.*", 2366 ".*r2: E160020:.*", 2367 ".*r2: E160020:.*", 2368 ".*r4: E160013:.*", 2369 ".*r6: E160013:.*", 2370 ".*r6: E160013:.*", 2371 ".*r10: E160013:.*", 2372 ".*r10: E160013:.*", 2373 ".*r12: E145001:.*", 2374 ".*r12: E145001:.*", 2375 ".*r14: E160013:.*", 2376 ".*r14: E160013:.*", 2377 ".*r16: E145001:.*", 2378 ".*r16: E145001:.*", 2379 ".*r18: E160013:.*", 2380 ".*r18: E160013:.*"]) 2381 2382 exp_err1 = svntest.verify.RegexListOutput([".*Error verifying revision 2.", 2383 "svnadmin: E160020:.*", 2384 "svnadmin: E160020:.*", 2385 ".*Error verifying revision 4.", 2386 "svnadmin: E160013:.*", 2387 ".*Error verifying revision 6.", 2388 "svnadmin: E160013:.*", 2389 "svnadmin: E160013:.*", 2390 ".*Error verifying revision 10.", 2391 "svnadmin: E160013:.*", 2392 "svnadmin: E160013:.*", 2393 ".*Error verifying revision 12.", 2394 "svnadmin: E145001:.*", 2395 "svnadmin: E145001:.*", 2396 ".*Error verifying revision 14.", 2397 "svnadmin: E160013:.*", 2398 "svnadmin: E160013:.*", 2399 ".*Error verifying revision 16.", 2400 "svnadmin: E145001:.*", 2401 "svnadmin: E145001:.*", 2402 ".*Error verifying revision 18.", 2403 "svnadmin: E160013:.*", 2404 "svnadmin: E160013:.*", 2405 "svnadmin: E205012:.*"], False) 2406 2407 # If CHANGED_PATHS is emulated, FSFS fails earlier, generating fewer 2408 # of the same messages per revision. 2409 exp_out2 = svntest.verify.RegexListOutput([".*Verified revision 0.", 2410 ".*Verified revision 1.", 2411 ".*Verified revision 3.", 2412 ".*Verified revision 5.", 2413 ".*Verified revision 7.", 2414 ".*Verified revision 8.", 2415 ".*Verified revision 9.", 2416 ".*Verified revision 11.", 2417 ".*Verified revision 13.", 2418 ".*Verified revision 15.", 2419 ".*Verified revision 17.", 2420 ".*Verified revision 19.", 2421 ".*", 2422 ".*Summary.*", 2423 ".*r2: E160020:.*", 2424 ".*r2: E160020:.*", 2425 ".*r4: E160013:.*", 2426 ".*r6: E160013:.*", 2427 ".*r10: E160013:.*", 2428 ".*r10: E160013:.*", 2429 ".*r12: E145001:.*", 2430 ".*r12: E145001:.*", 2431 ".*r14: E160013:.*", 2432 ".*r16: E145001:.*", 2433 ".*r16: E145001:.*", 2434 ".*r18: E160013:.*"]) 2435 2436 exp_err2 = svntest.verify.RegexListOutput([".*Error verifying revision 2.", 2437 "svnadmin: E160020:.*", 2438 "svnadmin: E160020:.*", 2439 ".*Error verifying revision 4.", 2440 "svnadmin: E160013:.*", 2441 ".*Error verifying revision 6.", 2442 "svnadmin: E160013:.*", 2443 ".*Error verifying revision 10.", 2444 "svnadmin: E160013:.*", 2445 "svnadmin: E160013:.*", 2446 ".*Error verifying revision 12.", 2447 "svnadmin: E145001:.*", 2448 "svnadmin: E145001:.*", 2449 ".*Error verifying revision 14.", 2450 "svnadmin: E160013:.*", 2451 ".*Error verifying revision 16.", 2452 "svnadmin: E145001:.*", 2453 "svnadmin: E145001:.*", 2454 ".*Error verifying revision 18.", 2455 "svnadmin: E160013:.*", 2456 "svnadmin: E205012:.*"], False) 2457 2458 # Determine which pattern to use. 2459 # Note that index() will throw an exception if the string can't be found. 2460 try: 2461 rev6_line = errput.index('* Error verifying revision 6.\n'); 2462 rev10_line = errput.index('* Error verifying revision 10.\n'); 2463 2464 error_count = 0 2465 for line in errput[rev6_line+1:rev10_line]: 2466 if "svnadmin: E" in line: 2467 error_count = error_count + 1 2468 2469 if error_count == 1: 2470 exp_out = exp_out2 2471 exp_err = exp_err2 2472 else: 2473 exp_out = exp_out1 2474 exp_err = exp_err1 2475 except ValueError: 2476 exp_out = exp_out1 2477 exp_err = exp_err1 2478 2479 if (svntest.main.fs_has_rep_sharing()): 2480 exp_out.insert(0, ".*Verifying.*metadata.*") 2481 if svntest.main.options.fsfs_sharding is not None: 2482 for x in range(0, 19 / svntest.main.options.fsfs_sharding): 2483 exp_out.insert(0, ".*Verifying.*metadata.*") 2484 if svntest.main.is_fs_log_addressing(): 2485 exp_out.insert(0, ".*Verifying.*metadata.*") 2486 2487 if svntest.verify.verify_outputs("Unexpected error while running 'svnadmin verify'.", 2488 output, errput, exp_out, exp_err): 2489 raise svntest.Failure 2490 2491 exit_code, output, errput = svntest.main.run_svnadmin("verify", 2492 sbox.repo_dir) 2493 2494 exp_out = svntest.verify.RegexListOutput([".*Verified revision 0.", 2495 ".*Verified revision 1."]) 2496 exp_err = svntest.verify.RegexListOutput([".*Error verifying revision 2.", 2497 "svnadmin: E160020:.*", 2498 "svnadmin: E160020:.*"], False) 2499 2500 if (svntest.main.fs_has_rep_sharing()): 2501 exp_out.insert(0, ".*Verifying.*metadata.*") 2502 if svntest.main.options.fsfs_sharding is not None: 2503 for x in range(0, 19 / svntest.main.options.fsfs_sharding): 2504 exp_out.insert(0, ".*Verifying.*metadata.*") 2505 if svntest.main.is_fs_log_addressing(): 2506 exp_out.insert(0, ".*Verifying.*metadata.*") 2507 2508 if svntest.verify.verify_outputs("Unexpected error while running 'svnadmin verify'.", 2509 output, errput, exp_out, exp_err): 2510 raise svntest.Failure 2511 2512 2513 exit_code, output, errput = svntest.main.run_svnadmin("verify", 2514 "--quiet", 2515 sbox.repo_dir) 2516 2517 exp_out = [] 2518 exp_err = svntest.verify.RegexListOutput([".*Error verifying revision 2.", 2519 "svnadmin: E160020:.*", 2520 "svnadmin: E160020:.*"], False) 2521 2522 if svntest.verify.verify_outputs("Output of 'svnadmin verify' is unexpected.", 2523 output, errput, exp_out, exp_err): 2524 raise svntest.Failure 2525 2526 # Don't leave a corrupt repository 2527 svntest.main.safe_rmtree(sbox.repo_dir, True) 2528 2529 2530def verify_denormalized_names(sbox): 2531 "detect denormalized names and name collisions" 2532 2533 sbox.build(create_wc=False, empty=True) 2534 2535 dumpfile_location = os.path.join(os.path.dirname(sys.argv[0]), 2536 'svnadmin_tests_data', 2537 'normalization_check.dump') 2538 load_dumpstream(sbox, svntest.actions.load_dumpfile(dumpfile_location)) 2539 2540 exit_code, output, errput = svntest.main.run_svnadmin( 2541 "verify", "--check-normalization", sbox.repo_dir) 2542 2543 expected_output_regex_list = [ 2544 ".*Verified revision 0.", 2545 ".*Verified revision 1.", 2546 ".*Verified revision 2.", 2547 ".*Verified revision 3.", 2548 # A/{Eacute}/{aring}lpha 2549 "WARNING 0x0003: Duplicate representation of path 'A/.*/.*lpha'", 2550 ".*Verified revision 4.", 2551 ".*Verified revision 5.", 2552 # Q/{aring}lpha 2553 "WARNING 0x0004: Duplicate representation of path '/Q/.*lpha'" 2554 # A/{Eacute} 2555 " in svn:mergeinfo property of 'A/.*'", 2556 ".*Verified revision 6.", 2557 ".*Verified revision 7."] 2558 2559 # The BDB backend doesn't do global metadata verification. 2560 if (svntest.main.fs_has_rep_sharing() and not svntest.main.is_fs_type_bdb()): 2561 expected_output_regex_list.insert(0, ".*Verifying repository metadata.*") 2562 2563 if svntest.main.options.fsfs_sharding is not None: 2564 for x in range(0, 7 / svntest.main.options.fsfs_sharding): 2565 expected_output_regex_list.insert(0, ".*Verifying.*metadata.*") 2566 2567 if svntest.main.is_fs_log_addressing(): 2568 expected_output_regex_list.insert(0, ".* Verifying metadata at revision 0.*") 2569 2570 exp_out = svntest.verify.RegexListOutput(expected_output_regex_list) 2571 exp_err = svntest.verify.ExpectedOutput([]) 2572 2573 svntest.verify.verify_outputs( 2574 "Unexpected error while running 'svnadmin verify'.", 2575 output, errput, exp_out, exp_err) 2576 2577 2578@SkipUnless(svntest.main.is_fs_type_fsfs) 2579def fsfs_recover_old_non_empty(sbox): 2580 "fsfs recover non-empty --compatible-version=1.3" 2581 2582 # Around trunk@1560210, 'svnadmin recover' wrongly errored out 2583 # for the --compatible-version=1.3 Greek tree repository: 2584 # svnadmin: E200002: Serialized hash missing terminator 2585 2586 sbox.build(create_wc=False, minor_version=3) 2587 svntest.actions.run_and_verify_svnadmin(None, [], "recover", 2588 sbox.repo_dir) 2589 2590 2591@SkipUnless(svntest.main.is_fs_type_fsfs) 2592def fsfs_hotcopy_old_non_empty(sbox): 2593 "fsfs hotcopy non-empty --compatible-version=1.3" 2594 2595 # Around trunk@1560210, 'svnadmin hotcopy' wrongly errored out 2596 # for the --compatible-version=1.3 Greek tree repository: 2597 # svnadmin: E160006: No such revision 1 2598 2599 sbox.build(create_wc=False, minor_version=3) 2600 backup_dir, backup_url = sbox.add_repo_path('backup') 2601 svntest.actions.run_and_verify_svnadmin(None, [], "hotcopy", 2602 sbox.repo_dir, backup_dir) 2603 2604 check_hotcopy_fsfs(sbox.repo_dir, backup_dir) 2605 2606 2607def load_ignore_dates(sbox): 2608 "svnadmin load --ignore-dates" 2609 2610 # All revisions in the loaded repository should come after this time. 2611 start_time = time.localtime() 2612 time.sleep(1) 2613 2614 sbox.build(create_wc=False, empty=True) 2615 2616 dumpfile_location = os.path.join(os.path.dirname(sys.argv[0]), 2617 'svnadmin_tests_data', 2618 'skeleton_repos.dump') 2619 dumpfile_skeleton = svntest.actions.load_dumpfile(dumpfile_location) 2620 2621 load_dumpstream(sbox, dumpfile_skeleton, '--ignore-dates') 2622 svntest.actions.run_and_verify_svnlook(['6\n'], 2623 None, 'youngest', sbox.repo_dir) 2624 for rev in range(1, 6): 2625 exit_code, output, errput = svntest.main.run_svnlook('date', '-r', rev, 2626 sbox.repo_dir) 2627 if errput: 2628 raise SVNUnexpectedStderr(errput) 2629 rev_time = time.strptime(output[0].rstrip()[:19], '%Y-%m-%d %H:%M:%S') 2630 if rev_time < start_time: 2631 raise svntest.Failure("Revision time for r%d older than load start time\n" 2632 " rev_time: %s\n" 2633 " start_time: %s" 2634 % (rev, str(rev_time), str(start_time))) 2635 2636 2637@SkipUnless(svntest.main.is_fs_type_fsfs) 2638def fsfs_hotcopy_old_with_id_changes(sbox): 2639 "fsfs hotcopy old with node-id and copy-id changes" 2640 2641 # Around trunk@1573728, running 'svnadmin hotcopy' for the 2642 # --compatible-version=1.3 repository with certain node-id and copy-id 2643 # changes ended with mismatching db/current in source and destination: 2644 # 2645 # source: "2 l 1" destination: "2 k 1", 2646 # "3 l 2" "3 4 2" 2647 # (and so on...) 2648 # 2649 # We test this case by creating a --compatible-version=1.3 repository 2650 # and committing things that result in node-id and copy-id changes. 2651 # After every commit, we hotcopy the repository to a new destination 2652 # and check whether the source of the backup and the backup itself are 2653 # identical. We also maintain a separate --incremental backup, which 2654 # is updated and checked after every commit. 2655 sbox.build(create_wc=True, minor_version=3) 2656 2657 inc_backup_dir, inc_backup_url = sbox.add_repo_path('incremental-backup') 2658 2659 # r1 = Initial greek tree sandbox. 2660 backup_dir, backup_url = sbox.add_repo_path('backup-after-r1') 2661 svntest.actions.run_and_verify_svnadmin(None, [], "hotcopy", 2662 sbox.repo_dir, backup_dir) 2663 svntest.actions.run_and_verify_svnadmin(None, [], "hotcopy", 2664 "--incremental", 2665 sbox.repo_dir, inc_backup_dir) 2666 check_hotcopy_fsfs(sbox.repo_dir, backup_dir) 2667 check_hotcopy_fsfs(sbox.repo_dir, inc_backup_dir) 2668 2669 # r2 = Add a new property. 2670 sbox.simple_propset('foo', 'bar', 'A/mu') 2671 sbox.simple_commit(message='r2') 2672 2673 backup_dir, backup_url = sbox.add_repo_path('backup-after-r2') 2674 svntest.actions.run_and_verify_svnadmin(None, [], "hotcopy", 2675 sbox.repo_dir, backup_dir) 2676 svntest.actions.run_and_verify_svnadmin(None, [], "hotcopy", 2677 "--incremental", 2678 sbox.repo_dir, inc_backup_dir) 2679 check_hotcopy_fsfs(sbox.repo_dir, backup_dir) 2680 check_hotcopy_fsfs(sbox.repo_dir, inc_backup_dir) 2681 2682 # r3 = Copy a file. 2683 sbox.simple_copy('A/B/E', 'A/B/E1') 2684 sbox.simple_commit(message='r3') 2685 2686 backup_dir, backup_url = sbox.add_repo_path('backup-after-r3') 2687 svntest.actions.run_and_verify_svnadmin(None, [], "hotcopy", 2688 sbox.repo_dir, backup_dir) 2689 svntest.actions.run_and_verify_svnadmin(None, [], "hotcopy", 2690 "--incremental", 2691 sbox.repo_dir, inc_backup_dir) 2692 check_hotcopy_fsfs(sbox.repo_dir, backup_dir) 2693 check_hotcopy_fsfs(sbox.repo_dir, inc_backup_dir) 2694 2695 # r4 = Remove an existing file ... 2696 sbox.simple_rm('A/D/gamma') 2697 sbox.simple_commit(message='r4') 2698 2699 backup_dir, backup_url = sbox.add_repo_path('backup-after-r4') 2700 svntest.actions.run_and_verify_svnadmin(None, [], "hotcopy", 2701 sbox.repo_dir, backup_dir) 2702 svntest.actions.run_and_verify_svnadmin(None, [], "hotcopy", 2703 "--incremental", 2704 sbox.repo_dir, inc_backup_dir) 2705 check_hotcopy_fsfs(sbox.repo_dir, backup_dir) 2706 check_hotcopy_fsfs(sbox.repo_dir, inc_backup_dir) 2707 2708 # r5 = ...and replace it with a new file here. 2709 sbox.simple_add_text("This is the replaced file.\n", 'A/D/gamma') 2710 sbox.simple_commit(message='r5') 2711 2712 backup_dir, backup_url = sbox.add_repo_path('backup-after-r5') 2713 svntest.actions.run_and_verify_svnadmin(None, [], "hotcopy", 2714 sbox.repo_dir, backup_dir) 2715 svntest.actions.run_and_verify_svnadmin(None, [], "hotcopy", 2716 "--incremental", 2717 sbox.repo_dir, inc_backup_dir) 2718 check_hotcopy_fsfs(sbox.repo_dir, backup_dir) 2719 check_hotcopy_fsfs(sbox.repo_dir, inc_backup_dir) 2720 2721 # r6 = Add an entirely new file. 2722 sbox.simple_add_text('This is an entirely new file.\n', 'A/C/mu1') 2723 sbox.simple_commit(message='r6') 2724 2725 backup_dir, backup_url = sbox.add_repo_path('backup-after-r6') 2726 svntest.actions.run_and_verify_svnadmin(None, [], "hotcopy", 2727 sbox.repo_dir, backup_dir) 2728 svntest.actions.run_and_verify_svnadmin(None, [], "hotcopy", 2729 "--incremental", 2730 sbox.repo_dir, inc_backup_dir) 2731 check_hotcopy_fsfs(sbox.repo_dir, backup_dir) 2732 check_hotcopy_fsfs(sbox.repo_dir, inc_backup_dir) 2733 2734 # r7 = Change the content of the existing file (this changeset does 2735 # not bump the next-id and copy-id counters in the repository). 2736 sbox.simple_append('A/mu', 'This is change in the existing file.\n') 2737 sbox.simple_commit(message='r7') 2738 2739 backup_dir, backup_url = sbox.add_repo_path('backup-after-r7') 2740 svntest.actions.run_and_verify_svnadmin(None, [], "hotcopy", 2741 sbox.repo_dir, backup_dir) 2742 svntest.actions.run_and_verify_svnadmin(None, [], "hotcopy", 2743 "--incremental", 2744 sbox.repo_dir, inc_backup_dir) 2745 check_hotcopy_fsfs(sbox.repo_dir, backup_dir) 2746 check_hotcopy_fsfs(sbox.repo_dir, inc_backup_dir) 2747 2748 2749@SkipUnless(svntest.main.fs_has_pack) 2750def verify_packed(sbox): 2751 "verify packed with small shards" 2752 2753 # Configure two files per shard to trigger packing. 2754 sbox.build() 2755 patch_format(sbox.repo_dir, shard_size=2) 2756 2757 # Play with our greek tree. These changesets fall into two 2758 # separate shards with r2 and r3 being in shard 1 ... 2759 sbox.simple_append('iota', "Line.\n") 2760 sbox.simple_append('A/D/gamma', "Another line.\n") 2761 sbox.simple_commit(message='r2') 2762 sbox.simple_propset('foo', 'bar', 'iota') 2763 sbox.simple_propset('foo', 'baz', 'A/mu') 2764 sbox.simple_commit(message='r3') 2765 2766 # ...and r4 and r5 being in shard 2. 2767 sbox.simple_rm('A/C') 2768 sbox.simple_copy('A/B/E', 'A/B/E1') 2769 sbox.simple_move('A/mu', 'A/B/mu') 2770 sbox.simple_commit(message='r4') 2771 sbox.simple_propdel('foo', 'A/B/mu') 2772 sbox.simple_commit(message='r5') 2773 2774 if svntest.main.is_fs_type_fsfs and svntest.main.options.fsfs_packing: 2775 # With --fsfs-packing, everything is already packed and we 2776 # can skip this part. 2777 pass 2778 else: 2779 expected_output = ["Packing revisions in shard 0...done.\n", 2780 "Packing revisions in shard 1...done.\n", 2781 "Packing revisions in shard 2...done.\n"] 2782 svntest.actions.run_and_verify_svnadmin(expected_output, [], 2783 "pack", sbox.repo_dir) 2784 2785 if svntest.main.is_fs_log_addressing(): 2786 expected_output = ["* Verifying metadata at revision 0 ...\n", 2787 "* Verifying metadata at revision 2 ...\n", 2788 "* Verifying metadata at revision 4 ...\n", 2789 "* Verifying repository metadata ...\n", 2790 "* Verified revision 0.\n", 2791 "* Verified revision 1.\n", 2792 "* Verified revision 2.\n", 2793 "* Verified revision 3.\n", 2794 "* Verified revision 4.\n", 2795 "* Verified revision 5.\n"] 2796 else: 2797 expected_output = ["* Verifying repository metadata ...\n", 2798 "* Verified revision 0.\n", 2799 "* Verified revision 1.\n", 2800 "* Verified revision 2.\n", 2801 "* Verified revision 3.\n", 2802 "* Verified revision 4.\n", 2803 "* Verified revision 5.\n"] 2804 2805 svntest.actions.run_and_verify_svnadmin(expected_output, [], 2806 "verify", sbox.repo_dir) 2807 2808# Test that 'svnadmin freeze' is nestable. (For example, this ensures it 2809# won't take system-global locks, only repository-scoped ones.) 2810# 2811# This could be useful to easily freeze a small number of repositories at once. 2812# 2813# ### We don't actually test that freeze takes a write lock anywhere (not even 2814# ### in C tests.) 2815def freeze_freeze(sbox): 2816 "svnadmin freeze svnadmin freeze (some-cmd)" 2817 2818 sbox.build(create_wc=False, read_only=True) 2819 second_repo_dir, _ = sbox.add_repo_path('backup') 2820 svntest.actions.run_and_verify_svnadmin(None, [], "hotcopy", 2821 sbox.repo_dir, second_repo_dir) 2822 2823 if svntest.main.is_fs_type_fsx() or \ 2824 (svntest.main.is_fs_type_fsfs() and \ 2825 svntest.main.options.server_minor_version < 9): 2826 # FSFS repositories created with --compatible-version=1.8 and less 2827 # erroneously share the filesystem data (locks, shared transaction 2828 # data, ...) between hotcopy source and destination. This is fixed 2829 # for new FS formats, but in order to avoid a deadlock for old formats, 2830 # we have to manually assign a new UUID for the hotcopy destination. 2831 # As of trunk@1618024, the same applies to FSX repositories. 2832 svntest.actions.run_and_verify_svnadmin([], None, 2833 'setuuid', second_repo_dir) 2834 2835 svntest.actions.run_and_verify_svnadmin(None, [], 2836 'freeze', '--', sbox.repo_dir, 2837 svntest.main.svnadmin_binary, 'freeze', '--', second_repo_dir, 2838 sys.executable, '-c', 'True') 2839 2840 arg_file = sbox.get_tempname() 2841 svntest.main.file_write(arg_file, 2842 "%s\n%s\n" % (sbox.repo_dir, second_repo_dir)) 2843 2844 svntest.actions.run_and_verify_svnadmin(None, [], 2845 'freeze', '-F', arg_file, '--', 2846 sys.executable, '-c', 'True') 2847 2848def verify_metadata_only(sbox): 2849 "verify metadata only" 2850 2851 sbox.build(create_wc = False) 2852 exit_code, output, errput = svntest.main.run_svnadmin("verify", 2853 sbox.repo_dir, 2854 "--metadata-only") 2855 if errput: 2856 raise SVNUnexpectedStderr(errput) 2857 2858 # Unfortunately, older formats won't test as thoroughly than newer ones 2859 # resulting in different progress output. BDB will do a full check but 2860 # not produce any output. 2861 if svntest.main.is_fs_log_addressing(): 2862 svntest.verify.compare_and_display_lines( 2863 "Unexpected error while running 'svnadmin verify'.", 2864 'STDOUT', ["* Verifying metadata at revision 0 ...\n", 2865 "* Verifying repository metadata ...\n"], output) 2866 elif svntest.main.fs_has_rep_sharing() \ 2867 and not svntest.main.is_fs_type_bdb(): 2868 svntest.verify.compare_and_display_lines( 2869 "Unexpected error while running 'svnadmin verify'.", 2870 'STDOUT', ["* Verifying repository metadata ...\n"], output) 2871 else: 2872 svntest.verify.compare_and_display_lines( 2873 "Unexpected error while running 'svnadmin verify'.", 2874 'STDOUT', [], output) 2875 2876 2877@Skip(svntest.main.is_fs_type_bdb) 2878def verify_quickly(sbox): 2879 "verify quickly using metadata" 2880 2881 sbox.build(create_wc = False) 2882 rev_file = open(fsfs_file(sbox.repo_dir, 'revs', '1'), 'r+b') 2883 2884 # set new contents 2885 rev_file.seek(8) 2886 rev_file.write(b'#') 2887 rev_file.close() 2888 2889 exit_code, output, errput = svntest.main.run_svnadmin("verify", 2890 sbox.repo_dir, 2891 "--metadata-only") 2892 2893 # unfortunately, some backends needs to do more checks than other 2894 # resulting in different progress output 2895 if svntest.main.is_fs_log_addressing(): 2896 exp_out = svntest.verify.RegexListOutput([]) 2897 exp_err = svntest.verify.RegexListOutput(["svnadmin: E160004:.*"], False) 2898 else: 2899 exp_out = svntest.verify.RegexListOutput([]) 2900 exp_err = svntest.verify.RegexListOutput([]) 2901 2902 if (svntest.main.fs_has_rep_sharing()): 2903 exp_out.insert(0, ".*Verifying.*metadata.*") 2904 if svntest.verify.verify_outputs("Unexpected error while running 'svnadmin verify'.", 2905 output, errput, exp_out, exp_err): 2906 raise svntest.Failure 2907 2908 # Don't leave a corrupt repository 2909 svntest.main.safe_rmtree(sbox.repo_dir, True) 2910 2911 2912@SkipUnless(svntest.main.is_fs_type_fsfs) 2913@SkipUnless(svntest.main.fs_has_pack) 2914def fsfs_hotcopy_progress(sbox): 2915 "hotcopy progress reporting" 2916 2917 # Check how 'svnadmin hotcopy' reports progress for non-incremental 2918 # and incremental scenarios. The progress output can be affected by 2919 # the --fsfs-packing option, so skip the test if that is the case. 2920 if svntest.main.options.fsfs_packing: 2921 raise svntest.Skip('fsfs packing set') 2922 2923 # Create an empty repository, configure three files per shard. 2924 sbox.build(create_wc=False, empty=True) 2925 patch_format(sbox.repo_dir, shard_size=3) 2926 2927 inc_backup_dir, inc_backup_url = sbox.add_repo_path('incremental-backup') 2928 2929 # Nothing really exciting for the empty repository. 2930 expected_full = [ 2931 "* Copied revision 0.\n" 2932 ] 2933 expected_incremental = [ 2934 "* Copied revision 0.\n", 2935 ] 2936 2937 backup_dir, backup_url = sbox.add_repo_path('backup-0') 2938 svntest.actions.run_and_verify_svnadmin(expected_full, [], 2939 'hotcopy', 2940 sbox.repo_dir, backup_dir) 2941 svntest.actions.run_and_verify_svnadmin(expected_incremental, [], 2942 'hotcopy', '--incremental', 2943 sbox.repo_dir, inc_backup_dir) 2944 2945 # Commit three revisions. After this step we have a full shard 2946 # (r0, r1, r2) and the second shard (r3) with a single revision. 2947 for i in range(3): 2948 svntest.actions.run_and_verify_svn(None, [], 'mkdir', 2949 '-m', svntest.main.make_log_msg(), 2950 sbox.repo_url + '/dir-%i' % i) 2951 expected_full = [ 2952 "* Copied revision 0.\n", 2953 "* Copied revision 1.\n", 2954 "* Copied revision 2.\n", 2955 "* Copied revision 3.\n", 2956 ] 2957 expected_incremental = [ 2958 "* Copied revision 1.\n", 2959 "* Copied revision 2.\n", 2960 "* Copied revision 3.\n", 2961 ] 2962 2963 backup_dir, backup_url = sbox.add_repo_path('backup-1') 2964 svntest.actions.run_and_verify_svnadmin(expected_full, [], 2965 'hotcopy', 2966 sbox.repo_dir, backup_dir) 2967 svntest.actions.run_and_verify_svnadmin(expected_incremental, [], 2968 'hotcopy', '--incremental', 2969 sbox.repo_dir, inc_backup_dir) 2970 2971 # Pack everything (r3 is still unpacked) and hotcopy again. In this case, 2972 # the --incremental output should track the incoming (r0, r1, r2) pack and 2973 # should not mention r3, because it is already a part of the destination 2974 # and is *not* a part of the incoming pack. 2975 svntest.actions.run_and_verify_svnadmin(None, [], 'pack', 2976 sbox.repo_dir) 2977 expected_full = [ 2978 "* Copied revisions from 0 to 2.\n", 2979 "* Copied revision 3.\n", 2980 ] 2981 expected_incremental = [ 2982 "* Copied revisions from 0 to 2.\n", 2983 ] 2984 2985 backup_dir, backup_url = sbox.add_repo_path('backup-2') 2986 svntest.actions.run_and_verify_svnadmin(expected_full, [], 2987 'hotcopy', 2988 sbox.repo_dir, backup_dir) 2989 svntest.actions.run_and_verify_svnadmin(expected_incremental, [], 2990 'hotcopy', '--incremental', 2991 sbox.repo_dir, inc_backup_dir) 2992 2993 # Fill the second shard, pack again, commit several unpacked revisions 2994 # on top of it. Rerun the hotcopy and check the progress output. 2995 for i in range(4, 6): 2996 svntest.actions.run_and_verify_svn(None, [], 'mkdir', 2997 '-m', svntest.main.make_log_msg(), 2998 sbox.repo_url + '/dir-%i' % i) 2999 3000 svntest.actions.run_and_verify_svnadmin(None, [], 'pack', 3001 sbox.repo_dir) 3002 3003 for i in range(6, 8): 3004 svntest.actions.run_and_verify_svn(None, [], 'mkdir', 3005 '-m', svntest.main.make_log_msg(), 3006 sbox.repo_url + '/dir-%i' % i) 3007 expected_full = [ 3008 "* Copied revisions from 0 to 2.\n", 3009 "* Copied revisions from 3 to 5.\n", 3010 "* Copied revision 6.\n", 3011 "* Copied revision 7.\n", 3012 ] 3013 expected_incremental = [ 3014 "* Copied revisions from 3 to 5.\n", 3015 "* Copied revision 6.\n", 3016 "* Copied revision 7.\n", 3017 ] 3018 3019 backup_dir, backup_url = sbox.add_repo_path('backup-3') 3020 svntest.actions.run_and_verify_svnadmin(expected_full, [], 3021 'hotcopy', 3022 sbox.repo_dir, backup_dir) 3023 svntest.actions.run_and_verify_svnadmin(expected_incremental, [], 3024 'hotcopy', '--incremental', 3025 sbox.repo_dir, inc_backup_dir) 3026 3027 3028@SkipUnless(svntest.main.is_fs_type_fsfs) 3029def fsfs_hotcopy_progress_with_revprop_changes(sbox): 3030 "incremental hotcopy progress with changed revprops" 3031 3032 # The progress output can be affected by the --fsfs-packing 3033 # option, so skip the test if that is the case. 3034 if svntest.main.options.fsfs_packing: 3035 raise svntest.Skip('fsfs packing set') 3036 3037 # Create an empty repository, commit several revisions and hotcopy it. 3038 sbox.build(create_wc=False, empty=True) 3039 3040 for i in range(6): 3041 svntest.actions.run_and_verify_svn(None, [], 'mkdir', 3042 '-m', svntest.main.make_log_msg(), 3043 sbox.repo_url + '/dir-%i' % i) 3044 expected_output = [ 3045 "* Copied revision 0.\n", 3046 "* Copied revision 1.\n", 3047 "* Copied revision 2.\n", 3048 "* Copied revision 3.\n", 3049 "* Copied revision 4.\n", 3050 "* Copied revision 5.\n", 3051 "* Copied revision 6.\n", 3052 ] 3053 3054 backup_dir, backup_url = sbox.add_repo_path('backup') 3055 svntest.actions.run_and_verify_svnadmin(expected_output, [], 3056 'hotcopy', 3057 sbox.repo_dir, backup_dir) 3058 3059 # Amend a few log messages in the source, run the --incremental hotcopy. 3060 # The progress output should only mention the corresponding revisions. 3061 revprop_file = sbox.get_tempname() 3062 svntest.main.file_write(revprop_file, "Modified log message.") 3063 3064 for i in [1, 3, 6]: 3065 svntest.actions.run_and_verify_svnadmin(None, [], 3066 'setrevprop', 3067 sbox.repo_dir, '-r', i, 3068 'svn:log', revprop_file) 3069 expected_output = [ 3070 "* Copied revision 1.\n", 3071 "* Copied revision 3.\n", 3072 "* Copied revision 6.\n", 3073 ] 3074 svntest.actions.run_and_verify_svnadmin(expected_output, [], 3075 'hotcopy', '--incremental', 3076 sbox.repo_dir, backup_dir) 3077 3078 3079@SkipUnless(svntest.main.is_fs_type_fsfs) 3080def fsfs_hotcopy_progress_old(sbox): 3081 "hotcopy --compatible-version=1.3 progress" 3082 3083 sbox.build(create_wc=False, empty=True, minor_version=3) 3084 3085 inc_backup_dir, inc_backup_url = sbox.add_repo_path('incremental-backup') 3086 3087 # Nothing really exciting for the empty repository. 3088 expected_full = [ 3089 "* Copied revision 0.\n" 3090 ] 3091 expected_incremental = [ 3092 "* Copied revision 0.\n", 3093 ] 3094 3095 backup_dir, backup_url = sbox.add_repo_path('backup-0') 3096 svntest.actions.run_and_verify_svnadmin(expected_full, [], 3097 'hotcopy', 3098 sbox.repo_dir, backup_dir) 3099 svntest.actions.run_and_verify_svnadmin(expected_incremental, [], 3100 'hotcopy', '--incremental', 3101 sbox.repo_dir, inc_backup_dir) 3102 3103 # Commit three revisions, hotcopy and check the progress output. 3104 for i in range(3): 3105 svntest.actions.run_and_verify_svn(None, [], 'mkdir', 3106 '-m', svntest.main.make_log_msg(), 3107 sbox.repo_url + '/dir-%i' % i) 3108 3109 expected_full = [ 3110 "* Copied revision 0.\n", 3111 "* Copied revision 1.\n", 3112 "* Copied revision 2.\n", 3113 "* Copied revision 3.\n", 3114 ] 3115 expected_incremental = [ 3116 "* Copied revision 1.\n", 3117 "* Copied revision 2.\n", 3118 "* Copied revision 3.\n", 3119 ] 3120 3121 backup_dir, backup_url = sbox.add_repo_path('backup-1') 3122 svntest.actions.run_and_verify_svnadmin(expected_full, [], 3123 'hotcopy', 3124 sbox.repo_dir, backup_dir) 3125 svntest.actions.run_and_verify_svnadmin(expected_incremental, [], 3126 'hotcopy', '--incremental', 3127 sbox.repo_dir, inc_backup_dir) 3128 3129 3130@SkipUnless(svntest.main.fs_has_unique_freeze) 3131def freeze_same_uuid(sbox): 3132 "freeze multiple repositories with same UUID" 3133 3134 sbox.build(create_wc=False) 3135 3136 first_repo_dir, _ = sbox.add_repo_path('first') 3137 second_repo_dir, _ = sbox.add_repo_path('second') 3138 3139 # Test that 'svnadmin freeze A (svnadmin freeze B)' does not deadlock for 3140 # new FSFS formats, even if 'A' and 'B' share the same UUID. Create two 3141 # repositories by loading the same dump file, ... 3142 svntest.main.create_repos(first_repo_dir) 3143 svntest.main.create_repos(second_repo_dir) 3144 3145 dump_path = os.path.join(os.path.dirname(sys.argv[0]), 3146 'svnadmin_tests_data', 3147 'skeleton_repos.dump') 3148 dump_contents = open(dump_path, 'rb').readlines() 3149 svntest.actions.run_and_verify_load(first_repo_dir, dump_contents) 3150 svntest.actions.run_and_verify_load(second_repo_dir, dump_contents) 3151 3152 # ...and execute the 'svnadmin freeze -F' command. 3153 arg_file = sbox.get_tempname() 3154 svntest.main.file_write(arg_file, 3155 "%s\n%s\n" % (first_repo_dir, second_repo_dir)) 3156 3157 svntest.actions.run_and_verify_svnadmin(None, None, 3158 'freeze', '-F', arg_file, '--', 3159 sys.executable, '-c', 'True') 3160 3161 3162@Skip(svntest.main.is_fs_type_fsx) 3163def upgrade(sbox): 3164 "upgrade --compatible-version=1.3" 3165 3166 sbox.build(create_wc=False, minor_version=3) 3167 svntest.actions.run_and_verify_svnadmin(None, [], "upgrade", 3168 sbox.repo_dir) 3169 # Does the repository work after upgrade? 3170 svntest.actions.run_and_verify_svn(['Committing transaction...\n', 3171 'Committed revision 2.\n'], [], 'mkdir', 3172 '-m', svntest.main.make_log_msg(), 3173 sbox.repo_url + '/dir') 3174 3175def load_txdelta(sbox): 3176 "exercising svn_txdelta_target on BDB" 3177 3178 sbox.build(empty=True) 3179 3180 # This dumpfile produced a BDB repository that generated cheksum 3181 # mismatches on read caused by the improper handling of 3182 # svn_txdelta_target ops. The bug was fixed by r1640832. 3183 3184 dumpfile_location = os.path.join(os.path.dirname(sys.argv[0]), 3185 'svnadmin_tests_data', 3186 'load_txdelta.dump.gz') 3187 dumpfile = gzip.open(dumpfile_location, "rb").readlines() 3188 3189 load_dumpstream(sbox, dumpfile) 3190 3191 # Verify would fail with a checksum mismatch: 3192 # * Error verifying revision 14. 3193 # svnadmin: E200014: MD5 checksum mismatch on representation 'r': 3194 # expected: 5182e8876ed894dc7fe28f6ff5b2fee6 3195 # actual: 5121f82875508863ad70daa8244e6947 3196 3197 exit_code, output, errput = svntest.main.run_svnadmin("verify", sbox.repo_dir) 3198 if errput: 3199 raise SVNUnexpectedStderr(errput) 3200 if svntest.verify.verify_outputs( 3201 "Output of 'svnadmin verify' is unexpected.", None, output, None, 3202 ".*Verified revision *"): 3203 raise svntest.Failure 3204 3205@Issues(4563) 3206def load_no_svndate_r0(sbox): 3207 "load without svn:date on r0" 3208 3209 sbox.build(create_wc=False, empty=True) 3210 3211 # svn:date exits 3212 svntest.actions.run_and_verify_svnlook([' svn:date\n'], [], 3213 'proplist', '--revprop', '-r0', 3214 sbox.repo_dir) 3215 3216 dump_old = [b"SVN-fs-dump-format-version: 2\n", b"\n", 3217 b"UUID: bf52886d-358d-4493-a414-944a6e5ad4f5\n", b"\n", 3218 b"Revision-number: 0\n", 3219 b"Prop-content-length: 10\n", 3220 b"Content-length: 10\n", b"\n", 3221 b"PROPS-END\n", b"\n"] 3222 svntest.actions.run_and_verify_load(sbox.repo_dir, dump_old) 3223 3224 # svn:date should have been removed 3225 svntest.actions.run_and_verify_svnlook([], [], 3226 'proplist', '--revprop', '-r0', 3227 sbox.repo_dir) 3228 3229# This is only supported for FSFS 3230# The port to FSX is still pending, BDB won't support it. 3231@SkipUnless(svntest.main.is_fs_type_fsfs) 3232def hotcopy_read_only(sbox): 3233 "'svnadmin hotcopy' a read-only source repository" 3234 sbox.build() 3235 svntest.main.chmod_tree(sbox.repo_dir, 0, svntest.main.S_ALL_WRITE) 3236 3237 backup_dir, backup_url = sbox.add_repo_path('backup') 3238 exit_code, output, errput = svntest.main.run_svnadmin("hotcopy", 3239 sbox.repo_dir, 3240 backup_dir) 3241 3242 # r/o repos are hard to clean up. Make it writable again. 3243 svntest.main.chmod_tree(sbox.repo_dir, svntest.main.S_ALL_WRITE, 3244 svntest.main.S_ALL_WRITE) 3245 if errput: 3246 logger.warn("Error: hotcopy failed") 3247 raise SVNUnexpectedStderr(errput) 3248 3249@SkipUnless(svntest.main.is_fs_type_fsfs) 3250@SkipUnless(svntest.main.fs_has_pack) 3251def fsfs_pack_non_sharded(sbox): 3252 "'svnadmin pack' on a non-sharded repository" 3253 3254 # Configure two files per shard to trigger packing. 3255 sbox.build(create_wc = False, 3256 minor_version = min(svntest.main.options.server_minor_version,3)) 3257 3258 # Skip for pre-cooked sharded repositories 3259 if is_sharded(sbox.repo_dir): 3260 raise svntest.Skip('sharded pre-cooked repository') 3261 3262 svntest.actions.run_and_verify_svnadmin( 3263 None, [], "upgrade", sbox.repo_dir) 3264 svntest.actions.run_and_verify_svnadmin( 3265 ['svnadmin: Warning - this repository is not sharded. Packing has no effect.\n'], 3266 [], "pack", sbox.repo_dir) 3267 3268def load_revprops(sbox): 3269 "svnadmin load-revprops" 3270 3271 sbox.build(create_wc=False, empty=True) 3272 3273 dump_path = os.path.join(os.path.dirname(sys.argv[0]), 3274 'svnadmin_tests_data', 3275 'skeleton_repos.dump') 3276 dump_contents = open(dump_path, 'rb').readlines() 3277 load_and_verify_dumpstream(sbox, None, [], None, False, dump_contents) 3278 3279 svntest.actions.run_and_verify_svnlook(['Initial setup...\n', '\n'], 3280 [], 'log', '-r1', sbox.repo_dir) 3281 3282 # After loading the dump, amend one of the log message in the repository. 3283 input_file = sbox.get_tempname() 3284 svntest.main.file_write(input_file, 'Modified log message...\n') 3285 3286 svntest.actions.run_and_verify_svnadmin([], [], 'setlog', '--bypass-hooks', 3287 '-r1', sbox.repo_dir, input_file) 3288 svntest.actions.run_and_verify_svnlook(['Modified log message...\n', '\n'], 3289 [], 'log', '-r1', sbox.repo_dir) 3290 3291 # Load the same dump, but with 'svnadmin load-revprops'. Doing so should 3292 # restore the log message to its original state. 3293 svntest.main.run_command_stdin(svntest.main.svnadmin_binary, None, 0, 3294 True, dump_contents, 'load-revprops', 3295 sbox.repo_dir) 3296 3297 svntest.actions.run_and_verify_svnlook(['Initial setup...\n', '\n'], 3298 [], 'log', '-r1', sbox.repo_dir) 3299 3300def dump_revprops(sbox): 3301 "svnadmin dump-revprops" 3302 3303 sbox.build(create_wc=False) 3304 3305 # Dump revprops only. 3306 exit_code, dump_contents, errput = \ 3307 svntest.actions.run_and_verify_svnadmin(None, [], "dump-revprops", "-q", 3308 sbox.repo_dir) 3309 3310 # We expect the dump to contain no path changes 3311 for line in dump_contents: 3312 if line.find(b"Node-path: ") > -1: 3313 logger.warn("Error: path change found in revprops-only dump.") 3314 raise svntest.Failure 3315 3316 # Remember the current log message for r1 3317 exit_code, log_msg, errput = \ 3318 svntest.actions.run_and_verify_svnlook(None, [], 'log', '-r1', 3319 sbox.repo_dir) 3320 3321 # Now, change the log message in the repository. 3322 input_file = sbox.get_tempname() 3323 svntest.main.file_write(input_file, 'Modified log message...\n') 3324 3325 svntest.actions.run_and_verify_svnadmin([], [], 'setlog', '--bypass-hooks', 3326 '-r1', sbox.repo_dir, input_file) 3327 svntest.actions.run_and_verify_svnlook(['Modified log message...\n', '\n'], 3328 [], 'log', '-r1', sbox.repo_dir) 3329 3330 # Load the same dump with 'svnadmin load-revprops'. Doing so should 3331 # restore the log message to its original state. 3332 svntest.main.run_command_stdin(svntest.main.svnadmin_binary, None, 0, 3333 True, dump_contents, 'load-revprops', 3334 sbox.repo_dir) 3335 3336 svntest.actions.run_and_verify_svnlook(log_msg, [], 'log', '-r1', 3337 sbox.repo_dir) 3338 3339@XFail(svntest.main.is_fs_type_fsx) 3340@Issue(4598) 3341def dump_no_op_change(sbox): 3342 "svnadmin dump with no-op changes" 3343 3344 sbox.build(create_wc=False, empty=True) 3345 empty_file = sbox.get_tempname() 3346 svntest.main.file_write(empty_file, '') 3347 3348 svntest.actions.run_and_verify_svnmucc(None, [], 3349 '-U', sbox.repo_url, 3350 '-m', svntest.main.make_log_msg(), 3351 'put', empty_file, 'bar') 3352 # Commit a no-op change. 3353 svntest.actions.run_and_verify_svnmucc(None, [], 3354 '-U', sbox.repo_url, 3355 '-m', svntest.main.make_log_msg(), 3356 'put', empty_file, 'bar') 3357 # Dump and load the repository. 3358 _, dump, _ = svntest.actions.run_and_verify_svnadmin(None, [], 3359 'dump', '-q', 3360 sbox.repo_dir) 3361 sbox2 = sbox.clone_dependent() 3362 sbox2.build(create_wc=False, empty=True) 3363 load_and_verify_dumpstream(sbox2, None, [], None, False, dump) 3364 3365 # We expect svn log -v to yield identical results for both original and 3366 # reconstructed repositories. This used to fail as described in the 3367 # Issue 4598 (https://issues.apache.org/jira/browse/SVN-4598), at least 3368 # around r1706415. 3369 # 3370 # Test svn log -v for r2: 3371 _, expected, _ = svntest.actions.run_and_verify_svn(None, [], 'log', '-v', 3372 '-r2', sbox.repo_url) 3373 found = [True for line in expected if line.find('M /bar\n') != -1] 3374 if not found: 3375 raise svntest.Failure 3376 svntest.actions.run_and_verify_svn(expected, [], 'log', '-v', 3377 '-r2', sbox2.repo_url) 3378 # Test svn log -v for /bar: 3379 _, expected, _ = svntest.actions.run_and_verify_svn(None, [], 'log', '-v', 3380 sbox.repo_url + '/bar') 3381 found = [True for line in expected if line.find('M /bar\n') != -1] 3382 if not found: 3383 raise svntest.Failure 3384 svntest.actions.run_and_verify_svn(expected, [], 'log', '-v', 3385 sbox2.repo_url + '/bar') 3386 3387@XFail(svntest.main.is_fs_type_bdb) 3388@XFail(svntest.main.is_fs_type_fsx) 3389@Issue(4623) 3390def dump_no_op_prop_change(sbox): 3391 "svnadmin dump with no-op property change" 3392 3393 sbox.build(create_wc=False, empty=True) 3394 empty_file = sbox.get_tempname() 3395 svntest.main.file_write(empty_file, '') 3396 3397 svntest.actions.run_and_verify_svnmucc(None, [], 3398 '-U', sbox.repo_url, 3399 '-m', svntest.main.make_log_msg(), 3400 'put', empty_file, 'bar', 3401 'propset', 'pname', 'pval', 'bar') 3402 # Commit a no-op property change. 3403 svntest.actions.run_and_verify_svnmucc(None, [], 3404 '-U', sbox.repo_url, 3405 '-m', svntest.main.make_log_msg(), 3406 'propset', 'pname', 'pval', 'bar') 3407 # Dump and load the repository. 3408 _, dump, _ = svntest.actions.run_and_verify_svnadmin(None, [], 3409 'dump', '-q', 3410 sbox.repo_dir) 3411 sbox2 = sbox.clone_dependent() 3412 sbox2.build(create_wc=False, empty=True) 3413 load_and_verify_dumpstream(sbox2, None, [], None, False, dump) 3414 3415 # Test svn log -v for r2: 3416 _, expected, _ = svntest.actions.run_and_verify_svn(None, [], 'log', '-v', 3417 '-r2', sbox.repo_url) 3418 found = [True for line in expected if line.find('M /bar\n') != -1] 3419 if not found: 3420 raise svntest.Failure 3421 svntest.actions.run_and_verify_svn(expected, [], 'log', '-v', 3422 '-r2', sbox2.repo_url) 3423 # Test svn log -v for /bar: 3424 _, expected, _ = svntest.actions.run_and_verify_svn(None, [], 'log', '-v', 3425 sbox.repo_url + '/bar') 3426 found = [True for line in expected if line.find('M /bar\n') != -1] 3427 if not found: 3428 raise svntest.Failure 3429 svntest.actions.run_and_verify_svn(expected, [], 'log', '-v', 3430 sbox2.repo_url + '/bar') 3431 3432def load_no_flush_to_disk(sbox): 3433 "svnadmin load --no-flush-to-disk" 3434 3435 sbox.build(empty=True) 3436 3437 # Can't test the "not flushing to disk part", but loading the 3438 # dump should work. 3439 dump = clean_dumpfile() 3440 expected = [ 3441 svntest.wc.State('', { 3442 'A' : svntest.wc.StateItem(contents="text\n", 3443 props={'svn:keywords': 'Id'}) 3444 }) 3445 ] 3446 load_and_verify_dumpstream(sbox, [], [], expected, True, dump, 3447 '--no-flush-to-disk', '--ignore-uuid') 3448 3449def dump_to_file(sbox): 3450 "svnadmin dump --file ARG" 3451 3452 sbox.build(create_wc=False, empty=False) 3453 expected_dump = svntest.actions.run_and_verify_dump(sbox.repo_dir) 3454 3455 file = sbox.get_tempname() 3456 svntest.actions.run_and_verify_svnadmin2([], 3457 ["* Dumped revision 0.\n", 3458 "* Dumped revision 1.\n"], 3459 0, 'dump', '--file', file, 3460 sbox.repo_dir) 3461 actual_dump = open(file, 'rb').readlines() 3462 svntest.verify.compare_dump_files(None, None, expected_dump, actual_dump) 3463 3464 # Test that svnadmin dump --file overwrites existing files. 3465 file = sbox.get_tempname() 3466 svntest.main.file_write(file, '') 3467 svntest.actions.run_and_verify_svnadmin2([], 3468 ["* Dumped revision 0.\n", 3469 "* Dumped revision 1.\n"], 3470 0, 'dump', '--file', file, 3471 sbox.repo_dir) 3472 actual_dump = open(file, 'rb').readlines() 3473 svntest.verify.compare_dump_files(None, None, expected_dump, actual_dump) 3474 3475def load_from_file(sbox): 3476 "svnadmin load --file ARG" 3477 3478 sbox.build(empty=True) 3479 3480 file = sbox.get_tempname() 3481 with open(file, 'wb') as f: 3482 f.writelines(clean_dumpfile()) 3483 svntest.actions.run_and_verify_svnadmin2(None, [], 3484 0, 'load', '--file', file, 3485 '--ignore-uuid', sbox.repo_dir) 3486 expected_tree = \ 3487 svntest.wc.State('', { 3488 'A' : svntest.wc.StateItem(contents="text\n", 3489 props={'svn:keywords': 'Id'}) 3490 }) 3491 svntest.actions.run_and_verify_svn(svntest.verify.AnyOutput, [], 3492 'update', sbox.wc_dir) 3493 svntest.actions.verify_disk(sbox.wc_dir, expected_tree, check_props=True) 3494 3495def dump_exclude(sbox): 3496 "svnadmin dump with excluded paths" 3497 3498 sbox.build(create_wc=False) 3499 3500 # Dump repository with /A/D/H and /A/B/E paths excluded. 3501 _, dump, _ = svntest.actions.run_and_verify_svnadmin(None, [], 3502 'dump', '-q', 3503 '--exclude', '/A/D/H', 3504 '--exclude', '/A/B/E', 3505 sbox.repo_dir) 3506 3507 # Load repository from dump. 3508 sbox2 = sbox.clone_dependent() 3509 sbox2.build(create_wc=False, empty=True) 3510 load_and_verify_dumpstream(sbox2, None, [], None, False, dump) 3511 3512 # Check log. 3513 expected_output = svntest.verify.RegexListOutput([ 3514 '-+\\n', 3515 'r1\ .*\n', 3516 # '/A/D/H' and '/A/B/E' is not added. 3517 re.escape('Changed paths:\n'), 3518 re.escape(' A /A\n'), 3519 re.escape(' A /A/B\n'), 3520 re.escape(' A /A/B/F\n'), 3521 re.escape(' A /A/B/lambda\n'), 3522 re.escape(' A /A/C\n'), 3523 re.escape(' A /A/D\n'), 3524 re.escape(' A /A/D/G\n'), 3525 re.escape(' A /A/D/G/pi\n'), 3526 re.escape(' A /A/D/G/rho\n'), 3527 re.escape(' A /A/D/G/tau\n'), 3528 re.escape(' A /A/D/gamma\n'), 3529 re.escape(' A /A/mu\n'), 3530 re.escape(' A /iota\n'), 3531 '-+\\n' 3532 ]) 3533 svntest.actions.run_and_verify_svn(expected_output, [], 3534 'log', '-v', '-q', sbox2.repo_url) 3535 3536def dump_exclude_copysource(sbox): 3537 "svnadmin dump with excluded copysource" 3538 3539 sbox.build(create_wc=False, empty=True) 3540 3541 # Create default repository structure. 3542 svntest.actions.run_and_verify_svn(svntest.verify.AnyOutput, [], "mkdir", 3543 sbox.repo_url + '/trunk', 3544 sbox.repo_url + '/branches', 3545 sbox.repo_url + '/tags', 3546 "-m", "Create repository structure.") 3547 3548 # Create a branch. 3549 svntest.actions.run_and_verify_svn(svntest.verify.AnyOutput, [], "copy", 3550 sbox.repo_url + '/trunk', 3551 sbox.repo_url + '/branches/branch1', 3552 "-m", "Create branch.") 3553 3554 # Dump repository with /trunk excluded. 3555 _, dump, _ = svntest.actions.run_and_verify_svnadmin(None, [], 3556 'dump', '-q', 3557 '--exclude', '/trunk', 3558 sbox.repo_dir) 3559 3560 # Load repository from dump. 3561 sbox2 = sbox.clone_dependent() 3562 sbox2.build(create_wc=False, empty=True) 3563 load_and_verify_dumpstream(sbox2, None, [], None, False, dump) 3564 3565 # Check log. 3566 expected_output = svntest.verify.RegexListOutput([ 3567 '-+\\n', 3568 'r2\ .*\n', 3569 re.escape('Changed paths:\n'), 3570 # Simple add, not copy. 3571 re.escape(' A /branches/branch1\n'), 3572 '-+\\n', 3573 'r1\ .*\n', 3574 # '/trunk' is not added. 3575 re.escape('Changed paths:\n'), 3576 re.escape(' A /branches\n'), 3577 re.escape(' A /tags\n'), 3578 '-+\\n' 3579 ]) 3580 svntest.actions.run_and_verify_svn(expected_output, [], 3581 'log', '-v', '-q', sbox2.repo_url) 3582 3583def dump_include(sbox): 3584 "svnadmin dump with included paths" 3585 3586 sbox.build(create_wc=False, empty=True) 3587 3588 # Create a couple of directories. 3589 # Note that we can't use greek tree as it contains only two top-level 3590 # nodes. Including non top-level nodes (e.g. '--include /A/B/E') will 3591 # produce unloadable dump for now. 3592 svntest.actions.run_and_verify_svn(svntest.verify.AnyOutput, [], "mkdir", 3593 sbox.repo_url + '/A', 3594 sbox.repo_url + '/B', 3595 sbox.repo_url + '/C', 3596 "-m", "Create folder.") 3597 3598 # Dump repository with /A and /C paths included. 3599 _, dump, _ = svntest.actions.run_and_verify_svnadmin(None, [], 3600 'dump', '-q', 3601 '--include', '/A', 3602 '--include', '/C', 3603 sbox.repo_dir) 3604 3605 # Load repository from dump. 3606 sbox2 = sbox.clone_dependent() 3607 sbox2.build(create_wc=False, empty=True) 3608 load_and_verify_dumpstream(sbox2, None, [], None, False, dump) 3609 3610 # Check log. 3611 expected_output = svntest.verify.RegexListOutput([ 3612 '-+\\n', 3613 'r1\ .*\n', 3614 # '/B' is not added. 3615 re.escape('Changed paths:\n'), 3616 re.escape(' A /A\n'), 3617 re.escape(' A /C\n'), 3618 '-+\\n' 3619 ]) 3620 svntest.actions.run_and_verify_svn(expected_output, [], 3621 'log', '-v', '-q', sbox2.repo_url) 3622 3623def dump_not_include_copysource(sbox): 3624 "svnadmin dump with not included copysource" 3625 3626 sbox.build(create_wc=False, empty=True) 3627 3628 # Create default repository structure. 3629 svntest.actions.run_and_verify_svn(svntest.verify.AnyOutput, [], "mkdir", 3630 sbox.repo_url + '/trunk', 3631 sbox.repo_url + '/branches', 3632 sbox.repo_url + '/tags', 3633 "-m", "Create repository structure.") 3634 3635 # Create a branch. 3636 svntest.actions.run_and_verify_svn(svntest.verify.AnyOutput, [], "copy", 3637 sbox.repo_url + '/trunk', 3638 sbox.repo_url + '/branches/branch1', 3639 "-m", "Create branch.") 3640 3641 # Dump repository with only /branches included. 3642 _, dump, _ = svntest.actions.run_and_verify_svnadmin(None, [], 3643 'dump', '-q', 3644 '--include', '/branches', 3645 sbox.repo_dir) 3646 3647 # Load repository from dump. 3648 sbox2 = sbox.clone_dependent() 3649 sbox2.build(create_wc=False, empty=True) 3650 load_and_verify_dumpstream(sbox2, None, [], None, False, dump) 3651 3652 # Check log. 3653 expected_output = svntest.verify.RegexListOutput([ 3654 '-+\\n', 3655 'r2\ .*\n', 3656 re.escape('Changed paths:\n'), 3657 # Simple add, not copy. 3658 re.escape(' A /branches/branch1\n'), 3659 '-+\\n', 3660 'r1\ .*\n', 3661 # Only '/branches' is added in r1. 3662 re.escape('Changed paths:\n'), 3663 re.escape(' A /branches\n'), 3664 '-+\\n' 3665 ]) 3666 svntest.actions.run_and_verify_svn(expected_output, [], 3667 'log', '-v', '-q', sbox2.repo_url) 3668 3669def dump_exclude_by_pattern(sbox): 3670 "svnadmin dump with paths excluded by pattern" 3671 3672 sbox.build(create_wc=False, empty=True) 3673 3674 # Create a couple of directories. 3675 svntest.actions.run_and_verify_svn(svntest.verify.AnyOutput, [], "mkdir", 3676 sbox.repo_url + '/aaa', 3677 sbox.repo_url + '/aab', 3678 sbox.repo_url + '/aac', 3679 sbox.repo_url + '/bbc', 3680 "-m", "Create repository structure.") 3681 3682 # Dump with paths excluded by pattern. 3683 _, dump, _ = svntest.actions.run_and_verify_svnadmin(None, [], 3684 'dump', '-q', 3685 '--exclude', '/aa?', 3686 '--pattern', 3687 sbox.repo_dir) 3688 3689 # Load repository from dump. 3690 sbox2 = sbox.clone_dependent() 3691 sbox2.build(create_wc=False, empty=True) 3692 load_and_verify_dumpstream(sbox2, None, [], None, False, dump) 3693 3694 # Check log. 3695 expected_output = svntest.verify.RegexListOutput([ 3696 '-+\\n', 3697 'r1\ .*\n', 3698 re.escape('Changed paths:\n'), 3699 # Only '/bbc' is added in r1. 3700 re.escape(' A /bbc\n'), 3701 '-+\\n' 3702 ]) 3703 svntest.actions.run_and_verify_svn(expected_output, [], 3704 'log', '-v', '-q', sbox2.repo_url) 3705 3706def dump_include_by_pattern(sbox): 3707 "svnadmin dump with paths included by pattern" 3708 3709 sbox.build(create_wc=False, empty=True) 3710 3711 # Create a couple of directories. 3712 svntest.actions.run_and_verify_svn(svntest.verify.AnyOutput, [], "mkdir", 3713 sbox.repo_url + '/aaa', 3714 sbox.repo_url + '/aab', 3715 sbox.repo_url + '/aac', 3716 sbox.repo_url + '/bbc', 3717 "-m", "Create repository structure.") 3718 3719 # Dump with paths included by pattern. 3720 _, dump, _ = svntest.actions.run_and_verify_svnadmin(None, [], 3721 'dump', '-q', 3722 '--include', '/aa?', 3723 '--pattern', 3724 sbox.repo_dir) 3725 3726 # Load repository from dump. 3727 sbox2 = sbox.clone_dependent() 3728 sbox2.build(create_wc=False, empty=True) 3729 load_and_verify_dumpstream(sbox2, None, [], None, False, dump) 3730 3731 # Check log. 3732 expected_output = svntest.verify.RegexListOutput([ 3733 '-+\\n', 3734 'r1\ .*\n', 3735 # '/bbc' is not added. 3736 re.escape('Changed paths:\n'), 3737 re.escape(' A /aaa\n'), 3738 re.escape(' A /aab\n'), 3739 re.escape(' A /aac\n'), 3740 '-+\\n' 3741 ]) 3742 svntest.actions.run_and_verify_svn(expected_output, [], 3743 'log', '-v', '-q', sbox2.repo_url) 3744 3745def dump_exclude_all_rev_changes(sbox): 3746 "svnadmin dump with all revision changes excluded" 3747 3748 sbox.build(create_wc=False, empty=True) 3749 3750 # Create a couple of directories (r1). 3751 svntest.actions.run_and_verify_svn(svntest.verify.AnyOutput, [], "mkdir", 3752 sbox.repo_url + '/r1a', 3753 sbox.repo_url + '/r1b', 3754 sbox.repo_url + '/r1c', 3755 "-m", "Revision 1.") 3756 3757 # Create a couple of directories (r2). 3758 svntest.actions.run_and_verify_svn(svntest.verify.AnyOutput, [], "mkdir", 3759 sbox.repo_url + '/r2a', 3760 sbox.repo_url + '/r2b', 3761 sbox.repo_url + '/r2c', 3762 "-m", "Revision 2.") 3763 3764 # Create a couple of directories (r3). 3765 svntest.actions.run_and_verify_svn(svntest.verify.AnyOutput, [], "mkdir", 3766 sbox.repo_url + '/r3a', 3767 sbox.repo_url + '/r3b', 3768 sbox.repo_url + '/r3c', 3769 "-m", "Revision 3.") 3770 3771 # Dump with paths excluded by pattern. 3772 _, dump, _ = svntest.actions.run_and_verify_svnadmin(None, [], 3773 'dump', '-q', 3774 '--exclude', '/r2?', 3775 '--pattern', 3776 sbox.repo_dir) 3777 3778 # Load repository from dump. 3779 sbox2 = sbox.clone_dependent() 3780 sbox2.build(create_wc=False, empty=True) 3781 load_and_verify_dumpstream(sbox2, None, [], None, False, dump) 3782 3783 # Check log. Revision properties ('svn:log' etc.) should be empty for r2. 3784 expected_output = svntest.verify.RegexListOutput([ 3785 '-+\\n', 3786 'r3 | jrandom | .* | 1 line\\n', 3787 re.escape('Changed paths:'), 3788 re.escape(' A /r3a'), 3789 re.escape(' A /r3b'), 3790 re.escape(' A /r3c'), 3791 '', 3792 re.escape('Revision 3.'), 3793 '-+\\n', 3794 re.escape('r2 | (no author) | (no date) | 1 line'), 3795 '', 3796 '', 3797 '-+\\n', 3798 'r1 | jrandom | .* | 1 line\\n', 3799 re.escape('Changed paths:'), 3800 re.escape(' A /r1a'), 3801 re.escape(' A /r1b'), 3802 re.escape(' A /r1c'), 3803 '', 3804 re.escape('Revision 1.'), 3805 '-+\\n', 3806 ]) 3807 svntest.actions.run_and_verify_svn(expected_output, [], 3808 'log', '-v', sbox2.repo_url) 3809 3810def dump_invalid_filtering_option(sbox): 3811 "dump with --include and --exclude simultaneously" 3812 3813 sbox.build(create_wc=False, empty=False) 3814 3815 # Attempt to dump repository with '--include' and '--exclude' options 3816 # specified simultaneously. 3817 expected_error = ".*: '--exclude' and '--include' options cannot be used " \ 3818 "simultaneously" 3819 svntest.actions.run_and_verify_svnadmin(None, expected_error, 3820 'dump', '-q', 3821 '--exclude', '/A/D/H', 3822 '--include', '/A/B/E', 3823 sbox.repo_dir) 3824 3825@Issue(4725) 3826def load_issue4725(sbox): 3827 """load that triggers issue 4725""" 3828 3829 sbox.build(empty=True) 3830 3831 sbox.simple_mkdir('subversion') 3832 sbox.simple_commit() 3833 sbox.simple_mkdir('subversion/trunk') 3834 sbox.simple_mkdir('subversion/branches') 3835 sbox.simple_commit() 3836 sbox.simple_mkdir('subversion/trunk/src') 3837 sbox.simple_commit() 3838 3839 _, dump, _ = svntest.actions.run_and_verify_svnadmin(None, [], 3840 'dump', '-q', 3841 sbox.repo_dir) 3842 3843 sbox2 = sbox.clone_dependent() 3844 sbox2.build(create_wc=False, empty=True) 3845 load_and_verify_dumpstream(sbox2, None, [], None, False, dump, '-M100') 3846 3847@Issue(4767) 3848def dump_no_canonicalize_svndate(sbox): 3849 "svnadmin dump shouldn't canonicalize svn:date" 3850 3851 sbox.build(create_wc=False, empty=True) 3852 svntest.actions.enable_revprop_changes(sbox.repo_dir) 3853 3854 # set svn:date in a non-canonical format (not six decimal places) 3855 propval = "2015-01-01T00:00:00.0Z" 3856 svntest.actions.run_and_verify_svn(svntest.verify.AnyOutput, [], 3857 "propset", "--revprop", "-r0", "svn:date", 3858 propval, 3859 sbox.repo_url) 3860 3861 dump_lines = svntest.actions.run_and_verify_dump(sbox.repo_dir) 3862 assert propval.encode() + b'\n' in dump_lines 3863 3864def check_recover_prunes_rep_cache(sbox, enable_rep_sharing): 3865 """Check 'recover' prunes the rep-cache while enable-rep-sharing is 3866 true/false. 3867 """ 3868 # Remember the initial rep cache content. 3869 rep_cache_r1 = read_rep_cache(sbox.repo_dir) 3870 #print '\n'.join([h + ": " + repr(ref) for h, ref in rep_cache_r1.items()]) 3871 3872 # Commit one new rep and check the rep-cache is extended. 3873 sbox.simple_append('iota', 'New line.\n') 3874 sbox.simple_commit() 3875 rep_cache_r2 = read_rep_cache(sbox.repo_dir) 3876 if not (len(rep_cache_r2) == len(rep_cache_r1) + 1): 3877 raise svntest.Failure 3878 3879 fsfs_conf = svntest.main.get_fsfs_conf_file_path(sbox.repo_dir) 3880 svntest.main.file_append(fsfs_conf, 3881 # Add a newline in case the existing file doesn't 3882 # end with one. 3883 "\n" 3884 "[rep-sharing]\n" 3885 "enable-rep-sharing = %s\n" 3886 % (('true' if enable_rep_sharing else 'false'),)) 3887 3888 # Break r2 in such a way that 'recover' will discard it 3889 head_rev_path = fsfs_file(sbox.repo_dir, 'revs', '2') 3890 os.remove(head_rev_path) 3891 current_path = os.path.join(sbox.repo_dir, 'db', 'current') 3892 svntest.main.file_write(current_path, '1\n') 3893 3894 # Recover back to r1. 3895 svntest.actions.run_and_verify_svnadmin(None, [], 3896 "recover", sbox.repo_dir) 3897 svntest.actions.run_and_verify_svnlook(['1\n'], [], 'youngest', 3898 sbox.repo_dir) 3899 3900 # Check the rep-cache is pruned. 3901 rep_cache_recovered = read_rep_cache(sbox.repo_dir) 3902 if not (rep_cache_recovered == rep_cache_r1): 3903 raise svntest.Failure 3904 3905@Issue(4077) 3906@SkipUnless(svntest.main.is_fs_type_fsfs) 3907@SkipUnless(svntest.main.python_sqlite_can_read_without_rowid) 3908def recover_prunes_rep_cache_when_enabled(sbox): 3909 "recover prunes rep cache when enabled" 3910 sbox.build() 3911 3912 check_recover_prunes_rep_cache(sbox, enable_rep_sharing=True) 3913 3914@Issue(4077) 3915@SkipUnless(svntest.main.is_fs_type_fsfs) 3916@SkipUnless(svntest.main.python_sqlite_can_read_without_rowid) 3917def recover_prunes_rep_cache_when_disabled(sbox): 3918 "recover prunes rep cache when disabled" 3919 sbox.build() 3920 3921 check_recover_prunes_rep_cache(sbox, enable_rep_sharing=False) 3922 3923@Issue(4760) 3924def dump_include_copied_directory(sbox): 3925 "include copied directory with nested nodes" 3926 3927 sbox.build(create_wc=False) 3928 3929 svntest.actions.run_and_verify_svn(svntest.verify.AnyOutput, [], "copy", 3930 sbox.repo_url + '/A/D', 3931 sbox.repo_url + '/COPY', 3932 "-m", "Create branch.") 3933 3934 # Dump repository with only /COPY path included. 3935 _, dump, _ = svntest.actions.run_and_verify_svnadmin(None, [], 3936 'dump', '-q', 3937 '--include', '/COPY', 3938 sbox.repo_dir) 3939 3940 # Load repository from dump. 3941 sbox2 = sbox.clone_dependent() 3942 sbox2.build(create_wc=False, empty=True) 3943 load_and_verify_dumpstream(sbox2, None, [], None, False, dump) 3944 3945 # Check log. 3946 expected_output = svntest.verify.RegexListOutput([ 3947 '-+\\n', 3948 'r2\ .*\n', 3949 # Only '/COPY' is added 3950 re.escape('Changed paths:\n'), 3951 re.escape(' A /COPY'), 3952 re.escape(' A /COPY/G'), 3953 re.escape(' A /COPY/G/pi'), 3954 re.escape(' A /COPY/G/rho'), 3955 re.escape(' A /COPY/G/tau'), 3956 re.escape(' A /COPY/H'), 3957 re.escape(' A /COPY/H/chi'), 3958 re.escape(' A /COPY/H/omega'), 3959 re.escape(' A /COPY/H/psi'), 3960 re.escape(' A /COPY/gamma'), 3961 '-+\\n', 3962 'r1\ .*\n', 3963 '-+\\n' 3964 ]) 3965 svntest.actions.run_and_verify_svn(expected_output, [], 3966 'log', '-v', '-q', sbox2.repo_url) 3967 3968def load_normalize_node_props(sbox): 3969 "svnadmin load --normalize node props" 3970 3971 dump_str = b"""SVN-fs-dump-format-version: 2 3972 3973UUID: dc40867b-38f6-0310-9f5f-f81aa277e06f 3974 3975Revision-number: 0 3976Prop-content-length: 56 3977Content-length: 56 3978 3979K 8 3980svn:date 3981V 27 39822005-05-03T19:09:41.129900Z 3983PROPS-END 3984 3985Revision-number: 1 3986Prop-content-length: 99 3987Content-length: 99 3988 3989K 7 3990svn:log 3991V 0 3992 3993K 10 3994svn:author 3995V 2 3996pl 3997K 8 3998svn:date 3999V 27 40002005-05-03T19:10:19.975578Z 4001PROPS-END 4002 4003Node-path:\x20 4004Node-kind: dir 4005Node-action: change 4006Prop-content-length: 32 4007Content-length: 32 4008 4009K 10 4010svn:ignore 4011V 3 4012\n\r\n 4013PROPS-END 4014 4015 4016""" 4017 sbox.build(empty=True) 4018 4019 # Try to load the dumpstream, expecting a failure (because of mixed 4020 # EOLs in the svn:ignore property value). 4021 exp_err = svntest.verify.RegexListOutput(['svnadmin: E125005:.*', 4022 'svnadmin: E125017:.*'], 4023 match_all=False) 4024 load_and_verify_dumpstream(sbox, [], exp_err, dumpfile_revisions, 4025 False, dump_str, '--ignore-uuid') 4026 4027 # Now try it again with prop normalization. 4028 svntest.actions.load_repo(sbox, dump_str=dump_str, 4029 bypass_prop_validation=False, 4030 normalize_props=True) 4031 # We should get the normalized property value. 4032 exit_code, output, _ = svntest.main.run_svn(None, 'pg', 'svn:ignore', 4033 '--no-newline', 4034 sbox.repo_url) 4035 svntest.verify.verify_exit_code(None, exit_code, 0) 4036 if output != ['\n', '\n']: 4037 raise svntest.Failure("Unexpected property value %s" % output) 4038 4039@SkipUnless(svntest.main.is_fs_type_fsfs) 4040@SkipUnless(svntest.main.fs_has_rep_sharing) 4041@SkipUnless(svntest.main.python_sqlite_can_read_without_rowid) 4042def build_repcache(sbox): 4043 "svnadmin build-repcache" 4044 4045 sbox.build(create_wc = False) 4046 4047 # Remember and remove the existing rep-cache. 4048 rep_cache = read_rep_cache(sbox.repo_dir) 4049 rep_cache_path = os.path.join(sbox.repo_dir, 'db', 'rep-cache.db') 4050 os.remove(rep_cache_path) 4051 4052 # Build a new rep-cache and compare with the original one. 4053 expected_output = ["* Processed revision 1.\n"] 4054 svntest.actions.run_and_verify_svnadmin(expected_output, [], 4055 "build-repcache", sbox.repo_dir) 4056 4057 new_rep_cache = read_rep_cache(sbox.repo_dir) 4058 if new_rep_cache != rep_cache: 4059 raise svntest.Failure 4060 4061 4062######################################################################## 4063# Run the tests 4064 4065 4066# list all tests here, starting with None: 4067test_list = [ None, 4068 extra_headers, 4069 extra_blockcontent, 4070 inconsistent_headers, 4071 empty_date, 4072 dump_copied_dir, 4073 dump_move_dir_modify_child, 4074 dump_quiet, 4075 hotcopy_dot, 4076 hotcopy_format, 4077 setrevprop, 4078 verify_windows_paths_in_repos, 4079 verify_incremental_fsfs, 4080 fsfs_recover_db_current, 4081 fsfs_recover_old_db_current, 4082 load_with_parent_dir, 4083 set_uuid, 4084 reflect_dropped_renumbered_revs, 4085 fsfs_recover_handle_missing_revs_or_revprops_file, 4086 create_in_repo_subdir, 4087 verify_with_invalid_revprops, 4088 dont_drop_valid_mergeinfo_during_incremental_loads, 4089 hotcopy_symlink, 4090 load_bad_props, 4091 verify_non_utf8_paths, 4092 test_lslocks_and_rmlocks, 4093 load_ranges, 4094 hotcopy_incremental, 4095 hotcopy_incremental_packed, 4096 locking, 4097 mergeinfo_race, 4098 recover_old_empty, 4099 verify_keep_going, 4100 verify_keep_going_quiet, 4101 verify_invalid_path_changes, 4102 verify_denormalized_names, 4103 fsfs_recover_old_non_empty, 4104 fsfs_hotcopy_old_non_empty, 4105 load_ignore_dates, 4106 fsfs_hotcopy_old_with_id_changes, 4107 verify_packed, 4108 freeze_freeze, 4109 verify_metadata_only, 4110 verify_quickly, 4111 fsfs_hotcopy_progress, 4112 fsfs_hotcopy_progress_with_revprop_changes, 4113 fsfs_hotcopy_progress_old, 4114 freeze_same_uuid, 4115 upgrade, 4116 load_txdelta, 4117 load_no_svndate_r0, 4118 hotcopy_read_only, 4119 fsfs_pack_non_sharded, 4120 load_revprops, 4121 dump_revprops, 4122 dump_no_op_change, 4123 dump_no_op_prop_change, 4124 load_no_flush_to_disk, 4125 dump_to_file, 4126 load_from_file, 4127 dump_exclude, 4128 dump_exclude_copysource, 4129 dump_include, 4130 dump_not_include_copysource, 4131 dump_exclude_by_pattern, 4132 dump_include_by_pattern, 4133 dump_exclude_all_rev_changes, 4134 dump_invalid_filtering_option, 4135 load_issue4725, 4136 dump_no_canonicalize_svndate, 4137 recover_prunes_rep_cache_when_enabled, 4138 recover_prunes_rep_cache_when_disabled, 4139 dump_include_copied_directory, 4140 load_normalize_node_props, 4141 build_repcache, 4142 ] 4143 4144if __name__ == '__main__': 4145 svntest.main.run_tests(test_list) 4146 # NOTREACHED 4147 4148 4149### End of file. 4150