Author: julianfoad
Date: Thu Dec 10 17:31:30 2009
New Revision: 889342

URL: http://svn.apache.org/viewvc?rev=889342&view=rev
Log:
Add a test skeleton for obliterate, and a set of test helper functions that
it uses.

* subversion/tests/cmdline/obliterate_tests.py
  New file, with a skeleton of a test for obliterate. So far, this just
  exercises the obliterate operation and doesn't actually check the
  result.

* subversion/tests/cmdline/svntest/objects.py
  New file, with classes for easily performing common testing steps on a
  repository and on a WC.

Added:
    subversion/trunk/subversion/tests/cmdline/obliterate_tests.py   (with props)
    subversion/trunk/subversion/tests/cmdline/svntest/objects.py   (with props)

Added: subversion/trunk/subversion/tests/cmdline/obliterate_tests.py
URL: 
http://svn.apache.org/viewvc/subversion/trunk/subversion/tests/cmdline/obliterate_tests.py?rev=889342&view=auto
==============================================================================
--- subversion/trunk/subversion/tests/cmdline/obliterate_tests.py (added)
+++ subversion/trunk/subversion/tests/cmdline/obliterate_tests.py Thu Dec 10 
17:31:30 2009
@@ -0,0 +1,130 @@
+#!/usr/bin/env python
+#
+#  obliterate_tests.py:  testing Obliterate
+#
+#  Subversion is a tool for revision control.
+#  See http://subversion.tigris.org for more information.
+#
+# ====================================================================
+#    Licensed to the Apache Software Foundation (ASF) under one
+#    or more contributor license agreements.  See the NOTICE file
+#    distributed with this work for additional information
+#    regarding copyright ownership.  The ASF licenses this file
+#    to you under the Apache License, Version 2.0 (the
+#    "License"); you may not use this file except in compliance
+#    with the License.  You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+#    Unless required by applicable law or agreed to in writing,
+#    software distributed under the License is distributed on an
+#    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+#    KIND, either express or implied.  See the License for the
+#    specific language governing permissions and limitations
+#    under the License.
+######################################################################
+
+# General modules
+import shutil, sys, re, os
+
+# Our testing module
+import svntest
+from svntest import main, actions, wc, objects
+
+# (abbreviation)
+Item = wc.StateItem
+XFail = svntest.testcase.XFail
+Skip = svntest.testcase.Skip
+SkipUnless = svntest.testcase.SkipUnless
+
+
+######################################################################
+# Test utilities
+#
+
+def create_dd1_scenarios(wc):
+  """Create, in the initially empty repository of the SvnWC WC, the
+     obliteration test scenarios depicted in each "Example 1" in
+     <notes/obliterate/fspec-dd1/dd1-file-ops.svg>."""
+
+  # r1: base directories
+  for dir in ['f-mod', 'f-add', 'f-del', 'f-rpl', 'f-mov']:
+    wc.svn_mkdir(dir)
+  wc.svn_commit()
+
+  # r2 to r48 inclusive
+  for r in range(2, 9):
+    wc.svn_set_props('', { 'this-is-rev': str(r) })
+    wc.svn_commit()
+
+  # r49: add the files used in the scenarios
+  wc.svn_file_create_add('f-mod/F', "Pear\n")
+  wc.svn_file_create_add('f-del/F', "Pear\n")
+  wc.svn_file_create_add('f-rpl/F', "Pear\n")
+  wc.svn_file_create_add('f-mov/E', "Pear\n")  # 'E' will be moved to 'F'
+  wc.svn_commit()
+
+  # r50: the rev to be obliterated
+  wc.file_modify('f-mod/F', 'Apple\n')
+  wc.svn_file_create_add('f-add/F', 'Apple\n')
+  wc.svn_delete('f-del/F')
+  wc.svn_delete('f-rpl/F')
+  wc.svn_file_create_add('f-rpl/F', 'Apple\n')
+  wc.svn_move('f-mov/E', 'f-mov/F')
+  wc.file_modify('f-mov/F', 'Apple\n')
+  rev = wc.svn_commit(log='Rev to be obliterated')
+
+  # r51
+  for dir in ['f-mod', 'f-add', 'f-rpl', 'f-mov']:
+    wc.file_modify(dir + '/F', 'Orange\n')
+  wc.svn_commit()
+
+  return rev
+
+######################################################################
+# Tests
+#
+#   Each test must return on success or raise on failure.
+
+#----------------------------------------------------------------------
+
+def obliterate_1(sbox):
+  "test svn obliterate"
+
+  # Create empty repos and WC
+  main.create_repos(sbox.repo_dir)
+  expected_out = svntest.wc.State(sbox.wc_dir, {})
+  expected_disk = svntest.wc.State(sbox.wc_dir, {})
+  actions.run_and_verify_checkout(sbox.repo_url, sbox.wc_dir, expected_out,
+                                  expected_disk)
+
+  # Create test utility objects
+  wc = objects.SvnWC(sbox.wc_dir)
+  repo = objects.SvnRepository(sbox.repo_url, sbox.repo_dir)
+
+  os.chdir(sbox.wc_dir)
+
+  # Create scenarios ready for obliteration
+  apple_rev = create_dd1_scenarios(wc)
+
+  # Dump the repository state
+  repo.dump('before.dump')
+
+  # Obliterate d/f...@{content=apple}
+  repo.obliterate_node_rev('/d/foo', apple_rev)
+
+  # Dump the repository state
+  repo.dump('after.dump')
+
+
+########################################################################
+# Run the tests
+
+# list all tests here, starting with None:
+test_list = [ None,
+              obliterate_1,
+             ]
+
+if __name__ == '__main__':
+  svntest.main.run_tests(test_list)
+  # NOTREACHED

Propchange: subversion/trunk/subversion/tests/cmdline/obliterate_tests.py
------------------------------------------------------------------------------
    svn:executable = *

Propchange: subversion/trunk/subversion/tests/cmdline/obliterate_tests.py
------------------------------------------------------------------------------
    svn:mime-type = text/x-python

Added: subversion/trunk/subversion/tests/cmdline/svntest/objects.py
URL: 
http://svn.apache.org/viewvc/subversion/trunk/subversion/tests/cmdline/svntest/objects.py?rev=889342&view=auto
==============================================================================
--- subversion/trunk/subversion/tests/cmdline/svntest/objects.py (added)
+++ subversion/trunk/subversion/tests/cmdline/svntest/objects.py Thu Dec 10 
17:31:30 2009
@@ -0,0 +1,334 @@
+#!/usr/bin/env python
+#
+#  objects.py: Objects that keep track of state during a test
+#
+#  Subversion is a tool for revision control.
+#  See http://subversion.tigris.org for more information.
+#
+# ====================================================================
+#    Licensed to the Apache Software Foundation (ASF) under one
+#    or more contributor license agreements.  See the NOTICE file
+#    distributed with this work for additional information
+#    regarding copyright ownership.  The ASF licenses this file
+#    to you under the Apache License, Version 2.0 (the
+#    "License"); you may not use this file except in compliance
+#    with the License.  You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+#    Unless required by applicable law or agreed to in writing,
+#    software distributed under the License is distributed on an
+#    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+#    KIND, either express or implied.  See the License for the
+#    specific language governing permissions and limitations
+#    under the License.
+######################################################################
+
+# General modules
+import shutil, sys, re, os, subprocess
+
+import csvn.wc
+
+# Our testing module
+import svntest
+from svntest import actions, main, tree, verify, wc
+
+
+######################################################################
+# Helpers
+
+def local_path(path):
+  """Convert a path from internal style ('/' separators) to the local style."""
+  if path == '':
+    path = '.'
+  return os.sep.join(path.split('/'))
+
+
+def db_dump(repo_path, table):
+  """Yield a human-readable representation of the rows of the BDB table
+  TABLE in the repo at REPO_PATH.  Yield one line of text at a time.
+  Calls the external program "db_dump" which is supplied with BDB."""
+  table_path = repo_path + "/db/" + table
+  process = subprocess.Popen(["db_dump", "-p", table_path],
+                             stdout=subprocess.PIPE, universal_newlines=True)
+  retcode = process.wait()
+  assert retcode == 0
+
+  # Strip out the header and footer; copy the rest into FILE.
+  copying = False
+  for line in process.stdout.readlines():
+    if line == "HEADER=END\n":
+      copying = True;
+    elif line == "DATA=END\n":
+      break
+    elif copying:
+      yield line
+
+def crude_bdb_parse(line):
+  """Replace BDB ([klen] kval) syntax with (kval) syntax. This is a very
+  crude parser, only just good enough to make a more human-friendly output
+  in common cases. The result is not intended to be unambiguous or machine-
+  parsable."""
+  lparts = line.split('(')
+  new_lparts = []
+  for lpart in lparts:
+    rparts = lpart.split(')')
+    new_rparts = []
+    for rpart in rparts:
+      words = rpart.split(' ')
+
+      # Set new_words to all the words that we want to keep
+      new_words = []
+      last_len = None
+      last_word = None
+      for word in words:
+        if last_len is None:
+          try:
+            last_len = int(word)
+            last_word = word
+          except ValueError:
+            if last_word is not None:
+              new_words += [last_word]
+            new_words += [word]
+            last_word = None
+            last_len = None
+        else:  # print a word that was previously prefixed by its len
+          if not len(word) == last_len + word.count('\\') * 2:
+            print "## parsed wrongly: %d %d '%s'" % (last_len, len(word), word)
+          if word == "":
+            new_words += ["''"]
+          else:
+            new_words += [word]
+          last_word = None
+          last_len = None
+
+      if last_word is not None:
+        new_words += [last_word]
+
+      new_rparts += [' '.join(new_words)]
+    new_lparts += [')'.join(new_rparts)]
+  new_line = '('.join(new_lparts)
+  return new_line
+
+def dump_bdb(repo_path, dump_dir):
+  """Dump all the known BDB tables in the repository at REPO_PATH into a
+  single text file in DUMP_DIR.  Omit any "next-key" records."""
+  dump_file = dump_dir + "/all.bdb"
+  file = open(dump_file, 'w')
+  for table in ['revisions', 'transactions', 'changes', 'copies', 'nodes',
+                'node-origins', 'representations', 'checksum-reps', 'strings',
+                'locks', 'lock-tokens', 'miscellaneous', 'uuids']:
+    file.write(table + ":\n")
+    for line in db_dump(repo_path, table):
+      if line == " next-key\n":
+        break
+      file.write(crude_bdb_parse(line))
+    file.write("\n")
+  file.close()
+
+  # Dump the svn view of the repository
+  #svnadmin dump    repo_path > dump_dir + "/dump.svn"
+  #svnadmin lslocks repo_path > dump_dir + "/locks.svn"
+  #svnadmin lstxns  repo_path > dump_dir + "/txns.svn"
+
+
+######################################################################
+# Class SvnRepository
+
+class SvnRepository:
+  """An object of class SvnRepository represents a Subversion repository,
+     providing both a client-side view and a server-side view."""
+
+  def __init__(self, repo_url, repo_dir):
+    self.repo_url = repo_url
+    self.repo_absdir = os.path.abspath(repo_dir)
+
+  def dump(self, output_dir):
+    """Dump the repository into the directory OUTPUT_DIR"""
+    ldir = local_path(output_dir)
+    os.mkdir(ldir)
+    print "## SvnRepository::dump(rep_dir=" + self.repo_absdir + ")"
+
+    """Run a BDB dump on the repository"""
+    #subprocess.call(["/home/julianfoad/bin/svn-dump-bdb", self.repo_absdir, 
ldir])
+    dump_bdb(self.repo_absdir, ldir)
+
+    """Run 'svnadmin dump' on the repository."""
+    exit_code, stdout, stderr = \
+      actions.run_and_verify_svnadmin(None, None, None,
+                                      'dump', self.repo_absdir)
+    ldumpfile = local_path(output_dir + "/svnadmin.dump")
+    main.file_write(ldumpfile, ''.join(stderr))
+    main.file_append(ldumpfile, ''.join(stdout))
+
+
+  def obliterate_node_rev(self, path, rev):
+    """Obliterate the single node-rev PATH in revision REV."""
+    actions.run_and_verify_svn(None, None, [],
+                               'obliterate',
+                               self.repo_url + '/' + path + '@' + str(rev))
+
+
+######################################################################
+# Class SvnWC
+
+class SvnWC:
+  """An object of class SvnWC represents a WC, and provides methods for
+     operating on it. It keeps track of the state of the WC and of the
+     repository, so that the expected results of common operations are
+     automatically known.
+
+     Path arguments to class methods paths are relative to the WC dir and
+     in Subversion canonical form ('/' separators).
+     """
+
+  def __init__(self, wc_dir):
+    """Initialize the object to use the existing WC at path WC_DIR.
+    """
+    self.wc_absdir = os.path.abspath(wc_dir)
+    # 'state' is, at all times, the 'wc.State' representation of the state
+    # of the WC, with paths relative to 'wc_absdir'.
+    #self.state = wc.State('', {})
+    initial_wc_tree = tree.build_tree_from_wc(self.wc_absdir, load_props=True)
+    self.state = initial_wc_tree.as_state()
+    self.state.add({
+      '': wc.StateItem()
+    })
+    # This WC's idea of the repository's head revision.
+    # ### Shouldn't be in this class: should ask the repository instead.
+    self.head_rev = 0
+
+    # ### experimenting with csvn (Python Ctypes bindings)
+    #self.wc = csvn.wc.WC(self.wc_absdir)
+
+    print "## new SvnWC:"
+    print self
+
+  def __str__(self):
+    return "SvnWC(head_rev=" + str(self.head_rev) + ", state={" + \
+      str(self.state.desc) + \
+      "})"
+
+  def svn_mkdir(self, rpath):
+    lpath = local_path(rpath)
+    actions.run_and_verify_svn(None, None, [], 'mkdir', lpath)
+
+    self.state.add({
+      rpath : wc.StateItem(status='A ')
+    })
+
+#  def propset(self, pname, pvalue, *rpaths):
+#    "Set property 'pname' to value 'pvalue' on each path in 'rpaths'"
+#    local_paths = tuple([local_path(rpath) for rpath in rpaths])
+#    actions.run_and_verify_svn(None, None, [], 'propset', pname, pvalue,
+#                               *local_paths)
+
+  def svn_set_props(self, rpath, props):
+    """Change the properties of PATH to be the dictionary {name -> value} 
PROPS.
+    """
+    lpath = local_path(rpath)
+    #for prop in path's existing props:
+    #  actions.run_and_verify_svn(None, None, [], 'propdel',
+    #                             prop, lpath)
+    for prop in props:
+      actions.run_and_verify_svn(None, None, [], 'propset',
+                                 prop, props[prop], lpath)
+    self.state.tweak(rpath, props=props)
+
+  def svn_file_create_add(self, rpath, content=None, props=None):
+    "Make and add a file with some default content, and keyword expansion."
+    lpath = local_path(rpath)
+    ldirname, filename = os.path.split(lpath)
+    if content is None:
+      # Default content
+      content = "This is the file '" + filename + "'.\n" + \
+                "Last changed in '$Revision$'.\n"
+    main.file_write(lpath, content)
+    actions.run_and_verify_svn(None, None, [], 'add', lpath)
+
+    self.state.add({
+      rpath : wc.StateItem(status='A ')
+    })
+    if props is None:
+      # Default props
+      props = {
+        'svn:keywords': 'Revision'
+      }
+    self.svn_set_props(rpath, props)
+
+  def file_modify(self, rpath, content=None, props=None):
+    "Make text and property mods to a WC file."
+    lpath = local_path(rpath)
+    if content is not None:
+      #main.file_append(lpath, "An extra line.\n")
+      #actions.run_and_verify_svn(None, None, [], 'propset',
+      #                           'newprop', 'v', lpath)
+      main.file_write(lpath, content)
+      self.state.tweak(rpath, content=content)
+    if props is not None:
+      self.set_props(rpath, props)
+      self.state.tweak(rpath, props=props)
+
+  def svn_move(self, rpath1, rpath2, parents=False):
+    """Move/rename the existing WC item RPATH1 to become RPATH2.
+    RPATH2 must not already exist. If PARENTS is true, any missing parents
+    of RPATH2 will be created."""
+    lpath1 = local_path(rpath1)
+    lpath2 = local_path(rpath2)
+    args = [lpath1, lpath2]
+    if parents:
+      args += ['--parents']
+    actions.run_and_verify_svn(None, None, [], 'copy', *args)
+    self.state.add({
+      rpath2: self.state.desc[rpath1]
+    })
+    self.state.remove(rpath1)
+
+  def svn_copy(self, rpath1, rpath2, parents=False, rev=None):
+    """Copy the existing WC item RPATH1 to become RPATH2.
+    RPATH2 must not already exist. If PARENTS is true, any missing parents
+    of RPATH2 will be created. If REV is not None, copy revision REV of
+    the node identified by WC item RPATH1."""
+    lpath1 = local_path(rpath1)
+    lpath2 = local_path(rpath2)
+    args = [lpath1, lpath2]
+    if rev is not None:
+      args += ['-r', rev]
+    if parents:
+      args += ['--parents']
+    actions.run_and_verify_svn(None, None, [], 'copy', *args)
+    self.state.add({
+      rpath2: self.state.desc[rpath1]
+    })
+
+  def svn_delete(self, rpath):
+    "Delete a WC path locally."
+    lpath = local_path(rpath)
+    actions.run_and_verify_svn(None, None, [], 'delete', lpath)
+
+  def svn_commit(self, rpath='', log=''):
+    "Commit a WC path (recursively). Return the new revision number."
+    lpath = local_path(rpath)
+    actions.run_and_verify_svn(None, verify.AnyOutput, [],
+                               'commit', '-m', log, lpath)
+    actions.run_and_verify_update(lpath, None, None, None)
+    self.head_rev += 1
+    print "## head-rev == " + str(self.head_rev)
+    return self.head_rev
+
+#  def svn_merge(self, rev_spec, source, target, exp_out=None):
+#    """Merge a single change from path 'source' to path 'target'.
+#    SRC_CHANGE_NUM is either a number (to cherry-pick that specific change)
+#    or a command-line option revision range string such as '-r10:20'."""
+#    lsource = local_path(source)
+#    ltarget = local_path(target)
+#    if isinstance(rev_spec, int):
+#      rev_spec = '-c' + str(rev_spec)
+#    if exp_out is None:
+#      target_re = re.escape(target)
+#      exp_1 = "--- Merging r.* into '" + target_re + ".*':"
+#      exp_2 = "(A |D |[UG] | [UG]|[UG][UG])   " + target_re + ".*"
+#      exp_out = verify.RegexOutput(exp_1 + "|" + exp_2)
+#    actions.run_and_verify_svn(None, exp_out, [],
+#                               'merge', rev_spec, lsource, ltarget)
+

Propchange: subversion/trunk/subversion/tests/cmdline/svntest/objects.py
------------------------------------------------------------------------------
    svn:mime-type = text/x-python


Reply via email to