Author: julianfoad
Date: Mon Mar 19 13:43:41 2018
New Revision: 1827200

URL: http://svn.apache.org/viewvc?rev=1827200&view=rev
Log:
Viewspec: Add regression tests for 'svn-viewspec.py'.

Patch by: Paul Hammant <paul[_AT_]hammant.org>
modified by me

* tools/client-side/svn-viewspec.py
  Prepare for testability: allow passing in a mock 'os.system' object, and
  make main() externally callable.

* tools/client-side/svnviewspec_test.py
  New file, for use with Python 'pytest'.

Added:
    subversion/trunk/tools/client-side/svnviewspec_test.py   (with props)
Modified:
    subversion/trunk/tools/client-side/svn-viewspec.py

Modified: subversion/trunk/tools/client-side/svn-viewspec.py
URL: 
http://svn.apache.org/viewvc/subversion/trunk/tools/client-side/svn-viewspec.py?rev=1827200&r1=1827199&r2=1827200&view=diff
==============================================================================
--- subversion/trunk/tools/client-side/svn-viewspec.py (original)
+++ subversion/trunk/tools/client-side/svn-viewspec.py Mon Mar 19 13:43:41 2018
@@ -121,6 +121,8 @@ DEPTH_FILES      = 'files'
 DEPTH_IMMEDIATES = 'immediates'
 DEPTH_INFINITY   = 'infinity'
 
+os_system = None
+args = None
 
 class TreeNode:
     """A representation of a single node in a Subversion sparse
@@ -280,10 +282,10 @@ def checkout_tree(base_url, revision, tr
     if revision != -1:
         revision_str = "--revision=%d " % (revision)
     if is_top:
-        os.system('svn checkout "%s" "%s" --depth=%s %s'
+        os_system('svn checkout "%s" "%s" --depth=%s %s'
                   % (base_url, target_dir, depth, revision_str))
     else:
-        os.system('svn update "%s" --set-depth=%s %s'
+        os_system('svn update "%s" --set-depth=%s %s'
                   % (target_dir, depth, revision_str))
     child_names = tree_node.children.keys()
     child_names.sort(svn_path_compare_paths)
@@ -304,27 +306,34 @@ def checkout_spec(viewspec, target_dir):
 
 def usage_and_exit(errmsg=None):
     stream = errmsg and sys.stderr or sys.stdout
-    msg = __doc__.replace("__SCRIPTNAME__", os.path.basename(sys.argv[0]))
+    msg = __doc__.replace("__SCRIPTNAME__", os.path.basename(args[0]))
     stream.write(msg)
     if errmsg:
         stream.write("ERROR: %s\n" % (errmsg))
-    sys.exit(errmsg and 1 or 0)
+        return 1
+    return 0
+
+def main(os_sys, args_in):
+    global os_system
+    global args
+    os_system = os_sys
+    args = args_in
 
-def main():
-    argc = len(sys.argv)
+    argc = len(args)
     if argc < 2:
-        usage_and_exit('Not enough arguments.')
-    subcommand = sys.argv[1]
+        return usage_and_exit('Not enough arguments.')
+    subcommand = args[1]
     if subcommand == 'help':
-        usage_and_exit()
+        return usage_and_exit()
     elif subcommand == 'help-format':
         msg = FORMAT_HELP.replace("__SCRIPTNAME__",
-                                  os.path.basename(sys.argv[0]))
+                                  os.path.basename(args[0]))
         sys.stdout.write(msg)
+        return 1
     elif subcommand == 'examine':
         if argc < 3:
-            usage_and_exit('No viewspec file specified.')
-        fp = (sys.argv[2] == '-') and sys.stdin or open(sys.argv[2], 'r')
+            return usage_and_exit('No viewspec file specified.')
+        fp = (args[2] == '-') and sys.stdin or open(args[2], 'r')
         viewspec = parse_viewspec(fp)
         sys.stdout.write("Url: %s\n" % (viewspec.base_url))
         revision = viewspec.revision
@@ -336,13 +345,14 @@ def main():
         viewspec.tree.dump(True)
     elif subcommand == 'checkout':
         if argc < 3:
-            usage_and_exit('No viewspec file specified.')
+            return usage_and_exit('No viewspec file specified.')
         if argc < 4:
-            usage_and_exit('No target directory specified.')
-        fp = (sys.argv[2] == '-') and sys.stdin or open(sys.argv[2], 'r')
-        checkout_spec(parse_viewspec(fp), sys.argv[3])
+            return usage_and_exit('No target directory specified.')
+        fp = (args[2] == '-') and sys.stdin or open(args[2], 'r')
+        checkout_spec(parse_viewspec(fp), args[3])
     else:
-        usage_and_exit('Unknown subcommand "%s".' % (subcommand))
+        return usage_and_exit('Unknown subcommand "%s".' % (subcommand))
 
 if __name__ == "__main__":
-    main()
+    if main(os.system, sys.argv):
+        sys.exit(1)

Added: subversion/trunk/tools/client-side/svnviewspec_test.py
URL: 
http://svn.apache.org/viewvc/subversion/trunk/tools/client-side/svnviewspec_test.py?rev=1827200&view=auto
==============================================================================
--- subversion/trunk/tools/client-side/svnviewspec_test.py (added)
+++ subversion/trunk/tools/client-side/svnviewspec_test.py Mon Mar 19 13:43:41 
2018
@@ -0,0 +1,428 @@
+#!/usr/bin/env python
+#
+#  svnviewspec_test.py:  testing the 'svn-viewspec.py' tool.
+#
+#  Execute these tests using 'pytest' (pytest.org):
+#    py.test svnviewspec_test.py
+#
+#  Subversion is a tool for revision control.
+#  See http://subversion.apache.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.
+######################################################################
+
+
+import sys
+import os.path
+import tempfile
+
+sys.path.append(os.path.abspath(os.path.realpath(__file__)))
+
+svn_viewspec = __import__("svn-viewspec")
+__SCRIPTNAME__ = 'svn-viewspec.py'
+
+
+#########################################################################
+# mock the 'system' call
+os_system_lines = []
+
+def my_os_system(str):
+    os_system_lines.append(str)
+
+def setup_function(function):
+    global os_system_lines
+    os_system_lines = []
+
+#########################################################################
+
+def perform_viewspec(args):
+    return svn_viewspec.main(my_os_system, [__SCRIPTNAME__] + args)
+
+def test_that_viewspec_does_checkout_for_its_own_inline_example(capsys):
+
+    spec = """Format: 1
+Url: http://svn.apache.org/r/a/s
+Revision: 36
+
+trunk/**
+branches/1.5.x/**
+branches/1.6.x/**
+README
+branches/1.4.x/STATUS
+branches/1.4.x/s/tests/cmdline/~
+"""
+
+    perform_viewspec(
+        ["checkout", (make_temp_file_containing(spec)), "a/b"])
+    sys_out_lines, sys_err_lines = capsys.readouterr()
+
+    expected = \
+"""svn checkout "http://svn.apache.org/r/a/s"; "a/b" --depth=empty --revision=36
+svn update "a/b/README" --set-depth=empty --revision=36
+svn update "a/b/branches" --set-depth=empty --revision=36
+svn update "a/b/branches/1.4.x" --set-depth=empty --revision=36
+svn update "a/b/branches/1.4.x/STATUS" --set-depth=empty --revision=36
+svn update "a/b/branches/1.4.x/s" --set-depth=empty --revision=36
+svn update "a/b/branches/1.4.x/s/tests" --set-depth=empty --revision=36
+svn update "a/b/branches/1.4.x/s/tests/cmdline" --set-depth=files --revision=36
+svn update "a/b/branches/1.5.x" --set-depth=infinity --revision=36
+svn update "a/b/branches/1.6.x" --set-depth=infinity --revision=36
+svn update "a/b/trunk" --set-depth=infinity --revision=36"""
+
+    all_the_lines_should_be_the_same(expected, os_system_lines)
+
+    all_the_lines_should_be_the_same([], sys_err_lines)
+    all_the_lines_should_be_the_same([], sys_out_lines)
+
+
+def test_that_viewspec_does_checkout_for_a_tilde_at_root_case(capsys):
+
+    spec = """Format: 1
+Url: http://svn.apache.org/repos/asf/svn
+
+~
+"""
+
+    perform_viewspec(
+        ["checkout", (make_temp_file_containing(spec)), "a/b"])
+    sys_out_lines, sys_err_lines = capsys.readouterr()
+
+    expected = \
+"""svn checkout "http://svn.apache.org/repos/asf/svn"; "a/b" --depth=files"""
+
+    # print ">>>" + "\n".join(os_system_lines) + "<<<"
+
+    all_the_lines_should_be_the_same(expected, os_system_lines)
+
+    all_the_lines_should_be_the_same([], sys_err_lines)
+    all_the_lines_should_be_the_same([], sys_out_lines)
+
+
+def test_that_viewspec_does_checkout_a_splat_at_root_case(capsys):
+
+    spec = """Format: 1
+Url: http://svn.apache.org/r/a/svn
+
+*
+"""
+
+    perform_viewspec(
+        ["checkout", (make_temp_file_containing(spec)), "a/b"])
+    sys_out_lines, sys_err_lines = capsys.readouterr()
+
+    expected = \
+"""svn checkout "http://svn.apache.org/r/a/svn"; "a/b" --depth=immediates"""
+
+    # print ">>>" + "\n".join(os_system_lines) + "<<<"
+
+    all_the_lines_should_be_the_same(expected, os_system_lines)
+
+    all_the_lines_should_be_the_same([], sys_err_lines)
+    all_the_lines_should_be_the_same([], sys_out_lines)
+
+
+def test_that_viewspec_does_checkout_an_infinity_from_root_case(capsys):
+
+    spec = """Format: 1
+Url: http://svn.apache.org/r/a/s
+
+**
+"""
+
+    perform_viewspec(
+        ["checkout", (make_temp_file_containing(spec)), "a/b"])
+    sys_out_lines, sys_err_lines = capsys.readouterr()
+
+    expected = \
+"""svn checkout "http://svn.apache.org/r/a/s"; "a/b" --depth=infinity"""
+
+    # print ">>>" + "\n".join(os_system_lines) + "<<<"
+
+    all_the_lines_should_be_the_same(expected, os_system_lines)
+
+    all_the_lines_should_be_the_same([], sys_err_lines)
+    all_the_lines_should_be_the_same([], sys_out_lines)
+
+
+def 
test_that_viewspec_does_checkout_for_a_splat_n_tilde_in_a_subdir_case(capsys):
+    spec = """Format: 1
+Url: http://svn.apache.org/repos/asf/svn
+
+branches/foo/*
+branches/bar/~
+"""
+
+    perform_viewspec(
+      ["checkout", (make_temp_file_containing(spec)), "a/b"])
+    sys_out_lines, sys_err_lines = capsys.readouterr()
+
+    expected = \
+"""svn checkout "http://svn.apache.org/repos/asf/svn"; "a/b" --depth=empty
+svn update "a/b/branches" --set-depth=empty
+svn update "a/b/branches/bar" --set-depth=files
+svn update "a/b/branches/foo" --set-depth=immediates"""
+
+    all_the_lines_should_be_the_same(expected, os_system_lines)
+
+    all_the_lines_should_be_the_same([], sys_err_lines)
+    all_the_lines_should_be_the_same([], sys_out_lines)
+
+
+def test_that_viewspec_does_examine_for_its_own_inline_example2(capsys):
+
+    spec = """Format: 1
+Url: http://svn.apache.org/repos/asf/subversion
+Revision: 36366
+
+trunk/**
+branches/1.5.x/**
+branches/1.6.x/**
+README
+branches/1.4.x/STATUS
+branches/1.4.x/subversion/tests/cmdline/~
+"""
+
+    perform_viewspec(
+        ["examine", (make_temp_file_containing(spec)), "a/b"])
+    sys_out_lines, sys_err_lines = capsys.readouterr()
+
+    all_the_lines_should_be_the_same([], os_system_lines)
+
+    expected = \
+      """Path:  (depth=empty)
+  Path: README (depth=empty)
+  Path: branches (depth=empty)
+    Path: 1.4.x (depth=empty)
+      Path: STATUS (depth=empty)
+      Path: subversion (depth=empty)
+        Path: tests (depth=empty)
+          Path: cmdline (depth=files)
+    Path: 1.5.x (depth=infinity)
+    Path: 1.6.x (depth=infinity)
+  Path: trunk (depth=infinity)"""
+
+    all_the_lines_should_be_the_same(expected, sys_err_lines)
+
+    expected = \
+      """Url: http://svn.apache.org/repos/asf/subversion
+Revision: 36366
+
+"""
+
+    all_the_lines_should_be_the_same(expected, sys_out_lines)
+
+
+def test_that_viewspec_prints_help(capsys):
+
+    rc = perform_viewspec(["help"])
+    sys_out_lines, sys_err_lines = capsys.readouterr()
+
+    assert 0 == rc
+
+    all_the_lines_should_be_the_same([], sys_err_lines)
+
+    expected = \
+      """__SCRIPTNAME__: checkout utility for sparse Subversion working copies
+
+Usage: 1. __SCRIPTNAME__ checkout VIEWSPEC-FILE TARGET-DIR
+       2. __SCRIPTNAME__ examine VIEWSPEC-FILE
+       3. __SCRIPTNAME__ help
+       4. __SCRIPTNAME__ help-format
+
+VIEWSPEC-FILE is the path of a file whose contents describe a
+Subversion sparse checkouts layout, or '-' if that description should
+be read from stdin.  TARGET-DIR is the working copy directory created
+by this script as it checks out the specified layout.
+
+1. Parse VIEWSPEC-FILE and execute the necessary 'svn' command-line
+   operations to build out a working copy tree at TARGET-DIR.
+
+2. Parse VIEWSPEC-FILE and dump out a human-readable representation of
+   the tree described in the specification.
+
+3. Show this usage message.
+
+4. Show information about the file format this program expects.
+      """.replace("__SCRIPTNAME__", __SCRIPTNAME__)
+
+    all_the_lines_should_be_the_same(expected, sys_out_lines)
+
+
+def test_that_viewspec_prints_unknown_subcommand(capsys):
+
+    rc = perform_viewspec(["helppppppppp"])
+    sys_out_lines, sys_err_lines = capsys.readouterr()
+
+    assert 1 == rc
+
+    all_the_lines_should_be_the_same([], sys_out_lines)
+
+    expected = \
+      """__SCRIPTNAME__: checkout utility for sparse Subversion working copies
+
+Usage: 1. __SCRIPTNAME__ checkout VIEWSPEC-FILE TARGET-DIR
+       2. __SCRIPTNAME__ examine VIEWSPEC-FILE
+       3. __SCRIPTNAME__ help
+       4. __SCRIPTNAME__ help-format
+
+VIEWSPEC-FILE is the path of a file whose contents describe a
+Subversion sparse checkouts layout, or '-' if that description should
+be read from stdin.  TARGET-DIR is the working copy directory created
+by this script as it checks out the specified layout.
+
+1. Parse VIEWSPEC-FILE and execute the necessary 'svn' command-line
+   operations to build out a working copy tree at TARGET-DIR.
+
+2. Parse VIEWSPEC-FILE and dump out a human-readable representation of
+   the tree described in the specification.
+
+3. Show this usage message.
+
+4. Show information about the file format this program expects.
+
+ERROR: Unknown subcommand "helppppppppp".""".replace("__SCRIPTNAME__", 
__SCRIPTNAME__)
+
+    all_the_lines_should_be_the_same(expected, sys_err_lines)
+
+
+def test_that_viewspec_prints_help_format(capsys):
+
+    rc = perform_viewspec(["help-format"])
+    sys_out_lines, sys_err_lines = capsys.readouterr()
+
+    assert 1 == rc
+
+    # print ">>>" + "\n".join(sys_out_lines) + "<<<<"
+
+    expected = \
+      """Viewspec File Format
+====================
+
+The viewspec file format used by this tool is a collection of headers
+(using the typical one-per-line name:value syntax), followed by an
+empty line, followed by a set of one-per-line rules.
+
+The headers must contain at least the following:
+
+    Format   - version of the viewspec format used throughout the file
+    Url      - base URL applied to all rules; tree checkout location
+
+The following headers are optional:
+
+    Revision - version of the tree items to checkout
+
+Following the headers and blank line separator are the path rules.
+The rules are list of URLs -- relative to the base URL stated in the
+headers -- with optional annotations to specify the desired working
+copy depth of each item:
+
+    PATH/**  - checkout PATH and all its children to infinite depth
+    PATH/*   - checkout PATH and its immediate children
+    PATH/~   - checkout PATH and its file children
+    PATH     - checkout PATH non-recursively
+
+By default, the top-level directory (associated with the base URL) is
+checked out with empty depth.  You can override this using the special
+rules '**', '*', and '~' as appropriate.
+
+It is not necessary to explicitly list the parent directories of each
+path associated with a rule.  If the parent directory of a given path
+is not "covered" by a previous rule, it will be checked out with empty
+depth.
+
+Examples
+========
+
+Here's a sample viewspec file:
+
+    Format: 1
+    Url: http://svn.apache.org/repos/asf/subversion
+    Revision: 36366
+
+    trunk/**
+    branches/1.5.x/**
+    branches/1.6.x/**
+    README
+    branches/1.4.x/STATUS
+    branches/1.4.x/subversion/tests/cmdline/~
+
+You may wish to version your viewspec files.  If so, you can use this
+script in conjunction with 'svn cat' to fetch, parse, and act on a
+versioned viewspec file:
+
+    $ svn cat http://svn.example.com/specs/dev-spec.txt |
+         __SCRIPTNAME__ checkout - /path/to/target/directory
+         """.replace("__SCRIPTNAME__", __SCRIPTNAME__)
+
+    all_the_lines_should_be_the_same(expected, sys_out_lines)
+
+    all_the_lines_should_be_the_same([], sys_err_lines)
+
+
+def test_that_viewspec_does_checkout_with_no_revision_specified(capsys):
+
+    spec = """Format: 1
+Url: http://svn.apache.org/repos/asf/svn
+
+trunk/**
+branches/1.5.x/**
+branches/1.6.x/**
+README
+branches/1.4.x/STATUS
+branches/1.4.x/s/tests/cmdline/~
+"""
+
+    perform_viewspec(
+      ["checkout", (make_temp_file_containing(spec)), "a/b"])
+    sys_out_lines, sys_err_lines = capsys.readouterr()
+
+    expected = \
+      """svn checkout "http://svn.apache.org/repos/asf/svn"; "a/b" --depth=empty
+      svn update "a/b/README" --set-depth=empty
+      svn update "a/b/branches" --set-depth=empty
+      svn update "a/b/branches/1.4.x" --set-depth=empty
+      svn update "a/b/branches/1.4.x/STATUS" --set-depth=empty
+      svn update "a/b/branches/1.4.x/s" --set-depth=empty
+      svn update "a/b/branches/1.4.x/s/tests" --set-depth=empty
+      svn update "a/b/branches/1.4.x/s/tests/cmdline" --set-depth=files
+      svn update "a/b/branches/1.5.x" --set-depth=infinity
+      svn update "a/b/branches/1.6.x" --set-depth=infinity
+      svn update "a/b/trunk" --set-depth=infinity  """
+
+    all_the_lines_should_be_the_same(expected, os_system_lines)
+    all_the_lines_should_be_the_same([], sys_err_lines)
+    all_the_lines_should_be_the_same([], sys_out_lines)
+
+
+def all_the_lines_should_be_the_same(expected_lines,
+                                     actual_lines):
+    if not isinstance(expected_lines, list):
+        expected_lines = expected_lines.splitlines()
+    if not isinstance(actual_lines, list):
+        actual_lines = actual_lines.splitlines()
+    assert len(actual_lines) == len(expected_lines)
+    for i, line in enumerate(actual_lines):
+        assert line.strip() == expected_lines[i].strip()
+
+
+def make_temp_file_containing(spec):
+    new_file, filename = tempfile.mkstemp()
+    os.write(new_file, spec)
+    os.close(new_file)
+    return filename

Propchange: subversion/trunk/tools/client-side/svnviewspec_test.py
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: subversion/trunk/tools/client-side/svnviewspec_test.py
------------------------------------------------------------------------------
    svn:executable = *

Propchange: subversion/trunk/tools/client-side/svnviewspec_test.py
------------------------------------------------------------------------------
    svn:mime-type = text/x-python


Reply via email to