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