~ubuntu-branches/debian/sid/subversion/sid

« back to all changes in this revision

Viewing changes to subversion/tests/cmdline/svntest/sandbox.py

  • Committer: Package Import Robot
  • Author(s): James McCoy
  • Date: 2015-08-07 21:32:47 UTC
  • mfrom: (0.2.15) (4.1.7 experimental)
  • Revision ID: package-import@ubuntu.com-20150807213247-ozyewtmgsr6tkewl
Tags: 1.9.0-1
* Upload to unstable
* New upstream release.
  + Security fixes
    - CVE-2015-3184: Mixed anonymous/authenticated path-based authz with
      httpd 2.4
    - CVE-2015-3187: svn_repos_trace_node_locations() reveals paths hidden
      by authz
* Add >= 2.7 requirement for python-all-dev Build-Depends, needed to run
  tests.
* Remove Build-Conflicts against ruby-test-unit.  (Closes: #791844)
* Remove patches/apache_module_dependency in favor of expressing the
  dependencies in authz_svn.load/dav_svn.load.
* Build-Depend on apache2-dev (>= 2.4.16) to ensure ap_some_authn_required()
  is available when building mod_authz_svn and Depend on apache2-bin (>=
  2.4.16) for runtime support.

Show diffs side-by-side

added added

removed removed

Lines of Context:
24
24
import os
25
25
import shutil
26
26
import copy
27
 
import urllib
28
27
import logging
 
28
import re
29
29
 
30
30
import svntest
31
31
 
32
32
logger = logging.getLogger()
33
33
 
34
34
 
 
35
def make_mirror(sbox, source_prop_encoding=None):
 
36
  """Make a mirror of the repository in SBOX.
 
37
  """
 
38
  # Set up the mirror repository.
 
39
  dest_sbox = sbox.clone_dependent()
 
40
  dest_sbox.build(create_wc=False, empty=True)
 
41
  exit_code, output, errput = svntest.main.run_svnlook("uuid", sbox.repo_dir)
 
42
  svntest.actions.run_and_verify_svnadmin2(None, None, 0,
 
43
                                           'setuuid', dest_sbox.repo_dir,
 
44
                                           output[0][:-1])
 
45
  svntest.actions.enable_revprop_changes(dest_sbox.repo_dir)
 
46
 
 
47
  repo_url = sbox.repo_url
 
48
  dest_repo_url = dest_sbox.repo_url
 
49
 
 
50
  # Synchronize it.
 
51
  args = (svntest.main.svnrdump_crosscheck_authentication,)
 
52
  if source_prop_encoding:
 
53
    args = args + ("--source-prop-encoding=" + source_prop_encoding,)
 
54
  svntest.actions.run_and_verify_svnsync(svntest.verify.AnyOutput, [],
 
55
                                         "initialize",
 
56
                                         dest_repo_url, repo_url, *args)
 
57
  svntest.actions.run_and_verify_svnsync(None, [],
 
58
                                         "synchronize",
 
59
                                         dest_repo_url, repo_url, *args)
 
60
 
 
61
  return dest_sbox
 
62
 
 
63
def verify_mirror(repo_url, repo_dir, expected_dumpfile):
 
64
  """Compare the repository content at REPO_URL/REPO_DIR with that in
 
65
     EXPECTED_DUMPFILE (which is a non-delta dump).
 
66
  """
 
67
  # Remove some SVNSync-specific housekeeping properties from the
 
68
  # mirror repository in preparation for the comparison dump.
 
69
  for prop_name in ("svn:sync-from-url", "svn:sync-from-uuid",
 
70
                    "svn:sync-last-merged-rev"):
 
71
    svntest.actions.run_and_verify_svn(
 
72
      None, [], "propdel", "--revprop", "-r", "0",
 
73
      prop_name, repo_url)
 
74
  # Create a dump file from the mirror repository.
 
75
  dumpfile_s_n = svntest.actions.run_and_verify_dump(repo_dir)
 
76
  # Compare the mirror's dumpfile, ignoring any expected differences:
 
77
  # The original dumpfile in some cases lacks 'Text-content-sha1' headers;
 
78
  # the mirror dump always has them -- ### Why?
 
79
  svnsync_headers_always = re.compile("Text-content-sha1: ")
 
80
  dumpfile_a_n_cmp = [l for l in expected_dumpfile
 
81
                      if not svnsync_headers_always.match(l)]
 
82
  dumpfile_s_n_cmp = [l for l in dumpfile_s_n
 
83
                      if not svnsync_headers_always.match(l)]
 
84
  svntest.verify.compare_dump_files(None, None,
 
85
                                    dumpfile_a_n_cmp,
 
86
                                    dumpfile_s_n_cmp)
 
87
 
 
88
 
35
89
class Sandbox:
36
90
  """Manages a sandbox (one or more repository/working copy pairs) for
37
91
  a test to operate within."""
46
100
    # This flag is set to True by build() and returned by is_built()
47
101
    self._is_built = False
48
102
 
49
 
  def _set_name(self, name, read_only=False):
 
103
    self.was_cwd = os.getcwd()
 
104
 
 
105
  def _set_name(self, name, read_only=False, empty=False):
50
106
    """A convenience method for renaming a sandbox, useful when
51
107
    working with multiple repositories in the same unit test."""
52
108
    if not name is None:
54
110
    self.read_only = read_only
55
111
    self.wc_dir = os.path.join(svntest.main.general_wc_dir, self.name)
56
112
    self.add_test_path(self.wc_dir)
57
 
    if not read_only:
 
113
    if empty or not read_only:  # use a local repo
58
114
      self.repo_dir = os.path.join(svntest.main.general_repo_dir, self.name)
59
115
      self.repo_url = (svntest.main.options.test_area_url + '/'
60
 
                       + urllib.pathname2url(self.repo_dir))
 
116
                       + svntest.wc.svn_uri_quote(
 
117
                                self.repo_dir.replace(os.path.sep, '/')))
61
118
      self.add_test_path(self.repo_dir)
62
119
    else:
63
120
      self.repo_dir = svntest.main.pristine_greek_repos_dir
64
121
      self.repo_url = svntest.main.pristine_greek_repos_url
65
122
 
66
 
    ### TODO: Move this into to the build() method
67
 
    # For dav tests we need a single authz file which must be present,
68
 
    # so we recreate it each time a sandbox is created with some default
69
 
    # contents, making sure that an empty file is never present
70
123
    if self.repo_url.startswith("http"):
71
 
      # this dir doesn't exist out of the box, so we may have to make it
72
 
      if not os.path.exists(svntest.main.work_dir):
73
 
        os.makedirs(svntest.main.work_dir)
74
124
      self.authz_file = os.path.join(svntest.main.work_dir, "authz")
75
 
      tmp_authz_file = os.path.join(svntest.main.work_dir, "authz-" + self.name)
76
 
      open(tmp_authz_file, 'w').write("[/]\n* = rw\n")
77
 
      shutil.move(tmp_authz_file, self.authz_file)
78
125
      self.groups_file = os.path.join(svntest.main.work_dir, "groups")
79
 
 
80
 
    # For svnserve tests we have a per-repository authz file, and it
81
 
    # doesn't need to be there in order for things to work, so we don't
82
 
    # have any default contents.
83
126
    elif self.repo_url.startswith("svn"):
84
127
      self.authz_file = os.path.join(self.repo_dir, "conf", "authz")
85
128
      self.groups_file = os.path.join(self.repo_dir, "conf", "groups")
102
145
      shutil.copytree(self.wc_dir, clone.wc_dir, symlinks=True)
103
146
    return clone
104
147
 
105
 
  def build(self, name=None, create_wc=True, read_only=False,
 
148
  def build(self, name=None, create_wc=True, read_only=False, empty=False,
106
149
            minor_version=None):
107
150
    """Make a 'Greek Tree' repo (or refer to the central one if READ_ONLY),
 
151
       or make an empty repo if EMPTY is true,
108
152
       and check out a WC from it (unless CREATE_WC is false). Change the
109
153
       sandbox's name to NAME. See actions.make_repo_and_wc() for details."""
110
 
    self._set_name(name, read_only)
111
 
    svntest.actions.make_repo_and_wc(self, create_wc, read_only, minor_version)
 
154
    self._set_name(name, read_only, empty)
 
155
    self._ensure_authz()
 
156
    svntest.actions.make_repo_and_wc(self, create_wc, read_only, empty,
 
157
                                     minor_version)
112
158
    self._is_built = True
113
159
 
 
160
  def _ensure_authz(self):
 
161
    "make sure the repository is accessible"
 
162
 
 
163
    if self.repo_url.startswith("http"):
 
164
      default_authz = "[/]\n* = rw\n"
 
165
 
 
166
      if (svntest.main.options.parallel == 0
 
167
          and (not os.path.isfile(self.authz_file)
 
168
               or open(self.authz_file,'r').read() != default_authz)):
 
169
 
 
170
        tmp_authz_file = os.path.join(svntest.main.work_dir, "authz-" + self.name)
 
171
        open(tmp_authz_file, 'w').write(default_authz)
 
172
        shutil.move(tmp_authz_file, self.authz_file)
 
173
 
114
174
  def authz_name(self, repo_dir=None):
115
175
    "return this sandbox's name for use in an authz file"
116
176
    repo_dir = repo_dir or self.repo_dir
135
195
    path = (os.path.join(svntest.main.general_repo_dir, self.name)
136
196
            + '.' + suffix)
137
197
    url = svntest.main.options.test_area_url + \
138
 
                                        '/' + urllib.pathname2url(path)
 
198
                                        '/' + svntest.wc.svn_uri_quote(
 
199
                                                path.replace(os.path.sep, '/'))
139
200
    self.add_test_path(path, remove)
140
201
    return path, url
141
202
 
185
246
       of this sbox, or relative to OS-style path WC_DIR if supplied."""
186
247
    if wc_dir is None:
187
248
      wc_dir = self.wc_dir
188
 
    return os.path.join(wc_dir, svntest.wc.to_ospath(relpath))
 
249
 
 
250
    if relpath == '':
 
251
      return wc_dir
 
252
    else:
 
253
      return os.path.join(wc_dir, svntest.wc.to_ospath(relpath))
189
254
 
190
255
  def ospaths(self, relpaths, wc_dir=None):
191
256
    """Return a list of RELPATHS but with each path converted to an OS-style
212
277
                                  temporary and 'TEMP' or 'PERM',
213
278
                                  parts[1])
214
279
 
 
280
  def file_protocol_url(self):
 
281
    """get a file:// url pointing to the repository"""
 
282
    return svntest.main.file_scheme_prefix + \
 
283
           svntest.wc.svn_uri_quote(
 
284
                os.path.abspath(self.repo_dir).replace(os.path.sep, '/'))
 
285
 
215
286
  def simple_update(self, target=None, revision='HEAD'):
216
287
    """Update the WC or TARGET.
217
288
       TARGET is a relpath relative to the WC."""
317
388
        raise Exception("Unexpected line '" + line + "' in proplist output" + str(out))
318
389
    return props
319
390
 
320
 
  def simple_add_symlink(self, dest, target):
321
 
    """Create a symlink TARGET pointing to DEST and add it to subversion"""
 
391
  def simple_symlink(self, dest, target):
 
392
    """Create a symlink TARGET pointing to DEST"""
322
393
    if svntest.main.is_posix_os():
323
394
      os.symlink(dest, self.ospath(target))
324
395
    else:
325
396
      svntest.main.file_write(self.ospath(target), "link %s" % dest)
 
397
 
 
398
  def simple_add_symlink(self, dest, target, add=True):
 
399
    """Create a symlink TARGET pointing to DEST and add it to subversion"""
 
400
    self.simple_symlink(dest, target)
326
401
    self.simple_add(target)
327
 
    if not svntest.main.is_posix_os():
328
 
      # '*' is evaluated on Windows
 
402
    if not svntest.main.is_posix_os():      # '*' is evaluated on Windows
329
403
      self.simple_propset('svn:special', 'X', target)
330
404
 
331
405
  def simple_add_text(self, text, *targets):
360
434
  def simple_append(self, dest, contents, truncate=False):
361
435
    """Append CONTENTS to file DEST, optionally truncating it first.
362
436
       DEST is a relpath relative to the WC."""
363
 
    open(self.ospath(dest), truncate and 'w' or 'a').write(contents)
 
437
    open(self.ospath(dest), truncate and 'wb' or 'ab').write(contents)
364
438
 
365
439
  def simple_lock(self, *targets):
366
440
    """Lock TARGETS in the WC.
369
443
    targets = self.ospaths(targets)
370
444
    svntest.main.run_svn(False, 'lock', *targets)
371
445
 
 
446
  def youngest(self):
 
447
    _, output, _ = svntest.actions.run_and_verify_svnlook(
 
448
                     svntest.verify.AnyOutput, [],
 
449
                     'youngest', self.repo_dir)
 
450
    youngest = int(output[0])
 
451
    return youngest
 
452
 
 
453
  def verify_repo(self):
 
454
    """
 
455
    """
 
456
    svnrdump_headers_missing = re.compile(
 
457
        "Text-content-sha1: .*|Text-copy-source-md5: .*|"
 
458
        "Text-copy-source-sha1: .*|Text-delta-base-sha1: .*"
 
459
    )
 
460
    svnrdump_headers_always = re.compile(
 
461
        "Prop-delta: .*"
 
462
    )
 
463
 
 
464
    dumpfile_a_n = svntest.actions.run_and_verify_dump(self.repo_dir,
 
465
                                                       deltas=False)
 
466
    dumpfile_a_d = svntest.actions.run_and_verify_dump(self.repo_dir,
 
467
                                                       deltas=True)
 
468
    dumpfile_r_d = svntest.actions.run_and_verify_svnrdump(
 
469
      None, svntest.verify.AnyOutput, [], 0, 'dump', '-q', self.repo_url,
 
470
      svntest.main.svnrdump_crosscheck_authentication)
 
471
 
 
472
    # Compare the two deltas dumpfiles, ignoring expected differences
 
473
    dumpfile_a_d_cmp = [l for l in dumpfile_a_d
 
474
                       if not svnrdump_headers_missing.match(l)
 
475
                                and not svnrdump_headers_always.match(l)]
 
476
    dumpfile_r_d_cmp = [l for l in dumpfile_r_d
 
477
                       if not svnrdump_headers_always.match(l)]
 
478
    # Ignore differences in number of blank lines between node records,
 
479
    # as svnrdump puts 3 whereas svnadmin puts 2 after a replace-with-copy.
 
480
    svntest.verify.compare_dump_files(None, None,
 
481
                                      dumpfile_a_d_cmp,
 
482
                                      dumpfile_r_d_cmp,
 
483
                                      ignore_number_of_blank_lines=True)
 
484
 
 
485
    # Try loading the dump files.
 
486
    # For extra points, load each with the other tool:
 
487
    #   svnadmin dump | svnrdump load
 
488
    #   svnrdump dump | svnadmin load
 
489
    repo_dir_a_n, repo_url_a_n = self.add_repo_path('load_a_n')
 
490
    svntest.main.create_repos(repo_dir_a_n)
 
491
    svntest.actions.enable_revprop_changes(repo_dir_a_n)
 
492
    svntest.actions.run_and_verify_svnrdump(
 
493
      dumpfile_a_n, svntest.verify.AnyOutput, [], 0, 'load', repo_url_a_n,
 
494
      svntest.main.svnrdump_crosscheck_authentication)
 
495
 
 
496
    repo_dir_a_d, repo_url_a_d = self.add_repo_path('load_a_d')
 
497
    svntest.main.create_repos(repo_dir_a_d)
 
498
    svntest.actions.enable_revprop_changes(repo_dir_a_d)
 
499
    svntest.actions.run_and_verify_svnrdump(
 
500
      dumpfile_a_d, svntest.verify.AnyOutput, [], 0, 'load', repo_url_a_d,
 
501
      svntest.main.svnrdump_crosscheck_authentication)
 
502
 
 
503
    repo_dir_r_d, repo_url_r_d = self.add_repo_path('load_r_d')
 
504
    svntest.main.create_repos(repo_dir_r_d)
 
505
    svntest.actions.run_and_verify_load(repo_dir_r_d, dumpfile_r_d)
 
506
 
 
507
    # Dump the loaded repositories in the same way; expect exact equality
 
508
    reloaded_dumpfile_a_n = svntest.actions.run_and_verify_dump(repo_dir_a_n)
 
509
    reloaded_dumpfile_a_d = svntest.actions.run_and_verify_dump(repo_dir_a_d)
 
510
    reloaded_dumpfile_r_d = svntest.actions.run_and_verify_dump(repo_dir_r_d)
 
511
    svntest.verify.compare_dump_files(None, None,
 
512
                                      reloaded_dumpfile_a_n,
 
513
                                      reloaded_dumpfile_a_d,
 
514
                                      ignore_uuid=True)
 
515
    svntest.verify.compare_dump_files(None, None,
 
516
                                      reloaded_dumpfile_a_d,
 
517
                                      reloaded_dumpfile_r_d,
 
518
                                      ignore_uuid=True)
 
519
 
 
520
    # Run each dump through svndumpfilter and check for no further change.
 
521
    for dumpfile in [dumpfile_a_n,
 
522
                     dumpfile_a_d,
 
523
                     dumpfile_r_d
 
524
                     ]:
 
525
      ### No buffer size seems to work for update_tests-2. So skip that test?
 
526
      ### (Its dumpfile size is ~360 KB non-delta, ~180 KB delta.)
 
527
      if len(''.join(dumpfile)) > 100000:
 
528
        continue
 
529
 
 
530
      exit_code, dumpfile2, errput = svntest.main.run_command_stdin(
 
531
        svntest.main.svndumpfilter_binary, None, -1, True,
 
532
        dumpfile, '--quiet', 'include', '/')
 
533
      assert not exit_code and not errput
 
534
      # Ignore empty prop sections in the input file during comparison, as
 
535
      # svndumpfilter strips them.
 
536
      # Ignore differences in number of blank lines between node records,
 
537
      # as svndumpfilter puts 3 instead of 2 after an add or delete record.
 
538
      svntest.verify.compare_dump_files(None, None, dumpfile, dumpfile2,
 
539
                                        expect_content_length_always=True,
 
540
                                        ignore_empty_prop_sections=True,
 
541
                                        ignore_number_of_blank_lines=True)
 
542
 
 
543
    # Run the repository through 'svnsync' and check that this does not
 
544
    # change the repository content. (Don't bother if it's already been
 
545
    # created by svnsync.)
 
546
    if "svn:sync-from-url\n" not in dumpfile_a_n:
 
547
      dest_sbox = make_mirror(self)
 
548
      verify_mirror(dest_sbox.repo_url, dest_sbox.repo_dir, dumpfile_a_n)
 
549
 
 
550
  def verify(self, skip_cross_check=False):
 
551
    """Do additional testing that should hold for any sandbox, such as
 
552
       verifying that the repository can be dumped.
 
553
    """
 
554
    if (not skip_cross_check
 
555
        and svntest.main.tests_verify_dump_load_cross_check()):
 
556
      if self.is_built() and not self.read_only:
 
557
        # verify that we can in fact dump the repo
 
558
        # (except for the few tests that deliberately corrupt the repo)
 
559
        os.chdir(self.was_cwd)
 
560
        if os.path.exists(self.repo_dir):
 
561
          logger.info("VERIFY: running dump/load cross-check")
 
562
          self.verify_repo()
 
563
      else:
 
564
        logger.info("VERIFY: WARNING: skipping dump/load cross-check:"
 
565
                    " is-built=%s, read-only=%s"
 
566
                    % (self.is_built() and "true" or "false",
 
567
                       self.read_only and "true" or "false"))
 
568
    pass
372
569
 
373
570
def is_url(target):
374
571
  return (target.startswith('^/')