[svn.haxx.se] · SVN Dev · SVN Users · SVN Org · TSVN Dev · TSVN Users · Subclipse Dev · Subclipse Users · this month's index

More Test Code

From: Sam TH <sam_at_uchicago.edu>
Date: 2001-04-20 15:16:39 CEST

Well, in the interests of not doing my homework, I've written some
more test code. We know can generate trees from entries files (of
either type) and then compare those trees against other kinds of
trees. I've written another test to use this to make sure the
checked out wc dir matches it's own entries files.

Attached is the huge diff against current test code, plus all of
svn_tree.py. There are a couple extra routines in there that aren't
used yet.

#!/usr/bin/env python
#
# svn_tree.py: tools for comparing directory trees
#
# Subversion is a tool for revision control.
# See http://subversion.tigris.org for more information.
#
# ====================================================================
# Copyright (c) 2001 Sam Tobin-Hochstadt. All rights reserved.
#
# This software is licensed as described in the file COPYING, which
# you should have received as part of this distribution. The terms
# are also available at http://subversion.tigris.org/license-1.html.
# If newer versions of this license are posted there, you may use a
# newer version instead, at your option.
#
######################################################################

class SVNTreeNode:
    """A node in the directory tree.

    If CHILDREN is None, then the node is a file.
    Otherwise, CHILDREN is a list of the nodes making up that
    directorie's children.
    
    NAME is simply the name of the file of directory"""
    
    def __init__(self, name, children=None):
        self.name = name
        self.children = children

    def __cmp__(self, other):
        "Allows comparison on trees. Only compares the names."

        # could this be rewritten as 'return
        # self.name.__cmp__(other.name)' ??
        if self.name == other.name:
            return 0
        if self.name > other.name:
            return 1
        else:
            return -1

    def add_child(self, newchild):
        """Add a child to a node. If the child already exists. merget
        the new node's children with the current child node's
        children. The merge is done recursively."""
        if self.children is None:
            self.children = []
        n = 0
        for a in self.children:
            if a.name == newchild.name:
                n = 1
                break
        if n == 0:
            self.children.append(newchild)
        elif not (newchild.children is None):
            for i in newchild.children:
                a.add_child(i)

# reserved name of the root of the tree
root_node_name = "__SVN_ROOT_NODE"

class SVNTreeError(Exception):
    "Exception raised if you screw up in this module."
    pass

class SVNTreeUnequal(Exception):
    "Exception raised if two trees are unequal."
    pass

########################################################################
# Code for handling creating trees from lists of pathnames. This will
# most likely be used for generating expected trees. Also useful for
# handling the output of SVN commands
#

def create_from_path(path):
    """Create and return a new tree, given a path representing a single
    entry into that tree."""
    # internal use only

    # get a list of all the names in the path
    # each of these will be a child of the former
    elements = path.split("/")
    if len(elements) == 0:
        raise SVNTreeError

    root_node = SVNTreeNode(elements[0], None)

    add_elements_as_path(root_node, elements[1:])

    return root_node

def add_elements_as_path(top_node, path_list):
    """Add the elements in PATH as if they were a single path below
    TOP_NODE."""
    
    # The idea of this function is to take a list like so:
    # ['A', 'B', 'C'] and a top node, say 'Z', and generate a tree
    # like this:
    #
    # Z -> A -> B -> C
    #
    # where 1 -> 2 means 2 is a child of 1.
    #
    # It's sort of hard to describe clearly, but it's quite useful.
    
    prev_node = top_node
    for i in path_list:
        new_node = SVNTreeNode(i, None)
        prev_node.add_child(new_node)
        prev_node = new_node

def build_tree_from_paths(paths):
    "Take PATHS and create a tree from them."

    root = SVNTreeNode(root_node_name, None)
    
    for i in paths:
        root.add_child(create_from_path(i))

    return root

#######################################################################
# Code for creating trees from the actual on-disk files. This is the
# most fundamental result, and (obviously) the most important for SVN
# to get right.
#

import os.path # lots of stuff
import os

def handle_dir(path, current_parent, ignore_cvs):

    # get a list of all the files

    all_files = os.listdir(path)
    files = []
    dirs = []

    # put dirs and files in their own lists, and remove SVN dirs

    for f in all_files:
        f = os.path.join(path, f)
        if (os.path.isdir(f) and os.path.basename(f) != 'SVN'
            and (not(ignore_cvs) or os.path.basename(f) != 'CVS')):
            dirs.append(f)
        elif os.path.isfile(f):
            files.append(f)

    # add each file as a child of CURRENT_PARENT

    for f in files:
        current_parent.add_child(SVNTreeNode(os.path.basename(f)))

    # for each subdir, create a node, walk its tree, add it as a child

    for d in dirs:
        new_dir_node = SVNTreeNode(os.path.basename(d))
        handle_dir(d, new_dir_node, ignore_cvs)
        current_parent.add_child(new_dir_node)

def build_tree_from_wc(wc_path, ignore_cvs=0):
    """Takes WC_PATH as the path to a working copy. Walks the tree below
    that path, and creates the tree based on the actual found
    files. If IGNORE_CVS is true, then exclude CVS dirs from the tree
    as well as SVN dirs."""

    root = SVNTreeNode(root_node_name, None)

    # Walk the tree recursively
    handle_dir(os.path.normpath(wc_path), root, ignore_cvs)

    return root

########################################################################
# Code for creating trees from entries files on disk. It does the
# right thing, regardless of whether the entries files found are SVN or
# CVS. Just don't use it ones with both, or it will get very
# confused.
# ### samth todo -- fix this
# ### samth todo -- use the ancestor field, instead of the path
#

import svn_entry

def handle_entries_file(path, current_parent):
    
    # get a dictionary of all the entries in the entries file

    if os.path.exists(os.path.join(path, "SVN/entries")):
        entry_path = os.path.join(path, "SVN/entries")
    elif os.path.exists(os.path.join(path, "CVS/Entries")):
        entry_path = os.path.join(path, "CVS/Entries")

    entry_dict = svn_entry.get_entries(entry_path)

    files = []
    dirs = []

    # put dirs and files in their own lists

    for k in entry_dict.keys():
        if entry_dict[k].atts['kind'] == 'file':
            files.append(k)
        elif (entry_dict[k].atts['kind'] == 'dir') and (k != ""):
            dirs.append(k)
            
    # add each file as a child of CURRENT_PARENT

    for f in files:
        current_parent.add_child(SVNTreeNode(f))

    # for each subdir, create a node, walk its tree, add it as a child

    for d in dirs:
        new_dir_node = SVNTreeNode(d)
        handle_entries_file(os.path.join(path, d), new_dir_node)
        current_parent.add_child(new_dir_node)

def build_tree_from_entries(wc_path):

    """Takes WC_PATH as the path to a working copy. Walks the tree below
    that path, and creates the tree based on the actual found
    files."""
    
    root = SVNTreeNode(root_node_name, None)

    # Walk the tree recursively
    handle_entries_file(os.path.normpath(wc_path), root)

    return root

#######################################################################
# Code for handling trees generally.
#

def compare_trees(a, b):
    """Check whether A and B are the equal as trees. This comparison
    does not take into account the order of children."""
    try:
        if a.name != b.name:
            print "Error: %s is not the same as %s." % (a.name, b.name)
            raise SVNTreeUnequal
        if a.children is None:
            if b.children is None:
                # They are both files with the same name, and so are equal.
                return 1
            else:
                # A is a file and B is a dir.
                raise SVNTreeUnequal
        if b.children is None:
            # B is a file and A is a dir.
            raise SVNTreeUnequal

        # order of children doesn't matter
        a.children.sort()
        b.children.sort()
        for i in range(max(len(a.children), len(b.children))):
            compare_trees(a.children[i], b.children[i])
    except IndexError:
        # We ran off the end of one of the lists
        print "Error: unequal number of children"
        # Propogate the error upward
        raise SVNTreeUnequal
    except SVNTreeUnequal:
        if a.name == root_node_name:
            return 0
        else:
            # TODO Some nice pretty-printing of structure here
            print "Unequal at node %s" % a.name
            raise SVNTreeUnequal
    return 1

    

def dump_tree(n,indent=""):
    "Print out a nice representation of the tree."
    # Code partially stolen from Dave Beazley

    # We have to take the len(n.children), so we use a substitute.
    if n.children is None:
        tmp_children = []
    else:
        tmp_children = n.children

    if n.name == root_node_name:
        print "%s%s" % (indent, "Top of the tree")
    else:
        print "%s%s" % (indent, n.name)

    # Shift the lines over
    indent = indent.replace("-"," ")
    indent = indent.replace("+"," ")
    for i in range(len(tmp_children)):
        c = tmp_children[i]
        if i == len(tmp_children)-1:
            # We're at the end of the list
            dump_tree(c,indent + " +-- ")
        else:
            dump_tree(c,indent + " |-- ")

#########################################
## Test code -- to be removed

greek_paths = ['iota', 'A', 'A/mu', 'A/B', 'A/B/lambda', 'A/B/E',
               'A/B/E/alpha', 'A/B/E/beta', 'A/B/F', 'A/C', 'A/D',
               'A/D/gamma', 'A/D/G', 'A/D/G/pi', 'A/D/G/rho', 'A/D/G/tau',
               'A/D/H', 'A/D/H/chi', 'A/D/H/psi', 'A/D/H/omega']

greek_paths2 = ['iota', 'A', 'A/mu', 'A/B', 'A/B/lambda', 'A/B/E',
                'A/D/gamma', 'A/D/G', 'A/D/G/pi', 'A/D/G/rho', 'A/D/G/tau',
                'A/B/E/alpha', 'A/B/E/beta', 'A/B/F', 'A/C', 'A/D',
                'A/D/H', 'A/D/H/chi', 'A/D/H/psi', 'A/D/H/omega']

greek_paths3 = ['iota', 'A', 'A/mu', 'A/B', 'A/B/lambda', 'A/B/E',
                'A/D/gamma', 'A/D/G', 'A/D/G/pi', 'A/D/G/rho', 'A/D/G/tau',
                'A/B/E/alpha', 'A/B/E/beta', 'A/B/F', 'A/C', 'A/D',
                'A/D/H', 'A/D/H/chi', 'A/D/H/psi', 'A/D/H/omegafoo']

if __name__ == '__main__':
    # We were invoked on the command line
    test_tree = build_tree_from_paths(greek_paths)
    test_tree2 = build_tree_from_paths(greek_paths2)
    test_tree3 = build_tree_from_paths(greek_paths3)
    
    dump_tree(test_tree)
    dump_tree(test_tree2)
    
    if 1 == compare_trees(test_tree, test_tree3):
        print "they are equal"
    else:
        print "they are not equal"

### End of file.
# local variables:
# eval: (load-file "../../../svn-dev.el")
# end:
    

Index: svn_entry.py
===================================================================
RCS file: /cvs/subversion/subversion/tests/clients/cmdline/svn_entry.py,v
retrieving revision 1.4
diff -u -r1.4 svn_entry.py
--- svn_entry.py 2001/04/12 18:11:02 1.4
+++ svn_entry.py 2001/04/20 13:00:21
@@ -26,12 +26,13 @@
 # kind, revision, ancestor. Other optional keys *might* be
 # present, such as prop-time, text-time, add, delete, conflict.
 
-import xml.parsers.expat # you may need to install this package
+import xml.parsers.expat # you may need to install this package
+import copy # for deepcopy
 
 class svn_entry:
   "An object that represents an entry from an 'entries' file."
 
- def __init__(self, attributes): # constructor
+ def __init__(self, attributes={}): # constructor
     self.atts = attributes
 
   def prettyprint(self):
@@ -50,6 +51,25 @@
     self.parser = xml.parsers.expat.ParserCreate()
     self.parser.StartElementHandler = self.handle_start_tag
 
+ def is_svn_entries(self, file):
+ "Determine if FILE is a CVS or an SVN entries file."
+
+ # read the first line, and strip off whitespace
+ first_line = file.readline().strip()
+
+ # strip of any blank lines at the beginning
+ while first_line[0] == '\n':
+ first_line = file.readline().strip()
+
+ # go back to the beginning, so that parsing will work properly
+ file.seek(0)
+
+ if first_line.find("xml") != -1:
+ return 1
+
+ elif (first_line[0] == '/') or (first_line[0] == 'D'):
+ return 0
+
   def handle_start_tag(self, name, attrs):
     "Expat callback that receives a new open-tag."
 
@@ -71,14 +91,73 @@
                   
       self.entry_dict[attrs['name']] = entry # store the new entry
 
+ def svn_parse_file(self, file):
+ self.parser.ParseFile(file)
+
+ def cvs_parse_file(self, file):
+ "Parse the CVS 'Entries' file FILE into a dictionary."
+
+ # Create a parent entry to represent the current directory
+ # This directory isn't in the Entries file, and isn't really under
+ # CVS's control, but SVN has an entry for it, and we want to be
+ # compatible
+ parent = svn_entry({})
+ parent.atts['name'] = "" # for consistency with subversion
+ # this is arbitary, since it isn't versioned by CVS
+ parent.atts['revision'] = "1"
+ # FIXME This is wrong. How can we get this info? Do we want to
+ # fix this here, or when we create the tree? Doing it here
+ # requires info not in the Entries file
+ parent.atts['ancestor'] = "anni"
+ parent.atts['kind'] = "dir"
+ self.entry_dict[parent.atts['name']] = parent
+
+ lines = file.readlines()
+ for l in lines:
+ entry = svn_entry()
+ if l[0] == 'D': # Directories begin with D
+ if len(l) == 1:
+ # If a directory contains no subdirectories, the last line
+ # of the entries file is a single 'D'
+ continue
+ else:
+ entry.atts['kind'] = 'dir'
+ l = l[1:]
+ else:
+ entry.atts['kind'] = 'file'
+ props = l.split("/")
+ if l[0] == '\n': # skip empty entries
+ continue
+ entry.atts['name'] = props[1]
+ entry.atts['revision'] = props[2]
+ entry.atts['text-time'] = props[3]
+ entry.atts['ancestor'] = parent.atts['ancestor'] + '/' \
+ + entry.atts['name']
+
+ self.entry_dict[entry.atts['name']] = copy.deepcopy(entry) # store the new entry
+
+ def parse_file(self, file):
+ if self.is_svn_entries(file):
+ self.svn_parse_file(file)
+ else:
+ self.cvs_parse_file(file)
+
+ def prettyprint(self):
+ "Pretty-print the entire set of entries"
+ for i in self.entry_dict.values():
+ i.prettyprint()
+
+
 
 # The main exported routine
 def get_entries(path):
- "Parse the entries file at PATH and return a list of svn_entry objects."
+ """Parse the entries file at PATH and return a dictionary of entry
+ objects. This can be passed either a CVS or an SVN entries file,
+ and it will do the right thing."""
 
- entryparser = svn_entryparser() # make a parser instance
+ entryparser = svn_entryparser()
   fp = open(path, 'r')
- entryparser.parser.ParseFile(fp)
+ entryparser.parse_file(fp)
   fp.close()
   return entryparser.entry_dict
 
Index: svn_test_main.py
===================================================================
RCS file: /cvs/subversion/subversion/tests/clients/cmdline/svn_test_main.py,v
retrieving revision 1.4
diff -u -r1.4 svn_test_main.py
--- svn_test_main.py 2001/04/16 01:13:40 1.4
+++ svn_test_main.py 2001/04/20 13:00:21
@@ -43,6 +43,8 @@
 
 # Global: set this to the location of the svn binary
 svn_binary = '../../../client/svn'
+# Global: set this to the location of the svnadmin binary
+svnadmin_binary = '../../../svnadmin/svnadmin'
 
 ######################################################################
 # Utilities shared by the tests
@@ -57,7 +59,16 @@
   infile, outfile = os.popen2(command) # run command, get 2 file descriptors
   return outfile.readlines() # convert stdout to list of lines
 
+# For running svnadmin. Ignores the output.
+def run_svnadmin(*varargs):
+ "Run svnadmin with VARARGS."
 
+ command = svnadmin_binary
+ for arg in varargs:
+ command = command + " " + `arg` # build the command string
+ os.popen2(command) # run command
+
+
 # For clearing away working copies
 def remove_wc(dirname):
   "Remove a working copy named DIRNAME."
@@ -72,6 +83,15 @@
   fp = open(path, 'a') # open in (a)ppend mode
   fp.write(new_text)
   fp.close()
+
+# For creating blank new repositories
+def create_repos(path):
+ """Create a brand-new SVN repository at PATH. If PATH does not yet
+ exist, create it."""
+
+ if not(os.path.exists(path)):
+ os.makedirs(path) # this creates all the intermediate dirs, if neccessary
+ run_svnadmin("create", path)
 
 # -- put more shared routines here --
 
Index: xml_tests.py
===================================================================
RCS file: /cvs/subversion/subversion/tests/clients/cmdline/xml_tests.py,v
retrieving revision 1.2
diff -u -r1.2 xml_tests.py
--- xml_tests.py 2001/04/16 22:18:01 1.2
+++ xml_tests.py 2001/04/20 13:00:21
@@ -158,16 +158,63 @@
 
   return 0 # final success
 
+#-----------------------------------------------------------------------------
 
+def xml_test_3():
+ """verify the actual wc from co1-inline.xml - 2"""
+
+ import svn_tree
+
+ wc_dir = 'wc-t3'
+
+ # actually do the check out
+ svn_test_main.run_svn ('co', '-d', wc_dir, \
+ '--xml-file', XML_DIR + '/co1-inline.xml', \
+ '-r 1', ANCESTOR_PATH)
+
+
+ exp_tree = svn_tree.build_tree_from_paths(greek_paths)
+ result_tree = svn_tree.build_tree_from_wc(wc_dir)
+
+ if svn_tree.compare_trees(exp_tree, result_tree):
+ return 0
+ else:
+ return 1
+
+#----------------------------------------------------------------------------
+
+def xml_test_4():
+ """verify that the entries files match the wc"""
+
+ import svn_tree, svn_entry
+
+ wc_dir = 'wc-t4'
+
+ # actually do the checkout
+ svn_test_main.run_svn ('co', '-d', wc_dir, \
+ '--xml-file', XML_DIR + '/co1-inline.xml', \
+ '-r 1', ANCESTOR_PATH)
+
+ exp_tree = svn_tree.build_tree_from_wc(wc_dir)
+ result_tree = svn_tree.build_tree_from_entries(wc_dir)
+
+ if svn_tree.compare_trees(exp_tree, result_tree):
+ return 0
+ else:
+ return 1
+
 ########################################################################
 ## List all tests here, starting with None:
 test_list = [ None,
               xml_test_1,
- xml_test_2
+ xml_test_2,
+ xml_test_3,
+ xml_test_4
              ]
 
-## And run the main test routine on them:
-svn_test_main.client_test(test_list)
+if __name__ == '__main__':
+ ## And run the main test routine on them:
+ svn_test_main.client_test(test_list)
 
 ### End of file.
 # local variables:

           
sam th --- sam_at_uchicago.edu --- http://www.abisource.com/~sam/
OpenPGP Key: CABD33FC --- http://samth.dyndns.org/key
DeCSS: http://samth.dyndns.org/decss

  • application/pgp-signature attachment: stored
Received on Sat Oct 21 14:36:29 2006

This is an archived mail posted to the Subversion Dev mailing list.

This site is subject to the Apache Privacy Policy and the Apache Public Forum Archive Policy.