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