Author: rinrab
Date: Wed May 28 19:32:09 2025
New Revision: 1925915

URL: http://svn.apache.org/viewvc?rev=1925915&view=rev
Log:
On the 'utf8-cmdline-prototype' branch: sync with trunk@r1925914.

Modified:
    subversion/branches/utf8-cmdline-prototype/   (props changed)
    subversion/branches/utf8-cmdline-prototype/CMakeLists.txt
    subversion/branches/utf8-cmdline-prototype/build/run_tests.py
    subversion/branches/utf8-cmdline-prototype/subversion/include/svn_opt.h
    subversion/branches/utf8-cmdline-prototype/subversion/libsvn_subr/opt.c
    subversion/branches/utf8-cmdline-prototype/subversion/svnlook/svnlook.c
    subversion/branches/utf8-cmdline-prototype/subversion/svnmucc/svnmucc.c
    
subversion/branches/utf8-cmdline-prototype/subversion/tests/cmdline/prop_tests.py
    
subversion/branches/utf8-cmdline-prototype/subversion/tests/cmdline/svnmucc_tests.py
    
subversion/branches/utf8-cmdline-prototype/subversion/tests/cmdline/svntest/actions.py
    
subversion/branches/utf8-cmdline-prototype/subversion/tests/cmdline/svntest/main.py
    
subversion/branches/utf8-cmdline-prototype/subversion/tests/cmdline/svntest/verify.py

Propchange: subversion/branches/utf8-cmdline-prototype/
------------------------------------------------------------------------------
  Merged /subversion/trunk:r1925721-1925914

Modified: subversion/branches/utf8-cmdline-prototype/CMakeLists.txt
URL: 
http://svn.apache.org/viewvc/subversion/branches/utf8-cmdline-prototype/CMakeLists.txt?rev=1925915&r1=1925914&r2=1925915&view=diff
==============================================================================
--- subversion/branches/utf8-cmdline-prototype/CMakeLists.txt (original)
+++ subversion/branches/utf8-cmdline-prototype/CMakeLists.txt Wed May 28 
19:32:09 2025
@@ -83,6 +83,7 @@ cmake_dependent_option(SVN_ENABLE_TOOLS
 option(SVN_ENABLE_TESTS "Build Subversion test-suite" OFF)
 option(SVN_TEST_EXPAND "Expand tests; This will slow-down configuration, but 
you will have an ability to run any subtest" OFF)
 option(SVN_TEST_CONFIGURE_FOR_PARALLEL "Configures tests for parallel run 
execution" OFF)
+set(SVN_TEST_COMMAND_ARGUMENTS "" CACHE STRING "Additional command line 
options to be passed to run_tests.py")
 option(SVN_ENABLE_APACHE_MODULES "Build modules for Apache HTTPD" OFF)
 
 option(SVN_ENABLE_SWIG_PERL "Enable Subversion SWIG bindings for Perl" OFF)
@@ -778,12 +779,28 @@ if(SVN_ENABLE_TESTS)
   find_package(Python3 COMPONENTS Interpreter REQUIRED)
   set(run_tests_script "${CMAKE_CURRENT_SOURCE_DIR}/build/run_tests.py")
   set(list_tests_script "${CMAKE_CURRENT_SOURCE_DIR}/build/list_tests.py")
+  set(test_base_dir "${CMAKE_CURRENT_BINARY_DIR}/Testing")
+
+  # Create the virtual environment for Python tests.
+  execute_process(
+    COMMAND
+      "${Python3_EXECUTABLE}" "${run_tests_script}"
+      --create-python-venv "${test_base_dir}"
+      ${CMAKE_CURRENT_SOURCE_DIR}
+      OUTPUT_VARIABLE command_output
+      WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}"
+      RESULT_VARIABLE command_result
+  )
+  if (command_result)
+    message(FATAL_ERROR "run_tests.py --create-python-venv failed.")
+  endif()
+  string(STRIP "${command_output}" python3_test_executable)
 
   function(add_py_test name prog)
     if(SVN_TEST_CONFIGURE_FOR_PARALLEL)
-      set(test_root "${CMAKE_CURRENT_BINARY_DIR}/Testing/${name}")
+      set(test_root "${test_base_dir}/${name}")
     else()
-      set(test_root "${CMAKE_CURRENT_BINARY_DIR}/Testing")
+      set(test_root "${test_base_dir}")
     endif()
 
     file(MAKE_DIRECTORY "${test_root}/subversion/tests/cmdline")
@@ -792,12 +809,13 @@ if(SVN_ENABLE_TESTS)
       NAME
         "${name}"
       COMMAND
-        "${Python3_EXECUTABLE}" "${run_tests_script}"
+        "${python3_test_executable}" "${run_tests_script}"
         --bin ${binary_dir}
         --tools-bin ${binary_dir}
         --verbose
         --log-to-stdout
         --set-log-level=WARNING
+        ${SVN_TEST_COMMAND_ARGUMENTS}
         ${CMAKE_CURRENT_SOURCE_DIR}
         ${test_root}
         "${prog}"

Modified: subversion/branches/utf8-cmdline-prototype/build/run_tests.py
URL: 
http://svn.apache.org/viewvc/subversion/branches/utf8-cmdline-prototype/build/run_tests.py?rev=1925915&r1=1925914&r2=1925915&view=diff
==============================================================================
--- subversion/branches/utf8-cmdline-prototype/build/run_tests.py (original)
+++ subversion/branches/utf8-cmdline-prototype/build/run_tests.py Wed May 28 
19:32:09 2025
@@ -259,6 +259,8 @@ class TestHarness:
       cmdline.append('--tools-bin=%s' % self.opts.tools_bin)
     if self.opts.svn_bin is not None:
       cmdline.append('--bin=%s' % self.opts.svn_bin)
+    if self.opts.venv_base is not None:
+      cmdline.append('--python-venv=%s' % self.opts.venv_base)
     if self.opts.url is not None:
       cmdline.append('--url=%s' % self.opts.url)
     if self.opts.fs_type is not None:
@@ -326,16 +328,17 @@ class TestHarness:
 
       global svntest
       svntest = importlib.import_module('svntest')
-      extra_packages = svntest.main.ensure_dependencies()
       svntest.main.parse_options(cmdline, optparse.SUPPRESS_USAGE)
       svntest.testcase.TextColors.disable()
+      dependency_path = svntest.main.ensure_dependencies()
 
       # We have to update PYTHONPATH, otherwise the whole setting up of a
       # virtualenv and installing dependencies will happen for every test case.
-      python_path = os.environ.get("PYTHONPATH")
-      python_path = (extra_packages if not python_path
-                     else "%s:%s" % (extra_packages, python_path))
-      os.environ["PYTHONPATH"] = python_path
+      if dependency_path:
+        python_path = os.environ.get("PYTHONPATH")
+        python_path = (dependency_path if not python_path
+                       else "%s:%s" % (dependency_path, python_path))
+        os.environ["PYTHONPATH"] = python_path
     finally:
       os.chdir(old_cwd)
 
@@ -674,6 +677,12 @@ class TestHarness:
       for x in failed_list:
         sys.stdout.write(x)
 
+    xml_error_list = [x for x in log_lines if x[:8] == 'E: XML: ']
+    if xml_error_list:
+      print('There were some XML validation errors, checking' + self.logfile)
+      for x in sorted(set(xml_error_list)):
+        sys.stdout.write(x[3:])
+
     # Print summaries, from least interesting to most interesting.
     if self.opts.list_tests:
       print('Summary of test listing:')
@@ -1032,6 +1041,13 @@ def create_parser():
                     help='Use the svn binaries installed in this path')
   parser.add_option('--tools-bin', action='store', dest='tools_bin',
                     help='Use the svn tools installed in this path')
+  parser.add_option('--create-python-venv', action='store', dest='create_venv',
+                    help=('Create the Python virtual environment inside this'
+                          ' path and install the dependencies used by the'
+                          ' test suite, then exit. Do not run any tests.'))
+  parser.add_option('--python-venv', action='store', dest='venv_base',
+                    help=('Use the virtual environment inside this path to'
+                          ' find the dependencies used by the test suite.'))
   parser.add_option('--fsfs-sharding', action='store', type='int',
                     help='Default shard size (for fsfs)')
   parser.add_option('--fsfs-packing', action='store_true',
@@ -1097,12 +1113,22 @@ def create_parser():
 
 def main():
   (opts, args) = create_parser().parse_args(sys.argv[1:])
+  if opts.create_venv:
+    main_create_venv(opts, args)
+    sys.exit(0)
+
+  # Normal mode: don't create a virtual environment, run tests or whatever
+  # else was requested instead. Create the virtual environment on demand.
+  assert not opts.create_venv
 
   if len(args) < 3:
     print("{}: at least three positional arguments required; got {!r}".format(
       os.path.basename(sys.argv[0]), args
     ))
     sys.exit(2)
+  abs_srcdir = args[0]
+  abs_builddir = args[1]
+  programs = args[2:]
 
   if opts.log_to_stdout:
     logfile = None
@@ -1111,11 +1137,30 @@ def main():
     logfile = os.path.abspath('tests.log')
     faillogfile = os.path.abspath('fails.log')
 
-  th = TestHarness(args[0], args[1], logfile, faillogfile, opts)
-  failed = th.run(args[2:])
+  th = TestHarness(abs_srcdir, abs_builddir, logfile, faillogfile, opts)
+  failed = th.run(programs)
   if failed:
     sys.exit(1)
 
+def main_create_venv(opts, args):
+  # Environment creation mode: create the requested virtual environment,
+  # install required dependencies and exit.
+  assert opts.create_venv
+
+  if len(args) < 1:
+    print("{}: at least one positional argument required; got {!r}".format(
+      os.path.basename(sys.argv[0]), args
+    ))
+    sys.exit(2)
+  abs_srcdir = args[0]
+
+  sys.path.insert(0, os.path.join(abs_srcdir, "subversion", "tests", 
"cmdline"))
+  svntest = importlib.import_module("svntest")
+  svntest.main.venv_base = opts.create_venv
+  venv_dir = svntest.main.venv_path()
+  python_prog, _ = svntest.main.create_python_venv(venv_dir, quiet=True)
+  print(python_prog)
+
 
 # Run main if not imported as a module
 if __name__ == '__main__':

Modified: 
subversion/branches/utf8-cmdline-prototype/subversion/include/svn_opt.h
URL: 
http://svn.apache.org/viewvc/subversion/branches/utf8-cmdline-prototype/subversion/include/svn_opt.h?rev=1925915&r1=1925914&r2=1925915&view=diff
==============================================================================
--- subversion/branches/utf8-cmdline-prototype/subversion/include/svn_opt.h 
(original)
+++ subversion/branches/utf8-cmdline-prototype/subversion/include/svn_opt.h Wed 
May 28 19:32:09 2025
@@ -482,6 +482,24 @@ typedef struct svn_opt_revision_range_t
   svn_opt_revision_t end;
 } svn_opt_revision_range_t;
 
+
+/**
+ * Parse NULL-terminated C string @a str as a revision number and
+ * store its value in @a rev.
+ *
+ * If @a str is not a valid revision number, then the error
+ * #SVN_ERR_REVNUM_PARSE_FAILURE error is returned.  Negative numbers
+ * parsed from @a str are considered invalid, and result in the same error.
+ *
+ * Unlike svn_revnum_parse(), this function support our cmdline revision
+ * number format, whereas the revnum may be prefixed with an 'r' symbol.
+ *
+ * @since New in 1.15
+ * @see svn_revnum_parse()
+ */
+svn_error_t *
+svn_opt_parse_revnum(svn_revnum_t *rev, const char *str);
+
 /**
  * Set @a *start_revision and/or @a *end_revision according to @a arg,
  * where @a arg is "N" or "N:M", like so:
@@ -553,9 +571,10 @@ svn_opt_parse_revision_to_range(apr_arra
  *
  * @since New in 1.15.
  */
-int svn_opt_parse_change_to_range(apr_array_header_t *opt_ranges,
-                                  const char *arg,
-                                  apr_pool_t *result_pool);
+int
+svn_opt_parse_change_to_range(apr_array_header_t *opt_ranges,
+                              const char *arg,
+                              apr_pool_t *result_pool);
 
 /**
  * Resolve peg revisions and operational revisions in the following way:

Modified: 
subversion/branches/utf8-cmdline-prototype/subversion/libsvn_subr/opt.c
URL: 
http://svn.apache.org/viewvc/subversion/branches/utf8-cmdline-prototype/subversion/libsvn_subr/opt.c?rev=1925915&r1=1925914&r2=1925915&view=diff
==============================================================================
--- subversion/branches/utf8-cmdline-prototype/subversion/libsvn_subr/opt.c 
(original)
+++ subversion/branches/utf8-cmdline-prototype/subversion/libsvn_subr/opt.c Wed 
May 28 19:32:09 2025
@@ -710,6 +710,16 @@ int svn_opt_parse_change_to_range(apr_ar
 }
 
 svn_error_t *
+svn_opt_parse_revnum(svn_revnum_t *rev, const char *str)
+{
+  /* Allow any number of 'r's to prefix a revision number. */
+  while (*str == 'r')
+    str++;
+
+  return svn_error_trace(svn_revnum_parse(rev, str, NULL));
+}
+
+svn_error_t *
 svn_opt_resolve_revisions(svn_opt_revision_t *peg_rev,
                           svn_opt_revision_t *op_rev,
                           svn_boolean_t is_url,

Modified: 
subversion/branches/utf8-cmdline-prototype/subversion/svnlook/svnlook.c
URL: 
http://svn.apache.org/viewvc/subversion/branches/utf8-cmdline-prototype/subversion/svnlook/svnlook.c?rev=1925915&r1=1925914&r2=1925915&view=diff
==============================================================================
--- subversion/branches/utf8-cmdline-prototype/subversion/svnlook/svnlook.c 
(original)
+++ subversion/branches/utf8-cmdline-prototype/subversion/svnlook/svnlook.c Wed 
May 28 19:32:09 2025
@@ -2528,7 +2528,7 @@ sub_main(int *exit_code,
       switch (opt_id)
         {
         case 'r':
-          SVN_ERR(svn_revnum_parse(&opt_state.rev, utf8_opt_arg, NULL));
+          SVN_ERR(svn_opt_parse_revnum(&opt_state.rev, utf8_opt_arg));
           break;
 
         case 't':

Modified: 
subversion/branches/utf8-cmdline-prototype/subversion/svnmucc/svnmucc.c
URL: 
http://svn.apache.org/viewvc/subversion/branches/utf8-cmdline-prototype/subversion/svnmucc/svnmucc.c?rev=1925915&r1=1925914&r2=1925915&view=diff
==============================================================================
--- subversion/branches/utf8-cmdline-prototype/subversion/svnmucc/svnmucc.c 
(original)
+++ subversion/branches/utf8-cmdline-prototype/subversion/svnmucc/svnmucc.c Wed 
May 28 19:32:09 2025
@@ -429,7 +429,7 @@ log_message_func(const char **log_msg,
       svn_string_t *message = svn_string_create(lmb->log_message, pool);
 
       SVN_ERR_W(svn_subst_translate_string2(&message, NULL, NULL,
-                                            message, NULL, FALSE,
+                                            message, "UTF-8", FALSE,
                                             pool, pool),
                 _("Error normalizing log message to internal format"));
 
@@ -576,6 +576,7 @@ sub_main(int *exit_code,
             const char *filename;
             SVN_ERR(svn_utf_cstring_to_utf8(&filename, arg, pool));
             SVN_ERR(svn_stringbuf_from_file2(&filedata, filename, pool));
+            SVN_ERR(svn_utf_stringbuf_to_utf8(&filedata, filedata, pool));
           }
           break;
         case 'u':
@@ -595,19 +596,7 @@ sub_main(int *exit_code,
           root_url = sanitize_url(root_url, pool);
           break;
         case 'r':
-          {
-            const char *saved_arg = arg;
-            char *digits_end = NULL;
-            while (*arg == 'r')
-              arg++;
-            base_revision = strtol(arg, &digits_end, 10);
-            if ((! SVN_IS_VALID_REVNUM(base_revision))
-                || (! digits_end)
-                || *digits_end)
-              return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
-                                       _("Invalid revision number '%s'"),
-                                       saved_arg);
-          }
+          SVN_ERR(svn_opt_parse_revnum(&base_revision, arg));
           break;
         case with_revprop_opt:
           SVN_ERR(svn_utf_cstring_to_utf8(&opt_arg, arg, pool));
@@ -774,7 +763,7 @@ sub_main(int *exit_code,
 
   lmb.non_interactive = non_interactive;
   lmb.ctx = ctx;
-    /* Make sure we have a log message to use. */
+  /* Make sure we have a log message to use. */
   SVN_ERR(sanitize_log_sources(&lmb.log_message, message, revprops, filedata,
                                pool, pool));
 
@@ -822,23 +811,11 @@ sub_main(int *exit_code,
       if (action->action == ACTION_CP)
         {
           const char *rev_str = APR_ARRAY_IDX(action_args, i, const char *);
-          if (strcmp(rev_str, "head") == 0)
-            action->rev = SVN_INVALID_REVNUM;
-          else if (strcmp(rev_str, "HEAD") == 0)
+          if (svn_cstring_casecmp(rev_str, "head") == 0)
             action->rev = SVN_INVALID_REVNUM;
           else
-            {
-              char *end;
-
-              while (*rev_str == 'r')
-                ++rev_str;
+            SVN_ERR(svn_opt_parse_revnum(&action->rev, rev_str));
 
-              action->rev = strtol(rev_str, &end, 0);
-              if (*end)
-                return svn_error_createf(SVN_ERR_INCORRECT_PARAMS, NULL,
-                                         "'%s' is not a revision\n",
-                                         rev_str);
-            }
           if (++i == action_args->nelts)
             return insufficient();
         }

Modified: 
subversion/branches/utf8-cmdline-prototype/subversion/tests/cmdline/prop_tests.py
URL: 
http://svn.apache.org/viewvc/subversion/branches/utf8-cmdline-prototype/subversion/tests/cmdline/prop_tests.py?rev=1925915&r1=1925914&r2=1925915&view=diff
==============================================================================
--- 
subversion/branches/utf8-cmdline-prototype/subversion/tests/cmdline/prop_tests.py
 (original)
+++ 
subversion/branches/utf8-cmdline-prototype/subversion/tests/cmdline/prop_tests.py
 Wed May 28 19:32:09 2025
@@ -2640,6 +2640,9 @@ def xml_unsafe_author(sbox):
                                      wc_dir)
 
 @Issue(4415)
+@Issue(4919)
+@XFail(lambda: (svntest.main.is_bad_xml_fatal()
+                and not svntest.main.is_ra_type_dav()))
 def xml_unsafe_author2(sbox):
   "svn:author with XML unsafe chars 2"
 
@@ -2666,6 +2669,13 @@ def xml_unsafe_author2(sbox):
     expected_author = 'foo\bbar'
 
   # Use svn ls in --xml mode to test locale independent output.
+  # FIXME: Theat literal \b in the author field is invalid XML.
+  #        Should be encoded as a character entity and enclosed
+  #        in a CDATA section, like this:
+  #
+  #            <[CDATA[foo@#08;bar]]>
+  #        or
+  #            foo<[CDATA[@#08;]]>bar
   expected_output = [
     '<?xml version="1.0" encoding="UTF-8"?>\n',
     '<lists>\n',
@@ -2694,8 +2704,8 @@ def xml_unsafe_author2(sbox):
     '</lists>\n'
     ]
 
-  svntest.actions.run_and_verify_svn(expected_output, [],
-                                     'ls', '--xml', repo_url)
+  svntest.actions.run_and_verify_svn_xml(expected_output, [],
+                                         'list', '--xml', repo_url)
 
   expected_info = [{
       'Repository Root' : sbox.repo_url,

Modified: 
subversion/branches/utf8-cmdline-prototype/subversion/tests/cmdline/svnmucc_tests.py
URL: 
http://svn.apache.org/viewvc/subversion/branches/utf8-cmdline-prototype/subversion/tests/cmdline/svnmucc_tests.py?rev=1925915&r1=1925914&r2=1925915&view=diff
==============================================================================
--- 
subversion/branches/utf8-cmdline-prototype/subversion/tests/cmdline/svnmucc_tests.py
 (original)
+++ 
subversion/branches/utf8-cmdline-prototype/subversion/tests/cmdline/svnmucc_tests.py
 Wed May 28 19:32:09 2025
@@ -313,7 +313,7 @@ def basic_svnmucc(sbox):
 
   # Expected missing revision error
   xtest_svnmucc(sbox.repo_url,
-                ["svnmucc: E200004: 'a' is not a revision"
+                ["svnmucc: E200022: Invalid revision number found parsing 'a'"
                  ], #---------
                 '-m', 'log msg',
                 'cp', 'a', 'b')

Modified: 
subversion/branches/utf8-cmdline-prototype/subversion/tests/cmdline/svntest/actions.py
URL: 
http://svn.apache.org/viewvc/subversion/branches/utf8-cmdline-prototype/subversion/tests/cmdline/svntest/actions.py?rev=1925915&r1=1925914&r2=1925915&view=diff
==============================================================================
--- 
subversion/branches/utf8-cmdline-prototype/subversion/tests/cmdline/svntest/actions.py
 (original)
+++ 
subversion/branches/utf8-cmdline-prototype/subversion/tests/cmdline/svntest/actions.py
 Wed May 28 19:32:09 2025
@@ -797,11 +797,11 @@ def run_and_verify_log_xml(expected_log_
   # We'll parse the output unless the caller specifies expected_stderr or
   # expected_stdout for run_and_verify_svn.
   parse = True
-  if expected_stderr == None:
+  if expected_stderr is None:
     expected_stderr = []
   else:
     parse = False
-  if expected_stdout != None:
+  if expected_stdout is not None:
     parse = False
 
   log_args = list(args)
@@ -813,6 +813,7 @@ def run_and_verify_log_xml(expected_log_
     'log', '--xml', *log_args)
   if not parse:
     return
+  verify.validate_xml_schema('log', stdout)
 
   entries = LogParser().parse(stdout)
   for index in range(len(entries)):
@@ -1647,9 +1648,9 @@ def run_and_verify_status_xml(expected_e
 
   exit_code, output, errput = run_and_verify_svn(None, [],
                                                  'status', '--xml', *args)
-
   if len(errput) > 0:
     raise Failure
+  verify.validate_xml_schema('status', output)
 
   doc = parseString(''.join(output))
   entries = doc.getElementsByTagName('entry')
@@ -1726,6 +1727,7 @@ def run_and_verify_inherited_prop_xml(pa
 
   if len(errput) > 0:
     raise Failure
+  ## FIXME: Need XML schema: verify.validate_xml_schema('props', output)
 
   # Props inherited from within the WC are keyed on absolute paths.
   expected_iprops = {}
@@ -1798,10 +1800,10 @@ def run_and_verify_diff_summarize_xml(er
                                                  'diff', '--summarize',
                                                  '--xml', *args)
 
-
   # Return if errors are present since they were expected
   if len(errput) > 0:
     return
+  verify.validate_xml_schema('diff', output)
 
   doc = parseString(''.join(output))
   paths = doc.getElementsByTagName("path")

Modified: 
subversion/branches/utf8-cmdline-prototype/subversion/tests/cmdline/svntest/main.py
URL: 
http://svn.apache.org/viewvc/subversion/branches/utf8-cmdline-prototype/subversion/tests/cmdline/svntest/main.py?rev=1925915&r1=1925914&r2=1925915&view=diff
==============================================================================
--- 
subversion/branches/utf8-cmdline-prototype/subversion/tests/cmdline/svntest/main.py
 (original)
+++ 
subversion/branches/utf8-cmdline-prototype/subversion/tests/cmdline/svntest/main.py
 Wed May 28 19:32:09 2025
@@ -36,6 +36,7 @@ import xml
 import urllib
 import logging
 import hashlib
+import importlib
 import zipfile
 import codecs
 import queue
@@ -222,11 +223,13 @@ work_dir = "svn-test-work"
 
 # Directory for the Python virtual environment where we install
 # external dependencies of the test environment
-venv_dir = os.path.join(work_dir, "__venv__")
+venv_base = work_dir
+venv_path = lambda: os.path.join(venv_base, "__venv__")
+venv_create = True
 
 # List of dependencies
+found_dependencies = set()
 SVN_TESTS_REQUIRE = ["lxml", "rnc2rng"]
-dependencies_ensured = False
 
 # Constant for the merge info property.
 SVN_PROP_MERGEINFO = "svn:mergeinfo"
@@ -1753,6 +1756,13 @@ def is_httpd_authz_provider_enabled():
 def is_remote_http_connection_allowed():
   return options.allow_remote_http_connection
 
+# XML schema validation
+def is_bad_xml_fatal():
+  """Are we treating invalid XML output as a fatal error?"""
+  # Only if we have all the necessary dependencies.
+  return {'lxml', 'rnc2rnd'} & found_dependencies
+
+
 def wc_format(ver=None):
   """Return the WC format number used by Subversion version VER.
 
@@ -1856,6 +1866,8 @@ class TestSpawningThread(threading.Threa
       args.append('--allow-remote-http-connection')
     if options.svn_bin:
       args.append('--bin=' + options.svn_bin)
+    if options.venv_base:
+      args.append('--python-venv=' + options.venv_base)
     if options.store_pristine:
       args.append('--store-pristine=' + options.store_pristine)
     if options.valgrind:
@@ -2230,6 +2242,9 @@ def _create_parser(usage=None):
                     help='Whether to clean up')
   parser.add_option('--enable-sasl', action='store_true',
                     help='Whether to enable SASL authentication')
+  parser.add_option('--python-venv', action='store', dest='venv_base',
+                    help=('Use the virtual environment inside this path to'
+                          ' find the dependencies used by the test suite.'))
   parser.add_option('--bin', action='store', dest='svn_bin',
                     help='Use the svn binaries installed in this path')
   parser.add_option('--use-jsvn', action='store_true',
@@ -2336,6 +2351,8 @@ def parse_options(arglist=sys.argv[1:],
   """Parse the arguments in arg_list, and set the global options object with
      the results"""
 
+  global venv_base
+  global venv_create
   global options
 
   parser = _create_parser(usage)
@@ -2385,7 +2402,9 @@ def parse_options(arglist=sys.argv[1:],
                     svn_wc__max_supported_format_version(),
                     options.wc_format_version))
 
-  pass
+  if options.venv_base:
+    venv_base = options.venv_base
+    venv_create = False
 
   return (parser, args)
 
@@ -2417,7 +2436,6 @@ def run_tests(test_list, serial_only = F
         appropriate exit code.
   """
 
-  ensure_dependencies()
   sys.exit(execute_tests(test_list, serial_only))
 
 def ensure_dependencies():
@@ -2428,18 +2446,42 @@ def ensure_dependencies():
         upgrade the venv in that case. In practice, we won't.
   """
 
-  global dependencies_ensured
-  if dependencies_ensured:
-    return
-
+  venv_dir = os.path.abspath(venv_path())
   package_path = os.path.join(venv_dir, "lib",
                               "python%d.%d" % sys.version_info[:2],
                               "site-packages")
-  package_path = os.path.abspath(package_path)
-  if package_path in sys.path:
-    dependencies_ensured = True
-    return
 
+  # Check if all our dependencies are installed. It doesn't matter if
+  # they're installed in our venv, as long as they're available.
+  found_dependencies.clear()
+  saved_sys_path = sys.path[:]
+  try:
+    sys.path.insert(0, package_path)
+    for package in SVN_TESTS_REQUIRE:
+      importlib.import_module(package)
+      found_dependencies.add(package)
+    have_required = True
+  except ImportError:
+    have_required = False
+  finally:
+    sys.path[:] = saved_sys_path
+
+  if have_required:
+    if package_path not in sys.path:
+      sys.path.append(package_path)
+    return package_path
+
+  if venv_create:
+    python_prog, python_path = create_python_venv(venv_dir)
+    if python_prog is not None:
+      assert python_path == package_path
+      if package_path not in sys.path:
+        sys.path.append(package_path)
+      found_dependencies.update(set(SVN_TESTS_REQUIRE))
+      return package_path
+  return None
+
+def create_python_venv(venv_dir, quiet=False):
   try:
     # Create the virtual environment
     if not os.path.isdir(venv_dir):
@@ -2447,19 +2489,22 @@ def ensure_dependencies():
         safe_rmtree(venv_dir)
       venv.create(venv_dir, with_pip=True)
 
-    # Install any (new) dependencies
-    pip = os.path.join(venv_dir, venv_bin, "pip"+_exe)
+    # Install the dependencies
+    pip = os.path.join(venv_dir, venv_bin, "pip" + _exe)
     pip_options = ("--disable-pip-version-check", "--require-virtualenv")
     subprocess.run([pip, *pip_options, "install", *SVN_TESTS_REQUIRE],
-                   check=True)
+                   check=True, stdout=subprocess.PIPE if quiet else None)
+    importlib.invalidate_caches()
 
-    sys.path.append(package_path)
-    dependencies_ensured = True
-    return package_path
-  except Exception as ex:
-    print("WARNING: Could not install test dependencies,"
-          " some tests will be skipped", file=sys.stderr)
-    print(ex, file=sys.stderr)
+    python_prog = os.path.join(venv_dir, venv_bin, "python" + _exe)
+    python_path = os.path.join(venv_dir, "lib",
+                               "python%d.%d" % sys.version_info[:2],
+                               "site-packages")
+    return python_prog, python_path
+  except Exception:
+    if logger:
+      logger.warning('Could not install test dependencies', exc_info=True)
+    return None, None
 
 def get_issue_details(issue_numbers):
   """For each issue number in ISSUE_NUMBERS query the issue
@@ -2659,6 +2704,10 @@ def execute_tests(test_list, serial_only
     wc_incomplete_tester_binary = os.path.join(options.tools_bin,
                                                'wc-incomplete-tester' + _exe)
 
+  assert options.venv_base is None or venv_base == options.venv_base, \
+    'venv_base=%s options.venv_base=%s' % (venv_base, options.venv_base)
+  ensure_dependencies()
+
   ######################################################################
 
   # Cleanup: if a previous run crashed or interrupted the python

Modified: 
subversion/branches/utf8-cmdline-prototype/subversion/tests/cmdline/svntest/verify.py
URL: 
http://svn.apache.org/viewvc/subversion/branches/utf8-cmdline-prototype/subversion/tests/cmdline/svntest/verify.py?rev=1925915&r1=1925914&r2=1925915&view=diff
==============================================================================
--- 
subversion/branches/utf8-cmdline-prototype/subversion/tests/cmdline/svntest/verify.py
 (original)
+++ 
subversion/branches/utf8-cmdline-prototype/subversion/tests/cmdline/svntest/verify.py
 Wed May 28 19:32:09 2025
@@ -1062,8 +1062,14 @@ def validate_xml_schema(name: str, lines
     source = ''.join(lines)
     document = etree.parse(BytesIO(source.encode("utf-8")))
     if not schema.validate(document):
-      raise SVNXMLSchemaValidationError("schema %s" % schema_name)
-  except Exception:
-    print("ERROR: XML output does not conform to schema", schema_name)
-    print(source)
-    raise
+      raise SVNXMLSchemaValidationError(schema.error_log)
+  except ImportError:
+    logger.error("XML: Module lxml.etree not found")
+    return
+  except Exception as ex:
+    logger.error("XML: " + str(ex))
+    logger.warning("XML:\n" + "\n".join(repr(line) for line in lines))
+    if svntest.main.is_bad_xml_fatal():
+      raise
+    else:
+      logger.warning("XML:", exc_info=True)


Reply via email to